Compare commits
6
Commits
bu_parallel
...
bindings
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a7ea7976f | ||
|
|
ec4347e7d3 | ||
|
|
21e034ee34 | ||
|
|
8f81f27d92 | ||
|
|
d729a7d3f2 | ||
|
|
17601916f4 |
@@ -0,0 +1,164 @@
|
||||
/* B I N D I N G . H
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this file; see the file named COPYING for more
|
||||
* information.
|
||||
*/
|
||||
|
||||
/** @addtogroup bu_binding
|
||||
* @brief
|
||||
* Universal input binding schema and action dispatch.
|
||||
*/
|
||||
/** @{ */
|
||||
/** @file bu/binding.h */
|
||||
|
||||
#ifndef BU_BINDING_H
|
||||
#define BU_BINDING_H
|
||||
|
||||
#include "common.h"
|
||||
#include "bu/defines.h"
|
||||
#include "bu/vls.h"
|
||||
|
||||
__BEGIN_DECLS
|
||||
|
||||
/* Standardized Attribute Keys (for W3C validation logic) */
|
||||
#define BU_BINDING_ATTR_TYPE "type"
|
||||
#define BU_BINDING_ATTR_KEY "key" /* Values MUST be W3C standard strings */
|
||||
#define BU_BINDING_ATTR_BUTTON "button" /* Values MUST be W3C button integers */
|
||||
#define BU_BINDING_ATTR_MODIFIERS "modifiers"
|
||||
|
||||
/* Standardized Event Types */
|
||||
#define BU_BINDING_EVENT_KEY_PRESS "key-press"
|
||||
#define BU_BINDING_EVENT_KEY_RELEASE "key-release"
|
||||
#define BU_BINDING_EVENT_MOUSE_PRESS "mouse-press"
|
||||
#define BU_BINDING_EVENT_MOUSE_RELEASE "mouse-release"
|
||||
#define BU_BINDING_EVENT_MOUSE_MOTION "mouse-motion"
|
||||
#define BU_BINDING_EVENT_MOUSE_SCROLL "mouse-scroll"
|
||||
|
||||
/**
|
||||
* @brief Action callback signature
|
||||
*
|
||||
* Invoked when an incoming event matches a loaded binding.
|
||||
* The callback receives the full JSON string of the triggering event.
|
||||
*
|
||||
* @param event_json Raw JSON string describing the incoming event
|
||||
* @param registered_data Opaque pointer passed during registration
|
||||
* @param event_data Opaque pointer passed during event processing
|
||||
*/
|
||||
typedef void (*bu_action_func_t)(const char *event_json, void *registered_data, void *event_data);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Load JSON bindings from a file
|
||||
*
|
||||
* Parses a JSON configuration specifying bindings for various contexts.
|
||||
* Performs two-phase validation:
|
||||
* 1. Checks standard fields (like W3C key strings) for correctness.
|
||||
* 2. Invokes context-specific validation hooks (if registered).
|
||||
*
|
||||
* @param json_path Path to the JSON file
|
||||
* @return 0 on success, <0 on error
|
||||
*/
|
||||
BU_EXPORT extern int bu_binding_load(const char *json_path);
|
||||
|
||||
/**
|
||||
* @brief Load JSON bindings from a raw string
|
||||
*
|
||||
* Parses a JSON configuration string specifying bindings for various contexts.
|
||||
* Allows applications to embed overriding bindings natively without external files.
|
||||
*
|
||||
* @param json_string Raw JSON string containing bindings
|
||||
* @return 0 on success, <0 on error
|
||||
*/
|
||||
BU_EXPORT extern int bu_binding_load_str(const char *json_string);
|
||||
|
||||
/**
|
||||
* @brief Register a JSON schema for a specific context
|
||||
*
|
||||
* Defines the allowed vocabulary for a context using a JSON schema file.
|
||||
* The schema file is lazily parsed during bu_binding_load().
|
||||
*
|
||||
* @param context The interaction context (e.g., "dm", "fb")
|
||||
* @param json_path Absolute path to the schema JSON file
|
||||
*/
|
||||
BU_EXPORT extern void bu_binding_register_schema(const char *context, const char *json_path);
|
||||
|
||||
/**
|
||||
* @brief Augment an existing schema with a custom action
|
||||
*
|
||||
* @param context The interaction context (e.g., "dm")
|
||||
* @param action_name The new abstract action string
|
||||
* @param description Human-readable description of the action
|
||||
*/
|
||||
BU_EXPORT extern void bu_binding_schema_add_action(const char *context, const char *action_name, const char *description);
|
||||
|
||||
/**
|
||||
* @brief Remove a custom action from an existing schema
|
||||
*
|
||||
* @param context The interaction context (e.g., "dm")
|
||||
* @param action_name The abstract action string to remove
|
||||
*/
|
||||
BU_EXPORT extern void bu_binding_schema_remove_action(const char *context, const char *action_name);
|
||||
|
||||
/**
|
||||
* @brief Register a C callback for a semantic action
|
||||
*
|
||||
* @param action_name The abstract action string (e.g., "fb_close_window")
|
||||
* @param func The C function to invoke when the action is triggered
|
||||
* @param client_data Opaque pointer passed to the callback
|
||||
*/
|
||||
BU_EXPORT extern void bu_binding_register_action(const char *action_name, bu_action_func_t func, void *client_data);
|
||||
|
||||
/**
|
||||
* @brief Process an incoming native event
|
||||
*
|
||||
* Matches the event JSON against the loaded bindings for the given context.
|
||||
* If a matching action is found and a handler is registered for that action,
|
||||
* the callback is invoked.
|
||||
*
|
||||
* @param context The interaction context (e.g., "view3d", "fb_raw")
|
||||
* @param event_json A JSON string containing the event attributes
|
||||
* @param event_data Opaque pointer passed to the callback representing instance state
|
||||
* @return 1 if an action was triggered and handled, 0 otherwise
|
||||
*/
|
||||
BU_EXPORT extern int bu_binding_process_event(const char *context, const char *event_json, void *event_data);
|
||||
|
||||
/**
|
||||
* @brief Check if a specific action is bound in a given context
|
||||
*
|
||||
* Use this to verify that an application has provided a binding for an essential action.
|
||||
*
|
||||
* @param context The interaction context (e.g., "view3d", "fb_raw")
|
||||
* @param action_name The abstract action string (e.g., "view_mouse_drag_rotate")
|
||||
* @return 1 if bound, 0 if not
|
||||
*/
|
||||
BU_EXPORT extern int bu_binding_action_is_bound(const char *context, const char *action_name);
|
||||
|
||||
__END_DECLS
|
||||
|
||||
#endif /* BU_BINDING_H */
|
||||
|
||||
/** @} */
|
||||
/*
|
||||
* Local Variables:
|
||||
* mode: C
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
@@ -444,6 +444,9 @@ DM_EXPORT extern int fb_get_interactive(const struct fb *ifp);
|
||||
*/
|
||||
DM_EXPORT extern int fb_next_event(struct fb *ifp, struct fb_event *e);
|
||||
|
||||
/** Dispatch an application-facing event through the framebuffer bindings. */
|
||||
DM_EXPORT extern int fb_process_event_bindings(struct fb *ifp, const struct fb_event *e);
|
||||
|
||||
|
||||
/* Library entry points */
|
||||
|
||||
|
||||
@@ -227,6 +227,32 @@ struct rt_crofton_params {
|
||||
double time_ms; /**< wall-clock time budget (ms); 0 = disabled */
|
||||
};
|
||||
|
||||
/** One solid partition observed by a Crofton ray. */
|
||||
struct rt_crofton_segment {
|
||||
point_t in_point;
|
||||
vect_t in_normal;
|
||||
point_t out_point;
|
||||
vect_t out_normal;
|
||||
fastf_t thickness;
|
||||
size_t ray_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structured samples and estimates produced by rt_crofton_collect().
|
||||
* Release @p segments with rt_crofton_result_free().
|
||||
*/
|
||||
struct rt_crofton_result {
|
||||
struct rt_crofton_segment *segments;
|
||||
size_t segment_count;
|
||||
size_t ray_count;
|
||||
size_t crossing_count;
|
||||
double surface_area;
|
||||
double volume;
|
||||
};
|
||||
|
||||
/** Initialize an rt_crofton_result before first use. */
|
||||
#define RT_CROFTON_RESULT_INIT { NULL, 0, 0, 0, 0.0, 0.0 }
|
||||
|
||||
|
||||
/**
|
||||
* Run the Cauchy-Crofton ray-sampling estimator on an already-prepared
|
||||
@@ -337,6 +363,26 @@ struct rt_crofton_params {
|
||||
*/
|
||||
RT_EXPORT extern int rt_crofton_shoot(double *out_surf_area, double *out_volume, point_t *out_aabb_min, point_t *out_aabb_max, point_t out_obb[8], point_t **out_points, size_t *out_point_count, struct rt_i *rtip, const struct rt_crofton_params *params, const fastf_t *bbox_min, const fastf_t *bbox_max);
|
||||
|
||||
/**
|
||||
* Run Crofton sampling while preserving oriented entry/exit pairs.
|
||||
*
|
||||
* @p result must be initialized with RT_CROFTON_RESULT_INIT. On success it
|
||||
* owns the returned segment array. @p rtip must already be prepared.
|
||||
* @p ray_offset skips that many samples in the deterministic sequence and
|
||||
* labels the returned rays starting at that value. This lets successive
|
||||
* calls use disjoint validation streams.
|
||||
*/
|
||||
RT_EXPORT extern int rt_crofton_collect(struct rt_crofton_result *result,
|
||||
struct rt_i *rtip,
|
||||
const struct rt_crofton_params *params,
|
||||
size_t ray_offset,
|
||||
const fastf_t *bbox_min,
|
||||
const fastf_t *bbox_max);
|
||||
|
||||
/** Release storage owned by a structured Crofton result. */
|
||||
RT_EXPORT extern void rt_crofton_result_free(
|
||||
struct rt_crofton_result *result);
|
||||
|
||||
|
||||
/**
|
||||
* Cauchy-Crofton surface-area and/or volume estimator for a primitive.
|
||||
|
||||
@@ -41,6 +41,7 @@ set(
|
||||
badmagic.c
|
||||
bitv.c
|
||||
bomb.c
|
||||
binding.cpp
|
||||
booleanize.c
|
||||
bu_init.cpp
|
||||
cache.cpp
|
||||
@@ -234,7 +235,7 @@ set(
|
||||
mime.cmake
|
||||
parallel.h
|
||||
parallel_cpp11thread.cpp
|
||||
process.h
|
||||
process_private.h
|
||||
sha1.h
|
||||
struetype.h
|
||||
vfont.h
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
/* B I N D I N G . C P P
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this file; see the file named COPYING for more
|
||||
* information.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include "bu/binding.h"
|
||||
#include "bu/log.h"
|
||||
#include "bu/malloc.h"
|
||||
#include "bu/str.h"
|
||||
#include "bu/vls.h"
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
/*
|
||||
* Internal data structures to hold the parsed bindings and callbacks
|
||||
*/
|
||||
struct ActionCallback {
|
||||
bu_action_func_t func;
|
||||
void *client_data;
|
||||
};
|
||||
|
||||
struct BindingDef {
|
||||
json match;
|
||||
std::string action;
|
||||
};
|
||||
|
||||
/* Global state for libbu bindings */
|
||||
static std::map<std::string, std::vector<BindingDef>>& get_contexts() {
|
||||
static auto* ptr = new std::map<std::string, std::vector<BindingDef>>();
|
||||
return *ptr;
|
||||
}
|
||||
static std::map<std::string, ActionCallback>& get_action_registry() {
|
||||
static auto* ptr = new std::map<std::string, ActionCallback>();
|
||||
return *ptr;
|
||||
}
|
||||
static std::map<std::string, std::string>& get_schema_paths() {
|
||||
static auto* ptr = new std::map<std::string, std::string>();
|
||||
return *ptr;
|
||||
}
|
||||
static std::map<std::string, std::set<std::string>>& get_schemas() {
|
||||
static auto* ptr = new std::map<std::string, std::set<std::string>>();
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
static void
|
||||
ensure_schema_loaded(const std::string& ctx_name)
|
||||
{
|
||||
if (get_schemas().find(ctx_name) != get_schemas().end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = get_schema_paths().find(ctx_name);
|
||||
if (it == get_schema_paths().end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream file(it->second);
|
||||
if (!file.is_open()) {
|
||||
bu_log("ensure_schema_loaded: Failed to open schema %s\n", it->second.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
json j;
|
||||
try {
|
||||
file >> j;
|
||||
} catch (const json::parse_error& e) {
|
||||
bu_log("ensure_schema_loaded: JSON parse error in schema %s: %s\n", it->second.c_str(), e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
std::set<std::string> valid_actions;
|
||||
if (j.contains("actions") && j["actions"].is_object()) {
|
||||
for (auto el = j["actions"].begin(); el != j["actions"].end(); ++el) {
|
||||
valid_actions.insert(el.key());
|
||||
}
|
||||
}
|
||||
get_schemas()[ctx_name] = valid_actions;
|
||||
}
|
||||
|
||||
/* W3C Key validation placeholder. A full implementation would check against
|
||||
* an exhaustive list of W3C standard keys (e.g., "Escape", "ArrowUp").
|
||||
* For demonstration, we'll just implement a simple sanity check.
|
||||
*/
|
||||
static int
|
||||
validate_w3c_key(const std::string& key)
|
||||
{
|
||||
/* Example: Reject completely upper-case strings that might be native C macros */
|
||||
if (key.length() > 1 && key == "RETURN") return 0;
|
||||
if (key.length() > 1 && key == "SPACE") return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* W3C Button validation. Values 0, 1, 2, 3, 4 are standard. */
|
||||
static int
|
||||
validate_w3c_button(int button)
|
||||
{
|
||||
return (button >= 0 && button <= 4);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
bu_binding_load(const char *json_path)
|
||||
{
|
||||
if (!json_path) return -1;
|
||||
|
||||
std::ifstream file(json_path);
|
||||
if (!file.is_open()) {
|
||||
bu_log("bu_binding_load: Failed to open %s\n", json_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
return bu_binding_load_str(buffer.str().c_str());
|
||||
}
|
||||
|
||||
int
|
||||
bu_binding_load_str(const char *json_string)
|
||||
{
|
||||
if (!json_string) return -1;
|
||||
|
||||
json j;
|
||||
try {
|
||||
j = json::parse(json_string);
|
||||
} catch (const json::parse_error& e) {
|
||||
bu_log("bu_binding_load_str: JSON parse error: %s\n", e.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!j.contains("contexts") || !j["contexts"].is_object()) {
|
||||
bu_log("bu_binding_load_str: Invalid JSON structure (missing 'contexts' object)\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (auto it = j["contexts"].begin(); it != j["contexts"].end(); ++it) {
|
||||
std::string ctx_name = it.key();
|
||||
const auto& ctx_obj = it.value();
|
||||
|
||||
if (!ctx_obj.contains("bindings") || !ctx_obj["bindings"].is_array()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<BindingDef> defs;
|
||||
|
||||
ensure_schema_loaded(ctx_name);
|
||||
bool has_schema = (get_schemas().find(ctx_name) != get_schemas().end());
|
||||
|
||||
for (const auto& binding : ctx_obj["bindings"]) {
|
||||
if (!binding.contains("match") || !binding.contains("action")) {
|
||||
bu_log("bu_binding_load_str: Binding missing 'match' or 'action' in context %s. Skipping.\n", ctx_name.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
const json& match = binding["match"];
|
||||
std::string action = binding["action"].get<std::string>();
|
||||
|
||||
/* Phase 1 Validation: Check W3C standard compliance */
|
||||
int is_valid = 1;
|
||||
if (match.contains(BU_BINDING_ATTR_KEY)) {
|
||||
if (!match[BU_BINDING_ATTR_KEY].is_string() || !validate_w3c_key(match[BU_BINDING_ATTR_KEY].get<std::string>())) {
|
||||
bu_log("bu_binding_load_str: Invalid W3C key '%s' in context %s. Binding rejected.\n",
|
||||
match[BU_BINDING_ATTR_KEY].dump().c_str(), ctx_name.c_str());
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
if (match.contains(BU_BINDING_ATTR_BUTTON)) {
|
||||
if (!match[BU_BINDING_ATTR_BUTTON].is_number_integer() || !validate_w3c_button(match[BU_BINDING_ATTR_BUTTON].get<int>())) {
|
||||
bu_log("bu_binding_load_str: Invalid W3C button '%s' in context %s. Binding rejected.\n",
|
||||
match[BU_BINDING_ATTR_BUTTON].dump().c_str(), ctx_name.c_str());
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) continue;
|
||||
|
||||
/* Phase 2 Validation: Schema constraints */
|
||||
if (has_schema) {
|
||||
if (get_schemas()[ctx_name].find(action) == get_schemas()[ctx_name].end()) {
|
||||
bu_log("bu_binding_load_str: Binding for action '%s' rejected by schema for context '%s'.\n",
|
||||
action.c_str(), ctx_name.c_str());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
BindingDef def;
|
||||
def.match = match;
|
||||
def.action = action;
|
||||
get_contexts()[ctx_name].insert(get_contexts()[ctx_name].begin(), def);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
bu_binding_register_schema(const char *context, const char *json_path)
|
||||
{
|
||||
if (!context || !json_path) return;
|
||||
get_schema_paths()[std::string(context)] = std::string(json_path);
|
||||
}
|
||||
|
||||
void
|
||||
bu_binding_schema_add_action(const char *context, const char *action_name, const char *description)
|
||||
{
|
||||
if (!context || !action_name) return;
|
||||
std::string ctx(context);
|
||||
ensure_schema_loaded(ctx);
|
||||
get_schemas()[ctx].insert(std::string(action_name));
|
||||
}
|
||||
|
||||
void
|
||||
bu_binding_schema_remove_action(const char *context, const char *action_name)
|
||||
{
|
||||
if (!context || !action_name) return;
|
||||
std::string ctx(context);
|
||||
ensure_schema_loaded(ctx);
|
||||
get_schemas()[ctx].erase(std::string(action_name));
|
||||
}
|
||||
|
||||
void
|
||||
bu_binding_register_action(const char *action_name, bu_action_func_t func, void *client_data)
|
||||
{
|
||||
if (!action_name || !func) return;
|
||||
|
||||
ActionCallback cb;
|
||||
cb.func = func;
|
||||
cb.client_data = client_data;
|
||||
|
||||
get_action_registry()[std::string(action_name)] = cb;
|
||||
}
|
||||
|
||||
/* Helper to check if the json 'event' contains all the key-value pairs required by 'match' */
|
||||
static int
|
||||
is_match(const json& match, const json& event)
|
||||
{
|
||||
for (auto it = match.begin(); it != match.end(); ++it) {
|
||||
if (!event.contains(it.key())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If it's an array (like modifiers), we need to ensure all elements in 'match' exist in 'event' */
|
||||
if (it.value().is_array()) {
|
||||
if (!event[it.key()].is_array()) return 0;
|
||||
|
||||
for (const auto& req_mod : it.value()) {
|
||||
int found = 0;
|
||||
for (const auto& ev_mod : event[it.key()]) {
|
||||
if (ev_mod == req_mod) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return 0;
|
||||
}
|
||||
}
|
||||
/* Otherwise, simple equality */
|
||||
else if (event[it.key()] != it.value()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
bu_binding_process_event(const char *context, const char *event_json, void *event_data)
|
||||
{
|
||||
if (!context || !event_json) return 0;
|
||||
|
||||
std::string ctx_name(context);
|
||||
auto it = get_contexts().find(ctx_name);
|
||||
if (it == get_contexts().end()) {
|
||||
return 0; /* Context not found */
|
||||
}
|
||||
|
||||
const char *json_str = event_json;
|
||||
|
||||
json event_json_obj;
|
||||
try {
|
||||
event_json_obj = json::parse(json_str);
|
||||
} catch (const json::parse_error& e) {
|
||||
bu_log("bu_binding_process_event: Invalid event JSON: %s\n", e.what());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!event_json_obj.is_object()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const auto& def : it->second) {
|
||||
if (is_match(def.match, event_json_obj)) {
|
||||
/* Match found */
|
||||
auto reg_it = get_action_registry().find(def.action);
|
||||
if (reg_it != get_action_registry().end()) {
|
||||
reg_it->second.func(event_json, reg_it->second.client_data, event_data);
|
||||
return 1;
|
||||
} else {
|
||||
/* We have a binding, but no callback registered for this action */
|
||||
bu_log("Warning: Event mapped to action '%s', but no callback is registered.\n", def.action.c_str());
|
||||
return 1; /* Still mark as handled since it matched a binding */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0; /* Unhandled */
|
||||
}
|
||||
|
||||
extern "C" int
|
||||
bu_binding_action_is_bound(const char *context, const char *action_name)
|
||||
{
|
||||
if (!context || !action_name) return 0;
|
||||
|
||||
std::string ctx(context);
|
||||
std::string act(action_name);
|
||||
|
||||
auto it = get_contexts().find(ctx);
|
||||
if (it == get_contexts().end()) return 0;
|
||||
|
||||
for (const auto& def : it->second) {
|
||||
if (def.action == act) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* mode: C++
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
+1
-1
@@ -49,7 +49,7 @@
|
||||
#include "bu/str.h"
|
||||
#include "bu/datetime.h"
|
||||
#include "bu/vls.h"
|
||||
#include "./process.h"
|
||||
#include "./process_private.h"
|
||||
|
||||
#ifndef HAVE_KILL
|
||||
# include <TlHelp32.h>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* P R O C E S S . H
|
||||
/* P R O C E S S _ P R I V A T E . H
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2013-2026 United States Government as represented by
|
||||
@@ -20,8 +20,8 @@
|
||||
|
||||
/* Private libbu process related definitions shared between multiple files */
|
||||
|
||||
#ifndef LIBBU_PROCESS_H
|
||||
#define LIBBU_PROCESS_H
|
||||
#ifndef LIBBU_PROCESS_PRIVATE_H
|
||||
#define LIBBU_PROCESS_PRIVATE_H
|
||||
|
||||
/* Apparently the termination code on Windows is arbitrary and somewhat
|
||||
* ill-defined - see https://bugs.python.org/issue31863 for more info. Try our
|
||||
@@ -29,7 +29,7 @@
|
||||
* abort identification. */
|
||||
#define BU_MSVC_ABORT_EXIT 97101 /* ascii - a == 97 && e == 101 */
|
||||
|
||||
#endif /* LIBBU_PROCESS_H */
|
||||
#endif /* LIBBU_PROCESS_PRIVATE_H */
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
@@ -10,6 +10,7 @@ set(
|
||||
test_bomb.c
|
||||
test_badmagic.c
|
||||
test_basename.c
|
||||
test_binding.cpp
|
||||
test_bitv.c
|
||||
test_booleanize.c
|
||||
test_cache.cpp
|
||||
@@ -122,6 +123,7 @@ brlcad_addexec(bu_gethostname test_gethostname.c libbu TEST)
|
||||
brlcad_add_test(NAME bu_argv COMMAND bu_test test_argv)
|
||||
brlcad_add_test(NAME bu_backtrace COMMAND bu_test test_backtrace)
|
||||
brlcad_add_test(NAME bu_bomb COMMAND bu_test test_bomb "$<TARGET_FILE:bu_test>")
|
||||
brlcad_add_test(NAME bu_binding COMMAND bu_test test_binding "${CMAKE_CURRENT_SOURCE_DIR}/test_bindings.json")
|
||||
brlcad_add_test(NAME bu_dir COMMAND bu_test test_dir)
|
||||
brlcad_add_test(NAME bu_list_empty COMMAND bu_test test_list 0)
|
||||
brlcad_add_test(NAME bu_list_single COMMAND bu_test test_list 1)
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/* T E S T _ B I N D I N G . C P P
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this file; see the file named COPYING for more
|
||||
* information.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bu/binding.h"
|
||||
#include "bu/vls.h"
|
||||
|
||||
int action_triggered = 0;
|
||||
|
||||
void mock_action_handler(const char *event_json, void *registered_data, void *event_data) {
|
||||
action_triggered = 1;
|
||||
printf("Action handler triggered with JSON: %s\n", event_json);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bu_setprogname(argv[0]);
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s <path_to_test_bindings.json>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *json_path = argv[1];
|
||||
|
||||
/* Register schema to restrict allowed actions */
|
||||
bu_binding_schema_add_action("fb", "fb_zoom", "Zoom");
|
||||
bu_binding_schema_add_action("fb", "fb_close_window", "Close Window");
|
||||
|
||||
/* Register hooks before loading JSON */
|
||||
bu_binding_register_action("fb_close_window", mock_action_handler, NULL);
|
||||
|
||||
printf("Loading bindings from %s\n", json_path);
|
||||
if (bu_binding_load(json_path) < 0) {
|
||||
printf("Failed to load bindings.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Test 1: Simulate a matching event */
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"mouse-press\", \"button\": 2}");
|
||||
|
||||
action_triggered = 0;
|
||||
bu_binding_process_event("fb", bu_vls_cstr(&ev_json), NULL);
|
||||
|
||||
if (!action_triggered) {
|
||||
printf("Test 1 Failed: Expected action was not triggered.\n");
|
||||
return -1;
|
||||
}
|
||||
printf("Test 1 Passed: Action triggered successfully.\n");
|
||||
|
||||
/* Test 2: Simulate a non-matching event */
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"mouse-press\", \"button\": 1}");
|
||||
action_triggered = 0;
|
||||
bu_binding_process_event("fb", bu_vls_cstr(&ev_json), NULL);
|
||||
|
||||
if (action_triggered) {
|
||||
printf("Test 2 Failed: Action should not have been triggered.\n");
|
||||
return -1;
|
||||
}
|
||||
printf("Test 2 Passed: Non-matching event correctly ignored.\n");
|
||||
|
||||
/* Test 3: The Escape key binding should have been rejected by the validator */
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"key-press\", \"key\": \"Escape\"}");
|
||||
action_triggered = 0;
|
||||
bu_binding_process_event("fb", bu_vls_cstr(&ev_json), NULL);
|
||||
|
||||
if (action_triggered) {
|
||||
printf("Test 3 Failed: Escape binding should have been filtered by the validator.\n");
|
||||
return -1;
|
||||
}
|
||||
printf("Test 3 Passed: Validator correctly rejected binding at load time.\n");
|
||||
|
||||
bu_vls_free(&ev_json);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* mode: C++
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"contexts": {
|
||||
"fb": {
|
||||
"bindings": [
|
||||
{
|
||||
"match": {
|
||||
"type": "mouse-press",
|
||||
"button": 2
|
||||
},
|
||||
"action": "fb_close_window"
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"type": "key-press",
|
||||
"key": "Escape"
|
||||
},
|
||||
"action": "fb_invalid_action"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,11 @@ set_property(
|
||||
PROPERTY COMPILE_DEFINITIONS "DM_PLUGIN_SUFFIX=\"${CMAKE_SHARED_LIBRARY_SUFFIX}\""
|
||||
)
|
||||
|
||||
# Files related to key/mouse binding management
|
||||
brlcad_manage_files(dm_bindings.json ${DATA_DIR}/bindings)
|
||||
brlcad_manage_files(dm_schema.json ${DATA_DIR}/bindings)
|
||||
brlcad_manage_files(fb_schema.json ${DATA_DIR}/bindings)
|
||||
|
||||
brlcad_find_package(PNG REQUIRED)
|
||||
find_package(OpenSSL QUIET)
|
||||
|
||||
@@ -165,3 +170,4 @@ cmakefiles(${libdm_ignore_files})
|
||||
# indent-tabs-mode: t
|
||||
# End:
|
||||
# ex: shiftwidth=2 tabstop=8
|
||||
|
||||
|
||||
@@ -2925,6 +2925,7 @@ X24_handle_event(struct fb *ifp, XEvent *event)
|
||||
case ButtonPress:
|
||||
{
|
||||
int button = (int) event->xbutton.button;
|
||||
int w3c_button = 0;
|
||||
|
||||
/* In interactive mode, report the event to the application
|
||||
* rather than consuming it here. */
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"contexts": {
|
||||
"fb": {
|
||||
"description": "Standard interactions for raw framebuffers (rt, fbserv)",
|
||||
"bindings": [
|
||||
{
|
||||
"match": {
|
||||
"type": "mouse-press",
|
||||
"button": 1
|
||||
},
|
||||
"action": "fb_print_pixel_rgb"
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"type": "mouse-press",
|
||||
"button": 2
|
||||
},
|
||||
"action": "fb_close_window"
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"type": "key-press",
|
||||
"key": "Escape"
|
||||
},
|
||||
"action": "fb_close_window"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dm": {
|
||||
"description": "Standard bindings for the Geometry Editor (qged & mged)",
|
||||
"bindings": [
|
||||
{ "match": { "type": "key-press", "key": "A" }, "action": "view_toggle_adc" },
|
||||
{ "match": { "type": "key-press", "key": "M" }, "action": "view_toggle_model_axes" },
|
||||
{ "match": { "type": "key-press", "key": "V" }, "action": "view_toggle_view_axes" },
|
||||
{ "match": { "type": "key-press", "key": "2" }, "action": "view_2" },
|
||||
{ "match": { "type": "key-press", "key": "3" }, "action": "view_3" },
|
||||
{ "match": { "type": "key-press", "key": "4" }, "action": "view_4" },
|
||||
{ "match": { "type": "key-press", "key": "5" }, "action": "view_5" },
|
||||
{ "match": { "type": "key-press", "key": "6" }, "action": "view_6" },
|
||||
{ "match": { "type": "key-press", "key": "7" }, "action": "view_7" },
|
||||
{ "match": { "type": "key-press", "key": "F" }, "action": "view_front" },
|
||||
{ "match": { "type": "key-press", "key": "T" }, "action": "view_top" },
|
||||
{ "match": { "type": "key-press", "key": "B" }, "action": "view_bottom" },
|
||||
{ "match": { "type": "key-press", "key": "L" }, "action": "view_left" },
|
||||
{ "match": { "type": "key-press", "key": "R", "modifiers": ["Shift"] }, "action": "view_rear" },
|
||||
{ "match": { "type": "key-press", "key": "R" }, "action": "view_right" },
|
||||
{ "match": { "type": "mouse-press", "button": 0 }, "action": "view_mouse_press_left" },
|
||||
{ "match": { "type": "mouse-release", "button": 0 }, "action": "view_mouse_release_left" },
|
||||
{ "match": { "type": "mouse-release", "button": 2 }, "action": "view_mouse_release_right" },
|
||||
{ "match": { "type": "mouse-release", "button": 1 }, "action": "view_mouse_release_middle" },
|
||||
{ "match": { "type": "mouse-move", "button": 0, "modifiers": ["Control"] }, "action": "view_mouse_drag_rotate" },
|
||||
{ "match": { "type": "mouse-move", "button": 0, "modifiers": ["Shift"] }, "action": "view_mouse_drag_translate" },
|
||||
{ "match": { "type": "mouse-move", "button": 0, "modifiers": ["Control", "Shift"] }, "action": "view_mouse_drag_scale" },
|
||||
{ "match": { "type": "mouse-move", "button": 0 }, "action": "view_mouse_drag_adjust" },
|
||||
{ "match": { "type": "mouse-wheel" }, "action": "view_mouse_wheel_scale" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,10 +66,22 @@ dm_init_msgs()
|
||||
}
|
||||
|
||||
|
||||
#include "bu/binding.h"
|
||||
|
||||
static void
|
||||
libdm_init(void)
|
||||
{
|
||||
bu_vls_init(&init_msgs);
|
||||
|
||||
const char *dm_schema = bu_dir(NULL, 0, BU_DIR_DATA, "bindings", "dm_schema.json", NULL);
|
||||
if (dm_schema) {
|
||||
bu_binding_register_schema("dm", dm_schema);
|
||||
}
|
||||
|
||||
const char *fb_schema = bu_dir(NULL, 0, BU_DIR_DATA, "bindings", "fb_schema.json", NULL);
|
||||
if (fb_schema) {
|
||||
bu_binding_register_schema("fb", fb_schema);
|
||||
}
|
||||
|
||||
const char *ppath = bu_dir(NULL, 0, BU_DIR_LIBEXEC, "dm", NULL);
|
||||
char **dm_filenames;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include "bu/binding.h"
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
@@ -62,6 +63,13 @@ dm_open(void *ctx, void *interp, const char *type, int argc, const char *argv[])
|
||||
|
||||
const struct dm *d = d_it->second;
|
||||
struct dm *dmp = d->i->dm_open(ctx, interp, argc, argv);
|
||||
|
||||
char *bindings_path = (char *)bu_dir(NULL, 0, BU_DIR_DATA, "bindings", "dm_bindings.json", NULL);
|
||||
if (bindings_path) {
|
||||
bu_binding_load(bindings_path);
|
||||
bu_free(bindings_path, "bindings_path");
|
||||
}
|
||||
|
||||
return dmp;
|
||||
}
|
||||
|
||||
@@ -528,6 +536,12 @@ found_interface:
|
||||
|
||||
return FB_NULL;
|
||||
}
|
||||
char *bindings_path = (char *)bu_dir(NULL, 0, BU_DIR_DATA, "bindings", "dm_bindings.json", NULL);
|
||||
if (bindings_path) {
|
||||
bu_binding_load(bindings_path);
|
||||
bu_free(bindings_path, "bindings_path");
|
||||
}
|
||||
|
||||
return ifp;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"actions": {
|
||||
"view_toggle_adc": "Toggle Angle Distance Cursor",
|
||||
"view_toggle_model_axes": "Toggle Model Axes",
|
||||
"view_toggle_view_axes": "Toggle View Axes",
|
||||
"view_2": "Set View 2",
|
||||
"view_3": "Set View 3",
|
||||
"view_4": "Set View 4",
|
||||
"view_5": "Set View 5",
|
||||
"view_6": "Set View 6",
|
||||
"view_7": "Set View 7",
|
||||
"view_front": "Set Front View",
|
||||
"view_top": "Set Top View",
|
||||
"view_bottom": "Set Bottom View",
|
||||
"view_left": "Set Left View",
|
||||
"view_right": "Set Right View",
|
||||
"view_rear": "Set Rear View",
|
||||
"view_mouse_drag_rotate": "Rotate view via mouse drag",
|
||||
"view_mouse_drag_translate": "Translate view via mouse drag",
|
||||
"view_mouse_drag_scale": "Scale view via mouse drag",
|
||||
"view_mouse_wheel_scale": "Scale view via mouse wheel",
|
||||
"view_mouse_press_left": "Left mouse press action",
|
||||
"view_mouse_press_right": "Right mouse press action"
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@
|
||||
#include "./include/private.h"
|
||||
#include "icv.h"
|
||||
#include "dm.h"
|
||||
#include "bu/binding.h"
|
||||
|
||||
|
||||
struct fb *fb_get(void)
|
||||
@@ -258,6 +259,59 @@ long fb_poll_rate(struct fb *ifp)
|
||||
return ifp->i->if_poll_refresh_rate;
|
||||
}
|
||||
|
||||
static int
|
||||
fb_event_button_to_w3c(int button)
|
||||
{
|
||||
return (button >= 1 && button <= 3) ? button - 1 : button;
|
||||
}
|
||||
|
||||
int
|
||||
fb_process_event_bindings(struct fb *ifp, const struct fb_event *event)
|
||||
{
|
||||
struct bu_vls event_json = BU_VLS_INIT_ZERO;
|
||||
int handled = 0;
|
||||
|
||||
if (!ifp || !event)
|
||||
return 0;
|
||||
|
||||
switch (event->type) {
|
||||
case FB_EVENT_BUTTON_PRESS:
|
||||
bu_vls_sprintf(&event_json,
|
||||
"{\"type\": \"mouse-press\", \"button\": %d, \"x\": %d, \"y\": %d}",
|
||||
fb_event_button_to_w3c(event->button), event->x, event->y);
|
||||
break;
|
||||
case FB_EVENT_BUTTON_RELEASE:
|
||||
bu_vls_sprintf(&event_json,
|
||||
"{\"type\": \"mouse-release\", \"button\": %d, \"x\": %d, \"y\": %d}",
|
||||
fb_event_button_to_w3c(event->button), event->x, event->y);
|
||||
break;
|
||||
case FB_EVENT_MOTION:
|
||||
bu_vls_sprintf(&event_json,
|
||||
"{\"type\": \"mouse-move\", \"x\": %d, \"y\": %d}",
|
||||
event->x, event->y);
|
||||
break;
|
||||
case FB_EVENT_KEY_PRESS:
|
||||
if (event->keycode == 27) {
|
||||
bu_vls_sprintf(&event_json, "{\"type\": \"key-press\", \"key\": \"Escape\"}");
|
||||
} else if (event->keycode >= 32 && event->keycode <= 126) {
|
||||
bu_vls_sprintf(&event_json,
|
||||
"{\"type\": \"key-press\", \"key\": \"%c\"}",
|
||||
event->keycode);
|
||||
}
|
||||
break;
|
||||
case FB_EVENT_CLOSE:
|
||||
bu_vls_sprintf(&event_json, "{\"type\": \"key-press\", \"key\": \"Escape\"}");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (bu_vls_strlen(&event_json) > 0)
|
||||
handled = bu_binding_process_event("fb", bu_vls_cstr(&event_json), ifp);
|
||||
bu_vls_free(&event_json);
|
||||
return handled;
|
||||
}
|
||||
|
||||
void fb_set_interactive(struct fb *ifp, int on)
|
||||
{
|
||||
if (!ifp)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"actions": {
|
||||
"fb_print_pixel_rgb": "Print RGB values of pixel under cursor",
|
||||
"fb_close_window": "Close framebuffer window"
|
||||
}
|
||||
}
|
||||
+171
-72
@@ -61,6 +61,10 @@
|
||||
#include "dm.h"
|
||||
#include "./fb_ogl.h"
|
||||
|
||||
#include "bu/binding.h"
|
||||
#include "bu/file.h"
|
||||
#include <X11/keysym.h>
|
||||
|
||||
extern struct fb ogl_interface;
|
||||
|
||||
#define DIRECT_COLOR_VISUAL_ALLOWED 0
|
||||
@@ -774,6 +778,52 @@ ogl_configureWindow(struct fb *ifp, int width, int height)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
ogl_action_fb_print_pixel_rgb(const struct bu_vls *event_json, void *registered_data, void *event_data)
|
||||
{
|
||||
struct fb *ifp = (struct fb *)event_data;
|
||||
int x = -1, y = -1;
|
||||
char *p;
|
||||
|
||||
if (!ifp) return;
|
||||
|
||||
if ((p = strstr(bu_vls_cstr(event_json), "\"x\":"))) {
|
||||
sscanf(p + 4, "%d", &x);
|
||||
}
|
||||
if ((p = strstr(bu_vls_cstr(event_json), "\"y\":"))) {
|
||||
sscanf(p + 4, "%d", &y);
|
||||
}
|
||||
|
||||
if (x < 0 || y < 0) {
|
||||
fb_log("No RGB (outside image viewport)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t memidx = (y * WIN(ifp)->mi_pixwidth) * sizeof(struct fb_pixel);
|
||||
struct fb_pixel *oglp = (struct fb_pixel *)&ifp->i->if_mem[memidx];
|
||||
|
||||
fb_log("At image (%d, %d), real RGB=(%3d %3d %3d)\n",
|
||||
x, y, (int)oglp[x].red, (int)oglp[x].green, (int)oglp[x].blue);
|
||||
}
|
||||
|
||||
static void
|
||||
ogl_action_fb_close_window(const struct bu_vls *event_json, void *registered_data, void *event_data)
|
||||
{
|
||||
struct fb *ifp = (struct fb *)event_data;
|
||||
if (ifp) OGL(ifp)->alive = 0;
|
||||
}
|
||||
|
||||
static int bindings_loaded = 0;
|
||||
static void
|
||||
ogl_init_bindings(void)
|
||||
{
|
||||
if (bindings_loaded) return;
|
||||
bindings_loaded = 1;
|
||||
|
||||
|
||||
bu_binding_register_action("fb_print_pixel_rgb", ogl_action_fb_print_pixel_rgb, NULL);
|
||||
bu_binding_register_action("fb_close_window", ogl_action_fb_close_window, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
ogl_do_event(struct fb *ifp)
|
||||
@@ -781,76 +831,122 @@ ogl_do_event(struct fb *ifp)
|
||||
XEvent event;
|
||||
|
||||
while (XCheckWindowEvent(OGL(ifp)->dispp, OGL(ifp)->wind,
|
||||
OGL(ifp)->event_mask, &event)) {
|
||||
switch (event.type) {
|
||||
case Expose:
|
||||
if (!OGL(ifp)->use_ext_ctrl)
|
||||
expose_callback(ifp);
|
||||
break;
|
||||
case ButtonPress:
|
||||
{
|
||||
int button = (int) event.xbutton.button;
|
||||
if (button == Button1) {
|
||||
/* Check for single button mouse remap.
|
||||
* ctrl-1 => 2
|
||||
* meta-1 => 3
|
||||
* cmdkey => 3
|
||||
*/
|
||||
if (event.xbutton.state & ControlMask) {
|
||||
button = Button2;
|
||||
} else if (event.xbutton.state & Mod1Mask) {
|
||||
button = Button3;
|
||||
} else if (event.xbutton.state & Mod2Mask) {
|
||||
button = Button3;
|
||||
}
|
||||
}
|
||||
|
||||
switch (button) {
|
||||
case Button1:
|
||||
break;
|
||||
case Button2:
|
||||
{
|
||||
int x, y;
|
||||
register struct fb_pixel *oglp;
|
||||
|
||||
x = event.xbutton.x;
|
||||
y = ifp->i->if_height - event.xbutton.y - 1;
|
||||
|
||||
if (x < 0 || y < 0) {
|
||||
fb_log("No RGB (outside image viewport)\n");
|
||||
break;
|
||||
}
|
||||
|
||||
size_t memidx = (y * WIN(ifp)->mi_pixwidth) * sizeof(struct fb_pixel);
|
||||
oglp = (struct fb_pixel *)&ifp->i->if_mem[memidx];
|
||||
|
||||
fb_log("At image (%d, %d), real RGB=(%3d %3d %3d)\n",
|
||||
x, y, (int)oglp[x].red, (int)oglp[x].green, (int)oglp[x].blue);
|
||||
|
||||
break;
|
||||
}
|
||||
case Button3:
|
||||
OGL(ifp)->alive = 0;
|
||||
break;
|
||||
default:
|
||||
fb_log("unhandled mouse event\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ConfigureNotify:
|
||||
{
|
||||
XConfigureEvent *conf = (XConfigureEvent *)&event;
|
||||
|
||||
if (conf->width == OGL(ifp)->win_width &&
|
||||
conf->height == OGL(ifp)->win_height)
|
||||
return;
|
||||
|
||||
ogl_configureWindow(ifp, conf->width, conf->height);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
OGL(ifp)->event_mask, &event)) {
|
||||
switch (event.type) {
|
||||
case Expose:
|
||||
if (!OGL(ifp)->use_ext_ctrl)
|
||||
expose_callback(ifp);
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev = {FB_EVENT_EXPOSE, 0, 0, 0, 0, 0};
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
}
|
||||
break;
|
||||
case ButtonPress:
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = FB_EVENT_BUTTON_PRESS;
|
||||
ev.button = (int)event.xbutton.button;
|
||||
ev.x = event.xbutton.x;
|
||||
ev.y = ifp->i->if_height - event.xbutton.y - 1;
|
||||
ev.state = (int)event.xbutton.state;
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
} else {
|
||||
int button = (int)event.xbutton.button;
|
||||
int w3c_button = 0;
|
||||
if (button == Button1) {
|
||||
if (event.xbutton.state & ControlMask) button = Button2;
|
||||
else if (event.xbutton.state & Mod1Mask) button = Button3;
|
||||
else if (event.xbutton.state & Mod2Mask) button = Button3;
|
||||
}
|
||||
if (button == Button1) w3c_button = 0;
|
||||
else if (button == Button2) w3c_button = 1;
|
||||
else if (button == Button3) w3c_button = 2;
|
||||
else w3c_button = button;
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json,
|
||||
"{\"type\": \"mouse-press\", \"button\": %d, \"x\": %d, \"y\": %d}",
|
||||
w3c_button, event.xbutton.x,
|
||||
ifp->i->if_height - event.xbutton.y - 1);
|
||||
bu_binding_process_event("fb", bu_vls_cstr(&ev_json), ifp);
|
||||
bu_vls_free(&ev_json);
|
||||
}
|
||||
break;
|
||||
case ButtonRelease:
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = FB_EVENT_BUTTON_RELEASE;
|
||||
ev.button = (int)event.xbutton.button;
|
||||
ev.x = event.xbutton.x;
|
||||
ev.y = ifp->i->if_height - event.xbutton.y - 1;
|
||||
ev.state = (int)event.xbutton.state;
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
}
|
||||
break;
|
||||
case MotionNotify:
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = FB_EVENT_MOTION;
|
||||
ev.x = event.xmotion.x;
|
||||
ev.y = ifp->i->if_height - event.xmotion.y - 1;
|
||||
ev.state = (int)event.xmotion.state;
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
}
|
||||
break;
|
||||
case KeyPress:
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev;
|
||||
char keybuf[8] = {0};
|
||||
KeySym keysym;
|
||||
int n;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = FB_EVENT_KEY_PRESS;
|
||||
n = XLookupString(&event.xkey, keybuf, sizeof(keybuf), &keysym, NULL);
|
||||
ev.keycode = (n > 0) ? (unsigned char)keybuf[0] : (int)keysym;
|
||||
ev.state = (int)event.xkey.state;
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
} else {
|
||||
KeySym keysym = XLookupKeysym(&event.xkey, 0);
|
||||
char *key_str = XKeysymToString(keysym);
|
||||
if (key_str) {
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json,
|
||||
"{\"type\": \"key-press\", \"key\": \"%s\"}", key_str);
|
||||
bu_binding_process_event("fb", bu_vls_cstr(&ev_json), ifp);
|
||||
bu_vls_free(&ev_json);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ClientMessage:
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev = {FB_EVENT_CLOSE, 0, 0, 0, 0, 0};
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
} else {
|
||||
OGL(ifp)->alive = 0;
|
||||
}
|
||||
break;
|
||||
case ConfigureNotify:
|
||||
{
|
||||
XConfigureEvent *conf = (XConfigureEvent *)&event;
|
||||
if (conf->width == OGL(ifp)->win_width &&
|
||||
conf->height == OGL(ifp)->win_height)
|
||||
break;
|
||||
ogl_configureWindow(ifp, conf->width, conf->height);
|
||||
if (fb_get_interactive(ifp)) {
|
||||
struct fb_event ev;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = FB_EVENT_RESIZE;
|
||||
ev.x = conf->width;
|
||||
ev.y = conf->height;
|
||||
fb_enqueue_event(ifp, &ev);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1021,9 +1117,10 @@ fb_ogl_open(struct fb *ifp, const char *file, int width, int height)
|
||||
int mode, i;
|
||||
long valuemask;
|
||||
XSetWindowAttributes swa;
|
||||
|
||||
FB_CK_FB(ifp->i);
|
||||
|
||||
ogl_init_bindings();
|
||||
|
||||
/*
|
||||
* First, attempt to determine operating mode for this open,
|
||||
* based upon the "unit number" or flags.
|
||||
@@ -1214,7 +1311,7 @@ fb_ogl_open(struct fb *ifp, const char *file, int width, int height)
|
||||
OGL(ifp)->vip->screen);
|
||||
swa.event_mask = OGL(ifp)->event_mask =
|
||||
ExposureMask | KeyPressMask | KeyReleaseMask |
|
||||
ButtonPressMask | ButtonReleaseMask;
|
||||
ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask;
|
||||
swa.colormap = OGL(ifp)->xcmap;
|
||||
|
||||
#define XCreateWindowDebug(display, parent, x, y, width, height, border_width, depth, class, visual, valuemask, attributes) \
|
||||
@@ -1381,6 +1478,8 @@ ogl_open_existing(struct fb *ifp, int width, int height, struct fb_platform_spec
|
||||
{
|
||||
struct ogl_fb_info *ogl_internal = (struct ogl_fb_info *)fb_p->data;
|
||||
BU_CKMAG(fb_p, FB_OGL_MAGIC, "ogl framebuffer");
|
||||
|
||||
ogl_init_bindings();
|
||||
return open_existing(ifp, ogl_internal->dpy, ogl_internal->win,
|
||||
ogl_internal->cmap, ogl_internal->vip, width, height, ogl_internal->glxc,
|
||||
ogl_internal->double_buffer, ogl_internal->soft_cmap);
|
||||
|
||||
@@ -188,8 +188,7 @@ test_uncertain()
|
||||
require(result.shells.empty() || (result.shells.front().orientation == ShellOrientation::Indeterminate &&
|
||||
!result.shells.front().corrected), "invalid parameter-loop senses caused an incorrect face repair");
|
||||
auto flat = box();
|
||||
ON_Xform flatten;
|
||||
flatten.DiagonalTransformation(1.0);
|
||||
ON_Xform flatten = ON_Xform::DiagonalTransformation(1.0);
|
||||
flatten[2][2] = 1.0e-13;
|
||||
require(flat->Transform(flatten), "could not construct thin shell");
|
||||
result = repair_brep_orientation(*flat);
|
||||
|
||||
+241
-246
@@ -37,6 +37,12 @@ extern "C" {
|
||||
#include "qtcad/defines.h"
|
||||
#include "bindings.h"
|
||||
|
||||
extern "C" {
|
||||
#include "bu/binding.h"
|
||||
#include "bu/file.h"
|
||||
#include "bu/log.h"
|
||||
}
|
||||
|
||||
typedef void (*bounds_update_t)(struct bview *);
|
||||
static std::unordered_map<struct bview *, bounds_update_t> drag_bounds_updates;
|
||||
static std::unordered_map<struct bview *, long long> drag_update_ts;
|
||||
@@ -68,247 +74,256 @@ restore_drag_bounds_update(struct bview *v, int refresh_bounds)
|
||||
drag_update_ts.erase(v);
|
||||
}
|
||||
|
||||
// TODO - look into QShortcut, see if it might be a better way
|
||||
// to manage this
|
||||
static int bindings_loaded = 0;
|
||||
|
||||
static void qtcad_action_toggle_adc(const char *, void *, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
if (v) v->gv_s->gv_adc.draw = !v->gv_s->gv_adc.draw;
|
||||
}
|
||||
static void qtcad_action_toggle_model_axes(const char *, void *, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
if (v) v->gv_s->gv_model_axes.draw = !v->gv_s->gv_model_axes.draw;
|
||||
}
|
||||
static void qtcad_action_toggle_view_axes(const char *, void *, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
if (v) v->gv_s->gv_view_axes.draw = !v->gv_s->gv_view_axes.draw;
|
||||
}
|
||||
|
||||
static void qtcad_action_view_set(const char *, void *registered_data, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
const char *aet_str = (const char *)registered_data;
|
||||
if (v && aet_str) {
|
||||
bn_decode_vect(v->gv_aet, aet_str);
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
}
|
||||
}
|
||||
|
||||
static void qtcad_action_mouse_release(const char *event_json, void *, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
if (!v) return;
|
||||
|
||||
int cx = 0, cy = 0, mode = 0, dx = 1, dy = 1;
|
||||
char *p;
|
||||
if ((p = strstr(event_json, "\"cx\":"))) sscanf(p + 5, "%d", &cx);
|
||||
if ((p = strstr(event_json, "\"cy\":"))) sscanf(p + 5, "%d", &cy);
|
||||
if ((p = strstr(event_json, "\"mode\":"))) sscanf(p + 7, "%d", &mode);
|
||||
|
||||
int button = 0;
|
||||
if ((p = strstr(event_json, "\"button\":"))) sscanf(p + 9, "%d", &button);
|
||||
|
||||
unsigned long long view_flags = BV_IDLE;
|
||||
if (button == 0) { // Left
|
||||
if (mode != BV_CENTER) { view_flags = BV_SCALE; dx = 10; dy = 5; }
|
||||
else { view_flags = BV_CENTER; dx = cx; dy = cy; }
|
||||
} else if (button == 2) { // Right
|
||||
if (mode == BV_CENTER) return;
|
||||
view_flags = BV_SCALE; dx = 1; dy = 2;
|
||||
} else if (button == 1) { // Middle
|
||||
view_flags = BV_CENTER; dx = cx; dy = cy;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
point_t keypt = VINIT_ZERO;
|
||||
bv_adjust(v, dx, dy, keypt, 0, view_flags);
|
||||
}
|
||||
|
||||
static void qtcad_action_adjust_view(const char *event_json, void *registered_data, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
if (!v) return;
|
||||
|
||||
int dx = 0, dy = 0, mode = 0;
|
||||
int view_flags = (int)(intptr_t)registered_data;
|
||||
char *p;
|
||||
|
||||
if ((p = strstr(event_json, "\"dx\":"))) sscanf(p + 5, "%d", &dx);
|
||||
if ((p = strstr(event_json, "\"dy\":"))) sscanf(p + 5, "%d", &dy);
|
||||
if ((p = strstr(event_json, "\"mode\":"))) sscanf(p + 7, "%d", &mode);
|
||||
|
||||
if (view_flags == 0) { // dynamic fallback
|
||||
view_flags = mode;
|
||||
if (mode == BV_CENTER) view_flags = BV_SCALE;
|
||||
}
|
||||
|
||||
if (view_flags == BV_SCALE) {
|
||||
int mdelta = (abs(dx) > abs(dy)) ? dx : -dy;
|
||||
int f = (int)(2*100*(double)abs(mdelta)/(double)v->gv_height);
|
||||
if (mdelta > 0) { dy = 101 + f; dx = 100; }
|
||||
else { dy = 99 - f; dx = 100; }
|
||||
}
|
||||
|
||||
point_t center;
|
||||
MAT_DELTAS_GET_NEG(center, v->gv_center);
|
||||
|
||||
if (view_flags & (BV_ROT | BV_TRANS | BV_SCALE))
|
||||
suspend_drag_bounds_update(v);
|
||||
|
||||
bv_adjust(v, dx, dy, center, 0, view_flags);
|
||||
}
|
||||
|
||||
static void qtcad_action_wheel_scale(const char *event_json, void *, void *event_data) {
|
||||
struct bview *v = (struct bview *)event_data;
|
||||
if (!v) return;
|
||||
|
||||
int delta = 0;
|
||||
char *p;
|
||||
if ((p = strstr(event_json, "\"delta\":"))) sscanf(p + 8, "%d", &delta);
|
||||
|
||||
int dx = 100 + delta;
|
||||
int dy = 100;
|
||||
point_t origin = VINIT_ZERO;
|
||||
bv_adjust(v, dx, dy, origin, 0, BV_SCALE);
|
||||
}
|
||||
|
||||
extern "C" QCAD_EXPORT int qtcad_load_bindings(const char *json_path) {
|
||||
if (bu_binding_load(json_path) < 0) {
|
||||
bu_log("libqtcad: Failed to load bindings from %s\n", json_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!bu_binding_action_is_bound("dm", "view_toggle_adc") ||
|
||||
!bu_binding_action_is_bound("dm", "view_2")) {
|
||||
bu_log("libqtcad Validation Failed: Provided bindings missing required standard actions.\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qtcad_init_bindings(void) {
|
||||
if (bindings_loaded) return;
|
||||
bindings_loaded = 1;
|
||||
|
||||
bu_binding_register_action("view_toggle_adc", qtcad_action_toggle_adc, NULL);
|
||||
bu_binding_register_action("view_toggle_model_axes", qtcad_action_toggle_model_axes, NULL);
|
||||
bu_binding_register_action("view_toggle_view_axes", qtcad_action_toggle_view_axes, NULL);
|
||||
|
||||
bu_binding_register_action("view_2", qtcad_action_view_set, (void*)"35 -25 0");
|
||||
bu_binding_register_action("view_3", qtcad_action_view_set, (void*)"35 25 0");
|
||||
bu_binding_register_action("view_4", qtcad_action_view_set, (void*)"45 45 0");
|
||||
bu_binding_register_action("view_5", qtcad_action_view_set, (void*)"145 25 0");
|
||||
bu_binding_register_action("view_6", qtcad_action_view_set, (void*)"215 25 0");
|
||||
bu_binding_register_action("view_7", qtcad_action_view_set, (void*)"325 25 0");
|
||||
bu_binding_register_action("view_front", qtcad_action_view_set, (void*)"0 0 0");
|
||||
bu_binding_register_action("view_top", qtcad_action_view_set, (void*)"270 90 0");
|
||||
bu_binding_register_action("view_bottom", qtcad_action_view_set, (void*)"270 -90 0");
|
||||
bu_binding_register_action("view_left", qtcad_action_view_set, (void*)"90 0 0");
|
||||
bu_binding_register_action("view_rear", qtcad_action_view_set, (void*)"180 0 0");
|
||||
bu_binding_register_action("view_right", qtcad_action_view_set, (void*)"270 0 0");
|
||||
|
||||
bu_binding_register_action("view_mouse_release_left", qtcad_action_mouse_release, NULL);
|
||||
bu_binding_register_action("view_mouse_release_right", qtcad_action_mouse_release, NULL);
|
||||
bu_binding_register_action("view_mouse_release_middle", qtcad_action_mouse_release, NULL);
|
||||
|
||||
bu_binding_register_action("view_mouse_drag_rotate", qtcad_action_adjust_view, (void*)(intptr_t)BV_ROT);
|
||||
bu_binding_register_action("view_mouse_drag_translate", qtcad_action_adjust_view, (void*)(intptr_t)BV_TRANS);
|
||||
bu_binding_register_action("view_mouse_drag_scale", qtcad_action_adjust_view, (void*)(intptr_t)BV_SCALE);
|
||||
bu_binding_register_action("view_mouse_drag_adjust", qtcad_action_adjust_view, (void*)(intptr_t)0);
|
||||
bu_binding_register_action("view_mouse_wheel_scale", qtcad_action_wheel_scale, NULL);
|
||||
}
|
||||
|
||||
static void append_modifiers(struct bu_vls *vls, QInputEvent *e) {
|
||||
if (!e->modifiers()) return;
|
||||
bu_vls_printf(vls, ", \"modifiers\": [");
|
||||
int added = 0;
|
||||
if (e->modifiers().testFlag(Qt::ShiftModifier)) { bu_vls_printf(vls, "\"Shift\""); added=1; }
|
||||
if (e->modifiers().testFlag(Qt::ControlModifier)) { if(added) bu_vls_printf(vls, ", "); bu_vls_printf(vls, "\"Control\""); added=1; }
|
||||
if (e->modifiers().testFlag(Qt::AltModifier)) { if(added) bu_vls_printf(vls, ", "); bu_vls_printf(vls, "\"Alt\""); }
|
||||
bu_vls_printf(vls, "]");
|
||||
}
|
||||
|
||||
int CADkeyPressEvent(struct bview *v, int UNUSED(x_prev), int UNUSED(y_prev), QKeyEvent *k)
|
||||
{
|
||||
QTCAD_EVENT("keyPress", 1);
|
||||
if (!v)
|
||||
return 0;
|
||||
#if 0
|
||||
QString kstr = QKeySequence(k->key()).toString();
|
||||
bu_log("%s\n", kstr.toStdString().c_str());
|
||||
#endif
|
||||
switch (k->key()) {
|
||||
case 'A':
|
||||
v->gv_s->gv_adc.draw = !v->gv_s->gv_adc.draw;
|
||||
return 1;
|
||||
case 'M':
|
||||
v->gv_s->gv_model_axes.draw = !v->gv_s->gv_model_axes.draw;
|
||||
return 1;
|
||||
case 'V':
|
||||
v->gv_s->gv_view_axes.draw = !v->gv_s->gv_view_axes.draw;
|
||||
return 1;
|
||||
case '2':
|
||||
bn_decode_vect(v->gv_aet, "35 -25 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case '3':
|
||||
bn_decode_vect(v->gv_aet, "35 25 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case '4':
|
||||
bn_decode_vect(v->gv_aet, "45 45 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case '5':
|
||||
bn_decode_vect(v->gv_aet, "145 25 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case '6':
|
||||
bn_decode_vect(v->gv_aet, "215 25 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case '7':
|
||||
bn_decode_vect(v->gv_aet, "325 25 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case 'F':
|
||||
bn_decode_vect(v->gv_aet, "0 0 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case 'T':
|
||||
bn_decode_vect(v->gv_aet, "270 90 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case 'B':
|
||||
bn_decode_vect(v->gv_aet, "270 -90 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case 'L':
|
||||
bn_decode_vect(v->gv_aet, "90 0 0");
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
case 'R':
|
||||
if (k->modifiers().testFlag(Qt::ShiftModifier) == true) {
|
||||
bn_decode_vect(v->gv_aet, "180 0 0");
|
||||
} else {
|
||||
bn_decode_vect(v->gv_aet, "270 0 0");
|
||||
}
|
||||
bv_mat_aet(v);
|
||||
bv_update(v);
|
||||
return 1;
|
||||
default:
|
||||
break;
|
||||
if (!v) return 0;
|
||||
qtcad_init_bindings();
|
||||
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
char key_str[8] = {0};
|
||||
if (k->key() >= 0x20 && k->key() <= 0x7E) {
|
||||
key_str[0] = (char)k->key();
|
||||
} else {
|
||||
QString kstr = QKeySequence(k->key()).toString();
|
||||
strncpy(key_str, kstr.toStdString().c_str(), 7);
|
||||
}
|
||||
return 0;
|
||||
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"key-press\", \"key\": \"%s\"", key_str);
|
||||
append_modifiers(&ev_json, k);
|
||||
bu_vls_printf(&ev_json, "}");
|
||||
|
||||
int handled = bu_binding_process_event("dm", bu_vls_cstr(&ev_json), v);
|
||||
bu_vls_free(&ev_json);
|
||||
return handled;
|
||||
}
|
||||
|
||||
int CADmousePressEvent(struct bview *v, int UNUSED(x_prev), int UNUSED(y_prev), QMouseEvent *e)
|
||||
{
|
||||
QTCAD_EVENT("mousePress", 1);
|
||||
if (!v) return 0;
|
||||
qtcad_init_bindings();
|
||||
|
||||
if (!v)
|
||||
return 0;
|
||||
int button = -1;
|
||||
if (e->buttons().testFlag(Qt::LeftButton)) button = 0;
|
||||
else if (e->buttons().testFlag(Qt::MiddleButton)) button = 1;
|
||||
else if (e->buttons().testFlag(Qt::RightButton)) button = 2;
|
||||
if (button < 0) return 0;
|
||||
|
||||
// If we're intending the mouse motion to do the work,
|
||||
// then the press has to be a no-op. If we're going
|
||||
// to do configurable key bindings, this will take some
|
||||
// thought - if we want unmodded left button to be a
|
||||
// rotation, and Ctrl+Left to do something else, these
|
||||
// checks are all going to have to be exact-flag-combo-only
|
||||
// actions.
|
||||
if (e->modifiers()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (e->buttons().testFlag(Qt::LeftButton)) {
|
||||
//bu_log("Press Left\n");
|
||||
}
|
||||
if (e->buttons().testFlag(Qt::RightButton)) {
|
||||
//bu_log("Press Right\n");
|
||||
}
|
||||
if (e->buttons().testFlag(Qt::MiddleButton)) {
|
||||
//bu_log("Press Middle\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"mouse-press\", \"button\": %d", button);
|
||||
append_modifiers(&ev_json, e);
|
||||
bu_vls_printf(&ev_json, "}");
|
||||
|
||||
int handled = bu_binding_process_event("dm", bu_vls_cstr(&ev_json), v);
|
||||
bu_vls_free(&ev_json);
|
||||
return handled;
|
||||
}
|
||||
|
||||
int CADmouseReleaseEvent(struct bview *v, double x_press, double y_press, int UNUSED(x_prev), int UNUSED(y_prev), QMouseEvent *e, int mode)
|
||||
{
|
||||
QTCAD_EVENT("mouseRelease", 1);
|
||||
|
||||
if (!v)
|
||||
return 0;
|
||||
|
||||
if (!v) return 0;
|
||||
qtcad_init_bindings();
|
||||
restore_drag_bounds_update(v, 1);
|
||||
|
||||
// If we're intending the mouse motion to do the work,
|
||||
// then the release has to be a no-op. If we're going
|
||||
// to do configurable key bindings, this will take some
|
||||
// thought - if we want unmodded left button to be a
|
||||
// rotation, and Ctrl+Left to do something else, these
|
||||
// checks are all going to have to be exact-flag-combo-only
|
||||
// actions.
|
||||
if (e->modifiers()) {
|
||||
return 0;
|
||||
}
|
||||
if (e->buttons().testFlag(Qt::LeftButton) || e->buttons().testFlag(Qt::RightButton)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double cx, cy;
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
cx = (double)e->x();
|
||||
cy = (double)e->y();
|
||||
double cx = e->x();
|
||||
double cy = e->y();
|
||||
#else
|
||||
cx = e->position().x();
|
||||
cy = e->position().y();
|
||||
double cx = e->position().x();
|
||||
double cy = e->position().y();
|
||||
#endif
|
||||
if ((fabs(cx - x_press) > 10) || (fabs(cy - y_press) > 10))
|
||||
return 0;
|
||||
|
||||
int dx = 1;
|
||||
int dy = 1;
|
||||
unsigned long long view_flags = BV_IDLE;
|
||||
if ((fabs(cx - x_press) > 10) || (fabs(cy - y_press) > 10)) return 0;
|
||||
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
//bu_log("Release Left\n");
|
||||
if (mode != BV_CENTER) {
|
||||
view_flags = BV_SCALE;
|
||||
dx = 10;
|
||||
dy = 5;
|
||||
} else {
|
||||
view_flags = BV_CENTER;
|
||||
dx = (int)cx;
|
||||
dy = (int)cy;
|
||||
}
|
||||
}
|
||||
if (e->button() == Qt::RightButton) {
|
||||
//bu_log("Release Right\n");
|
||||
if (mode == BV_CENTER)
|
||||
return 0;
|
||||
view_flags = BV_SCALE;
|
||||
dx = 1;
|
||||
dy = 2;
|
||||
}
|
||||
int button = -1;
|
||||
if (e->button() == Qt::LeftButton) button = 0;
|
||||
else if (e->button() == Qt::MiddleButton) button = 1;
|
||||
else if (e->button() == Qt::RightButton) button = 2;
|
||||
if (button < 0) return 0;
|
||||
|
||||
if (e->button() == Qt::MiddleButton) {
|
||||
//bu_log("Release Center\n");
|
||||
view_flags = BV_CENTER;
|
||||
dx = (int)cx;
|
||||
dy = (int)cy;
|
||||
}
|
||||
|
||||
point_t keypt = VINIT_ZERO;
|
||||
return bv_adjust(v, dx, dy, keypt, 0, view_flags);
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"mouse-release\", \"button\": %d, \"cx\": %d, \"cy\": %d, \"mode\": %d", button, (int)cx, (int)cy, mode);
|
||||
append_modifiers(&ev_json, e);
|
||||
bu_vls_printf(&ev_json, "}");
|
||||
|
||||
int handled = bu_binding_process_event("dm", bu_vls_cstr(&ev_json), v);
|
||||
bu_vls_free(&ev_json);
|
||||
return handled;
|
||||
}
|
||||
|
||||
int CADmouseMoveEvent(struct bview *v, int x_prev, int y_prev, QMouseEvent *e, int mode)
|
||||
{
|
||||
QTCAD_EVENT("mouseMove", 2);
|
||||
if (!v) return 0;
|
||||
qtcad_init_bindings();
|
||||
|
||||
if (!v)
|
||||
return 0;
|
||||
|
||||
unsigned long long view_flags = BV_IDLE;
|
||||
|
||||
if (x_prev == -INT_MAX) {
|
||||
//x_prev = e->x();
|
||||
//y_prev = e->y();
|
||||
return 0;
|
||||
}
|
||||
|
||||
view_flags = mode;
|
||||
if (mode == BV_CENTER)
|
||||
view_flags = BV_SCALE;
|
||||
|
||||
if (e->buttons().testFlag(Qt::LeftButton)) {
|
||||
//bu_log("Left\n");
|
||||
|
||||
if (e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
//bu_log("Ctrl+Left\n");
|
||||
view_flags = BV_ROT;
|
||||
}
|
||||
|
||||
if (e->modifiers().testFlag(Qt::ShiftModifier)) {
|
||||
//bu_log("Shift+Left\n");
|
||||
view_flags = BV_TRANS;
|
||||
}
|
||||
|
||||
if (e->modifiers().testFlag(Qt::ShiftModifier) && e->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
//bu_log("Ctrl+Shift+Left\n");
|
||||
view_flags = BV_SCALE;
|
||||
}
|
||||
}
|
||||
|
||||
if (e->buttons().testFlag(Qt::MiddleButton)) {
|
||||
//bu_log("Middle\n");
|
||||
}
|
||||
|
||||
if (e->buttons().testFlag(Qt::RightButton)) {
|
||||
//bu_log("Right\n");
|
||||
}
|
||||
|
||||
if (!e->buttons().testFlag(Qt::LeftButton))
|
||||
return 0;
|
||||
if (x_prev == -INT_MAX) return 0;
|
||||
if (!e->buttons().testFlag(Qt::LeftButton)) return 0;
|
||||
|
||||
long long now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
std::unordered_map<struct bview *, long long>::iterator ts_it = drag_update_ts.find(v);
|
||||
if (ts_it != drag_update_ts.end() && (now_ms - ts_it->second) < drag_update_interval_ms)
|
||||
return -1;
|
||||
if (ts_it != drag_update_ts.end() && (now_ms - ts_it->second) < drag_update_interval_ms) return -1;
|
||||
drag_update_ts[v] = now_ms;
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
@@ -319,53 +334,33 @@ int CADmouseMoveEvent(struct bview *v, int x_prev, int y_prev, QMouseEvent *e, i
|
||||
int dy = e->position().y() - y_prev;
|
||||
#endif
|
||||
|
||||
if (view_flags == BV_SCALE) {
|
||||
// Build in some sensitivity to how much the mouse moved when doing
|
||||
// a motion based scale
|
||||
int mdelta = (abs(dx) > abs(dy)) ? dx : -dy;
|
||||
int f = (int)(2*100*(double)abs(mdelta)/(double)v->gv_height);
|
||||
|
||||
if (mdelta > 0) {
|
||||
dy = 101 + f;
|
||||
dx = 100;
|
||||
} else {
|
||||
dy = 99 - f;
|
||||
dx = 100;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO - the key point and the mode/flags are all hardcoded
|
||||
// right now, but eventually for shift grips they will need to
|
||||
// respond to the various mod keys. The intent is to set flags
|
||||
// based on which mod keys are set to allow bv_adjust to
|
||||
// do the correct math.
|
||||
point_t center;
|
||||
MAT_DELTAS_GET_NEG(center, v->gv_center);
|
||||
|
||||
if (view_flags & (BV_ROT | BV_TRANS | BV_SCALE))
|
||||
suspend_drag_bounds_update(v);
|
||||
|
||||
return bv_adjust(v, dx, dy, center, 0, view_flags);
|
||||
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"mouse-move\", \"button\": 0, \"dx\": %d, \"dy\": %d, \"mode\": %d", dx, dy, mode);
|
||||
append_modifiers(&ev_json, e);
|
||||
bu_vls_printf(&ev_json, "}");
|
||||
|
||||
int handled = bu_binding_process_event("dm", bu_vls_cstr(&ev_json), v);
|
||||
bu_vls_free(&ev_json);
|
||||
return handled;
|
||||
}
|
||||
|
||||
int CADwheelEvent(struct bview *v, QWheelEvent *e)
|
||||
{
|
||||
QTCAD_EVENT("mouseWheel", 1);
|
||||
|
||||
if (!v)
|
||||
return 0;
|
||||
if (!v) return 0;
|
||||
qtcad_init_bindings();
|
||||
|
||||
QPoint delta = e->angleDelta();
|
||||
int mdelta = -1* delta.y() / 8;
|
||||
|
||||
int dx = 100 + mdelta;
|
||||
int dy = 100;
|
||||
|
||||
point_t origin = VINIT_ZERO;
|
||||
return bv_adjust(v, dx, dy, origin, 0, BV_SCALE);
|
||||
int mdelta = -1 * delta.y() / 8;
|
||||
|
||||
struct bu_vls ev_json = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&ev_json, "{\"type\": \"mouse-wheel\", \"delta\": %d", mdelta);
|
||||
append_modifiers(&ev_json, e);
|
||||
bu_vls_printf(&ev_json, "}");
|
||||
|
||||
int handled = bu_binding_process_event("dm", bu_vls_cstr(&ev_json), v);
|
||||
bu_vls_free(&ev_json);
|
||||
return handled;
|
||||
}
|
||||
|
||||
// Local Variables:
|
||||
|
||||
@@ -427,6 +427,7 @@ endif(BRLCAD_ENABLE_SPR)
|
||||
set(LIBRT_PUBLIC_LIBS ${librt_deps} OPENNURBS::OPENNURBS)
|
||||
set(
|
||||
LIBRT_PRIVATE_LIBS
|
||||
Threads::Threads
|
||||
${OPENCL_LIBS}
|
||||
mmesh::mmesh
|
||||
REGEX::REGEX
|
||||
|
||||
+469
-203
@@ -45,24 +45,28 @@
|
||||
* intersecting closed surface), and total_chord is the sum of solid
|
||||
* segment lengths.
|
||||
*
|
||||
* References:
|
||||
* Li et al. (2003), "Using low-discrepancy sequences and the Crofton
|
||||
* formula to compute surface areas of geometric models",
|
||||
* Computer-Aided Design 35, 771-782.
|
||||
*
|
||||
* Liu et al. (2010), "Surface area estimation of digitized 3D objects
|
||||
* using quasi-Monte Carlo methods", Pattern Recognition 43, 3900-3909.
|
||||
* Chord endpoints are independent pseudorandom points on the bounding
|
||||
* sphere. Reinitializing the generator with a fixed seed makes sampling
|
||||
* reproducible without changing the ordinary Monte Carlo convergence
|
||||
* behavior on which the estimator's stability checks rely.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <string.h>
|
||||
#include <thread>
|
||||
#include <time.h>
|
||||
#include <vector>
|
||||
|
||||
#include "vmath.h"
|
||||
#include "bu/log.h"
|
||||
@@ -100,8 +104,15 @@
|
||||
#define RT_CROFTON_STABILITY_MIN_WINDOWS 2u
|
||||
|
||||
/** Fixed seed for reproducible estimates in tests and cross-platform runs. */
|
||||
#define RT_CROFTON_RNG_SEED 0x9e3779b97f4a7c15ULL
|
||||
#define RT_CROFTON_RNG_SEED UINT64_C(0x9e3779b97f4a7c15)
|
||||
|
||||
/** Two sphere coordinates for each of two chord endpoints. */
|
||||
#define RT_CROFTON_RANDOM_VALUES_PER_RAY 4u
|
||||
|
||||
/** Convert the high 53 random bits exactly to a double in [0, 1). */
|
||||
static constexpr unsigned int CROFTON_RANDOM_SHIFT = 11u;
|
||||
static constexpr double CROFTON_RANDOM_SCALE =
|
||||
1.0 / 9007199254740992.0;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Internal types */
|
||||
@@ -110,6 +121,7 @@
|
||||
struct crofton_ray {
|
||||
point_t r_pt;
|
||||
vect_t r_dir;
|
||||
size_t id;
|
||||
};
|
||||
|
||||
struct crofton_shared {
|
||||
@@ -117,14 +129,19 @@ struct crofton_shared {
|
||||
size_t total_crossings; /* in+out hit events */
|
||||
double total_chord; /* solid segment length sum (mm) */
|
||||
size_t total_rays; /* rays fired (hits + misses) */
|
||||
size_t ray_offset; /* first identifier in this sample stream */
|
||||
point_t *points;
|
||||
size_t point_count;
|
||||
size_t point_capacity;
|
||||
struct rt_crofton_segment *segments;
|
||||
size_t segment_count;
|
||||
size_t segment_capacity;
|
||||
bool collect_points;
|
||||
bool collect_segments;
|
||||
};
|
||||
|
||||
struct crofton_worker_data {
|
||||
struct application *ap; /* per-CPU application struct */
|
||||
struct application ap; /* per-CPU application struct */
|
||||
struct crofton_ray *rays; /* shared ray array (read-only) */
|
||||
size_t start;
|
||||
size_t end;
|
||||
@@ -135,6 +152,10 @@ struct crofton_worker_data {
|
||||
point_t *local_points;
|
||||
size_t local_point_count;
|
||||
size_t local_point_capacity;
|
||||
struct rt_crofton_segment *local_segments;
|
||||
size_t local_segment_count;
|
||||
size_t local_segment_capacity;
|
||||
size_t current_ray;
|
||||
};
|
||||
|
||||
|
||||
@@ -143,46 +164,88 @@ struct crofton_worker_data {
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int
|
||||
crofton_hit(struct application *ap, struct partition *PartHeadp, struct seg *UNUSED(segs))
|
||||
crofton_hit(struct application *ap, struct partition *PartHeadp,
|
||||
struct seg *UNUSED(segs))
|
||||
{
|
||||
struct crofton_worker_data *wd = (struct crofton_worker_data *)ap->a_uptr;
|
||||
|
||||
struct crofton_worker_data *worker =
|
||||
(struct crofton_worker_data *)ap->a_uptr;
|
||||
size_t crossings = 0;
|
||||
double chord = 0.0;
|
||||
double chord = 0.0;
|
||||
|
||||
struct partition *pp;
|
||||
for (pp = PartHeadp->pt_forw; pp != PartHeadp; pp = pp->pt_forw) {
|
||||
/* Each partition contributes an in-hit and an out-hit */
|
||||
crossings += 2;
|
||||
chord += pp->pt_outhit->hit_dist - pp->pt_inhit->hit_dist;
|
||||
if (wd->shared->collect_points) {
|
||||
if (wd->local_point_count + 2 > wd->local_point_capacity) {
|
||||
const size_t initial_capacity = 1024;
|
||||
size_t new_capacity = wd->local_point_capacity ?
|
||||
2 * wd->local_point_capacity : initial_capacity;
|
||||
while (new_capacity < wd->local_point_count + 2)
|
||||
new_capacity *= 2;
|
||||
wd->local_points = (point_t *)bu_realloc(wd->local_points,
|
||||
new_capacity * sizeof(point_t), "Crofton worker hit points");
|
||||
wd->local_point_capacity = new_capacity;
|
||||
}
|
||||
VJOIN1(wd->local_points[wd->local_point_count], ap->a_ray.r_pt,
|
||||
pp->pt_inhit->hit_dist, ap->a_ray.r_dir);
|
||||
wd->local_point_count++;
|
||||
VJOIN1(wd->local_points[wd->local_point_count], ap->a_ray.r_pt,
|
||||
pp->pt_outhit->hit_dist, ap->a_ray.r_dir);
|
||||
wd->local_point_count++;
|
||||
}
|
||||
struct partition *partition;
|
||||
for (partition = PartHeadp->pt_forw; partition != PartHeadp;
|
||||
partition = partition->pt_forw) {
|
||||
fastf_t thickness = partition->pt_outhit->hit_dist -
|
||||
partition->pt_inhit->hit_dist;
|
||||
crossings += 2;
|
||||
chord += thickness;
|
||||
|
||||
if (worker->shared->collect_points) {
|
||||
if (worker->local_point_count + 2 >
|
||||
worker->local_point_capacity) {
|
||||
const size_t initial_capacity = 1024;
|
||||
size_t new_capacity = worker->local_point_capacity ?
|
||||
2 * worker->local_point_capacity : initial_capacity;
|
||||
while (new_capacity < worker->local_point_count + 2)
|
||||
new_capacity *= 2;
|
||||
worker->local_points = (point_t *)bu_realloc(
|
||||
worker->local_points,
|
||||
new_capacity * sizeof(point_t),
|
||||
"Crofton worker hit points");
|
||||
worker->local_point_capacity = new_capacity;
|
||||
}
|
||||
VJOIN1(worker->local_points[worker->local_point_count],
|
||||
ap->a_ray.r_pt, partition->pt_inhit->hit_dist,
|
||||
ap->a_ray.r_dir);
|
||||
worker->local_point_count++;
|
||||
VJOIN1(worker->local_points[worker->local_point_count],
|
||||
ap->a_ray.r_pt, partition->pt_outhit->hit_dist,
|
||||
ap->a_ray.r_dir);
|
||||
worker->local_point_count++;
|
||||
}
|
||||
|
||||
if (worker->shared->collect_segments) {
|
||||
if (worker->local_segment_count + 1 >
|
||||
worker->local_segment_capacity) {
|
||||
const size_t initial_capacity = 512;
|
||||
size_t new_capacity = worker->local_segment_capacity ?
|
||||
2 * worker->local_segment_capacity : initial_capacity;
|
||||
worker->local_segments =
|
||||
(struct rt_crofton_segment *)bu_realloc(
|
||||
worker->local_segments,
|
||||
new_capacity * sizeof(struct rt_crofton_segment),
|
||||
"Crofton worker segments");
|
||||
worker->local_segment_capacity = new_capacity;
|
||||
}
|
||||
|
||||
struct rt_crofton_segment *sample =
|
||||
&worker->local_segments[worker->local_segment_count];
|
||||
struct soltab *in_solid =
|
||||
partition->pt_inseg->seg_stp;
|
||||
struct soltab *out_solid =
|
||||
partition->pt_outseg->seg_stp;
|
||||
VJOIN1(sample->in_point, ap->a_ray.r_pt,
|
||||
partition->pt_inhit->hit_dist, ap->a_ray.r_dir);
|
||||
VJOIN1(sample->out_point, ap->a_ray.r_pt,
|
||||
partition->pt_outhit->hit_dist, ap->a_ray.r_dir);
|
||||
RT_HIT_NORMAL(sample->in_normal, partition->pt_inhit,
|
||||
in_solid, &ap->a_ray, partition->pt_inflip);
|
||||
RT_HIT_NORMAL(sample->out_normal, partition->pt_outhit,
|
||||
out_solid, &ap->a_ray, partition->pt_outflip);
|
||||
sample->thickness = thickness;
|
||||
sample->ray_id = worker->rays[worker->current_ray].id;
|
||||
if (!VNEAR_ZERO(sample->in_normal, VUNITIZE_TOL) &&
|
||||
!VNEAR_ZERO(sample->out_normal, VUNITIZE_TOL))
|
||||
worker->local_segment_count++;
|
||||
}
|
||||
}
|
||||
|
||||
wd->local_crossings += crossings;
|
||||
wd->local_chord += chord;
|
||||
wd->local_rays += 1;
|
||||
|
||||
worker->local_crossings += crossings;
|
||||
worker->local_chord += chord;
|
||||
worker->local_rays++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
crofton_miss(struct application *ap)
|
||||
{
|
||||
@@ -193,159 +256,273 @@ crofton_miss(struct application *ap)
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Parallel worker */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void
|
||||
crofton_worker(int id, void *data)
|
||||
{
|
||||
struct crofton_worker_data *wd = &((struct crofton_worker_data *)data)[id - 1];
|
||||
struct application *ap = wd->ap;
|
||||
struct crofton_ray *rays = wd->rays;
|
||||
|
||||
for (size_t i = wd->start; i < wd->end; i++) {
|
||||
VMOVE(ap->a_ray.r_pt, rays[i].r_pt);
|
||||
VMOVE(ap->a_ray.r_dir, rays[i].r_dir);
|
||||
/* r_dir is already unit-length (set during ray generation) */
|
||||
rt_shootray(ap);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Point / ray generation */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static double
|
||||
crofton_rand01(std::mt19937_64 &rng)
|
||||
crofton_random01(std::mt19937_64 &rng)
|
||||
{
|
||||
return std::generate_canonical<double, 53>(rng);
|
||||
return static_cast<double>(rng() >> CROFTON_RANDOM_SHIFT) *
|
||||
CROFTON_RANDOM_SCALE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
random_point_on_sphere(double radius, const point_t center, point_t out, std::mt19937_64 &rng)
|
||||
random_point_on_sphere(double radius, const point_t center, double u,
|
||||
double v, point_t point)
|
||||
{
|
||||
double theta = 2.0 * M_PI * crofton_rand01(rng);
|
||||
double phi = acos(2.0 * crofton_rand01(rng) - 1.0);
|
||||
double sp = sin(phi);
|
||||
out[X] = center[X] + radius * sp * cos(theta);
|
||||
out[Y] = center[Y] + radius * sp * sin(theta);
|
||||
out[Z] = center[Z] + radius * cos(phi);
|
||||
double z = 1.0 - 2.0 * u;
|
||||
double radial = sqrt(std::max(0.0, 1.0 - z * z));
|
||||
double angle = 2.0 * M_PI * v;
|
||||
point[X] = center[X] + radius * radial * cos(angle);
|
||||
point[Y] = center[Y] + radius * radial * sin(angle);
|
||||
point[Z] = center[Z] + radius * z;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate @p nrays chord rays from 2*nrays random points on the
|
||||
* bounding sphere. Each ray connects one randomly chosen point to
|
||||
* another.
|
||||
* Generate independent random chord endpoints. Pairing consecutive IID
|
||||
* sphere points has the same distribution as shuffling a point array before
|
||||
* pairing, but consumes exactly four random values per ray. That fixed
|
||||
* stride lets callers continue the deterministic stream with ray_offset.
|
||||
*/
|
||||
static void
|
||||
generate_rays(struct crofton_ray *rays, size_t nrays,
|
||||
double radius, const point_t center, std::mt19937_64 &rng)
|
||||
generate_rays(struct crofton_ray *rays, size_t ray_count, size_t first_id,
|
||||
double radius, const point_t center, std::mt19937_64 &rng)
|
||||
{
|
||||
size_t npts = nrays * 2;
|
||||
|
||||
point_t *pts = (point_t *)bu_calloc(npts, sizeof(point_t), "crofton pts");
|
||||
for (size_t i = 0; i < npts; i++)
|
||||
random_point_on_sphere(radius, center, pts[i], rng);
|
||||
|
||||
/* Shuffle so pairing is random */
|
||||
for (size_t i = npts - 1; i > 0; i--) {
|
||||
size_t j = (size_t)(crofton_rand01(rng) * (i + 1));
|
||||
if (j > i) j = i;
|
||||
point_t tmp;
|
||||
VMOVE(tmp, pts[i]);
|
||||
VMOVE(pts[i], pts[j]);
|
||||
VMOVE(pts[j], tmp);
|
||||
for (size_t i = 0; i < ray_count; i++) {
|
||||
point_t endpoint;
|
||||
random_point_on_sphere(radius, center, crofton_random01(rng),
|
||||
crofton_random01(rng), rays[i].r_pt);
|
||||
random_point_on_sphere(radius, center, crofton_random01(rng),
|
||||
crofton_random01(rng), endpoint);
|
||||
VSUB2(rays[i].r_dir, endpoint, rays[i].r_pt);
|
||||
VUNITIZE(rays[i].r_dir);
|
||||
rays[i].id = first_id + i;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < nrays; i++) {
|
||||
VMOVE(rays[i].r_pt, pts[i * 2]);
|
||||
VSUB2(rays[i].r_dir, pts[i * 2 + 1], pts[i * 2]);
|
||||
VUNITIZE(rays[i].r_dir);
|
||||
}
|
||||
|
||||
bu_free(pts, "crofton pts");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Reusable parallel workers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
class CroftonWorkerPool
|
||||
{
|
||||
public:
|
||||
CroftonWorkerPool(const struct application *ap_template,
|
||||
struct resource *resources, size_t worker_count) :
|
||||
count(worker_count), data(worker_count)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
data[i].ap = *ap_template;
|
||||
data[i].ap.a_resource = &resources[i];
|
||||
data[i].shared = NULL;
|
||||
data[i].ap.a_uptr = &data[i];
|
||||
}
|
||||
|
||||
try {
|
||||
executor = std::thread(&CroftonWorkerPool::run_workers, this);
|
||||
} catch (...) {
|
||||
free_worker_points();
|
||||
throw;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
if (!workers_started.wait_for(lock, worker_start_timeout,
|
||||
[this]() { return registered == count; })) {
|
||||
stopping = true;
|
||||
work_ready.notify_all();
|
||||
lock.unlock();
|
||||
executor.join();
|
||||
free_worker_points();
|
||||
throw std::runtime_error("worker startup timed out");
|
||||
}
|
||||
}
|
||||
|
||||
~CroftonWorkerPool()
|
||||
{
|
||||
stop_and_join();
|
||||
free_worker_points();
|
||||
}
|
||||
|
||||
CroftonWorkerPool(const CroftonWorkerPool &) = delete;
|
||||
CroftonWorkerPool &operator=(const CroftonWorkerPool &) = delete;
|
||||
|
||||
void run(struct crofton_ray *rays, size_t nrays,
|
||||
struct crofton_shared *shared)
|
||||
{
|
||||
const size_t rays_per_worker = nrays / count;
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
struct crofton_worker_data &wd = data[i];
|
||||
wd.rays = rays;
|
||||
wd.start = i * rays_per_worker;
|
||||
wd.end = (i == count - 1) ? nrays : (i + 1) * rays_per_worker;
|
||||
wd.shared = shared;
|
||||
wd.local_crossings = 0;
|
||||
wd.local_chord = 0.0;
|
||||
wd.local_rays = 0;
|
||||
wd.local_point_count = 0;
|
||||
wd.local_segment_count = 0;
|
||||
}
|
||||
completed = 0;
|
||||
generation++;
|
||||
work_ready.notify_all();
|
||||
work_done.wait(lock, [this]() { return completed == count; });
|
||||
}
|
||||
|
||||
const std::vector<struct crofton_worker_data> &worker_data() const
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
private:
|
||||
/* Native thread creation should complete promptly. A bounded startup wait
|
||||
* turns partial bu_parallel startup into a reported failure, not a hang. */
|
||||
static constexpr std::chrono::seconds worker_start_timeout{5};
|
||||
|
||||
static void worker_entry(int UNUSED(id), void *context)
|
||||
{
|
||||
CroftonWorkerPool *pool = static_cast<CroftonWorkerPool *>(context);
|
||||
size_t slot = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(pool->mutex);
|
||||
slot = pool->registered++;
|
||||
if (pool->registered == pool->count)
|
||||
pool->workers_started.notify_one();
|
||||
}
|
||||
pool->worker(slot);
|
||||
}
|
||||
|
||||
void run_workers()
|
||||
{
|
||||
bu_parallel(worker_entry, count, this);
|
||||
}
|
||||
|
||||
void worker(size_t slot)
|
||||
{
|
||||
size_t observed_generation = 0;
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
while (true) {
|
||||
work_ready.wait(lock, [this, observed_generation]() {
|
||||
return stopping || generation != observed_generation;
|
||||
});
|
||||
if (stopping)
|
||||
return;
|
||||
|
||||
observed_generation = generation;
|
||||
struct crofton_worker_data *wd = &data[slot];
|
||||
lock.unlock();
|
||||
for (size_t i = wd->start; i < wd->end; i++) {
|
||||
wd->current_ray = i;
|
||||
VMOVE(wd->ap.a_ray.r_pt, wd->rays[i].r_pt);
|
||||
VMOVE(wd->ap.a_ray.r_dir, wd->rays[i].r_dir);
|
||||
rt_shootray(&wd->ap);
|
||||
}
|
||||
lock.lock();
|
||||
completed++;
|
||||
if (completed == count)
|
||||
work_done.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void stop_and_join()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
stopping = true;
|
||||
work_ready.notify_all();
|
||||
}
|
||||
if (executor.joinable())
|
||||
executor.join();
|
||||
}
|
||||
|
||||
void free_worker_points()
|
||||
{
|
||||
for (struct crofton_worker_data &wd : data) {
|
||||
if (wd.local_points)
|
||||
bu_free(wd.local_points, "Crofton worker hit points");
|
||||
if (wd.local_segments)
|
||||
bu_free(wd.local_segments, "Crofton worker segments");
|
||||
wd.local_points = NULL;
|
||||
wd.local_segments = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
size_t count;
|
||||
std::vector<struct crofton_worker_data> data;
|
||||
std::thread executor;
|
||||
std::mutex mutex;
|
||||
std::condition_variable workers_started;
|
||||
std::condition_variable work_ready;
|
||||
std::condition_variable work_done;
|
||||
size_t registered = 0;
|
||||
size_t generation = 0;
|
||||
size_t completed = 0;
|
||||
bool stopping = false;
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* One iteration: fire nrays, accumulate into shared */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void
|
||||
do_one_iteration(struct application *ap_template,
|
||||
struct resource *resources,
|
||||
size_t nrays,
|
||||
double radius,
|
||||
const point_t center,
|
||||
struct crofton_shared *shared,
|
||||
std::mt19937_64 &rng)
|
||||
do_one_iteration(CroftonWorkerPool &pool, size_t ray_count, double radius,
|
||||
const point_t center, struct crofton_shared *shared,
|
||||
std::mt19937_64 &rng)
|
||||
{
|
||||
struct crofton_ray *rays = (struct crofton_ray *)bu_calloc(
|
||||
nrays, sizeof(struct crofton_ray), "crofton rays");
|
||||
ray_count, sizeof(struct crofton_ray), "Crofton rays");
|
||||
generate_rays(rays, ray_count, shared->ray_offset + shared->total_rays,
|
||||
radius, center, rng);
|
||||
pool.run(rays, ray_count, shared);
|
||||
|
||||
generate_rays(rays, nrays, radius, center, rng);
|
||||
for (const struct crofton_worker_data &worker : pool.worker_data()) {
|
||||
shared->total_crossings += worker.local_crossings;
|
||||
shared->total_chord += worker.local_chord;
|
||||
shared->total_rays += worker.local_rays;
|
||||
|
||||
size_t ncpus = bu_avail_cpus();
|
||||
if (ncpus < 1) ncpus = 1;
|
||||
if (ncpus > MAX_PSW) ncpus = MAX_PSW;
|
||||
if (worker.local_point_count) {
|
||||
const size_t needed =
|
||||
shared->point_count + worker.local_point_count;
|
||||
if (needed > shared->point_capacity) {
|
||||
size_t new_capacity = shared->point_capacity ?
|
||||
2 * shared->point_capacity : needed;
|
||||
if (new_capacity < needed)
|
||||
new_capacity = needed;
|
||||
shared->points = (point_t *)bu_realloc(shared->points,
|
||||
new_capacity * sizeof(point_t),
|
||||
"Crofton hit points");
|
||||
shared->point_capacity = new_capacity;
|
||||
}
|
||||
memcpy(&shared->points[shared->point_count],
|
||||
worker.local_points,
|
||||
worker.local_point_count * sizeof(point_t));
|
||||
shared->point_count = needed;
|
||||
}
|
||||
|
||||
struct crofton_worker_data *wdata = (struct crofton_worker_data *)bu_calloc(
|
||||
ncpus, sizeof(struct crofton_worker_data), "crofton wdata");
|
||||
|
||||
size_t per_cpu = nrays / ncpus;
|
||||
|
||||
for (size_t i = 0; i < ncpus; i++) {
|
||||
struct application *a = (struct application *)bu_calloc(
|
||||
1, sizeof(struct application), "crofton app");
|
||||
*a = *ap_template; /* struct copy */
|
||||
a->a_resource = &resources[i];
|
||||
|
||||
struct crofton_worker_data *wd = &wdata[i];
|
||||
wd->ap = a;
|
||||
wd->rays = rays;
|
||||
wd->start = i * per_cpu;
|
||||
wd->end = (i == ncpus - 1) ? nrays : (i + 1) * per_cpu;
|
||||
wd->shared = shared;
|
||||
a->a_uptr = wd;
|
||||
if (worker.local_segment_count) {
|
||||
const size_t needed =
|
||||
shared->segment_count + worker.local_segment_count;
|
||||
if (needed > shared->segment_capacity) {
|
||||
size_t new_capacity = shared->segment_capacity ?
|
||||
2 * shared->segment_capacity : needed;
|
||||
if (new_capacity < needed)
|
||||
new_capacity = needed;
|
||||
shared->segments =
|
||||
(struct rt_crofton_segment *)bu_realloc(
|
||||
shared->segments,
|
||||
new_capacity * sizeof(struct rt_crofton_segment),
|
||||
"Crofton segments");
|
||||
shared->segment_capacity = new_capacity;
|
||||
}
|
||||
memcpy(&shared->segments[shared->segment_count],
|
||||
worker.local_segments,
|
||||
worker.local_segment_count *
|
||||
sizeof(struct rt_crofton_segment));
|
||||
shared->segment_count = needed;
|
||||
}
|
||||
}
|
||||
|
||||
bu_parallel(crofton_worker, (int)ncpus, (void *)wdata);
|
||||
|
||||
for (size_t i = 0; i < ncpus; i++) {
|
||||
shared->total_crossings += wdata[i].local_crossings;
|
||||
shared->total_chord += wdata[i].local_chord;
|
||||
shared->total_rays += wdata[i].local_rays;
|
||||
if (wdata[i].local_point_count) {
|
||||
const size_t needed = shared->point_count + wdata[i].local_point_count;
|
||||
if (needed > shared->point_capacity) {
|
||||
size_t new_capacity = shared->point_capacity ?
|
||||
2 * shared->point_capacity : needed;
|
||||
if (new_capacity < needed)
|
||||
new_capacity = needed;
|
||||
shared->points = (point_t *)bu_realloc(shared->points,
|
||||
new_capacity * sizeof(point_t), "Crofton hit points");
|
||||
shared->point_capacity = new_capacity;
|
||||
}
|
||||
memcpy(&shared->points[shared->point_count], wdata[i].local_points,
|
||||
wdata[i].local_point_count * sizeof(point_t));
|
||||
shared->point_count = needed;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ncpus; i++) {
|
||||
if (wdata[i].local_points)
|
||||
bu_free(wdata[i].local_points, "Crofton worker hit points");
|
||||
bu_free(wdata[i].ap, "crofton app");
|
||||
}
|
||||
bu_free(wdata, "crofton wdata");
|
||||
bu_free(rays, "crofton rays");
|
||||
bu_free(rays, "Crofton rays");
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Public API: rt_crofton_shoot */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -375,7 +552,8 @@ do_one_iteration(struct application *ap_template,
|
||||
* sampling (>= 0) on success; -1 on bad arguments.
|
||||
*/
|
||||
static int
|
||||
crofton_shoot_impl(point_t **out_points,
|
||||
crofton_shoot_impl(struct rt_crofton_result *out_result,
|
||||
point_t **out_points,
|
||||
size_t *out_point_count,
|
||||
double *out_surf_area,
|
||||
double *out_volume,
|
||||
@@ -384,13 +562,22 @@ crofton_shoot_impl(point_t **out_points,
|
||||
point_t out_obb[8],
|
||||
struct rt_i *rtip,
|
||||
const struct rt_crofton_params *params,
|
||||
size_t ray_offset,
|
||||
const fastf_t *bbox_min,
|
||||
const fastf_t *bbox_max)
|
||||
{
|
||||
if (!rtip || (!out_surf_area && !out_volume && !out_aabb_min && !out_obb && !out_points) ||
|
||||
((out_points == NULL) != (out_point_count == NULL)) ||
|
||||
((out_aabb_min == NULL) != (out_aabb_max == NULL)))
|
||||
return -1;
|
||||
if (!rtip ||
|
||||
(!out_result && !out_surf_area && !out_volume && !out_aabb_min &&
|
||||
!out_obb && !out_points) ||
|
||||
((out_points == NULL) != (out_point_count == NULL)) ||
|
||||
((out_aabb_min == NULL) != (out_aabb_max == NULL)) ||
|
||||
(out_result && out_result->segments) ||
|
||||
ray_offset > UINT64_MAX / RT_CROFTON_RANDOM_VALUES_PER_RAY)
|
||||
return -1;
|
||||
if (out_result) {
|
||||
struct rt_crofton_result empty = RT_CROFTON_RESULT_INIT;
|
||||
*out_result = empty;
|
||||
}
|
||||
if (out_points) {
|
||||
*out_points = NULL;
|
||||
*out_point_count = 0;
|
||||
@@ -473,10 +660,14 @@ crofton_shoot_impl(point_t **out_points,
|
||||
if (!use_default && max_rays > 0 && stability_mm <= 0.0 && time_ms <= 0.0)
|
||||
batch = max_rays; /* single-iteration mode */
|
||||
|
||||
size_t ncpus = bu_avail_cpus();
|
||||
if (ncpus < 1) ncpus = 1;
|
||||
if (ncpus > MAX_PSW) ncpus = MAX_PSW;
|
||||
|
||||
/* ---- Initialize per-CPU resources ---- */
|
||||
struct resource *resources = (struct resource *)bu_calloc(
|
||||
MAX_PSW, sizeof(struct resource), "crofton resources");
|
||||
for (int i = 0; i < MAX_PSW; i++)
|
||||
ncpus, sizeof(struct resource), "crofton resources");
|
||||
for (size_t i = 0; i < ncpus; i++)
|
||||
rt_init_resource(&resources[i], i, rtip);
|
||||
|
||||
/* ---- Set up application template ---- */
|
||||
@@ -494,10 +685,32 @@ crofton_shoot_impl(point_t **out_points,
|
||||
/* ---- Shared accumulator ---- */
|
||||
struct crofton_shared shared;
|
||||
memset(&shared, 0, sizeof(shared));
|
||||
shared.collect_points = out_points != NULL || out_aabb_min != NULL || out_obb != NULL;
|
||||
const bool stabilize_surface = out_surf_area != NULL || shared.collect_points;
|
||||
shared.ray_offset = ray_offset;
|
||||
shared.collect_points =
|
||||
out_points != NULL || out_aabb_min != NULL || out_obb != NULL;
|
||||
shared.collect_segments = out_result != NULL;
|
||||
const bool stabilize_surface =
|
||||
out_surf_area != NULL || out_result != NULL ||
|
||||
shared.collect_points;
|
||||
|
||||
CroftonWorkerPool *pool = NULL;
|
||||
try {
|
||||
pool = new CroftonWorkerPool(&ap, resources, ncpus);
|
||||
} catch (const std::exception &e) {
|
||||
bu_log("rt_crofton: unable to start worker pool: %s\n", e.what());
|
||||
for (size_t i = 0; i < ncpus; i++) {
|
||||
if (resources[i].re_magic == RESOURCE_MAGIC) {
|
||||
rt_clean_resource_basic(rtip, &resources[i]);
|
||||
BU_PTBL_SET(&rtip->rti_resources, i, NULL);
|
||||
}
|
||||
}
|
||||
bu_free(resources, "crofton resources");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::mt19937_64 rng(RT_CROFTON_RNG_SEED);
|
||||
rng.discard(static_cast<uint64_t>(ray_offset) *
|
||||
RT_CROFTON_RANDOM_VALUES_PER_RAY);
|
||||
|
||||
const double FOUR_PI = 4.0 * M_PI;
|
||||
const double PI = M_PI;
|
||||
@@ -521,7 +734,7 @@ crofton_shoot_impl(point_t **out_points,
|
||||
curr_rays = batch;
|
||||
}
|
||||
|
||||
do_one_iteration(&ap, resources, curr_rays, R, center, &shared, rng);
|
||||
do_one_iteration(*pool, curr_rays, R, center, &shared, rng);
|
||||
iteration++;
|
||||
|
||||
if (shared.total_rays == 0) break;
|
||||
@@ -562,9 +775,12 @@ crofton_shoot_impl(point_t **out_points,
|
||||
} else {
|
||||
/* ---- Parametric loop: n_rays / stability_mm / time_ms ---- */
|
||||
double prev_r_sa = -1.0, prev_r_v = -1.0;
|
||||
double prev_sa = -1.0, prev_v = -1.0;
|
||||
size_t total_fired = 0;
|
||||
size_t stable_windows = 0;
|
||||
int64_t t0 = (time_ms > 0.0) ? bu_gettime() : 0;
|
||||
const bool use_relative_stability = out_result && time_ms > 0.0 &&
|
||||
stability_mm <= 0.0;
|
||||
|
||||
for (;;) {
|
||||
/* Time-budget check before firing */
|
||||
@@ -581,7 +797,7 @@ crofton_shoot_impl(point_t **out_points,
|
||||
if (fire > remaining) fire = remaining;
|
||||
}
|
||||
|
||||
do_one_iteration(&ap, resources, fire, R, center, &shared, rng);
|
||||
do_one_iteration(*pool, fire, R, center, &shared, rng);
|
||||
total_fired += fire;
|
||||
|
||||
if (shared.total_rays == 0) break;
|
||||
@@ -594,27 +810,39 @@ crofton_shoot_impl(point_t **out_points,
|
||||
/ (double)shared.total_rays;
|
||||
|
||||
/* Stability check */
|
||||
if (stability_mm > 0.0 &&
|
||||
if ((stability_mm > 0.0 || use_relative_stability) &&
|
||||
shared.total_rays >= RT_CROFTON_STABILITY_MIN_RAYS &&
|
||||
shared.total_crossings >= RT_CROFTON_STABILITY_MIN_CROSSINGS) {
|
||||
double r_sa = (curr_est_sa > 0.0) ? sqrt(curr_est_sa * INV_4PI) : 0.0;
|
||||
double r_v = (curr_est_v > 0.0) ? cbrt(curr_est_v * INV_4PI3) : 0.0;
|
||||
|
||||
if (prev_r_sa >= 0.0) {
|
||||
int sa_ok = (!stabilize_surface) ||
|
||||
fabs(r_sa - prev_r_sa) < stability_mm;
|
||||
int v_ok = (!out_volume) ||
|
||||
fabs(r_v - prev_r_v) < stability_mm;
|
||||
if (sa_ok && v_ok) {
|
||||
stable_windows++;
|
||||
if (stable_windows >= RT_CROFTON_STABILITY_MIN_WINDOWS)
|
||||
break;
|
||||
} else {
|
||||
stable_windows = 0;
|
||||
}
|
||||
bool sa_ok = false;
|
||||
bool v_ok = false;
|
||||
if (use_relative_stability) {
|
||||
sa_ok = !stabilize_surface || (prev_sa > 0.0 &&
|
||||
fabs(curr_est_sa - prev_sa) / prev_sa * 100.0 <=
|
||||
RT_CROFTON_DEFAULT_THRESHOLD);
|
||||
v_ok = !out_volume || (prev_v > 0.0 &&
|
||||
fabs(curr_est_v - prev_v) / prev_v * 100.0 <=
|
||||
RT_CROFTON_DEFAULT_THRESHOLD);
|
||||
prev_sa = curr_est_sa;
|
||||
prev_v = curr_est_v;
|
||||
} else {
|
||||
double r_sa = (curr_est_sa > 0.0) ?
|
||||
sqrt(curr_est_sa * INV_4PI) : 0.0;
|
||||
double r_v = (curr_est_v > 0.0) ?
|
||||
cbrt(curr_est_v * INV_4PI3) : 0.0;
|
||||
sa_ok = !stabilize_surface || (prev_r_sa >= 0.0 &&
|
||||
fabs(r_sa - prev_r_sa) < stability_mm);
|
||||
v_ok = !out_volume || (prev_r_v >= 0.0 &&
|
||||
fabs(r_v - prev_r_v) < stability_mm);
|
||||
prev_r_sa = r_sa;
|
||||
prev_r_v = r_v;
|
||||
}
|
||||
if (sa_ok && v_ok) {
|
||||
stable_windows++;
|
||||
if (stable_windows >= RT_CROFTON_STABILITY_MIN_WINDOWS)
|
||||
break;
|
||||
} else {
|
||||
stable_windows = 0;
|
||||
}
|
||||
prev_r_sa = r_sa;
|
||||
prev_r_v = r_v;
|
||||
}
|
||||
|
||||
/* Time-budget check after firing */
|
||||
@@ -624,8 +852,16 @@ crofton_shoot_impl(point_t **out_points,
|
||||
}
|
||||
}
|
||||
|
||||
if (out_surf_area) *out_surf_area = curr_est_sa;
|
||||
if (out_volume) *out_volume = curr_est_v;
|
||||
if (out_surf_area)
|
||||
*out_surf_area = curr_est_sa;
|
||||
if (out_volume)
|
||||
*out_volume = curr_est_v;
|
||||
if (out_result) {
|
||||
out_result->surface_area = curr_est_sa;
|
||||
out_result->volume = curr_est_v;
|
||||
out_result->ray_count = shared.total_rays;
|
||||
out_result->crossing_count = shared.total_crossings;
|
||||
}
|
||||
if (out_aabb_min) {
|
||||
for (size_t i = 0; i < shared.point_count; ++i)
|
||||
VMINMAX(*out_aabb_min, *out_aabb_max, shared.points[i]);
|
||||
@@ -641,11 +877,19 @@ crofton_shoot_impl(point_t **out_points,
|
||||
bounds_ret = -1;
|
||||
}
|
||||
if (out_points && !bounds_ret) {
|
||||
*out_points = shared.points;
|
||||
*out_point_count = shared.point_count;
|
||||
*out_points = shared.points;
|
||||
*out_point_count = shared.point_count;
|
||||
} else if (shared.points) {
|
||||
bu_free(shared.points, "Crofton hit points");
|
||||
bu_free(shared.points, "Crofton hit points");
|
||||
}
|
||||
if (out_result && !bounds_ret) {
|
||||
out_result->segments = shared.segments;
|
||||
out_result->segment_count = shared.segment_count;
|
||||
} else if (shared.segments) {
|
||||
bu_free(shared.segments, "Crofton segments");
|
||||
}
|
||||
|
||||
delete pool;
|
||||
|
||||
/* Clean each resource and NULL out its slot in rtip->rti_resources.
|
||||
* This is necessary because crofton_from_ip calls rt_i_destroy(rtip)
|
||||
@@ -657,7 +901,7 @@ crofton_shoot_impl(point_t **out_points,
|
||||
*
|
||||
* By setting the slot to NULL we let rt_i_destroy's cleanup skip it,
|
||||
* and then we can safely bu_free the resources array. */
|
||||
for (int i = 0; i < MAX_PSW; i++) {
|
||||
for (size_t i = 0; i < ncpus; i++) {
|
||||
if (resources[i].re_magic == RESOURCE_MAGIC) {
|
||||
rt_clean_resource_basic(rtip, &resources[i]);
|
||||
BU_PTBL_SET(&rtip->rti_resources, i, NULL);
|
||||
@@ -681,9 +925,31 @@ rt_crofton_shoot(double *out_surf_area, double *out_volume,
|
||||
const struct rt_crofton_params *params,
|
||||
const fastf_t *bbox_min, const fastf_t *bbox_max)
|
||||
{
|
||||
return crofton_shoot_impl(out_points, out_point_count, out_surf_area,
|
||||
out_volume, out_aabb_min, out_aabb_max, out_obb, rtip, params,
|
||||
bbox_min, bbox_max);
|
||||
return crofton_shoot_impl(NULL, out_points, out_point_count,
|
||||
out_surf_area, out_volume, out_aabb_min, out_aabb_max, out_obb,
|
||||
rtip, params, 0, bbox_min, bbox_max);
|
||||
}
|
||||
|
||||
int
|
||||
rt_crofton_collect(struct rt_crofton_result *result, struct rt_i *rtip,
|
||||
const struct rt_crofton_params *params, size_t ray_offset,
|
||||
const fastf_t *bbox_min, const fastf_t *bbox_max)
|
||||
{
|
||||
if (!result)
|
||||
return -1;
|
||||
return crofton_shoot_impl(result, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, rtip, params, ray_offset, bbox_min, bbox_max);
|
||||
}
|
||||
|
||||
void
|
||||
rt_crofton_result_free(struct rt_crofton_result *result)
|
||||
{
|
||||
if (!result)
|
||||
return;
|
||||
if (result->segments)
|
||||
bu_free(result->segments, "Crofton segments");
|
||||
struct rt_crofton_result empty = RT_CROFTON_RESULT_INIT;
|
||||
*result = empty;
|
||||
}
|
||||
|
||||
|
||||
@@ -821,7 +1087,7 @@ crofton_from_ip_n(const struct rt_db_internal *ip,
|
||||
double sa = 0.0;
|
||||
double vol = 0.0;
|
||||
(void)rt_crofton_shoot(&sa, &vol, NULL, NULL, NULL, NULL, NULL,
|
||||
rtip, params, NULL, NULL);
|
||||
rtip, params, NULL, NULL);
|
||||
|
||||
if (out_sa) *out_sa = sa;
|
||||
if (out_vol) *out_vol = vol;
|
||||
|
||||
@@ -1543,8 +1543,7 @@ permute_cell(point_t A,
|
||||
struct dsp_specific *dsp,
|
||||
struct dsp_rpp *dsp_rpp)
|
||||
{
|
||||
int x, y;
|
||||
|
||||
point_t tmp;
|
||||
|
||||
#ifdef FULL_DSP_DEBUGGING
|
||||
if (RT_G_DEBUG & RT_DEBUG_HF) {
|
||||
@@ -1558,10 +1557,8 @@ permute_cell(point_t A,
|
||||
switch (dsp->dsp_i.dsp_cuttype) {
|
||||
case DSP_CUT_DIR_llUR:
|
||||
return DSP_CUT_DIR_llUR;
|
||||
break;
|
||||
|
||||
case DSP_CUT_DIR_ADAPT: {
|
||||
point_t tmp;
|
||||
size_t cx = (size_t)dsp_rpp->dsp_min[X];
|
||||
size_t cy = (size_t)dsp_rpp->dsp_min[Y];
|
||||
|
||||
@@ -1570,28 +1567,8 @@ permute_cell(point_t A,
|
||||
dsp_rpp->dsp_min[X],
|
||||
dsp_rpp->dsp_min[Y]);
|
||||
|
||||
/*
|
||||
* We look at the points in the diagonal next cells to
|
||||
* determine the curvature along each diagonal of this
|
||||
* cell. This cell is divided into two triangles by
|
||||
* cutting across the cell in the direction of least
|
||||
* curvature.
|
||||
*
|
||||
* * * * *
|
||||
* \ /
|
||||
* \C D/
|
||||
* * *--* *
|
||||
* |\/|
|
||||
* |/\|
|
||||
* * *--* *
|
||||
* /A B\
|
||||
* / \
|
||||
* * * * *
|
||||
*/
|
||||
|
||||
/* leaf-cell precondition: the rpp spans exactly one grid unit, so
|
||||
* (cx, cy) plus the cell counts fully describe the curvature
|
||||
* stencil that rt_dsp_cell_cut() recomputes.
|
||||
/* The rpp spans one grid cell; use its indices to select the
|
||||
* lower-curvature diagonal.
|
||||
*/
|
||||
BU_ASSERT(dsp_rpp->dsp_max[X] == dsp_rpp->dsp_min[X] + 1);
|
||||
BU_ASSERT(dsp_rpp->dsp_max[Y] == dsp_rpp->dsp_min[Y] + 1);
|
||||
@@ -1599,15 +1576,16 @@ permute_cell(point_t A,
|
||||
if (rt_dsp_cell_cut(&dsp->dsp_i, cx, cy,
|
||||
(size_t)dsp->xsiz, (size_t)dsp->ysiz)
|
||||
== DSP_CUT_DIR_llUR) {
|
||||
/* A-D cut is fine, no need to permute */
|
||||
if (RT_G_DEBUG & RT_DEBUG_HF)
|
||||
bu_log("A-D cut\n");
|
||||
|
||||
return DSP_CUT_DIR_llUR;
|
||||
|
||||
}
|
||||
|
||||
/* prefer the B-C cut */
|
||||
}
|
||||
/* fall through */
|
||||
case DSP_CUT_DIR_ULlr:
|
||||
/* Both B-C modes need the same corner order. The top triangles
|
||||
* must keep outward (+Z) normals for the entry/exit state machine.
|
||||
*/
|
||||
VMOVE(tmp, A);
|
||||
VMOVE(A, B);
|
||||
VMOVE(B, D);
|
||||
@@ -1615,35 +1593,7 @@ permute_cell(point_t A,
|
||||
VMOVE(C, tmp);
|
||||
if (RT_G_DEBUG & RT_DEBUG_HF)
|
||||
bu_log("B-C cut\n");
|
||||
|
||||
return DSP_CUT_DIR_ULlr;
|
||||
|
||||
break;
|
||||
}
|
||||
case DSP_CUT_DIR_ULlr:
|
||||
/* assign the values for the corner points
|
||||
*
|
||||
* D----C
|
||||
* | |
|
||||
* | |
|
||||
* | |
|
||||
* B----A
|
||||
*/
|
||||
x = dsp_rpp->dsp_min[X];
|
||||
y = dsp_rpp->dsp_min[Y];
|
||||
VSET(B, x, y, DSP(&dsp->dsp_i, x, y));
|
||||
|
||||
x = dsp_rpp->dsp_max[X];
|
||||
VSET(A, x, y, DSP(&dsp->dsp_i, x, y));
|
||||
|
||||
y = dsp_rpp->dsp_max[Y];
|
||||
VSET(C, x, y, DSP(&dsp->dsp_i, x, y));
|
||||
|
||||
x = dsp_rpp->dsp_min[X];
|
||||
VSET(D, x, y, DSP(&dsp->dsp_i, x, y));
|
||||
|
||||
return DSP_CUT_DIR_ULlr;
|
||||
break;
|
||||
}
|
||||
bu_log("%s:%d Unknown DSP cut direction: %d\n",
|
||||
__FILE__, __LINE__, dsp->dsp_i.dsp_cuttype);
|
||||
|
||||
@@ -43,6 +43,18 @@ rel_err(double estimated, double exact)
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
crofton_segments_equal(const struct rt_crofton_segment *left,
|
||||
const struct rt_crofton_segment *right)
|
||||
{
|
||||
return left->ray_id == right->ray_id &&
|
||||
NEAR_EQUAL(left->thickness, right->thickness, SMALL_FASTF) &&
|
||||
VNEAR_EQUAL(left->in_point, right->in_point, SMALL_FASTF) &&
|
||||
VNEAR_EQUAL(left->in_normal, right->in_normal, SMALL_FASTF) &&
|
||||
VNEAR_EQUAL(left->out_point, right->out_point, SMALL_FASTF) &&
|
||||
VNEAR_EQUAL(left->out_normal, right->out_normal, SMALL_FASTF);
|
||||
}
|
||||
|
||||
static int
|
||||
verify_crofton_estimates(void)
|
||||
{
|
||||
@@ -379,6 +391,102 @@ run_convergence_case(struct db_i *dbip,
|
||||
&sample_points, &sample_count,
|
||||
rtip, &p, NULL, NULL);
|
||||
run_sec = (double)(bu_gettime() - t0) / 1000000.0;
|
||||
|
||||
if (i == 0) {
|
||||
struct rt_crofton_result samples = RT_CROFTON_RESULT_INIT;
|
||||
const size_t ray_offset = 4096u;
|
||||
const size_t continuation_rays = 256u;
|
||||
struct rt_crofton_params sample_params =
|
||||
{ray_offset + continuation_rays, 0.0, 0.0};
|
||||
int sample_ret = rt_crofton_collect(&samples, rtip,
|
||||
&sample_params, 0, NULL, NULL);
|
||||
int full_samples_valid = sample_ret > 0 && samples.segments &&
|
||||
samples.segment_count * 2 == samples.crossing_count &&
|
||||
samples.ray_count == sample_params.n_rays;
|
||||
if (!full_samples_valid) {
|
||||
printf(" %-24s invalid structured Crofton output\\n", label);
|
||||
failures++;
|
||||
} else {
|
||||
for (size_t sample_index = 0;
|
||||
sample_index < samples.segment_count; sample_index++) {
|
||||
struct rt_crofton_segment *segment =
|
||||
&samples.segments[sample_index];
|
||||
if (segment->thickness <= 0.0 ||
|
||||
!NEAR_EQUAL(DIST_PNT_PNT(segment->in_point,
|
||||
segment->out_point), segment->thickness, RT_LEN_TOL) ||
|
||||
!NEAR_EQUAL(MAGNITUDE(segment->in_normal), 1.0,
|
||||
VUNITIZE_TOL) ||
|
||||
!NEAR_EQUAL(MAGNITUDE(segment->out_normal), 1.0,
|
||||
VUNITIZE_TOL) ||
|
||||
segment->ray_id >= samples.ray_count) {
|
||||
printf(" %-24s invalid structured segment\\n", label);
|
||||
failures++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct rt_crofton_result offset_samples =
|
||||
RT_CROFTON_RESULT_INIT;
|
||||
struct rt_crofton_params offset_params =
|
||||
{continuation_rays, 0.0, 0.0};
|
||||
sample_ret = rt_crofton_collect(&offset_samples, rtip,
|
||||
&offset_params, ray_offset, NULL, NULL);
|
||||
int offset_samples_valid = sample_ret > 0 &&
|
||||
offset_samples.ray_count == offset_params.n_rays;
|
||||
if (!offset_samples_valid) {
|
||||
printf(" %-24s invalid offset Crofton output\\n", label);
|
||||
failures++;
|
||||
} else {
|
||||
for (size_t sample_index = 0;
|
||||
sample_index < offset_samples.segment_count;
|
||||
sample_index++) {
|
||||
size_t ray_id = offset_samples.segments[sample_index].ray_id;
|
||||
if (ray_id < ray_offset ||
|
||||
ray_id >= ray_offset + offset_params.n_rays) {
|
||||
printf(" %-24s invalid offset ray identifier\\n",
|
||||
label);
|
||||
failures++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (full_samples_valid && offset_samples_valid) {
|
||||
size_t suffix_start = 0;
|
||||
while (suffix_start < samples.segment_count &&
|
||||
samples.segments[suffix_start].ray_id < ray_offset)
|
||||
suffix_start++;
|
||||
size_t suffix_count = samples.segment_count - suffix_start;
|
||||
|
||||
if (suffix_count != offset_samples.segment_count) {
|
||||
printf(" %-24s random stream continuation count mismatch\\n",
|
||||
label);
|
||||
failures++;
|
||||
} else {
|
||||
for (size_t sample_index = 0;
|
||||
sample_index < suffix_count; sample_index++) {
|
||||
const struct rt_crofton_segment *full_segment =
|
||||
&samples.segments[suffix_start + sample_index];
|
||||
const struct rt_crofton_segment *offset_segment =
|
||||
&offset_samples.segments[sample_index];
|
||||
if (!crofton_segments_equal(full_segment, offset_segment)) {
|
||||
printf(" %-24s random stream continuation mismatch\\n",
|
||||
label);
|
||||
failures++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rt_crofton_result_free(&samples);
|
||||
if (samples.segments || samples.segment_count)
|
||||
failures++;
|
||||
rt_crofton_result_free(&offset_samples);
|
||||
if (offset_samples.segments || offset_samples.segment_count)
|
||||
failures++;
|
||||
}
|
||||
rt_i_destroy(rtip);
|
||||
|
||||
if (elapsed_total_sec)
|
||||
|
||||
@@ -414,13 +414,20 @@ flat_dsp_analytic(uint32_t xcnt, uint32_t ycnt, unsigned short h,
|
||||
/* Build an in-memory DB, prep, and run comparison */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
enum dsp_ray_check_mode {
|
||||
DSP_RAY_CHECK_NONE,
|
||||
DSP_RAY_CHECK_FLAT,
|
||||
DSP_RAY_CHECK_SMOOTH,
|
||||
DSP_RAY_CHECK_BC_CUT
|
||||
};
|
||||
|
||||
struct dsp_case {
|
||||
const char *label;
|
||||
uint32_t xcnt, ycnt;
|
||||
const unsigned short *buf;
|
||||
int cuttype;
|
||||
int smooth;
|
||||
int ray_checks;
|
||||
enum dsp_ray_check_mode ray_checks;
|
||||
/* stom diagonal (identity: dx=dy=dz=1): */
|
||||
double dx, dy, dz;
|
||||
/* analytic SA/vol (-1 if not available): */
|
||||
@@ -550,7 +557,7 @@ check_smooth_normal(const struct dsp_case *tc, struct soltab *stp)
|
||||
static int
|
||||
run_prepped_ray_checks(const struct dsp_case *tc, struct rt_i *rtip)
|
||||
{
|
||||
if (!tc->ray_checks)
|
||||
if (tc->ray_checks == DSP_RAY_CHECK_NONE)
|
||||
return 0;
|
||||
|
||||
int failures = 0;
|
||||
@@ -562,16 +569,14 @@ run_prepped_ray_checks(const struct dsp_case *tc, struct rt_i *rtip)
|
||||
|
||||
double x_extent = (tc->xcnt - 1) * tc->dx;
|
||||
double y_extent = (tc->ycnt - 1) * tc->dy;
|
||||
/* ray_checks == 1 is reserved for flat DSP cases, so buf[0] is the
|
||||
* expected terrain height for the deterministic vertical/side rays.
|
||||
*/
|
||||
double top_z = (double)tc->buf[0] * tc->dz;
|
||||
double expected_vertical_in = 10.0;
|
||||
double expected_vertical_out = expected_vertical_in + top_z;
|
||||
point_t o;
|
||||
vect_t d;
|
||||
|
||||
if (tc->ray_checks == 1) {
|
||||
if (tc->ray_checks == DSP_RAY_CHECK_FLAT) {
|
||||
/* Flat cases use buf[0] as the terrain height for direct rays. */
|
||||
double top_z = (double)tc->buf[0] * tc->dz;
|
||||
double expected_vertical_in = 10.0;
|
||||
double expected_vertical_out = expected_vertical_in + top_z;
|
||||
VSET(o, 0.5 * x_extent, 0.5 * y_extent, top_z + 10.0);
|
||||
VSET(d, 0.0, 0.0, -1.0);
|
||||
failures += check_direct_ray("vertical down flat DSP", stp, rtip,
|
||||
@@ -598,12 +603,22 @@ run_prepped_ray_checks(const struct dsp_case *tc, struct rt_i *rtip)
|
||||
failures += check_direct_ray("vertical up flat DSP", stp, rtip,
|
||||
o, d, 1, expected_vertical_in,
|
||||
expected_vertical_out, 0);
|
||||
} else if (tc->ray_checks == 2) {
|
||||
} else if (tc->ray_checks == DSP_RAY_CHECK_SMOOTH) {
|
||||
VSET(o, 0.5 * x_extent, 0.5 * y_extent, 10000.0);
|
||||
VSET(d, 0.0, 0.0, -1.0);
|
||||
failures += check_direct_ray("smooth-mode direct vertical shot", stp, rtip,
|
||||
o, d, 1, -1.0, -1.0, 0);
|
||||
failures += check_smooth_normal(tc, stp);
|
||||
} else if (tc->ray_checks == DSP_RAY_CHECK_BC_CUT) {
|
||||
/* In cell (3,3), the B-C triangle rises from 315 at y=3.25 by
|
||||
* 68 per x. A horizontal ray at z=330 enters at x=3+15/68
|
||||
* and exits through the outer wall at x=4. This also checks
|
||||
* that the explicit B-C cut gives the top an outward normal.
|
||||
*/
|
||||
VSET(o, 2.5, 3.25, 330.0);
|
||||
VSET(d, 1.0, 0.0, 0.0);
|
||||
failures += check_direct_ray("explicit B-C top-to-side shot", stp, rtip,
|
||||
o, d, 1, 0.5 + 15.0 / 68.0, 1.5, 1);
|
||||
}
|
||||
|
||||
return failures;
|
||||
@@ -782,7 +797,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"flat 5x5 h=100 (4x4 cells, identity stom)",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 1, 1.0, 1.0, 1.0, asa, avol
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_FLAT, 1.0, 1.0, 1.0, asa, avol
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -798,7 +813,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"flat 10x10 h=200 (9x9 cells)",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, asa, avol
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, asa, avol
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -814,7 +829,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"flat 10x10 h=50 (stom dx=5 dy=5 dz=2)",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 1, 5.0, 5.0, 2.0, asa, avol
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_FLAT, 5.0, 5.0, 2.0, asa, avol
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -829,7 +844,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"ramp 9x9 (h=100+20x+10y)",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -844,7 +859,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"asymmetric 5x5 explicit ULlr cut",
|
||||
GW, GH, buf, DSP_CUT_DIR_ULlr, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_ULlr, 0, DSP_RAY_CHECK_BC_CUT, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -859,7 +874,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"asymmetric 5x5 adaptive cut",
|
||||
GW, GH, buf, DSP_CUT_DIR_ADAPT, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_ADAPT, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -874,7 +889,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"smooth=1 ramp normal check",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 1, 2, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 1, DSP_RAY_CHECK_SMOOTH, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -889,7 +904,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"smooth=2 ramp normal check",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 2, 2, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 2, DSP_RAY_CHECK_SMOOTH, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -908,7 +923,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"sinusoidal 16x16",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
}
|
||||
@@ -928,7 +943,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"complex 33x33 wave",
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
bu_free(buf, "33x33 buf");
|
||||
@@ -971,7 +986,7 @@ test_synthetic(void)
|
||||
|
||||
struct dsp_case tc = {
|
||||
"larger 129x129 mixed ramp/wave terrain",
|
||||
grid_width, grid_height, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
grid_width, grid_height, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
|
||||
};
|
||||
failures += run_inmem_case(&tc);
|
||||
bu_free(sin_x, "129x129 sin x");
|
||||
|
||||
+180
-234
@@ -49,6 +49,7 @@
|
||||
#include "bu/path.h"
|
||||
#include "bu/process.h"
|
||||
#include "bu/units.h"
|
||||
#include "bu/binding.h"
|
||||
#include "vmath.h"
|
||||
#include "rt/db4.h"
|
||||
#include "rt/geom.h"
|
||||
@@ -2824,6 +2825,148 @@ to_data_vZ(struct ged *gedp,
|
||||
}
|
||||
|
||||
|
||||
static void tclcad_action_eval_aet(const struct bu_vls *ev, void *reg, void *event_data) {
|
||||
struct bview *gdvp = (struct bview *)event_data;
|
||||
const char *aet_args = (const char *)reg;
|
||||
struct bu_vls cmd = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&cmd, "%s aet %s %s", bu_vls_cstr(¤t_top->to_gedp->go_name), bu_vls_cstr(&gdvp->gv_name), aet_args);
|
||||
Tcl_Eval(current_top->to_interp, bu_vls_cstr(&cmd));
|
||||
bu_vls_free(&cmd);
|
||||
}
|
||||
|
||||
static void tclcad_action_mouse_drag(const struct bu_vls *ev, void *reg, void *event_data) {
|
||||
struct bview *gdvp = (struct bview *)event_data;
|
||||
const char *cmd_name = (const char *)reg;
|
||||
int cx = gdvp->gv_prevMouseX;
|
||||
int cy = gdvp->gv_prevMouseY;
|
||||
char *p;
|
||||
if ((p = strstr(bu_vls_cstr(ev), "\"cx\":"))) sscanf(p + 5, "%d", &cx);
|
||||
if ((p = strstr(bu_vls_cstr(ev), "\"cy\":"))) sscanf(p + 5, "%d", &cy);
|
||||
|
||||
struct bu_vls cmd = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&cmd, "%s %s %s %d %d", bu_vls_cstr(¤t_top->to_gedp->go_name), cmd_name, bu_vls_cstr(&gdvp->gv_name), cx, cy);
|
||||
Tcl_Eval(current_top->to_interp, bu_vls_cstr(&cmd));
|
||||
bu_vls_free(&cmd);
|
||||
}
|
||||
|
||||
static void tclcad_action_wheel_scale(const struct bu_vls *ev, void *reg, void *event_data) {
|
||||
struct bview *gdvp = (struct bview *)event_data;
|
||||
int delta = 0;
|
||||
char *p;
|
||||
if ((p = strstr(bu_vls_cstr(ev), "\"delta\":"))) sscanf(p + 8, "%d", &delta);
|
||||
const char *sf = (delta > 0) ? "1.1" : "0.9";
|
||||
struct bu_vls cmd = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&cmd, "%s zoom %s %s", bu_vls_cstr(¤t_top->to_gedp->go_name), bu_vls_cstr(&gdvp->gv_name), sf);
|
||||
Tcl_Eval(current_top->to_interp, bu_vls_cstr(&cmd));
|
||||
bu_vls_free(&cmd);
|
||||
}
|
||||
|
||||
static void tclcad_action_mouse_click(const struct bu_vls *ev, void *reg, void *event_data) {
|
||||
struct bview *gdvp = (struct bview *)event_data;
|
||||
const char *cmd_format = (const char *)reg;
|
||||
int cx = gdvp->gv_prevMouseX;
|
||||
int cy = gdvp->gv_prevMouseY;
|
||||
char *p;
|
||||
if ((p = strstr(bu_vls_cstr(ev), "\"cx\":"))) sscanf(p + 5, "%d", &cx);
|
||||
if ((p = strstr(bu_vls_cstr(ev), "\"cy\":"))) sscanf(p + 5, "%d", &cy);
|
||||
|
||||
struct bu_vls cmd = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&cmd, cmd_format, bu_vls_cstr(¤t_top->to_gedp->go_name), bu_vls_cstr(&gdvp->gv_name), cx, cy);
|
||||
Tcl_Eval(current_top->to_interp, bu_vls_cstr(&cmd));
|
||||
bu_vls_free(&cmd);
|
||||
}
|
||||
|
||||
static int bindings_loaded_mged = 0;
|
||||
|
||||
static int tclcad_load_bindings_cmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]) {
|
||||
if (argc < 2) {
|
||||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " <json_path>\"", NULL);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
char *path = (char *)bu_dir(NULL, 0, BU_DIR_DATA, argv[1], NULL);
|
||||
if (!path) {
|
||||
Tcl_AppendResult(interp, "Failed to resolve path: ", argv[1], NULL);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if (bu_binding_load(path) < 0) {
|
||||
bu_free(path, "path");
|
||||
Tcl_AppendResult(interp, "Failed to load bindings from: ", argv[1], NULL);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
bu_free(path, "path");
|
||||
|
||||
/* Validate that application provided required bindings */
|
||||
if (!bu_binding_action_is_bound("dm", "view_mouse_drag_rotate") ||
|
||||
!bu_binding_action_is_bound("dm", "view_mouse_drag_translate") ||
|
||||
!bu_binding_action_is_bound("dm", "view_mouse_drag_scale")) {
|
||||
Tcl_AppendResult(interp, "Validation Failed: The provided bindings do not map inputs for view_mouse_drag_rotate/translate/scale. These are required standard actions.", NULL);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static int tclcad_dispatch_event_cmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]) {
|
||||
if (argc < 8) return TCL_OK;
|
||||
struct tclcad_obj *to = (struct tclcad_obj *)clientData;
|
||||
struct bview *gdvp = bv_set_find_view(&to->to_gedp->ged_views, argv[2]);
|
||||
if (!gdvp) return TCL_OK;
|
||||
|
||||
int x = atoi(argv[5]);
|
||||
int y = atoi(argv[6]);
|
||||
int state = atoi(argv[7]);
|
||||
|
||||
struct bu_vls ev = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&ev, "{\"type\": \"%s\"", argv[3]);
|
||||
|
||||
if (!strcmp(argv[3], "key-press")) {
|
||||
char key_str[16] = {0};
|
||||
bu_strlcpy(key_str, argv[4], sizeof(key_str));
|
||||
if (strlen(key_str) == 1 && key_str[0] >= 'a' && key_str[0] <= 'z') {
|
||||
key_str[0] = key_str[0] - 'a' + 'A';
|
||||
}
|
||||
bu_vls_printf(&ev, ", \"key\": \"%s\"", key_str);
|
||||
} else if (!strcmp(argv[3], "mouse-wheel")) {
|
||||
int delta = (argc > 8) ? atoi(argv[8]) : 0;
|
||||
int mdelta = -1 * delta / 120;
|
||||
bu_vls_printf(&ev, ", \"delta\": %d", mdelta);
|
||||
} else {
|
||||
int btn = atoi(argv[4]) - 1;
|
||||
if (!strcmp(argv[3], "mouse-move")) {
|
||||
if (state & 0x100) btn = 0;
|
||||
else if (state & 0x200) btn = 1;
|
||||
else if (state & 0x400) btn = 2;
|
||||
else btn = -1;
|
||||
|
||||
int dx = x - gdvp->gv_prevMouseX;
|
||||
int dy = y - gdvp->gv_prevMouseY;
|
||||
bu_vls_printf(&ev, ", \"dx\": %d, \"dy\": %d, \"mode\": 0", dx, dy);
|
||||
}
|
||||
if (btn >= 0) bu_vls_printf(&ev, ", \"button\": %d", btn);
|
||||
bu_vls_printf(&ev, ", \"cx\": %d, \"cy\": %d", x, y);
|
||||
}
|
||||
|
||||
if (state & (1 | 4 | 8)) {
|
||||
bu_vls_printf(&ev, ", \"modifiers\": [");
|
||||
int added = 0;
|
||||
if (state & 1) { bu_vls_printf(&ev, "\"Shift\""); added=1; }
|
||||
if (state & 4) { if(added) bu_vls_printf(&ev, ", "); bu_vls_printf(&ev, "\"Control\""); added=1; }
|
||||
if (state & 8) { if(added) bu_vls_printf(&ev, ", "); bu_vls_printf(&ev, "\"Alt\""); }
|
||||
bu_vls_printf(&ev, "]");
|
||||
}
|
||||
bu_vls_printf(&ev, "}");
|
||||
|
||||
bu_binding_process_event("dm", bu_vls_cstr(&ev), gdvp);
|
||||
|
||||
gdvp->gv_prevMouseX = x;
|
||||
gdvp->gv_prevMouseY = y;
|
||||
|
||||
bu_vls_free(&ev);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
to_init_default_bindings(struct bview *gdvp)
|
||||
{
|
||||
@@ -2832,6 +2975,35 @@ to_init_default_bindings(struct bview *gdvp)
|
||||
if (dm_get_pathname((struct dm *)gdvp->dmp)) {
|
||||
struct bu_vls *pathvls = dm_get_pathname((struct dm *)gdvp->dmp);
|
||||
if (pathvls) {
|
||||
if (!bindings_loaded_mged) {
|
||||
bindings_loaded_mged = 1;
|
||||
|
||||
|
||||
bu_binding_register_action("view_2", tclcad_action_eval_aet, (void*)"35 -25 0");
|
||||
bu_binding_register_action("view_3", tclcad_action_eval_aet, (void*)"35 25 0");
|
||||
bu_binding_register_action("view_4", tclcad_action_eval_aet, (void*)"45 45 0");
|
||||
bu_binding_register_action("view_5", tclcad_action_eval_aet, (void*)"145 25 0");
|
||||
bu_binding_register_action("view_6", tclcad_action_eval_aet, (void*)"215 25 0");
|
||||
bu_binding_register_action("view_7", tclcad_action_eval_aet, (void*)"325 25 0");
|
||||
bu_binding_register_action("view_front", tclcad_action_eval_aet, (void*)"0 0 0");
|
||||
bu_binding_register_action("view_top", tclcad_action_eval_aet, (void*)"270 90 0");
|
||||
bu_binding_register_action("view_bottom", tclcad_action_eval_aet, (void*)"270 -90 0");
|
||||
bu_binding_register_action("view_left", tclcad_action_eval_aet, (void*)"90 0 0");
|
||||
bu_binding_register_action("view_rear", tclcad_action_eval_aet, (void*)"180 0 0");
|
||||
bu_binding_register_action("view_right", tclcad_action_eval_aet, (void*)"270 0 0");
|
||||
|
||||
bu_binding_register_action("view_mouse_drag_rotate", tclcad_action_mouse_drag, (void*)"mouse_rot");
|
||||
bu_binding_register_action("view_mouse_drag_translate", tclcad_action_mouse_drag, (void*)"mouse_trans");
|
||||
bu_binding_register_action("view_mouse_drag_scale", tclcad_action_mouse_drag, (void*)"mouse_scale");
|
||||
bu_binding_register_action("view_mouse_wheel_scale", tclcad_action_wheel_scale, NULL);
|
||||
|
||||
bu_binding_register_action("view_mouse_press_left", tclcad_action_mouse_click, (void*)"%s zoom %s 0.5");
|
||||
bu_binding_register_action("view_mouse_press_right", tclcad_action_mouse_click, (void*)"%s zoom %s 2.0");
|
||||
|
||||
Tcl_CreateCommand(current_top->to_interp, "tclcad_dispatch_event", tclcad_dispatch_event_cmd, (ClientData)current_top, (Tcl_CmdDeleteProc *)NULL);
|
||||
Tcl_CreateCommand(current_top->to_interp, "tclcad_load_bindings", tclcad_load_bindings_cmd, (ClientData)current_top, (Tcl_CmdDeleteProc *)NULL);
|
||||
}
|
||||
|
||||
bu_vls_printf(&bindings, "bind %s <Configure> {%s configure %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
@@ -2848,240 +3020,14 @@ to_init_default_bindings(struct bview *gdvp)
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Mouse Bindings */
|
||||
bu_vls_printf(&bindings, "bind %s <2> {%s vslew %s %%x %%y; focus %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name),
|
||||
bu_vls_addr(pathvls));
|
||||
bu_vls_printf(&bindings, "bind %s <1> {%s zoom %s 0.5; focus %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name),
|
||||
bu_vls_addr(pathvls));
|
||||
bu_vls_printf(&bindings, "bind %s <3> {%s zoom %s 2.0; focus %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name),
|
||||
bu_vls_addr(pathvls));
|
||||
bu_vls_printf(&bindings, "bind %s <4> {%s zoom %s 1.1; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <5> {%s zoom %s 0.9; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <MouseWheel> {if {%%D < 0} {%s zoom %s 0.9} else {%s zoom %s 1.1}; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Idle Mode */
|
||||
bu_vls_printf(&bindings, "bind %s <ButtonRelease> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <KeyRelease-Control_L> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <KeyRelease-Control_R> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <KeyRelease-Shift_L> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <KeyRelease-Shift_R> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <KeyRelease-Alt_L> {%s idle_mode %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <KeyRelease-Alt_R> {%s idle_mode %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Rotate Mode */
|
||||
bu_vls_printf(&bindings, "bind %s <Control-ButtonRelease-1> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-ButtonPress-1> {%s rotate_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-ButtonPress-2> {%s rotate_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-ButtonPress-3> {%s rotate_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Translate Mode */
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-ButtonRelease-1> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-ButtonPress-1> {%s translate_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-ButtonPress-2> {%s translate_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-ButtonPress-3> {%s translate_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Scale Mode */
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Shift-ButtonRelease-1> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Shift-ButtonPress-1> {%s scale_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Shift-ButtonPress-2> {%s scale_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Shift-ButtonPress-3> {%s scale_mode %s %%x %%y}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Constrained Rotate Mode */
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Lock-ButtonRelease-1> {%s idle_mode %s}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Lock-ButtonPress-1> {%s constrain_rmode %s x %%x %%y; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Lock-ButtonPress-2> {%s constrain_rmode %s y %%x %%y; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Control-Lock-ButtonPress-3> {%s constrain_rmode %s z %%x %%y; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Constrained Translate Mode */
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-Lock-ButtonRelease-1> {%s idle_mode %s; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-Lock-ButtonPress-1> {%s constrain_tmode %s x %%x %%y; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-Lock-ButtonPress-2> {%s constrain_tmode %s y %%x %%y; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Shift-Lock-ButtonPress-3> {%s constrain_tmode %s z %%x %%y; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
/* Key Bindings */
|
||||
bu_vls_printf(&bindings, "bind %s 3 {%s aet %s 35 25; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s 4 {%s aet %s 45 45; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s f {%s aet %s 0 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s F {%s aet %s 0 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s R {%s aet %s 180 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s r {%s aet %s 270 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s l {%s aet %s 90 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s L {%s aet %s 90 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s t {%s aet %s 270 90; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s T {%s aet %s 270 90; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s b {%s aet %s 270 -90; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s B {%s aet %s 270 -90; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s + {%s zoom %s 2.0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s = {%s zoom %s 2.0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s _ {%s zoom %s 0.5; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s - {%s zoom %s 0.5; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Key-Left> {%s rot %s -v 0 1 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Key-Right> {%s rot %s -v 0 -1 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Key-Up> {%s rot %s -v 1 0 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Key-Down> {%s rot %s -v -1 0 0; break}; ",
|
||||
bu_vls_addr(pathvls),
|
||||
bu_vls_addr(¤t_top->to_gedp->go_name),
|
||||
bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
Tcl_Eval(current_top->to_interp, bu_vls_addr(&bindings));
|
||||
/* Dispatch all events to bu_binding */
|
||||
bu_vls_printf(&bindings, "bind %s <Any-KeyPress> {tclcad_dispatch_event %%W %s %s key-press %%K %%x %%y %%s; break}; ", bu_vls_addr(pathvls), bu_vls_addr(¤t_top->to_gedp->go_name), bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Any-ButtonPress> {tclcad_dispatch_event %%W %s %s mouse-press %%b %%x %%y %%s; break}; ", bu_vls_addr(pathvls), bu_vls_addr(¤t_top->to_gedp->go_name), bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Any-ButtonRelease> {tclcad_dispatch_event %%W %s %s mouse-release %%b %%x %%y %%s; break}; ", bu_vls_addr(pathvls), bu_vls_addr(¤t_top->to_gedp->go_name), bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <Any-Motion> {tclcad_dispatch_event %%W %s %s mouse-move 0 %%x %%y %%s; break}; ", bu_vls_addr(pathvls), bu_vls_addr(¤t_top->to_gedp->go_name), bu_vls_addr(&gdvp->gv_name));
|
||||
bu_vls_printf(&bindings, "bind %s <MouseWheel> {tclcad_dispatch_event %%W %s %s mouse-wheel 0 %%x %%y %%s %%D; break}; ", bu_vls_addr(pathvls), bu_vls_addr(¤t_top->to_gedp->go_name), bu_vls_addr(&gdvp->gv_name));
|
||||
|
||||
Tcl_Eval(current_top->to_interp, bu_vls_cstr(&bindings));
|
||||
}
|
||||
}
|
||||
bu_vls_free(&bindings);
|
||||
|
||||
@@ -49,12 +49,8 @@ proc mged_bind_dm { w } {
|
||||
|
||||
bind $w <Enter> [list mged_activate_dm $w]
|
||||
|
||||
#default mouse bindings
|
||||
default_mouse_bindings $w
|
||||
# Load standardized JSON bindings for mged
|
||||
|
||||
#default key bindings
|
||||
set forwarding_key($w) 0
|
||||
default_key_bindings $w
|
||||
}
|
||||
|
||||
proc print_return_val str {
|
||||
@@ -64,129 +60,6 @@ proc print_return_val str {
|
||||
}
|
||||
}
|
||||
|
||||
if ![info exists mged_default(dm_key_bindings)] {
|
||||
set mged_default(dm_key_bindings) "\tKey Sequence\t\tBehavior
|
||||
\ta\t\t\ttoggle angle distance cursor (ADC)
|
||||
\te\t\t\ttoggle edit axes
|
||||
\tm\t\t\ttoggle model axes
|
||||
\tv\t\t\ttoggle view axes
|
||||
\ti\t\t\tadvance illumination pointer forward
|
||||
\tI\t\t\tadvance illumination pointer backward
|
||||
\tp\t\t\tsimulate mouse press (i.e. to pick a solid)
|
||||
\t3\t\t\tview - ae 35 25
|
||||
\t4\t\t\tview - ae 45 45
|
||||
\tf\t\t\tfront view
|
||||
\tt\t\t\ttop view
|
||||
\tb\t\t\tbottom view
|
||||
\tl\t\t\tleft view
|
||||
\tr\t\t\tright view
|
||||
\tR\t\t\trear view
|
||||
\ts\t\t\tenter solid illumination state
|
||||
\to\t\t\tenter object illumination state
|
||||
\tq\t\t\treject edit
|
||||
\tu\t\t\tzero knobs and sliders
|
||||
\t<F1>\t\t\ttoggle depthcue
|
||||
\t<F2>\t\t\ttoggle zclip
|
||||
\t<F3>\t\t\ttoggle perspective
|
||||
\t<F4>\t\t\ttoggle zbuffer
|
||||
\t<F5>\t\t\ttoggle lighting
|
||||
\t<F6>\t\t\ttoggle perspective angle
|
||||
\t<F7>\t\t\ttoggle faceplate
|
||||
\t<F8>\t\t\ttoggle faceplate GUI
|
||||
\t<F9>\t\t\ttoggle keystroke forwarding
|
||||
\t<F12>\t\t\tzero knobs
|
||||
\t<Left>\t\t\tabsolute rotate about y axis
|
||||
\t<Right>\t\t\tabsolute rotate about y axis
|
||||
\t<Down>\t\t\tabsolute rotate about x axis
|
||||
\t<Up>\t\t\tabsolute rotate about x axis
|
||||
\t<Shift-Left>\t\tabsolute translate in X direction
|
||||
\t<Shift-Right>\t\tabsolute translate in X direction
|
||||
\t<Shift-Down>\t\tabsolute translate in Z direction
|
||||
\t<Shift-Up>\t\tabsolute translate in Z direction
|
||||
\t<Control-Shift-Left>\tabsolute rotate about z axis
|
||||
\t<Control-Shift-Right>\tabsolute rotate about z axis
|
||||
\t<Control-Shift-Down>\tabsolute translate in Y direction
|
||||
\t<Control-Shift-Up>\tabsolute translate in Y direction
|
||||
\t<Control-n>\t\tgoto next view
|
||||
\t<Control-p>\t\tgoto previous view
|
||||
\t<Control-t>\t\ttoggle between the current view and the last view"
|
||||
}
|
||||
|
||||
proc default_key_bindings { w } {
|
||||
# common view shortcuts
|
||||
bind $w 2 "winset $w; ae 35 -25; break"
|
||||
bind $w 3 "winset $w; press 35,25; break"
|
||||
bind $w 4 "winset $w; press 45,45; break"
|
||||
bind $w 5 "winset $w; ae 145 25; break"
|
||||
bind $w 6 "winset $w; ae 215 25; break"
|
||||
bind $w 7 "winset $w; ae 325 25; break"
|
||||
|
||||
# default key shortcuts
|
||||
bind $w a "winset $w; adc; break"
|
||||
bind $w b "winset $w; press bottom; break"
|
||||
bind $w c "open_cmd_win id_0"
|
||||
bind $w e "winset $w; rset ax edit_draw !; update_gui $w edit_draw \[rset ax edit_draw\]; break"
|
||||
bind $w f "winset $w; press front; break"
|
||||
bind $w i "winset $w; aip f; break"
|
||||
bind $w l "winset $w; press left; break"
|
||||
bind $w m "winset $w; rset ax model_draw !; update_gui $w model_draw \[rset ax model_draw\]; break"
|
||||
bind $w o "winset $w; press oill; break"
|
||||
bind $w p "winset $w; M 1 0 0; break"
|
||||
bind $w q "winset $w; press reject; break"
|
||||
bind $w r "winset $w; press right; break"
|
||||
bind $w s "winset $w; press sill; break"
|
||||
bind $w t "winset $w; press top; break"
|
||||
bind $w u "winset $w; svb; break"
|
||||
bind $w v "winset $w; rset ax view_draw !; update_gui $w view_draw \[rset ax view_draw\]; break"
|
||||
|
||||
# default shift+key shortcuts
|
||||
bind $w A "winset $w; press accept; break"
|
||||
bind $w I "winset $w; aip b; break"
|
||||
bind $w N "winset $w; puts \[nirt -b\]; break"
|
||||
bind $w P "winset $w; catch {sed_apply}; break"
|
||||
bind $w R "winset $w; press rear; break"
|
||||
bind $w S "winset $w; catch {sed_reset}; break"
|
||||
|
||||
# default control+key shortcuts
|
||||
bind $w <Control-n> "winset $w; _mged_view_ring next; break"
|
||||
bind $w <Control-p> "winset $w; _mged_view_ring prev; break"
|
||||
bind $w <Control-t> "winset $w; _mged_view_ring toggle; break"
|
||||
|
||||
# shift grips navigation
|
||||
bind $w <Control-Shift-Down> "winset $w; knob -i aY \$mged_default(tran_factor); break"
|
||||
bind $w <Control-Shift-Left> "winset $w; knob -i az \$mged_default(rot_factor); break"
|
||||
bind $w <Control-Shift-Right> "winset $w; knob -i az -\$mged_default(rot_factor); break"
|
||||
bind $w <Control-Shift-Up> "winset $w; knob -i aY -\$mged_default(tran_factor); break"
|
||||
bind $w <Down> "winset $w; knob -i ax \$mged_default(rot_factor); break"
|
||||
bind $w <Left> "winset $w; knob -i ay -\$mged_default(rot_factor); break"
|
||||
bind $w <Right> "winset $w; knob -i ay \$mged_default(rot_factor); break"
|
||||
bind $w <Up> "winset $w; knob -i ax -\$mged_default(rot_factor); break"
|
||||
bind $w <Shift-Down> "winset $w; knob -i aZ -\$mged_default(tran_factor); break"
|
||||
bind $w <Shift-Left> "winset $w; knob -i aX \$mged_default(tran_factor); break"
|
||||
bind $w <Shift-Right> "winset $w; knob -i aX -\$mged_default(tran_factor); break"
|
||||
bind $w <Shift-Up> "winset $w; knob -i aZ \$mged_default(tran_factor); break"
|
||||
|
||||
# function keys (settings)
|
||||
bind $w <F1> "winset $w; dm set depthcue !; update_gui $w depthcue \[dm set depthcue\]; break"
|
||||
bind $w <F2> "winset $w; dm set zclip !; update_gui $w zclip \[dm set zclip\]; break"
|
||||
bind $w <F3> "winset $w; set perspective_mode !; update_gui $w perspective_mode \$perspective_mode; break"
|
||||
bind $w <F4> "winset $w; dm set zbuffer !; update_gui $w zbuffer \[dm set zbuffer\]; break"
|
||||
bind $w <F5> "winset $w; dm set lighting !; update_gui $w lighting \[dm set lighting\]; break"
|
||||
bind $w <F6> "winset $w; set toggle_perspective !; break"
|
||||
bind $w <F7> "winset $w; set faceplate !; update_gui $w faceplate \$faceplate; break"
|
||||
bind $w <F8> "winset $w; set orig_gui !; update_gui $w orig_gui \$orig_gui; break"
|
||||
bind $w <F9> "toggle_forward_key_bindings $w; update_gui $w forward_keys \$forwarding_key($w); break"
|
||||
bind $w <F12> "winset $w; knob zero; break"
|
||||
|
||||
# interrupt
|
||||
bind $w <Escape> "winset $w; reset_everything $w; break"
|
||||
|
||||
# Throw away other key events
|
||||
bind $w <KeyPress> {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
proc reset_everything { w } {
|
||||
global mged_gui
|
||||
|
||||
@@ -202,274 +75,6 @@ proc reset_everything { w } {
|
||||
set_mouse_behavior $id
|
||||
}
|
||||
|
||||
proc set_forward_keys { w val } {
|
||||
global forwarding_key
|
||||
|
||||
set forwarding_key($w) $val
|
||||
if {$forwarding_key($w)} {
|
||||
forward_key_bindings $w
|
||||
} else {
|
||||
default_key_bindings $w
|
||||
}
|
||||
}
|
||||
|
||||
proc toggle_forward_key_bindings { w } {
|
||||
global forwarding_key
|
||||
|
||||
if {$forwarding_key($w)} {
|
||||
default_key_bindings $w
|
||||
set forwarding_key($w) 0
|
||||
} else {
|
||||
forward_key_bindings $w
|
||||
set forwarding_key($w) 1
|
||||
}
|
||||
}
|
||||
|
||||
proc forward_key_bindings { w } {
|
||||
set id [get_player_id_dm $w]
|
||||
|
||||
if {$id == "mged"} {
|
||||
return
|
||||
}
|
||||
|
||||
# First, unset the default key bindings
|
||||
bind $w a {}
|
||||
bind $w e {}
|
||||
bind $w m {}
|
||||
bind $w v {}
|
||||
bind $w i {}
|
||||
bind $w I {}
|
||||
bind $w p {}
|
||||
bind $w 2 {}
|
||||
bind $w 3 {}
|
||||
bind $w 4 {}
|
||||
bind $w 5 {}
|
||||
bind $w 6 {}
|
||||
bind $w 7 {}
|
||||
bind $w f {}
|
||||
bind $w t {}
|
||||
bind $w b {}
|
||||
bind $w l {}
|
||||
bind $w N {}
|
||||
bind $w r {}
|
||||
bind $w R {}
|
||||
bind $w s {}
|
||||
bind $w o {}
|
||||
bind $w q {}
|
||||
bind $w u {}
|
||||
bind $w c {}
|
||||
bind $w A {}
|
||||
bind $w P {}
|
||||
bind $w S {}
|
||||
bind $w <F1> {}
|
||||
bind $w <F2> {}
|
||||
bind $w <F3> {}
|
||||
bind $w <F4> {}
|
||||
bind $w <F5> {}
|
||||
bind $w <F6> {}
|
||||
bind $w <F7> {}
|
||||
bind $w <F8> {}
|
||||
bind $w <F12> {}
|
||||
|
||||
bind $w <Left> {}
|
||||
bind $w <Right> {}
|
||||
bind $w <Down> {}
|
||||
bind $w <Up> {}
|
||||
bind $w <Shift-Left> {}
|
||||
bind $w <Shift-Right> {}
|
||||
bind $w <Shift-Down> {}
|
||||
bind $w <Shift-Up> {}
|
||||
bind $w <Control-Shift-Left> {}
|
||||
bind $w <Control-Shift-Right> {}
|
||||
bind $w <Control-Shift-Down> {}
|
||||
bind $w <Control-Shift-Up> {}
|
||||
|
||||
bind $w <Control-n> {}
|
||||
bind $w <Control-p> {}
|
||||
bind $w <Control-t> {}
|
||||
|
||||
bind $w <Escape> {}
|
||||
|
||||
# The focus commands in the binding below are necessary to insure
|
||||
# that .$id.t gets the event.
|
||||
bind $w <KeyPress> "\
|
||||
focus .$id.t;\
|
||||
set mged_gui(.$id.t,insert_char_flag) 1;\
|
||||
event generate .$id.t <KeyPress> -state %s -keysym %K;\
|
||||
set mged_gui(.$id.t,insert_char_flag) 0;\
|
||||
focus %W;\
|
||||
break"
|
||||
}
|
||||
|
||||
proc default_mouse_bindings { w } {
|
||||
global no_focus_hack
|
||||
global transform
|
||||
global tcl_platform
|
||||
|
||||
# default button bindings
|
||||
if {![regexp {^[0-9]+} $::tcl_platform(osVersion) osMajorVersion]} {
|
||||
set osMajorVersion 0
|
||||
}
|
||||
|
||||
if {[info exists no_focus_hack] && $no_focus_hack} {
|
||||
bind $w <1> "winset $w; zoom 0.5; break"
|
||||
bind $w <2> "winset $w; set tmpstr \[dm m %x %y\]; print_return_val \$tmpstr; break"
|
||||
bind $w <3> "winset $w; zoom 2.0; break"
|
||||
|
||||
# Mouse wheel fun with Tcl/Tk - see https://wiki.tcl-lang.org/page/mousewheel
|
||||
bind $w <MouseWheel> "winset $w; focus $w; if {%D < 0} {zoom 0.9} else {zoom 1.1}"
|
||||
bind $w <Button-4> {event generate [focus] <MouseWheel> -delta 120}
|
||||
bind $w <Button-5> {event generate [focus] <MouseWheel> -delta -120}
|
||||
|
||||
if {$::tcl_platform(os) == "Darwin"} {
|
||||
# Mac option-key emulates mouse 2 (doesn't work?)
|
||||
bind $w <Option-ButtonPress-1> "winset $w; set tmpstr \[dm m %x %y\]; print_return_val \$tmpstr; break"
|
||||
# Mac command-key emulates mouse 3
|
||||
bind $w <Mod2-ButtonPress-1> "winset $w; zoom 2.0; break"
|
||||
}
|
||||
} else {
|
||||
bind $w <1> "winset $w; focus $w; zoom 0.5; break"
|
||||
bind $w <2> "winset $w; focus $w; set tmpstr \[dm m %x %y\]; print_return_val \$tmpstr; break"
|
||||
bind $w <3> "winset $w; focus $w; zoom 2.0; break"
|
||||
|
||||
# Mouse wheel fun with Tcl/Tk - see https://wiki.tcl-lang.org/page/mousewheel
|
||||
bind $w <MouseWheel> "winset $w; focus $w; if {%D < 0} {zoom 0.9} else {zoom 1.1}; break"
|
||||
bind $w <Button-4> {event generate [focus] <MouseWheel> -delta 120}
|
||||
bind $w <Button-5> {event generate [focus] <MouseWheel> -delta -120}
|
||||
|
||||
if {$::tcl_platform(os) == "Darwin"} {
|
||||
# Mac command-key emulates mouse 2 (doesn't work?)
|
||||
bind $w <Option-ButtonPress-1> "winset $w; focus $w; set tmpstr \[dm m %x %y\]; print_return_val \$tmpstr; break"
|
||||
# Mac command-key emulates mouse 3
|
||||
bind $w <Mod2-ButtonPress-1> "winset $w; focus $w; zoom 2.0; break"
|
||||
}
|
||||
}
|
||||
|
||||
bind $w <ButtonRelease> "winset $w; dm idle; break"
|
||||
bind $w <Motion> "winset $w; if {\[dm type\] == \"tkswrast\"} {dm motion %x %y}; break"
|
||||
bind $w <KeyRelease-Control_L> "winset $w; dm idle; break"
|
||||
bind $w <KeyRelease-Control_R> "winset $w; dm idle; break"
|
||||
bind $w <KeyRelease-Shift_L> "winset $w; dm idle; break"
|
||||
bind $w <KeyRelease-Shift_R> "winset $w; dm idle; break"
|
||||
bind $w <KeyRelease-Alt_L> "winset $w; dm idle; break"
|
||||
bind $w <KeyRelease-Alt_R> "winset $w; dm idle; break"
|
||||
|
||||
if ![catch {adc draw} result] {
|
||||
set adcflag $result
|
||||
} else {
|
||||
set adcflag 0
|
||||
}
|
||||
|
||||
if {$adcflag == "1" && $transform == "a"} {
|
||||
bind $w <Shift-ButtonPress-1> "winset $w; dm adc t %x %y; \
|
||||
shift_grip_hints $w \"Translate ADC\"; break"
|
||||
bind $w <Shift-ButtonPress-2> "winset $w; dm adc t %x %y; \
|
||||
shift_grip_hints $w \"Translate ADC\"; break"
|
||||
bind $w <Shift-ButtonPress-3> "winset $w; dm adc d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
|
||||
bind $w <Control-ButtonPress-1> "winset $w; dm adc 1 %x %y; \
|
||||
shift_grip_hints $w \"Rotate Angle 1\"; break"
|
||||
bind $w <Control-ButtonPress-2> "winset $w; dm adc 2 %x %y; \
|
||||
shift_grip_hints $w \"Rotate Angle 2\"; break"
|
||||
bind $w <Control-ButtonPress-3> "winset $w; dm adc d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
|
||||
bind $w <Shift-Control-ButtonPress-1> "winset $w; dm adc d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
bind $w <Shift-Control-ButtonPress-2> "winset $w; dm adc d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
bind $w <Shift-Control-ButtonPress-3> "winset $w; dm adc d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
|
||||
#constrained adc defaults
|
||||
bind $w <Alt-Shift-ButtonPress-1> "winset $w; dm con a x %x %y; \
|
||||
shift_grip_hints $w \"X Translate ADC\"; break"
|
||||
bind $w <Alt-Shift-ButtonPress-2> "winset $w; dm con a y %x %y; \
|
||||
shift_grip_hints $w \"Y Translate ADC\"; break"
|
||||
bind $w <Alt-Shift-ButtonPress-3> "winset $w; dm con a d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
|
||||
bind $w <Alt-Control-ButtonPress-1> "winset $w; dm con a 1 %x %y; \
|
||||
shift_grip_hints $w \"Rotate Angle 1\"; break"
|
||||
bind $w <Alt-Control-ButtonPress-2> "winset $w; dm con a 2 %x %y; \
|
||||
shift_grip_hints $w \"Rotate Angle 2\"; break"
|
||||
bind $w <Alt-Control-ButtonPress-3> "winset $w; dm con a d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
|
||||
bind $w <Alt-Shift-Control-ButtonPress-1> "winset $w; dm con a d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
bind $w <Alt-Shift-Control-ButtonPress-2> "winset $w; dm con a d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
bind $w <Alt-Shift-Control-ButtonPress-3> "winset $w; dm con a d %x %y; \
|
||||
shift_grip_hints $w \"Translate Tick Distance\"; break"
|
||||
} else {
|
||||
bind $w <Shift-ButtonPress-1> "winset $w; dm am t %x %y; \
|
||||
shift_grip_hints $w Translate; break"
|
||||
bind $w <Shift-ButtonPress-2> "winset $w; dm am t %x %y; \
|
||||
shift_grip_hints $w Translate; break"
|
||||
bind $w <Shift-ButtonPress-3> "winset $w; dm am t %x %y; \
|
||||
shift_grip_hints $w Translate; break"
|
||||
|
||||
bind $w <Control-ButtonPress-1> "winset $w; dm am r %x %y; \
|
||||
shift_grip_hints $w Rotate; break"
|
||||
bind $w <Control-ButtonPress-2> "winset $w; dm am r %x %y; \
|
||||
shift_grip_hints $w Rotate; break"
|
||||
bind $w <Control-ButtonPress-3> "winset $w; dm am r %x %y; \
|
||||
shift_grip_hints $w Rotate; break"
|
||||
|
||||
bind $w <Shift-Control-ButtonPress-1> "winset $w; dm am s %x %y; \
|
||||
shift_grip_hints $w Scale/Zoom; break"
|
||||
bind $w <Shift-Control-ButtonPress-2> "winset $w; dm am s %x %y; \
|
||||
shift_grip_hints $w Scale/Zoom; break"
|
||||
bind $w <Shift-Control-ButtonPress-3> "winset $w; dm am s %x %y; \
|
||||
shift_grip_hints $w Scale/Zoom; break"
|
||||
|
||||
#constrained defaults
|
||||
bind $w <Alt-Shift-ButtonPress-1> "winset $w; dm con t x %x %y; \
|
||||
shift_grip_hints $w \"X Translation\"; break"
|
||||
bind $w <Alt-Shift-ButtonPress-2> "winset $w; dm con t y %x %y; \
|
||||
shift_grip_hints $w \"Y Translation\"; break"
|
||||
bind $w <Alt-Shift-ButtonPress-3> "winset $w; dm con t z %x %y; \
|
||||
shift_grip_hints $w \"Z Translation\"; break"
|
||||
|
||||
bind $w <Alt-Control-ButtonPress-1> "winset $w; dm con r x %x %y; \
|
||||
shift_grip_hints $w \"X Rotation\"; break"
|
||||
bind $w <Alt-Control-ButtonPress-2> "winset $w; dm con r y %x %y; \
|
||||
shift_grip_hints $w \"Y Rotation\"; break"
|
||||
bind $w <Alt-Control-ButtonPress-3> "winset $w; dm con r z %x %y; \
|
||||
shift_grip_hints $w \"Z Rotation\"; break"
|
||||
|
||||
bind $w <Alt-Shift-Control-ButtonPress-1> "winset $w; dm con s x %x %y; \
|
||||
scale_shift_grip_hints $w X; break"
|
||||
bind $w <Alt-Shift-Control-ButtonPress-2> "winset $w; dm con s y %x %y; \
|
||||
scale_shift_grip_hints $w Y; break"
|
||||
bind $w <Alt-Shift-Control-ButtonPress-3> "winset $w; dm con s z %x %y; \
|
||||
scale_shift_grip_hints $w Z; break"
|
||||
}
|
||||
}
|
||||
|
||||
proc shift_grip_hints { w hint } {
|
||||
global mged_display
|
||||
global mged_gui
|
||||
global win_to_id
|
||||
|
||||
if ![info exists win_to_id($w)] {
|
||||
return
|
||||
}
|
||||
|
||||
set id $win_to_id($w)
|
||||
set mged_gui($id,illum_label) $hint
|
||||
}
|
||||
|
||||
proc scale_shift_grip_hints { w axis } {
|
||||
if {[status state] == "OBJ EDIT"} {
|
||||
shift_grip_hints $w "$axis Scale"
|
||||
} else {
|
||||
shift_grip_hints $w "Scale/Zoom"
|
||||
}
|
||||
}
|
||||
|
||||
proc update_gui { w vname val } {
|
||||
global mged_players
|
||||
global mged_gui
|
||||
@@ -482,6 +87,14 @@ proc update_gui { w vname val } {
|
||||
}
|
||||
}
|
||||
|
||||
# Dummy stubs to prevent legacy scripts from failing if they try to invoke them
|
||||
proc default_key_bindings { w } {}
|
||||
proc default_mouse_bindings { w } {}
|
||||
proc forward_key_bindings { w } {}
|
||||
proc toggle_forward_key_bindings { w } {}
|
||||
proc set_forward_keys { w val } {}
|
||||
proc shift_grip_hints { w hint } {}
|
||||
proc scale_shift_grip_hints { w axis } {}
|
||||
|
||||
# Local Variables:
|
||||
# mode: Tcl
|
||||
|
||||
Reference in New Issue
Block a user