Compare commits

..
6 Commits
Author SHA1 Message Date
Clifford Yapp 1ab8c379e9 Stash some thoughts about what to do with BoTs. 2026-09-18 10:12:00 -04:00
Clifford Yapp c12e55973a Stash some info gleaned from old reports and manuals. 2026-09-18 10:12:00 -04:00
Clifford Yapp 1968ba02a3 Quick stab at adding some support for pre-GIFT COMGEOM
Only known surviving examples would be fuzzy printouts in old report scans -
would need really good OCR work to get those set up to actually test, so in the
meantime just checkpointing the V0 work to revisit later.
2026-09-18 10:12:00 -04:00
Clifford Yapp 40621be317 Various minor adjustments 2026-09-18 10:12:00 -04:00
Clifford Yapp 92ba4caa42 Use correct type for opt target 2026-09-18 10:12:00 -04:00
Clifford Yapp ef75af075a Initial checkpoint of C++ rework of comgeom-g 2026-09-18 10:12:00 -04:00
325 changed files with 28160 additions and 43961 deletions
+2 -2
View File
@@ -384,7 +384,7 @@ jobs:
osx:
name: macOS Clang
needs: [linux]
runs-on: macos-15
runs-on: macos-14
strategy:
fail-fast: true
steps:
@@ -403,7 +403,7 @@ jobs:
- name: Prepare bext dependencies
uses: ./brlcad/.github/actions/prepare-bext
with:
platform-channel: macos-15-${{ runner.arch }}-release
platform-channel: macos-14-${{ runner.arch }}-release
source-dir: ${{ github.workspace }}/brlcad
output-dir: ${{ runner.temp }}/bext_output
build-type: Release
+1 -1
View File
@@ -177,7 +177,7 @@ jobs:
osx_bext:
name: macOS - BRL-CAD bext build
needs: [linux_bext]
runs-on: macos-15
runs-on: macos-14
strategy:
fail-fast: true
if: ${{ !cancelled() }}
+20 -36
View File
@@ -231,7 +231,7 @@ set(CMAKE_FIND_USE_PACKAGE_REGISTRY FALSE)
# Prepare to manage and build BRL-CAD's external dependencies. Specifies a
# specific commit hash to use in the 'bext' repository.
include(BRLCAD_ExternalDeps)
brlcad_bext_init(42d8d7ca57f9d2f61192b41f0b41a731f1ba6c47)
brlcad_bext_init(12115e6c5922e2db27a9b88e00dd714af5315f69)
#---------------------------------------------------------------------
# Define various utilities.
@@ -352,10 +352,9 @@ set(CMAKE_SKIP_BUILD_RPATH FALSE)
# it should only be set to the installation value when actually installed.
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
# Installed runtime paths are defined explicitly below. Do not append
# directories discovered on the build host, since those paths may not exist
# or may resolve to unrelated libraries on the installed system.
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
# Add the automatically determined parts of the RPATH which point to
# directories outside the build tree to the install RPATH
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
# The following logic is what allows binaries to run successfully in the build
# directory AND install directory.
@@ -679,13 +678,8 @@ endif(MSVC)
# before use so platforms without GNU/LLVM-style linker options are skipped.
brlcad_check_linker_flag(C "LINKER:--no-undefined" BRLCAD_LINKER_NO_UNDEFINED_C_FLAG_FOUND)
brlcad_check_linker_flag(CXX "LINKER:--no-undefined" BRLCAD_LINKER_NO_UNDEFINED_CXX_FLAG_FOUND)
if(BRLCAD_LINKER_NO_UNDEFINED_C_FLAG_FOUND AND BRLCAD_LINKER_NO_UNDEFINED_CXX_FLAG_FOUND
AND NOT BRLCAD_ENABLE_ADDRESS_SANITIZER)
if(BRLCAD_LINKER_NO_UNDEFINED_C_FLAG_FOUND AND BRLCAD_LINKER_NO_UNDEFINED_CXX_FLAG_FOUND)
set(BRLCAD_LINKER_NO_UNDEFINED_FLAG "LINKER:--no-undefined" CACHE STRING "Linker flag requiring resolved shared library symbols" FORCE)
else()
# Clang links the AddressSanitizer runtime into executables, leaving its
# shared-library instrumentation symbols unresolved until then.
set(BRLCAD_LINKER_NO_UNDEFINED_FLAG "" CACHE STRING "Linker flag requiring resolved shared library symbols" FORCE)
endif()
brlcad_check_linker_flags(C BRLCAD_LINKER_WHOLE_ARCHIVE_GROUP_C_FLAG_FOUND "LINKER:--whole-archive" "LINKER:--no-whole-archive")
brlcad_check_linker_flags(CXX BRLCAD_LINKER_WHOLE_ARCHIVE_GROUP_CXX_FLAG_FOUND "LINKER:--whole-archive" "LINKER:--no-whole-archive")
@@ -1648,31 +1642,21 @@ if(NOT MSVC)
endif(BRLCAD_WARNINGS OR BRLCAD_ENABLE_STRICT)
if(BRLCAD_ENABLE_COVERAGE)
if(CMAKE_C_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
check_c_flag(fprofile-instr-generate)
check_cxx_flag(fprofile-instr-generate)
check_c_flag(fcoverage-mapping)
check_cxx_flag(fcoverage-mapping)
if(NOT FPROFILE_INSTR_GENERATE_C_FLAG_FOUND OR
NOT FPROFILE_INSTR_GENERATE_CXX_FLAG_FOUND OR
NOT FCOVERAGE_MAPPING_C_FLAG_FOUND OR
NOT FCOVERAGE_MAPPING_CXX_FLAG_FOUND)
message(FATAL_ERROR "Clang source-based coverage is not supported by this toolchain.")
endif()
add_compile_options(-fprofile-instr-generate -fcoverage-mapping)
add_link_options(-fprofile-instr-generate)
else()
check_c_flag(ftest-coverage)
check_cxx_flag(ftest-coverage)
if(FTEST_COVERAGE_C_FLAG_FOUND AND FTEST_COVERAGE_CXX_FLAG_FOUND)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -lgcov -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
else()
message(FATAL_ERROR "GCC coverage is not supported by this toolchain.")
endif()
endif()
# TODO: These seem to GCC specific flags - should probably set up for clang
# as well if that's our compiler...
# https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
# If we need different settings for different tools, should
# encapsulate each tool's setup in a function
check_c_flag(ftest-coverage)
check_cxx_flag(ftest-coverage)
if(FTEST_COVERAGE_C_FLAG_FOUND)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -lgcov -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
else(FTEST_COVERAGE_C_FLAG_FOUND)
message(SEND_ERROR "Building with coverage is not supported by BRL-CAD on this platform.")
endif(FTEST_COVERAGE_C_FLAG_FOUND)
endif(BRLCAD_ENABLE_COVERAGE)
if(BRLCAD_ENABLE_STRICT)
+10011 -8734
View File
File diff suppressed because it is too large Load Diff
+5 -39
View File
@@ -151,13 +151,10 @@ recommended for serious developers.
Various useful configurations for working with bext:
* Acquire bext and its dependencies from another repository host
* Get a git clone that has everything which might be needed (large,
but useful for offline building)
cmake .. -DBRLCAD_REPOSITORY_ROOT=https://git.example.org/brlcad
* Acquire bext and its dependencies from local bare Git mirrors
cmake .. "-DBRLCAD_REPOSITORY_ROOT=/path/to/mirror/BRL-CAD"
git clone --recurse-submodules https://github.com/BRL-CAD/brlcad
* Have prebuilt bext outputs to reuse - BRL-CAD will simply copy and prepare
the pre-existing files
@@ -401,40 +398,10 @@ Aliases: ENABLE_RUNTIME_DEBUG, ENABLE_RUN_TIME_DEBUG, ENABLE_RUNTIME_DEBUGGING
ENABLE_RUN_TIME_DEBUGGING
--- BRLCAD_REPOSITORY_ROOT ---
Repository URL or local directory containing bext and its dependency
repositories. Defaults to https://github.com/BRL-CAD. The root directly
contains repositories such as bext.git and astyle.git; BRL-CAD does not
append BRL-CAD to the root.
Remote Git URLs (including SSH), native paths, and file:// URLs are supported.
Relative native paths are resolved against the BRL-CAD source directory.
Quote the entire -D argument for paths containing spaces. The setting is
cached and is also passed as BEXT_REPOSITORY_ROOT when BRL-CAD configures
bext, including when BRLCAD_EXT_SOURCE_DIR supplies an existing checkout.
Alternate roots require bext sources with BEXT_REPOSITORY_ROOT support.
Local roots restrict CMake-driven Git acquisition and queries to file
transport, with no online fallback. Mirrors must contain all required pinned
commits and recursive submodules. Dependencies outside bext's canonical URL
prefix require their own Git URL mappings. This setting does not restrict
network access by dependency build scripts or change persistent Git settings.
BRLCAD_EXT_DIR still selects prebuilt outputs, and changing the repository
root alone does not force those outputs to be rebuilt.
To return to the default source:
cmake .. -DBRLCAD_REPOSITORY_ROOT=https://github.com/BRL-CAD
--- BRLCAD_EXT_DIR ---
If specified, BRL-CAD will look in this directory for bext compiled outputs to
bundle and use during the build. Relative paths are resolved against the
BRL-CAD source directory. Distcheck subconfigures reuse this resolved output
directory and preserve BRLCAD_REPOSITORY_ROOT, BRLCAD_EXT_SOURCE_DIR, and
BRLCAD_EXT_PARALLEL when specified.
bundle and use during the build.
--- BRLCAD_EXT_SOURCE_DIR ---
@@ -442,8 +409,7 @@ BRLCAD_EXT_PARALLEL when specified.
If specified, BRL-CAD will look in this directory for bext source files. Use
this when you have a pre-existing bext clone and don't want BRL-CAD's configure
process to pull down another copy, but are still looking to have the BRL-CAD
configure process manage producing bext build outputs. Relative paths are
resolved against the BRL-CAD source directory.
configure process manage producing bext build outputs.
--- BRLCAD_EXT_PARALLEL ---
+46 -86
View File
@@ -10,107 +10,78 @@ descriptive text that emphasizes or further describes significant
changes made. See document footer for additional details.
----------------------------------------------------------------------
--- 2026-10-12 Release 7.46.0 ---
--- 20XX-XX-XX Release 7.46.0 ---
----------------------------------------------------------------------
This release expands geometry conversion and improves modeling
reliability. STEP support adds AP203e2, AP214, and AP242
import/export, native CSG round trips, and AP242 product and
manufacturing information (PMI) import. IGES and JT conversion is
expanded, and new OpenSCAD CSG import/export tools are available.
Facetization is more reliable, NMG solid ray tracing is 10-20 times
faster, CSG ray tracing is faster, and BoT, DSP, and point-cloud ray
intersections are corrected. MGED gains numerous fixes to primitive,
sketch, and combination editing, drawing, dialogs, and preferences.
New tools support object nesting with arrange, building generation
with mkbuilding, annotation editing, and primitive repair. Geometry
analysis, image utilities, Windows support, documentation, and bundled
dependencies also receive substantial updates.
* corrected Archer logic for saving window size - Cliff Yapp
* fixed inside BoT rays, DSP surface normals, PNTS bounds - Cliff Yapp
* improved mged fallback HTML manual viewer - Cliff Yapp
* fixed gqa undefined densities and exit status - Chris McGregor
* added ARBN support to the mged inside command - Cliff Yapp
* improved HYP neck-ratio labels and BoT face selection - Cliff Yapp
* fixed mged view panning, edit rotation, & ray queries - Cliff Yapp
* preserved ARB, CLINE, TGC, & extrusion geometry edits - Cliff Yapp
* restored mged command prompt focus at GUI startup - Cliff Yapp
* improved pixscale & bwscale scaling and image piping - Sean Morrison
* restored pixdiff behavior on .pix, added -b for .bw - Sean Morrison
* improved facetize fallback methods and reliability - Cliff Yapp
* updated mged and Archer for Tcl 8.6.18 and Itcl/Itk 4 - Cliff Yapp
* updated MGED and Archer for Tcl 8.6.18 and Itcl/Itk 4 - Cliff Yapp
* fixed writes to fileless geometry databases - Cliff Yapp
* fixed nastran-g handling of large property IDs - Rupesh, Cliff Yapp
* fixed dxf-g invalid polyface idx crash - Meghna45-code, Cliff Yapp
* fixed dxf-g invalid polyface index crashes - Meghna45-code, Cliff Yapp
* fixed Archer primitive editors under Itcl 4 - Cliff Yapp
* fixed Archer shutdown menu cleanup errors - Cliff Yapp
* fixed MGED Build Pattern dialog under Itcl 4 - Cliff Yapp
* fixed TclCAD framebuffer listing and deletion - Cliff Yapp
* fixed mged Build Pattern progress updates - Cliff Yapp
* fixed mged search -exec reentry and Ctrl-C crashes - Cliff Yapp
* fixed mged asynchronous commands and clone progress - Cliff Yapp
* accelerated large and repeated mged path drawing - Cliff Yapp
* fixed MGED Build Pattern progress updates - Cliff Yapp
* fixed MGED search -exec reentry and Ctrl-C crashes - Cliff Yapp
* fixed MGED asynchronous commands and clone progress - Cliff Yapp
* accelerated large and repeated MGED path drawing - Cliff Yapp
* fixed ARB7 inside solid construction - Cliff Yapp
* fixed direct ARB face and edge-direction editing - Cliff Yapp
* fixed rtwizard startup with framebuffer contention - Cliff Yapp
* fixed Archer annotation line display updates - Cliff Yapp
* fixed Archer sketch scale drift in non-mm databases - Cliff Yapp
* fixed toggle arrow callbacks during setup - Cliff Yapp
* fixed Tcl dialogs with multi-word titles and msgs - Cliff Yapp
* fixed Tcl dialogs with multi-word titles and messages - Cliff Yapp
* fixed Archer Tank Wizard gun generation - Cliff Yapp
* fixed mged and Archer sketch arc tangency editing - Cliff Yapp
* fixed MGED and Archer sketch arc tangency editing - Cliff Yapp
* fixed Archer ARB4 point 3 editing - Cliff Yapp
* fixed ARB4 storage from make and mk_arb4 - Cliff Yapp
* fixed ARB4 and ARB6 center point calculations - Cliff Yapp
* fixed concurrent mged and rtwizard framebuffer use - Cliff Yapp
* fixed mged ShotVis editing and reload failures - Cliff Yapp
* fixed concurrent MGED and rtwizard framebuffer use - Cliff Yapp
* fixed MGED ShotVis editing and reload failures - Cliff Yapp
* fixed Tcl widget geometry and selection defects - Cliff Yapp
* fixed mged BoT editor property handling - Cliff Yapp
* fixed mged LOD dialog and adaptive BoT wireframes - Cliff Yapp
* fixed mged Plot, PostScript, and manual launch tools - Cliff Yapp
* fixed mged Overlap Tool multi-object result handling - Cliff Yapp
* fixed MGED BoT editor property handling - Cliff Yapp
* fixed MGED LOD dialog and adaptive BoT wireframes - Cliff Yapp
* fixed MGED Plot, PostScript, and manual launch tools - Cliff Yapp
* fixed MGED Overlap Tool multi-object result handling - Cliff Yapp
* eliminated 16-fold redundant gchecker grid analysis - Cliff Yapp
* fixed submodel transforms & relative database paths - Sean Morrison
* fixed Archer tree handling for names with spaces - Cliff Yapp
* fixed Archer tree handling for object names with spaces - Cliff Yapp
* fixed RtWizard multi-object edge occlusion handling - Cliff Yapp
* fixed RtWizard title, About, and preference handling - Cliff Yapp
* fixed mged startup file false legacy-syntax warnings - Cliff Yapp
* fixed mged saved-view deletion and collaboration sync - Cliff Yapp
* fixed mged font preferences apply and persistence - Cliff Yapp
* fixed MGED startup file false legacy-syntax warnings - Cliff Yapp
* fixed MGED saved-view deletion and collaboration sync - Cliff Yapp
* fixed MGED font preferences apply and persistence - Cliff Yapp
* fixed Archer combination edits changing Boolean trees - Cliff Yapp
* fixed Archer command-line help startup crash - Cliff Yapp
* fixed mged Geometry Browser menu and close behavior - Cliff Yapp
* fixed mged collaboration view unshare crash - Cliff Yapp
* added override for Archer's preference file location - Cliff Yapp
* fixed MGED Geometry Browser menu and close behavior - Cliff Yapp
* fixed MGED collaboration view unshare crash - Cliff Yapp
* added an override for Archer's preference file location - Cliff Yapp
* fixed embedded Archer display and mode menus - Cliff Yapp
* added Archer -h and --help command-line options - Cliff Yapp
* fixed Archer Help opening the documentation menu - Sean Morrison
* added arrange nest command for packing db objects - Sean Morrison
* added mkbuilding for parametric/OSM/CityJSON models - Sean Morrison
* added arrange nest command for packing database objects - Sean Morrison
* new mkbuilding tool for parametric/OSM/CityJSON models - Sean Morrison
* cross-platform BINUNIF and DSP byte-order handling - Sean Morrison
* improved several cv default behaviors (s,32,64,n) - Sean Morrison
* improved several cv default behaviors (s, 32, 64, n) - Sean Morrison
* fixed invalid bot handling bug applying matrices - Sean Morrison
* improved glTF import robustness via gcv - Sean Morrison
* fixed bot dump glTF/GLB export; -F is now rejected - Cliff Yapp
* faster NMG solid ray tracing (10-20x faster) - Sean Morrison
* preserved prim colors in shaded BoT and B-Rep drawing - Cliff Yapp
* preserved B-Rep attrs through in-place geometry edits - Cliff Yapp
* fixed gcv filter arguments w/ one-character values - Cliff Yapp
* preserved primitive colors in shaded BoT and B-Rep drawing - Cliff Yapp
* preserved B-Rep attributes through in-place geometry edits - Cliff Yapp
* fixed gcv filter arguments containing one-character values - Cliff Yapp
* improved iges-g importer robustness - Cliff Yapp
* expanded g-iges native CSG and B-Rep export support - Cliff Yapp
* added IGES import and export support to gcv - Cliff Yapp
* rt -b query option now supports a single value idx - Sean Morrison
* rt -b query option now supports a single value index - Sean Morrison
* fixed .dpix file format portability and icv support - Sean Morrison
* expanded pixscale support for piped image data - Sean Morrison
* fixed HYP volume and ARBN totals reported by analyze - Cliff Yapp
* fixed VOL data-file lookup and missing-data errors - Chris McGregor
* corrected rtarea sampling/exposed-area calculations - Cliff Yapp
* corrected rtarea sampling and exposed-area calculations - Cliff Yapp
* bot split now groups results and preserves mesh data - Cliff Yapp
* bot decimate now preserves nrmls, UVs, and plate data - Cliff Yapp
* bot decimate now preserves normals, UVs, and plate data - Cliff Yapp
* added raytracing support for annotation objects - Cliff Yapp
* fixed Windows subprocess quoting and empty arguments - Cliff Yapp
* fixed mged X embedded framebuffer display manager crash - Cliff Yapp
* fixed Windows drive-letter path handling - Cliff Yapp
* fixed MGED X embedded framebuffer display manager crash - Cliff Yapp
* added annotate command for managing annotation objects - Cliff Yapp
* added repair command for supported invalid primitives - Cliff Yapp
* changed default new-region material code from 1 to 0 - Keith Bowman
@@ -132,34 +103,26 @@ dependencies also receive substantial updates.
* added option validation to image & framebuffer tools - Sean Morrison
* added contributor, API, command, & raytracing guides - Sean Morrison
* added and updated command and library manual pages - Sean Morrison
* added pnts write -f xyz export option - Chris McGregor
* fixed mged ARB5 point editing - Cliff Yapp
* fixed mged gqa and rtcheck stale overlap plots - Cliff Yapp
* fixed procedural tree generator crashes - Sean Morrison, Cliff Yapp
* added gaia Earth models with day/dusk/night textures - Sean Morrison
* fixed fbcolor ramps & Windows input - Cliff Yapp, Sean Morrison
* added parashot gravity trajectory path generator - Sean Morrison
* added metaball pnt scaling (via metaball_move_pnt) - Sean Morrison
* added metaball point scaling (via metaball_move_pnt) - Sean Morrison
* all raytracing apps now report object prep failures - Sean Morrison
* improved fast4-g robustness and error reporting - Cliff Yapp
* improved robustness of g-vrml plate mode handling - Cliff Yapp
* added support for tick drawing on mged model axis - Cliff Yapp
* fixed MGED ARB5 point, pipe, and sketch editing - Cliff Yapp
* added pnts write -f xyz export option - Chris McGregor
* added support for tick drawing on MGED model axis - Cliff Yapp
* added naca456 left and paired wing generation - Cliff Yapp
* added patch-g -j option, specify object thicknesses - Sean Morrison
* corrected bwstat median and input error handling - Sean Morrison
* fixed fbcolor color ramp bug drawing the last color - Sean Morrison
* fixed gist -Q quick preview option - Sean Morrison
* rt preserves eye_pt setting in command scripts - Sean Morrison
* removed trailing whitespace from nirt output - Sean Morrison
* rt -l8 heat graphs now measure CPU time - Sean Morrison
* documented gqa centroid & moments of inertia options - Sean Morrison
* pixdiff now calculates pixel differences on bw files - Sean Morrison
* fixed bwcrop single-axis crops & pixembed edge fill - Sean Morrison
* fixed bwcrop single-axis crops and pixembed edge fill - Sean Morrison
* fixed isize in non-mm databases - Josh Baker
* fixed metaball adjust clearing points on errors - Sean Morrison
* fixed benchmark+pixcmp correctness validation bug - Sean Morrison
* fixed mged gqa option and value parsing - Sean Morrison
* improved gist hierarchy overflow rendering - Chris McGregor
* fixed rtxray -o grayscale/BW output - Sean Morrison
* fbhelp writes help to stdout and rest to stderr - Sean Morrison
* FB now supports /dev/stdout and /dev/stderr devices - Sean Morrison
* fixed mged rt -C + -o background color to file bug - Sean Morrison
@@ -168,7 +131,7 @@ dependencies also receive substantial updates.
* added rtsil -o support for bw output - Sean Morrison
* added cutting plane animation generation to rtwizard - Cliff Yapp
* fixed pixdiff bug on Windows - Sean Morrison
* fixed mged startup window placement on Windows - Chris McGregor
* fixed MGED startup window placement on Windows - Chris McGregor
* fixed pixborder utility right-side edge bug - Sean Morrison
* fixed 'wavelet' image processing tool on Windows - Sean Morrison
* quieted mged killtree spurious lookup failure output - Sean Morrison
@@ -179,16 +142,14 @@ dependencies also receive substantial updates.
* new doc guide, a visual overview of BRL-CAD objects - Sean Morrison
* improved triangle quality produced by bot remesh - Cliff Yapp
* updated numerous manpage command references - Josh Baker
* fixed submodel paths relative to parent databases - Sean Morrison
* improved performance making huge combinations - Sean Morrison
* added PNTS get, adjust, form, & g2asc/asc2g support - Chris McGregor
* fixed ANNOT get, adjust, & g2asc/asc2g support - Chris McGregor
* added make -t primitive type listing - Chris McGregor
* added gcv import conversion support for vrml/wrl 1.0 - Sean Morrison
* fixed gcv appending to existing geometry databases - Sean Morrison
* added CSS names, hex, HSL/HSV to libbu color parsing - Sean Morrison
* improved OpenNURBS B-Rep import and adjustment safety - Cliff Yapp
* improved Debian application menus/MIME associations - Jordi Sayol
* updated Tcl/Tk dependency to v8.6.18 using bundled Itcl - Cliff Yapp
* improved Debian application menus and MIME associations - Jordi Sayol
* updated Clipper2 dependency to v1.5.4 - Cliff Yapp
* updated Eigen dependency to v5.0.1 - Cliff Yapp
* updated GDAL dependency to v3.13.3 - Cliff Yapp
@@ -200,6 +161,7 @@ dependencies also receive substantial updates.
* updated OpenNURBS dependency to v8.34.26223.11001 - Cliff Yapp
* updated PROJ dependency to v9.8.1 - Cliff Yapp
* updated PoissonRecon dependency to v18.75 - Cliff Yapp
* updated Tcl/Tk dependency to v8.6.18, using bundled Itcl - Cliff Yapp
* updated libjpeg-turbo dependency to v3.2.0 - Cliff Yapp
* updated libosmesa dependency to SHA1 bfa90de - Cliff Yapp
* updated libpng dependency to v1.6.58 - Cliff Yapp
@@ -308,7 +270,7 @@ and extensively improving existing docs.
* added metadata-aware image diff & nirt analysis - Cliff Yapp
* added basic PNTS point-cloud ray tracing - Sean Morrison
* improved nirt behavior in Windows bash consoles - Cliff Yapp
* added g2asc/asc2g support for DSP and EBM - Sean Morrison
* added g2asc/asc2g support for PNTS, DSP, and EBM - Sean Morrison
* updated gct mesh decimation dependency to mmesh - Cliff Yapp
* added procedural wing, propeller & tree generators - Cliff Yapp
* added ISO tire dimensions & named tread presets - Cliff Yapp
@@ -580,7 +542,6 @@ for more information.
----------------------------------------------------------------------
--- 2023-11-06 Release 7.38.0 ---
----------------------------------------------------------------------
This major release of BRL-CAD introduces significant improvements to
the robustness of the facetize command by leveraging the Manifold
library for boolean evaluation of meshes. Facetization is an operation
@@ -619,7 +580,6 @@ processing high level assemblies in BRL-CAD's example geometry files:
----------------------------------------------------------------------
--- 2023-07-26 Release 7.36.0 ---
----------------------------------------------------------------------
This major release of BRL-CAD introduces significant updates to
geometry conversion support. The update includes modernization for
NURBS importing and interaction, BoT manipulation, and incorporates
+3 -3
View File
@@ -1,6 +1,6 @@
BRL-CAD
Release 7.46.0
http://brlcad.org/
BRL-CAD
Release 7.44.2
http://brlcad.org/
BRL-CAD is a powerful cross-platform open source combinatorial
solid modeling system that includes an interactive 3D solid geometry
+18 -34
View File
@@ -4,54 +4,38 @@
:manmanual: BRL-CAD
== NAME
pixdiff - highlight differences between two pix or bw images
pixdiff - highlight samples which differ between two pix or bw files
== SYNOPSIS
[source]
----
pixdiff _file1.pix_ _file2.pix_ > _out.pix_
pixdiff -b _file1.bw_ _file2.bw_ > _out.bw_
pixdiff _file1.bw_ _file2.bw_ > _out.bw_
----
[[description]]
== DESCRIPTION
By default, *pixdiff* compares raw *pix*(5) images as RGB pixels and writes
a PIX difference image to standard output. When all three channels of a
pixel match, it writes a dim grayscale version of that pixel to all three
output channels. When any channel differs, each matching channel is written
as zero, each channel differing by one is written as 192, and each channel
differing by more than one is written as 255.
*pixdiff* compares two raw *pix*(5) or *bw*(5) images byte by byte and
writes a difference image of the same size to standard output. Bytes that
differ by +/-1 are written at intensity 192, while larger differences are
written at full intensity 255. Matching bytes are written at half their
original intensity, providing visual context for locating differences.
With *-b*, *pixdiff* compares raw *bw*(5) images one byte at a time and
writes a BW difference image. Differing samples use the same 192 and 255
intensities; matching samples are written at half their original intensity.
No image dimensions are needed. The two inputs must be the same size in
bytes, and PIX input must contain complete three-byte pixels. Otherwise,
*pixdiff* reports an error. Either input file may be given as
Because the comparison does not interpret pixel structure, no format option
is needed and *pixdiff* is independent of image resolution. The two inputs
must be the same size in bytes; otherwise the program reports the mismatch
and exits. Either input file may be given as
*-* to read from standard input. Output must be redirected; *pixdiff*
refuses to write a raw image stream to a terminal.
Upon completion, *pixdiff* reports matching, off-by-one, and larger-difference
channel or sample counts on standard error.
Upon completion, the count of bytes that matched, that were off by 1, and
that were off by many is reported on standard error.
*bwdiff -n -b* produces a similar BW highlight image, but writes 255 rather
than 192 for off-by-one differences. The default *bwdiff*(1) output instead
shows signed differences around a neutral gray.
[[options]]
== OPTIONS
*-b*::
Treat both inputs and the output as one-byte-per-pixel BW images. Without
this option, use three-byte-per-pixel PIX images.
*-h*::
Show usage information.
Also see *bwdiff*(1), which can also operate on *pix*(5) files and can
produce other kinds of difference output.
[[examples]]
@@ -75,10 +59,10 @@ on standard error give a quick quantitative sense of how much changed.
====
[source,bash]
----
pixdiff -b depth_a.bw depth_b.bw > depth_diff.bw
pixdiff depth_a.bw depth_b.bw > depth_diff.bw
----
Writes a grayscale highlight image using byte-wise comparison. Matching
samples retain half their input intensity as context.
Writes a grayscale highlight image using the same byte comparison and
statistics as the color example; no format option is needed.
====
+2 -3
View File
@@ -18,8 +18,7 @@ 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/contour.h_, _analyze/polygonize.h_, _analyze/nirt.h_,
_analyze/voxelize.h_, and
_analyze/polygonize.h_, _analyze/nirt.h_, _analyze/voxelize.h_, and
_analyze/worker.h_.
== DESCRIPTION
@@ -33,7 +32,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, manifold dual contouring and voxelization of geometry, and the
processing, polygonization 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.
-50
View File
@@ -200,56 +200,6 @@ ____
The LIBRT_DEBUG environment variable may be set to the hexadecimal bit vector as described by the DEBUG_* flags listed in the raytrace.h header. Similarly, many LIBRT applications provide a "-x #" command-line option which will take precedence over the environment variable.
The RT_DEBUG_RTCMP flag (0x10000000) (set with LIBRT_DEBUG=10000000)
sets up rtcmp capture, but recording starts disabled. Set
LIBRT_RTCMP_FILE before enabling the flag, then call
rt_rtcmp_capture_set_enabled(1) from the application or a debugger to
record selected rays. Call rt_rtcmp_capture_set_enabled(0) to stop
recording. Enabling returns -1 if the debug flag is unset or the output
writer failed to initialize; disabling returns 0 even before setup. A
ray must begin while recording is enabled, and leaving recording
disabled when it finishes suppresses its record. The debug flag must be
set before concurrent ray shooting; use the setter to change recording
state while rays are running.
Captured hits append one high-precision rtcmp JSON record per ray with
final partitions. Misses produce no record. librt never clears the
output file; the caller must clear or rotate it before a new comparison
as needed. Separate processes must not write to the same file
concurrently. Pending records are flushed within two seconds under
normal operation and during normal process shutdown; call
rt_rtcmp_capture_flush() to wait for pending records when using a
debugger. Disabling recording does not discard records already queued. A
process terminated abnormally may lose pending records. rtcmp comparison
files should use the same hit-only capture mode on both sides; its
generation mode also includes misses. The output can be expanded to also
include primitive segments if LIBRT_RTCMP_PRIMITIVES is set to 1 before
capture is set up.
For example, run a single-threaded ray tracing application in a debugger
and stop at `rt_shootray` for the suspect ray. Capture just that call,
then compare it with a capture of the same ray from a reference build:
[source,shell]
----
$ LIBRT_DEBUG=10000000 LIBRT_RTCMP_FILE=trial.json \
gdb --args ./ray_application model.g
(gdb) break rt_shootray
(gdb) run
(gdb) call rt_rtcmp_capture_set_enabled(1)
(gdb) finish
(gdb) call rt_rtcmp_capture_set_enabled(0)
(gdb) call rt_rtcmp_capture_flush()
(gdb) quit
$ rtcmp -c reference.json trial.json
----
Continue to the suspect ray before enabling capture if the breakpoint
first stops on another ray. Produce `reference.json` with the same ray
and the same capture mode; set LIBRT_RTCMP_PRIMITIVES=1 for both runs
when primitive segments are needed to locate a difference before Boolean
evaluation.
The LIBRT_V4FLIP environment variable may be set to a true or false value to respectively force or disable endianness interpretation of v4 geometry database files. The default behavior is to automatically detect whether flipping the endian interpretation will help LIBRT read a binary-incompatible v4 geometry database file. Setting LIBRT_V4FLIP will override automatic detection.
+16 -24
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] [-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}
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}
----
@@ -45,8 +45,6 @@ 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.
@@ -63,21 +61,13 @@ 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 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.
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.
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.
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.
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 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.
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.
[[facetize_options]]
@@ -104,10 +94,6 @@ 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.)
@@ -133,7 +119,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 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.
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.
*--method-opts*::
@@ -245,15 +231,21 @@ mged>**facetize --tolerate-failures large_model large_model.partial.bot**
====
.Refine a Manifold Dual Contouring conversion
.Increase Decimation in CM Mode
[example]
====
This selects MDC directly and requests a finer cell size with a larger ray
budget than the default:
mged>**facetize --methods CM --method-opts feature_size=0.06 d_feature_size=2.0 ellipse.r ell.bot**
mged>**facetize --methods MDC --method-opts "MDC feature_size=0.1 max_rays=400000" ellipse.r ell.bot**
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.
Smaller feature sizes increase mesh detail, ray count, memory use, and runtime.
====
.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.
====
+16 -13
View File
@@ -679,21 +679,23 @@ Convert CSG geometry to a 3D voxel grid by:
fill fraction
* Level-of-detail parameter controls sub-voxel sampling
==== 7.5 Manifold Dual Contouring
==== 7.5 Implicit Surface Polygonizer (Marching Cubes / Tetrahedral)
`contour.cpp` evaluates CSG geometry directly with `librt` and constructs
closed, oriented triangle meshes:
An implicit surface polygonizer (Bloomenthal tetrahedral marching) for
converting any inside/outside function to a triangle mesh.
* 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
`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
==== 7.6 Geometry Differencing (raydiff)
@@ -1710,6 +1712,7 @@ 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,6 +55,7 @@ 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
@@ -0,0 +1,9 @@
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
+1
View File
@@ -61,4 +61,5 @@ Author: Paul Mackerras (paulus@cs.anu.edu.au),
file:/src/libtclcad/tkImgFmtPIX.c
file:/src/tclscripts/cad_clrpick.tcl
file:/src/tclscripts/html_library.tcl
-1
View File
@@ -31,7 +31,6 @@
#include "common.h"
#include "analyze/defines.h"
#include "analyze/contour.h"
#include "analyze/debug.h"
#include "analyze/diff.h"
#include "analyze/density.h"
-1
View File
@@ -1,6 +1,5 @@
set(
analyze_headers
contour.h
debug.h
defines.h
density.h
-118
View File
@@ -1,118 +0,0 @@
/* 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
*/
+2 -22
View File
@@ -43,29 +43,9 @@ struct analyze_polygonize_params {
size_t minimum_free_mem;
int verbosity;
};
#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
*/
#define ANALYZE_POLYGONIZE_PARAMS_DEFAULT { 0, 0, 30, 150000000, 0 }
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
-1
View File
@@ -55,7 +55,6 @@
#include "bg/clip.h"
#include "bg/lseg.h"
#include "bg/obr.h"
#include "bg/pca.h"
#include "bg/plane.h"
#include "bg/polygon.h"
#include "bg/sat.h"
+4 -4
View File
@@ -50,12 +50,12 @@ BG_EXPORT extern int bg_lseg_clip(fastf_t *xp1, fastf_t *yp1, fastf_t *xp2, fast
* The RPP has faces parallel to the coordinate planes and is defined
* by a minimum point and a maximum point.
*
* Despite its historical name, this function clips the finite line segment
* from a to b.
* FIXME: the function name implies this takes a point,dir for a,b but
* it actually takes a line segment going from points a to b!
*
* Returns -
* 0 if the segment does not hit the RPP,
* !0 if the segment hits the RPP.
* 0 if ray does not hit RPP,
* !0 if ray hits RPP.
*
* Implicit Return -
* if !0 was returned, "a" and "b" have been clipped to the RPP.
+3 -6
View File
@@ -126,12 +126,9 @@ BG_EXPORT extern int bg_3d_coplanar_obr(point_t *center,
* \endverbatim
*
*
* @param[out] pnts eight non-NULL, caller-provided point_t storage
* locations for the oriented bounding box vertices
* @param[in] points_3d flat array containing 3 * pnt_cnt coordinate values
* @param[in] pnt_cnt number of points in points_3d
*
* @return 0 on success and -1 for invalid input or when no box can be fit
* @param[out] pnts eight points of oriented bounding box
* @param points_3d array of 3D points
* @param pnt_cnt number of points in pnts array
*/
BG_EXPORT extern int bg_3d_obb(point_t **pnts,
const fastf_t *points_3d,
+4 -4
View File
@@ -21,7 +21,7 @@
/*----------------------------------------------------------------------*/
/** @addtogroup bg_pca
*
* Principal Component Analysis
* Principle Component Analysis
*
* Calculates an XYZ coordinate system such that it aligns with the largest
* variations in the supplied data. Intuitively, it "aligns" with the
@@ -50,7 +50,7 @@
* point_t p;
* for (size_t i = 0; i < pntcnt; i++) {
* MAT4X3PNT(p, RT, pnts[i]);
* VMOVE(pnts[i], p);
* VMOVE(pnts[i], v);
* }
* @endcode
*/
@@ -69,7 +69,7 @@ __BEGIN_DECLS
/**
* @brief
* Perform a Principal Component Analysis on a set of points.
* Perform a Principle Component Analysis on a set of points.
*
* Outputs are a center point and XYZ vectors for the coordinate system.
*
@@ -88,7 +88,7 @@ BG_EXPORT extern int bg_pca(point_t *c, vect_t *xaxis, vect_t *yaxis, vect_t *za
__END_DECLS
#endif /* BG_PCA_H */
#endif /* BG_PLANE_H */
/** @} */
/*
* Local Variables:
+4 -7
View File
@@ -1171,10 +1171,9 @@ BG_EXPORT extern int bg_isect_planes(point_t pt,
/**
* @brief
* Given an origin and a normal, create a plane_t with a unit normal.
* The input point and normal are not modified. A zero-length normal fails.
* Given an origin and a normal, create a plane_t.
*/
BG_EXPORT extern int bg_plane_pt_nrml(plane_t *p, const point_t pt, const vect_t nrml);
BG_EXPORT extern int bg_plane_pt_nrml(plane_t *p, point_t pt, vect_t nrml);
/**
* @brief
@@ -1189,15 +1188,13 @@ BG_EXPORT extern int bg_fit_plane(point_t *c, vect_t *n, size_t npnts, point_t *
/**
* @brief
* Find the closest U,V point on the plane p to 3d point pt. The plane normal
* need not be unit length.
* Find the closest U,V point on the plane p to 3d point pt.
*/
BG_EXPORT extern int bg_plane_closest_pt(fastf_t *u, fastf_t *v, plane_t *p, point_t *pt);
/**
* @brief
* Return the 3D point on the plane at parametric coordinates u, v. The plane
* normal need not be unit length.
* Return the 3D point on the plane at parametric coordinates u, v.
*/
BG_EXPORT extern int bg_plane_pt_at(point_t *pt, plane_t *p, fastf_t u, fastf_t v);
+1 -2
View File
@@ -293,7 +293,6 @@ BG_EXPORT extern int bg_3d_polygon_area(fastf_t *area, size_t npts, const point_
* @param[in] npts Number of point_ts, stored in pts
* @param[in] pts all points of the polygon, sorted counter-clockwise.
* The array mustn't contain duplicated points or non-coplanar points.
* A zero-area polygon fails.
*
* @return 0 if calculation was successful
* @return 1 if calculation failed, e.g. because one in-parameter is a NULL-pointer
@@ -308,7 +307,7 @@ BG_EXPORT extern int bg_3d_polygon_centroid(point_t *cent, size_t npts, const po
*@param[in] npts Number of points, pts contains
*@param pts Array of point_ts, building a convex polygon. Duplicated points
*aren't allowed. The points in the array will be sorted counter-clockwise.
*@param[in] cmp Plane equation of the polygon. Its normal must be nonzero.
*@param[in] cmp Plane equation of the polygon
*
*@return 0 if calculation was successful
*@return 1 if calculation failed, e.g. because pts is a NULL-pointer
+8 -20
View File
@@ -39,9 +39,7 @@
__BEGIN_DECLS
/**
* Test for an intersection between an infinite line and an Axis-Aligned
* Bounding Box (AABB). ldir need not be unit length, but must be nonzero.
* aabb_extent supplies the positive half extents along the coordinate axes.
* Test for an intersection between a line and an Axis-Aligned Bounding Box (AABB).
*
* Returns 1 if they intersect, 0 otherwise.
*/
@@ -52,10 +50,7 @@ bg_sat_line_aabb(
);
/**
* Test for an intersection between an infinite line and an Oriented Bounding
* Box (OBB). ldir need not be unit length, but must be nonzero. Each OBB
* extent vector points from the center to a face center; nonzero vectors must
* be mutually perpendicular, and a zero vector represents a degenerate axis.
* Test for an intersection between a line and an Oriented Bounding Box (OBB).
*
* Returns 1 if they intersect, 0 otherwise.
*/
@@ -66,9 +61,7 @@ bg_sat_line_obb(
);
/**
* Test for an intersection between a triangle and an Axis-Aligned Bounding
* Box (AABB). extent supplies the positive half extents along the coordinate
* axes.
* Test for an intersection between a triangle and an Axis-Aligned Bounding Box (AABB).
*
* Returns 1 if they intersect, 0 otherwise.
*/
@@ -79,10 +72,7 @@ bg_sat_tri_aabb(
);
/**
* Test for an intersection between a triangle and an Oriented Bounding Box
* (OBB). Each extent vector points from the center to a face center; nonzero
* vectors must be mutually perpendicular, and a zero vector represents a
* degenerate axis.
* Test for an intersection between a triangle and an Oriented Bounding Box (OBB).
*
* Returns 1 if they intersect, 0 otherwise.
*/
@@ -95,9 +85,8 @@ bg_sat_tri_obb(
/**
* Test for an intersection between an Axis-Aligned Bounding Box (AABB) and an
* Oriented Bounding Box (OBB). The latter is defined by a center point and
* three extent vectors from the center to the centers of the various faces.
* Nonzero extent vectors must be mutually perpendicular; a zero vector
* represents a degenerate axis.
* three perpendicular vectors from the center to the centers of the various
* faces.
*
* Returns 1 if they intersect, 0 otherwise.
*/
@@ -109,9 +98,8 @@ bg_sat_aabb_obb(
/**
* Test for an intersection between two Oriented Bounding Boxes (OBBs). The
* boxes are defined by a center point and three extent vectors from the center
* to the centers of the various faces. Nonzero extent vectors must be
* mutually perpendicular; a zero vector represents a degenerate axis.
* boxes are defined by a center point and three perpendicular vectors from the
* center to the centers of the various faces.
*
* Returns 1 if they intersect, 0 otherwise.
*/
+5 -127
View File
@@ -67,7 +67,6 @@
#define BG_SPSR_H
#include "common.h"
#include <stdint.h>
#include "vmath.h"
#include "bg/defines.h"
@@ -110,7 +109,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 8
#define BG_3D_SPSR_DEFAULT_DEPTH 11
#define BG_3D_SPSR_DEFAULT_KERNELDEPTH 0
#define BG_3D_SPSR_DEFAULT_ITERATIONS 0
#define BG_3D_SPSR_DEFAULT_FULL_DEPTH 0
@@ -118,13 +117,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.5
#define BG_3D_SPSR_DEFAULT_SCALE 1.1
#define BG_3D_SPSR_DEFAULT_SAMPLES_PER_NODE 1.1
#define BG_3D_SPSR_DEFAULT_SCALE 1.0
#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 2.0
#define BG_3D_SPSR_DEFAULT_POINT_WEIGHT 8.0 /* DefaultPointWeightMultiplier * Degree */
#define BG_3D_SPSR_DEFAULT_NONMANIFOLD 0
#define BG_3D_SPSR_DEFAULT_LINEARFIT 0
#define BG_3D_SPSR_DEFAULT_EXACT 1
@@ -168,131 +167,10 @@ struct bg_3d_spsr_opts {
* @return 0 if successful, else error
*
*/
DEPRECATED BG_EXPORT int bg_3d_spsr(int **faces, int *num_faces, point_t **vertices, int *num_vertices,
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 */
+1 -1
View File
@@ -52,7 +52,7 @@ BG_EXPORT extern double bg_tri_closest_pt(
__END_DECLS
#endif /* BG_TRI_PT_H */
#endif /* BG_TRI_TRI_H */
/** @} */
/*
* Local Variables:
+1 -1
View File
@@ -54,7 +54,7 @@ BG_EXPORT extern int bg_isect_tri_line(point_t *isect,
* If isect is non-NULL, it is written only when an intersection is found.
*/
BG_EXPORT extern int bg_isect_tri_ray(const point_t orig,
const vect_t dir,
const point_t dir,
const point_t vert0,
const point_t vert1,
const point_t vert2,
+1 -5
View File
@@ -87,11 +87,7 @@ BG_EXPORT extern int bg_trimesh_oriented(int vcnt, int fcnt, fastf_t *v, int *f)
/**
* Check if a mesh is topologically solid. Returns 1 if the mesh is NOT SOLID
* and 0 if the mesh is SOLID. A SOLID (0) outcome indicates the mesh satisfies
* all three criteria: Closed, Manifold, Oriented. If @p bedges is non-NULL,
* it is set to NULL when no edge diagnostics are present, or to a bu_calloc()
* allocated array of vertex-index pairs for unmatched, misoriented, and excess
* edges. The caller must bu_free() a non-NULL result. Use
* bg_trimesh_solid2() when structured diagnostics and their counts are needed.
* all three criteria: Closed, Manifold, Oriented
*/
BG_EXPORT extern int bg_trimesh_solid(int vcnt, int fcnt, fastf_t *v, int *f, int **bedges);
+12 -15
View File
@@ -214,8 +214,8 @@ BN_EXPORT extern void bn_mat_ae(mat_t m,
double elev);
/**
* Find the azimuth and elevation angles, in degrees, that correspond
* to the direction (not including twist) given by a direction vector.
* Find the azimuth and elevation angles that correspond to the
* direction (not including twist) given by a direction vector.
*/
BN_EXPORT extern void bn_ae_vec(fastf_t *azp,
fastf_t *elp,
@@ -236,16 +236,14 @@ BN_EXPORT extern void bn_aet_vec(fastf_t *az,
fastf_t accuracy);
/**
* Find a unit vector from the origin given azimuth and elevation in
* radians.
* Find a unit vector from the origin given azimuth and elevation.
*/
BN_EXPORT extern void bn_vec_ae(vect_t vec,
fastf_t az,
fastf_t el);
/**
* Find a vector from the origin given azimuth and elevation in radians,
* and distance.
* Find a vector from the origin given azimuth, elevation, and distance.
*/
BN_EXPORT extern void bn_vec_aed(vect_t vec,
fastf_t az,
@@ -312,10 +310,8 @@ BN_EXPORT extern void bn_vec_perp(vect_t new_vec,
* space by the angle between the two. There are many candidate
* matrices.
*
* The input 'from' and 'to' vectors need not be unit length. They are
* normalized internally. Near-parallel inputs are handled using the
* angular tolerance in 'tol'. If either input cannot be normalized,
* the result is the identity matrix.
* The input 'from' and 'to' vectors need not be unit length.
* MAT4X3VEC(to, m, from) is the identity that is created.
*
*/
BN_EXPORT extern void bn_mat_fromto(mat_t m,
@@ -348,11 +344,12 @@ BN_EXPORT extern void bn_mat_zrot(mat_t m,
double cosz);
/**
* Given a nonzero direction vector D, produce a matrix which
* rotates D onto the -Z axis. D is normalized internally. Z-axis
* directions use a deterministic roll, and a zero direction produces
* the identity matrix. This matrix will be suitable for use as a
* model2view matrix.
* Given a direction vector D of unit length, product a matrix which
* rotates that vector D onto the -Z axis. This matrix will be
* suitable for use as a "model2view" matrix.
*
* XXX This routine will fail if the vector is already more or less
* aligned with the Z axis.
*
* This is done in several steps.
*
-31
View File
@@ -37,42 +37,11 @@ __BEGIN_DECLS
typedef struct bn_multipoly {
uint32_t magic;
/**< Number of coefficients in the first polynomial variable. */
int dgrs;
/**< Number of coefficients in the second polynomial variable. */
int dgrt;
/**< Coefficient of S^s * T^t is cf[s][t]. */
double **cf;
} bn_multipoly_t;
/**
* Allocate a zero-valued bivariate polynomial with the specified
* coefficient counts. Both counts must be positive.
*/
BN_EXPORT extern struct bn_multipoly *bn_multipoly_new(int dgrs, int dgrt);
/** Release a bivariate polynomial allocated by bn_multipoly_new(). */
BN_EXPORT extern void bn_multipoly_free(struct bn_multipoly *poly);
/** Grow a polynomial to at least the specified coefficient counts. */
BN_EXPORT extern struct bn_multipoly *bn_multipoly_grow(struct bn_multipoly *poly,
int dgrs,
int dgrt);
/** Set the coefficient of S^s * T^t, growing the polynomial as needed. */
BN_EXPORT extern struct bn_multipoly *bn_multipoly_set(struct bn_multipoly *poly,
int s,
int t,
double value);
/** Return the sum of two bivariate polynomials. */
BN_EXPORT extern struct bn_multipoly *bn_multipoly_add(const struct bn_multipoly *p1,
const struct bn_multipoly *p2);
/** Return the product of two bivariate polynomials. */
BN_EXPORT extern struct bn_multipoly *bn_multipoly_mul(const struct bn_multipoly *p1,
const struct bn_multipoly *p2);
__END_DECLS
#endif /* BN_MULTIPOLY_H */
+4 -20
View File
@@ -45,10 +45,7 @@ __BEGIN_DECLS
/**
* Polynomial data type
*
* Coefficients are stored from the highest to the lowest degree:
* `cf[0]` is the coefficient of `X^dgr` and `cf[dgr]` is the
* constant coefficient.
* bn_poly->cf[n] corresponds to X^n
*/
typedef struct bn_poly {
uint32_t magic;
@@ -66,9 +63,6 @@ typedef struct bn_poly {
*
* @brief
* multiply two polynomials
*
* The output may alias either input. If the result exceeds
* BN_MAX_POLY_DEGREE, returns BN_POLY_NULL without modifying product.
*/
BN_EXPORT extern struct bn_poly *bn_poly_mul(struct bn_poly *product,
const struct bn_poly *m1,
@@ -87,8 +81,6 @@ BN_EXPORT extern struct bn_poly *bn_poly_scale(struct bn_poly *eqn,
* bn_poly_add
* @brief
* add two polynomials
*
* The output may alias either input.
*/
BN_EXPORT extern struct bn_poly *bn_poly_add(struct bn_poly *sum,
const struct bn_poly *poly1,
@@ -98,8 +90,6 @@ BN_EXPORT extern struct bn_poly *bn_poly_add(struct bn_poly *sum,
* bn_poly_sub
* @brief
* subtract two polynomials
*
* The output may alias either input.
*/
BN_EXPORT extern struct bn_poly *bn_poly_sub(struct bn_poly *diff,
const struct bn_poly *poly1,
@@ -108,10 +98,7 @@ BN_EXPORT extern struct bn_poly *bn_poly_sub(struct bn_poly *diff,
/**
* @brief
* Divides any polynomial into any other polynomial using synthetic
* division. Both polynomials must have real coefficients and the
* divisor's leading coefficient must be nonzero. The quotient and
* remainder outputs must be distinct; the quotient may alias the
* dividend. All other output/input pairs must be distinct.
* division. Both polynomials must have real coefficients.
*/
BN_EXPORT extern void bn_poly_synthetic_division(struct bn_poly *quo,
struct bn_poly *rem,
@@ -121,8 +108,7 @@ BN_EXPORT extern void bn_poly_synthetic_division(struct bn_poly *quo,
/**
*@brief
* Uses the quadratic formula to find the roots (in `complex' form) of
* any quadratic equation with real coefficients. A nonzero leading
* coefficient need not be one.
* any quadratic equation with real coefficients.
*
* @return 1 for success
* @return 0 for fail.
@@ -158,7 +144,6 @@ BN_EXPORT extern int bn_poly_quadratic_roots(struct bn_complex roots[],
* If D < 0, there will be three unequal real roots.
*
* Returns 1 for success, 0 for fail.
* A nonzero leading coefficient need not be one.
*/
BN_EXPORT extern int bn_poly_cubic_roots(struct bn_complex roots[],
const struct bn_poly *eqn);
@@ -166,8 +151,7 @@ BN_EXPORT extern int bn_poly_cubic_roots(struct bn_complex roots[],
/**
*@brief
* Uses the quartic formula to find the roots (in `complex' form)
* of any quartic equation with real coefficients. A nonzero leading
* coefficient need not be one.
* of any quartic equation with real coefficients.
*
* @return 1 for success
* @return 0 for fail.
+3 -4
View File
@@ -100,8 +100,8 @@ BN_EXPORT extern void quat_double(quat_t qout,
/**
*@brief Gives the bisector of quaternions q1 and q2.
*
* Inputs must be unit quaternions. Antipodal inputs use a valid
* great-circle midpoint.
* (Could be done with quat_slerp and factor 0.5)
* [I believe they must be unit quaternions this to work]
*/
BN_EXPORT extern void quat_bisect(quat_t qout,
const quat_t q1,
@@ -161,8 +161,7 @@ BN_EXPORT extern void quat_exp(quat_t out,
/**
*@brief
* Take the natural logarithm of a unit quaternion. The identity
* quaternion maps to the zero quaternion.
* Take the natural logarithm of a unit quaternion.
* Code by Ken Shoemake.
*/
BN_EXPORT extern void quat_log(quat_t out,
+9 -16
View File
@@ -205,30 +205,23 @@ BU_EXPORT extern int bu_sscanf(const char *src, const char *fmt, ...) _BU_ATTR_S
/** Routines for scanning certain kinds of data. */
/**
* Scans a sequence of fastf_t numbers from a string or stdin.
* Scans a sequence of fastf_t numbers from a string or stdin
*
* Scanning fastf_t numbers with bu_sscanf() is difficult, because
* doing so requires scanning to some intermediate type like double
* and then assigning to the fastf_t variable to convert the value to
* whatever type fastf_t really is. This function makes it possible
* to scan a series of fastf_t numbers easily, by doing the required
* conversion internally. Delimiters form a non-empty character set:
* one or more delimiter characters must separate values, and repeated
* delimiters are skipped. Empty scan fields are therefore not
* supported (e.g., "0.0,,0.0,1.0" scans as 3 fields, not 4 with the
* second skipped). A trailing delimiter is not consumed after the
* final requested value.
* to scan a series of fastf_t numbers separated by some character(s)
* easily, by doing the required conversion internally to the
* functions. As series of delimiter characters will be skipped,
* empty scan fields are not supported (e.g., "0.0,,0.0,1.0" will scan
* as 3 fields, not 4 with the 2nd skipped).
*
* @param[out] c Optional destination for the number of input characters
* consumed. It is set to zero before scanning and remains zero for
* invalid arguments or when no value is scanned.
* @param[out] c Returns number of characters scanned by the function
* @param[in] src A source string to scan from, or NULL to read from stdin
* @param[in] delim Non-empty set of delimiter characters to skip between
* scan values
* @param[in] delim Any delimiter character(s) to skip between scan values
* @param[in] n Number of fastf_t values to scan from the src input string
* @param[out] ... Exactly n fastf_t pointers for storing scanned values;
* individual pointers may be NULL
* @return Number of values scanned
* @param[out] ... Pointers to fastf_t for storing scanned values (optional)
*
*/
BU_EXPORT extern int bu_scan_fastf_t(int *c, const char *src, const char *delim, size_t n, ...);
+1 -14
View File
@@ -103,23 +103,10 @@ 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, subject to any process-local limit set by
* bu_avail_cpus_set().
* be available to this process now.
*/
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.
+1 -1
View File
@@ -1 +1 @@
46
45
+2 -2
View File
@@ -84,7 +84,7 @@ __BEGIN_DECLS
#define RT_DEBUG_HF 0x04000000 /**< @brief 27 Height Field solids */
#define RT_DEBUG_MESHING 0x08000000 /**< @brief 28 Print meshing/triangulation details */
#define RT_DEBUG_RTCMP 0x10000000 /**< @brief 29 Set up rtcmp capture; recording uses a runtime toggle. If LIBRT_RTCMP_PRIMITIVES=1 includes primitive segments. */
#define RT_DEBUG_UNUSED_3 0x10000000 /**< @brief 29 Unassigned */
#define RT_DEBUG_UNUSED_4 0x20000000 /**< @brief 30 Unassigned */
/* Options which will cause the library to write binary debugging output */
@@ -96,7 +96,7 @@ __BEGIN_DECLS
"\040PL_BOX" \
"\037PL_SOLIDS" \
"\036UNUSED_4" \
"\035RTCMP" \
"\035UNUSED_3" \
"\034UNUSED_2" \
"\033HF" \
"\032EBM" \
+6 -23
View File
@@ -465,17 +465,6 @@ rt_get_solid_keypoint(struct rt_edit *s, point_t *pt, const char **strp, fastf_t
RT_EXPORT extern void
rt_edit_set_edflag(struct rt_edit *s, int edflag);
/**
* Set a parameter-edit translation target expressed in model base units.
*
* rt_edit's parameter-edit handlers consume e_para in database local units,
* while keypoints and edit axes are maintained in model base units. This
* helper performs the boundary conversion for callers that have computed a
* target from a keypoint, axis, or view transform.
*/
RT_EXPORT extern void
rt_edit_set_translation_target(struct rt_edit *s, const point_t target);
RT_EXPORT extern int
rt_edit_knob_cmd_process(
struct rt_edit *s,
@@ -510,10 +499,8 @@ rt_knob_edit_sca(
int matrix_edit);
/* Equivalent to sedit - run editing logic after input data is set in
* rt_edit container. Transient input flags are consumed whether the edit
* succeeds or fails; parameter values remain available to the caller.
* Returns BRLCAD_ERROR if the primitive edit fails. */
RT_EXPORT extern int
* rt_edit container */
RT_EXPORT extern void
rt_edit_process(struct rt_edit *s);
/**
@@ -534,7 +521,7 @@ rt_edit_snap_point(point2d_t pt, const struct rt_edit *s);
* restored later with rt_edit_revert().
*
* The snapshot is stored inside the rt_edit struct. Calling this
* function again replaces the previous snapshot only on success.
* function again overwrites any previous snapshot (single-level undo).
*
* @return BRLCAD_OK on success, BRLCAD_ERROR if the export failed.
*/
@@ -545,8 +532,8 @@ rt_edit_checkpoint(struct rt_edit *s);
* Restore primitive parameters from the snapshot saved by
* rt_edit_checkpoint().
*
* The snapshot remains available for repeated restores. If none has
* been saved, this function logs a message and returns BRLCAD_ERROR.
* If no snapshot has been saved (or the last snapshot was already
* consumed) this function logs a message and returns BRLCAD_ERROR.
*
* @return BRLCAD_OK on success, BRLCAD_ERROR otherwise.
*/
@@ -710,11 +697,7 @@ enum rt_constraint_edit_op_kind {
RT_CONSTRAINT_EDIT_OP_SET_BEND,
RT_CONSTRAINT_EDIT_OP_SCALE_OD,
RT_CONSTRAINT_EDIT_OP_SCALE_ID,
RT_CONSTRAINT_EDIT_OP_SCALE_BEND,
/* Set every point when an all-zero dimension has no scale reference. */
RT_CONSTRAINT_EDIT_OP_SET_ALL_OD,
RT_CONSTRAINT_EDIT_OP_SET_ALL_ID,
RT_CONSTRAINT_EDIT_OP_SET_ALL_BEND
RT_CONSTRAINT_EDIT_OP_SCALE_BEND
};
struct rt_constraint_edit_param_ref {
-46
View File
@@ -227,32 +227,6 @@ struct rt_crofton_params {
double time_ms; /**< wall-clock time budget (ms); 0 = disabled */
};
/** One solid partition observed by a Crofton ray. */
struct rt_crofton_segment {
point_t in_point;
vect_t in_normal;
point_t out_point;
vect_t out_normal;
fastf_t thickness;
size_t ray_id;
};
/**
* Structured samples and estimates produced by rt_crofton_collect().
* Release @p segments with rt_crofton_result_free().
*/
struct rt_crofton_result {
struct rt_crofton_segment *segments;
size_t segment_count;
size_t ray_count;
size_t crossing_count;
double surface_area;
double volume;
};
/** Initialize an rt_crofton_result before first use. */
#define RT_CROFTON_RESULT_INIT { NULL, 0, 0, 0, 0.0, 0.0 }
/**
* Run the Cauchy-Crofton ray-sampling estimator on an already-prepared
@@ -363,26 +337,6 @@ struct rt_crofton_result {
*/
RT_EXPORT extern int rt_crofton_shoot(double *out_surf_area, double *out_volume, point_t *out_aabb_min, point_t *out_aabb_max, point_t out_obb[8], point_t **out_points, size_t *out_point_count, struct rt_i *rtip, const struct rt_crofton_params *params, const fastf_t *bbox_min, const fastf_t *bbox_max);
/**
* Run Crofton sampling while preserving oriented entry/exit pairs.
*
* @p result must be initialized with RT_CROFTON_RESULT_INIT. On success it
* owns the returned segment array. @p rtip must already be prepared.
* @p ray_offset skips that many samples in the deterministic sequence and
* labels the returned rays starting at that value. This lets successive
* calls use disjoint validation streams.
*/
RT_EXPORT extern int rt_crofton_collect(struct rt_crofton_result *result,
struct rt_i *rtip,
const struct rt_crofton_params *params,
size_t ray_offset,
const fastf_t *bbox_min,
const fastf_t *bbox_max);
/** Release storage owned by a structured Crofton result. */
RT_EXPORT extern void rt_crofton_result_free(
struct rt_crofton_result *result);
/**
* Cauchy-Crofton surface-area and/or volume estimator for a primitive.
+3
View File
@@ -55,11 +55,14 @@ struct clt_tri_specific {
};
#endif
#define RT_BOT_SURFNO_INTERIOR (-1)
/* Shared between bot and ars at the moment */
struct bot_specific {
unsigned char bot_mode;
unsigned char bot_orientation;
unsigned char bot_flags;
unsigned char bot_parity_shot;
size_t bot_ntri;
fastf_t *bot_thickness;
struct bu_bitv *bot_facemode;
-1
View File
@@ -41,7 +41,6 @@ struct rt_nmg_edit {
struct loopuse *lu_copy; /* copy of loop to be extruded */
plane_t lu_pl; /* plane equation for loop to be extruded */
struct shell *es_s; /* Shell where extrusion is to end up */
int es_s_from_extrusion; /* Replace this edit's prior preview */
point_t lu_keypoint; /* keypoint of lu_copy for extrusion */
struct vertex *es_v; /* Currently selected NMG vertex (ECMD_NMG_VPICK) */
struct faceuse *es_fu; /* Currently selected NMG faceuse (ECMD_NMG_FPICK) */
-12
View File
@@ -55,18 +55,6 @@
__BEGIN_DECLS
#define RT_RTCMP_CAPTURE_RUNTIME_TOGGLE 1
/** Set RT_DEBUG_RTCMP recording: nonzero enables, zero disables.
* Enabling requires the debug flag and a usable output writer.
* Return 0 on success and -1 on failure. Disabling succeeds even
* before setup.
*/
RT_EXPORT extern int rt_rtcmp_capture_set_enabled(int enabled);
/** Wait until pending RT_DEBUG_RTCMP records have reached the output file. */
RT_EXPORT extern int rt_rtcmp_capture_flush(void);
/**
* @brief
* Shoot a ray
+38 -62
View File
@@ -30,7 +30,6 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
include(CMakeParseArguments)
include("${CMAKE_CURRENT_LIST_DIR}/BRLCAD_Repository.cmake")
# Try to locate a usable git executable. On Windows, Visual Studio may
# provide git in non-standard locations; try common Program Files and
@@ -89,15 +88,6 @@ function(brlcad_bext_init BEXT_SHA1)
set(BRLCAD_EXT_DIR "${BRLCAD_EXT_DIR_ENV}")
endif(BRLCAD_EXT_DIR_ENV AND NOT DEFINED BRLCAD_EXT_DIR)
# Resolve paths before probing them so later subconfigures use the same
# directories even when launched from a different working directory.
foreach(_brlcad_ext_path_var BRLCAD_EXT_DIR BRLCAD_EXT_SOURCE_DIR)
if(DEFINED ${_brlcad_ext_path_var} AND NOT "${${_brlcad_ext_path_var}}" STREQUAL "")
get_filename_component(${_brlcad_ext_path_var} "${${_brlcad_ext_path_var}}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}")
set(${_brlcad_ext_path_var} "${${_brlcad_ext_path_var}}" PARENT_SCOPE)
endif()
endforeach()
set(_brlcad_ext_dir_specified FALSE)
set(_brlcad_ext_dir_managed FALSE)
@@ -158,14 +148,14 @@ function(brlcad_bext_init BEXT_SHA1)
elseif(NOT _brlcad_ext_dir_specified)
message(
WARNING
"External dependencies will be acquired from ${_BRLCAD_REPOSITORY_ROOT}, configured, and built automatically as-needed. Set BRLCAD_EXT_DIR to specify instead."
"External dependencies will be downloaded, configured, and built automatically as-needed. Set BRLCAD_EXT_DIR to specify instead."
)
endif()
message(
STATUS
"BRLCAD_EXT_DIR specifies prebuilt external dependencies directory\n"
"containing 'install' and 'noinstall' folders, outputs from installing\n"
"${_BRLCAD_BEXT_REPOSITORY}.\n"
"https://github.com/BRL-CAD/bext.\n"
)
endif(NOT BRLCAD_EXT_NOINSTALL_DIR OR NOT BRLCAD_EXT_INSTALL_DIR)
@@ -221,24 +211,28 @@ function(brlcad_rel_version BRLCAD_REL)
set(${BRLCAD_REL} "rel-${VMAJOR}-${VMINOR}-${VPATCH}" PARENT_SCOPE)
endfunction()
# Branch/tag queries are advisory; an unavailable source cannot validate them.
# Look up branch or tag SHA1 hashes from primary github.com repo
function(remote_sha1 SHA1_VAR BRANCH)
set(${SHA1_VAR} "" PARENT_SCOPE)
if(NOT GIT_EXEC)
set(${SHA1_VAR} "" PARENT_SCOPE)
return()
endif()
brlcad_repository_git_command(git_command)
# This needs a working internet connection to succeed (and GitHub
# must be up and working as well.)
execute_process(
COMMAND "${CMAKE_COMMAND}" -E env GIT_TERMINAL_PROMPT=0 --
${git_command} -c credential.helper= -c credential.interactive=false
ls-remote "${_BRLCAD_BEXT_REPOSITORY}" "${BRANCH}"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
RESULT_VARIABLE git_result
OUTPUT_VARIABLE remote_refs
ERROR_QUIET
)
if(git_result STREQUAL "0" AND remote_refs MATCHES "^([0-9a-f]+)[ \t]")
set(${SHA1_VAR} "${CMAKE_MATCH_1}" PARENT_SCOPE)
COMMAND ${GIT_EXEC} ls-remote https://github.com/BRL-CAD/bext.git ${BRANCH}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
RESULT_VARIABLE LS_REMOTE_STATUS
OUTPUT_VARIABLE LS_REMOTE_STR
ERROR_VARIABLE LS_REMOTE_STR
)
if(NOT LS_REMOTE_STATUS AND LS_REMOTE_STR)
string(REPLACE " " ";" LS_REMOTE_STR "${LS_REMOTE_STR}")
string(REPLACE "\t" ";" LS_REMOTE_STR "${LS_REMOTE_STR}")
list(GET LS_REMOTE_STR 0 REMOTE_SHA1)
set(${SHA1_VAR} "${REMOTE_SHA1}" PARENT_SCOPE)
else()
set(${SHA1_VAR} "" PARENT_SCOPE)
endif()
endfunction()
@@ -373,29 +367,24 @@ function(setup_bext_dir)
# no pre-existing sources, so we're cloning into the build dir
set(BRLCAD_EXT_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/bext")
brlcad_repository_git_command(git_command)
message(STATUS "Cloning bext from ${_BRLCAD_BEXT_REPOSITORY}")
execute_process(
COMMAND ${git_command} clone "${_BRLCAD_BEXT_REPOSITORY}" "${BRLCAD_EXT_SOURCE_DIR}"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
RESULT_VARIABLE git_result
)
if(NOT git_result STREQUAL "0")
message(FATAL_ERROR "Unable to clone bext from ${_BRLCAD_BEXT_REPOSITORY} (${git_result})")
endif()
execute_process(
COMMAND ${git_command} -c advice.detachedHead=false checkout "${BEXT_SHA1}"
WORKING_DIRECTORY "${BRLCAD_EXT_SOURCE_DIR}"
RESULT_VARIABLE git_result
)
if(NOT git_result STREQUAL "0")
message(FATAL_ERROR "Unable to check out pinned bext commit ${BEXT_SHA1} from ${_BRLCAD_BEXT_REPOSITORY} (${git_result}); ensure the repository contains this commit")
endif()
# Record the pin only after checkout succeeds.
# Write current SHA1 for subsequent processing
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/bext.sha1" "${BEXT_SHA1}")
distclean("${CMAKE_CURRENT_BINARY_DIR}/bext.sha1")
# Clone
message("BRL-CAD bext clone command: ${GIT_EXEC} clone https://github.com/BRL-CAD/bext.git")
execute_process(
COMMAND ${GIT_EXEC} clone https://github.com/BRL-CAD/bext.git
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
message("BRL-CAD bext checkout command: ${GIT_EXEC} -c advice.detachedHead=false checkout ${BEXT_SHA1}")
execute_process(
COMMAND ${GIT_EXEC} -c advice.detachedHead=false checkout ${BEXT_SHA1}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bext
COMMAND_ERROR_IS_FATAL ANY
)
# Configure process will need to clean up bext
if (BRLCAD_BEXT_CLEANUP)
set(BEXT_SRC_CLEANUP TRUE PARENT_SCOPE)
@@ -436,12 +425,6 @@ function(brlcad_ext_setup)
message(FATAL_ERROR "Invalid bext directory: ${BRLCAD_EXT_SOURCE_DIR}")
endif()
if(NOT _BRLCAD_REPOSITORY_ROOT STREQUAL "${_BRLCAD_REPOSITORY_DEFAULT_ROOT}/"
AND NOT EXISTS "${BRLCAD_EXT_SOURCE_DIR}/CMake/BextGit.cmake")
message(FATAL_ERROR
"bext sources in ${BRLCAD_EXT_SOURCE_DIR} do not support BEXT_REPOSITORY_ROOT; update bext before building with BRLCAD_REPOSITORY_ROOT=${BRLCAD_REPOSITORY_ROOT}")
endif()
# We do allow the user to specify a build dir to be reused, however
# this is unpredictable and may cause unexpected results depending
# on what is or isn't rebuilt - use with care.
@@ -480,18 +463,11 @@ function(brlcad_ext_setup)
endif(BRLCAD_ENABLE_APPLESEED)
# Define CMake arguments we'll need regardless of mode
set(bext_shallow_clone ON)
if(_BRLCAD_REPOSITORY_LOCAL)
set(bext_shallow_clone OFF)
endif()
set(CMAKE_CMD_ARGS
-S "${BRLCAD_EXT_SOURCE_DIR}" -B "${BRLCAD_EXT_BUILD_DIR}"
"-DBEXT_REPOSITORY_ROOT:STRING=${_BRLCAD_REPOSITORY_ROOT}"
"-DGIT_SHALLOW_CLONE:BOOL=${bext_shallow_clone}"
"-DENABLE_ALL=${BEXT_ENABLE_ALL}" "-DUSE_GDAL=${BEXT_USE_GDAL}"
"-DUSE_QT=${BEXT_USE_QT}" "-DUSE_TCL=${BEXT_USE_TCL}"
"-DUSE_APPLESEED=${BEXT_USE_APPLESEED}" "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
"-DCMAKE_INSTALL_PREFIX=${BRLCAD_EXT_INSTALL_DIR}"
-S ${BRLCAD_EXT_SOURCE_DIR} -B ${BRLCAD_EXT_BUILD_DIR} -DGIT_SHALLOW_CLONE=ON
-DENABLE_ALL=${BEXT_ENABLE_ALL} -DUSE_GDAL=${BEXT_USE_GDAL} -DUSE_QT=${BEXT_USE_QT} -DUSE_TCL=${BEXT_USE_TCL}
-DUSE_APPLESEED=${BEXT_USE_APPLESEED} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_INSTALL_PREFIX=${BRLCAD_EXT_INSTALL_DIR}
)
if(BRLCAD_X11_PROVIDER_RESOLVED STREQUAL "XMIN" AND BRLCAD_XMIN_ROOT)
list(APPEND CMAKE_CMD_ARGS
+16 -25
View File
@@ -1108,9 +1108,9 @@ function(brlcad_install_strclear_replace stamp_dir strclear source install_file
endif()
endfunction()
function(brlcad_install_binary_postprocess stamp_dir strclear source install_file install_type mode rpath_tool install_rpath build_lib_path rel_rpath verbose)
function(brlcad_install_binary_postprocess stamp_dir strclear source install_file install_type mode rpath_tool install_rpath build_lib_path rel_rpath use_selective_rpath verbose)
set(_brlcad_physical_file "$ENV{DESTDIR}${install_file}")
set(_brlcad_signature "binary-postprocess|${install_type}|${mode}|${rpath_tool}|${install_rpath}|${build_lib_path}|${rel_rpath}|${strclear}")
set(_brlcad_signature "binary-postprocess|${install_type}|${mode}|${rpath_tool}|${install_rpath}|${build_lib_path}|${rel_rpath}|${use_selective_rpath}|${strclear}")
_brlcad_postprocess_needed(_brlcad_needed "${stamp_dir}" "${source}" "${_brlcad_physical_file}" "${_brlcad_signature}")
if(NOT _brlcad_needed)
return()
@@ -1121,41 +1121,32 @@ function(brlcad_install_binary_postprocess stamp_dir strclear source install_fil
message(FATAL_ERROR "Post-install copy did not create ${_brlcad_physical_file}")
endif()
set(_brlcad_result 0)
set(_brlcad_rpath_output)
set(_brlcad_rpath_error)
if("${mode}" STREQUAL "RPATH_TOOL")
# Install the exact project-defined search order. A selective update can
# leave an existing relative-only RPATH without the canonical install path.
set(_brlcad_selective_rpath_args)
if(use_selective_rpath)
set(_brlcad_selective_rpath_args --set-rpath-if-needed --set-rpath-if-needed-prepend --stale-rpath-prefix "${build_lib_path}")
endif()
execute_process(
COMMAND "${rpath_tool}" --set-rpath "${install_rpath}" "${_brlcad_physical_file}"
COMMAND "${rpath_tool}" --set-rpath "${install_rpath}" ${_brlcad_selective_rpath_args} "${_brlcad_physical_file}"
RESULT_VARIABLE _brlcad_result
OUTPUT_VARIABLE _brlcad_rpath_output
ERROR_VARIABLE _brlcad_rpath_error
)
elseif("${mode}" STREQUAL "APPLE")
execute_process(
COMMAND install_name_tool -delete_rpath "${build_lib_path}" "${_brlcad_physical_file}"
RESULT_VARIABLE _brlcad_result
OUTPUT_VARIABLE _brlcad_rpath_output
ERROR_VARIABLE _brlcad_rpath_error
OUTPUT_VARIABLE _brlcad_output
ERROR_VARIABLE _brlcad_error
)
if(_brlcad_result EQUAL 0)
execute_process(
COMMAND install_name_tool -add_rpath "${rel_rpath}" "${_brlcad_physical_file}"
RESULT_VARIABLE _brlcad_result
OUTPUT_VARIABLE _brlcad_rpath_output
ERROR_VARIABLE _brlcad_rpath_error
)
endif()
endif()
if(NOT _brlcad_result EQUAL 0)
message(
WARNING
"Post-install RPATH update failed for ${_brlcad_physical_file}\n"
"stdout: ${_brlcad_rpath_output}\n"
"stderr: ${_brlcad_rpath_error}"
)
message(WARNING "Post-install RPATH update failed for ${_brlcad_physical_file}")
return()
endif()
@@ -1849,9 +1840,13 @@ endfunction()
elseif(APPLE)
set(_brlcad_install_postprocess_mode "APPLE")
endif(P_RPATH_EXECUTABLE)
set(_brlcad_install_use_selective_rpath FALSE)
if(P_RPATH_SUPPORTS_SET_IF_NEEDED_PREPEND)
set(_brlcad_install_use_selective_rpath TRUE)
endif(P_RPATH_SUPPORTS_SET_IF_NEEDED_PREPEND)
install(
CODE
"include(\"${BRLCAD_EXT_INSTALL_POSTPROCESS_SCRIPT}\")\nbrlcad_install_binary_postprocess(\"${BRLCAD_EXT_INSTALL_POSTPROCESS_STAMP_DIR}\" \"${STRCLEAR_EXECUTABLE}\" \"${CMAKE_BINARY_DIR}/${tf}\" \"\${CMAKE_INSTALL_PREFIX}/${tf}\" \"PROGRAM\" \"${_brlcad_install_postprocess_mode}\" \"${_brlcad_install_postprocess_tool}\" \"${_brlcad_install_postprocess_rpath}\" \"${CMAKE_BINARY_DIR}/${LIB_DIR}\" \"${REL_RPATH}\" \"${BRLCAD_VERBOSE}\")"
"include(\"${BRLCAD_EXT_INSTALL_POSTPROCESS_SCRIPT}\")\nbrlcad_install_binary_postprocess(\"${BRLCAD_EXT_INSTALL_POSTPROCESS_STAMP_DIR}\" \"${STRCLEAR_EXECUTABLE}\" \"${CMAKE_BINARY_DIR}/${tf}\" \"\${CMAKE_INSTALL_PREFIX}/${tf}\" \"PROGRAM\" \"${_brlcad_install_postprocess_mode}\" \"${_brlcad_install_postprocess_tool}\" \"${_brlcad_install_postprocess_rpath}\" \"${CMAKE_BINARY_DIR}/${LIB_DIR}\" \"${REL_RPATH}\" \"${_brlcad_install_use_selective_rpath}\" \"${BRLCAD_VERBOSE}\")"
)
continue()
endif("${tf}" IN_LIST ALL_BINARY_FILES)
@@ -2205,11 +2200,7 @@ macro(find_package_opencv)
set(OpenCV_DIR_TMP "${OpenCV_DIR}")
set(OpenCV_DIR "${CMAKE_BINARY_DIR}/${LIB_DIR}/cmake/opencv4")
set(OpenCV_ROOT ${CMAKE_BINARY_DIR})
find_package(OpenCV CONFIG COMPONENTS core imgproc imgcodecs highgui)
if(OpenCV_FOUND AND OpenCV_VERSION_MAJOR GREATER_EQUAL 5)
# OpenCV 5 moved boundingRect from imgproc to geometry.
find_package(OpenCV CONFIG COMPONENTS geometry)
endif()
find_package(OpenCV COMPONENTS core features2d imgproc highgui)
unset(OpenCV_ROOT)
# If no bundled copy, see what the system has
-108
View File
@@ -1,108 +0,0 @@
# 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.
#
# 3. The name of the author may not be used to endorse or promote
# products derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
include_guard(GLOBAL)
set(_BRLCAD_REPOSITORY_DEFAULT_ROOT "https://github.com/BRL-CAD")
set(BRLCAD_REPOSITORY_ROOT "${_BRLCAD_REPOSITORY_DEFAULT_ROOT}" CACHE STRING
"Repository URL or local mirror directory containing bext and its dependency repositories")
function(brlcad_repository_root out_root out_local)
set(root "${BRLCAD_REPOSITORY_ROOT}")
set(local FALSE)
if(root STREQUAL "")
message(FATAL_ERROR "BRLCAD_REPOSITORY_ROOT must be a repository URL or local mirror directory")
endif()
# Match bext's URL handling, but resolve relative paths against BRL-CAD.
# Drive paths must be checked before scp-style SSH locations.
if(root MATCHES "^file://")
set(local TRUE)
string(REPLACE "=" "%3D" root "${root}")
elseif(root MATCHES "^[A-Za-z]:[/\\\\]" OR
NOT (root MATCHES "^[A-Za-z][A-Za-z0-9+.-]*://" OR root MATCHES "^[^/]+:.+"))
set(local TRUE)
string(REPLACE "\\" "/" root "${root}")
if(WIN32 OR NOT root MATCHES "^[A-Za-z]:/")
get_filename_component(root "${root}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}")
endif()
# Escape literal percent signs first so native paths cannot introduce
# URL escapes. An equals sign also has meaning in Git's -c assignments.
string(REPLACE "%" "%25" root "${root}")
string(REPLACE " " "%20" root "${root}")
string(REPLACE "#" "%23" root "${root}")
string(REPLACE "?" "%3F" root "${root}")
string(REPLACE "=" "%3D" root "${root}")
if(root MATCHES "^[A-Za-z]:/")
set(root "file:///${root}")
elseif(root MATCHES "^//")
set(root "file:${root}")
else()
set(root "file://${root}")
endif()
endif()
if(NOT root MATCHES "^file:///*$")
string(REGEX REPLACE "/+$" "" root "${root}")
string(APPEND root "/")
else()
set(root "file:///")
endif()
set(${out_root} "${root}" PARENT_SCOPE)
set(${out_local} "${local}" PARENT_SCOPE)
endfunction()
function(brlcad_repository_git_command out_command)
set(git_command "${GIT_EXEC}")
if(WIN32)
list(APPEND git_command -c core.longpaths=true)
endif()
if(_BRLCAD_REPOSITORY_LOCAL)
list(APPEND git_command -c protocol.file.allow=always)
# This also constrains recursive Git processes and user URL rewrites.
list(PREPEND git_command "${CMAKE_COMMAND}" -E env GIT_ALLOW_PROTOCOL=file --)
endif()
set(${out_command} "${git_command}" PARENT_SCOPE)
endfunction()
brlcad_repository_root(_BRLCAD_REPOSITORY_ROOT _BRLCAD_REPOSITORY_LOCAL)
set(_BRLCAD_BEXT_REPOSITORY "${_BRLCAD_REPOSITORY_ROOT}bext.git")
message(STATUS "BRL-CAD repository root: ${_BRLCAD_REPOSITORY_ROOT}")
if(_BRLCAD_REPOSITORY_LOCAL)
message(STATUS "BRL-CAD Git acquisition: file transport only")
endif()
# Local Variables:
# tab-width: 8
# mode: cmake
# indent-tabs-mode: t
# End:
# ex: shiftwidth=2 tabstop=8
-1
View File
@@ -15,7 +15,6 @@ set(
BRLCAD_Options.cmake
BRLCAD_Probe_Validation.cmake
BRLCAD_Regress_Util.cmake
BRLCAD_Repository.cmake
BRLCAD_Summary.cmake
BRLCAD_Targets.cmake
BRLCAD_Test_Wrappers.cmake
+1 -31
View File
@@ -95,33 +95,6 @@ add_custom_target(
)
set_target_properties(distcheck-source_archives PROPERTIES FOLDER "BRL-CAD Distribution Checking")
# Dependency paths must survive CMake, build-tool, and shell parsing. Load
# their values from a cache file rather than embedding them in command text.
set(distcheck_dependency_cache "${CMAKE_CURRENT_BINARY_DIR}/CMakeTmp/distcheck_dependencies.cmake")
set(distcheck_dependency_vars BRLCAD_REPOSITORY_ROOT BRLCAD_EXT_DIR)
foreach(distcheck_optional_var BRLCAD_EXT_SOURCE_DIR BRLCAD_EXT_PARALLEL)
if(DEFINED ${distcheck_optional_var})
list(APPEND distcheck_dependency_vars ${distcheck_optional_var})
endif()
endforeach()
file(WRITE "${distcheck_dependency_cache}" "")
foreach(distcheck_var IN LISTS distcheck_dependency_vars)
set(distcheck_value "${${distcheck_var}}")
if(distcheck_var STREQUAL "BRLCAD_REPOSITORY_ROOT")
set(distcheck_value "${_BRLCAD_REPOSITORY_ROOT}")
endif()
set(distcheck_type STRING)
if(distcheck_var MATCHES "_DIR$")
set(distcheck_type PATH)
endif()
string(REPLACE "\\" "\\\\" distcheck_value "${distcheck_value}")
string(REPLACE "\"" "\\\"" distcheck_value "${distcheck_value}")
string(REPLACE "$" "\\$" distcheck_value "${distcheck_value}")
file(APPEND "${distcheck_dependency_cache}"
"set(${distcheck_var} \"${distcheck_value}\" CACHE ${distcheck_type} \"Inherited dependency setting\" FORCE)\n")
endforeach()
distclean("${distcheck_dependency_cache}")
# Utility function for defining individual distcheck targets
macro(
create_distcheck
@@ -147,10 +120,7 @@ macro(
# Need to set these locally so configure_file will pick them up...
set(TARGET_SUFFIX ${TARGET_SUFFIX})
# Keep machine-specific settings out of the options saved for CI above.
# The shared cache replaces each variant's unquoted output argument.
string(REPLACE "-DBRLCAD_EXT_DIR=${BRLCAD_EXT_DIR}" "" CMAKE_OPTS "${CMAKE_OPTS_IN}")
string(APPEND CMAKE_OPTS " -C \"${distcheck_dependency_cache}\"")
set(CMAKE_OPTS ${CMAKE_OPTS_IN})
# For configure_file, need to set these as variables not just input parameters
set(source_dir "${source_dir}")
-6
View File
@@ -1,9 +1,3 @@
brlcad (7.46.0-0) unstable; urgency=low
* update brlcad version
-- Cliff Yapp <cyapp@brlcad.org> Thu, 07 Oct 2026 20:24:00 +0000
brlcad (7.44.0-0) unstable; urgency=low
* update brlcad version
+1 -1
View File
@@ -11,7 +11,7 @@
\f0\i\b\fs72 \cf0 README
\fs36 \
\f1\i0\b0\fs28 This is BRL-CAD 7.46.0 for Mac OS X. Please send any bugs, comments, questions, or suggestions to the project website.\
\f1\i0\b0\fs28 This is BRL-CAD 7.44.0 for Mac OS X. Please send any bugs, comments, questions, or suggestions to the project website.\
\fs22 \
+1 -1
View File
@@ -4,7 +4,7 @@
\margl1440\margr1440\vieww8020\viewh6280\viewkind0
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
\f0\fs22 \cf0 This installer will guide you through the installation of BRL-CAD 7.46.0 for Mac OS X and Darwin.\
\f0\fs22 \cf0 This installer will guide you through the installation of BRL-CAD 7.44.0 for Mac OS X and Darwin.\
\
This distribution of BRL-CAD requires approximately 500 MB of available disk space and does not include source code.\
\
-14
View File
@@ -117,20 +117,6 @@ if(TARGET libged)
endif(TARGET libged)
if(TARGET librt AND FUZZER_SUPPORTED)
set(RT_EDIT_FUZZ_DIR "${CMAKE_SOURCE_DIR}/src/librt/tests/edit")
brlcad_addexec(
fuzz_rt_edit_test
"${RT_EDIT_FUZZ_DIR}/fuzz.cpp;${RT_EDIT_FUZZ_DIR}/ars.cpp;${RT_EDIT_FUZZ_DIR}/bot.cpp"
"libbu;librt;libnmg;libwdb;libbrep"
TEST
)
set_target_properties(fuzz_rt_edit_test PROPERTIES UNITY_BUILD OFF)
target_compile_options(fuzz_rt_edit_test PRIVATE ${FUZZER_EXE_COMPILE_OPTIONS})
target_link_options(fuzz_rt_edit_test PRIVATE ${FUZZER_LINK_OPTIONS})
BRLCAD_ADD_TEST(NAME fuzz_rt_edit_smoke COMMAND fuzz_rt_edit_test -runs=1 -max_total_time=5)
endif()
cmakefiles(
CMakeLists.txt
fuzz_ged.cpp
+17 -13
View File
@@ -79,9 +79,7 @@ endif(NOT EXISTS \"\${GCV_EXEC}\")\n"
# setup conversion tests
foreach(in_file ${INPUT_FILES})
get_filename_component(curr_in_basename ${in_file} NAME_WLE)
# Tests in the same directory may run concurrently, so their output paths
# must not overlap.
set(out_path "${CMAKE_CURRENT_BINARY_DIR}/conv-${TARGET_DIRECTION}-${curr_in_basename}")
set(out_path "${CMAKE_CURRENT_BINARY_DIR}/conv-${curr_in_basename}")
# need to split suffix so we can distinguish from different converters
if(${fmt}_EXPORTER)
@@ -122,7 +120,7 @@ endif(NOT EXISTS \"\${GCV_EXEC}\")\n"
)
endif(${fmt}_GCV_OPTIONS)
set(out_distclean ${out_distclean} "${out_path}-${out_fmt}${out_suffix}")
set(out_distclean ${out_distclean}} "${out_path}-${out_fmt}${out_suffix}")
set(expected_output_files "${expected_output_files} \"${out_path}-${out_fmt}${out_suffix}\"")
endforeach(out_fmt ${${fmt}_CONVERTERS})
@@ -164,8 +162,19 @@ execute_process(
file(APPEND \"\${LOGFILE}\" \"\${log_msg}\\n\")
unset(log_msg)
"
)
# check output files exist and are a reasonable size
set(OUTPUT_FILES ${OUTPUT_FILES} ${out_distclean})
unset(expected_output_files)
unset(out_distclean)
endforeach(in_file ${INPUT_FILES})
file(
APPEND
${cmake_in_path}
"
# check output files exists and are a reasonable size
foreach(out_file \${ofiles})
if(NOT EXISTS \"\${out_file}\")
file(APPEND \"\${LOGFILE}\" \"FAILURE: \${curr_result}, \${out_file} not found.\")
@@ -182,18 +191,13 @@ foreach(out_file \${ofiles})
file(APPEND \"\${LOGFILE}\" \"FAILURE: \${curr_result} file size is \${file_size} for \${out_file}\\n Expecting ${EXPECTED_FILE_SIZE} with delta ${FILE_SIZE_DELTA}\")
message(FATAL_ERROR \"[gcv] Failure while converting ${in_file}, see \${LOGFILE} for more info.\")
endif(\"\${size_delta}\" GREATER \"${FILE_SIZE_DELTA}\")
endif(NOT EXISTS \"\${out_file}\")
endif(NOT EXISTS \"\${out_file}\")\n
# cleanup file from build dir
file(REMOVE \"\${out_file}\")
endforeach(out_file \${ofiles})
"
)
set(OUTPUT_FILES ${OUTPUT_FILES} ${out_distclean})
unset(expected_output_files)
unset(out_distclean)
endforeach(in_file ${INPUT_FILES})
"
)
# assume the fmt is the gcv dependency unless specified
if(${fmt}_DEPS)
+1 -1
View File
@@ -2,7 +2,7 @@ if(TARGET gcv-assetimport)
gcv_regress_util("gltf" "tetra-gltf2.0.gltf;tetra-glb2.0.glb" DEPS "gcv;gcv-assetimport" GCV_OPTIONS "--input-format=assetimport")
gcv_regress_util("glb" "tetra.g" DEPS "gcv;gcv-assetimport" GCV_OPTIONS "--output-format=assetimport" EXPECTED_FILE_SIZE "1476" EXPORTER)
gcv_regress_util("gltf" "tetra.g" DEPS "gcv;gcv-assetimport" GCV_OPTIONS "--output-format=assetimport" EXPECTED_FILE_SIZE "3356" EXPORTER)
distclean("${CMAKE_CURRENT_BINARY_DIR}/conv-regress-g-gltf-tetra.bin")
distclean("${CMAKE_CURRENT_BINARY_DIR}/conv-tetra.bin")
endif(TARGET gcv-assetimport)
if(TARGET gcv-gltf)
+2 -2
View File
@@ -1,7 +1,7 @@
if(TARGET gcv-assetimport)
set(dep_str DEPS "gcv;gcv-obj;gcv-assetimport" CONVERTERS "assetimport")
distclean("${CMAKE_CURRENT_BINARY_DIR}/conv-regress-g-obj-tetra-assetimport.mtl") # hard to track companion files
distclean("${CMAKE_CURRENT_BINARY_DIR}/conv-tetra-assetimport.mtl") # hard to track companion files
else()
set(dep_str "")
endif(TARGET gcv-assetimport)
@@ -9,7 +9,7 @@ endif(TARGET gcv-assetimport)
gcv_regress_util("obj" "tetra.obj" ${dep_str})
gcv_regress_util("obj_unnamed_group" "tetra-unnamed-group.obj" DEPS "gcv;gcv-obj")
gcv_regress_util("obj" "tetra.g" ${dep_str} EXPORTER)
distclean("${CMAKE_CURRENT_BINARY_DIR}/conv-regress-g-obj-tetra.mtl")
distclean("${CMAKE_CURRENT_BINARY_DIR}/conv-tetra.mtl")
cmakefiles(
CMakeLists.txt
+16 -259
View File
@@ -68,13 +68,6 @@
* ("/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"
@@ -85,9 +78,7 @@
#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"
@@ -151,36 +142,6 @@ 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.
@@ -188,8 +149,23 @@ run_facetize_command(const char *gfile, int argc, const char **argv, int verbose
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};
return run_facetize_command(gfile, 3, av, verbose);
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;
}
/**
@@ -236,46 +212,6 @@ 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.
@@ -1184,183 +1120,6 @@ 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 */
/* ------------------------------------------------------------------ */
@@ -1392,8 +1151,6 @@ 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");
-1
View File
@@ -23,7 +23,6 @@ set(
gqa.mass_mix.am.out
gqa.mass_mix.aw.out
gqa.mass_mix.u0.out
gqa.missing_density.out
gqa.mged
gqa.overlaps.plot3
gqa.sphere_adaptive.out
-24
View File
@@ -67,11 +67,6 @@ run_capture ( ) {
return $ret
}
expect_failure ( ) {
"$@"
test $? -ne 0
}
extract_last_number ( ) {
label="$1"
file="$2"
@@ -106,19 +101,6 @@ assert_close ( ) {
fi
}
assert_contains ( ) {
expected="$1"
file="$2"
desc="$3"
if grep -F -q "$expected" "$file" ; then
log "PASS: $desc"
else
log "FAIL: $desc"
STATUS="`expr $STATUS + 1`"
fi
}
MGED="`ensearch mged`"
if test ! -f "$MGED" ; then
log "Unable to find mged, aborting"
@@ -195,10 +177,6 @@ adjust mass_air.r GIFTmater 2
g mass_air_only.g mass_air.r
g mass_air_mix.g mass_solid.r mass_air.r
in missing_density.s rpp 2 3 0 1 0 1
r missing_density.r u missing_density.s
adjust missing_density.r GIFTmater 123
in sphere.s sph 0 0 0 1
r sphere.r u sphere.s
adjust sphere.r GIFTmater 5
@@ -265,7 +243,6 @@ run_capture gqa.mass_mix.aw.out $GQABIN -u m,m^3,kg -g 250mm-50mm -Aw gqa.g mass
run_capture gqa.mass_mix.am.out $GQABIN -u m,m^3,kg -g 250mm-50mm -Am gqa.g mass_air_mix.g
run_capture gqa.mass_mix.u0.out $GQABIN -u m,m^3,kg -g 250mm-50mm -U 0 -Aw gqa.g mass_air_mix.g
run_capture gqa.sphere_adaptive.out $GQABIN -u m,m^3,kg -q -Av -g 0.05m gqa.g sphere.r
run_capture gqa.missing_density.out expect_failure $GQABIN -u m,m^3,kg -g 250mm-50mm -f density_table.txt -Aw gqa.g missing_density.r
MASS_AIR_AW="`extract_last_number 'Average total weight:' gqa.mass_air.aw.out`"
MASS_AIR_AM="`extract_last_number 'Average total weight:' gqa.mass_air.am.out`"
@@ -280,7 +257,6 @@ assert_close 1001 "$MASS_MIX_AW" 0.001 "mixed solid+air modeled weight with -Aw"
assert_close "$MASS_MIX_AW" "$MASS_MIX_AM" 0.001 "-Aw and -Am agree on mixed modeled air"
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"
assert_contains "Density 123 on region /missing_density.r is not defined in the density table." gqa.missing_density.out "undefined density produces an error"
if [ $STATUS = 0 ] ; then
-13
View File
@@ -68,7 +68,6 @@ set(
sca_view.mged
search_exec.mged
sed.mged
sed_reset.mged
set_perspective.mged
setview.mged
size.mged
@@ -101,7 +100,6 @@ if(TARGET mged)
continue()
endif(TNAME STREQUAL "")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${testfile}")
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/${testfile}" TEST_CONTENTS)
if(TNAME STREQUAL "search_exec")
string(CONFIGURE "${TEST_CONTENTS}" TEST_CONTENTS @ONLY)
@@ -128,12 +126,6 @@ if(TARGET mged)
elseif(TNAME STREQUAL "arb")
set(TEST_EXPECT "'arb' testing completed")
set(TEST_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/regress_${testfile}")
elseif(TNAME MATCHES "^(extrude|facedef|mirface|permute)$")
set(TEST_EXPECT "'${TNAME}' testing completed")
set(TEST_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/regress_${testfile}")
elseif(TNAME STREQUAL "edgedir")
set(TEST_EXPECT "'edarb edgedir' testing completed")
set(TEST_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/regress_${testfile}")
elseif(TNAME STREQUAL "killtree")
set(TEST_EXPECT "KILL Solid: killtree_dup.s")
set(TEST_REJECT "db_lookup(killtree_dup.s) failed")
@@ -146,10 +138,6 @@ if(TARGET mged)
elseif(TNAME STREQUAL "prim_edit")
set(TEST_EXPECT "Primitive parameter editing testing completed")
set(TEST_REJECT "FAIL:")
elseif(TNAME STREQUAL "sed_reset")
set(TEST_EXPECT "PASS: 'sed_reset' testing completed")
set(TEST_REJECT "FAIL:")
set(TEST_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/regress_${testfile}")
elseif(TNAME STREQUAL "overlap_plotting")
set(TEST_DB "${CMAKE_SOURCE_DIR}/src/libged/tests/ged_tests.g")
set(TEST_EXPECT "PASS overlap plotting")
@@ -199,7 +187,6 @@ distclean(${mged_outfiles})
cmakefiles(
CMakeLists.txt
csg_edit.mged
mged.tcl
regression_resources.tcl
search_exec_procs.tcl
-31
View File
@@ -119,35 +119,4 @@ if {![catch {get $invalid_inner}]} {
}
# ARBN uses one independent thickness per plane. Exercise unequal offsets so
# the test also verifies the plane ordering and inward normal convention.
set arbn_outer inside_arbn_outer.s
set arbn_inner inside_arbn_inner.s
set arbn_thicknesses {1 2 3 4 1.5 0.5}
in $arbn_outer arbn 6 \
1 0 0 10 -1 0 0 0 \
0 1 0 10 0 -1 0 0 \
0 0 1 10 0 0 -1 0
if {[catch {inside $arbn_outer $arbn_inner {*}$arbn_thicknesses} result]} {
error "inside failed for ARBN: $result"
}
set arbn_definition [get $arbn_inner]
set expected_offsets {9 -2 7 -4 8.5 -0.5}
for {set plane 0} {$plane < 6} {incr plane} {
set equation [lindex $arbn_definition [expr {4 + 2 * $plane}]]
set actual_offset [lindex $equation 3]
if {abs($actual_offset - [lindex $expected_offsets $plane]) > $plane_tolerance} {
error "ARBN inside plane $plane has offset $actual_offset: $arbn_definition"
}
}
facetize -n inside_arbn_inner_nmg.s $arbn_inner
set invalid_arbn_inner inside_arbn_invalid.s
if {![catch {inside $arbn_outer $invalid_arbn_inner {*}[lrepeat 6 6]}]} {
error "ARBN inside accepted thicknesses that collapse the solid"
}
if {![catch {get $invalid_arbn_inner}]} {
error "ARBN inside wrote an invalid collapsed solid"
}
puts "*** 'arb' testing completed ***"
-406
View File
@@ -962,140 +962,8 @@ foreach test [list \
csg_matrix_mouse_check {*}$test
}
puts "--- Repeated graphical object transforms ---"
proc csg_matrix_repeated_mouse_check {} {
global orig_gui transform csg_mouse_dm_pixels csg_mouse_view_size
global csg_drag_scale
set prim csg_matrix_repeat.s
set inner csg_matrix_repeat_inner.c
set outer csg_matrix_repeat_outer.c
set saved_orig_gui $orig_gui
set expected [csg_edit_identity_matrix]
set failed [catch {
in $prim sph 0 0 0 10
comb $inner u $prim
comb $outer u $inner
set orig_gui 0
for {set step 0} {$step < 16} {incr step} {
e $outer
center 0 0 0
size $csg_mouse_view_size
setview 0 0 0
dm size $csg_mouse_dm_pixels $csg_mouse_dm_pixels
_mged_press oill
_mged_ill -e -i 1 /$outer/$inner/$prim
_mged_matpick 2
if {[status state] ne "OBJ EDIT" || $transform ne "e"} {
error "step $step did not enter object editing"
}
set view_before [csg_edit_view_state]
if {$step % 2 == 0} {
press Rotate
dm con r z 100 100
set operation [csg_matrix_axis_rotation z 10]
set motion {dm motion 140 100}
} else {
press Scale
dm am s 100 100
set operation [csg_matrix_scale all $csg_drag_scale]
set motion {dm motion 164 100}
}
set motion_code [catch {uplevel #0 $motion} motion_result]
if {$motion_code != 0 && $motion_code != 2} {
error "step $step mouse motion failed: $motion_result"
}
dm idle
press accept
set expected [csg_matrix_multiply $operation $expected]
csg_edit_comb_leaf_matrix_at $inner $prim $expected
csg_edit_view_state_at $view_before
d $outer
}
} failure]
set orig_gui $saved_orig_gui
catch {press reject}
catch {Z}
foreach object [list $outer $inner $prim] {
catch {kill $object}
}
if {$failed} {
puts " FAIL: repeated object mouse rotations/scales ($failure)"
} else {
puts " PASS: repeated object mouse rotations/scales"
}
}
csg_matrix_repeated_mouse_check
puts "--- All ARB point, edge, and face states ---"
proc csg_edit_sed_point {attribute} {
set serialized [get_sed]
set index [lsearch -exact $serialized $attribute]
if {$index < 0 || $index + 1 >= [llength $serialized]} {
error "get_sed has no $attribute: $serialized"
}
return [lindex $serialized [expr {$index + 1}]]
}
proc csg_arb_repeated_mouse_check {} {
global orig_gui csg_bv_coord_half_range csg_mouse_dm_pixels
global csg_mouse_view_size
set source csg_lifecycle_arb5.s
set prim csg_arb_repeat.s
set saved_orig_gui $orig_gui
set expected {}
set failed [catch {
cp $source $prim
e $prim
center 0 0 0
size $csg_mouse_view_size
setview 0 0 0
dm size $csg_mouse_dm_pixels $csg_mouse_dm_pixels
set orig_gui 0
sed $prim
press {Move Edges}
press {Move Point 5}
set original_z [lindex [db get $source V5] 2]
for {set step 0} {$step < 64} {incr step} {
if {$step > 0 && $step % 8 == 0} {
press sedit
press {Move Edges}
press {Move Point 5}
}
set mouse_x [expr {150 + 2 * ($step % 5)}]
set mouse_y [expr {150 + 2 * (($step * 3) % 5)}]
M 1 $mouse_x $mouse_y
set expected [list \
[expr {$mouse_x / $csg_bv_coord_half_range * $csg_mouse_view_size / 2.0}] \
[expr {$mouse_y / $csg_bv_coord_half_range * $csg_mouse_view_size / 2.0}] \
$original_z]
set actual [csg_edit_sed_point V5]
if {[csg_edit_point_distance $actual $expected] > 0.000001} {
error "step $step expected V5 $expected, got $actual"
}
}
press accept
d $prim
if {[csg_edit_point_distance [db get $prim V5] $expected] > 0.000001} {
error "accepted V5 differs from the final mouse target"
}
foreach vertex {V1 V2 V3 V4} {
csg_edit_point_matches_source $source $vertex $prim
}
arb_standard_storage_check arb5 $prim
} failure]
set orig_gui $saved_orig_gui
catch {press reject}
catch {d $prim}
catch {kill $prim}
if {$failed} {
puts " FAIL: repeated ARB5 mouse movement ($failure)"
} else {
puts " PASS: repeated ARB5 mouse movement"
}
}
csg_arb_repeated_mouse_check
# Every ARB menu entry is checked independently. The variants do not share
# menu indices, so sampling one edge or face would miss precisely the class of
# integration failure this test is intended to catch. Move each selection a
@@ -1454,280 +1322,6 @@ csg_edit_sequence_check csg_lifecycle_dsp.s "Toggle Smooth" {
{press {Toggle Smooth}}
} [list csg_edit_scalar_at sm 1]
puts "--- Specialized mouse editing and menu inventory ---"
proc csg_edit_prepare_mouse_view {} {
global csg_dispatch_view_size
center 0 0 0
size $csg_dispatch_view_size
setview 0 0 0
}
array set csg_mouse_covered {}
proc csg_mouse_case {source menu_items label commands validator} {
global csg_mouse_covered
csg_edit_sequence_check $source $label $commands $validator
set type [lindex [db get $source] 0]
if {![info exists csg_mouse_covered($type)]} {
set csg_mouse_covered($type) {}
}
foreach item $menu_items {
if {[lsearch -exact $csg_mouse_covered($type) $item] < 0} {
lappend csg_mouse_covered($type) $item
}
}
}
proc csg_edit_menu_inventory_check {source ignored} {
global csg_mouse_covered
if {[catch {
Z
e $source
sed $source
set actual [mmenu_get 0]
set type [lindex [db get $source] 0]
set covered {}
if {[info exists csg_mouse_covered($type)]} {
set covered $csg_mouse_covered($type)
}
foreach item $actual {
set tested [expr {[lsearch -exact $covered $item] >= 0}]
set exempt [expr {[lsearch -exact $ignored $item] >= 0}]
if {$tested == $exempt} {
error "menu entry '$item' needs exactly one mouse-test or exclusion"
}
}
foreach item [concat $covered $ignored] {
if {[lsearch -exact $actual $item] < 0} {
error "recorded menu entry '$item' is absent from $actual"
}
}
press reject
d $source
} failure]} {
puts " FAIL: menu inventory $source ($failure)"
catch {press reject}
catch {Z}
} else {
puts " PASS: menu inventory $source"
}
}
csg_edit_prepare_mouse_view
set csg_special_mouse_target [list \
[expr {512.0 / $csg_bv_coord_half_range * $csg_dispatch_view_size / 2.0}] \
[expr {512.0 / $csg_bv_coord_half_range * $csg_dispatch_view_size / 2.0}] 0]
set csg_special_mouse_scale [expr {1.0 + 2047.0 / ($csg_bv_coord_half_range * 4.0)}]
set csg_special_mouse_shrink [expr {
1.0 / (1.0 + 1024.0 / ($csg_bv_coord_half_range * 4.0))}]
csg_mouse_case csg_lifecycle_pipe.s {{Move Point}} "mouse select and move point" {
{csg_edit_prepare_mouse_view}
{press {Select Point}}
{p 0 0 0}
{press {Move Point}}
{M 1 512 512}
} [list csg_edit_point_at V0 $csg_special_mouse_target]
in csg_mouse_pipe_topology.s pipe 4 \
0 0 0 1 2 3 \
0 0 30 1 2 3 \
30 40 80 1 2 3 \
80 80 100 1 2 3
csg_mouse_case csg_mouse_pipe_topology.s {{Select Point} {Move Point}} "mouse select point" {
{csg_edit_prepare_mouse_view}
{press {Select Point}}
{M 1 0 0}
{press {Move Point}}
{M 1 512 512}
} [list csg_edit_point_at V0 $csg_special_mouse_target]
csg_mouse_case csg_mouse_pipe_topology.s {{Append Point}} "mouse append point" {
{csg_edit_prepare_mouse_view}
{press {Append Point}}
{M 1 512 512}
} [list csg_edit_point_at V4 {5 5 0}]
csg_mouse_case csg_mouse_pipe_topology.s {{Prepend Point}} "mouse prepend point" {
{csg_edit_prepare_mouse_view}
{press {Prepend Point}}
{M 1 512 512}
} [list csg_edit_point_at V0 {5 5 0}]
csg_mouse_case csg_mouse_pipe_topology.s {{Split Segment}} "mouse split segment" {
{csg_edit_prepare_mouse_view}
{press {Select Point}}
{p 0 0 0}
{press {Split Segment}}
{M 1 512 512}
} [list csg_edit_point_at V1 {5 5 0}]
foreach {item attribute initial} {
{Set Point OD} O0 5
{Set Point ID} I0 3
{Set Point Bend} R0 6
} {
csg_mouse_case csg_lifecycle_pipe.s [list $item] "mouse $item" [list \
{csg_edit_prepare_mouse_view} \
{press {Select Point}} {p 0 0 0} [list press $item] \
{M 1 0 -1024}] \
[list csg_edit_scalar_at $attribute [expr {$initial * $csg_special_mouse_shrink}]]
}
foreach {item attributes initial} {
{Set Pipe OD} {O0 O1 O2 O3} {5 5 6 6}
{Set Pipe ID} {I0 I1 I2 I3} {3 3 2 0}
{Set Pipe Bend} {R0 R1 R2 R3} {6 7 10 8}
} {
set expected {}
foreach value $initial {
lappend expected [expr {$value * $csg_special_mouse_shrink}]
}
csg_mouse_case csg_lifecycle_pipe.s [list $item] "mouse $item" [list \
{csg_edit_prepare_mouse_view} \
[list press $item] {M 1 0 -1024}] \
[list csg_edit_scalar_values_at $attributes $expected]
}
proc csg_edit_bot_vertices_moved {source moved delta prim} {
set vertices [db get $source V]
for {set index 0} {$index < [llength $vertices]} {incr index} {
set expected [lindex $vertices $index]
if {[lsearch -exact $moved $index] >= 0} {
set translated {}
foreach value $expected shift $delta {
lappend translated [expr {$value + $shift}]
}
set expected $translated
}
csg_edit_list_value_at V $index $expected $prim
}
}
csg_mouse_case csg_lifecycle_bot.s {{Move Vertex}} "mouse move vertex" {
{csg_edit_prepare_mouse_view}
{press {Pick Vertex}}
{p 0}
{press {Move Vertex}}
{M 1 512 512}
} [list csg_edit_bot_vertices_moved csg_lifecycle_bot.s {0} \
$csg_special_mouse_target]
csg_mouse_case csg_lifecycle_bot.s {{Pick Vertex} {Move Vertex}} "mouse pick and move vertex" {
{csg_edit_prepare_mouse_view}
{press {Pick Vertex}}
{M 1 0 0}
{press {Move Vertex}}
{M 1 512 512}
} [list csg_edit_bot_vertices_moved csg_lifecycle_bot.s {0} \
$csg_special_mouse_target]
csg_mouse_case csg_lifecycle_bot.s {{Move Edge}} "mouse move edge" {
{csg_edit_prepare_mouse_view}
{press {Pick Edge}}
{p 0 1}
{press {Move Edge}}
{M 1 1024 512}
} [list csg_edit_bot_vertices_moved csg_lifecycle_bot.s {0 1} {10 5 0}]
csg_mouse_case csg_lifecycle_bot.s {{Pick Edge} {Move Edge}} "mouse pick and move edge" {
{csg_edit_prepare_mouse_view}
{press {Pick Edge}}
{M 1 512 512}
{press {Move Edge}}
{M 1 1024 512}
} [list csg_edit_bot_vertices_moved csg_lifecycle_bot.s {0 1} {10 5 0}]
csg_mouse_case csg_lifecycle_bot.s {{Move Triangle}} "mouse move triangle" {
{csg_edit_prepare_mouse_view}
{press {Pick Triangle}}
{p 0 1 2}
{press {Move Triangle}}
{M 1 512 512}
} [list csg_edit_bot_vertices_moved csg_lifecycle_bot.s {0 1 2} \
$csg_special_mouse_target]
csg_mouse_case csg_lifecycle_ebm.s {{Extrude Depth}} "mouse extrusion depth" {
{csg_edit_prepare_mouse_view}
{press {Extrude Depth}}
{M 1 0 2047}
} [list csg_edit_scalar_at H [expr {4.0 * $csg_special_mouse_scale}]]
csg_mouse_case csg_lifecycle_vol.s {{Threshold (low)}} "mouse low threshold" {
{csg_edit_prepare_mouse_view}
{press {Threshold (low)}}
{p 10}
{M 1 0 2047}
} [list csg_edit_scalar_at lo [expr {int(10.0 * $csg_special_mouse_scale)}]]
csg_mouse_case csg_lifecycle_vol.s {{Voxel Size (X Y Z)}} "mouse voxel size" {
{csg_edit_prepare_mouse_view}
{press {Voxel Size (X Y Z)}}
{M 1 0 2047}
} [list csg_edit_point_at size [list \
$csg_special_mouse_scale $csg_special_mouse_scale $csg_special_mouse_scale]]
csg_mouse_case csg_lifecycle_vol.s {{Threshold (hi)}} "mouse high threshold" {
{csg_edit_prepare_mouse_view}
{press {Threshold (hi)}}
{M 1 0 -1024}
} [list csg_edit_scalar_at hi [expr {int(255.0 * $csg_special_mouse_shrink)}]]
csg_mouse_case csg_lifecycle_dsp.s {{Set X}} "mouse X scale" {
{csg_edit_prepare_mouse_view}
{press {Set X}}
{M 1 0 2047}
} [list csg_edit_matrix_component_at stom 0 $csg_special_mouse_scale]
foreach {item index} {{Set Y} 5 {Set ALT} 10} {
csg_mouse_case csg_lifecycle_dsp.s [list $item] "mouse $item" [list \
{csg_edit_prepare_mouse_view} \
[list press $item] {M 1 0 2047}] \
[list csg_edit_matrix_component_at stom $index $csg_special_mouse_scale]
}
foreach type {arbn brep} {
set source csg_lifecycle_${type}.s
csg_edit_sequence_check $source "generic mouse translation" {
{csg_edit_prepare_mouse_view}
{keypoint 0 0 0}
{press sxy}
{M 1 512 512}
} [list csg_edit_bbox_translated $source $csg_special_mouse_target]
}
# Every specialized menu entry must be accounted for. Exclusions are menu
# headings, non-positional actions/settings, or the BOT triangle picker (tested
# under Xmin because it needs Tk). A new entry without a mouse case or
# explicit exclusion fails here.
foreach {source ignored} {
csg_lifecycle_pipe.s {
{PIPE MENU} {Next Point} {Previous Point} {Delete Point}
}
csg_lifecycle_bot.s {
{BOT MENU} {Pick Triangle} {Move Vertex List}
{Split Edge} {Split Face} {Fuse Vertices} {Fuse Faces}
{Delete Triangle} {Select Mode} {Select Orientation}
{Set flags} {Set Face Thickness} {Set Face Mode}
}
csg_lifecycle_dsp.s {
{DSP MENU} Name {Toggle Smooth} {Set Data Source}
}
csg_lifecycle_ebm.s {
{EBM MENU} {File Name} {File Size (W N)}
}
csg_lifecycle_vol.s {
{VOL MENU} {File Name} {File Size (X Y Z)}
}
csg_lifecycle_arbn.s {}
csg_lifecycle_brep.s {}
} {
csg_edit_menu_inventory_check $source $ignored
}
# A view-space request must remain view-space even when the editor's current
# coordinate system is object-relative. This is the fast path underlying the
# corresponding real mouse drag in the Xmin GUI test.
set csg_saved_coords [rset var coords]
set csg_saved_transform [rset var transform]
if {[catch {
Z
e csg_lifecycle_bot.s
sed csg_lifecycle_bot.s
center 0 0 0
rset var coords o
rset var transform v
knob -i -v aX 1
if {[csg_edit_point_distance [center] {-1 0 0}] > 0.000001} {
error "explicit view knob left center at [center]"
}
} csg_view_override_error]} {
puts " FAIL: view-space translation with object coordinates ($csg_view_override_error)"
} else {
puts " PASS: view-space translation with object coordinates"
}
catch {press reject}
catch {Z}
rset var coords $csg_saved_coords
rset var transform $csg_saved_transform
file delete -force $csg_data_file
file delete -force $csg_dsp_file
-33
View File
@@ -43,37 +43,4 @@ foreach {arb_type face expected_type} $arb_extrude_cases {
arb_standard_storage_check $expected_type $prim
}
# A one-inch extrusion must extend a face by 25.4 base millimeters through
# both the direct libged command and MGED's interactive solid editor.
set inch_in_mm 25.4
set tolerance 0.001
foreach mode {direct interactive} {
set suffix "_inch_$mode"
in_arb8 extrude $suffix
set prim "extrude_arb8${suffix}.s"
set base_x [lindex [db get $prim V1] 0]
units in
if {$mode eq "direct"} {
if {[catch {edarb extrude $prim 1234 1} result]} {
error "inch edarb extrusion failed: $result"
}
} else {
Z
e $prim
sed $prim
if {[catch {extrude 1234 1} result]} {
error "inch MGED extrusion failed: $result"
}
accept
Z
}
units mm
set new_x [lindex [db get $prim V5] 0]
if {abs($new_x - $base_x - $inch_in_mm) > $tolerance} {
error "inch $mode extrusion moved V5 by [expr {$new_x - $base_x}] mm"
}
}
puts "*** 'extrude' testing completed ***"
-23
View File
@@ -31,7 +31,6 @@ in tor_pe.s tor 0 0 0 0 0 1 20 2
prim_edit_check tor_pe.s "Set Radius 1" 30 r_a 30 -1
prim_edit_check tor_pe.s "Set Radius 2" 5 r_h 5 -1
prim_edit_mouse_check tor_pe.s "Set Radius 1" r_a 30 -1
prim_edit_mouse_check tor_pe.s "Set Radius 2" r_h 5 -1
kill tor_pe.s
puts "--- ELL (ellipsoid: A, B, C) ---"
@@ -40,8 +39,6 @@ prim_edit_check ell_pe.s "Set A" 8 A 8 0
prim_edit_check ell_pe.s "Set B" 6 B 6 1
prim_edit_check ell_pe.s "Set C" 4 C 4 2
prim_edit_mouse_check ell_pe.s "Set A" A 8 0
prim_edit_mouse_check ell_pe.s "Set B" B 6 1
prim_edit_mouse_check ell_pe.s "Set C" C 4 2
kill ell_pe.s
puts "--- RPC (right parabolic cylinder: H, r) ---"
@@ -49,7 +46,6 @@ in rpc_pe.s rpc 0 0 0 0 0 10 0 5 0 3
prim_edit_check rpc_pe.s "Set H" 20 H 20 2
prim_edit_check rpc_pe.s "Set r" 6 r 6 -1
prim_edit_mouse_check rpc_pe.s "Set H" H 20 2
prim_edit_mouse_check rpc_pe.s "Set r" r 6 -1
kill rpc_pe.s
puts "--- RHC (right hyperbolic cylinder: H, r, c) ---"
@@ -58,8 +54,6 @@ prim_edit_check rhc_pe.s "Set H" 20 H 20 2
prim_edit_check rhc_pe.s "Set r" 6 r 6 -1
prim_edit_check rhc_pe.s "Set c" 3 c 3 -1
prim_edit_mouse_check rhc_pe.s "Set r" r 6 -1
prim_edit_mouse_check rhc_pe.s "Set H" H 20 2
prim_edit_mouse_check rhc_pe.s "Set c" c 3 -1
kill rhc_pe.s
puts "--- EPA (elliptical paraboloid: H, r_1, r_2) ---"
@@ -68,8 +62,6 @@ prim_edit_check epa_pe.s "Set H" 20 H 20 2
prim_edit_check epa_pe.s "Set A" 8 r_1 8 -1
prim_edit_check epa_pe.s "Set B" 4 r_2 4 -1
prim_edit_mouse_check epa_pe.s "Set H" H 20 2
prim_edit_mouse_check epa_pe.s "Set A" r_1 8 -1
prim_edit_mouse_check epa_pe.s "Set B" r_2 4 -1
kill epa_pe.s
puts "--- EHY (elliptical hyperboloid: H, r_1, c) ---"
@@ -78,8 +70,6 @@ prim_edit_check ehy_pe.s "Set H" 20 H 20 2
prim_edit_check ehy_pe.s "Set A" 8 r_1 8 -1
prim_edit_check ehy_pe.s "Set c" 3 c 3 -1
prim_edit_mouse_check ehy_pe.s "Set A" r_1 8 -1
prim_edit_mouse_check ehy_pe.s "Set H" H 20 2
prim_edit_mouse_check ehy_pe.s "Set c" c 3 -1
kill ehy_pe.s
puts "--- PART (particle: H, r_v, r_h) ---"
@@ -88,8 +78,6 @@ prim_edit_check part_pe.s "Set H" 20 H 20 2
prim_edit_check part_pe.s "Set v" 4 r_v 4 -1
prim_edit_check part_pe.s "Set h" 3 r_h 3 -1
prim_edit_mouse_check part_pe.s "Set v" r_v 4 -1
prim_edit_mouse_check part_pe.s "Set H" H 20 2
prim_edit_mouse_check part_pe.s "Set h" r_h 3 -1
kill part_pe.s
puts "--- TGC (truncated general cone: H, A, B, C, D) ---"
@@ -103,10 +91,6 @@ prim_edit_check tgc_pe.s "Set B" 3 B 3 1
prim_edit_check tgc_pe.s "Set C" 3 C 3 0
prim_edit_check tgc_pe.s "Set D" 4 D 4 1
prim_edit_mouse_check tgc_pe.s "Set H" H 8 2
prim_edit_mouse_check tgc_pe.s "Set A" A 4 0
prim_edit_mouse_check tgc_pe.s "Set B" B 3 1
prim_edit_mouse_check tgc_pe.s "Set C" C 3 0
prim_edit_mouse_check tgc_pe.s "Set D" D 4 1
kill tgc_pe.s
puts "--- ETO (elliptical torus: r, C, D) ---"
@@ -116,7 +100,6 @@ prim_edit_check eto_pe.s "Set r" 8 r 8 -1
prim_edit_check eto_pe.s "Set D" 1.5 r_d 1.5 -1
prim_edit_mouse_check eto_pe.s "Set r" r 8 -1
prim_edit_mouse_check eto_pe.s "Set C" C 5 0
prim_edit_mouse_check eto_pe.s "Set D" r_d 1.5 -1
kill eto_pe.s
puts "--- HYP (one-sheet hyperboloid: H, A, B, c) ---"
@@ -128,9 +111,6 @@ prim_edit_check hyp_pe.s "Set A" 2 A 10 1
prim_edit_check hyp_pe.s "Set B" 2 b 4 -1
prim_edit_check hyp_pe.s "Set c" 0.5 bnr 0.25 -1
prim_edit_mouse_check hyp_pe.s "Set H" H 20 2
prim_edit_mouse_check hyp_pe.s "Set A" A 10 1
prim_edit_mouse_check hyp_pe.s "Set B" b 4 -1
prim_edit_mouse_check hyp_pe.s "Set c" bnr 0.25 -1
kill hyp_pe.s
puts "--- SUPERELL (superellipsoid: A, B, C) ---"
@@ -139,8 +119,6 @@ prim_edit_check superell_pe.s "Set A" 5 A 5 0
prim_edit_check superell_pe.s "Set B" 4 B 4 1
prim_edit_check superell_pe.s "Set C" 3 C 3 2
prim_edit_mouse_check superell_pe.s "Set A" A 5 0
prim_edit_mouse_check superell_pe.s "Set B" B 4 1
prim_edit_mouse_check superell_pe.s "Set C" C 3 2
kill superell_pe.s
puts "--- CLINE (cylinder line: H, R, plate thickness) ---"
@@ -151,7 +129,6 @@ prim_edit_check cline_pe.s "Set R" 4 R 4 -1
prim_edit_check cline_pe.s "Set plate thickness" 0.4 T 0.4 -1
prim_edit_mouse_check cline_pe.s "Set H" H 8 2
prim_edit_mouse_check cline_pe.s "Set R" R 4 -1
prim_edit_mouse_check cline_pe.s "Set plate thickness" T 0.4 -1
kill cline_pe.s
puts "--- NMG (non-manifold geometry: Pick Edge, Move Edge, Next EU) ---"
-64
View File
@@ -1,64 +0,0 @@
puts "*** Testing 'sed_reset' command ***"
proc sed_reset_check {primitive edit_commands} {
e $primitive
sed $primitive
if {[status state] ne "SOL EDIT"} {
error "$primitive entered [status state], not SOL EDIT"
}
set original [get_sed]
foreach command $edit_commands {
uplevel #0 $command
}
if {[get_sed] eq $original} {
error "$primitive edit did not change the working geometry"
}
sed_reset
if {[status state] ne "SOL EDIT"} {
error "$primitive reset entered [status state], not SOL EDIT"
}
if {[get_sed] ne $original} {
error "$primitive reset did not restore the original geometry"
}
press reject
if {[status state] ne "VIEWING"} {
error "$primitive reject entered [status state], not VIEWING"
}
d $primitive
}
if {[catch {
in sed_reset_arb8.s arb8 \
0 0 0 10 0 0 10 10 0 0 10 0 \
0 0 10 10 0 10 10 10 10 0 10 10
sed_reset_check sed_reset_arb8.s {
{press {Move Faces}}
{press {Move Face 1234}}
{p 0 0 -1}
}
} failure]} {
puts "FAIL: ARB8 face move reset ($failure)"
catch {press reject}
catch {Z}
} else {
puts "PASS: ARB8 face move reset"
}
if {[catch {
make sed_reset_cline.s cline
sed_reset_check sed_reset_cline.s {
{press {Move End H}}
{p 3 4 12}
}
} failure]} {
puts "FAIL: CLINE edit reset ($failure)"
catch {press reject}
catch {Z}
} else {
puts "PASS: CLINE edit reset"
}
puts "PASS: 'sed_reset' testing completed"
+2 -17
View File
@@ -93,7 +93,6 @@ if(BRLCAD_X11_PROVIDER_RESOLVED STREQUAL "XMIN" AND
"PLOT3_ASC_BIN=$<TARGET_FILE:plot3-asc>"
)
mged_add_xmin_gui_fixture(help "mged;dm-tkswrast;m35.g")
mged_add_xmin_gui_fixture(edit-interaction "mged;dm-tkswrast;m35.g")
mged_add_xmin_gui_fixture(bot "mged;dm-tkswrast;m35.g")
mged_add_xmin_gui_fixture(editors "mged;dm-tkswrast;m35.g")
mged_add_xmin_gui_fixture(query-pattern "mged;dm-tkswrast;m35.g")
@@ -144,18 +143,6 @@ if(BRLCAD_X11_PROVIDER_RESOLVED STREQUAL "XMIN" AND
)
brlcad_regression_test(regress-archer-embedded-xmin-gui
"archer;bwish" STAND_ALONE TEST_DEFINED)
brlcad_add_gui_test(
NAME regress-archer-geometry-xmin-gui
TIMEOUT 120
ENVIRONMENT ${_gui_smoke_environment}
"GUI_TEST_LIBRARY=${_gui_test_library}"
COMMAND "${SH_EXEC}" "${_gui_tcl_runner}" archer-geometry
$<TARGET_FILE:bwish>
"${CMAKE_CURRENT_SOURCE_DIR}/archer_geometry_xmin_gui.tcl"
)
brlcad_regression_test(regress-archer-geometry-xmin-gui
"archer;bwish" STAND_ALONE TEST_DEFINED)
endif()
endif()
@@ -213,7 +200,6 @@ cmakefiles(
archer_action.sh
archer_smoke.sh
archer_embedded_xmin_gui.tcl
archer_geometry_xmin_gui.tcl
archer_menu_manifest.txt
archer_xmin_gui.tcl
qged_action.sh
@@ -226,19 +212,18 @@ cmakefiles(
mged/mged_xmin_gui.sh
mged/xmin_gui_bot.tcl
mged/xmin_gui_controls.tcl
mged/xmin_gui_edit.tcl
mged/xmin_gui_edit_interaction.tcl
mged/xmin_gui_editors.tcl
mged/xmin_gui_help.tcl
mged/xmin_gui_lod.tcl
mged/xmin_gui_overlaps.tcl
mged/xmin_gui_pipe_oracle.tcl
mged/xmin_gui_query_pattern.tcl
mged/xmin_gui_render_outputs.tcl
mged/xmin_gui_search_exec.tcl
mged/xmin_gui_shotvis.tcl
mged/xmin_gui_test.tcl
mged/xmin_gui_workflows.tcl
mged/xmin_gui_edit.tcl
mged/xmin_gui_pipe_oracle.tcl
)
# Local Variables:
-134
View File
@@ -1,134 +0,0 @@
# A R C H E R _ G E O M E T R Y _ X M I N _ G U I . T C L
# 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.
#
###
if {![info exists ::env(GUI_TEST_LIBRARY)]} {
puts stderr "GUI_TEST_LIBRARY is required"
exit 2
}
source $::env(GUI_TEST_LIBRARY)
set ::env(ARCHER_PREFS_FILE) [file join $::env(GUI_TEST_DIR) .archerrc]
proc archer_geometry_preferences {{geometry ""}} {
set channel [open $::env(ARCHER_PREFS_FILE) w]
if {$geometry ne ""} {
puts $channel [list set mWindowGeometry $geometry]
}
close $channel
}
proc archer_geometry_saved {} {
set channel [open $::env(ARCHER_PREFS_FILE) r]
set contents [read $channel]
close $channel
if {[regexp -line {^set mWindowGeometry (.+)$} $contents -> geometry]} {
return $geometry
}
return ""
}
proc archer_geometry_settle {} {
# Allow MapNotify and the deferred geometry restore to reach the event loop.
set settle_delay_ms 100
after $settle_delay_ms [list set ::archer_geometry_settled 1]
vwait ::archer_geometry_settled
update
::gui::test::require_no_background_errors
}
if {[catch {
package require Archer 1.0
::gui::test::capture_background_errors
wm withdraw .
set saved_geometry 900x650+120+90
set startup_geometry 1000x700+20+30
set updated_geometry 950x680+60+70
set default_geometry 920x660+40+50
archer_geometry_preferences $saved_geometry
Archer .unmapped
::gui::test::require {[wm state .unmapped] eq "withdrawn"} \
"startup fixture unexpectedly showed its window"
::itcl::delete object .unmapped
archer_geometry_settle
::gui::test::require {[archer_geometry_saved] eq $saved_geometry} \
"closing an unmapped Archer replaced its saved geometry"
archer_geometry_preferences
Archer .unmapped_new
::itcl::delete object .unmapped_new
archer_geometry_settle
::gui::test::require {[archer_geometry_saved] eq ""} \
"closing an unmapped Archer saved an unfinished layout size"
archer_geometry_preferences $saved_geometry
Archer .restore
.restore configure -geometry $startup_geometry
wm geometry .restore $startup_geometry
event generate [.restore component hpane] <Map>
archer_geometry_settle
::gui::test::require {[wm geometry .restore] eq $startup_geometry} \
"a child Map event restored the main window's geometry prematurely"
wm deiconify .restore
archer_geometry_settle
::gui::test::require {[wm geometry .restore] eq $saved_geometry} \
"mapping the main window did not restore its saved geometry"
wm geometry .restore $updated_geometry
event generate [.restore component hpane] <Map>
archer_geometry_settle
::gui::test::require {[wm geometry .restore] eq $updated_geometry} \
"a later child Map event reset the resized main window"
wm withdraw .restore
::itcl::delete object .restore
archer_geometry_settle
::gui::test::require {[archer_geometry_saved] eq $updated_geometry} \
"closing a previously mapped window did not save its latest geometry"
archer_geometry_preferences
Archer .default
wm geometry .default $default_geometry
wm deiconify .default
archer_geometry_settle
::gui::test::require {[wm geometry .default] eq $default_geometry} \
"an empty geometry option reset the window to its layout size"
wm withdraw .default
::itcl::delete object .default
archer_geometry_settle
} message options]} {
puts stderr $message
if {[dict exists $options -errorinfo]} {
puts stderr [dict get $options -errorinfo]
}
puts "FAIL: Archer window geometry regression"
exit 1
}
puts "PASS: Archer window geometry restoration and startup cancellation"
exit 0
# Local Variables:
# tab-width: 8
# mode: Tcl
# tcl-indent-level: 4
# indent-tabs-mode: t
# End:
# ex: shiftwidth=4 tabstop=8
File diff suppressed because it is too large Load Diff
+1 -41
View File
@@ -50,18 +50,6 @@ proc ::mged::xmin::help::exercise_command_manual {top} {
::gui::test::require {
[llength $selection] == 1 && [$toc get $selection] eq "rt"
} "man rt did not select and load the rt manual page"
event generate $toc <<ListboxSelect>>
::mged::gui::test::settle
man search
set html_view [$browser component browser].htmlview
set html_widget [$html_view html]
$html_view goto {#hierarchy_performance}
::mged::gui::test::settle
::gui::test::require {[lindex [$html_widget yview] 0] > 0.0} \
"manual browser did not follow an in-page hyperlink"
::gui::test::require {
[catch {man xmin_page_that_does_not_exist} message] &&
[string first "couldn't find manual page" $message] >= 0
@@ -163,38 +151,10 @@ proc ::mged::xmin::help::exercise_html_manual {top} {
set mged_browser [file join $::env(GUI_TEST_DIR) missing-browser]
::mged::gui::test::invoke $top {Help Manual}
set fallback $top.man
set viewer $fallback.html
::gui::test::require {
[winfo exists $fallback] && [winfo ismapped $fallback] &&
[winfo exists $viewer] && [$viewer title] eq "Table of Contents"
[string length [string trim [$fallback.text get 1.0 end]]] > 100
} "Manual did not load MGED's internal HTML viewer"
set special_path [file join [pwd] {manual path} {contents#1.html}]
::gui::test::require {
[::mged::manual::local_path \
[::mged::manual::file_uri $special_path]] eq \
[file normalize $special_path]
} "internal Manual viewer did not preserve special path characters"
set html [$viewer html]
set contents [$html text text]
::gui::test::require {
[string first "Preface" $contents] >= 0 &&
[string first "background-color" $contents] < 0
} "internal Manual viewer did not render its table of contents"
$viewer goto mged.html#csg
::mged::gui::test::settle
set contents [$html text text]
::gui::test::require {
[$viewer title] eq "Mged User's Manual" &&
[string first "Modeling With CSG" $contents] >= 0
} "internal Manual viewer did not follow its document links"
$fallback.f.back invoke
::mged::gui::test::settle
::gui::test::require {[$viewer title] eq "Table of Contents"} \
"internal Manual viewer Back button did not restore the prior page"
destroy $fallback
}
+11 -22
View File
@@ -53,41 +53,30 @@ endif(BRLCAD_ENABLE_TCL)
# Archer plugins
set(
archer_utility_FILES
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
plugins/Utility/README
)
set(archer_wizard_FILES plugins/Wizards/humanwizard.tcl plugins/Wizards/tankwizard.tcl plugins/Wizards/tirewizard.tcl)
if(BRLCAD_ENABLE_TCL)
# Archer scans only the plugin category directories for registration scripts.
# Keep class definitions in their subdirectories so they are not sourced twice.
foreach(plugin_file IN LISTS archer_utility_FILES archer_wizard_FILES)
get_filename_component(plugin_dir "${plugin_file}" DIRECTORY)
file(RELATIVE_PATH plugin_dir "${CMAKE_CURRENT_SOURCE_DIR}/plugins" "${CMAKE_CURRENT_SOURCE_DIR}/${plugin_dir}")
brlcad_adddata("${plugin_file}" "plugins/archer/${plugin_dir}")
endforeach()
brlcad_adddata(archer_utility_FILES plugins/archer/Utility)
brlcad_adddata(archer_wizard_FILES plugins/archer/Wizards)
brlcad_adddata(plugins/Commands/README plugins/archer/Command)
brlcad_adddata(plugins/Core/README plugins/archer/Core)
brlcad_adddata(plugins/Utility/attrGroupsDisplayUtilityP/AttrGroupsDisplayUtilityP.tcl plugins/archer/Utility/attrGroupsDisplayUtilityP)
brlcad_adddata(plugins/Utility/botUtilityP/BotUtilityP.tcl plugins/archer/Utility/botUtilityP)
brlcad_adddata(plugins/Utility/lodUtilityP/LODUtilityP.tcl plugins/archer/Utility/lodUtilityP)
brlcad_adddata(plugins/Wizards/humanwizard/HumanWizard.tcl plugins/archer/Wizards/humanwizard)
brlcad_adddata(plugins/Wizards/tankwizard/TankWizard.tcl plugins/archer/Wizards/tankwizard)
brlcad_adddata(plugins/Wizards/tankwizard/images/tank.png plugins/archer/Wizards/tankwizardIA/images)
brlcad_adddata(plugins/Wizards/tirewizard/TireWizard.tcl plugins/archer/Wizards/tirewizard)
endif(BRLCAD_ENABLE_TCL)
set(
archer_ignore_files
CMakeLists.txt
plugins/Commands/README
plugins/Core/README
${archer_utility_FILES}
${archer_wizard_FILES}
${archer_txt}
+4 -2
View File
@@ -17,8 +17,10 @@ brlcad_addexec(asc-nmg nmg/asc-nmg.c "libwdb;${M_LIBRARY}" FOLDER Conv)
brlcad_addexec(bot_dump bot_dump.c "libged;librt" FOLDER Conv)
verbose_add_subdirectory("src/conv" comgeom)
set(
comgeom-g_SRCS
comgeom-g_old_SRCS
comgeom/cvt.c
comgeom/f2a.c
comgeom/mat.c
@@ -27,7 +29,7 @@ set(
comgeom/solid.c
comgeom/tools.c
)
brlcad_addexec(comgeom-g "${comgeom-g_SRCS}" "libwdb;libbn;libbu;${M_LIBRARY}" FOLDER Conv)
brlcad_addexec(comgeom-g_old "${comgeom-g_old_SRCS}" "libwdb;libbn;libbu;${M_LIBRARY}" FOLDER Conv)
brlcad_addexec(conv-vg2g conv-vg2g.c "libbu" FOLDER Conv)
+28
View File
@@ -0,0 +1,28 @@
set(comgeom_srcs
ComGeomConverter.cpp
ComGeomReader.cpp
RegionManager.cpp
SolidParser.cpp
main.cpp
)
brlcad_addexec(comgeom-g "${comgeom_srcs}" "libwdb;librt;libnmg;libbu" FOLDER Conv)
target_include_directories(comgeom-g BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
cmakefiles(
CMakeLists.txt
COMGEOM_V1.txt
COMGEOM_versions.txt
ComGeomConverter.h
ComGeomReader.h
RegionManager.h
SolidParser.h
bot_to_arbn_pipeline_thoughts.txt
)
# Local Variables:
# tab-width: 8
# mode: cmake
# indent-tabs-mode: t
# End:
# ex: shiftwidth=2 tabstop=8
+498
View File
@@ -0,0 +1,498 @@
COM-GEOM v1 FORMAT DEFINITION FOR THE GIFT CODE
Based on BRL 1975 Manuals (Vol. I: BRL Report 1802; Vol. II: ARBRL-TR-02189)
---------------------------------------------------------------------
SECTION 0. GENERAL PRINCIPLES
1. Coordinate System
- Right-handed Cartesian system (X,Y,Z).
- Origin is user-defined (e.g., turret datum intersection for a tank).
- All dimensional values (coordinates, lengths, radii) use a single unit family
declared on the Title Card (columns 79-80): IN, FT, CM, M, or MM (default
IN).
2. Data Organization (Target Description)
A COM-GEOM data set consists of (in order):
1) Title Card
2) Target Specification Card
3) Solid Table (one or more cards per solid)
4) Region Table (one or more cards per region; ends with "-1" sentinel card)
5) (Optional) Region RPP Table
6) Region Identification Table
7) Program Option / Control Cards (for GIFT processing and output runs)
3. Numeric Formats
Unless otherwise noted:
- Integers are right- or left-adjusted in their column ranges.
- Floating point fields: F10.5 (classic) or F10.n where width 10 columns,
decimal point explicit.
- Blank where a "+" intersection operator is implied.
4. Combination Logic (Region Construction)
Given solids S1, S2 ...
- Intersection: adjacency (implicit "+"). Example: 1 2 3 means S1 intersect S2
intersect S3.
- Subtraction: prefix "-" before a solid number: ... -7 means subtract volume
of S7 from accumulated set.
- Union: keyword OR splits disjoint boolean branches: BranchA OR BranchB.
- Dummy region: region card with only region number and no solids (assigned
special identification code 111).
- Recommended practice (per BRL guidance): Avoid OR on Region cards; instead
assign identical identification (Item) codes to multiple simple regions.
5. Air Space and Items
Each region is either:
- Item (component) region: has item code (e.g., armor plate, wheel).
- Air region: has air space code (e.g., crew compartment air).
Rules:
- Item regions must not overlap (beyond TOL).
- Air regions may overlap other air regions with same air code.
- Air regions must NOT overlap air regions with different air codes (beyond
TOL).
- Air regions may overlap item regions (they represent enclosing void).
6. Tolerances (Control Card)
- Overlap tolerance TOL: maximum allowable spatial overlap distance between
regions before flagged as error.
- Line-of-sight gap tolerance TOLLOS: maximum axial gap along a ray ignored
when assembling ray intersection chains.
7. End Sentinel & Codes
- Void Space Code: numeric code inserted where no component.
- Phantom Item Code: code assigned to "phantom armor" or shielding constructs
generated during checking.
- End-of-ray Signal Code: code marking termination of component stack for a ray.
8. Storage Model
MASTER-ASTER equivalence array holds all processed geometry. Size NDQ must be
large enough; formula given (Section 11 of Vol II) reproduced below (see
Section 7).
---------------------------------------------------------------------
SECTION 1. TITLE CARD
Columns (1-80):
1-60: Free text title (target name/description).
61-78: (Usually blank or ancillary description).
79-80: Units code (IN, FT, CM, M, MM). If blank defaults to IN.
Example:
SAMPLE LIGHT ARMORED CAR WITH DRIVER IN
---------------------------------------------------------------------
SECTION 2. TARGET SPECIFICATION CARD
Format: (Counts of solids / regions / surrounding RPPs)
Columns (fixed interpretation from manuals):
1-10 Number of Solids (NSOLID)
11-20 Number of Regions (NREGION, expected or max)
21-30 Number of Surrounding RPPs (if used) (NRPP) or MAX Regions (context dependent)
31-40 MAX pointers or storage (implementation field) - historically used internally
Remaining columns may be reserved or blank.
If a dedicated second card exists (variant implementations) it repeats extended capacity fields.
Example:
20 20 1
---------------------------------------------------------------------
SECTION 3. SOLID TABLE
Each solid has a unique consecutive integer ID starting at 1.
GENERAL CARD FORMAT (all solids):
Columns:
1-3 Solid Number (integer)
4-6 Solid Symbol (3 chars): RPP BOX RAW ARB ARS ELL SPH RCC REC TRC TEC TOR
7 Variant / Vertex Count / Option flag (for ARB, ARS, ELL1) else blank
11-70 Numeric fields (F10.5 blocks)
71-80 Comment (recommended: name or purpose)
3.1 Rectangular Parallelepiped (RPP)
No explicit vertex; define bounding box aligned to global axes.
Fields (F10.5 each):
XMIN, XMAX, YMIN, YMAX, ZMIN, ZMAX.
Example:
6 RPP 20.0000 23.0000 -10.0000 10.0000 28.0000 46.0000 BODY
3.2 BOX (Arbitrarily oriented)
Vertex V and three mutually perpendicular edge vectors H, W, D.
Fields:
Vx Vy Vz Hx Hy Hz Wx Wy Wz Dx Dy Dz
Example:
1 BOX 21.5000 37.0000 0.0000 0.0000 45.0000 11.0000 40.0000 0.0000 0.0000 11.0000 0.0000 0.0000 STEERING WHEEL
3.3 RAW (Right Angle Wedge)
Vertex V; two perpendicular legs H,W in wedge plane; depth D.
Fields:
Vx Vy Vz Hx Hy Hz Wx Wy Wz Dx Dy Dz
Example:
12 RAW -75.0000 -36.0000 12.0000 60.0000 12.0000 0.0000 60.0000 12.0000 0.0000 -60.0000 12.0000 0.0000 FENDER
3.4 ARB (Convex Polyhedron) - ARB4 / ARB5 / ARB6 / ARB7 / ARB8
Column 7 = number of vertices (4-8).
Card sequence: Enough cards to list all vertices (each vertex: X Y Z).
One vertex triple per F10.5 block; continue across cards.
Face set is internally inferred by GIFT according to vertex count pattern (see manual).
Example ARB5 (two cards if needed):
3 ARB5 33.5000 37.0000 12.0000 46.0000 -75.0000 -75.0000 20.0000 50.0000 0.0000 ...
(continuation)
3.5 ARS (Triangular Surfaced Polyhedron)
Column 7: M (number of curves)
Column 11-20: N (points per curve)
Then M x N vertex points laid out curve by curve (each curve on one or more cards).
Repeated vertices allowed; triangles formed between successive curves (see Vol I Fig. 13).
Example:
11 ARS7 (points...) (comments)
3.6 ELL / ELL1 (Ellipsoid of Revolution)
Two input modes:
(a) ELL: Foci F1, F2 and major axis length L.
Fields: F1x F1y F1z F2x F2y F2z L
(b) ELL1 (Column 7 = 1): Center V, semi-major axis vector A, radius R of equatorial circle.
Fields: Vx Vy Vz Ax Ay Az R
Example:
14 ELL1 24.0000 52.0000 0.0000 35.0000 0.0000 20.0000 7.5000 DRIVER CHEST
3.7 SPH (Sphere)
Center V and radius R.
Fields: Vx Vy Vz R
Example:
15 SPH 45.0000 -30.0000 -30.0000 7.5000 HEAD
3.8 RCC (Right Circular Cylinder)
Center of one circular base (vertex V), height vector H, radius R.
Fields: Vx Vy Vz Hx Hy Hz R
Example:
16 RCC -30.0000 -70.0000 -30.0000 48.0000 24.0000 0.0000 13.0000 LEG
3.9 REC (Right Elliptical Cylinder)
Center V, height vector H, semi-major axis vector A, semi-minor axis vector B (both in base plane).
Fields: Vx Vy Vz Hx Hy Hz Ax Ay Az Bx By Bz
Example:
17 REC -30.0000 -30.0000 -30.0000 48.0000 24.0000 0.0000 13.0000 13.0000 0.0000 5.0000 0.0000 0.0000 ARM
3.10 TRC (Truncated Circular Cone)
Base center V, height vector H, base radius R1, top radius R2.
Fields: Vx Vy Vz Hx Hy Hz R1 R2
Example:
18 TRC -30.0000 -30.0000 -70.0000 48.0000 24.0000 0.0000 13.0000 9.0000 TORSO LOWER
3.11 TEC (Truncated Elliptic Cone)
Base center V, height vector H, base semi-major A, base semi-minor B, ratio P (BaseEllipse / TopEllipse).
Fields: Vx Vy Vz Hx Hy Hz Ax Ay Az Bx By Bz P
Example:
19 TEC -30.0000 -70.0000 0.0000 48.0000 24.0000 0.0000 24.0000 0.0000 20.0000 12.0000 0.0000 10.0000 1.5000
3.12 TOR (Torus)
Center V, normal vector N (to torus plane), major radius R1 (centerline), tube radius R2.
Fields: Vx Vy Vz Nx Ny Nz R1 R2
Example:
20 TOR 21.5000 37.0000 0.0000 0.0000 45.0000 11.0000 40.0000 11.0000 STEERING RIM
---------------------------------------------------------------------
SECTION 4. REGION TABLE
Card Format (per region):
Columns:
1-2 Blank (recommended)
3-5 Region Number (integer, consecutive starting at 1)
6 Blank
7-8 "OR" if union logic is used (introductory OR for region; optional but required if any OR appears later)
9-69 Sequence of signed solid references (2-3 digit solid numbers with optional leading minus sign; blanks imply "+" intersection). Space or "OR" may appear to break into disjoint branches.
70-80 Comment (e.g., semantic label, region definition subset index)
Grammar rules:
- Intersection: adjacency (implicit +).
- Subtraction: a leading "-" before the solid number.
- Union: OR separating disjoint boolean expressions.
- End of Region Table: card with region number -1 in columns 4-5 (" -1" Card).
Example (two-card region expression 1 + 2 - 3 OR 4 + 5 - 6):
Card 1:
___111 OR 1 2 -3 4
Card 2:
___111 5 -6
Dummy region (no solids):
___012 (columns 9-69 blank) (Region 12 dummy)
Sentinel:
____-1
Best Practice:
Prefer multiple simple non-OR regions sharing a common identification code (see Section 5) over complex OR expressions.
---------------------------------------------------------------------
SECTION 5. REGION IDENTIFICATION TABLE
Each card assigns either an Item Code (component) or Air Space Code to a region.
Format:
Columns:
1-2 Blank
3-5 Region Number
6-10 Blank or separator
11-15 Item or Air Space Code (integer)
16-69 Optional additional codes or reserved blanks
70-80 Comment (verbal description + region formula)
Special Codes:
- 01 Exterior air / Surrounding void (example usage; manuals show 02,03,05 for interior categories).
- 02 Crew compartment air
- 03 Passenger compartment air
- 05 Engine compartment air
- 09 (DO NOT USE) reserved
- 111 Dummy region code
- 501 Track region (may be remapped to 502 for track edge by GIFT processing)
- 999 Forbidden as a component code
Item Code Ranges (per Table 3 of manual):
1- 99 Personnel & miscellaneous interior
100-199 Armor & structural
200-299 Powertrain / engine group
300-399 Suspension / drive
400-499 Mobility auxiliaries
500-599 Track / running gear (501 special)
600-699 Armament
700-799 Fire control
800-899 Communications / electronics
900-998 Ammunition
If a region is air: its code is an air space code; otherwise an item code.
Example:
__001 40 STEERING WHEEL 1-2
__002 100 STEERING SHAFT 2
__012 111 DUMMY
__019 02 INTERIOR AIR 19
__020 02 INTERIOR AIR 20
---------------------------------------------------------------------
SECTION 6. REGION RPP TABLE (OPTIONAL)
Purpose: Override automatically computed bounding boxes (per region).
Card Format:
Columns:
1-10 Region Number
11-20 XMIN
21-30 XMAX
31-40 YMIN
41-50 YMAX
51-60 ZMIN
61-70 ZMAX
71-80 Comment
Omit dummy regions (those with identification code 111).
If absent for a region, GIFT computes bounding RPP automatically.
Example:
1 20.0000 23.0000 -10.0000 10.0000 28.0000 46.0000
2 15.0000 21.5000 -11.0000 11.0000 28.0000 46.0000
---------------------------------------------------------------------
SECTION 7. STORAGE FORMULA (MASTER-ASTER SIZE NDQ)
Minimum NDQ (memory words) must satisfy largest of:
1) LDES - 1
2) 6 * (NSOLID) + LREGON - 1
3) K * (NREGION) + LRIN - 1 (K = 5 CDC, 8 UNIVAC, 11 IBM)
Plus if alphanumeric region descriptions stored:
NDQ >= LDES + (K - 1) * NREGION - 1
Detailed formula from Volume II (for precise sizing):
TOTAL STORAGE =
4 * (#Solids)
+ 7 * (#RCC)
+12 * (#REC)
+ 8 * (#TRC)
+15 * (#TEC)
+ 4 * (#SPH)
+12 * (#ELL/ELL1)
+11 * (#TOR)
+ 6 * (#RPP)
+15 * (#BOX)
+12 * (#RAW)
+ (ARB Storage)
+ (Non-convex ARS Storage)
+ (Convex ARS Storage)
+14 * (#Regions)
+ (#Region Descriptors)
+ 6
+ (Alphanumeric Storage optional)
ARB Storage per ARB:
4 * (#faces) + 1
Non-convex ARS:
88 + 4 * (L + 2 * N * M)
where L = #points read directly
M = #curves
N = #points per curve
Convex ARS:
4 * (#non-degenerate faces) + 1
Region Descriptors:
Count of non-zero entries in columns 9-69 of Region Table.
Alphanumeric storage per region:
CDC=4, UNIVAC=7, IBM=10 words.
---------------------------------------------------------------------
SECTION 8. CONTROL (PROGRAM OPTION) CARD (GIFT EXECUTION)
Columns (classic Control Card from Vol II):
1 Use processed geometry from binary Unit 4 (0=no, non-zero=yes)
2 Write processed geometry to binary Unit 4 (0=no, non-zero=yes)
3-5 FORTRAN unit number for raw COM-GEOM input (0 or 5 = Unit 5)
6 Print MASTER-ASTER array (debug) if non-zero
7 Print Ordered Region Identification Table if non-zero
8 Print Ordered Region RPP Equivalent Table if non-zero
9 Print Solid RPP Equivalent Table if non-zero
10 Suppress printing of Solid/Region tables if non-zero (No Print)
11-30 Reserved / (legacy fields)
31-40 Overlap tolerance TOL (F10)
41-50 Line-of-sight gap tolerance TOLLOS (F10)
51-55 Void Space Code (integer; default 1 if 0)
56-60 Phantom Item Code (default 111 if 0)
61-65 End-of-ray Signal Code (default 9 if 0)
66-80 Comments
Example:
0 1 5 0 1 1 0 0.010000 0.010000 1 111 9 RUN A
---------------------------------------------------------------------
SECTION 9. VALIDATION & RULES SUMMARY
1) Solid Numbers: Consecutive starting at 1. No gaps.
2) Region Numbers: Consecutive starting at 1; end sentinel: region -1 card.
3) Dummy Regions: Allowed; must carry identification code 111; no solids.
4) Item Regions: Must not overlap beyond TOL.
5) Air Regions: May overlap same air code; not different air codes beyond TOL.
6) Negative Descriptors: Subtraction only applies to immediately preceding accumulated set in branch.
7) OR Branches: Disjoint boolean unions; minimized use recommended.
8) Tolerances: Set to reflect modeling precision; typical TOL=TOLLOS=0.01 in same units.
9) Special Codes: Avoid 999, 09, ensure proper use of 111, 501/502.
---------------------------------------------------------------------
SECTION 10. MINIMAL WORKED EXAMPLE
Title:
SAMPLE LIGHT VEHICLE WITH DRIVER IN
Target Specification:
20 20 1
Solid (excerpt):
1 BOX 21.5000 37.0000 0.0000 0.0000 45.0000 11.0000 40.0000 0.0000 0.0000 11.0000 0.0000 0.0000 STEERING WHEEL
2 TOR 21.5000 37.0000 0.0000 0.0000 45.0000 11.0000 40.0000 11.0000 RIM
3 SPH 33.5000 37.0000 12.0000 7.5000 HEAD
...
20 RCC -30.0000 -70.0000 -30.0000 48.0000 24.0000 0.0000 13.0000 LEG
Region Table (excerpt):
1 1 2 (STEERING WHEEL ASSEMBLY: BOX intersect TOR)
2 3 (HEAD)
3 4 -5 6 (ARM: Intersection of solids 4 & 6 minus 5)
...
12 (Dummy)
...
End: -1
Region Identification:
1 40 STEERING WHEEL 1 2
2 100 STEERING SHAFT 3
3 100 ARM (UPPER) 4 -5 6
12 111 DUMMY
19 02 INTERIOR AIR
20 02 INTERIOR AIR
Region RPP (optional):
1 20.0 23.0 -10.0 10.0 28.0 46.0
2 32.0 37.5 -11.0 11.0 28.0 46.0
...
Control Card:
0 1 5 0 1 0 0 0 0.010000 0.010000 1 111 9 STD RUN
---------------------------------------------------------------------
SECTION 11. COMMON PITFALLS AND RECOMMENDATIONS
1) Overlapping Items: Resolve by subtracting one from the other or adjusting tolerance; large overlaps are modeling errors, not tolerance issues.
2) Excessive OR Usage: Replace with multiple regions sharing identification codes to simplify ray tracing and checking.
3) Insufficient NDQ: Use storage formula; double NDQ if "NO MORE ROOM" messages appear after >75% of Solid Table processed.
4) Dummy Regions: Keep for numbering alignment only; always code 111.
5) Precision: If coordinate acquisition is from drawings with limited precision, set TOL/TOLLOS to at least the drawing resolution.
6) ARS Conversions: For legacy MAGIC/SHOTGEN conversions using ARS, verify point ordering and duplication to preserve intended triangular mesh.
7) TEC Ratio P: Ensure P > 1; if P < 1 code will treat cone as inverted (swap top/base); better to input consistent orientation.
---------------------------------------------------------------------
SECTION 12. DIFFERENCES VS. OTHER ERA GEOMETRY FORMATS
MAGIC / SHOTGEN vs COM-GEOM v1:
- COM-GEOM v1 uses boolean combinatorial solids with explicit card columns.
- Emphasizes minimal OR, encourages identification grouping.
- Tolerances (TOL/TOLLOS) user-adjustable on Control Card (MAGIC earlier versions required source edits).
- Storage formulas explicitly published; user responsible for NDQ sizing (contrast with more opaque internal allocation in other codes).
---------------------------------------------------------------------
SECTION 13. QUICK REFERENCE TABLES
13.1 Solid Symbols and Required Fields
Symbol | Type | Fields (Order)
RPP | Axis-aligned box | XMIN XMAX YMIN YMAX ZMIN ZMAX
BOX | Oriented box | Vx Vy Vz Hx Hy Hz Wx Wy Wz Dx Dy Dz
RAW | Right wedge | Vx Vy Vz Hx Hy Hz Wx Wy Wz Dx Dy Dz
ARB4-8 | Convex polyhedron | Vertex triples (count in col 7)
ARS | Triangulated polyhedron | M, N then M x N vertex triples
ELL | Ellipsoid (foci) | F1x F1y F1z F2x F2y F2z L
ELL1 | Ellipsoid (axis) | Vx Vy Vz Ax Ay Az R (col 7=1)
SPH | Sphere | Vx Vy Vz R
RCC | Right circular cylinder | Vx Vy Vz Hx Hy Hz R
REC | Right elliptical cylinder | Vx Vy Vz Hx Hy Hz Ax Ay Az Bx By Bz
TRC | Truncated circular cone | Vx Vy Vz Hx Hy Hz R1 R2
TEC | Truncated elliptic cone | Vx Vy Vz Hx Hy Hz Ax Ay Az Bx By Bz P
TOR | Torus | Vx Vy Vz Nx Ny Nz R1 R2
13.2 Special Identification Codes
111 Dummy region
501 Track (may become 502)
01,02,03,05 Air categories (examples)
999 Forbidden component code
09 Forbidden air code
---------------------------------------------------------------------
SECTION 14. EXTENSIBILITY NOTES
To add new solid types (historical practice for later internal variants):
- Reserve a new 3-char symbol in columns 4-6.
- Define fixed F10.5 field sequence and document vertex or vector semantics.
- Update MASTER-ASTER storage calculator with new per-solid word count.
- Maintain backward compatibility by keeping existing card column positions.
---------------------------------------------------------------------
SECTION 15. VALIDATION CHECKLIST
Before running GIFT:
[ ] Title card unit code set.
[ ] NSOLID equals number of solid definitions.
[ ] NREGION equals number of region definitions (excluding -1 sentinel).
[ ] Solid numbers consecutive from 1.
[ ] Region numbers consecutive from 1; -1 end card present.
[ ] Region Identification codes valid (no 999, no 09).
[ ] Dummy regions have 111 code and no solids.
[ ] NDQ sized using formula (or rule of thumb 45*NSOLID + margin).
[ ] TOL/TOLLOS appropriate for measurement precision.
[ ] Void / Phantom / End-of-ray codes non-zero or rely on defaults (1,111,9).
[ ] Optional Region RPP Table consistent (no dummy region entries).
[ ] No unintended overlaps (test with CHECK option).
[ ] Use CHECK / TESTG GIFT options for geometry validation before production runs.
---------------------------------------------------------------------
End of COM-GEOM v1 ASCII specification.
+350
View File
@@ -0,0 +1,350 @@
# COMGEOM Versions (1967–present)
This is a survey of the COMGEOM geometry input language across its lifecycle,
from the earliest MAGI/MAGIC card decks through the stable v1 and the tokenized
v4/v5 formats. It highlights what changed between versions, when those changes
occurred, and provides practical guidance for recognizing and migrating old
decks.
Quick summary: v0 (pre‑v1) was the early MAGIC card style with implicit
intersections and “OR” unions; v1 formalized strict punched‑card columns; v2/v3
were transitional internal experiments (partial free‑field parsing,
parentheses, generalized cones); v4 is the fully tokenized, parenthesized
grammar; v5 added attribute/metadata lines atop v4.
---
## Timeline
- 1967–1974: Pre‑formal MAGIC/MAGI “v0”
- 12 primitive shapes; implicit intersection (adjacency), `OR` for unions; no parentheses
- Dual ellipsoid input forms; 5‑card ARB; optional TESTG/VOLUM/AREA sections
- Region IDs (3‑digit components / 2‑digit spaces) already in place
- 1974–1976: COMGEOM v1 (formal punched‑card format)
- Strict column positions; fixed parameter counts per primitive
- Region table layout (cards 16A/16B) and `-1` terminator card
- Error taxonomy; RPP enclosure required
- 1977–1981: Transitional v2 → v3 (internal, not publicly specified)
- v2: Column‑first with free‑field fallback; early generalized cones; explicit `+` tokens begin to appear
- v3: Free‑field first; limited parentheses; explicit `+ - UNION/OR`; auto bounding boxes begin to appear
- 1982–1984: COMGEOM v4 (stable token grammar)
- Whitespace/tokenized parsing; parentheses; decoupled geometry vs. region structure
- 1985+: COMGEOM v5 (v4 + attributes)
- Attribute/metadata lines: material, aircode, color, region parameters; same boolean grammar as v4
Modern translators (e.g., `comgeom-g`) typically support v1, v4, and v5. Other
versions would require manual adaptation.
---
## Summary of Noteworthy Changes
|------------|---------|--------------------------------------|------------------------------------------------|
| Era | Version | Context | Noteworthy Changes |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1967–1970 | v0 | Early MAGI/MAGIC on CDC/BRL | 12 primitives; adjacency = intersection; |
| | | | `-` subtract; `OR` unions; no parentheses; |
| | | | TEC permits oblique height vectors |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1970–1974 | v0 | MAGIC manuals | Canonical 5‑card ARB; dual ellipsoid input |
| | | | (foci vs center form); optional |
| | | | TESTG/VOLUM/AREA cards; strict(er) carding |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1974–1976 | v1 | GIFT integration; CDC/FORTRAN | Formal fixed‑columns per primitive; |
| | | | region table cards (16A/16B); `-1` end marker; |
| | | | RPP enclosure required |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1977–1979 | v2 | Internal expansion | Column parsing w/ free‑field fallback; |
| | | | explicit `+` tokens; early generalized cones; |
| | | | torus parameter ordering fixes |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1979–1981 | v3 | Move to machine‑independent parsing | Whitespace tokenization; shallow parentheses; |
| | | | explicit `+ - UNION/OR`; auto RPP; |
| | | | early ARB face validation |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1982–1984 | v4 | Consolidation into BRL‑CAD pipeline | Fully tokenized whitespace grammar; |
| | | | stable parentheses; clean geometry/structure |
| | | | separation |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1985+ | v5 | Attribute/metadata enrichment | Adds attribute lines (material, aircode, |
| | | | color, region params); same v4 |
| | | | geometry/boolean grammar |
|------------|---------|--------------------------------------|------------------------------------------------|
Provenance: v0 details are drawn from the 1967 MAGI report and the MAGIC JTCG
User Manual (61 JTCG/ME‑71‑7‑1). v1 is documented in BRL Report 1802 and
ARBRL‑TR‑02189. v2/v3 are reconstructed from tool comments and translator
heuristics. v4/v5 are reflected in BRL‑CAD-era translators and user guides.
---
## Primitive set by version
The early “MAGIC twelve” primitives were established by 1970 and remained the
backbone across versions:
- RPP (axis‑aligned box), BOX (oriented box), SPH, RCC (right circular
cylinder), REC (right elliptical cylinder), TRC (truncated right angle cone),
TEC (truncated elliptic cone; height vector need not be perpendicular to base),
ELL (ellipsoid of revolution), RAW/WED (right angle wedge), ARB (arbitrary
convex polyhedron), ARS (arbitrary curved surface), TOR (torus)
| Primitive | v0 (MAGIC) | v1 | v2/v3 (notes) | v4 | v5 |
|-----------|------------|----|------------------------------------|----|----|
| RPP | ✓ | ✓ | Auto‑derive allowed (v3) | ✓ | ✓ |
| BOX | ✓ | ✓ | | ✓ | ✓ |
| SPH | ✓ | ✓ | | ✓ | ✓ |
| RCC | ✓ | ✓ | Param order normalized (v3) | ✓ | ✓ |
| REC | ✓ | ✓ | | ✓ | ✓ |
| TRC | ✓ | ✓ | Distinct from TEC clarified (v3) | ✓ | ✓ |
| TEC | ✓ | ✓ | Pre‑TGC experiments | ✓ | ✓ |
| ELL | ✓ (dual) | ✓ | Dual input retained | ✓ | ✓ |
| RAW/WED | ✓ | ✓ | | ✓ | ✓ |
| ARB | ✓ (5‑card) | ✓ | Face validation begins (v3) | ✓ | ✓ |
| ARS | ✓ | ✓ | Same semantics | ✓ | ✓ |
| TOR | ✓ | ✓ | Minor<Major enforcement tightened | ✓ | ✓ |
Notes:
- ARB in v0/v1 is the “MAGIC 5‑card” convex polyhedron: 8 vertices then 6 faces
by ordinal indices; BRL‑CAD later canonicalizes to `arb8`.
- Ellipsoid dual input (foci+major length or center+axis+equatorial radius) is
an early quirk that persists; translators normalize internally.
- TEC (truncated elliptic cone) allows a non‑perpendicular height vector as
early as v0; generalized cone (TGC) is a later BRL‑CAD primitive outside
COMGEOM proper.
---
## Boolean and region semantics across versions
### v0 (pre‑v1)
- Intersection: implied by adjacency of body references (`Region = 1 2` means
inside both 1 and 2)
- Subtraction: leading minus (`-3` means outside body 3)
- Union: `OR` between clauses (`Region = (1 -2) OR (4 -5)`)
- No parentheses: evaluation is effectively left‑to‑right by clause; `OR` lumps
subregions
- “Buttressing” rule: where two bodies share a surface, each region must negate
the other body to avoid ambiguous boundaries (e.g., `+2 -3` vs `+3 -2`)
Example (v0 style):
```
200 = 2 -3
300 = 3 -2
```
### v1
- Same operators, strict fixed columns:
- Region number cols 1–5, optional `OR` marker (IA(1)), then up to 9
operator+body tokens per card
- Continuation cards permitted; `-1` in cols 1–5 terminates region table
- Parentheses still not part of the grammar; grouping via `OR` card positions
### v2 / v3 (transitional)
- v2: Column parsing with free‑field fallback; explicit `+` tokens increasingly
required in ambiguous decks
- v3: Free‑field tokenization; limited parentheses; `UNION`/`U` recognized
synonyms for `OR`
- Automatic bounding boxes if RPP omitted (conventional runs)
### v4 / v5
- Full token grammar; parentheses supported; explicit `+ - OR UNION` with
predictable precedence
- Geometry declarations cleanly separated from boolean structure; comments
supported
Comparative snippets:
- v0/v1 region equation (fixed columns):
```
001 +2 -3
002 +3 -2
-1
```
- v4/v5 region expression (tokenized):
```
region 001: +2 -3
region 002: +3 -2
```
- v4/v5 with grouping:
```
region 010: ( +2 -3 ) OR ( +4 -5 )
```
---
## Card / field structure changes
### v0 (MAGIC card decks)
- Mixed adherence to columns
- Multi‑card primitives:
- BOX: Vx Vy Vz / Ax Ay Az / Bx By Bz / Cx Cy Cz (2 cards)
- RCC/REC/TRC/RAW: 2 cards; TEC: 3 cards; ARB: 5 cards; ARS: 2 +
M*ceil((N+1)/2) cards
- Region identification card per region: region number + component code or
space code + description
### v1
- Formal fixed‑column definitions per primitive; field counts validated
- RPP mandatory enclosure
- Region table formatting codified; `-1` terminator
- Error messages categorized (fatal vs non‑fatal)
### v2/v3 (reconstructed)
- Free‑field parsing introduced to accept large/overflow coordinates
- Whole‑line comments (`#` or `!`) accepted in some parsers (internal)
- Early “DUMMY” region recognition; improved error diagnostics
### v4/v5
- Whitespace/token parsing only; columns ignored
- v5 adds attribute/metadata lines (see next section)
---
## Attributes and metadata
- v0/v1: Region identification via numeric codes only (3‑digit component;
2‑digit space). Materials/colors external or implicit.
- v5: Attribute lines augment region definitions:
- `material=Steel`, `aircode=02`, `color=0/255/0`, `los=...`, `density=...`
- Name=value or token style, parsed separately from geometry/boolean
---
## Recognizing versions in the wild (cheat sheet)
- Likely v0 (MAGIC):
- Cards referencing TESTG/VOLUM/AREA sections
- Primitive blocks matching MAGIC manual’s 2/3/5‑card counts and ordering
- Region tables with `-1` terminator but no parentheses anywhere; `OR`
appears literally
- Ellipsoid blocks with foci pair + major length
- v1:
- Strict columns; values neatly aligned; region table with IA(1) `OR` markers
- RPP enclosure present and required
- v2/v3 (transitional):
- Mixture of column and free‑field lines; occasional parentheses; `UNION`
token present
- Comments starting `#` or `!` in legacy sources; generalized cone parameters
pre‑TGC
- v4:
- Whitespace tokens; parentheses; clean separation of bodies from boolean
structure; no attribute lines
- v5:
- Same as v4 plus attribute/metadata lines (e.g., `material=`, `aircode=`,
`color=`)
---
## Migration guidance
1. Inventory and validate
- Count per‑primitive parameters; flag lines with wrong cardinality
- Verify region table completeness: every point belongs to exactly one
region; buttressing negations present
2. Convert v0 → v1
- Pad to fixed columns; split multi‑card primitives correctly
- Keep dual ellipsoid inputs; normalize internally if supported
3. Convert v1 → v4/v5
- Translate region boolean expressions to explicit tokens with parentheses
- Add attributes (v5): material, aircode (01 external, 02 crew, 05 engine,
09 terminus), region properties
4. Validate by ray tracing
- Reproduce small sample angles from manual output (cell listings,
obliquities, LOS/normal thickness) to confirm equivalence
Pitfalls:
- Ellipsoid dual form mapping; ensure axis vector direction/length matches
major axis semantics
- TEC/TRC confusion in transitional decks; verify height vector
perpendicularity (TEC: may be oblique)
- ARB face index blocks: ensure 6 faces present; repeat a face for 4/5‑face
forms (per v1 storage) or convert to `arb8`
---
## Examples
### v0 style (MAGIC region)
```
15 +15 -17 -14 -16
16 +15 +16 -14 -17 -21 -22
-1
```
### v1 fixed columns (same semantics)
```
00015 +15 -17 -14 -16
00016 +15 +16 -14 -17 -21 -22
-1
```
### v4/v5 tokenized with grouping
```
region 15: (+15 -17 -14 -16)
region 16: ((+15 +16 -14 -17) OR (-21 -22)) # if logically intended
```
### v5 attributes
```
region 15: (+15 -17 -14 -16)
material=Steel
aircode=02
color=128/128/128
```
---
## Region identification codes (early and stable)
Components (3 digits):
- 001–099 Internal components
- 100–199 Armor
- 200–299 Fuel (often reused in later targets)
- 300–399 Misc external
- 400–499 Gun components
- 500–599 Track suspension (501 reserved for track; edge hits become 502)
- 600–699 Wheel suspension
- 700–799 Power train
- 800–899 Miscellaneous
- 900–998 Unused
- 999 Ground/soil
Spaces (2 digits):
- 01 External air
- 02 Crew compartment air
- 05 Engine compartment air
- 09 No further target
---
## References
- MAGI (1967): Early geometric description technique for vulnerability analysis
(MAGI‑6701)
- MAGIC User Manual (JTCG/ME 61 JTCG/ME‑71‑7‑1; ‑7‑2‑1; ‑7‑2‑2): Combinatorial
geometry, body/region cards, optional QA routines, sample problems
- BRL Report 1802; ARBRL‑TR‑02189: Formal COMGEOM v1 card specifications
- BRL‑CAD documentation: Primitive mappings (e.g., `arb8`, TGC) and later
pipeline translators
---
## Appendix: Field counts per primitive (v0/v1 reference)
- RPP: 6 scalars (Xmin Xmax Ymin Ymax Zmin Zmax)
- BOX: 12 scalars (Vx Vy Vz Ax Ay Az Bx By Bz Cx Cy Cz) over 2 cards
- SPH: 4 scalars (Cx Cy Cz R)
- RCC: 7 scalars over 2 cards (Vx Vy Vz Hx Hy Hz R)
- REC: 12 scalars over 2 cards (Vx Vy Vz Hx Hy Hz R1x R1y R1z R2x R2y R2z)
- TRC: 8 scalars over 2 cards (Vx Vy Vz Hx Hy Hz R1 R2)
- ELL: (foci Fl(3) F2(3) + L) OR (center V(3) + A(3) + equatorial R)
- RAW/WED: 12 scalars over 2 cards (V(3) + leg A(3) + leg B(3) + width C(3); A and B interchangeable)
- ARB: 5 cards: vertices 1–2 on card A; pairs (3,4), (5,6), (7,8) on cards B; faces (6 x 4 ordinals) on card C
- TEC: 13 scalars over 3 cards (V(3) H(3) A(3) B(3) P)
- ARS: 2 header integers (M curves, N points) + M * ceil((N+1)/2) cards of point pairs
+186
View File
@@ -0,0 +1,186 @@
#include "ComGeomConverter.h"
#include <cstdio>
#include <cstring>
#include "ComGeomReader.h"
#include "SolidParser.h"
#include "RegionManager.h"
ComGeomConverter::ComGeomConverter(const std::string &in,
const std::string &out,
const ConverterOptions &opts)
: m_inPath(in), m_outPath(out), m_opts(opts)
{}
ComGeomConverter::~ComGeomConverter() {
if (m_in) fclose(m_in);
if (m_out) wdb_close(m_out);
}
bool ComGeomConverter::openInput() {
if (!bu_file_exists(m_inPath.c_str(), NULL)) {
std::perror(m_inPath.c_str());
return false;
}
m_in = fopen(m_inPath.c_str(), "rb");
if (!m_in) {
std::perror(m_inPath.c_str());
return false;
}
return true;
}
bool ComGeomConverter::openOutput() {
if (bu_file_exists(m_outPath.c_str(), NULL))
bu_log("Warning: appending to existing database\n");
m_out = wdb_fopen(m_outPath.c_str());
if (!m_out) {
std::perror(m_outPath.c_str());
return false;
}
return true;
}
bool ComGeomConverter::writeDbHeader(const std::string &title, const std::string &units) {
if (mk_conversion(units.c_str()) < 0) {
std::printf("WARNING: unknown units '%s', using inches\n", units.c_str());
(void)mk_conversion("in");
}
if (mk_id_units(m_out, title.c_str(), units.c_str()) < 0) {
std::printf("Unable to write database ID, units='%s'\n", units.c_str());
return false;
}
return true;
}
int ComGeomConverter::run() {
if (m_opts.version != 0 && m_opts.version != 1 && m_opts.version != 4 && m_opts.version != 5) {
std::fprintf(stderr,"version %d not supported\n", m_opts.version);
return 1;
}
if (!openInput() || !openOutput()) return 1;
std::printf("Reading version %d COMGEOM file\n", m_opts.version);
/* Title & units */
std::string title, units;
{
ComGeomReader r(m_in);
auto l = r.next("title card");
if (!l) { std::printf("Empty input file: no title record\n"); return 10; }
std::string_view sv(l->s);
if (m_opts.version == 0) {
title = ComGeomReader::rstrip(sv);
ComGeomReader::trimLeadingSpacesInplace(title);
units = "in";
} else if (m_opts.version == 1) {
title = ComGeomReader::rstrip(sv);
ComGeomReader::trimLeadingSpacesInplace(title);
units = "in";
} else {
units.assign(sv.substr(0,2));
title.assign(sv.substr(3));
ComGeomReader::trimLeadingSpacesInplace(title);
title = ComGeomReader::rstrip(title);
units = ComGeomReader::rstrip(units);
ComGeomReader::toLowerInplace(units);
}
std::printf("Title: %s\nUnits: %s\n", title.c_str(), units.c_str());
}
if (!writeDbHeader(title, units)) return 1;
int solTotal=0, regTotal=0;
{
ComGeomReader r(m_in);
if (m_opts.version == 0) {
// Attempt single read first
auto l = r.next("v0 constraints candidate");
if (l) {
std::string_view sv(l->s);
solTotal = ComGeomReader::fieldInt(sv, 30, 10);
regTotal = ComGeomReader::fieldInt(sv, 40, 10);
}
if (solTotal <= 0 || regTotal <= 0) {
// Scan up to 50 lines to find plausible NBODY/NRMAX pair
solTotal = regTotal = 0;
for (int scan=0; scan<50; ++scan) {
auto cand = r.next("scan constraints");
if (!cand) break;
std::string_view sv2(cand->s);
int nb = ComGeomReader::fieldInt(sv2, 30, 10);
int nr = ComGeomReader::fieldInt(sv2, 40, 10);
if (nb > 0 && nr > 0) {
solTotal = nb;
regTotal = nr;
break;
}
}
if (solTotal == 0 || regTotal == 0) {
std::printf("Warning: v0 NBODY/NRMAX not found, dynamic sizing will be used\n");
solTotal = 0; // dynamic
regTotal = 0;
}
}
} else if (m_opts.version == 1) {
solTotal = regTotal = 9999;
} else {
auto l = r.next("control card");
if (!l) { std::printf("No control card .... STOP\n"); return 10; }
std::string_view sv(l->s);
if (m_opts.version == 4) {
solTotal = ComGeomReader::fieldInt(sv, 0, 20);
regTotal = ComGeomReader::fieldInt(sv, 20, 10);
} else {
solTotal = ComGeomReader::fieldInt(sv, 0, 5);
regTotal = ComGeomReader::fieldInt(sv, 5, 5);
}
}
if (m_opts.verbose)
std::printf("Expecting %d solids, %d regions\n", solTotal, regTotal);
}
/* Solids */
{
if (m_opts.verbose) std::puts("Primitive table");
ComGeomReader r(m_in);
SolidParserOptions sopts{m_opts.version,m_opts.verbose,m_opts.suffix};
sopts.legacy_trunc = m_opts.legacy_truncate_names;
SolidParser sp(r, m_out, sopts);
sp.setSolidsExpected(solTotal);
while (true) {
auto res = sp.parseNext();
if (res.status == SolidParseResult::Eof) {
if (m_opts.verbose)
std::printf("\nprocessed %d of %d solids\n\n", sp.solidsProcessed(), solTotal);
if (solTotal > 0 && sp.solidsProcessed() < solTotal && m_opts.version > 1) {
std::printf("some solids are missing, aborting\n");
return 1;
}
break;
}
if (res.status == SolidParseResult::Error) {
std::fprintf(stderr,"error converting solid %d: %s\n",
sp.solidsProcessed(), res.message.c_str());
}
}
}
/* Regions */
{
if (m_opts.verbose) std::puts("\nRegion table");
ComGeomReader r(m_in);
RegionManagerOptions ropts;
ropts.version = m_opts.version;
ropts.verbose = m_opts.verbose;
ropts.suffix = m_opts.suffix;
ropts.regTotal = regTotal;
ropts.legacy_trunc = m_opts.legacy_truncate_names;
RegionManager rm(r, m_out, ropts);
if (!rm.parseRegions()) return 10;
if (m_opts.verbose) std::puts("\nGroups");
if (!rm.writeGroups()) return 10;
if (m_opts.verbose) std::puts("");
}
return 0;
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef BRLCAD_COMGEOM_CONVERTER_H
#define BRLCAD_COMGEOM_CONVERTER_H
#include "common.h"
#include <string>
extern "C" {
#include "wdb.h"
#include "bu/file.h"
#include "bu/log.h"
#include "bu/app.h"
}
struct ConverterOptions {
int version = 5;
int verbose = 0;
std::string suffix;
bool legacy_truncate_names = false; // enable legacy suffix truncation (max 13 chars)
};
class ComGeomConverter {
public:
ComGeomConverter(const std::string &inputPath,
const std::string &outputPath,
const ConverterOptions &opts);
~ComGeomConverter();
int run();
private:
bool openInput();
bool openOutput();
bool writeDbHeader(const std::string &title, const std::string &units);
private:
std::string m_inPath, m_outPath;
ConverterOptions m_opts;
FILE *m_in = nullptr;
rt_wdb *m_out = nullptr;
};
#endif
+76
View File
@@ -0,0 +1,76 @@
#include "ComGeomReader.h"
#include <cstdlib>
#include <cctype>
#include <algorithm>
ComGeomReader::ComGeomReader(FILE *fp) : m_fp(fp) {}
ComGeomReader::~ComGeomReader() = default;
std::optional<ComGeomReader::Line> ComGeomReader::next(const char * /*title*/) {
if (!m_fp) return std::nullopt;
std::string s;
int c = 0;
while ((c = fgetc(m_fp)) != EOF) {
if (c == '\r') {
int nc = fgetc(m_fp);
if (nc != '\n' && nc != EOF) ungetc(nc, m_fp);
break;
}
if (c == '\n') break;
s.push_back(static_cast<char>(c));
}
if (c == EOF && s.empty())
return std::nullopt;
m_line++;
return Line{std::move(s), m_line};
}
int ComGeomReader::fieldInt(std::string_view sv, size_t start, size_t len) {
if (start >= sv.size()) return 0;
size_t end = std::min(sv.size(), start + len);
// Work on the field window only
size_t b = start, e = end;
// Trim leading whitespace
while (b < e && std::isspace(static_cast<unsigned char>(sv[b]))) b++;
// Trim trailing whitespace
while (e > b && std::isspace(static_cast<unsigned char>(sv[e-1]))) e--;
if (b >= e) return 0;
// Note that we preserve internal spaces to match the behavior of the
// original C code - "1 23" would be read as 1 rather than 123. Whether
// or not this is really desirable behavior is a question - i.e. whether
// there is a valid use case where a line would make the above separation
// with intent in the COMGEOM formats - but for now we match legacy code.
std::string token(sv.substr(b, e - b));
return std::atoi(token.c_str());
}
double ComGeomReader::fieldDouble(std::string_view sv, size_t start, size_t len) {
if (start >= sv.size()) return 0.0;
size_t end = std::min(sv.size(), start + len);
// Work on the field window only
size_t b = start, e = end;
// Trim leading whitespace
while (b < e && std::isspace(static_cast<unsigned char>(sv[b]))) b++;
// Trim trailing whitespace
while (e > b && std::isspace(static_cast<unsigned char>(sv[e-1]))) e--;
if (b >= e) return 0.0;
std::string token(sv.substr(b, e - b)); // preserve internal spaces to match legacy C
return std::atof(token.c_str());
}
std::string ComGeomReader::rstrip(std::string_view s) {
size_t end = s.size();
while (end > 0 && s[end-1] == ' ') end--;
return std::string(s.substr(0, end));
}
void ComGeomReader::toLowerInplace(std::string &s) {
for (auto &c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
void ComGeomReader::trimLeadingSpacesInplace(std::string &s) {
size_t i = 0;
while (i < s.size() && std::isspace(static_cast<unsigned char>(s[i]))) i++;
if (i) s.erase(0, i);
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef BRLCAD_COMGEOM_READER_H
#define BRLCAD_COMGEOM_READER_H
#include "common.h"
#include <cstdio>
#include <string>
#include <optional>
#include <string_view>
class ComGeomReader {
public:
struct Line {
std::string s;
size_t number = 0;
std::string_view view() const { return s; }
};
explicit ComGeomReader(FILE *fp);
~ComGeomReader();
std::optional<Line> next(const char *title);
static int fieldInt(std::string_view sv, size_t start, size_t len);
static double fieldDouble(std::string_view sv, size_t start, size_t len);
static std::string rstrip(std::string_view s);
static void toLowerInplace(std::string &s);
static void trimLeadingSpacesInplace(std::string &s);
size_t lineNo() const { return m_line; }
private:
FILE *m_fp = nullptr;
size_t m_line = 0;
};
#endif
+424
View File
@@ -0,0 +1,424 @@
#include "RegionManager.h"
#include <cstdio>
#include <cstring>
#include <utility>
RegionManager::RegionManager(ComGeomReader &reader, rt_wdb *outfp, const RegionManagerOptions &opts)
: m_r(reader), m_out(outfp), m_opts(opts)
{
// For dynamic v0 decks (regTotal == 0) preallocate provisional capacity
size_t allocCount = static_cast<size_t>(m_opts.regTotal + 1);
if (m_opts.version == 0 && m_opts.regTotal == 0) {
allocCount = 4097; // provisional region cap
}
m_regionHeads.resize(allocCount);
for (auto &wm : m_regionHeads) {
WMEMBER_INIT(&wm);
}
groupInit();
}
RegionManager::~RegionManager() {
for (auto &wm : m_regionHeads) {
if (wm.wm_name) {
bu_free(wm.wm_name, "region wm_name");
wm.wm_name = nullptr;
}
}
for (int i = 0; i < m_groupCount; ++i) {
if (m_groups[i].head.wm_name) {
bu_free(m_groups[i].head.wm_name, "group wm_name");
m_groups[i].head.wm_name = nullptr;
}
}
}
std::string RegionManager::makeRegionName(int n) const {
std::string suf = m_opts.suffix;
if (m_opts.legacy_trunc && suf.size() > 13) suf.resize(13);
char buf[64];
std::snprintf(buf, sizeof(buf), "r%d%s", n, suf.c_str());
return std::string(buf);
}
void RegionManager::colPrint(const std::string &s) {
if (!m_opts.verbose) return;
std::fputs(s.c_str(), stdout);
m_colCur += s.size();
while (m_colCur < 78 && (m_colCur % 10)) {
putchar(' ');
++m_colCur;
}
if (m_colCur >= 78) {
std::fputc('\n', stdout);
m_colCur = 0;
}
}
/* ---------------- v0 (MAGIC) region table with OR semantics ---------------- */
bool RegionManager::parseRegionTableV0() {
auto first = m_r.next("v0 region card");
if (!first) { std::fprintf(stderr,"v0 region: EOF\n"); return false; }
std::string current = first->s;
int lastRegNum = 0;
while (true) {
int reg_num = ComGeomReader::fieldInt(current, 0, 5);
if (reg_num < 0) break;
if (reg_num == 0) {
auto nl = m_r.next("v0 region card");
if (!nl) break;
current = nl->s;
continue;
}
// resize if needed (dynamic)
if (reg_num >= static_cast<int>(m_regionHeads.size())) {
std::fprintf(stderr,"v0 region number %d exceeds provisional allocation\n", reg_num);
return false;
}
lastRegNum = reg_num;
wmember &head = m_regionHeads[static_cast<size_t>(reg_num)];
if (!head.wm_name) head.wm_name = bu_strdup(makeRegionName(reg_num).c_str());
struct Term { int body; bool subtract; };
std::vector<std::vector<Term>> clauses;
clauses.emplace_back();
auto parseLine = [&](const std::string &line) {
for (int i=0;i<9;++i) {
size_t slot = 6 + i*7;
if (slot + 7 > line.size()) break;
std::string field = line.substr(slot, 7);
std::string op = field.substr(0,2);
std::string numstr = field.substr(2);
ComGeomReader::trimLeadingSpacesInplace(op);
numstr = ComGeomReader::rstrip(numstr);
ComGeomReader::trimLeadingSpacesInplace(numstr);
if (numstr.empty()) continue;
int bnum = std::atoi(numstr.c_str());
if (bnum == 0) continue;
if (op == "OR" || op == "or") {
if (!clauses.back().empty()) clauses.emplace_back();
continue;
}
bool subtract = (bnum < 0);
if (subtract) bnum = -bnum;
clauses.back().push_back({bnum, subtract});
}
};
parseLine(current);
while (true) {
auto nextLine = m_r.next("v0 region card");
if (!nextLine) break;
int newRegTest = ComGeomReader::fieldInt(nextLine->s, 0, 5);
std::string trimmed = ComGeomReader::rstrip(nextLine->s);
std::string t2 = trimmed; ComGeomReader::trimLeadingSpacesInplace(t2);
if (t2 == "end") {
if (m_opts.regTotal == 0) m_opts.regTotal = reg_num;
break;
}
if (newRegTest < 0) { // negative sentinel
if (m_opts.regTotal == 0) m_opts.regTotal = reg_num;
current = nextLine->s;
break;
}
if (newRegTest > 0) { // new region starts
current = nextLine->s;
break;
}
parseLine(nextLine->s);
}
int clauseIdx = 0;
for (auto &cl : clauses) {
if (cl.empty()) continue;
wmember clauseHead; WMEMBER_INIT(&clauseHead);
char cname[64]; std::snprintf(cname, sizeof(cname), "r%d_c%d", reg_num, ++clauseIdx);
clauseHead.wm_name = bu_strdup(cname);
bool firstTerm = true;
for (auto &t : cl) {
std::string memberName = std::string("s") + std::to_string(t.body) + m_opts.suffix;
int op;
if (firstTerm) {
op = WMOP_UNION; // baseline
firstTerm = false;
} else {
op = t.subtract ? WMOP_SUBTRACT : WMOP_INTERSECT;
}
if (!mk_addmember(memberName.c_str(), &clauseHead.l, nullptr, op)) {
std::fprintf(stderr,"v0 mk_addmember failed clause %s member %s\n", cname, memberName.c_str());
}
}
mk_comb(m_out, clauseHead.wm_name, &clauseHead.l, 0,
"", "", nullptr, 0,0,0,0,0,0,0);
if (!mk_addmember(clauseHead.wm_name, &head.l, nullptr, WMOP_UNION)) {
std::fprintf(stderr,"v0 mk_addmember region %d clause %s\n", reg_num, clauseHead.wm_name);
}
}
if (m_opts.verbose) colPrint(head.wm_name);
int sentinelTest = ComGeomReader::fieldInt(current, 0, 5);
if (sentinelTest < 0) break;
}
if (m_opts.regTotal == 0) m_opts.regTotal = lastRegNum;
return true;
}
bool RegionManager::parseRegions() {
if (m_opts.version == 0) {
if (!parseRegionTableV0()) return false;
for (int i=1;i<=m_opts.regTotal && i < static_cast<int>(m_regionHeads.size());++i) registerRegion(i,0,0,0,0);
return true;
}
if (!parseRegionTable()) return false;
if (m_opts.version == 1) {
for (int i = 1; i <= m_opts.regTotal; ++i) {
registerRegion(i, 0, 0, 0, 0);
}
return true;
}
return parseIdentTable();
}
bool RegionManager::parseRegionTable() {
auto first = m_r.next("region card");
if (!first) {
std::fprintf(stderr, "getregion: EOF\n");
return false;
}
if (ComGeomReader::fieldInt(first->s, 0, 5) != 1) {
std::fprintf(stderr, "First region card not #1\n");
return false;
}
std::string current = first->s;
while (true) {
int reg_num = ComGeomReader::fieldInt(current, 0, 5);
if (reg_num < 0) {
return true;
}
if (reg_num > m_opts.regTotal) {
std::fprintf(stderr, "%d regions > claimed %d\n", reg_num, m_opts.regTotal);
return false;
}
wmember &head = m_regionHeads[static_cast<size_t>(reg_num)];
if (!head.wm_name) {
head.wm_name = bu_strdup(makeRegionName(reg_num).c_str());
}
while (true) {
size_t base = (m_opts.version == 1) ? 10 : 6;
for (int i = 0; i < 9; ++i) {
size_t off = base + i * 7;
if (off + 7 > current.size()) break;
char token[8] = {0};
int k = 0;
for (int j = 2; j < 7; ++j) {
if (off + j >= current.size()) break;
char c = current[off + j];
if (!std::isspace(static_cast<unsigned char>(c))) {
token[k++] = c;
}
}
if (k == 0) continue;
int inst_num = std::atoi(token);
if (inst_num == 0) continue;
int op = WMOP_UNION;
bool region_ref = false;
if (m_opts.version == 5) {
char flag = (off + 1 < current.size()) ? current[off + 1] : ' ';
if (flag == 'g' || flag == 'G') {
region_ref = true;
} else if (flag == 'R' || flag == 'r') {
op = WMOP_UNION;
} else {
if (inst_num < 0) {
op = WMOP_SUBTRACT;
inst_num = -inst_num;
} else {
op = WMOP_INTERSECT;
}
}
} else {
char flag = (off + 1 < current.size()) ? current[off + 1] : ' ';
if (flag == ' ') {
if (inst_num < 0) {
op = WMOP_SUBTRACT;
inst_num = -inst_num;
} else {
op = WMOP_INTERSECT;
}
} else {
op = WMOP_UNION;
}
}
std::string suf = m_opts.suffix;
if (m_opts.legacy_trunc && suf.size() > 13) suf.resize(13);
std::string memberName = region_ref
? makeRegionName(inst_num)
: (std::string("s") + std::to_string(inst_num) + suf);
if (!mk_addmember(memberName.c_str(), &head.l, nullptr, op)) {
std::fprintf(stderr, "mk_addmember failed for region %d member '%s'\n",
reg_num, memberName.c_str());
}
}
auto nextLine = m_r.next("region card");
if (!nextLine) {
std::fprintf(stderr, "getregion: EOF\n");
return false;
}
std::string trimmed = ComGeomReader::rstrip(nextLine->s);
ComGeomReader::trimLeadingSpacesInplace(trimmed);
if (trimmed == "end") {
m_opts.regTotal = reg_num;
return true;
}
int possible = ComGeomReader::fieldInt(nextLine->s, 0, 5);
if (possible != 0) {
current = nextLine->s;
break;
}
current = nextLine->s;
}
if (m_opts.verbose) colPrint(head.wm_name);
}
}
bool RegionManager::parseIdentTable() {
if (m_opts.version == 4) {
(void)m_r.next("blank card");
}
if (m_opts.verbose) std::puts("\nRegion ident table");
while (true) {
auto line = m_r.next("ident card");
if (!line) return true;
if (line->s.empty()) return true;
int reg_num, id, air, mat, los;
if (m_opts.version == 5) {
reg_num = ComGeomReader::fieldInt(line->s, 0, 5);
id = ComGeomReader::fieldInt(line->s, 5, 5);
air = ComGeomReader::fieldInt(line->s, 10, 5);
mat = ComGeomReader::fieldInt(line->s, 15, 5);
los = ComGeomReader::fieldInt(line->s, 20, 5);
} else {
reg_num = ComGeomReader::fieldInt(line->s, 0, 10);
id = ComGeomReader::fieldInt(line->s, 10, 10);
air = ComGeomReader::fieldInt(line->s, 20, 10);
mat = ComGeomReader::fieldInt(line->s, 74, 3);
los = ComGeomReader::fieldInt(line->s, 77, 3);
}
if (reg_num <= 0) return true;
registerRegion(reg_num, id, air, mat, los);
}
}
void RegionManager::registerRegion(int regNum, int id, int air, int mat, int los) {
if (regNum <= 0 || regNum >= static_cast<int>(m_regionHeads.size())) return;
wmember &head = m_regionHeads[static_cast<size_t>(regNum)];
if (BU_LIST_IS_EMPTY(&head.l)) {
if (m_opts.verbose && head.wm_name) {
colPrint(std::string("(") + head.wm_name + ")");
}
return;
}
int ret = mk_comb(m_out, head.wm_name, &head.l, 1,
"", "", nullptr,
id, air, mat, los, 0, 0, 1);
if (ret < 0) {
std::fprintf(stderr, "mk_comb failed for region %d (%s)\n", regNum, head.wm_name);
} else {
groupAdd(id, head.wm_name);
if (m_opts.verbose) colPrint(head.wm_name);
}
}
void RegionManager::groupInit() {
struct Def { const char *n; int lo; int hi; } defs[] = {
{"g00",0,0},{"g0",1,99},{"g1",100,199},{"g2",200,299},{"g3",300,399},
{"g4",400,499},{"g5",500,599},{"g6",600,699},{"g7",700,799},{"g8",800,899},
{"g9",900,999},{"g10",1000,1999},{"g11",2000,2999},{"g12",3000,3999},{"g13",4000,4999},
{"g14",5000,5999},{"g15",6000,6999},{"g16",7000,7999},{"g17",8000,8999},{"g18",9000,9999},
{"g19",10000,32767}
};
for (const auto &d : defs) {
if (m_groupCount >= kMaxGroups) {
std::fprintf(stderr, "Too many groups (limit %d)\n", kMaxGroups);
break;
}
groupRegister(d.n, d.lo, d.hi);
}
}
void RegionManager::groupRegister(const char *name, int lo, int hi) {
Group &g = m_groups[m_groupCount];
WMEMBER_INIT(&g.head);
std::string suf = m_opts.suffix;
if (m_opts.legacy_trunc && suf.size() > 13) suf.resize(13);
std::string full = std::string(name) + suf;
g.head.wm_name = bu_strdup(full.c_str());
g.lo = lo;
g.hi = hi;
g.used = false;
++m_groupCount;
}
void RegionManager::groupAdd(int identVal, const char *regionName) {
for (int i = m_groupCount - 1; i >= 0; --i) {
if (identVal < m_groups[i].lo || identVal > m_groups[i].hi) continue;
if (!mk_addmember(regionName, &m_groups[i].head.l, nullptr, WMOP_UNION)) {
std::fprintf(stderr, "mk_addmember failed adding region '%s' to group '%s'\n",
regionName, m_groups[i].head.wm_name);
} else {
m_groups[i].used = true;
}
return;
}
std::fprintf(stderr, "Unable to find group for ident %d (region %s)\n", identVal, regionName);
}
bool RegionManager::writeGroups() {
wmember allHead;
WMEMBER_INIT(&allHead);
allHead.wm_name = const_cast<char *>("all.g");
for (int i = 0; i < m_groupCount; ++i) {
Group &g = m_groups[i];
if (BU_LIST_IS_EMPTY(&g.head.l)) continue;
int ret = mk_comb(m_out, g.head.wm_name, &g.head.l, 0,
nullptr, nullptr, nullptr,
0, 0, 0, 0, 0, 0, 0);
if (ret < 0) {
std::fprintf(stderr, "mk_comb (group) failed for '%s'\n", g.head.wm_name);
continue;
}
if (!mk_addmember(g.head.wm_name, &allHead.l, nullptr, WMOP_UNION)) {
std::fprintf(stderr, "mk_addmember failed adding '%s' to all.g\n", g.head.wm_name);
}
if (m_opts.verbose) colPrint(g.head.wm_name);
}
int retAll = mk_comb(m_out, "all.g", &allHead.l, 0,
nullptr, nullptr, nullptr,
0, 0, 0, 0, 0, 0, 0);
if (retAll < 0) {
std::fprintf(stderr, "mk_comb failed for all.g\n");
return false;
}
return true;
}
+74
View File
@@ -0,0 +1,74 @@
#pragma once
#include "common.h"
#include <array>
#include <string>
#include <vector>
extern "C" {
#include "vmath.h"
#include "raytrace.h"
#include "wdb.h"
}
#include "ComGeomReader.h"
struct RegionManagerOptions {
int version = 5;
long verbose = 0;
std::string suffix;
int regTotal = 0;
bool legacy_trunc = false; // optional legacy suffix truncation (max 13 chars)
};
/*
* Modernized RegionManager:
* - Uses WMEMBER_INIT for region and group heads (matches wdb.h)
* - Avoids container reallocation for group heads (std::array)
* - Relies on mk_addmember for node allocation and mk_comb/mk_lfcomb
* to free membership lists (per wdb.h contract)
* - No manual traversal freeing of member nodes (use mk_freemembers only
* for temporary aggregation lists we explicitly build, e.g. "all.g" head)
* - Stable addresses: region heads stored in std::vector sized once,
* group heads in fixed array
*/
class RegionManager {
public:
RegionManager(ComGeomReader &reader, rt_wdb *outfp, const RegionManagerOptions &opts);
~RegionManager();
bool parseRegions(); // read region membership cards
bool writeGroups(); // emit group combinations + all.g
private:
std::string makeRegionName(int n) const;
void colPrint(const std::string &s);
bool parseRegionTable();
bool parseRegionTableV0();
bool parseIdentTable();
void registerRegion(int regNum, int id, int air, int mat, int los);
void groupInit();
void groupRegister(const char *name, int lo, int hi);
void groupAdd(int identVal, const char *regionName);
private:
ComGeomReader &m_r;
rt_wdb *m_out = nullptr;
RegionManagerOptions m_opts;
std::vector<wmember> m_regionHeads; // index by region number (1..regTotal)
size_t m_colCur = 0;
struct Group {
wmember head; // combination head (membership list)
int lo = 0;
int hi = 0;
bool used = false; // whether at least one region fell into range
};
static constexpr int kMaxGroups = 21;
std::array<Group, kMaxGroups> m_groups;
int m_groupCount = 0;
};
+830
View File
@@ -0,0 +1,830 @@
#include "SolidParser.h"
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
/* Helper: build min/max from 6 RPP values */
static inline void vset_minmax(fastf_t *min, fastf_t *max, const fastf_t *dd) {
VSET(min, dd[0], dd[2], dd[4]);
VSET(max, dd[1], dd[3], dd[5]);
}
SolidParser::SolidParser(ComGeomReader &reader, rt_wdb *outfp, const SolidParserOptions &opts)
: m_r(reader), m_out(outfp), m_opts(opts)
{}
std::string SolidParser::makeSolidName(int n) const {
std::string suf = m_opts.suffix;
if (m_opts.legacy_trunc && suf.size() > 13) suf.resize(13);
char buf[64] = {0};
std::snprintf(buf, sizeof(buf), "s%d%s", n, suf.c_str());
return std::string(buf);
}
void SolidParser::colPrint(const std::string &s) {
if (!m_opts.verbose) return;
std::fputs(s.c_str(), stdout);
m_colCur += s.size();
while (m_colCur < 78 && (m_colCur % 10)) {
putchar(' ');
++m_colCur;
}
if (m_colCur >= 78) {
std::fputc('\n', stdout);
m_colCur = 0;
}
}
bool SolidParser::parseSolidHeader(const std::string &line, int &solidNum,
std::string &typeLower, std::string &lineOutForData) {
std::string_view sv(line);
std::string solidType;
switch (m_opts.version) {
case 5:
solidNum = ComGeomReader::fieldInt(sv, 0, 5);
solidType = ComGeomReader::rstrip(sv.substr(5, 5));
break;
case 4:
solidNum = ComGeomReader::fieldInt(sv, 0, 3);
solidType = ComGeomReader::rstrip(sv.substr(3, 4));
break;
case 1:
solidNum = ComGeomReader::fieldInt(sv, 5, 4);
solidType = ComGeomReader::rstrip(sv.substr(2, 3));
break;
default:
return false;
}
ComGeomReader::toLowerInplace(solidType);
typeLower = solidType;
lineOutForData = line;
return true;
}
bool SolidParser::getSolData(std::vector<fastf_t> &data, int num, int solidNum,
bool /*firstCardAlreadyRead*/, std::string lineBuf) {
data.clear();
data.reserve(num);
int cd = 1;
std::string line = std::move(lineBuf);
while (num > 0) {
std::string_view sv(line);
if (cd != 1) {
if ((m_opts.version == 5 && sv.size() > 5 && sv[5] != ' ') ||
(m_opts.version == 4 && sv.size() > 3 && sv[3] != ' ')) {
std::fprintf(stderr, "solid %d (continuation) card %d non-blank\n", solidNum, cd);
return false;
}
}
int j = std::min(num, 6);
for (int i = 0; i < j; ++i) {
data.push_back(static_cast<fastf_t>(ComGeomReader::fieldDouble(sv, 10 + i*10, 10)));
}
num -= j;
if (num <= 0) break;
auto l = m_r.next("solid continuation card");
if (!l) {
std::fprintf(stderr, "too few cards for solid %d\n", solidNum);
return false;
}
line = l->s;
++cd;
}
return true;
}
bool SolidParser::getXSolData(std::vector<fastf_t> &data, int num, int solidNum) {
data.clear();
data.reserve(num);
int cd = 1;
while (num > 0) {
auto l = m_r.next("x solid card");
if (!l) {
std::fprintf(stderr, "too few cards for solid %d\n", solidNum);
return false;
}
std::string_view sv(l->s);
if (cd != 1) {
if ((m_opts.version == 5 && sv.size() > 5 && sv[5] != ' ') ||
(m_opts.version == 4 && sv.size() > 3 && sv[3] != ' ')) {
std::fprintf(stderr, "solid %d (continuation) card %d non-blank\n", solidNum, cd);
return false;
}
}
int j = std::min(num, 6);
for (int i = 0; i < j; ++i) {
data.push_back(static_cast<fastf_t>(ComGeomReader::fieldDouble(sv, 10 + i*10, 10)));
}
num -= j;
++cd;
}
return true;
}
bool SolidParser::isIntegerField(std::string_view sv, size_t start, size_t len, int &value) const {
if (start >= sv.size()) { value = 0; return false; }
size_t end = std::min(sv.size(), start + len);
size_t b = start;
while (b < end && std::isspace(static_cast<unsigned char>(sv[b]))) b++;
if (b >= end) { value = 0; return false; }
bool hasDigit=false; bool hasDot=false;
for (size_t i=b;i<end;++i) {
char c = sv[i];
if (std::isspace(static_cast<unsigned char>(c))) break;
if (c == '.') hasDot = true;
if (std::isdigit(static_cast<unsigned char>(c))) hasDigit = true;
else if (c=='-' || c=='+') continue;
else { value=0; return false; }
}
if (!hasDigit || hasDot) { value=0; return false; }
std::string token(sv.substr(b, end-b));
value = std::atoi(token.c_str());
return true;
}
bool SolidParser::isLikelyFaceCard(const std::string &line) const {
// Heuristic: first four 10-char fields each parse as small integers (1..8) & no decimal points.
int vals=0;
for (int f=0; f<4; ++f) {
int v=0;
if (!isIntegerField(line, 10 + f*10, 10, v)) return false;
if (v < -8 || v > 8 || v==0) return false;
vals++;
}
return (vals==4);
}
/* ---------------- Primitive Handlers ---------------- */
SolidParseResult SolidParser::handle_ars(const std::string &name, const std::string &firstLine) {
std::string_view sv(firstLine);
int ncurves = ComGeomReader::fieldInt(sv, 10, 10);
int pts_per_curve = ComGeomReader::fieldInt(sv, 20, 10);
if (ncurves <= 0 || pts_per_curve <= 0)
return {SolidParseResult::Error, "ars invalid counts"};
std::vector<std::vector<fastf_t>> curves(ncurves);
for (int i = 0; i < ncurves; ++i) {
curves[i].resize(static_cast<size_t>(pts_per_curve) * 3);
if (!getXSolData(curves[i], pts_per_curve * 3, m_solWork))
return {SolidParseResult::Error, "ars curve data read failure"};
}
std::vector<fastf_t*> ptrs(ncurves);
for (int i = 0; i < ncurves; ++i) ptrs[i] = curves[i].data();
int ret = mk_ars(m_out, name.c_str(), ncurves, pts_per_curve, ptrs.data());
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_ars failed" : ""};
}
SolidParseResult SolidParser::handle_rpp(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 6) return {SolidParseResult::Error, "rpp needs 6 values"};
fastf_t min[3], max[3];
vset_minmax(min, max, dd.data());
int ret = mk_rpp(m_out, name.c_str(), min, max);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_rpp failed" : ""};
}
SolidParseResult SolidParser::handle_box(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 12) return {SolidParseResult::Error, "box needs 12 values"};
point_t v[8];
const fastf_t *D0 = &dd[0], *D1=&dd[3], *D2=&dd[6], *D3=&dd[9];
VMOVE(v[0], D0);
VADD2(v[1], D0, D2);
VADD3(v[2], D0, D2, D1);
VADD2(v[3], D0, D1);
VADD2(v[4], D0, D3);
VADD3(v[5], D0, D3, D2);
VADD4(v[6], D0, D3, D2, D1);
VADD3(v[7], D0, D3, D1);
int ret = mk_arb8(m_out, name.c_str(), &v[0][0]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(box) failed" : ""};
}
SolidParseResult SolidParser::handle_raw_wed(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 12) return {SolidParseResult::Error, "raw/wed needs 12 values"};
point_t v[8];
const fastf_t *D0=&dd[0], *D1=&dd[3], *D2=&dd[6], *D3=&dd[9];
VMOVE(v[0], D0);
VADD2(v[1], D0, D2);
VMOVE(v[2], v[1]);
VADD2(v[3], D0, D1);
VADD2(v[4], D0, D3);
VADD3(v[5], D0, D3, D2);
VMOVE(v[6], v[5]);
VADD3(v[7], D0, D3, D1);
int ret = mk_arb8(m_out, name.c_str(), &v[0][0]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(raw/wed) failed" : ""};
}
SolidParseResult SolidParser::handle_rvw(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 7) return {SolidParseResult::Error, "rvw needs 7 values"};
vect_t V; VMOVE(V, &dd[0]);
double a2=dd[3], theta=dd[4], phi=dd[5], h2=dd[6];
double angle1 = (phi + theta - 90.0) * DEG2RAD;
double angle2 = (phi + theta) * DEG2RAD;
double a2theta = a2 * std::tan(theta * DEG2RAD);
vect_t a,b,c;
VSET(a, a2theta * std::cos(angle1), a2theta * std::sin(angle1), 0.0);
VSET(b, -a2 * std::cos(angle2), -a2 * std::sin(angle2), 0.0);
VSET(c, 0.0, 0.0, h2);
point_t vert[8];
VSUB2(vert[0], V, b);
VMOVE(vert[1], V);
VMOVE(vert[2], vert[1]);
VADD2(vert[3], vert[0], a);
VADD2(vert[4], vert[0], c);
VADD2(vert[5], vert[1], c);
VMOVE(vert[6], vert[5]);
VADD2(vert[7], vert[3], c);
int ret = mk_arb8(m_out, name.c_str(), &vert[0][0]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(rvw) failed" : ""};
}
SolidParseResult SolidParser::handle_arw(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 12) return {SolidParseResult::Error, "arw needs 12 values"};
point_t v[8];
const fastf_t *D0=&dd[0], *D1=&dd[3], *D2=&dd[6], *D3=&dd[9];
VMOVE(v[0], D0);
VADD2(v[1], D0, D2);
VADD3(v[2], D0, D2, D3);
VADD2(v[3], D0, D3);
VADD2(v[4], D0, D1);
VMOVE(v[5], v[4]);
VADD3(v[6], D0, D1, D3);
VMOVE(v[7], v[6]);
int ret = mk_arb8(m_out, name.c_str(), &v[0][0]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(arw) failed" : ""};
}
/* ---------- MAGIC v0 ARB generic handler ---------- */
SolidParseResult SolidParser::handle_arb_magic(const std::string &name, const std::string &firstLine) {
// Multi-card ARB parsing:
// Collect vertex triples until a face card is detected (integer ordinals 1..8).
std::vector<fastf_t> vertexDoubles;
std::string line = firstLine;
int maxVertexTriples = 8;
// Include data from first line if it contains numeric fields (after header)
auto consumeVertexCard = [&](const std::string &card) {
std::string_view sv(card);
for (int i=0;i<6;++i) { // up to 6 fields (i.e., 2 triples per card typical)
double val = ComGeomReader::fieldDouble(sv, 10 + i*10, 10);
// Heuristic: consider any numeric (including 0) as part of vertex stream until a face card appears
vertexDoubles.push_back(static_cast<fastf_t>(val));
if ((int)vertexDoubles.size()/3 >= maxVertexTriples) break;
}
};
// First card may have header only; attempt to parse numeric fields
consumeVertexCard(line);
// Read continuation cards until face card or limit reached
while ((int)vertexDoubles.size()/3 < maxVertexTriples) {
auto next = m_r.next("arb card");
if (!next) break;
if (isLikelyFaceCard(next->s)) {
line = next->s;
break;
}
consumeVertexCard(next->s);
}
int vertexCount = static_cast<int>(vertexDoubles.size()/3);
if (vertexCount < 4) return {SolidParseResult::Error, "arb v0 insufficient vertices"};
// Build point array
std::vector<fastf_t> verts(vertexCount*3);
std::copy(vertexDoubles.begin(), vertexDoubles.begin()+vertexCount*3, verts.begin());
// Read face cards (up to 6 faces)
std::vector<std::vector<int>> faces;
while (true) {
if (!isLikelyFaceCard(line)) {
auto peek = m_r.next("arb maybe face");
if (!peek) break;
line = peek->s;
if (!isLikelyFaceCard(line)) break;
}
std::string_view sv(line);
std::vector<int> f;
for (int i=0;i<4;++i) {
int v=0;
if (isIntegerField(sv, 10 + i*10, 10, v) && v != 0) {
f.push_back(v);
}
}
if (!f.empty()) faces.push_back(f);
auto next = m_r.next("arb face next");
if (!next) break;
if (!isLikelyFaceCard(next->s)) {
line = next->s; // pass to main dispatcher for next solid
break;
}
line = next->s;
if ((int)faces.size() >= 6) break;
}
// Validate face indices
for (const auto &f : faces) {
for (int idx : f) {
int absIdx = std::abs(idx);
if (absIdx < 1 || absIdx > vertexCount)
return {SolidParseResult::Error, "arb face index out of range"};
}
}
// Convert based on vertex count
SolidParseResult result;
if (vertexCount == 4) {
result = handle_arb4(name, verts);
} else if (vertexCount == 5) {
result = handle_arb5(name, verts);
} else if (vertexCount == 6) {
result = handle_arb6(name, verts);
} else if (vertexCount == 7) {
result = handle_arb7(name, verts);
} else if (vertexCount == 8) {
result = handle_arb8(name, verts);
} else {
return {SolidParseResult::Error, "arb v0 unsupported vertex count"};
}
if (result.status != SolidParseResult::Ok) {
// Fallback to ARBN if vertex-based conversion fails
std::fprintf(stderr, "arb v0 vertex conversion failed, attempting arbn fallback\n");
// Build plane equations from faces using indices (if enough faces)
if (faces.size() >= 4) {
// Simple plane extraction: pick triple for each face (first three indices)
std::vector<plane_t> eqn(faces.size());
for (size_t i=0;i<faces.size();++i) {
int a = std::abs(faces[i][0]) - 1;
int b = std::abs(faces[i][1]) - 1;
int c = std::abs(faces[i][2]) - 1;
if (a<0||b<0||c<0 || a>=vertexCount||b>=vertexCount||c>=vertexCount) continue;
plane_t pl;
point_t pa, pb, pc;
VMOVE(pa, &verts[a*3]);
VMOVE(pb, &verts[b*3]);
VMOVE(pc, &verts[c*3]);
struct bn_tol tol;
tol.magic = BN_TOL_MAGIC;
tol.dist = 0.0005;
tol.dist_sq = tol.dist * tol.dist;
tol.perp = 1e-6;
tol.para = 1 - tol.perp;
if (bg_make_plane_3pnts(pl, pa, pb, pc, &tol) == 0) {
VMOVE(eqn[i], pl);
}
}
int ret = mk_arbn(m_out, name.c_str(), (int)faces.size(), eqn.data());
if (ret < 0) return {SolidParseResult::Error, "arbn fallback failed"};
return {SolidParseResult::Ok, ""};
}
}
return result;
}
SolidParseResult SolidParser::handle_arb4(const std::string &name, const std::vector<fastf_t> &dd4) {
if (dd4.size() < 12) return {SolidParseResult::Error, "arb4 needs 12 values"};
int ret = mk_arb4(m_out, name.c_str(), dd4.data());
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb4 failed" : ""};
}
SolidParseResult SolidParser::handle_arb5(const std::string &name, const std::vector<fastf_t> &dd5) {
if (dd5.size() < 15) return {SolidParseResult::Error, "arb5 needs 15 values"};
std::vector<fastf_t> dd_full(24);
std::copy(dd5.begin(), dd5.begin()+15, dd_full.begin());
const fastf_t *v4 = &dd5[12];
for (int rep=5; rep<=7; ++rep) std::copy(v4, v4+3, dd_full.begin()+rep*3);
int ret = mk_arb8(m_out, name.c_str(), dd_full.data());
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(arb5) failed" : ""};
}
SolidParseResult SolidParser::handle_arb6(const std::string &name, const std::vector<fastf_t> &dd6) {
if (dd6.size() < 18) return {SolidParseResult::Error, "arb6 needs 18 values"};
std::vector<fastf_t> dd_full(24);
std::copy(dd6.begin(), dd6.begin()+18, dd_full.begin());
const fastf_t *D4=&dd6[12];
const fastf_t *D5=&dd6[15];
std::copy(D4, D4+3, dd_full.begin()+5*3); // D(5)=D(4)
std::copy(D5, D5+3, dd_full.begin()+6*3); // D(6)=original D(5)
std::copy(D5, D5+3, dd_full.begin()+7*3); // D(7)=original D(5)
int ret = mk_arb8(m_out, name.c_str(), dd_full.data());
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(arb6) failed" : ""};
}
SolidParseResult SolidParser::handle_arb7(const std::string &name, const std::vector<fastf_t> &dd7) {
if (dd7.size() < 21) return {SolidParseResult::Error, "arb7 needs 21 values"};
std::vector<fastf_t> dd_full(24);
std::copy(dd7.begin(), dd7.begin()+21, dd_full.begin());
const fastf_t *D4=&dd7[12];
std::copy(D4, D4+3, dd_full.begin()+7*3); // D(7)=D(4)
int ret = mk_arb8(m_out, name.c_str(), dd_full.data());
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(arb7) failed" : ""};
}
SolidParseResult SolidParser::handle_arb8(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 24) return {SolidParseResult::Error, "arb8 needs 24 values"};
int ret = mk_arb8(m_out, name.c_str(), dd.data());
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8 failed" : ""};
}
SolidParseResult SolidParser::handle_rcc(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 7) return {SolidParseResult::Error, "rcc needs 7 values"};
int ret = mk_rcc(m_out, name.c_str(), dd.data(), dd.data()+3, dd[6]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_rcc failed" : ""};
}
SolidParseResult SolidParser::handle_rec(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 12) return {SolidParseResult::Error, "rec needs 12 values"};
int ret = mk_tgc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd.data()+9, dd.data()+6, dd.data()+9);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_tgc(rec) failed" : ""};
}
SolidParseResult SolidParser::handle_trc(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 8) return {SolidParseResult::Error, "trc needs 8 values"};
int ret = mk_trc_h(m_out, name.c_str(), dd.data(), dd.data()+3, dd[6], dd[7]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_trc_h failed" : ""};
}
SolidParseResult SolidParser::handle_tec(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 13) return {SolidParseResult::Error, "tec needs 13 values"};
double p = dd[12];
if (NEAR_ZERO(p, SMALL)) return {SolidParseResult::Error, "tec p cannot be zero"};
vect_t topA, topB;
double invp = 1.0/p;
VSCALE(topA, dd.data()+6, invp);
VSCALE(topB, dd.data()+9, invp);
int ret = mk_tgc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd.data()+9, topA, topB);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_tgc(tec) failed" : ""};
}
SolidParseResult SolidParser::handle_tgc(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 14) return {SolidParseResult::Error, "tgc needs 14 values"};
vect_t topA, topB;
double magBaseA = MAGNITUDE(dd.data()+6);
double magBaseB = MAGNITUDE(dd.data()+9);
if (magBaseA < SMALL || magBaseB < SMALL) return {SolidParseResult::Error,"tgc zero-length A or B"};
VSCALE(topA, dd.data()+6, dd[12]/magBaseA);
VSCALE(topB, dd.data()+9, dd[13]/magBaseB);
int ret = mk_tgc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd.data()+9, topA, topB);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_tgc failed" : ""};
}
SolidParseResult SolidParser::handle_sph(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 4) return {SolidParseResult::Error, "sph needs 4 values"};
int ret = mk_sph(m_out, name.c_str(), dd.data(), dd[3]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_sph failed" : ""};
}
SolidParseResult SolidParser::handle_wir(const std::string &name, const std::string &firstLine) {
int numpts = ComGeomReader::fieldInt(firstLine, 8, 2);
if (numpts <= 0) return {SolidParseResult::Error, "wir invalid point count"};
int total = numpts*3 + 1;
std::vector<fastf_t> raw;
if (!getSolData(raw, total, m_solWork, true, firstLine))
return {SolidParseResult::Error, "wir data read failure"};
if ((int)raw.size() < total) return {SolidParseResult::Error, "wir insufficient data"};
double radius = raw.back();
if (radius <= 0.0) return {SolidParseResult::Error, "wir invalid radius"};
double diameter = radius * 2.0;
struct bu_list head;
BU_LIST_INIT(&head);
for (int i=0;i<numpts;++i) {
struct wdb_pipe_pnt *ps;
BU_ALLOC(ps, struct wdb_pipe_pnt);
ps->l.magic = WDB_PIPESEG_MAGIC;
VMOVE(ps->pp_coord, &raw[i*3]);
ps->pp_id = 0;
ps->pp_od = diameter;
ps->pp_bendradius = diameter;
BU_LIST_INSERT(&head, &ps->l);
}
int ret = mk_pipe(m_out, name.c_str(), &head);
mk_pipe_free(&head);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_pipe failed" : ""};
}
SolidParseResult SolidParser::handle_rpc(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 10) return {SolidParseResult::Error, "rpc needs 10 values"};
int ret = mk_rpc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_rpc failed" : ""};
}
SolidParseResult SolidParser::handle_rhc(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 11) return {SolidParseResult::Error, "rhc needs 11 values"};
int ret = mk_rhc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9], dd[10]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_rhc failed" : ""};
}
SolidParseResult SolidParser::handle_epa(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 11) return {SolidParseResult::Error, "epa needs 11 values"};
int ret = mk_epa(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9], dd[10]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_epa failed" : ""};
}
SolidParseResult SolidParser::handle_ehy(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 12) return {SolidParseResult::Error, "ehy needs 12 values"};
int ret = mk_ehy(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9], dd[10], dd[11]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_ehy failed" : ""};
}
SolidParseResult SolidParser::handle_eto(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 11) return {SolidParseResult::Error, "eto needs 11 values"};
int ret = mk_eto(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9], dd[10]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_eto failed" : ""};
}
SolidParseResult SolidParser::handle_v4_ell_transform(const std::string &name, const std::vector<fastf_t> &foci) {
if (foci.size() < 7) return {SolidParseResult::Error, "v4 ell needs 7 values"};
vect_t F1,F2; VMOVE(F1,&foci[0]); VMOVE(F2,&foci[3]);
double L = foci[6];
vect_t V; VADD2SCALE(V,F1,F2,0.5);
vect_t work; VSUB2(work,F2,F1);
double m1 = MAGNITUDE(work);
if (m1 < SMALL) return {SolidParseResult::Error,"degenerate ell"};
double a_scale = (L*0.5)/m1;
vect_t baseA; VSCALE(baseA, work, a_scale);
double r = std::sqrt(MAGSQ(baseA) - (m1*0.5)*(m1*0.5));
if (!(r>0.0)) return {SolidParseResult::Error,"invalid ell radius"};
vect_t Vp{V[0]+M_PI, V[1]+M_PI, V[2]+M_PI};
vect_t B; VCROSS(B,Vp,baseA); double mb=MAGNITUDE(B); if (mb<SMALL) return {SolidParseResult::Error,"degenerate B"};
VSCALE(B,B,r/mb);
vect_t C; VCROSS(C,baseA,B); double mc=MAGNITUDE(C); if (mc<SMALL) return {SolidParseResult::Error,"degenerate C"};
VSCALE(C,C,r/mc);
int ret = mk_ell(m_out, name.c_str(), V, baseA, B, C);
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_ell failed":""};
}
SolidParseResult SolidParser::handle_ell(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 7) return {SolidParseResult::Error, "ell needs 7 values"};
vect_t V, A; VMOVE(V,&dd[0]); VMOVE(A,&dd[3]);
double r = dd[6]; if (r <= 0.0) return {SolidParseResult::Error,"invalid ell radius"};
vect_t Vp{V[0]+M_PI,V[1]+M_PI,V[2]+M_PI};
vect_t B; VCROSS(B,Vp,A); double mb=MAGNITUDE(B); if (mb<SMALL) return {SolidParseResult::Error,"degenerate B"};
VSCALE(B,B,r/mb);
vect_t C; VCROSS(C,A,B); double mc=MAGNITUDE(C); if (mc<SMALL) return {SolidParseResult::Error,"degenerate C"};
VSCALE(C,C,r/mc);
int ret = mk_ell(m_out, name.c_str(), V, A, B, C);
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_ell failed":""};
}
SolidParseResult SolidParser::handle_ellg(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 12) return {SolidParseResult::Error, "ellg needs 12 values"};
int ret = mk_ell(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd.data()+9);
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_ell failed":""};
}
SolidParseResult SolidParser::handle_tor(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 8) return {SolidParseResult::Error, "tor needs 8 values"};
int ret = mk_tor(m_out, name.c_str(), dd.data(), dd.data()+3, dd[6], dd[7]);
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_tor failed":""};
}
SolidParseResult SolidParser::handle_haf(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 4) return {SolidParseResult::Error, "haf needs 4 values"};
vect_t N; VMOVE(N,&dd[0]);
double magN = MAGNITUDE(N);
if (magN < SMALL) return {SolidParseResult::Error,"haf normal too small"};
int ret = mk_half(m_out, name.c_str(), N, -dd[3]);
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_half failed":""};
}
SolidParseResult SolidParser::handle_arbn(const std::string &name, const std::string &firstLine) {
std::string_view sv(firstLine);
int npt = ComGeomReader::fieldInt(sv, 10 + 0*10, 10);
int npe = ComGeomReader::fieldInt(sv, 10 + 1*10, 10);
int neq = ComGeomReader::fieldInt(sv, 10 + 2*10, 10);
int nae = ComGeomReader::fieldInt(sv, 10 + 3*10, 10);
int nface = npe + neq + nae;
if (npt < 1) return {SolidParseResult::Error, "arbn requires >=1 point"};
if (nface < 1) return {SolidParseResult::Error, "arbn requires >=1 face"};
std::vector<plane_t> eqn(static_cast<size_t>(nface));
std::vector<int> used(static_cast<size_t>(nface), 0);
std::vector<fastf_t> input_points;
input_points.reserve(npt*3);
struct bn_tol tol;
tol.magic = BN_TOL_MAGIC;
tol.dist = 0.0005;
tol.dist_sq = tol.dist * tol.dist;
tol.perp = 1e-6;
tol.para = 1 - tol.perp;
{ // vertex points
std::vector<fastf_t> pts;
if (!getXSolData(pts, npt*3, m_solWork))
return {SolidParseResult::Error, "arbn vertex read failure"};
input_points = std::move(pts);
}
int cur_eq = 0;
int symm = 0;
/* Planes from triples */
for (int i=0;i<npe;i+=6) {
auto l = m_r.next("arbn vertex point indices");
if (!l) return {SolidParseResult::Error,"arbn EOF (point indices)"};
std::string_view lv(l->s);
for (int j=0;j<6 && (i+j)<npe;++j) {
int q = ComGeomReader::fieldInt(lv, 10 + j*10 + 0, 4);
int r = ComGeomReader::fieldInt(lv, 10 + j*10 + 4, 3);
int s = ComGeomReader::fieldInt(lv, 10 + j*10 + 7, 3);
if (q==0 || r==0 || s==0) continue;
point_t a,b,c;
auto fetch = [&](int idx, point_t &out){
int abs_idx = std::abs(idx)-1;
VMOVE(out, &input_points[abs_idx*3]);
if (idx < 0) { out[Y] = -out[Y]; symm = 1; }
};
fetch(q,a); fetch(r,b); fetch(s,c);
if (bg_make_plane_3pnts(eqn[cur_eq], a, b, c, &tol) < 0) {
if (m_opts.verbose) std::fprintf(stderr,"arbn degenerate 3p plane ignored\n");
continue;
}
++cur_eq;
}
}
/* Planes from equations */
for (int i=0;i<neq;++i) {
auto l = m_r.next("arbn plane equation card");
if (!l) return {SolidParseResult::Error,"arbn EOF (equation)"};
std::string_view lv(l->s);
eqn[cur_eq][X] = ComGeomReader::fieldDouble(lv, 10, 10);
eqn[cur_eq][Y] = ComGeomReader::fieldDouble(lv, 20, 10);
eqn[cur_eq][Z] = ComGeomReader::fieldDouble(lv, 30, 10);
eqn[cur_eq][W] = ComGeomReader::fieldDouble(lv, 40, 10);
double scale = MAGNITUDE(eqn[cur_eq]);
if (scale < SMALL) {
if (m_opts.verbose) std::fprintf(stderr,"arbn plane normal too small (eqn)\n");
continue;
}
scale = 1.0/scale;
VSCALE(eqn[cur_eq], eqn[cur_eq], scale);
eqn[cur_eq][W] *= scale;
++cur_eq;
}
/* Planes from az/el */
for (int i=0;i<nae;i+=2) {
auto l = m_r.next("arbn az/el card");
if (!l) return {SolidParseResult::Error,"arbn EOF (az/el)"};
std::string_view lv(l->s);
for (int j=0;j<2 && (i+j)<nae;++j) {
double az = ComGeomReader::fieldDouble(lv, 10 + j*30 + 0*10, 10) * DEG2RAD;
double el = ComGeomReader::fieldDouble(lv, 10 + j*30 + 1*10, 10) * DEG2RAD;
int vert_no = ComGeomReader::fieldInt(lv, 10 + j*30 + 2*10, 10);
if (vert_no == 0) break;
double cos_el = std::cos(el);
eqn[cur_eq][X] = std::cos(az) * cos_el;
eqn[cur_eq][Y] = std::sin(az) * cos_el;
eqn[cur_eq][Z] = std::sin(el);
point_t pt;
int abs_idx = std::abs(vert_no)-1;
VMOVE(pt, &input_points[abs_idx*3]);
if (vert_no < 0) pt[Y] = -pt[Y];
eqn[cur_eq][W] = VDOT(pt, eqn[cur_eq]);
++cur_eq;
}
}
if (cur_eq != nface)
return {SolidParseResult::Error, "arbn face count mismatch"};
point_t cent; VSETALL(cent, 0);
for (int i=0;i<npt;++i) VADD2(cent, cent, &input_points[i*3]);
VSCALE(cent, cent, 1.0/static_cast<double>(npt));
if (symm) cent[Y] = 0.0;
constexpr double DIST_TOL = 1.0e-8;
for (int i=0;i<nface;++i) {
double dist = VDOT(eqn[i], cent) - eqn[i][W];
if (dist < -DIST_TOL) continue;
if (dist > DIST_TOL) {
VREVERSE(eqn[i], eqn[i]);
eqn[i][W] = -eqn[i][W];
} else {
return {SolidParseResult::Error,"arbn centroid lies on face"};
}
}
std::vector<double> vertex;
int last_vertex = 0;
for (int i=0;i<nface-2;++i) {
for (int j=i+1;j<nface-1;++j) {
double dot = VDOT(eqn[i], eqn[j]);
if (!NEAR_ZERO(dot, 0.999999)) continue;
for (int k=j+1;k<nface;++k) {
point_t pt;
if (bg_make_pnt_3planes(pt, eqn[i], eqn[j], eqn[k]) < 0) continue;
bool outside=false;
for (int m=0;m<nface;++m) {
if (m==i||m==j||m==k) continue;
if (VDOT(pt, eqn[m]) - eqn[m][W] > DIST_TOL) { outside=true; break; }
}
if (outside) continue;
bool duplicate=false;
for (int m=0;m<last_vertex;++m) {
vect_t dv; VSUB2(dv, pt, &vertex[m*3]);
if (MAGSQ(dv) < 1.0e-10) { duplicate=true; break; }
}
if (duplicate) continue;
vertex.resize((last_vertex+1)*3);
VMOVE(&vertex[last_vertex*3], pt);
++last_vertex;
used[i]++; used[j]++; used[k]++;
}
}
}
for (int i=0;i<nface;++i)
if (used[i]==0)
return {SolidParseResult::Error,"arbn face unused (not convex)"};
int ret = mk_arbn(m_out, name.c_str(), nface, eqn.data());
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_arbn failed":""};
}
/* ---------------- Dispatcher ---------------- */
SolidParseResult SolidParser::parseNext() {
if (m_solWork >= m_solTotal && m_solTotal > 0) {
return {SolidParseResult::Eof, ""};
}
auto l = m_r.next("solid card");
if (!l) return {SolidParseResult::Eof, ""};
int solidNum = 0;
std::string type, firstLine = l->s;
if (!parseSolidHeader(l->s, solidNum, type, firstLine)) {
if (m_solWork >= m_solTotal) return {SolidParseResult::Eof, ""};
return {SolidParseResult::Error, "invalid solid header"};
}
// v1 end sentinel handled in region code; in solids, "end" terminates
if (type == "end") {
return {SolidParseResult::Eof, ""};
}
if (m_opts.version == 5) {
int expectedNext = m_solWork + 1;
if (solidNum != expectedNext) {
// proceed but warn
std::fprintf(stderr,
"warning: solid numbering mismatch: expected %d, got %d - consuming this solid to maintain alignment\n",
expectedNext, solidNum);
}
}
// Always advance processed count; names use sequential counter
m_solWork++;
std::string name = makeSolidName(m_solWork);
colPrint(name);
std::vector<fastf_t> dd;
if (type == "ars") return handle_ars(name, firstLine);
if (type == "rpp") { if(!getSolData(dd,6,solidNum,true,firstLine)) return {SolidParseResult::Error,"rpp data read"}; return handle_rpp(name,dd); }
if (type == "box") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"box data read"}; return handle_box(name,dd); }
if (type == "raw" || type == "wed") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"raw/wed data read"}; return handle_raw_wed(name,dd); }
if (type == "rvw") { if(!getSolData(dd,7,solidNum,true,firstLine)) return {SolidParseResult::Error,"rvw data read"}; return handle_rvw(name,dd); }
if (type == "arw") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"arw data read"}; return handle_arw(name,dd); }
if (m_opts.version == 0 && type == "arb") return handle_arb_magic(name, firstLine);
if (type == "arb4") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb4 data read"}; return handle_arb4(name,dd); }
if (type == "arb5") { if(!getSolData(dd,15,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb5 data read"}; return handle_arb5(name,dd); }
if (type == "arb6") { if(!getSolData(dd,18,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb6 data read"}; return handle_arb6(name,dd); }
if (type == "arb7") { if(!getSolData(dd,21,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb7 data read"}; return handle_arb7(name,dd); }
if (type == "arb8") { if(!getSolData(dd,24,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb8 data read"}; return handle_arb8(name,dd); }
if (type == "rcc") { if(!getSolData(dd,7,solidNum,true,firstLine)) return {SolidParseResult::Error,"rcc data read"}; return handle_rcc(name,dd); }
if (type == "rec") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"rec data read"}; return handle_rec(name,dd); }
if (type == "trc") { if(!getSolData(dd,8,solidNum,true,firstLine)) return {SolidParseResult::Error,"trc data read"}; return handle_trc(name,dd); }
if (type == "tec") { if(!getSolData(dd,13,solidNum,true,firstLine)) return {SolidParseResult::Error,"tec data read"}; return handle_tec(name,dd); }
if (type == "tgc") { if(!getSolData(dd,14,solidNum,true,firstLine)) return {SolidParseResult::Error,"tgc data read"}; return handle_tgc(name,dd); }
if (type == "sph") { if(!getSolData(dd,4,solidNum,true,firstLine)) return {SolidParseResult::Error,"sph data read"}; return handle_sph(name,dd); }
if (type.rfind("wir",0)==0) return handle_wir(name, firstLine);
if (type == "rpc") { if(!getSolData(dd,10,solidNum,true,firstLine)) return {SolidParseResult::Error,"rpc data read"}; return handle_rpc(name,dd); }
if (type == "rhc") { if(!getSolData(dd,11,solidNum,true,firstLine)) return {SolidParseResult::Error,"rhc data read"}; return handle_rhc(name,dd); }
if (type == "epa") { if(!getSolData(dd,11,solidNum,true,firstLine)) return {SolidParseResult::Error,"epa data read"}; return handle_epa(name,dd); }
if (type == "ehy") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"ehy data read"}; return handle_ehy(name,dd); }
if (type == "eto") { if(!getSolData(dd,11,solidNum,true,firstLine)) return {SolidParseResult::Error,"eto data read"}; return handle_eto(name,dd); }
if (type == "ellg") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"ellg data read"}; return handle_ellg(name,dd); }
if (type == "ell" || type == "ell1") {
if (m_opts.version <=4 && type=="ell") {
if (!getSolData(dd,7,solidNum,true,firstLine)) return {SolidParseResult::Error,"v4 ell data read"};
return handle_v4_ell_transform(name, dd);
} else {
if (!getSolData(dd,7,solidNum,true,firstLine)) return {SolidParseResult::Error,"ell data read"};
return handle_ell(name, dd);
}
}
if (type == "tor") { if(!getSolData(dd,8,solidNum,true,firstLine)) return {SolidParseResult::Error,"tor data read"}; return handle_tor(name,dd); }
if (type == "haf") { if(!getSolData(dd,4,solidNum,true,firstLine)) return {SolidParseResult::Error,"haf data read"}; return handle_haf(name,dd); }
if (type == "arbn") return handle_arbn(name, firstLine);
std::fprintf(stderr, "getsolid: unsupported type '%s'\n", type.c_str());
return {SolidParseResult::Error, "unsupported solid"};
}
+93
View File
@@ -0,0 +1,93 @@
#ifndef BRLCAD_SOLID_PARSER_H
#define BRLCAD_SOLID_PARSER_H
#include "common.h"
#include <string>
#include <vector>
#include <optional>
extern "C" {
#include "vmath.h"
#include "raytrace.h"
#include "wdb.h"
#include "bg.h"
}
#include "ComGeomReader.h"
struct SolidParseResult {
enum Status { Ok, Eof, Error } status = Error;
std::string message;
};
struct SolidParserOptions {
int version = 5;
int verbose = 0;
std::string suffix;
bool legacy_trunc = false;
};
class SolidParser {
public:
SolidParser(ComGeomReader &reader, rt_wdb *outfp, const SolidParserOptions &opts);
SolidParseResult parseNext();
int solidsExpected() const { return m_solTotal; }
int solidsProcessed() const { return m_solWork; }
void setSolidsExpected(int c) { m_solTotal = c; }
private:
std::string makeSolidName(int n) const;
void colPrint(const std::string &s);
bool parseSolidHeader(const std::string &line, int &solidNum,
std::string &typeLower, std::string &lineOutForData);
bool getSolData(std::vector<fastf_t> &data, int num, int solidNum,
bool firstCardAlreadyRead, std::string lineBuf);
bool getXSolData(std::vector<fastf_t> &data, int num, int solidNum);
/* Helpers */
bool isIntegerField(std::string_view sv, size_t start, size_t len, int &value) const;
bool isLikelyFaceCard(const std::string &line) const;
/* Primitive handlers */
SolidParseResult handle_ars(const std::string &name, const std::string &firstLine);
SolidParseResult handle_rpp(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_box(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_raw_wed(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_rvw(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_arw(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_arb_magic(const std::string &name, const std::string &firstLine);
SolidParseResult handle_arb4(const std::string &name, const std::vector<fastf_t> &dd4);
SolidParseResult handle_arb5(const std::string &name, const std::vector<fastf_t> &dd5);
SolidParseResult handle_arb6(const std::string &name, const std::vector<fastf_t> &dd6);
SolidParseResult handle_arb7(const std::string &name, const std::vector<fastf_t> &dd7);
SolidParseResult handle_arb8(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_rcc(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_rec(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_trc(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_tec(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_tgc(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_sph(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_wir(const std::string &name, const std::string &firstLine);
SolidParseResult handle_rpc(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_rhc(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_epa(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_ehy(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_eto(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_v4_ell_transform(const std::string &name, const std::vector<fastf_t> &foci);
SolidParseResult handle_ell(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_ellg(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_tor(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_haf(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_arbn(const std::string &name, const std::string &firstLine);
private:
ComGeomReader &m_r;
rt_wdb *m_out = nullptr;
SolidParserOptions m_opts;
int m_solTotal = 0;
int m_solWork = 0;
size_t m_colCur = 0;
};
#endif
@@ -0,0 +1,376 @@
================================================================================
BRL-CAD BoT → COMGEOM ARBN Export Pipeline
RLM (Robust Low-Poly Meshing Inspired) + HACD/V-HACD Convex Decomposition
================================================================================
Purpose:
--------
Provide a clear, implementation-oriented blueprint for converting arbitrary
triangle meshes (BOTs) into a set of convex COMGEOM ARBN solids suitable for
region assembly. This document is intended both for human developers and as
context for future GPT-assisted sessions.
Context:
--------
COMGEOM does not have a native arbitrary triangle mesh primitive. ARBN
(primitives defined by intersection of half-spaces / planes) can represent
convex polyhedra only. Arbitrary non-convex BOT geometry must be decomposed
into convex parts, each described as an ARBN. To reduce complexity and noise,
we first simplify and planarize the mesh with an RLM-inspired stage, then
decompose using HACD/V-HACD, and finally produce ARBNs.
High-Level Pipeline Stages:
---------------------------
1. Mesh Acquisition & Validation
2. RLM-Inspired Simplification (Planar Clustering & Polygon Extraction)
3. Optional Primitive Recognition (Planes, Cylinders, Cones) (future enhancement)
4. Convex Decomposition (HACD/V-HACD or fallback hull partitioning)
5. Convex Part → Plane Set Conversion
6. ARBN Construction via Incremental Half-Space Clipping
7. Region Assembly (OR union of convex ARBN solids)
8. Quality Assurance (QA) & Reporting
9. Output COMGEOM Deck (version selectable: v1 / v4 / v5)
10. Optional JSON summary and statistics
Key Design Goals:
-----------------
- Produce clean, planar-faced convex solids minimizing plane count.
- Heal small cracks, near-coplanar face fragmentation, and sliver triangles.
- Provide tunable tolerances for clustering, plane fitting, snapping, and decomposition.
- Maintain sharp features (edges with large dihedral angles).
- Scale to large meshes (hundreds of thousands of triangles) via segmentation.
Terminology:
------------
BOT : BRL-CAD triangle mesh primitive.
RLM-inspired : Robust Low-Poly Meshing style operations (plane clustering, feature retention).
HACD/V-HACD : Hierarchical Approximate Convex Decomposition algorithms.
ARBN : COMGEOM arbitrary N-plane convex solid.
Plane cluster : Group of triangles forming an approximate planar patch.
Convex part : Output of HACD or grouping guaranteed to be convex.
Clipper : Incremental half-space intersection algorithm producing ARBN vertices.
Region : COMGEOM Boolean combination (union/intersection/subtraction) of solids.
Assumptions:
------------
- BOT input is either closed and manifold or can be repaired (hole fill / thickening).
- Decomposition tools are available (HACD or V-HACD library; otherwise fallback convex hull).
- Numeric tolerances are chosen relative to model scale (bbox diagonal).
- Complexity controls (max planes per ARBN, max clusters, max convex parts) prevent runaway resource usage.
Environment / Flags (Proposed):
-------------------------------
--simplify=none|rlm : Enable RLM-stage planar clustering.
--rlm-angle=deg : Angle threshold for normal clustering (default 2.0).
--rlm-dist=scale : Distance tolerance for plane residual (default 1e-3 * bbox_diag).
--rlm-snap=scale : Vertex snap tolerance (default 5e-4 * bbox_diag).
--rlm-sharp=deg : Dihedral angle above which edges are preserved (default 30).
--rlm-min-area=scale^2 : Minimum polygon area; discard degenerates.
--hacd=on|off : Enable HACD decomposition stage.
--hacd-concavity=value : HACD concavity parameter.
--hacd-max-parts=N : Max number of convex parts.
--hacd-min-volume=scale^3 : Minimum part volume threshold.
--decomp-order=pre|post : RLM before or after HACD (default pre).
--max-planes-per-arbn=N : Safety cap (default 512).
--arbn-hash=on|off : Spatial hash for vertex deduplication (default on).
--arbn-stats=path : Stats dump file.
--export-version=1|4|5 : COMGEOM dialect (default 5).
--json=path : Emit JSON export report.
--material-map=file.json : Attribute enrichment mapping.
--color-map=file.json : Region color mapping.
Stage 1: Mesh Acquisition & Validation
--------------------------------------
Inputs: BOT primitive (triangle list).
Steps:
1. Load triangles; compute bounding box.
2. Verify manifold conditions:
- Each edge referenced by 1 or 2 faces.
- No zero-area triangles.
3. If open surface and user requests solid export:
- Either abort or apply thickening (offset + stitch) – optional future feature.
4. Deduplicate vertices (hash by quantized position).
5. Compute per-face normals and edge dihedral angles.
Outputs:
- Clean triangle list, adjacency (half-edge or edge-to-face map).
- BBox, scale metrics.
Stage 2: RLM-Inspired Simplification
------------------------------------
Goal: Merge nearly coplanar triangles into planar clusters.
Algorithm Sketch:
1. Seed clusters: each triangle forms initial cluster with its plane.
2. Normal clustering: merge cluster pairs if angle between planes < rlm-angle AND distance between centroids projects within rlm-dist residual.
3. Plane fitting: For each cluster, fit plane via PCA/least squares to vertex set. Recompute residuals.
4. Split clusters where residual > rlm-dist or non-manifold boundaries arise.
5. Extract boundary polygon:
- Collect boundary edges (adjacent triangle only once).
- Project boundary vertices to plane.
- Compute 2D convex (or general boundary) hull; remove interior points.
6. Vertex snapping:
- Project vertices onto fitted plane.
- Merge vertices within rlm-snap tolerance.
7. Sharp edges retention:
- Do not merge clusters across edges with dihedral > rlm-sharp.
8. Degeneracy filtering:
- Discard polygons with area < rlm-min-area or internal angle < min_angle (~5°).
9. Optional: Triangulate polygon if required by downstream (for HACD only if non-convex planar patch).
Outputs:
- Reduced set of planar patches (clusters) with cleaned boundary polygons.
- Mapping from original faces to cluster.
Stage 3: Optional Primitive Recognition (Future Enhancement)
------------------------------------------------------------
Idea: Detect canonical primitives (planes, cylinders, spheres, cones) before clustering to reduce complexity:
- RANSAC or multi-pass fitting.
- Replace large planar clusters with a BOX or RPP if axis-aligned.
- Defer for initial implementation.
Stage 4: Convex Decomposition (HACD/V-HACD)
-------------------------------------------
If mesh (post-RLM) remains non-convex:
1. Run HACD on simplified mesh.
- Inputs: tolerance (concavity), max parts, min volume.
2. Obtain convex parts (triangle subsets).
3. For each convex part:
- Optionally re-run a localized RLM stage again (Mode: post-hacd).
4. If HACD disabled:
- Option hull-only: compute global convex hull (loss of concavity details).
- Reject or warn if non-convex and no decomposition path chosen.
Outputs:
- Collection of convex parts (each either a polygonal shell).
Stage 5: Convex Part → Plane Set Conversion
-------------------------------------------
For each convex part:
1. Compute convex hull of part (even if already convex; double-check).
2. Extract hull faces; each face yields a plane (normal outward, distance).
3. Normalize planes; cluster near-duplicates (angle & offset tolerance).
4. Verify face consistency (no inverted normals; positive volume).
5. Cap plane count; if > max-planes-per-arbn → either split, relax tolerance, or fallback to legacy enumeration (warning).
Outputs:
- Plane list per convex part.
Stage 6: ARBN Construction
--------------------------
Use incremental half-space clipping (implemented arbn_clip):
1. Seed bounding polyhedron from plane offsets (derived radius).
2. For each plane in part:
- Clip polyhedron (spatial hash for vertex reuse).
- Abort if volume becomes zero (log error; fallback to hull triangulation?).
3. Gather final vertex set and face loops.
4. Record stats (input planes, duplicates removed, final face/vertex count).
Outputs:
- ARBN solid definition: plane equations only (COMGEOM stores planes; vertex enumeration implicit).
- Optional internal triangulation only for QA (not stored in deck).
Stage 7: Region Assembly
------------------------
Single region for entire original BOT:
- Boolean semantics: Region = OR of all ARBN solids derived from decomposition.
- If decomposition yields components with distinct attributes (e.g., materials), create multiple regions (each corresponding to an attribute class).
- For COMGEOM v1: multiple cards with OR semantics; for v4/v5: parenthesized expression: ( +S1 ) OR ( +S2 ) OR ...
Stage 8: QA & Validation
------------------------
Checks:
1. Volume > 0 and matches (within tolerance) original mesh volume (approx. via voxel sampling or Monte Carlo).
2. Surface area near original (after simplification) minus sliver removal.
3. Watertightness: ray shoot tests (random directions) see consistent in/out transitions.
4. Plane count sanity (no explosion).
5. Overlaps: sampled points belong to exactly one convex part unless intended union.
Stage 9: Output COMGEOM Deck
----------------------------
For each ARBN:
- Solid table entries listing plane equations (v1 fixed columns or v5 token).
Region table:
- Union of solids, optional attributes.
Ident table:
- Region codes, space codes, materials, LOS (v5 attribute lines or numeric fields).
Attribute enhancement (v5):
- material=, color=R/G/B, aircode=, density= if available.
Stage 10: Reporting
-------------------
JSON report keys:
{
"input": { "triangles": N, "bbox": [...], "scale": S },
"rlm": { "clusters": C, "merged_faces": F, "avg_residual": R },
"hacd": { "enabled": true, "parts": P, "concavity": X },
"arbn": { "solids": S, "avg_planes": A, "max_planes": M },
"region": { "count": Rg, "boolean": "OR" },
"stats": { "warnings": W, "errors": E },
"timing": { "rlm_ms": t1, "hacd_ms": t2, "arbn_ms": t3 }
}
Parameter Tuning Guidelines:
----------------------------
angle_tol (cluster) : 1–2 degrees (start small; larger increases merging).
distance_tol (plane fit) : 1e-3 × bbox_diag (reduce for high precision parts).
snap_tol (vertex merge) : 5e-4 × bbox_diag (balance healing vs distortion).
sharp_threshold : 30 degrees (increase to preserve more edges).
hacd_concavity : 0.002–0.01 typical; lower → more parts, higher → fewer.
max_planes_per_arbn : 256–512 recommended safety.
min_face_area : 1e-10 × bbox_diag^2 (filter numerical artifacts).
min_part_volume : 1e-9 × bbox_diag^3 (discard tiny fragments).
Error Handling Strategy:
------------------------
- Non-manifold input → attempt repair; if fails, abort with message.
- Excessive plane count → split cluster or relax clustering thresholds.
- Zero-volume ARBN creation → fallback to convex hull or mark as skipped.
- HACD failure → fallback to hull-only mode with warning.
- Tolerance misconfiguration → auto-suggest adjustments in JSON report.
Performance Considerations:
---------------------------
- Spatial hashing for vertex dedup in ARBN clipper (O(1) average insert/find).
- Parallelization possibilities:
- RLM clustering per spatial block / octree node.
- HACD usually internal multi-threading (depending on library).
- ARBN construction per convex part in parallel threads.
- Memory caps: early discard of clusters under min_face_area prevents growth.
- Streaming: flush solids incrementally to output files.
Security / Robustness Notes:
----------------------------
- Validate all user-supplied tolerances; clamp unreasonable values.
- Protect against extremely large mesh inputs by early bounding checks.
- Consistent use of relative tolerances tied to model scale to avoid numeric collapse.
Interfaces & Extensibility:
---------------------------
Future flags:
--primitive-detect=on/off : Pre-pass for cylinders/cones/spheres.
--thicken-open-mesh=scale : Convert open surfaces to solids.
--exact-predicates=on/off : Introduce robust orientation tests.
--parallel=N : Explicit thread count.
Data Structures (Minimal Outline):
----------------------------------
struct RLMCluster {
vect_t plane_normal;
double plane_offset;
double rms_error;
size_t *face_indices;
size_t face_count;
int sharp_boundary; /* flag */
point_t *boundary_vertices;
size_t boundary_count;
};
struct ConvexPart {
size_t *triangle_indices;
size_t tri_count;
plane_t *planes;
size_t plane_count;
};
struct ARBNSolid {
plane_t *planes;
size_t plane_count;
/* optional stats */
};
Pseudo-Code Summary:
--------------------
function export_bot_to_comgeom(bot, params):
triangles = load(bot)
validate(triangles)
if params.simplify == RLM:
clusters = rlm_cluster(triangles, params)
triangles = rebuild_from_clusters(clusters) // optional
parts = (params.hacd ? hacd_decompose(triangles, params) : [triangles])
solids = []
for part in parts:
planes = extract_planes(part, params)
arbn = build_arbn(planes, params)
if arbn.valid: solids.append(arbn)
region = build_region_union(solids)
write_comgeom(solids, region, params)
write_report(params, stats)
Potential Failure Scenarios & Responses:
----------------------------------------
1. Input mesh massively non-manifold:
- Response: abort early; suggest repair tool invocation.
2. RLM merges too aggressively (loss of moderate detail):
- Response: output warning; advise lower angle_tol.
3. HACD produces thousands of tiny parts:
- Response: raise concavity tolerance; filter by min_part_volume.
4. ARBN plane overflow:
- Response: split part along largest variance axis; recursively process.
5. Output region expression too large:
- Response: chunk region definitions or group solids logically by type.
Testing Strategy:
-----------------
Unit:
- Plane fitting accuracy (synthetic coplanar sets).
- Clustering stability under noise.
- ARBN clipper invariants (volume positive, faces planar).
Integration:
- BOT sample models (simple, moderate, complex).
- Comparison of original vs simplified volumes (Monte Carlo sampling).
- Performance benchmarking (timings for each stage).
Regression:
- Fuzz small random perturbations in vertex positions; ensure pipeline stable.
- Large mesh stress (e.g., 250k triangles) within acceptable memory/time.
JSON Report Example:
--------------------
{
"input": { "triangles": 50234, "bbox": [ [0,0,0], [10,5,7] ], "scale": 12.247 },
"rlm": { "clusters": 340, "merged_faces": 48000, "avg_residual": 0.00072 },
"hacd": { "enabled": true, "parts": 128, "concavity": 0.005 },
"arbn": { "solids": 128, "avg_planes": 18, "max_planes": 42 },
"region": { "count": 1, "boolean": "OR" },
"stats": { "warnings": 3, "errors": 0 },
"timing": { "rlm_ms": 1834, "hacd_ms": 2975, "arbn_ms": 622 }
}
Open Questions (For Future Iteration):
--------------------------------------
- Should we incorporate curved primitive detection before RLM to reduce plane counts further?
- Acceptable loss thresholds for feature simplification (volume delta %, area delta %)?
- Deterministic mode (fixed random seeds) for reproducible CI outputs?
- Support for per-material segmentation feeding separate region definitions?
Recommended Initial Implementation Order:
----------------------------------------
Phase 1: BOT validation + adjacency + dihedral computation
Phase 2: Basic RLM clustering (angle + residual) + boundary extraction
Phase 3: HACD integration (API wrapper) + parameter mapping
Phase 4: Plane extraction + ARBN clipper integration
Phase 5: Region assembly + COMGEOM writing (v5 first)
Phase 6: JSON reporting + stats instrumentation
Phase 7: Tuning + advanced cluster splitting + QA benchmarks
Phase 8: Optional primitive recognition and improved heuristics
Risk Summary:
-------------
- Over-simplification: mitigated by exposing user parameters and reporting cluster stats.
- Performance bottlenecks: spatial hashing, early culling, parallel cluster operations.
- Numerical instability: per-stage tolerance scaling; failover to hull approximations.
- Large memory footprints: streaming cluster processing and releasing intermediate buffers.
References (Conceptual Inspirations):
-------------------------------------
- Robust Low-Poly Meshing (RLM) paper (site referenced).
- HACD / V-HACD algorithms (hierarchical approximate convex decomposition).
- Plane clustering & PCA fitting (standard computational geometry).
- Incremental half-space intersection for convex polyhedra.
- Shewchuk robust predicates (optional future exact arithmetic upgrade).
- RANSAC-based primitive detection (future enhancements).
End of Document
================================================================================
+122
View File
@@ -0,0 +1,122 @@
#include "common.h"
#include <string>
#include <vector>
#include <cstdio>
#include <cstdlib>
extern "C" {
#include "bu/app.h"
#include "bu/opt.h"
#include "bu/vls.h"
}
#include "ComGeomConverter.h"
/* Custom arg processor to copy a string argument into a std::string */
static int
opt_set_string(struct bu_vls *msg, size_t argc, const char **argv, void *set_var)
{
BU_OPT_CHECK_ARGV0(msg, argc, argv, "string");
if (!set_var) return -1;
std::string *sp = static_cast<std::string *>(set_var);
*sp = argv[0];
return 1;
}
struct CliOptions {
ConverterOptions conv;
int show_help = 0;
int legacy_truncate_names = 0;
std::vector<std::string> positionals;
};
static void usage(const char *argv0, const struct bu_opt_desc *opt_defs)
{
std::fprintf(stderr,
"Usage: %s [options] input_file output_file\n\n", argv0);
char *help = bu_opt_describe(opt_defs, NULL);
if (help) {
std::fprintf(stderr, "%s\n", help);
bu_free(help, "help string");
}
std::fprintf(stderr,
"\nExamples:\n"
" %s model.cg model.g # convert using defaults (version 5)\n"
" %s -v 0 in_magic.cg out.g # convert MAGIC-era v0 deck\n"
" %s -v 4 -s _cg4 in.cg out.g # convert v4, append suffix to names\n"
" %s -t -s _longsuffix in.cg out.g # legacy suffix truncation (13 chars)\n"
" %s -d 2 input.cg out.g # verbose debug level 2\n",
argv0, argv0, argv0, argv0, argv0);
}
int main(int argc, const char **argv)
{
bu_setprogname(argv[0]);
CliOptions co;
struct bu_opt_desc opt_defs[7];
BU_OPT(opt_defs[0], "h", "help", "", NULL, &co.show_help, "Show help and exit.");
BU_OPT(opt_defs[1], "v", "version", "N", &bu_opt_int, &co.conv.version, "Set COMGEOM input version (0,1,4,5; default 5).");
BU_OPT(opt_defs[2], "d", "debug", "N", &bu_opt_int, &co.conv.verbose, "Set verbosity/debug level (integer).");
BU_OPT(opt_defs[3], "s", "suffix", "STR", opt_set_string, &co.conv.suffix, "Append name suffix to all generated objects.");
BU_OPT(opt_defs[4], "V", "verbose", "", &bu_opt_incr_long,&co.conv.verbose, "Increment verbosity (may be repeated).");
BU_OPT(opt_defs[5], "t", "legacy-truncate-names", "", NULL, &co.legacy_truncate_names, "Enable legacy name truncation (suffix max 13 chars).");
BU_OPT_NULL(opt_defs[6]);
std::vector<const char *> argvec;
argvec.reserve(static_cast<size_t>(argc - 1));
for (int i = 1; i < argc; ++i) argvec.push_back(argv[i]);
struct bu_vls msgs = BU_VLS_INIT_ZERO;
int ret = bu_opt_parse(&msgs, argvec.size(), argvec.data(), opt_defs);
if (ret < 0) {
if (BU_VLS_IS_INITIALIZED(&msgs) && bu_vls_strlen(&msgs) > 0)
std::fprintf(stderr, "%s", bu_vls_addr(&msgs));
usage(argv[0], opt_defs);
bu_vls_free(&msgs);
return 1;
}
size_t unused = static_cast<size_t>(ret);
for (size_t i = 0; i < unused; ++i)
co.positionals.emplace_back(argvec[i]);
if (co.show_help) {
usage(argv[0], opt_defs);
bu_vls_free(&msgs);
return 0;
}
if (co.positionals.size() != 2) {
std::fprintf(stderr,
"Error: expected 2 positional arguments (input_file output_file), got %zu.\n",
co.positionals.size());
usage(argv[0], opt_defs);
bu_vls_free(&msgs);
return 1;
}
if (co.conv.version != 0 && co.conv.version != 1 && co.conv.version != 4 && co.conv.version != 5) {
std::fprintf(stderr, "Error: unsupported COMGEOM version %d (valid: 0,1,4,5).\n",
co.conv.version);
usage(argv[0], opt_defs);
bu_vls_free(&msgs);
return 1;
}
if (BU_VLS_IS_INITIALIZED(&msgs) && bu_vls_strlen(&msgs) > 0 && co.conv.verbose)
std::fprintf(stderr, "%s", bu_vls_addr(&msgs));
bu_vls_free(&msgs);
const std::string inputPath = co.positionals[0];
const std::string outputPath = co.positionals[1];
co.conv.legacy_truncate_names = (co.legacy_truncate_names != 0);
ComGeomConverter converter(inputPath, outputPath, co.conv);
return converter.run();
}
+1 -1
View File
@@ -752,7 +752,7 @@ tessellate_part(struct creo_conv_info *cinfo, ProMdl model, struct bu_vls **snam
}
/* Check solidity */
success = !bg_trimesh_solid((int)vert_tree->curr_vert, (size_t)faces.size() / 3,
success = bg_trimesh_solid((int)vert_tree->curr_vert, (size_t)faces.size() / 3,
vert_tree->the_array, &faces[0], NULL);
/* If it's not solid and we're testing solidity, keep trying... */
+18 -30
View File
@@ -23,20 +23,10 @@
#include "InformationGatherer.h"
#include "bu/log.h"
// flush results, if we have any, then exit
static void reportAndExit(const std::string &message, const std::string &result = "")
{
if (!result.empty())
bu_log("%s%s", result.c_str(), result.back() == '\n' ? "" : "\n");
bu_exit(BRLCAD_ERROR, "%s", message.c_str());
}
std::string getCmdPath(std::string exeDir, const char* cmd) {
char buf[MAXPATHLEN] = {0};
if (!bu_dir(buf, MAXPATHLEN, exeDir.c_str(), cmd, BU_DIR_EXT, NULL))
reportAndExit("Couldn't find " + std::string(cmd) + ", aborting.\n");
bu_exit(BRLCAD_ERROR, "Couldn't find %s, aborting.\n", cmd);
return std::string(buf);
}
@@ -68,13 +58,13 @@ getSurfaceArea(Options* opt, std::map<std::string, std::string> UNUSED(map), std
bu_process_create(&p, rtarea_av, BU_PROCESS_HIDE_WINDOW | BU_PROCESS_OUT_EQ_ERR);
if (bu_process_pid(p) <= 0) {
reportAndExit("Problem with getSurfaceArea, aborting\n");
bu_exit(BRLCAD_ERROR, "Problem with getSurfaceArea, aborting\n");
}
char buffer[128];
std::string result;
std::string result = "";
int read_cnt = 0;
while ((read_cnt = bu_process_read_n(p, BU_PROCESS_STDOUT, sizeof(buffer) - 1, buffer)) > 0) {
while ((read_cnt = bu_process_read_n(p, BU_PROCESS_STDOUT, 128-1, buffer)) > 0) {
/* NOTE: read does not ensure null-termination, thus buffersize-1 */
buffer[read_cnt] = '\0';
result += buffer;
@@ -90,7 +80,7 @@ getSurfaceArea(Options* opt, std::map<std::string, std::string> UNUSED(map), std
try {
surfArea += stod(result);
} catch (const std::invalid_argument& ia) {
reportAndExit("Invalid argument for getSurfaceArea. Got (" + std::string(ia.what()) + "), aborting.\n", result);
bu_exit(BRLCAD_ERROR, "Invalid argument for getSurfaceArea. Got (%s), aborting.\n", ia.what());
}
}
}
@@ -210,7 +200,7 @@ getVerificationData(struct ged* UNUSED(g), Options* opt, std::map<std::string, s
struct bu_process* p;
int read_cnt = 0;
char buffer[1024] = {0};
std::string result;
std::string result = "";
std::string ncpu(std::to_string(opt->getNCPU()));
std::string dFile(opt->getDensityFile());
@@ -243,23 +233,21 @@ getVerificationData(struct ged* UNUSED(g), Options* opt, std::map<std::string, s
bu_process_create(&p, const_cast<const char**>(gqa_av_vec.data()), BU_PROCESS_HIDE_WINDOW | BU_PROCESS_OUT_EQ_ERR);
if (bu_process_pid(p) <= 0) {
reportAndExit("Problem in getVerificationData gqa process creation, aborting\n");
bu_exit(BRLCAD_ERROR, "Problem in getVerificationData gqa process creation, aborting\n");
}
while ((read_cnt = bu_process_read_n(p, BU_PROCESS_STDOUT, sizeof(buffer) - 1, buffer)) > 0) {
result = "";
while ((read_cnt = bu_process_read_n(p, BU_PROCESS_STDOUT, 1024-1, buffer)) > 0) {
buffer[read_cnt] = '\0';
result += buffer;
}
const int gqa_status = bu_process_wait_n(&p, 0);
// sanity - we should have clean status code and not the "no density found" message
const bool no_density_data = result.find(no_density_msg) != std::string::npos;
if (gqa_status != 0 && (!no_density_data || !dFile.empty())) {
reportAndExit("Problem collecting gqa volume and mass, aborting\n", result);
if (bu_process_wait_n(&p, 0) != 0) {
bu_exit(BRLCAD_ERROR, "Problem collecting gqa volume and mass, aborting\n");
}
if (!no_density_data) {
// make sure we did not get 'no density file' error message
if (result.find(no_density_msg) == std::string::npos) {
// NOTE: tmp_val is zeroed at each parseLabeledNumber call
if (parseLabeledNumber(tmp_val, result, volume_label))
volume += tmp_val;
@@ -275,17 +263,17 @@ getVerificationData(struct ged* UNUSED(g), Options* opt, std::map<std::string, s
bu_process_create(&p, const_cast<const char**>(gqa_av_vec.data()), BU_PROCESS_HIDE_WINDOW | BU_PROCESS_OUT_EQ_ERR);
if (bu_process_pid(p) <= 0) {
reportAndExit("Problem in getVerificationData gqa process creation, aborting\n");
bu_exit(BRLCAD_ERROR, "Problem in getVerificationData gqa process creation, aborting\n");
}
result.clear();
while ((read_cnt = bu_process_read_n(p, BU_PROCESS_STDOUT, sizeof(buffer) - 1, buffer)) > 0) {
result = "";
while ((read_cnt = bu_process_read_n(p, BU_PROCESS_STDOUT, 1024-1, buffer)) > 0) {
buffer[read_cnt] = '\0';
result += buffer;
}
if (bu_process_wait_n(&p, 0) != 0) {
reportAndExit("Problem collecting gqa volume, aborting\n", result);
bu_exit(BRLCAD_ERROR, "Problem collecting gqa volume, aborting\n");
}
if (parseLabeledNumber(tmp_val, result, volume_label))
@@ -393,7 +381,7 @@ InformationGatherer::getMainComp()
ged_exec_exists(g, 2, cmd);
std::string res = bu_vls_addr(g->ged_result_str);
if (res != "1") {
reportAndExit("Could not find component (" + topcomp + "), aborting.\n");
bu_exit(BRLCAD_ERROR, "Could not find component (%s), aborting.\n", topname);
}
int entities = getNumEntities(opt->getTopComp());
-3
View File
@@ -34,9 +34,6 @@
// OpenCV header files
#include <opencv2/opencv.hpp>
#if CV_VERSION_MAJOR >= 5
#include <opencv2/geometry.hpp>
#endif
#include <opencv2/core/core.hpp>
#include "opencv2/highgui/highgui.hpp"
+2 -3
View File
@@ -43,7 +43,6 @@ main(int argc, char *argv[])
int i, j;
int db_index;
int c;
int ret;
const char **av;
struct ged *gedp;
@@ -104,14 +103,14 @@ main(int argc, char *argv[])
bu_exit(1, usage, argv[0]);
}
ret = ged_exec_gqa(gedp, j, av);
(void)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, "av");
return ret;
return 0;
}
+1 -2
View File
@@ -29,7 +29,6 @@ set(
api.c
centroid.c
check_options.c
contour.cpp
density.cpp
globals.c
inside.c
@@ -69,7 +68,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};${EIGEN3_INCLUDE_DIR}"
brlcad_addlib(libanalyze "${LIBANALYZE_SOURCES}" "" "${CMAKE_CURRENT_BINARY_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
-4
View File
@@ -1,6 +1,3 @@
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)
@@ -16,7 +13,6 @@ brlcad_add_test(NAME analyze_densities_std COMMAND analyze_densities std)
cmakefiles(
CMakeLists.txt
arbs.g
contour.cpp
raydiff.g
)
-425
View File
@@ -1,425 +0,0 @@
/* 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
*/
+4 -4
View File
@@ -113,10 +113,10 @@ bg_lseg_clip(fastf_t *xp1, fastf_t *yp1, fastf_t *xp2, fastf_t *yp2, fastf_t cli
int
bg_ray_vclip(point_t a, point_t b, const fastf_t *min_pt, const fastf_t *max_pt)
{
vect_t diff;
double sv;
double st;
double mindist, maxdist;
static vect_t diff;
static double sv;
static double st;
static double mindist, maxdist;
fastf_t *pt = &a[0];
fastf_t *dir = &diff[0];
int i;
+2 -2
View File
@@ -22,7 +22,7 @@
/** @file libbg/pca.cpp
*
* @brief
* Principal Component Analysis.
* Principle Component Analysis.
*
*/
@@ -74,7 +74,7 @@ bg_pca(point_t *c, vect_t *xa, vect_t *ya, vect_t *za, size_t npnts, const point
}
// 3. Perform SVD
Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeFullU);
Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeThinU);
// 4. Extract the vectors from the U matrix
vect_t xaxis, yaxis, zaxis;
+73 -44
View File
@@ -2620,50 +2620,20 @@ bg_lseg3_lseg3_parallel(const point_t sg1pt1, const point_t sg1pt2,
}
extern "C" int
bg_plane_pt_nrml(plane_t *p, const point_t pt, const vect_t nrml)
bg_plane_pt_nrml(plane_t *p, point_t pt, vect_t nrml)
{
vect_t normal;
fastf_t normal_length;
if (!p || !pt || !nrml)
if (!p)
return -1;
VMOVE(normal, nrml);
normal_length = MAGNITUDE(normal);
if (normal_length <= SMALL_FASTF)
return -1;
VSCALE(normal, normal, 1.0 / normal_length);
(*p)[X] = normal[X];
(*p)[Y] = normal[Y];
(*p)[Z] = normal[Z];
(*p)[H] = VDOT(normal, pt);
vect_t unrml;
VMOVE(unrml, nrml);
VUNITIZE(nrml);
(*p)[0] = unrml[0];
(*p)[1] = unrml[1];
(*p)[2] = unrml[2];
(*p)[3] = unrml[0]*pt[0] + unrml[1]*pt[1] + unrml[2]*pt[2];
return 0;
}
static int
plane_coordinate_frame(point_t origin, vect_t xaxis, vect_t yaxis, const plane_t *p)
{
vect_t normal;
fastf_t normal_length;
VSET(normal, (*p)[X], (*p)[Y], (*p)[Z]);
normal_length = MAGNITUDE(normal);
if (normal_length <= SMALL_FASTF)
return -1;
VSCALE(normal, normal, 1.0 / normal_length);
VSCALE(origin, normal, (*p)[H] / normal_length);
bn_vec_perp(xaxis, normal);
VUNITIZE(xaxis);
VCROSS(yaxis, normal, xaxis);
VUNITIZE(yaxis);
return 0;
}
// Use PCA fit a plane to vertex points
extern "C" int
bg_fit_plane(point_t *c, vect_t *n, size_t npnts, point_t *pnts)
@@ -2694,11 +2664,40 @@ bg_plane_closest_pt(fastf_t *u, fastf_t *v, plane_t *p, point_t *pt)
return -1;
point_t origin;
vect_t xaxis, yaxis;
vect_t xaxis, yaxis, zaxis;
VSET(zaxis, (*p)[0], (*p)[1], (*p)[2]);
fastf_t x, y, z, d;
x = fabs((*p)[0]);
y = fabs((*p)[1]);
z = fabs((*p)[2]);
d = 0.0;
if ( y >= x && y >= z ) {
d = x;
x = y;
y = d;
} else if ( z >= x && z >= y ) {
d = x;
x = z;
z = d;
}
if ( x > SMALL_FASTF ) {
y /= x;
z /= x;
d = x*sqrt(1.0 + y*y + z*z);
} else if ( x > 0.0 && x < MAX_FASTF ) {
d = x;
}
if (plane_coordinate_frame(origin, xaxis, yaxis, p))
if (d < 0 || NEAR_ZERO(d, SMALL_FASTF))
return -1;
VMOVE(origin, zaxis);
VSCALE(origin, origin, 1/d*(*p)[3]);
VUNITIZE(zaxis);
bn_vec_perp(xaxis, zaxis);
VUNITIZE(xaxis);
VCROSS(yaxis, zaxis, xaxis);
VUNITIZE(yaxis);
vect_t vc;
VSUB2(vc, *pt, origin);
@@ -2712,15 +2711,45 @@ bg_plane_closest_pt(fastf_t *u, fastf_t *v, plane_t *p, point_t *pt)
extern "C" int
bg_plane_pt_at(point_t *pt, plane_t *p, fastf_t u, fastf_t v)
{
if (!pt || !p)
if (!pt)
return -1;
point_t origin;
vect_t xaxis, yaxis;
vect_t xaxis, yaxis, zaxis;
VSET(zaxis, (*p)[0], (*p)[1], (*p)[2]);
fastf_t x, y, z, d;
x = fabs((*p)[0]);
y = fabs((*p)[1]);
z = fabs((*p)[2]);
d = 0.0;
if ( y >= x && y >= z ) {
d = x;
x = y;
y = d;
} else if ( z >= x && z >= y ) {
d = x;
x = z;
z = d;
}
if ( x > SMALL_FASTF ) {
y /= x;
z /= x;
d = x*sqrt(1.0 + y*y + z*z);
} else if ( x > 0.0 && x < MAX_FASTF ) {
d = x;
}
if (plane_coordinate_frame(origin, xaxis, yaxis, p))
if (d < 0 || NEAR_ZERO(d, SMALL_FASTF))
return -1;
VMOVE(origin, zaxis);
VSCALE(origin, origin, 1/d*(*p)[3]);
VUNITIZE(zaxis);
bn_vec_perp(xaxis, zaxis);
VUNITIZE(xaxis);
VCROSS(yaxis, zaxis, xaxis);
VUNITIZE(yaxis);
VSCALE(xaxis, xaxis, u);
VSCALE(yaxis, yaxis, v);
VADD2(origin, origin, xaxis);
+72 -51
View File
@@ -83,7 +83,7 @@ bg_polygon_view_bbox(point2d_t *bmin, point2d_t *bmax, struct bg_polygon *p, mat
// contours. ONLY considering positive contour points.
for (size_t i = 0; i < p->num_contours; i++) {
struct bg_poly_contour *c = &p->contour[i];
if ((p->hole && p->hole[i]) || !c->num_points)
if (!c->num_points)
continue;
for (size_t j = 0; j < c->num_points; j++) {
point_t vpoint;
@@ -143,45 +143,64 @@ bg_3d_polygon_area(fastf_t *area, size_t npts, const point_t *pts)
int
bg_3d_polygon_centroid(point_t *cent, size_t npts, const point_t *pts)
{
point_t centroid = VINIT_ZERO;
vect_t normal = VINIT_ZERO;
vect_t edge0, edge1, cross;
fastf_t signed_area = 0.0;
size_t i;
fastf_t x_0 = 0.0;
fastf_t x_1 = 0.0;
fastf_t y_0 = 0.0;
fastf_t y_1 = 0.0;
fastf_t z_0 = 0.0;
fastf_t z_1 = 0.0;
fastf_t a = 0.0;
fastf_t signedArea = 0.0;
if (!pts || !cent || npts < 3)
return 1;
for (size_t i = 1; i + 1 < npts; i++) {
VSUB2(edge0, pts[i], pts[0]);
VSUB2(edge1, pts[i + 1], pts[0]);
VCROSS(cross, edge0, edge1);
VADD2(normal, normal, cross);
/* Calculate Centroid projection for face for x-y-plane */
for (i = 0; i < npts-1; i++) {
x_0 = pts[i][0];
y_0 = pts[i][1];
x_1 = pts[i+1][0];
y_1 = pts[i+1][1];
a = x_0 *y_1 - x_1*y_0;
signedArea += a;
*cent[0] += (x_0 + x_1)*a;
*cent[1] += (y_0 + y_1)*a;
}
if (MAGNITUDE(normal) <= SMALL_FASTF)
return 1;
VUNITIZE(normal);
x_0 = pts[i][0];
y_0 = pts[i][1];
x_1 = pts[0][0];
y_1 = pts[0][1];
a = x_0 *y_1 - x_1*y_0;
signedArea += a;
*cent[0] += (x_0 + x_1)*a;
*cent[1] += (y_0 + y_1)*a;
for (size_t i = 1; i + 1 < npts; i++) {
point_t triangle_centroid;
fastf_t triangle_area;
signedArea *= 0.5;
*cent[0] /= (6.0*signedArea);
*cent[1] /= (6.0*signedArea);
VSUB2(edge0, pts[i], pts[0]);
VSUB2(edge1, pts[i + 1], pts[0]);
VCROSS(cross, edge0, edge1);
triangle_area = VDOT(cross, normal);
signed_area += triangle_area;
VADD2(triangle_centroid, pts[0], pts[i]);
VADD2(triangle_centroid, triangle_centroid, pts[i + 1]);
VSCALE(triangle_centroid, triangle_centroid, triangle_area / 3.0);
VADD2(centroid, centroid, triangle_centroid);
/* calculate Centroid projection for face for x-z-plane */
signedArea = 0.0;
for (i = 0; i < npts-1; i++) {
x_0 = pts[i][0];
z_0 = pts[i][2];
x_1 = pts[i+1][0];
z_1 = pts[i+1][2];
a = x_0 *z_1 - x_1*z_0;
signedArea += a;
*cent[2] += (z_0 + z_1)*a;
}
x_0 = pts[i][0];
z_0 = pts[i][2];
x_1 = pts[0][0];
z_1 = pts[0][2];
a = x_0 *z_1 - x_1*z_0;
signedArea += a;
*cent[2] += (z_0 + z_1)*a;
if (NEAR_ZERO(signed_area, SMALL_FASTF))
return 1;
VSCALE(centroid, centroid, 1.0 / signed_area);
VMOVE(*cent, centroid);
signedArea *= 0.5;
*cent[2] /= (6.0*signedArea);
return 0;
}
@@ -244,7 +263,6 @@ bg_3d_polygon_make_pnts_planes(size_t *npts, point_t **pts, size_t neqs, const p
struct sort_ccw_data {
point_t centroid;
vect_t x_axis;
vect_t y_axis;
};
@@ -254,26 +272,20 @@ static int
sort_ccw_3d(const void *left, const void *right, void *context)
{
const struct sort_ccw_data *data = (const struct sort_ccw_data *)context;
const point_t *left_point = (const point_t *)left;
const point_t *right_point = (const point_t *)right;
vect_t left_offset, right_offset;
double left_angle, right_angle;
double left_radius, right_radius;
VSUB2(left_offset, *left_point, data->centroid);
VSUB2(right_offset, *right_point, data->centroid);
left_angle = atan2(VDOT(left_offset, data->y_axis),
VDOT(left_offset, data->x_axis));
right_angle = atan2(VDOT(right_offset, data->y_axis),
VDOT(right_offset, data->x_axis));
const fastf_t *left_point = (const fastf_t *)left;
const fastf_t *right_point = (const fastf_t *)right;
double left_angle = atan2(VDOT(left_point, data->y_axis),
VDOT(left_point, data->x_axis));
double right_angle = atan2(VDOT(right_point, data->y_axis),
VDOT(right_point, data->x_axis));
if (left_angle < right_angle)
return -1;
if (left_angle > right_angle)
return 1;
left_radius = MAGSQ(left_offset);
right_radius = MAGSQ(right_offset);
double left_radius = MAGSQ(left_point);
double right_radius = MAGSQ(right_point);
if (left_radius < right_radius)
return -1;
if (left_radius > right_radius)
@@ -286,10 +298,11 @@ int
bg_3d_polygon_sort_ccw(size_t npts, point_t *pts, plane_t cmp)
{
size_t i;
point_t centroid;
vect_t normal;
struct sort_ccw_data data;
if (!pts || !cmp || npts < 3)
if (!pts || npts < 3)
return 1;
if (MAGNITUDE(cmp) < VDIVIDE_TOL)
return 1;
@@ -299,13 +312,21 @@ bg_3d_polygon_sort_ccw(size_t npts, point_t *pts, plane_t cmp)
bn_vec_ortho(data.x_axis, normal);
VCROSS(data.y_axis, normal, data.x_axis);
VSETALL(data.centroid, 0.0);
/* Angular ordering is about the polygon center, not the model origin.
* Translate temporarily so the comparator only needs the plane axes. */
VSETALL(centroid, 0.0);
for (i = 0; i < npts; i++)
VADD2(data.centroid, data.centroid, pts[i]);
VSCALE(data.centroid, data.centroid, 1.0 / (fastf_t)npts);
VADD2(centroid, centroid, pts[i]);
VSCALE(centroid, centroid, 1.0 / (double)npts);
for (i = 0; i < npts; i++)
VSUB2(pts[i], pts[i], centroid);
bu_sort(pts, npts, sizeof(point_t), sort_ccw_3d, &data);
for (i = 0; i < npts; i++)
VADD2(pts[i], pts[i], centroid);
return 0;
}
+117 -779
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,832 +19,170 @@
*/
/** @file spsr.cpp
*
* Screened Poisson surface reconstruction for fixed and adaptive oriented
* point sets.
* Interface to Adaptive Multigrid Solvers for Screened Poisson Surface Reconstruction
* Compatible with mkazhdan/PoissonRecon API (v18.x+).
*
*/
#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
# pragma GCC diagnostic push /* start new diagnostic pragma */
# pragma GCC diagnostic ignored "-Wall"
#elif defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic push /* start new diagnostic pragma */
# pragma clang diagnostic ignored "-Weverything"
#endif
#include "SPSR/Reconstructors.h"
#if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic pop
# pragma GCC diagnostic pop /* end ignoring warnings */
#elif defined(__clang__)
# pragma clang diagnostic pop
# pragma clang diagnostic pop /* end ignoring warnings */
#endif
using namespace PoissonRecon;
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;
};
// Input stream for oriented samples: implements `read` for coordinates and normals
template<typename Real, unsigned int Dim>
struct point_stream : public Reconstructor::InputOrientedSampleStream<Real, Dim>
struct PtStream : public Reconstructor::InputOrientedSampleStream<Real, Dim>
{
point_stream(const std::vector<struct bg_3d_spsr_sample> &samples)
: current(0), input(samples)
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;
}
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;
protected:
unsigned int _size, _current;
const point_t *_input_pnts;
const vect_t *_input_nrmls;
};
template<typename Index>
struct polygon_stream : public Reconstructor::OutputFaceStream<2>
// Output face stream: must implement write() and size()
template <typename Index>
struct PolygonStream : public Reconstructor::OutputFaceStream<2>
{
polygon_stream(std::vector<std::vector<Index>> &output) : polygons(output)
{
}
size_t size(void) const
{
return polygons.size();
}
PolygonStream(std::vector<std::vector<Index>> &polygonStream) : _polygons(polygonStream) {}
size_t size(void) const { return _polygons.size(); }
size_t write(const std::vector<node_index_type> &polygon)
{
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;
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;
}
private:
std::vector<std::vector<Index>> &polygons;
protected:
std::vector<std::vector<Index>> &_polygons;
};
template<typename Real, unsigned int Dim>
struct vertex_stream : public Reconstructor::OutputLevelSetVertexStream<Real, Dim>
// Output vertex stream: must implement write() and size()
template <typename Real, unsigned int Dim>
struct VertexStream : public Reconstructor::OutputLevelSetVertexStream<Real, Dim>
{
vertex_stream(std::vector<Real> &output) : coordinates(output)
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 &)
{
for (unsigned int d = 0; d < Dim; d++) _vCoordinates.push_back(p[d]);
return _vCoordinates.size() / Dim - 1;
}
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;
protected:
std::vector<Real> &_vCoordinates;
};
static double
elapsed_seconds(const clock_type::time_point &start)
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)
{
return std::chrono::duration<double>(clock_type::now() - start).count();
}
using Real = fastf_t;
static const unsigned int FEMSig = FEMDegreeAndBType<Reconstructor::Poisson::DefaultFEMDegree, Reconstructor::Poisson::DefaultFEMBoundary>::Signature;
using FEMSigs = IsotropicUIntPack<3, FEMSig>;
static bool
finite_vector(const fastf_t *vector)
{
return std::isfinite(vector[X]) && std::isfinite(vector[Y]) &&
std::isfinite(vector[Z]);
}
// Set up multithreading
PoissonRecon::ThreadPool::ParallelizationType = PoissonRecon::ThreadPool::ASYNC;
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;
// 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;
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;
Reconstructor::LevelSetExtractionParameters extractionParams;
extractionParams.forceManifold = true;
extractionParams.linearFit = false;
extractionParams.polygonMesh = false;
extractionParams.verbose = false;
return options.nonManifold >= 0 && options.nonManifold <= 1 &&
options.linearFit >= 0 && options.linearFit <= 1 &&
options.exact >= 0 && options.exact <= 1;
}
PtStream<Real, 3> vstream(input_points_3d, input_normals_3d, num_input_pnts);
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;
using Implicit = Reconstructor::Implicit<Real, 3, FEMSigs>;
using Solver = Reconstructor::Poisson::Solver<Real, 3, FEMSigs>;
Implicit *implicit = Solver::Solve(vstream, solverParams);
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)
{
if (!implicit) {
bu_log("PoissonRecon: Solver::Solve failed\n");
return -1;
}
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);
std::vector<Real> vCoordinates;
PolygonStream<int> pStream(polygons);
VertexStream<Real, 3> vStream(vCoordinates);
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;
implicit->extractLevelSet(vStream, pStream, extractionParams);
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);
}
*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];
}
return true;
delete implicit;
return 0;
}
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
*/
// 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
+1 -26
View File
@@ -33,10 +33,6 @@ 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)
@@ -146,7 +142,7 @@ brlcad_add_test(NAME bg_plane_mk_plane_3pts_succeed COMMAND bg_test pla
#
# A-D are plane equation parameters, and P is the test point.
brlcad_add_test(NAME bg_plane_pt_project_1 COMMAND bg_test plane_pt 6 0,0,1,1 2,2,1)
brlcad_add_test(NAME bg_plane_pt_project_2 COMMAND bg_test plane_pt 6 0.737397,0.461098,0.493593,236.3 77.526016919831704,185.674010580054585,189.465070961146)
brlcad_add_test(NAME bg_plane_pt_project_2 COMMAND bg_test plane_pt 6 0.737397,0.461098,0.493593,236.3 77.526016919831704,185.674010580054585,189.465011325663767)
# For function #7 (bg_coplanar_pts), the <args> format is as follows:
#
@@ -310,24 +306,7 @@ brlcad_addexec(bg_line_distance line_distance.c "${BG_TEST_LIBS}" TEST)
brlcad_add_test(NAME bg_line_distance COMMAND bg_line_distance)
# ************ plane coordinate tests ***********
brlcad_addexec(bg_plane_coordinates plane_coordinates.c "${BG_TEST_LIBS}" TEST)
brlcad_add_test(NAME bg_plane_coordinates COMMAND bg_plane_coordinates)
# ************ pca.c tests ***********
brlcad_addexec(bg_pca pca.c "${BG_TEST_LIBS}" TEST)
brlcad_add_test(NAME bg_pca COMMAND bg_pca)
# ************ polygon.c tests ***********
brlcad_addexec(bg_polygon polygon.c "${BG_TEST_LIBS}" TEST)
brlcad_add_test(NAME bg_polygon COMMAND bg_polygon)
brlcad_addexec(bg_polygon_triangulate polygon_triangulate.c "${BG_TEST_LIBS}" TEST)
brlcad_add_test(NAME bg_polygon_triangulate COMMAND bg_polygon_triangulate)
@@ -469,10 +448,6 @@ brlcad_addexec(bg_trimesh_area trimesh_area.c "${BG_TEST_LIBS}" TEST)
#BRLCAD_ADD_TEST(NAME bg_trimesh_area COMMAND bg_trimesh_area)
brlcad_addexec(bg_trimesh_solid trimesh_solid.c "${BG_TEST_LIBS}" TEST)
brlcad_add_test(NAME bg_trimesh_solid COMMAND bg_trimesh_solid)
# ************ trimesh_repair tests ***********
brlcad_addexec(bg_trimesh_repair trimesh_repair.cpp "${BG_TEST_LIBS}" TEST)
-70
View File
@@ -1,70 +0,0 @@
/* P C A . 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.
*/
#include "common.h"
#include "bn.h"
#include "bu.h"
#include "bg.h"
static int
orthonormal_axes(const vect_t xaxis, const vect_t yaxis, const vect_t zaxis)
{
return !NEAR_EQUAL(MAGSQ(xaxis), 1.0, BN_TOL_DIST)
|| !NEAR_EQUAL(MAGSQ(yaxis), 1.0, BN_TOL_DIST)
|| !NEAR_EQUAL(MAGSQ(zaxis), 1.0, BN_TOL_DIST)
|| !NEAR_ZERO(VDOT(xaxis, yaxis), BN_TOL_DIST)
|| !NEAR_ZERO(VDOT(xaxis, zaxis), BN_TOL_DIST)
|| !NEAR_ZERO(VDOT(yaxis, zaxis), BN_TOL_DIST);
}
int
main(int argc, char **argv)
{
const point_t one_point[] = {{1.0, 2.0, 3.0}};
const point_t two_points[] = {{1.0, 2.0, 3.0}, {5.0, 2.0, 3.0}};
const point_t one_center = {1.0, 2.0, 3.0};
const point_t two_center = {3.0, 2.0, 3.0};
const vect_t xaxis_expected = {1.0, 0.0, 0.0};
point_t center;
vect_t xaxis, yaxis, zaxis;
fastf_t alignment;
int failed = 0;
bu_setprogname(argv[0]);
if (argc != 1)
return 1;
if (bg_pca(&center, &xaxis, &yaxis, &zaxis, 1, one_point) != BRLCAD_OK ||
!VNEAR_EQUAL(center, one_center, BN_TOL_DIST) ||
orthonormal_axes(xaxis, yaxis, zaxis)) {
failed = 1;
}
if (bg_pca(&center, &xaxis, &yaxis, &zaxis, 2, two_points) != BRLCAD_OK ||
!VNEAR_EQUAL(center, two_center, BN_TOL_DIST) ||
orthonormal_axes(xaxis, yaxis, zaxis)) {
failed = 1;
}
alignment = VDOT(xaxis, xaxis_expected);
if (!NEAR_EQUAL(alignment * alignment, 1.0, BN_TOL_DIST))
failed = 1;
return failed;
}
-69
View File
@@ -1,69 +0,0 @@
/* P L A N E _ C O O R D I N A T E S . 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.
*/
#include "common.h"
#include "bn.h"
#include "bu.h"
#include "bg.h"
int
main(int argc, char **argv)
{
const point_t plane_point = {0.0, 0.0, 3.0};
const vect_t original_normal = {0.0, 0.0, 2.0};
const vect_t expected_normal = {0.0, 0.0, 1.0};
vect_t normal;
vect_t zero_normal = VINIT_ZERO;
plane_t plane;
plane_t unnormalized_plane = {0.0, 0.0, 2.0, 6.0};
point_t query = {2.0, 2.0, 3.0};
point_t off_plane_query = {2.0, 2.0, 5.0};
const point_t expected_projection = {2.0, 2.0, 3.0};
point_t reconstructed = VINIT_ZERO;
fastf_t u, v;
int failed = 0;
bu_setprogname(argv[0]);
if (argc != 1)
return 1;
VMOVE(normal, original_normal);
if (bg_plane_pt_nrml(&plane, plane_point, normal) ||
!VNEAR_EQUAL(plane, expected_normal, BN_TOL_DIST) ||
!NEAR_EQUAL(plane[H], 3.0, BN_TOL_DIST) ||
!VNEAR_EQUAL(normal, original_normal, BN_TOL_DIST)) {
failed = 1;
}
if (bg_plane_pt_nrml(&plane, plane_point, zero_normal) != -1)
failed = 1;
if (bg_plane_closest_pt(&u, &v, &unnormalized_plane, &query) ||
bg_plane_pt_at(&reconstructed, &unnormalized_plane, u, v) ||
!VNEAR_EQUAL(reconstructed, query, BN_TOL_DIST)) {
failed = 1;
}
if (bg_plane_closest_pt(&u, &v, &unnormalized_plane, &off_plane_query) ||
bg_plane_pt_at(&reconstructed, &unnormalized_plane, u, v) ||
!VNEAR_EQUAL(reconstructed, expected_projection, BN_TOL_DIST)) {
failed = 1;
}
return failed;
}

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