dcowden / cadquery

CadQuery-- a parametric cad script framework
Other
432 stars 56 forks source link

Select inner edges of shell #177

Closed ghost closed 7 years ago

ghost commented 7 years ago

I'm trying to script an object like the image below. I can get the "empty box" using box() and shell() but I can't work out how to select the four inner edges of the top face to apply a chamfer. Is this possible?

Thanks

image

jmwright commented 7 years ago

If all else fails, you might be able to use the nearest point and boolean selectors to get it done. Can you post your code so that I can try some things out?

ghost commented 7 years ago

Thanks @jmwright

This is how I've built the empty box:

import cadquery as cq
from Helpers import show

result = cq.Workplane("XY").box(2, 2, 2)
result = result.faces(">Z").shell(-0.2)

show(result)
jmwright commented 7 years ago

We've got a newer DirectionNth selector (largely undocumented still unfortunately) that can be used to select faces at a certain depth. I started to experiment with that, but am having trouble getting all of the edges selected at once so that FreeCAD doesn't reorder them on me.

https://github.com/dcowden/cadquery/pull/147/files

import cadquery as cq
from cadquery import selectors
from Helpers import show

result = cq.Workplane("XY").box(2, 2, 2)
result = result.faces(">Z").shell(-0.2)
result = result.faces(">Z").edges("<X[0]").chamfer(0.125)

show(result)

I tried the nearest point selector too, but that only selected one edge and is kind of a hackey way to do that selection. I feel like the DirectionNthSelector is probably the way to do what you want, I just haven't spent enough time with it yet to get it right.

@adam-urbanczyk You implemented the DirectionNthSelector. Do you have any thoughts on this?

jmwright commented 7 years ago

@hackscribble The following code does get the result for me that you're looking for, but it's a horrible way to handle the selectors. FreeCAD often selects orders edges differently from execution to execution, so you may not get the same result.

import cadquery as cq
from cadquery import selectors
from Helpers import show

result = cq.Workplane("XY").box(2, 2, 2)
result = result.faces(">Z").shell(-0.2)
result = result.faces(">Z").edges("<X[1]").chamfer(0.125)
result = result.faces(">Z").edges("<Y[2]").chamfer(0.125)
result = result.faces(">Z").edges("<X[0]").chamfer(0.125)
result = result.faces(">Z").edges("<Y[0]").chamfer(0.125)

show(result)

screenshot_2017-04-10_11-12-07

dcowden commented 7 years ago

@hackscribble I'm intrigued by this example, thanks for posting it.

CQ 2.0 will feature 'created by' and 'modifid by' queries, which makes many selections easier. In this case, it makes it easier, but not really easy. We could do:

  result = result.shell(-0.2,

name="myshell").edges(createdby="myshell").edges(">Z")

I think that would work here, but its still not super-intuitive.

On Mon, Apr 10, 2017 at 11:13 AM, Jeremy Wright notifications@github.com wrote:

@hackscribble https://github.com/hackscribble The following code does get the result for me that you're looking for, but it's a horrible way to handle the selectors. FreeCAD often selects orders edges differently from execution to execution, so you may not get the same result.

import cadquery as cqfrom cadquery import selectorsfrom Helpers import show

result = cq.Workplane("XY").box(2, 2, 2) result = result.faces(">Z").shell(-0.2) result = result.faces(">Z").edges("<X[1]").chamfer(0.125) result = result.faces(">Z").edges("<Y[2]").chamfer(0.125) result = result.faces(">Z").edges("<X[0]").chamfer(0.125) result = result.faces(">Z").edges("<Y[0]").chamfer(0.125)

show(result)

[image: screenshot_2017-04-10_11-12-07] https://cloud.githubusercontent.com/assets/1015439/24868623/9d1a4c8a-1dde-11e7-8133-13eb27e36b2d.png

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/dcowden/cadquery/issues/177#issuecomment-292980386, or mute the thread https://github.com/notifications/unsubscribe-auth/ABPOA8VMsvVDZq50_JxZR1To8wsn4timks5rukcPgaJpZM4M4veI .

