Skip to content

How to structure lines

Posted on:March 16, 2023

This time I am working fully on extracting the 2D logic of replicad into its own package.

One of my aims with this move is to come up with a clean data structure that support the 2D CAD use case. Flatten has what seems like the best data structure I have seen so far - but it feels like it misses some things still.

Generally I want to have a set of data structure that express well what you can and can’t do with them. The canonical example is that boolean operation only make sense on closed path. This should be expressed in the data structure.

What do I came up with

A graph representing where I am at now with data structures

I have some nested levels of abstractions:

Segments are the basic building block - what I called “curve” in replicad. These are segment of lines, arcs of circle or ellipse, Bézier curves. They always have a start and end point.

You can put a list of segments end to end and create a stroke. I have identified two types of strokes so far, what I call strands and what I call loops. Loop are strokes that close on themselves (and therefore define an inside and an outside). Strands do not and can therefore be extended.

The implementation I have for booleans in replicad is on what I call now loops. But loops are not enough - because you might have loops within loops. And fusing two loops together can result in loops within loops.

This is where figures come into play. A figure is a contour loop with multiple hole loops within them. The good thing is that applying boolean operations on figures as I have defined them only returns (list of) figures. A point within a whole in a figure is on the outside of that figure then.

What I still need to figure out

First, I wonder if I should have an object to encapsulate the notion of list of non intersecting figures. I have this in replicad (this is the Blueprints), but I am not sure if it is necessary. I might call that a diagram.

Then I also need to play a bit more on how strokes and figures interact (or not) together. Typically I want a way to have them both live together in some kind of structure. For instance, the dielines I produce for deckinabox are typically a mix of strokes and figures. It feels like implementing offset operations on strokes might give some insight on what I should do there.

How does flatten do it

Flatten has a very similar way to organise its notion, with some twists.

Their face is similar to my loop, but they do not have a concept similar to my figure. Instead they have a polygon container that can have multiple faces, that can be categorised as either islands (i.e. contour loop) or hole (hole loop). As far as I understand (I have not played with it), you can have multiple island within a single polygon.

Their implementation seems to follow from what boolean operations produce - you can obtain multiple figures when doing an operation with multiple faces. I preferred my approach as it fits better the I perceive what a shape is. That said, if I end up implementing diagrams as I mentionned in the previous sections, they would be very similar to polygons.

The notion of multiline - similar to my stroke is also probably less important to where I want to go. That said, I am still for from it.

Additionally I try to implement my algorithm so that they can work with any orientation. While I see how having a convention is very useful internally, I am not sure why one would need to expose it as part of the API.

A note on Vectors (or Points)

I have been hesitating to create a Vector object with methods to work on them. Instead I went for a simple tuple (as I do in replicad) with a set of helper functions. I am not sure this is the best choice, but this is also what tldraw went for, so I will be wrong in good company.

Another note on precision

As of now, I have implemented precision as a parameter of a segment. This is partially how open cascade does it - but I need to do a bit more research on what are the different approaches and what are the tradeoffs.