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

filter/map/apply/sort/[]/invoke #1514

Merged
merged 20 commits into from
Jul 3, 2024
Merged

Conversation

voneiden
Copy link
Contributor

Some time back I made cq-filter to allow easier manipulation of workplane objects without breaking out of the fluent API. @adam-urbanczyk requested in voneiden/cq-filter#1 to merge some features from cq-filter into cadquery, namely:

  • getitem
  • filter
  • sort

and optionally

  • group

First off, I've decided to rename filter to filter_objects and sort to sort_objects. I think the verbosity is warranted as Workplane is a fairly complex object.

The cq-filter implementation of group created an intermediary workplane with extra fields to support slicing groups. I never particularly liked doing it this way, so I spent some time thinking how this could be done without the intermediary workplane. One option, used in this PR, is to create a separate Group object that supports slicing. This does require however the ability for the Group to iterate through candidate objects independently, so it can not be used with filter_objects as it allows the supplied filter function to operate only one one CQObject at a time.

Therefore I needed to introduce another method, currently named as map_objects which hands over the workplane objects to the map function and expects a new list of objects to be returned.

Group supports fixed window groups (xn - x0 <= tol) and moving window groups (xn - xn-1 <= tol)


Currently the PR is missing documentation and tests, but I'm opening this to allow discussion of implementation details. I'm flexible regarding the scope of this PR, if it feels like Group doesn't belong here then we'll drop it.

I'm a bit low on free dev time for the time being so this might progress at a snails pace but we'll get there.

Copy link

codecov bot commented Jan 30, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 94.91%. Comparing base (bf47f12) to head (0c3b810).
Report is 3 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1514      +/-   ##
==========================================
+ Coverage   94.86%   94.91%   +0.05%     
==========================================
  Files          28       28              
  Lines        6228     6259      +31     
  Branches     1262     1271       +9     
==========================================
+ Hits         5908     5941      +33     
+ Misses        193      192       -1     
+ Partials      127      126       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@adam-urbanczyk
Copy link
Member

Thanks, it too me a while to get to this. I'll do some rework. I'd like to descope group for now, feels like too big of a change.

Remove group
Rename
More generic []
Add apply / change map
@michaelgale
Copy link

I like the intent behind cq_filter and the proposed changes. I wonder if these features are in fact already available with the use of the Selector classes. A big clue is the fact that any derived sub-class of Selector must implement their own filter method. In my cq-kit package I extend Selector with a multitude of utility selectors similar to the proposed features such as:
EdgeLengthSelector, WireLengthSelector, RadiusSelector, EdgeCountSelector, SameLengthAsObjectSelector, SharedVerticesWithObjectSelector, and much more

With Selector classes you can effectively filter any CQ object(s) with the added benefit that Selector classes can be usefully combined logically and arithmetically since they implement operator overloading of +, -, and, not, etc.

@adam-urbanczyk
Copy link
Member

filter is indeed a quick and dirty way to implement selectors. You'd use it for one-offs and implement a proper cq.Selector for something more reusable. map and apply are for something else.

@adam-urbanczyk adam-urbanczyk changed the title PoC commit for discussion PoC commit for discussion [filter/map/apply/sort/[]] May 23, 2024
@adam-urbanczyk
Copy link
Member

@jmwright @lorenzncode @voneiden what do you think about it? I'd say this would be it (minus docs and groupby).

I'm still wondering if we should allow to map via Callable[[Iterable[CQObject]], Sequence[CQObject]] too. Regarding groupby it could be implemented using cq.Compound so that no new classes need to be created.

@voneiden
Copy link
Contributor Author

voneiden commented Jun 6, 2024

Looks pretty neat and clean to me, I like it.

Trivial: do you mind if I tidy up the commits a bit? I'd like to amend my original commit message at least since it's very ambiguous.

@adam-urbanczyk
Copy link
Member

I'm going to squash anyhows, so why not.

Additional idea: allow callbacks with 0 arity or no rv for debugging (if multimethod allows to dispatch on that). Use case would be quick debugging.

w.apply(breakpoint).apply(show)

@voneiden
Copy link
Contributor Author

voneiden commented Jun 8, 2024

Alright, if gets squashed then I suppose it's fine as is with the surrounding context.

The idea you presented seems like it could be useful, but IMO the current interface is very clean and easy to understand. I'd slightly lean towards having a separate method for this use case but no strong feelings. Multimethod could fine too, if the docstring explains the secondary behaviour in layman terms.

