Welcome to mirror list, hosted at ThFree Co, Russian Federation.

archipack_snap.py « archipack - git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 9d50add7e90286714027d44499787018ae2ea418 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# -*- 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 {'RUNNING', '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
import logging

logger = logging.getLogger("archipack")


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()
    placeloc = Vector()
    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_elements = None
    snap_target = None
    pivot_point = None
    trans_orientation = None


def snap_point(takeloc=None,
               draw=None,
               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.copy()
        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.copy()

    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
    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 = context.selected_objects[:]
        SnapStore.act = context.object
        bpy.ops.object.select_all(action="DESELECT")
        ts = context.tool_settings
        SnapStore.use_snap = ts.use_snap
        SnapStore.snap_elements = ts.snap_elements
        SnapStore.snap_target = ts.snap_target
        SnapStore.pivot_point = ts.transform_pivot_point
        SnapStore.trans_orientation = context.scene.transform_orientation_slots[0].type
        self.create_helper(context)
        # Use a timer to broadcast a TIMER event while transform.translate is running
        self._timer = context.window_manager.event_timer_add(0.1, window=context.window)

        if SnapStore.draw is not None:
            args = (self, context)
            self._draw_handler = bpy.types.SpaceView3D.draw_handler_add(SnapStore.draw, args, 'WINDOW', 'POST_PIXEL')

    def remove_timer(self, context):
        if self._timer is not None:
            context.window_manager.event_timer_remove(self._timer)

    def exit(self, context):

        self.remove_timer(context)

        if self._draw_handler is not None:
            bpy.types.SpaceView3D.draw_handler_remove(self._draw_handler, 'WINDOW')

        # Restore original context
        if hasattr(context, "tool_settings"):
            ts = context.tool_settings
            ts.use_snap = SnapStore.use_snap
            ts.snap_elements = SnapStore.snap_elements
            ts.snap_target = SnapStore.snap_target
            ts.transform_pivot_point = SnapStore.pivot_point
        context.scene.transform_orientation_slots[0].type = SnapStore.trans_orientation
        for o in SnapStore.sel:
            o.select_set(state=True)
        if SnapStore.act is not None:
            SnapStore.act.select_set(state=True)
            context.view_layer.objects.active = SnapStore.act
        self.destroy_helper(context)
        logger.debug("Snap.exit %s", context.object.name)

    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 = bpy.data.objects.get('Archipack_snap_helper')
        if helper is not None:
            # print("helper found")
            if context.scene.objects.get('Archipack_snap_helper') is None:
                # print("link helper")
                # self.link_object_to_scene(context, helper)
                context.scene.collection.objects.link(helper)
        else:
            # print("create helper")
            m = bpy.data.meshes.new("Archipack_snap_helper")
            m.vertices.add(count=1)
            helper = bpy.data.objects.new("Archipack_snap_helper", m)
            context.scene.collection.objects.link(helper)
            helper.use_fake_user = True
            helper.data.use_fake_user = True

        helper.matrix_world = SnapStore.helper_matrix
        helper.select_set(state=True)
        context.view_layer.objects.active = helper
        SnapStore.helper = helper

    def destroy_helper(self, context):
        """
            Unlink helper
            currently only support OBJECT mode
        """
        if SnapStore.helper is not None:
            # @TODO: Fix this
            # self.unlink_object_from_scene(context, 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 = {'INTERNAL'}  # , 'UNDO'

    def modal(self, context, event):

        if SnapStore.helper is not None:
            logger.debug("Snap.modal event %s %s location:%s",
                         event.type,
                         event.value,
                         SnapStore.helper.location)

        context.area.tag_redraw()

        if event.type in ('TIMER', 'NOTHING'):
            SnapStore.callback(context, event, 'RUNNING', self)
            return {'PASS_THROUGH'}

        if event.type not in ('ESC', 'RIGHTMOUSE', 'LEFTMOUSE', 'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'):
            return {'PASS_THROUGH'}

        if event.type in ('ESC', 'RIGHTMOUSE'):
            SnapStore.callback(context, event, 'CANCEL', self)
        else:
            SnapStore.placeloc = SnapStore.helper.location
            # on tt modal exit with right click, the delta is 0 so exit
            if self.delta.length == 0:
                SnapStore.callback(context, event, 'CANCEL', self)
            else:
                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':

            if event.type in ('ESC', 'RIGHTMOUSE'):
                return {'FINISHED'}

            self.init(context, event)

            logger.debug("Snap.invoke event %s %s location:%s act:%s",
                         event.type,
                         event.value,
                         SnapStore.helper.location, context.object.name)

            context.window_manager.modal_handler_add(self)

            bpy.ops.transform.translate('INVOKE_DEFAULT',
                                        constraint_axis=SnapStore.constraint_axis,
                                        orient_type=SnapStore.transform_orientation,
                                        release_confirm=SnapStore.release_confirm)

            logger.debug("Snap.invoke transform.translate done")

            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)