ambi-recurve-limb-flat.py raw

   1  """
   2  Flat limb strip cutout — the 316L sheet blank before any bending.
   3  A tapered hexagon, widest at center, symmetric taper to tips.
   4  3 bolt holes at center. Thickness from sheet stock (3.5mm at center
   5  tapering to 1.4mm at tips — but this is a flat cutout so we use
   6  a single sheet thickness and show the WIDTH taper only).
   7  
   8  Run in Blender: Scripting workspace > Open > Run Script
   9  """
  10  
  11  import bpy
  12  import bmesh
  13  import math
  14  
  15  def clear_scene():
  16      bpy.ops.object.select_all(action='SELECT')
  17      bpy.ops.object.delete()
  18  
  19  def make_material(name, color, metallic=0.0, roughness=0.5):
  20      mat = bpy.data.materials.new(name)
  21      mat.use_nodes = True
  22      bsdf = mat.node_tree.nodes["Principled BSDF"]
  23      bsdf.inputs["Base Color"].default_value = (*color, 1.0)
  24      bsdf.inputs["Metallic"].default_value = metallic
  25      bsdf.inputs["Roughness"].default_value = roughness
  26      return mat
  27  
  28  def main():
  29      clear_scene()
  30  
  31      steel = make_material("316L_FullHard", (0.08, 0.08, 0.08), metallic=0.8, roughness=0.4)
  32  
  33      # Taper profile from doc (distance from center in mm, half-width in mm)
  34      # Symmetric — we define one side and mirror.
  35      #   Center:  44mm wide  (half = 22)
  36      #   140mm:   40mm wide  (half = 20)
  37      #   250mm:   32mm wide  (half = 16)
  38      #   350mm:   24mm wide  (half = 12)
  39      #   500mm:   20mm wide  (half = 10)
  40      #
  41      # Thickness taper (for the Z extrusion at each section):
  42      #   Center:  3.5mm
  43      #   140mm:   3.0mm
  44      #   250mm:   2.2mm
  45      #   350mm:   1.8mm
  46      #   500mm:   1.4mm
  47  
  48      # Profile points: (y_mm, half_width_mm, thickness_mm)
  49      profile = [
  50          (0,   22.0, 3.5),
  51          (50,  21.5, 3.35),
  52          (100, 20.8, 3.15),
  53          (140, 20.0, 3.0),
  54          (180, 18.4, 2.7),
  55          (220, 17.2, 2.4),
  56          (250, 16.0, 2.2),
  57          (280, 14.6, 2.05),
  58          (310, 13.2, 1.9),
  59          (350, 12.0, 1.8),
  60          (380, 11.2, 1.7),
  61          (420, 10.6, 1.55),
  62          (460, 10.2, 1.45),
  63          (500, 10.0, 1.4),
  64      ]
  65  
  66      # Build mesh: for each Y station, create a rectangular cross-section
  67      # (width varies, thickness varies). Full strip is symmetric about Y=0.
  68      bm = bmesh.new()
  69  
  70      # Build both halves: negative Y then positive Y
  71      all_stations = []
  72      for y_mm, hw_mm, th_mm in reversed(profile):
  73          if y_mm > 0:
  74              all_stations.append((-y_mm, hw_mm, th_mm))
  75      for y_mm, hw_mm, th_mm in profile:
  76          all_stations.append((y_mm, hw_mm, th_mm))
  77  
  78      # Scale to meters
  79      for y_mm, hw_mm, th_mm in all_stations:
  80          y = y_mm / 1000.0
  81          hw = hw_mm / 1000.0
  82          th = th_mm / 1000.0
  83  
  84          # Four corners of cross-section at this Y station
  85          bm.verts.new((-hw, y, 0))
  86          bm.verts.new(( hw, y, 0))
  87          bm.verts.new(( hw, y, th))
  88          bm.verts.new((-hw, y, th))
  89  
  90      bm.verts.ensure_lookup_table()
  91      n = len(all_stations)
  92  
  93      # Side faces
  94      for i in range(n - 1):
  95          base = i * 4
  96          for j in range(4):
  97              v0 = bm.verts[base + j]
  98              v1 = bm.verts[base + (j + 1) % 4]
  99              v2 = bm.verts[base + 4 + (j + 1) % 4]
 100              v3 = bm.verts[base + 4 + j]
 101              bm.faces.new([v0, v1, v2, v3])
 102  
 103      # Cap ends
 104      bm.faces.new([bm.verts[0], bm.verts[1], bm.verts[2], bm.verts[3]])
 105      last = (n - 1) * 4
 106      bm.faces.new([bm.verts[last+3], bm.verts[last+2], bm.verts[last+1], bm.verts[last]])
 107  
 108      mesh = bpy.data.meshes.new("Limb_Strip_Flat")
 109      bm.to_mesh(mesh)
 110      bm.free()
 111  
 112      obj = bpy.data.objects.new("Limb_Strip_Flat", mesh)
 113      bpy.context.scene.collection.objects.link(obj)
 114      obj.data.materials.append(steel)
 115  
 116      # Smooth shading
 117      for f in obj.data.polygons:
 118          f.use_smooth = True
 119  
 120      # Subdivision for smoothness
 121      mod = obj.modifiers.new("Subsurf", 'SUBSURF')
 122      mod.levels = 2
 123      mod.render_levels = 3
 124  
 125      # --- Bolt holes (3x M6, 30mm spacing at center) ---
 126      for offset_mm in [-30, 0, 30]:
 127          bpy.ops.mesh.primitive_cylinder_add(
 128              radius=0.00325,  # 6.5mm clearance hole / 2
 129              depth=0.006,     # through the thickness
 130              location=(0, offset_mm / 1000.0, 0.00175))
 131          hole = bpy.context.active_object
 132          hole.name = f"BoltHole_{offset_mm}"
 133  
 134          # Boolean difference
 135          mod = obj.modifiers.new(f"Hole_{offset_mm}", 'BOOLEAN')
 136          mod.operation = 'DIFFERENCE'
 137          mod.object = hole
 138          bpy.context.view_layer.objects.active = obj
 139          bpy.ops.object.modifier_apply(modifier=f"Hole_{offset_mm}")
 140          bpy.data.objects.remove(hole)
 141  
 142      bpy.ops.object.select_all(action='DESELECT')
 143      obj.select_set(True)
 144      bpy.context.view_layer.objects.active = obj
 145  
 146      print("Flat limb strip cutout created.")
 147      print("1000mm total length, tapered width (44mm center -> 20mm tips),")
 148      print("tapered thickness (3.5mm center -> 1.4mm tips), 3x M6 bolt holes.")
 149      print("This is the blank before press-brake recurve forming.")
 150  
 151  if __name__ == "__main__":
 152      main()
 153