761 lines
34 KiB
Python
761 lines
34 KiB
Python
import os
|
|
import re
|
|
import json
|
|
import threading
|
|
import tkinter as tk
|
|
from tkinter import filedialog, messagebox, ttk
|
|
import pythoncom
|
|
import win32com.client
|
|
import time
|
|
from win32com.client import pythoncom as pycom
|
|
|
|
PROGRESS_LOG = 'export_progress.json'
|
|
|
|
def safe_open_doc(docs, dwg_path, retries=5, delay=3):
|
|
for attempt in range(retries):
|
|
try:
|
|
return docs.Open(dwg_path)
|
|
except Exception as e:
|
|
if 'Call was rejected by callee' in str(e) and attempt < retries - 1:
|
|
time.sleep(delay)
|
|
else:
|
|
raise
|
|
|
|
# --- Backend Logic ---
|
|
def set_block_attribute(doc, block_name, attr_tag, value):
|
|
# Search PaperSpace for block references
|
|
for entity in doc.PaperSpace:
|
|
if entity.ObjectName == 'AcDbBlockReference' and entity.Name == block_name:
|
|
try:
|
|
for attrib in entity.GetAttributes():
|
|
if attrib.TagString.upper() == attr_tag.upper():
|
|
attrib.TextString = value
|
|
return True
|
|
except Exception:
|
|
continue
|
|
return False
|
|
|
|
def set_wyncorp_block(doc, chunk, log_operation=None):
|
|
# First try to find UPS.AE 11X17 (V0) block and set DWGDESC1
|
|
for entity in doc.PaperSpace:
|
|
if entity.ObjectName == 'AcDbBlockReference' and entity.Name == "UPS.AE 11X17 (V0)":
|
|
try:
|
|
value = "IO LAYOUT " + ", ".join(chunk)
|
|
for attrib in entity.GetAttributes():
|
|
if attrib.TagString.upper() == "DWGDESC1":
|
|
attrib.TextString = value
|
|
if log_operation:
|
|
log_operation(f"Inputting block DWGDESC1: {value}")
|
|
return True
|
|
except Exception:
|
|
continue
|
|
|
|
# If UPS.AE block not found, try WYNCORP-DSIZE_AS block and set TITLE2 and TITLE3
|
|
for entity in doc.PaperSpace:
|
|
if entity.ObjectName == 'AcDbBlockReference' and entity.Name == "WYNCORP-DSIZE_AS":
|
|
try:
|
|
title2_val = "/".join(chunk[:2]) if chunk[:2] else ""
|
|
title3_val = "/".join(chunk[2:4]) if chunk[2:4] else ""
|
|
if title3_val:
|
|
title3_val += " IO DRAWINGS"
|
|
else:
|
|
title3_val = "IO DRAWINGS"
|
|
for attrib in entity.GetAttributes():
|
|
if attrib.TagString.upper() == "TITLE2":
|
|
attrib.TextString = title2_val
|
|
if log_operation:
|
|
log_operation(f"Inputting block TITLE2: {title2_val}")
|
|
if attrib.TagString.upper() == "TITLE3":
|
|
attrib.TextString = title3_val
|
|
if log_operation:
|
|
log_operation(f"Inputting block TITLE3: {title3_val}")
|
|
return True
|
|
except Exception:
|
|
continue
|
|
return False
|
|
|
|
def transform_dpm_name_for_network(raw_name):
|
|
try:
|
|
import re as _re
|
|
m = _re.search(r"^(.*)_(DPM\d+)$", raw_name, _re.IGNORECASE)
|
|
if m:
|
|
rest = m.group(1)
|
|
dpm = m.group(2).upper()
|
|
return f"{dpm}_{rest}_ENET"
|
|
return f"{raw_name}_ENET"
|
|
except Exception:
|
|
return f"{raw_name}_ENET"
|
|
|
|
def set_network_block(doc, raw_name, log_operation=None):
|
|
value_title3 = transform_dpm_name_for_network(raw_name)
|
|
# Prefer WYNCORP-DSIZE_AS: TITLE2 = NETWORK DETAILS, TITLE3 = transformed
|
|
for entity in doc.PaperSpace:
|
|
if entity.ObjectName == 'AcDbBlockReference' and entity.Name == "WYNCORP-DSIZE_AS":
|
|
try:
|
|
for attrib in entity.GetAttributes():
|
|
if attrib.TagString.upper() == "TITLE2":
|
|
attrib.TextString = "NETWORK DETAILS"
|
|
if log_operation:
|
|
log_operation("Inputting block TITLE2: NETWORK DETAILS")
|
|
if attrib.TagString.upper() == "TITLE3":
|
|
attrib.TextString = value_title3
|
|
if log_operation:
|
|
log_operation(f"Inputting block TITLE3: {value_title3}")
|
|
return True
|
|
except Exception:
|
|
continue
|
|
# Fallback to UPS.AE 11X17 (V0) -> DWGDESC1 (use raw DPM name, no transform)
|
|
for entity in doc.PaperSpace:
|
|
if entity.ObjectName == 'AcDbBlockReference' and entity.Name == "UPS.AE 11X17 (V0)":
|
|
try:
|
|
for attrib in entity.GetAttributes():
|
|
if attrib.TagString.upper() == "DWGDESC1":
|
|
fallback_val = f"NETWORK DETAILS {raw_name}"
|
|
attrib.TextString = fallback_val
|
|
if log_operation:
|
|
log_operation(f"Inputting block DWGDESC1: {fallback_val}")
|
|
return True
|
|
except Exception:
|
|
continue
|
|
return False
|
|
|
|
def process_single_file(dwg_path, base_dwg_path, layout_template, template_layout_name, index, total, acad, docs, block_names, mode="IO", log_operation=None, error_callback=None):
|
|
new_doc = None
|
|
# Auto-close if already open
|
|
for doc in list(docs):
|
|
try:
|
|
if os.path.abspath(doc.FullName).lower() == os.path.abspath(dwg_path).lower():
|
|
doc.Close(False)
|
|
# time.sleep(2)
|
|
except Exception:
|
|
continue
|
|
try:
|
|
if log_operation:
|
|
log_operation(f"Opening file: {os.path.basename(dwg_path)}")
|
|
new_doc = safe_open_doc(docs, dwg_path)
|
|
# time.sleep(2)
|
|
# Check if Xref already exists (skip attaching when processing the master file itself)
|
|
is_master_file = os.path.abspath(dwg_path).lower() == os.path.abspath(base_dwg_path).lower()
|
|
xref_exists = False
|
|
if not is_master_file:
|
|
for item in new_doc.ModelSpace:
|
|
if item.ObjectName == "AcDbBlockReference":
|
|
try:
|
|
if item.Name == "XREF1":
|
|
xref_exists = True
|
|
break
|
|
except:
|
|
continue
|
|
if not xref_exists:
|
|
if log_operation:
|
|
log_operation("Attaching Xref...")
|
|
# Use relative path - just the filename if in same folder
|
|
master_filename = os.path.basename(base_dwg_path)
|
|
insertion_point = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [0, 0, 0])
|
|
new_doc.ModelSpace.AttachExternalReference(
|
|
master_filename, "XREF1", insertion_point, 1, 1, 1, 0, False)
|
|
# Check if layout exists
|
|
layout_exists = False
|
|
for layout in new_doc.Layouts:
|
|
if layout.Name.upper() == template_layout_name.upper():
|
|
layout_exists = True
|
|
break
|
|
if not layout_exists:
|
|
if log_operation:
|
|
log_operation("Adding template layout...")
|
|
filedia_original = new_doc.GetVariable("FILEDIA")
|
|
new_doc.SetVariable("FILEDIA", 0)
|
|
command_sequence = (
|
|
"_-LAYOUT\n"
|
|
"T\n"
|
|
f'"{layout_template}"\n'
|
|
f"{template_layout_name}\n"
|
|
"\n"
|
|
)
|
|
acad.ActiveDocument.SendCommand(command_sequence)
|
|
# time.sleep(2)
|
|
new_doc.SetVariable("FILEDIA", filedia_original)
|
|
# Activate layout
|
|
new_doc.ActiveLayout = new_doc.Layouts.Item(template_layout_name)
|
|
# time.sleep(0.5)
|
|
# Pan/zoom viewport
|
|
if log_operation:
|
|
log_operation("Zooming to layout area...")
|
|
|
|
# Set viewport values based on mode
|
|
if mode == "IO":
|
|
base_x = 19.13
|
|
base_y = 0.7881
|
|
block_width = 38.5
|
|
height = 22.2955
|
|
else: # NETWORK mode
|
|
# NETWORK mode needs different values - working backwards from your feedback
|
|
# For 19 files with total width 826.5: block_width = 826.5/19 = 43.5
|
|
# For 10th file (index 9) to be 369: 369 = base_x + (9 * 43.5)
|
|
# So base_x = 369 - 391.5 = -22.5
|
|
base_x = -22
|
|
base_y = 13.25
|
|
block_width = 43.5
|
|
height = 26.1748
|
|
|
|
acad.ActiveDocument.SendCommand("MSPACE\n")
|
|
# time.sleep(0.5)
|
|
# For first file (index 0), use base position. For subsequent files, add block_width * index
|
|
center_x = base_x + (index * block_width)
|
|
center_y = base_y
|
|
zoom_command = f'ZOOM C {center_x},{center_y} {height}\n'
|
|
acad.ActiveDocument.SendCommand(zoom_command)
|
|
# time.sleep(1)
|
|
acad.ActiveDocument.SendCommand("PSPACE\n")
|
|
# time.sleep(0.5)
|
|
# --- Set block attribute for this layout ---
|
|
if mode == "IO":
|
|
# IO mode: use chunks of 4 names
|
|
start_idx = index * 4
|
|
chunk = block_names[start_idx:start_idx+4]
|
|
block_set = False
|
|
if chunk:
|
|
value = "IO LAYOUT " + "/".join(chunk)
|
|
if log_operation:
|
|
log_operation(f"Inputting block names: {value}")
|
|
block_set = set_block_attribute(new_doc, "UPS.AE 11X17 (V0)", "DWGDESC1", value)
|
|
# If not found, try WYNCORP-DSIZE_AS logic
|
|
if not block_set and chunk:
|
|
if log_operation:
|
|
log_operation(f"Trying WYNCORP-DSIZE_AS block for chunk: {chunk}")
|
|
set_wyncorp_block(new_doc, chunk, log_operation=log_operation)
|
|
else: # NETWORK mode
|
|
# NETWORK mode: use single name transformed: DPMx_<rest>_ENET,
|
|
# and set TITLE2/TITLE3 on WYNCORP when present; fallback to UPS.AE
|
|
if block_names and index < len(block_names):
|
|
raw_name = block_names[index]
|
|
if log_operation:
|
|
log_operation(f"Preparing NETWORK labels for: {raw_name}")
|
|
set_network_block(new_doc, raw_name, log_operation=log_operation)
|
|
# Delete default layouts
|
|
if log_operation:
|
|
log_operation("Deleting default layouts (Layout1, Layout2)...")
|
|
for layout_name in ["Layout1", "Layout2"]:
|
|
delete_command = f"_-LAYOUT\nD\n{layout_name}\n"
|
|
acad.ActiveDocument.SendCommand(delete_command)
|
|
time.sleep(1)
|
|
# Save and restore FILEDIA, then close
|
|
if log_operation:
|
|
log_operation("Saving file and restoring FILEDIA...")
|
|
new_doc.Save()
|
|
try:
|
|
new_doc.SetVariable("FILEDIA", 1)
|
|
except Exception:
|
|
pass
|
|
except Exception as e:
|
|
if error_callback:
|
|
error_callback(str(e))
|
|
raise
|
|
finally:
|
|
try:
|
|
if new_doc is not None:
|
|
new_doc.Close(False)
|
|
# time.sleep(2)
|
|
except Exception:
|
|
pass
|
|
return True
|
|
|
|
# --- Progress Log ---
|
|
def load_progress():
|
|
if os.path.exists(PROGRESS_LOG):
|
|
with open(PROGRESS_LOG, 'r') as f:
|
|
return set(json.load(f))
|
|
return set()
|
|
|
|
def save_progress(done_files):
|
|
with open(PROGRESS_LOG, 'w') as f:
|
|
json.dump(list(done_files), f)
|
|
|
|
def is_autocad_running():
|
|
try:
|
|
win32com.client.GetActiveObject("AutoCAD.Application")
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
def read_block_names(names_file):
|
|
if not names_file or not os.path.exists(names_file):
|
|
return []
|
|
with open(names_file, 'r', encoding='utf-8') as f:
|
|
return [line.strip() for line in f if line.strip()]
|
|
|
|
def delete_bak_for_dwg(dwg_path, log_operation=None, error_callback=None):
|
|
bak_path = os.path.splitext(dwg_path)[0] + '.bak'
|
|
try:
|
|
if os.path.exists(bak_path):
|
|
os.remove(bak_path)
|
|
if log_operation:
|
|
log_operation(f"Deleted backup file: {os.path.basename(bak_path)}")
|
|
except Exception as e:
|
|
if error_callback:
|
|
error_callback(f"Failed to delete backup file {os.path.basename(bak_path)}: {e}")
|
|
|
|
def delete_all_bak_files(root_dir, log_operation=None, error_callback=None):
|
|
deleted_count = 0
|
|
try:
|
|
for current_root, _, files in os.walk(root_dir):
|
|
for file_name in files:
|
|
if file_name.lower().endswith('.bak'):
|
|
bak_path = os.path.join(current_root, file_name)
|
|
try:
|
|
os.remove(bak_path)
|
|
deleted_count += 1
|
|
except Exception as e:
|
|
if error_callback:
|
|
error_callback(f"Failed to delete backup file {file_name}: {e}")
|
|
if log_operation:
|
|
log_operation(f"Deleted {deleted_count} remaining .bak file(s) after full completion.")
|
|
except Exception as e:
|
|
if error_callback:
|
|
error_callback(f"Failed during final backup cleanup: {e}")
|
|
|
|
# --- UI ---
|
|
def main():
|
|
root = tk.Tk()
|
|
root.title("Export Layouts Batch Processor")
|
|
root.geometry("750x750")
|
|
root.configure(bg="#181818")
|
|
root.resizable(False, False)
|
|
|
|
# Modern font
|
|
FONT = ("Segoe UI", 11)
|
|
FONT_BOLD = ("Segoe UI", 12, "bold")
|
|
GOLD = "#FFD700"
|
|
BLACK = "#181818"
|
|
WHITE = "#FFFFFF"
|
|
|
|
style = ttk.Style()
|
|
style.theme_use('clam')
|
|
style.configure("TButton", font=FONT, background=GOLD, foreground=BLACK, borderwidth=0, focusthickness=3, focuscolor=GOLD, padding=8)
|
|
style.map("TButton", background=[('active', GOLD), ('pressed', GOLD)])
|
|
style.configure("TEntry", fieldbackground=BLACK, foreground=GOLD, borderwidth=1)
|
|
style.configure("TLabel", background=BLACK, foreground=GOLD, font=FONT)
|
|
style.configure("TProgressbar", troughcolor=BLACK, bordercolor=BLACK, background=GOLD, lightcolor=GOLD, darkcolor=GOLD, thickness=20)
|
|
|
|
master_path = tk.StringVar()
|
|
template_path = tk.StringVar()
|
|
drawings_files = [] # List of selected DWG files
|
|
drawings_files_var = tk.StringVar(value="No drawings selected.")
|
|
progress_var = tk.IntVar(value=0)
|
|
status_var = tk.StringVar(value="Idle.")
|
|
current_file_var = tk.StringVar(value="No file processing yet.")
|
|
error_log_var = tk.StringVar(value="")
|
|
names_file = tk.StringVar()
|
|
block_names = []
|
|
|
|
# --- Logging variables ---
|
|
operations_log_var = tk.StringVar(value="")
|
|
error_log_var = tk.StringVar(value="")
|
|
|
|
def log_operation(msg):
|
|
prev = operations_log_var.get()
|
|
operations_log_var.set((prev + "\n" if prev else "") + msg)
|
|
|
|
def append_error(msg):
|
|
prev = error_log_var.get()
|
|
error_log_var.set((prev + "\n" if prev else "") + msg)
|
|
|
|
def select_master():
|
|
path = filedialog.askopenfilename(title="Select Master DWG", filetypes=[("DWG Files", "*.dwg")])
|
|
if path:
|
|
master_path.set(path)
|
|
|
|
def select_template():
|
|
path = filedialog.askopenfilename(title="Select Template DWG", filetypes=[("DWG Files", "*.dwg")])
|
|
if path:
|
|
template_path.set(path)
|
|
|
|
def select_drawings_files():
|
|
nonlocal drawings_files
|
|
files = filedialog.askopenfilenames(title="Select Drawing Files", filetypes=[("DWG Files", "*.dwg")])
|
|
if files:
|
|
drawings_files = list(files)
|
|
drawings_files_var.set(f"{len(drawings_files)} files selected.")
|
|
else:
|
|
drawings_files = []
|
|
drawings_files_var.set("No drawings selected.")
|
|
|
|
def select_names_file():
|
|
path = filedialog.askopenfilename(title="Select Block Names File", filetypes=[("Text Files", "*.txt")])
|
|
if path:
|
|
names_file.set(path)
|
|
|
|
cancel_requested = tk.BooleanVar(value=False)
|
|
|
|
def set_buttons_state(state):
|
|
# IO tab buttons
|
|
try:
|
|
run_btn.config(state=state)
|
|
start_over_btn.config(state=state)
|
|
cancel_btn.config(state="normal" if state == "disabled" else "disabled")
|
|
except Exception:
|
|
pass
|
|
# NETWORK tab buttons
|
|
try:
|
|
n_run_btn.config(state=state)
|
|
n_start_over_btn.config(state=state)
|
|
n_cancel_btn.config(state="normal" if state == "disabled" else "disabled")
|
|
except Exception:
|
|
pass
|
|
|
|
def start_batch():
|
|
nonlocal block_names
|
|
if not master_path.get() or not template_path.get() or not drawings_files or not names_file.get():
|
|
messagebox.showerror("Error", "Please select master, template DWG files, drawing files, and block names file.")
|
|
return
|
|
block_names = read_block_names(names_file.get())
|
|
if not block_names:
|
|
messagebox.showerror("Error", "Block names file is empty or not found.")
|
|
return
|
|
|
|
# Determine which tab is active
|
|
current_tab = main_notebook.select()
|
|
if "IO" in main_notebook.tab(current_tab, "text"):
|
|
mode = "IO"
|
|
else:
|
|
mode = "NETWORK"
|
|
|
|
progress_var.set(0)
|
|
status_var.set("Processing...")
|
|
current_file_var.set("")
|
|
operations_log_var.set("")
|
|
error_log_var.set("")
|
|
cancel_requested.set(False)
|
|
set_buttons_state("disabled")
|
|
threading.Thread(target=lambda: run_batch(mode), daemon=True).start()
|
|
|
|
def start_over():
|
|
if os.path.exists(PROGRESS_LOG):
|
|
os.remove(PROGRESS_LOG)
|
|
progress_var.set(0)
|
|
status_var.set("Progress reset. Ready.")
|
|
current_file_var.set("")
|
|
error_log_var.set("")
|
|
operations_log_var.set("")
|
|
|
|
def cancel_batch():
|
|
cancel_requested.set(True)
|
|
status_var.set("Cancelling... (will stop after current file)")
|
|
|
|
def run_batch(mode="IO"):
|
|
try:
|
|
# Build processing order: master first, then sorted drawings (excluding master if present)
|
|
master = master_path.get()
|
|
drawings = [f for f in list(drawings_files) if os.path.abspath(f).lower() != os.path.abspath(master).lower()]
|
|
def sort_key(file_name):
|
|
match = re.search(r'LAYOUT\s*(\d+)', os.path.basename(file_name), re.IGNORECASE)
|
|
if match:
|
|
return int(match.group(1))
|
|
return float('inf')
|
|
drawings.sort(key=sort_key)
|
|
files = [master] + drawings
|
|
total = len(files)
|
|
done_files = load_progress()
|
|
pythoncom.CoInitialize()
|
|
acad = win32com.client.Dispatch("AutoCAD.Application")
|
|
docs = acad.Documents
|
|
|
|
template_doc = docs.Open(template_path.get())
|
|
time.sleep(2) # Give AutoCAD time to fully load the template
|
|
template_layout_name = None
|
|
try:
|
|
for layout in template_doc.Layouts:
|
|
if layout.Name.upper() != "MODEL":
|
|
template_layout_name = layout.Name
|
|
break
|
|
except Exception as e:
|
|
append_error(f"Error reading template layouts: {e}")
|
|
template_layout_name = "TITLE PAGE" # Fallback to default name
|
|
finally:
|
|
try:
|
|
template_doc.Close(False)
|
|
except Exception:
|
|
pass
|
|
if not template_layout_name:
|
|
status_var.set("No layout found in template file!")
|
|
pythoncom.CoUninitialize()
|
|
set_buttons_state("normal")
|
|
return
|
|
idx = 0
|
|
while idx < total:
|
|
if cancel_requested.get():
|
|
status_var.set("Batch cancelled by user.")
|
|
break
|
|
fname = files[idx]
|
|
base_fname = os.path.basename(fname)
|
|
if base_fname in done_files:
|
|
progress_var.set(int(100 * (idx + 1) / total))
|
|
idx += 1
|
|
continue
|
|
dwg_path = fname
|
|
current_file_var.set(f"Processing: {base_fname} ({idx+1}/{total})")
|
|
try:
|
|
ok = process_single_file(
|
|
dwg_path, master_path.get(), template_path.get(), template_layout_name, idx, total, acad, docs, block_names, mode, log_operation=log_operation, error_callback=append_error
|
|
)
|
|
if ok:
|
|
done_files.add(base_fname)
|
|
save_progress(done_files)
|
|
delete_bak_for_dwg(dwg_path, log_operation=log_operation, error_callback=append_error)
|
|
progress_var.set(int(100 * (idx + 1) / total))
|
|
idx += 1
|
|
else:
|
|
append_error(f"Failed: {base_fname}")
|
|
idx += 1 # Continue to next file
|
|
except Exception as e:
|
|
append_error(f"Error processing {base_fname}: {e}")
|
|
idx += 1 # Continue to next file
|
|
if not cancel_requested.get() and len(done_files) >= total:
|
|
delete_all_bak_files(os.getcwd(), log_operation=log_operation, error_callback=append_error)
|
|
|
|
if not cancel_requested.get():
|
|
status_var.set("All files processed (or resumed to last file).")
|
|
current_file_var.set("")
|
|
except Exception as e:
|
|
append_error(f"Batch processing error: {e}")
|
|
status_var.set("Batch processing failed.")
|
|
finally:
|
|
set_buttons_state("normal")
|
|
try:
|
|
pythoncom.CoUninitialize()
|
|
except Exception:
|
|
pass
|
|
|
|
# --- Modern UI Layout ---
|
|
pad = {'padx': 16, 'pady': 8}
|
|
frame = tk.Frame(root, bg=BLACK)
|
|
frame.pack(fill="both", expand=True)
|
|
|
|
tk.Label(frame, text="Export Layouts Batch Processor", font=FONT_BOLD, fg=GOLD, bg=BLACK).pack(pady=(18, 10))
|
|
|
|
# Main tabbed interface
|
|
main_notebook = ttk.Notebook(frame)
|
|
main_notebook.pack(fill="both", expand=True, padx=16, pady=(0, 10))
|
|
|
|
# IO Tab
|
|
io_frame = tk.Frame(main_notebook, bg=BLACK)
|
|
network_frame = tk.Frame(main_notebook, bg=BLACK)
|
|
main_notebook.add(io_frame, text="IO")
|
|
main_notebook.add(network_frame, text="NETWORK")
|
|
|
|
# IO Tab Content
|
|
# Master DWG
|
|
row1 = tk.Frame(io_frame, bg=BLACK)
|
|
row1.pack(fill="x", **pad)
|
|
row1.grid_columnconfigure(1, weight=1)
|
|
tk.Label(row1, text="Master DWG (Xref):", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
|
|
master_entry = tk.Entry(row1, textvariable=master_path, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD)
|
|
master_entry.grid(row=0, column=1, sticky="ew", padx=(0,8))
|
|
ttk.Button(row1, text="Browse...", command=select_master).grid(row=0, column=2, sticky="e")
|
|
|
|
# Template DWG
|
|
row2 = tk.Frame(io_frame, bg=BLACK)
|
|
row2.pack(fill="x", **pad)
|
|
row2.grid_columnconfigure(1, weight=1)
|
|
tk.Label(row2, text="Template DWG:", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
|
|
template_entry = tk.Entry(row2, textvariable=template_path, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD)
|
|
template_entry.grid(row=0, column=1, sticky="ew", padx=(0,8))
|
|
ttk.Button(row2, text="Browse...", command=select_template).grid(row=0, column=2, sticky="e")
|
|
|
|
# Drawings files (multi-select)
|
|
row3 = tk.Frame(io_frame, bg=BLACK)
|
|
row3.pack(fill="x", **pad)
|
|
row3.grid_columnconfigure(1, weight=1)
|
|
tk.Label(row3, text="Drawings:", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
|
|
tk.Label(row3, textvariable=drawings_files_var, font=FONT, fg=GOLD, bg=BLACK, anchor="w").grid(row=0, column=1, sticky="ew", padx=(0,8))
|
|
ttk.Button(row3, text="Browse...", command=select_drawings_files).grid(row=0, column=2, sticky="e")
|
|
|
|
# Block names file
|
|
row4 = tk.Frame(io_frame, bg=BLACK)
|
|
row4.pack(fill="x", **pad)
|
|
row4.grid_columnconfigure(1, weight=1)
|
|
tk.Label(row4, text="Block Names File:", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
|
|
tk.Entry(row4, textvariable=names_file, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD).grid(row=0, column=1, sticky="ew", padx=(0,8))
|
|
ttk.Button(row4, text="Browse...", command=select_names_file).grid(row=0, column=2, sticky="e")
|
|
|
|
# Progress bar with white border
|
|
tk.Label(io_frame, text="Progress:", font=FONT, fg=GOLD, bg=BLACK, anchor="w").pack(fill="x", **pad)
|
|
pb_border = tk.Frame(io_frame, bg=WHITE, padx=2, pady=2)
|
|
pb_border.pack(pady=(0,10), padx=32, fill="x")
|
|
ttk.Progressbar(pb_border, variable=progress_var, maximum=100, length=500, style="TProgressbar").pack(fill="x")
|
|
|
|
# Status and current file
|
|
tk.Label(io_frame, textvariable=status_var, font=FONT, fg=WHITE, bg=BLACK).pack(fill="x", **pad)
|
|
tk.Label(io_frame, textvariable=current_file_var, font=FONT, fg=GOLD, bg=BLACK).pack(fill="x", **pad)
|
|
|
|
# Tabbed log area
|
|
log_notebook = ttk.Notebook(io_frame)
|
|
log_notebook.pack(padx=32, pady=(10,10), fill="both", expand=False)
|
|
op_frame = tk.Frame(log_notebook, bg=BLACK)
|
|
err_frame = tk.Frame(log_notebook, bg=BLACK)
|
|
log_notebook.add(op_frame, text="Operations Log")
|
|
log_notebook.add(err_frame, text="Error Log")
|
|
# Operations log with auto-hiding scrollbar
|
|
op_scroll = tk.Scrollbar(op_frame)
|
|
op_text = tk.Text(op_frame, height=7, width=80, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD, borderwidth=2, relief="groove", state="disabled", yscrollcommand=op_scroll.set, wrap="word")
|
|
op_scroll.config(command=op_text.yview)
|
|
op_text.pack(side="left", fill="both", expand=True)
|
|
# Error log with auto-hiding scrollbar
|
|
err_scroll = tk.Scrollbar(err_frame)
|
|
err_text = tk.Text(err_frame, height=7, width=80, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD, borderwidth=2, relief="groove", state="disabled", yscrollcommand=err_scroll.set, wrap="word")
|
|
err_scroll.config(command=err_text.yview)
|
|
err_text.pack(side="left", fill="both", expand=True)
|
|
|
|
def show_scrollbar_if_needed(text_widget, scrollbar):
|
|
text_widget.update_idletasks()
|
|
if text_widget.yview()[0] > 0 or text_widget.yview()[1] < 1:
|
|
scrollbar.pack(side="right", fill="y")
|
|
else:
|
|
scrollbar.pack_forget()
|
|
|
|
def update_op_log(*args):
|
|
op_text.config(state="normal")
|
|
op_text.delete(1.0, tk.END)
|
|
op_text.insert(tk.END, operations_log_var.get())
|
|
op_text.see(tk.END)
|
|
op_text.config(state="disabled")
|
|
show_scrollbar_if_needed(op_text, op_scroll)
|
|
def update_err_log(*args):
|
|
err_text.config(state="normal")
|
|
err_text.delete(1.0, tk.END)
|
|
err_text.insert(tk.END, error_log_var.get())
|
|
err_text.see(tk.END)
|
|
err_text.config(state="disabled")
|
|
show_scrollbar_if_needed(err_text, err_scroll)
|
|
operations_log_var.trace_add('write', update_op_log)
|
|
error_log_var.trace_add('write', update_err_log)
|
|
op_text.bind('<Configure>', lambda e: show_scrollbar_if_needed(op_text, op_scroll))
|
|
err_text.bind('<Configure>', lambda e: show_scrollbar_if_needed(err_text, err_scroll))
|
|
|
|
# Style the selected tab
|
|
style.layout("TNotebook.Tab", [
|
|
('Notebook.tab', {'sticky': 'nswe', 'children': [
|
|
('Notebook.padding', {'side': 'top', 'sticky': 'nswe', 'children': [
|
|
('Notebook.focus', {'side': 'top', 'sticky': 'nswe', 'children': [
|
|
('Notebook.label', {'side': 'top', 'sticky': ''})
|
|
]})
|
|
]})
|
|
]})
|
|
])
|
|
style.map("TNotebook.Tab",
|
|
background=[('selected', GOLD), ('!selected', BLACK)],
|
|
foreground=[('selected', BLACK), ('!selected', GOLD)])
|
|
|
|
# Buttons
|
|
btn_frame = tk.Frame(io_frame, bg=BLACK)
|
|
btn_frame.pack(pady=(10, 18))
|
|
run_btn = ttk.Button(btn_frame, text="Run Batch", command=start_batch)
|
|
run_btn.pack(side="left", padx=12)
|
|
start_over_btn = ttk.Button(btn_frame, text="Start Over", command=start_over)
|
|
start_over_btn.pack(side="left", padx=12)
|
|
cancel_btn = ttk.Button(btn_frame, text="Cancel", command=cancel_batch, state="disabled")
|
|
cancel_btn.pack(side="left", padx=12)
|
|
|
|
# NETWORK Tab Content
|
|
# Master DWG
|
|
n_row1 = tk.Frame(network_frame, bg=BLACK)
|
|
n_row1.pack(fill="x", **pad)
|
|
n_row1.grid_columnconfigure(1, weight=1)
|
|
tk.Label(n_row1, text="Master DWG (Xref):", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
|
|
n_master_entry = tk.Entry(n_row1, textvariable=master_path, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD)
|
|
n_master_entry.grid(row=0, column=1, sticky="ew", padx=(0,8))
|
|
ttk.Button(n_row1, text="Browse...", command=select_master).grid(row=0, column=2, sticky="e")
|
|
|
|
# Template DWG
|
|
n_row2 = tk.Frame(network_frame, bg=BLACK)
|
|
n_row2.pack(fill="x", **pad)
|
|
n_row2.grid_columnconfigure(1, weight=1)
|
|
tk.Label(n_row2, text="Template DWG:", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
|
|
n_template_entry = tk.Entry(n_row2, textvariable=template_path, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD)
|
|
n_template_entry.grid(row=0, column=1, sticky="ew", padx=(0,8))
|
|
ttk.Button(n_row2, text="Browse...", command=select_template).grid(row=0, column=2, sticky="e")
|
|
|
|
# Drawings files (multi-select)
|
|
n_row3 = tk.Frame(network_frame, bg=BLACK)
|
|
n_row3.pack(fill="x", **pad)
|
|
n_row3.grid_columnconfigure(1, weight=1)
|
|
tk.Label(n_row3, text="Drawings:", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
|
|
tk.Label(n_row3, textvariable=drawings_files_var, font=FONT, fg=GOLD, bg=BLACK, anchor="w").grid(row=0, column=1, sticky="ew", padx=(0,8))
|
|
ttk.Button(n_row3, text="Browse...", command=select_drawings_files).grid(row=0, column=2, sticky="e")
|
|
|
|
# Block names file
|
|
n_row4 = tk.Frame(network_frame, bg=BLACK)
|
|
n_row4.pack(fill="x", **pad)
|
|
n_row4.grid_columnconfigure(1, weight=1)
|
|
tk.Label(n_row4, text="Block Names File:", font=FONT, fg=GOLD, bg=BLACK, width=18, anchor="w").grid(row=0, column=0, sticky="w")
|
|
tk.Entry(n_row4, textvariable=names_file, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD).grid(row=0, column=1, sticky="ew", padx=(0,8))
|
|
ttk.Button(n_row4, text="Browse...", command=select_names_file).grid(row=0, column=2, sticky="e")
|
|
|
|
# Progress bar with white border
|
|
tk.Label(network_frame, text="Progress:", font=FONT, fg=GOLD, bg=BLACK, anchor="w").pack(fill="x", **pad)
|
|
n_pb_border = tk.Frame(network_frame, bg=WHITE, padx=2, pady=2)
|
|
n_pb_border.pack(pady=(0,10), padx=32, fill="x")
|
|
ttk.Progressbar(n_pb_border, variable=progress_var, maximum=100, length=500, style="TProgressbar").pack(fill="x")
|
|
|
|
# Status and current file
|
|
tk.Label(network_frame, textvariable=status_var, font=FONT, fg=WHITE, bg=BLACK).pack(fill="x", **pad)
|
|
tk.Label(network_frame, textvariable=current_file_var, font=FONT, fg=GOLD, bg=BLACK).pack(fill="x", **pad)
|
|
|
|
# Tabbed log area
|
|
n_log_notebook = ttk.Notebook(network_frame)
|
|
n_log_notebook.pack(padx=32, pady=(10,10), fill="both", expand=False)
|
|
n_op_frame = tk.Frame(n_log_notebook, bg=BLACK)
|
|
n_err_frame = tk.Frame(n_log_notebook, bg=BLACK)
|
|
n_log_notebook.add(n_op_frame, text="Operations Log")
|
|
n_log_notebook.add(n_err_frame, text="Error Log")
|
|
# Operations log with auto-hiding scrollbar
|
|
n_op_scroll = tk.Scrollbar(n_op_frame)
|
|
n_op_text = tk.Text(n_op_frame, height=7, width=80, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD, borderwidth=2, relief="groove", state="disabled", yscrollcommand=n_op_scroll.set, wrap="word")
|
|
n_op_scroll.config(command=n_op_text.yview)
|
|
n_op_text.pack(side="left", fill="both", expand=True)
|
|
# Error log with auto-hiding scrollbar
|
|
n_err_scroll = tk.Scrollbar(n_err_frame)
|
|
n_err_text = tk.Text(n_err_frame, height=7, width=80, font=FONT, fg=GOLD, bg=BLACK, insertbackground=GOLD, highlightbackground=GOLD, highlightcolor=GOLD, borderwidth=2, relief="groove", state="disabled", yscrollcommand=n_err_scroll.set, wrap="word")
|
|
n_err_scroll.config(command=n_err_text.yview)
|
|
n_err_text.pack(side="left", fill="both", expand=True)
|
|
|
|
def n_show_scrollbar_if_needed(text_widget, scrollbar):
|
|
text_widget.update_idletasks()
|
|
if text_widget.yview()[0] > 0 or text_widget.yview()[1] < 1:
|
|
scrollbar.pack(side="right", fill="y")
|
|
else:
|
|
scrollbar.pack_forget()
|
|
|
|
def n_update_op_log(*args):
|
|
n_op_text.config(state="normal")
|
|
n_op_text.delete(1.0, tk.END)
|
|
n_op_text.insert(tk.END, operations_log_var.get())
|
|
n_op_text.see(tk.END)
|
|
n_op_text.config(state="disabled")
|
|
n_show_scrollbar_if_needed(n_op_text, n_op_scroll)
|
|
def n_update_err_log(*args):
|
|
n_err_text.config(state="normal")
|
|
n_err_text.delete(1.0, tk.END)
|
|
n_err_text.insert(tk.END, error_log_var.get())
|
|
n_err_text.see(tk.END)
|
|
n_err_text.config(state="disabled")
|
|
n_show_scrollbar_if_needed(n_err_text, n_err_scroll)
|
|
operations_log_var.trace_add('write', n_update_op_log)
|
|
error_log_var.trace_add('write', n_update_err_log)
|
|
n_op_text.bind('<Configure>', lambda e: n_show_scrollbar_if_needed(n_op_text, n_op_scroll))
|
|
n_err_text.bind('<Configure>', lambda e: n_show_scrollbar_if_needed(n_err_text, n_err_scroll))
|
|
|
|
# Buttons
|
|
n_btn_frame = tk.Frame(network_frame, bg=BLACK)
|
|
n_btn_frame.pack(pady=(10, 18))
|
|
n_run_btn = ttk.Button(n_btn_frame, text="Run Batch", command=start_batch)
|
|
n_run_btn.pack(side="left", padx=12)
|
|
n_start_over_btn = ttk.Button(n_btn_frame, text="Start Over", command=start_over)
|
|
n_start_over_btn.pack(side="left", padx=12)
|
|
n_cancel_btn = ttk.Button(n_btn_frame, text="Cancel", command=cancel_batch, state="disabled")
|
|
n_cancel_btn.pack(side="left", padx=12)
|
|
|
|
root.mainloop()
|
|
|
|
if __name__ == "__main__":
|
|
main() |