Skip to content
Merged
2 changes: 1 addition & 1 deletion profiles/dreamcast.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "dreamcast",
"displayName": "Sega Dreamcast (validation)",
"displayName": "Sega Dreamcast",
"description": "Validation-only lint preset for Dreamcast-class workflows. PVR-oriented texture format allow-list placeholder (png/jpg/pvr) with triangle, bone, and draw-call budgets aligned with common homebrew targets. Does not export PVR-ready assets or guarantee KallistiOS/Hektic compliance — model export remains engine-specific.",
"rules": {
"max_triangle_count": 15000,
Expand Down
2 changes: 1 addition & 1 deletion profiles/mobile-low.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "mobile-low",
"displayName": "Mobile Low-end (source validation)",
"displayName": "Mobile Low-end",
"description": "Validation-only preset for low-end mobile GPU/CPU source asset budgets. Tight triangle, material, texture, and bone limits for glTF/FBX prep before Unity/Unreal/Godot mobile cooks. Warnings only — not a substitute for on-device profiling.",
"rules": {
"allowed_formats": ["fbx", "glb", "gltf", "vrm", "obj"],
Expand Down
2 changes: 1 addition & 1 deletion profiles/modern-console.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "modern-console",
"displayName": "Modern Console (source validation)",
"displayName": "Modern Console",
"description": "Validation-only preset for PS5/Xbox/Series-class source assets (glTF/FBX). Conservative AAA triangle, material, texture, bone, and animation budgets for engine import pipelines. Does not export cooked platform binaries — tune per title and ship via Unreal/Unity/Godot/custom cook steps. require_lod rule is planned once LOD scan rules land.",
"rules": {
"allowed_formats": ["fbx", "glb", "gltf", "vrm", "obj"],
Expand Down
2 changes: 1 addition & 1 deletion profiles/n64.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "n64",
"displayName": "Nintendo 64 (validation)",
"displayName": "Nintendo 64",
"description": "Validation-only lint preset for N64-class pipelines. Warns on triangle, bone, submesh, draw-call, and texture budgets typical of N64 homebrew and preservation ports. Does not emit ROM-ready geometry or guarantee microcode compliance — defaults are tunable starting points, not certification.",
"rules": {
"max_triangle_count": 12000,
Expand Down
2 changes: 1 addition & 1 deletion profiles/nds.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "nds",
"displayName": "Nintendo DS (validation)",
"displayName": "Nintendo DS",
"description": "Validation-only lint preset for NDS-class demake and homebrew checks. Tight polygon and texture budgets with palette/texture size warnings suited to dual-screen handheld targets. Does not export NSBMD/NSBTX or validate proprietary SDK output — NSBMD/NSBTX feasibility is separate research.",
"rules": {
"max_triangle_count": 4000,
Expand Down
2 changes: 1 addition & 1 deletion profiles/ps1.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "ps1",
"displayName": "PlayStation 1 (validation)",
"displayName": "PlayStation 1",
"description": "Validation-only lint preset for PS1-class homebrew and demake workflows. Applies conservative triangle, material, bone, and texture budgets plus power-of-two texture warnings. Does not export TIM/CLUT or guarantee hardware compliance — tune limits per title. TIM import is future work.",
"rules": {
"max_triangle_count": 5000,
Expand Down
2 changes: 1 addition & 1 deletion profiles/steamdeck.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "steamdeck",
"displayName": "Steam Deck (source validation)",
"displayName": "Steam Deck",
"description": "Validation-only preset for Steam Deck / handheld-PC class performance heuristics on glTF/FBX source assets. Higher budgets than switch-like but still warns on draw calls, texture size, and animation bloat. Does not produce cooked builds — validation for engine import only.",
"rules": {
"allowed_formats": ["fbx", "glb", "gltf", "vrm", "obj"],
Expand Down
2 changes: 1 addition & 1 deletion profiles/switch-like.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "switch-like",
"displayName": "Switch-class (source validation)",
"displayName": "Switch-class",
"description": "Validation-only preset for handheld/console-class GPU and CPU heuristics aligned with Nintendo Switch–style glTF/FBX source asset prep. Warns on triangle, material, texture, bone, and animation budgets — not certifiable TRC limits. Export via engine cooked pipelines, not proprietary SDK mesh formats.",
"rules": {
"allowed_formats": ["fbx", "glb", "gltf", "vrm", "obj"],
Expand Down
2 changes: 1 addition & 1 deletion profiles/vr.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "vr",
"displayName": "VR (source validation)",
"displayName": "VR",
"description": "Validation-only preset for VR source assets where stereo rendering and frame-time budgets demand lean geometry, materials, and textures. glTF/FBX preparation checks only — export via engine VR cook pipelines, not platform SDK mesh formats.",
"rules": {
"allowed_formats": ["fbx", "glb", "gltf", "vrm", "obj"],
Expand Down
2 changes: 1 addition & 1 deletion profiles/webgl.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "webgl",
"displayName": "WebGL / WebGPU (source validation)",
"displayName": "WebGL / WebGPU",
"description": "Validation-only preset for browser 3D pipelines (WebGL/WebGPU). Caps triangles, draw calls, texture dimensions, and animation keyframes on glTF/FBX source assets. Does not emit web-ready bundles — validate source, then export via your web engine toolchain.",
"rules": {
"allowed_formats": ["fbx", "glb", "gltf", "vrm", "obj"],
Expand Down
11 changes: 5 additions & 6 deletions qml/BottomContextPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,12 @@ Rectangle {
Layout.fillWidth: true
spacing: 18

SummaryText { label: "Profile"; value: AssetScanController.selectedProfileId.length > 0 ? AssetScanController.selectedProfileId : "None" }
SummaryText { label: "Asset Root"; value: root.rootFolderName() }
SummaryText { label: "Folder Scan"; value: AssetScanController.scanning ? "Running" : (AssetScanController.hasResults ? (AssetScanController.summaryErrors + " err / " + AssetScanController.summaryWarnings + " warn") : "Not run") }
SummaryText { label: "Selection"; value: MeshValidator.hasSelection ? PropertiesPanelController.selectionName : "None" }
SummaryText { label: "Findings"; value: root.issueSummary() }
SummaryText { label: "Suggestions"; value: root.suggestionSummary() }
SummaryText { label: "Fixable"
value: (MeshValidator.hasFixableIssues
|| MeshValidator.hasCacheOptimization) ? "Yes" : "No" }
SummaryText { label: "Status"; value: MeshValidator.validating ? "Running" : (MeshValidator.validated ? "Ready" : "Idle") }
SummaryText { label: "Mesh Findings"; value: root.issueSummary() }
SummaryText { label: "Mesh Status"; value: MeshValidator.validating ? "Running" : (MeshValidator.validated ? "Ready" : "Idle") }
Item { Layout.fillWidth: true }
}
}
Expand Down
258 changes: 258 additions & 0 deletions qml/PropertiesPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import AnimationControl 1.0
import EditorMode 1.0
import MaterialEditorQML 1.0
import ThemeManager 1.0
import AssetBrowser 1.0

Rectangle {
id: root
Expand Down Expand Up @@ -446,6 +447,17 @@ Rectangle {
Component.onCompleted: content = materialPresetsComponent
}

// ---- Asset Folder Scan (platform profile) ----
CollapsibleSection {
title: "Asset Folder Scan"
sectionVisible: root.modeToolSectionVisible(
EditorModeController.ValidationMode,
true)
expanded: false

Component.onCompleted: content = assetScanComponent
}

// ---- Mesh Validation ----
CollapsibleSection {
title: "Mesh Validation"
Expand Down Expand Up @@ -2299,6 +2311,7 @@ Rectangle {
objectName: "workspaceAssetBrowserButton"
visible: root.showAllModeTools
|| EditorModeController.currentMode === EditorModeController.MaterialMode
|| EditorModeController.currentMode === EditorModeController.ValidationMode
text: "Asset Browser"
onClicked: root.revealBottomTool("assetBrowser")
}
Expand Down Expand Up @@ -4216,6 +4229,251 @@ Rectangle {
}
}

// ---- Asset Folder Scan (Validation mode) ----
Component {
id: assetScanComponent

Column {
width: parent ? parent.width : 200
padding: 8
spacing: 6

Text {
width: parent.width - 16
wrapMode: Text.Wrap
font.pixelSize: 10
color: PropertiesPanelController.textColor
opacity: 0.75
text: "Pick an assets folder below (or open Asset Browser \u2192 Browse\u2026). "
+ "Scan uses the same rules as qtmesh scan --target in a separate process."
}

Row {
spacing: 6
width: parent.width - 16
Text {
text: "Profile:"
color: PropertiesPanelController.textColor
font.pixelSize: 11
anchors.verticalCenter: parent.verticalCenter
width: 44
}
ThemedComboBox {
id: profileCombo
width: parent.width - 50
model: AssetScanController.profileLabels
currentIndex: {
const idx = AssetScanController.profileIds.indexOf(AssetScanController.selectedProfileId)
return idx >= 0 ? idx : 0
}
onActivated: function(index) {
if (index >= 0 && index < AssetScanController.profileIds.length)
AssetScanController.selectedProfileId = AssetScanController.profileIds[index]
}
ToolTip.visible: hovered && AssetScanController.profileDescription.length > 0
ToolTip.text: AssetScanController.profileDescription
ToolTip.delay: 400
}
}

Text {
width: parent.width - 16
visible: AssetScanController.profileDescription.length > 0
wrapMode: Text.Wrap
font.pixelSize: 10
color: PropertiesPanelController.textColor
opacity: 0.7
text: AssetScanController.profileDescription
}

Row {
spacing: 6
width: parent.width - 16

Rectangle {
width: parent.width - 66
height: 24
radius: 3
color: PropertiesPanelController.inputColor
border.color: PropertiesPanelController.borderColor
border.width: 1
clip: true

Text {
anchors.fill: parent
anchors.leftMargin: 6
anchors.rightMargin: 6
verticalAlignment: Text.AlignVCenter
text: AssetBrowserController.rootPath
color: PropertiesPanelController.textColor
font.pixelSize: 10
elide: Text.ElideLeft
}
}

Rectangle {
width: 60
height: 24
radius: 3
activeFocusOnTab: true
Accessible.role: Accessible.Button
Accessible.name: "Browse asset folder"
color: folderBrowseMouse.containsMouse || activeFocus
? Qt.lighter(PropertiesPanelController.inputColor, 1.3)
: PropertiesPanelController.inputColor
border.color: activeFocus ? PropertiesPanelController.highlightColor
: PropertiesPanelController.borderColor
border.width: 1
Text {
anchors.centerIn: parent
text: "Browse\u2026"
color: PropertiesPanelController.textColor
font.pixelSize: 10
}
MouseArea {
id: folderBrowseMouse
anchors.fill: parent
hoverEnabled: true
onClicked: AssetBrowserController.browseForDirectory()
}
Keys.onSpacePressed: folderBrowseMouse.clicked(null)
Keys.onReturnPressed: folderBrowseMouse.clicked(null)
Keys.onEnterPressed: folderBrowseMouse.clicked(null)
}
}

Rectangle {
width: parent.width - 16; height: 24; radius: 3
activeFocusOnTab: true
Accessible.role: Accessible.Button
Accessible.name: "Open Asset Browser panel"
color: browserMouse.containsMouse || activeFocus
? Qt.lighter(PropertiesPanelController.controlBgColor, 1.08)
: PropertiesPanelController.controlBgColor
border.color: activeFocus ? PropertiesPanelController.highlightColor
: PropertiesPanelController.borderColor
border.width: 1
Text {
anchors.centerIn: parent
text: "Open Asset Browser panel"
color: PropertiesPanelController.textColor
font.pixelSize: 10
}
MouseArea {
id: browserMouse
anchors.fill: parent
hoverEnabled: true
onClicked: root.revealBottomTool("assetBrowser")
}
Keys.onSpacePressed: browserMouse.clicked(null)
Keys.onReturnPressed: browserMouse.clicked(null)
Keys.onEnterPressed: browserMouse.clicked(null)
}

Rectangle {
width: parent.width - 16; height: 28; radius: 3
activeFocusOnTab: true
Accessible.role: Accessible.Button
Accessible.name: "Scan asset folder"
color: scanMouse.pressed ? Qt.darker(PropertiesPanelController.highlightColor, 1.2)
: (scanMouse.containsMouse || activeFocus)
? Qt.lighter(PropertiesPanelController.highlightColor, 1.1)
: PropertiesPanelController.highlightColor
opacity: AssetScanController.scanning ? 0.55 : 1.0
Text {
anchors.centerIn: parent
text: AssetScanController.scanning ? "Scanning\u2026" : "Scan Asset Folder"
color: "white"
font.pixelSize: 12
}
MouseArea {
id: scanMouse
anchors.fill: parent
hoverEnabled: true
enabled: !AssetScanController.scanning
onClicked: AssetScanController.scanFolder(AssetBrowserController.rootPath)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Keys.onSpacePressed: if (!AssetScanController.scanning) scanMouse.clicked(null)
Keys.onReturnPressed: if (!AssetScanController.scanning) scanMouse.clicked(null)
Keys.onEnterPressed: if (!AssetScanController.scanning) scanMouse.clicked(null)
}

Text {
width: parent.width - 16
visible: AssetScanController.scanning
text: "Running qtmesh scan in background\u2026"
color: PropertiesPanelController.textColor
font.pixelSize: 11
font.italic: true
}

Text {
width: parent.width - 16
visible: AssetScanController.hasResults && !AssetScanController.scanning
wrapMode: Text.Wrap
font.pixelSize: 10
color: PropertiesPanelController.textColor
text: "Scanned " + AssetScanController.summaryScanned
+ " \u2022 passed " + AssetScanController.summaryPassed
+ " \u2022 warnings " + AssetScanController.summaryWarnings
+ " \u2022 errors " + AssetScanController.summaryErrors
}

Column {
width: parent.width - 16
spacing: 3
visible: AssetScanController.hasResults
&& !AssetScanController.scanning
&& AssetScanController.findings.length > 0

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Repeater {
model: AssetScanController.findings

Row {
spacing: 6
width: parent.width

Text {
text: modelData.severity === "error" ? "\u2718" : "\u26A0"
color: modelData.severity === "error" ? "#e05050" : "#e0a030"
font.pixelSize: 13
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: (modelData.file ? modelData.file + ": " : "") + modelData.message
color: PropertiesPanelController.textColor
font.pixelSize: 10
wrapMode: Text.Wrap
width: parent.width - 24
anchors.verticalCenter: parent.verticalCenter
}
}
}
}

Text {
id: assetScanFeedback
width: parent.width - 16
wrapMode: Text.Wrap
font.pixelSize: 10
color: "#c06060"
text: ""

Connections {
target: AssetScanController
function onError(msg) {
assetScanFeedback.color = "#c06060"
assetScanFeedback.text = msg
}
function onScanFinished(ok, message) {
assetScanFeedback.color = ok ? "#60c060" : "#c06060"
assetScanFeedback.text = message
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
}
}

// ---- Mesh Validation Content ----
Component {
id: validationComponent
Expand Down
Loading
Loading