7 from gpu_extras
.batch
import batch_for_shader
15 from bpy_extras
.view3d_utils
import (
16 region_2d_to_location_3d
,
17 location_3d_to_region_2d
,
20 from .carver_utils
import (
27 from mathutils
import (
34 def get_text_info(self
, context
, help_txt
):
35 """ Return the dimensions of each part of the text """
37 #Extract the longest first option in sublist
38 max_option
= max(list(blf
.dimensions(0, row
[0])[0] for row
in help_txt
))
40 #Extract the longest key in sublist
41 max_key
= max(list(blf
.dimensions(0, row
[1])[0] for row
in help_txt
))
43 #Space between option and key with a comma separator (" : ")
44 comma
= blf
.dimensions(0, "_:_")[0]
46 #Get a default height for all the letters
47 line_height
= (blf
.dimensions(0, "gM")[1] * 1.45)
49 #Get the total height of the text
52 bloc_height
+= line_height
54 return(help_txt
, bloc_height
, max_option
, max_key
, comma
)
56 def draw_string(self
, color1
, color2
, left
, bottom
, text
, max_option
, divide
= 1):
57 """ Draw the text like 'option : key' or just 'option' """
60 ui_scale
= bpy
.context
.preferences
.system
.ui_scale
62 blf
.enable(font_id
,blf
.SHADOW
)
63 blf
.shadow(font_id
, 0, 0.0, 0.0, 0.0, 1.0)
64 blf
.shadow_offset(font_id
,2,-2)
65 line_height
= (blf
.dimensions(font_id
, "gM")[1] * 1.45)
68 # Test if the text is a list formatted like : ('option', 'key')
69 if isinstance(text
,list):
71 spacer_width
= blf
.dimensions(font_id
, spacer_text
)[0]
73 blf
.position(font_id
, (left
), (bottom
+ y_offset
), 0)
74 blf
.color(font_id
, *color1
)
75 blf
.draw(font_id
, string
[0])
76 blf
.position(font_id
, (left
+ max_option
), (bottom
+ y_offset
), 0)
77 blf
.draw(font_id
, spacer_text
)
78 blf
.color(font_id
, *color2
)
79 blf
.position(font_id
, (left
+ max_option
+ spacer_width
), (bottom
+ y_offset
), 0)
80 blf
.draw(font_id
, string
[1])
81 y_offset
+= line_height
83 # The text is formatted like : ('option')
84 blf
.position(font_id
, left
, (bottom
+ y_offset
), 0)
85 blf
.color(font_id
, *color1
)
86 blf
.draw(font_id
, text
)
87 y_offset
+= line_height
89 blf
.disable(font_id
,blf
.SHADOW
)
91 # Opengl draw on screen
92 def draw_callback_px(self
, context
):
94 region
= context
.region
95 UIColor
= (0.992, 0.5518, 0.0, 1.0)
101 self
.carver_prefs
= context
.preferences
.addons
[__package__
].preferences
104 color1
= (1.0, 1.0, 1.0, 1.0)
107 #The mouse is outside the active region
108 if not self
.in_view_3d
:
109 color1
= color2
= (1.0, 0.2, 0.1, 1.0)
112 PrimitiveType
= "Rectangle"
113 if self
.CutType
== CIRCLE
:
114 PrimitiveType
= "Circle"
115 if self
.CutType
== LINE
:
116 PrimitiveType
= "Line"
119 overlap
= context
.preferences
.system
.use_region_overlap
123 for region
in context
.area
.regions
:
124 if region
.type == 'TOOLS':
125 t_panel_width
= region
.width
128 region_width
= int(region
.width
/ 2.0)
132 # Draw the center command from bottom to top
134 # Get the size of the text
135 text_size
= 18 if region
.width
>= 850 else 12
136 ui_scale
= bpy
.context
.preferences
.system
.ui_scale
137 blf
.size(0, round(text_size
* ui_scale
), 72)
140 if (self
.ObjectMode
is False) and (self
.ProfileMode
is False):
143 TypeStr
= "Cursor Depth [" + self
.carver_prefs
.Key_Depth
+ "]"
144 BoolStr
= "(ON)" if self
.snapCursor
else "(OFF)"
145 help_txt
= [[TypeStr
, BoolStr
]]
147 # Close poygonal shape
148 if self
.CreateMode
and self
.CutType
== LINE
:
149 TypeStr
= "Close [" + self
.carver_prefs
.Key_Close
+ "]"
150 BoolStr
= "(ON)" if self
.Closed
else "(OFF)"
151 help_txt
+= [[TypeStr
, BoolStr
]]
153 if self
.CreateMode
is False:
155 TypeStr
= "Apply Operations [" + self
.carver_prefs
.Key_Apply
+ "]"
156 BoolStr
= "(OFF)" if self
.dont_apply_boolean
else "(ON)"
157 help_txt
+= [[TypeStr
, BoolStr
]]
159 #Auto update for bevel
160 TypeStr
= "Bevel Update [" + self
.carver_prefs
.Key_Update
+ "]"
161 BoolStr
= "(ON)" if self
.Auto_BevelUpdate
else "(OFF)"
162 help_txt
+= [[TypeStr
, BoolStr
]]
164 # Circle subdivisions
165 if self
.CutType
== CIRCLE
:
166 TypeStr
= "Subdivisions [" + self
.carver_prefs
.Key_Subrem
+ "][" + self
.carver_prefs
.Key_Subadd
+ "]"
167 BoolStr
= str((int(360 / self
.stepAngle
[self
.step
])))
168 help_txt
+= [[TypeStr
, BoolStr
]]
171 help_txt
+= [["Type [Space]", PrimitiveType
]]
173 help_txt
+= [["Cut Type [Space]", PrimitiveType
]]
177 TypeStr
= "Instantiate [" + self
.carver_prefs
.Key_Instant
+ "]"
178 BoolStr
= "(ON)" if self
.Instantiate
else "(OFF)"
179 help_txt
= [[TypeStr
, BoolStr
]]
183 TypeStr
= "Random Rotation [" + self
.carver_prefs
.Key_Randrot
+ "]"
184 BoolStr
= "(ON)" if self
.RandomRotation
else "(OFF)"
185 help_txt
+= [[TypeStr
, BoolStr
]]
188 if self
.BrushSolidify
:
189 TypeStr
= "Thickness [" + self
.carver_prefs
.Key_Depth
+ "]"
191 BoolStr
= str(round(self
.ProfileBrush
.modifiers
["CT_SOLIDIFY"].thickness
, 2))
193 BoolStr
= str(round(self
.ObjectBrush
.modifiers
["CT_SOLIDIFY"].thickness
, 2))
194 help_txt
+= [[TypeStr
, BoolStr
]]
197 if (self
.ObjectMode
):
198 TypeStr
= "Carve Depth [" + self
.carver_prefs
.Key_Depth
+ "]"
199 BoolStr
= str(round(self
.ObjectBrush
.data
.vertices
[0].co
.z
, 2))
200 help_txt
+= [[TypeStr
, BoolStr
]]
202 TypeStr
= "Brush Depth [" + self
.carver_prefs
.Key_BrushDepth
+ "]"
203 BoolStr
= str(round(self
.BrushDepthOffset
, 2))
204 help_txt
+= [[TypeStr
, BoolStr
]]
206 help_txt
, bloc_height
, max_option
, max_key
, comma
= get_text_info(self
, context
, help_txt
)
207 xCmd
= region_width
- (max_option
+ max_key
+ comma
) / 2
208 draw_string(self
, color1
, color2
, xCmd
, y_txt
, help_txt
, max_option
, divide
= 2)
212 LineWidth
= (max_option
+ max_key
+ comma
) / 2
213 if region
.width
>= 850:
216 LineWidth
= (max_option
+ max_key
+ comma
)
217 coords
= [(int(region_width
- LineWidth
/2), y_txt
+ bloc_height
+ 8), \
218 (int(region_width
+ LineWidth
/2), y_txt
+ bloc_height
+ 8)]
219 draw_shader(self
, UIColor
, 1, 'LINES', coords
, self
.carver_prefs
.LineWidth
)
222 if self
.CreateMode
and ((self
.ObjectMode
is False) and (self
.ProfileMode
is False)):
223 BooleanMode
= "Create"
225 if self
.ObjectMode
or self
.ProfileMode
:
226 BooleanType
= "Difference) [T]" if self
.BoolOps
== self
.difference
else "Union) [T]"
228 "Object Brush (" + BooleanType
if self
.ObjectMode
else "Profil Brush (" + BooleanType
231 "Difference" if (self
.shift
is False) and (self
.ForceRebool
is False) else "Rebool"
233 # Display boolean mode
234 text_size
= 40 if region
.width
>= 850 else 20
235 blf
.size(0, round(text_size
* ui_scale
), 72)
237 draw_string(self
, color2
, color2
, region_width
- (blf
.dimensions(0, BooleanMode
)[0]) / 2, \
238 y_txt
+ bloc_height
+ 16, BooleanMode
, 0, divide
= 2)
240 if region
.width
>= 850:
242 if self
.AskHelp
is False:
244 blf
.size(0, round(13 * ui_scale
), 72)
245 help_txt
= "[" + self
.carver_prefs
.Key_Help
+ "] for help"
246 txt_width
= blf
.dimensions(0, help_txt
)[0]
247 txt_height
= (blf
.dimensions(0, "gM")[1] * 1.45)
249 # Draw a rectangle and put the text "H for Help"
252 rect_vertices
= [(xrect
- 5, yrect
- 5), (xrect
+ txt_width
+ 5, yrect
- 5), \
253 (xrect
+ txt_width
+ 5, yrect
+ txt_height
+ 5), (xrect
- 5, yrect
+ txt_height
+ 5)]
254 draw_shader(self
, (0.0, 0.0, 0.0), 0.3, 'TRI_FAN', rect_vertices
, self
.carver_prefs
.LineWidth
)
255 draw_string(self
, color1
, color2
, xrect
, yrect
, help_txt
, 0)
259 xHelp
= 30 + t_panel_width
262 if self
.ObjectMode
or self
.ProfileMode
:
264 help_txt
= [["Object Mode", self
.carver_prefs
.Key_Brush
]]
266 help_txt
= [["Cut Mode", self
.carver_prefs
.Key_Brush
]]
270 ["Profil Brush", self
.carver_prefs
.Key_Brush
],\
271 ["Move Cursor", "Ctrl + LMB"]
274 if (self
.ObjectMode
is False) and (self
.ProfileMode
is False):
275 if self
.CreateMode
is False:
277 ["Create geometry", self
.carver_prefs
.Key_Create
],\
281 ["Cut", self
.carver_prefs
.Key_Create
],\
283 if self
.CutMode
== RECTANGLE
:
285 ["Dimension", "MouseMove"],\
286 ["Move all", "Alt"],\
287 ["Validate", "LMB"],\
291 elif self
.CutMode
== CIRCLE
:
293 ["Rotation and Radius", "MouseMove"],\
294 ["Move all", "Alt"],\
295 ["Subdivision", self
.carver_prefs
.Key_Subrem
+ " " + self
.carver_prefs
.Key_Subadd
],\
296 ["Incremental rotation", "Ctrl"],\
300 elif self
.CutMode
== LINE
:
302 ["Dimension", "MouseMove"],\
303 ["Move all", "Alt"],\
304 ["Validate", "Space"],\
305 ["Rebool", "Shift"],\
307 ["Scale Snap", "WheelMouse"],\
312 ["Difference", "Space"],\
313 ["Rebool", "Shift + Space"],\
314 ["Duplicate", "Alt + Space"],\
315 ["Scale", self
.carver_prefs
.Key_Scale
],\
316 ["Rotation", "LMB + Move"],\
317 ["Step Angle", "CTRL + LMB + Move"],\
321 help_txt
+=[["Previous or Next Profile", self
.carver_prefs
.Key_Subadd
+ " " + self
.carver_prefs
.Key_Subrem
]]
324 ["Create / Delete rows", chr(8597)],\
325 ["Create / Delete cols", chr(8596)],\
326 ["Gap for rows or columns", self
.carver_prefs
.Key_Gapy
+ " " + self
.carver_prefs
.Key_Gapx
]
329 blf
.size(0, round(15 * ui_scale
), 72)
330 help_txt
, bloc_height
, max_option
, max_key
, comma
= get_text_info(self
, context
, help_txt
)
331 draw_string(self
, color1
, color2
, xHelp
, yHelp
, help_txt
, max_option
)
334 xrect
= region
.width
- t_panel_width
- 80
336 coords
= [(xrect
, yrect
), (xrect
+60, yrect
), (xrect
+60, yrect
-60), (xrect
, yrect
-60)]
338 # Draw rectangle background in the lower right
339 draw_shader(self
, (0.0, 0.0, 0.0), 0.3, 'TRI_FAN', coords
, size
=self
.carver_prefs
.LineWidth
)
341 # Use numpy to get the vertices and indices of the profile object to draw
343 location
= Vector((region
.width
- t_panel_width
- WidthProfil
, 50, 0))
346 mesh
= bpy
.data
.meshes
[self
.Profils
[self
.nProfil
][0]]
347 mesh
.calc_loop_triangles()
348 vertices
= np
.empty((len(mesh
.vertices
), 3), 'f')
349 indices
= np
.empty((len(mesh
.loop_triangles
), 3), 'i')
350 mesh
.vertices
.foreach_get("co", np
.reshape(vertices
, len(mesh
.vertices
) * 3))
351 mesh
.loop_triangles
.foreach_get("vertices", np
.reshape(indices
, len(mesh
.loop_triangles
) * 3))
353 for idx
, vals
in enumerate(vertices
):
355 vals
[0] * ProfilScale
+ location
.x
,
356 vals
[1] * ProfilScale
+ location
.y
,
357 vals
[2] * ProfilScale
+ location
.z
360 #Draw the silhouette of the mesh
361 draw_shader(self
, UIColor
, 0.5, 'TRIS', coords
, size
=self
.carver_prefs
.LineWidth
, indices
=indices
)
366 if len(self
.mouse_path
) > 1:
367 x0
= self
.mouse_path
[0][0]
368 y0
= self
.mouse_path
[0][1]
369 x1
= self
.mouse_path
[1][0]
370 y1
= self
.mouse_path
[1][1]
373 if self
.CutType
== RECTANGLE
:
375 (x0
+ self
.xpos
, y0
+ self
.ypos
), (x1
+ self
.xpos
, y0
+ self
.ypos
), \
376 (x1
+ self
.xpos
, y1
+ self
.ypos
), (x0
+ self
.xpos
, y1
+ self
.ypos
)
378 indices
= ((0, 1, 2), (2, 0, 3))
380 self
.rectangle_coord
= coords
382 draw_shader(self
, UIColor
, 1, 'LINE_LOOP', coords
, size
=self
.carver_prefs
.LineWidth
)
385 draw_shader(self
, UIColor
, 1, 'POINTS', coords
, size
=3)
387 if self
.shift
or self
.CreateMode
:
388 draw_shader(self
, UIColor
, 0.5, 'TRIS', coords
, size
=self
.carver_prefs
.LineWidth
, indices
=indices
)
390 # Draw grid (based on the overlay options) to show the incremental snapping
392 mini_grid(self
, context
, UIColor
)
395 elif self
.CutType
== LINE
:
400 for idx
, vals
in enumerate(self
.mouse_path
):
401 coords
.append([vals
[0] + self
.xpos
, vals
[1] + self
.ypos
])
402 indices
.append([idx
])
406 draw_shader(self
, UIColor
, 1.0, 'LINE_LOOP', coords
, size
=self
.carver_prefs
.LineWidth
)
408 draw_shader(self
, UIColor
, 1.0, 'LINE_STRIP', coords
, size
=self
.carver_prefs
.LineWidth
)
411 draw_shader(self
, UIColor
, 1.0, 'POINTS', coords
, size
=3)
414 if (self
.shift
) or (self
.CreateMode
and self
.Closed
):
415 draw_shader(self
, UIColor
, 0.5, 'TRI_FAN', coords
, size
=self
.carver_prefs
.LineWidth
)
417 # Draw grid (based on the overlay options) to show the incremental snapping
419 mini_grid(self
, context
, UIColor
)
422 elif self
.CutType
== CIRCLE
:
423 # Create a circle using a tri fan
424 tris_coords
, indices
= draw_circle(self
, x0
, y0
)
426 # Remove the vertex in the center to get the outer line of the circle
427 line_coords
= tris_coords
[1:]
428 draw_shader(self
, UIColor
, 1.0, 'LINE_LOOP', line_coords
, size
=self
.carver_prefs
.LineWidth
)
430 if self
.shift
or self
.CreateMode
:
431 draw_shader(self
, UIColor
, 0.5, 'TRIS', tris_coords
, size
=self
.carver_prefs
.LineWidth
, indices
=indices
)
433 if (self
.ObjectMode
or self
.ProfileMode
) and len(self
.CurrentSelection
) > 0:
435 region
= context
.region
436 rv3d
= context
.space_data
.region_3d
439 ob
= self
.ObjectBrush
441 ob
= self
.ProfileBrush
442 mat
= ob
.matrix_world
444 # 50% alpha, 2 pixel width line
445 bgl
.glEnable(bgl
.GL_BLEND
)
447 bbox
= [mat
@ Vector(b
) for b
in ob
.bound_box
]
448 objBBDiagonal
= objDiagonal(self
.CurrentSelection
[0])
452 UIColor
= (0.5, 1.0, 0.0, 1.0)
455 UIColor
= (1.0, 0.8, 0.0, 1.0)
459 CRadius
= ((bbox
[7] - bbox
[0]).length
) / 2
460 for i
in range(int(len(self
.CLR_C
) / 3)):
461 vector3d
= (self
.CLR_C
[idx
* 3] * CRadius
+ self
.CurLoc
.x
, \
462 self
.CLR_C
[idx
* 3 + 1] * CRadius
+ self
.CurLoc
.y
, \
463 self
.CLR_C
[idx
* 3 + 2] * CRadius
+ self
.CurLoc
.z
)
464 vector2d
= bpy_extras
.view3d_utils
.location_3d_to_region_2d(region
, rv3d
, vector3d
)
465 if vector2d
is not None:
466 line_coords
.append((vector2d
[0], vector2d
[1]))
468 if len(line_coords
) > 0 :
469 draw_shader(self
, UIColor
, 1.0, 'LINE_LOOP', line_coords
, size
=gl_size
)
472 if self
.quat_rot
is not None:
473 ob
.location
= self
.CurLoc
476 v
.z
= self
.BrushDepthOffset
477 ob
.location
+= self
.quat_rot
@ v
482 e
.z
= self
.aRotZ
/ 25.0
484 qe
= e
.to_quaternion()
485 qRot
= self
.quat_rot
@ qe
486 ob
.rotation_mode
= 'QUATERNION'
487 ob
.rotation_quaternion
= qRot
488 ob
.rotation_mode
= 'XYZ'
491 if self
.ProfileBrush
is not None:
492 self
.ProfileBrush
.location
= self
.CurLoc
493 self
.ProfileBrush
.rotation_mode
= 'QUATERNION'
494 self
.ProfileBrush
.rotation_quaternion
= qRot
495 self
.ProfileBrush
.rotation_mode
= 'XYZ'
499 bgl
.glDisable(bgl
.GL_BLEND
)