Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assembly hierarchy #534

Open
wants to merge 23 commits into
base: assembly-tags
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5eebcd5
Use OCP 7.4 for now
adam-urbanczyk Nov 13, 2020
3136ede
Added radius method to Mixin1D
marcus7070 Nov 18, 2020
255b464
Added RadiusNthSelector
marcus7070 Nov 18, 2020
821886f
Add Mac OS exception to Mixin1D.radius
marcus7070 Nov 18, 2020
78d100d
Merge pull request #504 from marcus7070/marcus7070/edgeRadius
jmwright Nov 20, 2020
b22436c
Implement tag based constraint definition (#514)
adam-urbanczyk Nov 24, 2020
3914bc3
`occ_impl` doc strings (#491)
marcus7070 Nov 30, 2020
cf275b0
Constraints docs [WIP] (#524)
adam-urbanczyk Nov 30, 2020
c14ab6b
Trying to move to env variable in Appveyor
jmwright Dec 1, 2020
1b52f10
Merge pull request #528 from jmwright/master
jmwright Dec 4, 2020
40a4995
added hierarchical identifiers
bernhardBV Dec 5, 2020
9affb15
split _query into _query_workplane and _query
bernhardBV Dec 5, 2020
156e331
simplify subclassing class Assembly without overriding add method
bernhardBV Dec 5, 2020
54cd86b
added test for hierarchical identities
bernhardBV Dec 5, 2020
c42fa77
Add mirror from face features (with example now fixed) (#527)
blooop Dec 8, 2020
a0c0415
Fixed code typos in revolve example
jmwright Dec 8, 2020
52ae385
Merge pull request #536 from jmwright/master
jmwright Dec 8, 2020
a03648c
Change to ProjectedOrigin (#532)
adam-urbanczyk Dec 8, 2020
8ffbf1e
More strict grammar
adam-urbanczyk Dec 9, 2020
8ecbf9d
Merge branch 'master' into assembly-hierarchy
adam-urbanczyk Dec 10, 2020
da99cd0
Grammar fix
adam-urbanczyk Dec 12, 2020
9d1e335
Black fix
adam-urbanczyk Dec 12, 2020
f19895e
Black fix
adam-urbanczyk Dec 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[run]
branch = True
omit = cadquery/utils.py

[report]
exclude_lines =
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ environment:
MINICONDA_DIRNAME: C:\Miniconda37-x64

ANACONDA_TOKEN:
secure: nxF/a2f3iS9KXGu7B/wKJYAk7Sm5wyAjoZoqJvPbRoVK4saaozVwOxDrjwJjJAYb
secure: $(anaconda_token)

install:
- set "PATH=%MINICONDA_DIRNAME%;%MINICONDA_DIRNAME%\\Scripts;%PATH%"
Expand Down
66 changes: 51 additions & 15 deletions cadquery/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,26 @@
ConstraintKinds = Literal["Plane", "Point", "Axis"]
ExportLiterals = Literal["STEP", "XML"]

PATH_DELIM = "/"

# enitity selector grammar definiiton
def _define_grammar():

from pyparsing import Literal as Literal, Word, Optional, alphas, alphanums
from pyparsing import (
Literal as Literal,
Word,
Optional,
alphas,
alphanums,
delimitedList,
)

Separator = Literal("@").suppress()
TagSeparator = Literal("?").suppress()

Name = Word(alphas, alphanums + "_").setResultsName("name")
Name = delimitedList(
Word(alphas, alphanums + "_"), PATH_DELIM, combine=True
).setResultsName("name")
Tag = Word(alphas, alphanums + "_").setResultsName("tag")
Selector = _selector_grammar.setResultsName("selector")

Expand Down Expand Up @@ -174,19 +185,27 @@ def __init__(
self.constraints = []
self.objects = {self.name: self}

def _copy(self) -> "Assembly":
def _change_prefix(self, prefix):
_, delim, rest = self.name.rpartition(PATH_DELIM)
return prefix + delim + rest

def _add_prefix(self, path):
return path if self.name is None else self.name + PATH_DELIM + path

def _copy(self, name: str) -> "Assembly":
"""
Make a deep copy of an assembly
"""

rv = self.__class__(self.obj, self.loc, self.name, self.color)
rv = self.__class__(self.obj, self.loc, name, self.color)

for ch in self.children:
ch_copy = ch._copy()
full_name = ch._change_prefix(name)
ch_copy = ch._copy(full_name)
ch_copy.parent = rv

rv.children.append(ch_copy)
rv.objects[ch_copy.name] = ch_copy
rv.objects[full_name] = ch_copy
rv.objects.update(ch_copy.objects)

return rv
Expand Down Expand Up @@ -233,27 +252,26 @@ def add(self, arg, **kwargs):
"""

if isinstance(arg, Assembly):
name = self._add_prefix(kwargs.get("name", arg.name))
subassy = arg._copy(name)

subassy = arg._copy()

subassy.loc = kwargs["loc"] if kwargs.get("loc") else arg.loc
subassy.name = kwargs["name"] if kwargs.get("name") else arg.name
subassy.color = kwargs["color"] if kwargs.get("color") else arg.color
subassy.loc = kwargs.get("loc", arg.loc)
subassy.color = kwargs.get("color", arg.color)
subassy.parent = self

self.children.append(subassy)
self.objects[subassy.name] = subassy
self.objects.update(subassy.objects)

else:
assy = Assembly(arg, **kwargs)
assy = self.__class__(arg, **kwargs)
assy.parent = self

self.add(assy)

return self

def _query(self, q: str) -> Tuple[str, Optional[Shape]]:
def _query_workplane(self, q: str) -> Tuple[str, Workplane]:
"""
Execute a selector query on the assembly.
The query is expected to be in the following format:
Expand All @@ -278,7 +296,7 @@ def _query(self, q: str) -> Tuple[str, Optional[Shape]]:
obj = self.objects[name].obj

if isinstance(obj, Workplane) and query.tag:
tmp = obj.ctx.tags[query.tag]
tmp = obj._getTagged(query.tag)
elif isinstance(obj, (Workplane, Shape)):
tmp = Workplane().add(obj)
else:
Expand All @@ -289,7 +307,25 @@ def _query(self, q: str) -> Tuple[str, Optional[Shape]]:
else:
res = tmp

val = res.val()
return name, res

def _query(self, q: str) -> Tuple[str, Optional[Shape]]:
"""
Execute a selector query on the assembly.
The query is expected to be in the following format:

name[?tag][@kind@args]

valid example include:

obj_name @ faces @ >Z
obj_name?tag1@faces@>Z
obj_name ? tag
obj_name

"""
name, obj = self._query_workplane(q)
val = obj.val()

return name, val if isinstance(val, Shape) else None

Expand Down
74 changes: 62 additions & 12 deletions cadquery/cq.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,14 +413,13 @@ def toOCC(self) -> Any:

return self.val().wrapped

@deprecate_kwarg("centerOption", "ProjectedOrigin")
def workplane(
self,
offset: float = 0.0,
invert: bool = False,
centerOption: Literal[
"CenterOfMass", "ProjectedOrigin", "CenterOfBoundBox"
] = "CenterOfMass",
] = "ProjectedOrigin",
origin: Optional[VectorLike] = None,
) -> "Workplane":
"""
Expand Down Expand Up @@ -636,9 +635,10 @@ def last(self) -> "Workplane":
"""
return self.newObject([self.objects[-1]])

def end(self) -> "Workplane":
def end(self, n: int = 1) -> "Workplane":
"""
Return the parent of this CQ element
Return the nth parent of this CQ element
:param n: number of ancestor to return (default: 1)
:rtype: a CQ object
:raises: ValueError if there are no more parents in the chain.

Expand All @@ -650,10 +650,15 @@ def end(self) -> "Workplane":

CQ(obj).faces("+Z")
"""
if self.parent:
return self.parent
else:
raise ValueError("Cannot End the chain-- no parents!")

rv = self
for _ in range(n):
if rv.parent:
rv = rv.parent
else:
raise ValueError("Cannot End the chain-- no parents!")

return rv

def _findType(self, types, searchStack=True, searchParents=True):

Expand Down Expand Up @@ -1021,17 +1026,62 @@ def rotate(
]
)

def mirror(self, mirrorPlane="XY", basePointVector=(0, 0, 0)):
def mirror(
self,
mirrorPlane: Union[
Literal["XY", "YX", "XZ", "ZX", "YZ", "ZY"], VectorLike, Face, "Workplane"
] = "XY",
basePointVector: Optional[VectorLike] = None,
union: bool = False,
):
"""
Mirror a single CQ object. This operation is the same as in the FreeCAD PartWB's mirroring

:param mirrorPlane: the plane to mirror about
:type mirrorPlane: string, one of "XY", "YX", "XZ", "ZX", "YZ", "ZY" the planes
:param basePointVector: the base point to mirror about
or the normal vector of the plane eg (1,0,0) or a Face object
:param basePointVector: the base point to mirror about (this is overwritten if a Face is passed)
:type basePointVector: tuple
:param union: If true will perform a union operation on the mirrored object
:type union: bool
"""
newS = self.newObject([self.objects[0].mirror(mirrorPlane, basePointVector)])
return newS.first()

mp: Union[Literal["XY", "YX", "XZ", "ZX", "YZ", "ZY"], Vector]
bp: Vector
face: Optional[Face] = None

# handle mirrorPLane
if isinstance(mirrorPlane, Workplane):
val = mirrorPlane.val()
if isinstance(val, Face):
mp = val.normalAt()
face = val
else:
raise ValueError(f"Face required, got {val}")
elif isinstance(mirrorPlane, Face):
mp = mirrorPlane.normalAt()
face = mirrorPlane
elif not isinstance(mirrorPlane, str):
mp = Vector(mirrorPlane)
else:
mp = mirrorPlane

# handle basePointVector
if face and basePointVector is None:
bp = face.Center()
elif basePointVector is None:
bp = Vector()
else:
bp = Vector(basePointVector)

newS = self.newObject(
[obj.mirror(mp, bp) for obj in self.vals() if isinstance(obj, Shape)]
)

if union:
return self.union(newS)
else:
return newS

def translate(self, vec: VectorLike) -> "Workplane":
"""
Expand Down
Loading