{"id":12106,"date":"2026-03-24T21:35:00","date_gmt":"2026-03-25T02:35:00","guid":{"rendered":"https:\/\/www.rushworth.us\/lisa\/?p=12106"},"modified":"2026-03-27T13:37:06","modified_gmt":"2026-03-27T18:37:06","slug":"blender-api-bending-a-plane","status":"publish","type":"post","link":"https:\/\/www.rushworth.us\/lisa\/?p=12106","title":{"rendered":"Blender API: Bending a 2D Rectangle"},"content":{"rendered":"\n<p>Another attempt to create a t-post bracket using a script. This creates a 2D rectangle, bends it, and then solidifies it into a 3d object. <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nimport bpy\nimport bmesh\nimport math\nfrom mathutils import Vector, Matrix\n\n# -----------------------------\n# Reset \/ clear scene\n# -----------------------------\nfor obj in list(bpy.data.objects):\n    bpy.data.objects.remove(obj, do_unlink=True)\n\n# -----------------------------\n# Scene units: mm (1 BU = 1 mm)\n# -----------------------------\nscene = bpy.context.scene\nscene.unit_settings.system = &#039;METRIC&#039;\nscene.unit_settings.scale_length = 0.001\n\nINCH_TO_MM = 25.4\ndef inch(x):  # returns mm (Blender units)\n    return x * INCH_TO_MM\n\n# -----------------------------\n# Parameters\n# -----------------------------\nsize_x_in = 3.0\nsize_y_in = 7.0\nthickness_in = 0.25  # SOLIDIFY thickness\n\nfold1_offset_in = 0.5   # from MIN-Y end\nfold2_offset_in = 2.0   # from MIN-Y end\n\nfold1_rad = math.radians(-80.0)\nfold2_rad = math.radians(80.0)\n\nsubdivide_cuts = 60\nEPS_Y = 1e-5  # mm tolerance for &quot;on the fold line&quot;\n\n# -----------------------------\n# Create flat sheet (plane)\n# -----------------------------\nbpy.ops.mesh.primitive_plane_add(size=1.0, location=(0.0, 0.0, 0.0))\nobj = bpy.context.active_object\nobj.name = &quot;Bracket&quot;\nobj.dimensions = (inch(size_x_in), inch(size_y_in), 0.0)\nbpy.ops.object.transform_apply(location=False, rotation=False, scale=True)\n\n# Subdivide for clean fold lines\nbpy.ops.object.mode_set(mode=&#039;EDIT&#039;)\nbpy.ops.mesh.select_all(action=&#039;SELECT&#039;)\nbpy.ops.mesh.subdivide(number_cuts=subdivide_cuts)\nbpy.ops.object.mode_set(mode=&#039;OBJECT&#039;)\n\n# Compute fold Y positions\nhalf_y = inch(size_y_in) \/ 2.0\nmin_y = -half_y\ny_fold1 = min_y + inch(fold1_offset_in)\ny_fold2 = min_y + inch(fold2_offset_in)\n\n# Add both fold lines\nbm = bmesh.new()\nbm.from_mesh(obj.data)\n\nfor y_fold in (y_fold1, y_fold2):\n    geom = bm.verts&#x5B;:] + bm.edges&#x5B;:] + bm.faces&#x5B;:]\n    bmesh.ops.bisect_plane(\n        bm,\n        geom=geom,\n        plane_co=Vector((0.0, y_fold, 0.0)),\n        plane_no=Vector((0.0, 1.0, 0.0)),\n        clear_inner=False,\n        clear_outer=False\n    )\n\nbm.normal_update()\nbm.to_mesh(obj.data)\nbm.free()\n\n# -----------------------------\n# Re-open bmesh, store ORIGINAL Y per vertex\n# -----------------------------\nbm = bmesh.new()\nbm.from_mesh(obj.data)\nbm.verts.ensure_lookup_table()\n\norig_y_layer = bm.verts.layers.float.new(&quot;orig_y&quot;)\nfor v in bm.verts:\n    v&#x5B;orig_y_layer] = v.co.y\n\n# ============================================================\n# FOLD 1\n# ============================================================\nhinge_verts_1 = &#x5B;v for v in bm.verts if abs(v&#x5B;orig_y_layer] - y_fold1) &lt; EPS_Y]\nif not hinge_verts_1:\n    raise RuntimeError(&quot;No hinge vertices found for fold 1. Increase subdivide_cuts or EPS_Y.&quot;)\n\nhinge_point_1 = Vector((0.0, 0.0, 0.0))\nfor v in hinge_verts_1:\n    hinge_point_1 += v.co\nhinge_point_1 \/= len(hinge_verts_1)\n\nverts_to_rotate_1 = &#x5B;v for v in bm.verts if v&#x5B;orig_y_layer] &gt; (y_fold1 + EPS_Y)]\nrot1 = Matrix.Rotation(fold1_rad, 4, &#039;X&#039;)\nbmesh.ops.rotate(bm, verts=verts_to_rotate_1, cent=hinge_point_1, matrix=rot1)\n\n# ============================================================\n# FOLD 2\n# ============================================================\nhinge_verts_2 = &#x5B;v for v in bm.verts if abs(v&#x5B;orig_y_layer] - y_fold2) &lt; EPS_Y]\nif not hinge_verts_2:\n    raise RuntimeError(&quot;No hinge vertices found for fold 2. Increase subdivide_cuts or EPS_Y.&quot;)\n\nhinge_point_2 = Vector((0.0, 0.0, 0.0))\nfor v in hinge_verts_2:\n    hinge_point_2 += v.co\nhinge_point_2 \/= len(hinge_verts_2)\n\nverts_to_rotate_2 = &#x5B;v for v in bm.verts if v&#x5B;orig_y_layer] &gt; (y_fold2 + EPS_Y)]\nrot2 = Matrix.Rotation(fold2_rad, 4, &#039;X&#039;)\nbmesh.ops.rotate(bm, verts=verts_to_rotate_2, cent=hinge_point_2, matrix=rot2)\n\n# Write back mesh\nbm.normal_update()\nbm.to_mesh(obj.data)\nbm.free()\n\n# -----------------------------\n# Solidify AFTER folding\n# -----------------------------\nsolid = obj.modifiers.new(name=&quot;Solidify_0p5in&quot;, type=&#039;SOLIDIFY&#039;)\nsolid.thickness = inch(thickness_in)  # 0.5&quot;\nsolid.offset = 0.0                    # centered thickness (equal on both sides)\nsolid.use_even_offset = True\nsolid.use_rim = True\n\n# Optional: keep object active\nbpy.ops.object.select_all(action=&#039;DESELECT&#039;)\nobj.select_set(True)\nbpy.context.view_layer.objects.active = obj\n<\/pre><\/div>","protected":false},"excerpt":{"rendered":"<p>Another attempt to create a t-post bracket using a script. This creates a 2D rectangle, bends it, and then solidifies it into a 3d object.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2163,1945],"tags":[2158,2182,664],"class_list":["post-12106","post","type-post","status-publish","format-standard","hentry","category-blender-3d-printing","category-python","tag-blender","tag-blender-api","tag-python"],"_links":{"self":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12106","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=12106"}],"version-history":[{"count":2,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12106\/revisions"}],"predecessor-version":[{"id":12108,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12106\/revisions\/12108"}],"wp:attachment":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=12106"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=12106"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=12106"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}