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
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ qtmesh anim model.fbx --list # list animations
qtmesh anim model.fbx --list --json # list animations (JSON)
qtmesh anim model.fbx --rename "Take 001" "Idle" -o out.fbx # rename an animation
qtmesh anim base.fbx --merge walk.fbx run.fbx -o merged.fbx
qtmesh anim model.fbx --resample 30 -o optimized.fbx # resample to 30 keyframes
qtmesh anim model.fbx --decimate-step 5 -o lighter.fbx # keep every 5th keyframe
qtmesh anim model.fbx --resample 30 --animation "Walk" -o out.fbx # resample specific animation
qtmesh pose model.fbx --animation "Walk" --time 0.5 -o posed.stl # export single frame
qtmesh pose model.fbx --animation "Dance" --count 4 -o pose_%02d.stl # export N evenly spaced frames
qtmesh validate model.fbx # validate mesh (exit 1 if errors found)
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.22.0 LANGUAGES C CXX)
project(QtMeshEditor VERSION 2.23.0 LANGUAGES C CXX)
message(STATUS "Building QtMeshEditor version ${PROJECT_VERSION}")

set(QTMESHEDITOR_VERSION_STRING "\"${PROJECT_VERSION}\"")
Expand Down
242 changes: 242 additions & 0 deletions qml/PropertiesPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ Rectangle {

Component.onCompleted: content = validationComponent
}

// ---- Undo History ----
CollapsibleSection {
title: "Undo History"
expanded: false

Component.onCompleted: content = undoHistoryComponent
}
}
}

