Compare commits

...
6 Commits
Author SHA1 Message Date
Clifford Yapp 1a7ea7976f backend consistency work 2026-09-18 18:02:31 +00:00
Clifford Yapp ec4347e7d3 Start prototyping a system for defining bindings in a toolkit agnostic way. 2026-09-18 17:53:25 +00:00
Clifford Yapp 21e034ee34 Rename so we don't end up conflicting with an MSVC header 2026-09-18 16:13:27 +00:00
Clifford Yapp 8f81f27d92 librt: correct winding for explicit DSP B-C cell cuts
The explicit ULlr permutation inverted both terrain triangle normals.
The cell intersection state machine uses normal direction to classify
entry and exit, so top-to-side rays could disappear.

An independent triangle mesh and the DSP received the same 500,000
Crofton chords. The mesh returned 3,689 hit rays, while DSP returned
3,463; the DSP surface estimate was 10.35% below the exact area.
Using the adaptive B-C permutation for explicit cuts preserves the
selected diagonal and restores outward top normals. DSP then returned
3,688 hit rays and the area error fell to 4.53%.

Add a fixed top-to-side ray that checks entry and exit distances plus
the top normal.
2026-09-18 15:45:52 +00:00
Clifford Yapp d729a7d3f2 librt: make Crofton sampling deterministic and reusable
Retain ordinary Monte Carlo chord sampling while making its stream
explicit and portable. Draw independent sphere endpoints from a
fixed-seed mt19937_64 generator so every setup is reproducible
without changing the estimator's statistical convergence model.

Convert the generator's high 53 bits explicitly to avoid library-
specific real-number conversions. Consume four values per ray so
ray offsets reproduce disjoint continuations of the same stream.

Reuse a bounded C++ worker pool and stop only after windowed area
estimates remain stable. This avoids repeated thread construction
while retaining the existing stopping criteria.

Add optional collection of ray identifiers, paired entry and exit
hit points, normals, and partition thickness. This gives
reconstruction callers structured source evidence while preserving
lightweight area and volume estimation. Link librt to the thread
target and extend the Crofton tests to verify that offset calls
exactly reproduce the uninterrupted stream.
2026-09-18 14:48:05 +00:00
Clifford Yapp 17601916f4 Use static diagonal transformation in IGES test 2026-09-18 14:02:22 +00:00
28 changed files with 2117 additions and 1239 deletions
+164
View File
@@ -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
*/
+3
View File
@@ -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 */
+46
View File
@@ -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.
+2 -1
View File
@@ -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
+362
View File
@@ -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
View File
@@ -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:
+2
View File
@@ -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)
+109
View File
@@ -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
*/
+22
View File
@@ -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"
}
]
}
}
}
+6
View File
@@ -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
+1
View File
@@ -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. */
+59
View File
@@ -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" }
]
}
}
}
+12
View File
@@ -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;
+14
View File
@@ -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;
}
+25
View File
@@ -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"
}
}
+54
View File
@@ -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)
+6
View File
@@ -0,0 +1,6 @@
{
"actions": {
"fb_print_pixel_rgb": "Print RGB values of pixel under cursor",
"fb_close_window": "Close framebuffer window"
}
}
+171 -72
View File
@@ -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
View File
@@ -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:
+1
View File
@@ -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
View File
@@ -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;
+9 -59
View File
@@ -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);
+108
View File
@@ -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)
+36 -21
View File
@@ -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
View File
@@ -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(&current_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(&current_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(&current_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(&current_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(&current_top->to_gedp->go_name),
@@ -2848,240 +3020,14 @@ to_init_default_bindings(struct bview *gdvp)
bu_vls_addr(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_top->to_gedp->go_name),
bu_vls_addr(&gdvp->gv_name),
bu_vls_addr(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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);
+9 -396
View File
@@ -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