-
Notifications
You must be signed in to change notification settings - Fork 294
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
Topology traversal selection #1013
base: master
Are you sure you want to change the base?
Conversation
minimal working example
Thanks @Jojain - let me digest this. I must say I planned to implement this in a completely different way - by refactoring of the selectors. |
No problem, there isn't much code and I think it's self explaining enough but if you want me to explain feel free to ask. The main point of this is that it doesn't restrain selections to be thinning down the numbers of objects on the stack. This is particularly useful on complex model where it is a lot easier for the human brain to think of a succession of simple selection rather than coming up with only one selection call that would target the desired shape. I would also be happy to know how you would want to refactor selectors to see if I can be of any help ! |
Do selectors have to thin down the number of objects, though? Right now they technically do as they are implemented as filters, but is that a design principle or just an implementation limitation/oversight? Currently Here's a dirty monkey-patched proof-of-concept: from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any, Iterable, Optional, Union
import cadquery as cq
from cadquery import Selector, Shape, Workplane
from topo_explorer import ConnectedShapesExplorer
# new selector API
@dataclass(frozen=True)
class SelectionContext(): #TODO better name?
workplane: Workplane
objType: Any
tag: Optional[str]
def collectProperty(self, objType:Optional[Any]=None):
cq_obj = self.workplane._getTagged(self.tag) if self.tag else self.workplane
return cq_obj._collectProperty(objType if objType else self.objType)
#TODO additional useful stuff would go here to keep the actual NewSelector class simple/stable
class NewSelector(ABC):
@abstractmethod
def select(self, ctx: SelectionContext) -> Iterable[Shape]:
pass
# monkey-patching it in
_old_selectObjects = Workplane._selectObjects
def _new_selectObjects(
self: Workplane,
objType: Any,
selector: Optional[Union[Selector, str, NewSelector]] = None,
tag: Optional[str] = None,
) -> Workplane:
if isinstance(selector, NewSelector):
ctx = SelectionContext(workplane=self, objType=objType, tag=tag)
return self.newObject(selector.select(ctx))
else:
return _old_selectObjects(self, objType, selector, tag)
Workplane._selectObjects = _new_selectObjects
# example usage
class Connected(NewSelector):
def __init__(self, include_source: bool=False) -> None:
self.include_child_shape = include_source
def select(self, ctx: SelectionContext) -> Iterable[Shape]:
types = {
'Edges': 'Edge',
'Faces': 'Face',
#TODO add the rest or change `topo_explorer.ENUM_MAPPING` to match
}
solid = ctx.workplane.findSolid()
for object in ctx.workplane.objects:
explorer = ConnectedShapesExplorer(solid, object)
yield from explorer.search(types[ctx.objType], self.include_child_shape)
boxes = cq.Workplane().box(10, 10, 10) - cq.Workplane().box(2, 10, 10)
edges = boxes.faces("<Z").faces('<X').edges(Connected())
faces = boxes.faces("<Z").faces('>X').faces(Connected(include_source=True)) I don't use cadquery enough to know if this is the one true way to refactor selectors but something along those lines would be an improvement for topology-based use cases. |
Hello I've made a minimal working example of a topology traversal capability for CQ. It aims to answer #565 ( and eventually #371 )
Since the design has not been discussed that much in the issues I propose the following design. It it currently limited in capability but if we have an agreement on the design I will add all the needed cases (considering objects connected not necessarily by vertices)
The proposed design would allow to do such thing :
box = cq.Workplane().box(10, 10, 10).faces(">Z").connected("Edge")
Which gives the following result :
Or :
box = cq.Workplane().box(10, 10, 10).faces(">Z").connected("Face", include_child_shape=True)
Let me know if you have comments or recommandation