Skip to content

Local Assets: Show a mesh's own LODs, physics and origins as an uploaded mesh would - #629

Open
Martin-Pitt wants to merge 57 commits into
AlchemyViewer:developfrom
Martin-Pitt:feature-ui/local-mesh-lod-names
Open

Martin-Pitt wants to merge 57 commits into
AlchemyViewer:developfrom
Martin-Pitt:feature-ui/local-mesh-lod-names

Conversation

@Martin-Pitt

@Martin-Pitt Martin-Pitt commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Builds on #628, whose loader change keeps the names this reads, and #621, whose file-origin rule places the parts; until both merge, this PR's diff also shows their commits. The changes here are three commits, to lllocalmesh.cpp/.h and the local-mesh hooks in llmeshrepository.cpp.

Why

With #628, a model file can carry each part's own LOD and physics models: NAME_LOD2, NAME_LOD1, NAME_LOD0 and NAME_PHYS beside NAME. The uploader files them into NAME's slots, but Local Assets still rezzed every one of them as a part of the preview linkset, so a ten-part arm came in as fifty overlapping prims.

What it does

  • LLLocalMesh::ingestScene leaves out a model whose name ends in _LOD0, _LOD1, _LOD2 or _PHYS (before a submodel's letter) when the file also has a model named for its part.
  • A suffixed model with no such part is still rezzed, as before.
  • LODs: each part keeps the file's NAME_LOD0, _LOD1 and _LOD2 as volumes, and the repository's local-mesh hook serves the one for the LOD the object asks for, falling back to the High model where the file has none. The preview now switches detail with distance as an uploaded mesh does.
  • Physics: a part's NAME_PHYS becomes the physics mesh getDecomposition() returns, so the physics display draws it; hasPhysicsShape() reports it.
  • Origins: each static part takes the uploader's From file pivot (LLModelPreview::getFileOriginPivot): its geometry, LOD models and physics shape move by the offset the uploader writes into the position domain, and the part is placed at its pivot, so a door or joint turns about its file origin and the linkset sits where the file placed it. Rigged parts are unchanged.
  • Nothing is generated or decomposed, so loading and live reloads stay as fast as before.

Testing

Compiled; not yet run in a viewer.

🤖 Generated with Claude Code

Martin-Pitt and others added 23 commits October 29, 2026 03:29
The uploader always centres a model on its bounding box, so an object's
origin, and the axis it turns about, sits at the box's centre. A door had to
carry hidden geometry to put its hinge on an edge.

The Upload options tab gets a pivot within the bounding box, -1 to 1 per axis.
The uploader shifts every stored position domain, and the analyzed hulls'
domain, so that the origin lands there; the quantized positions stay as they
are. The sim keeps analyzed hulls where their domain puts them but fits any
other physics shape to the box, so the pivot is held at the centre while a
physics model is set but not analyzed, and a warning says the object will
collide as its centred default hull when there is no physics model. It is
held as well for rigged mesh, which its skeleton places, and for an upload
of more than one model, whose pieces it would pull apart.

The preview's display options get Origin, which draws the object's axes at
the pivot, and Joint position overrides no longer wraps, to make room.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Percent of the half-extent suits snapping to a face; metres from the centre
suit a hinge at a known distance. The pivot is kept as a fraction of the box,
so metres follow the scale and the model's size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The right panel was 140 tall, so the Origin check box, and part of Joints, lay below it where clicks never reached them; the preview gives up the height.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A combo item's text is its value, so the metres item reported "m" and the pivot stayed in percent; the labels are attributes now, as the file's other combos have them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each axis is a shaft and a solid cone in the gizmo's colours, red X forward, so the origin reads as the object's frame rather than three crossing lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gDebugProgram draws one uniform colour, so every arrow took the last one set, blue; gUIProgram takes the vertex colours. Culling is off so the cones show from any side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Metres divided by the scene's bounding box, so a rotated instance moved by the wrong amount; they now use the instance's local half-extents, as the pivot and the Origin arrows do. A flat axis keeps its pivot at zero in percent too, and an analyzed hull on the base model, which the upload sends when there is no physics model, counts as physics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The file already says where each model's origin is, such as its object
origin in Blender: the loader centres the model on its box and records
where it moved the centre from. The pivot now has three modes:

- From file, the default: each model's origin as the file gives it, for
  uploads of one model or several. A model whose origin lies outside its
  box keeps its centre, since that origin is a scene's.
- Centre: as uploads were before.
- Custom: the spinners, for a single model, starting from the pivot last
  shown.

Each model's offset now travels with it to the upload, and each instance
stands on its own pivot, so a linkset's parts stay where the file placed
them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The radio items took their default layout, so they stacked under the
label and over the spinners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The modes stack in a column after the label, with the spinners and the
status to their right.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pivot override of each instance's position dropped the upload origin
that decomposeMeshMatrix adds, so pivoted uploads landed at the region's
corner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The instance transform already carries the import scale, so multiplying
by it again doubled the half extents a pivot in metres was read against.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file holding NAME, NAME_LOD2, NAME_LOD1, NAME_LOD0 and NAME_PHYS uploaded
every one of them as a model of its own: the loaders cut the suffixes off,
so each became a copy of NAME, and LOD slots filled only from separate
files. A high LOD file now keeps its names whole, and the preview moves
each suffixed model whose base the file has into that LOD's slot, where
the matching by name that separate LOD files use finds it. Those LODs are
not generated over; a LOD only some models have is generated instead.
Submodels move with their root, a later high file releases what an
earlier one filled, and physics taken this way warns of models without
one as an explicit physics file does. A suffixed model with no base is
named without its suffix, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With skin weights shown, the mesh is drawn on the preview avatar, away
from the instance transform the origin is drawn from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Whether a pivot is allowed was last worked out before the upload data was
rebuilt, so a physics file loaded since could still be sent unanalyzed
under a pivot the sim would not follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Physics copied from a LOD the high file then replaced kept the old file's
models, physics copied from a LOD no longer counted as the high file's,
and the default physics shape was cleared by every rebuild before parts
without a _PHYS could use it. A LOD set whose submodels differ from the
high LOD's is now generated rather than kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
How to place an origin on a hinge or joint in Blender, what the Pivot
modes and status lines mean, and the usual mistakes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
How to name and lay out parts and their _LOD and _PHYS objects in Blender
for a single-file upload, with the glTF export settings it needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file holding NAME beside NAME_LOD2, NAME_LOD1, NAME_LOD0 and NAME_PHYS
rezzed every one of them as a part of the preview linkset. Those are
NAME's lower LODs and physics shape, as the uploader reads the file, so
the preview now builds only the parts. A suffixed model with no NAME
beside it is still a part.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b33aaed6-257d-458d-905d-1371c1d2c52b
Commits

Reviewing files that changed from the base of the PR and between 82ce9ca and 98b00e9.

Files ignored due to path filters: 3

  • doc/mesh/images/blender-gltf-export.png is excluded by !**/*.png
  • doc/mesh/images/blender-outliner.png is excluded by !**/*.png
  • doc/mesh/images/uploader-pivot.png is excluded by !**/*.png
Files selected for processing: 15

  • doc/mesh/multi-object-single-file.md
  • doc/mesh/object-origins.md
  • indra/llprimitive/lldaeloader.cpp
  • indra/llprimitive/llmodel.cpp
  • indra/llprimitive/llmodel.h
  • indra/newview/gltf/llgltfloader.cpp
  • indra/newview/llfloatermodelpreview.cpp
  • indra/newview/llfloatermodelpreview.h
  • indra/newview/lllocalmesh.cpp
  • indra/newview/lllocalmesh.h
  • indra/newview/llmeshrepository.cpp
  • indra/newview/llmeshrepository.h
  • indra/newview/llmodelpreview.cpp
  • indra/newview/llmodelpreview.h
  • indra/newview/skins/default/xui/en/floater_model_preview.xml
Summary

Summary by CodeRabbit

  • New Features

    • A single .glb can include a model’s high-, medium-, and low-detail versions, plus a physics model. The upload preview recognizes models labeled with _LOD0, _LOD1, _LOD2, and _PHYS and assigns them to the corresponding slots.
    • Local mesh previews can display available detail levels and use an included physics model for collision.
  • Documentation

    • Added a guide covering scene setup, naming, materials, export settings, and common issues when preparing a multi-part Blender model for a single-file upload.

The change adds support for named _LOD0, _LOD1, _LOD2, and _PHYS models embedded in a high-LOD file. Loaders preserve their names, the upload preview assigns matching models to slots, and local mesh loading uses available LOD volumes and physics decompositions. A guide documents Blender setup and export.

Changes: Named LOD and Physics Models

Layer / File(s) Summary
Preserve and document suffixed model names
doc/mesh/multi-object-single-file.md, indra/llprimitive/lldaeloader.cpp, indra/newview/gltf/llgltfloader.cpp
The guide describes object naming, scene setup, export settings, and common mistakes. DAE and glTF loaders preserve full high-LOD labels so suffixes remain available.
Assign embedded models in the upload preview
indra/newview/llmodelpreview.h, indra/newview/llmodelpreview.cpp
The preview assigns matching suffixed models to LOD and physics slots. It releases missing or incomplete embedded LODs, skips automatic generation for supplied LODs, and handles physics sourced from an embedded model.
Use auxiliary models for local meshes
indra/newview/lllocalmesh.h, indra/newview/lllocalmesh.cpp, indra/newview/llmeshrepository.cpp
Local mesh parts store associated LOD volumes and physics decompositions. Loading requests the selected LOD volume and checks whether a local physics decomposition exists.

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant HighLODFile
  participant LLModelPreview
  participant ModelSlots
  participant AutomaticLODGeneration
  HighLODFile->>LLModelPreview: Load scene with suffixed model names
  LLModelPreview->>LLModelPreview: splitSuffixedModels(filename)
  LLModelPreview->>ModelSlots: Assign matching models to LOD and physics slots
  LLModelPreview->>AutomaticLODGeneration: Queue slots not supplied by the high-LOD file
Loading

Possibly related PRs

  • AlchemyViewer/Alchemy#628: Preserves and assigns suffixed models in high-LOD files; this PR extends the behavior to preview handling and local mesh loading.

Suggested labels: c/cpp, llprimitive, viewer

Suggested reviewers: ryemutt

Merge Risk: Moderate · previous review 82ce9

Resolve the physics-import crash and duplicated or missing upload geometry before merging. Local lower-LOD previews can also display incomplete, unskinned, or incorrectly textured geometry, and replacing the High file can override a selected LOD source.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed

Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The description follows the template's main sections. It has a Description section that explains the motivation, the changes (LODs, physics, origins), and testing. It does not fill in the Related Issu…
Title check Passed The title clearly describes the main change: local mesh previews now show embedded LOD and physics assets like uploaded meshes. It is concise and specific.
Full details: Description check

Explanation

The description follows the template's main sections. It has a Description section that explains the motivation, the changes (LODs, physics, origins), and testing. It does not fill in the Related Issues section with an issue link. It links PR #628 and PR #621 as dependencies, which is not the same as a linked issue. The Checklist and Additional Notes sections are not addressed. The core content is complete, so the check passes, but the missing issue link and checklist status should be added.

Full details: Docstring Coverage

Explanation

Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 18 functions across 7 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Share

A rabbit packs the LODs in line,
With suffixes neat and names that shine.
The physics mesh hops in the scene,
While Blender exports all in green.
Three little levels bound and play,
The rabbit thumps: “Ship one file today!”

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: cac338f7-7ed0-4698-8814-9eb3b1b26cd3
Commits

Reviewing files that changed from the base of the PR and between 6ba1a87 and 82ce9ca.

Files ignored due to path filters: 2

  • doc/mesh/images/blender-gltf-export.png is excluded by !**/*.png
  • doc/mesh/images/blender-outliner.png is excluded by !**/*.png
Files selected for processing: 8

  • doc/mesh/multi-object-single-file.md
  • indra/llprimitive/lldaeloader.cpp
  • indra/newview/gltf/llgltfloader.cpp
  • indra/newview/lllocalmesh.cpp
  • indra/newview/lllocalmesh.h
  • indra/newview/llmeshrepository.cpp
  • indra/newview/llmodelpreview.cpp
  • indra/newview/llmodelpreview.h

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

A local mesh showed its High model at every distance and had no physics
shape. Each part now keeps the file's NAME_LOD0-2 models as volumes the
repository serves for those LODs, falling back to the High model where
the file has none, and its NAME_PHYS model as the physics mesh the
physics display draws. Nothing is generated or decomposed, so a reload
stays as quick as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Martin-Pitt Martin-Pitt changed the title Leave a local mesh's named LOD and physics models out of its parts Show a local mesh's own LOD and physics models as an uploaded mesh would Nov 3, 2026
…esh-lod-names

A local mesh places each part on the origin its file gives, which needs the uploader pivot's file-origin rule and offsets.

@coderabbitai coderabbitai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • Fix comments: Fix CodeRabbit comments on this PR

Comment thread indra/newview/lllocalmesh.cpp Resolved
Comment thread indra/newview/lllocalmesh.cpp Resolved
Comment thread indra/newview/lllocalmesh.cpp Resolved
Comment thread indra/newview/lllocalmesh.cpp Resolved
Comment thread indra/newview/llmodelpreview.cpp Resolved
Comment thread indra/newview/llmodelpreview.cpp Resolved
A local mesh centred every part on its bounding box, so a door or joint
turned about its middle in the preview though the uploader keeps its file
origin. Each static part now takes the uploader's From file pivot: its
geometry, LOD models and physics shape move by the same offset the
uploader writes into the position domain, and the part is placed at its
pivot, so the linkset sits where the file placed it. Rigged parts stay
as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Martin-Pitt Martin-Pitt changed the title Show a local mesh's own LOD and physics models as an uploaded mesh would Show a local mesh's own LODs, physics and origins as an uploaded mesh would Nov 3, 2026
Martin-Pitt and others added 4 commits November 3, 2026 05:37
A model with more than eight materials uploads as a root and submodels,
so Custom and metres were refused for what is one model. The warning that
a pivoted part collides with a centred default hull was also hidden
whenever any other part of the linkset had physics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collada instances of a high file were still cut at any _LOD or _PHYS in
their names, so a part such as Wall_LODGE found no High model. A LOD set
was judged complete by its count, so a part without its own LOD could take
another's submodel; it now needs each part's model by name.
default_physics_shape_PHYS goes to physics without a part of that name,
as the separate physics file it stands for would. Physics loaded from a
file of its own no longer follows a LOD it was once copied from, and the
LOD controls show each slot's source once the file has set it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esh-lod-names

# Conflicts:
#	indra/newview/llmodelpreview.cpp
@Martin-Pitt

Copy link
Copy Markdown
Contributor Author

Okay, tested it with an example 3D model which has origins setup and multiple LODs which trigger normally as you would expect:
image

Martin-Pitt and others added 24 commits November 3, 2026 09:42
One rigged part, or one with physics not yet analyzed, held every part of
the upload at its centre; now only that part is held. Skin weights left
out of the upload no longer count as rigged, and a metre pivot reads the
import scale as it is now rather than as the upload data was last built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Physics set to use a LOD was copied from it while it was released for
generation, so it stayed empty and the upload fell back to the High
model's own hulls. It is now copied when the LOD it uses is generated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esh-lod-names

# Conflicts:
#	indra/newview/llmodelpreview.h
Two instances of one rigged model left the pivot controls enabled though
both were held at the centre, and a rigged part hid the warning that a
pivoted static part without physics collides with a centred hull.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The upload takes its skin from the High model, which can carry weights
its base does not, so such a mesh escaped the rigged check and had its
domain shifted. Rigged mesh is now judged from both models, and its
upload offset is zero whatever the pivot mode says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esh-lod-names

# Conflicts:
#	indra/newview/llmodelpreview.cpp
Physics chosen as a copy of a LOD was copied once and went stale when
the LOD changed: a new high file, the file's own LODs, generation or a
LOD file loaded later all left the old copy. Rebuilding the upload data
now copies that LOD again wherever it differs, which replaces the copies
the split and generation made at their own points. Every instance of a
shared leftover suffixed model now takes its stripped name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reloaded high file without _PHYS models emptied the physics slot but left the physics source on 'file', its file controls enabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant