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

.loft() randomly flips wires from which it is made #520

Closed
Andy-Valentine opened this issue Nov 25, 2020 · 3 comments
Closed

.loft() randomly flips wires from which it is made #520

Andy-Valentine opened this issue Nov 25, 2020 · 3 comments
Labels
question Further information is requested

Comments

@Andy-Valentine
Copy link

Andy-Valentine commented Nov 25, 2020

I am trying to make a complex object with varying cross-sections by lofting through a series of wires.

Here is what I noticed: sometimes .loft() inverts the orientation of the wire creating a self-intersection, which causes subsequent .union() operations to fail miserably.

Below is a very simplified version of what I am doing:

import cadquery as cq

#tuple contains sets of wires locations and angles: 
#(x_offset,y_offset,z_offest),(x_angle,y_angle,z_angle),(x_angle,y_angle,z_angle)

body_tuple=((0,0,0),(0,0,0),(0,0,0),
            (0,0,20),(0,0,0),(0,22.5,0),
            (0,0,30),(0,22.5,0),(0,22.5,0),
            (0,0,40),(0,22.5,0),(0,22.5,0),
            (0,0,45),(0,22.5,0),(0,0,0)
            )

#iteratively making a set of wires with rectangular cross sections

body_wires = cq.Workplane("XZ")
for i in range(0,int(len(body_tuple)/3)):
    body_wires= body_wires.workplane().transformed(rotate=cq.Vector(body_tuple[1+3*i])).transformed(offset=cq.Vector(body_tuple[3*i]),rotate=cq.Vector(body_tuple[2+3*i])).rect(5,5)

#lofting the set of wires

body=body_wires.loft(filled=True,ruled=True,combine=True)

show_object(body, options=dict(alpha=0.5,color='orange'))

this code works fine:
ok

BUT, when I change one offset value for the last section from 45 to 95 to make it longer:

body_tuple=((0,0,0),(0,0,0),(0,0,0),
            (0,0,20),(0,0,0),(0,22.5,0),
            (0,0,30),(0,22.5,0),(0,22.5,0),
            (0,0,40),(0,22.5,0),(0,22.5,0),
            (0,0,95),(0,22.5,0),(0,0,0)
            )

then .loft() suddenly flips the last wire and creates a self-intersection:
Capture

I made quite a lot of complex objects using this approach, including polylines and multilevel loops. This issue seems to randomly appear and disappear. Sometimes simply changing an offset or an angle by 0.1 removes the problem, sometimes it persists. It is not related to absolute or relative coordinates of the part (i.e. crossing from positive to negative values on an axis). Also rotating the "glitchy" wire by +/-180 degrees doesn't change anything. However, when rotating it by some other angle like 140 the glitch disappears.

Any Ideas why this happens and how to fix this? To me it seems like there is either:

a) some reference point bug in the .loft() function, i.e. it picks the wrong clockwise/counterclockwise direction for the next wire
b) some relative rotation glitch in the workplane.transformed(), which flips the wire before loft. The fact that rotating the last wire by 180 degrees doesn't solve this issue makes it unlikely, though.

c) or perhaps it is related to Union glitch described in #519 and #500 ?

@adam-urbanczyk
Copy link
Member

adam-urbanczyk commented Nov 26, 2020

Looks like you pushed the loft algo (BRepOffsetAPI_ThruSections) beyond it's limits. I'm not sure how it is implemented on the OCCT side, but I thought that the main use case is constructing solids passing through sections that are (roughly) parallel and not too far apart.

What you are trying to achieve can be better done using sweep:

path = cq.Workplane().polyline(pts)
body = cq.Workplane("XZ").rect(5,5).sweep(path,transition='round')

image

NB: full code starting with your inputs below, it could much much shorter if the pts were defined in absolute terms and not incrementally:

import cadquery as cq

#tuple contains sets of wires locations and angles: 
#(x_offset,y_offset,z_offest),(x_angle,y_angle,z_angle),(x_angle,y_angle,z_angle)

