Trouble combining objects

694 views
Skip to first unread message

Carlos Ribeiro

unread,
Jul 1, 2021, 12:43:18 AM7/1/21
to CadQuery
Hi,

My name is Carlos Ribeiro, I'm from Brazil, that's my first post here. I'm a semi-pro maker from Brazil. I made my career on IT & telecommunications but started a makerspace business five years ago to explore digital manufacturing & have been learning a lot since then.

 I've been hearing about CadQuery for some time now. I was writing a project in OpenSCAD and having severe problems with geometry, so I decided to give CadQuery a try. I used to develop simple webapps using Python years ago for a living, and I still write some code from time to time. It's nice to be able to write code in a proper programming language with all the constructs.

That said, the transition from OpenSCAD to CadQuery is sometimes a bit harder than I expected. My main problem now - and that's something that's becoming a showstopper - is that I'm still having sobe trouble when combining objects that lead to strange geometry artifacts.

I'm designing a space helmet for a customer. I have to 3D print a few hundred pieces and both speed and quality are a must. I prototyped the helmet using Tinkercad, and it worked fine, but as it's not paramertic, any small changes require a lot of manual steps.

The object was designed to print without supports (in fact the supports are included into the structure of the piece). It's a thin shell with a inner frame that makes it strong yet light.

My main problem is that when I add the components of the structure to the helmet, the geometry is not being properly combined. I'm sure I'm missing something here in the way I'm adding the parts.

Helmet - Under - Solid.png

