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.

Download sample file

Imports

Implementation sample

Minimal required imports
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)

Minimal implementation using all features
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)

Minimal implementation using all features
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

Minimal implementation using all features
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 menu operator

Preset system require two operators, this one display graphical preset menu.

Naming convention

ARCHIPACK_OT_ prefix and then your object name then _preset_menu bl_idname name is archipack. prefix and then your object name then _preset_menu

Requirements

  • MUST define preset_subdir using PropertyGroup class name

Implementation sample

Minimal implementation using all features
class ARCHIPACK_OT_myobject_preset_menu(PresetMenuOperator, Operator):
        bl_idname = "archipack.myobject_preset_menu"
        bl_label = "Myobject preset"
        preset_subdir = "archipack_myobject"

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

Minimal implementation using all features
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

Minimal implementation using all features
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

Minimal implementation using all features
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"

Shift + a and add mesh menu

on draw_menu():

layout.operator("archipack.door_preset_menu",
                                text="Door",
                                icon_value=icons["door"].icon_id
                                ).preset_operator = "archipack.door"

Register and unregister function

archipack_myobject.register()
archipack_myobject.unregister()