ghost commented 7 years ago

Many thanks @jmwright and @dcowden

I'll play with that solution.

Maybe another way entirely would be to create a separate solid, in this case an upside down square-based pyramid with the point cut off, position it upside down over the shell at the right height and do a cut?

EDIT

Cutter method seems to work ...

box_x = 12
box_y = 6
box_z = 5
thickness = 0.6
chamfer_top = 0.3
chamfer_side = 0.3

plate = cq.Workplane("XY").box(box_x, box_y, box_z).faces(">Z)") \
    .shell(-thickness)

cutter = cq.Workplane("XY").workplane(offset=box_z/2.0) \
    .rect(box_x-2*(thickness-chamfer_top), box_y-2*(thickness-chamfer_top)) \
    .workplane(offset=-chamfer_side).rect(box_x-2*thickness, box_y-2*thickness) \
    .loft(combine=True)

result = plate.cut(cutter)

show(result)
dcowden commented 7 years ago

yes, another option is that you can always 'break out' of the CQ chained selectors and do things pro grammatically by hand.

for example, if you do ....edges().val(), you'll get a list of edge objects. then, you could loop through them by hand and do arbitrary logic. that'd be uglier than Jeremy's earlier suggestion though.

On Mon, Apr 10, 2017 at 11:24 AM, Hackscribble notifications@github.com wrote:

Many thanks @jmwright https://github.com/jmwright and @dcowden https://github.com/dcowden

I'll play with that solution.

Maybe another way entirely would be to create a separate solid, in this case an upside down square-based pyramid with the point cut off, position it upside down over the shell at the right height and do a cut?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dcowden/cadquery/issues/177#issuecomment-292983992, or mute the thread https://github.com/notifications/unsubscribe-auth/ABPOA5_e8nwaCJlFWrVJe27MrGglyQZTks5rukm_gaJpZM4M4veI .

jmwright commented 7 years ago

I do think that there's probably a clever way to combine the selectors to accomplish this, but I haven't come up with it yet. I'll try different ideas as they come to me today. @dcowden is right though, if you want to you can break the edges out and manually check if they're in the right location to be chamfered.

http://dcowden.github.io/cadquery/classreference.html#cadquery.StringSyntaxSelector

adam-urbanczyk commented 7 years ago

I did not test it (yet), but something as follows should work too:

res_final = res_shelled.faces(">Z").edges("not(<X or >X or <Y or >Y)").chamfer(0.125)

In the end what we probably need here is an inner/outer boundry selector.

jmwright commented 7 years ago

Thanks Adam. An inner/outer boundary selector does sound ideal for this use case.

I tried the code you suggested, so I now have the following.

import cadquery as cq
from cadquery import selectors
from Helpers import show

result = cq.Workplane("XY").box(2, 2, 2)
result = result.faces(">Z").shell(-0.2)
# result = result.faces(">Z").edges("<X[1]").chamfer(0.125)
# result = result.faces(">Z").edges("<Y[2]").chamfer(0.125)
# result = result.faces(">Z").edges("<X[0]").chamfer(0.125)
# result = result.faces(">Z").edges("<Y[0]").chamfer(0.125)

result_final = result.faces(">Z").edges("not(<X or >X or <Y or >Y)").chamfer(0.125)

show(result_final)

However, I get this error:

