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

how to iteratively loft wires in cadquery 2? #319

Open
lalebarde opened this issue Apr 14, 2020 · 11 comments
Open

how to iteratively loft wires in cadquery 2? #319

lalebarde opened this issue Apr 14, 2020 · 11 comments
Labels
question Further information is requested

Comments

@lalebarde
Copy link

Same post as here. I can loft a 2D shape from one plane to another like this:

import cadquery as cq
from jupyter_cadquery.cadquery import show
result = cq.Workplane("XY").rect(1,2).workplane()  \
     .transformed(offset=cq.Vector(0, -0.5, 1.0),rotate=cq.Vector(10, 0, 0)) \
     .rect(1,2).loft(combine=True)
show(result)

image
image

I would like to repeat the operation several times like this:

import cadquery as cq
from jupyter_cadquery.cadquery import show

wp = cq.Workplane("XY").rect(1,2).workplane()
result = None
for i in range(0,5):
    wp2 = wp.transformed(offset=cq.Vector(0, -0.5, 1.0),rotate=cq.Vector(10, 0, 0))
    if result == None:
        result = wp2.rect(1,2).loft(combine=True)
    else:
        nextpart = wp2.rect(1,2).loft(combine=True)
        result = result.union(nextpart)
    wp = wp2
show(result)

I have the following error:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-54-37e88c00eca8> in <module>
      9         result = wp2.rect(1,2).loft(combine=True)
     10     else:
---> 11         nextpart = wp2.rect(1,2).loft(combine=True)
     12         result = result.union(nextpart)
     13     wp = wp2

~/anaconda3/envs/cq-jl/lib/python3.7/site-packages/cadquery/cq.py in loft(self, filled, ruled, combine)
   2897         self.ctx.pendingWires = []
   2898 
-> 2899         r = Solid.makeLoft(wiresToLoft, ruled)
   2900 
   2901         if combine:

~/anaconda3/envs/cq-jl/lib/python3.7/site-packages/cadquery/occ_impl/shapes.py in makeLoft(cls, listOfWire, ruled)
   1594         # the True flag requests building a solid instead of a shell.
   1595         if len(listOfWire) < 2:
-> 1596             raise ValueError("More than one wire is required")
   1597         loft_builder = BRepOffsetAPI_ThruSections(True, ruled)
   1598 

ValueError: More than one wire is required

At least, If I suppress the for loop and keep the same variable decomposition, it works:

import cadquery as cq
from jupyter_cadquery.cadquery import show

wp = cq.Workplane("XY").rect(1,2).workplane()
wp2 = wp.transformed(offset=cq.Vector(0, -0.5, 1.0),rotate=cq.Vector(10, 0, 0))
result = wp2.rect(1,2).loft(combine=True)
wp = wp2
show(result)

I think I am close to the result, but I cannot find where I mistake myself. The problem occurs when I come in the loop the second time. I think I have accumulated already two wires in wp2. I should keep only the last one before adding a new one, but I don't know how to do it.

@lalebarde lalebarde changed the title how to iteratively loft wires in cadquery? how to iteratively loft wires in cadquery 2? Apr 14, 2020
@lalebarde
Copy link
Author

I have tried wp = cq.copyWorkplane(wp2) instead of wp = wp2, but I get the following error:

AttributeError: module 'cadquery' has no attribute 'copyWorkplane'

I have supposed it is right from the documentation here:

class cadquery.CQ(obj)
copyWorkplane(obj)[source]

Copies the workplane from obj.

Parameters

    obj (a CQ object) – an object to copy the workplane from
Returns

    a CQ object with obj’s workplane

@adam-urbanczyk
Copy link
Member

Why do you want to actually loft? There is a sweep function that can even take different sections.

@adam-urbanczyk adam-urbanczyk added the question Further information is requested label Apr 14, 2020
@lalebarde
Copy link
Author

I need control on the section orientation: yaw, roll and pitch

@lalebarde
Copy link
Author

Here is an example with sweep:

import cadquery as cq
from math import sin, cos, pi
SEGMENTS = 48

def sinusoidal_ring(rad=25, segments=SEGMENTS):
    outline = []
    for i in range(segments):
        angle = i * 2 * pi / segments
        x = rad * (cos(angle) + i/segments)
        y = rad * sin(angle)
        z = 2 * rad * (sin(angle) / 5 + i / segments)
        outline.append((x, y, z))
    return outline

def triangle(base = 15, ratio = 2):
    return [(-base/2, 0), (base/2, 0), (0, base*ratio)]

def extrude_example():
    shape = triangle() 
    path = sinusoidal_ring(rad=50)
    p= cq.Workplane("XY").spline(path,includeCurrent=True)
    s = cq.Workplane("YZ").polyline(shape).close().sweep(p)
    return s

result = extrude_example()
show_object(result)

The section starts with a triangle oriented towards the Z axes, and does not stay as this along the path.

@lalebarde
Copy link
Author

image
image

@adam-urbanczyk
Copy link
Member

Is this what you want to get?

obraz

I used raw OCC features, in the end it will be implemented in CQ in relation to #97

import cadquery as cq
from math import sin, cos, pi
SEGMENTS = 148