@lorenzncode
Copy link
Member

Looks good with minimal testing so far! I'll play with it more.

if we should allow to map via Callable[[Iterable[CQObject]], Sequence[CQObject]] too.

Does this mean something like the following works without an explicit call to compound:

import cadquery as cq
from cadquery.occ_impl.shapes import *


def mycallback(obj):
    if obj.Center().x > 0:
        # return compound([obj.moved((0, -2, 0)), obj.moved((0, 2, 0))])
        return [obj.moved((0, -2, 0)), obj.moved((0, 2, 0))]
    else:
        return obj


r = cq.Workplane().rarray(20, 20, 2, 2).box(1, 1, 1, combine=False).map(mycallback)

@adam-urbanczyk
Copy link
Member

if we should allow to map via Callable[[Iterable[CQObject]], Sequence[CQObject]] too.

Does this mean something like the following works without an explicit call to compound:

This would actually imply Callable[[Iterable[CQObject]], Union[CQObject, Sequence[CQObject]] which is slightly more generic. Do you find it useful?

@lorenzncode
Copy link
Member

This would actually imply Callable[[Iterable[CQObject]], Union[CQObject, Sequence[CQObject]] which is slightly more generic. Do you find it useful?

If the more generic form can work it might be useful. It looks like there is a workaround for this case if not.

@adam-urbanczyk
Copy link
Member

In the meantime I added invoke for calling breakpoint or show inline. It can be also used to implement plugins without monkey patching.

@adam-urbanczyk adam-urbanczyk mentioned this pull request Jun 24, 2024
@adam-urbanczyk adam-urbanczyk marked this pull request as ready for review June 27, 2024 17:02
@adam-urbanczyk adam-urbanczyk changed the title PoC commit for discussion [filter/map/apply/sort/[]] filter/map/apply/sort/[]/invoke Jun 27, 2024
@adam-urbanczyk
Copy link
Member

I added some docs and I think this is ready to be merged. Any thoughts @lorenzncode @voneiden @jmwright ?

Copy link
Member

@lorenzncode lorenzncode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invoke is another very useful addition! I like the new option for extending CQ. The docs are clear.

Regarding invoke with builtin and arity 0 - my understanding is breakpoint is the primary intended use case (perhaps other special cases).

Copy link
Contributor Author

@voneiden voneiden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the slow review, I'm in 🌴 mode. 😃

cadquery/cq.py Outdated Show resolved Hide resolved
cadquery/cq.py Outdated
Comment on lines 4490 to 4506
if isbuiltin(f):
arity = 0 # assume 0 arity for builtins; they cannot be introspected
else:
arity = f.__code__.co_argcount # NB: this is not understood by mypy

rv = self

if arity == 0:
f() # type: ignore
elif arity == 1:
res = f(self) # type: ignore
if res is not None:
rv = res
else:
raise ValueError("Provided function {f} accepts too many arguemnts")

return rv
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not requesting this to be changed, but I think something like this would be a bit more mundane...?

Suggested change
if isbuiltin(f):
arity = 0 # assume 0 arity for builtins; they cannot be introspected
else:
arity = f.__code__.co_argcount # NB: this is not understood by mypy
rv = self
if arity == 0:
f() # type: ignore
elif arity == 1:
res = f(self) # type: ignore
if res is not None:
rv = res
else:
raise ValueError("Provided function {f} accepts too many arguemnts")
return rv
rv = self
try:
res = f(self) # type: ignore
except TypeError:
res = f() # type: ignore
if res is not None:
rv = res
return rv

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, but I find mine more clear. Also it has slightly different semantics, f could throw for other reason than wrong number of parameters.

cadquery/cq.py Outdated Show resolved Hide resolved
cadquery/cq.py Outdated Show resolved Hide resolved
doc/extending.rst Outdated Show resolved Hide resolved
Copy link
Member

@jmwright jmwright left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Thanks @voneiden !

adam-urbanczyk and others added 2 commits July 3, 2024 18:22
Co-authored-by: Matti Eiden <[email protected]>
Co-authored-by: Matti Eiden <[email protected]>
@adam-urbanczyk
Copy link
Member

Merging, thanks @voneiden !

@adam-urbanczyk adam-urbanczyk merged commit 9ee703d into CadQuery:master Jul 3, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants