Manipulable¶
Manipulable objects provide on-screen handles to modiy properties.
Requirements¶
- The object mesh or scale or location (something) must update on parameter change.
- We need a place to store manipulator data (type, and openGl 3d points location, name of property we should manipulate)
- On manipulate start, draw gl feedback using 3d points location.
- When manipulating occur, update object’s manipulated data and 3d gl points location so manipulator update on screen.
Implementation, the easy way for simple objects¶
Typical setup on objects with one single main PropertyGroup.
Requirements¶
- The PropertyGroup name must be prefixed with archipack_
- The PropertyGroup must inherit from Manipulable.
- You MUST implement setup_manipulators and call it from update.
- You MUST update the manipulators location of gl points.
Implementation sample¶
def update(self, context):
self.update(context)
class archipack_your_object(Manipulable, PropertyGroup):
width = FloatProperty(update=update)
n_parts = IntProperty(update=update)
z = FloatProperty(update=update)
# Implement setup_manipulators method
def setup_manipulators(self):
if len(self.manipulators) > 0:
return
# Sample 1 : add a size manipulator
s = self.manipulators.add()
# manipulator type
s.type_key = 'SIZE'
# define property name we manipulate
s.prop1_name = "width"
# Sample 2 : add a counter manipulator
s = self.manipulators.add()
s.prop1_name = "n_parts"
s.type_key = 'COUNTER'
# Sample 3 : add size manipulator draw on xz plane
s = self.manipulators.add()
s.type_key = 'SIZE'
s.prop1_name = "z"
# draw this one over xz plane
s.normal = (0, 1, 0)
def update(self, context):
o = self.find_in_selection(context, self.auto_update)
if o is None:
return
# you MUST call setup_manipulators
self.setup_manipulators()
.. update your mesh
# Update manipulators location in object local space
x = 0.5 * self.width
# width
self.manipulators[0].set_pts([(-x, 0, 0), (x, 0, 0), (1, 0, 0)])
# counter
self.manipulators[1].set_pts([(-x, 0, 0), (-x, 0.5, 0), (1, 0, 0)])
# z
self.manipulators[2].set_pts([(-x, 0, 0), (-x, 0, self.z), (-1, 0, 0)])
Updating manipulator location¶
self.manipulators[x].set_pts([p0, p1, p2], normal=Vector((0, 0, 1)))
For size type manipulators¶
- p0 and p1 are start and end location of manipulator vector 3d in object local space
- p2 is a vector used to scale/direction manipulator by default use Vector((1, 0, 0)) to place on the right side at 1 unit
- normal is optionnal allow to set a plane to draw manipulator default to plane xy with Vector((0, 0, 1))
For arc / radius type manipulators¶
- p0 is center vector 3d in object local space
- p1 and p2 are vector 3d arc start and arc end points relative to center
- normal is optionnal allow to set a plane to draw manipulator default to plane xy with Vector((0, 0, 1))
Manipulation Operator¶
in invoke method use manipulable_invoke(context, event)
Implementation sample¶
class ARCHIPACK_OT_your_object_manipulate(Operator):
bl_idname = "archipack.your_object_manipulate"
bl_label = "Manipulate"
bl_description = "Manipulate"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(self, context):
return archipack_your_object.filter(context.active_object)
def invoke(self, context, event):
d = archipack_your_object.datablock(context.active_object)
d.manipulable_invoke(context)
return {'FINISHED'}
Create Operator¶
Requirements¶
- MUST inherit from ArchipackCreateTool
- MUST call manipulate()
manipulate takes care of auto_manipulate so when creating many objects you may set it to false
Implementation sample Archipack’s Window (stripped down)¶
class ARCHIPACK_OT_window(ArchipackCreateTool, Operator):
bl_idname = "archipack.window"
bl_label = "Window"
bl_description = "Window"
def execute(self, context):
if context.mode == "OBJECT":
# ensure context is free from other objects
bpy.ops.object.select_all(action="DESELECT")
# o is created base object
o = self.create(context)
o.location = bpy.context.scene.cursor_location
# o must be selected and active
o.select = True
context.scene.objects.active = o
self.manipulate()
return {'FINISHED'}
else:
self.report({'WARNING'}, "Archipack: Option only valid in Object mode")
return {'CANCELLED'}
Available manipulators (type_key)¶
- SIZE : Modify a size by one side.
- DUMB_SIZE : Display a size, not editable
- SIZE_LOC : Modify a size by any side, for objects with pivot at center, preserving other side moving object according.
- SNAP_SIZE_LOC : Modify a size by any side, snap aware one for objects with pivot at center, preserving other side moving object according.
- ANGLE : Modify an angle
- DUMB_ANGLE : Display an angle, not editable
- ARC_ANGLE_RADIUS : Modify angle and radius, specify angle property name in .prop1_name and radius in .prop2_name
- COUNTER : Modify an integer, step by step when clicking on arrows.
- DELTA_LOC : Modify location of an object, use prop1_name to setup axis in [‘x’, ‘y’, ‘z’]
- DUMB_STRING : Draw a string, use prop1_name as string to draw.
- WALL_SNAP : Draggable snap point, prop1_name is a part identifier, prop2_name is z property to draw placeholder, manipulate parts based objects (currently wall, fences, slab)
Manipulator data structure¶
- archipack_manipulator PropertyGroup to store each manipulator properties.
- Manipulable to add manipulation ability on a PropertyGroup
- Manipulator instance taking care of screen gl drawing, mouse and keyboard events, updating data according changes.
archipack_manipulator implementation¶
class archipack_manipulator(PropertyGroup):
"""
A property group to add to manipulable objects
type_key: type of manipulator
prop1_name = the property name of object to modify
prop2_name = another property name of object to modify (eg: angle and radius)
p0, p1, p2 3d Vectors as base points to represent manipulators on screen
normal Vector normal of plane on with draw manipulator
"""
type_key = StringProperty(default='SIZE')
# How 3d points are stored in manipulators ?
# SIZE = 2 absolute positionned and a scaling vector
# RADIUS = 1 absolute positionned (center) and 2 relatives (sides)
# POLYGON = 2 absolute positionned and a relative vector (for rect polygons)
pts_mode = StringProperty(default='SIZE')
prop1_name = StringProperty()
prop2_name = StringProperty()
p0 = FloatVectorProperty(subtype='XYZ')
p1 = FloatVectorProperty(subtype='XYZ')
p2 = FloatVectorProperty(subtype='XYZ')
# allow orientation of manipulators by default on xy plane,
# but may be used to constrain heights on local object space
normal = FloatVectorProperty(subtype='XYZ', default=(0, 0, 1))
def set_pts(self, pts, normal=None):
"""
set 3d location of gl points (in object space)
pts: array of 3 vectors 3d
normal: optionnal vector 3d default to Z axis
"""
Manipulable implementation (stripped down)¶
class Manipulable():
"""
A class extending PropertyGroup to setup gl manipulators
Beware : prevent crash calling manipulable_disable()
before changing manipulated data structure
"""
manipulators = CollectionProperty(
type=archipack_manipulator,
description="store 3d points to draw gl manipulators"
)
def manipulable_invoke(self, context):
"""
call this in operator invoke()
May override when needed
"""
if self.manipulate_mode:
self.manipulable_disable(context)
return False
self.manip_stack = []
self.manipulable_setup(context)
self.manipulate_mode = True
# dont forget to call base class _invoke
self._manipulable_invoke(context)
def manipulable_setup(self, context):
"""
Implement the setup part as per parent object basis
This is default implementation for simple objects
with manipulators linked to base object properties
May override when needed
"""
self.manipulable_disable(context)
o = context.active_object
self.setup_manipulators()
for m in self.manipulators:
# m.setup create Manipulator instance
self.manip_stack.append(m.setup(context, o, self))
# Callbacks
def manipulable_release(self, context):
"""
Override with action to do on mouse release
eg: big update
"""
return
def manipulable_exit(self, context):
"""
Override with action to do when modal exit
"""
return
def manipulable_manipulate(self, context, event, manipulator):
"""
Override with action to do when a handle is active (pressed and mousemove)
"""
return
While not required for simple manipulations, you may override manipulable_setup() in your object data propertygroup to handle manipulator setup. Use manipulable datablock .setup(object, datablock) method to create manipulator hanlder. Manipulators handlers do hold references to base object, datablock to modify, take care of drawing gl handles on screen, and handle mouse and keyboard inputs.
Warning
Data structure change and manipulators
Before any data structure changes on manipulable properties, you MUST call .manipulable_disable(context) update your datastructure, then set .manipulable_refresh = True Failing to do so will result in ACCESS_VIOLATION crash errors.