diff --git a/plugins/localselectors/README.md b/plugins/localselectors/README.md new file mode 100644 index 0000000..3628554 --- /dev/null +++ b/plugins/localselectors/README.md @@ -0,0 +1,37 @@ +# Sample Plugin + +This plugin modifies `Workplane` selectors so that they can be used to specify axes in the local coordinate plane. +This is done by using the lowercase letters `x`, `y`, and `z` instead of the uppercase ones. + +## Installation + +``` +pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=localcoordinates&subdirectory=plugins/localcoordinates" +``` + + +## Dependencies + +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). +It uses a lot of internal structures, so it may break more easily on later versions of CadQuery than other plugins. +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 + +## Usage + +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. + +```python +import cadquery as cq + +result = (cq.Workplane().rect(50, 50) + .extrude(50)) + +new_workplane = (result.faces(">x") # this should be the same as '>X' because we're starting off in the default coordinate system + .workplane()) +result2 = (new_workplane.rect(30, 30) + .extrude(30)) + +new_workplane = (result2 + .faces(">z") + .workplane()) # this should be the face sticking away from the first cube +``` diff --git a/plugins/localselectors/__init__.py b/plugins/localselectors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/localselectors/localselectors.py b/plugins/localselectors/localselectors.py new file mode 100644 index 0000000..7f9a72a --- /dev/null +++ b/plugins/localselectors/localselectors.py @@ -0,0 +1,153 @@ +import cadquery as cq + +from cadquery.occ_impl.geom import Vector +from cadquery.occ_impl.shape_protocols import ( + geom_LUT_EDGE, + geom_LUT_FACE, +) + +from pyparsing import ( + pyparsing_common, + Literal, + Word, + nums, + Optional, + Combine, + oneOf, + Group, + infixNotation, + opAssoc, +) + + +def _makeGrammar(): + """ + Define the simple string selector grammar using PyParsing + """ + + # float definition + point = Literal(".") + plusmin = Literal("+") | Literal("-") + number = Word(nums) + integer = Combine(Optional(plusmin) + number) + floatn = Combine(integer + Optional(point + Optional(number))) + + # vector definition + lbracket = Literal("(") + rbracket = Literal(")") + comma = Literal(",") + vector = Combine( + lbracket + floatn("x") + comma + floatn("y") + comma + floatn("z") + rbracket, + adjacent=False, + ) + + # direction definition + simple_dir = oneOf( + ["X", "Y", "Z", "XY", "XZ", "YZ"] + ["x", "y", "z", "xy", "xz", "yz"] + ) + direction = simple_dir("simple_dir") | vector("vector_dir") + + # CQ type definition + cqtype = oneOf( + set(geom_LUT_EDGE.values()) | set(geom_LUT_FACE.values()), caseless=True, + ) + cqtype = cqtype.setParseAction(pyparsing_common.upcaseTokens) + + # type operator + type_op = Literal("%") + + # direction operator + direction_op = oneOf([">", "<"]) + + # center Nth operator + center_nth_op = oneOf([">>", "<<"]) + + # index definition + ix_number = Group(Optional("-") + Word(nums)) + lsqbracket = Literal("[").suppress() + rsqbracket = Literal("]").suppress() + + index = lsqbracket + ix_number("index") + rsqbracket + + # other operators + other_op = oneOf(["|", "#", "+", "-"]) + + # named view + named_view = oneOf(["front", "back", "left", "right", "top", "bottom"]) + + return ( + direction("only_dir") + | (type_op("type_op") + cqtype("cq_type")) + | (direction_op("dir_op") + direction("dir") + Optional(index)) + | (center_nth_op("center_nth_op") + direction("dir") + Optional(index)) + | (other_op("other_op") + direction("dir")) + | named_view("named_view") + ) + + +old_getVector = cq.selectors._SimpleStringSyntaxSelector._getVector + + +def _getVector(self, pr): + if ( + "simple_dir" in pr + and pr.simple_dir in cq.selectors._SimpleStringSyntaxSelector.local_axes + ): + return cq.selectors._SimpleStringSyntaxSelector.local_axes[pr.simple_dir] + else: + return old_getVector(self, pr) + + +class LocalCoordinates: + def __init__(self, plane): + self.plane = plane + self.old_axes = None + + def __enter__(self): + self.old_axes, cq.selectors._SimpleStringSyntaxSelector.local_axes = ( + cq.selectors._SimpleStringSyntaxSelector.local_axes, + { + "x": self.plane.xDir, + "y": self.plane.yDir, + "z": self.plane.zDir, + "xy": self.plane.xDir + self.plane.yDir, + "yz": self.plane.yDir + self.plane.zDir, + "xz": self.plane.xDir + self.plane.zDir, + }, + ) + + def __exit__(self, _exc_type, _exc_value, _traceback): + cq.selectors._SimpleStringSyntaxSelector.local_axes = self.old_axes + + +def _filter(self, objs, selector): + selectorObj: Selector + if selector: + if isinstance(selector, str): + with LocalCoordinates(self.plane): + selectorObj = cq.selectors.StringSyntaxSelector(selector) + else: + selectorObj = selector + toReturn = selectorObj.filter(objs) + else: + toReturn = objs + + return toReturn + + +cq.selectors._SimpleStringSyntaxSelector.local_axes = { + "x": Vector(1, 0, 0), + "y": Vector(0, 1, 0), + "z": Vector(0, 0, 1), + "xy": Vector(1, 1, 0), + "yz": Vector(0, 1, 1), + "xz": Vector(1, 0, 1), +} +cq.selectors._SimpleStringSyntaxSelector._getVector = _getVector + +cq.selectors._grammar = _makeGrammar() # make a grammar instance +cq.selectors._expression_grammar = cq.selectors._makeExpressionGrammar( + cq.selectors._grammar +) + +cq.Workplane._filter = _filter diff --git a/plugins/localselectors/setup.py b/plugins/localselectors/setup.py new file mode 100644 index 0000000..b0695c3 --- /dev/null +++ b/plugins/localselectors/setup.py @@ -0,0 +1,51 @@ +from setuptools import setup, find_packages + +# Change these variables to set the information for your plugin +version = "1.0.0" # Please update this version number when updating the plugin +plugin_name = "localselectors" # The name of your plugin +description = "Adds local coordinator selectors to CadQuery" +long_description = ( + "Monkey patches in local coordinate selectors so you can use things like '>x'" +) +author = "Kelvin Ly" +author_email = "cactorium" +packages = [] # List of packages that will be installed with this plugin +py_modules = ["localselectors"] # Put the name of your plugin's .py file here +install_requires = ( + [] +) # Any dependencies that pip also needs to install to make this plugin work + + +setup( + name=plugin_name, + version=version, + url="https://github.com/CadQuery/cadquery-plugins", + license="Apache Public License 2.0", + author=author, + author_email=author_email, + description=description, + long_description=long_description, + packages=packages, + py_modules=py_modules, + install_requires=install_requires, + include_package_data=True, + zip_safe=False, + platforms="any", + test_suite="tests", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: End Users/Desktop", + "Intended Audience :: Information Technology", + "Intended Audience :: Science/Research", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: Apache Software License", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: Unix", + "Programming Language :: Python", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Internet", + "Topic :: Scientific/Engineering", + ], +)