def sinusoidal_ring(rad=250, segments=SEGMENTS, N = 1):
    outline = []
    for i in range(segments):
        angle = N*i * 2 * pi / segments
        x = rad * (cos(angle) + i/segments)
        y = rad * sin(angle)
        z = 2 * rad * (sin(angle) / 5 + i / segments)
        outline.append((x, y, z))
    return outline

def triangle(base = 15, ratio = 2):
    return [(-base/2, 0), (base/2, 0), (0, base*ratio)]

def extrude_example():
    shape = triangle() 
    
    p= cq.Workplane("XY",origin=(0,0,30)).spline(path,includeCurrent=True)
    s = cq.Workplane("YZ").polyline(shape).close().sweep(p,isFrenet=True)
    return s,p

path = sinusoidal_ring(rad=50)

p1 = cq.Workplane("XY",origin=(0,0,30)).spline(path,includeCurrent=True)
p2 = cq.Workplane("XY",origin=(0,0,0)).spline(path,includeCurrent=True)
t = cq.Workplane("YZ").polyline(triangle()).close()

result,p = extrude_example()

import OCC

ps = OCC.Core.BRepOffsetAPI.BRepOffsetAPI_MakePipeShell(p1.wire().val().wrapped)
ps.Add(t.val().wrapped)
ps.SetMode(p2.wire().val().wrapped,True)
ps.Build()
ps.MakeSolid()

res = cq.Shape(ps.Shape())

show_object(res)
show_object(p1)
show_object(p2)

@lalebarde
Copy link
Author

Oh yes, thank you very much Adam! I cannot use OCC directly at this time, so, you have saved me. Very nice to ear from you it will be in CQ.

@lalebarde
Copy link
Author

Adam, there is something strange: I understand the spline starts at the origin because of includeCurrent=True in the spline command. If I put false, I obtain this:

image

image

@lalebarde lalebarde reopened this Apr 15, 2020
@lalebarde
Copy link
Author

I would like also to explore the different OCC options:

ps.SetMode(p2.wire().val().wrapped,True,OCC.Core.BRepOffsetAPI.BRepFill_ContactOnBorder)

raises the error:

AttributeError: module 'OCC.Core.BRepOffsetAPI' has no attribute 'BRepFill_ContactOnBorder'

@lalebarde
Copy link
Author

Adam, I have made a more realistic test and unfortunatly, what brings ps.SetMode(p2.wire().val().wrapped,True) is not what I am looking for.

import cadquery as cq
from math import sin, cos, pi
SEGMENTS = 10

def test(l=20):
    return[(0,0,0),(l,0,0),(2*l,0,l/2),(3*l,0,4*l),(4*l,l,5*l),(4*l,2*l,6*l),(3*l,4*l,6*l)]

def triangle(base = 15, ratio = 2):
    return [(-base/2, 0), (base/2, 0), (0, base*ratio)]


path = test()

p1 = cq.Workplane("XY",origin=(0,0,40)).polyline(path)
p2 = cq.Workplane("XY",origin=(0,0,0)).polyline(path)
t = cq.Workplane("YZ").polyline(triangle()).close()

import OCC

ps = OCC.Core.BRepOffsetAPI.BRepOffsetAPI_MakePipeShell(p1.wire().val().wrapped)
ps.Add(t.val().wrapped)
ps.SetMode(p2.wire().val().wrapped,True)
ps.Build()
ps.MakeSolid()

res = cq.Shape(ps.Shape())

show_object(res)
show_object(p1)
show_object(p2)

image
image

As you can see from the bottom view, the pattern don't rotate and the section of the volume becomes a line. Think of my need as a stair handrail. In my post here, my use case is (1, 1, 0) no yaw, but the section can roll when the handrail turns right or left, and can pitch when it climbs.

OCC is very hard to learn because the documentation is light and one has to test thoroughly to figure out what it can perform, or review the code if possible - and testing is hard because it lacks examples. Probably there is a way to use BRepOffsetAPI_MakePipeShell to fit ma case, but I cannot figure it out. So, maybe the best solution for me would be to going on with loft, where I can master what I want. loft enables to control precisely the location of every sections and I can calculate them. But I have failed above to perform it cf ValueError: More than one wire is required

@lalebarde
Copy link
Author

Here is the working code:

import cadquery as cq
wp = cq.Workplane("XY").rect(1,2).workplane()
result = None
for i in range(0,5):
    wp2 = wp.transformed(offset=cq.Vector(0, -0.5, 1.0),rotate=cq.Vector(10, 0, 0)).rect(1,2).workplane()
    if result == None:
        result = wp2.loft(combine=True)
    else:
        nextpart = wp2.loft(combine=True)
        result = result.union(nextpart)
    wp = wp.transformed(offset=cq.Vector(0, -0.5, 1.0),rotate=cq.Vector(10, 0, 0)).rect(1,2).workplane()
show_object(result, options=dict(alpha=0.5,color='red'))

image

The point is to push back the last wire in the CQ stack, what a simple python assigment could not do (wp = wp2). So I changed it to wp = wp.transformed(offset=cq.Vector(0, -0.5, 1.0),rotate=cq.Vector(10, 0, 0)).rect(1,2).workplane() to make work the CQ kernel.

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