Expand All @@ -106,6 +114,37 @@ Rectangle {
property int nodeCount: treeModel ? treeModel.rowCount() : 0
property bool delegatesActive: true

// Scene header with reparent button
Row {
width: outlinerColumn.width
height: 22
spacing: 4

Text {
text: "\u25A1 Scene (Root)"
color: PropertiesPanelController.textColor
font.pixelSize: 11; font.bold: true
anchors.verticalCenter: parent.verticalCenter
leftPadding: 4
}

Item { width: 1; height: 1; Layout.fillWidth: true }

// "Move to Root" button — visible when a non-root node is selected
Rectangle {
visible: PropertiesPanelController.selectionName !== "" &&
PropertiesPanelController.canReparentNode(PropertiesPanelController.selectionName, "root")
width: toRootText.implicitWidth + 10; height: 18; radius: 3
anchors.verticalCenter: parent.verticalCenter
color: toRootMa.containsMouse ? PropertiesPanelController.highlightColor : PropertiesPanelController.headerColor
border.color: PropertiesPanelController.borderColor; border.width: 1
Text { id: toRootText; anchors.centerIn: parent; text: "\u2191 to Root"; color: PropertiesPanelController.textColor; font.pixelSize: 9 }
MouseArea { id: toRootMa; anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
onClicked: PropertiesPanelController.reparentNode(PropertiesPanelController.selectionName, "root")
}
}
Comment on lines +117 to +145

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's read the relevant section of the file to see the current state
cat -n qml/PropertiesPanel.qml | sed -n '110,155p'

Repository: fernandotonon/QtMeshEditor

Length of output: 2657


🏁 Script executed:

# Let's also check if there are any imports related to Layouts in the file
head -30 qml/PropertiesPanel.qml

Repository: fernandotonon/QtMeshEditor

Length of output: 810


🌐 Web query:

Qt Quick Row Layout.fillWidth behavior - does Row support Layout attached properties

💡 Result:

No. Row (QtQuick) is a positioner, not part of the Qt Quick Layouts system, so it does not honor Layout.* attached properties like Layout.fillWidth. Row just positions children; any “fill” behavior must be done via explicit width bindings/anchors (or spacers you size yourself). Row provides a different attached object (Positioner) for per-child positioning info, not Layout. [2]

If you want Layout.fillWidth / Layout.fillHeight behavior, use RowLayout (QtQuick.Layouts). In a RowLayout, child items support Layout.fillWidth and other Layout.* attached properties. [1]

Practical rule:

  • Use RowLayout when you want automatic resizing via Layout.*. [1]
  • Use Row when you only want simple positioning and will manage sizes yourself. [2]

Sources: RowLayout docs [1], Positioner/Row docs [2].


Layout.fillWidth won't work in Row—button won't right-align as intended.

Row is a positioner, not part of Qt Quick Layouts, so it ignores Layout.* attached properties. The spacer at line 131 won't expand, and the "↑ to Root" button stays packed next to the label.

Replace Row with Item and use explicit anchoring (or switch to RowLayout from QtQuick.Layouts):

Proposed fix
-            Row {
+            Item {
                 width: outlinerColumn.width
                 height: 22
-                spacing: 4

                 Text {
+                    id: sceneRootLabel
                     text: "\u25A1 Scene (Root)"
                     color: PropertiesPanelController.textColor
                     font.pixelSize: 11; font.bold: true
+                    anchors.left: parent.left
+                    anchors.leftMargin: 4
                     anchors.verticalCenter: parent.verticalCenter
-                    leftPadding: 4
                 }

-                Item { width: 1; height: 1; Layout.fillWidth: true }
-
                 // "Move to Root" button — visible when a non-root node is selected
                 Rectangle {
+                    anchors.right: parent.right
                     visible: PropertiesPanelController.selectionName !== "" &&
                              PropertiesPanelController.canReparentNode(PropertiesPanelController.selectionName, "root")
                     width: toRootText.implicitWidth + 10; height: 18; radius: 3
                     anchors.verticalCenter: parent.verticalCenter
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@qml/PropertiesPanel.qml` around lines 117 - 145, The current Row positioner
ignores the Layout.fillWidth spacer so the "↑ to Root" button won't right-align;
replace the Row with an Item (or switch to RowLayout from QtQuick.Layouts) and
use explicit anchors to position children: keep the container width =
outlinerColumn.width and height = 22, anchor the Text (the label with id
toRootText's sibling) to the left (anchors.left/verticalCenter), anchor the
"Move to Root" Rectangle to the right (anchors.right/verticalCenter) and remove
the Layout.fillWidth spacer Item, and ensure the Rectangle still references
toRootMa for hover and calls
PropertiesPanelController.reparentNode(PropertiesPanelController.selectionName,
"root") on click so behavior remains unchanged.

}

Repeater {
model: outlinerColumn.nodeCount

Expand Down Expand Up @@ -201,6 +240,52 @@ Rectangle {
TransformField { label: "Z"; value: PropertiesPanelController.scaleZ; color: "#4040c0"
onNewValue: function(val) { PropertiesPanelController.scaleZ = val } }
}

// Pivot Point
Text {
text: "Pivot Point (P)"
color: PropertiesPanelController.textColor
font.pixelSize: 11
font.bold: true
topPadding: 4
}
Row {
spacing: 4
width: parent.width - 16

property int activePivot: PropertiesPanelController.pivotMode

Repeater {
model: [
{ label: "Center", mode: 0 },
{ label: "Bottom", mode: 1 },
{ label: "Origin", mode: 2 }
]
delegate: Rectangle {
required property var modelData
required property int index
width: (parent.width - 8) / 3
height: 24
radius: 3
color: PropertiesPanelController.pivotMode === modelData.mode
? PropertiesPanelController.highlightColor
: PropertiesPanelController.inputColor
border.width: 1
border.color: PropertiesPanelController.borderColor

Text {
anchors.centerIn: parent
text: modelData.label
color: PropertiesPanelController.textColor
font.pixelSize: 10
}
MouseArea {
anchors.fill: parent
onClicked: PropertiesPanelController.pivotMode = modelData.mode
}
}
}
}
}
}

Expand Down Expand Up @@ -1253,4 +1338,161 @@ Rectangle {
}
}
}

// ---- Undo History Content ----
Component {
id: undoHistoryComponent

Column {
width: parent ? parent.width : 200
spacing: 0

property var historyEntries: PropertiesPanelController.undoHistory
property int currentIndex: PropertiesPanelController.undoIndex

// Empty state
Text {
visible: historyEntries.length === 0
text: "No undo history"
color: Qt.darker(PropertiesPanelController.textColor, 1.4)
font.pixelSize: 11
font.italic: true
padding: 8
}

// "Clean State" entry (index 0 — before any command)
Rectangle {
visible: historyEntries.length > 0
width: parent.width
height: 26
color: currentIndex === 0 ? PropertiesPanelController.highlightColor
: historyCleanMouse.containsMouse ? Qt.lighter(PropertiesPanelController.panelColor, 1.15)
: "transparent"

Row {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 8
spacing: 6

Text {
text: currentIndex === 0 ? "\u25B6" : ""
color: currentIndex === 0 ? "white" : PropertiesPanelController.textColor
font.pixelSize: 9
anchors.verticalCenter: parent.verticalCenter
}

Text {
text: "Initial State"
color: currentIndex === 0 ? "white" : Qt.darker(PropertiesPanelController.textColor, 1.2)
font.pixelSize: 11
font.italic: true
}
}

MouseArea {
id: historyCleanMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: PropertiesPanelController.undoToIndex(0)
}
}

// Command entries
Repeater {
model: historyEntries

Rectangle {
required property var modelData
required property int index

width: parent ? parent.width : 200
height: 26
color: {
var isActive = (index < currentIndex)
var isCurrent = (index === currentIndex - 1)
if (isCurrent) return PropertiesPanelController.highlightColor
if (historyEntryMouse.containsMouse) return Qt.lighter(PropertiesPanelController.panelColor, 1.15)
if (!isActive) return Qt.darker(PropertiesPanelController.panelColor, 1.05)
return "transparent"
}

Row {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 8
spacing: 6

Text {
text: (index === currentIndex - 1) ? "\u25B6" : ""
color: (index === currentIndex - 1) ? "white" : PropertiesPanelController.textColor
font.pixelSize: 9
anchors.verticalCenter: parent.verticalCenter
}

Text {
text: modelData.text || ("Command " + (index + 1))
color: {
var isActive = (index < currentIndex)
var isCurrent = (index === currentIndex - 1)
if (isCurrent) return "white"
if (!isActive) return Qt.darker(PropertiesPanelController.textColor, 1.4)
return PropertiesPanelController.textColor
}
font.pixelSize: 11
}
}

MouseArea {
id: historyEntryMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: PropertiesPanelController.undoToIndex(index + 1)
}
}
}

// Separator
Rectangle {
visible: historyEntries.length > 0
width: parent.width
height: 1
color: PropertiesPanelController.borderColor
}

// Clear History button
Rectangle {
visible: historyEntries.length > 0
width: parent.width - 16
height: 26
anchors.horizontalCenter: parent.horizontalCenter
radius: 3
color: clearHistoryMouse.pressed ? Qt.darker(PropertiesPanelController.headerColor, 1.2)
: clearHistoryMouse.containsMouse ? Qt.lighter(PropertiesPanelController.headerColor, 1.2)
: PropertiesPanelController.headerColor
border.color: PropertiesPanelController.borderColor
border.width: 1

Text {
anchors.centerIn: parent
text: "Clear History"
color: PropertiesPanelController.textColor
font.pixelSize: 11
}

MouseArea {
id: clearHistoryMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: PropertiesPanelController.clearUndoHistory()
}
}

// Bottom padding
Item { width: 1; height: 8 }
}
}
}
9 changes: 7 additions & 2 deletions qml/SceneTreeNode.qml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Column {
property bool hasChildren: childCount > 0
property string nodeName: treeModel ? (treeModel.data(nodeIndex) || "") : ""
property bool selected: false
// Only Node-type items are draggable (not entities/submeshes)
property bool isNodeType: treeModel ? (treeModel.data(nodeIndex, 259) === "Node" || treeModel.data(nodeIndex, 259) === "Group") : false

width: parent ? parent.width : 200

Expand All @@ -31,33 +33,36 @@ Column {

// Row for this node
Rectangle {
id: nodeRow
width: treeNode.width
height: 22
color: treeNode.selected
? PropertiesPanelController.highlightColor
: (rowMouse.containsMouse ? Qt.lighter(PropertiesPanelController.panelColor, 1.15)
: "transparent")

// Full-row mouse area for selection (behind everything)
// Full-row mouse area for selection
MouseArea {
id: rowMouse
anchors.fill: parent
hoverEnabled: true
// acceptedButtons default is Qt.LeftButton

onClicked: function(mouse) {
if (treeModel) {
var multiSelect = (mouse.modifiers & Qt.ControlModifier) ||
(mouse.modifiers & Qt.ShiftModifier)
treeModel.selectItem(nodeIndex.row, treeModel.parent(nodeIndex), multiSelect)
}
}

}

Row {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 4 + indentLevel * 16
spacing: 4
z: 10 // Above drop areas

// Expand/collapse chevron button
Item {
Expand Down
Loading
Loading