Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,393 changes: 4,393 additions & 0 deletions attributes_listing.xml

Large diffs are not rendered by default.

5,111 changes: 4,695 additions & 416 deletions gdtf-spec.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions proposal/attributes_listing_wheels_usage/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.14
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@
],
"definition": "Controls audio-controlled functionality of animation wheel (n).",
"explanation": "This attribute controls how the animation wheel will behave when responding to an audio source for use with the AnimationWheel(n). When using this AnimationWheel(n)Audio as a Channel Function attribute, make sure that the attribute for the Logical Channel is set to AnimationWheel(n). In case that AnimationWheel(n) already exists in the same geometry, you can use the AnimationWheel(n)Audio as the Logical Channel attribute.",
"visual": "This attribute controls how the animation wheel will behave when responding to an audio source for use with the AnimationWheel(n).",
"visual": "",
"_label": "Animation Wheel Audio"
},
{
Expand Down Expand Up @@ -1523,7 +1523,7 @@
"_label": "Video Key Color Green"
},
{
"_name": "VideoColorKey_B",
"_name": "VideoKeyColor_B",
"_prettyName": "B",
"_feature": "Color.ColorKey",
"_physicalUnit": "None",
Expand Down
10 changes: 10 additions & 0 deletions proposal/attributes_listing_wheels_usage/map.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"_name": "TiltRotate",
"_prettyName": "T Rotate",
"_feature": "Position.PanTilt",
"_physicalUnit": "AngularSpeed",
"definition": "Controls the speed of the fixture's continuous tilt movement (vertical axis).",
"explanation": "For fixtures with the ability to have a continuous tilt rotation, the “Tilt Rotate” control defines how fast (speed) a fixture can rotat
e about the tilt axis. Usually, the vertical axis.",
"visual": "",
"_label": "Tilt Rotate"

161 changes: 161 additions & 0 deletions proposal/attributes_listing_wheels_usage/merge_attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""
Merge gdtf_attributes_with_description.json into attributes_listing.xml
based on Attribute Name.

Rules:
- Match JSON _name to XML Attribute@Name.
- Map fields:
_name -> Name
_prettyName -> Pretty
_feature -> Feature
_physicalUnit -> PhysicalUnit
definition -> Definition
explanation -> Explanation
_label -> Label
- Keep all other XML attributes and structure.
- JSON values overwrite existing XML values when non-empty.
- Reformat output with a custom formatter (2-space indent).
"""

import json
from pathlib import Path
import xml.etree.ElementTree as ET

JSON_PATH = Path("gdtf_attributes_with_description.json")
XML_PATH = Path("attributes_listing.xml")

MAP_FIELDS = {
"_name": "Name",
"_prettyName": "Pretty",
"_feature": "Feature",
"_physicalUnit": "PhysicalUnit",
"definition": "Definition",
"explanation": "Explanation",
"_label": "Label",
}

DEFAULT_ATTRS = {
"UseEmmiter": "False",
"UseFilter": "False",
"UseWheel": "False",
"UseWheelSlot": "False",
"UseGamut": "False",
"UseColorSpace": "False",
}


def load_json(path: Path):
obj = json.loads(path.read_text(encoding="utf-8"))
if isinstance(obj, dict) and "Attribute" in obj:
items = obj["Attribute"]
elif isinstance(obj, list):
items = obj
else:
raise ValueError("Unexpected JSON structure")
if not isinstance(items, list):
raise ValueError("JSON 'Attribute' is not a list")
return items


def build_index(items):
by_name = {}
for it in items:
name = it.get("_name")
if name:
by_name[name] = it
return by_name


def merge(xml_path: Path, by_name):
tree = ET.parse(xml_path)
root = tree.getroot()

updated = 0
matched = 0
missing_in_json = []

for attr in root.findall(".//Attribute"):
name = attr.get("Name")
if not name:
continue
data = by_name.get(name)
if not data:
missing_in_json.append(name)
continue
matched += 1
for jkey, xkey in MAP_FIELDS.items():
if jkey not in data:
continue
val = data.get(jkey)
if val is None or val == "":
continue
val = str(val)
if attr.get(xkey) != val:
attr.set(xkey, val)
updated += 1
for xkey, val in DEFAULT_ATTRS.items():
if attr.get(xkey) != val:
attr.set(xkey, val)
updated += 1

return tree, root, matched, updated, missing_in_json


def xml_escape(text: str) -> str:
return (
text.replace("&", "&")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace('"', "&quot;")
)


def format_custom(root: ET.Element, indent: str = " ") -> str:
lines = ["<?xml version=\"1.0\"?>"]

def write_elem(elem: ET.Element, level: int) -> None:
pad = indent * level
tag = elem.tag
attrs = elem.attrib
children = list(elem)
text = (elem.text or "").strip()

if attrs:
lines.append(f"{pad}<{tag}")
for k, v in attrs.items():
lines.append(f"{pad}{indent}{k}=\"{xml_escape(str(v))}\"")
else:
lines.append(f"{pad}<{tag}")

if children or text:
lines[-1] = lines[-1] + ">"
if text:
lines.append(f"{pad}{indent}{xml_escape(text)}")
for child in children:
write_elem(child, level + 1)
if child.tail and child.tail.strip():
lines.append(f"{pad}{indent}{xml_escape(child.tail.strip())}")
lines.append(f"{pad}</{tag}>")
else:
lines[-1] = lines[-1] + "/>"

write_elem(root, 0)
return "\n".join(lines) + "\n"


def main():
items = load_json(JSON_PATH)
by_name = build_index(items)

tree, root, matched, updated, missing_in_json = merge(XML_PATH, by_name)

formatted = format_custom(root)
XML_PATH.write_text(formatted, encoding="utf-8")

print(f"matched {matched} attributes; updated {updated} fields")
print(f"missing in json: {len(missing_in_json)}")


if __name__ == "__main__":
main()
33 changes: 33 additions & 0 deletions proposal/attributes_listing_wheels_usage/print_attr_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from pathlib import Path
import xml.etree.ElementTree as ET


XML_PATH = Path(__file__).with_name("attributes_listing.xml")
HEADER = (
"| Attribute | Description"
" "
)
SEPARATOR = "|----------------------------------|-----------------------------| "
ATTRIBUTE_WIDTH = 32
DESCRIPTION_WIDTH = 297


def main() -> None:
root = ET.parse(XML_PATH).getroot()

print(HEADER)
print(SEPARATOR)

for attribute in root.find("Attributes").findall("Attribute"):
name = attribute.get("Name")
description = attribute.get("Definition")
if not name or not description:
print("skipping", attribute.get("Name"))
continue
name = name.replace("_", r"\_")
description = description.replace("_", r"\_")
print(f"| {name:<{ATTRIBUTE_WIDTH}} | {description:<{DESCRIPTION_WIDTH}} |")


if __name__ == "__main__":
main()
7 changes: 7 additions & 0 deletions proposal/attributes_listing_wheels_usage/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
name = "attributes-listing-wheels-usage"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = []
8 changes: 8 additions & 0 deletions proposal/attributes_listing_wheels_usage/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading