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.
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
containerFielddiffers from that node type's defaultcontainerField,X3D(...).XML()emits the child without anycontainerFieldattribute. 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:
x3d4.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(defaultchildren) placed inskeleton/jointsHAnimSegment(defaultchildren) placed insegmentsImageTexture(defaulttexture) placed inbaseTexture/emissiveTexture/normalTexture/ … ofPhysicalMaterial/UnlitMaterialReproduction 1a — HAnim skeleton
Actual — the root joint carries no
containerField:Expected — the skeleton root joint must carry
containerField='skeleton':Impact: players that render
HAnimHumanoidvia itsskeletonfield place the joint into the defaultchildrenfield 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 aftercontainerField='skeleton'is added by hand.Reproduction 1b — PBR / unlit textures
Actual: each
ImageTextureis emitted with nocontainerField.Expected:
containerField='emissiveTexture'(resp.'baseTexture').Impact: the texture inherits
ImageTexture's defaultcontainerFieldtexture, whichPhysicalMaterial/UnlitMaterialdo 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
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
containerFieldis never set from the field it was placed in:Also, x3d.py exposes no
containerFieldsetter: the constructor rejects acontainerField=kwarg and a post-construction attribute assignment is ignored atXML()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
containerFieldto the field's container name whenever it differs from the child type's defaultcontainerField.Net effect
Programmatically built HAnim humanoids and PBR scenes serialize to XML that passes XSD validation yet renders incorrectly, because
containerFieldrouting is not part of schema validation.