Skip to content

Instantly share code, notes, and snippets.

@fragmuffin
Created September 12, 2017 10:35
Show Gist options
  • Save fragmuffin/733ff68409f96f2d67ad68981cad362b to your computer and use it in GitHub Desktop.
Save fragmuffin/733ff68409f96f2d67ad68981cad362b to your computer and use it in GitHub Desktop.
`cadquery` CSG Operation Registration experiment
#!/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))
@fragmuffin
Copy link
Author

output

--- 1 : 
doing stuff to <__main__.Shape object at 0x7f61d56c9450>, with (value=9001, another_val=5)
val = 9021

--- 2
generating 'modified' return value
doing stuff to <__main__.Shape object at 0x7f61d56c9450>, with (value=8, another_val=5)
val = (28, [8, 8, 8, 8, 8])

--- 3 : 'created', 'result' isn't mandatory
generating 'created' return value
doing stuff to <__main__.Shape object at 0x7f61d56c9450>, with (value=7, another_val=3)
val = 012

--- 4 : 'result' doesn't have to be first
generating 'created' return value
doing stuff to <__main__.Shape object at 0x7f61d56c9450>, with (value=7, another_val=300)
val = ('0123456789', 27)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment