added additional fields
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user