Skip to content

Commit

Permalink
Plugin for local coordinate system selectors (#35)
Browse files Browse the repository at this point in the history
* 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 <44443899+nobkd@users.noreply.github.com>

* Update plugins/localselectors/README.md

Co-authored-by: nobkd <44443899+nobkd@users.noreply.github.com>

* Fix linter errors

---------

Co-authored-by: nobkd <44443899+nobkd@users.noreply.github.com>
  • Loading branch information
cactorium and nobkd authored Jun 8, 2024
1 parent 7a2d1a8 commit d263aa9
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 0 deletions.
37 changes: 37 additions & 0 deletions plugins/localselectors/README.md
Original file line number Diff line number Diff line change
@@ -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
```
Empty file.
153 changes: 153 additions & 0 deletions plugins/localselectors/localselectors.py
Original file line number Diff line number Diff line change
@@ -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
51 changes: 51 additions & 0 deletions plugins/localselectors/setup.py
Original file line number Diff line number Diff line change
@@ -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",
],
)

0 comments on commit d263aa9

Please sign in to comment.