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
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.29.0 LANGUAGES C CXX)
project(QtMeshEditor VERSION 2.30.0 LANGUAGES C CXX)
message(STATUS "Building QtMeshEditor version ${PROJECT_VERSION}")

set(QTMESHEDITOR_VERSION_STRING "\"${PROJECT_VERSION}\"")
Expand Down
139 changes: 128 additions & 11 deletions qml/PropertiesPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -1181,20 +1181,11 @@ Rectangle {
spacing: 6
width: parent.width - 16

ComboBox {
ThemedComboBox {
id: exportFormatCombo
width: 90; height: 26
model: ["gltf", "glb", "fbx", "obj", "mesh"]
background: Rectangle {
color: PropertiesPanelController.headerColor
border.color: PropertiesPanelController.borderColor; border.width: 1; radius: 3
}
contentItem: Text {
leftPadding: 6
text: exportFormatCombo.displayText
color: PropertiesPanelController.textColor; font.pixelSize: 11
verticalAlignment: Text.AlignVCenter
}
font.pixelSize: 11
}

Rectangle {
Expand Down Expand Up @@ -1641,11 +1632,38 @@ Rectangle {

// Expanded content
Column {
id: entityGroupColumn
visible: groupExpanded
width: parent.width
spacing: 2
leftPadding: 8

// Per-entity tolerance preset for the Simplify button.
// Stored on the column so every animation row in this group reads
// the same value.
property string simplifyPreset: "balanced"

// Tolerance preset row
Row {
spacing: 6; topPadding: 4; bottomPadding: 2
Text {
text: "Simplify tolerance:"
color: PropertiesPanelController.textColor; font.pixelSize: 10
anchors.verticalCenter: parent.verticalCenter
}
ThemedComboBox {
id: simplifyPresetCombo
width: 130; height: 22
model: ["Conservative", "Balanced", "Aggressive"]
currentIndex: 1
font.pixelSize: 10
onActivated: {
var v = ["conservative", "balanced", "aggressive"][currentIndex]
entityGroupColumn.simplifyPreset = v
}
}
}

// Animation rows
Repeater {
model: grp.animations
Expand Down Expand Up @@ -1694,6 +1712,66 @@ Rectangle {
onEditingFinished: { if (text.length > 0 && text !== modelData.name) PropertiesPanelController.renameAnimation(grp.entity, modelData.name, text); visible = false }
Keys.onEscapePressed: visible = false
}

// Simplify button — removes redundant keyframes from this animation.
// Hidden for non-skeletal entities since simplifyAnimation
// requires skeletal node tracks.
Rectangle {
id: simplifyBtn
visible: grp.hasSkeleton
width: 22; height: 18; radius: 3
anchors.verticalCenter: parent.verticalCenter
color: simplifyMouse.pressed ? Qt.darker(PropertiesPanelController.headerColor, 1.2)
: simplifyMouse.containsMouse ? Qt.lighter(PropertiesPanelController.headerColor, 1.2)
: PropertiesPanelController.headerColor
border.color: PropertiesPanelController.borderColor; border.width: 1

// Lazy-cached redundancy analysis. The walk is O(N keyframes),
// ~25ms on a Mixamo clip — running it from a binding fires on
// every delegate refresh, which dominates the inspector frame
// budget. We compute on first hover, then re-run whenever the
// user changes the preset.
property var cachedAnalysis: null
function refreshAnalysis() {
cachedAnalysis = PropertiesPanelController.analyzeAnimationKeyframes(
grp.entity, modelData.name, entityGroupColumn.simplifyPreset)
}
Connections {
target: entityGroupColumn
function onSimplifyPresetChanged() { simplifyBtn.cachedAnalysis = null }
}

Text {
anchors.centerIn: parent
text: "\u2702" // scissors — keyframe trimming
color: PropertiesPanelController.textColor; font.pixelSize: 11
}
ToolTip.visible: simplifyMouse.containsMouse
ToolTip.delay: 600
ToolTip.text: {
var a = simplifyBtn.cachedAnalysis
if (!a) return "Simplify (hover to analyze…)"
if (a.total === 0) return "Simplify (analyze unavailable)"
return "Simplify (" + entityGroupColumn.simplifyPreset + "): "
+ a.redundant + " of " + a.total
+ " keyframes redundant (" + a.percent.toFixed(1) + "%)"
}
MouseArea {
id: simplifyMouse; anchors.fill: parent; hoverEnabled: true
onEntered: {
if (!simplifyBtn.cachedAnalysis)
simplifyBtn.refreshAnalysis()
}
onClicked: {
var preset = entityGroupColumn.simplifyPreset
var removed = PropertiesPanelController.simplifyAnimation(grp.entity, modelData.name, preset)
simplifyResultPopup.removed = removed
simplifyResultPopup.animName = modelData.name
simplifyResultPopup.open()
simplifyBtn.cachedAnalysis = null // invalidate after mutation
}
}
}
}
}
}
Expand Down Expand Up @@ -1903,4 +1981,43 @@ Rectangle {
Item { width: 1; height: 8 }
}
}

// Result popup for the per-animation Simplify button.
Popup {
id: simplifyResultPopup
modal: true; focus: true
anchors.centerIn: Overlay.overlay
padding: 16
property int removed: 0
property string animName: ""
background: Rectangle {
color: PropertiesPanelController.panelColor
border.color: PropertiesPanelController.borderColor; border.width: 1
radius: 4
}
contentItem: Column {
spacing: 10
Text {
text: simplifyResultPopup.removed > 0
? "Removed " + simplifyResultPopup.removed + " redundant keyframe(s) from '" + simplifyResultPopup.animName + "'."
: "No redundant keyframes found in '" + simplifyResultPopup.animName + "'."
color: PropertiesPanelController.textColor
font.pixelSize: 12
wrapMode: Text.WordWrap; width: 320
}
Rectangle {
width: 80; height: 24; radius: 3
anchors.right: parent.right
color: simplifyOkMouse.containsMouse
? Qt.lighter(PropertiesPanelController.headerColor, 1.2)
: PropertiesPanelController.headerColor
border.color: PropertiesPanelController.borderColor
Text { anchors.centerIn: parent; text: "OK"; color: PropertiesPanelController.textColor; font.pixelSize: 11 }
MouseArea {
id: simplifyOkMouse; anchors.fill: parent; hoverEnabled: true
onClicked: simplifyResultPopup.close()
}
}
}
}
}
60 changes: 35 additions & 25 deletions qml/ThemedComboBox.qml
Original file line number Diff line number Diff line change
@@ -1,87 +1,97 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import ThemeManager 1.0

// Themed ComboBox matching the look of the typeahead dropdowns elsewhere
// in the inspector (filter row + list with hover highlight). Pulls colors
// from ThemeManager so it works in any panel.
ComboBox {
id: control

delegate: ItemDelegate {
id: itemDelegate
width: control.width
implicitHeight: 22
padding: 0
leftPadding: 6
rightPadding: 6
contentItem: Text {
text: modelData
color: MaterialEditorQML.textColor
color: ThemeManager.textColor
font: control.font
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
highlighted: control && control.highlightedIndex === index
background: Rectangle {
color: parent.highlighted ? MaterialEditorQML.highlightColor : MaterialEditorQML.buttonColor
color: itemDelegate.highlighted ? ThemeManager.highlightColor : "transparent"
}
}

indicator: Canvas {
id: canvas
x: control.width - width - control.rightPadding
y: control.topPadding + (control.availableHeight - height) / 2
width: 12
height: 8
width: 9
height: 5
contextType: "2d"

Connections {
target: control
function onPressedChanged() { canvas.requestPaint() }
}

onPaint: {
context.reset()
context.moveTo(0, 0)
context.lineTo(width, 0)
context.lineTo(width / 2, height)
context.closePath()
context.fillStyle = MaterialEditorQML.buttonTextColor
context.fillStyle = ThemeManager.textColor
context.fill()
}
}

contentItem: Text {
leftPadding: 10
rightPadding: control.indicator.width + control.spacing
leftPadding: 6
rightPadding: control.indicator.width + control.spacing + 4
text: control.displayText
font: control.font
color: MaterialEditorQML.buttonTextColor
color: ThemeManager.textColor
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}

background: Rectangle {
implicitWidth: 120
implicitHeight: 30
color: MaterialEditorQML.buttonColor
border.color: MaterialEditorQML.borderColor
implicitHeight: 22
color: ThemeManager.inputColor
border.color: ThemeManager.borderColor
border.width: control.visualFocus ? 2 : 1
radius: 3
}


// Pop directly under the input (no gap) — matches the SceneTreeNode
// material typeahead. Default ComboBox.popup leaves ~5px gap on macOS.
popup: Popup {
y: control.height - 1
y: control.height
width: control.width
implicitHeight: contentItem.implicitHeight
padding: 1

contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: control.delegateModel
currentIndex: control ? control.highlightedIndex : 0

ScrollIndicator.vertical: ScrollIndicator { }
}

background: Rectangle {
color: MaterialEditorQML.panelColor
border.color: MaterialEditorQML.borderColor
color: ThemeManager.inputColor
border.color: ThemeManager.borderColor
border.width: 1
radius: 3
}
}
}
}
Loading
Loading