added additional fields

This commit is contained in:
2026-01-20 12:44:53 +01:00
parent aebccaf470
commit a174c7f200

View File

@@ -2,92 +2,117 @@ import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import xml.etree.ElementTree as ET
import os
class XMLExtractorApp:
def __init__(self, root):
self.root = root
self.root.title("XML Shape to Symbol Extractor")
self.root.geometry("600x600")
self.root.resizable(False, False)
self.input_file = None
self.tree = None
self.additional_fields = {}
# Define which fields to show for each kind
self.kind_fields_map = {
"ers": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "beam", "cd", "abm", "wt", "note", "d_mk", "d_adj_x", "d_adj_y", "d_adj_z"],
"zers": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "bm_w", "bm_t", "cd_w", "cd_t", "abm_w", "abm_t", "wt", "note", "d_mk", "d_adj_x", "d_adj_y", "d_adj_z"],
"fres": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "bm_w", "bm_t", "cd_w", "cd_t", "abm_w", "abm_t", "wt", "note", "d_mk", "d_adj_x", "d_adj_y", "d_adj_z"],
"par": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "bm_x", "bm_y", "cd", "abm_x", "abm_y", "wt", "note", "d_mk", "d_adj_x", "d_adj_y", "d_adj_z"],
"fl": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "beam", "cd", "abm", "wt", "note", "d_mk", "d_adj_x", "d_adj_y", "d_adj_z"],
"strip": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "bm_x", "bm_y", "cd", "abm_x", "abm_y", "wt", "cpf", "lpc", "dbl", "note", "d_mk", "d_adj_x", "d_adj_y", "d_adj_z"],
"scroll": ["name", "fname", "id", "sid", "have", "note", "scroll", "dmode", "d_mk", "acc_loc"],
"mirror": ["name", "fname", "id", "sid", "have", "note", "scroll", "dmode", "d_mk", "acc_loc"],
"focus": ["name", "fname", "id", "sid"],
"misc": ["name", "fname", "id", "sid", "have", "acc_loc"],
"mover": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "bm_w", "bm_t", "cd_w", "cd_t", "abm_w", "abm_t", "wt", "note", "scroll", "dmode", "d_mk", "d_adj_x", "d_adj_y", "d_adj_z"],
"cmymvr": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "bm_w", "bm_t", "cd_w", "cd_t", "abm_w", "abm_t", "wt", "note", "scroll", "dmode", "mixtype", "d_mk", "d_adj_x", "d_adj_y", "d_adj_z"],
"rgb": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "bm_x", "bm_y", "cd", "abm_x", "abm_y", "wt", "note", "scroll", "dmode", "mixtype", "d_adj_x", "d_adj_y", "d_adj_z"],
"rgba": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "bm_x", "bm_y", "cd", "abm_x", "abm_y", "wt", "note", "scroll", "dmode", "mixtype", "d_adj_x", "d_adj_y", "d_adj_z"],
"led7": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "bm_x", "bm_y", "cd", "abm_x", "abm_y", "wt", "note", "scroll", "dmode", "mixtype", "d_adj_x", "d_adj_y", "d_adj_z"],
"led7s": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "bm_x", "bm_y", "cd", "abm_x", "abm_y", "wt", "cpf", "lpc", "dbl", "note", "scroll", "dmode", "mixtype", "d_adj_x", "d_adj_y", "d_adj_z"],
"cmers": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "beam", "cd", "abm", "wt", "note", "scroll", "dmode", "mixtype", "d_mk", "d_adj_x", "d_adj_y", "d_adj_z"],
"cmzers": ["name", "fname", "id", "sid", "have", "lamp", "watts", "frame", "bm_w", "bm_t", "cd_w", "cd_t", "abm_w", "abm_t", "wt", "note", "scroll", "dmode", "mixtype", "d_mk", "d_adj_x", "d_adj_y", "d_adj_z"],
"cmscroll": ["name", "fname", "id", "sid", "have", "note", "scroll", "dmode", "mixtype", "d_mk"],
"netnode": ["name", "fname", "id", "sid", "have", "note", "dmode"]
}
self.create_widgets()
def create_widgets(self):
# Title
title = tk.Label(self.root, text="XML Shape to Symbol Extractor",
font=("Arial", 16, "bold"))
title.pack(pady=10)
# Info label
info = tk.Label(self.root, text="Extracts from: /layers/layer/shape\nWrites to: /key/kentry/custom/symbol/group/shape",
font=("Arial", 9), fg="gray", justify=tk.CENTER)
info.pack()
# Input file frame
input_frame = tk.Frame(self.root)
input_frame.pack(pady=10, padx=20, fill=tk.X)
tk.Label(input_frame, text="Input LXXPLOT File:",
font=("Arial", 10)).pack(anchor=tk.W)
file_display_frame = tk.Frame(input_frame)
file_display_frame.pack(fill=tk.X, pady=5)
self.file_label = tk.Label(file_display_frame, text="No file selected",
fg="gray", anchor=tk.W)
self.file_label.pack(side=tk.LEFT, fill=tk.X, expand=True)
browse_btn = tk.Button(file_display_frame, text="Browse",
command=self.browse_file)
browse_btn.pack(side=tk.RIGHT)
# Output file frame
output_frame = tk.Frame(self.root)
output_frame.pack(pady=10, padx=20, fill=tk.X)
tk.Label(output_frame, text="Output LXKEY File:",
font=("Arial", 10)).pack(anchor=tk.W)
self.output_entry = tk.Entry(output_frame, font=("Arial", 10))
self.output_entry.pack(fill=tk.X, pady=5)
self.output_entry.insert(0, "symbol_output.lxkey")
# Additional fields frame
fields_frame = tk.Frame(self.root)
fields_frame.pack(pady=10, padx=20, fill=tk.X)
tk.Label(fields_frame, text="Additional Tag Values:",
font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(0, 5))
# Name field (displayed as "Type")
name_frame = tk.Frame(fields_frame)
name_frame.pack(fill=tk.X, pady=2)
tk.Label(name_frame, text="Type:", width=10, anchor=tk.W).pack(side=tk.LEFT)
self.name_entry = tk.Entry(name_frame, font=("Arial", 10))
self.name_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
# ID field
id_frame = tk.Frame(fields_frame)
id_frame.pack(fill=tk.X, pady=2)
tk.Label(id_frame, text="ID:", width=10, anchor=tk.W).pack(side=tk.LEFT)
self.id_entry = tk.Entry(id_frame, font=("Arial", 10))
self.id_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
# Fname field
fname_frame = tk.Frame(fields_frame)
fname_frame.pack(fill=tk.X, pady=2)
tk.Label(fname_frame, text="Fname:", width=10, anchor=tk.W).pack(side=tk.LEFT)
self.fname_entry = tk.Entry(fname_frame, font=("Arial", 10))
self.fname_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
# Kind field (dropdown)
kind_frame = tk.Frame(fields_frame)
kind_frame.pack(fill=tk.X, pady=2)
tk.Label(kind_frame, text="Kind:", width=10, anchor=tk.W).pack(side=tk.LEFT)
# Dictionary mapping display names to values
self.kind_options = {
"eko / ERS": "ers",
@@ -111,7 +136,7 @@ class XMLExtractorApp:
"Color Mixing DMX Device": "cmscroll",
"Network Device": "netnode"
}
self.kind_var = tk.StringVar()
self.kind_dropdown = ttk.Combobox(kind_frame, textvariable=self.kind_var,
values=list(self.kind_options.keys()),
@@ -119,20 +144,141 @@ class XMLExtractorApp:
font=("Arial", 10))
self.kind_dropdown.pack(side=tk.LEFT, fill=tk.X, expand=True)
self.kind_dropdown.current(0) # Set default to first option
# Additional fields button
additional_btn = tk.Button(self.root, text="Additional Fields",
command=self.open_additional_fields,
font=("Arial", 10),
bg="#2196F3", fg="white",
padx=15, pady=8)
additional_btn.pack(pady=10)
# Extract button
extract_btn = tk.Button(self.root, text="Extract and Convert",
command=self.extract_and_convert,
font=("Arial", 12, "bold"),
bg="#4CAF50", fg="white",
padx=20, pady=10)
extract_btn.pack(pady=20)
extract_btn.pack(pady=5)
# Status label
self.status_label = tk.Label(self.root, text="",
font=("Arial", 9), fg="blue")
self.status_label.pack(pady=5)
def open_additional_fields(self):
# Get the selected kind value
selected_display = self.kind_var.get()
selected_kind = self.kind_options.get(selected_display, "ers")
# Get fields to display for this kind
fields_to_show = self.kind_fields_map.get(selected_kind, [])
# Create popup window
popup = tk.Toplevel(self.root)
popup.title("Additional Fields")
popup.geometry("500x600")
popup.resizable(False, False)
# Title
title = tk.Label(popup, text=f"Additional Fields - {selected_display}",
font=("Arial", 14, "bold"))
title.pack(pady=10)
# Create scrollable frame
canvas = tk.Canvas(popup)
scrollbar = tk.Scrollbar(popup, orient="vertical", command=canvas.yview)
scrollable_frame = tk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
# Field definitions (field_name, label)
all_fields_data = {
"name": "Type",
"fname": "Full Name",
"id": "ID",
"sid": "Symbol ID",
"have": "Inventory",
"balance": "Balance",
"lamp": "Lamp",
"watts": "Watts",
"frame": "Color Frame",
"beam": "Field Angle",
"bm_x": "Field X",
"bm_y": "Field Y",
"bm_w": "Field Wide",
"bm_t": "Field Tight",
"cd": "Candela",
"cd_w": "Candela Wide",
"cd_t": "Candela Tight",
"abm": "Beam Angle",
"abm_x": "Beam X",
"abm_y": "Beam Y",
"abm_w": "Beam Wide",
"abm_t": "Beam Tight",
"wt": "Weight",
"cpf": "Sections Per Fixture",
"lpc": "Lamps Per Circuit",
"dbl": "Distance Between Lamps",
"note": "More Info",
"scroll": "Device Params",
"dmode": "Mode",
"mixtype": "Mix Type",
"d_mk": "Default Mark",
"acc_loc": "Placement",
"d_adj_x": "Default X Offset",
"d_adj_y": "Default Y Offset",
"d_adj_z": "Default Z Offset"
}
# Create entry fields only for fields that should be shown
self.additional_entries = {}
for field_name in fields_to_show:
if field_name in all_fields_data:
label = all_fields_data[field_name]
field_frame = tk.Frame(scrollable_frame)
field_frame.pack(fill=tk.X, padx=20, pady=3)
tk.Label(field_frame, text=f"{label}:", width=20, anchor=tk.W).pack(side=tk.LEFT)
entry = tk.Entry(field_frame, font=("Arial", 9))
entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
# Pre-fill with existing value if any
if field_name in self.additional_fields:
entry.insert(0, self.additional_fields[field_name])
self.additional_entries[field_name] = entry
canvas.pack(side="left", fill="both", expand=True, padx=(10, 0), pady=10)
scrollbar.pack(side="right", fill="y", pady=10, padx=(0, 10))
# Save button
save_btn = tk.Button(popup, text="Save",
command=lambda: self.save_additional_fields(popup),
font=("Arial", 10, "bold"),
bg="#4CAF50", fg="white",
padx=20, pady=8)
save_btn.pack(pady=10)
def save_additional_fields(self, popup):
# Save all field values
for field_name, entry in self.additional_entries.items():
value = entry.get().strip()
if value:
self.additional_fields[field_name] = value
elif field_name in self.additional_fields:
del self.additional_fields[field_name]
popup.destroy()
self.status_label.config(text="Additional fields saved", fg="green")
def browse_file(self):
filename = filedialog.askopenfilename(
title="Select LXXPLOT File",
@@ -142,80 +288,87 @@ class XMLExtractorApp:
self.input_file = filename
self.file_label.config(text=os.path.basename(filename), fg="black")
self.status_label.config(text="File loaded successfully", fg="green")
def extract_and_convert(self):
# Validate inputs
if not self.input_file:
messagebox.showerror("Error", "Please select an input LXXPLOT file")
return
output_filename = self.output_entry.get().strip()
if not output_filename:
messagebox.showerror("Error", "Please enter an output file name")
return
# Get the directory of the input file and create full output path
input_dir = os.path.dirname(self.input_file)
output_file = os.path.join(input_dir, output_filename)
try:
# Parse the LXXPLOT file (XML format)
tree = ET.parse(self.input_file)
root = tree.getroot()
# Find the shape tag using the xpath: /layers/layer/shape
shape_element = root.find("./layers/layer/shape")
if shape_element is None:
messagebox.showerror("Error",
"Path '/layers/layer/shape' not found in the LXXPLOT file")
return
# Create the new XML structure: /key/kentry/custom/symbol/group/shape
new_root = ET.Element("key")
kentry = ET.SubElement(new_root, "kentry")
# Add name, kind, and fname as siblings of custom with user-provided values
name = ET.SubElement(kentry, "name")
name.text = self.name_entry.get().strip()
id_elem = ET.SubElement(kentry, "id")
id_elem.text = self.id_entry.get().strip()
kind = ET.SubElement(kentry, "kind")
# Get the value corresponding to the selected display name
selected_display = self.kind_var.get()
kind.text = self.kind_options.get(selected_display, "")
fname = ET.SubElement(kentry, "fname")
fname.text = self.fname_entry.get().strip()
custom = ET.SubElement(kentry, "custom")
symbol = ET.SubElement(custom, "symbol")
group = ET.SubElement(symbol, "group")
shape = ET.SubElement(group, "shape")
# Copy the content from shape element to the new shape element
shape.text = shape_element.text
shape.attrib = shape_element.attrib
# Copy all child elements from original shape to new shape
for child in shape_element:
shape.append(child)
# Add additional fields as siblings of custom
for field_name, field_value in self.additional_fields.items():
# Skip fields already added (name, id, kind, fname)
if field_name not in ["name", "id", "kind", "fname"]:
field_elem = ET.SubElement(kentry, field_name)
field_elem.text = field_value
# Create and write the new LXKEY file
new_tree = ET.ElementTree(new_root)
ET.indent(new_tree, space=" ")
new_tree.write(output_file, encoding="utf-8", xml_declaration=True)
self.status_label.config(
text=f"Successfully converted and saved to {output_filename}",
fg="green"
)
messagebox.showinfo("Success",
f"Shape content extracted and written to /key/kentry/custom/symbol/group/shape\nSaved to: {output_file}")
except ET.ParseError as e:
messagebox.showerror("Parse Error",
f"Failed to parse XML file:\n{str(e)}")
@@ -223,7 +376,7 @@ class XMLExtractorApp:
except Exception as e:
messagebox.showerror("Error", f"An error occurred:\n{str(e)}")
self.status_label.config(text="Error occurred", fg="red")
if __name__ == "__main__":
root = tk.Tk()
app = XMLExtractorApp(root)