04 Familiarizing Yourself with the Predefined Macros

Document created by David Kosakowski on May 1, 2018Last modified by David Kosakowski on May 7, 2018
Version 4Show Document
  • View in full screen mode

To familiarize yourself with the predefined macros and what types of controls and functions they include, double-click each one in the Solution Explorer and review the code as you will do for one macro now. If you open TBC and then launch a predefined macro, you can also press F1 to open the context-sensitive help topic.

Here are code walkthroughs of parts of a few of the installed macros:

  1. Double-click the ListProjectObjects.xaml and ListProjectObjects.py files to open them.

  2. Right-click the tab for each file and select New Vertical Tab Group so they all open side-by-side.

  3. In the Designer pane, select different UI controls to see the code for each (in brown text) in the XAML source view.

  4. In the .py file, review:

    a. The purpose of the command (in brown text at the top)

    b. The DLL (assembly) references that were added

    c. The command definition that is setup; this is what defines the file as a command in TBC and includes its key, name, caption, and UI form

    d. Where the associated .xaml file is referenced

    e. The other UI elements that are referenced

    f. Comments (in green text)

  5. Now open another specific file: AssignNamesFromInsideText.py.

  6. Review the setup information at the top. These lines reference the libraries and objects the macro uses.


      """
      Sample TBC macro to find text objects inside closed lines and assign the text string as the line name
      """
      import clr
      # Reference the WPF assemblies
      clr.AddReference('IronPython.Wpf')
      import wpf
      from System.IO import StreamReader
      from System.Windows.Controls import StackPanel
      clr.AddReference("Trimble.Vce.Core")
      from Trimble.Vce.Core.Components import WorldView, Project
      from Trimble.Vce.Core import TransactMethodCall
      clr.AddReference("Trimble.Vce.Geometry")
      from Trimble.Vce.Geometry import Point3D, Side
      clr.AddReference("Trimble.Vce.Alignment")
      from Trimble.Vce.Alignment import *
      clr.AddReference("Trimble.Vce.Interfaces")
      from Trimble.Vce.Interfaces import *
      clr.AddReference("Trimble.Vce.UI.Controls")
      from Trimble.Vce.UI.Controls import GlobalSelection
      clr.AddReference("Trimble.Vce.ForeignCad")
      from Trimble.Vce.ForeignCad import *

    For example, the macro uses Point3D from the geometry library (Trimble.Vce.Geometry), and also imports WorldView and Project from the core library (Trimble.Vce.Core).

  7. Review what the actual setup function does. This code is called when the macro is initially scanned on startup, and then it is saved to the dictionary. This code defines the setup and calls the command data that needs to be filled in.

    The key, command name, and caption are also defined. The UIForm assigns a class name that must match the class (AssignNameFromInsideText) for the StackPanel in the next section.

    The help file, context-sensitive topic ID, and icon are also defined in this section. You can also see how to check for and handle an exception, such as (for example) if the icon is not there; in this case, the exception is ignored with a "pass".


  8. # define the command key and name
    def Setup(cmdData):
      cmdData.Key = "AssignNameFromInsideText"
      cmdData.CommandName = "AssignNameFromInsideText"
      cmdData.Caption = "_Assign Name From Inside Text"
      cmdData.UIForm = "AssignNameFromInsideText"
      cmdData.HelpFile=Macros.chm"
      cmdData.HelpTopic-"22199"
      try:
       b = Bitmap(macroFileFolder + "\\" + cmdData.Key + ".bmp")
      cmdData.ImageSmall = b
       except:
        pass

    Notice that when you launch the command in TBC, the program asks if you have an execute method, if yes, it will run. Then it asks if you have defined a ui class that defines what the command will look like. creates an empty command shell and the class below because it does not have an execute method, but does have a UIForm. ADD

  9. Review the class created in the StackPanel.


    def __init__(self, currentProject):
        with StreamReader(MacroFileFolder + r"\AssignNameFromInsideText.xaml") as s:
            wpf.LoadComponent(self, s)
        self.currentProject = currentProject
    def OnLoad(self, cmd, btnOK, btnCancel, event):
        self.Caption = cmd.Command.Caption
        #self.objs.AddTypeFilter(clr.GetClrType(Text))
        #self.objs.AddTypeFilter(clr.GetClrType(MText))
        self.objs.IsEntityValidCallback=self.IsValid
        self.tType = clr.GetClrType(TextEntity)
        self.lType = clr.GetClrType(SnapIn.IPolyseg)
    def CancelClicked(self, sender, args):
        sender.CloseUICommand()
    def IsValid(self, serial):
        o=self.currentProject.Concordance.Lookup(serial)
        if isinstance(o, self.tType):
            return True
        if isinstance(o, self.lType):
            return True
        return False
    def OkClicked(self, sender, e):
        # tell undo manager we are starting to do something
         self.currentProject.TransactionManager.AddBeginMark(Client.CommandGranularity.Command, self.Caption
         wv = self.currentProject[Project.FixedSerial.WorldView]
        # pause the graphics so every change doesn't draw
        wv.PauseGraphicsCache(True)  

    The init is called when the command is created. After it's created, the onload method is called and that what creates the UI. Then you see an OnLoad call that sets things like the TextEntity and i.Polyseg (the polygonal linetype) when the command is starting up. You can also see what gets called in the code if you click Cancel. For the OK function, you see code similar to the C# code:

  • The TransactionManager adds a BeginMark so it knows that all the changes occurring are associated with this one step, so that Undo works. You could skip adding this, but then Undo will not name the change to be undone, and may also undo unexpected steps.

  • The WorldView graphics are paused because you do not want to update those on every small change, unless you are only changing several dozen objects.

  1. Now you get to code that does something. Two dictionaries are created for the text and line objects and told to put any closed shape lines and text objects in the appropriate ones once found. Then every object in the project is searched ("looped thru") to find objects that meet the line and text criteria, and then looped through once more to see which text objects are inside of the closed polygons (a method is called on the geometry that asks is the text's insertion point within the polyseg). If inside, the name of the text object is copied to the line object's name.


try:
# define dict for lines and text
foundTexts = {}
foundLines = {}
  # the "with" statement will unroll any changes if something go wrong
with TransactMethodCall(self.currentProject.TransactionCollector) as failGuard:
    # find text and boundaries
    for o in self.objs.SelectedMembers(self.currentProject):
        if isinstance(o, self.tType):
            foundTexts.Add(o, o.AlignmentPoint)
        elif isinstance(o, self.lType):
            polySeg = o.ComputePolySeg()
            if polySeg:
                if polySeg.IsClosed:
                    foundLines.Add(o, polySeg)
    count=0
    # loop thru the lines looking for text
    for lineObj,linePolyseg in foundLines.items():
        matchText=0
        for textObj,textPoint in foundTexts.items():
            # check if text is inside polySeg
            if linePolyseg.PointInPolyseg(textPoint) == Side.In:
                lineObj.Name=text  
  1. At the end, all of the changes are committed. This is done (using a "with" statement) so that if you hit an exception during the macro's process before reaching the commit, all of the changes will be rolled back so you do not get any partially done operations. REVISE


you MUST commit the change to DB or they will all be un-done (inside the "with" scope)
failGuard.Commit()
finally:
self.currentProject.TransactionManager.AddEndMark(Client.CommandGranularity.Command)
wv.PauseGraphicsCache(False)
return True # return True to close cmd U

For a macro with UI, there is a second part:

  1. The XAML code is a description of what the UI should look like. It is simple code with a couple of namespace declarations, a label, and a member selection in the stackpanel.

    <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="Select Lines / Text:" /> <wpf:MemberSelection x:Name="objs" />


</StackPanel>

Another example

Here is code from another installed macro: AddIsopachTrimble Business Center caption: Add IsopachPurpose: Add delta elevations to existing surface; represents original plus the scaled thickness adds elevations; scale can be zero, so you can trim off to account for shrink swell factors; creates a new surface based on an isopach and a reference surface where you can adjust the differences, such as only using the positive elevationsIncluded controls: Label, TextBox, wpf:NumericEdit, wpf:ComboBoxEntityPicker, OK, cancel

First, you are referencing assemblies, such as Model3D as the surface class:

# Reference the WPF assemblies
import clr
clr.AddReference('IronPython.Wpf')
import wpf
clr.AddReference("Trimble.Vce.Core")
from Trimble.Vce.Core.Components import WorldView, Project
from Trimble.Vce.Core import TransactMethodCall
from System import Type, Tuple, Array, Double
from System.IO import StreamReader
from System.Collections.Generic import List
from System.Windows import  Window, ResourceDictionary
from System.Windows.Controls import StackPanel
from System.Windows.Input import Keyboard

clr.AddReference("Trimble.Vce.Interfaces")
from Trimble.Vce.Interfaces.Client import CommandGranularity
clr.AddReference("Trimble.Vce.UI.Controls")
from Trimble.Vce.UI.Controls import SurfaceTypeLists
clr.AddReference("Trimble.Vce.Gem")
from Trimble.Vce.Gem import Model3D
clr.AddReference("Trimble.Vce.Geometry")
from Trimble.Vce.Geometry import Point3D

On load, you set factors, such as the elevation range:


def OnLoad(self, cmd, btnOK, btnCancel, event):
       self.Caption = cmd.Command.Caption
       t = Tuple.Create[Array[Type],Project](Array[Type](SurfaceTypeLists.AllWithCutFillMap), self.currentProject)
       self.isopachSurface.FilterByEntityTypes = t
       self.refSurface.FilterByEntityTypes = t
       self.isopachSurface.AllowNone = False
       self.refSurface.AllowNone = False
       self.posFactor.Value = 1.0
       self.negFactor.Value = 1.0
       self.posFactor.MaxValue = 10000.0
       self.negFactor.MaxValue = 10000.0
       self.posFactor.MinValue = 10000.0
       self.negFactor.MinValue = 10000.0

On the click, you get the name of the surface you want (GetUniqueName helper class that says it must be a unique name), and makes sure you have actually selected both the isopach and reference surface:


def OkClicked(self, sender, e):
       wv = self.currentProject[Project.FixedSerial.WorldView]
       surName = self.surfaceName.Text
       surName = Model3D.GetUniqueName(surName, None, wv) #make sure name is unique
       if not surName:
           # empty name, need to report name error
           Keyboard.Focus(self.surfaceName)
           return False
       if self.isopachSurface.SelectedSerial == 0:
           self.isopachSurface.StatusMessage = "Enter valid scale factor"
           return False
       if self.refSurface.SelectedSerial == 0:
           return False
       posF = self.posFactor.Value
       if Double.IsNaN(posF):
           self.posFactor.StatusMessage = "Enter valid scale factor"
           return False
       negF = self.negFactor.Value
       if Double.IsNaN(negF):
           self.posFactor.StatusMessage = "Enter valid scale factor"
           return False

Valid factor is checking the numbers. Start with a 'with' transact method call. all of this goes together. You tell WorldView to add a Model3D, which returns the defined surface. Assign a name and then go through and copy a set of triangles, reference one, and then go through and copy the set of triangles from the reference surface. Then go through and adjust the elevation of the specific points based on how they compare to that specific model, setting the elevation at each vertex point.


# the "with" statement will unroll any changes if something go wrong
       with TransactMethodCall(self.currentProject.TransactionCollector) as failGuard:
           newSurface = wv.Add(clr.GetClrType(Model3D))
           newSurface.Name = surName

           refS = wv.Lookup(self.refSurface.SelectedSerial)
           sur = wv.Lookup(self.isopachSurface.SelectedSerial)
           #get copy of vertices and trianges.
           gem1 = sur.GemCopy
           v = 0
           while v < gem1.NumberOfVertices:
               p = gem1.GetVertexPoint(v)
               rtn = refS.PickSurface(p)
               if rtn[0]:
                   if p.Z > 0:
                       z = p.Z * posF
                   else:
                       z = p.Z * negF
                   z = rtn[1].Z + z
                   p = Point3D(p.X, p.Y, z)
                   gem1.SetVertexPoint(v, p)
               v = v + 1

And Another Macro Example

VS file name: SwitchTextTrimble Business Center caption: Switch TextPurpose: Switch the text string of two text objects, including options on what text properties to include in the swap.Included controls: StackPanel, Label, wpf:SelectEntity, Expander, CheckBox (options for what you want to switch)

Good for: OnLoad give me only TextTypes, SetDefaulOptions, Optionmanager saves the values so the next time you run it, settings will persist. OptionsManager saves externally in an Options.bin file so defaults are same from project to project (or use ? class that is stored with the project). Clicking OK gets two pieces of text.

This code is fairly simple. There is a little bit for startup:


def Setup(cmdData, macroFileFolder):
   cmdData.Key = "SwitchText"
   cmdData.CommandName = "SwitchText"
   cmdData.Caption = "_Switch Text"
   cmdData.UIForm = "SwitchText"
   cmdData.HelpFile="Macros.chm"
   cmdData.HelpTopic="22206"
   try:
       b = Bitmap(macroFileFolder + "\\" + cmdData.Key + ".bmp")
       cmdData.ImageSmall = b
   except:
       pass

You could copy this as a prototypical startup. Now on load you tell the program you only want text type objects


def OnLoad(self, cmd, btnOK, btnCancel, event):
       self.Caption = cmd.Command.Caption
       btnOK.Content = "Apply"
       btnCancel.Content = "Close"
       self.tObj1.AddTypeFilter(clr.GetClrType(Text))
       self.tObj1.AddTypeFilter(clr.GetClrType(MText))
       self.SetDefaultOptions()

Now set default options using a class called OptionsManager that saves values so next time you run the macro it will use the same values.


def SetDefaultOptions(self):
       self.switchText.IsChecked = OptionsManager.GetBool("SwitchText.switchText", True)
       self.switchHeight.IsChecked = OptionsManager.GetBool("SwitchText.switchHeight", False)
       self.switchWidthFactor.IsChecked = OptionsManager.GetBool("SwitchText.switchWidthFactor", False)
       self.switchRotation.IsChecked = OptionsManager.GetBool("SwitchText.switchRotation", False)
       self.switchObliqueAngle.IsChecked = OptionsManager.GetBool("SwitchText.switchObliqueAngle", False)
       self.switchJusification.IsChecked = OptionsManager.GetBool("SwitchText.switchJusification", False)
       self.switchTextStyle.IsChecked = OptionsManager.GetBool("SwitchText.switchTextStyle", False)
       self.switchLayer.IsChecked = OptionsManager.GetBool("SwitchText.switchLayer", False)
       self.switchColor.IsChecked = OptionsManager.GetBool("SwitchText.switchColor", False)

OptionsManager saves the values outside in the options.bin file, so if you are in a different project, the options are the same, or you can save the options with the project.

When you click Okay, the code gets the two pieces of text, runs through and sets the first text string to the second and vice versa using if/then statements.

Question: How about a macro in which when you click on a line, it reports the midpoint as a coordinate?

Answer: You could use the Assign Names from Inside Text macro, but replace the multi-select control with a single select control so that when you pick the line, you get an event that says "this value has changed" so you get the current value in the control. To get geometry, you use code like this:


# the "with" statement will unroll any changes if something go wrong
           with TransactMethodCall(self.currentProject.TransactionCollector) as failGuard:
               # find text and boundaries
               for o in self.objs.SelectedMembers(self.currentProject):
                   if isinstance(o, self.tType):
                       foundTexts.Add(o, o.AlignmentPoint)
                   elif isinstance(o, self.lType):
                       polySeg = o.ComputePolySeg()
                       if polySeg:
                           if polySeg.IsClosed:
                               foundLines.Add(o, polySeg)
               count=0
               # loop thru the lines looking for text
               for lineObj,linePolyseg in foundLines.items():
                   matchText=0
                   for textObj,textPoint in foundTexts.items():
                       # check if text is inside polySeg
                       if linePolyseg.PointInPolyseg(textPoint) == Side.In:
                           lineObj.Name=textObj.TextString

Polyseg is a geometry class that generically defines all the points, arcs, and lines and you ask this linework "give me your midpoint" and report it in a label box. This might take 30 minutes to write.

Next topic: 05 Understanding the Object Model Framework, Etc.

Attachments

    Outcomes