Skip to content

Commit cbe6bfa

Browse files
authored
Merge pull request #945 from compas-dev/artist-context
Register all available artists and load based on context.
2 parents eb6b5dc + aad90e1 commit cbe6bfa

File tree

20 files changed

+187
-311
lines changed

20 files changed

+187
-311
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
* Added `compas_rhino.DEFAULT_VERSION`.
1313
* Added `clean` option to `compas_rhino.install` to remove existing symlinks if they cannot be imported from the current environment.
14+
* Added `compas.is_grasshopper`.
15+
* Added `compas.GH`.
16+
* Added `compas.artists.Artist.CONTEXT`.
17+
* Added `compas.artists.Artist.AVAILABLE_CONTEXTS`.
18+
* Added `compas.artists.artist.register_artists` pluggable.
1419

1520
### Changed
1621

@@ -19,15 +24,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1924
* Fixed error in parameter list of `compas_rhino.geometry.curves.new_nurbscurve`.
2025
* Fixed error in parameter list of `compas_rhino.geometry.curves.new_nurbscurve_from_interpolation`.
2126
* Fixed error in parameter list of `compas_rhino.geometry.curves.new_nurbscurve_from_step`.
22-
2327
* Changed `compas_rhino.install` to remove broken symlinks.
2428
* Changed `compas_rhino.install` to reinstall broken symlinks if they can be imported from the current environment.
2529
* Changed `compas_rhino.uninstall` to remove broken symlinks.
2630
* Changed `compas_rhino.install_plugin` to remove broken symlinks.
2731
* Changed default Rhino version for installation to `7.0`.
32+
* Fixed bug in `compas_ghpython` related to importing `Grasshopper` prematurely.
33+
* Changed `compas.artists.Artist.ITAM_ARTIST` to context-based dict.
2834

2935
### Removed
3036

37+
* Removed `compas.artists.artist.new_artist` pluggable.
38+
3139

3240
## [1.12.2] 2021-11-30
3341

