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.
#!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): ''' Create a new point. This *should* take a position and return a PointID, but it seems to be working incorrectly at the moment, so it assigns a random position and returns both the position and point ID. ''' current_point = point_loc.New() ''' Modo *should* make the new point the active point, so we can work on it straight away, but it doesn't. Thankfully, one of the returned values from the New() function is the PointID. So we can simply select the newly created point by it's ID and work on it. ''' point_loc.Select(current_point) ''' Now we have the new point selected, we need to create a position vector and set the new points position. ''' 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) point_loc.SetPos(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")