Wow I'd have been a long time puzzling this one out. I will try it!
I'm actually going down the attribute rabbit hole, since I also have other bits of text in that string I want as attribute values. I will post working code when I have some.
Original Message:
Sent: 02-05-2025 17:38
From: Ronny Schneider
Subject: Updating Point Feature Code strings
Probable solution, haven't tested in depth.
Lock the "PointFeature" which is something else again and as cumbersome to get to as finding the PointManager, unless I miss an easier way. I always see the unnecessary cycles for that looping in my mind, that hurts.
from Trimble.Sdk.Interfaces import FeatureStatusfrom Trimble.Vce.Features import PointFeature for observer in self.currentProject.Concordance.GetIsObservedBy(point.SerialNumber): if observer and isinstance(observer, PointFeature): # you may have to check for other Feature types as well, i.e. LineFeature observer.UpdateStatus(FeatureStatus.Locked)
First you have to find what is observing your point and then change that one, instead your point.
In German we have a saying "shot from the back, through the chest, into the eye".
------------------------------
Ronny Schneider
Original Message:
Sent: 02-05-2025 06:19
From: David Brubacher
Subject: Updating Point Feature Code strings
Success! and another problem to solve, but this is the way. As promised, here is the C# code that will walk through all feature codes on all points. Hopefully this helps someone.
My patterns for testing:
// set up the check patterns private static readonly Regex TrailingMinusPattern = new Regex(@"(?<First>[A-Z]{1,6}[0-9]{0,3})(?<Second>-|\+)(?<LineEnd>\s|\z)", RegexOptions.Compiled); private static readonly Regex TrailingNumberPattern = new Regex(@"(?<First>HC|HD|SC|SD|TD|TC)(?<Second>\d{1,5})(?<LineEnd>:\s|\z)", RegexOptions.Compiled);
The main processing loop:
/// <summary> /// Perform the processing /// </summary> /// <param name="project">A reference to the current project</param> public void DoProcessing(Project project) { // get a reference to the point manager snap in PointManager pointManager = null; foreach (ISnapIn snapIn in project) { if (!(snapIn is PointManager @in)) continue; pointManager = @in; break; } // fail early if (pointManager == null) { _results.AppendLine("FATAL ERROR: Failed to connect to the Point Manager"); return; } // get all the points in the project var points = PointCollection.ProvidePointCollection(project); _results.AppendLine($"{points.Count} points in this project"); int totalMinusReplacement = 0, totalSpacing = 0, excludeFromSurfaceOverride = 0; // process all the points in the project foreach (Point point in points) { // skip points with no feature codes if (string.IsNullOrEmpty(point.FeatureCode)) continue; // get the collection of codes attached to this point var codes = pointManager.AssociatedRDFeatures(point.SerialNumber); int thisMinusCount = 0, thisSpaceCount = 0; foreach (var t in codes) { var thisCode = t.Code.ToUpper(); // fix trailing minus signs thisCode = DoReplacement(thisCode, TrailingMinusPattern, out var minusCount); // the trailing minus means 'exclude from surface'. This is meant as an override // to the default defined in the feature code definition. Note we only want to // set this once for the parent point, thus the test that thisMinusCount is 0 if (minusCount > 0 && thisMinusCount == 0 && point.IncludeInSurface) { point.IncludeInSurface = false; excludeFromSurfaceOverride++; } // now we can add the number of replacements in this code to the total for this point thisMinusCount += minusCount; // fix trailing numerics after feature codes thisCode = DoReplacement(thisCode, TrailingNumberPattern, out var spacingCount); thisSpaceCount += spacingCount; // Lastly we update this code t.Code = thisCode; } // update the totals for the whole project totalMinusReplacement += thisMinusCount; totalSpacing += thisSpaceCount; } // report results _results.AppendLine($"Moved {totalMinusReplacement} minus signs."); _results.AppendLine($"Applied {excludeFromSurfaceOverride} overrides to surface inclusion."); _results.AppendLine($"Spaced size data on {totalSpacing} codes."); }
And finally, the DoReplacement method
/// <summary> /// Perform a replacement /// </summary> /// <param name="featureCode">The feature code string attached to a point</param> /// <param name="pattern">The Regex pattern to check against</param> /// <param name="matchCount">Out value indicating the number of matches</param> /// <param name="replacement">An optional replacement value to use in the event of a match</param> /// <returns>A string with the replacements performed</returns> private string DoReplacement(string featureCode, Regex pattern, out int matchCount, string replacement = null) { matchCount = pattern.Matches(featureCode).Count; return matchCount > 0 ? pattern.Replace(featureCode, replacement ?? "${First} ${Second}${LineEnd}") : featureCode; }
The only issues is to 'lock' the IncludeInSurface property because a recompute sets it to the default value in the FXL.
Thanks for your help Ronnie.
------------------------------
David Brubacher
Original Message:
Sent: 02-04-2025 15:10
From: Ronny Schneider
Subject: Updating Point Feature Code strings
You're right, there is a difference between just created points and changing existing ones.
I didn't remember that I had run into that before, too many macros in the meanwhile.
I had solved it the following way
# the same PointManager as pmfeatures = pm.AssociatedRDFeatures(o.SerialNumber)for fc in features: fc.Code = fc.Code + "xxx"
some Trimble sample macros show the following code:
from Trimble.Vce.Interfaces.FeatureCoding import IFeatureCodeHost, IFeatureCodef = IFeatureCodeHost.FeatureCodes.GetValue(o)for fc in f: fc.Code = fc.Code + "xxx"
Either way, if the existing "code(s)" aren't recognized by the code processing, aren't present in the used FXL, they're considered one single string, and it will replace the whole lot.
If the single codes are present in the FXL, then it will step through, and you can change each one individually.
An additional benefit is that you don't lose existing attributes if keep the code and just add an additional number, as you would if you'd change the code manually in the properties pane.
------------------------------
Ronny Schneider
Original Message:
Sent: 02-04-2025 08:22
From: David Brubacher
Subject: Updating Point Feature Code strings
Thanks for looking Ronny.
It was your code I cribbed from in the first place! You probably recognize some similarities. And that's what makes me curious... your python code is not in a transaction and the point manager snap-in looks like it probably does a transaction under the covers, so that's probably not it. When I step through, the tooltip on the point.SerialNumber call is SnapInTransact.SerialNumber.get().
The big difference is you are creating a new point, and I am modifying an existing point. In both cases the serial number comes from the parent container point.
SetFeatureCodeAtPoint returns void so I don't know if it succeeded or failed. I've stepped through the code and the Set... method is called, but the FeatureCode property remains unchanged, however the Dirty property is true. This tells me I need to commit the changes, which brings me back to transactions or a commit method on the PointManager, but there's nothing obvious.
------------------------------
David Brubacher
Original Message:
Sent: 02-03-2025 21:43
From: Ronny Schneider
Subject: Updating Point Feature Code strings
Forget it, I just saw that you use it.
PointManager has a method "SetFeatureCodeAtPoint".
This is the only way I got it to work.
#find PointManager as object for o in self.currentProject: if isinstance(o, PointManager): pm = o pnew = Point3D() pnew.X = p1.X pnew.Y = p1.Y pnew.Z = p2.Z pnew_wv = CoordPoint.CreatePoint(self.currentProject, p1name + addtoname) #change FeatureCode by using the PointManager if newcode=="": pm.SetFeatureCodeAtPoint(pnew_wv.SerialNumber, p1code) else: pm.SetFeatureCodeAtPoint(pnew_wv.SerialNumber, newcode) pnew_wv.AddPosition(pnew) pnew_wv.Layer = self.layerpicker.SelectedSerialNumber# another option to create a named point is by using from Trimble.Vce.Coordinates import Point as CoordPoint, OfficeEnteredCoordpnew_named = CoordPoint.CreatePoint(self.currentProject, pnew_name)keyed_coord = KeyedIn(CoordSystem.eGrid, pnew.Y, pnew.X, CoordQuality.eSurvey, pnew.Z, CoordQuality.eSurvey, CoordComponentType.eElevation, System.DateTime.UtcNow)OfficeEnteredCoord.AddOfficeEnteredCoord(self.currentProject, pnew_named, keyed_coord)try: pm.SetFeatureCodeAtPoint(pnew_named.SerialNumber, fc)
By the way, did you see my private message in your forum inbox?
------------------------------
Ronny Schneider
------------------------------
-------------------------------------------
Original Message:
Sent: 02-03-2025 19:41
From: David Brubacher
Subject: Updating Point Feature Code strings
I have a bunch of legacy data with different rules around feature code formatting. When I bring the legacy data into TBC I have a lot of tedious editing to do, thus begins my first macro.
I have a pretty simple python part that calls C# code. This works, including properly modifying the feature code string and reporting to the user. I am also using the PointManager to set the updated string, but it doesn't stick. Does this have to happen in a transaction?
// get all the points in the project
var points = PointCollection.ProvidePointCollection(project);
_results.AppendLine($"{points.Count} points in this project");
int totalMinusReplacement = 0, totalSpacing = 0;
// get a reference to the point manager snap in
PointManager pointManager = null;
foreach (ISnapIn snapIn in project)
{
if (!(snapIn is PointManager @in))
continue;
pointManager = @in;
break;
}
if (pointManager == null)
return;
// process all the points in the project
foreach (Point point in points)
{
if (string.IsNullOrEmpty(point.FeatureCode))
continue;
var code = point.FeatureCode.ToUpper();
// fix trailing minus signs
code = DoReplacement(code, TrailingMinusPattern, out var minusCount);
totalMinusReplacement += minusCount;
// fix trailing numerics after feature codes
code = DoReplacement(code, TrailingNumberPattern, out var spacingCount);
totalSpacing += spacingCount;
// did we make any changes?
if (minusCount != 0 || spacingCount != 0)
// update the feature code
pointManager.SetFeatureCodeAtPoint(point.SerialNumber, code); // Here is the issue
}
_results.AppendLine($"Moved {totalMinusReplacement} minus signs.");
_results.AppendLine($"Spaced size data on {totalSpacing} codes.");
------------------------------
David Brubacher
------------------------------