Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a7ea7976f | ||
|
|
ec4347e7d3 | ||
|
|
21e034ee34 | ||
|
|
8f81f27d92 | ||
|
|
d729a7d3f2 | ||
|
|
17601916f4 |
@@ -0,0 +1,164 @@
|
||||
/* B I N D I N G . H
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this file; see the file named COPYING for more
|
||||
* information.
|
||||
*/
|
||||
|
||||
/** @addtogroup bu_binding
|
||||
* @brief
|
||||
* Universal input binding schema and action dispatch.
|
||||
*/
|
||||
/** @{ */
|
||||
/** @file bu/binding.h */
|
||||
|
||||
#ifndef BU_BINDING_H
|
||||
#define BU_BINDING_H
|
||||
|
||||
#include "common.h"
|
||||
#include "bu/defines.h"
|
||||
#include "bu/vls.h"
|
||||
|
||||
__BEGIN_DECLS
|
||||
|
||||
/* Standardized Attribute Keys (for W3C validation logic) */
|
||||
#define BU_BINDING_ATTR_TYPE "type"
|
||||
#define BU_BINDING_ATTR_KEY "key" /* Values MUST be W3C standard strings */
|
||||
#define BU_BINDING_ATTR_BUTTON "button" /* Values MUST be W3C button integers */
|
||||
#define BU_BINDING_ATTR_MODIFIERS "modifiers"
|
||||
|
||||
/* Standardized Event Types */
|
||||
#define BU_BINDING_EVENT_KEY_PRESS "key-press"
|
||||
#define BU_BINDING_EVENT_KEY_RELEASE "key-release"
|
||||
#define BU_BINDING_EVENT_MOUSE_PRESS "mouse-press"
|
||||
#define BU_BINDING_EVENT_MOUSE_RELEASE "mouse-release"
|
||||
#define BU_BINDING_EVENT_MOUSE_MOTION "mouse-motion"
|
||||
#define BU_BINDING_EVENT_MOUSE_SCROLL "mouse-scroll"
|
||||
|
||||
/**
|
||||
* @brief Action callback signature
|
||||
*
|
||||
* Invoked when an incoming event matches a loaded binding.
|
||||
* The callback receives the full JSON string of the triggering event.
|
||||
*
|
||||
* @param event_json Raw JSON string describing the incoming event
|
||||
* @param registered_data Opaque pointer passed during registration
|
||||
* @param event_data Opaque pointer passed during event processing
|
||||
*/
|
||||
typedef void (*bu_action_func_t)(const char *event_json, void *registered_data, void *event_data);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Load JSON bindings from a file
|
||||
*
|
||||
* Parses a JSON configuration specifying bindings for various contexts.
|
||||
* Performs two-phase validation:
|
||||
* 1. Checks standard fields (like W3C key strings) for correctness.
|
||||
* 2. Invokes context-specific validation hooks (if registered).
|
||||
*
|
||||
* @param json_path Path to the JSON file
|
||||
* @return 0 on success, <0 on error
|
||||
*/
|
||||
BU_EXPORT extern int bu_binding_load(const char *json_path);
|
||||
|
||||
/**
|
||||
* @brief Load JSON bindings from a raw string
|
||||
*
|
||||
* Parses a JSON configuration string specifying bindings for various contexts.
|
||||
* Allows applications to embed overriding bindings natively without external files.
|
||||
*
|
||||
* @param json_string Raw JSON string containing bindings
|
||||
* @return 0 on success, <0 on error
|
||||
*/
|
||||
BU_EXPORT extern int bu_binding_load_str(const char *json_string);
|
||||
|
||||
/**
|
||||
* @brief Register a JSON schema for a specific context
|
||||
*
|
||||
* Defines the allowed vocabulary for a context using a JSON schema file.
|
||||
* The schema file is lazily parsed during bu_binding_load().
|
||||
*
|
||||
* @param context The interaction context (e.g., "dm", "fb")
|
||||
* @param json_path Absolute path to the schema JSON file
|
||||
*/
|
||||
BU_EXPORT extern void bu_binding_register_schema(const char *context, const char *json_path);
|
||||
|
||||
/**
|
||||
* @brief Augment an existing schema with a custom action
|
||||
*
|
||||
* @param context The interaction context (e.g., "dm")
|
||||
* @param action_name The new abstract action string
|
||||
* @param description Human-readable description of the action
|
||||
*/
|
||||
BU_EXPORT extern void bu_binding_schema_add_action(const char *context, const char *action_name, const char *description);
|
||||
|
||||
/**
|
||||
* @brief Remove a custom action from an existing schema
|
||||
*
|
||||
* @param context The interaction context (e.g., "dm")
|
||||
* @param action_name The abstract action string to remove
|
||||
*/
|
||||
BU_EXPORT extern void bu_binding_schema_remove_action(const char *context, const char *action_name);
|
||||
|
||||
/**
|
||||
* @brief Register a C callback for a semantic action
|
||||
*
|
||||
* @param action_name The abstract action string (e.g., "fb_close_window")
|
||||
* @param func The C function to invoke when the action is triggered
|
||||
* @param client_data Opaque pointer passed to the callback
|
||||
*/
|
||||
BU_EXPORT extern void bu_binding_register_action(const char *action_name, bu_action_func_t func, void *client_data);
|
||||
|
||||
/**
|
||||
* @brief Process an incoming native event
|
||||
*
|
||||
* Matches the event JSON against the loaded bindings for the given context.
|
||||
* If a matching action is found and a handler is registered for that action,
|
||||
* the callback is invoked.
|
||||
*
|
||||
* @param context The interaction context (e.g., "view3d", "fb_raw")
|
||||
* @param event_json A JSON string containing the event attributes
|
||||
* @param event_data Opaque pointer passed to the callback representing instance state
|
||||
* @return 1 if an action was triggered and handled, 0 otherwise
|
||||
*/
|
||||
BU_EXPORT extern int bu_binding_process_event(const char *context, const char *event_json, void *event_data);
|
||||
|
||||
/**
|
||||
* @brief Check if a specific action is bound in a given context
|
||||
*
|
||||
* Use this to verify that an application has provided a binding for an essential action.
|
||||
*
|
||||
* @param context The interaction context (e.g., "view3d", "fb_raw")
|
||||
* @param action_name The abstract action string (e.g., "view_mouse_drag_rotate")
|
||||
* @return 1 if bound, 0 if not
|
||||
*/
|
||||
BU_EXPORT extern int bu_binding_action_is_bound(const char *context, const char *action_name);
|
||||
|
||||
__END_DECLS
|
||||
|
||||
#endif /* BU_BINDING_H */
|
||||
|
||||
/** @} */
|
||||
/*
|
||||
* Local Variables:
|
||||
* mode: C
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
@@ -444,6 +444,9 @@ DM_EXPORT extern int fb_get_interactive(const struct fb *ifp);
|
||||
*/
|
||||
DM_EXPORT extern int fb_next_event(struct fb *ifp, struct fb_event *e);
|
||||
|
||||
/** Dispatch an application-facing event through the framebuffer bindings. */
|
||||
DM_EXPORT extern int fb_process_event_bindings(struct fb *ifp, const struct fb_event *e);
|
||||
|
||||
|
||||
/* Library entry points */
|
||||
|
||||
|
||||
@@ -227,6 +227,32 @@ struct rt_crofton_params {
|
||||
double time_ms; /**< wall-clock time budget (ms); 0 = disabled */
|
||||
};
|
||||
|
||||
/** One solid partition observed by a Crofton ray. */
|
||||
struct rt_crofton_segment {
|
||||
point_t in_point;
|
||||
vect_t in_normal;
|
||||
point_t out_point;
|
||||
vect_t out_normal;
|
||||
fastf_t thickness;
|
||||
size_t ray_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structured samples and estimates produced by rt_crofton_collect().
|
||||
* Release @p segments with rt_crofton_result_free().
|
||||
*/
|
||||
struct rt_crofton_result {
|
||||
struct rt_crofton_segment *segments;
|
||||
size_t segment_count;
|
||||
size_t ray_count;
|
||||
size_t crossing_count;
|
||||
double surface_area;
|
||||
double volume;
|
||||
};
|
||||
|
||||
/** Initialize an rt_crofton_result before first use. */
|
||||
#define RT_CROFTON_RESULT_INIT { NULL, 0, 0, 0, 0.0, 0.0 }
|
||||
|
||||
|
||||
/**
|
||||
* Run the Cauchy-Crofton ray-sampling estimator on an already-prepared
|
||||
@@ -337,6 +363,26 @@ struct rt_crofton_params {
|
||||
*/
|
||||
RT_EXPORT extern int rt_crofton_shoot(double *out_surf_area, double *out_volume, point_t *out_aabb_min, point_t *out_aabb_max, point_t out_obb[8], point_t **out_points, size_t *out_point_count, struct rt_i *rtip, const struct rt_crofton_params *params, const fastf_t *bbox_min, const fastf_t *bbox_max);
|
||||
|
||||
/**
|
||||
* Run Crofton sampling while preserving oriented entry/exit pairs.
|
||||
*
|
||||
* @p result must be initialized with RT_CROFTON_RESULT_INIT. On success it
|
||||
* owns the returned segment array. @p rtip must already be prepared.
|
||||
* @p ray_offset skips that many samples in the deterministic sequence and
|
||||
* labels the returned rays starting at that value. This lets successive
|
||||
* calls use disjoint validation streams.
|
||||
*/
|
||||
RT_EXPORT extern int rt_crofton_collect(struct rt_crofton_result *result,
|
||||
struct rt_i *rtip,
|
||||
const struct rt_crofton_params *params,
|
||||
size_t ray_offset,
|
||||
const fastf_t *bbox_min,
|
||||
const fastf_t *bbox_max);
|
||||
|
||||
/** Release storage owned by a structured Crofton result. */
|
||||
RT_EXPORT extern void rt_crofton_result_free(
|
||||
struct rt_crofton_result *result);
|
||||
|
||||
|
||||
/**
|
||||
* Cauchy-Crofton surface-area and/or volume estimator for a primitive.
|
||||
|
||||
@@ -17,10 +17,8 @@ brlcad_addexec(asc-nmg nmg/asc-nmg.c "libwdb;${M_LIBRARY}" FOLDER Conv)
|
||||
|
||||
brlcad_addexec(bot_dump bot_dump.c "libged;librt" FOLDER Conv)
|
||||
|
||||
verbose_add_subdirectory("src/conv" comgeom)
|
||||
|
||||
set(
|
||||
comgeom-g_old_SRCS
|
||||
comgeom-g_SRCS
|
||||
comgeom/cvt.c
|
||||
comgeom/f2a.c
|
||||
comgeom/mat.c
|
||||
@@ -29,7 +27,7 @@ set(
|
||||
comgeom/solid.c
|
||||
comgeom/tools.c
|
||||
)
|
||||
brlcad_addexec(comgeom-g_old "${comgeom-g_old_SRCS}" "libwdb;libbn;libbu;${M_LIBRARY}" FOLDER Conv)
|
||||
brlcad_addexec(comgeom-g "${comgeom-g_SRCS}" "libwdb;libbn;libbu;${M_LIBRARY}" FOLDER Conv)
|
||||
|
||||
brlcad_addexec(conv-vg2g conv-vg2g.c "libbu" FOLDER Conv)
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
set(comgeom_srcs
|
||||
ComGeomConverter.cpp
|
||||
ComGeomReader.cpp
|
||||
RegionManager.cpp
|
||||
SolidParser.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
brlcad_addexec(comgeom-g "${comgeom_srcs}" "libwdb;librt;libnmg;libbu" FOLDER Conv)
|
||||
target_include_directories(comgeom-g BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
cmakefiles(
|
||||
CMakeLists.txt
|
||||
COMGEOM_V1.txt
|
||||
COMGEOM_versions.txt
|
||||
ComGeomConverter.h
|
||||
ComGeomReader.h
|
||||
RegionManager.h
|
||||
SolidParser.h
|
||||
bot_to_arbn_pipeline_thoughts.txt
|
||||
)
|
||||
|
||||
# Local Variables:
|
||||
# tab-width: 8
|
||||
# mode: cmake
|
||||
# indent-tabs-mode: t
|
||||
# End:
|
||||
# ex: shiftwidth=2 tabstop=8
|
||||
@@ -1,498 +0,0 @@
|
||||
COM-GEOM v1 FORMAT DEFINITION FOR THE GIFT CODE
|
||||
Based on BRL 1975 Manuals (Vol. I: BRL Report 1802; Vol. II: ARBRL-TR-02189)
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 0. GENERAL PRINCIPLES
|
||||
|
||||
1. Coordinate System
|
||||
- Right-handed Cartesian system (X,Y,Z).
|
||||
- Origin is user-defined (e.g., turret datum intersection for a tank).
|
||||
- All dimensional values (coordinates, lengths, radii) use a single unit family
|
||||
declared on the Title Card (columns 79-80): IN, FT, CM, M, or MM (default
|
||||
IN).
|
||||
|
||||
2. Data Organization (Target Description)
|
||||
A COM-GEOM data set consists of (in order):
|
||||
1) Title Card
|
||||
2) Target Specification Card
|
||||
3) Solid Table (one or more cards per solid)
|
||||
4) Region Table (one or more cards per region; ends with "-1" sentinel card)
|
||||
5) (Optional) Region RPP Table
|
||||
6) Region Identification Table
|
||||
7) Program Option / Control Cards (for GIFT processing and output runs)
|
||||
|
||||
3. Numeric Formats
|
||||
Unless otherwise noted:
|
||||
- Integers are right- or left-adjusted in their column ranges.
|
||||
- Floating point fields: F10.5 (classic) or F10.n where width 10 columns,
|
||||
decimal point explicit.
|
||||
- Blank where a "+" intersection operator is implied.
|
||||
|
||||
4. Combination Logic (Region Construction)
|
||||
Given solids S1, S2 ...
|
||||
- Intersection: adjacency (implicit "+"). Example: 1 2 3 means S1 intersect S2
|
||||
intersect S3.
|
||||
- Subtraction: prefix "-" before a solid number: ... -7 means subtract volume
|
||||
of S7 from accumulated set.
|
||||
- Union: keyword OR splits disjoint boolean branches: BranchA OR BranchB.
|
||||
- Dummy region: region card with only region number and no solids (assigned
|
||||
special identification code 111).
|
||||
- Recommended practice (per BRL guidance): Avoid OR on Region cards; instead
|
||||
assign identical identification (Item) codes to multiple simple regions.
|
||||
|
||||
5. Air Space and Items
|
||||
Each region is either:
|
||||
- Item (component) region: has item code (e.g., armor plate, wheel).
|
||||
- Air region: has air space code (e.g., crew compartment air).
|
||||
Rules:
|
||||
- Item regions must not overlap (beyond TOL).
|
||||
- Air regions may overlap other air regions with same air code.
|
||||
- Air regions must NOT overlap air regions with different air codes (beyond
|
||||
TOL).
|
||||
- Air regions may overlap item regions (they represent enclosing void).
|
||||
|
||||
6. Tolerances (Control Card)
|
||||
- Overlap tolerance TOL: maximum allowable spatial overlap distance between
|
||||
regions before flagged as error.
|
||||
- Line-of-sight gap tolerance TOLLOS: maximum axial gap along a ray ignored
|
||||
when assembling ray intersection chains.
|
||||
|
||||
7. End Sentinel & Codes
|
||||
- Void Space Code: numeric code inserted where no component.
|
||||
- Phantom Item Code: code assigned to "phantom armor" or shielding constructs
|
||||
generated during checking.
|
||||
- End-of-ray Signal Code: code marking termination of component stack for a ray.
|
||||
|
||||
8. Storage Model
|
||||
MASTER-ASTER equivalence array holds all processed geometry. Size NDQ must be
|
||||
large enough; formula given (Section 11 of Vol II) reproduced below (see
|
||||
Section 7).
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 1. TITLE CARD
|
||||
|
||||
Columns (1-80):
|
||||
1-60: Free text title (target name/description).
|
||||
61-78: (Usually blank or ancillary description).
|
||||
79-80: Units code (IN, FT, CM, M, MM). If blank defaults to IN.
|
||||
|
||||
Example:
|
||||
SAMPLE LIGHT ARMORED CAR WITH DRIVER IN
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 2. TARGET SPECIFICATION CARD
|
||||
|
||||
Format: (Counts of solids / regions / surrounding RPPs)
|
||||
Columns (fixed interpretation from manuals):
|
||||
1-10 Number of Solids (NSOLID)
|
||||
11-20 Number of Regions (NREGION, expected or max)
|
||||
21-30 Number of Surrounding RPPs (if used) (NRPP) or MAX Regions (context dependent)
|
||||
31-40 MAX pointers or storage (implementation field) - historically used internally
|
||||
Remaining columns may be reserved or blank.
|
||||
|
||||
If a dedicated second card exists (variant implementations) it repeats extended capacity fields.
|
||||
|
||||
Example:
|
||||
20 20 1
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 3. SOLID TABLE
|
||||
|
||||
Each solid has a unique consecutive integer ID starting at 1.
|
||||
|
||||
GENERAL CARD FORMAT (all solids):
|
||||
Columns:
|
||||
1-3 Solid Number (integer)
|
||||
4-6 Solid Symbol (3 chars): RPP BOX RAW ARB ARS ELL SPH RCC REC TRC TEC TOR
|
||||
7 Variant / Vertex Count / Option flag (for ARB, ARS, ELL1) else blank
|
||||
11-70 Numeric fields (F10.5 blocks)
|
||||
71-80 Comment (recommended: name or purpose)
|
||||
|
||||
3.1 Rectangular Parallelepiped (RPP)
|
||||
No explicit vertex; define bounding box aligned to global axes.
|
||||
Fields (F10.5 each):
|
||||
XMIN, XMAX, YMIN, YMAX, ZMIN, ZMAX.
|
||||
Example:
|
||||
6 RPP 20.0000 23.0000 -10.0000 10.0000 28.0000 46.0000 BODY
|
||||
|
||||
3.2 BOX (Arbitrarily oriented)
|
||||
Vertex V and three mutually perpendicular edge vectors H, W, D.
|
||||
Fields:
|
||||
Vx Vy Vz Hx Hy Hz Wx Wy Wz Dx Dy Dz
|
||||
Example:
|
||||
1 BOX 21.5000 37.0000 0.0000 0.0000 45.0000 11.0000 40.0000 0.0000 0.0000 11.0000 0.0000 0.0000 STEERING WHEEL
|
||||
|
||||
3.3 RAW (Right Angle Wedge)
|
||||
Vertex V; two perpendicular legs H,W in wedge plane; depth D.
|
||||
Fields:
|
||||
Vx Vy Vz Hx Hy Hz Wx Wy Wz Dx Dy Dz
|
||||
Example:
|
||||
12 RAW -75.0000 -36.0000 12.0000 60.0000 12.0000 0.0000 60.0000 12.0000 0.0000 -60.0000 12.0000 0.0000 FENDER
|
||||
|
||||
3.4 ARB (Convex Polyhedron) - ARB4 / ARB5 / ARB6 / ARB7 / ARB8
|
||||
Column 7 = number of vertices (4-8).
|
||||
Card sequence: Enough cards to list all vertices (each vertex: X Y Z).
|
||||
One vertex triple per F10.5 block; continue across cards.
|
||||
Face set is internally inferred by GIFT according to vertex count pattern (see manual).
|
||||
Example ARB5 (two cards if needed):
|
||||
3 ARB5 33.5000 37.0000 12.0000 46.0000 -75.0000 -75.0000 20.0000 50.0000 0.0000 ...
|
||||
(continuation)
|
||||
|
||||
3.5 ARS (Triangular Surfaced Polyhedron)
|
||||
Column 7: M (number of curves)
|
||||
Column 11-20: N (points per curve)
|
||||
Then M x N vertex points laid out curve by curve (each curve on one or more cards).
|
||||
Repeated vertices allowed; triangles formed between successive curves (see Vol I Fig. 13).
|
||||
Example:
|
||||
11 ARS7 (points...) (comments)
|
||||
|
||||
3.6 ELL / ELL1 (Ellipsoid of Revolution)
|
||||
Two input modes:
|
||||
(a) ELL: Foci F1, F2 and major axis length L.
|
||||
Fields: F1x F1y F1z F2x F2y F2z L
|
||||
(b) ELL1 (Column 7 = 1): Center V, semi-major axis vector A, radius R of equatorial circle.
|
||||
Fields: Vx Vy Vz Ax Ay Az R
|
||||
Example:
|
||||
14 ELL1 24.0000 52.0000 0.0000 35.0000 0.0000 20.0000 7.5000 DRIVER CHEST
|
||||
|
||||
3.7 SPH (Sphere)
|
||||
Center V and radius R.
|
||||
Fields: Vx Vy Vz R
|
||||
Example:
|
||||
15 SPH 45.0000 -30.0000 -30.0000 7.5000 HEAD
|
||||
|
||||
3.8 RCC (Right Circular Cylinder)
|
||||
Center of one circular base (vertex V), height vector H, radius R.
|
||||
Fields: Vx Vy Vz Hx Hy Hz R
|
||||
Example:
|
||||
16 RCC -30.0000 -70.0000 -30.0000 48.0000 24.0000 0.0000 13.0000 LEG
|
||||
|
||||
3.9 REC (Right Elliptical Cylinder)
|
||||
Center V, height vector H, semi-major axis vector A, semi-minor axis vector B (both in base plane).
|
||||
Fields: Vx Vy Vz Hx Hy Hz Ax Ay Az Bx By Bz
|
||||
Example:
|
||||
17 REC -30.0000 -30.0000 -30.0000 48.0000 24.0000 0.0000 13.0000 13.0000 0.0000 5.0000 0.0000 0.0000 ARM
|
||||
|
||||
3.10 TRC (Truncated Circular Cone)
|
||||
Base center V, height vector H, base radius R1, top radius R2.
|
||||
Fields: Vx Vy Vz Hx Hy Hz R1 R2
|
||||
Example:
|
||||
18 TRC -30.0000 -30.0000 -70.0000 48.0000 24.0000 0.0000 13.0000 9.0000 TORSO LOWER
|
||||
|
||||
3.11 TEC (Truncated Elliptic Cone)
|
||||
Base center V, height vector H, base semi-major A, base semi-minor B, ratio P (BaseEllipse / TopEllipse).
|
||||
Fields: Vx Vy Vz Hx Hy Hz Ax Ay Az Bx By Bz P
|
||||
Example:
|
||||
19 TEC -30.0000 -70.0000 0.0000 48.0000 24.0000 0.0000 24.0000 0.0000 20.0000 12.0000 0.0000 10.0000 1.5000
|
||||
|
||||
3.12 TOR (Torus)
|
||||
Center V, normal vector N (to torus plane), major radius R1 (centerline), tube radius R2.
|
||||
Fields: Vx Vy Vz Nx Ny Nz R1 R2
|
||||
Example:
|
||||
20 TOR 21.5000 37.0000 0.0000 0.0000 45.0000 11.0000 40.0000 11.0000 STEERING RIM
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 4. REGION TABLE
|
||||
|
||||
Card Format (per region):
|
||||
Columns:
|
||||
1-2 Blank (recommended)
|
||||
3-5 Region Number (integer, consecutive starting at 1)
|
||||
6 Blank
|
||||
7-8 "OR" if union logic is used (introductory OR for region; optional but required if any OR appears later)
|
||||
9-69 Sequence of signed solid references (2-3 digit solid numbers with optional leading minus sign; blanks imply "+" intersection). Space or "OR" may appear to break into disjoint branches.
|
||||
70-80 Comment (e.g., semantic label, region definition subset index)
|
||||
|
||||
Grammar rules:
|
||||
- Intersection: adjacency (implicit +).
|
||||
- Subtraction: a leading "-" before the solid number.
|
||||
- Union: OR separating disjoint boolean expressions.
|
||||
- End of Region Table: card with region number -1 in columns 4-5 (" -1" Card).
|
||||
|
||||
Example (two-card region expression 1 + 2 - 3 OR 4 + 5 - 6):
|
||||
Card 1:
|
||||
___111 OR 1 2 -3 4
|
||||
Card 2:
|
||||
___111 5 -6
|
||||
|
||||
Dummy region (no solids):
|
||||
___012 (columns 9-69 blank) (Region 12 dummy)
|
||||
|
||||
Sentinel:
|
||||
____-1
|
||||
|
||||
Best Practice:
|
||||
Prefer multiple simple non-OR regions sharing a common identification code (see Section 5) over complex OR expressions.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 5. REGION IDENTIFICATION TABLE
|
||||
|
||||
Each card assigns either an Item Code (component) or Air Space Code to a region.
|
||||
|
||||
Format:
|
||||
Columns:
|
||||
1-2 Blank
|
||||
3-5 Region Number
|
||||
6-10 Blank or separator
|
||||
11-15 Item or Air Space Code (integer)
|
||||
16-69 Optional additional codes or reserved blanks
|
||||
70-80 Comment (verbal description + region formula)
|
||||
|
||||
Special Codes:
|
||||
- 01 Exterior air / Surrounding void (example usage; manuals show 02,03,05 for interior categories).
|
||||
- 02 Crew compartment air
|
||||
- 03 Passenger compartment air
|
||||
- 05 Engine compartment air
|
||||
- 09 (DO NOT USE) reserved
|
||||
- 111 Dummy region code
|
||||
- 501 Track region (may be remapped to 502 for track edge by GIFT processing)
|
||||
- 999 Forbidden as a component code
|
||||
|
||||
Item Code Ranges (per Table 3 of manual):
|
||||
1- 99 Personnel & miscellaneous interior
|
||||
100-199 Armor & structural
|
||||
200-299 Powertrain / engine group
|
||||
300-399 Suspension / drive
|
||||
400-499 Mobility auxiliaries
|
||||
500-599 Track / running gear (501 special)
|
||||
600-699 Armament
|
||||
700-799 Fire control
|
||||
800-899 Communications / electronics
|
||||
900-998 Ammunition
|
||||
|
||||
If a region is air: its code is an air space code; otherwise an item code.
|
||||
|
||||
Example:
|
||||
__001 40 STEERING WHEEL 1-2
|
||||
__002 100 STEERING SHAFT 2
|
||||
__012 111 DUMMY
|
||||
__019 02 INTERIOR AIR 19
|
||||
__020 02 INTERIOR AIR 20
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 6. REGION RPP TABLE (OPTIONAL)
|
||||
|
||||
Purpose: Override automatically computed bounding boxes (per region).
|
||||
|
||||
Card Format:
|
||||
Columns:
|
||||
1-10 Region Number
|
||||
11-20 XMIN
|
||||
21-30 XMAX
|
||||
31-40 YMIN
|
||||
41-50 YMAX
|
||||
51-60 ZMIN
|
||||
61-70 ZMAX
|
||||
71-80 Comment
|
||||
|
||||
Omit dummy regions (those with identification code 111).
|
||||
If absent for a region, GIFT computes bounding RPP automatically.
|
||||
|
||||
Example:
|
||||
1 20.0000 23.0000 -10.0000 10.0000 28.0000 46.0000
|
||||
2 15.0000 21.5000 -11.0000 11.0000 28.0000 46.0000
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 7. STORAGE FORMULA (MASTER-ASTER SIZE NDQ)
|
||||
|
||||
Minimum NDQ (memory words) must satisfy largest of:
|
||||
1) LDES - 1
|
||||
2) 6 * (NSOLID) + LREGON - 1
|
||||
3) K * (NREGION) + LRIN - 1 (K = 5 CDC, 8 UNIVAC, 11 IBM)
|
||||
|
||||
Plus if alphanumeric region descriptions stored:
|
||||
NDQ >= LDES + (K - 1) * NREGION - 1
|
||||
|
||||
Detailed formula from Volume II (for precise sizing):
|
||||
TOTAL STORAGE =
|
||||
4 * (#Solids)
|
||||
+ 7 * (#RCC)
|
||||
+12 * (#REC)
|
||||
+ 8 * (#TRC)
|
||||
+15 * (#TEC)
|
||||
+ 4 * (#SPH)
|
||||
+12 * (#ELL/ELL1)
|
||||
+11 * (#TOR)
|
||||
+ 6 * (#RPP)
|
||||
+15 * (#BOX)
|
||||
+12 * (#RAW)
|
||||
+ (ARB Storage)
|
||||
+ (Non-convex ARS Storage)
|
||||
+ (Convex ARS Storage)
|
||||
+14 * (#Regions)
|
||||
+ (#Region Descriptors)
|
||||
+ 6
|
||||
+ (Alphanumeric Storage optional)
|
||||
|
||||
ARB Storage per ARB:
|
||||
4 * (#faces) + 1
|
||||
|
||||
Non-convex ARS:
|
||||
88 + 4 * (L + 2 * N * M)
|
||||
where L = #points read directly
|
||||
M = #curves
|
||||
N = #points per curve
|
||||
|
||||
Convex ARS:
|
||||
4 * (#non-degenerate faces) + 1
|
||||
|
||||
Region Descriptors:
|
||||
Count of non-zero entries in columns 9-69 of Region Table.
|
||||
|
||||
Alphanumeric storage per region:
|
||||
CDC=4, UNIVAC=7, IBM=10 words.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 8. CONTROL (PROGRAM OPTION) CARD (GIFT EXECUTION)
|
||||
|
||||
Columns (classic Control Card from Vol II):
|
||||
1 Use processed geometry from binary Unit 4 (0=no, non-zero=yes)
|
||||
2 Write processed geometry to binary Unit 4 (0=no, non-zero=yes)
|
||||
3-5 FORTRAN unit number for raw COM-GEOM input (0 or 5 = Unit 5)
|
||||
6 Print MASTER-ASTER array (debug) if non-zero
|
||||
7 Print Ordered Region Identification Table if non-zero
|
||||
8 Print Ordered Region RPP Equivalent Table if non-zero
|
||||
9 Print Solid RPP Equivalent Table if non-zero
|
||||
10 Suppress printing of Solid/Region tables if non-zero (No Print)
|
||||
11-30 Reserved / (legacy fields)
|
||||
31-40 Overlap tolerance TOL (F10)
|
||||
41-50 Line-of-sight gap tolerance TOLLOS (F10)
|
||||
51-55 Void Space Code (integer; default 1 if 0)
|
||||
56-60 Phantom Item Code (default 111 if 0)
|
||||
61-65 End-of-ray Signal Code (default 9 if 0)
|
||||
66-80 Comments
|
||||
|
||||
Example:
|
||||
0 1 5 0 1 1 0 0.010000 0.010000 1 111 9 RUN A
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 9. VALIDATION & RULES SUMMARY
|
||||
|
||||
1) Solid Numbers: Consecutive starting at 1. No gaps.
|
||||
2) Region Numbers: Consecutive starting at 1; end sentinel: region -1 card.
|
||||
3) Dummy Regions: Allowed; must carry identification code 111; no solids.
|
||||
4) Item Regions: Must not overlap beyond TOL.
|
||||
5) Air Regions: May overlap same air code; not different air codes beyond TOL.
|
||||
6) Negative Descriptors: Subtraction only applies to immediately preceding accumulated set in branch.
|
||||
7) OR Branches: Disjoint boolean unions; minimized use recommended.
|
||||
8) Tolerances: Set to reflect modeling precision; typical TOL=TOLLOS=0.01 in same units.
|
||||
9) Special Codes: Avoid 999, 09, ensure proper use of 111, 501/502.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 10. MINIMAL WORKED EXAMPLE
|
||||
|
||||
Title:
|
||||
SAMPLE LIGHT VEHICLE WITH DRIVER IN
|
||||
|
||||
Target Specification:
|
||||
20 20 1
|
||||
|
||||
Solid (excerpt):
|
||||
1 BOX 21.5000 37.0000 0.0000 0.0000 45.0000 11.0000 40.0000 0.0000 0.0000 11.0000 0.0000 0.0000 STEERING WHEEL
|
||||
2 TOR 21.5000 37.0000 0.0000 0.0000 45.0000 11.0000 40.0000 11.0000 RIM
|
||||
3 SPH 33.5000 37.0000 12.0000 7.5000 HEAD
|
||||
...
|
||||
20 RCC -30.0000 -70.0000 -30.0000 48.0000 24.0000 0.0000 13.0000 LEG
|
||||
|
||||
Region Table (excerpt):
|
||||
1 1 2 (STEERING WHEEL ASSEMBLY: BOX intersect TOR)
|
||||
2 3 (HEAD)
|
||||
3 4 -5 6 (ARM: Intersection of solids 4 & 6 minus 5)
|
||||
...
|
||||
12 (Dummy)
|
||||
...
|
||||
End: -1
|
||||
|
||||
Region Identification:
|
||||
1 40 STEERING WHEEL 1 2
|
||||
2 100 STEERING SHAFT 3
|
||||
3 100 ARM (UPPER) 4 -5 6
|
||||
12 111 DUMMY
|
||||
19 02 INTERIOR AIR
|
||||
20 02 INTERIOR AIR
|
||||
|
||||
Region RPP (optional):
|
||||
1 20.0 23.0 -10.0 10.0 28.0 46.0
|
||||
2 32.0 37.5 -11.0 11.0 28.0 46.0
|
||||
...
|
||||
|
||||
Control Card:
|
||||
0 1 5 0 1 0 0 0 0.010000 0.010000 1 111 9 STD RUN
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 11. COMMON PITFALLS AND RECOMMENDATIONS
|
||||
|
||||
1) Overlapping Items: Resolve by subtracting one from the other or adjusting tolerance; large overlaps are modeling errors, not tolerance issues.
|
||||
2) Excessive OR Usage: Replace with multiple regions sharing identification codes to simplify ray tracing and checking.
|
||||
3) Insufficient NDQ: Use storage formula; double NDQ if "NO MORE ROOM" messages appear after >75% of Solid Table processed.
|
||||
4) Dummy Regions: Keep for numbering alignment only; always code 111.
|
||||
5) Precision: If coordinate acquisition is from drawings with limited precision, set TOL/TOLLOS to at least the drawing resolution.
|
||||
6) ARS Conversions: For legacy MAGIC/SHOTGEN conversions using ARS, verify point ordering and duplication to preserve intended triangular mesh.
|
||||
7) TEC Ratio P: Ensure P > 1; if P < 1 code will treat cone as inverted (swap top/base); better to input consistent orientation.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 12. DIFFERENCES VS. OTHER ERA GEOMETRY FORMATS
|
||||
|
||||
MAGIC / SHOTGEN vs COM-GEOM v1:
|
||||
- COM-GEOM v1 uses boolean combinatorial solids with explicit card columns.
|
||||
- Emphasizes minimal OR, encourages identification grouping.
|
||||
- Tolerances (TOL/TOLLOS) user-adjustable on Control Card (MAGIC earlier versions required source edits).
|
||||
- Storage formulas explicitly published; user responsible for NDQ sizing (contrast with more opaque internal allocation in other codes).
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 13. QUICK REFERENCE TABLES
|
||||
|
||||
13.1 Solid Symbols and Required Fields
|
||||
|
||||
Symbol | Type | Fields (Order)
|
||||
RPP | Axis-aligned box | XMIN XMAX YMIN YMAX ZMIN ZMAX
|
||||
BOX | Oriented box | Vx Vy Vz Hx Hy Hz Wx Wy Wz Dx Dy Dz
|
||||
RAW | Right wedge | Vx Vy Vz Hx Hy Hz Wx Wy Wz Dx Dy Dz
|
||||
ARB4-8 | Convex polyhedron | Vertex triples (count in col 7)
|
||||
ARS | Triangulated polyhedron | M, N then M x N vertex triples
|
||||
ELL | Ellipsoid (foci) | F1x F1y F1z F2x F2y F2z L
|
||||
ELL1 | Ellipsoid (axis) | Vx Vy Vz Ax Ay Az R (col 7=1)
|
||||
SPH | Sphere | Vx Vy Vz R
|
||||
RCC | Right circular cylinder | Vx Vy Vz Hx Hy Hz R
|
||||
REC | Right elliptical cylinder | Vx Vy Vz Hx Hy Hz Ax Ay Az Bx By Bz
|
||||
TRC | Truncated circular cone | Vx Vy Vz Hx Hy Hz R1 R2
|
||||
TEC | Truncated elliptic cone | Vx Vy Vz Hx Hy Hz Ax Ay Az Bx By Bz P
|
||||
TOR | Torus | Vx Vy Vz Nx Ny Nz R1 R2
|
||||
|
||||
13.2 Special Identification Codes
|
||||
111 Dummy region
|
||||
501 Track (may become 502)
|
||||
01,02,03,05 Air categories (examples)
|
||||
999 Forbidden component code
|
||||
09 Forbidden air code
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 14. EXTENSIBILITY NOTES
|
||||
|
||||
To add new solid types (historical practice for later internal variants):
|
||||
- Reserve a new 3-char symbol in columns 4-6.
|
||||
- Define fixed F10.5 field sequence and document vertex or vector semantics.
|
||||
- Update MASTER-ASTER storage calculator with new per-solid word count.
|
||||
- Maintain backward compatibility by keeping existing card column positions.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
SECTION 15. VALIDATION CHECKLIST
|
||||
|
||||
Before running GIFT:
|
||||
[ ] Title card unit code set.
|
||||
[ ] NSOLID equals number of solid definitions.
|
||||
[ ] NREGION equals number of region definitions (excluding -1 sentinel).
|
||||
[ ] Solid numbers consecutive from 1.
|
||||
[ ] Region numbers consecutive from 1; -1 end card present.
|
||||
[ ] Region Identification codes valid (no 999, no 09).
|
||||
[ ] Dummy regions have 111 code and no solids.
|
||||
[ ] NDQ sized using formula (or rule of thumb 45*NSOLID + margin).
|
||||
[ ] TOL/TOLLOS appropriate for measurement precision.
|
||||
[ ] Void / Phantom / End-of-ray codes non-zero or rely on defaults (1,111,9).
|
||||
[ ] Optional Region RPP Table consistent (no dummy region entries).
|
||||
[ ] No unintended overlaps (test with CHECK option).
|
||||
[ ] Use CHECK / TESTG GIFT options for geometry validation before production runs.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
||||
End of COM-GEOM v1 ASCII specification.
|
||||
@@ -1,350 +0,0 @@
|
||||
# COMGEOM Versions (1967–present)
|
||||
|
||||
This is a survey of the COMGEOM geometry input language across its lifecycle,
|
||||
from the earliest MAGI/MAGIC card decks through the stable v1 and the tokenized
|
||||
v4/v5 formats. It highlights what changed between versions, when those changes
|
||||
occurred, and provides practical guidance for recognizing and migrating old
|
||||
decks.
|
||||
|
||||
Quick summary: v0 (pre‑v1) was the early MAGIC card style with implicit
|
||||
intersections and “OR” unions; v1 formalized strict punched‑card columns; v2/v3
|
||||
were transitional internal experiments (partial free‑field parsing,
|
||||
parentheses, generalized cones); v4 is the fully tokenized, parenthesized
|
||||
grammar; v5 added attribute/metadata lines atop v4.
|
||||
|
||||
---
|
||||
|
||||
## Timeline
|
||||
|
||||
- 1967–1974: Pre‑formal MAGIC/MAGI “v0”
|
||||
- 12 primitive shapes; implicit intersection (adjacency), `OR` for unions; no parentheses
|
||||
- Dual ellipsoid input forms; 5‑card ARB; optional TESTG/VOLUM/AREA sections
|
||||
- Region IDs (3‑digit components / 2‑digit spaces) already in place
|
||||
- 1974–1976: COMGEOM v1 (formal punched‑card format)
|
||||
- Strict column positions; fixed parameter counts per primitive
|
||||
- Region table layout (cards 16A/16B) and `-1` terminator card
|
||||
- Error taxonomy; RPP enclosure required
|
||||
- 1977–1981: Transitional v2 → v3 (internal, not publicly specified)
|
||||
- v2: Column‑first with free‑field fallback; early generalized cones; explicit `+` tokens begin to appear
|
||||
- v3: Free‑field first; limited parentheses; explicit `+ - UNION/OR`; auto bounding boxes begin to appear
|
||||
- 1982–1984: COMGEOM v4 (stable token grammar)
|
||||
- Whitespace/tokenized parsing; parentheses; decoupled geometry vs. region structure
|
||||
- 1985+: COMGEOM v5 (v4 + attributes)
|
||||
- Attribute/metadata lines: material, aircode, color, region parameters; same boolean grammar as v4
|
||||
|
||||
Modern translators (e.g., `comgeom-g`) typically support v1, v4, and v5. Other
|
||||
versions would require manual adaptation.
|
||||
|
||||
---
|
||||
|
||||
## Summary of Noteworthy Changes
|
||||
|------------|---------|--------------------------------------|------------------------------------------------|
|
||||
| Era | Version | Context | Noteworthy Changes |
|
||||
|------------|---------|--------------------------------------|------------------------------------------------|
|
||||
| 1967–1970 | v0 | Early MAGI/MAGIC on CDC/BRL | 12 primitives; adjacency = intersection; |
|
||||
| | | | `-` subtract; `OR` unions; no parentheses; |
|
||||
| | | | TEC permits oblique height vectors |
|
||||
|------------|---------|--------------------------------------|------------------------------------------------|
|
||||
| 1970–1974 | v0 | MAGIC manuals | Canonical 5‑card ARB; dual ellipsoid input |
|
||||
| | | | (foci vs center form); optional |
|
||||
| | | | TESTG/VOLUM/AREA cards; strict(er) carding |
|
||||
|------------|---------|--------------------------------------|------------------------------------------------|
|
||||
| 1974–1976 | v1 | GIFT integration; CDC/FORTRAN | Formal fixed‑columns per primitive; |
|
||||
| | | | region table cards (16A/16B); `-1` end marker; |
|
||||
| | | | RPP enclosure required |
|
||||
|------------|---------|--------------------------------------|------------------------------------------------|
|
||||
| 1977–1979 | v2 | Internal expansion | Column parsing w/ free‑field fallback; |
|
||||
| | | | explicit `+` tokens; early generalized cones; |
|
||||
| | | | torus parameter ordering fixes |
|
||||
|------------|---------|--------------------------------------|------------------------------------------------|
|
||||
| 1979–1981 | v3 | Move to machine‑independent parsing | Whitespace tokenization; shallow parentheses; |
|
||||
| | | | explicit `+ - UNION/OR`; auto RPP; |
|
||||
| | | | early ARB face validation |
|
||||
|------------|---------|--------------------------------------|------------------------------------------------|
|
||||
| 1982–1984 | v4 | Consolidation into BRL‑CAD pipeline | Fully tokenized whitespace grammar; |
|
||||
| | | | stable parentheses; clean geometry/structure |
|
||||
| | | | separation |
|
||||
|------------|---------|--------------------------------------|------------------------------------------------|
|
||||
| 1985+ | v5 | Attribute/metadata enrichment | Adds attribute lines (material, aircode, |
|
||||
| | | | color, region params); same v4 |
|
||||
| | | | geometry/boolean grammar |
|
||||
|------------|---------|--------------------------------------|------------------------------------------------|
|
||||
|
||||
Provenance: v0 details are drawn from the 1967 MAGI report and the MAGIC JTCG
|
||||
User Manual (61 JTCG/ME‑71‑7‑1). v1 is documented in BRL Report 1802 and
|
||||
ARBRL‑TR‑02189. v2/v3 are reconstructed from tool comments and translator
|
||||
heuristics. v4/v5 are reflected in BRL‑CAD-era translators and user guides.
|
||||
|
||||
---
|
||||
|
||||
## Primitive set by version
|
||||
|
||||
The early “MAGIC twelve” primitives were established by 1970 and remained the
|
||||
backbone across versions:
|
||||
|
||||
- RPP (axis‑aligned box), BOX (oriented box), SPH, RCC (right circular
|
||||
cylinder), REC (right elliptical cylinder), TRC (truncated right angle cone),
|
||||
TEC (truncated elliptic cone; height vector need not be perpendicular to base),
|
||||
ELL (ellipsoid of revolution), RAW/WED (right angle wedge), ARB (arbitrary
|
||||
convex polyhedron), ARS (arbitrary curved surface), TOR (torus)
|
||||
|
||||
| Primitive | v0 (MAGIC) | v1 | v2/v3 (notes) | v4 | v5 |
|
||||
|-----------|------------|----|------------------------------------|----|----|
|
||||
| RPP | ✓ | ✓ | Auto‑derive allowed (v3) | ✓ | ✓ |
|
||||
| BOX | ✓ | ✓ | | ✓ | ✓ |
|
||||
| SPH | ✓ | ✓ | | ✓ | ✓ |
|
||||
| RCC | ✓ | ✓ | Param order normalized (v3) | ✓ | ✓ |
|
||||
| REC | ✓ | ✓ | | ✓ | ✓ |
|
||||
| TRC | ✓ | ✓ | Distinct from TEC clarified (v3) | ✓ | ✓ |
|
||||
| TEC | ✓ | ✓ | Pre‑TGC experiments | ✓ | ✓ |
|
||||
| ELL | ✓ (dual) | ✓ | Dual input retained | ✓ | ✓ |
|
||||
| RAW/WED | ✓ | ✓ | | ✓ | ✓ |
|
||||
| ARB | ✓ (5‑card) | ✓ | Face validation begins (v3) | ✓ | ✓ |
|
||||
| ARS | ✓ | ✓ | Same semantics | ✓ | ✓ |
|
||||
| TOR | ✓ | ✓ | Minor<Major enforcement tightened | ✓ | ✓ |
|
||||
|
||||
Notes:
|
||||
- ARB in v0/v1 is the “MAGIC 5‑card” convex polyhedron: 8 vertices then 6 faces
|
||||
by ordinal indices; BRL‑CAD later canonicalizes to `arb8`.
|
||||
- Ellipsoid dual input (foci+major length or center+axis+equatorial radius) is
|
||||
an early quirk that persists; translators normalize internally.
|
||||
- TEC (truncated elliptic cone) allows a non‑perpendicular height vector as
|
||||
early as v0; generalized cone (TGC) is a later BRL‑CAD primitive outside
|
||||
COMGEOM proper.
|
||||
|
||||
---
|
||||
|
||||
## Boolean and region semantics across versions
|
||||
|
||||
### v0 (pre‑v1)
|
||||
- Intersection: implied by adjacency of body references (`Region = 1 2` means
|
||||
inside both 1 and 2)
|
||||
- Subtraction: leading minus (`-3` means outside body 3)
|
||||
- Union: `OR` between clauses (`Region = (1 -2) OR (4 -5)`)
|
||||
- No parentheses: evaluation is effectively left‑to‑right by clause; `OR` lumps
|
||||
subregions
|
||||
- “Buttressing” rule: where two bodies share a surface, each region must negate
|
||||
the other body to avoid ambiguous boundaries (e.g., `+2 -3` vs `+3 -2`)
|
||||
|
||||
Example (v0 style):
|
||||
```
|
||||
200 = 2 -3
|
||||
300 = 3 -2
|
||||
```
|
||||
|
||||
### v1
|
||||
- Same operators, strict fixed columns:
|
||||
- Region number cols 1–5, optional `OR` marker (IA(1)), then up to 9
|
||||
operator+body tokens per card
|
||||
- Continuation cards permitted; `-1` in cols 1–5 terminates region table
|
||||
- Parentheses still not part of the grammar; grouping via `OR` card positions
|
||||
|
||||
### v2 / v3 (transitional)
|
||||
- v2: Column parsing with free‑field fallback; explicit `+` tokens increasingly
|
||||
required in ambiguous decks
|
||||
- v3: Free‑field tokenization; limited parentheses; `UNION`/`U` recognized
|
||||
synonyms for `OR`
|
||||
- Automatic bounding boxes if RPP omitted (conventional runs)
|
||||
|
||||
### v4 / v5
|
||||
- Full token grammar; parentheses supported; explicit `+ - OR UNION` with
|
||||
predictable precedence
|
||||
- Geometry declarations cleanly separated from boolean structure; comments
|
||||
supported
|
||||
|
||||
Comparative snippets:
|
||||
- v0/v1 region equation (fixed columns):
|
||||
```
|
||||
001 +2 -3
|
||||
002 +3 -2
|
||||
-1
|
||||
```
|
||||
- v4/v5 region expression (tokenized):
|
||||
```
|
||||
region 001: +2 -3
|
||||
region 002: +3 -2
|
||||
```
|
||||
- v4/v5 with grouping:
|
||||
```
|
||||
region 010: ( +2 -3 ) OR ( +4 -5 )
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Card / field structure changes
|
||||
|
||||
### v0 (MAGIC card decks)
|
||||
- Mixed adherence to columns
|
||||
- Multi‑card primitives:
|
||||
- BOX: Vx Vy Vz / Ax Ay Az / Bx By Bz / Cx Cy Cz (2 cards)
|
||||
- RCC/REC/TRC/RAW: 2 cards; TEC: 3 cards; ARB: 5 cards; ARS: 2 +
|
||||
M*ceil((N+1)/2) cards
|
||||
- Region identification card per region: region number + component code or
|
||||
space code + description
|
||||
|
||||
### v1
|
||||
- Formal fixed‑column definitions per primitive; field counts validated
|
||||
- RPP mandatory enclosure
|
||||
- Region table formatting codified; `-1` terminator
|
||||
- Error messages categorized (fatal vs non‑fatal)
|
||||
|
||||
### v2/v3 (reconstructed)
|
||||
- Free‑field parsing introduced to accept large/overflow coordinates
|
||||
- Whole‑line comments (`#` or `!`) accepted in some parsers (internal)
|
||||
- Early “DUMMY” region recognition; improved error diagnostics
|
||||
|
||||
### v4/v5
|
||||
- Whitespace/token parsing only; columns ignored
|
||||
- v5 adds attribute/metadata lines (see next section)
|
||||
|
||||
---
|
||||
|
||||
## Attributes and metadata
|
||||
|
||||
- v0/v1: Region identification via numeric codes only (3‑digit component;
|
||||
2‑digit space). Materials/colors external or implicit.
|
||||
- v5: Attribute lines augment region definitions:
|
||||
- `material=Steel`, `aircode=02`, `color=0/255/0`, `los=...`, `density=...`
|
||||
- Name=value or token style, parsed separately from geometry/boolean
|
||||
|
||||
---
|
||||
|
||||
## Recognizing versions in the wild (cheat sheet)
|
||||
|
||||
- Likely v0 (MAGIC):
|
||||
- Cards referencing TESTG/VOLUM/AREA sections
|
||||
- Primitive blocks matching MAGIC manual’s 2/3/5‑card counts and ordering
|
||||
- Region tables with `-1` terminator but no parentheses anywhere; `OR`
|
||||
appears literally
|
||||
- Ellipsoid blocks with foci pair + major length
|
||||
|
||||
- v1:
|
||||
- Strict columns; values neatly aligned; region table with IA(1) `OR` markers
|
||||
- RPP enclosure present and required
|
||||
|
||||
- v2/v3 (transitional):
|
||||
- Mixture of column and free‑field lines; occasional parentheses; `UNION`
|
||||
token present
|
||||
- Comments starting `#` or `!` in legacy sources; generalized cone parameters
|
||||
pre‑TGC
|
||||
|
||||
- v4:
|
||||
- Whitespace tokens; parentheses; clean separation of bodies from boolean
|
||||
structure; no attribute lines
|
||||
|
||||
- v5:
|
||||
- Same as v4 plus attribute/metadata lines (e.g., `material=`, `aircode=`,
|
||||
`color=`)
|
||||
|
||||
---
|
||||
|
||||
## Migration guidance
|
||||
|
||||
1. Inventory and validate
|
||||
- Count per‑primitive parameters; flag lines with wrong cardinality
|
||||
- Verify region table completeness: every point belongs to exactly one
|
||||
region; buttressing negations present
|
||||
|
||||
2. Convert v0 → v1
|
||||
- Pad to fixed columns; split multi‑card primitives correctly
|
||||
- Keep dual ellipsoid inputs; normalize internally if supported
|
||||
|
||||
3. Convert v1 → v4/v5
|
||||
- Translate region boolean expressions to explicit tokens with parentheses
|
||||
- Add attributes (v5): material, aircode (01 external, 02 crew, 05 engine,
|
||||
09 terminus), region properties
|
||||
|
||||
4. Validate by ray tracing
|
||||
- Reproduce small sample angles from manual output (cell listings,
|
||||
obliquities, LOS/normal thickness) to confirm equivalence
|
||||
|
||||
Pitfalls:
|
||||
- Ellipsoid dual form mapping; ensure axis vector direction/length matches
|
||||
major axis semantics
|
||||
- TEC/TRC confusion in transitional decks; verify height vector
|
||||
perpendicularity (TEC: may be oblique)
|
||||
- ARB face index blocks: ensure 6 faces present; repeat a face for 4/5‑face
|
||||
forms (per v1 storage) or convert to `arb8`
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### v0 style (MAGIC region)
|
||||
```
|
||||
15 +15 -17 -14 -16
|
||||
16 +15 +16 -14 -17 -21 -22
|
||||
-1
|
||||
```
|
||||
|
||||
### v1 fixed columns (same semantics)
|
||||
```
|
||||
00015 +15 -17 -14 -16
|
||||
00016 +15 +16 -14 -17 -21 -22
|
||||
-1
|
||||
```
|
||||
|
||||
### v4/v5 tokenized with grouping
|
||||
```
|
||||
region 15: (+15 -17 -14 -16)
|
||||
region 16: ((+15 +16 -14 -17) OR (-21 -22)) # if logically intended
|
||||
```
|
||||
|
||||
### v5 attributes
|
||||
```
|
||||
region 15: (+15 -17 -14 -16)
|
||||
material=Steel
|
||||
aircode=02
|
||||
color=128/128/128
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Region identification codes (early and stable)
|
||||
|
||||
Components (3 digits):
|
||||
- 001–099 Internal components
|
||||
- 100–199 Armor
|
||||
- 200–299 Fuel (often reused in later targets)
|
||||
- 300–399 Misc external
|
||||
- 400–499 Gun components
|
||||
- 500–599 Track suspension (501 reserved for track; edge hits become 502)
|
||||
- 600–699 Wheel suspension
|
||||
- 700–799 Power train
|
||||
- 800–899 Miscellaneous
|
||||
- 900–998 Unused
|
||||
- 999 Ground/soil
|
||||
|
||||
Spaces (2 digits):
|
||||
- 01 External air
|
||||
- 02 Crew compartment air
|
||||
- 05 Engine compartment air
|
||||
- 09 No further target
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- MAGI (1967): Early geometric description technique for vulnerability analysis
|
||||
(MAGI‑6701)
|
||||
- MAGIC User Manual (JTCG/ME 61 JTCG/ME‑71‑7‑1; ‑7‑2‑1; ‑7‑2‑2): Combinatorial
|
||||
geometry, body/region cards, optional QA routines, sample problems
|
||||
- BRL Report 1802; ARBRL‑TR‑02189: Formal COMGEOM v1 card specifications
|
||||
- BRL‑CAD documentation: Primitive mappings (e.g., `arb8`, TGC) and later
|
||||
pipeline translators
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Field counts per primitive (v0/v1 reference)
|
||||
|
||||
- RPP: 6 scalars (Xmin Xmax Ymin Ymax Zmin Zmax)
|
||||
- BOX: 12 scalars (Vx Vy Vz Ax Ay Az Bx By Bz Cx Cy Cz) over 2 cards
|
||||
- SPH: 4 scalars (Cx Cy Cz R)
|
||||
- RCC: 7 scalars over 2 cards (Vx Vy Vz Hx Hy Hz R)
|
||||
- REC: 12 scalars over 2 cards (Vx Vy Vz Hx Hy Hz R1x R1y R1z R2x R2y R2z)
|
||||
- TRC: 8 scalars over 2 cards (Vx Vy Vz Hx Hy Hz R1 R2)
|
||||
- ELL: (foci Fl(3) F2(3) + L) OR (center V(3) + A(3) + equatorial R)
|
||||
- RAW/WED: 12 scalars over 2 cards (V(3) + leg A(3) + leg B(3) + width C(3); A and B interchangeable)
|
||||
- ARB: 5 cards: vertices 1–2 on card A; pairs (3,4), (5,6), (7,8) on cards B; faces (6 x 4 ordinals) on card C
|
||||
- TEC: 13 scalars over 3 cards (V(3) H(3) A(3) B(3) P)
|
||||
- ARS: 2 header integers (M curves, N points) + M * ceil((N+1)/2) cards of point pairs
|
||||
@@ -1,186 +0,0 @@
|
||||
#include "ComGeomConverter.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include "ComGeomReader.h"
|
||||
#include "SolidParser.h"
|
||||
#include "RegionManager.h"
|
||||
|
||||
ComGeomConverter::ComGeomConverter(const std::string &in,
|
||||
const std::string &out,
|
||||
const ConverterOptions &opts)
|
||||
: m_inPath(in), m_outPath(out), m_opts(opts)
|
||||
{}
|
||||
|
||||
ComGeomConverter::~ComGeomConverter() {
|
||||
if (m_in) fclose(m_in);
|
||||
if (m_out) wdb_close(m_out);
|
||||
}
|
||||
|
||||
bool ComGeomConverter::openInput() {
|
||||
if (!bu_file_exists(m_inPath.c_str(), NULL)) {
|
||||
std::perror(m_inPath.c_str());
|
||||
return false;
|
||||
}
|
||||
m_in = fopen(m_inPath.c_str(), "rb");
|
||||
if (!m_in) {
|
||||
std::perror(m_inPath.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ComGeomConverter::openOutput() {
|
||||
if (bu_file_exists(m_outPath.c_str(), NULL))
|
||||
bu_log("Warning: appending to existing database\n");
|
||||
m_out = wdb_fopen(m_outPath.c_str());
|
||||
if (!m_out) {
|
||||
std::perror(m_outPath.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ComGeomConverter::writeDbHeader(const std::string &title, const std::string &units) {
|
||||
if (mk_conversion(units.c_str()) < 0) {
|
||||
std::printf("WARNING: unknown units '%s', using inches\n", units.c_str());
|
||||
(void)mk_conversion("in");
|
||||
}
|
||||
if (mk_id_units(m_out, title.c_str(), units.c_str()) < 0) {
|
||||
std::printf("Unable to write database ID, units='%s'\n", units.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int ComGeomConverter::run() {
|
||||
if (m_opts.version != 0 && m_opts.version != 1 && m_opts.version != 4 && m_opts.version != 5) {
|
||||
std::fprintf(stderr,"version %d not supported\n", m_opts.version);
|
||||
return 1;
|
||||
}
|
||||
if (!openInput() || !openOutput()) return 1;
|
||||
|
||||
std::printf("Reading version %d COMGEOM file\n", m_opts.version);
|
||||
|
||||
/* Title & units */
|
||||
std::string title, units;
|
||||
{
|
||||
ComGeomReader r(m_in);
|
||||
auto l = r.next("title card");
|
||||
if (!l) { std::printf("Empty input file: no title record\n"); return 10; }
|
||||
std::string_view sv(l->s);
|
||||
if (m_opts.version == 0) {
|
||||
title = ComGeomReader::rstrip(sv);
|
||||
ComGeomReader::trimLeadingSpacesInplace(title);
|
||||
units = "in";
|
||||
} else if (m_opts.version == 1) {
|
||||
title = ComGeomReader::rstrip(sv);
|
||||
ComGeomReader::trimLeadingSpacesInplace(title);
|
||||
units = "in";
|
||||
} else {
|
||||
units.assign(sv.substr(0,2));
|
||||
title.assign(sv.substr(3));
|
||||
ComGeomReader::trimLeadingSpacesInplace(title);
|
||||
title = ComGeomReader::rstrip(title);
|
||||
units = ComGeomReader::rstrip(units);
|
||||
ComGeomReader::toLowerInplace(units);
|
||||
}
|
||||
std::printf("Title: %s\nUnits: %s\n", title.c_str(), units.c_str());
|
||||
}
|
||||
if (!writeDbHeader(title, units)) return 1;
|
||||
|
||||
int solTotal=0, regTotal=0;
|
||||
{
|
||||
ComGeomReader r(m_in);
|
||||
if (m_opts.version == 0) {
|
||||
// Attempt single read first
|
||||
auto l = r.next("v0 constraints candidate");
|
||||
if (l) {
|
||||
std::string_view sv(l->s);
|
||||
solTotal = ComGeomReader::fieldInt(sv, 30, 10);
|
||||
regTotal = ComGeomReader::fieldInt(sv, 40, 10);
|
||||
}
|
||||
if (solTotal <= 0 || regTotal <= 0) {
|
||||
// Scan up to 50 lines to find plausible NBODY/NRMAX pair
|
||||
solTotal = regTotal = 0;
|
||||
for (int scan=0; scan<50; ++scan) {
|
||||
auto cand = r.next("scan constraints");
|
||||
if (!cand) break;
|
||||
std::string_view sv2(cand->s);
|
||||
int nb = ComGeomReader::fieldInt(sv2, 30, 10);
|
||||
int nr = ComGeomReader::fieldInt(sv2, 40, 10);
|
||||
if (nb > 0 && nr > 0) {
|
||||
solTotal = nb;
|
||||
regTotal = nr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (solTotal == 0 || regTotal == 0) {
|
||||
std::printf("Warning: v0 NBODY/NRMAX not found, dynamic sizing will be used\n");
|
||||
solTotal = 0; // dynamic
|
||||
regTotal = 0;
|
||||
}
|
||||
}
|
||||
} else if (m_opts.version == 1) {
|
||||
solTotal = regTotal = 9999;
|
||||
} else {
|
||||
auto l = r.next("control card");
|
||||
if (!l) { std::printf("No control card .... STOP\n"); return 10; }
|
||||
std::string_view sv(l->s);
|
||||
if (m_opts.version == 4) {
|
||||
solTotal = ComGeomReader::fieldInt(sv, 0, 20);
|
||||
regTotal = ComGeomReader::fieldInt(sv, 20, 10);
|
||||
} else {
|
||||
solTotal = ComGeomReader::fieldInt(sv, 0, 5);
|
||||
regTotal = ComGeomReader::fieldInt(sv, 5, 5);
|
||||
}
|
||||
}
|
||||
if (m_opts.verbose)
|
||||
std::printf("Expecting %d solids, %d regions\n", solTotal, regTotal);
|
||||
}
|
||||
|
||||
/* Solids */
|
||||
{
|
||||
if (m_opts.verbose) std::puts("Primitive table");
|
||||
ComGeomReader r(m_in);
|
||||
SolidParserOptions sopts{m_opts.version,m_opts.verbose,m_opts.suffix};
|
||||
sopts.legacy_trunc = m_opts.legacy_truncate_names;
|
||||
SolidParser sp(r, m_out, sopts);
|
||||
sp.setSolidsExpected(solTotal);
|
||||
while (true) {
|
||||
auto res = sp.parseNext();
|
||||
if (res.status == SolidParseResult::Eof) {
|
||||
if (m_opts.verbose)
|
||||
std::printf("\nprocessed %d of %d solids\n\n", sp.solidsProcessed(), solTotal);
|
||||
if (solTotal > 0 && sp.solidsProcessed() < solTotal && m_opts.version > 1) {
|
||||
std::printf("some solids are missing, aborting\n");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (res.status == SolidParseResult::Error) {
|
||||
std::fprintf(stderr,"error converting solid %d: %s\n",
|
||||
sp.solidsProcessed(), res.message.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Regions */
|
||||
{
|
||||
if (m_opts.verbose) std::puts("\nRegion table");
|
||||
ComGeomReader r(m_in);
|
||||
RegionManagerOptions ropts;
|
||||
ropts.version = m_opts.version;
|
||||
ropts.verbose = m_opts.verbose;
|
||||
ropts.suffix = m_opts.suffix;
|
||||
ropts.regTotal = regTotal;
|
||||
ropts.legacy_trunc = m_opts.legacy_truncate_names;
|
||||
RegionManager rm(r, m_out, ropts);
|
||||
if (!rm.parseRegions()) return 10;
|
||||
if (m_opts.verbose) std::puts("\nGroups");
|
||||
if (!rm.writeGroups()) return 10;
|
||||
if (m_opts.verbose) std::puts("");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#ifndef BRLCAD_COMGEOM_CONVERTER_H
|
||||
#define BRLCAD_COMGEOM_CONVERTER_H
|
||||
|
||||
#include "common.h"
|
||||
#include <string>
|
||||
|
||||
extern "C" {
|
||||
#include "wdb.h"
|
||||
#include "bu/file.h"
|
||||
#include "bu/log.h"
|
||||
#include "bu/app.h"
|
||||
}
|
||||
|
||||
struct ConverterOptions {
|
||||
int version = 5;
|
||||
int verbose = 0;
|
||||
std::string suffix;
|
||||
bool legacy_truncate_names = false; // enable legacy suffix truncation (max 13 chars)
|
||||
};
|
||||
|
||||
class ComGeomConverter {
|
||||
public:
|
||||
ComGeomConverter(const std::string &inputPath,
|
||||
const std::string &outputPath,
|
||||
const ConverterOptions &opts);
|
||||
~ComGeomConverter();
|
||||
int run();
|
||||
|
||||
private:
|
||||
bool openInput();
|
||||
bool openOutput();
|
||||
bool writeDbHeader(const std::string &title, const std::string &units);
|
||||
|
||||
private:
|
||||
std::string m_inPath, m_outPath;
|
||||
ConverterOptions m_opts;
|
||||
FILE *m_in = nullptr;
|
||||
rt_wdb *m_out = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,76 +0,0 @@
|
||||
#include "ComGeomReader.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cctype>
|
||||
#include <algorithm>
|
||||
|
||||
ComGeomReader::ComGeomReader(FILE *fp) : m_fp(fp) {}
|
||||
ComGeomReader::~ComGeomReader() = default;
|
||||
|
||||
std::optional<ComGeomReader::Line> ComGeomReader::next(const char * /*title*/) {
|
||||
if (!m_fp) return std::nullopt;
|
||||
std::string s;
|
||||
int c = 0;
|
||||
while ((c = fgetc(m_fp)) != EOF) {
|
||||
if (c == '\r') {
|
||||
int nc = fgetc(m_fp);
|
||||
if (nc != '\n' && nc != EOF) ungetc(nc, m_fp);
|
||||
break;
|
||||
}
|
||||
if (c == '\n') break;
|
||||
s.push_back(static_cast<char>(c));
|
||||
}
|
||||
if (c == EOF && s.empty())
|
||||
return std::nullopt;
|
||||
m_line++;
|
||||
return Line{std::move(s), m_line};
|
||||
}
|
||||
|
||||
int ComGeomReader::fieldInt(std::string_view sv, size_t start, size_t len) {
|
||||
if (start >= sv.size()) return 0;
|
||||
size_t end = std::min(sv.size(), start + len);
|
||||
// Work on the field window only
|
||||
size_t b = start, e = end;
|
||||
// Trim leading whitespace
|
||||
while (b < e && std::isspace(static_cast<unsigned char>(sv[b]))) b++;
|
||||
// Trim trailing whitespace
|
||||
while (e > b && std::isspace(static_cast<unsigned char>(sv[e-1]))) e--;
|
||||
if (b >= e) return 0;
|
||||
// Note that we preserve internal spaces to match the behavior of the
|
||||
// original C code - "1 23" would be read as 1 rather than 123. Whether
|
||||
// or not this is really desirable behavior is a question - i.e. whether
|
||||
// there is a valid use case where a line would make the above separation
|
||||
// with intent in the COMGEOM formats - but for now we match legacy code.
|
||||
std::string token(sv.substr(b, e - b));
|
||||
return std::atoi(token.c_str());
|
||||
}
|
||||
|
||||
double ComGeomReader::fieldDouble(std::string_view sv, size_t start, size_t len) {
|
||||
if (start >= sv.size()) return 0.0;
|
||||
size_t end = std::min(sv.size(), start + len);
|
||||
// Work on the field window only
|
||||
size_t b = start, e = end;
|
||||
// Trim leading whitespace
|
||||
while (b < e && std::isspace(static_cast<unsigned char>(sv[b]))) b++;
|
||||
// Trim trailing whitespace
|
||||
while (e > b && std::isspace(static_cast<unsigned char>(sv[e-1]))) e--;
|
||||
if (b >= e) return 0.0;
|
||||
std::string token(sv.substr(b, e - b)); // preserve internal spaces to match legacy C
|
||||
return std::atof(token.c_str());
|
||||
}
|
||||
|
||||
std::string ComGeomReader::rstrip(std::string_view s) {
|
||||
size_t end = s.size();
|
||||
while (end > 0 && s[end-1] == ' ') end--;
|
||||
return std::string(s.substr(0, end));
|
||||
}
|
||||
|
||||
void ComGeomReader::toLowerInplace(std::string &s) {
|
||||
for (auto &c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
|
||||
void ComGeomReader::trimLeadingSpacesInplace(std::string &s) {
|
||||
size_t i = 0;
|
||||
while (i < s.size() && std::isspace(static_cast<unsigned char>(s[i]))) i++;
|
||||
if (i) s.erase(0, i);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#ifndef BRLCAD_COMGEOM_READER_H
|
||||
#define BRLCAD_COMGEOM_READER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
class ComGeomReader {
|
||||
public:
|
||||
struct Line {
|
||||
std::string s;
|
||||
size_t number = 0;
|
||||
std::string_view view() const { return s; }
|
||||
};
|
||||
|
||||
explicit ComGeomReader(FILE *fp);
|
||||
~ComGeomReader();
|
||||
|
||||
std::optional<Line> next(const char *title);
|
||||
|
||||
static int fieldInt(std::string_view sv, size_t start, size_t len);
|
||||
static double fieldDouble(std::string_view sv, size_t start, size_t len);
|
||||
|
||||
static std::string rstrip(std::string_view s);
|
||||
static void toLowerInplace(std::string &s);
|
||||
static void trimLeadingSpacesInplace(std::string &s);
|
||||
|
||||
size_t lineNo() const { return m_line; }
|
||||
|
||||
private:
|
||||
FILE *m_fp = nullptr;
|
||||
size_t m_line = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,424 +0,0 @@
|
||||
#include "RegionManager.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
RegionManager::RegionManager(ComGeomReader &reader, rt_wdb *outfp, const RegionManagerOptions &opts)
|
||||
: m_r(reader), m_out(outfp), m_opts(opts)
|
||||
{
|
||||
// For dynamic v0 decks (regTotal == 0) preallocate provisional capacity
|
||||
size_t allocCount = static_cast<size_t>(m_opts.regTotal + 1);
|
||||
if (m_opts.version == 0 && m_opts.regTotal == 0) {
|
||||
allocCount = 4097; // provisional region cap
|
||||
}
|
||||
m_regionHeads.resize(allocCount);
|
||||
for (auto &wm : m_regionHeads) {
|
||||
WMEMBER_INIT(&wm);
|
||||
}
|
||||
groupInit();
|
||||
}
|
||||
|
||||
RegionManager::~RegionManager() {
|
||||
for (auto &wm : m_regionHeads) {
|
||||
if (wm.wm_name) {
|
||||
bu_free(wm.wm_name, "region wm_name");
|
||||
wm.wm_name = nullptr;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < m_groupCount; ++i) {
|
||||
if (m_groups[i].head.wm_name) {
|
||||
bu_free(m_groups[i].head.wm_name, "group wm_name");
|
||||
m_groups[i].head.wm_name = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string RegionManager::makeRegionName(int n) const {
|
||||
std::string suf = m_opts.suffix;
|
||||
if (m_opts.legacy_trunc && suf.size() > 13) suf.resize(13);
|
||||
char buf[64];
|
||||
std::snprintf(buf, sizeof(buf), "r%d%s", n, suf.c_str());
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
void RegionManager::colPrint(const std::string &s) {
|
||||
if (!m_opts.verbose) return;
|
||||
std::fputs(s.c_str(), stdout);
|
||||
m_colCur += s.size();
|
||||
while (m_colCur < 78 && (m_colCur % 10)) {
|
||||
putchar(' ');
|
||||
++m_colCur;
|
||||
}
|
||||
if (m_colCur >= 78) {
|
||||
std::fputc('\n', stdout);
|
||||
m_colCur = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- v0 (MAGIC) region table with OR semantics ---------------- */
|
||||
bool RegionManager::parseRegionTableV0() {
|
||||
auto first = m_r.next("v0 region card");
|
||||
if (!first) { std::fprintf(stderr,"v0 region: EOF\n"); return false; }
|
||||
std::string current = first->s;
|
||||
|
||||
int lastRegNum = 0;
|
||||
while (true) {
|
||||
int reg_num = ComGeomReader::fieldInt(current, 0, 5);
|
||||
if (reg_num < 0) break;
|
||||
if (reg_num == 0) {
|
||||
auto nl = m_r.next("v0 region card");
|
||||
if (!nl) break;
|
||||
current = nl->s;
|
||||
continue;
|
||||
}
|
||||
// resize if needed (dynamic)
|
||||
if (reg_num >= static_cast<int>(m_regionHeads.size())) {
|
||||
std::fprintf(stderr,"v0 region number %d exceeds provisional allocation\n", reg_num);
|
||||
return false;
|
||||
}
|
||||
lastRegNum = reg_num;
|
||||
wmember &head = m_regionHeads[static_cast<size_t>(reg_num)];
|
||||
if (!head.wm_name) head.wm_name = bu_strdup(makeRegionName(reg_num).c_str());
|
||||
|
||||
struct Term { int body; bool subtract; };
|
||||
std::vector<std::vector<Term>> clauses;
|
||||
clauses.emplace_back();
|
||||
|
||||
auto parseLine = [&](const std::string &line) {
|
||||
for (int i=0;i<9;++i) {
|
||||
size_t slot = 6 + i*7;
|
||||
if (slot + 7 > line.size()) break;
|
||||
std::string field = line.substr(slot, 7);
|
||||
std::string op = field.substr(0,2);
|
||||
std::string numstr = field.substr(2);
|
||||
ComGeomReader::trimLeadingSpacesInplace(op);
|
||||
numstr = ComGeomReader::rstrip(numstr);
|
||||
ComGeomReader::trimLeadingSpacesInplace(numstr);
|
||||
if (numstr.empty()) continue;
|
||||
int bnum = std::atoi(numstr.c_str());
|
||||
if (bnum == 0) continue;
|
||||
if (op == "OR" || op == "or") {
|
||||
if (!clauses.back().empty()) clauses.emplace_back();
|
||||
continue;
|
||||
}
|
||||
bool subtract = (bnum < 0);
|
||||
if (subtract) bnum = -bnum;
|
||||
clauses.back().push_back({bnum, subtract});
|
||||
}
|
||||
};
|
||||
parseLine(current);
|
||||
|
||||
while (true) {
|
||||
auto nextLine = m_r.next("v0 region card");
|
||||
if (!nextLine) break;
|
||||
int newRegTest = ComGeomReader::fieldInt(nextLine->s, 0, 5);
|
||||
std::string trimmed = ComGeomReader::rstrip(nextLine->s);
|
||||
std::string t2 = trimmed; ComGeomReader::trimLeadingSpacesInplace(t2);
|
||||
if (t2 == "end") {
|
||||
if (m_opts.regTotal == 0) m_opts.regTotal = reg_num;
|
||||
break;
|
||||
}
|
||||
if (newRegTest < 0) { // negative sentinel
|
||||
if (m_opts.regTotal == 0) m_opts.regTotal = reg_num;
|
||||
current = nextLine->s;
|
||||
break;
|
||||
}
|
||||
if (newRegTest > 0) { // new region starts
|
||||
current = nextLine->s;
|
||||
break;
|
||||
}
|
||||
parseLine(nextLine->s);
|
||||
}
|
||||
|
||||
int clauseIdx = 0;
|
||||
for (auto &cl : clauses) {
|
||||
if (cl.empty()) continue;
|
||||
wmember clauseHead; WMEMBER_INIT(&clauseHead);
|
||||
char cname[64]; std::snprintf(cname, sizeof(cname), "r%d_c%d", reg_num, ++clauseIdx);
|
||||
clauseHead.wm_name = bu_strdup(cname);
|
||||
bool firstTerm = true;
|
||||
for (auto &t : cl) {
|
||||
std::string memberName = std::string("s") + std::to_string(t.body) + m_opts.suffix;
|
||||
int op;
|
||||
if (firstTerm) {
|
||||
op = WMOP_UNION; // baseline
|
||||
firstTerm = false;
|
||||
} else {
|
||||
op = t.subtract ? WMOP_SUBTRACT : WMOP_INTERSECT;
|
||||
}
|
||||
if (!mk_addmember(memberName.c_str(), &clauseHead.l, nullptr, op)) {
|
||||
std::fprintf(stderr,"v0 mk_addmember failed clause %s member %s\n", cname, memberName.c_str());
|
||||
}
|
||||
}
|
||||
mk_comb(m_out, clauseHead.wm_name, &clauseHead.l, 0,
|
||||
"", "", nullptr, 0,0,0,0,0,0,0);
|
||||
if (!mk_addmember(clauseHead.wm_name, &head.l, nullptr, WMOP_UNION)) {
|
||||
std::fprintf(stderr,"v0 mk_addmember region %d clause %s\n", reg_num, clauseHead.wm_name);
|
||||
}
|
||||
}
|
||||
if (m_opts.verbose) colPrint(head.wm_name);
|
||||
|
||||
int sentinelTest = ComGeomReader::fieldInt(current, 0, 5);
|
||||
if (sentinelTest < 0) break;
|
||||
}
|
||||
if (m_opts.regTotal == 0) m_opts.regTotal = lastRegNum;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RegionManager::parseRegions() {
|
||||
if (m_opts.version == 0) {
|
||||
if (!parseRegionTableV0()) return false;
|
||||
for (int i=1;i<=m_opts.regTotal && i < static_cast<int>(m_regionHeads.size());++i) registerRegion(i,0,0,0,0);
|
||||
return true;
|
||||
}
|
||||
if (!parseRegionTable()) return false;
|
||||
if (m_opts.version == 1) {
|
||||
for (int i = 1; i <= m_opts.regTotal; ++i) {
|
||||
registerRegion(i, 0, 0, 0, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return parseIdentTable();
|
||||
}
|
||||
|
||||
bool RegionManager::parseRegionTable() {
|
||||
auto first = m_r.next("region card");
|
||||
if (!first) {
|
||||
std::fprintf(stderr, "getregion: EOF\n");
|
||||
return false;
|
||||
}
|
||||
if (ComGeomReader::fieldInt(first->s, 0, 5) != 1) {
|
||||
std::fprintf(stderr, "First region card not #1\n");
|
||||
return false;
|
||||
}
|
||||
std::string current = first->s;
|
||||
|
||||
while (true) {
|
||||
int reg_num = ComGeomReader::fieldInt(current, 0, 5);
|
||||
if (reg_num < 0) {
|
||||
return true;
|
||||
}
|
||||
if (reg_num > m_opts.regTotal) {
|
||||
std::fprintf(stderr, "%d regions > claimed %d\n", reg_num, m_opts.regTotal);
|
||||
return false;
|
||||
}
|
||||
wmember &head = m_regionHeads[static_cast<size_t>(reg_num)];
|
||||
if (!head.wm_name) {
|
||||
head.wm_name = bu_strdup(makeRegionName(reg_num).c_str());
|
||||
}
|
||||
|
||||
while (true) {
|
||||
size_t base = (m_opts.version == 1) ? 10 : 6;
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
size_t off = base + i * 7;
|
||||
if (off + 7 > current.size()) break;
|
||||
|
||||
char token[8] = {0};
|
||||
int k = 0;
|
||||
for (int j = 2; j < 7; ++j) {
|
||||
if (off + j >= current.size()) break;
|
||||
char c = current[off + j];
|
||||
if (!std::isspace(static_cast<unsigned char>(c))) {
|
||||
token[k++] = c;
|
||||
}
|
||||
}
|
||||
if (k == 0) continue;
|
||||
int inst_num = std::atoi(token);
|
||||
if (inst_num == 0) continue;
|
||||
|
||||
int op = WMOP_UNION;
|
||||
bool region_ref = false;
|
||||
|
||||
if (m_opts.version == 5) {
|
||||
char flag = (off + 1 < current.size()) ? current[off + 1] : ' ';
|
||||
if (flag == 'g' || flag == 'G') {
|
||||
region_ref = true;
|
||||
} else if (flag == 'R' || flag == 'r') {
|
||||
op = WMOP_UNION;
|
||||
} else {
|
||||
if (inst_num < 0) {
|
||||
op = WMOP_SUBTRACT;
|
||||
inst_num = -inst_num;
|
||||
} else {
|
||||
op = WMOP_INTERSECT;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
char flag = (off + 1 < current.size()) ? current[off + 1] : ' ';
|
||||
if (flag == ' ') {
|
||||
if (inst_num < 0) {
|
||||
op = WMOP_SUBTRACT;
|
||||
inst_num = -inst_num;
|
||||
} else {
|
||||
op = WMOP_INTERSECT;
|
||||
}
|
||||
} else {
|
||||
op = WMOP_UNION;
|
||||
}
|
||||
}
|
||||
|
||||
std::string suf = m_opts.suffix;
|
||||
if (m_opts.legacy_trunc && suf.size() > 13) suf.resize(13);
|
||||
|
||||
std::string memberName = region_ref
|
||||
? makeRegionName(inst_num)
|
||||
: (std::string("s") + std::to_string(inst_num) + suf);
|
||||
|
||||
if (!mk_addmember(memberName.c_str(), &head.l, nullptr, op)) {
|
||||
std::fprintf(stderr, "mk_addmember failed for region %d member '%s'\n",
|
||||
reg_num, memberName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
auto nextLine = m_r.next("region card");
|
||||
if (!nextLine) {
|
||||
std::fprintf(stderr, "getregion: EOF\n");
|
||||
return false;
|
||||
}
|
||||
std::string trimmed = ComGeomReader::rstrip(nextLine->s);
|
||||
ComGeomReader::trimLeadingSpacesInplace(trimmed);
|
||||
if (trimmed == "end") {
|
||||
m_opts.regTotal = reg_num;
|
||||
return true;
|
||||
}
|
||||
int possible = ComGeomReader::fieldInt(nextLine->s, 0, 5);
|
||||
if (possible != 0) {
|
||||
current = nextLine->s;
|
||||
break;
|
||||
}
|
||||
current = nextLine->s;
|
||||
}
|
||||
|
||||
if (m_opts.verbose) colPrint(head.wm_name);
|
||||
}
|
||||
}
|
||||
|
||||
bool RegionManager::parseIdentTable() {
|
||||
if (m_opts.version == 4) {
|
||||
(void)m_r.next("blank card");
|
||||
}
|
||||
if (m_opts.verbose) std::puts("\nRegion ident table");
|
||||
|
||||
while (true) {
|
||||
auto line = m_r.next("ident card");
|
||||
if (!line) return true;
|
||||
if (line->s.empty()) return true;
|
||||
|
||||
int reg_num, id, air, mat, los;
|
||||
if (m_opts.version == 5) {
|
||||
reg_num = ComGeomReader::fieldInt(line->s, 0, 5);
|
||||
id = ComGeomReader::fieldInt(line->s, 5, 5);
|
||||
air = ComGeomReader::fieldInt(line->s, 10, 5);
|
||||
mat = ComGeomReader::fieldInt(line->s, 15, 5);
|
||||
los = ComGeomReader::fieldInt(line->s, 20, 5);
|
||||
} else {
|
||||
reg_num = ComGeomReader::fieldInt(line->s, 0, 10);
|
||||
id = ComGeomReader::fieldInt(line->s, 10, 10);
|
||||
air = ComGeomReader::fieldInt(line->s, 20, 10);
|
||||
mat = ComGeomReader::fieldInt(line->s, 74, 3);
|
||||
los = ComGeomReader::fieldInt(line->s, 77, 3);
|
||||
}
|
||||
|
||||
if (reg_num <= 0) return true;
|
||||
registerRegion(reg_num, id, air, mat, los);
|
||||
}
|
||||
}
|
||||
|
||||
void RegionManager::registerRegion(int regNum, int id, int air, int mat, int los) {
|
||||
if (regNum <= 0 || regNum >= static_cast<int>(m_regionHeads.size())) return;
|
||||
wmember &head = m_regionHeads[static_cast<size_t>(regNum)];
|
||||
|
||||
if (BU_LIST_IS_EMPTY(&head.l)) {
|
||||
if (m_opts.verbose && head.wm_name) {
|
||||
colPrint(std::string("(") + head.wm_name + ")");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int ret = mk_comb(m_out, head.wm_name, &head.l, 1,
|
||||
"", "", nullptr,
|
||||
id, air, mat, los, 0, 0, 1);
|
||||
if (ret < 0) {
|
||||
std::fprintf(stderr, "mk_comb failed for region %d (%s)\n", regNum, head.wm_name);
|
||||
} else {
|
||||
groupAdd(id, head.wm_name);
|
||||
if (m_opts.verbose) colPrint(head.wm_name);
|
||||
}
|
||||
}
|
||||
|
||||
void RegionManager::groupInit() {
|
||||
struct Def { const char *n; int lo; int hi; } defs[] = {
|
||||
{"g00",0,0},{"g0",1,99},{"g1",100,199},{"g2",200,299},{"g3",300,399},
|
||||
{"g4",400,499},{"g5",500,599},{"g6",600,699},{"g7",700,799},{"g8",800,899},
|
||||
{"g9",900,999},{"g10",1000,1999},{"g11",2000,2999},{"g12",3000,3999},{"g13",4000,4999},
|
||||
{"g14",5000,5999},{"g15",6000,6999},{"g16",7000,7999},{"g17",8000,8999},{"g18",9000,9999},
|
||||
{"g19",10000,32767}
|
||||
};
|
||||
for (const auto &d : defs) {
|
||||
if (m_groupCount >= kMaxGroups) {
|
||||
std::fprintf(stderr, "Too many groups (limit %d)\n", kMaxGroups);
|
||||
break;
|
||||
}
|
||||
groupRegister(d.n, d.lo, d.hi);
|
||||
}
|
||||
}
|
||||
|
||||
void RegionManager::groupRegister(const char *name, int lo, int hi) {
|
||||
Group &g = m_groups[m_groupCount];
|
||||
WMEMBER_INIT(&g.head);
|
||||
std::string suf = m_opts.suffix;
|
||||
if (m_opts.legacy_trunc && suf.size() > 13) suf.resize(13);
|
||||
std::string full = std::string(name) + suf;
|
||||
g.head.wm_name = bu_strdup(full.c_str());
|
||||
g.lo = lo;
|
||||
g.hi = hi;
|
||||
g.used = false;
|
||||
++m_groupCount;
|
||||
}
|
||||
|
||||
void RegionManager::groupAdd(int identVal, const char *regionName) {
|
||||
for (int i = m_groupCount - 1; i >= 0; --i) {
|
||||
if (identVal < m_groups[i].lo || identVal > m_groups[i].hi) continue;
|
||||
if (!mk_addmember(regionName, &m_groups[i].head.l, nullptr, WMOP_UNION)) {
|
||||
std::fprintf(stderr, "mk_addmember failed adding region '%s' to group '%s'\n",
|
||||
regionName, m_groups[i].head.wm_name);
|
||||
} else {
|
||||
m_groups[i].used = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
std::fprintf(stderr, "Unable to find group for ident %d (region %s)\n", identVal, regionName);
|
||||
}
|
||||
|
||||
bool RegionManager::writeGroups() {
|
||||
wmember allHead;
|
||||
WMEMBER_INIT(&allHead);
|
||||
allHead.wm_name = const_cast<char *>("all.g");
|
||||
|
||||
for (int i = 0; i < m_groupCount; ++i) {
|
||||
Group &g = m_groups[i];
|
||||
if (BU_LIST_IS_EMPTY(&g.head.l)) continue;
|
||||
|
||||
int ret = mk_comb(m_out, g.head.wm_name, &g.head.l, 0,
|
||||
nullptr, nullptr, nullptr,
|
||||
0, 0, 0, 0, 0, 0, 0);
|
||||
if (ret < 0) {
|
||||
std::fprintf(stderr, "mk_comb (group) failed for '%s'\n", g.head.wm_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mk_addmember(g.head.wm_name, &allHead.l, nullptr, WMOP_UNION)) {
|
||||
std::fprintf(stderr, "mk_addmember failed adding '%s' to all.g\n", g.head.wm_name);
|
||||
}
|
||||
if (m_opts.verbose) colPrint(g.head.wm_name);
|
||||
}
|
||||
|
||||
int retAll = mk_comb(m_out, "all.g", &allHead.l, 0,
|
||||
nullptr, nullptr, nullptr,
|
||||
0, 0, 0, 0, 0, 0, 0);
|
||||
if (retAll < 0) {
|
||||
std::fprintf(stderr, "mk_comb failed for all.g\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
#include "vmath.h"
|
||||
#include "raytrace.h"
|
||||
#include "wdb.h"
|
||||
}
|
||||
|
||||
#include "ComGeomReader.h"
|
||||
|
||||
struct RegionManagerOptions {
|
||||
int version = 5;
|
||||
long verbose = 0;
|
||||
std::string suffix;
|
||||
int regTotal = 0;
|
||||
bool legacy_trunc = false; // optional legacy suffix truncation (max 13 chars)
|
||||
};
|
||||
|
||||
/*
|
||||
* Modernized RegionManager:
|
||||
* - Uses WMEMBER_INIT for region and group heads (matches wdb.h)
|
||||
* - Avoids container reallocation for group heads (std::array)
|
||||
* - Relies on mk_addmember for node allocation and mk_comb/mk_lfcomb
|
||||
* to free membership lists (per wdb.h contract)
|
||||
* - No manual traversal freeing of member nodes (use mk_freemembers only
|
||||
* for temporary aggregation lists we explicitly build, e.g. "all.g" head)
|
||||
* - Stable addresses: region heads stored in std::vector sized once,
|
||||
* group heads in fixed array
|
||||
*/
|
||||
class RegionManager {
|
||||
public:
|
||||
RegionManager(ComGeomReader &reader, rt_wdb *outfp, const RegionManagerOptions &opts);
|
||||
~RegionManager();
|
||||
|
||||
bool parseRegions(); // read region membership cards
|
||||
bool writeGroups(); // emit group combinations + all.g
|
||||
|
||||
private:
|
||||
std::string makeRegionName(int n) const;
|
||||
void colPrint(const std::string &s);
|
||||
|
||||
bool parseRegionTable();
|
||||
bool parseRegionTableV0();
|
||||
bool parseIdentTable();
|
||||
void registerRegion(int regNum, int id, int air, int mat, int los);
|
||||
|
||||
void groupInit();
|
||||
void groupRegister(const char *name, int lo, int hi);
|
||||
void groupAdd(int identVal, const char *regionName);
|
||||
|
||||
private:
|
||||
ComGeomReader &m_r;
|
||||
rt_wdb *m_out = nullptr;
|
||||
RegionManagerOptions m_opts;
|
||||
|
||||
std::vector<wmember> m_regionHeads; // index by region number (1..regTotal)
|
||||
size_t m_colCur = 0;
|
||||
|
||||
struct Group {
|
||||
wmember head; // combination head (membership list)
|
||||
int lo = 0;
|
||||
int hi = 0;
|
||||
bool used = false; // whether at least one region fell into range
|
||||
};
|
||||
|
||||
static constexpr int kMaxGroups = 21;
|
||||
std::array<Group, kMaxGroups> m_groups;
|
||||
int m_groupCount = 0;
|
||||
};
|
||||
@@ -1,830 +0,0 @@
|
||||
#include "SolidParser.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
/* Helper: build min/max from 6 RPP values */
|
||||
static inline void vset_minmax(fastf_t *min, fastf_t *max, const fastf_t *dd) {
|
||||
VSET(min, dd[0], dd[2], dd[4]);
|
||||
VSET(max, dd[1], dd[3], dd[5]);
|
||||
}
|
||||
|
||||
SolidParser::SolidParser(ComGeomReader &reader, rt_wdb *outfp, const SolidParserOptions &opts)
|
||||
: m_r(reader), m_out(outfp), m_opts(opts)
|
||||
{}
|
||||
|
||||
std::string SolidParser::makeSolidName(int n) const {
|
||||
std::string suf = m_opts.suffix;
|
||||
if (m_opts.legacy_trunc && suf.size() > 13) suf.resize(13);
|
||||
char buf[64] = {0};
|
||||
std::snprintf(buf, sizeof(buf), "s%d%s", n, suf.c_str());
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
void SolidParser::colPrint(const std::string &s) {
|
||||
if (!m_opts.verbose) return;
|
||||
std::fputs(s.c_str(), stdout);
|
||||
m_colCur += s.size();
|
||||
while (m_colCur < 78 && (m_colCur % 10)) {
|
||||
putchar(' ');
|
||||
++m_colCur;
|
||||
}
|
||||
if (m_colCur >= 78) {
|
||||
std::fputc('\n', stdout);
|
||||
m_colCur = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool SolidParser::parseSolidHeader(const std::string &line, int &solidNum,
|
||||
std::string &typeLower, std::string &lineOutForData) {
|
||||
std::string_view sv(line);
|
||||
std::string solidType;
|
||||
switch (m_opts.version) {
|
||||
case 5:
|
||||
solidNum = ComGeomReader::fieldInt(sv, 0, 5);
|
||||
solidType = ComGeomReader::rstrip(sv.substr(5, 5));
|
||||
break;
|
||||
case 4:
|
||||
solidNum = ComGeomReader::fieldInt(sv, 0, 3);
|
||||
solidType = ComGeomReader::rstrip(sv.substr(3, 4));
|
||||
break;
|
||||
case 1:
|
||||
solidNum = ComGeomReader::fieldInt(sv, 5, 4);
|
||||
solidType = ComGeomReader::rstrip(sv.substr(2, 3));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
ComGeomReader::toLowerInplace(solidType);
|
||||
typeLower = solidType;
|
||||
lineOutForData = line;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SolidParser::getSolData(std::vector<fastf_t> &data, int num, int solidNum,
|
||||
bool /*firstCardAlreadyRead*/, std::string lineBuf) {
|
||||
data.clear();
|
||||
data.reserve(num);
|
||||
int cd = 1;
|
||||
std::string line = std::move(lineBuf);
|
||||
while (num > 0) {
|
||||
std::string_view sv(line);
|
||||
if (cd != 1) {
|
||||
if ((m_opts.version == 5 && sv.size() > 5 && sv[5] != ' ') ||
|
||||
(m_opts.version == 4 && sv.size() > 3 && sv[3] != ' ')) {
|
||||
std::fprintf(stderr, "solid %d (continuation) card %d non-blank\n", solidNum, cd);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
int j = std::min(num, 6);
|
||||
for (int i = 0; i < j; ++i) {
|
||||
data.push_back(static_cast<fastf_t>(ComGeomReader::fieldDouble(sv, 10 + i*10, 10)));
|
||||
}
|
||||
num -= j;
|
||||
if (num <= 0) break;
|
||||
auto l = m_r.next("solid continuation card");
|
||||
if (!l) {
|
||||
std::fprintf(stderr, "too few cards for solid %d\n", solidNum);
|
||||
return false;
|
||||
}
|
||||
line = l->s;
|
||||
++cd;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SolidParser::getXSolData(std::vector<fastf_t> &data, int num, int solidNum) {
|
||||
data.clear();
|
||||
data.reserve(num);
|
||||
int cd = 1;
|
||||
while (num > 0) {
|
||||
auto l = m_r.next("x solid card");
|
||||
if (!l) {
|
||||
std::fprintf(stderr, "too few cards for solid %d\n", solidNum);
|
||||
return false;
|
||||
}
|
||||
std::string_view sv(l->s);
|
||||
if (cd != 1) {
|
||||
if ((m_opts.version == 5 && sv.size() > 5 && sv[5] != ' ') ||
|
||||
(m_opts.version == 4 && sv.size() > 3 && sv[3] != ' ')) {
|
||||
std::fprintf(stderr, "solid %d (continuation) card %d non-blank\n", solidNum, cd);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
int j = std::min(num, 6);
|
||||
for (int i = 0; i < j; ++i) {
|
||||
data.push_back(static_cast<fastf_t>(ComGeomReader::fieldDouble(sv, 10 + i*10, 10)));
|
||||
}
|
||||
num -= j;
|
||||
++cd;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SolidParser::isIntegerField(std::string_view sv, size_t start, size_t len, int &value) const {
|
||||
if (start >= sv.size()) { value = 0; return false; }
|
||||
size_t end = std::min(sv.size(), start + len);
|
||||
size_t b = start;
|
||||
while (b < end && std::isspace(static_cast<unsigned char>(sv[b]))) b++;
|
||||
if (b >= end) { value = 0; return false; }
|
||||
bool hasDigit=false; bool hasDot=false;
|
||||
for (size_t i=b;i<end;++i) {
|
||||
char c = sv[i];
|
||||
if (std::isspace(static_cast<unsigned char>(c))) break;
|
||||
if (c == '.') hasDot = true;
|
||||
if (std::isdigit(static_cast<unsigned char>(c))) hasDigit = true;
|
||||
else if (c=='-' || c=='+') continue;
|
||||
else { value=0; return false; }
|
||||
}
|
||||
if (!hasDigit || hasDot) { value=0; return false; }
|
||||
std::string token(sv.substr(b, end-b));
|
||||
value = std::atoi(token.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SolidParser::isLikelyFaceCard(const std::string &line) const {
|
||||
// Heuristic: first four 10-char fields each parse as small integers (1..8) & no decimal points.
|
||||
int vals=0;
|
||||
for (int f=0; f<4; ++f) {
|
||||
int v=0;
|
||||
if (!isIntegerField(line, 10 + f*10, 10, v)) return false;
|
||||
if (v < -8 || v > 8 || v==0) return false;
|
||||
vals++;
|
||||
}
|
||||
return (vals==4);
|
||||
}
|
||||
|
||||
/* ---------------- Primitive Handlers ---------------- */
|
||||
|
||||
SolidParseResult SolidParser::handle_ars(const std::string &name, const std::string &firstLine) {
|
||||
std::string_view sv(firstLine);
|
||||
int ncurves = ComGeomReader::fieldInt(sv, 10, 10);
|
||||
int pts_per_curve = ComGeomReader::fieldInt(sv, 20, 10);
|
||||
if (ncurves <= 0 || pts_per_curve <= 0)
|
||||
return {SolidParseResult::Error, "ars invalid counts"};
|
||||
|
||||
std::vector<std::vector<fastf_t>> curves(ncurves);
|
||||
for (int i = 0; i < ncurves; ++i) {
|
||||
curves[i].resize(static_cast<size_t>(pts_per_curve) * 3);
|
||||
if (!getXSolData(curves[i], pts_per_curve * 3, m_solWork))
|
||||
return {SolidParseResult::Error, "ars curve data read failure"};
|
||||
}
|
||||
std::vector<fastf_t*> ptrs(ncurves);
|
||||
for (int i = 0; i < ncurves; ++i) ptrs[i] = curves[i].data();
|
||||
|
||||
int ret = mk_ars(m_out, name.c_str(), ncurves, pts_per_curve, ptrs.data());
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_ars failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_rpp(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 6) return {SolidParseResult::Error, "rpp needs 6 values"};
|
||||
fastf_t min[3], max[3];
|
||||
vset_minmax(min, max, dd.data());
|
||||
int ret = mk_rpp(m_out, name.c_str(), min, max);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_rpp failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_box(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 12) return {SolidParseResult::Error, "box needs 12 values"};
|
||||
point_t v[8];
|
||||
const fastf_t *D0 = &dd[0], *D1=&dd[3], *D2=&dd[6], *D3=&dd[9];
|
||||
VMOVE(v[0], D0);
|
||||
VADD2(v[1], D0, D2);
|
||||
VADD3(v[2], D0, D2, D1);
|
||||
VADD2(v[3], D0, D1);
|
||||
VADD2(v[4], D0, D3);
|
||||
VADD3(v[5], D0, D3, D2);
|
||||
VADD4(v[6], D0, D3, D2, D1);
|
||||
VADD3(v[7], D0, D3, D1);
|
||||
int ret = mk_arb8(m_out, name.c_str(), &v[0][0]);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(box) failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_raw_wed(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 12) return {SolidParseResult::Error, "raw/wed needs 12 values"};
|
||||
point_t v[8];
|
||||
const fastf_t *D0=&dd[0], *D1=&dd[3], *D2=&dd[6], *D3=&dd[9];
|
||||
VMOVE(v[0], D0);
|
||||
VADD2(v[1], D0, D2);
|
||||
VMOVE(v[2], v[1]);
|
||||
VADD2(v[3], D0, D1);
|
||||
VADD2(v[4], D0, D3);
|
||||
VADD3(v[5], D0, D3, D2);
|
||||
VMOVE(v[6], v[5]);
|
||||
VADD3(v[7], D0, D3, D1);
|
||||
int ret = mk_arb8(m_out, name.c_str(), &v[0][0]);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(raw/wed) failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_rvw(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 7) return {SolidParseResult::Error, "rvw needs 7 values"};
|
||||
vect_t V; VMOVE(V, &dd[0]);
|
||||
double a2=dd[3], theta=dd[4], phi=dd[5], h2=dd[6];
|
||||
double angle1 = (phi + theta - 90.0) * DEG2RAD;
|
||||
double angle2 = (phi + theta) * DEG2RAD;
|
||||
double a2theta = a2 * std::tan(theta * DEG2RAD);
|
||||
vect_t a,b,c;
|
||||
VSET(a, a2theta * std::cos(angle1), a2theta * std::sin(angle1), 0.0);
|
||||
VSET(b, -a2 * std::cos(angle2), -a2 * std::sin(angle2), 0.0);
|
||||
VSET(c, 0.0, 0.0, h2);
|
||||
point_t vert[8];
|
||||
VSUB2(vert[0], V, b);
|
||||
VMOVE(vert[1], V);
|
||||
VMOVE(vert[2], vert[1]);
|
||||
VADD2(vert[3], vert[0], a);
|
||||
VADD2(vert[4], vert[0], c);
|
||||
VADD2(vert[5], vert[1], c);
|
||||
VMOVE(vert[6], vert[5]);
|
||||
VADD2(vert[7], vert[3], c);
|
||||
int ret = mk_arb8(m_out, name.c_str(), &vert[0][0]);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(rvw) failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_arw(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 12) return {SolidParseResult::Error, "arw needs 12 values"};
|
||||
point_t v[8];
|
||||
const fastf_t *D0=&dd[0], *D1=&dd[3], *D2=&dd[6], *D3=&dd[9];
|
||||
VMOVE(v[0], D0);
|
||||
VADD2(v[1], D0, D2);
|
||||
VADD3(v[2], D0, D2, D3);
|
||||
VADD2(v[3], D0, D3);
|
||||
VADD2(v[4], D0, D1);
|
||||
VMOVE(v[5], v[4]);
|
||||
VADD3(v[6], D0, D1, D3);
|
||||
VMOVE(v[7], v[6]);
|
||||
int ret = mk_arb8(m_out, name.c_str(), &v[0][0]);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(arw) failed" : ""};
|
||||
}
|
||||
|
||||
/* ---------- MAGIC v0 ARB generic handler ---------- */
|
||||
SolidParseResult SolidParser::handle_arb_magic(const std::string &name, const std::string &firstLine) {
|
||||
// Multi-card ARB parsing:
|
||||
// Collect vertex triples until a face card is detected (integer ordinals 1..8).
|
||||
std::vector<fastf_t> vertexDoubles;
|
||||
std::string line = firstLine;
|
||||
int maxVertexTriples = 8;
|
||||
|
||||
// Include data from first line if it contains numeric fields (after header)
|
||||
auto consumeVertexCard = [&](const std::string &card) {
|
||||
std::string_view sv(card);
|
||||
for (int i=0;i<6;++i) { // up to 6 fields (i.e., 2 triples per card typical)
|
||||
double val = ComGeomReader::fieldDouble(sv, 10 + i*10, 10);
|
||||
// Heuristic: consider any numeric (including 0) as part of vertex stream until a face card appears
|
||||
vertexDoubles.push_back(static_cast<fastf_t>(val));
|
||||
if ((int)vertexDoubles.size()/3 >= maxVertexTriples) break;
|
||||
}
|
||||
};
|
||||
|
||||
// First card may have header only; attempt to parse numeric fields
|
||||
consumeVertexCard(line);
|
||||
|
||||
// Read continuation cards until face card or limit reached
|
||||
while ((int)vertexDoubles.size()/3 < maxVertexTriples) {
|
||||
auto next = m_r.next("arb card");
|
||||
if (!next) break;
|
||||
if (isLikelyFaceCard(next->s)) {
|
||||
line = next->s;
|
||||
break;
|
||||
}
|
||||
consumeVertexCard(next->s);
|
||||
}
|
||||
|
||||
int vertexCount = static_cast<int>(vertexDoubles.size()/3);
|
||||
if (vertexCount < 4) return {SolidParseResult::Error, "arb v0 insufficient vertices"};
|
||||
|
||||
// Build point array
|
||||
std::vector<fastf_t> verts(vertexCount*3);
|
||||
std::copy(vertexDoubles.begin(), vertexDoubles.begin()+vertexCount*3, verts.begin());
|
||||
|
||||
// Read face cards (up to 6 faces)
|
||||
std::vector<std::vector<int>> faces;
|
||||
while (true) {
|
||||
if (!isLikelyFaceCard(line)) {
|
||||
auto peek = m_r.next("arb maybe face");
|
||||
if (!peek) break;
|
||||
line = peek->s;
|
||||
if (!isLikelyFaceCard(line)) break;
|
||||
}
|
||||
std::string_view sv(line);
|
||||
std::vector<int> f;
|
||||
for (int i=0;i<4;++i) {
|
||||
int v=0;
|
||||
if (isIntegerField(sv, 10 + i*10, 10, v) && v != 0) {
|
||||
f.push_back(v);
|
||||
}
|
||||
}
|
||||
if (!f.empty()) faces.push_back(f);
|
||||
auto next = m_r.next("arb face next");
|
||||
if (!next) break;
|
||||
if (!isLikelyFaceCard(next->s)) {
|
||||
line = next->s; // pass to main dispatcher for next solid
|
||||
break;
|
||||
}
|
||||
line = next->s;
|
||||
if ((int)faces.size() >= 6) break;
|
||||
}
|
||||
|
||||
// Validate face indices
|
||||
for (const auto &f : faces) {
|
||||
for (int idx : f) {
|
||||
int absIdx = std::abs(idx);
|
||||
if (absIdx < 1 || absIdx > vertexCount)
|
||||
return {SolidParseResult::Error, "arb face index out of range"};
|
||||
}
|
||||
}
|
||||
|
||||
// Convert based on vertex count
|
||||
SolidParseResult result;
|
||||
if (vertexCount == 4) {
|
||||
result = handle_arb4(name, verts);
|
||||
} else if (vertexCount == 5) {
|
||||
result = handle_arb5(name, verts);
|
||||
} else if (vertexCount == 6) {
|
||||
result = handle_arb6(name, verts);
|
||||
} else if (vertexCount == 7) {
|
||||
result = handle_arb7(name, verts);
|
||||
} else if (vertexCount == 8) {
|
||||
result = handle_arb8(name, verts);
|
||||
} else {
|
||||
return {SolidParseResult::Error, "arb v0 unsupported vertex count"};
|
||||
}
|
||||
|
||||
if (result.status != SolidParseResult::Ok) {
|
||||
// Fallback to ARBN if vertex-based conversion fails
|
||||
std::fprintf(stderr, "arb v0 vertex conversion failed, attempting arbn fallback\n");
|
||||
// Build plane equations from faces using indices (if enough faces)
|
||||
if (faces.size() >= 4) {
|
||||
// Simple plane extraction: pick triple for each face (first three indices)
|
||||
std::vector<plane_t> eqn(faces.size());
|
||||
for (size_t i=0;i<faces.size();++i) {
|
||||
int a = std::abs(faces[i][0]) - 1;
|
||||
int b = std::abs(faces[i][1]) - 1;
|
||||
int c = std::abs(faces[i][2]) - 1;
|
||||
if (a<0||b<0||c<0 || a>=vertexCount||b>=vertexCount||c>=vertexCount) continue;
|
||||
plane_t pl;
|
||||
point_t pa, pb, pc;
|
||||
VMOVE(pa, &verts[a*3]);
|
||||
VMOVE(pb, &verts[b*3]);
|
||||
VMOVE(pc, &verts[c*3]);
|
||||
struct bn_tol tol;
|
||||
tol.magic = BN_TOL_MAGIC;
|
||||
tol.dist = 0.0005;
|
||||
tol.dist_sq = tol.dist * tol.dist;
|
||||
tol.perp = 1e-6;
|
||||
tol.para = 1 - tol.perp;
|
||||
if (bg_make_plane_3pnts(pl, pa, pb, pc, &tol) == 0) {
|
||||
VMOVE(eqn[i], pl);
|
||||
}
|
||||
}
|
||||
int ret = mk_arbn(m_out, name.c_str(), (int)faces.size(), eqn.data());
|
||||
if (ret < 0) return {SolidParseResult::Error, "arbn fallback failed"};
|
||||
return {SolidParseResult::Ok, ""};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_arb4(const std::string &name, const std::vector<fastf_t> &dd4) {
|
||||
if (dd4.size() < 12) return {SolidParseResult::Error, "arb4 needs 12 values"};
|
||||
int ret = mk_arb4(m_out, name.c_str(), dd4.data());
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb4 failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_arb5(const std::string &name, const std::vector<fastf_t> &dd5) {
|
||||
if (dd5.size() < 15) return {SolidParseResult::Error, "arb5 needs 15 values"};
|
||||
std::vector<fastf_t> dd_full(24);
|
||||
std::copy(dd5.begin(), dd5.begin()+15, dd_full.begin());
|
||||
const fastf_t *v4 = &dd5[12];
|
||||
for (int rep=5; rep<=7; ++rep) std::copy(v4, v4+3, dd_full.begin()+rep*3);
|
||||
int ret = mk_arb8(m_out, name.c_str(), dd_full.data());
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(arb5) failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_arb6(const std::string &name, const std::vector<fastf_t> &dd6) {
|
||||
if (dd6.size() < 18) return {SolidParseResult::Error, "arb6 needs 18 values"};
|
||||
std::vector<fastf_t> dd_full(24);
|
||||
std::copy(dd6.begin(), dd6.begin()+18, dd_full.begin());
|
||||
const fastf_t *D4=&dd6[12];
|
||||
const fastf_t *D5=&dd6[15];
|
||||
std::copy(D4, D4+3, dd_full.begin()+5*3); // D(5)=D(4)
|
||||
std::copy(D5, D5+3, dd_full.begin()+6*3); // D(6)=original D(5)
|
||||
std::copy(D5, D5+3, dd_full.begin()+7*3); // D(7)=original D(5)
|
||||
int ret = mk_arb8(m_out, name.c_str(), dd_full.data());
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(arb6) failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_arb7(const std::string &name, const std::vector<fastf_t> &dd7) {
|
||||
if (dd7.size() < 21) return {SolidParseResult::Error, "arb7 needs 21 values"};
|
||||
std::vector<fastf_t> dd_full(24);
|
||||
std::copy(dd7.begin(), dd7.begin()+21, dd_full.begin());
|
||||
const fastf_t *D4=&dd7[12];
|
||||
std::copy(D4, D4+3, dd_full.begin()+7*3); // D(7)=D(4)
|
||||
int ret = mk_arb8(m_out, name.c_str(), dd_full.data());
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(arb7) failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_arb8(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 24) return {SolidParseResult::Error, "arb8 needs 24 values"};
|
||||
int ret = mk_arb8(m_out, name.c_str(), dd.data());
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8 failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_rcc(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 7) return {SolidParseResult::Error, "rcc needs 7 values"};
|
||||
int ret = mk_rcc(m_out, name.c_str(), dd.data(), dd.data()+3, dd[6]);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_rcc failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_rec(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 12) return {SolidParseResult::Error, "rec needs 12 values"};
|
||||
int ret = mk_tgc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd.data()+9, dd.data()+6, dd.data()+9);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_tgc(rec) failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_trc(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 8) return {SolidParseResult::Error, "trc needs 8 values"};
|
||||
int ret = mk_trc_h(m_out, name.c_str(), dd.data(), dd.data()+3, dd[6], dd[7]);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_trc_h failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_tec(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 13) return {SolidParseResult::Error, "tec needs 13 values"};
|
||||
double p = dd[12];
|
||||
if (NEAR_ZERO(p, SMALL)) return {SolidParseResult::Error, "tec p cannot be zero"};
|
||||
vect_t topA, topB;
|
||||
double invp = 1.0/p;
|
||||
VSCALE(topA, dd.data()+6, invp);
|
||||
VSCALE(topB, dd.data()+9, invp);
|
||||
int ret = mk_tgc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd.data()+9, topA, topB);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_tgc(tec) failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_tgc(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 14) return {SolidParseResult::Error, "tgc needs 14 values"};
|
||||
vect_t topA, topB;
|
||||
double magBaseA = MAGNITUDE(dd.data()+6);
|
||||
double magBaseB = MAGNITUDE(dd.data()+9);
|
||||
if (magBaseA < SMALL || magBaseB < SMALL) return {SolidParseResult::Error,"tgc zero-length A or B"};
|
||||
VSCALE(topA, dd.data()+6, dd[12]/magBaseA);
|
||||
VSCALE(topB, dd.data()+9, dd[13]/magBaseB);
|
||||
int ret = mk_tgc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd.data()+9, topA, topB);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_tgc failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_sph(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 4) return {SolidParseResult::Error, "sph needs 4 values"};
|
||||
int ret = mk_sph(m_out, name.c_str(), dd.data(), dd[3]);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_sph failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_wir(const std::string &name, const std::string &firstLine) {
|
||||
int numpts = ComGeomReader::fieldInt(firstLine, 8, 2);
|
||||
if (numpts <= 0) return {SolidParseResult::Error, "wir invalid point count"};
|
||||
int total = numpts*3 + 1;
|
||||
std::vector<fastf_t> raw;
|
||||
if (!getSolData(raw, total, m_solWork, true, firstLine))
|
||||
return {SolidParseResult::Error, "wir data read failure"};
|
||||
if ((int)raw.size() < total) return {SolidParseResult::Error, "wir insufficient data"};
|
||||
double radius = raw.back();
|
||||
if (radius <= 0.0) return {SolidParseResult::Error, "wir invalid radius"};
|
||||
double diameter = radius * 2.0;
|
||||
struct bu_list head;
|
||||
BU_LIST_INIT(&head);
|
||||
for (int i=0;i<numpts;++i) {
|
||||
struct wdb_pipe_pnt *ps;
|
||||
BU_ALLOC(ps, struct wdb_pipe_pnt);
|
||||
ps->l.magic = WDB_PIPESEG_MAGIC;
|
||||
VMOVE(ps->pp_coord, &raw[i*3]);
|
||||
ps->pp_id = 0;
|
||||
ps->pp_od = diameter;
|
||||
ps->pp_bendradius = diameter;
|
||||
BU_LIST_INSERT(&head, &ps->l);
|
||||
}
|
||||
int ret = mk_pipe(m_out, name.c_str(), &head);
|
||||
mk_pipe_free(&head);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_pipe failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_rpc(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 10) return {SolidParseResult::Error, "rpc needs 10 values"};
|
||||
int ret = mk_rpc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9]);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_rpc failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_rhc(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 11) return {SolidParseResult::Error, "rhc needs 11 values"};
|
||||
int ret = mk_rhc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9], dd[10]);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_rhc failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_epa(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 11) return {SolidParseResult::Error, "epa needs 11 values"};
|
||||
int ret = mk_epa(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9], dd[10]);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_epa failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_ehy(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 12) return {SolidParseResult::Error, "ehy needs 12 values"};
|
||||
int ret = mk_ehy(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9], dd[10], dd[11]);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_ehy failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_eto(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 11) return {SolidParseResult::Error, "eto needs 11 values"};
|
||||
int ret = mk_eto(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9], dd[10]);
|
||||
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_eto failed" : ""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_v4_ell_transform(const std::string &name, const std::vector<fastf_t> &foci) {
|
||||
if (foci.size() < 7) return {SolidParseResult::Error, "v4 ell needs 7 values"};
|
||||
vect_t F1,F2; VMOVE(F1,&foci[0]); VMOVE(F2,&foci[3]);
|
||||
double L = foci[6];
|
||||
vect_t V; VADD2SCALE(V,F1,F2,0.5);
|
||||
vect_t work; VSUB2(work,F2,F1);
|
||||
double m1 = MAGNITUDE(work);
|
||||
if (m1 < SMALL) return {SolidParseResult::Error,"degenerate ell"};
|
||||
double a_scale = (L*0.5)/m1;
|
||||
vect_t baseA; VSCALE(baseA, work, a_scale);
|
||||
double r = std::sqrt(MAGSQ(baseA) - (m1*0.5)*(m1*0.5));
|
||||
if (!(r>0.0)) return {SolidParseResult::Error,"invalid ell radius"};
|
||||
vect_t Vp{V[0]+M_PI, V[1]+M_PI, V[2]+M_PI};
|
||||
vect_t B; VCROSS(B,Vp,baseA); double mb=MAGNITUDE(B); if (mb<SMALL) return {SolidParseResult::Error,"degenerate B"};
|
||||
VSCALE(B,B,r/mb);
|
||||
vect_t C; VCROSS(C,baseA,B); double mc=MAGNITUDE(C); if (mc<SMALL) return {SolidParseResult::Error,"degenerate C"};
|
||||
VSCALE(C,C,r/mc);
|
||||
int ret = mk_ell(m_out, name.c_str(), V, baseA, B, C);
|
||||
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_ell failed":""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_ell(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 7) return {SolidParseResult::Error, "ell needs 7 values"};
|
||||
vect_t V, A; VMOVE(V,&dd[0]); VMOVE(A,&dd[3]);
|
||||
double r = dd[6]; if (r <= 0.0) return {SolidParseResult::Error,"invalid ell radius"};
|
||||
vect_t Vp{V[0]+M_PI,V[1]+M_PI,V[2]+M_PI};
|
||||
vect_t B; VCROSS(B,Vp,A); double mb=MAGNITUDE(B); if (mb<SMALL) return {SolidParseResult::Error,"degenerate B"};
|
||||
VSCALE(B,B,r/mb);
|
||||
vect_t C; VCROSS(C,A,B); double mc=MAGNITUDE(C); if (mc<SMALL) return {SolidParseResult::Error,"degenerate C"};
|
||||
VSCALE(C,C,r/mc);
|
||||
int ret = mk_ell(m_out, name.c_str(), V, A, B, C);
|
||||
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_ell failed":""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_ellg(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 12) return {SolidParseResult::Error, "ellg needs 12 values"};
|
||||
int ret = mk_ell(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd.data()+9);
|
||||
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_ell failed":""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_tor(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 8) return {SolidParseResult::Error, "tor needs 8 values"};
|
||||
int ret = mk_tor(m_out, name.c_str(), dd.data(), dd.data()+3, dd[6], dd[7]);
|
||||
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_tor failed":""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_haf(const std::string &name, const std::vector<fastf_t> &dd) {
|
||||
if (dd.size() < 4) return {SolidParseResult::Error, "haf needs 4 values"};
|
||||
vect_t N; VMOVE(N,&dd[0]);
|
||||
double magN = MAGNITUDE(N);
|
||||
if (magN < SMALL) return {SolidParseResult::Error,"haf normal too small"};
|
||||
int ret = mk_half(m_out, name.c_str(), N, -dd[3]);
|
||||
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_half failed":""};
|
||||
}
|
||||
|
||||
SolidParseResult SolidParser::handle_arbn(const std::string &name, const std::string &firstLine) {
|
||||
std::string_view sv(firstLine);
|
||||
int npt = ComGeomReader::fieldInt(sv, 10 + 0*10, 10);
|
||||
int npe = ComGeomReader::fieldInt(sv, 10 + 1*10, 10);
|
||||
int neq = ComGeomReader::fieldInt(sv, 10 + 2*10, 10);
|
||||
int nae = ComGeomReader::fieldInt(sv, 10 + 3*10, 10);
|
||||
int nface = npe + neq + nae;
|
||||
if (npt < 1) return {SolidParseResult::Error, "arbn requires >=1 point"};
|
||||
if (nface < 1) return {SolidParseResult::Error, "arbn requires >=1 face"};
|
||||
|
||||
std::vector<plane_t> eqn(static_cast<size_t>(nface));
|
||||
std::vector<int> used(static_cast<size_t>(nface), 0);
|
||||
std::vector<fastf_t> input_points;
|
||||
input_points.reserve(npt*3);
|
||||
|
||||
struct bn_tol tol;
|
||||
tol.magic = BN_TOL_MAGIC;
|
||||
tol.dist = 0.0005;
|
||||
tol.dist_sq = tol.dist * tol.dist;
|
||||
tol.perp = 1e-6;
|
||||
tol.para = 1 - tol.perp;
|
||||
|
||||
{ // vertex points
|
||||
std::vector<fastf_t> pts;
|
||||
if (!getXSolData(pts, npt*3, m_solWork))
|
||||
return {SolidParseResult::Error, "arbn vertex read failure"};
|
||||
input_points = std::move(pts);
|
||||
}
|
||||
|
||||
int cur_eq = 0;
|
||||
int symm = 0;
|
||||
|
||||
/* Planes from triples */
|
||||
for (int i=0;i<npe;i+=6) {
|
||||
auto l = m_r.next("arbn vertex point indices");
|
||||
if (!l) return {SolidParseResult::Error,"arbn EOF (point indices)"};
|
||||
std::string_view lv(l->s);
|
||||
for (int j=0;j<6 && (i+j)<npe;++j) {
|
||||
int q = ComGeomReader::fieldInt(lv, 10 + j*10 + 0, 4);
|
||||
int r = ComGeomReader::fieldInt(lv, 10 + j*10 + 4, 3);
|
||||
int s = ComGeomReader::fieldInt(lv, 10 + j*10 + 7, 3);
|
||||
if (q==0 || r==0 || s==0) continue;
|
||||
point_t a,b,c;
|
||||
auto fetch = [&](int idx, point_t &out){
|
||||
int abs_idx = std::abs(idx)-1;
|
||||
VMOVE(out, &input_points[abs_idx*3]);
|
||||
if (idx < 0) { out[Y] = -out[Y]; symm = 1; }
|
||||
};
|
||||
fetch(q,a); fetch(r,b); fetch(s,c);
|
||||
if (bg_make_plane_3pnts(eqn[cur_eq], a, b, c, &tol) < 0) {
|
||||
if (m_opts.verbose) std::fprintf(stderr,"arbn degenerate 3p plane ignored\n");
|
||||
continue;
|
||||
}
|
||||
++cur_eq;
|
||||
}
|
||||
}
|
||||
|
||||
/* Planes from equations */
|
||||
for (int i=0;i<neq;++i) {
|
||||
auto l = m_r.next("arbn plane equation card");
|
||||
if (!l) return {SolidParseResult::Error,"arbn EOF (equation)"};
|
||||
std::string_view lv(l->s);
|
||||
eqn[cur_eq][X] = ComGeomReader::fieldDouble(lv, 10, 10);
|
||||
eqn[cur_eq][Y] = ComGeomReader::fieldDouble(lv, 20, 10);
|
||||
eqn[cur_eq][Z] = ComGeomReader::fieldDouble(lv, 30, 10);
|
||||
eqn[cur_eq][W] = ComGeomReader::fieldDouble(lv, 40, 10);
|
||||
double scale = MAGNITUDE(eqn[cur_eq]);
|
||||
if (scale < SMALL) {
|
||||
if (m_opts.verbose) std::fprintf(stderr,"arbn plane normal too small (eqn)\n");
|
||||
continue;
|
||||
}
|
||||
scale = 1.0/scale;
|
||||
VSCALE(eqn[cur_eq], eqn[cur_eq], scale);
|
||||
eqn[cur_eq][W] *= scale;
|
||||
++cur_eq;
|
||||
}
|
||||
|
||||
/* Planes from az/el */
|
||||
for (int i=0;i<nae;i+=2) {
|
||||
auto l = m_r.next("arbn az/el card");
|
||||
if (!l) return {SolidParseResult::Error,"arbn EOF (az/el)"};
|
||||
std::string_view lv(l->s);
|
||||
for (int j=0;j<2 && (i+j)<nae;++j) {
|
||||
double az = ComGeomReader::fieldDouble(lv, 10 + j*30 + 0*10, 10) * DEG2RAD;
|
||||
double el = ComGeomReader::fieldDouble(lv, 10 + j*30 + 1*10, 10) * DEG2RAD;
|
||||
int vert_no = ComGeomReader::fieldInt(lv, 10 + j*30 + 2*10, 10);
|
||||
if (vert_no == 0) break;
|
||||
double cos_el = std::cos(el);
|
||||
eqn[cur_eq][X] = std::cos(az) * cos_el;
|
||||
eqn[cur_eq][Y] = std::sin(az) * cos_el;
|
||||
eqn[cur_eq][Z] = std::sin(el);
|
||||
point_t pt;
|
||||
int abs_idx = std::abs(vert_no)-1;
|
||||
VMOVE(pt, &input_points[abs_idx*3]);
|
||||
if (vert_no < 0) pt[Y] = -pt[Y];
|
||||
eqn[cur_eq][W] = VDOT(pt, eqn[cur_eq]);
|
||||
++cur_eq;
|
||||
}
|
||||
}
|
||||
|
||||
if (cur_eq != nface)
|
||||
return {SolidParseResult::Error, "arbn face count mismatch"};
|
||||
|
||||
point_t cent; VSETALL(cent, 0);
|
||||
for (int i=0;i<npt;++i) VADD2(cent, cent, &input_points[i*3]);
|
||||
VSCALE(cent, cent, 1.0/static_cast<double>(npt));
|
||||
if (symm) cent[Y] = 0.0;
|
||||
|
||||
constexpr double DIST_TOL = 1.0e-8;
|
||||
for (int i=0;i<nface;++i) {
|
||||
double dist = VDOT(eqn[i], cent) - eqn[i][W];
|
||||
if (dist < -DIST_TOL) continue;
|
||||
if (dist > DIST_TOL) {
|
||||
VREVERSE(eqn[i], eqn[i]);
|
||||
eqn[i][W] = -eqn[i][W];
|
||||
} else {
|
||||
return {SolidParseResult::Error,"arbn centroid lies on face"};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> vertex;
|
||||
int last_vertex = 0;
|
||||
for (int i=0;i<nface-2;++i) {
|
||||
for (int j=i+1;j<nface-1;++j) {
|
||||
double dot = VDOT(eqn[i], eqn[j]);
|
||||
if (!NEAR_ZERO(dot, 0.999999)) continue;
|
||||
for (int k=j+1;k<nface;++k) {
|
||||
point_t pt;
|
||||
if (bg_make_pnt_3planes(pt, eqn[i], eqn[j], eqn[k]) < 0) continue;
|
||||
bool outside=false;
|
||||
for (int m=0;m<nface;++m) {
|
||||
if (m==i||m==j||m==k) continue;
|
||||
if (VDOT(pt, eqn[m]) - eqn[m][W] > DIST_TOL) { outside=true; break; }
|
||||
}
|
||||
if (outside) continue;
|
||||
bool duplicate=false;
|
||||
for (int m=0;m<last_vertex;++m) {
|
||||
vect_t dv; VSUB2(dv, pt, &vertex[m*3]);
|
||||
if (MAGSQ(dv) < 1.0e-10) { duplicate=true; break; }
|
||||
}
|
||||
if (duplicate) continue;
|
||||
vertex.resize((last_vertex+1)*3);
|
||||
VMOVE(&vertex[last_vertex*3], pt);
|
||||
++last_vertex;
|
||||
used[i]++; used[j]++; used[k]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i=0;i<nface;++i)
|
||||
if (used[i]==0)
|
||||
return {SolidParseResult::Error,"arbn face unused (not convex)"};
|
||||
|
||||
int ret = mk_arbn(m_out, name.c_str(), nface, eqn.data());
|
||||
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_arbn failed":""};
|
||||
}
|
||||
|
||||
/* ---------------- Dispatcher ---------------- */
|
||||
SolidParseResult SolidParser::parseNext() {
|
||||
if (m_solWork >= m_solTotal && m_solTotal > 0) {
|
||||
return {SolidParseResult::Eof, ""};
|
||||
}
|
||||
|
||||
auto l = m_r.next("solid card");
|
||||
if (!l) return {SolidParseResult::Eof, ""};
|
||||
|
||||
int solidNum = 0;
|
||||
std::string type, firstLine = l->s;
|
||||
if (!parseSolidHeader(l->s, solidNum, type, firstLine)) {
|
||||
if (m_solWork >= m_solTotal) return {SolidParseResult::Eof, ""};
|
||||
return {SolidParseResult::Error, "invalid solid header"};
|
||||
}
|
||||
|
||||
// v1 end sentinel handled in region code; in solids, "end" terminates
|
||||
if (type == "end") {
|
||||
return {SolidParseResult::Eof, ""};
|
||||
}
|
||||
|
||||
if (m_opts.version == 5) {
|
||||
int expectedNext = m_solWork + 1;
|
||||
if (solidNum != expectedNext) {
|
||||
// proceed but warn
|
||||
std::fprintf(stderr,
|
||||
"warning: solid numbering mismatch: expected %d, got %d - consuming this solid to maintain alignment\n",
|
||||
expectedNext, solidNum);
|
||||
}
|
||||
}
|
||||
|
||||
// Always advance processed count; names use sequential counter
|
||||
m_solWork++;
|
||||
|
||||
std::string name = makeSolidName(m_solWork);
|
||||
colPrint(name);
|
||||
|
||||
std::vector<fastf_t> dd;
|
||||
|
||||
if (type == "ars") return handle_ars(name, firstLine);
|
||||
if (type == "rpp") { if(!getSolData(dd,6,solidNum,true,firstLine)) return {SolidParseResult::Error,"rpp data read"}; return handle_rpp(name,dd); }
|
||||
if (type == "box") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"box data read"}; return handle_box(name,dd); }
|
||||
if (type == "raw" || type == "wed") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"raw/wed data read"}; return handle_raw_wed(name,dd); }
|
||||
if (type == "rvw") { if(!getSolData(dd,7,solidNum,true,firstLine)) return {SolidParseResult::Error,"rvw data read"}; return handle_rvw(name,dd); }
|
||||
if (type == "arw") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"arw data read"}; return handle_arw(name,dd); }
|
||||
if (m_opts.version == 0 && type == "arb") return handle_arb_magic(name, firstLine);
|
||||
if (type == "arb4") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb4 data read"}; return handle_arb4(name,dd); }
|
||||
if (type == "arb5") { if(!getSolData(dd,15,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb5 data read"}; return handle_arb5(name,dd); }
|
||||
if (type == "arb6") { if(!getSolData(dd,18,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb6 data read"}; return handle_arb6(name,dd); }
|
||||
if (type == "arb7") { if(!getSolData(dd,21,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb7 data read"}; return handle_arb7(name,dd); }
|
||||
if (type == "arb8") { if(!getSolData(dd,24,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb8 data read"}; return handle_arb8(name,dd); }
|
||||
if (type == "rcc") { if(!getSolData(dd,7,solidNum,true,firstLine)) return {SolidParseResult::Error,"rcc data read"}; return handle_rcc(name,dd); }
|
||||
if (type == "rec") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"rec data read"}; return handle_rec(name,dd); }
|
||||
if (type == "trc") { if(!getSolData(dd,8,solidNum,true,firstLine)) return {SolidParseResult::Error,"trc data read"}; return handle_trc(name,dd); }
|
||||
if (type == "tec") { if(!getSolData(dd,13,solidNum,true,firstLine)) return {SolidParseResult::Error,"tec data read"}; return handle_tec(name,dd); }
|
||||
if (type == "tgc") { if(!getSolData(dd,14,solidNum,true,firstLine)) return {SolidParseResult::Error,"tgc data read"}; return handle_tgc(name,dd); }
|
||||
if (type == "sph") { if(!getSolData(dd,4,solidNum,true,firstLine)) return {SolidParseResult::Error,"sph data read"}; return handle_sph(name,dd); }
|
||||
if (type.rfind("wir",0)==0) return handle_wir(name, firstLine);
|
||||
if (type == "rpc") { if(!getSolData(dd,10,solidNum,true,firstLine)) return {SolidParseResult::Error,"rpc data read"}; return handle_rpc(name,dd); }
|
||||
if (type == "rhc") { if(!getSolData(dd,11,solidNum,true,firstLine)) return {SolidParseResult::Error,"rhc data read"}; return handle_rhc(name,dd); }
|
||||
if (type == "epa") { if(!getSolData(dd,11,solidNum,true,firstLine)) return {SolidParseResult::Error,"epa data read"}; return handle_epa(name,dd); }
|
||||
if (type == "ehy") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"ehy data read"}; return handle_ehy(name,dd); }
|
||||
if (type == "eto") { if(!getSolData(dd,11,solidNum,true,firstLine)) return {SolidParseResult::Error,"eto data read"}; return handle_eto(name,dd); }
|
||||
if (type == "ellg") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"ellg data read"}; return handle_ellg(name,dd); }
|
||||
if (type == "ell" || type == "ell1") {
|
||||
if (m_opts.version <=4 && type=="ell") {
|
||||
if (!getSolData(dd,7,solidNum,true,firstLine)) return {SolidParseResult::Error,"v4 ell data read"};
|
||||
return handle_v4_ell_transform(name, dd);
|
||||
} else {
|
||||
if (!getSolData(dd,7,solidNum,true,firstLine)) return {SolidParseResult::Error,"ell data read"};
|
||||
return handle_ell(name, dd);
|
||||
}
|
||||
}
|
||||
if (type == "tor") { if(!getSolData(dd,8,solidNum,true,firstLine)) return {SolidParseResult::Error,"tor data read"}; return handle_tor(name,dd); }
|
||||
if (type == "haf") { if(!getSolData(dd,4,solidNum,true,firstLine)) return {SolidParseResult::Error,"haf data read"}; return handle_haf(name,dd); }
|
||||
if (type == "arbn") return handle_arbn(name, firstLine);
|
||||
|
||||
std::fprintf(stderr, "getsolid: unsupported type '%s'\n", type.c_str());
|
||||
return {SolidParseResult::Error, "unsupported solid"};
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
#ifndef BRLCAD_SOLID_PARSER_H
|
||||
#define BRLCAD_SOLID_PARSER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
extern "C" {
|
||||
#include "vmath.h"
|
||||
#include "raytrace.h"
|
||||
#include "wdb.h"
|
||||
#include "bg.h"
|
||||
}
|
||||
|
||||
#include "ComGeomReader.h"
|
||||
|
||||
struct SolidParseResult {
|
||||
enum Status { Ok, Eof, Error } status = Error;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct SolidParserOptions {
|
||||
int version = 5;
|
||||
int verbose = 0;
|
||||
std::string suffix;
|
||||
bool legacy_trunc = false;
|
||||
};
|
||||
|
||||
class SolidParser {
|
||||
public:
|
||||
SolidParser(ComGeomReader &reader, rt_wdb *outfp, const SolidParserOptions &opts);
|
||||
SolidParseResult parseNext();
|
||||
int solidsExpected() const { return m_solTotal; }
|
||||
int solidsProcessed() const { return m_solWork; }
|
||||
void setSolidsExpected(int c) { m_solTotal = c; }
|
||||
|
||||
private:
|
||||
std::string makeSolidName(int n) const;
|
||||
void colPrint(const std::string &s);
|
||||
bool parseSolidHeader(const std::string &line, int &solidNum,
|
||||
std::string &typeLower, std::string &lineOutForData);
|
||||
bool getSolData(std::vector<fastf_t> &data, int num, int solidNum,
|
||||
bool firstCardAlreadyRead, std::string lineBuf);
|
||||
bool getXSolData(std::vector<fastf_t> &data, int num, int solidNum);
|
||||
|
||||
/* Helpers */
|
||||
bool isIntegerField(std::string_view sv, size_t start, size_t len, int &value) const;
|
||||
bool isLikelyFaceCard(const std::string &line) const;
|
||||
|
||||
/* Primitive handlers */
|
||||
SolidParseResult handle_ars(const std::string &name, const std::string &firstLine);
|
||||
SolidParseResult handle_rpp(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_box(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_raw_wed(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_rvw(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_arw(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_arb_magic(const std::string &name, const std::string &firstLine);
|
||||
SolidParseResult handle_arb4(const std::string &name, const std::vector<fastf_t> &dd4);
|
||||
SolidParseResult handle_arb5(const std::string &name, const std::vector<fastf_t> &dd5);
|
||||
SolidParseResult handle_arb6(const std::string &name, const std::vector<fastf_t> &dd6);
|
||||
SolidParseResult handle_arb7(const std::string &name, const std::vector<fastf_t> &dd7);
|
||||
SolidParseResult handle_arb8(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_rcc(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_rec(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_trc(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_tec(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_tgc(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_sph(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_wir(const std::string &name, const std::string &firstLine);
|
||||
SolidParseResult handle_rpc(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_rhc(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_epa(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_ehy(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_eto(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_v4_ell_transform(const std::string &name, const std::vector<fastf_t> &foci);
|
||||
SolidParseResult handle_ell(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_ellg(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_tor(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_haf(const std::string &name, const std::vector<fastf_t> &dd);
|
||||
SolidParseResult handle_arbn(const std::string &name, const std::string &firstLine);
|
||||
|
||||
private:
|
||||
ComGeomReader &m_r;
|
||||
rt_wdb *m_out = nullptr;
|
||||
SolidParserOptions m_opts;
|
||||
int m_solTotal = 0;
|
||||
int m_solWork = 0;
|
||||
size_t m_colCur = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,376 +0,0 @@
|
||||
================================================================================
|
||||
BRL-CAD BoT → COMGEOM ARBN Export Pipeline
|
||||
RLM (Robust Low-Poly Meshing Inspired) + HACD/V-HACD Convex Decomposition
|
||||
================================================================================
|
||||
|
||||
Purpose:
|
||||
--------
|
||||
Provide a clear, implementation-oriented blueprint for converting arbitrary
|
||||
triangle meshes (BOTs) into a set of convex COMGEOM ARBN solids suitable for
|
||||
region assembly. This document is intended both for human developers and as
|
||||
context for future GPT-assisted sessions.
|
||||
|
||||
Context:
|
||||
--------
|
||||
COMGEOM does not have a native arbitrary triangle mesh primitive. ARBN
|
||||
(primitives defined by intersection of half-spaces / planes) can represent
|
||||
convex polyhedra only. Arbitrary non-convex BOT geometry must be decomposed
|
||||
into convex parts, each described as an ARBN. To reduce complexity and noise,
|
||||
we first simplify and planarize the mesh with an RLM-inspired stage, then
|
||||
decompose using HACD/V-HACD, and finally produce ARBNs.
|
||||
|
||||
High-Level Pipeline Stages:
|
||||
---------------------------
|
||||
1. Mesh Acquisition & Validation
|
||||
2. RLM-Inspired Simplification (Planar Clustering & Polygon Extraction)
|
||||
3. Optional Primitive Recognition (Planes, Cylinders, Cones) (future enhancement)
|
||||
4. Convex Decomposition (HACD/V-HACD or fallback hull partitioning)
|
||||
5. Convex Part → Plane Set Conversion
|
||||
6. ARBN Construction via Incremental Half-Space Clipping
|
||||
7. Region Assembly (OR union of convex ARBN solids)
|
||||
8. Quality Assurance (QA) & Reporting
|
||||
9. Output COMGEOM Deck (version selectable: v1 / v4 / v5)
|
||||
10. Optional JSON summary and statistics
|
||||
|
||||
Key Design Goals:
|
||||
-----------------
|
||||
- Produce clean, planar-faced convex solids minimizing plane count.
|
||||
- Heal small cracks, near-coplanar face fragmentation, and sliver triangles.
|
||||
- Provide tunable tolerances for clustering, plane fitting, snapping, and decomposition.
|
||||
- Maintain sharp features (edges with large dihedral angles).
|
||||
- Scale to large meshes (hundreds of thousands of triangles) via segmentation.
|
||||
|
||||
Terminology:
|
||||
------------
|
||||
BOT : BRL-CAD triangle mesh primitive.
|
||||
RLM-inspired : Robust Low-Poly Meshing style operations (plane clustering, feature retention).
|
||||
HACD/V-HACD : Hierarchical Approximate Convex Decomposition algorithms.
|
||||
ARBN : COMGEOM arbitrary N-plane convex solid.
|
||||
Plane cluster : Group of triangles forming an approximate planar patch.
|
||||
Convex part : Output of HACD or grouping guaranteed to be convex.
|
||||
Clipper : Incremental half-space intersection algorithm producing ARBN vertices.
|
||||
Region : COMGEOM Boolean combination (union/intersection/subtraction) of solids.
|
||||
|
||||
Assumptions:
|
||||
------------
|
||||
- BOT input is either closed and manifold or can be repaired (hole fill / thickening).
|
||||
- Decomposition tools are available (HACD or V-HACD library; otherwise fallback convex hull).
|
||||
- Numeric tolerances are chosen relative to model scale (bbox diagonal).
|
||||
- Complexity controls (max planes per ARBN, max clusters, max convex parts) prevent runaway resource usage.
|
||||
|
||||
Environment / Flags (Proposed):
|
||||
-------------------------------
|
||||
--simplify=none|rlm : Enable RLM-stage planar clustering.
|
||||
--rlm-angle=deg : Angle threshold for normal clustering (default 2.0).
|
||||
--rlm-dist=scale : Distance tolerance for plane residual (default 1e-3 * bbox_diag).
|
||||
--rlm-snap=scale : Vertex snap tolerance (default 5e-4 * bbox_diag).
|
||||
--rlm-sharp=deg : Dihedral angle above which edges are preserved (default 30).
|
||||
--rlm-min-area=scale^2 : Minimum polygon area; discard degenerates.
|
||||
--hacd=on|off : Enable HACD decomposition stage.
|
||||
--hacd-concavity=value : HACD concavity parameter.
|
||||
--hacd-max-parts=N : Max number of convex parts.
|
||||
--hacd-min-volume=scale^3 : Minimum part volume threshold.
|
||||
--decomp-order=pre|post : RLM before or after HACD (default pre).
|
||||
--max-planes-per-arbn=N : Safety cap (default 512).
|
||||
--arbn-hash=on|off : Spatial hash for vertex deduplication (default on).
|
||||
--arbn-stats=path : Stats dump file.
|
||||
--export-version=1|4|5 : COMGEOM dialect (default 5).
|
||||
--json=path : Emit JSON export report.
|
||||
--material-map=file.json : Attribute enrichment mapping.
|
||||
--color-map=file.json : Region color mapping.
|
||||
|
||||
Stage 1: Mesh Acquisition & Validation
|
||||
--------------------------------------
|
||||
Inputs: BOT primitive (triangle list).
|
||||
Steps:
|
||||
1. Load triangles; compute bounding box.
|
||||
2. Verify manifold conditions:
|
||||
- Each edge referenced by 1 or 2 faces.
|
||||
- No zero-area triangles.
|
||||
3. If open surface and user requests solid export:
|
||||
- Either abort or apply thickening (offset + stitch) – optional future feature.
|
||||
4. Deduplicate vertices (hash by quantized position).
|
||||
5. Compute per-face normals and edge dihedral angles.
|
||||
|
||||
Outputs:
|
||||
- Clean triangle list, adjacency (half-edge or edge-to-face map).
|
||||
- BBox, scale metrics.
|
||||
|
||||
Stage 2: RLM-Inspired Simplification
|
||||
------------------------------------
|
||||
Goal: Merge nearly coplanar triangles into planar clusters.
|
||||
|
||||
Algorithm Sketch:
|
||||
1. Seed clusters: each triangle forms initial cluster with its plane.
|
||||
2. Normal clustering: merge cluster pairs if angle between planes < rlm-angle AND distance between centroids projects within rlm-dist residual.
|
||||
3. Plane fitting: For each cluster, fit plane via PCA/least squares to vertex set. Recompute residuals.
|
||||
4. Split clusters where residual > rlm-dist or non-manifold boundaries arise.
|
||||
5. Extract boundary polygon:
|
||||
- Collect boundary edges (adjacent triangle only once).
|
||||
- Project boundary vertices to plane.
|
||||
- Compute 2D convex (or general boundary) hull; remove interior points.
|
||||
6. Vertex snapping:
|
||||
- Project vertices onto fitted plane.
|
||||
- Merge vertices within rlm-snap tolerance.
|
||||
7. Sharp edges retention:
|
||||
- Do not merge clusters across edges with dihedral > rlm-sharp.
|
||||
8. Degeneracy filtering:
|
||||
- Discard polygons with area < rlm-min-area or internal angle < min_angle (~5°).
|
||||
9. Optional: Triangulate polygon if required by downstream (for HACD only if non-convex planar patch).
|
||||
|
||||
Outputs:
|
||||
- Reduced set of planar patches (clusters) with cleaned boundary polygons.
|
||||
- Mapping from original faces to cluster.
|
||||
|
||||
Stage 3: Optional Primitive Recognition (Future Enhancement)
|
||||
------------------------------------------------------------
|
||||
Idea: Detect canonical primitives (planes, cylinders, spheres, cones) before clustering to reduce complexity:
|
||||
- RANSAC or multi-pass fitting.
|
||||
- Replace large planar clusters with a BOX or RPP if axis-aligned.
|
||||
- Defer for initial implementation.
|
||||
|
||||
Stage 4: Convex Decomposition (HACD/V-HACD)
|
||||
-------------------------------------------
|
||||
If mesh (post-RLM) remains non-convex:
|
||||
1. Run HACD on simplified mesh.
|
||||
- Inputs: tolerance (concavity), max parts, min volume.
|
||||
2. Obtain convex parts (triangle subsets).
|
||||
3. For each convex part:
|
||||
- Optionally re-run a localized RLM stage again (Mode: post-hacd).
|
||||
4. If HACD disabled:
|
||||
- Option hull-only: compute global convex hull (loss of concavity details).
|
||||
- Reject or warn if non-convex and no decomposition path chosen.
|
||||
|
||||
Outputs:
|
||||
- Collection of convex parts (each either a polygonal shell).
|
||||
|
||||
Stage 5: Convex Part → Plane Set Conversion
|
||||
-------------------------------------------
|
||||
For each convex part:
|
||||
1. Compute convex hull of part (even if already convex; double-check).
|
||||
2. Extract hull faces; each face yields a plane (normal outward, distance).
|
||||
3. Normalize planes; cluster near-duplicates (angle & offset tolerance).
|
||||
4. Verify face consistency (no inverted normals; positive volume).
|
||||
5. Cap plane count; if > max-planes-per-arbn → either split, relax tolerance, or fallback to legacy enumeration (warning).
|
||||
|
||||
Outputs:
|
||||
- Plane list per convex part.
|
||||
|
||||
Stage 6: ARBN Construction
|
||||
--------------------------
|
||||
Use incremental half-space clipping (implemented arbn_clip):
|
||||
1. Seed bounding polyhedron from plane offsets (derived radius).
|
||||
2. For each plane in part:
|
||||
- Clip polyhedron (spatial hash for vertex reuse).
|
||||
- Abort if volume becomes zero (log error; fallback to hull triangulation?).
|
||||
3. Gather final vertex set and face loops.
|
||||
4. Record stats (input planes, duplicates removed, final face/vertex count).
|
||||
|
||||
Outputs:
|
||||
- ARBN solid definition: plane equations only (COMGEOM stores planes; vertex enumeration implicit).
|
||||
- Optional internal triangulation only for QA (not stored in deck).
|
||||
|
||||
Stage 7: Region Assembly
|
||||
------------------------
|
||||
Single region for entire original BOT:
|
||||
- Boolean semantics: Region = OR of all ARBN solids derived from decomposition.
|
||||
- If decomposition yields components with distinct attributes (e.g., materials), create multiple regions (each corresponding to an attribute class).
|
||||
- For COMGEOM v1: multiple cards with OR semantics; for v4/v5: parenthesized expression: ( +S1 ) OR ( +S2 ) OR ...
|
||||
|
||||
Stage 8: QA & Validation
|
||||
------------------------
|
||||
Checks:
|
||||
1. Volume > 0 and matches (within tolerance) original mesh volume (approx. via voxel sampling or Monte Carlo).
|
||||
2. Surface area near original (after simplification) minus sliver removal.
|
||||
3. Watertightness: ray shoot tests (random directions) see consistent in/out transitions.
|
||||
4. Plane count sanity (no explosion).
|
||||
5. Overlaps: sampled points belong to exactly one convex part unless intended union.
|
||||
|
||||
Stage 9: Output COMGEOM Deck
|
||||
----------------------------
|
||||
For each ARBN:
|
||||
- Solid table entries listing plane equations (v1 fixed columns or v5 token).
|
||||
Region table:
|
||||
- Union of solids, optional attributes.
|
||||
Ident table:
|
||||
- Region codes, space codes, materials, LOS (v5 attribute lines or numeric fields).
|
||||
Attribute enhancement (v5):
|
||||
- material=, color=R/G/B, aircode=, density= if available.
|
||||
|
||||
Stage 10: Reporting
|
||||
-------------------
|
||||
JSON report keys:
|
||||
{
|
||||
"input": { "triangles": N, "bbox": [...], "scale": S },
|
||||
"rlm": { "clusters": C, "merged_faces": F, "avg_residual": R },
|
||||
"hacd": { "enabled": true, "parts": P, "concavity": X },
|
||||
"arbn": { "solids": S, "avg_planes": A, "max_planes": M },
|
||||
"region": { "count": Rg, "boolean": "OR" },
|
||||
"stats": { "warnings": W, "errors": E },
|
||||
"timing": { "rlm_ms": t1, "hacd_ms": t2, "arbn_ms": t3 }
|
||||
}
|
||||
|
||||
Parameter Tuning Guidelines:
|
||||
----------------------------
|
||||
angle_tol (cluster) : 1–2 degrees (start small; larger increases merging).
|
||||
distance_tol (plane fit) : 1e-3 × bbox_diag (reduce for high precision parts).
|
||||
snap_tol (vertex merge) : 5e-4 × bbox_diag (balance healing vs distortion).
|
||||
sharp_threshold : 30 degrees (increase to preserve more edges).
|
||||
hacd_concavity : 0.002–0.01 typical; lower → more parts, higher → fewer.
|
||||
max_planes_per_arbn : 256–512 recommended safety.
|
||||
min_face_area : 1e-10 × bbox_diag^2 (filter numerical artifacts).
|
||||
min_part_volume : 1e-9 × bbox_diag^3 (discard tiny fragments).
|
||||
|
||||
Error Handling Strategy:
|
||||
------------------------
|
||||
- Non-manifold input → attempt repair; if fails, abort with message.
|
||||
- Excessive plane count → split cluster or relax clustering thresholds.
|
||||
- Zero-volume ARBN creation → fallback to convex hull or mark as skipped.
|
||||
- HACD failure → fallback to hull-only mode with warning.
|
||||
- Tolerance misconfiguration → auto-suggest adjustments in JSON report.
|
||||
|
||||
Performance Considerations:
|
||||
---------------------------
|
||||
- Spatial hashing for vertex dedup in ARBN clipper (O(1) average insert/find).
|
||||
- Parallelization possibilities:
|
||||
- RLM clustering per spatial block / octree node.
|
||||
- HACD usually internal multi-threading (depending on library).
|
||||
- ARBN construction per convex part in parallel threads.
|
||||
- Memory caps: early discard of clusters under min_face_area prevents growth.
|
||||
- Streaming: flush solids incrementally to output files.
|
||||
|
||||
Security / Robustness Notes:
|
||||
----------------------------
|
||||
- Validate all user-supplied tolerances; clamp unreasonable values.
|
||||
- Protect against extremely large mesh inputs by early bounding checks.
|
||||
- Consistent use of relative tolerances tied to model scale to avoid numeric collapse.
|
||||
|
||||
Interfaces & Extensibility:
|
||||
---------------------------
|
||||
Future flags:
|
||||
--primitive-detect=on/off : Pre-pass for cylinders/cones/spheres.
|
||||
--thicken-open-mesh=scale : Convert open surfaces to solids.
|
||||
--exact-predicates=on/off : Introduce robust orientation tests.
|
||||
--parallel=N : Explicit thread count.
|
||||
|
||||
Data Structures (Minimal Outline):
|
||||
----------------------------------
|
||||
struct RLMCluster {
|
||||
vect_t plane_normal;
|
||||
double plane_offset;
|
||||
double rms_error;
|
||||
size_t *face_indices;
|
||||
size_t face_count;
|
||||
int sharp_boundary; /* flag */
|
||||
point_t *boundary_vertices;
|
||||
size_t boundary_count;
|
||||
};
|
||||
|
||||
struct ConvexPart {
|
||||
size_t *triangle_indices;
|
||||
size_t tri_count;
|
||||
plane_t *planes;
|
||||
size_t plane_count;
|
||||
};
|
||||
|
||||
struct ARBNSolid {
|
||||
plane_t *planes;
|
||||
size_t plane_count;
|
||||
/* optional stats */
|
||||
};
|
||||
|
||||
Pseudo-Code Summary:
|
||||
--------------------
|
||||
function export_bot_to_comgeom(bot, params):
|
||||
triangles = load(bot)
|
||||
validate(triangles)
|
||||
if params.simplify == RLM:
|
||||
clusters = rlm_cluster(triangles, params)
|
||||
triangles = rebuild_from_clusters(clusters) // optional
|
||||
parts = (params.hacd ? hacd_decompose(triangles, params) : [triangles])
|
||||
solids = []
|
||||
for part in parts:
|
||||
planes = extract_planes(part, params)
|
||||
arbn = build_arbn(planes, params)
|
||||
if arbn.valid: solids.append(arbn)
|
||||
region = build_region_union(solids)
|
||||
write_comgeom(solids, region, params)
|
||||
write_report(params, stats)
|
||||
|
||||
Potential Failure Scenarios & Responses:
|
||||
----------------------------------------
|
||||
1. Input mesh massively non-manifold:
|
||||
- Response: abort early; suggest repair tool invocation.
|
||||
2. RLM merges too aggressively (loss of moderate detail):
|
||||
- Response: output warning; advise lower angle_tol.
|
||||
3. HACD produces thousands of tiny parts:
|
||||
- Response: raise concavity tolerance; filter by min_part_volume.
|
||||
4. ARBN plane overflow:
|
||||
- Response: split part along largest variance axis; recursively process.
|
||||
5. Output region expression too large:
|
||||
- Response: chunk region definitions or group solids logically by type.
|
||||
|
||||
Testing Strategy:
|
||||
-----------------
|
||||
Unit:
|
||||
- Plane fitting accuracy (synthetic coplanar sets).
|
||||
- Clustering stability under noise.
|
||||
- ARBN clipper invariants (volume positive, faces planar).
|
||||
|
||||
Integration:
|
||||
- BOT sample models (simple, moderate, complex).
|
||||
- Comparison of original vs simplified volumes (Monte Carlo sampling).
|
||||
- Performance benchmarking (timings for each stage).
|
||||
|
||||
Regression:
|
||||
- Fuzz small random perturbations in vertex positions; ensure pipeline stable.
|
||||
- Large mesh stress (e.g., 250k triangles) within acceptable memory/time.
|
||||
|
||||
JSON Report Example:
|
||||
--------------------
|
||||
{
|
||||
"input": { "triangles": 50234, "bbox": [ [0,0,0], [10,5,7] ], "scale": 12.247 },
|
||||
"rlm": { "clusters": 340, "merged_faces": 48000, "avg_residual": 0.00072 },
|
||||
"hacd": { "enabled": true, "parts": 128, "concavity": 0.005 },
|
||||
"arbn": { "solids": 128, "avg_planes": 18, "max_planes": 42 },
|
||||
"region": { "count": 1, "boolean": "OR" },
|
||||
"stats": { "warnings": 3, "errors": 0 },
|
||||
"timing": { "rlm_ms": 1834, "hacd_ms": 2975, "arbn_ms": 622 }
|
||||
}
|
||||
|
||||
Open Questions (For Future Iteration):
|
||||
--------------------------------------
|
||||
- Should we incorporate curved primitive detection before RLM to reduce plane counts further?
|
||||
- Acceptable loss thresholds for feature simplification (volume delta %, area delta %)?
|
||||
- Deterministic mode (fixed random seeds) for reproducible CI outputs?
|
||||
- Support for per-material segmentation feeding separate region definitions?
|
||||
|
||||
Recommended Initial Implementation Order:
|
||||
----------------------------------------
|
||||
Phase 1: BOT validation + adjacency + dihedral computation
|
||||
Phase 2: Basic RLM clustering (angle + residual) + boundary extraction
|
||||
Phase 3: HACD integration (API wrapper) + parameter mapping
|
||||
Phase 4: Plane extraction + ARBN clipper integration
|
||||
Phase 5: Region assembly + COMGEOM writing (v5 first)
|
||||
Phase 6: JSON reporting + stats instrumentation
|
||||
Phase 7: Tuning + advanced cluster splitting + QA benchmarks
|
||||
Phase 8: Optional primitive recognition and improved heuristics
|
||||
|
||||
Risk Summary:
|
||||
-------------
|
||||
- Over-simplification: mitigated by exposing user parameters and reporting cluster stats.
|
||||
- Performance bottlenecks: spatial hashing, early culling, parallel cluster operations.
|
||||
- Numerical instability: per-stage tolerance scaling; failover to hull approximations.
|
||||
- Large memory footprints: streaming cluster processing and releasing intermediate buffers.
|
||||
|
||||
References (Conceptual Inspirations):
|
||||
-------------------------------------
|
||||
- Robust Low-Poly Meshing (RLM) paper (site referenced).
|
||||
- HACD / V-HACD algorithms (hierarchical approximate convex decomposition).
|
||||
- Plane clustering & PCA fitting (standard computational geometry).
|
||||
- Incremental half-space intersection for convex polyhedra.
|
||||
- Shewchuk robust predicates (optional future exact arithmetic upgrade).
|
||||
- RANSAC-based primitive detection (future enhancements).
|
||||
|
||||
End of Document
|
||||
================================================================================
|
||||
@@ -1,122 +0,0 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
extern "C" {
|
||||
#include "bu/app.h"
|
||||
#include "bu/opt.h"
|
||||
#include "bu/vls.h"
|
||||
}
|
||||
|
||||
#include "ComGeomConverter.h"
|
||||
|
||||
/* Custom arg processor to copy a string argument into a std::string */
|
||||
static int
|
||||
opt_set_string(struct bu_vls *msg, size_t argc, const char **argv, void *set_var)
|
||||
{
|
||||
BU_OPT_CHECK_ARGV0(msg, argc, argv, "string");
|
||||
if (!set_var) return -1;
|
||||
std::string *sp = static_cast<std::string *>(set_var);
|
||||
*sp = argv[0];
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct CliOptions {
|
||||
ConverterOptions conv;
|
||||
int show_help = 0;
|
||||
int legacy_truncate_names = 0;
|
||||
std::vector<std::string> positionals;
|
||||
};
|
||||
|
||||
static void usage(const char *argv0, const struct bu_opt_desc *opt_defs)
|
||||
{
|
||||
std::fprintf(stderr,
|
||||
"Usage: %s [options] input_file output_file\n\n", argv0);
|
||||
|
||||
char *help = bu_opt_describe(opt_defs, NULL);
|
||||
if (help) {
|
||||
std::fprintf(stderr, "%s\n", help);
|
||||
bu_free(help, "help string");
|
||||
}
|
||||
std::fprintf(stderr,
|
||||
"\nExamples:\n"
|
||||
" %s model.cg model.g # convert using defaults (version 5)\n"
|
||||
" %s -v 0 in_magic.cg out.g # convert MAGIC-era v0 deck\n"
|
||||
" %s -v 4 -s _cg4 in.cg out.g # convert v4, append suffix to names\n"
|
||||
" %s -t -s _longsuffix in.cg out.g # legacy suffix truncation (13 chars)\n"
|
||||
" %s -d 2 input.cg out.g # verbose debug level 2\n",
|
||||
argv0, argv0, argv0, argv0, argv0);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
bu_setprogname(argv[0]);
|
||||
|
||||
CliOptions co;
|
||||
|
||||
struct bu_opt_desc opt_defs[7];
|
||||
BU_OPT(opt_defs[0], "h", "help", "", NULL, &co.show_help, "Show help and exit.");
|
||||
BU_OPT(opt_defs[1], "v", "version", "N", &bu_opt_int, &co.conv.version, "Set COMGEOM input version (0,1,4,5; default 5).");
|
||||
BU_OPT(opt_defs[2], "d", "debug", "N", &bu_opt_int, &co.conv.verbose, "Set verbosity/debug level (integer).");
|
||||
BU_OPT(opt_defs[3], "s", "suffix", "STR", opt_set_string, &co.conv.suffix, "Append name suffix to all generated objects.");
|
||||
BU_OPT(opt_defs[4], "V", "verbose", "", &bu_opt_incr_long,&co.conv.verbose, "Increment verbosity (may be repeated).");
|
||||
BU_OPT(opt_defs[5], "t", "legacy-truncate-names", "", NULL, &co.legacy_truncate_names, "Enable legacy name truncation (suffix max 13 chars).");
|
||||
BU_OPT_NULL(opt_defs[6]);
|
||||
|
||||
std::vector<const char *> argvec;
|
||||
argvec.reserve(static_cast<size_t>(argc - 1));
|
||||
for (int i = 1; i < argc; ++i) argvec.push_back(argv[i]);
|
||||
|
||||
struct bu_vls msgs = BU_VLS_INIT_ZERO;
|
||||
int ret = bu_opt_parse(&msgs, argvec.size(), argvec.data(), opt_defs);
|
||||
|
||||
if (ret < 0) {
|
||||
if (BU_VLS_IS_INITIALIZED(&msgs) && bu_vls_strlen(&msgs) > 0)
|
||||
std::fprintf(stderr, "%s", bu_vls_addr(&msgs));
|
||||
usage(argv[0], opt_defs);
|
||||
bu_vls_free(&msgs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t unused = static_cast<size_t>(ret);
|
||||
for (size_t i = 0; i < unused; ++i)
|
||||
co.positionals.emplace_back(argvec[i]);
|
||||
|
||||
if (co.show_help) {
|
||||
usage(argv[0], opt_defs);
|
||||
bu_vls_free(&msgs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (co.positionals.size() != 2) {
|
||||
std::fprintf(stderr,
|
||||
"Error: expected 2 positional arguments (input_file output_file), got %zu.\n",
|
||||
co.positionals.size());
|
||||
usage(argv[0], opt_defs);
|
||||
bu_vls_free(&msgs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (co.conv.version != 0 && co.conv.version != 1 && co.conv.version != 4 && co.conv.version != 5) {
|
||||
std::fprintf(stderr, "Error: unsupported COMGEOM version %d (valid: 0,1,4,5).\n",
|
||||
co.conv.version);
|
||||
usage(argv[0], opt_defs);
|
||||
bu_vls_free(&msgs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (BU_VLS_IS_INITIALIZED(&msgs) && bu_vls_strlen(&msgs) > 0 && co.conv.verbose)
|
||||
std::fprintf(stderr, "%s", bu_vls_addr(&msgs));
|
||||
bu_vls_free(&msgs);
|
||||
|
||||
const std::string inputPath = co.positionals[0];
|
||||
const std::string outputPath = co.positionals[1];
|
||||
|
||||
co.conv.legacy_truncate_names = (co.legacy_truncate_names != 0);
|
||||
|
||||
ComGeomConverter converter(inputPath, outputPath, co.conv);
|
||||
return converter.run();
|
||||
}
|
||||
@@ -41,6 +41,7 @@ set(
|
||||
badmagic.c
|
||||
bitv.c
|
||||
bomb.c
|
||||
binding.cpp
|
||||
booleanize.c
|
||||
bu_init.cpp
|
||||
cache.cpp
|
||||
@@ -234,7 +235,7 @@ set(
|
||||
mime.cmake
|
||||
parallel.h
|
||||
parallel_cpp11thread.cpp
|
||||
process.h
|
||||
process_private.h
|
||||
sha1.h
|
||||
struetype.h
|
||||
vfont.h
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
/* B I N D I N G . 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 <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include "bu/binding.h"
|
||||
#include "bu/log.h"
|
||||
#include "bu/malloc.h"
|
||||
#include "bu/str.h"
|
||||
#include "bu/vls.h"
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
/*
|
||||
* Internal data structures to hold the parsed bindings and callbacks
|
||||
*/
|
||||
struct ActionCallback {
|
||||
bu_action_func_t func;
|
||||
void *client_data;
|
||||
};
|
||||
|
||||
struct BindingDef {
|
||||
json match;
|
||||
std::string action;
|
||||
};
|
||||
|
||||
/* Global state for libbu bindings */
|
||||
static std::map<std::string, std::vector<BindingDef>>& get_contexts() {
|
||||
static auto* ptr = new std::map<std::string, std::vector<BindingDef>>();
|
||||
return *ptr;
|
||||
}
|
||||
static std::map<std::string, ActionCallback>& get_action_registry() {
|
||||
static auto* ptr = new std::map<std::string, ActionCallback>();
|
||||
return *ptr;
|
||||
}
|
||||
static std::map<std::string, std::string>& get_schema_paths() {
|
||||
static auto* ptr = new std::map<std::string, std::string>();
|
||||
return *ptr;
|
||||
}
|
||||
static std::map<std::string, std::set<std::string>>& get_schemas() {
|
||||
static auto* ptr = new std::map<std::string, std::set<std::string>>();
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
static void
|
||||
ensure_schema_loaded(const std::string& ctx_name)
|
||||
{
|
||||
if (get_schemas().find(ctx_name) != get_schemas().end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = get_schema_paths().find(ctx_name);
|
||||
if (it == get_schema_paths().end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream file(it->second);
|
||||
if (!file.is_open()) {
|
||||
bu_log("ensure_schema_loaded: Failed to open schema %s\n", it->second.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
json j;
|
||||
try {
|
||||
file >> j;
|
||||
} catch (const json::parse_error& e) {
|
||||
bu_log("ensure_schema_loaded: JSON parse error in schema %s: %s\n", it->second.c_str(), e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
std::set<std::string> valid_actions;
|
||||
if (j.contains("actions") && j["actions"].is_object()) {
|
||||
for (auto el = j["actions"].begin(); el != j["actions"].end(); ++el) {
|
||||
valid_actions.insert(el.key());
|
||||
}
|
||||
}
|
||||
get_schemas()[ctx_name] = valid_actions;
|
||||
}
|
||||
|
||||
/* W3C Key validation placeholder. A full implementation would check against
|
||||
* an exhaustive list of W3C standard keys (e.g., "Escape", "ArrowUp").
|
||||
* For demonstration, we'll just implement a simple sanity check.
|
||||
*/
|
||||
static int
|
||||
validate_w3c_key(const std::string& key)
|
||||
{
|
||||
/* Example: Reject completely upper-case strings that might be native C macros */
|
||||
if (key.length() > 1 && key == "RETURN") return 0;
|
||||
if (key.length() > 1 && key == "SPACE") return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* W3C Button validation. Values 0, 1, 2, 3, 4 are standard. */
|
||||
static int
|
||||
validate_w3c_button(int button)
|
||||
{
|
||||
return (button >= 0 && button <= 4);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
bu_binding_load(const char *json_path)
|
||||
{
|
||||
if (!json_path) return -1;
|
||||
|
||||
std::ifstream file(json_path);
|
||||
if (!file.is_open()) {
|
||||
bu_log("bu_binding_load: Failed to open %s\n", json_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
return bu_binding_load_str(buffer.str().c_str());
|
||||
}
|
||||
|
||||
int
|
||||
bu_binding_load_str(const char *json_string)
|
||||
{
|
||||
if (!json_string) return -1;
|
||||
|
||||
json j;
|
||||
try {
|
||||
j = json::parse(json_string);
|
||||
} catch (const json::parse_error& e) {
|
||||
bu_log("bu_binding_load_str: JSON parse error: %s\n", e.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!j.contains("contexts") || !j["contexts"].is_object()) {
|
||||
bu_log("bu_binding_load_str: Invalid JSON structure (missing 'contexts' object)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (auto it = j["contexts"].begin(); it != j["contexts"].end(); ++it) {
|
||||
std::string ctx_name = it.key();
|
||||
const auto& ctx_obj = it.value();
|
||||
|
||||
if (!ctx_obj.contains("bindings") || !ctx_obj["bindings"].is_array()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<BindingDef> defs;
|
||||
|
||||
ensure_schema_loaded(ctx_name);
|
||||
bool has_schema = (get_schemas().find(ctx_name) != get_schemas().end());
|
||||
|
||||
for (const auto& binding : ctx_obj["bindings"]) {
|
||||
if (!binding.contains("match") || !binding.contains("action")) {
|
||||
bu_log("bu_binding_load_str: Binding missing 'match' or 'action' in context %s. Skipping.\n", ctx_name.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
const json& match = binding["match"];
|
||||
std::string action = binding["action"].get<std::string>();
|
||||
|
||||
/* Phase 1 Validation: Check W3C standard compliance */
|
||||
int is_valid = 1;
|
||||
if (match.contains(BU_BINDING_ATTR_KEY)) {
|
||||
if (!match[BU_BINDING_ATTR_KEY].is_string() || !validate_w3c_key(match[BU_BINDING_ATTR_KEY].get<std::string>())) {
|
||||
bu_log("bu_binding_load_str: Invalid W3C key '%s' in context %s. Binding rejected.\n",
|
||||
match[BU_BINDING_ATTR_KEY].dump().c_str(), ctx_name.c_str());
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
if (match.contains(BU_BINDING_ATTR_BUTTON)) {
|
||||
if (!match[BU_BINDING_ATTR_BUTTON].is_number_integer() || !validate_w3c_button(match[BU_BINDING_ATTR_BUTTON].get<int>())) {
|
||||
bu_log("bu_binding_load_str: Invalid W3C button '%s' in context %s. Binding rejected.\n",
|
||||
match[BU_BINDING_ATTR_BUTTON].dump().c_str(), ctx_name.c_str());
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) continue;
|
||||
|
||||
/* Phase 2 Validation: Schema constraints */
|
||||
if (has_schema) {
|
||||
if (get_schemas()[ctx_name].find(action) == get_schemas()[ctx_name].end()) {
|
||||
bu_log("bu_binding_load_str: Binding for action '%s' rejected by schema for context '%s'.\n",
|
||||
action.c_str(), ctx_name.c_str());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
BindingDef def;
|
||||
def.match = match;
|
||||
def.action = action;
|
||||
get_contexts()[ctx_name].insert(get_contexts()[ctx_name].begin(), def);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
bu_binding_register_schema(const char *context, const char *json_path)
|
||||
{
|
||||
if (!context || !json_path) return;
|
||||
get_schema_paths()[std::string(context)] = std::string(json_path);
|
||||
}
|
||||
|
||||
void
|
||||
bu_binding_schema_add_action(const char *context, const char *action_name, const char *description)
|
||||
{
|
||||
if (!context || !action_name) return;
|
||||
std::string ctx(context);
|
||||
ensure_schema_loaded(ctx);
|
||||
get_schemas()[ctx].insert(std::string(action_name));
|
||||
}
|
||||
|
||||
void
|
||||
bu_binding_schema_remove_action(const char *context, const char *action_name)
|
||||
{
|
||||
if (!context || !action_name) return;
|
||||
std::string ctx(context);
|
||||
ensure_schema_loaded(ctx);
|
||||
get_schemas()[ctx].erase(std::string(action_name));
|
||||
}
|
||||
|
||||
void
|
||||
bu_binding_register_action(const char *action_name, bu_action_func_t func, void *client_data)
|
||||
{
|
||||
if (!action_name || !func) return;
|
||||
|
||||
ActionCallback cb;
|
||||
cb.func = func;
|
||||
cb.client_data = client_data;
|
||||
|
||||
get_action_registry()[std::string(action_name)] = cb;
|
||||
}
|
||||
|
||||
/* Helper to check if the json 'event' contains all the key-value pairs required by 'match' */
|
||||
static int
|
||||
is_match(const json& match, const json& event)
|
||||
{
|
||||
for (auto it = match.begin(); it != match.end(); ++it) {
|
||||
if (!event.contains(it.key())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If it's an array (like modifiers), we need to ensure all elements in 'match' exist in 'event' */
|
||||
if (it.value().is_array()) {
|
||||
if (!event[it.key()].is_array()) return 0;
|
||||
|
||||
for (const auto& req_mod : it.value()) {
|
||||
int found = 0;
|
||||
for (const auto& ev_mod : event[it.key()]) {
|
||||
if (ev_mod == req_mod) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return 0;
|
||||
}
|
||||
}
|
||||
/* Otherwise, simple equality */
|
||||
else if (event[it.key()] != it.value()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
bu_binding_process_event(const char *context, const char *event_json, void *event_data)
|
||||
{
|
||||
if (!context || !event_json) return 0;
|
||||
|
||||
std::string ctx_name(context);
|
||||
auto it = get_contexts().find(ctx_name);
|
||||
if (it == get_contexts().end()) {
|
||||
return 0; /* Context not found */
|
||||
}
|
||||
|
||||
const char *json_str = event_json;
|
||||
|
||||
json event_json_obj;
|
||||
try {
|
||||
event_json_obj = json::parse(json_str);
|
||||
} catch (const json::parse_error& e) {
|
||||
bu_log("bu_binding_process_event: Invalid event JSON: %s\n", e.what());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!event_json_obj.is_object()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const auto& def : it->second) {
|
||||
if (is_match(def.match, event_json_obj)) {
|
||||
/* Match found */
|
||||
auto reg_it = get_action_registry().find(def.action);
|
||||
if (reg_it != get_action_registry().end()) {
|
||||
reg_it->second.func(event_json, reg_it->second.client_data, event_data);
|
||||
return 1;
|
||||
} else {
|
||||
/* We have a binding, but no callback registered for this action */
|
||||
bu_log("Warning: Event mapped to action '%s', but no callback is registered.\n", def.action.c_str());
|
||||
return 1; /* Still mark as handled since it matched a binding */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0; /* Unhandled */
|
||||
}
|
||||
|
||||
extern "C" int
|
||||
bu_binding_action_is_bound(const char *context, const char *action_name)
|
||||
{
|
||||
if (!context || !action_name) return 0;
|
||||
|
||||
std::string ctx(context);
|
||||
std::string act(action_name);
|
||||
|
||||
auto it = get_contexts().find(ctx);
|
||||
if (it == get_contexts().end()) return 0;
|
||||
|
||||
for (const auto& def : it->second) {
|
||||
if (def.action == act) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* mode: C++
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
+1
-1
@@ -49,7 +49,7 @@
|
||||
#include "bu/str.h"
|
||||
#include "bu/datetime.h"
|
||||
#include "bu/vls.h"
|
||||
#include "./process.h"
|
||||
#include "./process_private.h"
|
||||
|
||||
#ifndef HAVE_KILL
|
||||
# include <TlHelp32.h>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* P R O C E S S . H
|
||||
/* P R O C E S S _ P R I V A T E . H
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2013-2026 United States Government as represented by
|
||||
@@ -20,8 +20,8 @@
|
||||
|
||||
/* Private libbu process related definitions shared between multiple files */
|
||||
|
||||
#ifndef LIBBU_PROCESS_H
|
||||
#define LIBBU_PROCESS_H
|
||||
#ifndef LIBBU_PROCESS_PRIVATE_H
|
||||
#define LIBBU_PROCESS_PRIVATE_H
|
||||
|
||||
/* Apparently the termination code on Windows is arbitrary and somewhat
|
||||
* ill-defined - see https://bugs.python.org/issue31863 for more info. Try our
|
||||
@@ -29,7 +29,7 @@
|
||||
* abort identification. */
|
||||
#define BU_MSVC_ABORT_EXIT 97101 /* ascii - a == 97 && e == 101 */
|
||||
|
||||
#endif /* LIBBU_PROCESS_H */
|
||||
#endif /* LIBBU_PROCESS_PRIVATE_H */
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
@@ -10,6 +10,7 @@ set(
|
||||
test_bomb.c
|
||||
test_badmagic.c
|
||||
test_basename.c
|
||||
test_binding.cpp
|
||||
test_bitv.c
|
||||
test_booleanize.c
|
||||
test_cache.cpp
|
||||
@@ -122,6 +123,7 @@ brlcad_addexec(bu_gethostname test_gethostname.c libbu TEST)
|
||||
brlcad_add_test(NAME bu_argv COMMAND bu_test test_argv)
|
||||
brlcad_add_test(NAME bu_backtrace COMMAND bu_test test_backtrace)
|
||||
brlcad_add_test(NAME bu_bomb COMMAND bu_test test_bomb "$<TARGET_FILE:bu_test>")
|
||||
brlcad_add_test(NAME bu_binding COMMAND bu_test test_binding "${CMAKE_CURRENT_SOURCE_DIR}/test_bindings.json")
|
||||
brlcad_add_test(NAME bu_dir COMMAND bu_test test_dir)
|
||||
brlcad_add_test(NAME bu_list_empty COMMAND bu_test test_list 0)
|
||||
brlcad_add_test(NAME bu_list_single COMMAND bu_test test_list 1)
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/* T E S T _ B I N D I N G . 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 <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bu/binding.h"
|
||||
#include "bu/vls.h"
|
||||
|
||||
int action_triggered = 0;
|
||||
|
||||
void mock_action_handler(const char *event_json, void *registered_data, void *event_data) {
|
||||
action_triggered = 1;
|
||||
printf("Action handler triggered with JSON: %s\n", event_json);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bu_setprogname(argv[0]);
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s <path_to_test_bindings.json>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *json_path = argv[1];
|
||||
|
||||
/* Register schema to restrict allowed actions */
|
||||
bu_binding_schema_add_action("fb", "fb_zoom", "Zoom");
|
||||
bu_binding_schema_add_action("fb", "fb_close_window", "Close Window");
|
||||
|
||||
/* Register hooks before loading JSON */
|
||||
bu_binding_register_action("fb_close_window", mock_action_handler, NULL);
|
||||
|
||||
printf("Loading bindings from %s\n", json_path);
|
||||
if (bu_binding_load(json_path) < 0) {
|
||||
printf("Failed to load bindings.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Test 1: Simulate a matching event */
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"mouse-press\", \"button\": 2}");
|
||||
|
||||
action_triggered = 0;
|
||||
bu_binding_process_event("fb", bu_vls_cstr(&ev_json), NULL);
|
||||
|
||||
if (!action_triggered) {
|
||||
printf("Test 1 Failed: Expected action was not triggered.\n");
|
||||
return -1;
|
||||
}
|
||||
printf("Test 1 Passed: Action triggered successfully.\n");
|
||||
|
||||
/* Test 2: Simulate a non-matching event */
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"mouse-press\", \"button\": 1}");
|
||||
action_triggered = 0;
|
||||
bu_binding_process_event("fb", bu_vls_cstr(&ev_json), NULL);
|
||||
|
||||
if (action_triggered) {
|
||||
printf("Test 2 Failed: Action should not have been triggered.\n");
|
||||
return -1;
|
||||
}
|
||||
printf("Test 2 Passed: Non-matching event correctly ignored.\n");
|
||||
|
||||
/* Test 3: The Escape key binding should have been rejected by the validator */
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"key-press\", \"key\": \"Escape\"}");
|
||||
action_triggered = 0;
|
||||
bu_binding_process_event("fb", bu_vls_cstr(&ev_json), NULL);
|
||||
|
||||
if (action_triggered) {
|
||||
printf("Test 3 Failed: Escape binding should have been filtered by the validator.\n");
|
||||
return -1;
|
||||
}
|
||||
printf("Test 3 Passed: Validator correctly rejected binding at load time.\n");
|
||||
|
||||
bu_vls_free(&ev_json);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* mode: C++
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"contexts": {
|
||||
"fb": {
|
||||
"bindings": [
|
||||
{
|
||||
"match": {
|
||||
"type": "mouse-press",
|
||||
"button": 2
|
||||
},
|
||||
"action": "fb_close_window"
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"type": "key-press",
|
||||
"key": "Escape"
|
||||
},
|
||||
"action": "fb_invalid_action"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,11 @@ set_property(
|
||||
PROPERTY COMPILE_DEFINITIONS "DM_PLUGIN_SUFFIX=\"${CMAKE_SHARED_LIBRARY_SUFFIX}\""
|
||||
)
|
||||
|
||||
# Files related to key/mouse binding management
|
||||
brlcad_manage_files(dm_bindings.json ${DATA_DIR}/bindings)
|
||||
brlcad_manage_files(dm_schema.json ${DATA_DIR}/bindings)
|
||||
brlcad_manage_files(fb_schema.json ${DATA_DIR}/bindings)
|
||||
|
||||
brlcad_find_package(PNG REQUIRED)
|
||||
find_package(OpenSSL QUIET)
|
||||
|
||||
@@ -165,3 +170,4 @@ cmakefiles(${libdm_ignore_files})
|
||||
# indent-tabs-mode: t
|
||||
# End:
|
||||
# ex: shiftwidth=2 tabstop=8
|
||||
|
||||
|
||||
@@ -2925,6 +2925,7 @@ X24_handle_event(struct fb *ifp, XEvent *event)
|
||||
case ButtonPress:
|
||||
{
|
||||
int button = (int) event->xbutton.button;
|
||||
int w3c_button = 0;
|
||||
|
||||
/* In interactive mode, report the event to the application
|
||||
* rather than consuming it here. */
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"contexts": {
|
||||
"fb": {
|
||||
"description": "Standard interactions for raw framebuffers (rt, fbserv)",
|
||||
"bindings": [
|
||||
{
|
||||
"match": {
|
||||
"type": "mouse-press",
|
||||
"button": 1
|
||||
},
|
||||
"action": "fb_print_pixel_rgb"
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"type": "mouse-press",
|
||||
"button": 2
|
||||
},
|
||||
"action": "fb_close_window"
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"type": "key-press",
|
||||
"key": "Escape"
|
||||
},
|
||||
"action": "fb_close_window"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dm": {
|
||||
"description": "Standard bindings for the Geometry Editor (qged & mged)",
|
||||
"bindings": [
|
||||
{ "match": { "type": "key-press", "key": "A" }, "action": "view_toggle_adc" },
|
||||
{ "match": { "type": "key-press", "key": "M" }, "action": "view_toggle_model_axes" },
|
||||
{ "match": { "type": "key-press", "key": "V" }, "action": "view_toggle_view_axes" },
|
||||
{ "match": { "type": "key-press", "key": "2" }, "action": "view_2" },
|
||||
{ "match": { "type": "key-press", "key": "3" }, "action": "view_3" },
|
||||
{ "match": { "type": "key-press", "key": "4" }, "action": "view_4" },
|
||||
{ "match": { "type": "key-press", "key": "5" }, "action": "view_5" },
|
||||
{ "match": { "type": "key-press", "key": "6" }, "action": "view_6" },
|
||||
{ "match": { "type": "key-press", "key": "7" }, "action": "view_7" },
|
||||
{ "match": { "type": "key-press", "key": "F" }, "action": "view_front" },
|
||||
{ "match": { "type": "key-press", "key": "T" }, "action": "view_top" },
|
||||
{ "match": { "type": "key-press", "key": "B" }, "action": "view_bottom" },
|
||||
{ "match": { "type": "key-press", "key": "L" }, "action": "view_left" },
|
||||
{ "match": { "type": "key-press", "key": "R", "modifiers": ["Shift"] }, "action": "view_rear" },
|
||||
{ "match": { "type": "key-press", "key": "R" }, "action": "view_right" },
|
||||
{ "match": { "type": "mouse-press", "button": 0 }, "action": "view_mouse_press_left" },
|
||||
{ "match": { "type": "mouse-release", "button": 0 }, "action": "view_mouse_release_left" },
|
||||
{ "match": { "type": "mouse-release", "button": 2 }, "action": "view_mouse_release_right" },
|
||||
{ "match": { "type": "mouse-release", "button": 1 }, "action": "view_mouse_release_middle" },
|
||||
{ "match": { "type": "mouse-move", "button": 0, "modifiers": ["Control"] }, "action": "view_mouse_drag_rotate" },
|
||||
{ "match": { "type": "mouse-move", "button": 0, "modifiers": ["Shift"] }, "action": "view_mouse_drag_translate" },
|
||||
{ "match": { "type": "mouse-move", "button": 0, "modifiers": ["Control", "Shift"] }, "action": "view_mouse_drag_scale" },
|
||||
{ "match": { "type": "mouse-move", "button": 0 }, "action": "view_mouse_drag_adjust" },
|
||||
{ "match": { "type": "mouse-wheel" }, "action": "view_mouse_wheel_scale" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,10 +66,22 @@ dm_init_msgs()
|
||||
}
|
||||
|
||||
|
||||
#include "bu/binding.h"
|
||||
|
||||
static void
|
||||
libdm_init(void)
|
||||
{
|
||||
bu_vls_init(&init_msgs);
|
||||
|
||||
const char *dm_schema = bu_dir(NULL, 0, BU_DIR_DATA, "bindings", "dm_schema.json", NULL);
|
||||
if (dm_schema) {
|
||||
bu_binding_register_schema("dm", dm_schema);
|
||||
}
|
||||
|
||||
const char *fb_schema = bu_dir(NULL, 0, BU_DIR_DATA, "bindings", "fb_schema.json", NULL);
|
||||
if (fb_schema) {
|
||||
bu_binding_register_schema("fb", fb_schema);
|
||||
}
|
||||
|
||||
const char *ppath = bu_dir(NULL, 0, BU_DIR_LIBEXEC, "dm", NULL);
|
||||
char **dm_filenames;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include "bu/binding.h"
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
@@ -62,6 +63,13 @@ dm_open(void *ctx, void *interp, const char *type, int argc, const char *argv[])
|
||||
|
||||
const struct dm *d = d_it->second;
|
||||
struct dm *dmp = d->i->dm_open(ctx, interp, argc, argv);
|
||||
|
||||
char *bindings_path = (char *)bu_dir(NULL, 0, BU_DIR_DATA, "bindings", "dm_bindings.json", NULL);
|
||||
if (bindings_path) {
|
||||
bu_binding_load(bindings_path);
|
||||
bu_free(bindings_path, "bindings_path");
|
||||
}
|
||||
|
||||
return dmp;
|
||||
}
|
||||
|
||||
@@ -528,6 +536,12 @@ found_interface:
|
||||
|
||||
return FB_NULL;
|
||||
}
|
||||
char *bindings_path = (char *)bu_dir(NULL, 0, BU_DIR_DATA, "bindings", "dm_bindings.json", NULL);
|
||||
if (bindings_path) {
|
||||
bu_binding_load(bindings_path);
|
||||
bu_free(bindings_path, "bindings_path");
|
||||
}
|
||||
|
||||
return ifp;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"actions": {
|
||||
"view_toggle_adc": "Toggle Angle Distance Cursor",
|
||||
"view_toggle_model_axes": "Toggle Model Axes",
|
||||
"view_toggle_view_axes": "Toggle View Axes",
|
||||
"view_2": "Set View 2",
|
||||
"view_3": "Set View 3",
|
||||
"view_4": "Set View 4",
|
||||
"view_5": "Set View 5",
|
||||
"view_6": "Set View 6",
|
||||
"view_7": "Set View 7",
|
||||
"view_front": "Set Front View",
|
||||
"view_top": "Set Top View",
|
||||
"view_bottom": "Set Bottom View",
|
||||
"view_left": "Set Left View",
|
||||
"view_right": "Set Right View",
|
||||
"view_rear": "Set Rear View",
|
||||
"view_mouse_drag_rotate": "Rotate view via mouse drag",
|
||||
"view_mouse_drag_translate": "Translate view via mouse drag",
|
||||
"view_mouse_drag_scale": "Scale view via mouse drag",
|
||||
"view_mouse_wheel_scale": "Scale view via mouse wheel",
|
||||
"view_mouse_press_left": "Left mouse press action",
|
||||
"view_mouse_press_right": "Right mouse press action"
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@
|
||||
#include "./include/private.h"
|
||||
#include "icv.h"
|
||||
#include "dm.h"
|
||||
#include "bu/binding.h"
|
||||
|
||||
|
||||
struct fb *fb_get(void)
|
||||
@@ -258,6 +259,59 @@ long fb_poll_rate(struct fb *ifp)
|
||||
return ifp->i->if_poll_refresh_rate;
|
||||
}
|
||||
|
||||
static int
|
||||
fb_event_button_to_w3c(int button)
|
||||
{
|
||||
return (button >= 1 && button <= 3) ? button - 1 : button;
|
||||
}
|
||||
|
||||
int
|
||||
fb_process_event_bindings(struct fb *ifp, const struct fb_event *event)
|
||||
{
|
||||
struct bu_vls event_json = BU_VLS_INIT_ZERO;
|
||||
int handled = 0;
|
||||
|
||||
if (!ifp || !event)
|
||||
return 0;
|
||||
|
||||
switch (event->type) {
|
||||
case FB_EVENT_BUTTON_PRESS:
|
||||
bu_vls_sprintf(&event_json,
|
||||
"{\"type\": \"mouse-press\", \"button\": %d, \"x\": %d, \"y\": %d}",
|
||||
fb_event_button_to_w3c(event->button), event->x, event->y);
|
||||
break;
|
||||
case FB_EVENT_BUTTON_RELEASE:
|
||||
bu_vls_sprintf(&event_json,
|
||||
"{\"type\": \"mouse-release\", \"button\": %d, \"x\": %d, \"y\": %d}",
|
||||
fb_event_button_to_w3c(event->button), event->x, event->y);
|
||||
break;
|
||||
case FB_EVENT_MOTION:
|
||||
bu_vls_sprintf(&event_json,
|
||||
"{\"type\": \"mouse-move\", \"x\": %d, \"y\": %d}",
|
||||
event->x, event->y);
|
||||
break;
|
||||
case FB_EVENT_KEY_PRESS:
|
||||
if (event->keycode == 27) {
|
||||
bu_vls_sprintf(&event_json, "{\"type\": \"key-press\", \"key\": \"Escape\"}");
|
||||
} else if (event->keycode >= 32 && event->keycode <= 126) {
|
||||
bu_vls_sprintf(&event_json,
|
||||
"{\"type\": \"key-press\", \"key\": \"%c\"}",
|
||||
event->keycode);
|
||||
}
|
||||
break;
|
||||
case FB_EVENT_CLOSE:
|
||||
bu_vls_sprintf(&event_json, "{\"type\": \"key-press\", \"key\": \"Escape\"}");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (bu_vls_strlen(&event_json) > 0)
|
||||
handled = bu_binding_process_event("fb", bu_vls_cstr(&event_json), ifp);
|
||||
bu_vls_free(&event_json);
|
||||
return handled;
|
||||
}
|
||||
|
||||
void fb_set_interactive(struct fb *ifp, int on)
|
||||
{
|
||||
if (!ifp)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"actions": {
|
||||
"fb_print_pixel_rgb": "Print RGB values of pixel under cursor",
|
||||
"fb_close_window": "Close framebuffer window"
|
||||
}
|
||||
}
|
||||
+171
-72
@@ -61,6 +61,10 @@
|
||||
#include "dm.h"
|
||||
#include "./fb_ogl.h"
|
||||
|
||||
#include "bu/binding.h"
|
||||
#include "bu/file.h"
|
||||
#include <X11/keysym.h>
|
||||
|
||||
extern struct fb ogl_interface;
|
||||
|
||||
#define DIRECT_COLOR_VISUAL_ALLOWED 0
|
||||
@@ -774,6 +778,52 @@ ogl_configureWindow(struct fb *ifp, int width, int height)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
ogl_action_fb_print_pixel_rgb(const struct bu_vls *event_json, void *registered_data, void *event_data)
|
||||
{
|
||||
struct fb *ifp = (struct fb *)event_data;
|
||||
int x = -1, y = -1;
|
||||
char *p;
|
||||
|
||||
if (!ifp) return;
|
||||
|
||||
if ((p = strstr(bu_vls_cstr(event_json), "\"x\":"))) {
|
||||
sscanf(p + 4, "%d", &x);
|
||||
}
|
||||
if ((p = strstr(bu_vls_cstr(event_json), "\"y\":"))) {
|
||||
sscanf(p + 4, "%d", &y);
|
||||
}
|
||||
|
||||
if (x < 0 || y < 0) {
|
||||
fb_log("No RGB (outside image viewport)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t memidx = (y * WIN(ifp)->mi_pixwidth) * sizeof(struct fb_pixel);
|
||||
struct fb_pixel *oglp = (struct fb_pixel *)&ifp->i->if_mem[memidx];
|
||||
|
||||
fb_log("At image (%d, %d), real RGB=(%3d %3d %3d)\n",
|
||||
x, y, (int)oglp[x].red, (int)oglp[x].green, (int)oglp[x].blue);
|
||||
}
|
||||
|
||||
static void
|
||||
ogl_action_fb_close_window(const struct bu_vls *event_json, void *registered_data, void *event_data)
|
||||
{
|
||||
struct fb *ifp = (struct fb *)event_data;
|
||||
if (ifp) OGL(ifp)->alive = 0;
|
||||
}
|
||||
|
||||
static int bindings_loaded = 0;
|
||||
static void
|
||||
ogl_init_bindings(void)
|
||||
{
|
||||
if (bindings_loaded) return;
|
||||
bindings_loaded = 1;
|
||||
|
||||
|
||||
bu_binding_register_action("fb_print_pixel_rgb", ogl_action_fb_print_pixel_rgb, NULL);
|
||||
bu_binding_register_action("fb_close_window", ogl_action_fb_close_window, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
ogl_do_event(struct fb *ifp)
|
||||
@@ -781,76 +831,122 @@ ogl_do_event(struct fb *ifp)
|
||||
XEvent event;
|
||||
|
||||
while (XCheckWindowEvent(OGL(ifp)->dispp, OGL(ifp)->wind,
|
||||
OGL(ifp)->event_mask, &event)) {
|
||||
switch (event.type) {
|
||||
case Expose:
|
||||
if (!OGL(ifp)->use_ext_ctrl)
|
||||
expose_callback(ifp);
|
||||
break;
|
||||
case ButtonPress:
|
||||
{
|
||||
int button = (int) event.xbutton.button;
|
||||
if (button == Button1) {
|
||||
/* Check for single button mouse remap.
|
||||
* ctrl-1 => 2
|
||||
* meta-1 => 3
|
||||
* cmdkey => 3
|
||||
*/
|
||||
if (event.xbutton.state & ControlMask) {
|
||||
button = Button2;
|
||||
} else if (event.xbutton.state & Mod1Mask) {
|
||||
button = Button3;
|
||||
} else if (event.xbutton.state & Mod2Mask) {
|
||||
button = Button3;
|
||||
}
|
||||
}
|
||||
|
||||
switch (button) {
|
||||
case Button1:
|
||||
break;
|
||||
case Button2:
|
||||
{
|
||||
int x, y;
|
||||
register struct fb_pixel *oglp;
|
||||
|
||||
x = event.xbutton.x;
|
||||
y = ifp->i->if_height - event.xbutton.y - 1;
|
||||
|
||||
if (x < 0 || y < 0) {
|
||||
fb_log("No RGB (outside image viewport)\n");
|
||||
break;
|
||||
}
|
||||
|
||||
size_t memidx = (y * WIN(ifp)->mi_pixwidth) * sizeof(struct fb_pixel);
|
||||
oglp = (struct fb_pixel *)&ifp->i->if_mem[memidx];
|
||||
|
||||
fb_log("At image (%d, %d), real RGB=(%3d %3d %3d)\n",
|
||||
x, y, (int)oglp[x].red, (int)oglp[x].green, (int)oglp[x].blue);
|
||||
|
||||
break;
|
||||
}
|
||||
case Button3:
|
||||
OGL(ifp)->alive = 0;
|
||||
break;
|
||||
default:
|
||||
fb_log("unhandled mouse event\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ConfigureNotify:
|
||||
{
|
||||
XConfigureEvent *conf = (XConfigureEvent *)&event;
|
||||
|
||||
if (conf->width == OGL(ifp)->win_width &&
|
||||
conf->height == OGL(ifp)->win_height)
|
||||
return;
|
||||
|
||||
ogl_configureWindow(ifp, conf->width, conf->height);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
OGL(ifp)->event_mask, &event)) {
|
||||
switch (event.type) {
|
||||
case Expose:
|
||||
if (!OGL(ifp)->use_ext_ctrl)
|
||||
expose_callback(ifp);
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev = {FB_EVENT_EXPOSE, 0, 0, 0, 0, 0};
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
}
|
||||
break;
|
||||
case ButtonPress:
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = FB_EVENT_BUTTON_PRESS;
|
||||
ev.button = (int)event.xbutton.button;
|
||||
ev.x = event.xbutton.x;
|
||||
ev.y = ifp->i->if_height - event.xbutton.y - 1;
|
||||
ev.state = (int)event.xbutton.state;
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
} else {
|
||||
int button = (int)event.xbutton.button;
|
||||
int w3c_button = 0;
|
||||
if (button == Button1) {
|
||||
if (event.xbutton.state & ControlMask) button = Button2;
|
||||
else if (event.xbutton.state & Mod1Mask) button = Button3;
|
||||
else if (event.xbutton.state & Mod2Mask) button = Button3;
|
||||
}
|
||||
if (button == Button1) w3c_button = 0;
|
||||
else if (button == Button2) w3c_button = 1;
|
||||
else if (button == Button3) w3c_button = 2;
|
||||
else w3c_button = button;
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json,
|
||||
"{\"type\": \"mouse-press\", \"button\": %d, \"x\": %d, \"y\": %d}",
|
||||
w3c_button, event.xbutton.x,
|
||||
ifp->i->if_height - event.xbutton.y - 1);
|
||||
bu_binding_process_event("fb", bu_vls_cstr(&ev_json), ifp);
|
||||
bu_vls_free(&ev_json);
|
||||
}
|
||||
break;
|
||||
case ButtonRelease:
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = FB_EVENT_BUTTON_RELEASE;
|
||||
ev.button = (int)event.xbutton.button;
|
||||
ev.x = event.xbutton.x;
|
||||
ev.y = ifp->i->if_height - event.xbutton.y - 1;
|
||||
ev.state = (int)event.xbutton.state;
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
}
|
||||
break;
|
||||
case MotionNotify:
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = FB_EVENT_MOTION;
|
||||
ev.x = event.xmotion.x;
|
||||
ev.y = ifp->i->if_height - event.xmotion.y - 1;
|
||||
ev.state = (int)event.xmotion.state;
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
}
|
||||
break;
|
||||
case KeyPress:
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev;
|
||||
char keybuf[8] = {0};
|
||||
KeySym keysym;
|
||||
int n;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = FB_EVENT_KEY_PRESS;
|
||||
n = XLookupString(&event.xkey, keybuf, sizeof(keybuf), &keysym, NULL);
|
||||
ev.keycode = (n > 0) ? (unsigned char)keybuf[0] : (int)keysym;
|
||||
ev.state = (int)event.xkey.state;
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
} else {
|
||||
KeySym keysym = XLookupKeysym(&event.xkey, 0);
|
||||
char *key_str = XKeysymToString(keysym);
|
||||
if (key_str) {
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json,
|
||||
"{\"type\": \"key-press\", \"key\": \"%s\"}", key_str);
|
||||
bu_binding_process_event("fb", bu_vls_cstr(&ev_json), ifp);
|
||||
bu_vls_free(&ev_json);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ClientMessage:
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev = {FB_EVENT_CLOSE, 0, 0, 0, 0, 0};
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
} else {
|
||||
OGL(ifp)->alive = 0;
|
||||
}
|
||||
break;
|
||||
case ConfigureNotify:
|
||||
{
|
||||
XConfigureEvent *conf = (XConfigureEvent *)&event;
|
||||
if (conf->width == OGL(ifp)->win_width &&
|
||||
conf->height == OGL(ifp)->win_height)
|
||||
break;
|
||||
ogl_configureWindow(ifp, conf->width, conf->height);
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = FB_EVENT_RESIZE;
|
||||
ev.x = conf->width;
|
||||
ev.y = conf->height;
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1021,9 +1117,10 @@ fb_ogl_open(struct fb *ifp, const char *file, int width, int height)
|
||||
int mode, i;
|
||||
long valuemask;
|
||||
XSetWindowAttributes swa;
|
||||
|
||||
FB_CK_FB(ifp->i);
|
||||
|
||||
ogl_init_bindings();
|
||||
|
||||
/*
|
||||
* First, attempt to determine operating mode for this open,
|
||||
* based upon the "unit number" or flags.
|
||||
@@ -1214,7 +1311,7 @@ fb_ogl_open(struct fb *ifp, const char *file, int width, int height)
|
||||
OGL(ifp)->vip->screen);
|
||||
swa.event_mask = OGL(ifp)->event_mask =
|
||||
ExposureMask | KeyPressMask | KeyReleaseMask |
|
||||
ButtonPressMask | ButtonReleaseMask;
|
||||
ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask;
|
||||
swa.colormap = OGL(ifp)->xcmap;
|
||||
|
||||
#define XCreateWindowDebug(display, parent, x, y, width, height, border_width, depth, class, visual, valuemask, attributes) \
|
||||
@@ -1381,6 +1478,8 @@ ogl_open_existing(struct fb *ifp, int width, int height, struct fb_platform_spec
|
||||
{
|
||||
struct ogl_fb_info *ogl_internal = (struct ogl_fb_info *)fb_p->data;
|
||||
BU_CKMAG(fb_p, FB_OGL_MAGIC, "ogl framebuffer");
|
||||
|
||||
ogl_init_bindings();
|
||||
return open_existing(ifp, ogl_internal->dpy, ogl_internal->win,
|
||||
ogl_internal->cmap, ogl_internal->vip, width, height, ogl_internal->glxc,
|
||||
ogl_internal->double_buffer, ogl_internal->soft_cmap);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+241
-246
@@ -37,6 +37,12 @@ extern "C" {
|
||||
#include "qtcad/defines.h"
|
||||
#include "bindings.h"
|
||||
|
||||
extern "C" {
|
||||
#include "bu/binding.h"
|
||||
#include "bu/file.h"
|
||||
#include "bu/log.h"
|
||||
}
|
||||
|
||||
typedef void (*bounds_update_t)(struct bview *);
|
||||
static std::unordered_map<struct bview *, bounds_update_t> drag_bounds_updates;
|
||||
static std::unordered_map<struct bview *, long long> drag_update_ts;
|
||||
@@ -68,247 +74,256 @@ restore_drag_bounds_update(struct bview *v, int refresh_bounds)
|
||||
drag_update_ts.erase(v);
|
||||
}
|
||||
|
||||
// TODO - look into QShortcut, see if it might be a better way
|
||||
// to manage this
|
||||
static int bindings_loaded = 0;
|
||||
|
||||
static void qtcad_action_toggle_adc(const char *, void *, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
if (v) v->gv_s->gv_adc.draw = !v->gv_s->gv_adc.draw;
|
||||
}
|
||||
static void qtcad_action_toggle_model_axes(const char *, void *, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
if (v) v->gv_s->gv_model_axes.draw = !v->gv_s->gv_model_axes.draw;
|
||||
}
|
||||
static void qtcad_action_toggle_view_axes(const char *, void *, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
if (v) v->gv_s->gv_view_axes.draw = !v->gv_s->gv_view_axes.draw;
|
||||
}
|
||||
|
||||
static void qtcad_action_view_set(const char *, void *registered_data, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
const char *aet_str = (const char *)registered_data;
|
||||
if (v && aet_str) {
|
||||
bn_decode_vect(v->gv_aet, aet_str);
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
}
|
||||
}
|
||||
|
||||
static void qtcad_action_mouse_release(const char *event_json, void *, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
if (!v) return;
|
||||
|
||||
int cx = 0, cy = 0, mode = 0, dx = 1, dy = 1;
|
||||
char *p;
|
||||
if ((p = strstr(event_json, "\"cx\":"))) sscanf(p + 5, "%d", &cx);
|
||||
if ((p = strstr(event_json, "\"cy\":"))) sscanf(p + 5, "%d", &cy);
|
||||
if ((p = strstr(event_json, "\"mode\":"))) sscanf(p + 7, "%d", &mode);
|
||||
|
||||
int button = 0;
|
||||
if ((p = strstr(event_json, "\"button\":"))) sscanf(p + 9, "%d", &button);
|
||||
|
||||
unsigned long long view_flags = BV_IDLE;
|
||||
if (button == 0) { // Left
|
||||
if (mode != BV_CENTER) { view_flags = BV_SCALE; dx = 10; dy = 5; }
|
||||
else { view_flags = BV_CENTER; dx = cx; dy = cy; }
|
||||
} else if (button == 2) { // Right
|
||||
if (mode == BV_CENTER) return;
|
||||
view_flags = BV_SCALE; dx = 1; dy = 2;
|
||||
} else if (button == 1) { // Middle
|
||||
view_flags = BV_CENTER; dx = cx; dy = cy;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
point_t keypt = VINIT_ZERO;
|
||||
bv_adjust(v, dx, dy, keypt, 0, view_flags);
|
||||
}
|
||||
|
||||
static void qtcad_action_adjust_view(const char *event_json, void *registered_data, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
if (!v) return;
|
||||
|
||||
int dx = 0, dy = 0, mode = 0;
|
||||
int view_flags = (int)(intptr_t)registered_data;
|
||||
char *p;
|
||||
|
||||
if ((p = strstr(event_json, "\"dx\":"))) sscanf(p + 5, "%d", &dx);
|
||||
if ((p = strstr(event_json, "\"dy\":"))) sscanf(p + 5, "%d", &dy);
|
||||
if ((p = strstr(event_json, "\"mode\":"))) sscanf(p + 7, "%d", &mode);
|
||||
|
||||
if (view_flags == 0) { // dynamic fallback
|
||||
view_flags = mode;
|
||||
if (mode == BV_CENTER) view_flags = BV_SCALE;
|
||||
}
|
||||
|
||||
if (view_flags == BV_SCALE) {
|
||||
int mdelta = (abs(dx) > abs(dy)) ? dx : -dy;
|
||||
int f = (int)(2*100*(double)abs(mdelta)/(double)v->gv_height);
|
||||
if (mdelta > 0) { dy = 101 + f; dx = 100; }
|
||||
else { dy = 99 - f; dx = 100; }
|
||||
}
|
||||
|
||||
point_t center;
|
||||
MAT_DELTAS_GET_NEG(center, v->gv_center);
|
||||
|
||||
if (view_flags & (BV_ROT | BV_TRANS | BV_SCALE))
|
||||
suspend_drag_bounds_update(v);
|
||||
|
||||
bv_adjust(v, dx, dy, center, 0, view_flags);
|
||||
}
|
||||
|
||||
static void qtcad_action_wheel_scale(const char *event_json, void *, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
if (!v) return;
|
||||
|
||||
int delta = 0;
|
||||
char *p;
|
||||
if ((p = strstr(event_json, "\"delta\":"))) sscanf(p + 8, "%d", &delta);
|
||||
|
||||
int dx = 100 + delta;
|
||||
int dy = 100;
|
||||
point_t origin = VINIT_ZERO;
|
||||
bv_adjust(v, dx, dy, origin, 0, BV_SCALE);
|
||||
}
|
||||
|
||||
extern "C" QCAD_EXPORT int qtcad_load_bindings(const char *json_path) {
|
||||
if (bu_binding_load(json_path) < 0) {
|
||||
bu_log("libqtcad: Failed to load bindings from %s\n", json_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!bu_binding_action_is_bound("dm", "view_toggle_adc") ||
|
||||
!bu_binding_action_is_bound("dm", "view_2")) {
|
||||
bu_log("libqtcad Validation Failed: Provided bindings missing required standard actions.\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qtcad_init_bindings(void) {
|
||||
if (bindings_loaded) return;
|
||||
bindings_loaded = 1;
|
||||
|
||||
bu_binding_register_action("view_toggle_adc", qtcad_action_toggle_adc, NULL);
|
||||
bu_binding_register_action("view_toggle_model_axes", qtcad_action_toggle_model_axes, NULL);
|
||||
bu_binding_register_action("view_toggle_view_axes", qtcad_action_toggle_view_axes, NULL);
|
||||
|
||||
bu_binding_register_action("view_2", qtcad_action_view_set, (void*)"35 -25 0");
|
||||
bu_binding_register_action("view_3", qtcad_action_view_set, (void*)"35 25 0");
|
||||
bu_binding_register_action("view_4", qtcad_action_view_set, (void*)"45 45 0");
|
||||
bu_binding_register_action("view_5", qtcad_action_view_set, (void*)"145 25 0");
|
||||
bu_binding_register_action("view_6", qtcad_action_view_set, (void*)"215 25 0");
|
||||
bu_binding_register_action("view_7", qtcad_action_view_set, (void*)"325 25 0");
|
||||
bu_binding_register_action("view_front", qtcad_action_view_set, (void*)"0 0 0");
|
||||
bu_binding_register_action("view_top", qtcad_action_view_set, (void*)"270 90 0");
|
||||
bu_binding_register_action("view_bottom", qtcad_action_view_set, (void*)"270 -90 0");
|
||||
bu_binding_register_action("view_left", qtcad_action_view_set, (void*)"90 0 0");
|
||||
bu_binding_register_action("view_rear", qtcad_action_view_set, (void*)"180 0 0");
|
||||
bu_binding_register_action("view_right", qtcad_action_view_set, (void*)"270 0 0");
|
||||
|
||||
bu_binding_register_action("view_mouse_release_left", qtcad_action_mouse_release, NULL);
|
||||
bu_binding_register_action("view_mouse_release_right", qtcad_action_mouse_release, NULL);
|
||||
bu_binding_register_action("view_mouse_release_middle", qtcad_action_mouse_release, NULL);
|
||||
|
||||
bu_binding_register_action("view_mouse_drag_rotate", qtcad_action_adjust_view, (void*)(intptr_t)BV_ROT);
|
||||
bu_binding_register_action("view_mouse_drag_translate", qtcad_action_adjust_view, (void*)(intptr_t)BV_TRANS);
|
||||
bu_binding_register_action("view_mouse_drag_scale", qtcad_action_adjust_view, (void*)(intptr_t)BV_SCALE);
|
||||
bu_binding_register_action("view_mouse_drag_adjust", qtcad_action_adjust_view, (void*)(intptr_t)0);
|
||||
bu_binding_register_action("view_mouse_wheel_scale", qtcad_action_wheel_scale, NULL);
|
||||
}
|
||||
|
||||
static void append_modifiers(struct bu_vls *vls, QInputEvent *e) {
|
||||
if (!e->modifiers()) return;
|
||||
bu_vls_printf(vls, ", \"modifiers\": [");
|
||||
int added = 0;
|
||||
if (e->modifiers().testFlag(Qt::ShiftModifier)) { bu_vls_printf(vls, "\"Shift\""); added=1; }
|
||||
if (e->modifiers().testFlag(Qt::ControlModifier)) { if(added) bu_vls_printf(vls, ", "); bu_vls_printf(vls, "\"Control\""); added=1; }
|
||||
if (e->modifiers().testFlag(Qt::AltModifier)) { if(added) bu_vls_printf(vls, ", "); bu_vls_printf(vls, "\"Alt\""); }
|
||||
bu_vls_printf(vls, "]");
|
||||
}
|
||||
|
||||
int CADkeyPressEvent(struct bview *v, int UNUSED(x_prev), int UNUSED(y_prev), QKeyEvent *k)
|
||||
{
|
||||
QTCAD_EVENT("keyPress", 1);
|
||||
if (!v)
|
||||
return 0;
|
||||
#if 0
|
||||
QString kstr = QKeySequence(k->key()).toString();
|
||||
bu_log("%s\n", kstr.toStdString().c_str());
|
||||
#endif
|
||||
switch (k->key()) {
|
||||
case 'A':
|
||||
v->gv_s->gv_adc.draw = !v->gv_s->gv_adc.draw;
|
||||
return 1;
|
||||
case 'M':
|
||||
v->gv_s->gv_model_axes.draw = !v->gv_s->gv_model_axes.draw;
|
||||
return 1;
|
||||
case 'V':
|
||||
v->gv_s->gv_view_axes.draw = !v->gv_s->gv_view_axes.draw;
|
||||
return 1;
|
||||
case '2':
|
||||
bn_decode_vect(v->gv_aet, "35 -25 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case '3':
|
||||
bn_decode_vect(v->gv_aet, "35 25 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case '4':
|
||||
bn_decode_vect(v->gv_aet, "45 45 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case '5':
|
||||
bn_decode_vect(v->gv_aet, "145 25 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case '6':
|
||||
bn_decode_vect(v->gv_aet, "215 25 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case '7':
|
||||
bn_decode_vect(v->gv_aet, "325 25 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case 'F':
|
||||
bn_decode_vect(v->gv_aet, "0 0 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case 'T':
|
||||
bn_decode_vect(v->gv_aet, "270 90 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case 'B':
|
||||
bn_decode_vect(v->gv_aet, "270 -90 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case 'L':
|
||||
bn_decode_vect(v->gv_aet, "90 0 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case 'R':
|
||||
if (k->modifiers().testFlag(Qt::ShiftModifier) == true) {
|
||||
bn_decode_vect(v->gv_aet, "180 0 0");
|
||||
} else {
|
||||
bn_decode_vect(v->gv_aet, "270 0 0");
|
||||
}
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
default:
|
||||
break;
|
||||
if (!v) return 0;
|
||||
qtcad_init_bindings();
|
||||
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
char key_str[8] = {0};
|
||||
if (k->key() >= 0x20 && k->key() <= 0x7E) {
|
||||
key_str[0] = (char)k->key();
|
||||
} else {
|
||||
QString kstr = QKeySequence(k->key()).toString();
|
||||
strncpy(key_str, kstr.toStdString().c_str(), 7);
|
||||
}
|
||||
return 0;
|
||||
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"key-press\", \"key\": \"%s\"", key_str);
|
||||
append_modifiers(&ev_json, k);
|
||||
bu_vls_printf(&ev_json, "}");
|
||||
|
||||
int handled = bu_binding_process_event("dm", bu_vls_cstr(&ev_json), v);
|
||||
bu_vls_free(&ev_json);
|
||||
return handled;
|
||||
}
|
||||
|
||||
int CADmousePressEvent(struct bview *v, int UNUSED(x_prev), int UNUSED(y_prev), QMouseEvent *e)
|
||||
{
|
||||
QTCAD_EVENT("mousePress", 1);
|
||||
if (!v) return 0;
|
||||
qtcad_init_bindings();
|
||||
|
||||
if (!v)
|
||||
return 0;
|
||||
int button = -1;
|
||||
if (e->buttons().testFlag(Qt::LeftButton)) button = 0;
|
||||
else if (e->buttons().testFlag(Qt::MiddleButton)) button = 1;
|
||||
else if (e->buttons().testFlag(Qt::RightButton)) button = 2;
|
||||
if (button < 0) return 0;
|
||||
|
||||
// If we're intending the mouse motion to do the work,
|
||||
// then the press has to be a no-op. If we're going
|
||||
// to do configurable key bindings, this will take some
|
||||
// thought - if we want unmodded left button to be a
|
||||
// rotation, and Ctrl+Left to do something else, these
|
||||
// checks are all going to have to be exact-flag-combo-only
|
||||
// actions.
|
||||
if (e->modifiers()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (e->buttons().testFlag(Qt::LeftButton)) {
|
||||
//bu_log("Press Left\n");
|
||||
}
|
||||
if (e->buttons().testFlag(Qt::RightButton)) {
|
||||
//bu_log("Press Right\n");
|
||||
}
|
||||
if (e->buttons().testFlag(Qt::MiddleButton)) {
|
||||
//bu_log("Press Middle\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"mouse-press\", \"button\": %d", button);
|
||||
append_modifiers(&ev_json, e);
|
||||
bu_vls_printf(&ev_json, "}");
|
||||
|
||||
int handled = bu_binding_process_event("dm", bu_vls_cstr(&ev_json), v);
|
||||
bu_vls_free(&ev_json);
|
||||
return handled;
|
||||
}
|
||||
|
||||
int CADmouseReleaseEvent(struct bview *v, double x_press, double y_press, int UNUSED(x_prev), int UNUSED(y_prev), QMouseEvent *e, int mode)
|
||||
{
|
||||
QTCAD_EVENT("mouseRelease", 1);
|
||||
|
||||
if (!v)
|
||||
return 0;
|
||||
|
||||
if (!v) return 0;
|
||||
qtcad_init_bindings();
|
||||
restore_drag_bounds_update(v, 1);
|
||||
|
||||
// If we're intending the mouse motion to do the work,
|
||||
// then the release has to be a no-op. If we're going
|
||||
// to do configurable key bindings, this will take some
|
||||
// thought - if we want unmodded left button to be a
|
||||
// rotation, and Ctrl+Left to do something else, these
|
||||
// checks are all going to have to be exact-flag-combo-only
|
||||
// actions.
|
||||
if (e->modifiers()) {
|
||||
return 0;
|
||||
}
|
||||
if (e->buttons().testFlag(Qt::LeftButton) || e->buttons().testFlag(Qt::RightButton)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double cx, cy;
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
cx = (double)e->x();
|
||||
cy = (double)e->y();
|
||||
double cx = e->x();
|
||||
double cy = e->y();
|
||||
#else
|
||||
cx = e->position().x();
|
||||
cy = e->position().y();
|
||||
double cx = e->position().x();
|
||||
double cy = e->position().y();
|
||||
#endif
|
||||
if ((fabs(cx - x_press) > 10) || (fabs(cy - y_press) > 10))
|
||||
return 0;
|
||||
|
||||
int dx = 1;
|
||||
int dy = 1;
|
||||
unsigned long long view_flags = BV_IDLE;
|
||||
if ((fabs(cx - x_press) > 10) || (fabs(cy - y_press) > 10)) return 0;
|
||||
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
//bu_log("Release Left\n");
|
||||
if (mode != BV_CENTER) {
|
||||
view_flags = BV_SCALE;
|
||||
dx = 10;
|
||||
dy = 5;
|
||||
} else {
|
||||
view_flags = BV_CENTER;
|
||||
dx = (int)cx;
|
||||
dy = (int)cy;
|
||||
}
|
||||
}
|
||||
if (e->button() == Qt::RightButton) {
|
||||
//bu_log("Release Right\n");
|
||||
if (mode == BV_CENTER)
|
||||
return 0;
|
||||
view_flags = BV_SCALE;
|
||||
dx = 1;
|
||||
dy = 2;
|
||||
}
|
||||
int button = -1;
|
||||
if (e->button() == Qt::LeftButton) button = 0;
|
||||
else if (e->button() == Qt::MiddleButton) button = 1;
|
||||
else if (e->button() == Qt::RightButton) button = 2;
|
||||
if (button < 0) return 0;
|
||||
|
||||
if (e->button() == Qt::MiddleButton) {
|
||||
//bu_log("Release Center\n");
|
||||
view_flags = BV_CENTER;
|
||||
dx = (int)cx;
|
||||
dy = (int)cy;
|
||||
}
|
||||
|
||||
point_t keypt = VINIT_ZERO;
|
||||
return bv_adjust(v, dx, dy, keypt, 0, view_flags);
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"mouse-release\", \"button\": %d, \"cx\": %d, \"cy\": %d, \"mode\": %d", button, (int)cx, (int)cy, mode);
|
||||
append_modifiers(&ev_json, e);
|
||||
bu_vls_printf(&ev_json, "}");
|
||||
|
||||
int handled = bu_binding_process_event("dm", bu_vls_cstr(&ev_json), v);
|
||||
bu_vls_free(&ev_json);
|
||||
return handled;
|
||||
}
|
||||
|
||||
int CADmouseMoveEvent(struct bview *v, int x_prev, int y_prev, QMouseEvent *e, int mode)
|
||||
{
|
||||
QTCAD_EVENT("mouseMove", 2);
|
||||
if (!v) return 0;
|
||||
qtcad_init_bindings();
|
||||
|
||||
if (!v)
|
||||
return 0;
|
||||
|
||||
unsigned long long view_flags = BV_IDLE;
|
||||
|
||||
if (x_prev == -INT_MAX) {
|
||||
//x_prev = e->x();
|
||||
//y_prev = e->y();
|
||||
return 0;
|
||||
}
|
||||
|
||||
view_flags = mode;
|
||||
if (mode == BV_CENTER)
|
||||
view_flags = BV_SCALE;
|
||||
|
||||
if (e->buttons().testFlag(Qt::LeftButton)) {
|
||||
//bu_log("Left\n");
|
||||
|
||||
if (e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
//bu_log("Ctrl+Left\n");
|
||||
view_flags = BV_ROT;
|
||||
}
|
||||
|
||||
if (e->modifiers().testFlag(Qt::ShiftModifier)) {
|
||||
//bu_log("Shift+Left\n");
|
||||
view_flags = BV_TRANS;
|
||||
}
|
||||
|
||||
if (e->modifiers().testFlag(Qt::ShiftModifier) && e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
//bu_log("Ctrl+Shift+Left\n");
|
||||
view_flags = BV_SCALE;
|
||||
}
|
||||
}
|
||||
|
||||
if (e->buttons().testFlag(Qt::MiddleButton)) {
|
||||
//bu_log("Middle\n");
|
||||
}
|
||||
|
||||
if (e->buttons().testFlag(Qt::RightButton)) {
|
||||
//bu_log("Right\n");
|
||||
}
|
||||
|
||||
if (!e->buttons().testFlag(Qt::LeftButton))
|
||||
return 0;
|
||||
if (x_prev == -INT_MAX) return 0;
|
||||
if (!e->buttons().testFlag(Qt::LeftButton)) return 0;
|
||||
|
||||
long long now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
std::unordered_map<struct bview *, long long>::iterator ts_it = drag_update_ts.find(v);
|
||||
if (ts_it != drag_update_ts.end() && (now_ms - ts_it->second) < drag_update_interval_ms)
|
||||
return -1;
|
||||
if (ts_it != drag_update_ts.end() && (now_ms - ts_it->second) < drag_update_interval_ms) return -1;
|
||||
drag_update_ts[v] = now_ms;
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
@@ -319,53 +334,33 @@ int CADmouseMoveEvent(struct bview *v, int x_prev, int y_prev, QMouseEvent *e, i
|
||||
int dy = e->position().y() - y_prev;
|
||||
#endif
|
||||
|
||||
if (view_flags == BV_SCALE) {
|
||||
// Build in some sensitivity to how much the mouse moved when doing
|
||||
// a motion based scale
|
||||
int mdelta = (abs(dx) > abs(dy)) ? dx : -dy;
|
||||
int f = (int)(2*100*(double)abs(mdelta)/(double)v->gv_height);
|
||||
|
||||
if (mdelta > 0) {
|
||||
dy = 101 + f;
|
||||
dx = 100;
|
||||
} else {
|
||||
dy = 99 - f;
|
||||
dx = 100;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO - the key point and the mode/flags are all hardcoded
|
||||
// right now, but eventually for shift grips they will need to
|
||||
// respond to the various mod keys. The intent is to set flags
|
||||
// based on which mod keys are set to allow bv_adjust to
|
||||
// do the correct math.
|
||||
point_t center;
|
||||
MAT_DELTAS_GET_NEG(center, v->gv_center);
|
||||
|
||||
if (view_flags & (BV_ROT | BV_TRANS | BV_SCALE))
|
||||
suspend_drag_bounds_update(v);
|
||||
|
||||
return bv_adjust(v, dx, dy, center, 0, view_flags);
|
||||
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"mouse-move\", \"button\": 0, \"dx\": %d, \"dy\": %d, \"mode\": %d", dx, dy, mode);
|
||||
append_modifiers(&ev_json, e);
|
||||
bu_vls_printf(&ev_json, "}");
|
||||
|
||||
int handled = bu_binding_process_event("dm", bu_vls_cstr(&ev_json), v);
|
||||
bu_vls_free(&ev_json);
|
||||
return handled;
|
||||
}
|
||||
|
||||
int CADwheelEvent(struct bview *v, QWheelEvent *e)
|
||||
{
|
||||
QTCAD_EVENT("mouseWheel", 1);
|
||||
|
||||
if (!v)
|
||||
return 0;
|
||||
if (!v) return 0;
|
||||
qtcad_init_bindings();
|
||||
|
||||
QPoint delta = e->angleDelta();
|
||||
int mdelta = -1* delta.y() / 8;
|
||||
|
||||
int dx = 100 + mdelta;
|
||||
int dy = 100;
|
||||
|
||||
point_t origin = VINIT_ZERO;
|
||||
return bv_adjust(v, dx, dy, origin, 0, BV_SCALE);
|
||||
int mdelta = -1 * delta.y() / 8;
|
||||
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"mouse-wheel\", \"delta\": %d", mdelta);
|
||||
append_modifiers(&ev_json, e);
|
||||
bu_vls_printf(&ev_json, "}");
|
||||
|
||||
int handled = bu_binding_process_event("dm", bu_vls_cstr(&ev_json), v);
|
||||
bu_vls_free(&ev_json);
|
||||
return handled;
|
||||
}
|
||||
|
||||
// Local Variables:
|
||||
|
||||
@@ -427,6 +427,7 @@ endif(BRLCAD_ENABLE_SPR)
|
||||
set(LIBRT_PUBLIC_LIBS ${librt_deps} OPENNURBS::OPENNURBS)
|
||||
set(
|
||||
LIBRT_PRIVATE_LIBS
|
||||
Threads::Threads
|
||||
${OPENCL_LIBS}
|
||||
mmesh::mmesh
|
||||
REGEX::REGEX
|
||||
|
||||
+469
-203
@@ -45,24 +45,28 @@
|
||||
* intersecting closed surface), and total_chord is the sum of solid
|
||||
* segment lengths.
|
||||
*
|
||||
* References:
|
||||
* Li et al. (2003), "Using low-discrepancy sequences and the Crofton
|
||||
* formula to compute surface areas of geometric models",
|
||||
* Computer-Aided Design 35, 771-782.
|
||||
*
|
||||
* Liu et al. (2010), "Surface area estimation of digitized 3D objects
|
||||
* using quasi-Monte Carlo methods", Pattern Recognition 43, 3900-3909.
|
||||
* Chord endpoints are independent pseudorandom points on the bounding
|
||||
* sphere. Reinitializing the generator with a fixed seed makes sampling
|
||||
* reproducible without changing the ordinary Monte Carlo convergence
|
||||
* behavior on which the estimator's stability checks rely.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <string.h>
|
||||
#include <thread>
|
||||
#include <time.h>
|
||||
#include <vector>
|
||||
|
||||
#include "vmath.h"
|
||||
#include "bu/log.h"
|
||||
@@ -100,8 +104,15 @@
|
||||
#define RT_CROFTON_STABILITY_MIN_WINDOWS 2u
|
||||
|
||||
/** Fixed seed for reproducible estimates in tests and cross-platform runs. */
|
||||
#define RT_CROFTON_RNG_SEED 0x9e3779b97f4a7c15ULL
|
||||
#define RT_CROFTON_RNG_SEED UINT64_C(0x9e3779b97f4a7c15)
|
||||
|
||||
/** Two sphere coordinates for each of two chord endpoints. */
|
||||
#define RT_CROFTON_RANDOM_VALUES_PER_RAY 4u
|
||||
|
||||
/** Convert the high 53 random bits exactly to a double in [0, 1). */
|
||||
static constexpr unsigned int CROFTON_RANDOM_SHIFT = 11u;
|
||||
static constexpr double CROFTON_RANDOM_SCALE =
|
||||
1.0 / 9007199254740992.0;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Internal types */
|
||||
@@ -110,6 +121,7 @@
|
||||
struct crofton_ray {
|
||||
point_t r_pt;
|
||||
vect_t r_dir;
|
||||
size_t id;
|
||||
};
|
||||
|
||||
struct crofton_shared {
|
||||
@@ -117,14 +129,19 @@ struct crofton_shared {
|
||||
size_t total_crossings; /* in+out hit events */
|
||||
double total_chord; /* solid segment length sum (mm) */
|
||||
size_t total_rays; /* rays fired (hits + misses) */
|
||||
size_t ray_offset; /* first identifier in this sample stream */
|
||||
point_t *points;
|
||||
size_t point_count;
|
||||
size_t point_capacity;
|
||||
struct rt_crofton_segment *segments;
|
||||
size_t segment_count;
|
||||
size_t segment_capacity;
|
||||
bool collect_points;
|
||||
bool collect_segments;
|
||||
};
|
||||
|
||||
struct crofton_worker_data {
|
||||
struct application *ap; /* per-CPU application struct */
|
||||
struct application ap; /* per-CPU application struct */
|
||||
struct crofton_ray *rays; /* shared ray array (read-only) */
|
||||
size_t start;
|
||||
size_t end;
|
||||
@@ -135,6 +152,10 @@ struct crofton_worker_data {
|
||||
point_t *local_points;
|
||||
size_t local_point_count;
|
||||
size_t local_point_capacity;
|
||||
struct rt_crofton_segment *local_segments;
|
||||
size_t local_segment_count;
|
||||
size_t local_segment_capacity;
|
||||
size_t current_ray;
|
||||
};
|
||||
|
||||
|
||||
@@ -143,46 +164,88 @@ struct crofton_worker_data {
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int
|
||||
crofton_hit(struct application *ap, struct partition *PartHeadp, struct seg *UNUSED(segs))
|
||||
crofton_hit(struct application *ap, struct partition *PartHeadp,
|
||||
struct seg *UNUSED(segs))
|
||||
{
|
||||
struct crofton_worker_data *wd = (struct crofton_worker_data *)ap->a_uptr;
|
||||
|
||||
struct crofton_worker_data *worker =
|
||||
(struct crofton_worker_data *)ap->a_uptr;
|
||||
size_t crossings = 0;
|
||||
double chord = 0.0;
|
||||
double chord = 0.0;
|
||||
|
||||
struct partition *pp;
|
||||
for (pp = PartHeadp->pt_forw; pp != PartHeadp; pp = pp->pt_forw) {
|
||||
/* Each partition contributes an in-hit and an out-hit */
|
||||
crossings += 2;
|
||||
chord += pp->pt_outhit->hit_dist - pp->pt_inhit->hit_dist;
|
||||
if (wd->shared->collect_points) {
|
||||
if (wd->local_point_count + 2 > wd->local_point_capacity) {
|
||||
const size_t initial_capacity = 1024;
|
||||
size_t new_capacity = wd->local_point_capacity ?
|
||||
2 * wd->local_point_capacity : initial_capacity;
|
||||
while (new_capacity < wd->local_point_count + 2)
|
||||
new_capacity *= 2;
|
||||
wd->local_points = (point_t *)bu_realloc(wd->local_points,
|
||||
new_capacity * sizeof(point_t), "Crofton worker hit points");
|
||||
wd->local_point_capacity = new_capacity;
|
||||
}
|
||||
VJOIN1(wd->local_points[wd->local_point_count], ap->a_ray.r_pt,
|
||||
pp->pt_inhit->hit_dist, ap->a_ray.r_dir);
|
||||
wd->local_point_count++;
|
||||
VJOIN1(wd->local_points[wd->local_point_count], ap->a_ray.r_pt,
|
||||
pp->pt_outhit->hit_dist, ap->a_ray.r_dir);
|
||||
wd->local_point_count++;
|
||||
}
|
||||
struct partition *partition;
|
||||
for (partition = PartHeadp->pt_forw; partition != PartHeadp;
|
||||
partition = partition->pt_forw) {
|
||||
fastf_t thickness = partition->pt_outhit->hit_dist -
|
||||
partition->pt_inhit->hit_dist;
|
||||
crossings += 2;
|
||||
chord += thickness;
|
||||
|
||||
if (worker->shared->collect_points) {
|
||||
if (worker->local_point_count + 2 >
|
||||
worker->local_point_capacity) {
|
||||
const size_t initial_capacity = 1024;
|
||||
size_t new_capacity = worker->local_point_capacity ?
|
||||
2 * worker->local_point_capacity : initial_capacity;
|
||||
while (new_capacity < worker->local_point_count + 2)
|
||||
new_capacity *= 2;
|
||||
worker->local_points = (point_t *)bu_realloc(
|
||||
worker->local_points,
|
||||
new_capacity * sizeof(point_t),
|
||||
"Crofton worker hit points");
|
||||
worker->local_point_capacity = new_capacity;
|
||||
}
|
||||
VJOIN1(worker->local_points[worker->local_point_count],
|
||||
ap->a_ray.r_pt, partition->pt_inhit->hit_dist,
|
||||
ap->a_ray.r_dir);
|
||||
worker->local_point_count++;
|
||||
VJOIN1(worker->local_points[worker->local_point_count],
|
||||
ap->a_ray.r_pt, partition->pt_outhit->hit_dist,
|
||||
ap->a_ray.r_dir);
|
||||
worker->local_point_count++;
|
||||
}
|
||||
|
||||
if (worker->shared->collect_segments) {
|
||||
if (worker->local_segment_count + 1 >
|
||||
worker->local_segment_capacity) {
|
||||
const size_t initial_capacity = 512;
|
||||
size_t new_capacity = worker->local_segment_capacity ?
|
||||
2 * worker->local_segment_capacity : initial_capacity;
|
||||
worker->local_segments =
|
||||
(struct rt_crofton_segment *)bu_realloc(
|
||||
worker->local_segments,
|
||||
new_capacity * sizeof(struct rt_crofton_segment),
|
||||
"Crofton worker segments");
|
||||
worker->local_segment_capacity = new_capacity;
|
||||
}
|
||||
|
||||
struct rt_crofton_segment *sample =
|
||||
&worker->local_segments[worker->local_segment_count];
|
||||
struct soltab *in_solid =
|
||||
partition->pt_inseg->seg_stp;
|
||||
struct soltab *out_solid =
|
||||
partition->pt_outseg->seg_stp;
|
||||
VJOIN1(sample->in_point, ap->a_ray.r_pt,
|
||||
partition->pt_inhit->hit_dist, ap->a_ray.r_dir);
|
||||
VJOIN1(sample->out_point, ap->a_ray.r_pt,
|
||||
partition->pt_outhit->hit_dist, ap->a_ray.r_dir);
|
||||
RT_HIT_NORMAL(sample->in_normal, partition->pt_inhit,
|
||||
in_solid, &ap->a_ray, partition->pt_inflip);
|
||||
RT_HIT_NORMAL(sample->out_normal, partition->pt_outhit,
|
||||
out_solid, &ap->a_ray, partition->pt_outflip);
|
||||
sample->thickness = thickness;
|
||||
sample->ray_id = worker->rays[worker->current_ray].id;
|
||||
if (!VNEAR_ZERO(sample->in_normal, VUNITIZE_TOL) &&
|
||||
!VNEAR_ZERO(sample->out_normal, VUNITIZE_TOL))
|
||||
worker->local_segment_count++;
|
||||
}
|
||||
}
|
||||
|
||||
wd->local_crossings += crossings;
|
||||
wd->local_chord += chord;
|
||||
wd->local_rays += 1;
|
||||
|
||||
worker->local_crossings += crossings;
|
||||
worker->local_chord += chord;
|
||||
worker->local_rays++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
crofton_miss(struct application *ap)
|
||||
{
|
||||
@@ -193,159 +256,273 @@ crofton_miss(struct application *ap)
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Parallel worker */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void
|
||||
crofton_worker(int id, void *data)
|
||||
{
|
||||
struct crofton_worker_data *wd = &((struct crofton_worker_data *)data)[id - 1];
|
||||
struct application *ap = wd->ap;
|
||||
struct crofton_ray *rays = wd->rays;
|
||||
|
||||
for (size_t i = wd->start; i < wd->end; i++) {
|
||||
VMOVE(ap->a_ray.r_pt, rays[i].r_pt);
|
||||
VMOVE(ap->a_ray.r_dir, rays[i].r_dir);
|
||||
/* r_dir is already unit-length (set during ray generation) */
|
||||
rt_shootray(ap);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Point / ray generation */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static double
|
||||
crofton_rand01(std::mt19937_64 &rng)
|
||||
crofton_random01(std::mt19937_64 &rng)
|
||||
{
|
||||
return std::generate_canonical<double, 53>(rng);
|
||||
return static_cast<double>(rng() >> CROFTON_RANDOM_SHIFT) *
|
||||
CROFTON_RANDOM_SCALE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
random_point_on_sphere(double radius, const point_t center, point_t out, std::mt19937_64 &rng)
|
||||
random_point_on_sphere(double radius, const point_t center, double u,
|
||||
double v, point_t point)
|
||||
{
|
||||
double theta = 2.0 * M_PI * crofton_rand01(rng);
|
||||
double phi = acos(2.0 * crofton_rand01(rng) - 1.0);
|
||||
double sp = sin(phi);
|
||||
out[X] = center[X] + radius * sp * cos(theta);
|
||||
out[Y] = center[Y] + radius * sp * sin(theta);
|
||||
out[Z] = center[Z] + radius * cos(phi);
|
||||
double z = 1.0 - 2.0 * u;
|
||||
double radial = sqrt(std::max(0.0, 1.0 - z * z));
|
||||
double angle = 2.0 * M_PI * v;
|
||||
point[X] = center[X] + radius * radial * cos(angle);
|
||||
point[Y] = center[Y] + radius * radial * sin(angle);
|
||||
point[Z] = center[Z] + radius * z;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate @p nrays chord rays from 2*nrays random points on the
|
||||
* bounding sphere. Each ray connects one randomly chosen point to
|
||||
* another.
|
||||
* Generate independent random chord endpoints. Pairing consecutive IID
|
||||
* sphere points has the same distribution as shuffling a point array before
|
||||
* pairing, but consumes exactly four random values per ray. That fixed
|
||||
* stride lets callers continue the deterministic stream with ray_offset.
|
||||
*/
|
||||
static void
|
||||
generate_rays(struct crofton_ray *rays, size_t nrays,
|
||||
double radius, const point_t center, std::mt19937_64 &rng)
|
||||
generate_rays(struct crofton_ray *rays, size_t ray_count, size_t first_id,
|
||||
double radius, const point_t center, std::mt19937_64 &rng)
|
||||
{
|
||||
size_t npts = nrays * 2;
|
||||
|
||||
point_t *pts = (point_t *)bu_calloc(npts, sizeof(point_t), "crofton pts");
|
||||
for (size_t i = 0; i < npts; i++)
|
||||
random_point_on_sphere(radius, center, pts[i], rng);
|
||||
|
||||
/* Shuffle so pairing is random */
|
||||
for (size_t i = npts - 1; i > 0; i--) {
|
||||
size_t j = (size_t)(crofton_rand01(rng) * (i + 1));
|
||||
if (j > i) j = i;
|
||||
point_t tmp;
|
||||
VMOVE(tmp, pts[i]);
|
||||
VMOVE(pts[i], pts[j]);
|
||||
VMOVE(pts[j], tmp);
|
||||
for (size_t i = 0; i < ray_count; i++) {
|
||||
point_t endpoint;
|
||||
random_point_on_sphere(radius, center, crofton_random01(rng),
|
||||
crofton_random01(rng), rays[i].r_pt);
|
||||
random_point_on_sphere(radius, center, crofton_random01(rng),
|
||||
crofton_random01(rng), endpoint);
|
||||
VSUB2(rays[i].r_dir, endpoint, rays[i].r_pt);
|
||||
VUNITIZE(rays[i].r_dir);
|
||||
rays[i].id = first_id + i;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < nrays; i++) {
|
||||
VMOVE(rays[i].r_pt, pts[i * 2]);
|
||||
VSUB2(rays[i].r_dir, pts[i * 2 + 1], pts[i * 2]);
|
||||
VUNITIZE(rays[i].r_dir);
|
||||
}
|
||||
|
||||
bu_free(pts, "crofton pts");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Reusable parallel workers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
class CroftonWorkerPool
|
||||
{
|
||||
public:
|
||||
CroftonWorkerPool(const struct application *ap_template,
|
||||
struct resource *resources, size_t worker_count) :
|
||||
count(worker_count), data(worker_count)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
data[i].ap = *ap_template;
|
||||
data[i].ap.a_resource = &resources[i];
|
||||
data[i].shared = NULL;
|
||||
data[i].ap.a_uptr = &data[i];
|
||||
}
|
||||
|
||||
try {
|
||||
executor = std::thread(&CroftonWorkerPool::run_workers, this);
|
||||
} catch (...) {
|
||||
free_worker_points();
|
||||
throw;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
if (!workers_started.wait_for(lock, worker_start_timeout,
|
||||
[this]() { return registered == count; })) {
|
||||
stopping = true;
|
||||
work_ready.notify_all();
|
||||
lock.unlock();
|
||||
executor.join();
|
||||
free_worker_points();
|
||||
throw std::runtime_error("worker startup timed out");
|
||||
}
|
||||
}
|
||||
|
||||
~CroftonWorkerPool()
|
||||
{
|
||||
stop_and_join();
|
||||
free_worker_points();
|
||||
}
|
||||
|
||||
CroftonWorkerPool(const CroftonWorkerPool &) = delete;
|
||||
CroftonWorkerPool &operator=(const CroftonWorkerPool &) = delete;
|
||||
|
||||
void run(struct crofton_ray *rays, size_t nrays,
|
||||
struct crofton_shared *shared)
|
||||
{
|
||||
const size_t rays_per_worker = nrays / count;
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
struct crofton_worker_data &wd = data[i];
|
||||
wd.rays = rays;
|
||||
wd.start = i * rays_per_worker;
|
||||
wd.end = (i == count - 1) ? nrays : (i + 1) * rays_per_worker;
|
||||
wd.shared = shared;
|
||||
wd.local_crossings = 0;
|
||||
wd.local_chord = 0.0;
|
||||
wd.local_rays = 0;
|
||||
wd.local_point_count = 0;
|
||||
wd.local_segment_count = 0;
|
||||
}
|
||||
completed = 0;
|
||||
generation++;
|
||||
work_ready.notify_all();
|
||||
work_done.wait(lock, [this]() { return completed == count; });
|
||||
}
|
||||
|
||||
const std::vector<struct crofton_worker_data> &worker_data() const
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
private:
|
||||
/* Native thread creation should complete promptly. A bounded startup wait
|
||||
* turns partial bu_parallel startup into a reported failure, not a hang. */
|
||||
static constexpr std::chrono::seconds worker_start_timeout{5};
|
||||
|
||||
static void worker_entry(int UNUSED(id), void *context)
|
||||
{
|
||||
CroftonWorkerPool *pool = static_cast<CroftonWorkerPool *>(context);
|
||||
size_t slot = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(pool->mutex);
|
||||
slot = pool->registered++;
|
||||
if (pool->registered == pool->count)
|
||||
pool->workers_started.notify_one();
|
||||
}
|
||||
pool->worker(slot);
|
||||
}
|
||||
|
||||
void run_workers()
|
||||
{
|
||||
bu_parallel(worker_entry, count, this);
|
||||
}
|
||||
|
||||
void worker(size_t slot)
|
||||
{
|
||||
size_t observed_generation = 0;
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
while (true) {
|
||||
work_ready.wait(lock, [this, observed_generation]() {
|
||||
return stopping || generation != observed_generation;
|
||||
});
|
||||
if (stopping)
|
||||
return;
|
||||
|
||||
observed_generation = generation;
|
||||
struct crofton_worker_data *wd = &data[slot];
|
||||
lock.unlock();
|
||||
for (size_t i = wd->start; i < wd->end; i++) {
|
||||
wd->current_ray = i;
|
||||
VMOVE(wd->ap.a_ray.r_pt, wd->rays[i].r_pt);
|
||||
VMOVE(wd->ap.a_ray.r_dir, wd->rays[i].r_dir);
|
||||
rt_shootray(&wd->ap);
|
||||
}
|
||||
lock.lock();
|
||||
completed++;
|
||||
if (completed == count)
|
||||
work_done.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void stop_and_join()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
stopping = true;
|
||||
work_ready.notify_all();
|
||||
}
|
||||
if (executor.joinable())
|
||||
executor.join();
|
||||
}
|
||||
|
||||
void free_worker_points()
|
||||
{
|
||||
for (struct crofton_worker_data &wd : data) {
|
||||
if (wd.local_points)
|
||||
bu_free(wd.local_points, "Crofton worker hit points");
|
||||
if (wd.local_segments)
|
||||
bu_free(wd.local_segments, "Crofton worker segments");
|
||||
wd.local_points = NULL;
|
||||
wd.local_segments = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
size_t count;
|
||||
std::vector<struct crofton_worker_data> data;
|
||||
std::thread executor;
|
||||
std::mutex mutex;
|
||||
std::condition_variable workers_started;
|
||||
std::condition_variable work_ready;
|
||||
std::condition_variable work_done;
|
||||
size_t registered = 0;
|
||||
size_t generation = 0;
|
||||
size_t completed = 0;
|
||||
bool stopping = false;
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* One iteration: fire nrays, accumulate into shared */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void
|
||||
do_one_iteration(struct application *ap_template,
|
||||
struct resource *resources,
|
||||
size_t nrays,
|
||||
double radius,
|
||||
const point_t center,
|
||||
struct crofton_shared *shared,
|
||||
std::mt19937_64 &rng)
|
||||
do_one_iteration(CroftonWorkerPool &pool, size_t ray_count, double radius,
|
||||
const point_t center, struct crofton_shared *shared,
|
||||
std::mt19937_64 &rng)
|
||||
{
|
||||
struct crofton_ray *rays = (struct crofton_ray *)bu_calloc(
|
||||
nrays, sizeof(struct crofton_ray), "crofton rays");
|
||||
ray_count, sizeof(struct crofton_ray), "Crofton rays");
|
||||
generate_rays(rays, ray_count, shared->ray_offset + shared->total_rays,
|
||||
radius, center, rng);
|
||||
pool.run(rays, ray_count, shared);
|
||||
|
||||
generate_rays(rays, nrays, radius, center, rng);
|
||||
for (const struct crofton_worker_data &worker : pool.worker_data()) {
|
||||
shared->total_crossings += worker.local_crossings;
|
||||
shared->total_chord += worker.local_chord;
|
||||
shared->total_rays += worker.local_rays;
|
||||
|
||||
size_t ncpus = bu_avail_cpus();
|
||||
if (ncpus < 1) ncpus = 1;
|
||||
if (ncpus > MAX_PSW) ncpus = MAX_PSW;
|
||||
if (worker.local_point_count) {
|
||||
const size_t needed =
|
||||
shared->point_count + worker.local_point_count;
|
||||
if (needed > shared->point_capacity) {
|
||||
size_t new_capacity = shared->point_capacity ?
|
||||
2 * shared->point_capacity : needed;
|
||||
if (new_capacity < needed)
|
||||
new_capacity = needed;
|
||||
shared->points = (point_t *)bu_realloc(shared->points,
|
||||
new_capacity * sizeof(point_t),
|
||||
"Crofton hit points");
|
||||
shared->point_capacity = new_capacity;
|
||||
}
|
||||
memcpy(&shared->points[shared->point_count],
|
||||
worker.local_points,
|
||||
worker.local_point_count * sizeof(point_t));
|
||||
shared->point_count = needed;
|
||||
}
|
||||
|
||||
struct crofton_worker_data *wdata = (struct crofton_worker_data *)bu_calloc(
|
||||
ncpus, sizeof(struct crofton_worker_data), "crofton wdata");
|
||||
|
||||
size_t per_cpu = nrays / ncpus;
|
||||
|
||||
for (size_t i = 0; i < ncpus; i++) {
|
||||
struct application *a = (struct application *)bu_calloc(
|
||||
1, sizeof(struct application), "crofton app");
|
||||
*a = *ap_template; /* struct copy */
|
||||
a->a_resource = &resources[i];
|
||||
|
||||
struct crofton_worker_data *wd = &wdata[i];
|
||||
wd->ap = a;
|
||||
wd->rays = rays;
|
||||
wd->start = i * per_cpu;
|
||||
wd->end = (i == ncpus - 1) ? nrays : (i + 1) * per_cpu;
|
||||
wd->shared = shared;
|
||||
a->a_uptr = wd;
|
||||
if (worker.local_segment_count) {
|
||||
const size_t needed =
|
||||
shared->segment_count + worker.local_segment_count;
|
||||
if (needed > shared->segment_capacity) {
|
||||
size_t new_capacity = shared->segment_capacity ?
|
||||
2 * shared->segment_capacity : needed;
|
||||
if (new_capacity < needed)
|
||||
new_capacity = needed;
|
||||
shared->segments =
|
||||
(struct rt_crofton_segment *)bu_realloc(
|
||||
shared->segments,
|
||||
new_capacity * sizeof(struct rt_crofton_segment),
|
||||
"Crofton segments");
|
||||
shared->segment_capacity = new_capacity;
|
||||
}
|
||||
memcpy(&shared->segments[shared->segment_count],
|
||||
worker.local_segments,
|
||||
worker.local_segment_count *
|
||||
sizeof(struct rt_crofton_segment));
|
||||
shared->segment_count = needed;
|
||||
}
|
||||
}
|
||||
|
||||
bu_parallel(crofton_worker, (int)ncpus, (void *)wdata);
|
||||
|
||||
for (size_t i = 0; i < ncpus; i++) {
|
||||
shared->total_crossings += wdata[i].local_crossings;
|
||||
shared->total_chord += wdata[i].local_chord;
|
||||
shared->total_rays += wdata[i].local_rays;
|
||||
if (wdata[i].local_point_count) {
|
||||
const size_t needed = shared->point_count + wdata[i].local_point_count;
|
||||
if (needed > shared->point_capacity) {
|
||||
size_t new_capacity = shared->point_capacity ?
|
||||
2 * shared->point_capacity : needed;
|
||||
if (new_capacity < needed)
|
||||
new_capacity = needed;
|
||||
shared->points = (point_t *)bu_realloc(shared->points,
|
||||
new_capacity * sizeof(point_t), "Crofton hit points");
|
||||
shared->point_capacity = new_capacity;
|
||||
}
|
||||
memcpy(&shared->points[shared->point_count], wdata[i].local_points,
|
||||
wdata[i].local_point_count * sizeof(point_t));
|
||||
shared->point_count = needed;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ncpus; i++) {
|
||||
if (wdata[i].local_points)
|
||||
bu_free(wdata[i].local_points, "Crofton worker hit points");
|
||||
bu_free(wdata[i].ap, "crofton app");
|
||||
}
|
||||
bu_free(wdata, "crofton wdata");
|
||||
bu_free(rays, "crofton rays");
|
||||
bu_free(rays, "Crofton rays");
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Public API: rt_crofton_shoot */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -375,7 +552,8 @@ do_one_iteration(struct application *ap_template,
|
||||
* sampling (>= 0) on success; -1 on bad arguments.
|
||||
*/
|
||||
static int
|
||||
crofton_shoot_impl(point_t **out_points,
|
||||
crofton_shoot_impl(struct rt_crofton_result *out_result,
|
||||
point_t **out_points,
|
||||
size_t *out_point_count,
|
||||
double *out_surf_area,
|
||||
double *out_volume,
|
||||
@@ -384,13 +562,22 @@ crofton_shoot_impl(point_t **out_points,
|
||||
point_t out_obb[8],
|
||||
struct rt_i *rtip,
|
||||
const struct rt_crofton_params *params,
|
||||
size_t ray_offset,
|
||||
const fastf_t *bbox_min,
|
||||
const fastf_t *bbox_max)
|
||||
{
|
||||
if (!rtip || (!out_surf_area && !out_volume && !out_aabb_min && !out_obb && !out_points) ||
|
||||
((out_points == NULL) != (out_point_count == NULL)) ||
|
||||
((out_aabb_min == NULL) != (out_aabb_max == NULL)))
|
||||
return -1;
|
||||
if (!rtip ||
|
||||
(!out_result && !out_surf_area && !out_volume && !out_aabb_min &&
|
||||
!out_obb && !out_points) ||
|
||||
((out_points == NULL) != (out_point_count == NULL)) ||
|
||||
((out_aabb_min == NULL) != (out_aabb_max == NULL)) ||
|
||||
(out_result && out_result->segments) ||
|
||||
ray_offset > UINT64_MAX / RT_CROFTON_RANDOM_VALUES_PER_RAY)
|
||||
return -1;
|
||||
if (out_result) {
|
||||
struct rt_crofton_result empty = RT_CROFTON_RESULT_INIT;
|
||||
*out_result = empty;
|
||||
}
|
||||
if (out_points) {
|
||||
*out_points = NULL;
|
||||
*out_point_count = 0;
|
||||
@@ -473,10 +660,14 @@ crofton_shoot_impl(point_t **out_points,
|
||||
if (!use_default && max_rays > 0 && stability_mm <= 0.0 && time_ms <= 0.0)
|
||||
batch = max_rays; /* single-iteration mode */
|
||||
|
||||
size_t ncpus = bu_avail_cpus();
|
||||
if (ncpus < 1) ncpus = 1;
|
||||
if (ncpus > MAX_PSW) ncpus = MAX_PSW;
|
||||
|
||||
/* ---- Initialize per-CPU resources ---- */
|
||||
struct resource *resources = (struct resource *)bu_calloc(
|
||||
MAX_PSW, sizeof(struct resource), "crofton resources");
|
||||
for (int i = 0; i < MAX_PSW; i++)
|
||||
ncpus, sizeof(struct resource), "crofton resources");
|
||||
for (size_t i = 0; i < ncpus; i++)
|
||||
rt_init_resource(&resources[i], i, rtip);
|
||||
|
||||
/* ---- Set up application template ---- */
|
||||
@@ -494,10 +685,32 @@ crofton_shoot_impl(point_t **out_points,
|
||||
/* ---- Shared accumulator ---- */
|
||||
struct crofton_shared shared;
|
||||
memset(&shared, 0, sizeof(shared));
|
||||
shared.collect_points = out_points != NULL || out_aabb_min != NULL || out_obb != NULL;
|
||||
const bool stabilize_surface = out_surf_area != NULL || shared.collect_points;
|
||||
shared.ray_offset = ray_offset;
|
||||
shared.collect_points =
|
||||
out_points != NULL || out_aabb_min != NULL || out_obb != NULL;
|
||||
shared.collect_segments = out_result != NULL;
|
||||
const bool stabilize_surface =
|
||||
out_surf_area != NULL || out_result != NULL ||
|
||||
shared.collect_points;
|
||||
|
||||
CroftonWorkerPool *pool = NULL;
|
||||
try {
|
||||
pool = new CroftonWorkerPool(&ap, resources, ncpus);
|
||||
} catch (const std::exception &e) {
|
||||
bu_log("rt_crofton: unable to start worker pool: %s\n", e.what());
|
||||
for (size_t i = 0; i < ncpus; i++) {
|
||||
if (resources[i].re_magic == RESOURCE_MAGIC) {
|
||||
rt_clean_resource_basic(rtip, &resources[i]);
|
||||
BU_PTBL_SET(&rtip->rti_resources, i, NULL);
|
||||
}
|
||||
}
|
||||
bu_free(resources, "crofton resources");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::mt19937_64 rng(RT_CROFTON_RNG_SEED);
|
||||
rng.discard(static_cast<uint64_t>(ray_offset) *
|
||||
RT_CROFTON_RANDOM_VALUES_PER_RAY);
|
||||
|
||||
const double FOUR_PI = 4.0 * M_PI;
|
||||
const double PI = M_PI;
|
||||
@@ -521,7 +734,7 @@ crofton_shoot_impl(point_t **out_points,
|
||||
curr_rays = batch;
|
||||
}
|
||||
|
||||
do_one_iteration(&ap, resources, curr_rays, R, center, &shared, rng);
|
||||
do_one_iteration(*pool, curr_rays, R, center, &shared, rng);
|
||||
iteration++;
|
||||
|
||||
if (shared.total_rays == 0) break;
|
||||
@@ -562,9 +775,12 @@ crofton_shoot_impl(point_t **out_points,
|
||||
} else {
|
||||
/* ---- Parametric loop: n_rays / stability_mm / time_ms ---- */
|
||||
double prev_r_sa = -1.0, prev_r_v = -1.0;
|
||||
double prev_sa = -1.0, prev_v = -1.0;
|
||||
size_t total_fired = 0;
|
||||
size_t stable_windows = 0;
|
||||
int64_t t0 = (time_ms > 0.0) ? bu_gettime() : 0;
|
||||
const bool use_relative_stability = out_result && time_ms > 0.0 &&
|
||||
stability_mm <= 0.0;
|
||||
|
||||
for (;;) {
|
||||
/* Time-budget check before firing */
|
||||
@@ -581,7 +797,7 @@ crofton_shoot_impl(point_t **out_points,
|
||||
if (fire > remaining) fire = remaining;
|
||||
}
|
||||
|
||||
do_one_iteration(&ap, resources, fire, R, center, &shared, rng);
|
||||
do_one_iteration(*pool, fire, R, center, &shared, rng);
|
||||
total_fired += fire;
|
||||
|
||||
if (shared.total_rays == 0) break;
|
||||
@@ -594,27 +810,39 @@ crofton_shoot_impl(point_t **out_points,
|
||||
/ (double)shared.total_rays;
|
||||
|
||||
/* Stability check */
|
||||
if (stability_mm > 0.0 &&
|
||||
if ((stability_mm > 0.0 || use_relative_stability) &&
|
||||
shared.total_rays >= RT_CROFTON_STABILITY_MIN_RAYS &&
|
||||
shared.total_crossings >= RT_CROFTON_STABILITY_MIN_CROSSINGS) {
|
||||
double r_sa = (curr_est_sa > 0.0) ? sqrt(curr_est_sa * INV_4PI) : 0.0;
|
||||
double r_v = (curr_est_v > 0.0) ? cbrt(curr_est_v * INV_4PI3) : 0.0;
|
||||
|
||||
if (prev_r_sa >= 0.0) {
|
||||
int sa_ok = (!stabilize_surface) ||
|
||||
fabs(r_sa - prev_r_sa) < stability_mm;
|
||||
int v_ok = (!out_volume) ||
|
||||
fabs(r_v - prev_r_v) < stability_mm;
|
||||
if (sa_ok && v_ok) {
|
||||
stable_windows++;
|
||||
if (stable_windows >= RT_CROFTON_STABILITY_MIN_WINDOWS)
|
||||
break;
|
||||
} else {
|
||||
stable_windows = 0;
|
||||
}
|
||||
bool sa_ok = false;
|
||||
bool v_ok = false;
|
||||
if (use_relative_stability) {
|
||||
sa_ok = !stabilize_surface || (prev_sa > 0.0 &&
|
||||
fabs(curr_est_sa - prev_sa) / prev_sa * 100.0 <=
|
||||
RT_CROFTON_DEFAULT_THRESHOLD);
|
||||
v_ok = !out_volume || (prev_v > 0.0 &&
|
||||
fabs(curr_est_v - prev_v) / prev_v * 100.0 <=
|
||||
RT_CROFTON_DEFAULT_THRESHOLD);
|
||||
prev_sa = curr_est_sa;
|
||||
prev_v = curr_est_v;
|
||||
} else {
|
||||
double r_sa = (curr_est_sa > 0.0) ?
|
||||
sqrt(curr_est_sa * INV_4PI) : 0.0;
|
||||
double r_v = (curr_est_v > 0.0) ?
|
||||
cbrt(curr_est_v * INV_4PI3) : 0.0;
|
||||
sa_ok = !stabilize_surface || (prev_r_sa >= 0.0 &&
|
||||
fabs(r_sa - prev_r_sa) < stability_mm);
|
||||
v_ok = !out_volume || (prev_r_v >= 0.0 &&
|
||||
fabs(r_v - prev_r_v) < stability_mm);
|
||||
prev_r_sa = r_sa;
|
||||
prev_r_v = r_v;
|
||||
}
|
||||
if (sa_ok && v_ok) {
|
||||
stable_windows++;
|
||||
if (stable_windows >= RT_CROFTON_STABILITY_MIN_WINDOWS)
|
||||
break;
|
||||
} else {
|
||||
stable_windows = 0;
|
||||
}
|
||||
prev_r_sa = r_sa;
|
||||
prev_r_v = r_v;
|
||||
}
|
||||
|
||||
/* Time-budget check after firing */
|
||||
@@ -624,8 +852,16 @@ crofton_shoot_impl(point_t **out_points,
|
||||
}
|
||||
}
|
||||
|
||||
if (out_surf_area) *out_surf_area = curr_est_sa;
|
||||
if (out_volume) *out_volume = curr_est_v;
|
||||
if (out_surf_area)
|
||||
*out_surf_area = curr_est_sa;
|
||||
if (out_volume)
|
||||
*out_volume = curr_est_v;
|
||||
if (out_result) {
|
||||
out_result->surface_area = curr_est_sa;
|
||||
out_result->volume = curr_est_v;
|
||||
out_result->ray_count = shared.total_rays;
|
||||
out_result->crossing_count = shared.total_crossings;
|
||||
}
|
||||
if (out_aabb_min) {
|
||||
for (size_t i = 0; i < shared.point_count; ++i)
|
||||
VMINMAX(*out_aabb_min, *out_aabb_max, shared.points[i]);
|
||||
@@ -641,11 +877,19 @@ crofton_shoot_impl(point_t **out_points,
|
||||
bounds_ret = -1;
|
||||
}
|
||||
if (out_points && !bounds_ret) {
|
||||
*out_points = shared.points;
|
||||
*out_point_count = shared.point_count;
|
||||
*out_points = shared.points;
|
||||
*out_point_count = shared.point_count;
|
||||
} else if (shared.points) {
|
||||
bu_free(shared.points, "Crofton hit points");
|
||||
bu_free(shared.points, "Crofton hit points");
|
||||
}
|
||||
if (out_result && !bounds_ret) {
|
||||
out_result->segments = shared.segments;
|
||||
out_result->segment_count = shared.segment_count;
|
||||
} else if (shared.segments) {
|
||||
bu_free(shared.segments, "Crofton segments");
|
||||
}
|
||||
|
||||
delete pool;
|
||||
|
||||
/* Clean each resource and NULL out its slot in rtip->rti_resources.
|
||||
* This is necessary because crofton_from_ip calls rt_i_destroy(rtip)
|
||||
@@ -657,7 +901,7 @@ crofton_shoot_impl(point_t **out_points,
|
||||
*
|
||||
* By setting the slot to NULL we let rt_i_destroy's cleanup skip it,
|
||||
* and then we can safely bu_free the resources array. */
|
||||
for (int i = 0; i < MAX_PSW; i++) {
|
||||
for (size_t i = 0; i < ncpus; i++) {
|
||||
if (resources[i].re_magic == RESOURCE_MAGIC) {
|
||||
rt_clean_resource_basic(rtip, &resources[i]);
|
||||
BU_PTBL_SET(&rtip->rti_resources, i, NULL);
|
||||
@@ -681,9 +925,31 @@ rt_crofton_shoot(double *out_surf_area, double *out_volume,
|
||||
const struct rt_crofton_params *params,
|
||||
const fastf_t *bbox_min, const fastf_t *bbox_max)
|
||||
{
|
||||
return crofton_shoot_impl(out_points, out_point_count, out_surf_area,
|
||||
out_volume, out_aabb_min, out_aabb_max, out_obb, rtip, params,
|
||||
bbox_min, bbox_max);
|
||||
return crofton_shoot_impl(NULL, out_points, out_point_count,
|
||||
out_surf_area, out_volume, out_aabb_min, out_aabb_max, out_obb,
|
||||
rtip, params, 0, bbox_min, bbox_max);
|
||||
}
|
||||
|
||||
int
|
||||
rt_crofton_collect(struct rt_crofton_result *result, struct rt_i *rtip,
|
||||
const struct rt_crofton_params *params, size_t ray_offset,
|
||||
const fastf_t *bbox_min, const fastf_t *bbox_max)
|
||||
{
|
||||
if (!result)
|
||||
return -1;
|
||||
return crofton_shoot_impl(result, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, rtip, params, ray_offset, bbox_min, bbox_max);
|
||||
}
|
||||
|
||||
void
|
||||
rt_crofton_result_free(struct rt_crofton_result *result)
|
||||
{
|
||||
if (!result)
|
||||
return;
|
||||
if (result->segments)
|
||||
bu_free(result->segments, "Crofton segments");
|
||||
struct rt_crofton_result empty = RT_CROFTON_RESULT_INIT;
|
||||
*result = empty;
|
||||
}
|
||||
|
||||
|
||||
@@ -821,7 +1087,7 @@ crofton_from_ip_n(const struct rt_db_internal *ip,
|
||||
double sa = 0.0;
|
||||
double vol = 0.0;
|
||||
(void)rt_crofton_shoot(&sa, &vol, NULL, NULL, NULL, NULL, NULL,
|
||||
rtip, params, NULL, NULL);
|
||||
rtip, params, NULL, NULL);
|
||||
|
||||
if (out_sa) *out_sa = sa;
|
||||
if (out_vol) *out_vol = vol;
|
||||
|
||||
@@ -1543,8 +1543,7 @@ permute_cell(point_t A,
|
||||
struct dsp_specific *dsp,
|
||||
struct dsp_rpp *dsp_rpp)
|
||||
{
|
||||
int x, y;
|
||||
|
||||
point_t tmp;
|
||||
|
||||
#ifdef FULL_DSP_DEBUGGING
|
||||
if (RT_G_DEBUG & RT_DEBUG_HF) {
|
||||
@@ -1558,10 +1557,8 @@ permute_cell(point_t A,
|
||||
switch (dsp->dsp_i.dsp_cuttype) {
|
||||
case DSP_CUT_DIR_llUR:
|
||||
return DSP_CUT_DIR_llUR;
|
||||
break;
|
||||
|
||||
case DSP_CUT_DIR_ADAPT: {
|
||||
point_t tmp;
|
||||
size_t cx = (size_t)dsp_rpp->dsp_min[X];
|
||||
size_t cy = (size_t)dsp_rpp->dsp_min[Y];
|
||||
|
||||
@@ -1570,28 +1567,8 @@ permute_cell(point_t A,
|
||||
dsp_rpp->dsp_min[X],
|
||||
dsp_rpp->dsp_min[Y]);
|
||||
|
||||
/*
|
||||
* We look at the points in the diagonal next cells to
|
||||
* determine the curvature along each diagonal of this
|
||||
* cell. This cell is divided into two triangles by
|
||||
* cutting across the cell in the direction of least
|
||||
* curvature.
|
||||
*
|
||||
* * * * *
|
||||
* \ /
|
||||
* \C D/
|
||||
* * *--* *
|
||||
* |\/|
|
||||
* |/\|
|
||||
* * *--* *
|
||||
* /A B\
|
||||
* / \
|
||||
* * * * *
|
||||
*/
|
||||
|
||||
/* leaf-cell precondition: the rpp spans exactly one grid unit, so
|
||||
* (cx, cy) plus the cell counts fully describe the curvature
|
||||
* stencil that rt_dsp_cell_cut() recomputes.
|
||||
/* The rpp spans one grid cell; use its indices to select the
|
||||
* lower-curvature diagonal.
|
||||
*/
|
||||
BU_ASSERT(dsp_rpp->dsp_max[X] == dsp_rpp->dsp_min[X] + 1);
|
||||
BU_ASSERT(dsp_rpp->dsp_max[Y] == dsp_rpp->dsp_min[Y] + 1);
|
||||
@@ -1599,15 +1576,16 @@ permute_cell(point_t A,
|
||||
if (rt_dsp_cell_cut(&dsp->dsp_i, cx, cy,
|
||||
(size_t)dsp->xsiz, (size_t)dsp->ysiz)
|
||||
== DSP_CUT_DIR_llUR) {
|
||||
/* A-D cut is fine, no need to permute */
|
||||
if (RT_G_DEBUG & RT_DEBUG_HF)
|
||||
bu_log("A-D cut\n");
|
||||
|
||||
return DSP_CUT_DIR_llUR;
|
||||
|
||||
}
|
||||
|
||||
/* prefer the B-C cut */
|
||||
}
|
||||
/* fall through */
|
||||
case DSP_CUT_DIR_ULlr:
|
||||
/* Both B-C modes need the same corner order. The top triangles
|
||||
* must keep outward (+Z) normals for the entry/exit state machine.
|
||||
*/
|
||||
VMOVE(tmp, A);
|
||||
VMOVE(A, B);
|
||||
VMOVE(B, D);
|
||||
@@ -1615,35 +1593,7 @@ permute_cell(point_t A,
|
||||
VMOVE(C, tmp);
|
||||
if (RT_G_DEBUG & RT_DEBUG_HF)
|
||||
bu_log("B-C cut\n");
|
||||
|
||||
return DSP_CUT_DIR_ULlr;
|
||||
|
||||
break;
|
||||
}
|
||||
case DSP_CUT_DIR_ULlr:
|
||||
/* assign the values for the corner points
|
||||
*
|
||||
* D----C
|
||||
* | |
|
||||
* | |
|
||||
* | |
|
||||
* B----A
|
||||
*/
|
||||
x = dsp_rpp->dsp_min[X];
|
||||
y = dsp_rpp->dsp_min[Y];
|
||||
VSET(B, x, y, DSP(&dsp->dsp_i, x, y));
|
||||
|
||||
x = dsp_rpp->dsp_max[X];
|
||||
VSET(A, x, y, DSP(&dsp->dsp_i, x, y));
|
||||
|
||||
y = dsp_rpp->dsp_max[Y];
|
||||
VSET(C, x, y, DSP(&dsp->dsp_i, x, y));
|
||||
|
||||
x = dsp_rpp->dsp_min[X];
|
||||
VSET(D, x, y, DSP(&dsp->dsp_i, x, y));
|
||||
|
||||
return DSP_CUT_DIR_ULlr;
|
||||
break;
|
||||
}
|
||||
bu_log("%s:%d Unknown DSP cut direction: %d\n",
|
||||
__FILE__, __LINE__, dsp->dsp_i.dsp_cuttype);
|
||||
|
||||
@@ -43,6 +43,18 @@ rel_err(double estimated, double exact)
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
crofton_segments_equal(const struct rt_crofton_segment *left,
|
||||
const struct rt_crofton_segment *right)
|
||||
{
|
||||
return left->ray_id == right->ray_id &&
|
||||
NEAR_EQUAL(left->thickness, right->thickness, SMALL_FASTF) &&
|
||||
VNEAR_EQUAL(left->in_point, right->in_point, SMALL_FASTF) &&
|
||||
VNEAR_EQUAL(left->in_normal, right->in_normal, SMALL_FASTF) &&
|
||||
VNEAR_EQUAL(left->out_point, right->out_point, SMALL_FASTF) &&
|
||||
VNEAR_EQUAL(left->out_normal, right->out_normal, SMALL_FASTF);
|
||||
}
|
||||
|
||||
static int
|
||||
verify_crofton_estimates(void)
|
||||
{
|
||||
@@ -379,6 +391,102 @@ run_convergence_case(struct db_i *dbip,
|
||||
&sample_points, &sample_count,
|
||||
rtip, &p, NULL, NULL);
|
||||
run_sec = (double)(bu_gettime() - t0) / 1000000.0;
|
||||
|
||||
if (i == 0) {
|
||||
struct rt_crofton_result samples = RT_CROFTON_RESULT_INIT;
|
||||
const size_t ray_offset = 4096u;
|
||||
const size_t continuation_rays = 256u;
|
||||
struct rt_crofton_params sample_params =
|
||||
{ray_offset + continuation_rays, 0.0, 0.0};
|
||||
int sample_ret = rt_crofton_collect(&samples, rtip,
|
||||
&sample_params, 0, NULL, NULL);
|
||||
int full_samples_valid = sample_ret > 0 && samples.segments &&
|
||||
samples.segment_count * 2 == samples.crossing_count &&
|
||||
samples.ray_count == sample_params.n_rays;
|
||||
if (!full_samples_valid) {
|
||||
printf(" %-24s invalid structured Crofton output\\n", label);
|
||||
failures++;
|
||||
} else {
|
||||
for (size_t sample_index = 0;
|
||||
sample_index < samples.segment_count; sample_index++) {
|
||||
struct rt_crofton_segment *segment =
|
||||
&samples.segments[sample_index];
|
||||
if (segment->thickness <= 0.0 ||
|
||||
!NEAR_EQUAL(DIST_PNT_PNT(segment->in_point,
|
||||
segment->out_point), segment->thickness, RT_LEN_TOL) ||
|
||||
!NEAR_EQUAL(MAGNITUDE(segment->in_normal), 1.0,
|
||||
VUNITIZE_TOL) ||
|
||||
!NEAR_EQUAL(MAGNITUDE(segment->out_normal), 1.0,
|
||||
VUNITIZE_TOL) ||
|
||||
segment->ray_id >= samples.ray_count) {
|
||||
printf(" %-24s invalid structured segment\\n", label);
|
||||
failures++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct rt_crofton_result offset_samples =
|
||||
RT_CROFTON_RESULT_INIT;
|
||||
struct rt_crofton_params offset_params =
|
||||
{continuation_rays, 0.0, 0.0};
|
||||
sample_ret = rt_crofton_collect(&offset_samples, rtip,
|
||||
&offset_params, ray_offset, NULL, NULL);
|
||||
int offset_samples_valid = sample_ret > 0 &&
|
||||
offset_samples.ray_count == offset_params.n_rays;
|
||||
if (!offset_samples_valid) {
|
||||
printf(" %-24s invalid offset Crofton output\\n", label);
|
||||
failures++;
|
||||
} else {
|
||||
for (size_t sample_index = 0;
|
||||
sample_index < offset_samples.segment_count;
|
||||
sample_index++) {
|
||||
size_t ray_id = offset_samples.segments[sample_index].ray_id;
|
||||
if (ray_id < ray_offset ||
|
||||
ray_id >= ray_offset + offset_params.n_rays) {
|
||||
printf(" %-24s invalid offset ray identifier\\n",
|
||||
label);
|
||||
failures++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (full_samples_valid && offset_samples_valid) {
|
||||
size_t suffix_start = 0;
|
||||
while (suffix_start < samples.segment_count &&
|
||||
samples.segments[suffix_start].ray_id < ray_offset)
|
||||
suffix_start++;
|
||||
size_t suffix_count = samples.segment_count - suffix_start;
|
||||
|
||||
if (suffix_count != offset_samples.segment_count) {
|
||||
printf(" %-24s random stream continuation count mismatch\\n",
|
||||
label);
|
||||
failures++;
|
||||
} else {
|
||||
for (size_t sample_index = 0;
|
||||
sample_index < suffix_count; sample_index++) {
|
||||
const struct rt_crofton_segment *full_segment =
|
||||
&samples.segments[suffix_start + sample_index];
|
||||
const struct rt_crofton_segment *offset_segment =
|
||||
&offset_samples.segments[sample_index];
|
||||
if (!crofton_segments_equal(full_segment, offset_segment)) {
|
||||
printf(" %-24s random stream continuation mismatch\\n",
|
||||
label);
|
||||
failures++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rt_crofton_result_free(&samples);
|
||||
if (samples.segments || samples.segment_count)
|
||||
failures++;
|
||||
rt_crofton_result_free(&offset_samples);
|
||||
if (offset_samples.segments || offset_samples.segment_count)
|
||||
failures++;
|
||||
}
|
||||
rt_i_destroy(rtip);
|
||||
|
||||
if (elapsed_total_sec)
|
||||
|
||||
@@ -414,13 +414,20 @@ flat_dsp_analytic(uint32_t xcnt, uint32_t ycnt, unsigned short h,
|
||||
/* Build an in-memory DB, prep, and run comparison */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
enum dsp_ray_check_mode {
|
||||
DSP_RAY_CHECK_NONE,
|
||||
DSP_RAY_CHECK_FLAT,
|
||||
DSP_RAY_CHECK_SMOOTH,
|
||||
DSP_RAY_CHECK_BC_CUT
|
||||
};
|
||||
|
||||
struct dsp_case {
|
||||
const char *label;
|
||||
uint32_t xcnt, ycnt;
|
||||
const unsigned short *buf;
|
||||
int cuttype;
|
||||
int smooth;
|
||||
int ray_checks;
|
||||
enum dsp_ray_check_mode ray_checks;
|
||||
/* stom diagonal (identity: dx=dy=dz=1): */
|
||||
double dx, dy, dz;
|
||||
/* analytic SA/vol (-1 if not available): */
|
||||
@@ -550,7 +557,7 @@ check_smooth_normal(const struct dsp_case *tc, struct soltab *stp)
|
||||
static int
|
||||
run_prepped_ray_checks(const struct dsp_case *tc, struct rt_i *rtip)
|
||||
{
|
||||
if (!tc->ray_checks)
|
||||
if (tc->ray_checks == DSP_RAY_CHECK_NONE)
|
||||
return 0;
|
||||
|
||||
int failures = 0;
|
||||
@@ -562,16 +569,14 @@ run_prepped_ray_checks(const struct dsp_case *tc, struct rt_i *rtip)
|
||||
|
||||
double x_extent = (tc->xcnt - 1) * tc->dx;
|
||||
double y_extent = (tc->ycnt - 1) * tc->dy;
|
||||
/* ray_checks == 1 is reserved for flat DSP cases, so buf[0] is the
|
||||
* expected terrain height for the deterministic vertical/side rays.
|
||||
*/
|
||||
double top_z = (double)tc->buf[0] * tc->dz;
|
||||
double expected_vertical_in = 10.0;
|
||||
double expected_vertical_out = expected_vertical_in + top_z;
|
||||
point_t o;
|
||||
vect_t d;
|
||||
|
||||
if (tc->ray_checks == 1) {
|
||||
if (tc->ray_checks == DSP_RAY_CHECK_FLAT) {
|
||||
/* Flat cases use buf[0] as the terrain height for direct rays. */
|
||||
double top_z = (double)tc->buf[0] * tc->dz;
|
||||
double expected_vertical_in = 10.0;
|
||||
double expected_vertical_out = expected_vertical_in + top_z;
|
||||
VSET(o, 0.5 * x_extent, 0.5 * y_extent, top_z + 10.0);
|
||||
VSET(d, 0.0, 0.0, -1.0);
|
||||
failures += check_direct_ray("vertical down flat DSP", stp, rtip,
|
||||
@@ -598,12 +603,22 @@ run_prepped_ray_checks(const struct dsp_case *tc, struct rt_i *rtip)
|
||||
failures += check_direct_ray("vertical up flat DSP", stp, rtip,
|
||||
o, d, 1, expected_vertical_in,
|
||||
expected_vertical_out, 0);
|
||||
} else if (tc->ray_checks == 2) {
|
||||
} else if (tc->ray_checks == DSP_RAY_CHECK_SMOOTH) {
|
||||
VSET(o, 0.5 * x_extent, 0.5 * y_extent, 10000.0);
|
||||
VSET(d, 0.0, 0.0, -1.0);
|
||||
failures += check_direct_ray("smooth-mode direct vertical shot", stp, rtip,
|
||||
o, d, 1, -1.0, -1.0, 0);
|
||||
failures += check_smooth_normal(tc, stp);
|
||||
} else if (tc->ray_checks == DSP_RAY_CHECK_BC_CUT) {
|
||||
/* In cell (3,3), the B-C triangle rises from 315 at y=3.25 by
|
||||
* 68 per x. A horizontal ray at z=330 enters at x=3+15/68
|
||||
* and exits through the outer wall at x=4. This also checks
|
||||
* that the explicit B-C cut gives the top an outward normal.
|
||||
*/
|
||||
VSET(o, 2.5, 3.25, 330.0);
|
||||
VSET(d, 1.0, 0.0, 0.0);
|
||||
failures += check_direct_ray("explicit B-C top-to-side shot", stp, rtip,
|
||||
o, d, 1, 0.5 + 15.0 / 68.0, 1.5, 1);
|
||||
}
|
||||
|
||||
return failures;
|
||||
@@ -782,7 +797,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"flat 5x5 h=100 (4x4 cells, identity stom)",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 1, 1.0, 1.0, 1.0, asa, avol
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_FLAT, 1.0, 1.0, 1.0, asa, avol
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -798,7 +813,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"flat 10x10 h=200 (9x9 cells)",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, asa, avol
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, asa, avol
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -814,7 +829,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"flat 10x10 h=50 (stom dx=5 dy=5 dz=2)",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 1, 5.0, 5.0, 2.0, asa, avol
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_FLAT, 5.0, 5.0, 2.0, asa, avol
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -829,7 +844,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"ramp 9x9 (h=100+20x+10y)",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -844,7 +859,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"asymmetric 5x5 explicit ULlr cut",
|
||||
GW, GH, buf, DSP_CUT_DIR_ULlr, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_ULlr, 0, DSP_RAY_CHECK_BC_CUT, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -859,7 +874,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"asymmetric 5x5 adaptive cut",
|
||||
GW, GH, buf, DSP_CUT_DIR_ADAPT, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_ADAPT, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -874,7 +889,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"smooth=1 ramp normal check",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 1, 2, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 1, DSP_RAY_CHECK_SMOOTH, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -889,7 +904,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"smooth=2 ramp normal check",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 2, 2, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 2, DSP_RAY_CHECK_SMOOTH, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -908,7 +923,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"sinusoidal 16x16",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -928,7 +943,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"complex 33x33 wave",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
bu_free(buf, "33x33 buf");
|
||||
@@ -971,7 +986,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"larger 129x129 mixed ramp/wave terrain",
|
||||
grid_width, grid_height, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
grid_width, grid_height, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
bu_free(sin_x, "129x129 sin x");
|
||||
|
||||
+180
-234
@@ -49,6 +49,7 @@
|
||||
#include "bu/path.h"
|
||||
#include "bu/process.h"
|
||||
#include "bu/units.h"
|
||||
#include "bu/binding.h"
|
||||
#include "vmath.h"
|
||||
#include "rt/db4.h"
|
||||
#include "rt/geom.h"
|
||||
@@ -2824,6 +2825,148 @@ to_data_vZ(struct ged *gedp,
|
||||
}
|
||||
|
||||
|
||||
static void tclcad_action_eval_aet(const struct bu_vls *ev, void *reg, void *event_data) {
|
||||
struct bview *gdvp = (struct bview *)event_data;
|
||||
const char *aet_args = (const char *)reg;
|
||||
struct bu_vls cmd = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&cmd, "%s aet %s %s", bu_vls_cstr(¤t_top->to_gedp->go_name), bu_vls_cstr(&gdvp->gv_name), aet_args);
|
||||
Tcl_Eval(current_top->to_interp, bu_vls_cstr(&cmd));
|
||||
bu_vls_free(&cmd);
|
||||
}
|
||||
|
||||
static void tclcad_action_mouse_drag(const struct bu_vls *ev, void *reg, void *event_data) {
|
||||
struct bview *gdvp = (struct bview *)event_data;
|
||||
const char *cmd_name = (const char *)reg;
|
||||
int cx = gdvp->gv_prevMouseX;
|
||||
int cy = gdvp->gv_prevMouseY;
|
||||
char *p;
|
||||
if ((p = strstr(bu_vls_cstr(ev), "\"cx\":"))) sscanf(p + 5, "%d", &cx);
|
||||
if ((p = strstr(bu_vls_cstr(ev), "\"cy\":"))) sscanf(p + 5, "%d", &cy);
|
||||
|
||||
struct bu_vls cmd = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&cmd, "%s %s %s %d %d", bu_vls_cstr(¤t_top->to_gedp->go_name), cmd_name, bu_vls_cstr(&gdvp->gv_name), cx, cy);
|
||||
Tcl_Eval(current_top->to_interp, bu_vls_cstr(&cmd));
|
||||
bu_vls_free(&cmd);
|
||||
}
|
||||
|
||||
static void tclcad_action_wheel_scale(const struct bu_vls *ev, void *reg, void *event_data) {
|
||||
struct bview *gdvp = (struct bview *)event_data;
|
||||
int delta = 0;
|
||||
char *p;
|
||||
if ((p = strstr(bu_vls_cstr(ev), "\"delta\":"))) sscanf(p + 8, "%d", &delta);
|
||||
const char *sf = (delta > 0) ? "1.1" : "0.9";
|
||||
struct bu_vls cmd = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&cmd, "%s zoom %s %s", bu_vls_cstr(¤t_top->to_gedp->go_name), bu_vls_cstr(&gdvp->gv_name), sf);
|
||||
Tcl_Eval(current_top->to_interp, bu_vls_cstr(&cmd));
|
||||
bu_vls_free(&cmd);
|
||||
}
|
||||
|
||||
static void tclcad_action_mouse_click(const struct bu_vls *ev, void *reg, void *event_data) {
|
||||
struct bview *gdvp = (struct bview *)event_data;
|
||||
const char *cmd_format = (const char *)reg;
|
||||
int cx = gdvp->gv_prevMouseX;
|
||||
int cy = gdvp->gv_prevMouseY;
|
||||
char *p;
|
||||
if ((p = strstr(bu_vls_cstr(ev), "\"cx\":"))) sscanf(p + 5, "%d", &cx);
|
||||
if ((p = strstr(bu_vls_cstr(ev), "\"cy\":"))) sscanf(p + 5, "%d", &cy);
|
||||
|
||||
struct bu_vls cmd = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&cmd, cmd_format, bu_vls_cstr(¤t_top->to_gedp->go_name), bu_vls_cstr(&gdvp->gv_name), cx, cy);
|
||||
Tcl_Eval(current_top->to_interp, bu_vls_cstr(&cmd));
|
||||
bu_vls_free(&cmd);
|
||||
}
|
||||
|
||||
static int bindings_loaded_mged = 0;
|
||||
|
||||
static int tclcad_load_bindings_cmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]) {
|
||||
if (argc < 2) {
|
||||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " <json_path>\"", NULL);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
char *path = (char *)bu_dir(NULL, 0, BU_DIR_DATA, argv[1], NULL);
|
||||
if (!path) {
|
||||
Tcl_AppendResult(interp, "Failed to resolve path: ", argv[1], NULL);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if (bu_binding_load(path) < 0) {
|
||||
bu_free(path, "path");
|
||||
Tcl_AppendResult(interp, "Failed to load bindings from: ", argv[1], NULL);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
bu_free(path, "path");
|
||||
|
||||
/* Validate that application provided required bindings */
|
||||
if (!bu_binding_action_is_bound("dm", "view_mouse_drag_rotate") ||
|
||||
!bu_binding_action_is_bound("dm", "view_mouse_drag_translate") ||
|
||||
!bu_binding_action_is_bound("dm", "view_mouse_drag_scale")) {
|
||||
Tcl_AppendResult(interp, "Validation Failed: The provided bindings do not map inputs for view_mouse_drag_rotate/translate/scale. These are required standard actions.", NULL);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int tclcad_dispatch_event_cmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]) {
|
||||
if (argc < 8) return TCL_OK;
|
||||
struct tclcad_obj *to = (struct tclcad_obj *)clientData;
|
||||
struct bview *gdvp = bv_set_find_view(&to->to_gedp->ged_views, argv[2]);
|
||||
if (!gdvp) return TCL_OK;
|
||||
|
||||
int x = atoi(argv[5]);
|
||||
int y = atoi(argv[6]);
|
||||
int state = atoi(argv[7]);
|
||||
|
||||
struct bu_vls ev = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&ev, "{\"type\": \"%s\"", argv[3]);
|
||||
|
||||
if (!strcmp(argv[3], "key-press")) {
|
||||
char key_str[16] = {0};
|
||||
bu_strlcpy(key_str, argv[4], sizeof(key_str));
|
||||
if (strlen(key_str) == 1 && key_str[0] >= 'a' && key_str[0] <= 'z') {
|
||||
key_str[0] = key_str[0] - 'a' + 'A';
|
||||
}
|
||||
bu_vls_printf(&ev, ", \"key\": \"%s\"", key_str);
|
||||
} else if (!strcmp(argv[3], "mouse-wheel")) {
|
||||
int delta = (argc > 8) ? atoi(argv[8]) : 0;
|
||||
int mdelta = -1 * delta / 120;
|
||||
bu_vls_printf(&ev, ", \"delta\": %d", mdelta);
|
||||
} else {
|
||||
int btn = atoi(argv[4]) - 1;
|
||||
if (!strcmp(argv[3], "mouse-move")) {
|
||||
if (state & 0x100) btn = 0;
|
||||
else if (state & 0x200) btn = 1;
|
||||
else if (state & 0x400) btn = 2;
|
||||
else btn = -1;
|
||||
|
||||
int dx = x - gdvp->gv_prevMouseX;
|
||||
int dy = y - gdvp->gv_prevMouseY;
|
||||
bu_vls_printf(&ev, ", \"dx\": %d, \"dy\": %d, \"mode\": 0", dx, dy);
|
||||
}
|
||||
if (btn >= 0) bu_vls_printf(&ev, ", \"button\": %d", btn);
|
||||
bu_vls_printf(&ev, ", \"cx\": %d, \"cy\": %d", x, y);
|
||||
}
|
||||
|
||||
if (state & (1 | 4 | 8)) {
|
||||
bu_vls_printf(&ev, ", \"modifiers\": [");
|
||||
int added = 0;
|
||||
if (state & 1) { bu_vls_printf(&ev, "\"Shift\""); added=1; }
|
||||
if (state & 4) { if(added) bu_vls_printf(&ev, ", "); bu_vls_printf(&ev, "\"Control\""); added=1; }
|
||||
if (state & 8) { if(added) bu_vls_printf(&ev, ", "); bu_vls_printf(&ev, "\"Alt\""); }
|
||||
bu_vls_printf(&ev, "]");
|
||||
}
|
||||
bu_vls_printf(&ev, "}");
|
||||
|
||||
bu_binding_process_event("dm", bu_vls_cstr(&ev), gdvp);
|
||||
|
||||
gdvp->gv_prevMouseX = x;
|
||||
gdvp->gv_prevMouseY = y;
|
||||
|
||||
bu_vls_free(&ev);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
to_init_default_bindings(struct bview *gdvp)
|
||||
{
|
||||
@@ -2832,6 +2975,35 @@ to_init_default_bindings(struct bview *gdvp)
|
||||
if (dm_get_pathname((struct dm *)gdvp->dmp)) {
|
||||
struct bu_vls *pathvls = dm_get_pathname((struct dm *)gdvp->dmp);
|
||||
if (pathvls) {
|
||||
if (!bindings_loaded_mged) {
|
||||
bindings_loaded_mged = 1;
|
||||
|
||||
|
||||
bu_binding_register_action("view_2", tclcad_action_eval_aet, (void*)"35 -25 0");
|
||||
bu_binding_register_action("view_3", tclcad_action_eval_aet, (void*)"35 25 0");
|
||||
bu_binding_register_action("view_4", tclcad_action_eval_aet, (void*)"45 45 0");
|
||||
bu_binding_register_action("view_5", tclcad_action_eval_aet, (void*)"145 25 0");
|
||||
bu_binding_register_action("view_6", tclcad_action_eval_aet, (void*)"215 25 0");
|
||||
bu_binding_register_action("view_7", tclcad_action_eval_aet, (void*)"325 25 0");
|
||||
bu_binding_register_action("view_front", tclcad_action_eval_aet, (void*)"0 0 0");
|
||||
bu_binding_register_action("view_top", tclcad_action_eval_aet, (void*)"270 90 0");
|
||||
bu_binding_register_action("view_bottom", tclcad_action_eval_aet, (void*)"270 -90 0");
|
||||
bu_binding_register_action("view_left", tclcad_action_eval_aet, (void*)"90 0 0");
|
||||
bu_binding_register_action("view_rear", tclcad_action_eval_aet, (void*)"180 0 0");
|
||||
bu_binding_register_action("view_right", tclcad_action_eval_aet, (void*)"270 0 0");
|
||||
|
||||
bu_binding_register_action("view_mouse_drag_rotate", tclcad_action_mouse_drag, (void*)"mouse_rot");
|
||||
bu_binding_register_action("view_mouse_drag_translate", tclcad_action_mouse_drag, (void*)"mouse_trans");
|
||||
bu_binding_register_action("view_mouse_drag_scale", tclcad_action_mouse_drag, (void*)"mouse_scale");
|
||||
bu_binding_register_action("view_mouse_wheel_scale", tclcad_action_wheel_scale, NULL);
|
||||
|
||||
bu_binding_register_action("view_mouse_press_left", tclcad_action_mouse_click, (void*)"%s zoom %s 0.5");
|
||||
bu_binding_register_action("view_mouse_press_right", tclcad_action_mouse_click, (void*)"%s zoom %s 2.0");
|
||||
|
||||
Tcl_CreateCommand(current_top->to_interp, "tclcad_dispatch_event", tclcad_dispatch_event_cmd, (ClientData)current_top, (Tcl_CmdDeleteProc *)NULL);
|
||||
Tcl_CreateCommand(current_top->to_interp, "tclcad_load_bindings", tclcad_load_bindings_cmd, (ClientData)current_top, (Tcl_CmdDeleteProc *)NULL);
|
||||
}
|
||||
|
||||
bu_vls_printf(&bindings, "bind %s <Configure> {%s configure %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
@@ -2848,240 +3020,14 @@ to_init_default_bindings(struct bview *gdvp)
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Mouse Bindings */
|
||||
bu_vls_printf(&bindings, "bind %s <2> {%s vslew %s %%x %%y; focus %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name),
|
||||
bu_vls_addr(pathvls));
|
||||
bu_vls_printf(&bindings, "bind %s <1> {%s zoom %s 0.5; focus %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name),
|
||||
bu_vls_addr(pathvls));
|
||||
bu_vls_printf(&bindings, "bind %s <3> {%s zoom %s 2.0; focus %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name),
|
||||
bu_vls_addr(pathvls));
|
||||
bu_vls_printf(&bindings, "bind %s <4> {%s zoom %s 1.1; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <5> {%s zoom %s 0.9; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <MouseWheel> {if {%%D < 0} {%s zoom %s 0.9} else {%s zoom %s 1.1}; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Idle Mode */
|
||||
bu_vls_printf(&bindings, "bind %s <ButtonRelease> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <KeyRelease-Control_L> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <KeyRelease-Control_R> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <KeyRelease-Shift_L> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <KeyRelease-Shift_R> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <KeyRelease-Alt_L> {%s idle_mode %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <KeyRelease-Alt_R> {%s idle_mode %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Rotate Mode */
|
||||
bu_vls_printf(&bindings, "bind %s <Control-ButtonRelease-1> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-ButtonPress-1> {%s rotate_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-ButtonPress-2> {%s rotate_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-ButtonPress-3> {%s rotate_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Translate Mode */
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-ButtonRelease-1> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-ButtonPress-1> {%s translate_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-ButtonPress-2> {%s translate_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-ButtonPress-3> {%s translate_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Scale Mode */
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Shift-ButtonRelease-1> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Shift-ButtonPress-1> {%s scale_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Shift-ButtonPress-2> {%s scale_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Shift-ButtonPress-3> {%s scale_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Constrained Rotate Mode */
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Lock-ButtonRelease-1> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Lock-ButtonPress-1> {%s constrain_rmode %s x %%x %%y; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Lock-ButtonPress-2> {%s constrain_rmode %s y %%x %%y; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Lock-ButtonPress-3> {%s constrain_rmode %s z %%x %%y; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Constrained Translate Mode */
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-Lock-ButtonRelease-1> {%s idle_mode %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-Lock-ButtonPress-1> {%s constrain_tmode %s x %%x %%y; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-Lock-ButtonPress-2> {%s constrain_tmode %s y %%x %%y; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-Lock-ButtonPress-3> {%s constrain_tmode %s z %%x %%y; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Key Bindings */
|
||||
bu_vls_printf(&bindings, "bind %s 3 {%s aet %s 35 25; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s 4 {%s aet %s 45 45; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s f {%s aet %s 0 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s F {%s aet %s 0 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s R {%s aet %s 180 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s r {%s aet %s 270 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s l {%s aet %s 90 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s L {%s aet %s 90 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s t {%s aet %s 270 90; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s T {%s aet %s 270 90; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s b {%s aet %s 270 -90; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s B {%s aet %s 270 -90; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s + {%s zoom %s 2.0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s = {%s zoom %s 2.0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s _ {%s zoom %s 0.5; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s - {%s zoom %s 0.5; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Key-Left> {%s rot %s -v 0 1 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Key-Right> {%s rot %s -v 0 -1 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Key-Up> {%s rot %s -v 1 0 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Key-Down> {%s rot %s -v -1 0 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
Tcl_Eval(current_top->to_interp, bu_vls_addr(&bindings));
|
||||
/* Dispatch all events to bu_binding */
|
||||
bu_vls_printf(&bindings, "bind %s <Any-KeyPress> {tclcad_dispatch_event %%W %s %s key-press %%K %%x %%y %%s; break}; ", bu_vls_addr(pathvls), bu_vls_addr(¤t_top->to_gedp->go_name), bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Any-ButtonPress> {tclcad_dispatch_event %%W %s %s mouse-press %%b %%x %%y %%s; break}; ", bu_vls_addr(pathvls), bu_vls_addr(¤t_top->to_gedp->go_name), bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Any-ButtonRelease> {tclcad_dispatch_event %%W %s %s mouse-release %%b %%x %%y %%s; break}; ", bu_vls_addr(pathvls), bu_vls_addr(¤t_top->to_gedp->go_name), bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Any-Motion> {tclcad_dispatch_event %%W %s %s mouse-move 0 %%x %%y %%s; break}; ", bu_vls_addr(pathvls), bu_vls_addr(¤t_top->to_gedp->go_name), bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <MouseWheel> {tclcad_dispatch_event %%W %s %s mouse-wheel 0 %%x %%y %%s %%D; break}; ", bu_vls_addr(pathvls), bu_vls_addr(¤t_top->to_gedp->go_name), bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
Tcl_Eval(current_top->to_interp, bu_vls_cstr(&bindings));
|
||||
}
|
||||
}
|
||||
bu_vls_free(&bindings);
|
||||
|
||||
@@ -49,12 +49,8 @@ proc mged_bind_dm { w } {
|
||||
|
||||
bind $w <Enter> [list mged_activate_dm $w]
|
||||
|
||||
#default mouse bindings
|
||||
default_mouse_bindings $w
|
||||
# Load standardized JSON bindings for mged
|
||||
|
||||
#default key bindings
|
||||
set forwarding_key($w) 0
|
||||
default_key_bindings $w
|
||||
}
|
||||
|
||||
proc print_return_val str {
|
||||
@@ -64,129 +60,6 @@ proc print_return_val str {
|
||||
}
|
||||
}
|
||||
|
||||
if ![info exists mged_default(dm_key_bindings)] {
|
||||
set mged_default(dm_key_bindings) "\tKey Sequence\t\tBehavior
|
||||
\ta\t\t\ttoggle angle distance cursor (ADC)
|
||||
\te\t\t\ttoggle edit axes
|
||||
\tm\t\t\ttoggle model axes
|
||||
\tv\t\t\ttoggle view axes
|
||||
\ti\t\t\tadvance illumination pointer forward
|
||||
\tI\t\t\tadvance illumination pointer backward
|
||||
\tp\t\t\tsimulate mouse press (i.e. to pick a solid)
|
||||
\t3\t\t\tview - ae 35 25
|
||||
\t4\t\t\tview - ae 45 45
|
||||
\tf\t\t\tfront view
|
||||
\tt\t\t\ttop view
|
||||
\tb\t\t\tbottom view
|
||||
\tl\t\t\tleft view
|
||||
\tr\t\t\tright view
|
||||
\tR\t\t\trear view
|
||||
\ts\t\t\tenter solid illumination state
|
||||
\to\t\t\tenter object illumination state
|
||||
\tq\t\t\treject edit
|
||||
\tu\t\t\tzero knobs and sliders
|
||||
\t<F1>\t\t\ttoggle depthcue
|
||||
\t<F2>\t\t\ttoggle zclip
|
||||
\t<F3>\t\t\ttoggle perspective
|
||||
\t<F4>\t\t\ttoggle zbuffer
|
||||
\t<F5>\t\t\ttoggle lighting
|
||||
\t<F6>\t\t\ttoggle perspective angle
|
||||
\t<F7>\t\t\ttoggle faceplate
|
||||
\t<F8>\t\t\ttoggle faceplate GUI
|
||||
\t<F9>\t\t\ttoggle keystroke forwarding
|
||||
\t<F12>\t\t\tzero knobs
|
||||
\t<Left>\t\t\tabsolute rotate about y axis
|
||||
\t<Right>\t\t\tabsolute rotate about y axis
|
||||
\t<Down>\t\t\tabsolute rotate about x axis
|
||||
\t<Up>\t\t\tabsolute rotate about x axis
|
||||
\t<Shift-Left>\t\tabsolute translate in X direction
|
||||
\t<Shift-Right>\t\tabsolute translate in X direction
|
||||
\t<Shift-Down>\t\tabsolute translate in Z direction
|
||||
\t<Shift-Up>\t\tabsolute translate in Z direction
|
||||
\t<Control-Shift-Left>\tabsolute rotate about z axis
|
||||
\t<Control-Shift-Right>\tabsolute rotate about z axis
|
||||
\t<Control-Shift-Down>\tabsolute translate in Y direction
|
||||
\t<Control-Shift-Up>\tabsolute translate in Y direction
|
||||
\t<Control-n>\t\tgoto next view
|
||||
\t<Control-p>\t\tgoto previous view
|
||||
\t<Control-t>\t\ttoggle between the current view and the last view"
|
||||
}
|
||||
|
||||
proc default_key_bindings { w } {
|
||||
# common view shortcuts
|
||||
bind $w 2 "winset $w; ae 35 -25; break"
|
||||
bind $w 3 "winset $w; press 35,25; break"
|
||||
bind $w 4 "winset $w; press 45,45; break"
|
||||
bind $w 5 "winset $w; ae 145 25; break"
|
||||
bind $w 6 "winset $w; ae 215 25; break"
|
||||
bind $w 7 "winset $w; ae 325 25; break"
|
||||
|
||||
# default key shortcuts
|
||||
bind $w a "winset $w; adc; break"
|
||||
bind $w b "winset $w; press bottom; break"
|
||||
bind $w c "open_cmd_win id_0"
|
||||
bind $w e "winset $w; rset ax edit_draw !; update_gui $w edit_draw \[rset ax edit_draw\]; break"
|
||||
bind $w f "winset $w; press front; break"
|
||||
bind $w i "winset $w; aip f; break"
|
||||
bind $w l "winset $w; press left; break"
|
||||
bind $w m "winset $w; rset ax model_draw !; update_gui $w model_draw \[rset ax model_draw\]; break"
|
||||
bind $w o "winset $w; press oill; break"
|
||||
bind $w p "winset $w; M 1 0 0; break"
|
||||
bind $w q "winset $w; press reject; break"
|
||||
bind $w r "winset $w; press right; break"
|
||||
bind $w s "winset $w; press sill; break"
|
||||
bind $w t "winset $w; press top; break"
|
||||
bind $w u "winset $w; svb; break"
|
||||
bind $w v "winset $w; rset ax view_draw !; update_gui $w view_draw \[rset ax view_draw\]; break"
|
||||
|
||||
# default shift+key shortcuts
|
||||
bind $w A "winset $w; press accept; break"
|
||||
bind $w I "winset $w; aip b; break"
|
||||
bind $w N "winset $w; puts \[nirt -b\]; break"
|
||||
bind $w P "winset $w; catch {sed_apply}; break"
|
||||
bind $w R "winset $w; press rear; break"
|
||||
bind $w S "winset $w; catch {sed_reset}; break"
|
||||
|
||||
# default control+key shortcuts
|
||||
bind $w <Control-n> "winset $w; _mged_view_ring next; break"
|
||||
bind $w <Control-p> "winset $w; _mged_view_ring prev; break"
|
||||
bind $w <Control-t> "winset $w; _mged_view_ring toggle; break"
|
||||
|
||||
# shift grips navigation
|
||||
bind $w <Control-Shift-Down> "winset $w; knob -i aY \$mged_default(tran_factor); break"
|
||||
bind $w <Control-Shift-Left> "winset $w; knob -i az \$mged_default(rot_factor); break"
|
||||
bind $w <Control-Shift-Right> "winset $w; knob -i az -\$mged_default(rot_factor); break"
|
||||
bind $w <Control-Shift-Up> "winset $w; knob -i aY -\$mged_default(tran_factor); break"
|
||||
bind $w <Down> "winset $w; knob -i ax \$mged_default(rot_factor); break"
|
||||
bind $w <Left> "winset $w; knob -i ay -\$mged_default(rot_factor); break"
|
||||
bind $w <Right> "winset $w; knob -i ay \$mged_default(rot_factor); break"
|
||||
bind $w <Up> "winset $w; knob -i ax -\$mged_default(rot_factor); break"
|
||||
bind $w <Shift-Down> "winset $w; knob -i aZ -\$mged_default(tran_factor); break"
|
||||
bind $w <Shift-Left> "winset $w; knob -i aX \$mged_default(tran_factor); break"
|
||||
bind $w <Shift-Right> "winset $w; knob -i aX -\$mged_default(tran_factor); break"
|
||||
bind $w <Shift-Up> "winset $w; knob -i aZ \$mged_default(tran_factor); break"
|
||||
|
||||
# function keys (settings)
|
||||
bind $w <F1> "winset $w; dm set depthcue !; update_gui $w depthcue \[dm set depthcue\]; break"
|
||||
bind $w <F2> "winset $w; dm set zclip !; update_gui $w zclip \[dm set zclip\]; break"
|
||||
bind $w <F3> "winset $w; set perspective_mode !; update_gui $w perspective_mode \$perspective_mode; break"
|
||||
bind $w <F4> "winset $w; dm set zbuffer !; update_gui $w zbuffer \[dm set zbuffer\]; break"
|
||||
bind $w <F5> "winset $w; dm set lighting !; update_gui $w lighting \[dm set lighting\]; break"
|
||||
bind $w <F6> "winset $w; set toggle_perspective !; break"
|
||||
bind $w <F7> "winset $w; set faceplate !; update_gui $w faceplate \$faceplate; break"
|
||||
bind $w <F8> "winset $w; set orig_gui !; update_gui $w orig_gui \$orig_gui; break"
|
||||
bind $w <F9> "toggle_forward_key_bindings $w; update_gui $w forward_keys \$forwarding_key($w); break"
|
||||
bind $w <F12> "winset $w; knob zero; break"
|
||||
|
||||
# interrupt
|
||||
bind $w <Escape> "winset $w; reset_everything $w; break"
|
||||
|
||||
# Throw away other key events
|
||||
bind $w <KeyPress> {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
proc reset_everything { w } {
|
||||
global mged_gui
|
||||
|
||||
@@ -202,274 +75,6 @@ proc reset_everything { w } {
|
||||
set_mouse_behavior $id
|
||||
}
|
||||
|
||||
proc set_forward_keys { w val } {
|
||||
global forwarding_key
|
||||
|
||||
set forwarding_key($w) $val
|
||||
if {$forwarding_key($w)} {
|
||||
forward_key_bindings $w
|
||||
} else {
|
||||
default_key_bindings $w
|
||||
}
|
||||
}
|
||||
|
||||
proc toggle_forward_key_bindings { w } {
|
||||
global forwarding_key
|
||||
|
||||
if {$forwarding_key($w)} {
|
||||
default_key_bindings $w
|
||||
set forwarding_key($w) 0
|
||||
} else {
|
||||
forward_key_bindings $w
|
||||
set forwarding_key($w) 1
|
||||
}
|
||||
}
|
||||
|
||||
proc forward_key_bindings { w } {
|
||||
set id [get_player_id_dm $w]
|
||||
|
||||
if {$id == "mged"} {
|
||||
return
|
||||
}
|
||||
|
||||
# First, unset the default key bindings
|
||||
bind $w a {}
|
||||
bind $w e {}
|
||||
bind $w m {}
|
||||
bind $w v {}
|
||||
bind $w i {}
|
||||
bind $w I {}
|
||||
bind $w p {}
|
||||
bind $w 2 {}
|
||||
bind $w 3 {}
|
||||
bind $w 4 {}
|
||||
bind $w 5 {}
|
||||
bind $w 6 {}
|
||||
bind $w 7 {}
|
||||
bind $w f {}
|
||||
bind $w t {}
|
||||
bind $w b {}
|
||||
bind $w l {}
|
||||
bind $w N {}
|
||||
bind $w r {}
|
||||
bind $w R {}
|
||||
bind $w s {}
|
||||
bind $w o {}
|
||||
bind $w q {}
|
||||
bind $w u {}
|
||||
bind $w c {}
|
||||
bind $w A {}
|
||||
bind $w P {}
|
||||
bind $w S {}
|
||||
bind $w <F1> {}
|
||||
bind $w <F2> {}
|
||||
bind $w <F3> {}
|
||||
bind $w <F4> {}
|
||||
bind $w <F5> {}
|
||||
bind $w <F6> {}
|
||||
bind $w <F7> {}
|
||||
bind $w <F8> {}
|
||||
bind $w <F12> {}
|
||||
|
||||
bind $w <Left> {}
|
||||
bind $w <Right> {}
|
||||
bind $w <Down> {}
|
||||
bind $w <Up> {}
|
||||
bind $w <Shift-Left> {}
|
||||
bind $w <Shift-Right> {}
|
||||
bind $w <Shift-Down> {}
|
||||
bind $w <Shift-Up> {}
|
||||
bind $w <Control-Shift-Left> {}
|
||||
bind $w <Control-Shift-Right> {}
|
||||
bind $w <Control-Shift-Down> {}
|
||||
bind $w <Control-Shift-Up> {}
|
||||
|
||||
bind $w <Control-n> {}
|
||||
bind $w <Control-p> {}
|
||||
bind $w <Control-t> {}
|
||||
|
||||
bind $w <Escape> {}
|
||||
|
||||
# The focus commands in the binding below are necessary to insure
|
||||
# that .$id.t gets the event.
|
||||
bind $w <KeyPress> "\
|
||||
focus .$id.t;\
|
||||
set mged_gui(.$id.t,insert_char_flag) 1;\
|
||||
event generate .$id.t <KeyPress> -state %s -keysym %K;\
|
||||
set mged_gui(.$id.t,insert_char_flag) 0;\
|
||||
focus %W;\
|
||||
break"
|
||||
}
|
||||
|
||||
proc default_mouse_bindings { w } {
|
||||
global no_focus_hack
|
||||
global transform
|
||||
global tcl_platform
|
||||
|
||||
# default button bindings
|
||||
if {![regexp {^[0-9]+} $::tcl_platform(osVersion) osMajorVersion]} {
|
||||
set osMajorVersion 0
|
||||
}
|
||||
|
||||
if {[info exists no_focus_hack] && $no_focus_hack} {
|
||||
bind $w <1> "winset $w; zoom 0.5; break"
|
||||
bind $w <2> "winset $w; set tmpstr \[dm m %x %y\]; print_return_val \$tmpstr; break"
|
||||
bind $w <3> "winset $w; zoom 2.0; break"
|
||||
|
||||
# Mouse wheel fun with Tcl/Tk - see https://wiki.tcl-lang.org/page/mousewheel
|
||||
bind $w <MouseWheel> "winset $w; focus $w; if {%D < 0} {zoom 0.9} else {zoom 1.1}"
|
||||
bind $w <Button-4> {event generate [focus] <MouseWheel> -delta 120}
|
||||
bind $w <Button-5> {event generate [focus] <MouseWheel> -delta -120}
|
||||
|
||||
if {$::tcl_platform(os) == "Darwin"} {
|
||||
# Mac option-key emulates mouse 2 (doesn't work?)
|
||||
bind $w <Option-ButtonPress-1> "winset $w; set tmpstr \[dm m %x %y\]; print_return_val \$tmpstr; break"
|
||||
# Mac command-key emulates mouse 3
|
||||
bind $w <Mod2-ButtonPress-1> "winset $w; zoom 2.0; break"
|
||||
}
|
||||
} else {
|
||||
bind $w <1> "winset $w; focus $w; zoom 0.5; break"
|
||||
bind $w <2> "winset $w; focus $w; set tmpstr \[dm m %x %y\]; print_return_val \$tmpstr; break"
|
||||
bind $w <3> "winset $w; focus $w; zoom 2.0; break"
|
||||
|
||||
# Mouse wheel fun with Tcl/Tk - see https://wiki.tcl-lang.org/page/mousewheel
|
||||
bind $w <MouseWheel> "winset $w; focus $w; if {%D < 0} {zoom 0.9} else {zoom 1.1}; break"
|
||||
bind $w <Button-4> {event generate [focus] <MouseWheel> -delta 120}
|
||||
bind $w <Button-5> {event generate [focus] <MouseWheel> -delta -120}
|
||||
|
||||
if {$::tcl_platform(os) == "Darwin"} {
|
||||
# Mac command-key emulates mouse 2 (doesn't work?)
|
||||
bind $w <Option-ButtonPress-1> "winset $w; focus $w; set tmpstr \[dm m %x %y\]; print_return_val \$tmpstr; break"
|
||||
# Mac command-key emulates mouse 3
|
||||
bind $w <Mod2-ButtonPress-1> "winset $w; focus $w; zoom 2.0; break"
|
||||
}
|
||||
}
|
||||
|
||||
bind $w <ButtonRelease> "winset $w; dm idle; break"
|
||||
bind $w <Motion> "winset $w; if {\[dm type\] == \"tkswrast\"} {dm motion %x %y}; break"
|
||||
bind $w <KeyRelease-Control_L> "winset $w; dm idle; break"
|
||||
bind $w <KeyRelease-Control_R> "winset $w; dm idle; break"
|
||||
bind $w <KeyRelease-Shift_L> "winset $w; dm idle; break"
|
||||
bind $w <KeyRelease-Shift_R> "winset $w; dm idle; break"
|
||||
bind $w <KeyRelease-Alt_L> "winset $w; dm idle; break"
|
||||
bind $w <KeyRelease-Alt_R> "winset $w; dm idle; break"
|
||||
|
||||
if ![catch {adc draw} result] {
|
||||
set adcflag $result
|
||||
} else {
|
||||
set adcflag 0
|
||||
}
|
||||
|
||||
if {$adcflag == "1" && $transform == "a"} {
|
||||
bind $w <Shift-ButtonPress-1> "winset $w; dm adc t %x %y; \
|
||||
shift_grip_hints $w \"Translate ADC\"; break"
|
||||
bind $w <Shift-ButtonPress-2> "winset $w; dm adc t %x %y; \
|
||||
shift_grip_hints $w \"Translate ADC\"; break"
|
||||
bind $w <Shift-ButtonPress-3> "winset $w; dm adc d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
|
||||
bind $w <Control-ButtonPress-1> "winset $w; dm adc 1 %x %y; \
|
||||
shift_grip_hints $w \"Rotate Angle 1\"; break"
|
||||
bind $w <Control-ButtonPress-2> "winset $w; dm adc 2 %x %y; \
|
||||
shift_grip_hints $w \"Rotate Angle 2\"; break"
|
||||
bind $w <Control-ButtonPress-3> "winset $w; dm adc d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
|
||||
bind $w <Shift-Control-ButtonPress-1> "winset $w; dm adc d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
bind $w <Shift-Control-ButtonPress-2> "winset $w; dm adc d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
bind $w <Shift-Control-ButtonPress-3> "winset $w; dm adc d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
|
||||
#constrained adc defaults
|
||||
bind $w <Alt-Shift-ButtonPress-1> "winset $w; dm con a x %x %y; \
|
||||
shift_grip_hints $w \"X Translate ADC\"; break"
|
||||
bind $w <Alt-Shift-ButtonPress-2> "winset $w; dm con a y %x %y; \
|
||||
shift_grip_hints $w \"Y Translate ADC\"; break"
|
||||
bind $w <Alt-Shift-ButtonPress-3> "winset $w; dm con a d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
|
||||
bind $w <Alt-Control-ButtonPress-1> "winset $w; dm con a 1 %x %y; \
|
||||
shift_grip_hints $w \"Rotate Angle 1\"; break"
|
||||
bind $w <Alt-Control-ButtonPress-2> "winset $w; dm con a 2 %x %y; \
|
||||
shift_grip_hints $w \"Rotate Angle 2\"; break"
|
||||
bind $w <Alt-Control-ButtonPress-3> "winset $w; dm con a d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
|
||||
bind $w <Alt-Shift-Control-ButtonPress-1> "winset $w; dm con a d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
bind $w <Alt-Shift-Control-ButtonPress-2> "winset $w; dm con a d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
bind $w <Alt-Shift-Control-ButtonPress-3> "winset $w; dm con a d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
} else {
|
||||
bind $w <Shift-ButtonPress-1> "winset $w; dm am t %x %y; \
|
||||
shift_grip_hints $w Translate; break"
|
||||
bind $w <Shift-ButtonPress-2> "winset $w; dm am t %x %y; \
|
||||
shift_grip_hints $w Translate; break"
|
||||
bind $w <Shift-ButtonPress-3> "winset $w; dm am t %x %y; \
|
||||
shift_grip_hints $w Translate; break"
|
||||
|
||||
bind $w <Control-ButtonPress-1> "winset $w; dm am r %x %y; \
|
||||
shift_grip_hints $w Rotate; break"
|
||||
bind $w <Control-ButtonPress-2> "winset $w; dm am r %x %y; \
|
||||
shift_grip_hints $w Rotate; break"
|
||||
bind $w <Control-ButtonPress-3> "winset $w; dm am r %x %y; \
|
||||
shift_grip_hints $w Rotate; break"
|
||||
|
||||
bind $w <Shift-Control-ButtonPress-1> "winset $w; dm am s %x %y; \
|
||||
shift_grip_hints $w Scale/Zoom; break"
|
||||
bind $w <Shift-Control-ButtonPress-2> "winset $w; dm am s %x %y; \
|
||||
shift_grip_hints $w Scale/Zoom; break"
|
||||
bind $w <Shift-Control-ButtonPress-3> "winset $w; dm am s %x %y; \
|
||||
shift_grip_hints $w Scale/Zoom; break"
|
||||
|
||||
#constrained defaults
|
||||
bind $w <Alt-Shift-ButtonPress-1> "winset $w; dm con t x %x %y; \
|
||||
shift_grip_hints $w \"X Translation\"; break"
|
||||
bind $w <Alt-Shift-ButtonPress-2> "winset $w; dm con t y %x %y; \
|
||||
shift_grip_hints $w \"Y Translation\"; break"
|
||||
bind $w <Alt-Shift-ButtonPress-3> "winset $w; dm con t z %x %y; \
|
||||
shift_grip_hints $w \"Z Translation\"; break"
|
||||
|
||||
bind $w <Alt-Control-ButtonPress-1> "winset $w; dm con r x %x %y; \
|
||||
shift_grip_hints $w \"X Rotation\"; break"
|
||||
bind $w <Alt-Control-ButtonPress-2> "winset $w; dm con r y %x %y; \
|
||||
shift_grip_hints $w \"Y Rotation\"; break"
|
||||
bind $w <Alt-Control-ButtonPress-3> "winset $w; dm con r z %x %y; \
|
||||
shift_grip_hints $w \"Z Rotation\"; break"
|
||||
|
||||
bind $w <Alt-Shift-Control-ButtonPress-1> "winset $w; dm con s x %x %y; \
|
||||
scale_shift_grip_hints $w X; break"
|
||||
bind $w <Alt-Shift-Control-ButtonPress-2> "winset $w; dm con s y %x %y; \
|
||||
scale_shift_grip_hints $w Y; break"
|
||||
bind $w <Alt-Shift-Control-ButtonPress-3> "winset $w; dm con s z %x %y; \
|
||||
scale_shift_grip_hints $w Z; break"
|
||||
}
|
||||
}
|
||||
|
||||
proc shift_grip_hints { w hint } {
|
||||
global mged_display
|
||||
global mged_gui
|
||||
global win_to_id
|
||||
|
||||
if ![info exists win_to_id($w)] {
|
||||
return
|
||||
}
|
||||
|
||||
set id $win_to_id($w)
|
||||
set mged_gui($id,illum_label) $hint
|
||||
}
|
||||
|
||||
proc scale_shift_grip_hints { w axis } {
|
||||
if {[status state] == "OBJ EDIT"} {
|
||||
shift_grip_hints $w "$axis Scale"
|
||||
} else {
|
||||
shift_grip_hints $w "Scale/Zoom"
|
||||
}
|
||||
}
|
||||
|
||||
proc update_gui { w vname val } {
|
||||
global mged_players
|
||||
global mged_gui
|
||||
@@ -482,6 +87,14 @@ proc update_gui { w vname val } {
|
||||
}
|
||||
}
|
||||
|
||||
# Dummy stubs to prevent legacy scripts from failing if they try to invoke them
|
||||
proc default_key_bindings { w } {}
|
||||
proc default_mouse_bindings { w } {}
|
||||
proc forward_key_bindings { w } {}
|
||||
proc toggle_forward_key_bindings { w } {}
|
||||
proc set_forward_keys { w val } {}
|
||||
proc shift_grip_hints { w hint } {}
|
||||
proc scale_shift_grip_hints { w axis } {}
|
||||
|
||||
# Local Variables:
|
||||
# mode: Tcl
|
||||
|
||||
Reference in New Issue
Block a user