feat(PS1): Psy-Q PLY quad topology + qtmesh scan and import dialog#478
Conversation
…export - buildMeshFromTriSoup: indexed mesh with welded corners (quantized pos/normal/colour) - exportPsyqPlyFromEntity: per-submesh quad merge (shared directed edge, normal agreement) with Psy-Q quad face lines; MAT face colours only if all exported faces have colour data - Remove duplicate WeldKey/quantize definitions after moving welding keys earlier Co-authored-by: Cursor <cursoragent@cursor.com>
- Record per-face vertex count (3 or 4) when parsing PLY; store blob on mesh under kPsyqPlyFaceLayoutUserKey after import - Export uses stored layout on single-submesh meshes when triangle count still matches; emit type-1 quad lines without heuristic merge; fall back if winding no longer matches - Document that Ogre still renders triangle lists; quads are not n-gons in GPU Co-authored-by: Cursor <cursoragent@cursor.com>
- On import, rebuild polygon lists from Psy-Q face layout and welded triangle indices, then writeNgonFacesToMesh (same qtme.faces.<i> binding as FBX) so Edit Mode and export see true quads. - Recover quad corner order as PS1/TMD split (v0,v1,v2)+(v1,v2,v3) after per-triangle winding flips, instead of sorting corners by angle. - Export prefers readNgonFacesFromMesh for single-submesh meshes; fan n-gons to Psy-Q triangles; fall back to heuristic quad merge otherwise. - readNgonFacesFromMesh: catch std::bad_cast on wrong Any payload. - Drop the separate Psy-Q face-layout UserAny blob; qtme.faces is canonical. Co-authored-by: Cursor <cursoragent@cursor.com>
- Weld positions and normals in separate tables (PosWeldKey / NrmWeldKey); header line uses nV and nN independently like original Psy-Q files. - Face lines emit distinct vertex and normal index tuples (tri and quad). - Heuristic quad merge carries parallel normal indices; merged quads pick per-corner normals from the source triangle that owns each welded position. - Import-time corner weld still uses combined pos+normal+colour (unchanged). Co-authored-by: Cursor <cursoragent@cursor.com>
- Add documentation/playstation-rsd-ply.md (RSD descriptor, PLY/MAT, qtme.faces vs quad merge, normal pool welding) - README + DocsApp: PlayStation RSD, clarify .ply dispatch, link to doc - PS1PLY_test: Ogre fixture tests for heuristic quad merge and quad import re-export - WelcomeDialog: include .ply and .rsd in quick-open filter Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- ScanEngine: inspect TMD, Psy-Q PLY, and RSD-referenced geometry via Ogre headless context; default Assimp include set adds tmd/rsd extensions. - ScanConfig: merge **/*.tmd, **/*.rsd, **/*.ply into explicit scan.include from qtmesh.yml so local configs do not drop PlayStation assets. - qtmesh.yml: document PlayStation include patterns for repo scans. - WelcomeDialog + File→Import: use MeshImporterExporter import filter helper and Manager::defaultImportExtensions() so startup dialog matches Import. - Tests: ScanEngine (RSD/TMD/Psyq PLY), ScanConfig include merge, Manager, MeshImporterExporter filter builder. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📝 WalkthroughWalkthroughPR expands PlayStation/Psy-Q mesh format support (.tmd, .rsd, .ply) by introducing new extension/filter accessor APIs, implementing headless Ogre-based asset inspection including RSD descriptor parsing, refining PS1 PLY quad-merge validation, and providing comprehensive test coverage and infrastructure. ChangesPlayStation Format Support & Asset Scanning
PS1 PLY Export Quad-Merge Validation
Test Infrastructure & Fixtures
API & Integration Tests
CI Configuration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1ddc725c98
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-authored-by: Cursor <cursoragent@cursor.com>
…skip quad merge on split normals - fillAssetInfoFromOgreMesh adds vertexData for submeshes with useSharedVertices=false when sharedVertexData exists (CodeRabbit P2). - Heuristic tri→quad merge requires matching welded normal indices on the shared interior edge (CodeRabbit P1). - Tests: ScanEngine Ogre inspect count helper (QTMESH_UNIT_TESTS), PS1PLY export split-edge case; TestHelpers mesh factory. Co-authored-by: Cursor <cursoragent@cursor.com>
- PS1PLY tests: single createInterleavedPosNormalMesh builder for two-tri fixtures. - TransformCommands_test: reuse TestHelpers createInMemoryMeshSharedVertsPlusLocalSubmesh. Co-authored-by: Cursor <cursoragent@cursor.com>
… test mesh - PS1PLY: PsyqWeldedTri bundles corners; drops 14-arg function and redundant static. - TestHelpers shared+local mesh: constexpr std::array buffers; NOSONAR for Ogre-owned VertexData. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/WelcomeDialog.cpp (1)
71-75: ⚡ Quick winLog the click before opening the dialog.
Right now the
ui.actionbreadcrumb is only added after a file is picked, so cancelled opens disappear from telemetry. Add the breadcrumb beforeQFileDialog::getOpenFileName(...).Suggested change
connect(openFileBtn, &QPushButton::clicked, this, [this]() { + SentryReporter::addBreadcrumb("ui.action", "Welcome: Open File"); const QString filter = MeshImporterExporter::importFileDialogFilterFromExtensionList( Manager::defaultImportExtensions()); QString file = QFileDialog::getOpenFileName( this, tr("Open 3D File"), QString(), filter, nullptr, QFileDialog::DontUseNativeDialog | QFileDialog::HideNameFilterDetails);As per coding guidelines, "Track user-facing actions and significant operations with
SentryReporter::addBreadcrumb(category, message). Use\"ui.action\"for toolbar/menu clicks,\"ai.tool_call\"for MCP tool invocations,\"file.import\"/\"file.export\"for I/O operations."🤖 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 `@src/WelcomeDialog.cpp` around lines 71 - 75, The code currently calls QFileDialog::getOpenFileName(...) and only adds the ui.action breadcrumb after a file is chosen, so cancelled opens aren't logged; before calling QFileDialog::getOpenFileName in WelcomeDialog (the block that builds filter via MeshImporterExporter::importFileDialogFilterFromExtensionList and uses Manager::defaultImportExtensions()), call SentryReporter::addBreadcrumb("ui.action", "Open 3D File") to log the toolbar/menu click immediately so the action is recorded even if the dialog is cancelled.
🤖 Prompt for all review comments with 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.
Inline comments:
In `@src/ScanConfig.cpp`:
- Around line 313-320: The lambda hasExtensionGlob currently treats any
substring match as a hit (using p.contains(token)), which incorrectly matches
things like "**/*.ply2"; update hasExtensionGlob to only accept true glob
patterns for the extension by performing an exact glob check against each
pattern instead of substring matching — e.g. check if a pattern equals "*.ext"
or ends with a path-separator + "*.ext" (or use a QRegularExpression anchored to
the end like "(?:.*/|\\*\\*/)?\\*\\.ext$") so that only exact "*.ext" globs
(possibly prefixed by directories or "**/") are considered present; modify the
implementation in hasExtensionGlob and iterate over patterns accordingly.
In `@src/ScanEngine.cpp`:
- Around line 642-666: The code restores info.filePath back to the .rsd after
calling ScanEngine::inspectAsset (using geomPath) which causes
require_textures_exist to resolve relative texture paths against the .rsd
instead of the referenced geometry; to fix, stop overwriting info.filePath with
the .rsd path—either leave info.filePath as the inspected asset path (use
inner.filePath or geomPath) or move the assignment so it happens only after
texture existence checks; update relativePath/format/fileSize likewise to
reflect the inspected asset (inner) so require_textures_exist resolves relative
textures correctly.
- Around line 157-205: The code currently sets info.materialCount =
mesh->getNumSubMeshes(), which overcounts when multiple submeshes share the same
material; modify fillAssetInfoFromOgreMesh to compute unique materials instead:
collect non-empty material names from each SubMesh (use sm->getMaterialName())
into a temporary set or deduplicate info.materialNames, then set
info.materialCount to the size of that unique collection (and ensure
info.materialNames only contains unique names if you want them de-duplicated).
Update/remove the hard-coded assignment of mesh->getNumSubMeshes() and compute
the value after iterating submeshes.
---
Nitpick comments:
In `@src/WelcomeDialog.cpp`:
- Around line 71-75: The code currently calls QFileDialog::getOpenFileName(...)
and only adds the ui.action breadcrumb after a file is chosen, so cancelled
opens aren't logged; before calling QFileDialog::getOpenFileName in
WelcomeDialog (the block that builds filter via
MeshImporterExporter::importFileDialogFilterFromExtensionList and uses
Manager::defaultImportExtensions()), call
SentryReporter::addBreadcrumb("ui.action", "Open 3D File") to log the
toolbar/menu click immediately so the action is recorded even if the dialog is
cancelled.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e4c00c6b-0f6f-4223-b1cc-7236b1902b55
📒 Files selected for processing (19)
qtmesh.ymlsrc/CMakeLists.txtsrc/Manager.cppsrc/Manager.hsrc/Manager_test.cppsrc/MeshImporterExporter.cppsrc/MeshImporterExporter.hsrc/MeshImporterExporter_test.cppsrc/PS1/PS1PLY.cppsrc/PS1/PS1PLY_test.cppsrc/ScanConfig.cppsrc/ScanConfig.hsrc/ScanEngine.cppsrc/ScanEngine.hsrc/ScanEngine_test.cppsrc/TestHelpers.hsrc/WelcomeDialog.cppsrc/commands/TransformCommands_test.cppsrc/mainwindow.cpp
… scan mesh id - Replace std::function import callback with a template (Sonar cpp:S5213). - nextScanInspectMeshId() replaces mutable file-scope atomic. - RSD/TMD/PLY inspect lambdas capture paths explicitly; const SubMesh pointers in counts. Co-authored-by: Cursor <cursoragent@cursor.com>
|



