Developper guide¶
Want to contribute ? add your own parametric object ?
Archipack’s api provide all needed parts to handle parametric objects, as easy to import subclasses, so you may focus on your base object mesh building. Here you’ll find a “quick start” dev guide with all required parts.
Imports¶
Implementation sample¶
import bpy
from bpy.types import Operator, PropertyGroup, Mesh, Panel
# Minimal required property types
from bpy.props import (
FloatProperty, BoolProperty, CollectionProperty
)
from mathutils import Vector
# Bmesh made easy
from .bmesh_utils import BmeshEdit as bmed
# Manipulable
from .archipack_manipulator import Manipulable
# Preset system
from .archipack_preset import ArchipackPreset, PresetMenuOperator
# Base for Propertygroup and create tool Operator
from .archipack_object import ArchipackCreateTool, ArchipackObject
PropertyGroup¶
The main propertyGroup is the heart of parametric object, generating mesh on parameter changes using bmesh, and updating “manipulators” location according.
Note
Separate properties for vertex, faces, material indexes and uvmaps is not required, made like this as for such simple object it allow to keep code clean. More complex objects may require to build uvs maps knowing vertex location, and such design pattern would not work.
Naming convention¶
archipack_ prefix, then your meanfull class name
Requirements¶
MUST inherit from ArchipackObject and Manipulable
MUST implement update() function generating / refreshing mesh and manipulators location, see Updating manipulator location
MUST implement setup_manipulators() function filling manipulators Collection see Available manipulators (type_key)
MUST have auto_update property to disable mesh updating for bulk updates
Properties SHOULD call update (use update=update in property definition)
Implementation sample (a box)¶
def update(self, context):
self.update(context)
class archipack_myobject(ArchipackObject, Manipulable, PropertyGroup):
""" Archipack toolkit sample"""
x = FloatProperty(
name="Width",
default=2.0, min=0.01,
unit='LENGTH', subtype='DISTANCE',
update=update
)
y = FloatProperty(
name="Depth",
default=2.0, min=0.01,
unit='LENGTH', subtype='DISTANCE',
update=update
)
z = FloatProperty(
name="Height",
default=2.0, min=0.01,
unit='LENGTH', subtype='DISTANCE',
update=update
)
auto_update = BoolProperty(
# Wont save auto_update state in any case
options={'SKIP_SAVE'},
default=True,
update=update
)
@property
def verts(self):
"""
Object vertices coords
"""
x = 0.5 * self.x
y = 0.5 * self.y
z = self.z
return [
(-x, y, 0),
(-x, -y, 0),
(x, -y, 0),
(x, y, 0),
(-x, y, z),
(-x, -y, z),
(x, -y, z),
(x, y, z)
]
@property
def faces(self):
"""
Object faces vertices index
"""
return [
(0, 1, 2, 3),
(7, 6, 5, 4),
(7, 4, 0, 3),
(4, 5, 1, 0),
(5, 6, 2, 1),
(6, 7, 3, 2)
]
@property
def uvs(self):
"""
Object faces uv coords
"""
return [
[(0, 0), (0, 1), (1, 1), (1, 0)],
[(0, 0), (0, 1), (1, 1), (1, 0)],
[(0, 0), (0, 1), (1, 1), (1, 0)],
[(0, 0), (0, 1), (1, 1), (1, 0)],
[(0, 0), (0, 1), (1, 1), (1, 0)],
[(0, 0), (0, 1), (1, 1), (1, 0)]
]
@property
def matids(self):
"""
Object material indexes for each face
"""
return [0, 0, 0, 0, 0, 0]
def setup_manipulators(self):
if len(self.manipulators) < 1:
# add manipulator for x property
s = self.manipulators.add()
s.prop1_name = "x"
s.type_key = 'SIZE'
# add manipulator for y property
s = self.manipulators.add()
s.prop1_name = "y"
s.type_key = 'SIZE'
# add manipulator for z property
s = self.manipulators.add()
s.prop1_name = "z"
s.type_key = 'SIZE'
# draw this one on xz plane
s.normal = Vector((0, 1, 0))
def update(self, context):
# provide support for "copy to selected"
o = self.find_in_selection(context, self.auto_update)
if o is None:
return
# dynamically create manipulators when needed
self.setup_manipulators()
# update your mesh from parameters
bmed.buildmesh(context,
o,
self.verts,
self.faces,
matids=self.matids,
uvs=self.uvs,
weld=False)
# update manipulators location (3d location in object coordsystem)
x, y = 0.5 * self.x, 0.5 * self.y
self.manipulators[0].set_pts([(-x, -y, 0), (x, -y, 0), (1, 0, 0)])
self.manipulators[1].set_pts([(-x, -y, 0), (-x, y, 0), (-1, 0, 0)])
self.manipulators[2].set_pts([(x, -y, 0), (x, -y, self.z), (-1, 0, 0)])
# always restore context
self.restore_context(context)
Panel (T panel in UI)¶
Main ui panel to modify object properties.
Naming convention¶
ARCHIPACK_PT_ prefix then your object name
Requirements¶
MUST poll using your PropertyGroup class
MUST call manipulate operator
MUST call presets operators
Implementation sample (a box)¶
class ARCHIPACK_PT_myobject(Panel):
bl_idname = "ARCHIPACK_PT_myobject"
bl_label = "MyObject"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'ArchiPack'
@classmethod
def poll(cls, context):
# ensure your object panel only show when active object is the right one
return archipack_myobject.filter(context.active_object)
def draw(self, context):
o = context.active_object
if not archipack_myobject.filter(o):
return
layout = self.layout
# retrieve datablock of your object
props = archipack_myobject.datablock(o)
# Manipulate mode operator
layout.operator('archipack.myobject_manipulate', icon='HAND')
box = layout.box()
row = box.row(align=True)
# Presets operators
row.operator("archipack.myobject_preset_menu",
text=bpy.types.ARCHIPACK_OT_myobject_preset_menu.bl_label)
row.operator("archipack.myobject_preset",
text="",
icon='ZOOMIN')
row.operator("archipack.myobject_preset",
text="",
icon='ZOOMOUT').remove_active = True
row = layout.row()
box = row.box()
box.label(text="Size")
box.prop(props, 'x')
box.prop(props, 'y')
box.prop(props, 'z')
Create Tool¶
This operator implement creation tool for your object. You may add it in N Panel and/or shift+A menu to allow users to create your objects.
Naming convention¶
ARCHIPACK_OT_ prefix and then your object name bl_idname name is archipack. prefix and then your object name
Requirements¶
MUST inherit from ArchipackCreateTool
bl_idname MUST match your object’s class name
MUST call load_preset, passing your class property datablock
MUST create an object, a mesh and link to scene
MUST add your PropertyGoup
Implementation sample¶
class ARCHIPACK_OT_myobject(ArchipackCreateTool, Operator):
bl_idname = "archipack.myobject"
bl_label = "Myobject"
bl_description = "Create Myobject"
bl_category = 'Archipack'
bl_options = {'REGISTER', 'UNDO'}
def create(self, context):
# Create an empty mesh datablock
m = bpy.data.meshes.new("Myobject")
# Create an object using the mesh datablock
o = bpy.data.objects.new("Myobject", m)
# Add your properties on mesh datablock
d = m.archipack_myobject.add()
# Link object into scene
context.scene.objects.link(o)
# select and make active
o.select = True
context.scene.objects.active = o
# Load preset into datablock
self.load_preset(d)
# add a material
self.add_material(o)
return o
def execute(self, context):
if context.mode == "OBJECT":
bpy.ops.object.select_all(action="DESELECT")
o = self.create(context)
o.location = bpy.context.scene.cursor_location
o.select = True
context.scene.objects.active = o
# Start manipulate mode
self.manipulate()
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
return {'CANCELLED'}
Preset add/destroy operator¶
Preset system require two operators, this one allow user to create and destroy presets.
Naming convention¶
Use ARCHIPACK_OT_ prefix and then your object name then _preset bl_idname name is archipack. prefix and then your object name then _preset
Requirements¶
MUST define preset_menu using the class name of preset menu operator
MAY define blacklist, an array of property names you don’t want to save in presets
Implementation sample¶
class ARCHIPACK_OT_myobject_preset(ArchipackPreset, Operator):
"""Add a Myobject Preset"""
bl_idname = "archipack.myobject_preset"
bl_label = "Add Myobject preset"
preset_menu = "ARCHIPACK_OT_myobject_preset_menu"
@property
def blacklist(self):
return ['manipulators']
Manipuate Operator¶
This operator allow user to enter/exit from “manipulate” mode.
Naming convention¶
Use ARCHIPACK_OT_ prefix and then your object name then _manipulate bl_idname name is archipack. prefix and then your object name then _manipulate
Requirements¶
MUST poll using your propertyGroup class
MUST call .manipulable_invoke(context)
Implementation sample¶
class ARCHIPACK_OT_myobject_manipulate(Operator):
bl_idname = "archipack.myobject_manipulate"
bl_label = "Manipulate"
bl_description = "Manipulate"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(self, context):
return archipack_myobject.filter(context.active_object)
def invoke(self, context, event):
d = archipack_myobject.datablock(context.active_object)
d.manipulable_invoke(context)
return {'FINISHED'}
Register/Unregister¶
Each archipack object must register / unregister itself
Requirements¶
PropertyGroup MUST register/unregister and be added as CollectionProperty of Mesh
Panel, create, preset and manipulate Operators MUST register/unregister
Implementation sample¶
def register():
bpy.utils.register_class(archipack_myobject)
Mesh.archipack_myobject = CollectionProperty(type=archipack_myobject)
bpy.utils.register_class(ARCHIPACK_PT_myobject)
bpy.utils.register_class(ARCHIPACK_OT_myobject)
bpy.utils.register_class(ARCHIPACK_OT_myobject_preset_menu)
bpy.utils.register_class(ARCHIPACK_OT_myobject_preset)
bpy.utils.register_class(ARCHIPACK_OT_myobject_manipulate)
def unregister():
bpy.utils.unregister_class(archipack_myobject)
del Mesh.archipack_myobject
bpy.utils.unregister_class(ARCHIPACK_PT_myobject)
bpy.utils.unregister_class(ARCHIPACK_OT_myobject)
bpy.utils.unregister_class(ARCHIPACK_OT_myobject_preset_menu)
bpy.utils.unregister_class(ARCHIPACK_OT_myobject_preset)
bpy.utils.unregister_class(ARCHIPACK_OT_myobject_manipulate)
Modify the __init__.py¶
Imports¶
imp.reload(archipack_myobject)
from . import archipack_myobject
Create panel¶
class TOOLS_PT_Archipack_Create(Panel) in draw function:
col = row.column()
subrow = col.row(align=True)
subrow.operator("archipack.myobject_preset_menu",
text="myobj",
icon_value=icons["door"].icon_id
).preset_operator = "archipack.myobject"
subrow.operator("archipack.myobject_preset_menu",
text="",
icon='GREASEPENCIL'
).preset_operator = "archipack.myobject_draw"
Register and unregister function¶
archipack_myobject.register()
archipack_myobject.unregister()