vpype icon indicating copy to clipboard operation
vpype copied to clipboard

Feature: `fill` command (add fill pattern to closed shapes)

Open abey79 opened this issue 3 years ago • 8 comments

Move over the fill command from abey79/vpype-exploration, adding the following in the process:

  • option for arbitrary orientation
  • open paths should be kept by default
  • documentation
  • (optional) target layer (to send fill pattern to a different layer)
  • (optional) cross-hatched pattern

abey79 avatar Aug 24 '20 09:08 abey79

As an interesting bit of math that serves as part of the basis of https://github.com/inkstitch/inkstitch project there's an interesting bit of math behind fills of vectorized shape geometries that says that so you can always find a Eulerian path through a fill so long as the fill is itself eulerian. Basically if you have a closed shape, there's a path from a point back to that point giving it 0 required endpoints. If you clip a fill where no internal part of that fill has a odd parity node order, with a closed shape. You if you travel along that edge of the shape used for clipping/cutting and double every other segment at the intersections of with the fill lines. You end up creating a graph that is by definition Eulerian itself. And, in fact, it will have only even nodes. So if travel from any point in any direction and never cross the same segment twice, (inserting loops into the path taken). You will always end up with a path you can travel without ever lifting your pen.

So effectively if you clip a background field of infinitely long lines with any closed shape. Then mark nodes at the intersections of the closed shape cutting/clipping those those lines. And duplicating every other line along the edge. This would give you 2 paths in 1 path out along the edge and the 1 intersection path down the rung for a total of 4. If the shape isn't contiguous and doesn't have any overlap you'll have some sections of the graph with zero paths to them. So you know they are a different graph if any node doesn't have any node visited and this would likely be caught when you're adding the loops to the already established path.

MeerK40t has an example of this bit of code. If you select a large closed shape and type "embroider" it'll do this weird fill operation. https://www.youtube.com/watch?v=t0cs2ppi4P0

While it matters less with pen plotters which is more in line with this (and doesn't matter as much for laser cutters either). For embroidery these types of fills are a huge requirement since cutting the thread is a resort of last choice.

tatarize avatar Nov 25 '20 05:11 tatarize

I tweaked the fill command a bit for a project I was working on, and ended up adding cut-outs along with hatch support with some angle & gap options - just wanted to drop a note here about it for posterity before I lose all context for the changes I made! Here's the modified file, and here's a quick test I did with it:

0 3mm-test jpg 81ce0635b2f547f57939770bd980e995

jackhumbert avatar Oct 07 '21 22:10 jackhumbert

Proper handling of fill, including shapes with holes, cannot be properly implemented with vpype 1's data model. vpype 2's data model will support shapes with hole, so it'll be in a better position to properly integrate this feature.

abey79 avatar Mar 10 '23 16:03 abey79

The embroidery addon I wrote for vpype did filling very much like this. Basically you just need to treat all paths as closed and all non-contiguous paths as compound.

tatarize avatar Mar 13 '23 20:03 tatarize

Semantically, two overlapping <circle> should be drawn and filled as the union of the shapes, while the same two circles described in a single <path d="M... M..."> should apply whatever fill rule (typically even/odd). This semantics is lost with vpype 1's data model.

abey79 avatar Mar 13 '23 21:03 abey79

That seems like it's up to the fill algorithm. You're talking even-odd vs. non-zero fill rules. If I have a bunch of closed shapes and I treat them all in a even-odd bit, it doesn't matter whether the contours are simple or compound.

So if I have two circles and they are inside each other and different sizes I have a donut-like shape if we call each circle a contour of the primary shape: donut (or letter O). If I then do a fill of this and another potentially overlapping circle. We could get a bunch of different things, but if we're also using an even/odd fill rule then it wouldn't matter if we started with 3 unrelated circles.

For non-zero we'd be doing essentially even/odd fill for the two contours of the donut and then a non-zero for the donut + circle. But, if we were doing even/odd for the circle too we'd only be doing even-odd for everything.

The same is actually true for non-zero, if our two contours of our initial shape were filled in non-zero too then it doesn't matter. The only reason we need the understanding of the different contours and is when we want something like mixing the different fill rules so we can make a o and then union that with an e. or something.

The semantics of drawing circles is literally the semantics of filling them with pixels (that's what drawing is). So it's up to the fill algorithm. Which could well just ask the user for that information.

tatarize avatar Mar 13 '23 23:03 tatarize

What you suggest consists of a blanket override of the actual SVG semantics. With vpype 1, there is no alternative to that. With vpype 2, I want to be able to implement a fill command that properly renders this SVG:

<?xml version="1.0" encoding="utf-8" ?>
<svg xmlns="http://www.w3.org/2000/svg" baseProfile="tiny" height="500" version="1.2" width="500">
    <g fill-rule="evenodd" fill="red" stroke="none">
        <!-- two distinct rectangles -->
        <path d="M 10 10 l 50 0 l 0 50 l -50 0 l 0 -50 z"/>
        <path d="M 30 30 l 50 0 l 0 50 l -50 0 l 0 -50 z"/>

        <!-- two compound rectangles -->
        <path d="M 10 260 l 50 0 l 0 50 l -50 0 l 0 -50 z M 30 280 l 50 0 l 0 50 l -50 0 l 0 -50 z"/>
    </g>
</svg>

fill_semantics

abey79 avatar Mar 14 '23 07:03 abey79

Somewhat. I'm simply saying that if you have shapes that we can determine are closed. We can fill them in the way the user dictates. That drawing is the same thing as filling them with vectors as such. So yeah, in some cases the SVG will give you a fill-rule but that just means you don't have access to the original fill rule, not that the shapes can't be filled. You can fill them with whatever rule you decide. You could pass that as an option to the fill in question. It gets a bit more complex when you want compound paths and fill rules since that case is really these shapes get this fill rule, and this other shape gets a different fill rule.

The lossy nature of loading in vpype-1 doesn't negate the fact that you could still fill the shapes. You can totally fill them, you just fill them with a particular rule. And usually standard even-odd is going to be your best bet.

tatarize avatar Mar 14 '23 09:03 tatarize