cadquery-freecad-module/ThirdParty/cqparts/display/environment.py
2018-12-29 06:10:03 -05:00

181 lines
5.8 KiB
Python

__all__ = [
'display_environments',
'map_environment',
'DisplayEnvironment',
]
import logging
log = logging.getLogger(__name__)
display_environments = []
def map_environment(**kwargs):
"""
Decorator to map a DisplayEnvironment for displaying components.
The decorated environment will be chosen if its condition is ``True``, and
its order is the smallest.
:param add_to: if set to ``globals()``, display environment's constructor
may reference its own type.
:type add_to: :class:`dict`
Any additional named parameters will be passed to the constructor of
the decorated DisplayEnvironment.
See :class:`DisplayEnvironment` for example usage.
**NameError on importing**
The following code::
@map_environment(
name='abc', order=10, condition=lambda: True,
)
class SomeDisplayEnv(DisplayEnvironment):
def __init__(self, *args, **kwargs):
super(SomeDisplayEnv, self).__init__(*args, **kwargs)
Will raise the Exception::
NameError: global name 'SomeDisplayEnv' is not defined
Because this ``map_environment`` decorator attempts to instantiate
this class before it's returned to populate the ``global()`` dict.
To cicrumvent this problem, set ``add_to`` to ``globals()``::
@map_environment(
name='abc', order=10, condition=lambda: True,
add_to=globals(),
)
class SomeDisplayEnv(DisplayEnvironment):
... as above
"""
def inner(cls):
global display_environments
assert issubclass(cls, DisplayEnvironment), "can only map DisplayEnvironment classes"
# Add class to it's local globals() so constructor can reference
# its own type
add_to = kwargs.pop('add_to', {})
add_to[cls.__name__] = cls
# Create display environment
disp_env = cls(**kwargs)
# is already mappped?
try:
i = display_environments.index(disp_env) # raises ValueError
# report duplicate
raise RuntimeError(
("environment %r already mapped, " % display_environments[i]) +
("can't map duplicate %r" % disp_env)
)
except ValueError:
pass # as expected
# map class
display_environments = sorted(display_environments + [disp_env])
return cls
return inner
class DisplayEnvironment(object):
def __init__(self, name=None, order=0, condition=lambda: True):
self.name = name
self.order = order
self.condition = condition
def __repr__(self):
return "<{cls}: {name}, {order}>".format(
cls=type(self).__name__,
name=self.name,
order=self.order,
)
def __lt__(self, other): # sort only uses __lt__
return self.order < other.order
def __eq__(self, other):
return self.name == other.name
def display(self, *args, **kwargs):
return self.display_callback(*args, **kwargs)
def display_callback(self, *args, **kwargs):
"""
Display given component in this environment.
.. note::
To be overridden by inheriting classes
An example of a introducing a custom display environment.
.. doctest::
import cqparts
from cqparts.display.environment import DisplayEnvironment, map_environment
def is_text_env():
# function that returns True if it's run in the
# desired environment.
import sys
# Python 2.x
if sys.version_info[0] == 2:
return isinstance(sys.stdout, file)
# Python 3.x
import io
return isinstance(sys.stdout, io.TextIOWrapper)
@map_environment(
name="text",
order=0, # force display to be first priority
condition=is_text_env,
)
class TextDisplay(DisplayEnvironment):
def display_callback(self, component, **kwargs):
# Print component details to STDOUT
if isinstance(component, cqparts.Assembly):
sys.stdout.write(component.tree_str(add_repr=True))
else: # assumed to be a cqparts.Part
sys.stdout.write("%r\\n" % (component))
``is_text_env()`` checks if there's a valid ``sys.stdout`` to write to,
``TextDisplay`` defines how to display any given component,
and the ``@map_environment`` decorator adds the display paired with
its environment test function.
When using :meth:`display() <cqparts.display.display>`, this display
will be used if ``is_text_env()`` returns ``True``, and no previously
mapped environment with a smaller ``order`` tested ``True``:
.. doctest::
# create component to display
from cqparts_misc.basic.primatives import Cube
cube = Cube()
# display component
from cqparts.display import display
display(cube)
The ``display_callback`` will be called via
:meth:`display() <DisplayEnvironment.display>`. So to call this
display method directly:
.. doctest::
TextDisplay().display(cube)
:raises: NotImplementedError if not overridden
"""
if type(self) is DisplayEnvironment:
raise RuntimeError(
("%r is not a functional display environment, " % (type(self))) +
"it's meant to be inherited by an implemented environment"
)
raise NotImplementedError(
"display_callback function not overridden by %r" % (type(self))
)