3 # ##### BEGIN GPL LICENSE BLOCK #####
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
19 # ##### END GPL LICENSE BLOCK #####
23 # ----------------------------------------------------------
24 # Author: Stephen Leger (s-leger)
25 # Inspired by Okavango's np_point_move
26 # ----------------------------------------------------------
29 from .archipack_snap import snap_point
31 snap_point(takeloc, draw_callback, action_callback, constraint_axis)
35 takeloc Vector3d location of point to snap
37 constraint_axis boolean tuple for each axis
38 eg: (True, True, False) to constrtaint to xy plane
40 draw_callback(context, sp)
45 action_callback(context, event, state, sp)
46 state in {'RUNNING', 'SUCCESS', 'CANCEL'}
52 - delta = placeloc - takeloc
58 may change grid size to 0.1 round feature (SHIFT)
59 see https://blenderartists.org/forum/showthread.php?205158-Blender-2-5-Snap-mode-increment
60 then use a SHIFT use grid snap
65 from bpy
.types
import Operator
66 from mathutils
import Vector
, Matrix
69 logger
= logging
.getLogger("archipack")
72 def dumb_callback(context
, event
, state
, sp
):
76 def dumb_draw(sp
, context
):
89 constraint_axis
= (True, True, False)
90 helper_matrix
= Matrix()
91 transform_orientation
= 'GLOBAL'
92 release_confirm
= True
102 trans_orientation
= None
105 def snap_point(takeloc
=None,
107 callback
=dumb_callback
,
109 constraint_axis
=(True, True, False),
110 transform_orientation
='GLOBAL',
112 release_confirm
=True):
114 Invoke op from outside world
115 in a convenient importable function
117 transform_orientation in [‘GLOBAL’, ‘LOCAL’, ‘NORMAL’, ‘GIMBAL’, ‘VIEW’]
119 draw(sp, context) a draw callback
120 callback(context, event, state, sp) action callback
123 takeloc Vector, unconstraint or system axis constraints
124 takemat Matrix, constaint to this matrix as 'LOCAL' coordsys
125 The snap source helper use it as world matrix
126 so it is possible to constraint to user defined coordsys.
128 SnapStore
.draw
= draw
129 SnapStore
.callback
= callback
130 SnapStore
.constraint_axis
= constraint_axis
131 SnapStore
.release_confirm
= release_confirm
133 if takemat
is not None:
134 SnapStore
.helper_matrix
= takemat
135 takeloc
= takemat
.translation
.copy()
136 transform_orientation
= 'LOCAL'
137 elif takeloc
is not None:
138 SnapStore
.helper_matrix
= Matrix
.Translation(takeloc
)
140 raise ValueError("ArchipackSnap: Either takeloc or takemat must be defined")
142 SnapStore
.takeloc
= takeloc
143 SnapStore
.placeloc
= takeloc
.copy()
145 SnapStore
.transform_orientation
= transform_orientation
147 # @NOTE: unused mode var to switch between OBJECT and EDIT mode
148 # for ArchipackSnapBase to be able to handle both modes
149 # must implements corresponding helper create and delete actions
150 SnapStore
.mode
= mode
151 bpy
.ops
.archipack
.snap('INVOKE_DEFAULT')
152 # return helper so we are able to move it "live"
153 return SnapStore
.helper
156 class ArchipackSnapBase():
158 Helper class for snap Operators
159 store and restore context
160 create and destroy helper
161 install and remove a draw_callback working while snapping
163 store and provide access to 3d Vectors
164 in draw_callback and action_callback
165 - delta = placeloc - takeloc
171 self
._draw
_handler
= None
173 def init(self
, context
, event
):
175 # if SnapStore.instances_running < 1:
176 SnapStore
.sel
= context
.selected_objects
[:]
177 SnapStore
.act
= context
.object
178 bpy
.ops
.object.select_all(action
="DESELECT")
179 ts
= context
.tool_settings
180 SnapStore
.use_snap
= ts
.use_snap
181 SnapStore
.snap_elements
= ts
.snap_elements
182 SnapStore
.snap_target
= ts
.snap_target
183 SnapStore
.pivot_point
= ts
.transform_pivot_point
184 SnapStore
.trans_orientation
= context
.scene
.transform_orientation_slots
[0].type
185 self
.create_helper(context
)
186 # Use a timer to broadcast a TIMER event while transform.translate is running
187 self
._timer
= context
.window_manager
.event_timer_add(0.1, window
=context
.window
)
189 if SnapStore
.draw
is not None:
190 args
= (self
, context
)
191 self
._draw
_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(SnapStore
.draw
, args
, 'WINDOW', 'POST_PIXEL')
193 def remove_timer(self
, context
):
194 if self
._timer
is not None:
195 context
.window_manager
.event_timer_remove(self
._timer
)
197 def exit(self
, context
):
199 self
.remove_timer(context
)
201 if self
._draw
_handler
is not None:
202 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._draw
_handler
, 'WINDOW')
204 # Restore original context
205 if hasattr(context
, "tool_settings"):
206 ts
= context
.tool_settings
207 ts
.use_snap
= SnapStore
.use_snap
208 ts
.snap_elements
= SnapStore
.snap_elements
209 ts
.snap_target
= SnapStore
.snap_target
210 ts
.transform_pivot_point
= SnapStore
.pivot_point
211 context
.scene
.transform_orientation_slots
[0].type = SnapStore
.trans_orientation
212 for o
in SnapStore
.sel
:
213 o
.select_set(state
=True)
214 if SnapStore
.act
is not None:
215 SnapStore
.act
.select_set(state
=True)
216 context
.view_layer
.objects
.active
= SnapStore
.act
217 self
.destroy_helper(context
)
218 logger
.debug("Snap.exit %s", context
.object.name
)
220 def create_helper(self
, context
):
222 Create a helper with fake user
223 or find older one in bpy data and relink to scene
224 currently only support OBJECT mode
226 Do target helper be linked to scene in order to work ?
229 helper
= bpy
.data
.objects
.get('Archipack_snap_helper')
230 if helper
is not None:
231 # print("helper found")
232 if context
.scene
.objects
.get('Archipack_snap_helper') is None:
233 # print("link helper")
234 # self.link_object_to_scene(context, helper)
235 context
.scene
.collection
.objects
.link(helper
)
237 # print("create helper")
238 m
= bpy
.data
.meshes
.new("Archipack_snap_helper")
239 m
.vertices
.add(count
=1)
240 helper
= bpy
.data
.objects
.new("Archipack_snap_helper", m
)
241 context
.scene
.collection
.objects
.link(helper
)
242 helper
.use_fake_user
= True
243 helper
.data
.use_fake_user
= True
245 helper
.matrix_world
= SnapStore
.helper_matrix
246 helper
.select_set(state
=True)
247 context
.view_layer
.objects
.active
= helper
248 SnapStore
.helper
= helper
250 def destroy_helper(self
, context
):
253 currently only support OBJECT mode
255 if SnapStore
.helper
is not None:
257 # self.unlink_object_from_scene(context, SnapStore.helper)
258 SnapStore
.helper
= None
262 return self
.placeloc
- self
.takeloc
266 return SnapStore
.takeloc
270 # take from helper when there so the delta
271 # is working even while modal is running
272 if SnapStore
.helper
is not None:
273 return SnapStore
.helper
.location
275 return SnapStore
.placeloc
278 class ARCHIPACK_OT_snap(ArchipackSnapBase
, Operator
):
279 bl_idname
= 'archipack.snap'
280 bl_label
= 'Archipack snap'
281 bl_options
= {'INTERNAL'} # , 'UNDO'
283 def modal(self
, context
, event
):
285 if SnapStore
.helper
is not None:
286 logger
.debug("Snap.modal event %s %s location:%s",
289 SnapStore
.helper
.location
)
291 context
.area
.tag_redraw()
293 if event
.type in ('TIMER', 'NOTHING'):
294 SnapStore
.callback(context
, event
, 'RUNNING', self
)
295 return {'PASS_THROUGH'}
297 if event
.type not in ('ESC', 'RIGHTMOUSE', 'LEFTMOUSE', 'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'):
298 return {'PASS_THROUGH'}
300 if event
.type in ('ESC', 'RIGHTMOUSE'):
301 SnapStore
.callback(context
, event
, 'CANCEL', self
)
303 SnapStore
.placeloc
= SnapStore
.helper
.location
304 # on tt modal exit with right click, the delta is 0 so exit
305 if self
.delta
.length
== 0:
306 SnapStore
.callback(context
, event
, 'CANCEL', self
)
308 SnapStore
.callback(context
, event
, 'SUCCESS', self
)
311 # self.report({'INFO'}, "ARCHIPACK_OT_snap exit")
314 def invoke(self
, context
, event
):
315 if context
.area
.type == 'VIEW_3D':
317 if event
.type in ('ESC', 'RIGHTMOUSE'):
320 self
.init(context
, event
)
322 logger
.debug("Snap.invoke event %s %s location:%s act:%s",
325 SnapStore
.helper
.location
, context
.object.name
)
327 context
.window_manager
.modal_handler_add(self
)
329 bpy
.ops
.transform
.translate('INVOKE_DEFAULT',
330 constraint_axis
=SnapStore
.constraint_axis
,
331 orient_type
=SnapStore
.transform_orientation
,
332 release_confirm
=SnapStore
.release_confirm
)
334 logger
.debug("Snap.invoke transform.translate done")
336 return {'RUNNING_MODAL'}
338 self
.report({'WARNING'}, "View3D not found, cannot run operator")
343 bpy
.utils
.register_class(ARCHIPACK_OT_snap
)
347 bpy
.utils
.unregister_class(ARCHIPACK_OT_snap
)