Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Three singletons manage core state. All run on the main thread. Access via `Clas

- **OgreWidget** (`src/OgreWidget.h/cpp`): QWidget subclass that creates an Ogre::RenderWindow from the native window handle.
- **EditorViewport** (`src/EditorViewport.h/cpp`): Wraps OgreWidget, runs render loop via QTimer.
- **MainWindow** (`src/mainwindow.h/cpp`): QMainWindow + Ogre::FrameListener. Contains viewports, toolbars, dock widgets.
- **MainWindow** (`src/mainwindow.h/cpp`): QMainWindow + Ogre::FrameListener. Contains viewports, toolbars, dock widgets. Right sidebar hosts the QML Inspector panel directly (no tab widget). Animation Control dock at bottom auto-shows for animated entities.

### Material Editor (QML)

Expand All @@ -95,6 +95,39 @@ Three singletons manage core state. All run on the main thread. Access via `Clas
- Visibility requires both the toggle (`setVisible`) and an active widget — hides automatically when viewports are closed and reappears when a new viewport gets focus.
- The QML window uses `Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint` and software rendering (`QQuickWindow::setSceneGraphBackend("software")`) to avoid GL conflicts with Ogre.

### Transform System

- **TransformOperator** (`src/TransformOperator.h/cpp`): Singleton implementing SELECT/TRANSLATE/ROTATE/SCALE modes. Owns three gizmos (TranslationGizmo, RotationGizmo, ScaleGizmo). Supports WORLD/LOCAL transform space. Mouse interaction: ray-cast gizmo for axis selection, plane intersection for drag transforms.
- **ScaleGizmo** (`src/ScaleGizmo.h/cpp`): Scale gizmo with cube handles at axis endpoints. Follows TranslationGizmo pattern (ManualObject per axis, highlight/fade).
- **Keyboard shortcuts** (Unity convention): `Q`=Select, `W`=Translate, `E`=Rotate, `R`=Scale, `F`=Frame selection, `X`=Toggle World/Local space.
- **SpaceCamera::frameSelection()**: Computes bounding sphere of selection and positions camera to fit it in view.

### Undo/Redo System

- **UndoManager** (`src/UndoManager.h/cpp`): Singleton wrapping `QUndoStack`. Push commands, undo/redo via `Ctrl+Z`/`Ctrl+Shift+Z`.
- **TransformCommands** (`src/commands/TransformCommands.h/cpp`): `TranslateCommand`, `RotateCommand`, `ScaleCommand`, `DeleteCommand`. Translate and Scale support command merging. State captured on mouse press, command pushed on mouse release in TransformOperator.

### QML Inspector Panel

- **PropertiesPanelController** (`src/PropertiesPanelController.h/cpp`): QML_SINGLETON providing transform values, selection state, scene tree model, primitive parameters, animation data (enable/loop/rename), skeleton debug toggles. Bridges all scene data to QML.
- **SceneTreeModel** (`src/SceneTreeModel.h/cpp`): QAbstractItemModel exposing hierarchical scene tree (Nodes → Entities → SubEntities) to QML. Supports multi-select, material name get/set on submeshes, debounced rebuild on scene changes.
- **PropertiesPanel.qml** (`qml/PropertiesPanel.qml`): Main inspector with collapsible sections:
- **Scene** — recursive tree view (SceneTreeNode.qml) with expand/collapse, Ctrl+click multi-select, material typeahead dropdown on submeshes
- **Transform** — position/rotation/scale spinbox fields with up/down arrow keys and buttons
- **Primitive** — context-sensitive fields per primitive type (size, radius, height, segments, UV)
- **Animations** — per-entity groups with enable/loop checkboxes, double-click rename, play/pause, skeleton/weights toggles
- **CollapsibleSection.qml**, **SceneTreeNode.qml**, **TransformField.qml** — reusable QML components.
- Loaded as QQuickWidget directly in the right dock (replaces old tab widget with Transform/Material/Edit/Animation tabs).

### Theme System

- **ThemeManager** (`src/ThemeManager.h/cpp`): QML_SINGLETON providing canonical theme colors synced from QPalette. All colors (window, panel, header, text, button, highlight, border, accent) derived from the active QPalette.

### Indie Game Dev Features

- **BatchExporter** (`src/BatchExporter.h/cpp`): Multi-file conversion wrapping CLIPipeline. Supports progress reporting.
- **MaterialPresetLibrary** (`src/MaterialPresetLibrary.h/cpp`): QML_SINGLETON providing one-click material presets (Plastic, Metal, Wood, Glass, Unlit, Wireframe).

### MCP Server

- **MCPServer** (`src/MCPServer.h/cpp`): JSON-RPC 2.0 over stdio + HTTP REST API on configurable port.
Expand Down Expand Up @@ -133,7 +166,7 @@ Three singletons manage core state. All run on the main thread. Access via `Clas

## Development Guidelines

- **UI: QML over Widgets.** New UI should be built in QML (Qt Quick), not Qt Widgets. The project is migrating from Widgets to QML. The Material Editor (`qml/`) is the reference for the QML approach. Existing Widget-based UI (`ui_files/`) remains but should not be extended.
- **UI: QML over Widgets.** New UI should be built in QML (Qt Quick), not Qt Widgets. The Inspector panel (`qml/PropertiesPanel.qml`) and Material Editor (`qml/MaterialEditorWindow.qml`) are the reference for the QML approach. The old Transform/Material/Edit/Animation tabs have been replaced by the QML Inspector. AnimationWidget and PrimitivesWidget still exist as hidden backing widgets but are not user-visible tabs.
- **Cross-platform: Windows, Linux (Ubuntu), macOS.** All code must compile and run on all three. Guard platform-specific APIs with `#ifdef Q_OS_WIN`, `#ifdef Q_OS_MACOS`, `#ifdef Q_OS_LINUX`. Test the CI build across all three platforms before merging.
- **Unit tests.** Add Google Test unit tests for new functionality. Test files live alongside source in `src/` with the `_test.cpp` suffix (e.g., `Manager_test.cpp`). CI runs tests only on Linux to save budget, so:
- Features that depend on optional components (e.g., local LLM / llama.cpp) may not be available in the test environment — guard with `#ifdef ENABLE_LOCAL_LLM` or skip gracefully.
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ cmake_minimum_required(VERSION 3.24.0)
cmake_policy(SET CMP0005 NEW)
cmake_policy(SET CMP0048 NEW) # manages project version

