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)
26 # ----------------------------------------------------------
28 from math
import atan2
, pi
29 from mathutils
import Vector
, Matrix
30 from mathutils
.geometry
import intersect_line_plane
, intersect_point_line
, intersect_line_sphere
31 from bpy_extras
import view3d_utils
32 from bpy
.types
import PropertyGroup
, Operator
33 from bpy
.props
import (
40 from bpy
.app
.handlers
import persistent
41 from .archipack_snap
import snap_point
42 from .archipack_keymaps
import Keymaps
43 from .archipack_gl
import (
44 GlLine
, GlArc
, GlText
,
45 GlPolyline
, GlPolygon
,
46 TriHandle
, SquareHandle
, EditableText
,
47 FeedbackPanel
, GlCursorArea
52 # Snap aware manipulators use a dirty hack :
53 # draw() as a callback to update values in realtime
54 # as transform.translate in use to allow snap
55 # does catch all events.
56 # This however has a wanted side effect:
57 # the manipulator take precedence over already running
58 # ones, and prevent select mode to start.
61 # Other manipulators should use same technique to take
62 # precedence over already running ones when active
65 # Select mode does suffer from this stack effect:
66 # the last running wins. The point is left mouse select mode
67 # requiring left drag to be RUNNING_MODAL to prevent real
68 # objects select and move during manipulators selection.
71 # First run a separate modal dedicated to select mode.
72 # Selecting in whole manips stack when required
73 # (manips[key].manipulable.manip_stack)
74 # Must investigate for a way to handle unselect after drag done.
78 logger
= logging
.getLogger("archipack")
82 Change object location when moving 1 point
83 When False, change data.origin instead
85 USE_MOVE_OBJECT
= True
86 # Arrow sizes (world units)
88 # Handle area size (pixels)
92 # a global manipulator stack reference
93 # prevent Blender "ACCESS_VIOLATION" crashes
94 # use a dict to prevent collisions
95 # between many objects being in manipulate mode
96 # use object names as loose keys
97 # NOTE : use app.drivers to reset before file load
101 class ArchipackActiveManip
:
103 Store manipulated object
104 - object_name: manipulated object name
105 - stack: array of Manipulators instances
106 - manipulable: Manipulable instance
108 def __init__(self
, object_name
):
109 self
.object_name
= object_name
110 # manipulators stack for object
112 # reference to object manipulable instance
113 self
.manipulable
= None
115 def is_snapping(self
, ctx
):
117 Check if snap is active
119 return ctx
.active_object
and ctx
.active_object
.name
.startswith("Archipack_")
124 Check for manipulable validity
125 to disable modal when required
127 o
= bpy
.data
.objects
.get(self
.object_name
)
129 self
.manipulable
is None or
131 # The object is not selected and snap is not active
132 not (self
.is_snapping(bpy
.context
) or o
.select_get())
137 Exit manipulation mode
138 - exit from all running manipulators
139 - empty manipulators stack
140 - set manipulable.manipulate_mode to False
141 - remove reference to manipulable
146 if self
.manipulable
is not None:
147 self
.manipulable
.manipulate_mode
= False
148 self
.manipulable
= None
149 self
.object_name
= ""
153 def remove_manipulable(key
):
155 disable and remove a manipulable from stack
158 # print("remove_manipulable key:%s" % (key))
159 if key
in manips
.keys():
164 def check_stack(key
):
166 check for stack item validity
167 use in modal to destroy invalid modals
168 return true when invalid / not found
172 if key
not in manips
.keys():
173 # print("check_stack : key not found %s" % (key))
175 elif manips
[key
].dirty
:
176 # print("check_stack : key.dirty %s" % (key))
177 remove_manipulable(key
)
183 # print("empty_stack()")
185 kill every manipulators in stack
189 for key
in manips
.keys():
194 def add_manipulable(key
, manipulable
):
196 add a ArchipackActiveManip into the stack
197 if not already present
198 setup reference to manipulable
199 return manipulators stack
202 if key
not in manips
.keys():
203 # print("add_manipulable() key:%s not found create new" % (key))
204 manips
[key
] = ArchipackActiveManip(key
)
206 manips
[key
].manipulable
= manipulable
207 return manips
[key
].stack
210 # ------------------------------------------------------------------
211 # Define Manipulators
212 # ------------------------------------------------------------------
217 Manipulator base class to derive other
218 handle keyboard and modal events
219 provide convenient funcs including getter and setter for datablock values
220 store reference of base object, datablock and manipulator
223 ".", ",", "-", "+", "1", "2", "3",
224 "4", "5", "6", "7", "8", "9", "0",
225 "c", "m", "d", "k", "h", "a",
226 " ", "/", "*", "'", "\""
231 'LEFT_ARROW', 'RIGHT_ARROW'
234 def __init__(self
, context
, o
, datablock
, manipulator
, snap_callback
=None):
236 o : object to manipulate
237 datablock : object data to manipulate
238 manipulator: object archipack_manipulator datablock
239 snap_callback: on snap enabled manipulators, will be called when drag occurs
241 self
.keymap
= Keymaps(context
)
242 self
.feedback
= FeedbackPanel()
244 self
.selectable
= False
245 self
.selected
= False
246 # active text input value for manipulator
247 self
.keyboard_input_active
= False
249 # unit for keyboard input value
250 self
.value_type
= 'LENGTH'
251 self
.pts_mode
= 'SIZE'
253 self
.datablock
= datablock
254 self
.manipulator
= manipulator
255 self
.snap_callback
= snap_callback
256 self
.origin
= Vector((0, 0, 1))
257 self
.mouse_pos
= Vector((0, 0))
258 self
.length_entered
= ""
260 args
= (self
, context
)
261 self
._handle
= bpy
.types
.SpaceView3D
.draw_handler_add(self
.draw_callback
, args
, 'WINDOW', 'POST_PIXEL')
264 def poll(cls
, context
):
266 Allow manipulator enable/disable
268 handles will not show
274 Modal exit, DON'T EVEN TRY TO OVERRIDE
276 if self
._handle
is not None:
277 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
280 print("Manipulator.exit() handle not found %s" % (type(self
).__name
__))
282 # Mouse event handlers, MUST be overridden
283 def mouse_press(self
, context
, event
):
285 Manipulators must implement
286 mouse press event handler
287 return True to callback manipulable_manipulate
289 raise NotImplementedError
291 def mouse_release(self
, context
, event
):
293 Manipulators must implement
294 mouse mouse_release event handler
295 return False to callback manipulable_release
297 raise NotImplementedError
299 def mouse_move(self
, context
, event
):
301 Manipulators must implement
302 mouse move event handler
303 return True to callback manipulable_manipulate
305 raise NotImplementedError
307 # Keyboard event handlers, MAY be overridden
308 def keyboard_done(self
, context
, event
, value
):
310 Manipulators may implement
311 keyboard value validated event handler
312 value: changed by keyboard
313 return True to callback manipulable_manipulate
317 def keyboard_editing(self
, context
, event
, value
):
319 Manipulators may implement
320 keyboard value changed event handler
321 value: string changed by keyboard
322 allow realtime update of label
323 return False to show edited value on window header
324 return True when feedback show right on screen
326 self
.label_value
= value
329 def keyboard_cancel(self
, context
, event
):
331 Manipulators may implement
332 keyboard entry cancelled
336 def cancel(self
, context
, event
):
338 Manipulators may implement
339 cancelled event (ESC RIGHTCLICK)
344 def undo(self
, context
, event
):
346 Manipulators may implement
351 # Internal, do not override unless you really
352 # really really deeply know what you are doing
353 def keyboard_eval(self
, context
, event
):
355 evaluate keyboard entry while typing
356 do not override this one
362 self
.length_entered
= self
.length_entered
[:self
.line_pos
] + c
+ self
.length_entered
[self
.line_pos
:]
365 if self
.length_entered
:
366 if event
.type == 'BACK_SPACE':
367 self
.length_entered
= self
.length_entered
[:self
.line_pos
- 1] + self
.length_entered
[self
.line_pos
:]
370 elif event
.type == 'DEL':
371 self
.length_entered
= self
.length_entered
[:self
.line_pos
] + self
.length_entered
[self
.line_pos
+ 1:]
373 elif event
.type == 'LEFT_ARROW':
374 self
.line_pos
= (self
.line_pos
- 1) % (len(self
.length_entered
) + 1)
376 elif event
.type == 'RIGHT_ARROW':
377 self
.line_pos
= (self
.line_pos
+ 1) % (len(self
.length_entered
) + 1)
380 value
= bpy
.utils
.units
.to_value(context
.scene
.unit_settings
.system
, self
.value_type
, self
.length_entered
)
381 draw_on_header
= self
.keyboard_editing(context
, event
, value
)
382 except: # ValueError:
383 draw_on_header
= True
388 if self
.length_entered
:
390 a
= self
.length_entered
[:pos
] + '|' + self
.length_entered
[pos
:]
391 context
.area
.header_text_set("%s" % (a
))
393 # modal mode: do not let event bubble up
396 def modal(self
, context
, event
):
399 handle mouse, and keyboard events
400 enable and disable feedback
402 # print("Manipulator modal:%s %s" % (event.value, event.type))
404 if event
.type == 'MOUSEMOVE':
405 return self
.mouse_move(context
, event
)
407 elif event
.value
== 'PRESS':
409 if event
.type == 'LEFTMOUSE':
410 active
= self
.mouse_press(context
, event
)
412 self
.feedback
.enable()
415 elif self
.keymap
.check(event
, self
.keymap
.undo
):
416 if self
.keyboard_input_active
:
417 self
.keyboard_input_active
= False
418 self
.keyboard_cancel(context
, event
)
419 self
.feedback
.disable()
423 elif self
.keyboard_input_active
and (
424 event
.ascii
in self
.keyboard_ascii
or
425 event
.type in self
.keyboard_type
428 return self
.keyboard_eval(context
, event
)
430 elif event
.type in {'ESC', 'RIGHTMOUSE'}:
431 self
.feedback
.disable()
432 if self
.keyboard_input_active
:
433 # allow keyboard exit without setting value
434 self
.length_entered
= ""
436 self
.keyboard_input_active
= False
437 self
.keyboard_cancel(context
, event
)
440 self
.cancel(context
, event
)
444 elif event
.value
== 'RELEASE':
446 if event
.type == 'LEFTMOUSE':
447 if not self
.keyboard_input_active
:
448 self
.feedback
.disable()
449 return self
.mouse_release(context
, event
)
451 elif self
.keyboard_input_active
and event
.type in {'RET', 'NUMPAD_ENTER'}:
452 # validate keyboard input
453 if self
.length_entered
!= "":
455 value
= bpy
.utils
.units
.to_value(
456 context
.scene
.unit_settings
.system
,
457 self
.value_type
, self
.length_entered
)
458 self
.length_entered
= ""
459 ret
= self
.keyboard_done(context
, event
, value
)
460 except: # ValueError:
462 self
.keyboard_cancel(context
, event
)
464 context
.area
.header_text_set(None)
465 self
.keyboard_input_active
= False
466 self
.feedback
.disable()
471 def mouse_position(self
, event
):
473 store mouse position in a 2d Vector
475 self
.mouse_pos
.x
, self
.mouse_pos
.y
= event
.mouse_region_x
, event
.mouse_region_y
477 def get_pos3d(self
, context
):
479 convert mouse pos to 3d point over plane defined by origin and normal
482 region
= context
.region
483 rv3d
= context
.region_data
484 rM
= context
.active_object
.matrix_world
.to_3x3()
485 view_vector_mouse
= view3d_utils
.region_2d_to_vector_3d(region
, rv3d
, self
.mouse_pos
)
486 ray_origin_mouse
= view3d_utils
.region_2d_to_origin_3d(region
, rv3d
, self
.mouse_pos
)
487 pt
= intersect_line_plane(ray_origin_mouse
, ray_origin_mouse
+ view_vector_mouse
,
488 self
.origin
, rM
@ self
.manipulator
.normal
, False)
489 # fix issue with parallel plane
491 pt
= intersect_line_plane(ray_origin_mouse
, ray_origin_mouse
+ view_vector_mouse
,
492 self
.origin
, view_vector_mouse
, False)
495 def get_value(self
, data
, attr
, index
=-1):
497 Datablock value getter with index support
501 return getattr(data
, attr
)[index
]
503 return getattr(data
, attr
)
505 print("get_value of %s %s failed" % (data
, attr
))
508 def set_value(self
, context
, data
, attr
, value
, index
=-1):
510 Datablock value setter with index support
513 if self
.get_value(data
, attr
, index
) != value
:
515 # switch context so unselected object may be manipulable too
517 state
= o
.select_get()
518 o
.select_set(state
=True)
519 context
.view_layer
.objects
.active
= o
521 getattr(data
, attr
)[index
] = value
523 setattr(data
, attr
, value
)
524 o
.select_set(state
=state
)
525 old
.select_set(state
=True)
526 context
.view_layer
.objects
.active
= old
530 def preTranslate(self
, tM
, vec
):
532 return a preTranslated Matrix
534 vec Vector translation
536 return tM
@ Matrix
.Translation(vec
)
538 def _move(self
, o
, axis
, value
):
540 vec
= Vector((value
, 0, 0))
542 vec
= Vector((0, value
, 0))
544 vec
= Vector((0, 0, value
))
545 o
.matrix_world
= self
.preTranslate(o
.matrix_world
, vec
)
547 def move_linked(self
, context
, axis
, value
):
549 Move an object along local axis
550 takes care of linked too, fix issue #8
552 old
= context
.active_object
553 bpy
.ops
.object.select_all(action
='DESELECT')
554 self
.o
.select_set(state
=True)
555 context
.view_layer
.objects
.active
= self
.o
556 bpy
.ops
.object.select_linked(type='OBDATA')
557 for o
in context
.selected_objects
:
559 self
._move
(o
, axis
, value
)
560 bpy
.ops
.object.select_all(action
='DESELECT')
561 old
.select_set(state
=True)
562 context
.view_layer
.objects
.active
= old
564 def move(self
, context
, axis
, value
):
566 Move an object along local axis
568 self
._move
(self
.o
, axis
, value
)
571 # Generic snap tool for line based archipack objects (fence, wall, maybe stair too)
575 class WallSnapManipulator(Manipulator
):
577 np_station snap inspired manipulator
578 Use prop1_name as string part index
579 Use prop2_name as string identifier height property for placeholders
581 Misnamed as it work for all line based archipack's
582 primitives, currently wall and fences,
583 but may also work with stairs (sharing same data structure)
585 def __init__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None):
586 self
.placeholder_area
= GlPolygon((0.5, 0, 0, 0.2))
587 self
.placeholder_line
= GlPolyline((0.5, 0, 0, 0.8))
588 self
.placeholder_line
.closed
= True
589 self
.label
= GlText()
591 self
.handle
= SquareHandle(handle_size
, 1.2 * arrow_size
, draggable
=True, selectable
=True)
592 Manipulator
.__init
__(self
, context
, o
, datablock
, manipulator
, snap_callback
)
593 self
.selectable
= True
595 def select(self
, cursor_area
):
596 self
.selected
= self
.selected
or cursor_area
.in_area(self
.handle
.pos_2d
)
597 self
.handle
.selected
= self
.selected
599 def deselect(self
, cursor_area
):
600 self
.selected
= not cursor_area
.in_area(self
.handle
.pos_2d
)
601 self
.handle
.selected
= self
.selected
603 def check_hover(self
):
604 self
.handle
.check_hover(self
.mouse_pos
)
606 def mouse_press(self
, context
, event
):
609 if self
.handle
.hover
:
611 self
.handle
.active
= True
613 idx
= int(self
.manipulator
.prop1_name
)
615 # get selected manipulators idx
617 for m
in manips
[self
.o
.name
].stack
:
618 if m
is not None and m
.selected
:
619 selection
.append(int(m
.manipulator
.prop1_name
))
621 # store all points of wall
622 for i
, part
in enumerate(self
.datablock
.parts
):
623 p0
, p1
, side
, normal
= part
.manipulators
[2].get_pts(self
.o
.matrix_world
)
624 # if selected p0 will move and require placeholder
625 gl_pts3d
.append((p0
, p1
, i
in selection
or i
== idx
))
627 self
.feedback
.instructions(context
, "Move / Snap", "Drag to move, use keyboard to input values", [
629 ('X Y', 'Constraint to axis (toggle Global Local None)'),
630 ('SHIFT+Z', 'Constraint to xy plane'),
631 ('MMBTN', 'Constraint to axis'),
632 ('RIGHTCLICK or ESC', 'exit without change')
634 self
.feedback
.enable()
635 self
.handle
.hover
= False
636 self
.o
.select_set(state
=True)
637 takeloc
, right
, side
, dz
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
638 dx
= (right
- takeloc
).normalized()
641 [dx
.x
, dy
.x
, dz
.x
, takeloc
.x
],
642 [dx
.y
, dy
.y
, dz
.y
, takeloc
.y
],
643 [dx
.z
, dy
.z
, dz
.z
, takeloc
.z
],
646 snap_point(takemat
=takemat
, draw
=self
.sp_draw
, callback
=self
.sp_callback
,
647 constraint_axis
=(True, True, False))
648 # this prevent other selected to run
653 def mouse_release(self
, context
, event
):
655 self
.handle
.active
= False
657 self
.feedback
.disable()
658 # False to callback manipulable_release
661 def sp_callback(self
, context
, event
, state
, sp
):
663 np station callback on moving, place, or cancel
666 logger
.debug("WallSnapManipulator.sp_callback")
668 if state
== 'SUCCESS':
670 o
.select_set(state
=True)
671 context
.view_layer
.objects
.active
= o
672 # apply changes to wall
674 g
= d
.get_generator()
676 # rotation relative to object
677 rM
= o
.matrix_world
.inverted().to_3x3()
679 # x_axis = (rM @ Vector((1, 0, 0))).to_2d()
683 for p0
, p1
, selected
in gl_pts3d
:
687 # new location in object space
688 pt
= g
.segs
[idx
].lerp(0) + delta
.to_2d()
690 # move last point of segment before current
692 g
.segs
[idx
- 1].p1
= pt
694 # move first point of current segment
699 # update properties from generator
701 d
.auto_update
= False
702 for p0
, p1
, selected
in gl_pts3d
:
706 # adjust segment before current
709 part
= d
.parts
[idx
- 1]
712 part
.a0
= w
.delta_angle(g
.segs
[idx
- 2])
716 if "C_" in part
.type:
719 part
.length
= w
.length
721 # adjust current segment
726 part
.a0
= w
.delta_angle(g
.segs
[idx
- 1])
729 # move object when point 0
731 d
.move_object(o
, o
.matrix_world
.translation
+ sp
.delta
)
732 # self.o.location += sp.delta
733 # self.o.matrix_world.translation += sp.delta
737 if "C_" in part
.type:
740 part
.length
= w
.length
743 if idx
+ 1 < d
.n_parts
:
744 d
.parts
[idx
+ 1].a0
= g
.segs
[idx
+ 1].delta_angle(w
)
748 self
.mouse_release(context
, event
)
749 if hasattr(d
, "relocate_childs"):
750 d
.relocate_childs(context
, o
, g
)
754 if state
== 'CANCEL':
755 self
.mouse_release(context
, event
)
756 logger
.debug("WallSnapManipulator.sp_callback done")
760 def sp_draw(self
, sp
, context
):
761 # draw wall placeholders
762 logger
.debug("WallSnapManipulator.sp_draw")
769 z
= self
.get_value(self
.datablock
, self
.manipulator
.prop2_name
)
772 for p0
, p1
, selected
in gl_pts3d
:
775 # when selected, p0 is moving
776 # last one p1 should move too
777 # last one require a placeholder too
779 if len(placeholders
) > 0:
780 placeholders
[-1][1] = pt
781 placeholders
[-1][2] = True
782 placeholders
.append([pt
, p1
, selected
])
784 # first selected and closed -> should move last p1 too
785 if gl_pts3d
[0][2] and self
.datablock
.closed
:
786 placeholders
[-1][1] = placeholders
[0][0].copy()
787 placeholders
[-1][2] = True
789 # last one not visible when not closed
790 if not self
.datablock
.closed
:
791 placeholders
[-1][2] = False
793 for p0
, p1
, selected
in placeholders
:
795 self
.placeholder_area
.set_pos([p0
, p1
, Vector((p1
.x
, p1
.y
, p1
.z
+ z
)), Vector((p0
.x
, p0
.y
, p0
.z
+ z
))])
796 self
.placeholder_line
.set_pos([p0
, p1
, Vector((p1
.x
, p1
.y
, p1
.z
+ z
)), Vector((p0
.x
, p0
.y
, p0
.z
+ z
))])
797 self
.placeholder_area
.draw(context
, render
=False)
798 self
.placeholder_line
.draw(context
, render
=False)
800 p0
, p1
, side
, normal
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
802 self
.line
.v
= sp
.delta
803 self
.label
.set_pos(context
, self
.line
.length
, self
.line
.lerp(0.5), self
.line
.v
, normal
=Vector((0, 0, 1)))
804 self
.line
.draw(context
, render
=False)
805 self
.label
.draw(context
, render
=False)
806 logger
.debug("WallSnapManipulator.sp_draw done")
808 def mouse_move(self
, context
, event
):
809 self
.mouse_position(event
)
810 if self
.handle
.active
:
811 # False here to pass_through
812 # print("i'm able to pick up mouse move event while transform running")
818 def draw_callback(self
, _self
, context
, render
=False):
819 left
, right
, side
, normal
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
820 self
.handle
.set_pos(context
, left
, (left
- right
).normalized(), normal
=normal
)
821 self
.handle
.draw(context
, render
)
822 self
.feedback
.draw(context
, render
)
825 class CounterManipulator(Manipulator
):
827 increase or decrease an integer step by step
828 right on click to prevent misuse
830 def __init__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None):
831 self
.handle_left
= TriHandle(handle_size
, arrow_size
, draggable
=True)
832 self
.handle_right
= TriHandle(handle_size
, arrow_size
, draggable
=True)
833 self
.line_0
= GlLine()
834 self
.label
= GlText()
835 self
.label
.unit_mode
= 'NONE'
836 self
.label
.precision
= 0
837 Manipulator
.__init
__(self
, context
, o
, datablock
, manipulator
, snap_callback
)
839 def check_hover(self
):
840 self
.handle_right
.check_hover(self
.mouse_pos
)
841 self
.handle_left
.check_hover(self
.mouse_pos
)
843 def mouse_press(self
, context
, event
):
844 if self
.handle_right
.hover
:
845 value
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
846 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, value
+ 1)
847 self
.handle_right
.active
= True
849 if self
.handle_left
.hover
:
850 value
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
851 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, value
- 1)
852 self
.handle_left
.active
= True
856 def mouse_release(self
, context
, event
):
858 self
.handle_right
.active
= False
859 self
.handle_left
.active
= False
862 def mouse_move(self
, context
, event
):
863 self
.mouse_position(event
)
864 if self
.handle_right
.active
:
866 if self
.handle_left
.active
:
872 def draw_callback(self
, _self
, context
, render
=False):
874 draw on screen feedback using gl.
876 # won't render counter
879 left
, right
, side
, normal
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
882 self
.line_0
.v
= right
- left
883 self
.line_0
.z_axis
= normal
884 self
.label
.z_axis
= normal
885 value
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
886 self
.handle_left
.set_pos(context
, self
.line_0
.p
, -self
.line_0
.v
, normal
=normal
)
887 self
.handle_right
.set_pos(context
, self
.line_0
.lerp(1), self
.line_0
.v
, normal
=normal
)
888 self
.label
.set_pos(context
, value
, self
.line_0
.lerp(0.5), self
.line_0
.v
, normal
=normal
)
889 self
.label
.draw(context
, render
)
890 self
.handle_left
.draw(context
, render
)
891 self
.handle_right
.draw(context
, render
)
894 class DumbStringManipulator(Manipulator
):
896 not a real manipulator, but allow to show a string
898 def __init__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None):
899 self
.label
= GlText(colour
=(0, 0, 0, 1))
900 self
.label
.unit_mode
= 'NONE'
901 self
.label
.label
= manipulator
.prop1_name
902 Manipulator
.__init
__(self
, context
, o
, datablock
, manipulator
, snap_callback
)
904 def check_hover(self
):
907 def mouse_press(self
, context
, event
):
910 def mouse_release(self
, context
, event
):
913 def mouse_move(self
, context
, event
):
916 def draw_callback(self
, _self
, context
, render
=False):
918 draw on screen feedback using gl.
920 # won't render string
923 left
, right
, side
, normal
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
924 pos
= left
+ 0.5 * (right
- left
)
925 self
.label
.set_pos(context
, None, pos
, pos
, normal
=normal
)
926 self
.label
.draw(context
, render
)
929 class SizeManipulator(Manipulator
):
931 def __init__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None):
932 self
.handle_left
= TriHandle(handle_size
, arrow_size
)
933 self
.handle_right
= TriHandle(handle_size
, arrow_size
, draggable
=True)
934 self
.line_0
= GlLine()
935 self
.line_1
= GlLine()
936 self
.line_2
= GlLine()
937 self
.label
= EditableText(handle_size
, arrow_size
, draggable
=True)
938 # self.label.label = 'S '
939 Manipulator
.__init
__(self
, context
, o
, datablock
, manipulator
, snap_callback
)
941 def check_hover(self
):
942 self
.handle_right
.check_hover(self
.mouse_pos
)
943 self
.label
.check_hover(self
.mouse_pos
)
945 def mouse_press(self
, context
, event
):
947 if self
.handle_right
.hover
:
949 self
.original_size
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
950 self
.original_location
= self
.o
.matrix_world
.translation
.copy()
951 self
.feedback
.instructions(context
, "Size", "Drag or Keyboard to modify size", [
954 ('RIGHTCLICK or ESC', 'cancel')
956 left
, right
, side
, dz
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
957 dx
= (right
- left
).normalized()
960 [dx
.x
, dy
.x
, dz
.x
, right
.x
],
961 [dx
.y
, dy
.y
, dz
.y
, right
.y
],
962 [dx
.z
, dy
.z
, dz
.z
, right
.z
],
965 gl_pts3d
= [left
, right
]
966 snap_point(takemat
=takemat
,
967 callback
=self
.sp_callback
,
968 constraint_axis
=(True, False, False))
969 self
.handle_right
.active
= True
972 self
.feedback
.instructions(context
, "Size", "Use keyboard to modify size",
973 [('ENTER', 'Validate'), ('RIGHTCLICK or ESC', 'cancel')])
974 self
.label
.active
= True
975 self
.keyboard_input_active
= True
979 def mouse_release(self
, context
, event
):
982 self
.handle_right
.active
= False
983 if not self
.keyboard_input_active
:
984 self
.feedback
.disable()
987 def mouse_move(self
, context
, event
):
988 self
.mouse_position(event
)
990 self
.update(context
, event
)
996 def cancel(self
, context
, event
):
998 self
.mouse_release(context
, event
)
999 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, self
.original_size
)
1001 def keyboard_done(self
, context
, event
, value
):
1002 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, value
)
1003 self
.label
.active
= False
1006 def keyboard_cancel(self
, context
, event
):
1007 self
.label
.active
= False
1010 def update(self
, context
, event
):
1014 pt
= self
.get_pos3d(context
)
1015 pt
, t
= intersect_point_line(pt
, self
.line_0
.p
, self
.line_2
.p
)
1016 length
= (self
.line_0
.p
- pt
).length
1018 length
= round(length
, 1)
1019 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, length
)
1021 def draw_callback(self
, _self
, context
, render
=False):
1023 draw on screen feedback using gl.
1025 left
, right
, side
, normal
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
1027 self
.line_1
.p
= left
1028 self
.line_1
.v
= right
- left
1029 self
.line_0
.z_axis
= normal
1030 self
.line_1
.z_axis
= normal
1031 self
.line_2
.z_axis
= normal
1032 self
.label
.z_axis
= normal
1033 self
.line_0
= self
.line_1
.sized_normal(0, side
.x
* 1.1)
1034 self
.line_2
= self
.line_1
.sized_normal(1, side
.x
* 1.1)
1035 self
.line_1
.offset(side
.x
* 1.0)
1036 self
.handle_left
.set_pos(context
, self
.line_1
.p
, -self
.line_1
.v
, normal
=normal
)
1037 self
.handle_right
.set_pos(context
, self
.line_1
.lerp(1), self
.line_1
.v
, normal
=normal
)
1038 if not self
.keyboard_input_active
:
1039 self
.label_value
= self
.line_1
.length
1040 self
.label
.set_pos(context
, self
.label_value
, self
.line_1
.lerp(0.5), self
.line_1
.v
, normal
=normal
)
1041 self
.line_0
.draw(context
, render
)
1042 self
.line_1
.draw(context
, render
)
1043 self
.line_2
.draw(context
, render
)
1044 self
.handle_left
.draw(context
, render
)
1045 self
.handle_right
.draw(context
, render
)
1046 self
.label
.draw(context
, render
)
1047 self
.feedback
.draw(context
, render
)
1049 def sp_callback(self
, context
, event
, state
, sp
):
1050 logger
.debug("SizeManipulator.sp_callback")
1053 p0
= gl_pts3d
[0].copy()
1054 p1
= gl_pts3d
[1].copy()
1056 if state
!= 'CANCEL':
1059 length
= (p0
- p1
).length
1061 if state
!= 'CANCEL' and event
.alt
:
1063 length
= round(length
, 2)
1065 length
= round(length
, 1)
1067 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, length
)
1069 if state
!= 'RUNNING':
1070 self
.mouse_release(context
, event
)
1072 logger
.debug("SizeManipulator.sp_callback done")
1075 class SizeLocationManipulator(SizeManipulator
):
1077 Handle resizing by any of the boundaries
1078 of objects with centered pivots
1079 so when size change, object should move of the
1080 half of the change in the direction of change.
1082 Also take care of moving linked objects too
1083 Changing size is not necessary as link does
1084 already handle this and childs panels are
1085 updated by base object.
1087 def __init__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None):
1088 SizeManipulator
.__init
__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
)
1089 self
.handle_left
.draggable
= True
1091 def check_hover(self
):
1092 self
.handle_right
.check_hover(self
.mouse_pos
)
1093 self
.handle_left
.check_hover(self
.mouse_pos
)
1094 self
.label
.check_hover(self
.mouse_pos
)
1096 def mouse_press(self
, context
, event
):
1097 if self
.handle_right
.hover
:
1099 self
.original_location
= self
.o
.matrix_world
.translation
.copy()
1100 self
.original_size
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1101 self
.feedback
.instructions(context
, "Size", "Drag to modify size", [
1102 ('ALT', 'Round value'), ('RIGHTCLICK or ESC', 'cancel')
1104 self
.handle_right
.active
= True
1106 if self
.handle_left
.hover
:
1108 self
.original_location
= self
.o
.matrix_world
.translation
.copy()
1109 self
.original_size
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1110 self
.feedback
.instructions(context
, "Size", "Drag to modify size", [
1111 ('ALT', 'Round value'), ('RIGHTCLICK or ESC', 'cancel')
1113 self
.handle_left
.active
= True
1115 if self
.label
.hover
:
1116 self
.feedback
.instructions(context
, "Size", "Use keyboard to modify size",
1117 [('ENTER', 'Validate'), ('RIGHTCLICK or ESC', 'cancel')])
1118 self
.label
.active
= True
1119 self
.keyboard_input_active
= True
1123 def mouse_release(self
, context
, event
):
1126 self
.handle_right
.active
= False
1127 self
.handle_left
.active
= False
1128 if not self
.keyboard_input_active
:
1129 self
.feedback
.disable()
1132 def mouse_move(self
, context
, event
):
1133 self
.mouse_position(event
)
1134 if self
.handle_right
.active
or self
.handle_left
.active
:
1135 self
.update(context
, event
)
1141 def keyboard_done(self
, context
, event
, value
):
1142 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, value
)
1143 # self.move_linked(context, self.manipulator.prop2_name, dl)
1144 self
.label
.active
= False
1145 self
.feedback
.disable()
1148 def cancel(self
, context
, event
):
1150 self
.mouse_release(context
, event
)
1151 # must move back to original location
1152 itM
= self
.o
.matrix_world
.inverted()
1153 dl
= self
.get_value(itM
@ self
.original_location
, self
.manipulator
.prop2_name
)
1155 self
.move(context
, self
.manipulator
.prop2_name
, dl
)
1156 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, self
.original_size
)
1157 self
.move_linked(context
, self
.manipulator
.prop2_name
, dl
)
1159 def update(self
, context
, event
):
1163 pt
= self
.get_pos3d(context
)
1164 pt
, t
= intersect_point_line(pt
, self
.line_0
.p
, self
.line_2
.p
)
1166 len_0
= (pt
- self
.line_0
.p
).length
1167 len_1
= (pt
- self
.line_2
.p
).length
1169 length
= max(len_0
, len_1
)
1172 length
= round(length
, 1)
1174 dl
= length
- self
.line_1
.length
1181 self
.move(context
, self
.manipulator
.prop2_name
, dl
)
1182 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, length
)
1183 self
.move_linked(context
, self
.manipulator
.prop2_name
, dl
)
1186 class SnapSizeLocationManipulator(SizeLocationManipulator
):
1188 Snap aware extension of SizeLocationManipulator
1189 Handle resizing by any of the boundaries
1190 of objects with centered pivots
1191 so when size change, object should move of the
1192 half of the change in the direction of change.
1194 Also take care of moving linked objects too
1195 Changing size is not necessary as link does
1196 already handle this and childs panels are
1197 updated by base object.
1201 def __init__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None):
1202 SizeLocationManipulator
.__init
__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
)
1204 def mouse_press(self
, context
, event
):
1206 if self
.handle_right
.hover
:
1208 self
.original_size
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1209 self
.original_location
= self
.o
.matrix_world
.translation
.copy()
1210 self
.feedback
.instructions(context
, "Size", "Drag or Keyboard to modify size", [
1213 ('RIGHTCLICK or ESC', 'cancel')
1215 left
, right
, side
, dz
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
1216 dx
= (right
- left
).normalized()
1219 [dx
.x
, dy
.x
, dz
.x
, right
.x
],
1220 [dx
.y
, dy
.y
, dz
.y
, right
.y
],
1221 [dx
.z
, dy
.z
, dz
.z
, right
.z
],
1224 gl_pts3d
= [left
, right
]
1225 snap_point(takemat
=takemat
,
1226 callback
=self
.sp_callback
,
1227 constraint_axis
=(True, False, False))
1229 self
.handle_right
.active
= True
1232 if self
.handle_left
.hover
:
1234 self
.original_size
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1235 self
.original_location
= self
.o
.matrix_world
.translation
.copy()
1236 self
.feedback
.instructions(context
, "Size", "Drag or Keyboard to modify size", [
1239 ('RIGHTCLICK or ESC', 'cancel')
1241 left
, right
, side
, dz
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
1242 dx
= (left
- right
).normalized()
1245 [dx
.x
, dy
.x
, dz
.x
, left
.x
],
1246 [dx
.y
, dy
.y
, dz
.y
, left
.y
],
1247 [dx
.z
, dy
.z
, dz
.z
, left
.z
],
1250 gl_pts3d
= [left
, right
]
1251 snap_point(takemat
=takemat
,
1252 callback
=self
.sp_callback
,
1253 constraint_axis
=(True, False, False))
1254 self
.handle_left
.active
= True
1257 if self
.label
.hover
:
1258 self
.feedback
.instructions(context
, "Size", "Use keyboard to modify size",
1259 [('ENTER', 'Validate'), ('RIGHTCLICK or ESC', 'cancel')])
1260 self
.label
.active
= True
1261 self
.keyboard_input_active
= True
1266 def sp_callback(self
, context
, event
, state
, sp
):
1267 logger
.debug("SnapSizeLocationManipulator.sp_callback")
1269 p0
= gl_pts3d
[0].copy()
1270 p1
= gl_pts3d
[1].copy()
1272 if state
!= 'CANCEL':
1273 if self
.handle_right
.active
:
1278 l0
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1279 length
= (p0
- p1
).length
1281 if state
!= 'CANCEL' and event
.alt
:
1283 length
= round(length
, 2)
1285 length
= round(length
, 1)
1289 if self
.handle_left
.active
:
1293 # snap_helper = context.object
1294 self
.move(context
, self
.manipulator
.prop2_name
, dl
)
1295 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, length
)
1296 self
.move_linked(context
, self
.manipulator
.prop2_name
, dl
)
1298 # snapping child objects may require base object update
1299 # eg manipulating windows requiring wall update
1300 if self
.snap_callback
is not None:
1301 snap_helper
= context
.active_object
1302 self
.snap_callback(context
, o
=self
.o
, manipulator
=self
)
1303 snap_helper
.select_set(state
=True)
1305 if state
!= 'RUNNING':
1306 self
.mouse_release(context
, event
)
1308 logger
.debug("SnapSizeLocationManipulator.sp_callback done")
1311 class DeltaLocationManipulator(SizeManipulator
):
1313 Move a child window or door in wall segment
1314 not limited to this by the way
1316 def __init__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None):
1317 SizeManipulator
.__init
__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
)
1318 self
.label
.label
= ''
1319 self
.feedback
.instructions(context
, "Move", "Drag to move", [
1321 ('SHIFT', 'Round value'),
1322 ('RIGHTCLICK or ESC', 'cancel')
1325 def check_hover(self
):
1326 self
.handle_right
.check_hover(self
.mouse_pos
)
1328 def mouse_press(self
, context
, event
):
1330 if self
.handle_right
.hover
:
1331 self
.original_location
= self
.o
.matrix_world
.translation
.copy()
1333 self
.feedback
.enable()
1334 self
.handle_right
.active
= True
1336 left
, right
, side
, dz
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
1338 dx
= dp
.normalized()
1340 p0
= left
+ 0.5 * dp
1342 [dx
.x
, dy
.x
, dz
.x
, p0
.x
],
1343 [dx
.y
, dy
.y
, dz
.y
, p0
.y
],
1344 [dx
.z
, dy
.z
, dz
.z
, p0
.z
],
1348 snap_point(takemat
=takemat
,
1349 callback
=self
.sp_callback
,
1351 self
.manipulator
.prop1_name
== 'x',
1352 self
.manipulator
.prop1_name
== 'y',
1353 self
.manipulator
.prop1_name
== 'z'))
1357 def mouse_release(self
, context
, event
):
1359 self
.feedback
.disable()
1361 self
.handle_right
.active
= False
1364 def mouse_move(self
, context
, event
):
1365 self
.mouse_position(event
)
1366 if self
.handle_right
.active
:
1367 # self.update(context, event)
1373 def sp_callback(self
, context
, event
, state
, sp
):
1374 logger
.debug("DeltaLocationManipulator.sp_callback")
1376 if state
== 'CANCEL':
1377 self
.cancel(context
, event
)
1380 p0
= gl_pts3d
[0].copy()
1382 itM
= self
.o
.matrix_world
.inverted()
1383 dl
= self
.get_value(itM
@ p1
, self
.manipulator
.prop1_name
)
1384 self
.move(context
, self
.manipulator
.prop1_name
, dl
)
1386 # snapping child objects may require base object update
1387 # eg manipulating windows requiring wall update
1388 if self
.snap_callback
is not None:
1389 snap_helper
= context
.active_object
1390 self
.snap_callback(context
, o
=self
.o
, manipulator
=self
)
1391 snap_helper
.select_set(state
=True)
1393 if state
== 'SUCCESS':
1394 self
.mouse_release(context
, event
)
1396 logger
.debug("DeltaLocationManipulator.sp_callback done")
1398 def cancel(self
, context
, event
):
1400 self
.mouse_release(context
, event
)
1401 # must move back to original location
1402 itM
= self
.o
.matrix_world
.inverted()
1403 dl
= self
.get_value(itM
@ self
.original_location
, self
.manipulator
.prop1_name
)
1404 self
.move(context
, self
.manipulator
.prop1_name
, dl
)
1406 def draw_callback(self
, _self
, context
, render
=False):
1408 draw on screen feedback using gl.
1410 left
, right
, side
, normal
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
1412 self
.line_1
.p
= left
1413 self
.line_1
.v
= right
- left
1414 self
.line_1
.z_axis
= normal
1415 self
.handle_left
.set_pos(context
, self
.line_1
.lerp(0.5), -self
.line_1
.v
, normal
=normal
)
1416 self
.handle_right
.set_pos(context
, self
.line_1
.lerp(0.5), self
.line_1
.v
, normal
=normal
)
1417 self
.handle_left
.draw(context
, render
)
1418 self
.handle_right
.draw(context
, render
)
1419 self
.feedback
.draw(context
)
1422 class DumbSizeManipulator(SizeManipulator
):
1424 Show a size while not being editable
1426 def __init__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None):
1427 SizeManipulator
.__init
__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
)
1428 self
.handle_right
.draggable
= False
1429 self
.label
.draggable
= False
1430 self
.label
.colour_inactive
= (0, 0, 0, 1)
1431 # self.label.label = 'Dumb '
1433 def mouse_move(self
, context
, event
):
1437 class AngleManipulator(Manipulator
):
1440 There is a default shortcut to +5 and -5 on angles with left/right arrows
1442 Manipulate angle between segments
1446 def __init__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None):
1448 self
.handle_right
= TriHandle(handle_size
, arrow_size
, draggable
=True)
1449 self
.handle_center
= SquareHandle(handle_size
, arrow_size
)
1451 self
.line_0
= GlLine()
1452 self
.line_1
= GlLine()
1453 self
.label_a
= EditableText(handle_size
, arrow_size
, draggable
=True)
1454 self
.label_a
.unit_type
= 'ANGLE'
1455 Manipulator
.__init
__(self
, context
, o
, datablock
, manipulator
, snap_callback
)
1456 self
.pts_mode
= 'RADIUS'
1458 def check_hover(self
):
1459 self
.handle_right
.check_hover(self
.mouse_pos
)
1460 self
.label_a
.check_hover(self
.mouse_pos
)
1462 def mouse_press(self
, context
, event
):
1463 if self
.handle_right
.hover
:
1465 self
.original_angle
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1466 self
.feedback
.instructions(context
, "Angle", "Drag to modify angle", [
1467 ('SHIFT', 'Round value'),
1468 ('RIGHTCLICK or ESC', 'cancel')
1470 self
.handle_right
.active
= True
1472 if self
.label_a
.hover
:
1473 self
.feedback
.instructions(context
, "Angle (degree)", "Use keyboard to modify angle",
1474 [('ENTER', 'validate'),
1475 ('RIGHTCLICK or ESC', 'cancel')])
1476 self
.value_type
= 'ROTATION'
1477 self
.label_a
.active
= True
1478 self
.label_value
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1479 self
.keyboard_input_active
= True
1483 def mouse_release(self
, context
, event
):
1485 self
.handle_right
.active
= False
1489 def mouse_move(self
, context
, event
):
1490 self
.mouse_position(event
)
1492 # print("AngleManipulator.mouse_move")
1493 self
.update(context
, event
)
1499 def keyboard_done(self
, context
, event
, value
):
1500 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, value
)
1501 self
.label_a
.active
= False
1504 def keyboard_cancel(self
, context
, event
):
1505 self
.label_a
.active
= False
1508 def cancel(self
, context
, event
):
1510 self
.mouse_release(context
, event
)
1511 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, self
.original_angle
)
1513 def update(self
, context
, event
):
1514 pt
= self
.get_pos3d(context
)
1516 v
= 2 * self
.arc
.r
* (pt
- c
).normalized()
1519 p0
, p1
= intersect_line_sphere(v0
, v1
, c
, self
.arc
.r
)
1520 if p0
is not None and p1
is not None:
1522 if (p1
- pt
).length
< (p0
- pt
).length
:
1526 da
= atan2(v
.y
, v
.x
) - self
.line_0
.angle
1531 # from there pi > da > -pi
1532 # print("a:%.4f da:%.4f a0:%.4f" % (atan2(v.y, v.x), da, self.line_0.angle))
1538 da
= round(da
/ pi
* 180, 0) / 180 * pi
1539 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, da
)
1541 def draw_callback(self
, _self
, context
, render
=False):
1542 c
, left
, right
, normal
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
1543 self
.line_0
.z_axis
= normal
1544 self
.line_1
.z_axis
= normal
1545 self
.arc
.z_axis
= normal
1546 self
.label_a
.z_axis
= normal
1551 self
.line_0
.v
= left
1552 self
.line_0
.v
= -self
.line_0
.cross
.normalized()
1553 self
.line_1
.v
= right
1554 self
.line_1
.v
= self
.line_1
.cross
.normalized()
1555 self
.arc
.a0
= self
.line_0
.angle
1556 self
.arc
.da
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1558 self
.handle_right
.set_pos(context
, self
.line_1
.lerp(1),
1559 self
.line_1
.sized_normal(1, -1 if self
.arc
.da
> 0 else 1).v
)
1560 self
.handle_center
.set_pos(context
, self
.arc
.c
, -self
.line_0
.v
)
1561 label_value
= self
.arc
.da
1562 if self
.keyboard_input_active
:
1563 label_value
= self
.label_value
1564 self
.label_a
.set_pos(context
, label_value
, self
.arc
.lerp(0.5), -self
.line_0
.v
)
1565 self
.arc
.draw(context
, render
)
1566 self
.line_0
.draw(context
, render
)
1567 self
.line_1
.draw(context
, render
)
1568 self
.handle_right
.draw(context
, render
)
1569 self
.handle_center
.draw(context
, render
)
1570 self
.label_a
.draw(context
, render
)
1571 self
.feedback
.draw(context
, render
)
1574 class DumbAngleManipulator(AngleManipulator
):
1575 def __init__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None):
1576 AngleManipulator
.__init
__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None)
1577 self
.handle_right
.draggable
= False
1578 self
.label_a
.draggable
= False
1580 def draw_callback(self
, _self
, context
, render
=False):
1581 c
, left
, right
, normal
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
1582 self
.line_0
.z_axis
= normal
1583 self
.line_1
.z_axis
= normal
1584 self
.arc
.z_axis
= normal
1585 self
.label_a
.z_axis
= normal
1590 self
.line_0
.v
= left
1591 self
.line_0
.v
= -self
.line_0
.cross
.normalized()
1592 self
.line_1
.v
= right
1593 self
.line_1
.v
= self
.line_1
.cross
.normalized()
1595 # prevent ValueError in angle_signed
1596 if self
.line_0
.length
== 0 or self
.line_1
.length
== 0:
1599 self
.arc
.a0
= self
.line_0
.angle
1600 self
.arc
.da
= self
.line_1
.v
.to_2d().angle_signed(self
.line_0
.v
.to_2d())
1602 self
.handle_right
.set_pos(context
, self
.line_1
.lerp(1),
1603 self
.line_1
.sized_normal(1, -1 if self
.arc
.da
> 0 else 1).v
)
1604 self
.handle_center
.set_pos(context
, self
.arc
.c
, -self
.line_0
.v
)
1605 label_value
= self
.arc
.da
1606 self
.label_a
.set_pos(context
, label_value
, self
.arc
.lerp(0.5), -self
.line_0
.v
)
1607 self
.arc
.draw(context
, render
)
1608 self
.line_0
.draw(context
, render
)
1609 self
.line_1
.draw(context
, render
)
1610 self
.handle_right
.draw(context
, render
)
1611 self
.handle_center
.draw(context
, render
)
1612 self
.label_a
.draw(context
, render
)
1613 self
.feedback
.draw(context
, render
)
1616 class ArcAngleManipulator(Manipulator
):
1618 Manipulate angle of an arc
1619 when angle < 0 the arc center is on the left part of the circle
1620 when angle > 0 the arc center is on the right part of the circle
1624 def __init__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None):
1627 self
.handle_left
= SquareHandle(handle_size
, arrow_size
)
1629 self
.handle_right
= TriHandle(handle_size
, arrow_size
, draggable
=True)
1630 self
.handle_center
= SquareHandle(handle_size
, arrow_size
)
1632 self
.line_0
= GlLine()
1633 self
.line_1
= GlLine()
1634 self
.label_a
= EditableText(handle_size
, arrow_size
, draggable
=True)
1635 self
.label_r
= EditableText(handle_size
, arrow_size
, draggable
=False)
1636 self
.label_a
.unit_type
= 'ANGLE'
1637 Manipulator
.__init
__(self
, context
, o
, datablock
, manipulator
, snap_callback
)
1638 self
.pts_mode
= 'RADIUS'
1640 def check_hover(self
):
1641 self
.handle_right
.check_hover(self
.mouse_pos
)
1642 self
.label_a
.check_hover(self
.mouse_pos
)
1644 def mouse_press(self
, context
, event
):
1645 if self
.handle_right
.hover
:
1647 self
.original_angle
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1648 self
.feedback
.instructions(context
, "Angle (degree)", "Drag to modify angle", [
1649 ('SHIFT', 'Round value'),
1650 ('RIGHTCLICK or ESC', 'cancel')
1652 self
.handle_right
.active
= True
1654 if self
.label_a
.hover
:
1655 self
.feedback
.instructions(context
, "Angle (degree)", "Use keyboard to modify angle",
1656 [('ENTER', 'validate'),
1657 ('RIGHTCLICK or ESC', 'cancel')])
1658 self
.value_type
= 'ROTATION'
1659 self
.label_value
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1660 self
.label_a
.active
= True
1661 self
.keyboard_input_active
= True
1663 if self
.label_r
.hover
:
1664 self
.feedback
.instructions(context
, "Radius", "Use keyboard to modify radius",
1665 [('ENTER', 'validate'),
1666 ('RIGHTCLICK or ESC', 'cancel')])
1667 self
.value_type
= 'LENGTH'
1668 self
.label_r
.active
= True
1669 self
.keyboard_input_active
= True
1673 def mouse_release(self
, context
, event
):
1675 self
.handle_right
.active
= False
1679 def mouse_move(self
, context
, event
):
1680 self
.mouse_position(event
)
1681 if self
.handle_right
.active
:
1682 self
.update(context
, event
)
1688 def keyboard_done(self
, context
, event
, value
):
1689 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, value
)
1690 self
.label_a
.active
= False
1691 self
.label_r
.active
= False
1694 def keyboard_cancel(self
, context
, event
):
1695 self
.label_a
.active
= False
1696 self
.label_r
.active
= False
1699 def cancel(self
, context
, event
):
1701 self
.mouse_release(context
, event
)
1702 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, self
.original_angle
)
1704 def update(self
, context
, event
):
1706 pt
= self
.get_pos3d(context
)
1709 v
= 2 * self
.arc
.r
* (pt
- c
).normalized()
1712 p0
, p1
= intersect_line_sphere(v0
, v1
, c
, self
.arc
.r
)
1714 if p0
is not None and p1
is not None:
1715 # find nearest mouse intersection point
1716 if (p1
- pt
).length
< (p0
- pt
).length
:
1721 s
= self
.arc
.tangeant(0, 1)
1722 res
, d
, t
= s
.point_sur_segment(pt
)
1725 a
= self
.arc
.sized_normal(0, self
.arc
.r
).angle
1727 a
= self
.arc
.sized_normal(0, -self
.arc
.r
).angle
1729 da
= atan2(v
.y
, v
.x
) - a
1738 # top side bound to +- pi
1746 da
= round(da
/ pi
* 180, 0) / 180 * pi
1747 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, da
)
1749 def draw_callback(self
, _self
, context
, render
=False):
1750 # center : 3d points
1751 # left : 3d vector pt-c
1752 # right : 3d vector pt-c
1753 c
, left
, right
, normal
= self
.manipulator
.get_pts(self
.o
.matrix_world
)
1754 self
.line_0
.z_axis
= normal
1755 self
.line_1
.z_axis
= normal
1756 self
.arc
.z_axis
= normal
1757 self
.label_a
.z_axis
= normal
1758 self
.label_r
.z_axis
= normal
1763 self
.line_0
.v
= left
1764 self
.line_1
.v
= right
1765 self
.arc
.a0
= self
.line_0
.angle
1766 self
.arc
.da
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1767 self
.arc
.r
= left
.length
1768 self
.handle_left
.set_pos(context
, self
.line_0
.lerp(1), self
.line_0
.v
)
1769 self
.handle_right
.set_pos(context
, self
.line_1
.lerp(1),
1770 self
.line_1
.sized_normal(1, -1 if self
.arc
.da
> 0 else 1).v
)
1771 self
.handle_center
.set_pos(context
, self
.arc
.c
, -self
.line_0
.v
)
1772 label_a_value
= self
.arc
.da
1773 label_r_value
= self
.arc
.r
1774 if self
.keyboard_input_active
:
1775 if self
.value_type
== 'LENGTH':
1776 label_r_value
= self
.label_value
1778 label_a_value
= self
.label_value
1779 self
.label_a
.set_pos(context
, label_a_value
, self
.arc
.lerp(0.5), -self
.line_0
.v
)
1780 self
.label_r
.set_pos(context
, label_r_value
, self
.line_0
.lerp(0.5), self
.line_0
.v
)
1781 self
.arc
.draw(context
, render
)
1782 self
.line_0
.draw(context
, render
)
1783 self
.line_1
.draw(context
, render
)
1784 self
.handle_left
.draw(context
, render
)
1785 self
.handle_right
.draw(context
, render
)
1786 self
.handle_center
.draw(context
, render
)
1787 self
.label_r
.draw(context
, render
)
1788 self
.label_a
.draw(context
, render
)
1789 self
.feedback
.draw(context
, render
)
1792 class ArcAngleRadiusManipulator(ArcAngleManipulator
):
1794 Manipulate angle and radius of an arc
1795 when angle < 0 the arc center is on the left part of the circle
1796 when angle > 0 the arc center is on the right part of the circle
1800 def __init__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
=None):
1801 ArcAngleManipulator
.__init
__(self
, context
, o
, datablock
, manipulator
, handle_size
, snap_callback
)
1802 self
.handle_center
= TriHandle(handle_size
, arrow_size
, draggable
=True)
1803 self
.label_r
.draggable
= True
1805 def check_hover(self
):
1806 self
.handle_right
.check_hover(self
.mouse_pos
)
1807 self
.handle_center
.check_hover(self
.mouse_pos
)
1808 self
.label_a
.check_hover(self
.mouse_pos
)
1809 self
.label_r
.check_hover(self
.mouse_pos
)
1811 def mouse_press(self
, context
, event
):
1812 if self
.handle_right
.hover
:
1814 self
.original_angle
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1815 self
.feedback
.instructions(context
, "Angle (degree)", "Drag to modify angle", [
1816 ('SHIFT', 'Round value'),
1817 ('RIGHTCLICK or ESC', 'cancel')
1819 self
.handle_right
.active
= True
1821 if self
.handle_center
.hover
:
1823 self
.original_radius
= self
.get_value(self
.datablock
, self
.manipulator
.prop2_name
)
1824 self
.feedback
.instructions(context
, "Radius", "Drag to modify radius", [
1825 ('SHIFT', 'Round value'),
1826 ('RIGHTCLICK or ESC', 'cancel')
1828 self
.handle_center
.active
= True
1830 if self
.label_a
.hover
:
1831 self
.feedback
.instructions(context
, "Angle (degree)", "Use keyboard to modify angle",
1832 [('ENTER', 'validate'),
1833 ('RIGHTCLICK or ESC', 'cancel')])
1834 self
.value_type
= 'ROTATION'
1835 self
.label_value
= self
.get_value(self
.datablock
, self
.manipulator
.prop1_name
)
1836 self
.label_a
.active
= True
1837 self
.keyboard_input_active
= True
1839 if self
.label_r
.hover
:
1840 self
.feedback
.instructions(context
, "Radius", "Use keyboard to modify radius",
1841 [('ENTER', 'validate'),
1842 ('RIGHTCLICK or ESC', 'cancel')])
1843 self
.value_type
= 'LENGTH'
1844 self
.label_r
.active
= True
1845 self
.keyboard_input_active
= True
1849 def mouse_release(self
, context
, event
):
1852 self
.handle_right
.active
= False
1853 self
.handle_center
.active
= False
1856 def mouse_move(self
, context
, event
):
1857 self
.mouse_position(event
)
1858 if self
.handle_right
.active
:
1859 self
.update(context
, event
)
1861 elif self
.handle_center
.active
:
1862 self
.update_radius(context
, event
)
1868 def keyboard_done(self
, context
, event
, value
):
1869 if self
.value_type
== 'LENGTH':
1870 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop2_name
, value
)
1871 self
.label_r
.active
= False
1873 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, value
)
1874 self
.label_a
.active
= False
1877 def update_radius(self
, context
, event
):
1878 pt
= self
.get_pos3d(context
)
1880 left
= self
.line_0
.lerp(1)
1881 p
, t
= intersect_point_line(pt
, c
, left
)
1882 radius
= (left
- p
).length
1884 radius
= round(radius
, 1)
1885 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop2_name
, radius
)
1887 def cancel(self
, context
, event
):
1888 if self
.handle_right
.active
:
1889 self
.mouse_release(context
, event
)
1890 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop1_name
, self
.original_angle
)
1891 if self
.handle_center
.active
:
1892 self
.mouse_release(context
, event
)
1893 self
.set_value(context
, self
.datablock
, self
.manipulator
.prop2_name
, self
.original_radius
)
1896 # ------------------------------------------------------------------
1897 # Define a single Manipulator Properties to store on object
1898 # ------------------------------------------------------------------
1901 # Allow registering manipulators classes
1902 manipulators_class_lookup
= {}
1905 def register_manipulator(type_key
, manipulator_class
):
1906 if type_key
in manipulators_class_lookup
.keys():
1907 raise RuntimeError("Manipulator of type {} already exists, unable to override".format(type_key
))
1908 manipulators_class_lookup
[type_key
] = manipulator_class
1911 class archipack_manipulator(PropertyGroup
):
1913 A property group to add to manipulable objects
1914 type_key: type of manipulator
1915 prop1_name = the property name of object to modify
1916 prop2_name = another property name of object to modify (eg: angle and radius)
1917 p0, p1, p2 3d Vectors as base points to represent manipulators on screen
1918 normal Vector normal of plane on with draw manipulator
1920 type_key
: StringProperty(default
='SIZE')
1922 # How 3d points are stored in manipulators ?
1923 # SIZE = 2 absolute positioned and a scaling vector
1924 # RADIUS = 1 absolute positioned (center) and 2 relatives (sides)
1925 # POLYGON = 2 absolute positioned and a relative vector (for rect polygons)
1927 pts_mode
: StringProperty(default
='SIZE')
1928 prop1_name
: StringProperty()
1929 prop2_name
: StringProperty()
1930 p0
: FloatVectorProperty(subtype
='XYZ')
1931 p1
: FloatVectorProperty(subtype
='XYZ')
1932 p2
: FloatVectorProperty(subtype
='XYZ')
1933 # allow orientation of manipulators by default on xy plane,
1934 # but may be used to constrain heights on local object space
1935 normal
: FloatVectorProperty(subtype
='XYZ', default
=(0, 0, 1))
1937 def set_pts(self
, pts
, normal
=None):
1939 set 3d location of gl points (in object space)
1940 pts: array of 3 vectors 3d
1941 normal: optional vector 3d default to Z axis
1943 pts
= [Vector(p
) for p
in pts
]
1944 self
.p0
, self
.p1
, self
.p2
= pts
1945 if normal
is not None:
1946 self
.normal
= Vector(normal
)
1948 def get_pts(self
, tM
):
1950 convert points from local to world absolute
1951 to draw them at the right place
1952 tM : object's world matrix
1955 if self
.pts_mode
in ['SIZE', 'POLYGON']:
1956 return tM
@ self
.p0
, tM
@ self
.p1
, self
.p2
, rM
@ self
.normal
1958 return tM
@ self
.p0
, rM
@ self
.p1
, rM
@ self
.p2
, rM
@ self
.normal
1960 def get_prefs(self
, context
):
1965 # retrieve addon name from imports
1966 addon_name
= __name__
.split('.')[0]
1967 prefs
= context
.preferences
.addons
[addon_name
].preferences
1968 arrow_size
= prefs
.arrow_size
1969 handle_size
= prefs
.handle_size
1973 def setup(self
, context
, o
, datablock
, snap_callback
=None):
1975 Factory return a manipulator object or None
1977 datablock: datablock to modify
1978 snap_callback: function call y
1981 self
.get_prefs(context
)
1983 global manipulators_class_lookup
1985 if self
.type_key
not in manipulators_class_lookup
.keys() or \
1986 not manipulators_class_lookup
[self
.type_key
].poll(context
):
1987 # RuntimeError is overkill but may be enabled for debug purposes
1988 # Silently ignore allow skipping manipulators if / when deps as not meet
1989 # manip stack will simply be filled with None objects
1990 # raise RuntimeError("Manipulator of type {} not found".format(self.type_key))
1993 m
= manipulators_class_lookup
[self
.type_key
](context
, o
, datablock
, self
, handle_size
, snap_callback
)
1994 # points storage model as described upside
1995 self
.pts_mode
= m
.pts_mode
1999 # ------------------------------------------------------------------
2000 # Define Manipulable to make a PropertyGroup manipulable
2001 # ------------------------------------------------------------------
2004 class ARCHIPACK_OT_manipulate(Operator
):
2005 bl_idname
= "archipack.manipulate"
2006 bl_label
= "Manipulate"
2007 bl_description
= "Manipulate"
2008 bl_options
= {'REGISTER', 'UNDO'}
2010 object_name
: StringProperty(default
="")
2013 def poll(self
, context
):
2014 return context
.active_object
is not None
2016 def exit_selectmode(self
, context
, key
):
2018 Hide select area on exit
2021 if key
in manips
.keys():
2022 if manips
[key
].manipulable
is not None:
2023 manips
[key
].manipulable
.manipulable_exit_selectmode(context
)
2025 def modal(self
, context
, event
):
2027 # Exit on stack change
2028 # handle multiple object stack
2029 # use object_name property to find manupulated object in stack
2030 # select and make object active
2031 # and exit when not found
2032 if context
.area
is not None:
2033 context
.area
.tag_redraw()
2034 key
= self
.object_name
2035 if check_stack(key
):
2036 self
.exit_selectmode(context
, key
)
2037 remove_manipulable(key
)
2038 # print("modal exit by check_stack(%s)" % (key))
2041 res
= manips
[key
].manipulable
.manipulable_modal(context
, event
)
2043 if 'FINISHED' in res
:
2044 self
.exit_selectmode(context
, key
)
2045 remove_manipulable(key
)
2046 # print("modal exit by {FINISHED}")
2050 def invoke(self
, context
, event
):
2051 if context
.space_data
is not None and context
.space_data
.type == 'VIEW_3D':
2052 context
.window_manager
.modal_handler_add(self
)
2053 return {'RUNNING_MODAL'}
2055 self
.report({'WARNING'}, "Active space must be a View3d")
2056 return {'CANCELLED'}
2059 class ARCHIPACK_OT_disable_manipulate(Operator
):
2060 bl_idname
= "archipack.disable_manipulate"
2061 bl_label
= "Disable Manipulate"
2062 bl_description
= "Disable any active manipulator"
2063 bl_options
= {'REGISTER', 'UNDO'}
2066 def poll(self
, context
):
2069 def execute(self
, context
):
2074 class Manipulable():
2076 A class extending PropertyGroup to setup gl manipulators
2077 Beware : prevent crash calling manipulable_disable()
2078 before changing manipulated data structure
2080 manipulators
: CollectionProperty(
2081 type=archipack_manipulator
,
2082 # options={'SKIP_SAVE'},
2083 # options={'HIDDEN'},
2084 description
="store 3d points to draw gl manipulators"
2087 # TODO: make simple instance vars
2088 manipulable_refresh
: BoolProperty(
2090 options
={'SKIP_SAVE'},
2091 description
="Flag enable to rebuild manipulators when data model change"
2093 manipulate_mode
: BoolProperty(
2095 options
={'SKIP_SAVE'},
2096 description
="Flag manipulation state so we are able to toggle"
2098 select_mode
: BoolProperty(
2100 options
={'SKIP_SAVE'},
2101 description
="Flag select state so we are able to toggle"
2103 manipulable_selectable
: BoolProperty(
2105 options
={'SKIP_SAVE'},
2106 description
="Flag make manipulators selectable"
2110 manipulable_area
= None
2111 manipulable_start_point
= None
2112 manipulable_end_point
= None
2113 manipulable_draw_handler
= None
2115 def setup_manipulators(self
):
2117 Must implement manipulators creation
2118 TODO: call from update and manipulable_setup
2120 raise NotImplementedError
2122 def manipulable_draw_callback(self
, _self
, context
):
2123 self
.manipulable_area
.draw(context
)
2125 def manipulable_disable(self
, context
):
2127 disable gl draw handlers
2130 if self
.keymap
is None:
2131 self
.keymap
= Keymaps(context
)
2132 self
.manipulable_area
= GlCursorArea()
2133 self
.manipulable_start_point
= Vector((0, 0))
2134 self
.manipulable_end_point
= Vector((0, 0))
2136 o
= context
.active_object
2138 self
.manipulable_exit_selectmode(context
)
2139 remove_manipulable(o
.name
)
2140 self
.manip_stack
= add_manipulable(o
.name
, self
)
2142 self
.manipulate_mode
= False
2143 self
.select_mode
= False
2145 def manipulable_exit_selectmode(self
, context
):
2146 self
.manipulable_area
.disable()
2147 self
.select_mode
= False
2148 # remove select draw handler
2149 if self
.manipulable_draw_handler
is not None:
2150 bpy
.types
.SpaceView3D
.draw_handler_remove(
2151 self
.manipulable_draw_handler
,
2153 self
.manipulable_draw_handler
= None
2155 def manipulable_setup(self
, context
):
2157 TODO: Implement the setup part as per parent object basis
2159 self
.manipulable_disable(context
)
2160 o
= context
.active_object
2161 self
.setup_manipulators()
2162 for m
in self
.manipulators
:
2163 self
.manip_stack
.append(m
.setup(context
, o
, self
))
2165 def _manipulable_invoke(self
, context
):
2167 object_name
= context
.active_object
.name
2169 # store a reference to self for operators
2170 add_manipulable(object_name
, self
)
2172 # copy context so manipulator always use
2173 # invoke time context
2174 ctx
= context
.copy()
2176 # take care of context switching
2177 # when call from outside of 3d view
2178 if context
.space_data
is not None and context
.space_data
.type != 'VIEW_3D':
2179 for window
in bpy
.context
.window_manager
.windows
:
2180 screen
= window
.screen
2181 for area
in screen
.areas
:
2182 if area
.type == 'VIEW_3D':
2184 for region
in area
.regions
:
2185 if region
.type == 'WINDOW':
2186 ctx
['region'] = region
2189 bpy
.ops
.archipack
.manipulate(ctx
, 'INVOKE_DEFAULT', object_name
=object_name
)
2191 def manipulable_invoke(self
, context
):
2193 call this in operator invoke()
2195 if override don't forget to call:
2196 _manipulable_invoke(context)
2199 # print("manipulable_invoke self.manipulate_mode:%s" % (self.manipulate_mode))
2201 if self
.manipulate_mode
:
2202 self
.manipulable_disable(context
)
2205 # bpy.ops.archipack.disable_manipulate('INVOKE_DEFAULT')
2207 # self.manip_stack = []
2208 # kills other's manipulators
2209 # self.manipulate_mode = True
2210 self
.manipulable_setup(context
)
2211 self
.manipulate_mode
= True
2213 self
._manipulable
_invoke
(context
)
2217 def manipulable_modal(self
, context
, event
):
2219 call in operator modal()
2220 should not be overridden
2221 as it provide all needed
2222 functionality out of the box
2225 # setup again when manipulators type change
2226 if self
.manipulable_refresh
:
2227 # print("manipulable_refresh")
2228 self
.manipulable_refresh
= False
2229 self
.manipulable_setup(context
)
2230 self
.manipulate_mode
= True
2232 if context
.area
is None:
2233 self
.manipulable_disable(context
)
2236 context
.area
.tag_redraw()
2238 if self
.keymap
is None:
2239 self
.keymap
= Keymaps(context
)
2241 if self
.keymap
.check(event
, self
.keymap
.undo
):
2242 # user feedback on undo by disabling manipulators
2243 self
.manipulable_disable(context
)
2246 # clean up manipulator on delete
2247 if self
.keymap
.check(event
, self
.keymap
.delete
): # {'X'}:
2249 # for doors and windows, seek and destroy holes object if any
2250 # a dedicated delete method into those objects may be an option ?
2251 # A type check is required any way we choose
2253 # Time for a generic archipack's datablock getter / filter into utils
2255 # May also be implemented into nearly hidden "reference point"
2256 # to delete / duplicate / link duplicate / unlink of
2257 # a complete set of wall, doors and windows at once
2258 self
.manipulable_disable(context
)
2260 if bpy
.ops
.object.delete
.poll():
2261 bpy
.ops
.object.delete('INVOKE_DEFAULT', use_global
=False)
2266 # handle keyborad for select mode
2267 if self.select_mode:
2268 if event.type in {'A'} and event.value == 'RELEASE':
2269 return {'RUNNING_MODAL'}
2272 for manipulator
in self
.manip_stack
:
2273 # manipulator should return false on left mouse release
2274 # so proper release handler is called
2275 # and return true to call manipulate when required
2276 # print("manipulator:%s" % manipulator)
2277 if manipulator
is not None and manipulator
.modal(context
, event
):
2278 self
.manipulable_manipulate(context
, event
, manipulator
)
2279 return {'RUNNING_MODAL'}
2281 # print("Manipulable %s %s" % (event.type, event.value))
2283 # Manipulators are not active so check for selection
2284 if event
.type == 'LEFTMOUSE':
2286 # either we are starting select mode
2287 # user press on area not over maniuplator
2288 # Prevent 3 mouse emultation to select when alt pressed
2289 if self
.manipulable_selectable
and event
.value
== 'PRESS' and not event
.alt
:
2290 self
.select_mode
= True
2291 self
.manipulable_area
.enable()
2292 self
.manipulable_start_point
= Vector((event
.mouse_region_x
, event
.mouse_region_y
))
2293 self
.manipulable_area
.set_location(
2295 self
.manipulable_start_point
,
2296 self
.manipulable_start_point
)
2297 # add a select draw handler
2298 args
= (self
, context
)
2299 self
.manipulable_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(
2300 self
.manipulable_draw_callback
,
2305 # as this prevent click over ui
2306 # return {'RUNNING_MODAL'}
2308 elif event
.value
== 'RELEASE':
2309 if self
.select_mode
:
2312 self
.manipulable_exit_selectmode(context
)
2315 # return {'RUNNING_MODAL'}
2318 # allow manipulator action on release
2319 for manipulator
in self
.manip_stack
:
2320 if manipulator
is not None and manipulator
.selectable
:
2321 manipulator
.selected
= False
2322 self
.manipulable_release(context
)
2324 elif self
.select_mode
and event
.type == 'MOUSEMOVE' and event
.value
== 'PRESS':
2325 # update select area size
2326 self
.manipulable_end_point
= Vector((event
.mouse_region_x
, event
.mouse_region_y
))
2327 self
.manipulable_area
.set_location(
2329 self
.manipulable_start_point
,
2330 self
.manipulable_end_point
)
2333 for i
, manipulator
in enumerate(self
.manip_stack
):
2334 if manipulator
is not None and manipulator
.selectable
:
2335 manipulator
.deselect(self
.manipulable_area
)
2338 for i
, manipulator
in enumerate(self
.manip_stack
):
2339 if manipulator
is not None and manipulator
.selectable
:
2340 manipulator
.select(self
.manipulable_area
)
2341 # keep focus to prevent left select mouse to actually move object
2342 return {'RUNNING_MODAL'}
2344 # event.alt here to prevent 3 button mouse emulation exit while zooming
2345 if event
.type in {'RIGHTMOUSE', 'ESC'} and event
.value
== 'PRESS' and not event
.alt
:
2346 self
.manipulable_disable(context
)
2347 self
.manipulable_exit(context
)
2350 return {'PASS_THROUGH'}
2353 def manipulable_release(self
, context
):
2355 Override with action to do on mouse release
2360 def manipulable_exit(self
, context
):
2362 Override with action to do when modal exit
2366 def manipulable_manipulate(self
, context
, event
, manipulator
):
2368 Override with action to do when a handle is active (pressed and mousemove)
2374 def cleanup(dummy
=None):
2379 # Register default manipulators
2381 global manipulators_class_lookup
2382 manipulators_class_lookup
= {}
2384 register_manipulator('SIZE', SizeManipulator
)
2385 register_manipulator('SIZE_LOC', SizeLocationManipulator
)
2386 register_manipulator('ANGLE', AngleManipulator
)
2387 register_manipulator('DUMB_ANGLE', DumbAngleManipulator
)
2388 register_manipulator('ARC_ANGLE_RADIUS', ArcAngleRadiusManipulator
)
2389 register_manipulator('COUNTER', CounterManipulator
)
2390 register_manipulator('DUMB_SIZE', DumbSizeManipulator
)
2391 register_manipulator('DELTA_LOC', DeltaLocationManipulator
)
2392 register_manipulator('DUMB_STRING', DumbStringManipulator
)
2394 # snap aware size loc
2395 register_manipulator('SNAP_SIZE_LOC', SnapSizeLocationManipulator
)
2396 # register_manipulator('SNAP_POINT', SnapPointManipulator)
2397 # wall's line based object snap
2398 register_manipulator('WALL_SNAP', WallSnapManipulator
)
2399 bpy
.utils
.register_class(ARCHIPACK_OT_manipulate
)
2400 bpy
.utils
.register_class(ARCHIPACK_OT_disable_manipulate
)
2401 bpy
.utils
.register_class(archipack_manipulator
)
2402 bpy
.app
.handlers
.load_pre
.append(cleanup
)
2407 global manipulators_class_lookup
2410 manipulators_class_lookup
.clear()
2411 del manipulators_class_lookup
2412 bpy
.utils
.unregister_class(ARCHIPACK_OT_manipulate
)
2413 bpy
.utils
.unregister_class(ARCHIPACK_OT_disable_manipulate
)
2414 bpy
.utils
.unregister_class(archipack_manipulator
)
2415 bpy
.app
.handlers
.load_pre
.remove(cleanup
)