This module defines the base class AssetEntity
used directly or inherited for specific assets.
Asset entities are graphic element displayed on the 3D view and loaded from asset files.
It is typically used for table and robot assets.
Supported asset files are in Collada format (.dae
).
Other formats could be also supported, but not tested
(see Open Asset Import Library).
AssetEntity
Bases: QEntity
Base class for asset entities
This class inherits from Qt3DCore.QEntity
Attributes:
Name |
Type |
Description |
ready |
Signal
|
Qt signal emitted when the asset entity is ready to be used
|
asset_ready |
bool
|
True if the asset is ready
|
asset_entity |
QEntity
|
first useful QEntity in the asset tree
|
transform_component |
|
QTransform holding the asset's translation and orientation
|
Source code in cogip/entities/asset.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 | class AssetEntity(Qt3DCore.QEntity):
"""Base class for asset entities
This class inherits from [`Qt3DCore.QEntity`](https://doc.qt.io/qtforpython-6/PySide6/Qt3DCore/QEntity.html)
Attributes:
ready: Qt signal emitted when the asset entity is ready to be used
asset_ready: `True` if the asset is ready
asset_entity: first useful `QEntity` in the asset tree
transform_component: `QTransform` holding the asset's translation and orientation
"""
ready: qtSignal = qtSignal()
def __init__(self, asset_path: Path, scale: float = 1.0, parent: Qt3DCore.QEntity = None):
"""
The constructor checks the asset's file and starts loading the entity.
The entity load is asynchronous, a signal is emitted when it is done
(see [`on_loader_status_changed`][cogip.entities.asset.AssetEntity.on_loader_status_changed])
Arguments:
asset_path: path of the asset file
scale: scale to apply to the entity after load
"""
super().__init__(parent)
self.asset_ready: bool = False
self.asset_path: Path = asset_path
self.scale: float = scale
self.asset_entity: Qt3DCore.QEntity = None
self.transform_component = Qt3DCore.QTransform()
self.addComponent(self.transform_component)
if not self.asset_path.exists():
raise FileNotFoundError(f"File not found '{self.asset_path}'")
if not self.asset_path.is_file():
raise IsADirectoryError(f"'{self.asset_path}' is not a file")
self.loader = Qt3DRender.QSceneLoader(self)
self.loader.statusChanged.connect(self.on_loader_status_changed)
self.loader.setObjectName(self.asset_path.name)
self.addComponent(self.loader)
self.loader.setSource(QtCore.QUrl(f"file:{self.asset_path}"))
@qtSlot(Qt3DRender.QSceneLoader.Status)
def on_loader_status_changed(self, status: Qt3DRender.QSceneLoader.Status):
"""
When the loader has finished, clean the entity tree,
record the main `QEntity` and its `QTransform` component.
Then it generated the dot tree
(see [`generate_tree`][cogip.entities.asset.AssetEntity.generate_tree]),
run the [`post_init`][cogip.entities.asset.AssetEntity.post_init] pass,
and emit the `ready` signal.
Arguments:
status: current loader status
"""
if (
status.value != Qt3DRender.QSceneLoader.Ready.value
): # .value is workaround to a Python binding bug in PySide6 6.4.
return
self.post_init()
self.generate_tree()
self.asset_ready = True
self.ready.emit()
def generate_tree(self):
"""
Generate a tree of all entities and components starting from the main entity.
The generated file is stored next to the asset file and it has the same name
but with `.tree.dot` extension.
It is a text file written in [Graphviz](https://graphviz.org/) format.
To read this file:
```bash
sudo apt install graphviz okular
dot -Tpdf assets/robot2019.tree.dot | okular -
```
"""
tree_filename = self.asset_path.with_suffix(".tree.dot")
with tree_filename.open(mode="w") as fd:
fd.write('graph ""\n')
fd.write("{\n")
fd.write('label="Entity tree"\n')
root_node_number = 0
traverse_tree(self, root_node_number, fd)
fd.write("}\n")
def post_init(self):
"""
Post initialization method that can be overloaded.
It it executed after entity and transform components are ready.
"""
pass
|
__init__(asset_path, scale=1.0, parent=None)
The constructor checks the asset's file and starts loading the entity.
The entity load is asynchronous, a signal is emitted when it is done
(see on_loader_status_changed
)
Parameters:
Name |
Type |
Description |
Default |
asset_path |
Path
|
|
required
|
scale |
float
|
scale to apply to the entity after load
|
1.0
|
Source code in cogip/entities/asset.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 | def __init__(self, asset_path: Path, scale: float = 1.0, parent: Qt3DCore.QEntity = None):
"""
The constructor checks the asset's file and starts loading the entity.
The entity load is asynchronous, a signal is emitted when it is done
(see [`on_loader_status_changed`][cogip.entities.asset.AssetEntity.on_loader_status_changed])
Arguments:
asset_path: path of the asset file
scale: scale to apply to the entity after load
"""
super().__init__(parent)
self.asset_ready: bool = False
self.asset_path: Path = asset_path
self.scale: float = scale
self.asset_entity: Qt3DCore.QEntity = None
self.transform_component = Qt3DCore.QTransform()
self.addComponent(self.transform_component)
if not self.asset_path.exists():
raise FileNotFoundError(f"File not found '{self.asset_path}'")
if not self.asset_path.is_file():
raise IsADirectoryError(f"'{self.asset_path}' is not a file")
self.loader = Qt3DRender.QSceneLoader(self)
self.loader.statusChanged.connect(self.on_loader_status_changed)
self.loader.setObjectName(self.asset_path.name)
self.addComponent(self.loader)
self.loader.setSource(QtCore.QUrl(f"file:{self.asset_path}"))
|
generate_tree()
Generate a tree of all entities and components starting from the main entity.
The generated file is stored next to the asset file and it has the same name
but with .tree.dot
extension.
It is a text file written in Graphviz format.
To read this file:
sudo apt install graphviz okular
dot -Tpdf assets/robot2019.tree.dot | okular -
Source code in cogip/entities/asset.py
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 | def generate_tree(self):
"""
Generate a tree of all entities and components starting from the main entity.
The generated file is stored next to the asset file and it has the same name
but with `.tree.dot` extension.
It is a text file written in [Graphviz](https://graphviz.org/) format.
To read this file:
```bash
sudo apt install graphviz okular
dot -Tpdf assets/robot2019.tree.dot | okular -
```
"""
tree_filename = self.asset_path.with_suffix(".tree.dot")
with tree_filename.open(mode="w") as fd:
fd.write('graph ""\n')
fd.write("{\n")
fd.write('label="Entity tree"\n')
root_node_number = 0
traverse_tree(self, root_node_number, fd)
fd.write("}\n")
|
on_loader_status_changed(status)
When the loader has finished, clean the entity tree,
record the main QEntity
and its QTransform
component.
Then it generated the dot tree
(see generate_tree
),
run the post_init
pass,
and emit the ready
signal.
Parameters:
Name |
Type |
Description |
Default |
status |
Status
|
|
required
|
Source code in cogip/entities/asset.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 | @qtSlot(Qt3DRender.QSceneLoader.Status)
def on_loader_status_changed(self, status: Qt3DRender.QSceneLoader.Status):
"""
When the loader has finished, clean the entity tree,
record the main `QEntity` and its `QTransform` component.
Then it generated the dot tree
(see [`generate_tree`][cogip.entities.asset.AssetEntity.generate_tree]),
run the [`post_init`][cogip.entities.asset.AssetEntity.post_init] pass,
and emit the `ready` signal.
Arguments:
status: current loader status
"""
if (
status.value != Qt3DRender.QSceneLoader.Ready.value
): # .value is workaround to a Python binding bug in PySide6 6.4.
return
self.post_init()
self.generate_tree()
self.asset_ready = True
self.ready.emit()
|
post_init()
Post initialization method that can be overloaded.
It it executed after entity and transform components are ready.
Source code in cogip/entities/asset.py
| def post_init(self):
"""
Post initialization method that can be overloaded.
It it executed after entity and transform components are ready.
"""
pass
|
traverse_tree(node, next_node_nb, fd)
Recursive function traversing all child entities and write its node
and all its components to the .dot file.
Parameters:
Name |
Type |
Description |
Default |
node |
QEntity
|
|
required
|
next_node_nb |
int
|
|
required
|
fd |
TextIO
|
opened file descriptor used to write the tree
|
required
|
Return
tuple of current and next node numbers
Source code in cogip/entities/asset.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162 | def traverse_tree(node: Qt3DCore.QEntity, next_node_nb: int, fd: TextIO) -> tuple[int, int]:
"""
Recursive function traversing all child entities and write its node
and all its components to the .dot file.
Arguments:
node: entity to traverse
next_node_nb: next node number
fd: opened file descriptor used to write the tree
Return:
tuple of current and next node numbers
"""
current_node_nb = next_node_nb
next_node_nb += 1
# Insert current node in the tree
fd.write(f'n{current_node_nb:03d} [label="{node.metaObject().className()}\n{node.objectName()}"] ;\n')
# Enumerate components
for comp in node.components():
fd.write(f'n{next_node_nb:03d} [shape=box,label="{comp.metaObject().className()}\n{comp.objectName()}"] ;\n')
fd.write(f"n{current_node_nb:03d} -- n{next_node_nb:03d} [style=dotted];\n")
next_node_nb += 1
# Build tree for children
for child_node in node.children():
if isinstance(child_node, Qt3DCore.QEntity):
child_node_nb, next_node_nb = traverse_tree(child_node, next_node_nb, fd)
fd.write(f"n{current_node_nb:03d} -- n{child_node_nb:03d} ;\n")
return current_node_nb, next_node_nb
|