Skip to content

XML serializer omits containerField for nodes in a non-default field (HAnim skeleton, PBR/unlit textures) #2

Description

@EUPHEMEME

A quick human note: I hit this building HAnim and PBR scenes programmatically with x3d.py — the output validates against the schema but renders wrong, so it took a while to track down. Minimal repros below; happy to help with a fix.

Summary

When a node is supplied through a typed field whose containerField differs from that node type's default containerField, X3D(...).XML() emits the child without any containerField attribute. Conformant parsers then route the child into the node's default container — the wrong field — so the node is silently dropped or misfiled. The output still passes XSD schema validation.

Environment: x3d 4.0.65.4 (pip install x3d), Python 3.12.

This affects every node type that may legally appear in more than one parent field, e.g.:

  • HAnimJoint (default children) placed in skeleton / joints
  • HAnimSegment (default children) placed in segments
  • ImageTexture (default texture) placed in baseTexture / emissiveTexture / normalTexture / … of PhysicalMaterial / UnlitMaterial

Reproduction 1a — HAnim skeleton

from x3d import x3d as X
h = X.HAnimHumanoid(DEF="H", name="h", version="2.0",
        skeleton=[X.HAnimJoint(DEF="r", name="humanoid_root",
                  children=[X.HAnimSegment(DEF="s", name="seg")])])
print(h.XML())

Actual — the root joint carries no containerField:

<HAnimHumanoid DEF='H' name='h'>
  <HAnimJoint DEF='r' name='humanoid_root'>
    <HAnimSegment DEF='s' name='seg'/>
  </HAnimJoint>
</HAnimHumanoid>

Expected — the skeleton root joint must carry containerField='skeleton':

  <HAnimJoint DEF='r' name='humanoid_root' containerField='skeleton'>

Impact: players that render HAnimHumanoid via its skeleton field place the joint into the default children field instead and render nothing — the whole humanoid is invisible, with no warning. Reproduced in two independent conformant X3D 4.0 players (Castle Model Viewer 5.3 and X_ITE); the figure appears only after containerField='skeleton' is added by hand.

Reproduction 1b — PBR / unlit textures

from x3d import x3d as X
print(X.Appearance(material=X.UnlitMaterial(emissiveColor=[1,1,1],
        emissiveTexture=X.ImageTexture(url=["t.png"]))).XML())
print(X.Appearance(material=X.PhysicalMaterial(baseColor=[1,1,1],
        baseTexture=X.ImageTexture(url=["t.png"]))).XML())

Actual: each ImageTexture is emitted with no containerField.
Expected: containerField='emissiveTexture' (resp. 'baseTexture').
Impact: the texture inherits ImageTexture's default containerField texture, which PhysicalMaterial/UnlitMaterial do not define as a field — so it is dropped and the surface renders untextured (flat base color). Reproduced in Castle Model Viewer 5.3 and X_ITE.

Control (correct) — node in its default field

X.HAnimJoint(DEF='j', children=[X.HAnimSegment(DEF='s')]).XML()
# HAnimSegment's default containerField IS 'children', so omission is correct here.

The bug appears only when the field's container differs from the node default — exactly the case the typed field keyword should disambiguate.

Diagnosis

After construction the child's containerField is never set from the field it was placed in:

j = X.HAnimJoint(DEF="r", name="root")
X.HAnimHumanoid(skeleton=[j])
print(repr(getattr(j, "containerField", "<none>")))   # -> '<none>'

Also, x3d.py exposes no containerField setter: the constructor rejects a containerField= kwarg and a post-construction attribute assignment is ignored at XML() time — so string post-processing of the serialized output is currently the only workaround.

Suggested fix

When serializing (or when assigning a typed SFNode/MFNode field), set each child's containerField to the field's container name whenever it differs from the child type's default containerField.

Net effect

Programmatically built HAnim humanoids and PBR scenes serialize to XML that passes XSD validation yet renders incorrectly, because containerField routing is not part of schema validation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions