Source code for skppy.parser.definitions

# SPDX-License-Identifier: MIT
"""Decode component definitions and their independent entity scopes.

The modern ``model.dat`` definitions section contains identity and behavior
metadata followed by a nested entities block. :func:`parse_definitions`
returns :class:`~skppy.data_structure.entities.ComponentDefinition` objects
whose vertex and entity IDs are local to each definition. Normal applications
should use :func:`skppy.load`; this module is the bounded section decoder used
by the full model pipeline.
"""

from __future__ import annotations

from typing import List

from ..data_structure.model_metadata import AttributeDictionary
from .attributes import parse_entity_attribute_dictionaries
from .entities import parse_entities
from ..data_structure.entities import ComponentDefinition, Entities
from .tlv import (
    TlvTag,
    find_child,
    iter_records,
    read_bool,
    read_compact_int,
    read_guid,
    read_utf8,
)


[docs] def parse_definitions( definitions_container_payload: bytes, *, attribute_dictionaries_by_object_id: dict[int, list[AttributeDictionary]] | None = None, ) -> List[ComponentDefinition]: """Parse the component definitions section (tag 0x01F9) of model.dat. Parameters ---------- definitions_container_payload : bytes Raw payload of the DEFINITIONS_CONTAINER (0x1770) TLV record. Returns ------- list of ComponentDefinition Fully parsed component definitions, each with its own Entities. """ list_p = find_child(definitions_container_payload, TlvTag.DEFINITIONS_LIST) if not list_p: return [] definitions: List[ComponentDefinition] = [] for tag, def_p in iter_records(list_p): if tag != TlvTag.DEFINITION_RECORD: continue defn = _parse_definition(def_p) definitions.append(defn) attributes = _parse_definition_attributes(def_p) if attributes and attribute_dictionaries_by_object_id is not None: attribute_dictionaries_by_object_id[defn.id] = attributes return definitions
def _parse_definition_attributes(payload: bytes) -> list[AttributeDictionary]: """Read dictionaries from the entity base that owns a definition.""" entities = find_child(payload, TlvTag.ENTITIES) entity_base = find_child(entities, TlvTag.ENTITY_BASE) if entities is not None else None return parse_entity_attribute_dictionaries(entity_base) if entity_base is not None else [] def _parse_definition(payload: bytes) -> ComponentDefinition: definition = ComponentDefinition() # - ID: entity base lives inside TlvTag.ENTITIES (0x1388), not directly in # the definition record. Path: 0x1388 -> 0x07D0 -> 0x05DC -> 0x05DE - entities_p = find_child(payload, TlvTag.ENTITIES) if entities_p: eb = find_child(entities_p, TlvTag.ENTITY_BASE) if eb: id_wrap = find_child(eb, TlvTag.ID_WRAPPER) if id_wrap: id_val = find_child(id_wrap, TlvTag.ID_VALUE) if id_val: definition.id = read_compact_int(id_val) guid_p = find_child(payload, TlvTag.DEFINITION_GUID) definition.guid = read_guid(guid_p) if guid_p else b"\x00" * 16 name_p = find_child(payload, TlvTag.DEFINITION_NAME) definition.name = read_utf8(name_p) if name_p else f"definition_{definition.id}" desc_p = find_child(payload, TlvTag.DEFINITION_DESC) definition.description = read_utf8(desc_p) if desc_p else "" # - Definition extras - loaded_from_p = find_child(payload, TlvTag.DEFINITION_LOADED_FROM) definition.loaded_from = read_utf8(loaded_from_p) if loaded_from_p else "" ts_p = find_child(payload, TlvTag.DEFINITION_TIMESTAMP) definition.timestamp = read_compact_int(ts_p) if ts_p else 0 mod_p = find_child(payload, TlvTag.DEFINITION_MODIFIED) definition.modified = read_bool(mod_p) if mod_p else False type_p = find_child(payload, TlvTag.DEFINITION_TYPE) definition.definition_type = read_compact_int(type_p) if type_p else 0 packed_p = find_child(payload, TlvTag.DEFINITION_PACKED_PAYLOAD) definition.packed_payload = packed_p if packed_p else None # - Component behavior (0x1B58) - behavior_p = find_child(payload, TlvTag.COMPONENT_BEHAVIOR_BLOCK) if behavior_p: sm = find_child(behavior_p, TlvTag.BEHAVIOR_SNAP_MODE) definition.behavior_snap_mode = read_compact_int(sm) if sm else 0 nsm = find_child(behavior_p, TlvTag.BEHAVIOR_NO_SCALE_MASK) definition.behavior_no_scale_mask = read_compact_int(nsm) if nsm else 0 se = find_child(behavior_p, TlvTag.BEHAVIOR_SNAP_ENABLED) definition.behavior_snap_enabled = read_bool(se) if se else False co = find_child(behavior_p, TlvTag.BEHAVIOR_CUTS_OPENING) definition.behavior_cuts_opening = read_bool(co) if co else False afc = find_child(behavior_p, TlvTag.BEHAVIOR_ALWAYS_FACE_CAMERA) definition.behavior_always_face_camera = read_bool(afc) if afc else False sfs = find_child(behavior_p, TlvTag.BEHAVIOR_SHADOWS_FACE_SUN) definition.behavior_shadows_face_sun = read_bool(sfs) if sfs else False # - Entities (0x1388 nested inside the definition) - definition.entities = parse_entities(entities_p) if entities_p else Entities() return definition