Skip to content

Commit e0548d9

Browse files
authored
Merge pull request #1032 from compas-dev/fixes-assembly
Small fixes/improvements to Assembly
2 parents 44d5f07 + bfe7f3b commit e0548d9

File tree

6 files changed

+111
-33
lines changed

6 files changed

+111
-33
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
* Added `Mesh.to_lines` method and tests.
1818
* Added `Data.guid` to JSON serialization.
1919
* Added `Data.guid` to pickle state.
20+
* Added `Assembly.find_by_key` to locate parts by key.
2021

2122
### Changed
2223

2324
* Set `jinja >= 3.0` to dev dependencies to fix docs build error.
2425
* Fixed removing of collections for `compas_plotters`.
2526
* Fixed bug in `compas.robots.Configuration`.
27+
* Rebuild part index after deserialization in `Assembly`.
2628

2729
### Removed
2830

src/compas/datastructures/__init__.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@
2323
Part
2424
2525
26+
Exceptions
27+
==========
28+
29+
.. autosummary::
30+
:toctree: generated/
31+
:nosignatures:
32+
33+
AssemblyError
34+
FeatureError
35+
36+
2637
Functions
2738
=========
2839
@@ -296,7 +307,9 @@
296307

297308
from .assembly import (
298309
Assembly,
299-
Part
310+
Part,
311+
AssemblyError,
312+
FeatureError,
300313
)
301314

302315
if not compas.IPY:
@@ -439,6 +452,8 @@
439452
# Assemblies
440453
'Assembly',
441454
'Part',
455+
'AssemblyError',
456+
'FeatureError',
442457
]
443458

444459
if not compas.IPY:
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
from __future__ import print_function
21
from __future__ import absolute_import
32
from __future__ import division
3+
from __future__ import print_function
44

5-
from .assembly import Assembly # noqa: F401 F403
6-
from .part import Part # noqa: F401 F403
5+
from .exceptions import AssemblyError # noqa: F401
6+
from .exceptions import FeatureError # noqa: F401
7+
from .assembly import Assembly # noqa: F401
8+
from .part import Part # noqa: F401

src/compas/datastructures/assembly/assembly.py

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class Assembly(Datastructure):
3030

3131
def __init__(self, name=None, **kwargs):
3232
super(Assembly, self).__init__()
33-
self.attributes = {'name': name or 'Assembly'}
33+
self.attributes = {"name": name or "Assembly"}
3434
self.attributes.update(kwargs)
3535
self.graph = Graph()
3636
self._parts = {}
@@ -42,39 +42,43 @@ def __init__(self, name=None, **kwargs):
4242
@property
4343
def DATASCHEMA(self):
4444
import schema
45-
return schema.Schema({
46-
"attributes": dict,
47-
"graph": Graph,
48-
})
45+
46+
return schema.Schema(
47+
{
48+
"attributes": dict,
49+
"graph": Graph,
50+
}
51+
)
4952

5053
@property
5154
def JSONSCHEMANAME(self):
52-
return 'assembly'
55+
return "assembly"
5356

5457
@property
5558
def data(self):
5659
data = {
57-
'attributes': self.attributes,
58-
'graph': self.graph.data,
60+
"attributes": self.attributes,
61+
"graph": self.graph.data,
5962
}
6063
return data
6164

6265
@data.setter
6366
def data(self, data):
64-
self.attributes.update(data['attributes'] or {})
65-
self.graph.data = data['graph']
67+
self.attributes.update(data["attributes"] or {})
68+
self.graph.data = data["graph"]
69+
self._parts = {part.guid: part.key for part in self.parts()}
6670

6771
# ==========================================================================
6872
# properties
6973
# ==========================================================================
7074

7175
@property
7276
def name(self):
73-
return self.attributes.get('name') or self.__class__.__name__
77+
return self.attributes.get("name") or self.__class__.__name__
7478

7579
@name.setter
7680
def name(self, value):
77-
self.attributes['name'] = value
81+
self.attributes["name"] = value
7882

