n00b questions/suggestions: "perpendicular" edges, "not" selector, and mandatory assembly name

86 views
Skip to first unread message

Ami Fischman

unread,
Feb 13, 2021, 4:21:30 PM2/13/21
to cadq...@googlegroups.com
I've used openjscad for a few years and am excited to find cadquery this past week for its incremental build approach and fantastic docs!

In using CQ for a couple of starter projects, I ran into some issues, listed below. None are blocking me now but each took some time to work around, so I figured I'd share. LMK if more details would be useful or if I've misunderstood anything.
1) "#" is documented to select edges "Perpendicular", agreeing with the name of PerpendicularDirSelector, but the implementation is "not parallel", not "is at 90-degrees to" which is what I expected from the naming. Bug or feature?
2) the example for "not" makes it read like a function call, implying e.g. "not(|Z) and |Z" should return zero edges, but actually it's a unary operator so that string is equivalent to "not |Z". IMO would be clearer to add a space, and maybe an example with no parens to show they're not part of "not" itself.
2') The same misunderstanding led me to frequent ParseErrors for e.g. "#X and not(|Z)" because it needs to be "#X and (not |Z)". The ParseErrors are pretty inscrutable to a newcomer.
3) The assembly tutorial omits to mention the need to name assemblies and then include the assembly name as a prefix with a slash before each part's name, without which KeyErrors are raised.
Concrete examples for each of these are in https://gist.github.com/fischman/c169da4d4f30e101ed1d4e3778337375

A philosophical question:
4) While building up a complex part, I find myself wanting to restrict edge/face selection to just the last addition. Is there a way to do this without doing that last addition as a separate part?
As an example this is a desired outcome:
first = cq.Workplane('XY').box(1,1.5,1.5)
second = cq.Workplane('XY').copyWorkplane(first.faces(">X").workplane()).rect(0.5,0.5).extrude(0.5).edges("|X").fillet(0.1)
second.union(first)
but I'm wondering if it can be done in a single fluent chain like:
cq.Workplane('XY').box(1,1.5,1.5).faces(">X").rect(0.5,0.5).extrude(0.5).tag("t").edges("|X", tag="t").fillet(0.1)
which isn't quite right b/c it apparently tags *everything* that comes before it, not just the result of the last extrude() preceding it.

Cheers,
-a

Adam Urbanczyk

unread,
Feb 14, 2021, 9:11:05 AM2/14/21
to CadQuery
Thanks for the detailed feedback!

1) Looks like a functional bug, we might want to update the docs.
2) Good point
2') Might need to define a better grammar.
3) I cannot reproduce the issue. Are you sure you are on the latest CQ version? The following code works fine

b1 = cq.Workplane('XY').box(1,1.5,1.5)
b2 = cq.Workplane('XY').box(1,0.5,0.5)


a = (
cq.Assembly(name='a')
.add(b1, name="b1", color=cq.Color("red"))
.add(b2, name="b2", color=cq.Color("green"))
)

a.constrain("b2@faces@<X", "b1@faces@>X", "Plane").solve()
show_object(a)

And this does not (which is the intended behavior - you only need / to address sub-assemblies)

b1 = cq.Workplane('XY').box(1,1.5,1.5)
b2 = cq.Workplane('XY').box(1,0.5,0.5)

a = (
    cq.Assembly(name="a")
    .add(b1, name="b1", color=cq.Color("red"))
    .add(b2, name="b2", color=cq.Color("green"))
)
print(a.objects.keys())
show_object(a.constrain("a/b2@faces@<X", "a/b1@faces@>X", "Plane").solve()

4) Currently not, but AFAIR there is an issue to implement tags for operation results. I would not count on it being implement soon though. You can use
combine=False and union manually, but this will not be a single fluent call. You can also use a different approach:

r = (
     cq.Workplane()
     .box(1,1.5,1.5)
     .faces(">X").workplane()
     .rect(0.5,0.5)
     .extrude(0.5)
     .edges('|X').edges('>X')
     .fillet(0.1)
)

Tag saves a reference to the current workplane
 - it does not contain "everything" in the generic case.

Ami Fischman

unread,
Feb 14, 2021, 2:24:22 PM2/14/21
to Adam Urbanczyk, CadQuery
3) I cannot reproduce the issue. Are you sure you are on the latest CQ version?

Thanks for the hint; indeed, my cq.__version__ is '2.1dev'. Filed https://github.com/bernhard-42/jupyter-cadquery/issues/35 to upgrade.
 
4) [...]You can also use a different approach:

Here you're just suggesting that I can use extra filtering that happens to match the second object's edges and not the first's, right?  My original question arose in a more complex part where there wasn't an easy way to filter for the last object's edges as distinct from earlier edges and I ended up using a BoxSelector. I guess BoxSelector feels hacky to me because it involves explicit bounds instead of being symbolic, and maybe I just need to get over that hangup.
 
Tag saves a reference to the current workplane
 - it does not contain "everything" in the generic case.

Just to clarify, are these correct statements?
a) there's no distinction between "the current workplane" (as you use it above) and "everything in the fluent chain from cq.Workplane() call to the .tag() call"; and
b) the tag= kwarg to the edges/faces/etc functions serves to exclude elements that were created *after* the .tag() call, but there's no corresponding way to exclude elements that were created before a certain point.

Cheers,
-a
Reply all
Reply to author
Forward
0 new messages