import bpy from mathutils import Euler, Vector import math import bmesh def set_origin_to_bottom_center(obj): """ Sets the origin of the given object to its bottom center. Args: obj (bpy.types.Object): The object whose origin will be set. """ # Ensure the object is active and selected bpy.ops.object.select_all(action='DESELECT') obj.select_set(True) bpy.context.view_layer.objects.active = obj # Calculate the bounding box in local coordinates bbox_corners = obj.bound_box min_z = min([corner[2] for corner in bbox_corners]) max_z = max([corner[2] for corner in bbox_corners]) # Calculate the bottom center in local coordinates center_x = (max([corner[0] for corner in bbox_corners]) + min([corner[0] for corner in bbox_corners])) / 2 center_y = (max([corner[1] for corner in bbox_corners]) + min([corner[1] for corner in bbox_corners])) / 2 bottom_center_local = (center_x, center_y, min_z) # Transform the local bottom center to world coordinates bottom_center_world = obj.matrix_world @ Vector(bottom_center_local) # Set the 3D cursor to the bottom center bpy.context.scene.cursor.location = bottom_center_world # Set the origin to the 3D cursor bpy.ops.object.origin_set(type='ORIGIN_CURSOR') import bpy from mathutils import Vector def create_cube(location, dimensions, name): """ Creates a cube by stitching vertices together at the specified location and dimensions, and sets its origin to the bottom center. Args: location (tuple): A tuple (x, y, z) specifying the bottom center of the cube. dimensions (tuple): A tuple (x, y, z) specifying the cube's dimensions. name (str): The name to assign to the cube. Returns: bpy.types.Object: The created cube object. """ # Calculate half dimensions hx, hy, hz = dimensions[0] / 2, dimensions[1] / 2, dimensions[2] # Define the vertices of the cube vertices = [ Vector((-hx, -hy, 0)), # Bottom face Vector((hx, -hy, 0)), Vector((hx, hy, 0)), Vector((-hx, hy, 0)), Vector((-hx, -hy, hz)), # Top face Vector((hx, -hy, hz)), Vector((hx, hy, hz)), Vector((-hx, hy, hz)), ] # Define the faces using the vertices faces = [ (0, 1, 2, 3), # Bottom face (4, 5, 6, 7), # Top face (0, 1, 5, 4), # Side faces (1, 2, 6, 5), (2, 3, 7, 6), (3, 0, 4, 7), ] # Create a new mesh and object mesh = bpy.data.meshes.new(name) cube_object = bpy.data.objects.new(name, mesh) # Link the object to the scene bpy.context.collection.objects.link(cube_object) # Create the mesh from vertices and faces mesh.from_pydata(vertices, [], faces) mesh.update() # Move the object to the correct location cube_object.location = Vector(location) # Set origin to bottom center bpy.context.view_layer.objects.active = cube_object bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='BOUNDS') return cube_object def create_cylinder(location, r, height, name, rotation=(0, 0, 0)): """ Creates a cylinder at the specified location with the given radius and height and sets its origin to the bottom center. Args: location (tuple): A tuple (x, y, z) specifying the cylinder's location. r (float): The radius of the cylinder. height (float): The height of the cylinder. name (str): The name to assign to the cylinder. rotation (tuple): A tuple (x, y, z) specifying the rotation in radians. Returns: bpy.types.Object: The created cylinder object. """ bpy.ops.mesh.primitive_cylinder_add(location=location, radius=r, depth=height) cylinder = bpy.context.active_object cylinder.name = name cylinder.rotation_euler = Euler(rotation) # Set origin to bottom center set_origin_to_bottom_center(cylinder) return cylinder def clear_scene(): """Deletes all objects in the current scene.""" bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action="SELECT") bpy.ops.object.delete(use_global=False) def subtract_model(obj1, obj2, remove=True, fast_mode=True): """ Subtracts obj2 from obj1 using a Boolean modifier. Optionally removes obj2 from the scene after the operation. Allows switching between Fast and Exact modes. """ if obj1 is None or obj2 is None: print("Error: One of the objects is None. Cannot perform Boolean operation.") return if boolean_disable: print("Boolean operations are disabled. Skipping subtraction.") return # Add a Boolean modifier to obj1 bool_mod = obj1.modifiers.new(name="BooleanModifier", type="BOOLEAN") bool_mod.object = obj2 bool_mod.operation = "DIFFERENCE" # Set the solver to Fast or Exact bool_mod.solver = "FAST" if fast_mode else "EXACT" # Apply the modifier bpy.context.view_layer.objects.active = obj1 bpy.ops.object.modifier_apply(modifier=bool_mod.name) # Optionally remove obj2 if remove: bpy.data.objects.remove(obj2) def add_model(obj1, obj2, remove=True, fast_mode=True): """ Adds obj2 to obj1 using a Boolean modifier. Optionally removes obj2 from the scene after the operation. Allows switching between Fast and Exact modes. """ if obj1 is None or obj2 is None: print("Error: One of the objects is None. Cannot perform Boolean operation.") return if boolean_disable: print("Boolean operations are disabled. Skipping addition.") return # Add a Boolean modifier to obj1 bool_mod = obj1.modifiers.new(name="BooleanModifier", type="BOOLEAN") bool_mod.object = obj2 bool_mod.operation = "UNION" # Set the solver to Fast or Exact bool_mod.solver = "FAST" if fast_mode else "EXACT" # Apply the modifier bpy.context.view_layer.objects.active = obj1 bpy.ops.object.modifier_apply(modifier=bool_mod.name) # Optionally remove obj2 if remove: bpy.data.objects.remove(obj2) def duplicate_model(obj, new_name): """ Duplicates an object and assigns it a new name. Args: obj (bpy.types.Object): The object to duplicate. new_name (str): The name to assign to the duplicate object. Returns: bpy.types.Object: The duplicated object. """ if not obj: print("Error: No object provided to duplicate.") return None # Deselect all objects bpy.ops.object.select_all(action='DESELECT') # Select the object to duplicate obj.select_set(True) bpy.context.view_layer.objects.active = obj # Duplicate the object bpy.ops.object.duplicate() duplicate = bpy.context.active_object duplicate.name = new_name # Set origin to bottom center for the duplicate set_origin_to_bottom_center(duplicate) return duplicate def remove_object(obj2): bpy.data.objects.remove(obj2) # Global flag to disable Boolean operations boolean_disable = False # Clear the scene clear_scene() def make_iron_holder(): # Create the main cube with correct dimensions and origin main_obj = create_cube(location=(0, 0, 0), dimensions=(20, 20, 20), name="Part") # Create the cylinder and add it to the main cube cylinder = create_cylinder(location=(10, 0, 0), r=10, height=20, name="Cylinder_rounding") subtract_model(cylinder,main_obj,False) add_model(main_obj, cylinder) #drill out 17 dia cylinder = create_cylinder(location=(10, 0, 10), r=17/2, height=13, name="Cylinder17mm") subtract_model(main_obj,cylinder) #center 14 dia cylinder = create_cylinder(location=(10, 0, -3), r=14/2, height=20, name="Cylinder14mm") subtract_model(main_obj,cylinder) #ceneter 14 dia cylinder = create_cylinder(location=(15, 0, -3), r=13/2, height=35, name="Cylinder13mm_bore") subtract_model(main_obj,cylinder) obj_sdh = create_cube(location=(0, 25, 0), dimensions=(20, 30, 20), name="side_cord_holder") # Create the cylinder and add it to the main cube cylinder = create_cylinder(location=(00, 40, 0), r=10, height=20, name="sh_rounding") subtract_model(cylinder,main_obj,False) add_model(obj_sdh, cylinder) # drill out 12 cylinder = create_cylinder(location=(0, 40, 0), r=12/2, height=20, name="sh_Cylinder12mm") subtract_model(obj_sdh,cylinder) # drill out 17 cylinder = create_cylinder(location=(0, 40, 7), r=17/2, height=20, name="sh_Cylinder17mm") subtract_model(obj_sdh,cylinder) #ceneter 14 dia cylinder = create_cylinder(location=(0, 50, 7), r=13/2, height=35, name="sh_Cylinder13mm_bore") subtract_model(obj_sdh,cylinder) add_model(main_obj,obj_sdh) def merge_nerby_vertices(obj, threshold=0.001): """ Merges nearby vertices in the given object based on the threshold distance. Args: obj (bpy.types.Object): The object whose vertices will be merged. threshold (float): The maximum distance between vertices to merge. """ bpy.context.view_layer.objects.active = obj bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.remove_doubles(threshold=threshold) bpy.ops.object.mode_set(mode='OBJECT') def make_caliper_holder(): # cut slot out def cut_slot(main_obj): cutter = create_cube(location=(1,0,1.6),dimensions=(26,16.3,10.6),name="cutter_slot") subtract_model(main_obj,cutter) def cut_bottom_slant(main_obj): cutter = create_cube(location=(7.6814,0,0.5521),dimensions=(6,15.8,6),name="cutter_bottom_slot") rotation=(0,0.244346,0) # ~14 degree cutter.rotation_euler = Euler(rotation) subtract_model(main_obj,cutter) def make_lid(main_obj): lid = create_cube(location=(0, 0, 8.7), dimensions=(19, 27.4, 1.4), name="Lid") subtract_model(lid,main_obj,remove=False)# make a negative impression on the lid of the box to make it fit rotation=(0,3.14,0) # flip the lid face down lid.rotation_euler = Euler(rotation) lid.location.x= 25 lid.location.y=0 lid.location.z = 0 main_obj = create_cube(location=(0, 0, 0), dimensions=(20, 28, 9.5), name="Part") cut_slot(main_obj) cut_bottom_slant(main_obj) merge_nerby_vertices(main_obj,0.05) # merge vertices within 0.05 distance lid = make_lid(main_obj) make_caliper_holder()