Compare commits

..
13 Commits
Author SHA1 Message Date
Clifford Yapp dac8f504bf Complete experimental GQA bounds and visualization
Derive finite sampling bounds from prepared region trees so bounded
half-space intersections and subtractions remain analyzable.  Reject
unbounded selections explicitly, propagate unbounded submodels, and keep
infinite solids out of the Crofton sampling sphere without removing them
from Boolean evaluation.

Add an unsampled bounds measurement with text and JSON reporting.  Use
the same conservative bounds for sampling, adaptive dimensional targets,
and automatic visualization scale.

Restore Plot3 output for volume, gaps, overlaps, adjacent air, and
exposed air.  Provide exact all-rays and bounded representative
policies.  Representatives retain deterministic spatial samples, draw
finite grid or Crofton support footprints, and preserve diagnostic
severity and region identity.

Publish immutable plot batches only between parallel ray batches.
Add an MGED main-thread bridge that incrementally replaces or appends
the named gqa::analysis overlay while keeping all view mutation on the
display-owning thread.  Repair named vlblock replacement so refreshed
objects preserve their identity and view ownership.

Document the behavior, limitations, and supporting sampling references,
including Owen's replication guidance.  Add the analytic and complex
model comparison report.  Expand regression coverage for bounds,
half-spaces, submodels, plot policies, diagnostic files, and live MGED
output.
2026-09-21 16:06:58 +00:00
Clifford Yapp 37f583285d fixes 2026-09-21 06:33:46 -04:00
Clifford Yapp ce1307152b Add experimental GQA analysis modes
Add a separate --analyze interface while preserving existing GQA
behavior.  Move the new measurement engine into libanalyze so the
standalone and GED commands share bu_opt parsing, sampling, density
loading, reporting, and schema-versioned JSON output.

Provide axis-aligned and rotated three-view grids.  The rotated mode
builds an orthonormal view triad, clips boundary cells to projected
model bounds, includes transverse covariance in mass moments, and
shifts successive grids to reduce alignment aliasing.  Run grid rays
in parallel with worker-local accumulators and publish only complete
three-view passes.

Add time-first defaults, scale-aware starting spacing, dimensional
stability checks, directional spread reporting, and progress updates.
Retain explicit spacing, refinement, ray, seed, and deadline controls
for repeatable studies.

Extend Crofton sampling with selectable random and QMC sequences,
streaming region and ray visitors, reusable parallel sessions,
diagnostic overlap records, progress callbacks, and explicit stopping
statistics.  Estimate sampling intervals from independent randomized
replications and keep pilot planning separate from production results.

Add invalid-ray replay support to nirt, update the user and developer
manuals, and expand regression coverage for parsing, clipped and
rotated grids, uncertainty, diagnostics, output safety, and
reproducibility.  Reject malformed Crofton controls before sampling
and make legacy overlap invalidation explicit in its report.
2026-09-20 12:02:59 +00:00
Clifford Yapp 7880489c2b Fix MGED Unity build declarations 2026-09-20 12:02:59 +00:00
Clifford Yapp 55e788215a Support using OpenQMC for Cauchy/Crofton sampling instead of PRNG 2026-09-20 12:02:59 +00:00
Clifford Yapp 0d745fca35 Use bu_vls for VFONT paths
The VFONT loader used a 128-byte buffer when constructing the bundled
font path. Odd-pathname distcheck directories exceeded that limit in CI
environments, causing fblabel to reject the default font and all
dependent rtwizard tests to fail. Use a bu_vls path so the installed
font can be opened regardless of build prefix length, and release it on
all loader exits.
2026-09-18 16:39:52 -04:00
Clifford Yapp 5270e67abe List files for distcheck 2026-09-18 15:47:02 -04:00
Clifford Yapp 14051845a3 facetize: add source-guided adaptive SPSR
Fixed-size point dumps make Poisson reconstruction unreliable as a
last-resort facetization method.  They can miss thin partitions while
oversampling surfaces that are already well resolved, and an arbitrary
point count does not show whether source characterization is complete.

Seed reconstruction with stabilized Crofton samples.  Retain paired
entry and exit normals, partition thickness, and ray identities so thin
geometry contributes explicit local information instead of unrelated
points.

Add a bounded adaptive libbg driver around the existing upstream
Poisson solver.  Each solve reports residual, density, normal, and
surface-variation hints to a caller callback.  Facetize responds with
exact source rays and validates partitions, endpoints, chord length,
area, volume, and closed-manifold topology before accepting a result.

Bound refinement by time, point, pass, and convergence limits.  Relax
only chord and thin-partition thresholds when the source BoT is already
known to be non-manifold.  Deprecate the fixed-array API, remove the
intermediate point-sampling path, adapt pnts callers, and add focused
coverage.  The vendored SPSR implementation remains unchanged.

Use configured method priority for perturbation variants so an MDC-only
run never invokes NMG.  Report finalization progress and retry counts
while the fallback hierarchy is evaluated.
2026-09-18 16:14:16 +00:00
Clifford Yapp d6fa937212 libanalyze: replace marching cubes with adaptive MDC
The legacy marching-cubes polygonizer sampled a uniform grid and could
miss thin or high-aspect features, merge undersampled tunnels, and emit
surfaces whose shared-edge winding was inconsistent.  Increasing the
global resolution made those failures prohibitively expensive rather
than addressing their local causes.

Add a ray-driven manifold dual contouring API built on an adaptive
octree.  Cache exact signs and Hermite edge crossings from librt
partitions, place feature-preserving vertices with bounded QEF solves,
and refine locally for thin walls, multiple crossings, interior
surfaces, and ambiguous topology.  Build faces from shared crossings
with deterministic winding and require closed manifold output.

Back analyze_polygonize with the new implementation, expose MDC as a
facetize method, and remove the obsolete polygonizer code, licensing,
continuation sampler, and CM default.  Keep NMG available when polygon
preservation is more important and it can successfully process the
input.

Document the Ju, Schaefer, and Warren algorithmic basis and the
bottom-up Euler-characteristic escalation path.  Add coverage for thin
walls, tunnels, overlapping CSG, toroidal cases, winding, resource
limits, and the compatibility polygonalize entry point.
2026-09-18 16:14:16 +00:00
Clifford Yapp 37aee260c1 facetize: retire the legacy pipeline and harden region results
The compatibility implementation duplicated command behavior and
retained unreliable paths that had been superseded by the planned
worker pipeline.  Keeping both implementations made option handling and
failure behavior needlessly difficult to reason about.

Remove the old facetize sources and MGED registration so all command
modes use the maintained orchestrator.  Preserve implicitly selected
region roots in the output database and keep region retries scoped to
validated perturbation inputs.

Tighten source and result validation, reject empty preparation results,
and suppress expected import or preparation diagnostics only inside a
scoped log hook.  Keep routine details out of the terminal summary and
write complete detail logs when concise reporting is not possible.

Extend regression and validation coverage for implicit roots, tolerated
failures, and malformed or unsupported inputs.
2026-09-18 16:14:16 +00:00
Clifford Yapp 56d64428ae facetize: separate orchestration and isolate NMG evaluation
The command implementation mixed option handling, mode selection,
database setup, scheduling, and result publication.  NMG Boolean work
also ran in the parent process, allowing known NMG failures to terminate
the overall conversion.

Introduce an explicit planning and orchestration layer so each command
mode follows one visible execution path.  Replace positional worker
arguments with typed request and result messages that are validated
before they can alter the working database.

Move NMG Boolean evaluation into the supervised subprocess framework.
Transfer trees and BoTs through the existing transaction protocol,
report failures without killing the parent, and reject primitives that
do not provide the required tessellation callback.

Update facetize regression and worker-protocol coverage for the new
boundaries and failure semantics.
2026-09-18 16:14:16 +00:00
Clifford Yapp 0f6db7016d facetize: add supervised parallel primitive workers
Primitive tessellation previously paid process and database setup
costs per object and left the parent with limited visibility into work
that timed out or died.  Large models consequently spent substantial
time outside the tessellation algorithms themselves.

Add long-lived supervised workers and a bounded scheduler for
independent objects.  Divide CPUs between worker processes and each
worker internal parallelism, reuse opened databases, and retain cache
state across requests.

Make result publication transactional.  A WRITE_STARTED acknowledgment
closes the READY and PROCEED race, while the parent detects timeouts and
abnormal exits, restarts workers, and records only completed object
results.  Reuse temporary result databases without leaking state between
requests.

Add process and database-transfer helpers together with worker,
recovery, and validation tests.  Document the parallel job control and
its resource behavior.
2026-09-18 16:14:15 +00:00
Clifford Yapp cbffc1171b libbu: support resource-aware worker processes
Large subprocess pools need to divide machine resources without allowing
each worker internal parallelism to consume every CPU.  Add a
process-local limit to bu_avail_cpus() and bu_parallel(), with atomic
updates and coverage for applying and removing the limit.

Make BU_MEM_AVAIL count FreeBSD reclaimable inactive and cache queues
in addition to immediately free pages.  This prevents idle systems from
appearing memory-starved to resource-aware algorithms and adds native
sysctl checks for the reported values.

Rename the private process.h to process_private.h.  This prevents the
source header from shadowing the MSVC CRT header used by std::thread,
which otherwise leaves _beginthreadex undeclared.
2026-09-18 16:14:15 +00:00
149 changed files with 20797 additions and 11438 deletions
-246
View File
@@ -1,246 +0,0 @@
Annotation and view-object rendering notes
==========================================
Scope
-----
BRL-CAD has two related, but deliberately distinct, ways to represent visual
markup:
* An annotation is an ID_ANNOT database primitive. It is persistent model
content with enough semantic and presentation information to be reproduced
outside the application that created it.
* A view object is application-owned scene state. It is useful for interactive
construction and manipulation, but is intentionally not backed by a database
object.
They share portions of the interactive display infrastructure, but they do not
currently have interchangeable ownership, lifetime, or rendering semantics.
Annotation capabilities
-----------------------
An rt_annot_internal stores a model-space anchor, a set of 2D vertices, and
either a model plane basis or screen-space placement. Its segment types cover
lines, circular arcs, NURBS, Beziers, text, and filled polygons. Segment style
records include a semantic role, line pattern and width, RGBA color, font or
symbol, text scaling, and decorative flags.
The libged annotate command can create styled text, leaders, linear/angular/
radius/diameter/ordinate dimensions, and bounding-box autodim groups. It can
regenerate derived annotations from saved attributes and associate them with
source model objects without making them part of those objects' CSG trees.
Annotations therefore have the properties needed for persistent markup:
* database naming, paths, transforms, and instances;
* survival across save/reopen and transfer to another application;
* semantic segment roles and per-segment presentation data;
* model-space and view-dependent screen-space placement; and
* high-level generation metadata and source-object associations.
An annotation is informational coverage, not a CSG solid. The raytracing
implementation preserves that distinction. It prepares annotation coverage
from active database paths and composites it on primary samples after ordinary
geometry has established color and depth. Model-space annotation coverage is
occluded by nearer solid geometry. Screen-space annotation coverage uses the
render view and image dimensions. Annotations do not acquire artificial
volume, alter model bounds, cast shadows, or participate in reflection and
refraction rays.
Visible coverage is returned in back-to-front layers. Model-space layers are
ordered by ray distance, followed by view-space layers ordered by projected
anchor depth; segment draw order breaks ties within an annotation. This lets
the image renderers apply source-over alpha consistently while retaining the
single-frontmost-hit query for compatibility. NIRT continues to report one
frontmost segment per model-space annotation rather than exposing these
presentation layers as solid partitions.
Color follows normal database tree state. An annotation segment with an
explicit RGBA style wins; otherwise it inherits the resolved combination-path
color; otherwise it is white. The legacy region-ID color table is not applied
to annotation display or coverage, since annotations are informational leaves
rather than regions.
Prepared coverage uses the existing libbg RTree implementation. A 3D RTree
culls model-space annotation instances and a 2D local RTree culls segments
inside every annotation. Screen-space instance extents cannot be placed in a
static world-space index because they change with the view, so those instances
remain in a compact list while still using their local segment RTree. This
avoids a new spatial data structure and keeps prepared scenes immutable and
safe for concurrent sample queries.
The rt, ART, and ADRT/ISST integrations follow this overlay model. ERT invokes
rt with the database, displayed database paths, and view, so database-backed
annotations can follow that route as well. ERT does not currently serialize
or pass application-owned view objects to its subprocess.
NIRT keeps annotation diagnostics opt-in. `useannot 1` enables model-space
annotation intersections, and the `fmt a` action reports them. Default NIRT
output is unchanged. Screen-space annotations are deliberately omitted
because a NIRT ray does not define an image size, pixel footprint, or complete
model-to-view mapping.
View-object capabilities
------------------------
The core representation is bv_scene_obj. A view set owns shared view objects,
and a particular bview can also own local objects. Independent views force
local ownership. bv_obj_put recycles objects when their application lifetime
ends; the objects are not written to the .g database.
The `view obj` command exposes the principal markup types:
* Lines are model-coordinate polylines represented by vlists. Generic scene
rendering can add arrowheads to segment ends.
* Labels have a model anchor and optional model-space target/leader. Their text
is projected and drawn as screen-facing HUD text. If the command omits Z,
screen X/Y coordinates are projected onto the current view plane at creation
time.
* Polygons include general polygons, circles, ellipses, rectangles, and
squares. Creation and editing operate on a saved view plane with a view-Z
offset. Selection, movement, open/close, area, overlap, and 2D boolean
operations are supported. A "fill" is currently a set of hatch lines, not
an opaque surface. Polygons can be explicitly imported from or exported to
persistent sketch primitives.
Scene objects also provide object color, transparency, pixel line width, dash
style, and arrow dimensions. Not all of these properties have a uniform
command interface for every object type.
There are other visual layers that should not be conflated with these objects.
Legacy gv_tcl data/sdata arrows, axes, lines, labels, and polygons have a
separate representation. Faceplate/HUD elements are screen UI. Database
objects temporarily cloned for editing are also transient, but retain database
primitive semantics and live in the database-object scene containers.
Comparison
----------
Both annotations and view objects eventually produce display-manager pixels,
often through bv_scene_obj and vlists. That common final path does not make
their source representations equivalent.
Annotations are the canonical choice when markup must be saved, named in a
database, instanced, shared, raytraced from a .g file, or understood by another
program. View objects are the right choice for application/session state that
must be created and manipulated without changing the database.
Some view objects have a direct annotation analogue, but conversion is not
lossless without type-aware logic:
* A view polyline maps naturally to line segments, including arrowhead style.
* A polygon outline and its hatch lines can map to annotation coverage, but the
existing view "fill" must not silently become an opaque filled polygon.
* A label is hybrid. Its target and leader are model-space geometry while its
glyphs remain screen-facing at a projected model anchor.
* Arbitrary vlist extraction is insufficient because display-matrix commands,
point and line sizes, child objects, glyph behavior, and semantic roles are
otherwise lost or ambiguous.
View-object line width and annotation line width also need an explicit unit
policy. View-object widths are display pixels. Screen-space annotation widths
are view-dependent coverage widths. A model-space annotation's line_width is
used as a scale on the automatically selected model-space stroke width. In
other words, removing a separate raytracer "stroke-width" setting does not
remove width control: the annotation's existing line_width/`--line-width`
style remains the single user-facing control. The renderer interprets it in
the appropriate coordinate system rather than exposing a second competing
knob.
Recommended transient rendering design
--------------------------------------
Do not make librt or the raytracers depend on bv_scene_obj, libbv application
containers, or libged. Do not manufacture tubes, TGCs, meshes, or geometry
just in front of the view plane. Artificial geometry would affect bounds,
depth, shadows, and secondary rays, and would make antialiasing and perspective
behavior depend on a fabricated distance from the eye.
If transient view objects need to appear in raytraced output, use an explicit
application-to-renderer snapshot boundary:
1. The GED/application layer snapshots selected live view objects when a render
is launched. This is the only layer that owns their lifetime and semantics.
2. A type-aware adapter in libged or libbv translates supported objects into a
renderer-neutral overlay/coverage description. It should reuse annotation
coverage preparation through an in-memory annotation-instance interface,
rather than duplicate line, glyph, and arrow intersection algorithms.
3. The snapshot travels with the exact view state: model-to-view transform,
perspective, image width and height, and sampling convention. In-process
renderers can receive it directly; subprocess rt/ERT needs an explicit
serialized side channel.
4. Raytracers composite this coverage only on primary image samples, after
geometry color and depth are known. It remains outside CSG, model bounds,
shadows, reflections, and refractions.
5. Initial support should be deliberately finite: polyline strokes and
arrowheads, label glyphs and leaders, polygon outlines, and polygon hatches.
Faceplate elements and legacy gv_tcl overlays should be separately specified
opt-in layers rather than being included accidentally.
6. Snapshot records should preserve object name and type, RGBA, visibility,
local/shared ownership, presentation units, and source identity. Unsupported
object types should produce a deliberate diagnostic or documented omission.
Application-side compositing of the interactive display-manager layer over an
ERT framebuffer is a viable smaller feature if only ERT screen captures need
view objects. It would closely reproduce the interactive view, but it would
not make the objects available to standalone rt, ART, or ISST output and should
not be presented as the general renderer solution.
Conversion between the two systems should remain explicit. Automatically
turning view objects into database annotations would violate their deliberately
transient contract. A user command to promote selected view markup to a
persistent annotation would be reasonable; polygon-to-sketch export is an
existing precedent. Conversely, a view-object editor may be used as a front
end for constructing an annotation, provided the persistence action is clear.
Maintenance findings before implementing the adapter
----------------------------------------------------
Several representation issues should be corrected before type-driven snapshot
conversion is relied upon:
* Container-selection flags (BV_DB_OBJS, BV_VIEW_OBJS, BV_LOCAL_OBJS, and
BV_CHILD_OBJS) reuse numeric bits also used for semantic s_type_flags
(BV_DBOBJ_BASED, BV_VIEWONLY, BV_LINES, and others). bv_obj_create currently
assigns its type argument to s_type_flags. A local view object consequently
acquires BV_LINES incidentally, while the line command does not explicitly
tag lines. The namespaces should be separated and every object explicitly
typed.
* Lines, labels, and polygons should have unambiguous type tags and per-type
lifecycle/extraction callbacks. Snapshot conversion must inspect typed data,
not infer semantics from a rendered vlist.
* Label construction should initialize every bv_label field deliberately,
including size, line_flag, anchor, arrow, and target. Current construction
does not establish all of those values on every path.
* The status of legacy gv_tcl overlay data should be decided explicitly:
migrate it, support it through a separate adapter, or document its exclusion.
* Screen-space annotation display and raytrace units need a parity test. The
legacy display-matrix vlist path and the ray coverage path currently express
screen offsets through different mechanisms; their apparent agreement must
be verified rather than assumed.
* Per-segment annotation colors exceed the legacy interactive vlist object's
single-color model. Uniform annotate-command colors work today, but true
multicolor interactive parity may require split scene objects or a richer
display representation.
Tests for a future adapter should cover shared and local ownership, cleanup,
label anchors and leaders, polygon holes/hatches/view-Z, perspective, pixel
width, model-space occlusion, screen-space overlay, and serialization across
the ERT subprocess boundary.
Conclusion
----------
Persistent annotations and transient view objects should continue to coexist.
The annotation coverage scene is the correct renderer-side abstraction for
both saved annotations and a future snapshot of transient markup. The
application should perform the semantic conversion and transfer a view-complete
snapshot; the renderer should treat it as primary-ray coverage, never as
invented scene geometry.
+348
View File
@@ -0,0 +1,348 @@
# GQA sampling-method comparison
## Executive result
The new methods are a substantial practical improvement, but Crofton is not a
uniform numerical improvement over grids.
- **Crofton QMC is the best general-purpose stochastic method.** It is far
more accurate than ordinary random Crofton at the same ray count, avoids the
severe orientation bias seen in the legacy grid on thin or oblique models,
supports uncertainty intervals, and reached a 1% volume-interval target on
every bounded `share/db` case in the 10-second study.
- **Rotated grid is the strongest deterministic control.** It converges very
quickly on the analytic fixtures and usually agrees closely with Crofton on
complex-model volume, centroid, and inertia. It is not uniformly reliable:
the full Generic Twin surface-area result remains about 6.5% above the two
Crofton results.
- **Legacy axis-aligned GQA remains useful for compatibility, not truth.** It
can be excellent on aligned or symmetric geometry, but agreement among its
three axial views does not detect shared grid bias. Havoc volume is 18.2%
above the new-method consensus at the finest practical legacy grid tested.
- **Ordinary random Crofton is primarily a control.** Its observed error falls
close to the expected inverse-square-root rate, but QMC needs substantially
fewer rays for the same precision on these tests.
The recommended experimental default is therefore time-first QMC with an
explicit accuracy status, retaining rotated grid and random Crofton as
diagnostic controls. Legacy output should remain available as a compatibility
and comparison control. The experimental path now supplies conservative
bounds, traditional Plot3 files, bounded representative plots, and incremental
MGED visualization.
## Test protocol
- Analytic fixtures: a 1000 mm cube and radius-1000 mm sphere.
- Solid density: synthetic 1 g/cm^3. Consequently mass relative error equals
volume relative error. The air fixture uses 0.001 g/cm^3 for modeled air.
- Random and QMC convergence values are medians across ten fixed outer seeds.
- Comparable-effort analytic table: 76,800 legacy rays, about 60,000 rotated
rays, and 100,000 random/QMC rays.
- Complex-model comparison: the finest practical legacy grid, the deepest
completed rotated grid, and fixed high-ray Crofton runs. Those are
stabilization comparisons, not strictly equal-work comparisons.
- The four complex models are Havoc, the M35 cab, wrapped `Goliath.c`, and the
complete Generic Twin `all` hierarchy.
- Complex models have no analytic truth. “Consensus” below is the arithmetic
mean of the three new-method values and is only a comparison reference.
For uncertainty, the implementation uses independent randomized replications,
following the design guidance in A. B. Owen, [“Error estimation for quasi-Monte
Carlo” (2025), section 5](https://arxiv.org/abs/2501.00150v3). The pilot
forecast and its safety factor are engineering heuristics, not results
attributed to Owen. Only fresh production replications determine the reported
interval.
## Analytic measurement accuracy
All entries are errors; lower is better. Experimental inertia tensors were
translated from the model origin to the object centroid before comparison with
legacy output.
| Output | Fixture | Legacy axis grid | Rotated grid | Crofton random | Crofton QMC | Result |
|---|---|---:|---:|---:|---:|---|
| Volume | Cube | **0%** | 0.00392% | 0.18574% | 0.00674% | Legacy wins this specially aligned case |
| Volume | Sphere | 0.01170% | 0.00679% | 0.24152% | **0.00211%** | QMC improves on legacy by 82.0% |
| Mass | Cube | **0%** | 0.00392% | 0.18574% | 0.00674% | Same relative result as volume |
| Mass | Sphere | 0.01170% | 0.00679% | 0.24152% | **0.00211%** | Same relative result as volume |
| Surface area | Cube | unavailable | **0.00468%** | 0.12256% | 0.03220% | New capability; rotated wins |
| Surface area | Sphere | unavailable | 0.01267% | 0.39150% | **0.00200%** | New capability; QMC wins |
| Centroid | Cube | 3.6079 mm | **0.0102 mm** | 1.1650 mm | 0.1952 mm | Rotated improves on legacy by 99.7% |
| Centroid | Sphere | **approximately 0** | 0.0261 mm | 3.0817 mm | 0.1139 mm | Axial symmetry gives legacy exact cancellation |
| Inertia | Cube | 0.00276% | **0.00224%** | 0.32629% | 0.02884% | Rotated improves on legacy by 18.8% |
| Inertia | Sphere | 0.01534% | **0.01222%** | 0.42519% | 0.01539% | QMC is effectively tied; rotated wins |
Selected relative changes make the tradeoff clearer:
| Comparison at comparable effort | Change relative to legacy error |
|---|---:|
| Sphere volume, rotated | 41.9% lower |
| Sphere volume, QMC | 82.0% lower |
| Sphere volume, random | 1,964% higher |
| Cube centroid, rotated | 99.7% lower |
| Cube centroid, QMC | 94.6% lower |
| Cube inertia, QMC | 944% higher |
| Cube inertia, random | 11,713% higher |
| Sphere inertia, rotated | 20.4% lower |
The apparent contradictions are real sampling effects. An aligned grid can
integrate an aligned box volume exactly while placing its estimated centroid
at cell centers. Symmetric axial views can cancel sphere centroid error. That
does not make axis-aligned sampling uniformly more accurate on arbitrary
geometry.
## Convergence rates
The fitted relation is `error ~ rays^slope`; a more negative slope indicates
faster observed error reduction. Fits span the measured sampling ladders and
should not be extrapolated as guarantees.
| Method | Volume slope | Area slope | Centroid slope | Inertia slope |
|---|---:|---:|---:|---:|
| Legacy axis grid | -1.025 on sphere; cube exact | unavailable | -0.500 on cube; sphere cancels | -1.000 to -1.037 |
| Rotated grid | -0.650 to -0.918 | -0.673 to -0.681 | -0.934 to -1.118 | -0.863 to -0.898 |
| Crofton random | -0.460 to -0.462 | -0.480 to -0.487 | -0.487 to -0.531 | -0.480 to -0.546 |
| Crofton QMC | -0.576 to -0.696 | -0.573 to -0.753 | -0.786 to -0.844 | -0.757 to -0.834 |
Random Crofton behaves close to inverse square root. QMC improves every
measured random-Crofton slope. Grid convergence is faster on these smooth
fixtures, but its result can plateau around an orientation-specific bias.
The next table reports the first tested sampling rung meeting each threshold
on **both** analytic fixtures. A dash means the threshold was not reached by
the largest tested rung.
| Method | Volume 1% / 0.1% / 0.01% | Area 1% / 0.1% / 0.01% | Centroid 1 mm / 0.1 mm | Inertia 1% / 0.1% / 0.01% |
|---|---:|---:|---:|---:|
| Legacy axis grid | 1,200 / 19,200 / 307,200 | — | 4,915,200 / — | 1,200 / 19,200 / 307,200 |
| Rotated grid | 2,940 / 14,373 / 239,005 | 2,940 / 59,501 / 954,145 | 2,940 / 59,501 | 2,940 / 14,373 / 239,005 |
| Crofton random | 25,000 / 1,600,000 / — | 25,000 / 800,000 / — | 800,000 / — | 25,000 / 3,200,000 / — |
| Crofton QMC | 12,500 / 12,500 / 100,000 | 12,500 / 50,000 / 1,600,000 | 25,000 / 400,000 | 12,500 / 25,000 / 800,000 |
## Time-first and adaptive behavior on `share/db`
### Ten seconds or a 1% interval
The implemented pilot/forecast/production planner was run on all 138 bounded,
non-half-containing roots from the original corpus. The requested result was
a pointwise 95% volume-interval half-width no larger than 1%, with a 10-second
deadline and no ray cap.
| Outcome | Result |
|---|---:|
| Target met | **138 / 138** |
| Median rays | 300,000 |
| 90th-percentile rays | 1,863,584 |
| Maximum rays | 45,943,072 |
| Median time | 0.11 s |
| 90th-percentile time | 1.41 s |
| 95th-percentile time | 2.53 s |
| Maximum time | 8.40 s |
| Median relative half-width | 0.368% |
| 90th-percentile relative half-width | 0.652% |
| Maximum relative half-width | 0.896% |
This replaces the misleading earlier 100,000-total-ray result, where only
49/138 met 1% because the cap also paid for a discarded pilot and often left
only 25,000–75,000 production rays. Ray count varies by more than two orders
of magnitude across the corpus, while all wall times remained under the
declared deadline. That strongly supports time as the primary user-facing
resource control.
### Automatic dimensional stability with a 60-second default
The dimensional stability rule used a 0.00005 mm target, scaled downward for
very small objects, and required two stable doubling comparisons.
| Outcome | Result |
|---|---:|
| Stopped stable before deadline | 23 / 138 |
| Reached 60-second limit | 115 / 138 |
| Mean rays | 104.2 million |
| Within the independent adaptive result's 95% interval | 129 / 138 |
| Median difference from adaptive point estimate | 0.100% |
| 90th-percentile difference | 0.334% |
| 95th-percentile difference | 0.454% |
| Maximum difference | 1.204% |
The independent adaptive interval is not analytic truth, so the interval count
is an agreement check rather than a coverage claim. The practical conclusion
is still clear: the modeling-distance stability floor is much stricter than a
1% answer requirement for most corpus objects. It is valuable as an absolute
terminal criterion, but using it alone makes the default consume the full
minute in 83.3% of cases.
### Legacy comparison over the corpus
At the earlier 100,000-ray cap, 123 roots produced paired legacy and Crofton
volumes. Median relative difference was 0.544%, the 90th percentile was
3.573%, and 113/123 were within 5%. Fifteen legacy runs exceeded 120 seconds;
all had fast Crofton answers. Analytic checks on notable disagreements favor
the new estimator:
| Model | Analytic volume (mm^3) | Legacy error | Crofton error |
|---|---:|---:|---:|
| `pic.g/box.r` | 1.17368243 | -10.126% | -0.175% |
| `cube.g/light.r` | 6.08899047 | +1.940% | -0.0146% |
| `lgt-test.g/box.r` | 19,233.2255 | +2.496% | -0.175% |
## Large complex models
Volumes are in billions of mm^3. Legacy uses its finest practical tested grid;
Havoc, M35, and Goliath use rotated level 3 and 3.2 million Crofton rays. The
full Generic Twin uses rotated level 5 and 12.8 million Crofton rays.
| Model | Legacy | Rotated | Random | QMC | Legacy vs new-method consensus |
|---|---:|---:|---:|---:|---:|
| Havoc | 2.864720 | 2.420378 | 2.422244 | 2.426672 | **+18.226%** |
| M35 cab | 0.547483 | 0.537888 | 0.538267 | 0.537940 | +1.757% |
| Goliath | 0.708337 | 0.709737 | 0.709080 | 0.709610 | -0.161% |
| Generic Twin, full | 29.415300 | 29.725600 | 29.830135 | 29.801455 | -1.244% |
The legacy finest-grid centroids differ from the new-method centroid mean by
about 547 mm on Havoc, 11.1 mm on M35, 2.1 mm on Goliath, and 8.1 mm on the full
Generic Twin. Havoc is the clearest evidence of axis-aligned aliasing in a
real model.
Cross-method spread among the three new methods summarizes consistency for all
numeric outputs. Volume and mass have the same relative spread because of the
uniform synthetic density. Inertia spread uses tensor trace about the model
origin; centroid reports the maximum pairwise distance.
| Model | Volume/mass spread | Area spread | Centroid separation | Inertia-trace spread |
|---|---:|---:|---:|---:|
| Havoc | 0.260% | 0.336% | 12.31 mm | 0.203% |
| M35 cab | 0.070% | 0.113% | 1.73 mm | 0.107% |
| Goliath | 0.093% | 0.0097% | 0.68 mm | 0.204% |
| Generic Twin, full | 0.351% | **6.445%** | 23.75 mm | 0.344% |
The Generic Twin area disagreement is the main unresolved answer regression.
Random and QMC agree with each other to 0.108%, while rotated grid is 6.5%
higher. This points to orientation/boundary-count behavior in the rotated
area estimator rather than Crofton noise.
The declared 10-second, 1%-interval mode also succeeded on all four complex
models after the half-space correction:
| Model | Volume (10^9 mm^3) | Relative 95% half-width | Rays | Internal time | Difference from deep new-method consensus |
|---|---:|---:|---:|---:|---:|
| Havoc | 2.430949 | 0.600% | 3,119,616 | 1.691 s | +0.324% |
| M35 cab | 0.536708 | 0.352% | 1,360,416 | 0.407 s | -0.246% |
| Goliath | 0.710126 | 0.221% | 300,000 | 1.221 s | +0.092% |
| Generic Twin, full | 29.871938 | 0.463% | 3,746,112 | 1.526 s | +0.289% |
Goliath illustrates why time and rays are separate concerns: only 300,000 rays
were needed statistically, but its geometry makes each ray much more expensive
than in the other models.
## Modeled-air consistency
Expected volume is 2.0 billion mm^3 with air included and 1.0 billion mm^3
without it. Expected included mass is 1,001,000 g and excluded mass is
1,000,000 g.
| Output | Legacy | Rotated | Random | QMC |
|---|---:|---:|---:|---:|
| Included volume error | **0%** | +0.00339% | +0.39436% | +0.00286% |
| Excluded volume error | **0%** | -0.00289% | +0.02053% | +0.00081% |
| Included mass error | **0%** | -0.00288% | +0.02127% | +0.00081% |
| Included centroid error | 14.43 mm | **0.052 mm** | 1.01 mm | 0.184 mm |
| Excluded centroid error | 14.43 mm | **0.052 mm** | 1.01 mm | 0.184 mm |
All methods apply air inclusion consistently. The legacy exact totals again
coexist with a cell-center centroid bias.
## Diagnostic outputs
Diagnostics are sampled observations, not proofs that unobserved problems are
absent. “Largest” values are therefore directional extreme samples and
generally increase as more favorable rays are found.
| Check | Legacy | Rotated | Random | QMC | Assessment |
|---|---|---|---|---|---|
| Overlap | Found; max 8,000 mm | Found; 2,888.6 mm | Found; 9,714.2 mm | Found; 10,185.5 mm | Analytic directional maximum is 11,357.8 mm; QMC is closest |
| Gaps/internal voids | Found | Found | Found | Found | New output distinguishes `internal-void` and both region orders |
| Adjacent air | Legacy stand-alone run crashed without a plot prefix | Found at 0 mm | Found at 0 mm | Found at 0 mm | Existing legacy defect; experimental modes are clean |
| Exposed air | Found; max 9,000 mm | Found; 12,122.8 mm | Found; 12,765.4 mm | Found; 12,406.1 mm | Analytic box diagonal is 14,456 mm; random is closest at this budget |
Crofton has much better angular coverage than three fixed views for overlap and
exposed-air extremes. Rotating only one triad does not guarantee that it will
sample the direction of a narrow or maximal defect.
## Output and interface coverage
| Capability | Legacy | Rotated grid | Crofton random/QMC | Status |
|---|---|---|---|---|
| Volume | yes | yes | yes | Comparable |
| Mass/weight | yes | yes | yes | Comparable with density data |
| Centroid | yes | yes | yes | Comparable |
| Moments/products of inertia | yes, about centroid | yes, about model origin | yes, about model origin | Convention mismatch must remain explicit |
| Surface area | no | yes | yes | Experimental improvement |
| Overlaps | yes | yes | yes | Crofton negative result remains probabilistic |
| Gaps/internal voids | yes | yes | yes | Experimental labeling is clearer |
| Adjacent air | yes, but tested crash | yes | yes | Legacy defect exposed |
| Exposed air | yes | yes | yes | Better angular coverage experimentally |
| Modeled-air include/exclude | yes | yes | yes | Consistent |
| Per-object/per-region results | text | JSON/text | JSON/text | Experimental output is easier to consume |
| Bounding-box report | yes | yes | yes | Experimental bounds need no sampling rays and reject unbounded results |
| Plot3 diagnostic output | yes | all or representative | all or representative | Experimental modes also support incremental MGED display |
| Progress, replay, uncertainty | no | progress | progress, invalid-ray replay, intervals | Experimental improvement |
## Half-space behavior
Half-spaces must remain in ray tracing when they clip finite geometry; simply
removing them changes Boolean meaning. The corrected implementation derives
conservative sampling bounds from prepared region trees and ignores infinite
solids only when constructing the Crofton sampling sphere.
- A cube intersected by a half-space and the complementary subtraction each
report the expected 500,000,000 mm^3.
- The combined split reports the original 1,000,000,000 mm^3 in both grid and
Crofton tests.
- The complete Generic Twin, whose fuel regions use half-space clipping, now
runs successfully and meets the adaptive 1% target in 1.526 seconds.
- `cube.g/cloud.r.bak` is another bounded half-containing root and now runs.
- A standalone half-space, or a selected union containing an independently
unbounded region, is rejected with an explicit unbounded-region diagnostic.
- Unboundedness is now propagated through `SUBMODEL`. This prevents the prior
`primitives.g/other` infinite-hit crash while preserving bounded submodels
whose internal half-space is clipped by finite geometry.
Of the twelve original direct or indirect half-containing corpus roots, two
are bounded and now analyzable; ten are genuinely unbounded selections and are
rejected before sampling.
## Recommendations
1. **Use QMC for the experimental accuracy-oriented default.** Keep a time
ceiling as the primary resource control, report progress, and always expose
`target_met`, `target_not_met`, or `time_limit` with the achieved interval.
2. **Retain the geometry-scale stability rule as an absolute floor, not the
sole ordinary stopping target.** It consumed the full minute on 115/138
cases even though the independent comparison was already subpercent for
almost all of them.
3. **Keep rotated grid as a deterministic control.** It is excellent on the
analytic fixtures and gives users a non-statistical alternative, but it
should not replace QMC until the Generic Twin area discrepancy is resolved.
4. **Investigate rotated-grid surface-area accumulation next.** Test multiple
independent triads, internal-interface counting, clipped boundary-cell
weights, and convergence under orientation changes. The Crofton pair's
0.108% agreement gives a strong reference, though not analytic truth.
5. **Keep ordinary random Crofton for validation and debugging.** It is an
important unbiased control for QMC, but should not be the performance
default at the tested precision levels.
6. **Keep representative plotting as the interactive default.** The `all`
policy is valuable for exact legacy-style debugging but grows directly with
ray count. Continue using bounded spatial representatives for ordinary
live viewing, and clearly document inertia reference conversion when
presenting legacy and experimental tensors side by side.
7. **Expand interval calibration.** Repeat the 10-second corpus with multiple
independent outer seeds and analytic fixtures to measure empirical coverage,
especially for area, centroid, inertia, near-zero results, and
`--accuracy-scope all`.
8. **Retain half-space/submodel regressions.** Test finite intersection,
subtraction, bounded submodels, standalone unbounded regions, and nested
unbounded submodels. Do not filter half-spaces out of Boolean evaluation.
9. **Treat diagnostic negatives conservatively.** Report sampling evidence
and event-probability bounds, never “no overlap exists,” unless a separate
exhaustive geometric proof is implemented.
+1
View File
@@ -4,6 +4,7 @@ set(
api_reference.adoc
bool_eval_development.adoc
getting_started_as_contributor.adoc
gqa_analysis.adoc
iterate_geometry.adoc
libged_command_authoring.adoc
raytracing_pipeline.adoc
+244
View File
@@ -0,0 +1,244 @@
= GQA Analysis Design
The experimental `gqa --analyze` path separates geometric observations from
reporting. Ray callbacks accumulate region-attributed raw moments, boundary
crossings, and diagnostic events; the command layer applies units, object
rollups, and schema-versioned output. The engine is exposed through the
C-compatible `analyze_gqa` libanalyze API; libged supplies only its open
database, result string, and density-loader adapter. The reusable sampling
interval calculation is separately exposed as
`analyze_sampling_interval95`; paired ratio estimates use
`analyze_sampling_ratio_interval95`.
== Measurement conventions
Lengths use millimetres internally, mass uses grams, surface area counts region
partition boundaries, and inertia is about the model origin. Modeled air is
included by default and can be excluded explicitly. Grid boundary cells are
clipped to the model bounds. These choices are written to JSON so a consumer
does not have to infer them from command options.
The optional `bounds` measurement is not sampled. Conservative bounds are
combined from the prepared region Boolean trees rather than directly unioning
raw primitive boxes. This keeps finite intersections and subtractions
involving half-spaces usable while rejecting a result whose region-tree bound
is unbounded. As elsewhere in librt, subtraction does not shrink this
conservative box. The same bounds define the sampling domain and the
automatic plot scale.
== Grid sampling
The experimental grid implementation has two modes. The `grid` mode is an
axis-aligned three-view control. The `grid-rotated` mode constructs an
orthonormal direction triad from an azimuth and elevation, projects all eight
corners of the tight model bounding box into each perpendicular view plane,
and traces the resulting rectangular grids. Both modes clip boundary cells
to their projection intervals and weight each chord by its cell's actual
area. Rotated-cell covariance is included when accumulating mass moments;
otherwise an oblique cell would incorrectly be treated as having only
axis-aligned transverse extent.
The grid worker also records the clipped width and orientation of every
sampled cell. These are visualization metadata only: volume and moment
accumulation continues to use the same clipped area and covariance.
The rotated mode changes the two-dimensional grid phase between refinements.
The general shifted-lattice technique is described by R. Cranley and T. N. L.
Patterson, _Randomization of Number Theoretic Methods for Multiple
Integration_ (1976), https://doi.org/10.1137/0713071. GQA uses deterministic
irrational shifts for reproducibility, so they are an anti-aliasing measure,
not the randomized construction or an uncertainty estimate from that paper.
Grid rays execute in parallel using one librt resource and one GQA accumulator
per worker. Region metrics, problem observations, and material errors are
merged only after a batch completes. This preserves the existing serial
bookkeeping semantics without locking every partition callback. A refinement
is publishable only after all three views finish; rays from an incomplete pass
at the deadline are counted and reported as discarded rather than mixed into
the preceding complete estimate.
Grid convergence uses dimensional quantities. The change between successive
area and volume estimates is expressed as the corresponding sphere-radius
change, and the maximum-minus-minimum directional volume radius measures
view-dependent aliasing. Both must be below the requested distance for two
successive refinements. Stability across levels alone is insufficient:
axis-aligned geometry can produce a nearly unchanged average while one view
remains anomalous. When a half-spacing pass will not fit the remaining time,
the planner may choose an intermediate spacing that is still at least 1.05
times finer; it stops instead of starting a refinement too small to add useful
resolution.
== Crofton sampling
An oriented line needs four uniform coordinates: two for direction and two for
the perpendicular offset. The default QMC implementation uses a randomized
rank-one lattice when OpenQMC is available; ordinary pseudorandom sampling is
also available as a diagnostic cross-check. A ray offset continues one point
set. A seed and stream identifier select a separately randomized point set.
The distinction matters because consecutive points from one QMC sequence are
dependent and cannot be treated as independent error replicates.
The implementation follows Art Owen's _Error estimation for quasi-Monte Carlo_
(2025), especially section 5:
https://arxiv.org/abs/2501.00150v3. Owen recommends independent randomizations,
at least ten replications, and a Student-t interval across replicate estimates.
GQA uses 16 replications. Libanalyze uses the conservative 95 percent
Student-t critical value for nine degrees of freedom for any accepted replicate
count, avoiding intervals that are too narrow because a normal critical value
was used for a small replication count.
The reported intervals are approximate and pointwise. Selecting
`--accuracy-scope all` requires each selected model, unambiguous object, and
region metric to pass, but does not claim simultaneous 95 percent family-wise
coverage.
== Adaptive accuracy
Adaptive accuracy has two statistically separate stages:
. A pilot set of 16 randomized replications estimates the production budget.
The normal deadline-driven pilot uses 100000 rays. A root-n squared-error
scaling and an empirically calibrated safety factor are used only as planning
heuristics.
. The pilot estimates are discarded. Sixteen fresh streams produce the
published estimates, intervals, and pass/fail result.
Fresh validation prevents the stopping decision and the reported interval from
using the same favorable random fluctuation. QMC convergence need not follow a
stable power law, so the production interval is authoritative: an optimistic
pilot can reduce work, but it cannot assert success. This separation, the use
of independently randomized replications, and the Student-t interval follow
A. B. Owen, _Error estimation for quasi-Monte Carlo_ (2025), section 5:
https://arxiv.org/abs/2501.00150v3. The root-n forecast and safety factor are
engineering policy, not claims attributed to Owen.
When an adaptive target is combined with `--time` and no explicit `--rays`,
the pilot also measures throughput. The forecast is clamped to 80 percent of
the ray count expected to fit in the remaining wall time, reserving headroom
for variation and reporting. An explicit ray count remains an additional hard
cap. A deadline-constrained production estimate that misses its target is
reported with `time_limit`; it is not promoted to success.
The 16 streams execute serially through one reusable Crofton worker session.
Each stream's rays use the session's all-CPU worker pool. Worker-local events
are merged and delivered to libanalyze callbacks serially, preserving GQA's
per-region bookkeeping without launching competing all-CPU replications.
== Default resource and stability policy
Grid and Crofton measurement-only runs use elapsed time as their default
resource limit. Unless the caller supplies the sampler's explicit work limit
(`--refine` or `--rays`) or `--time`, the analysis deadline is 60000 ms. An
explicit ray count remains available as a Crofton hard cap for reproducibility
and specialized testing, but ray count is not the ordinary user-facing
resource control.
For non-uncertainty measurement runs without an explicit stability target,
the automatic equivalent-radius target is
`min(0.1 * BN_TOL_DIST, 0.001 * smallest_positive_tight_model_span)`.
The first term asks for stability an order of magnitude below librt's normal
0.0005 mm modeling tolerance. The scale-relative term begins tightening the
target for models below 0.05 mm, before the fixed tolerance becomes large
relative to the selected geometry. Finite region-tree bounds are used because
the prepared model RPP is deliberately rounded outward and raw half-space
bounds are infinite. This stopping rule is an
engineering convergence heuristic and is reported as `stable`; it does not
claim the pointwise coverage of the independent-replication intervals.
For Crofton sampling, only estimates needed by the selected measurements
participate in the test: area requests stabilize the surface-area radius,
while volume, mass, centroid, and moment requests stabilize the volume radius.
Stability is evaluated at 20,000 rays and then whenever the cumulative ray
count doubles. Two successive doubling intervals must meet the target.
Comparing fixed 2,000-ray increments would be misleading because each
increment becomes a vanishing fraction of the cumulative estimate and its
apparent change eventually tends to zero even when sampling error remains
material.
Grid sampling applies the selected area or volume-radius refinement change and
always checks directional volume-radius spread as an aliasing diagnostic.
The Crofton progress callback runs serially after completed parallel ray
batches. GQA throttles those callbacks to approximately one report per second
and includes elapsed time, total rays, throughput, the current volume estimate,
and observed versus requested stability. A callback never reads worker-local
state while rays are in flight.
== Plot publication and live views
Plotting is downstream of sampling. Grid workers append immutable sample
records to worker-local accumulators, and Crofton visitors append records
while librt is in its serialized delivery phase. Libanalyze publishes only
after a parallel batch completes. Consequently Plot3 output and live-view
output consume the same ordered line batches and cannot alter ray selection,
partition bookkeeping, or numerical estimates.
The `all` policy preserves every chord and diagnostic line, including rays
from successive complete grid refinements. It is useful for exact debugging
but its storage and display cost grows with sample count. The default
`representative` policy quantizes sample centers in model space and retains a
deterministic one-element min-hash reservoir for each category, direction
class, region pair, and occupied cell. The reservoir construction follows
the mergeable fixed-size sampling idea in J. S. Vitter, _Random Sampling with
a Reservoir_ (1985), https://doi.org/10.1145/3147.3165. Severity wins first
for diagnostic observations so the most significant issue in a cell remains
visible; deterministic priority resolves equal-severity samples.
Representative samples are rendered as finite-support glyphs. Grid glyphs
use the exact clipped pixel footprint. Crofton glyphs use the representative
side length corresponding to their average `pi R^2/N` transverse sampling
area, where `R` is the bounding-sphere radius and `N` is the ray count. The
spatial-cell scale independently limits how many glyphs are retained. This
follows the visual principle of surfels, which draw
sample support instead of infinitesimal points: H. Pfister et al., _Surfels:
Surface Elements as Rendering Primitives_ (2000),
https://doi.org/10.1145/344779.344936. Bounding the number of displayed
samples by occupied spatial cells applies the fixed-detail motivation of S.
Rusinkiewicz and M. Levoy, _QSplat: A Multiresolution Point Rendering System
for Large Meshes_ (2000), https://doi.org/10.1145/344779.344940, without
claiming to implement its hierarchy.
An incomplete grid refinement never replaces the last complete
representative snapshot. Adjacent-air samples use an interface-centered
footprint and a bounded directional tick; the full following air chord would
overstate the location of the transition.
The libanalyze callback contains plain immutable model-coordinate lines and
has no display-system dependency. MGED copies each batch, synchronously
marshals the update through its existing GUI-event bridge, rebuilds the named
`gqa::analysis` view object on the display-owning thread, and requests a
refresh. This is the same ownership rule used for other asynchronous GED
work: worker threads calculate data, while all view-list mutation remains on
the GUI thread.
Relative and absolute tolerances may be combined. For a scalar estimate
`theta`, the accepted half-width is the larger of
`relative_error * abs(theta)` and the applicable absolute tolerance. This
lets absolute tolerances govern quantities near zero. A centroid point
estimate is the ratio of the replicate-mean first moment to replicate-mean
mass. Its interval uses paired first-moment and mass residuals in a
first-order delta calculation, retaining their covariance; zero-mass
centroids remain unavailable.
== Diagnostic evidence
Overlap, gap, and modeled-air results are sampled observations, not proofs of
absence. Crofton overlap-only runs first find positive-volume intersections of
region-instance bounding boxes and divide rays evenly among those candidates.
Raw overlap events are clipped to their candidate box and filtered by the
requested depth tolerance. For an ordinary randomized sequence with zero
events, the report includes the exact one-sided 95 percent binomial upper bound
`1 - 0.05^(1/N)` on the per-ray event probability.
Invalid partitions are rejected deliberately and may be written to a separate
replay file. Publication uses temporary sibling files followed by rename so a
failed write does not leave a new partial report at the requested path.
== Reproducibility and schema
The seed, resolved sequence, total pilot and production ray counts, unused
budget, measurement conventions, and every interval are recorded in JSON
schema version 2. Each diagnostic observation retains its actual ray ID,
origin, and direction. Invalid-ray files can be replayed by `nirt --replay`.
+173
View File
@@ -37,6 +37,179 @@ For error and interface calculations, the grid is refined until an error is foun
When multiple calculations are performed, the most extensive path is taken. For example, if weight and overlap calculations are performed, then the grid will be refined until the refinement limit is reached, or an overlap is detected.
[[experimental_analysis]]
== EXPERIMENTAL ANALYSIS
The separate `--analyze` interface preserves the behavior of existing *gqa*
invocations. It reports a concise summary and can write all region results and
individual problem observations as structured JSON. The JSON includes
per-object rollups when every region belongs unambiguously to one requested
object. This interface requires an explicit object list, including when used
inside MGED.
[source]
----
gqa --analyze [analysis options] model.g object [object ...]
----
`--measure` accepts a comma-separated selection from
`volume,mass,area,centroid,moments,bounds`. With no measurement or check selection,
the default is `volume,area`. A check-only selection does not require density
data. Centroid and moments require mass. `--check` accepts
`overlaps,gaps,adjacent-air,exposed-air` and defaults to no checks.
The model totals and region measurements use mm, mm^3^, mm^2^, and grams.
Mass needs a density table, selected with `--density file` or found using the
normal gqa density search.
The `bounds` measure reports the finite, conservative region-tree bounding box
and its three face areas without firing sampling rays. It can be combined
with the sampled measures. Subtraction does not shrink the reported box. A
bounded intersection or subtraction involving a half-space is supported; a
selected region whose region-tree bound is unbounded is rejected.
`--sampler grid`, the experimental interface's default, is the axis-aligned
control corresponding to the traditional three-view method. `--sampler
grid-rotated` uses a non-axis-aligned orthonormal view triad. Its first
direction defaults to 35 degrees azimuth and 25 degrees elevation and may be
set with `--azimuth degrees` and
`--elevation degrees`; the other two directions are derived orthogonally.
Rotated refinements use deterministic plane-grid shifts so successive passes
do not repeatedly sample the same alignment. These shifts reduce aliasing but
do not make grid results statistical confidence estimates.
Both grid modes use `--spacing mm`. When it is omitted, the initial spacing
is the larger of 50 mm and one 128th of the tight model bounding-box diagonal.
This keeps the first pass tractable for exceptionally large models while
preserving the familiar 50 mm start for ordinary models. `--refine levels`
sets the maximum number of additional grid passes and disables the default
time control when used alone. Each normal refinement halves the spacing;
under a time limit, a final intermediate spacing may be selected so a complete
three-view pass fits the remaining budget. Boundary cells are clipped and
weighted by their actual projected area rather than counted as full cells.
`--sampler crofton` uses isotropic rays. For a measurement-only run with
either grid or Crofton sampling and no explicit resource control, sampling has
a 60000 ms budget and stops sooner when its automatic equivalent-radius
stability target is reached. The planner may stop before the exact deadline
when there is not enough time for another complete sampling checkpoint.
For Crofton sampling, `--rays count` replaces that time-first default with an
explicit hard ray cap; `--sequence random|qmc` chooses an explicit sequence.
If omitted, the standard librt sequence selection is used. A QMC request
requires a build with OpenQMC.
`--seed value` makes the selected randomized sequence reproducible.
`--stability mm` requests equivalent-radius convergence. For a grid, both the
change from the preceding refinement and the spread among its three
directional volume estimates must meet the target for two successive passes.
For Crofton sampling, two successive doubling checkpoints must meet the
target. `--time ms` sets an internal wall-clock budget. A time-controlled run
reports progress at approximately one-second intervals. Grid progress
includes the level, spacing, completed rays, throughput, and directional
volumes; Crofton progress includes the current volume and stability when
available.
The automatic stability target for either measurement sampler is the smaller
of `BN_TOL_DIST / 10` (0.00005 mm) and 0.1 percent of the smallest positive
tight model-bounding-box span. The scale-dependent term prevents the fixed
modeling tolerance from becoming coarse relative to a small valid object.
This is a convergence heuristic, not a confidence interval. It applies only
to estimates needed by the requested measurements and requires two stable
comparisons at doubling ray-count checkpoints for Crofton sampling. Grid
refinement applies the same two-interval policy to complete three-view passes
and also requires its directional spread to meet the target. The result
distinguishes `stable`, `time_limit`, and an explicitly requested ray or
refinement limit stopping condition.
`--tolerance mm` ignores overlap and gap depths at or below the threshold
(default 0). Crofton diagnostic checks use the observed ray partition paths
and therefore remain sampling evidence rather than exhaustive proofs. Gaps
between two partitions of the same region are labeled `internal-void` in the
summary.
When Crofton is used for an overlap-only request (that is, `--check overlaps`
with no `--measure`), the experimental path first indexes finite region-instance
bounding boxes and samples only their intersecting candidate boxes. The summary
and JSON record report the number of candidate boxes considered. This is a
broad-phase acceleration and does not establish that two solids actually
intersect; the targeted rays still provide the overlap evidence.
For targeted overlap checks, `--stability mm` compares successive equivalent-radius
estimates of each candidate box, using the same rays that test the geometry. The
known box volume and surface area provide calibration checks, and the sampled rays
must cover all three dominant direction classes. A candidate stops only when the
radius change and these calibration checks meet the requested target, subject to
the ray budget. JSON records the calibration errors and whether the target was
reached in `box_stability_reached` and `box_calibration_reached`.
For randomized sequences, a candidate with no observed overlap event also reports
a 95 percent upper bound on the per-ray event probability. This quantifies the
strength of a sampling-negative result; it is not an exhaustive proof of absence.
The measurements are
sampled estimates, and diagnostic checks can miss problems that no ray hits.
`--plot-prefix prefix` writes the selected observations as binary Plot3 files
named `prefixvolume.plot3`, `prefixgaps.plot3`, `prefixoverlaps.plot3`,
`prefixadj_air.plot3`, and `prefixexp_air.plot3`. Only files corresponding to
requested measurements or checks are created. `--plot-mode all` writes every
sampled chord, matching the legacy plotting intent and retaining all grid
refinement passes. The default, `--plot-mode representative`, keeps one
deterministic sample per occupied spatial cell, direction class, category, and
region pair. It draws the actual projected grid-cell footprint; a Crofton
glyph represents its average `pi R^2/N` transverse sampling area. This bounded-detail
mode preserves visible coverage as refinement proceeds instead of making the
plot grow in direct proportion to the ray count. `--plot-resolution mm`
overrides the automatic representative cell size of one 128th of the model
diagonal. Plot selection never changes the rays or numerical estimates.
Inside MGED, `--plot-view` displays the same output in the active view as
`gqa::analysis` and refreshes it incrementally at approximately one-second
intervals. Representative mode replaces the overlay with its latest bounded
snapshot; all mode appends new rays and can become expensive. Adjacent-air
snapshots mark the sampled interface with a footprint and a short directional
tick, avoiding an invisible zero-length line without implying that the whole
air chord is defective. `--plot-view` is unavailable to standalone *gqa*.
`--air include|exclude` controls whether modeled air contributes to
measurement totals; the default is `include`. Diagnostic air checks still
trace modeled air when it is excluded from measurements. Surface area counts
region partition boundaries, including internal interfaces, and moments of
inertia are reported about the model origin.
`--uncertainty` divides the Crofton ray budget among 16 independently
randomized replications and reports approximate pointwise 95 percent
Student-t intervals. It applies only to measurement-only Crofton runs.
`--relative-error fraction` requests adaptive accuracy and
`--absolute-error measure=value[,measure=value...]` sets absolute targets for
`volume` (mm^3^), `mass` (g), `area` (mm^2^), `centroid` (mm), or
`moments` (g mm^2^). When both forms apply, the larger tolerance is used.
`--accuracy-scope model|all` selects model totals only or model, object, and
region results. The `all` intervals remain pointwise rather than a
simultaneous family-wise confidence statement.
Adaptive accuracy uses a pilot stage to forecast a production ray count, then
discards the pilot estimates and evaluates the target with fresh independent
production streams. With `--time ms` and no explicit `--rays`, the deadline
is the resource limit: the pilot measures ray throughput and the production
forecast is restricted to fit the remaining time with scheduling headroom.
When both are specified, the ray count is also a hard cap. When neither is
specified, adaptive accuracy uses the default 60000 ms deadline. A bare
`--uncertainty` request without an accuracy target retains the 100000-ray
budget because it has no accuracy condition with which to plan a deadline.
The result explicitly reports `target_met`, `target_not_met`, `time_limit`, or
`insufficient_evidence`. `time_limit` means the deadline constrained a
production result that did not meet the requested interval width; the estimate
and its achieved interval are still reported. The pilot, forecast, production
count and timing, planning safety factor, deadline state, and actual interval
are recorded in JSON. The planning heuristic and safety factor do not alter
the published interval: only the fresh production replications determine it.
`--json file` writes a schema-version 2 JSON record with model and region
measurements and every observed problem, including full region paths and ray
information.
`--invalid-rays file` writes a separate replay-oriented JSON file for rejected
Crofton partitions. Each record includes the ray ID, origin, direction, hit
distances, thickness, region path, and rejection reason.
Without `--json`, the command prints only the summary. Reported surface area
counts region partition boundaries, including internal interfaces.
[[volume_and_weight_calculation]]
== VOLUME AND WEIGHT CALCULATION
+15 -53
View File
@@ -19,6 +19,20 @@ nirt [options] model.g [objects]...
*nirt* (Natalie's Interactive Ray-Tracer) traces rays through the specified _objects_ in the database _model.g_, using *librt*(3), and produces an ASCII report of what each ray encounters. Commands are read from standard input. By default the user interacts with *nirt*, repeatedly specifying ray origination points and directions and controlling the format and destination of the reports. Locations may be input and output in either model coordinates (_x_, _y_, _z_) or view (a.k.a. grid-plane) coordinates (_h_, _v_, _d_), and direction may be expressed either as a vector in model coordinates or as azimuth and elevation angles.
Replay invalid-ray records generated by analysis tools with `--replay` and
`--ray-id`. The selected record's origin and direction are restored at JSON
precision, and one shot is performed before *nirt* exits:
[source]
----
nirt --replay invalid_rays.json --ray-id 18432 model.g object
----
Replay files use schema version 1 or 2 and contain a `rays` array. Each record
has a unique nonnegative integer `ray_id`, a finite numeric `origin_mm`
three-vector, and a finite nonzero `direction` three-vector. Both options are
required. The `--replay=FILE` and `--ray-id=N` forms are also accepted.
When standard input is not a terminal (for example, when it is redirected from a file or a pipe), *nirt* defaults to silent mode, making it convenient for scripted ray queries.
[[options]]
@@ -132,10 +146,6 @@ Command to set the backout flag. With no option, prints the current value. When
Sets the _useair_ member of the __rt_i__ structure to the integer _n_. If _n_ is 0, then *nirt* ignores any air in the geometry. If this command is invoked with no arguments, *nirt* prints the current value of _useair_. Default is 0.
*useannot [* _n_*]*::
Enables (_n_=1) or disables (_n_=0) diagnostic annotation intersections. When enabled, *nirt* evaluates all model-space annotations below the active database paths and invokes the *a* output action for each crossing. Screen-space annotations are not evaluated because an individual NIRT ray does not supply a pixel size or model-to-view transform. If this command is invoked with no arguments, *nirt* prints the current value. Default is 0.
*overlap_claims [* _n_*]*::
Specifies how to handle multiple regions' claims to segments of a ray. If _n_ is 0 or "resolve", then all overlaps are resolved in favor of a single region and any other claimants are ignored. If _n_ is 2 or "rebuild_all", then all overlaps are rebuilt, so any overlapping regions along the ray create individual (geometrically intersecting) partitions. If _n_ is 3 or "retain", then all overlaps are retained. In this case, the resulting partitions are always geometrically disjoint, each one is owned by a single region according to the current overlap resolution strategy, but every claimant is recorded. If _n_ is 1 or "rebuild_fastgen", then *nirt* takes on _FASTGEN_ behavior, so overlaps of plate-mode primitives are rebuilt, but other overlaps are retained. This command is useful with the __claimant_count__, __claimant_list__, and __claimant_listn__ output items. Default is "resolve".
@@ -201,11 +211,7 @@ Quits *nirt*.
[[output_statements]]
== OUTPUT STATEMENTS
*nirt* allows the user a high degree of control, via the _fmt_ command, over what information gets printed out for each ray and in what format. There are eight types of output statements, each of which is executed under appropriate circumstances. The types and their use are:
*a*::
Annotation. Executed once for each model-space annotation crossing when *useannot* is enabled. Crossings are reported in ray-distance order after the ordinary solid shotline output. The default format is empty.
*nirt* allows the user a high degree of control, via the _fmt_ command, over what information gets printed out for each ray and in what format. There are six types of output statements, each of which is executed under appropriate circumstances. The types and their use are:
*r*::
@@ -286,41 +292,6 @@ azimuth of view (i.e., of ray direction).
elevation of view (i.e., of ray direction).
Annotation Information
*annot_path*::
full database path of the annotation primitive.
*annot_segment*::
zero-based annotation segment index.
*annot_role*::
semantic role of the annotation segment, such as `text`, `leader`, `dimension`, or `arrowhead`.
*annot_x*::
_x_ coordinate of the annotation hit point.
*annot_y*::
_y_ coordinate of the annotation hit point.
*annot_z*::
_z_ coordinate of the annotation hit point.
*annot_d*::
_d_ grid-plane coordinate of the annotation hit point.
*annot_visible*::
1 if no non-air solid surface is closer to the ray origin, otherwise 0. Occluded annotation crossings are still reported so they can be inspected during debugging.
Partition Information
@@ -564,15 +535,6 @@ nirt -A material_id -A los_density model.g all
Each region the ray passes through is reported together with its _material_id_ and _los_density_ attribute values via the *attributes* output item, which is useful for vulnerability and mass-properties analyses.
====
.Reporting annotation crossings for debugging
====
[source,bash]
----
nirt -s -e 'useannot 1;fmt a "%s %d (%g %g %g) visible=%d\n" annot_path annot_segment annot_x annot_y annot_z annot_visible;s' model.g component dimensions
----
This opt-in format reports every model-space annotation crossing and whether it is in front of the first solid surface. Ordinary NIRT output is unchanged unless *useannot* is enabled and an *a* format is supplied.
====
[[hints]]
== HINTS
+3 -2
View File
@@ -18,7 +18,8 @@ Link with *-lanalyze*.
The public interface is organized across the headers included by
_analyze.h_, including _analyze/diff.h_, _analyze/density.h_,
_analyze/grid.h_, _analyze/heal.h_, _analyze/info.h_, _analyze/pnts.h_,
_analyze/polygonize.h_, _analyze/nirt.h_, _analyze/voxelize.h_, and
_analyze/contour.h_, _analyze/polygonize.h_, _analyze/nirt.h_,
_analyze/voxelize.h_, and
_analyze/worker.h_.
== DESCRIPTION
@@ -32,7 +33,7 @@ to gather the information they need.
Facilities provided by the library include model differencing, material
density and mass property analysis, ray-based gridding and sampling,
geometry healing, model information queries, point-cloud generation and
processing, polygonization and voxelization of geometry, and the
processing, manifold dual contouring and voxelization of geometry, and the
interactive ray-tracing engine used by the *nirt*(1) command. A parallel
worker interface supports distributing analysis work across multiple
threads.
+2 -32
View File
@@ -25,7 +25,7 @@ where the "char" argument is replaced by a single character identifying the cate
[[nirt_format_categories]]
== Output Categories
NIRT offers eight categories of output reporting:
NIRT offers seven categories of output reporting:
[cols="3*"]
[%noheader]
@@ -33,9 +33,6 @@ NIRT offers eight categories of output reporting:
|*Category Name*
|*Char*
|*Description*
|*Annotation*
|*a*
|Output for each model-space annotation crossing when annotation reporting is enabled with *useannot 1*. The default annotation format is empty.
|*Ray*
|*r*
|Report of ray information. The formatting associated with r will print once per ray, regardless of whether a region is encountered.
@@ -65,7 +62,7 @@ NIRT offers eight categories of output reporting:
[[nirt_format_str]]
== Format Strings
NIRT's format specifications are a subset of those found in printf implementations. The d, i, f, e, E, g, G, and s types are supported, as well as width and precision specifiers.
NIRT's format specifications are a subset of those found in printf implementations. Specifically, the d, i, and f types are supported, as well as width and precision specifiers.
[[nirt_format_keys]]
@@ -103,33 +100,6 @@ Unlike the full C/C++ printf, NIRT accepts only pre-defined string keys that ide
Footnote and Miss categories have no particular keys unique to them, although they (like all categories) can use the Ray keys if desired. Partition, Overlap, and Gap, on the other hand, have keys that are unique to their contexts and valid only within those contexts:
.Annotation Variables
[cols="2*"]
[%noheader]
|===
|annot_path
|Full database path of the annotation primitive.
|annot_segment
|Zero-based annotation segment index.
|annot_role
|Semantic role of the annotation segment, such as `text`, `leader`, `dimension`, or `arrowhead`.
|annot_x
|x coordinate of the annotation hit point.
|annot_y
|y coordinate of the annotation hit point.
|annot_z
|z coordinate of the annotation hit point.
|annot_d
|d grid-plane coordinate of the annotation hit point.
|annot_visible
|1 if no non-air solid surface is closer to the ray origin, otherwise 0.
|===
Annotation reporting is diagnostic and opt-in. It returns all model-space
annotation crossings in ray-distance order, including occluded crossings.
Screen-space annotations are not reported because a NIRT shot does not define
the pixel dimensions and model-to-view transform needed to evaluate them.
.Partition Variables
[cols="2*"]
[%noheader]
+9 -8
View File
@@ -40,11 +40,12 @@ Coordinates and explicit sizes use the database's current modeling units.
Creation commands draw the new annotation immediately. Use *--no-draw* to
create it without changing the display. Names must not already exist.
Ray-traced annotations are zero-thickness informational coverage rather than
solid regions. Model-space annotations are depth-tested against ordinary
geometry. Screen-space annotations are generated for the current camera and
composited in the foreground; they do not participate in shadows, reflections,
line-of-sight thickness, or Boolean evaluation.
Model-space annotations also participate in raytracing. Librt expands their
strokes and filled areas into transient mesh geometry during preparation; the
generated geometry is not written to the database. It uses the normal region
material and color resolved from the containing combination tree. Screen-space
annotations remain display-only because primitive preparation has no render
view or image dimensions with which to place them.
The supported dimensions follow the general presentation conventions of ISO
129-1. Lettering defaults to OSIFont, an ISO 3098-style technical font shipped
@@ -208,9 +209,9 @@ Set the persistent annotation color using integer red, green, and blue values
from 0 through 255.
*--line-width width*::
Set the presentation line width. Display and screen-space ray rendering use
the value as a pixel width. Model-space ray rendering uses it to scale the
automatic stroke width derived from the annotation's text height or extent.
Set the display line width. For a model-space raytrace, this value scales the
physical stroke width inferred from the annotation's text height or overall
extent.
*--line-style style*::
Set semantic line-pattern metadata to *continuous*, *dashed*, *dotted*,
+24 -16
View File
@@ -10,7 +10,7 @@ facetize - Creates __new_object__ using __old_object__ as a guide, creating an e
[source]
----
facetize [-h] [-v] [-q] [-n] [-r] [-s suffix] [-p prefix] [--in-place] [--max-time #] [--max-pnts #] [--resume] [--methods m1,m2,...] [--method-opts METHOD opt1=val opt2=val...] [--no-empty] [--log-file filename] [--nmg-booleval] [--disable-fixup] [--perturb] [--no-perturb] [--perturb-sa-tol #] [--perturb-vol-tol #] [--tolerate-failures] [-B -t threshold] {old_object new_object}
facetize [-h] [-v] [-q] [-n] [-r] [-j jobs] [-s suffix] [-p prefix] [--in-place] [--max-time #] [--max-pnts #] [--resume] [--methods m1,m2,...] [--method-opts METHOD opt1=val opt2=val...] [--no-empty] [--log-file filename] [--nmg-booleval] [--disable-fixup] [--perturb] [--no-perturb] [--perturb-sa-tol #] [--perturb-vol-tol #] [--tolerate-failures] [-B -t threshold] {old_object new_object}
----
@@ -45,6 +45,8 @@ By default, the Manifold BoT path can make use of a coplanarity-avoidance pertur
When the perturbation step is active, per-region *facetize* validates the initial Boolean output against the original CSG geometry using a Crofton stochastic raytrace. The default tolerance thresholds that trigger a perturb retry are 10% for both surface area and volume; these can be adjusted with *--perturb-sa-tol* and *--perturb-vol-tol*. At the end of a per-region facetization run *facetize* prints a concise validation summary listing how many regions passed, triggered a perturb retry, or encountered one of the following noteworthy conditions:
_Unbounded halfspace (validation unavailable)_: Crofton surface-area and volume comparisons require a finite sampling domain. A standalone halfspace, or another CSG result without finite bounds, is therefore not sampled and is counted as having validation unavailable. A bounded object intersected with or subtracted by a halfspace can still be validated because its finite operand supplies the sampling bounds.
_Sub-mm / few-hit bypass (accepted with note)_: When the Crofton sampler finds only a very small number of ray-surface intersections (below an internal threshold of 50 crossings), the sampling noise alone would exceed 14% relative error, making any SA comparison meaningless. Geometry in this category is typically sub-millimetre in cross-section (e.g., a TGC with a radius under 0.2 mm) but the stochastic sampler has confirmed that some geometry is present. The BoT conversion is accepted and the region is counted separately in the summary. Users should verify that such extreme geometry reflects actual modeling intent rather than an accidental near-degenerate primitive.
_Zero-hit replacement_: When the Crofton sampler fires rays but records zero intersections with the CSG geometry while the corresponding BoT is non-empty, *facetize* concludes that the Boolean evaluation most likely produces empty geometry (for example, a subtractor that fully engulfs the base object). In this case the non-empty BoT is discarded and replaced with an empty BoT to match the expected raytrace behavior. If this replacement appears unexpected for a given region, the *lint* command can be used to inspect the result.
@@ -61,13 +63,21 @@ Different tolerance settings will create finer or coarser meshes. See the *tol*
Primitive tessellation is performed in a child process against a private working copy of the database in the BRL-CAD cache. The original database is not modified as individual primitive meshes are generated. Only after the conversion succeeds are the requested results transferred from the working database. This separation also permits an interrupted conversion to retain its intermediate state for use with *--resume*.
The parent command sends primitive names to a persistent tessellation process one at a time. The child reports a result for each name, so a timeout, conversion failure, or child-process crash that occurs before a database write can be associated directly with the affected primitive. The child is restarted when necessary and processing continues with the next name; the parent does not subdivide and retry the whole group to discover which object failed. Unless output is suppressed with *--quiet*, long tessellation batches also report their current object and completed-object count at approximately five-second intervals.
The parent command sends primitive names to a small pool of persistent tessellation processes. Each child reports a result for every name, so a timeout, conversion failure, or child-process crash can be associated directly with the affected primitive. A child is restarted when necessary and processing continues with the next name; the parent does not subdivide and retry the whole group to discover which object failed. Unless output is suppressed with *--quiet*, long tessellation batches also report their completed-object count and active-worker state at approximately five-second intervals.
Database updates use a separate write handshake. After preparing a mesh in memory, the child announces that it is ready to write and reports the approximate mesh size, then waits. The parent acknowledges that it is waiting for the write before the child may update the working database. The child explicitly reports when the update is complete. Consequently, the normal per-object tessellation limit, including *--max-time* and method-specific limits, applies to mesh preparation and does not interrupt an acknowledged database write.
By default, *facetize* uses at most two worker processes and only enables the second when both CPU and memory information indicate it is safe. It reserves the larger of one GiB or one quarter of total physical memory for the rest of the system. Starting another job also requires at least 512 MiB beyond that reserve, or the largest worker resident-memory measurement seen so far if that is greater. If the platform cannot report the required memory information, *facetize* falls back to one worker. The available CPU count is divided among worker processes and is also imposed as the thread limit inside each worker, preventing nested algorithms from independently using all CPUs. *--jobs* changes the maximum process count; it does not bypass the memory admission checks.
Per-region BoT conversion uses this worker policy for both independent CSG validation and initial Manifold Boolean evaluation. When more than one worker is available, persistent validation processes each open their own read-only handle to the original database and compute Crofton surface-area and volume references for different regions. Persistent Boolean processes then evaluate different region trees using independent read-only handles to one shared snapshot of the tessellated working database. That snapshot also serves as the rollback checkpoint, so increasing *--jobs* does not create additional full database copies. This process isolation avoids sharing mutable raytrace preparation or database state between concurrent evaluations. Each result remains associated with the requested region name. The parent commits completed BoTs through one writer and continues to perform hierarchy changes, validation decisions, and any perturb retries serially. A one-worker run retains the established in-process order and avoids the snapshot phase.
Validation and Boolean workers report their completed-region count and active-worker state approximately every five seconds. When *--max-time* is specified, it also bounds each parallel CSG reference or Boolean request; a timed-out process is terminated, and the failure is associated with that region. Failure or timeout of supplementary CSG validation does not cause unrelated regions to be retried.
Database updates use a separate write handshake. After preparing a mesh in memory, a child announces that it is ready to write and reports the approximate mesh size and its current resident memory, then waits. The parent grants write permission, the child acknowledges when it is starting the write, and the child explicitly reports when the update is complete. Consequently, the normal per-object tessellation limit, including *--max-time* and method-specific limits, applies to mesh preparation and does not interrupt an acknowledged write.
With multiple workers, primitive tessellation children write to small staging databases rather than modifying the shared working database. Region Boolean children read one immutable cache-local snapshot while completed results are committed to the working database. The snapshot doubles as the recovery checkpoint and is opened independently by each worker; it is not duplicated per worker. For very large files, snapshot preparation reports the amount copied and elapsed time at five-second intervals. A region result is first completed in memory and enters the write handshake only when it is ready to replace the prior staged result. After a child reports a completed staging write, the parent submits that result to a single persistent writer process. The writer announces that it is ready, waits for the parent's permission, acknowledges that it is starting the write, transfers the result through one persistent working-database handle, and reports completion. This permits tessellation and initial region Boolean evaluation to proceed in parallel without concurrent database allocation updates or repeated full directory scans, while allowing the parent to supervise the canonical database write itself. With one worker, the established direct path avoids the extra staging I/O and database snapshot.
Before the first primitive conversion, the parent measures write throughput on the same cache filesystem as the working database. It writes a temporary file in 1 MiB chunks until approximately one second has elapsed or 128 MiB has been written, then removes the file immediately. Successful mesh writes of at least 1 MiB subsequently refine this profile. For each database update, the parent allows ten times the projected write time, with a minimum of five seconds and a maximum of five minutes. This measured allowance protects legitimate long writes on slow storage while still detecting a genuinely stalled update.
If a child fails before write permission is granted, the working database is known to be unchanged and no rollback is needed. A failure, crash, or write-deadline expiration after permission is granted but before completion is ambiguous: the working database may contain a partial update. In that case *facetize* restores its cache-local checkpoint and replays only the primitive conversions already known to have succeeded. The original database remains untouched during this recovery.
If a child fails before acknowledging that its write has started, the working database is known to be unchanged and no rollback is needed. In parallel mode, a failure during a child write affects only that worker's disposable staging database, so the failed object is reported and the worker can be restarted without replaying unrelated objects. A serial child failure during a direct write, or a writer-process failure after acknowledging write start, is ambiguous because the working database may contain a partial update. In those cases *facetize* restores its cache-local checkpoint and replays only the conversions not already tied to a specific failure. The original database remains untouched during this recovery.
[[facetize_options]]
@@ -94,6 +104,10 @@ Instructs *facetize* to produce NMG solids rather than BoT solids.
Rather than converting all specified inputs to one top level object, walk specified trees and individually convert regions to facetized objects. This mode is useful when wanting to display models in shaded mode while preserving color and region ID properties. If no regions are present a normal conversion is performed instead.
*-j, --jobs #*::
Set the maximum number of facetize worker processes. A value of 1 forces serial processing. The default value of 0 selects a conservative automatic limit based on available CPUs and memory. Memory checks may delay or reduce parallel work even when a larger value is requested. In per-region BoT mode the same limit controls parallel CSG reference sampling and initial Manifold Boolean evaluation.
*-s, --suffix*::
User specified suffix to use avoiding naming conflicts (naming conflict avoidance is needed when creating objects in *-r* mode.)
@@ -119,7 +133,7 @@ Resume an incomplete facetization process (will utilize cached data from a previ
*--methods*::
Accepts a comma separated list of one or more facetization methods to use. The default is to try all algorithms available as needed, but if a user knows they want to try a specific methodology they can call it out using this option.
Accepts a comma separated list of one or more facetization methods to use. The default tries NMG tessellation first, Manifold Dual Contouring (MDC) second, and SPSR as a final fallback. Other listed methods may be selected explicitly if a user wants to try a specific methodology.
*--method-opts*::
@@ -231,21 +245,15 @@ mged>**facetize --tolerate-failures large_model large_model.partial.bot**
====
.Increase Decimation in CM Mode
.Refine a Manifold Dual Contouring conversion
[example]
====
mged>**facetize --methods CM --method-opts feature_size=0.06 d_feature_size=2.0 ellipse.r ell.bot**
This selects MDC directly and requests a finer cell size with a larger ray
budget than the default:
The default settings on this object in CM mode will produce a little over 8000 faces in the final bot, but the addition of the *d_feature_size* option with a >1.5 value has dramatically reduced the count to 104.
mged>**facetize --methods MDC --method-opts "MDC feature_size=0.1 max_rays=400000" ellipse.r ell.bot**
====
.Decrease Decimation in CM Mode
[example]
====
mged>**facetize --methods CM --method-opts feature_size=0.06 d_feature_size=0.01 ellipse.r ell.bot**
If the intent is preserve a more detailed mesh, reducing *decimation-feature-size* will keep more of the original triangles - in the above example, almost 250 thousand of the original 6.9 million faces were retained. NOTE: use lower values here cautiously, as extremely large meshes can be very difficult for many tools to deal with.
Smaller feature sizes increase mesh detail, ray count, memory use, and runtime.
====
+102
View File
@@ -38,6 +38,108 @@ region. Overlaps and other geometry errors invalidate reported volume and
mass properties.
[[experimental_analysis]]
== EXPERIMENTAL ANALYSIS
The separate `--analyze` interface preserves the behavior of existing *gqa*
invocations. It reports a concise summary and can write all region results and
individual problem observations as structured JSON. The JSON includes
per-object rollups when every region belongs unambiguously to one requested
object. This interface requires an explicit object list, including when used
inside MGED.
[source]
----
gqa --analyze [analysis options] object [object ...]
----
`--measure` accepts a comma-separated selection from
`volume,mass,area,centroid,moments,bounds`. With no measurement or check selection,
the default is `volume,area`. A check-only selection does not require density
data. Centroid and moments require mass. `--check` accepts
`overlaps,gaps,adjacent-air,exposed-air` and defaults to no checks.
The model totals and region measurements use mm, mm^3^, mm^2^, and grams.
Mass needs a density table, selected with `--density file` or found using the
normal gqa density search.
The `bounds` measure reports the finite, conservative region-tree bounding box
and its three face areas without firing rays. Subtraction does not shrink the
reported box. Bounded results involving half-spaces are supported; regions
whose region-tree bounds are unbounded are rejected.
`--sampler grid`, the experimental interface's default, is the axis-aligned
three-view control. `--sampler grid-rotated` uses a non-axis-aligned
orthonormal view triad and deterministically shifts the plane grids between
refinements to reduce alignment aliasing. Its first direction defaults to 35
degrees azimuth and 25 degrees elevation and may be changed with `--azimuth`
and `--elevation`.
Both grid modes use `--spacing mm`; when omitted, the initial spacing is the
larger of 50 mm and one 128th of the tight model diagonal. `--refine levels`
sets a maximum number of additional passes. `--sampler crofton` uses
isotropic rays. Measurement-only runs without an explicit resource control
use a 60000 ms budget and stop sooner when the automatic stability target is
reached. The planner may stop before the exact deadline when there is not
enough time for another complete sampling checkpoint. `--rays count`
supplies a Crofton hard ray cap;
`--sequence random|qmc` chooses an explicit sequence. A QMC request requires
a build with OpenQMC. `--seed value` makes randomized sampling reproducible.
`--stability mm` requests equivalent-radius convergence. A grid must meet the
target for both refinement change and three-view directional spread twice in
succession. `--time ms` sets an internal wall-clock budget.
The automatic target is the smaller of `BN_TOL_DIST / 10` (0.00005 mm)
and 0.1 percent of the smallest positive tight model span, allowing valid small
objects to use a proportionally smaller target. Only estimates needed by the
requested measurements participate in Crofton convergence, and two
comparisons at doubling ray-count checkpoints must be stable. Time-controlled
runs report progress at approximately one-second intervals. The summary and
JSON identify `stable`, `time_limit`, or an explicitly requested ray or
refinement limit as appropriate.
`--tolerance mm` ignores overlap and gap depths at or below the threshold
(default 0). Gaps between two
partitions of the same region are labeled `internal-void` in the summary.
The measurements are
sampled estimates, and diagnostic checks can miss problems that no ray hits.
`--plot-prefix prefix` writes selected results to the traditional
`volume.plot3`, `gaps.plot3`, `overlaps.plot3`, `adj_air.plot3`, and
`exp_air.plot3` suffixes. `--plot-mode all` records every sampled chord and
all grid refinements. The default `representative` mode keeps one
deterministic sample per occupied spatial cell, direction, category, and
region pair, and draws finite sample footprints so the plot remains visually
useful as refinement increases. `--plot-resolution mm` overrides the default
cell size of one 128th of the model diagonal. Plotting does not change the
sampling calculation.
In MGED, `--plot-view` incrementally refreshes a `gqa::analysis` overlay. The
representative overlay is replaced by each bounded snapshot; all mode appends
rays and can be expensive. Adjacent-air snapshots show an interface footprint
and a short directional tick. Standalone *gqa* does not provide a live view.
`--air include|exclude` controls whether modeled air contributes to
measurements; inclusion is the default. Surface area counts region partition
boundaries and inertia is about the model origin.
`--uncertainty` reports approximate pointwise 95 percent intervals from 16
independently randomized Crofton replications.
`--relative-error fraction` and
`--absolute-error measure=value[,measure=value...]` request adaptive accuracy;
`--accuracy-scope model|all` controls which reported measurements must meet
the target. A pilot estimates the required production budget, but only fresh
independent production streams determine the reported accuracy status.
With `--time ms` and no explicit `--rays`, measured throughput limits the
production plan to the remaining time. If both are specified, the ray count
is also a hard cap. With neither specified, the default deadline is 60000 ms.
Results distinguish `target_met`, `target_not_met`,
`time_limit`, and `insufficient_evidence`, and JSON records the pilot,
forecast, production timing, deadline state, and achieved interval.
`--json file` writes a versioned JSON record with model and region measurements
and every observed problem, including full region paths and ray information.
`--invalid-rays file` writes a separate replay-oriented JSON file for rejected
Crofton partitions. Each record includes the ray ID, origin, direction, hit
distances, thickness, region path, and rejection reason.
Without `--json`, the command prints only the summary. Reported surface area
counts region partition boundaries, including internal interfaces.
[[how_gqa_works]]
== HOW GQA WORKS
+13 -16
View File
@@ -679,23 +679,21 @@ Convert CSG geometry to a 3D voxel grid by:
fill fraction
* Level-of-detail parameter controls sub-voxel sampling
==== 7.5 Implicit Surface Polygonizer (Marching Cubes / Tetrahedral)
==== 7.5 Manifold Dual Contouring
An implicit surface polygonizer (Bloomenthal tetrahedral marching) for
converting any inside/outside function to a triangle mesh.
`contour.cpp` evaluates CSG geometry directly with `librt` and constructs
closed, oriented triangle meshes:
`polygonizer.c` (based on Jules Bloomenthal’s "`An Implicit Surface
Polygonizer,`" Graphics Gems IV, 1994) converts any implicit function
(inside/outside test) to a triangle mesh:
* Maintains a hash table of evaluated lattice corners and edges
* Expanding front: starts at a seed point and spreads outward, testing
adjacent cube faces
* Each transitive cube is optionally decomposed into 6 tetrahedra for
sub-cube accuracy
* Edge-surface crossing is found by bisection (`converge`) then the
vertex normal is estimated from finite differences
* Used in `libanalyze` for converting CSG regions to faceted form
* Recursively refines an implicit octree using cached axis-aligned rays along
node box edges and interior quarter- and center-lines
* Reuses complete ray partitions at the conforming finest grid and retains
evaluated boundary points with analytic surface normals
* Resolves ambiguous faces consistently and permits multiple vertices in a
cell to preserve local topology
* Places vertices with bounded quadratic-error minimization
* Validates the resulting indexed mesh before returning it
* Backs the legacy `analyze_polygonize` interface without retaining the old
seed-driven marching-tetrahedra implementation
==== 7.6 Geometry Differencing (raydiff)
@@ -1712,7 +1710,6 @@ import
runtime
* *MT19937*: Mersenne Twister PRNG
* *pbrt-v3* (derived): HLBVH construction algorithm
* *Bloomenthal polygonizer*: implicit surface polygonization
* *Screened Poisson Reconstruction* (Michael Kazhdan): surface
reconstruction from point clouds
* *Ball Pivoting Algorithm*: surface reconstruction
-1
View File
@@ -55,7 +55,6 @@ brlcad_manage_files(pbrt.txt ${DOC_DIR}/legal/embedded TRIGGER
brlcad_manage_files(picohash.txt ${DOC_DIR}/legal/embedded TRIGGER gist)
brlcad_manage_files(pixcii.txt ${DOC_DIR}/legal/embedded TRIGGER libicv)
brlcad_manage_files(point_in_polygon.txt ${DOC_DIR}/legal/embedded TRIGGER libbg)
brlcad_manage_files(polygonizer.txt ${DOC_DIR}/legal/embedded TRIGGER libanalyze)
brlcad_manage_files(predicates.txt ${DOC_DIR}/legal/embedded TRIGGER libbg)
brlcad_manage_files(profont.txt ${DOC_DIR}/legal/embedded TRIGGER libbu)
brlcad_manage_files(pstdint.txt ${DOC_DIR}/legal/embedded TRIGGER libbu)
-9
View File
@@ -1,9 +0,0 @@
https://www.bloomenthal.com/JBloom/code/polygonizer.c
Authored by Jules Bloomenthal, Xerox PARC.
Copyright (c) Xerox Corporation, 1991. All rights reserved.
Permission is granted to reproduce, use and distribute this code for
any and all purposes, provided that this notice appears in all copies.
file:/src/libanalyze/polygonizer.c
+5 -2
View File
@@ -31,17 +31,20 @@
#include "common.h"
#include "analyze/defines.h"
#include "analyze/contour.h"
#include "analyze/debug.h"
#include "analyze/diff.h"
#include "analyze/density.h"
#include "analyze/gqa.h"
#include "analyze/grid.h"
#include "analyze/heal.h"
#include "analyze/info.h"
#include "analyze/nirt.h"
#include "analyze/pnts.h"
#include "analyze/polygonize.h"
#include "analyze/nirt.h"
#include "analyze/worker.h"
#include "analyze/sampling.h"
#include "analyze/voxelize.h"
#include "analyze/worker.h"
#endif /* ANALYZE_H */
+3
View File
@@ -1,15 +1,18 @@
set(
analyze_headers
contour.h
debug.h
defines.h
density.h
diff.h
gqa.h
grid.h
heal.h
info.h
nirt.h
pnts.h
polygonize.h
sampling.h
voxelize.h
worker.h
)
+118
View File
@@ -0,0 +1,118 @@
/* C O N T O U R . H
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
/** @addtogroup libanalyze
* @{ */
/** @file analyze/contour.h
*
* Ray-driven manifold dual contouring of evaluated BRL-CAD geometry.
*/
#ifndef ANALYZE_CONTOUR_H
#define ANALYZE_CONTOUR_H
#include "common.h"
#include <stddef.h>
#include "vmath.h"
#include "analyze/defines.h"
__BEGIN_DECLS
#define ANALYZE_MDC_DEFAULT_MAX_RAYS 0
#define ANALYZE_MDC_DEFAULT_MINIMUM_FREE_MEM 150000000
#define ANALYZE_MDC_DEFAULT_MIN_DEPTH 2
#define ANALYZE_MDC_DEFAULT_MAX_DEPTH 10
#define ANALYZE_MDC_MAX_DEPTH 20
#define ANALYZE_MDC_DEFAULT_MAX_TIME 600
struct db_i;
/** Resource and accuracy controls for analyze_mdc(). */
struct analyze_mdc_params {
/** Requested maximum finest-cell edge length. Zero selects an
* implementation default based on the model bounds. */
fastf_t feature_size;
/** Optional user ceiling for the automatically derived ray budget.
* Zero applies only geometry, depth, time, and memory limits. */
size_t max_rays;
/** Abort before available memory drops below this many bytes. */
size_t minimum_free_mem;
/** Minimum and maximum power-of-two grid depths. */
int min_depth;
int max_depth;
/** Maximum wall-clock processing time in seconds. Zero is unlimited. */
int max_time;
/** Emit progress diagnostics when non-zero. */
int verbosity;
};
#define ANALYZE_MDC_PARAMS_DEFAULT { \
0.0, \
ANALYZE_MDC_DEFAULT_MAX_RAYS, \
ANALYZE_MDC_DEFAULT_MINIMUM_FREE_MEM, \
ANALYZE_MDC_DEFAULT_MIN_DEPTH, \
ANALYZE_MDC_DEFAULT_MAX_DEPTH, \
ANALYZE_MDC_DEFAULT_MAX_TIME, \
0 \
}
enum analyze_mdc_status {
ANALYZE_MDC_OK = 0,
ANALYZE_MDC_INVALID_INPUT = 1,
ANALYZE_MDC_PREP_FAILED = 2,
ANALYZE_MDC_NO_SURFACE = 3,
ANALYZE_MDC_RAY_LIMIT = 4,
ANALYZE_MDC_TIMEOUT = 5,
ANALYZE_MDC_MEMORY_LIMIT = 6,
ANALYZE_MDC_AMBIGUOUS = 7,
ANALYZE_MDC_NOT_MANIFOLD = 8
};
/**
* Evaluate @p object with librt and construct an indexed triangle mesh.
*
* On success, the caller owns @p faces and @p vertices and must release them
* with bu_free(). Failure leaves all outputs empty.
*/
ANALYZE_EXPORT extern enum analyze_mdc_status
analyze_mdc(int **faces, size_t *num_faces, point_t **vertices,
size_t *num_vertices, const char *object, struct db_i *dbip,
const struct analyze_mdc_params *params);
__END_DECLS
#endif /* ANALYZE_CONTOUR_H */
/** @} */
/*
* Local Variables:
* mode: C
* tab-width: 8
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+153
View File
@@ -0,0 +1,153 @@
/* G Q A . H
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
/** @addtogroup libanalyze */
/** @{ */
/** @file analyze/gqa.h */
#ifndef ANALYZE_GQA_H
#define ANALYZE_GQA_H
#include <stddef.h>
#include "common.h"
#include "analyze/defines.h"
__BEGIN_DECLS
struct analyze_densities;
struct bu_vls;
struct db_i;
/**
* Load the density table selected by @p filename, or the caller's normal
* default density source when it is NULL. On success, ownership of both
* returned allocations passes to analyze_gqa(); @p source must be allocated
* with bu_malloc() and @p densities must be releasable by
* analyze_densities_destroy().
*/
typedef int (*analyze_gqa_density_loader)(
struct analyze_densities **densities,
char **source,
const char *filename,
void *data);
/** Receive an informational progress line during a long analysis. */
typedef void (*analyze_gqa_progress_handler)(const char *message, void *data);
/** Categories used to separate quantitative-analysis plot output. */
enum analyze_gqa_plot_category {
ANALYZE_GQA_PLOT_VOLUME = 0,
ANALYZE_GQA_PLOT_GAP,
ANALYZE_GQA_PLOT_OVERLAP,
ANALYZE_GQA_PLOT_ADJACENT_AIR,
ANALYZE_GQA_PLOT_EXPOSED_AIR,
ANALYZE_GQA_PLOT_CATEGORY_COUNT
};
/** Whether a plot batch extends or replaces the preceding visualization. */
enum analyze_gqa_plot_action {
ANALYZE_GQA_PLOT_APPEND = 0,
ANALYZE_GQA_PLOT_REPLACE
};
/** One colored line in model coordinates. */
struct analyze_gqa_plot_line {
int category;
unsigned char color[3];
double start[3];
double end[3];
};
/**
* Immutable plot update. Storage is valid only for the duration of the
* callback. An empty replacement clears a previous visualization.
*/
struct analyze_gqa_plot_batch {
int action;
const struct analyze_gqa_plot_line *lines;
size_t line_count;
};
/** Receive a serialized plot update after a completed ray batch. */
typedef void (*analyze_gqa_plot_handler)(
const struct analyze_gqa_plot_batch *batch,
void *data);
/**
* Invocation-local services needed by the quantitative analysis engine.
*
* The database and result string remain caller-owned. A density loader is
* required only for analyses that request mass-dependent measurements. The
* progress and plot handlers are optional. Plot handlers run serially after
* completed ray batches and must not call back into the active analysis.
*/
struct analyze_gqa_context {
struct db_i *dbip;
struct bu_vls *result;
analyze_gqa_density_loader load_densities;
void *density_data;
analyze_gqa_progress_handler report_progress;
void *progress_data;
analyze_gqa_plot_handler report_plot;
void *plot_data;
};
/**
* Locate the model filename in a standalone @c gqa @c --analyze command line.
* This lets a caller open the database before invoking analyze_gqa() without
* duplicating the engine's option-arity rules.
*
* @return the zero-based argument index, or -1 for malformed input.
*/
ANALYZE_EXPORT extern int analyze_gqa_model_argument(
int argc,
const char *argv[]);
/**
* Run the versioned GQA analysis interface.
*
* @p argv uses the command form accepted by @c gqa @c --analyze; argv[0] is
* the command name and argv[1] is @c --analyze. The selected objects follow
* all options. Results and diagnostics replace context->result.
*
* The function does not retain any context or geometry pointers after return.
*
* @return ANALYZE_OK on success; ANALYZE_ERROR on invalid input or failure.
*/
ANALYZE_EXPORT extern int analyze_gqa(
const struct analyze_gqa_context *context,
int argc,
const char *argv[]);
__END_DECLS
#endif /* ANALYZE_GQA_H */
/** @} */
/*
* Local Variables:
* tab-width: 8
* mode: C
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+22 -2
View File
@@ -43,9 +43,29 @@ struct analyze_polygonize_params {
size_t minimum_free_mem;
int verbosity;
};
#define ANALYZE_POLYGONIZE_PARAMS_DEFAULT { 0, 0, 30, 150000000, 0 }
#define ANALYZE_POLYGONIZE_DEFAULT_MAX_CYCLE_TIME 30
#define ANALYZE_POLYGONIZE_DEFAULT_MINIMUM_FREE_MEM 150000000
#define ANALYZE_POLYGONIZE_PARAMS_DEFAULT { 0, 0, \
ANALYZE_POLYGONIZE_DEFAULT_MAX_CYCLE_TIME, \
ANALYZE_POLYGONIZE_DEFAULT_MINIMUM_FREE_MEM, 0 }
/**
* Legacy polygonization interface, implemented by analyze_mdc().
*
* @p size is the requested maximum cell edge length. @p p_s is retained for
* source and binary compatibility but is no longer used as a starting seed.
* The caller owns successful output arrays and must release them with
* bu_free(). Failure leaves all outputs empty.
*
* @return 0 on success
* @return -1 on invalid input or polygonization failure
* @return 2 when a time limit is reached
* @return 3 when the minimum-free-memory limit is reached
*/
ANALYZE_EXPORT extern int
analyze_polygonize(int **faces, int *num_faces, point_t **vertices, int *num_vertices, fastf_t size, point_t p_s, const char *obj, struct db_i *dbip, struct analyze_polygonize_params *p);
analyze_polygonize(int **faces, int *num_faces, point_t **vertices,
int *num_vertices, fastf_t size, point_t p_s, const char *obj,
struct db_i *dbip, struct analyze_polygonize_params *p);
__END_DECLS
+80
View File
@@ -0,0 +1,80 @@
/* S A M P L I N G . H
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*/
/** @addtogroup libanalyze */
/** @{ */
/** @file analyze/sampling.h */
#ifndef ANALYZE_SAMPLING_H
#define ANALYZE_SAMPLING_H
#include "common.h"
#include "analyze/defines.h"
__BEGIN_DECLS
/** Minimum independent replications accepted by the interval estimator. */
#define ANALYZE_SAMPLING_MIN_REPLICATES 10u
struct analyze_sampling_interval {
double estimate; /**< arithmetic mean of the replicate estimates */
double half_width; /**< approximate two-sided 95% half-width */
};
/**
* Estimate a pointwise two-sided 95% sampling interval from independent
* randomized replicate estimates. At least ANALYZE_SAMPLING_MIN_REPLICATES
* finite values are required. The implementation uses the conservative
* 95% Student-t critical value for nine degrees of freedom for every accepted
* replicate count.
*
* This API is suitable for ordinary Monte Carlo or independently randomized
* QMC replications. Consecutive points from one deterministic or randomized
* QMC sequence are not independent replications.
*
* @return ANALYZE_OK on success; ANALYZE_ERROR for invalid input.
*/
ANALYZE_EXPORT extern int analyze_sampling_interval95(
struct analyze_sampling_interval *interval,
const double *replicate_estimates,
size_t replicate_count);
/**
* Estimate a pointwise two-sided 95% interval for a ratio of replicate means.
* Numerator and denominator values must be paired independent replications.
* The point estimate is mean(numerator) / mean(denominator), and the standard
* error uses their paired first-order delta-method residuals. This retains
* numerator/denominator covariance and is appropriate for derived quantities
* such as a mass centroid.
*
* @return ANALYZE_OK on success; ANALYZE_ERROR for invalid input or a zero
* denominator mean.
*/
ANALYZE_EXPORT extern int analyze_sampling_ratio_interval95(
struct analyze_sampling_interval *interval,
const double *replicate_numerators,
const double *replicate_denominators,
size_t replicate_count);
__END_DECLS
#endif /* ANALYZE_SAMPLING_H */
/** @} */
/*
* Local Variables:
* tab-width: 8
* mode: C
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+127 -5
View File
@@ -67,6 +67,7 @@
#define BG_SPSR_H
#include "common.h"
#include <stdint.h>
#include "vmath.h"
#include "bg/defines.h"
@@ -109,7 +110,7 @@ struct bg_3d_spsr_opts {
#define BG_3D_SPSR_BOUNDARY_DIRICHLET 3
#define BG_3D_SPSR_DEFAULT_DEGREE 1 /* DEFAULT_FEM_DEGREE */
#define BG_3D_SPSR_DEFAULT_DEPTH 11
#define BG_3D_SPSR_DEFAULT_DEPTH 8
#define BG_3D_SPSR_DEFAULT_KERNELDEPTH 0
#define BG_3D_SPSR_DEFAULT_ITERATIONS 0
#define BG_3D_SPSR_DEFAULT_FULL_DEPTH 0
@@ -117,13 +118,13 @@ struct bg_3d_spsr_opts {
#define BG_3D_SPSR_DEFAULT_BASEVCYCLES 0
#define BG_3D_SPSR_DEFAULT_MAX_MEM 0
#define BG_3D_SPSR_DEFAULT_THREADS 0
#define BG_3D_SPSR_DEFAULT_SAMPLES_PER_NODE 1.1
#define BG_3D_SPSR_DEFAULT_SCALE 1.0
#define BG_3D_SPSR_DEFAULT_SAMPLES_PER_NODE 1.5
#define BG_3D_SPSR_DEFAULT_SCALE 1.1
#define BG_3D_SPSR_DEFAULT_WIDTH 0.0
#define BG_3D_SPSR_DEFAULT_CONFIDENCE 0.0
#define BG_3D_SPSR_DEFAULT_CONFIDENCE_BIAS 0.0
#define BG_3D_SPSR_DEFAULT_CGSOLVER_ACCURACY 1.0e-3
#define BG_3D_SPSR_DEFAULT_POINT_WEIGHT 8.0 /* DefaultPointWeightMultiplier * Degree */
#define BG_3D_SPSR_DEFAULT_POINT_WEIGHT 2.0
#define BG_3D_SPSR_DEFAULT_NONMANIFOLD 0
#define BG_3D_SPSR_DEFAULT_LINEARFIT 0
#define BG_3D_SPSR_DEFAULT_EXACT 1
@@ -167,10 +168,131 @@ struct bg_3d_spsr_opts {
* @return 0 if successful, else error
*
*/
BG_EXPORT int bg_3d_spsr(int **faces, int *num_faces, point_t **vertices, int *num_vertices,
DEPRECATED BG_EXPORT int bg_3d_spsr(int **faces, int *num_faces, point_t **vertices, int *num_vertices,
const point_t *input_points_3d, const vect_t *input_normals_3d,
int num_input_pnts, struct bg_3d_spsr_opts *opts);
/** Reasons the SPSR solver may request additional source samples. */
#define BG_3D_SPSR_REFINE_RESIDUAL 0x01u
#define BG_3D_SPSR_REFINE_NORMAL 0x02u
#define BG_3D_SPSR_REFINE_DENSITY 0x04u
#define BG_3D_SPSR_REFINE_SURFACE_VARIATION 0x08u
/** Adaptive SPSR termination states. */
#define BG_3D_SPSR_COMPLETE 0
#define BG_3D_SPSR_CALLBACK_STOP 1
#define BG_3D_SPSR_POINT_LIMIT 2
#define BG_3D_SPSR_TIME_LIMIT 3
#define BG_3D_SPSR_NO_NEW_SAMPLES 4
#define BG_3D_SPSR_SOLVER_ERROR 5
/**
* One oriented source sample. A positive @p thickness records the length of
* the solid ray partition that supplied the sample. Non-zero equal pair IDs
* identify the entry and exit samples of the same partition.
*/
struct bg_3d_spsr_sample {
point_t point;
vect_t normal;
fastf_t thickness;
uint64_t pair_id;
};
/** A spatial location at which the solver would benefit from more samples. */
struct bg_3d_spsr_refinement_hint {
point_t point;
vect_t normal;
fastf_t error;
unsigned int reasons;
};
/** Source-to-candidate measurements supplied by an adaptive callback. */
struct bg_3d_spsr_validation {
size_t ray_count;
size_t source_hit_rays;
size_t partition_mismatch_rays;
size_t thin_partition_count;
size_t thin_partition_mismatches;
fastf_t endpoint_error_p95;
fastf_t endpoint_error_p99;
fastf_t chord_error_fraction;
fastf_t surface_area_error_fraction;
fastf_t volume_error_fraction;
int passed;
};
/** Information passed to an adaptive source-sampling callback. */
struct bg_3d_spsr_refinement_request {
size_t pass;
fastf_t target_feature_size;
double remaining_time;
const point_t *vertices;
size_t vertex_count;
const int *faces;
size_t face_count;
const struct bg_3d_spsr_refinement_hint *hints;
size_t hint_count;
};
/**
* Callback response. Sample storage remains owned by the callback and need
* only remain valid until the callback returns; libbg copies it immediately.
*/
struct bg_3d_spsr_refinement_response {
const struct bg_3d_spsr_sample *samples;
size_t sample_count;
struct bg_3d_spsr_validation validation;
/** Non-zero asks the adaptive driver to stop after this candidate. */
int stop_refinement;
};
typedef int (*bg_3d_spsr_refinement_func_t)(
struct bg_3d_spsr_refinement_response *response,
const struct bg_3d_spsr_refinement_request *request,
void *client_data);
/** Options controlling a bounded adaptive SPSR reconstruction. */
struct bg_3d_spsr_adaptive_opts {
struct bg_3d_spsr_opts solver;
size_t max_refinement_passes;
size_t max_points;
fastf_t target_feature_size;
double max_time;
};
/** Last-resort solve budget; callbacks may stop earlier when progress stalls. */
#define BG_3D_SPSR_DEFAULT_REFINEMENT_PASSES 8
#define BG_3D_SPSR_ADAPTIVE_OPTS_DEFAULT { \
BG_3D_SPSR_OPTS_DEFAULT, \
BG_3D_SPSR_DEFAULT_REFINEMENT_PASSES, \
0, \
0.0, \
0.0 }
/** Summary of an adaptive SPSR attempt. */
struct bg_3d_spsr_report {
size_t solve_count;
size_t initial_sample_count;
size_t final_sample_count;
size_t requested_sample_count;
size_t accepted_sample_count;
double elapsed_time;
int termination;
struct bg_3d_spsr_validation validation;
};
/**
* Reconstruct a surface and, when requested, obtain targeted additional
* samples from @p refine. A callback is required when refinement passes are
* enabled. The final candidate must be marked as passed by the callback.
*/
BG_EXPORT int bg_3d_spsr_adaptive(int **faces, int *num_faces,
point_t **vertices, int *num_vertices,
const struct bg_3d_spsr_sample *input_samples, size_t sample_count,
const struct bg_3d_spsr_adaptive_opts *opts,
bg_3d_spsr_refinement_func_t refine, void *client_data,
struct bg_3d_spsr_report *report);
__END_DECLS
#endif /* BG_SPSR_H */
+14 -1
View File
@@ -103,10 +103,23 @@ BU_EXPORT extern void bu_nice_set(int newnice);
/**
* Return the maximum number of physical CPUs that are considered to
* be available to this process now.
* be available to this process now, subject to any process-local limit set by
* bu_avail_cpus_set().
*/
BU_EXPORT extern size_t bu_avail_cpus(void);
/**
* Limit the CPU count reported by bu_avail_cpus() and honored by subsequent
* bu_parallel() calls in this process. A value of zero removes the limit.
* Call this before starting worker threads; changing the limit does not resize
* work that is already running.
*
* This is useful when several independent processes share one machine and
* each process contains code that selects its own parallelism with
* bu_avail_cpus().
*/
BU_EXPORT extern void bu_avail_cpus_set(size_t ncpu);
/**
* Create parallel threads of execution.
+6
View File
@@ -82,6 +82,10 @@ typedef int (*ged_func_ptr)(struct ged *, int, const char *[]);
/* Callback related definitions */
typedef void (*ged_io_func_t)(void *, int);
typedef void (*ged_refresh_func_t)(void *);
typedef void (*ged_main_thread_callback_t)(void *);
/** Run callback synchronously on the display-owning thread. */
typedef void (*ged_run_on_main_thread_func_t)(void *,
ged_main_thread_callback_t, void *);
typedef void (*ged_create_vlist_solid_func_t)(void *, struct bv_scene_obj *);
typedef void (*ged_create_vlist_display_list_func_t)(void *, struct display_list *);
typedef void (*ged_destroy_vlist_func_t)(void *, unsigned int, int);
@@ -235,6 +239,8 @@ struct ged {
struct ged_callback_state *ged_cbs;
void (*ged_refresh_handler)(void *); /**< @brief function for handling refresh requests */
void *ged_refresh_clientdata; /**< @brief client data passed to refresh handler */
ged_run_on_main_thread_func_t ged_run_on_main_thread; /**< @brief run display-affine work on the owning thread */
void *ged_main_thread_clientdata; /**< @brief client data passed to the main-thread handler */
void (*ged_output_handler)(struct ged *, char *); /**< @brief function for handling output */
void (*ged_create_vlist_scene_obj_callback)(void *, struct bv_scene_obj *); /**< @brief function to call after creating a vlist to create display list for solid */
void (*ged_create_vlist_display_list_callback)(void *, struct display_list *); /**< @brief function to call after all vlist created that loops through creating display list for each solid */
+270 -11
View File
@@ -196,12 +196,12 @@ RT_EXPORT extern int rt_obj_prep_serialize(struct soltab *stp, const struct rt_d
/**
* Stopping-criteria parameters for rt_crofton_shoot() and rt_crofton_sample().
*
* All fields default to zero. Behaviour when all three are zero (or the
* pointer is NULL) is identical to the historical default: fire 2 000 rays
* and stop when two successive 1 %-threshold iterations agree.
* All stopping fields default to zero. Behaviour when all three are zero
* (or the pointer is NULL) is identical to the historical default: fire
* 2 000 rays and stop when two successive 1 %-threshold iterations agree.
*
* When one or more non-zero fields are provided, sampling continues until
* the FIRST criterion that is met:
* When one or more non-zero stopping fields are provided, sampling continues
* until the FIRST criterion that is met:
*
* n_rays > 0 Stop once this many rays have been fired in total.
*
@@ -211,30 +211,65 @@ RT_EXPORT extern int rt_obj_prep_serialize(struct soltab *stp, const struct rt_d
* iterations: r_sa = sqrt(SA / (4*pi)) for surface area
* and r_v = cbrt(3*V / (4*pi)) for volume. Sampling
* stops when both |Δr_sa| and |Δr_v| (for whichever
* outputs are requested) remain < stability_mm for
* outputs are requested) remain <= stability_mm for
* consecutive stable windows.
*
* time_ms > 0 Stop once this many wall-clock milliseconds have
* elapsed, returning the best estimate accumulated so far.
*
* When multiple fields are non-zero the first criterion to fire wins,
* giving callers fine control over the accuracy / speed trade-off.
* When multiple stopping fields are non-zero, the first criterion to fire
* wins, giving callers fine control over the accuracy / speed trade-off.
*
* @c stability_metrics selects which estimates must meet @c stability_mm.
* The default derives them from the requested output arguments; visitor APIs
* return both statistics and therefore consider both unless told otherwise.
*
* The optional progress callback runs serially after completed ray batches.
* It is informational and must not call back into the active Crofton
* session.
*/
typedef void (*rt_crofton_progress_fn)(size_t ray_count,
size_t crossing_count,
double surface_area,
double volume,
double stability_mm,
int stability_evaluated,
double elapsed_ms,
void *data);
/** Estimates considered by stability-based stopping. */
enum rt_crofton_stability_metric {
RT_CROFTON_STABILITY_DEFAULT = 0,
RT_CROFTON_STABILITY_SURFACE_AREA = 1,
RT_CROFTON_STABILITY_VOLUME = 2,
RT_CROFTON_STABILITY_ALL =
RT_CROFTON_STABILITY_SURFACE_AREA | RT_CROFTON_STABILITY_VOLUME
};
struct rt_crofton_params {
size_t n_rays; /**< max total rays; 0 = no limit via this criterion */
double stability_mm; /**< equivalent-radius stability target (mm); 0 = disabled */
double time_ms; /**< wall-clock time budget (ms); 0 = disabled */
unsigned int stability_metrics; /**< rt_crofton_stability_metric bitmask */
rt_crofton_progress_fn progress; /**< optional serial progress callback */
void *progress_data; /**< caller data for progress */
};
/** One solid partition observed by a Crofton ray. */
struct rt_crofton_segment {
point_t ray_origin; /**< sampled line origin */
vect_t ray_direction; /**< sampled unit line direction */
point_t in_point;
vect_t in_normal;
point_t out_point;
vect_t out_normal;
fastf_t in_distance; /**< distance from ray_origin to in_point */
fastf_t out_distance; /**< distance from ray_origin to out_point */
fastf_t thickness;
size_t ray_id;
const struct region *region; /**< owning region, valid while rtip lives */
const struct soltab *in_solid; /**< solid producing the entry hit */
const struct soltab *out_solid; /**< solid producing the exit hit */
};
/**
@@ -250,9 +285,83 @@ struct rt_crofton_result {
double volume;
};
/** Initialize an rt_crofton_result before first use. */
#define RT_CROFTON_RESULT_INIT { NULL, 0, 0, 0, 0.0, 0.0 }
/** Explicit sequence selection for experimental Crofton callers. */
enum rt_crofton_sequence {
RT_CROFTON_SEQUENCE_DEFAULT = 0,
RT_CROFTON_SEQUENCE_RANDOM,
RT_CROFTON_SEQUENCE_QMC
};
/** Criterion that ended a Crofton sampling call. */
enum rt_crofton_stop_reason {
RT_CROFTON_STOP_NONE = 0,
RT_CROFTON_STOP_RAYS,
RT_CROFTON_STOP_STABILITY,
RT_CROFTON_STOP_TIME
};
/** Estimates from a Crofton run that does not retain every segment. */
struct rt_crofton_stats {
size_t ray_count;
size_t crossing_count;
double surface_area;
double volume;
double stability_mm; /**< last observed equivalent-radius change */
int stability_evaluated; /**< non-zero when stability_mm was observed */
size_t invalid_partition_count; /**< rejected non-finite partitions */
enum rt_crofton_stop_reason stop_reason; /**< criterion that ended sampling */
};
/** Opaque reusable worker session for serial Crofton sampling calls. */
struct rt_crofton_session;
/** Called serially after each ray batch; the segment is borrowed. */
typedef void (*rt_crofton_segment_fn)(
const struct rt_crofton_segment *segment, void *data);
/** A complete sampled ray and its valid positive-length partitions. */
struct rt_crofton_ray {
size_t ray_id;
point_t origin;
vect_t direction;
const struct rt_crofton_segment *segments;
size_t segment_count;
};
/** Called serially after each ray batch; the ray and segments are borrowed. */
typedef void (*rt_crofton_ray_fn)(const struct rt_crofton_ray *ray, void *data);
/** A raw overlap event observed before boolean overlap resolution. */
struct rt_crofton_overlap {
size_t ray_id;
point_t origin;
vect_t direction;
point_t in_point;
point_t out_point;
fastf_t depth;
const struct region *first_region;
const struct region *second_region;
};
/** Called serially after each ray batch for each raw overlap event. */
typedef void (*rt_crofton_overlap_fn)(const struct rt_crofton_overlap *overlap,
void *data);
struct rt_crofton_invalid_ray {
size_t ray_id;
point_t origin;
vect_t direction;
fastf_t in_distance;
fastf_t out_distance;
fastf_t thickness;
const struct region *region;
const char *reason;
};
typedef void (*rt_crofton_invalid_fn)(const struct rt_crofton_invalid_ray *ray,
void *data);
/**
* Run the Cauchy-Crofton ray-sampling estimator on an already-prepared
@@ -284,6 +393,26 @@ struct rt_crofton_result {
* bound fit failure. A return
* value of 0 means no geometry was intersected by the sampler.
*
* @section crofton_sampler Sampling method
*
* When BRL-CAD is built with BRLCAD_ENABLE_QMC, Crofton sampling defaults to
* OpenQMC's randomized rank-one lattice. It maps four uniformly distributed
* coordinates directly to the two direction and two perpendicular-offset
* coordinates of an oriented line. Directions are isotropic, and the offset
* radius uses R*sqrt(u) so lines are uniform by area across the bounding disk,
* not concentrated toward its center. This is the same invariant line
* measure used by the independent-random-endpoint construction, so the
* Crofton area and volume formulas and stopping criteria do not change.
*
* The lattice improves finite-sample coverage but its samples are correlated,
* unlike ordinary Monte Carlo samples. OpenQMC shuffles indices and applies
* toroidal shifts to mitigate alignment artifacts; a fixed pattern schedule
* keeps runs reproducible. Set the LIBRT_CROFTON_USE_PRNG environment
* variable to a value accepted by bu_str_true() to force the original
* independent-PRNG generator. That path is useful for compatibility and for
* checking whether an unexpected result is specific to lattice correlation.
*
*
* @section crofton_near_tol Near-tolerance sliver geometry: CSG vs BoT divergence
*
* The Cauchy-Crofton formula is mathematically exact for any well-defined
@@ -379,6 +508,136 @@ RT_EXPORT extern int rt_crofton_collect(struct rt_crofton_result *result,
const fastf_t *bbox_min,
const fastf_t *bbox_max);
/** Whether the librt build supports the QMC Crofton sequence. */
RT_EXPORT extern int rt_crofton_qmc_available(void);
/** Resolve DEFAULT to the sequence selected by this process. */
RT_EXPORT extern enum rt_crofton_sequence rt_crofton_resolve_sequence(
enum rt_crofton_sequence sequence);
/**
* Visit region-attributed segments without retaining the full sample set.
* The callback runs serially between bounded ray batches. It receives every
* positive-length partition; a normal may be zero when the raytracer cannot
* calculate one. Borrowed pointers are valid only during the callback,
* except for the region pointer, which remains valid while @p rtip is alive.
*/
RT_EXPORT extern int rt_crofton_visit(struct rt_crofton_stats *stats,
struct rt_i *rtip,
const struct rt_crofton_params *params,
size_t ray_offset,
const fastf_t *bbox_min,
const fastf_t *bbox_max,
enum rt_crofton_sequence sequence,
rt_crofton_segment_fn visitor,
rt_crofton_invalid_fn invalid_visitor,
void *data);
/**
* Seeded form of rt_crofton_visit(). A zero seed selects the library default;
* distinct stream identifiers select reproducible randomized replications.
* Independent replications, rather than offsets within one point set, are
* required when estimating randomized-QMC sampling error.
*/
RT_EXPORT extern int rt_crofton_visit_seeded(
struct rt_crofton_stats *stats,
struct rt_i *rtip,
const struct rt_crofton_params *params,
size_t ray_offset,
const fastf_t *bbox_min,
const fastf_t *bbox_max,
enum rt_crofton_sequence sequence,
uint64_t seed,
uint64_t stream_id,
rt_crofton_segment_fn visitor,
rt_crofton_invalid_fn invalid_visitor,
void *data);
/**
* Create a reusable Crofton worker session for one prepared raytrace instance.
*
* A session retains its parallel worker pool and per-CPU raytrace resources
* across calls. Calls using a session must be serialized, and @p rtip must
* remain alive and unchanged until rt_crofton_session_destroy() returns.
* This facility avoids repeatedly starting an all-CPU worker pool when a
* caller evaluates several independent randomized replications.
*/
RT_EXPORT extern struct rt_crofton_session *rt_crofton_session_create(
struct rt_i *rtip);
/** Release a reusable Crofton worker session and its raytrace resources. */
RT_EXPORT extern void rt_crofton_session_destroy(
struct rt_crofton_session *session);
/**
* Session-backed form of rt_crofton_visit_seeded().
*
* The seed and stream semantics are identical to
* rt_crofton_visit_seeded(). Keeping replications on distinct stream
* identifiers is required for randomized-QMC error estimation; reusing the
* worker session does not combine or otherwise correlate their samples.
*/
RT_EXPORT extern int rt_crofton_session_visit_seeded(
struct rt_crofton_session *session,
struct rt_crofton_stats *stats,
const struct rt_crofton_params *params,
size_t ray_offset,
const fastf_t *bbox_min,
const fastf_t *bbox_max,
enum rt_crofton_sequence sequence,
uint64_t seed,
uint64_t stream_id,
rt_crofton_segment_fn visitor,
rt_crofton_invalid_fn invalid_visitor,
void *data);
/**
* Visit complete rays, including rays with no valid partitions. Segment
* pointers are borrowed and valid only during the callback. The ray record
* contains only positive-length partitions; rejected partitions are reported
* through @p invalid_visitor when supplied.
*/
RT_EXPORT extern int rt_crofton_visit_rays(struct rt_crofton_stats *stats,
struct rt_i *rtip,
const struct rt_crofton_params *params,
size_t ray_offset,
const fastf_t *bbox_min,
const fastf_t *bbox_max,
enum rt_crofton_sequence sequence,
rt_crofton_ray_fn ray_visitor,
rt_crofton_invalid_fn invalid_visitor,
void *data);
/** Extended ray visitor with raw overlap events. */
RT_EXPORT extern int rt_crofton_visit_rays_ex(struct rt_crofton_stats *stats,
struct rt_i *rtip,
const struct rt_crofton_params *params,
size_t ray_offset,
const fastf_t *bbox_min,
const fastf_t *bbox_max,
enum rt_crofton_sequence sequence,
rt_crofton_ray_fn ray_visitor,
rt_crofton_overlap_fn overlap_visitor,
rt_crofton_invalid_fn invalid_visitor,
void *data);
/** Seeded form of rt_crofton_visit_rays_ex(). */
RT_EXPORT extern int rt_crofton_visit_rays_seeded_ex(
struct rt_crofton_stats *stats,
struct rt_i *rtip,
const struct rt_crofton_params *params,
size_t ray_offset,
const fastf_t *bbox_min,
const fastf_t *bbox_max,
enum rt_crofton_sequence sequence,
uint64_t seed,
uint64_t stream_id,
rt_crofton_ray_fn ray_visitor,
rt_crofton_overlap_fn overlap_visitor,
rt_crofton_invalid_fn invalid_visitor,
void *data);
/** Release storage owned by a structured Crofton result. */
RT_EXPORT extern void rt_crofton_result_free(
struct rt_crofton_result *result);
@@ -387,8 +646,8 @@ RT_EXPORT extern void rt_crofton_result_free(
/**
* Cauchy-Crofton surface-area and/or volume estimator for a primitive.
*
* Creates a temporary in-memory raytrace of @p ip and fires random chord
* rays until the stopping criteria in @p params are satisfied (or all
* Creates a temporary in-memory raytrace of @p ip and samples Crofton lines
* until the stopping criteria in @p params are satisfied (or all
* criteria are zero / params is NULL, in which case the 2 000-ray default
* is used). Stores the estimated surface area in @p *area (if non-NULL)
* and the estimated volume in @p *vol (if non-NULL).
-85
View File
@@ -29,19 +29,10 @@
#include "bu/list.h"
#include "bu/vls.h"
#include "bn/tol.h"
#include "rt/geom.h"
#include "rt/defines.h"
__BEGIN_DECLS
struct db_i;
struct xray;
#define RT_ANNOT_SCREEN_DPI 96.0
#define RT_ANNOT_MILLIMETERS_PER_INCH 25.4
#define RT_ANNOT_SCREEN_PIXELS_PER_MM \
(RT_ANNOT_SCREEN_DPI / RT_ANNOT_MILLIMETERS_PER_INCH)
RT_EXPORT extern struct rt_annot_internal *rt_copy_annot(const struct rt_annot_internal *annot_ip);
/** Validate annotation topology, model-space placement, and optional segment
@@ -49,82 +40,6 @@ RT_EXPORT extern struct rt_annot_internal *rt_copy_annot(const struct rt_annot_i
RT_EXPORT extern int rt_annot_validate(const struct rt_annot_internal *annot_ip,
struct bu_vls *messages);
/** Opaque, immutable collection of prepared annotation instances. */
struct rt_annot_scene;
/** Camera state needed to evaluate view-space annotations. Pixel sample
* coordinates use a lower-left origin. */
struct rt_annot_view {
mat_t model2view;
size_t width;
size_t height;
fastf_t perspective;
};
/** A zero-thickness annotation coverage event. path is owned by the scene. */
struct rt_annot_hit {
const char *path;
size_t segment;
uint32_t role;
point_t point;
fastf_t distance;
unsigned char color[4];
int screen_space;
int visible;
};
/** Prepare annotations referenced by the supplied top-level database paths.
* Boolean operations are intentionally ignored: annotations are
* informational leaves, not solids. */
RT_EXPORT extern struct rt_annot_scene *rt_annot_scene_create(
struct db_i *dbip, int path_count, const char * const *paths,
const struct bg_tess_tol *ttol, const struct bn_tol *tol);
RT_EXPORT extern void rt_annot_scene_destroy(struct rt_annot_scene *scene);
RT_EXPORT extern size_t rt_annot_scene_count(const struct rt_annot_scene *scene);
/** Return model-space bounds for prepared annotations. Screen-space geometry
* contributes its model-space anchor because its extent is view-dependent. */
RT_EXPORT extern int rt_annot_scene_bounds(const struct rt_annot_scene *scene,
point_t minimum, point_t maximum);
/** Return the top visible coverage event for a primary sample. */
RT_EXPORT extern int rt_annot_scene_query(const struct rt_annot_scene *scene,
const struct rt_annot_view *view, const struct xray *ray,
fastf_t sample_x, fastf_t sample_y, fastf_t scene_distance,
struct rt_annot_hit *hit);
/** Return all visible primary-sample coverage events in back-to-front order.
* Model-space events precede screen-space events. When hits is NULL or
* capacity is zero, only the required result count is returned. */
RT_EXPORT extern size_t rt_annot_scene_query_layers(
const struct rt_annot_scene *scene, const struct rt_annot_view *view,
const struct xray *ray, fastf_t sample_x, fastf_t sample_y,
fastf_t scene_distance, struct rt_annot_hit *hits, size_t capacity);
/** Return all model-space coverage events, sorted by ray distance. When hits
* is NULL or capacity is zero, only the required result count is returned. */
RT_EXPORT extern size_t rt_annot_scene_query_model(const struct rt_annot_scene *scene,
const struct xray *ray, fastf_t scene_distance,
struct rt_annot_hit *hits, size_t capacity);
/** Alpha-composite a coverage result over a linear RGB sample. */
RT_EXPORT extern void rt_annot_hit_blend(fastf_t color[3],
const struct rt_annot_hit *hit);
/** Alpha-composite all visible primary-sample coverage over color. If
* front_hit is non-NULL, it receives the frontmost event. */
RT_EXPORT extern int rt_annot_scene_composite(const struct rt_annot_scene *scene,
const struct rt_annot_view *view, const struct xray *ray,
fastf_t sample_x, fastf_t sample_y, fastf_t scene_distance,
fastf_t color[3], struct rt_annot_hit *front_hit);
/** Generate one annotation segment in annotation-local XY coordinates. This
* is shared by plotting and the coverage preparer to keep curve and font
* expansion identical. */
RT_EXPORT extern int rt_annot_segment_vlist(struct bu_list *vlfree,
struct bu_list *vhead, const struct bg_tess_tol *ttol,
const struct rt_annot_internal *annot_ip, size_t segment);
__END_DECLS
/** @} */
+1
View File
@@ -258,6 +258,7 @@ function(BRLCAD_Summary)
ext_report("Netpbm" NETPBM_STATUS)
ext_report("OpenCV" OPENCV_STATUS)
ext_report("OpenNURBS" OPENNURBS_STATUS)
ext_report("OpenQMC" OPENQMC_STATUS REQUIRED_VARS "BRLCAD_ENABLE_QMC")
ext_report("OSMesa" OSMESA_STATUS)
ext_report("Portable Network Graphics" PNG_STATUS)
+4
View File
@@ -308,6 +308,10 @@ if(BRLCAD_ENABLE_OPENCL)
endif(NOT OPENCL_FOUND)
endif(BRLCAD_ENABLE_OPENCL)
# Enable quasi-Monte Carlo sampling where supported
option(BRLCAD_ENABLE_QMC "Enable quasi-Monte Carlo sampling" OFF)
mark_as_advanced(BRLCAD_ENABLE_QMC)
# Enable features requiring OpenVDB
option(BRLCAD_ENABLE_OPENVDB "Enable features requiring OpenVDB" OFF)
mark_as_advanced(BRLCAD_ENABLE_OPENVDB)
-1
View File
@@ -1,6 +1,5 @@
# default.nrt
# Description: default - Standard NIRT Reporting Format
fmt a ""
fmt r "Origin (x y z) = (%.8f %.8f %.8f) (h v d) = (%.4f %.4f %.4f)\nDirection (x y z) = (%.8f %.8f %.8f) (az el) = (%.8f %.8f)\n" x_orig y_orig z_orig h v d_orig x_dir y_dir z_dir a e
fmt h " Region Name Entry (x y z) LOS Obliq_in Attrib\n"
fmt p "%-20s (%9.4f %9.4f %9.4f) %8.4f %8.4f%s\n" reg_name x_in y_in z_in los obliq_in attributes
+262 -17
View File
@@ -68,6 +68,13 @@
* ("/tc6_reg.c/sub6.s#sub"), so the lookup missed and no variant was
* applied, leaving the phantom face. After the fix the plan is built
* from region roots so keys match and the phantom face is eliminated.
*
* TC7 - NMG Boolean isolation and output modes: verifies that an interrupted
* worker cannot commit output, then exercises BoT, NMG, region, and
* in-place output paths.
*
* TC8 - implicit solid roots survive the working-database close and remain
* valid members of the imported output hierarchy.
*/
#include "common.h"
@@ -78,7 +85,9 @@
#include <string>
#include "bu/app.h"
#include "bu/env.h"
#include "bu/file.h"
#include "bu/log.h"
#include "bu/malloc.h"
#include "bu/str.h"
#include "bu/vls.h"
@@ -142,6 +151,36 @@ write_comb2(struct rt_wdb *wdbp,
0, 0, 0, 0, 0, 0, 0);
}
static int
write_comb1(struct rt_wdb *wdbp, const char *comb_name, const char *member, int is_region)
{
struct bu_list head;
BU_LIST_INIT(&head);
mk_addmember(member, &head, NULL, WMOP_UNION);
return mk_comb(wdbp, comb_name, &head, is_region, NULL, NULL, NULL,
0, 0, 0, 0, 0, 0, 0);
}
static int
run_facetize_command(const char *gfile, int argc, const char **argv, int verbose)
{
struct ged *gedp = ged_open("db", gfile, 1);
if (!gedp) {
bu_log("[regress_facetize] ged_open(%s) failed\n", gfile);
return BRLCAD_ERROR;
}
int ret = ged_exec(gedp, argc, argv);
if (verbose || ret != BRLCAD_OK) {
const char *log = bu_vls_cstr(gedp->ged_result_str);
if (log && log[0])
bu_log("[facetize] %s\n", log);
}
ged_close(gedp);
return ret;
}
/**
* Open @a gfile with ged_open and run "facetize @a input @a output".
* Returns BRLCAD_OK on success, BRLCAD_ERROR otherwise.
@@ -149,23 +188,8 @@ write_comb2(struct rt_wdb *wdbp,
static int
run_facetize(const char *gfile, const char *input, const char *output, int verbose)
{
struct ged *gedp = ged_open("db", gfile, 1);
if (!gedp) {
bu_log("[regress_facetize] ged_open(%s) failed\n", gfile);
return BRLCAD_ERROR;
}
const char *av[4] = {"facetize", input, output, NULL};
int ret = ged_exec(gedp, 3, av);
if (verbose || ret != BRLCAD_OK) {
const char *log = bu_vls_cstr(gedp->ged_result_str);
if (log && log[0])
bu_log("[facetize] %s\n", log);
}
ged_close(gedp);
return ret;
return run_facetize_command(gfile, 3, av, verbose);
}
/**
@@ -212,6 +236,46 @@ bu_log("[regress_facetize] check_bot: '%s' type %d is not a BoT\n",
return ok;
}
static int
check_object_type(const char *gfile, const char *object_name, int expected_type)
{
struct db_i *dbip = db_open(gfile, DB_OPEN_READONLY);
if (!dbip || db_dirbuild(dbip) < 0) {
if (dbip)
db_close(dbip);
return BRLCAD_ERROR;
}
struct directory *dp = db_lookup(dbip, object_name, LOOKUP_QUIET);
if (!dp) {
bu_log("[regress_facetize] '%s' does not exist\n", object_name);
db_close(dbip);
return BRLCAD_ERROR;
}
struct rt_db_internal intern;
RT_DB_INTERNAL_INIT(&intern);
if (rt_db_get_internal(&intern, dp, dbip, NULL) < 0) {
db_close(dbip);
return BRLCAD_ERROR;
}
int ret = (intern.idb_minor_type == expected_type) ? BRLCAD_OK : BRLCAD_ERROR;
if (ret != BRLCAD_OK)
bu_log("[regress_facetize] '%s' type %d, expected %d\n",
object_name, intern.idb_minor_type, expected_type);
rt_db_free_internal(&intern);
db_close(dbip);
return ret;
}
static int
count_log_hook(void *data, void *UNUSED(message))
{
int *count = (int *)data;
(*count)++;
return 0;
}
/**
* Run rt_crofton_shoot on @a obj_name in @a gfile.
* Stores SA (mm2) and VOL (mm3) in *sa / *vol.
@@ -236,7 +300,9 @@ crofton_estimate(const char *gfile, const char *obj_name,
rt_prep_parallel(rtip, 1);
/* REGRESS_CROFTON_SAMPLES rays, REGRESS_CROFTON_PCT% convergence threshold */
struct rt_crofton_params p = { REGRESS_CROFTON_SAMPLES, REGRESS_CROFTON_STABLE_TARGET, 0.0};
struct rt_crofton_params p = {
REGRESS_CROFTON_SAMPLES, REGRESS_CROFTON_STABLE_TARGET, 0.0,
RT_CROFTON_STABILITY_DEFAULT, NULL, NULL};
int ret = rt_crofton_shoot(sa, vol, NULL, NULL, NULL, NULL, NULL,
rtip, &p, NULL, NULL);
rt_i_destroy(rtip);
@@ -1120,6 +1186,183 @@ tc6_havoc_wind9_pattern(const char *tmpdir, int verbose)
return BRLCAD_OK;
}
static int
tc7_nmg_modes(const char *tmpdir, int verbose)
{
bu_log("[regress_facetize] TC7: NMG Boolean and output modes...\n");
struct bu_vls gpath = BU_VLS_INIT_ZERO;
bu_vls_printf(&gpath, "%s/tc7_nmg.g", tmpdir);
const char *gfile = bu_vls_cstr(&gpath);
if (bu_file_exists(gfile, NULL))
bu_file_delete(gfile);
struct db_i *dbip = db_create(gfile, 5);
if (!dbip) {
bu_vls_free(&gpath);
return BRLCAD_ERROR;
}
struct rt_wdb *wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_DEFAULT);
point_t box_pts[8] = {
{0.0, 0.0, 0.0}, {10.0, 0.0, 0.0},
{10.0, 10.0, 0.0}, {0.0, 10.0, 0.0},
{0.0, 0.0, 10.0}, {10.0, 0.0, 10.0},
{10.0, 10.0, 10.0}, {0.0, 10.0, 10.0}
};
if (write_arb8(wdbp, "box.s", box_pts) < 0 ||
write_comb1(wdbp, "box.r", "box.s", 1) < 0 ||
write_comb1(wdbp, "box.c", "box.s", 0) < 0) {
wdb_close(wdbp);
bu_vls_free(&gpath);
return BRLCAD_ERROR;
}
wdb_close(wdbp);
const char *bot_av[] = {"facetize", "--nmg-booleval", "box.r", "box.bot", NULL};
const char *fault_av[] = {"facetize", "--nmg-booleval", "box.r", "fault.bot", NULL};
const char *nmg_av[] = {"facetize", "-n", "box.r", "box.nmg", NULL};
const char *regions_av[] = {"facetize", "-r", "--nmg-booleval", "box.r", "box.regions", NULL};
const char *fallback_av[] = {"facetize", "-r", "--nmg-booleval", "box.c", "box-fallback.bot", NULL};
const char *object_in_place_av[] = {"facetize", "--in-place", "--nmg-booleval", "box.c", NULL};
const char *in_place_av[] = {"facetize", "-r", "--in-place", "--nmg-booleval", "box.r", NULL};
int ret = BRLCAD_OK;
auto check_tc7 = [&ret](bool passed, const char *description) {
if (passed)
return;
bu_log("[regress_facetize] TC7: FAIL - %s\n", description);
ret = BRLCAD_ERROR;
};
struct bu_vls fault_path = BU_VLS_INIT_ZERO;
bu_vls_printf(&fault_path, "%s/tc7_nmg_worker.fault", tmpdir);
(void)bu_file_delete(bu_vls_cstr(&fault_path));
(void)bu_setenv("LIBGED_FACETIZE_TEST_FAULT",
"nmg-before-ready:fault.bot", 1);
(void)bu_setenv("LIBGED_FACETIZE_TEST_FAULT_FILE",
bu_vls_cstr(&fault_path), 1);
int fault_ret = run_facetize_command(gfile, 4, fault_av, verbose);
(void)bu_setenv("LIBGED_FACETIZE_TEST_FAULT", "", 1);
(void)bu_setenv("LIBGED_FACETIZE_TEST_FAULT_FILE", "", 1);
check_tc7(fault_ret == BRLCAD_ERROR,
"an interrupted NMG Boolean worker did not report failure");
check_tc7(bu_file_exists(bu_vls_cstr(&fault_path), NULL),
"NMG Boolean worker fault did not trigger");
check_tc7(check_object_type(gfile, "fault.bot", ID_BOT) != BRLCAD_OK,
"an interrupted NMG Boolean worker committed an output");
(void)bu_file_delete(bu_vls_cstr(&fault_path));
bu_vls_free(&fault_path);
int hook_calls = 0;
bu_log_add_hook(count_log_hook, &hook_calls);
int bot_ret = run_facetize_command(gfile, 4, bot_av, verbose);
int hook_calls_before_probe = hook_calls;
bu_log("facetize NMG hook restoration probe\n");
bu_log_delete_hook(count_log_hook, &hook_calls);
check_tc7(bot_ret == BRLCAD_OK, "NMG Boolean BoT command failed");
check_tc7(hook_calls == hook_calls_before_probe + 1,
"temporary log hook was not restored");
check_tc7(check_bot_exists(gfile, "box.bot") == BRLCAD_OK,
"NMG Boolean BoT output is missing");
int nmg_ret = run_facetize_command(gfile, 4, nmg_av, verbose);
check_tc7(nmg_ret == BRLCAD_OK, "NMG output command failed");
check_tc7(check_object_type(gfile, "box.nmg", ID_NMG) == BRLCAD_OK,
"NMG output has the wrong type");
int regions_ret = run_facetize_command(gfile, 5, regions_av, verbose);
check_tc7(regions_ret == BRLCAD_OK, "NMG Boolean region command failed");
check_tc7(check_object_type(gfile, "box.regions", ID_COMBINATION) == BRLCAD_OK,
"NMG Boolean region output is missing");
int fallback_ret = run_facetize_command(gfile, 5, fallback_av, verbose);
check_tc7(fallback_ret == BRLCAD_OK,
"region mode object fallback command failed");
check_tc7(check_bot_exists(gfile, "box-fallback.bot") == BRLCAD_OK,
"region mode object fallback output is missing");
int object_in_place_ret = run_facetize_command(gfile, 4,
object_in_place_av, verbose);
check_tc7(object_in_place_ret == BRLCAD_OK,
"in-place NMG Boolean object command failed");
check_tc7(check_object_type(gfile, "box.c", ID_BOT) == BRLCAD_OK,
"in-place NMG Boolean object has the wrong type");
int in_place_ret = run_facetize_command(gfile, 5, in_place_av, verbose);
check_tc7(in_place_ret == BRLCAD_OK,
"in-place NMG Boolean region command failed");
check_tc7(check_object_type(gfile, "box.r", ID_COMBINATION) == BRLCAD_OK,
"in-place region root has the wrong type");
check_tc7(check_bot_exists(gfile, "box.r.bot") == BRLCAD_OK,
"in-place region BoT output is missing");
bu_log("[regress_facetize] TC7: %s\n", ret == BRLCAD_OK ? "PASS" : "FAIL");
bu_vls_free(&gpath);
return ret;
}
/* ------------------------------------------------------------------ */
/* TC8: implicit root replacement persists across database reopen */
/* ------------------------------------------------------------------ */
static int
tc8_implicit_root_persistence(const char *tmpdir, int verbose)
{
bu_log("[regress_facetize] TC8: implicit root persistence...\n");
struct bu_vls gpath = BU_VLS_INIT_ZERO;
bu_vls_printf(&gpath, "%s/tc8_implicit_root.g", tmpdir);
const char *gfile = bu_vls_cstr(&gpath);
if (bu_file_exists(gfile, NULL))
bu_file_delete(gfile);
struct db_i *dbip = db_create(gfile, 5);
if (!dbip) {
bu_vls_free(&gpath);
return BRLCAD_ERROR;
}
struct rt_wdb *wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_DEFAULT);
point_t region_points[8] = {
{0.0, 0.0, 0.0}, {10.0, 0.0, 0.0},
{10.0, 10.0, 0.0}, {0.0, 10.0, 0.0},
{0.0, 0.0, 10.0}, {10.0, 0.0, 10.0},
{10.0, 10.0, 10.0}, {0.0, 10.0, 10.0}
};
point_t loose_points[8] = {
{20.0, 0.0, 0.0}, {30.0, 0.0, 0.0},
{30.0, 10.0, 0.0}, {20.0, 10.0, 0.0},
{20.0, 0.0, 10.0}, {30.0, 0.0, 10.0},
{30.0, 10.0, 10.0}, {20.0, 10.0, 10.0}
};
if (!wdbp || write_arb8(wdbp, "region.s", region_points) < 0 ||
write_arb8(wdbp, "loose.s", loose_points) < 0 ||
write_comb1(wdbp, "box.r", "region.s", 1) < 0 ||
write_comb2(wdbp, "assembly.c", "box.r", 'u',
"loose.s", 'u') < 0) {
if (wdbp)
wdb_close(wdbp);
bu_vls_free(&gpath);
return BRLCAD_ERROR;
}
wdb_close(wdbp);
const char *facetize_args[] = {
"facetize", "-r", "assembly.c", "assembly.regions", NULL
};
int ret = run_facetize_command(gfile, 4, facetize_args, verbose);
if (ret == BRLCAD_OK &&
(check_object_type(gfile, "assembly.regions", ID_COMBINATION) != BRLCAD_OK ||
check_object_type(gfile, "facetize_assembly.c", ID_COMBINATION) != BRLCAD_OK ||
check_bot_exists(gfile, "box.r.bot") != BRLCAD_OK ||
check_bot_exists(gfile, "facetize_loose.s") != BRLCAD_OK))
ret = BRLCAD_ERROR;
bu_log("[regress_facetize] TC8: %s\n",
ret == BRLCAD_OK ? "PASS" : "FAIL");
bu_vls_free(&gpath);
return ret;
}
/* ------------------------------------------------------------------ */
/* main */
/* ------------------------------------------------------------------ */
@@ -1151,6 +1394,8 @@ return 1;
if (tc4_havoc_reuse(tmpdir, verbose) != BRLCAD_OK) ret = 1;
if (tc5_near_coplanar_subTOL(tmpdir, verbose) != BRLCAD_OK) ret = 1;
if (tc6_havoc_wind9_pattern(tmpdir, verbose) != BRLCAD_OK) ret = 1;
if (tc7_nmg_modes(tmpdir, verbose) != BRLCAD_OK) ret = 1;
if (tc8_implicit_root_persistence(tmpdir, verbose) != BRLCAD_OK) ret = 1;
if (ret == 0)
bu_log("[regress_facetize] All tests PASSED\n");
+52 -3
View File
@@ -4,10 +4,10 @@ if(TARGET gqa AND TARGET bview_plot3 AND TARGET plot3stat)
brlcad_regression_test(regress-gqa_plot "bview_plot3;gqa;plot3stat" TEST_SCRIPT "${TSCRIPT}" EXEC gqa VEXEC bview_plot3)
endif(TARGET gqa AND TARGET bview_plot3 AND TARGET plot3stat)
if(SH_EXEC AND TARGET mged AND TARGET gqa)
if(SH_EXEC AND TARGET mged AND TARGET gqa AND TARGET nirt)
brlcad_add_test(NAME regress-gqa COMMAND ${SH_EXEC} "${CMAKE_CURRENT_SOURCE_DIR}/gqa.sh" ${CMAKE_SOURCE_DIR})
brlcad_regression_test(regress-gqa "gqa;mged" TEST_DEFINED)
endif(SH_EXEC AND TARGET mged AND TARGET gqa)
brlcad_regression_test(regress-gqa "gqa;mged;nirt" TEST_DEFINED)
endif(SH_EXEC AND TARGET mged AND TARGET gqa AND TARGET nirt)
# list of temporary files
set(
@@ -18,6 +18,53 @@ set(
gqa.g
gqa.gaps.plot3
gqa.log
gqa.matrix.g
gqa.matrix.mged
gqa.modern.options.out
gqa.modern.invalid_value.out
gqa.modern.unknown_option.out
gqa.modern.grid.out
gqa.modern.grid.json
gqa.modern.collision.out
gqa.modern.alias_collision.out
gqa.alias.json
gqa.modern.grid_edge.out
gqa.modern.rotated.out
gqa.modern.rotated.json
gqa.modern.grid_defaults.out
gqa.modern.grid_defaults.json
gqa.modern.axis_angle.out
gqa.modern.refine.out
gqa.modern.refine.json
gqa.modern.air_excluded.out
gqa.modern.matrix.out
gqa.modern.random.out
gqa.modern.uncertainty.out
gqa.modern.uncertainty.json
gqa.modern.centroid.out
gqa.modern.centroid.json
gqa.modern.accuracy.out
gqa.modern.accuracy.json
gqa.modern.absolute.out
gqa.modern.absolute.json
gqa.modern.seed_a.out
gqa.modern.seed_b.out
gqa.modern.issues.out
gqa.modern.issues.json
gqa.modern.crofton.out
gqa.modern.crofton.json
gqa.modern.tolerance.out
gqa.modern.stability_cap.out
gqa.modern.defaults.out
gqa.modern.defaults.json
gqa.modern.deadline.out
gqa.modern.deadline.json
gqa.modern.time_limit.out
gqa.modern.time_limit.json
gqa.modern.gaps.out
gqa.modern.gaps.json
gqa.modern.air.out
gqa.modern.air.json
gqa.mass_air.am.out
gqa.mass_air.aw.out
gqa.mass_mix.am.out
@@ -25,6 +72,8 @@ set(
gqa.mass_mix.u0.out
gqa.mged
gqa.overlaps.plot3
gqa.replay.json
gqa.replay.out
gqa.sphere_adaptive.out
gqa.volume.plot3
ovlp_overlaps.plot3
+390
View File
@@ -112,6 +112,11 @@ if test ! -f "$GQABIN" ; then
log "Unable to find gqa, aborting"
exit 1
fi
NIRT="`ensearch nirt`"
if test ! -f "$NIRT" ; then
log "Unable to find nirt, aborting"
exit 1
fi
rm -f density_table.txt
echo "5 1 stuff" > density_table.txt
@@ -169,6 +174,23 @@ in mass_solid.s rpp 0 1 0 1 0 1
r mass_solid.r u mass_solid.s
adjust mass_solid.r GIFTmater 5
# Half-spaces may split finite geometry but do not independently define a
# measurable volume.
in split.s half 1 0 0 0.5
r split_intersection.r u mass_solid.s + split.s
r split_subtraction.r u mass_solid.s - split.s
g split.g split_intersection.r split_subtraction.r
r unbounded.r u split.s
in bounded_submodel.s submodel split_intersection.r 0 ""
r bounded_submodel.r u bounded_submodel.s
in unbounded_submodel.s submodel unbounded.r 0 ""
r unbounded_submodel.r u unbounded_submodel.s
# A 0.01 mm solid verifies that automatic stability scales below
# BN_TOL_DIST rather than treating sub-tolerance geometry as ordinary size.
in tiny.s rpp 0 0.00001 0 0.00001 0 0.00001
r tiny.r u tiny.s
in mass_air.s rpp 1 2 0 1 0 1
r mass_air.r u mass_air.s
adjust mass_air.r air 2
@@ -191,6 +213,19 @@ $MGED -c gqa.g <<EOF >> $LOGFILE 2>&1
`cat gqa.mged`
EOF
cat > gqa.matrix.mged <<EOF
units m
in box.s rpp 0 1 0 1 0 1
r left.r u box.s
r right.r u box.s
comb moved.c u right.r
arced moved.c/right.r matrix rarc xlate 0.5 0 0
g model left.r moved.c
q
EOF
rm -f gqa.matrix.g
$MGED -c gqa.matrix.g < gqa.matrix.mged >> $LOGFILE 2>&1
GQA="$GQABIN -u m,m^3,kg -g 250mm-50mm -p gqa."
STATUS=0
@@ -259,6 +294,361 @@ assert_close 1000 "$MASS_MIX_U0" 0.001 "-U 0 excludes modeled air from weight"
assert_close 4.18879 "$SPHERE_VOLUME" 0.01 "adaptive sphere volume refines beyond symmetric first pass"
# The experimental interface must remain separate from the legacy options.
run_capture gqa.modern.grid.out $GQABIN --analyze --measure volume,mass,area --spacing 50 --refine 0 --json gqa.modern.grid.json gqa.g mass_solid.r
MODERN_GRID_VOLUME="`extract_last_number 'Volume:' gqa.modern.grid.out`"
MODERN_GRID_MASS="`extract_last_number 'Mass:' gqa.modern.grid.out`"
assert_close 1000000000 "$MODERN_GRID_VOLUME" 1000 "experimental grid cube volume"
assert_close 1000000 "$MODERN_GRID_MASS" 1 "experimental grid cube mass"
if test ! -s gqa.modern.grid.json || ! grep -q '"schema_version": 2' gqa.modern.grid.json || ! grep -q '"path": "/mass_solid.r"' gqa.modern.grid.json ; then
log "FAIL: experimental JSON region record"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.bounds.out $GQABIN --analyze --measure bounds --json gqa.modern.bounds.json gqa.g mass_solid.r
if ! grep -q '^gqa analysis (grid), 0 rays$' gqa.modern.bounds.out ||
! grep -q '^Bounding box: 0 0 0 1000 1000 1000 mm$' gqa.modern.bounds.out ||
! grep -q '"face_areas_mm2":' gqa.modern.bounds.json ; then
log "FAIL: experimental Boolean bounds report"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.plot_all.out $GQABIN --analyze --measure volume --spacing 50 --refine 1 --plot-prefix gqa.modern.plot.all. --plot-mode all gqa.g mass_solid.r
run_capture gqa.modern.plot_representative.out $GQABIN --analyze --measure volume --spacing 50 --refine 1 --plot-prefix gqa.modern.plot.representative. --plot-mode representative --plot-resolution 250 gqa.g mass_solid.r
ALL_PLOT_SIZE="`wc -c < gqa.modern.plot.all.volume.plot3 | tr -d ' '`"
REPRESENTATIVE_PLOT_SIZE="`wc -c < gqa.modern.plot.representative.volume.plot3 | tr -d ' '`"
if ! cmp -s gqa.modern.plot_all.out gqa.modern.plot_representative.out ||
test ! -s gqa.modern.plot.all.volume.plot3 ||
test ! -s gqa.modern.plot.representative.volume.plot3 ||
test "$REPRESENTATIVE_PLOT_SIZE" -ge "$ALL_PLOT_SIZE" ; then
log "FAIL: experimental all and representative volume plots"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.plot_overlap.out $GQABIN --analyze --check overlaps --spacing 250 --refine 0 --plot-prefix gqa.modern.plot.overlap. gqa.g overlaps
run_capture gqa.modern.plot_gap.out $GQABIN --analyze --check gaps --spacing 50 --refine 0 --plot-prefix gqa.modern.plot.gap. gqa.g gap.g
run_capture gqa.modern.plot_adj_air.out $GQABIN --analyze --check adjacent-air --spacing 250 --refine 0 --plot-prefix gqa.modern.plot.air. gqa.g adj_air.g
run_capture gqa.modern.plot_exp_air.out $GQABIN --analyze --check exposed-air --spacing 250 --refine 0 --plot-prefix gqa.modern.plot.air. gqa.g exposed_air.g
if test ! -s gqa.modern.plot.overlap.overlaps.plot3 ||
test ! -s gqa.modern.plot.gap.gaps.plot3 ||
test ! -s gqa.modern.plot.air.adj_air.plot3 ||
test ! -s gqa.modern.plot.air.exp_air.plot3 ; then
log "FAIL: experimental diagnostic Plot3 output"
STATUS="`expr $STATUS + 1`"
fi
if $GQABIN --analyze --measure volume --spacing 50 --refine 0 --plot-view gqa.g mass_solid.r > gqa.modern.plot_view.out 2>&1 ; then
log "FAIL: standalone gqa accepted --plot-view"
STATUS="`expr $STATUS + 1`"
fi
$MGED -c gqa.g > gqa.modern.mged_plot_view.out 2>&1 <<EOF
gqa --analyze --measure volume --spacing 100 --refine 0 --plot-view mass_solid.r
view objs
quit
EOF
cat gqa.modern.mged_plot_view.out >> "$LOGFILE"
if ! grep -q '^Volume: 1e+09 mm\^3$' gqa.modern.mged_plot_view.out ||
! grep -q '^gqa::analysis$' gqa.modern.mged_plot_view.out ; then
log "FAIL: MGED live analysis plot"
STATUS="`expr $STATUS + 1`"
fi
GQA_DB_CHECKSUM_BEFORE="`cksum gqa.g`"
if $GQABIN --analyze --measure volume --json gqa.g gqa.g mass_solid.r > gqa.modern.collision.out 2>&1 ; then
log "FAIL: analysis accepted the database as its JSON output"
STATUS="`expr $STATUS + 1`"
fi
GQA_DB_CHECKSUM_AFTER="`cksum gqa.g`"
if test "x$GQA_DB_CHECKSUM_BEFORE" != "x$GQA_DB_CHECKSUM_AFTER" ; then
log "FAIL: rejected output collision modified the database"
STATUS="`expr $STATUS + 1`"
fi
if $GQABIN --analyze --measure volume --json gqa.alias.json --invalid-rays ./gqa.alias.json gqa.g mass_solid.r > gqa.modern.alias_collision.out 2>&1 ; then
log "FAIL: analysis accepted aliased JSON and invalid-ray output paths"
STATUS="`expr $STATUS + 1`"
fi
if test -e gqa.alias.json ; then
log "FAIL: rejected aliased output paths created an output file"
STATUS="`expr $STATUS + 1`"
fi
# The shared bu_opt parser must recognize assignment syntax and options on
# either side of positional database and object arguments.
run_capture gqa.modern.options.out $GQABIN --analyze --measure=volume gqa.g --sampler=crofton --sequence=random mass_solid.r --rays=1000
if ! grep -q '^gqa analysis (crofton, random), 1000 rays$' gqa.modern.options.out ||
! grep -q '^Stopping condition: ray_limit$' gqa.modern.options.out ; then
log "FAIL: experimental bu_opt syntax and positional argument handling"
STATUS="`expr $STATUS + 1`"
fi
# Exercise QMC when this build provides it. A failed probe is a valid skip
# only when gqa explicitly reports that QMC support is unavailable.
log "... checking for QMC support"
QMC_STATUS=0
$GQABIN --analyze --measure=volume --sampler=crofton --sequence=qmc --rays=1000 gqa.g mass_solid.r > gqa.modern.qmc.out 2>&1 || QMC_STATUS=$?
cat gqa.modern.qmc.out >> "$LOGFILE"
if test $QMC_STATUS -eq 0 ; then
if ! grep -q '^gqa analysis (crofton, qmc), 1000 rays$' gqa.modern.qmc.out ||
! grep -q '^Stopping condition: ray_limit$' gqa.modern.qmc.out ; then
log "FAIL: experimental QMC analysis"
STATUS="`expr $STATUS + 1`"
fi
else
if ! grep -q '^QMC is unavailable in this build\.$' gqa.modern.qmc.out ; then
log "FAIL: experimental QMC availability check"
STATUS="`expr $STATUS + 1`"
fi
fi
if $GQABIN --analyze --measure=invalid gqa.g mass_solid.r > gqa.modern.invalid_value.out 2>&1 ; then
log "FAIL: analysis accepted an invalid bu_opt value"
STATUS="`expr $STATUS + 1`"
fi
if $GQABIN --analyze --unknown-option gqa.g mass_solid.r > gqa.modern.unknown_option.out 2>&1 ; then
log "FAIL: analysis accepted an unknown bu_opt option"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.grid_edge.out $GQABIN --analyze --measure volume,area --spacing 600 --refine 0 gqa.g mass_solid.r
MODERN_GRID_EDGE_VOLUME="`extract_last_number 'Volume:' gqa.modern.grid_edge.out`"
MODERN_GRID_EDGE_AREA="`extract_last_number 'Area:' gqa.modern.grid_edge.out`"
assert_close 1000000000 "$MODERN_GRID_EDGE_VOLUME" 1000 "experimental clipped grid-cell volume"
assert_close 6000000 "$MODERN_GRID_EDGE_AREA" 1 "experimental clipped grid-cell area"
run_capture gqa.modern.half_intersection.out $GQABIN --analyze --measure volume --spacing 50 --refine 0 gqa.g split_intersection.r
run_capture gqa.modern.half_subtraction.out $GQABIN --analyze --measure volume --spacing 50 --refine 0 gqa.g split_subtraction.r
run_capture gqa.modern.half_split.out $GQABIN --analyze --measure volume --spacing 50 --refine 0 gqa.g split.g
HALF_INTERSECTION_VOLUME="`extract_last_number 'Volume:' gqa.modern.half_intersection.out`"
HALF_SUBTRACTION_VOLUME="`extract_last_number 'Volume:' gqa.modern.half_subtraction.out`"
HALF_SPLIT_VOLUME="`extract_last_number 'Volume:' gqa.modern.half_split.out`"
assert_close 500000000 "$HALF_INTERSECTION_VOLUME" 1000 "experimental half-space intersection volume"
assert_close 500000000 "$HALF_SUBTRACTION_VOLUME" 1000 "experimental half-space subtraction volume"
assert_close 1000000000 "$HALF_SPLIT_VOLUME" 1000 "experimental half-space split volume"
run_capture gqa.modern.half_crofton.out $GQABIN --analyze --measure volume --sampler crofton --sequence random --seed 42 --rays 20000 gqa.g split.g
HALF_CROFTON_VOLUME="`extract_last_number 'Volume:' gqa.modern.half_crofton.out`"
assert_close 1000000000 "$HALF_CROFTON_VOLUME" 50000000 "Crofton half-space split volume"
run_capture gqa.modern.half_submodel.out $GQABIN --analyze --measure volume --sampler crofton --sequence random --seed 42 --rays 20000 gqa.g bounded_submodel.r
HALF_SUBMODEL_VOLUME="`extract_last_number 'Volume:' gqa.modern.half_submodel.out`"
assert_close 500000000 "$HALF_SUBMODEL_VOLUME" 25000000 "Crofton submodel half-space clipping volume"
if $GQABIN --analyze --measure volume --spacing 50 --refine 0 gqa.g unbounded.r > gqa.modern.unbounded.out 2>&1 ; then
log "FAIL: experimental analysis accepted an unbounded region"
STATUS="`expr $STATUS + 1`"
fi
if ! grep -q '^Cannot analyze unbounded region /unbounded.r\.$' gqa.modern.unbounded.out ; then
log "FAIL: experimental unbounded-region diagnostic"
STATUS="`expr $STATUS + 1`"
fi
if $GQABIN --analyze --measure volume --sampler crofton --rays 100 gqa.g unbounded_submodel.r > gqa.modern.unbounded_submodel.out 2>&1 ; then
log "FAIL: experimental analysis accepted an unbounded submodel"
STATUS="`expr $STATUS + 1`"
fi
if ! grep -q '^Cannot analyze unbounded region /unbounded_submodel.r\.$' gqa.modern.unbounded_submodel.out ; then
log "FAIL: experimental unbounded-submodel diagnostic"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.refine.out $GQABIN --analyze --measure volume,area --spacing 600 --refine 1 --json gqa.modern.refine.json gqa.g mass_solid.r
if ! grep -q '^Final refinement volume change:' gqa.modern.refine.out ||
! grep -q '"level": 1' gqa.modern.refine.json ; then
log "FAIL: experimental grid refinement evidence"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.rotated.out $GQABIN --analyze --measure volume,mass,area,centroid,moments --density density_table.txt --sampler grid-rotated --spacing 50 --refine 2 --azimuth 35 --elevation 25 --json gqa.modern.rotated.json gqa.g mass_solid.r
MODERN_ROTATED_VOLUME="`extract_last_number 'Volume:' gqa.modern.rotated.out`"
MODERN_ROTATED_MASS="`extract_last_number 'Mass:' gqa.modern.rotated.out`"
MODERN_ROTATED_AREA="`extract_last_number 'Area:' gqa.modern.rotated.out`"
assert_close 1000000000 "$MODERN_ROTATED_VOLUME" 1000000 "experimental rotated-grid cube volume"
assert_close 1000000 "$MODERN_ROTATED_MASS" 1000 "experimental rotated-grid cube mass"
assert_close 6000000 "$MODERN_ROTATED_AREA" 10000 "experimental rotated-grid cube area"
if ! grep -q '^gqa analysis (grid-rotated),' gqa.modern.rotated.out ||
! grep -q '^Centroid:' gqa.modern.rotated.out ||
! grep -q '^Inertia tensor' gqa.modern.rotated.out ||
! grep -q '^Grid directional spread:' gqa.modern.rotated.out ||
! grep -q '"sampler": "grid-rotated"' gqa.modern.rotated.json ||
! grep -q '"centroid_mm":' gqa.modern.rotated.json ||
! grep -q '"inertia_tensor_g_mm2":' gqa.modern.rotated.json ||
! grep -q '"grid_view_directions":' gqa.modern.rotated.json ||
! grep -q '"phase_u":' gqa.modern.rotated.json ; then
log "FAIL: experimental rotated-grid evidence"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.grid_defaults.out $GQABIN --analyze --measure volume --json gqa.modern.grid_defaults.json gqa.g mass_solid.r
if ! grep -q '^Stopping condition: stable$' gqa.modern.grid_defaults.out ||
! grep -q '"sampler": "grid"' gqa.modern.grid_defaults.json ||
! grep -q '"spacing_defaulted": true' gqa.modern.grid_defaults.json ||
! grep -q '"stability_defaulted": true' gqa.modern.grid_defaults.json ||
! grep -q '"time_defaulted": true' gqa.modern.grid_defaults.json ||
! grep -q '"time_ms": 60000.0' gqa.modern.grid_defaults.json ; then
log "FAIL: grid time-first automatic stability defaults"
STATUS="`expr $STATUS + 1`"
fi
if $GQABIN --analyze --measure volume --sampler grid --azimuth 35 --refine 0 gqa.g mass_solid.r > gqa.modern.axis_angle.out 2>&1 ; then
log "FAIL: axis-aligned grid accepted a rotated-grid orientation"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.air_excluded.out $GQABIN --analyze --measure volume,mass --air exclude --spacing 50 --refine 0 gqa.g mass_air_mix.g
MODERN_SOLID_VOLUME="`extract_last_number 'Volume:' gqa.modern.air_excluded.out`"
MODERN_SOLID_MASS="`extract_last_number 'Mass:' gqa.modern.air_excluded.out`"
assert_close 1000000000 "$MODERN_SOLID_VOLUME" 1000 "experimental air-excluded volume"
assert_close 1000000 "$MODERN_SOLID_MASS" 1 "experimental air-excluded mass"
run_capture gqa.modern.random.out $GQABIN --analyze --measure volume,area --sampler crofton --sequence random --rays 20000 gqa.g mass_solid.r
MODERN_RANDOM_VOLUME="`extract_last_number 'Volume:' gqa.modern.random.out`"
assert_close 1000000000 "$MODERN_RANDOM_VOLUME" 50000000 "experimental random Crofton cube volume"
run_capture gqa.modern.uncertainty.out $GQABIN --analyze --measure volume,area --sampler crofton --sequence random --rays 3200 --uncertainty --accuracy-scope all --json gqa.modern.uncertainty.json gqa.g mass_solid.r
if ! grep -q '^Approximate 95% sampling intervals (16 independent replicates):' gqa.modern.uncertainty.out ||
! grep -q '"replicates": 16' gqa.modern.uncertainty.json ||
! grep -q '"metric": "object:mass_solid.r.volume_mm3"' gqa.modern.uncertainty.json ||
! grep -q '"metric": "region:/mass_solid.r#' gqa.modern.uncertainty.json ||
! grep -q '"half_width":' gqa.modern.uncertainty.json ; then
log "FAIL: Crofton independent-replicate uncertainty"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.centroid.out $GQABIN --analyze --measure mass,centroid --density density_table.txt --sampler crofton --sequence random --rays 3200 --uncertainty --json gqa.modern.centroid.json gqa.g mass_solid.r
if ! grep -q '^Centroid:' gqa.modern.centroid.out ||
! grep -q '"metric": "model.centroid_x_mm"' gqa.modern.centroid.json ||
! grep -q '"centroid_mm":' gqa.modern.centroid.json ; then
log "FAIL: Crofton paired-ratio centroid uncertainty"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.absolute.out $GQABIN --analyze --measure volume --sampler crofton --sequence random --rays 6400 --absolute-error volume=100000000 --json gqa.modern.absolute.json gqa.g mass_solid.r
if ! grep -q '^Accuracy target:' gqa.modern.absolute.out ||
! grep -q '"volume": 100000000' gqa.modern.absolute.json ||
! grep -q '"target_tolerance": 100000000' gqa.modern.absolute.json ; then
log "FAIL: Crofton absolute accuracy target"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.seed_a.out $GQABIN --analyze --measure volume --sampler crofton --sequence random --seed 42 --rays 1000 gqa.g mass_solid.r
run_capture gqa.modern.seed_b.out $GQABIN --analyze --measure volume --sampler crofton --sequence random --seed 42 --rays 1000 gqa.g mass_solid.r
if ! cmp -s gqa.modern.seed_a.out gqa.modern.seed_b.out ; then
log "FAIL: seeded Crofton sampling is not reproducible"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.defaults.out $GQABIN --analyze --measure volume --sampler crofton --sequence random --seed 42 --json gqa.modern.defaults.json gqa.g tiny.r
if ! grep -q '^Stopping condition: stable' gqa.modern.defaults.out ||
! grep -q '"rays": null' gqa.modern.defaults.json ||
! grep -q '"stability_defaulted": true' gqa.modern.defaults.json ||
! grep -q '"stability_mm": 1e-05' gqa.modern.defaults.json ||
! grep -q '"time_defaulted": true' gqa.modern.defaults.json ||
! grep -q '"time_ms": 60000.0' gqa.modern.defaults.json ; then
log "FAIL: Crofton time-first automatic stability defaults"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.accuracy.out $GQABIN --analyze --measure volume,area --sampler crofton --sequence random --rays 6400 --uncertainty --relative-error 0.1 --json gqa.modern.accuracy.json gqa.g mass_solid.r
if ! grep -q '^Accuracy target:' gqa.modern.accuracy.out ||
! grep -q '"pilot_rays":' gqa.modern.accuracy.json ||
! grep -q '"production_rays":' gqa.modern.accuracy.json ; then
log "FAIL: Crofton pilot and production accuracy evaluation"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.deadline.out $GQABIN --analyze --measure volume --sampler crofton --sequence random --uncertainty --relative-error 0.1 --time 1000 --json gqa.modern.deadline.json gqa.g mass_solid.r
if ! grep -q '^Accuracy target: target_met' gqa.modern.deadline.out ||
! grep -q '^Adaptive plan:' gqa.modern.deadline.out ||
! grep -q '^Deadline:' gqa.modern.deadline.out ||
! grep -q '"rays": null' gqa.modern.deadline.json ||
! grep -q '"forecast_production_rays":' gqa.modern.deadline.json ||
! grep -q '"planning_safety_factor": 2' gqa.modern.deadline.json ||
! grep -q '"deadline_hit": false' gqa.modern.deadline.json ; then
log "FAIL: Crofton deadline-driven adaptive accuracy"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.time_limit.out $GQABIN --analyze --measure volume --sampler crofton --sequence random --uncertainty --relative-error 0.000000001 --time 500 --json gqa.modern.time_limit.json gqa.g mass_solid.r
if ! grep -q '^Accuracy target: time_limit' gqa.modern.time_limit.out ||
! grep -q '"accuracy_status": "time_limit"' gqa.modern.time_limit.json ||
! grep -q '"deadline_limited": true' gqa.modern.time_limit.json ; then
log "FAIL: Crofton adaptive deadline status"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.issues.out $GQABIN --analyze --measure volume --check overlaps --spacing 250 --json gqa.modern.issues.json gqa.g overlaps
if ! grep -q '^overlap: ' gqa.modern.issues.out ||
! grep -q '"type": "overlap"' gqa.modern.issues.json ||
! grep -q '"start_point_mm"' gqa.modern.issues.json ||
! grep -q '"end_point_mm"' gqa.modern.issues.json ; then
log "FAIL: experimental overlap report"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.matrix.out $GQABIN --analyze --measure volume --check overlaps --spacing 100 gqa.matrix.g model
if ! grep -q '/model/moved.c/right.r' gqa.modern.matrix.out ||
grep -q '^Area:' gqa.modern.matrix.out ; then
log "FAIL: transformed overlap path must remain complete"
STATUS="`expr $STATUS + 1`"
fi
# Crofton diagnostics use complete ray partitions and must report the same
# intentionally constructed overlap, gap, and air cases as the grid path.
run_capture gqa.modern.crofton.out $GQABIN --analyze --sampler crofton --sequence random --rays 12000 --check overlaps --json gqa.modern.crofton.json gqa.g overlaps
if ! grep -q 'Overlap candidate boxes: ' gqa.modern.crofton.out ||
! grep -q '^overlap: ' gqa.modern.crofton.out ||
! grep -q '"targeted_overlap": true' gqa.modern.crofton.json ||
! grep -q '"event_probability_upper_bound_95"' gqa.modern.crofton.json ||
! grep -q '"type": "overlap"' gqa.modern.crofton.json ; then
log "FAIL: Crofton targeted overlap report"
STATUS="`expr $STATUS + 1`"
fi
CROFTON_EVENT_COUNT="`grep -c '"ray_id":' gqa.modern.crofton.json`"
CROFTON_RAY_COUNT="`grep '"ray_id":' gqa.modern.crofton.json | sort -u | wc -l | tr -d ' '`"
if test "$CROFTON_EVENT_COUNT" -ne "$CROFTON_RAY_COUNT" ||
test "$CROFTON_RAY_COUNT" -le 1 ; then
log "FAIL: Crofton overlap events must be delivered once with their own ray IDs"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.tolerance.out $GQABIN --analyze --sampler crofton --sequence random --rays 1000 --check overlaps --tolerance 1000000000 gqa.g overlaps
if grep -q '^overlap: ' gqa.modern.tolerance.out ; then
log "FAIL: Crofton overlap tolerance"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.stability_cap.out $GQABIN --analyze --measure volume --sampler crofton --sequence random --rays 100 --stability 1 gqa.g mass_solid.r
if ! grep -q '), 100 rays$' gqa.modern.stability_cap.out ; then
log "FAIL: Crofton stability must honor the ray limit"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.gaps.out $GQABIN --analyze --measure volume --sampler crofton --sequence random --rays 5000 --check gaps --json gqa.modern.gaps.json gqa.g gap.g
if ! grep -q '^gap: ' gqa.modern.gaps.out ||
! grep -q '"type": "gap"' gqa.modern.gaps.json ; then
log "FAIL: Crofton gap report"
STATUS="`expr $STATUS + 1`"
fi
run_capture gqa.modern.air.out $GQABIN --analyze --measure volume --sampler crofton --sequence random --rays 5000 --check adjacent-air,exposed-air --json gqa.modern.air.json gqa.g adj_air.g exposed_air.g
if ! grep -q '"sampler": "crofton"' gqa.modern.air.json ||
! grep -q '"ray_count": 5000' gqa.modern.air.json ; then
log "FAIL: Crofton air diagnostic run"
STATUS="`expr $STATUS + 1`"
fi
cat > gqa.replay.json <<EOF
{"schema_version":2,"model":["mass_solid.r"],"rays":[{"ray_id":7,"origin_mm":[1500,500,500],"direction":[-1,0,0]}]}
EOF
run_capture gqa.replay.out $NIRT --replay gqa.replay.json --ray-id 7 gqa.g mass_solid.r
if ! grep -q 'Replaying ray 7' gqa.replay.out ||
! grep -q 'mass_solid.r' gqa.replay.out ; then
log "FAIL: nirt replay of analysis ray record"
STATUS="`expr $STATUS + 1`"
fi
if [ $STATUS = 0 ] ; then
log "-> gqa.sh succeeded"
else
+1
View File
@@ -187,6 +187,7 @@ distclean(${mged_outfiles})
cmakefiles(
CMakeLists.txt
csg_edit.mged
mged.tcl
regression_resources.tcl
search_exec_procs.tcl
+2 -3
View File
@@ -53,7 +53,7 @@ set(
)
brlcad_addlib(librender "${LIBRENDER_SRCS}" "" "${ADRT_LOCAL_INCLUDE_DIRS}"
PRIVATE_LIBS libgcv librt ZLIB::ZLIB ${M_LIBRARY}
PRIVATE_LIBS libgcv ZLIB::ZLIB ${M_LIBRARY}
)
set_target_properties(librender PROPERTIES VERSION 20.0.1 SOVERSION 20)
add_dependencies(librender gcv_plugins)
@@ -109,9 +109,8 @@ cmakefiles(isst.c)
add_subdirectory(master)
add_subdirectory(slave)
add_subdirectory(tests)
set(adrt_ignore_files load.h load_g.h tienet.h)
set(adrt_ignore_files load.h tienet.h)
cmakefiles(${adrt_ignore_files})
cmakefiles(CMakeLists.txt)
-8
View File
@@ -90,19 +90,11 @@ enum
#define ADRT_NAME_SIZE 256
/* TIE geometry is normalized from millimeters to meters while loading. */
#define ADRT_MODEL_UNITS_PER_TIE_UNIT 1000.0
#define ADRT_LOAD_FORMAT_G 42
#define ADRT_LOAD_FORMAT_REG 43
#define ADRT_LOAD_FORMAT_KDTREE 44
struct rt_annot_scene;
RENDER_EXPORT extern int load_g(struct tie_s *tie, const char *db, int argc, const char **argv, struct adrt_mesh_s **);
RENDER_EXPORT extern int load_g_annotations(struct tie_s *tie, const char *db,
int argc, const char **argv, struct adrt_mesh_s **,
struct rt_annot_scene **annotations);
#endif
+38 -23
View File
@@ -59,7 +59,6 @@ struct isst_s {
struct render_camera_s camera;
struct camera_tile_s tile;
struct adrt_mesh_s *meshes;
struct rt_annot_scene *annotations;
tienet_buffer_t buffer_image;
int ogl, sflags, w, h, gs, ui;
double dt, fps, uic;
@@ -68,7 +67,6 @@ struct isst_s {
void *texdata;
vect_t camera_pos_init;
vect_t camera_focus_init;
fastf_t scene_radius;
int64_t t1;
int64_t t2;
int dirty;
@@ -121,7 +119,13 @@ resize_isst(struct isst_s *isstp)
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
isstp->texdata = realloc(isstp->texdata, isstp->camera.w * isstp->camera.h * 3);
{
/* realloc through a temporary so a failure does not leak (and null out)
* the existing buffer. */
void *tmp = realloc(isstp->texdata, isstp->camera.w * isstp->camera.h * 3);
if (tmp)
isstp->texdata = tmp;
}
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, isstp->camera.w, isstp->camera.h, 0, GL_RGB, GL_UNSIGNED_BYTE, isstp->texdata);
glDisable(GL_LIGHTING);
@@ -154,14 +158,11 @@ isst_load_g(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc,
argv = (char **)malloc(sizeof(char *) * (strlen(Tcl_GetString(objv[3])) + 1)); /* allocate way too much. */
argc = (int)bu_argv_from_string(argv, strlen(Tcl_GetString(objv[3])), Tcl_GetString(objv[3]));
rt_annot_scene_destroy(isst->annotations);
isst->annotations = NULL;
load_g_annotations(isst->tie, Tcl_GetString(objv[2]), argc,
(const char **)argv, &(isst->meshes), &isst->annotations);
load_g(isst->tie, Tcl_GetString(objv[2]), argc, (const char **)argv, &(isst->meshes));
free(argv);
isst->scene_radius = render_camera_fit_scene(&isst->camera, isst->tie,
isst->annotations, ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSETALL(isst->camera.pos, isst->tie->radius);
VMOVE(isst->camera.focus, isst->tie->mid);
VMOVE(isst->camera_pos_init, isst->camera.pos);
VMOVE(isst->camera_focus_init, isst->camera.focus);
@@ -238,9 +239,7 @@ paint_window(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Ob
isst->buffer_image.ind = 0;
render_camera_prep(&isst->camera);
render_camera_render_annotations(&isst->camera, isst->tie, &isst->tile,
&isst->buffer_image, isst->annotations,
ADRT_MODEL_UNITS_PER_TIE_UNIT);
render_camera_render(&isst->camera, isst->tie, &isst->tile, &isst->buffer_image);
isst->t1 = bu_gettime();
@@ -283,6 +282,7 @@ set_resolution(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_
}
CLAMP(resolution, 1, 20);
/* gs is the render grid size; 0 means render at full window resolution */
if (resolution == 20)
isst->gs = 0;
else
@@ -326,10 +326,13 @@ isst_zap(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *c
return TCL_ERROR;
}
/* Release the buffers owned directly by the workspace before the struct
* itself. (The tie and mesh list have a more involved lifecycle and are
* left for a dedicated teardown.) */
TIENET_BUFFER_FREE(isst->buffer_image);
if (isst->texdata)
free(isst->texdata);
rt_annot_scene_destroy(isst->annotations);
bu_free(isst, "isst free");
isst = NULL;
@@ -351,7 +354,7 @@ render_mode(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj
if (objc == 4)
buf = Tcl_GetString(objv[3]);
/* pack the 'rest' into buf - probably should use a vls for this*/
/* pack the 'rest' into buf - probably should use a vls for this */
if ( strlen(mode) == 3 && bu_strncmp("cut", mode, 3) == 0 ) {
struct adrt_mesh_s *mesh;
@@ -388,22 +391,26 @@ zero_view(ClientData UNUSED(clientData), Tcl_Interp *UNUSED(interp), int UNUSED(
static int
move_walk(ClientData UNUSED(clientData), Tcl_Interp *interp, int UNUSED(objc), Tcl_Obj *const *objv)
move_walk(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv)
{
vect_t vec;
int flag;
if (objc < 3) {
Tcl_WrongNumArgs(interp, 1, objv, "pathName flag");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[2], &flag) != TCL_OK)
return TCL_ERROR;
if (flag >= 0) {
VSUB2(vec, isst->camera.focus, isst->camera.pos);
VSCALE(vec, vec, 0.1 * isst->scene_radius);
VSCALE(vec, vec, 0.1 * isst->tie->radius);
VADD2(isst->camera.pos, isst->camera.pos, vec);
VADD2(isst->camera.focus, isst->camera.focus, vec);
} else {
VSUB2(vec, isst->camera.pos, isst->camera.focus);
VSCALE(vec, vec, 0.1 * isst->scene_radius);
VSCALE(vec, vec, 0.1 * isst->tie->radius);
VADD2(isst->camera.pos, isst->camera.pos, vec);
VADD2(isst->camera.focus, isst->camera.focus, vec);
}
@@ -412,12 +419,16 @@ move_walk(ClientData UNUSED(clientData), Tcl_Interp *interp, int UNUSED(objc), T
}
static int
move_strafe(ClientData UNUSED(clientData), Tcl_Interp *interp, int UNUSED(objc), Tcl_Obj *const *objv)
move_strafe(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv)
{
vect_t vec, dir, up;
int flag;
if (objc < 3) {
Tcl_WrongNumArgs(interp, 1, objv, "pathName flag");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[2], &flag) != TCL_OK)
return TCL_ERROR;
@@ -426,13 +437,13 @@ move_strafe(ClientData UNUSED(clientData), Tcl_Interp *interp, int UNUSED(objc),
if (flag >= 0) {
VSUB2(dir, isst->camera.focus, isst->camera.pos);
VCROSS(vec, dir, up);
VSCALE(vec, vec, 0.1 * isst->scene_radius);
VSCALE(vec, vec, 0.1 * isst->tie->radius);
VADD2(isst->camera.pos, isst->camera.pos, vec);
VADD2(isst->camera.focus, isst->camera.pos, dir);
} else {
VSUB2(dir, isst->camera.focus, isst->camera.pos);
VCROSS(vec, dir, up);
VSCALE(vec, vec, -0.1 * isst->scene_radius);
VSCALE(vec, vec, -0.1 * isst->tie->radius);
VADD2(isst->camera.pos, isst->camera.pos, vec);
VADD2(isst->camera.focus, isst->camera.pos, dir);
}
@@ -540,10 +551,14 @@ aerotate(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *c
VSCALE(vecdfoc, vecdfoc, mag_focus);
VADD2(isst->camera.focus, isst->camera_focus_init, vecdfoc);
}
/* Update the tcl copies of the az/el vars */
/* Update the tcl copies of the az/el vars. Store them in the same units
* as isst_load_g (radians via -DEG2RAD); previously aerotate wrote the raw
* degrees from bn_ae_vec, leaving the Tcl vars inconsistent after a drag. */
VSUB2(vec, isst->camera.focus, isst->camera.pos);
VUNITIZE(vec);
bn_ae_vec(&az, &el, vec);
az = az * -DEG2RAD;
el = el * -DEG2RAD;
bu_vls_sprintf(&tclstr, "%f", az);
Tcl_SetVar(interp, "az", bu_vls_addr(&tclstr), 0);
bu_vls_sprintf(&tclstr, "%f", el);
@@ -561,7 +576,7 @@ open_dm(ClientData UNUSED(cdata), Tcl_Interp *interp, int UNUSED(objc), Tcl_Obj
dmp = dm_open(NULL, (void *)interp, dm_default_type(), sizeof(av)/sizeof(void*)-1, (const char **)av);
if (dmp == DM_NULL) {
printf("dm failed?\n");
printf("Failed to open display manager\n");
return TCL_ERROR;
}
@@ -639,7 +654,7 @@ const char *fullname;
argv = __argv;
#endif
/* initialize progname for run-tim resource finding */
/* initialize progname for run-time resource finding */
bu_setprogname(argv[0]);
#ifdef HAVE_WINDOWS_H
+21 -225
View File
@@ -29,7 +29,6 @@
#include "bu/dylib.h"
#include "bu/log.h"
#include "bu/str.h"
#include "rt/primitives/annot.h"
#include "./camera.h"
@@ -44,27 +43,13 @@ struct render_shader_s {
static struct render_shader_s *shaders = NULL;
struct render_camera_annotation_data {
const struct rt_annot_scene *scene;
fastf_t model_units_per_tie_unit;
struct rt_annot_view view;
};
struct render_camera_parallel_data {
render_camera_thread_data_t thread;
struct render_camera_annotation_data annotation;
};
static const fastf_t render_camera_default_scene_radius = 1.0;
/* number of leading characters compared when matching a shader by name */
#define RENDER_SHADER_NAME_CMP_LEN 8
void render_camera_render_thread(int cpu, void *ptr); /* for bu_parallel */
static void render_camera_prep_ortho(render_camera_t *camera);
static void render_camera_prep_persp(render_camera_t *camera);
static void render_camera_prep_persp_dof(render_camera_t *camera);
static void render_camera_annotation_view(const render_camera_t *camera,
fastf_t model_units_per_tie_unit, struct rt_annot_view *view);
static struct render_shader_s *render_shader_register (const char *name, int (*init)(render_t *, const char *));
@@ -408,147 +393,20 @@ render_camera_prep(render_camera_t *camera)
}
fastf_t
render_camera_fit_scene(render_camera_t *camera, const struct tie_s *tie,
const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit)
{
point_t annotation_min, annotation_max;
point_t scene_min, scene_max;
vect_t offset;
fastf_t scene_radius;
if (!camera || !tie)
return 0.0;
if (!annotations || !rt_annot_scene_bounds(annotations,
annotation_min, annotation_max)) {
VSETALL(camera->pos, tie->radius);
VMOVE(camera->focus, tie->mid);
return tie->radius;
}
if (!isfinite(model_units_per_tie_unit) ||
model_units_per_tie_unit <= 0.0)
model_units_per_tie_unit = 1.0;
VSCALE(annotation_min, annotation_min,
1.0 / model_units_per_tie_unit);
VSCALE(annotation_max, annotation_max,
1.0 / model_units_per_tie_unit);
VMOVE(scene_min, annotation_min);
VMOVE(scene_max, annotation_max);
if (tie->tri_num) {
VMINMAX(scene_min, scene_max, tie->amin);
VMINMAX(scene_min, scene_max, tie->amax);
}
VADD2SCALE(camera->focus, scene_min, scene_max, 0.5);
VSUB2(offset, scene_max, scene_min);
scene_radius = 0.5 * MAGNITUDE(offset);
if (!isfinite(scene_radius) || scene_radius <= SMALL_FASTF)
scene_radius = render_camera_default_scene_radius;
/* Keep the established isometric initial direction while positioning it
* relative to the combined scene center. */
VSETALL(offset, scene_radius);
VADD2(camera->pos, camera->focus, offset);
return scene_radius;
}
static void
render_camera_annotation_view(const render_camera_t *camera,
fastf_t model_units_per_tie_unit, struct rt_annot_view *view)
{
vect_t look, up, side, temp, right;
point_t model_position;
fastf_t angle, sine, cosine, xy_scale = 1.0;
mat_t *matrix = &view->model2view;
VSUB2(look, camera->focus, camera->pos);
VUNITIZE(look);
VSET(up, 0.0, 0.0, 1.0);
VMOVE(temp, look);
angle = VDOT(up, temp);
VSCALE(temp, temp, angle);
VSUB2(up, up, temp);
VUNITIZE(up);
VCROSS(side, up, look);
sine = sin(-camera->tilt * DEG2RAD);
cosine = cos(-camera->tilt * DEG2RAD);
VSCALE(up, up, cosine);
VSCALE(side, side, sine);
VADD2(up, up, side);
VCROSS(side, up, look);
VREVERSE(right, side);
MAT_IDN(*matrix);
if (camera->type == RENDER_CAMERA_ORTHOGRAPHIC &&
camera->aspect > 0.0 && camera->gridsize > 0.0) {
xy_scale = 2.0 / (camera->aspect * camera->gridsize *
model_units_per_tie_unit);
} else if (camera->aspect > 0.0) {
xy_scale = 1.0 / camera->aspect;
}
for (size_t axis = 0; axis < 3; ++axis) {
(*matrix)[axis] = right[axis] * xy_scale;
(*matrix)[4 + axis] = up[axis] * xy_scale;
(*matrix)[8 + axis] = -look[axis];
}
VSCALE(model_position, camera->pos, model_units_per_tie_unit);
(*matrix)[3] = -VDOT(&(*matrix)[0], model_position);
(*matrix)[7] = -VDOT(&(*matrix)[4], model_position);
(*matrix)[11] = -VDOT(&(*matrix)[8], model_position);
view->width = camera->w;
view->height = camera->h;
view->perspective =
camera->type == RENDER_CAMERA_PERSPECTIVE ? 2.0 * camera->fov : 0.0;
}
static void
render_camera_composite_annotation(render_camera_thread_data_t *td,
const struct render_camera_annotation_data *annotation,
const struct tie_ray_s *tie_ray, fastf_t sample_x, fastf_t sample_y,
vect_t *pixel)
{
struct rt_annot_hit back_layer = {0};
struct xray ray;
fastf_t scene_distance = INFINITY;
if (!annotation || !annotation->scene)
return;
VSCALE(ray.r_pt, tie_ray->pos, annotation->model_units_per_tie_unit);
VMOVE(ray.r_dir, tie_ray->dir);
if (!rt_annot_scene_query_layers(annotation->scene, &annotation->view,
&ray, sample_x, sample_y, INFINITY, &back_layer, 1))
return;
if (!back_layer.screen_space) {
struct tie_id_s id;
struct tie_ray_s depth_ray = *tie_ray;
if (TIE_WORK(td->tie, &depth_ray, &id, render_hit, NULL))
scene_distance = id.dist * annotation->model_units_per_tie_unit;
}
(void)rt_annot_scene_composite(annotation->scene, &annotation->view,
&ray, sample_x, sample_y, scene_distance, *pixel, NULL);
}
static void
render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *td,
const struct render_camera_annotation_data *annotation)
void
render_camera_render_thread(int UNUSED(cpu), void *ptr)
{
render_camera_thread_data_t *td;
int d, n, res_ind, scanline, v_scanline;
vect_t pixel, accum;
vect_t v1 = VINIT_ZERO;
vect_t v2 = VINIT_ZERO;
struct tie_ray_s ray;
struct tie_ray_s annotation_ray;
fastf_t view_inv;
VSETALL(v1, 0);
td = (render_camera_thread_data_t *)ptr;
view_inv = 1.0 / td->camera->view_num;
td->camera->render.tie = td->tie;
@@ -599,15 +457,9 @@ render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *t
VMOVE(ray.pos, td->camera->view_list[d].pos);
ray.depth = 0;
VUNITIZE(ray.dir);
annotation_ray = ray;
/* Compute pixel value using this ray */
td->camera->render.work(&td->camera->render, td->tie, &ray, &pixel);
render_camera_composite_annotation(td, annotation,
&annotation_ray,
(fastf_t)n + 0.5,
(fastf_t)td->camera->h - (fastf_t)v_scanline - 0.5,
&pixel);
VADD2(accum, accum, pixel);
}
@@ -624,7 +476,6 @@ render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *t
VMOVE(ray.pos, td->camera->view_list[0].pos);
ray.depth = 0;
VUNITIZE(ray.dir);
annotation_ray = ray;
/* Compute pixel value using this ray */
td->camera->render.work(&td->camera->render, td->tie, &ray, &pixel);
@@ -639,19 +490,12 @@ render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *t
VSET(pixel, (TFLOAT)RENDER_CAMERA_BGR, (TFLOAT)RENDER_CAMERA_BGG, (TFLOAT)RENDER_CAMERA_BGB);
ray.depth = 0;
annotation_ray = ray;
/* Compute pixel value using this ray */
td->camera->render.work(&td->camera->render, td->tie, &ray, &pixel);
}
}
if (td->camera->view_num == 1)
render_camera_composite_annotation(td, annotation,
&annotation_ray, (fastf_t)n + 0.5,
(fastf_t)td->camera->h - (fastf_t)v_scanline - 0.5,
&pixel);
if (td->tile->format == RENDER_CAMERA_BIT_DEPTH_24) {
V_MIN(pixel[0], 1);
@@ -679,30 +523,9 @@ render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *t
void
render_camera_render_thread(int cpu, void *ptr)
render_camera_render(render_camera_t *camera, struct tie_s *tie, camera_tile_t *tile, tienet_buffer_t *result)
{
render_camera_render_thread_impl(cpu, (render_camera_thread_data_t *)ptr,
NULL);
}
static void
render_camera_render_annotations_thread(int cpu, void *ptr)
{
struct render_camera_parallel_data *data =
(struct render_camera_parallel_data *)ptr;
render_camera_render_thread_impl(cpu, &data->thread, &data->annotation);
}
static void
render_camera_render_internal(render_camera_t *camera, struct tie_s *tie,
camera_tile_t *tile, tienet_buffer_t *result,
const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit)
{
struct render_camera_parallel_data data = {0};
render_camera_thread_data_t td;
unsigned int scanline;
uint32_t ind;
@@ -720,47 +543,19 @@ render_camera_render_internal(render_camera_t *camera, struct tie_s *tie,
TCOPY(camera_tile_t, tile, 0, result->data, result->ind);
result->ind += sizeof(camera_tile_t);
data.thread.tie = tie;
data.thread.camera = camera;
data.thread.tile = tile;
data.thread.res_buf = &((char *)result->data)[result->ind];
td.tie = tie;
td.camera = camera;
td.tile = tile;
td.res_buf = &((char *)result->data)[result->ind];
scanline = 0;
data.thread.scanline = &scanline;
data.thread.sem_tie_worker = bu_semaphore_register("sem_tie_worker");
data.annotation.scene = annotations;
data.annotation.model_units_per_tie_unit =
model_units_per_tie_unit > 0.0 ? model_units_per_tie_unit : 1.0;
if (annotations)
render_camera_annotation_view(camera,
data.annotation.model_units_per_tie_unit, &data.annotation.view);
td.scanline = &scanline;
td.sem_tie_worker = bu_semaphore_register("sem_tie_worker");
if (annotations)
bu_parallel(render_camera_render_annotations_thread,
camera->thread_num, &data);
else
bu_parallel(render_camera_render_thread, camera->thread_num,
&data.thread);
bu_parallel(render_camera_render_thread, camera->thread_num, &td);
result->ind = ind;
}
void
render_camera_render(render_camera_t *camera, struct tie_s *tie,
camera_tile_t *tile, tienet_buffer_t *result)
{
render_camera_render_internal(camera, tie, tile, result, NULL, 1.0);
}
void
render_camera_render_annotations(render_camera_t *camera, struct tie_s *tie,
camera_tile_t *tile, tienet_buffer_t *result,
const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit)
{
render_camera_render_internal(camera, tie, tile, result, annotations,
model_units_per_tie_unit);
return;
}
@@ -825,9 +620,9 @@ render_shader_unload_plugin(render_t *r, const char *name)
{
#ifdef HAVE_DLFCN_H
struct render_shader_s *t, *s = shaders, *meh;
if (!bu_strncmp(s->name, name, 8)) {
if (!bu_strncmp(s->name, name, RENDER_SHADER_NAME_CMP_LEN)) {
t = s->next;
if (r && r->shader && !bu_strncmp(r->shader, name, 8)) {
if (r && r->shader && !bu_strncmp(r->shader, name, RENDER_SHADER_NAME_CMP_LEN)) {
meh = s->next;
while (meh) {
if (render_shader_init(r, meh->name, NULL) != -1)
@@ -846,7 +641,7 @@ LOADED:
}
while (s->next) {
if (!bu_strncmp(s->next->name, name, 8)) {
if (!bu_strncmp(s->next->name, name, RENDER_SHADER_NAME_CMP_LEN)) {
if (r)
render_shader_init(r, s->name, NULL);
if (s->next->dlh)
@@ -856,6 +651,7 @@ LOADED:
bu_free(t, "unload shader");
return 0;
}
s = s->next;
}
bu_log("Could not find shader \"%s\"\n", name);
@@ -871,7 +667,7 @@ render_shader_init(render_t *r, const char *name, const char *buf)
{
struct render_shader_s *s = shaders;
while (s) {
if (!bu_strncmp(s->name, name, 8)) {
if (!bu_strncmp(s->name, name, RENDER_SHADER_NAME_CMP_LEN)) {
s->init(r, buf);
r->shader = s->name;
return 0;
-11
View File
@@ -99,18 +99,7 @@ __BEGIN_DECLS
RENDER_EXPORT extern void render_camera_init(render_camera_t *camera, size_t threads);
RENDER_EXPORT extern void render_camera_free(render_camera_t *camera);
RENDER_EXPORT extern void render_camera_prep(render_camera_t *camera);
/** Center a camera on TIE geometry and any prepared annotations. Returns the
* effective scene radius in TIE coordinates. */
RENDER_EXPORT extern fastf_t render_camera_fit_scene(render_camera_t *camera,
const struct tie_s *tie, const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit);
RENDER_EXPORT extern void render_camera_render(render_camera_t *camera, struct tie_s *tie, camera_tile_t *tile, tienet_buffer_t *result);
/** Render with an externally owned annotation scene. The coordinate scale
* converts TIE coordinates to database model coordinates. */
RENDER_EXPORT extern void render_camera_render_annotations(render_camera_t *camera,
struct tie_s *tie, camera_tile_t *tile, tienet_buffer_t *result,
const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit);
RENDER_EXPORT extern int render_shader_init(render_t *, const char *name, const char *buf);
RENDER_EXPORT extern const char *render_shader_load_plugin(const char *filename);
+10 -86
View File
@@ -40,13 +40,11 @@
#include "vmath.h"
#include "nmg.h"
#include "rt/geom.h"
#include "rt/search.h"
#include "raytrace.h"
#include "rt/tie.h"
#include "adrt.h"
#include "adrt_struct.h"
#include "load_g.h"
static struct bn_tol tol; /* calculation tolerance */
@@ -106,42 +104,6 @@ struct gcv_data {
static struct gcv_data gcvwriter = {{load_nmg_to_adrt_gcvwrite, NULL, NULL}, NULL};
int
adrt_path_has_geometry(struct db_i *database, const char *path)
{
static const char *geometry_search = "! -type comb ! -type annot";
struct directory *root;
if (!database || !path)
return 0;
root = db_lookup(database, path, LOOKUP_QUIET);
/* Let db_walk_tree report a missing path rather than hiding the error. */
if (!root)
return 1;
return db_search(NULL, DB_SEARCH_QUIET, geometry_search, 1, &root,
database, NULL, NULL, NULL) != 0;
}
union tree *
adrt_leaf_tess(struct db_tree_state *tsp, const struct db_full_path *pathp,
struct rt_db_internal *ip, void *client_data)
{
union tree *result;
if (!ip || ip->idb_type != ID_ANNOT)
return rt_booltree_leaf_tess(tsp, pathp, ip, client_data);
/* Annotations are rendered directly and have no NMG representation. */
BU_GET(result, union tree);
RT_TREE_INIT(result);
result->tr_op = OP_NOP;
return result;
}
/* load the region into the tie image */
static void
load_nmg_to_adrt_internal(struct adrt_mesh_s *mesh, struct nmgregion *r)
@@ -191,8 +153,7 @@ load_nmg_to_adrt_internal(struct adrt_mesh_s *mesh, struct nmgregion *r)
NMG_CK_VERTEX(v);
/* convert mm to m */
VSCALE((*tribuf[vert_count]).v, v->vg_p->coord,
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[vert_count]).v, v->vg_p->coord, 1.0/1000.0);
vert_count++;
}
if (vert_count > 3)
@@ -287,12 +248,9 @@ load_nmg_to_adrt_regstart(struct db_tree_state *ts, const struct db_full_path *p
for (i=0;i<bot->num_faces;i++)
{
VSCALE((*tribuf[0]).v, (bot->vertices+3*bot->faces[3*i+0]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[1]).v, (bot->vertices+3*bot->faces[3*i+1]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[2]).v, (bot->vertices+3*bot->faces[3*i+2]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[0]).v, (bot->vertices+3*bot->faces[3*i+0]), 1.0/1000.0);
VSCALE((*tribuf[1]).v, (bot->vertices+3*bot->faces[3*i+1]), 1.0/1000.0);
VSCALE((*tribuf[2]).v, (bot->vertices+3*bot->faces[3*i+2]), 1.0/1000.0);
TIE_VAL(tie_push)(cur_tie, tribuf, 1, mesh, 0);
}
@@ -340,16 +298,11 @@ load_nmg_to_adrt_gcvwrite(struct nmgregion *r, const struct db_full_path *pathp,
int
load_g_annotations(struct tie_s *tie, const char *db, int argc,
const char **argv, struct adrt_mesh_s **meshes,
struct rt_annot_scene **annotations)
load_g(struct tie_s *tie, const char *db, int argc, const char **argv, struct adrt_mesh_s **meshes)
{
struct model *the_model;
struct bg_tess_tol ttol; /* tessellation tolerance in mm */
struct db_tree_state tree_state; /* includes tol & model */
const char **geometry_paths = NULL;
int geometry_count = 0;
int walk_result = 0;
cur_tie = tie; /* NOTE: cur_tie is a file-scope global; this is not
* reentrant and would need locking to be thread-safe. */
@@ -385,21 +338,13 @@ load_g_annotations(struct tie_s *tie, const char *db, int argc,
if ((dbip = db_open(db, DB_OPEN_READONLY)) == DBI_NULL) {
perror(db);
bu_log("Unable to open geometry database file (%s)\n", db);
nmg_km(the_model);
return -1;
}
if (db_dirbuild(dbip)) {
bu_log("ERROR: db_dirbuild failed\n");
db_close(dbip);
dbip = DBI_NULL;
nmg_km(the_model);
return -1;
}
if (annotations) {
*annotations = rt_annot_scene_create(dbip, argc, argv, &ttol, &tol);
}
BN_CK_TOL(tree_state.ts_tol);
BG_CK_TESS_TOL(tree_state.ts_ttol);
@@ -417,26 +362,15 @@ load_g_annotations(struct tie_s *tie, const char *db, int argc,
tribuf[1] = (TIE_3 *)bu_malloc(sizeof(TIE_3) * 3, "triangle tribuffer");
tribuf[2] = (TIE_3 *)bu_malloc(sizeof(TIE_3) * 3, "triangle tribuffer");
if (argc > 0) {
geometry_paths = (const char **)bu_calloc((size_t)argc,
sizeof(const char *), "ADRT geometry paths");
for (int i = 0; i < argc; ++i) {
if (adrt_path_has_geometry(dbip, argv[i]))
geometry_paths[geometry_count++] = argv[i];
}
}
if (geometry_count) {
walk_result = db_walk_tree(dbip,
geometry_count, /* number of toplevel regions */
geometry_paths, /* region names */
(void) db_walk_tree(dbip,
argc, /* number of toplevel regions */
argv, /* region names */
1, /* ncpu */
&tree_state, /* initial tree state */
load_nmg_to_adrt_regstart, /* region start function */
gcv_region_end, /* region end function */
adrt_leaf_tess, /* leaf func */
rt_booltree_leaf_tess, /* leaf func */
(void *)&gcvwriter); /* client data */
}
/* Release dynamic storage */
nmg_km(the_model);
@@ -446,20 +380,10 @@ load_g_annotations(struct tie_s *tie, const char *db, int argc,
bu_free(tribuf[1], "vert");
bu_free(tribuf[2], "vert");
bu_free(tribuf, "tri");
if (geometry_paths)
bu_free(geometry_paths, "ADRT geometry paths");
TIE_VAL(tie_prep)(cur_tie);
return walk_result;
}
int
load_g(struct tie_s *tie, const char *db, int argc, const char **argv,
struct adrt_mesh_s **meshes)
{
return load_g_annotations(tie, db, argc, argv, meshes, NULL);
return 0;
}
/*
-43
View File
@@ -1,43 +0,0 @@
/* L O A D _ G . H
* BRL-CAD / ADRT
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef ADRT_LOAD_G_H
#define ADRT_LOAD_G_H
#include "common.h"
#include "adrt.h"
struct db_i;
struct db_tree_state;
struct db_full_path;
struct rt_db_internal;
union tree;
__BEGIN_DECLS
RENDER_EXPORT extern int adrt_path_has_geometry(struct db_i *dbip,
const char *path);
RENDER_EXPORT extern union tree *adrt_leaf_tess(struct db_tree_state *tsp,
const struct db_full_path *pathp, struct rt_db_internal *ip,
void *client_data);
__END_DECLS
#endif /* ADRT_LOAD_G_H */
/*
* Local Variables:
* tab-width: 8
* mode: C
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
-16
View File
@@ -1,16 +0,0 @@
brlcad_addexec(adrt_annotation_test annotation.c
"librender;libwdb;librt;libbu;${M_LIBRARY}" TEST)
target_include_directories(adrt_annotation_test PRIVATE
${BRLCAD_SOURCE_DIR}/src/adrt
${BRLCAD_SOURCE_DIR}/src/adrt/librender
)
brlcad_add_test(NAME adrt_annotation_test COMMAND adrt_annotation_test)
cmakefiles(annotation.c CMakeLists.txt)
# Local Variables:
# tab-width: 8
# mode: cmake
# indent-tabs-mode: t
# End:
# ex: shiftwidth=2 tabstop=8
-179
View File
@@ -1,179 +0,0 @@
/* A N N O T A T I O N . C
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "common.h"
#include <math.h>
#include <stdio.h>
#include "bu/app.h"
#include "bu/file.h"
#include "raytrace.h"
#include "rt/primitives/annot.h"
#include "wdb.h"
#include "load_g.h"
#include "librender/camera.h"
static void
write_test_database(struct db_i *dbip)
{
struct rt_wdb *wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_DISK);
struct rt_annot_internal annot = {0};
point2d_t vertices[2];
struct line_seg line = {0};
void *segments[1];
int reverse[1] = {0};
point_t center = VINIT_ZERO;
struct wmember members;
if (wdbp == RT_WDB_NULL)
bu_exit(1, "unable to open ADRT annotation test database\n");
annot.magic = RT_ANNOT_INTERNAL_MAGIC;
VSET(annot.V, 10.0, 0.0, 0.0);
annot.flags = RT_ANNOT_MODEL_SPACE;
VSET(annot.u_vec, 0.0, 1.0, 0.0);
VSET(annot.v_vec, 0.0, 0.0, 1.0);
annot.vert_count = 2;
annot.verts = vertices;
V2SET(vertices[0], -1.0, 0.0);
V2SET(vertices[1], 1.0, 0.0);
annot.ant.count = 1;
annot.ant.reverse = reverse;
annot.ant.segments = segments;
line.magic = CURVE_LSEG_MAGIC;
line.start = 0;
line.end = 1;
segments[0] = &line;
if (rt_annot_validate(&annot, NULL) ||
mk_annot(wdbp, "test.annot", &annot) < 0 ||
mk_sph(wdbp, "test.sph", center, 1.0) < 0)
bu_exit(1, "unable to write ADRT annotation test objects\n");
BU_LIST_INIT(&members.l);
if (!mk_addmember("test.annot", &members.l, NULL, WMOP_UNION) ||
mk_lcomb(wdbp, "annotation-only.c", &members, 0, NULL, NULL,
NULL, 0))
bu_exit(1, "unable to write annotation-only test combination\n");
BU_LIST_INIT(&members.l);
if (!mk_addmember("test.annot", &members.l, NULL, WMOP_UNION) ||
!mk_addmember("test.sph", &members.l, NULL, WMOP_UNION) ||
mk_lcomb(wdbp, "mixed.c", &members, 0, NULL, NULL, NULL, 0))
bu_exit(1, "unable to write mixed test combination\n");
}
static void
check_geometry_filter(struct db_i *dbip)
{
struct rt_db_internal intern;
union tree *leaf;
if (adrt_path_has_geometry(dbip, "test.annot") ||
adrt_path_has_geometry(dbip, "annotation-only.c") ||
!adrt_path_has_geometry(dbip, "test.sph") ||
!adrt_path_has_geometry(dbip, "mixed.c"))
bu_exit(1, "ADRT annotation geometry filtering failed\n");
RT_DB_INTERNAL_INIT(&intern);
intern.idb_type = ID_ANNOT;
leaf = adrt_leaf_tess(NULL, NULL, &intern, NULL);
if (!leaf || leaf->tr_op != OP_NOP)
bu_exit(1, "ADRT annotation leaf did not become a no-op\n");
db_free_tree(leaf);
}
static void
check_camera_fit(struct db_i *dbip)
{
const char *paths[] = {"test.annot"};
struct bg_tess_tol ttol = BG_TESS_TOL_INIT_TOL;
struct bn_tol tol = BN_TOL_INIT_TOL;
struct rt_annot_scene *scene = rt_annot_scene_create(dbip, 1, paths,
&ttol, &tol);
render_camera_t camera = {0};
struct tie_s tie = {0};
fastf_t radius;
const fastf_t combined_radius = 0.5 * sqrt(41.0);
if (!scene || rt_annot_scene_count(scene) != 1)
bu_exit(1, "unable to prepare ADRT annotation test scene\n");
tie.radius = 2.0;
VSET(tie.mid, 1.0, 2.0, 3.0);
radius = render_camera_fit_scene(&camera, &tie, NULL, 1.0);
if (!NEAR_EQUAL(radius, 2.0, BN_TOL_DIST) ||
!VNEAR_EQUAL(camera.focus, tie.mid, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[X], 2.0, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[Y], 2.0, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[Z], 2.0, BN_TOL_DIST))
bu_exit(1, "ADRT geometry-only camera framing changed\n");
radius = render_camera_fit_scene(&camera, &tie, scene, 10.0);
if (radius <= SMALL_FASTF ||
!NEAR_EQUAL(camera.focus[X], 1.0, BN_TOL_DIST) ||
!NEAR_ZERO(camera.focus[Y], BN_TOL_DIST) ||
!NEAR_ZERO(camera.focus[Z], BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[X] - camera.focus[X], radius, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[Y] - camera.focus[Y], radius, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[Z] - camera.focus[Z], radius, BN_TOL_DIST))
bu_exit(1, "ADRT annotation-only camera framing failed: radius=%g "
"focus=(%g %g %g) pos=(%g %g %g)\n", radius,
V3ARGS(camera.focus), V3ARGS(camera.pos));
tie.tri_num = 1;
VSET(tie.amin, -2.0, -2.0, -2.0);
VSET(tie.amax, 0.0, 2.0, 2.0);
radius = render_camera_fit_scene(&camera, &tie, scene, 10.0);
if (!NEAR_EQUAL(radius, combined_radius, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.focus[X], -0.5, BN_TOL_DIST) ||
!NEAR_ZERO(camera.focus[Y], BN_TOL_DIST) ||
!NEAR_ZERO(camera.focus[Z], BN_TOL_DIST))
bu_exit(1, "ADRT combined geometry and annotation framing failed\n");
rt_annot_scene_destroy(scene);
}
int
main(int argc, char **argv)
{
char path[MAXPATHLEN];
struct db_i *dbip;
bu_setprogname(argv[0]);
if (argc != 1)
return 1;
{
FILE *fp = bu_temp_file(path, MAXPATHLEN);
if (!fp)
bu_exit(1, "unable to create ADRT annotation test path\n");
fclose(fp);
}
dbip = db_create(path, 5);
if (dbip == DBI_NULL)
bu_exit(1, "unable to create ADRT annotation test database\n");
write_test_database(dbip);
check_geometry_filter(dbip);
check_camera_fit(dbip);
db_close(dbip);
bu_file_delete(path);
return 0;
}
/*
* Local Variables:
* tab-width: 8
* mode: C
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+17 -4
View File
@@ -53,12 +53,23 @@ endif(BRLCAD_ENABLE_TCL)
# Archer plugins
set(
archer_utility_FILES
plugins/Utility/attrGroupsDisplayUtilityP.tcl
plugins/Utility/botUtilityP.tcl
plugins/Utility/lodUtilityP.tcl
plugins/Utility/README
plugins/Utility/attrGroupsDisplayUtilityP.tcl
plugins/Utility/attrGroupsDisplayUtilityP/AttrGroupsDisplayUtilityP.tcl
plugins/Utility/botUtilityP.tcl
plugins/Utility/botUtilityP/BotUtilityP.tcl
plugins/Utility/lodUtilityP.tcl
plugins/Utility/lodUtilityP/LODUtilityP.tcl
)
set(archer_wizard_FILES
plugins/Wizards/humanwizard.tcl
plugins/Wizards/humanwizard/HumanWizard.tcl
plugins/Wizards/tankwizard.tcl
plugins/Wizards/tankwizard/TankWizard.tcl
plugins/Wizards/tankwizard/images/tank.png
plugins/Wizards/tirewizard.tcl
plugins/Wizards/tirewizard/TireWizard.tcl
)
set(archer_wizard_FILES plugins/Wizards/humanwizard.tcl plugins/Wizards/tankwizard.tcl plugins/Wizards/tirewizard.tcl)
if(BRLCAD_ENABLE_TCL)
brlcad_adddata(archer_utility_FILES plugins/archer/Utility)
@@ -77,6 +88,8 @@ endif(BRLCAD_ENABLE_TCL)
set(
archer_ignore_files
CMakeLists.txt
plugins/Commands/README
plugins/Core/README
${archer_utility_FILES}
${archer_wizard_FILES}
${archer_txt}
+480 -399
View File
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -64,17 +64,16 @@ ArtTileCallback::on_tile_end(const renderer::Frame* frame, const size_t tile_x,
{
foundation::Tile& t = frame->image().tile(tile_x, tile_y);
const foundation::Tile rgb(t, PixelFormatUInt8);
const foundation::CanvasProperties& properties = frame->image().properties();
// fb_write(fbp, tile_x, tile_y, rgb.get_storage(), rgb.get_size());
// printf("yay!\n");
// printf("%lu %lu \n", tile_x, tile_y);
// printf("%lu %lu \n", rgb.get_width(), rgb.get_height());
for (size_t y = 0; y < rgb.get_height(); y++) {
for (size_t x = 0; x < rgb.get_width(); x++) {
size_t x_coord = tile_x * properties.m_tile_width + x;
size_t y_coord = tile_y * properties.m_tile_height + y;
size_t img_h = properties.m_canvas_height;
fb_write(fbp, (int)x_coord, (int)(img_h - y_coord - 1), rgb.get_storage()+((y * rgb.get_width() * 4) + (x * 4)), 1);
size_t x_coord = tile_x * rgb.get_width() + x;
size_t y_coord = tile_y * rgb.get_height() + y;
size_t img_h = frame->image().properties().m_canvas_height;
fb_write(fbp, (int)x_coord, (int)(img_h - y_coord), rgb.get_storage()+((y * rgb.get_width() * 4) + (x * 4)), 1);
}
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ if (TARGET ged-check)
endif()
brlcad_addexec(gex "gex.cpp" "libbu;librt")
brlcad_addexec(glint glint.cpp "librt;${M_LIBRARY}")
brlcad_addexec(gqa gqa.c "libged;librt")
brlcad_addexec(gqa gqa.c "libged;libanalyze;librt")
brlcad_addexec(gtransfer gtransfer.c "librt;libpkg" NO_INSTALL)
+25 -2
View File
@@ -21,8 +21,6 @@
*
* Perform quantitative analysis checks on geometry.
*
* XXX need to look at gap computation
*
* plot the points where overlaps start/stop
*
* Designed to be a framework for 3d sampling of the geometry volume.
@@ -30,6 +28,7 @@
#include "common.h"
#include "analyze/gqa.h"
#include "bu/app.h"
#include "bu/cmd.h"
#include "bu/getopt.h"
@@ -51,6 +50,30 @@ main(int argc, char *argv[])
bu_opterr = 0;
bu_optind = 1;
if (argc > 1 && bu_strcmp(argv[1], "--analyze") == 0) {
int model_index = analyze_gqa_model_argument(argc,
(const char **)argv);
if (model_index < 0 || model_index + 1 >= argc)
bu_exit(1, "Usage: %s --analyze [options] model object [objects...]\n", argv[0]);
av = (const char **)bu_calloc(argc, sizeof(char *), "analysis argv");
for (i = j = 0; i < argc; i++) {
if (i != model_index)
av[j++] = argv[i];
}
av[0] = "gqa";
gedp = ged_open("db", argv[model_index], 1);
if (gedp == GED_NULL) {
bu_free((void *)av, "analysis argv");
bu_exit(1, "Cannot open %s\n", argv[model_index]);
}
c = ged_exec_gqa(gedp, j, av);
if (bu_vls_strlen(gedp->ged_result_str) > 0)
bu_log("%s", bu_vls_addr(gedp->ged_result_str));
ged_close(gedp);
bu_free((void *)av, "analysis argv");
return c == BRLCAD_OK ? 0 : 1;
}
/* Get past command line options. */
while ((c = bu_getopt(argc, argv, "A:a:de:f:g:Gn:N:p:P:qrS:t:U:u:vV:W:h?")) != -1) {
switch (c) {
+1
View File
@@ -42,6 +42,7 @@ if(BRLCAD_ENABLE_QT AND OPENGL_TARGETS)
Qt6::Widgets
Qt6::OpenGL
Qt6::OpenGLWidgets
${OPENGL_TARGETS}
)
else(Qt6Widgets_FOUND)
qt5_wrap_cpp(moc_srcs ${isst_headers})
+15 -52
View File
@@ -26,7 +26,6 @@
#include <QFile>
#include <QPlainTextEdit>
#include <QTextStream>
#include <vector>
#include "gfile.h"
#include "bu/malloc.h"
@@ -34,7 +33,6 @@
#include "gcv.h"
#include "nmg.h"
#include "rt/geom.h"
#include "load_g.h"
/* Replace the load_g globals from adrt with a struct that is
* passed through the callers. */
@@ -104,8 +102,7 @@ nmg_to_adrt_internal(TIE_3 **tribuf, struct tie_s *cur_tie, struct adrt_mesh_s *
NMG_CK_VERTEX(v);
/* convert mm to m */
VSCALE((*tribuf[vert_count]).v, v->vg_p->coord,
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[vert_count]).v, v->vg_p->coord, 1.0/1000.0);
vert_count++;
}
if (vert_count > 3)
@@ -188,12 +185,9 @@ nmg_to_adrt_regstart(struct db_tree_state *ts, const struct db_full_path *path,
for (i=0;i<bot->num_faces;i++)
{
VSCALE((*d->tribuf[0]).v, (bot->vertices+3*bot->faces[3*i+0]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*d->tribuf[1]).v, (bot->vertices+3*bot->faces[3*i+1]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*d->tribuf[2]).v, (bot->vertices+3*bot->faces[3*i+2]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*d->tribuf[0]).v, (bot->vertices+3*bot->faces[3*i+0]), 1.0/1000.0);
VSCALE((*d->tribuf[1]).v, (bot->vertices+3*bot->faces[3*i+1]), 1.0/1000.0);
VSCALE((*d->tribuf[2]).v, (bot->vertices+3*bot->faces[3*i+2]), 1.0/1000.0);
TIE_VAL(tie_push)(d->cur_tie, d->tribuf, 1, mesh, 0);
}
@@ -239,13 +233,6 @@ nmg_to_adrt_gcvwrite(struct nmgregion *r, const struct db_full_path *pathp, stru
nmg_to_adrt_internal(d->tribuf, d->cur_tie, mesh, r);
}
GFile::~GFile()
{
closedb();
}
int
GFile::load_g(const char *filename, int argc, const char *argv[])
{
@@ -254,9 +241,6 @@ GFile::load_g(const char *filename, int argc, const char *argv[])
struct bg_tess_tol ttol; /* tessellation tolerance in mm */
struct db_tree_state tree_state; /* includes tol & model */
struct isst_nmg_data d;
std::vector<const char *> geometry_paths;
closedb();
RT_DBTS_INIT(&tree_state);
tree_state.ts_tol = &tol;
@@ -294,8 +278,6 @@ GFile::load_g(const char *filename, int argc, const char *argv[])
}
if (db_dirbuild(dbip)) {
bu_log("ERROR: db_dirbuild failed\n");
db_close(dbip);
dbip = NULL;
return -1;
}
d.dbip = dbip;
@@ -322,30 +304,20 @@ GFile::load_g(const char *filename, int argc, const char *argv[])
tribuf[2] = (TIE_3 *)bu_malloc(sizeof(TIE_3) * 3, "triangle tribuffer");
d.tribuf = this->tribuf;
annotations = rt_annot_scene_create(dbip, argc, argv, &ttol, &tol);
for (int i = 0; i < argc; ++i) {
if (adrt_path_has_geometry(dbip, argv[i]))
geometry_paths.push_back(argv[i]);
}
if (!geometry_paths.empty()) {
(void) db_walk_tree(dbip,
(int)geometry_paths.size(), /* number of toplevel regions */
geometry_paths.data(), /* region names */
1, /* ncpu */
&tree_state, /* initial tree state */
nmg_to_adrt_regstart, /* region start function */
gcv_region_end, /* region end function */
adrt_leaf_tess, /* leaf func */
(void *)&gcvwriter); /* client data */
}
(void) db_walk_tree(dbip,
argc, /* number of toplevel regions */
argv, /* region names */
1, /* ncpu */
&tree_state, /* initial tree state */
nmg_to_adrt_regstart, /* region start function */
gcv_region_end, /* region end function */
rt_booltree_leaf_tess, /* leaf func */
(void *)&gcvwriter); /* client data */
/* Release dynamic storage */
nmg_km(the_model);
rt_vlist_cleanup();
db_close(dbip);
dbip = NULL;
bu_free(tribuf[0], "vert");
bu_free(tribuf[1], "vert");
bu_free(tribuf[2], "vert");
@@ -359,17 +331,7 @@ GFile::load_g(const char *filename, int argc, const char *argv[])
void
GFile::closedb()
{
rt_annot_scene_destroy(annotations);
annotations = NULL;
if (tie) {
TIE_FREE(tie);
bu_free(tie, "free tie");
tie = NULL;
}
if (dbip) {
db_close(dbip);
dbip = NULL;
}
bu_free(tie, "free tie");
current_file.clear();
}
@@ -383,3 +345,4 @@ GFile::closedb()
* End:
* ex: shiftwidth=4 tabstop=8
*/
+5 -8
View File
@@ -32,7 +32,6 @@
extern "C" {
#include "bn/tol.h"
#include "raytrace.h"
#include "rt/primitives/annot.h"
#include "rt/tie.h"
#include "adrt.h"
@@ -44,16 +43,13 @@ class GFile : public QObject
Q_OBJECT
public:
~GFile() override;
int load_g(const char *filename, int argc, const char **argv);
void closedb();
struct tie_s *tie = NULL;
struct adrt_mesh_s *meshes = NULL;
struct db_i *dbip = NULL;
struct rt_annot_scene *annotations = NULL;
TIE_3 **tribuf = NULL;
struct tie_s *tie;
struct adrt_mesh_s *meshes;
struct db_i *dbip;
TIE_3 **tribuf;
QString current_file;
};
@@ -70,3 +66,4 @@ class GFile : public QObject
* End:
* ex: shiftwidth=4 tabstop=8
*/
+2 -4
View File
@@ -28,9 +28,6 @@
int
ISSTApp::load_g(const char *filename, int argc, const char *argv[])
{
/* Synchronize with the render thread before GFile releases the old
* annotation scene and TIE data. */
w.canvas->clear_tie();
if (g.load_g(filename, argc, argv)) {
w.statusBar()->showMessage("open failed");
return -1;
@@ -38,7 +35,7 @@ ISSTApp::load_g(const char *filename, int argc, const char *argv[])
// The OpenGL widget manages the rendering, so let it know about the
// TIE data structure associated with the current model
w.canvas->set_tie(g.tie, g.annotations);
w.canvas->set_tie(g.tie);
return 0;
}
@@ -54,3 +51,4 @@ ISSTApp::load_g(const char *filename, int argc, const char *argv[])
* End:
* ex: shiftwidth=4 tabstop=8
*/
+4 -1
View File
@@ -36,14 +36,16 @@
class ISSTApp : public QApplication
{
Q_OBJECT
public:
ISSTApp(int &argc, char *argv[]) :QApplication(argc, argv) {};
~ISSTApp() {};
int load_g(const char *filename, int argc, const char **argv);
GFile g;
ISST_MainWindow w;
GFile g;
};
#endif // ISSTAPP_H
@@ -58,3 +60,4 @@ class ISSTApp : public QApplication
* End:
* ex: shiftwidth=4 tabstop=8
*/
+9 -28
View File
@@ -159,13 +159,8 @@ void TIERenderer::render()
m_grabCond.wait(&m_grabMutex);
QMutexLocker lock(&m_renderMutex);
m_grabMutex.unlock();
/* The GUI may clear a scene while a queued render is waiting for the
* context. Return the context before leaving that stale request. */
if (m_exiting || !tie) {
ctx->moveToThread(qGuiApp->thread());
if (m_exiting)
return;
}
Q_ASSERT(ctx->thread() == QThread::currentThread());
@@ -188,8 +183,7 @@ void TIERenderer::render()
// Core TIE render
render_camera_prep(&camera);
render_camera_render_annotations(&camera, tie, &tile, &buffer_image,
annotations, ADRT_MODEL_UNITS_PER_TIE_UNIT);
render_camera_render(&camera, tie, &tile, &buffer_image);
glDisable(GL_LIGHTING);
@@ -303,31 +297,17 @@ void isstGL::grabContext()
}
void
isstGL::clear_tie()
isstGL::set_tie(struct tie_s *in_tie)
{
m_renderer->lockRenderer();
m_renderer->tie = NULL;
m_renderer->annotations = NULL;
m_renderer->changed = false;
m_renderer->unlockRenderer();
}
void
isstGL::set_tie(struct tie_s *in_tie, struct rt_annot_scene *annotations)
{
m_renderer->lockRenderer();
m_renderer->tie = in_tie;
m_renderer->annotations = annotations;
render_camera_fit_scene(&m_renderer->camera, m_renderer->tie, annotations,
ADRT_MODEL_UNITS_PER_TIE_UNIT);
// Initialize the camera position
VSETALL(m_renderer->camera.pos, m_renderer->tie->radius);
VMOVE(m_renderer->camera.focus, m_renderer->tie->mid);
// Record the initial settings for use in subsequent calculations
VMOVE(m_renderer->camera_pos_init, m_renderer->camera.pos);
VMOVE(m_renderer->camera_focus_init, m_renderer->camera.focus);
m_renderer->changed = true;
m_renderer->unlockRenderer();
VSETALL(m_renderer->camera_pos_init, m_renderer->tie->radius);
VMOVE(m_renderer->camera_focus_init, m_renderer->tie->mid);
// Having just loaded a new TIE scene,
// we need a new image
@@ -389,3 +369,4 @@ void isstGL::save_image() {
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+2 -3
View File
@@ -77,7 +77,6 @@ class TIERenderer : public QObject, protected QOpenGLFunctions
public:
struct tie_s *tie = NULL; // From parent app
struct rt_annot_scene *annotations = NULL; // Owned by the parent app
struct render_camera_s camera;
vect_t camera_pos_init;
vect_t camera_focus_init;
@@ -114,8 +113,7 @@ class isstGL : public QOpenGLWidget
explicit isstGL(QWidget *parent = nullptr);
~isstGL();
void clear_tie();
void set_tie(struct tie_s *in_tie, struct rt_annot_scene *annotations);
void set_tie(struct tie_s *in_tie);
void save_image();
@@ -155,3 +153,4 @@ class isstGL : public QOpenGLWidget
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+4 -1
View File
@@ -29,8 +29,10 @@ set(
api.c
centroid.c
check_options.c
contour.cpp
density.cpp
globals.c
gqa.cpp
inside.c
mass.c
moments.c
@@ -41,6 +43,7 @@ set(
overlaps.c
polygonizer.c
raydiff.c
sampling.c
#find_subtracted_shapes.cpp
surf_area.c
util.cpp
@@ -68,7 +71,7 @@ set(
cmakefiles(${libanalyze_ignored_files})
# Note - libanalyze_deps is defined by ${BRLCAD_SOURCE_DIR}/src/source_dirs.cmake
brlcad_addlib(libanalyze "${LIBANALYZE_SOURCES}" "" "${CMAKE_CURRENT_BINARY_DIR}"
brlcad_addlib(libanalyze "${LIBANALYZE_SOURCES}" "" "${CMAKE_CURRENT_BINARY_DIR};${EIGEN3_INCLUDE_DIR}"
PUBLIC_LIBS ${libanalyze_deps}
)
set_target_properties(libanalyze PROPERTIES VERSION 20.0.1 SOVERSION 20)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3 -196
View File
@@ -480,22 +480,6 @@ _nirt_get_resource(struct nirt_state *nss)
}
static void
_nirt_annotation_result_clear(struct nirt_state *nss)
{
delete nss->i->vals->annotation;
nss->i->vals->annotation = NULL;
}
static void
_nirt_annotation_scene_clear(struct nirt_state *nss)
{
rt_annot_scene_destroy(nss->i->annotation_scene);
nss->i->annotation_scene = NULL;
}
int
_nirt_raytrace_prep(struct nirt_state *nss)
{
@@ -533,17 +517,11 @@ _nirt_raytrace_prep(struct nirt_state *nss)
bu_free(attrs, "objs");
return -1;
}
bu_free(objs, "objs");
bu_free(attrs, "objs");
nmsg(nss, "Prepping the geometry...\n");
rt_prep(nss->i->ap->a_rt_i);
_nirt_annotation_scene_clear(nss);
if (nss->i->use_annotations) {
nss->i->annotation_scene = rt_annot_scene_create(nss->i->dbip,
ocnt, objs, &nss->i->ap->a_rt_i->rti_ttol,
&nss->i->ap->a_rt_i->rti_tol);
}
bu_free(objs, "objs");
nmsg(nss, "%s ", (nss->i->active_paths.size() == 1) ? "Object" : "Objects");
for (size_t i = 0; i < nss->i->active_paths.size(); i++) {
if (i == 0) {
@@ -982,17 +960,6 @@ _nirt_print_fmt_substr(struct nirt_state *nss, struct bu_vls *ostr, const char *
nirt_print_key("a", r->a);
nirt_print_key("e", r->e);
if (r->annotation) {
nirt_print_key("annot_path", r->annotation->path.c_str());
nirt_print_key("annot_segment", r->annotation->segment);
nirt_print_key("annot_role", r->annotation->role.c_str());
nirt_print_key("annot_x", r->annotation->point[X] * base2local);
nirt_print_key("annot_y", r->annotation->point[Y] * base2local);
nirt_print_key("annot_z", r->annotation->point[Z] * base2local);
nirt_print_key("annot_d", r->annotation->d * base2local);
nirt_print_key("annot_visible", r->annotation->visible);
}
if (!r->seg || r->seg->type == 0) return;
if (r->seg->type == NIRT_PARTITION_SEG || r->seg->type == NIRT_ALL_SEG) {
@@ -1089,9 +1056,6 @@ _nirt_report(struct nirt_state *nss, char type, struct nirt_output_record *r)
std::vector<std::pair<std::string,std::string> >::iterator f_it;
switch (type) {
case 'a':
fmt_vect = &nss->i->fmt.annotation;
break;
case 'r':
fmt_vect = &nss->i->fmt.ray;
break;
@@ -1127,75 +1091,6 @@ _nirt_report(struct nirt_state *nss, char type, struct nirt_output_record *r)
}
static const char *
_nirt_annotation_role(uint32_t role)
{
switch (role) {
case RT_ANNOT_ROLE_UNSPECIFIED:
return "unspecified";
case RT_ANNOT_ROLE_GEOMETRY:
return "geometry";
case RT_ANNOT_ROLE_TEXT:
return "text";
case RT_ANNOT_ROLE_LEADER:
return "leader";
case RT_ANNOT_ROLE_EXTENSION:
return "extension";
case RT_ANNOT_ROLE_DIMENSION:
return "dimension";
case RT_ANNOT_ROLE_ARROWHEAD:
return "arrowhead";
case RT_ANNOT_ROLE_SYMBOL:
return "symbol";
case RT_ANNOT_ROLE_MASK:
return "mask";
case RT_ANNOT_ROLE_CENTERMARK:
return "centermark";
case RT_ANNOT_ROLE_PLACEHOLDER:
return "placeholder";
case RT_ANNOT_ROLE_TEXT_DECORATION:
return "text-decoration";
default:
return "unknown";
}
}
static void
_nirt_report_annotations(struct nirt_state *nss)
{
const size_t count = rt_annot_scene_query_model(nss->i->annotation_scene,
&nss->i->ap->a_ray, nss->i->first_solid_distance, NULL, 0);
if (!count)
return;
std::vector<struct rt_annot_hit> hits(count);
const size_t found = rt_annot_scene_query_model(nss->i->annotation_scene,
&nss->i->ap->a_ray, nss->i->first_solid_distance, hits.data(),
hits.size());
nirt_seg *saved_segment = nss->i->vals->seg;
nss->i->vals->seg = NULL;
for (size_t i = 0; i < std::min(found, hits.size()); ++i) {
const struct rt_annot_hit &hit = hits[i];
if (hit.segment > (size_t)std::numeric_limits<int>::max()) {
nerr(nss, "Annotation segment index is too large for NIRT reporting\n");
continue;
}
if (!nss->i->vals->annotation)
nss->i->vals->annotation = new nirt_annotation_record;
nirt_annotation_record *record = nss->i->vals->annotation;
record->path = hit.path ? hit.path : "";
record->segment = (int)hit.segment;
record->role = _nirt_annotation_role(hit.role);
VMOVE(record->point, hit.point);
record->d = d_calc(nss, record->point);
record->visible = hit.visible;
_nirt_report(nss, 'a', nss->i->vals);
}
nss->i->vals->seg = saved_segment;
}
/************************
* Raytracing Callbacks *
************************/
@@ -1318,21 +1213,6 @@ _nirt_if_hit(struct application *ap, struct partition *part_head, struct seg *UN
int ev_odd = 1; /* first partition is colored as "odd" */
point_t out_old = VINIT_ZERO;
double d_out_old = 0.0;
nss->i->first_solid_distance = INFINITY;
for (part = part_head->pt_forw; part != part_head; part = part->pt_forw) {
if (part->pt_regionp->reg_aircode)
continue;
if (part->pt_inhit->hit_dist >= 0.0) {
nss->i->first_solid_distance = part->pt_inhit->hit_dist;
break;
}
if (part->pt_outhit->hit_dist >= 0.0) {
nss->i->first_solid_distance = part->pt_outhit->hit_dist;
break;
}
}
nirt_seg *s = new nirt_seg;
if (vals->seg) {
delete vals->seg;
@@ -1669,10 +1549,9 @@ static const struct nirt_cmd_desc nirt_descs[] = {
{ "s", "shoot a ray at the target", NULL },
{ "backout", "back out of model", NULL },
{ "useair", "set/query use of air", "<0|1|2|...>" },
{ "useannot", "set/query annotation reporting", "<0|1>" },
{ "units", "set/query local units", "<mm|cm|m|in|ft>" },
{ "overlap_claims", "set/query overlap rebuilding/retention", "<0|1|2|3>" },
{ "fmt", "set/query output formats", "{arhpfmog} format item item ..." },
{ "fmt", "set/query output formats", "{rhpfmog} format item item ..." },
{ "plotfile", "designate an output file to hold plot data", "file" },
{ "print", "query an output item", "item" },
{ "debug", "set/query nirt debug flags", "[-h] [-l [lib]] [-C [lib]] [-V [lib] [val]] [lib [flag]]" },
@@ -1949,9 +1828,6 @@ _nirt_cmd_shoot(void *ns, int argc, const char **UNUSED(argv))
return -1;
}
_nirt_annotation_result_clear(nss);
nss->i->first_solid_distance = INFINITY;
/* If we have no active rtip, we don't need to prep or shoot */
if (!_nirt_get_rtip(nss)) {
return 0;
@@ -1984,10 +1860,9 @@ _nirt_cmd_shoot(void *ns, int argc, const char **UNUSED(argv))
nss->i->ap->a_ray.r_pt[2] * nss->i->base2local,
V3ARGS(nss->i->ap->a_ray.r_dir));
// TODO - any necessary initialization for data collection by callbacks
_nirt_init_ovlp(nss);
(void)rt_shootray(nss->i->ap);
if (nss->i->use_annotations)
_nirt_report_annotations(nss);
// Undo backout
for (i = 0; i < 3; ++i) {
@@ -2090,45 +1965,6 @@ _nirt_cmd_use_air(void *ns, int argc, const char *argv[])
}
extern "C" int
_nirt_cmd_use_annotations(void *ns, int argc, const char *argv[])
{
struct nirt_state *nss = (struct nirt_state *)ns;
struct bu_vls optparse_msg = BU_VLS_INIT_ZERO;
int enabled = 0;
if (!ns) return -1;
if (argc == 1) {
nout(nss, "use_annotations = %d\n", nss->i->use_annotations);
return 0;
}
if (argc > 2) {
nerr(nss, "Usage: useannot %s\n", _nirt_get_desc_args("useannot"));
return -1;
}
if (bu_opt_int(&optparse_msg, 1, (const char **)&argv[1],
(void *)&enabled) == -1) {
nerr(nss, "Error: bu_opt value read failure: %s\n\nUsage: useannot %s\n",
bu_vls_cstr(&optparse_msg), _nirt_get_desc_args("useannot"));
bu_vls_free(&optparse_msg);
return -1;
}
bu_vls_free(&optparse_msg);
if (enabled != 0 && enabled != 1) {
nerr(nss, "useannot must be set to 0 (off) or 1 (on)\n");
return -1;
}
if (enabled != nss->i->use_annotations) {
nss->i->use_annotations = enabled;
_nirt_annotation_scene_clear(nss);
_nirt_annotation_result_clear(nss);
if (enabled)
nss->i->need_reprep = 1;
}
return 0;
}
extern "C" int
_nirt_cmd_units(void *ns, int argc, const char *argv[])
{
@@ -2249,9 +2085,6 @@ _nirt_cmd_format_output(void *ns, int argc, const char **argv)
// print out current fmt str for the specified type
struct bu_vls ostr = BU_VLS_INIT_ZERO;
switch (type) {
case 'a':
_nirt_print_fmt_str(&ostr, nss->i->fmt.annotation);
break;
case 'r':
_nirt_print_fmt_str(&ostr, nss->i->fmt.ray);
break;
@@ -2358,10 +2191,6 @@ _nirt_cmd_format_output(void *ns, int argc, const char **argv)
* formatting instructions with the new. */
set_fmt:
switch (type) {
case 'a':
nss->i->fmt.annotation.clear();
nss->i->fmt.annotation = fmt_tmp;
break;
case 'r':
nss->i->fmt.ray.clear();
nss->i->fmt.ray = fmt_tmp;
@@ -2714,7 +2543,6 @@ _nirt_cmd_state(void *ns, int argc, const char *argv[])
bu_vls_printf(&dumpstr, "xyz %g %g %g\n", V3ARGS(nss->i->vals->orig));
bu_vls_printf(&dumpstr, "dir %g %g %g\n", V3ARGS(nss->i->vals->dir));
bu_vls_printf(&dumpstr, "useair %d\n", nss->i->use_air);
bu_vls_printf(&dumpstr, "useannot %d\n", nss->i->use_annotations);
bu_vls_printf(&dumpstr, "units %s\n", bu_units_string(nss->i->local2base));
bu_vls_printf(&dumpstr, "overlap_claims ");
switch (nss->i->overlap_claims) {
@@ -2735,8 +2563,6 @@ _nirt_cmd_state(void *ns, int argc, const char *argv[])
break;
}
struct bu_vls ostr = BU_VLS_INIT_ZERO;
_nirt_print_fmt_cmd(&ostr, 'a', nss->i->fmt.annotation);
bu_vls_printf(&dumpstr, "%s", bu_vls_cstr(&ostr));
_nirt_print_fmt_cmd(&ostr, 'r', nss->i->fmt.ray);
bu_vls_printf(&dumpstr, "%s", bu_vls_cstr(&ostr));
_nirt_print_fmt_cmd(&ostr, 'h', nss->i->fmt.head);
@@ -2791,7 +2617,6 @@ const struct bu_cmdtab _libanalyze_nirt_cmds[] = {
{ "state", _nirt_cmd_state},
{ "units", _nirt_cmd_units},
{ "useair", _nirt_cmd_use_air},
{ "useannot", _nirt_cmd_use_annotations},
{ "xyz", _nirt_cmd_target_coor},
{ (char *)NULL, NULL}
};
@@ -2958,14 +2783,6 @@ static const char *nirt_cmd_tbl_defs =
"z_dir, FNOUNIT, Ray direction unit vector z component,"
"a, FNOUNIT, Azimuth,"
"e, FNOUNIT, Elevation,"
"annot_path, STRING, Annotation database path,"
"annot_segment, INT, Annotation segment index,"
"annot_role, STRING, Annotation segment role,"
"annot_x, FLOAT, Annotation hit X coordinate,"
"annot_y, FLOAT, Annotation hit Y coordinate,"
"annot_z, FLOAT, Annotation hit Z coordinate,"
"annot_d, FLOAT, Annotation hit gridplane distance,"
"annot_visible, INT, Annotation hit visibility,"
"x_in, FLOAT, ,"
"y_in, FLOAT, ,"
"z_in, FLOAT, ,"
@@ -3078,7 +2895,6 @@ nirt_init(struct nirt_state *ns)
n->base2local = 0.0;
n->local2base = 0.0;
n->use_air = 0;
n->use_annotations = 0;
n->backout = 1;
n->b_state = false;
@@ -3093,8 +2909,6 @@ nirt_init(struct nirt_state *ns)
BU_GET(n->res_air, struct resource);
n->rtip = RTI_NULL;
n->rtip_air = RTI_NULL;
n->annotation_scene = NULL;
n->first_solid_distance = INFINITY;
n->need_reprep = 1;
BU_GET(n->val_types, struct bu_attribute_value_set);
@@ -3108,7 +2922,6 @@ nirt_init(struct nirt_state *ns)
n->vals->h = 0.0;
n->vals->v = 0.0;
n->vals->d_orig = 0.0;
n->vals->annotation = NULL;
n->vals->seg = NULL;
_nirt_diff_create(ns);
@@ -3212,8 +3025,6 @@ nirt_clear_dbip(struct nirt_state *ns)
{
if (!ns) return -1;
_nirt_annotation_scene_clear(ns);
_nirt_annotation_result_clear(ns);
db_close(ns->i->dbip);
ns->i->dbip = NULL;
@@ -3234,9 +3045,6 @@ void
nirt_destroy(struct nirt_state *ns)
{
if (!ns) return;
_nirt_annotation_scene_clear(ns);
_nirt_annotation_result_clear(ns);
bu_vls_free(&ns->nirt_cmd);
bu_vls_free(ns->i->err);
bu_vls_free(ns->i->msg);
@@ -3249,7 +3057,6 @@ nirt_destroy(struct nirt_state *ns)
db_close(ns->i->dbip);
delete ns->i->vals->seg;
BU_PUT(ns->i->vals, struct nirt_output_record);
BU_PUT(ns->i->res, struct resource);
+1 -17
View File
@@ -83,7 +83,7 @@ extern "C" b_off_t ftello(FILE *);
#define NIRT_OVLP_RETAIN 3
#define NIRT_PRINTF_SPECIFIERS "difeEgGs"
#define NIRT_OUTPUT_TYPE_SPECIFIERS "arhpfmog"
#define NIRT_OUTPUT_TYPE_SPECIFIERS "rhpfmog"
#define HELPFLAG "--print-help"
#define PURPOSEFLAG "--print-purpose"
@@ -274,16 +274,6 @@ class nirt_seg {
}
};
class nirt_annotation_record {
public:
std::string path;
int segment;
std::string role;
point_t point;
fastf_t d;
int visible;
};
/* This record structure doesn't have to correspond exactly to the above list
* of available values, but it needs to retain sufficient information to
* support the ability to generate all of them upon request. */
@@ -296,7 +286,6 @@ struct nirt_output_record {
fastf_t a;
fastf_t e;
struct nirt_overlap ovlp_list;
nirt_annotation_record *annotation;
nirt_seg *seg;
};
@@ -305,7 +294,6 @@ struct nirt_diff_state;
class nirt_fmt_state {
public:
std::vector<std::pair<std::string,std::string> > annotation;
std::vector<std::pair<std::string,std::string> > ray;
std::vector<std::pair<std::string,std::string> > head;
std::vector<std::pair<std::string,std::string> > part;
@@ -315,7 +303,6 @@ class nirt_fmt_state {
std::vector<std::pair<std::string,std::string> > gap;
void clear() {
annotation.clear();
ray.clear();
head.clear();
part.clear();
@@ -360,7 +347,6 @@ struct nirt_state_impl {
int backout;
int overlap_claims;
int use_air;
int use_annotations;
std::set<std::string> attrs; // active attributes
std::vector<std::string> active_paths; // active paths for raytracer
struct nirt_output_record *vals;
@@ -382,8 +368,6 @@ struct nirt_state_impl {
struct resource *res;
struct rt_i *rtip_air;
struct resource *res_air;
struct rt_annot_scene *annotation_scene;
fastf_t first_solid_distance;
int need_reprep;
/* internal format specifier arrays */
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
/* S A M P L I N G . C
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*/
#include "common.h"
#include <float.h>
#include <math.h>
#include "analyze/sampling.h"
/*
* Owen recommends at least ten independent randomized-QMC replications and
* an ordinary Student-t interval across their estimates. See A. B. Owen,
* "Error estimation for quasi-Monte Carlo" (2025), section 5,
* https://arxiv.org/abs/2501.00150v3.
*
* Using t(0.975, 9) for all R >= 10 is conservative relative to the smaller
* critical value for R - 1 degrees of freedom and avoids making the public
* API depend on a special-functions package.
*/
#define ANALYZE_STUDENT_T_95_DF9 2.2621571627409915
int
analyze_sampling_interval95(struct analyze_sampling_interval *interval,
const double *replicate_estimates, size_t replicate_count)
{
if (!interval || !replicate_estimates ||
replicate_count < ANALYZE_SAMPLING_MIN_REPLICATES)
return ANALYZE_ERROR;
double sum = 0.0;
for (size_t i = 0; i < replicate_count; i++) {
if (!isfinite(replicate_estimates[i])) return ANALYZE_ERROR;
sum += replicate_estimates[i];
}
interval->estimate = sum / (double)replicate_count;
double squared_deviation = 0.0;
for (size_t i = 0; i < replicate_count; i++) {
const double deviation = replicate_estimates[i] - interval->estimate;
squared_deviation += deviation * deviation;
}
const double variance = squared_deviation / (double)(replicate_count - 1);
interval->half_width = ANALYZE_STUDENT_T_95_DF9 *
sqrt(variance / (double)replicate_count);
return isfinite(interval->estimate) && isfinite(interval->half_width) ?
ANALYZE_OK : ANALYZE_ERROR;
}
int
analyze_sampling_ratio_interval95(struct analyze_sampling_interval *interval,
const double *replicate_numerators,
const double *replicate_denominators, size_t replicate_count)
{
if (!interval || !replicate_numerators || !replicate_denominators ||
replicate_count < ANALYZE_SAMPLING_MIN_REPLICATES)
return ANALYZE_ERROR;
double numerator_sum = 0.0;
double denominator_sum = 0.0;
for (size_t i = 0; i < replicate_count; i++) {
if (!isfinite(replicate_numerators[i]) ||
!isfinite(replicate_denominators[i]))
return ANALYZE_ERROR;
numerator_sum += replicate_numerators[i];
denominator_sum += replicate_denominators[i];
}
const double mean_numerator = numerator_sum / (double)replicate_count;
const double mean_denominator = denominator_sum / (double)replicate_count;
if (!isfinite(mean_numerator) || !isfinite(mean_denominator) ||
fabs(mean_denominator) <= DBL_MIN)
return ANALYZE_ERROR;
interval->estimate = mean_numerator / mean_denominator;
double squared_residual = 0.0;
for (size_t i = 0; i < replicate_count; i++) {
/* Linearization of Xbar/Ybar retains the paired X,Y covariance. */
const double residual = (replicate_numerators[i] -
interval->estimate * replicate_denominators[i]) / mean_denominator;
squared_residual += residual * residual;
}
const double variance = squared_residual /
(double)(replicate_count - 1);
interval->half_width = ANALYZE_STUDENT_T_95_DF9 *
sqrt(variance / (double)replicate_count);
return isfinite(interval->estimate) && isfinite(interval->half_width) ?
ANALYZE_OK : ANALYZE_ERROR;
}
/*
* Local Variables:
* tab-width: 8
* mode: C
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+7 -3
View File
@@ -1,9 +1,10 @@
brlcad_addexec(analyze_contour contour.cpp "libanalyze;libwdb;librt;libbg;libbu" TEST)
brlcad_add_test(NAME analyze_contour COMMAND analyze_contour)
brlcad_addexec(analyze_raydiff raydiff.c "libanalyze;librt;libbu" TEST)
brlcad_addexec(analyze_sp solid_partitions.c "libanalyze;librt;libbu" TEST)
brlcad_addexec(analyze_nhit nhit.cpp "libanalyze;librt;libbu" TEST_USESDATA)
brlcad_addexec(analyze_nirt_annotations nirt_annotations.cpp
"libanalyze;libwdb;librt;libbu" TEST_USESDATA)
brlcad_add_test(NAME analyze_nirt_annotations COMMAND analyze_nirt_annotations)
brlcad_addexec(analyze_sampling sampling_test.c "libanalyze;libbn" TEST)
#####################################
# analyze_densities testing #
@@ -12,11 +13,14 @@ brlcad_addexec(analyze_densities density.cpp "libanalyze;libbu" TEST)
brlcad_add_test(NAME analyze_densities_null COMMAND analyze_densities)
brlcad_add_test(NAME analyze_densities_std COMMAND analyze_densities std)
brlcad_add_test(NAME analyze_sampling_interval COMMAND analyze_sampling)
cmakefiles(
CMakeLists.txt
arbs.g
contour.cpp
raydiff.g
sampling_test.c
)
# Local Variables:
+425
View File
@@ -0,0 +1,425 @@
/* A N A L Y Z E _ C O N T O U R . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED.
*/
/** @file contour.cpp
*
* Basic topology and resource-limit tests for analyze_mdc().
*/
#include "common.h"
#include <algorithm>
#include <climits>
#include <cmath>
#include <cstdio>
#include <set>
#include <utility>
#include "bu/app.h"
#include "bu/malloc.h"
#include "bg/trimesh.h"
#include "analyze/contour.h"
#include "analyze/polygonize.h"
#include "raytrace.h"
#include "wdb.h"
static constexpr double METRIC_RELATIVE_TOLERANCE = 0.10;
static int failures = 0;
static void
expect(bool condition, const char *message)
{
if (condition)
return;
std::fprintf(stderr, "FAIL: %s\n", message);
failures++;
}
static void
expect_metric(double actual, double expected, const char *object,
const char *metric)
{
double relative_error = std::fabs(actual - expected) / expected;
if (actual >= 0.0 && relative_error <= METRIC_RELATIVE_TOLERANCE)
return;
std::fprintf(stderr,
"FAIL: %s %s is %.17g, expected %.17g (relative error %.2f%%)\n",
object, metric, actual, expected, 100.0 * relative_error);
failures++;
}
static void
check_mesh(struct db_i *dbip, const char *object, int expected_components = 1,
double expected_area = 0.0, double expected_volume = 0.0,
size_t minimum_faces = 4, int expected_euler = INT_MAX)
{
int *faces = NULL;
point_t *vertices = NULL;
size_t face_count = 0;
size_t vertex_count = 0;
struct analyze_mdc_params params = ANALYZE_MDC_PARAMS_DEFAULT;
params.min_depth = 4;
params.max_depth = 8;
params.max_rays = 0;
params.minimum_free_mem = 0;
params.max_time = 0;
params.verbosity = 0;
enum analyze_mdc_status status = analyze_mdc(&faces, &face_count,
&vertices, &vertex_count, object, dbip, &params);
expect(status == ANALYZE_MDC_OK, object);
if (status == ANALYZE_MDC_OK) {
expect(vertex_count >= 4, "contour has vertices");
expect(face_count >= minimum_faces, "contour has expected resolution");
expect(bg_trimesh_solid2(static_cast<int>(vertex_count),
static_cast<int>(face_count),
reinterpret_cast<fastf_t *>(vertices), faces, NULL) == 0,
"contour is a closed oriented manifold");
if (expected_euler != INT_MAX) {
std::set<std::pair<int, int>> edges;
for (size_t face = 0; face < face_count; face++) {
for (size_t corner = 0; corner < 3; corner++) {
int a = faces[3 * face + corner];
int b = faces[3 * face + (corner + 1) % 3];
edges.insert(std::minmax(a, b));
}
}
long long euler = static_cast<long long>(vertex_count) -
static_cast<long long>(edges.size()) +
static_cast<long long>(face_count);
expect(euler == expected_euler,
"contour has the expected Euler characteristic");
}
if (expected_area > 0.0)
expect_metric(bg_trimesh_area(faces, face_count, vertices,
vertex_count), expected_area, object, "surface area");
if (expected_volume > 0.0)
expect_metric(bg_trimesh_volume(faces, face_count, vertices,
vertex_count), expected_volume, object, "volume");
int *face_indices = NULL;
int *component_offsets = NULL;
int components = bg_trimesh_separate(&face_indices,
&component_offsets, faces, static_cast<int>(face_count));
expect(components == expected_components,
"contour has the expected connected components");
if (face_indices)
bu_free(face_indices, "MDC test component faces");
if (component_offsets)
bu_free(component_offsets, "MDC test component offsets");
}
if (faces)
bu_free(faces, "MDC test faces");
if (vertices)
bu_free(vertices, "MDC test vertices");
}
static bool
make_region(struct rt_wdb *wdbp, const char *region, const char *solid)
{
struct wmember member;
BU_LIST_INIT(&member.l);
if (!mk_addmember(solid, &member.l, NULL, WMOP_UNION))
return false;
return mk_lcomb(wdbp, region, &member, 1, NULL, NULL, NULL, 0) == 0;
}
int
main(int UNUSED(argc), const char **argv)
{
bu_setprogname(argv[0]);
struct db_i *dbip = db_create_inmem();
expect(dbip != NULL, "create in-memory database");
if (!dbip)
return 1;
struct rt_wdb *wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_INMEM);
expect(wdbp != NULL, "open in-memory database");
if (!wdbp) {
db_close(dbip);
return 1;
}
constexpr double sphere_radius = 10.0;
constexpr double torus_major_radius = 10.0;
constexpr double torus_minor_radius = 2.5;
constexpr double slender_torus_major_radius = 100.0;
constexpr double slender_torus_minor_radius = 2.0;
constexpr double lower_radius = 1.5;
constexpr double upper_radius = 1.0;
point_t center = VINIT_ZERO;
point_t minimum = {-8.0, -6.0, -4.0};
point_t maximum = {8.0, 6.0, 4.0};
expect(mk_sph(wdbp, "sphere.s", center, sphere_radius) == 0,
"create test sphere");
expect(mk_rpp(wdbp, "box.s", minimum, maximum) == 0,
"create test box");
point_t thin_minimum = {-5.0, -2.5, -0.05};
point_t thin_maximum = {5.0, 2.5, 0.05};
expect(mk_rpp(wdbp, "thin.s", thin_minimum, thin_maximum) == 0,
"create thin-wall test box");
point_t tunnel_minimum = {-10.0, -10.0, -10.0};
point_t tunnel_maximum = {10.0, 10.0, 10.0};
point_t tunnel_base = {0.0, 0.0, -11.0};
vect_t tunnel_height = {0.0, 0.0, 22.0};
expect(mk_rpp(wdbp, "tunnel_outer.s", tunnel_minimum,
tunnel_maximum) == 0, "create tunnel test outer box");
expect(mk_rcc(wdbp, "tunnel_void.s", tunnel_base, tunnel_height,
1.0) == 0, "create tunnel test void");
struct wmember tunnel_members;
BU_LIST_INIT(&tunnel_members.l);
expect(mk_addmember("tunnel_outer.s", &tunnel_members.l, NULL,
WMOP_UNION) != NULL, "add tunnel outer box");
expect(mk_addmember("tunnel_void.s", &tunnel_members.l, NULL,
WMOP_SUBTRACT) != NULL, "subtract tunnel void");
expect(mk_lcomb(wdbp, "tunnel.r", &tunnel_members, 1, NULL, NULL,
NULL, 0) == 0, "create tunnel test region");
vect_t normal = {0.0, 0.0, 1.0};
expect(mk_tor(wdbp, "torus.s", center, normal, torus_major_radius,
torus_minor_radius) == 0,
"create test torus");
expect(mk_tor(wdbp, "slender_torus.s", center, normal,
slender_torus_major_radius, slender_torus_minor_radius) == 0,
"create high-aspect torus");
point_t tgc_base = {-4.0, -3.0, -6.0};
vect_t tgc_height = {2.0, 1.0, 12.0};
vect_t tgc_a = {5.0, 0.0, 0.0};
vect_t tgc_b = {0.0, 3.0, 0.0};
vect_t tgc_c = {2.5, 0.0, 0.0};
vect_t tgc_d = {0.0, 1.5, 0.0};
expect(mk_tgc(wdbp, "tgc.s", tgc_base, tgc_height, tgc_a, tgc_b,
tgc_c, tgc_d) == 0, "create test TGC");
point_t concave_arb[8] = {
{500.0, -500.0, -500.0},
{500.0, 500.0, -500.0},
{500.0, -169.922, -304.688},
{500.0, -500.0, 500.0},
{400.0, -500.0, -500.0},
{400.0, 500.0, -500.0},
{400.0, -169.922, -304.688},
{400.0, -500.0, 500.0}
};
expect(mk_arb8(wdbp, "concave.s",
reinterpret_cast<const fastf_t *>(concave_arb)) == 0,
"create sharp concave ARB");
point_t lower_center = {-8.0, -5.0, -3.0};
point_t upper_center = {9.0, 4.0, 6.0};
expect(mk_sph(wdbp, "lower.s", lower_center, lower_radius) == 0,
"create lower disconnected sphere");
expect(mk_sph(wdbp, "upper.s", upper_center, upper_radius) == 0,
"create upper disconnected sphere");
struct wmember members;
BU_LIST_INIT(&members.l);
expect(mk_addmember("lower.s", &members.l, NULL, WMOP_UNION) != NULL,
"add lower disconnected sphere");
expect(mk_addmember("upper.s", &members.l, NULL, WMOP_UNION) != NULL,
"add upper disconnected sphere");
expect(mk_lcomb(wdbp, "disconnected.r", &members, 1, NULL, NULL, NULL,
0) == 0, "create disconnected region");
constexpr double overlap_radius = 5.0;
constexpr double overlap_offset = 3.0;
point_t overlap_left_center = {-overlap_offset, 0.0, 0.0};
point_t overlap_right_center = {overlap_offset, 0.0, 0.0};
expect(mk_sph(wdbp, "overlap_left.s", overlap_left_center,
overlap_radius) == 0, "create left overlapping sphere");
expect(mk_sph(wdbp, "overlap_right.s", overlap_right_center,
overlap_radius) == 0, "create right overlapping sphere");
expect(make_region(wdbp, "overlap_left.r", "overlap_left.s"),
"create left overlapping region");
expect(make_region(wdbp, "overlap_right.r", "overlap_right.s"),
"create right overlapping region");
struct wmember overlap_members;
BU_LIST_INIT(&overlap_members.l);
expect(mk_addmember("overlap_left.r", &overlap_members.l, NULL,
WMOP_UNION) != NULL, "add left overlapping region");
expect(mk_addmember("overlap_right.r", &overlap_members.l, NULL,
WMOP_UNION) != NULL, "add right overlapping region");
expect(mk_lcomb(wdbp, "overlap.c", &overlap_members, 0, NULL, NULL,
NULL, 0) == 0, "create overlapping-region combination");
vect_t box_size;
VSUB2(box_size, maximum, minimum);
const double sphere_area = 4.0 * M_PI * sphere_radius * sphere_radius;
const double sphere_volume = 4.0 * M_PI * sphere_radius * sphere_radius *
sphere_radius / 3.0;
const double box_area = 2.0 * (box_size[X] * box_size[Y] +
box_size[X] * box_size[Z] + box_size[Y] * box_size[Z]);
const double box_volume = box_size[X] * box_size[Y] * box_size[Z];
vect_t thin_size;
VSUB2(thin_size, thin_maximum, thin_minimum);
const double thin_area = 2.0 * (thin_size[X] * thin_size[Y] +
thin_size[X] * thin_size[Z] + thin_size[Y] * thin_size[Z]);
const double thin_volume =
thin_size[X] * thin_size[Y] * thin_size[Z];
const double torus_area = 4.0 * M_PI * M_PI * torus_major_radius *
torus_minor_radius;
const double torus_volume = 2.0 * M_PI * M_PI * torus_major_radius *
torus_minor_radius * torus_minor_radius;
const double slender_torus_area = 4.0 * M_PI * M_PI *
slender_torus_major_radius * slender_torus_minor_radius;
const double disconnected_area = 4.0 * M_PI *
(lower_radius * lower_radius + upper_radius * upper_radius);
const double disconnected_volume = 4.0 * M_PI *
(lower_radius * lower_radius * lower_radius +
upper_radius * upper_radius * upper_radius) / 3.0;
const double overlap_distance = 2.0 * overlap_offset;
const double overlap_cap_height = overlap_radius -
0.5 * overlap_distance;
const double overlap_intersection_volume = M_PI *
(4.0 * overlap_radius + overlap_distance) *
std::pow(2.0 * overlap_radius - overlap_distance, 2.0) / 12.0;
const double overlap_area = 8.0 * M_PI * overlap_radius *
overlap_radius - 4.0 * M_PI * overlap_radius * overlap_cap_height;
const double overlap_volume = 8.0 * M_PI * overlap_radius *
overlap_radius * overlap_radius / 3.0 -
overlap_intersection_volume;
check_mesh(dbip, "sphere.s", 1, sphere_area, sphere_volume);
check_mesh(dbip, "box.s", 1, box_area, box_volume);
check_mesh(dbip, "thin.s", 1, thin_area, thin_volume, 10000);
check_mesh(dbip, "torus.s", 1, torus_area, torus_volume, 4, 0);
check_mesh(dbip, "slender_torus.s", 1, slender_torus_area,
0.0, 4, 0);
check_mesh(dbip, "tunnel.r", 1, 0.0, 0.0, 4, 0);
check_mesh(dbip, "tgc.s");
check_mesh(dbip, "concave.s");
check_mesh(dbip, "disconnected.r", 2, disconnected_area,
disconnected_volume);
check_mesh(dbip, "overlap.c", 1, overlap_area, overlap_volume);
{
int *coarse_faces = NULL;
point_t *coarse_vertices = NULL;
size_t coarse_face_count = 0;
size_t coarse_vertex_count = 0;
struct analyze_mdc_params coarse = ANALYZE_MDC_PARAMS_DEFAULT;
coarse.min_depth = 3;
coarse.max_depth = 3;
coarse.max_rays = 50000;
coarse.minimum_free_mem = 0;
coarse.max_time = 0;
enum analyze_mdc_status coarse_status = analyze_mdc(&coarse_faces,
&coarse_face_count, &coarse_vertices, &coarse_vertex_count,
"tunnel.r", dbip, &coarse);
expect(coarse_status == ANALYZE_MDC_AMBIGUOUS,
"under-resolved tunnel requests refinement");
expect(!coarse_faces && !coarse_vertices && !coarse_face_count &&
coarse_vertex_count == 0,
"under-resolved tunnel leaves outputs empty");
if (coarse_faces)
bu_free(coarse_faces, "coarse MDC test faces");
if (coarse_vertices)
bu_free(coarse_vertices, "coarse MDC test vertices");
}
{
int *compatibility_faces = NULL;
point_t *compatibility_vertices = NULL;
int compatibility_face_count = 0;
int compatibility_vertex_count = 0;
point_t unused_seed = VINIT_ZERO;
struct analyze_polygonize_params compatibility =
ANALYZE_POLYGONIZE_PARAMS_DEFAULT;
compatibility.max_time = 0;
compatibility.max_cycle_time = 0;
compatibility.minimum_free_mem = 0;
int status = analyze_polygonize(&compatibility_faces,
&compatibility_face_count, &compatibility_vertices,
&compatibility_vertex_count, 1.0, unused_seed, "sphere.s",
dbip, &compatibility);
expect(status == 0, "polygonize compatibility interface");
if (status == 0) {
expect(compatibility_face_count >= 4,
"compatibility contour has faces");
expect(compatibility_vertex_count >= 4,
"compatibility contour has vertices");
expect(bg_trimesh_solid2(compatibility_vertex_count,
compatibility_face_count,
reinterpret_cast<fastf_t *>(compatibility_vertices),
compatibility_faces, NULL) == 0,
"compatibility contour is a closed oriented manifold");
}
if (compatibility_faces)
bu_free(compatibility_faces, "compatibility test faces");
if (compatibility_vertices)
bu_free(compatibility_vertices, "compatibility test vertices");
compatibility.max_time = 1;
compatibility.time_offset = 1;
expect(analyze_polygonize(&compatibility_faces,
&compatibility_face_count, &compatibility_vertices,
&compatibility_vertex_count, 1.0, unused_seed, "sphere.s",
dbip, &compatibility) == 2,
"compatibility interface preserves timeout status");
expect(!compatibility_faces && !compatibility_vertices &&
!compatibility_face_count && !compatibility_vertex_count,
"compatibility timeout leaves outputs empty");
}
int *faces = NULL;
point_t *vertices = NULL;
size_t face_count = 0;
size_t vertex_count = 0;
struct analyze_mdc_params limited = ANALYZE_MDC_PARAMS_DEFAULT;
limited.min_depth = 4;
limited.max_depth = 4;
limited.max_rays = 1;
limited.minimum_free_mem = 0;
expect(analyze_mdc(&faces, &face_count, &vertices, &vertex_count,
"sphere.s", dbip, &limited) == ANALYZE_MDC_RAY_LIMIT,
"ray limit is enforced during sampling");
expect(!faces && !vertices && !face_count && !vertex_count,
"failure leaves outputs empty");
expect(analyze_mdc(NULL, &face_count, &vertices, &vertex_count,
"sphere.s", dbip, NULL) == ANALYZE_MDC_INVALID_INPUT,
"invalid output pointer is rejected");
db_close(dbip);
return failures ? 1 : 0;
}
/*
* Local Variables:
* mode: C++
* tab-width: 8
* c-basic-offset: 4
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
-191
View File
@@ -1,191 +0,0 @@
/* N I R T _ A N N O T A T I O N S . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by the
* U.S. Army Research Laboratory.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "common.h"
#include <cstdio>
#include <cstdlib>
#include "analyze.h"
#include "bu/app.h"
#include "bu/file.h"
#include "bu/malloc.h"
#include "raytrace.h"
#include "wdb.h"
static constexpr fastf_t FRONT_ANNOTATION_X = 18.0;
static constexpr fastf_t REAR_ANNOTATION_X = 10.0;
static constexpr fastf_t SCREEN_ANNOTATION_X = 19.0;
static constexpr fastf_t OCCLUDER_MINIMUM_X = 15.0;
static constexpr fastf_t OCCLUDER_MAXIMUM_X = 16.0;
static constexpr fastf_t RAY_ORIGIN_X = 20.0;
static constexpr fastf_t TEST_HALF_EXTENT = 1.0;
static int
capture_output(struct nirt_state *state, void *data)
{
struct bu_vls current = BU_VLS_INIT_ZERO;
struct bu_vls *captured = (struct bu_vls *)data;
nirt_log(&current, state, NIRT_OUT);
bu_vls_vlscat(captured, &current);
bu_vls_free(&current);
return 0;
}
static void
write_annotation(struct rt_wdb *wdbp, const char *name, fastf_t x,
bool screen_space)
{
struct rt_annot_internal annotation = {};
struct line_seg line = {};
struct rt_annot_seg_style style = {};
int reverse = 0;
void *segment = &line;
point2d_t vertices[2] = {{0.0, 0.0}, {TEST_HALF_EXTENT, 0.0}};
annotation.magic = RT_ANNOT_INTERNAL_MAGIC;
VSET(annotation.V, x, 0.0, 0.0);
annotation.flags = screen_space ? RT_ANNOT_SCREEN_SPACE :
RT_ANNOT_MODEL_SPACE;
VSET(annotation.u_vec, 0.0, 1.0, 0.0);
VSET(annotation.v_vec, 0.0, 0.0, 1.0);
annotation.vert_count = 2;
annotation.verts = vertices;
annotation.ant.count = 1;
annotation.ant.reverse = &reverse;
annotation.ant.segments = &segment;
annotation.styles = &style;
line.magic = CURVE_LSEG_MAGIC;
line.start = 0;
line.end = 1;
style.role = RT_ANNOT_ROLE_LEADER;
style.line_pattern = RT_ANNOT_LINE_CONTINUOUS;
if (rt_annot_validate(&annotation, NULL) ||
mk_annot(wdbp, name, &annotation) < 0)
bu_exit(EXIT_FAILURE, "unable to write %s\n", name);
}
static void
clear_standard_formats(struct nirt_state *state)
{
const char *commands[] = {
"fmt r \"\"", "fmt h \"\"", "fmt p \"\"", "fmt f \"\"",
"fmt m \"\"", "fmt o \"\"", "fmt g \"\""
};
for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); ++i)
if (nirt_exec(state, commands[i]))
bu_exit(EXIT_FAILURE, "NIRT rejected command: %s\n", commands[i]);
}
static void
get_output(struct bu_vls *output, struct bu_vls *captured)
{
bu_vls_sprintf(output, "%s", bu_vls_cstr(captured));
bu_vls_trunc(captured, 0);
}
int
main(int argc, char **argv)
{
char path[MAXPATHLEN];
struct db_i *dbip;
struct rt_wdb *wdbp;
struct nirt_state *state;
struct bu_vls captured = BU_VLS_INIT_ZERO;
struct bu_vls output = BU_VLS_INIT_ZERO;
struct wmember members;
point_t minimum, maximum;
bu_setprogname(argv[0]);
if (argc != 1)
return EXIT_FAILURE;
{
FILE *fp = bu_temp_file(path, sizeof(path));
if (!fp)
bu_exit(EXIT_FAILURE, "unable to create temporary database path\n");
fclose(fp);
}
dbip = db_create(path, 5);
if (!dbip)
bu_exit(EXIT_FAILURE, "unable to create temporary database\n");
wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_DISK);
if (!wdbp)
bu_exit(EXIT_FAILURE, "unable to open temporary database for writing\n");
write_annotation(wdbp, "front.annot", FRONT_ANNOTATION_X, false);
write_annotation(wdbp, "rear.annot", REAR_ANNOTATION_X, false);
write_annotation(wdbp, "screen.annot", SCREEN_ANNOTATION_X, true);
VSET(minimum, OCCLUDER_MINIMUM_X, -TEST_HALF_EXTENT,
-TEST_HALF_EXTENT);
VSET(maximum, OCCLUDER_MAXIMUM_X, TEST_HALF_EXTENT,
TEST_HALF_EXTENT);
if (mk_rpp(wdbp, "occluder.s", minimum, maximum) < 0)
bu_exit(EXIT_FAILURE, "unable to write occluder\n");
BU_LIST_INIT(&members.l);
if (!mk_addmember("occluder.s", &members.l, NULL, WMOP_UNION) ||
mk_lcomb(wdbp, "occluder.r", &members, 1, NULL, NULL, NULL, 0))
bu_exit(EXIT_FAILURE, "unable to write occluding region\n");
BU_GET(state, struct nirt_state);
if (nirt_init(state))
bu_exit(EXIT_FAILURE, "unable to initialize NIRT\n");
nirt_udata(state, &captured);
nirt_hook(state, capture_output, NIRT_OUT);
if (nirt_exec(state,
"draw front.annot rear.annot screen.annot occluder.r") ||
nirt_init_dbip(state, dbip))
bu_exit(EXIT_FAILURE, "unable to prepare NIRT test database\n");
clear_standard_formats(state);
if (nirt_exec(state,
"fmt a \"%s|%d|%s|%.1f|%.1f|%d\\n\" annot_path annot_segment annot_role annot_x annot_d annot_visible"))
bu_exit(EXIT_FAILURE, "unable to set annotation report format\n");
struct bu_vls shot = BU_VLS_INIT_ZERO;
bu_vls_sprintf(&shot, "backout 0;xyz %g 0 0;dir -1 0 0;s",
RAY_ORIGIN_X);
if (nirt_exec(state, bu_vls_cstr(&shot)))
bu_exit(EXIT_FAILURE, "unable to shoot default NIRT ray\n");
bu_vls_free(&shot);
get_output(&output, &captured);
if (bu_vls_strlen(&output))
bu_exit(EXIT_FAILURE,
"annotations were reported without useannot: %s\n",
bu_vls_cstr(&output));
if (nirt_exec(state, "useannot 1;s"))
bu_exit(EXIT_FAILURE, "unable to shoot annotation NIRT ray\n");
get_output(&output, &captured);
struct bu_vls expected = BU_VLS_INIT_ZERO;
bu_vls_sprintf(&expected,
"/front.annot|0|leader|%.1f|%.1f|1\n"
"/rear.annot|0|leader|%.1f|%.1f|0\n",
FRONT_ANNOTATION_X, FRONT_ANNOTATION_X,
REAR_ANNOTATION_X, REAR_ANNOTATION_X);
if (!BU_STR_EQUAL(bu_vls_cstr(&output), bu_vls_cstr(&expected)))
bu_exit(EXIT_FAILURE, "unexpected annotation report:\n%s",
bu_vls_cstr(&output));
bu_vls_free(&expected);
nirt_destroy(state);
BU_PUT(state, struct nirt_state);
bu_vls_free(&captured);
bu_vls_free(&output);
db_close(dbip);
bu_file_delete(path);
return EXIT_SUCCESS;
}
+98
View File
@@ -0,0 +1,98 @@
/* S A M P L I N G _ T E S T . C
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
#include "common.h"
#include <float.h>
#include <stdio.h>
#include "analyze/gqa.h"
#include "analyze/sampling.h"
#include "bu/app.h"
#include "vmath.h"
int
main(int UNUSED(argc), char *argv[])
{
static const double estimates[ANALYZE_SAMPLING_MIN_REPLICATES] = {
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};
struct analyze_sampling_interval interval;
static const double ratio_numerators[ANALYZE_SAMPLING_MIN_REPLICATES] = {
1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0, 81.0, 100.0};
static const double zero_denominators[ANALYZE_SAMPLING_MIN_REPLICATES] = {0.0};
bu_setprogname(argv[0]);
if (analyze_sampling_interval95(&interval, estimates,
ANALYZE_SAMPLING_MIN_REPLICATES) != ANALYZE_OK ||
!NEAR_EQUAL(interval.estimate, 5.5, SMALL_FASTF) ||
!(interval.half_width > 0.0) || !(interval.half_width < DBL_MAX)) {
fprintf(stderr, "valid sampling interval was rejected\n");
return 1;
}
if (analyze_sampling_ratio_interval95(&interval, ratio_numerators,
estimates, ANALYZE_SAMPLING_MIN_REPLICATES) != ANALYZE_OK ||
!NEAR_EQUAL(interval.estimate, 7.0, SMALL_FASTF) ||
!(interval.half_width > 0.0) || !(interval.half_width < DBL_MAX)) {
fprintf(stderr, "valid ratio sampling interval was rejected\n");
return 1;
}
if (analyze_sampling_ratio_interval95(&interval, ratio_numerators,
zero_denominators, ANALYZE_SAMPLING_MIN_REPLICATES) != ANALYZE_ERROR) {
fprintf(stderr, "zero-denominator ratio interval was accepted\n");
return 1;
}
if (analyze_sampling_interval95(&interval, estimates,
ANALYZE_SAMPLING_MIN_REPLICATES - 1) != ANALYZE_ERROR) {
fprintf(stderr, "undersized replicate set was accepted\n");
return 1;
}
{
const char *arguments[] = {
"gqa", "--analyze", "--uncertainty", "--rays", "1600",
"model.g", "object"};
if (analyze_gqa_model_argument(7, arguments) != 5) {
fprintf(stderr, "analysis model argument was not located\n");
return 1;
}
}
{
const char *arguments[] = {
"gqa", "--analyze", "--rays", "model.g"};
if (analyze_gqa_model_argument(4, arguments) >= 0) {
fprintf(stderr, "missing analysis option value was accepted\n");
return 1;
}
}
return 0;
}
/*
* Local Variables:
* tab-width: 8
* mode: C
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+6 -12
View File
@@ -419,7 +419,7 @@ protected:
ListNode* AllocListNode();
void FreeListNode(ListNode* a_listNode);
bool Overlap(Rect* a_rectA, Rect* a_rectB) const;
bool Intersect(const Rect* a_rect, const Ray* a_ray) const;
bool Intersect(Rect* a_rect, Ray* a_ray) const;
void ReInsert(Node* a_node, ListNode** a_listNode);
bool Search(Node* a_node, Rect* a_rect, int& a_foundCount, std::function<bool(const DataType&, void *)> callback, void *a_context) const;
void RemoveAllRec(Node* a_node);
@@ -433,15 +433,14 @@ protected:
size_t CheckNodes(Node *a_nodeA, Node *a_nodeB, std::set<DataType> *a_result, std::set<DataType> *b_result);
size_t CheckNodes(Node *a_nodeA, Node *a_nodeB, std::set<std::pair<DataType, DataType>> *result);
size_t IsectNode(const Node *a_node, const Ray *a_ray,
std::set<DataType> *result) const;
size_t IsectNode(Node *a_node, Ray *a_ray, std::set<DataType> *result);
Node* m_root = nullptr; ///< Root of tree
ElementTypeReal m_unitSphereVolume = kElementTypeRealZero; ///< Unit sphere constant for required number of dimensions
public:
// Find the leaves intersected by a ray, returning the data from the leaves in a set.
size_t Intersects(const Ray *a_ray, std::set<DataType> *result) const;
size_t Intersects(Ray *a_ray, std::set<DataType> *result);
void plot(const char *fname);
void plot2d(const char *fname);
@@ -1552,7 +1551,7 @@ bool RTREE_QUAL::Overlap(Rect* a_rectA, Rect* a_rectB) const
// Decide whether a ray intersect a Rect.
RTREE_TEMPLATE
bool RTREE_QUAL::Intersect(const Rect* a_rect, const Ray* a_ray) const
bool RTREE_QUAL::Intersect(Rect* a_rect, Ray* a_ray) const
{
RTreeAssert(a_rect);
if (kNumDimensions != 3) return false;
@@ -1686,8 +1685,7 @@ size_t RTREE_QUAL::Overlaps(RTree &other, std::set<std::pair<DataType, DataType>
RTREE_TEMPLATE
size_t RTREE_QUAL::IsectNode(const Node *a_node, const Ray *a_ray,
std::set<DataType> *result) const
size_t RTREE_QUAL::IsectNode(Node *a_node, Ray *a_ray, std::set<DataType> *result)
{
size_t ocnt = 0;
for (int index = 0; index < a_node->m_count; ++index) {
@@ -1705,8 +1703,7 @@ size_t RTREE_QUAL::IsectNode(const Node *a_node, const Ray *a_ray,
// Loosely based on the idea from the openNURBS code to search two R-trees for all pairs elements whose bounding boxes overlap.
RTREE_TEMPLATE
size_t RTREE_QUAL::Intersects(const Ray *a_ray,
std::set<DataType> *result) const
size_t RTREE_QUAL::Intersects(Ray *a_ray, std::set<DataType> *result)
{
if (!m_root) return 0;
size_t ovlp_cnt = IsectNode(m_root, a_ray, result);
@@ -1803,9 +1800,6 @@ void RTREE_QUAL::plot2d(const char *fname)
}
#undef BB_PLOT2D
#undef BB_PLOT
#undef TREE_LEAF_FACE_3D
#undef RTREE_TEMPLATE
#undef RTREE_QUAL
+796 -134
View File
@@ -8,8 +8,8 @@
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
@@ -19,170 +19,832 @@
*/
/** @file spsr.cpp
*
* Interface to Adaptive Multigrid Solvers for Screened Poisson Surface Reconstruction
* Compatible with mkazhdan/PoissonRecon API (v18.x+).
*
* Screened Poisson surface reconstruction for fixed and adaptive oriented
* point sets.
*/
#include "common.h"
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstring>
#include <exception>
#include <limits>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <utility>
#include <vector>
#include "vmath.h"
#include "bu/log.h"
#include "bu/malloc.h"
#include "bg/spsr.h"
#include "RTree.h"
#if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic push /* start new diagnostic pragma */
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wall"
#elif defined(__clang__)
# pragma clang diagnostic push /* start new diagnostic pragma */
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Weverything"
#endif
#include "SPSR/Reconstructors.h"
#if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic pop /* end ignoring warnings */
# pragma GCC diagnostic pop
#elif defined(__clang__)
# pragma clang diagnostic pop /* end ignoring warnings */
# pragma clang diagnostic pop
#endif
using namespace PoissonRecon;
// Input stream for oriented samples: implements `read` for coordinates and normals
template<typename Real, unsigned int Dim>
struct PtStream : public Reconstructor::InputOrientedSampleStream<Real, Dim>
{
PtStream(const point_t *ipnts, const vect_t *inorms, int cnt)
: _size(cnt), _current(0), _input_pnts(ipnts), _input_nrmls(inorms) {}
void reset(void) { _current = 0; }
bool read(Point<Real, Dim> &p, Point<Real, Dim> &n)
{
if (_current < _size) {
for (size_t d = 0; d < Dim; d++)
p[d] = _input_pnts[_current][d];
for (size_t d = 0; d < Dim; d++)
n[d] = _input_nrmls[_current][d];
_current++;
return true;
}
return false;
}
protected:
unsigned int _size, _current;
const point_t *_input_pnts;
const vect_t *_input_nrmls;
namespace {
using clock_type = std::chrono::steady_clock;
using real_type = fastf_t;
constexpr size_t SPSR_MAX_HINTS = 4096;
constexpr size_t SPSR_MIN_NEIGHBORS = 4;
constexpr double SPSR_NORMAL_ANGLE_DEGREES = 15.0;
constexpr double SPSR_DEDUP_FRACTION = 0.25;
constexpr int SPSR_MAX_DEPTH = 30;
struct spsr_mesh {
std::vector<int> faces;
std::vector<real_type> vertices;
};
// Output face stream: must implement write() and size()
template <typename Index>
struct PolygonStream : public Reconstructor::OutputFaceStream<2>
template<typename Real, unsigned int Dim>
struct point_stream : public Reconstructor::InputOrientedSampleStream<Real, Dim>
{
PolygonStream(std::vector<std::vector<Index>> &polygonStream) : _polygons(polygonStream) {}
size_t size(void) const { return _polygons.size(); }
point_stream(const std::vector<struct bg_3d_spsr_sample> &samples)
: current(0), input(samples)
{
}
void reset(void)
{
current = 0;
}
bool read(Point<Real, Dim> &point, Point<Real, Dim> &normal)
{
if (current >= input.size())
return false;
for (size_t dimension = 0; dimension < Dim; dimension++) {
point[dimension] = input[current].point[dimension];
normal[dimension] = input[current].normal[dimension];
}
current++;
return true;
}
private:
size_t current;
const std::vector<struct bg_3d_spsr_sample> &input;
};
template<typename Index>
struct polygon_stream : public Reconstructor::OutputFaceStream<2>
{
polygon_stream(std::vector<std::vector<Index>> &output) : polygons(output)
{
}
size_t size(void) const
{
return polygons.size();
}
size_t write(const std::vector<node_index_type> &polygon)
{
std::vector<Index> poly(polygon.size());
for (size_t i = 0; i < polygon.size(); i++) poly[i] = (Index)polygon[i];
_polygons.push_back(poly);
return _polygons.size() - 1;
std::vector<Index> converted(polygon.size());
for (size_t i = 0; i < polygon.size(); i++)
converted[i] = static_cast<Index>(polygon[i]);
polygons.push_back(std::move(converted));
return polygons.size() - 1;
}
protected:
std::vector<std::vector<Index>> &_polygons;
private:
std::vector<std::vector<Index>> &polygons;
};
// Output vertex stream: must implement write() and size()
template <typename Real, unsigned int Dim>
struct VertexStream : public Reconstructor::OutputLevelSetVertexStream<Real, Dim>
template<typename Real, unsigned int Dim>
struct vertex_stream : public Reconstructor::OutputLevelSetVertexStream<Real, Dim>
{
VertexStream(std::vector<Real> &vCoordinates) : _vCoordinates(vCoordinates) {}
size_t size(void) const { return _vCoordinates.size() / Dim; }
size_t write(const Point<Real, Dim> &p, const Point<Real, Dim> &, const Real &)
vertex_stream(std::vector<Real> &output) : coordinates(output)
{
for (unsigned int d = 0; d < Dim; d++) _vCoordinates.push_back(p[d]);
return _vCoordinates.size() / Dim - 1;
}
protected:
std::vector<Real> &_vCoordinates;
size_t size(void) const
{
return coordinates.size() / Dim;
}
size_t write(const Point<Real, Dim> &point, const Point<Real, Dim> &,
const Real &)
{
for (unsigned int dimension = 0; dimension < Dim; dimension++)
coordinates.push_back(point[dimension]);
return coordinates.size() / Dim - 1;
}
private:
std::vector<Real> &coordinates;
};
extern "C" int
bg_3d_spsr(int **faces, int *num_faces, point_t **points, int *num_pnts,
const point_t *input_points_3d, const vect_t *input_normals_3d,
int num_input_pnts, struct bg_3d_spsr_opts *spsr_opts)
static double
elapsed_seconds(const clock_type::time_point &start)
{
using Real = fastf_t;
static const unsigned int FEMSig = FEMDegreeAndBType<Reconstructor::Poisson::DefaultFEMDegree, Reconstructor::Poisson::DefaultFEMBoundary>::Signature;
using FEMSigs = IsotropicUIntPack<3, FEMSig>;
// Set up multithreading
PoissonRecon::ThreadPool::ParallelizationType = PoissonRecon::ThreadPool::ASYNC;
// Solver and extraction parameters
Reconstructor::Poisson::SolutionParameters<Real> solverParams;
solverParams.verbose = false;
solverParams.depth = (spsr_opts) ? spsr_opts->depth : 8;
solverParams.fullDepth = (spsr_opts) ? spsr_opts->full_depth : 11;
solverParams.samplesPerNode = (spsr_opts) ? spsr_opts->samples_per_node : 1.5;
solverParams.exactInterpolation = true;
Reconstructor::LevelSetExtractionParameters extractionParams;
extractionParams.forceManifold = true;
extractionParams.linearFit = false;
extractionParams.polygonMesh = false;
extractionParams.verbose = false;
PtStream<Real, 3> vstream(input_points_3d, input_normals_3d, num_input_pnts);
using Implicit = Reconstructor::Implicit<Real, 3, FEMSigs>;
using Solver = Reconstructor::Poisson::Solver<Real, 3, FEMSigs>;
Implicit *implicit = Solver::Solve(vstream, solverParams);
if (!implicit) {
bu_log("PoissonRecon: Solver::Solve failed\n");
return -1;
}
std::vector<std::vector<int>> polygons;
std::vector<Real> vCoordinates;
PolygonStream<int> pStream(polygons);
VertexStream<Real, 3> vStream(vCoordinates);
implicit->extractLevelSet(vStream, pStream, extractionParams);
*num_faces = (int)(polygons.size());
*num_pnts = (int)(vCoordinates.size() / 3);
bu_log("Point cnt: %d\n", *num_pnts);
bu_log("Face cnt: %d\n", *num_faces);
// Allocate output arrays
(*faces) = (int *)bu_calloc(*num_faces * 3, sizeof(int), "faces array");
(*points) = (point_t *)bu_calloc(*num_pnts, sizeof(point_t), "points array");
// Copy faces (triangulated output expected)
for (int i = 0; i < *num_faces; i++) {
if (polygons[i].size() != 3) {
bu_log("Warning: polygon %d is not a triangle (has %zu vertices)\n", i, polygons[i].size());
// TODO - triangulate if we don't have a triangle.
continue;
}
(*faces)[3*i+0] = polygons[i][0];
(*faces)[3*i+1] = polygons[i][1];
(*faces)[3*i+2] = polygons[i][2];
}
// Copy points
for (int i = 0; i < *num_pnts; i++) {
(*points)[i][X] = vCoordinates[3*i+0];
(*points)[i][Y] = vCoordinates[3*i+1];
(*points)[i][Z] = vCoordinates[3*i+2];
}
delete implicit;
return 0;
return std::chrono::duration<double>(clock_type::now() - start).count();
}
static bool
finite_vector(const fastf_t *vector)
{
return std::isfinite(vector[X]) && std::isfinite(vector[Y]) &&
std::isfinite(vector[Z]);
}
// Local Variables:
// tab-width: 8
// mode: C++
// c-basic-offset: 4
// indent-tabs-mode: t
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8 cino=N-s
static bool
valid_options(const struct bg_3d_spsr_opts &options)
{
if (options.degree != BG_3D_SPSR_DEFAULT_DEGREE ||
options.btype != BG_3D_SPSR_BOUNDARY_NEUMANN ||
options.max_memory_GB != BG_3D_SPSR_DEFAULT_MAX_MEM ||
options.threads != BG_3D_SPSR_DEFAULT_THREADS ||
!NEAR_ZERO(options.confidence, SMALL_FASTF) ||
!NEAR_ZERO(options.confidence_bias, SMALL_FASTF))
return false;
if (options.depth < 1 || options.depth > SPSR_MAX_DEPTH ||
options.kerneldepth < 0 || options.iterations < 0 ||
options.full_depth < 0 || options.base_depth < 0 ||
options.baseVcycles < 0 || options.samples_per_node <= 0.0 ||
options.scale < 1.0 || options.width < 0.0 ||
options.cgsolver_accuracy <= 0.0 || options.point_weight < 0.0)
return false;
return options.nonManifold >= 0 && options.nonManifold <= 1 &&
options.linearFit >= 0 && options.linearFit <= 1 &&
options.exact >= 0 && options.exact <= 1;
}
static void
set_solver_options(
Reconstructor::Poisson::SolutionParameters<real_type> &solver,
Reconstructor::LevelSetExtractionParameters &extraction,
const struct bg_3d_spsr_opts &options)
{
solver.depth = static_cast<unsigned int>(options.depth);
solver.kernelDepth = options.kerneldepth ?
static_cast<unsigned int>(options.kerneldepth) :
static_cast<unsigned int>(-1);
solver.iters = options.iterations ?
static_cast<unsigned int>(options.iterations) : 8u;
solver.fullDepth = options.full_depth ?
static_cast<unsigned int>(options.full_depth) :
std::min(5u, solver.depth);
solver.baseDepth = options.base_depth ?
static_cast<unsigned int>(options.base_depth) :
static_cast<unsigned int>(-1);
solver.baseVCycles = options.baseVcycles ?
static_cast<unsigned int>(options.baseVcycles) : 1u;
solver.samplesPerNode = options.samples_per_node;
solver.scale = options.scale;
solver.width = options.width;
solver.cgSolverAccuracy = options.cgsolver_accuracy;
solver.pointWeight = options.point_weight;
solver.exactInterpolation = options.exact != 0;
solver.confidence = false;
solver.verbose = false;
solver.showResidual = false;
extraction.forceManifold = options.nonManifold == 0;
extraction.linearFit = options.linearFit != 0;
extraction.polygonMesh = false;
extraction.verbose = false;
}
static fastf_t
sample_extent(const std::vector<struct bg_3d_spsr_sample> &samples)
{
point_t minimum;
point_t maximum;
VSETALL(minimum, INFINITY);
VSETALL(maximum, -INFINITY);
for (const auto &sample : samples)
VMINMAX(minimum, maximum, sample.point);
return DIST_PNT_PNT(minimum, maximum);
}
class sample_set
{
public:
sample_set(fastf_t tolerance, size_t limit) :
distance_tolerance(tolerance), distance_tolerance_sq(tolerance * tolerance),
point_limit(limit)
{
}
bool append(const struct bg_3d_spsr_sample &input)
{
if (!finite_vector(input.point) || !finite_vector(input.normal))
return false;
struct bg_3d_spsr_sample sample = input;
fastf_t normal_length = MAGNITUDE(sample.normal);
if (!std::isfinite(normal_length) || normal_length <= VUNITIZE_TOL)
return false;
VSCALE(sample.normal, sample.normal, 1.0 / normal_length);
double minimum[3];
double maximum[3];
for (size_t dimension = 0; dimension < 3; dimension++) {
minimum[dimension] = sample.point[dimension] - distance_tolerance;
maximum[dimension] = sample.point[dimension] + distance_tolerance;
}
bool duplicate = false;
index.Search(minimum, maximum,
[&sample, this, &duplicate](const size_t &candidate, void *) {
if (DIST_PNT_PNT_SQ(sample.point, values[candidate].point) <=
distance_tolerance_sq) {
duplicate = true;
return false;
}
return true;
}, NULL);
if (duplicate || (point_limit && values.size() >= point_limit))
return false;
size_t sample_index = values.size();
values.push_back(sample);
double point[3] = {
sample.point[X], sample.point[Y], sample.point[Z]
};
index.Insert(point, point, sample_index);
return true;
}
const std::vector<struct bg_3d_spsr_sample> &samples(void) const
{
return values;
}
bool full(void) const
{
return point_limit && values.size() >= point_limit;
}
private:
fastf_t distance_tolerance;
fastf_t distance_tolerance_sq;
size_t point_limit;
std::vector<struct bg_3d_spsr_sample> values;
RTree<size_t, double, 3> index;
};
static void
append_hint(std::vector<struct bg_3d_spsr_refinement_hint> &hints,
const point_t point, const vect_t normal, fastf_t error,
unsigned int reasons)
{
struct bg_3d_spsr_refinement_hint hint = {};
VMOVE(hint.point, point);
VMOVE(hint.normal, normal);
hint.error = error;
hint.reasons = reasons;
hints.push_back(hint);
}
template<typename Implicit>
static void
sample_hints(std::vector<struct bg_3d_spsr_refinement_hint> &hints,
const std::vector<struct bg_3d_spsr_sample> &samples,
const Implicit &implicit, fastf_t feature_size)
{
const fastf_t normal_dot_limit =
std::cos(SPSR_NORMAL_ANGLE_DEGREES * DEG2RAD);
typename Implicit::Evaluator evaluator = implicit.evaluator();
for (const auto &sample : samples) {
Point<real_type, 3> point;
for (size_t dimension = 0; dimension < 3; dimension++)
point[dimension] = sample.point[dimension];
try {
Point<real_type, 3> gradient = evaluator.grad(point);
real_type gradient_length = std::sqrt(
gradient[0] * gradient[0] + gradient[1] * gradient[1] +
gradient[2] * gradient[2]);
unsigned int reasons = 0;
fastf_t error = feature_size;
vect_t normal;
VSET(normal, gradient[0], gradient[1], gradient[2]);
if (gradient_length <= VUNITIZE_TOL) {
reasons = BG_3D_SPSR_REFINE_RESIDUAL |
BG_3D_SPSR_REFINE_NORMAL;
VSETALL(normal, 0.0);
} else {
fastf_t residual = std::fabs(
evaluator(point) - implicit.isoValue) / gradient_length;
fastf_t normal_dot = std::fabs(
VDOT(sample.normal, normal) / gradient_length);
if (residual > feature_size) {
reasons |= BG_3D_SPSR_REFINE_RESIDUAL;
error = residual;
}
if (normal_dot < normal_dot_limit) {
reasons |= BG_3D_SPSR_REFINE_NORMAL;
error = std::max(error,
feature_size * (1.0 + normal_dot_limit - normal_dot));
}
VSCALE(normal, normal, 1.0 / gradient_length);
}
if (reasons)
append_hint(hints, sample.point, normal, error, reasons);
} catch (const std::exception &) {
append_hint(hints, sample.point, sample.normal,
2.0 * feature_size, BG_3D_SPSR_REFINE_RESIDUAL);
}
}
}
static void
density_hints(std::vector<struct bg_3d_spsr_refinement_hint> &hints,
const std::vector<struct bg_3d_spsr_sample> &samples,
fastf_t feature_size)
{
RTree<size_t, double, 3> index;
for (size_t i = 0; i < samples.size(); i++) {
double point[3] = {
samples[i].point[X], samples[i].point[Y], samples[i].point[Z]
};
index.Insert(point, point, i);
}
fastf_t search_distance_sq = feature_size * feature_size;
for (size_t i = 0; i < samples.size(); i++) {
double minimum[3];
double maximum[3];
for (size_t dimension = 0; dimension < 3; dimension++) {
minimum[dimension] =
samples[i].point[dimension] - feature_size;
maximum[dimension] =
samples[i].point[dimension] + feature_size;
}
size_t neighbor_count = 0;
index.Search(minimum, maximum,
[&samples, i, search_distance_sq, &neighbor_count](
const size_t &candidate, void *) {
if (DIST_PNT_PNT_SQ(samples[i].point,
samples[candidate].point) <= search_distance_sq)
neighbor_count++;
return neighbor_count < SPSR_MIN_NEIGHBORS;
}, NULL);
if (neighbor_count < SPSR_MIN_NEIGHBORS)
append_hint(hints, samples[i].point, samples[i].normal,
feature_size, BG_3D_SPSR_REFINE_DENSITY);
}
}
static void
surface_variation_hints(
std::vector<struct bg_3d_spsr_refinement_hint> &hints,
const spsr_mesh &mesh, fastf_t feature_size)
{
size_t vertex_count = mesh.vertices.size() / 3;
std::vector<std::array<fastf_t, 3>> first_normal(vertex_count);
std::vector<fastf_t> variation(vertex_count, 0.0);
std::vector<unsigned char> have_normal(vertex_count, 0);
const fastf_t variation_limit =
1.0 - std::cos(SPSR_NORMAL_ANGLE_DEGREES * DEG2RAD);
for (size_t face = 0; face < mesh.faces.size() / 3; face++) {
int ia = mesh.faces[3 * face];
int ib = mesh.faces[3 * face + 1];
int ic = mesh.faces[3 * face + 2];
const fastf_t *a = &mesh.vertices[3 * static_cast<size_t>(ia)];
const fastf_t *b = &mesh.vertices[3 * static_cast<size_t>(ib)];
const fastf_t *c = &mesh.vertices[3 * static_cast<size_t>(ic)];
vect_t ab;
vect_t ac;
vect_t normal;
VSUB2(ab, b, a);
VSUB2(ac, c, a);
VCROSS(normal, ab, ac);
fastf_t length = MAGNITUDE(normal);
if (length <= VUNITIZE_TOL)
continue;
VSCALE(normal, normal, 1.0 / length);
const int vertices[3] = {ia, ib, ic};
for (size_t corner = 0; corner < 3; corner++) {
size_t vertex = static_cast<size_t>(vertices[corner]);
if (!have_normal[vertex]) {
VMOVE(first_normal[vertex].data(), normal);
have_normal[vertex] = 1;
} else {
variation[vertex] = std::max(variation[vertex],
1.0 - std::fabs(VDOT(first_normal[vertex].data(), normal)));
}
}
}
for (size_t vertex = 0; vertex < vertex_count; vertex++) {
if (variation[vertex] <= variation_limit)
continue;
const fastf_t *point = &mesh.vertices[3 * vertex];
append_hint(hints, point, first_normal[vertex].data(),
feature_size * (1.0 + variation[vertex]),
BG_3D_SPSR_REFINE_SURFACE_VARIATION);
}
}
static void
limit_hints(std::vector<struct bg_3d_spsr_refinement_hint> &hints,
fastf_t feature_size)
{
std::sort(hints.begin(), hints.end(),
[](const struct bg_3d_spsr_refinement_hint &left,
const struct bg_3d_spsr_refinement_hint &right) {
return left.error > right.error;
});
RTree<size_t, double, 3> locations;
std::vector<struct bg_3d_spsr_refinement_hint> selected;
selected.reserve(std::min(hints.size(), SPSR_MAX_HINTS));
fastf_t separation = feature_size * SPSR_DEDUP_FRACTION;
fastf_t separation_sq = separation * separation;
for (const auto &hint : hints) {
double minimum[3];
double maximum[3];
for (size_t dimension = 0; dimension < 3; dimension++) {
minimum[dimension] = hint.point[dimension] - separation;
maximum[dimension] = hint.point[dimension] + separation;
}
bool nearby = false;
locations.Search(minimum, maximum,
[&hint, &selected, separation_sq, &nearby](
const size_t &candidate, void *) {
if (DIST_PNT_PNT_SQ(hint.point,
selected[candidate].point) <= separation_sq) {
nearby = true;
return false;
}
return true;
}, NULL);
if (nearby)
continue;
size_t hint_index = selected.size();
selected.push_back(hint);
double point[3] = {hint.point[X], hint.point[Y], hint.point[Z]};
locations.Insert(point, point, hint_index);
if (selected.size() == SPSR_MAX_HINTS)
break;
}
hints.swap(selected);
}
template<typename Implicit>
static bool
extract_mesh(spsr_mesh &mesh, const Implicit &implicit,
const Reconstructor::LevelSetExtractionParameters &parameters)
{
std::vector<std::vector<int>> polygons;
polygon_stream<int> faces(polygons);
vertex_stream<real_type, 3> vertices(mesh.vertices);
implicit.extractLevelSet(vertices, faces, parameters);
if (mesh.vertices.empty() || mesh.vertices.size() % 3 ||
mesh.vertices.size() / 3 >
static_cast<size_t>(std::numeric_limits<int>::max()) ||
polygons.empty() ||
polygons.size() > static_cast<size_t>(std::numeric_limits<int>::max()))
return false;
mesh.faces.reserve(polygons.size() * 3);
for (const auto &polygon : polygons) {
if (polygon.size() != 3)
return false;
for (int vertex : polygon) {
if (vertex < 0 ||
static_cast<size_t>(vertex) >= mesh.vertices.size() / 3)
return false;
mesh.faces.push_back(vertex);
}
}
return true;
}
static bool
solve_once(spsr_mesh &mesh,
std::vector<struct bg_3d_spsr_refinement_hint> &hints,
const std::vector<struct bg_3d_spsr_sample> &samples,
const struct bg_3d_spsr_adaptive_opts &options)
{
static std::mutex solver_mutex;
std::lock_guard<std::mutex> guard(solver_mutex);
static const unsigned int fem_signature =
FEMDegreeAndBType<Reconstructor::Poisson::DefaultFEMDegree,
Reconstructor::Poisson::DefaultFEMBoundary>::Signature;
using fem_signatures = IsotropicUIntPack<3, fem_signature>;
using implicit_type =
Reconstructor::Implicit<real_type, 3, fem_signatures>;
using solver_type =
Reconstructor::Poisson::Solver<real_type, 3, fem_signatures>;
Reconstructor::Poisson::SolutionParameters<real_type> solver_parameters;
Reconstructor::LevelSetExtractionParameters extraction_parameters;
set_solver_options(solver_parameters, extraction_parameters,
options.solver);
ThreadPool::ParallelizationType = ThreadPool::ASYNC;
point_stream<real_type, 3> points(samples);
std::unique_ptr<implicit_type> implicit(
solver_type::Solve(points, solver_parameters));
if (!implicit || !extract_mesh(mesh, *implicit, extraction_parameters))
return false;
if (options.target_feature_size > 0.0) {
sample_hints(hints, samples, *implicit,
options.target_feature_size);
density_hints(hints, samples, options.target_feature_size);
surface_variation_hints(hints, mesh,
options.target_feature_size);
limit_hints(hints, options.target_feature_size);
}
return true;
}
static bool
copy_mesh(int **faces, int *face_count, point_t **vertices,
int *vertex_count, const spsr_mesh &mesh)
{
size_t faces_size = mesh.faces.size() * sizeof(int);
size_t vertices_size = mesh.vertices.size() * sizeof(real_type);
int *new_faces = static_cast<int *>(bu_malloc(faces_size,
"SPSR face array"));
point_t *new_vertices = static_cast<point_t *>(bu_malloc(vertices_size,
"SPSR vertex array"));
if (!new_faces || !new_vertices) {
if (new_faces)
bu_free(new_faces, "SPSR face array");
if (new_vertices)
bu_free(new_vertices, "SPSR vertex array");
return false;
}
std::memcpy(new_faces, mesh.faces.data(), faces_size);
std::memcpy(new_vertices, mesh.vertices.data(), vertices_size);
*faces = new_faces;
*face_count = static_cast<int>(mesh.faces.size() / 3);
*vertices = new_vertices;
*vertex_count = static_cast<int>(mesh.vertices.size() / 3);
return true;
}
static void
initialize_outputs(int **faces, int *face_count, point_t **vertices,
int *vertex_count, struct bg_3d_spsr_report *report)
{
if (faces)
*faces = NULL;
if (face_count)
*face_count = 0;
if (vertices)
*vertices = NULL;
if (vertex_count)
*vertex_count = 0;
if (report)
std::memset(report, 0, sizeof(*report));
}
} // namespace
extern "C" int
bg_3d_spsr_adaptive(int **faces, int *num_faces, point_t **vertices,
int *num_vertices, const struct bg_3d_spsr_sample *input_samples,
size_t sample_count, const struct bg_3d_spsr_adaptive_opts *input_options,
bg_3d_spsr_refinement_func_t refine, void *client_data,
struct bg_3d_spsr_report *report)
{
initialize_outputs(faces, num_faces, vertices, num_vertices, report);
if (!faces || !num_faces || !vertices || !num_vertices ||
!input_samples || !sample_count)
return BRLCAD_ERROR;
struct bg_3d_spsr_adaptive_opts default_options =
BG_3D_SPSR_ADAPTIVE_OPTS_DEFAULT;
const struct bg_3d_spsr_adaptive_opts &options =
input_options ? *input_options : default_options;
if (!valid_options(options.solver) ||
options.target_feature_size < 0.0 || options.max_time < 0.0 ||
(options.max_refinement_passes &&
(!refine || options.target_feature_size <= 0.0)) ||
(options.max_points && options.max_points < sample_count))
return BRLCAD_ERROR;
fastf_t extent = 0.0;
{
std::vector<struct bg_3d_spsr_sample> initial(
input_samples, input_samples + sample_count);
extent = sample_extent(initial);
}
if (!std::isfinite(extent) || extent <= SMALL_FASTF)
return BRLCAD_ERROR;
fastf_t deduplication_distance =
options.target_feature_size > 0.0 ?
options.target_feature_size * SPSR_DEDUP_FRACTION :
std::max(extent * 1.0e-12, static_cast<fastf_t>(SMALL_FASTF));
sample_set accumulated(deduplication_distance, options.max_points);
for (size_t i = 0; i < sample_count; i++)
accumulated.append(input_samples[i]);
if (accumulated.samples().empty())
return BRLCAD_ERROR;
const clock_type::time_point start = clock_type::now();
if (report)
report->initial_sample_count = accumulated.samples().size();
bool point_limit_reached = accumulated.full();
for (size_t pass = 0; ; pass++) {
if (options.max_time > 0.0 &&
elapsed_seconds(start) >= options.max_time) {
if (report)
report->termination = BG_3D_SPSR_TIME_LIMIT;
break;
}
spsr_mesh mesh;
std::vector<struct bg_3d_spsr_refinement_hint> hints;
try {
if (!solve_once(mesh, hints, accumulated.samples(), options)) {
if (report)
report->termination = BG_3D_SPSR_SOLVER_ERROR;
break;
}
} catch (const std::exception &) {
if (report)
report->termination = BG_3D_SPSR_SOLVER_ERROR;
break;
} catch (...) {
if (report)
report->termination = BG_3D_SPSR_SOLVER_ERROR;
break;
}
if (report)
report->solve_count++;
if (!refine) {
if (copy_mesh(faces, num_faces, vertices, num_vertices, mesh)) {
if (report) {
report->termination = BG_3D_SPSR_COMPLETE;
report->validation.passed = 1;
report->final_sample_count = accumulated.samples().size();
report->elapsed_time = elapsed_seconds(start);
}
return BRLCAD_OK;
}
if (report)
report->termination = BG_3D_SPSR_SOLVER_ERROR;
break;
}
double remaining_time = options.max_time > 0.0 ?
std::max(0.0, options.max_time - elapsed_seconds(start)) : 0.0;
if (options.max_time > 0.0 && remaining_time <= 0.0) {
if (report)
report->termination = BG_3D_SPSR_TIME_LIMIT;
break;
}
struct bg_3d_spsr_refinement_request request = {};
request.pass = pass;
request.target_feature_size = options.target_feature_size;
request.remaining_time = remaining_time;
request.vertices =
reinterpret_cast<const point_t *>(mesh.vertices.data());
request.vertex_count = mesh.vertices.size() / 3;
request.faces = mesh.faces.data();
request.face_count = mesh.faces.size() / 3;
request.hints = hints.data();
request.hint_count = hints.size();
struct bg_3d_spsr_refinement_response response = {};
if (refine(&response, &request, client_data) != BRLCAD_OK) {
if (report)
report->termination = BG_3D_SPSR_CALLBACK_STOP;
break;
}
if (report)
report->validation = response.validation;
if (response.validation.passed) {
if (copy_mesh(faces, num_faces, vertices, num_vertices, mesh)) {
if (report) {
report->termination = BG_3D_SPSR_COMPLETE;
report->final_sample_count = accumulated.samples().size();
report->elapsed_time = elapsed_seconds(start);
}
return BRLCAD_OK;
}
if (report)
report->termination = BG_3D_SPSR_SOLVER_ERROR;
break;
}
if (response.stop_refinement) {
if (report)
report->termination = BG_3D_SPSR_CALLBACK_STOP;
break;
}
if (pass == options.max_refinement_passes) {
if (report)
report->termination = point_limit_reached ?
BG_3D_SPSR_POINT_LIMIT : BG_3D_SPSR_CALLBACK_STOP;
break;
}
if (response.sample_count && !response.samples) {
if (report)
report->termination = BG_3D_SPSR_CALLBACK_STOP;
break;
}
if (report)
report->requested_sample_count += response.sample_count;
size_t accepted = 0;
for (size_t i = 0; i < response.sample_count; i++) {
if (accumulated.append(response.samples[i]))
accepted++;
}
if (report)
report->accepted_sample_count += accepted;
point_limit_reached = accumulated.full();
if (!accepted) {
if (report)
report->termination = point_limit_reached ?
BG_3D_SPSR_POINT_LIMIT : BG_3D_SPSR_NO_NEW_SAMPLES;
break;
}
}
if (report) {
report->final_sample_count = accumulated.samples().size();
report->elapsed_time = elapsed_seconds(start);
}
return BRLCAD_ERROR;
}
extern "C" int
bg_3d_spsr(int **faces, int *num_faces, point_t **vertices, int *num_vertices,
const point_t *input_points_3d, const vect_t *input_normals_3d,
int num_input_pnts, struct bg_3d_spsr_opts *spsr_opts)
{
if (!input_points_3d || !input_normals_3d || num_input_pnts <= 0)
return BRLCAD_ERROR;
std::vector<struct bg_3d_spsr_sample> samples(
static_cast<size_t>(num_input_pnts));
for (size_t i = 0; i < samples.size(); i++) {
VMOVE(samples[i].point, input_points_3d[i]);
VMOVE(samples[i].normal, input_normals_3d[i]);
}
struct bg_3d_spsr_adaptive_opts options =
BG_3D_SPSR_ADAPTIVE_OPTS_DEFAULT;
options.max_refinement_passes = 0;
if (spsr_opts)
options.solver = *spsr_opts;
return bg_3d_spsr_adaptive(faces, num_faces, vertices, num_vertices,
samples.data(), samples.size(), &options, NULL, NULL, NULL);
}
/*
* Local Variables:
* tab-width: 8
* mode: C++
* c-basic-offset: 4
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8 cino=N-s
*/
+4
View File
@@ -33,6 +33,10 @@ brlcad_addexec(bg_aabb_ray aabb_ray.c "${BG_TEST_LIBS}" TEST)
brlcad_add_test(NAME bg_aabb_ray COMMAND bg_aabb_ray)
brlcad_addexec(bg_spsr spsr.cpp "${BG_TEST_LIBS}" TEST)
brlcad_add_test(NAME bg_spsr COMMAND bg_spsr)
brlcad_addexec(bg_polygon_sort polygon_sort.c "${BG_TEST_LIBS}" TEST)
brlcad_add_test(NAME bg_polygon_sort COMMAND bg_polygon_sort)
+200
View File
@@ -0,0 +1,200 @@
/* S P S R . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
#include "common.h"
#include <cmath>
#include <vector>
#include "vmath.h"
#include "bu/app.h"
#include "bu/log.h"
#include "bu/malloc.h"
#include "bg/spsr.h"
static int failures = 0;
static void
expect(bool condition, const char *message)
{
if (condition)
return;
bu_log("FAIL: %s\n", message);
failures++;
}
static std::vector<struct bg_3d_spsr_sample>
sphere_samples(size_t count)
{
const fastf_t golden_angle = M_PI * (3.0 - std::sqrt(5.0));
std::vector<struct bg_3d_spsr_sample> samples(count);
for (size_t i = 0; i < count; i++) {
fastf_t y = 1.0 - 2.0 *
(static_cast<fastf_t>(i) + 0.5) / count;
fastf_t radius = std::sqrt(std::max(0.0, 1.0 - y * y));
fastf_t angle = golden_angle * i;
VSET(samples[i].point, radius * std::cos(angle), y,
radius * std::sin(angle));
VMOVE(samples[i].normal, samples[i].point);
}
return samples;
}
struct callback_state {
size_t calls;
size_t hints;
struct bg_3d_spsr_sample additional_sample;
bool refine_once;
bool stop;
};
static int
accept_candidate(struct bg_3d_spsr_refinement_response *response,
const struct bg_3d_spsr_refinement_request *request, void *data)
{
struct callback_state *state =
static_cast<struct callback_state *>(data);
state->calls++;
state->hints = request->hint_count;
response->validation.ray_count = 100;
if (state->stop) {
response->validation.passed = 0;
response->stop_refinement = 1;
} else if (state->refine_once && state->calls == 1) {
response->samples = &state->additional_sample;
response->sample_count = 1;
response->validation.passed = 0;
} else {
response->validation.passed = 1;
}
return BRLCAD_OK;
}
int
main(int UNUSED(argc), const char **argv)
{
bu_setprogname(argv[0]);
std::vector<struct bg_3d_spsr_sample> samples = sphere_samples(256);
struct bg_3d_spsr_adaptive_opts options =
BG_3D_SPSR_ADAPTIVE_OPTS_DEFAULT;
options.solver.depth = 5;
options.max_refinement_passes = 0;
int *faces = NULL;
int face_count = 0;
point_t *vertices = NULL;
int vertex_count = 0;
struct bg_3d_spsr_report report = {};
int ret = bg_3d_spsr_adaptive(&faces, &face_count, &vertices,
&vertex_count, samples.data(), samples.size(), &options,
NULL, NULL, &report);
expect(ret == BRLCAD_OK, "fixed structured reconstruction succeeds");
expect(face_count > 0 && vertex_count > 0,
"fixed structured reconstruction returns a mesh");
expect(report.solve_count == 1 && report.validation.passed,
"fixed structured reconstruction reports one successful solve");
if (faces)
bu_free(faces, "SPSR test faces");
if (vertices)
bu_free(vertices, "SPSR test vertices");
struct callback_state state = {};
options.max_refinement_passes = 3;
options.target_feature_size = 0.1;
options.max_points = 512;
faces = NULL;
vertices = NULL;
face_count = 0;
vertex_count = 0;
ret = bg_3d_spsr_adaptive(&faces, &face_count, &vertices,
&vertex_count, samples.data(), samples.size(), &options,
accept_candidate, &state, &report);
expect(ret == BRLCAD_OK, "callback can accept an adaptive candidate");
expect(state.calls == 1, "accepted candidate stops adaptive solving");
expect(report.validation.ray_count == 100,
"adaptive report preserves callback validation");
if (faces)
bu_free(faces, "SPSR test faces");
if (vertices)
bu_free(vertices, "SPSR test vertices");
state = {};
state.stop = true;
faces = NULL;
vertices = NULL;
face_count = 0;
vertex_count = 0;
ret = bg_3d_spsr_adaptive(&faces, &face_count, &vertices,
&vertex_count, samples.data(), samples.size(), &options,
accept_candidate, &state, &report);
expect(ret == BRLCAD_ERROR,
"callback can stop an unproductive adaptive solve");
expect(state.calls == 1 && report.solve_count == 1 &&
report.termination == BG_3D_SPSR_CALLBACK_STOP,
"callback stop preserves the final validation report");
state = {};
state.refine_once = true;
VSET(state.additional_sample.point, 1.0, 0.0, 0.0);
VSET(state.additional_sample.normal, 1.0, 0.0, 0.0);
faces = NULL;
vertices = NULL;
face_count = 0;
vertex_count = 0;
ret = bg_3d_spsr_adaptive(&faces, &face_count, &vertices,
&vertex_count, samples.data(), samples.size(), &options,
accept_candidate, &state, &report);
expect(ret == BRLCAD_OK, "adaptive refinement can add a source sample");
expect(state.calls == 2 && report.solve_count == 2,
"new source sample triggers a second solve");
expect(report.accepted_sample_count == 1 &&
report.final_sample_count == samples.size() + 1,
"adaptive report counts incorporated samples");
if (faces)
bu_free(faces, "SPSR test faces");
if (vertices)
bu_free(vertices, "SPSR test vertices");
options.solver.threads = 1;
ret = bg_3d_spsr_adaptive(&faces, &face_count, &vertices,
&vertex_count, samples.data(), samples.size(), &options,
accept_candidate, &state, NULL);
expect(ret == BRLCAD_ERROR,
"unsupported solver settings are rejected");
expect(bg_3d_spsr_adaptive(NULL, &face_count, &vertices,
&vertex_count, samples.data(), samples.size(), &options,
accept_candidate, &state, NULL) == BRLCAD_ERROR,
"invalid output arguments are rejected");
return failures ? 1 : 0;
}
/*
* Local Variables:
* tab-width: 8
* mode: C++
* c-basic-offset: 4
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8 cino=N-s
*/
+31 -17
View File
@@ -388,31 +388,45 @@ mem_sysctl(int type, size_t *memsz)
return -2;
#if defined(__FreeBSD__) && defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTL)
static const char *const page_size_name = "hw.pagesize";
static const char *const total_memory_name = "hw.physmem";
static const char *const free_pages_name = "vm.stats.vm.v_free_count";
static const char *const available_page_names[] = {
"vm.stats.vm.v_free_count",
"vm.stats.vm.v_inactive_count",
"vm.stats.vm.v_cache_count"
};
uint64_t page_size = 0;
size_t value_size = sizeof(page_size);
if (sysctlbyname(page_size_name, &page_size, &value_size, NULL, 0) != 0 ||
page_size == 0)
return -1;
if (type == BU_MEM_PAGE_SIZE)
return mem_size_from_uint64(page_size, memsz);
return mem_page_size(memsz);
uint64_t memory = 0;
value_size = sizeof(memory);
const char *memory_name = total_memory_name;
if (type == BU_MEM_AVAIL) {
memory_name = free_pages_name;
if (sysctlbyname(memory_name, &memory, &value_size, NULL, 0) != 0 ||
memory > UINT64_MAX / page_size)
return -1;
memory *= page_size;
} else if (sysctlbyname(memory_name, &memory, &value_size, NULL, 0) != 0) {
return -1;
/* FreeBSD's free queue omits clean inactive and cache pages that the VM
* can reclaim without paging another process. Counting only v_free_count
* makes an otherwise idle system appear memory-starved and needlessly
* disables memory-aware parallel work. */
for (size_t i = 0; i < sizeof(available_page_names) / sizeof(available_page_names[0]); i++) {
uint32_t pages = 0;
size_t value_size = sizeof(pages);
if (sysctlbyname(available_page_names[i], &pages, &value_size,
NULL, 0) != 0 || value_size != sizeof(pages)) {
/* v_free_count is required. Cache accounting is absent on some
* supported FreeBSD versions, so supplementary queues are optional. */
if (i == 0)
return -1;
continue;
}
if ((uint64_t)pages > UINT64_MAX - memory)
return -1;
memory += (uint64_t)pages;
}
return mem_pages_to_bytes(memory, memsz);
}
size_t value_size = sizeof(memory);
if (sysctlbyname(total_memory_name, &memory, &value_size, NULL, 0) != 0 ||
(value_size != sizeof(uint32_t) && value_size != sizeof(uint64_t)))
return -1;
return mem_size_from_uint64(memory, memsz);
#endif
return 1;
+47 -1
View File
@@ -24,6 +24,9 @@
#include <ctype.h>
#include <math.h>
#include <string.h>
#if !defined(_WIN32) || defined(__CYGWIN__)
# include <stdatomic.h>
#endif
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
@@ -130,6 +133,36 @@ void parallel_cpp11thread(void (*func)(int, void *), size_t ncpu, void *arg);
int BU_SEM_THREAD = BU_SEM_ID_THREAD;
#if defined(_WIN32) && !defined(__CYGWIN__)
static LONG bu_cpu_limit = 0;
static size_t
cpu_limit_get(void)
{
return (size_t)InterlockedCompareExchange(&bu_cpu_limit, 0, 0);
}
static void
cpu_limit_set(size_t ncpu)
{
(void)InterlockedExchange(&bu_cpu_limit, (LONG)ncpu);
}
#else
static atomic_size_t bu_cpu_limit = ATOMIC_VAR_INIT(0);
static size_t
cpu_limit_get(void)
{
return atomic_load_explicit(&bu_cpu_limit, memory_order_acquire);
}
static void
cpu_limit_set(size_t ncpu)
{
atomic_store_explicit(&bu_cpu_limit, ncpu, memory_order_release);
}
#endif
typedef enum {
PARALLEL_GET = 0,
@@ -355,7 +388,9 @@ bu_avail_cpus(void)
}
if (LIKELY(ncpu > 0)) {
return ncpu;
size_t cpu_limit = cpu_limit_get();
return (cpu_limit && cpu_limit < (size_t)ncpu) ? cpu_limit :
(size_t)ncpu;
}
/* non-PARALLEL */
@@ -363,6 +398,13 @@ bu_avail_cpus(void)
}
void
bu_avail_cpus_set(size_t ncpu)
{
cpu_limit_set((ncpu > MAX_PSW) ? MAX_PSW : ncpu);
}
/**********************************************************************/
@@ -538,6 +580,10 @@ parallel_interface_arg_stub(struct thread_data *user_thread_data)
void
bu_parallel(void (*func)(int, void *), size_t ncpu, void *arg)
{
size_t cpu_limit = cpu_limit_get();
if (ncpu && cpu_limit && ncpu > cpu_limit)
ncpu = cpu_limit;
#ifndef PARALLEL
if (!func)
+8 -2
View File
@@ -1077,11 +1077,17 @@ bu_process_poll(struct bu_process *pinfo, int *exit_status)
}
#if defined(_WIN32)
/* An exit code can be available before process teardown releases
* inherited files and pipes. Only report completion once signaled. */
DWORD wait_result = WaitForSingleObject(pinfo->hProcess, 0);
if (wait_result == WAIT_TIMEOUT)
return 0;
if (wait_result != WAIT_OBJECT_0)
return -1;
DWORD status = 0;
if (!GetExitCodeProcess(pinfo->hProcess, &status))
return -1;
if (status == STILL_ACTIVE)
return 0;
pinfo->exit_status = (status == BU_MSVC_ABORT_EXIT) ?
ERROR_PROCESS_ABORTED : (int)status;
+52 -5
View File
@@ -40,7 +40,7 @@
#include "bio.h"
#include "bresource.h"
#if defined(__APPLE__) && defined(HAVE_SYS_SYSCTL_H)
#if (defined(__APPLE__) || defined(__FreeBSD__)) && defined(HAVE_SYS_SYSCTL_H)
# include <sys/sysctl.h>
#endif
@@ -214,8 +214,12 @@ mem_test_failure(const char *query, ssize_t result, const size_t *output,
static int
platform_mem_tests(ssize_t all_mem)
platform_mem_tests(ssize_t all_mem, ssize_t avail_mem, ssize_t page_mem)
{
(void)all_mem;
(void)avail_mem;
(void)page_mem;
#if defined(__APPLE__) && defined(HAVE_SYS_SYSCTL_H)
uint64_t native_total = 0;
size_t value_size = sizeof(native_total);
@@ -230,8 +234,51 @@ platform_mem_tests(ssize_t all_mem)
"reported %llu\n", all_mem, (unsigned long long)native_total);
return -2;
}
#else
(void)all_mem;
#elif defined(__FreeBSD__) && defined(HAVE_SYS_SYSCTL_H)
uint64_t native_total = 0;
size_t value_size = sizeof(native_total);
if (sysctlbyname("hw.physmem", &native_total, &value_size, NULL, 0) != 0 ||
(value_size != sizeof(uint32_t) && value_size != sizeof(uint64_t))) {
bu_log("bu_mem platform test: unable to query hw.physmem\n");
return -1;
}
if (native_total != (uint64_t)all_mem) {
bu_log("bu_mem platform test: BU_MEM_ALL returned %zd; hw.physmem "
"reported %llu\n", all_mem, (unsigned long long)native_total);
return -2;
}
uint64_t reclaimable_pages = 0;
const char *const page_names[] = {
"vm.stats.vm.v_free_count",
"vm.stats.vm.v_inactive_count",
"vm.stats.vm.v_cache_count"
};
for (size_t i = 0; i < sizeof(page_names) / sizeof(page_names[0]); i++) {
uint32_t pages = 0;
value_size = sizeof(pages);
if (sysctlbyname(page_names[i], &pages, &value_size, NULL, 0) != 0 ||
value_size != sizeof(pages)) {
if (i == 0)
return -3;
continue;
}
reclaimable_pages += pages;
}
/* VM counters may change between the bu_mem and direct sysctl queries.
* A one-percent allowance is ample for that race while still detecting
* the former free-pages-only implementation. */
uint64_t native_available = reclaimable_pages * (uint64_t)page_mem;
uint64_t allowance = native_total / 100u;
uint64_t available_difference = ((uint64_t)avail_mem > native_available) ?
(uint64_t)avail_mem - native_available :
native_available - (uint64_t)avail_mem;
if (available_difference > allowance) {
bu_log("bu_mem platform test: BU_MEM_AVAIL returned %zd; FreeBSD "
"reclaimable queues reported %llu\n", avail_mem,
(unsigned long long)native_available);
return -4;
}
#endif
return 0;
}
@@ -445,7 +492,7 @@ main(int ac, char *av[])
if (avail_mem > all_mem)
return mem_test_failure("BU_MEM_AVAIL exceeds BU_MEM_ALL", avail_mem,
NULL, -5);
if (platform_mem_tests(all_mem) != 0)
if (platform_mem_tests(all_mem, avail_mem, page_mem) != 0)
return -6;
ssize_t process_mem = bu_mem(BU_MEM_PROCESS_AVAIL, NULL);
ssize_t resident_mem = bu_mem(BU_MEM_PROCESS_RESIDENT, NULL);
+26 -1
View File
@@ -121,6 +121,30 @@ main(int argc, char *argv[])
if (bu_getprogname()[0] == '\0')
bu_setprogname(argv[0]);
{
size_t detected_cpus = bu_avail_cpus();
size_t requested_cpus = (detected_cpus > MAX_PSW) ? MAX_PSW :
detected_cpus;
struct parallel_data limit_data = {1, NULL};
bu_avail_cpus_set(1);
if (bu_avail_cpus() != 1) {
bu_log("bu_avail_cpus process limit [FAIL]\n");
return 1;
}
memset(counter, 0, sizeof(counter));
bu_parallel(callback, requested_cpus, &limit_data);
if (tally(MAX_PSW) != 1) {
bu_log("bu_parallel process limit [FAIL]\n");
return 1;
}
bu_avail_cpus_set(0);
if (bu_avail_cpus() != detected_cpus) {
bu_log("bu_avail_cpus process limit reset [FAIL]\n");
return 1;
}
bu_log("bu_avail_cpus and bu_parallel process limit [PASS]\n");
}
while ((c = bu_getopt(argc, argv, "P:!:")) != -1) {
switch (c) {
case 'P':
@@ -137,8 +161,9 @@ main(int argc, char *argv[])
}
/* test calling without a hook function */
memset(counter, 0, sizeof(counter));
bu_parallel(NULL, ncpu, NULL);
if (tally(ncpu) != 0) {
if (tally(MAX_PSW) != 0) {
bu_log("bu_parallel zero callback [FAIL]\n");
return 1;
}
+15 -10
View File
@@ -26,11 +26,10 @@
#include "bu/app.h"
#include "bu/malloc.h"
#include "bu/vfont.h"
#include "bu/vls.h"
#define FONTDIR2 "/usr/lib/vfont"
#define DEFAULT_FONT "nonie.r.12"
#define FONTNAMESZ 128
#include "./vfont.h"
@@ -127,7 +126,7 @@ vfont_get(char *font)
register struct vfont *vfp = VFONT_NULL;
register FILE *fp = NULL;
register int i;
char fname[FONTNAMESZ] = {'\0'};
struct bu_vls fname = BU_VLS_INIT_ZERO;
unsigned char header[2*5]; /* 5 16-bit vax shorts */
unsigned char dispatch[10*256]; /* 256 10-byte structs */
uint16_t magic;
@@ -135,21 +134,24 @@ vfont_get(char *font)
const char *const_font;
const_font = (font == NULL) ? DEFAULT_FONT : (const char *)font;
bu_vls_strcpy(&fname, const_font);
/* Open the file and read in the header information. */
if ((fp = fopen(const_font, "rb")) == NULL) {
snprintf(fname, FONTNAMESZ, "%s/%s", bu_dir(NULL, 0, BU_DIR_DATA, "vfont", NULL), const_font);
if ((fp = fopen(fname, "rb")) == NULL) {
snprintf(fname, FONTNAMESZ, "%s/%s", FONTDIR2, const_font);
if ((fp = fopen(fname, "rb")) == NULL) {
bu_vls_sprintf(&fname, "%s/%s", bu_dir(NULL, 0, BU_DIR_DATA, "vfont", NULL), const_font);
if ((fp = fopen(bu_vls_addr(&fname), "rb")) == NULL) {
bu_vls_sprintf(&fname, "%s/%s", FONTDIR2, const_font);
if ((fp = fopen(bu_vls_addr(&fname), "rb")) == NULL) {
bu_vls_free(&fname);
return VFONT_NULL;
}
}
}
if (fread((char *)header, sizeof(header), 1, fp) != 1 ||
fread((char *)dispatch, sizeof(dispatch), 1, fp) != 1) {
fprintf(stderr, "vfont_get(%s): header read error\n", fname);
fprintf(stderr, "vfont_get(%s): header read error\n", bu_vls_addr(&fname));
fclose(fp);
bu_vls_free(&fname);
return VFONT_NULL;
}
magic = _vax_gshort(&header[0*2]) & 0xFFFF;
@@ -157,8 +159,9 @@ vfont_get(char *font)
if (UNLIKELY(magic != 0436)) {
fprintf(stderr, "vfont_get(%s): bad magic number 0%o\n",
fname, magic);
bu_vls_addr(&fname), magic);
fclose(fp);
bu_vls_free(&fname);
return VFONT_NULL;
}
@@ -166,10 +169,11 @@ vfont_get(char *font)
BU_ALLOC(vfp, struct vfont);
vfp->vf_bits = (char *)bu_malloc((size_t)size, "vfont bits");
if (fread(vfp->vf_bits, (size_t)size, 1, fp) != 1) {
fprintf(stderr, "vfont_get(%s): bitmap read error\n", fname);
fprintf(stderr, "vfont_get(%s): bitmap read error\n", bu_vls_addr(&fname));
fclose(fp);
bu_free(vfp->vf_bits, "vfont bits");
bu_free((char *)vfp, "vfont");
bu_vls_free(&fname);
return VFONT_NULL;
}
@@ -194,6 +198,7 @@ vfont_get(char *font)
vdp->vd_width = _vax_gshort(&cp[8]);
}
fclose(fp);
bu_vls_free(&fname);
return vfp;
}
+5
View File
@@ -595,6 +595,11 @@ bv_vlblock_obj(struct bv_vlblock *vbp, struct bview *v, const char *name)
s = bv_obj_get(v, BV_VIEW_OBJS);
}
/* Reset clears both fields, but replacement objects must retain their
* identity and view ownership before children are allocated. */
s->s_v = v;
bu_vls_sprintf(&s->s_name, "%s", name);
for (size_t i = 0; i < vbp->nused; i++) {
if (!BU_LIST_IS_EMPTY(&(vbp->head[i]))) {
struct bv_scene_obj *sc = bv_obj_get_child(s);
+6 -8
View File
@@ -62,8 +62,9 @@ constexpr fastf_t DEFAULT_LEADER_MARGIN_SCALE = 2.0;
constexpr fastf_t DEFAULT_SCREEN_TEXT_HEIGHT_MM = 3.0;
/* The display-plane renderer uses this nominal density. --dpi makes the
* physical size explicit for displays whose effective density differs. */
constexpr fastf_t DEFAULT_SCREEN_DPI = RT_ANNOT_SCREEN_DPI;
constexpr fastf_t DISPLAY_PIXELS_PER_MM = RT_ANNOT_SCREEN_PIXELS_PER_MM;
constexpr fastf_t DEFAULT_SCREEN_DPI = 96.0;
constexpr fastf_t MILLIMETERS_PER_INCH = 25.4;
constexpr fastf_t DISPLAY_PIXELS_PER_MM = DEFAULT_SCREEN_DPI / MILLIMETERS_PER_INCH;
constexpr double AUTODIM_TEXT_WIDTH_SCALE = 0.65;
/* Layout penalties deliberately dominate the smaller rewards so avoiding
* collisions and crossings wins over merely moving a label farther out. */
@@ -472,12 +473,10 @@ parse_options(struct ged *gedp, int argc, const char **argv,
bu_vls_printf(gedp->ged_result_str, "Precision must be between 0 and 15");
return -1;
}
if (!std::isfinite(opts.text_height) || !std::isfinite(opts.line_width) ||
opts.text_height < 0.0 || opts.line_width < 0.0 ||
std::isinf(opts.offset) ||
if (opts.text_height < 0.0 || opts.line_width < 0.0 ||
(std::isfinite(opts.offset) && opts.offset < 0.0)) {
bu_vls_printf(gedp->ged_result_str,
"Text height and line width must be finite and non-negative; offset must be finite and non-negative when specified");
"Text height, line width, and offset must not be negative");
return -1;
}
if (!std::isfinite(opts.dpi) || opts.dpi <= 0.0) {
@@ -2905,8 +2904,7 @@ cmd_info(void *data, int argc, const char **argv)
if (rt_db_get_internal(&intern, dp, gedp->dbip, NULL) == ID_ANNOT) {
struct rt_annot_internal *annotation =
static_cast<struct rt_annot_internal *>(intern.idb_ptr);
bu_vls_printf(gedp->ged_result_str,
"\nspace: %s\nsegments: %zu",
bu_vls_printf(gedp->ged_result_str, "\nspace: %s\nsegments: %zu",
(annotation->flags & RT_ANNOT_MODEL_SPACE) ? "model" : "view",
annotation->ant.count);
rt_db_free_internal(&intern);
+9 -13
View File
@@ -3,16 +3,21 @@ brlcad_find_package(manifold REQUIRED)
set(FACETIZE_LIBS libged libbu manifold::manifold)
add_subdirectory(subprocess)
add_subdirectory(tests)
set(
FACETIZE_SRCS
brep.cpp
facetize.cpp
nmg.cpp
orchestrator.cpp
plan.cpp
process.cpp
regions.cpp
transfer.cpp
tri.cpp
util.cpp
validation.cpp
worker.cpp
)
@@ -26,23 +31,14 @@ ged_plugin_library(ged-facetize
# Plugin requires subprocess to work
add_dependencies(ged-facetize ged_exec facetize_process)
# Old version of facetize
set(OLD_FACETIZE_SRCS
old_facetize.cpp
old_facetize_log.c
)
ged_plugin_library(ged-old_facetize
${OLD_FACETIZE_SRCS}
INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}
LIBS ${FACETIZE_LIBS}
)
cmakefiles(
CMakeLists.txt
tess_opts.h
ged_facetize.h
old_facetize.h
orchestrator.h
process.h
transfer.h
validation.h
worker.h
TODO
)
+41 -30
View File
@@ -35,26 +35,27 @@
#include "./ged_facetize.h"
int
_nonovlp_brep_facetize(struct _ged_facetize_state *s, int argc, const char **argv)
_nonovlp_brep_facetize(struct _ged_facetize_state *s, const FacetizePlan &plan)
{
int newobj_cnt;
struct directory **dpa = NULL;
RT_CHECK_DBI(s->dbip);
if (argc <= 0) return BRLCAD_ERROR;
dpa = (struct directory **)bu_calloc(argc, sizeof(struct directory *), "dp array");
newobj_cnt = _ged_sort_existing_objs(s->dbip, argc, argv, dpa);
if (_ged_validate_objs_list(s, argc, argv, newobj_cnt) == BRLCAD_ERROR) {
bu_free(dpa, "dp array");
return BRLCAD_ERROR;
int argc = (int)plan.inputs.size();
std::vector<const char *> argv = plan.input_argv();
struct directory **dpa = (struct directory **)bu_calloc(argc,
sizeof(struct directory *), "BRep input directory array");
for (int i = 0; i < argc; i++) {
dpa[i] = db_lookup(s->dbip, argv[i], LOOKUP_QUIET);
if (!dpa[i]) {
facetize_failure(s, "BRep input object '%s' disappeared before processing", argv[i]);
bu_free(dpa, "BRep input directory array");
return BRLCAD_ERROR;
}
}
/* If anything specified has subtractions or intersections, we can't facetize it with
* this logic - that would require all-up Boolean evaluation processing. */
const char *non_union = "-bool + -or -bool -";
if (db_search(NULL, DB_SEARCH_QUIET, non_union, newobj_cnt, dpa, s->dbip, NULL, NULL, NULL) > 0) {
if (db_search(NULL, DB_SEARCH_QUIET, non_union, argc, dpa, s->dbip, NULL, NULL, NULL) > 0) {
bu_free(dpa, "dp array");
bu_vls_printf(s->gedp->ged_result_str, "Found intersection or subtraction objects in specified inputs - currently unsupported. Aborting.\n");
return BRLCAD_ERROR;
@@ -63,7 +64,7 @@ _nonovlp_brep_facetize(struct _ged_facetize_state *s, int argc, const char **arg
/* If anything other than combs or breps exists in the specified inputs, we can't
* process with this logic - requires a preliminary brep conversion. */
const char *obj_types = "! -type c -and ! -type brep";
if (db_search(NULL, DB_SEARCH_QUIET, obj_types, newobj_cnt, dpa, s->dbip, NULL, NULL, NULL) > 0) {
if (db_search(NULL, DB_SEARCH_QUIET, obj_types, argc, dpa, s->dbip, NULL, NULL, NULL) > 0) {
bu_free(dpa, "dp array");
bu_vls_printf(s->gedp->ged_result_str, "Found objects in specified inputs which are not of type comb or brep- currently unsupported. Aborting.\n");
return BRLCAD_ERROR;
@@ -83,12 +84,17 @@ _nonovlp_brep_facetize(struct _ged_facetize_state *s, int argc, const char **arg
bu_free(dpa, "dp array");
return BRLCAD_ERROR;
}
for (int i = 0; i < newobj_cnt; i++) {
int xac = 3;
for (int i = 0; i < argc; i++) {
int xac = 2;
const char *xav[3] = {NULL};
xav[0] = "xpush";
xav[1] = dpa[i]->d_namep;
ged_exec_xpush(wgedp, xac, (const char **)xav);
if (ged_exec_xpush(wgedp, xac, (const char **)xav) != BRLCAD_OK) {
facetize_failure(s, "xpush failed for BRep input '%s'", dpa[i]->d_namep);
ged_close(wgedp);
bu_free(dpa, "BRep input directory array");
return BRLCAD_ERROR;
}
}
/* Used the libged tolerances */
@@ -103,7 +109,7 @@ _nonovlp_brep_facetize(struct _ged_facetize_state *s, int argc, const char **arg
const char *active_breps = "-type brep";
struct bu_ptbl *br;
BU_ALLOC(br, struct bu_ptbl);
if (db_search(br, DB_SEARCH_RETURN_UNIQ_DP, active_breps, newobj_cnt, dpa, wgedp->dbip, NULL, NULL, NULL) < 0) {
if (db_search(br, DB_SEARCH_RETURN_UNIQ_DP, active_breps, argc, dpa, wgedp->dbip, NULL, NULL, NULL) < 0) {
bu_free(dpa, "dp array");
bu_free(br, "brep results");
return BRLCAD_ERROR;
@@ -210,13 +216,6 @@ _nonovlp_brep_facetize(struct _ged_facetize_state *s, int argc, const char **arg
}
}
/* Done changing stuff in working database. */
ged_close(wgedp);
for (size_t i = 0; i < ss_cdt.size(); i++) {
ON_Brep_CDT_Destroy(ss_cdt[i]);
}
/* Keep out just what we asked for into a .g file */
struct bu_vls kwfile = BU_VLS_INIT_ZERO;
bu_vls_sprintf(&kwfile, "%s_keep", bu_vls_cstr(s->wfile));
@@ -227,7 +226,21 @@ _nonovlp_brep_facetize(struct _ged_facetize_state *s, int argc, const char **arg
av[i+2] = argv[i];
}
av[argc+2] = NULL;
ged_exec_keep(wgedp, argc+2, av);
if (ged_exec_keep(wgedp, argc+2, av) != BRLCAD_OK) {
facetize_failure(s, "unable to stage processed BRep hierarchy");
bu_free(av, "av");
bu_vls_free(&kwfile);
ged_close(wgedp);
for (size_t i = 0; i < ss_cdt.size(); i++)
ON_Brep_CDT_Destroy(ss_cdt[i]);
return BRLCAD_ERROR;
}
/* Done changing stuff in working database. */
ged_close(wgedp);
for (size_t i = 0; i < ss_cdt.size(); i++)
ON_Brep_CDT_Destroy(ss_cdt[i]);
/* Merge working geometry into original file */
av[0] = "dbconcat";
@@ -235,14 +248,13 @@ _nonovlp_brep_facetize(struct _ged_facetize_state *s, int argc, const char **arg
av[2] = bu_vls_cstr(&kwfile);
av[3] = "brep_facetize_"; // TODO - customize
av[4] = NULL;
ged_exec_dbconcat(s->gedp, 4, av);
int concat_ret = ged_exec_dbconcat(s->gedp, 4, av);
bu_free(av, "av");
bu_vls_free(&kwfile);
/* Clean up */
bu_dirclear(s->wdir);
s->cleanup_workspace = true;
return BRLCAD_OK;
return (concat_ret == BRLCAD_OK) ? BRLCAD_OK : BRLCAD_ERROR;
}
// Local Variables:
@@ -253,4 +265,3 @@ _nonovlp_brep_facetize(struct _ged_facetize_state *s, int argc, const char **arg
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+199 -299
View File
@@ -32,6 +32,7 @@
#include <vector>
#include "bu/app.h"
#include "bu/file.h"
#include "bu/path.h"
#include "bu/opt.h"
#include "wdb.h"
@@ -42,6 +43,8 @@
#include "./tess_opts.h"
#include "./ged_facetize.h"
static const char FACETIZE_NMG_OUTPUT_FILE_SUFFIX[] = "_nmg_outputs.g";
static int
facetize_process_output(struct bu_vls *output, const char **command)
{
@@ -98,223 +101,132 @@ _facetize_methods_help(struct ged *gedp)
bu_vls_free(&method_output);
}
struct _ged_facetize_state *
_ged_facetize_state_create()
static int
facetize_commit_nmg_outputs(struct _ged_facetize_state *s,
const std::vector<std::string> &output_names)
{
struct _ged_facetize_state *s = NULL;
BU_GET(s, struct _ged_facetize_state);
s->verbosity = 0;
s->no_empty = 0;
s->make_nmg = 0;
s->nonovlp_brep = 0;
s->no_fixup = 0;
s->nmg_booleval = 0;
s->use_variant_plan = 1;
s->tolerate_failures = 0;
s->tolerated_failures = 0;
s->tolerated_failure_details = 0;
s->tolerated_failure_omitted = 0;
s->inspection_regions = 0;
s->perturb_sa_tol = 10.0;
s->perturb_vol_tol = 10.0;
if (!s || output_names.empty())
return BRLCAD_ERROR;
s->wdir = NULL;
BU_GET(s->log_file, struct bu_vls);
bu_vls_init(s->log_file);
s->log_file_is_temporary = 0;
s->lfile = NULL;
BU_GET(s->failure_msg, struct bu_vls);
bu_vls_init(s->failure_msg);
BU_GET(s->tolerated_failure_log, struct bu_vls);
bu_vls_init(s->tolerated_failure_log);
BU_GET(s->region_summary, struct bu_vls);
bu_vls_init(s->region_summary);
BU_GET(s->primitive_summary, struct bu_vls);
bu_vls_init(s->primitive_summary);
BU_GET(s->inspection_log, struct bu_vls);
bu_vls_init(s->inspection_log);
BU_GET(s->wfile, struct bu_vls);
bu_vls_init(s->wfile);
BU_GET(s->bname, struct bu_vls);
bu_vls_init(s->bname);
s->regions = 0;
s->resume = 0;
s->in_place = 0;
BU_GET(s->suffix, struct bu_vls);
bu_vls_init(s->suffix);
BU_GET(s->prefix, struct bu_vls);
bu_vls_init(s->prefix);
BU_GET(s->solid_suffix, struct bu_vls);
bu_vls_init(s->solid_suffix);
bu_vls_sprintf(s->solid_suffix, ".bot");
s->max_time = 0;
s->max_pnts = 0;
s->tol = NULL;
s->nonovlp_threshold = 0;
s->gedp = NULL;
s->write_profiled = 0;
s->write_profile_bytes = 0.0;
s->write_profile_usec = 0.0;
s->variant_plan = NULL;
return s;
}
void _ged_facetize_state_destroy(struct _ged_facetize_state *s)
{
if (!s)
return;
if (s->wdir)
bu_free(s->wdir, "wdir");
if (s->bname) {
bu_vls_free(s->bname);
BU_PUT(s->bname, struct bu_vls);
}
if (s->lfile) {
fclose(s->lfile);
s->lfile = NULL;
}
if (s->log_file) {
bu_vls_free(s->log_file);
BU_PUT(s->log_file, struct bu_vls);
}
if (s->failure_msg) {
bu_vls_free(s->failure_msg);
BU_PUT(s->failure_msg, struct bu_vls);
}
if (s->tolerated_failure_log) {
bu_vls_free(s->tolerated_failure_log);
BU_PUT(s->tolerated_failure_log, struct bu_vls);
}
if (s->region_summary) {
bu_vls_free(s->region_summary);
BU_PUT(s->region_summary, struct bu_vls);
}
if (s->primitive_summary) {
bu_vls_free(s->primitive_summary);
BU_PUT(s->primitive_summary, struct bu_vls);
}
if (s->inspection_log) {
bu_vls_free(s->inspection_log);
BU_PUT(s->inspection_log, struct bu_vls);
}
if (s->wfile) {
bu_vls_free(s->wfile);
BU_PUT(s->wfile, struct bu_vls);
}
if (s->prefix) {
bu_vls_free(s->prefix);
BU_PUT(s->prefix, struct bu_vls);
}
if (s->suffix) {
bu_vls_free(s->suffix);
BU_PUT(s->suffix, struct bu_vls);
}
if (s->solid_suffix) {
bu_vls_free(s->solid_suffix);
BU_PUT(s->solid_suffix, struct bu_vls);
}
if (s->variant_plan) {
delete (FacetizeVariantPlan *)s->variant_plan;
s->variant_plan = NULL;
}
BU_PUT(s, struct _ged_facetize_state);
}
int
_ged_facetize_objs(struct _ged_facetize_state *s, int argc, const char **argv)
{
int ret = BRLCAD_ERROR;
int newobj_cnt, i;
int ok_cnt = 0;
const char *oname = NULL;
const char *av[2];
struct directory **dpa = NULL;
struct directory *idpa[2];
struct db_i *dbip = s->dbip;
RT_CHECK_DBI(dbip);
if (argc < 0) return BRLCAD_ERROR;
dpa = (struct directory **)bu_calloc(argc, sizeof(struct directory *), "dp array");
newobj_cnt = _ged_sort_existing_objs(dbip, argc, argv, dpa);
if (_ged_validate_objs_list(s, argc, argv, newobj_cnt) == BRLCAD_ERROR) {
bu_free(dpa, "dp array");
std::string keep_file = std::string(bu_vls_cstr(s->wfile)) +
FACETIZE_NMG_OUTPUT_FILE_SUFFIX;
(void)bu_file_delete(keep_file.c_str());
struct ged *working_gedp = ged_open("db", bu_vls_cstr(s->wfile), 1);
if (!working_gedp) {
facetize_failure(s, "unable to open staged NMG Boolean database '%s'",
bu_vls_cstr(s->wfile));
return BRLCAD_ERROR;
}
if (!s->in_place) {
oname = argv[argc-1];
argc--;
std::vector<const char *> keep_argv;
keep_argv.reserve(output_names.size() + 2);
keep_argv.push_back("keep");
keep_argv.push_back(keep_file.c_str());
for (const std::string &output_name : output_names)
keep_argv.push_back(output_name.c_str());
int keep_result = ged_exec_keep(working_gedp, (int)keep_argv.size(),
keep_argv.data());
ged_close(working_gedp);
if (keep_result != BRLCAD_OK) {
facetize_failure(s, "unable to stage NMG Boolean outputs for import");
(void)bu_file_delete(keep_file.c_str());
return BRLCAD_ERROR;
}
/* If we're doing an NMG output, or we have been instructed to, use the
* old-school libnmg booleval */
if (s->make_nmg || s->nmg_booleval) {
if (!s->in_place) {
ret = _ged_facetize_nmgeval(s, argc, argv, oname);
goto booleval_cleanup;
} else {
for (i = 0; i < argc; i++) {
av[0] = argv[i];
av[1] = NULL;
ret = _ged_facetize_nmgeval(s, 1, av, av[0]);
if (ret == BRLCAD_ERROR && s->tolerate_failures) {
facetize_tolerated_failure(s, "object '%s' failed during NMG boolean evaluation and was skipped", argv[i]);
continue;
}
if (ret == BRLCAD_ERROR)
goto booleval_cleanup;
ok_cnt++;
}
if (s->tolerate_failures && ok_cnt > 0)
ret = BRLCAD_OK;
goto booleval_cleanup;
const char *concat_argv[] = {"dbconcat", "-O", keep_file.c_str(), NULL};
int concat_result = ged_exec_dbconcat(s->gedp, 3, concat_argv);
(void)bu_file_delete(keep_file.c_str());
if (concat_result != BRLCAD_OK) {
facetize_failure(s, "unable to import staged NMG Boolean outputs");
return BRLCAD_ERROR;
}
bu_vls_trunc(s->gedp->ged_result_str, 0);
db_update_nref(s->dbip);
return BRLCAD_OK;
}
int
_ged_facetize_objs(struct _ged_facetize_state *s, const FacetizePlan &plan)
{
int ret = BRLCAD_ERROR;
int ok_cnt = 0;
struct db_i *dbip = s->dbip;
RT_CHECK_DBI(dbip);
int argc = (int)plan.inputs.size();
std::vector<const char *> argv = plan.input_argv();
struct directory **dpa = (struct directory **)bu_calloc(argc,
sizeof(struct directory *), "facetize input directory array");
for (int i = 0; i < argc; i++) {
dpa[i] = db_lookup(dbip, argv[i], LOOKUP_QUIET);
if (!dpa[i]) {
facetize_failure(s, "input object '%s' disappeared before evaluation", argv[i]);
bu_free(dpa, "facetize input directory array");
return BRLCAD_ERROR;
}
}
// If we're not doing NMG, use the Manifold booleval
if (!s->in_place) {
ret = _ged_facetize_booleval(s, argc, dpa, oname, false, false);
const char *output_name = plan.execution.writes_in_place() ?
NULL : plan.output.c_str();
if (plan.execution.uses_nmg_boolean()) {
if (_ged_facetize_working_file_setup(s, NULL) != BRLCAD_OK) {
facetize_failure(s,
"unable to prepare the NMG Boolean working database");
goto booleval_cleanup;
}
struct db_i *work_dbip = db_open(bu_vls_cstr(s->wfile),
DB_OPEN_READWRITE);
if (!work_dbip || db_dirbuild(work_dbip) < 0) {
if (work_dbip)
db_close(work_dbip);
facetize_failure(s,
"unable to open the NMG Boolean working database");
goto booleval_cleanup;
}
std::vector<std::string> successful_outputs;
if (!plan.execution.writes_in_place()) {
ret = _ged_facetize_nmgeval(s, work_dbip,
s->dbip->dbi_filename, plan.inputs, output_name);
if (ret == BRLCAD_OK)
successful_outputs.emplace_back(output_name);
} else {
for (int i = 0; i < argc; i++) {
std::vector<std::string> object_input(1, plan.inputs[i]);
ret = _ged_facetize_nmgeval(s, work_dbip,
s->dbip->dbi_filename, object_input, argv[i]);
if (ret == BRLCAD_ERROR && s->tolerate_failures) {
facetize_tolerated_failure(s,
"object '%s' failed during NMG boolean evaluation and was skipped",
argv[i]);
continue;
}
if (ret == BRLCAD_ERROR)
break;
successful_outputs.emplace_back(argv[i]);
}
if (s->tolerate_failures && !successful_outputs.empty())
ret = BRLCAD_OK;
}
db_close(work_dbip);
if (ret == BRLCAD_OK)
ret = facetize_commit_nmg_outputs(s, successful_outputs);
if (ret == BRLCAD_OK)
s->cleanup_workspace = true;
goto booleval_cleanup;
}
if (!plan.execution.writes_in_place()) {
ret = _ged_facetize_booleval(s, argc, dpa, output_name, false, false);
} else {
for (i = 0; i < argc; i++) {
idpa[0] = dpa[i];
idpa[1] = NULL;
ret = _ged_facetize_booleval(s, 1, (struct directory **)idpa, argv[i], false, false);
for (int i = 0; i < argc; i++) {
struct directory *object_dpa[] = {dpa[i], NULL};
ret = _ged_facetize_booleval(s, 1, object_dpa, argv[i], false, false);
if (ret == BRLCAD_ERROR && s->tolerate_failures) {
facetize_tolerated_failure(s, "object '%s' failed during BoT boolean evaluation and was skipped", argv[i]);
facetize_tolerated_failure(s,
"object '%s' failed during BoT boolean evaluation and was skipped",
argv[i]);
continue;
}
if (ret == BRLCAD_ERROR)
@@ -328,11 +240,10 @@ _ged_facetize_objs(struct _ged_facetize_state *s, int argc, const char **argv)
// Report on the primitive processing
facetize_collect_primitive_summary(s);
// After collecting info for summary, we can now clean up working files
bu_dirclear(s->wdir);
s->cleanup_workspace = true;
booleval_cleanup:
bu_free(dpa, "dp array");
bu_free(dpa, "facetize input directory array");
return ret;
}
@@ -348,22 +259,32 @@ ged_facetize_core(struct ged *gedp, int argc, const char *argv[])
long verbosity = 0;
int force_perturb = 0;
int disable_perturb = 0;
method_options_t *method_options = new method_options_t;
struct _ged_facetize_state *s = _ged_facetize_state_create();
s->gedp = gedp;
s->dbip = gedp->dbip;
s->method_opts = method_options;
int nmg_output = 0;
int region_mode = 0;
int in_place = 0;
int nmg_boolean = 0;
int nonoverlap_brep = 0;
bool use_perturbation = false;
GED_CHECK_DATABASE_OPEN(gedp, BRLCAD_ERROR);
GED_CHECK_READ_ONLY(gedp, BRLCAD_ERROR);
GED_CHECK_ARGC_GT_0(gedp, argc, BRLCAD_ERROR);
FacetizeSession session(gedp);
struct _ged_facetize_state *s = session.state();
method_options_t *method_options = session.method_options();
FacetizePlan plan;
/* General options */
struct bu_opt_desc d[25];
struct bu_opt_desc d[26];
BU_OPT(d[ 0], "h", "help", "", NULL, &print_help, "Print help and exit");
BU_OPT(d[ 1], "v", "verbose", "", &bu_opt_incr_long, &verbosity, "Verbose output (multiple flags increase verbosity)");
BU_OPT(d[ 2], "q", "quiet", "", NULL, &quiet, "Suppress all output (overrides verbose flag)");
BU_OPT(d[ 3], "n", "nmg-output", "", NULL, &(s->make_nmg), "Create an N-Manifold Geometry (NMG) object (default is to create a triangular BoT mesh). Note that this will disable most other processing options and may reduce the conversion success rate.");
BU_OPT(d[ 4], "r", "regions", "", NULL, &(s->regions), "For combs, walk the trees and create new copies of the hierarchies with each region's CSG tree replaced by a facetized evaluation of that region. By default, enables perturb methodology (can be disabled - see --no-perturb)");
BU_OPT(d[ 3], "n", "nmg-output", "", NULL, &nmg_output, "Create an N-Manifold Geometry (NMG) object (default is to create a triangular BoT mesh). Note that this will disable most other processing options and may reduce the conversion success rate.");
BU_OPT(d[ 4], "r", "regions", "", NULL, &region_mode, "For combs, walk the trees and create new copies of the hierarchies with each region's CSG tree replaced by a facetized evaluation of that region. By default, enables perturb methodology (can be disabled - see --no-perturb)");
BU_OPT(d[ 5], "s", "suffix", "<str>", &bu_opt_vls, s->suffix, "When creating new objects for facetize outputs, use this suffix to avoid conflicts");
BU_OPT(d[ 6], "p", "prefix", "<str>", &bu_opt_vls, s->prefix, "When creating new objects for facetize, use this prefix to avoid conflicts");
BU_OPT(d[ 7], "", "in-place", "", NULL, &(s->in_place), "Replace the specified object(s) with their facetizations. (Warning: this option changes pre-existing geometry!)");
BU_OPT(d[ 7], "", "in-place", "", NULL, &in_place, "Replace the specified object(s) with their facetizations. (Warning: this option changes pre-existing geometry!)");
BU_OPT(d[ 8], "", "max-time", "#", &bu_opt_int, &(s->max_time), "Maximum time to spend per object (in seconds). Default is method specific. Note that specifying shorter times may cut off conversions (particularly using sampling methods) that could succeed with longer runtimes. Per-method time limits can also be adjusted to allow longer runtimes on slower methods.");
BU_OPT(d[ 9], "", "max-pnts", "#", &bu_opt_int, &(s->max_pnts), "Maximum number of pnts per object to use when applying ray sampling methods.");
BU_OPT(d[10], "", "resume", "", NULL, &(s->resume), "Resume an interrupted conversion");
@@ -371,20 +292,17 @@ ged_facetize_core(struct ged *gedp, int argc, const char *argv[])
BU_OPT(d[12], "", "method-opts", "METHOD opt1=val opt2=val...", &_tess_method_opts, method_options, "For the specified method, set the specified options.");
BU_OPT(d[13], "", "no-empty", "", NULL, &(s->no_empty), "Do not output empty BoT objects if the boolean evaluation results in an empty solid.");
BU_OPT(d[14], "", "log-file", "<filename>", &bu_opt_vls, s->log_file, "Specify a location to use for the log file.");
BU_OPT(d[15], "", "nmg-booleval", "", NULL, &s->nmg_booleval, "Use libnmg Boolean evaluation algorithm, even if we're producing a BoT. Less robust, but if it succeeds it may produce cleaner output for coplanar inputs.");
BU_OPT(d[15], "", "nmg-booleval", "", NULL, &nmg_boolean, "Use libnmg Boolean evaluation algorithm, even if we're producing a BoT. Less robust, but if it succeeds it may produce cleaner output for coplanar inputs.");
BU_OPT(d[16], "", "disable-fixup", "", NULL, &s->no_fixup, "Disable post-processing steps intended to improve generated meshes.");
BU_OPT(d[17], "", "perturb", "", NULL, &force_perturb, "Enable the coplanarity-avoidance perturbation step (overrides non -r option default, conflicts with --no-perturb).");
BU_OPT(d[18], "", "no-perturb", "", NULL, &disable_perturb, "Disable the coplanarity-avoidance perturbation step (overrides -r option default, conflicts with --perturb).");
BU_OPT(d[19], "B", "", "", NULL, &s->nonovlp_brep, "EXPERIMENTAL: non-overlapping facetization to BoT objects of union-only brep comb tree.");
BU_OPT(d[19], "B", "", "", NULL, &nonoverlap_brep, "EXPERIMENTAL: non-overlapping facetization to BoT objects of union-only brep comb tree.");
BU_OPT(d[20], "t", "threshold", "#", &bu_opt_fastf_t, &s->nonovlp_threshold, "EXPERIMENTAL: max ovlp threshold length for -B mode.");
BU_OPT(d[21], "", "perturb-sa-tol", "#", &bu_opt_fastf_t, &s->perturb_sa_tol, "Surface-area percentage threshold (0-100) that triggers the coplanarity-avoidance perturb retry when the CSG Crofton SA differs from the BoT SA by more than this amount. Default is 10.");
BU_OPT(d[22], "", "perturb-vol-tol", "#", &bu_opt_fastf_t, &s->perturb_vol_tol, "Volume percentage threshold (0-100) that triggers the coplanarity-avoidance perturb retry when the CSG Crofton volume differs from the BoT volume by more than this amount. Default is 10.");
BU_OPT(d[23], "", "tolerate-failures", "", NULL, &s->tolerate_failures, "Continue after failed primitive or subtree evaluations and generate a partial result. The output will not be a complete representation of the input if any failures are tolerated.");
BU_OPT_NULL(d[24]);
GED_CHECK_DATABASE_OPEN(gedp, BRLCAD_ERROR);
GED_CHECK_READ_ONLY(gedp, BRLCAD_ERROR);
GED_CHECK_ARGC_GT_0(gedp, argc, BRLCAD_ERROR);
BU_OPT(d[24], "j", "jobs", "#", &bu_opt_int, &s->max_workers, "Maximum number of facetize worker processes. The default is selected conservatively from CPU and memory availability.");
BU_OPT_NULL(d[25]);
/* skip command name argv[0] */
argc-=(argc>0); argv+=(argc>0);
@@ -399,7 +317,7 @@ ged_facetize_core(struct ged *gedp, int argc, const char *argv[])
bu_vls_printf(gedp->ged_result_str, "option parsing failed: %s\n", bu_vls_cstr(&omsg));
ret = BRLCAD_ERROR;
bu_vls_free(&omsg);
goto ged_facetize_memfree;
goto ged_facetize_done;
}
bu_vls_free(&omsg);
@@ -407,17 +325,49 @@ ged_facetize_core(struct ged *gedp, int argc, const char *argv[])
if (force_perturb && disable_perturb) {
bu_vls_printf(gedp->ged_result_str, "Can only specify one of --perturb or --no-perturb\n");
ret = BRLCAD_ERROR;
goto ged_facetize_memfree;
goto ged_facetize_done;
}
if (s->max_workers < 0 || s->max_workers > MAX_PSW) {
bu_vls_printf(gedp->ged_result_str,
"--jobs must be between 0 and %d\n", MAX_PSW);
ret = BRLCAD_ERROR;
goto ged_facetize_done;
}
if (s->max_time < 0) {
bu_vls_printf(gedp->ged_result_str,
"--max-time must be greater than or equal to 0\n");
ret = BRLCAD_ERROR;
goto ged_facetize_done;
}
if (s->max_pnts < 0) {
bu_vls_printf(gedp->ged_result_str,
"--max-pnts must be greater than or equal to 0\n");
ret = BRLCAD_ERROR;
goto ged_facetize_done;
}
// Set no_perturb according to options. Enable by default for -r, disable
// if not doing -r (one large BoT, generally a less likely candidate for
// worrying about volume/surf_area matching.)
s->no_perturb = (s->regions) ? 0 : 1;
if (region_mode && nonoverlap_brep) {
bu_vls_printf(gedp->ged_result_str,
"--regions and -B select different processing scopes and cannot be combined\n");
ret = BRLCAD_ERROR;
goto ged_facetize_done;
}
s->execution.output_format = nmg_output ?
FacetizeOutputFormat::Nmg : FacetizeOutputFormat::Bot;
s->execution.boolean_engine = (nmg_output || nmg_boolean) ?
FacetizeBooleanEngine::Nmg : FacetizeBooleanEngine::Manifold;
s->execution.scope = nonoverlap_brep ? FacetizeScope::NonOverlappingBrep :
(region_mode ? FacetizeScope::Regions : FacetizeScope::Objects);
s->execution.commit_mode = in_place ?
FacetizeCommitMode::InPlace : FacetizeCommitMode::NewObject;
use_perturbation = region_mode;
if (disable_perturb)
s->no_perturb = 1;
use_perturbation = false;
if (force_perturb)
s->no_perturb = 0;
use_perturbation = true;
s->execution.perturb_mode = use_perturbation ?
FacetizePerturbMode::Enabled : FacetizePerturbMode::Disabled;
s->verbosity = (int)verbosity;
@@ -442,88 +392,38 @@ ged_facetize_core(struct ged *gedp, int argc, const char *argv[])
s->verbosity = -1;
/* Don't allow incorrect type suffixes */
if (s->make_nmg && BU_STR_EQUAL(bu_vls_cstr(s->solid_suffix), ".bot")) {
if (s->execution.writes_nmg() && BU_STR_EQUAL(bu_vls_cstr(s->solid_suffix), ".bot")) {
bu_vls_sprintf(s->solid_suffix, ".nmg");
}
if (!s->make_nmg && BU_STR_EQUAL(bu_vls_cstr(s->solid_suffix), ".nmg")) {
if (!s->execution.writes_nmg() && BU_STR_EQUAL(bu_vls_cstr(s->solid_suffix), ".nmg")) {
bu_vls_sprintf(s->solid_suffix, ".bot");
}
/* Check if we want/need help */
need_help += (argc < 1);
need_help += (argc < 2 && !s->in_place && !s->regions && !s->resume && !s->nonovlp_brep);
if (print_help || need_help || argc < 1) {
need_help = argc < (s->execution.writes_in_place() ? 1 : 2);
if (print_help || need_help) {
_ged_cmd_help(gedp, usage, d);
_facetize_methods_help(gedp);
ret = (need_help) ? BRLCAD_ERROR : BRLCAD_OK;
goto ged_facetize_memfree;
goto ged_facetize_done;
}
/* Beyond this point, we're likely to need info on the cache directory. Generate some
* paths and strings we will need. */
{
// Get the root filename
char rfname[MAXPATHLEN];
bu_file_realpath(gedp->dbip->dbi_filename, rfname);
bu_path_component(s->bname, rfname, BU_PATH_BASENAME);
// Hash the path string and construct a location in the cache directory
unsigned long long hash_num = bu_data_hash((void *)bu_vls_cstr(s->bname), bu_vls_strlen(s->bname));
struct bu_vls dname = BU_VLS_INIT_ZERO;
bu_vls_sprintf(&dname, "facetize_%llu", hash_num);
s->wdir = (char *)bu_calloc(MAXPATHLEN, sizeof(char), "wdir");
bu_dir(s->wdir, MAXPATHLEN, BU_DIR_CACHE, bu_vls_cstr(&dname), NULL);
// If we're starting over, clear the old working directory
if (!s->resume && bu_file_directory(s->wdir)) {
bu_dirclear(s->wdir);
}
if (!bu_file_directory(s->wdir)) {
// Set up the directory
bu_mkdir(s->wdir);
}
if (!bu_vls_strlen(s->log_file)) {
char tmplfile[MAXPATHLEN];
s->log_file_is_temporary = 1;
bu_vls_sprintf(&dname, "facetize_%s.log", bu_vls_cstr(s->bname));
bu_dir(tmplfile, MAXPATHLEN, s->wdir, bu_vls_cstr(&dname), NULL);
bu_vls_sprintf(s->log_file, "%s", tmplfile);
}
bu_vls_free(&dname);
s->lfile = fopen(bu_vls_cstr(s->log_file), "a");
if (!s->lfile) {
bu_vls_printf(gedp->ged_result_str, "Unable to open log file %s for writing\n", bu_vls_cstr(s->log_file));
ret = BRLCAD_ERROR;
goto ged_facetize_memfree;
}
if (s->execution.processes_breps() &&
NEAR_ZERO(s->nonovlp_threshold, SMALL_FASTF)) {
bu_vls_printf(gedp->ged_result_str,
"-B option requires a specified length threshold\n");
ret = BRLCAD_ERROR;
goto ged_facetize_done;
}
/* If we're doing the experimental brep-only logic, it's a separate process */
if (s->nonovlp_brep) {
if (NEAR_ZERO(s->nonovlp_threshold, SMALL_FASTF)) {
bu_vls_printf(gedp->ged_result_str, "-B option requires a specified length threshold\n");
ret = BRLCAD_ERROR;
goto ged_facetize_memfree;
}
ret = _nonovlp_brep_facetize(s, argc, argv);
goto ged_facetize_memfree;
if (facetize_build_plan(s, argc, argv, plan) != BRLCAD_OK ||
facetize_prepare_workspace(s, plan) != BRLCAD_OK) {
ret = BRLCAD_ERROR;
goto ged_facetize_done;
}
ret = facetize_execute_plan(s, plan);
/* Multi-region mode has a different processing logic */
if (s->regions) {
ret = _ged_facetize_regions(s, argc, argv);
} else {
ret = _ged_facetize_objs(s, argc, argv);
}
ged_facetize_memfree:
facetize_summary(s);
_ged_facetize_state_destroy(s);
delete method_options;
ged_facetize_done:
return ret;
}
+28 -17
View File
@@ -36,11 +36,12 @@
#include "vmath.h"
#include "bu/vls.h"
#include "bn/tol.h"
#include "bg/spsr.h"
#include "rt/geom.h"
#include "raytrace.h"
#include "ged/defines.h"
#include "./orchestrator.h"
__BEGIN_DECLS
#define FACETIZE_METHOD_ATTR "facetize_method"
@@ -50,10 +51,7 @@ struct _ged_facetize_state {
// Output
int verbosity;
int no_empty;
int make_nmg;
int nonovlp_brep;
int no_fixup;
int no_perturb;
int use_variant_plan;
int tolerate_failures;
int tolerated_failures;
@@ -68,6 +66,7 @@ struct _ged_facetize_state {
fastf_t perturb_vol_tol;
char *wdir;
bool cleanup_workspace;
struct bu_vls *wfile;
struct bu_vls *bname;
struct bu_vls *log_file;
@@ -80,14 +79,13 @@ struct _ged_facetize_state {
struct bu_vls *inspection_log;
// Processing
int regions;
FacetizeExecutionOptions execution;
int resume;
int in_place;
int nmg_booleval;
// Settings
int max_time;
int max_pnts;
int max_workers;
struct bu_vls *prefix;
struct bu_vls *suffix;
@@ -102,7 +100,6 @@ struct _ged_facetize_state {
struct db_i *dbip;
union tree *facetize_tree;
void *method_opts;
void *log_s;
/* Cache-local write measurements seed and refine worker write deadlines. */
int write_profiled;
@@ -132,13 +129,15 @@ extern int
_db_uniq_test(struct bu_vls *n, void *data);
extern int
_ged_validate_objs_list(struct _ged_facetize_state *s, int argc, const char *argv[], int newobj_cnt);
_ged_facetize_regions(struct _ged_facetize_state *s, const FacetizePlan &plan);
extern int
_ged_facetize_regions(struct _ged_facetize_state *s, int argc, const char **argv);
_ged_facetize_objs(struct _ged_facetize_state *s, const FacetizePlan &plan);
extern int
_ged_facetize_nmgeval(struct _ged_facetize_state *s, int argc, const char **argv, const char *newname);
_ged_facetize_nmgeval(struct _ged_facetize_state *s,
struct db_i *target_dbip, const char *database_path,
const std::vector<std::string> &input_names, const char *newname);
extern int
_ged_facetize_booleval(struct _ged_facetize_state *s, int argc, struct directory **dpa, const char *newname, bool output_to_working, bool cleanup);
@@ -158,7 +157,16 @@ _ged_facetize_leaves_tri(struct _ged_facetize_state *s, struct db_i *dbip, struc
extern int
_ged_facetize_booleval_tri(struct _ged_facetize_state *s, struct db_i *dbip, struct rt_wdb *wdbp, int argc, const char **argv, const char *newname, struct bu_list *vlfree, bool output_to_working, int curr_cnt, int total_cnt);
extern int _nonovlp_brep_facetize(struct _ged_facetize_state *s, int argc, const char **argv);
/**
* Evaluate a tessellated CSG tree and write the resulting BoT to an explicit
* database. The input database is never modified unless it is also supplied
* as @p output_dbip. This form lets isolated workers evaluate into an
* in-memory database before entering the staged-write protocol.
*/
extern int
_ged_facetize_booleval_tri_to_db(struct _ged_facetize_state *s, struct db_i *dbip, struct rt_wdb *wdbp, int argc, const char **argv, const char *newname, struct bu_list *vlfree, struct db_i *output_dbip, int curr_cnt, int total_cnt);
extern int _nonovlp_brep_facetize(struct _ged_facetize_state *s, const FacetizePlan &plan);
extern struct rt_bot_internal *
bot_fixup(struct _ged_facetize_state *s, struct db_i *wdbip, struct directory *bot_dp, const char *bname);
@@ -229,17 +237,20 @@ struct FacetizeVariantPlan {
* Returns an allocated FacetizeVariantPlan owned by the caller (or NULL on
* allocation failure). Primitives without ft_perturb support are counted in
* n_perturb_fallbacks and will fall back to the original mesh at booleval time.
* If working_dbip is non-NULL it must be an indexed writable handle for the
* working database; it is borrowed and remains open on return.
*/
extern FacetizeVariantPlan *
_ged_facetize_build_variant_plan(struct _ged_facetize_state *s,
int argc,
struct directory **dpa);
struct directory **dpa,
struct db_i *working_dbip);
/**
* Tessellate the variant primitives in the working .g using the NMG method.
* Called after _ged_facetize_leaves_tri() once original leaves are already
* BoTs, either eagerly for direct booleval or lazily on the first
* validation-triggered retry in region mode. Updates
* Tessellate the variant primitives in the working .g using the configured
* primitive methods and options. Called after _ged_facetize_leaves_tri() once
* original leaves are already BoTs, either eagerly for direct booleval or
* lazily on the first validation-triggered retry in region mode. Updates
* plan->n_variant_tess_failures for any variants that could not be
* tessellated (they will silently fall back to the original mesh at booleval).
*/
+158 -380
View File
@@ -19,405 +19,183 @@
*/
/** @file libged/facetize/nmg.cpp
*
* Classic NMG boolean evaluation path.
*
* Parent-side launch policy for isolated NMG Boolean evaluation.
*/
#include "common.h"
#include <string.h>
#include "bio.h"
#include <string>
#include <vector>
#include "bu/app.h"
#include "bu/datetime.h"
#include "bu/file.h"
#include "bu/hook.h"
#include "bu/vls.h"
#include "bu/log.h"
#include "bu/path.h"
#include "bu/process.h"
#include "bu/snooze.h"
#include "raytrace.h"
#include "../ged_private.h"
#include "./ged_facetize.h"
#include "./process.h"
#include "./transfer.h"
#include "./worker.h"
struct _ged_facetize_logging_state {
struct bu_hook_list *saved_bomb_hooks;
struct bu_hook_list *saved_log_hooks;
struct bu_vls *nmg_log;
struct bu_vls *nmg_log_header;
int nmg_log_print_header;
int stderr_stashed;
int serr;
int fnull;
};
extern "C" {
static int
_facetize_bomb_hook(void *cdata, void *str)
{
struct _ged_facetize_state *o = (struct _ged_facetize_state *)cdata;
struct _ged_facetize_logging_state *log_s = (struct _ged_facetize_logging_state *)o->log_s;
if (log_s->nmg_log_print_header) {
bu_vls_printf(log_s->nmg_log, "%s\n", bu_vls_addr(log_s->nmg_log_header));
log_s->nmg_log_print_header = 0;
}
bu_vls_printf(log_s->nmg_log, "%s\n", (const char *)str);
return 0;
}
static int
_facetize_nmg_logging_hook(void *data, void *str)
{
struct _ged_facetize_state *o = (struct _ged_facetize_state *)data;
struct _ged_facetize_logging_state *log_s = (struct _ged_facetize_logging_state *)o->log_s;
if (log_s->nmg_log_print_header) {
bu_vls_printf(log_s->nmg_log, "%s\n", bu_vls_addr(log_s->nmg_log_header));
log_s->nmg_log_print_header = 0;
}
bu_vls_printf(log_s->nmg_log, "%s\n", (const char *)str);
return 0;
}
static void
_facetize_log_nmg(struct _ged_facetize_state *o)
{
if (fileno(stderr) < 0)
return;
/* Seriously, bu_bomb, we don't want you blathering
* to stderr... shut down stderr temporarily. */
struct _ged_facetize_logging_state *log_s = (struct _ged_facetize_logging_state *)o->log_s;
log_s->fnull = open(bu_file_null(), O_WRONLY);
if (log_s->fnull != -1) {
log_s->serr = fileno(stderr);
log_s->stderr_stashed = dup(log_s->serr);
dup2(log_s->fnull, log_s->serr);
close(log_s->fnull);
}
/* Set bu_log logging to capture in nmg_log, rather than the
* application defaults */
bu_log_hook_delete_all();
bu_log_add_hook(_facetize_nmg_logging_hook, (void *)o);
/* Also engage the nmg bomb hooks */
bu_bomb_delete_all_hooks();
bu_bomb_add_hook(_facetize_bomb_hook, (void *)o);
}
static void
_facetize_log_default(struct _ged_facetize_state *o)
{
if (fileno(stderr) < 0 || !o || !o->log_s)
return;
/* Put stderr back */
struct _ged_facetize_logging_state *log_s = (struct _ged_facetize_logging_state *)o->log_s;
if (log_s->fnull != -1) {
fflush(stderr);
dup2(log_s->stderr_stashed, log_s->serr);
close(log_s->stderr_stashed);
log_s->fnull = -1;
}
/* Restore bu_bomb hooks to the application defaults */
bu_bomb_delete_all_hooks();
bu_bomb_restore_hooks(log_s->saved_bomb_hooks);
/* Restore bu_log hooks to the application defaults */
bu_log_hook_delete_all();
bu_log_hook_restore_all(log_s->saved_log_hooks);
}
}
static union tree *
facetize_region_end(struct db_tree_state *tsp,
const struct db_full_path *pathp,
union tree *curtree,
void *client_data)
{
union tree **facetize_tree;
if (tsp) RT_CK_DBTS(tsp);
if (pathp) RT_CK_FULL_PATH(pathp);
struct _ged_facetize_state *s = (struct _ged_facetize_state *)client_data;
facetize_tree = &s->facetize_tree;
if (curtree->tr_op == OP_NOP) return curtree;
if (*facetize_tree) {
union tree *tr;
BU_ALLOC(tr, union tree);
RT_TREE_INIT(tr);
tr->tr_op = OP_UNION;
tr->tr_b.tb_regionp = REGION_NULL;
tr->tr_b.tb_left = *facetize_tree;
tr->tr_b.tb_right = curtree;
*facetize_tree = tr;
} else {
*facetize_tree = curtree;
}
/* Tree has been saved, and will be freed later */
return TREE_NULL;
}
static union tree *
facetize_nmg_leaf_tess(struct db_tree_state *tsp, const struct db_full_path *pathp, struct rt_db_internal *ip, void *client_data)
{
union tree *ret = rt_booltree_leaf_tess(tsp, pathp, ip, NULL);
if (!ret) {
struct _ged_facetize_state *s = (struct _ged_facetize_state *)client_data;
if (s && s->tolerate_failures && pathp) {
char *path_str = db_path_to_string(pathp);
facetize_tolerated_failure(s, "NMG leaf tessellation failed for '%s'; leaf will be omitted from boolean evaluation", path_str ? path_str : "(unknown)");
if (path_str)
bu_free(path_str, "path string");
}
}
return ret;
}
static struct model *
_try_nmg_facetize(struct _ged_facetize_state *s, struct bu_list *vlfree, int argc, const char **argv)
{
struct db_i *dbip = s->dbip;
int i;
int failed = 0;
struct db_tree_state init_state;
union tree *facetize_tree;
struct model *nmg_model;
struct rt_wdb *wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_DEFAULT);
struct _ged_facetize_logging_state *log_s;
BU_GET(log_s, struct _ged_facetize_logging_state);
BU_GET(log_s->nmg_log, struct bu_vls);
bu_vls_init(log_s->nmg_log);
BU_GET(log_s->nmg_log_header, struct bu_vls);
bu_vls_init(log_s->nmg_log_header);
s->log_s = log_s;
_facetize_log_nmg(s);
db_init_db_tree_state(&init_state, dbip);
/* Establish tolerances */
init_state.ts_ttol = &wdbp->wdb_ttol;
init_state.ts_tol = &wdbp->wdb_tol;
s->facetize_tree = (union tree *)0;
facetize_tree = (union tree *)0;
nmg_model = nmg_mm();
init_state.ts_m = &nmg_model;
if (!BU_SETJUMP) {
/* try */
i = db_walk_tree(dbip, argc, (const char **)argv,
1,
&init_state,
0, /* take all regions */
facetize_region_end,
facetize_nmg_leaf_tess,
(void *)s
);
} else {
/* catch */
BU_UNSETJUMP;
_facetize_log_default(s);
bu_vls_free(log_s->nmg_log_header);
bu_vls_free(log_s->nmg_log);
BU_PUT(log_s->nmg_log_header, struct _ged_facetize_logging_state);
BU_PUT(log_s->nmg_log, struct _ged_facetize_logging_state);
BU_PUT(log_s, struct _ged_facetize_logging_state);
s->log_s = NULL;
if (s->facetize_tree) {
db_free_tree(s->facetize_tree);
s->facetize_tree = NULL;
}
return NULL;
} BU_UNSETJUMP;
facetize_tree = s->facetize_tree;
if (i < 0) {
/* Destroy NMG */
_facetize_log_default(s);
bu_vls_free(log_s->nmg_log_header);
bu_vls_free(log_s->nmg_log);
BU_PUT(log_s->nmg_log_header, struct _ged_facetize_logging_state);
BU_PUT(log_s->nmg_log, struct _ged_facetize_logging_state);
BU_PUT(log_s, struct _ged_facetize_logging_state);
s->log_s = NULL;
if (s->facetize_tree) {
db_free_tree(s->facetize_tree);
s->facetize_tree = NULL;
}
return NULL;
}
if (facetize_tree) {
if (!BU_SETJUMP) {
/* try */
failed = nmg_boolean(facetize_tree, nmg_model, vlfree, &wdbp->wdb_tol);
} else {
/* catch */
BU_UNSETJUMP;
_facetize_log_default(s);
bu_vls_free(log_s->nmg_log_header);
bu_vls_free(log_s->nmg_log);
BU_PUT(log_s->nmg_log_header, struct _ged_facetize_logging_state);
BU_PUT(log_s->nmg_log, struct _ged_facetize_logging_state);
BU_PUT(log_s, struct _ged_facetize_logging_state);
s->log_s = NULL;
if (s->facetize_tree) {
db_free_tree(s->facetize_tree);
s->facetize_tree = NULL;
}
return NULL;
} BU_UNSETJUMP;
} else {
failed = 1;
}
if (!failed && facetize_tree) {
NMG_CK_REGION(facetize_tree->tr_d.td_r);
facetize_tree->tr_d.td_r = (struct nmgregion *)NULL;
}
if (failed && s->tolerate_failures) {
facetize_tolerated_failure(s, "NMG boolean evaluation failed after leaf tessellation; no partial NMG result could be generated for this tree");
}
if (facetize_tree) {
db_free_tree(facetize_tree);
s->facetize_tree = NULL;
}
_facetize_log_default(s);
bu_vls_free(log_s->nmg_log_header);
bu_vls_free(log_s->nmg_log);
BU_PUT(log_s->nmg_log_header, struct _ged_facetize_logging_state);
BU_PUT(log_s->nmg_log, struct _ged_facetize_logging_state);
BU_PUT(log_s, struct _ged_facetize_logging_state);
s->log_s = NULL;
return (failed) ? NULL : nmg_model;
}
static int
_write_nmg(struct _ged_facetize_state *s, struct model *nmg_model, const char *name)
{
struct db_i *dbip = s->dbip;
struct rt_db_internal intern;
struct directory *dp;
/* Export NMG as a new solid */
RT_DB_INTERNAL_INIT(&intern);
intern.idb_major_type = DB5_MAJORTYPE_BRLCAD;
intern.idb_type = ID_NMG;
intern.idb_meth = &OBJ[ID_NMG];
intern.idb_ptr = (void *)nmg_model;
dp = db_diradd(dbip, name, RT_DIR_PHONY_ADDR, 0, RT_DIR_SOLID, (void *)&intern.idb_type);
if (dp == RT_DIR_NULL) {
if (s->verbosity > 0) {
bu_log("Cannot add %s to directory\n", name);
}
return BRLCAD_ERROR;
}
if (rt_db_put_internal(dp, dbip, &intern) < 0) {
if (s->verbosity > 0) {
bu_log("Failed to write %s to database\n", name);
}
rt_db_free_internal(&intern);
return BRLCAD_ERROR;
}
return BRLCAD_OK;
}
static int
_write_bot(struct _ged_facetize_state *s, struct rt_bot_internal *bot, const char *name)
{
struct db_i *dbip = s->dbip;
struct rt_db_internal intern;
struct directory *dp;
/* Export BoT as a new solid */
RT_DB_INTERNAL_INIT(&intern);
intern.idb_major_type = DB5_MAJORTYPE_BRLCAD;
intern.idb_type = ID_BOT;
intern.idb_meth = &OBJ[ID_BOT];
intern.idb_ptr = (void *)bot;
dp = db_diradd(dbip, name, RT_DIR_PHONY_ADDR, 0, RT_DIR_SOLID, (void *)&intern.idb_type);
if (dp == RT_DIR_NULL) {
if (s->verbosity > 0) {
bu_log("Cannot add %s to directory\n", name);
}
return BRLCAD_ERROR;
}
if (rt_db_put_internal(dp, dbip, &intern) < 0) {
if (s->verbosity > 0) {
bu_log("Failed to write %s to database\n", name);
}
rt_db_free_internal(&intern);
return BRLCAD_ERROR;
}
return BRLCAD_OK;
}
static const int FACETIZE_NMG_POLL_USEC = 1000;
static const char FACETIZE_NMG_RESULT_FILE[] = "nmg_boolean_result.g";
int
_ged_facetize_nmgeval(struct _ged_facetize_state *s, int argc, const char **argv, const char *oname)
_ged_facetize_nmgeval(struct _ged_facetize_state *s,
struct db_i *target_dbip, const char *database_path,
const std::vector<std::string> &input_names, const char *output_name)
{
int ret = BRLCAD_OK;
struct db_i *dbip = s->dbip;
struct rt_wdb *wdbp;
struct rt_bot_internal *bot = NULL;
struct bu_list *vlfree = &rt_vlfree;
struct model *nmg_model = _try_nmg_facetize(s, vlfree, argc, argv);
if (!s || !target_dbip || !database_path || !database_path[0] ||
input_names.empty() || !output_name || !output_name[0] || !s->wdir)
return BRLCAD_ERROR;
if (nmg_model == NULL) {
if (s->verbosity > 1) {
bu_log("NMG(%s): no resulting region, aborting\n", oname);
FacetizeWorkerRequest request;
request.operation = s->execution.writes_nmg() ?
FacetizeWorkerOperation::NmgBooleanToNmg :
FacetizeWorkerOperation::NmgBooleanToBot;
request.input_names = input_names;
request.output_name = output_name;
if (!request.valid())
return BRLCAD_ERROR;
char result_file[MAXPATHLEN];
bu_dir(result_file, MAXPATHLEN, s->wdir, FACETIZE_NMG_RESULT_FILE, NULL);
(void)bu_file_delete(result_file);
char executable[MAXPATHLEN];
bu_dir(executable, MAXPATHLEN, BU_DIR_BIN, "ged_exec", BU_DIR_EXT, NULL);
std::vector<std::string> command;
command.push_back(executable);
command.push_back("facetize_process");
command.push_back(database_path);
struct bu_process *process = NULL;
FILE *process_input = NULL;
FacetizeWorkerClient channel;
if (facetize_process_start(&process, &process_input, channel, command,
result_file, "--nmg-server") != BRLCAD_OK) {
(void)bu_file_delete(result_file);
facetize_log(s, 0,
"FACETIZE: unable to start the NMG Boolean worker for %s\n",
output_name);
return BRLCAD_ERROR;
}
if (!channel.send_request(request)) {
(void)facetize_process_stop(s, &process, process_input, channel, true);
(void)bu_file_delete(result_file);
facetize_log(s, 0,
"FACETIZE: unable to submit %s to the NMG Boolean worker\n",
output_name);
return BRLCAD_ERROR;
}
facetize_log(s, 1, "FACETIZE: evaluating %s with NMG Boolean...\n",
output_name);
FacetizeWorkerStatus status;
int64_t request_start = bu_gettime();
int64_t write_deadline = 0;
bool write_permitted = false;
bool interrupted = false;
bool evaluation_timed_out = false;
bool write_timed_out = false;
while (!status.result_received) {
facetize_process_drain_stdout(s, process, channel, &status);
facetize_process_drain_stderr(s, process);
int64_t now = bu_gettime();
if (status.write_ready && !write_permitted) {
if (s->max_time > 0 && now - request_start >=
BU_SEC2USEC(s->max_time)) {
interrupted = true;
evaluation_timed_out = true;
break;
}
write_permitted = true;
double write_timeout = facetize_write_timeout_seconds(
status.payload_size, 0.0, 0.0);
write_deadline = now + BU_SEC2USEC(write_timeout);
if (!channel.send_write_proceed()) {
interrupted = true;
break;
}
continue;
}
ret = BRLCAD_ERROR;
goto ged_nmg_obj_memfree;
if (status.result_received) {
if (status.write_started && !status.write_done)
interrupted = true;
break;
}
if (bu_process_poll(process, NULL) != 0) {
/* The final status may reach the pipe just before process exit.
* Drain once more before deciding the worker was interrupted. */
facetize_process_drain_stdout(s, process, channel, &status);
facetize_process_drain_stderr(s, process);
interrupted = !status.result_received ||
(status.write_started && !status.write_done);
break;
}
if (!write_permitted && s->max_time > 0 &&
now - request_start >= BU_SEC2USEC(s->max_time)) {
interrupted = true;
evaluation_timed_out = true;
break;
}
if (write_permitted && now >= write_deadline) {
interrupted = true;
write_timed_out = true;
break;
}
(void)bu_snooze(FACETIZE_NMG_POLL_USEC);
}
if (!s->make_nmg) {
wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_DEFAULT);
if (!BU_SETJUMP) {
/* try */
bot = (struct rt_bot_internal *)nmg_mdl_to_bot(nmg_model, vlfree, &wdbp->wdb_tol);
} else {
/* catch */
BU_UNSETJUMP;
return BRLCAD_ERROR;
} BU_UNSETJUMP;
ret = _write_bot(s, bot, oname);
int process_status = facetize_process_stop(s, &process, process_input,
channel, interrupted);
int ret = BRLCAD_ERROR;
if (!interrupted && process_status == BRLCAD_OK &&
status.result_received && status.write_done &&
status.result == BRLCAD_OK) {
int expected_type = request.operation ==
FacetizeWorkerOperation::NmgBooleanToNmg ? ID_NMG : ID_BOT;
ret = facetize_transfer_staged_object(target_dbip, result_file,
output_name, expected_type);
}
(void)bu_file_delete(result_file);
if (ret == BRLCAD_OK) {
facetize_log(s, 1,
"FACETIZE: NMG Boolean evaluation succeeded for %s\n",
output_name);
return BRLCAD_OK;
}
if (evaluation_timed_out) {
facetize_log(s, 0,
"FACETIZE: NMG Boolean evaluation timed out after %d seconds for %s\n",
s->max_time, output_name);
} else if (write_timed_out) {
facetize_log(s, 0,
"FACETIZE: staging the NMG Boolean result timed out for %s\n",
output_name);
} else if (interrupted) {
facetize_log(s, 0,
"FACETIZE: NMG Boolean worker exited while processing %s\n",
output_name);
} else if (process_status != BRLCAD_OK) {
facetize_log(s, 0,
"FACETIZE: NMG Boolean worker exited abnormally after processing %s\n",
output_name);
} else if (status.result != BRLCAD_OK) {
facetize_log(s, 0,
"FACETIZE: NMG Boolean evaluation failed for %s\n",
output_name);
} else {
/* Write the NMG */
ret = _write_nmg(s, nmg_model, oname);
facetize_log(s, 0,
"FACETIZE: unable to transfer the staged NMG Boolean result for %s\n",
output_name);
}
ged_nmg_obj_memfree:
if (s->verbosity >= 0 && ret != BRLCAD_OK) {
bu_log("NMG: failed to generate %s\n", oname);
}
return ret;
return BRLCAD_ERROR;
}
// Local Variables:
File diff suppressed because it is too large Load Diff
-92
View File
@@ -1,92 +0,0 @@
/* O L D _ F A C E T I Z E . H
* BRL-CAD
*
* Copyright (c) 2008-2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
/** @file libged/old_facetize.h
*
* The facetize command, circa v7.38.2
*
*/
#include "common.h"
#include "vmath.h"
#include "bu.h"
#include "bg.h"
__BEGIN_DECLS
/* Ideally all of this could be in facetize.cpp, but the open() calls
* used by the logging routines are problematic in C++ with Visual C++. */
struct _old_ged_facetize_opts {
int quiet;
int verbosity;
int regions;
int resume;
int retry;
int in_place;
fastf_t feature_size;
fastf_t feature_scale;
fastf_t d_feature_size;
int max_time;
struct bg_tess_tol *tol;
struct bu_vls *faceted_suffix;
/* NMG specific options */
int triangulate;
int make_nmg;
int nmgbool;
int manifold;
int screened_poisson;
int continuation;
int method_flags;
int nmg_use_tnurbs;
/* Poisson specific options */
int max_pnts;
struct bg_3d_spsr_opts s_opts;
/* Brep specific options */
double nonovlp_threshold;
/* internal */
struct bu_attribute_value_set *c_map;
struct bu_attribute_value_set *s_map;
struct bu_hook_list *saved_bomb_hooks;
struct bu_hook_list *saved_log_hooks;
struct bu_vls *nmg_log;
struct bu_vls *nmg_log_header;
int nmg_log_print_header;
int stderr_stashed;
int serr;
int fnull;
struct bu_vls *froot;
struct bu_vls *manifold_comb;
struct bu_vls *nmg_comb;
struct bu_vls *continuation_comb;
struct bu_vls *spsr_comb;
};
extern void
_old_ged_facetize_log_nmg(struct _old_ged_facetize_opts *o);
extern void
_old_ged_facetize_log_default(struct _old_ged_facetize_opts *o);
__END_DECLS
-119
View File
@@ -1,119 +0,0 @@
/* F A C E T I Z E _ L O G . C
* BRL-CAD
*
* Copyright (c) 2008-2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
/** @file libged/facetize_log.c
*
* logging mechanisms for the facetize command.
*
*/
#include "common.h"
#include <string.h>
#include "bio.h"
#include "bu/hook.h"
#include "bu/vls.h"
#include "../ged_private.h"
#include "./old_facetize.h"
static int
_ged_facetize_bomb_hook(void *cdata, void *str)
{
struct _old_ged_facetize_opts *o = (struct _old_ged_facetize_opts *)cdata;
if (o->nmg_log_print_header) {
bu_vls_printf(o->nmg_log, "%s\n", bu_vls_addr(o->nmg_log_header));
o->nmg_log_print_header = 0;
}
bu_vls_printf(o->nmg_log, "%s\n", (const char *)str);
return 0;
}
static int
_ged_facetize_nmg_logging_hook(void *data, void *str)
{
struct _old_ged_facetize_opts *o = (struct _old_ged_facetize_opts *)data;
if (o->nmg_log_print_header) {
bu_vls_printf(o->nmg_log, "%s\n", bu_vls_addr(o->nmg_log_header));
o->nmg_log_print_header = 0;
}
bu_vls_printf(o->nmg_log, "%s\n", (const char *)str);
return 0;
}
void
_old_ged_facetize_log_nmg(struct _old_ged_facetize_opts *o)
{
if (fileno(stderr) < 0)
return;
/* Seriously, bu_bomb, we don't want you blathering
* to stderr... shut down stderr temporarily. */
o->fnull = open(bu_file_null(), O_WRONLY);
if (o->fnull != -1) {
o->serr = fileno(stderr);
o->stderr_stashed = dup(o->serr);
dup2(o->fnull, o->serr);
close(o->fnull);
}
/* Set bu_log logging to capture in nmg_log, rather than the
* application defaults */
bu_log_hook_delete_all();
bu_log_add_hook(_ged_facetize_nmg_logging_hook, (void *)o);
/* Also engage the nmg bomb hooks */
bu_bomb_delete_all_hooks();
bu_bomb_add_hook(_ged_facetize_bomb_hook, (void *)o);
}
void
_old_ged_facetize_log_default(struct _old_ged_facetize_opts *o)
{
if (fileno(stderr) < 0)
return;
/* Put stderr back */
if (o->fnull != -1) {
fflush(stderr);
dup2(o->stderr_stashed, o->serr);
close(o->stderr_stashed);
o->fnull = -1;
}
/* Restore bu_bomb hooks to the application defaults */
bu_bomb_delete_all_hooks();
bu_bomb_restore_hooks(o->saved_bomb_hooks);
/* Restore bu_log hooks to the application defaults */
bu_log_hook_delete_all();
bu_log_hook_restore_all(o->saved_log_hooks);
}
/*
* Local Variables:
* tab-width: 8
* mode: C
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+363
View File
@@ -0,0 +1,363 @@
/* O R C H E S T R A T O R . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*/
/** @file libged/facetize/orchestrator.cpp
*
* Command planning, session lifetime, and workspace ownership for facetize.
*/
#include "common.h"
#include <cstring>
#include <cstdio>
#include <fstream>
#include <sstream>
#include <string>
#include <sys/stat.h>
#include "bu/app.h"
#include "bu/file.h"
#include "bu/path.h"
#include "bu/vls.h"
#include "../ged_private.h"
#include "./ged_facetize.h"
#include "./tess_opts.h"
static const unsigned int FACETIZE_WORKSPACE_SCHEMA = 1;
static const char FACETIZE_MANIFEST_NAME[] = "request.manifest";
std::vector<const char *>
FacetizePlan::input_argv() const
{
std::vector<const char *> argv;
argv.reserve(inputs.size());
for (const std::string &input : inputs)
argv.push_back(input.c_str());
return argv;
}
static struct _ged_facetize_state *
facetize_state_create()
{
struct _ged_facetize_state *s = NULL;
BU_GET(s, struct _ged_facetize_state);
s->verbosity = 0;
s->no_empty = 0;
s->no_fixup = 0;
s->use_variant_plan = 1;
s->tolerate_failures = 0;
s->tolerated_failures = 0;
s->tolerated_failure_details = 0;
s->tolerated_failure_omitted = 0;
s->inspection_regions = 0;
s->perturb_sa_tol = 10.0;
s->perturb_vol_tol = 10.0;
s->wdir = NULL;
s->cleanup_workspace = false;
BU_GET(s->log_file, struct bu_vls);
bu_vls_init(s->log_file);
s->log_file_is_temporary = 0;
s->lfile = NULL;
BU_GET(s->failure_msg, struct bu_vls);
bu_vls_init(s->failure_msg);
BU_GET(s->tolerated_failure_log, struct bu_vls);
bu_vls_init(s->tolerated_failure_log);
BU_GET(s->region_summary, struct bu_vls);
bu_vls_init(s->region_summary);
BU_GET(s->primitive_summary, struct bu_vls);
bu_vls_init(s->primitive_summary);
BU_GET(s->inspection_log, struct bu_vls);
bu_vls_init(s->inspection_log);
BU_GET(s->wfile, struct bu_vls);
bu_vls_init(s->wfile);
BU_GET(s->bname, struct bu_vls);
bu_vls_init(s->bname);
BU_GET(s->suffix, struct bu_vls);
bu_vls_init(s->suffix);
BU_GET(s->prefix, struct bu_vls);
bu_vls_init(s->prefix);
BU_GET(s->solid_suffix, struct bu_vls);
bu_vls_init(s->solid_suffix);
bu_vls_sprintf(s->solid_suffix, ".bot");
s->resume = 0;
s->max_time = 0;
s->max_pnts = 0;
s->max_workers = 0;
s->tol = NULL;
s->nonovlp_threshold = 0;
s->error_flag = 0;
s->gedp = NULL;
s->dbip = NULL;
s->facetize_tree = NULL;
s->method_opts = NULL;
s->write_profiled = 0;
s->write_profile_bytes = 0.0;
s->write_profile_usec = 0.0;
s->variant_plan = NULL;
return s;
}
static void
facetize_state_destroy(struct _ged_facetize_state *s)
{
if (!s)
return;
if (s->lfile)
fclose(s->lfile);
/* Windows cannot delete the workspace while its log is still open. */
if (s->cleanup_workspace)
bu_dirclear(s->wdir);
if (s->wdir)
bu_free(s->wdir, "wdir");
struct bu_vls *strings[] = {
s->bname, s->log_file, s->failure_msg, s->tolerated_failure_log,
s->region_summary, s->primitive_summary, s->inspection_log, s->wfile,
s->prefix, s->suffix, s->solid_suffix
};
for (struct bu_vls *str : strings) {
if (!str)
continue;
bu_vls_free(str);
BU_PUT(str, struct bu_vls);
}
delete (FacetizeVariantPlan *)s->variant_plan;
BU_PUT(s, struct _ged_facetize_state);
}
FacetizeSession::FacetizeSession(struct ged *gedp)
{
methods = new method_options_t;
session_state = facetize_state_create();
session_state->gedp = gedp;
session_state->dbip = gedp->dbip;
session_state->method_opts = methods;
}
FacetizeSession::~FacetizeSession()
{
facetize_summary(session_state);
facetize_state_destroy(session_state);
delete methods;
}
int
facetize_build_plan(struct _ged_facetize_state *s, int argc,
const char **argv, FacetizePlan &plan)
{
if (!s || !s->dbip || argc <= 0 || !argv)
return BRLCAD_ERROR;
plan.execution = s->execution;
plan.inputs.clear();
plan.output.clear();
std::vector<std::string> missing;
for (int i = 0; i < argc; i++) {
if (db_lookup(s->dbip, argv[i], LOOKUP_QUIET) == RT_DIR_NULL)
missing.emplace_back(argv[i]);
else
plan.inputs.emplace_back(argv[i]);
}
if (plan.execution.writes_in_place()) {
if (!missing.empty()) {
bu_vls_printf(s->gedp->ged_result_str,
"In-place conversion requires every input object to exist:\n");
for (const std::string &name : missing)
bu_vls_printf(s->gedp->ged_result_str, " %s\n", name.c_str());
return BRLCAD_ERROR;
}
} else {
if (missing.size() != 1) {
if (missing.empty())
bu_vls_printf(s->gedp->ged_result_str,
"A new output object name is required.\n");
else
bu_vls_printf(s->gedp->ged_result_str,
"Exactly one new output object name is required; found %zu.\n",
missing.size());
return BRLCAD_ERROR;
}
plan.output = missing.front();
}
if (plan.inputs.empty()) {
bu_vls_printf(s->gedp->ged_result_str,
"No existing input objects were specified.\n");
return BRLCAD_ERROR;
}
return BRLCAD_OK;
}
static std::string
facetize_request_signature(struct _ged_facetize_state *s,
const FacetizePlan &plan, const char *source_path,
const struct stat &source_stat)
{
std::ostringstream signature;
signature << "schema=" << FACETIZE_WORKSPACE_SCHEMA << '\n';
signature << "source=" << source_path << '\n';
signature << "source_size=" << source_stat.st_size << '\n';
signature << "source_mtime=" << source_stat.st_mtime << '\n';
signature << "scope=" << static_cast<int>(plan.execution.scope) << '\n';
signature << "engine=" << static_cast<int>(plan.execution.boolean_engine) << '\n';
signature << "output_format=" << static_cast<int>(plan.execution.output_format) << '\n';
signature << "commit=" << static_cast<int>(plan.execution.commit_mode) << '\n';
signature << "perturb=" << static_cast<int>(plan.execution.perturb_mode) << '\n';
signature << "no_empty=" << s->no_empty << '\n';
signature << "no_fixup=" << s->no_fixup << '\n';
signature << "tolerate_failures=" << s->tolerate_failures << '\n';
signature << "max_time=" << s->max_time << '\n';
signature << "max_pnts=" << s->max_pnts << '\n';
signature << "max_workers=" << s->max_workers << '\n';
signature << "perturb_sa_tol=" << s->perturb_sa_tol << '\n';
signature << "perturb_vol_tol=" << s->perturb_vol_tol << '\n';
signature << "prefix=" << bu_vls_cstr(s->prefix) << '\n';
signature << "suffix=" << bu_vls_cstr(s->suffix) << '\n';
signature << "output=" << plan.output << '\n';
for (const std::string &input : plan.inputs)
signature << "input=" << input << '\n';
method_options_t *methods = (method_options_t *)s->method_opts;
if (methods) {
for (const std::string &method : methods->methods)
signature << "method=" << method << '\n';
for (const auto &method_entry : methods->options_map)
for (const auto &option_entry : method_entry.second)
signature << "method_option=" << method_entry.first << ':'
<< option_entry.first << '=' << option_entry.second << '\n';
}
return signature.str();
}
static bool
read_file(const char *path, std::string &contents)
{
std::ifstream input(path, std::ios::binary);
if (!input)
return false;
std::ostringstream buffer;
buffer << input.rdbuf();
contents = buffer.str();
return input.good() || input.eof();
}
static bool
write_file(const char *path, const std::string &contents)
{
std::ofstream output(path, std::ios::binary | std::ios::trunc);
if (!output)
return false;
output.write(contents.data(), (std::streamsize)contents.size());
return output.good();
}
int
facetize_prepare_workspace(struct _ged_facetize_state *s,
const FacetizePlan &plan)
{
char source_path[MAXPATHLEN];
if (!bu_file_realpath(s->dbip->dbi_filename, source_path)) {
bu_vls_printf(s->gedp->ged_result_str,
"Unable to resolve database path '%s'.\n", s->dbip->dbi_filename);
return BRLCAD_ERROR;
}
struct stat source_stat;
if (stat(source_path, &source_stat) != 0) {
bu_vls_printf(s->gedp->ged_result_str,
"Unable to inspect database '%s'.\n", source_path);
return BRLCAD_ERROR;
}
bu_path_component(s->bname, source_path, BU_PATH_BASENAME);
unsigned long long path_hash = bu_data_hash(source_path, strlen(source_path));
struct bu_vls workspace_name = BU_VLS_INIT_ZERO;
bu_vls_sprintf(&workspace_name, "facetize_%llu", path_hash);
s->wdir = (char *)bu_calloc(MAXPATHLEN, sizeof(char), "facetize workspace");
bu_dir(s->wdir, MAXPATHLEN, BU_DIR_CACHE, bu_vls_cstr(&workspace_name), NULL);
bool workspace_exists = bu_file_directory(s->wdir);
if (!s->resume && workspace_exists)
bu_dirclear(s->wdir);
if (!bu_file_directory(s->wdir))
bu_mkdir(s->wdir);
if (!bu_file_directory(s->wdir)) {
bu_vls_printf(s->gedp->ged_result_str,
"Unable to create facetize workspace '%s'.\n", s->wdir);
bu_vls_free(&workspace_name);
return BRLCAD_ERROR;
}
char manifest_path[MAXPATHLEN];
bu_dir(manifest_path, MAXPATHLEN, s->wdir, FACETIZE_MANIFEST_NAME, NULL);
std::string expected_manifest = facetize_request_signature(s, plan,
source_path, source_stat);
if (s->resume) {
std::string actual_manifest;
if (!read_file(manifest_path, actual_manifest) || actual_manifest != expected_manifest) {
bu_vls_printf(s->gedp->ged_result_str,
"Cannot resume: workspace does not match the database and facetize options.\n");
bu_vls_free(&workspace_name);
return BRLCAD_ERROR;
}
} else if (!write_file(manifest_path, expected_manifest)) {
bu_vls_printf(s->gedp->ged_result_str,
"Unable to write facetize workspace manifest '%s'.\n", manifest_path);
bu_vls_free(&workspace_name);
return BRLCAD_ERROR;
}
if (!bu_vls_strlen(s->log_file)) {
char log_path[MAXPATHLEN];
s->log_file_is_temporary = 1;
bu_vls_sprintf(&workspace_name, "facetize_%s.log", bu_vls_cstr(s->bname));
bu_dir(log_path, MAXPATHLEN, s->wdir, bu_vls_cstr(&workspace_name), NULL);
bu_vls_sprintf(s->log_file, "%s", log_path);
}
bu_vls_free(&workspace_name);
s->lfile = fopen(bu_vls_cstr(s->log_file), "a");
if (!s->lfile) {
bu_vls_printf(s->gedp->ged_result_str,
"Unable to open log file %s for writing\n", bu_vls_cstr(s->log_file));
return BRLCAD_ERROR;
}
return BRLCAD_OK;
}
int
facetize_execute_plan(struct _ged_facetize_state *s,
const FacetizePlan &plan)
{
if (plan.execution.processes_breps())
return _nonovlp_brep_facetize(s, plan);
if (plan.execution.processes_regions())
return _ged_facetize_regions(s, plan);
return _ged_facetize_objs(s, plan);
}
// Local Variables:
// tab-width: 8
// mode: C++
// c-basic-offset: 4
// indent-tabs-mode: t
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+96
View File
@@ -0,0 +1,96 @@
/* O R C H E S T R A T O R . H
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*/
#ifndef LIBGED_FACETIZE_ORCHESTRATOR_H
#define LIBGED_FACETIZE_ORCHESTRATOR_H
#include <string>
#include <vector>
#include "bu/defines.h"
struct ged;
struct _ged_facetize_state;
class method_options_t;
enum class FacetizeOutputFormat {
Bot,
Nmg
};
enum class FacetizeBooleanEngine {
Manifold,
Nmg
};
enum class FacetizeScope {
Objects,
Regions,
NonOverlappingBrep
};
enum class FacetizeCommitMode {
NewObject,
InPlace
};
enum class FacetizePerturbMode {
Disabled,
Enabled
};
struct FacetizeExecutionOptions {
FacetizeOutputFormat output_format = FacetizeOutputFormat::Bot;
FacetizeBooleanEngine boolean_engine = FacetizeBooleanEngine::Manifold;
FacetizeScope scope = FacetizeScope::Objects;
FacetizeCommitMode commit_mode = FacetizeCommitMode::NewObject;
FacetizePerturbMode perturb_mode = FacetizePerturbMode::Disabled;
bool writes_nmg() const { return output_format == FacetizeOutputFormat::Nmg; }
bool uses_nmg_boolean() const { return boolean_engine == FacetizeBooleanEngine::Nmg; }
bool processes_regions() const { return scope == FacetizeScope::Regions; }
bool processes_breps() const { return scope == FacetizeScope::NonOverlappingBrep; }
bool writes_in_place() const { return commit_mode == FacetizeCommitMode::InPlace; }
bool uses_perturbation() const { return perturb_mode == FacetizePerturbMode::Enabled; }
};
struct FacetizePlan {
FacetizeExecutionOptions execution;
std::vector<std::string> inputs;
std::string output;
std::vector<const char *> input_argv() const;
};
class FacetizeSession {
public:
explicit FacetizeSession(struct ged *gedp);
~FacetizeSession();
FacetizeSession(const FacetizeSession &) = delete;
FacetizeSession &operator=(const FacetizeSession &) = delete;
struct _ged_facetize_state *state() const { return session_state; }
method_options_t *method_options() const { return methods; }
private:
struct _ged_facetize_state *session_state = nullptr;
method_options_t *methods = nullptr;
};
int facetize_build_plan(struct _ged_facetize_state *s, int argc,
const char **argv, FacetizePlan &plan);
int facetize_prepare_workspace(struct _ged_facetize_state *s,
const FacetizePlan &plan);
int facetize_execute_plan(struct _ged_facetize_state *s,
const FacetizePlan &plan);
#endif /* LIBGED_FACETIZE_ORCHESTRATOR_H */
+22 -13
View File
@@ -344,7 +344,10 @@ create_variant_in_working_g(struct db_i *wdbip,
point_t bmin, bmax;
/* If bounds fail, keep -1 and fall back to the non-micro/default floor. */
fastf_t bbox_diag = -1.0;
if (rt_bound_internal(src_dbip, src_dp, bmin, bmax) == 0)
struct bn_tol tol;
BN_TOL_INIT(&tol);
if (src_intern.idb_meth && src_intern.idb_meth->ft_bbox &&
src_intern.idb_meth->ft_bbox(&src_intern, &bmin, &bmax, &tol) == 0)
bbox_diag = DIST_PNT_PNT(bmin, bmax);
fastf_t factor = variant_perturb_factor(src_name, is_sub, idx, bbox_diag);
@@ -395,7 +398,8 @@ create_variant_in_working_g(struct db_i *wdbip,
FacetizeVariantPlan *
_ged_facetize_build_variant_plan(struct _ged_facetize_state *s,
int argc,
struct directory **dpa)
struct directory **dpa,
struct db_i *working_dbip)
{
if (!s || !s->dbip || argc <= 0 || !dpa)
return NULL;
@@ -427,18 +431,22 @@ _ged_facetize_build_variant_plan(struct _ged_facetize_state *s,
* name is not in dbnames, then insert the chosen name into
* dbnames to reserve it for subsequent instances.
* ============================================================ */
struct db_i *wdbip =
db_open(bu_vls_cstr(s->wfile), DB_OPEN_READWRITE);
struct db_i *wdbip = working_dbip;
bool close_wdbip = false;
if (!wdbip) {
delete plan;
return NULL;
wdbip = db_open(bu_vls_cstr(s->wfile), DB_OPEN_READWRITE);
if (!wdbip) {
delete plan;
return NULL;
}
close_wdbip = true;
if (db_dirbuild(wdbip) < 0) {
db_close(wdbip);
delete plan;
return NULL;
}
db_update_nref(wdbip);
}
if (db_dirbuild(wdbip) < 0) {
db_close(wdbip);
delete plan;
return NULL;
}
db_update_nref(wdbip);
/* Pre-collect all names currently in the working .g */
std::set<std::string> dbnames;
@@ -553,7 +561,8 @@ _ged_facetize_build_variant_plan(struct _ged_facetize_state *s,
}
}
db_close(wdbip);
if (close_wdbip)
db_close(wdbip);
return plan;
}
+237
View File
@@ -0,0 +1,237 @@
/* P R O C E S S . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
/* Apple's POSIX feature profile hides F_SETNOSIGPIPE. */
#if defined(__APPLE__) && !defined(_DARWIN_C_SOURCE)
# define _DARWIN_C_SOURCE
#endif
#include "common.h"
#include <algorithm>
#include <fstream>
#if defined(__APPLE__)
# include <errno.h>
# include <fcntl.h>
# include <string.h>
#endif
#include "bu/datetime.h"
#include "bu/file.h"
#include "bu/log.h"
#include "bu/process.h"
#include "bu/snooze.h"
#include "./ged_facetize.h"
#include "./process.h"
static const size_t FACETIZE_PROCESS_IO_BUFFER_SIZE = 4096u;
static const size_t FACETIZE_FILE_COPY_BUFFER_SIZE = 1024u * 1024u;
static const int FACETIZE_PROCESS_POLL_USEC = 1000;
static const int FACETIZE_PROCESS_SHUTDOWN_TIMEOUT_SEC = 5;
static const double FACETIZE_USEC_TO_SEC_DIVISOR = 1.0e6;
static const double FACETIZE_WRITE_TIMEOUT_FACTOR = 10.0;
static const double FACETIZE_WRITE_TIMEOUT_MIN_SEC = 5.0;
static const double FACETIZE_WRITE_TIMEOUT_MAX_SEC = 300.0;
const size_t FACETIZE_WRITE_PROFILE_MIN_BYTES = 1024u * 1024u;
void
facetize_process_drain_stdout(struct _ged_facetize_state *state,
struct bu_process *process, FacetizeWorkerClient &channel,
FacetizeWorkerStatus *status)
{
char buffer[FACETIZE_PROCESS_IO_BUFFER_SIZE];
int fd = bu_process_fileno(process, BU_PROCESS_STDOUT);
FacetizeWorkerStatus ignored_status;
FacetizeWorkerStatus *output_status = status ? status : &ignored_status;
std::vector<std::string> diagnostics;
while (fd >= 0 && bu_process_pending(fd)) {
int count = bu_process_read_n(process, BU_PROCESS_STDOUT,
(int)sizeof(buffer), buffer);
if (count <= 0)
break;
channel.consume_output(buffer, (size_t)count, *output_status,
diagnostics);
}
for (const std::string &diagnostic : diagnostics)
facetize_log(state, 1, "%s\n", diagnostic.c_str());
}
void
facetize_process_drain_stderr(struct _ged_facetize_state *state,
struct bu_process *process)
{
char buffer[FACETIZE_PROCESS_IO_BUFFER_SIZE];
int fd = bu_process_fileno(process, BU_PROCESS_STDERR);
while (fd >= 0 && bu_process_pending(fd)) {
int count = bu_process_read_n(process, BU_PROCESS_STDERR,
(int)sizeof(buffer), buffer);
if (count <= 0)
break;
facetize_log(state, 1, "%.*s", count, buffer);
}
}
int
facetize_process_reap(struct _ged_facetize_state *state,
struct bu_process **process, FacetizeWorkerClient &channel,
bool terminate)
{
if (!process || !*process)
return BRLCAD_ERROR;
if (terminate)
(void)bu_process_terminate(*process);
int64_t deadline = bu_gettime() +
BU_SEC2USEC(FACETIZE_PROCESS_SHUTDOWN_TIMEOUT_SEC);
int poll_result = bu_process_poll(*process, NULL);
while (poll_result == 0 && bu_gettime() < deadline) {
facetize_process_drain_stdout(state, *process, channel, NULL);
facetize_process_drain_stderr(state, *process);
(void)bu_snooze(FACETIZE_PROCESS_POLL_USEC);
poll_result = bu_process_poll(*process, NULL);
}
facetize_process_drain_stdout(state, *process, channel, NULL);
facetize_process_drain_stderr(state, *process);
if (poll_result != 1)
(void)bu_process_terminate(*process);
return bu_process_wait_n(process, 0);
}
int
facetize_process_stop(struct _ged_facetize_state *state,
struct bu_process **process, FILE *&process_input,
FacetizeWorkerClient &channel, bool terminate)
{
int status = facetize_process_reap(state, process, channel, terminate);
process_input = NULL;
channel.reset(NULL);
return status;
}
int
facetize_process_start(struct bu_process **process, FILE **process_input,
FacetizeWorkerClient &channel,
const std::vector<std::string> &command,
const std::string &result_file, const char *mode)
{
if (!process || !process_input || !mode)
return BRLCAD_ERROR;
std::vector<const char *> process_command;
for (const std::string &argument : command)
process_command.push_back(argument.c_str());
if (!result_file.empty()) {
process_command.push_back("--result-file");
process_command.push_back(result_file.c_str());
}
process_command.push_back(mode);
process_command.push_back(NULL);
*process = NULL;
*process_input = NULL;
channel.reset(NULL);
bu_process_create(process, process_command.data(), BU_PROCESS_HIDE_WINDOW);
if (*process)
*process_input = bu_process_file_open(*process, BU_PROCESS_STDIN);
bool input_ready = *process && *process_input;
#if defined(__APPLE__)
if (input_ready && fcntl(fileno(*process_input), F_SETNOSIGPIPE, 1) < 0) {
/* Darwin sends a broken pipe's SIGPIPE to the process, so masking
* only the writing thread cannot protect this worker channel. */
bu_log("FACETIZE: unable to suppress SIGPIPE on worker stdin: %s\n",
strerror(errno));
input_ready = false;
}
#endif
if (input_ready && setvbuf(*process_input, NULL, _IONBF, 0) == 0) {
channel.reset(*process_input);
return BRLCAD_OK;
}
if (*process) {
(void)bu_process_terminate(*process);
(void)bu_process_wait_n(process, 0);
}
*process_input = NULL;
return BRLCAD_ERROR;
}
double
facetize_write_timeout_seconds(size_t payload_size,
double profiled_write_bytes, double profiled_write_usec)
{
if (profiled_write_bytes <= 0.0 || profiled_write_usec <= 0.0)
return FACETIZE_WRITE_TIMEOUT_MAX_SEC;
double bytes_per_second = profiled_write_bytes *
FACETIZE_USEC_TO_SEC_DIVISOR / profiled_write_usec;
double projected_seconds = (double)payload_size / bytes_per_second;
double timeout_seconds = projected_seconds * FACETIZE_WRITE_TIMEOUT_FACTOR;
return std::min(FACETIZE_WRITE_TIMEOUT_MAX_SEC,
std::max(FACETIZE_WRITE_TIMEOUT_MIN_SEC, timeout_seconds));
}
int
facetize_file_copy(const char *source, const char *destination,
FacetizeFileCopyProgress progress, void *progress_data)
{
if (!source || !destination)
return BRLCAD_ERROR;
std::ifstream input(source, std::ios::binary);
if (!input.is_open())
return BRLCAD_ERROR;
std::ofstream output(destination, std::ios::binary | std::ios::trunc);
if (!output.is_open())
return BRLCAD_ERROR;
std::vector<char> buffer(FACETIZE_FILE_COPY_BUFFER_SIZE);
uint64_t bytes_copied = 0;
bool copy_succeeded = true;
while (input) {
input.read(buffer.data(), (std::streamsize)buffer.size());
std::streamsize count = input.gcount();
if (count <= 0)
break;
output.write(buffer.data(), count);
if (!output.good()) {
copy_succeeded = false;
break;
}
bytes_copied += (uint64_t)count;
if (progress)
progress(bytes_copied, progress_data);
}
copy_succeeded = copy_succeeded && input.eof() && !input.bad();
if (copy_succeeded) {
output.flush();
copy_succeeded = output.good();
}
input.close();
output.close();
if (!copy_succeeded)
bu_file_delete(destination);
return copy_succeeded ? BRLCAD_OK : BRLCAD_ERROR;
}
// Local Variables:
// tab-width: 8
// mode: C++
// c-basic-offset: 4
// indent-tabs-mode: t
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+64
View File
@@ -0,0 +1,64 @@
/* P R O C E S S . H
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*/
#ifndef LIBGED_FACETIZE_PROCESS_H
#define LIBGED_FACETIZE_PROCESS_H
#include <cstdio>
#include <cstdint>
#include <string>
#include <vector>
#include "bu/defines.h"
#include "./worker.h"
struct bu_process;
struct _ged_facetize_state;
extern const size_t FACETIZE_WRITE_PROFILE_MIN_BYTES;
typedef void (*FacetizeFileCopyProgress)(uint64_t bytes_copied, void *data);
void
facetize_process_drain_stdout(struct _ged_facetize_state *state,
struct bu_process *process, FacetizeWorkerClient &channel,
FacetizeWorkerStatus *status);
void
facetize_process_drain_stderr(struct _ged_facetize_state *state,
struct bu_process *process);
int
facetize_process_start(struct bu_process **process, FILE **process_input,
FacetizeWorkerClient &channel,
const std::vector<std::string> &command,
const std::string &result_file, const char *mode);
int
facetize_process_reap(struct _ged_facetize_state *state,
struct bu_process **process, FacetizeWorkerClient &channel,
bool terminate);
int
facetize_process_stop(struct _ged_facetize_state *state,
struct bu_process **process, FILE *&process_input,
FacetizeWorkerClient &channel, bool terminate);
double
facetize_write_timeout_seconds(size_t payload_size,
double profiled_write_bytes, double profiled_write_usec);
int
facetize_file_copy(const char *source, const char *destination,
FacetizeFileCopyProgress progress = NULL, void *progress_data = NULL);
#endif /* LIBGED_FACETIZE_PROCESS_H */
File diff suppressed because it is too large Load Diff
@@ -4,12 +4,18 @@ set(TESS_LIBS libged manifold::manifold)
set(
GED_TESS_SRCS
brep_csg.cpp
continuation.cpp
sample.cpp
spsr.cpp
main.cpp
mdc.cpp
nmg.cpp
nmg_boolean.cpp
util.cpp
../plan.cpp
../process.cpp
../tri.cpp
../transfer.cpp
../util.cpp
../validation.cpp
../worker.cpp
)
@@ -20,6 +26,7 @@ ged_subprocess_library(facetize_process
cmakefiles(
CMakeLists.txt
nmg_boolean.h
tessellate.h
${GED_TESS_SRCS}
)
@@ -1,293 +0,0 @@
/* C O N T I N U A T I O N . C P P
* BRL-CAD
*
* Copyright (c) 2008-2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
/** @file libged/facetize/continuation.cpp
*
* The facetize command's Continuation Method implementation.
*/
#include "common.h"
#include <sstream>
#include <string>
#include <vector>
#include "vmath.h"
#include "bu/datetime.h"
#include "bg/trimesh.h"
#include "raytrace.h"
#include "analyze/polygonize.h"
#include "../../ged_private.h"
#include "./tessellate.h"
#define FACETIZE_MEMORY_THRESHOLD 150000000
static void
_pnts_bbox(point_t rpp_min, point_t rpp_max, int pnt_cnt, point_t *pnts)
{
int i = 0;
if (!rpp_min || !rpp_max || !pnts || !pnts || !pnt_cnt) return;
VSETALL(rpp_min, INFINITY);
VSETALL(rpp_max, -INFINITY);
for (i = 0; i < pnt_cnt; i++) {
VMINMAX(rpp_min, rpp_max, pnts[i]);
}
}
static double
_bbox_vol(point_t b_min, point_t b_max)
{
double bbox_vol = 0.0;
fastf_t b_xlen, b_ylen, b_zlen;
b_xlen = fabs(b_max[X] - b_min[X]);
b_ylen = fabs(b_max[Y] - b_min[Y]);
b_zlen = fabs(b_max[Z] - b_min[Z]);
bbox_vol = b_xlen * b_ylen * b_zlen;
return bbox_vol;
}
void free_bot_internal(struct rt_bot_internal *bot)
{
if (!bot)
return;
if (bot->faces)
bu_free(bot->faces, "faces");
if (bot->vertices)
bu_free(bot->vertices, "vertices");
BU_PUT(bot, struct rt_bot_internal);
}
int
bot_gen(struct rt_bot_internal **obot, fastf_t feature_size, point_t seed, const char *objname, struct db_i *dbip, struct analyze_polygonize_params *params)
{
/* Build the BoT */
struct rt_bot_internal *bot;
BU_GET(bot, struct rt_bot_internal);
bot->magic = RT_BOT_INTERNAL_MAGIC;
bot->mode = RT_BOT_SOLID;
bot->orientation = RT_BOT_UNORIENTED;
bot->thickness = (fastf_t *)NULL;
bot->face_mode = (struct bu_bitv *)NULL;
bot->faces = NULL;
bot->vertices = NULL;
int ret = analyze_polygonize(&(bot->faces), (int *)&(bot->num_faces),
(point_t **)&(bot->vertices),
(int *)&(bot->num_vertices),
feature_size, seed, objname, dbip, params);
if (ret == 3)
bu_log("CM: Too little available memory to continue, aborting\n");
if (ret == 2)
bu_log("CM: timed out after %d seconds with size %g\n", params->max_cycle_time, feature_size);
if (!ret) {
*obot = bot;
} else {
free_bot_internal(bot);
}
return ret;
}
int
continuation_mesh(struct rt_bot_internal **obot, struct db_i *dbip, const char *objname, tess_opts *s, point_t seed)
{
int first_run = 1;
int fatal_error_cnt = 0;
double successful_feature_size = 0.0;
unsigned long int successful_bot_count = 1000;
int decimation_succeeded = 0;
struct rt_bot_internal *bot = NULL;
struct rt_bot_internal *dbot = NULL;
int pret = 0;
int total_elapsed = 0;
struct analyze_polygonize_params params = ANALYZE_POLYGONIZE_PARAMS_DEFAULT;
double feature_size = (s->cm_options.feature_size > 0) ? s->cm_options.feature_size : 2*s->cm_options.avg_thickness;
/* Run the polygonize routine. Because it is quite simple to
* accidentally specify inputs that will take huge amounts of time
* to run, we will attempt a series of progressively courser
* polygonize runs until we either succeed, or reach a feature
* size that is greater than 2x the average thickness according to
* the raytracer without succeeding. If max_time has been
* explicitly set to 0 by the caller this will run unbounded, but
* the algorithm is n**2 and we're trying the finest level first
* so may run a *very* long time... */
params.max_time = s->cm_options.max_time;
params.max_cycle_time = s->cm_options.max_cycle_time;
params.verbosity = 1;
params.minimum_free_mem = FACETIZE_MEMORY_THRESHOLD;
while (!pret && (feature_size > 0.9*s->cm_options.target_feature_size) && fatal_error_cnt < 8 ) {
struct rt_bot_internal *candidate = NULL;
double timestamp = bu_gettime();
params.time_offset = total_elapsed + 10; // Allow a few seconds extra for decimation
pret = bot_gen(&candidate, feature_size, seed, objname, dbip, &params);
fastf_t delta = (int)((bu_gettime() - timestamp)/1e6);
if (pret || candidate->num_faces < successful_bot_count || delta < 2) {
if (pret == 3)
break;
if (pret == 2) {
/* If we still haven't had a successful run, back the feature size out and try again */
if (first_run) {
pret = 0;
feature_size = feature_size * 5;
fatal_error_cnt++;
continue;
}
}
if (pret != 2 && !(s->cm_options.feature_size > 0)) {
/* Something about the previous size didn't work - nudge the feature size and try again
* unless we've had multiple fatal errors. */
pret = 0;
bu_log("CM: error at size %g\n", feature_size);
/* If we've had a successful first run, just nudge the feature
* size down and retry. If we haven't succeeded yet, and we've
* got just this one error, try dropping the feature size down
* by an order of magnitude. If we haven't succeed yet *and*
* we've got multiple fatal errors, try dropping it by half. */
feature_size = feature_size * ((!first_run) ? 0.95 : ((fatal_error_cnt) ? 0.5 : 0.1));
bu_log("CM: retrying with size %g\n", feature_size);
fatal_error_cnt++;
continue;
}
feature_size = successful_feature_size;
if (bot && bot->faces) {
if (s->cm_options.feature_size <= 0) {
bu_log("CM: unable to polygonize at target size (%g), using last successful BoT with %d faces, feature size %g\n", s->cm_options.target_feature_size, (int)bot->num_faces, successful_feature_size);
} else {
bu_log("CM: successfully created %d faces, feature size %g\n", (int)bot->num_faces, successful_feature_size);
}
}
} else {
/* if we have had a fatal error in the past, reset on subsequent success */
fatal_error_cnt = 0;
successful_feature_size = feature_size;
bu_log("CM: completed in %g seconds with size %g\n", delta, feature_size);
feature_size = feature_size * ((delta < 5) ? 0.7 : 0.9);
successful_bot_count = candidate->num_faces;
if (bot)
free_bot_internal(bot);
bot = candidate;
}
// If we got this far, we've had a successful first run
first_run = 0;
// If we're about to run up against our max time limit, bail rather than keep trying
total_elapsed += delta;
if (s->cm_options.max_cycle_time < delta*2) {
if (total_elapsed + s->cm_options.max_cycle_time > s->cm_options.max_time) {
feature_size = successful_feature_size;
break;
}
} else {
if (total_elapsed + delta*2 > s->cm_options.max_time) {
feature_size = successful_feature_size;
break;
}
}
}
if (bot && bot->num_faces) {
bu_log("CM: successfully polygonized BoT with %d faces at feature size %g\n", (int)bot->num_faces, successful_feature_size);
}
if (!bot || !bot->faces) {
bu_log("CM: surface reconstruction failed: %s\n", objname);
if (bot && bot->vertices) bu_free(bot->vertices, "verts");
if (bot && bot->faces) bu_free(bot->faces, "verts");
if (bot)
BU_PUT(bot, struct rt_bot_internal *);
return BRLCAD_ERROR;
}
/* do decimation */
{
double d_feature_size = (s->cm_options.d_feature_size > 0) ? s->cm_options.d_feature_size : 1.5 * feature_size;
*obot = bot;
bu_log("CM: decimating with feature size %g\n", d_feature_size);
dbot = _tess_facetize_decimate(bot, d_feature_size);
if (bot == dbot || !dbot->num_vertices || !dbot->num_faces) {
bu_log("decimation failed\n");
return BRLCAD_ERROR;
}
bot = dbot;
decimation_succeeded = 1;
}
/* Get the volume of the bounding box of the decimated BoT */
point_t b_min, b_max;
VSETALL(b_min, INFINITY);
VSETALL(b_max, -INFINITY);
_pnts_bbox(b_min, b_max, bot->num_vertices, (point_t *)bot->vertices);
double bot_bbox_vol = _bbox_vol(b_min, b_max);
/* Check the BoT bbox vol against the bounding box
* of the point cloud - a large difference means something probably isn't
* right. For the moment, use >50% difference. */
if (fabs(s->cm_options.pnts_bbox_vol - bot_bbox_vol) > s->cm_options.pnts_bbox_vol * 0.5) {
if (bot->vertices) bu_free(bot->vertices, "verts");
if (bot->faces) bu_free(bot->faces, "verts");
BU_PUT(bot, struct rt_bot_internal);
return BRLCAD_ERROR;
}
/* Check validity - do not return an invalid BoT */
{
int not_solid = bg_trimesh_solid2(bot->num_vertices, bot->num_faces, (fastf_t *)bot->vertices, (int *)bot->faces, NULL);
if (not_solid) {
if (bot->vertices) bu_free(bot->vertices, "verts");
if (bot->faces) bu_free(bot->faces, "verts");
BU_PUT(bot, struct rt_bot_internal);
bu_log("CM: facetization failed, final BoT was not solid\n");
return BRLCAD_ERROR;
}
}
if (decimation_succeeded) {
bu_log("CM: decimation succeeded, final BoT has %d faces\n", (int)bot->num_faces);
}
*obot = bot;
return BRLCAD_OK;
}
// Local Variables:
// tab-width: 8
// mode: C++
// c-basic-offset: 4
// indent-tabs-mode: t
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+535 -102
View File
@@ -34,17 +34,67 @@
#include <string>
#include <vector>
#include <stdlib.h>
#include "bu/app.h"
#include "bu/env.h"
#include "bu/file.h"
#include "bu/opt.h"
#include "bg/trimesh.h"
#include "rt/primitives/bot.h"
#include "ged.h"
#define TESS_OPTS_IMPLEMENTATION
#include "../ged_facetize.h"
#include "../tess_opts.h"
#include "../transfer.h"
#include "../validation.h"
#include "../worker.h"
#include "./nmg_boolean.h"
#include "./tessellate.h"
static const char FACETIZE_TEST_FAULT_ENV[] =
"LIBGED_FACETIZE_TEST_FAULT";
static const char FACETIZE_TEST_FAULT_FILE_ENV[] =
"LIBGED_FACETIZE_TEST_FAULT_FILE";
static const char FACETIZE_TEST_FAULT_TESS_READY[] = "tess-before-ready";
static const char FACETIZE_TEST_FAULT_STAGE_DONE[] = "stage-before-done";
static const char FACETIZE_TEST_FAULT_WRITER_PROCEED[] =
"writer-before-proceed";
static const char FACETIZE_TEST_FAULT_WRITER_DONE[] = "writer-before-done";
static const char FACETIZE_TEST_FAULT_NMG_READY[] = "nmg-before-ready";
/**
* Terminate at a named protocol boundary when explicitly armed by a test.
* Requiring both an exact stage/object selector and a writable one-shot claim
* file keeps this inaccessible during normal operation and prevents restarted
* workers from repeatedly taking the same fault.
*/
static void
facetize_test_fault(const char *stage, const char *object_name)
{
const char *fault = getenv(FACETIZE_TEST_FAULT_ENV);
const char *claim_file = getenv(FACETIZE_TEST_FAULT_FILE_ENV);
if (!fault || !fault[0] || !claim_file || !claim_file[0] ||
!stage || !object_name)
return;
std::string expected = std::string(stage) + ":" + object_name;
if (expected != fault || bu_file_exists(claim_file, NULL))
return;
FILE *claim = fopen(claim_file, "wb");
if (!claim)
return;
bool written = fwrite(expected.data(), 1, expected.size(), claim) ==
expected.size();
bool closed = fclose(claim) == 0;
if (!written || !closed)
return;
bu_exit(BRLCAD_ERROR, "FACETIZE: injected test fault at %s for %s\n",
stage, object_name);
}
static void
facetize_payload_add(size_t *payload_size, size_t count, size_t item_size)
{
@@ -96,19 +146,11 @@ facetize_bot_payload_size(const struct rt_bot_internal *bot)
return payload_size;
}
static void
rt_pnts_free(struct rt_pnts_internal *pnts)
static size_t
facetize_resident_size()
{
struct pnt_normal *rpnt = (struct pnt_normal *)pnts->point;
if (rpnt) {
struct pnt_normal *entry;
while (BU_LIST_WHILE(entry, pnt_normal, &(rpnt->l))) {
BU_LIST_DEQUEUE(&(entry->l));
BU_PUT(entry, struct pnt_normal);
}
BU_PUT(rpnt, struct pnt_normal);
}
BU_PUT(pnts, struct rt_pnts_internal);
ssize_t resident_size = bu_mem(BU_MEM_PROCESS_RESIDENT, NULL);
return (resident_size > 0) ? (size_t)resident_size : 0;
}
static void
@@ -118,28 +160,14 @@ method_setup(tess_opts *s)
return;
std::vector<std::string> *methods = &s->method_opts.methods;
if (!methods->size()) {
methods->push_back(std::string("NMG"));
methods->push_back(std::string("CM"));
methods->push_back(std::string("SPSR"));
}
if (!methods->size())
*methods = tess_default_methods();
// Now that we've set any default overrides for multiple types, get the
// method specific options for each method set up
s->nmg_options.sync(s->method_opts);
s->cm_options.sync(s->method_opts);
s->mdc_options.sync(s->method_opts);
s->spsr_options.sync(s->method_opts);
// Set the sampling options. If CM is active we will be using its settings
// to sample first, so default to those values.
bool sample_sync = false;
if (std::find(methods->begin(), methods->end(), std::string("CM")) != methods->end()) {
s->pnt_options.sync(s->cm_options);
sample_sync = true;
}
if (!sample_sync && std::find(methods->begin(), methods->end(), std::string("SPSR")) != methods->end()) {
s->pnt_options.sync(s->spsr_options);
}
}
static int
@@ -163,8 +191,8 @@ dp_tessellate(struct rt_bot_internal **obot, struct bu_vls *method_flag, struct
}
struct rt_pnts_internal *pnts = NULL;
bool free_pnts = false;
struct rt_bot_internal *bot = NULL;
bool best_effort_source = false;
int propVal;
int ret = BRLCAD_OK;
@@ -199,7 +227,7 @@ dp_tessellate(struct rt_bot_internal **obot, struct bu_vls *method_flag, struct
// If we are going to try a pnts wrapping, there are only a few
// candidates in the fallback methods list that we can use.
mset.erase(std::string("NMG"));
mset.erase(std::string("CM"));
mset.erase(std::string("MDC"));
// point the pnts arguments to the internal point data
pnts = (struct rt_pnts_internal *)intern.idb_ptr;
@@ -227,6 +255,7 @@ dp_tessellate(struct rt_bot_internal **obot, struct bu_vls *method_flag, struct
// Volumetric bot - if it can be manifold we're good, but if
// not we need to try and repair it.
if (!bot_is_manifold(bot)) {
best_effort_source = true;
// Nope - try repairing
struct rt_bot_repair_info settings = RT_BOT_REPAIR_INFO_INIT;
// We're aggressive preparing facetize inputs, since non-lint-passing
@@ -273,53 +302,22 @@ dp_tessellate(struct rt_bot_internal **obot, struct bu_vls *method_flag, struct
}
}
if (mset.find(std::string("CM")) != mset.end()) {
// The continuation method (CM) is a marching algorithm using an
// inside/outside test, building from a seed point on the surface.
//
// CM needs some awareness of properties of the solid, so we use the
// raytrace interrogation to build up that data. Unlike the sampling
// methods we don't make direct use of the points beyond using one of
// them for the seed, but we do use information collected during the
// sampling process.
if (!pnts) {
pnts = _tess_pnts_sample(dp->d_namep, dbip, s);
free_pnts = (pnts != NULL);
}
if (pnts) {
s->cm_options.sync(s->pnt_options);
struct pnt_normal *seed = BU_LIST_PNEXT(pnt_normal, (struct pnt_normal *)pnts->point);
ret = continuation_mesh(obot, dbip, dp->d_namep, s, seed->v);
if (ret == BRLCAD_OK) {
bu_vls_sprintf(method_flag, "CM");
goto dp_tessellate_cleanup;
}
if (mset.find(std::string("MDC")) != mset.end()) {
ret = mdc_mesh(obot, dbip, dp->d_namep, s);
if (ret == BRLCAD_OK) {
bu_vls_sprintf(method_flag, "MDC");
goto dp_tessellate_cleanup;
}
}
pnt_sampling_methods:
if (mset.find(std::string("SPSR")) != mset.end()) {
if (!pnts) {
pnts = _tess_pnts_sample(dp->d_namep, dbip, s);
free_pnts = (pnts != NULL);
} else {
if (!s->spsr_options.equals(s->pnt_options)) {
s->pnt_options.sync(s->spsr_options);
if (free_pnts)
rt_pnts_free(pnts);
pnts = _tess_pnts_sample(dp->d_namep, dbip, s);
free_pnts = (pnts != NULL);
}
}
if (pnts) {
s->spsr_options.sync(s->pnt_options);
ret = spsr_mesh(obot, dbip, pnts, s);
if (ret == BRLCAD_OK) {
bu_vls_sprintf(method_flag, "SPSR");
goto dp_tessellate_cleanup;
}
ret = spsr_mesh(obot, dbip, dp->d_namep, pnts,
best_effort_source, s);
if (ret == BRLCAD_OK) {
bu_vls_sprintf(method_flag, "SPSR");
goto dp_tessellate_cleanup;
}
}
@@ -341,8 +339,6 @@ pnt_sampling_methods:
ret = BRLCAD_ERROR;
dp_tessellate_cleanup:
if (free_pnts && pnts)
rt_pnts_free(pnts);
if (ret != BRLCAD_OK && *obot) {
_tess_facetize_free_bot(*obot);
*obot = NULL;
@@ -355,13 +351,13 @@ void
print_methods_info()
{
nmg_opts nopts;
cm_opts cmopts;
mdc_opts mdcopts;
spsr_opts spsropts;
std::string info;
info.append(nopts.print_options_help());
info.append(std::string("\n"));
info.append(cmopts.print_options_help());
info.append(mdcopts.print_options_help());
info.append(std::string("\n"));
info.append(spsropts.print_options_help());
fprintf(stdout, "%s\n", info.c_str());
@@ -370,36 +366,70 @@ print_methods_info()
void
print_tess_methods()
{
fprintf(stdout, "NMG CM SPSR");
fprintf(stdout, "NMG MDC SPSR\n");
}
static int
facetize_server_request(struct ged *gedp, const char *object_name,
tess_opts *s, FacetizeWorkerServer &worker_channel, bool *result_sent)
facetize_server_request(struct ged *gedp, struct db_i *result_dbip,
const FacetizeWorkerRequest &request,
FacetizeWorkerServer &worker_channel, bool *result_sent)
{
if (!gedp || !object_name || !s || !result_sent)
if (!gedp || !result_sent ||
request.operation != FacetizeWorkerOperation::TessellatePrimitive ||
request.input_names.size() != 1)
return BRLCAD_ERROR;
*result_sent = false;
if (!request.primitive.cache_directory.empty()) {
bu_mkdir(request.primitive.cache_directory.c_str());
if (bu_setenv("BU_DIR_CACHE",
request.primitive.cache_directory.c_str(), 1) != 0)
return BRLCAD_ERROR;
}
tess_opts options;
options.method_opts.methods = request.primitive.methods;
for (const std::string &option_string :
request.primitive.method_options) {
const char *option = option_string.c_str();
if (_tess_method_opts(NULL, 1, &option, &options.method_opts) != 1)
return BRLCAD_ERROR;
}
method_setup(&options);
if (request.primitive.point_limit > 0) {
options.spsr_options.max_pnts = request.primitive.point_limit;
}
const char *object_name = request.input_names.front().c_str();
struct directory *dp = db_lookup(gedp->dbip, object_name, LOOKUP_QUIET);
if (!dp || dp->d_major_type != DB5_MAJORTYPE_BRLCAD)
return BRLCAD_ERROR;
struct rt_bot_internal *obot = NULL;
struct bu_vls method = BU_VLS_INIT_ZERO;
int ret = dp_tessellate(&obot, &method, gedp, dp, s);
int ret = dp_tessellate(&obot, &method, gedp, dp, &options);
if (ret == BRLCAD_OK && obot) {
// Waiting here lets the parent stop an over-time tessellation without
// risking a partially replaced object in the working database.
facetize_test_fault(FACETIZE_TEST_FAULT_TESS_READY, object_name);
size_t payload_size = facetize_bot_payload_size(obot);
if (!worker_channel.send_write_ready(payload_size) ||
!worker_channel.receive_write_proceed()) {
if (!worker_channel.send_write_ready(payload_size,
facetize_resident_size()) ||
!worker_channel.receive_write_proceed() ||
!worker_channel.send_write_started()) {
_tess_facetize_free_bot(obot);
ret = BRLCAD_ERROR;
} else {
ret = _tess_facetize_write_bot(gedp->dbip, obot, object_name,
struct db_i *output_dbip = result_dbip ? result_dbip : gedp->dbip;
const char *output_name = result_dbip ?
FACETIZE_WORKER_RESULT_OBJECT : object_name;
ret = _tess_facetize_write_bot(output_dbip, obot, output_name,
bu_vls_cstr(&method));
if (!worker_channel.send_write_result(ret)) {
if (result_dbip && ret == BRLCAD_OK)
facetize_test_fault(FACETIZE_TEST_FAULT_STAGE_DONE,
object_name);
if (!worker_channel.send_write_result(ret,
facetize_resident_size())) {
ret = BRLCAD_ERROR;
} else {
*result_sent = true;
@@ -413,7 +443,7 @@ facetize_server_request(struct ged *gedp, const char *object_name,
}
static int
facetize_server(const char *work_file, tess_opts *s)
facetize_server(const char *work_file, const char *result_file)
{
int server_status = BRLCAD_OK;
setvbuf(stdout, NULL, _IONBF, 0);
@@ -422,11 +452,18 @@ facetize_server(const char *work_file, tess_opts *s)
if (!gedp)
return BRLCAD_ERROR;
struct db_i *result_dbip = result_file ?
db_create(result_file, BRLCAD_DB_FORMAT_LATEST) : NULL;
if (result_file && !result_dbip) {
ged_close(gedp);
return BRLCAD_ERROR;
}
FacetizeWorkerServer worker_channel(stdin, stdout);
while (true) {
std::string object_name;
FacetizeWorkerRequest request;
FacetizeWorkerReadResult read_result =
worker_channel.receive_request(object_name);
worker_channel.receive_request(request);
if (read_result == FacetizeWorkerReadResult::End)
break;
if (read_result == FacetizeWorkerReadResult::Error) {
@@ -435,14 +472,335 @@ facetize_server(const char *work_file, tess_opts *s)
}
bool result_sent = false;
int ret = facetize_server_request(gedp, object_name.c_str(), s,
worker_channel, &result_sent);
if (!result_sent && !worker_channel.send_tessellation_result(ret)) {
int ret = facetize_server_request(gedp, result_dbip,
request, worker_channel, &result_sent);
if (!result_sent && !worker_channel.send_tessellation_result(ret,
facetize_resident_size())) {
server_status = BRLCAD_ERROR;
break;
}
}
if (result_dbip)
db_close(result_dbip);
ged_close(gedp);
return server_status;
}
static int
facetize_validation_server(const char *source_file)
{
if (!source_file)
return BRLCAD_ERROR;
setvbuf(stdout, NULL, _IONBF, 0);
struct db_i *dbip = db_open(source_file, DB_OPEN_READONLY);
if (!dbip)
return BRLCAD_ERROR;
if (db_dirbuild(dbip) < 0) {
db_close(dbip);
return BRLCAD_ERROR;
}
int server_status = BRLCAD_OK;
FacetizeWorkerServer channel(stdin, stdout);
while (true) {
FacetizeWorkerRequest request;
FacetizeWorkerReadResult read_result =
channel.receive_request(request);
if (read_result == FacetizeWorkerReadResult::End)
break;
if (read_result == FacetizeWorkerReadResult::Error ||
request.operation != FacetizeWorkerOperation::ValidateCsg ||
request.input_names.size() != 1) {
server_status = BRLCAD_ERROR;
break;
}
const std::string &object_name = request.input_names.front();
double surface_area = -1.0;
double volume = -1.0;
long crossings = facetize_csg_metrics(dbip, object_name.c_str(),
&surface_area, &volume);
int result = (crossings >= 0) ? BRLCAD_OK : BRLCAD_ERROR;
if (!channel.send_csg_result(result, crossings, surface_area, volume,
facetize_resident_size())) {
server_status = BRLCAD_ERROR;
break;
}
}
db_close(dbip);
return server_status;
}
static int
facetize_region_server_request(struct _ged_facetize_state *state,
struct db_i *work_dbip, struct rt_wdb *work_wdbp,
struct db_i *staging_dbip, struct rt_wdb *staging_wdbp,
const char *object_name, FacetizeWorkerServer &channel,
bool *result_sent)
{
if (!state || !work_dbip || !work_wdbp || !staging_dbip ||
!staging_wdbp || !object_name || !result_sent)
return BRLCAD_ERROR;
*result_sent = false;
struct db_i *result_dbip = db_create_inmem();
if (!result_dbip)
return BRLCAD_ERROR;
const char *inputs[] = {object_name};
int ret = _ged_facetize_booleval_tri_to_db(state, work_dbip,
work_wdbp, 1, inputs, FACETIZE_WORKER_RESULT_OBJECT, &rt_vlfree,
result_dbip, 0, 0);
struct rt_db_internal result_internal;
RT_DB_INTERNAL_INIT(&result_internal);
bool result_internal_consumed = false;
struct directory *result_dp = (ret == BRLCAD_OK) ?
db_lookup(result_dbip, FACETIZE_WORKER_RESULT_OBJECT, LOOKUP_QUIET) :
RT_DIR_NULL;
if (!result_dp || result_dp->d_minor_type != ID_BOT ||
rt_db_get_internal(&result_internal, result_dp, result_dbip,
NULL) < 0) {
ret = BRLCAD_ERROR;
}
if (ret == BRLCAD_OK) {
struct rt_bot_internal *result_bot =
(struct rt_bot_internal *)result_internal.idb_ptr;
size_t payload_size = facetize_bot_payload_size(result_bot);
if (!channel.send_region_write_ready(payload_size,
facetize_resident_size(), state->tolerated_failures) ||
!channel.receive_write_proceed() ||
!channel.send_write_started()) {
ret = BRLCAD_ERROR;
} else {
struct directory *old_dp = db_lookup(staging_dbip,
FACETIZE_WORKER_RESULT_OBJECT, LOOKUP_QUIET);
if (old_dp && (db_delete(staging_dbip, old_dp) != 0 ||
db_dirdelete(staging_dbip, old_dp) != 0)) {
ret = BRLCAD_ERROR;
} else {
ret = wdb_put_internal(staging_wdbp,
FACETIZE_WORKER_RESULT_OBJECT, &result_internal,
1.0);
result_internal_consumed = true;
}
if (!channel.send_write_result(ret, facetize_resident_size()))
ret = BRLCAD_ERROR;
else
*result_sent = true;
}
}
if (!result_internal_consumed && result_internal.idb_ptr)
rt_db_free_internal(&result_internal);
db_close(result_dbip);
return ret;
}
static int
facetize_region_server(const char *work_file, const char *source_file,
const char *result_file)
{
if (!work_file || !source_file || !result_file)
return BRLCAD_ERROR;
setvbuf(stdout, NULL, _IONBF, 0);
struct db_i *work_dbip = db_open(work_file, DB_OPEN_READONLY);
struct db_i *source_dbip = db_open(source_file, DB_OPEN_READONLY);
if (!work_dbip || !source_dbip) {
if (work_dbip)
db_close(work_dbip);
if (source_dbip)
db_close(source_dbip);
return BRLCAD_ERROR;
}
if (db_dirbuild(work_dbip) < 0 || db_dirbuild(source_dbip) < 0) {
db_close(source_dbip);
db_close(work_dbip);
return BRLCAD_ERROR;
}
db_update_nref(work_dbip);
struct rt_wdb *work_wdbp = wdb_dbopen(work_dbip,
RT_WDB_TYPE_DB_DEFAULT);
if (!work_wdbp) {
db_close(source_dbip);
db_close(work_dbip);
return BRLCAD_ERROR;
}
(void)bu_file_delete(result_file);
struct db_i *staging_dbip = db_create(result_file,
BRLCAD_DB_FORMAT_LATEST);
struct rt_wdb *staging_wdbp = staging_dbip ?
wdb_dbopen(staging_dbip, RT_WDB_TYPE_DB_DEFAULT) : NULL;
if (!staging_dbip || !staging_wdbp) {
if (staging_dbip)
db_close(staging_dbip);
db_close(source_dbip);
db_close(work_dbip);
return BRLCAD_ERROR;
}
struct bu_vls failure_message = BU_VLS_INIT_ZERO;
struct bu_vls tolerated_failure_log = BU_VLS_INIT_ZERO;
struct _ged_facetize_state state = {};
state.verbosity = -1;
state.failure_msg = &failure_message;
state.tolerated_failure_log = &tolerated_failure_log;
state.dbip = source_dbip;
int server_status = BRLCAD_OK;
FacetizeWorkerServer channel(stdin, stdout);
while (true) {
FacetizeWorkerRequest request;
FacetizeWorkerReadResult read_result =
channel.receive_request(request);
if (read_result == FacetizeWorkerReadResult::End)
break;
if (read_result == FacetizeWorkerReadResult::Error ||
request.operation != FacetizeWorkerOperation::EvaluateRegion ||
request.input_names.size() != 1) {
server_status = BRLCAD_ERROR;
break;
}
const std::string &object_name = request.input_names.front();
state.no_empty = request.region.no_empty;
state.no_fixup = request.region.no_fixup;
state.tolerate_failures = request.region.tolerate_failures;
state.error_flag = 0;
state.facetize_tree = NULL;
state.tolerated_failures = 0;
state.tolerated_failure_details = 0;
state.tolerated_failure_omitted = 0;
bu_vls_trunc(&failure_message, 0);
bu_vls_trunc(&tolerated_failure_log, 0);
bool result_sent = false;
int ret = facetize_region_server_request(&state, work_dbip,
work_wdbp, staging_dbip, staging_wdbp, object_name.c_str(),
channel, &result_sent);
if (ret != BRLCAD_OK && bu_vls_strlen(&failure_message))
bu_log("FACETIZE: region Boolean evaluation failed for %s: %s\n",
object_name.c_str(), bu_vls_cstr(&failure_message));
if (!result_sent && !channel.send_tessellation_result(ret,
facetize_resident_size())) {
server_status = BRLCAD_ERROR;
break;
}
}
bu_vls_free(&tolerated_failure_log);
bu_vls_free(&failure_message);
db_close(staging_dbip);
db_close(source_dbip);
db_close(work_dbip);
return server_status;
}
static int
facetize_writer(const char *work_file)
{
setvbuf(stdout, NULL, _IONBF, 0);
struct db_i *dbip = db_open(work_file, DB_OPEN_READWRITE);
if (!dbip)
return BRLCAD_ERROR;
if (db_dirbuild(dbip) < 0) {
db_close(dbip);
return BRLCAD_ERROR;
}
int writer_status = BRLCAD_OK;
FacetizeWorkerServer channel(stdin, stdout);
while (true) {
FacetizeCommitRequest request;
FacetizeWorkerReadResult read_result = channel.receive_commit(request);
if (read_result == FacetizeWorkerReadResult::End)
break;
if (read_result == FacetizeWorkerReadResult::Error ||
!channel.send_write_ready(request.payload_size,
facetize_resident_size())) {
writer_status = BRLCAD_ERROR;
break;
}
facetize_test_fault(FACETIZE_TEST_FAULT_WRITER_PROCEED,
request.object_name.c_str());
if (!channel.receive_write_proceed()) {
writer_status = BRLCAD_ERROR;
break;
}
if (!channel.send_write_started()) {
writer_status = BRLCAD_ERROR;
break;
}
int ret = facetize_transfer_staged_object(dbip,
request.result_file.c_str(), request.object_name.c_str(), ID_BOT);
if (ret == BRLCAD_OK)
facetize_test_fault(FACETIZE_TEST_FAULT_WRITER_DONE,
request.object_name.c_str());
if (!channel.send_write_result(ret, facetize_resident_size())) {
writer_status = BRLCAD_ERROR;
break;
}
}
db_close(dbip);
return writer_status;
}
static int
facetize_nmg_server(const char *source_file, const char *result_file)
{
if (!source_file || !result_file)
return BRLCAD_ERROR;
setvbuf(stdout, NULL, _IONBF, 0);
struct ged *gedp = ged_open("db", source_file, 1);
if (!gedp)
return BRLCAD_ERROR;
struct db_i *result_dbip = db_create(result_file,
BRLCAD_DB_FORMAT_LATEST);
if (!result_dbip) {
ged_close(gedp);
return BRLCAD_ERROR;
}
FacetizeWorkerServer channel(stdin, stdout);
FacetizeWorkerRequest request;
FacetizeWorkerReadResult read_result = channel.receive_request(request);
int server_status = BRLCAD_OK;
FacetizeNmgBooleanResult *result = NULL;
int ret = (read_result == FacetizeWorkerReadResult::Request) ?
facetize_nmg_boolean_evaluate(gedp, request, &result) : BRLCAD_ERROR;
if (read_result != FacetizeWorkerReadResult::Request) {
server_status = BRLCAD_ERROR;
} else if (ret == BRLCAD_OK) {
size_t payload_size = facetize_nmg_boolean_payload_size(result);
facetize_test_fault(FACETIZE_TEST_FAULT_NMG_READY,
request.output_name.c_str());
if (!channel.send_write_ready(payload_size,
facetize_resident_size()) ||
!channel.receive_write_proceed() ||
!channel.send_write_started()) {
server_status = BRLCAD_ERROR;
} else {
ret = facetize_nmg_boolean_write(result_dbip, result);
if (!channel.send_write_result(ret, facetize_resident_size()))
server_status = BRLCAD_ERROR;
}
} else if (!channel.send_tessellation_result(ret,
facetize_resident_size())) {
server_status = BRLCAD_ERROR;
}
facetize_nmg_boolean_destroy(result);
db_close(result_dbip);
ged_close(gedp);
return server_status;
}
@@ -465,10 +823,16 @@ facetize_process(int argc, const char **argv)
int list_methods = 0;
int server_mode = 0;
int writer_mode = 0;
int validation_server_mode = 0;
int region_server_mode = 0;
int nmg_server_mode = 0;
int max_time = 0;
int max_pnts = 0;
int worker_threads = 0;
struct bu_vls result_file = BU_VLS_INIT_ZERO;
struct bu_opt_desc d[10];
struct bu_opt_desc d[16];
BU_OPT(d[ 0], "h", "help", "", NULL, &print_help, "Print help and exit");
BU_OPT(d[ 1], "", "list-methods", "", NULL, &list_methods, "List available tessellation methods. When used with -h, print an informational summary of each method.");
BU_OPT(d[ 2], "O", "overwrite", "", NULL, &(s.overwrite_obj), "Replace original object with BoT");
@@ -477,8 +841,14 @@ facetize_process(int argc, const char **argv)
BU_OPT(d[ 5], "", "max-time", "#", &bu_opt_int, &max_time, "Maximum number of seconds to allow for runtime (not supported by all methods).");
BU_OPT(d[ 6], "", "max-pnts", "#", &bu_opt_int, &max_pnts, "Maximum number of pnts to use when applying ray sampling methods.");
BU_OPT(d[ 7], "", "cache-dir", "dir", &bu_opt_vls, &cache_dir, "Directory to use for cached outputs (default is libbu cache directory).");
BU_OPT(d[ 8], "", "server", "", NULL, &server_mode, "Run as a persistent worker, overwriting requested objects in the working database.");
BU_OPT_NULL(d[ 9]);
BU_OPT(d[ 8], "", "server", "", NULL, &server_mode, "Run as a persistent worker.");
BU_OPT(d[ 9], "", "threads", "#", &bu_opt_int, &worker_threads, "Maximum CPU threads available to this process.");
BU_OPT(d[10], "", "result-file", "file.g", &bu_opt_vls, &result_file, "Write server results to a staging database.");
BU_OPT(d[11], "", "writer", "", NULL, &writer_mode, "Run as a persistent staged-result writer.");
BU_OPT(d[12], "", "validation-server", "", NULL, &validation_server_mode, "Run as a persistent CSG validation worker.");
BU_OPT(d[13], "", "region-server", "", NULL, &region_server_mode, "Run as a persistent region Boolean worker.");
BU_OPT(d[14], "", "nmg-server", "", NULL, &nmg_server_mode, "Run one isolated NMG Boolean request.");
BU_OPT_NULL(d[15]);
/* parse options */
struct bu_vls omsg = BU_VLS_INIT_ZERO;
@@ -490,13 +860,33 @@ facetize_process(int argc, const char **argv)
}
bu_vls_free(&omsg);
if (worker_threads < 0 || worker_threads > MAX_PSW) {
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return BRLCAD_ERROR;
}
if (worker_threads)
bu_avail_cpus_set((size_t)worker_threads);
int worker_mode_count = server_mode + writer_mode +
validation_server_mode + region_server_mode + nmg_server_mode;
if (worker_mode_count > 1) {
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return BRLCAD_ERROR;
}
if (list_methods && print_help) {
print_methods_info();
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return BRLCAD_OK;
}
if (list_methods) {
print_tess_methods();
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return BRLCAD_OK;
}
@@ -513,9 +903,55 @@ facetize_process(int argc, const char **argv)
bu_log("%s\n", bu_vls_cstr(&str));
bu_vls_free(&str);
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return BRLCAD_OK;
}
if (writer_mode) {
int ret = (argc == 1 && !bu_vls_strlen(&result_file)) ?
facetize_writer(argv[0]) : BRLCAD_ERROR;
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return ret;
}
if (validation_server_mode) {
int ret = (argc == 1 && !bu_vls_strlen(&result_file)) ?
facetize_validation_server(argv[0]) : BRLCAD_ERROR;
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return ret;
}
if (region_server_mode) {
int ret = (argc == 2 && bu_vls_strlen(&result_file)) ?
facetize_region_server(argv[0], argv[1],
bu_vls_cstr(&result_file)) : BRLCAD_ERROR;
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return ret;
}
if (nmg_server_mode) {
int ret = (argc == 1 && bu_vls_strlen(&result_file)) ?
facetize_nmg_server(argv[0], bu_vls_cstr(&result_file)) :
BRLCAD_ERROR;
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return ret;
}
if (server_mode) {
int ret = (argc == 1) ?
facetize_server(argv[0], bu_vls_strlen(&result_file) ?
bu_vls_cstr(&result_file) : NULL) :
BRLCAD_ERROR;
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return ret;
}
// If we have a non-default cache directory specified, set it up
if (bu_vls_strlen(&cache_dir)) {
// Make sure it's there first
@@ -524,19 +960,13 @@ facetize_process(int argc, const char **argv)
bu_setenv("BU_DIR_CACHE", bu_vls_cstr(&cache_dir), 1);
}
// Do the setup for the various methods
// Do the setup for the various tessellation methods.
method_setup(&s);
if (server_mode) {
int ret = (argc == 1 && s.overwrite_obj) ?
facetize_server(argv[0], &s) : BRLCAD_ERROR;
bu_vls_free(&cache_dir);
return ret;
}
if (argc < 2) {
bu_log("%s", usage);
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return BRLCAD_ERROR;
}
@@ -544,6 +974,7 @@ facetize_process(int argc, const char **argv)
struct ged *gedp = ged_open("db", argv[0], 1);
if (!gedp) {
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return BRLCAD_ERROR;
}
@@ -555,6 +986,7 @@ facetize_process(int argc, const char **argv)
bu_ptbl_free(&dps);
ged_close(gedp);
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return BRLCAD_ERROR;
}
bu_ptbl_ins(&dps, (long *)dp);
@@ -607,6 +1039,7 @@ facetize_process(int argc, const char **argv)
bu_ptbl_free(&dps);
ged_close(gedp);
bu_vls_free(&cache_dir);
bu_vls_free(&result_file);
return process_ret;
}
+137
View File
@@ -0,0 +1,137 @@
/* M D C . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
/** @file mdc.cpp
*
* Facetize adapter for libanalyze's ray-driven manifold dual contourer.
*/
#include "common.h"
#include "analyze/contour.h"
#include "bu/log.h"
#include "rt/primitives/bot.h"
#include "./tessellate.h"
#include "../validation.h"
static const char *
mdc_status_message(enum analyze_mdc_status status)
{
switch (status) {
case ANALYZE_MDC_OK:
return "success";
case ANALYZE_MDC_INVALID_INPUT:
return "invalid settings or input";
case ANALYZE_MDC_PREP_FAILED:
return "raytrace preparation failed";
case ANALYZE_MDC_NO_SURFACE:
return "no closed surface was sampled";
case ANALYZE_MDC_RAY_LIMIT:
return "ray limit reached";
case ANALYZE_MDC_TIMEOUT:
return "time limit reached";
case ANALYZE_MDC_MEMORY_LIMIT:
return "free-memory limit reached";
case ANALYZE_MDC_AMBIGUOUS:
return "surface remained ambiguous at the maximum depth";
case ANALYZE_MDC_NOT_MANIFOLD:
return "generated mesh was not a closed oriented manifold";
}
return "unknown error";
}
int
mdc_mesh(struct rt_bot_internal **output, struct db_i *dbip,
const char *object, tess_opts *settings)
{
if (!output || !dbip || !object || !settings)
return BRLCAD_ERROR;
*output = NULL;
const mdc_opts &options = settings->mdc_options;
if (!options.invalid_option.empty()) {
bu_log("MDC: invalid option for %s: %s\n", object,
options.invalid_option.c_str());
return BRLCAD_ERROR;
}
struct analyze_mdc_params params = ANALYZE_MDC_PARAMS_DEFAULT;
params.feature_size = options.feature_size;
params.max_rays = static_cast<size_t>(options.max_rays);
params.minimum_free_mem =
static_cast<size_t>(options.minimum_free_mem);
params.min_depth = options.min_depth;
params.max_depth = options.max_depth;
params.max_time = options.max_time;
params.verbosity = options.verbosity;
int *faces = NULL;
point_t *vertices = NULL;
size_t face_count = 0;
size_t vertex_count = 0;
struct bu_hook_list saved_hooks = BU_HOOK_LIST_INIT_ZERO;
bool silence_logs = (options.verbosity == 0);
if (silence_logs)
facetize_log_hooks_silence(&saved_hooks);
enum analyze_mdc_status status = analyze_mdc(&faces, &face_count,
&vertices, &vertex_count, object, dbip, &params);
if (silence_logs)
facetize_log_hooks_restore(&saved_hooks);
if (status != ANALYZE_MDC_OK) {
bu_log("MDC: failed to tessellate %s: %s\n", object,
mdc_status_message(status));
return BRLCAD_ERROR;
}
struct rt_bot_internal *bot;
BU_ALLOC(bot, struct rt_bot_internal);
bot->magic = RT_BOT_INTERNAL_MAGIC;
bot->mode = RT_BOT_SOLID;
bot->orientation = RT_BOT_CCW;
bot->num_faces = face_count;
bot->num_vertices = vertex_count;
bot->faces = faces;
bot->vertices = reinterpret_cast<fastf_t *>(vertices);
if (!bot_is_manifold(bot)) {
bu_log("MDC: generated mesh for %s is not accepted by Manifold\n",
object);
_tess_facetize_free_bot(bot);
return BRLCAD_ERROR;
}
*output = bot;
return BRLCAD_OK;
}
/*
* Local Variables:
* mode: C++
* tab-width: 8
* c-basic-offset: 4
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+2 -1
View File
@@ -42,7 +42,8 @@ _nmg_tessellate(struct rt_bot_internal **nbot, struct rt_db_internal *intern, te
int ret = BRLCAD_ERROR;
struct bu_list *vlfree = &rt_vlfree;
if (!nbot || !intern || !intern->idb_meth || !s)
if (!nbot || !intern || !intern->idb_meth || !s ||
!intern->idb_meth->ft_tessellate)
return BRLCAD_ERROR;
// TODO - get the nmg debug option from nmg_options and set it
@@ -0,0 +1,279 @@
/* N M G _ B O O L E A N . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
/** @file libged/facetize/subprocess/nmg_boolean.cpp
*
* Isolated NMG tree evaluation for the facetize worker. An NMG bomb may
* invalidate the model's topology, so a bombed request intentionally leaves
* its allocations to process teardown rather than risking another bomb while
* attempting cleanup.
*/
#include "common.h"
#include <string>
#include <vector>
#include "raytrace.h"
#include "wdb.h"
#include "../../ged_private.h"
#include "../ged_facetize.h"
#include "./nmg_boolean.h"
struct FacetizeNmgBooleanResult {
struct model *nmg_model = NULL;
struct rt_bot_internal *bot = NULL;
};
struct FacetizeNmgBooleanState {
union tree *tree = NULL;
};
static union tree *
facetize_nmg_region_end(struct db_tree_state *tsp,
const struct db_full_path *pathp, union tree *current_tree,
void *client_data)
{
if (tsp)
RT_CK_DBTS(tsp);
if (pathp)
RT_CK_FULL_PATH(pathp);
FacetizeNmgBooleanState *state =
static_cast<FacetizeNmgBooleanState *>(client_data);
if (!state || !current_tree || current_tree->tr_op == OP_NOP)
return current_tree;
if (!state->tree) {
state->tree = current_tree;
return TREE_NULL;
}
union tree *union_tree;
BU_ALLOC(union_tree, union tree);
RT_TREE_INIT(union_tree);
union_tree->tr_op = OP_UNION;
union_tree->tr_b.tb_regionp = REGION_NULL;
union_tree->tr_b.tb_left = state->tree;
union_tree->tr_b.tb_right = current_tree;
state->tree = union_tree;
return TREE_NULL;
}
static union tree *
facetize_nmg_leaf(struct db_tree_state *tsp,
const struct db_full_path *pathp, struct rt_db_internal *ip,
void *UNUSED(client_data))
{
union tree *leaf = rt_booltree_leaf_tess(tsp, pathp, ip, NULL);
if (!leaf && pathp) {
char *path = db_path_to_string(pathp);
bu_log("NMG leaf tessellation failed for '%s'\n",
path ? path : "(unknown)");
if (path)
bu_free(path, "NMG leaf path");
}
return leaf;
}
static int
facetize_nmg_compute(struct ged *gedp,
const FacetizeWorkerRequest &request,
FacetizeNmgBooleanResult *result)
{
struct rt_wdb *wdbp = wdb_dbopen(gedp->dbip, RT_WDB_TYPE_DB_DEFAULT);
if (!wdbp)
return BRLCAD_ERROR;
struct db_tree_state initial_state;
db_init_db_tree_state(&initial_state, gedp->dbip);
initial_state.ts_ttol = &wdbp->wdb_ttol;
initial_state.ts_tol = &wdbp->wdb_tol;
struct model *nmg_model = nmg_mm();
initial_state.ts_m = &nmg_model;
FacetizeNmgBooleanState state;
std::vector<const char *> input_argv;
input_argv.reserve(request.input_names.size());
for (const std::string &input_name : request.input_names)
input_argv.push_back(input_name.c_str());
int walk_result = BRLCAD_ERROR;
if (!BU_SETJUMP) {
walk_result = db_walk_tree(gedp->dbip, (int)input_argv.size(),
input_argv.data(), 1, &initial_state, NULL,
facetize_nmg_region_end, facetize_nmg_leaf, &state);
} else {
BU_UNSETJUMP;
bu_log("NMG tree tessellation bombed while processing %s\n",
request.output_name.c_str());
return BRLCAD_ERROR;
}
BU_UNSETJUMP;
if (walk_result < 0 || !state.tree) {
if (state.tree)
db_free_tree(state.tree);
nmg_km(nmg_model);
return BRLCAD_ERROR;
}
int boolean_result = BRLCAD_ERROR;
if (!BU_SETJUMP) {
boolean_result = nmg_boolean(state.tree, nmg_model, &rt_vlfree,
&wdbp->wdb_tol);
if (boolean_result == 0) {
NMG_CK_REGION(state.tree->tr_d.td_r);
state.tree->tr_d.td_r = NULL;
}
} else {
BU_UNSETJUMP;
bu_log("NMG Boolean evaluation bombed while processing %s\n",
request.output_name.c_str());
return BRLCAD_ERROR;
}
BU_UNSETJUMP;
if (boolean_result != 0) {
db_free_tree(state.tree);
nmg_km(nmg_model);
return BRLCAD_ERROR;
}
db_free_tree(state.tree);
if (request.operation == FacetizeWorkerOperation::NmgBooleanToNmg) {
result->nmg_model = nmg_model;
return BRLCAD_OK;
}
struct rt_bot_internal *bot = NULL;
if (!BU_SETJUMP) {
bot = static_cast<struct rt_bot_internal *>(
nmg_mdl_to_bot(nmg_model, &rt_vlfree, &wdbp->wdb_tol));
} else {
BU_UNSETJUMP;
bu_log("NMG-to-BoT conversion bombed while processing %s\n",
request.output_name.c_str());
return BRLCAD_ERROR;
}
BU_UNSETJUMP;
nmg_km(nmg_model);
if (!bot)
return BRLCAD_ERROR;
result->bot = bot;
return BRLCAD_OK;
}
int
facetize_nmg_boolean_evaluate(struct ged *gedp,
const FacetizeWorkerRequest &request,
FacetizeNmgBooleanResult **result)
{
if (!gedp || !result || !request.valid() ||
(request.operation != FacetizeWorkerOperation::NmgBooleanToBot &&
request.operation != FacetizeWorkerOperation::NmgBooleanToNmg))
return BRLCAD_ERROR;
*result = NULL;
FacetizeNmgBooleanResult *evaluation =
new FacetizeNmgBooleanResult;
if (facetize_nmg_compute(gedp, request, evaluation) != BRLCAD_OK) {
/* Bomb paths deliberately abandon unsafe NMG allocations until this
* one-shot worker exits. */
facetize_nmg_boolean_destroy(evaluation);
return BRLCAD_ERROR;
}
*result = evaluation;
return BRLCAD_OK;
}
size_t
facetize_nmg_boolean_payload_size(
const FacetizeNmgBooleanResult *UNUSED(result))
{
/* NMG model storage is graph-shaped and has no cheap serialized-size
* estimate. The supervisor applies its conservative default write
* timeout when the estimate is zero. */
return 0;
}
int
facetize_nmg_boolean_write(struct db_i *dbip,
FacetizeNmgBooleanResult *result)
{
if (!dbip || !result || (!result->bot && !result->nmg_model))
return BRLCAD_ERROR;
struct rt_db_internal intern;
RT_DB_INTERNAL_INIT(&intern);
intern.idb_major_type = DB5_MAJORTYPE_BRLCAD;
intern.idb_type = result->bot ? ID_BOT : ID_NMG;
intern.idb_meth = &OBJ[intern.idb_type];
intern.idb_ptr = result->bot ? static_cast<void *>(result->bot) :
static_cast<void *>(result->nmg_model);
bu_avs_init_empty(&intern.idb_avs);
(void)bu_avs_add(&intern.idb_avs, "facetized", "1");
(void)bu_avs_add(&intern.idb_avs, FACETIZE_METHOD_ATTR, "NMG Boolean");
struct directory *output = db_diradd(dbip,
FACETIZE_WORKER_RESULT_OBJECT, RT_DIR_PHONY_ADDR, 0,
RT_DIR_SOLID, &intern.idb_type);
if (!output) {
rt_db_free_internal(&intern);
result->bot = NULL;
result->nmg_model = NULL;
return BRLCAD_ERROR;
}
int write_result = rt_db_put_internal(output, dbip, &intern);
result->bot = NULL;
result->nmg_model = NULL;
return write_result < 0 ? BRLCAD_ERROR : BRLCAD_OK;
}
void
facetize_nmg_boolean_destroy(FacetizeNmgBooleanResult *result)
{
if (!result)
return;
if (result->bot) {
struct rt_db_internal intern;
RT_DB_INTERNAL_INIT(&intern);
intern.idb_major_type = DB5_MAJORTYPE_BRLCAD;
intern.idb_type = ID_BOT;
intern.idb_meth = &OBJ[ID_BOT];
intern.idb_ptr = result->bot;
rt_db_free_internal(&intern);
}
if (result->nmg_model)
nmg_km(result->nmg_model);
delete result;
}
// Local Variables:
// tab-width: 8
// mode: C++
// c-basic-offset: 4
// indent-tabs-mode: t
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
@@ -0,0 +1,41 @@
/* N M G _ B O O L E A N . H
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
#ifndef FACETIZE_PROCESS_NMG_BOOLEAN_H
#define FACETIZE_PROCESS_NMG_BOOLEAN_H
#include <cstddef>
#include "../worker.h"
struct db_i;
struct ged;
struct FacetizeNmgBooleanResult;
int facetize_nmg_boolean_evaluate(struct ged *gedp,
const FacetizeWorkerRequest &request,
FacetizeNmgBooleanResult **result);
size_t facetize_nmg_boolean_payload_size(
const FacetizeNmgBooleanResult *result);
int facetize_nmg_boolean_write(struct db_i *dbip,
FacetizeNmgBooleanResult *result);
void facetize_nmg_boolean_destroy(FacetizeNmgBooleanResult *result);
#endif /* FACETIZE_PROCESS_NMG_BOOLEAN_H */
-177
View File
@@ -1,177 +0,0 @@
/* S A M P L E . C P P
* BRL-CAD
*
* Copyright (c) 2008-2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
/** @file libged/facetize/continuation.cpp
*
* The facetize command's Continuation Method implementation.
*/
#include "common.h"
#include "vmath.h"
#include "bu/datetime.h"
#include "bg/trimesh.h"
#include "raytrace.h"
#include "analyze/polygonize.h"
#include "../../ged_private.h"
#include "./tessellate.h"
#define FACETIZE_MEMORY_THRESHOLD 150000000
static void
_rt_pnts_bbox(point_t rpp_min, point_t rpp_max, struct rt_pnts_internal *pnts)
{
struct pnt_normal *pn = NULL;
struct pnt_normal *pl = NULL;
if (!rpp_min || !rpp_max || !pnts || !pnts->point) return;
VSETALL(rpp_min, INFINITY);
VSETALL(rpp_max, -INFINITY);
pl = (struct pnt_normal *)pnts->point;
for (BU_LIST_FOR(pn, pnt_normal, &(pl->l))) {
VMINMAX(rpp_min, rpp_max, pn->v);
}
}
static double
_bbox_vol(point_t b_min, point_t b_max)
{
double bbox_vol = 0.0;
fastf_t b_xlen, b_ylen, b_zlen;
b_xlen = fabs(b_max[X] - b_min[X]);
b_ylen = fabs(b_max[Y] - b_min[Y]);
b_zlen = fabs(b_max[Z] - b_min[Z]);
bbox_vol = b_xlen * b_ylen * b_zlen;
return bbox_vol;
}
struct rt_pnts_internal *
_tess_pnts_sample(const char *oname, struct db_i *dbip, tess_opts *s)
{
if (!oname || !dbip || !s)
return NULL;
struct directory *dp = db_lookup(dbip, oname, LOOKUP_QUIET);
if (!dp)
return NULL;
/* If we have a point cloud or half, this won't work */
if (dp->d_minor_type == DB5_MINORTYPE_BRLCAD_PNTS || dp->d_minor_type == DB5_MINORTYPE_BRLCAD_HALF)
return NULL;
/* Key some settings off the bbox size */
point_t obj_min, obj_max;
struct bu_vls msg = BU_VLS_INIT_ZERO;
rt_obj_bounds(&msg, dbip, 1, (const char **)&oname, 0, obj_min, obj_max);
if (bu_vls_strlen(&msg))
bu_log("Bounds calculation: %s\n", bu_vls_cstr(&msg));
bu_vls_free(&msg);
point_t rpp_min, rpp_max;
VSETALL(rpp_min, INFINITY);
VSETALL(rpp_max, -INFINITY);
VMINMAX(rpp_min, rpp_max, (double *)obj_min);
VMINMAX(rpp_min, rpp_max, (double *)obj_max);
double xlen = fabs(rpp_max[X] - rpp_min[X]);
double ylen = fabs(rpp_max[Y] - rpp_min[Y]);
double zlen = fabs(rpp_max[Z] - rpp_min[Z]);
/* Pick our mode(s) */
int flags = 0;
flags |= RT_GEN_OBJ_PNTS_RAND;
//flags |= RT_GEN_OBJ_PNTS_SOBOL;
struct rt_pnts_internal *pnts = NULL;
BU_GET(pnts, struct rt_pnts_internal);
pnts->magic = RT_PNTS_INTERNAL_MAGIC;
pnts->scale = 0.0;
pnts->count = 0;
pnts->type = RT_PNT_TYPE_NRM;
pnts->point = NULL;
/* Shoot - we need both the avg thickness of the hit partitions and seed points */
struct bn_tol btol = BN_TOL_INIT_TOL;
if (rt_gen_obj_pnts(pnts, &s->pnt_options.avg_thickness, dbip, oname, &btol, flags, s->pnt_options.max_pnts, s->pnt_options.max_sample_time, 1) || pnts->count <= 0) {
struct pnt_normal *rpnt = (struct pnt_normal *)pnts->point;
if (rpnt) {
struct pnt_normal *entry;
while (BU_LIST_WHILE(entry, pnt_normal, &(rpnt->l))) {
BU_LIST_DEQUEUE(&(entry->l));
BU_PUT(entry, struct pnt_normal);
}
BU_PUT(rpnt, struct pnt_normal);
}
BU_PUT(pnts, struct rt_pnts_internal);
return NULL;
}
/* Check the volume of the bounding box of input object against the bounding box
* of the point cloud - a large difference means something probably isn't
* right. This one we are a bit more generous with, since the parent bbox
* may not be terribly tight. */
{
point_t p_min, p_max;
VSETALL(p_min, INFINITY);
VSETALL(p_max, -INFINITY);
_rt_pnts_bbox(p_min, p_max, pnts);
s->pnt_options.pnts_bbox_vol = _bbox_vol(p_min, p_max);
s->pnt_options.obj_bbox_vol = _bbox_vol(rpp_min, rpp_max);
if (fabs(s->pnt_options.obj_bbox_vol - s->pnt_options.pnts_bbox_vol)/s->pnt_options.obj_bbox_vol > 1) {
struct pnt_normal *rpnt = (struct pnt_normal *)pnts->point;
if (rpnt) {
struct pnt_normal *entry;
while (BU_LIST_WHILE(entry, pnt_normal, &(rpnt->l))) {
BU_LIST_DEQUEUE(&(entry->l));
BU_PUT(entry, struct pnt_normal);
}
BU_PUT(rpnt, struct pnt_normal);
}
BU_PUT(pnts, struct rt_pnts_internal);
return NULL;
}
}
/* Find the smallest value from either the bounding box lengths or the avg
* thickness observed by the rays. Some fraction of this value is our box
* size for the polygonizer and the decimation routine */
double min_len = (xlen < ylen) ? xlen : ylen;
min_len = (min_len < zlen) ? min_len : zlen;
min_len = (min_len < s->pnt_options.avg_thickness) ? min_len : s->pnt_options.avg_thickness;
if (s->pnt_options.feature_size > 0) {
s->pnt_options.target_feature_size = 0.5*s->pnt_options.feature_size;
} else {
s->pnt_options.target_feature_size = min_len * s->pnt_options.feature_scale;
}
bu_log("feature_size: %f\n", s->pnt_options.feature_size);
bu_log("feature_scale: %f\n", s->pnt_options.feature_scale);
bu_log("target_feature_size: %f\n", s->pnt_options.target_feature_size);
return pnts;
}
// Local Variables:
// tab-width: 8
// mode: C++
// c-basic-offset: 4
// indent-tabs-mode: t
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
File diff suppressed because it is too large Load Diff
+6 -7
View File
@@ -41,11 +41,10 @@ class tess_opts {
public:
method_options_t method_opts;
nmg_opts nmg_options;
cm_opts cm_options;
mdc_opts mdc_options;
spsr_opts spsr_options;
int overwrite_obj = 0;
sample_opts pnt_options; // Values used by sample.cpp
};
extern struct rt_bot_internal *
@@ -57,9 +56,6 @@ _tess_facetize_write_bot(struct db_i *dbip, struct rt_bot_internal *bot, const c
extern void
_tess_facetize_free_bot(struct rt_bot_internal *bot);
extern struct rt_pnts_internal *
_tess_pnts_sample(const char *oname, struct db_i *dbip, tess_opts *s);
extern int
_brep_csg_tessellate(struct rt_bot_internal **obot, struct ged *gedp,
struct directory *dp, tess_opts *s);
@@ -68,10 +64,13 @@ extern int
_nmg_tessellate(struct rt_bot_internal **nbot, struct rt_db_internal *intern, tess_opts *s);
extern int
continuation_mesh(struct rt_bot_internal **obot, struct db_i *dbip, const char *objname, tess_opts *s, point_t seed);
mdc_mesh(struct rt_bot_internal **output, struct db_i *dbip,
const char *object, tess_opts *settings);
extern int
spsr_mesh(struct rt_bot_internal **obot, struct db_i *dbip, struct rt_pnts_internal *pnts, tess_opts *s);
spsr_mesh(struct rt_bot_internal **obot, struct db_i *dbip,
const char *object, struct rt_pnts_internal *pnts,
bool best_effort_source, tess_opts *s);
extern bool
bot_is_manifold(struct rt_bot_internal *bot);

Some files were not shown because too many files have changed in this diff Show More