custom-nas/mechanical/generate_sheetmetal_dxf.py

175 lines
6.9 KiB
Python

#!/usr/bin/env python3
"""
Pure Python DXF R12 Generator for SendCutSend Sheet Metal Panels.
Generates compliant ASCII DXF files for laser cutting.
"""
import os
import math
class DXFWriter:
def __init__(self, filename):
self.filename = filename
self.entities = []
def add_line(self, x1, y1, x2, y2, layer="0"):
self.entities.append(
f"0\nLINE\n8\n{layer}\n10\n{x1:.4f}\n20\n{y1:.4f}\n30\n0.0\n11\n{x2:.4f}\n21\n{y2:.4f}\n31\n0.0\n"
)
def add_rect(self, x, y, width, height, layer="0"):
self.add_line(x, y, x + width, y, layer)
self.add_line(x + width, y, x + width, y + height, layer)
self.add_line(x + width, y + height, x, y + height, layer)
self.add_line(x, y + height, x, y, layer)
def add_circle(self, cx, cy, radius, layer="0"):
self.entities.append(
f"0\nCIRCLE\n8\n{layer}\n10\n{cx:.4f}\n20\n{cy:.4f}\n30\n0.0\n40\n{radius:.4f}\n"
)
def add_slot(self, cx, cy, width, height, layer="0"):
# Oblong horizontal or vertical slot
if width >= height:
r = height / 2.0
x1 = cx - width / 2.0 + r
x2 = cx + width / 2.0 - r
self.add_line(x1, cy - r, x2, cy - r, layer)
self.add_line(x1, cy + r, x2, cy + r, layer)
# approximate semicircles with line segments
steps = 16
for i in range(steps):
a1 = math.pi/2 + i * math.pi / steps
a2 = math.pi/2 + (i + 1) * math.pi / steps
self.add_line(x1 + r * math.cos(a1), cy + r * math.sin(a1),
x1 + r * math.cos(a2), cy + r * math.sin(a2), layer)
b1 = -math.pi/2 + i * math.pi / steps
b2 = -math.pi/2 + (i + 1) * math.pi / steps
self.add_line(x2 + r * math.cos(b1), cy + r * math.sin(b1),
x2 + r * math.cos(b2), cy + r * math.sin(b2), layer)
else:
r = width / 2.0
y1 = cy - height / 2.0 + r
y2 = cy + height / 2.0 - r
self.add_line(cx - r, y1, cx - r, y2, layer)
self.add_line(cx + r, y1, cx + r, y2, layer)
steps = 16
for i in range(steps):
a1 = 0 + i * math.pi / steps
a2 = 0 + (i + 1) * math.pi / steps
self.add_line(cx + r * math.cos(a1), y2 + r * math.sin(a1),
cx + r * math.cos(a2), y2 + r * math.sin(a2), layer)
b1 = math.pi + i * math.pi / steps
b2 = math.pi + (i + 1) * math.pi / steps
self.add_line(cx + r * math.cos(b1), y1 + r * math.sin(b1),
cx + r * math.cos(b2), y1 + r * math.sin(b2), layer)
def write(self):
content = [
"0\nSECTION\n2\nHEADER\n0\nENDSEC\n",
"0\nSECTION\n2\nTABLES\n0\nENDSEC\n",
"0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n",
"0\nSECTION\n2\nENTITIES\n",
"".join(self.entities),
"0\nENDSEC\n",
"0\nEOF\n"
]
with open(self.filename, "w", encoding="utf-8") as f:
f.write("".join(content))
print(f"Generated: {self.filename}")
def generate_all_dxf():
out_dir = os.path.join(os.path.dirname(__file__), "dxf")
os.makedirs(out_dir, exist_ok=True)
# 1. SMP-01 Top Cover (436 x 500)
top = DXFWriter(os.path.join(out_dir, "SMP-01_Top_Cover_Plate.dxf"))
top.add_rect(0, 0, 436.0, 500.0)
# Left & Right edge holes (Ø4.5mm -> r=2.25)
for y in [25.0, 150.0, 275.0, 400.0, 475.0]:
top.add_circle(10.0, y, 2.25)
top.add_circle(426.0, y, 2.25)
# Front & Rear edge holes
for x in [109.0, 218.0, 327.0]:
top.add_circle(x, 10.0, 2.25)
top.add_circle(x, 490.0, 2.25)
top.write()
# 2. SMP-02 Bottom Base Plate (436 x 500)
bot = DXFWriter(os.path.join(out_dir, "SMP-02_Bottom_Base_Plate.dxf"))
bot.add_rect(0, 0, 436.0, 500.0)
# Perimeter
for y in [25.0, 150.0, 275.0, 400.0, 475.0]:
bot.add_circle(10.0, y, 2.25)
bot.add_circle(426.0, y, 2.25)
for x in [109.0, 218.0, 327.0]:
bot.add_circle(x, 10.0, 2.25)
bot.add_circle(x, 490.0, 2.25)
# PCB Standoffs (Ø3.5mm -> r=1.75)
for (px, py) in [(40.0, 230.0), (200.0, 230.0), (40.0, 330.0), (200.0, 330.0)]:
bot.add_circle(px, py, 1.75)
# Mezzanine anchors
for (mx, my) in [(280.0, 230.0), (380.0, 230.0), (280.0, 430.0), (380.0, 430.0)]:
bot.add_circle(mx, my, 1.75)
bot.write()
# 3. SMP-03 & 04 Side Panels (500 x 260)
for side_name in ["SMP-03_Left_Side_Panel.dxf", "SMP-04_Right_Side_Panel.dxf"]:
side = DXFWriter(os.path.join(out_dir, side_name))
side.add_rect(0, 0, 500.0, 260.0)
for x in [25.0, 150.0, 275.0, 400.0, 475.0]:
side.add_circle(x, 10.0, 2.25)
side.add_circle(x, 250.0, 2.25)
for y in [70.0, 130.0, 190.0]:
side.add_circle(10.0, y, 2.25)
side.add_circle(490.0, y, 2.25)
side.write()
# 4. SMP-05 Rear I/O Panel (396 x 220)
rear = DXFWriter(os.path.join(out_dir, "SMP-05_Rear_IO_Exhaust_Plate.dxf"))
rear.add_rect(0, 0, 396.0, 220.0)
# Perimeter
for x in [30.0, 130.0, 266.0, 366.0]:
rear.add_circle(x, 10.0, 2.25)
rear.add_circle(x, 210.0, 2.25)
for y in [60.0, 110.0, 160.0]:
rear.add_circle(10.0, y, 2.25)
rear.add_circle(386.0, y, 2.25)
# Exhaust Honeycomb Grid (Array of Ø5.0mm ventilation holes for laser efficiency)
for hx in range(35, 220, 10):
for hy in range(35, 190, 10):
rear.add_circle(hx, hy, 3.5)
# Dual PCIe Vertical Slot Cutouts (120mm x 21.6mm)
rear.add_rect(250.0, 35.0, 22.0, 120.0)
rear.add_rect(280.0, 35.0, 22.0, 120.0)
# Host I/O Cutouts
rear.add_rect(335.0, 40.0, 32.0, 14.0) # Dual RJ45
rear.add_rect(335.0, 65.0, 16.0, 15.0) # Dual USB 3.0
rear.add_circle(355.0, 110.0, 5.75) # DC Barrel Jack (Ø11.5mm)
rear.add_rect(345.0, 135.0, 20.0, 10.5) # 8-pin Molex
rear.write()
# 5. SMP-06L & SMP-06R Rack Ears (40 x 260, 3mm)
for ear_name in ["SMP-06L_Rack_Mount_Ear.dxf", "SMP-06R_Rack_Mount_Ear.dxf"]:
ear = DXFWriter(os.path.join(out_dir, ear_name))
ear.add_rect(0, 0, 40.0, 260.0)
# Extrusion mounting holes (Ø5.5mm -> r=2.75)
for y in [20.0, 70.0, 130.0, 190.0, 240.0]:
ear.add_circle(15.0, y, 2.75)
# EIA-310-D Oblong Rack Slots (10.0mm x 6.8mm)
rack_y = [
12.7, 28.6, 44.5, # 1U
57.2, 73.0, 88.9, # 2U
101.6, 117.5, 133.4, # 3U
146.0, 161.9, 177.8, # 4U
190.5, 206.4, 222.2, # 5U
235.0, 250.8 # 6U
]
for ry in rack_y:
ear.add_slot(30.0, ry, 10.0, 6.8)
ear.write()
if __name__ == "__main__":
generate_all_dxf()