Point groups have the concept of a query that controls which points are included or excluded in the group. The Civil 3D UI exposes this functionality through the “Point Group Properties” dialog. You probably know there are four tabs in the dialog that are involved with point selection. The “Point Groups” tab allows you to specify other point groups to be included in the current group. The “Include” and “Exclude” tabs provide a simple interface to include and exclude points from the group based on characteristics of their properties. Finally, the “Query Builder” tab allows creating more advance queries by specifying properties and operators to build a query string.
We spent quite some time thinking how this functionality should be exposed through the API. Our goal was to provide something simple, yet powerful, so our users can create and modify queries programmatically, and we tried to eliminate as much complexity as possible. The result is an API that should allow you to accomplish the results you need and that it is easy to use.
In my previous post, I created some point groups and used a ‘StandarPointGroupQuery’ object to set the query of the group. The ‘StandardPointGroupQuery’ object exposes the functionality included in the first three tabs we discussed in the “Point Group Properties” dialog (Point Groups, Include, and Exclude). Most Civil 3D users utilize these tabs to create their point selections and only the most adventurous wonder around the “Query Builder” tab. Through the API, the usage might be different, but a ‘StandardPointGroupQuery’ should be enough for most queries, and it might be easier to use. The following code shows how a ‘StandardPointGroupQuery’ object is created and how their properties are set.
private void createStandardPointGroup()
StandardPointGroupQuery standard =
standard.IncludeElevations = ">100.5";
standard.IncludeFullDescriptions = "Contains*";
standard.IncludeNames = "Tree Cedar*";
standard.IncludeNumbers = "<1000";
standard.IncludeRawDescriptions = "TREE*";
standard.ExcludeElevations = ">300.0";
standard.ExcludeFullDescriptions = "Contains POLE*";
standard.ExcludeNames = "Tree maples*";
standard.ExcludeNumbers = "<200";
standard.ExcludeRawDescriptions = "POLE*";
createPointGroup("Standard Group", standard);
Private Sub createStandardPointGroup()
Dim standard As New StandardPointGroupQuery()
standard.IncludeElevations = ">100.5"
standard.IncludeFullDescriptions = "Contains*"
standard.IncludeNames = "Tree Cedar*"
standard.IncludeNumbers = "<1000"
standard.IncludeRawDescriptions = "TREE*"
standard.ExcludeElevations = ">300.0"
standard.ExcludeFullDescriptions = "Contains POLE*"
standard.ExcludeNames = "Tree maples*"
standard.ExcludeNumbers = "<200"
standard.ExcludeRawDescriptions = "POLE*"
createPointGroup("Standard Group", standard)
The ‘StandardPointGroupQuery’ also exposes a ‘PointGroups’ property of type ‘IList<string>’ that sets or gets the included point groups in the group. As you can see in the following code, we can set the query by invoking the ‘SetQuery()’ method of the point group. In a similar way, you can access the query object by invoking ‘GetQuery()’.
private void createPointGroup(string name, PointGroupQuery query)
ObjectId groupId = _pointGroups.Add(name);
PointGroup group = groupId.GetObject(OpenMode.ForRead)
Private Sub createPointGroup(name As String, query As PointGroupQuery)
If _pointGroups.Contains(name) Then
Dim groupId As ObjectId = _pointGroups.Add(name)
Dim group As PointGroup = TryCast(groupId.GetObject(OpenMode.ForRead),
As you probably figured out, the ‘PointGroups’ property exposes the functionality in the “Point Groups” tab, the ‘Include*’ properties match the “Include” tab, and the ‘Exclude*’ properties the ‘Exclude’ tab, which means that the syntax supported through the UI is also supported through the ‘StandardPointGroupQuery’ object in the API.
Some times, you will need more control over the queries. Civil 3D provides the “Query Builder” feature for those cases, and through the API, we provide the ‘CustomPointGroupQuery’ object. We explored different design alternatives for the ‘CustomPointGroupQuery’ object due to its complexity. At the end, we chose the design that fits better when working with code and manipulating the query programmatically.
We created a simple query language that it is parsed to generate the desired query. There are no complex keywords to learn just field names and operators, but the language is powerful enough to allow you to create any type of query. The following code shows some of the concepts in the language.
private void createCustomPointGroup()
string queryString =
@"(PointNumber>=100 AND PointNumber<200) OR
(FullDescription='Contains*' OR RawDescription='WE*')";
CustomPointGroupQuery custom = new CustomPointGroupQuery();
custom.QueryString = queryString;
createPointGroup("Custom Group", custom);
Private Sub createCustomPointGroup()
Dim queryString As String = "(PointNumber>=100 AND PointNumber<200) OR " _
& vbCr & vbLf & "(FullDescription='Contains*' OR RawDescription='WE*')"
Dim customQuery As New CustomPointGroupQuery()
customQuery.QueryString = queryString
createPointGroup("Custom Group", customQuery)
The query language is very intuitive and nothing to be scared about. Each of the supported fields has a name and each statement should be composed of a field name, a comparison operator, and a value. You can group statements using the logical operators ‘AND’ and ‘OR’, or you can negate a statement using the ‘NOT’ operator. Evaluation can be specified by the use of parenthesis. As you can see, the rules are similar to other programming/query languages avoiding complex keywords because all we are doing is “selecting” points.
When we first designed the language, we treated all the values as strings. The “Query Builder” UI does this, which allows you to set the value of the “Point Number” field to something like “1-100” to specified a range. At the end, we decided against this approach, mainly for a couple of reasons.
Treating numeric values in the language as strings is inconsistent with other programming/query languages. We believe this would have confused our users, who are used to write code.
Also, it would make the language less translatable to a real query language. If in the future if we decide to store the points in a database, we will have to make many changes to the parser to translate from our custom language to, let’s say, SQL.
Therefore, we decided to treat numeric values as their own type, which means that “point numbers” are unsigned integers and “point elevations” are doubles. If you need to specified the range above, you will use a query like “PointNumber>=1 AND PointNumber<=100”.
Building Query Strings
To simplify the process of building custom queries dynamically, we provide a ‘PointGroupQueryToken’ class/structure that exposes static read-only properties that provide the string values that can be used in queries to specify field names, operators, parenthesis, and delimiters.
If you downloaded the entire source, go ahead and run the “CDS_CreatePointGroupsWithQueries”. After the command runs, you should see the following results.
The “Include” tab of the “Standard Group” shows the values we set in the ‘StandardPointGroupQuery’ object. In the same way, the “Exclude” tab matches the values we set.
Looking at the properties for “Custom Group”, we can see the “Query Builder” tab shows the values of our query. Notice how the “Modify query” option is selected because we are using a ‘CustomPointGroupQuery’.