src/compas/__init__.py

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -21,57 +21,6 @@
2121
compas.topology
2222
compas.utilities
2323
24-
Utility functions
25-
=================
26-
27-
JSON handling
28-
-------------
29-
30-
.. autofunction:: json_dump
31-
32-
.. autofunction:: json_dumps
33-
34-
.. autofunction:: json_load
35-
36-
.. autofunction:: json_loads
37-
38-
Precision
39-
---------
40-
41-
.. autofunction:: set_precision
42-
43-
.. autodata:: PRECISION
44-
45-
Execution context
46-
-----------------
47-
48-
.. autofunction:: is_windows
49-
50-
.. autofunction:: is_linux
51-
52-
.. autofunction:: is_osx
53-
54-
.. autofunction:: is_mono
55-
56-
.. autofunction:: is_ironpython
57-
58-
.. autofunction:: is_rhino
59-
60-
.. autofunction:: is_blender
61-
62-
.. autodata:: WINDOWS
63-
64-
.. autodata:: LINUX
65-
66-
.. autodata:: OSX
67-
68-
.. autodata:: MONO
69-
70-
.. autodata:: IPY
71-
72-
.. autodata:: RHINO
73-
74-
.. autodata:: BLENDER
7524
7625
"""
7726
from __future__ import print_function
@@ -82,7 +31,7 @@
8231
from distutils.version import LooseVersion
8332

8433
import compas._os
85-
from compas._os import is_windows, is_linux, is_osx, is_mono, is_ironpython, is_rhino, is_blender
34+
from compas._os import is_windows, is_linux, is_osx, is_mono, is_ironpython, is_rhino, is_blender, is_grasshopper # noqa: F401
8635
from compas.data import json_dump, json_dumps, json_load, json_loads
8736

8837

src/compas/_os.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ class NotADirectoryError(Exception):
3737
'is_mono',
3838
'is_ironpython',
3939
'is_rhino',
40-
'is_blender'
40+
'is_blender',
41+
'is_grasshopper'
4142
]
4243

4344

@@ -100,17 +101,24 @@ def is_rhino():
100101
import Rhino # noqa : F401
101102
except ImportError:
102103
return False
103-
else:
104-
return True
104+
return True
105+
106+
107+
def is_grasshopper():
108+
try:
109+
import Rhino
110+
import scriptcontext
111+
except ImportError:
112+
return False
113+
return not isinstance(scriptcontext.doc, Rhino.RhinoDoc)
105114

106115

107116
def is_blender():
108117
try:
109118
import bpy # noqa : F401
110119
except ImportError:
111120
return False
112-
else:
113-
return True
121+
return True
114122

115123

116124
if is_windows():

src/compas/artists/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646

4747
BaseRobotModelArtist = RobotModelArtist
4848

49+
4950
__all__ = [
5051
'DataArtistNotRegistered',
5152
'Artist',

src/compas/artists/artist.py

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44

55
import inspect
66
from abc import abstractmethod
7+
from collections import defaultdict
78

9+
import compas
810
from compas.artists import DataArtistNotRegistered
911
from compas.plugins import pluggable
12+
from compas.plugins import PluginValidator
1013

1114

1215
@pluggable(category='drawing-utils')
@@ -19,19 +22,62 @@ def redraw():
1922
raise NotImplementedError
2023

2124

22-
@pluggable(category='factories')
23-
def new_artist(cls, *args, **kwargs):
25+
@pluggable(category='factories', selector='collect_all')
26+
def register_artists():
2427
raise NotImplementedError
2528

2629

30+
def identify_context():
31+
if compas.is_grasshopper():
32+
return 'Grasshopper'
33+
if compas.is_rhino():
34+
return 'Rhino'
35+
if compas.is_blender():
36+
return 'Blender'
37+
return None
38+
39+
40+
def _get_artist_cls(data, **kwargs):
41+
if 'context' in kwargs:
42+
Artist.CONTEXT = kwargs['context']
43+
else:
44+
Artist.CONTEXT = identify_context()
45+
46+
dtype = type(data)
47+
cls = None
48+
49+
if 'artist_type' in kwargs:
50+
cls = kwargs['artist_type']
51+
else:
52+
context = Artist.ITEM_ARTIST[Artist.CONTEXT]
53+
for type_ in inspect.getmro(dtype):
54+
cls = context.get(type_, None)
55+
if cls is not None:
56+
break
57+
58+
if cls is None:
59+
raise DataArtistNotRegistered('No artist is registered for this data type: {} in this context: {}'.format(dtype, Artist.CONTEXT))
60+
61+
return cls
62+
63+
2764
class Artist(object):
2865
"""Base class for all artists.
2966
"""
3067

31-
ITEM_ARTIST = {}
68+
__ARTISTS_REGISTERED = False
69+
70+
AVAILABLE_CONTEXTS = ['Rhino', 'Grasshopper', 'Blender', 'Plotter']
71+
CONTEXT = None
72+
ITEM_ARTIST = defaultdict(dict)
3273

3374
def __new__(cls, *args, **kwargs):
34-
return new_artist(cls, *args, **kwargs)
75+
if not Artist.__ARTISTS_REGISTERED:
76+
register_artists()
77+
Artist.__ARTISTS_REGISTERED = True
78+
cls = _get_artist_cls(args[0], **kwargs)
79+
PluginValidator.ensure_implementations(cls)
80+
return super(Artist, cls).__new__(cls)
3581

3682
@staticmethod
3783
def build(item, **kwargs):
@@ -49,7 +95,7 @@ def build(item, **kwargs):
4995
An artist of the type matching the provided item according to the item-artist map ``~Artist.ITEM_ARTIST``.
5096
The map is created by registering item-artist type pairs using ``~Artist.register``.
5197
"""
52-
artist_type = Artist.ITEM_ARTIST[type(item)]
98+
artist_type = _get_artist_cls(item, **kwargs)
5399
artist = artist_type(item, **kwargs)
54100
return artist
55101

@@ -58,21 +104,6 @@ def build_as(item, artist_type, **kwargs):
58104
artist = artist_type(item, **kwargs)
59105
return artist
60106

61-
@staticmethod
62-
def get_artist_cls(data, **kwargs):
63-
dtype = type(data)
64-
cls = None
65-
if 'artist_type' in kwargs:
66-
cls = kwargs['artist_type']
67-
else:
68-
for type_ in inspect.getmro(dtype):
69-
cls = Artist.ITEM_ARTIST.get(type_)
70-
if cls is not None:
71-
break
72-
if cls is None:
73-
raise DataArtistNotRegistered('No artist is registered for this data type: {}'.format(dtype))
74-
return cls
75-
76107
@staticmethod
77108
def clear():
78109
return clear()
@@ -82,8 +113,8 @@ def redraw():
82113
return redraw()
83114

84115
@staticmethod
85-
def register(item_type, artist_type):
86-
Artist.ITEM_ARTIST[item_type] = artist_type
116+
def register(item_type, artist_type, context=None):
117+
Artist.ITEM_ARTIST[context][item_type] = artist_type
87118

88119
@abstractmethod
89120
def draw(self):

src/compas_blender/artists/__init__.py

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@
7272
import compas_blender
7373

