forked from mistajuliax/archipack
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patharchipack_snap.py
347 lines (276 loc) · 11.2 KB
/
archipack_snap.py
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)