Traceback (most recent call last):
  File "/usr/lib/freecad/Mod/CadQuery/Gui/Command.py", line 128, in Activated
    imp.load_source('temp_module', tempFile.name)
  File "/tmp/tmpufIRtg", line 12, in <module>
    result_final = result.faces(">Z").edges("not(<X or >X or <Y or >Y)").chamfer(0.125)
  File "/usr/lib/freecad/Mod/CadQuery/Libs/cadquery-lib/cadquery/cq.py", line 582, in edges
    return self._selectObjects('Edges', selector)
  File "/usr/lib/freecad/Mod/CadQuery/Libs/cadquery-lib/cadquery/cq.py", line 483, in _selectObjects
    selectorObj = selectors.StringSyntaxSelector(selector)
  File "/usr/lib/freecad/Mod/CadQuery/Libs/cadquery-lib/cadquery/selectors.py", line 549, in __init__
    parsing_result = _grammar.parseString(selectorString)
  File "/usr/lib/freecad/Mod/CadQuery/Libs/pyparsing.py", line 1228, in parseString
    raise exc

Expected {{XY | XZ | X | YZ | Y | Z | Combine:({"(" Combine:({Combine:({[{"+" | "-"}] W:(0123...)}) [{"." [W:(0123...)]}]}) "," Combine:({Combine:({[{"+" | "-"}] W:(0123...)}) [{"." [W:(0123...)]}]}) "," Combine:({Combine:({[{"+" | "-"}] W:(0123...)}) [{"." [W:(0123...)]}]}) ")"})} | {"%" Plane | Cylinder | Sphere | Cone | Line | Circle | Arc} | {> | < {XY | XZ | X | YZ | Y | Z | Combine:({"(" Combine:({Combine:({[{"+" | "-"}] W:(0123...)}) [{"." [W:(0123...)]}]}) "," Combine:({Combine:({[{"+" | "-"}] W:(0123...)}) [{"." [W:(0123...)]}]}) "," Combine:({Combine:({[{"+" | "-"}] W:(0123...)}) [{"." [W:(0123...)]}]}) ")"})} [{Suppress:("[") Group:({["-"] W:(0123...)}) Suppress:("]")}]} | {| | # | + | - {XY | XZ | X | YZ | Y | Z | Combine:({"(" Combine:({Combine:({[{"+" | "-"}] W:(0123...)}) [{"." [W:(0123...)]}]}) "," Combine:({Combine:({[{"+" | "-"}] W:(0123...)}) [{"." [W:(0123...)]}]}) "," Combine:({Combine:({[{"+" | "-"}] W:(0123...)}) [{"." [W:(0123...)]}]}) ")"})}} | front | back | left | right | top | bottom} (at char 0), (line:1, col:1)
dcowden commented 7 years ago

inner/ounter boundary selector is a great idea!

so like:

object.faces(">Z").outerEdges() or .innerEdges() ?

On Mon, Apr 10, 2017 at 3:21 PM, Adam Urbańczyk notifications@github.com wrote:

I did not test it (yet), but something as follows should work too:

res_final = res_shelled.faces(">Z").edges("not(X or <Y or

Y)").chamfer(0.125)

In the end what we probably need here is an inner/outer boundry selector.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dcowden/cadquery/issues/177#issuecomment-293051792, or mute the thread https://github.com/notifications/unsubscribe-auth/ABPOA5e9hd3CPP-avBBEBdwPvGdrOlDGks5ruoE7gaJpZM4M4veI .

jmwright commented 7 years ago

There are some cases where there could be multiple sets of "concentric" inner edges/wires. Maybe add an index or selector for innerEdges() and outerEdges()?

dcowden commented 7 years ago

yes that's true, you can have islands-- so yes i think it would have to return a list. but i think we already have chained selectors that return the first, last, or an indexed value after one selector gives a list, right?

On Mon, Apr 10, 2017 at 5:35 PM, Jeremy Wright notifications@github.com wrote:

There are some cases where there could be multiple sets of "concentric" inner edges/wires. Maybe add an index or selector for innerEdges() and outerEdges()?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dcowden/cadquery/issues/177#issuecomment-293086185, or mute the thread https://github.com/notifications/unsubscribe-auth/ABPOA2EPdmQ1hk1aJXpVkhpgmDPVfJTSks5ruqCtgaJpZM4M4veI .

jmwright commented 7 years ago

@dcowden Is this what you're talking about? It refers to the stack.

http://dcowden.github.io/cadquery/classreference.html?highlight=first#cadquery.CQ.first

@hackscribble We've hijacked your issue here, but please feel free to post back. It's still your issue.

dcowden commented 7 years ago

​yes, and CQ.item(i).

so .edges().item(i) should get the ith item in a list.​

On Mon, Apr 10, 2017 at 7:28 PM, Jeremy Wright notifications@github.com wrote:

@dcowden https://github.com/dcowden Is this what you're talking about? It refers to the stack.

http://dcowden.github.io/cadquery/classreference.html? highlight=first#cadquery.CQ.first

@hackscribble https://github.com/hackscribble We've hijacked your issue here, but please feel free to post back. It's still your issue.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dcowden/cadquery/issues/177#issuecomment-293107460, or mute the thread https://github.com/notifications/unsubscribe-auth/ABPOAx0PIMaQ2k7WN6vExQzADOAoPs5jks5rursqgaJpZM4M4veI .

ghost commented 7 years ago

@hackscribble We've hijacked your issue here, but please feel free to post back. It's still your issue.

Happy to be hijacked :smile:

adam-urbanczyk commented 7 years ago

@jmwright it works for me. Weird... the only reason I can think of is we are running on different pyParsing versions (?). If you think it will add anything, I can write a test to cover this exact selector phrase.

import cadquery as cq
from cadquery import selectors
from Helpers import show

result = cq.Workplane("XY").box(2, 2, 2)
result = result.faces(">Z").shell(-0.2)

result_final = result.faces(">Z").edges("not(<X or >X or <Y or >Y)").chamfer(0.125)

cq_chamfer_test

adam-urbanczyk commented 7 years ago

@jmwright CI says all checks pass. Could it be something on your side then?

jmwright commented 7 years ago

@adam-urbanczyk What version of FreeCAD are you running? I've got the latest stable installed for Linux, which is 0.16 R6707 from the FreeCAD stable Ubuntu PPA. It looks like I also have pyparsing 2.1.5 embedded with the FreeCAD module too.

dcowden commented 7 years ago

wow, awesome Adam! that ends up being pretty simple-- i wouldnt have thought of that method at all. An excellent use of the powerful logical selectors.

On Tue, Apr 11, 2017 at 2:23 PM, Adam Urbańczyk notifications@github.com wrote:

@jmwright https://github.com/jmwright it works for me. Weird... the onnly reason I can think of is we are running on different pyParsing versions (?). If you think it will add anything, I can write a test to cover this exact selector pharse.

import cadquery as cq from cadquery import selectors from Helpers import show

result = cq.Workplane("XY").box(2, 2, 2) result = result.faces(">Z").shell(-0.2)

result_final = result.faces(">Z").edges("not(X or Y)").chamfer(0.125)

[image: cq_chamfer_test] https://cloud.githubusercontent.com/assets/13981538/24924286/91723c00-1ef4-11e7-96d2-e62efc8d184c.png

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dcowden/cadquery/issues/177#issuecomment-293355694, or mute the thread https://github.com/notifications/unsubscribe-auth/ABPOA7IIzIYCZOa4mv4Q026BRrRD6Rmtks5ru8UJgaJpZM4M4veI .

adam-urbanczyk commented 7 years ago

@jmwright Here is my FreeCad info:

OS: Ubuntu 15.10
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.16.6692 (Git)
Build type: None
Branch: master
Hash: 87293fac9bb85c5f5e0ebf563374e27d565116ae
Python version: 2.7.10
Qt version: 4.8.6
Coin version: 4.0.0a
OCC version: 6.8.0.oce-0.17

In my dev env I use the following pyparsing version:

pyparsing 2.0.3 py27_0

jmwright commented 7 years ago

We're pretty close on FreeCAD versions then, and the exception I get is from pyparsing. I'm thinking something probably changed between version 2.0.3 and 2.1.5 of pyparsing that's causing the error.

adam-urbanczyk commented 7 years ago

@hackscribble to sum up, I think the code below solves you problem. Could you verify that it works for you? If so, I'd propose we close the issue.

import cadquery as cq
from Helpers import show

result = cq.Workplane("XY").box(2, 2, 2).\
    faces(">Z").shell(-0.2).\
    faces(">Z").edges("not(<X or >X or <Y or >Y)").\
    chamfer(0.125)

show(result)

@jmwright Turns out that I am also using 2.1.5 (which is the version bundled with your FreeCad workbench):

>>> import pyparsing
>>> pyparsing.__version__
'2.1.5'
>>> pyparsing
<module 'pyparsing' from '/home/adam/.FreeCAD/Mod/CadQuery/Libs/pyparsing.pyc'>
>>> 

Could you check if the following works in your environment:

from cadquery import selectors
selectors._expression_grammar.parseString('not(<X or >X or <Y or >Y)',parseAll=True)
ghost commented 7 years ago

Hi @adam-urbanczyk I get the same error message as Jim when I run that script.

When I try the other code, I get the error message:

Traceback (most recent call last):
  File "/usr/lib/freecad/Mod/CadQuery/Gui/Command.py", line 181, in Activated
    imp.load_source('temp_module', tempFile.name)
  File "/tmp/tmp3C_FMV", line 2, in <module>
    selectors._expression_grammar.parseString('not(<X or >X or <Y or >Y)',parseAll=True)

'module' object has no attribute '_expression_grammar'
jmwright commented 7 years ago

@adam-urbanczyk When I try to run the code you posted, I get the following error.

Running the Python command 'CadQueryExecuteScript' failed:
Traceback (most recent call last):
  File "/usr/lib/freecad/Mod/CadQuery/Gui/Command.py", line 128, in Activated
    imp.load_source('temp_module', tempFile.name)
  File "/tmp/tmpzAEiK1", line 13, in <module>
    selectors._expression_grammar.parseString('not(<X or >X or <Y or >Y)',parseAll=True)

'module' object has no attribute '_expression_grammar'

I'm running Ubuntu 16.04 whereas you're running 15.10. Do we have a problem with an underlying library version?

adam-urbanczyk commented 7 years ago

It has to be there:

https://github.com/dcowden/cadquery/blob/90acdbc440bd63347dfc546845f3fcf5a4ece3e1/cadquery/selectors.py#L606

Could it be that you are not using the latest cq version in your env?

jmwright commented 7 years ago

@adam-urbanczyk Thanks for the catch. I have the latest release in the workbench, but it looks like your selector changes aren't in that release (0.5.2). We've been wanting to bump CadQuery's version number up to 1.0.0 for a long time. I think now is the time to do it. I'll start prepping that and hopefully get it released tomorrow.

@hackscribble In the meantime, if you're using the FreeCAD workbench you can download the latest master of CadQuery and replace the contents of this directory: https://github.com/jmwright/cadquery-freecad-module/tree/master/CadQuery/Libs/cadquery-lib

If you've installed CadQuery using pip, you can download the CadQuery master and then manipulate sys.path to look at that directory before the one pip installed. I can explain in more detail if you want. Sorry for the hacks, but I wanted to give you a workaround until Dave or I get a chance to push 1.0.0 out.

ghost commented 7 years ago

Thanks @jmwright and @adam-urbanczyk

I have updated cadquery-lib and the selector works :smile:

Thanks everyone for your help.

jmwright commented 7 years ago

Just FYI, the new version of CadQuery and the FreeCAD module have been released: https://groups.google.com/forum/#!topic/cadquery/DOMyLCpvrjE

adam-urbanczyk commented 7 years ago

@dcowden @jmwright I'll open an issue about lacking documentation of the extended selector syntax. I think it would be good to spread the word.