From a174c7f200b2e84371927f8c18f12892063f0960 Mon Sep 17 00:00:00 2001 From: sebmas Date: Tue, 20 Jan 2026 12:44:53 +0100 Subject: [PATCH] added additional fields --- lxplan_lib_creator.py | 241 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 197 insertions(+), 44 deletions(-) diff --git a/lxplan_lib_creator.py b/lxplan_lib_creator.py index 2d104c3..abe10ae 100644 --- a/lxplan_lib_creator.py +++ b/lxplan_lib_creator.py @@ -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( + "", + 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)