7474
from compas.plugins import plugin
75-
from compas.plugins import PluginValidator
7675
from compas.artists import Artist
7776

7877
from compas.geometry import Box
@@ -115,6 +114,7 @@
115114

116115
@plugin(category='drawing-utils', pluggable_name='clear', requires=['bpy'])
117116
def clear_blender():
117+
print('doing it')
118118
compas_blender.clear()
119119

120120

@@ -123,41 +123,26 @@ def redraw_blender():
123123
compas_blender.redraw()
124124

125125

126-
artists_registered = False
127-
128-
129-
@plugin(category='factories', pluggable_name='new_artist', tryfirst=True, requires=['bpy'])
130-
def new_artist_blender(cls, *args, **kwargs):
131-
# "lazy registration" seems necessary to avoid item-artist pairs to be overwritten unintentionally
132-
global artists_registered
133-
134-
if not artists_registered:
135-
BlenderArtist.register(Box, BoxArtist)
136-
BlenderArtist.register(Capsule, CapsuleArtist)
137-
BlenderArtist.register(Circle, CircleArtist)
138-
BlenderArtist.register(Cone, ConeArtist)
139-
BlenderArtist.register(Cylinder, CylinderArtist)
140-
BlenderArtist.register(Frame, FrameArtist)
141-
BlenderArtist.register(Line, LineArtist)
142-
BlenderArtist.register(Mesh, MeshArtist)
143-
BlenderArtist.register(Network, NetworkArtist)
144-
BlenderArtist.register(Point, PointArtist)
145-
BlenderArtist.register(Polygon, PolygonArtist)
146-
BlenderArtist.register(Polyhedron, PolyhedronArtist)
147-
BlenderArtist.register(Polyline, PolylineArtist)
148-
BlenderArtist.register(RobotModel, RobotModelArtist)
149-
BlenderArtist.register(Sphere, SphereArtist)
150-
BlenderArtist.register(Torus, TorusArtist)
151-
BlenderArtist.register(Vector, VectorArtist)
152-
artists_registered = True
153-
154-
data = args[0]
155-
156-
cls = Artist.get_artist_cls(data, **kwargs)
157-
158-
PluginValidator.ensure_implementations(cls)
159-
160-
return super(Artist, cls).__new__(cls)
126+
@plugin(category='factories', requires=['bpy'])
127+
def register_artists():
128+
Artist.register(Box, BoxArtist, context='Blender')
129+
Artist.register(Capsule, CapsuleArtist, context='Blender')
130+
Artist.register(Circle, CircleArtist, context='Blender')
131+
Artist.register(Cone, ConeArtist, context='Blender')
132+
Artist.register(Cylinder, CylinderArtist, context='Blender')
133+
Artist.register(Frame, FrameArtist, context='Blender')
134+
Artist.register(Line, LineArtist, context='Blender')
135+
Artist.register(Mesh, MeshArtist, context='Blender')
136+
Artist.register(Network, NetworkArtist, context='Blender')
137+
Artist.register(Point, PointArtist, context='Blender')
138+
Artist.register(Polygon, PolygonArtist, context='Blender')
139+
Artist.register(Polyhedron, PolyhedronArtist, context='Blender')
140+
Artist.register(Polyline, PolylineArtist, context='Blender')
141+
Artist.register(RobotModel, RobotModelArtist, context='Blender')
142+
Artist.register(Sphere, SphereArtist, context='Blender')
143+
Artist.register(Torus, TorusArtist, context='Blender')
144+
Artist.register(Vector, VectorArtist, context='Blender')
145+
print('Blender Artists registered.')
161146

162147

163148
__all__ = [

src/compas_blender/artists/artist.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,3 @@ def collection(self, value: Union[str, bpy.types.Collection]):
3838
self._collection = compas_blender.create_collection(value)
3939
else:
4040
raise Exception('Collection must be of type `str` or `bpy.types.Collection`.')
41-
42-
def clear(self) -> None:
43-
"""Delete all objects created by the artist."""
44-
compas_blender.delete_objects(self.collection.objects)

src/compas_ghpython/__init__.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,11 @@
1616
import compas
1717
import compas_rhino
1818

19-
# TODO: I believe this should be removed, as it pulls all utilities funcs
20-
# into first-level instead of the usual second-level namespace
21-
if compas.RHINO:
22-
from .utilities import * # noqa: F401 F403
23-
24-
2519
__version__ = '1.12.2'
2620

21+
if compas.is_rhino():
22+
from .utilities import * # noqa: F401 F403
23+
2724

2825
def get_grasshopper_plugin_path(version):
2926
version = compas_rhino._check_rhino_version(version)

0 commit comments

Comments
 (0)