project(QtMeshEditor VERSION 2.15.1 LANGUAGES C CXX)
project(QtMeshEditor VERSION 2.16.0 LANGUAGES C CXX)
message(STATUS "Building QtMeshEditor version ${PROJECT_VERSION}")

set(QTMESHEDITOR_VERSION_STRING "\"${PROJECT_VERSION}\"")
Expand Down
44 changes: 44 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,50 @@ <h3 class="feature-title">CLI & Automation</h3>
</div>
</section>

<!-- Keyboard Shortcuts -->
<section class="section">
<h2 class="section-title">Keyboard Shortcuts</h2>
<p class="section-subtitle">Unity-style shortcuts for fast 3D editing workflows.</p>

<div class="features-grid">
<div class="feature-card">
<h3 class="feature-title">Transform Tools</h3>
<table style="width: 100%; border-collapse: collapse; font-family: 'Source Code Pro', monospace; font-size: 0.9rem;">
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold; width: 60px;">Q</td><td style="padding: 8px;">Select mode</td></tr>
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">W</td><td style="padding: 8px;">Translate (Move)</td></tr>
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">E</td><td style="padding: 8px;">Rotate</td></tr>
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">R</td><td style="padding: 8px;">Scale</td></tr>
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">X</td><td style="padding: 8px;">Toggle World / Local space</td></tr>
<tr><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">Del</td><td style="padding: 8px;">Delete selected</td></tr>
</table>
</div>

<div class="feature-card">
<h3 class="feature-title">Camera &amp; View</h3>
<table style="width: 100%; border-collapse: collapse; font-family: 'Source Code Pro', monospace; font-size: 0.9rem;">
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold; width: 120px;">F</td><td style="padding: 8px;">Frame selection (zoom to fit)</td></tr>
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">Middle Mouse</td><td style="padding: 8px;">Orbit camera</td></tr>
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">Right Mouse</td><td style="padding: 8px;">Pan camera</td></tr>
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">Scroll Wheel</td><td style="padding: 8px;">Zoom in / out</td></tr>
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">Shift + Middle</td><td style="padding: 8px;">Roll camera</td></tr>
<tr><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">Arrow Keys</td><td style="padding: 8px;">Rotate camera</td></tr>
</table>
</div>

<div class="feature-card">
<h3 class="feature-title">Edit &amp; Selection</h3>
<table style="width: 100%; border-collapse: collapse; font-family: 'Source Code Pro', monospace; font-size: 0.9rem;">
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold; width: 120px;">Ctrl + Z</td><td style="padding: 8px;">Undo</td></tr>
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">Ctrl + Shift + Z</td><td style="padding: 8px;">Redo</td></tr>
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">Ctrl + O</td><td style="padding: 8px;">Open Scene</td></tr>
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">Ctrl + S</td><td style="padding: 8px;">Save Scene</td></tr>
<tr style="border-bottom: 1px solid rgba(0,255,65,0.2);"><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">Ctrl + Click</td><td style="padding: 8px;">Add to selection</td></tr>
<tr><td style="padding: 8px; color: var(--neon-cyan); font-weight: bold;">Shift + Click</td><td style="padding: 8px;">Remove from selection</td></tr>
</table>
</div>
</div>
</section>

<!-- Animation Merging Detail -->
<section class="section">
<h2 class="section-title">Animation Merging</h2>
Expand Down
64 changes: 64 additions & 0 deletions qml/CollapsibleSection.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import PropertiesPanel 1.0

Column {
id: root

property string title: "Section"
property bool expanded: true
property bool sectionVisible: true
default property alias content: contentLoader.sourceComponent

visible: sectionVisible
width: parent ? parent.width : 200

Rectangle {
id: header
width: parent.width
height: 28
color: headerMouse.containsMouse ? Qt.lighter(PropertiesPanelController.headerColor, 1.1)
: PropertiesPanelController.headerColor
border.color: PropertiesPanelController.borderColor
border.width: 1

RowLayout {
anchors.fill: parent
anchors.leftMargin: 6
anchors.rightMargin: 6
spacing: 4

Text {
text: root.expanded ? "\u25BC" : "\u25B6"
color: PropertiesPanelController.textColor
font.pixelSize: 10
}

Text {
text: root.title
color: PropertiesPanelController.textColor
font.pixelSize: 12
font.bold: true
Layout.fillWidth: true
}
}

MouseArea {
id: headerMouse
anchors.fill: parent
hoverEnabled: true
activeFocusOnTab: true
onClicked: root.expanded = !root.expanded
Keys.onReturnPressed: root.expanded = !root.expanded
Keys.onSpacePressed: root.expanded = !root.expanded
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

Loader {
id: contentLoader
width: parent.width
active: root.expanded
visible: root.expanded
}
}
Loading
Loading