discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Fixed number of points with BOSL2 round_corners()?

CF
Carsten Fuchs
Thu, Mar 4, 2021 9:30 AM

Dear group,

with the round_cordners() function from BOSL2 (https://github.com/revarbat/BOSL2/wiki/rounding.scad#function-round_corners), when I use the joint parameter along with $fn, is this guaranteed to always result in the same number of points, even if the angles at the corners are very small or even zero?

For example:

    round_corners(
        points,
        method="smooth",
        joint=list_of_joint_values,
        $fn=12   // is this guaranteed?
    )

My intention is to use the resulting shape with the skin() function from BOSL2, whose O(n³) characteristic of method=distance is too expensive for the number of points in my smoothed polygon.

Best regards,
Carsten

Dear group, with the `round_cordners()` function from BOSL2 (https://github.com/revarbat/BOSL2/wiki/rounding.scad#function-round_corners), when I use the `joint` parameter along with `$fn`, is this guaranteed to always result in the same number of points, even if the angles at the corners are very small or even zero? For example: round_corners( points, method="smooth", joint=list_of_joint_values, $fn=12 // is this guaranteed? ) My intention is to use the resulting shape with the `skin()` function from BOSL2, whose O(n³) characteristic of `method=distance` is too expensive for the number of points in my smoothed polygon. Best regards, Carsten
A
adrianv
Thu, Mar 4, 2021 1:41 PM

As long as you use method="smooth" and the joint parameter is nonzero, and
the $fs value isn't small enough to get involved, you will get exactly $fn
points added to the input.  If the joint parameter is exactly zero you will
get zero points added:  round_corners will not generate duplicated points at
a single vertex.

I am curious to see your example where you want to use skin() with the
"distance" method but with a rounded shape.  I didn't find this method to be
particularly suitable to such shapes, so what did I overlook?

Carsten Fuchs wrote

Dear group,

with the round_cordners() function from BOSL2
(https://github.com/revarbat/BOSL2/wiki/rounding.scad#function-round_corners),
when I use the joint parameter along with $fn, is this guaranteed to
always result in the same number of points, even if the angles at the
corners are very small or even zero?

For example:

     round_corners(
         points,
         method="smooth",
         joint=list_of_joint_values,
         $fn=12   // is this guaranteed?
     )

My intention is to use the resulting shape with the skin() function from
BOSL2, whose O(n³) characteristic of method=distance is too expensive
for the number of points in my smoothed polygon.

Best regards,
Carsten


OpenSCAD mailing list

Discuss@.openscad

As long as you use method="smooth" and the joint parameter is nonzero, and the $fs value isn't small enough to get involved, you will get exactly $fn points added to the input. If the joint parameter is exactly zero you will get zero points added: round_corners will not generate duplicated points at a single vertex. I am curious to see your example where you want to use skin() with the "distance" method but with a rounded shape. I didn't find this method to be particularly suitable to such shapes, so what did I overlook? Carsten Fuchs wrote > Dear group, > > with the `round_cordners()` function from BOSL2 > (https://github.com/revarbat/BOSL2/wiki/rounding.scad#function-round_corners), > when I use the `joint` parameter along with `$fn`, is this guaranteed to > always result in the same number of points, even if the angles at the > corners are very small or even zero? > > For example: > > round_corners( > points, > method="smooth", > joint=list_of_joint_values, > $fn=12 // is this guaranteed? > ) > > My intention is to use the resulting shape with the `skin()` function from > BOSL2, whose O(n³) characteristic of `method=distance` is too expensive > for the number of points in my smoothed polygon. > > Best regards, > Carsten > > _______________________________________________ > OpenSCAD mailing list > Discuss@.openscad > http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org -- Sent from: http://forum.openscad.org/
CF
Carsten Fuchs
Fri, Mar 5, 2021 7:19 AM

Hello,

Am 04.03.21 um 14:41 schrieb adrianv:

As long as you use method="smooth" and the joint parameter is nonzero, and the $fs value isn't small enough to get involved, you will get exactly $fn points added to the input.  If the joint parameter is exactly zero you will get zero points added:  round_corners will not generate duplicated points at a single vertex.  

Does that mean if joint is non-zero, $fn points are added even if the angle at the corner is zero?
(e.g. evenly spaced between the corner +/- joint value?)

I am curious to see your example where you want to use skin() with the "distance" method but with a rounded shape.  I didn't find this method to be particularly suitable to such shapes, so what did I overlook?  

We need the "distance" method whenever the number of points between two shapes for skin() changes and the other methods don't work well, don't we?

As method="distance" is O(n³) expensive, the aim of my question was to create the rounded input shapes to have the same number of points right from the start, so that method="direct" can be used.

Verbal version:

(You've kindly helped me a few weeks ago with the basis of this, so this is in fact a follow-up question.)

I have a shape (list of points) that (very roughly, here for simplification) follows the capital letter "M" (five points). These are rounded with round_corners(), then offset with function offset(), then concatenated, so that the concatenated result follows the outline of the letter "M".

Now I want to morph the shape, e.g. moving the original point in the center of the "M" upwards until it is at the same height as the upper points left and right. When this is done, the number of points for the morphed "M" that result from the rounding are less than the number of points of the original "M".

Now I would like to use skin() with the original "M" and the morphed "M".
I was not able to get good results with method="direct", whereas method="distance" looked good. However, it is also very expensive.

So my intention was to create the original "M" and the morphed "M" with the same number of points right from the start so that I can use method="direct"?

Sorry if this description is difficult to follow. I can make example code if it helps to clarify?

Best regards,
Carsten

Hello, Am 04.03.21 um 14:41 schrieb adrianv: > As long as you use method="smooth" and the joint parameter is nonzero, and the $fs value isn't small enough to get involved, you will get exactly $fn points added to the input.  If the joint parameter is exactly zero you will get zero points added:  round_corners will not generate duplicated points at a single vertex.   Does that mean if `joint` is non-zero, $fn points are added even if the angle at the corner is zero? (e.g. evenly spaced between the corner +/- joint value?) > I am curious to see your example where you want to use skin() with the "distance" method but with a rounded shape.  I didn't find this method to be particularly suitable to such shapes, so what did I overlook?   We need the "distance" method whenever the number of points between two shapes for `skin()` changes and the other methods don't work well, don't we? As `method="distance"` is O(n³) expensive, the aim of my question was to create the rounded input shapes to have the same number of points right from the start, so that `method="direct"` can be used. Verbal version: (You've kindly helped me a few weeks ago with the basis of this, so this is in fact a follow-up question.) I have a shape (list of points) that (very roughly, here for simplification) follows the capital letter "M" (five points). These are rounded with `round_corners()`, then offset with function `offset()`, then concatenated, so that the concatenated result follows the outline of the letter "M". Now I want to morph the shape, e.g. moving the original point in the center of the "M" upwards until it is at the same height as the upper points left and right. When this is done, the number of points for the morphed "M" that result from the rounding are less than the number of points of the original "M". Now I would like to use `skin()` with the original "M" and the morphed "M". I was not able to get good results with `method="direct"`, whereas `method="distance"` looked good. However, it is also very expensive. So my intention was to create the original "M" and the morphed "M" with the same number of points right from the start so that I can use `method="direct"`? Sorry if this description is difficult to follow. I can make example code if it helps to clarify? Best regards, Carsten
A
adrianv
Fri, Mar 5, 2021 1:51 PM

Yes, if the angle is zero (by which I assume you mean collinear points) then
you will get evenly spaced points between the joint points.  (Note: you can
test this yourself by running an example.)

With regards to skin, I honestly don't know whether I covered all the cases
with the provided methods.    If you are finding that you really need
"distance" with an example that has a large number of points then one
possible solution might be to supply an O(n^2) version of the distance
method that assumes that point 0 aligns to point 0.  (The current code runs
an alignment calculation one time for every possible shift of the two
profiles, which adds an extra O(n) to the run time.)

Another idea would be to run the part of the model that changes (the "point"
of the M) separately from the rest of the model and then union them.

In any case, I am curious to see the example as you had it configured with
the "distance" method, so if you can post some code that would be nice.

Carsten Fuchs wrote

Hello,

Am 04.03.21 um 14:41 schrieb adrianv:

As long as you use method="smooth" and the joint parameter is nonzero,
and the $fs value isn't small enough to get involved, you will get
exactly $fn points added to the input.  If the joint parameter is exactly
zero you will get zero points added:  round_corners will not generate
duplicated points at a single vertex.  

Does that mean if joint is non-zero, $fn points are added even if the
angle at the corner is zero?
(e.g. evenly spaced between the corner +/- joint value?)

I am curious to see your example where you want to use skin() with the
"distance" method but with a rounded shape.  I didn't find this method to
be particularly suitable to such shapes, so what did I overlook?  

We need the "distance" method whenever the number of points between two
shapes for skin() changes and the other methods don't work well, don't
we?

As method="distance" is O(n³) expensive, the aim of my question was to
create the rounded input shapes to have the same number of points right
from the start, so that method="direct" can be used.

Verbal version:

(You've kindly helped me a few weeks ago with the basis of this, so this
is in fact a follow-up question.)

I have a shape (list of points) that (very roughly, here for
simplification) follows the capital letter "M" (five points). These are
rounded with round_corners(), then offset with function offset(), then
concatenated, so that the concatenated result follows the outline of the
letter "M".

Now I want to morph the shape, e.g. moving the original point in the
center of the "M" upwards until it is at the same height as the upper
points left and right. When this is done, the number of points for the
morphed "M" that result from the rounding are less than the number of
points of the original "M".

Now I would like to use skin() with the original "M" and the morphed
"M".
I was not able to get good results with method="direct", whereas
method="distance" looked good. However, it is also very expensive.

So my intention was to create the original "M" and the morphed "M" with
the same number of points right from the start so that I can use
method="direct"?

Sorry if this description is difficult to follow. I can make example code
if it helps to clarify?

Best regards,
Carsten


OpenSCAD mailing list

Discuss@.openscad

Yes, if the angle is zero (by which I assume you mean collinear points) then you will get evenly spaced points between the joint points. (Note: you can test this yourself by running an example.) With regards to skin, I honestly don't know whether I covered all the cases with the provided methods. If you are finding that you really need "distance" with an example that has a large number of points then one possible solution might be to supply an O(n^2) version of the distance method that assumes that point 0 aligns to point 0. (The current code runs an alignment calculation one time for every possible shift of the two profiles, which adds an extra O(n) to the run time.) Another idea would be to run the part of the model that changes (the "point" of the M) separately from the rest of the model and then union them. In any case, I am curious to see the example as you had it configured with the "distance" method, so if you can post some code that would be nice. Carsten Fuchs wrote > Hello, > > Am 04.03.21 um 14:41 schrieb adrianv: >> As long as you use method="smooth" and the joint parameter is nonzero, >> and the $fs value isn't small enough to get involved, you will get >> exactly $fn points added to the input.  If the joint parameter is exactly >> zero you will get zero points added:  round_corners will not generate >> duplicated points at a single vertex.   > > Does that mean if `joint` is non-zero, $fn points are added even if the > angle at the corner is zero? > (e.g. evenly spaced between the corner +/- joint value?) > >> I am curious to see your example where you want to use skin() with the >> "distance" method but with a rounded shape.  I didn't find this method to >> be particularly suitable to such shapes, so what did I overlook?   > > We need the "distance" method whenever the number of points between two > shapes for `skin()` changes and the other methods don't work well, don't > we? > > As `method="distance"` is O(n³) expensive, the aim of my question was to > create the rounded input shapes to have the same number of points right > from the start, so that `method="direct"` can be used. > > > Verbal version: > > (You've kindly helped me a few weeks ago with the basis of this, so this > is in fact a follow-up question.) > > I have a shape (list of points) that (very roughly, here for > simplification) follows the capital letter "M" (five points). These are > rounded with `round_corners()`, then offset with function `offset()`, then > concatenated, so that the concatenated result follows the outline of the > letter "M". > > Now I want to morph the shape, e.g. moving the original point in the > center of the "M" upwards until it is at the same height as the upper > points left and right. When this is done, the number of points for the > morphed "M" that result from the rounding are less than the number of > points of the original "M". > > Now I would like to use `skin()` with the original "M" and the morphed > "M". > I was not able to get good results with `method="direct"`, whereas > `method="distance"` looked good. However, it is also very expensive. > > So my intention was to create the original "M" and the morphed "M" with > the same number of points right from the start so that I can use > `method="direct"`? > > > Sorry if this description is difficult to follow. I can make example code > if it helps to clarify? > > Best regards, > Carsten > > > > _______________________________________________ > OpenSCAD mailing list > Discuss@.openscad > http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org -- Sent from: http://forum.openscad.org/
CF
Carsten Fuchs
Mon, Mar 8, 2021 10:10 AM

Thanks for your reply!

Am 05.03.21 um 14:51 schrieb adrianv:

Yes, if the angle is zero (by which I assume you mean collinear points) then you will get evenly spaced points between the joint points.

Yes, sorry, colinear is the proper word.

 (Note: you can test this yourself by running an example.)

Yes, but I was wondering if this a part of the stable API, as it seems a case where optimization might remove these extra points in the future.

With regards to skin, I honestly don't know whether I covered all the cases with the provided methods.    If you are finding that you really need "distance" with an example that has a large number of points then one possible solution might be to supply an O(n^2) version of the distance method that assumes that point 0 aligns to point 0.  (The current code runs an alignment calculation one time for every possible shift of the two profiles, which adds an extra O(n) to the run time.)  

I'm not sure if I understood you correctly, but between two shapes, if one pair of points can be assumed to match (e.g. the first pair at indices 0, or a pair found by picking index 0 in the first shape and finding the smallest distance among all points in the second shape), maybe it would be possible to traverse from there:

For example, if we are at index 0 in the first shape and index 27 was the closest point in the second shape, iterate to index 1 in the first shape, then examine indices 27 and 28 in the second shape, picking the one that is closest? (I've not fully thought this through, this method is probably not good for generalization.)

Another idea would be to run the part of the model that changes (the "point" of the M) separately from the rest of the model and then union them.  

In any case, I am curious to see the example as you had it configured with the "distance" method, so if you can post some code that would be nice.

The attached sample is more complex than the "M", but it shows my case well: Please see module TestBody() with methods "direct" vs. "distance".

Best regards,
Carsten

 

 Carsten Fuchs wrote
 Hello,

 Am 04.03.21 um 14:41 schrieb adrianv:

As long as you use method="smooth" and the joint parameter is nonzero, and the $fs value isn't small enough to get involved, you will get exactly $fn points added to the input.  If the joint parameter is exactly zero you will get zero points added:  round_corners will not generate duplicated points at a single vertex.  

 Does that mean if `joint` is non-zero, $fn points are added even if the angle at the corner is zero?
 (e.g. evenly spaced between the corner +/- joint value?)

I am curious to see your example where you want to use skin() with the "distance" method but with a rounded shape.  I didn't find this method to be particularly suitable to such shapes, so what did I overlook?  

 We need the "distance" method whenever the number of points between two shapes for `skin()` changes and the other methods don't work well, don't we?

 As `method="distance"` is O(n³) expensive, the aim of my question was to create the rounded input shapes to have the same number of points right from the start, so that `method="direct"` can be used.


 Verbal version:

 (You've kindly helped me a few weeks ago with the basis of this, so this is in fact a follow-up question.)

 I have a shape (list of points) that (very roughly, here for simplification) follows the capital letter "M" (five points). These are rounded with `round_corners()`, then offset with function `offset()`, then concatenated, so that the concatenated result follows the outline of the letter "M".

 Now I want to morph the shape, e.g. moving the original point in the center of the "M" upwards until it is at the same height as the upper points left and right. When this is done, the number of points for the morphed "M" that result from the rounding are less than the number of points of the original "M".

 Now I would like to use `skin()` with the original "M" and the morphed "M".
 I was not able to get good results with `method="direct"`, whereas `method="distance"` looked good. However, it is also very expensive.

 So my intention was to create the original "M" and the morphed "M" with the same number of points right from the start so that I can use `method="direct"`?


 Sorry if this description is difficult to follow. I can make example code if it helps to clarify?

 Best regards,
 Carsten
Thanks for your reply! Am 05.03.21 um 14:51 schrieb adrianv: > Yes, if the angle is zero (by which I assume you mean collinear points) then you will get evenly spaced points between the joint points. Yes, sorry, colinear is the proper word. >  (Note: you can test this yourself by running an example.) Yes, but I was wondering if this a part of the stable API, as it seems a case where optimization might remove these extra points in the future. > With regards to skin, I honestly don't know whether I covered all the cases with the provided methods.    If you are finding that you really need "distance" with an example that has a large number of points then one possible solution might be to supply an O(n^2) version of the distance method that assumes that point 0 aligns to point 0.  (The current code runs an alignment calculation one time for every possible shift of the two profiles, which adds an extra O(n) to the run time.)   I'm not sure if I understood you correctly, but between two shapes, if one pair of points can be assumed to match (e.g. the first pair at indices 0, or a pair found by picking index 0 in the first shape and finding the smallest distance among all points in the second shape), maybe it would be possible to traverse from there: For example, if we are at index 0 in the first shape and index 27 was the closest point in the second shape, iterate to index 1 in the first shape, then examine indices 27 and 28 in the second shape, picking the one that is closest? (I've not fully thought this through, this method is probably not good for generalization.) > Another idea would be to run the part of the model that changes (the "point" of the M) separately from the rest of the model and then union them.   > > In any case, I am curious to see the example as you had it configured with the "distance" method, so if you can post some code that would be nice. The attached sample is more complex than the "M", but it shows my case well: Please see module TestBody() with methods "direct" vs. "distance". Best regards, Carsten   > > Carsten Fuchs wrote > Hello, > > Am 04.03.21 um 14:41 schrieb adrianv: > > As long as you use method="smooth" and the joint parameter is nonzero, and the $fs value isn't small enough to get involved, you will get exactly $fn points added to the input.  If the joint parameter is exactly zero you will get zero points added:  round_corners will not generate duplicated points at a single vertex.   > > Does that mean if `joint` is non-zero, $fn points are added even if the angle at the corner is zero? > (e.g. evenly spaced between the corner +/- joint value?) > > > I am curious to see your example where you want to use skin() with the "distance" method but with a rounded shape.  I didn't find this method to be particularly suitable to such shapes, so what did I overlook?   > > We need the "distance" method whenever the number of points between two shapes for `skin()` changes and the other methods don't work well, don't we? > > As `method="distance"` is O(n³) expensive, the aim of my question was to create the rounded input shapes to have the same number of points right from the start, so that `method="direct"` can be used. > > > Verbal version: > > (You've kindly helped me a few weeks ago with the basis of this, so this is in fact a follow-up question.) > > I have a shape (list of points) that (very roughly, here for simplification) follows the capital letter "M" (five points). These are rounded with `round_corners()`, then offset with function `offset()`, then concatenated, so that the concatenated result follows the outline of the letter "M". > > Now I want to morph the shape, e.g. moving the original point in the center of the "M" upwards until it is at the same height as the upper points left and right. When this is done, the number of points for the morphed "M" that result from the rounding are less than the number of points of the original "M". > > Now I would like to use `skin()` with the original "M" and the morphed "M". > I was not able to get good results with `method="direct"`, whereas `method="distance"` looked good. However, it is also very expensive. > > So my intention was to create the original "M" and the morphed "M" with the same number of points right from the start so that I can use `method="direct"`? > > > Sorry if this description is difficult to follow. I can make example code if it helps to clarify? > > Best regards, > Carsten >
A
adrianv
Mon, Mar 8, 2021 9:18 PM

I have no plans to change the behavior: you will always get $fn points.  I
will document the behavior.

I took a look at your example.  I think that it is a kind of example that in
principle one wants to solve with the "tangent" method, but unfortunately
this method fails when the shape has concave regions.  I agree that the
result with "reindex" is terrible.  So "distance" is your only option if you
seek an automated approach.

First a couple observations.  When you use "distance" the resulting shape is
not linear.  I find I want slices=10 perhaps to avoid zig-zags in the
surface.  You have slices=0 which I feel produces a poor result.  Secondly,
you have some very easy ways to divide the problem into smaller pieces.  You
can separate the inside curve and outside curve and skin() them separately
and then take the difference().  I tried this and run time fell from 90s to
20s.  With O(N^3) run time it helps a lot to do two half-sized problems
instead of one full problem.  Another big time saver is that your model has
mirror symmetry, so you can cut it in half once more and use mirror() to
create the second after after skinning.  This should give another big
savings.  So that's where you can go if you want to get a quick solution
without working too hard using the existing tools.

I have to say I feel that the solution is a bit funny looking around one of
the curves.  I suspect that if you manually match profiles, as you have been
trying to do, you should be able to get a superior solution.

This situation raises the question I previously mentioned about supplying a
"fast_distance" method that has O(N^2) run time.  The current method must
assume a starting alignment, so if you have polygons p and q it tries
aligning p[0] with q[0], then p[1] with q[0], then p[2] with q[0] and so on.
Once you pick a starting point, the algorithm that decides where to insert
the edges runs in quadratic time.  So the "fast_distance" method would
assume that p[0] aligns with q[0].  For your problem this seems to be
obviously desired.  Do you think this "fast_distance" method is worth
adding?  (And is there a better name for it?)

Carsten Fuchs wrote

Thanks for your reply!

Am 05.03.21 um 14:51 schrieb adrianv:

Yes, if the angle is zero (by which I assume you mean collinear points)
then you will get evenly spaced points between the joint points.

Yes, sorry, colinear is the proper word.

 (Note: you can test this yourself by running an example.)

Yes, but I was wondering if this a part of the stable API, as it seems a
case where optimization might remove these extra points in the future.

With regards to skin, I honestly don't know whether I covered all the
cases with the provided methods.    If you are finding that you really
need "distance" with an example that has a large number of points then
one possible solution might be to supply an O(n^2) version of the
distance method that assumes that point 0 aligns to point 0.  (The
current code runs an alignment calculation one time for every possible
shift of the two profiles, which adds an extra O(n) to the run time.)  

I'm not sure if I understood you correctly, but between two shapes, if one
pair of points can be assumed to match (e.g. the first pair at indices 0,
or a pair found by picking index 0 in the first shape and finding the
smallest distance among all points in the second shape), maybe it would be
possible to traverse from there:

For example, if we are at index 0 in the first shape and index 27 was the
closest point in the second shape, iterate to index 1 in the first shape,
then examine indices 27 and 28 in the second shape, picking the one that
is closest? (I've not fully thought this through, this method is probably
not good for generalization.)

Another idea would be to run the part of the model that changes (the
"point" of the M) separately from the rest of the model and then union
them.  

In any case, I am curious to see the example as you had it configured
with the "distance" method, so if you can post some code that would be
nice.

The attached sample is more complex than the "M", but it shows my case
well: Please see module TestBody() with methods "direct" vs. "distance".

Best regards,
Carsten

 

 Carsten Fuchs wrote
 Hello,

 Am 04.03.21 um 14:41 schrieb adrianv:

As long as you use method="smooth" and the joint parameter is

nonzero, and the $fs value isn't small enough to get involved, you will
get exactly $fn points added to the input.  If the joint parameter is
exactly zero you will get zero points added:  round_corners will not
generate duplicated points at a single vertex.  

 Does that mean if `joint` is non-zero, $fn points are added even if

the angle at the corner is zero?
(e.g. evenly spaced between the corner +/- joint value?)

I am curious to see your example where you want to use skin() with

the "distance" method but with a rounded shape.  I didn't find this
method to be particularly suitable to such shapes, so what did I
overlook?  

 We need the "distance" method whenever the number of points between

two shapes for skin() changes and the other methods don't work well,
don't we?

 As `method="distance"` is O(n³) expensive, the aim of my question was

to create the rounded input shapes to have the same number of points
right from the start, so that method="direct" can be used.

 Verbal version:

 (You've kindly helped me a few weeks ago with the basis of this, so

this is in fact a follow-up question.)

 I have a shape (list of points) that (very roughly, here for

simplification) follows the capital letter "M" (five points). These are
rounded with round_corners(), then offset with function offset(),
then concatenated, so that the concatenated result follows the outline of
the letter "M".

 Now I want to morph the shape, e.g. moving the original point in the

center of the "M" upwards until it is at the same height as the upper
points left and right. When this is done, the number of points for the
morphed "M" that result from the rounding are less than the number of
points of the original "M".

 Now I would like to use `skin()` with the original "M" and the

morphed "M".
I was not able to get good results with method="direct", whereas
method="distance" looked good. However, it is also very expensive.

 So my intention was to create the original "M" and the morphed "M"

with the same number of points right from the start so that I can use
method="direct"?

 Sorry if this description is difficult to follow. I can make example

code if it helps to clarify?

 Best regards,
 Carsten

OpenSCAD mailing list

Discuss@.openscad

http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

test.scad (4K)
<http://forum.openscad.org/attachment/32149/0/test.scad>

I have no plans to change the behavior: you will always get $fn points. I will document the behavior. I took a look at your example. I think that it is a kind of example that in principle one wants to solve with the "tangent" method, but unfortunately this method fails when the shape has concave regions. I agree that the result with "reindex" is terrible. So "distance" is your only option if you seek an automated approach. First a couple observations. When you use "distance" the resulting shape is not linear. I find I want slices=10 perhaps to avoid zig-zags in the surface. You have slices=0 which I feel produces a poor result. Secondly, you have some very easy ways to divide the problem into smaller pieces. You can separate the inside curve and outside curve and skin() them separately and then take the difference(). I tried this and run time fell from 90s to 20s. With O(N^3) run time it helps a lot to do two half-sized problems instead of one full problem. Another big time saver is that your model has mirror symmetry, so you can cut it in half once more and use mirror() to create the second after after skinning. This should give another big savings. So that's where you can go if you want to get a quick solution without working too hard using the existing tools. I have to say I feel that the solution is a bit funny looking around one of the curves. I suspect that if you manually match profiles, as you have been trying to do, you should be able to get a superior solution. This situation raises the question I previously mentioned about supplying a "fast_distance" method that has O(N^2) run time. The current method must assume a starting alignment, so if you have polygons p and q it tries aligning p[0] with q[0], then p[1] with q[0], then p[2] with q[0] and so on. Once you pick a starting point, the algorithm that decides where to insert the edges runs in quadratic time. So the "fast_distance" method would assume that p[0] aligns with q[0]. For your problem this seems to be obviously desired. Do you think this "fast_distance" method is worth adding? (And is there a better name for it?) Carsten Fuchs wrote > Thanks for your reply! > > Am 05.03.21 um 14:51 schrieb adrianv: >> Yes, if the angle is zero (by which I assume you mean collinear points) >> then you will get evenly spaced points between the joint points. > > Yes, sorry, colinear is the proper word. > >>  (Note: you can test this yourself by running an example.) > > Yes, but I was wondering if this a part of the stable API, as it seems a > case where optimization might remove these extra points in the future. > >> With regards to skin, I honestly don't know whether I covered all the >> cases with the provided methods.    If you are finding that you really >> need "distance" with an example that has a large number of points then >> one possible solution might be to supply an O(n^2) version of the >> distance method that assumes that point 0 aligns to point 0.  (The >> current code runs an alignment calculation one time for every possible >> shift of the two profiles, which adds an extra O(n) to the run time.)   > > I'm not sure if I understood you correctly, but between two shapes, if one > pair of points can be assumed to match (e.g. the first pair at indices 0, > or a pair found by picking index 0 in the first shape and finding the > smallest distance among all points in the second shape), maybe it would be > possible to traverse from there: > > For example, if we are at index 0 in the first shape and index 27 was the > closest point in the second shape, iterate to index 1 in the first shape, > then examine indices 27 and 28 in the second shape, picking the one that > is closest? (I've not fully thought this through, this method is probably > not good for generalization.) > >> Another idea would be to run the part of the model that changes (the >> "point" of the M) separately from the rest of the model and then union >> them.   >> >> In any case, I am curious to see the example as you had it configured >> with the "distance" method, so if you can post some code that would be >> nice. > > The attached sample is more complex than the "M", but it shows my case > well: Please see module TestBody() with methods "direct" vs. "distance". > > Best regards, > Carsten > > >   >> >> Carsten Fuchs wrote >> Hello, >> >> Am 04.03.21 um 14:41 schrieb adrianv: >> > As long as you use method="smooth" and the joint parameter is >> nonzero, and the $fs value isn't small enough to get involved, you will >> get exactly $fn points added to the input.  If the joint parameter is >> exactly zero you will get zero points added:  round_corners will not >> generate duplicated points at a single vertex.   >> >> Does that mean if `joint` is non-zero, $fn points are added even if >> the angle at the corner is zero? >> (e.g. evenly spaced between the corner +/- joint value?) >> >> > I am curious to see your example where you want to use skin() with >> the "distance" method but with a rounded shape.  I didn't find this >> method to be particularly suitable to such shapes, so what did I >> overlook?   >> >> We need the "distance" method whenever the number of points between >> two shapes for `skin()` changes and the other methods don't work well, >> don't we? >> >> As `method="distance"` is O(n³) expensive, the aim of my question was >> to create the rounded input shapes to have the same number of points >> right from the start, so that `method="direct"` can be used. >> >> >> Verbal version: >> >> (You've kindly helped me a few weeks ago with the basis of this, so >> this is in fact a follow-up question.) >> >> I have a shape (list of points) that (very roughly, here for >> simplification) follows the capital letter "M" (five points). These are >> rounded with `round_corners()`, then offset with function `offset()`, >> then concatenated, so that the concatenated result follows the outline of >> the letter "M". >> >> Now I want to morph the shape, e.g. moving the original point in the >> center of the "M" upwards until it is at the same height as the upper >> points left and right. When this is done, the number of points for the >> morphed "M" that result from the rounding are less than the number of >> points of the original "M". >> >> Now I would like to use `skin()` with the original "M" and the >> morphed "M". >> I was not able to get good results with `method="direct"`, whereas >> `method="distance"` looked good. However, it is also very expensive. >> >> So my intention was to create the original "M" and the morphed "M" >> with the same number of points right from the start so that I can use >> `method="direct"`? >> >> >> Sorry if this description is difficult to follow. I can make example >> code if it helps to clarify? >> >> Best regards, >> Carsten >> > > _______________________________________________ > OpenSCAD mailing list > Discuss@.openscad > http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org > > > test.scad (4K) > &lt;http://forum.openscad.org/attachment/32149/0/test.scad&gt; -- Sent from: http://forum.openscad.org/
CF
Carsten Fuchs
Fri, Mar 12, 2021 7:34 AM

Hello,

Am 08.03.21 um 22:18 schrieb adrianv:

I have no plans to change the behavior: you will always get $fn points.  I will document the behavior.  

Thanks!

[…] So that's where you can go if you want to get a quick solution without working too hard using the existing tools.  

Thank you!

This situation raises the question I previously mentioned about supplying a "fast_distance" method that has O(N^2) run time.  The current method must assume a starting alignment, so if you have polygons p and q it tries aligning p[0] with q[0], then p[1] with q[0], then p[2] with q[0] and so on.   Once you pick a starting point, the algorithm that decides where to insert the edges runs in quadratic time.  So the "fast_distance" method would assume that p[0] aligns with q[0].   For your problem this seems to be obviously desired.   Do you think this "fast_distance" method is worth adding?  (And is there a better name for it?)  

To be honest, I don't understand the algorithm enough to properly answer this question. I also looked at the code, but I don't understand the OpenSCAD language enough to follow. (It would be easier if this was C++, Python or Lua.)

The skin() function is very, very powerful. Maybe it is worth a consideration to factor the point alignment methods out of it? This would help with adding other methods, which could be of arbitrary complexity and could also be implemented by users: writing a custom alignment method plug-in would be much closer to my current OpenSCAD language skills, making own attempts much easier.

Also, I was wondering if a variant of method "distance" could work without segmentation, i.e. without adding points to any of the polygons. At least in my case, it seems not needed and if I understand the algorithm correctly, it would reduce the complexity to O(n²).

Best regards,
Carsten

 Carsten Fuchs wrote
 Thanks for your reply!

 Am 05.03.21 um 14:51 schrieb adrianv:

Yes, if the angle is zero (by which I assume you mean collinear points) then you will get evenly spaced points between the joint points.

 Yes, sorry, colinear is the proper word.

 (Note: you can test this yourself by running an example.)

 Yes, but I was wondering if this a part of the stable API, as it seems a case where optimization might remove these extra points in the future.

With regards to skin, I honestly don't know whether I covered all the cases with the provided methods.    If you are finding that you really need "distance" with an example that has a large number of points then one possible solution might be to supply an O(n^2) version of the distance method that assumes that point 0 aligns to point 0.  (The current code runs an alignment calculation one time for every possible shift of the two profiles, which adds an extra O(n) to the run time.)  

 I'm not sure if I understood you correctly, but between two shapes, if one pair of points can be assumed to match (e.g. the first pair at indices 0, or a pair found by picking index 0 in the first shape and finding the smallest distance among all points in the second shape), maybe it would be possible to traverse from there:

 For example, if we are at index 0 in the first shape and index 27 was the closest point in the second shape, iterate to index 1 in the first shape, then examine indices 27 and 28 in the second shape, picking the one that is closest? (I've not fully thought this through, this method is probably not good for generalization.)

Another idea would be to run the part of the model that changes (the "point" of the M) separately from the rest of the model and then union them.  

In any case, I am curious to see the example as you had it configured with the "distance" method, so if you can post some code that would be nice.

 The attached sample is more complex than the "M", but it shows my case well: Please see module TestBody() with methods "direct" vs. "distance".

 Best regards,
 Carsten


   

    Carsten Fuchs wrote
    Hello,

    Am 04.03.21 um 14:41 schrieb adrianv:
    > As long as you use method="smooth" and the joint parameter is nonzero, and the $fs value isn't small enough to get involved, you will get exactly $fn points added to the input.  If the joint parameter is exactly zero you will get zero points added:  round_corners will not generate duplicated points at a single vertex.  

    Does that mean if joint is non-zero, $fn points are added even if the angle at the corner is zero?
    (e.g. evenly spaced between the corner +/- joint value?)

    > I am curious to see your example where you want to use skin() with the "distance" method but with a rounded shape.  I didn't find this method to be particularly suitable to such shapes, so what did I overlook?  

    We need the "distance" method whenever the number of points between two shapes for skin() changes and the other methods don't work well, don't we?

    As method="distance" is O(n³) expensive, the aim of my question was to create the rounded input shapes to have the same number of points right from the start, so that method="direct" can be used.

    Verbal version:

    (You've kindly helped me a few weeks ago with the basis of this, so this is in fact a follow-up question.)

    I have a shape (list of points) that (very roughly, here for simplification) follows the capital letter "M" (five points). These are rounded with round_corners(), then offset with function offset(), then concatenated, so that the concatenated result follows the outline of the letter "M".

    Now I want to morph the shape, e.g. moving the original point in the center of the "M" upwards until it is at the same height as the upper points left and right. When this is done, the number of points for the morphed "M" that result from the rounding are less than the number of points of the original "M".

    Now I would like to use skin() with the original "M" and the morphed "M".
    I was not able to get good results with method="direct", whereas method="distance" looked good. However, it is also very expensive.

    So my intention was to create the original "M" and the morphed "M" with the same number of points right from the start so that I can use method="direct"?

    Sorry if this description is difficult to follow. I can make example code if it helps to clarify?

    Best regards,
    Carsten

Hello, Am 08.03.21 um 22:18 schrieb adrianv: > I have no plans to change the behavior: you will always get $fn points.  I will document the behavior.   Thanks! > […] So that's where you can go if you want to get a quick solution without working too hard using the existing tools.   Thank you! > This situation raises the question I previously mentioned about supplying a "fast_distance" method that has O(N^2) run time.  The current method must assume a starting alignment, so if you have polygons p and q it tries aligning p[0] with q[0], then p[1] with q[0], then p[2] with q[0] and so on.   Once you pick a starting point, the algorithm that decides where to insert the edges runs in quadratic time.  So the "fast_distance" method would assume that p[0] aligns with q[0].   For your problem this seems to be obviously desired.   Do you think this "fast_distance" method is worth adding?  (And is there a better name for it?)   To be honest, I don't understand the algorithm enough to properly answer this question. I also looked at the code, but I don't understand the OpenSCAD language enough to follow. (It would be easier if this was C++, Python or Lua.) The skin() function is very, very powerful. Maybe it is worth a consideration to factor the point alignment methods out of it? This would help with adding other methods, which could be of arbitrary complexity and could also be implemented by users: writing a custom alignment method plug-in would be much closer to my current OpenSCAD language skills, making own attempts much easier. Also, I was wondering if a variant of method "distance" could work without segmentation, i.e. without adding points to any of the polygons. At least in my case, it seems not needed and if I understand the algorithm correctly, it would reduce the complexity to O(n²). Best regards, Carsten > > Carsten Fuchs wrote > Thanks for your reply! > > Am 05.03.21 um 14:51 schrieb adrianv: > > Yes, if the angle is zero (by which I assume you mean collinear points) then you will get evenly spaced points between the joint points. > > Yes, sorry, colinear is the proper word. > > >  (Note: you can test this yourself by running an example.) > > Yes, but I was wondering if this a part of the stable API, as it seems a case where optimization might remove these extra points in the future. > > > With regards to skin, I honestly don't know whether I covered all the cases with the provided methods.    If you are finding that you really need "distance" with an example that has a large number of points then one possible solution might be to supply an O(n^2) version of the distance method that assumes that point 0 aligns to point 0.  (The current code runs an alignment calculation one time for every possible shift of the two profiles, which adds an extra O(n) to the run time.)   > > I'm not sure if I understood you correctly, but between two shapes, if one pair of points can be assumed to match (e.g. the first pair at indices 0, or a pair found by picking index 0 in the first shape and finding the smallest distance among all points in the second shape), maybe it would be possible to traverse from there: > > For example, if we are at index 0 in the first shape and index 27 was the closest point in the second shape, iterate to index 1 in the first shape, then examine indices 27 and 28 in the second shape, picking the one that is closest? (I've not fully thought this through, this method is probably not good for generalization.) > > > Another idea would be to run the part of the model that changes (the "point" of the M) separately from the rest of the model and then union them.   > > > > In any case, I am curious to see the example as you had it configured with the "distance" method, so if you can post some code that would be nice. > > The attached sample is more complex than the "M", but it shows my case well: Please see module TestBody() with methods "direct" vs. "distance". > > Best regards, > Carsten > > >    > > > >     Carsten Fuchs wrote > >     Hello, > > > >     Am 04.03.21 um 14:41 schrieb adrianv: > >     > As long as you use method="smooth" and the joint parameter is nonzero, and the $fs value isn't small enough to get involved, you will get exactly $fn points added to the input.  If the joint parameter is exactly zero you will get zero points added:  round_corners will not generate duplicated points at a single vertex.   > > > >     Does that mean if `joint` is non-zero, $fn points are added even if the angle at the corner is zero? > >     (e.g. evenly spaced between the corner +/- joint value?) > > > >     > I am curious to see your example where you want to use skin() with the "distance" method but with a rounded shape.  I didn't find this method to be particularly suitable to such shapes, so what did I overlook?   > > > >     We need the "distance" method whenever the number of points between two shapes for `skin()` changes and the other methods don't work well, don't we? > > > >     As `method="distance"` is O(n³) expensive, the aim of my question was to create the rounded input shapes to have the same number of points right from the start, so that `method="direct"` can be used. > > > > > >     Verbal version: > > > >     (You've kindly helped me a few weeks ago with the basis of this, so this is in fact a follow-up question.) > > > >     I have a shape (list of points) that (very roughly, here for simplification) follows the capital letter "M" (five points). These are rounded with `round_corners()`, then offset with function `offset()`, then concatenated, so that the concatenated result follows the outline of the letter "M". > > > >     Now I want to morph the shape, e.g. moving the original point in the center of the "M" upwards until it is at the same height as the upper points left and right. When this is done, the number of points for the morphed "M" that result from the rounding are less than the number of points of the original "M". > > > >     Now I would like to use `skin()` with the original "M" and the morphed "M". > >     I was not able to get good results with `method="direct"`, whereas `method="distance"` looked good. However, it is also very expensive. > > > >     So my intention was to create the original "M" and the morphed "M" with the same number of points right from the start so that I can use `method="direct"`? > > > > > >     Sorry if this description is difficult to follow. I can make example code if it helps to clarify? > > > >     Best regards, > >     Carsten > > >
CF
Carsten Fuchs
Fri, Mar 12, 2021 8:37 AM

Hello,

Am 04.03.21 um 14:41 schrieb adrianv:

As long as you use method="smooth" and the joint parameter is nonzero, and the $fs value isn't small enough to get involved, you will get exactly $fn points added to the input.  If the joint parameter is exactly zero you will get zero points added:  round_corners will not generate duplicated points at a single vertex.  

Unfortunately, it doesn't seem to work. Could you please consider the example code below? If flapsbump_rel_height is set to 0, no points are added to the polygon. If set to 0.001, some points are added, but fewer than $fn. I thought that I had forgotten to set $fs, but it doesn't seem to have any effect?

Best regards,
Carsten

include <BOSL2/std.scad>
include <BOSL2/rounding.scad>

function berechneQsKombi(breite, wandstaerke, orig=[0, 0], quality=1.0) =
let(
hb = breite / 2,
hb_tunnel = 70 + wandstaerke,
h_leiste = 6,
b_leiste = 6,
tilt = 0.75,
h_senke = -3,
b_senke = 4,
h_seiten = -64,
h_senkrecht = -36,
flapsbump_rel_height=10.0,  // set to 0 to see the problem

    basis_polygon = [
        [0, 0],
        [15, 0],
        [20, flapsbump_rel_height],
        [hb - b_leiste - tilt - b_senke - 1, flapsbump_rel_height],
        [hb - b_leiste - tilt - b_senke, h_senke + flapsbump_rel_height],
        [hb - b_leiste - tilt, h_senke + flapsbump_rel_height],
        [hb - b_leiste, h_leiste + flapsbump_rel_height],
        [hb, h_leiste + flapsbump_rel_height],
        [max(hb_tunnel, hb), h_seiten],
        [max(hb_tunnel, hb), h_seiten + h_senkrecht],
    ],

    joints = [
        0, 2.0, 2.5, 1.5, 1.0, 1.5, 2.75, 2.75, 8, 0
    ],

    punkte_aussen = round_corners(
        [for (p=basis_polygon) [p.x + orig.x, p.y + orig.y]],
        method="smooth",
        joint=joints,
        k=0.8,
     // $fs=0.02,   // seems to have no effect, even at 0.0
        $fn=10*quality
    ),

    punkte_innen = offset(punkte_aussen, delta=-wandstaerke)
)
concat(punkte_aussen, reverse(punkte_innen));

color("#0088CC")
translate([0, -1, 80])
rotate([90, 0, 0])
linear_extrude(10)
polygon(berechneQsKombi(124, 1.5, quality=1.0));

Hello, Am 04.03.21 um 14:41 schrieb adrianv: > As long as you use method="smooth" and the joint parameter is nonzero, and the $fs value isn't small enough to get involved, you will get exactly $fn points added to the input.  If the joint parameter is exactly zero you will get zero points added:  round_corners will not generate duplicated points at a single vertex.   Unfortunately, it doesn't seem to work. Could you please consider the example code below? If `flapsbump_rel_height` is set to 0, no points are added to the polygon. If set to 0.001, some points are added, but fewer than $fn. I thought that I had forgotten to set $fs, but it doesn't seem to have any effect? Best regards, Carsten include <BOSL2/std.scad> include <BOSL2/rounding.scad> function berechneQsKombi(breite, wandstaerke, orig=[0, 0], quality=1.0) = let( hb = breite / 2, hb_tunnel = 70 + wandstaerke, h_leiste = 6, b_leiste = 6, tilt = 0.75, h_senke = -3, b_senke = 4, h_seiten = -64, h_senkrecht = -36, flapsbump_rel_height=10.0, // set to 0 to see the problem basis_polygon = [ [0, 0], [15, 0], [20, flapsbump_rel_height], [hb - b_leiste - tilt - b_senke - 1, flapsbump_rel_height], [hb - b_leiste - tilt - b_senke, h_senke + flapsbump_rel_height], [hb - b_leiste - tilt, h_senke + flapsbump_rel_height], [hb - b_leiste, h_leiste + flapsbump_rel_height], [hb, h_leiste + flapsbump_rel_height], [max(hb_tunnel, hb), h_seiten], [max(hb_tunnel, hb), h_seiten + h_senkrecht], ], joints = [ 0, 2.0, 2.5, 1.5, 1.0, 1.5, 2.75, 2.75, 8, 0 ], punkte_aussen = round_corners( [for (p=basis_polygon) [p.x + orig.x, p.y + orig.y]], method="smooth", joint=joints, k=0.8, // $fs=0.02, // seems to have no effect, even at 0.0 $fn=10*quality ), punkte_innen = offset(punkte_aussen, delta=-wandstaerke) ) concat(punkte_aussen, reverse(punkte_innen)); color("#0088CC") translate([0, -1, 80]) rotate([90, 0, 0]) linear_extrude(10) polygon(berechneQsKombi(124, 1.5, quality=1.0));
A
adrianv
Fri, Mar 12, 2021 3:29 PM

There is a large comment block entitled "Minimum Distance Mapping using
Dynamic Programming" that explains the algorithm.  I think reading this will
be much more informative than trying to read the code, even for someone
expert in OpenSCAD.  But knowing how the algorithm works isn't very
important.  You just need to know what it does:  it inserts edges so as to
minimize the total sum of edge length.

You suggest to "factor the point alignment methods out".  This API change
would make the function harder to use without providing any benefit.  You
are not required to use the fancy methods.  You can always use simply
method="direct" and ensure that you supply profiles with matching point
count.

Consider this paragraph from the documentation of skin():

For this operation to be well-defined, the profiles must all have the
same vertex count and
we must assume that profiles are aligned so that vertex i links to
vertex i on all polygons.
Many interesting cases do not comply with this restriction.  Two basic
methods can handle
these cases: either add points to edges (resample) so that the profiles
are compatible,
or repeat vertices.  Repeating vertices allows two edges to terminate at
the same point, creating
triangular faces.  You can adjust non-matching profiles yourself
either by resampling them using subdivide_path or by duplicating
vertices using
repeat_entries.  It is OK to pass a profile that has the same vertex
repeated, such as
a square with 5 points (two of which are identical), so that it can match
up to a pentagon.
Such a combination would create a triangular face at the location of the
duplicated vertex.
Alternatively, skin provides methods (described below) for matching up
incompatible paths.

It appears that the text above isn't clear, because you appear confused
about the matter described in this paragraph, namely that you can choose to
construct your own alignment by resampling or repeating vertices.  If you
can indicate how the documentation could be improved that would be great.

To reiterate, for skin to work the profiles MUST all have the same number of
points.  If you have two shapes that fail this requirement then you (or
skin) MUST insert extra points somehow, either by repeating points (which
will result in triangular faces in the skinned shape) or by interpolating
(which results in quadrilateral faces).  If the inputs are incompatible,
skin() by default resamples the smaller one to match the larger.  The
"distance" method, on the other hand, repeats points in the shorter input to
produce a matching length.

If you don't want this to happen, don't invoke skin with incompatible
inputs.  If you want to devise your own fancy (or simple) algorithm for
making the alignment you can do so yourself outside of skin.  There is
nothing standing in the way of this.  All the existing methods do is take
polygons as input and produce new polygons as output that have matching
point count.  You can do whatever complicated calculation you need to insert
extra points so that the shapes have the same length.  Or do like I've been
suggesting and carefully insert extra points to create a "hand-crafted"
alignment for your particular shape.  Then call skin and it will simply link
the shapes you provide without any extra points inserted.

The run time of the "distance" method is determined by the size of the
input.  It is O(N^2) if you assume an initial alignment or O(N^3) if you
have to check every possible alignment.  When you use the "refine" option
with "distance" the refinement takes place after the distance alignment.
It is necessary to give a good rendering for curved "quadrilaterals" but
does not affect how the shapes are aligned, nor the computation time to
align the shapes.  Your shape, however, seems to benefit from insertion of
additional points on the flat regions.

I experimented with your shape and it seems quite difficult to get a truly
beautiful result with the automated methods.  I added "fast_distance" but I
think it is necessary to resample the points along some of the flat sections
to create more options for edges that must align with the curve, and even so
there is still a behavior occurring that I don't like, though it's a bit
subtle.  I tried using resample_path() and it produces a result nice in some
ways and bad in others.  Using $fs for all the arcs gave a result that
appears good on half but not so good on the other half.

To get the best result I think you need to subdivide the curves manually, as
I think you are now trying to do.  In this way you can choose how the
different parts of the curve will align to produce the most pleasing result.
Some of the flat parts need to be subsampled and aligned with curved parts.

Carsten Fuchs wrote

This situation raises the question I previously mentioned about supplying
a "fast_distance" method that has O(N^2) run time.  The current method
must assume a starting alignment, so if you have polygons p and q it
tries aligning p[0] with q[0], then p[1] with q[0], then p[2] with q[0]
and so on.   Once you pick a starting point, the algorithm that decides
where to insert the edges runs in quadratic time.  So the "fast_distance"
method would assume that p[0] aligns with q[0].   For your problem this
seems to be obviously desired.   Do you think this "fast_distance" method
is worth adding?  (And is there a better name for it?)  

To be honest, I don't understand the algorithm enough to properly answer
this question. I also looked at the code, but I don't understand the
OpenSCAD language enough to follow. (It would be easier if this was C++,
Python or Lua.)

The skin() function is very, very powerful. Maybe it is worth a
consideration to factor the point alignment methods out of it? This would
help with adding other methods, which could be of arbitrary complexity and
could also be implemented by users: writing a custom alignment method
plug-in would be much closer to my current OpenSCAD language skills,
making own attempts much easier.

Also, I was wondering if a variant of method "distance" could work without
segmentation, i.e. without adding points to any of the polygons. At least
in my case, it seems not needed and if I understand the algorithm
correctly, it would reduce the complexity to O(n²).

Best regards,
Carsten

 Carsten Fuchs wrote
 Thanks for your reply!

 Am 05.03.21 um 14:51 schrieb adrianv:

Yes, if the angle is zero (by which I assume you mean collinear

points) then you will get evenly spaced points between the joint points.

 Yes, sorry, colinear is the proper word.

 (Note: you can test this yourself by running an example.)

 Yes, but I was wondering if this a part of the stable API, as it

seems a case where optimization might remove these extra points in the
future.

With regards to skin, I honestly don't know whether I covered all

the cases with the provided methods.    If you are finding that you
really need "distance" with an example that has a large number of points
then one possible solution might be to supply an O(n^2) version of the
distance method that assumes that point 0 aligns to point 0.  (The
current code runs an alignment calculation one time for every possible
shift of the two profiles, which adds an extra O(n) to the run time.)  

 I'm not sure if I understood you correctly, but between two shapes,

if one pair of points can be assumed to match (e.g. the first pair at
indices 0, or a pair found by picking index 0 in the first shape and
finding the smallest distance among all points in the second shape),
maybe it would be possible to traverse from there:

 For example, if we are at index 0 in the first shape and index 27 was

the closest point in the second shape, iterate to index 1 in the first
shape, then examine indices 27 and 28 in the second shape, picking the
one that is closest? (I've not fully thought this through, this method is
probably not good for generalization.)

Another idea would be to run the part of the model that changes

(the "point" of the M) separately from the rest of the model and then
union them.  

In any case, I am curious to see the example as you had it

configured with the "distance" method, so if you can post some code that
would be nice.

 The attached sample is more complex than the "M", but it shows my

case well: Please see module TestBody() with methods "direct" vs.
"distance".

 Best regards,
 Carsten


   

    Carsten Fuchs wrote
    Hello,

    Am 04.03.21 um 14:41 schrieb adrianv:
    > As long as you use method="smooth" and the joint parameter is

nonzero, and the $fs value isn't small enough to get involved, you will
get exactly $fn points added to the input.  If the joint parameter is
exactly zero you will get zero points added:  round_corners will not
generate duplicated points at a single vertex.  

    Does that mean if joint is non-zero, $fn points are added

even if the angle at the corner is zero?

    (e.g. evenly spaced between the corner +/- joint value?)

    > I am curious to see your example where you want to use skin()

with the "distance" method but with a rounded shape.  I didn't find this
method to be particularly suitable to such shapes, so what did I
overlook?  

    We need the "distance" method whenever the number of points

between two shapes for skin() changes and the other methods don't work
well, don't we?

    As method="distance" is O(n³) expensive, the aim of my

question was to create the rounded input shapes to have the same number
of points right from the start, so that method="direct" can be used.

    Verbal version:

    (You've kindly helped me a few weeks ago with the basis of

this, so this is in fact a follow-up question.)

    I have a shape (list of points) that (very roughly, here for

simplification) follows the capital letter "M" (five points). These are
rounded with round_corners(), then offset with function offset(),
then concatenated, so that the concatenated result follows the outline of
the letter "M".

    Now I want to morph the shape, e.g. moving the original point

in the center of the "M" upwards until it is at the same height as the
upper points left and right. When this is done, the number of points for
the morphed "M" that result from the rounding are less than the number of
points of the original "M".

    Now I would like to use skin() with the original "M" and the

morphed "M".

    I was not able to get good results with method="direct",

whereas method="distance" looked good. However, it is also very
expensive.

    So my intention was to create the original "M" and the morphed

"M" with the same number of points right from the start so that I can use
method="direct"?

    Sorry if this description is difficult to follow. I can make

example code if it helps to clarify?

    Best regards,
    Carsten


OpenSCAD mailing list
To unsubscribe send an email to

discuss-leave@.openscad

There is a large comment block entitled "Minimum Distance Mapping using Dynamic Programming" that explains the algorithm. I think reading this will be much more informative than trying to read the code, even for someone expert in OpenSCAD. But knowing how the algorithm works isn't very important. You just need to know what it does: it inserts edges so as to minimize the total sum of edge length. You suggest to "factor the point alignment methods out". This API change would make the function harder to use without providing any benefit. You are not required to use the fancy methods. You can always use simply method="direct" and ensure that you supply profiles with matching point count. Consider this paragraph from the documentation of skin(): For this operation to be well-defined, the profiles must all have the same vertex count and we must assume that profiles are aligned so that vertex `i` links to vertex `i` on all polygons. Many interesting cases do not comply with this restriction. Two basic methods can handle these cases: either add points to edges (resample) so that the profiles are compatible, or repeat vertices. Repeating vertices allows two edges to terminate at the same point, creating triangular faces. You can adjust non-matching profiles yourself either by resampling them using `subdivide_path` or by duplicating vertices using `repeat_entries`. It is OK to pass a profile that has the same vertex repeated, such as a square with 5 points (two of which are identical), so that it can match up to a pentagon. Such a combination would create a triangular face at the location of the duplicated vertex. Alternatively, `skin` provides methods (described below) for matching up incompatible paths. It appears that the text above isn't clear, because you appear confused about the matter described in this paragraph, namely that you can choose to construct your own alignment by resampling or repeating vertices. If you can indicate how the documentation could be improved that would be great. To reiterate, for skin to work the profiles MUST all have the same number of points. If you have two shapes that fail this requirement then you (or skin) MUST insert extra points somehow, either by repeating points (which will result in triangular faces in the skinned shape) or by interpolating (which results in quadrilateral faces). If the inputs are incompatible, skin() by default resamples the smaller one to match the larger. The "distance" method, on the other hand, repeats points in the shorter input to produce a matching length. If you don't want this to happen, don't invoke skin with incompatible inputs. If you want to devise your own fancy (or simple) algorithm for making the alignment you can do so yourself outside of skin. There is nothing standing in the way of this. All the existing methods do is take polygons as input and produce new polygons as output that have matching point count. You can do whatever complicated calculation you need to insert extra points so that the shapes have the same length. Or do like I've been suggesting and carefully insert extra points to create a "hand-crafted" alignment for your particular shape. Then call skin and it will simply link the shapes you provide without any extra points inserted. The run time of the "distance" method is determined by the size of the input. It is O(N^2) if you assume an initial alignment or O(N^3) if you have to check every possible alignment. When you use the "refine" option with "distance" the refinement takes place *after* the distance alignment. It is necessary to give a good rendering for curved "quadrilaterals" but does not affect how the shapes are aligned, nor the computation time to align the shapes. Your shape, however, seems to benefit from insertion of additional points on the flat regions. I experimented with your shape and it seems quite difficult to get a truly beautiful result with the automated methods. I added "fast_distance" but I think it is necessary to resample the points along some of the flat sections to create more options for edges that must align with the curve, and even so there is still a behavior occurring that I don't like, though it's a bit subtle. I tried using resample_path() and it produces a result nice in some ways and bad in others. Using $fs for all the arcs gave a result that appears good on half but not so good on the other half. To get the best result I think you need to subdivide the curves manually, as I think you are now trying to do. In this way you can choose how the different parts of the curve will align to produce the most pleasing result. Some of the flat parts need to be subsampled and aligned with curved parts. Carsten Fuchs wrote >> This situation raises the question I previously mentioned about supplying >> a "fast_distance" method that has O(N^2) run time.  The current method >> must assume a starting alignment, so if you have polygons p and q it >> tries aligning p[0] with q[0], then p[1] with q[0], then p[2] with q[0] >> and so on.   Once you pick a starting point, the algorithm that decides >> where to insert the edges runs in quadratic time.  So the "fast_distance" >> method would assume that p[0] aligns with q[0].   For your problem this >> seems to be obviously desired.   Do you think this "fast_distance" method >> is worth adding?  (And is there a better name for it?)   > > To be honest, I don't understand the algorithm enough to properly answer > this question. I also looked at the code, but I don't understand the > OpenSCAD language enough to follow. (It would be easier if this was C++, > Python or Lua.) > > The skin() function is very, very powerful. Maybe it is worth a > consideration to factor the point alignment methods out of it? This would > help with adding other methods, which could be of arbitrary complexity and > could also be implemented by users: writing a custom alignment method > plug-in would be much closer to my current OpenSCAD language skills, > making own attempts much easier. > > Also, I was wondering if a variant of method "distance" could work without > segmentation, i.e. without adding points to any of the polygons. At least > in my case, it seems not needed and if I understand the algorithm > correctly, it would reduce the complexity to O(n²). > > Best regards, > Carsten > > >> >> Carsten Fuchs wrote >> Thanks for your reply! >> >> Am 05.03.21 um 14:51 schrieb adrianv: >> > Yes, if the angle is zero (by which I assume you mean collinear >> points) then you will get evenly spaced points between the joint points. >> >> Yes, sorry, colinear is the proper word. >> >> >  (Note: you can test this yourself by running an example.) >> >> Yes, but I was wondering if this a part of the stable API, as it >> seems a case where optimization might remove these extra points in the >> future. >> >> > With regards to skin, I honestly don't know whether I covered all >> the cases with the provided methods.    If you are finding that you >> really need "distance" with an example that has a large number of points >> then one possible solution might be to supply an O(n^2) version of the >> distance method that assumes that point 0 aligns to point 0.  (The >> current code runs an alignment calculation one time for every possible >> shift of the two profiles, which adds an extra O(n) to the run time.)   >> >> I'm not sure if I understood you correctly, but between two shapes, >> if one pair of points can be assumed to match (e.g. the first pair at >> indices 0, or a pair found by picking index 0 in the first shape and >> finding the smallest distance among all points in the second shape), >> maybe it would be possible to traverse from there: >> >> For example, if we are at index 0 in the first shape and index 27 was >> the closest point in the second shape, iterate to index 1 in the first >> shape, then examine indices 27 and 28 in the second shape, picking the >> one that is closest? (I've not fully thought this through, this method is >> probably not good for generalization.) >> >> > Another idea would be to run the part of the model that changes >> (the "point" of the M) separately from the rest of the model and then >> union them.   >> > >> > In any case, I am curious to see the example as you had it >> configured with the "distance" method, so if you can post some code that >> would be nice. >> >> The attached sample is more complex than the "M", but it shows my >> case well: Please see module TestBody() with methods "direct" vs. >> "distance". >> >> Best regards, >> Carsten >> >> >>    >> > >> >     Carsten Fuchs wrote >> >     Hello, >> > >> >     Am 04.03.21 um 14:41 schrieb adrianv: >> >     > As long as you use method="smooth" and the joint parameter is >> nonzero, and the $fs value isn't small enough to get involved, you will >> get exactly $fn points added to the input.  If the joint parameter is >> exactly zero you will get zero points added:  round_corners will not >> generate duplicated points at a single vertex.   >> > >> >     Does that mean if `joint` is non-zero, $fn points are added >> even if the angle at the corner is zero? >> >     (e.g. evenly spaced between the corner +/- joint value?) >> > >> >     > I am curious to see your example where you want to use skin() >> with the "distance" method but with a rounded shape.  I didn't find this >> method to be particularly suitable to such shapes, so what did I >> overlook?   >> > >> >     We need the "distance" method whenever the number of points >> between two shapes for `skin()` changes and the other methods don't work >> well, don't we? >> > >> >     As `method="distance"` is O(n³) expensive, the aim of my >> question was to create the rounded input shapes to have the same number >> of points right from the start, so that `method="direct"` can be used. >> > >> > >> >     Verbal version: >> > >> >     (You've kindly helped me a few weeks ago with the basis of >> this, so this is in fact a follow-up question.) >> > >> >     I have a shape (list of points) that (very roughly, here for >> simplification) follows the capital letter "M" (five points). These are >> rounded with `round_corners()`, then offset with function `offset()`, >> then concatenated, so that the concatenated result follows the outline of >> the letter "M". >> > >> >     Now I want to morph the shape, e.g. moving the original point >> in the center of the "M" upwards until it is at the same height as the >> upper points left and right. When this is done, the number of points for >> the morphed "M" that result from the rounding are less than the number of >> points of the original "M". >> > >> >     Now I would like to use `skin()` with the original "M" and the >> morphed "M". >> >     I was not able to get good results with `method="direct"`, >> whereas `method="distance"` looked good. However, it is also very >> expensive. >> > >> >     So my intention was to create the original "M" and the morphed >> "M" with the same number of points right from the start so that I can use >> `method="direct"`? >> > >> > >> >     Sorry if this description is difficult to follow. I can make >> example code if it helps to clarify? >> > >> >     Best regards, >> >     Carsten >> > >> > > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to > discuss-leave@.openscad -- Sent from: http://forum.openscad.org/
A
adrianv
Sat, Mar 13, 2021 3:55 AM

I get 90 points no matter the value of flapsbump_rel_height.  And $fs works
as expected.  Perhaps you have an old version?  I fixed some issues with
round_corners sampling a month or two ago.  Try upgrading to the latest
BOSL2 version.  Also it now supports the "fast_distance" method for skin(),
if you want to experiment with that.

Carsten Fuchs wrote

Hello,

Am 04.03.21 um 14:41 schrieb adrianv:

As long as you use method="smooth" and the joint parameter is nonzero,
and the $fs value isn't small enough to get involved, you will get
exactly $fn points added to the input.  If the joint parameter is exactly
zero you will get zero points added:  round_corners will not generate
duplicated points at a single vertex.  

Unfortunately, it doesn't seem to work. Could you please consider the
example code below? If flapsbump_rel_height is set to 0, no points are
added to the polygon. If set to 0.001, some points are added, but fewer
than $fn. I thought that I had forgotten to set $fs, but it doesn't seem
to have any effect?

Best regards,
Carsten

include <BOSL2/std.scad>
include <BOSL2/rounding.scad>

function berechneQsKombi(breite, wandstaerke, orig=[0, 0], quality=1.0) =
let(
hb = breite / 2,
hb_tunnel = 70 + wandstaerke,
h_leiste = 6,
b_leiste = 6,
tilt = 0.75,
h_senke = -3,
b_senke = 4,
h_seiten = -64,
h_senkrecht = -36,
flapsbump_rel_height=10.0,  // set to 0 to see the problem

     basis_polygon = [
         [0, 0],
         [15, 0],
         [20, flapsbump_rel_height],
         [hb - b_leiste - tilt - b_senke - 1, flapsbump_rel_height],
         [hb - b_leiste - tilt - b_senke, h_senke +

flapsbump_rel_height],
[hb - b_leiste - tilt, h_senke + flapsbump_rel_height],
[hb - b_leiste, h_leiste + flapsbump_rel_height],
[hb, h_leiste + flapsbump_rel_height],
[max(hb_tunnel, hb), h_seiten],
[max(hb_tunnel, hb), h_seiten + h_senkrecht],
],

     joints = [
         0, 2.0, 2.5, 1.5, 1.0, 1.5, 2.75, 2.75, 8, 0
     ],

     punkte_aussen = round_corners(
         [for (p=basis_polygon) [p.x + orig.x, p.y + orig.y]],
         method="smooth",
         joint=joints,
         k=0.8,
      // $fs=0.02,   // seems to have no effect, even at 0.0
         $fn=10*quality
     ),

     punkte_innen = offset(punkte_aussen, delta=-wandstaerke)
 )
 concat(punkte_aussen, reverse(punkte_innen));

color("#0088CC")
translate([0, -1, 80])
rotate([90, 0, 0])
linear_extrude(10)
polygon(berechneQsKombi(124, 1.5, quality=1.0));


OpenSCAD mailing list
To unsubscribe send an email to

discuss-leave@.openscad

I get 90 points no matter the value of flapsbump_rel_height. And $fs works as expected. Perhaps you have an old version? I fixed some issues with round_corners sampling a month or two ago. Try upgrading to the latest BOSL2 version. Also it now supports the "fast_distance" method for skin(), if you want to experiment with that. Carsten Fuchs wrote > Hello, > > Am 04.03.21 um 14:41 schrieb adrianv: >> As long as you use method="smooth" and the joint parameter is nonzero, >> and the $fs value isn't small enough to get involved, you will get >> exactly $fn points added to the input.  If the joint parameter is exactly >> zero you will get zero points added:  round_corners will not generate >> duplicated points at a single vertex.   > > Unfortunately, it doesn't seem to work. Could you please consider the > example code below? If `flapsbump_rel_height` is set to 0, no points are > added to the polygon. If set to 0.001, some points are added, but fewer > than $fn. I thought that I had forgotten to set $fs, but it doesn't seem > to have any effect? > > Best regards, > Carsten > > > > > include &lt;BOSL2/std.scad&gt; > include &lt;BOSL2/rounding.scad&gt; > > > function berechneQsKombi(breite, wandstaerke, orig=[0, 0], quality=1.0) = > let( > hb = breite / 2, > hb_tunnel = 70 + wandstaerke, > h_leiste = 6, > b_leiste = 6, > tilt = 0.75, > h_senke = -3, > b_senke = 4, > h_seiten = -64, > h_senkrecht = -36, > flapsbump_rel_height=10.0, // set to 0 to see the problem > > basis_polygon = [ > [0, 0], > [15, 0], > [20, flapsbump_rel_height], > [hb - b_leiste - tilt - b_senke - 1, flapsbump_rel_height], > [hb - b_leiste - tilt - b_senke, h_senke + > flapsbump_rel_height], > [hb - b_leiste - tilt, h_senke + flapsbump_rel_height], > [hb - b_leiste, h_leiste + flapsbump_rel_height], > [hb, h_leiste + flapsbump_rel_height], > [max(hb_tunnel, hb), h_seiten], > [max(hb_tunnel, hb), h_seiten + h_senkrecht], > ], > > joints = [ > 0, 2.0, 2.5, 1.5, 1.0, 1.5, 2.75, 2.75, 8, 0 > ], > > punkte_aussen = round_corners( > [for (p=basis_polygon) [p.x + orig.x, p.y + orig.y]], > method="smooth", > joint=joints, > k=0.8, > // $fs=0.02, // seems to have no effect, even at 0.0 > $fn=10*quality > ), > > punkte_innen = offset(punkte_aussen, delta=-wandstaerke) > ) > concat(punkte_aussen, reverse(punkte_innen)); > > > color("#0088CC") > translate([0, -1, 80]) > rotate([90, 0, 0]) > linear_extrude(10) > polygon(berechneQsKombi(124, 1.5, quality=1.0)); > _______________________________________________ > OpenSCAD mailing list > To unsubscribe send an email to > discuss-leave@.openscad -- Sent from: http://forum.openscad.org/