Created
September 12, 2017 10:35
-
-
Save fragmuffin/733ff68409f96f2d67ad68981cad362b to your computer and use it in GitHub Desktop.
`cadquery` CSG Operation Registration experiment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
import six | |
from collections import defaultdict | |
# ================= LIBRARY CODE ================= | |
# ---- Registration | |
registered_operations = defaultdict(dict) | |
def register(obj_type, name, keys=('result',)): | |
assert issubclass(obj_type, ObjectType), "bad object type: %r" % obj_type | |
assert isinstance(name, six.string_types), "bad name: %r" % name | |
assert 'result' in keys, "keys parameter must contain 'result'" | |
def inner(cls): | |
assert issubclass(cls, Operation), "operation classes must inherit from Operation" | |
assert name not in registered_operations, "duplicate operation name '%s': %r:%r" % ( | |
name, cls, registered_operations[name] | |
) | |
registered_operations[obj_type][name] = cls | |
# option to change class before it's returned. | |
# futureproofing: | |
# good if you want to make class changes in future versions of cadquery | |
# Get keys from inherited classes | |
inherited_keys = set() | |
for parent_class in cls.__bases__: | |
inherited_keys |= getattr(parent_class, 'key', set()) | |
cls.keys = inherited_keys | set(keys) | |
return cls | |
return inner | |
# ---- Objects (shapes, edges, faces, etc) | |
class ObjectType(object): | |
def __getattr__(self, key): | |
if key in registered_operations[self.__class__]: | |
op = registered_operations[self.__class__][key](self) | |
return op | |
if key in self.__dict__: | |
return self.__dict__[key] | |
raise AttributeError("'{cls}' object has no attribute '{key}'".format( | |
cls=self.__class__.__name__, | |
key=key | |
)) | |
class Face(ObjectType): | |
pass | |
class Shape(ObjectType): | |
def __init__(self, x=0): | |
self.x = x | |
# ---- Operation (prototype) | |
class Operation(object): | |
verify_evaluate_result = True # can be switched off | |
def __init__(self, obj): | |
self.obj = obj | |
self.requested = set() | |
def assert_eval_result(self, result): | |
if not isinstance(result, dict): | |
raise ValueError( | |
"operation {!r}.evaluate(...) ".format(type(self)) + \ | |
"must return a dict, not {!r}".format(type(result)) | |
) | |
for k in self.keys: | |
if k not in result: | |
raise ValueError( | |
"operation {!r}.evaluate(...) ".format(type(self)) + \ | |
"did not create a '{}' key".format(k) | |
) | |
def __call__(self, *largs, **kwargs): | |
# pop off list of requested return values | |
ret = kwargs.pop('ret', ('result',)) | |
if not isinstance(ret, (tuple, list)): | |
raise ValueError("'ret' parameter must be a tuple, got a {!r} instead".format(type(ret))) | |
self.requested = ret | |
# run evaluate() | |
eval_result = self.evaluate(*largs, **kwargs) | |
# verify result format | |
if self.verify_evaluate_result: | |
self.assert_eval_result(eval_result) | |
if len(self.requested) == 1: | |
return eval_result[iter(self.requested).next()] | |
else: | |
return tuple([ | |
eval_result[k] | |
for k in iter(self.requested) | |
]) | |
return eval_result | |
def evaluate(self, *largs, **kwargs): | |
raise NotImplemented("evaluate not overridden by %r" % self.__class__) | |
# ================= USER'S LIBRARY CODE ================= | |
@register( | |
Shape, 'do_stuff', | |
keys=( | |
'result', # default result | |
'modified', # | |
'created', | |
), | |
) | |
class DoStuff(Operation): | |
def evaluate(self, value, another_val=5): | |
val = { # setting defaults | |
'result': None, | |
'modified': None, | |
'created': None, | |
} | |
val['result'] = self.obj.x + value | |
# Modified : only processed if requested | |
if 'modified' in self.requested: | |
print("generating 'modified' return value") | |
val['modified'] = [value] * 5 | |
# Created : only processed if requested | |
if 'created' in self.requested: | |
print("generating 'created' return value") | |
val['created'] = ''.join(str(x) for x in range(min(another_val, 10))) | |
# self.obj will always be a Shape instance | |
print("doing stuff to %r, with (value=%s, another_val=%s)" % ( | |
self.obj, value, another_val | |
)) | |
return val | |
# ================= USER'S PARAMETRIC MODELING CODE ================= | |
s = Shape(x=20) | |
# Run 'do_stuff' with varied returns | |
print("\n--- 1 : ") | |
val = s.do_stuff(9001) | |
print("val = " + str(val)) | |
print("\n--- 2") | |
val = s.do_stuff(8, ret=('result', 'modified')) | |
print("val = " + str(val)) | |
print("\n--- 3 : 'created', 'result' isn't mandatory") | |
val = s.do_stuff(7, another_val=3, ret=('created', )) | |
print("val = " + str(val)) | |
print("\n--- 4 : 'result' doesn't have to be first") | |
val = s.do_stuff(7, ret=('created', 'result'), another_val=300) | |
print("val = " + str(val)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
output