Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22063d9add | ||
|
|
17601916f4 |
@@ -371,6 +371,7 @@ set(
|
||||
translate.adoc
|
||||
tree.adoc
|
||||
unhide.adoc
|
||||
unpush.adoc
|
||||
units.adoc
|
||||
v2m_point.adoc
|
||||
vars.adoc
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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))
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(¢er, &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(¢er, &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(¢er, &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
|
||||
*/
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
@@ -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:
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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(¢er, &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
|
||||
*/
|
||||
@@ -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 */
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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},
|
||||
|
||||
@@ -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},
|
||||
|
||||
Reference in New Issue
Block a user