Skip to content

Conversation

@vinicius795
Copy link

Adds the native DXF export path from master so --format dxf now renders directly through DXFSurface. The builder maps SVG-style commands into DXF entities: it converts cubic Béziers into true arcs when their control points lie on a common circle, otherwise flattens them into short chords; merges collinear line runs and contiguous arcs to reduce geometry; promotes closed all-arc runs to CIRCLE entities; and writes the rest as lightweight polylines with bulge values for arcs while attaching text as MTEXT.

  • Extends the CLI format registry so --format dxf now resolves to the new DXFSurface, letting the generator run without the pstoedit bridge. Internal formats now include DXF by default and the CLI still falls back to legacy converters for other external formats.
  • Introduces boxes/dxf_generator.py. DXFSurface.finish prefers the upstream Surface.prepare_paths when available; otherwise it replays the preprocessing locally and normalizes path commands into mutable lists before any affine transforms.

DXF Generator Workflow

  • DXFSurface.finish prepares paths/dogbones, instantiates EZDXFBuilder, sets drawing extents, then streams every path’s commands into the builder while propagating per-path lineweight.
  • EZDXFBuilder.add_commands walks the SVG-style command stream. It tries to reinterpret cubic Béziers as true arcs via _try_cubic_as_arc; this verifies that the control points lie on a single circle (matching tangents, radius, and sweep within tolerance). When that succeeds an analytic arc segment is emitted. When it fails, the cubic is flattened into 16 straight chords by _approximate_cubic, so only those curves that cannot be described as circular are “flattened” to line segments.
  • Explicit arc commands (A) are converted into center/radius/sweep data and marked as full circles whenever the endpoints coincide and the sweep covers 2π. Text commands are collected separately so geometry and annotations can be emitted in a single block.

Segment Merging and Output

  • Before writing entities, _flush_block first asks _merge_arc_segments to coalesce consecutive arcs that share the same center, radius, orientation, and endpoint with cumulative sweep ≤ 360°. _merge_line_run_once / _merge_line_segments repeatedly fold runs of adjacent, collinear line segments (treating reversed direction as collinear) so a straight path becomes a single line.
  • _segments_form_circle detects whether the merged arc run closes on itself with a full sweep; if so, _emit_circle creates a DXF CIRCLE entity. Otherwise _emit_polyline builds an LWPolyline.
  • _emit_polyline feeds the merged geometry through a vertex builder. Closed contours (start and end within _POINT_TOL) set the DXF “closed” flag. Arcs inside polylines are represented as bulged vertices (bulge = tan(|sweep|/4) with sign from orientation). Any path that reduces to just lines becomes a straight polyline segment.
  • Text specs gathered during command parsing become MTEXT entities after the geometry block finishes.

Additional Notes

  • Path harmonization converts tuple-based commands into lists before affine transforms, avoiding the mutation error that occurs when the legacy drawing.Path.transform tries to write into tuples.
  • The DXF builder continues to support per-path lineweight mapping, DXF header extents, and streaming to an in-memory BytesIO buffer for downstream use.

…rs directly through DXFSurface. The builder maps SVG-style commands into DXF entities: it converts cubic Béziers into true arcs when their control points lie on a common circle, otherwise flattens them into short chords; merges collinear line runs and contiguous arcs to reduce geometry; promotes closed all-arc runs to CIRCLE entities; and writes the rest as lightweight polylines with bulge values for arcs while attaching text as MTEXT.
@florianfesti
Copy link
Owner

Look like ezdxf is missing in the requirements.txt

I also wonder if the path optimizations is something we want in general and not only for the dxf backend. I need to check the arc code. We are doing some funky stuff in there like splitting arcs and circles into smaller segments on purpose. Not sure if this really still needed. That might date back to the libcairo days.

@florianfesti
Copy link
Owner

Also what is the motivation for the backend. Making things work without ps2edit or the the ps2edit output problematic for your machine?

@vinicius795
Copy link
Author

Look like ezdxf is missing in the requirements.txt

Fixed

