Skip to content

asset

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

path of the asset file

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

current loader 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
122
123
124
125
126
127
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

entity to traverse

required
next_node_nb int

next node number

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