TBC Macros and Extensions

 View Only
Expand all | Collapse all

Altering Attribute Values

  • 1.  Altering Attribute Values

    Posted 03-02-2025 17:07

    I have an FXL that has evolved to include a number of attributes, and I have data, often in the form of CSVs that doesn't include values for the attributes.
    The attributes are defined as 'office only', are mostly numeric and have a limited value range. What I am trying to do is assign the default value as found in the attribute definition to any points with attribute values that are null. The default value is within the allowed value range.
    This is C# code but should be readily understandable

           private void SetAttributesToDefault(Point point)
            {
                // get all the feature codes attached to this point
                var items = _pointManager.AssociatedRDFeatures(point.SerialNumber);
                // loop through each one
                foreach (var featureCode in items)
                {
                    // inspect the attributes
                    if (featureCode.Attributes.Length == 0)
                        continue;
                    foreach (var attribute in featureCode.Attributes)
                    {
                        // check for no attribute definition
                        if (attribute.Definition == null)
                            continue;
                        // Set the value of the instance attribute to the default value of the attribute definition
                        // when the current instance value is not a number
                        if (attribute.Type == AttributeTypes.Double)
                            if (double.IsNaN((double)attribute.GetValue()))
                            {
                                attribute.SetValue(attribute.Definition.GetDefaultValue());
                                //var observed = _project.Concordance.GetIsObservedBy(point.SerialNumber);
    
                            }
    
                    }
                }
            }

    All of this is happening inside a transaction, and the transaction commits

                        // commit the transaction
                        transaction.Commit();
                    }
                }
                catch (Exception e)
                {
                    
                    _results.Append($"ERROR: {e.Message} from {e.Source}");
                }
                finally
                {
                    // set an undo mark
                    _project.TransactionManager.AddEndMark(CommandGranularity.Command);
                    UIEvents.RaiseAfterDataProcessing(this, new UIEventArgs());
                    _project = null;
                }
            }

    While debugging I can see the attribute value change form NaN to (usually) 0, but when the macro finishes, the attribute is still null. What am I missing?



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


  • 2.  RE: Altering Attribute Values

    Posted 03-03-2025 10:10

    Ok I'm getting somewhere.
    If I issue 

    _project.Calculate(true);

    I still have the problem, but if I manually update a feature code and click the recalculate button, everything appears to be fine - defaults are set!
    My attributes are shown as dirty, but that's not propagating up to the point.
    Ideally, I'd like to force the recalculation which is what I thought my code would do. More reading and experimenting...



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



  • 3.  RE: Altering Attribute Values

    Posted 03-13-2025 08:30
    Edited by David Brubacher 03-13-2025 08:34

    I think I'm closer to this but not there yet.
    All of the examples I've found to date are creating new points and use the SetFeatureCodeAtPoint(serialNum,  code) method found on the PointManager. Trying this on an existing point merely appends additional feature codes without replacing and there are no Clear(...) or Update(...) methods found on the point manager object. The documentation on this method provides a good clue though "since the feature code is a property of the observation linked to the point this method modifies all connected observations for this point".
    There is another SnapIn called FeatureCodeManager which is very promising. It has two methods of interest: LookupObservationHosts(point) and LookupContributingObservationHosts(point). The difference between them is the latter filters out disabled observations. Both methods return a collection of objects implementing IFeatureCodeHost, like Trimble.Vce.Coordinates.ImportedCoordinate, Trimble.Vce.Data.RawData.RDStationBase and Trimble.Vce.Data.RawData.RDCoordinate. RDStationBase contains a collection of observations, which in turn has feature codes which are editable. I can modify the codes with this

                // 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 observations that host feature codes for this point
                    // var hosts = FeatureCodeManager.LookupContributingObservationsHosts(point);
                    var hosts = FeatureCodeManager.LookupObservationsHosts(point);
                    foreach (var host in hosts)
                    {
                        // loop through all the codes this observation hosts
                        foreach (var featureCode in host.FeatureCodes)
                        {
                            var result = ProcessFeatureCodes(featureCode);
                            if (result != featureCode.Code)
                            {
                                featureCode.Code = result;
                            }
    
                        }
                    }

    and the result is the codes change on screen! Hurray!
    Not so fast.
    The codes remain unchanged in the point spreadsheet and in the properties. I can recalculate the project with no change, but when I update the screen by changing the view filter (for instance) everything reverts to its unedited state.
    Transactions are committed, undos marked, and the pre and post UI events are raised as above. I have not sacrificed a goat at the top of the mountain yet, but I'm thinking about it.
    Does anyone have any ideas?

    I should add... Trimble.Vce.FeatureCoding.FeatureCodeObservation looks interesting too. It has an UpdateFeatureCode(string) method that I want to try but I can't figure out how to get an object of that type out of my project.



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



  • 4.  RE: Altering Attribute Values

    Posted 03-14-2025 10:14

    I believe you want to go down the FeatureCodeObservation route, as this is what is used within the FeatureCodeEditor.

    FeatureCodeObservation isn't a project entity, and more an ad-hoc helper class. 

    For generating the correct object, I would simply create a new FeatureCodeObservation(project) and call Populate(point)

    Then make changes to your Feature Code via UpdateFeatureCode(raw_string)

    You also may need to call FeatureCodeObservation.SetDefaultAttributeValuesInNewFeatureCode(oldFco, m_Fco);

    // Create + Populate Feature Code Observation
    string raw_string_feature_code = "IR BRSH"
    var m_Fco = new FeatureCodeObservation(project);
    m_Fco.Populate(point);
    
    // remember the old fco so we can know which attribute should show default value
    FeatureCodeObservation oldFco = new FeatureCodeObservation(m_Fco);
    
    // update the feature code, the attributes will be merged when updating
    m_Fco.UpdateFeatureCode(raw_string_feature_code);
    
    // set default values for attributes that exist in new created feature codes
    FeatureCodeObservation.SetDefaultAttributeValuesInNewFeatureCode(oldFco, m_Fco);
    

    After this, you may proceed up the mountain and deposit your goat. :)
    Let me know if this helps in anyway.



    ------------------------------
    Bryce Haire
    ------------------------------



  • 5.  RE: Altering Attribute Values

    Posted 03-14-2025 13:40
    Edited by David Brubacher 03-14-2025 13:41

    Thanks for having a look at this Bryce.
    I realized I could look at how the Feature Code Control in the SDK was doing it and as a result of my 'dotPeeking' I had code very similar to what you suggested ready to test.

                // process all the points in the project
                foreach (Point point in points)
                {
                    // skip points with no feature codes
                    if (string.IsNullOrEmpty(point.FeatureCode))
                        continue;
    
                    // check if the feature code is valid and get a feature code observation for our trouble
                    // move onto the next point if TBC considers it valid
                    if (FeatureCodeObservation.ValidateFeatureCode(Project, point, out var newFco))
                    {
                        // but optionally set attributes to default values first
                        if (SetAttributesToDefaultValues)
                            FeatureCodeObservation.SetDefaultAttributeValues(newFco);
                        continue;
                    }
    
                    // cleanup the feature code string and update our new feature code object
                    var result = CleanupFeatureCodeString(point.FeatureCode);
                    newFco.UpdateFeatureCode(result);
                    // make sure the defaults are set since TBC should now recognize codes in this string
                    FeatureCodeObservation.SetDefaultAttributeValues(newFco);
                }

    Unfortunately, my results have not changed, so I think it must be a misunderstanding on my part. Prior to TBC we used a homegrown feature code processor that (among other things) specified a trailing minus sign (with no space) for a code that should not contribute its elevation to the surface. Therefore FH- is the code for a fire hydrant whose elevation should not be used when generating a surface. We have nearly 25 years of historic data in JOB files and CSVs, and lots of field staff who will continue to code this way, so I want a one button fix. There are lots of other issues too, so manual fixes are a non-starter.
    can of course edit it manually in the point spreadsheet or in the point properties, so I know it is possible. I am doing a simple Regex replace using this pattern

    (?<First>[A-Z]{1,6}\d{0,3})(?<Second>-)(?<LineEnd>\s|\z|\b)

    and returning a string with a space between First and Second. This works flawlessly.
    Then I use that string as the string to update, but part of the string (the minus sign) is not a valid code. This is a screen shot of my locals window on point 147 which started with a code SIB- and I want to be SIB -. Feature code is still SIB-, but RawString is SIB -. Note this is before the transaction commit.
    and this is the properties window after the macro completes. Manually recomputing the project does nothing
    Note: Yes, I will completely drop the minus sign and set include in surface to false, but I have other points where the feature code string will always have other data such as a point number to join to.
    Clearly, I am not doing something, and I don't know what it is.



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



  • 6.  RE: Altering Attribute Values

    Posted 03-14-2025 13:54
    Edited by David Brubacher 03-14-2025 14:00

    Thinking more on this, I am assuming that in getting the FCO from the static validate method, updating the feature code in the FCO raises an internal event that the point has a new feature code, and all interested observers should update themselves. Some of my other explorations indicated this might not be true, as I discussed earlier regarding the graphics showing one thing but the properties and point spreadsheet showing another.
    Is there something that needs to be done with the FCO to commit it, apart from the transaction? I didn't see anything, but that's no guarantee. Is there a state I have to put the project in to say 'I'm changing things so don't get in the way', then unset and refresh?

    And in your example, how does TBC resolve the conflict between what the new and old FCO objects want the point state to be?

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



  • 7.  RE: Altering Attribute Values

    Posted 03-14-2025 14:21

    Try adding this as well :P

    Point point = MyObject as Point;
    
    Project prj = (Project)((IProjectSite)point.GetSite()).HostingProject;
    Trimble.Vce.Features.FeatureCoding.FeatureCodeObservation orginalFco = new FeatureCodeObservation(prj);
    
    orginalFco.Populate(point);
    Trimble.Vce.Features.FeatureCoding.FeatureCodeObservation fco = (FeatureCodeObservation)((IEmbeddableControlEditData)dataObj).Data;
    
    // TRY THIS
    orginalFco.UpdateFeatureNodes(fco.FeatureNodeList);
    orginalFco.UpdateRawFeature();
    
    // ALSO ADD THIS
    //This is a temporary hack until we cleanup the RDFeature and IFeatureCodeProvider to force the redraw of the point
    Trimble.Vce.Coordinates.PointCollection pointColl = point.GetSite() as Trimble.Vce.Coordinates.PointCollection;
    if (pointColl != null)
        pointColl.ViewSite.AddToGraphicsCache(point.SerialNumber);
    




    ------------------------------
    Bryce Haire
    ------------------------------



  • 8.  RE: Altering Attribute Values

    Posted 03-17-2025 07:28

    I wish I had good news Bryce
    I've tried multiple combinations and permutations of your suggestions. From the simple...

                // process all the points in the project
                foreach (Point point in points)
                {
                    // skip points with no feature codes
                    if (string.IsNullOrEmpty(point.FeatureCode))
                        continue;
    
                    // create and populate a Feature Code Observation
                    var currentFco = new FeatureCodeObservation(Project);
                    currentFco.Populate(point);
    
                    // cleanup the feature code string and update our new feature code object
                    var result = CleanupFeatureCodeString(point.FeatureCode);
                    currentFco.UpdateFeatureCode(result);
                    currentFco.UpdateRawFeature();
    
                    // force a point redraw
                    if (point.GetSite() is PointCollection pointCollection)
                        pointCollection.ViewSite.AddToGraphicsCache(point.SerialNumber);
                }

    to the more complex...

                // process all the points in the project
                foreach (Point point in points)
                {
                    // skip points with no feature codes
                    if (string.IsNullOrEmpty(point.FeatureCode))
                        continue;
    
                    // instead of using our Project property, discover the hosting project from the point
                    // NOTE: We will alert if the hosting project and the project property are different
                    // because if they never are, we will revert back to using the Project property
                    var thisProject = (Project)((IProjectSite)point.GetSite()).HostingProject;
                    if (thisProject.SerialNumber != Project.SerialNumber)
                        ResultsBuilder.Append(
                            $"Warning: Hosted project of point {point.Name} is different than the current project");
    
                    // create and populate a Feature Code Observation
                    var currentFco = new FeatureCodeObservation(thisProject);
                    currentFco.Populate(point);
    
                    // check if the feature code is valid and get a feature code observation for our trouble
                    // move onto the next point if TBC considers it valid
                    if (currentFco.ValidateFeatureCode(point.FeatureCode))
                    {
                        // but optionally set attributes to default values first
                        if (SetAttributesToDefaultValues)
                            FeatureCodeObservation.SetDefaultAttributeValues(currentFco);
                        continue;
                    }
    
                    // create a copy of the current fco
                    var newFco = new FeatureCodeObservation(currentFco);
    
                    // update the feature nodes from the current fco
                    newFco.UpdateFeatureNodes(currentFco.FeatureNodeList);
                    // cleanup the feature code string and update our new feature code object
                    var result = CleanupFeatureCodeString(point.FeatureCode);
                    newFco.UpdateFeatureCode(result);
                    newFco.UpdateRawFeature();
    
                    // it feels like we need to tell something this fco is the one we want to rule
                    // not sure if this is how to do it.
                    if (!newFco.ApplyTo(FeatureCodeManager.SerialNumber, thisProject))
                        ResultsBuilder.AppendLine($"ApplyTo unsuccessful at point {point.Name}");
                    
                    // make sure the defaults are set since TBC should now recognize code(s) in this string
                    FeatureCodeObservation.SetDefaultAttributeValues(newFco);
    
                    // force a point redraw
                    if (point.GetSite() is PointCollection pointCollection)
                        pointCollection.ViewSite.AddToGraphicsCache(point.SerialNumber);
                }

    and it feels like everything in between.

    In both of these cases I can see that the FCO gets the changes applied, but the changes never make it back to the parent Point and its observations. You can see in the 2nd example I am looking for ways to 'commit' the FCO to the parent Point. I tried PointManager and FeatureManager here, both failing to apply the changes.
    On screen and in the database, there is no effect. TBC makes no changes and doesn't complain that I've done something illegal.

    The only time I have ever had the screen change is with the code in post 3, but then it reverts.

    The force redraw code also does nothing because the point is never changed. I've tried this on a few different projects too, so either something critical is missing or all my projects are corrupted / configured in a way that prevents the updates I want to make, or there is a problem in the SDK code that drives TBC.
    I am using 2024.10 and the newest SDK.



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



  • 9.  RE: Altering Attribute Values
    Best Answer

    Posted 03-17-2025 11:43
    Here is a script I threw together that I believe does what you are trying (sets FC to static string).
    Relevant Code:
        def OkClicked(self, sender, e):
            # tell undo manager we are starting to do something
            self.currentProject.TransactionManager.AddBeginMark(CommandGranularity.Command, self.Caption)
    
            for o in self.objs.SelectedMembers(self.currentProject):
                if not self.IsValidObj(o):
                    continue
                fco = FeatureCodeObservation(self.currentProject)
                fco.Populate(o)
                new_fco = FeatureCodeObservation(fco)
                new_fco.UpdateFeatureCode('test_')
                
                FeatureCodeObservation.SetDefaultAttributeValuesInNewFeatureCode(fco, new_fco)
    
                fco.UpdateFeatureNodes(new_fco.FeatureNodeList)
                new_fco.UpdateRawFeature()
            
            self.currentProject.TransactionManager.AddEndMark(Client.CommandGranularity.Command)
            sender.CloseUICommand()
    
    Hopefully this gets you going. Let me know if this works for you. 

    AddFeatureCodeToPoint.py

    """
    TBC Macro to export trench templates
    """
    import clr
    # Reference the WPF assemblies
    clr.AddReference('IronPython.Wpf')
    import wpf
    from System import Guid
    from System.IO import StreamReader
    from System.Collections.Generic import List, Comparer
    from System.Windows.Controls import StackPanel
    from System.Windows.Input import Keyboard
    clr.AddReference("System.Drawing")
    from System.Drawing import Bitmap
    from Trimble.Vce.Interfaces.Point import IPoint
    
    try:
        clr.AddReference("Trimble.Sdk") # In version 5.50, model assemblies are "pre-referenced"
    except:
        clr.AddReference("Trimble.Vce.Core")
        clr.AddReference("Trimble.Vce.Alignment")
        clr.AddReference("Trimble.Vce.ForeignCad")
        clr.AddReference("Trimble.Vce.Data.Construction")
        clr.AddReference("Trimble.Vce.Geometry")
        clr.AddReference("Trimble.Vce.Units")
        clr.AddReference("Trimble.Vce.Interfaces")
        clr.AddReference("Trimble.DiskIO")
        clr.AddReference("Trimble.Vce.Gem")
    
    from Trimble.Vce.Core import TransactMethodCall
    
    from Trimble.Vce.Interfaces import Client
    from Trimble.Vce.Interfaces.SnapIn import IPolyseg, IViewMember, ISettableName, IName, IViewSite, ISnapInHelpers
    from Trimble.Vce.Interfaces.Point import Helpers
    from Trimble.Vce.ForeignCad import MText, Text
    
    from Trimble.Vce.Alignment.Linestring import Linestring
    from Trimble.Vce.Trench import TrenchTemplateCollection, TrenchTemplate
    from Trimble.Vce.Macros import GenericContainer
    
    from Trimble.DiskIO import OptionsManager
    
    from Trimble.Vce.Geometry import Point3D, Line2D, Side, Vector3D, Triangle3D, Triangle2D
    from Trimble.Vce.Geometry.PolySeg import PolySeg
    clr.AddReference("Trimble.Vce.UI.ConstructionCommands")
    from Trimble.Vce.UI.ConstructionCommands import EditTextCmd
    clr.AddReference("Trimble.Vce.UI.UIManager")
    from Trimble.Vce.UI.UIManager import UIEvents
    clr.AddReference("Trimble.Vce.UI.Controls")
    from Trimble.Vce.Features.FeatureCoding import FeatureCodeObservation
    from Trimble.Vce.Interfaces.Client import CommandGranularity
    
    newStyleCompare = False
    # later version of TBC moved these to a different assembly. If not found in new location, look at old
    try:
        from Trimble.Sdk.UI import CheckedListBoxItemViewModel
        newStyleCompare = True
    except:
        newStyleCompare = False
    
    from Trimble.Vce.Core import GlobalSelection
    
    def Setup(cmdData, macroFileFolder):
        cmdData.Key = "AddFeatureCodeToPoint"
        cmdData.CommandName = "AddFeatureCodeToPoint"
        cmdData.Caption = "AddFeatureCodeToPoint"
        cmdData.UIForm = "AddFeatureCodeToPoint"
        
        try:
            cmdData.Version = 1.00
            cmdData.MacroAuthor = "Trimble"
    
            cmdData.DefaultTabKey = "Test"
            cmdData.DefaultTabGroupKey = "TestGroup"
            cmdData.ShortCaption = "Add Feature Code To Point"
            cmdData.ToolTipTitle = "Add Feature Code To Point"
        except:
            pass
    
    class AddFeatureCodeToPoint(StackPanel): # this inherits from the WPF StackPanel control
        def __init__(self, currentProject, macroFileFolder):
            with StreamReader(macroFileFolder + r"\AddFeatureCodeToPoint.xaml") as s:
                wpf.LoadComponent(self, s)
            self.currentProject = currentProject
            self.macroVersion = 1.00
    
        def OnLoad(self, cmd, buttons, event):
            self.Caption = cmd.Command.Caption
            self.objs.IsEntityValidCallback=self.IsValid
            self.pType = clr.GetClrType(IPoint)
        
        def CancelClicked(self, cmd, args):
            cmd.CloseUICommand()
    
        def IsValid(self, serial):
            o=self.currentProject.Concordance.Lookup(serial)
            return self.IsValidObj(o)
    
        def IsValidObj(self, obj):
            # needs to be a point type
            if isinstance(obj, self.pType):
                return True
            return False
    
        def OkClicked(self, sender, e):
            # tell undo manager we are starting to do something
            self.currentProject.TransactionManager.AddBeginMark(CommandGranularity.Command, self.Caption)
    
            for o in self.objs.SelectedMembers(self.currentProject):
                if not self.IsValidObj(o):
                    continue
                fco = FeatureCodeObservation(self.currentProject)
                fco.Populate(o)
                new_fco = FeatureCodeObservation(fco)
                new_fco.UpdateFeatureCode('test_')
                
                FeatureCodeObservation.SetDefaultAttributeValuesInNewFeatureCode(fco, new_fco)
    
                fco.UpdateFeatureNodes(new_fco.FeatureNodeList)
                new_fco.UpdateRawFeature()
            
            self.currentProject.TransactionManager.AddEndMark(Client.CommandGranularity.Command)
            sender.CloseUICommand()
    

    AddFeatureCodeToPoint.xaml

    <StackPanel  HorizontalAlignment="Stretch"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Wpf="clr-namespace:Trimble.Vce.UI.Controls.Wpf;assembly=Trimble.Vce.UI.Controls">
    
        <Label Content="Points:"  />
        <Wpf:MemberSelection x:Name="objs" />
    </StackPanel>
    



    ------------------------------
    Bryce Haire
    ------------------------------



  • 10.  RE: Altering Attribute Values

    Posted 03-18-2025 10:10

    That did the trick Bryce, thanks!

    It looks like wrapping all of it in a transaction was the problem. I refactored my C# base class to be able to turn a number of different features on and off, including transactions and UI Events. I haven't yet tried it with UI Events back on to be sure, and will get back to you when I've studied it more thoroughly.

    Now that the code works, I see that some other questions I had regarding attributes have been answered. I just have to move a few size values out of the feature code string and into attributes and I will be done!



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



  • 11.  RE: Altering Attribute Values

    Posted 03-18-2025 10:56

    Sounds good. Glad to help. 



    ------------------------------
    Bryce Haire
    ------------------------------