Question :
How would one go about making an Add Mesh operator re-entrant ? There are three use cases for this that I can see (1) You want to rebuild the underlying mesh for an object where you have a stack of modifiers you'd like to preserve, (2)You'd like to adjust a bad design choice from earlier and (3) You'd like to build a new object from an existing object given it's dimensions, think russian dolls or possibly puzzle pieces where the next outer doll/next puzzle piece depends upon the previous one, having access to the original piece makes for easy specification for the next.
Example :
An Add On providing a cube generator with variable length, height and width and registers 'Add Custom Cube' under the 'Add Mesh' menu. When selected the mesh is produced along with a panel displaying the above properties. When one changes a property Blender deletes the mesh and produces a new one with the updated dimensions. If one then creates some other mesh e.g. a plane, one looses the focus/history on the original mesh. Re-selecting the original cube does not show the panel with it's properties and it is no longer editable. The only way one might modify the cube is to delete it and create a new cube. Is it perhaps possible to delete/modify the mesh and not the object which would protect say a stack of modifiers applied to the object ?
Factors :
The following are what I consider relevant in answering this, as it appears a bit involved.
1) Re-entrancy
How does one make a mesh's properties editable across reselections ? How does one re-enter the same operator ? Is it possible to have two operators. The first performing object creation the second performing it's updates.
2) Identification
Is there a way to uniquely identify the mesh/object for later editing e.g. Could I name the object 'Custom Mesh' or some thing to that affect ? Should one rather identify it be it's properties i.e. other meshes may not have length, width and height attributes.
3) Properties
How does one attach the dimensions to the cube so that they may be edited later ? Presumably the operator will be re-instantiated when called resorting to it's defaults and not using the cubes current settings.
4) Registration
It would appear necessary to make the class that generates the mesh a subclass of both bpy.types.Operator and bpy.types.Panel allowing it to be visible both when the mesh is created and when it is edited. Would this require class registration at two places or does once suffice.
Homework :
From what I have read the following seems relevant :
1) Re-entrancy
If I have understood the documentation and API references then execute
is called to create the original mesh and invoke
is called whenever a pertinent event occurs and both are executed only if poll
returns true. Logically Add Mesh
would run execute
to make the object. Subsequently the mesh may be modified from invoke
. This hints towards re-entrancy, I suspect I'm aiming for method 2, some sort of scene updater.
2) Identification
Identification seems to be readily handled by merely naming the mesh. When execute is first called there is no object the poll function must account for this somehow. There after the object name reflects the name it was assigned as creation. I'm not sure it the user changes the object name or the mesh name when they fiddle about in the outliner area, presumably they affect both. Is it possible to assign two meshes to an object ?
3) Properties
There seems to be multiple means of specifying a property.
It seems standard practice to assign it globally to an bpy.types.Object
as in bpy.type.Object.prop = bpy.props.*Property
i.e. one makes the property available to both the torus and the monkey when it's really only meant for the custom cube. An alternative is to assign it globally to the window manager. The catch is that assigning to an object persists when one closes the *.blend assigining to the window manager does not. (Further alternatives include scenes etc.)
Being a local is lekker
kinda guy I'd assign dimensions locally to the mesh instance using me['prop'] = value
. This benefits from persisting once one closes the *.blend, but it does not appear possible to attach subclasses of bpy.types.PropertyGroup
or any bpy.prop.*Property
. Such properties must be sadly be assigned globally (This is due to the way the data is stored in the *.blend file or in as it is moved from Python to C). One may store state locally within an operator or panel (I believe this too does not persist when closing *.blends).
3) Registriation
I don't see how one would register a class twice. I'm not sure a second class is necessary either.
Code :
Below I have included what I've achieved so far, which is essentially create an object and append the dimensions locally to the mesh. I have not succeeded in re-entering the dimensions so the cycle "Create Custom Cube > Create Plane > Select Custom Cube > Adjust Dimensions" fails at Select Custom Cube.
# Package Dependencies
import bpy
import bpy_extras
#import bmesh
from math import *
from mathutils import *
# Package Information
bl_info = {
"name" : "NAME",
"location" : "View3D > Add > Mesh > New GEOMETRY",
"category" : "Add Mesh",
"description" : "This adds a GEOMETRY",
#"warning" : "Alpha Release, much still to do...",
"author" : "AUTHOR",
#"support" : "TESTING",
#"wiki_url" : "",
#"tracker_url" : "",
"version" : (0, 1),
"blender" : (2, 69, 0),
}
# Mesh Properties
class GEOMETRYProperties(bpy.types.PropertyGroup) :
"""GEOMETRY properties"""
height = bpy.props.FloatProperty(
name = "Height",
description = "The height",
default = 5.0,
unit = 'LENGTH')
width = bpy.props.FloatProperty(
name = "Width",
description = "The width",
default = 5.0,
unit = 'LENGTH')
length = bpy.props.FloatProperty(
name = "Length",
description = "The length",
default = 5.0,
unit = 'LENGTH')
class GEOMETRYPanel(bpy.types.Panel) :
bl_idname = "mesh.primitive_geometry_modify"
bl_label = "Modify GEOMETRY"
bl_description = "This panel modifies a GEOMETRY"
bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
def draw(self, context) :
layout = self.layout
layout.operator("mesh.primitive_geometry_add")
class MESH_OT_primitive_GEOMETRY_add(bpy.types.Operator, bpy_extras.object_utils.AddObjectHelper) :
"""Creates GEOMETRY"""
bl_idname = "mesh.primitive_geometry_add"
bl_label = "Add GEOMETRY"
bl_description = "This operator creates a GEOMETRY"
bl_options = {'REGISTER', 'UNDO'}# , 'PRESET'}
dims = bpy.props.PointerProperty(name ="Dimensions", type=GEOMETRYProperties)
def add_GEOMETRY(self, width, length, height) :
tvrts = [(width*cos(radians(theta)), length*sin(radians(theta)), height/2) for theta in range(0,360,90)]
bvrts = [(width*cos(radians(theta)), length*sin(radians(theta)), -height/2) for theta in range(0,360,90)]
verts = [item for list in zip(tvrts,bvrts) for item in list]
edges = []
faces = list(zip(range(0,len(verts),2),range(1,len(verts),2),range(3,len(verts),2),range(2,len(verts),2))) + [(len(verts)-1,len(verts)-2,0,1)]
return verts, edges, faces
def invoke(self, context, event) :
if context.object :
data = context.object.data
self.dims.length = data['dims']['length']
self.dims.width = data['dims']['width']
self.dims.height = data['dims']['height']
return self.execute(context)
def execute(self, context) :
me = bpy.data.meshes.new(name="GEOMETRY")
ob = bpy_extras.object_utils.object_data_add(context, me, operator=self)
v,e,f = self.add_GEOMETRY(self.dims.width, self.dims.length, self.dims.height)
me.from_pydata(v, e, f)
me.update()
me['dims'] = {'length' : self.dims.length,
'width' : self.dims.width,
'height' : self.dims.height}
return {"FINISHED"}
def draw(self, context) :
layout = self.layout
# Dimensions
col = layout.column()
col.prop(self.dims, "height")
col.prop(self.dims, "width")
col.prop(self.dims, "length")
# Alignment
#col = layout.column(align=True)
#col.prop(self, 'view_align')
# Location
#col = layout.column(align=True)
#col.label(text="Location")
#col.prop(self, 'location', text="")
# Rotation
#col = layout.column(align=True)
#col.label(text="Rotation")
#col.prop(self, 'rotation', text="")
def addGEOMETRYMenu(self, context) :
self.layout.operator(MESH_OT_primitive_GEOMETRY_add.bl_idname, text="Add GEOMETRY")
def register():
bpy.utils.register_class(GEOMETRYProperties)
bpy.utils.register_class(MESH_OT_primitive_GEOMETRY_add)
bpy.types.INFO_MT_mesh_add.append(addGEOMETRYMenu)
def unregister():
bpy.utils.unregister_class(MESH_OT_primitive_GEOMETRY_add)
bpy.utils.unregister_class(GEOMETRYProperties)
bpy.types.INFO_MT_mesh_add.remove(addGEOMETRYMenu)
if __name__ == "__main__" :
register(