Here is MacroLauncher(), since it plays a role in shortening the .py file but also makes launching consistent and easy.
Original Message:
Sent: 03-19-2026 16:24
From: Quan Mueller
Subject: TML Development Environment for TBC 2025.21
Hi David!
Thanks for sharing about your process. Awesome to get your shim file down to 10 lines! Made me look at my "shim" file, and it's 210 lines (facepalm).
Though it has lots of blank lines for spacing, comments, etc. and is doing a lot more than just "code as much in C# as we can". You only have to modify 2 strings and 2 comments at the top to customize it. But it got my brain grinding on ways to automate/simplify it!
I'm meeting with my first TBC command student next week, and nothing like training someone on your process to get you to realize how much it needs to be improved. By the end of my grinding designing, the roadmap is to not need a Python file at all... in a nutshell, it'll be built/signed/deployed from the C# assembly that has the "command" class and the "command UI" class. A distraction from today's original goal, but a great improvement - thanks for inadvertantly challenging me!
And thanks for the Claude markdown file contents (I saved it to my desktop right away!). That gives me a great example of what items people are interested in and using (TBC's API is massive), and a great example of a markdown file. I've only dabbled a few times with Copilot in VS while doing some web development last year. But now I've added "sample AI markdown files" to my training idea list.
Keep up the great work!
------------------------------
Quan Mueller
Revenant Solutions | TBC Extension Developer
Superuser Program | superuser@revenantsolutions.com
Original Message:
Sent: 03-19-2026 08:46
From: David Brubacher
Subject: TML Development Environment for TBC 2025.21
I've chosen to abandon the python route except for a 10-line shim and do everything in C# like @Quan Mueller. It works very well and I can use VS 2026 and AI to help navigate the sparsely documented API. Here's some stuff out of my Claude file that might help
### Media / Photos- Get the container: `FilePropertiesContainer.ProvideFilePropertiesContainer(project)`- Enumerate: `foreach (var item in container)` - check if each item is `FileProperties`- Key properties on `FileProperties`: - `fp.FilePath` - current file path (readable/writable); use for relative paths (e.g., `"..\\Photos\\filename.jpg"`) - `fp.FullName` - absolute file path (readable/writable); set this to update the media link - `fp.Name` - file name only- Check if an object has media: `obj.FileProperties.Count > 0`- Objects with media (e.g., points with photos) expose `.FileProperties` collection; iterate with `for fp in obj`- `MediaFolder` / `MediaFolderContainer` types exist in the API but are NOT how photos are discovered or updated - use `FilePropertiesContainer` instead- Photos observe points via Concordance: `GetObserversOf(point.SerialNumber)` may include `FileProperties` or related objects### Feature Code Observations- Create via `new FeatureCodeObservation(project)`, then `Populate(point)`- `RawString` gives the current feature code string- `FeatureNodeList` contains parsed codes with their `Attributes`- To update: `UpdateFeatureCode(code)` then `UpdateRawFeature()`### Concordance (object relationships)- `GetObserversOf(serial)` - who is watching this object- `GetIsObservedBy(serial)` - what this object watches- `Lookup(serial)` - resolve serial to object### Points- Use `point.FullDescription1` / `point.FullDescription2` (not `.Description1`/`.Description2`)- `point.FeatureCode` - the raw feature code string- `point.IncludeInSurface` - unreliable after recompute- `point.SerialNumber` - unique identifier- `point.PointID` - the point name/ID (readable/writable)- `point.AnchorName` - alternate way to read the point name (used in Concordance lookups)- `point.Position` - `Point3D` with `.X` (Easting), `.Y` (Northing), `.Z` (Elevation)- `point.Layer` - layer serial number (readable/writable)### SnapIn Discovery- TBC project implements `IEnumerable<ISnapIn>` - iterate `foreach (ISnapIn snapIn in project)` to find containers- Use `TbcSnapInHelper.TryFindFirstSnapIn(typeof(T), out var result)` to locate specific snap-in types- Key snap-ins: `PointManager` (from `Trimble.Vce.Data.RawData`), `FeatureCodeManager`, `CSDContainer` (coordinate system)- `FeatureCodeManager.Provide(project)` returns the manager - do NOT cache; re-create each time (enumeration unreliable when reused)- `FeatureCodeManager` enumeration may not return all codes from the FXL; supplement with known codes manually### PointManager- Find via snap-in enumeration: `foreach (var o in project) { if (o is PointManager pm) ... }`- `pm.AssociatedRDFeatures(point.SerialNumber)` - returns feature code observations linked to a point- Each returned feature has `.Code` (readable/writable) - can rename feature codes this way### Feature Code Manager- Iterate with `foreach (var fc in featureCodeManager)` - each has `.Code`, `.Type` (GeometryTypes enum)- `GeometryTypes.Point`, `GeometryTypes.Block`, `GeometryTypes.Line` - filter by type- Code definitions come from the project's FXL (Feature Library) file- Attributes on codes: access via `FeatureCodeObservation.FeatureNodeList[i].Attributes`- Each attribute has `.Name`, `.Value`, `.AttributeType` (Double/String/Integer/List), `.Definition`### Layers- Access layer collection: `project.GetMemberContainer(MandatoryContainers.LayerCollection) as LayerCollection`- Also via fixed serial: `project[Project.FixedSerial.LayerContainer]`- Find layer: iterate `foreach (Layer layer in layerContainer)`, compare `layer.Name`- Create layer: `layerContainer.Add<Layer>()` then set `newLayer.Name`- Assign to object: `obj.Layer = layer.SerialNumber`- Layer members: `layer.Members` - returns serial number list of all elements on that layer- Verify layer exists: `Concordance.Lookup(serial)` then check `obj.GetSite() is LayerCollection`### Linestrings / Lines- Create: `var ls = container.Add(typeof(Linestring))` where container is usually WorldView- Get container for view: `ViewHelper.GetContainerForView(clickWindow)` - usually WorldView- Add elements: `ElementFactory.Create(typeof(IStraightSegment), typeof(IXYZLocation))` then `ls.AppendElement(e)`- For point-based linestrings: use `IPointIdLocation` instead of `IXYZLocation`, set `e.LocationSerialNumber = pointSerial`- Set properties: `ls.Name`, `ls.Layer`, `ls.Color`, `ls.LineStyle`, `ls.LineTypeScale`, `ls.Weight`- Close linestring: `ls.Closed = true` (if object implements `ILinestringModifier`)- Copy from existing geometry: `ls.Append(sourceObject, false, false)` or `ls.Append(polySeg, null, true, false)`- Delete old object after conversion: `project.ReplaceEntity(oldSerial, newSerialArray)`### Object Properties (common to most graphical objects)- `obj.Layer` - layer serial number (readable/writable)- `obj.Color` - color (readable/writable)- `obj.Weight` - line weight (readable/writable)- `obj.LineStyle` - line style serial number (readable/writable)- `obj.LineTypeScale` - line type scale factor (readable/writable)- `obj.Name` - object name; for some types use `IName.Name` interface instead- `obj.GetSite()` - returns the container the object lives in (WorldView, LayerCollection, etc.)
### Settings / Options Persistence- **Project-scoped persistence**: Use `ConstructionCommandsSettings.ProvideObject(project)` - this is the preferred and proven mechanism for storing custom key-value data in the TBC project file. - `settings.GetString(key)` / `settings.SetString(key, value)` - `settings.GetBoolean(key)` / `settings.SetBoolean(key, value)`
### Known API Quirks- `point.Description1`/`.Description2` return empty - use `point.FullDescription1`/`.FullDescription2`- `point.IncludeInSurface` resets to feature library default on project recompute- `FeatureCodeManager` enumeration is unreliable - may miss codes defined in the FXL- `AssociatedNotes(pointSerial)` only returns point-linked notes, not standalone job notes- Symbols cannot rotate directly - rotation must be tied to an attribute (which has bugs)- TBC version differences: `Trimble.Sdk` (v5.50+) vs older `Trimble.Vce.*` assemblies - use try/except pattern- `MediaFolderContainer` snap-in only exists when photos have been imported into the project- Points with no feature code still appear in `PointCollection` - always check `string.IsNullOrEmpty(point.FeatureCode)` before processing- `TransactionManager.AddEndMark` MUST be called in finally block - if missed, TBC becomes unresponsive and requires restart- Hold serial numbers instead of object references - safer across transaction boundaries- `obj.GetSite()` returns the container - use `instanceof LayerCollection` / `PointCollection` to determine object category - `settings.GetUInt32(key)` / `settings.SetUInt32(key, value)` - `settings.KeyExists(key)` - check before reading to distinguish "not set" from "empty" - Supported types: `string`, `int` (Int32), `uint` (UInt32), `double`, `bool` only - **Long, float, DateTime, Guid, arrays, collections, and null are NOT supported.** - Data is stored in `UserDefinedAttributes` (serial 27) against `ConstructionCommandsSettings` (serial 1135) and persists across project reopen/recompute - Use key format `MTE_{MacroName}.{SettingName}` (e.g., `MTE_ProjectProperties.OfficeName`)- **App-wide persistence**: Use `OptionsManager` for settings that are NOT project-specific (stored in Options.bin, shared across all projects) - use it only for app-wide macro settings, NOT for project-scoped data. - `OptionsManager.GetBool(key, default)`, `GetString`, `GetInt`, `GetDouble`, `GetUint` - `OptionsManager.SetValue(key, value)`- **WARNING**: `UserInfoCollection` custom keys do NOT reliably persist in TBC macros. Do not use `UserInfoCollection["UniqueID_*"]` as a persistence mechanism. The Fax field encoding technique (OfficeUserFax, FieldOperatorFax) was a workaround and is now superseded by `ConstructionCommandsSettings`.- **WARNING**: Encoding custom data into `IProjectUserInfo` Fax fields (`OfficeUserFax`, `FieldOperatorFax`, `CompanyFax`) is a legacy workaround - use `ConstructionCommandsSettings` instead. Standard `IProjectUserInfo` fields (name, email, phone, address) may still be written for TBC UI compatibility.### Units- `project.Units.Linear` - linear distance units- `project.Units.Station` - chainage/station units- `units.Convert(fromType, value, toType)` - convert between unit types- `units.DisplayType` - current display unit type- `units.Properties.Copy()` - copy format properties (then set `.AddSuffix = false` for raw numbers)- `units.Format(value, formatProperties)` - format a value as string### Surfaces (Model3D / DTM)- Select surface: use `SurfaceTypeLists.AllWithCutFillMap` as entity filter- Key properties: `surface.NumberOfTriangles`, `surface.NumberOfVertices`- `surface.GetVertexPoint(vertexIndex)` - returns `Point3D`- `surface.IsVertexPresent(vertexIndex)` - check if vertex exists- `surface.GetTriangleIVertex(triangleIndex, side)` - get vertex index for triangle corner- `surface.GetTriangleVertex(triangleIndex, side)` - returns `(vertexIndex, Point3D)` tuple- `surface.GetTriangleMaterial(index)` vs `surface.NullMaterialIndex()` - check if triangle is visible- `surface.GetTriangleOuterSide(triangle, side)` - check if edge is on surface boundary- `surface.PickSurface(point3d)` - returns `(found, surfacePoint)` for point-on-surface queries### Selection and Validation- Selection callback: `objs.IsEntityValidCallback = IsValid` - filter what can be selected- `SelectionContextMenuHandler` - customize right-click context menu - `optionMenu.ExcludedCommands = "SelectObservations | SelectPoints | SelectDuplicatePoints"`- Get selected entities: `objs.SelectedMembers(project)` - returns enumerable of selected objects- To array: `Array[ISnapIn](objs.SelectedMembers(project))`- Global selection: `GlobalSelection.Clear()` to deselect all### WorldView and Containers- Get WorldView: `project[Project.FixedSerial.WorldView] as WorldView`- Get block collection: `project[Project.FixedSerial.BlockCollection]`- Get layer container: `project[Project.FixedSerial.LayerContainer]`- Add objects: `worldView.Add(typeof(Linestring))`, `container.Add(typeof(T))`- Remove objects: `container.Remove(serialNumber)`- List objects: use `IMemberManagement` interface; check `IRepresentGraphically` for graphical objects- Plan set sheets: `PlanSetSheetView.GetPlanSetContainer(project)`### ModelEvents (entity change notifications)- `ModelEvents.EntityEdited += handler` - fires when an entity is modified- `ModelEvents.EntityDeleted += handler` - fires when an entity is deleted- Event args: `eArgs.EntitySerialNumber` - serial of the affected entity- Always unsubscribe in `Dispose`: `ModelEvents.EntityEdited -= handler`### User Attributes- Access via `SnapInAttributeExtension.UserAttributes.Overloads[ISnapIn](selectedObject)`- Returns dictionary of key-value pairs- Not available on the project object itself (`Project.FixedSerial.Project`)### Object Inspection (for diagnostic tools)- Type info: `obj.GetType()`, walk `.BaseType` for inheritance chain- Interfaces: `obj.GetType().GetInterfaces()`- Check `IName`: `if (obj is IName named) { named.Name }`- Concordance observers/observed: see Concordance section above### ForeignCad Types (imported CAD objects)- `PolyLine`, `PolyLineBase`, `Poly3D` - polyline types- `Arc` (geometry), `Circle` - curve types- `CadPoint` - CAD point (different from survey `Point`)- `MText`, `Text` (CadText) - text entities- `BlockReference` - block insert- `Leader` - leader line- `Hatch` - hatch pattern- All have `.Layer`, `.Color`, `.Weight` properties- Text-specific: `.TextStyleSerial`, `.AlignmentPoint`, `.Normal`- UCS handling: `IHaveUcs` interface, `UCSHelper.GetUcsTiltToWorld(obj.Normal)`
------------------------------
David Brubacher
Original Message:
Sent: 03-09-2026 05:07
From: Chris Siebern
Subject: TML Development Environment for TBC 2025.21
Hello TML Community,
I'm looking for links to any updated walkthroughs to establish a development environment For TML using TBC 2025.21:
-
Do I still have to use Visual Studio 2019, or can I use a newer build to get IronPython support?
-
Looking for updates for example project to review and learn from, example implementations?
Apologies if this has been answered multiple times. I watch the thread weekly looking for any updates for a new to TML walkthrough, but I have not found anything or simply missed it.
|
Chris Siebern, PE, PS
Application Success Manager Survey / Geospatial | Design Technology Services
Associate
chris.siebern@collierseng.com
Main: 877 627 3772
Remote
|
|
DISCLAIMER This e-mail is confidential. It may also be legally privileged. If you are not the addressee you may not copy, forward, disclose or use any part of this email text or attachments. If you have received this message in error, please delete it and all copies from your system and notify the sender immediately by return e-mail. Internet communications cannot be guaranteed to be timely, secure, error or virus free. The sender does not accept liability for any errors or omissions. Any drawings, sketches, images, or data are to be understood as copyright protected.
|
|