{"id":12025,"date":"2026-03-14T23:12:01","date_gmt":"2026-03-15T04:12:01","guid":{"rendered":"https:\/\/www.rushworth.us\/lisa\/?p=12025"},"modified":"2026-03-21T10:03:01","modified_gmt":"2026-03-21T15:03:01","slug":"blender-scripting-t-post-sign-holder","status":"publish","type":"post","link":"https:\/\/www.rushworth.us\/lisa\/?p=12025","title":{"rendered":"Blender Scripting &#8211; T-Post Sign Holder"},"content":{"rendered":"\n<p>We spent a lot of the day trying to modify 3D models that we found online to work as a sign holder. Something like the bent metal plates you can buy at the tractor store. Since these are simple polygons, I thought it might be easier to script the build (plus making changes to the dimensions would just require tweaking variables). <\/p>\n\n\n\n<p>Voila &#8211; hopefully it&#8217;s a T-post sign holder! It at least <em>looks<\/em> like one. <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/03\/blender-Bracket.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"584\" height=\"820\" src=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/03\/blender-Bracket.jpg\" alt=\"\" class=\"wp-image-12091\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/03\/blender-Bracket.jpg 584w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/03\/blender-Bracket-214x300.jpg 214w\" sizes=\"auto, (max-width: 584px) 100vw, 584px\" \/><\/a><\/figure>\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\n\n# Clear all existing objects\nfor obj in list(bpy.data.objects):\n    bpy.data.objects.remove(obj, do_unlink=True)\n\n# -----------------------------\n# Scene units (mm)\n# -----------------------------\nscene = bpy.context.scene\nscene.unit_settings.system = &#039;METRIC&#039;\nscene.unit_settings.scale_length = 0.001  # 1 Blender unit = 1 mm\n\nINCH = 25.4\ndef inch(x): return x * INCH\n\n# -----------------------------\n# PARAMETERS (mm)\n# -----------------------------\nbracket_thickness = inch(0.25)   # sheet thickness\nbracket_width = inch(3)   # bracket width (across the post)\n\n# Leg lengths (side profile)\nbracket_top_length = inch(1)        # bracket segment 1 length\nbracket_middle_length = inch(2)     # bracket segment 2 length\nbracket_bottom_length = inch(4.5)   # bracket segment 3 length\n\n# Bend included angles\nbend1_angle_included = 105.0   # top flange\nbend2_angle_included = 255.0   # web -&gt; long leg\n\n# If the long leg goes the wrong direction, flip this\nflip_second_bend = True\n\n# -----------------------------\n# Punch hole\n# -----------------------------\ndo_punch = True\n\n# T-post size references\ntpost_horizontal_hole_height = inch(0.25)\ntpost_horizontal_hole_width = inch(1.5)\ntpost_vertical_hole_height = inch(2)\ntpost_vertical_hole_width = inch(0.25)\npunch_clearance = 1.0 # clearance added around each rectangle (mm)\n\n# Position of t-post before rotation (Z from p0 end, and X across width)\npunch_center_z = inch(1)\npunch_center_x = bracket_width \/ 2\n\n# Vertical placement on top flange (Y=0 plane)\npunch_center_y = -inch(0.5)\n\n# -----------------------------\n# Optional bevel to make edges25ook more formed\n# -----------------------------\ndo_bevel = True\n\nbevel_width = inch(0.05)\nbevel_segments = 25\n\n# -----------------------------\n# Cleanup\n# -----------------------------\n#for n in &#x5B;&quot;BracketShape&quot;, &quot;PunchBar&quot;, &quot;PunchStem&quot;, &quot;HoleRight1&quot;, &quot;HoleRight2&quot;, &quot;HoleRight3&quot;, &quot;HoleRight4&quot;, &quot;HoleLeft1&quot;, &quot;HoleRLeft2&quot;, &quot;HoleLeft3&quot;, &quot;HoleLeft4&quot;]:\n#    o = bpy.data.objects.get(n)\n#    if o:\n#        bpy.data.objects.remove(o, do_unlink=True)\n\n# -----------------------------\n# Helpers (YZ plane directions)\n# Define 0\u00b0 as +Z. +90\u00b0 is +Y. -90\u00b0 is -Y.\n# -----------------------------\ndef unit_from_angle(deg_from_posZ):\n    a = math.radians(deg_from_posZ)\n    return Vector((0.0, math.sin(a), math.cos(a)))\n\ndef boolean_diff(target, cutter):\n    mod = target.modifiers.new(name=f&quot;BOOL_{cutter.name}&quot;, type=&quot;BOOLEAN&quot;)\n    mod.operation = &#039;DIFFERENCE&#039;\n    mod.solver = &#039;EXACT&#039;\n    mod.object = cutter\n    bpy.context.view_layer.objects.active = target\n    bpy.ops.object.modifier_apply(modifier=mod.name)\n    cutter.hide_set(True)\n\ndef add_cube(name, size_xyz, location_xyz, rotation_xyz):\n    bpy.ops.mesh.primitive_cube_add(size=1, location=location_xyz, rotation=rotation_xyz)\n    obj = bpy.context.active_object\n    obj.name = name\n    obj.scale = (size_xyz&#x5B;0], size_xyz&#x5B;1], size_xyz&#x5B;2])\n    bpy.ops.object.transform_apply()\n    return obj\n\ndef add_cylinder(name, radius, length, location_xyz, rotation_xyz):\n    bpy.ops.mesh.primitive_cylinder_add(radius=radius, depth=length, location=location_xyz, rotation=rotation_xyz)\n    obj = bpy.context.active_object\n    obj.name = name\n    bpy.ops.object.transform_apply()\n    return obj\n\n# Convert included bend angles to turn angles\nangle_top = 180.0 - bend1_angle_included\nangle_bottom = 180.0 - bend2_angle_included\n\n# Start along +Z (top flange)\ntheta0 = 0.0\nd0 = unit_from_angle(theta0)\n\n# After bend1, go &quot;down&quot; (toward -Y) by turning negative\ntheta1 = theta0 - angle_top\nd1 = unit_from_angle(theta1)\n\n# After bend2, go toward +Z again (or flip if needed)\ntheta2 = theta1 + (angle_bottom if not flip_second_bend else - angle_bottom)\nd2 = unit_from_angle(theta2)\n\n# Profile points (center surface)\np0 = Vector((0.0, 0.0, 0.0))      # free end of top flange\np1 = p0 + d0 * bracket_top_length       # bend1 line\np2 = p1 + d1 * bracket_middle_length    # bend2 line\np3 = p2 + d2 * bracket_bottom_length    # end of long leg\n\n# -----------------------------\n# Build a single connected sheet surface:\n# Create two polylines separated in X, then make quads between them.\n# -----------------------------\nmesh = bpy.data.meshes.new(&quot;BracketShapeMesh&quot;)\nbracket = bpy.data.objects.new(&quot;BracketShape&quot;, mesh)\nbpy.context.collection.objects.link(bracket)\nbpy.context.view_layer.objects.active = bracket\nbracket.select_set(True)\n\nbm = bmesh.new()\n\nx0, x1 = 0.0, bracket_width\n\n# Left side (x0)\nv0a = bm.verts.new((x0, p0.y, p0.z))\nv1a = bm.verts.new((x0, p1.y, p1.z))\nv2a = bm.verts.new((x0, p2.y, p2.z))\nv3a = bm.verts.new((x0, p3.y, p3.z))\n\n# Right side (x1)\nv0b = bm.verts.new((x1, p0.y, p0.z))\nv1b = bm.verts.new((x1, p1.y, p1.z))\nv2b = bm.verts.new((x1, p2.y, p2.z))\nv3b = bm.verts.new((x1, p3.y, p3.z))\n\n# Faces (one per segment)\nbm.faces.new((v0a, v0b, v1b, v1a))  # top flange\nbm.faces.new((v1a, v1b, v2b, v2a))  # web\nbm.faces.new((v2a, v2b, v3b, v3a))  # long leg\n\nbm.normal_update()\nbm.to_mesh(mesh)\nbm.free()\n\n# -----------------------------\n# Solidify to thickness (sheet metal look)\n# -----------------------------\nsolid = bracket.modifiers.new(&quot;Solidify&quot;, type=&quot;SOLIDIFY&quot;)\nsolid.thickness = bracket_thickness\nsolid.offset = 0.0\nbpy.ops.object.modifier_apply(modifier=solid.name)\n\n# -----------------------------\n# Punch the lowercase &quot;t&quot; on the top flange\n# (Top flange is flat at Y=0; punch straight through Y)\n# -----------------------------\nif do_punch:\n    tpost_length_y = bracket_thickness * 5  # ensure it fully cuts through\n\n    # Crossbar rectangle\n    horizontal_hole = add_cube(\n        &quot;PunchBar&quot;,\n        size_xyz=(tpost_horizontal_hole_width + 2 * punch_clearance, tpost_length_y, tpost_horizontal_hole_height + 2 * punch_clearance),\n        location_xyz=(punch_center_x, 13 + punch_center_y, punch_center_z),\n        rotation_xyz=(math.radians(90 - bend1_angle_included \/ 2), math.radians(0), math.radians(0))\n    )\n\n    # Stem rectangle (placed under the bar like a lowercase &quot;t&quot;)\n    vertical_hole = add_cube(\n        &quot;PunchStem&quot;,\n        size_xyz=(tpost_vertical_hole_width + 2 * punch_clearance, tpost_length_y, tpost_vertical_hole_height + 2 * punch_clearance),\n        location_xyz=(punch_center_x, punch_center_y, punch_center_z),\n        rotation_xyz=(math.radians(90), math.radians(0), math.radians(0))\n        #rotation_xyz=(math.radians(90 - bend1_angle_included \/ 2), math.radians(0), math.radians(0))\n    )\n    boolean_diff(bracket, vertical_hole)\n    boolean_diff(bracket, horizontal_hole)\n\n\nfor hole in range(4):\n    right_hole = add_cylinder(\n        &quot;HoleRight{}&quot;.format(hole),\n        radius=inch(0.125),\n        length=100,\n        location_xyz=(inch(0.5), -inch(2), inch(2) + inch(1.175) * hole),\n        rotation_xyz=(math.radians(90), 0, 0)\n    )\n    left_hole = add_cylinder(\n        &quot;HoleLeft{}&quot;.format(hole),\n        radius=inch(0.125),\n        length=100,\n        location_xyz=(inch(2.5), -inch(2), inch(2) + inch(1.175) * hole),\n        rotation_xyz=(math.radians(90), 0, 0)\n    )\n    boolean_diff(bracket, right_hole)\n    boolean_diff(bracket, left_hole)\n\n# -----------------------------\n# Optional bevel\n# -----------------------------\nif do_bevel:\n    bev = bracket.modifiers.new(&quot;Bevel&quot;, type=&quot;BEVEL&quot;)\n    bev.width = bevel_width\n    bev.segments = bevel_segments\n    bev.limit_method = &#039;ANGLE&#039;\n    #bev.angle_limit = math.radians(35)\n    bev.use_clamp_overlap = False\n    bpy.context.view_layer.objects.active = bracket\n    bpy.ops.object.modifier_apply(modifier=bev.name)\n<\/pre><\/div>\n\n\n<p>API Documentation Links:<\/p>\n\n\n\n<p><a href=\"https:\/\/docs.blender.org\/api\/current\/bpy.ops.mesh.html\">https:\/\/docs.blender.org\/api\/current\/bpy.ops.mesh.html<\/a><br><a href=\"https:\/\/docs.blender.org\/api\/current\/bmesh.ops.html\">https:\/\/docs.blender.org\/api\/current\/bmesh.ops.html<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>We spent a lot of the day trying to modify 3D models that we found online to work as a sign holder. Something like the bent metal plates you can buy at the tractor store. Since these are simple polygons, I thought it might be easier to script the build (plus making changes to the &hellip;<\/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,2173,664],"class_list":["post-12025","post","type-post","status-publish","format-standard","hentry","category-blender-3d-printing","category-python","tag-blender","tag-bmesh","tag-python"],"_links":{"self":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12025","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=12025"}],"version-history":[{"count":3,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12025\/revisions"}],"predecessor-version":[{"id":12092,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12025\/revisions\/12092"}],"wp:attachment":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=12025"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=12025"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=12025"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}