Spherize example

The code below implements a simple command plugin, demonstrating how to edit geometry using Python. It essentially takes all the points on the active layer and pushes them out to “spherize” the mesh.

To run the command, select at least one mesh layer and type “layer.spherize” into the command history.

‘’’Note’’’: As a custom command, 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
#python

'''

    A simple command plugin to demonstrate performing a spherize
    operation on all points in the selected layer.

    Author: Matt Cox

'''

import lx
import lxifc
import lxu.command
import lxu.select
import math

class Spherize_Cmd(lxu.command.BasicCommand):
    def __init__(self):
        lxu.command.BasicCommand.__init__(self)
        '''
            We want to have a single argument for this command. It will
            define the distance to push the geometry out by. This is an
            optional command and if it is undefined, we'll use a set distance
            of 1.0.
        '''
        self.dyna_Add('distance', lx.symbol.sTYPE_FLOAT)
        self.basic_SetFlags(0, lx.symbol.fCMDARG_OPTIONAL)

    def cmd_Flags(self):
        '''
            We also want to define some other flags for the command. Namely,
            fCMD_MODEL and fCMD_UNDO. This basically tells modo that we are
            performing an action that changes the internal state of the program,
            and the command should undoable/redoable.
        '''
        return lx.symbol.fCMD_MODEL | lx.symbol.fCMD_UNDO

    def basic_Enable(self, msg):
        '''
            We only want the command to be enabled if there is at least one mesh
            item selected. This is quite an easy test, we simply get a list
            of the current selected items and loop through them checking if they
            are a mesh item. As soon as we find a mesh, we return True. If we
            don't find a mesh, we return False.
        '''
        item_sel = lxu.select.ItemSelection()
        for i in range(0, len(item_sel.current())):
            '''
                Localize the current selected item.
            '''
            item_loc = lx.object.Item(item_sel.current()[i])

            '''
                Check that we actually have a localized item to work with.
            '''
            if item_loc.test() == False:
                continue

            '''
                Now we simply check if the item is a mesh or not, to do this,
                we need to query the SceneService for the Mesh item type and
                then check whether the current selection matches that type.
            '''
            scn_svc = lx.service.Scene()
            mesh_type = scn_svc.ItemTypeLookup(lx.symbol.sITYPE_MESH)
            if item_loc.TestType(mesh_type):
                return True

        '''
            We have been unable to find a mesh item, so we'll return False and
            disable the command. Ideally, we'd set a message for the user to
            tell them why the command is disabled.
        '''
        return False


    def basic_Execute(self, msg, flags):
        '''
            Here is where the "meat" of our command will go. Assuming that our
            command has passed the basic_Enable method, this function will be
            called to Execute our command.
        '''

        '''
            We want to get any arguments for the command, these are simply read
            by their index, in the order they are added to the constructor.
            As the only argument for this command is optional, we'll query
            whether it's Set, or whether we need to assume a default value.
        '''
        if self.dyna_IsSet(0) == True:
            target_dist = self.attr_GetFlt(0)
        else:
            target_dist = 1.0

        '''
            We'll be using the LayerService to interact with meshes in the
            scene. So the first step is to get a LayerService interface.
        '''
        layer_svc = lx.service.Layer()

        '''
            Now we want to iterate through the active layers. We do this
            using the LayerScan interface. We have to localize a LayerScan
            interface using the LayerService ScanAllocate method.
            The symbol "f_LAYERSCAN_EDIT_VERTS", tells modo that we want
            to scan active layers and edit the mesh. See the sdk wiki
            for the declaration of this symbol.
        '''
        layer_scan = lx.object.LayerScan(layer_svc.ScanAllocate(lx.symbol.f_LAYERSCAN_EDIT_VERTS))

        '''
            We'll just check that the LayerScan item localized correctly.
        '''
        if layer_scan.test() == False:
            return

        '''
            Now we simply want to iterrate through all the active layers and
            perform an operation on each of them. So we count the number of
            layers using the LayerScan interface and then loop through them.
        '''
        for n in range(0, layer_scan.Count()):
            '''
                Now we are on the current layer, we want to grab the mesh
                for the current layer. Then we can perform operations on it.
            '''
            mesh_loc = lx.object.Mesh(layer_scan.MeshEdit(n))

            '''
                As always, just confirm that we have correctly localized
                the mesh.
            '''
            if mesh_loc.test() == False:
                continue

            '''
                We also want to check that the point count is greater than
                zero. There's no real requirement to do this, but it makes
                things a little cleaner.
            '''
            if mesh_loc.PointCount() == 0:
                continue

            '''
                So that we can find the center of all the vertices, we'll simply
                get the bounding box of the mesh layer. The bounding box is defined
                by two vectors representing opposite corners of the bounding box.
                Once we have the bounding box, we calculate it's center.
            '''
            mesh_bounds = mesh_loc.BoundingBox(lx.symbol.iMARK_ANY)
            mesh_center = ((mesh_bounds[0][0]+mesh_bounds[1][0])/2,(mesh_bounds[0][1]+mesh_bounds[1][1])/2,(mesh_bounds[0][2]+mesh_bounds[1][2])/2)

            '''
                Here we want to iterate through all the points on the
                current mesh. Ideally, this would be done using a Visitor,
                but for simplicity sake in this example, we'll use a for loop.
            '''
            for i in range(0, mesh_loc.PointCount()):
                '''
                    Before we operate on the point, we need to localize the
                    point we want to work with.
                '''
                point_loc = lx.object.Point(mesh_loc.PointAccessor())

                '''
                    Yet again, we just double check that we have correctly
                    localized the point object, if we have then we select the
                    point we want to work with from its index.
                '''
                if point_loc.test() == False:
                    continue

                point_loc.SelectByIndex(i)

                '''
                    We'll simply get the point position and measure the distance
                    from the point to the mesh_center. We'll then divide the
                    target distance by the distance and then multiply the position
                    vector by the resulting calculation. This should move the point
                    to the desired distance along it's current vector.
                '''
                point_pos = point_loc.Pos()
                point_dist = math.sqrt(math.pow((point_pos[0]-mesh_center[0]),2)+math.pow((point_pos[1]-mesh_center[1]),2)+math.pow((point_pos[2]-mesh_center[2]),2))

                if point_dist == 0:
                    continue

                scale = target_dist / point_dist
                point_newPos = ((point_pos[0]*scale),(point_pos[1]*scale),(point_pos[2]*scale))

                '''
                    Now that we have calculated the new position, we want to set
                    the point position on the mesh.
                '''
                point_loc.SetPos(point_newPos)

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

        '''
            Finally, we need to call apply on the LayerScan interface. This tells
            modo to perform all the mesh edits.
        '''
        layer_scan.Apply()

'''
    "Blessing" the class promotes it to a fist class server. This basically
    means that modo will now recognize this plugin script as a command plugin.
'''
lx.bless(Spherize_Cmd, "layer.spherize")