Irev-Dev / curated-code-cad

A list of the various code-cad projects out there.
https://kurthutten.com/blog/curated-code-cad
MIT License
243 stars 9 forks source link

Add CadQuery example #12

Closed Irev-Dev closed 3 years ago

Irev-Dev commented 3 years ago

resolves #9

Want to see if anyone from CadQuery want to review this before I merge since this is kinda public example code for CadQuery.

See this tweet for what the model looks like https://twitter.com/IrevDev/status/1340984905337982981

Irev-Dev commented 3 years ago

Worth noting that I'm not a python dev.

Irev-Dev commented 3 years ago

Happy for you to review this @jmwright (or other), if not I'll merge it in a couple of days.

jmwright commented 3 years ago

@Irev-Dev Thanks for tagging me on this. You have to be careful with the size of your fillet relative to the size of your object. If I get a "BREP API command not done" (CAD kernel) error, I usually back the fillet down to 0.1 and go from there. Also, if you wrap your statement in parentheses you can drop the line continuation characters. It's personal preference I guess, but I think it makes the code more readable.

The bottom two edges in your diagram are indeed difficult to select. I've asked for the community to chime in on what seems to be non-intuitive behavior in the issue that's cross-referenced here now.

Irev-Dev commented 3 years ago

Thanks @jmwright, the parentheses is a good tip, I've updated it.

In regards to fillets being to big is there a way to try except applying the fillets? that would be cool, would open some possibilities, like applying the maximum fillet possible for that given geometry maybe 🤷 .

marcus7070 commented 3 years ago

There is a key difference between ">Y" and ">Y[-1]", see https://github.com/CadQuery/cadquery/issues/531 if you would like to learn more about it.

Here is a selector that does what you need it to:

import cadquery as cq

hookHeight = 50
width = 50
thickness = 2.5

result = (
    cq.Workplane("XY")
    .moveTo(-width/4,1)
    .lineTo(0,hookHeight)
    .lineTo(width/4,1)
    .lineTo(width/4+5,0)
    .lineTo(-width/4-5,0)
    .close()
    .extrude(thickness*1.5)
    .edges("(|Z except <Y)")
    .fillet(1)
)

show_object(result)

screenshot2020-12-24-104929

If I get https://github.com/CadQuery/cadquery/pull/549 merged, you will also have CenterNthSelector as an option.

marcus7070 commented 3 years ago

In regards to fillets being to big is there a way to try except applying the fillets? that would be cool, would open some possibilities, like applying the maximum fillet possible for that given geometry maybe .

First time I've actually tried this, works really well:

import cadquery as cq
import OCP

hookHeight = 50
width = 50
thickness = 2.5

result = (
    cq.Workplane("XY")
    .moveTo(-width/4,1)
    .lineTo(0,hookHeight)
    .lineTo(width/4,1)
    .lineTo(width/4+5,0)
    .lineTo(-width/4-5,0)
    .close()
    .extrude(thickness*1.5)
    .edges("(|Z except <Y)")
)

for rad in [10, 5, 4, 3, 2, 1, 0.5, 0.1]:
    try:
        filleted_result = result.fillet(rad)
        break
    except OCP.StdFail.StdFail_NotDone:
        pass
print(f"final radius: {rad}")

show_object(filleted_result)
final radius: 5

screenshot2020-12-24-105951

Irev-Dev commented 3 years ago

Thanks for the tips @marcus7070, I've made some changes.

Irev-Dev commented 3 years ago

@jmwright @marcus7070 I could see myself using @marcus7070's safe filleting technique all the time. Might it worth having .fillet and .safeFillet or maxFillet where the latter takes an array of radii and applies the maximum one?

jmwright commented 3 years ago

@Irev-Dev @marcus7070 That ranged try/except method is interesting, but is indeterminate within the range that you give it. You could end up with a fillet that's 10, or you could end up with one that's 0.1. I could see there being cases where that might be useful, but I think most of the time people want to know what size fillet they're going to end up with ahead of time. However, if it was implemented, maybe something like safeFillet(target=10, min=0.1, step_size=0.5) and/or safeFillet(range=[10, 5, 4, 3, 2, 1, 0.5, 0.1]), which would be equivalent to the code above.

I wonder if a good first step would be to modify the current fillet and chamfer operations to do the try/except check and give the user a specific "try a smaller value" error rather than letting the unhelpful "command not done" error be the only thing they see.

We're starting to get deeper into new feature discussion in a non-CadQuery repo, so maybe it's time for these two ideas to be separated out into issues on CadQuery/cadquery.

