Skip to content

Commit d263aa9

Browse files
cactoriumnobkd
andauthored
Plugin for local coordinate system selectors (#35)
* First working version of it; TODO pare it down * Simplify monkeypatch * Simplify further and remove unused imports * Add some description to the README Co-authored-by: nobkd <[email protected]> * Update plugins/localselectors/README.md Co-authored-by: nobkd <[email protected]> * Fix linter errors --------- Co-authored-by: nobkd <[email protected]>
1 parent 7a2d1a8 commit d263aa9

File tree

4 files changed

+241
-0
lines changed

4 files changed

+241
-0
lines changed

plugins/localselectors/README.md

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Sample Plugin
2+
3+
This plugin modifies `Workplane` selectors so that they can be used to specify axes in the local coordinate plane.
4+
This is done by using the lowercase letters `x`, `y`, and `z` instead of the uppercase ones.
5+
6+
## Installation
7+
8+
```
9+
pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=localcoordinates&subdirectory=plugins/localcoordinates"
10+
```
11+
12+
13+
## Dependencies
14+
15+
This plugin has no dependencies other than the cadquery library. To install CadQuery, follow the [instructions in its readme](https://github.com/CadQuery/cadquery#getting-started).
16+
It uses a lot of internal structures, so it may break more easily on later versions of CadQuery than other plugins.
17+
It was tested on CadQuery 2.5, feel free to post an issue in my [fork](https://github.com/cactorium/cadquery-plugins) if you run into any issues
18+
19+
## Usage
20+
21+
To use this plugin after it has been installed, import it to automatically patch its function(s) into the `cadquery.Workplane` class. Any function that uses string selectors should now work with these new selectors after import, but be sure to import `cadquery` first.
22+
23+
```python
24+
import cadquery as cq
25+
26+
result = (cq.Workplane().rect(50, 50)
27+
.extrude(50))
28+
29+
new_workplane = (result.faces(">x") # this should be the same as '>X' because we're starting off in the default coordinate system
30+
.workplane())
31+
result2 = (new_workplane.rect(30, 30)
32+
.extrude(30))
33+
34+
new_workplane = (result2
35+
.faces(">z")
36+
.workplane()) # this should be the face sticking away from the first cube
37+
```

plugins/localselectors/__init__.py

Whitespace-only changes.
+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import cadquery as cq
2+
3+
from cadquery.occ_impl.geom import Vector
4+
from cadquery.occ_impl.shape_protocols import (
5+
geom_LUT_EDGE,
6+
geom_LUT_FACE,
7+
)
8+
9+
from pyparsing import (
10+
pyparsing_common,
11+
Literal,
12+
Word,
13+
nums,
14+
Optional,
15+
Combine,
16+
oneOf,
17+
Group,
18+
infixNotation,
19+
opAssoc,
20+
)
21+
22+
23+
def _makeGrammar():
24+
"""
25+
Define the simple string selector grammar using PyParsing
26+
"""
27+
28+
# float definition
29+
point = Literal(".")
30+
plusmin = Literal("+") | Literal("-")
31+
number = Word(nums)
32+
integer = Combine(Optional(plusmin) + number)
33+
floatn = Combine(integer + Optional(point + Optional(number)))
34+
35+
# vector definition
36+
lbracket = Literal("(")
37+
rbracket = Literal(")")
38+
comma = Literal(",")
39+
vector = Combine(
40+
lbracket + floatn("x") + comma + floatn("y") + comma + floatn("z") + rbracket,
41+
adjacent=False,
42+
)
43+
44+
# direction definition
45+
simple_dir = oneOf(
46+
["X", "Y", "Z", "XY", "XZ", "YZ"] + ["x", "y", "z", "xy", "xz", "yz"]
47+
)
48+
direction = simple_dir("simple_dir") | vector("vector_dir")
49+
50+
# CQ type definition
51+
cqtype = oneOf(
52+
set(geom_LUT_EDGE.values()) | set(geom_LUT_FACE.values()), caseless=True,
53+
)
54+
cqtype = cqtype.setParseAction(pyparsing_common.upcaseTokens)
55+
56+
# type operator
57+
type_op = Literal("%")
58+
59+
# direction operator
60+
direction_op = oneOf([">", "<"])
61+
62+
# center Nth operator
63+
center_nth_op = oneOf([">>", "<<"])
64+
65+
# index definition
66+
ix_number = Group(Optional("-") + Word(nums))
67+
lsqbracket = Literal("[").suppress()
68+
rsqbracket = Literal("]").suppress()
69+
70+
index = lsqbracket + ix_number("index") + rsqbracket
71+
72+
# other operators
73+
other_op = oneOf(["|", "#", "+", "-"])
74+
75+
# named view
76+
named_view = oneOf(["front", "back", "left", "right", "top", "bottom"])
77+
78+
return (
79+
direction("only_dir")
80+
| (type_op("type_op") + cqtype("cq_type"))
81+
| (direction_op("dir_op") + direction("dir") + Optional(index))
82+
| (center_nth_op("center_nth_op") + direction("dir") + Optional(index))
83+
| (other_op("other_op") + direction("dir"))
84+
| named_view("named_view")
85+
)
86+
87+
88+
old_getVector = cq.selectors._SimpleStringSyntaxSelector._getVector
89+
90+
91+
def _getVector(self, pr):
92+
if (
93+
"simple_dir" in pr
94+
and pr.simple_dir in cq.selectors._SimpleStringSyntaxSelector.local_axes
95+
):
96+
return cq.selectors._SimpleStringSyntaxSelector.local_axes[pr.simple_dir]
97+
else:
98+
return old_getVector(self, pr)
99+
100+
101+
class LocalCoordinates:
102+
def __init__(self, plane):
103+
self.plane = plane
104+
self.old_axes = None
105+
106+
def __enter__(self):
107+
self.old_axes, cq.selectors._SimpleStringSyntaxSelector.local_axes = (
108+
cq.selectors._SimpleStringSyntaxSelector.local_axes,
109+
{
110+
"x": self.plane.xDir,
111+
"y": self.plane.yDir,
112+
"z": self.plane.zDir,
113+
"xy": self.plane.xDir + self.plane.yDir,
114+
"yz": self.plane.yDir + self.plane.zDir,
115+
"xz": self.plane.xDir + self.plane.zDir,
116+
},
117+
)
118+
119+
def __exit__(self, _exc_type, _exc_value, _traceback):
120+
cq.selectors._SimpleStringSyntaxSelector.local_axes = self.old_axes
121+
122+
123+
def _filter(self, objs, selector):
124+
selectorObj: Selector
125+
if selector:
126+
if isinstance(selector, str):
127+
with LocalCoordinates(self.plane):
128+
selectorObj = cq.selectors.StringSyntaxSelector(selector)
129+
else:
130+
selectorObj = selector
131+
toReturn = selectorObj.filter(objs)
132+
else:
133+
toReturn = objs
134+
135+
return toReturn
136+
137+
138+
cq.selectors._SimpleStringSyntaxSelector.local_axes = {
139+
"x": Vector(1, 0, 0),
140+
"y": Vector(0, 1, 0),
141+
"z": Vector(0, 0, 1),
142+
"xy": Vector(1, 1, 0),
143+
"yz": Vector(0, 1, 1),
144+
"xz": Vector(1, 0, 1),
145+
}
146+
cq.selectors._SimpleStringSyntaxSelector._getVector = _getVector
147+
148+
cq.selectors._grammar = _makeGrammar() # make a grammar instance
149+
cq.selectors._expression_grammar = cq.selectors._makeExpressionGrammar(
150+
cq.selectors._grammar
151+
)
152+
153+
cq.Workplane._filter = _filter

plugins/localselectors/setup.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from setuptools import setup, find_packages
2+
3+
# Change these variables to set the information for your plugin
4+
version = "1.0.0" # Please update this version number when updating the plugin
5+
plugin_name = "localselectors" # The name of your plugin
6+
description = "Adds local coordinator selectors to CadQuery"
7+
long_description = (
8+
"Monkey patches in local coordinate selectors so you can use things like '>x'"
9+
)
10+
author = "Kelvin Ly"
11+
author_email = "cactorium"
12+
packages = [] # List of packages that will be installed with this plugin
13+
py_modules = ["localselectors"] # Put the name of your plugin's .py file here
14+
install_requires = (
15+
[]
16+
) # Any dependencies that pip also needs to install to make this plugin work
17+
18+
19+
setup(
20+
name=plugin_name,
21+
version=version,
22+
url="https://github.com/CadQuery/cadquery-plugins",
23+
license="Apache Public License 2.0",
24+
author=author,
25+
author_email=author_email,
26+
description=description,
27+
long_description=long_description,
28+
packages=packages,
29+
py_modules=py_modules,
30+
install_requires=install_requires,
31+
include_package_data=True,
32+
zip_safe=False,
33+
platforms="any",
34+
test_suite="tests",
35+
classifiers=[
36+
"Development Status :: 5 - Production/Stable",
37+
"Intended Audience :: Developers",
38+
"Intended Audience :: End Users/Desktop",
39+
"Intended Audience :: Information Technology",
40+
"Intended Audience :: Science/Research",
41+
"Intended Audience :: System Administrators",
42+
"License :: OSI Approved :: Apache Software License",
43+
"Operating System :: POSIX",
44+
"Operating System :: MacOS",
45+
"Operating System :: Unix",
46+
"Programming Language :: Python",
47+
"Topic :: Software Development :: Libraries :: Python Modules",
48+
"Topic :: Internet",
49+
"Topic :: Scientific/Engineering",
50+
],
51+
)

0 commit comments

Comments
 (0)