| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705 |
- #
- # Copyright (c) Contributors to the Open 3D Engine Project.
- # For complete copyright and license terms please see the LICENSE at the root of this distribution.
- #
- # SPDX-License-Identifier: Apache-2.0 OR MIT
- #
- r'''
- ClassWizard.py
- GUI Mode Examples:
- Windows:
- PS C:\o3de> C:\o3de\python\python.cmd '.\Tools\ClassCreationWizard\ClassWizard.py' `
- --engine-path C:\o3de `
- --project-path C:\o3de\user\myproject
- Linux:
- $ ~/o3de$ ./python/python.sh Tools/ClassCreationWizard/ClassWizard.py \
- --engine-path /home/yourusername/o3de \
- --project-path /home/yourusername/o3de/user/myproject
- Example GUI Input:
- Component Details:
- Component Name: Image
- Component Type: Default
- Namespace: myproject
- Project Directory: C:\o3de\user\myproject\Gem
- Settings:
- [X] Add to project
- [X] Default License
- Non-GUI Mode Examples:
- Activated using the flags: --component-name
- Windows:
- PS C:\o3de> C:\o3de\python\python.cmd '.\Tools\ClassCreationWizard\ClassWizard.py' `
- --engine-path C:\o3de `
- --project-path C:\o3de\user\myproject `
- --component-name Image `
- --component-type Default `
- --namespace myproject `
- --add-to-project `
- --default-license
- Linux:
- $ ~/o3de$ ./python/python.sh Tools/ClassCreationWizard/ClassWizard.py \
- --engine-path $HOME/o3de/ \
- --project-path $HOME/o3de/user/myproject \
- --component-name Image \
- --component-type Default \
- --namespace myproject \
- --add-to-project \
- --default-license
- Required Arguments:
- --engine-path PATH Path to O3DE engine root
- Optional Arguments:
- --project-path PATH Path to O3DE project (required for non-GUI)
- --component-name NAME Component name (required for non-GUI)
- --component-type TYPE Component type: Default or Editor (required for non-GUI)
- --namespace NAME Component namespace (required for non-GUI)
- --add-to-project Automatically add to project's Gem folder
- --default-license Include default license
- '''
- import argparse
- import os
- import subprocess
- import sys
- import traceback
- from pathlib import Path
- import tkinter as tk
- import tkinter.font as tkFont
- from tkinter import filedialog, ttk
- LOCK_FILE = Path(__file__).parent / ".lock"
- def check_instance() -> bool:
- """Check if another instance is running"""
- if LOCK_FILE.exists():
- print("Another instance may already be running.")
- return False
- LOCK_FILE.touch()
- return True
- def remove_lock():
- """Remove the lock file on exit"""
- try:
- if LOCK_FILE.exists():
- LOCK_FILE.unlink()
- except:
- pass
- def validate_path(path: str) -> Path:
- """Validates path"""
- try:
- _path = Path(os.path.expanduser(path)).resolve()
- if not _path.exists():
- raise argparse.ArgumentTypeError(f"Path does not exist: {_path.name}")
- if not _path.is_dir():
- raise argparse.ArgumentTypeError(f"Not a directory: {_path.name}")
- return _path
- except Exception as e:
- raise argparse.ArgumentTypeError(f"Invalid path: {str(e)}")
- def validate_engine_path(path: str) -> Path:
- """Validates an O3DE engine path"""
- engine_path = validate_path(path)
- if not (engine_path / "engine.json").exists():
- raise argparse.ArgumentTypeError(
- f" Not a valid O3DE engine directory: {engine_path}\n"
- " Hint: engine.json file not found.\n"
- " Make sure you're pointing to the root of an O3DE engine directory.\n"
- " Example: --engine-path C:\\o3de"
- )
- return engine_path
- def validate_component_name(component_name, log=None) -> bool:
- """Validate the component name"""
- KEYWORDS = {
- 'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto',
- 'bitand', 'bitor', 'bool', 'break', 'case', 'catch',
- 'char', 'char8_t', 'char16_t', 'char32_t', 'class', 'compl',
- 'concept', 'const', 'consteval', 'constexpr', 'const_cast', 'continue',
- 'co_await', 'co_return', 'co_yield', 'decltype', 'default', 'delete',
- 'do', 'double', 'dynamic_cast', 'else', 'enum', 'explicit',
- 'export', 'extern', 'false', 'float', 'for', 'friend',
- 'goto', 'if', 'inline', 'int', 'long', 'mutable',
- 'namespace', 'new', 'noexcept', 'not', 'not_eq', 'nullptr',
- 'operator', 'or', 'or_eq', 'private', 'protected', 'public',
- 'register', 'reinterpret_cast','requires', 'return', 'short', 'signed',
- 'sizeof', 'static', 'static_assert','static_cast', 'struct', 'switch',
- 'template', 'this', 'thread_local', 'throw', 'true', 'try',
- 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using',
- 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'xor',
- 'xor_eq'
- }
- if not component_name:
- if log:
- log("Error: The name cannot be empty.")
- return False
- if (invalid := next((c for c in '*?+-,;=&%$`"\'/\\[]{}~#|<>!^@()#: \t\n\r\f\v' if c in component_name), None)):
- log and log(f"The name contains invalid character: {invalid}");
- return False
- if (
- not (component_name[0].isalpha() or component_name[0] == '_')
- or component_name.startswith('__')
- or (component_name.startswith('_') and len(component_name) > 1 and component_name[1].isupper())
- ):
- if log:
- log("Error: The name must start with a letter or single underscore.")
- return False
- if component_name in KEYWORDS:
- if log:
- log(f"Error: '{component_name}' is a C++ keyword. Please choose a different name.")
- return False
- return True
- def add_component_to_project(component_path: Path, component_name: str, namespace: str, log=None) -> bool:
- """Automatically integrates the component into the project's Gem folder."""
- def log_message(message):
- if log:
- log(message)
- else:
- print(message)
- try:
- log_message(f"Adding component to the project...")
- # Update {namespace}Module.cpp
- module_path = component_path / "Source" / f"{namespace}Module.cpp"
- if not module_path.exists():
- log_message(f"Error: Module file not found at {module_path}")
- log_message(" Hint: Make sure the Project Directory points to a valid Gem directory, not the root of a project. Usually it's where *_files.cmake resides.")
- return False
- with open(module_path, 'r', encoding='utf-8') as f:
- lines = f.read().splitlines()
- include_line = f'#include "{component_name}Component.h"'
- descriptor_line = f'{component_name}Component::CreateDescriptor()'
- # Insert include if not present
- if not any(include_line in line for line in lines):
- last_include_idx = max(i for i, line in enumerate(lines) if line.strip().startswith('#include'))
- lines.insert(last_include_idx + 1, include_line)
- # Insert descriptor if not present
- descriptor_inserted = any(descriptor_line in line for line in lines)
- if not descriptor_inserted:
- for i, line in enumerate(lines):
- if 'm_descriptors.insert' in line:
- insert_start = i
- break
- else:
- insert_start = -1
- if insert_start != -1:
- # Find the line with closing "});"
- for j in range(insert_start, len(lines)):
- if '});' in lines[j]:
- descriptor_end = j
- break
- else:
- descriptor_end = -1
- if descriptor_end != -1:
- # Determine indentation
- for k in range(insert_start, descriptor_end):
- if 'CreateDescriptor()' in lines[k]:
- indent = lines[k][:len(lines[k]) - len(lines[k].lstrip())]
- break
- else:
- indent = ' ' * 16
- # Insert new descriptor before closing
- prev_line_idx = descriptor_end - 1
- if not lines[prev_line_idx].strip().endswith(','):
- lines[prev_line_idx] = lines[prev_line_idx].rstrip() + ','
- # Insert new descriptor line
- lines.insert(descriptor_end, f'{indent}{descriptor_line},')
- # Write the generated content to the module_path with UTF-8 encoding
- with open(module_path, 'w', encoding='utf-8', newline='\n') as f:
- f.write('\n'.join(lines) + '\n')
- # Update {namespace}_files.cmake
- project_files_path = component_path / f"{namespace.lower()}_files.cmake"
- if not project_files_path.exists():
- log_message(f"Error: Could not find {project_files_path}")
- return False
- with open(project_files_path, 'r', encoding='utf-8') as f:
- content = f.read()
- # Add .h/.cpp files if not present
- new_files = [
- f' Source/{component_name}Component.cpp\n',
- f' Source/{component_name}Component.h\n'
- ]
- files_section_start = content.find('set(FILES')
- if files_section_start != -1:
- files_section_end = content.find(')', files_section_start)
- if files_section_end != -1:
- for new_file in new_files:
- if new_file not in content:
- content = content[:files_section_end] + new_file + content[files_section_end:]
- # Write the generated content to the project_files_path with UTF-8 encoding
- with open(project_files_path, 'w', encoding='utf-8') as f:
- f.write(content)
- return True
- except Exception as e:
- log_message(f"Error adding component: {str(e)}")
- return False
- def create_default_component(engine_path, project_dir, namespace, component_name,
- add_to_project=False, default_license=False, log=None) -> bool:
- """Creates a new default component with the specified parameters."""
- def log_message(message):
- if log:
- log(message)
- else:
- print(message)
- try:
- script_name = "o3de.bat" if sys.platform == "win32" else "o3de.sh"
- o3de_script = Path(engine_path) / "scripts" / script_name
- cmd = [
- str(o3de_script),
- "create-from-template",
- "-dp", str(project_dir),
- "-dn", component_name,
- "-tn", "DefaultComponent",
- "-r", "${GemName}", namespace
- ]
- if default_license:
- cmd.append("--keep-license-text")
- cmd.append("--force")
- log(f"Creating component: {component_name}...")
- result = subprocess.run(
- cmd,
- cwd=engine_path,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- text=True,
- check=True
- )
- if result.stdout:
- for line in result.stderr.splitlines():
- if not line or line.replace('.', '').isdigit():
- continue
- if '[INFO]' not in line and '[WARNING]' not in line:
- log_message("" + line)
- if result.stderr:
- for line in result.stderr.splitlines():
- if '[INFO]' not in line and '[WARNING]' not in line:
- log_message("" + line)
- log_message(f"Successfully created component: {component_name}")
- if add_to_project:
- success = add_component_to_project(Path(project_dir), component_name, namespace, log)
- if not success:
- log_message("Warning: Failed to automatically add the component to the project.")
- else:
- log_message("Successfully added component. The project may need to be rebuilt.")
- return True
- except subprocess.CalledProcessError as e:
- log_message(f"Failed to create component (exit code {e.returncode})")
- if e.stderr:
- log_message("" + e.stderr)
- return False
- except Exception as e:
- log_message(f"Error: {str(e)}")
- return False
- def create_editor_component(engine_path, project_dir, namespace, component_name,
- add_to_project=False, default_license=False, log=None) -> bool:
- """Creates an editor component with the specified parameters."""
- if log:
- log("Error: Editor component is not yet implemented. Please use 'Default' component type.")
- return False
- def create_component(engine_path, project_dir, namespace, component_name,
- component_type="Default", add_to_project=False, default_license=False, log=None)-> bool:
- """Creates a new O3DE component of the specified type."""
- if component_type == "Default":
- status = create_default_component(
- engine_path=engine_path,
- project_dir=project_dir,
- namespace=namespace,
- component_name=component_name,
- add_to_project=add_to_project,
- default_license=default_license,
- log=print
- )
- elif component_type == "Editor":
- status = create_editor_component(
- engine_path=engine_path,
- project_dir=project_path,
- namespace=namespace,
- component_name=component_name,
- add_to_project=add_to_project,
- default_license=default_license,
- log=print
- )
- return status
- class Tooltip:
- """Tooltip class for displaying text when hovering over a widget."""
- def __init__(self, widget, text, delay=515):
- self.widget = widget
- self.text = text
- self.tooltip = None
- self.delay = delay
- self.id = None
- self.widget.bind("<Enter>", self.start_timer)
- self.widget.bind("<Leave>", self.hide)
- def show(self, event=None):
- """Display the tooltip near the mouse pointer when hovering over the widget."""
- if self.tooltip:
- return
- x, y, _, _ = self.widget.bbox("insert")
- x += self.widget.winfo_rootx() + 25
- y += self.widget.winfo_rooty() + 25
- self.tooltip = tk.Toplevel(self.widget)
- self.tooltip.wm_overrideredirect(True)
- self.tooltip.wm_geometry(f"+{x}+{y}")
- label = ttk.Label(
- self.tooltip,
- text=self.text,
- background="#ffffe0",
- foreground="black",
- relief="solid",
- borderwidth=0,
- padding=5,
- wraplength=380)
- label.pack()
- def start_timer(self, event=None):
- self.id = self.widget.after(self.delay, self.show)
- def hide(self, event=None):
- """Hide the tooltip when the mouse leaves the widget."""
- if self.id:
- self.widget.after_cancel(self.id)
- self.id = None
- if self.tooltip:
- self.tooltip.destroy()
- self.tooltip = None
- class NewComponentWindow:
- """GUI window for creating a new component in an O3DE project.
- This class allows users to define settings such as the component name, type,
- namespace, and project location. It supports automatic integration with a project's Gem folder.
- """
- def __init__(self, root, engine_path, project_path):
- self.root = root
- self.engine_path = engine_path
- self.project_path = project_path
- self.namespace = tk.StringVar(value=project_path.parent.stem if project_path else "")
- self.root.title("Add C++ Component")
- self.root.minsize(300, 480) if sys.platform == "win32" else self.root.minsize(300, 500)
- self.root.geometry("500x480") if sys.platform == "win32" else self.root.geometry("500x500")
- self.root.protocol("WM_DELETE_WINDOW", self.close_window)
- self.root.columnconfigure(1, weight=1)
- # default style to all ttk widgets
- style = ttk.Style()
- style.theme_use('clam')
- self.root.configure(bg="#444444")
- self.root.option_add("*TEntry.Font", ("TkDefaultFont", 10))
- self.root.option_add("*TCombobox.Font", ("TkDefaultFont", 10))
- default_font = tkFont.nametofont("TkDefaultFont")
- default_font.configure(family="Sans Serif", size=10)
- style.configure('.', background='#444444', foreground='#8C8C8C')
- style.configure("C.TLabelframe", background="#444444", bordercolor="#4E4E4E", borderwidth=1, relief="solid")
- style.configure("C.TButton", background="#444444", bordercolor="#4E4E4E", borderwidth=1, relief="solid")
- # Main container
- main_frame = ttk.Frame(root, padding="10")
- main_frame.pack(fill=tk.BOTH, expand=True)
- # Component Details
- details_frame = ttk.LabelFrame(main_frame, text=" Component Details ", padding="10", style="C.TLabelframe")
- details_frame.pack(fill=tk.X, pady=5)
- # Configure grid for alignment
- for i in range(3):
- details_frame.columnconfigure(i, weight=1 if i == 1 else 0)
- # Row 0: Component Name
- ttk.Label(details_frame, text="Component Name:").grid(
- row=0, column=0, sticky="e", padx=5, pady=5)
- self.component_name = ttk.Entry(details_frame)
- self.component_name.grid(row=0, column=1, columnspan=1, sticky="ew", padx=5, pady=5)
- Tooltip(self.component_name, text="Enter the base name of your C++ component. \nThe template appends the word 'Component'.")
- # Row 1: Component Type
- ttk.Label(details_frame, text="Component Type:").grid(
- row=1, column=0, sticky="e", padx=5, pady=5)
- def on_component_select(event):
- """Component type selection"""
- if self.component_type.get() == "Editor":
- self.component_type.set("Default")
- self.clear_log()
- self.log_message("Info: Editor type is not yet implemented.")
- self.component_type = ttk.Combobox(
- details_frame,
- values=["Default", ("Editor")],
- state="readonly",
- width=18)
- self.component_type.current(0)
- self.component_type.grid(row=1, column=1, sticky="ew", padx=5, pady=5)
- self.component_type.bind("<<ComboboxSelected>>", on_component_select)
- Tooltip(self.component_type, "Select component type: 'Default' for runtime, 'Editor' for editor-specific functionality.")
- # Row 2: Namespace
- ttk.Label(details_frame, text="Namespace:").grid(
- row=2, column=0, sticky="e", padx=5, pady=5)
- self.namespace_entry = ttk.Entry(
- details_frame,
- textvariable=self.namespace)
- self.namespace_entry.grid(row=2, column=1, sticky="ew", padx=5, pady=5)
- Tooltip(self.namespace_entry, "Enter the C++ namespace for your component.\nThis is usually your project name.")
- # Empty cell for alignment
- ttk.Frame(details_frame, width=10).grid(row=2, column=2)
- # Row 3: Project Directory
- ttk.Label(details_frame, text="Project Directory:").grid(
- row=3, column=0, sticky="e", padx=5, pady=5)
- self.project_dir_var = tk.StringVar(value=str(project_path))
- self.project_dir_entry = ttk.Entry(
- details_frame,
- textvariable=self.project_dir_var)
- self.project_dir_entry.grid(row=3, column=1, sticky="ew", padx=5, pady=5)
- Tooltip(
- self.project_dir_entry,
- "Specifies the destination directory where the component will be created. "
- "To automatically add the component to the project, this must point to a valid Gem folder within the project. "
- "In that case, 'Add to project' must be checked.")
- # Browse Button
- self.browse_btn = ttk.Button(
- details_frame,
- text="...",
- width=3,
- command=self.browse_project_dir,
- style="C.TButton")
- self.browse_btn.grid(row=3, column=2, sticky="e", padx=5, pady=5)
- Tooltip(self.browse_btn, "Browse for a different project's Gem folder or destination directory.")
- # Settings Section
- settings_frame = ttk.LabelFrame(main_frame, text=" Settings ", padding="10", style="C.TLabelframe")
- settings_frame.pack(fill=tk.X, pady=5)
- # Checkboxes
- self.add_to_project = tk.BooleanVar(value=False)
- cmake_cb = ttk.Checkbutton(
- settings_frame,
- text="Add to project",
- variable=self.add_to_project,
- onvalue=True,
- offvalue=False)
- cmake_cb.pack(anchor="w", pady=2)
- Tooltip(cmake_cb, "Automatically add this component to the Gem's private CMake source files.")
- self.default_license = tk.BooleanVar(value=False)
- license_cb = ttk.Checkbutton(
- settings_frame,
- text="Default License",
- variable=self.default_license,
- onvalue=True,
- offvalue=False)
- license_cb.pack(anchor="w", pady=2)
- Tooltip(license_cb, "Include the default license header in the source files.")
- # Log Section
- log_frame = ttk.LabelFrame(main_frame, text=" Log ", padding="10", style="C.TLabelframe")
- log_frame.pack(fill=tk.BOTH, expand=True, pady=5)
- self.log_text = tk.Text(log_frame, height=3, state="disabled", bg="#444444", fg="#c4c4c4", relief="flat", bd=0,
- highlightthickness=0, highlightbackground="#444444", highlightcolor="#444444")
- self.log_text.pack(fill=tk.BOTH, expand=True)
- # Button Frame
- button_frame = ttk.Frame(main_frame)
- button_frame.pack(fill=tk.X, pady=5)
- ok_btn = ttk.Button(
- button_frame,
- text="Create",
- command=self.on_ok,
- style="C.TButton")
- ok_btn.pack(side="right", padx=5)
- Tooltip(ok_btn, "Create the component using the specified settings.")
- cancel_btn = ttk.Button(
- button_frame,
- text="Cancel",
- command=self.on_cancel,
- style="C.TButton")
- cancel_btn.pack(side="right")
- Tooltip(cancel_btn, "Close this window without creating a component.")
- def close_window(self):
- """Centralized for all close operations"""
- remove_lock()
- self.root.destroy()
- def log_message(self, message):
- """ Append a message to the log frame"""
- self.log_text.config(state="normal")
- self.log_text.insert("end", message + "\n")
- self.log_text.see("end")
- self.log_text.config(state="disabled")
- def clear_log(self):
- """Clear all content from the log"""
- self.log_text.config(state="normal")
- self.log_text.delete("1.0", "end")
- self.log_text.config(state="disabled")
- def browse_project_dir(self):
- """Open directory dialog to select project path"""
- selected_path = filedialog.askdirectory(
- title="Select Project Directory",
- initialdir=self.project_dir_var.get())
- if selected_path:
- self.project_dir_var.set(selected_path)
- self.clear_log()
- self.log_message(f"Project directory: {selected_path}")
- def on_cancel(self):
- """Close the window"""
- self.close_window()
- def on_ok(self):
- """Create the component using the specified settings"""
- self.clear_log()
- component_name = self.component_name.get().strip()
- if not component_name:
- self.log_message("Error: Component name is required!")
- return
- if not validate_component_name(component_name, log=self.log_message):
- return
- namespace = self.namespace.get().strip()
- if not namespace:
- self.log_message("Error: Namespace is required!")
- return
- if not validate_component_name(namespace, log=self.log_message):
- return
- component_type = self.component_type.get()
- project_dir = self.project_dir_var.get().strip()
- if not os.path.isdir(project_dir):
- self.log_message(f"Error: Project directory {project_dir} does not exist.")
- return
- add_to_project=self.add_to_project.get()
- self.log_message("Please wait...")
- self.root.update_idletasks()
- if component_type == "Default":
- create_default_component(engine_path=self.engine_path, project_dir=project_dir, component_name=component_name,
- namespace=namespace, add_to_project=add_to_project, default_license=self.default_license.get(), log=self.log_message)
- elif component_type == "Editor":
- create_editor_component(engine_path=self.engine_path, project_dir=project_dir, component_name=component_name,
- namespace=namespace, add_to_project=add_to_project, default_license=self.default_license.get(), log=self.log_message)
- def main():
- """
- Supports both GUI and command-line modes.
- Parses command line arguments, validates paths, and initiates either:
- GUI mode: Interactive Tkinter interface for creating components
- Non-GUI mode: Automated component creation using command-line arguments
- """
- # Check if an instance of the application is already running
- if not check_instance():
- sys.exit(1)
- try:
- # Command line arguments for the script
- # GUI mode
- parser = argparse.ArgumentParser()
- parser.add_argument("--engine-path", required=True, type=validate_engine_path, help="Path to O3DE engine")
- parser.add_argument("--project-path", nargs='?', default=None, type=validate_path, help="Path to O3DE project")
- # Non-GUI mode
- parser.add_argument("--component-name", help="Component name")
- parser.add_argument("--component-type", choices=["Default", "Editor"], help="Default or Editor")
- parser.add_argument("--namespace", help="Namespace")
- parser.add_argument("--default-license", action="store_true", help="Include default license")
- parser.add_argument("--add-to-project", action="store_true", help="Add to project's Gem folder")
- args, unknown = parser.parse_known_args()
- if unknown:
- print(f"Please check your input for typos or unquoted special characters!")
- sys.exit(1)
- engine_path = args.engine_path
- project_path = (args.project_path / "Gem") if (args.project_path and "Gem" not in args.project_path.parts) else args.project_path
- if args.component_name:
- if not args.project_path:
- print("Error: --project-path is required in non-GUI mode.")
- sys.exit(1)
- if not args.component_type:
- print("Error: --component-type is required in non-GUI mode.")
- sys.exit(1)
- if not validate_component_name(args.component_name, log=print):
- print(f"Error: --component-name is required in non-GUI mode. Please provide valid --component-name argument.")
- sys.exit(1)
- if not validate_component_name(args.namespace, log=print):
- print("Error: --namespace is required in non-GUI mode. Please provide valid --namespace argument.")
- sys.exit(1)
- success = create_component(
- engine_path=engine_path,
- project_dir=project_path,
- namespace=args.namespace,
- component_name=args.component_name,
- component_type=args.component_type,
- add_to_project=args.add_to_project,
- default_license=args.default_license,
- log=print
- )
- sys.exit(0 if success else 1)
- else:
- # Initialize the main Tkinter window
- root = tk.Tk()
- # Window icon setup (PNG format)
- icon_path = Path(engine_path).joinpath("Assets", "Editor", "UI", "Icons", "Editor Settings Manager.png")
- if not icon_path.exists():
- print(f"Icon not found at: {icon_path}")
- else:
- img = tk.PhotoImage(file=icon_path)
- root.iconphoto(True, img)
- # Create and run the main application window
- app = NewComponentWindow(root, engine_path, project_path)
- root.mainloop()
- except Exception:
- traceback.print_exc()
- sys.exit(1)
- finally:
- # Remove lock file before exiting
- remove_lock()
- if __name__ == "__main__":
- main()
|