feat(data_load_3d): static PLY/PCD 3D loader (PointCloud + Mesh3D)#189
Merged
Conversation
Task 1 wired only the subdirectory-mode loader list; the self-contained loaders (csv/mcap/parquet/ulog) are in both lists, so add data_load_3d to the standalone-mode else() block too. Plan Step 5 updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dening Address findings from spec/code-quality/Codex reviews of the packing core: - Return a self-owning PJ::sdk::PointCloud (bytes held via BufferAnchor) instead of the copy-unsafe BuiltCloud wrapper whose data span dangled on copy; drop BuiltCloud entirely. - Guard width*height*point_step against integer overflow and oversize allocation before resize() (kMaxCloudBytes cap), and reject a positive point count with zero stride (would spin the ascii loop). Prevents heap OOB writes / DoS on crafted headers. - Overflow-safe computeLayout stride accumulation (uint64_t + uint32_t cap). - Parse ascii with std::locale::classic() so dot-decimals survive a global comma-decimal locale (Qt may install one). - Skip non-finite doubles when packing integer fields (cast would be UB). - static_assert little-endian host; fix computeCentroid doc. - Tests: overflow rejection, no-fields rejection, kUnknown/short-ascii errors, count>1 pack/byte-swap, and a copy-survives-destruction test that locks in the anchor ownership. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parse .pcd header (FIELDS/SIZE/TYPE/COUNT/WIDTH/HEIGHT/DATA) into ParsedFields and funnel through cloud_common::buildPointCloud. ascii + binary supported; binary_compressed returns a clean error (deferred to a later task). Numeric header fields parsed with std::from_chars so malformed input returns an error instead of throwing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parse the ascii .ply header; vertices-only files become a self-owning PointCloud via cloud_common, files with faces become a self-owning Mesh3D holding the raw .ply bytes (renderer parses geometry). Element counts parsed with std::from_chars (no throwing); vertex-count uint32 narrowing guarded. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dispatch by extension (.pcd/.ply) to the readers, push the serialized PointCloud/Mesh3D to the ObjectStore at t=0 with builtin_object_type metadata (kPointCloud/kMesh3D) so PJ4 classifies+renders it, and push a scalar summary (num_points, num_faces for meshes, centroid x/y/z). Falls back to summary-only when no object-write host is bound. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vendor liblzf 3.6 (BSD-2-Clause) as separate C static libs (kept out of the C++ -Werror wall). Decode PCD binary_compressed: parse the compressed/ uncompressed size header, LZF-decompress, and transpose the field-major (SoA) buffer to packed AoS. The declared uncompressed size is validated against width*height*point_step (<= kMaxCloudBytes) BEFORE allocating, guarding against a decompression bomb. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Document the loader: PLY/PCD routing (cloud vs mesh), supported encodings (PCD ascii/binary/binary_compressed, PLY ascii/binary LE+BE), the t=0 single-snapshot behavior, the <stem> object topic + <stem>/summary scalars, and the VIEWPOINT-ignored limitation. Note the new (non-port) plugin in PORTING_PLAN. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract builtinObjectMetadata/lowerExtension/fileStem into loader_helpers.hpp
and unit-test them (house style: sibling data_load plugins test pure helpers).
Locks the {"builtin_object_type":"kPointCloud"|"kMesh3D"} JSON — the string
PJ4 classifies on to render the object — plus the extension/stem/dotfile logic,
against regression.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
f517359 to
d61ae17
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New
data_load_3dDataSource plugin: loads a static.ply/.pcdfile as a single 3D object at timestamp 0, plus a scalar summary. Not a port — a new plugin..pcd(any encoding)sdk::PointCloud.plyvertices-onlysdk::PointCloud.plywith facessdk::Mesh3D(raw asset bytes; renderer/Assimp parses geometry)DATA:ascii,binary,binary_compressed(LZF, via vendored liblzf 3.6, BSD-2-Clause).format:ascii,binary_little_endian,binary_big_endian.<stem>(tagged{"builtin_object_type":"kPointCloud"|"kMesh3D"}so PJ4 classifies + renders it) and a scalar topic<stem>/summary(num_pointsfor clouds,num_facesfor meshes,centroid/x,y,z). Degrades to summary-only when no object-write host is bound.Architecture
loader_3d.cpp(FileSourceBase) dispatches by extension to two hand-rolled header parsers (pcd_reader,ply_reader) that funnel through one shared packing core (cloud_common). All returned objects are self-owning (own their bytes viaanchor), so they are safe to copy/store past the reader call. The readers are total functions over untrusted input: malformed bytes return an error, never throw / read-or-write OOB / hang. Notable guards:from_charsnumeric parsing (no throwingstoul), akMaxCloudBytes(2 GiB) cap, a zero-stride-with-points reject, and a decompression-bomb guard (validate declared uncompressed size againstwidth*height*point_stepbefore allocating).Testing
cloud_common,pcd_reader,ply_reader,loader_helpers), all green under the strict-Werrorwall. Parsers are verified via serialize→deserialize round-trips through the real SDK codecs.builtin_object_typemetadata JSON — the string PJ4 classifies on to render the object — is pinned by a test.conanfile.py+manifest.json, noarrow/dep) into the standard build/test matrix; no workflow edit needed.Notes / follow-ups
pj_proto_app/GUI host in the per-plugin CI workspace) — recommended as a manual verification. The object-write seam and the metadata contract were verified against the actual SDK/PJ4 classifier headers.FileSourceBaseglue (summary-schema branching, null-host fallback) is verified by inspection + the pinned metadata/path-helper tests; aFakeHostloader test is a reasonable future addition (matchestoolbox_mosaico's pattern).🤖 Generated with Claude Code