Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added curvy_path as a complement to smooth_path, updated squircle() #1534

Merged
merged 15 commits into from
Jan 5, 2025

Conversation

amatulic
Copy link
Contributor

@amatulic amatulic commented Dec 30, 2024

  • Change to paths.scad beziers.scad: Added internal function path_to_bezcornerpath() - this function is used in both shapes2d.scad and rounding.scad
  • Changes to rounding.scad:
    • Minor spelling corrections to documentation (changed "aline" to "align")
    • Added documentation for splinesteps that was missing from smooth_path() documentation
    • Added new function curvy_path() to rounding.scad to complement smooth_path(). Both use beziers, but while smooth_path() connects the vertices of a path with a smooth curve, curvy_path() connects the midpoints of each segment with two beziers that join at the corner bisector, creating a bendy path inside each corner. A parameter sharpness, when zero, creates a circular corner arc from the shortest leg, and matches the corner when sharpness=1, with correct rounded corner positions interpolated for values between 0 and 1. An optional cutlimit parameter sets the maximum amount to cut off acute corners if the sharpness setting cuts off too much.
      Added method parameter to smooth_path(). Original method="edges", indicating the smooth path deviates from the segments (edges) of the path, and a new method="corners" indicating the smooth path deviates from the corners while intersecting the tangents of the segment midpoints. Both methods use the same size and relsize parameters. The "corners" method doesn't use tangents or uniform.
  • Changes to shapes2d.scad:
    • Changed squareness parameter name to sharpness because it made more sense, and consistent with parameter name in `curvy_path()' (reverted, changed back to squareness because sharpness is no longer used anywhere else)
    • Expanded squircle() in shapes2d.scad to add a new "bezier" squircle style
    • Added new example of a zoomed-in corner showing the subtle differences between the three styles

@amatulic
Copy link
Contributor Author

amatulic commented Jan 2, 2025

@adrianVmariano - Responses to comments in chat:

I think we need to brainstorm a better name.

I agree. At the time, I couldn't think of something better to use as a complement to smooth_path(). I originally wanted this to be an extension of smooth_path() but the parameters are too dissimilar.

Maybe smooth_path_inside? The curvy path is always interior to corners of the polygon corners, whereas the smooth_path is always exterior. curvy_path connects the segment midpoints, while smooth_path connects the corner vertices. Clearly they are complement each other but I can't think of a name that suggests they're complementary.

Also, the reason I wanted a cut parameter was so you could swap between different kinds of rounding while getting similar looking results.

This isn't intended for rounding corners, it's intended to make continuous curves with no straight edges. As such, the notion of "cut" in round_corners() it was more appropriate to have a limit to the amount of cut, rather than a fixed cut. A fixed cut was not feasible because the maximum "cut" is always determined by the shortest corner leg and it cannot be larger.

The cutlimit option may be useful, though it's not clear to me. It seems like the use pattern would be "darn, that cut off too much, so I'll apply a cut limit" and it's not so different from "darn, that cut off too much, so I'll change the sharpness parameter".

Actually that's all cutlimit does, is provide a convenient way to adjust the sharpness automatically for all corners that exceed the limit, so there is no need to pass an array specifying sharpness for every corner.

Directly setting cut does something more fundamentally different, and as I was saying, lets you potentially switch between this and round_corners.

I don't agree that directly setting the cut is useful for a curve that connects the midpoints of each line segment. It isn't hard to implement, it's basically saying you have a fixed cut but variable sharpness on each corner of an irregular polygon. I disagree that it should abort with an error if set too big for one corner, it should just use the maximum that the corner allows. This has always been a source of frustration for me with round_corners(). I would prefer the parameter to be limited when needed instead of aborting. Display a warning but don't abort.

Also, you can't use "9999999" as infinity. Use actual INF or default it to undef and use a conditional.

I'm embarrassed to admit I wasn't aware of INF.

Your loop that goes from 1 to .9999 can fail if the stepsize is too small. You should use lerpn instead. But also you are computing bezier points in a loop, which is terrible. If you don't want to use bezier_curve() for some reason then you need to set up the computation of all the bezier points as a single matrix multiply (which is what bezier_curve does for you).

Not a problem to use a matrix calculation instead. It's just the cubic case.

Actually I see you say that you're trying to avoid including beziers.scad, but duplicating functionality to avoid importing functionality that already exists does not make sense.

OK, here's where I had a difficult time deciding how the code should be arranged. The internal function to bend a corner needs to be somewhere that gets imported with std.scad because it gets called from both shapes2d.scad and rounding.scad, neither of which is included in std.scad. I didn't want to duplicate code in both places, so I felt paths.scad was the appropriate place for it. I also didn't want to add a new include statement to existing files that didn't have it before.

There is nothing (as far as I can tell) in std.scad that imports beziers.scad. Because the bezier calculation is basically a couple of lines of code, I felt it was better to simply include that calculation instead of dragging in beziers.scad just to support this new feature. I considered importing beziers.scad into shapes2d.scad but again, it would be needed only for squircle(). Do you have a suggestion about how it could be organized?

Would it make sense to have beziers.scad be part of the std.scad collection? That would solve my conundrum. I know in 90% of my projects, I'm using bezier curves. Up until now most of my stuff has been outside of BOSL2, but now that I'm making more use of BOSL2, I'm always including beziers.scad. But I know my own needs aren't the same as others.

Maybe it's best to include the corner bending code in beziers.scad instead of paths.scad and include beziers.scad in shapes2d.scad.

@adrianVmariano
Copy link
Collaborator

Note: I am not sure but wonder if posting here will reduce the number of people who see the comments. It may make sense to discuss the naming issue on the chat. We have never used these comments to discuss anything in the past. I'm not at the moment inspired with alternate names. Despite a very different set of parameters, we could still make this a mode of smooth_path() that just takes a totally different parameter set.

Does your code accept an array of sharpness values? Or just a single value? I was assuming the former. A fixed cut value doesn't make sense. But also, I'm not sure that a fixed sharpness value is a good limitation to impose. It appears to be a needless limitation.

I understand that you're thinking of the code in a particular way, that it "doesn't round corners". But it is still a natural alternative to round_corners because it takes an input path an produces a result that is based on that input but with the corners rounded off. It happens that your version rounds the corners "maximally" in some sense. But it's still a valid understanding of what it's doing. So how can a user flip between round_corners and your method most easily and get results that are the most similar so that the user can see what a essential difference is between approaches?

I think it's important to recognize that I don't know what users are going to do with my stuff, or what users' needs are, so assuming I know that leads to limitations that may be frustrating.

With regards to aborting when the user asks for something invalid, I really don't like the idea that the computer changes your input for you. We have talked about this philosophical difference in the past. I don't want to put a lot of effort into trying to figure out what to do when users ask for something invalid. I think it's potentially confusing to produce a result when the input is invalid. The problem is that there are lots of ways to fix an invalid round_corners rounding input. How does the computer decide? And if it makes the wrong choice (likely), how do I go about fixing it? It's also a problem because you might need to change a corner you already processed. Or at least that might be the best choice, so defining a process for optimizing a set of too-large roundings to fit onto a curve results in a whole new level of complexity and might best be done using some kind of global optimization. Having the computer make this kind of decision will probably make the process opaque to the user, so if I now want to decrease the rounding only on corner 5, I am puzzled when I lower its value substantially and the curve doesn't change....or it changes but at several corners, not just corner 5. Now if I had devised round_corners as a global rounding method and defined it to do something intelligent with excessive rounding values, the story could be different. What happens if the user asks for a unit square to be rounded with the impossible corner radii of 1, 2, 3 and 4? Do you get a circle? Do you get roundings with proportionate ratios that are maximal? Do you need parameters that determine what happens? But that is not the intent of round_corners, which is the simpler action of rounding the corners you indicate by the amount you request

It seems like the main argument in favor of having the computer fiddle with your input is that if you're sufficiently unconcerned with exactly how the result comes out, it lets you get a result with less effort. But for users that do care what exactly is going on, it's going to be frustrating. I'm the second kind of user and more interested in supporting that kind of well-defined process---invalid inputs should always be an error.

At one point I'd talked with Revar about putting rounding.scad into std.scad, which would also include beziers.scad. That was for issue #1384 and we had concluded that it would be OK to do that. But I had trouble with that implementation and didn't ever actually do it. It might be that bringing beziers.scad into std is the right answer. Frankly the architecture was designed by Revar and I don't have much insight into the benefits of having things excluded. Doesn't seem to create any performance penalty, so it's mainly a namespace issue.

Note that matrix multiply is the fastest operation in OpenSCAD, unless things have changed, 1-2 orders of magnitude faster than a list comprehension.

@amatulic
Copy link
Contributor Author

amatulic commented Jan 2, 2025

The code accepts an array of sharpness values.

The cutlimit is a limit to the cut size that applies to all corners. It's optional, and unnecessary if you pass an array of sharpness values, but if you pass it, the cut size gets limited. Also, calling it a "limit" instead of a hard requirement eliminates the need for aborting. A limit is an understandable concept if documented properly. BOSL2 already uses this concept in letting the user substitute other limiting values for EPSILON in certain functions.

Is cutlimit unnecessary? Maybe. I put it in there as an option because while I was playing with different shapes, I found myself wanting a way to limit the amount cut off from long thin corners. Imagine a four-pointed star with sharp points and obtuse angles in between. I wanted round inner corners but less round outer corners. Without cutlimit, sharpness=0 gives me this:
image
With cutlimit, I get what I want:

include <BOSL2/std.scad>
include <BOSL2/rounding.scad>
include <BOSL2/shapes2d.scad>
shape = star(n=4, or=30, ir=5);
stroke(shape, width=0.2, closed=true);
color("red") polygon(curvy_path(shape, sharpness=0, cutlimit=8, closed=true));

image

The problem is that there are lots of ways to fix an invalid round_corners rounding input. How does the computer decide?

For situations where there is one cause of aborting, it is easy to display a warning if that cause is due to a value being outside range that the user cannot know because the range is imposed by geometry. When there are multiple ways to fix it, then I agree it's correct to abort.

On the other hand, if you take the approach of explicitly saying in the documentation that some parameters are treated as limits rather than hard requirements, the issue goes away for those parameters, and it's sufficient to display a warning.

That said, I agree this isn't feasible with round_corners, however it is reasonable for curvy_path (whatever we'd call it).

I would much prefer if some BOSL2 functions simply displayed a console warning about a single-cause problem and fix it by limiting a parameter value. The user still gets information about the problem but also gets to see how it looks, instead of having to fiddle with values until it works. In my view, only invalid inputs that cause a catastrophic failure should abort, otherwise they should be accompanied by a warning.

I did try initially to integrate this into round_corners but it became clear as I was doing this that this curved path is more analogous to smooth_path, and forcing it into round_corners would introduce more incompatible parameters. The sharpness parameter is the main feature, it's a percentage or ratio like with squircle(), and round_corners doesn't have anything like that.

@adrianVmariano
Copy link
Collaborator

I don't object to cutlimit. I'm just saying it doesn't do the different thing I think seems likely to be useful, which is to let you switch between rounding this way or rounding with round_corners in a way where results are comparable.

It seems like automatic limiting like you suggest creates behavioral inconsistency in the library: sometimes when the situation is simple, functions/modules limit a parameter and do something you didn't ask for (but which probably works ok) with an easily overlooked message on the console. And sometimes, when the situation is more complex, invalid parameters generate an error. Are there some specific functions where you think that automatic limiting would be of some great help such as to warrant that kind of inconsistency? Could there be a better solution like a "max" argument or something?

@amatulic
Copy link
Contributor Author

amatulic commented Jan 3, 2025

A "max" or "min" argument might work in some cases. I remember trying some things with round_corners a year or so ago and getting frustrated by the error messages, wishing that a too-high value would simply be clipped, and I could still look at the result and see if it's what I wanted. Making such changes would be a separate PR. Nothing's actually broken so this would be something to look into later.

Here's what I propose as my to-do list:

  • Merge this curvy_path thing into smooth_path. Add new parameters:
    • method = "corners" or "midpoints" to control which smooth path to generate; through the corners or through and tangent to the segment midpoints. Default="corners" for compatibility with existing functionality.
    • sharpness = single value between [0...1] or array of values between [0...1], applicable only to method="midpoints"
    • cutlimit remains optional. Right now, internally it gets converted into a sharpness value that can be different for each corner (sharpness is a fraction while cutlimit is a fixed distance), which serves as a lower limit to the sharpness parameter.
  • Move _bend_path_corner function into beziers.scad
    • Replace the bezier calculations in _bend_path_corner to use bezier_path instead.
    • Use INF as default if needed instead of 999999 (habit of mine from lots of other projects)
  • Either include beziers.scad into shapes2d.scad or make beziers.scad be part of std.scad if @revarbat has no objection.
    • If beziers.scad is part of std.scad, would the documentation automatically change to omit the include <BOSL2/beziers.scad> line, or does something else need to be done?
    • That include line would also need to be removed from other files that already use it, and other files that contain examples that use it. I'm thnking that putting beziers.scad into std.scad should be a separate PR.

@adrianVmariano
Copy link
Collaborator

Using 999999 for infinity works great until somebody models the solar system in OpenSCAD on a meter scale, or makes a nanometer scale model of something. :)

Is there some reason not to support a cut parameter as a complete alternative to sharpness?

Does the shapes2d implementation call curvy_path or does it call _bend_path_corner? Which choice makes the most sense? (I would have thought calling curvy_path would make the most sense.)

Does it make sense to have _bend_path_corner be a user facing function that gives the bezier path, similar to how smooth_path is set up now?

I feel like the method name for the existing one should be "edges" and the new method "corners" because the existing method requires perturbs edges and has parameters associated with edges and the new one perturbs corners and has parameters associated with corners. But this is again a question we might bounce around others. I understand your point that it is preserving corners vs preserving midpoints. I often find that naming things well seems like a big challenge.

@amatulic
Copy link
Contributor Author

amatulic commented Jan 3, 2025

Is there some reason not to support a cut parameter as a complete alternative to sharpness?

If there's a choice between cut and sharpness, then no.

If you're proposing to use cut instead of sharpness, then yes. I want (and need) a proportional parameter, like with squircle(). Sharpness does that. It's the location of the curve closest to the corner, as a proportion of the distance between a circular round and the corner. Cut is a fixed distance value, useless to me for my own purposes. With a constant sharpness I can get a uniform look as the curve passes through the bisector of each corner. I would need to fiddle with various cut values to get the same look with an irregular path. I use cutlimit as a fixed value to modify sharpness if needed, if the sharpness of an acute corner turns out farther than I want it from the corner. Cutlimit translates to a different degree of sharpness depending on how acute the corner is.

As an alternative choice to sharpness, cut runs into the issue of aborting at certain corner angles if you insist on aborting instead of clipping. I find this annoying, but it can be done.

Does the shapes2d implementation call curvy_path or does it call _bend_path_corner?

Both shapes2d.scad and rounding.scad call _bend_path_corner, which is the workhorse. That's why I put it in paths.scad, so it would be common to both files. The curvy_path function in rounding.scad is tiny, it just accounts for open and closed paths and does error checking. The implementation in squircle() is a bit different from curvy_path, it's always closed, and I have to account for the size parameter. It could call curvy_path but then I'd have to include rounding.scad in shapes2d.scad! Not acceptable. It was better to put the bulk of the function in a file accessible to both shapes2d.scad and rounding.scad.

Does it make sense to have _bend_path_corner be a user facing function that gives the bezier path, similar to how smooth_path is set up now?

At its core, _bend_path_corner is just an elaborate setup to calculate control points for two cubic bezier curves that meet at the corner bisector, using sharpness as an input to control how round or sharp the bend is. The next version of it would likely use bezier_path functions.

I suppose it could be useful as a user-facing function to get a curve with specified sharpness that is tangent to the legs of the corners. I need to modify _bend_path_corner so doesn't even know about midpoints of segments, it should just take three points and make a path. curvy_path needs to pass midpoints or endpoints to _bend_path_corner depending on whether the path is open or closed.

Do you think it should be user-facing? If so, it might be best in beziers.scad although a case could be made for keeping it in paths.scad.

As for naming, intuitively I think smooth_path makes a path that intersects all the corners, and curvy_path makes a path that intersects all the edge midpoints. That is, with smooth_path the corner positions constrain the curve, and with curvy_path the edge midpoint positions constrain the curve. The image I posted in the chat showing both of them with the original polygon overlaid seems pretty clear that's what's going on, to me.

This could be handled in documentation. Instead of method we could call the parameter intersect or touch with the values "corner" if the curve must intersect/touch the corners of the input path, or "midpoint" if the curve must intersect/touch the midpoints.

@adrianVmariano
Copy link
Collaborator

I never suggested removing sharpness, just adding cut as an alternative, the way smooth_path has two different size mutually exclusive size options.

Note that you already explained your motivation for the names you suggested, so you don't need to repeat it. I'm just saying that a different conception is possible and to me my proposal is the intuitive one . We should probably ask others their thoughts. I prefer to stick with "method" if possible because it's a pretty common parameter name in the library when there are multiple modes of operation for a function.

As I mentioned earlier, Revar previously approved adding rounding.scad into std.scad in support of a different issue. If we did that, then the code in shapes2d could just call the curvy path function.

The smooth_path() function is literally a passthrough to a bezier path function path_to_bezpath() that computes a bezier path. The only thing that smooth_path does is evaluate the bezier path points to produce a regular path. This gives a user the option of working with the bezier path instead of the path. I don't actually know if it's useful to have the bezier path, or what one might do with it, but you could implement in a similar manner. I was not thinking we wanted the function that just handles a single corner to be user facing. At least at present, none of the corresponding functions for other kinds of corner treatment are user facing.

@amatulic
Copy link
Contributor Author

amatulic commented Jan 3, 2025

OK, going with your naming. If I think of "edges" as deviation from edges and "corners" as deviation from corners, it makes sense. Got it.

I note that in smooth_path, the documentation says "if your size value is too large it will be rounded down." That's what I'd prefer to do with cut: if it's too large it will be rounded down. It's easier to handle that way in code too.

Afterthought: Size is a deviation from the edge with method=edges. It could have a double use as a deviation from the corner with method=corners. Then there's no need to introduce an additional cut parameter. Then the meaning of size is the same in either case, consistent with the method name.

Another afterthought: I just realized that relsize is a proportion just like sharpness. I could eliminate the sharpness parameter, and re-use relsize for method=corners. It would be the opposite of sharpness, with relsize=1 being the maximum deviation (sharpness=0, a circular rounding), and relsize=0 being no deviation from the corner.

This is great, I could integrate curvy_path into smooth_corners without introducing any extra parameters except method! Even the tangents parameter could be used to alter the default tangent at the midpoint of each edge. I don't know why I didn't realize any of this earlier.

@adrianVmariano
Copy link
Collaborator

That does sound tidy. We may want to consider cut as an alternative (duplicate parameter) still for matching with round_corners. Not certain. Maybe it suffices to mention the relationship in the docs.

I guess that given the existing behavior of smooth path it would make sense to have the cut behavior work as you prefer. I'm not sure why I wrote it that way, though one observation is that it's pretty difficult to have insight into the maximum edge deviation, and in this case it is possible to easily threshold the value.

In the case of round_corners you say you wish it would "just" use the maximum but that's not a simple thing or even a well-defined thing to do, as I attempted to explain before. The function actually prints out a set of scale factors based on a simple calculation that it thinks will give valid roundings to guide you towards a valid rounding set. But if you apply them, they are usually unnecessarily extreme and don't necessarily produce a maxiimal rounding. The problem is that everything is interdependent: the maximum rounding at vertex i depends on what roundings have been applied at vertices i-1 and i+1.

@amatulic
Copy link
Contributor Author

amatulic commented Jan 4, 2025

All right. I pulled my code out of paths.scad and put it in beziers.scad, modified it to return a bezier path that smooth_path already uses for the "edges" method. Everything is working great, and I've modified most of the examples to illustrate the differences between each method. I haven't yet figured out how the tangents parameter works so I can implement something similar in the new "corners" method. The example that uses tangents passes an array of vectors multiplied by 1.25 and I don't really know why; I would have expected unit vectors or angles to be specified instead.

@adrianVmariano
Copy link
Collaborator

The tangents parameter gives a direction, so the length of the vector is ignored---the user is not required to normalize them. (That would mean displaying an error if the user supplied a non-normalized input.) The multiplication by 1.25 is pretty silly, though. I suggest you delete that from the example, and you can add to the example text that only the directions of the tangent vectors matter.

I'm not sure that it makes sense for you to support a tangents parameter. What would it do? In the edges method, you need a "tangent" at the corner, which is not exactly a well-defined thing, which is why it seemed desirable to let the user tweak it, or supply an analytically derived value.

Are you assuming that we'll add rounding.scad into std.scad?

@amatulic
Copy link
Contributor Author

amatulic commented Jan 4, 2025

Thanks, that example with tangents confused me. I see now. Does it matter which way the vector points, or does it just set an angle for the tangents on each side of the vertex?

The "corners" curve is always tangent to the midpoint of each segment. I was thinking of using the tangents vector to alter this tangent just to be able to use that parameter, but honestly I am reluctant to do it because I don't see the use, and it would be simpler if the user would specify a tangent offset angle rather than a tangent.

I am not including either beziers.scad or rounding.scad. That seems like a potentially big PR that affects a lot of files, and I'd rather not mix that up with this one, especially since you said earlier that you had trouble making rounding.scad be part of std.scad.

For now I just include beziers.scad in shape2d.scad. That makes this PR work and doesn't break anything else. The next commit of this PR modifies just shapes2d.scad, rounding.scad, and beziers.scad.

Also, from my perspective, it would make more sense to me to have beziers.scad be part of std.scad instead of rounding.scad.

@adrianVmariano
Copy link
Collaborator

Yes, it matters which way the vector points. You'll see if you flip one backwards that it creates a sharp point.

I don't see an obvious application either for adjusting tangents with the corners mode. And indeed, in that case, the baseline tangent is very clearly defined, so there's no reason to want to let the user specify something arbitrary. I think it's perfectly fine if tangents is given to just assert an error, "The tangents parameter is not allowed with method="corners"" or something like that.

I would say that including beziers.scad in shapes2d is equivalent to putting it in std.scad, since shapes2d.scad is in std.scad, but without the doc update that tells users you did so. It sort of hides it. So better to be up front. I'm not sure if you noticed the little "std" subscript that tells you which files are in std.scad on the wiki, for example. Of course...I still don't know how to update the wiki, so there's that issue.

Maybe I wasn't clear about the previous discussion. I talked with Revar about adding rounding.scad to std.scad and he thought that would be OK. I never actually did it because I didn't implement the thing that required it due to uncertainty in how to do the implementation. But presumably it's still OK, so we could do that. If rounding.scad is in then beziers.scad is in.

@amatulic
Copy link
Contributor Author

amatulic commented Jan 4, 2025

Is that really an error? I was going to say in the documentation "the tangents and uniform parameters are ignored with method=corners". I mean, setting those parameters does no harm, but it isn't a problem to assert an error.

@adrianVmariano
Copy link
Collaborator

Yes! Including invalid (unused) parameters should always be an error. The error handling throughout the library is not always complete, but I've had confused 5 minute periods where I could not figure out why a parameter was doing nothing and it was because it was being ignored for some reason---if only an error had been displayed it would have been a 2 second fix instead of 5 minutes of frustration. Also you can give a parameter accidentally with positional parameters and not even realize you've given that parameter.

@adrianVmariano
Copy link
Collaborator

Just to add more on my philosophy here: we should assume that if a user has provided input, they expect it to do something, or they wouldn't have gone to the trouble of providing that input. Silently ignoring input is confusing, just like silently changing input is confusing.

@amatulic
Copy link
Contributor Author

amatulic commented Jan 4, 2025

I get your philosophy. I was thinking more of a user trying something with method="edges" and then thinking "what happens if I change this to corners?" and sets method="corners" without changing anything else. I mean, the user can switch from "corners" to "edges" without changing anything, and I thought the reverse should also be true.

As for putting rounding.scad into std.scad, that would also mean beziers.scad is included anyway, so I figured let's do a baby step and put in beziers.scad first. I still think even that is out of scope for this PR and should be a separate PR.

@adrianVmariano
Copy link
Collaborator

adrianVmariano commented Jan 4, 2025

Yes, I understand the idea of being able to swap methods and have those parameters sitting there, ignored. Yeah, it's easier. But I think that modest convenience is not worth the price. You can just duplicate lines and swap back and forth with comments, which is actually even easier.

Also, suppose you do swap back and forth and end up deciding on a version with "corners" but have a lingering bogus tangents value. Then you pass your code to someone else who is looking at your code going WTF is this tangents value being passed here? What is it doing?

@amatulic
Copy link
Contributor Author

amatulic commented Jan 5, 2025

I get your point. I already implemented the error message. I need to figure out how to use Github to restore paths.scad to what it was. Github always mystifies me, I always have to search for how to do what I want. I know enough to checkout my branch, add files to a commit, and push the commit, but any more than that and I'm lost.

@amatulic
Copy link
Contributor Author

amatulic commented Jan 5, 2025

In beziers.scad I have a function analogous to path_to_bezpath, I call it path_to_bezcornerpath at the moment. It takes a path and creates a bezier path that can be used by bezpath_curve. The bezier path it creates is tangent to the midpoints of the path segments and deviates from the corners based on the size or relsize parameters. Currently it wouldn't appear in the wiki. I wouldn't mind keeping it that way unless you believe it should be exposed to users.

@adrianVmariano
Copy link
Collaborator

I don't really know if that bezier path is useful to users or not. Do you have any thoughts on the matter? Full parallelism would suggest exposing it (though under what name, I don't know).

@amatulic
Copy link
Contributor Author

amatulic commented Jan 5, 2025

I can't think of any use for it other than to duplicate the functionality of smooth_path with method="corners". It's called by both smooth_path and squircle. I did document it with comments led by /// instead of // so it doesn't appear in the wiki.

@adrianVmariano
Copy link
Collaborator

adrianVmariano commented Jan 5, 2025

The reason it would be useful would be that you want to directly operate on the bezier path somehow before producing your final result. But I don't know what that operation would be. Maybe making it into a surface somehow?

@amatulic
Copy link
Contributor Author

amatulic commented Jan 5, 2025

If I wanted to make a surface, I could just use vnf_vertex_array or vnf_tri_array with multiple calls to smooth_path to generate each row in 3D. I wouldn't be as interested in the bezier paths as much as the final bezier curves.

Intuitively when I'd want to make a curvy path, the first thing I think of is to define the extrema of the curve (the corners that the curve passes through), so the original smooth_path ("edges" method) makes a lot of sense. This new "corners" method doesn't make as much intuitive sense, at least to me. It's useful to me for making smooth continuous curves from polygons or stars, or for making a smooth curve constrained within some bounds, but for those purposes I don't really care about what the bezier control points are, I just want the curve.

@adrianVmariano
Copy link
Collaborator

You could do that, but perhaps you could do something simpler involving a bezier patch arrangement. I don't know enough about beziers to say if this is a convenient thing. The question is not whether a method exists without using the bezier points but whether a method exists (that people might want to use) which does use the bezier points.

@adrianVmariano adrianVmariano merged commit 5e96d17 into BelfrySCAD:master Jan 5, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants