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
8 changes: 6 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,12 @@ qtmesh scan ./assets --sarif report.sarif # write SARIF report to file
qtmesh scan ./assets --fix --dry-run # preview auto-fixes
qtmesh scan ./assets --include "*.fbx,*.glb" # filter by extension
qtmesh scan ./assets --fail-on warning # exit 1 on warnings or errors
qtmesh pack-textures --r ao.png --g rough.png --b metal.png -o orm.png # pack 3 grayscale maps into RGB (Unity ORM)
qtmesh pack-textures --r metal.png --g rough.png --bc 0 --no-alpha -o mr.png # Unreal MR (constant blue)
qtmesh pack-textures --r rough.png --invert-r -o gloss.png # invert: roughness → glossiness
```

CLI mode is activated by: (1) invoking via the `qtmesh` symlink, (2) passing `--cli`, or (3) using a recognized subcommand (`info`, `fix`, `convert`, `anim`, `validate`, `lod`, `pose`, `scan`, `material`) as the first argument. Use `--verbose` to see Ogre/engine debug output. Use `--no-telemetry` to permanently opt out of anonymous usage data collection.
CLI mode is activated by: (1) invoking via the `qtmesh` symlink, (2) passing `--cli`, or (3) using a recognized subcommand (`info`, `fix`, `convert`, `anim`, `validate`, `lod`, `pose`, `scan`, `material`, `pack-textures`) as the first argument. Use `--verbose` to see Ogre/engine debug output. Use `--no-telemetry` to permanently opt out of anonymous usage data collection.

If Xcode SDK is updated, clear CMake cache (`rm build_local/CMakeCache.txt`) and reconfigure.

Expand Down Expand Up @@ -159,6 +162,7 @@ Three singletons manage core state. All run on the main thread. Access via `Clas

- **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).
- **TextureChannelPacker** (`src/TextureChannelPacker.h/cpp`, slice G): pure-data packer that takes 1-4 grayscale source images (or constants) and writes a single packed RGBA texture (PNG/TGA/JPG). Each output channel is sampled via Rec.601 luminance from its source image, with an optional invert flag (useful for roughness↔glossiness). Smaller sources are bilinear-scaled up to match the largest input. Surfaced via the `qtmesh pack-textures` CLI subcommand, the `pack_textures` MCP tool, and the "Pack Channels…" button in the Material Editor.

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 | ⚡ Quick win

Update format list to include BMP for the texture packer docs.

This line documents only PNG/TGA/JPG, but the feature scope includes BMP output as well. Keeping this list complete avoids user confusion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` at line 165, Update the documentation line describing
TextureChannelPacker to include BMP as a supported output format; modify the
sentence that currently lists "PNG/TGA/JPG" to read "PNG/TGA/JPG/BMP" (or
otherwise include BMP) so the feature scope matches the implementation for
TextureChannelPacker (src/TextureChannelPacker.h/cpp) and the related interfaces
(qtmesh pack-textures CLI, pack_textures MCP tool, and the "Pack Channels…"
Material Editor button).


### MCP Server

Expand All @@ -171,7 +175,7 @@ Three singletons manage core state. All run on the main thread. Access via `Clas
### CLI Pipeline

- **CLIPipeline** (`src/CLIPipeline.h/cpp`): Headless command-line interface for mesh operations. All static methods — entry point is `CLIPipeline::run(argc, argv)`.
- Subcommands: `info`, `fix`, `convert`, `anim` (list/rename/merge), `validate`, `lod`, `pose`, `scan`.
- Subcommands: `info`, `fix`, `convert`, `anim` (list/rename/merge), `validate`, `lod`, `pose`, `scan`, `material`, `pack-textures`.
- Activated via `qtmesh` symlink (created at build time), `--cli` flag, or recognized subcommand as first arg.
- Redirects stdout to stderr (Ogre/Qt noise) and writes CLI output to the original stdout fd. Uses `_exit()` to avoid Ogre static destructor crashes on macOS.
- **AnimationMerger** (`src/AnimationMerger.h/cpp`): Public `renameAnimation()` static method used by both CLI and GUI for animation renaming.
Expand Down
57 changes: 57 additions & 0 deletions qml/PropertiesPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import QtQuick.Layouts
import PropertiesPanel 1.0
import AnimationControl 1.0
import EditorMode 1.0
import MaterialEditorQML 1.0

Rectangle {
id: root
Expand Down Expand Up @@ -1694,6 +1695,62 @@ Rectangle {
onClicked: PropertiesPanelController.triggerMaterialEditor()
}
}

// Slice G: Texture Channel Packer — utility for combining
// grayscale source images into a single packed RGBA texture
// (e.g. ORM = AO+Roughness+Metallic). Lives in Mode Tools
// because it operates on PNG/TGA files on disk, not on the
// currently-selected submesh's TUS.
Rectangle {
width: Math.min(parent.width - 16, packLabel.implicitWidth + 16)
height: 26
radius: 3
color: packMa.containsMouse
? PropertiesPanelController.highlightColor
: PropertiesPanelController.headerColor
border.color: PropertiesPanelController.borderColor
border.width: 1

Text {
id: packLabel
anchors.centerIn: parent
text: "Pack Texture Channels…"
color: PropertiesPanelController.textColor
font.pixelSize: 11
}
MouseArea {
id: packMa
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.openTextureChannelPackerDialog()
ToolTip.visible: containsMouse
ToolTip.delay: 500
ToolTip.text: "Pack 1–4 grayscale source images into a single RGBA texture (e.g. Unity ORM = AO+Roughness+Metallic, Unreal MR)."
}
}
}
}

// The dialog is loaded by URL (rather than as a typed component)
// because the Properties Panel's QML engine doesn't have the
// MaterialEditorQML module's import path set up — only its singleton
// C++ type. Using a Loader with a qrc:// source bypasses module
// resolution while keeping the dialog as a top-level child so it
// overlays the viewport correctly.
Loader {
id: textureChannelPackerLoader
active: false
anchors.centerIn: parent
source: "qrc:/MaterialEditorQML/TextureChannelPackerDialog.qml"
onLoaded: if (item && item.open) item.open()
}

function openTextureChannelPackerDialog() {
if (!textureChannelPackerLoader.active) {
textureChannelPackerLoader.active = true
} else if (textureChannelPackerLoader.item) {
textureChannelPackerLoader.item.open()
}
}

Expand Down
Loading
Loading