I also wonder if the path optimizations is something we want in general and not only for the dxf backend. I need to check the arc code. We are doing some funky stuff in there like splitting arcs and circles into smaller segments on purpose. Not sure if this really still needed. That might date back to the libcairo days.

I don't think that path optimization is something useful for other file formats. For DXF, it is kind of a must, because if you have too many lines in a single polyline it becomes impossible to edit. The other file formats I don't use, but as far as I know, there's no much editing after you export, and a laser machine doesn't care about arc or splines or a line segmented into many lines. That being said, path optimization is always welcome, but the way it is right now, I think it can be left as a side quest.

Also what is the motivation for the backend. Making things work without ps2edit or the the ps2edit output problematic for your machine?

I couldn't make the ps2edit generate arcs and circles. At first I created the dogbone inner corners, and when I exported the dxf it was terrible. It wasn't that the ps2edit was problematic for my machine, it was that I couldn't see that dxf looking like that, hahahaha, but for real, I think that having the dxf backend natively in python is better than needing another software.

vinicius795 and others added 3 commits October 31, 2025 11:20
… ezdxf header before assigning numeric values and type the attribute map as dict[str, Any] (boxes/dxf_generator.py (line 62), boxes/dxf_generator.py (line 68)); removed superfluous casts and ensured merged segments stay Segments, plus explicitly coerce command arguments to float/str for safe vector creation and text handling (boxes/dxf_generator.py (line 226), boxes/dxf_generator.py (line 654), boxes/dxf_generator.py (line 719)); kept the superclass call working by swapping the ignore code (boxes/dxf_generator.py (line 733)).

Pre-commit also wanted requirements.txt alphabetised, so ezdxf moved up and the missing trailing newline was restored (requirements.txt (line 1)).
--format dxf now renders directly through DXFSurface. The builder maps SVG-style commands into DXF entities: it converts cubic Béziers into true arcs when their control points lie on a common circle, otherwise flattens them into short chords; merges collinear line runs and contiguous arcs to reduce geometry; promotes closed all-arc runs to CIRCLE entities; and writes the rest as lightweight polylines with bulge values for arcs while attaching text as MTEXT.
@florianfesti
Copy link
Owner

Can you please double check the line width. At least in Inkscape the lines are 0.756mm (2.857px) instead of 0.2mm. The pieces otoh have the right size in mm.

The placement of the text seems to be off. Guess you don't support the center alignment. e.g. used for the text in the reference (although that might be an Inkscape issue).

@vinicius795
Copy link
Author

Sure — I’ll try to address both as soon as I can.

About the line width: in AutoCAD everything looks normal on my side, so this might be an Inkscape import/detail, but I’ll double-check what we’re actually writing into the DXF.

For my workflow the exported geometry should basically be “centerlines”, i.e. line thickness ideally should not exist. If a DXF comes in with an actual width/weight, it can become annoying to edit in AutoCAD and can also cause issues when sending the file to process-planning/CAM software.

What would you prefer for the DXF output?

  • Always force a default like 0.2mm (so it looks consistent in viewers like Inkscape), or
  • Keep it unset/zero by default, and let the viewer decide, or
  • Add an option so the user can define it (and maybe even reuse --burn as a default value)?

Text placement: yes, I probably don’t map SVG-style center alignment correctly yet. I’ll fix the alignment mapping and re-test in Inkscape as well.

@florianfesti
Copy link
Owner

Well, we are not actually using the line width in SVG either. The laser is supposed to cut in the line center. Line width is set to 2xburn so the line represents the material to be cut away and the remaining part is what's inside the line. If there are no reasons to keep the line at zero I'd just stick to that.

Things working perfectly in Inkscape is not a priority but just what's easiest for me to check. Treat these things more like things to double check.

But as I can't/haven't really done much proper testing I have re-added the old ps2edit output, so people can keep their work flow while we work out possible kinks in this backend.

@florianfesti
Copy link
Owner

The line width should actually be part of the dat structure. So you don't nened to worry about how it relates to everything but just use the one in there. For now nothing uses different widths but that that doesn't have to stay that way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants