| name | c3d-parcels |
| description | Sites, parcels, topology, subdivision, segments, labels, renumbering |
Civil 3D Parcels
Use this skill when creating, modifying, or querying parcels and sites, working with parcel segments and topology, or configuring parcel styles and labels.
Sites
Sites are containers that enforce topology between parcels and alignments. All parcels must belong to a site.
CivilDocument doc = CivilApplication.ActiveDocument;
ObjectIdCollection siteIds = doc.GetSiteIds();
foreach (ObjectId siteId in siteIds)
{
Site site = ts.GetObject(siteId, OpenMode.ForRead) as Site;
ed.WriteMessage("Site: {0}\n", site.Name);
ObjectIdCollection parcelIds = site.GetParcelIds();
ed.WriteMessage(" Parcels: {0}\n", parcelIds.Count);
ObjectIdCollection alignIds = site.GetAlignmentIds();
ed.WriteMessage(" Alignments: {0}\n", alignIds.Count);
ObjectIdCollection flIds = site.GetFeatureLineIds();
ed.WriteMessage(" Feature Lines: {0}\n", flIds.Count);
}
Creating a Site
ObjectId siteId = Site.Create(doc, "My Subdivision");
Site site = ts.GetObject(siteId, OpenMode.ForWrite) as Site;
site.Description = "Phase 1 residential lots";
Accessing Parcels
Site site = ts.GetObject(siteId, OpenMode.ForRead) as Site;
ObjectIdCollection parcelIds = site.GetParcelIds();
foreach (ObjectId parcelId in parcelIds)
{
Parcel parcel = ts.GetObject(parcelId, OpenMode.ForRead) as Parcel;
ed.WriteMessage("Parcel: {0}, Number: {1}, Area: {2:F2}\n",
parcel.Name, parcel.Number, parcel.Area);
}
Parcel Properties
Parcel parcel = ts.GetObject(parcelId, OpenMode.ForWrite) as Parcel;
double area = parcel.Area;
double perimeter = parcel.Perimeter;
parcel.Name = "Lot 15";
parcel.Number = 15;
parcel.Address = "123 Main St";
parcel.TaxId = "R-2026-0015";
parcel.LandUse = "Residential";
parcel.StyleId = parcelStyleId;
Creating Parcels from Objects
ObjectIdCollection entityIds = new ObjectIdCollection();
entityIds.Add(polylineId1);
entityIds.Add(polylineId2);
Site site = ts.GetObject(siteId, OpenMode.ForWrite) as Site;
site.CreateParcelsFromObjects(entityIds, parcelStyleId, labelStyleId);
Subdivision
Layout tools create parcels using subdivision algorithms. Civil 3D supports three modes:
- Frontage — subdivides a parent parcel along a frontage line (road alignment), constrained by minimum width and area
- Sliding Angle — sweeps lot lines at a specified angle from the frontage, constrained by minimum area/frontage and side (left or right)
- Free Form — manually defined boundaries within a parent parcel
Note: Subdivision methods are primarily interactive (wizard-driven in the Civil 3D UI). The .NET API supports creation from existing geometry via CreateParcelsFromObjects. For complex subdivision scenarios, use SendStringToExecute to invoke built-in subdivision commands.
Parcel Segments
Parcel boundaries are composed of ParcelSegment objects (lines and curves):
Parcel parcel = ts.GetObject(parcelId, OpenMode.ForRead) as Parcel;
int segmentCount = parcel.Segments.Count;
foreach (ParcelSegment segment in parcel.Segments)
{
ed.WriteMessage("Segment: Start=({0:F2},{1:F2}), End=({2:F2},{3:F2})\n",
segment.StartPoint.X, segment.StartPoint.Y,
segment.EndPoint.X, segment.EndPoint.Y);
if (segment.IsCurve)
{
ed.WriteMessage(" Curve: Radius={0:F2}, ArcLength={1:F2}\n",
segment.Radius, segment.Length);
}
else
{
ed.WriteMessage(" Line: Bearing={0}, Length={1:F2}\n",
segment.Direction, segment.Length);
}
}
Parcel Topology
Parcels in a site maintain topological relationships. When boundaries are shared, modifying one parcel automatically updates its neighbors.
Adjacency and Parent/Child
Parcel parcel = ts.GetObject(parcelId, OpenMode.ForRead) as Parcel;
bool isEnclosing = parcel.IsEnclosingParcel;
Site site = ts.GetObject(parcel.SiteId, OpenMode.ForRead) as Site;
ObjectIdCollection allParcelIds = site.GetParcelIds();
Right-of-Way
Right-of-way (ROW) parcels are created when alignments pass through a site. The alignment splits the site into parcels, and the ROW parcel represents the road corridor.
Parcel parcel = ts.GetObject(parcelId, OpenMode.ForRead) as Parcel;
ParcelStyle style = ts.GetObject(parcel.StyleId, OpenMode.ForRead) as ParcelStyle;
string styleName = style.Name;
Parcel Styles
ObjectId styleId = doc.Styles.ParcelStyles.Add("Residential Lot");
ParcelStyle style = ts.GetObject(styleId, OpenMode.ForWrite) as ParcelStyle;
style.GetDisplayStylePlan(ParcelDisplayStyleType.ParcelSegment).Color =
Color.FromColorIndex(ColorMethod.ByAci, 3);
style.GetDisplayStylePlan(ParcelDisplayStyleType.ParcelSegment).Lineweight =
LineWeight.LineWeight040;
style.GetDisplayStylePlan(ParcelDisplayStyleType.ParcelAreaFill).Visible = true;
style.GetDisplayStylePlan(ParcelDisplayStyleType.ParcelAreaFill).Color =
Color.FromRgb(200, 255, 200);
parcel.StyleId = styleId;
ObjectId existingStyleId = doc.Styles.ParcelStyles["Standard"];
Parcel Labels
Label Style Hierarchy
var parcelLabelStyles = doc.Styles.LabelStyles.ParcelLabelStyles;
var areaLabelStyles = parcelLabelStyles.AreaLabelStyles;
var lineLabelStyles = parcelLabelStyles.LineLabelStyles;
var curveLabelStyles = parcelLabelStyles.CurveLabelStyles;
Creating Labels
Point2d labelLocation = new Point2d(parcel.Centroid.X, parcel.Centroid.Y);
ObjectId areaLabelId = ParcelLabel.Create(
parcelId, areaLabelStyleId, labelLocation);
ObjectId lineLabelId = ParcelSegmentLabel.Create(
parcelId, segmentIndex, lineLabelStyleId);
ObjectId curveLabelId = ParcelSegmentLabel.Create(
parcelId, segmentIndex, curveLabelStyleId);
ObjectIdCollection existingLabels = parcel.GetAvailableLabelIds();
Label label = ts.GetObject(areaLabelId, OpenMode.ForWrite) as Label;
label.StyleId = newLabelStyleId;
Common Parcel Label Property Fields
Area labels:
<[Parcel Name(CP)]>
<[Parcel Number]>
<[Parcel Area(Usf|P0|RN|AP|Sn|OF)]>
<[Parcel Perimeter(Uft|P2|RN|AP|Sn|OF)]>
<[Parcel Address(CP)]>
<[Tax ID(CP)]>
Segment labels (line):
<[Segment Bearing(Udir|P0|B4)]>
<[Segment Length(Uft|P2|RN|AP|Sn|OF)]>
Segment labels (curve):
<[Segment Arc Length(Uft|P2|RN|AP|Sn|OF)]>
<[Segment Radius(Uft|P2|RN|AP|Sn|OF)]>
<[Segment Delta Angle(Udeg|P4)]>
<[Segment Chord Length(Uft|P2|RN|AP|Sn|OF)]>
<[Segment Chord Bearing(Udir|P0|B4)]>
Renumbering
Site site = ts.GetObject(siteId, OpenMode.ForRead) as Site;
ObjectIdCollection parcelIds = site.GetParcelIds();
int lotNumber = 1;
foreach (ObjectId pid in parcelIds)
{
Parcel p = ts.GetObject(pid, OpenMode.ForWrite) as Parcel;
if (p.IsEnclosingParcel) continue;
p.Number = lotNumber;
p.Name = string.Format("Lot {0}", lotNumber);
lotNumber++;
}
Note: GetParcelIds() does not guarantee any specific ordering. For specific ordering (e.g., clockwise around a cul-de-sac), sort parcels by centroid angle or frontage station before renumbering.
Parcel Analysis
Area and Frontage Checks
double minArea = 7500.0;
Site site = ts.GetObject(siteId, OpenMode.ForRead) as Site;
foreach (ObjectId pid in site.GetParcelIds())
{
Parcel p = ts.GetObject(pid, OpenMode.ForRead) as Parcel;
if (p.IsEnclosingParcel) continue;
if (p.Area < minArea)
ed.WriteMessage("WARNING: {0} area {1:F0} sf < minimum {2:F0}\n",
p.Name, p.Area, minArea);
}
Summary Statistics
double totalArea = 0, minLot = double.MaxValue, maxLot = double.MinValue;
int lotCount = 0;
foreach (ObjectId pid in parcelIds)
{
Parcel p = ts.GetObject(pid, OpenMode.ForRead) as Parcel;
if (p.IsEnclosingParcel) continue;
totalArea += p.Area;
minLot = Math.Min(minLot, p.Area);
maxLot = Math.Max(maxLot, p.Area);
lotCount++;
}
ed.WriteMessage("Lots: {0}, Min: {1:F0} sf, Max: {2:F0} sf, Avg: {3:F0} sf\n",
lotCount, minLot, maxLot, totalArea / lotCount);
Enumerating All Parcels
ObjectIdCollection siteIds = doc.GetSiteIds();
foreach (ObjectId siteId in siteIds)
{
Site site = ts.GetObject(siteId, OpenMode.ForRead) as Site;
foreach (ObjectId parcelId in site.GetParcelIds())
{
Parcel parcel = ts.GetObject(parcelId, OpenMode.ForRead) as Parcel;
ed.WriteMessage("[{0}] {1}: {2:F0} sq ft\n",
site.Name, parcel.Name, parcel.Area);
}
}
Gotchas
- All parcels must belong to a site — there is no "siteless" parcel concept (unlike siteless alignments)
- Adding an alignment to a site automatically splits existing parcels, creating ROW parcels
- Parcel topology is enforced within a site — editing one boundary updates adjacent parcels
Parcel.Area and Parcel.Perimeter are read-only computed properties based on segment geometry
ParcelSegment.Direction is in radians measured from north (not east), clockwise positive
- Subdivision methods (frontage, sliding angle) are interactive — the .NET API primarily supports creation from existing geometry via
CreateParcelsFromObjects
GetParcelIds() does not guarantee any specific ordering — sort manually for renumbering
- The enclosing (remainder) parcel updates automatically as child lots are created or deleted
- Parcel
Number is an integer property, not a string — use Name for alphanumeric identifiers like "Lot 15A"
- Modifying parcel segments directly is limited — use site topology operations or recreate from objects
- When a site contains both parcels and alignments, deleting an alignment merges the parcels it previously split
ParcelSegment curve properties (Radius, CenterPoint, IsClockwise) throw if accessed on a line segment — check IsCurve first
Related Skills
c3d-alignments — alignments within sites and their relationship to parcel topology
c3d-surfaces — surface-based grading and elevation data for parcel design
c3d-label-styles — general label style creation and configuration
c3d-root-objects — CivilDocument access, GetSiteIds(), and transaction patterns