TBC Macros and Extensions

 View Only
Expand all | Collapse all

Updating Point Feature Code strings

  • 1.  Updating Point Feature Code strings

    Posted 02-03-2025 19:42
    Edited by David Brubacher 02-05-2025 06:20

    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
    ------------------------------



  • 2.  RE: Updating Point Feature Code strings

    Posted 02-03-2025 21:43
    Edited by Ronny Schneider 02-04-2025 14:17

    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, OfficeEnteredCoord
    pnew_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
    ------------------------------



  • 3.  RE: Updating Point Feature Code strings

    Posted 02-04-2025 08:23

    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
    ------------------------------



  • 4.  RE: Updating Point Feature Code strings

    Posted 02-04-2025 15:10

    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 pm
    features = 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, IFeatureCode
    
    f = 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
    ------------------------------



  • 5.  RE: Updating Point Feature Code strings

    Posted 02-04-2025 16:06

    Thank you, Ronny!

    This looks very promising and may help with the next fix on my list; merged points that have different codes where all but one are check shots. Sometimes the check shot code is the 'master' and you get the yellow dot saying there are multiple codes.

    I looked at the Associated... methods and wondered if they would do what I wanted. I should have looked closer. I will investigate this evening and post my code if it works, or questions if it doesn't.



    ------------------------------
    David Brubacher
    ------------------------------



  • 6.  RE: Updating Point Feature Code strings

    Posted 02-05-2025 06:19
    Edited by David Brubacher 02-05-2025 06:23

    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
    ------------------------------



  • 7.  RE: Updating Point Feature Code strings

    Posted 02-05-2025 17:39

    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 FeatureStatus
    from 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
    ------------------------------



  • 8.  RE: Updating Point Feature Code strings

    Posted 02-05-2025 18:59

    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.



    ------------------------------
    David Brubacher
    ------------------------------