diff options
Diffstat (limited to 'archipack/archipack_snap.py')
-rw-r--r-- | archipack/archipack_snap.py | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/archipack/archipack_snap.py b/archipack/archipack_snap.py new file mode 100644 index 00000000..936a07d8 --- /dev/null +++ b/archipack/archipack_snap.py @@ -0,0 +1,309 @@ +# -*- coding:utf-8 -*- + +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +# ---------------------------------------------------------- +# Author: Stephen Leger (s-leger) +# Inspired by Okavango's np_point_move +# ---------------------------------------------------------- +""" + Usage: + from .archipack_snap import snap_point + + snap_point(takeloc, draw_callback, action_callback, constraint_axis) + + arguments: + + takeloc Vector3d location of point to snap + + constraint_axis boolean tuple for each axis + eg: (True, True, False) to constrtaint to xy plane + + draw_callback(context, sp) + sp.takeloc + sp.placeloc + sp.delta + + action_callback(context, event, state, sp) + state in {'SUCCESS', 'CANCEL'} + sp.takeloc + sp.placeloc + sp.delta + + with 3d Vectors + - delta = placeloc - takeloc + - takeloc + - placeloc + + + NOTE: + may change grid size to 0.1 round feature (SHIFT) + see https://blenderartists.org/forum/showthread.php?205158-Blender-2-5-Snap-mode-increment + then use a SHIFT use grid snap + +""" + +import bpy +from bpy.types import Operator +from mathutils import Vector, Matrix + + +def dumb_callback(context, event, state, sp): + return + + +def dumb_draw(sp, context): + return + + +class SnapStore: + """ + Global store + """ + callback = None + draw = None + helper = None + takeloc = Vector((0, 0, 0)) + placeloc = Vector((0, 0, 0)) + constraint_axis = (True, True, False) + helper_matrix = Matrix() + transform_orientation = 'GLOBAL' + release_confirm = True + instances_running = 0 + + # context related + act = None + sel = [] + use_snap = False + snap_element = None + snap_target = None + pivot_point = None + trans_orientation = None + + +def snap_point(takeloc=None, + draw=dumb_draw, + callback=dumb_callback, + takemat=None, + constraint_axis=(True, True, False), + transform_orientation='GLOBAL', + mode='OBJECT', + release_confirm=True): + """ + Invoke op from outside world + in a convenient importable function + + transform_orientation in [‘GLOBAL’, ‘LOCAL’, ‘NORMAL’, ‘GIMBAL’, ‘VIEW’] + + draw(sp, context) a draw callback + callback(context, event, state, sp) action callback + + Use either : + takeloc Vector, unconstraint or system axis constraints + takemat Matrix, constaint to this matrix as 'LOCAL' coordsys + The snap source helper use it as world matrix + so it is possible to constraint to user defined coordsys. + """ + SnapStore.draw = draw + SnapStore.callback = callback + SnapStore.constraint_axis = constraint_axis + SnapStore.release_confirm = release_confirm + if takemat is not None: + SnapStore.helper_matrix = takemat + takeloc = takemat.translation + transform_orientation = 'LOCAL' + elif takeloc is not None: + SnapStore.helper_matrix = Matrix().Translation(takeloc) + else: + raise ValueError("ArchipackSnap: Either takeloc or takemat must be defined") + SnapStore.takeloc = takeloc + SnapStore.placeloc = takeloc + SnapStore.transform_orientation = transform_orientation + + # @NOTE: unused mode var to switch between OBJECT and EDIT mode + # for ArchipackSnapBase to be able to handle both modes + # must implements corresponding helper create and delete actions + SnapStore.mode = mode + res = bpy.ops.archipack.snap('INVOKE_DEFAULT') + # return helper so we are able to move it "live" + return SnapStore.helper + +class ArchipackSnapBase(): + """ + Helper class for snap Operators + store and restore context + create and destroy helper + install and remove a draw_callback working while snapping + + store and provide access to 3d Vectors + in draw_callback and action_callback + - delta = placeloc - takeloc + - takeloc + - placeloc + """ + def __init__(self): + self._draw_handler = None + + def init(self, context, event): + # Store context data + if SnapStore.instances_running < 1: + SnapStore.sel = [o for o in context.selected_objects] + SnapStore.act = context.active_object + bpy.ops.object.select_all(action="DESELECT") + SnapStore.use_snap = context.tool_settings.use_snap + SnapStore.snap_element = context.tool_settings.snap_element + SnapStore.snap_target = context.tool_settings.snap_target + SnapStore.pivot_point = context.space_data.pivot_point + SnapStore.trans_orientation = context.space_data.transform_orientation + self.create_helper(context) + SnapStore.instances_running += 1 + # print("ArchipackSnapBase init: %s" % (SnapStore.instances_running)) + self.set_transform_orientation(context) + args = (self, context) + self._draw_handler = bpy.types.SpaceView3D.draw_handler_add(SnapStore.draw, args, 'WINDOW', 'POST_PIXEL') + + def exit(self, context): + bpy.types.SpaceView3D.draw_handler_remove(self._draw_handler, 'WINDOW') + # trick to allow launch 2nd instance + # via callback, preserve context as it + SnapStore.instances_running -= 1 + # print("ArchipackSnapBase exit: %s" % (SnapStore.instances_running)) + if SnapStore.instances_running > 0: + return + + self.destroy_helper(context) + # Restore original context + context.tool_settings.use_snap = SnapStore.use_snap + context.tool_settings.snap_element = SnapStore.snap_element + context.tool_settings.snap_target = SnapStore.snap_target + context.space_data.pivot_point = SnapStore.pivot_point + context.space_data.transform_orientation = SnapStore.trans_orientation + for o in SnapStore.sel: + o.select = True + if SnapStore.act is not None: + context.scene.objects.active = SnapStore.act + + def set_transform_orientation(self, context): + """ + Allow local constraint orientation to be set + """ + context.space_data.transform_orientation = SnapStore.transform_orientation + + def create_helper(self, context): + """ + Create a helper with fake user + or find older one in bpy data and relink to scene + currently only support OBJECT mode + + Do target helper be linked to scene in order to work ? + + """ + + helper_idx = bpy.data.objects.find('Archipack_snap_helper') + if helper_idx > -1: + helper = bpy.data.objects[helper_idx] + if context.scene.objects.find('Archipack_snap_helper') < 0: + context.scene.objects.link(helper) + else: + bpy.ops.object.add(type='MESH') + helper = context.active_object + helper.name = 'Archipack_snap_helper' + helper.use_fake_user = True + helper.data.use_fake_user = True + # hide snap helper + # helper.hide = True + helper.matrix_world = SnapStore.helper_matrix + helper.select = True + context.scene.objects.active = helper + SnapStore.helper = helper + + def destroy_helper(self, context): + """ + Unlink helper + currently only support OBJECT mode + """ + if SnapStore.helper is not None: + context.scene.objects.unlink(SnapStore.helper) + SnapStore.helper = None + + @property + def delta(self): + return self.placeloc - self.takeloc + + @property + def takeloc(self): + return SnapStore.takeloc + + @property + def placeloc(self): + # take from helper when there so the delta + # is working even while modal is running + if SnapStore.helper is not None: + return SnapStore.helper.location + else: + return SnapStore.placeloc + + +class ARCHIPACK_OT_snap(ArchipackSnapBase, Operator): + bl_idname = 'archipack.snap' + bl_label = 'Archipack snap' + bl_options = {'UNDO'} + + def modal(self, context, event): + # print("Snap.modal event %s %s" % (event.type, event.value)) + context.area.tag_redraw() + # NOTE: this part only run after transform LEFTMOUSE RELEASE + # or with ESC and RIGHTMOUSE + if event.type not in {'ESC', 'RIGHTMOUSE', 'LEFTMOUSE', 'MOUSEMOVE'}: + # print("Snap.modal skip unknown event %s %s" % (event.type, event.value)) + # self.report({'WARNING'}, "ARCHIPACK_OT_snap unknown event") + return{'PASS_THROUGH'} + if event.type in {'ESC', 'RIGHTMOUSE'}: + SnapStore.callback(context, event, 'CANCEL', self) + else: + SnapStore.placeloc = SnapStore.helper.location + SnapStore.callback(context, event, 'SUCCESS', self) + self.exit(context) + # self.report({'INFO'}, "ARCHIPACK_OT_snap exit") + return{'FINISHED'} + + def invoke(self, context, event): + if context.area.type == 'VIEW_3D': + # print("Snap.invoke event %s %s" % (event.type, event.value)) + self.init(context, event) + context.window_manager.modal_handler_add(self) + # print("SnapStore.transform_orientation%s" % (SnapStore.transform_orientation)) + bpy.ops.transform.translate('INVOKE_DEFAULT', + constraint_axis=SnapStore.constraint_axis, + constraint_orientation=SnapStore.transform_orientation, + release_confirm=SnapStore.release_confirm) + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "View3D not found, cannot run operator") + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(ARCHIPACK_OT_snap) + + +def unregister(): + bpy.utils.unregister_class(ARCHIPACK_OT_snap) |