Hello,
with a polygon like this: include <BOSL2/std.scad> include <BOSL2/rounding.scad> path = round_corners( [ [0, 0], [0, 30], [30, 0] ], cut=[4, 0, 0] ); polygon(path); move_copies(path) circle(r=0.5, $fn=32); I would like to subdivide the long edges so that there are no segments that are longer than some given number "max_segment_length". (The goal is to pre-refine the polygon before it is used with `skin()`, avoiding the problem of long, sharp triangles as is currently discussed in thread "Fixed number of points with BOSL2 round_corners()?" and other threads about `linear_extrude()` with twist.) I was looking at <https://github.com/revarbat/BOSL2/wiki/paths.scad#function-subdivide_path>, but it seems that this function could also affect the short segments at the rounded corner. Is there a function that works like: For each edge that is longer than `max_segment_length`, replace the edge with several colinear edges that are all of the same length that is at most `max_segment_length`. ? Best regards, Carsten _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Am 16.03.21 um 10:07 schrieb Carsten Fuchs:
> Is there a function that works like: > > For each edge that is longer than `max_segment_length`, replace the edge with several colinear edges that are all of the same length that is at most `max_segment_length`. > > ? Just for the fun of it, here is my custom implementation: // For a polygon segment (edge) that is made of the points `a` and `b`, // this function returns a list of points `[a0, a1, a2, …]` that subdivide the // original segment into smaller segments of equal length such that all segments // are shorter than (or at most equal to) `msl`. // Note that `a0 == a` and that the returned list does *not* include the last // point, which would be equal to `b`. Also note that if `a == b`, the empty // list is returned. function get_subdivs(a, b, msl) = let ( l = path_length([a, b]), num_segments = ceil(l / msl) ) [ for (i = [0 : num_segments-1]) let( s1 = i/num_segments, s2 = 1.0 - s1 ) a*s2 + b*s1 ]; // Makes sure that no segment of the given path is longer than `msl`, // the "maximum segment length". // The input path is assumed to be "open", i.e. the last point is not // understood to have an edge back to the first point. function subdivide_segments(path, msl) = assert(msl > 1.0) // echo("path len", len(path), "points", path) len(path) <= 1 ? path : concat( get_subdivs(path[0], path[1], msl), subdivide_segments([for (i = [1 : len(path)-1]) path[i]], msl) ); I'm sure that this can be improved, but this reflects my level of proficiency of the OpenSCAD language. ;-) Best regards, Carsten _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Off the top of my head, the simple implementation with BOSL2 should look something like:
- Revar
_______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by Carsten Fuchs
I don't think there is such a function already in the library.
Note that I would probably use turtle() for what you're trying to do. With turtle you can easily control exactly how many steps are in each line segment so that you can make the curves you are trying to skin line up the way you want. In other words, something like: turtle(["move", 10, // one step of length 10 "repeat", 5, ["move", 1], // five little steps of length 1 "arcsteps", 10, "arcright", 5, 75, // radius 5 turn, 75 deg, 10 steps ...
Sent from the OpenSCAD mailing list archive at Nabble.com. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by RevarBat
Am 17.03.21 um 19:40 schrieb Revar Desmera:
> Off the top of my head, the simple implementation with BOSL2 should look something like: > > function subdivide_max_seglen(path, maxlen, closed=false) = > assert(is_path(path)) > assert(is_finite(maxlen)) > assert(is_bool(closed)) > [ > for (p=pair(path,closed)) let( > steps = ceil(norm(p[1]-p[0])/maxlen) > ) each lerp(p[0],p[1],[0:1/steps:1-EPSILON]), > if (!closed) last(path) > ]; > > Thank you! Best regards, Carsten _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by adrianv
Am 17.03.21 um 23:09 schrieb adrianv:
> I don't think there is such a function already in the library. Maybe a useful method for `subdivide_path()`? Much easier to use than the other methods. ;-) > Note that I would probably use turtle() for what you're trying to do. With turtle you can easily control exactly how many steps are in each line segment so that you can make the curves you are trying to skin line up the way you want. In other words, something like: > > turtle(["move", 10, // one step of length 10 > "repeat", 5, ["move", 1], // five little steps of length 1 > "arcsteps", 10, "arcright", 5, 75, // radius 5 turn, 75 deg, 10 steps > ... Thanks. While I appreciate the beauty of the `turle()` function, I'm not sure if it really makes things easier in practice: For the parts that I make, I usually have a physical object in mind of which I take measures, dimensions, sometime angles, etc. In my current project (my first larger one) it was self-suggesting to work with cross-sections that never rotate out of their original plane, but change their shape and lateral position along the path of extrusion. When modeling such a cross-section it seems natural to start with a hand-crafted path, especially if it is to be parametrized, followed by post-processing steps like `round_corners()` and `subdivide_path()`. `turtle()` seems to make some tasks easier (and thus thank you for pointing it out, I was in fact not aware of its potential!), but it also seems to make some tasks harder, e.g. rounding corners with method "smooth". Reconsidering, I think that you're right though: For my next comparable polygon, I'll definitively try `turtle()` first. Best regards, Carsten -- Dipl.-Inf. Carsten Fuchs Industriegebiet 3 ℅ Rofu 55768 Hoppstädten-Weiersbach https://www.cafu.de _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
I’ve added `subdivide_long_segments()` to the library.
-Revar > On Mar 18, 2021, at 12:01 AM, Carsten Fuchs <[hidden email]> wrote: > > Am 17.03.21 um 23:09 schrieb adrianv: >> I don't think there is such a function already in the library. > > Maybe a useful method for `subdivide_path()`? > > Much easier to use than the other methods. ;-) > >> Note that I would probably use turtle() for what you're trying to do. With turtle you can easily control exactly how many steps are in each line segment so that you can make the curves you are trying to skin line up the way you want. In other words, something like: >> >> turtle(["move", 10, // one step of length 10 >> "repeat", 5, ["move", 1], // five little steps of length 1 >> "arcsteps", 10, "arcright", 5, 75, // radius 5 turn, 75 deg, 10 steps >> ... > > Thanks. While I appreciate the beauty of the `turle()` function, I'm not sure if it really makes things easier in practice: For the parts that I make, I usually have a physical object in mind of which I take measures, dimensions, sometime angles, etc. In my current project (my first larger one) it was self-suggesting to work with cross-sections that never rotate out of their original plane, but change their shape and lateral position along the path of extrusion. When modeling such a cross-section it seems natural to start with a hand-crafted path, especially if it is to be parametrized, followed by post-processing steps like `round_corners()` and `subdivide_path()`. > > `turtle()` seems to make some tasks easier (and thus thank you for pointing it out, I was in fact not aware of its potential!), but it also seems to make some tasks harder, e.g. rounding corners with method "smooth". Reconsidering, I think that you're right though: For my next comparable polygon, I'll definitively try `turtle()` first. > > Best regards, > Carsten > > > > -- > Dipl.-Inf. Carsten Fuchs > Industriegebiet 3 ℅ Rofu > 55768 Hoppstädten-Weiersbach > https://www.cafu.de > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to [hidden email] OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
In reply to this post by Carsten Fuchs
The problem I see for the shape you've been working with is that I think for a good skin result you need to add points in some places and not in others and you need a lot of control over where you add the points, and how many points you get. That kind of point insertion is hard to do on a shape after you've smoothed the corners with round_corners. I used debug_polygon to find where the long sides are and then I could apply subdivide_path with an appropriate vector valued N to divide just the desired segment, but then if I change my $fn for smoothing, everything needs to be redone. I suppose you could calculate the positions of the segments based on the $fn value. The new subdivide_long_segments doesn't give enough control, since I think you need to carefully divide the segments so that they match up with the arcs, which means you need to specify the number of points, not the maximum length. And you might find it useful to subdivide just half of a long segment.
With turtle you can avoid this complication because you can directly specify the point repetition where you need it and you can edit the point count in the middle of a shape without changing other parts of the shape. I think also you missed something about turtle. You would not use turtle and then invoke round_corners on the result. That would create the same problem as above where you don't know the index you want to operate on. Instead you would use turtle to create the finished shape, rounded corners included. That was why I mentioned the "arcleft" turtle command, which will make a rounded corner. The one problem I see in pondering how this works is that it's hard to make an arc in turtle that corresponds to a known segment length. That is, you have to specify the radius when "joint" would be better. I wonder if I need to add some different way of using the "arc" commands to make it easier to round a corner with a known vertex position, but I'm not sure what that would be.
Sent from the OpenSCAD mailing list archive at Nabble.com. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Hello,
Am 18.03.21 um 13:47 schrieb adrianv: > The problem I see for the shape you've been working with is that I think for a good skin result you need to add points in some places and not in others and you need a lot of control over where you add the points, and how many points you get. That kind of point insertion is hard to do on a shape after you've smoothed the corners with round_corners. Yes, thanks, that's a very accurate summary of the problem! > I used debug_polygon to find where the long sides are and then I could apply subdivide_path with an appropriate vector valued N to divide just the desired segment, but then if I change my $fn for smoothing, everything needs to be redone. For this problem, I now consider looping over the segments and identify the relevant ones by checking their length for some minimum value, checking their slope etc.. This is similar to the new `subdivide_long_segments()`, but customized to my specific use case – which, unfortunately, somewhat defeats it purpose and just confirms the unfortunate nature of the problem. > I suppose you could calculate the positions of the segments based on the $fn value. The new subdivide_long_segments doesn't give enough control, since I think you need to carefully divide the segments so that they match up with the arcs, which means you need to specify the number of points, not the maximum length. And you might find it useful to subdivide just half of a long segment. Exactly. Unfortunately, `subdivide_long_segments()` did not turn out to be the all-in-one general purpose solution that I hoped it would be. In fact, as you say, I have to have exact control over the number of vertices used. > With turtle you can avoid this complication because you can directly specify the point repetition where you need it and you can edit the point count in the middle of a shape without changing other parts of the shape. I think also you missed something about turtle. You would not use turtle and then invoke round_corners on the result. That would create the same problem as above where you don't know the index you want to operate on. Instead you would use turtle to create the finished shape, rounded corners included. That was why I mentioned the "arcleft" turtle command, which will make a rounded corner. While I understand your description, I still cannot see how it helps. Please consider this example: include <BOSL2/std.scad> include <BOSL2/rounding.scad> turtle_path = turtle([ "ymove", 30, // "arcsteps", 10, // "arcrightto", 5, 280, "jump", [35, 0], "xmove", -35 ]); stroke(turtle_path, width=0.8); What I would like to achieve is to round the triangle's top and right corners and to subdivide its sides, all with a constant number of points that doesn't change if the shape of the triangle changes. I've so far not found a way to achieve that without doing a lot of math calculations by hand. > The one problem I see in pondering how this works is that it's hard to make an arc in turtle that corresponds to a known segment length. That is, you have to specify the radius when "joint" would be better. I wonder if I need to add some different way of using the "arc" commands to make it easier to round a corner with a known vertex position, but I'm not sure what that would be. Maybe some turtle command like "take this and the two previous vertices, back off, and use the three vertices as input to `round_corners()` (with a given $fn)" ? It still would not help with the above example, though. Best regards, Carsten > > Carsten Fuchs wrote > Am 17.03.21 um 23:09 schrieb adrianv: > > I don't think there is such a function already in the library. > > Maybe a useful method for `subdivide_path()`? > > Much easier to use than the other methods. ;-) > > > Note that I would probably use turtle() for what you're trying to do. With turtle you can easily control exactly how many steps are in each line segment so that you can make the curves you are trying to skin line up the way you want. In other words, something like: > > > > turtle(["move", 10, // one step of length 10 > > "repeat", 5, ["move", 1], // five little steps of length 1 > > "arcsteps", 10, "arcright", 5, 75, // radius 5 turn, 75 deg, 10 steps > > ... > > Thanks. While I appreciate the beauty of the `turle()` function, I'm not sure if it really makes things easier in practice: For the parts that I make, I usually have a physical object in mind of which I take measures, dimensions, sometime angles, etc. In my current project (my first larger one) it was self-suggesting to work with cross-sections that never rotate out of their original plane, but change their shape and lateral position along the path of extrusion. When modeling such a cross-section it seems natural to start with a hand-crafted path, especially if it is to be parametrized, followed by post-processing steps like `round_corners()` and `subdivide_path()`. > > `turtle()` seems to make some tasks easier (and thus thank you for pointing it out, I was in fact not aware of its potential!), but it also seems to make some tasks harder, e.g. rounding corners with method "smooth". Reconsidering, I think that you're right though: For my next comparable polygon, I'll definitively try `turtle()` first. > > Best regards, > Carsten > OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Your idea to use round_corners with flat "corners" seems like it could be a good approach if you can figure out how to match the two shapes this way. It enables you to insert points along a flat length in your shape.
With regards to the turtle command, I wonder if you are familiar with the turtle concept. https://en.wikipedia.org/wiki/Turtle_graphics The reason I wonder about this is that you jumped immediately to the "non-turtle" commands that turtle() supports to give some added flexibility beyond true turtle graphics. Turtle is meant to be relative, not absolute. The absolute commands, especially "jump" will not integrate well with the relative commands and allow you to use "arcleft" or "arcright". The suggestion of passing three points to round_corners from turtle doesn't fit the turtle framework well because it requires changing the past. You ask how you can make a rounded triangle. Yes, it requires doing some math, though not a tremendous amount. The tricky bit is that to make a roundover with joint length d you need the radius to be d*tan(angle/2). I am wondering about how I could make a new way to use "arcleft" in turtle that would do this automatically. But for now, we do it with math. The other problem is that you give a triangle apparently by height and width. So we need to know the length of the hypotenuse and the angles at the corner. So here's the answer to your question of arbitrary triangle: h = 30; // triangle height w = 35; // triangle width top_angle = atan(w/h); // angle at top corner right_angle = atan(h/w); // angle at right corner hypot = sqrt(h*h+w*w); // hypotenuse length N=8; // points to add on sides joint_top=5; // joint length for top roundover joint_bot=8; // joint length for bottom roundover path = turtle(["left", // point up "repeat", N, ["move", (h-joint_top)/N], "arcsteps", 5, "arcright", joint_top*tan(top_angle/2), 180-top_angle, "repeat", N, ["move", (hypot-joint_top-joint_bot)/N], "arcright", joint_bot*tan(right_angle/2), 180-right_angle, "repeat", N, ["move", (w-joint_bot)/N] ]); move_copies(path) circle(r=.5,$fn=8); It looks like arcsteps is giving you a total of 5 points counting both endpoints, so it's really only making 4 new points. Probably that's a bug. I am wondering what would be the best way to solve this problem in general. Assuming you have some threshold length you could write a function that divides long segments into N parts while leaving short segments as they are. The N input could be a vector enabling different divisions for different long segments. Another idea is to write a function that enumerates the long segments so that you can then do something to them. (Perhaps the former invokes the latter.)
Sent from the OpenSCAD mailing list archive at Nabble.com. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Hello,
first a side note about something that is similar to what we're discussing. Please consider: z = [0.0, 0.1, 0.2, 0.3, 100.0, 100.1, 100.2, 100.3, 200.0]; slices = [ for (z_pair = pair(z)) let( dist = z_pair[1] - z_pair[0] ) assert(dist >= 0) floor(dist/20) ]; skin(cross_sections, z=z, slices=slices); In other words, `skin()`s ability to create a custom number of slices for each pair of cross-sections gets me out of the "needle triangles" trouble that the irregularly spaced z-values bring. (Which, btw., in my actual code are derived from an output of `round_corners()`…) Am 19.03.21 um 14:55 schrieb adrianv: > Your idea to use round_corners with flat "corners" seems like it could be a good approach if you can figure out how to match the two shapes this way. It enables you to insert points along a flat length in your shape. Well, yes, but in the meanwhile, I found problems with that, too: Even if `round_corners()` can be made to output the exact number of desired vertices (which, as discussed earlier, works great with method="smooth", parameter "joint" and "$n"), it can be difficult to foresee the exact locations of these vertices. For example, rounding this shape: C------D / A------B at B and C to make a smooth curve from level AB to level CD works well – until you need to have the same points with the same x-axis spacing but with all y=0 to obtain the compatible, flattened shape: A------B-C------D (Yes, the much better solution in this case was to sample a sin curve between B and C manually, which is however much more difficult in my full, non-reduced code.) To sum up, I agree that `round_corners()` is not the best tool here. :-) > With regards to the turtle command, I wonder if you are familiar with the turtle concept. > https://en.wikipedia.org/wiki/Turtle_graphics <https://en.wikipedia.org/wiki/Turtle_graphics> > The reason I wonder about this is that you jumped immediately to the "non-turtle" commands that turtle() supports to give some added flexibility beyond true turtle graphics. Turtle is meant to be relative, not absolute. Yes, I know and understand this. I used the commands with absolute coordinate references to keep the test case short and to emphasize that I could not see a good solution to the problem that is close and natural to the turtle paradigm. > The suggestion of passing three points to round_corners from turtle doesn't fit the turtle framework well because it requires changing the past. Well, an arc-like command could also accept two or three points further ahead along the path, e.g. "bezier", controlpoint1, controlpoint2, endpoint depending on the degree of Bezier curve that is supported. Or: "round_corner", cornerpoint, endpoint where the minimum of (the distance from the current position to the corner point) and (the distance from the corner point to the end point) could be the "joint" value. The end position of the turtle might even be != the endpoint if the computed "joint" value doesn't agree with it. > You ask how you can make a rounded triangle. Yes, it requires doing some math, though not a tremendous amount. Well… yes, it's not much. And it's not even difficult. But this was only a simple triangle, used as a test case. My real shape is not that much more complex (you've seen testcases with it that were somewhat reduced, but not by that much). However, the math would explode. If not explode, then at least multiply. Looking at this from a code maintenance point of view, this is not something that I'd want to generalize. In sum, I think that both the "round_offset() approach" as well as the "turtle approach" can both be used to create a solution to the problem, but both have their drawbacks. (I agree that the turtle approach looks a bit more "stable".) However, I wonder if this is not in fact an implied limit of the functional language that OpenSCAD implements. That shape that I have (which I believe is just a commonplace shape as found in many engineering applications) seems to be naturally build with some kind of turtle approach: "From the last point in the path, compute the next point or points, append them to the path, repeat." The key is that each computation (each turtle command) is different. Now, it seems to me that the "abstract turtle" would much easier be implemented in an imperative language: // pseudo code path = []; A = compute_starting_point(...); path.append(A); B = compute_next_point(path[-1], more_parameters, ...); path.append(B); ... turn = [p for p in some_curve]; path += turn // append the points in `turn` to `path` ... In a sense, it seems that "turtle" is kind of a special case (a subset?) of an imperative language, silently hidden in OpenSCAD. ;-) Please forgive me, but if it was ever considered to add imperative language support to OpenSCAD: Lua (http://www.lua.org/) would be a perfect fit! (And it would open OpenSCAD to folks who don't have a degree in CS…) I'll flesh out both approaches next and see which works best! :-) Best regards, Carsten > The tricky bit is that to make a roundover with joint length d you need the radius to be d*tan(angle/2). I am wondering about how I could make a new way to use "arcleft" in turtle that would do this automatically. But for now, we do it with math. The other problem is that you give a triangle apparently by height and width. So we need to know the length of the hypotenuse and the angles at the corner. So here's the answer to your question of arbitrary triangle: > > h = 30; // triangle height > w = 35; // triangle width > top_angle = atan(w/h); // angle at top corner > right_angle = atan(h/w); // angle at right corner > hypot = sqrt(h*h+w*w); // hypotenuse length > N=8; // points to add on sides > > joint_top=5; // joint length for top roundover > joint_bot=8; // joint length for bottom roundover > > path = turtle(["left", // point up > "repeat", N, ["move", (h-joint_top)/N], > "arcsteps", 5, > "arcright", joint_top*tan(top_angle/2), 180-top_angle, > "repeat", N, ["move", (hypot-joint_top-joint_bot)/N], > "arcright", joint_bot*tan(right_angle/2), 180-right_angle, > "repeat", N, ["move", (w-joint_bot)/N] > ]); > > move_copies(path) circle(r=.5,$fn=8); > > It looks like arcsteps is giving you a total of 5 points counting both endpoints, so it's really only making 4 new points. Probably that's a bug. > > > I am wondering what would be the best way to solve this problem in general. Assuming you have some threshold length you could write a function that divides long segments into N parts while leaving short segments as they are. The N input could be a vector enabling different divisions for different long segments. Another idea is to write a function that enumerates the long segments so that you can then do something to them. (Perhaps the former invokes the latter.) > > > Carsten Fuchs wrote > Hello, > > Am 18.03.21 um 13:47 schrieb adrianv: > > Exactly. Unfortunately, `subdivide_long_segments()` did not turn out to be the all-in-one general purpose solution that I hoped it would be. In fact, as you say, I have to have exact control over the number of vertices used. > > > With turtle you can avoid this complication because you can directly specify the point repetition where you need it and you can edit the point count in the middle of a shape without changing other parts of the shape. I think also you missed something about turtle. You would not use turtle and then invoke round_corners on the result. That would create the same problem as above where you don't know the index you want to operate on. Instead you would use turtle to create the finished shape, rounded corners included. That was why I mentioned the "arcleft" turtle command, which will make a rounded corner. > > While I understand your description, I still cannot see how it helps. Please consider this example: > > > include <BOSL2/std.scad> > include <BOSL2/rounding.scad> > > turtle_path = turtle([ > "ymove", 30, > // "arcsteps", 10, > // "arcrightto", 5, 280, > "jump", [35, 0], > "xmove", -35 > ]); > > stroke(turtle_path, width=0.8); > > > What I would like to achieve is to round the triangle's top and right corners and to subdivide its sides, all with a constant number of points that doesn't change if the shape of the triangle changes. I've so far not found a way to achieve that without doing a lot of math calculations by hand. > > > The one problem I see in pondering how this works is that it's hard to make an arc in turtle that corresponds to a known segment length. That is, you have to specify the radius when "joint" would be better. I wonder if I need to add some different way of using the "arc" commands to make it easier to round a corner with a known vertex position, but I'm not sure what that would be. > > Maybe some turtle command like "take this and the two previous vertices, back off, and use the three vertices as input to `round_corners()` (with a given $fn)" ? > It still would not help with the above example, though. > > Best regards, > Carsten > > > > > > > Carsten Fuchs wrote > > Am 17.03.21 um 23:09 schrieb adrianv: > > > I don't think there is such a function already in the library. > > > > Maybe a useful method for `subdivide_path()`? > > > > Much easier to use than the other methods. ;-) > > > > > Note that I would probably use turtle() for what you're trying to do. With turtle you can easily control exactly how many steps are in each line segment so that you can make the curves you are trying to skin line up the way you want. In other words, something like: > > > > > > turtle(["move", 10, // one step of length 10 > > > "repeat", 5, ["move", 1], // five little steps of length 1 > > > "arcsteps", 10, "arcright", 5, 75, // radius 5 turn, 75 deg, 10 steps > > > ... > > > > Thanks. While I appreciate the beauty of the `turle()` function, I'm not sure if it really makes things easier in practice: For the parts that I make, I usually have a physical object in mind of which I take measures, dimensions, sometime angles, etc. In my current project (my first larger one) it was self-suggesting to work with cross-sections that never rotate out of their original plane, but change their shape and lateral position along the path of extrusion. When modeling such a cross-section it seems natural to start with a hand-crafted path, especially if it is to be parametrized, followed by post-processing steps like `round_corners()` and `subdivide_path()`. > > > > `turtle()` seems to make some tasks easier (and thus thank you for pointing it out, I was in fact not aware of its potential!), but it also seems to make some tasks harder, e.g. rounding corners with method "smooth". Reconsidering, I think that you're right though: For my next comparable polygon, I'll definitively try `turtle()` first. > > > > Best regards, > > Carsten > > OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Hmmm. I wonder if there should be a way to insert slices proportional to z spacing. The reason I think that I didn't do this is because it doesn't make sense if you give slices in 3d.
It appears that you are seeking a general purpose solution that can handle some large family of shapes. This may be a particular challenge. The other question of course is what solution you favor, because there is not a unique "best" solution to this problem. When I was experimenting with the automated approaches I found some interesting ideas, some that I didn't think of on my own, for how the shapes could map to each other. In the case you show with mapping two rounded corners to flat, for example, I do not think that the best looking solution is the one where the x coordinates map directly. I think you will get a better looking solution if you preserve length a little better in the mapping and have some local displacement of x. Note with regards to the right paradigm for your problem: I don't know what your problem actually is. I see a shape, but don't know how you defined it, or what the natural parameters are for your shape. The fit of your problem to the turtle paradigm will depend on this. If your problem involves knowing the angles of corners turtle may be a good fit. But if it involves jumping to specific coordinates like your triangle example then less so. I'm not convinced that handling a more complex shape is as bad as you think because the geometry will presumably be local. But I do agree that it's not a very pleasing solution. I am of the opinion that the computer, not the user, should be the one doing the trigonometry. The problem with implementing your idea of rounding based on the previous 3 points is not an OpenSCAD limitation/concern. It's a concern about the conceptual definition of the turtle, that it carries more state. What is the definition of starting state? I feel that turtle is already pretty complex, with a lot of commands, so making it even more complex is a concern. The point is not to try to make turtle into everything, and solve all the problems with it. Things that are fundamentally not relative should probably be solved by some other method. I'm not sure that adding beziers to turtle is any better than using beziers directly. For corners, your proposal isn't relative, which makes me feel that it doesn't really belong in turtle. I was thinking perhaps of something like "round_corner_right", joint, angle but that's a terrible name. So that would create a rounding that would replace the corner: "move", joint, "right", angle, "move", joint But it seems like this isn't really the solution to your problem. If you want to use beziers you could just construct a bezier path. There is no need to use the turtle for that. And Revar just added some new functions to make construction of cubic bezier curves control points easier. Note that a "bezier path" is just a sequence of beziers joined end to end, and you can sample it with a fixed point count on each bezier. The real limitation in what we can do in OpenSCAD comes from lack of efficient data structures, or general issues with efficiency, not (directly) from its functional nature. Your problem is much too small for efficiency to be a concern. If you have some idea of how your problem could be solved but you think it requires an imperative approach, or you're not sure how it could be done in OpenSCAD, describe it here. What is some general framework that would do what you need? If there's some other framework for solving this kind of problem that is of general utility we can implement it and add it to the library. Note: I do not have a CS degree. ;) Note that you can use turtle() the way you show in your example, kind of, by saving the state from each return and then calling it again and passing in the state, though it's not clear what the advantage would be. You could compute the commands from the previous state(s). s1 = turtle(....,full_state=true); s2 = turtle(...,state=s1,full_state=true); s3 = turtle(..., state=s2,full_state=true); path = turtle(...,state=s3);
Sent from the OpenSCAD mailing list archive at Nabble.com. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Hello,
Am 19.03.21 um 22:28 schrieb adrianv: > Hmmm. I wonder if there should be a way to insert slices proportional to z spacing. The reason I think that I didn't do this is because it doesn't make sense if you give slices in 3d. Maybe it is still possible by considering the shortest distance (shortest edge) between each pair of cross-sections? > It appears that you are seeking a general purpose solution that can handle some large family of shapes. Well, I started with a physical part of old plastics that is torn and broken. The goal is to make a new one. I had the old part 3D scanned, but only as a visual, spatial reference; it is too broken to do anything else with it. The core work is re-modelling the part from scratch, which in turn started for me with taking measurements and drawing sketches. I'm still very new to this field, but I guess that many projects are like mine: Start from original models, measurements, plans or sketches, then re-model them in OpenSCAD. This works well as long as the philosophies agree, i.e. the physical part can well be modeled with geometry that is described by math functions that are described in a functional language. > In the case you show with mapping two rounded corners to flat, for example, I do not think that the best looking solution is the one where the x coordinates map directly. I think you will get a better looking solution if you preserve length a little better in the mapping and have some local displacement of x. Yes, that sounds good! I'm still experimenting with this. Just speculating: Maybe an algorithm that minimized energy, i.e. that formed a shape like one that a soap bubble would form when brought between the profiles, would give good results. But that is really all that I know about this. > The problem with implementing your idea of rounding based on the previous 3 points is not an OpenSCAD limitation/concern. It's a concern about the conceptual definition of the turtle, that it carries more state. As mentioned in my other mail, the second and third point could also be given as forward points as parameters in the turtle command. > I'm not sure that adding beziers to turtle is any better than using beziers directly. I mentioned bezier curves only to enhance the example. Of course, anything else works as well. > The real limitation in what we can do in OpenSCAD comes from lack of efficient data structures, or general issues with efficiency, not (directly) from its functional nature. Yes, I understand that. But see my remarks about Lua in the other thread. :-) > Your problem is much too small for efficiency to be a concern. If you have some idea of how your problem could be solved but you think it requires an imperative approach, or you're not sure how it could be done in OpenSCAD, describe it here. What is some general framework that would do what you need? If there's some other framework for solving this kind of problem that is of general utility we can implement it and add it to the library. Note: I do not have a CS degree. ;) I'm sure that any problem that I describe can eventually be solved in the OpenSCAD language. And I'm relatively sure that I will fail to come up with a framework, even at a vague level, that introduces something that is substantially new, helps and cannot be reduced to either a functional or imperative language solution. Hmmm. Maybe a very vague and very minor attempt anyways: I would like to have an easy way to compose parts of subparts. I'm not talking about modules and `union()`, but e.g. about paths. In the example with the triangle with the rounded corners and subdivided edges: I could imagine to create the rounded corners separately of each other, then interpolate the edges between the endpoints of the arcs of the corners, then merge all points into a single list. Yes, this can be done in OpenSCAD. But there, I cannot start by thinking about corners, angles and edges. I have to start with thinking about list comprehensions, recursion, list manipulation functions, let(…), the use and behavior of echo and assert, fighting one issue after the other. After all that is done, I can finally focus on the geometry. And if I did a mistake or just realize that I have to re-do some parts, it starts all over. Again, yes, the problem is not the language but me, the non-expert in functional languages who could have done it better, even so that re-doing parts is simple (in fact, it is, I've been changing my cross-section shape a number of times now and it never was an issue for the calling code). In summary, I wish to focus on the geometry, not on the language. Which becomes important as soon as I have to leave the comfortable scope of the language built-ins and the libraries. Best regards, Carsten _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
What I meant about the "large family of shapes" is that you have made remarks about "any triangle" or that this approach will fail if you change the dimensions. If you want a solution that works for any shape you have to work a lot harder. I mean, if you consider the example of skin for connecting a pentagon and square, you can very easily handle this one case by looking at your shapes and deciding where to add the extra edge. That is what I was suggesting you do for your problem---a solution just for your one part you're trying to make right now. Trying to solve the pentagon-square problem in general lead to the "distance" method which was very complicated.
Minimal surfaces (soap bubbles) require a frame. If you only have the end caps the minimal surface will be the two planar surfaces defined by the endcaps. The other thing to recall is that minimal surfaces don't stay "outside" the frame. the minimal surface of a cube is not at all what you want out of skin(). The "distance" algorithm is a natural minimization scheme: minimize the total edge length of the resulting shape. I suppose we could try instead to minimize the total surface area, though I believe it's hard to compute the surface area of a curved quadrilateral, which would make it slower. It's not clear to me how the results would differ with a surface area algorithm, though it seems like if they do differ, minimizing surface area is probably better. Giving a list of arbitrary points "forward" to turtle() doesn't fit the paradigm. My idea of adding a rounded corner is in effect supplying two future points, but in a way that fits the turtle framework better. What you are asking for is actually round_corners: go to a point with (x,y) value and make a rounded corner there. There are two approaches for combining paths. One is path_join and the other is using union(). The problem with path_join is that if you use the rounding features it will remove points from the sides, so you don't have a known number of points. triangle = [[0,0],[35,0],[0,30]]; newpath = path_join([for(p=pair(triangle,wrap=true)) subdivide_path(p,15,closed=false)], joint=12); move_copies(newpath) circle(r=.5); So to make this work, you would need to avoid the rounding feature and instead construct the edges and then make your own rounded corners in their correct orientations. And that's not convenient. I mean, if you're willing to think about the corners and their angles it could work, I suppose. The other option for combining paths is to use BOSL2's union function (or difference or intersection). For example: sq1=subdivide_path(square(10), 40); sq2=move([5,5], p=sq1); sq=union([sq1,sq2])[0]; // union produces list of paths, take first move_copies(sq) circle(r=.5); I think part of the problem you are facing is that really not much effort has gone to the idea of really trying to full control the number of points on a path, so lots of the methods may produce the right shape, but don't give you the control you need. I keep thinking about this idea as helpful for your problem: // Function: subdivide_long_segments() // Usage: // subdivide_long_segments(path, thresh, N, <closed>) // Description: // Subdivides the segments in the path whose length exceeds thresh, adding a specified // number of new points to each subdivided segment. You specify the number of points to // add by giving N as a number to add the same number of points on each segment, or a // vector to add a varying number of points on each segment. If you give a vector its // length must match the number of "long" segments. function subdivide_long_segments(path, thresh, N, closed=true) = let( lengths = path_segment_lengths(path,closed=closed), longind = [for(i=idx(lengths)) if (lengths[i]>thresh) i] ) assert(is_num(N) || is_vector(N,len(longind)), str("N must be a number or vector of length ",len(longind))) let( N = force_list(N,n=len(longind)), addpts = list_set([],longind,N) ) [for(i=[1:1:len(path)-(closed?1:2)]) each lerp(path[i],select(path,i+1), list_range(addpts[i]+1)/(addpts[i]+1))]; triangle = [[0,0],[35,0],[0,30]]; rtri = round_corners(triangle, method="smooth", cut=1, $fn=8); !move_copies(subdivide_long_segments(rtri, 5, [4,6,8])) circle(r=.5);
Sent from the OpenSCAD mailing list archive at Nabble.com. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Hello,
Am 20.03.21 um 14:53 schrieb adrianv: > I think part of the problem you are facing is that really not much effort has gone to the idea of really trying to full control the number of points on a path, so lots of the methods may produce the right shape, but don't give you the control you need. Well, yes, in hindsight that is right. However, I think it was almost impossible to get it right from the start. May overall approach was like this: 1. Take measures of the old physical object. This yields a sketch on a piece of paper and a lot of absolute or relative coordinates. 2. In OpenSCAD, make a polygon from the coordinates, that is, just carry the measurements from paper to a list of points that can be given to `polygon()` and `linear_extrude()`, `rotate_extrude()` and `skin()`. At this point, the main goal is reached: The part has been created. It is crude, but is geometrically valid and fits. It could be printed and used, but is not polished at all. 3. Use `round_corners()` and other functions to enhance the part. 4. Check if the part meets the quality criteria. Thin needle triangles come to the attention. Try to find a fix for those ==> large and unnatural problem ==> requires to redo everything from step 2. Please note how important it is/was to me to work in the space and terms of my actual problem (the physical object) in the beginning, and only think about steps 3 and especially 4 much later. Yes, in hindsight(!) I could have achieved a better result if I had been aware of the problems that step 3 and especially step 4 would bring. But how much would that have complicated and hindered the very important earlier steps! Now, what would I do if I had a similar project again, or had to write a guide for anyone else who is in a similar situation? Uh. I don't know. Here is an idea, and I really really hope that some of the comrades who don't see the value in an imperative language read this: Paths could be modeled from true objects. That is, not as a list of [x, y, z] coordinates, but as an object of type "Path" that keeps a list of objects like "Point_1, Edge_1, Point_2, Edge_2, …, Point_N". That would allow to address path elements (points and edges) by "name" or index number and to configure them, e.g. in example/pseudo code: myPath.items.get_point(3).set_rounding_radius(3); myPath.items.get_edge(27).set_num_subdivisions(8); and finally list_of_coordinates = myPath.compute_path_coordinates(sampling_resolution=2.0); Something like that. The key statement is that it allows users to work and think in the space of their problem, not in the space of the language of OpenSCAD. > I keep thinking about this idea as helpful for your problem: WOAH! That is excellent, thank you! :-) I was experimenting with something similar, but your solution is better! A minor issue: It doesn't work with `closed=false`. Shouldn't the list comprehension be like this? // ... [ for(i=[0:1:len(path)-(closed?1:2)]) each lerp(path[i],select(path,i+1), list_range(addpts[i]+1)/(addpts[i]+1)), if (!closed) last(path) ]; That is, start `i` at 0 and add the last point in `path` if not closed? ~~~~ I would like to add another thought, only loosely related to the above: Consider two 3D paths that are roughly parallel to the y-axis. For example: path1 = [ [ 0, 0, 0], [ 0, 20, 0], [ 0, 40, 0], [ 0, 60, 0], [ 0, 80, 0] ]; // path2 is offset 100 to the right and otherwise similar to, but not exactly like path1. path2 = [ [100, 0, 0], [100, 24, 0], [100, 40, 1], [100, 62, -3], [100, 80, 0] ]; What is the best way to "combine" those paths along the y-axis? Can we do anything better than resampling by y-coordinate? e.g.: for (y=[0 : 0.1 : 80]) echo(get_xz(path1, y), get_xz(path2, y)); Best regards, Carsten > // Function: subdivide_long_segments() > // Usage: > // subdivide_long_segments(path, thresh, N, <closed>) > // Description: > // Subdivides the segments in the path whose length exceeds thresh, adding a specified > // number of new points to each subdivided segment. You specify the number of points to > // add by giving N as a number to add the same number of points on each segment, or a > // vector to add a varying number of points on each segment. If you give a vector its > // length must match the number of "long" segments. > function subdivide_long_segments(path, thresh, N, closed=true) = > let( > lengths = path_segment_lengths(path,closed=closed), > longind = [for(i=idx(lengths)) if (lengths[i]>thresh) i] > ) > assert(is_num(N) || is_vector(N,len(longind)), str("N must be a number or vector of length ",len(longind))) > let( > N = force_list(N,n=len(longind)), > addpts = list_set([],longind,N) > ) > [for(i=[1:1:len(path)-(closed?1:2)]) each lerp(path[i],select(path,i+1), list_range(addpts[i]+1)/(addpts[i]+1))]; > > > triangle = [[0,0],[35,0],[0,30]]; > rtri = round_corners(triangle, method="smooth", cut=1, $fn=8); > !move_copies(subdivide_long_segments(rtri, 5, [4,6,8])) circle(r=.5); _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
When I was referring to effort in controlling point number in paths I meant in the BOSL2 development, not on your part.
The idea of constructing objects with named sections is interesting. I'm wondering if we might implement something like that, but I don't have a clear vision at the moment. It seems like just the idea of providing named mapping for parts and allowing properties to be set based on names could be very nice. You're right that subdivide_long_segments had bugs. More than you noticed. Maybe this version is good? // Function: subdivide_long_segments() // Usage: // subdivide_long_segments(path, thresh, N, <closed>) // Description: // Subdivides the segments in the path whose length exceeds thresh, adding a specified // number of new points to each subdivided segment. You specify the number of points to // add by giving N as a number to add the same number of points on each segment, or a // vector to add a varying number of points on each segment. If you give a vector its // length must match the number of "long" segments. function subdivide_long_segments(path, thresh, N, closed=true) = let( lengths = path_segment_lengths(path,closed=closed), longind = [for(i=idx(lengths)) if (lengths[i]>thresh) i] ) assert(is_num(N) || is_vector(N,len(longind)), str("N must be a number or vector of length ",len(longind))) let( N = force_list(N,n=len(longind)), addpts = list_set([],longind,N,minlen=len(path)-(closed?0:1)) ) [ for(i=[0:1:len(path)-(closed?1:2)]) each lerp(path[i],select(path,i+1), list_range(addpts[i]+1)/(addpts[i]+1)), if (!closed) last(path) ]; I do not understand how you want to "combine" your two paths. Do you want to average them to produce a single path? It would seem that the same kind of problem arises as in skin, namely that you must identify points on the paths with each other. The obvious way to do that is to take the nearest point on the path (path_closest_point). There's an asymmetry since you get different sampling depending on the path you choose as primary.
Sent from the OpenSCAD mailing list archive at Nabble.com. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Hello,
Am 21.03.21 um 13:51 schrieb adrianv: > You're right that subdivide_long_segments had bugs. More than you noticed. Maybe this version is good? Perfect! Thank you! >> path1 = [ [ 0, 0, 0], [ 0, 20, 0], [ 0, 40, 0], [ 0, 60, 0], [ 0, 80, 0] ]; >> >> // path2 is offset 100 to the right and otherwise similar to, but not exactly like path1. >> path2 = [ [100, 0, 0], [100, 24, 0], [100, 40, 1], [100, 62, -3], [100, 80, 0] ]; > I do not understand how you want to "combine" your two paths. I use them to construct the cross-sections given to `skin()`: The intention is to construct cross-sections in the xz-plane and to skin/extrude them along the y-axis. If e.g. the cross section was a polygon similar to a square, the coordinates of the upper left point could follow the first path (trivial for path1), the upper right point could follow the second path, the two bottom points have fixed coordinates (no paths for them). That is, the paths are used to define the contour of the top of the resulting body. So far, I used to keep all y-coordinates of the two paths in sync (contrary to the example with path1 and path2, which for the purpose of the example have a deliberate deviation) so that we can zip them (iterate in sync) and call `skin()` directly. However, the more I think about it, for cases like path1 and path2, it seems unavoidable to resample them like this: for (y=[0, 0.1, 80]) top_left = intersection of path1 with xz-plane through y top_right = intersection of path2 with xz-plane through y polygon[y_index] = [top_left, top_right, some_fixed_bottom_right, some_fixed_bottom_left] Is there a function to intersect paths with planes? Best regards, Carsten _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
To me it seems like if you have only small deviations in the y value that the best result is obtained by mapping corners to corners. Doing interpolation is going to map things in a way that will probably look ugly. So rather than resampling uniform in y, resample (if necessary) to create matching point count along the segments. Skin does not require coplanar polygons, so you can just pull the resulting polygons from the paths.
If you really want to solve the resampling problem, I think you will have to use plane_line_intersection() and walk the path, testing every segment to find the intersection points. Note there can be any number of intersection points between a plane and path, so the function needs to return a list. And I'm not sure what you do about the case where a path segment lie in the plane, but I suppose for your problem you can ignore this case.
Sent from the OpenSCAD mailing list archive at Nabble.com. _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Still not entirely complete, but here are the resulting parts:
https://www.thingiverse.com/thing:4805575 A thousand thanks for your generous help! Best regards, Carsten Am 23.03.21 um 13:18 schrieb adrianv: > To me it seems like if you have only small deviations in the y value that the best result is obtained by mapping corners to corners. Doing interpolation is going to map things in a way that will probably look ugly. So rather than resampling uniform in y, resample (if necessary) to create matching point count along the segments. Skin does not require coplanar polygons, so you can just pull the resulting polygons from the paths. > > If you really want to solve the resampling problem, I think you will have to use plane_line_intersection() and walk the path, testing every segment to find the intersection points. Note there can be any number of intersection points between a plane and path, so the function needs to return a list. And I'm not sure what you do about the case where a path segment lie in the plane, but I suppose for your problem you can ignore this case. > _______________________________________________ OpenSCAD mailing list To unsubscribe send an email to [hidden email] |
Free forum by Nabble | Edit this page |