body_tuple=((0,0,0),(0,0,0),(0,0,0),
            (0,0,20),(0,0,0),(0,22.5,0),
            (0,0,30),(0,22.5,0),(0,22.5,0),
            (0,0,40),(0,22.5,0),(0,22.5,0),
            (0,0,45),(0,22.5,0),(0,0,0)
            )

body_tuple=((0,0,0),(0,0,0),(0,0,0),
            (0,0,20),(0,0,0),(0,22.5,0),
            (0,0,30),(0,22.5,0),(0,22.5,0),
            (0,0,40),(0,22.5,0),(0,22.5,0),
            (0,0,95),(0,22.5,0),(0,0,0)
            )
#iteratively making a set of wires with rectangular cross sections


body_wires = cq.Workplane("XZ")
pts = []
for i in range(0,int(len(body_tuple)/3)):
    body_wires= body_wires.workplane().transformed(rotate=cq.Vector(body_tuple[1+3*i])).transformed(offset=cq.Vector(body_tuple[3*i]),rotate=cq.Vector(body_tuple[2+3*i]))
    pts.append(body_wires.plane.origin)

path = cq.Workplane().polyline(pts)
body = cq.Workplane("XZ").rect(5,5).sweep(path,transition='round')

@Andy-Valentine
Copy link
Author

Andy-Valentine commented Nov 26, 2020

Thanks Adam.

Regarding the code itself, this was the very first proof-of-concept version and now it is much leaner and similar to what you suggested. Yes, sweep seems like the more plausible solution for this sample case. However, I am actually working with varying cross-sections along the path, meaning I would need to use .sweep(path, multisection=True). It has two problems:

a) when using more than 2 wires it doesn't join segments linearly, but rather with splines (In the API I didn't find the parameter similar to .loft(ruled=True) for sweep() in multisection mode, maybe I missed something?)
b) it needs an additional path tuple, besides the cross-sections, so the code gets bulkier compared to .loft()

Basically it would look something like this:

pts=[(0,-1),(0,-6),(0,-11)]
path = cq.Workplane("XY").polyline(pts)
body=cq.Workplane("XZ").workplane(offset=1).rect(5,20)
body=body.workplane(offset=5).rect(5,10)
body=body.workplane(offset=5).rect(3,10)
thingy=body.sweep(path,multisection=True)

Capture

while I need to make straight sections and .loft(ruled=True) would be perfect if not this randomly occurring glitch.

In the meanwhile I found a workaround for this using multisweep, but it's kinda ugly. Basically I create multiple short sweeps from 2 wires only and then glue them:

pts=[(0,-1),(0,-6),(0,-11)]
sections=[(5,20),(5,10),(3,10)]
thingy=cq.Workplane()
for i in range(0,int(len(pts))-1):
    path = cq.Workplane().polyline((pts[i],pts[i+1]))
    body=cq.Workplane("XZ").workplane(offset=-1*pts[i][1]).rect(sections[i][0],sections[i][1])
    body=body.workplane(offset=-1*(pts[i+1][1]-pts[i][1])).rect(sections[i+1][0],sections[i+1][1])
    thingy=thingy.union(body.sweep(path, multisection=True), glue=True)

2

Now it does what I need without glitches reported in my previous post, but at the price of bulkier code and additional glueing operations. And yes, when using 2 wires per sweep I would still need to specify sections rotation angles manually, like I did in .loft() in the first post.

P.S. Also, I noticed something strange with workplanes in this mode: I needed to add (-1) multiplier to .workplane(offset=-1*x) because the path in its workplane was going in the opposite direction to workplanes of cross-sections for some reason, as if the X-Z workplane was flipped 180 degress.

@adam-urbanczyk adam-urbanczyk added the question Further information is requested label Nov 27, 2020
@adam-urbanczyk
Copy link
Member

adam-urbanczyk commented Nov 27, 2020

The only solutions I see is to use pairwise multisection sweep or add additional helper profiles to loft.

You can specify one polyline and generate locations along it using locationAt(), no need to specify angles and distances incrementally.

Regarding your PS, you are specifying the offset in the local coordinate system and a XZ plane has a different normal than a ZX plane.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants