Compare commits

..
2 Commits
59 changed files with 8862 additions and 6900 deletions
+1
View File
@@ -371,6 +371,7 @@ set(
translate.adoc
tree.adoc
unhide.adoc
unpush.adoc
units.adoc
v2m_point.adoc
vars.adoc
+140
View File
@@ -0,0 +1,140 @@
= UNPUSH(nged)
:doctype: manpage
:mansource: BRL-CAD
:manmanual: BRL-CAD MGED Commands
== NAME
unpush - infer and restore shared geometry from pushed object trees
== SYNOPSIS
[source]
----
unpush [-D] [-L] [-v] [-m rigid|similarity|affine] object [object ...]
----
[[description]]
== DESCRIPTION
The *unpush* command examines the trees rooted at the specified _objects_ and
looks for primitive shapes and combinations that differ only by a recoverable
placement transform. It can replace those copies with shared objects and move
their differing placements into combination leaf matrices. This is a
geometric inference operation: it does not require provenance or other
metadata from an earlier *push*, *xpush*, or *npush* operation.
Candidate primitives are first canonicalized into a stable local frame.
Candidates are accepted as a group only after their attributes and geometry
match within the database tolerance and the proposed replacement has been
reconstructed under every leaf matrix that will be changed. Combination and
region candidates are analyzed from the leaves upward. Their boolean tree
structure, operand order, child identities and relative matrices, region
properties, material and rendering fields, and custom attributes must agree.
Boolean operands are not reordered.
The command currently canonicalizes halfspaces, ellipsoids and spheres, tori,
elliptical tori, right parabolic and hyperbolic cylinders, elliptical
paraboloids and hyperboloids, particles, TGC and REC shapes, ARB4 through ARB8
shapes, BOT meshes, and superellipsoids. Other primitive types are reported
as unsupported and are left unchanged.
Invalid supported shapes are reported as failures and are also left unchanged.
[[modes]]
== TRANSFORM MODES
The *-m* option limits the class of transforms that may be recovered:
* *rigid* permits translation and rotation.
* *similarity* additionally permits uniform scale.
* *affine* permits primitive canonicalizers to consider nonuniform scale and
shear.
Dry-run analysis defaults to *affine*. Database writes constrain an affine
request to *similarity*, because BRL-CAD combination matrices do not accept
general shear. The report explicitly notes this constraint.
[[options]]
== OPTIONS
*-D*, *--dry-run*::
Analyze and report possible sharing without modifying the database. Running a
dry run first is strongly recommended.
*-L*, *--local*::
Restrict changed references to the selected trees. Objects still referenced
outside those trees are retained, and descendants of a selected combination
whose definition is itself exposed outside the selection are not changed.
Without *-L*, *unpush* may update matching references elsewhere in the database
so a redundant object can be removed globally.
*-m* _mode_, *--mode* _mode_::
Select *rigid*, *similarity*, or *affine* transform inference as described
above.
*-v*, *--verbosity*::
Increase reporting detail. Repeat the option to include replacement matrices
for primitive references.
*-h*, *--help*::
Print command help.
[[writes]]
== DATABASE CHANGES AND RECOVERY
Before writing, *unpush* accounts for database references and prepares every
affected object in memory. It writes new canonical primitives first, then
updates parent combinations and top-level wrappers, reads the results back, and
validates the exact rewritten leaves. Superseded objects are deleted only
after the updated reference counts show that they are unused. If an object
write or validation fails, the command restores the saved object definitions
and removes newly written canonical primitives when it is safe to do so.
Selected top-level primitive names are preserved by replacing those primitives
with one-leaf wrapper combinations. A selected top-level non-region
combination may likewise become a one-leaf combination referencing the shared
representative. Top-level regions are never nested inside wrapper regions;
such consolidation is deferred, although safe sharing below those regions may
still be restored.
The recovery process does not copy the complete database, which keeps it
practical for multi-gigabyte models. It protects against errors detected by
the running command, but it is not a crash-atomic database transaction. Keep
an external backup when modifying valuable data, particularly when storage or
system reliability is uncertain.
[[examples]]
== EXAMPLES
.Analyze an assembly without changing it.
[example]
====
mged>**unpush -D -v -m similarity vehicle**
====
.Restore sharing only within two selected assemblies.
[example]
====
mged>**unpush -L left_wheel right_wheel**
====
[[see_also]]
== SEE ALSO
*npush*(nged), *push*(nged), *xpush*(nged), *facetize*(nged)
[[author]]
== AUTHOR
BRL-CAD Team
[[bug_reports]]
== BUG REPORTS
Reports of bugs or problems should be submitted via electronic mail to devs@brlcad.org
+1
View File
@@ -444,6 +444,7 @@ Interactive MGED and GED commands. See also link:man1_index.html[Command-Line To
== U
* link:mann/unhide.html[unhide]
* link:mann/unpush.html[unpush]
* link:mann/units.html[units]
== V
+16 -3
View File
@@ -21,7 +21,7 @@
/*----------------------------------------------------------------------*/
/** @addtogroup bg_pca
*
* Principle Component Analysis
* Principal Component Analysis
*
* Calculates an XYZ coordinate system such that it aligns with the largest
* variations in the supplied data. Intuitively, it "aligns" with the
@@ -61,6 +61,9 @@
#define BG_PCA_H
#include "common.h"
#include <stddef.h>
#include "vmath.h"
#include "bg/defines.h"
@@ -69,7 +72,7 @@ __BEGIN_DECLS
/**
* @brief
* Perform a Principle Component Analysis on a set of points.
* Perform a Principal Component Analysis on a set of points.
*
* Outputs are a center point and XYZ vectors for the coordinate system.
*
@@ -86,9 +89,19 @@ __BEGIN_DECLS
*/
BG_EXPORT extern int bg_pca(point_t *c, vect_t *xaxis, vect_t *yaxis, vect_t *zaxis, size_t npnts, const point_t *pnts);
/**
* Perform PCA and also report the singular values of the centered point
* matrix, ordered to correspond to xaxis, yaxis, and zaxis. A zero singular
* value identifies a dimension in which the input point set has no extent.
* The values scale with the square root of the number of input points.
*/
BG_EXPORT extern int bg_pca_svd(point_t *c, vect_t *xaxis, vect_t *yaxis,
vect_t *zaxis, vect_t *singular_values,
size_t npnts, const point_t *pnts);
__END_DECLS
#endif /* BG_PLANE_H */
#endif /* BG_PCA_H */
/** @} */
/*
* Local Variables:
+14
View File
@@ -178,6 +178,20 @@ RT_EXPORT extern int rt_obj_make(const char *label, const point_t origin, double
*/
RT_EXPORT extern int rt_obj_xform(struct rt_db_internal *op, const mat_t mat, struct rt_db_internal *ip, int release, struct db_i *dbip);
/**
* Factor an object's placement into a canonical primitive and a matrix mapping
* that primitive back to the input geometry.
*
* The caller must pass an initialized, empty @p canonical and owns its contents
* on success. Applying @p canonical_to_input to @p canonical with ft_mat must
* reproduce geometry equivalent to @p input within @p tol.
*/
RT_EXPORT extern int rt_obj_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode);
/**
* obtain parameters for an object in libpc form
*/
+37
View File
@@ -62,6 +62,27 @@ __BEGIN_DECLS
struct rt_piecestate; /* forward declaration for ft_piece_shot / ft_piece_hitsegs */
/**
* Transformation classes a primitive canonicalizer may factor out of an
* object's stored parameters. Each successive mode permits all operations
* allowed by the preceding mode.
*/
enum rt_canonicalize_mode {
RT_CANONICALIZE_RIGID = 0,
RT_CANONICALIZE_SIMILARITY,
RT_CANONICALIZE_AFFINE
};
/**
* Return values for primitive canonicalization. Unsupported is distinct from
* an error so callers may safely skip primitives without an implementation.
*/
enum rt_canonicalize_status {
RT_CANONICALIZE_OK = 0,
RT_CANONICALIZE_ERROR,
RT_CANONICALIZE_UNSUPPORTED
};
/**
* This needs to be at the end of the raytrace.h header file, so that
* all the structure names are known. The "union record" and "struct
@@ -332,6 +353,22 @@ struct rt_functab {
int (*ft_validate)(struct bu_vls *error_msg, const struct rt_db_internal *ip, const struct bn_tol *tol);
#define RTFUNCTAB_FUNC_VALIDATE_CAST(_func) ((int (*)(struct bu_vls *, const struct rt_db_internal *, const struct bn_tol *))((void (*)(void))_func))
/**
* Factor primitive geometry into a deterministic local representation and
* a matrix mapping that representation back to the input geometry.
*
* On success, applying @p canonical_to_input to @p canonical with ft_mat
* must reproduce geometry equivalent to @p input within @p tol. The
* callback initializes and owns the contents of @p canonical. It must not
* modify @p input or access database state.
*/
int (*ft_canonicalize)(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode);
#define RTFUNCTAB_FUNC_CANONICALIZE_CAST(_func) ((int (*)(struct rt_db_internal *, mat_t, const struct rt_db_internal *, const struct bn_tol *, enum rt_canonicalize_mode))((void (*)(void))_func))
};
/**
-1
View File
@@ -34,7 +34,6 @@ model/gltf gltf glb
model/jt jt
model/obj obj
model/ply ply
model/vnd.freecad fcstd FCStd
model/shp shp
model/step stp step
model/stl stl
+8
View File
@@ -19,6 +19,13 @@ if(TARGET libged)
distclean("${CMAKE_CURRENT_BINARY_DIR}/regress-ged_push.log")
distclean("${CMAKE_CURRENT_BINARY_DIR}/ged_push.g")
brlcad_addexec(regress_unpush unpush.cpp "libged;libwdb;librt;libbg;libbu" TEST_USESDATA)
if(TARGET regress_unpush)
add_dependencies(regress_unpush ged_plugins)
endif(TARGET regress_unpush)
brlcad_add_test(NAME regress-ged_unpush COMMAND regress_unpush)
brlcad_regression_test(regress-ged_unpush "regress_unpush" TEST_DEFINED)
# Phase D regression: coplanar window-frame subtraction pattern.
# Exercises the instance-aware variant planning pass in the Manifold
# facetize pipeline (plan.cpp + _booltree_leaf_tess variant resolution).
@@ -44,6 +51,7 @@ cmakefiles(
mater.c
regdef.c
push.cpp
unpush.cpp
regress_facetize.cpp
regress_lint_raytrace.cpp
d2push_tests.g
File diff suppressed because it is too large Load Diff
+29 -3
View File
@@ -52,8 +52,9 @@
#include "bg/pca.h"
// Use SVD algorithm from Soderkvist to fit a plane to vertex points
extern "C" int
bg_pca(point_t *c, vect_t *xa, vect_t *ya, vect_t *za, size_t npnts, const point_t *pnts)
static int
bg_pca_impl(point_t *c, vect_t *xa, vect_t *ya, vect_t *za,
vect_t *singular_values, size_t npnts, const point_t *pnts)
{
if (!c || !xa || !ya || !za || npnts == 0 || !pnts)
return BRLCAD_ERROR;
@@ -74,7 +75,9 @@ 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::ComputeThinU);
// Full U supplies a complete 3D frame for one- and two-point inputs as
// well as ordinary 3D point sets.
Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeFullU);
// 4. Extract the vectors from the U matrix
vect_t xaxis, yaxis, zaxis;
@@ -93,10 +96,33 @@ bg_pca(point_t *c, vect_t *xa, vect_t *ya, vect_t *za, size_t npnts, const point
VMOVE(*xa, xaxis);
VMOVE(*ya, yaxis);
VMOVE(*za, zaxis);
if (singular_values) {
VSETALL(*singular_values, 0.0);
for (Eigen::Index i = 0; i < svd.singularValues().size() && i < 3; i++)
(*singular_values)[i] = svd.singularValues()(i);
}
return BRLCAD_OK;
}
extern "C" int
bg_pca_svd(point_t *c, vect_t *xa, vect_t *ya, vect_t *za,
vect_t *singular_values, size_t npnts, const point_t *pnts)
{
if (!singular_values)
return BRLCAD_ERROR;
return bg_pca_impl(c, xa, ya, za, singular_values, npnts, pnts);
}
extern "C" int
bg_pca(point_t *c, vect_t *xa, vect_t *ya, vect_t *za, size_t npnts, const point_t *pnts)
{
return bg_pca_impl(c, xa, ya, za, NULL, npnts, pnts);
}
/** @} */
// Local Variables:
// tab-width: 8
+4
View File
@@ -27,6 +27,9 @@ brlcad_addexec(bg_test "${CMAKE_CURRENT_BINARY_DIR}/bg_test.c" "${BG_TEST_LIBS}"
# TODO - convert this into the scheme used by other tests below
brlcad_addexec(bgtester bgtester.c "${BG_TEST_LIBS}" TEST)
brlcad_addexec(bg_pca pca.c "${BG_TEST_LIBS}" TEST)
brlcad_add_test(NAME bg_pca COMMAND bg_pca)
# ************ aabb_ray.c tests *************
brlcad_addexec(bg_aabb_ray aabb_ray.c "${BG_TEST_LIBS}" TEST)
@@ -492,6 +495,7 @@ brlcad_add_test(NAME bg_geogram_optimizer COMMAND bg_geogram_optimizer)
cmakefiles(
bg_test.c.in
geogram_optimizer.cpp
pca.c
plane_dist.c
plane_isect.c
plane_pt.c
+145
View File
@@ -0,0 +1,145 @@
/* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
#include "common.h"
#include <math.h>
#include "bu/app.h"
#include "bu/log.h"
#include "bg/pca.h"
#define PCA_TEST_EPSILON 1.0e-9
static int
near_value(fastf_t a, fastf_t b)
{
fastf_t scale = fmax(1.0, fmax(fabs(a), fabs(b)));
return fabs(a - b) <= PCA_TEST_EPSILON * scale;
}
static int
test_box(void)
{
point_t points[8];
point_t center;
vect_t xaxis;
vect_t yaxis;
vect_t zaxis;
vect_t singular_values;
size_t index = 0;
for (int xsign = -1; xsign <= 1; xsign += 2) {
for (int ysign = -1; ysign <= 1; ysign += 2) {
for (int zsign = -1; zsign <= 1; zsign += 2) {
VSET(points[index], 4.0 + 3.0 * xsign,
-2.0 + 2.0 * ysign, 7.0 + zsign);
index++;
}
}
}
if (bg_pca_svd(&center, &xaxis, &yaxis, &zaxis, &singular_values,
8, (const point_t *)points) != BRLCAD_OK) {
bu_log("box PCA failed\n");
return 1;
}
if (!near_value(center[X], 4.0) || !near_value(center[Y], -2.0) ||
!near_value(center[Z], 7.0) ||
!near_value(singular_values[X], 3.0 * sqrt(8.0)) ||
!near_value(singular_values[Y], 2.0 * sqrt(8.0)) ||
!near_value(singular_values[Z], sqrt(8.0)) ||
!near_value(MAGNITUDE(xaxis), 1.0) ||
!near_value(MAGNITUDE(yaxis), 1.0) ||
!near_value(MAGNITUDE(zaxis), 1.0) ||
!near_value(VDOT(xaxis, yaxis), 0.0) ||
!near_value(VDOT(xaxis, zaxis), 0.0) ||
!near_value(VDOT(yaxis, zaxis), 0.0)) {
bu_log("box PCA returned incorrect center, frame, or singular values\n");
return 1;
}
return 0;
}
static int
test_single_point(void)
{
const point_t point = {3.0, -5.0, 8.0};
point_t center;
vect_t xaxis;
vect_t yaxis;
vect_t zaxis;
vect_t singular_values;
if (bg_pca_svd(&center, &xaxis, &yaxis, &zaxis, &singular_values,
1, (const point_t *)&point) != BRLCAD_OK) {
bu_log("single-point PCA failed\n");
return 1;
}
if (!near_value(center[X], point[X]) || !near_value(center[Y], point[Y]) ||
!near_value(center[Z], point[Z]) || !VNEAR_ZERO(singular_values, PCA_TEST_EPSILON) ||
!near_value(MAGNITUDE(xaxis), 1.0) ||
!near_value(MAGNITUDE(yaxis), 1.0) ||
!near_value(MAGNITUDE(zaxis), 1.0)) {
bu_log("single-point PCA returned invalid output\n");
return 1;
}
return 0;
}
int
main(int UNUSED(argc), const char *argv[])
{
point_t center;
vect_t xaxis;
vect_t yaxis;
vect_t zaxis;
bu_setprogname(argv[0]);
if (test_box() || test_single_point())
return 1;
if (bg_pca_svd(&center, &xaxis, &yaxis, &zaxis, NULL, 0, NULL) != BRLCAD_ERROR) {
bu_log("invalid PCA input was not rejected\n");
return 1;
}
return 0;
}
/*
* Local Variables:
* mode: C
* tab-width: 8
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
-1
View File
@@ -189,7 +189,6 @@ brlcad_add_test(NAME bu_file COMMAND bu_test test_file)
# NOTE: these test numbers correspond with the bu_mime_t enum values in include/bu/mime.h
brlcad_add_test(NAME bu_file_mime_dxf COMMAND bu_test test_file_mime dxf 5 BU_MIME_MODEL_DXF) # tests dxf extension in model context
brlcad_add_test(NAME bu_file_mime_fcstd COMMAND bu_test test_file_mime fcstd 5 BU_MIME_MODEL_VND_FREECAD) # tests fcstd extension in model context
brlcad_add_test(NAME bu_file_mime_stp COMMAND bu_test test_file_mime stp 5 BU_MIME_MODEL_STEP) # tests stp extension in model context
brlcad_add_test(NAME bu_file_mime_png COMMAND bu_test test_file_mime png 3 BU_MIME_IMAGE_PNG) # tests png extension in image context
-1
View File
@@ -23,7 +23,6 @@ add_subdirectory(asc)
add_subdirectory(assetimport)
add_subdirectory(decimate)
add_subdirectory(fastgen4)
add_subdirectory(freecad)
add_subdirectory(gdal)
add_subdirectory(gltf)
add_subdirectory(iges)
-161
View File
@@ -1,161 +0,0 @@
# FreeCAD FCStd importer/exporter plugin for libgcv.
#
# This plugin intentionally does not bundle OpenCASCADE. It is compiled only
# when an external OpenCASCADE package, BRL-CAD's openNURBS, and zlib are
# available. FCStd ZIP archive I/O uses the local zlib-backed helper in this
# directory.
set(FREECAD_SRCS freecad_read.cpp freecad_write.cpp)
set(FREECAD_EXTRA_SRCS
fcstd_zip.cpp
fcstd_zip.h
open2open/include/open2open/brep_convert.h
open2open/include/open2open/fcstd_convert.h
open2open/include/open2open/geom_convert.h
open2open/include/open2open/mesh_convert.h
open2open/src/brep_convert.cpp
open2open/src/fcstd_convert.cpp
open2open/src/geom_convert.cpp
open2open/src/mesh_convert.cpp
)
set(_OPEN2OPEN_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/open2open")
if(NOT EXISTS "${_OPEN2OPEN_SOURCE_DIR}/include/open2open/fcstd_convert.h" OR NOT EXISTS "${_OPEN2OPEN_SOURCE_DIR}/src/fcstd_convert.cpp")
message(STATUS "gcv-freecad: local open2open source copy is incomplete - FreeCAD plugin disabled")
cmakefiles(${FREECAD_SRCS} CMakeLists.txt)
return()
endif()
set(_FREECAD_OCCT_BUILD_TREE FALSE)
if(OpenCASCADE_DIR)
get_filename_component(_FREECAD_OCCT_CONFIG_DIR "${OpenCASCADE_DIR}" ABSOLUTE)
get_filename_component(_FREECAD_OCCT_SOURCE_DIR "${_FREECAD_OCCT_CONFIG_DIR}" DIRECTORY)
if(EXISTS "${_FREECAD_OCCT_CONFIG_DIR}/OpenCASCADEConfig.cmake"
AND EXISTS "${_FREECAD_OCCT_CONFIG_DIR}/lin64/clang/lib"
AND EXISTS "${_FREECAD_OCCT_SOURCE_DIR}/src/Standard/Standard.hxx")
set(OpenCASCADE_FOUND TRUE)
set(_FREECAD_OCCT_BUILD_TREE TRUE)
set(OpenCASCADE_LIBRARY_DIR "${_FREECAD_OCCT_CONFIG_DIR}/lin64/clang/lib")
endif()
endif()
if(NOT OpenCASCADE_FOUND)
find_package(OpenCASCADE CONFIG QUIET)
if(NOT OpenCASCADE_FOUND)
find_package(OpenCASCADE QUIET)
endif()
endif()
if(NOT OpenCASCADE_FOUND)
message(STATUS "gcv-freecad: OpenCASCADE not found - FreeCAD plugin disabled")
cmakefiles(${FREECAD_SRCS} CMakeLists.txt)
return()
endif()
find_package(ZLIB REQUIRED)
if(TARGET ZLIB::ZLIB)
set(_FREECAD_ZLIB_LIB ZLIB::ZLIB)
else()
set(_FREECAD_ZLIB_LIB ${ZLIB_LIBRARIES})
endif()
brlcad_find_package(OPENNURBS REQUIRED SYSPATTERN openNURBS)
if(NOT TARGET OPENNURBS::OPENNURBS)
message(STATUS "gcv-freecad: BRL-CAD openNURBS target not found - FreeCAD plugin disabled")
cmakefiles(${FREECAD_SRCS} CMakeLists.txt)
return()
endif()
set(_FREECAD_OCCT_LIBS
TKernel
TKMath
TKG2d
TKG3d
TKGeomBase
TKBRep
TKGeomAlgo
TKTopAlgo
TKCDF
)
set(_FREECAD_OCCT_INCLUDE_DIRS ${OpenCASCADE_INCLUDE_DIR})
set(_FREECAD_OCCT_LIBRARY_DIRS ${OpenCASCADE_LIBRARY_DIR})
if(OpenCASCADE_DIR)
get_filename_component(_FREECAD_OCCT_CONFIG_DIR "${OpenCASCADE_DIR}" ABSOLUTE)
if(EXISTS "${_FREECAD_OCCT_CONFIG_DIR}/lin64/clang/lib")
list(APPEND _FREECAD_OCCT_LIBRARY_DIRS "${_FREECAD_OCCT_CONFIG_DIR}/lin64/clang/lib")
endif()
if(EXISTS "${_FREECAD_OCCT_CONFIG_DIR}/inc")
list(APPEND _FREECAD_OCCT_INCLUDE_DIRS "${_FREECAD_OCCT_CONFIG_DIR}/inc")
endif()
get_filename_component(_FREECAD_OCCT_SOURCE_DIR "${_FREECAD_OCCT_CONFIG_DIR}" DIRECTORY)
if(EXISTS "${_FREECAD_OCCT_SOURCE_DIR}/src/Standard/Standard.hxx")
file(GLOB _FREECAD_OCCT_SRC_INCLUDE_DIRS LIST_DIRECTORIES true "${_FREECAD_OCCT_SOURCE_DIR}/src/*")
list(APPEND _FREECAD_OCCT_INCLUDE_DIRS ${_FREECAD_OCCT_SRC_INCLUDE_DIRS})
endif()
endif()
list(REMOVE_DUPLICATES _FREECAD_OCCT_INCLUDE_DIRS)
list(REMOVE_DUPLICATES _FREECAD_OCCT_LIBRARY_DIRS)
set(_FREECAD_OPEN2OPEN_SRCS
"${CMAKE_CURRENT_SOURCE_DIR}/fcstd_zip.cpp"
"${_OPEN2OPEN_SOURCE_DIR}/src/geom_convert.cpp"
"${_OPEN2OPEN_SOURCE_DIR}/src/brep_convert.cpp"
"${_OPEN2OPEN_SOURCE_DIR}/src/mesh_convert.cpp"
"${_OPEN2OPEN_SOURCE_DIR}/src/fcstd_convert.cpp"
)
add_library(gcv_freecad_open2open OBJECT ${_FREECAD_OPEN2OPEN_SRCS})
target_include_directories(gcv_freecad_open2open
PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}"
"${_OPEN2OPEN_SOURCE_DIR}/include"
"${_OPEN2OPEN_SOURCE_DIR}/compat"
${_FREECAD_OCCT_INCLUDE_DIRS}
)
target_compile_definitions(gcv_freecad_open2open PUBLIC OPEN2OPEN_HAVE_LIBZIP)
target_link_libraries(gcv_freecad_open2open PUBLIC OPENNURBS::OPENNURBS ${_FREECAD_ZLIB_LIB})
target_compile_options(gcv_freecad_open2open PRIVATE $<$<CXX_COMPILER_ID:GNU,Clang>:-w>)
message(STATUS "gcv-freecad: FreeCAD plugin enabled using open2open at ${_OPEN2OPEN_SOURCE_DIR}")
gcv_plugin_library(gcv-freecad SHARED ${FREECAD_SRCS})
target_include_directories(gcv-freecad PRIVATE
$<TARGET_PROPERTY:gcv_freecad_open2open,INTERFACE_INCLUDE_DIRECTORIES>
)
target_compile_definitions(gcv-freecad PRIVATE BRLCADBUILD HAVE_CONFIG_H OPEN2OPEN_HAVE_LIBZIP)
if(_FREECAD_OCCT_LIBRARY_DIRS)
target_link_directories(gcv-freecad PRIVATE ${_FREECAD_OCCT_LIBRARY_DIRS})
endif()
target_link_libraries(gcv-freecad
libgcv
librt
libwdb
libbg
libbu
OPENNURBS::OPENNURBS
$<TARGET_OBJECTS:gcv_freecad_open2open>
${_FREECAD_OCCT_LIBS}
${_FREECAD_ZLIB_LIB}
)
validate_style(gcv-freecad "${FREECAD_SRCS}")
plugin_setup(gcv-freecad gcv)
cmakefiles(
${FREECAD_SRCS}
${FREECAD_EXTRA_SRCS}
CMakeLists.txt
)
# Local Variables:
# tab-width: 8
# mode: cmake
# indent-tabs-mode: t
# End:
# ex: shiftwidth=2 tabstop=8
-427
View File
@@ -1,427 +0,0 @@
#include "fcstd_zip.h"
#include <zlib.h>
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <map>
#include <string>
#include <vector>
struct zip_entry {
std::string name;
std::uint16_t method = 0;
std::uint32_t crc = 0;
std::uint32_t compressed_size = 0;
std::uint32_t uncompressed_size = 0;
std::uint32_t local_header_offset = 0;
std::vector<unsigned char> compressed;
std::vector<unsigned char> uncompressed;
};
struct zip {
std::string path;
bool write_mode = false;
std::map<std::string, zip_entry> entries;
};
struct zip_file {
std::vector<unsigned char> data;
std::size_t offset = 0;
};
struct zip_source {
std::vector<unsigned char> data;
};
static std::uint16_t
read_le16(const unsigned char *p)
{
return (std::uint16_t)p[0] | ((std::uint16_t)p[1] << 8);
}
static std::uint32_t
read_le32(const unsigned char *p)
{
return (std::uint32_t)p[0] |
((std::uint32_t)p[1] << 8) |
((std::uint32_t)p[2] << 16) |
((std::uint32_t)p[3] << 24);
}
static void
append_le16(std::vector<unsigned char> &out, std::uint16_t v)
{
out.push_back((unsigned char)(v & 0xff));
out.push_back((unsigned char)((v >> 8) & 0xff));
}
static void
append_le32(std::vector<unsigned char> &out, std::uint32_t v)
{
out.push_back((unsigned char)(v & 0xff));
out.push_back((unsigned char)((v >> 8) & 0xff));
out.push_back((unsigned char)((v >> 16) & 0xff));
out.push_back((unsigned char)((v >> 24) & 0xff));
}
static bool
read_file(const std::string &path, std::vector<unsigned char> &data)
{
std::ifstream in(path, std::ios::binary);
if (!in)
return false;
in.seekg(0, std::ios::end);
std::streamoff sz = in.tellg();
if (sz < 0)
return false;
in.seekg(0, std::ios::beg);
data.resize((std::size_t)sz);
if (!data.empty())
in.read((char *)data.data(), (std::streamsize)data.size());
return in.good() || in.eof();
}
static bool
inflate_raw(const std::vector<unsigned char> &input, std::uint32_t expected_size, std::vector<unsigned char> &output)
{
output.assign(expected_size, 0);
unsigned char empty_output = 0;
z_stream zs;
std::memset(&zs, 0, sizeof(zs));
zs.next_in = const_cast<Bytef *>(input.data());
zs.avail_in = (uInt)input.size();
zs.next_out = expected_size ? output.data() : &empty_output;
zs.avail_out = expected_size ? (uInt)output.size() : 1;
if (inflateInit2(&zs, -MAX_WBITS) != Z_OK)
return false;
int ret = inflate(&zs, Z_FINISH);
inflateEnd(&zs);
return ret == Z_STREAM_END && zs.total_out == expected_size;
}
static bool
deflate_raw(const std::vector<unsigned char> &input, std::vector<unsigned char> &output)
{
z_stream zs;
std::memset(&zs, 0, sizeof(zs));
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY) != Z_OK)
return false;
uLong bound = deflateBound(&zs, (uLong)input.size());
output.assign((std::size_t)bound, 0);
zs.next_in = const_cast<Bytef *>(input.data());
zs.avail_in = (uInt)input.size();
zs.next_out = output.data();
zs.avail_out = (uInt)output.size();
int ret = deflate(&zs, Z_FINISH);
if (ret != Z_STREAM_END) {
deflateEnd(&zs);
return false;
}
output.resize((std::size_t)zs.total_out);
deflateEnd(&zs);
return true;
}
static bool
parse_zip(zip_t *archive)
{
std::vector<unsigned char> file_data;
if (!read_file(archive->path, file_data) || file_data.size() < 22)
return false;
std::size_t scan_start = file_data.size() > 66000 ? file_data.size() - 66000 : 0;
std::size_t eocd = std::string::npos;
for (std::size_t i = file_data.size() - 22; i + 1 > scan_start; --i) {
if (read_le32(&file_data[i]) == 0x06054b50u) {
eocd = i;
break;
}
if (i == 0)
break;
}
if (eocd == std::string::npos)
return false;
std::uint16_t total_entries = read_le16(&file_data[eocd + 10]);
std::uint32_t cd_size = read_le32(&file_data[eocd + 12]);
std::uint32_t cd_offset = read_le32(&file_data[eocd + 16]);
if ((std::uint64_t)cd_offset + cd_size > file_data.size())
return false;
std::size_t pos = cd_offset;
for (std::uint16_t i = 0; i < total_entries; ++i) {
if (pos + 46 > file_data.size() || read_le32(&file_data[pos]) != 0x02014b50u)
return false;
std::uint16_t flags = read_le16(&file_data[pos + 8]);
std::uint16_t method = read_le16(&file_data[pos + 10]);
std::uint32_t crc = read_le32(&file_data[pos + 16]);
std::uint32_t csize = read_le32(&file_data[pos + 20]);
std::uint32_t usize = read_le32(&file_data[pos + 24]);
std::uint16_t name_len = read_le16(&file_data[pos + 28]);
std::uint16_t extra_len = read_le16(&file_data[pos + 30]);
std::uint16_t comment_len = read_le16(&file_data[pos + 32]);
std::uint32_t local_offset = read_le32(&file_data[pos + 42]);
if ((flags & 1) || (method != 0 && method != 8))
return false;
if (pos + 46u + name_len + extra_len + comment_len > file_data.size())
return false;
std::string name((const char *)&file_data[pos + 46], name_len);
if ((std::uint64_t)local_offset + 30 > file_data.size() ||
read_le32(&file_data[local_offset]) != 0x04034b50u)
return false;
std::uint16_t lname_len = read_le16(&file_data[local_offset + 26]);
std::uint16_t lextra_len = read_le16(&file_data[local_offset + 28]);
std::uint64_t data_offset = (std::uint64_t)local_offset + 30 + lname_len + lextra_len;
if (data_offset + csize > file_data.size())
return false;
zip_entry e;
e.name = name;
e.method = method;
e.crc = crc;
e.compressed_size = csize;
e.uncompressed_size = usize;
e.local_header_offset = local_offset;
e.compressed.assign(file_data.begin() + (std::ptrdiff_t)data_offset,
file_data.begin() + (std::ptrdiff_t)(data_offset + csize));
archive->entries[name] = std::move(e);
pos += 46u + name_len + extra_len + comment_len;
}
return true;
}
extern "C" zip_t *
zip_open(const char *path, int flags, int *errorp)
{
if (errorp)
*errorp = 0;
if (!path)
return nullptr;
zip_t *archive = new zip;
archive->path = path;
archive->write_mode = (flags & ZIP_CREATE) || (flags & ZIP_TRUNCATE);
if (!archive->write_mode) {
if (!parse_zip(archive)) {
delete archive;
if (errorp)
*errorp = 1;
return nullptr;
}
}
return archive;
}
extern "C" void
zip_discard(zip_t *archive)
{
delete archive;
}
extern "C" zip_file_t *
zip_fopen(zip_t *archive, const char *fname, int)
{
if (!archive || !fname)
return nullptr;
auto it = archive->entries.find(fname);
if (it == archive->entries.end())
return nullptr;
zip_file_t *f = new zip_file;
const zip_entry &entry = it->second;
if (!entry.uncompressed.empty() || entry.uncompressed_size == 0) {
f->data = entry.uncompressed;
} else if (entry.method == 0) {
f->data = entry.compressed;
if (f->data.size() != entry.uncompressed_size) {
delete f;
return nullptr;
}
} else if (!inflate_raw(entry.compressed, entry.uncompressed_size, f->data)) {
delete f;
return nullptr;
}
return f;
}
extern "C" zip_int64_t
zip_fread(zip_file_t *file, void *buf, zip_uint64_t nbytes)
{
if (!file || !buf)
return -1;
std::size_t remaining = file->data.size() - file->offset;
std::size_t n = std::min<std::size_t>((std::size_t)nbytes, remaining);
if (n)
std::memcpy(buf, file->data.data() + file->offset, n);
file->offset += n;
return (zip_int64_t)n;
}
extern "C" int
zip_fclose(zip_file_t *file)
{
delete file;
return 0;
}
extern "C" void
zip_stat_init(zip_stat_t *st)
{
if (st)
std::memset(st, 0, sizeof(*st));
}
extern "C" int
zip_stat(zip_t *archive, const char *fname, int, zip_stat_t *st)
{
if (!archive || !fname || !st)
return -1;
auto it = archive->entries.find(fname);
if (it == archive->entries.end())
return -1;
zip_stat_init(st);
st->valid = ZIP_STAT_SIZE;
st->size = it->second.uncompressed_size;
return 0;
}
extern "C" zip_source_t *
zip_source_buffer(zip_t *, const void *data, zip_uint64_t len, int)
{
if (!data && len)
return nullptr;
zip_source_t *src = new zip_source;
const unsigned char *bytes = (const unsigned char *)data;
src->data.assign(bytes, bytes + len);
return src;
}
extern "C" void
zip_source_free(zip_source_t *source)
{
delete source;
}
extern "C" zip_int64_t
zip_file_add(zip_t *archive, const char *name, zip_source_t *source, int)
{
if (!archive || !archive->write_mode || !name || !source)
return -1;
zip_entry e;
e.name = name;
e.uncompressed = source->data;
e.uncompressed_size = (std::uint32_t)e.uncompressed.size();
e.crc = crc32(0L, Z_NULL, 0);
if (!e.uncompressed.empty())
e.crc = crc32(e.crc, e.uncompressed.data(), (uInt)e.uncompressed.size());
std::vector<unsigned char> compressed;
if (deflate_raw(e.uncompressed, compressed) && compressed.size() < e.uncompressed.size()) {
e.method = 8;
e.compressed = std::move(compressed);
} else {
e.method = 0;
e.compressed = e.uncompressed;
}
e.compressed_size = (std::uint32_t)e.compressed.size();
archive->entries[e.name] = std::move(e);
delete source;
return (zip_int64_t)archive->entries.size() - 1;
}
static bool
write_all(std::ofstream &out, const std::vector<unsigned char> &data)
{
if (!data.empty())
out.write((const char *)data.data(), (std::streamsize)data.size());
return out.good();
}
extern "C" int
zip_close(zip_t *archive)
{
if (!archive)
return -1;
if (!archive->write_mode) {
delete archive;
return 0;
}
std::vector<unsigned char> file_data;
std::vector<unsigned char> central_dir;
for (auto &kv : archive->entries) {
zip_entry &e = kv.second;
e.local_header_offset = (std::uint32_t)file_data.size();
append_le32(file_data, 0x04034b50u);
append_le16(file_data, 20);
append_le16(file_data, 0x0800u);
append_le16(file_data, e.method);
append_le16(file_data, 0);
append_le16(file_data, 0);
append_le32(file_data, e.crc);
append_le32(file_data, e.compressed_size);
append_le32(file_data, e.uncompressed_size);
append_le16(file_data, (std::uint16_t)e.name.size());
append_le16(file_data, 0);
file_data.insert(file_data.end(), e.name.begin(), e.name.end());
file_data.insert(file_data.end(), e.compressed.begin(), e.compressed.end());
append_le32(central_dir, 0x02014b50u);
append_le16(central_dir, 20);
append_le16(central_dir, 20);
append_le16(central_dir, 0x0800u);
append_le16(central_dir, e.method);
append_le16(central_dir, 0);
append_le16(central_dir, 0);
append_le32(central_dir, e.crc);
append_le32(central_dir, e.compressed_size);
append_le32(central_dir, e.uncompressed_size);
append_le16(central_dir, (std::uint16_t)e.name.size());
append_le16(central_dir, 0);
append_le16(central_dir, 0);
append_le16(central_dir, 0);
append_le16(central_dir, 0);
append_le32(central_dir, 0);
append_le32(central_dir, e.local_header_offset);
central_dir.insert(central_dir.end(), e.name.begin(), e.name.end());
}
std::uint32_t cd_offset = (std::uint32_t)file_data.size();
std::uint32_t cd_size = (std::uint32_t)central_dir.size();
file_data.insert(file_data.end(), central_dir.begin(), central_dir.end());
append_le32(file_data, 0x06054b50u);
append_le16(file_data, 0);
append_le16(file_data, 0);
append_le16(file_data, (std::uint16_t)archive->entries.size());
append_le16(file_data, (std::uint16_t)archive->entries.size());
append_le32(file_data, cd_size);
append_le32(file_data, cd_offset);
append_le16(file_data, 0);
std::ofstream out(archive->path, std::ios::binary | std::ios::trunc);
bool ok = out && write_all(out, file_data);
out.close();
delete archive;
return ok ? 0 : -1;
}
-54
View File
@@ -1,54 +0,0 @@
/* Minimal FCStd ZIP archive interface for the local open2open copy.
*
* This intentionally implements only the archive operations used by
* open2open/src/fcstd_convert.cpp, using zlib for ZIP deflate streams.
*/
#ifndef GCV_FREECAD_FCSTD_ZIP_H
#define GCV_FREECAD_FCSTD_ZIP_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef int64_t zip_int64_t;
typedef uint64_t zip_uint64_t;
typedef struct zip zip_t;
typedef struct zip_file zip_file_t;
typedef struct zip_source zip_source_t;
typedef struct zip_stat {
zip_uint64_t valid;
zip_uint64_t size;
} zip_stat_t;
#define ZIP_RDONLY 0
#define ZIP_CREATE 1
#define ZIP_TRUNCATE 8
#define ZIP_FL_OVERWRITE 8192
#define ZIP_STAT_SIZE 0x0004u
zip_t *zip_open(const char *path, int flags, int *errorp);
int zip_close(zip_t *archive);
void zip_discard(zip_t *archive);
zip_file_t *zip_fopen(zip_t *archive, const char *fname, int flags);
zip_int64_t zip_fread(zip_file_t *file, void *buf, zip_uint64_t nbytes);
int zip_fclose(zip_file_t *file);
void zip_stat_init(zip_stat_t *st);
int zip_stat(zip_t *archive, const char *fname, int flags, zip_stat_t *st);
zip_source_t *zip_source_buffer(zip_t *archive, const void *data, zip_uint64_t len, int freep);
zip_int64_t zip_file_add(zip_t *archive, const char *name, zip_source_t *source, int flags);
void zip_source_free(zip_source_t *source);
#ifdef __cplusplus
}
#endif
#endif
@@ -1,104 +0,0 @@
FreeCAD GCV plugin improvement targets
======================================
Current measured baseline
-------------------------
Sample: (https://github.com/starseeker/open2open)
open2open/phasma-mech-assembly/FreeCAD native models/Simple structure w. panels.FCStd
Recent full import result:
.build/bin/gcv -i <sample.FCStd> -o /tmp/brlcad_freecad_simple_optimized.g
real 70.64s
output size 276M
With GCV_FREECAD_TIMING=1:
importer total_ms=47456
wall real=69.89s
converted=196
hidden=107
cached=64
conversion_failed=2
This means roughly 22s of the wall time is outside the measured FreeCAD
reader/converter path, most likely in downstream .g serialization.
Completed performance fix
-------------------------
OCCTToON_Brep loop-gap repair previously rebuilt a pcurve reference-count map
inside every loop by scanning all trims created so far. On large BReps this was
effectively O(loop count * trim count). It has been replaced with an incremental
trim_curve_refcount map.
Measured example:
b_ClydeSpace_30Wh_battery_pack__simplified__001_
before: convert_ms=14486, pass3_loop_gap_us=13812995
after: convert_ms=689, pass3_loop_gap_us=18837
Next targets
------------
1. Attribute the post-import .g writing cost.
The timed importer reports about 47.5s, while the complete gcv run takes
about 69.9s. Add or reuse timing around the GCV/database output path to
identify whether the remaining ~22s is object insertion, directory updates,
final db close/write, or BRep serialization.
2. Investigate large FeaturePython BRep entries.
The largest remaining successful object cost in the sample is:
b_CubeADCS_Gen_2_001_
FCStd entry size: 62631280 bytes
faces: 28576
read_ms=1122
brep_read_ms=2500
convert_ms=7659
add_ms=289
Similar large objects dominate the importer time. The immediate work is to
profile these individually and decide whether the cost is unavoidable geometry
size or whether more caching/instancing is possible.
3. Test file-backed OCCT BRep reads for large BRP payloads.
Current path reads BRP content from the FCStd zip into memory and then uses an
in-memory stream for BRepTools::Read. For tens-of-megabytes BRP payloads,
test extracting to a temporary file and using the filename-based OCCT read
API. Compare read_ms and brep_read_ms on CubeADCS-scale entries.
4. Avoid converting repeated assembly instances as full standalone BReps.
Exact BRP text caching helps when the payloads are byte-identical, but many
assembly repetitions differ by placement or generated payload. If FreeCAD
object placement/link metadata can be mapped to BRL-CAD comb instances with
transforms, this would reduce both conversion time and .g size. This is
likely the largest architectural improvement.
5. Handle or cheaply reject expensive conversion failures.
In the sample, two large antenna mechanism objects failed after expensive
parse/conversion work:
b_Phasma_Primus_Antenna_Deployment_Mechanism__001_001
read_ms=708, brep_read_ms=1456, convert_ms=3192
b_Phasma_Primus_Antenna_Deployment_Mechanism__001_002
read_ms=721, brep_read_ms=1488, convert_ms=3214
Determine why OCCTToON_Brep returns invalid for these and either fix the
conversion or add a cheaper preflight/skip path if they are out of scope.
6. Further split remaining Pass 3 face conversion time if needed.
After the loop-gap refcount fix, pass3_loop_gap_us is no longer dominant.
Remaining Pass 3 time is mostly surface conversion, pcurve conversion, and
miscellaneous face/loop/trim construction. If #1-#5 are exhausted, add finer
counters around NewFace/NewLoop/NewTrim, wire traversal, pcurve domain
remapping, and surface cache hits/misses.
7. Keep diagnostics gated.
GCV_FREECAD_TIMING is the current opt-in switch for detailed importer and
OCCTToON_Brep timings. Any additional instrumentation should stay behind this
environment variable, especially if placed inside face/edge/trim loops.
-241
View File
@@ -1,241 +0,0 @@
/* F R E E C A D _ R E A D . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
/** @file freecad_read.cpp
*
* Import FreeCAD .FCStd files into BRL-CAD using open2open's FCStd reader and
* OCCT -> openNURBS BRep conversion core.
*/
#include "common.h"
#include "bu/path.h"
#include "gcv/api.h"
#include "wdb.h"
#include "opennurbs.h"
#include "open2open/fcstd_convert.h"
#include <algorithm>
#include <cctype>
#include <cstring>
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace freecad_read
{
static std::string
clean_name(const std::string &raw, const char *fallback, int index)
{
std::string name = raw.empty() ? std::string(fallback) + "_" + std::to_string(index) : raw;
for (std::string::iterator it = name.begin(); it != name.end(); ++it) {
unsigned char c = static_cast<unsigned char>(*it);
if (!std::isalnum(c) && *it != '_' && *it != '-' && *it != '.')
*it = '_';
}
if (name.empty())
name = std::string(fallback) + "_" + std::to_string(index);
return name;
}
static std::string
unique_name(const std::string &base, std::map<std::string, int> &used)
{
std::map<std::string, int>::iterator found = used.find(base);
if (found == used.end()) {
used[base] = 1;
return base;
}
int next = ++found->second;
return base + "_" + std::to_string(next);
}
static std::string
root_name_from_path(const char *source_path, const gcv_opts *gcv_options)
{
if (gcv_options && gcv_options->default_name)
return clean_name(gcv_options->default_name, "freecad_model", 0);
bu_vls basename = BU_VLS_INIT_ZERO;
if (source_path && bu_path_component(&basename, source_path, BU_PATH_BASENAME_EXTLESS)) {
std::string result = clean_name(bu_vls_cstr(&basename), "freecad_model", 0);
bu_vls_free(&basename);
return result;
}
bu_vls_free(&basename);
return "freecad_model";
}
static void
add_color_attribute(struct rt_wdb *wdbp, const std::string &name, const ON_3dmObjectAttributes *attrs)
{
if (!wdbp || !attrs)
return;
ON_Color c = attrs->m_color;
if (c == ON_Color::UnsetColor)
return;
bu_vls rgb = BU_VLS_INIT_ZERO;
bu_vls_sprintf(&rgb, "%d/%d/%d", c.Red(), c.Green(), c.Blue());
(void)db5_update_attribute(name.c_str(), "color", bu_vls_cstr(&rgb), wdbp->dbip);
bu_vls_free(&rgb);
}
static int
freecad_read(gcv_context *context, const gcv_opts *gcv_options, const void *UNUSED(options_data), const char *source_path)
{
if (!context || !context->dbip || !source_path)
return 0;
ONX_Model model;
int converted = open2open::FCStdFileToONX_Model(source_path, model);
if (converted <= 0) {
bu_log("freecad_read: no FreeCAD shapes were converted from %s\n", source_path);
return 0;
}
struct rt_wdb *wdbp = wdb_dbopen(context->dbip, RT_WDB_TYPE_DB_INMEM);
if (!wdbp)
return 0;
std::map<std::string, int> used;
std::vector<std::string> solids;
ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::ModelGeometry);
int index = 0;
for (const ON_ModelComponent *component = it.FirstComponent(); component; component = it.NextComponent(), ++index) {
const ON_ModelGeometryComponent *geometry_component = ON_ModelGeometryComponent::Cast(component);
if (!geometry_component)
continue;
const ON_Geometry *geometry = geometry_component->Geometry(NULL);
if (!geometry)
continue;
std::unique_ptr<ON_Brep> brep_holder;
const ON_Brep *brep = ON_Brep::Cast(geometry);
if (!brep && geometry->HasBrepForm()) {
brep_holder.reset(geometry->BrepForm());
brep = brep_holder.get();
}
if (!brep)
continue;
if (brep->m_F.Count() <= 0)
continue;
std::string raw_name;
const ON_3dmObjectAttributes *attrs = geometry_component->Attributes(NULL);
if (attrs && attrs->m_name.IsNotEmpty()) {
ON_String name_utf8(attrs->m_name);
raw_name = name_utf8.Array();
}
std::string solid_name = unique_name(clean_name(raw_name, "freecad_shape", index), used);
ON_Brep *writable_brep = const_cast<ON_Brep *>(brep);
if (mk_brep(wdbp, solid_name.c_str(), (void *)writable_brep)) {
bu_log("freecad_read: failed to write BRep %s\n", solid_name.c_str());
continue;
}
(void)db5_update_attribute(solid_name.c_str(), "importer", "gcv-freecad", wdbp->dbip);
add_color_attribute(wdbp, solid_name, attrs);
solids.push_back(solid_name);
}
if (solids.empty()) {
bu_log("freecad_read: no BRep solids were written for %s\n", source_path);
return 0;
}
struct wmember root;
BU_LIST_INIT(&root.l);
for (std::vector<std::string>::const_iterator s = solids.begin(); s != solids.end(); ++s)
mk_addmember(s->c_str(), &root.l, NULL, WMOP_UNION);
std::string root_name = unique_name(root_name_from_path(source_path, gcv_options), used);
if (mk_comb(wdbp, root_name.c_str(), &root.l, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0))
bu_log("freecad_read: failed to write top-level combination %s\n", root_name.c_str());
return 1;
}
static int
freecad_can_read(const char *source_path)
{
if (!source_path)
return 0;
const char *ext = strrchr(source_path, '.');
if (ext && BU_STR_EQUIV(ext + 1, "fcstd"))
return 1;
open2open::FcstdDoc doc;
return open2open::ReadFcstdDoc(source_path, doc) ? 1 : 0;
}
static const struct gcv_filter gcv_conv_freecad_read = {
"FreeCAD Reader", GCV_FILTER_READ, BU_MIME_MODEL_VND_FREECAD, freecad_can_read,
NULL, NULL, freecad_read
};
extern "C" const struct gcv_filter gcv_conv_freecad_write;
static const struct gcv_filter * const filters[] = {&gcv_conv_freecad_read, &gcv_conv_freecad_write, NULL};
}
extern "C"
{
extern const gcv_plugin gcv_plugin_info_s = {freecad_read::filters};
COMPILER_DLLEXPORT const struct gcv_plugin *
gcv_plugin_info()
{
return &gcv_plugin_info_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
@@ -1,237 +0,0 @@
/* F R E E C A D _ W R I T E . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
/** @file freecad_write.cpp
*
* Export BRL-CAD BRep primitives to FreeCAD .FCStd using open2open's
* openNURBS -> OCCT writer.
*/
#include "common.h"
#include "bu/path.h"
#include "gcv/api.h"
#include "raytrace.h"
#include "rt/comb.h"
#include "rt/geom.h"
#include "opennurbs.h"
#include "open2open/fcstd_convert.h"
#include <cctype>
#include <cstring>
#include <set>
#include <string>
#include <vector>
namespace freecad_write
{
static std::string
clean_name(const char *raw, const char *fallback, int index)
{
std::string name = (raw && raw[0]) ? raw : std::string(fallback) + "_" + std::to_string(index);
for (std::string::iterator it = name.begin(); it != name.end(); ++it) {
unsigned char c = static_cast<unsigned char>(*it);
if (!std::isalnum(c) && *it != '_' && *it != '-' && *it != '.')
*it = '_';
}
return name;
}
static bool
append_brep(struct db_i *dbip, const struct directory *dp, ONX_Model &model, int index)
{
if (!dbip || !dp)
return false;
struct rt_db_internal intern;
RT_DB_INTERNAL_INIT(&intern);
if (rt_db_get_internal(&intern, dp, dbip, NULL) < 0)
return false;
if (intern.idb_type != ID_BREP) {
rt_db_free_internal(&intern);
return false;
}
struct rt_brep_internal *bi = (struct rt_brep_internal *)intern.idb_ptr;
if (!bi || !bi->brep) {
rt_db_free_internal(&intern);
return false;
}
ON_3dmObjectAttributes attrs;
attrs.Default();
attrs.m_name = clean_name(dp->d_namep, "brlcad_brep", index).c_str();
model.AddModelGeometryComponent(ON_Brep::New(*bi->brep), &attrs, true);
rt_db_free_internal(&intern);
return true;
}
static void
free_child_matrices(matp_t *mats)
{
if (!mats)
return;
for (int i = 0; mats[i]; ++i)
bu_free(mats[i], "free matrix");
bu_free(mats, "free mats array");
}
static int
append_breps_from_dp(struct db_i *dbip, const struct directory *dp, ONX_Model &model, int &index, std::set<const struct directory *> &seen_breps, std::set<const struct directory *> &seen_combs)
{
if (!dbip || !dp)
return 0;
if (dp->d_flags & RT_DIR_SOLID) {
if (seen_breps.count(dp))
return 0;
seen_breps.insert(dp);
if (append_brep(dbip, dp, model, index)) {
++index;
return 1;
}
return 0;
}
if (!(dp->d_flags & RT_DIR_COMB) || seen_combs.count(dp))
return 0;
seen_combs.insert(dp);
struct rt_db_internal intern;
RT_DB_INTERNAL_INIT(&intern);
if (rt_db_get_internal(&intern, dp, dbip, NULL) < 0)
return 0;
if (intern.idb_type != ID_COMBINATION) {
rt_db_free_internal(&intern);
return 0;
}
struct rt_comb_internal *comb = (struct rt_comb_internal *)intern.idb_ptr;
struct directory **children = NULL;
matp_t *mats = NULL;
int child_count = db_comb_children(dbip, comb, &children, NULL, &mats);
int brep_count = 0;
for (int i = 0; i < child_count && children && children[i] != RT_DIR_NULL; ++i)
brep_count += append_breps_from_dp(dbip, children[i], model, index, seen_breps, seen_combs);
if (children)
bu_free(children, "free children struct directory ptr array");
free_child_matrices(mats);
rt_db_free_internal(&intern);
return brep_count;
}
static int
append_selected_breps(struct db_i *dbip, const gcv_opts *gcv_options, ONX_Model &model)
{
int count = 0;
int index = 0;
std::set<const struct directory *> seen_breps;
std::set<const struct directory *> seen_combs;
if (gcv_options && gcv_options->num_objects) {
for (size_t i = 0; i < gcv_options->num_objects; ++i) {
const char *name = gcv_options->object_names[i];
const struct directory *dp = db_lookup(dbip, name, LOOKUP_QUIET);
if (!dp)
continue;
count += append_breps_from_dp(dbip, dp, model, index, seen_breps, seen_combs);
}
return count;
}
struct directory *dp;
FOR_ALL_DIRECTORY_START(dp, dbip) {
if (dp->d_flags & RT_DIR_SOLID) {
if (!seen_breps.count(dp) && append_brep(dbip, dp, model, index)) {
seen_breps.insert(dp);
++count;
++index;
}
}
} FOR_ALL_DIRECTORY_END;
return count;
}
static int
freecad_write(gcv_context *context, const gcv_opts *gcv_options, const void *UNUSED(options_data), const char *dest_path)
{
if (!context || !context->dbip || !dest_path)
return 0;
ONX_Model model;
model.AddDefaultLayer(L"Default", ON_Color::UnsetColor);
model.m_properties.m_RevisionHistory.NewRevision();
model.m_properties.m_Application.m_application_name = "BRL-CAD gcv FreeCAD writer";
int brep_count = append_selected_breps(context->dbip, gcv_options, model);
if (brep_count <= 0) {
bu_log("freecad_write: no BRep primitives available to export\n");
return 0;
}
int written = open2open::ONX_ModelToFCStdFile(dest_path, model);
if (written <= 0) {
bu_log("freecad_write: failed to write %s\n", dest_path);
return 0;
}
if (written < brep_count)
bu_log("freecad_write: wrote %d of %d BRep primitives to %s\n", written, brep_count, dest_path);
return 1;
}
extern "C" const struct gcv_filter gcv_conv_freecad_write = {
"FreeCAD Writer", GCV_FILTER_WRITE, BU_MIME_MODEL_VND_FREECAD, NULL,
NULL, NULL, freecad_write
};
}
// 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
@@ -1,49 +0,0 @@
// Copyright (c) 2026, open2open contributors
// SPDX-License-Identifier: MIT
//
// brep_convert.h — B-Rep topology conversion helpers.
//
// These functions translate complete B-Rep solid models between openNURBS
// (ON_Brep) and OpenCASCADE (TopoDS_Shape).
#ifndef OPEN2OPEN_BREP_CONVERT_H
#define OPEN2OPEN_BREP_CONVERT_H
#include <TopoDS_Shape.hxx>
// Forward declarations from openNURBS
class ON_Brep;
namespace open2open {
/// Convert an openNURBS B-Rep to an OCCT TopoDS_Shape (Shell or Solid).
///
/// All NURBS curves and surfaces in the brep are translated using the
/// geometry helpers from geom_convert.h. Non-NURBS primitives are
/// first converted to their NURBS representations via
/// ON_Curve::NurbsCurve() / ON_Surface::NurbsSurface().
///
/// @param brep Source B-Rep solid.
/// @param linear_tolerance Positional tolerance propagated to OCCT topology.
/// @return A TopoDS_Solid (or TopoDS_Shell for open models).
/// A null shape is returned on failure.
TopoDS_Shape ON_BrepToOCCT(const ON_Brep& brep,
double linear_tolerance = 1e-6);
/// Convert an OCCT TopoDS_Shape to an openNURBS ON_Brep.
///
/// The function traverses the shape with TopExp_Explorer, translates each
/// geometric entity to NURBS using the geometry helpers, and populates the
/// provided ON_Brep with the resulting topology.
///
/// @param shape Source OCCT shape (must contain Face topology).
/// @param brep Destination B-Rep (will be cleared first).
/// @param linear_tolerance Tolerance used when building the ON_Brep.
/// @return true on success; false if the conversion failed.
bool OCCTToON_Brep(const TopoDS_Shape& shape,
ON_Brep& brep,
double linear_tolerance = 1e-6);
} // namespace open2open
#endif // OPEN2OPEN_BREP_CONVERT_H
@@ -1,185 +0,0 @@
// Copyright (c) 2026, open2open contributors
// SPDX-License-Identifier: MIT
//
// fcstd_convert.h — FreeCAD .FCStd archive reader.
//
// Provides read-only access to the metadata and display colours stored in a
// FreeCAD `.FCStd` archive, complementing the B-Rep conversion functions in
// `brep_convert.h`.
//
// A `.FCStd` file is a standard ZIP archive containing:
//
// Document.xml — object tree (type, name, label, shape file mapping)
// GuiDocument.xml — per-object display settings (colour, visibility …)
// PartShape.brp, — OpenCASCADE B-Rep shapes in OCCT BRep text format
// PartShape1.brp …
// DiffuseColor, — per-face colour lists (binary, one per shape)
// DiffuseColor1 …
//
// DiffuseColor binary format
// --------------------------
// Each `DiffuseColor` file contains:
// uint32 count (little-endian: number of faces)
// uint32 color[count] (per face, packed as 0xRRGGBBAA little-endian)
//
// Note: FreeCAD stores alpha = 0 for fully opaque faces (inverted convention).
//
// Usage
// -----
// open2open::FcstdDoc doc;
// if (open2open::ReadFcstdDoc("/path/to/Model.FCStd", doc)) {
// for (auto& obj : doc.objects) {
// std::printf("name=%s brp=%s faces=%zu\n",
// obj.label.c_str(), obj.brp_file.c_str(),
// obj.face_colors.size());
// }
// }
//
// Dependencies
// ------------
// In this BRL-CAD-local copy, FCStd archive I/O is backed by zlib through the
// plugin's narrow archive helper. OPEN2OPEN_HAVE_LIBZIP is kept as the
// existing open2open compile guard for the FCStd read/write entry points.
#ifndef OPEN2OPEN_FCSTD_CONVERT_H
#define OPEN2OPEN_FCSTD_CONVERT_H
#include <cstdint>
#include <string>
#include <vector>
namespace open2open {
// ---------------------------------------------------------------------------
// Per-object display colour (packed 0xRRGGBBAA, alpha=0 → fully opaque)
// ---------------------------------------------------------------------------
using FcstdColor = std::uint32_t;
// Convenience helpers for unpacking a FcstdColor.
inline std::uint8_t FcstdColorR(FcstdColor c) { return (c >> 24) & 0xFFu; }
inline std::uint8_t FcstdColorG(FcstdColor c) { return (c >> 16) & 0xFFu; }
inline std::uint8_t FcstdColorB(FcstdColor c) { return (c >> 8) & 0xFFu; }
// alpha=0 means fully opaque in FreeCAD's convention.
inline std::uint8_t FcstdColorA(FcstdColor c) { return (c ) & 0xFFu; }
// Convert FreeCAD packed color to a normalised [0,1] RGBA tuple.
// FreeCAD alpha=0 → standard alpha=1 (opaque).
inline void FcstdColorToRGBAf(FcstdColor c,
float& r, float& g, float& b, float& a)
{
r = FcstdColorR(c) / 255.0f;
g = FcstdColorG(c) / 255.0f;
b = FcstdColorB(c) / 255.0f;
std::uint8_t fc_alpha = FcstdColorA(c);
a = (fc_alpha == 0) ? 1.0f : (1.0f - fc_alpha / 255.0f);
}
// ---------------------------------------------------------------------------
// Parse a DiffuseColor binary blob (in memory) into per-face colors.
//
// @param data Pointer to the raw bytes (e.g. content of DiffuseColor).
// @param size Number of bytes.
// @param colors_out Receives count packed FcstdColor values; cleared first.
// @return true on success; false if the data is truncated/invalid.
// ---------------------------------------------------------------------------
bool ParseDiffuseColors(const void* data,
std::size_t size,
std::vector<FcstdColor>& colors_out);
// ---------------------------------------------------------------------------
// Information about one geometric object inside an FCStd archive.
// ---------------------------------------------------------------------------
struct FcstdObject {
std::string name; ///< Internal name used as key (e.g. "Part__Feature")
std::string type; ///< FreeCAD object type (e.g. "Part::Feature")
std::string label; ///< User-facing label (e.g. "ESQ-126-38-G-D_socket")
std::string part_label; ///< Label of the containing App::Part (empty if none)
std::string brp_file; ///< Path inside ZIP to the BRep file (e.g. "PartShape.brp")
FcstdColor shape_color = 0xC8C8C800u; ///< Overall shape colour (packed RGBA, FreeCAD alpha=0 → opaque)
bool visible = true;
std::vector<FcstdColor> face_colors; ///< Per-face colours (may be empty)
};
// ---------------------------------------------------------------------------
// Document-level metadata extracted from Document.xml.
// ---------------------------------------------------------------------------
struct FcstdDocMeta {
std::string label; ///< Document label (e.g. "ESQ-126-38-G-D")
std::string created_by;
std::string creation_date;
std::string last_modified_date;
std::string comment;
std::string company;
};
// ---------------------------------------------------------------------------
// Everything read from one FCStd archive.
// ---------------------------------------------------------------------------
struct FcstdDoc {
FcstdDocMeta meta;
std::vector<FcstdObject> objects; ///< Only objects with a BRep shape file
};
// ---------------------------------------------------------------------------
// Read an FCStd archive and populate a FcstdDoc.
//
// Reads Document.xml, GuiDocument.xml, and all DiffuseColor binary blobs.
// Objects that have no associated BRep file (e.g. App::Origin, App::Line) are
// silently skipped.
//
// @param path Filesystem path to the .FCStd file.
// @param doc Output: populated on success; unchanged on failure.
// @return true if the archive was opened and at least Document.xml was
// parsed; false if the file could not be opened as a ZIP archive.
//
// This function requires OPEN2OPEN_HAVE_LIBZIP, which enables the local
// zlib-backed FCStd archive implementation in the BRL-CAD plugin build.
// ---------------------------------------------------------------------------
bool ReadFcstdDoc(const std::string& path, FcstdDoc& doc);
#ifdef OPEN2OPEN_HAVE_LIBZIP
// Forward-declare the openNURBS model type in its actual namespace.
// Callers must include "opennurbs.h" themselves.
} // namespace open2open
class ONX_Model;
namespace open2open {
// ---------------------------------------------------------------------------
// Convert an FCStd file to an ONX_Model.
//
// Opens the archive at @p path, reads all B-Rep shapes (*.brp entries),
// converts each shape via OCCTToON_Brep(), and appends to @p model.
// Object name, display colour and layer name are propagated as model
// attributes. Per-face colours from DiffuseColor are stored on the ON_Brep
// using the first face colour as the object colour.
//
// @param path Filesystem path to the .FCStd file.
// @param model Destination model. Existing content is NOT cleared.
// @param tol Linear tolerance passed to OCCTToON_Brep().
// @return Number of shapes successfully converted (0 on total failure).
// ---------------------------------------------------------------------------
int FCStdFileToONX_Model(const std::string& path,
::ONX_Model& model,
double tol = 1e-6);
// ---------------------------------------------------------------------------
// Convert an ONX_Model to a FreeCAD .FCStd archive.
//
// Writes each B-Rep geometry object in @p model as an OCCT BRep text file
// inside the ZIP archive, along with a minimal Document.xml that links
// object names to their shape files, and a GuiDocument.xml with display
// colours.
//
// @param path Output filesystem path for the .FCStd file.
// @param model Source openNURBS model.
// @param tol Linear tolerance passed to ON_BrepToOCCT().
// @return Number of shapes successfully written (0 on total failure).
// ---------------------------------------------------------------------------
int ONX_ModelToFCStdFile(const std::string& path,
const ::ONX_Model& model,
double tol = 1e-6);
#endif // OPEN2OPEN_HAVE_LIBZIP
} // namespace open2open
#endif // OPEN2OPEN_FCSTD_CONVERT_H
@@ -1,62 +0,0 @@
// Copyright (c) 2026, open2open contributors
// SPDX-License-Identifier: MIT
//
// geom_convert.h — Low-level NURBS geometry conversion helpers.
//
// These functions translate individual curve and surface objects between
// openNURBS and OpenCASCADE (OCCT) representations. They are the building
// blocks for the higher-level B-Rep topology conversion in brep_convert.h.
#ifndef OPEN2OPEN_GEOM_CONVERT_H
#define OPEN2OPEN_GEOM_CONVERT_H
#include <Standard_Handle.hxx>
#include <Geom_BSplineCurve.hxx>
#include <Geom_BSplineSurface.hxx>
#include <Geom2d_BSplineCurve.hxx>
// Forward declarations from openNURBS
class ON_NurbsCurve;
class ON_NurbsSurface;
namespace open2open {
//--------------------------------------------------------------------
// openNURBS → OCCT
//--------------------------------------------------------------------
/// Convert a 3-D openNURBS NURBS curve to an OCCT Geom_BSplineCurve.
/// @param c Source curve (m_dim must be 3).
/// @return Null handle if the conversion fails (e.g. wrong dimension).
Handle(Geom_BSplineCurve) ON_NurbsCurveToOCCT(const ON_NurbsCurve& c);
/// Convert a 2-D openNURBS NURBS curve (parameter-space trim) to a
/// Geom2d_BSplineCurve.
/// @param c Source curve (m_dim must be 2).
/// @return Null handle on failure.
Handle(Geom2d_BSplineCurve) ON_NurbsCurve2dToOCCT(const ON_NurbsCurve& c);
/// Convert an openNURBS NURBS surface to an OCCT Geom_BSplineSurface.
/// @param s Source surface.
/// @return Null handle on failure.
Handle(Geom_BSplineSurface) ON_NurbsSurfaceToOCCT(const ON_NurbsSurface& s);
//--------------------------------------------------------------------
// OCCT → openNURBS
//--------------------------------------------------------------------
/// Convert an OCCT Geom_BSplineCurve to an openNURBS ON_NurbsCurve.
/// @param c Source curve handle.
/// @param out Destination object (will be overwritten).
/// @return true on success.
bool OCCTCurveToON(const Handle(Geom_BSplineCurve)& c, ON_NurbsCurve& out);
/// Convert an OCCT Geom_BSplineSurface to an openNURBS ON_NurbsSurface.
/// @param s Source surface handle.
/// @param out Destination object (will be overwritten).
/// @return true on success.
bool OCCTSurfaceToON(const Handle(Geom_BSplineSurface)& s, ON_NurbsSurface& out);
} // namespace open2open
#endif // OPEN2OPEN_GEOM_CONVERT_H
@@ -1,49 +0,0 @@
// Copyright (c) 2026, open2open contributors
// SPDX-License-Identifier: MIT
//
// mesh_convert.h — Polygonal mesh conversion helpers.
//
// These functions translate polygonal mesh geometry between openNURBS
// (ON_Mesh) and OpenCASCADE (Poly_Triangulation).
#ifndef OPEN2OPEN_MESH_CONVERT_H
#define OPEN2OPEN_MESH_CONVERT_H
#include <Standard_Handle.hxx>
#include <Poly_Triangulation.hxx>
// Forward declaration from openNURBS
class ON_Mesh;
namespace open2open {
/// Convert an openNURBS polygon mesh to an OCCT Poly_Triangulation.
///
/// Quad faces in the openNURBS mesh are triangulated by splitting along
/// the diagonal between vertex 0 and vertex 2 of each quad. Vertex
/// positions, per-vertex normals, and per-vertex texture coordinates
/// are all preserved when present. Vertex colors are not carried over
/// because Poly_Triangulation does not support per-vertex colour data.
///
/// Vertex positions are taken from the double-precision array (m_dV) when
/// it is populated; otherwise from the single-precision array (m_V).
///
/// @param mesh Source openNURBS mesh.
/// @return A new Poly_Triangulation handle, or a null handle if the
/// mesh is empty or the conversion fails.
Handle(Poly_Triangulation) ON_MeshToOCCT(const ON_Mesh& mesh);
/// Convert an OCCT Poly_Triangulation to an openNURBS ON_Mesh.
///
/// Each triangle becomes an ON_MeshFace using the degenerate-quad encoding
/// that openNURBS uses for triangles: vi[2] == vi[3]. Per-vertex normals
/// and UV texture coordinates are copied when the triangulation provides them.
///
/// @param tri Source OCCT triangulation (must not be null).
/// @param mesh Destination mesh (cleared before writing).
/// @return true on success; false if @p tri is null or empty.
bool OCCTToON_Mesh(const Handle(Poly_Triangulation)& tri, ON_Mesh& mesh);
} // namespace open2open
#endif // OPEN2OPEN_MESH_CONVERT_H
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,413 +0,0 @@
// Copyright (c) 2026, open2open contributors
// SPDX-License-Identifier: MIT
//
// geom_convert.cpp — NURBS curve and surface translation between
// openNURBS and OpenCASCADE (OCCT).
#include "open2open/geom_convert.h"
// ---- openNURBS ----
#include "opennurbs.h"
// ---- OCCT ----
#include <TColgp_Array1OfPnt.hxx>
#include <TColgp_Array1OfPnt2d.hxx>
#include <TColgp_Array2OfPnt.hxx>
#include <TColStd_Array1OfReal.hxx>
#include <TColStd_Array1OfInteger.hxx>
#include <TColStd_Array2OfReal.hxx>
#include <Geom_BSplineCurve.hxx>
#include <Geom_BSplineSurface.hxx>
#include <Geom2d_BSplineCurve.hxx>
#include <vector>
namespace open2open {
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
/// Convert an openNURBS flat knot array to OCCT (unique knots + multiplicities).
///
/// openNURBS stores `order + cv_count - 2` knot values – omitting one
/// "phantom" knot at each end that OCCT needs. This function builds a
/// temporary extended array of length `num_knots + 2`, prepending
/// @p phantom_lo and appending @p phantom_hi, then deduplicates into
/// the OCCT (knot, multiplicity) format.
///
/// For clamped (non-periodic) curves pass `phantom_lo = flat_knots[0]`
/// and `phantom_hi = flat_knots[num_knots-1]`.
///
/// For uniform (non-clamped, non-periodic) curves pass extrapolated values:
/// `phantom_lo = 2*k[0] - k[1]` and `phantom_hi = 2*k[last] - k[last-1]`.
///
/// For periodic curves pass `periodic = true` and the phantoms are ignored.
static void ExtractKnotsAndMults(const double* flat_knots,
int num_knots,
TColStd_Array1OfReal& knots,
TColStd_Array1OfInteger& mults,
double phantom_lo,
double phantom_hi,
bool periodic = false)
{
if (periodic) {
// Periodic: pass through without adding phantom knots
int num_distinct = 0;
for (int i = 0; i < num_knots; ) {
++num_distinct;
int j = i + 1;
while (j < num_knots && flat_knots[j] == flat_knots[i]) ++j;
i = j;
}
knots.Resize(1, num_distinct, Standard_False);
mults.Resize(1, num_distinct, Standard_False);
int idx = 1, i = 0;
while (i < num_knots) {
int j = i + 1;
while (j < num_knots && flat_knots[j] == flat_knots[i]) ++j;
knots.SetValue(idx, flat_knots[i]);
mults.SetValue(idx, j - i);
++idx; i = j;
}
return;
}
// Build extended array: [phantom_lo] + flat_knots[] + [phantom_hi]
const int ext_len = num_knots + 2;
std::vector<double> ext(ext_len);
ext[0] = phantom_lo;
for (int k = 0; k < num_knots; ++k)
ext[k + 1] = flat_knots[k];
ext[ext_len - 1] = phantom_hi;
// Count distinct values in the extended array
int num_distinct = 0;
for (int i = 0; i < ext_len; ) {
++num_distinct;
int j = i + 1;
while (j < ext_len && ext[j] == ext[i]) ++j;
i = j;
}
knots.Resize(1, num_distinct, Standard_False);
mults.Resize(1, num_distinct, Standard_False);
int idx = 1, i = 0;
while (i < ext_len) {
int j = i + 1;
while (j < ext_len && ext[j] == ext[i]) ++j;
knots.SetValue(idx, ext[i]);
mults.SetValue(idx, j - i);
++idx; i = j;
}
}
// ---------------------------------------------------------------------------
// ON_NurbsCurve → Geom_BSplineCurve (3-D)
// ---------------------------------------------------------------------------
Handle(Geom_BSplineCurve) ON_NurbsCurveToOCCT(const ON_NurbsCurve& c)
{
if (c.m_dim != 3 || c.m_order < 2 || c.m_cv_count < c.m_order ||
!c.m_cv || !c.m_knot)
return Handle(Geom_BSplineCurve)();
const int degree = c.m_order - 1;
const int num_poles = c.m_cv_count;
const bool rational = (c.m_is_rat != 0);
// ON_IsKnotVectorPeriodic detects uniform knot spacing at both ends.
// We always build a non-periodic OCCT B-spline (the evaluation is
// identical) and add the two phantom knots that openNURBS omits.
// True closed curves are handled as non-periodic with a domain
// restricted to the interior knot span.
// --- control points (OCCT is 1-indexed, Euclidean) ---
TColgp_Array1OfPnt poles(1, num_poles);
TColStd_Array1OfReal weights(1, num_poles);
for (int i = 0; i < num_poles; ++i) {
const double* cv = c.m_cv + static_cast<std::ptrdiff_t>(i) * c.m_cv_stride;
if (rational) {
double w = cv[c.m_dim]; // homogeneous weight
if (w == 0.0) return Handle(Geom_BSplineCurve)();
poles.SetValue(i + 1, gp_Pnt(cv[0] / w, cv[1] / w, cv[2] / w));
weights.SetValue(i + 1, w);
} else {
poles.SetValue(i + 1, gp_Pnt(cv[0], cv[1], cv[2]));
weights.SetValue(i + 1, 1.0);
}
}
// --- knot vector ---
// openNURBS omits one "phantom" knot at each end; OCCT requires them.
// For clamped ends the phantom = boundary knot value.
// For non-clamped ends extrapolate by one knot-step.
const int num_flat_knots = c.m_order + c.m_cv_count - 2;
const double k0 = c.m_knot[0];
const double k1 = c.m_knot[1];
const double kn1 = c.m_knot[num_flat_knots - 2];
const double kn = c.m_knot[num_flat_knots - 1];
const double phantom_lo = (k0 != k1) ? 2.0 * k0 - k1 : k0;
const double phantom_hi = (kn != kn1) ? 2.0 * kn - kn1 : kn;
TColStd_Array1OfReal knots(1, 1);
TColStd_Array1OfInteger mults(1, 1);
ExtractKnotsAndMults(c.m_knot, num_flat_knots, knots, mults,
phantom_lo, phantom_hi, false);
try {
if (rational) {
return new Geom_BSplineCurve(poles, weights, knots, mults,
degree, false);
} else {
return new Geom_BSplineCurve(poles, knots, mults,
degree, false);
}
} catch (...) {
return Handle(Geom_BSplineCurve)();
}
}
// ---------------------------------------------------------------------------
// ON_NurbsCurve → Geom2d_BSplineCurve (2-D parameter-space)
// ---------------------------------------------------------------------------
Handle(Geom2d_BSplineCurve) ON_NurbsCurve2dToOCCT(const ON_NurbsCurve& c)
{
if (c.m_dim != 2 || c.m_order < 2 || c.m_cv_count < c.m_order ||
!c.m_cv || !c.m_knot)
return Handle(Geom2d_BSplineCurve)();
const int degree = c.m_order - 1;
const int num_poles = c.m_cv_count;
const bool rational = (c.m_is_rat != 0);
TColgp_Array1OfPnt2d poles(1, num_poles);
TColStd_Array1OfReal weights(1, num_poles);
for (int i = 0; i < num_poles; ++i) {
const double* cv = c.m_cv + static_cast<std::ptrdiff_t>(i) * c.m_cv_stride;
if (rational) {
double w = cv[c.m_dim];
if (w == 0.0) return Handle(Geom2d_BSplineCurve)();
poles.SetValue(i + 1, gp_Pnt2d(cv[0] / w, cv[1] / w));
weights.SetValue(i + 1, w);
} else {
poles.SetValue(i + 1, gp_Pnt2d(cv[0], cv[1]));
weights.SetValue(i + 1, 1.0);
}
}
const int num_flat_knots = c.m_order + c.m_cv_count - 2;
const double k2_0 = c.m_knot[0];
const double k2_1 = c.m_knot[1];
const double k2_n1 = c.m_knot[num_flat_knots - 2];
const double k2_n = c.m_knot[num_flat_knots - 1];
const double plo2 = (k2_0 != k2_1) ? 2.0 * k2_0 - k2_1 : k2_0;
const double phi2 = (k2_n != k2_n1) ? 2.0 * k2_n - k2_n1 : k2_n;
TColStd_Array1OfReal knots(1, 1);
TColStd_Array1OfInteger mults(1, 1);
ExtractKnotsAndMults(c.m_knot, num_flat_knots, knots, mults,
plo2, phi2, false);
try {
if (rational) {
return new Geom2d_BSplineCurve(poles, weights, knots, mults,
degree, false);
} else {
return new Geom2d_BSplineCurve(poles, knots, mults,
degree, false);
}
} catch (...) {
return Handle(Geom2d_BSplineCurve)();
}
}
// ---------------------------------------------------------------------------
// ON_NurbsSurface → Geom_BSplineSurface
// ---------------------------------------------------------------------------
Handle(Geom_BSplineSurface) ON_NurbsSurfaceToOCCT(const ON_NurbsSurface& s)
{
if (s.m_dim != 3 || s.m_order[0] < 2 || s.m_order[1] < 2 ||
s.m_cv_count[0] < s.m_order[0] || s.m_cv_count[1] < s.m_order[1] ||
!s.m_cv || !s.m_knot[0] || !s.m_knot[1])
return Handle(Geom_BSplineSurface)();
const int udeg = s.m_order[0] - 1;
const int vdeg = s.m_order[1] - 1;
const int nu = s.m_cv_count[0];
const int nv = s.m_cv_count[1];
const bool rational = (s.m_is_rat != 0);
// OCCT Array2: rows = U index (1..nu), cols = V index (1..nv)
TColgp_Array2OfPnt poles(1, nu, 1, nv);
TColStd_Array2OfReal weights(1, nu, 1, nv);
for (int i = 0; i < nu; ++i) {
for (int j = 0; j < nv; ++j) {
const double* cv = s.m_cv
+ static_cast<std::ptrdiff_t>(i) * s.m_cv_stride[0]
+ static_cast<std::ptrdiff_t>(j) * s.m_cv_stride[1];
if (rational) {
double w = cv[s.m_dim];
if (w == 0.0) return Handle(Geom_BSplineSurface)();
poles.SetValue(i + 1, j + 1,
gp_Pnt(cv[0] / w, cv[1] / w, cv[2] / w));
weights.SetValue(i + 1, j + 1, w);
} else {
poles.SetValue(i + 1, j + 1,
gp_Pnt(cv[0], cv[1], cv[2]));
weights.SetValue(i + 1, j + 1, 1.0);
}
}
}
const int nu_knots = s.m_order[0] + s.m_cv_count[0] - 2;
const int nv_knots = s.m_order[1] + s.m_cv_count[1] - 2;
// Compute phantom knot values for each parametric direction
auto PhantomLo = [](const double* k, int n) -> double {
return (k[0] != k[1]) ? 2.0 * k[0] - k[1] : k[0];
};
auto PhantomHi = [](const double* k, int n) -> double {
return (k[n-1] != k[n-2]) ? 2.0 * k[n-1] - k[n-2] : k[n-1];
};
TColStd_Array1OfReal uknots(1, 1), vknots(1, 1);
TColStd_Array1OfInteger umults(1, 1), vmults(1, 1);
ExtractKnotsAndMults(s.m_knot[0], nu_knots, uknots, umults,
PhantomLo(s.m_knot[0], nu_knots),
PhantomHi(s.m_knot[0], nu_knots), false);
ExtractKnotsAndMults(s.m_knot[1], nv_knots, vknots, vmults,
PhantomLo(s.m_knot[1], nv_knots),
PhantomHi(s.m_knot[1], nv_knots), false);
try {
if (rational) {
return new Geom_BSplineSurface(poles, weights,
uknots, vknots,
umults, vmults,
udeg, vdeg,
false, false);
} else {
return new Geom_BSplineSurface(poles,
uknots, vknots,
umults, vmults,
udeg, vdeg,
false, false);
}
} catch (...) {
return Handle(Geom_BSplineSurface)();
}
}
// ---------------------------------------------------------------------------
// Geom_BSplineCurve → ON_NurbsCurve (3-D)
// ---------------------------------------------------------------------------
bool OCCTCurveToON(const Handle(Geom_BSplineCurve)& c, ON_NurbsCurve& out)
{
if (c.IsNull()) return false;
const int degree = c->Degree();
const int num_poles = c->NbPoles();
const bool rational = c->IsRational() ? true : false;
if (!out.Create(3, rational, degree + 1, num_poles))
return false;
// --- control points ---
for (int i = 1; i <= num_poles; ++i) {
gp_Pnt p = c->Pole(i);
double w = rational ? c->Weight(i) : 1.0;
// openNURBS rational curves store homogeneous coordinates
out.SetCV(i - 1,
ON_4dPoint(p.X() * w, p.Y() * w, p.Z() * w, w));
}
// --- knot vector ---
// OCCT stores sum(mults) = num_poles + degree + 1 (non-periodic).
// openNURBS stores sum(flat) = num_poles + degree - 1 (2 fewer).
// The conversion always added one phantom knot at each end so we
// always strip exactly one from the first and last multiplicity groups.
const int num_knot_groups = c->NbKnots();
int ki = 0;
for (int j = 1; j <= num_knot_groups; ++j) {
double val = c->Knot(j);
int mult = c->Multiplicity(j);
if (j == 1 || j == num_knot_groups)
mult -= 1;
for (int m = 0; m < mult; ++m) {
out.m_knot[ki++] = val;
}
}
return out.IsValid() ? true : false;
}
// ---------------------------------------------------------------------------
// Geom_BSplineSurface → ON_NurbsSurface
// ---------------------------------------------------------------------------
bool OCCTSurfaceToON(const Handle(Geom_BSplineSurface)& s, ON_NurbsSurface& out)
{
if (s.IsNull()) return false;
const int udeg = s->UDegree();
const int vdeg = s->VDegree();
const int nu = s->NbUPoles();
const int nv = s->NbVPoles();
const bool rational = s->IsURational() || s->IsVRational();
if (!out.Create(3, rational, udeg + 1, vdeg + 1, nu, nv))
return false;
// --- control points ---
for (int i = 1; i <= nu; ++i) {
for (int j = 1; j <= nv; ++j) {
gp_Pnt p = s->Pole(i, j);
double w = rational ? s->Weight(i, j) : 1.0;
out.SetCV(i - 1, j - 1,
ON_4dPoint(p.X() * w, p.Y() * w, p.Z() * w, w));
}
}
// --- U knots ---
{
int ki = 0;
const int nku = s->NbUKnots();
// OCCT stores sum(mults) = nu + udeg + 1 (non-periodic).
// openNURBS stores sum(flat) = nu + udeg - 1 (2 fewer).
// Whether clamped or non-clamped, the conversion always added one
// phantom knot at each end, so we always strip one from each end.
for (int j = 1; j <= nku; ++j) {
double val = s->UKnot(j);
int mult = s->UMultiplicity(j);
if (j == 1 || j == nku)
mult -= 1;
for (int m = 0; m < mult; ++m)
out.m_knot[0][ki++] = val;
}
}
// --- V knots ---
{
int ki = 0;
const int nkv = s->NbVKnots();
for (int j = 1; j <= nkv; ++j) {
double val = s->VKnot(j);
int mult = s->VMultiplicity(j);
if (j == 1 || j == nkv)
mult -= 1;
for (int m = 0; m < mult; ++m)
out.m_knot[1][ki++] = val;
}
}
return out.IsValid() ? true : false;
}
} // namespace open2open
@@ -1,171 +0,0 @@
// Copyright (c) 2026, open2open contributors
// SPDX-License-Identifier: MIT
//
// mesh_convert.cpp — Polygonal mesh translation between openNURBS and OCCT.
#include "open2open/mesh_convert.h"
// ---- openNURBS ----
#include "opennurbs.h"
// ---- OCCT ----
#include <Poly_Triangulation.hxx>
#include <Poly_Triangle.hxx>
#include <gp_Pnt.hxx>
#include <gp_Pnt2d.hxx>
#include <gp_Vec3f.hxx>
namespace open2open {
// ---------------------------------------------------------------------------
// ON_Mesh → Poly_Triangulation
// ---------------------------------------------------------------------------
Handle(Poly_Triangulation) ON_MeshToOCCT(const ON_Mesh& mesh)
{
const int nv = mesh.m_V.Count();
const int nf = mesh.m_F.Count();
if (nv <= 0 || nf <= 0)
return Handle(Poly_Triangulation)();
// Count output triangles: quads become 2 triangles, tris stay as 1.
int ntri = 0;
for (int fi = 0; fi < nf; ++fi) {
const ON_MeshFace& f = mesh.m_F[fi];
ntri += f.IsTriangle() ? 1 : 2;
}
if (ntri == 0)
return Handle(Poly_Triangulation)();
const bool has_uv = mesh.HasTextureCoordinates() &&
mesh.m_T.Count() == nv;
const bool has_normals = mesh.HasVertexNormals() &&
mesh.m_N.Count() == nv;
Handle(Poly_Triangulation) tri =
new Poly_Triangulation(nv, ntri, has_uv, has_normals);
// Copy vertices — prefer double-precision array when available.
const bool use_double = (mesh.m_dV.Count() == nv);
for (int vi = 0; vi < nv; ++vi) {
gp_Pnt pt;
if (use_double) {
const ON_3dPoint& p = mesh.m_dV[vi];
pt = gp_Pnt(p.x, p.y, p.z);
} else {
const ON_3fPoint& p = mesh.m_V[vi];
pt = gp_Pnt(static_cast<double>(p.x),
static_cast<double>(p.y),
static_cast<double>(p.z));
}
tri->SetNode(vi + 1, pt); // Poly_Triangulation is 1-indexed
}
// Copy per-vertex normals.
if (has_normals) {
for (int ni = 0; ni < nv; ++ni) {
const ON_3fVector& n = mesh.m_N[ni];
tri->SetNormal(ni + 1,
gp_Vec3f(static_cast<float>(n.x),
static_cast<float>(n.y),
static_cast<float>(n.z)));
}
}
// Copy per-vertex texture coordinates.
if (has_uv) {
for (int ui = 0; ui < nv; ++ui) {
const ON_2fPoint& uv = mesh.m_T[ui];
tri->SetUVNode(ui + 1,
gp_Pnt2d(static_cast<double>(uv.x),
static_cast<double>(uv.y)));
}
}
// Copy faces — split quads into two triangles.
// ON_MeshFace vi[] is 0-indexed; Poly_Triangle is 1-indexed.
int tidx = 1;
for (int fi = 0; fi < nf; ++fi) {
const ON_MeshFace& f = mesh.m_F[fi];
tri->SetTriangle(tidx++,
Poly_Triangle(f.vi[0] + 1,
f.vi[1] + 1,
f.vi[2] + 1));
if (!f.IsTriangle()) {
// Second triangle covers the quad's second half:
// vertices 0, 2, 3 of the original quad.
tri->SetTriangle(tidx++,
Poly_Triangle(f.vi[0] + 1,
f.vi[2] + 1,
f.vi[3] + 1));
}
}
return tri;
}
// ---------------------------------------------------------------------------
// Poly_Triangulation → ON_Mesh
// ---------------------------------------------------------------------------
bool OCCTToON_Mesh(const Handle(Poly_Triangulation)& tri, ON_Mesh& mesh)
{
if (tri.IsNull())
return false;
const int nv = tri->NbNodes();
const int nt = tri->NbTriangles();
if (nv <= 0 || nt <= 0)
return false;
mesh.Destroy();
// Allocate and fill vertex array.
mesh.m_V.SetCapacity(nv);
mesh.m_V.SetCount(nv);
for (int vi = 1; vi <= nv; ++vi) {
const gp_Pnt& p = tri->Node(vi);
mesh.m_V[vi - 1] = ON_3fPoint(static_cast<float>(p.X()),
static_cast<float>(p.Y()),
static_cast<float>(p.Z()));
}
// Allocate and fill per-vertex normal array.
if (tri->HasNormals()) {
mesh.m_N.SetCapacity(nv);
mesh.m_N.SetCount(nv);
for (int ni = 1; ni <= nv; ++ni) {
gp_Vec3f n;
tri->Normal(ni, n);
mesh.m_N[ni - 1] = ON_3fVector(n.x(), n.y(), n.z());
}
}
// Allocate and fill per-vertex UV array.
if (tri->HasUVNodes()) {
mesh.m_T.SetCapacity(nv);
mesh.m_T.SetCount(nv);
for (int ui = 1; ui <= nv; ++ui) {
const gp_Pnt2d& uv = tri->UVNode(ui);
mesh.m_T[ui - 1] = ON_2fPoint(static_cast<float>(uv.X()),
static_cast<float>(uv.Y()));
}
}
// Allocate and fill face array.
// openNURBS encodes triangles as degenerate quads: vi[2] == vi[3].
mesh.m_F.SetCapacity(nt);
mesh.m_F.SetCount(nt);
for (int ti = 1; ti <= nt; ++ti) {
const Poly_Triangle& t = tri->Triangle(ti);
ON_MeshFace& f = mesh.m_F[ti - 1];
f.vi[0] = t.Value(1) - 1; // convert 1-indexed → 0-indexed
f.vi[1] = t.Value(2) - 1;
f.vi[2] = t.Value(3) - 1;
f.vi[3] = f.vi[2]; // degenerate-quad triangle encoding
}
return true;
}
} // namespace open2open
@@ -188,8 +188,7 @@ test_uncertain()
require(result.shells.empty() || (result.shells.front().orientation == ShellOrientation::Indeterminate &&
!result.shells.front().corrected), "invalid parameter-loop senses caused an incorrect face repair");
auto flat = box();
ON_Xform flatten;
flatten.DiagonalTransformation(1.0);
ON_Xform flatten = ON_Xform::DiagonalTransformation(1.0);
flatten[2][2] = 1.0e-13;
require(flat->Transform(flatten), "could not construct thin shell");
result = repair_brep_orientation(*flat);
+3
View File
@@ -328,6 +328,7 @@ set(
LIBGED_SOURCES
${GED_CMD_SRC}
${GED_STATIC_REGISTRATION_CPP}
canonicalize.cpp
edit_buf.cpp
exec.cpp
ged_init.cpp
@@ -355,6 +356,8 @@ set(
wireframe_eval.c
)
cmakefiles(canonicalize_private.h)
set_property(
SOURCE ged_init.cpp
APPEND
File diff suppressed because it is too large Load Diff
+109
View File
@@ -0,0 +1,109 @@
/* C A N O N I C A L I Z E _ P R I V A T E . H
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*/
#ifndef LIBGED_CANONICALIZE_PRIVATE_H
#define LIBGED_CANONICALIZE_PRIVATE_H
#include "common.h"
#include "bn/tol.h"
#include "ged/defines.h"
#include "raytrace.h"
__BEGIN_DECLS
/* These helpers are private libged interfaces shared by commands that need to
* recognize or reconstruct canonical primitive geometry. */
GED_EXPORT fastf_t _ged_matrix_roundoff_tolerance(fastf_t a, fastf_t b,
fastf_t database_limit);
GED_EXPORT int _ged_matrices_numerically_equal(const mat_t a, const mat_t b,
const struct bn_tol *tol);
GED_EXPORT int _ged_canonical_geometry_equal(
const struct rt_db_internal *a, const struct rt_db_internal *b,
const struct bn_tol *tol);
GED_EXPORT int _ged_canonical_geometry_metric(
const struct rt_db_internal *object, const struct bn_tol *tol,
fastf_t *metric, fastf_t *metric_tolerance);
GED_EXPORT int _ged_primitive_geometry_equal(
const struct rt_db_internal *a, const struct rt_db_internal *b,
const struct bn_tol *tol);
/* Allocate and populate output with input transformed by matrix. Output must
* be empty and remains empty on error. */
GED_EXPORT int _ged_transform_primitive(struct rt_db_internal *output,
const mat_t matrix, const struct rt_db_internal *input);
GED_EXPORT int _ged_transformed_geometry_equal(
const struct rt_db_internal *a, const mat_t a_matrix,
const struct rt_db_internal *b, const mat_t b_matrix,
const struct bn_tol *tol);
__END_DECLS
#ifdef __cplusplus
#include <map>
#include <string>
#include <vector>
struct GedCanonicalChildInfo {
size_t identity = 0;
mat_t placement = MAT_INIT_IDN;
};
struct GedCanonicalCombinationLeaf {
size_t child_identity = 0;
mat_t effective_matrix = MAT_INIT_IDN;
mat_t relative_matrix = MAT_INIT_IDN;
};
struct GedCanonicalCombinationRecord {
struct directory *dp = nullptr;
mat_t placement = MAT_INIT_IDN;
mat_t identity_placement = MAT_INIT_IDN;
mat_t representative_to_input = MAT_INIT_IDN;
std::string bucket;
std::vector<GedCanonicalCombinationLeaf> leaves;
fastf_t metric = 0.0;
fastf_t metric_tolerance = 0.0;
size_t identity = 0;
bool region = false;
bool valid = false;
};
struct GedCanonicalCombinationGroup {
std::vector<size_t> records;
};
struct GedCanonicalCombinationAnalysis {
std::vector<GedCanonicalCombinationRecord> records;
std::vector<GedCanonicalCombinationGroup> groups;
size_t failures = 0;
};
/* Identify equivalent combination DAG nodes bottom-up. Primitive children
* supply identities and canonical-to-input placements established by the
* caller's selected transform mode. Database metadata participates in
* identity when include_metadata is true; geometry-only consumers such as
* facetize deliberately ignore it. */
GED_EXPORT int _ged_canonical_combination_analysis(
GedCanonicalCombinationAnalysis *analysis, struct db_i *dbip,
const std::vector<struct directory *> &combinations,
const std::map<struct directory *, GedCanonicalChildInfo> &primitive_children,
const struct bn_tol *tol, bool include_metadata);
#endif
#endif /* LIBGED_CANONICALIZE_PRIVATE_H */
+3 -1
View File
@@ -1,12 +1,14 @@
set(PLUGIN_LIBS libged libbu)
set(PLUGIN_LIBS libged librt libbg libbu)
ged_plugin_library(ged-npush
npush.cpp
unpush.cpp
LIBS ${PLUGIN_LIBS}
)
cmakefiles(
CMakeLists.txt
unpush.cpp
)
# Local Variables:
+4 -1
View File
@@ -1708,11 +1708,14 @@ ged_npush_core(struct ged *gedp, int argc, const char *argv[])
#include "../include/plugin.h"
extern "C" int ged_unpush_core(struct ged *gedp, int argc, const char *argv[]);
#define GED_NPUSH_COMMANDS(X, XID) \
X(npush, ged_npush_core, GED_CMD_DEFAULT) \
X(unpush, ged_unpush_core, GED_CMD_DEFAULT) \
GED_DECLARE_COMMAND_SET(GED_NPUSH_COMMANDS)
GED_DECLARE_PLUGIN_MANIFEST("libged_npush", 1, GED_NPUSH_COMMANDS)
GED_DECLARE_PLUGIN_MANIFEST("libged_npush", 2, GED_NPUSH_COMMANDS)
// Local Variables:
// tab-width: 8
File diff suppressed because it is too large Load Diff
+4
View File
@@ -4,6 +4,9 @@ if(TARGET ged_test_tops)
endif(TARGET ged_test_tops)
brlcad_add_test(NAME ged_test_tops_moss COMMAND ged_test_tops "${CMAKE_CURRENT_SOURCE_DIR}/ged_tests.g")
brlcad_addexec(ged_test_canonicalize canonicalize.cpp libged TEST)
brlcad_add_test(NAME ged_canonicalize COMMAND ged_test_canonicalize)
brlcad_addexec(ged_test_rm ged_test_rm.c "libged;libwdb;librt;libbu" TEST)
if(TARGET ged_test_rm)
add_dependencies(ged_test_rm ged_plugins)
@@ -122,6 +125,7 @@ add_subdirectory(edit)
# in a separate file, since its bad input examples stand an excellent chance of
# breaking commands.
cmakefiles(
canonicalize.cpp
lint_tests.g
rt_search_tests.g
search_tests.g
+127
View File
@@ -0,0 +1,127 @@
/* C A N O N I C A L I Z E . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*/
#include "common.h"
#include <limits>
#include "bu/app.h"
#include "rt/func.h"
#include "../canonicalize_private.h"
namespace {
void
make_ell(struct rt_db_internal *intern, const point_t center,
const vect_t a, const vect_t b, const vect_t c)
{
RT_DB_INTERNAL_INIT(intern);
intern->idb_major_type = DB5_MAJORTYPE_BRLCAD;
intern->idb_minor_type = ID_ELL;
intern->idb_meth = &OBJ[ID_ELL];
BU_ALLOC(intern->idb_ptr, struct rt_ell_internal);
auto *ell = static_cast<struct rt_ell_internal *>(intern->idb_ptr);
ell->magic = RT_ELL_INTERNAL_MAGIC;
VMOVE(ell->v, center);
VMOVE(ell->a, a);
VMOVE(ell->b, b);
VMOVE(ell->c, c);
}
int
test_matrix_comparison(const struct bn_tol *tol)
{
mat_t a = MAT_INIT_IDN;
mat_t b = MAT_INIT_IDN;
const fastf_t epsilon = std::numeric_limits<fastf_t>::epsilon();
b[0] += 16.0 * epsilon;
if (!_ged_matrices_numerically_equal(a, b, tol))
return 1;
b[0] = a[0] + 0.5 * tol->perp;
if (_ged_matrices_numerically_equal(a, b, tol))
return 1;
b[0] = a[0];
b[3] = 0.5 * tol->dist;
return _ged_matrices_numerically_equal(a, b, tol) ? 1 : 0;
}
int
test_geometry_comparison(const struct bn_tol *tol)
{
const point_t center = VINIT_ZERO;
const vect_t x_axis = {2.0, 0.0, 0.0};
const vect_t y_axis = {0.0, 3.0, 0.0};
const vect_t z_axis = {0.0, 0.0, 4.0};
struct rt_db_internal a;
struct rt_db_internal equivalent;
make_ell(&a, center, x_axis, y_axis, z_axis);
make_ell(&equivalent, center, y_axis, x_axis, z_axis);
int failed = _ged_canonical_geometry_equal(&a, &equivalent, tol) ||
!_ged_primitive_geometry_equal(&a, &equivalent, tol);
rt_db_free_internal(&equivalent);
rt_db_free_internal(&a);
return failed;
}
int
test_transform(const struct bn_tol *tol)
{
const point_t center = VINIT_ZERO;
const vect_t x_axis = {2.0, 0.0, 0.0};
const vect_t y_axis = {0.0, 3.0, 0.0};
const vect_t z_axis = {0.0, 0.0, 4.0};
struct rt_db_internal input;
struct rt_db_internal transformed;
mat_t matrix = MAT_INIT_IDN;
make_ell(&input, center, x_axis, y_axis, z_axis);
matrix[0] = 0.0;
matrix[1] = -1.0;
matrix[4] = 1.0;
matrix[5] = 0.0;
MAT_DELTAS(matrix, 11.0, -7.0, 5.0);
RT_DB_INTERNAL_INIT(&transformed);
if (_ged_transform_primitive(&transformed, matrix, &input) != BRLCAD_OK) {
rt_db_free_internal(&input);
return 1;
}
int failed = !_ged_transformed_geometry_equal(&input, matrix,
&transformed, bn_mat_identity, tol);
rt_db_free_internal(&transformed);
rt_db_free_internal(&input);
return failed;
}
} /* namespace */
int
main(int UNUSED(argc), const char *argv[])
{
const struct bn_tol tol = BN_TOL_INIT_TOL;
bu_setprogname(argv[0]);
int failures = test_matrix_comparison(&tol) +
test_geometry_comparison(&tol) + test_transform(&tol);
if (failures)
bu_log("libged canonical geometry tests failed: %d\n", failures);
return failures ? 1 : 0;
}
+5
View File
@@ -161,6 +161,7 @@ set(
primitives/ars/ars_mirror.c
primitives/bot/bot.c
primitives/bot/bot_brep.cpp
primitives/bot/bot_canonicalize.cpp
primitives/bot/bot_edge.c
primitives/bot/bot_mirror.c
primitives/bot/bot_oriented_bbox.cpp
@@ -176,6 +177,7 @@ set(
primitives/bspline/bspline.cpp
primitives/bspline/bspline_brep.cpp
primitives/bspline/bspline_mirror.c
primitives/canonicalize.c
primitives/crofton.cpp
primitives/cline/cline.c
primitives/cline/cline_brep.cpp
@@ -228,6 +230,7 @@ set(
primitives/nmg/nmg_mirror.c
primitives/nmg/nmg_tri_mc.c
primitives/obj_adjust.c
primitives/obj_canonicalize.c
primitives/obj_curve.c
primitives/obj_describe.c
primitives/obj_export.c
@@ -325,6 +328,7 @@ set(
material/materialX.h
cut_hlbvh.h
prcomb.c
primitives/canonicalize_private.h
primitives/bot/bot_edge.h
primitives/bot/decimate_private.h
primitives/bot/bot_wireframe.cpp
@@ -482,6 +486,7 @@ brlcad_addlib(librt "${LIBRT_SOURCES}" "${RT_INCLUDE_DIRS}" "${RT_LOCAL_INCLUDE_
primitives/bot/edbot.c
primitives/bot/bot.c
primitives/bot/bot_brep.cpp
primitives/bot/bot_canonicalize.cpp
primitives/bot/bot_edge.c
primitives/bot/bot_mirror.c
primitives/bot/bot_oriented_bbox.cpp
+227
View File
@@ -1970,6 +1970,233 @@ rt_arb_mat(struct rt_db_internal *rop, const mat_t mat, const struct rt_db_inter
}
static int
arb_affine_frame(mat_t canonical_to_input, mat_t input_to_canonical,
const struct rt_arb_internal *arb, const struct bn_tol *tol)
{
vect_t first, second, third;
vect_t cross;
fastf_t first_mag = 0.0;
fastf_t cross_mag = 0.0;
int first_index = -1;
int second_index = -1;
int third_index = -1;
for (int i = 1; i < 8; i++) {
VSUB2(first, arb->pt[i], arb->pt[0]);
first_mag = MAGNITUDE(first);
if (first_mag > tol->dist) {
first_index = i;
break;
}
}
if (first_index < 0)
return 0;
for (int i = first_index + 1; i < 8; i++) {
fastf_t second_mag;
VSUB2(second, arb->pt[i], arb->pt[0]);
second_mag = MAGNITUDE(second);
if (second_mag <= tol->dist)
continue;
VCROSS(cross, first, second);
cross_mag = MAGNITUDE(cross);
if (cross_mag > first_mag * second_mag * tol->perp) {
second_index = i;
break;
}
}
if (second_index < 0)
return 0;
for (int i = second_index + 1; i < 8; i++) {
fastf_t third_mag;
VSUB2(third, arb->pt[i], arb->pt[0]);
third_mag = MAGNITUDE(third);
if (third_mag <= tol->dist)
continue;
if (fabs(VDOT(third, cross)) > third_mag * cross_mag * tol->perp) {
third_index = i;
break;
}
}
if (third_index < 0)
return 0;
VSUB2(first, arb->pt[first_index], arb->pt[0]);
VSUB2(second, arb->pt[second_index], arb->pt[0]);
VSUB2(third, arb->pt[third_index], arb->pt[0]);
MAT_IDN(canonical_to_input);
canonical_to_input[0] = first[X];
canonical_to_input[4] = first[Y];
canonical_to_input[8] = first[Z];
canonical_to_input[1] = second[X];
canonical_to_input[5] = second[Y];
canonical_to_input[9] = second[Z];
canonical_to_input[2] = third[X];
canonical_to_input[6] = third[Y];
canonical_to_input[10] = third[Z];
MAT_DELTAS_VEC(canonical_to_input, arb->pt[0]);
return bn_mat_inverse(input_to_canonical, canonical_to_input);
}
static int
arb_rigid_frame(mat_t canonical_to_input, mat_t input_to_canonical,
const struct rt_arb_internal *arb, const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
vect_t xaxis, yaxis, zaxis;
fastf_t longest_sq = 0.0;
fastf_t widest_sq = 0.0;
fastf_t highest = 0.0;
fastf_t uniform_scale = 1.0;
int x_index = -1;
int y_index = -1;
int z_index = -1;
for (int i = 1; i < 8; i++) {
vect_t delta;
fastf_t distance_sq;
VSUB2(delta, arb->pt[i], arb->pt[0]);
distance_sq = MAGSQ(delta);
if (distance_sq > longest_sq + tol->dist_sq) {
longest_sq = distance_sq;
x_index = i;
}
}
if (x_index < 0 || longest_sq <= tol->dist_sq)
return 0;
VSUB2(xaxis, arb->pt[x_index], arb->pt[0]);
VUNITIZE(xaxis);
for (int i = 1; i < 8; i++) {
vect_t delta, perpendicular;
fastf_t width_sq;
VSUB2(delta, arb->pt[i], arb->pt[0]);
VJOIN1(perpendicular, delta, -VDOT(delta, xaxis), xaxis);
width_sq = MAGSQ(perpendicular);
if (width_sq > widest_sq + tol->dist_sq) {
widest_sq = width_sq;
VMOVE(yaxis, perpendicular);
y_index = i;
}
}
if (y_index < 0 || widest_sq <= tol->dist_sq)
return 0;
VUNITIZE(yaxis);
VCROSS(zaxis, xaxis, yaxis);
for (int i = 1; i < 8; i++) {
vect_t delta;
fastf_t height;
VSUB2(delta, arb->pt[i], arb->pt[0]);
height = fabs(VDOT(delta, zaxis));
if (height > highest + tol->dist) {
highest = height;
z_index = i;
}
}
if (z_index < 0 || highest <= tol->dist)
return 0;
{
vect_t delta;
VSUB2(delta, arb->pt[z_index], arb->pt[0]);
if (VDOT(delta, zaxis) < 0.0) {
VREVERSE(yaxis, yaxis);
VREVERSE(zaxis, zaxis);
}
}
if (mode == RT_CANONICALIZE_SIMILARITY) {
fastf_t diameter_sq = 0.0;
for (int i = 0; i < 7; i++) {
for (int j = i + 1; j < 8; j++) {
vect_t delta;
VSUB2(delta, arb->pt[j], arb->pt[i]);
diameter_sq = fmax(diameter_sq, MAGSQ(delta));
}
}
uniform_scale = sqrt(diameter_sq);
if (uniform_scale <= tol->dist)
return 0;
}
MAT_IDN(canonical_to_input);
canonical_to_input[0] = xaxis[X];
canonical_to_input[4] = xaxis[Y];
canonical_to_input[8] = xaxis[Z];
canonical_to_input[1] = yaxis[X];
canonical_to_input[5] = yaxis[Y];
canonical_to_input[9] = yaxis[Z];
canonical_to_input[2] = zaxis[X];
canonical_to_input[6] = zaxis[Y];
canonical_to_input[10] = zaxis[Z];
MAT_DELTAS_VEC(canonical_to_input, arb->pt[0]);
canonical_to_input[15] = 1.0 / uniform_scale;
canonical_to_input[3] /= uniform_scale;
canonical_to_input[7] /= uniform_scale;
canonical_to_input[11] /= uniform_scale;
return bn_mat_inverse(input_to_canonical, canonical_to_input);
}
C_DECL int
rt_arb_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
const struct rt_arb_internal *aip;
struct rt_arb_internal *caip;
mat_t input_to_canonical;
int frame_ok;
int validation_issues = 0;
if (!canonical || !canonical_to_input || !input || !tol)
return RT_CANONICALIZE_ERROR;
if (mode < RT_CANONICALIZE_RIGID || mode > RT_CANONICALIZE_AFFINE)
return RT_CANONICALIZE_ERROR;
if (input->idb_type != ID_ARB8)
return RT_CANONICALIZE_ERROR;
aip = (const struct rt_arb_internal *)input->idb_ptr;
RT_ARB_CK_MAGIC(aip);
for (int i = 0; i < 8; i++) {
if (!isfinite(aip->pt[i][X]) || !isfinite(aip->pt[i][Y]) ||
!isfinite(aip->pt[i][Z]))
return RT_CANONICALIZE_ERROR;
}
(void)rt_arb_validate(NULL, aip, tol, &validation_issues);
/* Historic databases contain usable non-standard vertex encodings. The
* index-stable factorization preserves those encodings exactly. */
if (validation_issues & ~RT_ARB_VALIDATE_NONSTANDARD)
return RT_CANONICALIZE_ERROR;
if (mode == RT_CANONICALIZE_AFFINE)
frame_ok = arb_affine_frame(canonical_to_input, input_to_canonical, aip, tol);
else
frame_ok = arb_rigid_frame(canonical_to_input, input_to_canonical,
aip, tol, mode);
if (!frame_ok)
return RT_CANONICALIZE_ERROR;
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = ID_ARB8;
canonical->idb_meth = &OBJ[ID_ARB8];
BU_ALLOC(canonical->idb_ptr, struct rt_arb_internal);
caip = (struct rt_arb_internal *)canonical->idb_ptr;
caip->magic = RT_ARB_INTERNAL_MAGIC;
for (int i = 0; i < 8; i++)
MAT4X3PNT(caip->pt[i], input_to_canonical, aip->pt[i]);
return RT_CANONICALIZE_OK;
}
/**
* Import an arb from the db5 format and convert to the internal
* structure. Code duplicated from rt_arb_import4() with db5 help from
@@ -0,0 +1,283 @@
/* B O T _ C A N O N I C A L I Z E . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
#include "common.h"
#include <cmath>
#include "bg/pca.h"
#include "raytrace.h"
#include "rt/primitives/bot.h"
namespace {
constexpr size_t FRAME_AXIS_COUNT = 3;
constexpr size_t FRAME_PERMUTATION_COUNT = 6;
const size_t frame_permutations[FRAME_PERMUTATION_COUNT][FRAME_AXIS_COUNT] = {
{0, 1, 2},
{0, 2, 1},
{1, 0, 2},
{1, 2, 0},
{2, 0, 1},
{2, 1, 0}
};
const int frame_permutation_parity[FRAME_PERMUTATION_COUNT] = {
1, -1, -1, 1, 1, -1
};
bool
bot_input_valid(const struct rt_bot_internal *bot)
{
if (!bot || bot->num_vertices < 3 || !bot->vertices ||
(bot->num_faces && !bot->faces))
return false;
for (size_t i = 0; i < bot->num_vertices; i++) {
if (VINVALID(&bot->vertices[3 * i]))
return false;
}
for (size_t i = 0; i < 3 * bot->num_faces; i++) {
if (bot->faces[i] < 0 || static_cast<size_t>(bot->faces[i]) >= bot->num_vertices)
return false;
}
return true;
}
void
proper_pca_frame(vect_t axes[FRAME_AXIS_COUNT])
{
vect_t proper_z;
VCROSS(proper_z, axes[X], axes[Y]);
VUNITIZE(proper_z);
VMOVE(axes[Z], proper_z);
}
bool
pca_frame_is_unique(const vect_t singular_values, size_t point_count,
const struct bn_tol *tol)
{
fastf_t singular_tolerance = tol->dist * std::sqrt(static_cast<fastf_t>(point_count));
return !NEAR_EQUAL(singular_values[X], singular_values[Y], singular_tolerance) &&
!NEAR_EQUAL(singular_values[Y], singular_values[Z], singular_tolerance);
}
bool
anchor_frame(vect_t axes[FRAME_AXIS_COUNT], const point_t center,
const struct rt_bot_internal *bot, const struct bn_tol *tol)
{
vect_t offset;
vect_t residual;
fastf_t farthest_sq = -1.0;
fastf_t widest_sq = -1.0;
size_t farthest = 0;
size_t widest = 0;
for (size_t i = 0; i < bot->num_vertices; i++) {
VSUB2(offset, &bot->vertices[3 * i], center);
fastf_t distance_sq = MAGSQ(offset);
if (distance_sq > farthest_sq + tol->dist_sq) {
farthest_sq = distance_sq;
farthest = i;
}
}
if (farthest_sq <= tol->dist_sq)
return false;
VSUB2(axes[X], &bot->vertices[3 * farthest], center);
VUNITIZE(axes[X]);
for (size_t i = 0; i < bot->num_vertices; i++) {
VSUB2(offset, &bot->vertices[3 * i], center);
VJOIN1(residual, offset, -VDOT(offset, axes[X]), axes[X]);
fastf_t residual_sq = MAGSQ(residual);
if (residual_sq > widest_sq + tol->dist_sq) {
widest_sq = residual_sq;
widest = i;
}
}
if (widest_sq <= tol->dist_sq)
return false;
VSUB2(offset, &bot->vertices[3 * widest], center);
VJOIN1(axes[Y], offset, -VDOT(offset, axes[X]), axes[X]);
VUNITIZE(axes[Y]);
VCROSS(axes[Z], axes[X], axes[Y]);
VUNITIZE(axes[Z]);
return true;
}
void
frame_matrix(mat_t matrix, const vect_t axes[FRAME_AXIS_COUNT],
const size_t permutation[FRAME_AXIS_COUNT], const int signs[FRAME_AXIS_COUNT],
const point_t center)
{
mat_t rotate;
mat_t translate;
MAT_IDN(rotate);
for (size_t row = 0; row < FRAME_AXIS_COUNT; row++)
VSCALE(&rotate[4 * row], axes[permutation[row]], signs[row]);
MAT_IDN(translate);
MAT_DELTAS_VEC_NEG(translate, center);
bn_mat_mul(matrix, rotate, translate);
}
fastf_t
comparison_coordinate(fastf_t coordinate, const struct bn_tol *tol)
{
return NEAR_ZERO(coordinate, tol->dist) ? 0.0 : coordinate;
}
bool
candidate_is_less(const mat_t candidate, const mat_t current,
const struct rt_bot_internal *bot, const struct bn_tol *tol)
{
point_t candidate_point;
point_t current_point;
for (size_t i = 0; i < bot->num_vertices; i++) {
MAT4X3PNT(candidate_point, candidate, &bot->vertices[3 * i]);
MAT4X3PNT(current_point, current, &bot->vertices[3 * i]);
for (size_t coordinate = 0; coordinate < ELEMENTS_PER_POINT; coordinate++) {
fastf_t candidate_value = comparison_coordinate(candidate_point[coordinate], tol);
fastf_t current_value = comparison_coordinate(current_point[coordinate], tol);
if (candidate_value < current_value - tol->dist)
return true;
if (candidate_value > current_value + tol->dist)
return false;
}
}
return false;
}
}
extern "C" int
rt_bot_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
const struct rt_bot_internal *bot;
struct rt_bot_internal *canonical_bot;
struct rt_db_internal transformed;
point_t center;
vect_t axes[FRAME_AXIS_COUNT];
vect_t singular_values;
mat_t candidate;
mat_t input_to_canonical;
bool have_candidate = false;
if (!canonical || !canonical_to_input || !input || !tol)
return RT_CANONICALIZE_ERROR;
if (mode < RT_CANONICALIZE_RIGID || mode > RT_CANONICALIZE_AFFINE)
return RT_CANONICALIZE_ERROR;
if (input->idb_type != ID_BOT)
return RT_CANONICALIZE_ERROR;
bot = static_cast<const struct rt_bot_internal *>(input->idb_ptr);
RT_BOT_CK_MAGIC(bot);
if (!bot_input_valid(bot))
return RT_CANONICALIZE_ERROR;
/* rt_bot_mat currently reconstructs only the common per-vertex normal
* layout. Decline other layouts until that transform contract is fixed. */
if ((bot->bot_flags & RT_BOT_HAS_SURFACE_NORMALS) &&
bot->num_normals != bot->num_vertices)
return RT_CANONICALIZE_UNSUPPORTED;
if (bg_pca_svd(&center, &axes[X], &axes[Y], &axes[Z], &singular_values,
bot->num_vertices, reinterpret_cast<const point_t *>(bot->vertices)) != BRLCAD_OK)
return RT_CANONICALIZE_ERROR;
if (pca_frame_is_unique(singular_values, bot->num_vertices, tol)) {
proper_pca_frame(axes);
} else if (!anchor_frame(axes, center, bot, tol)) {
return RT_CANONICALIZE_ERROR;
}
/* PCA axis signs are arbitrary, and equivalent parameterizations may
* permute axes. Search all proper signed permutations and retain the
* lexicographically smallest vertex sequence. This preserves BOT index
* topology while making pushed copies choose the same local frame. */
for (size_t permutation = 0; permutation < FRAME_PERMUTATION_COUNT; permutation++) {
for (int xsign = -1; xsign <= 1; xsign += 2) {
for (int ysign = -1; ysign <= 1; ysign += 2) {
int signs[FRAME_AXIS_COUNT] = {
xsign,
ysign,
frame_permutation_parity[permutation] * xsign * ysign
};
frame_matrix(candidate, axes, frame_permutations[permutation], signs, center);
if (!have_candidate || candidate_is_less(candidate, input_to_canonical, bot, tol)) {
MAT_COPY(input_to_canonical, candidate);
have_candidate = true;
}
}
}
}
canonical_bot = rt_bot_dup(bot);
if (!canonical_bot)
return RT_CANONICALIZE_ERROR;
RT_DB_INTERNAL_INIT(&transformed);
transformed.idb_major_type = DB5_MAJORTYPE_BRLCAD;
transformed.idb_minor_type = ID_BOT;
transformed.idb_meth = &OBJ[ID_BOT];
transformed.idb_ptr = canonical_bot;
if (OBJ[ID_BOT].ft_mat(&transformed, input_to_canonical, input) != BRLCAD_OK) {
rt_bot_internal_free(canonical_bot);
return RT_CANONICALIZE_ERROR;
}
bn_mat_inv(canonical_to_input, input_to_canonical);
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = ID_BOT;
canonical->idb_meth = &OBJ[ID_BOT];
canonical->idb_ptr = canonical_bot;
return RT_CANONICALIZE_OK;
}
/*
* Local Variables:
* mode: C++
* tab-width: 8
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+93
View File
@@ -0,0 +1,93 @@
/* C A N O N I C A L I Z E . C
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*/
#include "common.h"
#include <math.h>
#include "bn/mat.h"
#include "vmath.h"
#include "canonicalize_private.h"
int
rt_make_canonical_similarity_frame(mat_t canonical_to_input,
fastf_t *canonical_lengths, const point_t origin,
const vect_t input_x, const vect_t input_z, const fastf_t *lengths,
size_t length_count, const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
vect_t x_axis;
vect_t y_axis;
vect_t z_axis;
fastf_t x_length;
fastf_t z_length;
fastf_t projection;
fastf_t uniform_scale = mode == RT_CANONICALIZE_RIGID ? 1.0 : 0.0;
mat_t orientation;
mat_t scale;
mat_t oriented_scale;
mat_t translate;
if (!canonical_to_input || !canonical_lengths || !origin || !input_x ||
!input_z || !lengths || !length_count || !tol ||
mode < RT_CANONICALIZE_RIGID || mode > RT_CANONICALIZE_AFFINE)
return 0;
if (!isfinite(origin[X]) || !isfinite(origin[Y]) ||
!isfinite(origin[Z]))
return 0;
x_length = MAGNITUDE(input_x);
z_length = MAGNITUDE(input_z);
if (!isfinite(x_length) || !isfinite(z_length) ||
x_length <= tol->dist || z_length <= tol->dist)
return 0;
VSCALE(x_axis, input_x, 1.0 / x_length);
VSCALE(z_axis, input_z, 1.0 / z_length);
projection = VDOT(x_axis, z_axis);
if (!NEAR_ZERO(projection, tol->perp))
return 0;
/* Remove accepted input roundoff so write-mode matrices are proper
* rotations rather than barely-sheared approximations. */
VJOIN1(x_axis, x_axis, -projection, z_axis);
VUNITIZE(x_axis);
VCROSS(y_axis, z_axis, x_axis);
VUNITIZE(y_axis);
for (size_t i = 0; i < length_count; i++) {
if (!isfinite(lengths[i]) || lengths[i] <= tol->dist)
return 0;
if (mode != RT_CANONICALIZE_RIGID)
uniform_scale = fmax(uniform_scale, lengths[i]);
}
for (size_t i = 0; i < length_count; i++)
canonical_lengths[i] = lengths[i] / uniform_scale;
MAT_IDN(orientation);
orientation[0] = x_axis[X];
orientation[4] = x_axis[Y];
orientation[8] = x_axis[Z];
orientation[1] = y_axis[X];
orientation[5] = y_axis[Y];
orientation[9] = y_axis[Z];
orientation[2] = z_axis[X];
orientation[6] = z_axis[Y];
orientation[10] = z_axis[Z];
MAT_IDN(scale);
scale[15] = 1.0 / uniform_scale;
bn_mat_mul(oriented_scale, orientation, scale);
MAT_IDN(translate);
MAT_DELTAS_VEC(translate, origin);
bn_mat_mul(canonical_to_input, translate, oriented_scale);
return 1;
}
@@ -0,0 +1,33 @@
/* C A N O N I C A L I Z E _ P R I V A T E . H
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*/
#ifndef LIBRT_PRIMITIVES_CANONICALIZE_PRIVATE_H
#define LIBRT_PRIMITIVES_CANONICALIZE_PRIVATE_H
#include "common.h"
#include <stddef.h>
#include "rt/functab.h"
/* Construct a right-handed frame whose local X and Z axes follow two
* perpendicular primitive directions. Scalar-radius primitives cannot
* represent a nonuniform affine transform in ft_mat, so affine requests use
* the strongest exactly reconstructable subset: a similarity transform. */
int
rt_make_canonical_similarity_frame(mat_t canonical_to_input,
fastf_t *canonical_lengths, const point_t origin,
const vect_t input_x, const vect_t input_z, const fastf_t *lengths,
size_t length_count, const struct bn_tol *tol,
enum rt_canonicalize_mode mode);
#endif /* LIBRT_PRIMITIVES_CANONICALIZE_PRIVATE_H */
+43
View File
@@ -162,6 +162,7 @@
#include "raytrace.h"
#include "../../librt_private.h"
#include "../canonicalize_private.h"
struct ehy_specific {
@@ -2118,6 +2119,48 @@ rt_ehy_mat(struct rt_db_internal *rop, const mat_t mat, const struct rt_db_inter
return BRLCAD_OK;
}
C_DECL int
rt_ehy_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
const struct rt_ehy_internal *eip;
struct rt_ehy_internal *ceip;
fastf_t lengths[4];
fastf_t canonical_lengths[4];
if (!canonical || !canonical_to_input || !input || !tol ||
input->idb_type != ID_EHY)
return RT_CANONICALIZE_ERROR;
eip = (const struct rt_ehy_internal *)input->idb_ptr;
RT_EHY_CK_MAGIC(eip);
lengths[0] = MAGNITUDE(eip->ehy_H);
lengths[1] = eip->ehy_r1;
lengths[2] = eip->ehy_r2;
lengths[3] = eip->ehy_c;
if (!rt_make_canonical_similarity_frame(canonical_to_input,
canonical_lengths, eip->ehy_V, eip->ehy_Au, eip->ehy_H,
lengths, 4, tol, mode))
return RT_CANONICALIZE_ERROR;
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = ID_EHY;
canonical->idb_meth = &OBJ[ID_EHY];
BU_ALLOC(canonical->idb_ptr, struct rt_ehy_internal);
ceip = (struct rt_ehy_internal *)canonical->idb_ptr;
ceip->ehy_magic = RT_EHY_INTERNAL_MAGIC;
VSETALL(ceip->ehy_V, 0.0);
VSET(ceip->ehy_H, 0.0, 0.0, canonical_lengths[0]);
VSET(ceip->ehy_Au, 1.0, 0.0, 0.0);
ceip->ehy_r1 = canonical_lengths[1];
ceip->ehy_r2 = canonical_lengths[2];
ceip->ehy_c = canonical_lengths[3];
return RT_CANONICALIZE_OK;
}
int
rt_ehy_functab_validate(struct bu_vls *error_msg, const struct rt_db_internal *ip, const struct bn_tol *tol)
{
+150
View File
@@ -1423,6 +1423,156 @@ rt_ell_mat(struct rt_db_internal *rop, const mat_t mat, const struct rt_db_inter
return BRLCAD_OK;
}
struct ell_canonical_axis {
vect_t dir;
fastf_t length;
size_t input_index;
};
static void
ell_canonical_axis_swap(struct ell_canonical_axis *a, struct ell_canonical_axis *b)
{
struct ell_canonical_axis tmp;
VMOVE(tmp.dir, a->dir);
tmp.length = a->length;
tmp.input_index = a->input_index;
VMOVE(a->dir, b->dir);
a->length = b->length;
a->input_index = b->input_index;
VMOVE(b->dir, tmp.dir);
b->length = tmp.length;
b->input_index = tmp.input_index;
}
static void
ell_canonical_axes_sort(struct ell_canonical_axis axes[3], const struct bn_tol *tol)
{
for (size_t i = 0; i < 2; i++) {
for (size_t j = i + 1; j < 3; j++) {
if (axes[j].length > axes[i].length + tol->dist ||
(NEAR_EQUAL(axes[j].length, axes[i].length, tol->dist) &&
axes[j].input_index < axes[i].input_index))
ell_canonical_axis_swap(&axes[i], &axes[j]);
}
}
}
static void
ell_canonical_orientation(mat_t orientation, const struct ell_canonical_axis axes[3])
{
MAT_IDN(orientation);
orientation[0] = axes[0].dir[X];
orientation[4] = axes[0].dir[Y];
orientation[8] = axes[0].dir[Z];
orientation[1] = axes[1].dir[X];
orientation[5] = axes[1].dir[Y];
orientation[9] = axes[1].dir[Z];
orientation[2] = axes[2].dir[X];
orientation[6] = axes[2].dir[Y];
orientation[10] = axes[2].dir[Z];
}
C_DECL int
rt_ell_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
const struct rt_ell_internal *eip;
struct rt_ell_internal *ceip;
struct ell_canonical_axis axes[3];
const fastf_t *input_axes[3];
vect_t cross;
mat_t orientation;
mat_t scale_mat;
mat_t oriented_scale;
mat_t translate;
fastf_t canonical_lengths[3];
fastf_t uniform_scale = 1.0;
if (!canonical || !canonical_to_input || !input || !tol)
return RT_CANONICALIZE_ERROR;
if (mode < RT_CANONICALIZE_RIGID || mode > RT_CANONICALIZE_AFFINE)
return RT_CANONICALIZE_ERROR;
if (input->idb_type != ID_ELL && input->idb_type != ID_SPH)
return RT_CANONICALIZE_ERROR;
eip = (const struct rt_ell_internal *)input->idb_ptr;
RT_ELL_CK_MAGIC(eip);
input_axes[0] = eip->a;
input_axes[1] = eip->b;
input_axes[2] = eip->c;
for (size_t i = 0; i < 3; i++) {
axes[i].length = MAGNITUDE(input_axes[i]);
if (axes[i].length <= tol->dist)
return RT_CANONICALIZE_ERROR;
VSCALE(axes[i].dir, input_axes[i], 1.0 / axes[i].length);
axes[i].input_index = i;
}
if (!NEAR_ZERO(VDOT(axes[0].dir, axes[1].dir), tol->perp) ||
!NEAR_ZERO(VDOT(axes[0].dir, axes[2].dir), tol->perp) ||
!NEAR_ZERO(VDOT(axes[1].dir, axes[2].dir), tol->perp))
return RT_CANONICALIZE_ERROR;
ell_canonical_axes_sort(axes, tol);
/* Axis signs do not alter ellipsoid geometry. Select the sign of the
* third axis so the placement matrix is a proper rotation. */
VCROSS(cross, axes[0].dir, axes[1].dir);
if (VDOT(cross, axes[2].dir) < 0.0)
VREVERSE(axes[2].dir, axes[2].dir);
if (mode == RT_CANONICALIZE_AFFINE) {
VSETALL(canonical_lengths, 1.0);
for (size_t i = 0; i < 3; i++)
VSCALE(axes[i].dir, axes[i].dir, axes[i].length);
ell_canonical_orientation(orientation, axes);
MAT_IDN(translate);
MAT_DELTAS_VEC(translate, eip->v);
bn_mat_mul(canonical_to_input, translate, orientation);
} else {
if (mode == RT_CANONICALIZE_SIMILARITY)
uniform_scale = axes[0].length;
for (size_t i = 0; i < 3; i++)
canonical_lengths[i] = axes[i].length / uniform_scale;
ell_canonical_orientation(orientation, axes);
MAT_IDN(scale_mat);
scale_mat[15] = 1.0 / uniform_scale;
bn_mat_mul(oriented_scale, orientation, scale_mat);
MAT_IDN(translate);
MAT_DELTAS_VEC(translate, eip->v);
bn_mat_mul(canonical_to_input, translate, oriented_scale);
}
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = input->idb_minor_type;
canonical->idb_meth = &OBJ[input->idb_minor_type];
BU_ALLOC(canonical->idb_ptr, struct rt_ell_internal);
ceip = (struct rt_ell_internal *)canonical->idb_ptr;
ceip->magic = RT_ELL_INTERNAL_MAGIC;
VSETALL(ceip->v, 0.0);
VSET(ceip->a, canonical_lengths[0], 0.0, 0.0);
VSET(ceip->b, 0.0, canonical_lengths[1], 0.0);
VSET(ceip->c, 0.0, 0.0, canonical_lengths[2]);
return RT_CANONICALIZE_OK;
}
/**
* Import an ellipsoid/sphere from the database format to the internal
* structure. Apply modeling transformations as well.
+41
View File
@@ -159,6 +159,7 @@
#include "raytrace.h"
#include "../../librt_private.h"
#include "../canonicalize_private.h"
static int epa_is_valid(struct rt_epa_internal *epa);
@@ -2044,6 +2045,46 @@ rt_epa_mat(struct rt_db_internal *rop, const mat_t mat, const struct rt_db_inter
return BRLCAD_OK;
}
C_DECL int
rt_epa_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
const struct rt_epa_internal *eip;
struct rt_epa_internal *ceip;
fastf_t lengths[3];
fastf_t canonical_lengths[3];
if (!canonical || !canonical_to_input || !input || !tol ||
input->idb_type != ID_EPA)
return RT_CANONICALIZE_ERROR;
eip = (const struct rt_epa_internal *)input->idb_ptr;
RT_EPA_CK_MAGIC(eip);
lengths[0] = MAGNITUDE(eip->epa_H);
lengths[1] = eip->epa_r1;
lengths[2] = eip->epa_r2;
if (!rt_make_canonical_similarity_frame(canonical_to_input,
canonical_lengths, eip->epa_V, eip->epa_Au, eip->epa_H,
lengths, 3, tol, mode))
return RT_CANONICALIZE_ERROR;
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = ID_EPA;
canonical->idb_meth = &OBJ[ID_EPA];
BU_ALLOC(canonical->idb_ptr, struct rt_epa_internal);
ceip = (struct rt_epa_internal *)canonical->idb_ptr;
ceip->epa_magic = RT_EPA_INTERNAL_MAGIC;
VSETALL(ceip->epa_V, 0.0);
VSET(ceip->epa_H, 0.0, 0.0, canonical_lengths[0]);
VSET(ceip->epa_Au, 1.0, 0.0, 0.0);
ceip->epa_r1 = canonical_lengths[1];
ceip->epa_r2 = canonical_lengths[2];
return RT_CANONICALIZE_OK;
}
/**
* Import an EPA from the database format to the internal format.
* Apply modeling transformations as well.
+111
View File
@@ -1931,6 +1931,117 @@ rt_eto_mat(struct rt_db_internal *rop, const mat_t mat, const struct rt_db_inter
}
C_DECL int
rt_eto_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
const struct rt_eto_internal *eip;
struct rt_eto_internal *ceip;
vect_t normal, horizontal, xaxis, yaxis;
mat_t orientation;
mat_t scale_mat;
mat_t oriented_scale;
mat_t translate;
fastf_t normal_mag;
fastf_t major_mag;
fastf_t vertical_mag;
fastf_t horizontal_mag;
fastf_t horizontal_sq;
fastf_t uniform_scale = 1.0;
if (!canonical || !canonical_to_input || !input || !tol)
return RT_CANONICALIZE_ERROR;
if (mode < RT_CANONICALIZE_RIGID || mode > RT_CANONICALIZE_AFFINE)
return RT_CANONICALIZE_ERROR;
if (input->idb_type != ID_ETO)
return RT_CANONICALIZE_ERROR;
eip = (const struct rt_eto_internal *)input->idb_ptr;
RT_ETO_CK_MAGIC(eip);
normal_mag = MAGNITUDE(eip->eto_N);
major_mag = MAGNITUDE(eip->eto_C);
if (!isfinite(eip->eto_V[X]) || !isfinite(eip->eto_V[Y]) ||
!isfinite(eip->eto_V[Z]) || !isfinite(normal_mag) ||
!isfinite(major_mag) || !isfinite(eip->eto_r) ||
!isfinite(eip->eto_rd) || normal_mag <= tol->dist ||
major_mag <= tol->dist || eip->eto_r <= tol->dist ||
eip->eto_rd <= tol->dist || eip->eto_rd > major_mag)
return RT_CANONICALIZE_ERROR;
VSCALE(normal, eip->eto_N, 1.0 / normal_mag);
vertical_mag = VDOT(eip->eto_C, normal);
if (vertical_mag < 0.0) {
VREVERSE(normal, normal);
vertical_mag = -vertical_mag;
}
VJOIN1(horizontal, eip->eto_C, -vertical_mag, normal);
horizontal_sq = MAGSQ(horizontal);
horizontal_mag = sqrt(fmax(0.0, horizontal_sq));
if (horizontal_mag > SMALL_FASTF) {
VSCALE(xaxis, horizontal, 1.0 / horizontal_mag);
} else {
/* A vertical C leaves rotation around N unconstrained. Project the
* least-aligned world axis so canonical input still produces an identity
* placement while other orientations remain numerically stable. */
vect_t reference = VINIT_ZERO;
if (fabs(normal[X]) <= fabs(normal[Y]) && fabs(normal[X]) <= fabs(normal[Z]))
reference[X] = 1.0;
else if (fabs(normal[Y]) <= fabs(normal[Z]))
reference[Y] = 1.0;
else
reference[Z] = 1.0;
VJOIN1(xaxis, reference, -VDOT(reference, normal), normal);
VUNITIZE(xaxis);
VSETALL(horizontal, 0.0);
horizontal_mag = 0.0;
}
VCROSS(yaxis, normal, xaxis);
VUNITIZE(yaxis);
/* rt_eto_mat can apply only rigid and uniform-scale placements to r and
* rd. AFFINE therefore intentionally uses the strongest transform that
* this primitive can reconstruct exactly: a similarity. */
if (mode != RT_CANONICALIZE_RIGID)
uniform_scale = eip->eto_r;
MAT_IDN(orientation);
orientation[0] = xaxis[X];
orientation[4] = xaxis[Y];
orientation[8] = xaxis[Z];
orientation[1] = yaxis[X];
orientation[5] = yaxis[Y];
orientation[9] = yaxis[Z];
orientation[2] = normal[X];
orientation[6] = normal[Y];
orientation[10] = normal[Z];
MAT_IDN(scale_mat);
scale_mat[15] = 1.0 / uniform_scale;
bn_mat_mul(oriented_scale, orientation, scale_mat);
MAT_IDN(translate);
MAT_DELTAS_VEC(translate, eip->eto_V);
bn_mat_mul(canonical_to_input, translate, oriented_scale);
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = ID_ETO;
canonical->idb_meth = &OBJ[ID_ETO];
BU_ALLOC(canonical->idb_ptr, struct rt_eto_internal);
ceip = (struct rt_eto_internal *)canonical->idb_ptr;
ceip->eto_magic = RT_ETO_INTERNAL_MAGIC;
VSETALL(ceip->eto_V, 0.0);
VSET(ceip->eto_N, 0.0, 0.0, 1.0);
VSET(ceip->eto_C, horizontal_mag / uniform_scale, 0.0,
vertical_mag / uniform_scale);
ceip->eto_r = eip->eto_r / uniform_scale;
ceip->eto_rd = eip->eto_rd / uniform_scale;
return RT_CANONICALIZE_OK;
}
/**
* Import a eto from the database format to the internal format.
* Apply modeling transformations at the same time.
+55
View File
@@ -653,6 +653,61 @@ rt_hlf_mat(struct rt_db_internal *rop, const mat_t mat, const struct rt_db_inter
return 0; /* OK */
}
C_DECL int
rt_hlf_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
struct rt_half_internal *hip;
struct rt_half_internal *chip;
vect_t normal;
vect_t zaxis = VINIT_ZERO;
point_t plane_point;
mat_t rotate;
mat_t translate;
fastf_t normal_mag;
fastf_t plane_dist;
if (!canonical || !canonical_to_input || !input || !tol)
return RT_CANONICALIZE_ERROR;
if (mode < RT_CANONICALIZE_RIGID || mode > RT_CANONICALIZE_AFFINE)
return RT_CANONICALIZE_ERROR;
if (input->idb_type != ID_HALF)
return RT_CANONICALIZE_ERROR;
hip = (struct rt_half_internal *)input->idb_ptr;
RT_HALF_CK_MAGIC(hip);
VMOVE(normal, hip->eqn);
normal_mag = MAGNITUDE(normal);
if (normal_mag <= tol->dist)
return RT_CANONICALIZE_ERROR;
VSCALE(normal, normal, 1.0 / normal_mag);
plane_dist = hip->eqn[W] / normal_mag;
VSCALE(plane_point, normal, plane_dist);
VSET(zaxis, 0.0, 0.0, 1.0);
bn_mat_fromto(rotate, zaxis, normal, tol);
MAT_IDN(translate);
MAT_DELTAS_VEC(translate, plane_point);
bn_mat_mul(canonical_to_input, translate, rotate);
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = ID_HALF;
canonical->idb_meth = &OBJ[ID_HALF];
BU_ALLOC(canonical->idb_ptr, struct rt_half_internal);
chip = (struct rt_half_internal *)canonical->idb_ptr;
chip->magic = RT_HALF_INTERNAL_MAGIC;
VSET(chip->eqn, 0.0, 0.0, 1.0);
chip->eqn[W] = 0.0;
return RT_CANONICALIZE_OK;
}
C_DECL int
rt_hlf_import5(struct rt_db_internal *ip, const struct bu_external *ep, register const fastf_t *mat, const struct db_i *dbip)
{
+68
View File
@@ -0,0 +1,68 @@
/* O B J _ C A N O N I C A L I Z E . C
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this file; see the file named COPYING for more
* information.
*/
#include "common.h"
#include "raytrace.h"
#include "rt/func.h"
int
rt_obj_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
const struct rt_functab *ft;
if (!canonical || !canonical_to_input || !input || !tol)
return RT_CANONICALIZE_ERROR;
RT_CK_DB_INTERNAL(canonical);
RT_CK_DB_INTERNAL(input);
BN_CK_TOL(tol);
if (canonical->idb_ptr || canonical->idb_meth ||
canonical->idb_major_type != -1 || canonical->idb_minor_type != -1)
return RT_CANONICALIZE_ERROR;
if (mode < RT_CANONICALIZE_RIGID || mode > RT_CANONICALIZE_AFFINE)
return RT_CANONICALIZE_ERROR;
if (input->idb_minor_type < 0 || input->idb_minor_type > ID_MAX_SOLID)
return RT_CANONICALIZE_ERROR;
ft = &OBJ[input->idb_minor_type];
if (!ft->ft_canonicalize)
return RT_CANONICALIZE_UNSUPPORTED;
return ft->ft_canonicalize(canonical, canonical_to_input, input, tol, mode);
}
/*
* Local Variables:
* mode: C
* tab-width: 8
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+105
View File
@@ -191,6 +191,7 @@
#include "raytrace.h"
#include "nmg.h"
#include "../../librt_private.h"
#include "../canonicalize_private.h"
struct part_specific {
@@ -1978,6 +1979,110 @@ rt_part_mat(struct rt_db_internal *rop, const mat_t mat, const struct rt_db_inte
}
C_DECL int
rt_part_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
const struct rt_part_internal *pip;
struct rt_part_internal *cpip;
point_t origin;
vect_t axis;
vect_t x_axis;
fastf_t vertex_radius;
fastf_t height_radius;
fastf_t height_length;
fastf_t maximum_radius;
fastf_t uniform_scale;
if (!canonical || !canonical_to_input || !input || !tol ||
input->idb_type != ID_PARTICLE || mode < RT_CANONICALIZE_RIGID ||
mode > RT_CANONICALIZE_AFFINE)
return RT_CANONICALIZE_ERROR;
pip = (const struct rt_part_internal *)input->idb_ptr;
RT_PART_CK_MAGIC(pip);
if (!isfinite(pip->part_V[X]) || !isfinite(pip->part_V[Y]) ||
!isfinite(pip->part_V[Z]) || !isfinite(pip->part_H[X]) ||
!isfinite(pip->part_H[Y]) || !isfinite(pip->part_H[Z]) ||
!isfinite(pip->part_vrad) || !isfinite(pip->part_hrad) ||
pip->part_vrad < 0.0 || pip->part_hrad < 0.0)
return RT_CANONICALIZE_ERROR;
height_length = MAGNITUDE(pip->part_H);
maximum_radius = fmax(pip->part_vrad, pip->part_hrad);
if (!isfinite(height_length) || maximum_radius <= tol->dist)
return RT_CANONICALIZE_ERROR;
uniform_scale = mode == RT_CANONICALIZE_RIGID ? 1.0 :
fmax(height_length, maximum_radius);
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = ID_PARTICLE;
canonical->idb_meth = &OBJ[ID_PARTICLE];
BU_ALLOC(canonical->idb_ptr, struct rt_part_internal);
cpip = (struct rt_part_internal *)canonical->idb_ptr;
cpip->part_magic = RT_PART_INTERNAL_MAGIC;
cpip->part_type = pip->part_type;
VSETALL(cpip->part_V, 0.0);
/* Imported particle spheres have H cleared by rt_part_mat. Do not turn a
* merely short cone into a different solid just because its height falls
* below the caller's modeling tolerance; the frame helper will reject
* nonzero axes that are too short to orient reliably. */
if (height_length <= SMALL_FASTF) {
mat_t scale;
mat_t translate;
MAT_IDN(scale);
scale[15] = 1.0 / uniform_scale;
MAT_IDN(translate);
MAT_DELTAS_VEC(translate, pip->part_V);
bn_mat_mul(canonical_to_input, translate, scale);
VSETALL(cpip->part_H, 0.0);
cpip->part_vrad = maximum_radius / uniform_scale;
cpip->part_hrad = cpip->part_vrad;
cpip->part_type = RT_PARTICLE_TYPE_SPHERE;
return RT_CANONICALIZE_OK;
}
if (pip->part_vrad <= pip->part_hrad) {
VMOVE(origin, pip->part_V);
VMOVE(axis, pip->part_H);
vertex_radius = pip->part_vrad;
height_radius = pip->part_hrad;
} else {
VADD2(origin, pip->part_V, pip->part_H);
VREVERSE(axis, pip->part_H);
vertex_radius = pip->part_hrad;
height_radius = pip->part_vrad;
}
/* Canonical input must retain an identity placement. Other axial
* directions may use any deterministic perpendicular because PART is
* rotationally symmetric about H. */
if (NEAR_ZERO(axis[X], tol->perp) &&
NEAR_ZERO(axis[Y], tol->perp) && axis[Z] > 0.0)
VSET(x_axis, 1.0, 0.0, 0.0);
else
bn_vec_ortho(x_axis, axis);
{
const fastf_t frame_lengths[2] = {height_length, maximum_radius};
fastf_t canonical_frame_lengths[2];
if (!rt_make_canonical_similarity_frame(canonical_to_input,
canonical_frame_lengths, origin, x_axis, axis, frame_lengths, 2,
tol, mode)) {
rt_db_free_internal(canonical);
return RT_CANONICALIZE_ERROR;
}
}
VSET(cpip->part_H, 0.0, 0.0, height_length / uniform_scale);
cpip->part_vrad = vertex_radius / uniform_scale;
cpip->part_hrad = height_radius / uniform_scale;
return RT_CANONICALIZE_OK;
}
C_DECL int
rt_part_import5(struct rt_db_internal *ip, const struct bu_external *ep, register const fastf_t *mat, const struct db_i *dbip)
{
+42
View File
@@ -174,6 +174,7 @@
#include "raytrace.h"
#include "../../librt_private.h"
#include "../canonicalize_private.h"
static int
rhc_is_valid(struct rt_rhc_internal *rhc);
@@ -1932,6 +1933,47 @@ rt_rhc_mat(struct rt_db_internal *rop, const mat_t mat, const struct rt_db_inter
return BRLCAD_OK;
}
C_DECL int
rt_rhc_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
const struct rt_rhc_internal *rip;
struct rt_rhc_internal *crip;
fastf_t lengths[4];
fastf_t canonical_lengths[4];
if (!canonical || !canonical_to_input || !input || !tol ||
input->idb_type != ID_RHC)
return RT_CANONICALIZE_ERROR;
rip = (const struct rt_rhc_internal *)input->idb_ptr;
RT_RHC_CK_MAGIC(rip);
lengths[0] = MAGNITUDE(rip->rhc_B);
lengths[1] = MAGNITUDE(rip->rhc_H);
lengths[2] = rip->rhc_r;
lengths[3] = rip->rhc_c;
if (!rt_make_canonical_similarity_frame(canonical_to_input,
canonical_lengths, rip->rhc_V, rip->rhc_B, rip->rhc_H, lengths,
4, tol, mode))
return RT_CANONICALIZE_ERROR;
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = ID_RHC;
canonical->idb_meth = &OBJ[ID_RHC];
BU_ALLOC(canonical->idb_ptr, struct rt_rhc_internal);
crip = (struct rt_rhc_internal *)canonical->idb_ptr;
crip->rhc_magic = RT_RHC_INTERNAL_MAGIC;
VSETALL(crip->rhc_V, 0.0);
VSET(crip->rhc_B, canonical_lengths[0], 0.0, 0.0);
VSET(crip->rhc_H, 0.0, 0.0, canonical_lengths[1]);
crip->rhc_r = canonical_lengths[2];
crip->rhc_c = canonical_lengths[3];
return RT_CANONICALIZE_OK;
}
/**
* Import an RHC from the database format to the internal format.
* Apply modeling transformations as well.
+40
View File
@@ -172,6 +172,7 @@
#include "raytrace.h"
#include "../../librt_private.h"
#include "../canonicalize_private.h"
#if defined(HAVE_ASINH) && !defined(HAVE_DECL_ASINH)
extern double asinh(double x);
@@ -1719,6 +1720,45 @@ rt_rpc_mat(struct rt_db_internal *rop, const mat_t mat, const struct rt_db_inter
return BRLCAD_OK;
}
C_DECL int
rt_rpc_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
const struct rt_rpc_internal *rip;
struct rt_rpc_internal *crip;
fastf_t lengths[3];
fastf_t canonical_lengths[3];
if (!canonical || !canonical_to_input || !input || !tol ||
input->idb_type != ID_RPC)
return RT_CANONICALIZE_ERROR;
rip = (const struct rt_rpc_internal *)input->idb_ptr;
RT_RPC_CK_MAGIC(rip);
lengths[0] = MAGNITUDE(rip->rpc_B);
lengths[1] = MAGNITUDE(rip->rpc_H);
lengths[2] = rip->rpc_r;
if (!rt_make_canonical_similarity_frame(canonical_to_input,
canonical_lengths, rip->rpc_V, rip->rpc_B, rip->rpc_H, lengths,
3, tol, mode))
return RT_CANONICALIZE_ERROR;
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = ID_RPC;
canonical->idb_meth = &OBJ[ID_RPC];
BU_ALLOC(canonical->idb_ptr, struct rt_rpc_internal);
crip = (struct rt_rpc_internal *)canonical->idb_ptr;
crip->rpc_magic = RT_RPC_INTERNAL_MAGIC;
VSETALL(crip->rpc_V, 0.0);
VSET(crip->rpc_B, canonical_lengths[0], 0.0, 0.0);
VSET(crip->rpc_H, 0.0, 0.0, canonical_lengths[1]);
crip->rpc_r = canonical_lengths[2];
return RT_CANONICALIZE_OK;
}
/**
* Import an RPC from the database format to the internal format.
* Apply modeling transformations as well.
+114
View File
@@ -998,11 +998,125 @@ rt_superell_mat(struct rt_db_internal *rop, const mat_t mat, const struct rt_db_
MAT4X3VEC(top->a, mat, sa);
MAT4X3VEC(top->b, mat, sb);
MAT4X3VEC(top->c, mat, sc);
top->n = tip->n;
top->e = tip->e;
return BRLCAD_OK;
}
static void
superell_canonical_orientation(mat_t orientation, const vect_t a,
const vect_t b, const vect_t c)
{
MAT_IDN(orientation);
orientation[0] = a[X];
orientation[4] = a[Y];
orientation[8] = a[Z];
orientation[1] = b[X];
orientation[5] = b[Y];
orientation[9] = b[Z];
orientation[2] = c[X];
orientation[6] = c[Y];
orientation[10] = c[Z];
}
C_DECL int
rt_superell_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
static const fastf_t maximum_curvature = 10000.0;
const struct rt_superell_internal *sip;
struct rt_superell_internal *csip;
const fastf_t *input_axes[3];
vect_t axes[3];
vect_t cross;
fastf_t lengths[3];
fastf_t canonical_lengths[3];
fastf_t uniform_scale = 1.0;
mat_t orientation;
mat_t scale;
mat_t oriented_scale;
mat_t translate;
if (!canonical || !canonical_to_input || !input || !tol)
return RT_CANONICALIZE_ERROR;
if (mode < RT_CANONICALIZE_RIGID || mode > RT_CANONICALIZE_AFFINE)
return RT_CANONICALIZE_ERROR;
if (input->idb_type != ID_SUPERELL)
return RT_CANONICALIZE_ERROR;
sip = (const struct rt_superell_internal *)input->idb_ptr;
RT_SUPERELL_CK_MAGIC(sip);
if (!isfinite(sip->v[X]) || !isfinite(sip->v[Y]) ||
!isfinite(sip->v[Z]) || !isfinite(sip->n) || !isfinite(sip->e) ||
sip->n <= tol->dist || sip->e <= tol->dist ||
sip->n > maximum_curvature || sip->e > maximum_curvature)
return RT_CANONICALIZE_ERROR;
input_axes[0] = sip->a;
input_axes[1] = sip->b;
input_axes[2] = sip->c;
for (size_t i = 0; i < 3; i++) {
lengths[i] = MAGNITUDE(input_axes[i]);
if (!isfinite(lengths[i]) || lengths[i] <= tol->dist)
return RT_CANONICALIZE_ERROR;
VSCALE(axes[i], input_axes[i], 1.0 / lengths[i]);
}
if (!NEAR_ZERO(VDOT(axes[0], axes[1]), tol->perp) ||
!NEAR_ZERO(VDOT(axes[0], axes[2]), tol->perp) ||
!NEAR_ZERO(VDOT(axes[1], axes[2]), tol->perp))
return RT_CANONICALIZE_ERROR;
/* Axis signs do not change a superellipsoid. Keep the placement a
* proper transform by selecting the third sign from the input frame. */
VCROSS(cross, axes[0], axes[1]);
if (VDOT(cross, axes[2]) < 0.0)
VREVERSE(axes[2], axes[2]);
if (mode == RT_CANONICALIZE_AFFINE) {
VSETALL(canonical_lengths, 1.0);
for (size_t i = 0; i < 3; i++)
VSCALE(axes[i], axes[i], lengths[i]);
superell_canonical_orientation(orientation, axes[0], axes[1], axes[2]);
MAT_IDN(translate);
MAT_DELTAS_VEC(translate, sip->v);
bn_mat_mul(canonical_to_input, translate, orientation);
} else {
if (mode == RT_CANONICALIZE_SIMILARITY)
uniform_scale = fmax(lengths[0], fmax(lengths[1], lengths[2]));
for (size_t i = 0; i < 3; i++)
canonical_lengths[i] = lengths[i] / uniform_scale;
superell_canonical_orientation(orientation, axes[0], axes[1], axes[2]);
MAT_IDN(scale);
scale[15] = 1.0 / uniform_scale;
bn_mat_mul(oriented_scale, orientation, scale);
MAT_IDN(translate);
MAT_DELTAS_VEC(translate, sip->v);
bn_mat_mul(canonical_to_input, translate, oriented_scale);
}
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = ID_SUPERELL;
canonical->idb_meth = &OBJ[ID_SUPERELL];
BU_ALLOC(canonical->idb_ptr, struct rt_superell_internal);
csip = (struct rt_superell_internal *)canonical->idb_ptr;
csip->magic = RT_SUPERELL_INTERNAL_MAGIC;
VSETALL(csip->v, 0.0);
VSET(csip->a, canonical_lengths[0], 0.0, 0.0);
VSET(csip->b, 0.0, canonical_lengths[1], 0.0);
VSET(csip->c, 0.0, 0.0, canonical_lengths[2]);
csip->n = sip->n;
csip->e = sip->e;
return RT_CANONICALIZE_OK;
}
/**
* Import an superellipsoid/sphere from the database format to the
* internal structure. Apply modeling transformations as wsuperell.
+97 -48
View File
@@ -83,6 +83,7 @@ extern "C" {
extern int rt_##name##_labels(struct rt_point_labels *pl, int pl_max, const mat_t xform, const struct rt_db_internal *ip, const struct bn_tol *utol); \
extern const char *rt_##name##_keypoint(point_t *pt, const char *keystr, const mat_t xform, const struct rt_db_internal *ip, const struct bn_tol *tol); \
extern int rt_##name##_mat(struct rt_db_internal *op, const mat_t mat, const struct rt_db_internal *ip); \
extern int rt_##name##_canonicalize(struct rt_db_internal *canonical, mat_t canonical_to_input, const struct rt_db_internal *input, const struct bn_tol *tol, enum rt_canonicalize_mode mode); \
extern int rt_##name##_perturb(struct rt_db_internal **oip, const struct rt_db_internal *ip, int planar_only, fastf_t factor); \
extern int rt_##name##_scene_obj(struct bv_scene_obj *vhead, struct directory *dp, struct db_i *dbip, const struct bg_tess_tol *ttol, const struct bn_tol *tol, const struct bview *info) \
@@ -246,7 +247,8 @@ const struct rt_functab OBJ[] = {
NULL,
NULL,
NULL,
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -298,7 +300,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_tor_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_tor_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_tor_canonicalize) /* canonicalize */
},
{
@@ -350,7 +353,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_tgc_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_tgc_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_tgc_functab_validate) /* validate */
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_tgc_functab_validate), /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_tgc_canonicalize) /* canonicalize */
},
{
@@ -402,7 +406,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_ell_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_ell_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_ell_functab_validate) /* validate */
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_ell_functab_validate), /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_ell_canonicalize)
},
{
@@ -454,7 +459,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_arb_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_arb_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_arb_functab_validate) /* validate */
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_arb_functab_validate), /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_arb_canonicalize) /* canonicalize */
},
{
@@ -506,7 +512,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_ars_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -558,7 +565,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_hlf_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_hlf_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_hlf_canonicalize)
},
{
@@ -610,7 +618,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_tgc_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_tgc_perturb), /* perturb - REC shares rt_tgc_internal */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_tgc_canonicalize) /* canonicalize */
},
{
@@ -662,7 +671,8 @@ const struct rt_functab OBJ[] = {
NULL, /* mat */
NULL, /* perturb */
NULL /* scene_obj */,
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -714,7 +724,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_nurb_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -766,7 +777,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_ell_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_ell_perturb), /* perturb - SPH shares rt_ell_internal */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_ell_canonicalize)
},
{
@@ -818,7 +830,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_nmg_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -870,7 +883,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_ebm_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -922,7 +936,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_vol_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -974,7 +989,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_arbn_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_arbn_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -1026,7 +1042,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_pipe_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -1078,7 +1095,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_part_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_part_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_part_canonicalize) /* canonicalize */
},
{
@@ -1130,7 +1148,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_rpc_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_rpc_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_rpc_functab_validate) /* validate */
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_rpc_functab_validate), /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_rpc_canonicalize) /* canonicalize */
},
{
@@ -1182,7 +1201,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_rhc_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_rhc_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_rhc_functab_validate) /* validate */
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_rhc_functab_validate), /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_rhc_canonicalize) /* canonicalize */
},
{
@@ -1234,7 +1254,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_epa_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_epa_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_epa_functab_validate) /* validate */
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_epa_functab_validate), /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_epa_canonicalize) /* canonicalize */
},
{
@@ -1286,7 +1307,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_ehy_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_ehy_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_ehy_functab_validate) /* validate */
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_ehy_functab_validate), /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_ehy_canonicalize) /* canonicalize */
},
{
@@ -1338,7 +1360,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_eto_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_eto_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_eto_functab_validate) /* validate */
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_eto_functab_validate), /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_eto_canonicalize) /* canonicalize */
},
{
@@ -1390,7 +1413,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_grp_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -1442,7 +1466,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_joint_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -1494,7 +1519,8 @@ const struct rt_functab OBJ[] = {
NULL, /* mat */
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -1546,7 +1572,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_dsp_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -1598,7 +1625,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_sketch_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -1650,7 +1678,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_extrude_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -1702,7 +1731,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_submodel_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -1754,7 +1784,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_cline_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -1806,7 +1837,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_bot_mat),
NULL, /* perturb */
NULL /* scene_obj */,
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_bot_functab_validate) /* validate */
RTFUNCTAB_FUNC_VALIDATE_CAST(rt_bot_functab_validate), /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_bot_canonicalize) /* canonicalize */
},
{
@@ -1858,7 +1890,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_comb_mat),
NULL, /* perturb */
NULL /* scene_obj */,
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -1912,7 +1945,8 @@ const struct rt_functab OBJ[] = {
NULL, /* mat */
NULL, /* perturb */
NULL /* scene_obj */,
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -1964,7 +1998,8 @@ const struct rt_functab OBJ[] = {
NULL, /* mat */
NULL, /* perturb */
NULL /* scene_obj */,
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -2018,7 +2053,8 @@ const struct rt_functab OBJ[] = {
NULL, /* mat */
NULL, /* perturb */
NULL /* scene_obj */,
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -2070,7 +2106,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_superell_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_superell_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
RTFUNCTAB_FUNC_CANONICALIZE_CAST(rt_superell_canonicalize) /* canonicalize */
},
{
@@ -2122,7 +2159,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_metaball_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -2174,7 +2212,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_brep_mat),
NULL, /* perturb */
NULL /* scene_obj */,
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -2226,7 +2265,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_hyp_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_hyp_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -2278,7 +2318,8 @@ const struct rt_functab OBJ[] = {
NULL, /* mat */
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -2330,7 +2371,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_revolve_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -2382,7 +2424,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_pnts_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -2434,7 +2477,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_annot_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -2486,7 +2530,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_hrt_mat),
RTFUNCTAB_FUNC_PERTURB_CAST(rt_hrt_perturb), /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
@@ -2539,7 +2584,8 @@ const struct rt_functab OBJ[] = {
RTFUNCTAB_FUNC_MAT_CAST(rt_datum_mat),
NULL, /* perturb */
RTFUNCTAB_FUNC_SCENE_OBJ_CAST(rt_generic_scene_obj),
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
@@ -2592,7 +2638,8 @@ const struct rt_functab OBJ[] = {
NULL, /* mat */
NULL, /* perturb */
NULL /* scene_obj */,
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -2644,7 +2691,8 @@ const struct rt_functab OBJ[] = {
NULL, /* mat */
NULL, /* perturb */
NULL /* scene_obj */,
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
},
{
@@ -2696,7 +2744,8 @@ const struct rt_functab OBJ[] = {
NULL, /* mat */
NULL, /* perturb */
NULL /* scene_obj */,
NULL /* validate */
NULL, /* validate */
NULL /* canonicalize */
}
};
+242
View File
@@ -1757,6 +1757,248 @@ rt_tgc_mat(struct rt_db_internal *rop, const mat_t mat, const struct rt_db_inter
}
static void
tgc_swap_vectors(vect_t a, vect_t b)
{
vect_t tmp;
VMOVE(tmp, a);
VMOVE(a, b);
VMOVE(b, tmp);
}
static int
tgc_ratio_pair_greater(fastf_t a0, fastf_t a1, fastf_t b0, fastf_t b1,
fastf_t tolerance)
{
fastf_t ahi = fmax(a0, a1);
fastf_t alo = fmin(a0, a1);
fastf_t bhi = fmax(b0, b1);
fastf_t blo = fmin(b0, b1);
if (ahi > bhi + tolerance)
return 1;
if (bhi > ahi + tolerance)
return 0;
return alo > blo + tolerance;
}
C_DECL int
rt_tgc_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
const struct rt_tgc_internal *tip;
struct rt_tgc_internal *ctip;
point_t v;
vect_t h, a, b, c, d;
vect_t u, w, normal;
fastf_t mag_h, mag_a, mag_b, mag_c, mag_d;
fastf_t ratio_c, ratio_d;
fastf_t h_a, h_b, h_w;
fastf_t uniform_scale = 1.0;
fastf_t ratio_tolerance;
int a_sign = 1;
int b_sign = 1;
int a_projection_zero;
int b_projection_zero;
int reverse_ends = 0;
if (!canonical || !canonical_to_input || !input || !tol)
return RT_CANONICALIZE_ERROR;
if (mode < RT_CANONICALIZE_RIGID || mode > RT_CANONICALIZE_AFFINE)
return RT_CANONICALIZE_ERROR;
if (input->idb_type != ID_TGC && input->idb_type != ID_REC)
return RT_CANONICALIZE_ERROR;
tip = (const struct rt_tgc_internal *)input->idb_ptr;
RT_TGC_CK_MAGIC(tip);
VMOVE(v, tip->v);
VMOVE(h, tip->h);
VMOVE(a, tip->a);
VMOVE(b, tip->b);
VMOVE(c, tip->c);
VMOVE(d, tip->d);
mag_h = MAGNITUDE(h);
mag_a = MAGNITUDE(a);
mag_b = MAGNITUDE(b);
mag_c = MAGNITUDE(c);
mag_d = MAGNITUDE(d);
if (!isfinite(v[X]) || !isfinite(v[Y]) || !isfinite(v[Z]) ||
!isfinite(mag_h) || !isfinite(mag_a) || !isfinite(mag_b) ||
!isfinite(mag_c) || !isfinite(mag_d) || mag_h <= tol->dist)
return RT_CANONICALIZE_ERROR;
if ((mag_a <= tol->dist && mag_c <= tol->dist) ||
(mag_b <= tol->dist && mag_d <= tol->dist))
return RT_CANONICALIZE_ERROR;
if ((mag_a <= tol->dist || mag_b <= tol->dist) &&
(mag_c <= tol->dist || mag_d <= tol->dist))
return RT_CANONICALIZE_ERROR;
if (input->idb_type == ID_REC) {
vect_t difference;
VSUB2(difference, a, c);
if (MAGNITUDE(difference) > tol->dist)
return RT_CANONICALIZE_ERROR;
VSUB2(difference, b, d);
if (MAGNITUDE(difference) > tol->dist ||
!NEAR_ZERO(VDOT(h, a) / (mag_h * mag_a), tol->perp) ||
!NEAR_ZERO(VDOT(h, b) / (mag_h * mag_b), tol->perp))
return RT_CANONICALIZE_ERROR;
}
/* A complete end supplies the affine basis. When both ends are
* complete, select between the two reciprocal taper descriptions so
* reversing an otherwise identical TGC does not change its canonical
* form. */
ratio_tolerance = fmax(tol->perp, SMALL_FASTF);
if (mag_a <= tol->dist || mag_b <= tol->dist) {
reverse_ends = 1;
} else if (mag_c > tol->dist && mag_d > tol->dist) {
fastf_t taper_c = mag_c / mag_a;
fastf_t taper_d = mag_d / mag_b;
int forward_greater = tgc_ratio_pair_greater(taper_c, taper_d,
1.0 / taper_c, 1.0 / taper_d, ratio_tolerance);
int reverse_greater = tgc_ratio_pair_greater(1.0 / taper_c,
1.0 / taper_d, taper_c, taper_d, ratio_tolerance);
reverse_ends = forward_greater;
if (!forward_greater && !reverse_greater &&
mode != RT_CANONICALIZE_AFFINE)
reverse_ends = tgc_ratio_pair_greater(mag_c, mag_d,
mag_a, mag_b, tol->dist);
}
if (reverse_ends) {
VADD2(v, v, h);
VREVERSE(h, h);
tgc_swap_vectors(a, c);
tgc_swap_vectors(b, d);
mag_a = MAGNITUDE(a);
mag_b = MAGNITUDE(b);
mag_c = MAGNITUDE(c);
mag_d = MAGNITUDE(d);
}
ratio_c = mag_c / mag_a;
ratio_d = mag_d / mag_b;
/* Axis interchange is a parameterization change, not a geometry
* change. Taper ratio is affine-invariant, so use it as the primary
* ordering key. */
if (ratio_d > ratio_c + ratio_tolerance ||
(mode != RT_CANONICALIZE_AFFINE &&
NEAR_EQUAL(ratio_c, ratio_d, ratio_tolerance) &&
mag_b > mag_a + tol->dist)) {
tgc_swap_vectors(a, b);
tgc_swap_vectors(c, d);
{
fastf_t tmp = mag_a;
mag_a = mag_b;
mag_b = tmp;
tmp = mag_c;
mag_c = mag_d;
mag_d = tmp;
tmp = ratio_c;
ratio_c = ratio_d;
ratio_d = tmp;
}
}
VSCALE(u, a, 1.0 / mag_a);
VSCALE(w, b, 1.0 / mag_b);
if (!NEAR_ZERO(VDOT(u, w), tol->perp))
return RT_CANONICALIZE_ERROR;
if (mag_c > tol->dist && !NEAR_EQUAL(VDOT(u, c) / mag_c, 1.0, tol->perp))
return RT_CANONICALIZE_ERROR;
if (mag_d > tol->dist && !NEAR_EQUAL(VDOT(w, d) / mag_d, 1.0, tol->perp))
return RT_CANONICALIZE_ERROR;
h_a = VDOT(h, u);
h_b = VDOT(h, w);
VCROSS(normal, u, w);
h_w = VDOT(h, normal);
if (fabs(h_w) <= mag_h * tol->perp)
return RT_CANONICALIZE_ERROR;
/* A/C and B/D may each be negated without changing the TGC. Use the
* height projections to resolve those signs without referring to world
* coordinates, which keeps the result invariant under placement. */
a_projection_zero = fabs(h_a) <= tol->dist;
b_projection_zero = fabs(h_b) <= tol->dist;
if (!a_projection_zero && h_a < 0.0)
a_sign = -1;
if (!b_projection_zero && h_b < 0.0)
b_sign = -1;
if (a_projection_zero && a_sign * b_sign * h_w < 0.0)
a_sign = -1;
else if (b_projection_zero && a_sign * b_sign * h_w < 0.0)
b_sign = -1;
if (a_sign < 0)
VREVERSE(u, u);
if (b_sign < 0)
VREVERSE(w, w);
VCROSS(normal, u, w);
MAT_IDN(canonical_to_input);
MAT_DELTAS_VEC(canonical_to_input, v);
if (mode == RT_CANONICALIZE_AFFINE) {
canonical_to_input[0] = u[X] * mag_a;
canonical_to_input[4] = u[Y] * mag_a;
canonical_to_input[8] = u[Z] * mag_a;
canonical_to_input[1] = w[X] * mag_b;
canonical_to_input[5] = w[Y] * mag_b;
canonical_to_input[9] = w[Z] * mag_b;
canonical_to_input[2] = h[X];
canonical_to_input[6] = h[Y];
canonical_to_input[10] = h[Z];
} else {
if (mode == RT_CANONICALIZE_SIMILARITY)
uniform_scale = fmax(mag_h, fmax(mag_a, fmax(mag_b, fmax(mag_c, mag_d))));
canonical_to_input[0] = u[X];
canonical_to_input[4] = u[Y];
canonical_to_input[8] = u[Z];
canonical_to_input[1] = w[X];
canonical_to_input[5] = w[Y];
canonical_to_input[9] = w[Z];
canonical_to_input[2] = normal[X];
canonical_to_input[6] = normal[Y];
canonical_to_input[10] = normal[Z];
canonical_to_input[15] = 1.0 / uniform_scale;
canonical_to_input[3] /= uniform_scale;
canonical_to_input[7] /= uniform_scale;
canonical_to_input[11] /= uniform_scale;
}
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = input->idb_minor_type;
canonical->idb_meth = &OBJ[input->idb_minor_type];
BU_ALLOC(canonical->idb_ptr, struct rt_tgc_internal);
ctip = (struct rt_tgc_internal *)canonical->idb_ptr;
ctip->magic = RT_TGC_INTERNAL_MAGIC;
VSETALL(ctip->v, 0.0);
if (mode == RT_CANONICALIZE_AFFINE) {
VSET(ctip->h, 0.0, 0.0, 1.0);
VSET(ctip->a, 1.0, 0.0, 0.0);
VSET(ctip->b, 0.0, 1.0, 0.0);
VSET(ctip->c, ratio_c, 0.0, 0.0);
VSET(ctip->d, 0.0, ratio_d, 0.0);
} else {
VSET(ctip->h, VDOT(h, u) / uniform_scale,
VDOT(h, w) / uniform_scale, VDOT(h, normal) / uniform_scale);
VSET(ctip->a, mag_a / uniform_scale, 0.0, 0.0);
VSET(ctip->b, 0.0, mag_b / uniform_scale, 0.0);
VSET(ctip->c, mag_c / uniform_scale, 0.0, 0.0);
VSET(ctip->d, 0.0, mag_d / uniform_scale, 0.0);
}
return RT_CANONICALIZE_OK;
}
/**
* Import a TGC from the database format to the internal format.
* Apply modeling transformations as well.
+62
View File
@@ -1840,6 +1840,68 @@ rt_tor_mat(struct rt_db_internal *rop, const mat_t mat, const struct rt_db_inter
return BRLCAD_OK;
}
C_DECL int
rt_tor_canonicalize(struct rt_db_internal *canonical,
mat_t canonical_to_input,
const struct rt_db_internal *input,
const struct bn_tol *tol,
enum rt_canonicalize_mode mode)
{
const struct rt_tor_internal *tip;
struct rt_tor_internal *ctip;
vect_t normal;
vect_t zaxis = VINIT_ZERO;
mat_t rotate;
mat_t scale_mat;
mat_t rotated_scale;
mat_t translate;
fastf_t normal_mag;
fastf_t uniform_scale = 1.0;
if (!canonical || !canonical_to_input || !input || !tol)
return RT_CANONICALIZE_ERROR;
if (mode < RT_CANONICALIZE_RIGID || mode > RT_CANONICALIZE_AFFINE)
return RT_CANONICALIZE_ERROR;
if (input->idb_type != ID_TOR)
return RT_CANONICALIZE_ERROR;
tip = (const struct rt_tor_internal *)input->idb_ptr;
RT_TOR_CK_MAGIC(tip);
normal_mag = MAGNITUDE(tip->h);
if (normal_mag <= tol->dist || tip->r_a <= tol->dist || tip->r_h <= tol->dist)
return RT_CANONICALIZE_ERROR;
VSCALE(normal, tip->h, 1.0 / normal_mag);
VSET(zaxis, 0.0, 0.0, 1.0);
bn_mat_fromto(rotate, zaxis, normal, tol);
if (mode != RT_CANONICALIZE_RIGID)
uniform_scale = tip->r_a;
MAT_IDN(scale_mat);
scale_mat[15] = 1.0 / uniform_scale;
bn_mat_mul(rotated_scale, rotate, scale_mat);
MAT_IDN(translate);
MAT_DELTAS_VEC(translate, tip->v);
bn_mat_mul(canonical_to_input, translate, rotated_scale);
canonical->idb_major_type = DB5_MAJORTYPE_BRLCAD;
canonical->idb_minor_type = ID_TOR;
canonical->idb_meth = &OBJ[ID_TOR];
BU_ALLOC(canonical->idb_ptr, struct rt_tor_internal);
ctip = (struct rt_tor_internal *)canonical->idb_ptr;
ctip->magic = RT_TOR_INTERNAL_MAGIC;
VSETALL(ctip->v, 0.0);
VSET(ctip->h, 0.0, 0.0, 1.0);
ctip->r_a = tip->r_a / uniform_scale;
ctip->r_h = tip->r_h / uniform_scale;
VSET(ctip->a, ctip->r_a, 0.0, 0.0);
VSET(ctip->b, 0.0, ctip->r_a, 0.0);
ctip->r_b = ctip->r_a;
return RT_CANONICALIZE_OK;
}
/**
* Taken from the database record:
* v vertex (point) of center of torus.
+4
View File
@@ -55,6 +55,9 @@ brlcad_addexec(rt_diff diff.c "${RT_TEST_LIBS}" TEST)
brlcad_addexec(rt_default_object default_object.c "librt;libwdb;libbu;${M_LIBRARY}" TEST)
brlcad_add_test(NAME rt_default_object COMMAND rt_default_object)
brlcad_addexec(rt_canonicalize canonicalize.c "${RT_TEST_LIBS}" TEST)
brlcad_add_test(NAME rt_canonicalize COMMAND rt_canonicalize)
brlcad_addexec(rt_db_name db_name.c "librt;libbu" TEST)
brlcad_add_test(NAME rt_db_name COMMAND rt_db_name)
@@ -216,6 +219,7 @@ set(
binunif.c
brep_io.cpp
bound_internal.c
canonicalize.c
crofton.c
db_name.c
default_object.c
File diff suppressed because it is too large Load Diff
+1
View File
@@ -796,6 +796,7 @@ static struct to_cmdtab ged_cmds[] = {
{"track", (char *)0, TO_UNLIMITED, to_pass_through_func, ged_exec_track},
{"tree", (char *)0, TO_UNLIMITED, to_pass_through_func, ged_exec_tree},
{"unhide", (char *)0, TO_UNLIMITED, to_pass_through_func, ged_exec_unhide},
{"unpush", (char *)0, TO_UNLIMITED, to_pass_through_func, ged_exec_unpush},
{"units", (char *)0, TO_UNLIMITED, to_pass_through_func, ged_exec_units},
{"v2m_point", "x y z", 5, to_view_func, ged_exec_v2m_point},
{"vdraw", (char *)0, TO_UNLIMITED, to_pass_through_and_refresh_func, ged_exec_vdraw},
+1
View File
@@ -457,6 +457,7 @@ static struct cmdtab mged_cmdtab[] = {
{MGED_CMD_MAGIC, "translate", f_tr_obj, GED_FUNC_PTR_NULL, NULL},
{MGED_CMD_MAGIC, "tree", cmd_ged_plain_wrapper, ged_exec_tree, NULL},
{MGED_CMD_MAGIC, "unhide", cmd_ged_plain_wrapper, ged_exec_unhide, NULL},
{MGED_CMD_MAGIC, "unpush", cmd_ged_plain_wrapper, ged_exec_unpush, NULL},
{MGED_CMD_MAGIC, "units", cmd_units, GED_FUNC_PTR_NULL, NULL},
{MGED_CMD_MAGIC, "v2m_point", cmd_ged_plain_wrapper, ged_exec_v2m_point, NULL},
{MGED_CMD_MAGIC, "vars", f_set, GED_FUNC_PTR_NULL, NULL},