Delete points

florisdejonge's Avatar

florisdejonge

27 Jun, 2020 06:55 AM

Hello,
I am struggling with the use of the delete points node. When using patterns the delete points node is more useful compared to the compound path node - if you want to fill a shape with a pattern. Nevertheless, if you want punch out the shape from a pattern Nodebox reconnects the points (which has some logic to it). Any ideas for a work-around? I've attached an example of what I am trying to do. The circle on the left should be left empty.
Floris

  1. Support Staff 1 Posted by john on 27 Jun, 2020 09:12 AM

    john's Avatar

    Hi Floris,

    I have attached a demo with two possible solutions.

    The first one replaces each line with 100 tiny line segments, each its own path. When you copy that 20 times you have 2000 paths. I then set the Delete node to paths instead of points. This removes all the tiny segments inside the circle.

    The second approach uses very thin rectangles instead of lines. Because rectangles are shapes instead of merely paths, you can use the compound node in difference mode to remove the circle.

    Notice the subtle difference. In the first case each tiny segment is either included or not, resulting in clean breaks. In the second case the stripes include tiny curves along the edge of the circle for a more precise outline.

    If you use a pattern other than horizontal lines you may need to make some adjustments to chop your pattern into tiny paths. But if your pattern consists of closed shapes (e.g. dots) you can just use a compound node.

    Let me know is this solves your problem.

    John

  2. 2 Posted by florisdejonge on 27 Jun, 2020 09:41 AM

    florisdejonge's Avatar

    Thanks John for providing two solutions. Since I want to draw the results with a penplotter I'm more interested in the first one because it contains lines and not shapes. The second one might work if I would fit a path to the shape though.

    With regard to plotting something with the first solution I run into a second problem. This results in a lot of small paths and therefore to a lot of up and down movements of the pen. So continuous lines are preferable. You've already provided something of a solution with your excellent node library :). With the Join-node I can combine all these paths. The result is pretty cool, but not particular what I was looking for. So is there a way to use the Join-node for a selection of paths/points? Slicing the list of paths could be a solution, but only if every 'row' contains the same number of paths.

    Floris

  3. Support Staff 3 Posted by john on 27 Jun, 2020 09:53 AM

    john's Avatar

    Hmmm. I agree it would be painful to make your plotter lift its pen 2000 times.

    There are several ways to solve this. If your pattern is always horizontal lines and your hole is always a circle (which I gather it is) one approach would be to actually calculate the intersections with the circle. A more general purpose solution would be to look for gaps between successive points in each line and use those to consolidate the segments.

    I will take a quick run at this and get back to you.

    John

  4. Support Staff 4 Posted by john on 27 Jun, 2020 10:16 AM

    john's Avatar

    OK, here you go.

    I used the circle intersection method. My "clean_lines" subnetwork returns a complete line if there are no collisions with the circle and two shorter lines (one before the circle and one after) if there are collisions. The subnetwork uses an intersct_circle node which I have not yet published; I will add it to the next release of my node library.

    Give this one a try and see if your plotter likes it.

    John

  5. 5 Posted by florisdejonge on 28 Jun, 2020 06:40 AM

    florisdejonge's Avatar

    Hi John,
    Thanks for exploring this solution. This would indeed work perfectly for plotting the drawing with the variables mentions: straight lines and with a circle. I liked the creative potential with different (multiple?) shapes and curved lines though (see examples provided). Therefore I followed your suggestion for a more general purpose solution. In the attached nodebox file I added a comparison which only selects the short line-segments. Unfortunately, I am subsequently stuck again at the earlier problem: connecting only points that are near eachother. I do not have a lot of experience with using booleans in Nodebox, but is it possible to only draw the line when the comparison is 'true'?

    I would consider this general purpose solutions more feasible. This might be a topic for another discussion, but I think this would also be applicable to something I was working on earlier: translating the flying goat algorithm to Nodebox. I am currently making a CMYK version (example attached - file is a bit slow to load). For this I found a way to generate a csv-file of RGB-values of an input image. This also resulted in small line segments which I joined in Adobe Illustrator, put that doesn't work with larger amounts of segments and it produces unwanted connections in the lighter areas. And I can think of even more ways in which such a join/connect-solution is applicable. So if your are willing to contribute to such a solution, that'd be great.

    Kind regards,
    Floris

  6. Support Staff 6 Posted by john on 28 Jun, 2020 08:39 PM

    john's Avatar

    Floris,

    Attached is a general purpose node, bg_segments, that chops each background path you give it into a minimal set of clean subpaths not occluded by a foreground shape (or group of shapes),

    To demonstrate it I use a large Times Roman "ab" as my foreground shape and three sets of background paths: straight lines at an angle, concentric circles, and sine waves. To show the separate identity of each segment path, I alternated colors between black and red (see screenshot). You can switch to data mode to verify that there are less than one hundred total subpaths for each case, not thousands as before.

    The bg_segments node takes a value for minimum length, which I default to 10. If you lower this value you will get a sharper definition around your foreground shapes, but computation time will increase. Lowering the value to 5 for these 3 samples increased rendering time from about 3 seconds to 10 seconds - acceptable. But below 5 the rendering time increases exponentially.

    bg_segments uses my subpath node which requires make_curve.py (included in the zip file).

    Please give this node a try and see if makes your plotter happy with whatever background paths and foreground shapes you can throw at it. If I hear back from you that it works and is useful, I will add it the next release of my library.

    I will leave the flying goat algorithm for another day. It has some similarities to this problem but will be challenging to do in an efficient way.

    John

  7. Support Staff 7 Posted by john on 29 Jun, 2020 01:50 AM

    john's Avatar

    Floris,

    Followup. I finally looked at your vector raster network (which was missing the noise.clj file so I found a copy and replaced it). I'm not yet ready to tackle a general purpose flying goat, but I did find something else you might appreciate...

    Some time ago I came across an algorithm for converting RGB values into CMYK dots. I converted this to NodeBox and made a demo which uses your CSV file as an input. The algorithm sizes the dots, slightly offsets them, and draws them in a certain z order.

    In theory you could send the output directly to a CMYK printer. There are a lot of dots, but I suppose you could try plotting each dot as well. You may have to adjust the scaling value to match dot sizes to the original photo size; I guessed at a value of 640 which seemed to produce decent results. Once that is right you can group everything and scale that to final output size.

    Zoom in to see the actual overlapping CMYK dots. Not perfect, but recognizable. And dots instead of flying goat line segments, but perhaps another step in that direction.

    John

    P.S. I'm not sure this flower photo, with its subtle gradations, is the best possible example to use for developing a flying goat line segment output. Maybe try something a little simpler with higher contrast for starters.

  8. 8 Posted by florisdejonge on 29 Jun, 2020 07:17 PM

    florisdejonge's Avatar

    John, This looks really good! Unfortunately, I do not have the time to test
    it and look into it in more detail. But I will get back to you at the end
    of the week. Kind regards, Floris

  9. 9 Posted by florisdejonge on 04 Jul, 2020 06:20 AM

    florisdejonge's Avatar

    John,
    It works perfectly! Although I don’t quite understand how it works yet. Of course, I don’t only want to use your solution, but I want to understand how you got there. There are a lot of subnetworks though, with some unknown elements to me. Would you be so kind to elaborate on what you did? In a subnetwork you often start with a null-node. Can you explain what causes you to use this? What does the ‘indices’ node do exactly? And I am not familiar with nodes like split_curve or split_path. Are those new ones?

    Either way, it is a great demonstration of what Nodebox with your extra node library is able to do. For example, it was clear to me what kind of things the individual nodes from the library would do (based on the examples provided), but not what they could do in combination with eachother.

    I am going to try to apply this new background-node to some different things I was working on. I will post the results on my instagrampage (or is the show-your-work-section of this forum being used?) I’ll still have to look at your CMYK program. It looks like a more efficient way. Of course, I will try to learn from it with regard to using certain insights with the penplotter :)

    Kind regards,
    Floris

  10. Support Staff 10 Posted by john on 04 Jul, 2020 08:13 AM

    john's Avatar

    Floris,

    I would be delighted to elaborate on how the bg_segments node works. It's very deep if you follow it all the way down and there are some bits along the way that are not at all obvious.

    Bg_segments (see screenshot) takes one background path at a time and tests it against a foreground shape. If there is no intersection it simply returns the background path unscathed. If there is an intersection it returns two or more distinct segments, each one a clean, continuous path. The min length controls the resolution of the testing.

    The first thing it does is resample the background path using the min length parameter. Resample adds a steady series of points along the path, but still returns a single path, so you have to feed it into a points node to recover those individual points.

    I feed that resampled path along with the foreground shape into a delete node and feed that into a points node as well. This gives me all the points in the original background path minus any points that happen to fall within the shape.

    My goal now is to build up an internal table that tells me, for each point in the original path, whether or not it was occluded (deleted) and, if so, which segment the foreground shape broke it into. For example, if the background path is a line and the foreground shape is the letter 'a', the line might first hit the left edge of the a, shine through the bowl of the a, then emerge from the other side. This would produce three distinct segments: the part before the a, the part inside the a, and part after the a.

    The first column of this table is an index I used to identify each original point - just the points numbered 0, 1, 2... My indices node does this simple task.

    The second column has a 0 if the point survived and a 1 if it was deleted. This is calculated from a "deleted" node which compares the original point set with the occluded point set. To do this it uses my handy find_item node which takes each point in the occluded set, one at a time, and compares it to the full set of original points taken all at once. Find_item returns one or more indices if there is a match (meaning the point was found). So I count these indices and, if none are returned, I return a 1 (indicating that the point is missing).

    The third column, the segment ID, is the ingenious part. To find it I simply take the running total of the 0s and 1s coming out of the deleted node. To see how this works recall our example from above of the letter a chopping a line into three pieces.

    The first set of points before the line strikes the a are all 0s, so the running total is all 0s as well. Then 4 or 5 points are deleted as the line passes the first part of the a, producing a series of 1s. The running total now begins to climb: 1, 2, 3, 4, 5. In the center of the a we get a string of 0s and the running total stays unchanged: 5, 5, 5, 5, 5, 5, 5. As we cross the right side of the a the running total climbs again: 6, 7, 8, 9. After that it's all 0s so the running total stays the same: 9, 9, 9, 9, etc.

    The table is now complete. Here comes the magic part: I simply filter the table to keep only the rows of points that were NOT deleted. If you look at the segment column you will see it now functions as a segment ID. All the points in the first segment have a segment value of 0. Points in the middle segment (inside the a) all have a segment value of 5. And all points in the final segment have a value of 9. The actual values (0, 5, and 9) don't matter; all that matters is there is now a unique identifier for each segment.

    If I lookup the segment column and apply a distinct node I get those three segment IDs. The count tells me how many segments there are. I can use each segment ID to filter on the third column to return only the points that belong to that segment.

    This is what the segments node does. Actually it does even more: it returns the actual subpath corresponding to the points included in each segment. To do this is uses my handy sub_path node.

    I will not terrify you by trying to explain in detail how the sub_path node works. For now just accept that it returns a clean continuous subpath between j% and k% of an original path. So all I have to do is find values for j and k which define each segment.

    I get these values by filtering for only the table rows matching a given segment ID and looking up the index value. This gives me a list of index values for each point in the segment. Suppose the segment inside the letter 'a' starts at index 35 and continues through index 47. In this case the first value on my list will be 35 and the last will be 47. To convert these into percentages all I need it the total number of points in the original background path (which I pass into the segments node).

    So each time the segments node fires it returns the subpath of each segment. If there was no intersection with the foreground shape, all rows of my table will have a segment ID of 0, j will be 0%, k will be 100%, and segments will return the entire path.

    That's it. The secret is my internal table and clever trick of using running totals to derive an ID for each segment. It takes some practice to come up with solutions like this. But I hope you see how useful these little internal tables are. By assigning each row a consecutive index and then filtering, sorting, and using the distinct node, you can do most anything.

    You ask about my frequent use of null nodes inside my subnetworks. When making subnetworks it's tempting to simply select a subset of nodes and "group into network". This may work at first, but if you want to modify that subnetwork (and sooner or later you will) you will often need to route one of the input lines to a new node.

    I have learned from experience to always make a separate node to receive each input to my subnetwork - and to rename that node so I can tell which input is which from inside the subnetwork. If the input value is a number I will use a number node or an integer node, if it's a string I will use a string node, if it's a boolean I will use a boolean node. And if its anything else - a path or a shape or a table row - I will use a null node. That's why you see so many of them in my code.

    The indices node I have described above. Split_curve and split_path are sub-processes within my sub_path node. Ask me again and I will launch into a separate elaboration of how that node works!

    Thanks for these excellent questions. Please keep them coming. And please do consider sharing some of your results in the show-your-work forum; that's what it's there for.

    John

  11. Support Staff 11 Posted by john on 05 Jul, 2020 11:53 PM

    john's Avatar

    UPDATE

    I made two improvements to the bg_segments node - which I now call the mask node:

    • it now runs about three times faster
    • you can now choose to show the occluded segments as well as the non-occluded

    To speed it up I changed the "deleted" subnetwork which returns a 1 if a resampled point was not occluded. Instead of using my find_item node (which is rather inefficient), I instead pass a distinct post-occlusion point list, count the items, add in each point I'm checking, pass it through a distinct node again, count those items, and compare. If the second count increased, the point I passed in must have been missing. The node functions just as it did before, but is now more efficient.

    NOTE: I discovered while doing this that I had to turn the post-occlusion points into strings before applying a distinct node. It turns out that if you apply a distinct node to points instead of strings, {x,y} is treated as a match for {x,-y}. This appears to be an obscure NodeBox bug that only shows up for background paths matching this unusual pattern (like certain concentric circles), so I was lucky to spot this. Turning the points into strings first avoids the problem.

    As you can see in the attached screenshot, setting the mask node to "return occluded" produces an inverse pattern to the previous example. This is not what you were looking for, but could produce some nice effects.

    Please play with this improved node and let me know if you spot any issues.

    Thanks!

    John

  12. 12 Posted by florisdejonge on 12 Jul, 2020 05:49 PM

    florisdejonge's Avatar

    Hi John,
    Thanks a lot for you explanation on this specific node and the thinking that went in it. There is some great advice in there. Nice to see that you have developed it even further. I like the addition of the occluded/non-occluded variable, because in its application of using two different colors for the occluded and non-occluded parts, you quickly want acces to both.

    I've played with it for a bit and in my experience that calculation time ramps up really fast when using multiple generated shapes and in quite a few instances crashes the program. I was considering using this node as a really easy way to create crosshatch fills, and have tried it with the attached example (the one on the left), but was unable to because the program freezes and in the end I used another solution (with fewer options for variation). What would you consider the maximum amount of shapes with a simple line pattern that this node or nodebox could reasonably handle? Or am I using it in a way it's not supposed to?

    Floris

  13. 13 Posted by florisdejonge on 12 Jul, 2020 07:22 PM

    florisdejonge's Avatar

    Tried some more. Went back to some more simple tests. Think I should have grouped the foreground geometry.

  14. Support Staff 14 Posted by john on 13 Jul, 2020 03:13 AM

    john's Avatar

    Floris,

    Yes, you should definitely group the foreground shapes before applying the mask. Not doing so will multiply the number of paths generated and the time taken by at least the number of shapes - or crash NodeBox as you discovered.

    I tried an experiment converting a photo to 43,000 different sized dots and grouped that to form a single foreground geometry. I then attempted to mask 10 horizontal lines, each 600 pixels wide, against it to essentially hatch each dot. At a minimum mask length of 5 it took 5 seconds. At a minimum length of 1 (as tight as I think you should ever try), it took 25 seconds.

    Slow, but not catastrophic. If I hadn't grouped the dots first, those times would be multiplied by 43,000! That's more than 12 days of continuous calculation (if NodeBox didn't freeze, which of course it would). In fact NodeBox tends to freeze if any one calculation takes more than about 7 minutes (on my machine). From previous experiments it looks like some kind of memory leak triggers a cascade of garbage collection processes which go beyond the point of no return after 7 minutes.

    There are a few things you can do to improve your render time. In addition to grouping the foreground, you can also reduce the length and number of background paths to the minimum needed. If you can tell in advance what parts of the image have no collisions you can draw those parts separately. Increase the mask's minimum width as high as you can without sacrificing quality. If calculations still take more than a few minutes - and if you're desperate - you can break the calculations into smaller sets of background paths, save each set as an SVG, then read the sets back in and recombine.

    My experiments using mask to hatch dots or other small shapes worked OK in some cases, but for complex photos with subtle boundaries the results were poor. Even at a length of 1 pixel the output looks pixelated and that resolution results in tens of thousands of short line segments. But then perhaps pen plotters are just not meant to draw photographs.

    Please keep experimenting. If you can't work around these problems with the mask node, perhaps we could attack things from a different angle.

    John

Reply to this discussion

Internal reply

Formatting help / Preview (switch to plain text) No formatting (switch to Markdown)

Attaching KB article:

»

Already uploaded files

  • deletepoints.zip 1.22 KB
  • Schermafbeelding_2020-06-20_om_17.11.03.png 123 KB

Attached Files

You can attach files up to 10MB

If you don't have an account yet, we need to confirm you're human and not a machine trying to post spam.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac

Recent Discussions

13 Jul, 2020 03:13 AM
07 Jun, 2020 04:08 AM
04 Jun, 2020 11:48 PM
25 May, 2020 09:13 PM
24 May, 2020 09:33 PM