Files
OrcaSlicer/src/libslic3r/LayOnFace.hpp
T
packerlschupfer 93c8b3f2b0 CLI: --ground-* orientation from the Lay on Face planes, and --inspect-mesh (#15073)
* CLI: --ground-face-* / --lay-flat / --center-on-bed orientation primitives

Adds the CLI counterparts to the GUI's lay-flat / face-pick gizmos.
Scripted / CI / AI pipelines can now set orientation without rendering
a wxWidgets frame; today the only way is a GUI round-trip.

New CLI actions (all operate in the mesh-local frame so they compose
with prior --rotate-* / --orient flags):

  --ground-largest-face 1     Auto-detect the largest planar-face
   or  --lay-flat 1           cluster (area-weighted), rotate so its
                              normal points -Z. Covers "this part has
                              one obvious flat side" cases.

  --ground-face-normal NX,NY,NZ    Pick the face whose mesh-local
                                   normal best matches the given
                                   vector; ground it. e.g.
                                   `--ground-face-normal 1,0,0`
                                   stands a part on its +X side.

  --ground-face-point X,Y,Z        Find the triangle containing the
                                   given mesh-local point; ground its
                                   face. Disambiguates when several
                                   faces share a normal (largest
                                   containing triangle wins).

  --center-on-bed 1                Translate so the XY bounding-box
                                   centroid lands at the bed center
                                   (derived from printable_area).

New file `src/slic3r/Utils/MeshOrient.{hpp,cpp}`:
- collect_triangles_object / compute_face_clusters — quantize
  per-triangle normals (0.001, ~0.06°) and area-weighted-average
  within clusters. Same clustering logic used by lay-flat.
- apply_ground_rotation — same math as Selection::flattening_rotate
  in the GUI (Selection.cpp:1432): world-space quaternion from the
  transformed normal to -Z, applied as offset * new_rot * old_no_offset
  on every instance of every object, then a per-instance Z-lift so the
  grounded face lands at exactly 0 (avoids "No layers were detected"
  from FP-error z≈-1e-9).
- ground_face_point uses a top-N cluster search + point-in-triangle
  test in local space; largest-area triangle wins on ambiguity.

Rationale: without these, any CLI pipeline that needs a specific
face on the bed must either encode custom rotation math per part or
break out of the pipeline into the GUI. Both are bad for
reproducibility. The --ground-face-* triple + the largest-face
auto-mode cover essentially every orientation intent expressible
in a slicing wizard.

Scope:
- `src/slic3r/Utils/MeshOrient.{hpp,cpp}` — new, ~420 lines
- `src/slic3r/CMakeLists.txt` — 2-line registration
- `src/libslic3r/PrintConfig.cpp` — 5 new CLIMiscConfigDef entries
- `src/OrcaSlicer.cpp` — 58-line handler block + 1 include

No behaviour change when the flags are absent.

(cherry picked from commit c45a9795e1)

* CLI grounding: choose among the Lay on Face planes, per object

Addresses review:
- Move the geometry of GLGizmoFlatten::update_planes() into
  libslic3r/LayOnFace and use it from the gizmo and the CLI, so the
  --ground-* options pick convex-hull faces per object and instance,
  with part transformations (--rotate-x/y) applied.
- Drop --center-on-bed, the --lay-flat alias and MeshOrient; make
  --ground-largest-face a coBool.
- Parse --ground-face-normal and --ground-face-point strictly. A point
  that only some objects contain grounds those and leaves the others.
- Fold in --inspect-mesh from #14603, reporting the same planes.
- Tests in tests/libslic3r/test_lay_on_face.cpp: bounding boxes before
  and after, rotate then ground, two objects, and a ribbed part whose
  parallel inner faces outsum its base.

* CLI --inspect-mesh, --ground-face-*: reject missing input and empty values

- Without an input file or --load-assemble-list, --inspect-mesh printed
  nothing and exited 0. Reject it up front with CLI_INVALID_PARAMS.
- An explicit empty --ground-face-normal or --ground-face-point was
  silently ignored. Only options given on the command line reach the
  transforms loop, so an empty value now fails the strict parse like any
  other malformed value.
2026-09-16 12:56:46 +08:00

49 lines
2.4 KiB
C++

#pragma once
#include "Point.hpp"
#include <vector>
namespace Slic3r {
class ModelObject;
// A face of an object's convex hull that the object can rest on. These are the faces the
// "Lay on Face" gizmo offers and the ones the CLI --ground-* options choose from.
//
// Frames: "object" coordinates have the volume transformations applied but not the instance
// transformation. "Instance" coordinates additionally have the instance rotation, scale and
// mirror applied, but not its offset.
struct LayOnFacePlane
{
Vec3d normal; // outward unit normal, object coordinates
Vec3d center; // centroid of the outline, object coordinates; on the face's mean plane
float area; // mm², instance coordinates
Pointf3s outline; // convex outline in the plane frame, where the face is horizontal
Transform3d to_plane_frame; // rotation from instance coordinates to the plane frame
};
// Candidate faces of the object's model parts, largest first. The instance transformation
// (without offset) is applied before measuring, so faces too small to rest on are dropped
// by their printed size: under 5 mm², a side under 1 mm, or an inner angle under 1°.
std::vector<LayOnFacePlane> lay_on_face_planes(const ModelObject &object, const Transform3d &instance_matrix_no_offset);
// Index of the largest plane, or -1 if `planes` is empty. Of planes with the same area, such as
// the top and bottom of a box, the one already facing down the most wins, so flat parts stay put.
int find_largest_plane(const std::vector<LayOnFacePlane> &planes);
// Index of the plane whose normal is closest to `direction` (object coordinates),
// or -1 if `planes` is empty.
int find_plane_by_normal(const std::vector<LayOnFacePlane> &planes, const Vec3d &direction);
// Index of the plane whose face contains `point` (object coordinates) within `tolerance` mm, or -1
// if there is none. `instance_matrix_no_offset` is the one the planes were computed with.
int find_plane_at_point(const std::vector<LayOnFacePlane> &planes, const Transform3d &instance_matrix_no_offset,
const Vec3d &point, double tolerance);
// Rotates the instance so that `normal` (object coordinates) points down, the same rotation as
// the gizmo applies, then drops the instance so its lowest point is at z = 0.
void lay_on_face(ModelObject &object, size_t instance_idx, const Vec3d &normal);
} // namespace Slic3r