Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeta-androcto <meta.androcto1@gmail.com>2019-05-01 08:07:43 +0300
committermeta-androcto <meta.androcto1@gmail.com>2019-05-01 08:07:43 +0300
commit175202efb25496c1225265c3f5962f4341101d32 (patch)
tree853547983947862303b32f5734e853e063a3d69a /object_carver
parent89568c1a42eb894d4c4b51dbc0a896f20a64bcfe (diff)
Update object_carver to 2.8 thanks @clarkx
Diffstat (limited to 'object_carver')
-rw-r--r--object_carver/__init__.py308
-rw-r--r--object_carver/carver_draw.py494
-rw-r--r--object_carver/carver_operator.py1347
-rw-r--r--object_carver/carver_preferences.py194
-rw-r--r--object_carver/carver_profils.py409
-rw-r--r--object_carver/carver_utils.py941
6 files changed, 3693 insertions, 0 deletions
diff --git a/object_carver/__init__.py b/object_carver/__init__.py
new file mode 100644
index 00000000..3c36c182
--- /dev/null
+++ b/object_carver/__init__.py
@@ -0,0 +1,308 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Carver",
+ "author": "Pixivore, Cedric LEPILLER, Ted Milker, Clarkx",
+ "description": "Multiple tools to carve or to create objects",
+ "version": (1, 2, 0),
+ "blender": (2, 80, 0),
+ "location": "3D View",
+ "warning": "",
+ "wiki_url": "",
+ "support": 'COMMUNITY',
+ "category": "Object"
+ }
+
+import bpy
+import imp
+from bpy.props import (
+ BoolProperty,
+ StringProperty,
+ IntProperty
+ )
+from bpy.types import (AddonPreferences, WorkSpaceTool)
+from bpy.utils.toolsystem import ToolDef
+
+from . import carver_utils
+imp.reload(carver_utils)
+from . import carver_profils
+imp.reload(carver_profils)
+from . import carver_draw
+imp.reload(carver_draw)
+from . import carver_operator
+imp.reload(carver_operator)
+
+# TODO : Create an icon for Carver MT
+# Add an icon in the toolbar
+# class CarverTool(WorkSpaceTool):
+# bl_space_type='VIEW_3D'
+# bl_context_mode='OBJECT'
+# bl_idname = "carver.operator"
+# bl_label = "Carver"
+# bl_description = (
+# "Multiple tools to carve \n"
+# "or to create objects"
+# )
+#
+# #Icons : \blender-2.80\2.80\datafiles\icons
+# #Todo: Create a new icon for Carver
+# bl_icon = "ops.mesh.knife_tool"
+# bl_widget = None
+# bl_keymap = (
+# ("carver.operator", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
+# )
+#
+# def draw_settings(context, layout, tool):
+# layout.prop(tool.operator_properties, "carver")
+
+
+class CarverPreferences(AddonPreferences):
+ bl_idname = __name__
+
+
+ Enable_Tab_01: BoolProperty(
+ name="Info",
+ description="Some general information and settings about the add-on",
+ default=False
+ )
+ Enable_Tab_02: BoolProperty(
+ name="Hotkeys",
+ description="List of the shortcuts used during carving",
+ default=False
+ )
+ Key_Create: StringProperty(
+ name="Object creation",
+ description="Object creation",
+ maxlen=1,
+ default="C"
+ )
+ Key_Update: StringProperty(
+ name="Auto Bevel Update",
+ description="Auto Bevel Update",
+ maxlen=1,
+ default="A",
+ )
+ Key_Bool: StringProperty(
+ name="Boolean type",
+ description="Boolean operation type",
+ maxlen=1,
+ default="T",
+ )
+ Key_Brush: StringProperty(
+ name="Brush Mode",
+ description="Brush Mode",
+ maxlen=1,
+ default="B",
+ )
+ Key_Help: StringProperty(
+ name="Help display",
+ description="Help display",
+ maxlen=1,
+ default="H",
+ )
+ Key_Instant: StringProperty(
+ name="Instantiate",
+ description="Instantiate object",
+ maxlen=1,
+ default="I",
+ )
+ Key_Close: StringProperty(
+ name="Close polygonal shape",
+ description="Close polygonal shape",
+ maxlen=1,
+ default="X",
+ )
+ Key_Apply: StringProperty(
+ name="Apply operation",
+ description="Apply operation",
+ maxlen=1,
+ default="Q",
+ )
+ Key_Scale: StringProperty(
+ name="Scale object",
+ description="Scale object",
+ maxlen=1,
+ default="S",
+ )
+ Key_Gapy: StringProperty(
+ name="Gap rows",
+ description="Scale gap between columns",
+ maxlen=1,
+ default="J",
+ )
+ Key_Gapx: StringProperty(
+ name="Gap columns",
+ description="Scale gap between columns",
+ maxlen=1,
+ default="U",
+ )
+ Key_Depth: StringProperty(
+ name="Depth",
+ description="Cursor depth or solidify pattern",
+ maxlen=1,
+ default="D",
+ )
+ Key_BrushDepth: StringProperty(
+ name="Brush Depth",
+ description="Brush depth",
+ maxlen=1,
+ default="C",
+ )
+ Key_Subadd: StringProperty(
+ name="Add subdivision",
+ description="Add subdivision",
+ maxlen=1,
+ default="X",
+ )
+ Key_Subrem: StringProperty(
+ name="Remove subdivision",
+ description="Remove subdivision",
+ maxlen=1,
+ default="W",
+ )
+ Key_Randrot: StringProperty(
+ name="Random rotation",
+ description="Random rotation",
+ maxlen=1,
+ default="R",
+ )
+ ProfilePrefix: StringProperty(
+ name="Profile prefix",
+ description="Prefix to look for profiles with",
+ default="Carver_Profile-",
+ )
+ LineWidth: IntProperty(
+ name="Line Width",
+ description="Thickness of the drawing lines",
+ default=1,
+ )
+ Key_Snap: StringProperty(
+ name="Grid Snap",
+ description="Grid Snap",
+ maxlen=1,
+ default="G",
+ )
+
+ def draw(self, context):
+ scene = context.scene
+ layout = self.layout
+
+ icon_1 = "TRIA_RIGHT" if not self.Enable_Tab_01 else "TRIA_DOWN"
+ box = layout.box()
+
+ box.prop(self, "Enable_Tab_01", text="Info and Settings", emboss=False, icon=icon_1)
+ if self.Enable_Tab_01:
+ box.label(text="Carver Operator:", icon="LAYER_ACTIVE")
+ box.label(text="Select a Mesh Object and press [CTRL]+[SHIFT]+[X] to carve",
+ icon="LAYER_USED")
+ box.label(text="To finish carving press [ESC] or [RIGHT CLICK]",
+ icon="LAYER_USED")
+ box.prop(self, "ProfilePrefix", text="Profile prefix")
+ row = box.row(align=True)
+ row.label(text="Line width:")
+ row.prop(self, "LineWidth", text="")
+
+ icon_2 = "TRIA_RIGHT" if not self.Enable_Tab_02 else "TRIA_DOWN"
+ box = layout.box()
+ box.prop(self, "Enable_Tab_02", text="Keys", emboss=False, icon=icon_2)
+ if self.Enable_Tab_02:
+ split = box.split(align=True)
+ box = split.box()
+ col = box.column(align=True)
+ col.label(text="Object Creation:")
+ col.prop(self, "Key_Create", text="")
+ col.label(text="Auto bevel update:")
+ col.prop(self, "Key_Update", text="")
+ col.label(text="Boolean operation type:")
+ col.prop(self, "Key_Bool", text="")
+ col.label(text="Brush Depth:")
+ col.prop(self, "Key_BrushDepth", text="")
+
+ box = split.box()
+ col = box.column(align=True)
+ col.label(text="Brush Mode:")
+ col.prop(self, "Key_Brush", text="")
+ col.label(text="Help display:")
+ col.prop(self, "Key_Help", text="")
+ col.label(text="Instantiate object:")
+ col.prop(self, "Key_Instant", text="")
+ col.label(text="Random rotation:")
+ col.prop(self, "Key_Randrot", text="")
+
+ box = split.box()
+ col = box.column(align=True)
+ col.label(text="Close polygonal shape:")
+ col.prop(self, "Key_Close", text="")
+ col.label(text="Apply operation:")
+ col.prop(self, "Key_Apply", text="")
+ col.label(text="Scale object:")
+ col.prop(self, "Key_Scale", text="")
+ col.label(text="Subdiv add:")
+ col.prop(self, "Key_Subadd", text="")
+
+ box = split.box()
+ col = box.column(align=True)
+ col.label(text="Gap rows:")
+ col.prop(self, "Key_Gapy", text="")
+ col.label(text="Gap columns:")
+ col.prop(self, "Key_Gapx", text="")
+ col.label(text="Depth / Solidify:")
+ col.prop(self, "Key_Depth", text="")
+ col.label(text="Subdiv Remove:")
+ col.prop(self, "Key_Subrem", text="")
+
+ box = split.box()
+ col = box.column(align=True)
+ col.label(text="Grid Snap:")
+ col.prop(self, "Key_Snap", text="")
+
+addon_keymaps = []
+
+def register():
+ print("Registered Carver")
+
+ bpy.utils.register_class(CarverPreferences)
+ # Todo : Add an icon in the toolbat
+ # bpy.utils.register_tool(CarverTool, separator=True, group=True)
+ carver_operator.register()
+
+ # add keymap entry
+ kcfg = bpy.context.window_manager.keyconfigs.addon
+ if kcfg:
+ km = kcfg.keymaps.new(name='3D View', space_type='VIEW_3D')
+ kmi = km.keymap_items.new("carver.operator", 'X', 'PRESS', shift=True, ctrl=True)
+ addon_keymaps.append((km, kmi))
+
+def unregister():
+ # Todo : Add an icon in the toolbat
+ # bpy.utils.unregister_tool(CarverTool)
+ carver_operator.unregister()
+ bpy.utils.unregister_class(CarverPreferences)
+
+ print("Unregistered Carver")
+
+ # remove keymap entry
+ for km, kmi in addon_keymaps:
+ km.keymap_items.remove(kmi)
+ addon_keymaps.clear()
+
+
+
+if __name__ == "__main__":
+ register()
diff --git a/object_carver/carver_draw.py b/object_carver/carver_draw.py
new file mode 100644
index 00000000..1e8d636c
--- /dev/null
+++ b/object_carver/carver_draw.py
@@ -0,0 +1,494 @@
+import bpy
+import bgl
+import blf
+import bpy_extras
+import numpy as np
+import gpu
+from gpu_extras.batch import batch_for_shader
+from math import(
+ cos,
+ sin,
+ ceil,
+ floor,
+ )
+
+from bpy_extras.view3d_utils import (
+ region_2d_to_location_3d,
+ location_3d_to_region_2d,
+)
+
+from .carver_utils import (
+ draw_circle,
+ draw_shader,
+ objDiagonal,
+ mini_grid,
+ )
+
+from mathutils import (
+ Color,
+ Euler,
+ Vector,
+ Quaternion,
+)
+
+def get_text_info(self, context, help_txt):
+ """ Return the dimensions of each part of the text """
+
+ #Extract the longest first option in sublist
+ max_option = max(list(blf.dimensions(0, row[0])[0] for row in help_txt))
+
+ #Extract the longest key in sublist
+ max_key = max(list(blf.dimensions(0, row[1])[0] for row in help_txt))
+
+ #Space between option and key with a comma separator (" : ")
+ comma = blf.dimensions(0, "_:_")[0]
+
+ #Get a default height for all the letters
+ line_height = (blf.dimensions(0, "gM")[1] * 1.45)
+
+ #Get the total height of the text
+ bloc_height = 0
+ for row in help_txt:
+ bloc_height += line_height
+
+ return(help_txt, bloc_height, max_option, max_key, comma)
+
+def draw_string(self, color1, color2, left, bottom, text, max_option, divide = 1):
+ """ Draw the text like 'option : key' or just 'option' """
+
+ font_id = 0
+ blf.enable(font_id,blf.SHADOW)
+ blf.shadow(font_id, 0, 0.0, 0.0, 0.0, 1.0)
+ blf.shadow_offset(font_id,2,-2)
+ line_height = (blf.dimensions(font_id, "gM")[1] * 1.45)
+ y_offset = 5
+
+ # Test if the text is a list formated like : ('option', 'key')
+ if isinstance(text,list):
+ for string in text:
+ blf.position(font_id, (left), (bottom + y_offset), 0)
+ blf.color(font_id, *color1)
+ blf.draw(font_id, string[0])
+ blf.position(font_id, (left + max_option), (bottom + y_offset), 0)
+ blf.draw(font_id, " : ")
+ blf.color(font_id, *color2)
+ blf.position(font_id, (left + max_option + 15), (bottom + y_offset), 0)
+ blf.draw(font_id, string[1])
+ y_offset += line_height
+ else:
+ # The text is formated like : ('option')
+ blf.position(font_id, left, (bottom + y_offset), 0)
+ blf.color(font_id, *color1)
+ blf.draw(font_id, text)
+ y_offset += line_height
+
+ blf.disable(font_id,blf.SHADOW)
+
+# Opengl draw on screen
+def draw_callback_px(self, context):
+ font_id = 0
+ region = context.region
+ UIColor = (0.992, 0.5518, 0.0, 1.0)
+
+ # Cut Type
+ RECTANGLE = 0
+ LINE = 1
+ CIRCLE = 2
+ self.carver_prefs = context.preferences.addons[__package__].preferences
+
+ # Color
+ color1 = (1.0, 1.0, 1.0, 1.0)
+ color2 = UIColor
+
+ #The mouse is outside the active region
+ if not self.in_view_3d:
+ color1 = color2 = (1.0, 0.2, 0.1, 1.0)
+
+ # Primitives type
+ PrimitiveType = "Rectangle"
+ if self.CutType == CIRCLE:
+ PrimitiveType = "Circle"
+ if self.CutType == LINE:
+ PrimitiveType = "Line"
+
+ # Width screen
+ overlap = context.preferences.system.use_region_overlap
+
+ t_panel_width = 0
+ if overlap:
+ for region in context.area.regions:
+ if region.type == 'TOOLS':
+ t_panel_width = region.width
+
+ # Initial position
+ region_width = int(region.width / 2.0)
+ y_txt = 10
+
+
+ # Draw the center command from bottom to top
+
+ # Get the size of the text
+ text_size = 18 if region.width >= 850 else 12
+ blf.size(0, int(round(text_size * bpy.context.preferences.view.ui_scale, 0)), 72)
+
+ # Help Display
+ if (self.ObjectMode is False) and (self.ProfileMode is False):
+
+ # Depth Cursor
+ TypeStr = "Cursor Depth [" + self.carver_prefs.Key_Depth + "]"
+ BoolStr = "(ON)" if self.snapCursor else "(OFF)"
+ help_txt = [[TypeStr, BoolStr]]
+
+ # Close poygonal shape
+ if self.CreateMode and self.CutType == LINE:
+ TypeStr = "Close [" + self.carver_prefs.Key_Close + "]"
+ BoolStr = "(ON)" if self.Closed else "(OFF)"
+ help_txt += [[TypeStr, BoolStr]]
+
+ if self.CreateMode is False:
+ # Apply Booleans
+ TypeStr = "Apply Operations [" + self.carver_prefs.Key_Apply + "]"
+ BoolStr = "(OFF)" if self.dont_apply_boolean else "(ON)"
+ help_txt += [[TypeStr, BoolStr]]
+
+ #Auto update for bevel
+ TypeStr = "Bevel Update [" + self.carver_prefs.Key_Update + "]"
+ BoolStr = "(ON)" if self.Auto_BevelUpdate else "(OFF)"
+ help_txt += [[TypeStr, BoolStr]]
+
+ # Circle subdivisions
+ if self.CutType == CIRCLE:
+ TypeStr = "Subdivisions [" + self.carver_prefs.Key_Subrem + "][" + self.carver_prefs.Key_Subadd + "]"
+ BoolStr = str((int(360 / self.stepAngle[self.step])))
+ help_txt += [[TypeStr, BoolStr]]
+
+ if self.CreateMode:
+ help_txt += [["Type [Space]", PrimitiveType]]
+ else:
+ help_txt += [["Cut Type [Space]", PrimitiveType]]
+
+ else:
+ # Instantiate
+ TypeStr = "Instantiate [" + self.carver_prefs.Key_Instant + "]"
+ BoolStr = "(ON)" if self.Instantiate else "(OFF)"
+ help_txt = [[TypeStr, BoolStr]]
+
+ # Random rotation
+ if self.alt:
+ TypeStr = "Random Rotation [" + self.carver_prefs.Key_Randrot + "]"
+ BoolStr = "(ON)" if self.RandomRotation else "(OFF)"
+ help_txt += [[TypeStr, BoolStr]]
+
+ # Thickness
+ if self.BrushSolidify:
+ TypeStr = "Thickness [" + self.carver_prefs.Key_Depth + "]"
+ if self.ProfileMode:
+ BoolStr = str(round(self.ProfileBrush.modifiers["CT_SOLIDIFY"].thickness, 2))
+ if self.ObjectMode:
+ BoolStr = str(round(self.ObjectBrush.modifiers["CT_SOLIDIFY"].thickness, 2))
+ help_txt += [[TypeStr, BoolStr]]
+
+ # Brush depth
+ if (self.ObjectMode):
+ TypeStr = "Carve Depth [" + self.carver_prefs.Key_Depth + "]"
+ BoolStr = str(round(self.ObjectBrush.data.vertices[0].co.z, 2))
+ help_txt += [[TypeStr, BoolStr]]
+
+ TypeStr = "Brush Depth [" + self.carver_prefs.Key_BrushDepth + "]"
+ BoolStr = str(round(self.BrushDepthOffset, 2))
+ help_txt += [[TypeStr, BoolStr]]
+
+ help_txt, bloc_height, max_option, max_key, comma = get_text_info(self, context, help_txt)
+ xCmd = region_width - (max_option + max_key + comma) / 2
+ draw_string(self, color1, color2, xCmd, y_txt, help_txt, max_option, divide = 2)
+
+
+ # Separator (Line)
+ LineWidth = (max_option + max_key + comma) / 2
+ if region.width >= 850:
+ LineWidth = 140
+
+ LineWidth = (max_option + max_key + comma)
+ coords = [(int(region_width - LineWidth/2), y_txt + bloc_height + 8), \
+ (int(region_width + LineWidth/2), y_txt + bloc_height + 8)]
+ draw_shader(self, UIColor, 1, 'LINES', coords, self.carver_prefs.LineWidth)
+
+ # Command Display
+ if self.CreateMode and ((self.ObjectMode is False) and (self.ProfileMode is False)):
+ BooleanMode = "Create"
+ else:
+ if self.ObjectMode or self.ProfileMode:
+ BooleanType = "Difference) [T]" if self.BoolOps == self.difference else "Union) [T]"
+ BooleanMode = \
+ "Object Brush (" + BooleanType if self.ObjectMode else "Profil Brush (" + BooleanType
+ else:
+ BooleanMode = \
+ "Difference" if (self.shift is False) and (self.ForceRebool is False) else "Rebool"
+
+ # Display boolean mode
+ text_size = 40 if region.width >= 850 else 20
+ blf.size(0, int(round(text_size * bpy.context.preferences.view.ui_scale, 0)), 72)
+
+ draw_string(self, color2, color2, region_width - (blf.dimensions(0, BooleanMode)[0]) / 2, \
+ y_txt + bloc_height + 16, BooleanMode, 0, divide = 2)
+
+ if region.width >= 850:
+
+ if self.AskHelp is False:
+ # "H for Help" text
+ blf.size(0, int(round(13 * bpy.context.preferences.view.ui_scale, 0)), 72)
+ help_txt = "[" + self.carver_prefs.Key_Help + "] for help"
+ txt_width = blf.dimensions(0, help_txt)[0]
+ txt_height = (blf.dimensions(0, "gM")[1] * 1.45)
+
+ # Draw a rectangle and put the text "H for Help"
+ xrect = 40
+ yrect = 40
+ rect_vertices = [(xrect - 5, yrect - 5), (xrect + txt_width + 5, yrect - 5), \
+ (xrect + txt_width + 5, yrect + txt_height + 5), (xrect - 5, yrect + txt_height + 5)]
+ draw_shader(self, (0.0, 0.0, 0.0), 0.3, 'TRI_FAN', rect_vertices, self.carver_prefs.LineWidth)
+ draw_string(self, color1, color2, xrect, yrect, help_txt, 0)
+
+ else:
+ #Draw the help text
+ xHelp = 30 + t_panel_width
+ yHelp = 10
+
+ if self.ObjectMode or self.ProfileMode:
+ if self.ProfileMode:
+ help_txt = [["Object Mode", self.carver_prefs.Key_Brush]]
+ else:
+ help_txt = [["Cut Mode", self.carver_prefs.Key_Brush]]
+
+ else:
+ help_txt =[
+ ["Profil Brush", self.carver_prefs.Key_Brush],\
+ ["Move Cursor", "Ctrl + LMB"]
+ ]
+
+ if (self.ObjectMode is False) and (self.ProfileMode is False):
+ if self.CreateMode is False:
+ help_txt +=[
+ ["Create geometry", self.carver_prefs.Key_Create],\
+ ]
+ else:
+ help_txt +=[
+ ["Cut", self.carver_prefs.Key_Create],\
+ ]
+ if self.CutMode == RECTANGLE:
+ help_txt +=[
+ ["Dimension", "MouseMove"],\
+ ["Move all", "Alt"],\
+ ["Validate", "LMB"],\
+ ["Rebool", "Shift"]
+ ]
+
+ elif self.CutMode == CIRCLE:
+ help_txt +=[
+ ["Rotation and Radius", "MouseMove"],\
+ ["Move all", "Alt"],\
+ ["Subdivision", self.carver_prefs.Key_Subrem + " " + self.carver_prefs.Key_Subadd],\
+ ["Incremental rotation", "Ctrl"],\
+ ["Rebool", "Shift"]
+ ]
+
+ elif self.CutMode == LINE:
+ help_txt +=[
+ ["Dimension", "MouseMove"],\
+ ["Move all", "Alt"],\
+ ["Validate", "Space"],\
+ ["Rebool", "Shift"],\
+ ["Snap", "Ctrl"],\
+ ["Scale Snap", "WheelMouse"],\
+ ]
+ else:
+ # ObjectMode
+ help_txt +=[
+ ["Difference", "Space"],\
+ ["Rebool", "Shift + Space"],\
+ ["Duplicate", "Alt + Space"],\
+ ["Scale", self.carver_prefs.Key_Scale],\
+ ["Rotation", "LMB + Move"],\
+ ["Step Angle", "CTRL + LMB + Move"],\
+ ]
+
+ if self.ProfileMode:
+ help_txt +=[["Previous or Next Profile", self.carver_prefs.Key_Subadd + " " + self.carver_prefs.Key_Subrem]]
+
+ help_txt +=[
+ ["Create / Delete rows", chr(8597)],\
+ ["Create / Delete cols", chr(8596)],\
+ ["Gap for rows or columns", self.carver_prefs.Key_Gapy + " " + self.carver_prefs.Key_Gapx]
+ ]
+
+ blf.size(0, int(round(15 * bpy.context.preferences.view.ui_scale, 0)), 72)
+ help_txt, bloc_height, max_option, max_key, comma = get_text_info(self, context, help_txt)
+ draw_string(self, color1, color2, xHelp, yHelp, help_txt, max_option)
+
+ if self.ProfileMode:
+ xrect = region.width - t_panel_width - 80
+ yrect = 80
+ coords = [(xrect, yrect), (xrect+60, yrect), (xrect+60, yrect-60), (xrect, yrect-60)]
+
+ # Draw rectangle background in the lower right
+ draw_shader(self, (0.0, 0.0, 0.0), 0.3, 'TRI_FAN', coords, size=self.carver_prefs.LineWidth)
+
+ # Use numpy to get the vertices and indices of the profile object to draw
+ WidthProfil = 50
+ location = Vector((region.width - t_panel_width - WidthProfil, 50, 0))
+ ProfilScale = 20.0
+ coords = []
+ mesh = bpy.data.meshes[self.Profils[self.nProfil][0]]
+ mesh.calc_loop_triangles()
+ vertices = np.empty((len(mesh.vertices), 3), 'f')
+ indices = np.empty((len(mesh.loop_triangles), 3), 'i')
+ mesh.vertices.foreach_get("co", np.reshape(vertices, len(mesh.vertices) * 3))
+ mesh.loop_triangles.foreach_get("vertices", np.reshape(indices, len(mesh.loop_triangles) * 3))
+
+ for idx, vals in enumerate(vertices):
+ coords.append([
+ vals[0] * ProfilScale + location.x,
+ vals[1] * ProfilScale + location.y,
+ vals[2] * ProfilScale + location.z
+ ])
+
+ #Draw the silhouette of the mesh
+ draw_shader(self, UIColor, 0.5, 'TRIS', coords, size=self.carver_prefs.LineWidth, indices=indices)
+
+
+ if self.CutMode:
+
+ if len(self.mouse_path) > 1:
+ x0 = self.mouse_path[0][0]
+ y0 = self.mouse_path[0][1]
+ x1 = self.mouse_path[1][0]
+ y1 = self.mouse_path[1][1]
+
+ # Cut rectangle
+ if self.CutType == RECTANGLE:
+ coords = [
+ (x0 + self.xpos, y0 + self.ypos), (x1 + self.xpos, y0 + self.ypos), \
+ (x1 + self.xpos, y1 + self.ypos), (x0 + self.xpos, y1 + self.ypos)
+ ]
+ indices = ((0, 1, 2), (2, 0, 3))
+
+ self.rectangle_coord = coords
+
+ draw_shader(self, UIColor, 1, 'LINE_LOOP', coords, size=self.carver_prefs.LineWidth)
+
+ #Draw points
+ draw_shader(self, UIColor, 1, 'POINTS', coords, size=3)
+
+ if self.shift or self.CreateMode:
+ draw_shader(self, UIColor, 0.5, 'TRIS', coords, size=self.carver_prefs.LineWidth, indices=indices)
+
+ # Draw grid (based on the overlay options) to show the incremental snapping
+ if self.ctrl:
+ mini_grid(self, context, UIColor)
+
+ # Cut Line
+ elif self.CutType == LINE:
+ coords = []
+ indices = []
+ top_grid = False
+
+ for idx, vals in enumerate(self.mouse_path):
+ coords.append([vals[0] + self.xpos, vals[1] + self.ypos])
+ indices.append([idx])
+
+ # Draw lines
+ if self.Closed:
+ draw_shader(self, UIColor, 1.0, 'LINE_LOOP', coords, size=self.carver_prefs.LineWidth)
+ else:
+ draw_shader(self, UIColor, 1.0, 'LINE_STRIP', coords, size=self.carver_prefs.LineWidth)
+
+ # Draw points
+ draw_shader(self, UIColor, 1.0, 'POINTS', coords, size=3)
+
+ # Draw polygon
+ if (self.shift) or (self.CreateMode and self.Closed):
+ draw_shader(self, UIColor, 0.5, 'TRI_FAN', coords, size=self.carver_prefs.LineWidth)
+
+ # Draw grid (based on the overlay options) to show the incremental snapping
+ if self.ctrl:
+ mini_grid(self, context, UIColor)
+
+ # Circle Cut
+ elif self.CutType == CIRCLE:
+ # Create a circle using a tri fan
+ tris_coords, indices = draw_circle(self, x0, y0)
+
+ # Remove the vertex in the center to get the outer line of the circle
+ line_coords = tris_coords[1:]
+ draw_shader(self, UIColor, 1.0, 'LINE_LOOP', line_coords, size=self.carver_prefs.LineWidth)
+
+ if self.shift or self.CreateMode:
+ draw_shader(self, UIColor, 0.5, 'TRIS', tris_coords, size=self.carver_prefs.LineWidth, indices=indices)
+
+ if (self.ObjectMode or self.ProfileMode) and len(self.CurrentSelection) > 0:
+ if self.ShowCursor:
+ region = context.region
+ rv3d = context.space_data.region_3d
+
+ if self.ObjectMode:
+ ob = self.ObjectBrush
+ if self.ProfileMode:
+ ob = self.ProfileBrush
+ mat = ob.matrix_world
+
+ # 50% alpha, 2 pixel width line
+ bgl.glEnable(bgl.GL_BLEND)
+
+ bbox = [mat @ Vector(b) for b in ob.bound_box]
+ objBBDiagonal = objDiagonal(self.CurrentSelection[0])
+
+ if self.shift:
+ gl_size = 4
+ UIColor = (0.5, 1.0, 0.0, 1.0)
+ else:
+ gl_size = 2
+ UIColor = (1.0, 0.8, 0.0, 1.0)
+
+ line_coords = []
+ idx = 0
+ CRadius = ((bbox[7] - bbox[0]).length) / 2
+ for i in range(int(len(self.CLR_C) / 3)):
+ vector3d = (self.CLR_C[idx * 3] * CRadius + self.CurLoc.x, \
+ self.CLR_C[idx * 3 + 1] * CRadius + self.CurLoc.y, \
+ self.CLR_C[idx * 3 + 2] * CRadius + self.CurLoc.z)
+ vector2d = bpy_extras.view3d_utils.location_3d_to_region_2d(region, rv3d, vector3d)
+ if vector2d is not None:
+ line_coords.append((vector2d[0], vector2d[1]))
+ idx += 1
+ if len(line_coords) > 0 :
+ draw_shader(self, UIColor, 1.0, 'LINE_LOOP', line_coords, size=gl_size)
+
+ # Object display
+ if self.quat_rot is not None:
+ ob.location = self.CurLoc
+ v = Vector()
+ v.x = v.y = 0.0
+ v.z = self.BrushDepthOffset
+ ob.location += self.quat_rot @ v
+
+ e = Euler()
+ e.x = 0.0
+ e.y = 0.0
+ e.z = self.aRotZ / 25.0
+
+ qe = e.to_quaternion()
+ qRot = self.quat_rot @ qe
+ ob.rotation_mode = 'QUATERNION'
+ ob.rotation_quaternion = qRot
+ ob.rotation_mode = 'XYZ'
+
+ if self.ProfileMode:
+ if self.ProfileBrush is not None:
+ self.ProfileBrush.location = self.CurLoc
+ self.ProfileBrush.rotation_mode = 'QUATERNION'
+ self.ProfileBrush.rotation_quaternion = qRot
+ self.ProfileBrush.rotation_mode = 'XYZ'
+
+ # Opengl defaults
+ bgl.glLineWidth(1)
+ bgl.glDisable(bgl.GL_BLEND)
diff --git a/object_carver/carver_operator.py b/object_carver/carver_operator.py
new file mode 100644
index 00000000..fa40380f
--- /dev/null
+++ b/object_carver/carver_operator.py
@@ -0,0 +1,1347 @@
+import bpy
+import bpy_extras
+import sys
+from bpy.props import (
+ BoolProperty,
+ IntProperty,
+ PointerProperty,
+ StringProperty,
+ EnumProperty,
+ )
+from mathutils import (
+ Vector,
+ )
+
+from bpy_extras.view3d_utils import (
+ region_2d_to_vector_3d,
+ region_2d_to_origin_3d,
+ region_2d_to_location_3d,
+ location_3d_to_region_2d,
+)
+from .carver_profils import (
+ Profils
+ )
+
+from .carver_utils import (
+ duplicateObject,
+ UndoListUpdate,
+ createMeshFromData,
+ SelectObject,
+ Selection_Save_Restore,
+ Selection_Save,
+ Selection_Restore,
+ update_grid,
+ objDiagonal,
+ Undo,
+ UndoAdd,
+ Pick,
+ rot_axis_quat,
+ MoveCursor,
+ Picking,
+ CreateCutSquare,
+ CreateCutCircle,
+ CreateCutLine,
+ boolean_operation,
+ update_bevel,
+ CreateBevel,
+ Rebool,
+ Snap_Cursor,
+ )
+
+from .carver_draw import draw_callback_px
+
+# Modal Operator
+class CARVER_OT_operator(bpy.types.Operator):
+ bl_idname = "carver.operator"
+ bl_label = "Carver"
+ bl_description = "Cut or create Meshes in Object mode"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def __init__(self):
+ context = bpy.context
+ # Carve mode: Cut, Object, Profile
+ self.CutMode = False
+ self.CreateMode = False
+ self.ObjectMode = False
+ self.ProfileMode = False
+
+ # Create mode
+ self.ExclusiveCreateMode = False
+ if len(context.selected_objects) == 0:
+ self.ExclusiveCreateMode = True
+ self.CreateMode = True
+
+ # Cut type (Rectangle, Circle, Line)
+ self.rectangle = 0
+ self.line = 1
+ self.circle = 2
+
+ # Cut Rectangle coordinates
+ self.rectangle_coord = []
+
+ # Selected type of cut
+ self.CutType = 0
+
+ # Boolean operation
+ self.difference = 0
+ self.union = 1
+
+ self.BoolOps = self.difference
+
+ self.CurrentSelection = context.selected_objects.copy()
+ self.CurrentActive = context.active_object
+ self.all_sel_obj_list = context.selected_objects.copy()
+ self.save_active_obj = None
+
+ args = (self, context)
+ self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
+
+ self.mouse_path = [(0, 0), (0, 0)]
+
+ # Keyboard event
+ self.shift = False
+ self.ctrl = False
+ self.alt = False
+
+ self.dont_apply_boolean = context.scene.mesh_carver.DontApply
+ self.Auto_BevelUpdate = True
+
+ # Circle variables
+ self.stepAngle = [2, 4, 5, 6, 9, 10, 15, 20, 30, 40, 45, 60, 72, 90]
+ self.step = 4
+
+ # Primitives Position
+ self.xpos = 0
+ self.ypos = 0
+ self.InitPosition = False
+
+ # Close polygonal shape
+ self.Closed = False
+
+ # Depth Cursor
+ self.snapCursor = context.scene.mesh_carver.DepthCursor
+
+ # Help
+ self.AskHelp = False
+
+ # Working object
+ self.OpsObj = context.active_object
+
+ # Rebool forced (cut line)
+ self.ForceRebool = False
+
+ self.ViewVector = Vector()
+ self.CurrentObj = None
+
+ # Brush
+ self.BrushSolidify = False
+ self.WidthSolidify = False
+ self.CarveDepth = False
+ self.BrushDepth = False
+ self.BrushDepthOffset = 0.0
+ self.snap = False
+
+ self.ObjectScale = False
+
+ #Init create circle primitive
+ self.CLR_C = []
+
+ # Cursor location
+ self.CurLoc = Vector((0.0, 0.0, 0.0))
+ self.SavCurLoc = Vector((0.0, 0.0, 0.0))
+
+ # Mouse region
+ self.mouse_region = -1, -1
+ self.SavMousePos = None
+ self.xSavMouse = 0
+
+ # Scale, rotate object
+ self.ascale = 0
+ self.aRotZ = 0
+ self.nRotZ = 0
+ self.quat_rot_axis = None
+ self.quat_rot = None
+
+ self.RandomRotation = context.scene.mesh_carver.ORandom
+
+ self.ShowCursor = True
+
+ self.Instantiate = context.scene.mesh_carver.OInstanciate
+
+ self.ProfileBrush = None
+ self.ObjectBrush = None
+
+ self.InitBrush = {
+ 'location' : None,
+ 'scale' : None,
+ 'rotation_quaternion' : None,
+ 'rotation_euler' : None,
+ 'display_type' : 'WIRE',
+ 'show_in_front' : False
+ }
+
+ # Array variables
+ self.nbcol = 1
+ self.nbrow = 1
+ self.gapx = 0
+ self.gapy = 0
+ self.scale_x = 1
+ self.scale_y = 1
+ self.GridScaleX = False
+ self.GridScaleY = False
+
+ @classmethod
+ def poll(cls, context):
+ ob = None
+ if len(context.selected_objects) > 0:
+ ob = context.selected_objects[0]
+ # Test if selected object or none (for create mode)
+ return (
+ (ob and ob.type == 'MESH' and context.mode == 'OBJECT') or
+ (context.mode == 'OBJECT' and ob is None) or
+ (context.mode == 'EDIT_MESH'))
+
+ def modal(self, context, event):
+ PI = 3.14156
+ region_types = {'WINDOW', 'UI'}
+ win = context.window
+
+ # Find the limit of the view3d region
+ self.check_region(context,event)
+
+ for area in win.screen.areas:
+ if area.type in ('VIEW_3D'):
+ for region in area.regions:
+ if not region_types or region.type in region_types:
+ region.tag_redraw()
+
+ # Change the snap increment value using the wheel mouse
+ if self.CutMode:
+ if self.alt is False:
+ if self.ctrl and (self.CutType in (self.line, self.rectangle)):
+ # Get the VIEW3D area
+ for i, a in enumerate(context.screen.areas):
+ if a.type == 'VIEW_3D':
+ space = context.screen.areas[i].spaces.active
+ grid_scale = space.overlay.grid_scale
+ grid_subdivisions = space.overlay.grid_subdivisions
+
+ if event.type == 'WHEELUPMOUSE':
+ space.overlay.grid_subdivisions += 1
+ elif event.type == 'WHEELDOWNMOUSE':
+ space.overlay.grid_subdivisions -= 1
+
+ if event.type in {
+ 'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE',
+ 'NUMPAD_1', 'NUMPAD_2', 'NUMPAD_3', 'NUMPAD_4', 'NUMPAD_6',
+ 'NUMPAD_7', 'NUMPAD_8', 'NUMPAD_9', 'NUMPAD_5'}:
+ return {'PASS_THROUGH'}
+
+ try:
+ # [Shift]
+ self.shift = True if event.shift else False
+
+ # [Ctrl]
+ self.ctrl = True if event.ctrl else False
+
+ # [Alt]
+ self.alt = False
+
+ # [Alt] press : Init position variable before moving the cut brush with LMB
+ if event.alt:
+ if self.InitPosition is False:
+ self.xpos = 0
+ self.ypos = 0
+ self.last_mouse_region_x = event.mouse_region_x
+ self.last_mouse_region_y = event.mouse_region_y
+ self.InitPosition = True
+ self.alt = True
+
+ # [Alt] release : update the coordinates
+ if self.InitPosition and self.alt is False:
+ for i in range(0, len(self.mouse_path)):
+ l = list(self.mouse_path[i])
+ l[0] += self.xpos
+ l[1] += self.ypos
+ self.mouse_path[i] = tuple(l)
+
+ self.xpos = self.ypos = 0
+ self.InitPosition = False
+
+ if event.type == 'SPACE' and event.value == 'PRESS':
+ # If object or profile mode is TRUE : Confirm the cut
+ if self.ObjectMode or self.ProfileMode:
+ # If array, remove double with intersect meshes
+ if ((self.nbcol + self.nbrow) > 3):
+ # Go in edit mode mode
+ bpy.ops.object.mode_set(mode='EDIT')
+ # Remove duplicate vertices
+ bpy.ops.mesh.remove_doubles()
+ # Return in object mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ if self.alt:
+ # Save selected objects
+ self.all_sel_obj_list = context.selected_objects.copy()
+ if len(context.selected_objects) > 0:
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ if self.ObjectMode:
+ SelectObject(self, self.ObjectBrush)
+ else:
+ SelectObject(self, self.ProfileBrush)
+ duplicateObject(self)
+ else:
+ # Brush Cut
+ self.Cut()
+ # Save selected objects
+ if self.ObjectMode:
+ if len(self.ObjectBrush.children) > 0:
+ self.all_sel_obj_list = context.selected_objects.copy()
+ if len(context.selected_objects) > 0:
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ if self.ObjectMode:
+ SelectObject(self, self.ObjectBrush)
+ else:
+ SelectObject(self, self.ProfileBrush)
+ duplicateObject(self)
+
+ UndoListUpdate(self)
+
+ # Save cursor position
+ self.SavMousePos = self.CurLoc
+ else:
+ if self.CutMode is False:
+ # Cut Mode
+ self.CutType += 1
+ if self.CutType > 2:
+ self.CutType = 0
+ else:
+ if self.CutType == self.line:
+ # Cuts creation
+ CreateCutLine(self, context)
+ if self.CreateMode:
+ # Object creation
+ self.CreateGeometry()
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+ # Cursor Snap
+ context.scene.mesh_carver.DepthCursor = self.snapCursor
+ # Object Instantiate
+ context.scene.mesh_carver.OInstanciate = self.Instantiate
+ # Random rotation
+ context.scene.mesh_carver.ORandom = self.RandomRotation
+
+ return {'FINISHED'}
+ else:
+ self.Cut()
+ UndoListUpdate(self)
+
+
+#-----------------------------------------------------
+# Object creation
+#-----------------------------------------------------
+
+
+ # Object creation
+ if event.type == self.carver_prefs.Key_Create and event.value == 'PRESS':
+ if self.ExclusiveCreateMode is False:
+ self.CreateMode = not self.CreateMode
+
+ # Auto Bevel Update
+ if event.type == self.carver_prefs.Key_Update and event.value == 'PRESS':
+ self.Auto_BevelUpdate = not self.Auto_BevelUpdate
+
+ # Boolean operation type
+ if event.type == self.carver_prefs.Key_Bool and event.value == 'PRESS':
+ if (self.ProfileMode is True) or (self.ObjectMode is True):
+ if self.BoolOps == self.difference:
+ self.BoolOps = self.union
+ else:
+ self.BoolOps = self.difference
+
+ # Brush Mode
+ if event.type == self.carver_prefs.Key_Brush and event.value == 'PRESS':
+ self.dont_apply_boolean = False
+ if (self.ProfileMode is False) and (self.ObjectMode is False):
+ self.ProfileMode = True
+ else:
+ self.ProfileMode = False
+ if self.ObjectBrush is not None:
+ if self.ObjectMode is False:
+ self.ObjectMode = True
+ self.BrushSolidify = False
+ self.CList = self.OB_List
+
+ Selection_Save_Restore(self)
+ context.scene.mesh_carver.nProfile = self.nProfil
+ else:
+ self.ObjectMode = False
+ else:
+ self.BrushSolidify = False
+ Selection_Save_Restore(self)
+
+ if self.ProfileMode:
+ createMeshFromData(self)
+ self.ProfileBrush = bpy.data.objects["CT_Profil"]
+ Selection_Save(self)
+ self.BrushSolidify = True
+
+ bpy.ops.object.select_all(action='TOGGLE')
+ self.ProfileBrush.select_set(True)
+ context.view_layer.objects.active = self.ProfileBrush
+ # Set xRay
+ self.ProfileBrush.show_in_front = True
+
+ bpy.ops.object.modifier_add(type='SOLIDIFY')
+ context.object.modifiers["Solidify"].name = "CT_SOLIDIFY"
+ context.object.modifiers["CT_SOLIDIFY"].thickness = 0.1
+
+ Selection_Restore(self)
+
+ self.CList = self.CurrentSelection
+ else:
+ if self.ObjectBrush is not None:
+ if self.ObjectMode is False:
+ if self.ObjectBrush is not None:
+ self.ObjectBrush.location = self.InitBrush['location']
+ self.ObjectBrush.scale = self.InitBrush['scale']
+ self.ObjectBrush.rotation_quaternion = self.InitBrush['rotation_quaternion']
+ self.ObjectBrush.rotation_euler = self.InitBrush['rotation_euler']
+ self.ObjectBrush.display_type = self.InitBrush['display_type']
+ self.ObjectBrush.show_in_front = self.InitBrush['show_in_front']
+
+ #Store active and selected objects
+ Selection_Save(self)
+
+ #Remove Carver modifier
+ self.BrushSolidify = False
+ bpy.ops.object.select_all(action='TOGGLE')
+ self.ObjectBrush.select_set(True)
+ context.view_layer.objects.active = self.ObjectBrush
+ bpy.ops.object.modifier_remove(modifier="CT_SOLIDIFY")
+
+ #Restore selected and active object
+ Selection_Restore(self)
+ else:
+ if self.SolidifyPossible:
+ #Store active and selected objects
+ Selection_Save(self)
+ self.BrushSolidify = True
+ bpy.ops.object.select_all(action='TOGGLE')
+ self.ObjectBrush.select_set(True)
+ context.view_layer.objects.active = self.ObjectBrush
+ # Set xRay
+ self.ObjectBrush.show_in_front = True
+ bpy.ops.object.modifier_add(type='SOLIDIFY')
+ context.object.modifiers["Solidify"].name = "CT_SOLIDIFY"
+ context.object.modifiers["CT_SOLIDIFY"].thickness = 0.1
+
+ #Restore selected and active object
+ Selection_Restore(self)
+
+ # Help display
+ if event.type == self.carver_prefs.Key_Help and event.value == 'PRESS':
+ self.AskHelp = not self.AskHelp
+
+ # Instantiate object
+ if event.type == self.carver_prefs.Key_Instant and event.value == 'PRESS':
+ self.Instantiate = not self.Instantiate
+
+ # Close polygonal shape
+ if event.type == self.carver_prefs.Key_Close and event.value == 'PRESS':
+ if self.CreateMode:
+ self.Closed = not self.Closed
+
+ if event.type == self.carver_prefs.Key_Apply and event.value == 'PRESS':
+ self.dont_apply_boolean = not self.dont_apply_boolean
+
+ # Scale object
+ if event.type == self.carver_prefs.Key_Scale and event.value == 'PRESS':
+ if self.ObjectScale is False:
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+ self.ObjectScale = True
+
+ # Grid : Snap on grid
+ if event.type == self.carver_prefs.Key_Snap and event.value == 'PRESS':
+ self.snap = not self.snap
+
+ # Array : Add column
+ if event.type == 'UP_ARROW' and event.value == 'PRESS':
+ self.nbrow += 1
+ update_grid(self, context)
+
+ # Array : Delete column
+ elif event.type == 'DOWN_ARROW' and event.value == 'PRESS':
+ self.nbrow -= 1
+ update_grid(self, context)
+
+ # Array : Add row
+ elif event.type == 'RIGHT_ARROW' and event.value == 'PRESS':
+ self.nbcol += 1
+ update_grid(self, context)
+
+ # Array : Delete row
+ elif event.type == 'LEFT_ARROW' and event.value == 'PRESS':
+ self.nbcol -= 1
+ update_grid(self, context)
+
+ # Array : Scale gap between columns
+ if event.type == self.carver_prefs.Key_Gapy and event.value == 'PRESS':
+ if self.GridScaleX is False:
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+ self.GridScaleX = True
+
+ # Array : Scale gap between rows
+ if event.type == self.carver_prefs.Key_Gapx and event.value == 'PRESS':
+ if self.GridScaleY is False:
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+ self.GridScaleY = True
+
+ # Cursor depth or solidify pattern
+ if event.type == self.carver_prefs.Key_Depth and event.value == 'PRESS':
+ if (self.ObjectMode is False) and (self.ProfileMode is False):
+ self.snapCursor = not self.snapCursor
+ else:
+ # Solidify
+
+ if (self.ObjectMode or self.ProfileMode) and (self.SolidifyPossible):
+ solidify = True
+
+ if self.ObjectMode:
+ z = self.ObjectBrush.data.vertices[0].co.z
+ ErrorMarge = 0.01
+ for v in self.ObjectBrush.data.vertices:
+ if abs(v.co.z - z) > ErrorMarge:
+ solidify = False
+ self.CarveDepth = True
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+ break
+
+ if solidify:
+ if self.ObjectMode:
+ for mb in self.ObjectBrush.modifiers:
+ if mb.type == 'SOLIDIFY':
+ AlreadySoldify = True
+ else:
+ for mb in self.ProfileBrush.modifiers:
+ if mb.type == 'SOLIDIFY':
+ AlreadySoldify = True
+
+ if AlreadySoldify is False:
+ Selection_Save(self)
+ self.BrushSolidify = True
+
+ bpy.ops.object.select_all(action='TOGGLE')
+ if self.ObjectMode:
+ self.ObjectBrush.select_set(True)
+ context.view_layer.objects.active = self.ObjectBrush
+ # Active le xray
+ self.ObjectBrush.show_in_front = True
+ else:
+ self.ProfileBrush.select_set(True)
+ context.view_layer.objects.active = self.ProfileBrush
+ # Active le xray
+ self.ProfileBrush.show_in_front = True
+
+ bpy.ops.object.modifier_add(type='SOLIDIFY')
+ context.object.modifiers["Solidify"].name = "CT_SOLIDIFY"
+
+ context.object.modifiers["CT_SOLIDIFY"].thickness = 0.1
+
+ Selection_Restore(self)
+
+ self.WidthSolidify = not self.WidthSolidify
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+
+ if event.type == self.carver_prefs.Key_BrushDepth and event.value == 'PRESS':
+ if self.ObjectMode:
+ self.CarveDepth = False
+
+ self.BrushDepth = True
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+
+ # Random rotation
+ if event.type == 'R' and event.value == 'PRESS':
+ self.RandomRotation = not self.RandomRotation
+
+ # Undo
+ if event.type == 'Z' and event.value == 'PRESS':
+ if self.ctrl:
+ if (self.CutType == self.line) and (self.CutMode):
+ if len(self.mouse_path) > 1:
+ self.mouse_path[len(self.mouse_path) - 1:] = []
+ else:
+ Undo(self)
+
+ # Mouse move
+ if event.type == 'MOUSEMOVE' :
+ if self.ObjectMode or self.ProfileMode:
+ fac = 50.0
+ if self.shift:
+ fac = 500.0
+ if self.WidthSolidify:
+ if self.ObjectMode:
+ bpy.data.objects[self.ObjectBrush.name].modifiers[
+ "CT_SOLIDIFY"].thickness += (event.mouse_region_x - self.mouse_region[0]) / fac
+ elif self.ProfileMode:
+ bpy.data.objects[self.ProfileBrush.name].modifiers[
+ "CT_SOLIDIFY"].thickness += (event.mouse_region_x - self.mouse_region[0]) / fac
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+ elif self.CarveDepth:
+ for v in self.ObjectBrush.data.vertices:
+ v.co.z += (event.mouse_region_x - self.mouse_region[0]) / fac
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+ elif self.BrushDepth:
+ self.BrushDepthOffset += (event.mouse_region_x - self.mouse_region[0]) / fac
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+ else:
+ if (self.GridScaleX):
+ self.gapx += (event.mouse_region_x - self.mouse_region[0]) / 50
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+ update_grid(self, context)
+ return {'RUNNING_MODAL'}
+
+ elif (self.GridScaleY):
+ self.gapy += (event.mouse_region_x - self.mouse_region[0]) / 50
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+ update_grid(self, context)
+ return {'RUNNING_MODAL'}
+
+ elif self.ObjectScale:
+ self.ascale = -(event.mouse_region_x - self.mouse_region[0])
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+
+ if self.ObjectMode:
+ self.ObjectBrush.scale.x -= float(self.ascale) / 150.0
+ if self.ObjectBrush.scale.x <= 0.0:
+ self.ObjectBrush.scale.x = 0.0
+ self.ObjectBrush.scale.y -= float(self.ascale) / 150.0
+ if self.ObjectBrush.scale.y <= 0.0:
+ self.ObjectBrush.scale.y = 0.0
+ self.ObjectBrush.scale.z -= float(self.ascale) / 150.0
+ if self.ObjectBrush.scale.z <= 0.0:
+ self.ObjectBrush.scale.z = 0.0
+
+ elif self.ProfileMode:
+ if self.ProfileBrush is not None:
+ self.ProfileBrush.scale.x -= float(self.ascale) / 150.0
+ self.ProfileBrush.scale.y -= float(self.ascale) / 150.0
+ self.ProfileBrush.scale.z -= float(self.ascale) / 150.0
+ else:
+ if self.LMB:
+ if self.ctrl:
+ self.aRotZ = - \
+ ((int((event.mouse_region_x - self.xSavMouse) / 10.0) * PI / 4.0) * 25.0)
+ else:
+ self.aRotZ -= event.mouse_region_x - self.mouse_region[0]
+ self.ascale = 0.0
+
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+ else:
+ target_hit, target_normal, target_eul_rotation = Pick(context, event, self)
+ if target_hit is not None:
+ self.ShowCursor = True
+ up_vector = Vector((0.0, 0.0, 1.0))
+ quat_rot_axis = rot_axis_quat(up_vector, target_normal)
+ self.quat_rot = target_eul_rotation @ quat_rot_axis
+ MoveCursor(quat_rot_axis, target_hit, self)
+ self.SavCurLoc = target_hit
+ if self.ctrl:
+ if self.SavMousePos is not None:
+ xEcart = abs(self.SavMousePos.x - self.SavCurLoc.x)
+ yEcart = abs(self.SavMousePos.y - self.SavCurLoc.y)
+ zEcart = abs(self.SavMousePos.z - self.SavCurLoc.z)
+ if (xEcart > yEcart) and (xEcart > zEcart):
+ self.CurLoc = Vector(
+ (target_hit.x, self.SavMousePos.y, self.SavMousePos.z))
+ if (yEcart > xEcart) and (yEcart > zEcart):
+ self.CurLoc = Vector(
+ (self.SavMousePos.x, target_hit.y, self.SavMousePos.z))
+ if (zEcart > xEcart) and (zEcart > yEcart):
+ self.CurLoc = Vector(
+ (self.SavMousePos.x, self.SavMousePos.y, target_hit.z))
+ else:
+ self.CurLoc = target_hit
+ else:
+ self.CurLoc = target_hit
+ else:
+ if self.CutMode:
+ if self.alt is False:
+ if self.ctrl :
+ # Find the closest position on the overlay grid and snap the mouse on it
+ # Draw a mini grid around the cursor
+ mouse_pos = [[event.mouse_region_x, event.mouse_region_y]]
+ Snap_Cursor(self, context, event, mouse_pos)
+
+ else:
+ if len(self.mouse_path) > 0:
+ self.mouse_path[len(self.mouse_path) -
+ 1] = (event.mouse_region_x, event.mouse_region_y)
+ else:
+ # [ALT] press, update position
+ self.xpos += (event.mouse_region_x - self.last_mouse_region_x)
+ self.ypos += (event.mouse_region_y - self.last_mouse_region_y)
+
+ self.last_mouse_region_x = event.mouse_region_x
+ self.last_mouse_region_y = event.mouse_region_y
+
+ elif event.type == 'LEFTMOUSE' and event.value == 'PRESS':
+ if self.ObjectMode or self.ProfileMode:
+ if self.LMB is False:
+ target_hit, target_normal, target_eul_rotation = Pick(context, event, self)
+ if target_hit is not None:
+ up_vector = Vector((0.0, 0.0, 1.0))
+ self.quat_rot_axis = rot_axis_quat(up_vector, target_normal)
+ self.quat_rot = target_eul_rotation @ self.quat_rot_axis
+ self.mouse_region = event.mouse_region_x, event.mouse_region_y
+ self.xSavMouse = event.mouse_region_x
+
+ if self.ctrl:
+ self.nRotZ = int((self.aRotZ / 25.0) / (PI / 4.0))
+ self.aRotZ = self.nRotZ * (PI / 4.0) * 25.0
+
+ self.LMB = True
+
+ # LEFTMOUSE
+ elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE' and self.in_view_3d:
+ if self.ObjectMode or self.ProfileMode:
+ # Rotation and scale
+ self.LMB = False
+ if self.ObjectScale is True:
+ self.ObjectScale = False
+
+ if self.GridScaleX is True:
+ self.GridScaleX = False
+
+ if self.GridScaleY is True:
+ self.GridScaleY = False
+
+ if self.WidthSolidify:
+ self.WidthSolidify = False
+
+ if self.CarveDepth is True:
+ self.CarveDepth = False
+
+ if self.BrushDepth is True:
+ self.BrushDepth = False
+
+ else:
+ if self.CutMode is False:
+ if self.ctrl:
+ Picking(context, event)
+
+ else:
+
+ if self.CutType == self.line:
+ if self.CutMode is False:
+ self.mouse_path.clear()
+ self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
+ self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
+ else:
+ self.mouse_path[0] = (event.mouse_region_x, event.mouse_region_y)
+ self.mouse_path[1] = (event.mouse_region_x, event.mouse_region_y)
+ self.CutMode = True
+ else:
+ if self.CutType != self.line:
+ # Cut creation
+ if self.CutType == self.rectangle:
+ CreateCutSquare(self, context)
+ if self.CutType == self.circle:
+ CreateCutCircle(self, context)
+ if self.CutType == self.line:
+ CreateCutLine(self, context)
+
+ if self.CreateMode:
+ # Object creation
+ self.CreateGeometry()
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+ # Depth Cursor
+ context.scene.mesh_carver.DepthCursor = self.snapCursor
+ # Instantiate object
+ context.scene.mesh_carver.OInstanciate = self.Instantiate
+ # Random rotation
+ context.scene.mesh_carver.ORandom = self.RandomRotation
+ # Apply operation
+ context.scene.mesh_carver.DontApply = self.dont_apply_boolean
+
+ # if Object mode, set initiale state
+ if self.ObjectBrush is not None:
+ self.ObjectBrush.location = self.InitBrush['location']
+ self.ObjectBrush.scale = self.InitBrush['scale']
+ self.ObjectBrush.rotation_quaternion = self.InitBrush['rotation_quaternion']
+ self.ObjectBrush.rotation_euler = self.InitBrush['rotation_euler']
+ self.ObjectBrush.display_type = self.InitBrush['display_type']
+ self.ObjectBrush.show_in_front = self.InitBrush['show_in_front']
+
+ # remove solidify
+ Selection_Save(self)
+ self.BrushSolidify = False
+
+ bpy.ops.object.select_all(action='TOGGLE')
+ self.ObjectBrush.select_set(True)
+ context.view_layer.objects.active = self.ObjectBrush
+
+ bpy.ops.object.modifier_remove(modifier="CT_SOLIDIFY")
+
+ Selection_Restore(self)
+
+ context.scene.mesh_carver.nProfile = self.nProfil
+
+ return {'FINISHED'}
+ else:
+ self.Cut()
+ UndoListUpdate(self)
+ else:
+ # Line
+ self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
+
+ # Change brush profil or circle subdivisions
+ elif (event.type == 'COMMA' and event.value == 'PRESS') or \
+ (event.type == self.carver_prefs.Key_Subrem and event.value == 'PRESS'):
+ # Brush profil
+ if self.ProfileMode:
+ self.nProfil += 1
+ if self.nProfil >= self.MaxProfil:
+ self.nProfil = 0
+ createMeshFromData(self)
+ # Circle subdivisions
+ if self.CutType == self.circle:
+ self.step += 1
+ if self.step >= len(self.stepAngle):
+ self.step = len(self.stepAngle) - 1
+ # Change brush profil or circle subdivisions
+ elif (event.type == 'PERIOD' and event.value == 'PRESS') or \
+ (event.type == self.carver_prefs.Key_Subadd and event.value == 'PRESS'):
+ # Brush profil
+ if self.ProfileMode:
+ self.nProfil -= 1
+ if self.nProfil < 0:
+ self.nProfil = self.MaxProfil - 1
+ createMeshFromData(self)
+ # Circle subdivisions
+ if self.CutType == self.circle:
+ if self.step > 0:
+ self.step -= 1
+ # Quit
+ elif event.type in {'RIGHTMOUSE', 'ESC'}:
+ # Depth Cursor
+ context.scene.mesh_carver.DepthCursor = self.snapCursor
+ # Instantiate object
+ context.scene.mesh_carver.OInstanciate = self.Instantiate
+ # Random Rotation
+ context.scene.mesh_carver.ORandom = self.RandomRotation
+ # Apply boolean operation
+ context.scene.mesh_carver.DontApply = self.dont_apply_boolean
+
+ # Reset Object
+ if self.ObjectBrush is not None:
+ self.ObjectBrush.location = self.InitBrush['location']
+ self.ObjectBrush.scale = self.InitBrush['scale']
+ self.ObjectBrush.rotation_quaternion = self.InitBrush['rotation_quaternion']
+ self.ObjectBrush.rotation_euler = self.InitBrush['rotation_euler']
+ self.ObjectBrush.display_type = self.InitBrush['display_type']
+ self.ObjectBrush.show_in_front = self.InitBrush['show_in_front']
+
+ # Remove solidify modifier
+ Selection_Save(self)
+ self.BrushSolidify = False
+
+ bpy.ops.object.select_all(action='TOGGLE')
+ self.ObjectBrush.select_set(True)
+ context.view_layer.objects.active = self.ObjectBrush
+
+ bpy.ops.object.modifier_remove(modifier="CT_SOLIDIFY")
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ Selection_Restore(self)
+
+ Selection_Save_Restore(self)
+ context.view_layer.objects.active = self.CurrentActive
+ context.scene.mesh_carver.nProfile = self.nProfil
+
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+
+ # Remove Copy Object Brush
+ if bpy.data.objects.get("CarverBrushCopy") is not None:
+ brush = bpy.data.objects["CarverBrushCopy"]
+ self.ObjectBrush.data = bpy.data.meshes[brush.data.name]
+ bpy.ops.object.select_all(action='DESELECT')
+ bpy.data.objects["CarverBrushCopy"].select_set(True)
+ bpy.ops.object.delete()
+
+ return {'FINISHED'}
+
+ return {'RUNNING_MODAL'}
+
+ except:
+ print("\n[Carver MT ERROR]\n")
+ import traceback
+ traceback.print_exc()
+
+ context.window.cursor_modal_set("DEFAULT")
+ context.area.header_text_set(None)
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+
+ self.report({'WARNING'},
+ "Operation finished. Failure during Carving (Check the console for more info)")
+
+ return {'FINISHED'}
+
+ def cancel(self, context):
+ # Note: used to prevent memory leaks on quitting Blender while the modal operator
+ # is still running, gets called on return {"CANCELLED"}
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+
+ def invoke(self, context, event):
+ if context.area.type != 'VIEW_3D':
+ self.report({'WARNING'},
+ "View3D not found or not currently active. Operation Cancelled")
+ self.cancel(context)
+ return {'CANCELLED'}
+
+ # test if some other object types are selected that are not meshes
+ for obj in context.selected_objects:
+ if obj.type != "MESH":
+ self.report({'WARNING'},
+ "Some selected objects are not of the Mesh type. Operation Cancelled")
+ self.cancel(context)
+ return {'CANCELLED'}
+
+ if context.mode == 'EDIT_MESH':
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ #Load the Carver preferences
+ self.carver_prefs = bpy.context.preferences.addons[__package__].preferences
+
+ # Get default patterns
+ self.Profils = []
+ for p in Profils:
+ self.Profils.append((p[0], p[1], p[2], p[3]))
+
+ for o in context.scene.objects:
+ if not o.name.startswith(self.carver_prefs.ProfilePrefix):
+ continue
+ # In-scene profiles may have changed, remove them to refresh
+ for m in bpy.data.meshes:
+ if m.name.startswith(self.carver_prefs.ProfilePrefix):
+ bpy.data.meshes.remove(m)
+
+ vertices = []
+ for v in o.data.vertices:
+ vertices.append((v.co.x, v.co.y, v.co.z))
+
+ faces = []
+ for f in o.data.polygons:
+ face = []
+ for v in f.vertices:
+ face.append(v)
+
+ faces.append(face)
+
+ self.Profils.append(
+ (o.name,
+ Vector((o.location.x, o.location.y, o.location.z)),
+ vertices, faces)
+ )
+
+ self.nProfil = context.scene.mesh_carver.nProfile
+ self.MaxProfil = len(self.Profils)
+
+
+ # reset selected profile if last profile exceeds length of array
+ if self.nProfil >= self.MaxProfil:
+ self.nProfil = context.scene.mesh_carver.nProfile = 0
+
+ if len(context.selected_objects) > 1:
+ self.ObjectBrush = context.active_object
+
+ # Copy the brush object
+ ob = bpy.data.objects.new("CarverBrushCopy", context.object.data.copy())
+ ob.location = self.ObjectBrush.location
+ context.collection.objects.link(ob)
+ context.scene.update()
+
+ # Save default variables
+ self.InitBrush['location'] = self.ObjectBrush.location.copy()
+ self.InitBrush['scale'] = self.ObjectBrush.scale.copy()
+ self.InitBrush['rotation_quaternion'] = self.ObjectBrush.rotation_quaternion.copy()
+ self.InitBrush['rotation_euler'] = self.ObjectBrush.rotation_euler.copy()
+ self.InitBrush['display_type'] = self.ObjectBrush.display_type
+ self.InitBrush['show_in_front'] = self.ObjectBrush.show_in_front
+
+ # Test if flat object
+ z = self.ObjectBrush.data.vertices[0].co.z
+ ErrorMarge = 0.01
+ self.SolidifyPossible = True
+ for v in self.ObjectBrush.data.vertices:
+ if abs(v.co.z - z) > ErrorMarge:
+ self.SolidifyPossible = False
+ break
+
+ self.CList = []
+ self.OPList = []
+ self.RList = []
+ self.OB_List = []
+
+ for obj in context.selected_objects:
+ if obj != self.ObjectBrush:
+ self.OB_List.append(obj)
+
+ # Left button
+ self.LMB = False
+
+ # Undo Variables
+ self.undo_index = 0
+ self.undo_limit = context.preferences.edit.undo_steps
+ self.undo_list = []
+
+ # Boolean operations type
+ self.BooleanType = 0
+
+ self.UList = []
+ self.UList_Index = -1
+ self.UndoOps = []
+
+ context.window_manager.modal_handler_add(self)
+ return {'RUNNING_MODAL'}
+
+ #Get the region area where the operator is used
+ def check_region(self,context,event):
+ if context.area != None:
+ if context.area.type == "VIEW_3D" :
+ for region in context.area.regions:
+ if region.type == "TOOLS":
+ t_panel = region
+ elif region.type == "UI":
+ ui_panel = region
+
+ view_3d_region_x = Vector((context.area.x + t_panel.width, context.area.x + context.area.width - ui_panel.width))
+ view_3d_region_y = Vector((context.region.y, context.region.y+context.region.height))
+
+ if (event.mouse_x > view_3d_region_x[0] and event.mouse_x < view_3d_region_x[1] \
+ and event.mouse_y > view_3d_region_y[0] and event.mouse_y < view_3d_region_y[1]):
+ self.in_view_3d = True
+ else:
+ self.in_view_3d = False
+ else:
+ self.in_view_3d = False
+
+ def CreateGeometry(self):
+ context = bpy.context
+ in_local_view = False
+
+ for area in context.screen.areas:
+ if area.type == 'VIEW_3D':
+ if area.spaces[0].local_view is not None:
+ in_local_view = True
+
+ if in_local_view:
+ bpy.ops.view3d.localview()
+
+ if self.ExclusiveCreateMode:
+ # Default width
+ objBBDiagonal = 0.5
+ else:
+ ActiveObj = self.CurrentSelection[0]
+ if ActiveObj is not None:
+ # Object dimensions
+ objBBDiagonal = objDiagonal(ActiveObj) / 4
+ subdivisions = 2
+
+ if len(context.selected_objects) > 0:
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ context.view_layer.objects.active = self.CurrentObj
+
+ bpy.data.objects[self.CurrentObj.name].select_set(True)
+ bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.select_mode(type="EDGE")
+ if self.snapCursor is False:
+ bpy.ops.transform.translate(value=self.ViewVector * objBBDiagonal * subdivisions)
+ bpy.ops.mesh.extrude_region_move(
+ TRANSFORM_OT_translate={"value": -self.ViewVector * objBBDiagonal * subdivisions * 2})
+
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.normals_make_consistent()
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ saved_location_0 = context.scene.cursor.location.copy()
+ bpy.ops.view3d.snap_cursor_to_active()
+ saved_location = context.scene.cursor.location.copy()
+ bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
+ context.scene.cursor.location = saved_location
+ bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
+ context.scene.cursor.location = saved_location_0
+
+ bpy.data.objects[self.CurrentObj.name].select_set(True)
+ bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
+
+ for o in self.all_sel_obj_list:
+ bpy.data.objects[o.name].select_set(True)
+
+ if in_local_view:
+ bpy.ops.view3d.localview()
+
+ self.CutMode = False
+ self.mouse_path.clear()
+ self.mouse_path = [(0, 0), (0, 0)]
+
+ def Cut(self):
+ context = bpy.context
+
+ # Local view ?
+ in_local_view = False
+ for area in context.screen.areas:
+ if area.type == 'VIEW_3D':
+ if area.spaces[0].local_view is not None:
+ in_local_view = True
+
+ if in_local_view:
+ bpy.ops.view3d.localview()
+
+ # Save cursor position
+ CursorLocation = context.scene.cursor.location.copy()
+
+ #List of selected objects
+ selected_obj_list = []
+
+ #Cut Mode with line
+ if (self.ObjectMode is False) and (self.ProfileMode is False):
+
+ #Compute the bounding Box
+ objBBDiagonal = objDiagonal(self.CurrentSelection[0])
+ if self.dont_apply_boolean:
+ subdivisions = 1
+ else:
+ subdivisions = 32
+
+ # Get selected objects
+ selected_obj_list = context.selected_objects.copy()
+
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ context.view_layer.objects.active = self.CurrentObj
+
+ bpy.data.objects[self.CurrentObj.name].select_set(True)
+ bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.select_mode(type="EDGE")
+ #Translate the created mesh away from the view
+ if (self.snapCursor is False) or (self.ForceRebool):
+ bpy.ops.transform.translate(value=self.ViewVector * objBBDiagonal * subdivisions)
+ #Extrude the mesh region and move the result
+ bpy.ops.mesh.extrude_region_move(
+ TRANSFORM_OT_translate={"value": -self.ViewVector * objBBDiagonal * subdivisions * 2})
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.normals_make_consistent()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ else:
+ # Create list
+ if self.ObjectMode:
+ for o in self.CurrentSelection:
+ if o != self.ObjectBrush:
+ selected_obj_list.append(o)
+ self.CurrentObj = self.ObjectBrush
+ else:
+ selected_obj_list = self.CurrentSelection
+ self.CurrentObj = self.ProfileBrush
+
+ for obj in self.CurrentSelection:
+ UndoAdd(self, "MESH", obj)
+
+ # List objects create with rebool
+ lastSelected = []
+
+ for ActiveObj in selected_obj_list:
+ context.scene.cursor.location = CursorLocation
+
+ if len(context.selected_objects) > 0:
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ # Select cut object
+ bpy.data.objects[self.CurrentObj.name].select_set(True)
+ context.view_layer.objects.active = self.CurrentObj
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Select object to cut
+ bpy.data.objects[ActiveObj.name].select_set(True)
+ context.view_layer.objects.active = ActiveObj
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='DESELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Boolean operation
+ if (self.shift is False) and (self.ForceRebool is False):
+ if self.ObjectMode or self.ProfileMode:
+ if self.BoolOps == self.union:
+ boolean_operation(bool_type="UNION")
+ else:
+ boolean_operation(bool_type="DIFFERENCE")
+ else:
+ boolean_operation(bool_type="DIFFERENCE")
+
+ # Apply booleans
+ if self.dont_apply_boolean is False:
+ BMname = "CT_" + self.CurrentObj.name
+ for mb in ActiveObj.modifiers:
+ if (mb.type == 'BOOLEAN') and (mb.name == BMname):
+ try:
+ bpy.ops.object.modifier_apply(apply_as='DATA', modifier=BMname)
+ except:
+ bpy.ops.object.modifier_remove(modifier=BMname)
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ self.report({'ERROR'}, str(exc_value))
+
+ bpy.ops.object.select_all(action='TOGGLE')
+ else:
+ if self.ObjectMode or self.ProfileMode:
+ for mb in self.CurrentObj.modifiers:
+ if (mb.type == 'SOLIDIFY') and (mb.name == "CT_SOLIDIFY"):
+ try:
+ bpy.ops.object.modifier_apply(apply_as='DATA', modifier="CT_SOLIDIFY")
+ except:
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ self.report({'ERROR'}, str(exc_value))
+
+ # Rebool
+ Rebool(context, self)
+
+ # Test if not empty object
+ if context.selected_objects[0]:
+ rebool_RT = context.selected_objects[0]
+ if len(rebool_RT.data.vertices) > 0:
+ # Create Bevel for new objects
+ CreateBevel(context, context.selected_objects[0])
+
+ UndoAdd(self, "REBOOL", context.selected_objects[0])
+
+ context.scene.cursor.location = ActiveObj.location
+ bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
+ else:
+ bpy.ops.object.delete(use_global=False)
+
+ context.scene.cursor.location = CursorLocation
+
+ if self.ObjectMode:
+ context.view_layer.objects.active = self.ObjectBrush
+ if self.ProfileMode:
+ context.view_layer.objects.active = self.ProfileBrush
+
+ if self.dont_apply_boolean is False:
+ # Apply booleans
+ BMname = "CT_" + self.CurrentObj.name
+ for mb in ActiveObj.modifiers:
+ if (mb.type == 'BOOLEAN') and (mb.name == BMname):
+ try:
+ bpy.ops.object.modifier_apply(apply_as='DATA', modifier=BMname)
+ except:
+ bpy.ops.object.modifier_remove(modifier=BMname)
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ self.report({'ERROR'}, str(exc_value))
+ # Get new objects created with rebool operations
+ if len(context.selected_objects) > 0:
+ if self.shift is True:
+ # Get the last object selected
+ lastSelected.append(context.selected_objects[0])
+
+ context.scene.cursor.location = CursorLocation
+
+ if self.dont_apply_boolean is False:
+ # Remove cut object
+ if (self.ObjectMode is False) and (self.ProfileMode is False):
+ if len(context.selected_objects) > 0:
+ bpy.ops.object.select_all(action='TOGGLE')
+ bpy.data.objects[self.CurrentObj.name].select_set(True)
+ bpy.ops.object.delete(use_global=False)
+ else:
+ if self.ObjectMode:
+ self.ObjectBrush.display_type = self.InitBrush['display_type']
+
+ if len(context.selected_objects) > 0:
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ # Select cut objects
+ for obj in lastSelected:
+ bpy.data.objects[obj.name].select_set(True)
+
+ for ActiveObj in selected_obj_list:
+ bpy.data.objects[ActiveObj.name].select_set(True)
+ context.view_layer.objects.active = ActiveObj
+ # Update bevel
+ list_act_obj = context.selected_objects.copy()
+ if self.Auto_BevelUpdate:
+ update_bevel(context)
+
+ # Re-select initial objects
+ bpy.ops.object.select_all(action='TOGGLE')
+ if self.ObjectMode:
+ # Re-select brush
+ self.ObjectBrush.select_set(True)
+ for ActiveObj in selected_obj_list:
+ bpy.data.objects[ActiveObj.name].select_set(True)
+ context.view_layer.objects.active = ActiveObj
+
+ # If object has children, set "Wire" draw type
+ if self.ObjectBrush is not None:
+ if len(self.ObjectBrush.children) > 0:
+ self.ObjectBrush.display_type = "WIRE"
+ if self.ProfileMode:
+ self.ProfileBrush.display_type = "WIRE"
+
+ if in_local_view:
+ bpy.ops.view3d.localview()
+
+ # Reset variables
+ self.CutMode = False
+ self.mouse_path.clear()
+ self.mouse_path = [(0, 0), (0, 0)]
+
+ self.ForceRebool = False
+
+ # bpy.ops.mesh.customdata_custom_splitnormals_clear()
+
+
+class CarverProperties(bpy.types.PropertyGroup):
+ DepthCursor: BoolProperty(
+ name="DepthCursor",
+ default=False
+ )
+ OInstanciate: BoolProperty(
+ name="Obj_Instantiate",
+ default=False
+ )
+ ORandom: BoolProperty(
+ name="Random_Rotation",
+ default=False
+ )
+ DontApply: BoolProperty(
+ name="Dont_Apply",
+ default=False
+ )
+ nProfile: IntProperty(
+ name="Num_Profile",
+ default=0
+ )
+
+
+def register():
+ from bpy.utils import register_class
+ bpy.utils.register_class(CARVER_OT_operator)
+ bpy.utils.register_class(CarverProperties)
+ bpy.types.Scene.mesh_carver = bpy.props.PointerProperty(type=CarverProperties)
+
+def unregister():
+ from bpy.utils import unregister_class
+ bpy.utils.unregister_class(CarverProperties)
+ bpy.utils.unregister_class(CARVER_OT_operator)
+ del bpy.types.Scene.mesh_carver
diff --git a/object_carver/carver_preferences.py b/object_carver/carver_preferences.py
new file mode 100644
index 00000000..b7131193
--- /dev/null
+++ b/object_carver/carver_preferences.py
@@ -0,0 +1,194 @@
+import bpy
+from bpy.props import (
+ BoolProperty,
+ IntProperty,
+ PointerProperty,
+ StringProperty,
+)
+
+class CarverPrefs(bpy.types.AddonPreferences):
+ bl_idname = __name__
+
+ Enable_Tab_01: BoolProperty(
+ name="Info",
+ description="Some general information and settings about the add-on",
+ default=False
+ )
+ Enable_Tab_02: BoolProperty(
+ name="Hotkeys",
+ description="List of the shortcuts used during carving",
+ default=False
+ )
+ bpy.types.Scene.Key_Create: StringProperty(
+ name="Object creation",
+ description="Object creation",
+ maxlen=1,
+ default="C"
+ )
+ bpy.types.Scene.Key_Update: StringProperty(
+ name="Auto Bevel Update",
+ description="Auto Bevel Update",
+ maxlen=1,
+ default="A",
+ )
+ bpy.types.Scene.Key_Bool: StringProperty(
+ name="Boolean type",
+ description="Boolean operation type",
+ maxlen=1,
+ default="T",
+ )
+ bpy.types.Scene.Key_Brush: StringProperty(
+ name="Brush Mode",
+ description="Brush Mode",
+ maxlen=1,
+ default="B",
+ )
+ bpy.types.Scene.Key_Help: StringProperty(
+ name="Help display",
+ description="Help display",
+ maxlen=1,
+ default="H",
+ )
+ bpy.types.Scene.Key_Instant: StringProperty(
+ name="Instantiate",
+ description="Instantiate object",
+ maxlen=1,
+ default="I",
+ )
+ bpy.types.Scene.Key_Close: StringProperty(
+ name="Close polygonal shape",
+ description="Close polygonal shape",
+ maxlen=1,
+ default="X",
+ )
+ bpy.types.Scene.Key_Apply: StringProperty(
+ name="Apply operation",
+ description="Apply operation",
+ maxlen=1,
+ default="Q",
+ )
+ bpy.types.Scene.Key_Scale: StringProperty(
+ name="Scale object",
+ description="Scale object",
+ maxlen=1,
+ default="S",
+ )
+ bpy.types.Scene.Key_Gapy: StringProperty(
+ name="Gap rows",
+ description="Scale gap between columns",
+ maxlen=1,
+ default="J",
+ )
+ bpy.types.Scene.Key_Gapx: StringProperty(
+ name="Gap columns",
+ description="Scale gap between columns",
+ maxlen=1,
+ default="U",
+ )
+ bpy.types.Scene.Key_Depth: StringProperty(
+ name="Depth",
+ description="Cursor depth or solidify pattern",
+ maxlen=1,
+ default="D",
+ )
+ bpy.types.Scene.Key_BrushDepth: StringProperty(
+ name="Brush Depth",
+ description="Brush depth",
+ maxlen=1,
+ default="C",
+ )
+ bpy.types.Scene.Key_Subadd: StringProperty(
+ name="Add subdivision",
+ description="Add subdivision",
+ maxlen=1,
+ default="X",
+ )
+ bpy.types.Scene.Key_Subrem: StringProperty(
+ name="Remove subdivision",
+ description="Remove subdivision",
+ maxlen=1,
+ default="W",
+ )
+ bpy.types.Scene.Key_Randrot: StringProperty(
+ name="Random rotation",
+ description="Random rotation",
+ maxlen=1,
+ default="R",
+ )
+ bpy.types.Scene.ProfilePrefix: StringProperty(
+ name="Profile prefix",
+ description="Prefix to look for profiles with",
+ default="Carver_Profile-"
+ )
+
+ def draw(self, context):
+ scene = context.scene
+ layout = self.layout
+ print("DRAW !")
+
+ icon_1 = "TRIA_RIGHT" if not self.Enable_Tab_01 else "TRIA_DOWN"
+ box = layout.box()
+
+ box.prop(self, "Enable_Tab_01", text="Info and Settings", emboss=False, icon=icon_1)
+ if self.Enable_Tab_01:
+ box.label(text="Carver Operator:", icon="LAYER_ACTIVE")
+ box.label(text="Select a Mesh Object and press [CTRL]+[SHIFT]+[X] to carve",
+ icon="LAYER_USED")
+ box.label(text="To finish carving press [ESC] or [RIGHT CLICK]",
+ icon="LAYER_USED")
+ box.prop(scene, "ProfilePrefix", text="Profile prefix")
+
+ icon_2 = "TRIA_RIGHT" if not self.Enable_Tab_02 else "TRIA_DOWN"
+ box = layout.box()
+ box.prop(self, "Enable_Tab_02", text="Keys", emboss=False, icon=icon_2)
+ if self.Enable_Tab_02:
+ split = box.split(align=True)
+ box = split.box()
+ col = box.column(align=True)
+ col.label(text="Object Creation:")
+ col.prop(scene, "Key_Create", text="")
+ col.label(text="Auto bevel update:")
+ col.prop(scene, "Key_Update", text="")
+ col.label(text="Boolean operation type:")
+ col.prop(scene, "Key_Bool", text="")
+ col.label(text="Brush Depth:")
+ col.prop(scene, "Key_BrushDepth", text="")
+
+ box = split.box()
+ col = box.column(align=True)
+ col.label(text="Brush Mode:")
+ col.prop(scene, "Key_Brush", text="")
+ col.label(text="Help display:")
+ col.prop(scene, "Key_Help", text="")
+ col.label(text="Instantiate object:")
+ col.prop(scene, "Key_Instant", text="")
+ col.label(text="Random rotation:")
+ col.prop(scene, "Key_Randrot", text="")
+
+ box = split.box()
+ col = box.column(align=True)
+ col.label(text="Close polygonal shape:")
+ col.prop(scene, "Key_Close", text="")
+ col.label(text="Apply operation:")
+ col.prop(scene, "Key_Apply", text="")
+ col.label(text="Scale object:")
+ col.prop(scene, "Key_Scale", text="")
+ col.label(text="Subdiv add:")
+ col.prop(scene, "Key_Subadd", text="")
+
+ box = split.box()
+ col = box.column(align=True)
+ col.label(text="Gap rows:")
+ col.prop(scene, "Key_Gapy", text="")
+ col.label(text="Gap columns:")
+ col.prop(scene, "Key_Gapx", text="")
+ col.label(text="Depth / Solidify:")
+ col.prop(scene, "Key_Depth", text="")
+ col.label(text="Subdiv Remove:")
+ col.prop(scene, "Key_Subrem", text="")
+
+def register():
+ bpy.utils.register_class(CarverPrefs)
+
+def unregister():
+ bpy.utils.unregister_class(CarverPrefs)
diff --git a/object_carver/carver_profils.py b/object_carver/carver_profils.py
new file mode 100644
index 00000000..d19478c9
--- /dev/null
+++ b/object_carver/carver_profils.py
@@ -0,0 +1,409 @@
+from mathutils import (
+ Vector,
+)
+
+
+Profils = [
+ ("TEST",
+ Vector((0,0,1)),
+ [(-1, 1, 0.032334), (1, 1, 0.032334),(-1, -1, 0.032334), (1, -1, 0.01032334)],
+ [(0, 1, 2), (2,1,3)]),
+ ("CTP_4882",
+ Vector((2.61824, -5.56469, 0)),
+ [(-1.156501, 0.799282, 0.032334),
+ (-0.967583, 0.838861, 0.032334),
+ (-1.10386, 0.846403, 0.032334),
+ (-1.034712, 0.86089, 0.032334),
+ (-1.88472, -0.564419, 0.032334),
+ (-1.924299, -0.375502, 0.032334),
+ (-1.93184, -0.511778, 0.032334),
+ (-1.946327, -0.44263, 0.032334),
+ (-0.219065, -0.869195, 0.032334),
+ (-0.149916, -0.854708, 0.032334),
+ (-0.286193, -0.847167, 0.032334),
+ (-0.097275, -0.807588, 0.032334),
+ (0.692551, 0.434324, 0.032334),
+ (0.678064, 0.503472, 0.032334),
+ (0.670523, 0.367196, 0.032334),
+ (0.630943, 0.556113, 0.032334),
+ (-0.780424, -0.44263, 0.032334),
+ (-0.765937, -0.511778, 0.032334),
+ (-0.758396, -0.375502, 0.032334),
+ (-0.718817, -0.564419, 0.032334),
+ (-0.53496, 0.556113, 0.032334),
+ (-0.49538, 0.367196, 0.032334),
+ (-0.487839, 0.503472, 0.032334),
+ (-0.473352, 0.434324, 0.032334),
+ (-1.263178, -0.807588, 0.032334),
+ (-1.452096, -0.847167, 0.032334),
+ (-1.315819, -0.854708, 0.032334),
+ (-1.384968, -0.869195, 0.032334),
+ (0.131191, 0.86089, 0.032334),
+ (0.062043, 0.846403, 0.032334),
+ (0.19832, 0.838861, 0.032334),
+ (0.009402, 0.799282, 0.032334),
+ (0.946838, -0.869195, 0.032334),
+ (1.015987, -0.854708, 0.032334),
+ (0.87971, -0.847167, 0.032334),
+ (1.068628, -0.807588, 0.032334),
+ (1.858454, 0.434324, 0.032334),
+ (1.843967, 0.503472, 0.032334),
+ (1.836426, 0.367196, 0.032334),
+ (1.796846, 0.556113, 0.032334),
+ (0.385479, -0.44263, 0.032334),
+ (0.399966, -0.511778, 0.032334),
+ (0.407507, -0.375502, 0.032334),
+ (0.447086, -0.564419, 0.032334),
+ (1.297095, 0.86089, 0.032334),
+ (1.227946, 0.846403, 0.032334),
+ (1.364223, 0.838861, 0.032334),
+ (1.175305, 0.799282, 0.032334),
+ ],
+ [[16, 17, 19], [5, 4, 24], [14, 12, 15], [14, 15, 31], [10, 8, 11], [15, 30, 31], [19, 10, 11],
+ [11, 14, 31], [31, 18, 11], [8, 9, 11], [18, 16, 19], [12, 13, 15], [18, 19, 11], [28, 29, 31],
+ [30, 28, 31], [24, 21, 0], [23, 22, 20], [20, 1, 0], [3, 2, 0], [0, 5, 24], [7, 6, 4], [4, 25, 24],
+ [27, 26, 24], [21, 23, 20], [1, 3, 0], [5, 7, 4], [25, 27, 24], [21, 20, 0], [40, 41, 43], [38, 36, 39],
+ [38, 39, 47], [34, 32, 35], [39, 46, 47], [43, 34, 35], [35, 38, 47], [47, 42, 35], [32, 33, 35],
+ [42, 40, 43], [36, 37, 39], [42, 43, 35], [44, 45, 47], [46, 44, 47]]),
+ ("CTP_8354",
+ Vector((-0.06267, -2.43829, -0.0)),
+ [(-0.534254, -1.0, 0.032334),
+ (-1.0, -0.534254, 0.032334),
+ (-0.654798, -0.98413, 0.032334),
+ (-0.767127, -0.937602, 0.032334),
+ (-0.863586, -0.863586, 0.032334),
+ (-0.937602, -0.767127, 0.032334),
+ (-0.98413, -0.654798, 0.032334),
+ (1.0, -0.534254, 0.032334),
+ (0.534254, -1.0, 0.032334),
+ (0.98413, -0.654798, 0.032334),
+ (0.937602, -0.767127, 0.032334),
+ (0.863586, -0.863586, 0.032334),
+ (0.767127, -0.937602, 0.032334),
+ (0.654798, -0.98413, 0.032334),
+ (-1.0, 0.534254, 0.032334),
+ (-0.534254, 1.0, 0.032334),
+ (-0.98413, 0.654798, 0.032334),
+ (-0.937602, 0.767127, 0.032334),
+ (-0.863586, 0.863586, 0.032334),
+ (-0.767127, 0.937602, 0.032334),
+ (-0.654798, 0.98413, 0.032334),
+ (0.534254, 1.0, 0.032334),
+ (1.0, 0.534254, 0.032334),
+ (0.654798, 0.98413, 0.032334),
+ (0.767127, 0.937602, 0.032334),
+ (0.863586, 0.863586, 0.032334),
+ (0.937602, 0.767127, 0.032334),
+ (0.98413, 0.654798, 0.032334),
+ (-0.763998, 0.518786, 0.032334),
+ (-0.763998, -0.518786, 0.032334),
+ (-0.754202, -0.593189, 0.032334),
+ (-0.731454, -0.648108, 0.032334),
+ (-0.695267, -0.695267, 0.032334),
+ (-0.648108, -0.731454, 0.032334),
+ (-0.593189, -0.754202, 0.032334),
+ (-0.518786, -0.763998, 0.032334),
+ (0.518786, -0.763998, 0.032334),
+ (0.593189, -0.754202, 0.032334),
+ (0.648108, -0.731454, 0.032334),
+ (0.695267, -0.695267, 0.032334),
+ (0.731454, -0.648108, 0.032334),
+ (0.754202, -0.593189, 0.032334),
+ (0.763998, -0.518786, 0.032334),
+ (0.763998, 0.518786, 0.032334),
+ (0.754202, 0.593189, 0.032334),
+ (0.731454, 0.648108, 0.032334),
+ (0.695267, 0.695267, 0.032334),
+ (0.648108, 0.731454, 0.032334),
+ (0.593189, 0.754202, 0.032334),
+ (0.518786, 0.763998, 0.032334),
+ (-0.518786, 0.763998, 0.032334),
+ (-0.593189, 0.754202, 0.032334),
+ (-0.648108, 0.731454, 0.032334),
+ (-0.695267, 0.695267, 0.032334),
+ (-0.731454, 0.648108, 0.032334),
+ (-0.754202, 0.593189, 0.032334),
+ (0.518786, 0.518786, 0.032334),
+ (-0.518786, 0.518786, 0.032334),
+ (0.518786, -0.518786, 0.032334),
+ (-0.518786, -0.518786, 0.032334),
+ (-0.593189, 0.518786, 0.032334),
+ (-0.593189, -0.518786, 0.032334),
+ (0.518786, -0.593189, 0.032334),
+ (-0.518786, -0.593189, 0.032334),
+ (-0.593189, -0.593189, 0.032334),
+ (0.593189, 0.518786, 0.032334),
+ (0.593189, -0.518786, 0.032334),
+ (0.593189, -0.593189, 0.032334),
+ (-0.593189, 0.593189, 0.032334),
+ (-0.518786, 0.593189, 0.032334),
+ (0.518786, 0.593189, 0.032334),
+ (0.593189, 0.593189, 0.032334),
+ (-0.648108, 0.593189, 0.032334),
+ (-0.648108, 0.518786, 0.032334),
+ (-0.648108, -0.518786, 0.032334),
+ (-0.648108, -0.593189, 0.032334),
+ (-0.695267, 0.593189, 0.032334),
+ (-0.695267, 0.518786, 0.032334),
+ (-0.695267, -0.518786, 0.032334),
+ (-0.695267, -0.593189, 0.032334),
+ (0.648108, 0.593189, 0.032334),
+ (0.648108, 0.518786, 0.032334),
+ (0.648108, -0.518786, 0.032334),
+ (0.648108, -0.593189, 0.032334),
+ (0.695267, 0.593189, 0.032334),
+ (0.695267, 0.518786, 0.032334),
+ (0.695267, -0.518786, 0.032334),
+ (0.695267, -0.593189, 0.032334),
+ ],
+ [[87, 39, 40, 41], [29, 28, 14, 1], [30, 29, 1, 6], [31, 30, 6, 5], [32, 31, 5, 4], [33, 32, 4, 3],
+ [34, 33, 3, 2], [35, 34, 2, 0], [36, 35, 0, 8], [37, 36, 8, 13], [38, 37, 13, 12], [39, 38, 12, 11],
+ [40, 39, 11, 10], [41, 40, 10, 9], [42, 41, 9, 7], [43, 42, 7, 22], [44, 43, 22, 27], [45, 44, 27, 26],
+ [46, 45, 26, 25], [47, 46, 25, 24], [48, 47, 24, 23], [49, 48, 23, 21], [50, 49, 21, 15], [51, 50, 15, 20],
+ [52, 51, 20, 19], [53, 52, 19, 18], [54, 53, 18, 17], [55, 54, 17, 16], [28, 55, 16, 14], [68, 69, 50, 51],
+ [63, 35, 36, 62], [69, 57, 56, 70], [84, 85, 43, 44], [64, 34, 35, 63], [57, 59, 58, 56], [85, 86, 42, 43],
+ [60, 61, 59, 57], [73, 74, 61, 60], [72, 68, 51, 52], [75, 33, 34, 64], [61, 64, 63, 59], [59, 63, 62, 58],
+ [86, 87, 41, 42], [74, 75, 64, 61], [58, 62, 67, 66], [56, 58, 66, 65], [70, 56, 65, 71], [62, 36, 37, 67],
+ [49, 70, 71, 48], [50, 69, 70, 49], [60, 57, 69, 68], [73, 60, 68, 72], [46, 84, 44, 45], [78, 79, 75, 74],
+ [77, 78, 74, 73], [77, 73, 72, 76], [76, 72, 52, 53], [79, 32, 33, 75], [29, 30, 79, 78], [28, 29, 78, 77],
+ [28, 77, 76, 55], [55, 76, 53, 54], [30, 31, 32, 79], [66, 67, 83, 82], [65, 66, 82, 81], [71, 65, 81, 80],
+ [48, 71, 80, 47], [67, 37, 38, 83], [82, 83, 87, 86], [81, 82, 86, 85], [80, 81, 85, 84], [47, 80, 84, 46],
+ [83, 38, 39, 87]]),
+ ("CTP_5585",
+ Vector((5.0114, -2.4281, 0.0)),
+ [(-0.490711, -1.0, 0.032334),
+ (-1.0, -0.490711, 0.032334),
+ (1.0, -0.490711, 0.032334),
+ (0.490711, -1.0, 0.032334),
+ (-1.0, 0.490711, 0.032334),
+ (-0.490711, 1.0, 0.032334),
+ (0.490711, 1.0, 0.032334),
+ (1.0, 0.490711, 0.032334),
+ (-0.51852, 0.291276, 0.032334),
+ (-0.51852, -0.291276, 0.032334),
+ (-0.291276, -0.51852, 0.032334),
+ (0.291276, -0.51852, 0.032334),
+ (0.51852, -0.291276, 0.032334),
+ (0.51852, 0.291276, 0.032334),
+ (0.291276, 0.51852, 0.032334),
+ (-0.291276, 0.51852, 0.032334),
+ ],
+ [[11, 12, 13, 14], [9, 8, 4, 1], [10, 9, 1, 0], [11, 10, 0, 3], [12, 11, 3, 2], [13, 12, 2, 7],
+ [14, 13, 7, 6], [15, 14, 6, 5], [8, 15, 5, 4], [9, 10, 15, 8], [10, 11, 14, 15]]),
+ ("CTP_6960",
+ Vector((-0.11417, 2.48371, -0.0)),
+ [(0.0, 1.0, 0.016827),
+ (-0.382683, 0.92388, 0.016827),
+ (-0.707107, 0.707107, 0.016827),
+ (-0.92388, 0.382683, 0.016827),
+ (-1.0, -0.0, 0.016827),
+ (-0.92388, -0.382684, 0.016827),
+ (-0.707107, -0.707107, 0.016827),
+ (-0.382683, -0.92388, 0.016827),
+ (-0.0, -1.0, 0.016827),
+ (0.382683, -0.92388, 0.016827),
+ (0.707107, -0.707107, 0.016827),
+ (0.92388, -0.382684, 0.016827),
+ (1.0, 0.0, 0.016827),
+ (0.923879, 0.382684, 0.016827),
+ (0.707107, 0.707107, 0.016827),
+ (0.382683, 0.92388, 0.016827),
+ (-0.0, 0.546859, 0.016827),
+ (-0.209274, 0.505231, 0.016827),
+ (-0.386687, 0.386687, 0.016827),
+ (-0.505231, 0.209274, 0.016827),
+ (-0.546859, -0.0, 0.016827),
+ (-0.505231, -0.209274, 0.016827),
+ (-0.386687, -0.386687, 0.016827),
+ (-0.209274, -0.505231, 0.016827),
+ (-0.0, -0.546859, 0.016827),
+ (0.209274, -0.505231, 0.016827),
+ (0.386687, -0.386688, 0.016827),
+ (0.505231, -0.209274, 0.016827),
+ (0.546858, 0.0, 0.016827),
+ (0.505231, 0.209274, 0.016827),
+ (0.386687, 0.386688, 0.016827),
+ (0.209273, 0.505232, 0.016827),
+ ],
+ [[3, 19, 18, 2], [11, 27, 26, 10], [4, 20, 19, 3], [12, 28, 27, 11], [5, 21, 20, 4], [13, 29, 28, 12],
+ [6, 22, 21, 5], [14, 30, 29, 13], [7, 23, 22, 6], [15, 31, 30, 14], [8, 24, 23, 7], [1, 17, 16, 0],
+ [0, 16, 31, 15], [9, 25, 24, 8], [2, 18, 17, 1], [10, 26, 25, 9]]),
+ ("CTP_5359",
+ Vector((5.50446, 2.41669, -0.0)),
+ [(0.0, 0.714247, 0.023261),
+ (-0.382683, 0.659879, 0.023261),
+ (-0.707107, 0.505049, 0.023261),
+ (-0.92388, 0.273331, 0.023261),
+ (-1.0, -0.0, 0.023261),
+ (-0.92388, -0.273331, 0.023261),
+ (-0.707107, -0.505049, 0.023261),
+ (-0.382683, -0.659879, 0.023261),
+ (-0.0, -0.714247, 0.023261),
+ (0.382683, -0.659879, 0.023261),
+ (0.707107, -0.505049, 0.023261),
+ (0.92388, -0.273331, 0.023261),
+ (1.0, 0.0, 0.023261),
+ (0.923879, 0.273331, 0.023261),
+ (0.707107, 0.505049, 0.023261),
+ (0.382683, 0.659879, 0.023261),
+ (-0.0, 0.303676, 0.023261),
+ (-0.162705, 0.28056, 0.023261),
+ (-0.30064, 0.214731, 0.023261),
+ (-0.392805, 0.116212, 0.023261),
+ (-0.425169, -0.0, 0.023261),
+ (-0.392805, -0.116212, 0.023261),
+ (-0.30064, -0.214731, 0.023261),
+ (-0.162705, -0.28056, 0.023261),
+ (-0.0, -0.303676, 0.023261),
+ (0.162705, -0.28056, 0.023261),
+ (0.30064, -0.214731, 0.023261),
+ (0.392805, -0.116212, 0.023261),
+ (0.425169, 0.0, 0.023261),
+ (0.392805, 0.116212, 0.023261),
+ (0.30064, 0.214731, 0.023261),
+ (0.162705, 0.28056, 0.023261),
+ ],
+ [[3, 19, 18, 2], [11, 27, 26, 10], [4, 20, 19, 3], [12, 28, 27, 11], [5, 21, 20, 4], [13, 29, 28, 12],
+ [6, 22, 21, 5], [14, 30, 29, 13], [7, 23, 22, 6], [15, 31, 30, 14], [8, 24, 23, 7], [1, 17, 16, 0],
+ [0, 16, 31, 15], [9, 25, 24, 8], [2, 18, 17, 1], [10, 26, 25, 9]]),
+ ("CTP_5424",
+ Vector((2.61824, 2.34147, 0.0)),
+ [(1.0, -1.0, 0.032334),
+ (-1.0, 1.0, 0.032334),
+ (1.0, 1.0, 0.032334),
+ (0.783867, -0.259989, 0.032334),
+ (-0.393641, 0.857073, 0.032334),
+ (0.73142, -0.116299, 0.032334),
+ (0.657754, 0.02916, 0.032334),
+ (0.564682, 0.172804, 0.032334),
+ (0.454497, 0.311098, 0.032334),
+ (0.329912, 0.440635, 0.032334),
+ (0.193995, 0.558227, 0.032334),
+ (0.050092, 0.660978, 0.032334),
+ (-0.098254, 0.746358, 0.032334),
+ (-0.247389, 0.812263, 0.032334),
+ ],
+ [[3, 0, 2], [10, 9, 2], [2, 1, 4], [2, 4, 13], [5, 3, 2], [6, 5, 2], [2, 13, 12], [2, 12, 11], [7, 6, 2],
+ [8, 7, 2], [2, 11, 10], [9, 8, 2]]),
+ ("CTP_3774",
+ Vector((2.61824, -2.52425, 0.0)),
+ [(1.0, 0.0, 0.020045),
+ (-1.0, 0.0, 0.020045),
+ (0.31903, -0.664947, 0.020045),
+ (-0.31903, -0.664947, 0.020045),
+ (-0.31903, 1.0, 0.020045),
+ (0.31903, 1.0, 0.020045),
+ (0.31903, 0.0, 0.020045),
+ (-0.31903, 0.0, 0.020045),
+ (-1.0, 0.614333, 0.020045),
+ (-0.614333, 1.0, 0.020045),
+ (-0.970643, 0.761921, 0.020045),
+ (-0.887041, 0.887041, 0.020045),
+ (-0.761921, 0.970643, 0.020045),
+ (0.614333, 1.0, 0.020045),
+ (1.0, 0.614333, 0.020045),
+ (0.761921, 0.970643, 0.020045),
+ (0.887041, 0.887041, 0.020045),
+ (0.970643, 0.761921, 0.020045),
+ (-0.31903, 0.614333, 0.020045),
+ (0.31903, 0.614333, 0.020045),
+ (0.31903, 0.761921, 0.020045),
+ (-0.31903, 0.761921, 0.020045),
+ (0.31903, 0.887041, 0.020045),
+ (-0.31903, 0.887041, 0.020045),
+ (0.614333, 0.614333, 0.020045),
+ (0.614333, 0.0, 0.020045),
+ (0.614333, 0.761921, 0.020045),
+ (0.614333, 0.887041, 0.020045),
+ (-0.614333, 0.761921, 0.020045),
+ (-0.614333, 0.0, 0.020045),
+ (-0.614333, 0.887041, 0.020045),
+ (-0.614333, 0.614333, 0.020045),
+ ],
+ [[6, 25, 24, 19], [6, 19, 18, 7], [2, 6, 7, 3], [1, 29, 31, 8], [8, 31, 28, 10], [19, 24, 26, 20],
+ [18, 19, 20, 21], [21, 20, 22, 23], [10, 28, 30, 11], [20, 26, 27, 22], [22, 27, 13, 5], [23, 22, 5, 4],
+ [11, 30, 9, 12], [17, 16, 27, 26], [14, 17, 26, 24], [24, 25, 0, 14], [15, 13, 27, 16], [9, 30, 23, 4],
+ [31, 29, 7, 18], [28, 31, 18, 21], [30, 28, 21, 23]]),
+ ("CTP_4473",
+ Vector((7.31539, 0.0, 0.0)),
+ [(0.24549, -1.0, 0.022454),
+ (-0.24549, -1.0, 0.022454),
+ (-0.24549, 1.0, 0.022454),
+ (0.24549, 1.0, 0.022454),
+ (1.0, 0.267452, 0.022454),
+ (1.0, -0.267452, 0.022454),
+ (-1.0, -0.267452, 0.022454),
+ (-1.0, 0.267452, 0.022454),
+ (0.24549, 0.267452, 0.022454),
+ (0.24549, -0.267452, 0.022454),
+ (-0.24549, 0.267452, 0.022454),
+ (-0.24549, -0.267452, 0.022454),
+ ],
+ [[8, 3, 2, 10], [0, 9, 11, 1], [4, 8, 9, 5], [8, 10, 11, 9], [10, 7, 6, 11]]),
+ ("CTP_4003",
+ Vector((4.91276, 0.0, 0.0)),
+ [(-1.0, -1.0, 0.026945),
+ (1.0, -1.0, 0.026945),
+ (-1.0, 1.0, 0.026945),
+ (-0.026763, -1.0, 0.026945),
+ (-0.026763, 1.0, 0.026945),
+ (1.0, -0.026763, 0.026945),
+ (0.238983, 0.965014, 0.026945),
+ (0.486619, 0.86244, 0.026945),
+ (0.699268, 0.699268, 0.026945),
+ (0.86244, 0.486619, 0.026945),
+ (0.965014, 0.238983, 0.026945),
+ (0.238983, -1.0, 0.026945),
+ (0.486619, -1.0, 0.026945),
+ (0.699268, -1.0, 0.026945),
+ (0.86244, -1.0, 0.026945),
+ (-0.026763, 0.479676, 0.026945),
+ (0.486619, 0.479676, 0.026945),
+ (0.699268, 0.479676, 0.026945),
+ (0.238983, 0.479676, 0.026945),
+ (0.865316, 0.479676, 0.026945),
+ (-1.0, 0.479676, 0.026945),
+ (0.86244, 0.479676, 0.026945),
+ (-0.026763, 0.238983, 0.026945),
+ (0.486619, 0.238983, 0.026945),
+ (0.699268, 0.238983, 0.026945),
+ (0.238983, 0.238983, 0.026945),
+ (-1.0, 0.238983, 0.026945),
+ (0.86244, 0.238983, 0.026945),
+ (-0.026763, -0.026763, 0.026945),
+ (0.486619, -0.026763, 0.026945),
+ (0.699268, -0.026763, 0.026945),
+ (0.238983, -0.026763, 0.026945),
+ (-1.0, -0.026763, 0.026945),
+ (0.86244, -0.026763, 0.026945),
+ ],
+ [[0, 3, 28, 32], [4, 15, 18, 6], [6, 18, 16, 7], [7, 16, 17, 8], [8, 17, 21, 9], [9, 21, 19], [18, 15, 22, 25],
+ [19, 21, 27, 10], [16, 18, 25, 23], [17, 16, 23, 24], [20, 15, 4, 2], [21, 17, 24, 27], [27, 24, 30, 33],
+ [23, 25, 31, 29], [24, 23, 29, 30], [25, 22, 28, 31], [26, 22, 15, 20], [10, 27, 33, 5], [31, 28, 3, 11],
+ [33, 30, 13, 14], [29, 31, 11, 12], [5, 33, 14, 1], [30, 29, 12, 13], [32, 28, 22, 26]]),
+ ("CTP_3430",
+ Vector((2.61824, 0.0, 0.0)),
+ [(-1.0, -1.0, 0.032334),
+ (1.0, -1.0, 0.032334),
+ (-1.0, 1.0, 0.032334),
+ (1.0, 1.0, 0.032334),
+ ],
+ [[0, 1, 3, 2]]),
+ ("CTP_7175",
+ Vector((0.0, 0.0, 0.0)),
+ [(-1.0, -1.0, 0.032334),
+ (1.0, -1.0, 0.032334),
+ (-1.0, 1.0, 0.032334),
+ (1.0, 1.0, 0.032334),
+ (0.0, 0.0, 0.032334),
+ (0.0, 0.0, 0.032334),
+ (0.0, 0.0, 0.032334),
+ (0.0, 0.0, 0.032334),
+ (0.0, 0.0, 0.032334),
+ (-0.636126, 0.636126, 0.032334),
+ (-0.636126, -0.636126, 0.032334),
+ (0.636126, -0.636126, 0.032334),
+ (0.636126, 0.636126, 0.032334),
+ ],
+ [[10, 9, 2, 0], [11, 10, 0, 1], [12, 11, 1, 3], [9, 12, 3, 2]]),
+]
diff --git a/object_carver/carver_utils.py b/object_carver/carver_utils.py
new file mode 100644
index 00000000..67c5769d
--- /dev/null
+++ b/object_carver/carver_utils.py
@@ -0,0 +1,941 @@
+
+import bpy
+import bgl
+import gpu
+from gpu_extras.batch import batch_for_shader
+import math
+import sys
+import random
+import bmesh
+from mathutils import (
+ Euler,
+ Matrix,
+ Vector,
+ Quaternion,
+)
+from mathutils.geometry import (
+ intersect_line_plane,
+)
+
+from math import (
+ sin,
+ cos,
+ pi,
+ )
+
+import bpy_extras
+
+from bpy_extras import view3d_utils
+from bpy_extras.view3d_utils import (
+ region_2d_to_vector_3d,
+ region_2d_to_location_3d,
+ location_3d_to_region_2d,
+)
+
+# Cut Square
+def CreateCutSquare(self, context):
+ """ Create a rectangle mesh """
+ far_limit = 10000.0
+ faces=[]
+
+ # Get the mouse coordinates
+ coord = self.mouse_path[0][0], self.mouse_path[0][1]
+
+ # New mesh
+ me = bpy.data.meshes.new('CMT_Square')
+ bm = bmesh.new()
+ bm.from_mesh(me)
+
+ # New object and link it to the scene
+ ob = bpy.data.objects.new('CMT_Square', me)
+ self.CurrentObj = ob
+ context.collection.objects.link(ob)
+
+ # Scene information
+ region = context.region
+ rv3d = context.region_data
+ depth_location = region_2d_to_vector_3d(region, rv3d, coord)
+ self.ViewVector = depth_location
+
+ # Get a point on a infinite plane and its direction
+ plane_normal = depth_location
+ plane_direction = plane_normal.normalized()
+
+ if self.snapCursor:
+ plane_point = context.scene.cursor.location
+ else:
+ plane_point = self.OpsObj.location if self.OpsObj is not None else Vector((0.0, 0.0, 0.0))
+
+ # Find the intersection of a line going thru each vertex and the infinite plane
+ for v_co in self.rectangle_coord:
+ vec = region_2d_to_vector_3d(region, rv3d, v_co)
+ p0 = region_2d_to_location_3d(region, rv3d,v_co, vec)
+ p1 = region_2d_to_location_3d(region, rv3d,v_co, vec) + plane_direction * far_limit
+ faces.append(bm.verts.new(intersect_line_plane(p0, p1, plane_point, plane_direction)))
+
+ # Update vertices index
+ bm.verts.index_update()
+ # New faces
+ t_face = bm.faces.new(faces)
+ # Set mesh
+ bm.to_mesh(me)
+
+
+# Cut Line
+def CreateCutLine(self, context):
+ """ Create a polygon mesh """
+ far_limit = 10000.0
+ vertices = []
+ faces = []
+ loc = []
+
+ # Get the mouse coordinates
+ coord = self.mouse_path[0][0], self.mouse_path[0][1]
+
+ # New mesh
+ me = bpy.data.meshes.new('CMT_Line')
+ bm = bmesh.new()
+ bm.from_mesh(me)
+
+ # New object and link it to the scene
+ ob = bpy.data.objects.new('CMT_Line', me)
+ self.CurrentObj = ob
+ context.collection.objects.link(ob)
+
+ # Scene information
+ region = context.region
+ rv3d = context.region_data
+ depth_location = region_2d_to_vector_3d(region, rv3d, coord)
+ self.ViewVector = depth_location
+
+ # Get a point on a infinite plane and its direction
+ plane_normal = depth_location
+ plane_direction = plane_normal.normalized()
+
+ if self.snapCursor:
+ plane_point = context.scene.cursor.location
+ else:
+ plane_point = self.OpsObj.location if self.OpsObj is not None else Vector((0.0, 0.0, 0.0))
+
+ # Use dict to remove doubles
+ # Find the intersection of a line going thru each vertex and the infinite plane
+ for idx, v_co in enumerate(list(dict.fromkeys(self.mouse_path))):
+ vec = region_2d_to_vector_3d(region, rv3d, v_co)
+ p0 = region_2d_to_location_3d(region, rv3d,v_co, vec)
+ p1 = region_2d_to_location_3d(region, rv3d,v_co, vec) + plane_direction * far_limit
+ loc.append(intersect_line_plane(p0, p1, plane_point, plane_direction))
+ vertices.append(bm.verts.new(loc[idx]))
+
+ if idx > 0:
+ bm.edges.new([vertices[idx-1],vertices[idx]])
+
+ faces.append(vertices[idx])
+
+ # Update vertices index
+ bm.verts.index_update()
+
+ # Nothing is selected, create close geometry
+ if self.CreateMode:
+ if self.Closed and len(vertices) > 1:
+ bm.edges.new([vertices[-1], vertices[0]])
+ bm.faces.new(faces)
+ else:
+ # Create faces if more than 2 vertices
+ if len(vertices) > 1 :
+ bm.edges.new([vertices[-1], vertices[0]])
+ bm.faces.new(faces)
+
+ bm.to_mesh(me)
+
+# Cut Circle
+def CreateCutCircle(self, context):
+ """ Create a circle mesh """
+ far_limit = 10000.0
+ FacesList = []
+
+ # Get the mouse coordinates
+ mouse_pos_x = self.mouse_path[0][0]
+ mouse_pos_y = self.mouse_path[0][1]
+ coord = self.mouse_path[0][0], self.mouse_path[0][1]
+
+ # Scene information
+ region = context.region
+ rv3d = context.region_data
+ depth_location = region_2d_to_vector_3d(region, rv3d, coord)
+ self.ViewVector = depth_location
+
+ # Get a point on a infinite plane and its direction
+ plane_point = context.scene.cursor.location if self.snapCursor else Vector((0.0, 0.0, 0.0))
+ plane_normal = depth_location
+ plane_direction = plane_normal.normalized()
+
+ # New mesh
+ me = bpy.data.meshes.new('CMT_Circle')
+ bm = bmesh.new()
+ bm.from_mesh(me)
+
+ # New object and link it to the scene
+ ob = bpy.data.objects.new('CMT_Circle', me)
+ self.CurrentObj = ob
+ context.collection.objects.link(ob)
+
+ # Create a circle using a tri fan
+ tris_fan, indices = draw_circle(self, mouse_pos_x, mouse_pos_y)
+
+ # Remove the vertex in the center to get the outer line of the circle
+ verts = tris_fan[1:]
+
+ # Find the intersection of a line going thru each vertex and the infinite plane
+ for vert in verts:
+ vec = region_2d_to_vector_3d(region, rv3d, vert)
+ p0 = region_2d_to_location_3d(region, rv3d, vert, vec)
+ p1 = p0 + plane_direction * far_limit
+ loc0 = intersect_line_plane(p0, p1, plane_point, plane_direction)
+ t_v0 = bm.verts.new(loc0)
+ FacesList.append(t_v0)
+
+ bm.verts.index_update()
+ bm.faces.new(FacesList)
+ bm.to_mesh(me)
+
+
+def create_2d_circle(self, step, radius, rotation = 0):
+ """ Create the vertices of a 2d circle at (0,0) """
+ verts = []
+ for angle in range(0, 360, step):
+ verts.append(math.cos(math.radians(angle + rotation)) * radius)
+ verts.append(math.sin(math.radians(angle + rotation)) * radius)
+ verts.append(0.0)
+ verts.append(math.cos(math.radians(0.0 + rotation)) * radius)
+ verts.append(math.sin(math.radians(0.0 + rotation)) * radius)
+ verts.append(0.0)
+ return(verts)
+
+
+def draw_circle(self, mouse_pos_x, mouse_pos_y):
+ """ Return the coordinates + indices of a circle using a triangle fan """
+ tris_verts = []
+ indices = []
+ segments = int(360 / self.stepAngle[self.step])
+ radius = self.mouse_path[1][0] - self.mouse_path[0][0]
+ rotation = (self.mouse_path[1][1] - self.mouse_path[0][1]) / 2
+
+ # Get the vertices of a 2d circle
+ verts = create_2d_circle(self, self.stepAngle[self.step], radius, rotation)
+
+ # Create the first vertex at mouse position for the center of the circle
+ tris_verts.append(Vector((mouse_pos_x + self.xpos , mouse_pos_y + self.ypos)))
+
+ # For each vertex of the circle, add the mouse position and the translation
+ for idx in range(int(len(verts) / 3) - 1):
+ tris_verts.append(Vector((verts[idx * 3] + mouse_pos_x + self.xpos, \
+ verts[idx * 3 + 1] + mouse_pos_y + self.ypos)))
+ i1 = idx+1
+ i2 = idx+2 if idx+2 <= segments else 1
+ indices.append((0,i1,i2))
+
+ return(tris_verts, indices)
+
+# Object dimensions (SCULPT Tools tips)
+def objDiagonal(obj):
+ return ((obj.dimensions[0]**2) + (obj.dimensions[1]**2) + (obj.dimensions[2]**2))**0.5
+
+
+# Bevel Update
+def update_bevel(context):
+ selection = context.selected_objects.copy()
+ active = context.active_object
+
+ if len(selection) > 0:
+ for obj in selection:
+ bpy.ops.object.select_all(action='DESELECT')
+ obj.select_set(True)
+ context.view_layer.objects.active = obj
+
+ # Test object name
+ # Subdive mode : Only bevel weight
+ if obj.data.name.startswith("S_") or obj.data.name.startswith("S "):
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.region_to_loop()
+ bpy.ops.transform.edge_bevelweight(value=1)
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ else:
+ # No subdiv mode : bevel weight + Crease + Sharp
+ CreateBevel(context, obj)
+
+ bpy.ops.object.select_all(action='DESELECT')
+
+ for obj in selection:
+ obj.select_set(True)
+ context.view_layer.objects.active = active
+
+# Create bevel
+def CreateBevel(context, CurrentObject):
+ # Save active object
+ SavActive = context.active_object
+
+ # Test if initial object has bevel
+ bevel_modifier = False
+ for modifier in SavActive.modifiers:
+ if modifier.name == 'Bevel':
+ bevel_modifier = True
+
+ if bevel_modifier:
+ # Active "CurrentObject"
+ context.view_layer.objects.active = CurrentObject
+
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Edge mode
+ bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
+ # Clear all
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.mark_sharp(clear=True)
+ bpy.ops.transform.edge_crease(value=-1)
+ bpy.ops.transform.edge_bevelweight(value=-1)
+
+ bpy.ops.mesh.select_all(action='DESELECT')
+
+ # Select (in radians) all 30° sharp edges
+ bpy.ops.mesh.edges_select_sharp(sharpness=0.523599)
+ # Apply bevel weight + Crease + Sharp to the selected edges
+ bpy.ops.mesh.mark_sharp()
+ bpy.ops.transform.edge_crease(value=1)
+ bpy.ops.transform.edge_bevelweight(value=1)
+
+ bpy.ops.mesh.select_all(action='DESELECT')
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ CurrentObject.data.use_customdata_edge_bevel = True
+
+ for i in range(len(CurrentObject.data.edges)):
+ if CurrentObject.data.edges[i].select is True:
+ CurrentObject.data.edges[i].bevel_weight = 1.0
+ CurrentObject.data.edges[i].use_edge_sharp = True
+
+ bevel_modifier = False
+ for m in CurrentObject.modifiers:
+ if m.name == 'Bevel':
+ bevel_modifier = True
+
+ if bevel_modifier is False:
+ bpy.ops.object.modifier_add(type='BEVEL')
+ mod = context.object.modifiers[-1]
+ mod.limit_method = 'WEIGHT'
+ mod.width = 0.01
+ mod.profile = 0.699099
+ mod.use_clight_overlap = False
+ mod.segments = 3
+ mod.loop_slide = False
+
+ bpy.ops.object.shade_smooth()
+
+ context.object.data.use_auto_smooth = True
+ context.object.data.auto_smooth_angle = 1.0471975
+
+ # Restore the active object
+ context.view_layer.objects.active = SavActive
+
+
+def MoveCursor(qRot, location, self):
+ """ In brush mode : Draw a circle around the brush """
+ if qRot is not None:
+ verts = create_2d_circle(self, 10, 1)
+ self.CLR_C.clear()
+ vc = Vector()
+ for idx in range(int(len(verts) / 3)):
+ vc.x = verts[idx * 3]
+ vc.y = verts[idx * 3 + 1]
+ vc.z = verts[idx * 3 + 2]
+ vc = qRot @ vc
+ self.CLR_C.append(vc.x)
+ self.CLR_C.append(vc.y)
+ self.CLR_C.append(vc.z)
+
+
+def rot_axis_quat(vector1, vector2):
+ """ Find the rotation (quaternion) from vector 1 to vector 2"""
+ vector1 = vector1.normalized()
+ vector2 = vector2.normalized()
+ cosTheta = vector1.dot(vector2)
+ rotationAxis = Vector((0.0, 0.0, 0.0))
+ if (cosTheta < -1 + 0.001):
+ v = Vector((0.0, 1.0, 0.0))
+ #Get the vector at the right angles to both
+ rotationAxis = vector1.cross(v)
+ rotationAxis = rotationAxis.normalized()
+ q = Quaternion()
+ q.w = 0.0
+ q.x = rotationAxis.x
+ q.y = rotationAxis.y
+ q.z = rotationAxis.z
+ else:
+ rotationAxis = vector1.cross(vector2)
+ s = math.sqrt((1.0 + cosTheta) * 2.0)
+ invs = 1 / s
+ q = Quaternion()
+ q.w = s * 0.5
+ q.x = rotationAxis.x * invs
+ q.y = rotationAxis.y * invs
+ q.z = rotationAxis.z * invs
+ return q
+
+
+# Picking (template)
+def Picking(context, event):
+ """ Put the 3d cursor on the closest object"""
+
+ # get the context arguments
+ scene = context.scene
+ region = context.region
+ rv3d = context.region_data
+ coord = event.mouse_region_x, event.mouse_region_y
+
+ # get the ray from the viewport and mouse
+ view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
+ ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
+ ray_target = ray_origin + view_vector
+
+ def visible_objects_and_duplis():
+ depsgraph = context.depsgraph
+ for dup in depsgraph.object_instances:
+ if dup.is_instance: # Real dupli instance
+ obj = dup.instance_object.original
+ yield (obj, dup.matrix.copy())
+ else: # Usual object
+ obj = dup.object.original
+ yield (obj, obj.matrix_world.copy())
+
+ def obj_ray_cast(obj, matrix):
+ # get the ray relative to the object
+ matrix_inv = matrix.inverted()
+ ray_origin_obj = matrix_inv @ ray_origin
+ ray_target_obj = matrix_inv @ ray_target
+ ray_direction_obj = ray_target_obj - ray_origin_obj
+ # cast the ray
+ success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)
+ if success:
+ return location, normal, face_index
+ return None, None, None
+
+ # cast rays and find the closest object
+ best_length_squared = -1.0
+ best_obj = None
+
+ # cast rays and find the closest object
+ for obj, matrix in visible_objects_and_duplis():
+ if obj.type == 'MESH':
+ hit, normal, face_index = obj_ray_cast(obj, matrix)
+ if hit is not None:
+ hit_world = matrix @ hit
+ length_squared = (hit_world - ray_origin).length_squared
+ if best_obj is None or length_squared < best_length_squared:
+ scene.cursor.location = hit_world
+ best_length_squared = length_squared
+ best_obj = obj
+ else:
+ if best_obj is None:
+ depth_location = region_2d_to_vector_3d(region, rv3d, coord)
+ loc = region_2d_to_location_3d(region, rv3d, coord, depth_location)
+ scene.cursor.location = loc
+
+
+def Pick(context, event, self, ray_max=10000.0):
+ region = context.region
+ rv3d = context.region_data
+ coord = event.mouse_region_x, event.mouse_region_y
+ view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
+ ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
+ ray_target = ray_origin + (view_vector * ray_max)
+
+ def obj_ray_cast(obj, matrix):
+ matrix_inv = matrix.inverted()
+ ray_origin_obj = matrix_inv @ ray_origin
+ ray_target_obj = matrix_inv @ ray_target
+ success, hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj)
+ if success:
+ return hit, normal, face_index
+ return None, None, None
+
+ best_length_squared = ray_max * ray_max
+ best_obj = None
+ for obj in self.CList:
+ matrix = obj.matrix_world
+ hit, normal, face_index = obj_ray_cast(obj, matrix)
+ rotation = obj.rotation_euler.to_quaternion()
+ if hit is not None:
+ hit_world = matrix @ hit
+ length_squared = (hit_world - ray_origin).length_squared
+ if length_squared < best_length_squared:
+ best_length_squared = length_squared
+ best_obj = obj
+ hits = hit_world
+ ns = normal
+ fs = face_index
+
+ if best_obj is not None:
+ return hits, ns, rotation
+
+ return None, None, None
+
+def SelectObject(self, copyobj):
+ copyobj.select_set(True)
+
+ for child in copyobj.children:
+ SelectObject(self, child)
+
+ if copyobj.parent is None:
+ bpy.context.view_layer.objects.active = copyobj
+
+# Undo
+def printUndo(self):
+ for l in self.UList:
+ print(l)
+
+
+def UndoAdd(self, type, obj):
+ """ Create a backup mesh before apply the action to the object """
+ if obj is None:
+ return
+
+ if type != "DUPLICATE":
+ bm = bmesh.new()
+ bm.from_mesh(obj.data)
+ self.UndoOps.append((obj, type, bm))
+ else:
+ self.UndoOps.append((obj, type, None))
+
+
+def UndoListUpdate(self):
+ self.UList.append((self.UndoOps.copy()))
+ self.UList_Index += 1
+ self.UndoOps.clear()
+
+
+def Undo(self):
+ if self.UList_Index < 0:
+ return
+ # get previous mesh
+ for o in self.UList[self.UList_Index]:
+ if o[1] == "MESH":
+ bm = o[2]
+ bm.to_mesh(o[0].data)
+
+ SelectObjList = bpy.context.selected_objects.copy()
+ Active_Obj = bpy.context.active_object
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ for o in self.UList[self.UList_Index]:
+ if o[1] == "REBOOL":
+ o[0].select_set(True)
+ o[0].hide_viewport = False
+
+ if o[1] == "DUPLICATE":
+ o[0].select_set(True)
+ o[0].hide_viewport = False
+
+ bpy.ops.object.delete(use_global=False)
+
+ for so in SelectObjList:
+ bpy.data.objects[so.name].select_set(True)
+ bpy.context.view_layer.objects.active = Active_Obj
+
+ self.UList_Index -= 1
+ self.UList[self.UList_Index + 1:] = []
+
+
+def duplicateObject(self):
+ if self.Instantiate:
+ bpy.ops.object.duplicate_move_linked(
+ OBJECT_OT_duplicate={
+ "linked": True,
+ "mode": 'TRANSLATION',
+ },
+ TRANSFORM_OT_translate={
+ "value": (0, 0, 0),
+ },
+ )
+ else:
+ bpy.ops.object.duplicate_move(
+ OBJECT_OT_duplicate={
+ "linked": False,
+ "mode": 'TRANSLATION',
+ },
+ TRANSFORM_OT_translate={
+ "value": (0, 0, 0),
+ },
+ )
+
+ ob_new = bpy.context.active_object
+
+ ob_new.location = self.CurLoc
+ v = Vector()
+ v.x = v.y = 0.0
+ v.z = self.BrushDepthOffset
+ ob_new.location += self.qRot * v
+
+ if self.ObjectMode:
+ ob_new.scale = self.ObjectBrush.scale
+ if self.ProfileMode:
+ ob_new.scale = self.ProfileBrush.scale
+
+ e = Euler()
+ e.x = e.y = 0.0
+ e.z = self.aRotZ / 25.0
+
+ # If duplicate with a grid, no random rotation (each mesh in the grid is already rotated randomly)
+ if (self.alt is True) and ((self.nbcol + self.nbrow) < 3):
+ if self.RandomRotation:
+ e.z += random.random()
+
+ qe = e.to_quaternion()
+ qRot = self.qRot * qe
+ ob_new.rotation_mode = 'QUATERNION'
+ ob_new.rotation_quaternion = qRot
+ ob_new.rotation_mode = 'XYZ'
+
+ if (ob_new.display_type == "WIRE") and (self.BrushSolidify is False):
+ ob_new.hide_viewport = True
+
+ if self.BrushSolidify:
+ ob_new.display_type = "SOLID"
+ ob_new.show_in_front = False
+
+ for o in bpy.context.selected_objects:
+ UndoAdd(self, "DUPLICATE", o)
+
+ if len(bpy.context.selected_objects) > 0:
+ bpy.ops.object.select_all(action='TOGGLE')
+ for o in self.all_sel_obj_list:
+ o.select_set(True)
+
+ bpy.context.view_layer.objects.active = self.OpsObj
+
+
+def update_grid(self, context):
+ """
+ Thanks to batFINGER for his help :
+ source : http://blender.stackexchange.com/questions/55864/multiple-meshes-not-welded-with-pydata
+ """
+ verts = []
+ edges = []
+ faces = []
+ numface = 0
+
+ if self.nbcol < 1:
+ self.nbcol = 1
+ if self.nbrow < 1:
+ self.nbrow = 1
+ if self.gapx < 0:
+ self.gapx = 0
+ if self.gapy < 0:
+ self.gapy = 0
+
+ # Get the data from the profils or the object
+ if self.ProfileMode:
+ brush = bpy.data.objects.new(
+ self.Profils[self.nProfil][0],
+ bpy.data.meshes[self.Profils[self.nProfil][0]]
+ )
+ obj = bpy.data.objects["CT_Profil"]
+ obfaces = brush.data.polygons
+ obverts = brush.data.vertices
+ lenverts = len(obverts)
+ else:
+ brush = bpy.data.objects["CarverBrushCopy"]
+ obj = context.selected_objects[0]
+ obverts = brush.data.vertices
+ obfaces = brush.data.polygons
+ lenverts = len(brush.data.vertices)
+
+ # Gap between each row / column
+ gapx = self.gapx
+ gapy = self.gapy
+
+ # Width of each row / column
+ widthx = brush.dimensions.x * self.scale_x
+ widthy = brush.dimensions.y * self.scale_y
+
+ # Compute the corners so the new object will be always at the center
+ left = -((self.nbcol - 1) * (widthx + gapx)) / 2
+ start = -((self.nbrow - 1) * (widthy + gapy)) / 2
+
+ for i in range(self.nbrow * self.nbcol):
+ row = i % self.nbrow
+ col = i // self.nbrow
+ startx = left + ((widthx + gapx) * col)
+ starty = start + ((widthy + gapy) * row)
+
+ # Add random rotation
+ if (self.RandomRotation) and not (self.GridScaleX or self.GridScaleY):
+ rotmat = Matrix.Rotation(math.radians(360 * random.random()), 4, 'Z')
+ for v in obverts:
+ v.co = v.co @ rotmat
+
+ verts.extend([((v.co.x - startx, v.co.y - starty, v.co.z)) for v in obverts])
+ faces.extend([[v + numface * lenverts for v in p.vertices] for p in obfaces])
+ numface += 1
+
+ # Update the mesh
+ # Create mesh data
+ mymesh = bpy.data.meshes.new("CT_Profil")
+ # Generate mesh data
+ mymesh.from_pydata(verts, edges, faces)
+ # Calculate the edges
+ mymesh.update(calc_edges=True)
+ # Update data
+ obj.data = mymesh
+ # Make the object active to remove doubles
+ context.view_layer.objects.active = obj
+
+
+def boolean_operation(bool_type="DIFFERENCE"):
+ ActiveObj = bpy.context.active_object
+ sel_index = 0 if bpy.context.selected_objects[0] != bpy.context.active_object else 1
+
+ # bpy.ops.object.modifier_apply(apply_as='DATA', modifier="CT_SOLIDIFY")
+ bool_name = "CT_" + bpy.context.selected_objects[sel_index].name
+ BoolMod = ActiveObj.modifiers.new(bool_name, "BOOLEAN")
+ BoolMod.object = bpy.context.selected_objects[sel_index]
+ BoolMod.operation = bool_type
+ bpy.context.selected_objects[sel_index].display_type = 'WIRE'
+ while ActiveObj.modifiers.find(bool_name) > 0:
+ bpy.ops.object.modifier_move_up(modifier=bool_name)
+
+
+def Rebool(context, self):
+
+ target_obj = context.active_object
+
+ Brush = context.selected_objects[1]
+ Brush.display_type = "WIRE"
+
+ #Deselect all
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ target_obj.display_type = "SOLID"
+ target_obj.select_set(True)
+ bpy.ops.object.duplicate()
+
+ rebool_obj = context.active_object
+
+ m = rebool_obj.modifiers.new("CT_INTERSECT", "BOOLEAN")
+ m.operation = "INTERSECT"
+ m.object = Brush
+
+ m = target_obj.modifiers.new("CT_DIFFERENCE", "BOOLEAN")
+ m.operation = "DIFFERENCE"
+ m.object = Brush
+
+ for mb in target_obj.modifiers:
+ if mb.type == 'BEVEL':
+ mb.show_viewport = False
+
+ if self.ObjectBrush or self.ProfileBrush:
+ rebool_obj.show_in_front = False
+ try:
+ bpy.ops.object.modifier_apply(apply_as='DATA', modifier="CT_SOLIDIFY")
+ except:
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ self.report({'ERROR'}, str(exc_value))
+
+ if self.dont_apply_boolean is False:
+ try:
+ bpy.ops.object.modifier_apply(apply_as='DATA', modifier="CT_INTERSECT")
+ except:
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ self.report({'ERROR'}, str(exc_value))
+
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ for mb in target_obj.modifiers:
+ if mb.type == 'BEVEL':
+ mb.show_viewport = True
+
+ context.view_layer.objects.active = target_obj
+ target_obj.select_set(True)
+ if self.dont_apply_boolean is False:
+ try:
+ bpy.ops.object.modifier_apply(apply_as='DATA', modifier="CT_DIFFERENCE")
+ except:
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ self.report({'ERROR'}, str(exc_value))
+
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ rebool_obj.select_set(True)
+
+def createMeshFromData(self):
+ if self.Profils[self.nProfil][0] not in bpy.data.meshes:
+ # Create mesh and object
+ me = bpy.data.meshes.new(self.Profils[self.nProfil][0])
+ # Create mesh from given verts, faces.
+ me.from_pydata(self.Profils[self.nProfil][2], [], self.Profils[self.nProfil][3])
+ me.validate(verbose=True, clean_customdata=True)
+ # Update mesh with new data
+ me.update()
+
+ if "CT_Profil" not in bpy.data.objects:
+ ob = bpy.data.objects.new("CT_Profil", bpy.data.meshes[self.Profils[self.nProfil][0]])
+ ob.location = Vector((0.0, 0.0, 0.0))
+
+ # Link object to scene and make active
+ bpy.context.collection.objects.link(ob)
+ bpy.context.scene.update()
+ bpy.context.view_layer.objects.active = ob
+ ob.select_set(True)
+ ob.location = Vector((10000.0, 0.0, 0.0))
+ ob.display_type = "WIRE"
+
+ self.SolidifyPossible = True
+ else:
+ bpy.data.objects["CT_Profil"].data = bpy.data.meshes[self.Profils[self.nProfil][0]]
+
+def Selection_Save_Restore(self):
+ if "CT_Profil" in bpy.data.objects:
+ Selection_Save(self)
+ bpy.ops.object.select_all(action='DESELECT')
+ bpy.data.objects["CT_Profil"].select_set(True)
+ bpy.context.view_layer.objects.active = bpy.data.objects["CT_Profil"]
+ if bpy.data.objects["CT_Profil"] in self.all_sel_obj_list:
+ self.all_sel_obj_list.remove(bpy.data.objects["CT_Profil"])
+ bpy.ops.object.delete(use_global=False)
+ Selection_Restore(self)
+
+def Selection_Save(self):
+ obj_name = getattr(bpy.context.active_object, "name", None)
+ self.all_sel_obj_list = bpy.context.selected_objects.copy()
+ self.save_active_obj = obj_name
+
+
+def Selection_Restore(self):
+ for o in self.all_sel_obj_list:
+ o.select_set(True)
+ if self.save_active_obj:
+ bpy.context.view_layer.objects.active = bpy.data.objects.get(self.save_active_obj, None)
+
+def Snap_Cursor(self, context, event, mouse_pos):
+ """ Find the closest position on the overlay grid and snap the mouse on it """
+ # Get the context arguments
+ region = context.region
+ rv3d = context.region_data
+
+ # Get the VIEW3D area
+ for i, a in enumerate(context.screen.areas):
+ if a.type == 'VIEW_3D':
+ space = context.screen.areas[i].spaces.active
+
+ # Get the grid overlay for the VIEW_3D
+ grid_scale = space.overlay.grid_scale
+ grid_subdivisions = space.overlay.grid_subdivisions
+
+ # Use the grid scale and subdivision to get the increment
+ increment = (grid_scale / grid_subdivisions)
+ half_increment = increment / 2
+
+ # Convert the 2d location of the mouse in 3d
+ for index, loc in enumerate(reversed(mouse_pos)):
+ mouse_loc_3d = region_2d_to_location_3d(region, rv3d, loc, (0, 0, 0))
+
+ # Get the remainder from the mouse location and the ratio
+ # Test if the remainder > to the half of the increment
+ for i in range(3):
+ modulo = mouse_loc_3d[i] % increment
+ if modulo < half_increment:
+ modulo = - modulo
+ else:
+ modulo = increment - modulo
+
+ # Add the remainder to get the closest location on the grid
+ mouse_loc_3d[i] = mouse_loc_3d[i] + modulo
+
+ # Get the snapped 2d location
+ snap_loc_2d = location_3d_to_region_2d(region, rv3d, mouse_loc_3d)
+
+ # Replace the last mouse location by the snapped location
+ if len(self.mouse_path) > 0:
+ self.mouse_path[len(self.mouse_path) - (index + 1) ] = tuple(snap_loc_2d)
+
+def mini_grid(self, context, color):
+ """ Draw a snap mini grid around the cursor based on the overlay grid"""
+ # Get the context arguments
+ region = context.region
+ rv3d = context.region_data
+
+ # Get the VIEW3D area
+ for i, a in enumerate(context.screen.areas):
+ if a.type == 'VIEW_3D':
+ space = context.screen.areas[i].spaces.active
+ screen_height = context.screen.areas[i].height
+ screen_width = context.screen.areas[i].width
+
+ #Draw the snap grid, only in ortho view
+ if not space.region_3d.is_perspective :
+ grid_scale = space.overlay.grid_scale
+ grid_subdivisions = space.overlay.grid_subdivisions
+ increment = (grid_scale / grid_subdivisions)
+
+ # Get the 3d location of the mouse forced to a snap value in the operator
+ mouse_coord = self.mouse_path[len(self.mouse_path) - 1]
+
+ snap_loc = region_2d_to_location_3d(region, rv3d, mouse_coord, (0, 0, 0))
+
+ # Add the increment to get the closest location on the grid
+ snap_loc[0] += increment
+ snap_loc[1] += increment
+
+ # Get the 2d location of the snap location
+ snap_loc = location_3d_to_region_2d(region, rv3d, snap_loc)
+ origin = location_3d_to_region_2d(region, rv3d, (0,0,0))
+
+ # Get the increment value
+ snap_value = snap_loc[0] - mouse_coord[0]
+
+ grid_coords = []
+
+ # Draw lines on X and Z axis from the cursor through the screen
+ grid_coords = [
+ (0, mouse_coord[1]), (screen_width, mouse_coord[1]),
+ (mouse_coord[0], 0), (mouse_coord[0], screen_height)
+ ]
+
+ # Draw a mlini grid around the cursor to show the snap options
+ grid_coords += [
+ (mouse_coord[0] + snap_value, mouse_coord[1] + 25 + snap_value),
+ (mouse_coord[0] + snap_value, mouse_coord[1] - 25 - snap_value),
+ (mouse_coord[0] + 25 + snap_value, mouse_coord[1] + snap_value),
+ (mouse_coord[0] - 25 - snap_value, mouse_coord[1] + snap_value),
+ (mouse_coord[0] - snap_value, mouse_coord[1] + 25 + snap_value),
+ (mouse_coord[0] - snap_value, mouse_coord[1] - 25 - snap_value),
+ (mouse_coord[0] + 25 + snap_value, mouse_coord[1] - snap_value),
+ (mouse_coord[0] - 25 - snap_value, mouse_coord[1] - snap_value),
+ ]
+ draw_shader(self, color, 0.3, 'LINES', grid_coords, size=2)
+
+
+def draw_shader(self, color, alpha, type, coords, size=1, indices=None):
+ """ Create a batch for a draw type """
+ bgl.glEnable(bgl.GL_BLEND)
+ bgl.glEnable(bgl.GL_LINE_SMOOTH)
+ if type =='POINTS':
+ bgl.glPointSize(size)
+ else:
+ bgl.glLineWidth(size)
+ try:
+ if len(coords[0])>2:
+ shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
+ else:
+ shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
+ batch = batch_for_shader(shader, type, {"pos": coords}, indices=indices)
+ shader.bind()
+ shader.uniform_float("color", (color[0], color[1], color[2], alpha))
+ batch.draw(shader)
+ bgl.glLineWidth(1)
+ bgl.glPointSize(1)
+ bgl.glDisable(bgl.GL_LINE_SMOOTH)
+ bgl.glDisable(bgl.GL_BLEND)
+ except:
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ self.report({'ERROR'}, str(exc_value))