7983
# ==========================================================================
8084
# customization
@@ -114,10 +118,10 @@ def add_part(self, part, key=None, **kwargs):
114118
115119
"""
116120
if part.guid in self._parts:
117-
raise AssemblyError('Part already added to the assembly')
121+
raise AssemblyError("Part already added to the assembly")
118122
key = self.graph.add_node(key=key, part=part, **kwargs)
119123
part.key = key
120-
self._parts[part.guid] = part
124+
self._parts[part.guid] = part.key
121125
return key
122126

123127
def add_connection(self, a, b, **kwargs):
@@ -143,10 +147,11 @@ def add_connection(self, a, b, **kwargs):
143147
If `a` and/or `b` are not in the assembly.
144148
145149
"""
150+
error_msg = "Both parts have to be added to the assembly before a connection can be created."
146151
if a.key is None or b.key is None:
147-
raise AssemblyError('Both parts have to be added to the assembly before a connection can be created.')
152+
raise AssemblyError(error_msg)
148153
if not self.graph.has_node(a.key) or not self.graph.has_node(b.key):
149-
raise AssemblyError('Both parts have to be added to the assembly before a connection can be created.')
154+
raise AssemblyError(error_msg)
150155
return self.graph.add_edge(a.key, b.key, **kwargs)
151156

152157
def parts(self):
@@ -159,7 +164,7 @@ def parts(self):
159164
160165
"""
161166
for node in self.graph.nodes():
162-
yield self.graph.node_attribute(node, 'part')
167+
yield self.graph.node_attribute(node, "part")
163168

164169
def connections(self, data=False):
165170
"""Iterate over the connections between the parts.
@@ -194,4 +199,29 @@ def find(self, guid):
194199
or None if the part can't be found.
195200
196201
"""
197-
return self._parts.get(guid)
202+
key = self._parts.get(guid)
203+
204+
if key is None:
205+
return None
206+
207+
return self.graph.node_attribute(key, "part")
208+
209+
def find_by_key(self, key):
210+
"""Find a part in the assembly by its key.
211+
212+
Parameters
213+
----------
214+
key : int | str, optional
215+
The identifier of the part in the assembly.
216+
217+
Returns
218+
-------
219+
:class:`~compas.datastructures.Part` | None
220+
The identified part,
221+
or None if the part can't be found.
222+
223+
"""
224+
if key not in self.graph.node:
225+
return None
226+
227+
return self.graph.node_attribute(key, "part")
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
21
class AssemblyError(Exception):
32
pass
43

54

6-
class FrameError(Exception):
7-
pass
8-
9-
105
class FeatureError(Exception):
116
pass

tests/compas/datastructures/test_assembly.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
from compas.datastructures import Assembly, assembly
1+
import pytest
2+
3+
from compas.datastructures import Assembly
4+
from compas.datastructures import AssemblyError
25
from compas.datastructures import Part
36

47

58
def test_init():
6-
assembly = Assembly(name='abc')
7-
assert assembly.name == 'abc'
9+
assembly = Assembly(name="abc")
10+
assert assembly.name == "abc"
811

9-
assembly = Assembly(attr1='value', attr2=3.14)
10-
assert assembly.attributes['attr1'] == 'value'
11-
assert assembly.attributes['attr2'] == 3.14
12+
assembly = Assembly(attr1="value", attr2=3.14)
13+
assert assembly.attributes["attr1"] == "value"
14+
assert assembly.attributes["attr2"] == 3.14
1215

1316

1417
def test_add_parts():
@@ -20,6 +23,16 @@ def test_add_parts():
2023
assert len(list(assembly.parts())) == 3
2124

2225

26+
def test_add_duplicate_parts():
27+
assembly = Assembly()
28+
29+
part = Part()
30+
assembly.add_part(part)
31+
32+
with pytest.raises(AssemblyError):
33+
assembly.add_part(part)
34+
35+
2336
def test_add_connections():
2437
assembly = Assembly()
2538
parts = [Part() for i in range(3)]
@@ -41,3 +54,24 @@ def test_find():
4154

4255
assembly.add_part(part)
4356
assert assembly.find(part.guid) == part
57+
58+
59+
def test_find_by_key():
60+
assembly = Assembly()
61+
part = Part()
62+
assembly.add_part(part, key=2)
63+
assert assembly.find_by_key(2) == part
64+
65+
part = Part()
66+
assembly.add_part(part, key="6")
67+
assert assembly.find_by_key("6") == part
68+
69+
assert assembly.find_by_key("100") is None
70+
71+
72+
def test_find_by_key_after_deserialization():
73+
assembly = Assembly()
74+
part = Part()
75+
assembly.add_part(part, key=2)
76+
assembly = Assembly.from_data(assembly.to_data())
77+
assert assembly.find_by_key(2) == part

0 commit comments

Comments
 (0)