196 lines
6.0 KiB
Python
196 lines
6.0 KiB
Python
from collections import defaultdict
|
|
|
|
from .. import Component
|
|
|
|
# ----------------- Exporting -----------------
|
|
class Exporter(object):
|
|
def __init__(self, obj):
|
|
self.obj = obj
|
|
|
|
def __call__(self):
|
|
raise NotImplementedError("%r exporter is not callable" % (type(self)))
|
|
|
|
|
|
exporter_index = defaultdict(dict)
|
|
# format: {<name>: {<class_base>: <exporter_class>}}
|
|
|
|
def register_exporter(name, base_class):
|
|
"""
|
|
Register an exporter to use for a :class:`Part <cqparts.Part>`,
|
|
:class:`Assembly <cqparts.Assembly>`, or both
|
|
(with :class:`Component <cqparts.Component>`).
|
|
|
|
Registration is necessary to use with
|
|
:meth:`Component.exporter() <cqparts.Component.exporter>`.
|
|
|
|
:param name: name (or 'key') of exporter
|
|
:type name: :class:`str`
|
|
:param base_class: class of :class:`Component <cqparts.Component>` to export
|
|
:type base_class: :class:`type`
|
|
|
|
.. doctest::
|
|
|
|
>>> from cqparts import Part
|
|
>>> from cqparts.codec import Exporter, register_exporter
|
|
|
|
>>> @register_exporter('my_type', Part)
|
|
... class MyExporter(Exporter):
|
|
... def __call__(self, filename='out.mytype'):
|
|
... print("export %r to %s" % (self.obj, filename))
|
|
|
|
>>> from cqparts_misc.basic.primatives import Sphere
|
|
>>> thing = Sphere(radius=5)
|
|
>>> thing.exporter('my_type')('some-file.mytype')
|
|
export <Sphere: radius=5.0> to some-file.mytype
|
|
|
|
"""
|
|
# Verify params
|
|
if not isinstance(name, str) or (not name):
|
|
raise TypeError("invalid name: %r" % name)
|
|
if not issubclass(base_class, Component):
|
|
raise TypeError("invalid base_class: %r, must be a %r subclass" % (base_class, Component))
|
|
|
|
def decorator(cls):
|
|
# --- Verify
|
|
# Can only be registered once
|
|
if base_class in exporter_index[name]:
|
|
raise TypeError("'%s' exporter type %r has already been registered" % (
|
|
name, base_class
|
|
))
|
|
|
|
# Verify class hierarchy will not conflict
|
|
# (so you can't have an exporter for a Component, and a Part. must be
|
|
# an Assembly, and a Part, respectively)
|
|
for key in exporter_index[name].keys():
|
|
if issubclass(key, base_class) or issubclass(base_class, key):
|
|
raise TypeError("'%s' exporter type %r is in conflict with %r" % (
|
|
name, base_class, key,
|
|
))
|
|
|
|
# --- Index
|
|
exporter_index[name][base_class] = cls
|
|
|
|
return cls
|
|
return decorator
|
|
|
|
|
|
def get_exporter(obj, name):
|
|
"""
|
|
Get an exporter for the
|
|
|
|
:param obj: object to export
|
|
:type obj: :class:`Component <cqparts.Component>`
|
|
:param name: registered name of exporter
|
|
:type name: :class:`str`
|
|
:return: an exporter instance of the given type
|
|
:rtype: :class:`Exporter`
|
|
:raises TypeError: if exporter cannot be found
|
|
"""
|
|
if name not in exporter_index:
|
|
raise TypeError(
|
|
("exporter type '%s' is not registered: " % name) +
|
|
("registered types: %r" % sorted(exporter_index.keys()))
|
|
)
|
|
|
|
for base_class in exporter_index[name]:
|
|
if isinstance(obj, base_class):
|
|
return exporter_index[name][base_class](obj)
|
|
|
|
raise TypeError("exporter type '%s' for a %r is not registered" % (
|
|
name, type(obj)
|
|
))
|
|
|
|
|
|
# ----------------- Importing -----------------
|
|
class Importer(object):
|
|
def __init__(self, cls):
|
|
self.cls = cls
|
|
|
|
|
|
importer_index = defaultdict(dict)
|
|
# format: {<name>: {<class_base>: <importer_class>}}
|
|
|
|
|
|
def register_importer(name, base_class):
|
|
|
|
# Verify params
|
|
if not isinstance(name, str) or (not name):
|
|
raise TypeError("invalid name: %r" % name)
|
|
if not issubclass(base_class, Component):
|
|
raise TypeError("invalid base_class: %r, must be a %r subclass" % (base_class, Component))
|
|
|
|
def decorator(cls):
|
|
# --- Verify
|
|
# Can only be registered once
|
|
if base_class in importer_index[name]:
|
|
raise TypeError("'%s' importer type %r has already been registered" % (
|
|
name, base_class
|
|
))
|
|
|
|
# Verify class hierarchy will not conflict
|
|
# (so you can't have an importer for a Component, and a Part. must be
|
|
# an Assembly, and a Part, respectively)
|
|
for key in importer_index[name].keys():
|
|
if issubclass(key, base_class) or issubclass(base_class, key):
|
|
raise TypeError("'%s' importer type %r is in conflict with %r" % (
|
|
name, base_class, key,
|
|
))
|
|
|
|
# --- Index
|
|
importer_index[name][base_class] = cls
|
|
|
|
return cls
|
|
return decorator
|
|
|
|
|
|
def get_importer(cls, name):
|
|
"""
|
|
Get an importer for the given registered type.
|
|
|
|
:param cls: class to import
|
|
:type cls: :class:`type`
|
|
:param name: registered name of importer
|
|
:type name: :class:`str`
|
|
:return: an importer instance of the given type
|
|
:rtype: :class:`Importer`
|
|
:raises TypeError: if importer cannot be found
|
|
"""
|
|
if name not in importer_index:
|
|
raise TypeError(
|
|
("importer type '%s' is not registered: " % name) +
|
|
("registered types: %r" % sorted(importer_index.keys()))
|
|
)
|
|
|
|
for base_class in importer_index[name]:
|
|
if issubclass(cls, base_class):
|
|
return importer_index[name][base_class](cls)
|
|
|
|
raise TypeError("importer type '%s' for a %r is not registered" % (
|
|
name, cls
|
|
))
|
|
|
|
|
|
# ----------------- housekeeping -----------------
|
|
|
|
__all__ = [
|
|
# Tools
|
|
'Exporter', 'register_exporter', 'get_exporter',
|
|
'Importer', 'register_importer', 'get_importer',
|
|
|
|
# Codecs
|
|
'AMFExporter',
|
|
'GLTFExporter',
|
|
'STEPExporter', 'STEPImporter',
|
|
'STLExporter',
|
|
'SVGExporter',
|
|
'ThreejsJSONExporter', 'ThreejsJSONAssemblyExporter',
|
|
|
|
]
|
|
|
|
from .amf import AMFExporter
|
|
from .gltf import GLTFExporter
|
|
from .step import STEPExporter, STEPPartImporter, STEPAssemblyImporter
|
|
from .stl import STLExporter
|
|
from .svg import SVGExporter
|
|
from .threejs_json import ThreejsJSONExporter, ThreejsJSONAssemblyExporter
|