DXF importing to use for routing and laser cutting

Here's the place for discussion related to CAM/CNC and the development of the Path module.
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Post Reply
JulianTodd
Posts: 74
Joined: Tue Oct 23, 2018 3:35 pm

DXF importing to use for routing and laser cutting

Post by JulianTodd »

This post is about what I've discovered in the codebase, in case there's any mistakes or misunderstandings I've made so far.

I'd like to make it as easy to use the router from a 2D input shape (often in DXF form, sometimes SVG) as it is to use a laser cutter. This capability would get a lot of immediate use where I work, and would allow me to shut down a different project that did a similar thing in javascript in the browser: https://bitbucket.org/goatchurch/laserplacer/

The very basic Path workbench features has all the necessary cutting of shapes, tabs dressup, etc.

So, I'm looking at the DXF importing feature, because that's how a lot of the shapes are supplied.

The problems I've found with the current implementation are:

(1) The colour property is lost from all the geometry. You don't get to assign certain colour to be of construction type in the sketcher.

(2) The geometry is individually listed in the model tree as well as in the sketch.

(3) None of the points are joined by constraints (eg at the endpoints) so it is impossible to pad the sketch into a solid shape in the Part Design workbench.

The issue of the C++ and the "legacy" python DXF importer is discussed here: https://forum.freecadweb.org/viewtopic.php?f=3&t=32493
(And it seems the C++ importer doesn't have the import into a sketch feature.)

The code that is doing the importing is here:
https://github.com/FreeCAD/FreeCAD/blob ... portDXF.py

What seems to happen is that line 1059 calls dxfReader.readDXF(filename), which parses the contents of the dxf file into a list of objects, and then we iterate through this list (by type) and call a series of FC functions to inject the geometry into the document.

Now, I think the base DXF parsing could be replaced with https://github.com/mozman/ezdxf because that library is getting a great deal of development, has the right license, and is being done by a single-minded developer who has no other distractions from making it perfect. (He comprehensively stomped my bug reports in the past, like this one https://github.com/mozman/dxfgrabber/issues/12 .)

So, the main thing to look at is how the importer then injects the geometry I need into FC.

Take the example of importing a square (four lines) into FC with the Sketch option.

My reading of the code suggests that the following is implemented:

Code: Select all

# You can try this in the Python console
import FreeCAD, Draft
doc = FreeCAD.getDocument("Unnamed")

l1 = Draft.makeWire([(10,10,0), (10,30,0)])
sh1 = Draft.makeSketch(l1, autoconstraints=True)
sketch = sh1
l2 = Draft.makeWire([(10,30,0), (30,30,0)])
sh2 = Draft.makeSketch(l2, autoconstraints=True, addTo=sketch)
l3 = Draft.makeWire([(30,30,0), (30,10,0)])
sh3 = Draft.makeSketch(l3, autoconstraints=True, addTo=sketch)
l4 = Draft.makeWire([(30,10,0), (10,10,0)])
sh4 = Draft.makeSketch(l4, autoconstraints=True, addTo=sketch)
All sketch objects, sh1, sh2, sh3, sh4 are the same (the function returns the addTo parameter). The sketch gets created after the first line is made, which explains the odd behaviour of how the sketch is the second thing to appear in the model list, after the first line object.

We could avoid leaving the model list clutted by including:

Code: Select all

doc.removeObject(l1.Name)
doc.removeObject(l2.Name)
doc.removeObject(l3.Name)
doc.removeObject(l4.Name)
This solves problem (2); although it would be better if we could inject the lines directly into the sketch.

Luckily the Python console when running the Sketcher gives a lot of feedback, so it appears you can do it like so:

Code: Select all

from FreeCAD import Vector
sketch = Draft.makeSketch([])
sketch.addGeometry(Part.LineSegment(Vector(10,10,0), Vector(10,30,0)))
sketch.addGeometry(Part.LineSegment(Vector(10,30,0), Vector(30,30,0)))
sketch.addGeometry(Part.LineSegment(Vector(30,30,0), Vector(30,10,0)))
sketch.addGeometry(Part.LineSegment(Vector(30,10,0), Vector(10,10,0)))
It is also possible to set the third line as a construction line, like this:

Code: Select all

sketch.toggleConstruction(2)
I think I can overcome the lack of colours in the sketches (used to define the different drawing/etching and cutting components of the design) by importing into separate sketches.

As for (3), maybe there is a feature outside of the importer that can apply to any sketch called "infer constraints" which will nail down any coincident nodes, and perhaps any tangencies in the way the arcs join.

It would be useful to be able to drag the shapes around the screen into position on the stock without them distorting or falling apart. This could be done by generating one point in the middle with a set of relative constructive rays coming out from it with fixed length and angle that nails down all the end points.

I guess this is the equivalent of wanting to import in a "dumb solid" from a different 3D CAD package, when you can't have all the constructive operations. This is my idea of a "dumb contour" that holds its shape, but isn't very editable. Can it exist in the Sketcher, or does it need hacking in (like putting a constructive ray to each point)?

In the meantime, it seems that the joining constraints can go in like:

Code: Select all

sketch.addConstraint(Sketcher.Constraint('Coincident',0,1,3,2)) 
sketch.addConstraint(Sketcher.Constraint('Coincident',0,2,1,1)) 
sketch.addConstraint(Sketcher.Constraint('Coincident',1,2,2,1)) 
sketch.addConstraint(Sketcher.Constraint('Coincident',2,2,3,1)) 
doc.recompute()
This is enough to make a shape that can be padded/extruded into a 3D shape that could be used on the router.

Am I on the right track here?

I hope to implement it in a fairly clean macro to achieve my requirements (that is joining all the contours and islands into connected objects and separating the etching lines from the cutting lines). How does FC handle a dependency on an outside Python library, like ezdxf?
User avatar
sliptonic
Veteran
Posts: 3457
Joined: Tue Oct 25, 2011 10:46 pm
Location: Columbia, Missouri
Contact:

Re: DXF importing to use for routing and laser cutting

Post by sliptonic »

JulianTodd wrote: Tue Dec 04, 2018 8:45 pm
I'd like to make it as easy to use the router from a 2D input shape (often in DXF form, sometimes SVG) as it is to use a laser cutter. This capability would get a lot of immediate use where I work, and would allow me to shut down a different project that did a similar thing in javascript in the browser: https://bitbucket.org/goatchurch/laserplacer/
I agree. I'd like to see this too. We routinely get requests at our hackerspace that sound the same.
The problems I've found with the current implementation are:

(1) The colour property is lost from all the geometry. You don't get to assign certain colour to be of construction type in the sketcher.
true, but you can assign colors to a sketch as a whole. I might be worth preserving the color and assinging it to the sketch for consistency.
(3) None of the points are joined by constraints (eg at the endpoints) so it is impossible to pad the sketch into a solid shape in the Part Design workbench.
This is not quite accurate. Technically there doesn't need to be constraints to allow padding if the end points are the same. Try drawing a rectangle in a sketch, then delete all the constraints. It can still be padded. That said, it would be cool to have a tool or macro that could identify points 'close enough' and add coincident constraints and also maybe horizontal/vertical/tangent as well.
Now, I think the base DXF parsing could be replaced with https://github.com/mozman/ezdxf because that library is getting a great deal of development, has the right license, and is being done by a single-minded developer who has no other distractions from making it perfect. (He comprehensively stomped my bug reports in the past, like this one https://github.com/mozman/dxfgrabber/issues/12 .)
cool. I'd love to see a better importer in FreeCAD!
As for (3), maybe there is a feature outside of the importer that can apply to any sketch called "infer constraints" which will nail down any coincident nodes, and perhaps any tangencies in the way the arcs join.

It would be useful to be able to drag the shapes around the screen into position on the stock without them distorting or falling apart. This could be done by generating one point in the middle with a set of relative constructive rays coming out from it with fixed length and angle that nails down all the end points.
I agree that it would be good to be able to drag elements around in a sketch and auto-assigning constraints would be cool but I don't think this is necessary from a Path perspective. A piece of 2D geometry, like a sketch, can be used as a Job base now. Once added to the job, it can be moved as a whole, translated and rotated, within the stock bounds to establish its position for cutting. That's different than setting position of the elements within the sketch coordinate system.
I guess this is the equivalent of wanting to import in a "dumb solid" from a different 3D CAD package, when you can't have all the constructive operations. This is my idea of a "dumb contour" that holds its shape, but isn't very editable. Can it exist in the Sketcher, or does it need hacking in (like putting a constructive ray to each point)?

...

This is enough to make a shape that can be padded/extruded into a 3D shape that could be used on the router.

Am I on the right track here?

I hope to implement it in a fairly clean macro to achieve my requirements (that is joining all the contours and islands into connected objects and separating the etching lines from the cutting lines). How does FC handle a dependency on an outside Python library, like ezdxf?
I think you are. You might also take a look at the macro DXF_To_Face_and_Sketch. It's not perfect but I was able to import your selectedlibrecadsaved.dxf file (with the legacy importer), select the subelements and run it to produce a face and sketch. The face could then be padded and either the pad or the 2D sketch could be used in a Path job.

We have a long way to go to make the workflow smooth and intuitive but many of the pieces are there.
chrisb
Veteran
Posts: 53949
Joined: Tue Mar 17, 2015 9:14 am

Re: DXF importing to use for routing and laser cutting

Post by chrisb »

sliptonic wrote: Wed Dec 05, 2018 5:03 amThat said, it would be cool to have a tool or macro that could identify points 'close enough' and add coincident constraints and also maybe horizontal/vertical/tangent as well.
Besides the setting of "Join geometry" in the DXF preferences there is always the possibility of using the Sketch validation to identify coincidences with a user definable tolerance.
A Sketcher Lecture with in-depth information is available in English, auf Deutsch, en français, en español.
JulianTodd
Posts: 74
Joined: Tue Oct 23, 2018 3:35 pm

Re: DXF importing to use for routing and laser cutting

Post by JulianTodd »

DXF_To_Face_and_Sketch is interesting as it gets us towards the convenience of the OnShape interface where you can select which facets in a sketch you extrude/pad. This is a lot more convenient than having to have one unambiguous loop in your sketch, or you get nothing.

I've just been playing around in their extrusion from sketch feature, and it's a little more sophisticated than just remembering the position of the mouse-click when reselecting the bounded from the overlapping edges as it seems to do shape matching by encoding the shape it is looking for in the diagram in the form of a string, like this:

Code: Select all

%B5$QueryM5S12$disambiguationDataA1M2S12$disambiguationTypeS8$TOPOLOGYS8$entitiesA1A2C0M5Sb$derivedFromC0M5Sa$entityTypeBa$EntityTypeS4$EDGESb$historyTypeS8$CREATIONSb$operationIdB2$IdA1S11.6$FDdsP5sqmdrEbhl_0wireOpS9$queryTypeSd$SKETCH_ENTITYSe$sketchEntityIdSc$x8xBTRM7P668R5R6R7R8R9CcA1S-d.7$imprintRcS7$IMPRINTD-1R5C7S4$FACER7R8R9R12RcR13
Anyway, the basic edgestofaces() here
https://github.com/FreeCAD/FreeCAD/blob ... om.py#L373, seems to collect collect all the edge, close them if they are open, and then does a series of overlapping subtractions with these shapes.

It's very confusing what code is using, but I think it's based on the OpenSCAD engine rather than the OpenCascade engine. This would mean that any extrusions of arcs will be faceted.

I think this feature of selectively extruding individual or groups of faces from a sketch would be desirable.
User avatar
JoshM
Posts: 456
Joined: Thu Oct 05, 2017 5:34 pm
Location: New Hampshire

Re: DXF importing to use for routing and laser cutting

Post by JoshM »

I've played with Sketcher from scripting a bunch lately.

I've found the DOF implementation can be finicky about the order of constraints. If the sketch begins with a template that is one possible state within the possible parametric permutations, it will work generally work well. The sketch is considered constrained when the first possible match is determined, which may not be the one you want--a small circle can be tangent to a larger circle on the inside or the outside, and it can jump... I have read post suggesting limit of about 50 constraints.

But, since you are starting with existing DXF shapes, and want to place them all in a sketch, the BLOCK constraint is likely helpful.

I've observed two script sketch implementations from macro-recording. Some shapes--rectangular as example--generate a list of geometry, and then constraints. Otherwise, each geometry is added, and then constrained before the following item. The latter approach is generally more orderly because you have explicitly defined the constraint order--not sure if you've observed the DOF algo fight it out for more complex sketches. BLOCK seems to lock the shape as is, and I believe is more stable.

You can explicitly set the Construction when creating the geometry:

Code: Select all

sketch.addGeometry(Part.LineSegment(Vector(30,10,0), Vector(10,10,0)), False)
You can also explicitly

Code: Select all

setConstraint(index, True) # or False
if you need to change explicitly.

Also, you can place the non-driven measurements of the geometry from. This is the Reference mode, shown in blue in the sketcher, with

Code: Select all

target_sketch.setDriving(index#,False)
.

You can also add external geometry between sketches, example:

Code: Select all

 App.ActiveDocument.Sketch.addExternal("Object","Edge")
I don't know the DXF side, but if it is a list of vectors that you are using to place line/arc geometry into sketcher, you could create a sketch for each layer, and put them all in a Part Container to de-clutter.
Post Reply