forked from hlorus/CAD_Sketcher
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgizmos.py
913 lines (724 loc) · 28.5 KB
/
gizmos.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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
import bpy
import bgl
import gpu
import blf
import math
from bpy.types import Gizmo, GizmoGroup
from enum import Enum, auto
from mathutils import Vector, Matrix
from mathutils.geometry import intersect_point_line
from . import functions, global_data, icon_manager
from .model.types import (
SlvsDistance,
SlvsAngle,
SlvsDiameter,
GenericConstraint,
DimensionalConstraint,
)
from .declarations import GizmoGroups, Gizmos, Operators
from .draw_handler import ensure_selection_texture
from .utilities.constants import HALF_TURN, QUARTER_TURN
# NOTE: idealy gizmo would expose active element as a property and
# operators would access hovered element from there
class VIEW3D_GT_slvs_preselection(Gizmo):
bl_idname = Gizmos.Preselection
__slots__ = ()
def draw(self, context):
pass
def test_select(self, context, location):
# reset gizmo highlight
if global_data.highlight_constraint:
global_data.highlight_constraint = None
context.area.tag_redraw()
if global_data.highlight_entities:
global_data.highlight_entities.clear()
context.area.tag_redraw()
# ensure selection texture is up to date
# TODO: avoid dependency on operators module?
ensure_selection_texture(context)
# sample selection texture and mark hovered entity
mouse_x, mouse_y = location
buffer = bgl.Buffer(bgl.GL_FLOAT, 4)
offscreen = global_data.offscreen
if not offscreen:
return -1
with offscreen.bind():
bgl.glPixelStorei(bgl.GL_UNPACK_ALIGNMENT, 1)
bgl.glReadPixels(mouse_x, mouse_y, 1, 1, bgl.GL_RGBA, bgl.GL_FLOAT, buffer)
if buffer.to_list()[3] > 0:
index = functions.rgb_to_index(*buffer.to_list()[:-1])
if index != global_data.hover:
global_data.hover = index
context.area.tag_redraw()
elif global_data.hover != -1:
context.area.tag_redraw()
global_data.hover = -1
return -1
def context_mode_check(context, widget_group):
tools = context.workspace.tools
mode = context.mode
for tool in tools:
if (tool.widget == widget_group) and (tool.mode == mode):
break
else:
context.window_manager.gizmo_group_type_unlink_delayed(widget_group)
return False
return True
GIZMO_OFFSET = Vector((10.0, 10.0))
GIZMO_GENERIC_SIZE = 5
FONT_ID = 0
class Color(Enum):
Default = auto()
Failed = auto()
Reference = auto()
Text = auto()
def get_color(color_type: Color, highlit: bool):
c_theme = functions.get_prefs().theme_settings.constraint
theme_match = {
# (type, highlit): color
(Color.Default, False): c_theme.default,
(Color.Default, True): c_theme.highlight,
(Color.Failed, False): c_theme.failed,
(Color.Failed, True): c_theme.failed_highlight,
(Color.Reference, False): c_theme.reference,
(Color.Reference, True): c_theme.reference_highlight,
(Color.Text, False): c_theme.text,
(Color.Text, True): c_theme.text_highlight,
}
return theme_match[(color_type, highlit)]
def get_constraint_color_type(constraint: GenericConstraint):
if constraint.failed:
return Color.Failed
elif isinstance(constraint, DimensionalConstraint) and constraint.is_reference:
return Color.Reference
else:
return Color.Default
class ConstraintGizmo:
def _get_constraint(self, context):
return context.scene.sketcher.constraints.get_from_type_index(
self.type, self.index
)
def get_constraint_color(self, constraint: GenericConstraint):
is_highlight = (
constraint == global_data.highlight_constraint or self.is_highlight
)
col = get_constraint_color_type(constraint)
return get_color(col, is_highlight)
def _set_colors(self, context, constraint: GenericConstraint):
"""Overwrite default color when gizmo is highlighted"""
color_setting = self.get_constraint_color(constraint)
self.color = color_setting[:3]
return color_setting
class VIEW3D_GT_slvs_constraint(ConstraintGizmo, Gizmo):
bl_idname = Gizmos.Constraint
__slots__ = (
"custom_shape",
"type",
"index",
"entity_index",
"offset",
)
def _update_matrix_basis(self, context, constr):
pos = None
if hasattr(self, "entity_index"):
entity = context.scene.sketcher.entities.get(self.entity_index)
if not entity or not hasattr(entity, "placement"):
return
pos = functions.get_2d_coords(context, entity.placement())
if not pos:
return
pos += GIZMO_OFFSET + self.offset
if pos:
mat = Matrix.Translation(Vector((pos[0], pos[1], 0.0)))
self.matrix_basis = mat
def test_select(self, context, location):
if not context.scene.sketcher.selectable_constraints:
return -1
location = Vector(location).to_3d()
location -= self.matrix_basis.translation
location *= 1.0 / self.scale_basis
import math
if math.pow(location.length, 2) < 1.0:
return 0
return -1
def draw(self, context):
constraint = self._get_constraint(context)
if not constraint.visible:
return
col = self._set_colors(context, constraint)
self._update_matrix_basis(context, constraint)
with gpu.matrix.push_pop():
gpu.matrix.load_matrix(self.matrix_basis)
ui_scale = context.preferences.system.ui_scale
scale = self.scale_basis * ui_scale
gpu.matrix.scale(Vector((scale, scale)))
icon_manager.draw(self.type, col)
def setup(self):
pass
def _get_formatted_value(context, constr):
from . import units
unit = constr.rna_type.properties["value"].unit
value = constr.value
if unit == "LENGTH":
if constr.type == "DIAMETER" and constr.setting:
s = "R" + units.format_distance(value)
else:
s = units.format_distance(value)
return s
elif unit == "ROTATION":
return units.format_angle(value)
return ""
class VIEW3D_GT_slvs_constraint_value(ConstraintGizmo, Gizmo):
"""Display the value of a dimensional constraint"""
bl_idname = Gizmos.ConstraintValue
__slots__ = ("type", "index", "width", "height")
def test_select(self, context, location):
if not context.scene.sketcher.selectable_constraints:
return -1
coords = Vector(location) - self.matrix_basis.translation.to_2d()
width, height = self.width, self.height
if -width / 2 < coords.x < width / 2 and -height / 2 < coords.y < height / 2:
return 0
return -1
def draw(self, context):
constr = self._get_constraint(context)
if not constr.visible or not hasattr(constr, "value_placement"):
return
color = get_color(Color.Text, self.is_highlight)
text = _get_formatted_value(context, constr)
dpi = context.preferences.system.dpi
text_size = functions.get_prefs().text_size
blf.color(FONT_ID, *color)
blf.size(FONT_ID, text_size, dpi)
self.width, self.height = blf.dimensions(FONT_ID, text)
margin = text_size / 4
pos = constr.value_placement(context)
if not pos:
return
self.matrix_basis = Matrix.Translation(
pos.to_3d()
) # Update Matrix for selection
blf.position(FONT_ID, pos[0] - self.width / 2, pos[1] + margin, 0)
blf.draw(FONT_ID, text)
def setup(self):
self.width = 0
self.height = 0
class ConstraintGizmoGeneric(ConstraintGizmo):
def _update_matrix_basis(self, constr):
self.matrix_basis = constr.matrix_basis()
def setup(self):
pass
def draw(self, context):
constr = self._get_constraint(context)
if not constr.visible:
return
self._set_colors(context, constr)
self._update_matrix_basis(constr)
self._create_shape(context, constr)
self.draw_custom_shape(self.custom_shape)
def draw_select(self, context, select_id):
if not context.scene.sketcher.selectable_constraints:
return
constr = self._get_constraint(context)
if not constr.visible:
return
self._create_shape(context, constr, select=True)
self.draw_custom_shape(self.custom_shape, select_id=select_id)
# NOTE: Idealy the geometry batch wouldn't be recreated every redraw,
# however the geom changes with the distance value, maybe at least track changes for that value
# if not hasattr(self, "custom_shape"):
def draw_arrow_shape(target, shoulder, width, is_3d=False):
v = shoulder - target
mat = Matrix.Rotation(QUARTER_TURN, (3 if is_3d else 2), "Z")
v.rotate(mat)
v.length = abs(width / 2)
return (
((shoulder + v)),
target,
target,
((shoulder - v)),
((shoulder - v)),
((shoulder + v)),
)
def get_overshoot(scale, dir):
if dir == 0:
return 0
# use factor of 0.005 for one-half arrowhead
overshoot = scale * 0.005 * functions.get_prefs().arrow_scale
return -math.copysign(overshoot, dir)
def get_arrow_size(dist, scale):
size = scale * 0.01 * functions.get_prefs().arrow_scale
size = min(size, abs(dist * 0.67))
size = math.copysign(size, dist)
return size, size / 2
class VIEW3D_GT_slvs_distance(Gizmo, ConstraintGizmoGeneric):
bl_idname = Gizmos.Distance
type = SlvsDistance.type
bl_target_properties = ({"id": "offset", "type": "FLOAT", "array_length": 1},)
__slots__ = (
"custom_shape",
"index",
)
def _get_helplines(self, context, constr, scale_1, scale_2):
ui_scale = context.preferences.system.ui_scale
dist = constr.value / 2 / ui_scale
offset = self.target_get_value("offset")
entity1, entity2 = constr.entity1, constr.entity2
if entity1.is_line():
entity1, entity2 = entity1.p1, entity1.p2
# Get constraints points in local space and adjust helplines based on their position
mat_inv = constr.matrix_basis().inverted()
def get_local(point):
return (mat_inv @ point.to_3d()) / ui_scale
# Store the two endpoints of the helplines in local space
points_local = []
# Add endpoint for entity1 helpline
if entity1.is_curve():
centerpoint = entity1.ct.co
if entity2.is_point():
targetpoint = entity2.co
elif entity2.is_line():
targetpoint, _ = intersect_point_line(
centerpoint, entity2.p1.co, entity2.p2.co
)
else:
# TODO: Handle the case for SlvsWorkplane
pass
targetvec = targetpoint - centerpoint
points_local.append(
get_local(centerpoint + entity1.radius * targetvec / targetvec.length)
)
else:
points_local.append(get_local(entity1.location))
# Add endpoint for entity2 helpline
if entity2.is_point():
points_local.append(get_local(entity2.location))
elif entity2.is_line():
line_points = (
get_local(entity2.p1.location),
get_local(entity2.p2.location),
)
line_points_side = [pos.y - offset > 0 for pos in line_points]
x = math.copysign(dist, line_points[0].x)
y = offset
if line_points_side[0] != line_points_side[1]:
# Distance line is between line points
y = offset
else:
# Get the closest point
points_delta = [abs(p.y - offset) for p in line_points]
i = int(points_delta[0] > points_delta[1])
y = line_points[i].y
points_local.append(Vector((x, y, 0.0)))
# Pick the points based on their x location
if points_local[0].x > points_local[1].x:
point_right, point_left = points_local
else:
point_right, point_left = reversed(points_local)
overshoot_1 = offset + get_overshoot(scale_1, point_left.y - offset)
overshoot_2 = offset + get_overshoot(scale_2, point_right.y - offset)
return (
(-dist, overshoot_1, 0.0),
(-dist, point_left.y, 0.0),
(dist, overshoot_2, 0.0),
(dist, point_right.y, 0.0),
)
def _create_shape(self, context, constr, select=False):
rv3d = context.region_data
ui_scale = context.preferences.system.ui_scale
half_dist = constr.value / 2 / ui_scale
offset = self.target_get_value("offset")
outset = constr.draw_outset
p1 = Vector((-half_dist, offset, 0.0))
p2 = Vector((half_dist, offset, 0.0))
if not constr.text_inside(ui_scale):
p1, p2 = p2, p1
p1_global, p2_global = [self.matrix_world @ p for p in (p1, p2)]
scale_1, scale_2 = [
functions.get_scale_from_pos(p, rv3d) for p in (p1_global, p2_global)
]
arrow_1 = get_arrow_size(half_dist, scale_1)
arrow_2 = get_arrow_size(half_dist, scale_2)
if constr.text_inside(ui_scale):
coords = (
*draw_arrow_shape(
p1, p1 + Vector((arrow_1[0], 0, 0)), arrow_1[1], is_3d=True
),
p1,
p2,
*draw_arrow_shape(
p2, p2 - Vector((arrow_2[0], 0, 0)), arrow_2[1], is_3d=True
),
*(
self._get_helplines(context, constr, scale_1, scale_2)
if not select
else ()
),
)
else: # the same thing, but with a little jitter to the outside
coords = (
*draw_arrow_shape(
p1, p1 + Vector((arrow_1[0], 0, 0)), arrow_1[1], is_3d=True
),
p1,
Vector(
(outset, offset, 0)
), # jitter back and forth to extend leader line for text_outside case
p1, # but it is unnecessary work for text_inside case
p2,
*draw_arrow_shape(
p2, p2 - Vector((arrow_2[0], 0, 0)), arrow_2[1], is_3d=True
),
*(
self._get_helplines(context, constr, scale_1, scale_2)
if not select
else ()
),
)
self.custom_shape = self.new_custom_shape("LINES", coords)
class VIEW3D_GT_slvs_angle(Gizmo, ConstraintGizmoGeneric):
bl_idname = Gizmos.Angle
type = SlvsAngle.type
bl_target_properties = ({"id": "offset", "type": "FLOAT", "array_length": 1},)
__slots__ = (
"custom_shape",
"index",
)
def _get_helplines(self, context, constr, scale_1, scale_2):
angle = abs(constr.value)
radius = self.target_get_value("offset")
overshoot_1 = get_overshoot(scale_1, radius)
overshoot_2 = get_overshoot(scale_2, radius)
return (
(0.0, 0.0),
functions.pol2cart(radius - overshoot_1, angle / 2),
(0.0, 0.0),
functions.pol2cart(radius - overshoot_2, -angle / 2),
)
def _create_shape(self, context, constr, select=False):
def get_arrow_angle():
# The arrowheads are placed on an arc spanning between the
# witness lines, and we want them to point "along" this arc.
# So we rotate the arrowhead by a quarter-turn plus (or minus)
# half the amount the arc segment underneath it rotates.
segment = length / abs(radius)
rotation = (
(QUARTER_TURN + segment / 2)
if constr.text_inside()
else (QUARTER_TURN - segment / 2)
)
return rotation
rv3d = context.region_data
# note: radius is signed value, but
# angle, length, lengths[], widths[] are all absolute values
radius = self.target_get_value("offset")
angle = abs(constr.value)
half_angle = angle / 2
p1 = functions.pol2cart(radius, -half_angle)
p2 = functions.pol2cart(radius, half_angle)
scales = []
lengths, widths = [], [] # Length is limited to no more than 1/3 the span
for p in (p1, p2):
scale = functions.get_scale_from_pos(self.matrix_world @ p.to_3d(), rv3d)
scales.append(scale)
length = min(
abs(get_arrow_size(radius, scale)[0]),
abs(radius * (angle / 3)),
)
lengths.append(length)
widths.append(length * 0.4)
arrow_angle = get_arrow_angle()
p1_s = p1.copy()
p1_s.rotate(Matrix.Rotation(arrow_angle, 2, "Z"))
p1_s.length = lengths[0]
p2_s = p2.copy()
p2_s.rotate(Matrix.Rotation(-arrow_angle, 2, "Z"))
p2_s.length = lengths[1]
if constr.text_inside():
coords = (
*draw_arrow_shape(p1, p1 + p1_s, widths[0]),
*functions.coords_arc_2d(
0, 0, radius, 32, angle=angle, offset=-half_angle, type="LINES"
),
*draw_arrow_shape(p2, p2 + p2_s, widths[1]),
*(self._get_helplines(context, constr, *scales) if not select else ()),
)
else:
leader_end = (
constr.draw_outset
) # signed angle, measured from the Constrained Angle's bisector
leader_start = math.copysign(half_angle, -leader_end)
leader_length = leader_end - leader_start
coords = (
*draw_arrow_shape(p1, p1 - p1_s, widths[0]),
*functions.coords_arc_2d(
0,
0,
radius,
16,
angle=leader_length,
offset=leader_start,
type="LINES",
),
*draw_arrow_shape(p2, p2 - p2_s, widths[1]),
*(self._get_helplines(context, constr, *scales) if not select else ()),
)
self.custom_shape = self.new_custom_shape("LINES", coords)
class VIEW3D_GT_slvs_diameter(Gizmo, ConstraintGizmoGeneric):
bl_idname = Gizmos.Diameter
type = SlvsDiameter.type
bl_target_properties = ({"id": "offset", "type": "FLOAT", "array_length": 1},)
__slots__ = (
"custom_shape",
"index",
)
def _create_shape(self, context, constr, select=False):
ui_scale = context.preferences.system.ui_scale
angle = constr.leader_angle
offset = constr.draw_offset / ui_scale
dist = constr.radius / ui_scale
rv3d = context.region_data
p1 = functions.pol2cart(-dist, angle)
p2 = functions.pol2cart(dist, angle)
p1_global, p2_global = [self.matrix_world @ p.to_3d() for p in (p1, p2)]
scale_1, scale_2 = [
functions.get_scale_from_pos(p, rv3d) for p in (p1_global, p2_global)
]
arrow_1 = get_arrow_size(dist, scale_1)
arrow_2 = get_arrow_size(dist, scale_2)
if constr.setting:
# RADIUS_MODE:
# drawn inside and outside as a single segment
if constr.text_inside():
coords = (
*draw_arrow_shape(
p2, functions.pol2cart(dist - arrow_2[0], angle), arrow_2[1]
),
p2,
(0, 0),
)
else:
coords = (
*draw_arrow_shape(
p2, functions.pol2cart(arrow_2[0] + dist, angle), arrow_2[1]
),
p2,
functions.pol2cart(offset, angle),
)
else:
# DIAMETER_MODE:
# drawn inside as a single segment
# drawn outside as a 2-segment gizmo
if constr.text_inside():
coords = (
*draw_arrow_shape(
p1, functions.pol2cart(arrow_2[0] - dist, angle), arrow_2[1]
),
p1,
p2,
*draw_arrow_shape(
p2, functions.pol2cart(dist - arrow_2[0], angle), arrow_2[1]
),
)
else:
coords = (
*draw_arrow_shape(
p2, functions.pol2cart(arrow_1[0] + dist, angle), arrow_1[1]
),
p2,
functions.pol2cart(offset, angle),
functions.pol2cart(
dist + (3 * arrow_2[0]), angle + HALF_TURN
), # limit length to 3 arrowheads
p1,
*draw_arrow_shape(
p1,
functions.pol2cart(dist + arrow_2[0], angle + HALF_TURN),
arrow_2[1],
),
)
self.custom_shape = self.new_custom_shape("LINES", coords)
class VIEW3D_GGT_slvs_preselection(GizmoGroup):
bl_idname = GizmoGroups.Preselection
bl_label = "preselection ggt"
bl_space_type = "VIEW_3D"
bl_region_type = "WINDOW"
bl_options = {"3D"}
# NOTE: it would be great to expose the hovered entity as a gizmogroup prop
# rather than using global variables...
@classmethod
def poll(cls, context):
return context_mode_check(context, cls.bl_idname)
def setup(self, context):
self.gizmo = self.gizmos.new(VIEW3D_GT_slvs_preselection.bl_idname)
specific_constraint_types = ("angle", "diameter", "distance")
def generic_constraints(context):
"""Iterate through constraints which don't have a specific gizmo"""
constrs = context.scene.sketcher.constraints
for prop_list in constrs.rna_type.properties:
name = prop_list.identifier
if name in ("name", "rna_type", *specific_constraint_types):
continue
list = getattr(constrs, name)
for entity in list:
yield entity
# TODO: This could already Skip entities and constraints that are not active
# TODO: only store indices instead of actual objects
def constraints_mapping(context):
# Get a constraints per entity mapping
entities = []
constraints = []
for c in generic_constraints(context):
for e in c.entities():
if e not in entities:
entities.append(e)
# i = len(entities)
i = entities.index(e)
if i >= len(constraints):
constraints.append([])
constrs = constraints[i]
if c not in constrs:
constrs.append(c)
assert len(entities) == len(constraints)
return entities, constraints
def set_gizmo_colors(gz, constraint):
theme = functions.get_prefs().theme_settings
color_type = get_constraint_color_type(constraint)
color = get_color(color_type, highlit=False)
color_highlight = get_color(color_type, highlit=True)
gz.color = color[0:-1]
gz.alpha = color[-1]
gz.color_highlight = color_highlight[0:-1]
gz.alpha_highlight = color_highlight[-1]
class ConstraintGenericGGT:
bl_space_type = "VIEW_3D"
bl_region_type = "WINDOW"
bl_options = {"PERSISTENT", "SCALE", "3D"}
def _list_from_type(self, context):
return context.scene.sketcher.constraints.get_list(self.type)
def setup(self, context):
for c in self._list_from_type(context):
if not c.is_active(context.scene.sketcher.active_sketch):
continue
gz = self.gizmos.new(self.gizmo_type)
gz.index = context.scene.sketcher.constraints.get_index(c)
set_gizmo_colors(gz, c)
gz.use_draw_modal = True
gz.target_set_prop("offset", c, "draw_offset")
props = gz.target_set_operator(Operators.TweakConstraintValuePos)
props.type = self.type
props.index = gz.index
def refresh(self, context):
# recreate gizmos here!
self.gizmos.clear()
self.setup(context)
@classmethod
def poll(cls, context):
# TODO: Allow to hide
return True
class VIEW3D_GGT_slvs_distance(GizmoGroup, ConstraintGenericGGT):
bl_idname = GizmoGroups.Distance
bl_label = "Distance Constraint Gizmo Group"
type = SlvsDistance.type
gizmo_type = VIEW3D_GT_slvs_distance.bl_idname
class VIEW3D_GGT_slvs_angle(GizmoGroup, ConstraintGenericGGT):
bl_idname = GizmoGroups.Angle
bl_label = "Angle Constraint Gizmo Group"
type = SlvsAngle.type
gizmo_type = VIEW3D_GT_slvs_angle.bl_idname
class VIEW3D_GGT_slvs_diameter(GizmoGroup, ConstraintGenericGGT):
bl_idname = GizmoGroups.Diameter
bl_label = "Diameter Gizmo Group"
type = SlvsDiameter.type
gizmo_type = VIEW3D_GT_slvs_diameter.bl_idname
def iter_dimenional_constraints(context):
ssc = context.scene.sketcher.constraints
collections = [ssc.distance, ssc.diameter, ssc.angle]
for coll in collections:
for c in coll:
yield c
class VIEW3D_GGT_slvs_constraint(GizmoGroup):
bl_idname = GizmoGroups.Constraint
bl_label = "Constraint Gizmo Group"
bl_space_type = "VIEW_3D"
bl_region_type = "WINDOW"
bl_options = {"PERSISTENT", "SCALE"}
@classmethod
def poll(cls, context):
# TODO: Allow to hide
return True
def setup(self, context):
theme = functions.get_prefs().theme_settings
mapping = {}
for c in context.scene.sketcher.constraints.all:
if not hasattr(c, "placements"):
continue
for e in c.placements():
if not mapping.get(e):
mapping[e] = [
c,
]
else:
mapping[e].append(c)
for e, constrs in mapping.items():
if not hasattr(e, "placement"):
continue
if not e.is_visible(context):
continue
active_sketch = context.scene.sketcher.active_sketch
for i, c in enumerate(constrs):
if not c.is_active(active_sketch):
continue
gz = self.gizmos.new(VIEW3D_GT_slvs_constraint.bl_idname)
gz.type = c.type
gz.index = context.scene.sketcher.constraints.get_index(c)
pos = functions.get_2d_coords(context, e.placement())
gz.entity_index = e.slvs_index
ui_scale = context.preferences.system.ui_scale
scale = functions.get_prefs().gizmo_scale * ui_scale
offset_base = Vector((scale * 1.0, 0.0))
offset = offset_base * i * ui_scale
gz.offset = offset
gz.scale_basis = scale
set_gizmo_colors(gz, c)
gz.use_draw_modal = True
op = Operators.ContextMenu
props = gz.target_set_operator(op)
props.type = c.type
props.index = gz.index
props.highlight_hover = True
props.highlight_members = True
# Add value gizmos for dimensional constraints
for c in iter_dimenional_constraints(context):
if not c.is_active(context.scene.sketcher.active_sketch):
continue
gz = self.gizmos.new(VIEW3D_GT_slvs_constraint_value.bl_idname)
index = context.scene.sketcher.constraints.get_index(c)
gz.type = c.type
gz.index = index
props = gz.target_set_operator(Operators.TweakConstraintValuePos)
props.type = c.type
props.index = index
def refresh(self, context):
# recreate gizmos here!
self.gizmos.clear()
self.setup(context)
classes = (
VIEW3D_GT_slvs_preselection,
VIEW3D_GT_slvs_constraint,
VIEW3D_GT_slvs_distance,
VIEW3D_GT_slvs_angle,
VIEW3D_GT_slvs_diameter,
VIEW3D_GT_slvs_constraint_value,
VIEW3D_GGT_slvs_preselection,
VIEW3D_GGT_slvs_constraint,
VIEW3D_GGT_slvs_distance,
VIEW3D_GGT_slvs_angle,
VIEW3D_GGT_slvs_diameter,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)