Manipulable ?

s-leger edited this page Aug 9, 2017 · 21 revisions

Concept

In order to make objects manipulable, there are some requirements.

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.

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.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.