Files
solvecraft/docs/point-clouds.md
T
Mario Geiger 1589e18f2a Point clouds: remember the source file and reload it (#8818) (#8821)
* Point clouds: remember the source file and reload it (#8818)

A cloud keeps the absolute path it was inserted from. pointcloud.reload
reads it again, keeping the cloud's name, placement, units and colouring,
and the filters on properties the file still has; the Edit Point Cloud card
has a Reload from file button.

The file is read beside the window through the file loader, with progress
and Cancel, as an insert is. A thinned cloud stays thinned to as many points,
which reads an unchanged file as the same points; any other is read as a new
insert would be, so a file that grew comes in whole.

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

* Point clouds: a reload asks before it reaches a network location (#8818)

A design names the cloud's file, and a design may come from anyone. On
Windows, only asking about `\\host\share\x` connects to that computer and
may send it the user's sign-in. So `pointcloud.reload` now reads through
`vfs::read_design_path`, as linked designs do, and Reload from file asks
with the same prompt as Update Links.

The button's label uses its stable message id. The strings gain Korean,
which a new label needs. `pointcloud.reload` gets its line in
tests/kernel-cases/EXEMPT, which the "Every command has a case" job needs.

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

* Point clouds: a reload reads under the limit the cloud was read under (#8818)

The limit was inferred from the counts, as if a cloud with fewer points than its
file had been thinned. A point that cannot be read makes the same difference:
such a PLY read as half its points on an unchanged file, since a limit one below
the file's count is a stride of two, and a file that grew was cut to the old count.
Keep the limit itself: a read is a function of the file and the limit.

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

* Point clouds: a reload drops a value filter when the file stops naming the values (#8818)

A set of values filters a label, and `pointcloud.edit` refuses one on a property with no names.
A reload kept such a filter while the property stayed, so a file that dropped its names comment
left a cloud every later edit refused. One predicate now says what is refused and what a reload
drops.

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

* Point clouds: a reload keeps its undo step and ends a play it moved (#8818)

A reload accepted with the Edit Point Cloud card open left the card's undo
boundary before it, so the card's next edit folded the reload into itself and
one Undo took back both. The boundary now moves past the reload.

A play told its filter by place alone. When a filter before it went, by a
reload, an agent's edit or an undo, the next filter took that place and the
play's range, on screen and written when the play ended. A play now names its
filter's property and ends, unwritten, when that place holds another.

`engine.batch` and the control hook compared command ids exactly, while the
engine takes any case: `POINTCLOUD.RELOAD` passed the refusal and read the file
on the window thread. Both now resolve the id as the engine does, for every
command that loads a file. The batch row of the protocol said the refusal held
only with the agent cursor shown; it always holds.

No test was run locally, at the owner's request: compile, clippy and fmt only.

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

* Point clouds: a file load keeps the request's own param_check (#8818)

`file_load::control` takes a command the file loader runs before
`control::handle` does, so that command skipped the request's `param_check`:
`pointcloud.reload` accepted a word that is no level, a `strict` call with an
unknown parameter read the file anyway, and the reply did not say the level.
`doc.open`, the imports and the DXF export went the same way.

The hook now takes the level itself and checks the call before the file is
touched, as a command that switches designs does: the worker's session goes on
to be the design's, which keeps its own level. Both share the same check, and
the reply says the level and any warnings.

No test was run locally, at the owner's request: compile, clippy and fmt only.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 06:56:52 +00:00

9.2 KiB
Raw Blame History

Point clouds

A point cloud is scanned points kept in the design as a reference to model from (#5151): a lidar or SLAM map, a photogrammetry result, a part from a handheld scanner. It is drawn as dots in the viewport, depth-tested with the bodies, and never becomes a body; nothing in the timeline depends on it.

Files

Extension What is read
.ply ASCII, binary little-endian and binary big-endian PLY: the vertex element's x, y, z (any numeric type) and its red, green, blue when present (bytes, 16-bit or 0…1 floats). Every other scalar vertex property (intensity, a class, a time…) is kept, by its name in the file, to colour and filter the cloud by; it is whole-numbered when its type is an integer. The edge element (vertex1, vertex2) and the face element (vertex_indices, a face as a fan of triangles) become segments and triangles between the points, with their own properties. Other elements are passed over.
.xyz, .pts Text, a point per line: x y z, optionally followed by r g b (six numbers) or by an intensity and r g b (seven or more, as PTS files write it). Spaces, tabs, commas or semicolons separate the numbers; # and // start comments; a PTS file's first line (its count) is skipped.

A PLY file with faces can also be inserted as a mesh body with Insert Mesh (file.insert_mesh), which makes it solid geometry rather than a reference.

Point cloud files say nothing about their length unit. units (mm, cm, m, in, ft) says what it is; the default is millimetres, as for every other import. Lidar and SLAM maps are usually in metres. The unit can be changed afterwards (Browser › Point Clouds › Edit Point Cloud, or pointcloud.edit with units): the points are scaled from the unit they came in as.

Reloading

A cloud remembers where its file was (source, the absolute path it was inserted from), so a file that changed can be read again: Reload from file in the Edit Point Cloud card, or pointcloud.reload. The points, colours, segments, triangles and properties are replaced; the name, placement, units, point size and colouring are kept, and so are the filters on properties the file still has. A filter on a set of named values goes when the file no longer names them. Colouring by a property the file no longer has goes back to the file's colours. The file is read under the limit the cloud was inserted with (max_points), so the same file reads as the very same points and a file that grew comes in whole unless it exceeds that limit, when it is thinned as an insert would thin it. The file is read beside the window, with progress and Cancel. A cloud opened from a design saved before it remembered its file cannot be reloaded. A file on another computer (\\host\share) is treated as a linked design's is: Reload from file asks before it connects to that server, and pointcloud.reload is refused until the server is allowed in Preferences.

Colours

Color By in the Edit Point Cloud card (or color_by) says how the points are coloured:

  • file (the default): the file's colour of each point, grey when it has none.
  • single: one colour (color) for every point.
  • height: a ramp by design Z, blue low through cyan, green and yellow to red high. It runs between two heights (color_range, mm). By default they are worked out from the points: all but the lowest and highest 2 %, so a few stray points don't flatten the ramp.
  • any property's name (x, y, z or one of the file's): the same ramp over the property's values or, for a whole-numbered property, a palette of ten colours (its value modulo ten; negative values grey). By default the ramp runs exactly over the values the property takes on the points every filter keeps, so filtering re-spreads the colours over what is left.

A ramp's range is a range track in the card; touching it sets the range and turns Automatic Range off, and ticking the box (or a double click beside the range) gives it back. color_range: null does the same from a command.

A whole-numbered property may name its values with a PLY comment, property: 0 name, 1 other name, -1 none (for example comment role: 0 outer wall, 1 inner wall). Coloured by it, the card shows a legend, a swatch and a name per value; clicking a name shows or hides that value.

Edges and faces

A cloud read from a PLY file with edges or faces draws them between its points: triangles filled, segments as lines or, when they have width and height properties, as beads of that stadium cross-section (a tube when the two are equal), meeting mitred where one runs into the next. Points no segment or triangle ends on stay dots, of the cloud's point size. A point that can't be read (not a finite number, or out of range) is left out with a warning, and so are the segments and triangles that end on it.

A property on the points varies along the segments and across the triangles that meet there; one on the segments or triangles is constant on each. Colours follow the same rule, and a range filter cuts an element exactly where its value crosses an end of the range, so a filter on a time property replays a path as it was drawn. A range track weighs each element by its measure, each kind over its own total: a dot counts 1, a segment its length, a triangle its area.

Filters

A filter keeps the points whose property lies in a range (both ends included) or, for a property with named values, is one of a set of values. Only what every filter keeps is shown; a cloud has at most 8 filters, and the view applies the first 4. Filter by in the card adds one over a property's whole span (or with all its named values); × removes it. Filters change what is drawn, never the points the design holds.

Range tracks

Each range filter, and a ramp's range, is edited on a track, as a range is in an audio editor. The track draws how much of the cloud lies at each value: a histogram of the property over what the other filters keep, stacked by colour, so it is also the colour legend. A thin strip above it shows the property's whole span, with the part the track shows framed.

  • Drag an end or the band between them to move them; drag elsewhere to draw a new range. Shift-click brings the nearer end to the pointer.
  • Double-click the band to fit the track to it; double-click beside it for the whole span. The wheel or a pinch zooms around the pointer, a sideways scroll pans, and dragging in the strip moves the framed part.
  • An end snaps to a value where the colour changes when no other change is near at this zoom; a whole-numbered property's ends snap to whole numbers. ⌥ moves ten times slower, without snapping.
  • Once the track is touched, ← and → bring the part touched last to the previous or next colour change (one pixel with ⌥, or where there is none).
  • Start, End and Length are fields too; Length keeps the start. A value typed with a sign is relative: +30 moves it by 30.
  • ▶ Play (or Space) moves the part touched last across the track in 20 seconds: an upper end grows the range, the band slides it, a lower end wipes it. The track follows it, and an automatic colour range follows it too. Touching the range stops it.

Size

A cloud keeps at most max_points points (5,000,000 unless asked, at most 20,000,000); a larger file is thinned evenly, one point in n, and the result says how many were kept. Thinning would cut a file's edges and faces, so a thinned file leaves them out, with a warning; raise max_points to keep them. A cloud holds at most 20,000,000 segments and as many triangles. Files up to 1 GiB are read. The points are stored single precision relative to the cloud's first point (kept double precision), so a scan far from the origin keeps its detail; they travel inside the design file as a binary part (12 bytes a point, 3 more with colours).

Commands

  • pointcloud.insert — path; name?, units?, max_points?, center? (move the middle of its base to the origin), point_size? (pixels), color_by? (file, single, height or a property's name), color? ([r, g, b] 0…1; alone it means single, null means file), color_range? ([low, high], or null for the automatic range; height_range is the same), filters? ([{"property": …, "range": [low, high]} or {"property": …, "values": [whole numbers]}], or null for none).
  • pointcloud.edit — cloud (id or name); name?, visible?, point_size?, color_by?, color?, color_range?, filters?, units?, matrix? (a rigid placement, as occurrence.move takes it).
  • pointcloud.reload — cloud; reads its file again (see Reloading).
  • pointcloud.delete.
  • pointcloud.list — each cloud with its source, its segment and triangle counts, its properties (name, whole-numbered or not, span, named values), its filters and its ramp's range as drawn (color_range).
  • doc.open opens a point cloud file as a new design holding it; dropping one on the window adds it to the open design.

Example

{"command": "pointcloud.insert", "params": {"path": "glim-points.ply", "units": "m", "color_by": "height"}}
{"command": "pointcloud.edit", "params": {"cloud": "glim-points", "color_by": "intensity", "filters": [{"property": "z", "range": [0, 2500]}]}}