PlaneTool example

The code below implements a simple tool plugin that demonstrates how to create geometry using Python. It essentially creates a plane on the active layer. Hauling in the viewport changes its size.

There’s no support for specifying the origin of the tool, there’s no support for snapping, workplane, falloffs, tool handles…etc. It’s really really simple. It doesn’t even have a form for displaying the properties that drive the tool. It’s just a very basic example of a tool.

To activate the tool, type “tool.set prim.plane on” into the command history.

‘’’Note’’’: As a plugin, the python source file needs to be placed in an ‘lxserv’ folder.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#!python

'''

    A simple tool plugin to demonstrate geometry creation. A plane is
    created on the active mesh layer, with the size controlled by hauling
    in the viewport.

    Tools are rather advanced and there is a lot of potential for taking
    this example further. For example, drawing tool handles, defining the
    origin of the geometry creation, support for falloffs, generating UVs...etc.

    Author: Matt Cox

'''

import lx
import lxifc
import lxu.attributes

class Plane_Tool(lxifc.Tool, lxifc.ToolModel, lxu.attributes.DynamicAttributes):

    def __init__(self):
        lxu.attributes.DynamicAttributes.__init__(self)

        '''
            Our tool is going to have a single value that controls how it
            operates. This value will control the size of the plane geometry
            that we create. So we create a new Dynamic Attribute, who's type
            is float and it's initial value is 1.0.
        '''
        self.dyna_Add('size', lx.symbol.sTYPE_FLOAT)
        self.attr_SetFlt(0,1.0)

        '''
            Allocate a vector type. We're setting the mimimum required here,
            but we could add some packets to the vector stack, that would
            allow us to do things like work with symmetry or falloffs.
        '''
        pkt_svc = lx.service.Packet()
        self.vec_type = pkt_svc.CreateVectorType(lx.symbol.sCATEGORY_TOOL)

    def tool_Reset(self):
        '''
            This function is called to reset the tool back to it's default.
            In our case, we are simply setting the plane size attribute
            back to the default of 1m.
        '''
        self.attr_SetFlt(0,1.0)

    def tool_Evaluate(self,vts):
        '''
            This is where our tool is actually evaluated. We're going to get
            the current layers and loop through them, generating new vertices
            and polygons, defined by the size attribute.
        '''

        '''
            We'll be using the LayerService to interact with meshes in the
            scene. We also want to localize a LayerScan object using the
            ScanAllocate method. The symbol "f_LAYERSCAN_EDIT", tells modo
            that we want to scan active layers and edit the mesh. See the SDK
            wiki for the declaration of this symbol.
        '''
        layer_svc = lx.service.Layer()
        layer_scan = lx.object.LayerScan(layer_svc.ScanAllocate(lx.symbol.f_LAYERSCAN_EDIT))

        if layer_scan.test() == False:
            return

        '''
            We have a single attribute that defines the size of the plane. We
            read it's value using the index or the order the attributes were
            added in the constructor. As we only have a single attribute on
            this tool, it's clearly going to be 0.
        '''
        size_attr = self.attr_GetFlt(0)

        '''
            We're going to operate on all active layers, so we simply want
            to loop through the active layers and perform some operation.
        '''
        for n in range(0,layer_scan.Count()):
            '''
                We want to grab the Edit Mesh on the current layer and
                localize it into a Mesh object, so that we can edit it.
            '''
            mesh_loc = lx.object.Mesh(layer_scan.MeshEdit(n))

            if mesh_loc.test() == False:
                continue

            '''
                As we are editing both Points and Polygons, we need to get
                some Point and Polygon interfaces from the mesh.
            '''
            point_loc = lx.object.Point(mesh_loc.PointAccessor())
            poly_loc = lx.object.Polygon(mesh_loc.PolygonAccessor())

            if poly_loc.test() == False or point_loc.test() == False:
                continue

            '''
                Now we are going to construct the plane geometry. To keep things
                simple, we're going to loop through a statement that creates 4
                new points. For each of the points, we'll store their ID in a
                list. Finally, we'll iterrate over the list and create the
                new polygon from the four points.
            '''
            points = ()
            for point in range(0,4):
                '''
                    First we want to calculate a position for the new point.
                '''
                point_pos = list()
                if point == 0:
                    point_pos = ((-size_attr)/2,0.0,(-size_attr)/2)
                elif point == 1:
                    point_pos = ((-size_attr)/2,0.0,(size_attr)/2)
                elif point == 2:
                    point_pos = ((size_attr)/2,0.0,(size_attr)/2)
                elif point == 3:
                    point_pos = ((size_attr)/2,0.0,(-size_attr)/2)

                '''
                    Create a new point. This method takes a position and
                    return a PointID, so we pass it the position vector we
                    created.
                '''
                current_point = point_loc.New(point_pos)

                '''
                    We have to pass a list of points to the Polygon New()
                    method. So we add the current PointID to the Points tuple.
                '''
                points = points + (current_point,)

            '''
                The Polygon New() method requires the list of points to be
                passed as a Storage Buffer. So we create a storage buffer, set
                it's type to store pointers, set the size to be number of points
                we intend to store in it and then finally, write out Points
                tuple to it.
            '''
            points_storage = lx.object.storage()
            points_storage.setType('p')
            points_storage.setSize(4)
            points_storage.set(points)

            '''
                Now we have our point positions, we create a new polygon.
            '''
            poly_loc.New(lx.symbol.iPTYP_FACE,points_storage,4,0)

            '''
                Before we move on to the next layer, we tell modo that have
                edited this mesh layer.
            '''
            layer_scan.SetMeshChange(n, lx.symbol.f_MESHEDIT_GEOMETRY)

        '''
            Finally, we call the Apply function, which closes our LayerScan
            interface and applies all our changes to the mesh.
        '''
        layer_scan.Apply()

    def tool_VectorType(self):
        '''
            This function simply returns the tool VectorType that we created
            in the constructor.
        '''
        return self.vec_type

    def tool_Order(self):
        '''
            This sets the position of the tool in the toolpipe.
        '''
        return lx.symbol.s_ORD_ACTR

    def tool_Task(self):
        '''
            Simply defines the type of task performed by this tool. We set
            this to an Action tool, which basically means it will alter the
            state of modo.
        '''
        return lx.symbol.i_TASK_ACTR

    def tmod_Flags(self):
        '''
            This sets how we intend to interact with the tool. The symbol
            "fTMOD_I0_ATTRHAUL" basically says that we expect to haul an
            attribute when clicking and dragging with the left mouse button.
        '''
        return lx.symbol.fTMOD_I0_ATTRHAUL

    def tmod_Initialize(self,vts,adjust,flags):
        '''
            This is called when the tool is activated. We use it to simply
            set the attribute that we hauling back to the default.
        '''
        adj_tool = lx.object.AdjustTool(adjust)
        adj_tool.SetFlt(0, 1.0)

    def tmod_Haul(self,index):
        '''
            Hauling is dependent on the direction of the haul. So a vertical
            haul can drive a different parameter to a horizontal haul. The
            direction of the haul is represented by an index, with 0
            representing horizontal and 1 representing vertical. The function
            simply returns the name of the attribute to drive, given it's index.
            As we only have one attribute, we'll set horizontal hauling to
            control it and vertical hauling to do nothing.
        '''
        if index == 0:
            return "size"
        else:
            return 0

'''
    "Blessing" the class promotes it to a fist class server. This basically
    means that modo will now recognize this plugin script as a tool plugin.
'''
lx.bless(Plane_Tool, "prim.plane")