1 # SPDX-License-Identifier: GPL-2.0-or-later
9 from gpu_extras
.batch
import batch_for_shader
17 from bpy_extras
.view3d_utils
import (
18 region_2d_to_location_3d
,
19 location_3d_to_region_2d
,
22 from .carver_utils
import (
29 from mathutils
import (
36 def get_text_info(self
, context
, help_txt
):
37 """ Return the dimensions of each part of the text """
39 #Extract the longest first option in sublist
40 max_option
= max(list(blf
.dimensions(0, row
[0])[0] for row
in help_txt
))
42 #Extract the longest key in sublist
43 max_key
= max(list(blf
.dimensions(0, row
[1])[0] for row
in help_txt
))
45 #Space between option and key with a comma separator (" : ")
46 comma
= blf
.dimensions(0, "_:_")[0]
48 #Get a default height for all the letters
49 line_height
= (blf
.dimensions(0, "gM")[1] * 1.45)
51 #Get the total height of the text
54 bloc_height
+= line_height
56 return(help_txt
, bloc_height
, max_option
, max_key
, comma
)
58 def draw_string(self
, color1
, color2
, left
, bottom
, text
, max_option
, divide
= 1):
59 """ Draw the text like 'option : key' or just 'option' """
62 ui_scale
= bpy
.context
.preferences
.system
.ui_scale
64 blf
.enable(font_id
,blf
.SHADOW
)
65 blf
.shadow(font_id
, 0, 0.0, 0.0, 0.0, 1.0)
66 blf
.shadow_offset(font_id
,2,-2)
67 line_height
= (blf
.dimensions(font_id
, "gM")[1] * 1.45)
70 # Test if the text is a list formatted like : ('option', 'key')
71 if isinstance(text
,list):
73 spacer_width
= blf
.dimensions(font_id
, spacer_text
)[0]
75 blf
.position(font_id
, (left
), (bottom
+ y_offset
), 0)
76 blf
.color(font_id
, *color1
)
77 blf
.draw(font_id
, string
[0])
78 blf
.position(font_id
, (left
+ max_option
), (bottom
+ y_offset
), 0)
79 blf
.draw(font_id
, spacer_text
)
80 blf
.color(font_id
, *color2
)
81 blf
.position(font_id
, (left
+ max_option
+ spacer_width
), (bottom
+ y_offset
), 0)
82 blf
.draw(font_id
, string
[1])
83 y_offset
+= line_height
85 # The text is formatted like : ('option')
86 blf
.position(font_id
, left
, (bottom
+ y_offset
), 0)
87 blf
.color(font_id
, *color1
)
88 blf
.draw(font_id
, text
)
89 y_offset
+= line_height
91 blf
.disable(font_id
,blf
.SHADOW
)
93 # Opengl draw on screen
94 def draw_callback_px(self
, context
):
96 region
= context
.region
97 UIColor
= (0.992, 0.5518, 0.0, 1.0)
103 self
.carver_prefs
= context
.preferences
.addons
[__package__
].preferences
106 color1
= (1.0, 1.0, 1.0, 1.0)
109 #The mouse is outside the active region
110 if not self
.in_view_3d
:
111 color1
= color2
= (1.0, 0.2, 0.1, 1.0)
114 PrimitiveType
= "Rectangle"
115 if self
.CutType
== CIRCLE
:
116 PrimitiveType
= "Circle"
117 if self
.CutType
== LINE
:
118 PrimitiveType
= "Line"
121 overlap
= context
.preferences
.system
.use_region_overlap
125 for region
in context
.area
.regions
:
126 if region
.type == 'TOOLS':
127 t_panel_width
= region
.width
130 region_width
= int(region
.width
/ 2.0)
134 # Draw the center command from bottom to top
136 # Get the size of the text
137 text_size
= 18 if region
.width
>= 850 else 12
138 ui_scale
= bpy
.context
.preferences
.system
.ui_scale
139 blf
.size(0, round(text_size
* ui_scale
), 72)
142 if (self
.ObjectMode
is False) and (self
.ProfileMode
is False):
145 TypeStr
= "Cursor Depth [" + self
.carver_prefs
.Key_Depth
+ "]"
146 BoolStr
= "(ON)" if self
.snapCursor
else "(OFF)"
147 help_txt
= [[TypeStr
, BoolStr
]]
149 # Close poygonal shape
150 if self
.CreateMode
and self
.CutType
== LINE
:
151 TypeStr
= "Close [" + self
.carver_prefs
.Key_Close
+ "]"
152 BoolStr
= "(ON)" if self
.Closed
else "(OFF)"
153 help_txt
+= [[TypeStr
, BoolStr
]]
155 if self
.CreateMode
is False:
157 TypeStr
= "Apply Operations [" + self
.carver_prefs
.Key_Apply
+ "]"
158 BoolStr
= "(OFF)" if self
.dont_apply_boolean
else "(ON)"
159 help_txt
+= [[TypeStr
, BoolStr
]]
161 #Auto update for bevel
162 TypeStr
= "Bevel Update [" + self
.carver_prefs
.Key_Update
+ "]"
163 BoolStr
= "(ON)" if self
.Auto_BevelUpdate
else "(OFF)"
164 help_txt
+= [[TypeStr
, BoolStr
]]
166 # Circle subdivisions
167 if self
.CutType
== CIRCLE
:
168 TypeStr
= "Subdivisions [" + self
.carver_prefs
.Key_Subrem
+ "][" + self
.carver_prefs
.Key_Subadd
+ "]"
169 BoolStr
= str((int(360 / self
.stepAngle
[self
.step
])))
170 help_txt
+= [[TypeStr
, BoolStr
]]
173 help_txt
+= [["Type [Space]", PrimitiveType
]]
175 help_txt
+= [["Cut Type [Space]", PrimitiveType
]]
179 TypeStr
= "Instantiate [" + self
.carver_prefs
.Key_Instant
+ "]"
180 BoolStr
= "(ON)" if self
.Instantiate
else "(OFF)"
181 help_txt
= [[TypeStr
, BoolStr
]]
185 TypeStr
= "Random Rotation [" + self
.carver_prefs
.Key_Randrot
+ "]"
186 BoolStr
= "(ON)" if self
.RandomRotation
else "(OFF)"
187 help_txt
+= [[TypeStr
, BoolStr
]]
190 if self
.BrushSolidify
:
191 TypeStr
= "Thickness [" + self
.carver_prefs
.Key_Depth
+ "]"
193 BoolStr
= str(round(self
.ProfileBrush
.modifiers
["CT_SOLIDIFY"].thickness
, 2))
195 BoolStr
= str(round(self
.ObjectBrush
.modifiers
["CT_SOLIDIFY"].thickness
, 2))
196 help_txt
+= [[TypeStr
, BoolStr
]]
199 if (self
.ObjectMode
):
200 TypeStr
= "Carve Depth [" + self
.carver_prefs
.Key_Depth
+ "]"
201 BoolStr
= str(round(self
.ObjectBrush
.data
.vertices
[0].co
.z
, 2))
202 help_txt
+= [[TypeStr
, BoolStr
]]
204 TypeStr
= "Brush Depth [" + self
.carver_prefs
.Key_BrushDepth
+ "]"
205 BoolStr
= str(round(self
.BrushDepthOffset
, 2))
206 help_txt
+= [[TypeStr
, BoolStr
]]
208 help_txt
, bloc_height
, max_option
, max_key
, comma
= get_text_info(self
, context
, help_txt
)
209 xCmd
= region_width
- (max_option
+ max_key
+ comma
) / 2
210 draw_string(self
, color1
, color2
, xCmd
, y_txt
, help_txt
, max_option
, divide
= 2)
214 LineWidth
= (max_option
+ max_key
+ comma
) / 2
215 if region
.width
>= 850:
218 LineWidth
= (max_option
+ max_key
+ comma
)
219 coords
= [(int(region_width
- LineWidth
/2), y_txt
+ bloc_height
+ 8), \
220 (int(region_width
+ LineWidth
/2), y_txt
+ bloc_height
+ 8)]
221 draw_shader(self
, UIColor
, 1, 'LINES', coords
, self
.carver_prefs
.LineWidth
)
224 if self
.CreateMode
and ((self
.ObjectMode
is False) and (self
.ProfileMode
is False)):
225 BooleanMode
= "Create"
227 if self
.ObjectMode
or self
.ProfileMode
:
228 BooleanType
= "Difference) [T]" if self
.BoolOps
== self
.difference
else "Union) [T]"
230 "Object Brush (" + BooleanType
if self
.ObjectMode
else "Profil Brush (" + BooleanType
233 "Difference" if (self
.shift
is False) and (self
.ForceRebool
is False) else "Rebool"
235 # Display boolean mode
236 text_size
= 40 if region
.width
>= 850 else 20
237 blf
.size(0, round(text_size
* ui_scale
), 72)
239 draw_string(self
, color2
, color2
, region_width
- (blf
.dimensions(0, BooleanMode
)[0]) / 2, \
240 y_txt
+ bloc_height
+ 16, BooleanMode
, 0, divide
= 2)
242 if region
.width
>= 850:
244 if self
.AskHelp
is False:
246 blf
.size(0, round(13 * ui_scale
), 72)
247 help_txt
= "[" + self
.carver_prefs
.Key_Help
+ "] for help"
248 txt_width
= blf
.dimensions(0, help_txt
)[0]
249 txt_height
= (blf
.dimensions(0, "gM")[1] * 1.45)
251 # Draw a rectangle and put the text "H for Help"
254 rect_vertices
= [(xrect
- 5, yrect
- 5), (xrect
+ txt_width
+ 5, yrect
- 5), \
255 (xrect
+ txt_width
+ 5, yrect
+ txt_height
+ 5), (xrect
- 5, yrect
+ txt_height
+ 5)]
256 draw_shader(self
, (0.0, 0.0, 0.0), 0.3, 'TRI_FAN', rect_vertices
, self
.carver_prefs
.LineWidth
)
257 draw_string(self
, color1
, color2
, xrect
, yrect
, help_txt
, 0)
261 xHelp
= 30 + t_panel_width
264 if self
.ObjectMode
or self
.ProfileMode
:
266 help_txt
= [["Object Mode", self
.carver_prefs
.Key_Brush
]]
268 help_txt
= [["Cut Mode", self
.carver_prefs
.Key_Brush
]]
272 ["Profil Brush", self
.carver_prefs
.Key_Brush
],\
273 ["Move Cursor", "Ctrl + LMB"]
276 if (self
.ObjectMode
is False) and (self
.ProfileMode
is False):
277 if self
.CreateMode
is False:
279 ["Create geometry", self
.carver_prefs
.Key_Create
],\
283 ["Cut", self
.carver_prefs
.Key_Create
],\
285 if self
.CutMode
== RECTANGLE
:
287 ["Dimension", "MouseMove"],\
288 ["Move all", "Alt"],\
289 ["Validate", "LMB"],\
293 elif self
.CutMode
== CIRCLE
:
295 ["Rotation and Radius", "MouseMove"],\
296 ["Move all", "Alt"],\
297 ["Subdivision", self
.carver_prefs
.Key_Subrem
+ " " + self
.carver_prefs
.Key_Subadd
],\
298 ["Incremental rotation", "Ctrl"],\
302 elif self
.CutMode
== LINE
:
304 ["Dimension", "MouseMove"],\
305 ["Move all", "Alt"],\
306 ["Validate", "Space"],\
307 ["Rebool", "Shift"],\
309 ["Scale Snap", "WheelMouse"],\
314 ["Difference", "Space"],\
315 ["Rebool", "Shift + Space"],\
316 ["Duplicate", "Alt + Space"],\
317 ["Scale", self
.carver_prefs
.Key_Scale
],\
318 ["Rotation", "LMB + Move"],\
319 ["Step Angle", "CTRL + LMB + Move"],\
323 help_txt
+=[["Previous or Next Profile", self
.carver_prefs
.Key_Subadd
+ " " + self
.carver_prefs
.Key_Subrem
]]
326 ["Create / Delete rows", chr(8597)],\
327 ["Create / Delete cols", chr(8596)],\
328 ["Gap for rows or columns", self
.carver_prefs
.Key_Gapy
+ " " + self
.carver_prefs
.Key_Gapx
]
331 blf
.size(0, round(15 * ui_scale
), 72)
332 help_txt
, bloc_height
, max_option
, max_key
, comma
= get_text_info(self
, context
, help_txt
)
333 draw_string(self
, color1
, color2
, xHelp
, yHelp
, help_txt
, max_option
)
336 xrect
= region
.width
- t_panel_width
- 80
338 coords
= [(xrect
, yrect
), (xrect
+60, yrect
), (xrect
+60, yrect
-60), (xrect
, yrect
-60)]
340 # Draw rectangle background in the lower right
341 draw_shader(self
, (0.0, 0.0, 0.0), 0.3, 'TRI_FAN', coords
, size
=self
.carver_prefs
.LineWidth
)
343 # Use numpy to get the vertices and indices of the profile object to draw
345 location
= Vector((region
.width
- t_panel_width
- WidthProfil
, 50, 0))
348 mesh
= bpy
.data
.meshes
[self
.Profils
[self
.nProfil
][0]]
349 mesh
.calc_loop_triangles()
350 vertices
= np
.empty((len(mesh
.vertices
), 3), 'f')
351 indices
= np
.empty((len(mesh
.loop_triangles
), 3), 'i')
352 mesh
.vertices
.foreach_get("co", np
.reshape(vertices
, len(mesh
.vertices
) * 3))
353 mesh
.loop_triangles
.foreach_get("vertices", np
.reshape(indices
, len(mesh
.loop_triangles
) * 3))
355 for idx
, vals
in enumerate(vertices
):
357 vals
[0] * ProfilScale
+ location
.x
,
358 vals
[1] * ProfilScale
+ location
.y
,
359 vals
[2] * ProfilScale
+ location
.z
362 #Draw the silhouette of the mesh
363 draw_shader(self
, UIColor
, 0.5, 'TRIS', coords
, size
=self
.carver_prefs
.LineWidth
, indices
=indices
)
368 if len(self
.mouse_path
) > 1:
369 x0
= self
.mouse_path
[0][0]
370 y0
= self
.mouse_path
[0][1]
371 x1
= self
.mouse_path
[1][0]
372 y1
= self
.mouse_path
[1][1]
375 if self
.CutType
== RECTANGLE
:
377 (x0
+ self
.xpos
, y0
+ self
.ypos
), (x1
+ self
.xpos
, y0
+ self
.ypos
), \
378 (x1
+ self
.xpos
, y1
+ self
.ypos
), (x0
+ self
.xpos
, y1
+ self
.ypos
)
380 indices
= ((0, 1, 2), (2, 0, 3))
382 self
.rectangle_coord
= coords
384 draw_shader(self
, UIColor
, 1, 'LINE_LOOP', coords
, size
=self
.carver_prefs
.LineWidth
)
387 draw_shader(self
, UIColor
, 1, 'POINTS', coords
, size
=3)
389 if self
.shift
or self
.CreateMode
:
390 draw_shader(self
, UIColor
, 0.5, 'TRIS', coords
, size
=self
.carver_prefs
.LineWidth
, indices
=indices
)
392 # Draw grid (based on the overlay options) to show the incremental snapping
394 mini_grid(self
, context
, UIColor
)
397 elif self
.CutType
== LINE
:
402 for idx
, vals
in enumerate(self
.mouse_path
):
403 coords
.append([vals
[0] + self
.xpos
, vals
[1] + self
.ypos
])
404 indices
.append([idx
])
408 draw_shader(self
, UIColor
, 1.0, 'LINE_LOOP', coords
, size
=self
.carver_prefs
.LineWidth
)
410 draw_shader(self
, UIColor
, 1.0, 'LINE_STRIP', coords
, size
=self
.carver_prefs
.LineWidth
)
413 draw_shader(self
, UIColor
, 1.0, 'POINTS', coords
, size
=3)
416 if (self
.shift
) or (self
.CreateMode
and self
.Closed
):
417 draw_shader(self
, UIColor
, 0.5, 'TRI_FAN', coords
, size
=self
.carver_prefs
.LineWidth
)
419 # Draw grid (based on the overlay options) to show the incremental snapping
421 mini_grid(self
, context
, UIColor
)
424 elif self
.CutType
== CIRCLE
:
425 # Create a circle using a tri fan
426 tris_coords
, indices
= draw_circle(self
, x0
, y0
)
428 # Remove the vertex in the center to get the outer line of the circle
429 line_coords
= tris_coords
[1:]
430 draw_shader(self
, UIColor
, 1.0, 'LINE_LOOP', line_coords
, size
=self
.carver_prefs
.LineWidth
)
432 if self
.shift
or self
.CreateMode
:
433 draw_shader(self
, UIColor
, 0.5, 'TRIS', tris_coords
, size
=self
.carver_prefs
.LineWidth
, indices
=indices
)
435 if (self
.ObjectMode
or self
.ProfileMode
) and len(self
.CurrentSelection
) > 0:
437 region
= context
.region
438 rv3d
= context
.space_data
.region_3d
441 ob
= self
.ObjectBrush
443 ob
= self
.ProfileBrush
444 mat
= ob
.matrix_world
446 # 50% alpha, 2 pixel width line
447 bgl
.glEnable(bgl
.GL_BLEND
)
449 bbox
= [mat
@ Vector(b
) for b
in ob
.bound_box
]
450 objBBDiagonal
= objDiagonal(self
.CurrentSelection
[0])
454 UIColor
= (0.5, 1.0, 0.0, 1.0)
457 UIColor
= (1.0, 0.8, 0.0, 1.0)
461 CRadius
= ((bbox
[7] - bbox
[0]).length
) / 2
462 for i
in range(int(len(self
.CLR_C
) / 3)):
463 vector3d
= (self
.CLR_C
[idx
* 3] * CRadius
+ self
.CurLoc
.x
, \
464 self
.CLR_C
[idx
* 3 + 1] * CRadius
+ self
.CurLoc
.y
, \
465 self
.CLR_C
[idx
* 3 + 2] * CRadius
+ self
.CurLoc
.z
)
466 vector2d
= bpy_extras
.view3d_utils
.location_3d_to_region_2d(region
, rv3d
, vector3d
)
467 if vector2d
is not None:
468 line_coords
.append((vector2d
[0], vector2d
[1]))
470 if len(line_coords
) > 0 :
471 draw_shader(self
, UIColor
, 1.0, 'LINE_LOOP', line_coords
, size
=gl_size
)
474 if self
.quat_rot
is not None:
475 ob
.location
= self
.CurLoc
478 v
.z
= self
.BrushDepthOffset
479 ob
.location
+= self
.quat_rot
@ v
484 e
.z
= self
.aRotZ
/ 25.0
486 qe
= e
.to_quaternion()
487 qRot
= self
.quat_rot
@ qe
488 ob
.rotation_mode
= 'QUATERNION'
489 ob
.rotation_quaternion
= qRot
490 ob
.rotation_mode
= 'XYZ'
493 if self
.ProfileBrush
is not None:
494 self
.ProfileBrush
.location
= self
.CurLoc
495 self
.ProfileBrush
.rotation_mode
= 'QUATERNION'
496 self
.ProfileBrush
.rotation_quaternion
= qRot
497 self
.ProfileBrush
.rotation_mode
= 'XYZ'
501 bgl
.glDisable(bgl
.GL_BLEND
)