Summary
This branch extends Psy-Q PLY import/export (quad/ngon metadata and split vertex/normal pools for welding) and fixes qtmesh scan and file pickers around PlayStation assets.
Scan (
qtmesh scan)PLY=and using the same Ogre importers as the editor (headless render target + materials).**/*.tmdand**/*.rsd** (Assimp does not register those extensions).qtmesh.ymlwith explicitscan.include: after loading YAML/JSON, missing**/*.tmd,**/*.rsd,**/*.ply** patterns are merged so local configs (like this repo’sqtmesh.yml) no longer exclude PS1 files when scanning a folder such as./TMD.qtmesh.ymllists TMD/RSD/PLY includes for clarity.UI
MeshImporterExporter::importFileDialogFilter()/importFileDialogFilterFromExtensionList(Manager::defaultImportExtensions())so filters stay aligned beforeMainWindowexists.Tests
ScanEngine_test: RSD→OBJ inspect, minimal TMD / Psy-Q PLY inspect (Ogre), YAML include merge, default globs.ScanConfig_test,Manager_test,MeshImporterExporter_test: filter and extension helpers.Notes
qtmesh.ymlmay still report rule errors (require_skeleton/require_animations) because those assets have no skeleton; files are still enumerated and inspected.Made with Cursor
Summary by CodeRabbit
New Features
.tmd,.rsd,.ply) in file dialogs and asset scanning.Improvements
Tests