Skip to content

Replace interoff lib completely in mcstas with geometry-mesh-lib - #3090

Draft
Lomholy wants to merge 21 commits into
mccode-dev:mainfrom
Lomholy:mesh_lib
Draft

Lomholy wants to merge 21 commits into
mccode-dev:mainfrom
Lomholy:mesh_lib

Conversation

@Lomholy

@Lomholy Lomholy commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Free-form text area

Please describe what your PR is adding in terms of features or bugfixes:
This PR removes the interoff lib from the remaining mcstas components, and makes them use the geometry-mesh-lib instead.

Therefore this PR adds a gravity intersection algorithm to the mesh lib.
The PR also adds a function to the mesh lib for checking if a mesh is closed or not, and a bug regarding the socket.off data file was found and fixed, and a check for foldings in meshes has been added.
The McXtrace components will be converted in a future PR.

During the work I also realized that no component could use the time of the hits as it was keyed to hits.t. it is therefore keyed to hits.time now

Declaration of use of AI-tools

  • Please add a checkmark here if you used AI-tools during the work for this contribution
  • Furter, please describe how / where and for what the tools were used:
    The debugging and code writing was done with claude.

Development OS / boundary conditions

Please describe what OS you developed and tested your additions on, and if any special dependencies are required:
MacOS Golden bridge


PR Checklist for contributing to McStas/McXtrace

