# -*- coding: utf-8 -*- # ##### 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": "CAD Support Tool", "description": "Pie menu for CAD data handling and modeling. Press Ctrl Shift E to display the menu. Worked on Ver 3.0", "author": "Toudou++", "version": (2, 2, 0), "blender": (3, 0, 0), "location": "Pie Menu", "doc_url": "https://www.cgradproject.com/archives/5572", "category": "Pie Menu" } import bpy, bmesh, math, mathutils from bpy.types import Menu, Operator from bpy.props import FloatProperty, StringProperty class VIEW3D_MT_CADSupportTool(Menu): bl_idname = "VIEW3D_MT_CADSupportTool" bl_label = "CAD_Support_Tool" def draw(self, context): layout = self.layout ob=context.object if ob and ob.type == 'MESH' and ob.mode == 'OBJECT': pie = layout.menu_pie() #Left slot pie.operator("class.create_custom_cube", text ="Create Custom Cube", icon ='MATCUBE') #Right slot pie.operator("class.create_cable", text ="Create Custom Cable", icon ='GP_SELECT_STROKES') #Bottom slot pie.operator("class.add_decimate_modifier", text ="Add Decimate", icon ='MOD_DECIM') #Top slot pie.operator("import_mesh.stl", text ="Import STL", icon ='IMPORT').global_scale=0.001 pie.separator() pie.separator() pie.separator() #Right Bottom slot pie.operator("class.apply_decimate_modifier", text ="Apply Decimate", icon ='MODIFIER') elif ob and ob.type == 'CURVE': pie = layout.menu_pie() #Left slot pie.operator("class.calc_curve_length", text ="Calculate Curve Length", icon ='ANIM') #Right slot pie.operator("class.create_cable", text ="Create Custom Cable", icon ='GP_SELECT_STROKES') elif ob and ob.type == 'MESH' and ob.mode == 'EDIT': pie = layout.menu_pie() #Left slot pie.operator("class.change_edge_length", text ="Set Edge Length", icon ='ARROW_LEFTRIGHT') #Right slot pie.operator("class.show_edge_length", text ="Show Edge Length", icon ='CENTER_ONLY') #Bottom slot pie.operator("mesh.tris_convert_to_quads", text ="Convert Tris to Quads", icon ='MESH_PLANE') #Top slot pie.operator("mesh.remove_doubles", text ="Remove Double Vertices", icon ='XRAY').threshold=0.001 else: pie = layout.menu_pie() #Left slot pie.operator("class.create_custom_cube", text ="Create Custom Cube", icon ='MATCUBE') #Right slot pie.operator("class.create_cable", text ="Create Custom Cable", icon ='GP_SELECT_STROKES') #Bottom slot pie.separator() #Top slot pie.operator("import_mesh.stl", text ="Import STL", icon ='IMPORT').global_scale=0.001 class TOU_OT_create_custom_cube(bpy.types.Operator): bl_idname = "class.create_custom_cube" bl_label = "Create Custom Cube" bl_options = {'REGISTER', 'UNDO'} target_W : StringProperty(name = "X") target_D : StringProperty(name = "Y") target_H : StringProperty(name = "Z") myObject_string : StringProperty( name = "ObjectName", default = "Cube" ) def execute(self, context): x = float(self.target_W) / 2 / 1000 y = float(self.target_D) / 2 / 1000 z = float(self.target_H) / 1000 verts=[] verts.append((x, -y, 0)) verts.append((x, y, 0)) verts.append((-x, y, 0)) verts.append((-x, -y, 0)) verts.append((x, -y, z)) verts.append((x, y, z)) verts.append((-x, y, z)) verts.append((-x, -y, z)) faces = [(0, 3, 2, 1), (4, 5, 6, 7), (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (4, 7, 3, 0)] mesh_data = bpy.data.meshes.new(self.myObject_string) mesh_data.from_pydata(verts, [], faces) mesh_data.update() ob = bpy.data.objects.new(self.myObject_string, mesh_data) ob.location = bpy.context.scene.cursor.location bpy.context.collection.objects.link(ob) bpy.context.space_data.overlay.show_extra_edge_length = True bpy.context.view_layer.objects.active = ob ob.select_set(state=True) return {'FINISHED'} def draw(self, context): layout = self.layout col = layout.column() row = layout.row(align=True) row.label(text="Object Name :") row.prop(self,"myObject_string", text="") col = layout.column() col.label(text="Dimensions(mm) :") row = layout.row() row.prop(self,"target_W") row.prop(self,"target_D") row.prop(self,"target_H") def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self, width=350) class TOU_OT_show_edge_length(bpy.types.Operator): bl_idname = "class.show_edge_length" bl_label = "Show Edge Length" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): sel =bpy.context.space_data.overlay if sel.show_extra_edge_length == False: sel.show_extra_edge_length = True else: sel.show_extra_edge_length = False return {'FINISHED'} class TOU_OT_create_cable(bpy.types.Operator): bl_idname = "class.create_cable" bl_label = "Create Custom Cable" bl_options = {'REGISTER', 'UNDO'} target_R : StringProperty(name = "Radius(mm)") myObject_string : StringProperty( name = "ObjectName", default = "Cable" ) def execute(self, context): bpy.ops.curve.primitive_bezier_curve_add() curve_data=bpy.context.object curve_data.name = self.myObject_string curve_data.data.dimensions ='3D' curve_data.data.resolution_u = 18 curve_data.data.fill_mode = 'FULL' curve_data.data.bevel_depth = float(self.target_R) / 1000 curve_data.data.bevel_resolution = 4 return {'FINISHED'} def draw(self, context): layout = self.layout col = layout.column() row = layout.row(align=True) row.label(text="Object Name :") row.prop(self,"myObject_string", text="") row = layout.row() row.prop(self,"target_R") def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self) class TOU_OT_calc_curve_length(bpy.types.Operator): bl_idname = "class.calc_curve_length" bl_label = "Calculate Curve Length" def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self) def draw(self, context): objs = context.selected_objects layout = self.layout col = layout.column() row = layout.row(align=True) row.label(text = str('{0:.2f}'.format(calLength(objs)*1000))+" mm") def execute(self, context): return {'FINISHED'} class TOU_OT_change_edge_length(bpy.types.Operator): bl_idname = "class.change_edge_length" bl_label = "Set Edge Length" target_length : FloatProperty(name = 'length(mm) :', default = 0.0) count = 0 me = vts_sequence = None @classmethod def poll(cls, context): return (context.edit_object) def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self) def execute(self, context): self.count += 1 if self.count == 1: ob = context.edit_object self.me = ob.data bm = bmesh.from_edit_mesh(self.me) if len(bm.select_history) > 1 and isinstance(bm.select_history[-1], bmesh.types.BMVert): self.vts_sequence = [i.index for i in [bm.select_history[j] for j in (-2,-1)]] elif len(bm.select_history) > 0 and isinstance(bm.select_history[-1], bmesh.types.BMEdge): self.vts_sequence = [i.index for i in bm.select_history[-1].verts] else: self.report({'ERROR'}, '1 edge OR 2 vertecies. Changing select mode will lost select-history.') return {'CANCELLED'} if abs(self.target_length) < 0.000000000001: tmp_vts_sequence = self.vts_sequence vector = bm.verts[tmp_vts_sequence[-2]].co - bm.verts[tmp_vts_sequence[-1]].co self.target_length = vector.length self.execute(context) else: bm = bmesh.from_edit_mesh(self.me) tmp_vts_sequence = self.vts_sequence vector = bm.verts[tmp_vts_sequence[-2]].co - bm.verts[tmp_vts_sequence[-1]].co vector.length = abs(self.target_length / 1000) if self.target_length > 0: bm.verts[tmp_vts_sequence[-1]].co = bm.verts[tmp_vts_sequence[-2]].co - vector elif self.target_length < 0: bm.verts[tmp_vts_sequence[-1]].co = bm.verts[tmp_vts_sequence[-2]].co + vector bmesh.update_edit_mesh(self.me, loop_triangles=True, destructive=False) return {'FINISHED'} class TOU_OT_add_decimate_modifier(bpy.types.Operator): bl_idname = "class.add_decimate_modifier" bl_label = "Add Decimate Modifier" bl_options = {'REGISTER', 'UNDO'} ratio: FloatProperty(name = "Decimate Ratio :", default = 0.4, min = 0, max = 1 ) countmodal =0 @classmethod def poll(cls, context): return context.object is not None def execute(self, context): ob=bpy.context if "mCusDecimate" not in ob.object.modifiers: bpy.ops.object.modifier_add(type='DECIMATE') ob.object.modifiers[len(ob.object.modifiers)-1].name = 'mCusDecimate' ob.object.modifiers["mCusDecimate"].ratio = self.ratio return {'FINISHED'} def modal(self, context, event): ob=bpy.context self.countmodal +=1 if event.type == 'WHEELUPMOUSE': self.ratio += 0.02 ob.object.modifiers["mCusDecimate"].ratio = self.ratio elif event.type == 'WHEELDOWNMOUSE': self.ratio -= 0.02 ob.object.modifiers["mCusDecimate"].ratio = self.ratio elif event.type == 'LEFTMOUSE': return {'FINISHED'} elif event.type in {'RIGHTMOUSE', 'ESC'}: bpy.ops.object.modifier_remove(modifier="mCusDecimate") return {'CANCELLED'} return {'RUNNING_MODAL'} def invoke(self, context, event): ob=bpy.context if context.object: if "mCusDecimate" not in ob.object.modifiers: bpy.ops.object.modifier_add(type='DECIMATE') ob.object.modifiers[len(ob.object.modifiers)-1].name = 'mCusDecimate' ob.object.modifiers["mCusDecimate"].ratio = self.ratio context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} else: self.report({'WARNING'}, "No active object, could not finish") return {'CANCELLED'} class TOU_OT_apply_decimate_modifier(bpy.types.Operator): bl_idname = "class.apply_decimate_modifier" bl_label = "Apply the Decimate Modifier" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): ob=bpy.context if "mCusDecimate" in ob.object.modifiers: bpy.ops.object.modifier_apply(modifier="mCusDecimate", report=True) return {'FINISHED'} classes = ( VIEW3D_MT_CADSupportTool, TOU_OT_create_custom_cube, TOU_OT_show_edge_length, TOU_OT_create_cable, TOU_OT_calc_curve_length, TOU_OT_change_edge_length, TOU_OT_add_decimate_modifier, TOU_OT_apply_decimate_modifier, ) addon_keymaps = [] def calLength(objs): length = 0.0 for obj in objs: if obj.type == "CURVE": prec = 1000 inc = 1/prec print# prec for i in range(0, prec): ti = i*inc tf = (i+1)*inc a = calct(obj, ti) b = calct(obj, tf) r = (b-a).magnitude length+=r return length def calct(obj, t): spl = None mw = obj.matrix_world if obj.data.splines.active == None: if len(obj.data.splines)>0: spl = obj.data.splines[0] else: spl = obj.data.splines.active if spl == None: return False if spl.type == "BEZIER": points = spl.bezier_points nsegs = len(points)-1 d = 1.0/nsegs seg = int(t/d) t1 = t/d - int(t/d) if t == 1: seg -= 1 t1 = 1.0 p = getbezpoints(spl, mw, seg) coord = cubic(p,t1) return coord def getbezpoints(spl, mt, seg=0): points = spl.bezier_points p0 = points[seg].co @ mt p1 = points[seg].handle_right @ mt p2 = points[seg+1].handle_left @ mt p3 = points[seg+1].co @ mt return p0, p1, p2, p3 def cubic(p, t): return p[0]*(1.0-t)**3.0+3.0*p[1]*t*(1.0-t)**2.0+3.0*p[2]*(t**2.0)*(1.0-t)+p[3]*t**3.0 def register(): for cls in classes: bpy.utils.register_class(cls) wm = bpy.context.window_manager kc = wm.keyconfigs.addon if kc: # km = wm.keyconfigs.addon.keymaps.new(name = "Window",space_type='EMPTY', region_type='WINDOW') km = wm.keyconfigs.addon.keymaps.new(name = '3D View Generic', space_type='VIEW_3D', region_type='WINDOW') kmi = km.keymap_items.new('wm.call_menu_pie', 'E', 'PRESS' ,ctrl=True, shift=True, alt=False) kmi.properties.name = "VIEW3D_MT_CADSupportTool" kmi.active = True addon_keymaps.append((km,kmi)) def unregister(): for cls in classes: bpy.utils.unregister_class(cls) wm = bpy.context.window_manager kc = wm.keyconfigs.addon if kc: for km, kmi in addon_keymaps: km.keymap_items.remove(kmi) addon_keymaps.clear() if __name__ == "__main__": register()