In any case I'm tagging @adam-urbanczyk to make sure this discussion isn't missed.

jmwright commented 3 years ago

As I think through the idea of changing the fillet and chamfer operations to do the try/except check I realize I didn't think it through well enough. With the way scripts are executed via CQGI I'm not sure we can make those checks work. I'll have to think about it a bit.

Irev-Dev commented 3 years ago

@jmwright I think whatever the decision is, it makes sense to have a deterministic version that insists on the radius and errors if not possible, and a version where users are happy for there to be some variation.

marcus7070 commented 3 years ago

As I think through the idea of changing the fillet and chamfer operations to do the try/except check I realize I didn't think it through well enough. With the way scripts are executed via CQGI I'm not sure we can make those checks work. I'll have to think about it a bit.

I think it could be done (not saying it should, I'm still not sure on that). Here is a similar handling of OCP exceptions that we already do: https://github.com/CadQuery/cadquery/blob/master/cadquery/occ_impl/shapes.py#L1195

adam-urbanczyk commented 3 years ago

Better error message is a good idea, but I'm skeptical regarding the try/catch in a loop.

BTW: why do you even have it in the code? This still works:

def hookBody():
    result = (
        cq.Workplane("YZ")
        .moveTo(-width/4,1)
        .lineTo(0,hookHeight)
        .lineTo(width/4,1)
        .lineTo(width/4+30,0)
        .lineTo(-width/4-30,0)
        .close()
        .extrude(thickness*1.5)
        .edges("|X except <Z").fillet(70)
        .translate((-thickness*1.5/2,0,height+thickness/2))
    )

    return result

obraz

Irev-Dev commented 3 years ago

@adam-urbanczyk it works with the current dimensions, but if somewhere to customise the dimensions, such large radii wouldn't work, this way it's not so brittle.

marcus7070 commented 3 years ago

BTW: why do you even have it in the code? This still works:

I did a lot of OpenSCAD before I used CadQuery and in my case it took me a little while to realise how huge an advantage it is that CadQuery is a Python module instead of it's own language. This gets you:

I like that the very pythonic way of try ... except error handling is in the example. It's a bit contrived, but it drives home the point that you are writing a full Python script and can do anything that Python can do.

adam-urbanczyk commented 3 years ago

Sure CQ is a Python module so you can use all the Python goodies. But I don't see such checks in the other examples. At least for opencsacade.js I can trigger an error due to a too big fillet radius. If your goal is to have roughly comparable codes, then I'd suggest to remove the try catch part.

Depending on which style you prefer, you could use polyline instead of a series of lineTo. Based on the comments regarding filleting, I sense that you are just interested in a smooth shape. You could use spline with specified tangents and avoid the discussion regarding fillet radii completely.

Irev-Dev commented 3 years ago

Sorry I've been a bit slow with this. I agree with you @adam-urbanczyk, as far as example code codes it would be better to use a spline. I'll fix that soon™️, and probably merge after that.

adam-urbanczyk commented 3 years ago

NP, here is my take:

height = 85.0
width = 120.0
thickness = 2.0
diameter = 22.0
holeDia = 28.0
hookHeight = 10.0

frame_pts = [(-width*0.95/2,0),(width*0.95/2,0),(0,height)]

hook_pts = [(-width/2.5,0), (0,hookHeight), (width/2.5,0),]
hook_tgts = [(1,0),(1,0)]

hook_hole_pts = [(-hookHeight/2,0),(0,hookHeight/2),(hookHeight/2,0)]

def frame(shouldRotate = False):
    return (
        cq.Workplane("YZ")
        .polyline(frame_pts)
        .close()
        .extrude(width/2,both=True)
        .faces('|X')
        .shell(thickness)
        .transformed((0,90,0),(0,holeDia+thickness,0))
        .circle(holeDia)
        .cutThruAll()
    )

def hook():
    return (
        cq.Workplane("YZ",origin=(-thickness*1.5/2,0,height+thickness/2))
        .spline(hook_pts,tangents=hook_tgts)
        .close()
        .extrude(thickness*1.5)
        .polyline(hook_hole_pts)
        .close()
        .cutThruAll()
        .faces('|(0,1,1)').edges('>Z')
        .fillet(thickness/2)
    )

result = (
    frame()
    .union(frame().rotateAboutCenter((0,0,1),90))
    .union(hook())
)
Irev-Dev commented 3 years ago

Oh great @adam-urbanczyk, Much more concise.