This is a view from the bottom that shows the internal structure. The problem is that when I add the frame (using the add() method) the resulting solid has some duplicated geometry, that causes problem when slicing for 3D printing (namely, there's some overextrusion as there are two overlapping objects.

Helmet - Top - Slicer.png

This is a view from the slicer at the top of the part. There are a few overlapping parts inside the part where there should be only the infill. There are also other similar artifacts on other parts of the solid.

The code that I'm using is written like this (shortened just as an example):

def helmet():
    # starts with the sphere
    c = cq.Workplane("XY").sphere(radius=R)

    # inner sphere is a custom function that creates a slightly
    # squished sphere to make the shell thicker at the top, 
    # which helps printing
    c = c.cut(make_inner_sphere_v2(R))

    # cuts the bottom; I think I should have used the slice method
    c = c.cut( 
            cq.Workplane("XY",origin=(0,0,-(R-H/2))).
            box(R*2, R*2, H)
        )

    # adds the frame elements
    for i in ANGLES:
        c = c.add(frame_element(i))

I'm sure I'm missing something. I'm using add & cut a lot. One reason is that I'm porting my OpenSCAD code which used CSG; but also, as the project relies extensively on spheres, it's not trivial to construct geometry because there are no flat faces to work with.

Any ideas?

Carlos Ribeiro

unread,
Jul 1, 2021, 1:06:42 AM7/1/21
to CadQuery
Just a piece of additional information: the extra geometry is cleared by Netfabb (I believe other tools for fixing STL may also work).

Adam Urbanczyk

unread,
Jul 1, 2021, 5:22:02 AM7/1/21
to CadQuery
You probably want to use union and not add. Add is not a bool op - it results in a model with multiple solids.

Carlos Ribeiro

unread,
Jul 1, 2021, 8:53:40 AM7/1/21
to Adam Urbanczyk, CadQuery
I've tried do use union() but there were still some other artifacts.
Is that the correct way to do things? I'll give it another try today.

Carlos Ribeiro
Mentoria & Consultoria em Projetos de Inovação



--
cadquery home: https://github.com/CadQuery/cadquery
post issues at https://github.com/CadQuery/cadquery/issues
run it at home at : https://github.com/CadQuery/CQ-editor
---
You received this message because you are subscribed to a topic in the Google Groups "CadQuery" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/cadquery/sMJX-VoOphQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to cadquery+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/cadquery/1c92f4f4-e895-46b8-b196-3d131f7ac2dbn%40googlegroups.com.

Jeremy Wright

unread,
Jul 1, 2021, 9:19:14 AM7/1/21
to CadQuery
This pull request might be of interest to you. Extruding to the next/last face, even when that face is not planar, seems like it would be useful here. https://github.com/CadQuery/cadquery/pull/782

Carlos Ribeiro

unread,
Jul 1, 2021, 9:45:00 AM7/1/21
to Jeremy Wright, CadQuery
Thanks for the tip. I'm using the prebuilt binary for Windows, I'm not sure if it's up to date. I believe I may need to install the latest build, is there a daily binary build that I can use? I can probably try to run in on MacOS also (it may be easier due to the Unix system behind it); things for Windows tend to be more cumbersome to install via scripts.

Carlos Ribeiro
Mentoria & Consultoria em Projetos de Inovação


Jeremy Wright

unread,
Jul 1, 2021, 10:12:02 AM7/1/21
to CadQuery
If you're logged into GitHub, there are weekly builds available here: https://github.com/jmwright/CQ-editor/actions/workflows/pyinstaller-builds-actions.yml

Just click on the latest build with the green checkmark, and scroll down to the Artifacts section. However, the PR I posted the link to hasn't been merged yet, so that functionality won't be in the prebuilt package yet.

Adam Urbanczyk

unread,
Jul 1, 2021, 6:22:52 PM7/1/21
to CadQuery
Yes union is a CSG op, add is not. You'll have to share a reproducible example if you want to get some help.

Carlos Ribeiro

unread,
Jul 1, 2021, 10:52:41 PM7/1/21
to Adam Urbanczyk, CadQuery
Sorry, I've had a tough day :-( But here's what I got, its a simpler case, another part of the same design which shows similar problems.

def visor():
    v = cq.Workplane("XY").sphere(radius=HELMET_RADIUS+LINE_WIDTH)
    v = v.cut(cq.Workplane("XY").sphere(radius=HELMET_RADIUS-LINE_WIDTH))
    v = v.workplane().split(keepTop=True)
    v = v.workplane().transformed(offset=(0,0,-HELMET_RADIUS/2), rotate=(VISOR_ANGLE,0,0)).split(keepBottom=True)
    v = v.rotate(axisStartPoint=(0,0,0), axisEndPoint=(1,0,0), angleDegrees=VISOR_ANGLE_OFFSET)
    v = v.cut(cq.Workplane("YZ").circle(radius=EAR_RADIUS).extrude(HELMET,both=True))
    return v

def torus(outerRadius, innerRadius, z=0):
    r = (outerRadius-innerRadius)/2;
    t = cq.Workplane("XZ", origin=(0,0,-z)).moveTo(innerRadius+r).circle(r).revolve()
    return t

def visor_torus(z):
    return torus(HELMET_RADIUS+VISOR_BORDER_RADIUS,HELMET_RADIUS-VISOR_BORDER_RADIUS,z);

def visor_frame():
    v1 = visor_torus(VISOR_BORDER_RADIUS)
    v2 = (visor_torus(-VISOR_BORDER_RADIUS)
         .rotate(axisStartPoint=(0,0,0), axisEndPoint=(1,0,0), angleDegrees=VISOR_ANGLE)
         )
    v = v1.add(v2).rotate(axisStartPoint=(0,0,0), axisEndPoint=(1,0,0), angleDegrees=VISOR_ANGLE_OFFSET)
    return v.copyWorkplane(cq.Workplane("XZ", origin=(0,0,0))).split(keepBottom=True)

def helmet():
    h = cq.Workplane("XY").sphere(radius=HELMET_RADIUS)
    h = h.add(visor_frame())
    h = h.cut(visor())
    return h

h = helmet()


This code uses add() and produces the following geometry
:
image.png

Now, if I use union() in the following lines, the geometry simply does not work and produces an 'empty' solid without any warning.

def helmet():
    h = cq.Workplane("XY").sphere(radius=HELMET_RADIUS)
    h = h.union(visor_frame())
    h = h.cut(visor())
    return h

image.png

The visor() method alone generates this part, which is subtracted from the sphere:

image.png

The visor_frame() method generates two toruses which are rotated and translated along the normal axis so they end up exactly above and below the visor part that is subtracted.

image.png

There are other problems; if I subtract the inner sphere (commented in the code above but activated in the code below), and cut the bottom part, it kind of works with add() but generates strange geometry inside (with a plane that should not exist at all).

def helmet():
    h = cq.Workplane("XY").sphere(radius=HELMET_RADIUS)
    h = h.union(visor_frame())
    h = h.cut(visor())

    # cuts a slightly squished sphere to make the top part a bit thicker
    h = h.cut(make_inner_sphere(HELMET_RADIUS))

    # cuts down the bottom part; could be done with slice or other similar method.
    h = h.cut(
            cq.Workplane("XY",origin=(0,0,-(HELMET_RADIUS-HELMET_FLOOR/2))).
            box(HELMET, HELMET, HELMET_FLOOR)
        )
    return h


image.png

These strange artifacts get increasingly cumbersome as I try to add more details inside the geometry. I managed to get to a point where I can add a lot more detail and correct lots of problems with Netfabb, but still, I can't add the visor_frame()  in any other way. It just breaks and there's no way that even Netfabb can correct it.


Carlos Ribeiro
Mentoria & Consultoria em Projetos de Inovação


Carlos Ribeiro

unread,
Jul 1, 2021, 10:54:45 PM7/1/21
to Adam Urbanczyk, CadQuery
Just a correction, the code below with union() does not work at all. It generates the strange geometry with add().

def helmet():
    h = cq.Workplane("XY").sphere(radius=HELMET_RADIUS)
    h = h.add(visor_frame())  # it doesn't work with union()

    h = h.cut(visor())

    # cuts a slightly squished sphere to make the top part a bit thicker
    h = h.cut(make_inner_sphere(HELMET_RADIUS))

    # cuts down the bottom part; could be done with slice or other similar method.
    h = h.cut(
            cq.Workplane("XY",origin=(0,0,-(HELMET_RADIUS-HELMET_FLOOR/2))).
            box(HELMET, HELMET, HELMET_FLOOR)
        )
    return h
Carlos Ribeiro
Mentoria & Consultoria em Projetos de Inovação


Carlos Ribeiro

unread,
Jul 2, 2021, 12:35:34 AM7/2/21
to CadQuery
I just noticed that I didn't post the parameters. Here's the full code.

# 3D print parameters
LINE_WIDTH = 0.5
LAYER = 0.2
MIN_OFFSET = LINE_WIDTH

# model parameters
HELMET = 80
HELMET_RADIUS = HELMET / 2
HELMET_WALL = 2 * LINE_WIDTH
HELMET_FLOOR = 20

EAR_RADIUS = 20

VISOR_BORDER_RADIUS = 1
VISOR_THICKNESS = 0.8
VISOR_ANGLE = 60;
VISOR_ANGLE_OFFSET = -15

def visor():
    v = cq.Workplane("XY").sphere(radius=HELMET_RADIUS+LINE_WIDTH)
    v = v.cut(cq.Workplane("XY").sphere(radius=HELMET_RADIUS-LINE_WIDTH))
    v = v.workplane().split(keepTop=True)
    v = v.workplane().transformed(offset=(0,0,-HELMET_RADIUS/2), rotate=(VISOR_ANGLE,0,0)).split(keepBottom=True)
    v = v.rotate(axisStartPoint=(0,0,0), axisEndPoint=(1,0,0), angleDegrees=VISOR_ANGLE_OFFSET)
    v = v.cut(cq.Workplane("YZ").circle(radius=EAR_RADIUS).extrude(HELMET,both=True))
    return v

def torus(outerRadius, innerRadius, z=0):
    r = (outerRadius-innerRadius)/2;
    t = cq.Workplane("XZ", origin=(0,0,-z)).moveTo(innerRadius+r).circle(r).revolve()
    return t

def visor_torus(z):
    return torus(HELMET_RADIUS+VISOR_BORDER_RADIUS,HELMET_RADIUS-VISOR_BORDER_RADIUS,z);

def visor_frame():
    v1 = visor_torus(VISOR_BORDER_RADIUS)
    v2 = (visor_torus(-VISOR_BORDER_RADIUS)
         .rotate(axisStartPoint=(0,0,0), axisEndPoint=(1,0,0), angleDegrees=VISOR_ANGLE)
         )
    v = v1.union(v2).rotate(axisStartPoint=(0,0,0), axisEndPoint=(1,0,0), angleDegrees=VISOR_ANGLE_OFFSET)
    return v.copyWorkplane(cq.Workplane("XZ", origin=(0,0,0))).split(keepBottom=True)

def helmet():
    h = cq.Workplane("XY").sphere(radius=HELMET_RADIUS)
    h = h.add(visor_frame())
    h = h.cut(visor())

h = helmet()

Adam Urbanczyk

unread,
Jul 2, 2021, 5:10:38 AM7/2/21
to CadQuery

As discussed on discord - it works on master. You could try with .union(...,clean=False) or just update to the latest version.
Reply all
Reply to author
Forward
0 new messages