glTF: update shader after Principled changes
[blender-addons.git] / object_carver / carver_draw.py
blob914b888441a6411c5c6de7403a2869e979beec33
1 # SPDX-FileCopyrightText: 2019-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import bpy
6 import blf
7 import bpy_extras
8 import numpy as np
9 import gpu
10 from gpu_extras.batch import batch_for_shader
11 from math import(
12 cos,
13 sin,
14 ceil,
15 floor,
18 from bpy_extras.view3d_utils import (
19 region_2d_to_location_3d,
20 location_3d_to_region_2d,
23 from .carver_utils import (
24 draw_circle,
25 draw_shader,
26 objDiagonal,
27 mini_grid,
30 from mathutils import (
31 Color,
32 Euler,
33 Vector,
34 Quaternion,
37 def get_text_info(self, context, help_txt):
38 """ Return the dimensions of each part of the text """
40 #Extract the longest first option in sublist
41 max_option = max(list(blf.dimensions(0, row[0])[0] for row in help_txt))
43 #Extract the longest key in sublist
44 max_key = max(list(blf.dimensions(0, row[1])[0] for row in help_txt))
46 #Space between option and key with a comma separator (" : ")
47 comma = blf.dimensions(0, "_:_")[0]
49 #Get a default height for all the letters
50 line_height = (blf.dimensions(0, "gM")[1] * 1.45)
52 #Get the total height of the text
53 bloc_height = 0
54 for row in help_txt:
55 bloc_height += line_height
57 return(help_txt, bloc_height, max_option, max_key, comma)
59 def draw_string(self, color1, color2, left, bottom, text, max_option, divide = 1):
60 """ Draw the text like 'option : key' or just 'option' """
62 font_id = 0
63 ui_scale = bpy.context.preferences.system.ui_scale
65 blf.enable(font_id,blf.SHADOW)
66 blf.shadow(font_id, 0, 0.0, 0.0, 0.0, 1.0)
67 blf.shadow_offset(font_id,2,-2)
68 line_height = (blf.dimensions(font_id, "gM")[1] * 1.45)
69 y_offset = 5
71 # Test if the text is a list formatted like : ('option', 'key')
72 if isinstance(text,list):
73 spacer_text = " : "
74 spacer_width = blf.dimensions(font_id, spacer_text)[0]
75 for string in text:
76 blf.position(font_id, (left), (bottom + y_offset), 0)
77 blf.color(font_id, *color1)
78 blf.draw(font_id, string[0])
79 blf.position(font_id, (left + max_option), (bottom + y_offset), 0)
80 blf.draw(font_id, spacer_text)
81 blf.color(font_id, *color2)
82 blf.position(font_id, (left + max_option + spacer_width), (bottom + y_offset), 0)
83 blf.draw(font_id, string[1])
84 y_offset += line_height
85 else:
86 # The text is formatted like : ('option')
87 blf.position(font_id, left, (bottom + y_offset), 0)
88 blf.color(font_id, *color1)
89 blf.draw(font_id, text)
90 y_offset += line_height
92 blf.disable(font_id,blf.SHADOW)
94 # Opengl draw on screen
95 def draw_callback_px(self, context):
96 font_id = 0
97 region = context.region
98 UIColor = (0.992, 0.5518, 0.0, 1.0)
100 # Cut Type
101 RECTANGLE = 0
102 LINE = 1
103 CIRCLE = 2
104 self.carver_prefs = context.preferences.addons[__package__].preferences
106 # Color
107 color1 = (1.0, 1.0, 1.0, 1.0)
108 color2 = UIColor
110 #The mouse is outside the active region
111 if not self.in_view_3d:
112 color1 = color2 = (1.0, 0.2, 0.1, 1.0)
114 # Primitives type
115 PrimitiveType = "Rectangle"
116 if self.CutType == CIRCLE:
117 PrimitiveType = "Circle"
118 if self.CutType == LINE:
119 PrimitiveType = "Line"
121 # Width screen
122 overlap = context.preferences.system.use_region_overlap
124 t_panel_width = 0
125 if overlap:
126 for region in context.area.regions:
127 if region.type == 'TOOLS':
128 t_panel_width = region.width
130 # Initial position
131 region_width = int(region.width / 2.0)
132 y_txt = 10
135 # Draw the center command from bottom to top
137 # Get the size of the text
138 text_size = 18 if region.width >= 850 else 12
139 ui_scale = bpy.context.preferences.system.ui_scale
140 blf.size(0, round(text_size * ui_scale))
142 # Help Display
143 if (self.ObjectMode is False) and (self.ProfileMode is False):
145 # Depth Cursor
146 TypeStr = "Cursor Depth [" + self.carver_prefs.Key_Depth + "]"
147 BoolStr = "(ON)" if self.snapCursor else "(OFF)"
148 help_txt = [[TypeStr, BoolStr]]
150 # Close poygonal shape
151 if self.CreateMode and self.CutType == LINE:
152 TypeStr = "Close [" + self.carver_prefs.Key_Close + "]"
153 BoolStr = "(ON)" if self.Closed else "(OFF)"
154 help_txt += [[TypeStr, BoolStr]]
156 if self.CreateMode is False:
157 # Apply Booleans
158 TypeStr = "Apply Operations [" + self.carver_prefs.Key_Apply + "]"
159 BoolStr = "(OFF)" if self.dont_apply_boolean else "(ON)"
160 help_txt += [[TypeStr, BoolStr]]
162 #Auto update for bevel
163 TypeStr = "Bevel Update [" + self.carver_prefs.Key_Update + "]"
164 BoolStr = "(ON)" if self.Auto_BevelUpdate else "(OFF)"
165 help_txt += [[TypeStr, BoolStr]]
167 # Circle subdivisions
168 if self.CutType == CIRCLE:
169 TypeStr = "Subdivisions [" + self.carver_prefs.Key_Subrem + "][" + self.carver_prefs.Key_Subadd + "]"
170 BoolStr = str((int(360 / self.stepAngle[self.step])))
171 help_txt += [[TypeStr, BoolStr]]
173 if self.CreateMode:
174 help_txt += [["Type [Space]", PrimitiveType]]
175 else:
176 help_txt += [["Cut Type [Space]", PrimitiveType]]
178 else:
179 # Instantiate
180 TypeStr = "Instantiate [" + self.carver_prefs.Key_Instant + "]"
181 BoolStr = "(ON)" if self.Instantiate else "(OFF)"
182 help_txt = [[TypeStr, BoolStr]]
184 # Random rotation
185 if self.alt:
186 TypeStr = "Random Rotation [" + self.carver_prefs.Key_Randrot + "]"
187 BoolStr = "(ON)" if self.RandomRotation else "(OFF)"
188 help_txt += [[TypeStr, BoolStr]]
190 # Thickness
191 if self.BrushSolidify:
192 TypeStr = "Thickness [" + self.carver_prefs.Key_Depth + "]"
193 if self.ProfileMode:
194 BoolStr = str(round(self.ProfileBrush.modifiers["CT_SOLIDIFY"].thickness, 2))
195 if self.ObjectMode:
196 BoolStr = str(round(self.ObjectBrush.modifiers["CT_SOLIDIFY"].thickness, 2))
197 help_txt += [[TypeStr, BoolStr]]
199 # Brush depth
200 if (self.ObjectMode):
201 TypeStr = "Carve Depth [" + self.carver_prefs.Key_Depth + "]"
202 BoolStr = str(round(self.ObjectBrush.data.vertices[0].co.z, 2))
203 help_txt += [[TypeStr, BoolStr]]
205 TypeStr = "Brush Depth [" + self.carver_prefs.Key_BrushDepth + "]"
206 BoolStr = str(round(self.BrushDepthOffset, 2))
207 help_txt += [[TypeStr, BoolStr]]
209 help_txt, bloc_height, max_option, max_key, comma = get_text_info(self, context, help_txt)
210 xCmd = region_width - (max_option + max_key + comma) / 2
211 draw_string(self, color1, color2, xCmd, y_txt, help_txt, max_option, divide = 2)
214 # Separator (Line)
215 LineWidth = (max_option + max_key + comma) / 2
216 if region.width >= 850:
217 LineWidth = 140
219 LineWidth = (max_option + max_key + comma)
220 coords = [(int(region_width - LineWidth/2), y_txt + bloc_height + 8), \
221 (int(region_width + LineWidth/2), y_txt + bloc_height + 8)]
222 draw_shader(self, UIColor, 1, 'LINES', coords, self.carver_prefs.LineWidth)
224 # Command Display
225 if self.CreateMode and ((self.ObjectMode is False) and (self.ProfileMode is False)):
226 BooleanMode = "Create"
227 else:
228 if self.ObjectMode or self.ProfileMode:
229 BooleanType = "Difference) [T]" if self.BoolOps == self.difference else "Union) [T]"
230 BooleanMode = \
231 "Object Brush (" + BooleanType if self.ObjectMode else "Profil Brush (" + BooleanType
232 else:
233 BooleanMode = \
234 "Difference" if (self.shift is False) and (self.ForceRebool is False) else "Rebool"
236 # Display boolean mode
237 text_size = 40 if region.width >= 850 else 20
238 blf.size(0, round(text_size * ui_scale))
240 draw_string(self, color2, color2, region_width - (blf.dimensions(0, BooleanMode)[0]) / 2, \
241 y_txt + bloc_height + 16, BooleanMode, 0, divide = 2)
243 if region.width >= 850:
245 if self.AskHelp is False:
246 # "H for Help" text
247 blf.size(0, round(13 * ui_scale))
248 help_txt = "[" + self.carver_prefs.Key_Help + "] for help"
249 txt_width = blf.dimensions(0, help_txt)[0]
250 txt_height = (blf.dimensions(0, "gM")[1] * 1.45)
252 # Draw a rectangle and put the text "H for Help"
253 xrect = 40
254 yrect = 40
255 rect_vertices = [(xrect - 5, yrect - 5), (xrect + txt_width + 5, yrect - 5), \
256 (xrect + txt_width + 5, yrect + txt_height + 5), (xrect - 5, yrect + txt_height + 5)]
257 draw_shader(self, (0.0, 0.0, 0.0), 0.3, 'TRI_FAN', rect_vertices, self.carver_prefs.LineWidth)
258 draw_string(self, color1, color2, xrect, yrect, help_txt, 0)
260 else:
261 #Draw the help text
262 xHelp = 30 + t_panel_width
263 yHelp = 10
265 if self.ObjectMode or self.ProfileMode:
266 if self.ProfileMode:
267 help_txt = [["Object Mode", self.carver_prefs.Key_Brush]]
268 else:
269 help_txt = [["Cut Mode", self.carver_prefs.Key_Brush]]
271 else:
272 help_txt =[
273 ["Profil Brush", self.carver_prefs.Key_Brush],\
274 ["Move Cursor", "Ctrl + LMB"]
277 if (self.ObjectMode is False) and (self.ProfileMode is False):
278 if self.CreateMode is False:
279 help_txt +=[
280 ["Create geometry", self.carver_prefs.Key_Create],\
282 else:
283 help_txt +=[
284 ["Cut", self.carver_prefs.Key_Create],\
286 if self.CutMode == RECTANGLE:
287 help_txt +=[
288 ["Dimension", "MouseMove"],\
289 ["Move all", "Alt"],\
290 ["Validate", "LMB"],\
291 ["Rebool", "Shift"]
294 elif self.CutMode == CIRCLE:
295 help_txt +=[
296 ["Rotation and Radius", "MouseMove"],\
297 ["Move all", "Alt"],\
298 ["Subdivision", self.carver_prefs.Key_Subrem + " " + self.carver_prefs.Key_Subadd],\
299 ["Incremental rotation", "Ctrl"],\
300 ["Rebool", "Shift"]
303 elif self.CutMode == LINE:
304 help_txt +=[
305 ["Dimension", "MouseMove"],\
306 ["Move all", "Alt"],\
307 ["Validate", "Space"],\
308 ["Rebool", "Shift"],\
309 ["Snap", "Ctrl"],\
310 ["Scale Snap", "WheelMouse"],\
312 else:
313 # ObjectMode
314 help_txt +=[
315 ["Difference", "Space"],\
316 ["Rebool", "Shift + Space"],\
317 ["Duplicate", "Alt + Space"],\
318 ["Scale", self.carver_prefs.Key_Scale],\
319 ["Rotation", "LMB + Move"],\
320 ["Step Angle", "CTRL + LMB + Move"],\
323 if self.ProfileMode:
324 help_txt +=[["Previous or Next Profile", self.carver_prefs.Key_Subadd + " " + self.carver_prefs.Key_Subrem]]
326 help_txt +=[
327 ["Create / Delete rows", chr(8597)],\
328 ["Create / Delete cols", chr(8596)],\
329 ["Gap for rows or columns", self.carver_prefs.Key_Gapy + " " + self.carver_prefs.Key_Gapx]
332 blf.size(0, round(15 * ui_scale))
333 help_txt, bloc_height, max_option, max_key, comma = get_text_info(self, context, help_txt)
334 draw_string(self, color1, color2, xHelp, yHelp, help_txt, max_option)
336 if self.ProfileMode:
337 xrect = region.width - t_panel_width - 80
338 yrect = 80
339 coords = [(xrect, yrect), (xrect+60, yrect), (xrect+60, yrect-60), (xrect, yrect-60)]
341 # Draw rectangle background in the lower right
342 draw_shader(self, (0.0, 0.0, 0.0), 0.3, 'TRI_FAN', coords, size=self.carver_prefs.LineWidth)
344 # Use numpy to get the vertices and indices of the profile object to draw
345 WidthProfil = 50
346 location = Vector((region.width - t_panel_width - WidthProfil, 50, 0))
347 ProfilScale = 20.0
348 coords = []
349 mesh = bpy.data.meshes[self.Profils[self.nProfil][0]]
350 mesh.calc_loop_triangles()
351 vertices = np.empty((len(mesh.vertices), 3), 'f')
352 indices = np.empty((len(mesh.loop_triangles), 3), 'i')
353 mesh.vertices.foreach_get("co", np.reshape(vertices, len(mesh.vertices) * 3))
354 mesh.loop_triangles.foreach_get("vertices", np.reshape(indices, len(mesh.loop_triangles) * 3))
356 for idx, vals in enumerate(vertices):
357 coords.append([
358 vals[0] * ProfilScale + location.x,
359 vals[1] * ProfilScale + location.y,
360 vals[2] * ProfilScale + location.z
363 #Draw the silhouette of the mesh
364 draw_shader(self, UIColor, 0.5, 'TRIS', coords, size=self.carver_prefs.LineWidth, indices=indices)
367 if self.CutMode:
369 if len(self.mouse_path) > 1:
370 x0 = self.mouse_path[0][0]
371 y0 = self.mouse_path[0][1]
372 x1 = self.mouse_path[1][0]
373 y1 = self.mouse_path[1][1]
375 # Cut rectangle
376 if self.CutType == RECTANGLE:
377 coords = [
378 (x0 + self.xpos, y0 + self.ypos), (x1 + self.xpos, y0 + self.ypos), \
379 (x1 + self.xpos, y1 + self.ypos), (x0 + self.xpos, y1 + self.ypos)
381 indices = ((0, 1, 2), (2, 0, 3))
383 self.rectangle_coord = coords
385 draw_shader(self, UIColor, 1, 'LINE_LOOP', coords, size=self.carver_prefs.LineWidth)
387 #Draw points
388 draw_shader(self, UIColor, 1, 'POINTS', coords, size=3)
390 if self.shift or self.CreateMode:
391 draw_shader(self, UIColor, 0.5, 'TRIS', coords, size=self.carver_prefs.LineWidth, indices=indices)
393 # Draw grid (based on the overlay options) to show the incremental snapping
394 if self.ctrl:
395 mini_grid(self, context, UIColor)
397 # Cut Line
398 elif self.CutType == LINE:
399 coords = []
400 indices = []
401 top_grid = False
403 for idx, vals in enumerate(self.mouse_path):
404 coords.append([vals[0] + self.xpos, vals[1] + self.ypos])
405 indices.append([idx])
407 # Draw lines
408 if self.Closed:
409 draw_shader(self, UIColor, 1.0, 'LINE_LOOP', coords, size=self.carver_prefs.LineWidth)
410 else:
411 draw_shader(self, UIColor, 1.0, 'LINE_STRIP', coords, size=self.carver_prefs.LineWidth)
413 # Draw points
414 draw_shader(self, UIColor, 1.0, 'POINTS', coords, size=3)
416 # Draw polygon
417 if (self.shift) or (self.CreateMode and self.Closed):
418 draw_shader(self, UIColor, 0.5, 'TRI_FAN', coords, size=self.carver_prefs.LineWidth)
420 # Draw grid (based on the overlay options) to show the incremental snapping
421 if self.ctrl:
422 mini_grid(self, context, UIColor)
424 # Circle Cut
425 elif self.CutType == CIRCLE:
426 # Create a circle using a tri fan
427 tris_coords, indices = draw_circle(self, x0, y0)
429 # Remove the vertex in the center to get the outer line of the circle
430 line_coords = tris_coords[1:]
431 draw_shader(self, UIColor, 1.0, 'LINE_LOOP', line_coords, size=self.carver_prefs.LineWidth)
433 if self.shift or self.CreateMode:
434 draw_shader(self, UIColor, 0.5, 'TRIS', tris_coords, size=self.carver_prefs.LineWidth, indices=indices)
436 if (self.ObjectMode or self.ProfileMode) and len(self.CurrentSelection) > 0:
437 if self.ShowCursor:
438 region = context.region
439 rv3d = context.space_data.region_3d
441 if self.ObjectMode:
442 ob = self.ObjectBrush
443 if self.ProfileMode:
444 ob = self.ProfileBrush
445 mat = ob.matrix_world
447 # 50% alpha, 2 pixel width line
448 gpu.state.blend_set('ALPHA')
450 bbox = [mat @ Vector(b) for b in ob.bound_box]
451 objBBDiagonal = objDiagonal(self.CurrentSelection[0])
453 if self.shift:
454 gl_size = 4
455 UIColor = (0.5, 1.0, 0.0, 1.0)
456 else:
457 gl_size = 2
458 UIColor = (1.0, 0.8, 0.0, 1.0)
460 line_coords = []
461 idx = 0
462 CRadius = ((bbox[7] - bbox[0]).length) / 2
463 for i in range(int(len(self.CLR_C) / 3)):
464 vector3d = (self.CLR_C[idx * 3] * CRadius + self.CurLoc.x, \
465 self.CLR_C[idx * 3 + 1] * CRadius + self.CurLoc.y, \
466 self.CLR_C[idx * 3 + 2] * CRadius + self.CurLoc.z)
467 vector2d = bpy_extras.view3d_utils.location_3d_to_region_2d(region, rv3d, vector3d)
468 if vector2d is not None:
469 line_coords.append((vector2d[0], vector2d[1]))
470 idx += 1
471 if len(line_coords) > 0 :
472 draw_shader(self, UIColor, 1.0, 'LINE_LOOP', line_coords, size=gl_size)
474 # Object display
475 if self.quat_rot is not None:
476 ob.location = self.CurLoc
477 v = Vector()
478 v.x = v.y = 0.0
479 v.z = self.BrushDepthOffset
480 ob.location += self.quat_rot @ v
482 e = Euler()
483 e.x = 0.0
484 e.y = 0.0
485 e.z = self.aRotZ / 25.0
487 qe = e.to_quaternion()
488 qRot = self.quat_rot @ qe
489 ob.rotation_mode = 'QUATERNION'
490 ob.rotation_quaternion = qRot
491 ob.rotation_mode = 'XYZ'
493 if self.ProfileMode:
494 if self.ProfileBrush is not None:
495 self.ProfileBrush.location = self.CurLoc
496 self.ProfileBrush.rotation_mode = 'QUATERNION'
497 self.ProfileBrush.rotation_quaternion = qRot
498 self.ProfileBrush.rotation_mode = 'XYZ'
500 # Opengl defaults
501 gpu.state.blend_set('NONE')