FreeCAD/DXF to .rd conversion, named upload to controller memory with idle check and read-back verification, read-only status/file listing, and a fake controller for dry runs. Co-Authored-By: Claude Opus 5.5 <[email protected]>
50 lines
2.6 KiB
Python
Executable File
50 lines
2.6 KiB
Python
Executable File
# fc2dxf.py: export a flat FreeCAD body (sheet part) to a LINE-only DXF for dxf2rd.py.
|
|
# Takes the largest planar face normal to the body's thinnest axis, writes its outline and holes
|
|
# with inner loops first (so the part doesn't drop before its holes are cut), origin at the
|
|
# bottom-left corner, mm. Curves are flattened to lines within 0.02 mm. Opens the .FCStd
|
|
# read-only and never saves it.
|
|
#
|
|
# usage (FreeCAD's headless interpreter):
|
|
# freecadcmd -c "import sys; sys.argv=['x','MODEL.FCStd','BodyLabel','OUT.dxf']; exec(open('fc2dxf.py').read())"
|
|
# Prints the part thickness: check it matches your stock. A blind pocket (not through) can't be
|
|
# cut, only engraved, so the script refuses bodies with planar faces between the two sides.
|
|
import FreeCAD, sys
|
|
MODEL, NAME, OUT = sys.argv[-3], sys.argv[-2], sys.argv[-1]
|
|
d = FreeCAD.openDocument(MODEL)
|
|
objs = d.getObjectsByLabel(NAME)
|
|
assert objs, f"no object labelled {NAME!r}"
|
|
s = objs[0].Shape; b = s.BoundBox
|
|
dims = [b.XLength, b.YLength, b.ZLength]; t = dims.index(min(dims))
|
|
ax = [i for i in range(3) if i != t]
|
|
mn = [b.XMin, b.YMin, b.ZMin]
|
|
flat = [f for f in s.Faces if f.Surface.__class__.__name__ == 'Plane'
|
|
and abs(abs(f.normalAt(0, 0)[t]) - 1) < 1e-6]
|
|
levels = sorted({round(f.CenterOfMass[t], 3) for f in flat})
|
|
assert len(levels) == 2, f"faces at {levels}: blind pocket or step, not a through-cut sheet part"
|
|
f = max(flat, key=lambda f: f.Area)
|
|
loops = []
|
|
for w in f.Wires:
|
|
segs = []
|
|
for e in w.OrderedEdges:
|
|
pts = ([e.Vertexes[0].Point, e.Vertexes[-1].Point] if e.Curve.__class__.__name__ == 'Line'
|
|
else e.discretize(Deflection=0.02))
|
|
pts = [(p[ax[0]] - mn[ax[0]], p[ax[1]] - mn[ax[1]]) for p in pts]
|
|
segs += list(zip(pts, pts[1:]))
|
|
xs = [p[0] for sg in segs for p in sg]; ys = [p[1] for sg in segs for p in sg]
|
|
loops.append(((max(xs) - min(xs)) * (max(ys) - min(ys)), segs))
|
|
loops.sort(key=lambda l: l[0]) # inner cuts first, outline last
|
|
out = ["0", "SECTION", "2", "HEADER", "9", "$INSUNITS", "70", "4", "0", "ENDSEC",
|
|
"0", "SECTION", "2", "ENTITIES"]
|
|
n = 0
|
|
for _, segs in loops:
|
|
for (x1, y1), (x2, y2) in segs:
|
|
if abs(x1 - x2) + abs(y1 - y2) < 1e-6:
|
|
continue
|
|
out += ["0", "LINE", "8", "CUT", "10", "%.4f" % x1, "20", "%.4f" % y1, "30", "0",
|
|
"11", "%.4f" % x2, "21", "%.4f" % y2, "31", "0"]; n += 1
|
|
out += ["0", "ENDSEC", "0", "EOF"]
|
|
open(OUT, 'w').write("\n".join(out) + "\n")
|
|
print(f"{NAME} -> {OUT}: {n} lines, {len(loops)} loops, {dims[ax[0]]:.2f} x {dims[ax[1]]:.2f} mm, "
|
|
f"thickness {dims[t]:.2f} mm")
|
|
FreeCAD.closeDocument(d.Name)
|