For a coherent and useful contribution to McStas/McXtrace, please fill in relevant parts of the checklist:

  • My contribution includes patches to an existing component file

    • I have used the mcdoc utility and rendered a reasonable documentation page for the component (please attach as screenshot in comments!)
    • I have ensured that basic use of the component is OK (e.g. an instrument using it compiles?)
    • I have used the mctest utility to test one or more instruments making use of the component (please attach mcviewtest report as screenshot in comments)
    • I have used the mccode-clangformat tool to apply the standard McCode component indentation scheme
    • I have used the mcrun --c-lint "linter" and followed advice to remove most / all warnings that are raised
  • My work touches / adds to the runtime lib code (.c,.h etc in multiple locations

    • I am have added reasoning and documentation for the change below
    • I am attaching test output in the comments
    • Unit test: every new or changed common/lib/share, mccode/nlib/share, mcstas-comps/share or mcxtrace-comps/share library and runtime file (.c, .h, .h.in) has a Test_lib_<name> instrument in examples/libtest/ (McStas and/or McXtrace), with a %Test: line where the result can be checked. If it cannot be standalone, say why in the free-form text (e.g. needs a component or external package)

lomholy_agentic and others added 19 commits November 10, 2026 14:51
McStas Shape and Shape_simple and McXtrace Shape read and draw the
geometry file with geometry-mesh-lib instead of interoff-lib:
off_init becomes gmesh_init_fit (same resizing, center passed as is)
and off_display becomes gmesh_display. PLY and STL files now work
too; the docs say so, and that center centres the bounding box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gmesh_intersect_gravity returns the hits of position + t*velocity +
t^2/2*acceleration with the mesh, sorted in t, as gmesh_intersect does
for a line. The BVH traversal is shared: gmesh_trace takes the
acceleration and then uses a box test per axis (gmesh_slab_gravity) and
the crossings of the parabola with each triangle plane
(gmesh_tri_hit_gravity, up to two per triangle). in_out uses the
velocity at the hit. With zero acceleration the straight-line code runs
unchanged, and gmesh_intersect now calls gmesh_intersect_gravity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Incoherent, PowderN, Isotropic_Sqw, Single_crystal,
Single_magnetic_crystal, Single_crystal_inelastic, Refractor and Lens
get a SHARE function <Component>_mesh_intersect that picks t0/t3 (and
the normals) from gmesh_intersect with the convention of off_intersect,
so the TRACE code around the call is unchanged. Hits ahead and behind
are searched separately, a hit on the surface the neutron sits on
counts as ahead, and the inside test is the one of Monitor_nD.
Normals keep the interoff orientation.

Guide_anyshape and the obsolete Guide_anyshape_r take the first hit
ahead from gmesh_intersect_gravity, so gravity is handled as before;
per-face coatings come from gmesh_face_prop. The parked Monitor_Sqw
uses Monitor_nD_MeshIntersect.

off_init becomes gmesh_init_fit with the same resizing, and off_display
gmesh_display. Isotropic_Sqw loses an unused global offdata.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
INITIALIZE runs checks of gmesh_intersect_gravity and exits with an
error when one fails: zero acceleration gives the hits of
gmesh_intersect, parabolas through cube.off give the analytic times
and in_out, and 2000 random parabolas through socket.off give the hits
of a 4000-piece polyline of gmesh_intersect lines. The random rays use
a fixed sequence, so the result does not depend on the seed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- geometry-mesh-lib: gmesh_intersect and gmesh_trace are back to the
  straight-line code of mesh_lib. Parabolas have their own traversal,
  gmesh_trace_gravity, under a separate section. gmesh_hit_time returns
  the time of a hit, for TRACE sections where t is the particle time.
- Samples: <Component>_mesh_intersect only searches ahead, skipping the
  surface the neutron sits on. A neutron inside gets t0 = 0 and t3 the
  next hit; outside, t0 and t3 are the next two hits.
- Refractor uses the same function, plus the normal of the next hit and
  an inside flag for inbulk. Lens takes the next hit and its normal
  from Lens_mesh_next.
- Guide_anyshape, Guide_anyshape_r: gmesh_intersect_gravity inline in
  TRACE.
- The parked Monitor_Sqw and McXtrace Shape are left as on mesh_lib.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In component TRACE sections t is the particle-time macro, so hit.t
did not compile there. With the field named time, TRACE reads
hits[i].time directly and the gmesh_hit_time accessor is removed.
All users are updated: geometry-mesh-lib, the McStas components and
libtest instrument of this branch, monitor_nd-lib, and the McStas and
McXtrace union-lib.

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

gmesh_entry_exit gives the next entry and exit hits of a line through a
mesh, or only the exit when the particle is inside. It skips the surface
the particle sits on; an open mesh counts as outside.

Incoherent, PowderN, Isotropic_Sqw, Single_crystal,
Single_magnetic_crystal and Single_crystal_inelastic call it in TRACE
instead of their own <Component>_mesh_intersect, with t0 = 0 inside.
Refractor calls it inline and takes the normal of the next hit from it.
The libtest instrument checks it on cube.off. Comments added or edited
on this branch use //.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The next hit is the entry from outside and the exit from inside, so
Lens calls gmesh_entry_exit at its three call sites and its own
Lens_mesh_next is removed. Results are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gmesh_entry_exit_gravity is gmesh_entry_exit for a parabola: it takes
the hits from gmesh_intersect_gravity. Both pick the entry and exit
with gmesh_pick_entry_exit. The libtest instrument checks it on
cube.off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Monitor_nD and Monitor_nD_noacc take t0/t1 and the mantid pixel from
gmesh_entry_exit, and Monitor_nD_MeshIntersect is removed from
monitor_nd-lib.

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

gmesh_entry_exit and gmesh_entry_exit_gravity skip a hit closer than
GMESH_TOL (default 1e-12 m) as the surface the particle sits on,
instead of 1e-9 of the mesh size. It can be changed from an instrument
with DEPENDENCY " -DGMESH_TOL=... ". The comments now say first entry
and exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The triangle fans of the faces at z = -25 and z = 25 reached past the
chords on which the side wall stands, and one triangle at each end
(faces 159, 160, 195, 219) folded back over the fan. The mesh was
closed and consistently oriented, but overlapped itself. Each face is
now triangulated inside its outline (two regions with a hole each).
The volume is unchanged; vertices 0, 1, 325 and 326 are no longer
used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se gmesh_inside + gmesh_intersect

geometry-mesh-lib:
- gmesh_init fails for a folded mesh, where the two triangles of an edge
  turn back onto each other (nfolded counts these edges).
- gmesh_inside answers from the first hit along a ray for a closed,
  oriented mesh, skipping hits closer than GMESH_TOL, with the parity
  test for edge hits and coincident hits. The bounding box test is
  padded by GMESH_TOL.
- gmesh_entry_exit and gmesh_entry_exit_gravity are removed.

Components:
- Incoherent, PowderN, Isotropic_Sqw, Single_crystal,
  Single_magnetic_crystal, Single_crystal_inelastic and Refractor use
  gmesh_inside and gmesh_intersect (t_min = GMESH_TOL/|v|) inline. The
  crystals enter a mesh 1 ns past its surface. Lens takes the first hit
  of gmesh_intersect. Isotropic_Sqw handles a mesh once, for either sign
  of thickness.
- The sample components document that a mesh must be a single, closed
  object.
- Shape, Shape_simple and these samples abort when the geometry file can
  not be used.
- Monitor_nD, Monitor_nD_noacc and monitor_nd-lib are as on mesh_lib,
  apart from the gmesh_hit time rename.

The libtest instrument checks the fold check and the gmesh_inside
shortcut instead of gmesh_entry_exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…L in components

- gmesh_inside no longer pads the bounding box, and returns 0 for an
  open mesh, which encloses no volume.
- Monitor_nD_MeshIntersect uses gmesh_inside for the inside test.
- The components pass t_min = GMESH_TOL to gmesh_intersect.
- Redundant braces in the mesh code of the components are removed.

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

The fold check moves out of gmesh_init into gmesh_check_closed. That
optional check passes a mesh that is closed and does not fold back
onto itself, and prints the reason otherwise. A folded mesh gets
closed = 0, so afterwards mesh->closed holds the result. gmesh_init
no longer rejects folded meshes, so open or folded surfaces can still
be used where no volume is needed (Shape, Guide_anyshape, Monitor_nD).

Incoherent, PowderN, Isotropic_Sqw, Single_crystal,
Single_magnetic_crystal, Single_crystal_inelastic, Refractor, Lens and
Union_mesh (McStas and McXtrace) call it and abort for a mesh that does
not pass. The libtest instrument checks it on cube.off, socket.off, the
open Guide.off and a folded cube.

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

gmesh_init counts the edges where the surface folds back onto itself
(nfolded, with the first pair of faces in folded_face) and warns about
them in its summary. Only gmesh_check_closed rejects such a mesh, using
these counts.

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

GMESH_TOL is removed. gmesh_inside traces from t_min = 0, and tells
coincident hits apart with GMESH_EPSILON times the mesh size.

The samples call gmesh_intersect with t_min = 0. The crystals enter a
mesh 1e-13 s past its surface (was 1 ns), and Refractor adds 1e-13 s to
the hit times it propagates to, so that a neutron does not sit on the
surface. Lens keeps a t_min of 1e-13 s, as a neutron reflected there
would otherwise be past the surface it left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
geometry-mesh-lib: gmesh_intersect_gravity; move McStas components from interoff-lib
@Lomholy

Lomholy commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

I will continue work on this tomorrow, but opening it now, as I didn't realize that I have to run!

…ocess

- gmesh_quadratic_roots had a parameter named constant. SasView's
  sas_kernel_header.c defines constant as a macro (const), so any
  instrument combining a SasView component with a geometry-mesh-lib user
  (e.g. Monitor_nD in templateSasView_Mantid) failed to compile. The
  parameter is now constant_term.
- Test_lib_geometry_mesh_lib wrote, read and removed one scratch file
  for its folded-mesh check from every MPI process, so under MPI one
  process could find it missing or half written. The file name now
  carries the MPI rank.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
geometry-mesh-lib: fix SasView macro clash and MPI race in its libtest
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant