import bpy, math from mathutils import Vector from pathlib import Path ROOT = Path(r'C:\projects\wheel') OUT = ROOT / 'models' / 'turn_signal_cam_concept' OUT.mkdir(parents=True, exist_ok=True) bpy.ops.object.select_all(action='SELECT'); bpy.ops.object.delete(use_global=False) def mat(name, color, metallic=0): m=bpy.data.materials.new(name); m.diffuse_color=(*color,1); m.use_nodes=True p=m.node_tree.nodes.get('Principled BSDF'); p.inputs['Base Color'].default_value=(*color,1); p.inputs['Metallic'].default_value=metallic; p.inputs['Roughness'].default_value=.4 return m blue=mat('Ring upper',(.055,.33,.7)); blue2=mat('Ring lower',(.08,.48,.8)); steel=mat('Shaft',(.36,.4,.43),.7); brass=mat('Key',(.93,.58,.13),.6); boltmat=mat('Bolts',(.23,.25,.28),.7); cammat=blue def box(name,location,scale,material=None): bpy.ops.mesh.primitive_cube_add(size=1,location=location); o=bpy.context.object;o.name=name;o.dimensions=scale;bpy.ops.object.transform_apply(location=False,rotation=False,scale=True) if material:o.data.materials.append(material) return o def boolean(obj,other,op): bpy.context.view_layer.objects.active=obj; mod=obj.modifiers.new('shape', 'BOOLEAN');mod.operation=op;mod.object=other;bpy.ops.object.modifier_apply(modifier=mod.name);bpy.data.objects.remove(other,do_unlink=True) def half(name,a0,a1,color): n=80; verts=[]; faces=[] for z in (0,8): for radius in (6,13): for i in range(n+1): a=math.radians(a0+(a1-a0)*i/n);verts.append((radius*math.cos(a),radius*math.sin(a),z)) off=n+1 for i in range(n): faces += [(i,i+1,off+i+1,off+i), (2*off+i,3*off+i,3*off+i+1,2*off+i+1), (i,2*off+i,2*off+i+1,i+1), (off+i,off+i+1,3*off+i+1,3*off+i)] faces.extend([(0,off,3*off,2*off),(n,2*off+n,3*off+n,off+n)]) mesh=bpy.data.meshes.new(name);mesh.from_pydata(verts,[],faces);mesh.update();o=bpy.data.objects.new(name,mesh);bpy.context.collection.objects.link(o);o.data.materials.append(color) return o top=half('Upper half with keyway',2,178,blue);bot=half('Lower half',182,358,blue2) boolean(top,box('slot',(0,6.5,4),(4.1,3.6,8.2)),'DIFFERENCE') # Each clamp ear overlaps the relevant semicircle. The ear screw passages run along Y. for side in (-1,1): for ring,y in ((top,2.65),(bot,-2.65)): boolean(ring,box('ear',(side*12.8,y,4),(6.0,5.0,8)),'UNION') # Head and shank are illustrative, separate parts. bpy.ops.mesh.primitive_cylinder_add(vertices=24,radius=1.4,depth=12,location=(side*13.1,0,4),rotation=(math.pi/2,0,0));bpy.context.object.name='M3 shank schematic';bpy.context.object.data.materials.append(boltmat) bpy.ops.mesh.primitive_cylinder_add(vertices=6,radius=2.4,depth=2,location=(side*13.1,6,4),rotation=(math.pi/2,0,0));bpy.context.object.name='M3 bolt head schematic';bpy.context.object.data.materials.append(boltmat) bpy.ops.mesh.primitive_cylinder_add(vertices=6,radius=2.4,depth=2,location=(side*13.1,-6,4),rotation=(math.pi/2,0,0));bpy.context.object.name='M3 nut schematic';bpy.context.object.data.materials.append(boltmat) # The protrusion overlaps the top semicircular ring before a union: one continuous piece. boolean(top,box('cam',(0,15,4),(8,7,8)),'UNION') bpy.ops.mesh.primitive_cylinder_add(vertices=96,radius=6,depth=28,location=(0,0,4));shaft=bpy.context.object;shaft.name='12mm shaft';shaft.data.materials.append(steel) boolean(shaft,box('shaft keyway',(0,6.2,4),(4.1,3.9,28.1)),'DIFFERENCE') key=box('4x4mm metal key',(0,6.2,5),(4,4,12),brass) # ground plane and soft lighting ground=box('Ground',(0,0,-12.5),(180,180,1),mat('Ground',(.97,.975,.985))) world=bpy.context.scene.world;world.color=(.8,.8,.8) def light(loc,power,size): bpy.ops.object.light_add(type='AREA',location=loc);o=bpy.context.object;o.data.energy=power;o.data.shape='DISK';o.data.size=size;o.rotation_euler=(Vector((0,0,0))-o.location).to_track_quat('-Z','Y').to_euler() light((5,10,65),25000,45);light((-30,-10,40),15000,35) bpy.ops.object.camera_add();camera=bpy.context.object;bpy.context.scene.camera=camera;camera.data.type='ORTHO';camera.data.ortho_scale=67 scene=bpy.context.scene;scene.render.engine='CYCLES';scene.cycles.samples=24;scene.render.resolution_x=1200;scene.render.resolution_y=900;scene.render.resolution_percentage=100;scene.render.image_settings.file_format='PNG';scene.world.color=(.8,.8,.8) camera.location=(43,60,58);camera.rotation_euler=(Vector((0,2,4))-camera.location).to_track_quat('-Z','Y').to_euler();scene.render.filepath=str(OUT/'perspective.png');bpy.ops.render.render(write_still=True) camera.location=(0,0,75);camera.rotation_euler=(0,0,0);camera.rotation_euler=(Vector((0,0,0))-camera.location).to_track_quat('-Z','Y').to_euler();camera.data.ortho_scale=52;scene.render.filepath=str(OUT/'front.png');bpy.ops.render.render(write_still=True)