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