From 94266c28195b4a43cb162593295745bc004a2420 Mon Sep 17 00:00:00 2001 From: HanifKoh <76276251+HanifKoh@users.noreply.github.com> Date: Wed, 30 Sep 2026 18:14:28 +0800 Subject: [PATCH 1/6] Fill Settings Missing From a CLI Project From Its System Presets (#15953) * Fill Settings Missing From a CLI Project From Its System Presets A project saved before a printer or process option existed has no value for it. The GUI takes such keys from the project's system preset; the CLI left them at the option default, so e.g. extruder_clearance_dist_to_rod sliced as 40 instead of the P1S's 33. The CLI now resolves the project's system printer and process presets by name and copies the keys the project lacks, skipping preset bookkeeping, print-host keys, the extruder variant layout and keys the legacy handler drops. PresetBundle::resolve_system_preset finds the vendor through its manifest or preset cache, so it also works in release builds, which ship vendors as caches only. * Load a CLI Project's Printer and Process Settings as the GUI Does The CLI filled only the keys a project lacked from its system preset. The GUI builds a project preset differently: the project's values go over the default preset, without the print-host keys, and every key the project does not list in different_settings_to_system is refreshed to its base system preset's current value. After a profile update the two sliced the same project differently. That step now lives in Preset::load_external_config, which takes plain configs and gets the base preset from a callback, so the collection lookup stays in PresetCollection. PresetBundle::project_different_keys builds the kept-key set from a project's escaped entry, adding the preset bookkeeping keys, and is used by both the GUI and the CLI. PresetCollection::load_external_preset calls the shared step with no change in behaviour. The CLI now builds the project's printer and process configs with the same step, passing the system preset from resolve_system_preset. That drops the hand-kept skip list for print-host and variant-layout keys and the legacy-key check: the shared step already excludes print-host keys and maps per-variant values onto the base preset's variant layout. The --uptodate path and a printer or process given on the command line are left as they were. Because the GUI's kept-key set always holds the bookkeeping keys, the refresh runs for every project that names a system preset, so the CLI now loads that preset's vendor on every such run. --- src/OrcaSlicer.cpp | 53 +++- src/libslic3r/Preset.cpp | 82 +++--- src/libslic3r/Preset.hpp | 9 + src/libslic3r/PresetBundle.cpp | 100 ++++--- src/libslic3r/PresetBundle.hpp | 12 +- tests/cli/CMakeLists.txt | 8 + tests/cli/test_cli_project_missing_keys.sh | 96 +++++++ .../libslic3r/test_preset_bundle_loading.cpp | 256 ++++++++++++++++++ 8 files changed, 534 insertions(+), 82 deletions(-) create mode 100644 tests/cli/test_cli_project_missing_keys.sh diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 1939988261..3ed76b8976 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -2116,9 +2116,14 @@ int CLI::run(int argc, char **argv) // One resolver for the whole run, so presets from the same vendor tree share its load. std::unique_ptr system_preset_resolver; - auto resolve_preset = [&ensure_cli_preset_bundle, &system_preset_resolver](const std::string &file, DynamicPrintConfig &config, - std::string &config_type, const std::string &config_from, - bool probe_type, std::string &error) { + auto ensure_system_preset_resolver = [&system_preset_resolver]() -> PresetBundle & { + if (!system_preset_resolver) + system_preset_resolver = std::make_unique(); + return *system_preset_resolver; + }; + auto resolve_preset = [&ensure_cli_preset_bundle, &ensure_system_preset_resolver](const std::string &file, DynamicPrintConfig &config, + std::string &config_type, const std::string &config_from, + bool probe_type, std::string &error) { const auto *inherits = config.option(BBL_JSON_KEY_INHERITS); if (!probe_type && (inherits == nullptr || inherits->value.empty())) return true; @@ -2126,9 +2131,7 @@ int CLI::run(int argc, char **argv) PresetBundle *bundle = nullptr; bool allow_source_manifest = false; if (config_from == "system") { - if (!system_preset_resolver) - system_preset_resolver = std::make_unique(); - bundle = system_preset_resolver.get(); + bundle = &ensure_system_preset_resolver(); allow_source_manifest = true; } else { bundle = ensure_cli_preset_bundle(error); @@ -3120,6 +3123,44 @@ int CLI::run(int argc, char **argv) return 0; }; + // Load the project's printer and process settings as the GUI loads its presets: over the default preset, + // with every key the project does not list as changed, including keys saved before an option existed, + // taken from its current system preset. + auto load_project_preset = [this, &ensure_system_preset_resolver, ¤t_different_settings, filament_count](const std::string &system_name, Preset::Type type) { + if (system_name.empty()) + return; + // Preset bookkeeping the CLI keeps in its own groups, e.g. inherits_group and print_compatible_printers. + static const std::set bookkeeping_keys = {"inherits", "compatible_printers", "compatible_prints", "compatible_printers_condition", + "compatible_prints_condition", "print_settings_id", "printer_settings_id"}; + const size_t index = type == Preset::TYPE_PRINTER ? filament_count + 1 : 0; + PresetBundle &resolver = ensure_system_preset_resolver(); + DynamicPrintConfig system_config; + t_config_option_keys keys; + const DynamicPrintConfig config = Preset::load_external_config(type, + type == Preset::TYPE_PRINTER ? resolver.printers.default_preset_for(m_print_config).config : resolver.prints.default_preset().config, + m_print_config, PresetBundle::project_different_keys(index < current_different_settings.size() ? current_different_settings[index] : std::string()), + [&](const std::string &) -> DynamicPrintConfig * { + std::string error; + if (resolver.resolve_system_preset(system_config, type, system_name, config_substitution_rule, error)) + return &system_config; + BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: system preset '%1%' not resolved (%2%); the project keeps its values") % system_name % error; + return nullptr; + }, &keys); + for (const std::string &key : keys) { + const ConfigOption *opt = config.option(key); + const ConfigOption *old = m_print_config.option(key); + if (bookkeeping_keys.count(key) != 0 || opt == nullptr || (old != nullptr && *old == *opt)) + continue; + BOOST_LOG_TRIVIAL(info) << boost::format("CLI: %1% from '%2%': %3% -> %4%") % key % system_name % (old ? old->serialize() : std::string("(missing)")) % opt->serialize(); + m_print_config.set_key_value(key, opt->clone()); + } + }; + // The --uptodate path refreshes the project from its own system configs. + if (new_printer_name.empty() && load_machine_config.empty()) + load_project_preset(current_printer_system_name, Preset::TYPE_PRINTER); + if (new_process_name.empty() && load_process_config.empty()) + load_project_preset(current_process_system_name, Preset::TYPE_PRINT); + std::vector& different_settings = m_print_config.option("different_settings_to_system", true)->values; std::vector& inherits_group = m_print_config.option("inherits_group", true)->values; inherits_group.resize(filament_count + 2, std::string()); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e8437f7c0b..cd02465fb0 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -976,6 +976,37 @@ void Preset::get_extruder_names_and_keysets(Type type, std::string& extruder_id_ } } +DynamicPrintConfig Preset::load_external_config(Type type, const DynamicPrintConfig &default_config, const DynamicPrintConfig &project_config, + const std::set &different_settings_list, + const std::function &find_base, + t_config_option_keys *keys) +{ + // Load the preset over a default preset, so that the missing fields are filled in from the default preset. + DynamicPrintConfig cfg(default_config); + // SoftFever: ignore print connection info from project + auto cfg_keys = cfg.keys(); + cfg_keys.erase(std::remove_if(cfg_keys.begin(), cfg_keys.end(), + [](std::string &val) { + return val == "print_host" || val == "print_host_webui" || val == "printhost_apikey" || + val == "printhost_cafile" || val == "printhost_user" || val == "printhost_password" || val == "printhost_port"; + }), + cfg_keys.end()); + cfg.apply_only(project_config, cfg_keys, true); + + //add different settings check logic, replace the old system preset's default value with new system preset's default values + if (!different_settings_list.empty()) { + if (DynamicPrintConfig *base_config = find_base(Preset::inherits(cfg))) { + std::string extruder_id_name, extruder_variant_name; + std::set *key_set1 = nullptr, *key_set2 = nullptr; + Preset::get_extruder_names_and_keysets(type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); + cfg.update_non_diff_values_to_base_config(*base_config, cfg_keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); + } + } + if (keys) + *keys = std::move(cfg_keys); + return cfg; +} + bool Preset::has_lidar(PresetBundle *preset_bundle) { bool has_lidar = false; @@ -2640,20 +2671,6 @@ std::pair PresetCollection::load_external_preset( const Semver file_version, const std::string filament_id) { - // Load the preset over a default preset, so that the missing fields are filled in from the default preset. - DynamicPrintConfig cfg(this->default_preset_for(combined_config).config); - // SoftFever: ignore print connection info from project - auto keys = cfg.keys(); - keys.erase(std::remove_if(keys.begin(), keys.end(), - [](std::string &val) { - return val == "print_host" || val == "print_host_webui" || val == "printhost_apikey" || - val == "printhost_cafile" || val == "printhost_user" || val == "printhost_password" || val == "printhost_port"; - }), - keys.end()); - cfg.apply_only(combined_config, keys, true); - std::string &inherits = Preset::inherits(cfg); - - //add different settings check logic, replace the old system preset's default value with new system preset's default values std::deque::iterator it = this->find_preset_internal(original_name); bool found = it != m_presets.end() && it->name == original_name; if (! found) { @@ -2662,24 +2679,25 @@ std::pair PresetCollection::load_external_preset( found = it != m_presets.end(); } - std::string extruder_id_name, extruder_variant_name; - std::set *key_set1 = nullptr, *key_set2 = nullptr; - Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2); - - if (!inherits.empty() && (different_settings_list.size() > 0)) { - auto iter = this->find_preset_internal(inherits); - if (iter == m_presets.end() || iter->name != inherits) - iter = this->find_preset_renamed(inherits); - if (iter != m_presets.end()) { - //std::vector dirty_options = cfg.diff(iter->config); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path; - cfg.update_non_diff_values_to_base_config(iter->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); - } - } - else if (found && it->is_system && (different_settings_list.size() > 0)) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% 's value to %2% 's values")%original_name %path; - cfg.update_non_diff_values_to_base_config(it->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2); - } + t_config_option_keys keys; + DynamicPrintConfig cfg = Preset::load_external_config(m_type, this->default_preset_for(combined_config).config, combined_config, different_settings_list, + [this, &original_name, &path, found, it](const std::string &inherits) -> DynamicPrintConfig * { + if (!inherits.empty()) { + auto iter = this->find_preset_internal(inherits); + if (iter == m_presets.end() || iter->name != inherits) + iter = this->find_preset_renamed(inherits); + if (iter == m_presets.end()) + return nullptr; + BOOST_LOG_TRIVIAL(info) << "load_external_preset" << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path; + return &iter->config; + } + if (found && it->is_system) { + BOOST_LOG_TRIVIAL(info) << "load_external_preset" << boost::format(": change preset %1% 's value to %2% 's values")%original_name %path; + return &it->config; + } + return nullptr; + }, &keys); + std::string &inherits = Preset::inherits(cfg); //BBS: add config related logs BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, type %1% , path %2%, name %3%, original_name %4%, inherits %5%")%Preset::get_type_string(m_type) %path %name %original_name %inherits; diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 329e835afe..418ba19fc6 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -403,6 +403,15 @@ public: std::string get_current_printer_type(PresetBundle *preset_bundle); // get current preset type static void get_extruder_names_and_keysets(Type type, std::string& extruder_id_name, std::string& extruder_variant_name, std::set** p_key_set1, std::set** p_key_set2); + // Config of a preset loaded from a project or config file: the project's values over the type's + // default preset config, without the print-host keys. When different_settings_list is not empty, + // every key not listed in it is then refreshed from the base system preset, which find_base returns + // for the project's "inherits" (nullptr when there is none), with the listed per-variant values + // mapped onto the base's extruder variants. keys, if given, receives the keys taken from the project. + static DynamicPrintConfig load_external_config(Type type, const DynamicPrintConfig &default_config, const DynamicPrintConfig &project_config, + const std::set &different_settings_list, + const std::function &find_base, + t_config_option_keys *keys = nullptr); std::string get_printer_id() const { return vendor ? vendor->id : ""; } bool has_lidar(PresetBundle *preset_bundle); diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index c5b78a8e0f..63e3ba6105 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -581,18 +581,20 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::path &root_dir, const std::string &vendor_id, ForwardCompatibilitySubstitutionRule compatibility_rule, - std::string &error) + std::string &error, bool allow_cache) { - auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule); + auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule, allow_cache); if (auto it = m_source_vendor_bundles.find(key); it != m_source_vendor_bundles.end()) return it->second.get(); // The library loads with no base of its own, so the tree a vendor inherits from // is the same one that resolves the library's own presets. - const PresetBundle *library = nullptr; + const std::string library_file = std::string(ORCA_FILAMENT_LIBRARY); + const PresetBundle *library = nullptr; if (vendor_id != ORCA_FILAMENT_LIBRARY && - boost::filesystem::is_regular_file(root_dir / (std::string(ORCA_FILAMENT_LIBRARY) + ".json"))) { - library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error); + (boost::filesystem::is_regular_file(root_dir / (library_file + ".json")) || + (allow_cache && boost::filesystem::is_regular_file(root_dir / (library_file + ".opc"))))) { + library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error, allow_cache); if (library == nullptr) { error = "OrcaFilamentLibrary contains invalid presets"; return nullptr; @@ -601,7 +603,7 @@ const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::pa auto bundle = std::make_unique(); bundle->m_preserve_vendor_source_paths = true; - bundle->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, compatibility_rule, library, false); + bundle->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, compatibility_rule, library, allow_cache); if (bundle->error_count() != 0) { error = "Vendor bundle contains invalid presets"; return nullptr; @@ -644,6 +646,43 @@ bool PresetBundle::resolve_preset_config_type(DynamicPrintConfig &config, Preset return true; } +bool PresetBundle::resolve_system_preset(DynamicPrintConfig &config, Preset::Type type, const std::string &name, + ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error) +{ + const std::string vendor_id = find_preset_vendor(name, type); + if (vendor_id.empty()) { + error = "No vendor lists the preset"; + return false; + } + // Release builds ship a vendor as its preset cache alone, without the profile JSONs. + auto installed = [&vendor_id](const fs::path &root) { + return fs::is_regular_file(root / (vendor_id + ".json")) || fs::is_regular_file(root / (vendor_id + ".opc")); + }; + fs::path root_dir = fs::path(data_dir()) / PRESET_SYSTEM_DIR; + if (!installed(root_dir)) + root_dir = fs::path(resources_dir()) / PRESET_PROFILES_DIR; + const bool cache_only = !fs::is_regular_file(root_dir / (vendor_id + ".json")); + + try { + const PresetBundle *vendor = load_source_vendor(root_dir, vendor_id, compatibility_rule, error, cache_only); + if (vendor == nullptr) + return false; + const PresetCollection &collection = type == Preset::TYPE_PRINTER ? vendor->printers : + type == Preset::TYPE_PRINT ? vendor->prints : vendor->filaments; + const Preset *preset = collection.find_preset(name, false); + if (preset == nullptr) { + error = "Preset was not found in its vendor bundle"; + return false; + } + config = preset->config; + } catch (const std::exception &ex) { + error = ex.what(); + return false; + } + error.clear(); + return true; +} + PresetBundle::PresetBundle(const PresetBundle &rhs) { *this = rhs; @@ -4665,6 +4704,15 @@ const std::set ignore_settings_list ={ "print_settings_id", "filament_settings_id", "printer_settings_id" }; +std::set PresetBundle::project_different_keys(const std::string &different_settings) +{ + std::vector keys; + Slic3r::unescape_strings_cstyle(different_settings, keys); + std::set keys_set(keys.begin(), keys.end()); + keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end()); + return keys_set; +} + DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optional> filament_maps_new, std::optional> filament_volume_maps_new) const { DynamicPrintConfig out; @@ -5372,15 +5420,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool if (!is_published) { //BBS: add different settings logic BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": load print preset from print_settings_id"); - std::vector print_different_keys_vector; - std::string print_different_settings = different_values[0]; - Slic3r::unescape_strings_cstyle(print_different_settings, print_different_keys_vector); - std::set print_different_keys_set(print_different_keys_vector.begin(), print_different_keys_vector.end()); - //if (!has_different_settings_to_system) { - // print_different_keys_set.clear(); - //} - //else - print_different_keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end()); + std::set print_different_keys_set = project_different_keys(different_values[0]); if (!print_compatible_printers.empty()) { ConfigOptionStrings* compatible_printers = config.option("compatible_printers", true); compatible_printers->values = print_compatible_printers; @@ -5391,15 +5431,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool //clear compatible printers clear_compatible_printers(config); - std::vector printer_different_keys_vector; - std::string printer_different_settings = different_values[num_filaments + 1]; - Slic3r::unescape_strings_cstyle(printer_different_settings, printer_different_keys_vector); - std::set printer_different_keys_set(printer_different_keys_vector.begin(), printer_different_keys_vector.end()); - //if (!has_different_settings_to_system) { - // printer_different_keys_set.clear(); - //} - //else - printer_different_keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end()); + std::set printer_different_keys_set = project_different_keys(different_values[num_filaments + 1]); //BBS: add config related logs BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": load printer preset from printer_settings_id"); load_preset(this->printers, num_filaments + 1, "printer_settings_id", printer_different_keys_set, std::string()); @@ -5418,15 +5450,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool Preset *loaded = nullptr; //BBS: add different settings logic - std::vector filament_different_keys_vector; - std::string filament_different_settings = different_values[1]; - Slic3r::unescape_strings_cstyle(filament_different_settings, filament_different_keys_vector); - std::set filament_different_keys_set(filament_different_keys_vector.begin(), filament_different_keys_vector.end()); - //if (!has_different_settings_to_system) { - // filament_different_keys_set.clear(); - //} - //else - filament_different_keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end()); + std::set filament_different_keys_set = project_different_keys(different_values[1]); std::string filament_id = filament_ids[0]; //BBS: add config related logs @@ -5489,15 +5513,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool cfg.opt_string("inherits", true) = inherits_values[i + 1]; //BBS: add different settings logic - std::vector filament_different_keys_vector; - std::string filament_different_settings = different_values[i+1]; - Slic3r::unescape_strings_cstyle(filament_different_settings, filament_different_keys_vector); - std::set filament_different_keys_set(filament_different_keys_vector.begin(), filament_different_keys_vector.end()); - //if (!has_different_settings_to_system) { - // filament_different_keys_set.clear(); - //} - //else - filament_different_keys_set.insert(ignore_settings_list.begin(), ignore_settings_list.end()); + std::set filament_different_keys_set = project_different_keys(different_values[i+1]); std::string filament_id = filament_ids[i]; diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 0bf1bbd5a5..0db1f6d9b1 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -235,6 +235,9 @@ public: // ORCA: utility function to find the vendor for a given preset name static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type); + // Keys a project keeps when its presets are loaded: those listed in its escaped + // "different_settings_to_system" entry for the preset, plus the preset bookkeeping keys. + static std::set project_different_keys(const std::string &different_settings); PresetBundle(); PresetBundle(const PresetBundle &rhs); @@ -274,6 +277,10 @@ public: const std::string &source_file, ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error, bool allow_source_manifest = true); + // Resolve a system preset by name. The vendor tree is read from data_dir()/system when installed + // there, as the GUI reads it, and from the bundled profiles otherwise. + bool resolve_system_preset(DynamicPrintConfig &config, Preset::Type type, const std::string &name, + ForwardCompatibilitySubstitutionRule compatibility_rule, std::string &error); // Load selections (current print, current filaments, current printer) from config.ini // This is done just once on application start up. @@ -808,13 +815,14 @@ private: // Vendor trees loaded by resolve_preset_config's manifest path, so every preset // resolved through this bundle shares one load per source root and vendor. The // filament library is one such tree, shared by every vendor under its root. - std::map, std::unique_ptr> + // A tree read from its preset cache is kept apart: its presets carry no source file. + std::map, std::unique_ptr> m_source_vendor_bundles; const PresetBundle *load_source_vendor(const boost::filesystem::path &root_dir, const std::string &vendor_id, ForwardCompatibilitySubstitutionRule compatibility_rule, - std::string &error); + std::string &error, bool allow_cache = false); // Orca: validation only - flag any printer with two or more compatible // filament presets sharing one filament_id (ambiguous AMS subtype match). diff --git a/tests/cli/CMakeLists.txt b/tests/cli/CMakeLists.txt index 6707159fb2..a0c9f2c078 100644 --- a/tests/cli/CMakeLists.txt +++ b/tests/cli/CMakeLists.txt @@ -15,3 +15,11 @@ set_tests_properties(cli_strict_mode PROPERTIES LABELS "CLI;RequiresApp" SKIP_RETURN_CODE 77 TIMEOUT 900) + +add_test(NAME cli_project_missing_keys + COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_project_missing_keys.sh $ ${ORCA_CLI_TEST_PYTHON} + ${CMAKE_SOURCE_DIR}/resources/profiles/BBL) +set_tests_properties(cli_project_missing_keys PROPERTIES + LABELS "CLI;RequiresApp" + SKIP_RETURN_CODE 77 + TIMEOUT 900) diff --git a/tests/cli/test_cli_project_missing_keys.sh b/tests/cli/test_cli_project_missing_keys.sh new file mode 100644 index 0000000000..4c55709ea0 --- /dev/null +++ b/tests/cli/test_cli_project_missing_keys.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# End-to-end check that the CLI loads a project's printer and process settings as the GUI does. +# +# The GUI takes every key a project does not list as changed from the project's current system preset: +# keys saved before an option existed, and keys holding an older system value. Keys the project lists +# in different_settings_to_system keep the project's value. A project is exported from the shipped +# Bambu Lab P1S presets; one printer key and one process key are removed, one printer key and one +# process key are changed without being listed, one key is changed and listed, and it is sliced again. +# +# usage: test_cli_project_missing_keys.sh +set -u + +BIN="${1:-}" +PY="${2:-python3}" +PROFILES="${3:-}" +# 77 is the test's SKIP_RETURN_CODE. +[ -x "$BIN" ] || { echo "SKIP: orca-slicer binary not found: $BIN"; exit 77; } +[ -d "$PROFILES" ] || { echo "FAIL: profiles directory not found: $PROFILES"; exit 1; } + +WORK="$(mktemp -d "${TMPDIR:-/tmp}/orca-cli-missing-keys.XXXXXX")" +trap 'rm -rf "$WORK"' EXIT + +"$PY" - "$WORK/cube.stl" <<'EOF' +import sys + +v = [(x, y, z) for z in (0, 10) for y in (0, 10) for x in (0, 10)] +with open(sys.argv[1], "w") as f: + f.write("solid cube\n") + for a, b, c, d in ((0, 2, 3, 1), (4, 5, 7, 6), (0, 1, 5, 4), (2, 6, 7, 3), (0, 4, 6, 2), (1, 3, 7, 5)): + for tri in ((v[a], v[b], v[c]), (v[a], v[c], v[d])): + f.write("facet normal 0 0 0\nouter loop\n") + for p in tri: + f.write("vertex %g %g %g\n" % p) + f.write("endloop\nendfacet\n") + f.write("endsolid cube\n") +EOF + +# slice [option...]: slice into $WORK//out.3mf with a fresh data directory. +slice() { + local out="$WORK/$1" input="$2"; shift 2 + mkdir -p "$out" + timeout 300 "$BIN" --datadir "$out/datadir" "$@" --slice 0 --outputdir "$out" --export-3mf out.3mf "$input" \ + > "$out/log" 2>&1 || { echo "FAIL: $1: orca-slicer exited $?"; tail -n 40 "$out/log"; exit 1; } +} + +slice base "$WORK/cube.stl" \ + --load-settings "$PROFILES/machine/Bambu Lab P1S 0.4 nozzle.json;$PROFILES/process/0.20mm Standard @BBL X1C.json" \ + --load-filaments "$PROFILES/filament/Bambu PLA Basic @BBL P1S 0.4 nozzle.json" + +# The removed keys, with their option defaults from PrintConfig.cpp; stale keys changed without being +# listed as different, which must come back with the system value; and a listed key the project keeps. +"$PY" - "$WORK/base/out.3mf" "$WORK/old.3mf" <<'EOF' || exit $? +import json, sys, zipfile + +src, dst = sys.argv[1], sys.argv[2] +missing = {"extruder_clearance_dist_to_rod": "40", "sparse_infill_density": "20%"} +with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout: + for item in zin.infolist(): + data = zin.read(item.filename) + if item.filename == "Metadata/project_settings.config": + config = json.loads(data) + for key, default in missing.items(): + if config[key] == default: + print("SKIP: %s is %s in the system preset, the option default, so the test cannot tell them apart" % (key, default)) + sys.exit(77) + expected = {key: config.pop(key) for key in missing} + for key in ("top_shell_layers", "extruder_clearance_height_to_rod"): + expected[key] = config[key] + config[key] = str(int(float(config[key])) + 1) + expected["wall_loops"] = str(int(config["wall_loops"]) + 1) + config["wall_loops"] = expected["wall_loops"] + different = config["different_settings_to_system"] + different[0] = ";".join([k for k in different[0].split(";") if k] + ["wall_loops"]) + data = json.dumps(config, indent=4) + zout.writestr(item, data) +with open(dst + ".expected.json", "w") as f: + json.dump(expected, f) +EOF + +slice project "$WORK/old.3mf" + +"$PY" - "$WORK/project/out.3mf" "$WORK/old.3mf.expected.json" <<'EOF' +import json, sys, zipfile + +with zipfile.ZipFile(sys.argv[1]) as z: + config = json.loads(z.read("Metadata/project_settings.config")) +with open(sys.argv[2]) as f: + expected = json.load(f) +errors = ["%s is %r, want %r" % (key, config.get(key), want) for key, want in expected.items() if config.get(key) != want] +for e in errors: + print("FAIL: " + e) +sys.exit(1 if errors else 0) +EOF +status=$? +[ "$status" -eq 0 ] || { tail -n 40 "$WORK/project/log"; exit 1; } +echo "PASS" diff --git a/tests/libslic3r/test_preset_bundle_loading.cpp b/tests/libslic3r/test_preset_bundle_loading.cpp index 49f0f90214..b1ec3903bb 100644 --- a/tests/libslic3r/test_preset_bundle_loading.cpp +++ b/tests/libslic3r/test_preset_bundle_loading.cpp @@ -5571,6 +5571,35 @@ struct ScopedDataDir ~ScopedDataDir() { set_data_dir(previous); } }; +// resources_dir() is process-wide too; system preset lookups scan its profiles directory. +struct ScopedResourcesDir +{ + std::string previous = resources_dir(); + explicit ScopedResourcesDir(const fs::path &dir) { set_resources_dir(dir.string()); } + ~ScopedResourcesDir() { set_resources_dir(previous); } +}; + +// An "Acme" vendor under root whose "Acme Printer" inherits extruder_clearance_dist_to_rod from an +// abstract base, with the printer in a nested sub_path so the name cannot be derived from the file. +void write_acme_printer_vendor(const fs::path &root, double dist_to_rod) +{ + const fs::path machine_dir = root / "Acme" / "machine"; + fs::create_directories(machine_dir / "nested"); + std::ofstream((root / "Acme.json").string()) + << R"({"version":"1.0.0","name":"Acme",)" + << R"("machine_model_list":[{"name":"Acme One","sub_path":"machine/model.json"}],"machine_list":[)" + << R"({"name":"fdm_acme_common","sub_path":"machine/base.json"},)" + << R"({"name":"Acme Printer","sub_path":"machine/nested/printer.json"}]})"; + std::ofstream((machine_dir / "model.json").string()) + << R"({"type":"machine_model","name":"Acme One","nozzle_diameter":"0.4"})"; + std::ofstream((machine_dir / "base.json").string()) + << R"({"type":"machine","name":"fdm_acme_common","from":"system","instantiation":"false",)" + << R"("extruder_clearance_dist_to_rod":")" << dist_to_rod << R"("})"; + std::ofstream((machine_dir / "nested" / "printer.json").string()) + << R"({"type":"machine","name":"Acme Printer","from":"system","instantiation":"true","inherits":"fdm_acme_common",)" + << R"("printer_model":"Acme One","printer_variant":"0.4"})"; +} + std::string read_file(const fs::path &file) { std::ifstream in(file.string(), std::ios::binary); @@ -5684,3 +5713,230 @@ TEST_CASE("A project saved with pressure advance per filament applies it to ever check_double_vector(pla.opt("filament_flow_ratio")->values, { 0.95, 0.96 }); check_double_vector(petg.opt("filament_flow_ratio")->values, { 0.97 }); } + +TEST_CASE("A system preset resolves by name from the bundled profiles", "[Preset][Bundle]") +{ + ScopedTemporaryDir temp_dir; + ScopedDataDir data(temp_dir.path() / "data"); + ScopedResourcesDir resources(temp_dir.path() / "resources"); + write_acme_printer_vendor(temp_dir.path() / "resources" / "profiles", 33.); + + PresetBundle bundle; + DynamicPrintConfig config; + std::string error; + REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Acme Printer", + ForwardCompatibilitySubstitutionRule::EnableSilent, error)); + CHECK(error.empty()); + CHECK_THAT(config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(33., 1e-6)); +} + +TEST_CASE("A system preset resolves from the data directory copy of its vendor", "[Preset][Bundle]") +{ + ScopedTemporaryDir temp_dir; + ScopedDataDir data(temp_dir.path() / "data"); + ScopedResourcesDir resources(temp_dir.path() / "resources"); + write_acme_printer_vendor(temp_dir.path() / "resources" / "profiles", 33.); + write_acme_printer_vendor(temp_dir.path() / "data" / PRESET_SYSTEM_DIR, 35.); + + PresetBundle bundle; + DynamicPrintConfig config; + std::string error; + REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Acme Printer", + ForwardCompatibilitySubstitutionRule::EnableSilent, error)); + CHECK_THAT(config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(35., 1e-6)); +} + +TEST_CASE("A system preset resolves from a vendor shipped as its preset cache alone", "[Preset][Bundle]") +{ + ScopedTemporaryDir temp_dir; + ScopedDataDir data(temp_dir.path() / "data"); + ScopedResourcesDir resources(temp_dir.path() / "resources"); + const fs::path profiles = temp_dir.path() / "resources" / "profiles"; + write_acme_printer_vendor(profiles, 33.); + + PresetBundle writer; + writer.set_generate_vendor_caches(true); + writer.load_vendor_configs_from_json(profiles.string(), "Acme", PresetBundle::LoadSystem, + ForwardCompatibilitySubstitutionRule::EnableSilent); + REQUIRE(fs::exists(profiles / "Acme.opc")); + // Release builds ship the cache and drop the profile JSONs, manifest included. + fs::remove(profiles / "Acme.json"); + fs::remove_all(profiles / "Acme"); + + PresetBundle bundle; + DynamicPrintConfig config; + std::string error; + REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Acme Printer", + ForwardCompatibilitySubstitutionRule::EnableSilent, error)); + CHECK_THAT(config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(33., 1e-6)); +} + +TEST_CASE("A system preset no vendor lists is not resolved", "[Preset][Bundle]") +{ + ScopedTemporaryDir temp_dir; + ScopedDataDir data(temp_dir.path() / "data"); + ScopedResourcesDir resources(temp_dir.path() / "resources"); + write_acme_printer_vendor(temp_dir.path() / "resources" / "profiles", 33.); + + PresetBundle bundle; + DynamicPrintConfig config; + std::string error; + CHECK_FALSE(bundle.resolve_system_preset(config, Preset::TYPE_PRINTER, "Unknown Printer", + ForwardCompatibilitySubstitutionRule::EnableSilent, error)); + CHECK_FALSE(error.empty()); +} + +namespace { + +// A default preset config for type, built the way PresetBundle builds its default presets. +DynamicPrintConfig external_default_config(Preset::Type type) +{ + DynamicPrintConfig config; + config.apply_only(static_cast(FullPrintConfig::defaults()), + type == Preset::TYPE_PRINTER ? Preset::printer_options() : Preset::print_options()); + Preset::inherits(config); + return config; +} + +// A find_base callback that counts its calls and returns base. +auto base_finder(DynamicPrintConfig *base, int &calls) +{ + return [base, &calls](const std::string &) { + ++calls; + return base; + }; +} + +} // namespace + +TEST_CASE("A key missing from a project takes the default preset value when there is no base", "[Preset][ExternalPreset]") +{ + DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINT); + defaults.set("wall_loops", 7, true); + const DynamicPrintConfig project; + int calls = 0; + + const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINT, defaults, project, {"sparse_infill_density"}, + base_finder(nullptr, calls)); + CHECK(calls == 1); + CHECK(config.opt_int("wall_loops") == 7); +} + +TEST_CASE("A key missing from a project takes its base preset value", "[Preset][ExternalPreset]") +{ + DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINT); + defaults.set("wall_loops", 7, true); + DynamicPrintConfig base = defaults; + base.set("wall_loops", 5, true); + const DynamicPrintConfig project; + int calls = 0; + + const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINT, defaults, project, {"sparse_infill_density"}, + base_finder(&base, calls)); + CHECK(config.opt_int("wall_loops") == 5); +} + +TEST_CASE("A project key listed as different keeps the project value", "[Preset][ExternalPreset]") +{ + const DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINT); + DynamicPrintConfig base = defaults; + base.set("wall_loops", 5, true); + DynamicPrintConfig project; + project.set("wall_loops", 4, true); + int calls = 0; + + const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINT, defaults, project, {"wall_loops"}, + base_finder(&base, calls)); + CHECK(config.opt_int("wall_loops") == 4); +} + +TEST_CASE("A project key not listed as different is refreshed to the base value", "[Preset][ExternalPreset]") +{ + const DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINT); + DynamicPrintConfig base = defaults; + base.set("wall_loops", 5, true); + DynamicPrintConfig project; + project.set("wall_loops", 4, true); + project.set("inherits", "Base Process", true); + std::string inherits; + + const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINT, defaults, project, {"sparse_infill_density"}, + [&base, &inherits](const std::string &name) { + inherits = name; + return &base; + }); + CHECK(inherits == "Base Process"); + CHECK(config.opt_int("wall_loops") == 5); +} + +TEST_CASE("An empty different settings list keeps the project values without a base", "[Preset][ExternalPreset]") +{ + DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINT); + defaults.set("top_shell_layers", 7, true); + DynamicPrintConfig base = defaults; + base.set("wall_loops", 5, true); + base.set("top_shell_layers", 9, true); + DynamicPrintConfig project; + project.set("wall_loops", 4, true); + int calls = 0; + + const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINT, defaults, project, {}, base_finder(&base, calls)); + CHECK(calls == 0); + CHECK(config.opt_int("wall_loops") == 4); + CHECK(config.opt_int("top_shell_layers") == 7); +} + +TEST_CASE("Print-host keys are never taken from a project", "[Preset][ExternalPreset]") +{ + DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINTER); + defaults.set("print_host", "", true); + defaults.set("printhost_apikey", "", true); + DynamicPrintConfig project; + project.set("print_host", "http://project-host", true); + project.set("printhost_apikey", "project-key", true); + project.set("printer_notes", "project notes", true); + t_config_option_keys keys; + int calls = 0; + + const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINTER, defaults, project, {"printer_notes"}, + base_finder(nullptr, calls), &keys); + CHECK(config.opt_string("print_host").empty()); + CHECK(config.opt_string("printhost_apikey").empty()); + CHECK(config.opt_string("printer_notes") == "project notes"); + CHECK_FALSE(contains_key(keys, "print_host")); + CHECK_FALSE(contains_key(keys, "printhost_apikey")); + CHECK(contains_key(keys, "printer_notes")); +} + +TEST_CASE("A per-variant project value maps onto its base preset's variant layout", "[Preset][ExternalPreset]") +{ + const DynamicPrintConfig defaults = external_default_config(Preset::TYPE_PRINT); + DynamicPrintConfig base = defaults; + base.set_key_value("print_extruder_id", new ConfigOptionInts({1, 1})); + base.set_key_value("print_extruder_variant", new ConfigOptionStrings({"Direct Drive Standard", "Direct Drive High Flow"})); + base.set_key_value("outer_wall_speed", new ConfigOptionFloats({200., 300.})); + base.set_key_value("inner_wall_speed", new ConfigOptionFloats({250., 350.})); + // A project saved before its printer had a High Flow variant. + DynamicPrintConfig project; + project.set_key_value("print_extruder_id", new ConfigOptionInts({1})); + project.set_key_value("print_extruder_variant", new ConfigOptionStrings({"Direct Drive Standard"})); + project.set_key_value("outer_wall_speed", new ConfigOptionFloats({100.})); + project.set_key_value("inner_wall_speed", new ConfigOptionFloats({150.})); + int calls = 0; + + const DynamicPrintConfig config = Preset::load_external_config(Preset::TYPE_PRINT, defaults, project, {"outer_wall_speed"}, + base_finder(&base, calls)); + CHECK(config.option("print_extruder_variant")->values == + std::vector{"Direct Drive Standard", "Direct Drive High Flow"}); + // The listed key keeps the project's Standard value and takes High Flow from the base. + check_double_vector(config.option("outer_wall_speed")->values, {100., 300.}); + check_double_vector(config.option("inner_wall_speed")->values, {250., 350.}); +} + +TEST_CASE("A project's different settings always keep the preset bookkeeping keys", "[Preset][ExternalPreset]") +{ + const std::set keys = PresetBundle::project_different_keys(escape_strings_cstyle({"wall_loops", "top_shell_layers"})); + for (const char *key : {"wall_loops", "top_shell_layers", "inherits", "print_settings_id", "filament_settings_id", "printer_settings_id"}) + CHECK(keys.count(key) == 1); + CHECK(PresetBundle::project_different_keys(std::string()).count("inherits") == 1); +} From ff2f42016a5675ef723f8fb0fd01ab04dac8d9cc Mon Sep 17 00:00:00 2001 From: Kris Austin Date: Wed, 30 Sep 2026 08:04:14 -0500 Subject: [PATCH 2/6] Fix CLI crash on a 3mf without project settings (#16004) When a 3mf is tagged as written by OrcaSlicer or BambuStudio, the CLI treats it as a project and reads the printer settings stored in the file. #14580 guarded four of those reads, but printable_height still called opt_float() without a check, so a 3mf whose project_settings.config is empty or missing crashed with a null dereference. The handy models OrcaBadge.3mf and OrcaSliced.3mf are both like this. Skip the read when the option is absent, like the reads around it. The bed-size logic further down already treats an old printable height of 0 as unknown and uses the current printer's. --- src/OrcaSlicer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 3ed76b8976..87352f505e 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -1913,7 +1913,8 @@ int CLI::run(int argc, char **argv) old_printable_width = static_cast(old_printable_bbox.size().x()); old_printable_depth = static_cast(old_printable_bbox.size().y()); } - old_printable_height = (int)(config.opt_float("printable_height")); + if (config.option("printable_height")) + old_printable_height = (int)(config.opt_float("printable_height")); if (config.option("extruder_clearance_height_to_rod")) old_height_to_rod = config.opt_float("extruder_clearance_height_to_rod"); From 97700c5ab505f679634fb927c55fdfa7ac724923 Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Wed, 30 Sep 2026 10:21:24 -0300 Subject: [PATCH 3/6] Gyroid Optimization (#16002) Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> --- src/libslic3r/Fill/FillGyroid.cpp | 111 +++++++++++++++--------------- tests/fff_print/test_fill.cpp | 56 +++++++++++++++ 2 files changed, 110 insertions(+), 57 deletions(-) diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp index c740dd2b4b..c9a108573d 100644 --- a/src/libslic3r/Fill/FillGyroid.cpp +++ b/src/libslic3r/Fill/FillGyroid.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "FillBase.hpp" #include "FillGyroid.hpp" @@ -123,44 +124,41 @@ static inline double f(double x, double z_sin, double z_cos, bool vertical, bool } } +// Repeats one period of a wave from the last sample at or before x_min to the first one at or after x_max. static inline Polyline make_wave( - const std::vector& one_period, double width, double height, double offset, double scaleFactor, - double z_cos, double z_sin, bool vertical, bool flip) + const std::vector& one_period, double x_min, double x_max, double offset, double scaleFactor, bool vertical) { - std::vector points = one_period; - double period = points.back()(0); - if (width != period) // do not extend if already truncated - { - points.reserve(one_period.size() * size_t(floor(width / period))); - points.pop_back(); + const double period = one_period.back().x(); + // The last sample of a period is the first one of the next. + const size_t n = one_period.size() - 1; + double x0 = std::floor(x_min / period) * period; + size_t i = 0; + while (i + 1 < n && x0 + one_period[i + 1].x() <= x_min) + ++i; - size_t n = points.size(); - do { - points.emplace_back(points[points.size()-n].x() + period, points[points.size()-n].y()); - } while (points.back()(0) < width - EPSILON); - - points.emplace_back(Vec2d(width, f(width, z_sin, z_cos, vertical, flip))); - } - - // and construct the final polyline to return: Polyline polyline; - polyline.points.reserve(points.size()); - for (auto& point : points) { - point(1) += offset; - point(1) = std::clamp(double(point.y()), 0., height); + polyline.points.reserve(size_t((x_max - x0) / period + 1.) * n + 1); + for (;;) { + Vec2d point(x0 + one_period[i].x(), one_period[i].y() + offset); + const bool last = point.x() >= x_max; if (vertical) std::swap(point(0), point(1)); polyline.points.emplace_back((point * scaleFactor).cast()); + if (last) + break; + if (++i == n) { + i = 0; + x0 += period; + } } - return polyline; } -static std::vector make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance) +static std::vector make_one_period(double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance) { std::vector points; double dx = M_PI_2; // exact coordinates on main inflexion lobes - double limit = std::min(2*M_PI, width); + double limit = 2*M_PI; points.reserve(coord_t(ceil(limit / tolerance / 3))); for (double x = 0.; x < limit - EPSILON; x += dx) { @@ -239,7 +237,8 @@ static inline double compute_omega_factor(double density_adjusted, double line_s return std::clamp(raw, 1.0, 2.0); } -static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height) +// Waves covering bbox, with the pattern anchored at origin. +static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, const BoundingBox &bbox, const Point &origin) { const double scaleFactor = scale_(line_spacing) / density_adjusted; @@ -254,29 +253,36 @@ static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double const double z_cos = cos(z); bool vertical = (std::abs(z_sin) <= std::abs(z_cos)); + // Range to cover in pattern units, with the waves running along x. + Vec2d lo = (bbox.min - origin).cast() / scaleFactor; + Vec2d hi = (bbox.max - origin).cast() / scaleFactor; double lower_bound = 0.; - double upper_bound = height; bool flip = true; if (vertical) { flip = false; lower_bound = -M_PI; - upper_bound = width - M_PI_2; - std::swap(width,height); + std::swap(lo(0), lo(1)); + std::swap(hi(0), hi(1)); } - std::vector one_period_odd = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time + std::vector one_period_odd = make_one_period(scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time flip = !flip; // even polylines are a bit shifted - std::vector one_period_even = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); - Polylines result; + std::vector one_period_even = make_one_period(scaleFactor, z_cos, z_sin, vertical, flip, tolerance); - for (double y0 = lower_bound; y0 < upper_bound + EPSILON; y0 += M_PI) { - // creates odd polylines - result.emplace_back(make_wave(one_period_odd, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip)); - // creates even polylines - y0 += M_PI; - if (y0 < upper_bound + EPSILON) { - result.emplace_back(make_wave(one_period_even, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip)); + // Every wave spans [offset + f_min, offset + f_max] across. + double f_min = std::numeric_limits::max(); + double f_max = std::numeric_limits::lowest(); + for (const std::vector *one_period : { &one_period_odd, &one_period_even }) + for (const Vec2d &point : *one_period) { + f_min = std::min(f_min, point.y()); + f_max = std::max(f_max, point.y()); } + + Polylines result; + for (int i = int(std::ceil((lo.y() - f_max - lower_bound) / M_PI)); lower_bound + i * M_PI + f_min <= hi.y(); ++i) { + Polyline &wave = result.emplace_back(make_wave(i % 2 == 0 ? one_period_odd : one_period_even, lo.x(), hi.x(), + lower_bound + i * M_PI, scaleFactor, vertical)); + wave.translate(origin); } return result; @@ -302,12 +308,12 @@ void FillGyroid::_fill_surface_single( // Distance between the gyroid waves in scaled coordinates. coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); - // align bounding box to a multiple of our grid module - bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance))); + // Anchor the pattern to our grid module; the 10-line shift keeps its established phase. + const coord_t shift = coord_t(10 * scale_(this->spacing)); + const Point origin = align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)) - Point(shift, shift); - // Expand the bounding box to avoid artifacts at the edges - coord_t expand = 10 * (scale_(this->spacing)); - bb.offset(expand); + // Keep the pattern ends and the multiline copies outside the contour. + bb.offset(scale_(this->spacing * params.multiline)); // generate pattern Polylines polylines; @@ -327,23 +333,14 @@ void FillGyroid::_fill_surface_single( const float density_factor = std::max(0.001f, float(params.density * DensityAdjust / params.multiline)); const float period = float(2.0 * M_PI) * float(this->spacing) / density_factor; - // bb is already expanded above by 10 * scale_(spacing) for edge artifacts; - // skip a second offset here to avoid raster-area bloat in the marching squares pass. + // A cell of margin for the rings closed along the raster border, and a fixed sampling grid for every region. + const coord_t cell = scaled(marchsq::GyroidField::gsizef); + bb.offset(cell); + bb.merge(align_to_grid(bb.min, Point(cell, cell))); marchsq::GyroidField sf(bb, this->z, period, float(omega)); polylines = marchsq::get_gyroid_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION); } else { - polylines = make_gyroid_waves( - scale_(this->z), - density_adjusted, - this->spacing, - ceil(bb.size()(0) / distance) + 1., - ceil(bb.size()(1) / distance) + 1.); - - // The parametric generator produces wave coords relative to the grid origin; - // shift them into absolute layer coords. The marching-squares branch above - // already emits absolute coords via GyroidField::to_Point, so it skips this. - for (Polyline &pl : polylines) - pl.translate(bb.min); + polylines = make_gyroid_waves(scale_(this->z), density_adjusted, this->spacing, bb, origin); } // Apply multiline offset if needed diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 789ab84457..7680206dc8 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -13,6 +13,7 @@ #include "libslic3r/AABBTreeLines.hpp" #include "libslic3r/Fill/Fill.hpp" #include "libslic3r/Fill/FillAdaptive.hpp" +#include "libslic3r/Fill/FillGyroid.hpp" #include "libslic3r/Flow.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/IntersectionPoints.hpp" @@ -1257,6 +1258,61 @@ TEST_CASE("Multiline infill of an object matches the infill of a larger object w } } +TEST_CASE("Gyroid infill of an object matches the infill of a larger object with the same center", "[Fill]") +{ + const bool optimized = GENERATE(false, true); + const int multiline = GENERATE(1, 2); + const float density = GENERATE(0.05f, 0.2f); + const double spacing = 0.45; + CAPTURE(optimized, multiline, density); + + auto circle = [](double radius) { + Polygon contour = make_circle_num_segments(scale_(radius), 120); + contour.translate(Point::new_scale(100., 60.)); + return ExPolygon(std::move(contour)); + }; + const ExPolygon object = circle(20.); + const ExPolygon larger = circle(30.); + auto fill = [optimized, multiline, density, spacing](const ExPolygon ®ion, double z) { + std::unique_ptr filler(Fill::new_from_type(ipGyroid)); + filler->spacing = spacing; + filler->angle = float(M_PI / 7.); + filler->z = z; + + FillParams params; + params.density = density; + params.multiline = multiline; + params.gyroid_optimized = optimized; + params.dont_adjust = true; + Surface surface(stInternal, region); + return filler->fill_surface(&surface, params); + }; + // Away from the boundary of the object, where both are clipped and connected the same way. + const Polygons inner = shrink(to_polygons(object), scale_(1.)); + auto farthest = [&inner](const Polylines &from, const Polylines &to) { + const AABBTreeLines::LinesDistancer tree(to_lines(to)); + double distance = 0.; + for (const Polyline &path : intersection_pl(from, inner)) + for (const Point &point : path.equally_spaced_points(scale_(0.2))) + distance = std::max(distance, tree.distance_from_lines(point)); + return unscale(distance); + }; + + // Marching squares simplifies rings that start elsewhere in each object. + const double tolerance = optimized ? SPARSE_INFILL_RESOLUTION + 0.01 : 0.01; + // Half a z period of the waves, through both switches between horizontal and vertical waves. + const double wave_distance = spacing * multiline / (density * FillGyroid::DensityAdjust); + for (int step = 0; step <= 8; ++step) { + const double z = wave_distance * M_PI * step / 8.; + CAPTURE(z); + const Polylines paths = fill(object, z); + REQUIRE_FALSE(paths.empty()); + const Polylines reference = fill(larger, z); + CHECK(farthest(reference, paths) < tolerance); + CHECK(farthest(paths, reference) < tolerance); + } +} + TEST_CASE("Multiline cubic infill follows the cubic lines without crossing itself", "[Fill]") { const int multiline = GENERATE(2, 3); From 8d69fa3e5a9d5424899b0f9c2aaceb4af61c2c38 Mon Sep 17 00:00:00 2001 From: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> Date: Wed, 30 Sep 2026 11:28:07 -0300 Subject: [PATCH 4/6] Add 'SECURITY' label to PR label bot (#16024) --- .github/workflows/pr-label-bot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pr-label-bot.yml b/.github/workflows/pr-label-bot.yml index fe74841831..199b4dd7e7 100644 --- a/.github/workflows/pr-label-bot.yml +++ b/.github/workflows/pr-label-bot.yml @@ -35,6 +35,7 @@ jobs: // kind of change 'crash', 'bug-fix', + 'SECURITY', 'enhancement', 'QoL', 'optimization', @@ -193,6 +194,7 @@ jobs: // kind of change 'crash', 'bug-fix', + 'SECURITY', 'enhancement', 'QoL', 'optimization', From a5413efc3784dd58a537409ac9c700ac519e4a6c Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Thu, 1 Oct 2026 00:30:44 +0800 Subject: [PATCH 5/6] refactor: printer agent infrastructure changes to work with other printer agents aside from bambu (#15710) * Add developer flag for printer agents * Parse user print info on the UI thread to prevent heap corruption (#119) get_user_print_info()'s HTTP fetch can run on a worker thread (e.g. BindJob), but parse_user_print_info() mutates userMachineList (insert/erase/delete MachineObject). on_machine_alive (SSDP) mutates the same maps on the UI thread without locking, so parsing off-thread races the map and frees MachineObjects out from under it -> heap corruption. Keep all device-list mutation on the UI thread: parse inline when already on the main thread, otherwise marshal via CallAfter so it stays serialized with on_machine_alive. * Prevent loss of user access code on LAN reselect Keep user access code intact to maintain access rights even if device slot is unpopulated, ensuring continuous connection and status message reception. * Harden send flow and separate upload failure recovery (#111) * fix(send): harden FT send path + IP pre-flight UX * Remove early returns * Working Moonraker and Qidi printer agent transport (#104) Folds the Qidi AMS box-mapping print overrides (apply_box_mapping + start_* wrappers) that the transport fix builds on. * Add support for runtime error status in plugins Distinguish a loaded plugin whose capability errored (RuntimeError, warn-styled, stays checked) from a load-time Error. Status now derives via resolve_plugin_status(); enum ordinal keeps dialog sort priority. Unloading clears stale errors. * Resolve duplicate agent ID conflicts Reject a printer-agent capability whose agent ID is already owned by another capability or built-in: flag the plugin error, disable the capability, and warn the user instead of silently ignoring it. * fix: checkbox should depend on plugin is_loaded status * Replace fake-enum printer agent dropdown (#121) A dedicated PrinterAgentChoice field reads rows straight from the live agent registry and stores the agent id string, replacing the fake-coEnum index mapping. The field moves to TabPrinter and registers with the searcher so UnsavedChanges renders it; the PhysicalPrinterDialog copy and its update hook are removed (#125). switch_printer_agent now resolves ids via resolve_printer_agent_id. * Reset device selection on agent swap or unload (#124) set_live_printer_agent centralizes the swap: deselect the machine, clear stale sidebar state and the previous agent's Other Devices, then install the new agent (or null when its provider vanished). Plugin load/unload callbacks refresh the dropdown and re-run agent selection. load_last_machine no longer falls back to the first available machine. * Gate agent mode behind use_printer_agents toggle Replace per-printer auto-activation (is_current_printer_agent_plugin) with a global experimental AppConfig toggle, default off: legacy print-host behavior is unchanged until the user opts in. The toggle drives device-tab routing, print button defaults, connect-button visibility and sidebar layout, and dedups machine-select dialog opens. * Track BBLPrinterAgentPlugin.py * Add printer-agent and plugin status tests Ports the agent lifecycle, duplicate agent-id, built-in-id clash and status-resolution tests. The loader runs on a detached worker thread, so the lifecycle tests live in their own executable. Tests install the production unload-side registry wiring themselves (no GUI in the test binary) and register agents manually so concurrent loads stay deterministic. * fix: pin HTTP to prevent connection refusal Set `use_ssl` to false to ensure Moonraker connectivity, as the service uses HTTP rather than HTTPS, preventing connection issues. Initialize device info early for reliable name resolution. * Bring Moonraker device panel to feature parity The monitor panel showed wrong or missing data for Moonraker printers, and its controls did nothing. Push payload now carries layer number and total layers. Remaining time replaces the wrong total_duration - print_duration formula. The chamber light toggle maps to Klipper SET_PIN / SET_LED, and pause, resume and stop post to /printer/print/{action}. Task thumbnails resolve via /server/files/thumbnails onto a new MachineObject thumbnail url. Filament sync switches to pull mode so the agent is queried on demand. Not compiled or run. * Stop blocking print on unreported nozzle data * fix: make Klipper macro lamp control reliable * Surface Moonraker webcams and gate unrunnable controls * Keep Bambu AMS dialect out of the agent waist M620 is Bambu firmware dialect, not a neutral command. Composing it in MachineObject let non-Bambu agents (Moonraker/Klipper) forward it and report success on firmware that cannot run it. Agents now own the dialect: the default refusal on IPrinterAgent returns not-supported so the UI can say so; BBLPrinterAgent keeps the byte-identical composition. * Fix multi-color filament logic Reuse color decoding across functions to improve code readability and maintain consistency in multi-color filament handling. * Move Moonraker commands off the UI thread Pause/resume/stop, g-code sends, temps, and light ran synchronous HTTP on the UI thread, freezing the app up to 10s per click on slow or unreachable printers. Run them on a single agent-owned FIFO worker so g-code ordering is preserved, while command translation stays synchronous so unsupported- command dialogs still work. Add a pending-disabled state to the pause, resume, and abort buttons for Moonraker-family printers: the icon only flips once the WebSocket reports the real state, which also rules out double-click races. * Show Snapmaker U1 camera in Device tab The U1 exposes no /server/webcams/list entry; its camera only captures after an explicit camera.start_monitor RPC, which the Moonraker websocket executes unauthenticated but only answers over MQTT - so the call is fire and forget. Start the camera when the camera view is shown and renew every 300 s: the printer retires the capture task at ~362 s and stop_monitor is accepted but ineffective, so teardown is simply to stop renewing. Frames land in monitor.jpg as still JPEGs (~2 fps at interval 0), so the webview loads a local HTML wrapper that repolls with a cache buster. * fix: start stream when camera URL changes * fix: stop Qidi slot parse throwing on null * Keep printer-agent progress in sync Keep the shared task progress aligned with agent reports that lack Bambu cloud task identity. Release the lazily allocated task during reset to avoid leaks when machine objects reconnect. * docs: document the printer-agent subsystem * fix: merge access codes into one * Reconcile implementation split with PR tip * feat: abstract remaining gcode commands in devicemanager * refactor: abstract bambu specific protocol to printer agent * refactor: push bbl workflows to bbl printer agent * remove unused * fix: default impl * fix callback error * fix: remove redundant cache * specify api for getting file transfer url * revert file transfer abstraction * fix: ams filament mapping workflow * feat: update qidi to use subscription based filament sync mode * fix: resolve stubgen byte header conflict * fix: ams sync info and periodic ams sync via subscription workflow * fix: skip filament sync dialog if filamentSyncMode is none * feat: parse nozzle information for qidi and moonraker printer agents * fix: extend access code requirements t 0, 8 or more characters. * remove irrelevant docs * fix: remove heavy includes from IPrinterAgent * fix: defer filesystem and camera abstractions * fix: remote do_fetch_filament_info from tests * cleanup moonraker and snapmaker printer agents * fix: access codes regression * fix: tests * fix: printer agent switching on preset change * fix: remove unused variable * fix: snapmaker U1 SelectMachineDialog blocking print * fix: merge artifact * fix: clear up some unrelated changes * feat: connect to cloud printer and monitor * feat: connect to cloud printer and monitor * feat: generic camera stream support for http snapshot and rtsp * fix: build & access code UI * feat: generic camera stream support for http snapshot and rtsp * fix: build & access code UI * feat: connect to cloud printer and monitor * feat: connect to cloud printer and monitor * fix: build errors * feat: camera via webrtc * fix: build * fix: cmake * feat: remove frame assembler and change config to set protocol * fix: orcaprinteragent refactor * fix: LAN paths and camera stream * feat: use ffmpeg to render http camera stream * fix: make model_id/dev_type optional instead of blocking * fix: connect via ip dialog * feat: LAN impl for Orca Printer Agent * fix: model_id resolution method for non bambu printers * fix: ffmpeg http camera stream jittering due to incomplete frames * fix: revert sdcard check * feat: check printer storage status before sending * fix: moonraker printer agent hang on printer power cut * fix: shim layer for any compatibiliity changes * fix: cloud printers were using the wrong MQTT endpoint * feat: cloud download via HTTP * temp: doc for intended change * fix: warnings * fix: warnings * fix: camera auto-play on startup * fix: split infra from impl * fix: uninitialized ams state blocking print * Fixes nullptr deref * Log first before std::move * fix: printer agent virutal optional functions * fix: parameterize orcaslicer_copy_test_dlls() for printer_agent_plugin_tests * Revert "fix: parameterize orcaslicer_copy_test_dlls() for printer_agent_plugin_tests" This reverts commit 2f566e377905e3398e87e2c9fdf08a045829bef1. * Guard libdatachannel. Remove unused code * fix: unit tests & unused variables * fix(ci): deps build order for datachannel * Resolve printer agent first before getting cloud printer agent * fix(ci): set depends openssl * fix: re-include apply header guarded by ifdef __APPLE__ * fix(ci): add libdatachannel to flatpak manifest * fix: add internal_developer_mode chekc back to MediaPlayCtrl::load() * fix: invoke js clearInterval on WebMediaController::stop * fix: change rtc log level * fix: inject provider, agent id and generation to get_user_print_info to ensure correct metadata * fix: revert moonraker specific behavior * fix: remove stale comment * fix: use ORCA_CLOUD_PROVIDER instead of hardcoded string * fix: remove hardcoded ICE servers * feat: enable https camera stream mode * fix: move non-mandatory printer agent function stubs to IPrinterAgent * fix: dedupe compatible printer type check * fix: stop the correct media controller * fix: scope get_my_machine_list to printers listed under the current printer agent * fix: move printer agent plugin tests into test_plugin_lifecycle.cpp * fix: always build bundled DataChannel dep * fix: disable unused DataChannel media support * revert: filament sync work * fix: wrap command_* with small wrapper * fix: regression bug, connecting to bambu needs bblp username * fix: default impl for vendor agnostic gcode commansd * refactor: media controller playback routing and ownership * fix: bump libdatachannel ver & update flatpak to use tar instead * fix: stop flatpak DataChannel build from re-cloning over the sandboxed network * fix: update windows ffmpeg prebuild * fix: update printer agent plugin API * fix: shift camera signaling channel to network agent * fix: follow external-packages for flatpak libdatachannel deps & add flatpak path to use source_dir * feat: add printer-agent.md doc to HLSD * fix: guard DeviceManager command dispatch when no printer agent is bound * fix: port BBL implementations from #15711 * refactor: connect_printer api and dialog * fix(tests): make omitted printer agent operations answer like a missing agent * fix: preserve printer agent defaults in PrinterAgentPluginCapabilityTrampoline * test: cover printer agent default command dispatch * fix: validate windows FFmpeg avformat library * feat: extend optional printer model warnings to calibration & ams workflows * fix: handle malformed printer progress values safely * fix: clear webview document on stop * chore: reduce diagnostic logging level to trace * refactor: centralize printer compatibility checks * tests: add device manager integration coverage * tests: cover WebMediaController lifecycle with wxWebView stub * fix: make integration tests headless * refactor: collapse command_ams_refresh_rfid and command_ams_refresh_rfid2 * refactor: make printer connection SSL agent-specific * fix: persist input printer host and port * fix: use correct device id for Moonraker connections * fix: make moonraker gcode commands asynchronous * fix: preserve moonraker device names * fix: add include for non BBL_RELEASE_TO_PUBLIC path in BBLPrinterAgent --------- Co-authored-by: Andrew <159703254+andrewsoonqn@users.noreply.github.com> Co-authored-by: SoftFever Co-authored-by: Lam Wei Lun --- CMakeLists.txt | 43 +- deps/CMakeLists.txt | 34 +- deps/DataChannel/DataChannel.cmake | 37 ++ deps/FFMPEG/FFMPEG.cmake | 30 +- docs/HLSD/printer-agent.md | 240 ++++++++++ resources/web/dialog/PluginsDialog/index.js | 4 +- resources/web/dialog/PluginsDialog/styles.css | 10 + scripts/flatpak/com.orcaslicer.OrcaSlicer.yml | 9 + src/libslic3r/PresetBundle.cpp | 1 - src/slic3r/CMakeLists.txt | 25 +- src/slic3r/GUI/AVVideoDecoder.cpp | 27 ++ src/slic3r/GUI/AVVideoDecoder.hpp | 10 + src/slic3r/GUI/CalibrationPanel.cpp | 2 +- .../GUI/CalibrationWizardPresetPage.cpp | 45 +- .../GUI/CalibrationWizardPresetPage.hpp | 4 +- src/slic3r/GUI/CameraPopup.cpp | 60 --- src/slic3r/GUI/CameraPopup.hpp | 13 +- src/slic3r/GUI/ConnectPrinter.cpp | 4 +- src/slic3r/GUI/DeviceCore/DevConfigUtil.cpp | 17 +- src/slic3r/GUI/DeviceCore/DevConfigUtil.h | 8 +- src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp | 8 +- src/slic3r/GUI/DeviceCore/DevManager.cpp | 185 +++++-- src/slic3r/GUI/DeviceCore/DevManager.h | 23 +- src/slic3r/GUI/DeviceManager.cpp | 309 ++++++++---- src/slic3r/GUI/DeviceManager.hpp | 44 +- src/slic3r/GUI/GUI_App.cpp | 81 ++-- src/slic3r/GUI/GUI_App.hpp | 8 +- src/slic3r/GUI/IMediaController.hpp | 26 + src/slic3r/GUI/Jobs/PrintJob.cpp | 3 +- src/slic3r/GUI/Jobs/SendJob.cpp | 4 +- src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/MediaPlayCtrl.cpp | 408 ++++++++++++---- src/slic3r/GUI/MediaPlayCtrl.h | 24 +- src/slic3r/GUI/Monitor.cpp | 12 +- src/slic3r/GUI/MultiMachine.cpp | 23 +- src/slic3r/GUI/MultiMachine.hpp | 1 - src/slic3r/GUI/PartSkipDialog.hpp | 2 +- src/slic3r/GUI/Plater.cpp | 113 ++++- src/slic3r/GUI/PluginStatus.hpp | 18 + src/slic3r/GUI/PluginsDialog.cpp | 13 +- src/slic3r/GUI/PrePrintChecker.cpp | 3 +- src/slic3r/GUI/PrePrintChecker.hpp | 3 +- src/slic3r/GUI/PresetComboBoxes.cpp | 14 +- src/slic3r/GUI/PresetComboBoxes.hpp | 2 + src/slic3r/GUI/ReleaseNote.cpp | 66 ++- src/slic3r/GUI/ReleaseNote.hpp | 4 + src/slic3r/GUI/SelectMachine.cpp | 125 +++-- src/slic3r/GUI/SendMultiMachinePage.cpp | 2 +- src/slic3r/GUI/SendToPrinter.cpp | 214 +++++---- src/slic3r/GUI/SendToPrinter.hpp | 1 - src/slic3r/GUI/StatusPanel.cpp | 211 ++++---- src/slic3r/GUI/StatusPanel.hpp | 12 +- src/slic3r/GUI/SyncAmsInfoDialog.cpp | 53 ++- src/slic3r/GUI/WebMediaController.cpp | 90 ++++ src/slic3r/GUI/WebMediaController.hpp | 31 ++ src/slic3r/GUI/WebRtcMediaController.cpp | 401 ++++++++++++++++ src/slic3r/GUI/WebRtcMediaController.hpp | 93 ++++ src/slic3r/GUI/wxMediaCtrl3.cpp | 450 ++++++++++++++---- src/slic3r/GUI/wxMediaCtrl3.h | 37 +- src/slic3r/Utils/BBLPrinterAgent.cpp | 112 ++++- src/slic3r/Utils/BBLPrinterAgent.hpp | 11 +- src/slic3r/Utils/CrealityPrintAgent.cpp | 2 +- src/slic3r/Utils/CrealityPrintAgent.hpp | 2 +- src/slic3r/Utils/FileTransferUtils.cpp | 2 +- src/slic3r/Utils/FileTransferUtils.hpp | 2 +- src/slic3r/Utils/ICameraSignalingChannel.hpp | 39 ++ src/slic3r/Utils/ICloudServiceAgent.hpp | 13 +- src/slic3r/Utils/IPrinterAgent.hpp | 196 +++++++- src/slic3r/Utils/MoonrakerPrinterAgent.cpp | 164 +++---- src/slic3r/Utils/MoonrakerPrinterAgent.hpp | 21 +- src/slic3r/Utils/NetworkAgent.cpp | 172 ++++++- src/slic3r/Utils/NetworkAgent.hpp | 27 +- src/slic3r/Utils/NetworkAgentFactory.cpp | 38 +- src/slic3r/Utils/OrcaCloudServiceAgent.cpp | 49 +- src/slic3r/Utils/OrcaCloudServiceAgent.hpp | 2 + src/slic3r/Utils/OrcaPrinterAgent.cpp | 52 +- src/slic3r/Utils/OrcaPrinterAgent.hpp | 11 +- src/slic3r/Utils/PrinterNetworkTypes.hpp | 34 ++ src/slic3r/Utils/QidiPrinterAgent.cpp | 2 +- src/slic3r/Utils/QidiPrinterAgent.hpp | 2 +- src/slic3r/Utils/SnapmakerPrinterAgent.cpp | 2 +- src/slic3r/Utils/SnapmakerPrinterAgent.hpp | 2 +- .../PrinterAgentPluginCapability.cpp | 30 ++ .../PrinterAgentPluginCapability.hpp | 68 +-- ...PrinterAgentPluginCapabilityTrampoline.hpp | 92 +++- tests/CMakeLists.txt | 16 +- tests/slic3rutils/CMakeLists.txt | 7 +- tests/slic3rutils/test_dev_mapping.cpp | 31 ++ .../test_device_manager_integration.cpp | 159 +++++++ tests/slic3rutils/test_device_progress.cpp | 141 ++++++ tests/slic3rutils/test_plugin_lifecycle.cpp | 242 ++++++++++ .../slic3rutils/test_plugin_printer_agent.cpp | 16 + tests/slic3rutils/test_plugin_status.cpp | 23 + tests/slic3rutils/test_printer_agent.cpp | 173 +++++++ .../slic3rutils/test_web_media_controller.cpp | 113 +++++ 95 files changed, 4641 insertions(+), 1169 deletions(-) create mode 100644 deps/DataChannel/DataChannel.cmake create mode 100644 docs/HLSD/printer-agent.md create mode 100644 src/slic3r/GUI/IMediaController.hpp create mode 100644 src/slic3r/GUI/WebMediaController.cpp create mode 100644 src/slic3r/GUI/WebMediaController.hpp create mode 100644 src/slic3r/GUI/WebRtcMediaController.cpp create mode 100644 src/slic3r/GUI/WebRtcMediaController.hpp create mode 100644 src/slic3r/Utils/ICameraSignalingChannel.hpp create mode 100644 src/slic3r/Utils/PrinterNetworkTypes.hpp create mode 100644 tests/slic3rutils/test_device_manager_integration.cpp create mode 100644 tests/slic3rutils/test_device_progress.cpp create mode 100644 tests/slic3rutils/test_plugin_status.cpp create mode 100644 tests/slic3rutils/test_printer_agent.cpp create mode 100644 tests/slic3rutils/test_web_media_controller.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a7b8adf5f..f462525030 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -834,6 +834,37 @@ find_package(OpenSSL REQUIRED) find_package(CURL REQUIRED) find_package(Freetype REQUIRED) +if (SLIC3R_GUI) + # LibDataChannel's installed export references its bundled dependencies, + # but does not install their CMake targets. Recreate those targets from + # the same dependency prefix before loading the LibDataChannel config. + if (NOT TARGET Usrsctp::usrsctp) + find_library(_ORCA_USRSCTP_LIBRARY NAMES usrsctp + PATHS "${CMAKE_PREFIX_PATH}/lib" NO_DEFAULT_PATH) + if (_ORCA_USRSCTP_LIBRARY) + add_library(Usrsctp::usrsctp UNKNOWN IMPORTED GLOBAL) + set_target_properties(Usrsctp::usrsctp PROPERTIES + IMPORTED_LOCATION "${_ORCA_USRSCTP_LIBRARY}" + IMPORTED_LINK_INTERFACE_LANGUAGES C + INTERFACE_LINK_LIBRARIES "Threads::Threads") + endif() + endif() + + if (NOT TARGET LibJuice::LibJuice) + find_library(_ORCA_LIBJUICE_LIBRARY NAMES juice + PATHS "${CMAKE_PREFIX_PATH}/lib" NO_DEFAULT_PATH) + if (_ORCA_LIBJUICE_LIBRARY) + add_library(LibJuice::LibJuice UNKNOWN IMPORTED GLOBAL) + set_target_properties(LibJuice::LibJuice PROPERTIES + IMPORTED_LOCATION "${_ORCA_LIBJUICE_LIBRARY}" + IMPORTED_LINK_INTERFACE_LANGUAGES C + INTERFACE_LINK_LIBRARIES "Threads::Threads") + endif() + endif() + + find_package(LibDataChannel CONFIG REQUIRED) +endif() + add_library(libcurl INTERFACE) target_link_libraries(libcurl INTERFACE CURL::libcurl) @@ -1114,6 +1145,7 @@ function(orcaslicer_copy_dlls target config postfix output_dlls) endif () file(COPY ${_occt_dlls} ${CMAKE_PREFIX_PATH}/bin/freetype.dll + ${CMAKE_PREFIX_PATH}/bin/avformat-61.dll ${CMAKE_PREFIX_PATH}/bin/avcodec-61.dll ${CMAKE_PREFIX_PATH}/bin/swresample-5.dll ${CMAKE_PREFIX_PATH}/bin/swscale-8.dll @@ -1126,6 +1158,7 @@ function(orcaslicer_copy_dlls target config postfix output_dlls) ${_out_dir}/WebView2Loader.dll ${_out_dir}/freetype.dll + ${_out_dir}/avformat-61.dll ${_out_dir}/avcodec-61.dll ${_out_dir}/swresample-5.dll ${_out_dir}/swscale-8.dll @@ -1149,7 +1182,10 @@ function(orcaslicer_copy_sos target config postfix output_sos) set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}") endif () - file(COPY ${CMAKE_PREFIX_PATH}/lib/libavcodec.so + file(COPY ${CMAKE_PREFIX_PATH}/lib/libavformat.so + ${CMAKE_PREFIX_PATH}/lib/libavformat.so.61 + ${CMAKE_PREFIX_PATH}/lib/libavformat.so.61.1.100 + ${CMAKE_PREFIX_PATH}/lib/libavcodec.so ${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61 ${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61.3.100 ${CMAKE_PREFIX_PATH}/lib/libavutil.so @@ -1164,6 +1200,9 @@ function(orcaslicer_copy_sos target config postfix output_sos) DESTINATION ${_out_dir}) set(${output_sos} + ${_out_dir}/libavformat.so + ${_out_dir}/libavformat.so.61 + ${_out_dir}/libavformat.so.61.1.100 ${_out_dir}/libavcodec.so ${_out_dir}/libavcodec.so.61 ${_out_dir}/libavcodec.so.61.3.100 @@ -1293,6 +1332,8 @@ endif () if (CMAKE_SYSTEM_NAME STREQUAL "Linux") set(LIBRARY_FILES + ${LIBDIR_BIN}/libavformat.so.61 + ${LIBDIR_BIN}/libavformat.so.61.1.100 ${LIBDIR_BIN}/libavcodec.so.61 ${LIBDIR_BIN}/libavcodec.so.61.3.100 ${LIBDIR_BIN}/libavutil.so.59 diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 502e5df071..aa64ad4dc9 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -164,7 +164,7 @@ if (NOT _is_multi AND NOT CMAKE_BUILD_TYPE) endif () function(orcaslicer_add_cmake_project projectname) - cmake_parse_arguments(P_ARGS "FORWARD_CONFIG" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND" "CMAKE_ARGS" ${ARGN}) + cmake_parse_arguments(P_ARGS "FORWARD_CONFIG" "INSTALL_DIR;BUILD_COMMAND;INSTALL_COMMAND;SOURCE_DIR" "CMAKE_ARGS" ${ARGN}) # MSVC is true for clang-cl as well, so the sub-build toolchain has to key on the # generator. A non-Visual-Studio superbuild passes its own generator down, and with @@ -210,12 +210,18 @@ function(orcaslicer_add_cmake_project projectname) set(_build_j "-j${NPROC}") endif () + set(_source_dir_arg "") + if (P_ARGS_SOURCE_DIR) + set(_source_dir_arg SOURCE_DIR ${P_ARGS_SOURCE_DIR}) + endif () + if (NOT IS_CROSS_COMPILE OR NOT APPLE) ExternalProject_Add( dep_${projectname} EXCLUDE_FROM_ALL ON INSTALL_DIR ${DESTDIR} DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname} + ${_source_dir_arg} ${_gen} CMAKE_ARGS -DCMAKE_POLICY_VERSION_MINIMUM=3.5 @@ -249,12 +255,14 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE) # note for future devs: shared libs may actually create a size reduction # but orcaslicer_deps tends to get really funny regarding linking after that (notably boost) # so, as much as I would like to use that, it's not happening - ExternalProject_Add_Step(dep_${projectname} free_download_space - DEPENDEES download # do after download - COMMENT "Freeing Space: Removing source archive" - WORKING_DIRECTORY ${DEP_DOWNLOAD_DIR} - COMMAND ${CMAKE_COMMAND} -E rm -r ${projectname} - ) + if (NOT P_ARGS_SOURCE_DIR) + ExternalProject_Add_Step(dep_${projectname} free_download_space + DEPENDEES download # do after download + COMMENT "Freeing Space: Removing source archive" + WORKING_DIRECTORY ${DEP_DOWNLOAD_DIR} + COMMAND ${CMAKE_COMMAND} -E rm -rf ${projectname} + ) + endif () ExternalProject_Add_Step(dep_${projectname} free_build_space DEPENDEES install # do after install COMMENT "Freeing Space: Removing source and build files" @@ -268,6 +276,7 @@ else() EXCLUDE_FROM_ALL ON INSTALL_DIR ${DESTDIR} DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/${projectname} + ${_source_dir_arg} ${_gen} CMAKE_ARGS -DCMAKE_POLICY_VERSION_MINIMUM=3.5 @@ -396,10 +405,6 @@ include(libnoise/libnoise.cmake) include(Draco/Draco.cmake) -include(FFMPEG/FFMPEG.cmake) -include(Assimp/Assimp.cmake) - - # I *think* 1.1 is used for *just* md5 hashing? # 3.1 has everything in the right place, but the md5 funcs used are deprecated # a grep across the repo shows it is used for other things @@ -410,6 +415,12 @@ if(NOT OPENSSL_FOUND) set(OPENSSL_PKG dep_OpenSSL) endif() +include(FFMPEG/FFMPEG.cmake) +include(Assimp/Assimp.cmake) + +include(DataChannel/DataChannel.cmake) +set(DATACHANNEL_PKG dep_DataChannel) + # we don't want to load a "wrong" openssl when loading curl # so, just don't even bother # ...i think this is how it works? change if wrong @@ -483,6 +494,7 @@ set(_dep_list dep_wxInspector dep_FFMPEG dep_Assimp + ${DATACHANNEL_PKG} ) if (MSVC) diff --git a/deps/DataChannel/DataChannel.cmake b/deps/DataChannel/DataChannel.cmake new file mode 100644 index 0000000000..c5ab8a8f1a --- /dev/null +++ b/deps/DataChannel/DataChannel.cmake @@ -0,0 +1,37 @@ +# libdatachannel is the native ICE/DTLS/SCTP implementation used by the +# GUI WebRTC camera controller. Keep the source revision fixed: the signaling +# protocol is evolving independently of this transport dependency. +# +# It vendors plog, usrsctp and libjuice as git submodules, which a plain +# GitHub tag tarball does not include. The flatpak sandbox has no network +# access during the build, so there the manifest itself clones the repo +# (submodules and all) into the dependency download directory before the +# sandbox closes. ExternalProject_Add is pointed at that existing checkout +# instead of being given its own network-dependent download method. +if (FLATPAK) + set(_datachannel_source + SOURCE_DIR ${DEP_DOWNLOAD_DIR}/DataChannel + ) +else() + set(_datachannel_source + GIT_REPOSITORY https://github.com/paullouisageneau/libdatachannel.git + GIT_TAG v0.24.5 + GIT_SHALLOW ON + GIT_SUBMODULES_RECURSE ON + ) +endif() + +orcaslicer_add_cmake_project(DataChannel + DEPENDS ${OPENSSL_PKG} + CMAKE_ARGS + -DNO_EXAMPLES=ON + -DNO_TESTS=ON + -DNO_WEBSOCKET=ON + -DNO_MEDIA=ON + -DUSE_NICE=OFF + -DUSE_SYSTEM_JUICE=OFF + -DUSE_SYSTEM_USRSCTP=OFF + -DOPENSSL_ROOT_DIR:PATH=${DESTDIR} + -DOPENSSL_USE_STATIC_LIBS=ON + ${_datachannel_source} +) diff --git a/deps/FFMPEG/FFMPEG.cmake b/deps/FFMPEG/FFMPEG.cmake index 38e952bbdd..4fd521321d 100644 --- a/deps/FFMPEG/FFMPEG.cmake +++ b/deps/FFMPEG/FFMPEG.cmake @@ -1,14 +1,26 @@ set(_conf_cmd ./configure) +set(_ffmpeg_depends) +set(_ffmpeg_configure_command ${_conf_cmd}) +if (TARGET dep_OpenSSL) + set(_ffmpeg_depends DEPENDS dep_OpenSSL) + set(_ffmpeg_configure_command + ${CMAKE_COMMAND} -E env + "PKG_CONFIG_PATH=${DESTDIR}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}" + ${_conf_cmd} + ) +endif() + if (MSVC) set(_source_dir "${CMAKE_BINARY_DIR}/dep_FFMPEG-prefix/src/dep_FFMPEG") - set(PREBUILD_URL_arm64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-winarm64-orca-shared-7.0.zip") - set(PREBUILD_HASH_arm64 "12f4140279f2f8469885e1b5b2e8be9d788882914c21523cacd56989f3548054") - set(PREBUILD_URL_x64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-win64-orca-shared-7.0.zip") - set(PREBUILD_HASH_x64 "e65916020ddb9ef84b2666dfbcbfc9b1d67f69d15b4a66db53754637bf2d498c") + set(PREBUILD_URL_arm64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-09-18-16-50/ffmpeg-n7.0.3-33-g887d4b4919-winarm64-orca-shared-7.0.zip") + set(PREBUILD_HASH_arm64 "da480cbb39680056de824c57ec4dc3bd577b479ebbc310ff1f9dc55cf014b4c1") + set(PREBUILD_URL_x64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-09-18-16-50/ffmpeg-n7.0.3-33-g887d4b4919-win64-orca-shared-7.0.zip") + set(PREBUILD_HASH_x64 "85da19daf198f5548259d8aabb349db84997a3f6e6886d8d7764114add9c6dae") ExternalProject_Add(dep_FFMPEG + ${_ffmpeg_depends} URL ${PREBUILD_URL_${DEPS_ARCH}} URL_HASH SHA256=${PREBUILD_HASH_${DEPS_ARCH}} DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG @@ -21,6 +33,8 @@ if (MSVC) ) else () + set(_openssl_cmd --enable-openssl) + if (APPLE) set(_minos_cmd "--extra-cflags=-mmacosx-version-min=${DEP_OSX_TARGET}" @@ -52,10 +66,11 @@ else () endif() ExternalProject_Add(dep_FFMPEG + ${_ffmpeg_depends} URL https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz URL_HASH SHA256=DEEDCABE339165214A3637DF4C86A507AEF0D793CF8774FF68735F4737E8DDBC DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG - CONFIGURE_COMMAND ${_conf_cmd} + CONFIGURE_COMMAND ${_ffmpeg_configure_command} ${_cross_cmd} ${_pic_cmd} ${_arch_cmd} @@ -63,20 +78,21 @@ else () "--prefix=${DESTDIR}" ${_link_cmd} ${_minos_cmd} + ${_openssl_cmd} --disable-doc --enable-small --disable-outdevs --disable-filters --enable-filter=*null*,afade,*fifo,*format,*resample,aeval,allrgb,allyuv,atempo,pan,*bars,color,*key,crop,draw*,eq*,framerate,*_qsv,*_vaapi,*v4l2*,hw*,scale,volume,test* --disable-protocols - --enable-protocol=file,fd,pipe,rtp,udp + --enable-protocol=file,fd,pipe,http,https,rtp,tcp,udp --disable-muxers --enable-muxer=rtp --disable-encoders --disable-decoders --enable-decoder=*aac*,h264*,mp3*,mjpeg,rv* --disable-demuxers - --enable-demuxer=h264,mp3,mov + --enable-demuxer=h264,mp3,mov,mpjpeg,rtsp,sdp --disable-zlib --disable-avdevice BUILD_IN_SOURCE ON diff --git a/docs/HLSD/printer-agent.md b/docs/HLSD/printer-agent.md new file mode 100644 index 0000000000..7062fab966 --- /dev/null +++ b/docs/HLSD/printer-agent.md @@ -0,0 +1,240 @@ +# Printer agents + +Printer agents isolate printer-specific communication from the rest of OrcaSlicer. The GUI and +`DeviceManager` operate on a shared set of printer operations and device state; a selected printer +agent implements those operations for a particular printer ecosystem. The agent boundary allows +Bambu, Moonraker-based printers, built-in integrations, and Python-provided integrations to use the +same application workflow without making the GUI understand every printer protocol. + +The current boundary is an adapter boundary around the existing application contract. In particular, +some request fields and message payloads still use the Bambu-shaped representation that existing +`MachineObject` and `DeviceManager` code consumes. The printer agent is responsible for translating +that representation into the protocol spoken by its printer. This is an intentional compatibility +constraint of the current design; the interface is not yet a neutral printer protocol. + +The v1 dialect migration path is deliberately narrow. `DeviceManager` currently speaks the Bambu JSON +dialect because that is the payload shape already used throughout the command and state workflow. The +v1 `OrcaPrinterAgent` also accepts that Bambu dialect. Its transport path places the small translation +needed for the target printer at `deliver_to_sink`, keeping the compatibility code at the edge rather +than spreading it through `DeviceManager` or the agent interface. + +The eventual direction is for `DeviceManager` to produce an Orca JSON dialect. The Bambu agent will then +own the translation from Orca JSON to Bambu's protocol, while `OrcaPrinterAgent` can forward the Orca +payload directly to its sink. The v1 translation at `deliver_to_sink` can then be removed without +changing `DeviceManager`, the command callers, or the rest of the agent workflow. + +## Components + +The system has four relevant layers: + +```text +GUI / DeviceManager / MachineObject + | + NetworkAgent + / \ + IPrinterAgent ICloudServiceAgent + | | + printer protocol authentication and cloud services +``` + +### `DeviceManager` and `MachineObject` + +`DeviceManager` owns the application-facing printer workflow. It maintains `MachineObject` instances, +updates their state, filters devices for the active printer agent, and initiates operations such as +homing, temperature changes, printing, subscriptions, and camera playback. + +`MachineObject` remains the shared state model used by the GUI. It does not contain the implementation +of a printer protocol. When a device is discovered or returned by a cloud query, the device is tagged +with the active `printer_agent_id`. Device lists and selected-machine operations use that tag to avoid +sending an operation through an agent that does not own the device. + +### `NetworkAgent` + +`NetworkAgent` is the façade used by the GUI and `DeviceManager`. It owns: + +- the currently selected `IPrinterAgent`; +- the registered cloud-service instances, indexed by provider; +- callbacks shared by the active printer agent and the application; +- the forwarding methods for printer commands and cloud operations. + +There is one active printer agent for the currently selected printer preset. Switching the preset +increments the machine-list generation, disconnects the old printer agent, removes its callbacks, and +installs the newly selected agent. The façade then forwards printer operations to that agent. + +Cloud operations are selected separately using a provider key. `NetworkAgent` forwards a cloud request +to the matching `ICloudServiceAgent`, and forwards cloud camera operations with a device ID. The +printer agent receives a cloud-agent pointer through `set_cloud_agent()` when it is created, allowing +printer communication to obtain cloud tokens without depending on a concrete cloud implementation. + +### `IPrinterAgent` + +`IPrinterAgent` is the printer-facing contract. It covers: + +- cloud-relay and direct-LAN message delivery; +- LAN connection, discovery, binding, and certificates; +- printer subscriptions and callbacks; +- print operations; +- filament synchronization; +- camera capability and local camera URL reporting; +- printer command methods. + +Concrete built-in implementations include the Bambu wrapper, the native Orca/Moonraker path, and +other printer-agent implementations registered by the application. A printer agent may use either +the cloud agent, a direct LAN connection, or both. + +### `ICloudServiceAgent` + +`ICloudServiceAgent` owns authentication and services provided by a cloud backend. It covers login +state, tokens, user and printer lists, settings synchronization, model services, cloud messages, and +cloud camera operations. + +Cloud camera operations are device-scoped: + +- `get_camera_url(dev_id, callback)` obtains a stream URL for one device; +- `create_camera_signaling_channel(dev_id)` creates signaling for one device where the provider + supports it. + +This is separate from the local camera URL exposed by `IPrinterAgent`, which is currently scoped to +the active printer agent because a normal LAN agent represents one physical printer connection. + +## Agent registration and selection + +`NetworkAgentFactory` maintains the printer-agent registry. Each registry entry contains an agent ID, +a display name, and a factory function. Built-in agents register during application initialization. +Python printer-agent capabilities register dynamically and contribute an agent ID and factory entry. + +The selected printer preset contains the printer-agent choice. If no explicit choice is stored, the +application preserves the existing default behavior: Bambu presets select the Bambu agent and other +presets select the native Orca agent. When a preset is changed, `GUI_App` resolves the effective agent +ID, obtains the corresponding cloud agent, creates the printer agent through the registry, and installs +it in `NetworkAgent`. + +The registry rejects conflicting agent IDs. This matters for Python plugins because an agent ID is the +stable identity used by presets and device ownership; two enabled plugin capabilities must not claim +the same ID. + +## Message and command flow + +There are two low-level message paths: + +- `send_message()` publishes a command through the printer's cloud relay; +- `send_message_to_printer()` sends a command directly to the printer over the LAN path. + +Both paths accept a JSON string, quality-of-service and flag values, and return the existing network +status code domain. The agent owns the conversion from that JSON contract to its native transport. + +The typed `command_*` methods are the application-facing convenience layer. The five generic defaults +currently implemented by `IPrinterAgent` construct the existing JSON dialect and route through the +same message path: + +| Method | Default operation | +| --- | --- | +| `command_xyz_abs()` | Send `G90` for absolute positioning | +| `command_auto_leveling()` | Send `G29` for bed leveling | +| `command_go_home()` | Use the supported homing operation or send `G28` | +| `command_set_bed()` | Use the supported bed control or send `M140` | +| `command_set_nozzle()` | Send `M104` for nozzle temperature | + +These are compatibility defaults for common printer workflows, not a guarantee that every firmware +implements every command identically. An agent can override a method when its protocol needs another +operation. For example, a Klipper configuration may use `BED_MESH_CALIBRATE` instead of `G29`. + +The remaining common command methods default to `ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED` because their +existing behavior is vendor-specific or has no portable implementation: + +- AMS RFID refresh; +- AMS calibration; +- AMS tray selection; +- camera start; +- axis control. + +The methods remain on the common interface so an agent that supports them can override them explicitly. +`sequence_id` remains part of the command contract because `DeviceManager` creates and tracks it as +the command ID. + +## Device ownership and stale responses + +Printer-agent ownership is represented by `printer_agent_id` on device records and `MachineObject` +instances. The active agent ID is attached when a device is discovered, returned by a cloud list, or +reused after a preset switch. Local-machine configuration also persists the agent ID so a saved LAN +device is not silently reused by an unrelated agent. + +Cloud printer-list responses carry three pieces of request context added by `NetworkAgent`: + +```text +provider cloud provider used for the request +agent_id active printer agent when the request was made +generation machine-list generation when the request was made +``` + +`DeviceManager` accepts the response only when those values still match the current provider, active +agent, and generation. This prevents a slow response from the previous preset or provider from +repopulating the current device list. + +The provider mapping is currently selected by `GUI_App`: the Bambu agent maps to the Bambu cloud +provider and other agents map to the Orca cloud provider. The generation check protects that existing +selection from races; it does not make cloud-provider ownership intrinsic to an agent. Cloud-printer +ownership and the broader Orca cloud services are therefore still separate architectural concerns. + +## Python printer agents + +`PrinterAgentPluginCapability` implements `IPrinterAgent` directly. The live capability object is +registered with `NetworkAgentFactory` and handed out as the printer agent when its agent ID is selected. +The plugin receives the selected `ICloudServiceAgent` through `set_cloud_agent()` just like a built-in +printer agent. + +Python plugins must implement the core communication and lifecycle methods required by the interface, +including agent metadata, printer connection, discovery callbacks, and the two message-send methods. +Methods that are meaningful only to a particular printer are optional overrides where the C++ base +class provides a default. + +All ten `command_*` methods are available in the Python binding and in the trampoline. Their override +status is intentionally optional: + +- the five generic commands use the C++ default when Python does not override them; +- the five vendor-specific commands return `NOT_SUPPORTED` unless Python supplies an implementation; +- a Python implementation can replace either behavior for its own protocol. + +The Python camera binding exposes HTTP, HTTPS, RTSP, and HTTP-snapshot modes. WebRTC remains a +built-in C++ camera mode, but is not exposed as a Python mode because the current Python capability +does not provide the corresponding cloud signaling-channel contract. + +## Camera playback boundary + +The camera stream mode describes how a stream is obtained; it does not by itself define ownership of +the wxWidgets view that renders it. `MediaPlayCtrl` selects and tears down the active backend, while +the wx parent owns the child window or renderer. This is important because a web view, native media +control, and frame-based/WebRTC renderer have different wx window-lifetime requirements. + +Cloud URL and signaling requests are routed through `NetworkAgent` to the cloud provider selected for +the device. Local URL requests are routed to the active printer agent. The distinction keeps cloud +account services device-scoped while preserving the current one-LAN-agent/one-printer model. + +## Compatibility constraints + +The printer-agent boundary intentionally preserves several existing application contracts: + +- Bambu-shaped JSON is still the shared command representation; +- existing network status codes are reused, with Orca-specific unsupported/capability errors added + in the Orca-reserved range; +- `MachineObject` remains the shared device-state model; +- preset and local-machine data retain compatibility with the existing agent-selection behavior; +- Python plugins use the existing capability and pybind11 registration system. + +The agent abstraction is therefore responsible for containing vendor differences, not for pretending +that all vendor protocols are identical. The planned Orca JSON dialect is the protocol-neutral command +model for the `DeviceManager`/agent boundary. Once it is introduced, Bambu-specific translation remains +inside the Bambu agent and the Orca agent's v1 sink adapter can be removed as a self-contained cleanup. + +## Main implementation locations + +- [`IPrinterAgent`](../../src/slic3r/Utils/IPrinterAgent.hpp) — printer-agent contract and generic command defaults +- [`ICloudServiceAgent`](../../src/slic3r/Utils/ICloudServiceAgent.hpp) — cloud service and per-device + cloud camera contract +- [`NetworkAgent`](../../src/slic3r/Utils/NetworkAgent.hpp) — façade and dispatch between active agents +- [`NetworkAgentFactory`](../../src/slic3r/Utils/NetworkAgentFactory.hpp) — built-in and Python agent registry +- [`DeviceManager`](../../src/slic3r/GUI/DeviceCore/DevManager.cpp) — device ownership, filtering, and + stale-response checks +- [`PrinterAgentPluginCapability`](../../src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp) + — Python bindings +- [`MediaPlayCtrl`](../../src/slic3r/GUI/MediaPlayCtrl.cpp) — camera backend selection and playback lifecycle diff --git a/resources/web/dialog/PluginsDialog/index.js b/resources/web/dialog/PluginsDialog/index.js index ad1242e4f9..f02cc08855 100644 --- a/resources/web/dialog/PluginsDialog/index.js +++ b/resources/web/dialog/PluginsDialog/index.js @@ -575,7 +575,7 @@ function CapabilityCanRun(plugin, capability) { } function IsPluginChecked(plugin) { - return GetStatus(plugin) === "Activated"; + return plugin.is_loaded; } function HasMixedCapabilityState(plugin) { @@ -1347,6 +1347,8 @@ function StatusDescription(plugin) { return "This plugin is still loading."; case "Error": return "This plugin is blocked until its error is fixed."; + case "RuntimeError": + return "This plugin is loaded but a capability reported an error."; case "Inactive": default: return "This plugin is inactive. Activate it to install or load it."; diff --git a/resources/web/dialog/PluginsDialog/styles.css b/resources/web/dialog/PluginsDialog/styles.css index 3d8c18be9e..9eef78760d 100644 --- a/resources/web/dialog/PluginsDialog/styles.css +++ b/resources/web/dialog/PluginsDialog/styles.css @@ -424,6 +424,11 @@ body.pane-resizing { font-weight: 600; } +.status-cell.status-runtimeerror { + color: var(--plugin-status-warn); + font-weight: 600; +} + .status-cell.status-loading { color: var(--plugin-status-warn); font-weight: 600; @@ -680,6 +685,11 @@ body.pane-resizing { color: var(--plugin-status-danger); } +.detail-status-chip.status-runtimeerror { + background: var(--plugin-status-warn-bg); + color: var(--plugin-status-warn); +} + .detail-status-chip.status-loading { background: var(--plugin-status-warn-bg); color: var(--plugin-status-warn); diff --git a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml index 404dbf5fe4..25cb4fdcc5 100644 --- a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml +++ b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml @@ -331,6 +331,15 @@ modules: sha256: deedcabe339165214a3637df4c86a507aef0d793cf8774ff68735f4737e8ddbc dest: external-packages/FFMPEG + # libdatachannel v0.24.5 + # The Git checkout includes the submodules that are missing from the + # GitHub source archive. DataChannel.cmake consumes it as SOURCE_DIR. + - type: git + url: https://github.com/paullouisageneau/libdatachannel.git + tag: v0.24.5 + commit: 443f6934d9007eb7076ab7825ba330f355fcbead + dest: external-packages/DataChannel + # --------------------------------------------------------------- # Fallback archives for deps normally provided by the GNOME SDK. # These are only used if find_package() fails to locate them. diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 63e3ba6105..d2913db55e 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -3954,7 +3954,6 @@ unsigned int PresetBundle::sync_ams_list(std::vector("filament_colour_type"); ConfigOptionInts * filament_map = project_config.option("filament_map"); ConfigOptionInts * filament_volume_map = project_config.option("filament_volume_map"); - // Snapshot and temporarily strip mixed filament slots so AMS sync operates on physical // filaments only. A mixed slot is virtual and has no tray to sync against; leaving it in // would let AMS mapping overwrite it and would break the physical-first slot ordering the diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index f6a8249d4f..c8e2f111d7 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -236,6 +236,7 @@ set(SLIC3R_GUI_SOURCES GUI/GLToolbar.hpp GUI/ImageDPIFrame.cpp GUI/ImageDPIFrame.hpp + GUI/IMediaController.hpp GUI/GUI_App.cpp GUI/GUI_App.hpp GUI/GUI_AuxiliaryList.cpp @@ -365,6 +366,8 @@ set(SLIC3R_GUI_SOURCES GUI/MediaFilePanel.h GUI/MediaPlayCtrl.cpp GUI/MediaPlayCtrl.h + GUI/WebRtcMediaController.cpp + GUI/WebRtcMediaController.hpp GUI/MeshUtils.cpp GUI/MeshUtils.hpp GUI/ModelMall.cpp @@ -560,6 +563,8 @@ set(SLIC3R_GUI_SOURCES GUI/WebUserLoginDialog.hpp GUI/WebViewDialog.cpp GUI/WebViewDialog.hpp + GUI/WebMediaController.hpp + GUI/WebMediaController.cpp GUI/Widgets/AMSControl.cpp GUI/Widgets/AMSControl.hpp GUI/Widgets/AMSItem.cpp @@ -754,6 +759,7 @@ set(SLIC3R_GUI_SOURCES Utils/NetworkAgentFactory.cpp Utils/ICloudServiceAgent.hpp Utils/IPrinterAgent.hpp + Utils/ICameraSignalingChannel.hpp Utils/OrcaCloudServiceAgent.cpp Utils/OrcaCloudServiceAgent.hpp Utils/OrcaPrinterAgent.cpp @@ -924,7 +930,7 @@ else() set(_opengl_link_lib OpenGL::GL) endif() -target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise pybind11::embed) +target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto LibDataChannel::LibDataChannel noise::noise pybind11::embed) if (CMAKE_SYSTEM_NAME STREQUAL "Linux") # Linux finds wxWidgets in module mode, whose include dirs and definitions @@ -981,29 +987,32 @@ endif () if (APPLE) # Static FFmpeg from the deps install: nothing to bundle into the .app, - # no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil. + # no rpath/install_name handling. Order matters: avformat -> avcodec -> swscale -> avutil. + find_library(LIBAVFORMAT_LIBRARY NAMES libavformat.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) - if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY) - message(FATAL_ERROR "Static FFmpeg (libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.") + if (NOT LIBAVFORMAT_LIBRARY OR NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY) + message(FATAL_ERROR "Static FFmpeg (libavformat.a/libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.") endif () - target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY}) + target_link_libraries(libslic3r_gui ${LIBAVFORMAT_LIBRARY} ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY}) target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include) elseif (WIN32) # Prebuilt shared FFmpeg from the deps install. Windows has no pkg-config, # so resolve the import libraries out of the deps prefix directly; the DLLs # are copied next to the executable by the top level CMakeLists. + find_library(LIBAVFORMAT_LIBRARY NAMES avformat PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBAVCODEC_LIBRARY NAMES avcodec PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBSWSCALE_LIBRARY NAMES swscale PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) find_library(LIBAVUTIL_LIBRARY NAMES avutil PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH) - if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY) - message(FATAL_ERROR "FFmpeg (avcodec/swscale/avutil) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps.") + if (NOT LIBAVFORMAT_LIBRARY OR NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY) + message(FATAL_ERROR "FFmpeg (avformat/avcodec/swscale/avutil) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps.") endif () - target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY}) + target_link_libraries(libslic3r_gui ${LIBAVFORMAT_LIBRARY} ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY}) target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include) else () pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET + libavformat libavcodec libswscale libavutil diff --git a/src/slic3r/GUI/AVVideoDecoder.cpp b/src/slic3r/GUI/AVVideoDecoder.cpp index d7b8432bd3..76965fedef 100644 --- a/src/slic3r/GUI/AVVideoDecoder.cpp +++ b/src/slic3r/GUI/AVVideoDecoder.cpp @@ -45,6 +45,25 @@ int AVVideoDecoder::open(Bambu_StreamInfo const &info) return 0; } +int AVVideoDecoder::open(AVCodecParameters const ¶meters) +{ + if (avcodec_parameters_to_context(codec_ctx_, ¶meters) < 0) + return -1; + + auto codec = avcodec_find_decoder(codec_ctx_->codec_id); + if (codec == nullptr) { + fprintf(stderr, "AVVideoDecoder: unsupported codec!\n"); + return -1; + } + if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) { + fprintf(stderr, "AVVideoDecoder: could not open codec\n"); + return -1; + } + + frame_ = av_frame_alloc(); + return frame_ == nullptr ? -1 : 0; +} + int AVVideoDecoder::decode(const Bambu_Sample &sample) { int ret = -1; @@ -71,6 +90,14 @@ int AVVideoDecoder::decode(const Bambu_Sample &sample) return ret; } +int AVVideoDecoder::decode(const AVPacket &packet) +{ + int ret = avcodec_send_packet(codec_ctx_, &packet); + if (ret == 0) + got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0; + return ret; +} + int AVVideoDecoder::flush() { int ret = avcodec_send_packet(codec_ctx_, nullptr); diff --git a/src/slic3r/GUI/AVVideoDecoder.hpp b/src/slic3r/GUI/AVVideoDecoder.hpp index 4111e860a2..d3d16a11c1 100644 --- a/src/slic3r/GUI/AVVideoDecoder.hpp +++ b/src/slic3r/GUI/AVVideoDecoder.hpp @@ -23,8 +23,10 @@ public: public: int open(Bambu_StreamInfo const &info); + int open(AVCodecParameters const ¶meters); int decode(Bambu_Sample const &sample); + int decode(AVPacket const &packet); int flush(); @@ -34,6 +36,14 @@ public: bool toWxBitmap(wxBitmap &bitmap, wxSize const & size); + // Native size of the most recently decoded frame, or an unspecified size if + // nothing has decoded yet. Lets a caller learn the video dimensions when the + // container/probe could not report them up front. + wxSize decoded_frame_size() const + { + return got_frame_ && frame_ ? wxSize{frame_->width, frame_->height} : wxSize{}; + } + private: AVCodecContext *codec_ctx_ = nullptr; AVFrame * frame_ = nullptr; diff --git a/src/slic3r/GUI/CalibrationPanel.cpp b/src/slic3r/GUI/CalibrationPanel.cpp index d906a8211c..14e1d5dea2 100644 --- a/src/slic3r/GUI/CalibrationPanel.cpp +++ b/src/slic3r/GUI/CalibrationPanel.cpp @@ -328,7 +328,7 @@ void SelectMObjectPopup::update_user_devices() } m_bind_machine_list.clear(); - m_bind_machine_list = dev->get_my_machine_list(); + m_bind_machine_list = dev->get_my_machine_list(dev->get_current_printer_agent_id()); //sort list std::vector> user_machine_list; diff --git a/src/slic3r/GUI/CalibrationWizardPresetPage.cpp b/src/slic3r/GUI/CalibrationWizardPresetPage.cpp index f4160885cb..01c9843305 100644 --- a/src/slic3r/GUI/CalibrationWizardPresetPage.cpp +++ b/src/slic3r/GUI/CalibrationWizardPresetPage.cpp @@ -6,8 +6,10 @@ #include "Widgets/Label.hpp" #include "MsgDialog.hpp" #include "libslic3r/Print.hpp" +#include "PrePrintChecker.hpp" #include "DeviceCore/DevConfig.h" +#include "DeviceCore/DevConfigUtil.h" #include "DeviceCore/DevExtruderSystem.h" #include "DeviceCore/DevFilaBlackList.h" #include "DeviceCore/DevFilaSystem.h" @@ -1637,29 +1639,6 @@ void CalibrationPresetPage::update_combobox_filaments(MachineObject* obj) select_default_compatible_filament(); } -bool CalibrationPresetPage::is_blocking_printing() -{ - DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); - if (!dev) return true; - - MachineObject* obj_ = dev->get_selected_machine(); - if (obj_ == nullptr) return true; - - PresetBundle* preset_bundle = wxGetApp().preset_bundle; - auto source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); - auto target_model = obj_->printer_type; - - if (source_model != target_model) { - std::vector compatible_machine = obj_->get_compatible_machine(); - vector::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model); - if (it == compatible_machine.end()) { - return true; - } - } - - return false; -} - bool CalibrationPresetPage::is_nozzle_info_synced() const { if (!curr_obj || !curr_obj->is_info_ready()) @@ -1741,11 +1720,13 @@ void CalibrationPresetPage::update_show_status() } } - //if (is_blocking_printing()) { - // show_status(CaliPresetPageStatus::CaliPresetStatusUnsupportedPrinter); - // return; - //} - //else + bool has_optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type); + if (PresetBundle *preset_bundle = wxGetApp().preset_bundle) { + has_optional_printer_model = has_optional_printer_model || + DevPrinterConfigUtil::is_optional_printer_model_id( + preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle)); + } + if (obj_->is_connecting() || !obj_->is_connected()) { show_status(CaliPresetPageStatus::CaliPresetStatusInConnecting); return; @@ -1797,7 +1778,9 @@ void CalibrationPresetPage::update_show_status() return; } - show_status(CaliPresetPageStatus::CaliPresetStatusNormal); + show_status(has_optional_printer_model ? + CaliPresetPageStatus::CaliPresetStatusOptionalPrinterModel : + CaliPresetPageStatus::CaliPresetStatusNormal); } @@ -1851,6 +1834,10 @@ void CalibrationPresetPage::show_status(CaliPresetPageStatus status) Layout(); Fit(); } + else if (status == CaliPresetPageStatus::CaliPresetStatusOptionalPrinterModel) { + update_print_status_msg(PrePrintChecker::get_pre_state_msg(PrintDialogStatus::PrintStatusOptionalPrinterModel), true); + Enable_Send_Button(true); + } else if (status == CaliPresetPageStatus::CaliPresetStatusNoUserLogin) { wxString msg_text = _L("No login account, only printers in LAN mode are displayed."); update_print_status_msg(msg_text, false); diff --git a/src/slic3r/GUI/CalibrationWizardPresetPage.hpp b/src/slic3r/GUI/CalibrationWizardPresetPage.hpp index adb4ce52ab..4d1adb1004 100644 --- a/src/slic3r/GUI/CalibrationWizardPresetPage.hpp +++ b/src/slic3r/GUI/CalibrationWizardPresetPage.hpp @@ -152,7 +152,8 @@ enum CaliPresetPageStatus CaliPresetStatusInConnecting, CaliPresetStatusFilamentIncompatible, CaliPresetStatusLanModeSDcardNotAvailable, - CaliPresetStatusDifferentNozzleDiameters + CaliPresetStatusDifferentNozzleDiameters, + CaliPresetStatusOptionalPrinterModel }; class CalibrationPresetPage : public CalibrationWizardPage @@ -268,7 +269,6 @@ protected: bool is_nozzle_info_synced() const; void show_status(CaliPresetPageStatus status); void Enable_Send_Button(bool enable); - bool is_blocking_printing(); bool need_check_sdcard(MachineObject* obj); CaliPresetPageStatus get_status() { return m_page_status; } diff --git a/src/slic3r/GUI/CameraPopup.cpp b/src/slic3r/GUI/CameraPopup.cpp index c4e3c5486c..099edd7de0 100644 --- a/src/slic3r/GUI/CameraPopup.cpp +++ b/src/slic3r/GUI/CameraPopup.cpp @@ -28,7 +28,6 @@ wxEND_EVENT_TABLE() wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent); wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent); -wxDEFINE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent); #define CAMERAPOPUP_CLICK_INTERVAL 20 @@ -102,34 +101,6 @@ CameraPopup::CameraPopup(wxWindow *parent) top_sizer->Add(0, 0, wxALL, 0); } - // Orca: custom IP camera source — lets the user point Live Video at any camera URL (Orca feature; not in the reference) - m_custom_camera_input_confirm = new Button(m_panel, _L("Enable")); - m_custom_camera_input_confirm->SetBackgroundColor(wxColour(38, 166, 154)); - m_custom_camera_input_confirm->SetBorderColor(wxColour(38, 166, 154)); - m_custom_camera_input_confirm->SetTextColor(wxColour(0xFFFFFE)); - m_custom_camera_input_confirm->SetFont(Label::Body_14); - m_custom_camera_input_confirm->SetMinSize(wxSize(FromDIP(90), FromDIP(30))); - m_custom_camera_input_confirm->SetPosition(wxDefaultPosition); - m_custom_camera_input_confirm->SetCornerRadius(FromDIP(12)); - m_custom_camera_input = new TextInput(m_panel, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize); - m_custom_camera_input->GetTextCtrl()->SetHint(_L("Hostname or IP")); - m_custom_camera_input->GetTextCtrl()->SetFont(Label::Body_14); - m_custom_camera_hint = new wxStaticText(m_panel, wxID_ANY, _L("Custom camera source")); - m_custom_camera_hint->Wrap(-1); - m_custom_camera_hint->SetFont(Label::Head_14); - m_custom_camera_hint->SetForegroundColour(TEXT_COL); - - m_custom_camera_input_confirm->Bind(wxEVT_BUTTON, &CameraPopup::on_camera_source_changed, this); - - if (!wxGetApp().app_config->get("camera", "custom_source").empty()) { - m_custom_camera_input->GetTextCtrl()->SetValue(wxGetApp().app_config->get("camera", "custom_source")); - set_custom_cam_button_state(wxGetApp().app_config->get("camera", "enable_custom_source") == "true"); - } - - top_sizer->Add(m_custom_camera_hint, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5)); - top_sizer->Add(0, 0, wxALL, 0); - top_sizer->Add(m_custom_camera_input, 2, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxALL, FromDIP(5)); - top_sizer->Add(m_custom_camera_input_confirm, 1, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5)); main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10)); auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed) @@ -184,37 +155,6 @@ void CameraPopup::sdcard_absent_hint() GetEventHandler()->ProcessEvent(evt); } -void CameraPopup::on_camera_source_changed(wxCommandEvent &event) -{ - if (m_obj && !m_custom_camera_input->GetTextCtrl()->IsEmpty()) { - handle_camera_source_change(); - } -} - -void CameraPopup::handle_camera_source_change() -{ - m_custom_camera_enabled = !m_custom_camera_enabled; - - set_custom_cam_button_state(m_custom_camera_enabled); - - wxGetApp().app_config->set("camera", "custom_source", m_custom_camera_input->GetTextCtrl()->GetValue().ToStdString()); - wxGetApp().app_config->set("camera", "enable_custom_source", m_custom_camera_enabled); - - wxCommandEvent evt(EVT_CAM_SOURCE_CHANGE); - evt.SetEventObject(this); - GetEventHandler()->ProcessEvent(evt); -} - -void CameraPopup::set_custom_cam_button_state(bool state) -{ - m_custom_camera_enabled = state; - auto stateColour = state ? wxColour(170, 0, 0) : wxColour(38, 166, 154); - auto stateText = state ? "Disable" : "Enable"; - m_custom_camera_input_confirm->SetBackgroundColor(stateColour); - m_custom_camera_input_confirm->SetBorderColor(stateColour); - m_custom_camera_input_confirm->SetLabel(_L(stateText)); -} - void CameraPopup::on_switch_recording(wxCommandEvent& event) { if (!m_obj) return; diff --git a/src/slic3r/GUI/CameraPopup.hpp b/src/slic3r/GUI/CameraPopup.hpp index f45ebb6e42..c9b1540d3b 100644 --- a/src/slic3r/GUI/CameraPopup.hpp +++ b/src/slic3r/GUI/CameraPopup.hpp @@ -15,14 +15,12 @@ #include "Widgets/SwitchButton.hpp" #include "Widgets/RadioBox.hpp" #include "Widgets/PopupWindow.hpp" -#include "Widgets/TextInput.hpp" namespace Slic3r { namespace GUI { wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent); wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent); -wxDECLARE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent); class CameraPopup : public PopupWindow { @@ -53,9 +51,6 @@ protected: void on_switch_recording(wxCommandEvent& event); void on_set_resolution(); void sdcard_absent_hint(); - void on_camera_source_changed(wxCommandEvent& event); - void handle_camera_source_change(); - void set_custom_cam_button_state(bool state); wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left); void select_curr_radiobox(int btn_idx); @@ -76,10 +71,10 @@ private: wxStaticText* m_text_liveview_retry; SwitchButton* m_switch_liveview_retry; #endif //BBL_RELEASE_TO_PUBLIC - wxStaticText* m_custom_camera_hint; - TextInput* m_custom_camera_input; - Button* m_custom_camera_input_confirm; - bool m_custom_camera_enabled{ false }; + // wxStaticText* m_custom_camera_hint; + // TextInput* m_custom_camera_input; + // Button* m_custom_camera_input_confirm; + // bool m_custom_camera_enabled{ false }; wxStaticText* m_text_resolution; wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM]; wxScrolledWindow *m_panel; diff --git a/src/slic3r/GUI/ConnectPrinter.cpp b/src/slic3r/GUI/ConnectPrinter.cpp index 3e78e7fe5c..8e3d58b413 100644 --- a/src/slic3r/GUI/ConnectPrinter.cpp +++ b/src/slic3r/GUI/ConnectPrinter.cpp @@ -35,7 +35,9 @@ ConnectPrinterDialog::ConnectPrinterDialog(wxWindow *parent, wxWindowID id, cons sizer_connect = new wxBoxSizer(wxHORIZONTAL); m_textCtrl_code = new TextInput(this, wxEmptyString); - m_textCtrl_code->GetTextCtrl()->SetMaxLength(10); + // OrcaSonar uses a 12-character base32 access code. Keep this field long + // enough for it while retaining the existing validation for LAN codes. + m_textCtrl_code->GetTextCtrl()->SetMaxLength(12); m_textCtrl_code->SetFont(Label::Body_14); m_textCtrl_code->SetCornerRadius(FromDIP(5)); m_textCtrl_code->SetSize(wxSize(FromDIP(330), FromDIP(40))); diff --git a/src/slic3r/GUI/DeviceCore/DevConfigUtil.cpp b/src/slic3r/GUI/DeviceCore/DevConfigUtil.cpp index 25008cad29..159fca6285 100644 --- a/src/slic3r/GUI/DeviceCore/DevConfigUtil.cpp +++ b/src/slic3r/GUI/DeviceCore/DevConfigUtil.cpp @@ -1,5 +1,7 @@ #include "DevConfigUtil.h" +#include "slic3r/GUI/DeviceManager.hpp" + #include #include #include "../I18N.hpp" @@ -41,6 +43,19 @@ static void _toolhead_translation_markers() std::string DevPrinterConfigUtil::m_resource_file_path = ""; +bool DevPrinterConfigUtil::is_printer_model_compatible(const std::string& source_model, MachineObject& machine) +{ + const std::string& target_model = machine.printer_type; + if (is_optional_printer_model_id(source_model) || is_optional_printer_model_id(target_model)) + return true; + + if (source_model == target_model) + return true; + + const auto compatible_machine = machine.get_compatible_machine(); + return std::find(compatible_machine.begin(), compatible_machine.end(), source_model) != compatible_machine.end(); +} + std::map DevPrinterConfigUtil::get_all_model_id_with_name() { @@ -405,4 +420,4 @@ std::string DevPrinterConfigUtil::get_toolhead_display_name( return result; } -}; \ No newline at end of file +}; diff --git a/src/slic3r/GUI/DeviceCore/DevConfigUtil.h b/src/slic3r/GUI/DeviceCore/DevConfigUtil.h index 2b4af44061..e42f2806d1 100644 --- a/src/slic3r/GUI/DeviceCore/DevConfigUtil.h +++ b/src/slic3r/GUI/DeviceCore/DevConfigUtil.h @@ -25,6 +25,8 @@ namespace Slic3r { +class MachineObject; + /// Toolhead component type (extruder / nozzle / hotend) enum class ToolHeadComponent { Extruder, @@ -59,6 +61,10 @@ public: /*printer*/ // info static std::map get_all_model_id_with_name(); + // A printer agent may not know the physical model. Keep that case optional so + // model compatibility checks do not turn missing identity into a hard error. + static bool is_printer_model_compatible(const std::string& source_model, MachineObject& machine); + static bool is_optional_printer_model_id(const std::string& model_id) { return model_id.empty(); } static std::string get_printer_type(const std::string& type_str) { return get_value_from_config(type_str, "printer_type"); } static std::string get_printer_display_name(const std::string& type_str) { return get_value_from_config(type_str, "display_name"); } static std::string get_printer_series_str(std::string type_str) { return get_value_from_config(type_str, "printer_series"); } @@ -227,4 +233,4 @@ static std::string _parse_printer_type(const std::string &type_str) return type_str; } -};// namespace Slic3r \ No newline at end of file +};// namespace Slic3r diff --git a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp index 881aa75d20..2cce41db9d 100644 --- a/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp +++ b/src/slic3r/GUI/DeviceCore/DevFilaSystem.cpp @@ -762,9 +762,9 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS { curr_tray->remain = -1; } - if (tray_it->contains("tray_slot_placeholder")) { - curr_tray->is_slot_placeholder = true; - } + // The tray objects are reused across status updates. Reset this + // state when a previously empty slot receives a filament again. + curr_tray->is_slot_placeholder = tray_it->contains("tray_slot_placeholder"); int ams_id_int = 0; int tray_id_int = 0; try @@ -999,4 +999,4 @@ void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* ob } } -} \ No newline at end of file +} diff --git a/src/slic3r/GUI/DeviceCore/DevManager.cpp b/src/slic3r/GUI/DeviceCore/DevManager.cpp index c03f0cfe59..5a0e4d768f 100644 --- a/src/slic3r/GUI/DeviceCore/DevManager.cpp +++ b/src/slic3r/GUI/DeviceCore/DevManager.cpp @@ -1,8 +1,10 @@ +#include "DevManager.h" #include #include -#include "DevManager.h" +#include +#include "CloudProvider.hpp" #include "DevUtil.h" // TODO: remove this include @@ -15,6 +17,8 @@ #include "libslic3r/Time.hpp" +#include "IPrinterAgent.hpp" + using namespace nlohmann; namespace { @@ -46,10 +50,12 @@ namespace { namespace Slic3r { - DeviceManager::DeviceManager(NetworkAgent* agent) + DeviceManager::DeviceManager(NetworkAgent* agent, bool enable_refresher, AppConfig* app_config) { - m_agent = agent; - m_refresher = new DeviceManagerRefresher(this); + m_agent = agent; + m_app_config = app_config; + if (enable_refresher) + m_refresher = new DeviceManagerRefresher(this); DevPrinterConfigUtil::InitFilePath(resources_dir()); @@ -60,9 +66,14 @@ namespace Slic3r } } + AppConfig* DeviceManager::get_app_config() const + { + return m_app_config ? m_app_config : GUI::wxGetApp().app_config; + } + void DeviceManager::load_local_machines_from_config() { - AppConfig* config = GUI::wxGetApp().app_config; + AppConfig* config = get_app_config(); if (!config) return; const auto local_machines = config->get_local_machines(); @@ -88,9 +99,8 @@ namespace Slic3r } } - void DeviceManager::update_local_machine(const MachineObject& m) + void DeviceManager::update_local_machine(const MachineObject& m, AppConfig* config) { - AppConfig* config = GUI::wxGetApp().app_config; if (config) { if (m.is_lan_mode_printer()) { if (m.has_access_right()) { @@ -111,7 +121,8 @@ namespace Slic3r DeviceManager::~DeviceManager() { - delete m_refresher; + if (m_refresher) + delete m_refresher; for (auto it = localMachineList.begin(); it != localMachineList.end(); it++) { @@ -171,14 +182,22 @@ namespace Slic3r return printer_agent ? printer_agent->get_agent_info().id : ""; } + std::string DeviceManager::get_current_cloud_provider() const + { + const std::string agent_id = get_current_printer_agent_id(); + if (!agent_id.empty()) + return agent_id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER; + return GUI::wxGetApp().get_printer_cloud_provider(); + } + void DeviceManager::EnableMultiMachine(bool enable) { m_agent->enable_multi_machine(enable); m_enable_mutil_machine = enable; } - void DeviceManager::start_refresher() { m_refresher->Start(); } - void DeviceManager::stop_refresher() { m_refresher->Stop(); } + void DeviceManager::start_refresher() { if (m_refresher) m_refresher->Start(); } + void DeviceManager::stop_refresher() { if (m_refresher) m_refresher->Stop(); } void DeviceManager::keep_alive() @@ -265,6 +284,10 @@ namespace Slic3r /* update userMachineList info */ auto it = userMachineList.find(dev_id); if (it != userMachineList.end()) { + // A reused entry may have been created while another printer agent was active. + // The response was obtained through the current agent, so move ownership with + // the entry; otherwise agent-scoped lists hide it after a preset switch. + it->second->printer_agent_id = get_current_printer_agent_id(); if (it->second->get_dev_ip() != dev_ip || it->second->bind_state != bind_state || it->second->bind_sec_link != sec_link || @@ -291,10 +314,15 @@ namespace Slic3r /* update localMachineList */ it = localMachineList.find(dev_id); + AppConfig* config = get_app_config(); + if (it != localMachineList.end()) { // update properties /* ip changed */ obj = it->second; + // A reused LAN entry may have been discovered while another printer agent was + // active. The current discovery message establishes ownership for this agent. + obj->printer_agent_id = get_current_printer_agent_id(); if (obj->get_dev_ip().compare(dev_ip) != 0) { if ( connection_name.empty() ) { @@ -384,7 +412,6 @@ namespace Slic3r obj->m_is_online = true; //load access code - AppConfig* config = Slic3r::GUI::wxGetApp().app_config; if (config) { obj->set_access_code(get_access_code_with_legacy_fallback(config, dev_id, obj->printer_agent_id), false); } @@ -407,7 +434,7 @@ namespace Slic3r fire_lifecycle_event(LifecycleEvent::DeviceDiscovered, ctx); } } - update_local_machine(*obj); + update_local_machine(*obj, config); } catch (...) { ; @@ -422,6 +449,9 @@ namespace Slic3r auto it = localMachineList.find(machine.dev_id); if (it != localMachineList.end()) { obj = it->second; + // insert_local_device is called by the active agent, so a reused entry must follow + // that agent as well; otherwise the agent-scoped printer list hides it. + obj->printer_agent_id = get_current_printer_agent_id(); } else { obj = new MachineObject(this, m_agent, machine.dev_name, machine.dev_id, machine.dev_ip); obj->printer_agent_id = get_current_printer_agent_id(); @@ -439,11 +469,16 @@ namespace Slic3r obj->last_alive = Slic3r::Utils::get_current_time_utc(); obj->set_access_code(access_code, false); - update_local_machine(*obj); + update_local_machine(*obj, get_app_config()); return obj; } + void DeviceManager::update_local_machine(const MachineObject& m) + { + update_local_machine(m, GUI::wxGetApp().app_config); + } + int DeviceManager::query_bind_status(std::string& msg, const std::string& provider) { if (!m_agent) @@ -514,7 +549,7 @@ namespace Slic3r MachineObject* DeviceManager::get_my_machine(std::string dev_id) { - auto list = get_my_machine_list(); + auto list = get_my_machine_list(get_current_printer_agent_id()); auto it = list.find(dev_id); if (it != list.end()) { @@ -551,25 +586,15 @@ namespace Slic3r OnSelectedMachineChanged(previous_selected_machine, selected_machine); } - void DeviceManager::clear_other_devices(const std::string& target_agent_id) + void DeviceManager::clear_other_devices() { - // why: on agent swap, keep "My Devices" but drop the transient "Other Devices" - // Those belong to the previous agent's network scan; the new agent's start_discovery re-populates its own. - // - // Also drop "My Devices" stamped by a different agent than the one we're swapping to - // (target_agent_id, passed by the caller since the live agent hasn't been repointed yet - // at this point): otherwise a device first discovered under agent A survives every swap - // with a stale printer_agent_id, stays hidden from every agent's filtered list, and only - // gets re-tagged if something happens to delete and re-create it (e.g. account logout). - // Dropping it here instead lets the new agent's start_discovery re-insert and re-stamp it - // like any other fresh device. - const auto my = get_my_machine_list(); + // Device entries are now scoped by printer_agent_id when they are presented. Keep + // agent-owned discoveries across a switch so agents without automatic discovery (and + // plugins whose devices have not received an access code yet) do not lose their list. + // Entries without an owner are legacy/unscoped and cannot safely be shown. for (auto it = localMachineList.begin(); it != localMachineList.end();) { - const bool is_my_device = my.find(it->first) != my.end(); - const bool agent_mismatch = !target_agent_id.empty() && it->second && - it->second->printer_agent_id != target_agent_id; - if (!is_my_device || agent_mismatch) + if (!it->second || it->second->printer_agent_id.empty()) { delete it->second; it = localMachineList.erase(it); @@ -585,8 +610,22 @@ namespace Slic3r { BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id << " cur_selected=" << selected_machine; - auto my_machine_list = get_my_machine_list(); + auto my_machine_list = get_my_machine_list(get_current_printer_agent_id()); auto it = my_machine_list.find(dev_id); + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: set_selected_machine lookup dev_id=" << dev_id + << " found=" << (it != my_machine_list.end()) + << " my_machine_count=" << my_machine_list.size() + << " current_agent=" << get_current_printer_agent_id() + << " provider=" << GUI::wxGetApp().get_printer_cloud_provider(); + if (it != my_machine_list.end() && it->second) { + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: target machine dev_id=" << it->second->get_dev_id() + << " printer_agent_id=" << it->second->printer_agent_id + << " connection_type=" << it->second->connection_type() + << " dev_connection_type=" << it->second->dev_connection_type; + } else if (!dev_id.empty()) { + BOOST_LOG_TRIVIAL(warning) << "Orca diagnostic: target machine was not found in the current agent's machine list"; + return false; + } // disconnect last if dev_id difference from previous one auto last_selected = my_machine_list.find(selected_machine); @@ -597,7 +636,9 @@ namespace Slic3r m_agent->disconnect_printer(); } else if (last_selected->second->connection_type() == "cloud") { - m_agent->set_user_selected_machine(""); + const int result = m_agent->set_user_selected_machine(""); + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: cleared previous cloud selection dev_id=" + << selected_machine << " result=" << result; } } @@ -632,11 +673,7 @@ namespace Slic3r m_agent->disconnect_printer(); it->second->reset(); -#if !BBL_RELEASE_TO_PUBLIC - it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false); -#else - it->second->connect(it->second->local_use_ssl); -#endif + it->second->connect(); it->second->set_lan_mode_connection_state(true); } } @@ -649,18 +686,16 @@ namespace Slic3r { // diff dev_id, cloud => set_user_selected_machine(new) BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new cloud machine, dev_id =" << dev_id; - m_agent->set_user_selected_machine(dev_id); + const int result = m_agent->set_user_selected_machine(dev_id); + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: set new cloud selection dev_id=" + << dev_id << " result=" << result; it->second->reset(); } else { BOOST_LOG_TRIVIAL(info) << "set_selected_machine: select new lan machine, dev_id =" << dev_id; it->second->reset(); -#if !BBL_RELEASE_TO_PUBLIC - it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false); -#else - it->second->connect(it->second->local_use_ssl); -#endif + it->second->connect(); it->second->set_lan_mode_connection_state(true); } } @@ -677,6 +712,8 @@ namespace Slic3r selected_machine = dev_id; record_user_last_machine(selected_machine); + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: DeviceManager selection complete selected_machine=" + << selected_machine; return true; } @@ -707,7 +744,9 @@ namespace Slic3r dev_list.push_back(it->first); BOOST_LOG_TRIVIAL(trace) << "add_user_subscribe: " << it->first; } - m_agent->add_subscribe(dev_list); + const int result = m_agent->add_subscribe(dev_list); + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: add_user_subscribe count=" << dev_list.size() + << " result=" << result; } @@ -720,7 +759,9 @@ namespace Slic3r dev_list.push_back(it->first); BOOST_LOG_TRIVIAL(trace) << "del_user_subscribe: " << it->first; } - m_agent->del_subscribe(dev_list); + const int result = m_agent->del_subscribe(dev_list); + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: del_user_subscribe count=" << dev_list.size() + << " result=" << result; } void DeviceManager::subscribe_device_list(std::vector dev_list) @@ -839,7 +880,24 @@ namespace Slic3r try { json j = json::parse(body); - const std::string provider = GUI::wxGetApp().get_printer_cloud_provider(); + + const bool has_request_context = j.contains("provider") && j.contains("agent_id") && j.contains("generation"); + const std::string current_provider = get_current_cloud_provider(); + const std::string provider = j.contains("provider") ? j["provider"].get() + : current_provider; + const std::string agent_id = j.contains("agent_id") ? j["agent_id"].get() + : get_current_printer_agent_id(); + const std::uint64_t generation = j.value("generation", std::uint64_t(0)); + + if (has_request_context && + (provider != current_provider || + agent_id != get_current_printer_agent_id() || + generation != (m_agent ? m_agent->get_user_machine_list_generation() : 0))) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": ignoring stale response provider=" + << provider << " agent_id=" << agent_id + << " generation=" << generation; + return; + } #if !BBL_RELEASE_TO_PUBLIC BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << j; @@ -862,11 +920,14 @@ namespace Slic3r /* update field */ obj = iter->second; obj->set_dev_id(dev_id); + // A device can be rediscovered by a different agent after a preset + // switch while retaining the same MachineObject instance. + obj->printer_agent_id = agent_id; } else { obj = new MachineObject(this, m_agent, "", "", ""); - obj->printer_agent_id = get_current_printer_agent_id(); + obj->printer_agent_id = agent_id; if (m_agent) { obj->set_bind_status(m_agent->get_user_name(provider)); @@ -874,13 +935,20 @@ namespace Slic3r if (obj->get_dev_ip().empty()) { - obj->get_dev_ip() = Slic3r::GUI::wxGetApp().app_config->get("ip_address", dev_id); + if (AppConfig* config = get_app_config()) + obj->get_dev_ip() = config->get("ip_address", dev_id); } userMachineList.insert(std::make_pair(dev_id, obj)); } if (!obj) continue; + // Orca cloud printers are only ever delivered through this REST + // account list; tag them so DeviceManager's cloud/lan branches + // (subscribe + deselect in set_selected_machine) treat them right. + if (provider == ORCA_CLOUD_PROVIDER) + obj->dev_connection_type = "cloud"; + if (!elem["dev_id"].is_null()) obj->set_dev_id(elem["dev_id"].get()); if (!elem["dev_name"].is_null()) @@ -912,6 +980,12 @@ namespace Slic3r acc_code.erase(std::remove(acc_code.begin(), acc_code.end(), '\n'), acc_code.end()); obj->set_access_code(acc_code); } + + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: parsed cloud machine dev_id=" << dev_id + << " name=" << obj->get_dev_name() + << " agent_id=" << obj->printer_agent_id + << " connection_type=" << obj->connection_type() + << " online=" << obj->m_is_online; } //remove MachineObject from userMachineList @@ -927,6 +1001,9 @@ namespace Slic3r iterat++; } } + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: parse_user_print_info complete provider=" << provider + << " parsed_count=" << new_list.size() + << " stored_count=" << userMachineList.size(); } } catch (std::exception& e) @@ -943,25 +1020,29 @@ namespace Slic3r unsigned int http_code; std::string body; int result = m_agent->get_user_print_info(&http_code, &body, provider); + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: get_user_print_info provider=" << provider + << " result=" << result << " http_code=" << http_code + << " body_bytes=" << body.size(); if (result == 0) { // parse_user_print_info and on_machine_alive (SSDP for discovery) both mutate the same userMachineList map. // on_machine_alive mutates the map on the UI thread, do the same for parse_user_print_info. + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: queueing parse_user_print_info on UI thread"; Slic3r::GUI::wxGetApp().CallAfter([this, body]() { parse_user_print_info(body); }); } } void DeviceManager::record_user_last_machine(const std::string& dev_id) { - if (Slic3r::GUI::wxGetApp().app_config) { - Slic3r::GUI::wxGetApp().app_config->set("user_last_selected_machine", dev_id); + if (AppConfig* config = get_app_config()) { + config->set("user_last_selected_machine", dev_id); } } std::string DeviceManager::get_user_last_machine() const { - if (Slic3r::GUI::wxGetApp().app_config) { - const auto& user_last_machine = Slic3r::GUI::wxGetApp().app_config->get("user_last_selected_machine"); + if (AppConfig* config = get_app_config()) { + const auto& user_last_machine = config->get("user_last_selected_machine"); if (!user_last_machine.empty()) { return user_last_machine; } else if (m_agent) { diff --git a/src/slic3r/GUI/DeviceCore/DevManager.h b/src/slic3r/GUI/DeviceCore/DevManager.h index 1f48baba98..1ba4631881 100644 --- a/src/slic3r/GUI/DeviceCore/DevManager.h +++ b/src/slic3r/GUI/DeviceCore/DevManager.h @@ -12,6 +12,7 @@ namespace Slic3r struct BBLocalMachine; class MachineObject; class NetworkAgent; +class AppConfig; namespace GUI { class GUI_App; @@ -24,6 +25,7 @@ class DeviceManager friend class DeviceManagerRefresher; private: NetworkAgent* m_agent{ nullptr }; + AppConfig* m_app_config{ nullptr }; DeviceManagerRefresher* m_refresher{ nullptr }; bool m_enable_mutil_machine = false; @@ -35,11 +37,13 @@ private: std::map userMachineList; /* dev_id -> MachineObject* cloudMachine of User */ public: - DeviceManager(NetworkAgent* agent = nullptr); + DeviceManager(NetworkAgent* agent = nullptr, bool enable_refresher = true, + AppConfig* app_config = nullptr); ~DeviceManager(); public: NetworkAgent* get_agent() const { return m_agent; } + AppConfig* get_app_config() const; void set_agent(NetworkAgent* agent); void start_refresher(); @@ -74,10 +78,9 @@ public: void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); } void clean_user_info(bool keep_local_selection = false); - // target_agent_id: id of the agent being swapped to (empty = no agent-mismatch check, - // just the original "drop Other Devices" behavior). Pass the incoming agent's id, not the - // live one - this runs before the live agent is repointed. - void clear_other_devices(const std::string& target_agent_id = ""); + // Retain agent-owned LAN discoveries across a switch; the active-agent list filter keeps + // entries from other agents hidden while allowing them to reappear when switched back. + void clear_other_devices(); void load_last_machine(); void update_user_machine_list_info(const std::string& provider); @@ -122,6 +125,7 @@ private: void keep_alive(); void check_pushing(); + std::string get_current_cloud_provider() const; void OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state); void OnSelectedMachineChanged(const std::string& pre_dev_id, const std::string& new_dev_id); @@ -134,14 +138,15 @@ public: std::string connection_type, std::string bind_state, std::string version, std::string access_code); static void update_local_machine(const MachineObject& m); + static void update_local_machine(const MachineObject& m, AppConfig* config); }; class DeviceManagerRefresher : public wxObject { - wxTimer* m_timer{ nullptr }; - int m_timer_interval_msec = 5000; + wxTimer* m_timer{nullptr}; + int m_timer_interval_msec = 5000; - DeviceManager* m_manager{ nullptr }; + DeviceManager* m_manager{nullptr}; public: DeviceManagerRefresher(DeviceManager* manger); @@ -154,4 +159,4 @@ public: protected: virtual void on_timer(wxTimerEvent& event); }; -}; \ No newline at end of file +}; diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index 013e411574..7d9abdee12 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -4,6 +4,7 @@ #include "I18N.hpp" #include "libslic3r/Time.hpp" #include "libslic3r/Thread.hpp" +#include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/NetworkAgent.hpp" #include "slic3r/plugin/PluginManager.hpp" #include "slic3r/Utils/NetworkAgentFactory.hpp" @@ -17,6 +18,7 @@ #include "ReleaseNote.hpp" #include #include +#include #include #include #include @@ -54,6 +56,7 @@ #include "DeviceCore/DevStatus.h" #include "DeviceCore/DevUpgrade.h" +#include "IPrinterAgent.hpp" #define CALI_DEBUG #define MINUTE_30 1800000 //ms @@ -380,8 +383,22 @@ NozzleVolumeType convert_to_nozzle_type(const std::string &str) wxString MachineObject::get_printer_type_display_str() const { std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type); + + // Bambu printers use m_resource_file_path + "/printers/" + type_str + ".json", which is a semantic that only works for their profiles. + // For any other profile, we can simply consult preset bundle if the model_id exists. + if (display_name.empty()) { + for (const auto& [vendor_id, vendor] : GUI::wxGetApp().preset_bundle->vendors) { + for (const auto& model : vendor.models) { + if (printer_type == model.model_id) + display_name = model.name; + } + } + } + if (!display_name.empty()) return display_name; + else if (printer_type == "orcasonar") + return "OrcaSonar Printer"; else return _L("Unknown"); } @@ -468,7 +485,7 @@ void MachineObject::set_access_code(std::string code, bool only_refresh) { this->access_code = code; if (only_refresh) { - AppConfig* config = GUI::wxGetApp().app_config; + AppConfig* config = m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config; if (config) { if (is_lan_mode_printer()) { // why: LAN codes are scoped via BBLocalMachine::access_code, keyed by dev_id and @@ -481,7 +498,7 @@ void MachineObject::set_access_code(std::string code, bool only_refresh) // fresh from the cloud API's current response, so there's no cross-agent leakage // risk to guard against there. if (!code.empty()) { - DeviceManager::update_local_machine(*this); + DeviceManager::update_local_machine(*this, config); } else { // Only patch an existing record's code - don't persist a brand-new // never-bound entry just because set_access_code("") was called on it. @@ -1348,7 +1365,6 @@ int MachineObject::command_get_access_code() { return this->publish_json(j); } - int MachineObject::command_request_push_all(bool request_now) { auto curr_time = std::chrono::system_clock::now(); @@ -1480,26 +1496,20 @@ int MachineObject::command_upgrade_module(std::string url, std::string module_ty int MachineObject::command_xyz_abs() { - return this->publish_gcode("G90 \n"); + if (!m_agent) return -1; + return command_with_dialog(m_agent->command_xyz_abs(get_dev_id(), MachineObject::m_sequence_id++, is_lan_mode_printer())); } int MachineObject::command_auto_leveling() { - return this->publish_gcode("G29 \n"); + if (!m_agent) return -1; + return command_with_dialog(m_agent->command_auto_leveling(get_dev_id(), MachineObject::m_sequence_id++, is_lan_mode_printer())); } int MachineObject::command_go_home() { - if (m_support_mqtt_homing) - { - json j; - j["print"]["command"] = "back_to_center"; - j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); - return this->publish_json(j); - } - - // gcode command - return this->is_in_printing() ? this->publish_gcode("G28 X\n") : this->publish_gcode("G28 \n"); + if (!m_agent) return -1; + return command_with_dialog(m_agent->command_go_home(get_dev_id(), this->is_in_printing(), m_support_mqtt_homing, MachineObject::m_sequence_id++, is_lan_mode_printer())); } int MachineObject::command_task_partskip(std::vector part_ids) @@ -1621,23 +1631,14 @@ int MachineObject::command_stop_buzzer() int MachineObject::command_set_bed(int temp) { - if (m_support_mqtt_bet_ctrl) - { - json j; - j["print"]["command"] = "set_bed_temp"; - j["print"]["temp"] = temp; - j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); - return this->publish_json(j); - } - - std::string gcode_str = (boost::format("M140 S%1%\n") % temp).str(); - return this->publish_gcode(gcode_str); + if (!m_agent) return -1; + return command_with_dialog(m_agent->command_set_bed(get_dev_id(), temp, m_support_mqtt_bet_ctrl, MachineObject::m_sequence_id++, is_lan_mode_printer())); } int MachineObject::command_set_nozzle(int temp) { - std::string gcode_str = (boost::format("M104 S%1%\n") % temp).str(); - return this->publish_gcode(gcode_str); + if (!m_agent) return -1; + return command_with_dialog(m_agent->command_set_nozzle(get_dev_id(), temp, MachineObject::m_sequence_id++, is_lan_mode_printer())); } int MachineObject::command_set_nozzle_new(int nozzle_id, int temp) @@ -1742,9 +1743,8 @@ int MachineObject::command_ams_user_settings(bool start_read_opt, bool tray_read int MachineObject::command_ams_calibrate(int ams_id) { - std::string gcode_cmd = (boost::format("M620 C%1% \n") % ams_id).str(); - BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd; - return this->publish_gcode(gcode_cmd); + if (!m_agent) return -1; + return command_with_dialog(m_agent->command_ams_calibrate(get_dev_id(), ams_id, MachineObject::m_sequence_id++, is_lan_mode_printer())); } int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max) @@ -1780,29 +1780,23 @@ int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::s return this->publish_json(j); } -int MachineObject::command_ams_refresh_rfid(std::string tray_id) +int MachineObject::command_ams_refresh_rfid(int ams_id, int slot_id) { - std::string gcode_cmd = (boost::format("M620 R%1% \n") % tray_id).str(); - BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd; - return this->publish_gcode(gcode_cmd); + if (!m_agent) return -1; + return command_with_dialog(m_agent->command_ams_refresh_rfid(get_dev_id(), ams_id, slot_id, MachineObject::m_sequence_id++, is_lan_mode_printer())); } -int MachineObject::command_ams_refresh_rfid2(int ams_id, int slot_id) +int MachineObject::command_start_camera() { - json j; - j["print"]["command"] = "ams_get_rfid"; - j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); - j["print"]["ams_id"] = ams_id; - j["print"]["slot_id"] = slot_id; - return this->publish_json(j); + if (!m_agent) return -1; + return m_agent->command_start_camera(get_dev_id()); } int MachineObject::command_ams_select_tray(std::string tray_id) { - std::string gcode_cmd = (boost::format("M620 P%1% \n") % tray_id).str(); - BOOST_LOG_TRIVIAL(trace) << "ams_debug: gcode_cmd" << gcode_cmd; - return this->publish_gcode(gcode_cmd); + if (!m_agent) return -1; + return command_with_dialog(m_agent->command_ams_select_tray(get_dev_id(), tray_id, MachineObject::m_sequence_id++, is_lan_mode_printer())); } int MachineObject::command_ams_control(std::string action) @@ -1961,47 +1955,10 @@ int MachineObject::command_ams_air_print_detect(bool air_print_detect) int MachineObject::command_axis_control(std::string axis, double unit, double input_val, int speed) { - if (m_support_mqtt_axis_control) - { - int dir = input_val > 0 ? 1 : -1; - // i3-arch printers move the bed for Y/Z, so the on-screen direction is - // reversed — same negation the g-code fallback below applies. - if (!is_core_xy() && (axis.compare("Y") == 0 || axis.compare("Z") == 0)) { - dir = -dir; - } - - json j; - j["print"]["command"] = "xyz_ctrl"; - j["print"]["axis"] = axis; - j["print"]["dir"] = dir; - j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0; - j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); - return this->publish_json(j); - } - - double value = input_val; - if (!is_core_xy()) { - if ( axis.compare("Y") == 0 - || axis.compare("Z") == 0) { - value = -1.0 * input_val; - } - } - - char cmd[256]; - if (axis.compare("X") == 0 - || axis.compare("Y") == 0 - || axis.compare("Z") == 0) { - sprintf(cmd, "M211 S \nM211 X1 Y1 Z1\nM1002 push_ref_mode\nG91 \nG1 %s%0.1f F%d\nM1002 pop_ref_mode\nM211 R\n", axis.c_str(), value * unit, speed); - } - else if (axis.compare("E") == 0) { - sprintf(cmd, "M83 \nG0 %s%0.1f F%d\n", axis.c_str(), value * unit, speed); - } - else { - return -1; - } - - - return this->publish_gcode(cmd); + if (!m_agent) return -1; + return command_with_dialog(m_agent->command_axis_control(get_dev_id(), axis, unit, input_val, speed, is_core_xy(), + m_support_mqtt_axis_control, MachineObject::m_sequence_id++, + is_lan_mode_printer())); } int MachineObject::command_extruder_control(int nozzle_id, double val) @@ -2626,7 +2583,12 @@ void MachineObject::reset() vt_slot.erase(vt_slot.begin() + 1); } } - subtask_ = nullptr; + // why: reset reuses MachineObject, so release its lazy subtask + // before dropping the pointer to prevent reconnect leaks. + if (subtask_) { + delete subtask_; + subtask_ = nullptr; + } has_extra_flow_type = false; m_partskip_ids.clear(); } @@ -2644,15 +2606,63 @@ void MachineObject::set_print_state(std::string status) } } -int MachineObject::connect(bool use_openssl) +// why: printer agents can report progress without BBL cloud task identity. +void MachineObject::update_print_progress(const json& value) +{ + if (value.is_string()) { + const std::string progress = value.get(); + int parsed_progress; + const auto result = std::from_chars(progress.data(), progress.data() + progress.size(), parsed_progress); + if (result.ec != std::errc{} || result.ptr != progress.data() + progress.size()) + return; + mc_print_percent = parsed_progress; + } else if (value.is_number_integer()) + mc_print_percent = value.get(); + else + return; + + if (BBLSubTask* curr_task = get_subtask()) + curr_task->task_progress = mc_print_percent; +} + +int MachineObject::connect() { if (get_dev_ip().empty()) return -1; - std::string username = "bblp"; + std::string username = m_agent ? m_agent->default_lan_username() : std::string(); std::string password = get_access_code(); + std::string port; + std::string input = get_dev_ip(); + + const bool use_ssl = input.rfind("https", 0) == 0; + + // This strips out the http/https prefix + std::string host = Http::get_host_from_url(input, &port); + std::string ca_file; + + if (GUI::wxGetApp().preset_bundle) { + const auto& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config; + if (port.empty()) + port = config.opt_string("printhost_port"); + ca_file = config.opt_string("printhost_cafile"); + } + + if (host.empty()) + host = get_dev_ip(); + + if (m_agent) { try { - return m_agent->connect_printer(get_dev_id(), get_dev_ip(), username, password, use_openssl); + PrinterConnectionParams params{ + get_dev_id(), + host, + port, + username, + password, + use_ssl, + ca_file + }; + return m_agent->connect_printer(params); } catch (...) { ; } @@ -2768,6 +2778,14 @@ int MachineObject::publish_json(const json& json_item, int qos, int flag) BOOST_LOG_TRIVIAL(error) << "publish_json: " << json_item.dump() << " code: " << rtn; } + // why: the agent is the only thing that knows what it can translate, so it reports + // not-supported in its return value and this - the single funnel every command_* builder + // passes through - is the one place that turns it into something the user sees. No list of + // unsupported commands is needed anywhere: an agent that has no case for a command says so. + if (rtn == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED || rtn == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE) { + show_unsupported_dlg(rtn); + } + return rtn; } @@ -3086,6 +3104,13 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ } } catch (...) {} + try { + if (j.contains("info")) + parse_new_info2(j["info"]); + } catch (...) { + BOOST_LOG_TRIVIAL(error) << "parse_json: failed to parse OrcaSonar capability info"; + } + try { if (auto ptr = m_fila_system->GetAmsFirmwareSwitch().lock()) { ptr->ParseFirmwareSwitch(j); @@ -3338,10 +3363,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ print_type = jj["print_type"].get(); } if (jj.contains("mc_percent")) { - if (jj["mc_percent"].is_string()) - mc_print_percent = stoi(j["print"]["mc_percent"].get()); - else if (jj["mc_percent"].is_number_integer()) - mc_print_percent = j["print"]["mc_percent"].get(); + update_print_progress(jj["mc_percent"]); } if (jj.contains("mc_print_sub_stage")) { if (jj["mc_print_sub_stage"].is_number_integer()) @@ -3511,6 +3533,9 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ this->task_id_ = jj["task_id"].get(); } + if (jj.contains("thumbnail_url") && jj["thumbnail_url"].is_string()) + m_agent_thumbnail_url = jj["thumbnail_url"].get(); + if (jj.contains("job_attr")) { int jobAttr = jj["job_attr"].get(); jobState_ = get_flag_bits(jobAttr, 4, 4); @@ -3556,7 +3581,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ update_slice_info(jj["project_id"].get(), jj["profile_id"].get(), jj["subtask_id"].get(), plate_index); BBLSubTask* curr_task = get_subtask(); if (curr_task) { - curr_task->task_progress = mc_print_percent; curr_task->printing_status = print_status; curr_task->task_id = jj["subtask_id"].get(); } @@ -3859,6 +3883,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ has_ipcam = true; } else { has_ipcam = false; + webcam_stream_url.clear(); } } if (ipcam.contains("resolution")) { @@ -3893,6 +3918,9 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ liveview_local = local_rtsp_url.empty() ? LVL_None : local_rtsp_url == "disable" ? LVL_Disable : boost::algorithm::starts_with(local_rtsp_url, "rtsps") ? LVL_Rtsps : LVL_Rtsp; } + if (ipcam.contains("stream_url") && ipcam["stream_url"].is_string()) { + webcam_stream_url = ipcam["stream_url"].get(); + } if (ipcam.contains("tutk_server")) { tutk_state = ipcam["tutk_server"].get(); } @@ -4651,7 +4679,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_ if (diff.count() > 10.0f) { BOOST_LOG_TRIVIAL(trace) << "parse_json timeout = " << diff.count(); } - DeviceManager::update_local_machine(*this); + DeviceManager::update_local_machine(*this, m_manager ? m_manager->get_app_config() : GUI::wxGetApp().app_config); return 0; } @@ -5460,6 +5488,86 @@ void MachineObject::parse_new_info(json print) } } +void MachineObject::parse_new_info2(const json& info) +{ + if (!info.is_object() || info.value("command", "") != "get_capabilities") + return; + + const auto capabilities_it = info.find("capabilities"); + if (capabilities_it == info.end() || !capabilities_it->is_object()) + return; + const auto flags_it = capabilities_it->find("flags"); + if (flags_it == capabilities_it->end() || !flags_it->is_object()) + return; + + const json& flags = *flags_it; + BOOST_LOG_TRIVIAL(info) << "parse_new_info2: OrcaSonar capability flags=" << flags.dump(); + + auto parse_bool = [&flags](const char* name, bool& target) { + const auto it = flags.find(name); + if (it != flags.end() && it->is_boolean()) + target = it->get(); + }; + + parse_bool("support_send_to_sd", is_support_send_to_sdcard); + parse_bool("support_filament_backup", is_support_filament_backup); + parse_bool("support_update_remain", is_support_update_remain); + parse_bool("support_auto_recovery_step_loss", is_support_auto_recovery_step_loss); + parse_bool("support_ams_humidity", is_support_ams_humidity); + parse_bool("support_prompt_sound", is_support_prompt_sound); + parse_bool("support_filament_tangle_detect", is_support_filament_tangle_detect); + parse_bool("support_1080dpi", is_support_1080dpi); + parse_bool("support_cloud_print_only", is_support_cloud_print_only); + parse_bool("support_command_ams_switch", is_support_command_ams_switch); + parse_bool("support_mqtt_alive", is_support_mqtt_alive); + parse_bool("support_motor_noise_cali", is_support_motor_noise_cali); + parse_bool("support_timelapse", is_support_timelapse); + parse_bool("support_user_preset", is_support_user_preset); + parse_bool("support_refresh_nozzle", is_support_refresh_nozzle); + parse_bool("support_flow_calibration", is_support_flow_calibration); + parse_bool("support_build_plate_marker_detect", is_support_build_plate_marker_detect); + parse_bool("support_nozzle_blob_detect", is_support_nozzle_blob_detection); + + if (!m_manager->IsMultiMachineEnabled() && !is_support_agora) + parse_bool("support_tunnel_mqtt", is_support_tunnel_mqtt); + + const auto bed_leveling_it = flags.find("support_bed_leveling"); + if (bed_leveling_it != flags.end() && bed_leveling_it->is_number_integer()) + is_support_bed_leveling = bed_leveling_it->get(); + + auto copy_bool = [&flags](json& target, const char* name) { + const auto it = flags.find(name); + if (it != flags.end() && it->is_boolean()) + target[name] = *it; + }; + + // The capability manifest uses an object for this range, while the legacy + // DeviceCore parser consumes a boolean plus a two-element range array. + json device_config; + copy_bool(device_config, "support_chamber"); + copy_bool(device_config, "support_first_layer_inspect"); + copy_bool(device_config, "support_ai_monitoring"); + copy_bool(device_config, "support_lidar_calibration"); + const auto chamber_edit_it = flags.find("support_chamber_temp_edit"); + if (chamber_edit_it != flags.end() && chamber_edit_it->is_boolean()) { + device_config["support_chamber_temp_edit"] = *chamber_edit_it; + } else if (chamber_edit_it != flags.end() && chamber_edit_it->is_object()) { + const auto min_it = chamber_edit_it->find("min"); + const auto max_it = chamber_edit_it->find("max"); + if (min_it != chamber_edit_it->end() && max_it != chamber_edit_it->end() && min_it->is_number() && max_it->is_number()) { + device_config["support_chamber_temp_edit"] = true; + device_config["support_chamber_temp_edit_range"] = {*min_it, *max_it}; + } + } + + json fan_config; + copy_bool(fan_config, "support_aux_fan"); + copy_bool(fan_config, "support_chamber_fan"); + + m_config->ParseConfig(device_config); + m_fan->ParseV2_0(fan_config); +} + static bool is_hex_digit(char c) { return std::isxdigit(static_cast(c)) != 0; } @@ -5965,6 +6073,15 @@ bool MachineObject::HasAms() const return m_fila_system->HasAms(); } +int MachineObject::command_with_dialog(int cmd_result) +{ + if (!m_agent) + return -1; + if (cmd_result == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED || cmd_result == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE) + show_unsupported_dlg(cmd_result); + return cmd_result; +} + void change_the_opacity(wxColour& colour) { if (colour.Alpha() == 255) { diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp index ca3b7c3c44..b69e2f8f5a 100644 --- a/src/slic3r/GUI/DeviceManager.hpp +++ b/src/slic3r/GUI/DeviceManager.hpp @@ -18,6 +18,7 @@ #include "boost/bimap/bimap.hpp" #include "libslic3r/calib.hpp" #include "libslic3r/Utils.hpp" +#include "slic3r/Utils/PrinterNetworkTypes.hpp" #include "DeviceCore/DevDefs.h" #include "DeviceCore/DevConfigUtil.h" @@ -100,7 +101,6 @@ struct DevPrintTaskRatingInfo; // given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware. bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter); - class MachineObject { private: @@ -307,7 +307,7 @@ public: bool ams_support_virtual_tray { true }; time_t ams_user_setting_start = 0; time_t ams_switch_filament_start = 0; - AmsStatusMain ams_status_main; + AmsStatusMain ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_IDLE; int ams_status_sub; int ams_version = 0; @@ -547,30 +547,12 @@ public: bool xcam_first_layer_inspector { false }; time_t xcam_first_layer_hold_start = 0; std::string local_rtsp_url; + std::string webcam_stream_url; std::string tutk_state; - enum LiveviewLocal { - LVL_None, - LVL_Disable, - LVL_Local, - LVL_Rtsps, - LVL_Rtsp - } liveview_local{ LVL_None }; - enum LiveviewRemote { - LVR_None, - LVR_Tutk, - LVR_Agora, - LVR_TutkAgora - } liveview_remote{ LVR_None }; - enum FileLocal { - FL_None, - FL_Local - } file_local{ FL_None }; - enum FileRemote { - FR_None, - FR_Tutk, - FR_Agora, - FR_TutkAgora - } file_remote{ FR_None }; + LiveviewLocal liveview_local{ LiveviewLocal::LVL_None }; + LiveviewRemote liveview_remote{ LiveviewRemote::LVR_None}; + FileLocal file_local{ FileLocal::FL_None }; + FileRemote file_remote{ FileRemote::FR_None }; enum PlateMakerDectect : int { @@ -696,7 +678,7 @@ public: /* machine mqtt apis */ - int connect(bool use_openssl = true); + int connect(); int disconnect(); json_diff print_json; @@ -708,6 +690,8 @@ public: std::string subtask_id_; std::string job_id_; std::string last_subtask_id_; + // note: printer-agent-supplied thumbnail url, empty when the agent supplies none. + std::string m_agent_thumbnail_url; BBLSliceInfo* slice_info {nullptr}; boost::thread* get_slice_info_thread { nullptr }; boost::thread* get_model_task_thread { nullptr }; @@ -765,6 +749,7 @@ public: int command_set_printer_nozzle(std::string nozzle_type, float diameter); int command_set_printer_nozzle2(int id, std::string nozzle_type, float diameter); int command_get_access_code(); + int command_start_camera(); int command_ack_proceed(json& proceed); int command_purification_disable(); int command_dont_remind_next_time(json& mqtt_guard_json); @@ -811,8 +796,7 @@ public: int command_ams_calibrate(int ams_id); int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max); int command_ams_select_tray(std::string tray_id); - int command_ams_refresh_rfid(std::string tray_id); - int command_ams_refresh_rfid2(int ams_id, int slot_id); + int command_ams_refresh_rfid(int ams_id, int slot_id); int command_ams_control(std::string action); int command_ams_drying_stop(); int command_start_extrusion_cali(int tray_index, int nozzle_temp, int bed_temp, float max_volumetric_speed, std::string setting_id = ""); @@ -894,6 +878,7 @@ public: static bool is_in_printing_status(std::string status); void set_print_state(std::string status); + void update_print_progress(const json& value); bool is_connected(); bool is_connecting(); @@ -962,6 +947,7 @@ public: /*for parse new info*/ bool check_enable_np(const json& print) const; void parse_new_info(json print); + void parse_new_info2(const json& info); int get_flag_bits(std::string str, int start, int count = 1) const; uint32_t get_flag_bits_no_border(std::string str, int start_idx, int count = 1) const; int get_flag_bits(int num, int start, int count = 1, int base = 10) const; @@ -990,7 +976,7 @@ public: void command_set_save_remote_print_file_to_storage(bool save); private: - + int command_with_dialog(int cmd_result); /* xcam door open check*/ bool is_support_door_open_check = false; DoorOpenCheckState xcam_door_open_check = DoorOpenCheckState::DOOR_OPEN_CHECK_DISABLE; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 72849dd78e..27135a44d7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -4,6 +4,7 @@ #include "libslic3r/Platform.hpp" #include "GUI_App.hpp" #include "Shortcuts.hpp" +#include "DeviceCore/DevConfigUtil.h" #include "BindDialog.hpp" #include "DeviceManager.hpp" #include "HMS.hpp" @@ -24,7 +25,6 @@ #include #include #include -#include #include #include @@ -41,9 +41,11 @@ #include #include #include +#include #include #include #include + #include #include #include @@ -88,7 +90,6 @@ #include "libslic3r/Thread.hpp" #include "libslic3r/miniz_extension.hpp" #include "libslic3r/Utils.hpp" -#include "libslic3r/Color.hpp" #include "slic3r/plugin/PluginManager.hpp" #include "slic3r/plugin/host/PluginHostUi.hpp" #include "slic3r/plugin/PythonInterpreter.hpp" @@ -108,19 +109,18 @@ #include "../Utils/PrintHost.hpp" #include "../Utils/Process.hpp" #include "../Utils/wxInspectorPlugins/Registration.hpp" -#include "../Utils/MacDarkMode.hpp" #include "../Utils/Http.hpp" #include "../Utils/InstanceID.hpp" #include "../Utils/UndoRedo.hpp" #include "slic3r/Config/Snapshot.hpp" #include "Preferences.hpp" #include "Tab.hpp" -#include "SysInfoDialog.hpp" #include "UpdateDialogs.hpp" #include "Mouse3DController.hpp" #include "RemovableDriveManager.hpp" #include "InstanceCheck.hpp" #ifdef __APPLE__ +#include "../Utils/MacDarkMode.hpp" #include "DeepLinkHandlerMac.h" #endif #include "NotificationManager.hpp" @@ -129,8 +129,6 @@ #include "PrintHostDialogs.hpp" #include "NetworkPluginDialog.hpp" #include "DesktopIntegrationDialog.hpp" -#include "SendSystemInfoDialog.hpp" -#include "ParamsDialog.hpp" #include "KBShortcutsDialog.hpp" #include "DownloadProgressDialog.hpp" #include "TroubleshootDialog.hpp" @@ -141,7 +139,6 @@ #include "Widgets/ProgressDialog.hpp" //BBS: DailyTip and UserGuide Dialog -#include "WebDownPluginDlg.hpp" #include "WebGuideDialog.hpp" #include "ReleaseNote.hpp" #include "PrivacyUpdateDialog.hpp" @@ -2419,34 +2416,23 @@ GUI_App::~GUI_App() bool GUI_App::is_blocking_printing(MachineObject *obj_) { - DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager(); - if (!dev) return true; - std::string target_model; - if (obj_ == nullptr) { - obj_ = dev->get_selected_machine(); - if (obj_) { - target_model = obj_->printer_type; - } - } else { - target_model = obj_->printer_type; - } - - if (!obj_) - { - return false; - } - PresetBundle *preset_bundle = wxGetApp().preset_bundle; - std::string source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); + const std::string source_model = preset_bundle + ? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) + : std::string(); + return is_blocking_printing(obj_, source_model); +} - if (source_model != target_model) { - std::vector compatible_machine = obj_->get_compatible_machine(); - vector::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model); - if (it == compatible_machine.end()) { - return true; - } - } - return false; +bool GUI_App::is_blocking_printing(MachineObject *obj_, const std::string& source_model) +{ + DeviceManager *dev = getDeviceManager(); + if (!dev) return true; + if (obj_ == nullptr) + obj_ = dev->get_selected_machine(); + if (!obj_) + return false; + + return !DevPrinterConfigUtil::is_printer_model_compatible(source_model, *obj_); } // If formatted for github, plaintext with OpenGL extensions enclosed into
. @@ -4007,20 +3993,16 @@ void GUI_App::set_live_printer_agent(std::shared_ptr agent) m_agent->set_user_selected_machine(""); // note: belt-and-suspenders (precedent: DeviceManagerRefresher::on_timer) dev->OnSelectedMachineLost(); // why: clear stale sidebar sync-status / AMS - // why: drop stale LAN discoveries; keep My Devices, but only those belonging to the - // agent we're about to swap to, so a device stamped by the outgoing agent doesn't - // linger hidden - the new agent's start_discovery re-inserts and re-stamps it fresh. - // agent is null when clearing the live agent entirely (e.g. plugin unload); there's no - // target to filter against then, so fall back to the original "keep all My Devices" - // behavior rather than guessing. - dev->clear_other_devices(agent ? agent->get_agent_info().id : std::string()); + // why: retain agent-owned LAN discoveries so agents without automatic discovery (for + // example the Moonraker-based Qidi/Snapmaker agents) can reuse them after a switch. + dev->clear_other_devices(); } m_agent->set_printer_agent(agent); sidebar().update_all_preset_comboboxes(); } -std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id) +std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id) const { if (!stored_id.empty()) return stored_id; @@ -4056,6 +4038,7 @@ void GUI_App::switch_printer_agent() std::string log_dir = data_dir(); std::string cloud_agent_id = agent_info.id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << agent_info.id; std::shared_ptr cloud_agent = m_agent->get_cloud_agent(cloud_agent_id); // Create new printer agent via registry @@ -4069,8 +4052,10 @@ void GUI_App::switch_printer_agent() return; } - // The factory caches agents per ID, so an identical pointer means the agent type is unchanged. - if (m_agent->get_printer_agent() == new_printer_agent) { + // Compare the registered IDs, not only the implementation pointer. Different registry IDs + // may intentionally be backed by the same implementation object (especially for plugins). + const auto current_printer_agent = m_agent->get_printer_agent(); + if (current_printer_agent && current_printer_agent->get_agent_info().id == effective_agent_id) { // Orca: the agent type is unchanged (e.g. switching between two Moonraker/Klipper // printer presets), so the selected machine and the agent's cached device_info still // point at the previously active printer preset. Re-select the machine when the new @@ -5053,14 +5038,16 @@ bool GUI_App::is_user_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER* return false; } -const std::string& GUI_App::get_printer_cloud_provider() const +std::string GUI_App::get_printer_cloud_provider() const { - // Orca todo: this need to be revisted. currently it is mainly used for device manager and related clausses and only bambu machines use them. - // - return BBL_CLOUD_PROVIDER; + const std::string agent_id = resolve_printer_agent_id( + preset_bundle ? preset_bundle->printers.get_edited_preset().config.opt_string("printer_agent") + : std::string()); + return agent_id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER; } + bool GUI_App::check_login(const std::string& provider/* = ORCA_CLOUD_PROVIDER*/) { bool result = false; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index a8fa8432b1..da37c79242 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -24,9 +24,6 @@ #include #include -#include -#include - //#define BBL_HAS_FIRST_PAGE 1 #define STUDIO_INACTIVE_TIMEOUT 15*60*1000 #define LOG_FILES_MAX_NUM 30 @@ -373,6 +370,7 @@ public: EAppMode get_app_mode() const { return m_app_mode; } Slic3r::DeviceManager* getDeviceManager() { return m_device_manager; } bool is_blocking_printing(MachineObject *obj_ = nullptr); + bool is_blocking_printing(MachineObject *obj_, const std::string& source_model); Slic3r::TaskManager* getTaskManager() { return m_task_manager; } HMSQuery* get_hms_query() { return hms_query; } NetworkAgent* getAgent() { return m_agent; } @@ -380,7 +378,7 @@ public: // Reconcile the live printer agent with the stored preset selection. void switch_printer_agent(); - std::string resolve_printer_agent_id(const std::string& stored_id); + std::string resolve_printer_agent_id(const std::string& stored_id) const; // ORCA TODO: in the future, bbl presets should specify "bbl" printer agent id // then, all resolve and canonical would just be ORCA<->"" std::string canonical_printer_agent_id(const std::string& picked_id); @@ -511,7 +509,7 @@ public: bool check_login(const std::string& provider = ORCA_CLOUD_PROVIDER); void get_login_info(const std::string& provider = ORCA_CLOUD_PROVIDER); bool is_user_login(const std::string& provider = ORCA_CLOUD_PROVIDER); - const std::string& get_printer_cloud_provider() const; + std::string get_printer_cloud_provider() const; void request_user_login(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER); void request_user_handle(int online_login = 0, const std::string& provider = ORCA_CLOUD_PROVIDER); diff --git a/src/slic3r/GUI/IMediaController.hpp b/src/slic3r/GUI/IMediaController.hpp new file mode 100644 index 0000000000..3b4cbf7d24 --- /dev/null +++ b/src/slic3r/GUI/IMediaController.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace Slic3r { namespace GUI { + +class IMediaController +{ +public: + virtual ~IMediaController() = default; + + virtual void Load(wxURI url) = 0; + + virtual void Play() = 0; + + virtual void Stop() = 0; + + virtual wxMediaState GetState() { return wxMediaState{}; } + + virtual int GetLastError() const { return {}; }; + + virtual wxSize GetVideoSize() const { return {}; }; +}; + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/PrintJob.cpp b/src/slic3r/GUI/Jobs/PrintJob.cpp index 76e45eb1d7..2dea8801b5 100644 --- a/src/slic3r/GUI/Jobs/PrintJob.cpp +++ b/src/slic3r/GUI/Jobs/PrintJob.cpp @@ -15,6 +15,7 @@ #include "slic3r/Utils/FileTransferUtils.hpp" #include "slic3r/Utils/BBLNetworkPlugin.hpp" +#include "NetworkAgent.hpp" namespace Slic3r { namespace GUI { @@ -216,7 +217,7 @@ void PrintJob::process(Ctl &ctl) params.dev_ip = m_dev_ip; params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; params.use_ssl_for_mqtt = m_local_use_ssl; - params.username = "bblp"; + params.username = m_agent->default_lan_username(); params.password = m_access_code; // check access code and ip address diff --git a/src/slic3r/GUI/Jobs/SendJob.cpp b/src/slic3r/GUI/Jobs/SendJob.cpp index 505475e4a9..a06144878c 100644 --- a/src/slic3r/GUI/Jobs/SendJob.cpp +++ b/src/slic3r/GUI/Jobs/SendJob.cpp @@ -127,7 +127,7 @@ void SendJob::process(Ctl &ctl) if (m_is_check_mode) { PrintParams verify_params; verify_params.dev_ip = m_dev_ip; - verify_params.username = "bblp"; + verify_params.username = agent->default_lan_username(); verify_params.password = m_access_code; verify_params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; verify_params.use_ssl_for_mqtt = m_local_use_ssl; @@ -219,7 +219,7 @@ void SendJob::process(Ctl &ctl) // local print access params.dev_ip = m_dev_ip; - params.username = "bblp"; + params.username = agent->default_lan_username(); params.password = m_access_code; params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; params.use_ssl_for_mqtt = m_local_use_ssl; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 343d95d3ff..08da15d3e3 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -4377,7 +4377,7 @@ void MainFrame::load_printer_url() if (auto *device_manager = wxGetApp().getDeviceManager()) { auto *machine = device_manager->get_selected_machine(); if (!machine) { - auto machines = device_manager->get_my_machine_list(); + auto machines = device_manager->get_my_machine_list(device_manager->get_current_printer_agent_id()); if (machines.size() == 1) machine = machines.begin()->second; } diff --git a/src/slic3r/GUI/MediaPlayCtrl.cpp b/src/slic3r/GUI/MediaPlayCtrl.cpp index 557d859cf7..c854683368 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.cpp +++ b/src/slic3r/GUI/MediaPlayCtrl.cpp @@ -1,4 +1,6 @@ #include "MediaPlayCtrl.h" +#include "WebMediaController.hpp" +#include "IPrinterAgent.hpp" #include "Widgets/Button.hpp" #include "Widgets/CheckBox.hpp" #include "Widgets/Label.hpp" @@ -7,6 +9,7 @@ #include "DeviceManager.hpp" #include "DeviceCore/DevConfigUtil.h" #include "slic3r/Utils/NetworkAgent.hpp" +#include "slic3r/Utils/NetworkAgentFactory.hpp" #include "libslic3r/Thread.hpp" #include "libslic3r/AppConfig.hpp" #include "I18N.hpp" @@ -15,11 +18,15 @@ #include "slic3r/Utils/BBLNetworkPlugin.hpp" +#include + #include #include #include #include #include +#include +#include #undef pid_t #include #ifdef __WIN32__ @@ -48,6 +55,7 @@ namespace GUI { MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos, const wxSize &size) : wxPanel(parent, wxID_ANY, pos, size) , m_media_ctrl(media_ctrl) + , m_active_media_controller(media_ctrl) { SetLabel("MediaPlayCtrl"); SetBackgroundColour(*wxWHITE); @@ -94,7 +102,10 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const w }); m_button_play->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto &e) { TogglePlay(); }); - m_button_play->Bind(wxEVT_RIGHT_UP, [this](auto & e) { m_media_ctrl->Play(); }); + m_button_play->Bind(wxEVT_RIGHT_UP, [this](auto & e) { + if (m_active_media_controller) + m_active_media_controller->Play(); + }); // Orca: live-view FAQ link binding removed (vendor URL) Bind(wxEVT_RIGHT_UP, [this](auto & e) { @@ -139,9 +150,12 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const w MediaPlayCtrl::~MediaPlayCtrl() { + if (m_webrtc_ctrl) + m_webrtc_ctrl->Stop(); + m_media_ctrl->EndExternalStream(); { boost::unique_lock lock(m_mutex); - m_tasks.push_back(""); + m_tasks.push_back({"", nullptr}); m_cond.notify_all(); } while (!m_thread.try_join_for(boost::chrono::milliseconds(10))) { @@ -151,59 +165,144 @@ MediaPlayCtrl::~MediaPlayCtrl() BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << this; } +void MediaPlayCtrl::SetWebMediaController(IMediaController *ctrl) +{ + m_web_ctrl = ctrl; + set_active_media_controller(current_mode()); +} + +void MediaPlayCtrl::set_active_media_controller(CameraStreamMode mode) +{ + switch (mode) { + case CameraStreamMode::http_snapshot: + if (auto *web_ctrl = dynamic_cast(m_web_ctrl)) + web_ctrl->set_mode(mode); + m_active_media_controller = m_web_ctrl; + break; + case CameraStreamMode::webrtc: + if (!m_webrtc_ctrl) { + m_webrtc_ctrl = std::make_unique( + [this](const wxImage& image, wxSize size) { m_media_ctrl->SetExternalFrame(image, size); }, + [this, token = std::weak_ptr(m_token)](WebRtcMediaController::Status status) { + if (token.expired()) + return; + CallAfter([this, status] { on_webrtc_status(status); }); + }); + } + m_active_media_controller = m_webrtc_ctrl.get(); + break; + default: + m_active_media_controller = m_media_ctrl; + break; + } +} + +CameraStreamMode MediaPlayCtrl::current_mode() const +{ + auto agent = wxGetApp().getAgent(); + return agent ? agent->get_camera_stream_mode() : CameraStreamMode::none; +} + void MediaPlayCtrl::SetMachineObject(MachineObject* obj) { - std::string machine = obj ? obj->get_dev_id() : ""; - if (obj) { - m_camera_exists = obj->has_ipcam; - m_dev_ver = obj->get_ota_version(); - m_lan_mode = obj->is_lan_mode_printer(); - m_lan_proto = obj->liveview_local; - m_remote_proto = obj->get_liveview_remote(); - m_lan_ip = obj->get_dev_ip(); - m_lan_passwd = obj->get_access_code(); - m_device_busy = obj->is_camera_busy_off(); - m_tutk_state = obj->tutk_state; - - if (DevPrinterConfigUtil::get_printer_series_str(obj->printer_type) == "series_o" && BBLNetworkPlugin::instance().use_legacy_network()) { - // Legacy plugin cannot support remote play for H2D, force using local mode - m_remote_proto = MachineObject::LVR_None; + const CameraStreamMode mode = current_mode(); + if (mode != m_last_mode) { + if (m_last_state != MEDIASTATE_IDLE) { + m_failed_code = 0; // a mode switch is not a stream failure - don't arm back-off + Stop(" "); } - } else { - m_camera_exists = false; - m_lan_mode = false; - m_lan_proto = MachineObject::LVL_None; - m_lan_ip.clear(); - m_lan_passwd.clear(); - m_dev_ver.clear(); - m_tutk_state.clear(); - m_remote_proto = 0; - m_device_busy = false; + m_last_mode = mode; } - Enable(obj && obj->is_info_ready() && obj->m_push_count > 0); - if (machine == m_machine) { - if (m_last_state == MEDIASTATE_IDLE && IsEnabled()) - Play(); + set_active_media_controller(mode); + + const bool uses_local_camera_url = mode == CameraStreamMode::http || mode == CameraStreamMode::https || + mode == CameraStreamMode::http_snapshot || mode == CameraStreamMode::rtsp; + const bool uses_webrtc = mode == CameraStreamMode::webrtc; + const std::string machine = obj ? obj->get_dev_id() : ""; + bool changed = false; + + if (uses_local_camera_url) { + auto agent = wxGetApp().getAgent(); + std::string url = agent ? agent->get_local_camera_stream_url() : ""; + m_camera_exists = !url.empty(); + Enable(obj && m_camera_exists); + changed = machine != m_machine || url != m_agent_camera_url; + m_agent_camera_url = url; + m_url = from_u8(url); + } else if (uses_webrtc) { + m_camera_exists = obj != nullptr; + Enable(obj != nullptr); + changed = machine != m_machine; + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::SetMachineObject webrtc: changed=" << changed + << " last_state=" << m_last_state << " web_user_stopped=" << m_web_user_stopped; + m_url.clear(); + m_agent_camera_url.clear(); + } else { + if (obj) { + m_camera_exists = obj->has_ipcam; + m_dev_ver = obj->get_ota_version(); + m_lan_mode = obj->is_lan_mode_printer(); + m_lan_proto = obj->liveview_local; + m_remote_proto = obj->get_liveview_remote(); + m_lan_ip = obj->get_dev_ip(); + m_lan_passwd = obj->get_access_code(); + m_device_busy = obj->is_camera_busy_off(); + m_tutk_state = obj->tutk_state; + + if (DevPrinterConfigUtil::get_printer_series_str(obj->printer_type) == "series_o" && BBLNetworkPlugin::instance().use_legacy_network()) { + // Legacy plugin cannot support remote play for H2D, force using local mode + m_remote_proto = LiveviewRemote::LVR_None; + } + } else { + m_camera_exists = false; + m_lan_mode = false; + m_lan_proto = LiveviewLocal::LVL_None; + m_lan_ip.clear(); + m_lan_passwd.clear(); + m_dev_ver.clear(); + m_tutk_state.clear(); + m_remote_proto = 0; + m_device_busy = false; + } + Enable(obj && obj->is_info_ready() && obj->m_push_count > 0); + if (machine == m_machine) + return; + + m_machine = machine; + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl switch machine: " << m_machine; + m_disable_lan = false; + m_failed_retry = 0; + m_last_failed_codes.clear(); + m_last_user_play = wxDateTime::Now(); + std::string stream_url; + if (get_stream_url(&stream_url)) { + m_streaming = boost::algorithm::contains(stream_url, "device=" + m_machine); + } else { + m_streaming = false; + } + if (m_last_state != MEDIASTATE_IDLE) + Stop(" "); + if (m_next_retry.IsValid()) // Try open 2 seconds later, to avoid state conflict + m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(2); + else + SetStatus("", false); return; } + m_machine = machine; - BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl switch machine: " << m_machine; - m_disable_lan = false; - m_failed_retry = 0; - m_last_failed_codes.clear(); - m_last_user_play = wxDateTime::Now(); - std::string stream_url; - if (get_stream_url(&stream_url)) { - m_streaming = boost::algorithm::contains(stream_url, "device=" + m_machine); - } else { - m_streaming = false; + if (!changed) + return; + + // A genuine target switch is not a stream failure and should clear the + // manual-stop state before the new target is allowed to play. + m_web_user_stopped = false; + if (uses_local_camera_url) { + m_failed_code = 0; + m_failed_retry = 0; + m_next_retry = wxDateTime(); } if (m_last_state != MEDIASTATE_IDLE) Stop(" "); - if (m_next_retry.IsValid()) // Try open 2 seconds later, to avoid state conflict - m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(2); - else - SetStatus("", false); } wxString hide_id_middle_string(wxString const &str, size_t offset = 0, size_t length = -1) @@ -254,13 +353,71 @@ void refresh_agora_url(char const* device, char const* dev_ver, char const* chan void MediaPlayCtrl::Play() { - if (!m_next_retry.IsValid() || wxDateTime::Now() < m_next_retry) + if ((m_next_retry.IsValid() && wxDateTime::Now() < m_next_retry) || !IsShownOnScreen() || m_last_state != MEDIASTATE_IDLE) return; - if (!IsShownOnScreen()) - return; - if (m_last_state != MEDIASTATE_IDLE) { + + const CameraStreamMode mode = current_mode(); + set_active_media_controller(mode); + m_last_mode = mode; + auto agent = wxGetApp().getAgent(); + + auto printer_agent = agent ? agent->get_printer_agent() : nullptr; + const bool is_bbl = (printer_agent ? printer_agent->get_agent_info().id : "") == BBL_PRINTER_AGENT_ID; + + if (!is_bbl) { + const bool is_webrtc = mode == CameraStreamMode::webrtc; + const bool is_snapshot = mode == CameraStreamMode::http_snapshot; + const bool is_http_stream = mode == CameraStreamMode::http || mode == CameraStreamMode::https || mode == CameraStreamMode::rtsp; + if (!is_webrtc && !is_snapshot && !is_http_stream) + return; + + auto *webrtc_ctrl = is_webrtc ? dynamic_cast(m_active_media_controller) : nullptr; + if (is_webrtc && (!webrtc_ctrl || !m_media_ctrl)) { + Stop(_L("Please confirm if the printer is connected.")); + return; + } + if (webrtc_ctrl && webrtc_ctrl->is_active()) + return; + + m_failed_code = 0; + if (!m_active_media_controller || m_machine.empty() || !IsEnabled() || !m_camera_exists || + (!is_webrtc && m_url.IsEmpty())) { + Stop(_L("Please confirm if the printer is connected.")); + return; + } + + if (is_webrtc) { + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Play webrtc: last_state=" << m_last_state << " failed_retry=" << m_failed_retry + << " shown=" << IsShownOnScreen(); + auto channel = agent ? agent->create_camera_signaling_channel(m_machine, wxGetApp().get_printer_cloud_provider()) : nullptr; + if (!channel) { + Stop(_L("Sign in to OrcaCloud to view the camera.")); + return; + } + webrtc_ctrl->set_signalling_channel(std::move(channel)); + m_media_ctrl->BeginExternalStream(); + m_last_state = MEDIASTATE_INITIALIZING; + SetStatus(_L("Initializing..."), false); + } else if (is_snapshot) { + m_last_state = wxMEDIASTATE_PLAYING; + SetStatus(_L("Playing..."), false); + } + + m_button_play->SetIcon("media_stop"); + if (m_active_media_controller == m_media_ctrl) { + // wxMediaCtrl3 reports when it has a decoded frame; load() waits for + // that event before queuing Play so the stream is not marked stopped. + load(); + } else { + m_active_media_controller->Load(wxURI(m_url)); + m_active_media_controller->Play(); + } + if (webrtc_ctrl) { + m_webrtc_epoch = webrtc_ctrl->epoch(); + } return; } + m_failed_code = 0; if (m_machine.empty()) { Stop(_L("Please confirm if the printer is connected.")); @@ -281,16 +438,15 @@ void MediaPlayCtrl::Play() } BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Play: " << m_lan_proto << m_remote_proto << m_disable_lan; - NetworkAgent *agent = wxGetApp().getAgent(); std::string agent_version = agent ? agent->get_version() : ""; - if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { + if (m_lan_proto > LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { m_disable_lan = m_remote_proto && !m_lan_mode; // try remote next time std::string url; - if (m_lan_proto == MachineObject::LVL_Local) + if (m_lan_proto == LiveviewLocal::LVL_Local) url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd; - else if (m_lan_proto == MachineObject::LVL_Rtsps) + else if (m_lan_proto == LiveviewLocal::LVL_Rtsps) url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps"; - else if (m_lan_proto == MachineObject::LVL_Rtsp) + else if (m_lan_proto == LiveviewLocal::LVL_Rtsp) url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp"; url += "&device=" + m_machine; url += "&net_ver=" + agent_version; @@ -312,8 +468,8 @@ void MediaPlayCtrl::Play() // !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_Disable (*) // !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_None (x) - if (m_lan_proto <= MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto)) { - Stop(m_lan_proto == MachineObject::LVL_None + if (m_lan_proto <= LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto)) { + Stop(m_lan_proto == LiveviewLocal::LVL_None ? _L("A problem occurred. Please update the printer firmware and try again.") : _L("LAN Only Liveview is off. Please turn on the liveview on printer screen.")); return; @@ -381,15 +537,78 @@ void MediaPlayCtrl::Play() void start_ping_test(); +void MediaPlayCtrl::StopWebStream() +{ + if (m_last_state == MEDIASTATE_IDLE) + return; + if (m_active_media_controller && m_active_media_controller == m_web_ctrl) + m_active_media_controller->Stop(); + m_button_play->SetIcon("media_play"); + m_last_state = MEDIASTATE_IDLE; + SetStatus(_L("Video Stopped."), false); +} + void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2) { + const bool webrtc_active = m_last_mode == CameraStreamMode::webrtc && + dynamic_cast(m_active_media_controller) != nullptr; + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::Stop: last_state=" << m_last_state + << " webrtc_active=" << webrtc_active << " failed_code=" << m_failed_code + << " msg='" << msg.ToUTF8().data() << "'"; + if (webrtc_active) { + m_active_media_controller->Stop(); + m_media_ctrl->EndExternalStream(); + } + switch (m_last_mode) { + case CameraStreamMode::http: + case CameraStreamMode::https: + case CameraStreamMode::http_snapshot: { + const bool snapshot = m_last_mode == CameraStreamMode::http_snapshot; + if (m_last_state != MEDIASTATE_IDLE) { + if (snapshot) { + if (m_active_media_controller) + m_active_media_controller->Stop(); + } else { + // http/https mode plays through the ffmpeg backend (m_media_ctrl), not + // the webview - tear its read thread down too, otherwise it keeps + // pulling and painting frames after the UI says "Video Stopped". + boost::unique_lock lock(m_mutex); + m_tasks.push_back({"", m_active_media_controller}); + m_cond.notify_all(); + } + m_button_play->SetIcon("media_play"); + m_last_state = MEDIASTATE_IDLE; + if (!msg.IsEmpty()) + SetStatus(msg); + else + SetStatus(_L("Video Stopped."), false); + // Keep retries bounded for an explicit or retry-driven playback attempt. + // m_failed_retry is cleared on success (onStateChanged) and on a deliberate + // machine switch (SetMachineObject); manual playback via TogglePlay resets it. + if (m_failed_code != 0) { + const bool auto_retry = wxGetApp().app_config->get("liveview", "auto_retry") != "false"; + ++m_failed_retry; + m_next_retry = auto_retry + ? wxDateTime::Now() + wxTimeSpan::Seconds(std::min(5 * m_failed_retry, 30)) + : wxDateTime::Now() + wxTimeSpan::Days(1); // "off": wait for a manual retry + } + } else if (!msg.IsEmpty()) { + SetStatus(msg, false); + } + return; + } + default: + break; + } + int last_state = m_last_state; if (m_last_state != MEDIASTATE_IDLE) { m_media_ctrl->InvalidateBestSize(); m_button_play->SetIcon("media_play"); boost::unique_lock lock(m_mutex); - m_tasks.push_back(""); + if (!webrtc_active) + m_tasks.push_back({"", m_active_media_controller}); m_cond.notify_all(); if (!msg.IsEmpty()) SetStatus(msg); @@ -454,15 +673,38 @@ void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2) m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(5 * m_failed_retry); } +void MediaPlayCtrl::on_webrtc_status(WebRtcMediaController::Status status) +{ + // Drop CallAfter-queued events from a superseded Play attempt. + if (status.epoch != m_webrtc_epoch) + return; + if (status.kind == WebRtcMediaController::Status::Connecting) { + m_last_state = MEDIASTATE_INITIALIZING; + SetStatus(_L("Initializing..."), false); + } else if (status.kind == WebRtcMediaController::Status::Playing) { + m_last_state = wxMEDIASTATE_PLAYING; + m_failed_code = 0; + m_failed_retry = 0; + SetStatus(_L("Playing..."), false); + } else if (status.kind == WebRtcMediaController::Status::Failed) { + m_failed_code = static_cast(status.code) + 1; + Stop(); + } + // Status::Stopped needs no action: a genuine failure arrives as Failed, and + // a stop we initiated is already handled by Stop() itself. +} + void MediaPlayCtrl::TogglePlay() { BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::TogglePlay"; if (m_last_state != MEDIASTATE_IDLE) { m_next_retry = wxDateTime(); + m_web_user_stopped = true; Stop(); } else { m_failed_retry = 0; m_user_triggered = true; + m_web_user_stopped = false; if (m_last_user_play + wxTimeSpan::Minutes(5) < wxDateTime::Now()) { m_last_failed_codes.clear(); m_last_user_play = wxDateTime::Now(); @@ -528,13 +770,13 @@ void MediaPlayCtrl::ToggleStream() wxGetApp().app_config->set("not_show_vcamera_stop_prev", "1"); if (res == wxID_CANCEL) return; } - if (m_lan_proto > MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { + if (m_lan_proto > LiveviewLocal::LVL_Disable && (m_lan_mode || !m_remote_proto) && !m_disable_lan && !m_lan_ip.empty()) { std::string url; - if (m_lan_proto == MachineObject::LVL_Local) + if (m_lan_proto == LiveviewLocal::LVL_Local) url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd; - else if (m_lan_proto == MachineObject::LVL_Rtsps) + else if (m_lan_proto == LiveviewLocal::LVL_Rtsps) url = "bambu:///rtsps___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsps"; - else if (m_lan_proto == MachineObject::LVL_Rtsp) + else if (m_lan_proto == LiveviewLocal::LVL_Rtsp) url = "bambu:///rtsp___" + m_lan_user + ":" + m_lan_passwd + "@" + m_lan_ip + "/streaming/live/1?proto=rtsp"; url += "&device=" + into_u8(m_machine); url += "&dev_ver=" + m_dev_ver; @@ -594,7 +836,9 @@ void MediaPlayCtrl::jump_to_play() void MediaPlayCtrl::onStateChanged(wxMediaEvent &event) { auto last_state = m_last_state; - auto state = m_media_ctrl->GetState(); + if (m_active_media_controller != m_media_ctrl) + return; + auto state = m_active_media_controller->GetState(); BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: " << state << ", last_state: " << last_state; if ((int) state < 0) return; { @@ -606,14 +850,14 @@ void MediaPlayCtrl::onStateChanged(wxMediaEvent &event) } if ((last_state == MEDIASTATE_IDLE || last_state == MEDIASTATE_INITIALIZING) && state == wxMEDIASTATE_STOPPED) { return; } if ((last_state == wxMEDIASTATE_PAUSED || last_state == wxMEDIASTATE_PLAYING) && state == wxMEDIASTATE_STOPPED) { - m_failed_code = m_media_ctrl->GetLastError(); + m_failed_code = m_active_media_controller->GetLastError(); Stop(); return; } if (last_state == MEDIASTATE_LOADING && (state == wxMEDIASTATE_STOPPED || state == wxMEDIASTATE_PAUSED)) { - wxSize size = m_media_ctrl->GetVideoSize(); + wxSize size = m_active_media_controller->GetVideoSize(); BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: size: " << size.x << "x" << size.y; - m_failed_code = m_media_ctrl->GetLastError(); + m_failed_code = m_active_media_controller->GetLastError(); if (size.GetWidth() >= 320) { m_last_state = state; m_failed_code = 0; @@ -623,7 +867,7 @@ void MediaPlayCtrl::onStateChanged(wxMediaEvent &event) m_failed_retry = 0; m_disable_lan = false; boost::unique_lock lock(m_mutex); - m_tasks.push_back(""); + m_tasks.push_back({"", m_active_media_controller}); m_cond.notify_all(); } else if (event.GetId()) { if (m_failed_code == 0) @@ -666,7 +910,8 @@ void MediaPlayCtrl::load() { m_last_state = MEDIASTATE_LOADING; SetStatus(_L("Loading...")); - if (wxGetApp().app_config->get("internal_developer_mode") == "true") { + const auto mode = current_mode(); + if (mode == CameraStreamMode::none && wxGetApp().app_config->get("internal_developer_mode") == "true") { std::string file_h264 = data_dir() + "/video.h264"; std::string file_info = data_dir() + "/video.info"; BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << file_h264; @@ -677,7 +922,7 @@ void MediaPlayCtrl::load() m_url = m_url + "&dump_info=" + boost::lexical_cast(dump_info_file); } boost::unique_lock lock(m_mutex); - m_tasks.push_back(m_url); + m_tasks.push_back({m_url, m_active_media_controller}); m_cond.notify_all(); } @@ -686,9 +931,8 @@ void MediaPlayCtrl::on_show_hide(wxShowEvent &evt) evt.Skip(); if (m_isBeingDeleted) return; m_failed_retry = 0; - if (m_next_retry.IsValid()) // Try open 2 seconds later, to avoid quick play/stop - m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(2); - IsShownOnScreen() ? Play() : Stop(); + if (!IsShownOnScreen()) + Stop(); } void MediaPlayCtrl::media_proc() @@ -698,30 +942,34 @@ void MediaPlayCtrl::media_proc() while (m_tasks.empty()) { m_cond.wait(lock); } - wxString url = m_tasks.front(); - if (m_tasks.size() >= 2 && !url.IsEmpty() && url[0] != '<' && m_tasks[1] == "") { - BOOST_LOG_TRIVIAL(trace) << "MediaPlayCtrl: busy skip url: " << url; + MediaTask task = m_tasks.front(); + if (m_tasks.size() >= 2 && !task.command.IsEmpty() && task.command[0] != '<' && + m_tasks[1].command == "" && task.controller == m_tasks[1].controller) { + BOOST_LOG_TRIVIAL(trace) << "MediaPlayCtrl: busy skip url: " << task.command; m_tasks.pop_front(); m_tasks.pop_front(); continue; } lock.unlock(); - if (url == "") { + if (task.command == "") { BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start stop"; - m_media_ctrl->Stop(); + if (task.controller) + task.controller->Stop(); BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end stop"; } - else if (url == "") { + else if (task.command == "") { break; } - else if (url == "") { + else if (task.command == "") { BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start play"; - m_media_ctrl->Play(); + if (task.controller) + task.controller->Play(); BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end play"; } else { BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load"; - m_media_ctrl->Load(wxURI(url)); + if (task.controller) + task.controller->Load(wxURI(task.command)); BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end load"; } lock.lock(); diff --git a/src/slic3r/GUI/MediaPlayCtrl.h b/src/slic3r/GUI/MediaPlayCtrl.h index 0a01daefc9..1d426fc685 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.h +++ b/src/slic3r/GUI/MediaPlayCtrl.h @@ -9,6 +9,9 @@ #define MediaPlayCtrl_h #include "wxMediaCtrl3.h" +#include "IMediaController.hpp" +#include "WebRtcMediaController.hpp" +#include "slic3r/Utils/IPrinterAgent.hpp" #include @@ -36,6 +39,10 @@ public: void SetMachineObject(MachineObject * obj); + void SetWebMediaController(IMediaController *ctrl); + + void StopWebStream(); + bool IsStreaming() const; void ToggleStream(); @@ -54,6 +61,7 @@ protected: void TogglePlay(); void SetStatus(wxString const &msg, bool hyperlink = true); + void on_webrtc_status(WebRtcMediaController::Status status); private: void load(); @@ -66,6 +74,9 @@ private: static bool get_stream_url(std::string *url = nullptr); + CameraStreamMode current_mode() const; + void set_active_media_controller(CameraStreamMode mode); + private: static inline const wxMediaState MEDIASTATE_IDLE = static_cast(3); static inline const wxMediaState MEDIASTATE_INITIALIZING = static_cast(4); @@ -76,6 +87,13 @@ private: std::shared_ptr m_token = std::make_shared(0); wxMediaCtrl3 * m_media_ctrl; + IMediaController * m_active_media_controller = nullptr; + IMediaController * m_web_ctrl = nullptr; + std::unique_ptr m_webrtc_ctrl; + CameraStreamMode m_last_mode = CameraStreamMode::none; + std::uint64_t m_webrtc_epoch = 0; + std::string m_agent_camera_url; + bool m_web_user_stopped = false; wxMediaState m_last_state = MEDIASTATE_IDLE; std::string m_machine; int m_lan_proto = 0; @@ -91,7 +109,11 @@ private: bool m_disable_lan = false; wxString m_url; - std::deque m_tasks; + struct MediaTask { + wxString command; + IMediaController *controller = nullptr; + }; + std::deque m_tasks; boost::mutex m_mutex; boost::condition_variable m_cond; boost::thread m_thread; diff --git a/src/slic3r/GUI/Monitor.cpp b/src/slic3r/GUI/Monitor.cpp index 7352ee3dd7..eb5a8eb5e9 100644 --- a/src/slic3r/GUI/Monitor.cpp +++ b/src/slic3r/GUI/Monitor.cpp @@ -34,6 +34,8 @@ #include "DeviceCore/DevManager.h" +#include + namespace Slic3r { namespace GUI { @@ -265,6 +267,7 @@ void MonitorPanel::msw_rescale() void MonitorPanel::select_machine(std::string machine_sn) { + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: MonitorPanel::select_machine queueing machine_sn=" << machine_sn; wxCommandEvent *event = new wxCommandEvent(wxEVT_COMMAND_CHOICE_SELECTED); event->SetString(machine_sn); wxQueueEvent(this, event); @@ -282,13 +285,20 @@ void MonitorPanel::on_timer(wxTimerEvent& event) void MonitorPanel::on_select_printer(wxCommandEvent& event) { Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); + const std::string requested_dev_id = event.GetString().ToStdString(); + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: MonitorPanel::on_select_printer requested_dev_id=" + << requested_dev_id << " device_manager=" << (dev ? "set" : "null"); if (!dev) return; if ( dev->get_selected_machine() && (dev->get_selected_machine()->get_dev_id() != event.GetString().ToStdString()) && m_hms_panel) { m_hms_panel->clear_hms_tag(); } - if (!dev->set_selected_machine(event.GetString().ToStdString())) + const bool selected = dev->set_selected_machine(requested_dev_id); + BOOST_LOG_TRIVIAL(trace) << "Orca diagnostic: MonitorPanel::on_select_printer set_selected_machine result=" + << selected << " selected_dev_id=" + << (dev->get_selected_machine() ? dev->get_selected_machine()->get_dev_id() : ""); + if (!selected) return; set_default(); diff --git a/src/slic3r/GUI/MultiMachine.cpp b/src/slic3r/GUI/MultiMachine.cpp index c0e84f40e9..df6ccc1298 100644 --- a/src/slic3r/GUI/MultiMachine.cpp +++ b/src/slic3r/GUI/MultiMachine.cpp @@ -51,7 +51,7 @@ void DeviceItem::sync_state() state_printable = 6; } - if (is_blocking_printing(obj_)) { + if (wxGetApp().is_blocking_printing(obj_)) { state_printable = 5; } @@ -104,27 +104,6 @@ void DeviceItem::unselected() } } -bool DeviceItem::is_blocking_printing(MachineObject* obj_) -{ - DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); - if (!dev) return true; - auto target_model = obj_->printer_type; - std::string source_model = ""; - - PresetBundle* preset_bundle = wxGetApp().preset_bundle; - source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); - - if (source_model != target_model) { - std::vector compatible_machine = obj_->get_compatible_machine(); - vector::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model); - if (it == compatible_machine.end()) { - return true; - } - } - - return false; -} - void DeviceItem::update_item(const DeviceItem* item) { // Except for the selected status, everything else is updated diff --git a/src/slic3r/GUI/MultiMachine.hpp b/src/slic3r/GUI/MultiMachine.hpp index 51f70b9d96..b38918225c 100644 --- a/src/slic3r/GUI/MultiMachine.hpp +++ b/src/slic3r/GUI/MultiMachine.hpp @@ -59,7 +59,6 @@ public: void selected(); void unselected(); - bool is_blocking_printing(MachineObject* obj_); void update_item(const DeviceItem* item); }; diff --git a/src/slic3r/GUI/PartSkipDialog.hpp b/src/slic3r/GUI/PartSkipDialog.hpp index b95681dcde..37697ced31 100644 --- a/src/slic3r/GUI/PartSkipDialog.hpp +++ b/src/slic3r/GUI/PartSkipDialog.hpp @@ -159,4 +159,4 @@ private: void OnApplyDialog(wxCommandEvent &event); }; -}} // namespace Slic3r::GUI \ No newline at end of file +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 51fdba9bf8..bd9327ad61 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2236,12 +2236,14 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man std::string machine_print_name = obj->get_show_printer_type(); PresetBundle *preset_bundle = wxGetApp().preset_bundle; std::string target_model_id = preset_bundle->printers.get_selected_preset().get_printer_type(preset_bundle); - Preset* machine_preset = get_printer_preset(obj); - if (!machine_preset) { + const bool optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj->printer_type); + const bool optional_target_model = DevPrinterConfigUtil::is_optional_printer_model_id(target_model_id); + Preset* machine_preset = optional_printer_model ? nullptr : get_printer_preset(obj); + if (!optional_printer_model && !optional_target_model && !machine_preset) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << "check error: machine_preset empty"; return false; } - if (machine_print_name != target_model_id) { + if (!optional_printer_model && !optional_target_model && machine_print_name != target_model_id) { MessageDialog dlg(this->plater, _L("The currently selected machine preset is inconsistent with the connected printer type.\n" "Are you sure to continue syncing?"), _L("Sync printer information"), wxICON_WARNING | wxYES | wxNO); if (dlg.ShowModal() == wxID_NO) { @@ -2433,6 +2435,11 @@ void Sidebar::priv::update_sync_status(const MachineObject *obj) return; } + if (DevPrinterConfigUtil::is_optional_printer_model_id(obj->printer_type)) { + clear_all_sync_status(); + return; + } + bool printer_synced = false; // 1. update printer status const Preset &cur_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); @@ -6066,11 +6073,30 @@ void Sidebar::load_ams_list(MachineObject* obj) filament_ams_list = build_filament_ams_list(obj); } - bool device_change = false; const std::string& device = obj ? obj->get_dev_id() : ""; - if (p->ams_list_device != device) { + const bool same_device = p->ams_list_device == device; + + // Keep sync metadata out of the device payload, but preserve it across a + // subscription refresh when the physical filament in a slot is unchanged. + // Otherwise the refreshed configs differ only by the missing + // filament_changed key, causing combo boxes to rebuild and lose their + // transient post-sync badges. + auto &previous_filament_ams_list = wxGetApp().preset_bundle->filament_ams_list; + for (auto &entry : filament_ams_list) { + auto previous = previous_filament_ams_list.find(entry.first); + const auto *previous_changed = previous == previous_filament_ams_list.end() ? nullptr : + dynamic_cast(previous->second.option("filament_changed")); + if (!same_device || previous_changed == nullptr || + previous->second.opt_string("filament_id", 0u) != entry.second.opt_string("filament_id", 0u)) { + continue; + } + entry.second.set_key_value("filament_changed", + new ConfigOptionBool{previous_changed->value}); + } + + bool device_change = !same_device; + if (device_change) { p->ams_list_device = device; - device_change = true; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1% items") % filament_ams_list.size(); if (wxGetApp().preset_bundle->filament_ams_list == filament_ams_list && !device_change) @@ -6080,9 +6106,27 @@ void Sidebar::load_ams_list(MachineObject* obj) wxGetApp().preset_bundle->filament_ams_list = filament_ams_list; for (auto c : p->combos_filament){ + c->set_sync_badge(false); c->update(); - if (device_change) { - c->ShowBadge(false);//change printer,then clear badge + } + + if (!device_change) { + size_t combo_index = 0; + for (const auto &entry : filament_ams_list) { + const auto &tray = entry.second; + const bool has_filament = !tray.opt_string("filament_id", 0u).empty(); + const bool is_placeholder = tray.has("filament_slot_placeholder") && + tray.opt_bool("filament_slot_placeholder", 0u); + if (!has_filament && !is_placeholder) { + continue; + } + if (combo_index >= p->combos_filament.size()) { + break; + } + const auto *filament_changed = dynamic_cast(tray.option("filament_changed")); + p->combos_filament[combo_index]->set_sync_badge( + has_filament && !is_placeholder && filament_changed != nullptr && filament_changed->value); + ++combo_index; } } @@ -6256,18 +6300,32 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn) auto tip = sync_color_only ? _L("Only filament color information has been synchronized from printer.") : _L("Filament type and color information have been synchronized, but slot information is not included."); c->SetToolTip(tip); - c->ShowBadge(true); + c->set_sync_badge(true); }; { // badge ams filament clear_combos_filament_badge(); if (sync_result.direct_sync) { - // Orca: PresetBundle::sync_ams_list rebuilds combos_filament - // 1:1 from the AMS trays that produce a combo (loaded trays + placeholders; non-placeholder - // empty trays are skipped), so every resulting combo is AMS-sourced and gets a badge. The - // previous per-tray index walked the full filament_ams_list (including the skipped empties), - // so an empty slot before a loaded one dropped the badge for the trailing filaments. - for (auto &c : p->combos_filament) { - badge_combox_filament(c); + // A placeholder contributes a preserved project filament to the + // overwrite result, but it is not AMS-sourced and must not get a + // sync badge. Non-placeholder empty trays are omitted entirely. + size_t combo_index = 0; + for (const auto &entry : wxGetApp().preset_bundle->filament_ams_list) { + const auto &tray = entry.second; + const bool has_filament = !tray.opt_string("filament_id", 0u).empty(); + const bool is_placeholder = tray.has("filament_slot_placeholder") && + tray.opt_bool("filament_slot_placeholder", 0u); + if (!has_filament && !is_placeholder) { + continue; + } + if (combo_index >= p->combos_filament.size()) { + break; + } + if (is_placeholder) { + p->combos_filament[combo_index]->set_sync_badge(false); + } else { + badge_combox_filament(p->combos_filament[combo_index]); + } + ++combo_index; } } } @@ -6535,6 +6593,11 @@ template void setup_dialog_position(T& info) void Sidebar::pop_sync_nozzle_and_ams_dialog() { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " begin pop_sync_nozzle_and_ams_dialog"; + auto agent = wxGetApp().getAgent(); + if (!agent || agent->get_filament_sync_mode() == FilamentSyncMode::none) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " filament synchronization is not supported; skipping dialog"; + return; + } wxTheApp->CallAfter([this]() { SyncNozzleAndAmsDialog::InputInfo temp_na_info; wxPoint big_btn_pt; @@ -6666,17 +6729,14 @@ void Sidebar::clear_combos_filament_badge() { auto &combos_filament = p->combos_filament; for (auto &c : combos_filament) { // clear flag - c->ShowBadge(false); + c->set_sync_badge(false); } } void Sidebar::udpate_combos_filament_badge() { auto &combos_filament = p->combos_filament; for (auto &c : combos_filament) { - auto selection = c->GetSelection(); - auto select_flag = c->GetFlag(selection); - auto ok = select_flag == (int) PresetComboBox::FilamentAMSType::FROM_AMS; - c->ShowBadge(ok); + c->update_badge_according_flag(); } } @@ -12661,7 +12721,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) sidebar->auto_calc_flushing_volumes(idx); } auto select_flag = combo->GetFlag(selection); - combo->ShowBadge(select_flag == (int)PresetComboBox::FilamentAMSType::FROM_AMS); + combo->set_sync_badge(select_flag == (int)PresetComboBox::FilamentAMSType::FROM_AMS); q->on_filament_change(idx); } bool select_preset = !combo->selection_is_changed_according_to_physical_printers(); @@ -21335,9 +21395,14 @@ bool Plater::is_same_printer_for_connected_and_selected(bool popup_warning) } if (!check_printer_initialized(obj, true, popup_warning)) return false; - Preset * machine_preset = get_printer_preset(obj); - if (!machine_preset) + const std::string machine_model = obj->printer_type; + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + const std::string selected_model = preset_bundle ? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) : std::string(); + if (!DevPrinterConfigUtil::is_optional_printer_model_id(machine_model) && + !DevPrinterConfigUtil::is_optional_printer_model_id(selected_model) && + !get_printer_preset(obj)) { return false; + } if (wxGetApp().is_blocking_printing()) { if (popup_warning) { diff --git a/src/slic3r/GUI/PluginStatus.hpp b/src/slic3r/GUI/PluginStatus.hpp index 477e9d99fe..635e35f98a 100644 --- a/src/slic3r/GUI/PluginStatus.hpp +++ b/src/slic3r/GUI/PluginStatus.hpp @@ -11,6 +11,7 @@ namespace Slic3r // IMPORTANT: ordinal order is the Plugins dialog Status sort priority. Activated, Error, + RuntimeError, Inactive, Loading }; @@ -21,11 +22,28 @@ namespace Slic3r { case PluginStatus::Activated: return "Activated"; case PluginStatus::Error: return "Error"; + case PluginStatus::RuntimeError: return "RuntimeError"; case PluginStatus::Inactive: return "Inactive"; case PluginStatus::Loading: return "Loading"; } return "Inactive"; } + + // why: a plugin whose module is live but whose catalog carries an error is a + // RUNTIME fault (e.g. a capability rejected at register time) - it stays + // loaded/checked and is only flagged, distinct from a load-time Error where + // the module never came up. Loading wins over both so an in-flight reload + // never flashes an error. + inline PluginStatus resolve_plugin_status(bool loading, bool has_error, bool is_loaded) + { + if (loading) + return PluginStatus::Loading; + if (has_error) + return is_loaded ? PluginStatus::RuntimeError : PluginStatus::Error; + if (is_loaded) + return PluginStatus::Activated; + return PluginStatus::Inactive; + } } } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PluginsDialog.cpp b/src/slic3r/GUI/PluginsDialog.cpp index 8b8a89a45a..6ac04a50da 100644 --- a/src/slic3r/GUI/PluginsDialog.cpp +++ b/src/slic3r/GUI/PluginsDialog.cpp @@ -193,6 +193,7 @@ nlohmann::json build_plugin_payload_item(const PluginDialogItem& dialog_item) payload_item["label"] = dialog_item.display_name; payload_item["source"] = to_string(dialog_item.source); payload_item["status"] = to_string(dialog_item.status); + payload_item["is_loaded"] = dialog_item.is_loaded; payload_item["error"] = dialog_item.error_text; payload_item["update_status"] = to_string(dialog_item.update_status); payload_item["unauthorized"] = dialog_item.unauthorized; @@ -337,14 +338,7 @@ PluginDialogItem build_plugin_dialog_item(const PluginDescriptor& descriptor) item.sharing_token = descriptor.sharing_token; item.thumbnail_url = descriptor.thumbnail_url; - if (item.loading) - item.status = PluginStatus::Loading; - else if (item.has_error) - item.status = PluginStatus::Error; - else if (item.is_loaded) - item.status = PluginStatus::Activated; - else - item.status = PluginStatus::Inactive; + item.status = resolve_plugin_status(item.loading, item.has_error, item.is_loaded); item.available_actions = evaluate_action_policy(item); const bool has_enabled_script = std::any_of(item.capabilities.begin(), item.capabilities.end(), @@ -790,6 +784,9 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled) } BOOST_LOG_TRIVIAL(info) << "Plugin unloaded from Plugins dialog: " << plugin_key; + // A user-disabled plugin has no meaningful error state. + if (!manager.clear_plugin_error(plugin_key)) + BOOST_LOG_TRIVIAL(warning) << "Failed to clear plugin error for " << plugin_key << " (failed to find)"; // A prior activation of this plugin is moot now; drop it so no stale "Activated" arrives later. if (m_activating_plugin_key == plugin_key) m_activating_plugin_key.clear(); diff --git a/src/slic3r/GUI/PrePrintChecker.cpp b/src/slic3r/GUI/PrePrintChecker.cpp index 0575af23ed..7d0ae63cf1 100644 --- a/src/slic3r/GUI/PrePrintChecker.cpp +++ b/src/slic3r/GUI/PrePrintChecker.cpp @@ -63,6 +63,7 @@ std::string PrePrintChecker::get_print_status_info(PrintDialogStatus status) case PrintStatusRackReading: return "PrintStatusRackReading"; case PrintStatusRackNozzleNumUnmeetWarning: return "PrintStatusRackNozzleNumUnmeetWarning"; case PrintStatusHasUnreliableNozzleWarning: return "PrintStatusHasUnreliableNozzleWarning"; + case PrintStatusOptionalPrinterModel: return "PrintStatusOptionalPrinterModel"; case PrintStatusWarningExtFilamentNotMatch: return "PrintStatusWarningExtFilamentNotMatch"; case PrintStatusFilamentWarningNozzleHRC: return "PrintStatusFilamentWarningNozzleHRC"; case PrintStatusTPUUnsupportCaliOn: return "PrintStatusTPUUnsupportCaliOn"; @@ -104,6 +105,7 @@ wxString PrePrintChecker::get_pre_state_msg(PrintDialogStatus status) case PrintStatusNeedConsistencyUpgrading: return _L("Cannot send the print job to a printer whose firmware must be updated."); case PrintStatusBlankPlate: return _L("Cannot send a print job for an empty plate."); case PrintStatusTimelapseNoSdcard: return _L("Storage needs to be inserted to record timelapse."); + case PrintStatusOptionalPrinterModel: return _L("The selected printer model could not be identified, so compatibility with the print file configuration cannot be verified. Please verify the printer preset before sending."); case PrintStatusMixAmsAndVtSlotWarning: return _L("You have selected both external and AMS filaments for an extruder. You will need to manually switch the external filament during printing."); case PrintStatusTPUUnsupportAutoCali: return _L("TPU 90A/TPU 85A is too soft and does not support automatic Flow Dynamics calibration."); case PrintStatusWarningKvalueNotUsed: return _L("Set dynamic flow calibration to 'OFF' to enable custom dynamic flow value."); @@ -379,4 +381,3 @@ bool PrinterMsgPanel::UpdateInfos(const std::vector& infos) } }; - diff --git a/src/slic3r/GUI/PrePrintChecker.hpp b/src/slic3r/GUI/PrePrintChecker.hpp index f629b70a73..5dea00a88f 100644 --- a/src/slic3r/GUI/PrePrintChecker.hpp +++ b/src/slic3r/GUI/PrePrintChecker.hpp @@ -112,6 +112,7 @@ enum PrintDialogStatus : unsigned int { // Orca: a nozzle diameter that differs from the one the printer remembers is a warning, // not an error, so non-standard nozzles can still be printed with. PrintStatusNozzleDiameterMismatch, + PrintStatusOptionalPrinterModel, PrintStatusPrinterWarningEnd, // Warnings for filament @@ -170,7 +171,7 @@ public: void add_with_checkbox(PrintDialogStatus state, wxString msg, wxString checkbox_label, bool checked, std::function checkbox_callback); static ::std::string get_print_status_info(PrintDialogStatus status); - wxString get_pre_state_msg(PrintDialogStatus status); + static wxString get_pre_state_msg(PrintDialogStatus status); static bool is_error(PrintDialogStatus status) { return (PrintStatusErrorBegin < status) && (PrintStatusErrorEnd > status); }; static bool is_error_printer(PrintDialogStatus status) { return (PrintStatusPrinterErrorBegin < status) && (PrintStatusPrinterErrorEnd > status); }; static bool is_error_filament(PrintDialogStatus status) { return (PrintStatusFilamentErrorBegin < status) && (PrintStatusFilamentErrorEnd > status); }; diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index ac419073d7..c5ffb533e2 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -361,7 +361,8 @@ wxString PresetComboBox::get_preset_item_name(unsigned int index) return GetString(index); } - std::map machine_list = dev->get_my_machine_list(); + std::map machine_list = + dev->get_my_machine_list(dev->get_current_printer_agent_id()); if (machine_list.empty()) { assert(false); m_selected_dev_id.clear(); @@ -479,7 +480,8 @@ void PresetComboBox::add_connected_printers(std::string selected, bool alias_nam if (!dev) return; - std::map machine_list = dev->get_my_machine_list(); + std::map machine_list = + dev->get_my_machine_list(dev->get_current_printer_agent_id()); if (machine_list.empty()) return; @@ -999,7 +1001,13 @@ void PlaterPresetComboBox::update_badge_according_flag() { auto selection = GetSelection(); auto select_flag = GetFlag(selection); auto ok = select_flag == (int) PresetComboBox::FilamentAMSType::FROM_AMS; - ShowBadge(ok); + ShowBadge(m_sync_badge || ok); +} + +void PlaterPresetComboBox::set_sync_badge(bool show) +{ + m_sync_badge = show; + ShowBadge(show); } bool PlaterPresetComboBox::switch_to_tab() diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 20f75f4616..b63f51dd70 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -205,6 +205,7 @@ public: void msw_rescale() override; void OnSelect(wxCommandEvent& evt) override; void update_badge_according_flag(); + void set_sync_badge(bool show); FilamentColor get_cur_color_info(); void show_default_color_picker(); @@ -214,6 +215,7 @@ public: private: // BBS wxColor m_color; + bool m_sync_badge{false}; }; diff --git a/src/slic3r/GUI/ReleaseNote.cpp b/src/slic3r/GUI/ReleaseNote.cpp index 58b3a40ca9..cb49da461a 100644 --- a/src/slic3r/GUI/ReleaseNote.cpp +++ b/src/slic3r/GUI/ReleaseNote.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include "Plater.hpp" @@ -29,6 +30,7 @@ #include "DeviceCore/DevManager.h" #include "DeviceCore/DevStorage.h" #include "md4c/src/md4c-html.h" +#include "../Utils/Http.hpp" namespace Slic3r { namespace GUI { @@ -1461,6 +1463,45 @@ InputIpAddressDialog::InputIpAddressDialog(wxWindow *parent) m_input_top_sizer->Add(0, 0, 0, wxTOP, FromDIP(4)); m_input_top_sizer->Add(m_input_area, 0, wxRIGHT | wxEXPAND, FromDIP(18)); + m_tips_cafile = new Label(ip_input_top_panel, _L("HTTPS CA File")); + m_input_cafile = new wxTextCtrl(ip_input_top_panel, wxID_ANY); + m_input_cafile->SetMinSize(wxSize(FromDIP(260), FromDIP(28))); + m_input_cafile->SetMaxSize(wxSize(FromDIP(260), FromDIP(28))); + + m_button_cafile = new Button(ip_input_top_panel, _L("Browse") + " " + dots); + m_button_cafile->SetStyle(ButtonStyle::Regular, ButtonType::Parameter); + m_button_cafile->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"); + wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks, + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_CANCEL) + m_input_cafile->SetValue(openFileDialog.GetPath()); + }); + + auto cafile_input_sizer = new wxBoxSizer(wxHORIZONTAL); + cafile_input_sizer->Add(m_input_cafile, 1, wxALIGN_CENTER_VERTICAL); + cafile_input_sizer->Add(m_button_cafile, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(10)); + + m_cafile_hint = new Label(ip_input_top_panel, _L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.")); + m_cafile_hint->Wrap(FromDIP(352)); + + m_input_top_sizer->Add(m_tips_cafile, 0, wxTOP | wxEXPAND, FromDIP(10)); + m_input_top_sizer->Add(cafile_input_sizer, 0, wxTOP | wxEXPAND, FromDIP(4)); + m_input_top_sizer->Add(m_cafile_hint, 0, wxTOP | wxEXPAND, FromDIP(4)); + + if (!Http::ca_file_supported()) { + m_input_cafile->Disable(); + m_button_cafile->Disable(); + m_cafile_hint->SetLabel(_L("This system uses HTTPS certificates from the system Certificate Store or Keychain. To use a custom CA file, import it there.")); + m_cafile_hint->Wrap(FromDIP(352)); + } + + if (wxGetApp().preset_bundle) { + const auto& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + if (config.has("printhost_cafile")) + m_input_cafile->SetValue(from_u8(config.opt_string("printhost_cafile"))); + } + ip_input_top_panel->SetSizer(m_input_top_sizer); ip_input_top_panel->Layout(); ip_input_top_panel->Fit(); @@ -1736,7 +1777,8 @@ void InputIpAddressDialog::set_machine_obj(MachineObject* obj) auto str_ip = m_input_ip->GetTextCtrl()->GetValue(); auto str_access_code = m_input_access_code->GetTextCtrl()->GetValue(); // ORCA enabling / disabling buttons with conditions enough to change its style - m_button_ok->Enable(isIp(str_ip.ToStdString()) && str_access_code.Length() == 8); + m_button_ok->Enable(isIp(str_ip.ToStdString()) && + (str_access_code.IsEmpty() || str_access_code.Length() >= 8)); Layout(); Fit(); @@ -1801,6 +1843,8 @@ void InputIpAddressDialog::on_ok(wxMouseEvent& evt) m_trouble_shoot->Hide(); std::string str_ip = m_input_ip->GetTextCtrl()->GetValue().ToStdString(); std::string str_access_code = m_input_access_code->GetTextCtrl()->GetValue().ToStdString(); + if (str_access_code.empty()) + str_access_code = "88888888"; std::string str_name = m_input_printer_name->GetTextCtrl()->GetValue().Strip(wxString::both).ToStdString(); // Serial number should not contain lower case letters, and bambu_network plugin crashes // if user entered the wrong serial number, so we call `Upper()` here. @@ -1820,6 +1864,17 @@ void InputIpAddressDialog::on_ok(wxMouseEvent& evt) Layout(); Fit(); + if (wxGetApp().preset_bundle) { + auto& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + std::string port; + Http::get_host_from_url(str_ip, &port); + config.opt_string("print_host") = str_ip; + if (!port.empty()) + config.opt_string("printhost_port") = port; + if (Http::ca_file_supported()) + config.opt_string("printhost_cafile") = m_input_cafile->GetValue().ToStdString(); + } + token_.reset(this, nop_deleter); m_thread = new boost::thread(boost::bind(&InputIpAddressDialog::workerThreadFunc, this, str_ip, str_access_code, str_sn, str_model_id, str_name)); } @@ -1835,6 +1890,8 @@ void InputIpAddressDialog::on_send_retry() Fit(); wxString ip = m_input_ip->GetTextCtrl()->GetValue(); wxString str_access_code = m_input_access_code->GetTextCtrl()->GetValue(); + if (str_access_code.IsEmpty()) + str_access_code = "88888888"; // check support function if (!m_obj) return; @@ -2056,7 +2113,7 @@ void InputIpAddressDialog::on_text(wxCommandEvent &evt) auto str_ip = m_input_ip->GetTextCtrl()->GetValue(); auto str_access_code = m_input_access_code->GetTextCtrl()->GetValue(); - if (str_access_code.empty()) { + if (str_access_code.IsEmpty()) { str_access_code = "88888888"; } @@ -2072,7 +2129,8 @@ void InputIpAddressDialog::on_text(wxCommandEvent &evt) } // ORCA enabling / disabling buttons with conditions enough to change its style - bool enable_btns = isIp(str_ip.ToStdString()) && str_access_code.Length() == 8 && invalid_access_code; + bool valid_access_code_length = str_access_code.IsEmpty() || str_access_code.Length() >= 8; + bool enable_btns = isIp(str_ip.ToStdString()) && valid_access_code_length && invalid_access_code; m_button_manual_setup->Enable(enable_btns); m_button_ok->Enable(enable_btns); @@ -2087,7 +2145,7 @@ InputIpAddressDialog::~InputIpAddressDialog() void InputIpAddressDialog::on_dpi_changed(const wxRect& suggested_rect) { - + m_button_cafile->Rescale(); } diff --git a/src/slic3r/GUI/ReleaseNote.hpp b/src/slic3r/GUI/ReleaseNote.hpp index cfd372bc97..5d7c0b25c4 100644 --- a/src/slic3r/GUI/ReleaseNote.hpp +++ b/src/slic3r/GUI/ReleaseNote.hpp @@ -315,12 +315,16 @@ public: Button* m_button_manual_setup{ nullptr }; Label* m_tips_ip{ nullptr }; Label* m_tips_access_code{ nullptr }; + Label* m_tips_cafile{ nullptr }; + Label* m_cafile_hint{ nullptr }; Label* m_tips_sn{nullptr}; Label* m_tips_modelID{nullptr}; Label* m_test_right_msg{ nullptr }; Label* m_test_wrong_msg{ nullptr }; TextInput* m_input_ip{ nullptr }; TextInput* m_input_access_code{ nullptr }; + wxTextCtrl* m_input_cafile{ nullptr }; + Button* m_button_cafile{ nullptr }; TextInput* m_input_printer_name{ nullptr }; TextInput* m_input_sn{ nullptr }; ComboBox* m_input_modelID{ nullptr }; diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp index 04eb2b1612..db4eb723bc 100644 --- a/src/slic3r/GUI/SelectMachine.cpp +++ b/src/slic3r/GUI/SelectMachine.cpp @@ -21,6 +21,7 @@ #include "Jobs/PlaterWorker.hpp" #include "DeviceCore/DevConfig.h" +#include "DeviceCore/DevConfigUtil.h" #include "DeviceCore/DevNozzleSystem.h" #include "DeviceCore/DevNozzleRack.h" #include "DeviceCore/DevExtensionTool.h" @@ -1128,7 +1129,10 @@ bool SelectMachineDialog::do_ams_mapping(MachineObject *obj_,bool use_ams) int filament_result = 0; std::vector map_opt; //four values: use_left_ams, use_right_ams, use_left_ext, use_right_ext - if (nozzle_nums > 1){ + // Orca: only do the per-physical-extruder left/right split when the device actually reports + // 2+ extruders. A non-BBL multi-nozzle printer (e.g. Snapmaker U1) reports a single extruder + // with one filament pool, so it maps as a single surface via the else branch below. + if (nozzle_nums > 1 && obj_->GetExtderSystem()->GetTotalExtderCount() > 1){ //get nozzle property, the extders are same? if (true/*!can_hybrid_mapping(obj_get_extder_data())*/){ std::vector m_ams_mapping_result_left, m_ams_mapping_result_right; @@ -2287,9 +2291,7 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector 1) msg_text = wxString::Format(_L("Filament %s does not match the filament in AMS slot %s. Please update the printer firmware to support AMS slot assignment."), params[0], params[1]); @@ -2315,8 +2317,10 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vectorprinter_type; - std::string source_model = ""; + if (m_print_type == PrintFromType::FROM_NORMAL) + return wxGetApp().is_blocking_printing(obj_); - if (m_print_type == PrintFromType::FROM_NORMAL) { - PresetBundle* preset_bundle = wxGetApp().preset_bundle; - source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); - - - }else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW) { - if (m_required_data_plate_data_list.size() > 0) { - source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id; - } - } - - if (source_model != target_model) { - std::vector compatible_machine = obj_->get_compatible_machine(); - vector::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model); - if (it == compatible_machine.end()) { - return true; - } - } - - return false; + std::string source_model; + if (m_print_type == PrintFromType::FROM_SDCARD_VIEW && !m_required_data_plate_data_list.empty()) + source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id; + return wxGetApp().is_blocking_printing(obj_, source_model); } static std::unordered_set _get_used_nozzle_idxes() @@ -2625,6 +2615,10 @@ bool SelectMachineDialog::is_same_printer_model() if(preset_bundle == nullptr) return result; const auto source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); const auto target_model = obj_->printer_type; + if (DevPrinterConfigUtil::is_optional_printer_model_id(source_model) || + DevPrinterConfigUtil::is_optional_printer_model_id(target_model)) { + return true; + } // Orca: ignore P1P -> P1S if (source_model != target_model) { if ((source_model == "C12" && target_model == "C11") || (source_model == "C11" && target_model == "C12") || @@ -3699,10 +3693,32 @@ void SelectMachineDialog::on_send_print() m_print_job->on_success([this]() { finish_mode(); }); m_print_job->on_check_ip_address_fail([this]() { - wxCommandEvent* evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS); - wxQueueEvent(this, evt); - wxGetApp().show_ip_address_enter_dialog(); - }); + // Invoked from the PrintJob worker thread when the LAN pre-flight (file upload + // verification) fails. Marshal device/UI access to the main thread. + CallAfter([this]() + { + // Reset the dialog out of sending mode so the user can retry. + wxCommandEvent* evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS); + wxQueueEvent(this, evt); + + DeviceManager* dev = wxGetApp().getDeviceManager(); + MachineObject* obj = dev ? dev->get_selected_machine() : nullptr; + + if (obj && obj->is_connected()) + { + // Connected: failed on file upload + MessageDialog dlg(this, + _L("Failed to upload the file to the printer's storage. Please try again."), + _L("Send Failed"), wxOK | wxICON_ERROR); + dlg.ShowModal(); + } + else + { + // Not connected: reenter ip and access code + wxGetApp().show_ip_address_enter_dialog(); + } + }); + }); // update ota version NetworkAgent* agent = wxGetApp().getAgent(); @@ -4536,11 +4552,13 @@ bool SelectMachineDialog::CheckErrorExtruderNozzleWithSlicing(MachineObject* obj // check nozzle data valid { - if (installed_ext_nozzle.GetNozzleType() == NozzleType::ntUndefine || - installed_ext_nozzle.GetNozzleDiameter() <= 0.0f) { - show_status(PrintDialogStatus::PrintStatusNozzleDataInvalid); - return false; - } + // Commented out the following as ntUndefine and 0.0f are default values + // (signifying that the value is not given) that should PASS, not fail + // if (installed_ext_nozzle.GetNozzleType() == NozzleType::ntUndefine || + // installed_ext_nozzle.GetNozzleDiameter() <= 0.0f) { + // show_status(PrintDialogStatus::PrintStatusNozzleDataInvalid); + // return false; + // } if (obj_->is_nozzle_flow_type_supported() && installed_ext_nozzle.GetNozzleFlowType() == NozzleFlowType::NONE_FLOWTYPE) { @@ -4569,7 +4587,10 @@ bool SelectMachineDialog::CheckErrorExtruderNozzleWithSlicing(MachineObject* obj // check nozzle diameter { - if (slicing_ext.nozzle_diameter != installed_ext_nozzle.GetNozzleDiameter()) { + // 0.0f is default when there is no nozzle diameter is given. + // In nozzle_diameter == 0.0f case, it passes and does not require a comparison + if (installed_ext_nozzle.GetNozzleDiameter() > 0.0f && + slicing_ext.nozzle_diameter != installed_ext_nozzle.GetNozzleDiameter()) { std::vector msg_params; if (ext_sys->GetTotalExtderCount() == 2) { const wxString& mismatch_nozzle_str = _get_nozzle_name(ext_sys->GetTotalExtderCount(), slicing_ext_idx); @@ -4763,6 +4784,22 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_) return; } + bool has_optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type); + if (m_print_type == PrintFromType::FROM_NORMAL) { + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + has_optional_printer_model = has_optional_printer_model || + (preset_bundle && DevPrinterConfigUtil::is_optional_printer_model_id( + preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle))); + } else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW && !m_required_data_plate_data_list.empty()) { + has_optional_printer_model = has_optional_printer_model || + DevPrinterConfigUtil::is_optional_printer_model_id( + m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id); + } + + if (has_optional_printer_model) { + show_status(PrintDialogStatus::PrintStatusOptionalPrinterModel); + } + if (is_blocking_printing(obj_)) { show_status(PrintDialogStatus::PrintStatusUnsupportedPrinter); return; @@ -5485,8 +5522,8 @@ void SelectMachineDialog::reset_and_sync_ams_list() item = new MaterialItem(m_filament_left_panel, colour_rgb, _L(display_materials[extruder])); m_sizer_ams_mapping_left->Add(item, 0, wxALL, FromDIP(5)); } - else if (m_filaments_map[extruder] == 2) - { + else // map == 2, or (non-BBL multi-nozzle) 3+; update_material_item_pos() collapses + { // these into the single panel when the device reports < 2 extruders. item = new MaterialItem(m_filament_right_panel, colour_rgb, _L(display_materials[extruder])); m_sizer_ams_mapping_right->Add(item, 0, wxALL, FromDIP(5)); } diff --git a/src/slic3r/GUI/SendMultiMachinePage.cpp b/src/slic3r/GUI/SendMultiMachinePage.cpp index b386d1f451..04becefeac 100644 --- a/src/slic3r/GUI/SendMultiMachinePage.cpp +++ b/src/slic3r/GUI/SendMultiMachinePage.cpp @@ -738,7 +738,7 @@ void SendMultiMachinePage::on_send(wxCommandEvent& event) if (obj && obj->is_online() && !obj->can_abort() && !obj->is_in_upgrading() && it->second->get_state_selected() == 1 && it->second->state_printable <= 2) { - if (!it->second->is_blocking_printing(obj)) { + if (!wxGetApp().is_blocking_printing(obj)) { PrintParams params = request_params(obj); print_params.push_back(params); } diff --git a/src/slic3r/GUI/SendToPrinter.cpp b/src/slic3r/GUI/SendToPrinter.cpp index 9071c0affd..d582cfdef7 100644 --- a/src/slic3r/GUI/SendToPrinter.cpp +++ b/src/slic3r/GUI/SendToPrinter.cpp @@ -24,6 +24,7 @@ #include "BitmapCache.hpp" #include "DeviceCore/DevManager.h" +#include "DeviceCore/DevConfigUtil.h" #include "DeviceCore/DevStorage.h" #include "slic3r/Utils/FileTransferUtils.hpp" @@ -300,7 +301,7 @@ SendToPrinterDialog::SendToPrinterDialog(Plater *plater) m_storage_panel->Layout(); // try to connect - m_statictext_printer_msg = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL); + m_statictext_printer_msg = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(400), -1), wxALIGN_CENTER_HORIZONTAL); m_statictext_printer_msg->SetFont(::Label::Body_13); m_statictext_printer_msg->SetForegroundColour(*wxBLACK); m_statictext_printer_msg->Hide(); @@ -760,9 +761,25 @@ void SendToPrinterDialog::update_priner_status_msg(wxString msg, bool is_warning if (str_new != str_old) { if (m_statictext_printer_msg->GetLabel() != msg) { m_statictext_printer_msg->SetLabel(msg); - m_statictext_printer_msg->SetMinSize(wxSize(FromDIP(400), -1)); - m_statictext_printer_msg->SetMaxSize(wxSize(FromDIP(400), -1)); - m_statictext_printer_msg->Wrap(FromDIP(400)); + const int wrap_width = FromDIP(400); + m_statictext_printer_msg->Wrap(wrap_width); + int line_count = 1; + const wxString wrapped_label = m_statictext_printer_msg->GetLabel(); + for (size_t i = 0; i < wrapped_label.length(); ++i) { + if (wrapped_label[i] == '\n') + ++line_count; + } + wxCoord text_width = 0; + wxCoord text_height = 0; + m_statictext_printer_msg->GetTextExtent(msg, &text_width, &text_height); + const int extent_line_count = text_width > 0 ? + std::max(1, (static_cast(text_width) + wrap_width - 1) / wrap_width) : 1; + line_count = std::max(line_count, extent_line_count); + const int line_height = std::max(m_statictext_printer_msg->GetCharHeight(), static_cast(text_height)); + const int min_height = std::max(m_statictext_printer_msg->GetBestSize().GetHeight(), + line_count * line_height + FromDIP(2)); + m_statictext_printer_msg->SetMinSize(wxSize(wrap_width, min_height)); + m_statictext_printer_msg->SetMaxSize(wxDefaultSize); m_statictext_printer_msg->Show(); Layout(); Fit(); @@ -1102,7 +1119,7 @@ void SendToPrinterDialog::update_user_printer() wxArrayString machine_list_name; std::map option_list; - option_list = dev->get_my_machine_list(); + option_list = dev->get_my_machine_list(dev->get_current_printer_agent_id()); // same machine only appear once for (auto it = option_list.begin(); it != option_list.end(); it++) { @@ -1260,7 +1277,7 @@ void SendToPrinterDialog::update_show_status() reset_timeout(); // reading done - if (is_blocking_printing(obj_)) { + if (wxGetApp().is_blocking_printing(obj_)) { show_status(PrintDialogStatus::PrintStatusUnsupportedPrinter); return; } @@ -1325,26 +1342,6 @@ void SendToPrinterDialog::update_show_status() } } -bool SendToPrinterDialog::is_blocking_printing(MachineObject* obj_) -{ - DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager(); - if (!dev) return true; - - PresetBundle* preset_bundle = wxGetApp().preset_bundle; - auto source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); - auto target_model = obj_->printer_type; - - if (source_model != target_model) { - std::vector compatible_machine = obj_->get_compatible_machine(); - vector::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model); - if (it == compatible_machine.end()) { - return true; - } - } - - return false; -} - void SendToPrinterDialog::Enable_Refresh_Button(bool en) { if (!en) { @@ -1413,79 +1410,68 @@ void SendToPrinterDialog::show_status(PrintDialogStatus status, std::vectorget_selected_machine() : nullptr; - MachineObject *obj = dm->get_selected_machine(); - if (obj == nullptr) { + if (!obj) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : obj is empty"; - m_connection_status = ConnectionStatus::NOT_START; - } - - int remote_proto = obj->get_file_remote(); - if (!remote_proto) { + if (obj && !obj->get_file_remote()) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : remote_proto is not support"; - m_connection_status = ConnectionStatus::NOT_START; - } + if (obj && obj->is_camera_busy_off()) + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : camera is busy"; - if (obj->is_camera_busy_off()) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : camera is busy"; - m_connection_status = ConnectionStatus::NOT_START; - } + NetworkAgent* agent = wxGetApp().getAgent(); - NetworkAgent *agent = wxGetApp().getAgent(); - std::string agent_version = agent ? agent->get_version() : ""; - std::string dev_ver = obj->get_ota_version(); - std::string dev_id = obj->get_dev_id(); - - if (m_url_timer && m_url_timer->IsRunning()) + if (m_url_timer && m_url_timer->IsRunning()) { m_url_timer->Stop(); } @@ -1709,19 +1683,40 @@ void SendToPrinterDialog::GetConnection() m_url_timer->GetId()); m_url_timer->StartOnce(8000); - if (agent) { + if (obj && agent) + { + std::string dev_ver = obj->get_ota_version(); + std::string dev_id = obj->get_dev_id(); + if (m_tcp_try_connect) { std::string devIP = obj->get_dev_ip(); std::string accessCode = obj->get_access_code(); std::string url = "bambu:///local/" + devIP + "?port=6000&user=" + "bblp" + "&passwd=" + accessCode; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tcp"; - m_filetransfer_tunnel = std::make_unique(module(), url); - m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) { - CallAfter([this, is_success, err_code, error_msg]() { - OnConnection(is_success, err_code, error_msg); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tcp, dev_id=" << dev_id + << ", dev_ip=" << devIP << ", access_code_len=" << accessCode.size(); + + try + { + m_filetransfer_tunnel = std::make_unique(module(), url); + m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) + { + CallAfter([this, is_success, err_code, error_msg]() + { + OnConnection(is_success, err_code, error_msg); + }); }); - }); - m_filetransfer_tunnel->start_connect(); + m_filetransfer_tunnel->start_connect(); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": tcp FileTransferTunnel unavailable for dev_id=" << + dev_id + << " dev_ip=" << devIP << ": " << e.what(); + if (m_url_timer && m_url_timer->IsRunning()) m_url_timer->Stop(); + m_filetransfer_tunnel.reset(); + m_connection_status = ConnectionStatus::CONNECTION_FAILED; + show_status(PrintDialogStatus::PrintStatusPublicInitFailed); + } } else if (m_tutk_try_connect) { @@ -1749,11 +1744,28 @@ void SendToPrinterDialog::GetConnection() if (boost::algorithm::starts_with(url, "bambu:///")) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tutk"; - m_filetransfer_tunnel = std::make_unique(module(), url); - m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) { - CallAfter([this, is_success, err_code, error_msg]() { OnConnection(is_success, err_code, error_msg); }); - }); - m_filetransfer_tunnel->start_connect(); + try + { + m_filetransfer_tunnel = std::make_unique(module(), url); + m_filetransfer_tunnel->on_connection( + [this](bool is_success, int err_code, std::string error_msg) + { + CallAfter([this, is_success, err_code, error_msg]() + { + OnConnection(is_success, err_code, error_msg); + }); + }); + m_filetransfer_tunnel->start_connect(); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": tutk FileTransferTunnel unavailable: " << e. +what(); + if (m_url_timer && m_url_timer->IsRunning()) m_url_timer->Stop(); + m_filetransfer_tunnel.reset(); + m_connection_status = ConnectionStatus::CONNECTION_FAILED; + show_status(PrintDialogStatus::PrintStatusPublicInitFailed); + } } else { @@ -1852,8 +1864,17 @@ void SendToPrinterDialog::ResetTunnelAndJob() void SendToPrinterDialog::CreateMediaAbilityJob() { - nlohmann::json media_ability = {{"cmd_type", 7}}; - m_filetransfer_mediability_job = std::make_unique(module(), std::string(media_ability.dump())); + nlohmann::json media_ability = {{"cmd_type", 7}}; + try + { + m_filetransfer_mediability_job = std::make_unique(module(), std::string(media_ability.dump())); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": FileTransferJob unavailable: " << e.what(); + show_status(PrintDialogStatus::PrintStatusPublicInitFailed); + return; + } m_filetransfer_mediability_job->on_result([this](int res, int resp_ec, std::string json_res, std::vector bin_res) { //this pl CallAfter([this, res, resp_ec, json_res] { @@ -1908,11 +1929,20 @@ void SendToPrinterDialog::CreateUploadFileJob(const std::string &path, const std {"cmd_type", 5}, }; upload_params["dest_storage"] = m_selected_storage; - upload_params["dest_name"] = name; // filenme no path - upload_params["file_path"] = path; + upload_params["dest_name"] = name; // filenme no path + upload_params["file_path"] = path; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Begin CreateUploadFileJob"; - m_filetransfer_uploadfile_job = std::make_unique(module(), std::string(upload_params.dump())); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Begin CreateUploadFileJob"; + try + { + m_filetransfer_uploadfile_job = std::make_unique(module(), std::string(upload_params.dump())); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": FileTransferJob unavailable: " << e.what(); + show_status(PrintDialogStatus::PrintStatusPublicUploadFiled); + return; + } m_filetransfer_uploadfile_job->on_result([this](int res, int resp_ec, std::string json_res, std::vector bin_res) { // CallAfter([this, res, resp_ec, json_res, bin_res] { UploadFileRessultCallback(res, resp_ec,json_res, bin_res); diff --git a/src/slic3r/GUI/SendToPrinter.hpp b/src/slic3r/GUI/SendToPrinter.hpp index 24e483ae33..ddfb017552 100644 --- a/src/slic3r/GUI/SendToPrinter.hpp +++ b/src/slic3r/GUI/SendToPrinter.hpp @@ -181,7 +181,6 @@ public: void reset_timeout(); void update_user_printer(); void update_show_status(); - bool is_blocking_printing(MachineObject* obj_); void prepare(int print_plate_idx); void check_focus(wxWindow* window); void check_fcous_state(wxWindow* window); diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index 0b6e4b6577..e4a9d906c0 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "DeviceCore/DevBed.h" #include "DeviceCore/DevCtrl.h" @@ -1501,27 +1502,26 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page() m_setting_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24))); m_setting_button->SetBackgroundColour(STATUS_TITLE_BG); - m_camera_switch_button = new wxStaticBitmap(m_panel_monitoring_title, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(38), FromDIP(24)), 0); - m_camera_switch_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24))); - m_camera_switch_button->SetBackgroundColour(STATUS_TITLE_BG); - m_camera_switch_button->SetBitmap(m_bitmap_switch_camera.bmp()); - m_camera_switch_button->Bind(wxEVT_LEFT_DOWN, &StatusBasePanel::on_camera_switch_toggled, this); - m_camera_switch_button->Bind(wxEVT_RIGHT_DOWN, [this](auto& e) { - const std::string js_request_pip = R"( - document.querySelector('video').requestPictureInPicture(); - )"; - m_custom_camera_view->RunScript(js_request_pip); - }); - m_camera_switch_button->Hide(); + // m_camera_switch_button = new wxStaticBitmap(m_panel_monitoring_title, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(38), FromDIP(24)), 0); + // m_camera_switch_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24))); + // m_camera_switch_button->SetBackgroundColour(STATUS_TITLE_BG); + // m_camera_switch_button->SetBitmap(m_bitmap_switch_camera.bmp()); + // m_camera_switch_button->Bind(wxEVT_RIGHT_DOWN, [this](auto& e) { + // const std::string js_request_pip = R"( + // document.querySelector('video').requestPictureInPicture(); + // )"; + // m_custom_camera_view->RunScript(js_request_pip); + // }); + // m_camera_switch_button->Hide(); m_bitmap_sdcard_img->SetToolTip(_L("Storage")); m_bitmap_timelapse_img->SetToolTip(_L("Timelapse")); m_bitmap_recording_img->SetToolTip(_L("Video")); m_bitmap_vcamera_img->SetToolTip(_L("Go Live")); m_setting_button->SetToolTip(_L("Camera Setting")); - m_camera_switch_button->SetToolTip(_L("Switch Camera View")); + // m_camera_switch_button->SetToolTip(_L("Switch Camera View")); - bSizer_monitoring_title->Add(m_camera_switch_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); + // bSizer_monitoring_title->Add(m_camera_switch_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); bSizer_monitoring_title->Add(m_bitmap_sdcard_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); bSizer_monitoring_title->Add(m_bitmap_timelapse_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); bSizer_monitoring_title->Add(m_bitmap_recording_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5)); @@ -1544,19 +1544,18 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page() m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString); m_custom_camera_view->EnableContextMenu(false); Bind(wxEVT_WEBVIEW_NAVIGATING, &StatusBasePanel::on_webview_navigating, this, m_custom_camera_view->GetId()); + m_web_media_controller = std::make_unique(m_custom_camera_view); m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40))); + m_media_play_ctrl->SetWebMediaController(m_web_media_controller.get()); m_custom_camera_view->Hide(); - m_custom_camera_view->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) { - if (evt.GetString() == "leavepictureinpicture") { - // When leaving PiP, video gets paused in some cases and toggling play - // programmatically does not work. - m_custom_camera_view->Reload(); - } - else if (evt.GetString() == "enterpictureinpicture") { - toggle_builtin_camera(); - } - }); + // m_custom_camera_view->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) { + // if (evt.GetString() == "leavepictureinpicture") { + // // When leaving PiP, video gets paused in some cases and toggling play + // // programmatically does not work. + // m_custom_camera_view->Reload(); + // } + // }); sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALL, 0); sizer->Add(m_custom_camera_view, 1, wxEXPAND | wxALL, 0); @@ -1566,10 +1565,6 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page() // // sizer->Add(media_ctrl_panel, 1, wxEXPAND | wxALL, 1); - if (wxGetApp().app_config->get("camera", "enable_custom_source") == "true") { - handle_camera_source_change(); - } - return sizer; } @@ -2333,6 +2328,27 @@ void StatusPanel::update_camera_state(MachineObject* obj) { if (!obj) return; + auto agent = wxGetApp().getAgent(); + const auto camera_mode = agent ? agent->get_camera_stream_mode() : CameraStreamMode::none; + const bool use_webview = camera_mode == CameraStreamMode::http_snapshot; + if (use_webview) { + //m_camera_switch_button->Hide(); + if (!m_custom_camera_view->IsShown()) { + // why: do not reload the WebView URL per tick, or redirects can cause a reload loop. + // MediaPlayCtrl (via its WebMediaController) owns loading/playing the stream itself. + m_custom_camera_view->Show(); + m_media_ctrl->Hide(); + } + } else { + if (m_custom_camera_view->IsShown()) { + m_custom_camera_view->Hide(); + // Stop the snapshot WebView before switching to native playback + // or leaving the camera mode. + m_media_play_ctrl->StopWebStream(); + } + m_media_ctrl->Show(); + } + //sdcard auto sdcard_state = obj->GetStorage()->get_sdcard_state(); if (m_last_sdcard != sdcard_state) { @@ -2364,7 +2380,12 @@ void StatusPanel::update_camera_state(MachineObject* obj) m_last_recording = obj->is_recording() ? 1 : 0; } - if (!m_bitmap_recording_img->IsShown()) { + if (use_webview) { + if (m_bitmap_recording_img->IsShown()) { + m_bitmap_recording_img->Hide(); + m_panel_monitoring_title->Layout(); + } + } else if (!m_bitmap_recording_img->IsShown()) { m_bitmap_recording_img->Show(); m_panel_monitoring_title->Layout(); } @@ -2421,6 +2442,8 @@ void StatusPanel::update_camera_state(MachineObject* obj) bool show_vcamera = m_media_play_ctrl->IsStreaming(); m_camera_popup->update(show_vcamera); } + + m_setting_button->Show(!use_webview); } StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name) @@ -3951,39 +3974,60 @@ void StatusPanel::update_cloud_subtask(MachineObject *obj) update_calib_bitmap(); if (obj->slice_info) { m_request_url = wxString(obj->slice_info->thumbnail_url); - if (!m_request_url.IsEmpty()) { - wxImage img; - std::map::iterator it = img_list.find(m_request_url); - if (it != img_list.end()) { - if (m_current_print_mode != PrintingTaskType::CALIBRATION ||(m_calib_mode == CalibMode::Calib_Flow_Rate && m_calib_method == CalibrationMethod::CALI_METHOD_MANUAL)) { - img = it->second; - wxImage resize_img = img.Scale(m_project_task_panel->get_bitmap_thumbnail()->GetSize().x, m_project_task_panel->get_bitmap_thumbnail()->GetSize().y); - m_project_task_panel->set_thumbnail_img(resize_img, ""); - m_project_task_panel->set_brightness_value(get_brightness_value(resize_img)); - } - if (this->obj) { - m_project_task_panel->set_plate_index(obj->m_plate_index); - } else { - m_project_task_panel->set_plate_index(-1); - } - task_thumbnail_state = ThumbnailState::TASK_THUMBNAIL; - BOOST_LOG_TRIVIAL(trace) << "web_request: use cache image"; - } else { - web_request = wxWebSession::GetDefault().CreateRequest(this, m_request_url); - BOOST_LOG_TRIVIAL(trace) << "monitor: start request thumbnail, url = " << m_request_url; - web_request.Start(); - m_start_loading_thumbnail = false; - } - } + load_thumbnail_from_url(m_request_url, obj); } } } +bool StatusPanel::load_thumbnail_from_url(const wxString &url, MachineObject *obj) +{ + if (url.IsEmpty()) + return false; + + wxImage img; + std::map::iterator it = img_list.find(url); + if (it != img_list.end()) { + if (m_current_print_mode != PrintingTaskType::CALIBRATION ||(m_calib_mode == CalibMode::Calib_Flow_Rate && m_calib_method == CalibrationMethod::CALI_METHOD_MANUAL)) { + img = it->second; + wxImage resize_img = img.Scale(m_project_task_panel->get_bitmap_thumbnail()->GetSize().x, m_project_task_panel->get_bitmap_thumbnail()->GetSize().y); + m_project_task_panel->set_thumbnail_img(resize_img, ""); + m_project_task_panel->set_brightness_value(get_brightness_value(resize_img)); + } + if (this->obj) { + m_project_task_panel->set_plate_index(obj->m_plate_index); + } else { + m_project_task_panel->set_plate_index(-1); + } + task_thumbnail_state = ThumbnailState::TASK_THUMBNAIL; + BOOST_LOG_TRIVIAL(trace) << "web_request: use cache image"; + } else { + m_request_url = url; + web_request = wxWebSession::GetDefault().CreateRequest(this, m_request_url); + BOOST_LOG_TRIVIAL(trace) << "monitor: start request thumbnail, url = " << m_request_url; + web_request.Start(); + m_start_loading_thumbnail = false; + } + return true; +} + void StatusPanel::update_sdcard_subtask(MachineObject *obj) { if (!obj) return; - if (!m_load_sdcard_thumbnail) { + const wxString thumbnail_url = wxString(obj->m_agent_thumbnail_url); + if (!thumbnail_url.IsEmpty()) { + if (m_request_url != thumbnail_url || !m_load_sdcard_thumbnail) { + if (web_request.IsOk() && web_request.GetState() == wxWebRequest::State_Active) + web_request.Cancel(); + update_calib_bitmap(); + m_request_url = thumbnail_url; + load_thumbnail_from_url(thumbnail_url, obj); + m_load_sdcard_thumbnail = true; + } + return; + } + + if (!m_load_sdcard_thumbnail || !m_request_url.IsEmpty()) { update_calib_bitmap(); if (m_current_print_mode != PrintingTaskType::CALIBRATION) { m_project_task_panel->get_bitmap_thumbnail()->SetBitmap(m_thumbnail_sdcard.bmp()); @@ -3991,6 +4035,7 @@ void StatusPanel::update_sdcard_subtask(MachineObject *obj) } task_thumbnail_state = ThumbnailState::SDCARD_THUMBNAIL; m_load_sdcard_thumbnail = true; + m_request_url.clear(); } } @@ -4720,11 +4765,11 @@ void StatusPanel::on_ams_refresh_rfid(wxCommandEvent &event) try { if (!use_new_command) { int tray_index = atoi(curr_ams_id.c_str()) * 4 + atoi(slot_it->second->id.c_str()); - obj->command_ams_refresh_rfid(std::to_string(tray_index)); + obj->command_ams_refresh_rfid(-1, tray_index); } if (use_new_command) { - obj->command_ams_refresh_rfid2(stoi(curr_ams_id), stoi(curr_can_id)); + obj->command_ams_refresh_rfid(stoi(curr_ams_id), stoi(curr_can_id)); } } catch (...) { @@ -4990,7 +5035,6 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event) } sdcard_hint_dlg->on_show(); }); - m_camera_popup->Bind(EVT_CAM_SOURCE_CHANGE, &StatusPanel::on_camera_source_change, this); wxWindow* ctrl = (wxWindow*)event.GetEventObject(); wxPoint pos = ctrl->ClientToScreen(wxPoint(0, 0)); wxSize sz = ctrl->GetSize(); @@ -5002,54 +5046,6 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event) } } -void StatusBasePanel::on_camera_source_change(wxCommandEvent& event) -{ - handle_camera_source_change(); -} - -void StatusBasePanel::handle_camera_source_change() -{ - const auto new_cam_url = wxGetApp().app_config->get("camera", "custom_source"); - const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true"; - - if (enabled && !new_cam_url.empty()) { - m_custom_camera_view->LoadURL(new_cam_url); - toggle_custom_camera(); - m_camera_switch_button->Show(); - } else { - toggle_builtin_camera(); - m_camera_switch_button->Hide(); - } -} - -void StatusBasePanel::toggle_builtin_camera() -{ - m_custom_camera_view->Hide(); - m_media_ctrl->Show(); - m_media_play_ctrl->Show(); -} - -void StatusBasePanel::toggle_custom_camera() -{ - const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true"; - - if (enabled) { - m_custom_camera_view->Show(); - m_media_ctrl->Hide(); - m_media_play_ctrl->Hide(); - } -} - -void StatusBasePanel::on_camera_switch_toggled(wxMouseEvent& event) -{ - const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true"; - if (enabled && m_media_ctrl->IsShown()) { - toggle_custom_camera(); - } else { - toggle_builtin_camera(); - } -} - void StatusBasePanel::remove_controls() { const std::string js_cleanup_video_element = R"( @@ -5202,6 +5198,11 @@ bool StatusPanel::is_stage_list_info_changed(MachineObject *obj) void StatusPanel::set_default() { BOOST_LOG_TRIVIAL(trace) << "status_panel: set_default"; + if (m_custom_camera_view->IsShown()) { + m_custom_camera_view->Hide(); + m_media_ctrl->Show(); + m_media_play_ctrl->StopWebStream(); + } obj = nullptr; last_subtask = nullptr; last_tray_exist_bits = -1; diff --git a/src/slic3r/GUI/StatusPanel.hpp b/src/slic3r/GUI/StatusPanel.hpp index d79c3d3915..0666703475 100644 --- a/src/slic3r/GUI/StatusPanel.hpp +++ b/src/slic3r/GUI/StatusPanel.hpp @@ -14,7 +14,9 @@ #include #include #include +#include #include "MediaPlayCtrl.h" +#include "WebMediaController.hpp" #include "AMSSetting.hpp" #include "Calibration.hpp" #include "CalibrationWizardPage.hpp" @@ -440,7 +442,7 @@ protected: wxStaticBitmap *m_bitmap_sdcard_img; wxStaticBitmap *m_bitmap_static_use_time; wxStaticBitmap *m_bitmap_static_use_weight; - wxStaticBitmap* m_camera_switch_button; + // wxStaticBitmap* m_camera_switch_button; wxMediaCtrl3 * m_media_ctrl; @@ -462,6 +464,8 @@ protected: ScalableButton *m_button_abort; Button * m_button_clean; wxWebView * m_custom_camera_view{nullptr}; + std::unique_ptr m_web_media_controller; + wxSimplebook* m_extruder_book; std::vector m_extruderImage; @@ -577,13 +581,8 @@ protected: virtual void on_axis_ctrl_e_up_10(wxCommandEvent &event) { event.Skip(); } virtual void on_axis_ctrl_e_down_10(wxCommandEvent &event) { event.Skip(); } virtual void on_nozzle_selected(wxCommandEvent &event) { event.Skip(); } - void on_camera_source_change(wxCommandEvent& event); - void handle_camera_source_change(); void remove_controls(); void on_webview_navigating(wxWebViewEvent& evt); - void on_camera_switch_toggled(wxMouseEvent& event); - void toggle_custom_camera(); - void toggle_builtin_camera(); public: StatusBasePanel(wxWindow * parent, @@ -631,6 +630,7 @@ class StatusPanel : public StatusBasePanel private: friend class MonitorPanel; void wire_controls(); + bool load_thumbnail_from_url(const wxString &url, MachineObject *obj); protected: std::shared_ptr m_slice_info_popup; diff --git a/src/slic3r/GUI/SyncAmsInfoDialog.cpp b/src/slic3r/GUI/SyncAmsInfoDialog.cpp index 07c75e809a..b481a1b71b 100644 --- a/src/slic3r/GUI/SyncAmsInfoDialog.cpp +++ b/src/slic3r/GUI/SyncAmsInfoDialog.cpp @@ -1746,6 +1746,8 @@ void SyncAmsInfoDialog::show_status(PrintDialogStatus status, std::vectorprinter_type; - std::string source_model = ""; + if (m_print_type == PrintFromType::FROM_NORMAL) + return wxGetApp().is_blocking_printing(obj_); - if (m_print_type == PrintFromType::FROM_NORMAL) { - PresetBundle *preset_bundle = wxGetApp().preset_bundle; - source_model = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); - - } else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW) { - if (m_required_data_plate_data_list.size() > 0) { source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id; } - } - - if (source_model != target_model) { - std::vector compatible_machine = obj_->get_compatible_machine(); - vector::iterator it = find(compatible_machine.begin(), compatible_machine.end(), source_model); - if (it == compatible_machine.end()) { return true; } - } - - return false; + std::string source_model; + if (m_print_type == PrintFromType::FROM_SDCARD_VIEW && !m_required_data_plate_data_list.empty()) + source_model = m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id; + return wxGetApp().is_blocking_printing(obj_, source_model); } bool SyncAmsInfoDialog::is_same_nozzle_type(std::string &filament_type, NozzleType &tag_nozzle_type) @@ -1934,7 +1923,13 @@ bool SyncAmsInfoDialog::is_same_printer_model() if (obj_ == nullptr) { return result; } PresetBundle *preset_bundle = wxGetApp().preset_bundle; - if (preset_bundle && preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) != obj_->printer_type) { + const std::string source_model = preset_bundle ? preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) : std::string(); + if (DevPrinterConfigUtil::is_optional_printer_model_id(source_model) || + DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type)) { + return true; + } + + if (preset_bundle && source_model != obj_->printer_type) { if ((obj_->is_support_upgrade_kit && obj_->installed_upgrade_kit) && (preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle) == "C12")) { return true; } @@ -2135,7 +2130,7 @@ void SyncAmsInfoDialog::update_user_printer() std::map option_list; // user machine list - option_list = dev->get_my_machine_list(); + option_list = dev->get_my_machine_list(dev->get_current_printer_agent_id()); // same machine only appear once for (auto it = option_list.begin(); it != option_list.end(); it++) { @@ -2268,6 +2263,18 @@ void SyncAmsInfoDialog::update_show_status() reset_timeout(); + bool has_optional_printer_model = DevPrinterConfigUtil::is_optional_printer_model_id(obj_->printer_type); + if (m_print_type == PrintFromType::FROM_NORMAL) { + if (PresetBundle *preset_bundle = wxGetApp().preset_bundle) { + has_optional_printer_model = has_optional_printer_model || + DevPrinterConfigUtil::is_optional_printer_model_id( + preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle)); + } + } else if (m_print_type == PrintFromType::FROM_SDCARD_VIEW && !m_required_data_plate_data_list.empty()) { + has_optional_printer_model = has_optional_printer_model || + DevPrinterConfigUtil::is_optional_printer_model_id(m_required_data_plate_data_list[m_print_plate_idx]->printer_model_id); + } + if (!obj_->GetConfig()->SupportPrintAllPlates() && m_print_plate_idx == PLATE_ALL_IDX) { show_status(PrintDialogStatus::PrintStatusNotSupportedPrintAll); return; @@ -2361,6 +2368,9 @@ void SyncAmsInfoDialog::update_show_status() } } } + + if (has_optional_printer_model) + show_status(PrintDialogStatus::PrintStatusOptionalPrinterModel); } bool SyncAmsInfoDialog::has_timelapse_warning() @@ -3210,6 +3220,7 @@ SyncAmsInfoDialog::~SyncAmsInfoDialog() { void SyncAmsInfoDialog::set_info(SyncInfo &info) { m_input_info = info; + reinit_dialog(); } void SyncAmsInfoDialog::update_lan_machine_list() diff --git a/src/slic3r/GUI/WebMediaController.cpp b/src/slic3r/GUI/WebMediaController.cpp new file mode 100644 index 0000000000..fe4b94d3ac --- /dev/null +++ b/src/slic3r/GUI/WebMediaController.cpp @@ -0,0 +1,90 @@ +#include "WebMediaController.hpp" + +#include + +namespace Slic3r { namespace GUI { + +namespace { + +void initialize_webview(wxWebView* webview) +{ + if (!webview) + return; + + webview->SetBackgroundColour(*wxBLACK); + webview->SetPage("", ""); +} + +} // namespace + +WebMediaController::WebMediaController(wxWebView* webview) + : m_webview(webview) +{ + initialize_webview(m_webview); +} + +void WebMediaController::Load(wxURI url) +{ + m_url = url.BuildURI().ToStdString(); +} + +void WebMediaController::set_mode(CameraStreamMode mode) +{ + m_stream_mode = mode; +} + +void WebMediaController::Play() +{ + if (!m_webview) + return; + + wxString url = wxString::FromUTF8(m_url); + wxString html = ""; + m_webview->SetPage(html, url); + } else { + // Load MJPEG streams as the top-level document. Some embedded WebView + // backends buffer a multipart stream when it is used as an resource, + // which introduces noticeable live-view latency. + m_webview->LoadURL(url); + } +} + +void WebMediaController::Stop() +{ + if (m_webview) { + m_webview->RunScript("if(typeof stopCameraRefresh==='function') stopCameraRefresh();"); + m_webview->Stop(); + m_webview->SetPage("", "about:blank"); + m_webview->ClearHistory(); + } + m_url.clear(); +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/WebMediaController.hpp b/src/slic3r/GUI/WebMediaController.hpp new file mode 100644 index 0000000000..e7756d8977 --- /dev/null +++ b/src/slic3r/GUI/WebMediaController.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include + +class wxWebView; + +namespace Slic3r { namespace GUI { + +class WebMediaController : public IMediaController +{ +public: + explicit WebMediaController(wxWebView* webview); + + void Load(wxURI url) override; + + void set_mode(CameraStreamMode mode); + + void Play() override; + + void Stop() override; + +private: + wxWebView* m_webview = nullptr; + std::string m_url; + CameraStreamMode m_stream_mode = CameraStreamMode::http; +}; + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/WebRtcMediaController.cpp b/src/slic3r/GUI/WebRtcMediaController.cpp new file mode 100644 index 0000000000..9c03c7c8b1 --- /dev/null +++ b/src/slic3r/GUI/WebRtcMediaController.cpp @@ -0,0 +1,401 @@ +#include "WebRtcMediaController.hpp" + +#include +#include + +#include +#include + +#include + +namespace { +void init_rtc_logger_once() +{ + static std::once_flag flag; + std::call_once(flag, [] { + rtc::InitLogger(rtc::LogLevel::Warning, [](rtc::LogLevel level, std::string message) { + BOOST_LOG_TRIVIAL(trace) << "[rtc:" << static_cast(level) << "] " << message; + }); + }); +} +} // namespace + +namespace Slic3r { namespace GUI { + +WebRtcMediaController::WebRtcMediaController(std::function frame_sink, + std::function on_status) + : m_frame_sink(std::move(frame_sink)) + , m_on_status(std::move(on_status)) +{ +} + +WebRtcMediaController::~WebRtcMediaController() +{ + Stop(); +} + +void WebRtcMediaController::report(Status status) +{ + status.epoch = m_epoch.load(); + BOOST_LOG_TRIVIAL(info) << "WebRTC: report kind=" << static_cast(status.kind) + << " code=" << static_cast(status.code) << " epoch=" << status.epoch; + { + std::lock_guard lock(m_mutex); + if (status.kind == Status::Connecting) + m_state = static_cast(4); + else if (status.kind == Status::Playing) + m_state = wxMEDIASTATE_PLAYING; + else + m_state = static_cast(3); + } + if (m_on_status) + m_on_status(status); +} + +void WebRtcMediaController::set_signalling_channel(std::unique_ptr channel) +{ + m_pending_signaling = std::move(channel); +} + +void WebRtcMediaController::Play() +{ + std::unique_ptr channel = std::move(m_pending_signaling); + // Tear down any previous attempt WITHOUT notifying: the Stopped that would + // otherwise be delivered (async, via CallAfter) races the new attempt's + // Connecting and makes the consumer cancel a session that is mid-connect. + teardown(false); + if (!channel) + return; + + m_epoch.fetch_add(1); + m_alive.store(true); + { + std::lock_guard lock(m_mutex); + m_signaling = std::move(channel); + m_jpeg_queue.clear(); + m_pending_candidates.clear(); + m_remote_description_set = false; + m_video_size = wxDefaultSize; + m_has_frame = false; + m_last_frame_time = {}; + } + + ICameraSignalingChannel* signaling = nullptr; + { + std::lock_guard lock(m_mutex); + signaling = m_signaling.get(); + } + signaling->on_ready = [this](std::vector servers) { + if (m_alive.load()) + on_ready(std::move(servers)); + }; + signaling->on_answer = [this](std::string sdp) { + if (m_alive.load()) + on_answer(std::move(sdp)); + }; + signaling->on_ice = [this](std::string candidate, std::string mid) { + if (m_alive.load()) + on_ice(std::move(candidate), std::move(mid)); + }; + signaling->on_unavailable = [this](CameraUnavailableReason reason, std::string detail) { + if (m_alive.load()) + on_unavailable(reason, std::move(detail)); + }; + + m_decode_thread = std::thread([this] { decode_loop(); }); + report({Status::Connecting}); + signaling->open(); +} + +void WebRtcMediaController::Stop() +{ + teardown(true); +} + +void WebRtcMediaController::teardown(bool notify) +{ + const bool was_alive = m_alive.exchange(false); + if (!was_alive && !m_decode_thread.joinable()) + return; + + m_cond.notify_all(); + std::unique_ptr signaling; + std::shared_ptr peer_connection; + { + std::lock_guard lock(m_mutex); + signaling = std::move(m_signaling); + peer_connection = std::move(m_peer_connection); + m_data_channel.reset(); + } + if (signaling) + signaling->close(); + if (peer_connection) + peer_connection->close(); + if (m_decode_thread.joinable()) + m_decode_thread.join(); + { + std::lock_guard lock(m_mutex); + m_jpeg_queue.clear(); + } + if (was_alive && notify) + report({Status::Stopped}); +} + +wxMediaState WebRtcMediaController::GetState() +{ + std::lock_guard lock(m_mutex); + return m_state; +} + +wxSize WebRtcMediaController::GetVideoSize() const +{ + std::lock_guard lock(m_mutex); + return m_video_size; +} + +void WebRtcMediaController::bind_data_channel(const std::shared_ptr& dc) +{ + const std::string label = dc->label(); + dc->onOpen([label] { BOOST_LOG_TRIVIAL(info) << "WebRTC: data channel '" << label << "' open"; }); + dc->onClosed([label] { BOOST_LOG_TRIVIAL(info) << "WebRTC: data channel '" << label << "' closed"; }); + dc->onError([label](std::string e) { + BOOST_LOG_TRIVIAL(warning) << "WebRTC: data channel '" << label << "' error: " << e; + }); + dc->onMessage( + [this](rtc::binary data) { + if (m_alive.load()) + enqueue_jpeg(std::vector(data.begin(), data.end())); + }, + [](rtc::string) {}); +} + +void WebRtcMediaController::on_ready(std::vector servers) +{ + init_rtc_logger_once(); + rtc::Configuration configuration; + // Allow complete-JPEG DataChannel messages up to 1 MiB. This value is + // advertised in SDP and becomes the upper bound for frames OrcaSonar can + // send to OrcaSlicer. + configuration.maxMessageSize = 1024 * 1024; + for (const CameraIceServer& server : servers) { + try { + rtc::IceServer ice_server(server.urls); + ice_server.username = server.username; + ice_server.password = server.credential; + configuration.iceServers.emplace_back(std::move(ice_server)); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(warning) << "WebRTC: invalid ICE server: " << e.what(); + } + } + + BOOST_LOG_TRIVIAL(info) << "WebRTC: creating peer connection with " << configuration.iceServers.size() << " ice servers"; + auto peer_connection = std::make_shared(std::move(configuration)); + peer_connection->onLocalDescription([this](rtc::Description description) { + if (!m_alive.load()) + return; + const std::string sdp(description); + BOOST_LOG_TRIVIAL(info) << "WebRTC: local description ready (" << description.typeString() + << "), OFFER SDP:\n" << sdp; + std::lock_guard lock(m_mutex); + if (m_signaling) + m_signaling->send_offer(sdp); + }); + peer_connection->onLocalCandidate([this](rtc::Candidate candidate) { + if (!m_alive.load()) + return; + std::lock_guard lock(m_mutex); + if (m_signaling) + m_signaling->send_ice(std::string(candidate), candidate.mid()); + }); + peer_connection->onStateChange([this](rtc::PeerConnection::State state) { + BOOST_LOG_TRIVIAL(info) << "WebRTC: peer state -> " << static_cast(state); + if (!m_alive.load()) + return; + if (state == rtc::PeerConnection::State::Failed || state == rtc::PeerConnection::State::Disconnected) + report({Status::Failed, Status::ICE_FAILED}); + }); + peer_connection->onGatheringStateChange([](rtc::PeerConnection::GatheringState state) { + BOOST_LOG_TRIVIAL(info) << "WebRTC: gathering state -> " << static_cast(state); + }); + + // Accept a DataChannel opened by the remote peer (OrcaSonar may create the + // "camera" channel from its side rather than answering the one we offer). + peer_connection->onDataChannel([this](std::shared_ptr dc) { + BOOST_LOG_TRIVIAL(info) << "WebRTC: remote opened data channel '" << dc->label() << "'"; + bind_data_channel(dc); + std::lock_guard lock(m_mutex); + m_data_channel = std::move(dc); + }); + + rtc::DataChannelInit init; + init.reliability.unordered = true; + // init.reliability.maxPacketLifeTime = std::chrono::milliseconds(350); + // Request one complete JPEG frame per DataChannel message. OrcaSonar + // keeps the legacy chunked protocol for clients that omit this property. + init.protocol = "orca-jpeg"; + auto data_channel = peer_connection->createDataChannel("camera", init); + if (data_channel) + bind_data_channel(data_channel); + + // Camera media is carried as one complete JPEG per DataChannel message; + // no RTP video track or application-level framing is required. + + std::shared_ptr peer_for_description; + { + std::lock_guard lock(m_mutex); + if (!m_alive.load()) + return; + m_peer_connection = std::move(peer_connection); + peer_for_description = m_peer_connection; + m_data_channel = std::move(data_channel); + } + if (peer_for_description) + peer_for_description->setLocalDescription(); +} + +void WebRtcMediaController::on_answer(std::string sdp) +{ + std::shared_ptr peer_connection; + { + std::lock_guard lock(m_mutex); + peer_connection = m_peer_connection; + } + BOOST_LOG_TRIVIAL(info) << "WebRTC: applying remote answer (" << sdp.size() << " bytes), ANSWER SDP:\n" << sdp; + if (!peer_connection) + return; + try { + peer_connection->setRemoteDescription(rtc::Description(sdp, "answer")); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(warning) << "WebRTC: setRemoteDescription failed: " << e.what(); + report({Status::Failed, Status::ICE_FAILED}); + return; + } + + // Flush any remote candidates that arrived before the answer. + std::vector> pending; + { + std::lock_guard lock(m_mutex); + m_remote_description_set = true; + pending.swap(m_pending_candidates); + } + BOOST_LOG_TRIVIAL(info) << "WebRTC: remote description set, flushing " << pending.size() + << " buffered candidate(s)"; + for (const auto& c : pending) { + try { + peer_connection->addRemoteCandidate(rtc::Candidate(c.first, c.second)); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(warning) << "WebRTC: addRemoteCandidate (buffered) failed: " << e.what(); + } + } +} + +void WebRtcMediaController::on_ice(std::string candidate, std::string mid) +{ + std::shared_ptr peer_connection; + { + std::lock_guard lock(m_mutex); + if (!m_remote_description_set) { + m_pending_candidates.emplace_back(std::move(candidate), std::move(mid)); + return; + } + peer_connection = m_peer_connection; + } + if (!peer_connection) + return; + try { + peer_connection->addRemoteCandidate(rtc::Candidate(candidate, mid)); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(warning) << "WebRTC: addRemoteCandidate failed: " << e.what(); + } +} + +void WebRtcMediaController::on_unavailable(CameraUnavailableReason reason, std::string detail) +{ + BOOST_LOG_TRIVIAL(warning) << "WebRTC camera unavailable: " << detail; + Status::Code code = Status::UNAVAILABLE_ERROR; + if (reason == CameraUnavailableReason::Busy) + code = Status::UNAVAILABLE_BUSY; + else if (reason == CameraUnavailableReason::Disabled) + code = Status::UNAVAILABLE_DISABLED; + else if (reason == CameraUnavailableReason::Closed) + code = Status::SIGNALING_CLOSED; + report({Status::Failed, code}); +} + +void WebRtcMediaController::enqueue_jpeg(std::vector jpeg) +{ + std::lock_guard lock(m_mutex); + if (m_jpeg_queue.size() >= 4) + m_jpeg_queue.pop_front(); + m_jpeg_queue.emplace_back(std::move(jpeg)); + m_cond.notify_one(); +} + +void WebRtcMediaController::deliver_jpeg(std::vector jpeg) +{ + const auto now = std::chrono::steady_clock::now(); + { + std::lock_guard lock(m_mutex); + if (m_last_frame_time != std::chrono::steady_clock::time_point{} && + now - m_last_frame_time < std::chrono::milliseconds(33)) + return; + m_last_frame_time = now; + } + wxMemoryInputStream stream(jpeg.data(), jpeg.size()); + wxImage image; + if (!image.LoadFile(stream, wxBITMAP_TYPE_JPEG)) { + report({Status::Failed, Status::DECODE_ERROR}); + return; + } + bool first_frame = false; + { + std::lock_guard lock(m_mutex); + m_video_size = image.GetSize(); + first_frame = !m_has_frame; + m_has_frame = true; + } + if (m_frame_sink) + m_frame_sink(image, image.GetSize()); + if (first_frame) + report({Status::Playing}); +} + +void WebRtcMediaController::decode_loop() +{ + int stall_polls = 0; + std::unique_lock lock(m_mutex); + while (m_alive.load()) { + const bool woke = m_cond.wait_for(lock, std::chrono::seconds(2), [this] { + return !m_alive.load() || !m_jpeg_queue.empty(); + }); + if (!m_alive.load()) + break; + if (!woke && !m_has_frame) { + const int pc_state = m_peer_connection ? static_cast(m_peer_connection->state()) : -1; + std::string dc = "none"; + if (m_data_channel) + dc = "label='" + m_data_channel->label() + "' open=" + + (m_data_channel->isOpen() ? "1" : "0"); + lock.unlock(); + BOOST_LOG_TRIVIAL(info) << "WebRTC: waiting for frames; peer_state=" << pc_state + << " data_channel=" << dc; + if (++stall_polls >= 8) { // ~16s connected with no frame -> give up so the UI can retry + report({Status::Failed, Status::TIMEOUT}); + lock.lock(); + break; + } + lock.lock(); + continue; + } + stall_polls = 0; + if (!m_jpeg_queue.empty()) { + auto jpeg = std::move(m_jpeg_queue.front()); + m_jpeg_queue.pop_front(); + lock.unlock(); + deliver_jpeg(std::move(jpeg)); + lock.lock(); + } + } +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/WebRtcMediaController.hpp b/src/slic3r/GUI/WebRtcMediaController.hpp new file mode 100644 index 0000000000..7a2ab02abb --- /dev/null +++ b/src/slic3r/GUI/WebRtcMediaController.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include "IMediaController.hpp" +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rtc { +class DataChannel; +class PeerConnection; +} + +namespace Slic3r { namespace GUI { + +class WebRtcMediaController : public IMediaController { +public: + struct Status { + enum Kind { Connecting, Playing, Stopped, Failed } kind = Stopped; + enum Code { + ICE_FAILED, + SIGNALING_CLOSED, + UNAVAILABLE_BUSY, + UNAVAILABLE_ERROR, + UNAVAILABLE_DISABLED, + DECODE_ERROR, + TIMEOUT, + } code = ICE_FAILED; + // Identifies the Play attempt this status belongs to, so the + // consumer can drop CallAfter-queued events from a superseded attempt. + std::uint64_t epoch = 0; + }; + + WebRtcMediaController(std::function frame_sink, + std::function on_status); + ~WebRtcMediaController() override; + + void set_signalling_channel(std::unique_ptr channel); + std::uint64_t epoch() const { return m_epoch.load(); } + bool is_active() const { return m_alive.load(); } + + void Load(wxURI) override {} + void Play() override; + void Stop() override; + wxMediaState GetState() override; + wxSize GetVideoSize() const override; + +private: + void teardown(bool notify); + void report(Status status); + void bind_data_channel(const std::shared_ptr& dc); + void on_ready(std::vector servers); + void on_answer(std::string sdp); + void on_ice(std::string candidate, std::string mid); + void on_unavailable(CameraUnavailableReason reason, std::string detail); + void decode_loop(); + void enqueue_jpeg(std::vector jpeg); + void deliver_jpeg(std::vector jpeg); + + mutable std::mutex m_mutex; + std::condition_variable m_cond; + std::deque> m_jpeg_queue; + // Remote candidates can arrive before the answer; libdatachannel rejects + // addRemoteCandidate until a remote description is set, so buffer them. + std::vector> m_pending_candidates; + bool m_remote_description_set = false; + std::unique_ptr m_pending_signaling; + std::unique_ptr m_signaling; + std::shared_ptr m_peer_connection; + std::shared_ptr m_data_channel; + std::thread m_decode_thread; + std::atomic m_alive{false}; + std::atomic m_epoch{0}; + wxMediaState m_state = static_cast(3); + wxSize m_video_size = wxDefaultSize; + std::function m_frame_sink; + std::function m_on_status; + bool m_has_frame = false; + std::chrono::steady_clock::time_point m_last_frame_time{}; +}; + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/wxMediaCtrl3.cpp b/src/slic3r/GUI/wxMediaCtrl3.cpp index 098db68808..58e258d7f3 100644 --- a/src/slic3r/GUI/wxMediaCtrl3.cpp +++ b/src/slic3r/GUI/wxMediaCtrl3.cpp @@ -4,6 +4,14 @@ #include "libslic3r/Utils.hpp" #include #include +#include +#include +#include +#include +extern "C" { +#include +#include +} #ifdef __WIN32__ #include #include @@ -43,9 +51,13 @@ wxMediaCtrl3::~wxMediaCtrl3() m_thread.join(); } +static void adjust_frame_size(wxSize& frame, wxSize const& video, wxSize const& window); + void wxMediaCtrl3::Load(wxURI url) { std::unique_lock lk(m_mutex); + if (m_external) + return; m_video_size = wxDefaultSize; m_error = 0; m_url.reset(new wxURI(url)); @@ -55,6 +67,8 @@ void wxMediaCtrl3::Load(wxURI url) void wxMediaCtrl3::Play() { std::unique_lock lk(m_mutex); + if (m_external) + return; if (m_state != wxMEDIASTATE_PLAYING) { m_state = wxMEDIASTATE_PLAYING; wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); @@ -74,6 +88,62 @@ void wxMediaCtrl3::Stop() Refresh(); } +void wxMediaCtrl3::SetExternalFrame(const wxImage& frame, wxSize videoSize) +{ + if (!frame.IsOk()) + return; + { + std::unique_lock lk(m_mutex); + if (!m_external) + return; + m_frame = frame; + m_video_size = videoSize.IsFullySpecified() ? videoSize : frame.GetSize(); + adjust_frame_size(m_frame_size, m_video_size, GetSize()); + } + CallAfter([this] { Refresh(); }); +} + +#ifdef _WIN32 +void wxMediaCtrl3::SetExternalFrame(const wxBitmap& frame, wxSize videoSize) +{ + if (!frame.IsOk()) + return; + { + std::unique_lock lk(m_mutex); + if (!m_external) + return; + m_frame = frame; + m_video_size = videoSize.IsFullySpecified() ? videoSize : frame.GetSize(); + adjust_frame_size(m_frame_size, m_video_size, GetSize()); + } + CallAfter([this] { Refresh(); }); +} +#endif + +void wxMediaCtrl3::BeginExternalStream() +{ + std::unique_lock lk(m_mutex); + m_external = true; + m_url.reset(); + m_active_url.reset(); + m_video_size = wxDefaultSize; + m_frame = wxImage(m_idle_image); + m_cond.notify_all(); + Refresh(); +} + +void wxMediaCtrl3::EndExternalStream() +{ + std::unique_lock lk(m_mutex); + m_external = false; + m_url.reset(); + m_active_url.reset(); + m_video_size = wxDefaultSize; + m_frame = wxImage(m_idle_image); + m_cond.notify_all(); + Refresh(); +} + void wxMediaCtrl3::SetIdleImage(wxString const &image) { if (m_idle_image == image) @@ -93,13 +163,13 @@ wxMediaState wxMediaCtrl3::GetState() return m_state; } -int wxMediaCtrl3::GetLastError() +int wxMediaCtrl3::GetLastError() const { std::unique_lock lk(m_mutex); return m_error; } -wxSize wxMediaCtrl3::GetVideoSize() +wxSize wxMediaCtrl3::GetVideoSize() const { std::unique_lock lk(m_mutex); return m_video_size; @@ -185,6 +255,210 @@ void wxMediaCtrl3::bambu_log(void *ctx, int level, tchar const *msg2) BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data(); } +// FFmpeg's own diagnostics (HTTP status, "Invalid data found", demuxer choice, +// missing stream dimensions, ...) are otherwise swallowed: a failed camera open +// only surfaces as wxMediaCtrl3's generic error code, which MediaPlayCtrl maps to +// the misleading "Player is malfunctioning" string. Forward them to the Orca log +// instead. Verbosity defaults to AV_LOG_VERBOSE and can be raised at runtime with +// ORCA_FFMPEG_LOG_LEVEL=debug|trace|... (or lowered to warning/error/quiet). +static int ffmpeg_log_level_from_env() +{ + const char *env = std::getenv("ORCA_FFMPEG_LOG_LEVEL"); + if (env == nullptr || *env == '\0') + return AV_LOG_VERBOSE; + const wxString v = wxString(env).Lower(); + if (v == "quiet") return AV_LOG_QUIET; + if (v == "panic") return AV_LOG_PANIC; + if (v == "fatal") return AV_LOG_FATAL; + if (v == "error") return AV_LOG_ERROR; + if (v == "warning") return AV_LOG_WARNING; + if (v == "info") return AV_LOG_INFO; + if (v == "verbose") return AV_LOG_VERBOSE; + if (v == "debug") return AV_LOG_DEBUG; + if (v == "trace") return AV_LOG_TRACE; + return AV_LOG_VERBOSE; +} + +static void ffmpeg_log_callback(void *avcl, int level, const char *fmt, va_list vl) +{ + if (level > av_log_get_level()) + return; + thread_local int print_prefix = 1; + char line[1024]; + av_log_format_line2(avcl, level, fmt, vl, line, (int) sizeof(line), &print_prefix); + size_t len = std::strlen(line); + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r' || line[len - 1] == ' ')) + line[--len] = '\0'; + if (len == 0) + return; + if (level <= AV_LOG_ERROR) + BOOST_LOG_TRIVIAL(error) << "ffmpeg: " << line; + else if (level <= AV_LOG_WARNING) + BOOST_LOG_TRIVIAL(warning) << "ffmpeg: " << line; + else if (level <= AV_LOG_INFO) + BOOST_LOG_TRIVIAL(info) << "ffmpeg: " << line; + else + BOOST_LOG_TRIVIAL(debug) << "ffmpeg: " << line; +} + +static void install_ffmpeg_logger() +{ + av_log_set_level(ffmpeg_log_level_from_env()); + av_log_set_callback(&ffmpeg_log_callback); +} + +int wxMediaCtrl3::ffmpeg_interrupt_callback(void *opaque) +{ + auto *ctrl = static_cast(opaque); + std::lock_guard lock(ctrl->m_mutex); + return ctrl->m_url != ctrl->m_active_url; +} + +int wxMediaCtrl3::PlayFfmpeg(std::shared_ptr const &url, std::unique_lock &lock) +{ + static std::once_flag logger_once; + std::call_once(logger_once, install_ffmpeg_logger); + + if (avformat_network_init() < 0) + return 2; + + AVFormatContext *format_context = avformat_alloc_context(); + if (!format_context) { + avformat_network_deinit(); + return 2; + } + + format_context->interrupt_callback = {&wxMediaCtrl3::ffmpeg_interrupt_callback, this}; + format_context->flags |= AVFMT_FLAG_NOBUFFER; + format_context->max_delay = 0; + m_active_url = url; + + auto finish = [&](int error) { + lock.unlock(); + avformat_close_input(&format_context); + avformat_network_deinit(); + lock.lock(); + m_active_url.reset(); + return error; + }; + + const std::string uri = url->BuildURI().ToUTF8().data(); + const wxString scheme = url->GetScheme(); + const bool http_stream = scheme.CmpNoCase("http") == 0 || scheme.CmpNoCase("https") == 0; + AVDictionary *options = nullptr; + if (http_stream) { + // Live multipart MJPEG. fflags=nobuffer / AVFMT_FLAG_NOBUFFER / max_delay=0 + // (set above) are the low-latency levers - they disable the demuxer + // read-ahead queue. probesize / analyzeduration only bound the one-off + // avformat_find_stream_info() at open; a 32-byte budget returned before a + // whole JPEG frame was seen, so width/height came back unset and the open + // was rejected. Give it room to identify one frame (a startup cost only). + // rw_timeout / timeout bound a wedged connect or read so a stale stream + // fails fast and is retried, instead of the reader thread hanging. + // avioflags=direct is deliberately NOT set: unbuffered reads make the + // mpjpeg demuxer emit "Packet corrupt" and bail on any short read across + // a multipart boundary. + av_dict_set(&options, "fflags", "nobuffer", 0); + av_dict_set(&options, "probesize", "5000000", 0); + av_dict_set(&options, "analyzeduration", "1000000", 0); + av_dict_set(&options, "rw_timeout", "5000000", 0); + av_dict_set(&options, "timeout", "5000000", 0); + } else { + av_dict_set(&options, "rtsp_transport", "tcp", 0); + } + lock.unlock(); + int error = avformat_open_input(&format_context, uri.c_str(), nullptr, &options); + av_dict_free(&options); + lock.lock(); + if (error < 0) + return finish(2); + + lock.unlock(); + error = avformat_find_stream_info(format_context, nullptr); + lock.lock(); + if (error < 0) + return finish(2); + + const int video_stream = av_find_best_stream(format_context, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); + if (video_stream < 0) + return finish(2); + + AVVideoDecoder decoder; + if (decoder.open(*format_context->streams[video_stream]->codecpar) < 0) + return finish(2); + + // Prefer the dimensions the container reported. A small probe budget, or a + // camera that doesn't announce a size up front, can leave these unset - in + // that case fill them in from the first frame that decodes (below) rather + // than failing the open outright. + auto apply_video_size = [&](wxSize size) { + if (!size.IsFullySpecified() || size.x <= 0 || size.y <= 0) + return false; + m_video_size = size; + adjust_frame_size(m_frame_size, m_video_size, GetSize()); + NotifyStopped(); + return true; + }; + bool have_size = apply_video_size({format_context->streams[video_stream]->codecpar->width, + format_context->streams[video_stream]->codecpar->height}); + int size_probe_frames = 0; // frames spent still waiting for a usable size + + AVPacket *packet = av_packet_alloc(); + if (!packet) + return finish(2); + + while (m_url == url) { + lock.unlock(); + error = av_read_frame(format_context, packet); + lock.lock(); + if (m_url != url) + break; + if (error < 0) + break; + if (packet->stream_index == video_stream) { + const int decode_error = decoder.decode(*packet); + if (decode_error == 0) { + if (!have_size) { + have_size = apply_video_size(decoder.decoded_frame_size()); + if (!have_size) { + av_packet_unref(packet); + // MJPEG yields a sized frame on the first full packet; if + // several seconds of frames never do, treat it as a bad + // stream instead of sitting in "Loading..." forever. + if (++size_probe_frames > 120) + break; + continue; + } + } + auto frame_size = m_frame_size; + lock.unlock(); +#ifdef _WIN32 + wxBitmap frame; + decoder.toWxBitmap(frame, frame_size); +#else + wxImage frame; + decoder.toWxImage(frame, frame_size); +#endif + lock.lock(); + if (m_url != url) + break; + if (frame.IsOk()) + m_frame = frame; + if (!m_refresh_pending.exchange(true)) { + CallAfter([this] { + m_refresh_pending.store(false); + Refresh(); + }); + } + } + } + av_packet_unref(packet); + } + + av_packet_free(&packet); + return finish(m_url == url ? 2 : 1); +} + void wxMediaCtrl3::PlayThread() { using namespace std::chrono_literals; @@ -197,42 +471,22 @@ void wxMediaCtrl3::PlayThread() continue; if (!url->HasScheme()) break; - lk.unlock(); - Bambu_Tunnel tunnel = nullptr; - int error = Bambu_Create(&tunnel, m_url->BuildURI().ToUTF8()); - if (error == 0) { - Bambu_SetLogger(tunnel, &wxMediaCtrl3::bambu_log, this); - error = Bambu_Open(tunnel); - if (error == 0) - error = Bambu_would_block; - } - lk.lock(); - while (error == int(Bambu_would_block)) { - m_cond.wait_for(lk, 100ms); - if (m_url != url) { - error = 1; - break; + const wxString scheme = url->GetScheme(); + const bool generic_ffmpeg = scheme.CmpNoCase("http") == 0 || scheme.CmpNoCase("https") == 0 || + scheme.CmpNoCase("rtsp") == 0 || scheme.CmpNoCase("rtsps") == 0; + int error = 0; + if (generic_ffmpeg) { + error = PlayFfmpeg(url, lk); + } else { + lk.unlock(); + Bambu_Tunnel tunnel = nullptr; + error = Bambu_Create(&tunnel, m_url->BuildURI().ToUTF8()); + if (error == 0) { + Bambu_SetLogger(tunnel, &wxMediaCtrl3::bambu_log, this); + error = Bambu_Open(tunnel); + if (error == 0) + error = Bambu_would_block; } - lk.unlock(); - error = Bambu_StartStream(tunnel, true); - lk.lock(); - } - Bambu_StreamInfo info; - if (error == 0) - error = Bambu_GetStreamInfo(tunnel, 0, &info); - AVVideoDecoder decoder; - int minFrameDuration = 0; - if (error == 0) { - decoder.open(info); - m_video_size = { info.format.video.width, info.format.video.height }; - adjust_frame_size(m_frame_size, m_video_size, GetSize()); - minFrameDuration = 800 / info.format.video.frame_rate; // 80% - NotifyStopped(); - } - Bambu_Sample sample; - while (error == 0) { - lk.unlock(); - error = Bambu_ReadSample(tunnel, &sample); lk.lock(); while (error == int(Bambu_would_block)) { m_cond.wait_for(lk, 100ms); @@ -240,59 +494,87 @@ void wxMediaCtrl3::PlayThread() error = 1; break; } + lk.unlock(); + error = Bambu_StartStream(tunnel, true); + lk.lock(); + } + Bambu_StreamInfo info; + if (error == 0) + error = Bambu_GetStreamInfo(tunnel, 0, &info); + AVVideoDecoder decoder; + int minFrameDuration = 0; + if (error == 0) { + decoder.open(info); + m_video_size = { info.format.video.width, info.format.video.height }; + adjust_frame_size(m_frame_size, m_video_size, GetSize()); + minFrameDuration = 800 / info.format.video.frame_rate; // 80% + NotifyStopped(); + } + Bambu_Sample sample; + while (error == 0) { lk.unlock(); error = Bambu_ReadSample(tunnel, &sample); lk.lock(); - } - if (error == 0) { - auto frame_size = m_frame_size; - lk.unlock(); - decoder.decode(sample); -#ifdef _WIN32 - wxBitmap bm; - decoder.toWxBitmap(bm, frame_size); -#else - wxImage bm; - decoder.toWxImage(bm, frame_size); -#endif - lk.lock(); - if (m_url != url) { - error = 1; - break; - } - if (bm.IsOk()) { - auto now = std::chrono::system_clock::now(); - if (m_last_PTS && (sample.decode_time - m_last_PTS) < 30000000ULL) { // 3s - auto next_PTS_expected = m_last_PTS_expected + std::chrono::milliseconds((sample.decode_time - m_last_PTS) / 10000ULL); - // The frame is late, catch up a little - auto next_PTS_practical = m_last_PTS_practical + std::chrono::milliseconds(minFrameDuration); - auto next_PTS = std::max(next_PTS_expected, next_PTS_practical); - if(now < next_PTS) - std::this_thread::sleep_until(next_PTS); - else - next_PTS = now; - //auto text = wxString::Format(L"wxMediaCtrl3 pts diff %ld\n", std::chrono::duration_cast(next_PTS - next_PTS_expected).count()); - //OutputDebugString(text); - m_last_PTS = sample.decode_time; - m_last_PTS_expected = next_PTS_expected; - m_last_PTS_practical = next_PTS; - } else { - // Resync - m_last_PTS = sample.decode_time; - m_last_PTS_expected = now; - m_last_PTS_practical = now; + while (error == int(Bambu_would_block)) { + m_cond.wait_for(lk, 100ms); + if (m_url != url) { + error = 1; + break; } - m_frame = bm; + lk.unlock(); + error = Bambu_ReadSample(tunnel, &sample); + lk.lock(); + } + if (error == 0) { + auto frame_size = m_frame_size; + lk.unlock(); + decoder.decode(sample); +#ifdef _WIN32 + wxBitmap bm; + decoder.toWxBitmap(bm, frame_size); +#else + wxImage bm; + decoder.toWxImage(bm, frame_size); +#endif + lk.lock(); + if (m_url != url) { + error = 1; + break; + } + if (bm.IsOk()) { + auto now = std::chrono::system_clock::now(); + if (m_last_PTS && (sample.decode_time - m_last_PTS) < 30000000ULL) { // 3s + auto next_PTS_expected = m_last_PTS_expected + std::chrono::milliseconds((sample.decode_time - m_last_PTS) / 10000ULL); + // The frame is late, catch up a little + auto next_PTS_practical = m_last_PTS_practical + std::chrono::milliseconds(minFrameDuration); + auto next_PTS = std::max(next_PTS_expected, next_PTS_practical); + if(now < next_PTS) + std::this_thread::sleep_until(next_PTS); + else + next_PTS = now; + //auto text = wxString::Format(L"wxMediaCtrl3 pts diff %ld\n", std::chrono::duration_cast(next_PTS - next_PTS_expected).count()); + //OutputDebugString(text); + m_last_PTS = sample.decode_time; + m_last_PTS_expected = next_PTS_expected; + m_last_PTS_practical = next_PTS; + } else { + // Resync + m_last_PTS = sample.decode_time; + m_last_PTS_expected = now; + m_last_PTS_practical = now; + } + m_frame = bm; + } + CallAfter([this] { Refresh(); }); } - CallAfter([this] { Refresh(); }); } - } - if (tunnel) { - lk.unlock(); - Bambu_Close(tunnel); - Bambu_Destroy(tunnel); - tunnel = nullptr; - lk.lock(); + if (tunnel) { + lk.unlock(); + Bambu_Close(tunnel); + Bambu_Destroy(tunnel); + tunnel = nullptr; + lk.lock(); + } } if (m_url == url) m_error = error; diff --git a/src/slic3r/GUI/wxMediaCtrl3.h b/src/slic3r/GUI/wxMediaCtrl3.h index ff4595ad32..936d5e5ddf 100644 --- a/src/slic3r/GUI/wxMediaCtrl3.h +++ b/src/slic3r/GUI/wxMediaCtrl3.h @@ -13,41 +13,51 @@ #include "wx/bitmap.h" #include "wx/uri.h" #include "wx/mediactrl.h" +#include "IMediaController.hpp" wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height); #define BAMBU_DYNAMIC +#include #include #include -#ifndef _WIN32 #include -#endif #include "Printer/BambuTunnel.h" class AVVideoDecoder; -class wxMediaCtrl3 : public wxWindow, BambuLib +class wxMediaCtrl3 : public wxWindow, public Slic3r::GUI::IMediaController, BambuLib { public: wxMediaCtrl3(wxWindow *parent); - ~wxMediaCtrl3(); + ~wxMediaCtrl3() override; - void Load(wxURI url); + void Load(wxURI url) override; - void Play(); + void Play() override; - void Stop(); + void Stop() override; + + // Render frames supplied by a controller which owns its own transport. + // The frame is copied while m_mutex is held; callers may release it after + // this method returns. + void SetExternalFrame(const wxImage& frame, wxSize videoSize); +#ifdef _WIN32 + void SetExternalFrame(const wxBitmap& frame, wxSize videoSize); +#endif + void BeginExternalStream(); + void EndExternalStream(); void SetIdleImage(wxString const & image); - wxMediaState GetState(); + wxMediaState GetState() override; - int GetLastError(); + int GetLastError() const override; - wxSize GetVideoSize(); + wxSize GetVideoSize() const override; protected: DECLARE_EVENT_TABLE() @@ -59,8 +69,10 @@ protected: void DoSetSize(int x, int y, int width, int height, int sizeFlags) override; static void bambu_log(void *ctx, int level, tchar const *msg); + static int ffmpeg_interrupt_callback(void *opaque); void PlayThread(); + int PlayFfmpeg(std::shared_ptr const &url, std::unique_lock &lock); void NotifyStopped(); @@ -77,12 +89,15 @@ private: #endif std::shared_ptr m_url; + std::shared_ptr m_active_url; + bool m_external = false; std::uint64_t m_last_PTS{0}; std::chrono::system_clock::time_point m_last_PTS_expected; std::chrono::system_clock::time_point m_last_PTS_practical; - std::mutex m_mutex; + mutable std::mutex m_mutex; std::condition_variable m_cond; std::thread m_thread; + std::atomic_bool m_refresh_pending{false}; }; #endif /* wxMediaCtrl3_h */ diff --git a/src/slic3r/Utils/BBLPrinterAgent.cpp b/src/slic3r/Utils/BBLPrinterAgent.cpp index 0c6225cc55..4e2015d5d7 100644 --- a/src/slic3r/Utils/BBLPrinterAgent.cpp +++ b/src/slic3r/Utils/BBLPrinterAgent.cpp @@ -1,15 +1,23 @@ #include "BBLPrinterAgent.hpp" #include "BBLNetworkPlugin.hpp" +#include "IPrinterAgent.hpp" #include "NetworkAgentFactory.hpp" +#include "NetworkAgent.hpp" #include "libslic3r/Utils.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/DeviceCore/DevManager.h" +#include #include #include #include +#include +#include using json = nlohmann::json; #include #include +#include namespace Slic3r { @@ -133,7 +141,7 @@ BBLPrinterAgent::~BBLPrinterAgent() = default; void BBLPrinterAgent::set_cloud_agent(std::shared_ptr cloud) { - m_cloud_agent = cloud; + (void) cloud; // BBL DLL manages tokens internally, so this is just for interface compliance } @@ -141,6 +149,94 @@ void BBLPrinterAgent::set_cloud_agent(std::shared_ptr cloud) // Communication // ============================================================================ +int BBLPrinterAgent::command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode) +{ + nlohmann::json j; + if (ams_id == -1) { + const std::string gcode = (boost::format("M620 R%1% \n") % slot_id).str(); + j["print"]["command"] = "gcode_line"; + j["print"]["param"] = gcode; + j["print"]["sequence_id"] = std::to_string(sequence_id); + return publish(dev_id, j, lan_mode); + } + + j["print"]["command"] = "ams_get_rfid"; + j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++); + j["print"]["ams_id"] = ams_id; + j["print"]["slot_id"] = slot_id; + return publish(dev_id, j, lan_mode); +} + +int BBLPrinterAgent::command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode) +{ + const std::string gcode = (boost::format("M620 C%1% \n") % ams_id).str(); + nlohmann::json j; + j["print"]["command"] = "gcode_line"; + j["print"]["param"] = gcode; + j["print"]["sequence_id"] = std::to_string(sequence_id); + return publish(dev_id, j, lan_mode); +} + +int BBLPrinterAgent::command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode) +{ + const std::string gcode = (boost::format("M620 P%1% \n") % tray_id).str(); + nlohmann::json j; + j["print"]["command"] = "gcode_line"; + j["print"]["param"] = gcode; + j["print"]["sequence_id"] = std::to_string(sequence_id); + return publish(dev_id, j, lan_mode); +} + +int BBLPrinterAgent::command_axis_control(std::string dev_id, std::string axis, double unit, double input_val, int speed, + bool is_core_xy, bool supports_mqtt_axis_control, int sequence_id, bool lan_mode) +{ + nlohmann::json j; + j["print"]["sequence_id"] = std::to_string(sequence_id); + + if (supports_mqtt_axis_control) { + int dir = input_val > 0 ? 1 : -1; + // i3-arch printers move the bed for Y/Z, so the on-screen direction is + // reversed -- same negation the g-code fallback below applies. + if (!is_core_xy && (axis == "Y" || axis == "Z")) + dir = -dir; + + j["print"]["command"] = "xyz_ctrl"; + j["print"]["axis"] = axis; + j["print"]["dir"] = dir; + j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0; + return publish(dev_id, j, lan_mode); + } + + double value = input_val; + if (!is_core_xy && (axis == "Y" || axis == "Z")) + value = -input_val; + + std::string value_str = (boost::format("%.1f") % (value * unit)).str(); + std::string gcode; + if (axis == "X" || axis == "Y" || axis == "Z") { + gcode = (boost::format("M211 S \nM211 X1 Y1 Z1\nM1002 push_ref_mode\nG91 \nG1 %1%%2% F%3%\nM1002 pop_ref_mode\nM211 R\n") + % axis % value_str % speed).str(); + } else if (axis == "E") { + gcode = (boost::format("M83 \nG0 %1%%2% F%3%\n") % axis % value_str % speed).str(); + } else { + return -1; + } + + j["print"]["command"] = "gcode_line"; + j["print"]["param"] = gcode; + return publish(dev_id, j, lan_mode); +} + +int BBLPrinterAgent::publish(const std::string& dev_id, const nlohmann::json& j, bool lan_mode) +{ + const int rtn = lan_mode ? send_message_to_printer(dev_id, j.dump(), 0, 0) : send_message(dev_id, j.dump(), 0, 0); + if (rtn == 0) + BOOST_LOG_TRIVIAL(info) << "publish_json: " << j.dump() << " code: " << rtn; + else + BOOST_LOG_TRIVIAL(error) << "publish_json: " << j.dump() << " code: " << rtn; + return rtn; +} + int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag) { json_str = from_orca_payload(std::move(json_str)); @@ -165,13 +261,23 @@ int BBLPrinterAgent::send_message(std::string dev_id, std::string json_str, int return -1; } -int BBLPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) +int BBLPrinterAgent::connect_printer(const PrinterConnectionParams& params) { auto& plugin = BBLNetworkPlugin::instance(); auto agent = plugin.get_agent(); auto func = plugin.get_connect_printer(); +#if !BBL_RELEASE_TO_PUBLIC + const bool use_ssl_for_mqtt = GUI::wxGetApp().app_config && + GUI::wxGetApp().app_config->get_bool("enable_ssl_for_mqtt"); +#else + bool use_ssl_for_mqtt = true; + if (auto* dev_manager = GUI::wxGetApp().getDeviceManager()) { + if (auto* machine = dev_manager->get_my_machine(params.dev_id)) + use_ssl_for_mqtt = machine->local_use_ssl; + } +#endif if (func && agent) { - return func(agent, dev_id, dev_ip, username, password, use_ssl); + return func(agent, params.dev_id, params.host, params.username, params.password, use_ssl_for_mqtt); } return -1; } diff --git a/src/slic3r/Utils/BBLPrinterAgent.hpp b/src/slic3r/Utils/BBLPrinterAgent.hpp index 83e21bb6d8..4398602976 100644 --- a/src/slic3r/Utils/BBLPrinterAgent.hpp +++ b/src/slic3r/Utils/BBLPrinterAgent.hpp @@ -5,6 +5,7 @@ #include "ICloudServiceAgent.hpp" #include #include +#include namespace Slic3r { @@ -28,7 +29,13 @@ public: // Communication int send_message(std::string dev_id, std::string json_str, int qos, int flag) override; - int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override; + int command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode) override; + int command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode) override; + int command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode) override; + int command_axis_control(std::string dev_id, std::string axis, double unit, double input_val, int speed, + bool is_core_xy, bool supports_mqtt_axis_control, int sequence_id, bool lan_mode) override; + std::string default_lan_username() const override { return "bblp"; } + int connect_printer(const PrinterConnectionParams& params) override; int disconnect_printer() override; int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override; @@ -99,7 +106,7 @@ public: static std::string from_orca_payload(std::string json_text); private: - std::shared_ptr m_cloud_agent; + int publish(const std::string& dev_id, const nlohmann::json& j, bool lan_mode); }; } // namespace Slic3r diff --git a/src/slic3r/Utils/CrealityPrintAgent.cpp b/src/slic3r/Utils/CrealityPrintAgent.cpp index f340a61267..4730903c59 100644 --- a/src/slic3r/Utils/CrealityPrintAgent.cpp +++ b/src/slic3r/Utils/CrealityPrintAgent.cpp @@ -282,7 +282,7 @@ bool CrealityPrintAgent::parse_cfs_response(const std::string& response, return true; } -bool CrealityPrintAgent::fetch_filament_info(std::string dev_id) +bool CrealityPrintAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode /*sync_mode*/) { if (device_info.dev_ip.empty()) { BOOST_LOG_TRIVIAL(warning) diff --git a/src/slic3r/Utils/CrealityPrintAgent.hpp b/src/slic3r/Utils/CrealityPrintAgent.hpp index e1096db536..cf22d104ec 100644 --- a/src/slic3r/Utils/CrealityPrintAgent.hpp +++ b/src/slic3r/Utils/CrealityPrintAgent.hpp @@ -41,7 +41,7 @@ public: static AgentInfo get_agent_info_static(); AgentInfo get_agent_info() override { return get_agent_info_static(); } - bool fetch_filament_info(std::string dev_id) override; + bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override; // Parse the boxsInfo JSON returned by CrealityPrint::query_boxes_info() into // a flat list of loaded slots, plus the count of CFS boxes the printer reports. diff --git a/src/slic3r/Utils/FileTransferUtils.cpp b/src/slic3r/Utils/FileTransferUtils.cpp index ebca741af5..822ccdf09d 100644 --- a/src/slic3r/Utils/FileTransferUtils.cpp +++ b/src/slic3r/Utils/FileTransferUtils.cpp @@ -224,4 +224,4 @@ void FileTransferJob::solve_result(ft_job_result result) res_json_.assign(result.json ? result.json : ""); } -} // namespace Slic3r \ No newline at end of file +} // namespace Slic3r diff --git a/src/slic3r/Utils/FileTransferUtils.hpp b/src/slic3r/Utils/FileTransferUtils.hpp index 037a1bef0c..ba91621a91 100644 --- a/src/slic3r/Utils/FileTransferUtils.hpp +++ b/src/slic3r/Utils/FileTransferUtils.hpp @@ -252,4 +252,4 @@ inline FileTransferModule &module() return *detail::g_mod; } -} // namespace Slic3r \ No newline at end of file +} // namespace Slic3r diff --git a/src/slic3r/Utils/ICameraSignalingChannel.hpp b/src/slic3r/Utils/ICameraSignalingChannel.hpp new file mode 100644 index 0000000000..c7971729b6 --- /dev/null +++ b/src/slic3r/Utils/ICameraSignalingChannel.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +namespace Slic3r { + +struct CameraIceServer { + std::string urls; + std::string username; + std::string credential; +}; + +enum class CameraUnavailableReason { + Busy, + Error, + Disabled, + Closed, +}; + +class ICameraSignalingChannel { +public: + virtual ~ICameraSignalingChannel() = default; + + virtual void open() = 0; + virtual void close() = 0; + virtual void send_offer(std::string sdp) = 0; + virtual void send_ice(std::string candidate, std::string mid) = 0; + + // These callbacks are invoked by the channel's worker thread. Consumers + // must marshal UI work to the GUI thread themselves. + std::function)> on_ready; + std::function on_answer; + std::function on_ice; + std::function on_unavailable; +}; + +} // namespace Slic3r diff --git a/src/slic3r/Utils/ICloudServiceAgent.hpp b/src/slic3r/Utils/ICloudServiceAgent.hpp index 7d326eb4c6..6457b3fda6 100644 --- a/src/slic3r/Utils/ICloudServiceAgent.hpp +++ b/src/slic3r/Utils/ICloudServiceAgent.hpp @@ -1,11 +1,11 @@ #ifndef __I_CLOUD_SERVICE_AGENT_HPP__ #define __I_CLOUD_SERVICE_AGENT_HPP__ +#include "ICameraSignalingChannel.hpp" #include "bambu_networking.hpp" #include "CloudProvider.hpp" #include "../../libslic3r/ProjectTask.hpp" #include -#include #include #include #include @@ -328,6 +328,17 @@ public: */ virtual int get_camera_url(std::string dev_id, std::function callback) = 0; + /** + * Create a camera signaling channel object for P2P camera streaming over WebRTC. + * + */ + virtual std::unique_ptr + create_camera_signaling_channel(const std::string& dev_id) + { + (void) dev_id; + return nullptr; + } + /** * Fetch staff-picked designs from model mall. */ diff --git a/src/slic3r/Utils/IPrinterAgent.hpp b/src/slic3r/Utils/IPrinterAgent.hpp index 1f0d45dc1e..293b0af2b8 100644 --- a/src/slic3r/Utils/IPrinterAgent.hpp +++ b/src/slic3r/Utils/IPrinterAgent.hpp @@ -11,6 +11,11 @@ #define ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE -7020 // a translation exists; this printer lacks the capability #include #include +#include +#include +#include +#include +#include namespace Slic3r { @@ -29,6 +34,17 @@ struct AgentInfo { std::string description; ///< Brief description of the agent's capabilities, e.g. "Orca printer agent" }; +struct PrinterConnectionParams +{ + std::string dev_id; + std::string host; // host address, usually the IP address without the http/https protocol + std::string port; // optional + std::string username; + std::string password; + bool use_ssl = false; // indicates if http or https + std::string ca_file; +}; + /** * FilamentSyncMode - Modes for filament data synchronization. * @@ -43,6 +59,15 @@ enum class FilamentSyncMode { pull ///< On-demand fetch via REST API (blocking call) }; +enum class CameraStreamMode { + none = 0, + http, // LAN or Cloud + https, // LAN or Cloud over TLS + rtsp, // LAN only + webrtc, // Cloud only + http_snapshot // HTTP endpoint returning one image per request +}; + /** * IPrinterAgent - Interface for printer operations. * @@ -84,10 +109,112 @@ public: */ virtual int send_message(std::string dev_id, std::string json_str, int qos, int flag) = 0; + // why: gcode is firmware dialect, not a waist concept - commands whose body is Bambu-dialect + // gcode live on the agent that speaks it; the default is an honest refusal that MachineObject's + // publish funnel turns into a dialog. + virtual int command_ams_refresh_rfid(std::string, int, int, int, bool) + { return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; } + virtual int command_ams_calibrate(std::string, int, int, bool) + { return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; } + virtual int command_ams_select_tray(std::string, std::string, int, bool) + { return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; } + virtual int command_start_camera(std::string) + { return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED; } + +private: + int publish_command_json(std::string dev_id, const nlohmann::json& j, bool lan_mode) + { + return lan_mode ? send_message_to_printer(dev_id, j.dump(), 0, 0) + : send_message(dev_id, j.dump(), 0, 0); + } + +public: + virtual int command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode) + { + nlohmann::json j; + j["print"]["command"] = "gcode_line"; + j["print"]["param"] = "G90 \n"; + j["print"]["sequence_id"] = std::to_string(sequence_id); + return publish_command_json(dev_id, j, lan_mode); + } + virtual int command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode) + { + nlohmann::json j; + j["print"]["command"] = "gcode_line"; + j["print"]["param"] = "G29 \n"; + j["print"]["sequence_id"] = std::to_string(sequence_id); + return publish_command_json(dev_id, j, lan_mode); + } + virtual int command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing, int sequence_id, bool lan_mode) + { + nlohmann::json j; + j["print"]["sequence_id"] = std::to_string(sequence_id); + if (supports_mqtt_homing) { + j["print"]["command"] = "back_to_center"; + } else { + j["print"]["command"] = "gcode_line"; + j["print"]["param"] = is_printing ? "G28 X\n" : "G28 \n"; + } + return publish_command_json(dev_id, j, lan_mode); + } + virtual int command_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl, int sequence_id, bool lan_mode) + { + nlohmann::json j; + j["print"]["sequence_id"] = std::to_string(sequence_id); + if (supports_mqtt_bed_ctrl) { + j["print"]["command"] = "set_bed_temp"; + j["print"]["temp"] = temp; + } else { + j["print"]["command"] = "gcode_line"; + j["print"]["param"] = (boost::format("M140 S%1%\n") % temp).str(); + } + return publish_command_json(dev_id, j, lan_mode); + } + virtual int command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode) + { + nlohmann::json j; + j["print"]["command"] = "gcode_line"; + j["print"]["param"] = (boost::format("M104 S%1%\n") % temp).str(); + j["print"]["sequence_id"] = std::to_string(sequence_id); + return publish_command_json(dev_id, j, lan_mode); + } + + virtual int command_axis_control(std::string dev_id, std::string axis, double unit, double input_val, int speed, + bool is_core_xy, bool supports_mqtt_axis_control, int sequence_id, bool lan_mode) + { + (void) supports_mqtt_axis_control; + + double value = input_val; + if (!is_core_xy && (axis == "Y" || axis == "Z")) + value = -input_val; + + std::string value_str = (boost::format("%.1f") % (value * unit)).str(); + std::string gcode; + if (axis == "X" || axis == "Y" || axis == "Z") { + gcode = (boost::format("G91 \nG1 %1%%2% F%3%\nG90 \n") % axis % value_str % speed).str(); + } else if (axis == "E") { + gcode = (boost::format("M83 \nG0 %1%%2% F%3%\n") % axis % value_str % speed).str(); + } else { + return -1; + } + + nlohmann::json j; + j["print"]["command"] = "gcode_line"; + j["print"]["param"] = gcode; + j["print"]["sequence_id"] = std::to_string(sequence_id); + return publish_command_json(dev_id, j, lan_mode); + } + + /** + * Default LAN account username for this agent's protocol, if it has a fixed one. + * Returns an empty string if the agent has no fixed default (e.g. caller must supply one). + */ + virtual std::string default_lan_username() const { return {}; } + /** * Establish a direct LAN connection to a printer. */ - virtual int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) = 0; + virtual int connect_printer(const PrinterConnectionParams& params) = 0; /** * Tear down the active LAN printer connection. @@ -105,12 +232,16 @@ public: /** * Validate current user certificates for the printer. */ - virtual int check_cert() = 0; + virtual int check_cert() { return BAMBU_NETWORK_SUCCESS; } /** * Install or refresh device certificate for LAN TLS. */ - virtual void install_device_cert(std::string dev_id, bool lan_only) = 0; + virtual void install_device_cert(std::string dev_id, bool lan_only) + { + (void) dev_id; + (void) lan_only; + } // ======================================================================== // Discovery @@ -126,7 +257,11 @@ public: /** * Ping the binding endpoint to check printer readiness. */ - virtual int ping_bind(std::string ping_code) = 0; + virtual int ping_bind(std::string ping_code) + { + (void) ping_code; + return BAMBU_NETWORK_SUCCESS; + } /** * Perform binding detection/handshake on a LAN printer. @@ -136,23 +271,50 @@ public: /** * Execute the multi-stage printer binding workflow. */ - virtual int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) = 0; + virtual int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, + std::string timezone, bool improved, OnUpdateStatusFn update_fn) + { + (void) dev_ip; + (void) dev_id; + (void) dev_model; + (void) sec_link; + (void) timezone; + (void) improved; + (void) update_fn; + return BAMBU_NETWORK_SUCCESS; + } /** * Remove the association between account and printer. */ - virtual int unbind(std::string dev_id) = 0; + virtual int unbind(std::string dev_id) + { + (void) dev_id; + return BAMBU_NETWORK_SUCCESS; + } /** * Request a one-time bind ticket from the server. */ - virtual int request_bind_ticket(std::string* ticket) = 0; + virtual int request_bind_ticket(std::string* ticket) + { + if (ticket) + *ticket = {}; + return BAMBU_NETWORK_SUCCESS; + } /** * Fetch the cloud snapshot image captured at a print failure. * Returns 0 if the request was dispatched; the image body arrives via callback(body, http_status). */ - virtual int get_hms_snapshot(std::string dev_id, std::string file_name, std::function callback) = 0; + virtual int get_hms_snapshot(std::string dev_id, std::string file_name, + std::function callback) + { + (void) dev_id; + (void) file_name; + (void) callback; + return -1; + } /** * Register callback for fatal HTTP errors. @@ -168,7 +330,7 @@ public: virtual std::string get_user_selected_machine() = 0; /** - * Update the selected machine preference. + * Update the selected cloud machine preference. */ virtual int set_user_selected_machine(std::string dev_id) = 0; @@ -284,12 +446,20 @@ public: */ virtual FilamentSyncMode get_filament_sync_mode() const { return FilamentSyncMode::none; } + /** + * Get the camera stream mode for this agent. This value can be deterministic and derived at + * runtime if the printer supports multiple camera stream modes. E.g. LAN => HTTP/HTTPS/RTSP, Cloud => WebRTC. + * + * @return CameraStreamMode indicating how the camera stream is obtained or used: + */ + virtual CameraStreamMode get_camera_stream_mode() const { return CameraStreamMode::none; } + /** * Refresh filament info from the printer synchronously. * Should only be called when get_filament_sync_mode() returns FilamentSyncMode::pull. * Populates the MachineObject's DevFilaSystem with fetched filament data. */ - virtual bool fetch_filament_info(std::string dev_id) { return false; } + virtual bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) { return false; } /** * Translate one filament id across the printer boundary. @@ -300,6 +470,12 @@ public: */ virtual std::string to_orca_filament_id(const std::string& printer_filament_id) const { return printer_filament_id; } virtual std::string from_orca_filament_id(const std::string& orca_filament_id) const { return orca_filament_id; } + + /** + * Get the current camera stream URL for this agent's active machine. + * Only meaningful when get_camera_stream_mode() returns an HTTP, HTTPS, or RTSP mode. + */ + virtual std::string get_camera_url() const { return {}; } }; } // namespace Slic3r diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index 384a69d51e..5d75955a1e 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -1,5 +1,6 @@ #include "MoonrakerPrinterAgent.hpp" #include "Http.hpp" +#include "IPrinterAgent.hpp" #include "libslic3r/Preset.hpp" #include "libslic3r/PresetBundle.hpp" #include "slic3r/GUI/GUI_App.hpp" @@ -136,9 +137,9 @@ int MoonrakerPrinterAgent::send_message_to_printer(std::string dev_id, std::stri return handle_request(dev_id, json_str); } -int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) +int MoonrakerPrinterAgent::connect_printer(const PrinterConnectionParams& params) { - if (dev_id.empty() || dev_ip.empty()) { + if (params.dev_id.empty() || params.host.empty()) { BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: connect_printer missing dev_id or dev_ip"; return BAMBU_NETWORK_ERR_INVALID_HANDLE; } @@ -148,7 +149,7 @@ int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_i uint64_t gen; { std::lock_guard lock(connect_mutex); - init_device_info(dev_id, dev_ip, username, password, use_ssl); + init_device_info(params.dev_id, params.host, params.username, params.password, params.use_ssl, params.port); gen = ++connect_generation; base_url = device_info.base_url; api_key = device_info.api_key; @@ -170,7 +171,7 @@ int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_i // Launch connection in background thread (capture by value to avoid data races) { std::lock_guard lock(connect_mutex); - connect_thread = std::thread([this, dev_id, base_url, api_key, gen]() { perform_connection_async(dev_id, base_url, api_key, gen); }); + connect_thread = std::thread([this, dev_id = params.dev_id, base_url, api_key, gen]() { perform_connection_async(dev_id, base_url, api_key, gen); }); } return BAMBU_NETWORK_SUCCESS; @@ -191,14 +192,6 @@ int MoonrakerPrinterAgent::disconnect_printer() return BAMBU_NETWORK_SUCCESS; } -int MoonrakerPrinterAgent::check_cert() { return BAMBU_NETWORK_SUCCESS; } - -void MoonrakerPrinterAgent::install_device_cert(std::string dev_id, bool lan_only) -{ - (void) dev_id; - (void) lan_only; -} - bool MoonrakerPrinterAgent::start_discovery(bool start, bool sending) { (void) sending; @@ -208,12 +201,6 @@ bool MoonrakerPrinterAgent::start_discovery(bool start, bool sending) return true; } -int MoonrakerPrinterAgent::ping_bind(std::string ping_code) -{ - (void) ping_code; - return BAMBU_NETWORK_SUCCESS; -} - int MoonrakerPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) { (void) sec_link; @@ -221,7 +208,7 @@ int MoonrakerPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, detect.dev_id = device_info.dev_id.empty() ? dev_ip : device_info.dev_id; detect.model_id = device_info.model_id.empty() ? device_info.model_name : device_info.model_id; // Prefer fetched hostname, then preset model name, then generic fallback - detect.dev_name = device_info.dev_name; + detect.dev_name = device_info.dev_name.empty() ? dev_ip : device_info.dev_name; detect.model_id = device_info.model_id; detect.version = device_info.version; detect.connect_type = "lan"; @@ -230,41 +217,6 @@ int MoonrakerPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, return BAMBU_NETWORK_SUCCESS; } -int MoonrakerPrinterAgent::bind( - std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) -{ - (void) dev_ip; - (void) dev_id; - (void) dev_model; - (void) sec_link; - (void) timezone; - (void) improved; - (void) update_fn; - return BAMBU_NETWORK_SUCCESS; -} - -int MoonrakerPrinterAgent::unbind(std::string dev_id) -{ - (void) dev_id; - return BAMBU_NETWORK_SUCCESS; -} - -int MoonrakerPrinterAgent::request_bind_ticket(std::string* ticket) -{ - if (ticket) - *ticket = ""; - return BAMBU_NETWORK_SUCCESS; -} - -int MoonrakerPrinterAgent::get_hms_snapshot(std::string dev_id, std::string file_name, std::function callback) -{ - // No BBL cloud snapshot source; report failure so the caller falls back. - (void) dev_id; - (void) file_name; - (void) callback; - return -1; -} - int MoonrakerPrinterAgent::set_server_callback(OnServerErrFn fn) { std::lock_guard lock(state_mutex); @@ -385,7 +337,7 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF if (update_fn) update_fn(PrintingStageSending, 0, "Starting print..."); std::string gcode = "SDCARD_PRINT_FILE FILENAME=" + upload_filename; - if (!send_gcode(device_info.dev_id, gcode)) { + if (!send_gcode_sync(device_info.dev_id, gcode)) { return BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED; } @@ -578,7 +530,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, } } -bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id) +bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode /*sync_mode*/) { std::vector trays; int max_lane_index = 0; @@ -1035,14 +987,11 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std:: } response["print"]["param"] = gcode; - if (send_gcode(dev_id, gcode)) { - response["print"]["result"] = "success"; + send_gcode_async(dev_id, gcode, [this, dev_id, response](bool success) mutable { + response["print"]["result"] = success ? "success" : "failed"; dispatch_message(dev_id, response.dump()); - return BAMBU_NETWORK_SUCCESS; - } - response["print"]["result"] = "failed"; - dispatch_message(dev_id, response.dump()); - return BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED; + }); + return BAMBU_NETWORK_SUCCESS; } // Print control commands @@ -1061,7 +1010,7 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std:: if (json["print"].contains("temp") && json["print"]["temp"].is_number()) { int temp = json["print"]["temp"].get(); std::string gcode = "SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=" + std::to_string(temp); - send_gcode(dev_id, gcode); + send_gcode_async(dev_id, gcode); return BAMBU_NETWORK_SUCCESS; } } @@ -1076,20 +1025,21 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std:: } std::string heater = (extruder_idx == 0) ? "extruder" : "extruder" + std::to_string(extruder_idx); std::string gcode = "SET_HEATER_TEMPERATURE HEATER=" + heater + " TARGET=" + std::to_string(temp); - send_gcode(dev_id, gcode); + send_gcode_async(dev_id, gcode); return BAMBU_NETWORK_SUCCESS; } } if (cmd == "home") { - return send_gcode(dev_id, "G28") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED; + send_gcode_async(dev_id, "G28"); + return BAMBU_NETWORK_SUCCESS; } } return BAMBU_NETWORK_SUCCESS; } -bool MoonrakerPrinterAgent::init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) +bool MoonrakerPrinterAgent::init_device_info(const std::string& dev_id, const std::string& dev_ip, const std::string& username, const std::string& password, bool use_ssl, const std::string& port) { device_info = MoonrakerDeviceInfo{}; auto* preset_bundle = GUI::wxGetApp().preset_bundle; @@ -1099,12 +1049,12 @@ bool MoonrakerPrinterAgent::init_device_info(std::string dev_id, std::string dev auto& preset = preset_bundle->printers.get_edited_preset(); const auto& printer_cfg = preset.config; - device_info.dev_ip = dev_ip; + device_info.dev_ip = dev_ip; device_info.api_key = password; device_info.model_name = printer_cfg.opt_string("printer_model"); device_info.model_id = preset.get_printer_type(preset_bundle); - device_info.base_url = use_ssl ? "https://" + dev_ip : "http://" + dev_ip; + device_info.base_url = normalize_base_url(use_ssl, dev_ip, port); device_info.dev_id = dev_id; device_info.version = ""; device_info.dev_name = device_info.dev_id; @@ -1224,7 +1174,45 @@ bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url, return true; } -bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::string& gcode) const +void MoonrakerPrinterAgent::send_gcode_async(const std::string& dev_id, const std::string& gcode, + std::function on_result) const +{ + (void) dev_id; + const std::string base_url = device_info.base_url; + const std::string api_key = device_info.api_key; + auto http = Http::post(join_url(base_url, "/printer/gcode/script")); + if (!api_key.empty()) { + http.header("X-Api-Key", api_key); + } + http.header("Content-Type", "application/json") + .set_post_body(nlohmann::json{{"script", gcode}}.dump()) + .timeout_connect(5) + .timeout_max(10) + .on_complete([on_result](std::string body, unsigned status_code) { + (void) body; + const bool success = status_code == 200; + if (!success) { + BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: send_gcode failed: HTTP error " << status_code; + } + if (on_result) { + on_result(success); + } + }) + .on_error([on_result](std::string body, std::string err, unsigned status_code) { + (void) body; + std::string error = err; + if (status_code > 0) { + error += " (HTTP " + std::to_string(status_code) + ")"; + } + BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: send_gcode failed: " << error; + if (on_result) { + on_result(false); + } + }) + .perform(); +} + +bool MoonrakerPrinterAgent::send_gcode_sync(const std::string& dev_id, const std::string& gcode) const { nlohmann::json payload; payload["script"] = gcode; @@ -2029,17 +2017,20 @@ bool MoonrakerPrinterAgent::upload_gcode(const std::string& local_path, int MoonrakerPrinterAgent::pause_print(const std::string& dev_id) { - return send_gcode(dev_id, "PAUSE") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED; + send_gcode_async(dev_id, "PAUSE"); + return BAMBU_NETWORK_SUCCESS; } int MoonrakerPrinterAgent::resume_print(const std::string& dev_id) { - return send_gcode(dev_id, "RESUME") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED; + send_gcode_async(dev_id, "RESUME"); + return BAMBU_NETWORK_SUCCESS; } int MoonrakerPrinterAgent::cancel_print(const std::string& dev_id) { - return send_gcode(dev_id, "CANCEL_PRINT") ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_SEND_MSG_FAILED; + send_gcode_async(dev_id, "CANCEL_PRINT"); + return BAMBU_NETWORK_SUCCESS; } bool MoonrakerPrinterAgent::send_jsonrpc_command(const std::string& base_url, @@ -2106,13 +2097,13 @@ void MoonrakerPrinterAgent::perform_connection_async(const std::string& dev_id, if (is_stale()) { return; } - device_info.dev_name = fetched_info.dev_name; + device_info.dev_name = fetched_info.dev_name.empty() ? dev_id : fetched_info.dev_name; device_info.version = fetched_info.version; device_info.klippy_state = fetched_info.klippy_state; } // Orca todo: disable websocket for now, as we don't use MonitorPanel for Moonraker printers yet -#if 0 +#if 1 // Query initial status nlohmann::json initial_status; if (query_printer_status(base_url, api_key, initial_status, error_msg)) { @@ -2153,26 +2144,11 @@ bool MoonrakerPrinterAgent::is_numeric(const std::string& value) return !value.empty() && std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c) != 0; }); } -std::string MoonrakerPrinterAgent::normalize_base_url(std::string host, const std::string& port) +std::string MoonrakerPrinterAgent::normalize_base_url(bool use_ssl, const std::string& host, const std::string& port) { - boost::trim(host); - if (host.empty()) { - return ""; - } - - std::string value = host; - if (is_numeric(port) && value.find("://") == std::string::npos && value.find(':') == std::string::npos) { - value += ":" + port; - } - - if (!boost::istarts_with(value, "http://") && !boost::istarts_with(value, "https://")) { - value = "http://" + value; - } - - if (value.size() > 1 && value.back() == '/') { - value.pop_back(); - } - + std::string value = use_ssl ? "https://" : "http://"; + value += host; + value += port.empty() ? "" : (":" + port); return value; } diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp index ae63570063..a25a088ada 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp @@ -28,24 +28,15 @@ public: // Communication int send_message(std::string dev_id, std::string json_str, int qos, int flag) override; - int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override; + int connect_printer(const PrinterConnectionParams& params) override; int disconnect_printer() override; int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override; - // Certificates - int check_cert() override; - void install_device_cert(std::string dev_id, bool lan_only) override; - // Discovery bool start_discovery(bool start, bool sending) override; // Binding - int ping_bind(std::string ping_code) override; int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override; - int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override; - int unbind(std::string dev_id) override; - int request_bind_ticket(std::string* ticket) override; - int get_hms_snapshot(std::string dev_id, std::string file_name, std::function callback) override; int set_server_callback(OnServerErrFn fn) override; // Machine Selection @@ -71,7 +62,7 @@ public: // Pull-mode agent (on-demand filament sync) FilamentSyncMode get_filament_sync_mode() const override { return FilamentSyncMode::pull; } - bool fetch_filament_info(std::string dev_id) override; + bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override; protected: struct MoonrakerDeviceInfo @@ -103,7 +94,7 @@ protected: void build_ams_payload(int ams_count, int max_lane_index, const std::vector& trays); // Methods that derived classes may need to override or access - virtual bool init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl); + virtual bool init_device_info(const std::string& dev_id, const std::string& dev_ip, const std::string& username, const std::string& password, bool use_ssl, const std::string& port); virtual bool fetch_device_info(const std::string& base_url, const std::string& api_key, MoonrakerDeviceInfo& info, std::string& error) const; // State access for derived classes @@ -111,7 +102,7 @@ protected: // Helpers bool is_numeric(const std::string& value); - std::string normalize_base_url(std::string host, const std::string& port); + std::string normalize_base_url(bool use_ssl, const std::string& host, const std::string& port); std::string sanitize_filename(const std::string& filename); std::string join_url(const std::string& base_url, const std::string& path) const; @@ -128,7 +119,9 @@ private: bool fetch_object_list(const std::string& base_url, const std::string& api_key, std::set& objects, std::string& error) const; bool query_printer_status(const std::string& base_url, const std::string& api_key, nlohmann::json& status, std::string& error) const; - bool send_gcode(const std::string& dev_id, const std::string& gcode) const; + bool send_gcode_sync(const std::string& dev_id, const std::string& gcode) const; + void send_gcode_async(const std::string& dev_id, const std::string& gcode, + std::function on_result = {}) const; void announce_printhost_device(); void dispatch_local_connect(int state, const std::string& dev_id, const std::string& msg); diff --git a/src/slic3r/Utils/NetworkAgent.cpp b/src/slic3r/Utils/NetworkAgent.cpp index b15e10c591..1f7705658e 100644 --- a/src/slic3r/Utils/NetworkAgent.cpp +++ b/src/slic3r/Utils/NetworkAgent.cpp @@ -1,11 +1,10 @@ -#include +#include "NetworkAgent.hpp" + #include -#include -#include #include -#include "libslic3r/Utils.hpp" -#include "NetworkAgent.hpp" +#include +#include "IPrinterAgent.hpp" #include "BBLNetworkPlugin.hpp" namespace Slic3r { @@ -115,6 +114,8 @@ void NetworkAgent::add_cloud_agent(const std::string& provider, std::shared_ptr< void NetworkAgent::set_printer_agent(std::shared_ptr printer_agent) { + m_user_machine_list_generation.fetch_add(1); + // Disconnect all callbacks from the old agent auto old_printer_agent = m_printer_agent; @@ -432,10 +433,26 @@ int NetworkAgent::check_user_task_report(int* task_id, bool* printable, const st int NetworkAgent::get_user_print_info(unsigned int* http_code, std::string* http_body, const std::string& provider) { + const std::string request_agent_id = m_printer_agent_id; + const std::uint64_t request_generation = m_user_machine_list_generation.fetch_add(1) + 1; const auto cloud_agent = get_cloud_agent(provider); - if (cloud_agent) - return cloud_agent->get_user_print_info(http_code, http_body); - return -1; + if (!cloud_agent) + return -1; + + const int result = cloud_agent->get_user_print_info(http_code, http_body); + if (result == 0 && http_body) { + try { + nlohmann::json response = nlohmann::json::parse(*http_body); + response["provider"] = provider; + response["agent_id"] = request_agent_id; + response["generation"] = request_generation; + *http_body = response.dump(); + } + catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " metadata injection exception=" << e.what(); + } + } + return result; } int NetworkAgent::get_user_tasks(TaskQueryParams params, std::string* http_body, const std::string& provider) @@ -515,6 +532,15 @@ int NetworkAgent::get_camera_url(std::string dev_id, std::function +NetworkAgent::create_camera_signaling_channel(const std::string& dev_id, const std::string& provider) +{ + const auto cloud_agent = get_cloud_agent(provider); + if (cloud_agent) + return cloud_agent->create_camera_signaling_channel(dev_id); + return nullptr; +} + int NetworkAgent::get_design_staffpick(int offset, int limit, std::function callback, const std::string& provider) { const auto cloud_agent = get_cloud_agent(provider); @@ -767,10 +793,81 @@ int NetworkAgent::send_message(std::string dev_id, std::string json_str, int qos return -1; } -int NetworkAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) +int NetworkAgent::command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode) { if (m_printer_agent) - return m_printer_agent->connect_printer(dev_id, dev_ip, username, password, use_ssl); + return m_printer_agent->command_ams_refresh_rfid(dev_id, ams_id, slot_id, sequence_id, lan_mode); + return -1; +} + +int NetworkAgent::command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode) +{ + if (m_printer_agent) + return m_printer_agent->command_ams_calibrate(dev_id, ams_id, sequence_id, lan_mode); + return -1; +} + +int NetworkAgent::command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode) +{ + if (m_printer_agent) + return m_printer_agent->command_ams_select_tray(dev_id, tray_id, sequence_id, lan_mode); + return -1; +} + +int NetworkAgent::command_start_camera(std::string dev_id) +{ + if (m_printer_agent) + return m_printer_agent->command_start_camera(dev_id); + return -1; +} + +int NetworkAgent::command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode) +{ + if (m_printer_agent) + return m_printer_agent->command_xyz_abs(dev_id, sequence_id, lan_mode); + return -1; +} + +int NetworkAgent::command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode) +{ + if (m_printer_agent) + return m_printer_agent->command_auto_leveling(dev_id, sequence_id, lan_mode); + return -1; +} + +int NetworkAgent::command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing, int sequence_id, bool lan_mode) +{ + if (m_printer_agent) + return m_printer_agent->command_go_home(dev_id, is_printing, supports_mqtt_homing, sequence_id, lan_mode); + return -1; +} + +int NetworkAgent::command_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl, int sequence_id, bool lan_mode) +{ + if (m_printer_agent) + return m_printer_agent->command_set_bed(dev_id, temp, supports_mqtt_bed_ctrl, sequence_id, lan_mode); + return -1; +} + +int NetworkAgent::command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode) +{ + if (m_printer_agent) + return m_printer_agent->command_set_nozzle(dev_id, temp, sequence_id, lan_mode); + return -1; +} + +int NetworkAgent::command_axis_control(std::string dev_id, std::string axis, double unit, double input_val, int speed, + bool is_core_xy, bool supports_mqtt_axis_control, int sequence_id, bool lan_mode) +{ + if (m_printer_agent) + return m_printer_agent->command_axis_control(dev_id, axis, unit, input_val, speed, is_core_xy, supports_mqtt_axis_control, sequence_id, lan_mode); + return -1; +} + +int NetworkAgent::connect_printer(const PrinterConnectionParams& params) +{ + if (m_printer_agent) + return m_printer_agent->connect_printer(params); return -1; } @@ -788,6 +885,13 @@ int NetworkAgent::send_message_to_printer(std::string dev_id, std::string json_s return -1; } +std::string NetworkAgent::default_lan_username() const +{ + if (m_printer_agent) + return m_printer_agent->default_lan_username(); + return {}; +} + int NetworkAgent::check_cert() { if (m_printer_agent) @@ -846,8 +950,14 @@ std::string NetworkAgent::get_user_selected_machine() int NetworkAgent::set_user_selected_machine(std::string dev_id) { - if (m_printer_agent) - return m_printer_agent->set_user_selected_machine(dev_id); + BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::set_user_selected_machine: dev_id=" << dev_id + << " printer_agent=" << (m_printer_agent ? m_printer_agent->get_agent_info().id : ""); + if (m_printer_agent) { + const int result = m_printer_agent->set_user_selected_machine(dev_id); + BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::set_user_selected_machine: result=" << result; + return result; + } + BOOST_LOG_TRIVIAL(warning) << "NetworkAgent::set_user_selected_machine: no printer agent"; return -1; } @@ -867,15 +977,27 @@ int NetworkAgent::stop_subscribe(std::string module) int NetworkAgent::add_subscribe(std::vector dev_list) { - if (m_printer_agent) - return m_printer_agent->add_subscribe(std::move(dev_list)); + BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::add_subscribe: count=" << dev_list.size() + << " printer_agent=" << (m_printer_agent ? m_printer_agent->get_agent_info().id : ""); + if (m_printer_agent) { + const int result = m_printer_agent->add_subscribe(std::move(dev_list)); + BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::add_subscribe: result=" << result; + return result; + } + BOOST_LOG_TRIVIAL(warning) << "NetworkAgent::add_subscribe: no printer agent"; return -1; } int NetworkAgent::del_subscribe(std::vector dev_list) { - if (m_printer_agent) - return m_printer_agent->del_subscribe(std::move(dev_list)); + BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::del_subscribe: count=" << dev_list.size() + << " printer_agent=" << (m_printer_agent ? m_printer_agent->get_agent_info().id : ""); + if (m_printer_agent) { + const int result = m_printer_agent->del_subscribe(std::move(dev_list)); + BOOST_LOG_TRIVIAL(trace) << "NetworkAgent::del_subscribe: result=" << result; + return result; + } + BOOST_LOG_TRIVIAL(warning) << "NetworkAgent::del_subscribe: no printer agent"; return -1; } @@ -921,10 +1043,10 @@ FilamentSyncMode NetworkAgent::get_filament_sync_mode() const return FilamentSyncMode::none; } -bool NetworkAgent::fetch_filament_info(std::string dev_id) +bool NetworkAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode) { if (m_printer_agent) { - return m_printer_agent->fetch_filament_info(dev_id); + return m_printer_agent->fetch_filament_info(dev_id, sync_mode); } return false; } @@ -943,6 +1065,20 @@ std::string NetworkAgent::from_orca_filament_id(const std::string& orca_filament return orca_filament_id; } +CameraStreamMode NetworkAgent::get_camera_stream_mode() const +{ + if (m_printer_agent) + return m_printer_agent->get_camera_stream_mode(); + return CameraStreamMode::none; +} + +std::string NetworkAgent::get_local_camera_stream_url() const +{ + if (m_printer_agent) + return m_printer_agent->get_camera_url(); + return {}; +} + int NetworkAgent::request_bind_ticket(std::string* ticket) { if (m_printer_agent) diff --git a/src/slic3r/Utils/NetworkAgent.hpp b/src/slic3r/Utils/NetworkAgent.hpp index 76128073a4..f93178db8b 100644 --- a/src/slic3r/Utils/NetworkAgent.hpp +++ b/src/slic3r/Utils/NetworkAgent.hpp @@ -2,16 +2,22 @@ #define __NETWORK_Agent_HPP__ #include "bambu_networking.hpp" + #include "libslic3r/ProjectTask.hpp" #include "ICloudServiceAgent.hpp" #include "IPrinterAgent.hpp" + #include +#include +#include #include #include #include namespace Slic3r { +class IPrinterAgent; + // Forward declaration class BBLNetworkPlugin; @@ -50,6 +56,7 @@ public: // Sub-agent accessors std::shared_ptr get_cloud_agent(const std::string& provider = ORCA_CLOUD_PROVIDER) const; std::shared_ptr get_printer_agent() const { return m_printer_agent; } + std::uint64_t get_user_machine_list_generation() const { return m_user_machine_list_generation.load(); } // Shared agent management void add_cloud_agent(const std::string& provider, std::shared_ptr agent); @@ -109,6 +116,7 @@ public: int query_bind_status(std::vector query_list, unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER); int modify_printer_name(std::string dev_id, std::string dev_name, const std::string& provider = ORCA_CLOUD_PROVIDER); int get_camera_url(std::string dev_id, std::function callback, const std::string& provider = ORCA_CLOUD_PROVIDER); + std::unique_ptr create_camera_signaling_channel(const std::string& dev_id, const std::string& provider = ORCA_CLOUD_PROVIDER); int get_design_staffpick(int offset, int limit, std::function callback, const std::string& provider = ORCA_CLOUD_PROVIDER); int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out, const std::string& provider = ORCA_CLOUD_PROVIDER); int get_model_publish_url(std::string* url, const std::string& provider = ORCA_CLOUD_PROVIDER); @@ -142,9 +150,21 @@ public: int set_on_local_message_fn(OnMessageFn fn); int set_server_callback(OnServerErrFn fn); int send_message(std::string dev_id, std::string json_str, int qos, int flag); - int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl); + int command_ams_refresh_rfid(std::string dev_id, int ams_id, int slot_id, int sequence_id, bool lan_mode); + int command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode); + int command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode); + int command_start_camera(std::string dev_id); + int command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode); + int command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode); + int command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing, int sequence_id, bool lan_mode); + int command_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl, int sequence_id, bool lan_mode); + int command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode); + int command_axis_control(std::string dev_id, std::string axis, double unit, double input_val, int speed, + bool is_core_xy, bool supports_mqtt_axis_control, int sequence_id, bool lan_mode); + int connect_printer(const PrinterConnectionParams& params); int disconnect_printer(); int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag); + std::string default_lan_username() const; int check_cert(); void install_device_cert(std::string dev_id, bool lan_only); bool start_discovery(bool start, bool sending); @@ -164,7 +184,9 @@ public: int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn); int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn); FilamentSyncMode get_filament_sync_mode() const; - bool fetch_filament_info(std::string dev_id); + bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull); + CameraStreamMode get_camera_stream_mode() const; + std::string get_local_camera_stream_url() const; std::string to_orca_filament_id(const std::string& printer_filament_id) const; std::string from_orca_filament_id(const std::string& orca_filament_id) const; int request_bind_ticket(std::string* ticket); @@ -195,6 +217,7 @@ private: std::map> m_cloud_agents; std::shared_ptr m_printer_agent; std::string m_printer_agent_id; + std::atomic m_user_machine_list_generation{0}; }; } diff --git a/src/slic3r/Utils/NetworkAgentFactory.cpp b/src/slic3r/Utils/NetworkAgentFactory.cpp index ff950d0946..e2e58b7510 100644 --- a/src/slic3r/Utils/NetworkAgentFactory.cpp +++ b/src/slic3r/Utils/NetworkAgentFactory.cpp @@ -16,8 +16,10 @@ #include #include #include +#include #include #include +#include namespace Slic3r { namespace { @@ -314,6 +316,21 @@ void NetworkAgentFactory::register_python_printer_agent(const std::string& plugi std::shared_ptr cached_agent; + auto reject_conflicting_capability = [plugin_key, capability_name](const std::string& error_message) { + if (!wxTheApp || GUI::wxGetApp().is_closing()) + return; + GUI::wxGetApp().CallAfter([plugin_key, capability_name, error_message]() { + if (GUI::wxGetApp().is_closing()) + return; + PluginManager& manager = PluginManager::instance(); + manager.set_plugin_error(plugin_key, error_message); + // note: the unload callback triggered by disabling will call deregister, + // which will be a no-op since the printer agent is never registered + manager.set_capability_enabled({PluginCapabilityType::PrinterConnection, capability_name, plugin_key}, false); + wxMessageBox(wxString::FromUTF8(error_message.c_str()), _L("Plugins"), wxOK | wxICON_WARNING, GUI::wxGetApp().GetTopWindow()); + }); + }; + { std::lock_guard lock(s_registry_mutex); @@ -332,9 +349,10 @@ void NetworkAgentFactory::register_python_printer_agent(const std::string& plugi auto& python_agent_ids = get_python_printer_agent_ids(); for (const auto& pair : python_agent_ids) { if (pair.first != capability_key && pair.second == info.id) { - BOOST_LOG_TRIVIAL(warning) << "Printer-agent plugin '" << capability_name << "' uses duplicate agent ID '" << info.id - << "' already registered by capability '" << pair.first.second << "' from plugin '" - << pair.first.first << "'"; + const std::string error_message = "Printer-agent '" + info.name + "' could not be enabled: agent ID '" + info.id + + "' is already registered by capability '" + pair.first.second + "' from plugin '" + pair.first.first + "'."; + BOOST_LOG_TRIVIAL(warning) << error_message; + reject_conflicting_capability(error_message); return; } } @@ -354,12 +372,22 @@ void NetworkAgentFactory::register_python_printer_agent(const std::string& plugi auto& agents = get_printer_agents(); auto agent_it = agents.find(info.id); + // why: reject only when the ID is owned by SOMEONE ELSE - a built-in has an empty + // plugin_identifier, another plugin/capability has a different plugin_full_ref. When it + // IS the same plugin_full_ref, this capability is just re-registering itself, so fall + // through and refresh. if (agent_it != agents.end() && agent_it->second.plugin_identifier != plugin_full_ref) { - BOOST_LOG_TRIVIAL(warning) << "Printer-agent plugin '" << capability_name << "' uses agent ID '" << info.id - << "' already registered by '" << agent_it->second.display_name << "'"; + const std::string error_message = "Printer-agent '" + info.name + "' could not be enabled: agent ID '" + info.id + + "' is already registered by '" + agent_it->second.display_name + "'."; + BOOST_LOG_TRIVIAL(warning) << error_message; + reject_conflicting_capability(error_message); return; } + // why: insert_or_assign, not emplace - reaching here means the ID is new, or the same + // capability is re-registering (same plugin_full_ref). In the re-register case we WANT to + // overwrite so the factory closure points at the current live capability instance; emplace + // would silently keep the stale entry. agents.insert_or_assign(info.id, PrinterAgentInfo(info.id, info.name, plugin_full_ref, std::move(factory))); python_agent_ids[capability_key] = info.id; diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp index 5d129a490e..f84d250c0d 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp @@ -83,6 +83,7 @@ constexpr const char* ORCA_UNSUBSCRIBE_PLUGINS = "/api/v1/plugins/subscriptions" constexpr const char* ORCA_PLUGINS_MINE = "/api/v1/plugins/mine"; constexpr const char* ORCA_PLUGINS_BASE = "/api/v1/plugins"; constexpr const char* ORCA_PLUGIN_DOWNLOAD_URL = "/api/v1/plugins/download"; +constexpr const char* ORCA_CLOUD_PRINTER = "/api/v1/printers"; constexpr const char* ORCA_CLOUD_LOGIN_PATH = "/orcaslicer-login"; @@ -2623,11 +2624,51 @@ int OrcaCloudServiceAgent::check_user_task_report(int* task_id, bool* printable) int OrcaCloudServiceAgent::get_user_print_info(unsigned int* http_code, std::string* http_body) { - BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_user_print_info (stub)"; + std::string response; + unsigned int code = 0; + int result = http_get(ORCA_CLOUD_PRINTER, &response, &code); + if (http_code) - *http_code = 200; - if (http_body) - *http_body = "{}"; + *http_code = code; + + if (result != 0 || code != 200) + return result != 0 ? result : BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED; + + try { + auto resp_json = nlohmann::json::parse(response); + nlohmann::json devices = nlohmann::json::array(); + + for (const auto& printer : resp_json.value("data", nlohmann::json::array())) { + const std::string role = printer.value("access_role", ""); + if (role.empty() || role == "viewer") + continue; + + nlohmann::json device; + device["dev_id"] = printer.value("id", ""); + device["dev_name"] = printer.value("name", ""); + if (printer.contains("model") && printer["model"].is_string()) + device["dev_model_name"] = printer["model"].get(); + + bool online = false; + if (printer.contains("status_snapshot") && printer["status_snapshot"].is_object()) { + const auto& status = printer["status_snapshot"].value("status", nlohmann::json::object()); + online = status.value("connection", nlohmann::json::object()).value("state", "") == "online"; + if (status.contains("job") && status["job"].is_object()) + device["task_status"] = status["job"].value("state", ""); + } + device["dev_online"] = online; + devices.push_back(std::move(device)); + } + + if (http_body) { + nlohmann::json out; + out["devices"] = std::move(devices); + *http_body = out.dump(); + } + } catch (const std::exception&) { + return BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED; + } + return BAMBU_NETWORK_SUCCESS; } diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.hpp b/src/slic3r/Utils/OrcaCloudServiceAgent.hpp index 3ae86ec27c..565772cf96 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.hpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.hpp @@ -1,6 +1,7 @@ #ifndef __ORCA_CLOUD_SERVICE_AGENT_HPP__ #define __ORCA_CLOUD_SERVICE_AGENT_HPP__ +#include "ICameraSignalingChannel.hpp" #include "ICloudServiceAgent.hpp" #include #include @@ -241,6 +242,7 @@ public: // ICloudServiceAgent Interface Implementation - Model Mall & Publishing // ======================================================================== int get_camera_url(std::string dev_id, std::function callback) override; + // std::unique_ptr create_camera_signaling_channel(const std::string& dev_id) override; int get_design_staffpick(int offset, int limit, std::function callback) override; int start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out) override; int get_model_publish_url(std::string* url) override; diff --git a/src/slic3r/Utils/OrcaPrinterAgent.cpp b/src/slic3r/Utils/OrcaPrinterAgent.cpp index cb70dafcca..987cc0efdb 100644 --- a/src/slic3r/Utils/OrcaPrinterAgent.cpp +++ b/src/slic3r/Utils/OrcaPrinterAgent.cpp @@ -1,4 +1,5 @@ #include "OrcaPrinterAgent.hpp" +#include "IPrinterAgent.hpp" #include "NetworkAgentFactory.hpp" namespace Slic3r { @@ -26,7 +27,7 @@ int OrcaPrinterAgent::send_message(std::string dev_id, std::string json_str, int return BAMBU_NETWORK_SUCCESS; } -int OrcaPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) +int OrcaPrinterAgent::connect_printer(const PrinterConnectionParams& params) { return BAMBU_NETWORK_SUCCESS; } @@ -41,19 +42,6 @@ int OrcaPrinterAgent::send_message_to_printer(std::string dev_id, std::string js return BAMBU_NETWORK_SUCCESS; } -// ============================================================================ -// Certificates - All Stubs -// ============================================================================ - -int OrcaPrinterAgent::check_cert() -{ - return BAMBU_NETWORK_SUCCESS; -} - -void OrcaPrinterAgent::install_device_cert(std::string dev_id, bool lan_only) -{ -} - // ============================================================================ // Discovery - Stub // ============================================================================ @@ -63,47 +51,11 @@ bool OrcaPrinterAgent::start_discovery(bool start, bool sending) return true; } -// ============================================================================ -// Binding - All Stubs -// ============================================================================ - -int OrcaPrinterAgent::ping_bind(std::string ping_code) -{ - return BAMBU_NETWORK_SUCCESS; -} - int OrcaPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) { return BAMBU_NETWORK_SUCCESS; } -int OrcaPrinterAgent::bind( - std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) -{ - return BAMBU_NETWORK_SUCCESS; -} - -int OrcaPrinterAgent::unbind(std::string dev_id) -{ - return BAMBU_NETWORK_SUCCESS; -} - -int OrcaPrinterAgent::request_bind_ticket(std::string* ticket) -{ - if (ticket) - *ticket = ""; - return BAMBU_NETWORK_SUCCESS; -} - -int OrcaPrinterAgent::get_hms_snapshot(std::string dev_id, std::string file_name, std::function callback) -{ - // No BBL cloud snapshot source; report failure so the caller falls back. - (void) dev_id; - (void) file_name; - (void) callback; - return -1; -} - int OrcaPrinterAgent::set_server_callback(OnServerErrFn fn) { std::lock_guard lock(state_mutex); diff --git a/src/slic3r/Utils/OrcaPrinterAgent.hpp b/src/slic3r/Utils/OrcaPrinterAgent.hpp index a1613420a5..7d2a75fe0f 100644 --- a/src/slic3r/Utils/OrcaPrinterAgent.hpp +++ b/src/slic3r/Utils/OrcaPrinterAgent.hpp @@ -28,24 +28,15 @@ public: // Communication int send_message(std::string dev_id, std::string json_str, int qos, int flag) override; - int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override; + int connect_printer(const PrinterConnectionParams& params) override; int disconnect_printer() override; int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override; - // Certificates - int check_cert() override; - void install_device_cert(std::string dev_id, bool lan_only) override; - // Discovery bool start_discovery(bool start, bool sending) override; // Binding - int ping_bind(std::string ping_code) override; int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override; - int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override; - int unbind(std::string dev_id) override; - int request_bind_ticket(std::string* ticket) override; - int get_hms_snapshot(std::string dev_id, std::string file_name, std::function callback) override; int set_server_callback(OnServerErrFn fn) override; // Machine Selection diff --git a/src/slic3r/Utils/PrinterNetworkTypes.hpp b/src/slic3r/Utils/PrinterNetworkTypes.hpp new file mode 100644 index 0000000000..d87be6cdcb --- /dev/null +++ b/src/slic3r/Utils/PrinterNetworkTypes.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +namespace Slic3r { + +enum LiveviewLocal { + LVL_None, + LVL_Disable, + LVL_Local, + LVL_Rtsps, + LVL_Rtsp +}; + +enum LiveviewRemote { + LVR_None, + LVR_Tutk, + LVR_Agora, + LVR_TutkAgora +}; + +enum FileLocal { + FL_None, + FL_Local +}; + +enum FileRemote { + FR_None, + FR_Tutk, + FR_Agora, + FR_TutkAgora +}; + +} // namespace Slic3r diff --git a/src/slic3r/Utils/QidiPrinterAgent.cpp b/src/slic3r/Utils/QidiPrinterAgent.cpp index 1f437853ba..4a90ec3771 100644 --- a/src/slic3r/Utils/QidiPrinterAgent.cpp +++ b/src/slic3r/Utils/QidiPrinterAgent.cpp @@ -40,7 +40,7 @@ AgentInfo QidiPrinterAgent::get_agent_info_static() return AgentInfo{"qidi", "Qidi", QidiPrinterAgent_VERSION, "Qidi printer agent"}; } -bool QidiPrinterAgent::fetch_filament_info(std::string dev_id) +bool QidiPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode /*sync_mode*/) { std::string error; diff --git a/src/slic3r/Utils/QidiPrinterAgent.hpp b/src/slic3r/Utils/QidiPrinterAgent.hpp index a8326edb53..ce7b4cda0d 100644 --- a/src/slic3r/Utils/QidiPrinterAgent.hpp +++ b/src/slic3r/Utils/QidiPrinterAgent.hpp @@ -19,7 +19,7 @@ public: AgentInfo get_agent_info() override { return get_agent_info_static(); } // Override filament sync (Qidi-specific implementation) - bool fetch_filament_info(std::string dev_id) override; + bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override; private: struct QidiFilamentDict diff --git a/src/slic3r/Utils/SnapmakerPrinterAgent.cpp b/src/slic3r/Utils/SnapmakerPrinterAgent.cpp index 5783738af7..6039eb260f 100644 --- a/src/slic3r/Utils/SnapmakerPrinterAgent.cpp +++ b/src/slic3r/Utils/SnapmakerPrinterAgent.cpp @@ -104,7 +104,7 @@ std::string SnapmakerPrinterAgent::combine_filament_type(const std::string& type return base; } -bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id) +bool SnapmakerPrinterAgent::fetch_filament_info(std::string dev_id, FilamentSyncMode /*sync_mode*/) { std::string url = join_url(device_info.base_url, "/printer/objects/query?print_task_config&filament_detect"); diff --git a/src/slic3r/Utils/SnapmakerPrinterAgent.hpp b/src/slic3r/Utils/SnapmakerPrinterAgent.hpp index 04c0a66b3f..d49fe21239 100644 --- a/src/slic3r/Utils/SnapmakerPrinterAgent.hpp +++ b/src/slic3r/Utils/SnapmakerPrinterAgent.hpp @@ -15,7 +15,7 @@ public: static AgentInfo get_agent_info_static(); AgentInfo get_agent_info() override { return get_agent_info_static(); } - bool fetch_filament_info(std::string dev_id) override; + bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override; private: // Combine filament_type + filament_sub_type into a unified type string diff --git a/src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp b/src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp index d428775c12..d0c88404fe 100644 --- a/src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp +++ b/src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp @@ -23,6 +23,14 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module) .value("Pull", FilamentSyncMode::pull) .export_values(); + py::enum_(printer_agent_module, "CameraStreamMode") + .value("None_", CameraStreamMode::none) + .value("HTTP", CameraStreamMode::http) + .value("HTTPS", CameraStreamMode::https) + .value("HTTP_SNAPSHOT", CameraStreamMode::http_snapshot) + .value("RTSP", CameraStreamMode::rtsp) + .export_values(); + py::class_(printer_agent_module, "AgentInfo") .def(py::init<>()) .def(py::init([](std::string id, std::string name, std::string version, std::string description) { @@ -89,6 +97,16 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module) .def_readwrite("task_ext_change_assist", &PrintParams::task_ext_change_assist) .def_readwrite("try_emmc_print", &PrintParams::try_emmc_print); + py::class_(printer_agent_module, "PrinterConnectionParams") + .def(py::init<>()) + .def_readwrite("dev_id", &PrinterConnectionParams::dev_id) + .def_readwrite("host", &PrinterConnectionParams::host) + .def_readwrite("port", &PrinterConnectionParams::port) + .def_readwrite("username", &PrinterConnectionParams::username) + .def_readwrite("password", &PrinterConnectionParams::password) + .def_readwrite("use_ssl", &PrinterConnectionParams::use_ssl) + .def_readwrite("ca_file", &PrinterConnectionParams::ca_file); + py::class_>( printer_agent_module, "PrinterAgentBase") .def(py::init<>()) @@ -98,6 +116,16 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module) .def("disconnect_printer", &PrinterAgentPluginCapability::disconnect_printer) .def("send_message", &PrinterAgentPluginCapability::send_message) .def("send_message_to_printer", &PrinterAgentPluginCapability::send_message_to_printer) + .def("command_ams_refresh_rfid", &PrinterAgentPluginCapability::command_ams_refresh_rfid) + .def("command_ams_calibrate", &PrinterAgentPluginCapability::command_ams_calibrate) + .def("command_ams_select_tray", &PrinterAgentPluginCapability::command_ams_select_tray) + .def("command_start_camera", &PrinterAgentPluginCapability::command_start_camera) + .def("command_xyz_abs", &PrinterAgentPluginCapability::command_xyz_abs) + .def("command_auto_leveling", &PrinterAgentPluginCapability::command_auto_leveling) + .def("command_go_home", &PrinterAgentPluginCapability::command_go_home) + .def("command_set_bed", &PrinterAgentPluginCapability::command_set_bed) + .def("command_set_nozzle", &PrinterAgentPluginCapability::command_set_nozzle) + .def("command_axis_control", &PrinterAgentPluginCapability::command_axis_control) .def("start_discovery", &PrinterAgentPluginCapability::start_discovery) .def("bind_detect", &PrinterAgentPluginCapability::bind_detect) .def("get_user_selected_machine", &PrinterAgentPluginCapability::get_user_selected_machine) @@ -105,7 +133,9 @@ void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module) .def("start_send_gcode_to_sdcard", &PrinterAgentPluginCapability::start_send_gcode_to_sdcard) .def("start_local_print", &PrinterAgentPluginCapability::start_local_print) .def("get_filament_sync_mode", &PrinterAgentPluginCapability::get_filament_sync_mode) + .def("get_camera_stream_mode", &PrinterAgentPluginCapability::get_camera_stream_mode) .def("fetch_filament_info", &PrinterAgentPluginCapability::fetch_filament_info) + .def("get_camera_url", &PrinterAgentPluginCapability::get_camera_url) .def("check_cert", &PrinterAgentPluginCapability::check_cert) .def("install_device_cert", &PrinterAgentPluginCapability::install_device_cert) .def("ping_bind", &PrinterAgentPluginCapability::ping_bind) diff --git a/src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp b/src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp index ede7c6a9b8..d33cee7afe 100644 --- a/src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp +++ b/src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp @@ -5,7 +5,6 @@ #include "IPrinterAgent.hpp" -#include #include #include @@ -24,60 +23,39 @@ public: PluginCapabilityType get_type() const override { return PluginCapabilityType::PrinterConnection; } // set_cloud_agent is the host-managed dependency injection point — the host hands the - // capability its ICloudServiceAgent — so it is the one operation kept native here. Every - // other IPrinterAgent operation is pure: the Python plugin must implement all of them. + // capability its ICloudServiceAgent — so it is the one operation kept native here. Optional + // operations inherit IPrinterAgent's defaults; the remaining operations are plugin-defined. void set_cloud_agent(std::shared_ptr cloud) final override { (void) cloud; } AgentInfo get_agent_info() override = 0; - int connect_printer( - std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override = 0; - int send_message(std::string dev_id, std::string json_str, int qos, int flag) override = 0; - int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override = 0; - bool start_discovery(bool start, bool sending) override = 0; - int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override = 0; - std::string get_user_selected_machine() override = 0; - int set_user_selected_machine(std::string dev_id) override = 0; - int start_send_gcode_to_sdcard(PrintParams params, - OnUpdateStatusFn update_fn, - WasCancelledFn cancel_fn, - OnWaitFn wait_fn) override = 0; - int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override = 0; - FilamentSyncMode get_filament_sync_mode() const override = 0; - bool fetch_filament_info(std::string dev_id) override = 0; + int connect_printer(const PrinterConnectionParams& params) override = 0; + int send_message(std::string dev_id, std::string json_str, int qos, int flag) override = 0; + int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override = 0; + bool start_discovery(bool start, bool sending) override = 0; + int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override = 0; + std::string get_user_selected_machine() override = 0; + int set_user_selected_machine(std::string dev_id) override = 0; + int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override = 0; + int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override = 0; - int check_cert() override = 0; - void install_device_cert(std::string dev_id, bool lan_only) override = 0; - int ping_bind(std::string ping_code) override = 0; - int bind(std::string dev_ip, - std::string dev_id, - std::string dev_model, - std::string sec_link, - std::string timezone, - bool improved, - OnUpdateStatusFn update_fn) override = 0; - int unbind(std::string dev_id) override = 0; - // request_bind_ticket has a std::string* out-param that cannot round-trip through a - // pybind11 override directly; the trampoline wraps it (the Python plugin returns a - // (result, ticket) tuple), so it stays pure here like the rest. - int request_bind_ticket(std::string* ticket) override = 0; - int get_hms_snapshot(std::string dev_id, std::string file_name, std::function callback) override = 0; - int set_server_callback(OnServerErrFn fn) override = 0; + int set_server_callback(OnServerErrFn fn) override = 0; int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override = 0; int start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, - OnWaitFn wait_fn) override = 0; - int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override = 0; + OnWaitFn wait_fn) override = 0; + int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override = 0; + + int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) override = 0; + int set_on_printer_connected_fn(OnPrinterConnectedFn fn) override = 0; + int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn) override = 0; + int set_on_message_fn(OnMessageFn fn) override = 0; + int set_on_user_message_fn(OnMessageFn fn) override = 0; + int set_on_local_connect_fn(OnLocalConnectedFn fn) override = 0; + int set_on_local_message_fn(OnMessageFn fn) override = 0; + int set_queue_on_main_fn(QueueOnMainFn fn) override = 0; - int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) override = 0; - int set_on_printer_connected_fn(OnPrinterConnectedFn fn) override = 0; - int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn) override = 0; - int set_on_message_fn(OnMessageFn fn) override = 0; - int set_on_user_message_fn(OnMessageFn fn) override = 0; - int set_on_local_connect_fn(OnLocalConnectedFn fn) override = 0; - int set_on_local_message_fn(OnMessageFn fn) override = 0; - int set_queue_on_main_fn(QueueOnMainFn fn) override = 0; }; } // namespace Slic3r diff --git a/src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp b/src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp index cdb533d2cb..2e15523430 100644 --- a/src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp +++ b/src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp @@ -5,7 +5,10 @@ #include "../../PyPluginTrampoline.hpp" #include "IPrinterAgent.hpp" +#include "pybind11/pybind11.h" +#include #include +#include #include @@ -22,6 +25,12 @@ } ORCA_PY_AGENT_CATCH(name) \ return printer_agent_failure() +#define ORCA_PY_AGENT_OVERRIDE_DEFAULT(ret, name, ...) \ + try { \ + ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, ret, PrinterAgentPluginCapability, name, ##__VA_ARGS__); \ + } ORCA_PY_AGENT_CATCH(name) \ + return printer_agent_failure() + namespace Slic3r { // NetworkAgent's no-agent answer: -1 for a status code, the empty value (false, "", none) otherwise. template T printer_agent_failure() @@ -42,9 +51,9 @@ public: ORCA_PY_AGENT_OVERRIDE(AgentInfo, get_agent_info); } - int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override + int connect_printer(const PrinterConnectionParams& params) override { - ORCA_PY_AGENT_OVERRIDE(int, connect_printer, dev_id, dev_ip, username, password, use_ssl); + ORCA_PY_AGENT_OVERRIDE(int, connect_printer, params); } int disconnect_printer() override @@ -62,6 +71,61 @@ public: ORCA_PY_AGENT_OVERRIDE(int, send_message_to_printer, dev_id, json_str, qos, flag); } + int command_ams_refresh_rfid(std::string dev_id, int ams_id,int slot_id, int sequence_id, bool lan_mode) override + { + ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_ams_refresh_rfid, dev_id, ams_id, slot_id, sequence_id, lan_mode); + } + + int command_ams_calibrate(std::string dev_id, int ams_id, int sequence_id, bool lan_mode) override + { + ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_ams_calibrate, dev_id, ams_id, sequence_id, lan_mode); + } + + int command_ams_select_tray(std::string dev_id, std::string tray_id, int sequence_id, bool lan_mode) override + { + ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_ams_select_tray, dev_id, tray_id, sequence_id, lan_mode); + } + + int command_start_camera(std::string dev_id) override + { + ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_start_camera, dev_id); + } + + int command_xyz_abs(std::string dev_id, int sequence_id, bool lan_mode) override + { + ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_xyz_abs, dev_id, sequence_id, lan_mode); + } + + int command_auto_leveling(std::string dev_id, int sequence_id, bool lan_mode) override + { + ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_auto_leveling, dev_id, sequence_id, lan_mode); + } + + int command_go_home(std::string dev_id, bool is_printing, bool supports_mqtt_homing, + int sequence_id, bool lan_mode) override + { + ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_go_home, dev_id, is_printing, supports_mqtt_homing, sequence_id, lan_mode); + } + + int command_set_bed(std::string dev_id, int temp, bool supports_mqtt_bed_ctrl, + int sequence_id, bool lan_mode) override + { + ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_set_bed, dev_id, temp, supports_mqtt_bed_ctrl, sequence_id, lan_mode); + } + + int command_set_nozzle(std::string dev_id, int temp, int sequence_id, bool lan_mode) override + { + ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_set_nozzle, dev_id, temp, sequence_id, lan_mode); + } + + int command_axis_control(std::string dev_id, std::string axis, double unit, double input_val, + int speed, bool is_core_xy, bool supports_mqtt_axis_control, + int sequence_id, bool lan_mode) override + { + ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, command_axis_control, dev_id, axis, unit, input_val, speed, + is_core_xy, supports_mqtt_axis_control, sequence_id, lan_mode); + } + bool start_discovery(bool start, bool sending) override { ORCA_PY_AGENT_OVERRIDE(bool, start_discovery, start, sending); @@ -95,12 +159,22 @@ public: FilamentSyncMode get_filament_sync_mode() const override { - ORCA_PY_AGENT_OVERRIDE(FilamentSyncMode, get_filament_sync_mode); + ORCA_PY_AGENT_OVERRIDE_DEFAULT(FilamentSyncMode, get_filament_sync_mode); } - bool fetch_filament_info(std::string dev_id) override + CameraStreamMode get_camera_stream_mode() const override { - ORCA_PY_AGENT_OVERRIDE(bool, fetch_filament_info, dev_id); + ORCA_PY_AGENT_OVERRIDE_DEFAULT(CameraStreamMode, get_camera_stream_mode); + } + + std::string get_camera_url() const override + { + ORCA_PY_AGENT_OVERRIDE_DEFAULT(std::string, get_camera_url); + } + + bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode) override + { + ORCA_PY_AGENT_OVERRIDE_DEFAULT(bool, fetch_filament_info, dev_id, sync_mode); } int check_cert() override @@ -110,7 +184,7 @@ public: void install_device_cert(std::string dev_id, bool lan_only) override { - ORCA_PY_AGENT_OVERRIDE(void, install_device_cert, dev_id, lan_only); + ORCA_PY_AGENT_OVERRIDE_DEFAULT(void, install_device_cert, dev_id, lan_only); } int ping_bind(std::string ping_code) override @@ -145,7 +219,7 @@ public: int get_hms_snapshot(std::string dev_id, std::string file_name, std::function callback) override { - ORCA_PY_AGENT_OVERRIDE(int, get_hms_snapshot, dev_id, file_name, callback); + ORCA_PY_AGENT_OVERRIDE_DEFAULT(int, get_hms_snapshot, dev_id, file_name, callback); } int set_server_callback(OnServerErrFn fn) override @@ -196,6 +270,8 @@ public: // request_bind_ticket returns its ticket through a std::string* out-param, which pybind11 // cannot marshal back through a plain override. We dispatch manually: the Python plugin // returns a (result, ticket) tuple, which we unpack into the int result and the out-param. + // Not required to be implemented, so a missing override answers with the same failure value + // as the other printer-agent operations. Leave the out-param untouched on failure. int request_bind_ticket(std::string* ticket) override { try { @@ -207,7 +283,7 @@ public: pybind11::function override = pybind11::get_override(static_cast(this), "request_bind_ticket"); if (!override) - pybind11::pybind11_fail("Tried to call pure virtual function \"PrinterAgentPluginCapability::request_bind_ticket\""); + return printer_agent_failure(); try { pybind11::tuple result = override().cast(); if (ticket) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f79a3c54c2..21077cd2c1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,18 +57,28 @@ function(orcaslicer_copy_test_dlls) # into it). Copy them next to the executable and give it an $ORIGIN # rpath so the loader finds them when the tests run on the CI unit-test # runner, which only receives this build/tests tree. - get_target_property(_linked_libs ${_TEST_NAME}_tests LINK_LIBRARIES) + # + # Optional first arg: the target to patch, for directories that define + # more than one test executable. Defaults to ${_TEST_NAME}_tests so + # existing single-executable callers don't need to pass it. + if (ARGC GREATER 0) + set(_target ${ARGV0}) + else() + set(_target ${_TEST_NAME}_tests) + endif() + + get_target_property(_linked_libs ${_target} LINK_LIBRARIES) if (NOT "libslic3r_gui" IN_LIST _linked_libs) return() endif() - set_property(TARGET ${_TEST_NAME}_tests PROPERTY BUILD_RPATH "$ORIGIN") + set_property(TARGET ${_target} PROPERTY BUILD_RPATH "$ORIGIN") set(_configs ${CMAKE_CONFIGURATION_TYPES}) if (NOT _configs) set(_configs "${CMAKE_BUILD_TYPE}") endif() foreach(_cfg IN LISTS _configs) - orcaslicer_copy_sos(${_TEST_NAME}_tests "${_cfg}" "" _unused_sos) + orcaslicer_copy_sos(${_target} "${_cfg}" "" _unused_sos) endforeach() endif() endfunction() diff --git a/tests/slic3rutils/CMakeLists.txt b/tests/slic3rutils/CMakeLists.txt index a3863134d5..f38dfeaa98 100644 --- a/tests/slic3rutils/CMakeLists.txt +++ b/tests/slic3rutils/CMakeLists.txt @@ -5,6 +5,9 @@ add_executable(${_TEST_NAME}_tests test_creality_cfs_match.cpp test_dev_mapping.cpp test_filament_bitmap_utils.cpp + test_device_progress.cpp + test_device_manager_integration.cpp + test_web_media_controller.cpp test_scene_raycaster.cpp test_lazy.cpp test_prebuild_queue.cpp @@ -17,6 +20,8 @@ add_executable(${_TEST_NAME}_tests test_plugin_capability_config.cpp test_plugin_config.cpp test_plugin_capabilities_in_use.cpp + test_plugin_status.cpp + test_printer_agent.cpp test_plugin_install.cpp test_plugin_lifecycle.cpp test_plugin_printer_agent.cpp @@ -39,7 +44,7 @@ endif () target_link_libraries(${_TEST_NAME}_tests test_common libslic3r_gui libslic3r pybind11::embed Catch2::Catch2WithMain) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") -orcaslicer_copy_test_dlls() +orcaslicer_copy_test_dlls(${_TEST_NAME}_tests) if (WIN32) add_custom_command(TARGET ${_TEST_NAME}_tests POST_BUILD diff --git a/tests/slic3rutils/test_dev_mapping.cpp b/tests/slic3rutils/test_dev_mapping.cpp index 1c1528f818..658117d2e4 100644 --- a/tests/slic3rutils/test_dev_mapping.cpp +++ b/tests/slic3rutils/test_dev_mapping.cpp @@ -26,6 +26,37 @@ using json = nlohmann::json; using namespace Slic3r; +TEST_CASE("AMS tray placeholder state follows the latest status", "[DevFilaSystem]") +{ + MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1"); + + const json empty_slot = json::parse(R"({ + "ams": { + "tray_exist_bits": "0", + "ams": [ { "id": "0", "info": "00000001", "tray": [ + { "id": "0", "tray_slot_placeholder": "1", "tray_color": "00000000" } + ] } ] + } + })"); + DevFilaSystemParser::ParseV1_0(empty_slot, &obj, obj.GetFilaSystem().get(), false); + + DevAmsTray* tray = obj.GetFilaSystem()->GetAmsTray("0", "0"); + REQUIRE(tray != nullptr); + REQUIRE(tray->is_slot_placeholder); + + const json loaded_slot = json::parse(R"({ + "ams": { + "tray_exist_bits": "1", + "ams": [ { "id": "0", "info": "00000001", "tray": [ + { "id": "0", "tray_color": "FF0000FF" } + ] } ] + } + })"); + DevFilaSystemParser::ParseV1_0(loaded_slot, &obj, obj.GetFilaSystem().get(), false); + + CHECK_FALSE(tray->is_slot_placeholder); +} + TEST_CASE("Switch-bound AMS trays map to the left extruder", "[DevMapping]") { MachineObject obj(nullptr, nullptr, "test", "test_dev", "127.0.0.1"); diff --git a/tests/slic3rutils/test_device_manager_integration.cpp b/tests/slic3rutils/test_device_manager_integration.cpp new file mode 100644 index 0000000000..2b042d4f8e --- /dev/null +++ b/tests/slic3rutils/test_device_manager_integration.cpp @@ -0,0 +1,159 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +using namespace Slic3r; +using json = nlohmann::json; + +namespace { + +class StubCloudAgent final : public OrcaCloudServiceAgent +{ +public: + StubCloudAgent() : OrcaCloudServiceAgent("") {} + + int get_user_print_info(unsigned int* http_code, std::string* http_body) override + { + if (http_code) + *http_code = 200; + if (http_body) + *http_body = R"({"devices":[]})"; + return 0; + } + + std::string get_user_name() override { return "integration-test-user"; } +}; + +class TestPrinterAgent final : public OrcaPrinterAgent +{ +public: + explicit TestPrinterAgent(std::string id) + : OrcaPrinterAgent(""), m_info{std::move(id), "Integration Test Agent", "1.0", "test agent"} + { + } + + AgentInfo get_agent_info() override { return m_info; } + +private: + AgentInfo m_info; +}; + +struct ScopedAppConfig +{ + AppConfig config; +}; + +std::string machine_list_response(const std::string& provider, const std::string& agent_id, + std::uint64_t generation, const std::string& name) +{ + json machine; + machine["dev_id"] = "integration-device"; + machine["dev_name"] = name; + machine["dev_online"] = true; + machine["task_status"] = "idle"; + + json response; + response["provider"] = provider; + response["agent_id"] = agent_id; + response["generation"] = generation; + response["devices"] = json::array({machine}); + return response.dump(); +} + +} // namespace + +TEST_CASE("Network agent stamps user-machine responses with request context", "[DeviceManager][integration]") +{ + auto cloud = std::make_shared(); + NetworkAgent network(cloud, nullptr); + network.set_printer_agent(std::make_shared("integration-agent-a")); + + const std::uint64_t generation_before = network.get_user_machine_list_generation(); + unsigned int http_code = 0; + std::string body; + REQUIRE(network.get_user_print_info(&http_code, &body, ORCA_CLOUD_PROVIDER) == 0); + + const json response = json::parse(body); + CHECK(http_code == 200); + CHECK(response["provider"] == ORCA_CLOUD_PROVIDER); + CHECK(response["agent_id"] == "integration-agent-a"); + CHECK(response["generation"] == generation_before + 1); +} + +TEST_CASE("Device manager ignores stale cloud machine responses", "[DeviceManager][integration]") +{ + ScopedAppConfig app_config; + NetworkAgent network(nullptr, std::make_shared("integration-agent")); + network.set_printer_agent(std::make_shared("integration-agent")); + DeviceManager manager(&network, false, &app_config.config); + + const std::uint64_t current_generation = network.get_user_machine_list_generation(); + manager.parse_user_print_info(machine_list_response(ORCA_CLOUD_PROVIDER, "integration-agent", + current_generation, "Fresh name")); + + auto machines = manager.get_user_machinelist(); + REQUIRE(machines.size() == 1); + REQUIRE(machines.at("integration-device") != nullptr); + CHECK(machines.at("integration-device")->get_dev_name() == "Fresh name"); + + manager.parse_user_print_info(machine_list_response(BBL_CLOUD_PROVIDER, "integration-agent", + current_generation, "Stale provider")); + manager.parse_user_print_info(machine_list_response(ORCA_CLOUD_PROVIDER, "other-agent", + current_generation, "Stale agent")); + manager.parse_user_print_info(machine_list_response(ORCA_CLOUD_PROVIDER, "integration-agent", + current_generation + 1, "Stale generation")); + + machines = manager.get_user_machinelist(); + REQUIRE(machines.size() == 1); + CHECK(machines.at("integration-device")->get_dev_name() == "Fresh name"); +} + +TEST_CASE("Device manager filters and rehomes devices by printer-agent ownership", "[DeviceManager][integration]") +{ + ScopedAppConfig app_config; + auto agent_a = std::make_shared("integration-agent-a"); + auto agent_b = std::make_shared("integration-agent-b"); + NetworkAgent network(nullptr, agent_a); + DeviceManager manager(&network, false, &app_config.config); + + BBLocalMachine machine; + machine.dev_id = "integration-lan-device"; + machine.dev_name = "Integration LAN device"; + machine.dev_ip = "192.0.2.10"; + machine.printer_type = "C11"; + + MachineObject* object = manager.insert_local_device(machine, "lan", "free", "", "access-code"); + REQUIRE(object != nullptr); + CHECK(object->printer_agent_id == "integration-agent-a"); + CHECK(manager.get_my_machine_list("integration-agent-a").count(machine.dev_id) == 1); + CHECK(manager.get_my_machine_list("integration-agent-b").empty()); + + network.set_printer_agent(agent_b); + CHECK(manager.get_my_machine_list("integration-agent-b").empty()); + + manager.on_machine_alive(R"({ + "dev_name":"Rediscovered device", + "dev_id":"integration-lan-device", + "dev_ip":"192.0.2.10", + "dev_type":"C11", + "dev_signal":"strong", + "connect_type":"lan", + "bind_state":"free" + })"); + + CHECK(object->printer_agent_id == "integration-agent-b"); + CHECK(manager.get_my_machine_list("integration-agent-a").empty()); + CHECK(manager.get_my_machine_list("integration-agent-b").count(machine.dev_id) == 1); +} diff --git a/tests/slic3rutils/test_device_progress.cpp b/tests/slic3rutils/test_device_progress.cpp new file mode 100644 index 0000000000..96dead46f0 --- /dev/null +++ b/tests/slic3rutils/test_device_progress.cpp @@ -0,0 +1,141 @@ +// why: match the GUI include order to avoid rpcndr.h byte/std::byte +// ambiguity in the Windows COM headers. +// why: wx/timer.h must precede DeviceManager.hpp because +// DeviceErrorDialog.hpp uses wxTimerEvent. +#ifdef WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include +#endif + +#include + +#include + +#include + +#include "slic3r/GUI/DeviceManager.hpp" + +#include + +using json = nlohmann::json; +using namespace Slic3r; + +TEST_CASE("Integer progress reaches the shared subtask", "[DeviceManager][Progress]") +{ + MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1"); + + machine.update_print_progress(json(37)); + + REQUIRE(machine.mc_print_percent == 37); + BBLSubTask* subtask = machine.get_subtask(); + REQUIRE(subtask != nullptr); + CHECK(subtask->task_progress == 37); +} + +TEST_CASE("String progress reaches the shared subtask", "[DeviceManager][Progress]") +{ + MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1"); + + machine.update_print_progress(json("41")); + + REQUIRE(machine.mc_print_percent == 41); + BBLSubTask* subtask = machine.get_subtask(); + REQUIRE(subtask != nullptr); + CHECK(subtask->task_progress == 41); +} + +TEST_CASE("Floating-point progress preserves the previous shared value", "[DeviceManager][Progress]") +{ + MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1"); + + machine.update_print_progress(json(29)); + REQUIRE(machine.mc_print_percent == 29); + BBLSubTask* subtask = machine.get_subtask(); + REQUIRE(subtask != nullptr); + REQUIRE(subtask->task_progress == 29); + + machine.update_print_progress(json(29.5)); + BBLSubTask* current_subtask = machine.get_subtask(); + REQUIRE(current_subtask != nullptr); + REQUIRE(current_subtask == subtask); + CHECK(machine.mc_print_percent == 29); + CHECK(current_subtask->task_progress == 29); +} + +TEST_CASE("Unsupported progress values leave a fresh machine unchanged", "[DeviceManager][Progress]") +{ + SECTION("boolean") { + MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1"); + REQUIRE(machine.subtask_ == nullptr); + + machine.update_print_progress(json(true)); + + CHECK(machine.mc_print_percent == 0); + CHECK(machine.subtask_ == nullptr); + } + + SECTION("null") { + MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1"); + REQUIRE(machine.subtask_ == nullptr); + + machine.update_print_progress(json(nullptr)); + + CHECK(machine.mc_print_percent == 0); + CHECK(machine.subtask_ == nullptr); + } + + SECTION("object") { + MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1"); + REQUIRE(machine.subtask_ == nullptr); + + machine.update_print_progress(json::object()); + + CHECK(machine.mc_print_percent == 0); + CHECK(machine.subtask_ == nullptr); + } + + SECTION("array") { + MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1"); + REQUIRE(machine.subtask_ == nullptr); + + machine.update_print_progress(json::array()); + + CHECK(machine.mc_print_percent == 0); + CHECK(machine.subtask_ == nullptr); + } +} + +TEST_CASE("Malformed string progress leaves a fresh machine unchanged", "[DeviceManager][Progress]") +{ + MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1"); + REQUIRE(machine.subtask_ == nullptr); + + CHECK_NOTHROW(machine.update_print_progress(json("not-a-percent"))); + CHECK(machine.mc_print_percent == 0); + CHECK(machine.subtask_ == nullptr); +} + +TEST_CASE("Zero progress replaces active shared progress", "[DeviceManager][Progress]") +{ + MachineObject machine(nullptr, nullptr, "test", "test-device", "127.0.0.1"); + + machine.update_print_progress(json(63)); + BBLSubTask* subtask = machine.get_subtask(); + REQUIRE(subtask != nullptr); + REQUIRE(machine.mc_print_percent == 63); + REQUIRE(subtask->task_progress == 63); + + machine.set_print_state("FAILED"); + machine.update_print_progress(json(0)); + + BBLSubTask* current_subtask = machine.get_subtask(); + REQUIRE(current_subtask != nullptr); + REQUIRE(current_subtask == subtask); + REQUIRE(machine.mc_print_percent == 0); + CHECK(current_subtask->task_progress == 0); +} diff --git a/tests/slic3rutils/test_plugin_lifecycle.cpp b/tests/slic3rutils/test_plugin_lifecycle.cpp index bac3a19ea9..0c96f8c0b3 100644 --- a/tests/slic3rutils/test_plugin_lifecycle.cpp +++ b/tests/slic3rutils/test_plugin_lifecycle.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -77,6 +78,96 @@ class EchoPackage(orca.base): orca.register_capability(Echo) )PY"; +// A minimal printer-agent plugin exposing exactly one PrinterConnection capability. +const char* const PRINTER_AGENT_PLUGIN_SOURCE = R"PY(# /// script +# requires-python = ">=3.12" +# dependencies = [] +# +# [tool.orcaslicer.plugin] +# name = "Lifecycle Test Agent" +# description = "Minimal printer-agent plugin for the lifecycle test." +# author = "tests" +# version = "1.0.0" +# type = "printer-connection" +# /// +import orca + + +class LifecycleTestAgentCapability(orca.printer_agent.PrinterAgentBase): + def get_name(self): + return "Lifecycle Test Agent" + + def get_agent_info(self): + return orca.printer_agent.AgentInfo( + id="lifecycle-test-agent", + name="Lifecycle Test Agent", + version="1.0.0", + description="Lifecycle test printer agent", + ) + + +@orca.plugin +class LifecycleTestPlugin(orca.base): + def register_capabilities(self): + orca.register_capability(LifecycleTestAgentCapability) +)PY"; + +// In production GUI_App::init_plugin_gui_wiring subscribes the agent-registry +// callbacks. The test binary has no GUI, so install the same unload-side wiring +// once so these tests exercise the production deregister-on-unload path. +// +// The load-side (register) wiring is deliberately not installed: duplicate-id +// coverage registers agents manually in a deterministic order instead. +void install_agent_registry_wiring() +{ + static bool installed = false; + if (installed) + return; + installed = true; + + PluginManager& manager = PluginManager::instance(); + manager.subscribe_on_unload_callback(NetworkAgentFactory::deregister_python_plugin); + manager.subscribe_on_capability_unload_callback([](const PluginCapabilityId& capability) { + if (capability.type == PluginCapabilityType::PrinterConnection) + NetworkAgentFactory::deregister_python_printer_agent(capability.plugin_key, capability.name); + }); +} + +// Duplicate-id coverage needs two distinct plugins with different package keys +// and classes while both return the same AgentInfo.id. +std::string make_agent_plugin_source(const std::string& suffix, + const std::string& display_name, + const std::string& agent_id) +{ + return std::string{} + + "# /// script\n" + + "# requires-python = \">=3.12\"\n" + + "# dependencies = []\n" + + "#\n" + + "# [tool.orcaslicer.plugin]\n" + + "# name = \"" + display_name + "\"\n" + + "# description = \"Duplicate-id fake printer-agent plugin.\"\n" + + "# author = \"tests\"\n" + + "# version = \"1.0.0\"\n" + + "# type = \"printer-connection\"\n" + + "# ///\n" + + "import orca\n" + + "\n\n" + + "class Cap" + suffix + "(orca.printer_agent.PrinterAgentBase):\n" + + " def get_name(self):\n" + + " return \"" + display_name + "\"\n" + + "\n" + + " def get_agent_info(self):\n" + + " return orca.printer_agent.AgentInfo(\n" + + " id=\"" + agent_id + "\", name=\"" + display_name + "\",\n" + + " version=\"1.0.0\", description=\"duplicate id test\")\n" + + "\n\n" + + "@orca.plugin\n" + + "class Plugin" + suffix + "(orca.base):\n" + + " def register_capabilities(self):\n" + + " orca.register_capability(Cap" + suffix + ")\n"; +} + // Writes {data_dir}/orca_plugins//.py and returns the plugin directory. fs::path write_plugin(const ScopedDataDir& data_dir_guard, const std::string& stem, const std::string& source) { @@ -222,6 +313,157 @@ TEST_CASE("A discovered script plugin loads and materializes its capability", "[ manager.unload_plugin("Echo_Plugin"); } +TEST_CASE("A printer-agent plugin registers and deregisters with its lifecycle", "[PluginLifecycle][Python]") +{ + ScopedPluginManager plugin_system; + if (!plugin_system.initialized) + SKIP("Bundled Python interpreter unavailable: " + PythonInterpreter::instance().last_error()); + + ScopedDataDir data_dir_guard("printer-agent-load"); + write_plugin(data_dir_guard, "LifecycleTestAgent", PRINTER_AGENT_PLUGIN_SOURCE); + + PluginManager& manager = PluginManager::instance(); + install_agent_registry_wiring(); + manager.discover_plugins(/*async=*/false, /*clear=*/true); + + PluginDescriptor descriptor; + REQUIRE(manager.try_get_valid_plugin_descriptor("LifecycleTestAgent", descriptor)); + + std::string error; + REQUIRE(load_and_wait(manager, "LifecycleTestAgent", error)); + INFO("load error: " << error); + + const auto capabilities = manager.get_plugin_capabilities( + "LifecycleTestAgent", PluginCapabilityType::PrinterConnection); + REQUIRE(capabilities.size() == 1); + NetworkAgentFactory::register_python_printer_agent("LifecycleTestAgent", capabilities.front()->name()); + + CHECK(NetworkAgentFactory::is_printer_agent_registered("lifecycle-test-agent")); + REQUIRE(manager.unload_plugin("LifecycleTestAgent")); + CHECK_FALSE(NetworkAgentFactory::is_printer_agent_registered("lifecycle-test-agent")); +} + +TEST_CASE("A duplicate printer-agent id is rejected without clobbering its owner", "[PluginLifecycle][Python]") +{ + ScopedPluginManager plugin_system; + if (!plugin_system.initialized) + SKIP("Bundled Python interpreter unavailable: " + PythonInterpreter::instance().last_error()); + + const std::string key_a = "DuplicateIdAgentA"; + const std::string key_b = "DuplicateIdAgentB"; + const std::string dup_id = "duplicate-id-agent"; + + ScopedDataDir data_dir_guard("printer-agent-duplicate"); + write_plugin(data_dir_guard, key_a, make_agent_plugin_source("A", "Duplicate Id Agent A", dup_id)); + write_plugin(data_dir_guard, key_b, make_agent_plugin_source("B", "Duplicate Id Agent B", dup_id)); + + PluginManager& manager = PluginManager::instance(); + install_agent_registry_wiring(); + manager.discover_plugins(/*async=*/false, /*clear=*/true); + + PluginDescriptor descriptor_a; + PluginDescriptor descriptor_b; + REQUIRE(manager.try_get_valid_plugin_descriptor(key_a, descriptor_a)); + REQUIRE(manager.try_get_valid_plugin_descriptor(key_b, descriptor_b)); + + std::string error; + REQUIRE(load_and_wait(manager, key_a, error)); + REQUIRE(load_and_wait(manager, key_b, error)); + + const auto capabilities_a = manager.get_plugin_capabilities(key_a, PluginCapabilityType::PrinterConnection); + const auto capabilities_b = manager.get_plugin_capabilities(key_b, PluginCapabilityType::PrinterConnection); + REQUIRE(capabilities_a.size() == 1); + REQUIRE(capabilities_b.size() == 1); + + NetworkAgentFactory::register_python_printer_agent(key_a, capabilities_a.front()->name()); + NetworkAgentFactory::register_python_printer_agent(key_b, capabilities_b.front()->name()); + + CHECK(NetworkAgentFactory::is_printer_agent_registered(dup_id)); + const PrinterAgentInfo* info = NetworkAgentFactory::get_printer_agent_info(dup_id); + REQUIRE(info != nullptr); + CHECK(info->plugin_identifier.find(key_a) != std::string::npos); + CHECK(info->plugin_identifier.find(key_b) == std::string::npos); + + REQUIRE(manager.unload_plugin(key_a)); + CHECK_FALSE(NetworkAgentFactory::is_printer_agent_registered(dup_id)); + manager.unload_plugin(key_b); +} + +TEST_CASE("A printer-agent plugin cannot claim a built-in agent id", "[PluginLifecycle][Python]") +{ + ScopedPluginManager plugin_system; + if (!plugin_system.initialized) + SKIP("Bundled Python interpreter unavailable: " + PythonInterpreter::instance().last_error()); + + NetworkAgentFactory::register_all_agents(); + REQUIRE(NetworkAgentFactory::is_printer_agent_registered(BBL_PRINTER_AGENT_ID)); + + const std::string plugin_key = "BuiltinClashAgent"; + ScopedDataDir data_dir_guard("printer-agent-builtin-clash"); + write_plugin(data_dir_guard, plugin_key, + make_agent_plugin_source("Clash", "Builtin Clash", BBL_PRINTER_AGENT_ID)); + + PluginManager& manager = PluginManager::instance(); + install_agent_registry_wiring(); + manager.discover_plugins(/*async=*/false, /*clear=*/true); + + PluginDescriptor descriptor; + REQUIRE(manager.try_get_valid_plugin_descriptor(plugin_key, descriptor)); + + std::string error; + REQUIRE(load_and_wait(manager, plugin_key, error)); + const auto capabilities = manager.get_plugin_capabilities(plugin_key, PluginCapabilityType::PrinterConnection); + REQUIRE(capabilities.size() == 1); + NetworkAgentFactory::register_python_printer_agent(plugin_key, capabilities.front()->name()); + + const PrinterAgentInfo* info = NetworkAgentFactory::get_printer_agent_info(BBL_PRINTER_AGENT_ID); + REQUIRE(info != nullptr); + CHECK_FALSE(info->is_plugin()); + CHECK(info->plugin_identifier.find(plugin_key) == std::string::npos); + + manager.unload_plugin(plugin_key); +} + +TEST_CASE("Re-registering a printer-agent capability preserves its registration", "[PluginLifecycle][Python]") +{ + ScopedPluginManager plugin_system; + if (!plugin_system.initialized) + SKIP("Bundled Python interpreter unavailable: " + PythonInterpreter::instance().last_error()); + + const std::string plugin_key = "ReRegisterAgent"; + const std::string agent_id = "re-register-agent"; + + ScopedDataDir data_dir_guard("printer-agent-reregister"); + write_plugin(data_dir_guard, plugin_key, + make_agent_plugin_source("Re", "Re Register", agent_id)); + + PluginManager& manager = PluginManager::instance(); + install_agent_registry_wiring(); + manager.discover_plugins(/*async=*/false, /*clear=*/true); + + PluginDescriptor descriptor; + REQUIRE(manager.try_get_valid_plugin_descriptor(plugin_key, descriptor)); + + std::string error; + REQUIRE(load_and_wait(manager, plugin_key, error)); + const auto capabilities = manager.get_plugin_capabilities(plugin_key, PluginCapabilityType::PrinterConnection); + REQUIRE(capabilities.size() == 1); + + NetworkAgentFactory::register_python_printer_agent(plugin_key, capabilities.front()->name()); + REQUIRE(NetworkAgentFactory::is_printer_agent_registered(agent_id)); + const PrinterAgentInfo* first = NetworkAgentFactory::get_printer_agent_info(agent_id); + REQUIRE(first != nullptr); + const std::string owner = first->plugin_identifier; + + NetworkAgentFactory::register_python_printer_agent(plugin_key, capabilities.front()->name()); + CHECK(NetworkAgentFactory::is_printer_agent_registered(agent_id)); + const PrinterAgentInfo* second = NetworkAgentFactory::get_printer_agent_info(agent_id); + REQUIRE(second != nullptr); + CHECK(second->plugin_identifier == owner); + + manager.unload_plugin(plugin_key); +} + TEST_CASE("Plugin manager can initialize again after shutdown", "[PluginLifecycle][Python]") { ScopedPluginManager plugin_system; diff --git a/tests/slic3rutils/test_plugin_printer_agent.cpp b/tests/slic3rutils/test_plugin_printer_agent.cpp index 2065360080..fc85363a39 100644 --- a/tests/slic3rutils/test_plugin_printer_agent.cpp +++ b/tests/slic3rutils/test_plugin_printer_agent.cpp @@ -115,6 +115,22 @@ TEST_CASE("A printer agent that omits its operations answers like a missing agen check_answers_like_no_agent(*agent); } +TEST_CASE("A printer agent uses IPrinterAgent defaults for omitted commands", "[PluginPrinterAgent][Python]") +{ + ScopedPluginManager plugin_system; + if (!plugin_system.initialized) + SKIP("Bundled Python interpreter unavailable: " + PythonInterpreter::instance().last_error()); + py::gil_scoped_acquire gil; + + auto agent = make_agent(" def send_message(self, dev_id, json_str, qos, flag): return 7\n" + " def send_message_to_printer(self, dev_id, json_str, qos, flag): return 8\n"); + REQUIRE(agent.agent); + + CHECK(agent->command_xyz_abs("dev", 1, false) == 7); + CHECK(agent->command_set_nozzle("dev", 200, 2, true) == 8); + CHECK(agent->command_ams_refresh_rfid("dev", -1, 0, 3, false) == ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED); +} + TEST_CASE("A printer agent operation returning the wrong type answers like a missing agent", "[PluginPrinterAgent][Python]") { ScopedPluginManager plugin_system; diff --git a/tests/slic3rutils/test_plugin_status.cpp b/tests/slic3rutils/test_plugin_status.cpp new file mode 100644 index 0000000000..c51c54c6c6 --- /dev/null +++ b/tests/slic3rutils/test_plugin_status.cpp @@ -0,0 +1,23 @@ +#include + +#include + +using Slic3r::GUI::PluginStatus; +using Slic3r::GUI::resolve_plugin_status; + +TEST_CASE("resolve_plugin_status precedence", "[plugin][status]") { + // the new branch: loaded module + error -> runtime fault, not a load failure. + REQUIRE(resolve_plugin_status(/*loading*/ false, /*has_error*/ true, /*is_loaded*/ true) == PluginStatus::RuntimeError); + + // error without a live module is a load-time Error. + REQUIRE(resolve_plugin_status(false, true, false) == PluginStatus::Error); + + // loading wins over a pending error so a reload never flashes red. + REQUIRE(resolve_plugin_status(true, true, true) == PluginStatus::Loading); + + // healthy loaded plugin. + REQUIRE(resolve_plugin_status(false, false, true) == PluginStatus::Activated); + + // nothing loaded, no error. + REQUIRE(resolve_plugin_status(false, false, false) == PluginStatus::Inactive); +} diff --git a/tests/slic3rutils/test_printer_agent.cpp b/tests/slic3rutils/test_printer_agent.cpp new file mode 100644 index 0000000000..1418001205 --- /dev/null +++ b/tests/slic3rutils/test_printer_agent.cpp @@ -0,0 +1,173 @@ +#include + +#include +#include + +#include "python_test_support.hpp" + +#include +#include + +#include + +using namespace Slic3r; +namespace py = pybind11; + +// =========================================================================== +// UNIT - printer-agent registry duplicate handling. +// Confirms a duplicate agent id is rejected so a plugin cannot shadow a built-in +// or previously registered agent. +// =========================================================================== +TEST_CASE("unit: printer-agent registry register / lookup / duplicate-reject", "[registry][unit]") +{ + // why: the registry is process-global state shared by the test binary, and + // Catch2 may run cases in any order. Use an id that cannot collide with + // built-ins or other cases. Avoid SECTIONs because each section re-runs the + // body and would register the same id twice. + const std::string id = "orca-test::registry-probe-7f3a"; + auto stub_factory = [](std::shared_ptr, const std::string&) + -> std::shared_ptr { return nullptr; }; + + REQUIRE_FALSE(NetworkAgentFactory::is_printer_agent_registered(id)); + + REQUIRE(NetworkAgentFactory::register_printer_agent(id, "Registry Probe", stub_factory)); + REQUIRE(NetworkAgentFactory::is_printer_agent_registered(id)); + + // Re-registering the same id is rejected and does not replace the entry. + REQUIRE_FALSE(NetworkAgentFactory::register_printer_agent(id, "Impostor", stub_factory)); + + // The first registration's display name survives the rejected duplicate. + const PrinterAgentInfo* info = NetworkAgentFactory::get_printer_agent_info(id); + REQUIRE(info != nullptr); + CHECK(info->display_name == "Registry Probe"); + + // It appears exactly once in the UI-population list. + auto agents = NetworkAgentFactory::get_registered_printer_agents(); + int count = 0; + for (const auto& a : agents) + if (a.id == id) + ++count; + CHECK(count == 1); +} + +// =========================================================================== +// INTEGRATION - the orca.printer_agent Python binding surface. +// Boots the embedded interpreter and asserts the C++ to Python contract that +// every printer-agent plugin subclasses. If a binding is renamed or removed, +// plugins fail at runtime even though C++ still compiles. +// =========================================================================== +// ensure_python_initialized()/import_orca_module() come from python_test_support.hpp: +// a bare scoped_interpreter with no PyConfig.home falls back to the build-time +// stdlib path, which doesn't exist beside this test binary, so Py_Initialize() +// fails to load the codecs needed for the filesystem encoding. The shared helper +// points PyConfig.home at the python/ runtime staged next to the test executable. + +TEST_CASE("integration: orca.printer_agent binding surface", "[integration][Python]") +{ + py::module_ orca = import_orca_module(); + + REQUIRE(py::hasattr(orca, "printer_agent")); + py::object pa = orca.attr("printer_agent"); + + // The base class every printer-agent plugin subclasses. + REQUIRE(py::hasattr(pa, "PrinterAgentBase")); + py::object base = pa.attr("PrinterAgentBase"); + for (const char* method : { "get_agent_info", "connect_printer", "disconnect_printer", + "send_message", "send_message_to_printer", + "command_ams_refresh_rfid", "command_ams_calibrate", + "command_ams_select_tray", "command_start_camera", + "command_xyz_abs", "command_auto_leveling", "command_go_home", + "command_set_bed", "command_set_nozzle", "command_axis_control", + "start_discovery", "bind_detect", + "start_print", "get_filament_sync_mode" }) { + CAPTURE(method); + CHECK(py::hasattr(base, method)); + } + + // AgentInfo value type - the registry identity the host reads (id is the key). + REQUIRE(py::hasattr(pa, "AgentInfo")); + py::object info = pa.attr("AgentInfo")("moonraker", "Moonraker", "1.0", "test agent"); + CHECK(info.attr("id").cast() == "moonraker"); + CHECK(info.attr("name").cast() == "Moonraker"); + + // FilamentSyncMode enum the host queries to pick pull vs subscription. + REQUIRE(py::hasattr(pa, "FilamentSyncMode")); + py::object mode = pa.attr("FilamentSyncMode"); + CHECK(py::hasattr(mode, "Pull")); + CHECK(py::hasattr(mode, "Subscription")); + CHECK(py::hasattr(mode, "None_")); + + REQUIRE(py::hasattr(pa, "CameraStreamMode")); + py::object camera_mode = pa.attr("CameraStreamMode"); + CHECK(py::hasattr(camera_mode, "HTTPS")); + CHECK_FALSE(py::hasattr(camera_mode, "WebRTC")); + + // Plugin-type enum exposed at module root (host reads it without the GIL). + CHECK(py::hasattr(orca, "PluginType")); +} + +// =========================================================================== +// INTEGRATION - plugin-registration API and discovery-context guards. +// These are the symbols every plugin package uses: the @orca.plugin decorator, +// orca.base, orca.register_capability, and the capability base modules. Checking +// them in the lightweight embedded-interpreter test catches binding breakage +// before the plugin-loader test needs to run. +// =========================================================================== +TEST_CASE("integration: orca plugin-registration API surface + discovery-context guards", "[integration][Python]") +{ + py::module_ orca = import_orca_module(); + + // Module-level surface every plugin package relies on. + // note: no "gcode" module here - this branch has no G-code capability module; + // PostProcessing exists only as a PluginType value. + for (const char* name : { "plugin", "register_capability", "base", "PythonPluginBase", + "PluginType", "PluginResult", "script", "printer_agent", "host" }) { + CAPTURE(name); + CHECK(py::hasattr(orca, name)); + } + + // Plugin package base and capability base contract. + CHECK(py::hasattr(orca.attr("base"), "register_capabilities")); + py::object cap_base = orca.attr("PythonPluginBase"); + for (const char* method : { "get_name", "get_type", "on_load", "on_unload" }) { + CAPTURE(method); + CHECK(py::hasattr(cap_base, method)); + } + + // The script capability module exposes its own base class. + CHECK(py::hasattr(orca.attr("script"), "ScriptPluginCapabilityBase")); + + // PluginType enum carries the values that route a capability, including PrinterConnection. + // note: no PostProcessing value on this branch's binding. + py::object types = orca.attr("PluginType"); + for (const char* value : { "PrinterConnection", "Script" }) { + CAPTURE(value); + CHECK(py::hasattr(types, value)); + } + + // note: this is testing behavior, not normal plugin loading. + // These APIs should only work while Orca is actively loading a plugin. + try { + // Calls Python's orca.register_capability(0) from C++. + // 0 is intentionally bogus. The important part is that there is no active + // plugin load context, so the function should reject the call immediately. + orca.attr("register_capability")(py::int_(0)); + + // If the call above does NOT throw, the test fails here. + FAIL("register_capability outside discovery context must raise"); + } catch (const py::error_already_set& error) { + // pybind11 wraps Python exceptions as py::error_already_set. + // This checks the Python exception type is ValueError. + CHECK(error.matches(PyExc_ValueError)); + } + + try { + // This is the function behind @orca.plugin. + // Same logic as above. + orca.attr("plugin")(py::int_(0)); + + FAIL("@orca.plugin outside discovery context must raise"); + } catch (const py::error_already_set& error) { + CHECK(error.matches(PyExc_ValueError)); + } +} diff --git a/tests/slic3rutils/test_web_media_controller.cpp b/tests/slic3rutils/test_web_media_controller.cpp new file mode 100644 index 0000000000..cdf756ca3a --- /dev/null +++ b/tests/slic3rutils/test_web_media_controller.cpp @@ -0,0 +1,113 @@ +#include + +#include + +#include + +#include +#include + +using namespace Slic3r; +using namespace Slic3r::GUI; + +namespace { + +class StubWebView final : public wxWebView +{ +public: + bool SetBackgroundColour(const wxColour&) override + { + events.emplace_back("background"); + return true; + } + + bool Create(wxWindow*, wxWindowID, const wxString&, const wxPoint&, const wxSize&, long, const wxString&) override { return true; } + wxString GetCurrentTitle() const override { return {}; } + wxString GetCurrentURL() const override { return {}; } + bool IsBusy() const override { return false; } + bool IsEditable() const override { return false; } + void LoadURL(const wxString& url) override + { + events.emplace_back("url"); + loaded_url = url.ToStdString(); + } + void Print() override {} + void RegisterHandler(wxSharedPtr) override {} + void Reload(wxWebViewReloadFlags) override {} + void SetEditable(bool) override {} + void Stop() override { events.emplace_back("stop"); } + bool CanGoBack() const override { return false; } + bool CanGoForward() const override { return false; } + void GoBack() override {} + void GoForward() override {} + void ClearHistory() override { events.emplace_back("history"); } + void EnableHistory(bool) override {} + wxVector> GetBackwardHistory() override { return {}; } + wxVector> GetForwardHistory() override { return {}; } + void LoadHistoryItem(wxSharedPtr) override {} + bool CanSetZoomType(wxWebViewZoomType) const override { return false; } + float GetZoomFactor() const override { return 1.0f; } + wxWebViewZoomType GetZoomType() const override { return wxWEBVIEW_ZOOM_TYPE_LAYOUT; } + void SetZoomFactor(float) override {} + void SetZoomType(wxWebViewZoomType) override {} + bool CanUndo() const override { return false; } + bool CanRedo() const override { return false; } + void Undo() override {} + void Redo() override {} + void* GetNativeBackend() const override { return nullptr; } + + bool RunScript(const wxString& javascript, wxString*) const override + { + events.emplace_back("script"); + script = javascript.ToStdString(); + return true; + } + +protected: + void DoSetPage(const wxString& html, const wxString& base_url) override + { + events.emplace_back("page"); + page = html.ToStdString(); + page_base = base_url.ToStdString(); + } + +public: + mutable std::vector events; + std::string page; + std::string page_base; + mutable std::string script; + std::string loaded_url; +}; + +} // namespace + +TEST_CASE("Web media controller tears down a snapshot lifecycle", "[WebMediaController][integration]") +{ + StubWebView view; + WebMediaController controller(&view); + + controller.set_mode(CameraStreamMode::http_snapshot); + controller.Load(wxURI("http://camera.example/frame.jpg")); + controller.Play(); + + REQUIRE(view.events.size() == 3); + CHECK(view.events[0] == "background"); + CHECK(view.events[1] == "page"); + CHECK(view.events[2] == "page"); + CHECK(view.page.find("stopCameraRefresh") != std::string::npos); + CHECK(view.page.find("http://camera.example/frame.jpg") != std::string::npos); + + controller.Stop(); + + REQUIRE(view.events.size() == 7); + CHECK(view.events[3] == "script"); + CHECK(view.events[4] == "stop"); + CHECK(view.events[5] == "page"); + CHECK(view.events[6] == "history"); + CHECK(view.script == "if(typeof stopCameraRefresh==='function') stopCameraRefresh();"); + CHECK(view.page.empty()); + CHECK(view.page_base == "about:blank"); + + controller.Play(); + CHECK(view.page.find("http://camera.example/frame.jpg") == std::string::npos); +} From ffbbd62355d3be161043073f800a5f33604cdb33 Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Wed, 30 Sep 2026 14:33:35 -0300 Subject: [PATCH 6/6] Clean design Docs and move context (#15803) --- deps/OCCT/OCCT.cmake | 2 +- docs/CAD/GAP_ANALYSIS_vs_ONSHAPE.md | 160 -- docs/CAD/cad_dependency_weight.md | 136 -- docs/CAD/cad_ux_guidelines.md | 704 -------- .../mate-connectors/BEAR_CONNECTOR_REVIEW.md | 169 -- .../mate-connectors/BearConnector_Cutter.step | 998 ---------- .../mate-connectors/BearConnector_Female.step | 1173 ------------ .../BearConnector_Female_Trimmed.step | 1048 ----------- .../BearConnector_Female_Trimmed.stl | Bin 74884 -> 0 bytes .../BearConnector_Female_Trimmed_wall.step | 1604 ----------------- .../BearConnector_Female_Trimmed_wall.stl | Bin 97684 -> 0 bytes .../mate-connectors/DESIGN_MATE_CONNECTORS.md | 922 ---------- .../design/mate-connectors/bear-01-view.png | Bin 100101 -> 0 bytes .../design/mate-connectors/bear-02-view.png | Bin 96449 -> 0 bytes .../design/mate-connectors/bear-02-zoom.png | Bin 276302 -> 0 bytes docs/CAD/design/mate-connectors/bear-flat.png | Bin 20664 -> 0 bytes .../design/mate-connectors/bear-sil-cmp.png | Bin 1330 -> 0 bytes docs/CAD/design/mate-connectors/bear-sil.png | Bin 1688 -> 0 bytes .../design/mate-connectors/bear_glyph_table.h | 30 - .../CAD/design/mate-connectors/bear_mesh.json | 1 - .../circularmateconnectors.png | Bin 13095 -> 0 bytes docs/CAD/design/mate-connectors/cmp-sheet.png | Bin 22841 -> 0 bytes .../connector-glyph-proposal.html | 299 --- .../design/mate-connectors/coplanar_test.py | 68 - .../cylindricalmateconnectors.png | Bin 17385 -> 0 bytes .../mate-connectors/faceted_ridge_key.scad | 140 -- docs/CAD/design/mate-connectors/fem-0.png | Bin 5985 -> 0 bytes docs/CAD/design/mate-connectors/fem-1.png | Bin 4068 -> 0 bytes docs/CAD/design/mate-connectors/fem-2.png | Bin 4274 -> 0 bytes docs/CAD/design/mate-connectors/fem-3.png | Bin 5767 -> 0 bytes docs/CAD/design/mate-connectors/fem-sheet.png | Bin 26495 -> 0 bytes .../CAD/design/mate-connectors/fem-sheet2.png | Bin 26906 -> 0 bytes docs/CAD/design/mate-connectors/female.stl | 226 --- .../design/mate-connectors/female_only.scad | 12 - docs/CAD/design/mate-connectors/fit_check.py | 20 - .../design/mate-connectors/glyph-preview.png | Bin 12890 -> 0 bytes .../design/mate-connectors/glyph_preview.py | 99 - .../CAD/design/mate-connectors/glyph_probe.py | 143 -- .../mate-connectors/handedness-sheet.png | Bin 35843 -> 0 bytes docs/CAD/design/mate-connectors/handedness.py | 112 -- .../linearedgemateconnectors.png | Bin 20301 -> 0 bytes .../CAD/design/mate-connectors/make_female.py | 135 -- .../mate-connector-virtual-sharp.png | Bin 31835 -> 0 bytes .../mateconnector-planarcentroid.png | Bin 26069 -> 0 bytes .../mateconnector-planarpoints.png | Bin 27133 -> 0 bytes .../mate-connectors/matepointiconLG.png | Bin 2368 -> 0 bytes .../matepointreorientsecondaryaxis.png | Bin 10329 -> 0 bytes .../design/mate-connectors/muzzle_variants.py | 71 - .../planarfacemateconnectors.png | Bin 5679 -> 0 bytes .../design/mate-connectors/relief-sheet.png | Bin 24238 -> 0 bytes .../design/mate-connectors/relief_sheet.py | 99 - .../CAD/design/mate-connectors/relief_test.py | 9 - docs/CAD/design/mate-connectors/render_key.py | 85 - docs/CAD/design/mate-connectors/render_stl.py | 76 - docs/CAD/design/mate-connectors/rk-0.png | Bin 4974 -> 0 bytes docs/CAD/design/mate-connectors/rk-1.png | Bin 5548 -> 0 bytes docs/CAD/design/mate-connectors/rk-2.png | Bin 4940 -> 0 bytes docs/CAD/design/mate-connectors/rk-3.png | Bin 6240 -> 0 bytes docs/CAD/design/mate-connectors/rk-4.png | Bin 4992 -> 0 bytes docs/CAD/design/mate-connectors/rk-back.png | 0 docs/CAD/design/mate-connectors/rk-front.png | 0 docs/CAD/design/mate-connectors/rk-iso.png | 0 docs/CAD/design/mate-connectors/rk-sheet.png | Bin 22160 -> 0 bytes .../design/mate-connectors/simplify-sheet.png | Bin 50634 -> 0 bytes .../design/mate-connectors/simplify_study.py | 142 -- docs/CAD/design/mate-connectors/size-test.png | Bin 9289 -> 0 bytes .../design/mate-connectors/sz-22-down+Z.png | Bin 293 -> 0 bytes .../design/mate-connectors/sz-22-grazing.png | Bin 204 -> 0 bytes docs/CAD/design/mate-connectors/sz-22-iso.png | Bin 238 -> 0 bytes .../design/mate-connectors/sz-32-down+Z.png | Bin 359 -> 0 bytes .../design/mate-connectors/sz-32-grazing.png | Bin 263 -> 0 bytes docs/CAD/design/mate-connectors/sz-32-iso.png | Bin 314 -> 0 bytes .../design/mate-connectors/sz-48-down+Z.png | Bin 502 -> 0 bytes .../design/mate-connectors/sz-48-grazing.png | Bin 344 -> 0 bytes docs/CAD/design/mate-connectors/sz-48-iso.png | Bin 464 -> 0 bytes docs/CAD/design/mate-connectors/t-+0.1-0.png | Bin 5951 -> 0 bytes docs/CAD/design/mate-connectors/t-+0.1-1.png | Bin 4786 -> 0 bytes docs/CAD/design/mate-connectors/t-+1.6-0.png | Bin 7205 -> 0 bytes docs/CAD/design/mate-connectors/t-+1.6-1.png | Bin 5871 -> 0 bytes docs/CAD/design/mate-connectors/trim-cmp.png | Bin 19567 -> 0 bytes .../CAD/design/mate-connectors/trim_female.py | 74 - .../design/mate-connectors/verify_trimmed.py | 25 - docs/CAD/design_tab.md | 333 ---- docs/CAD/design_tab_pr_description.md | 73 - docs/CAD/design_tab_upstream_portability.md | 162 -- docs/CAD/ux/interaction-model.md | 144 -- docs/CAD/ux/mockups/cmp_12__edge_str.svg | 1 - docs/CAD/ux/mockups/cmp_12__face_planar.svg | 1 - docs/CAD/ux/mockups/cmp_12__sk_line.svg | 1 - docs/CAD/ux/mockups/cmp_8__edge_str.svg | 1 - docs/CAD/ux/mockups/cmp_8__face_planar.svg | 1 - docs/CAD/ux/mockups/cmp_8__sk_line.svg | 1 - docs/CAD/ux/mockups/fresh__art.svg | 1 - docs/CAD/ux/mockups/fresh__art__modify.svg | 1 - docs/CAD/ux/mockups/fresh__bodies_2.svg | 1 - docs/CAD/ux/mockups/fresh__body_sheet.svg | 1 - .../ux/mockups/fresh__body_sheet__modify.svg | 1 - docs/CAD/ux/mockups/fresh__body_solid.svg | 1 - .../ux/mockups/fresh__body_solid__modify.svg | 1 - docs/CAD/ux/mockups/fresh__coordsys.svg | 1 - .../ux/mockups/fresh__coordsys__modify.svg | 1 - docs/CAD/ux/mockups/fresh__datum_axis.svg | 1 - .../ux/mockups/fresh__datum_axis__modify.svg | 1 - docs/CAD/ux/mockups/fresh__datum_plane.svg | 1 - .../ux/mockups/fresh__datum_plane__modify.svg | 1 - .../mockups/fresh__datum_plane__reference.svg | 1 - docs/CAD/ux/mockups/fresh__edge_circ.svg | 1 - docs/CAD/ux/mockups/fresh__edge_str.svg | 1 - .../ux/mockups/fresh__edge_str__reference.svg | 1 - docs/CAD/ux/mockups/fresh__face_cyl.svg | 1 - .../ux/mockups/fresh__face_cyl__reference.svg | 1 - docs/CAD/ux/mockups/fresh__face_other.svg | 1 - docs/CAD/ux/mockups/fresh__face_planar.svg | 1 - .../mockups/fresh__face_planar__reference.svg | 1 - docs/CAD/ux/mockups/fresh__none.svg | 1 - .../CAD/ux/mockups/fresh__none__reference.svg | 1 - docs/CAD/ux/mockups/fresh__sk_2ent.svg | 1 - .../CAD/ux/mockups/fresh__sk_2ent__create.svg | 1 - .../ux/mockups/fresh__sk_2ent__dressup.svg | 1 - .../CAD/ux/mockups/fresh__sk_2ent__modify.svg | 1 - .../ux/mockups/fresh__sk_2ent__reference.svg | 1 - docs/CAD/ux/mockups/fresh__sk_arc.svg | 1 - docs/CAD/ux/mockups/fresh__sk_arc__create.svg | 1 - docs/CAD/ux/mockups/fresh__sk_arc__modify.svg | 1 - .../ux/mockups/fresh__sk_arc__reference.svg | 1 - docs/CAD/ux/mockups/fresh__sk_line.svg | 1 - .../CAD/ux/mockups/fresh__sk_line__create.svg | 1 - .../ux/mockups/fresh__sk_line__dressup.svg | 1 - .../CAD/ux/mockups/fresh__sk_line__modify.svg | 1 - .../ux/mockups/fresh__sk_line__reference.svg | 1 - docs/CAD/ux/mockups/fresh__sk_loop.svg | 1 - docs/CAD/ux/mockups/fresh__sk_loop__add.svg | 1 - .../CAD/ux/mockups/fresh__sk_loop__modify.svg | 1 - docs/CAD/ux/mockups/fresh__sk_none.svg | 1 - .../CAD/ux/mockups/fresh__sk_none__create.svg | 1 - .../ux/mockups/fresh__sk_none__reference.svg | 1 - docs/CAD/ux/mockups/fresh__sk_point.svg | 1 - .../ux/mockups/fresh__sk_point__create.svg | 1 - .../ux/mockups/fresh__sk_point__reference.svg | 1 - docs/CAD/ux/mockups/fresh__vertex.svg | 1 - .../ux/mockups/fresh__vertex__reference.svg | 1 - docs/CAD/ux/mockups/gen_offer_mockups.py | 884 --------- docs/CAD/ux/mockups/list__body_solid.svg | 1 - docs/CAD/ux/mockups/list__edge_str.svg | 1 - docs/CAD/ux/mockups/list__face_planar.svg | 1 - .../CAD/ux/mockups/list__face_planar__add.svg | 1 - docs/CAD/ux/mockups/list__none.svg | 1 - docs/CAD/ux/mockups/list__sk_line.svg | 1 - docs/CAD/ux/mockups/list__sk_none__create.svg | 1 - docs/CAD/ux/mockups/rich__art.svg | 1 - docs/CAD/ux/mockups/rich__art__modify.svg | 1 - docs/CAD/ux/mockups/rich__bodies_2.svg | 1 - .../ux/mockups/rich__bodies_2__reference.svg | 1 - docs/CAD/ux/mockups/rich__body_sheet.svg | 1 - .../ux/mockups/rich__body_sheet__modify.svg | 1 - docs/CAD/ux/mockups/rich__body_solid.svg | 1 - .../ux/mockups/rich__body_solid__dressup.svg | 1 - .../ux/mockups/rich__body_solid__modify.svg | 1 - .../mockups/rich__body_solid__reference.svg | 1 - .../ux/mockups/rich__body_solid__remove.svg | 1 - .../ux/mockups/rich__body_solid__repeat.svg | 1 - docs/CAD/ux/mockups/rich__coordsys.svg | 1 - .../CAD/ux/mockups/rich__coordsys__modify.svg | 1 - docs/CAD/ux/mockups/rich__datum_axis.svg | 1 - .../ux/mockups/rich__datum_axis__modify.svg | 1 - docs/CAD/ux/mockups/rich__datum_plane.svg | 1 - .../ux/mockups/rich__datum_plane__modify.svg | 1 - .../mockups/rich__datum_plane__reference.svg | 1 - .../ux/mockups/rich__datum_plane__remove.svg | 1 - docs/CAD/ux/mockups/rich__edge_circ.svg | 1 - .../ux/mockups/rich__edge_circ__dressup.svg | 1 - docs/CAD/ux/mockups/rich__edge_str.svg | 1 - .../ux/mockups/rich__edge_str__dressup.svg | 1 - .../ux/mockups/rich__edge_str__reference.svg | 1 - docs/CAD/ux/mockups/rich__face_cyl.svg | 1 - .../CAD/ux/mockups/rich__face_cyl__modify.svg | 1 - .../ux/mockups/rich__face_cyl__reference.svg | 1 - docs/CAD/ux/mockups/rich__face_other.svg | 1 - .../ux/mockups/rich__face_other__modify.svg | 1 - docs/CAD/ux/mockups/rich__face_planar.svg | 1 - .../CAD/ux/mockups/rich__face_planar__add.svg | 1 - .../ux/mockups/rich__face_planar__dressup.svg | 1 - .../ux/mockups/rich__face_planar__modify.svg | 1 - .../mockups/rich__face_planar__reference.svg | 1 - .../ux/mockups/rich__face_planar__remove.svg | 1 - .../mockups/rich__face_planar__transform.svg | 1 - docs/CAD/ux/mockups/rich__none.svg | 1 - docs/CAD/ux/mockups/rich__none__reference.svg | 1 - docs/CAD/ux/mockups/rich__sk_2ent.svg | 1 - docs/CAD/ux/mockups/rich__sk_2ent__create.svg | 1 - .../CAD/ux/mockups/rich__sk_2ent__dressup.svg | 1 - docs/CAD/ux/mockups/rich__sk_2ent__modify.svg | 1 - .../ux/mockups/rich__sk_2ent__reference.svg | 1 - docs/CAD/ux/mockups/rich__sk_arc.svg | 1 - docs/CAD/ux/mockups/rich__sk_arc__create.svg | 1 - docs/CAD/ux/mockups/rich__sk_arc__modify.svg | 1 - .../ux/mockups/rich__sk_arc__reference.svg | 1 - docs/CAD/ux/mockups/rich__sk_line.svg | 1 - docs/CAD/ux/mockups/rich__sk_line__create.svg | 1 - .../CAD/ux/mockups/rich__sk_line__dressup.svg | 1 - docs/CAD/ux/mockups/rich__sk_line__modify.svg | 1 - .../ux/mockups/rich__sk_line__reference.svg | 1 - docs/CAD/ux/mockups/rich__sk_loop.svg | 1 - docs/CAD/ux/mockups/rich__sk_loop__add.svg | 1 - docs/CAD/ux/mockups/rich__sk_loop__modify.svg | 1 - docs/CAD/ux/mockups/rich__sk_none.svg | 1 - docs/CAD/ux/mockups/rich__sk_none__create.svg | 1 - .../ux/mockups/rich__sk_none__reference.svg | 1 - docs/CAD/ux/mockups/rich__sk_point.svg | 1 - .../CAD/ux/mockups/rich__sk_point__create.svg | 1 - .../ux/mockups/rich__sk_point__reference.svg | 1 - docs/CAD/ux/mockups/rich__vertex.svg | 1 - .../ux/mockups/rich__vertex__reference.svg | 1 - docs/CAD/ux/offer_atlas.html | 78 - docs/CAD/ux/offer_atlas_inline.html | 77 - docs/HLSD/design-tab.md | 213 +++ scripts/CAD/README.md | 17 +- scripts/CAD/build-gui-incremental.sh | 2 +- scripts/CAD/build-gui.sh | 4 +- .../CAD}/gen_offer_table.py | 52 +- .../CAD/mate-glyph}/bear.step | 0 .../CAD/mate-glyph}/bear_outline.json | 0 .../CAD/mate-glyph}/emit_glyph_table.py | 0 .../CAD/mate-glyph}/extract_outline.py | 0 {docs => scripts}/CAD/rig_build_traps.md | 0 scripts/CAD/run-all-checks.sh | 7 +- scripts/CAD/run-kernel-tests.sh | 2 +- {docs/CAD/ux => scripts/CAD}/tool_atlas.json | 6 +- src/slic3r/GUI/CAD/DesignOffer.hpp | 2 +- src/slic3r/GUI/CAD/DesignPanel.cpp | 4 +- src/slic3r/GUI/CAD/DesignSketchTool.cpp | 4 +- 231 files changed, 281 insertions(+), 10691 deletions(-) delete mode 100644 docs/CAD/GAP_ANALYSIS_vs_ONSHAPE.md delete mode 100644 docs/CAD/cad_dependency_weight.md delete mode 100644 docs/CAD/cad_ux_guidelines.md delete mode 100644 docs/CAD/design/mate-connectors/BEAR_CONNECTOR_REVIEW.md delete mode 100644 docs/CAD/design/mate-connectors/BearConnector_Cutter.step delete mode 100644 docs/CAD/design/mate-connectors/BearConnector_Female.step delete mode 100644 docs/CAD/design/mate-connectors/BearConnector_Female_Trimmed.step delete mode 100644 docs/CAD/design/mate-connectors/BearConnector_Female_Trimmed.stl delete mode 100644 docs/CAD/design/mate-connectors/BearConnector_Female_Trimmed_wall.step delete mode 100644 docs/CAD/design/mate-connectors/BearConnector_Female_Trimmed_wall.stl delete mode 100644 docs/CAD/design/mate-connectors/DESIGN_MATE_CONNECTORS.md delete mode 100644 docs/CAD/design/mate-connectors/bear-01-view.png delete mode 100644 docs/CAD/design/mate-connectors/bear-02-view.png delete mode 100644 docs/CAD/design/mate-connectors/bear-02-zoom.png delete mode 100644 docs/CAD/design/mate-connectors/bear-flat.png delete mode 100644 docs/CAD/design/mate-connectors/bear-sil-cmp.png delete mode 100644 docs/CAD/design/mate-connectors/bear-sil.png delete mode 100644 docs/CAD/design/mate-connectors/bear_glyph_table.h delete mode 100644 docs/CAD/design/mate-connectors/bear_mesh.json delete mode 100644 docs/CAD/design/mate-connectors/circularmateconnectors.png delete mode 100644 docs/CAD/design/mate-connectors/cmp-sheet.png delete mode 100644 docs/CAD/design/mate-connectors/connector-glyph-proposal.html delete mode 100644 docs/CAD/design/mate-connectors/coplanar_test.py delete mode 100644 docs/CAD/design/mate-connectors/cylindricalmateconnectors.png delete mode 100644 docs/CAD/design/mate-connectors/faceted_ridge_key.scad delete mode 100644 docs/CAD/design/mate-connectors/fem-0.png delete mode 100644 docs/CAD/design/mate-connectors/fem-1.png delete mode 100644 docs/CAD/design/mate-connectors/fem-2.png delete mode 100644 docs/CAD/design/mate-connectors/fem-3.png delete mode 100644 docs/CAD/design/mate-connectors/fem-sheet.png delete mode 100644 docs/CAD/design/mate-connectors/fem-sheet2.png delete mode 100644 docs/CAD/design/mate-connectors/female.stl delete mode 100644 docs/CAD/design/mate-connectors/female_only.scad delete mode 100644 docs/CAD/design/mate-connectors/fit_check.py delete mode 100644 docs/CAD/design/mate-connectors/glyph-preview.png delete mode 100644 docs/CAD/design/mate-connectors/glyph_preview.py delete mode 100644 docs/CAD/design/mate-connectors/glyph_probe.py delete mode 100644 docs/CAD/design/mate-connectors/handedness-sheet.png delete mode 100644 docs/CAD/design/mate-connectors/handedness.py delete mode 100644 docs/CAD/design/mate-connectors/linearedgemateconnectors.png delete mode 100644 docs/CAD/design/mate-connectors/make_female.py delete mode 100644 docs/CAD/design/mate-connectors/mate-connector-virtual-sharp.png delete mode 100644 docs/CAD/design/mate-connectors/mateconnector-planarcentroid.png delete mode 100644 docs/CAD/design/mate-connectors/mateconnector-planarpoints.png delete mode 100644 docs/CAD/design/mate-connectors/matepointiconLG.png delete mode 100644 docs/CAD/design/mate-connectors/matepointreorientsecondaryaxis.png delete mode 100644 docs/CAD/design/mate-connectors/muzzle_variants.py delete mode 100644 docs/CAD/design/mate-connectors/planarfacemateconnectors.png delete mode 100644 docs/CAD/design/mate-connectors/relief-sheet.png delete mode 100644 docs/CAD/design/mate-connectors/relief_sheet.py delete mode 100644 docs/CAD/design/mate-connectors/relief_test.py delete mode 100644 docs/CAD/design/mate-connectors/render_key.py delete mode 100644 docs/CAD/design/mate-connectors/render_stl.py delete mode 100644 docs/CAD/design/mate-connectors/rk-0.png delete mode 100644 docs/CAD/design/mate-connectors/rk-1.png delete mode 100644 docs/CAD/design/mate-connectors/rk-2.png delete mode 100644 docs/CAD/design/mate-connectors/rk-3.png delete mode 100644 docs/CAD/design/mate-connectors/rk-4.png delete mode 100644 docs/CAD/design/mate-connectors/rk-back.png delete mode 100644 docs/CAD/design/mate-connectors/rk-front.png delete mode 100644 docs/CAD/design/mate-connectors/rk-iso.png delete mode 100644 docs/CAD/design/mate-connectors/rk-sheet.png delete mode 100644 docs/CAD/design/mate-connectors/simplify-sheet.png delete mode 100644 docs/CAD/design/mate-connectors/simplify_study.py delete mode 100644 docs/CAD/design/mate-connectors/size-test.png delete mode 100644 docs/CAD/design/mate-connectors/sz-22-down+Z.png delete mode 100644 docs/CAD/design/mate-connectors/sz-22-grazing.png delete mode 100644 docs/CAD/design/mate-connectors/sz-22-iso.png delete mode 100644 docs/CAD/design/mate-connectors/sz-32-down+Z.png delete mode 100644 docs/CAD/design/mate-connectors/sz-32-grazing.png delete mode 100644 docs/CAD/design/mate-connectors/sz-32-iso.png delete mode 100644 docs/CAD/design/mate-connectors/sz-48-down+Z.png delete mode 100644 docs/CAD/design/mate-connectors/sz-48-grazing.png delete mode 100644 docs/CAD/design/mate-connectors/sz-48-iso.png delete mode 100644 docs/CAD/design/mate-connectors/t-+0.1-0.png delete mode 100644 docs/CAD/design/mate-connectors/t-+0.1-1.png delete mode 100644 docs/CAD/design/mate-connectors/t-+1.6-0.png delete mode 100644 docs/CAD/design/mate-connectors/t-+1.6-1.png delete mode 100644 docs/CAD/design/mate-connectors/trim-cmp.png delete mode 100644 docs/CAD/design/mate-connectors/trim_female.py delete mode 100644 docs/CAD/design/mate-connectors/verify_trimmed.py delete mode 100644 docs/CAD/design_tab.md delete mode 100644 docs/CAD/design_tab_pr_description.md delete mode 100644 docs/CAD/design_tab_upstream_portability.md delete mode 100644 docs/CAD/ux/interaction-model.md delete mode 100644 docs/CAD/ux/mockups/cmp_12__edge_str.svg delete mode 100644 docs/CAD/ux/mockups/cmp_12__face_planar.svg delete mode 100644 docs/CAD/ux/mockups/cmp_12__sk_line.svg delete mode 100644 docs/CAD/ux/mockups/cmp_8__edge_str.svg delete mode 100644 docs/CAD/ux/mockups/cmp_8__face_planar.svg delete mode 100644 docs/CAD/ux/mockups/cmp_8__sk_line.svg delete mode 100644 docs/CAD/ux/mockups/fresh__art.svg delete mode 100644 docs/CAD/ux/mockups/fresh__art__modify.svg delete mode 100644 docs/CAD/ux/mockups/fresh__bodies_2.svg delete mode 100644 docs/CAD/ux/mockups/fresh__body_sheet.svg delete mode 100644 docs/CAD/ux/mockups/fresh__body_sheet__modify.svg delete mode 100644 docs/CAD/ux/mockups/fresh__body_solid.svg delete mode 100644 docs/CAD/ux/mockups/fresh__body_solid__modify.svg delete mode 100644 docs/CAD/ux/mockups/fresh__coordsys.svg delete mode 100644 docs/CAD/ux/mockups/fresh__coordsys__modify.svg delete mode 100644 docs/CAD/ux/mockups/fresh__datum_axis.svg delete mode 100644 docs/CAD/ux/mockups/fresh__datum_axis__modify.svg delete mode 100644 docs/CAD/ux/mockups/fresh__datum_plane.svg delete mode 100644 docs/CAD/ux/mockups/fresh__datum_plane__modify.svg delete mode 100644 docs/CAD/ux/mockups/fresh__datum_plane__reference.svg delete mode 100644 docs/CAD/ux/mockups/fresh__edge_circ.svg delete mode 100644 docs/CAD/ux/mockups/fresh__edge_str.svg delete mode 100644 docs/CAD/ux/mockups/fresh__edge_str__reference.svg delete mode 100644 docs/CAD/ux/mockups/fresh__face_cyl.svg delete mode 100644 docs/CAD/ux/mockups/fresh__face_cyl__reference.svg delete mode 100644 docs/CAD/ux/mockups/fresh__face_other.svg delete mode 100644 docs/CAD/ux/mockups/fresh__face_planar.svg delete mode 100644 docs/CAD/ux/mockups/fresh__face_planar__reference.svg delete mode 100644 docs/CAD/ux/mockups/fresh__none.svg delete mode 100644 docs/CAD/ux/mockups/fresh__none__reference.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_2ent.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_2ent__create.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_2ent__dressup.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_2ent__modify.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_2ent__reference.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_arc.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_arc__create.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_arc__modify.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_arc__reference.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_line.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_line__create.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_line__dressup.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_line__modify.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_line__reference.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_loop.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_loop__add.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_loop__modify.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_none.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_none__create.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_none__reference.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_point.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_point__create.svg delete mode 100644 docs/CAD/ux/mockups/fresh__sk_point__reference.svg delete mode 100644 docs/CAD/ux/mockups/fresh__vertex.svg delete mode 100644 docs/CAD/ux/mockups/fresh__vertex__reference.svg delete mode 100644 docs/CAD/ux/mockups/gen_offer_mockups.py delete mode 100644 docs/CAD/ux/mockups/list__body_solid.svg delete mode 100644 docs/CAD/ux/mockups/list__edge_str.svg delete mode 100644 docs/CAD/ux/mockups/list__face_planar.svg delete mode 100644 docs/CAD/ux/mockups/list__face_planar__add.svg delete mode 100644 docs/CAD/ux/mockups/list__none.svg delete mode 100644 docs/CAD/ux/mockups/list__sk_line.svg delete mode 100644 docs/CAD/ux/mockups/list__sk_none__create.svg delete mode 100644 docs/CAD/ux/mockups/rich__art.svg delete mode 100644 docs/CAD/ux/mockups/rich__art__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__bodies_2.svg delete mode 100644 docs/CAD/ux/mockups/rich__bodies_2__reference.svg delete mode 100644 docs/CAD/ux/mockups/rich__body_sheet.svg delete mode 100644 docs/CAD/ux/mockups/rich__body_sheet__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__body_solid.svg delete mode 100644 docs/CAD/ux/mockups/rich__body_solid__dressup.svg delete mode 100644 docs/CAD/ux/mockups/rich__body_solid__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__body_solid__reference.svg delete mode 100644 docs/CAD/ux/mockups/rich__body_solid__remove.svg delete mode 100644 docs/CAD/ux/mockups/rich__body_solid__repeat.svg delete mode 100644 docs/CAD/ux/mockups/rich__coordsys.svg delete mode 100644 docs/CAD/ux/mockups/rich__coordsys__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__datum_axis.svg delete mode 100644 docs/CAD/ux/mockups/rich__datum_axis__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__datum_plane.svg delete mode 100644 docs/CAD/ux/mockups/rich__datum_plane__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__datum_plane__reference.svg delete mode 100644 docs/CAD/ux/mockups/rich__datum_plane__remove.svg delete mode 100644 docs/CAD/ux/mockups/rich__edge_circ.svg delete mode 100644 docs/CAD/ux/mockups/rich__edge_circ__dressup.svg delete mode 100644 docs/CAD/ux/mockups/rich__edge_str.svg delete mode 100644 docs/CAD/ux/mockups/rich__edge_str__dressup.svg delete mode 100644 docs/CAD/ux/mockups/rich__edge_str__reference.svg delete mode 100644 docs/CAD/ux/mockups/rich__face_cyl.svg delete mode 100644 docs/CAD/ux/mockups/rich__face_cyl__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__face_cyl__reference.svg delete mode 100644 docs/CAD/ux/mockups/rich__face_other.svg delete mode 100644 docs/CAD/ux/mockups/rich__face_other__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__face_planar.svg delete mode 100644 docs/CAD/ux/mockups/rich__face_planar__add.svg delete mode 100644 docs/CAD/ux/mockups/rich__face_planar__dressup.svg delete mode 100644 docs/CAD/ux/mockups/rich__face_planar__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__face_planar__reference.svg delete mode 100644 docs/CAD/ux/mockups/rich__face_planar__remove.svg delete mode 100644 docs/CAD/ux/mockups/rich__face_planar__transform.svg delete mode 100644 docs/CAD/ux/mockups/rich__none.svg delete mode 100644 docs/CAD/ux/mockups/rich__none__reference.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_2ent.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_2ent__create.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_2ent__dressup.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_2ent__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_2ent__reference.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_arc.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_arc__create.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_arc__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_arc__reference.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_line.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_line__create.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_line__dressup.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_line__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_line__reference.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_loop.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_loop__add.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_loop__modify.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_none.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_none__create.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_none__reference.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_point.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_point__create.svg delete mode 100644 docs/CAD/ux/mockups/rich__sk_point__reference.svg delete mode 100644 docs/CAD/ux/mockups/rich__vertex.svg delete mode 100644 docs/CAD/ux/mockups/rich__vertex__reference.svg delete mode 100644 docs/CAD/ux/offer_atlas.html delete mode 100644 docs/CAD/ux/offer_atlas_inline.html create mode 100644 docs/HLSD/design-tab.md rename {docs/CAD/ux/mockups => scripts/CAD}/gen_offer_table.py (80%) rename {docs/CAD/design/mate-connectors => scripts/CAD/mate-glyph}/bear.step (100%) rename {docs/CAD/design/mate-connectors => scripts/CAD/mate-glyph}/bear_outline.json (100%) rename {docs/CAD/design/mate-connectors => scripts/CAD/mate-glyph}/emit_glyph_table.py (100%) rename {docs/CAD/design/mate-connectors => scripts/CAD/mate-glyph}/extract_outline.py (100%) rename {docs => scripts}/CAD/rig_build_traps.md (100%) rename {docs/CAD/ux => scripts/CAD}/tool_atlas.json (99%) diff --git a/deps/OCCT/OCCT.cmake b/deps/OCCT/OCCT.cmake index 45263d6ae3..e1a65beef7 100644 --- a/deps/OCCT/OCCT.cmake +++ b/deps/OCCT/OCCT.cmake @@ -24,7 +24,7 @@ endif() # On macOS/Linux OCCT links statically, so an unreferenced toolkit costs build time and no # shipped bytes. The Windows figure is a real DLL cost and has NOT been measured -- an # earlier "3.77 MiB, Windows only" note here covered only two of the three toolkits and is -# not a number to quote. See docs/cad_dependency_weight.md. +# not a number to quote. See docs/HLSD/design-tab.md. if (IN_GIT_REPO) set(OCCT_DIRECTORY_FLAG --directory ${BINARY_DIR_REL}/dep_OCCT-prefix/src/dep_OCCT) diff --git a/docs/CAD/GAP_ANALYSIS_vs_ONSHAPE.md b/docs/CAD/GAP_ANALYSIS_vs_ONSHAPE.md deleted file mode 100644 index bf603cf2c1..0000000000 --- a/docs/CAD/GAP_ANALYSIS_vs_ONSHAPE.md +++ /dev/null @@ -1,160 +0,0 @@ -# Orca-CAD vs Onshape — capability gap analysis - -Generated 2026-07-22 by enumerating the source, not from recollection: -`CadFeatureType` and `add_*` in `src/libslic3r/CAD/CadDocument.hpp`, `Tool` in -`src/slic3r/GUI/CAD/DesignPanel.hpp`, `Mode` in `src/slic3r/GUI/CAD/DesignSketchTool.hpp`, -`SketchConstraintType` + `SketchEntity::Type` in `src/libslic3r/CAD/SketchEngine.hpp`, -and the JSON-RPC dispatch in `src/slic3r/GUI/CAD/McpControl.cpp`. - -**Scope note.** Onshape is a cloud PLM platform; Orca is a Design tab inside a -slicer. A large share of Onshape's surface (release management, branching, real-time -collaboration, FEA, rendering, PDM) is out of scope by construction and is listed -separately at the bottom rather than counted as a "missing tool". - ---- - -## 1. What Orca already has - -### 2D sketcher — near parity with Onshape -This is the strongest area. Very little is missing. - -| Category | Orca | -|---|---| -| Entities | Line, Polyline, Arc (3-point / tangent / center), Circle (center / 2-point / 3-point), Point, Ellipse, Elliptical arc, B-spline | -| Shapes | Rectangle (corner / center / oblique / rounded), Slot, Arc-slot, Polygon | -| Edit ops | Fillet, Chamfer, Offset, Mirror, Trim, Extend | -| Transforms | Move, Rotate, Scale, Linear array, Polar array | -| Constraints (19) | Fix, Coincident, Horizontal, Vertical, Distance, LockX, LockY, EqualLength, Parallel, Perpendicular, Concentric, Tangent, Midpoint, Symmetric, Angle, Radius, Diameter, PointOnLine, PointOnObject | -| Dimensions | Length, Diameter, Radius, Angle, Distance, Distance-to-line | - -Solver: vendored SolveSpace (`libslvs`, GPL-3.0) — the same solver lineage as a -commercial-grade sketcher. - -### Part features - -| Present | Notes | -|---|---| -| Extrude | + up-to-face / up-to-point, taper, flip | -| Revolve | angle-arc gizmo | -| Sweep | along a path | -| Loft | multi-profile | -| Fillet / Chamfer | edge-level | -| Draft | face taper | -| Shell | wall thickness + open face | -| Hole / Thread | face-aware placement | -| Pattern | linear + circular | -| Boolean | New / Add / Cut / Intersect, with face-mating | -| Cut | plane-based, signed offset | -| Datum plane | offset / 2-face / 2-edge derived | -| Import | STEP (B-rep) + mesh→B-rep (native mesh2step port) | -| Export | STEP (native B-rep, not tessellated) | -| Multi-body | + per-body colour | -| Section view | with flip | -| Undo/redo | full feature-tree recompute | -| 3MF persistence | parametric recipe survives save/load | - -### Automation -9 MCP JSON-RPC methods: `describe_tools`, `describe_scene`, `query_topology`, -`measure`, `slice_body`, `import_step`, `import_mesh`, `validate_against`, plus -build actions `extrude`, `revolve`, `fillet`, `chamfer`, `hole`, `boolean`, `pattern`. -Onshape's equivalent is its REST API + FeatureScript. - ---- - -## 2. Missing tools — ranked by impact - -### Tier 1 — structural absences (whole subsystems) - -**1. Assemblies and mates.** Entirely absent. No assembly document, no mate -connectors, no fastened / revolute / slider / cylindrical / planar / ball / pin-slot -mates, no assembly patterns, no interference detection, no exploded views. -`bool_target_face` / `bool_tool_face` do face-to-face *mating* for a boolean, which -is geometric alignment, not a kinematic joint. -*Impact:* multi-part products cannot be positioned or validated as a mechanism. -*Note:* an MCP-side `align_instance_to_face` / `create_*_mate` vocabulary already -exists on the Onshape bridge in this workspace, so the target semantics are known. - -**2. Drawings / 2D documentation.** Absent. No drawing sheets, dimensioned views, -section/detail views, GD&T, title blocks, or BOM. -*Impact:* nothing manufacturable-by-a-third-party leaves the tool. For 3D printing -this matters less than for machining, which is the honest reason it is Tier 1 by -CAD convention but arguably Tier 3 for this product. - -**3. Variables, equations, configurations.** Absent — no `add_variable`, no -expression evaluation, no configuration table. Every dimension is a literal double. -*Impact:* this is the biggest *parametric* gap. "Make this bracket for an M4 vs M5 -bolt" requires re-editing every dependent feature by hand. Onshape's Variable -Studio + configurations are a core differentiator, and this is the cheapest Tier 1 -item to close for the size of the payoff. - -**4. Surface modelling.** Absent. No surface extrude/revolve/loft/sweep, no fill, -knit, trim/extend surface, offset surface, or thicken. Orca is solid-only. -*Impact:* organic/complex shapes and repair of imported junk geometry are impossible. -OCCT already provides all of it (`TKOffset`, `TKBRep`), so the kernel is not the -blocker — only UI and feature plumbing. - -**5. Sheet metal.** Absent. No flange, bend, tab, relief, or flat-pattern unfold. -*Impact:* arguably out of scope for an FDM slicer; listed for completeness. - -### Tier 2 — individual features with clear demand - -| Missing | Why it matters | Cheap? | -|---|---|---| -| **Mirror body** (part-level) | Sketch mirror exists; mirroring a *solid* about a plane does not. Extremely common. | Yes — OCCT `gp_Trsf` mirror + fuse | -| **Helix / spiral curve** | No helix ⇒ no springs, no custom threads, no spiral vase geometry. Sweep exists but has no helical path to sweep along. | Yes | -| **Move / rotate body as a real feature** | `m_body_xform` exists but is **display-only** (memory #1655) — it never enters the B-rep. Export/boolean see the original position. | Medium | -| **Split body** | Cut removes material; splitting one body into two independently-usable bodies is absent. Very relevant for print-in-parts. | Medium | -| **Thicken** | Solid from a surface/face offset. | Needs surfaces | -| **Rib** | Standard structural feature. | Medium | -| **Delete face / move face / replace face** | Direct/dumb-solid editing — the main tool for fixing imported STEP. Given Orca imports STEP *and* meshes, its absence is felt. | Medium | -| **Datum axis, coordinate system** | Only datum *planes* exist. Axes are needed for revolve/pattern references. | Yes | -| **Mass properties** | `GeometryEngine` computes a volume internally, but there is no volume/mass/COM/inertia readout. For print cost/time estimation this is nearly free to expose. | Yes — trivial | -| **Measure tool in the GUI** | `measure` exists over MCP but there is no interactive measure in the UI. | Yes | -| **Hole standards library** | Hole exists, but no counterbore/countersink/tapped standards (ISO/ANSI) with callouts. | Medium | -| **Project / convert edges into a sketch** | Cannot reference existing solid edges as sketch geometry ("Use" in SolidWorks). A significant sketcher gap given everything else is present. | Medium | -| **Construction geometry** | Could not confirm a construction/reference-line flag on sketch entities. | Yes if absent | -| **Curve tools** | Projected curve, bridging curve, composite curve, 3D fit spline. | Medium | -| **Pattern on curve / pattern faces** | Pattern is linear + circular of whole bodies only; no curve-driven pattern, no feature/face pattern. | Medium | -| **Wrap / emboss** | Text or sketch wrapped onto a curved face. | Hard | -| **Enclose** | Solid from bounded void regions. | Medium | - -### Tier 3 — platform capabilities (out of scope by construction) - -Version control with branching/merging, release management, real-time multi-user -collaboration, cloud PDM, FeatureScript custom-feature authoring, simulation/FEA, -photorealistic rendering, app store/integrations. These are Onshape-the-platform, -not Onshape-the-modeller. Not defects in Orca. - ---- - -## 3. Recommended priority - -If the goal is "credible parametric CAD inside a slicer", the ordering that buys -the most capability per unit of work: - -1. **Variables + expressions** — unlocks genuine parametric reuse; no new kernel work. -2. **Mass properties + GUI measure** — nearly free, immediately useful for printing. -3. **Mirror body, datum axis, helix** — small, self-contained, high-frequency features. -4. **Promote move/rotate body from display-only to a real B-rep feature** — closes a - correctness gap, not just a missing tool (exports currently disagree with the view). -5. **Split body** — high value for print-in-parts workflows. -6. **Project edges into sketch** — the sketcher's most conspicuous hole. -7. **Surface modelling** — large, but OCCT already ships the algorithms. -8. **Assemblies** — largest effort; only worth it if Orca targets multi-part products. - -Deliberately last: drawings and sheet metal — high cost, low relevance to an -FDM-oriented tool. - ---- - -## 4. Honest summary - -Orca's **sketcher is at or near Onshape parity**, and its **solid feature set -covers the mainstream modelling path** (sketch → extrude/revolve/sweep/loft → -dress-up → boolean/pattern). What is absent is *breadth*: assemblies, surfaces, -sheet metal, drawings, and — most importantly for a tool calling itself parametric — -**variables and configurations**. - -The single most defensible criticism is #3: without variables, the feature tree is -parametric in *structure* but not in *value*, so the promise of "change one number -and the model updates" is only half delivered. diff --git a/docs/CAD/cad_dependency_weight.md b/docs/CAD/cad_dependency_weight.md deleted file mode 100644 index 0b64ac8097..0000000000 --- a/docs/CAD/cad_dependency_weight.md +++ /dev/null @@ -1,136 +0,0 @@ -# Dependency weight of the Design/CAD subsystem - -What the Design tab actually costs a maintainer who merges it. Written to be checkable: -every number below is reproducible with the command that produced it, and the places where -a number is still missing say so instead of guessing. - -Measured on Linux x86_64, OCCT V7_6_0, in the `snapmaker-deps` build image. - -## Summary - -| | Cost | -|---|---| -| New third-party dependencies | **none** | -| OCCT build flag | `BUILD_MODULE_ModelingAlgorithms=ON` | -| Extra OCCT toolkits *built* | 3 (TKFillet, TKOffset, TKFeat) | -| Extra OCCT toolkits *linked* | 2 (TKFillet, TKOffset) | -| Vendored code | `src/libslic3r/slvs`, 9,339 lines, 380 KiB, GPLv3 | -| Own object code | 6.79 MiB unstripped `.o` (7.13 MiB with the solver) | - -OCCT is **already** an upstream dependency — Orca uses it for STEP import. The Design tab -does not add a library; it turns on one more OCCT module. - -## The OCCT module flag - -`deps/OCCT/OCCT.cmake` gates the module on `SLIC3R_CAD`: - -```cmake --DBUILD_MODULE_ModelingAlgorithms=${SLIC3R_CAD} # was hard-coded OFF -``` - -With `SLIC3R_CAD=OFF` the deps prefix matches upstream exactly. - -`ModelingAlgorithms` contains 12 toolkits, but **most were already being built**, because -`DataExchange` — the STEP path upstream already ships — depends on them. The honest delta is -only the toolkits that DataExchange's dependency closure does *not* reach: - -``` -ModelingAlgorithms = TKGeomAlgo TKTopAlgo TKPrim TKBO TKBool TKHLR - TKFillet TKOffset TKFeat TKMesh TKXMesh TKShHealing - -already required by DataExchange: TKBO TKBool TKGeomAlgo TKHLR TKMesh - TKPrim TKShHealing TKTopAlgo -true delta: TKFeat TKFillet TKOffset TKXMesh -``` - -Reproduce by walking `adm/MODULES` and each toolkit's `src//EXTERNLIB` in the OCCT -source tree. - -### Sizes of the delta toolkits - -Static archives in the deps prefix. These are *build artifacts*, not shipped bytes — a -static link pulls in only the objects it references: - -| Toolkit | Archive | Referenced by the Design tab? | -|---|---|---| -| TKFillet | 7.40 MiB | yes — `BRepFilletAPI` | -| TKOffset | 5.38 MiB | yes — `BRepOffsetAPI`, `BRepOffset_` | -| TKFeat | 4.42 MiB | **no** | -| TKXMesh | — | not produced at all | - -TKFeat is worth calling out: nothing in the Design tab references it, and it is absent from -the `TKFillet`/`TKOffset` dependency closure, so it is built for nothing. OCCT's module flag -is all-or-nothing per module, which is why it comes along. It costs build time and zero -shipped bytes on any platform that links OCCT statically. - -**A correction to the record.** The comment in `deps/OCCT/OCCT.cmake` and the earlier -summary both said the delta was "TKFillet + TKOffset — 3.77 MiB, Windows only". The toolkit -list was incomplete: TKFeat is built too. The 3.77 MiB figure covers 2 of the 3 built -toolkits and has not been re-derived here — see the gap below. - -## What is not measured yet - -Two numbers a maintainer may reasonably ask for are **not** in this document, because -producing them honestly needs a build this machine cannot do: - -1. **Windows DLL delta.** OCCT builds shared on Windows, so the shipped cost there is real - DLL bytes rather than linker-selected objects. That needs a Windows build to size — - tracked as the cross-platform build proof (`gix`). -2. **Clean-build time delta.** Measuring it means building the deps prefix twice, with the - flag ON and OFF, on the same machine. The incremental figures from day-to-day work do not - answer the question and are not offered as if they did. - -Do not quote a number for either until it has been measured. - -## Vendored solver - -`src/libslic3r/slvs` — the 2D sketch constraint solver extracted from SolveSpace. - -- 19 files: 8 `.cpp`, 11 `.h`, plus `LICENSE` -- 9,339 lines, 380 KiB of source, 0.34 MiB of object code -- **GPLv3**, `LICENSE` preserved verbatim in the vendored directory - -The fork is **AGPLv3**. GPLv3 code combines into an AGPLv3 work without difficulty: AGPLv3 -§13 provides explicit compatibility in that direction. No licence question to resolve. - -It is live code, not a carried corpse — `SketchSolver.cpp` is its only consumer and drives -every sketch constraint in the Design tab. - -## Own code - -Object sizes from the release build (unstripped, so these include debug information and -overstate the shipped contribution): - -| Object | Size | -|---|---| -| DesignPanel.o | 2.22 MiB | -| McpControl.o | 1.69 MiB | -| DesignSketchTool.o | 0.88 MiB | -| CadDocument.o | 0.76 MiB | -| SketchEngine.o | 0.40 MiB | -| DesignCanvas.o | 0.37 MiB | -| GeometryEngine.o | 0.32 MiB | -| SketchSolver.o | 0.15 MiB | -| slvs (all objects) | 0.34 MiB | -| **total** | **7.13 MiB** | - -For scale, the linked binary is 137.1 MiB. - -## Reproducing - -```bash -# toolkit membership and dependency closure -R= -cat $R/adm/MODULES # module -> toolkits -cat $R/src//EXTERNLIB # toolkit -> its dependencies - -# archive sizes -ls -l /lib/libTK{Fillet,Offset,Feat}.a - -# what the Design tab actually references -grep -rE 'BRepFilletAPI|BRepOffsetAPI|BRepOffset_|BRepFeat' src/libslic3r/ - -# vendored solver -wc -l src/libslic3r/slvs/*.cpp src/libslic3r/slvs/**/*.h -head -3 src/libslic3r/slvs/LICENSE -``` diff --git a/docs/CAD/cad_ux_guidelines.md b/docs/CAD/cad_ux_guidelines.md deleted file mode 100644 index d0290a1d3d..0000000000 --- a/docs/CAD/cad_ux_guidelines.md +++ /dev/null @@ -1,704 +0,0 @@ -# Orca-CAD — UX guidelines and design charter - -Status: proposed, v1. Owner: design working group. Applies to the Design tab — -the parametric CAD environment inside OrcaSlicer. - -This document is a **review instrument**, not an essay. Sections 3–9 are written -so that a reviewer can hold a pull request against them and get a yes or a no. -If a rule here cannot be failed, it is badly written and should be rewritten. - ---- - -## 1. Why this exists - -A CAD tool acquires its interface by accretion. Every feature arrives needing -"just one more field", the side panel is the cheapest place to put it, and after -forty features the product is FreeCAD: complete, respected, and abandoned by -almost everyone who opens it once. That end state is not a failure of any single -decision. It is the sum of forty locally reasonable ones taken without a written -rule to violate. - -So we write the rule down first, and we make additions argue against it. - -## 2. Product thesis - -**Orca-CAD is a modelling space for people who want a part, inside the tool that -prints it.** - -Three audiences, one interface: - -- **The fourteen-year-old on a school laptop.** Free software, on the machine - they already have, with no account, no subscription, no licence and no - tutorial. They open the tab because they want a bracket for a bike light, and - an hour later it is printing. This is not the charity case at the bottom of - the list — it is the reason the project is worth doing. A CAD tool that only - the equipped can run is a tool for people who were already going to design - something; this one has to be a creative instrument in the hands of someone - who did not yet know they could make things. Everything in §6.1 exists to - keep that door open, and nothing gets to close it for the convenience of the - other two audiences. -- **The maker** who has an idea and a printer, and who has bounced off FreeCAD. - They should be modelling something real within ten minutes of first opening - the tab, without a tutorial, without knowing the word "constraint". -- **The mechanical designer** who needs assemblies, mates, exploded views, - variables, and a feature history they can edit six months later. They should - not have to leave for SolidWorks the moment the work gets serious. - -The order matters. When a decision helps one audience and hurts another, the -earlier one wins unless there is a written argument for why not. - -The reference for *how it feels* is Shapr3D: direct, gestural, quiet, almost no -chrome, depth revealed by what you touch rather than by what is on screen. The -anti-references are Blender (a modal keyboard language you must learn before the -first success) and FreeCAD (a workbench-and-dialog architecture where the -geometry is a preview of a form you fill in elsewhere). - -We are not cloning Shapr3D's feature set. We are adopting its *interaction -economy*: the smallest number of visible controls that still makes an expert -fast. - -**And one thing neither reference has:** Orca-CAD lives inside a slicer. The -plate, the nozzle, the material and the print constraints are known to the -application at design time. Designing for print is not a plugin here, it is the -home advantage. Where a rule below trades generality for print-awareness, it -trades in favour of print-awareness. - -## 3. The laws - -Non-negotiable. A change that breaks one of these does not get merged on the -grounds that it was easier, that the alternative is more work, or that another -CAD does it that way. Each law carries a test — the question a reviewer asks. - -### L1 — Geometry first: you point, then you act - -Controls live **on the geometry**: handles, arrows, points, small circles and -boxes, with an inline label tab for typed values. Not in a side panel of combos -and spin fields. - -The canonical gesture: **select a face or plane in the viewport, then click the -sketch tool.** Never: click the sketch tool, then choose a plane from a list. -The tool consumes what you pointed at — and, better still, the thing you pointed -at offers the tool itself (§4). - -> **Test.** Can the operation be performed start to finish without the pointer -> leaving the viewport, except to press the tool itself? If a control had to be -> added to a panel to make it work, the design is not finished. - -This is the law the others serve. It was stated after two proposals in a row -reached for a dropdown, and the failure mode it names is real and recurrent: a -fix that "adds a row to the plane combo" is the side-panel pattern wearing a -different hat. - -### L2 — Everything draggable is typable, and everything typable is draggable - -Any value produced by direct manipulation (a fillet radius, an extrude depth, a -pattern spacing, a plane offset) shows a live label on the geometry, and that -label is an editable field. Any value entered numerically has a corresponding -handle in the viewport. - -Dragging is for finding the answer. Typing is for committing to it. A tool that -offers only one of the two is half a tool. - -> **Test.** Point at the number the tool produces. Can you drag it? Can you -> click it and type? Both must be yes. - -### L3 — Noun then verb, always the same way round - -Selection precedes action, without exception, across sketch tools, features, -dress-up, booleans and mates. There is no tool in the product that is armed -first and asks for its input afterwards. - -> **Test.** Does this tool work if the user has already selected the thing they -> want it applied to? Does it work *only* that way? - -### L4 — No modal dialog in the modelling loop - -Dialogs belong to document-level actions: open, save, import, export, preferences. -Modelling never opens one. A feature that needs three values gets three labels on -the geometry, not a form; a feature that needs confirming gets a ghost preview and -a confirm/cancel puck in the scene beside it (§4.2) — an object, not a window: the -camera still orbits, the values are still editable, nothing is blocked. - -> **Test.** Between starting an operation and seeing its result, does a window -> appear that must be dismissed? If yes, redesign. - -### L5 — One click, one visible change - -Every click either changes what is on screen or tells the user why it did not. -A click that opens something invisible, arms an invisible state, or requires a -second identical click to have any effect is a defect, not a design. - -This law exists because we shipped its violation twice. Sketch-tool family -buttons were flyouts whose first click only rendered a pressed state — three -separate sessions filed bugs against tools that were working. Solid picking used -a click *cycle* (first click selects the body, second refines to the face), so -sketching on a face appeared broken to anyone who clicked a face once, the way -every human does. - -> **Test.** Perform the gesture exactly once, as a first-time user would. Take a -> screenshot. Is the state visibly different, and is the difference the one the -> user intended? - -### L6 — The default is the answer four times out of five - -Every option that has a default must have the *common* answer as its default, -measured against real parts, not against generality. "New body" as the default -result of an extrude is wrong: most extrudes join. Radius as the input for a -circle is wrong: drawings give diameter. - -> **Test.** Take ten real parts. In how many is the default correct? Below eight, -> change the default or infer it from context. - -### L7 — Errors are caught before the commit, in the user's words - -A self-intersecting profile, a cut that removes no material, a wall thinner than -the nozzle: these are reported at the moment they become knowable, on the -geometry that is wrong, phrased as what happened and what to do — not as a kernel -exception after the fact, and never silently. - -> **Test.** Is the failure detectable before the user commits? Then it must be -> reported before the user commits. Read the message aloud: does it name a thing -> the user can see and an action they can take? - -### L8 — The camera is the application's job - -Selecting a sketch plane orients the view to it. Committing a feature does not -throw the camera away. Zoom-to-fit exists and is one keystroke. The user is never -required to fight the view in order to reach the geometry, and orbit is bound to -the gesture people actually try. - -> **Test.** Count camera manipulations in a representative modelling session. -> Any camera action the application could have performed for the user is a bug. - -### L9 — Accessible by construction, not by retrofit - -The floor, applied to every new interaction (details in §6.2): full keyboard -reach, no meaning carried by colour alone, hit targets that survive a shaky hand -and a HiDPI screen, legible labels over an arbitrary 3D background, no gesture -that depends on timing. - -> **Test.** Drive the whole interaction from the keyboard. Then drive it in -> greyscale. Both must work. - -### L10 — Vocabulary from the drawing office - -Names come from the language of people who make parts: fillet, chamfer, boss, -rib, counterbore, mate, exploded view. Not from the kernel (no "boolean -subtract", no "B-rep"), not from invented product-speak. Where the drawing-office -word and the beginner's word differ, use the drawing-office word and make the -tooltip teach it — an approachable tool that leaves the user unable to talk to a -machinist has failed them. - -> **Test.** Would a shop-floor engineer recognise this word? Would a first-time -> user be able to look it up and find a real definition? - -### L11 — The floor is a school laptop, and nothing is behind a door - -The product runs, completely, on a low-end laptop with integrated graphics and a -small screen, offline, with no account, no subscription and no feature withheld. -No capability in this document is reserved for a paid tier, a cloud service, a -plugin, or a machine with a discrete GPU — there is one product and everybody -gets all of it. - -> **Test.** On the reference low-end machine (§6.1), at 1366×768, with the -> network cable pulled and no account ever created: does this feature work, and -> is it usable at an honest frame rate? Any "no" is a defect, not a limitation. - -## 4. Interaction grammar — object-driven - -The rules above compose into one sentence the whole product obeys: - -> **Point at geometry → the geometry offers what can be done to it → choose the -> tool → manipulate handles and type exact values → confirm or cancel.** - -The selection does not merely feed the tool. **The selection determines which -tools exist.** Pick a planar face and the product shows you the small set of -things a planar face can become — sketch on it, extrude it, hole it, shell it, -put a datum on it. Pick an edge and that set is fillet, chamfer, and the sketch -tools that can use it as a reference. Nothing else is offered, because nothing -else is possible. - -This is the single largest thing we can do for a first-time user, and it is -worth stating as the reason: a beginner's difficulty is not operating a tool, -it is **not knowing which tools apply to what they are looking at**. A palette -of sixty icons answers a question they cannot yet ask. A face that offers its -own five verbs teaches the model of the product by using it. It also removes an -entire class of failure — a tool that silently does nothing because the -selection was wrong can no longer be reached. - -### 4.1 The offer, and the one thing that makes it work - -The flow, in full: - -> **left-click the geometry to select it → right-click to open the offer → a -> vertical list, always in the same order, each row an icon, a name and its -> keyboard shortcut → click.** - -- **Selecting and acting are separate gestures.** Left-click only ever selects, - so pointing at things is quiet — nothing pops up while you look around. - Right-click on the selection opens the offer, at the pointer, over the - geometry it acts on. -- **Order is fixed and it is the whole point.** A verb occupies one permanent - row, and that row is the same in every selection where the verb appears. - Dress-up is the fourth row on an edge, on a face, on a body, on the day the - product ships and two years later. The hand learns the position; the eye stops - being needed. -- **What does not apply is DISABLED IN PLACE, never removed.** This is the - single strongest thing the list does, and it is why it beat the radial we - drew first: a greyed row still carries its name *and the reason it is grey* — - "Create a sketch, or pick a solid face, first", "Create a solid body to - pattern first" — in the words the product already ships. On a first-run - document the offer is therefore not a mostly-empty control but a map of what - the product does and what you have to do first. -- **It is an accelerator, not a toll gate.** The toolbar and the single-letter - shortcuts keep working exactly as they do now, and pressing a tool directly - consumes the same selection (L3). An expert never has to open the offer; a - beginner never has to know the toolbar exists. Both routes land in the same - place — this is the only way one interface serves §2's three audiences. -- **Every row shows its keyboard shortcut**, right-aligned so the keys stack - into a column the eye learns without trying, beside the icon and the - drawing-office word (L10). This is deliberate: the offer is the path by which - a user stops needing the offer. You reach for fillet in its row, the row says - "F", and one day your hand types F before the menu has finished opening. A - menu that teaches its own shortcut is how a beginner becomes the power user - who never opens it — the same interface at two speeds, with no "advanced mode" - between them (§7). -- **A family with more than one applicable verb opens a submenu** to the side, - in its own fixed order. A family with exactly one shows that verb directly, so - the common path is never one click longer than it needs to be. -- **It never blocks the view of what it acts on**: it opens beside the pick, - never over it, with a thin leader back to the point it belongs to, and it - dismisses the moment the selection changes. -- **The header names what is selected** ("Top face · Body 1"), because a user - who mis-picked should find that out before choosing a verb, not after. - -#### Opening the offer on every machine - -Right-click is the primary gesture and every platform must have a first-class -equivalent — this is a reach requirement (L11), not a nicety: - -| Input | Gesture | -|---|---| -| Two-button mouse | right-click | -| Trackpad | two-finger tap (the OS-standard secondary click) | -| macOS, one-button mouse | **long-press**, and Ctrl-click, which is the platform convention | -| Keyboard | the Menu key, or Shift+F10, on the current selection | -| Touch / pen | long-press | - -The long-press is an **additional** route, never the only one — §6.2 forbids -press-and-hold as a sole path to a function, and it stays forbidden. Every -opening gesture is reachable at least two ways on every platform, and the -keyboard route exists everywhere. A long-press must show that it is charging -(a growing ring under the finger) so a user who holds too briefly learns why -nothing happened rather than concluding the product is broken (L5). - -#### The row-constancy invariant - -This is the rule that has to survive every future feature, so it is written as -an invariant rather than as advice: - -> **Every verb has exactly one row index in the offer. That index is identical -> for every selection type in which the verb appears. Verbs that do not apply to -> the current selection are DISABLED IN PLACE, with their reason — the offer is -> never compacted, re-sorted or re-ordered. Adding a verb never changes the -> index of an existing one.** - -Two consequences the group must accept together with the invariant: - -- **No adaptive ordering. Ever.** Not most-used-first, not recently-used-first, - not per-selection frequency. An offer that rearranges itself to be helpful - destroys the only thing that made it fast, and it does so precisely for the - user who has just started to learn it. (Office 2000's adaptive menus are the - textbook case; they were removed.) -- **Greyed rows are the price, and they are cheap.** A compacted menu is shorter - and unlearnable. A constant one is a few rows longer, teaches while it waits, - and is memorised in a week. - -#### The map — RATIFIED 2026-07-31 - -The invariant is not negotiable, and as of 2026-07-31 neither is the assignment: -the row order below is **ratified**. It was argued once; it is not argued again. -Changing an index from here on is a breaking change to every user's muscle -memory and needs the group, not a pull request (§9 q12). - -Eight families, ordered so the sequence itself has a logic: material is created, -grows, is taken away, is refined, is repeated, is moved, is referred to, is -edited. - -| Row | Family | On a face | On an edge | On a body | On text/art | -|---|---|---|---|---|---| -| **1** | Create | Sketch on it | — | — | Edit text | -| **2** | Add material | Extrude, thicken | — | Combine, thicken | Extrude | -| **3** | Remove | Hole, shell | Thread | Shell, cut, split | — | -| **4** | Dress-up | Draft | Fillet, chamfer | Fillet, chamfer | — | -| **5** | Repeat | Pattern | Pattern along it | Pattern, mirror | Pattern | -| **6** | Transform | Align to, mate | — | Move, mate | Move, size | -| **7** | Reference | Plane, axis, measure | Axis, measure | Project, measure, mass | — | -| **8** | Modify | Delete face, edit | — | Edit, colour, delete | Replace art | - -A dash means the row is drawn greyed for that selection, with its reason. - -The authoritative version of this table is **`docs/ux/tool_atlas.json`**, which -carries all 52 verbs with their preconditions and their refusal strings, taken -from the code rather than from memory. Every state it produces — 20 selection -kinds × 2 document states, 40 primary menus and 73 submenus — is rendered by -`docs/ux/mockups/gen_offer_mockups.py` into `docs/ux/offer_atlas.html`. Read the -atlas before proposing a change to the map; the generator refuses to render an -address collision, so the map cannot silently rot. - -#### Rejected: the radial ring - -The first design put the eight families at eight compass points around the pick. -It is recorded here because it is a good idea that loses on evidence, and -someone will propose it again: - -- an inapplicable slot could only be drawn empty, and **an empty slot says - nothing** — the reason text above has nowhere to live; -- the measured fill was **3.45 of 8 slots**, so most of the control was blank - most of the time, and on a fresh document only two of eight were live; -- sketch-mode *Create* needs **nine** addresses; eight forced two primitives - behind a "More" slot, and a ninth position costs the 45° spacing that made the - ring worth having; -- long translated names do not fit around a circle, and screen readers and arrow - keys need bespoke handling a list gets for free; -- a 380 px disc over the model costs more on a 1366×768 screen than a 324 px - list beside it (§6.1). - -What it kept — equidistant targets and a future flick gesture — buys little in a -product whose experts live on the keyboard by design. - -### 4.2 Confirm and cancel are objects, not gestures - -The old rule — click empty space to commit — is withdrawn. It was an invisible -gesture with a destructive meaning: nothing on screen said it, and a stray click -committed a feature the user was still adjusting. That is exactly what L5 -forbids, and it is hostile to the audience §6.1 exists for. - -- **A pending feature carries a confirm/cancel puck**, attached to the geometry - it is editing, next to its handles: ✓ commits, ✗ discards. Enter and Escape - mirror them for the keyboard (L9). It is drawn where the user's attention - already is, and it is the only thing in the viewport that commits. -- **Empty space now means "clear the selection"** — the safe meaning, and the - same meaning everywhere. -- **This is not a dialog** (L4). It is two objects in the scene, on the - geometry, non-modal: the camera still orbits, the tree is still there, the - values are still editable while it waits. -- **Continuous tools do not ask.** Drawing a line, a rectangle, a circle commits - each entity as its own gesture completes — a ✓ per line would destroy the - inner loop. The puck belongs to *features* (extrude, fillet, hole, pattern, - mate) and to sketch edits that hold a pending state. Enter/Escape end a - continuous tool rather than confirming an entity. -- **Ambiguity resolves toward keeping work, never toward losing it.** Starting - another operation while a valid feature is pending commits it rather than - discarding it; if it is not valid, the product says why (L7) and keeps it - pending. Since undo reaches everything (§6.1), the recoverable direction is - always the right default. - -### 4.3 The rest of the grammar - -- **The status line is one imperative sentence** naming what the tool wants - next, and it names the target when the target came from a selection - ("Circle — click centre, then radius · on the picked face"). It is the - authoritative feedback surface for the armed tool; the toolbar is not. -- **Hover previews, click commits.** A hover shows the ghost of what a click - would do wherever this is cheap to compute. -- **Selection is persistent and visible** until consumed or cleared. A tool that - consumes a selection clears it, so the next feature cannot silently inherit it. -- **Every gesture is undoable**, and the feature tree is editable history, not a - log. Re-editing a feature re-enters the same on-geometry interaction that - created it — including its offer and its puck. - -## 5. Layout and screen budget - -The viewport is the application. Chrome is a tax on it. - -- **One toolbar**, contextual to the mode (model / sketch). Tools are grouped by - what they make, not by which subsystem implements them. -- **A left rail for the document, not for parameters**: feature tree, bodies, - variables. It answers "what exists", never "what value should this be". -- **No parameter panel.** Where one exists today it is technical debt with a - scheduled removal (§10). -- **Print context is ambient**, not a panel: the plate is visible in the design - space, and print-domain warnings appear on the geometry that will fail. -- **Nothing is added to permanent chrome without removing something**, or - demonstrating that the addition is used in the majority of sessions. -- **The budget is set by the smallest screen we serve**, 1366×768 (§6.1) — not - by the reviewer's monitor. Chrome that fits a 27-inch display and swallows a - laptop's has not fitted, it has just failed somewhere the author cannot see. - -## 6. Accessibility — reach first, then the assistive floor - -"Accessible" means two different things and the product owes both. §6.1 is about -**who can get in at all**; §6.2 is about **who can operate it once inside**. -Neither is a phase. Both are merge requirements. - -### 6.1 Reach — the door has to be open - -The premise of the whole project: someone with no money, no licence, no account, -no fast machine and no teacher can open this and make a real thing. Free -software on a school laptop is the only path to a CAD tool that reaches people -who were never going to be handed one. If a design decision quietly raises the -cost of entry, it has broken the premise, however elegant it is. - -- **The reference machine.** A 5-year-old laptop: dual/quad-core CPU, - **integrated graphics**, 8 GB RAM, **1366×768** screen, no discrete GPU. The - Design tab must be usable there, and any interaction that needs more is a - design failure to be solved, not a requirement to be documented. The GPU path - degrades gracefully to software rendering rather than refusing to start; the - viewport stays interactive while the kernel thinks. -- **1366×768 is the layout target, not the stretch case.** A form-heavy side - panel is not merely inelegant on that screen — it takes the model off it. - This is the second, independent argument for the whole of L1 and §5. -- **No account, no cloud, no connection.** The product works forever with the - network unplugged. Nothing is uploaded, no sign-in gates any feature, no - telemetry is required to use it. A school network that blocks everything must - not be able to block this. -- **No tier, no plugin wall, no "pro".** Every feature named in this document is - in the product everyone downloads. Assemblies and exploded views are not the - paid half. -- **Files belong to the user**, on their disk, in a format that outlives the - project: the design travels inside the ordinary project file, and the geometry - exports to STEP and mesh formats anyone can open. -- **Learnable without instruction.** The first solid comes with no - documentation, no video and no tutorial mode — from noticing that a face can - be clicked. Tooltips teach the vocabulary (L10) at the moment it is needed; - nothing is explained in a manual the user will never open. -- **Plain language at the entry tier.** The Make tier speaks in words a - thirteen-year-old reads without stopping. Precision comes with the tier that - needs it, and everything is translated, because "accessible" in English only - is not accessible. -- **Exploration must be free.** Undo reaches everything, work is never lost to a - wrong click, and no dialog ever asks the user to be sure. A tool that punishes - experiments teaches people to stop experimenting, which is the one thing this - audience cannot afford to learn. -- **The product never blames the user.** Failures are stated as what happened - and what to do (L7). "Invalid input" is not an acceptable sentence anywhere. - -### 6.2 Assistive floor - -- **Keyboard**: every operation reachable and completable without a pointer. - Single-letter shortcuts for sketch tools, shown in the offer itself (§4.1) as - well as in the tooltip. The offer opens from the keyboard (Menu key or - Shift+F10) and walks by arrow key and by type-ahead, so the row map works for - someone who never touches the pointer. A visible focus state on every - focusable element. No shortcut that only works while the pointer happens to be - over the canvas. -- **Colour**: never the sole carrier of meaning. Selection is colour *and* - outline; an error is colour *and* an icon *and* text. Verify in greyscale. -- **Contrast**: labels over the 3D viewport get a scrim or halo so 4.5:1 holds - against any background the model can produce, including a white body under a - white plate. -- **Targets**: handles and grips no smaller than 32 px at 100 % scale, scaling - with the OS factor; the grab tolerance is larger than the drawn glyph. -- **Timing**: no double-click-to-mean-something-else, no press-and-hold as the - only route to a function, no cycle that depends on repeated clicks - (see L5). The long-press that opens the offer on a one-button Mac and on touch - (§4.1) is explicitly an *additional* route — Ctrl-click, two-finger tap and - the keyboard all reach the same place — and it shows its own progress while - charging, so it never fails silently. -- **Motion**: animation is functional (showing where a thing went), never - decorative, and it respects the reduced-motion preference. -- **Text**: no fixed-width assumptions; the UI holds together in German and in - Chinese, at 125 % and 200 % scale. Every string routed through the normal - translation path. - -## 7. Depth without clutter — the three tiers - -Power for experts is delivered by **progressive disclosure of tools, never by -relocation of tools**. A tool that appears in a later tier is in the same place -it will always be; it is simply not shown yet. - -| Tier | Who | What appears | -|---|---|---| -| **Make** | first hour | Sketch, extrude, revolve, hole, fillet/chamfer, move, commit to plate | -| **Model** | competent user | Patterns, shell, draft, sweep/loft, booleans, reference geometry, variables, import/export | -| **Mechanism** | mechanical designer | Assemblies and mates, exploded views, interference detection, surfaces, feature-level editing of imported solids | - -Rules that keep this honest: - -1. **Tiers are non-modal.** No mode switch, no workbench selector, no "advanced - mode" toggle that changes the meaning of anything. The tier only governs what - is *offered*. -2. **A tier reveals itself by use.** Using a body reveals boolean tools; adding - a second body reveals assembly tools. The product notices what you are doing. -3. **Nothing moves when a tier appears.** A user who learned where fillet lives - finds it in the same place forever. -4. **An expert tool obeys the same grammar** as a beginner tool. Mates are - picked in 3D like everything else, not configured in a table. -5. **Exploded views are a view state**, not a document mode — reversible, - draggable along mate axes, and never a separate file. - -## 8. Designing for print — the home advantage - -Design-time knowledge the application already has, and must use: - -- **The plate is present** in the design space, at the real size, with the real - origin. Committing a body to the plate is one action and preserves placement. -- **Print-domain checks run on the model, on the geometry, before slicing**: - walls thinner than the nozzle, unsupported overhangs beyond the material's - angle, features smaller than the layer height, a part that does not fit the - build volume. -- **These are warnings on the geometry, never a report.** The thin wall glows; - the tooltip says how thin and what the nozzle is. -- **Material and machine context is inherited** from the active slicer profile, - not re-entered in the Design tab. -- **The round trip is preserved**: editing a design after slicing returns to the - feature history, not to a mesh. - -## 9. The review gate - -Every pull request that touches the Design tab UI answers these, in the PR body. -A "no" that is not accompanied by an argument is a request for changes. - -1. Which law (L1–L11) does the change most directly serve? -2. Can the whole operation be completed without the pointer leaving the - viewport? If not, why is this the exception? - And: does the relevant selection *offer* this tool (§4.1), or must the user - already know it exists? -3. Are the values draggable *and* typable? -4. Screenshot of the state after **exactly one** click of the new gesture, - performed as a first-time user. -5. Keyboard-only walkthrough: does it complete? -6. Greyscale screenshot: is every state still distinguishable? -7. What was **removed**? (Net additions to permanent chrome require an argument.) -8. Which tier does it belong to, and does it appear without moving anything else? -9. What does it do when the geometry is invalid, and is that reported before the - commit? -10. Interaction cost: actions required for the canonical task it addresses, - before and after. -11. Reach (L11): screenshot at 1366×768 with the panel open — is the model still - on screen? Does it run on integrated graphics? Does it need the network, an - account, or a file the user cannot keep? -12. If the change adds or moves a verb in the offer: which row, and is it that - verb's row in **every** selection where it appears? Did any existing verb's - index change? (If yes, this is not a UI change, it is a breaking change to - every user's muscle memory, and it needs the group — see §4.1.) Was - `docs/ux/tool_atlas.json` updated and the atlas regenerated? -13. If the change adds a pointer gesture: what is its keyboard equivalent, and - what does a one-button Mac, a trackpad and a touch screen do (§4.1)? - -## 10. Where we stand today — honest inventory - -Complying with the laws already: - -- Sketch inline editors — draw an entity and its dimension tab opens on the - geometry; Tab walks Length → Width → Angle. -- Fillet/chamfer draggable radius arrow with an editable value label. -- Extrude depth arrow; move-body three-axis arrows. -- Datum-plane resize handles and offset arrow; ghost reference planes picked in - 3D. -- Imported-art place/size gizmo. -- Sketch plane taken from the picked face, with the target named in the status - line, and the sketch-plane dropdown deleted outright. - -Violating them, with removal scheduled: - -- **Every tool card is a two-column form** of combos and spin fields in the left - panel. This is the single largest debt in the product and the reason this - document exists. Tracked as an epic; each card is replaced by its on-geometry - equivalent, not improved in place. It fails L1 and it fails L11 twice over — - on a 1366×768 screen the cards leave the model a strip. -- Seven remaining plane pickers still populate a combo instead of consuming a - viewport selection. -- Pattern has no on-geometry spacing arrow or count badge. -- Hole is positioned by X/Y fields rather than by a point on a face. -- Booleans and cuts pick their operands from lists rather than in 3D. -- Fillet/chamfer edge selection still requires the click cycle L5 forbids. -- **Selecting geometry offers nothing.** There is no contextual offer (§4.1): - the user faces the full toolbar whatever they have picked, and finds out that - a tool did not apply by it doing nothing. This is the largest single item of - new work the charter asks for. The map and every state of it are already - drawn (`docs/ux/offer_atlas.html`); what the group owes itself before the code - is ratifying the row order, since every verb built before that lands has to be - addressed afterwards anyway. -- **Committing is an invisible click in empty space** rather than the - confirm/cancel puck of §4.2 — the exact gesture that rule withdraws. - -Nothing on the violating list is defended. The only open question for each is -what its on-geometry replacement should be. - -## 11. How the group works - -**Roles.** Product/UX lead (owns this document and casts the tie-break vote on -interaction questions); kernel maintainer; GUI maintainer; a print-domain -reviewer; a mechanical-design reviewer who uses the product on real work; an -accessibility reviewer covering both senses of §6 — reach and assistive — who -owns the reference machine and actually runs on it. One person may hold more -than one role; the UX lead and the mechanical-design reviewer should not be the -same person, and nobody reviews reach from a workstation. - -**The absent audience needs a seat.** The fourteen-year-old is not in the room -and cannot file an issue. Someone in the group is accountable for B5 and B6, and -the group watches real first-timers use the product on the reference machine at -least once a quarter — school, makerspace, or a friend's kid. Everything else in -this document can be argued from principle; approachability can only be -observed. - -**Cadence.** A short weekly review of open interaction proposals. A monthly pass -over the violating inventory in §10 — anything that has not moved in two months -is either scheduled or explicitly accepted as permanent, with a reason written -into this document. - -**How a change moves.** - -1. *Problem* — a described user difficulty, ideally with an interaction-cost - measurement, never a solution in disguise. -2. *Sketch* — one or two on-geometry interaction proposals, drawn or described - as a gesture sequence. Reviewed against §3 before any code. -3. *Prototype* — built behind whatever the smallest safe path is, driven end to - end on a real display, and screenshotted at each state. -4. *Gate* — §9 answered in the PR. -5. *Merge*, then update §10. - -**Decisions are written down.** Any resolution that constrains future work is -appended to this document as a numbered law or as an accepted exception with its -reasoning. A decision that lives only in a call is not a decision. - -**How disagreements resolve.** Against the laws first. If the laws do not decide -it, the tie-break is the interaction cost measured on the canonical tasks in -§12; if that does not decide it, the UX lead chooses and records why. - -## 12. Canonical tasks — the benchmark - -The measure of every UX change is the cost of these five tasks. Each is timed and -counted (clicks, keystrokes, camera actions, mode switches) on the headless rig -and, periodically, with real users who have not seen the product. - -| # | Task | What it exercises | -|---|---|---| -| **B1** | Bracket: sketch an L, extrude, two holes, fillet the inside corner, send to plate | The inner loop | -| **B2** | Change a hole diameter and the plate thickness, six features deep, and rebuild | Parametric editability | -| **B3** | Take an imported STEP, delete a boss, close the face, thicken a wall to nozzle width | Direct editing + print awareness | -| **B4** | Two parts, one revolute mate, check interference, produce an exploded view | The Mechanism tier | -| **B5** | First-run: from opening the Design tab to a print-ready solid, no documentation | Approachability | -| **B6** | B1 again, on the reference machine at 1366×768, offline, on a fresh account-less install | Reach (L11) | - -Every task is run on the reference machine of §6.1, not on a workstation — a -number measured on a fast desktop describes an experience most of our users will -never have. B6 repeats the inner loop under the full entry conditions so that -reach is a measured quantity and not an intention. - -Targets are set once each task has been measured on the current build. B5's -target is expressed in minutes-to-first-solid **by someone who has never seen a -CAD program**, and it is the number this project is ultimately judged by. - ---- - -### Appendix — anti-patterns we have already paid for - -Kept because each cost real time and each is easy to reintroduce. - -- **The dropdown that grew a row.** Fixing "cannot sketch on a face" by adding a - "Face of Body 1" entry to a plane combo. It reads as a small fix and it is the - side-panel architecture reproducing itself. -- **The invisible first click.** Flyout buttons and pick cycles whose first click - changes nothing meaningful. Filed as bugs three separate times against working - code, and made a real bug look fixed when it was not. -- **The fix verified through a path the user will never take.** A face-sketch fix - confirmed by double-clicking to reach face level. Users click once. A fix - reachable only by an undiscoverable gesture is indistinguishable from no fix. -- **The wrong feedback surface.** Measuring an armed tool by the toolbar, which - never renders keyboard-armed state. The status line is the surface that - answers. -- **The silent success.** A cut that removed no material, reported as done. Now - an error naming the likely cause. diff --git a/docs/CAD/design/mate-connectors/BEAR_CONNECTOR_REVIEW.md b/docs/CAD/design/mate-connectors/BEAR_CONNECTOR_REVIEW.md deleted file mode 100644 index cc2d4b22aa..0000000000 --- a/docs/CAD/design/mate-connectors/BEAR_CONNECTOR_REVIEW.md +++ /dev/null @@ -1,169 +0,0 @@ -# BearConnector.step — examination - -> **Scope.** One file was supplied and it contains **one object: the male.** Everything below is -> measured from that single solid. Earlier drafts of this note reasoned about a female pocket and a -> mating pair — those objects were never supplied, so any statement about them was speculation and -> has been removed. The clearance, the fit, and the pocket's legibility are all **unassessed**. - -Measured, not eyeballed. Imported into the Design tab's own OpenCascade kernel -(`import_step` → one valid closed solid), topology queried, geometry checked numerically. -Flat drawing: `artifacts/shots/bear-flat.png`. Viewport: `artifacts/shots/bear-02-zoom.png`. - -**File:** AP242 Edition 2, ST-Developer. 1 `MANIFOLD_SOLID_BREP`, 1 `CLOSED_SHELL`. -**Size:** 83.06 × 66.69 × 17.27 mm. **Faces:** 30 — 24 planar + 6 cylindrical. -**Curves:** 69 lines + 12 circles. **No** splines, spheres, tori or cones. -**Relief:** only four Z levels — 0, 3.00, 10.66, 17.27. - ---- - -## What is right, and precisely so - -**The sloping ridge is implemented exactly as briefed.** From (0.00, 18.40, 17.27) to -(0.00, 46.72, 10.66): 28.3 mm long, 6.61 mm drop, **13.1° slope**, and both ends sit dead on -x = 0.00. It breaks 180° rotation on its own. - -**20.0° uniform draft on all four snout flanks**, identical to within 0.1°: -`(0,−0.94,0.342) (0.936,0.08,0.342) (0,0.94,0.342) (−0.936,0.08,0.342)`. That is a real, -deliberate lead-in — it self-centres into a matching pocket, and it demoulds and prints. - -**The eyes are exactly symmetric**: Ø9.87 at x = ±16.43, y = 48.01, matching to 0.01 mm. -Someone mirrored those on purpose. - -**The mating feature is extremely economical**: only **five edges** exist above the 3 mm plate — -the ridge plus two flank edges at each end. Base plate is exactly 3.00 mm. - -The low-poly constraint is honoured. All six cylinders are outline rounds and eye holes; none of -them is a mating surface. - ---- - -## The asymmetry is deliberate, and it is complete - -**Correction.** A first pass read the left/right differences as an unfinished mirror. That was wrong: -the asymmetry is intentional. Tested properly — every candidate self-symmetry, in the part's own -centred frame, with a generous 0.1 mm tolerance: - -| operation | edges mapped onto the part | -|---|---| -| identity | 81 / 81 — 100 % | -| mirror about x = 0 (left/right) | **0 / 81** | -| mirror about y = 0 (top/bottom) | **0 / 81** | -| rotate 180° about Z | **0 / 81** | -| rotate 90° about Z | **0 / 81** | -| mirror about the diagonal | **0 / 81** | - -**The symmetry group is trivial.** No rigid motion or reflection maps this part onto itself, so -**every partial view determines the orientation uniquely** — you never need to see the whole face to -know which way round it goes. That is the strongest possible result for a keying interface and it is -exactly what the earlier abstract glyph work kept failing to achieve: a symmetric shape seen at a -grazing angle, or half-occluded, gives an ambiguous read. - -### Does it let you GRASP the orientation? Measured, not asserted. - -Unique-in-principle and graspable-at-a-glance are different claims. The symmetry table proves the -first. For the second, the front-on picture (outline + eyes + mouth, filled) was rasterised and -compared against its own mirror and its own 180° rotation — the two ways a person can get it wrong. - -**By size** (percentage of pixels that differ): - -| width | vs mirror | vs rotated 180° | -|---|---|---| -| 16 px | 20.7 % | 26.0 % | -| 24 px | 21.9 % | 30.9 % | -| 32 px | 23.0 % | 28.1 % | -| 48 px | 22.4 % | 30.6 % | -| 80 px | 24.7 % | 31.0 % | -| 160 px | 23.6 % | 31.0 % | - -**The curve is flat.** The full signal is already there at 16 pixels and more resolution adds -nothing. That is the whole result: **the orientation cue lives at low spatial frequency**, carried by -the overall shape rather than by any detail. It therefore survives distance, blur, poor light, -peripheral vision, a small print and a low-resolution screen. It is the exact opposite of the abstract -disc glyph, whose roll cue was a small high-frequency feature and died at a grazing angle. - -**Partial views — a claim I made and then withdrew.** I ran a masked-window test and concluded that -a single quarter of the face was enough to read the orientation. **That test was invalid and the -conclusion is wrong.** It compared a window of the original against *the same window* of the mirrored -and rotated versions — which silently hands the observer the registration. It assumes you already -know that the patch you are looking at is the top-left quarter, which is exactly the thing you would -not know if you could only see a quarter. - -**You need to see the whole face.** The cues here are *relational*: the big ear only means something -next to the small ear, and the mouth offset only means something relative to the centreline. None of -them is self-locating. Whole-face is the operating condition, and the design should be judged and -used on that basis. - -That does not weaken the size result above, which always used the complete silhouette: the whole face -reads at 16 px. Needing all of it, and needing very little resolution of it, are compatible — and for -a part held in a hand, seeing all of it is the normal case. - -**The signal is allocated to the right risks.** The strongest cue (up to 41.7 %) guards against -inserting it upside down — the mistake people actually make. The weakest (~23 %) guards the mirror -case, which needs the part flipped over and which the protrusion already prevents mechanically. - -It also does mechanical work beyond the ridge. The ridge alone breaks 180° rotation; the asymmetric -outline additionally defeats the **mirrored-part** case — a mirror-image copy will not fit, so a -modelling or printing mirror is caught at assembly rather than three steps later. - -And for children specifically, a symmetric cartoon face reads as a mask; illustrators asymmetrise -deliberately so a face reads as a *character*. The asymmetry is earning its keep three ways at once. - -### What is worth keeping in mind anyway - -**The ears differ by 42 %** — left 8.33 mm wide (top y 65.68), right 11.81 mm (top y 66.69). Both -start at the same y = 60.79, so they read as a deliberate pair rather than an error. 42 % is well -above the perceptual threshold: you see it instantly. Good cue. - -**The mouth is a smirk** — x −21.93 … 0.00, centred at x = −10.96, stopping on the centreline. A -classic character device and a strong asymmetry. - -**The rounds are the best cue and the one safety question.** All four are on the left — Ø11.71 at -(−40.82, 7.38), Ø11.71 at (−34.76, 0.58), Ø10.00 at (−29.85, 60.83), Ø2.90 at (−26.70, 65.95) — and -the right side is entirely sharp. This is the *most locally readable* cue in the design: the ears -differ only by comparison (you must see both to know which is which), whereas a rounded corner tells -you "this is the left" from that corner alone, by eye **or by fingertip**. For children assembling by -feel that is the cue doing the real work. - -The tension is that "sharp" on a children's part is a hazard, and the obvious safety fix — round -everything — destroys the cue. The resolution is not round-vs-sharp but **large-vs-small radius**: -keep R≈6 on the left and give the right R≈1. R1 still reads and feels sharp locally, so the cue -survives, and the actual edge hazard goes away. That is the one recommendation that outlives the -correction. - -**One measurement that does not fit the story:** the outline is off-centre by **0.54 mm** (left reach -40.99, right reach 42.07). A deliberate cue should be unmissable; 0.54 mm is invisible. It is -probably a by-product of the other features rather than intent — worth a look, not a defect. - ---- - -## Two judgement calls, not defects - -**The snout is highest at the nose tip and slopes down toward the brow** — a real bear's muzzle -does the opposite. Anatomically it reads more like a beak or a horn than a snout. But mechanically -it is the better choice: the nose tip enters the pocket first and does the finding. Keep it if the -lead-in matters more than the likeness; flip it if "it must look like a bear" wins. - -**Only the male was supplied**, so the clearance, the fit and the pocket are unassessed. Nothing in -this note should be read as a judgement on them. - ---- - -## The strategic point, which is the real reason this design is good - -It gives orientation **a name**. "Ears up, nose down" needs no legend, no convention and no -documentation. Face recognition is the most robust pattern-matching humans have: it survives low -resolution, poor light, partial occlusion and peripheral vision. That is exactly the robustness the -abstract ridge key was reaching for, and here it comes for free. - -**One earlier objection does not transfer — noting it only so it is not carried over by mistake.** -In §8c of the design doc a female *pocket* measured as visually invisible — flat-shaded, a recess -reads as a blank rectangle — and I concluded male/female -is the wrong polarity cue. **That was a viewport finding, and it does not apply to a physical part.** -Nobody looks into the pocket of a toy; they feel it. For a part in a child's hands, male/female is -exactly the right polarity language. The earlier conclusion stands for the on-screen glyph and must -not be carried over to this. - -**The one rule to write down now:** the face and the key must never be allowed to disagree. People -will trust the face over the mechanics every time. Here they agree — ridge on the centreline, ears -up. If the face is ever restyled independently of the key, a user will orient by the bear and be -wrong. Tie them permanently, in the model and in whatever generates it. diff --git a/docs/CAD/design/mate-connectors/BearConnector_Cutter.step b/docs/CAD/design/mate-connectors/BearConnector_Cutter.step deleted file mode 100644 index c78e19e536..0000000000 --- a/docs/CAD/design/mate-connectors/BearConnector_Cutter.step +++ /dev/null @@ -1,998 +0,0 @@ -ISO-10303-21; -HEADER; -FILE_DESCRIPTION(('FreeCAD Model'),'2;1'); -FILE_NAME('Open CASCADE Shape Model','2026-08-05T12:46:26',('FreeCAD'),( - 'FreeCAD'),'Open CASCADE STEP processor 7.8','FreeCAD','Unknown'); -FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }')); -ENDSEC; -DATA; -#1 = APPLICATION_PROTOCOL_DEFINITION('international standard', - 'automotive_design',2000,#2); -#2 = APPLICATION_CONTEXT( - 'core data for automotive mechanical design processes'); -#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10); -#4 = PRODUCT_DEFINITION_SHAPE('','',#5); -#5 = PRODUCT_DEFINITION('design','',#6,#9); -#6 = PRODUCT_DEFINITION_FORMATION('','',#7); -#7 = PRODUCT('Open CASCADE STEP translator 7.8 1', - 'Open CASCADE STEP translator 7.8 1','',(#8)); -#8 = PRODUCT_CONTEXT('',#2,'mechanical'); -#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design'); -#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#15),#958); -#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14); -#12 = CARTESIAN_POINT('',(0.,0.,0.)); -#13 = DIRECTION('',(0.,0.,1.)); -#14 = DIRECTION('',(1.,0.,-0.)); -#15 = MANIFOLD_SOLID_BREP('',#16); -#16 = CLOSED_SHELL('',(#17,#229,#260,#497,#514,#531,#548,#565,#582,#599, - #616,#633,#650,#667,#684,#701,#718,#735,#747,#770,#794,#810,#822, - #839,#856,#878,#895,#912,#929,#946)); -#17 = ADVANCED_FACE('',(#18,#68,#79,#213),#224,.F.); -#18 = FACE_BOUND('',#19,.F.); -#19 = EDGE_LOOP('',(#20,#30,#38,#46,#54,#62)); -#20 = ORIENTED_EDGE('',*,*,#21,.F.); -#21 = EDGE_CURVE('',#22,#24,#26,.T.); -#22 = VERTEX_POINT('',#23); -#23 = CARTESIAN_POINT('',(19.029295926024,-0.2,-17.63009960955)); -#24 = VERTEX_POINT('',#25); -#25 = CARTESIAN_POINT('',(16.626582997737,-0.2,-8.940188245231)); -#26 = LINE('',#27,#28); -#27 = CARTESIAN_POINT('',(19.849519003668,-0.2,-20.59660707692)); -#28 = VECTOR('',#29,1.); -#29 = DIRECTION('',(-0.26649542889,0.,0.963836182336)); -#30 = ORIENTED_EDGE('',*,*,#31,.F.); -#31 = EDGE_CURVE('',#32,#22,#34,.T.); -#32 = VERTEX_POINT('',#33); -#33 = CARTESIAN_POINT('',(22.059435554995,-0.2,-3.734519760785)); -#34 = LINE('',#35,#36); -#35 = CARTESIAN_POINT('',(17.698510515043,-0.2,-23.73280021221)); -#36 = VECTOR('',#37,1.); -#37 = DIRECTION('',(-0.213058124893,0.,-0.977039526026)); -#38 = ORIENTED_EDGE('',*,*,#39,.F.); -#39 = EDGE_CURVE('',#40,#32,#42,.T.); -#40 = VERTEX_POINT('',#41); -#41 = CARTESIAN_POINT('',(-21.72552223146,-0.2,-3.734519760785)); -#42 = LINE('',#43,#44); -#43 = CARTESIAN_POINT('',(0.297084840953,-0.2,-3.734519760785)); -#44 = VECTOR('',#45,1.); -#45 = DIRECTION('',(1.,0.,0.)); -#46 = ORIENTED_EDGE('',*,*,#47,.F.); -#47 = EDGE_CURVE('',#48,#40,#50,.T.); -#48 = VERTEX_POINT('',#49); -#49 = CARTESIAN_POINT('',(-21.72552223146,-0.2,-8.903751135252)); -#50 = LINE('',#51,#52); -#51 = CARTESIAN_POINT('',(-21.72552223146,-0.2,-19.83215600037)); -#52 = VECTOR('',#53,1.); -#53 = DIRECTION('',(0.,0.,1.)); -#54 = ORIENTED_EDGE('',*,*,#55,.F.); -#55 = EDGE_CURVE('',#56,#48,#58,.T.); -#56 = VERTEX_POINT('',#57); -#57 = CARTESIAN_POINT('',(4.383041634064E-04,-0.2,-8.903751615529)); -#58 = LINE('',#59,#60); -#59 = CARTESIAN_POINT('',(-5.279852300138,-0.2,-8.903751135252)); -#60 = VECTOR('',#61,1.); -#61 = DIRECTION('',(-1.,0.,0.)); -#62 = ORIENTED_EDGE('',*,*,#63,.F.); -#63 = EDGE_CURVE('',#24,#56,#64,.T.); -#64 = LINE('',#65,#66); -#65 = CARTESIAN_POINT('',(4.347099726942,-0.2,-8.913277437397)); -#66 = VECTOR('',#67,1.); -#67 = DIRECTION('',(-0.999997598615,0.,2.191520817069E-03)); -#68 = FACE_BOUND('',#69,.F.); -#69 = EDGE_LOOP('',(#70)); -#70 = ORIENTED_EDGE('',*,*,#71,.F.); -#71 = EDGE_CURVE('',#72,#72,#74,.T.); -#72 = VERTEX_POINT('',#73); -#73 = CARTESIAN_POINT('',(21.163799345768,-0.2,-48.00951684793)); -#74 = CIRCLE('',#75,4.735522705283); -#75 = AXIS2_PLACEMENT_3D('',#76,#77,#78); -#76 = CARTESIAN_POINT('',(16.428276640485,-0.2,-48.00951684793)); -#77 = DIRECTION('',(-0.,1.,0.)); -#78 = DIRECTION('',(1.,0.,0.)); -#79 = FACE_BOUND('',#80,.F.); -#80 = EDGE_LOOP('',(#81,#91,#100,#108,#116,#125,#133,#142,#150,#158,#166 - ,#174,#182,#190,#198,#206)); -#81 = ORIENTED_EDGE('',*,*,#82,.T.); -#82 = EDGE_CURVE('',#83,#85,#87,.T.); -#83 = VERTEX_POINT('',#84); -#84 = CARTESIAN_POINT('',(-27.86158468659,-0.2,-65.78852163128)); -#85 = VERTEX_POINT('',#86); -#86 = CARTESIAN_POINT('',(-29.08766684168,-0.2,-64.52156932717)); -#87 = LINE('',#88,#89); -#88 = CARTESIAN_POINT('',(-29.44004200272,-0.2,-64.15744811364)); -#89 = VECTOR('',#90,1.); -#90 = DIRECTION('',(-0.695421216677,0.,0.718602345805)); -#91 = ORIENTED_EDGE('',*,*,#92,.F.); -#92 = EDGE_CURVE('',#93,#85,#95,.T.); -#93 = VERTEX_POINT('',#94); -#94 = CARTESIAN_POINT('',(-28.96712497021,-0.2,-57.16864680227)); -#95 = CIRCLE('',#96,5.2); -#96 = AXIS2_PLACEMENT_3D('',#97,#98,#99); -#97 = CARTESIAN_POINT('',(-25.35093464349,-0.2,-60.90537900045)); -#98 = DIRECTION('',(0.,-1.,0.)); -#99 = DIRECTION('',(-1.,0.,0.)); -#100 = ORIENTED_EDGE('',*,*,#101,.T.); -#101 = EDGE_CURVE('',#93,#102,#104,.T.); -#102 = VERTEX_POINT('',#103); -#103 = CARTESIAN_POINT('',(-26.93875323652,-0.2,-55.20570756731)); -#104 = LINE('',#105,#106); -#105 = CARTESIAN_POINT('',(-14.90185526362,-0.2,-43.55710346492)); -#106 = VECTOR('',#107,1.); -#107 = DIRECTION('',(0.718602345805,0.,0.695421216677)); -#108 = ORIENTED_EDGE('',*,*,#109,.T.); -#109 = EDGE_CURVE('',#102,#110,#112,.T.); -#110 = VERTEX_POINT('',#111); -#111 = CARTESIAN_POINT('',(-41.17904151244,-0.2,-10.52828909594)); -#112 = LINE('',#113,#114); -#113 = CARTESIAN_POINT('',(-32.39120436163,-0.2,-38.09921113329)); -#114 = VECTOR('',#115,1.); -#115 = DIRECTION('',(-0.30368282823,0.,0.952773183837)); -#116 = ORIENTED_EDGE('',*,*,#117,.F.); -#117 = EDGE_CURVE('',#118,#110,#120,.T.); -#118 = VERTEX_POINT('',#119); -#119 = CARTESIAN_POINT('',(-39.69176606491,-0.2,-4.408923352436)); -#120 = CIRCLE('',#121,6.054044962965); -#121 = AXIS2_PLACEMENT_3D('',#122,#123,#124); -#122 = CARTESIAN_POINT('',(-35.41090981799,-0.2,-8.689779599357)); -#123 = DIRECTION('',(0.,-1.,0.)); -#124 = DIRECTION('',(-1.,0.,0.)); -#125 = ORIENTED_EDGE('',*,*,#126,.T.); -#126 = EDGE_CURVE('',#118,#127,#129,.T.); -#127 = VERTEX_POINT('',#128); -#128 = CARTESIAN_POINT('',(-36.85603142851,-0.2,-1.573188716044)); -#129 = LINE('',#130,#131); -#130 = CARTESIAN_POINT('',(-36.19319006079,-0.2,-0.910347348321)); -#131 = VECTOR('',#132,1.); -#132 = DIRECTION('',(0.707106781187,0.,0.707106781187)); -#133 = ORIENTED_EDGE('',*,*,#134,.F.); -#134 = EDGE_CURVE('',#135,#127,#137,.T.); -#135 = VERTEX_POINT('',#136); -#136 = CARTESIAN_POINT('',(-32.57517518159,-0.2,0.2)); -#137 = CIRCLE('',#138,6.054044962965); -#138 = AXIS2_PLACEMENT_3D('',#139,#140,#141); -#139 = CARTESIAN_POINT('',(-32.57517518159,-0.2,-5.854044962965)); -#140 = DIRECTION('',(0.,-1.,0.)); -#141 = DIRECTION('',(-1.,0.,0.)); -#142 = ORIENTED_EDGE('',*,*,#143,.T.); -#143 = EDGE_CURVE('',#135,#144,#146,.T.); -#144 = VERTEX_POINT('',#145); -#145 = CARTESIAN_POINT('',(35.082842712475,-0.2,0.2)); -#146 = LINE('',#147,#148); -#147 = CARTESIAN_POINT('',(-7.942265537672,-0.2,0.2)); -#148 = VECTOR('',#149,1.); -#149 = DIRECTION('',(1.,0.,0.)); -#150 = ORIENTED_EDGE('',*,*,#151,.F.); -#151 = EDGE_CURVE('',#152,#144,#154,.T.); -#152 = VERTEX_POINT('',#153); -#153 = CARTESIAN_POINT('',(42.298608189024,-0.2,-7.015765476549)); -#154 = LINE('',#155,#156); -#155 = CARTESIAN_POINT('',(36.596246576251,-0.2,-1.313403863776)); -#156 = VECTOR('',#157,1.); -#157 = DIRECTION('',(-0.707106781187,0.,0.707106781187)); -#158 = ORIENTED_EDGE('',*,*,#159,.F.); -#159 = EDGE_CURVE('',#160,#152,#162,.T.); -#160 = VERTEX_POINT('',#161); -#161 = CARTESIAN_POINT('',(26.938753236523,-0.2,-55.20570756731)); -#162 = LINE('',#163,#164); -#163 = CARTESIAN_POINT('',(32.699020781567,-0.2,-37.13346923684)); -#164 = VECTOR('',#165,1.); -#165 = DIRECTION('',(0.30368282823,0.,0.952773183837)); -#166 = ORIENTED_EDGE('',*,*,#167,.F.); -#167 = EDGE_CURVE('',#168,#160,#170,.T.); -#168 = VERTEX_POINT('',#169); -#169 = CARTESIAN_POINT('',(32.703857168398,-0.2,-60.78483712899)); -#170 = LINE('',#171,#172); -#171 = CARTESIAN_POINT('',(16.008242280412,-0.2,-44.62779994931)); -#172 = VECTOR('',#173,1.); -#173 = DIRECTION('',(-0.718602345805,0.,0.695421216677)); -#174 = ORIENTED_EDGE('',*,*,#175,.F.); -#175 = EDGE_CURVE('',#176,#168,#178,.T.); -#176 = VERTEX_POINT('',#177); -#177 = CARTESIAN_POINT('',(26.715163243538,-0.2,-66.97315781796)); -#178 = LINE('',#179,#180); -#179 = CARTESIAN_POINT('',(30.25240665457,-0.2,-63.31800414721)); -#180 = VECTOR('',#181,1.); -#181 = DIRECTION('',(0.695421216677,0.,0.718602345805)); -#182 = ORIENTED_EDGE('',*,*,#183,.F.); -#183 = EDGE_CURVE('',#184,#176,#186,.T.); -#184 = VERTEX_POINT('',#185); -#185 = CARTESIAN_POINT('',(20.532019001374,-0.2,-60.98947335481)); -#186 = LINE('',#187,#188); -#187 = CARTESIAN_POINT('',(9.922784884512,-0.2,-50.72247862452)); -#188 = VECTOR('',#189,1.); -#189 = DIRECTION('',(0.718602345805,0.,-0.695421216677)); -#190 = ORIENTED_EDGE('',*,*,#191,.F.); -#191 = EDGE_CURVE('',#192,#184,#194,.T.); -#192 = VERTEX_POINT('',#193); -#193 = CARTESIAN_POINT('',(-20.53201900137,-0.2,-60.98947335481)); -#194 = LINE('',#195,#196); -#195 = CARTESIAN_POINT('',(5.354765181569,-0.2,-60.98947335481)); -#196 = VECTOR('',#197,1.); -#197 = DIRECTION('',(1.,0.,0.)); -#198 = ORIENTED_EDGE('',*,*,#199,.T.); -#199 = EDGE_CURVE('',#192,#200,#202,.T.); -#200 = VERTEX_POINT('',#201); -#201 = CARTESIAN_POINT('',(-25.53052705686,-0.2,-65.82673637491)); -#202 = LINE('',#203,#204); -#203 = CARTESIAN_POINT('',(-9.454421870603,-0.2,-50.26922436104)); -#204 = VECTOR('',#205,1.); -#205 = DIRECTION('',(-0.718602345805,0.,-0.695421216677)); -#206 = ORIENTED_EDGE('',*,*,#207,.F.); -#207 = EDGE_CURVE('',#83,#200,#208,.T.); -#208 = CIRCLE('',#209,1.648528137424); -#209 = AXIS2_PLACEMENT_3D('',#210,#211,#212); -#210 = CARTESIAN_POINT('',(-26.67694849991,-0.2,-64.64210018823)); -#211 = DIRECTION('',(0.,-1.,0.)); -#212 = DIRECTION('',(-1.,0.,0.)); -#213 = FACE_BOUND('',#214,.F.); -#214 = EDGE_LOOP('',(#215)); -#215 = ORIENTED_EDGE('',*,*,#216,.F.); -#216 = EDGE_CURVE('',#217,#217,#219,.T.); -#217 = VERTEX_POINT('',#218); -#218 = CARTESIAN_POINT('',(-11.6927539352,-0.2,-48.00951684793)); -#219 = CIRCLE('',#220,4.735522705283); -#220 = AXIS2_PLACEMENT_3D('',#221,#222,#223); -#221 = CARTESIAN_POINT('',(-16.42827664048,-0.2,-48.00951684793)); -#222 = DIRECTION('',(-0.,1.,0.)); -#223 = DIRECTION('',(1.,0.,0.)); -#224 = PLANE('',#225); -#225 = AXIS2_PLACEMENT_3D('',#226,#227,#228); -#226 = CARTESIAN_POINT('',(0.403056515455,-0.2,-33.34517655273)); -#227 = DIRECTION('',(0.,1.,0.)); -#228 = DIRECTION('',(1.,0.,0.)); -#229 = ADVANCED_FACE('',(#230),#255,.F.); -#230 = FACE_BOUND('',#231,.F.); -#231 = EDGE_LOOP('',(#232,#240,#241,#249)); -#232 = ORIENTED_EDGE('',*,*,#233,.T.); -#233 = EDGE_CURVE('',#234,#160,#236,.T.); -#234 = VERTEX_POINT('',#235); -#235 = CARTESIAN_POINT('',(26.938753236523,3.2,-55.20570756731)); -#236 = LINE('',#237,#238); -#237 = CARTESIAN_POINT('',(26.938753236523,3.,-55.20570756731)); -#238 = VECTOR('',#239,1.); -#239 = DIRECTION('',(0.,-1.,0.)); -#240 = ORIENTED_EDGE('',*,*,#159,.T.); -#241 = ORIENTED_EDGE('',*,*,#242,.F.); -#242 = EDGE_CURVE('',#243,#152,#245,.T.); -#243 = VERTEX_POINT('',#244); -#244 = CARTESIAN_POINT('',(42.298608189024,3.2,-7.015765476549)); -#245 = LINE('',#246,#247); -#246 = CARTESIAN_POINT('',(42.298608189024,3.,-7.015765476549)); -#247 = VECTOR('',#248,1.); -#248 = DIRECTION('',(0.,-1.,0.)); -#249 = ORIENTED_EDGE('',*,*,#250,.F.); -#250 = EDGE_CURVE('',#234,#243,#251,.T.); -#251 = LINE('',#252,#253); -#252 = CARTESIAN_POINT('',(32.699020781567,3.2,-37.13346923684)); -#253 = VECTOR('',#254,1.); -#254 = DIRECTION('',(0.30368282823,0.,0.952773183837)); -#255 = PLANE('',#256); -#256 = AXIS2_PLACEMENT_3D('',#257,#258,#259); -#257 = CARTESIAN_POINT('',(34.581352051556,3.,-31.22785130119)); -#258 = DIRECTION('',(-0.952773183837,0.,0.30368282823)); -#259 = DIRECTION('',(0.30368282823,0.,0.952773183837)); -#260 = ADVANCED_FACE('',(#261,#311,#322,#447,#481),#492,.T.); -#261 = FACE_BOUND('',#262,.T.); -#262 = EDGE_LOOP('',(#263,#273,#281,#289,#297,#305)); -#263 = ORIENTED_EDGE('',*,*,#264,.F.); -#264 = EDGE_CURVE('',#265,#267,#269,.T.); -#265 = VERTEX_POINT('',#266); -#266 = CARTESIAN_POINT('',(16.626582997737,3.2,-8.940188245231)); -#267 = VERTEX_POINT('',#268); -#268 = CARTESIAN_POINT('',(4.383041634064E-04,3.2,-8.903751615529)); -#269 = LINE('',#270,#271); -#270 = CARTESIAN_POINT('',(4.347099726942,3.2,-8.913277437397)); -#271 = VECTOR('',#272,1.); -#272 = DIRECTION('',(-0.999997598615,0.,2.191520817069E-03)); -#273 = ORIENTED_EDGE('',*,*,#274,.F.); -#274 = EDGE_CURVE('',#275,#265,#277,.T.); -#275 = VERTEX_POINT('',#276); -#276 = CARTESIAN_POINT('',(19.029295926024,3.2,-17.63009960955)); -#277 = LINE('',#278,#279); -#278 = CARTESIAN_POINT('',(19.849519003668,3.2,-20.59660707692)); -#279 = VECTOR('',#280,1.); -#280 = DIRECTION('',(-0.26649542889,0.,0.963836182336)); -#281 = ORIENTED_EDGE('',*,*,#282,.F.); -#282 = EDGE_CURVE('',#283,#275,#285,.T.); -#283 = VERTEX_POINT('',#284); -#284 = CARTESIAN_POINT('',(22.059435554995,3.2,-3.734519760785)); -#285 = LINE('',#286,#287); -#286 = CARTESIAN_POINT('',(17.698510515043,3.2,-23.73280021221)); -#287 = VECTOR('',#288,1.); -#288 = DIRECTION('',(-0.213058124893,0.,-0.977039526026)); -#289 = ORIENTED_EDGE('',*,*,#290,.F.); -#290 = EDGE_CURVE('',#291,#283,#293,.T.); -#291 = VERTEX_POINT('',#292); -#292 = CARTESIAN_POINT('',(-21.72552223146,3.2,-3.734519760785)); -#293 = LINE('',#294,#295); -#294 = CARTESIAN_POINT('',(0.297084840953,3.2,-3.734519760785)); -#295 = VECTOR('',#296,1.); -#296 = DIRECTION('',(1.,0.,0.)); -#297 = ORIENTED_EDGE('',*,*,#298,.F.); -#298 = EDGE_CURVE('',#299,#291,#301,.T.); -#299 = VERTEX_POINT('',#300); -#300 = CARTESIAN_POINT('',(-21.72552223146,3.2,-8.903751135252)); -#301 = LINE('',#302,#303); -#302 = CARTESIAN_POINT('',(-21.72552223146,3.2,-19.83215600037)); -#303 = VECTOR('',#304,1.); -#304 = DIRECTION('',(0.,0.,1.)); -#305 = ORIENTED_EDGE('',*,*,#306,.F.); -#306 = EDGE_CURVE('',#267,#299,#307,.T.); -#307 = LINE('',#308,#309); -#308 = CARTESIAN_POINT('',(-5.279852300138,3.2,-8.903751135252)); -#309 = VECTOR('',#310,1.); -#310 = DIRECTION('',(-1.,0.,0.)); -#311 = FACE_BOUND('',#312,.T.); -#312 = EDGE_LOOP('',(#313)); -#313 = ORIENTED_EDGE('',*,*,#314,.F.); -#314 = EDGE_CURVE('',#315,#315,#317,.T.); -#315 = VERTEX_POINT('',#316); -#316 = CARTESIAN_POINT('',(21.163799345768,3.2,-48.00951684793)); -#317 = CIRCLE('',#318,4.735522705283); -#318 = AXIS2_PLACEMENT_3D('',#319,#320,#321); -#319 = CARTESIAN_POINT('',(16.428276640485,3.2,-48.00951684793)); -#320 = DIRECTION('',(-0.,1.,0.)); -#321 = DIRECTION('',(1.,0.,0.)); -#322 = FACE_BOUND('',#323,.T.); -#323 = EDGE_LOOP('',(#324,#334,#343,#351,#360,#368,#374,#375,#383,#391, - #399,#407,#415,#424,#432,#441)); -#324 = ORIENTED_EDGE('',*,*,#325,.T.); -#325 = EDGE_CURVE('',#326,#328,#330,.T.); -#326 = VERTEX_POINT('',#327); -#327 = CARTESIAN_POINT('',(-26.93875323652,3.2,-55.20570756731)); -#328 = VERTEX_POINT('',#329); -#329 = CARTESIAN_POINT('',(-41.17904151244,3.2,-10.52828909594)); -#330 = LINE('',#331,#332); -#331 = CARTESIAN_POINT('',(-32.39120436163,3.2,-38.09921113329)); -#332 = VECTOR('',#333,1.); -#333 = DIRECTION('',(-0.30368282823,0.,0.952773183837)); -#334 = ORIENTED_EDGE('',*,*,#335,.F.); -#335 = EDGE_CURVE('',#336,#328,#338,.T.); -#336 = VERTEX_POINT('',#337); -#337 = CARTESIAN_POINT('',(-39.69176606491,3.2,-4.408923352436)); -#338 = CIRCLE('',#339,6.054044962965); -#339 = AXIS2_PLACEMENT_3D('',#340,#341,#342); -#340 = CARTESIAN_POINT('',(-35.41090981799,3.2,-8.689779599357)); -#341 = DIRECTION('',(0.,-1.,0.)); -#342 = DIRECTION('',(-1.,0.,0.)); -#343 = ORIENTED_EDGE('',*,*,#344,.T.); -#344 = EDGE_CURVE('',#336,#345,#347,.T.); -#345 = VERTEX_POINT('',#346); -#346 = CARTESIAN_POINT('',(-36.85603142851,3.2,-1.573188716044)); -#347 = LINE('',#348,#349); -#348 = CARTESIAN_POINT('',(-36.19319006079,3.2,-0.910347348321)); -#349 = VECTOR('',#350,1.); -#350 = DIRECTION('',(0.707106781187,0.,0.707106781187)); -#351 = ORIENTED_EDGE('',*,*,#352,.F.); -#352 = EDGE_CURVE('',#353,#345,#355,.T.); -#353 = VERTEX_POINT('',#354); -#354 = CARTESIAN_POINT('',(-32.57517518159,3.2,0.2)); -#355 = CIRCLE('',#356,6.054044962965); -#356 = AXIS2_PLACEMENT_3D('',#357,#358,#359); -#357 = CARTESIAN_POINT('',(-32.57517518159,3.2,-5.854044962965)); -#358 = DIRECTION('',(0.,-1.,0.)); -#359 = DIRECTION('',(-1.,0.,0.)); -#360 = ORIENTED_EDGE('',*,*,#361,.T.); -#361 = EDGE_CURVE('',#353,#362,#364,.T.); -#362 = VERTEX_POINT('',#363); -#363 = CARTESIAN_POINT('',(35.082842712475,3.2,0.2)); -#364 = LINE('',#365,#366); -#365 = CARTESIAN_POINT('',(-7.942265537672,3.2,0.2)); -#366 = VECTOR('',#367,1.); -#367 = DIRECTION('',(1.,0.,0.)); -#368 = ORIENTED_EDGE('',*,*,#369,.F.); -#369 = EDGE_CURVE('',#243,#362,#370,.T.); -#370 = LINE('',#371,#372); -#371 = CARTESIAN_POINT('',(36.596246576251,3.2,-1.313403863776)); -#372 = VECTOR('',#373,1.); -#373 = DIRECTION('',(-0.707106781187,0.,0.707106781187)); -#374 = ORIENTED_EDGE('',*,*,#250,.F.); -#375 = ORIENTED_EDGE('',*,*,#376,.F.); -#376 = EDGE_CURVE('',#377,#234,#379,.T.); -#377 = VERTEX_POINT('',#378); -#378 = CARTESIAN_POINT('',(32.703857168398,3.2,-60.78483712899)); -#379 = LINE('',#380,#381); -#380 = CARTESIAN_POINT('',(16.008242280412,3.2,-44.62779994931)); -#381 = VECTOR('',#382,1.); -#382 = DIRECTION('',(-0.718602345805,0.,0.695421216677)); -#383 = ORIENTED_EDGE('',*,*,#384,.F.); -#384 = EDGE_CURVE('',#385,#377,#387,.T.); -#385 = VERTEX_POINT('',#386); -#386 = CARTESIAN_POINT('',(26.715163243538,3.2,-66.97315781796)); -#387 = LINE('',#388,#389); -#388 = CARTESIAN_POINT('',(30.25240665457,3.2,-63.31800414721)); -#389 = VECTOR('',#390,1.); -#390 = DIRECTION('',(0.695421216677,0.,0.718602345805)); -#391 = ORIENTED_EDGE('',*,*,#392,.F.); -#392 = EDGE_CURVE('',#393,#385,#395,.T.); -#393 = VERTEX_POINT('',#394); -#394 = CARTESIAN_POINT('',(20.532019001374,3.2,-60.98947335481)); -#395 = LINE('',#396,#397); -#396 = CARTESIAN_POINT('',(9.922784884512,3.2,-50.72247862452)); -#397 = VECTOR('',#398,1.); -#398 = DIRECTION('',(0.718602345805,0.,-0.695421216677)); -#399 = ORIENTED_EDGE('',*,*,#400,.F.); -#400 = EDGE_CURVE('',#401,#393,#403,.T.); -#401 = VERTEX_POINT('',#402); -#402 = CARTESIAN_POINT('',(-20.53201900137,3.2,-60.98947335481)); -#403 = LINE('',#404,#405); -#404 = CARTESIAN_POINT('',(5.354765181569,3.2,-60.98947335481)); -#405 = VECTOR('',#406,1.); -#406 = DIRECTION('',(1.,0.,0.)); -#407 = ORIENTED_EDGE('',*,*,#408,.T.); -#408 = EDGE_CURVE('',#401,#409,#411,.T.); -#409 = VERTEX_POINT('',#410); -#410 = CARTESIAN_POINT('',(-25.53052705686,3.2,-65.82673637491)); -#411 = LINE('',#412,#413); -#412 = CARTESIAN_POINT('',(-9.454421870603,3.2,-50.26922436104)); -#413 = VECTOR('',#414,1.); -#414 = DIRECTION('',(-0.718602345805,0.,-0.695421216677)); -#415 = ORIENTED_EDGE('',*,*,#416,.F.); -#416 = EDGE_CURVE('',#417,#409,#419,.T.); -#417 = VERTEX_POINT('',#418); -#418 = CARTESIAN_POINT('',(-27.86158468659,3.2,-65.78852163128)); -#419 = CIRCLE('',#420,1.648528137424); -#420 = AXIS2_PLACEMENT_3D('',#421,#422,#423); -#421 = CARTESIAN_POINT('',(-26.67694849991,3.2,-64.64210018823)); -#422 = DIRECTION('',(0.,-1.,0.)); -#423 = DIRECTION('',(-1.,0.,0.)); -#424 = ORIENTED_EDGE('',*,*,#425,.T.); -#425 = EDGE_CURVE('',#417,#426,#428,.T.); -#426 = VERTEX_POINT('',#427); -#427 = CARTESIAN_POINT('',(-29.08766684168,3.2,-64.52156932717)); -#428 = LINE('',#429,#430); -#429 = CARTESIAN_POINT('',(-29.44004200272,3.2,-64.15744811364)); -#430 = VECTOR('',#431,1.); -#431 = DIRECTION('',(-0.695421216677,0.,0.718602345805)); -#432 = ORIENTED_EDGE('',*,*,#433,.F.); -#433 = EDGE_CURVE('',#434,#426,#436,.T.); -#434 = VERTEX_POINT('',#435); -#435 = CARTESIAN_POINT('',(-28.96712497021,3.2,-57.16864680227)); -#436 = CIRCLE('',#437,5.2); -#437 = AXIS2_PLACEMENT_3D('',#438,#439,#440); -#438 = CARTESIAN_POINT('',(-25.35093464349,3.2,-60.90537900045)); -#439 = DIRECTION('',(0.,-1.,0.)); -#440 = DIRECTION('',(-1.,0.,0.)); -#441 = ORIENTED_EDGE('',*,*,#442,.T.); -#442 = EDGE_CURVE('',#434,#326,#443,.T.); -#443 = LINE('',#444,#445); -#444 = CARTESIAN_POINT('',(-14.90185526362,3.2,-43.55710346492)); -#445 = VECTOR('',#446,1.); -#446 = DIRECTION('',(0.718602345805,0.,0.695421216677)); -#447 = FACE_BOUND('',#448,.T.); -#448 = EDGE_LOOP('',(#449,#459,#467,#475)); -#449 = ORIENTED_EDGE('',*,*,#450,.F.); -#450 = EDGE_CURVE('',#451,#453,#455,.T.); -#451 = VERTEX_POINT('',#452); -#452 = CARTESIAN_POINT('',(5.809375885494,3.2,-13.06417917474)); -#453 = VERTEX_POINT('',#454); -#454 = CARTESIAN_POINT('',(2.688069798796,3.2,-49.64588621989)); -#455 = LINE('',#456,#457); -#456 = CARTESIAN_POINT('',(4.914157977861,3.2,-23.55613296875)); -#457 = VECTOR('',#458,1.); -#458 = DIRECTION('',(-8.501532861635E-02,0.,-0.996379643459)); -#459 = ORIENTED_EDGE('',*,*,#460,.F.); -#460 = EDGE_CURVE('',#461,#451,#463,.T.); -#461 = VERTEX_POINT('',#462); -#462 = CARTESIAN_POINT('',(-5.809375885494,3.2,-13.06417917474)); -#463 = LINE('',#464,#465); -#464 = CARTESIAN_POINT('',(1.615747408047,3.2,-13.06417917474)); -#465 = VECTOR('',#466,1.); -#466 = DIRECTION('',(1.,0.,-3.066574716487E-16)); -#467 = ORIENTED_EDGE('',*,*,#468,.F.); -#468 = EDGE_CURVE('',#469,#461,#471,.T.); -#469 = VERTEX_POINT('',#470); -#470 = CARTESIAN_POINT('',(-2.688069798796,3.2,-49.64588621989)); -#471 = LINE('',#472,#473); -#472 = CARTESIAN_POINT('',(-4.890802006217,3.2,-23.82986495424)); -#473 = VECTOR('',#474,1.); -#474 = DIRECTION('',(-8.501532861635E-02,0.,0.996379643459)); -#475 = ORIENTED_EDGE('',*,*,#476,.F.); -#476 = EDGE_CURVE('',#453,#469,#477,.T.); -#477 = LINE('',#478,#479); -#478 = CARTESIAN_POINT('',(1.615747408047,3.2,-49.64588621989)); -#479 = VECTOR('',#480,1.); -#480 = DIRECTION('',(-1.,0.,0.)); -#481 = FACE_BOUND('',#482,.T.); -#482 = EDGE_LOOP('',(#483)); -#483 = ORIENTED_EDGE('',*,*,#484,.F.); -#484 = EDGE_CURVE('',#485,#485,#487,.T.); -#485 = VERTEX_POINT('',#486); -#486 = CARTESIAN_POINT('',(-11.6927539352,3.2,-48.00951684793)); -#487 = CIRCLE('',#488,4.735522705283); -#488 = AXIS2_PLACEMENT_3D('',#489,#490,#491); -#489 = CARTESIAN_POINT('',(-16.42827664048,3.2,-48.00951684793)); -#490 = DIRECTION('',(-0.,1.,0.)); -#491 = DIRECTION('',(1.,0.,0.)); -#492 = PLANE('',#493); -#493 = AXIS2_PLACEMENT_3D('',#494,#495,#496); -#494 = CARTESIAN_POINT('',(0.403056515455,3.2,-33.34517655273)); -#495 = DIRECTION('',(0.,1.,0.)); -#496 = DIRECTION('',(1.,0.,0.)); -#497 = ADVANCED_FACE('',(#498),#509,.F.); -#498 = FACE_BOUND('',#499,.F.); -#499 = EDGE_LOOP('',(#500,#506,#507,#508)); -#500 = ORIENTED_EDGE('',*,*,#501,.F.); -#501 = EDGE_CURVE('',#168,#377,#502,.T.); -#502 = LINE('',#503,#504); -#503 = CARTESIAN_POINT('',(32.703857168398,3.,-60.78483712899)); -#504 = VECTOR('',#505,1.); -#505 = DIRECTION('',(0.,1.,0.)); -#506 = ORIENTED_EDGE('',*,*,#167,.T.); -#507 = ORIENTED_EDGE('',*,*,#233,.F.); -#508 = ORIENTED_EDGE('',*,*,#376,.F.); -#509 = PLANE('',#510); -#510 = AXIS2_PLACEMENT_3D('',#511,#512,#513); -#511 = CARTESIAN_POINT('',(29.704873980143,3.,-57.88259703786)); -#512 = DIRECTION('',(-0.695421216677,0.,-0.718602345805)); -#513 = DIRECTION('',(-0.718602345805,0.,0.695421216677)); -#514 = ADVANCED_FACE('',(#515),#526,.F.); -#515 = FACE_BOUND('',#516,.F.); -#516 = EDGE_LOOP('',(#517,#523,#524,#525)); -#517 = ORIENTED_EDGE('',*,*,#518,.F.); -#518 = EDGE_CURVE('',#176,#385,#519,.T.); -#519 = LINE('',#520,#521); -#520 = CARTESIAN_POINT('',(26.715163243538,3.,-66.97315781796)); -#521 = VECTOR('',#522,1.); -#522 = DIRECTION('',(0.,1.,0.)); -#523 = ORIENTED_EDGE('',*,*,#175,.T.); -#524 = ORIENTED_EDGE('',*,*,#501,.T.); -#525 = ORIENTED_EDGE('',*,*,#384,.F.); -#526 = PLANE('',#527); -#527 = AXIS2_PLACEMENT_3D('',#528,#529,#530); -#528 = CARTESIAN_POINT('',(29.709510205968,3.,-63.87899747347)); -#529 = DIRECTION('',(-0.718602345805,0.,0.695421216677)); -#530 = DIRECTION('',(0.695421216677,0.,0.718602345805)); -#531 = ADVANCED_FACE('',(#532),#543,.F.); -#532 = FACE_BOUND('',#533,.F.); -#533 = EDGE_LOOP('',(#534,#540,#541,#542)); -#534 = ORIENTED_EDGE('',*,*,#535,.T.); -#535 = EDGE_CURVE('',#393,#184,#536,.T.); -#536 = LINE('',#537,#538); -#537 = CARTESIAN_POINT('',(20.532019001374,3.,-60.98947335481)); -#538 = VECTOR('',#539,1.); -#539 = DIRECTION('',(0.,-1.,0.)); -#540 = ORIENTED_EDGE('',*,*,#183,.T.); -#541 = ORIENTED_EDGE('',*,*,#518,.T.); -#542 = ORIENTED_EDGE('',*,*,#392,.F.); -#543 = PLANE('',#544); -#544 = AXIS2_PLACEMENT_3D('',#545,#546,#547); -#545 = CARTESIAN_POINT('',(23.522653113203,3.,-63.8836336993)); -#546 = DIRECTION('',(0.695421216677,0.,0.718602345805)); -#547 = DIRECTION('',(0.718602345805,0.,-0.695421216677)); -#548 = ADVANCED_FACE('',(#549),#560,.F.); -#549 = FACE_BOUND('',#550,.F.); -#550 = EDGE_LOOP('',(#551,#557,#558,#559)); -#551 = ORIENTED_EDGE('',*,*,#552,.F.); -#552 = EDGE_CURVE('',#192,#401,#553,.T.); -#553 = LINE('',#554,#555); -#554 = CARTESIAN_POINT('',(-20.53201900137,3.,-60.98947335481)); -#555 = VECTOR('',#556,1.); -#556 = DIRECTION('',(0.,1.,0.)); -#557 = ORIENTED_EDGE('',*,*,#191,.T.); -#558 = ORIENTED_EDGE('',*,*,#535,.F.); -#559 = ORIENTED_EDGE('',*,*,#400,.F.); -#560 = PLANE('',#561); -#561 = AXIS2_PLACEMENT_3D('',#562,#563,#564); -#562 = CARTESIAN_POINT('',(10.306473847682,3.,-60.98947335481)); -#563 = DIRECTION('',(0.,0.,1.)); -#564 = DIRECTION('',(0.,-1.,0.)); -#565 = ADVANCED_FACE('',(#566),#577,.T.); -#566 = FACE_BOUND('',#567,.T.); -#567 = EDGE_LOOP('',(#568,#574,#575,#576)); -#568 = ORIENTED_EDGE('',*,*,#569,.F.); -#569 = EDGE_CURVE('',#409,#200,#570,.T.); -#570 = LINE('',#571,#572); -#571 = CARTESIAN_POINT('',(-25.53052705686,3.,-65.82673637491)); -#572 = VECTOR('',#573,1.); -#573 = DIRECTION('',(0.,-1.,0.)); -#574 = ORIENTED_EDGE('',*,*,#408,.F.); -#575 = ORIENTED_EDGE('',*,*,#552,.F.); -#576 = ORIENTED_EDGE('',*,*,#199,.T.); -#577 = PLANE('',#578); -#578 = AXIS2_PLACEMENT_3D('',#579,#580,#581); -#579 = CARTESIAN_POINT('',(-23.00219525444,3.,-63.37996509944)); -#580 = DIRECTION('',(0.695421216677,0.,-0.718602345805)); -#581 = DIRECTION('',(-0.718602345805,0.,-0.695421216677)); -#582 = ADVANCED_FACE('',(#583),#594,.T.); -#583 = FACE_BOUND('',#584,.T.); -#584 = EDGE_LOOP('',(#585,#591,#592,#593)); -#585 = ORIENTED_EDGE('',*,*,#586,.F.); -#586 = EDGE_CURVE('',#417,#83,#587,.T.); -#587 = LINE('',#588,#589); -#588 = CARTESIAN_POINT('',(-27.86158468659,3.,-65.78852163128)); -#589 = VECTOR('',#590,1.); -#590 = DIRECTION('',(0.,-1.,0.)); -#591 = ORIENTED_EDGE('',*,*,#416,.T.); -#592 = ORIENTED_EDGE('',*,*,#569,.T.); -#593 = ORIENTED_EDGE('',*,*,#207,.F.); -#594 = CYLINDRICAL_SURFACE('',#595,1.648528137424); -#595 = AXIS2_PLACEMENT_3D('',#596,#597,#598); -#596 = CARTESIAN_POINT('',(-26.67694849991,3.,-64.64210018823)); -#597 = DIRECTION('',(0.,-1.,0.)); -#598 = DIRECTION('',(-1.,0.,0.)); -#599 = ADVANCED_FACE('',(#600),#611,.T.); -#600 = FACE_BOUND('',#601,.T.); -#601 = EDGE_LOOP('',(#602,#608,#609,#610)); -#602 = ORIENTED_EDGE('',*,*,#603,.F.); -#603 = EDGE_CURVE('',#426,#85,#604,.T.); -#604 = LINE('',#605,#606); -#605 = CARTESIAN_POINT('',(-29.08766684168,3.,-64.52156932717)); -#606 = VECTOR('',#607,1.); -#607 = DIRECTION('',(0.,-1.,0.)); -#608 = ORIENTED_EDGE('',*,*,#425,.F.); -#609 = ORIENTED_EDGE('',*,*,#586,.T.); -#610 = ORIENTED_EDGE('',*,*,#82,.T.); -#611 = PLANE('',#612); -#612 = AXIS2_PLACEMENT_3D('',#613,#614,#615); -#613 = CARTESIAN_POINT('',(-28.47462576413,3.,-65.15504547923)); -#614 = DIRECTION('',(-0.718602345805,0.,-0.695421216677)); -#615 = DIRECTION('',(-0.695421216677,0.,0.718602345805)); -#616 = ADVANCED_FACE('',(#617),#628,.T.); -#617 = FACE_BOUND('',#618,.T.); -#618 = EDGE_LOOP('',(#619,#625,#626,#627)); -#619 = ORIENTED_EDGE('',*,*,#620,.F.); -#620 = EDGE_CURVE('',#434,#93,#621,.T.); -#621 = LINE('',#622,#623); -#622 = CARTESIAN_POINT('',(-28.96712497021,3.,-57.16864680227)); -#623 = VECTOR('',#624,1.); -#624 = DIRECTION('',(0.,-1.,0.)); -#625 = ORIENTED_EDGE('',*,*,#433,.T.); -#626 = ORIENTED_EDGE('',*,*,#603,.T.); -#627 = ORIENTED_EDGE('',*,*,#92,.F.); -#628 = CYLINDRICAL_SURFACE('',#629,5.2); -#629 = AXIS2_PLACEMENT_3D('',#630,#631,#632); -#630 = CARTESIAN_POINT('',(-25.35093464349,3.,-60.90537900045)); -#631 = DIRECTION('',(0.,-1.,0.)); -#632 = DIRECTION('',(-1.,0.,0.)); -#633 = ADVANCED_FACE('',(#634),#645,.T.); -#634 = FACE_BOUND('',#635,.T.); -#635 = EDGE_LOOP('',(#636,#642,#643,#644)); -#636 = ORIENTED_EDGE('',*,*,#637,.F.); -#637 = EDGE_CURVE('',#326,#102,#638,.T.); -#638 = LINE('',#639,#640); -#639 = CARTESIAN_POINT('',(-26.93875323652,3.,-55.20570756731)); -#640 = VECTOR('',#641,1.); -#641 = DIRECTION('',(0.,-1.,0.)); -#642 = ORIENTED_EDGE('',*,*,#442,.F.); -#643 = ORIENTED_EDGE('',*,*,#620,.T.); -#644 = ORIENTED_EDGE('',*,*,#101,.T.); -#645 = PLANE('',#646); -#646 = AXIS2_PLACEMENT_3D('',#647,#648,#649); -#647 = CARTESIAN_POINT('',(-27.90836811563,3.,-56.14404399617)); -#648 = DIRECTION('',(-0.695421216677,0.,0.718602345805)); -#649 = DIRECTION('',(0.718602345805,0.,0.695421216677)); -#650 = ADVANCED_FACE('',(#651),#662,.T.); -#651 = FACE_BOUND('',#652,.T.); -#652 = EDGE_LOOP('',(#653,#659,#660,#661)); -#653 = ORIENTED_EDGE('',*,*,#654,.F.); -#654 = EDGE_CURVE('',#328,#110,#655,.T.); -#655 = LINE('',#656,#657); -#656 = CARTESIAN_POINT('',(-41.17904151244,3.,-10.52828909594)); -#657 = VECTOR('',#658,1.); -#658 = DIRECTION('',(0.,-1.,0.)); -#659 = ORIENTED_EDGE('',*,*,#325,.F.); -#660 = ORIENTED_EDGE('',*,*,#637,.T.); -#661 = ORIENTED_EDGE('',*,*,#109,.T.); -#662 = PLANE('',#663); -#663 = AXIS2_PLACEMENT_3D('',#664,#665,#666); -#664 = CARTESIAN_POINT('',(-34.04006158346,3.,-32.92609366041)); -#665 = DIRECTION('',(-0.952773183837,0.,-0.30368282823)); -#666 = DIRECTION('',(-0.30368282823,0.,0.952773183837)); -#667 = ADVANCED_FACE('',(#668),#679,.T.); -#668 = FACE_BOUND('',#669,.T.); -#669 = EDGE_LOOP('',(#670,#676,#677,#678)); -#670 = ORIENTED_EDGE('',*,*,#671,.F.); -#671 = EDGE_CURVE('',#336,#118,#672,.T.); -#672 = LINE('',#673,#674); -#673 = CARTESIAN_POINT('',(-39.69176606491,3.,-4.408923352436)); -#674 = VECTOR('',#675,1.); -#675 = DIRECTION('',(0.,-1.,0.)); -#676 = ORIENTED_EDGE('',*,*,#335,.T.); -#677 = ORIENTED_EDGE('',*,*,#654,.T.); -#678 = ORIENTED_EDGE('',*,*,#117,.F.); -#679 = CYLINDRICAL_SURFACE('',#680,6.054044962965); -#680 = AXIS2_PLACEMENT_3D('',#681,#682,#683); -#681 = CARTESIAN_POINT('',(-35.41090981799,3.,-8.689779599357)); -#682 = DIRECTION('',(0.,-1.,0.)); -#683 = DIRECTION('',(-1.,0.,0.)); -#684 = ADVANCED_FACE('',(#685),#696,.T.); -#685 = FACE_BOUND('',#686,.T.); -#686 = EDGE_LOOP('',(#687,#693,#694,#695)); -#687 = ORIENTED_EDGE('',*,*,#688,.F.); -#688 = EDGE_CURVE('',#345,#127,#689,.T.); -#689 = LINE('',#690,#691); -#690 = CARTESIAN_POINT('',(-36.85603142851,3.,-1.573188716044)); -#691 = VECTOR('',#692,1.); -#692 = DIRECTION('',(0.,-1.,0.)); -#693 = ORIENTED_EDGE('',*,*,#344,.F.); -#694 = ORIENTED_EDGE('',*,*,#671,.T.); -#695 = ORIENTED_EDGE('',*,*,#126,.T.); -#696 = PLANE('',#697); -#697 = AXIS2_PLACEMENT_3D('',#698,#699,#700); -#698 = CARTESIAN_POINT('',(-38.27389874671,3.,-2.99105603424)); -#699 = DIRECTION('',(-0.707106781187,0.,0.707106781187)); -#700 = DIRECTION('',(0.707106781187,0.,0.707106781187)); -#701 = ADVANCED_FACE('',(#702),#713,.T.); -#702 = FACE_BOUND('',#703,.T.); -#703 = EDGE_LOOP('',(#704,#710,#711,#712)); -#704 = ORIENTED_EDGE('',*,*,#705,.F.); -#705 = EDGE_CURVE('',#353,#135,#706,.T.); -#706 = LINE('',#707,#708); -#707 = CARTESIAN_POINT('',(-32.57517518159,3.,0.2)); -#708 = VECTOR('',#709,1.); -#709 = DIRECTION('',(0.,-1.,0.)); -#710 = ORIENTED_EDGE('',*,*,#352,.T.); -#711 = ORIENTED_EDGE('',*,*,#688,.T.); -#712 = ORIENTED_EDGE('',*,*,#134,.F.); -#713 = CYLINDRICAL_SURFACE('',#714,6.054044962965); -#714 = AXIS2_PLACEMENT_3D('',#715,#716,#717); -#715 = CARTESIAN_POINT('',(-32.57517518159,3.,-5.854044962965)); -#716 = DIRECTION('',(0.,-1.,0.)); -#717 = DIRECTION('',(-1.,0.,0.)); -#718 = ADVANCED_FACE('',(#719),#730,.T.); -#719 = FACE_BOUND('',#720,.T.); -#720 = EDGE_LOOP('',(#721,#727,#728,#729)); -#721 = ORIENTED_EDGE('',*,*,#722,.F.); -#722 = EDGE_CURVE('',#362,#144,#723,.T.); -#723 = LINE('',#724,#725); -#724 = CARTESIAN_POINT('',(35.082842712475,3.,0.2)); -#725 = VECTOR('',#726,1.); -#726 = DIRECTION('',(0.,-1.,0.)); -#727 = ORIENTED_EDGE('',*,*,#361,.F.); -#728 = ORIENTED_EDGE('',*,*,#705,.T.); -#729 = ORIENTED_EDGE('',*,*,#143,.T.); -#730 = PLANE('',#731); -#731 = AXIS2_PLACEMENT_3D('',#732,#733,#734); -#732 = CARTESIAN_POINT('',(-16.28758759079,3.,0.2)); -#733 = DIRECTION('',(0.,0.,1.)); -#734 = DIRECTION('',(0.,-1.,0.)); -#735 = ADVANCED_FACE('',(#736),#742,.F.); -#736 = FACE_BOUND('',#737,.F.); -#737 = EDGE_LOOP('',(#738,#739,#740,#741)); -#738 = ORIENTED_EDGE('',*,*,#151,.T.); -#739 = ORIENTED_EDGE('',*,*,#722,.F.); -#740 = ORIENTED_EDGE('',*,*,#369,.F.); -#741 = ORIENTED_EDGE('',*,*,#242,.T.); -#742 = PLANE('',#743); -#743 = AXIS2_PLACEMENT_3D('',#744,#745,#746); -#744 = CARTESIAN_POINT('',(38.67695526217,3.,-3.394112549695)); -#745 = DIRECTION('',(-0.707106781187,0.,-0.707106781187)); -#746 = DIRECTION('',(-0.707106781187,0.,0.707106781187)); -#747 = ADVANCED_FACE('',(#748),#765,.T.); -#748 = FACE_BOUND('',#749,.T.); -#749 = EDGE_LOOP('',(#750,#758,#764)); -#750 = ORIENTED_EDGE('',*,*,#751,.T.); -#751 = EDGE_CURVE('',#451,#752,#754,.T.); -#752 = VERTEX_POINT('',#753); -#753 = CARTESIAN_POINT('',(3.256654205567E-15,17.8572529153, - -18.39898295202)); -#754 = LINE('',#755,#756); -#755 = CARTESIAN_POINT('',(5.649679875255,3.602918464526,-13.21082950266 - )); -#756 = VECTOR('',#757,1.); -#757 = DIRECTION('',(-0.349023821871,0.880598971639,-0.320511814002)); -#758 = ORIENTED_EDGE('',*,*,#759,.T.); -#759 = EDGE_CURVE('',#752,#461,#760,.T.); -#760 = LINE('',#761,#762); -#761 = CARTESIAN_POINT('',(-5.275833888477,4.546144602338, - -13.55413574101)); -#762 = VECTOR('',#763,1.); -#763 = DIRECTION('',(-0.349023821871,-0.880598971639,0.320511814002)); -#764 = ORIENTED_EDGE('',*,*,#460,.T.); -#765 = PLANE('',#766); -#766 = AXIS2_PLACEMENT_3D('',#767,#768,#769); -#767 = CARTESIAN_POINT('',(2.828438300639,3.068404028665,-13.01628215822 - )); -#768 = DIRECTION('',(2.881637632171E-16,0.342020143326,0.939692620786)); -#769 = DIRECTION('',(-1.048830324052E-16,0.939692620786,-0.342020143326) - ); -#770 = ADVANCED_FACE('',(#771),#789,.T.); -#771 = FACE_BOUND('',#772,.T.); -#772 = EDGE_LOOP('',(#773,#781,#782,#783)); -#773 = ORIENTED_EDGE('',*,*,#774,.T.); -#774 = EDGE_CURVE('',#775,#752,#777,.T.); -#775 = VERTEX_POINT('',#776); -#776 = CARTESIAN_POINT('',(1.480297366167E-15,11.242400581089, - -46.71869179633)); -#777 = LINE('',#778,#779); -#778 = CARTESIAN_POINT('',(2.6645352591E-15,18.133069549222, - -17.21814831317)); -#779 = VECTOR('',#780,1.); -#780 = DIRECTION('',(5.275122655166E-17,0.227455280238,0.97378852709)); -#781 = ORIENTED_EDGE('',*,*,#751,.F.); -#782 = ORIENTED_EDGE('',*,*,#450,.T.); -#783 = ORIENTED_EDGE('',*,*,#784,.T.); -#784 = EDGE_CURVE('',#453,#775,#785,.T.); -#785 = LINE('',#786,#787); -#786 = CARTESIAN_POINT('',(1.103762571829,7.940067898719,-47.92064259636 - )); -#787 = VECTOR('',#788,1.); -#788 = DIRECTION('',(-0.299648208284,0.896513522642,0.326304236859)); -#789 = PLANE('',#790); -#790 = AXIS2_PLACEMENT_3D('',#791,#792,#793); -#791 = CARTESIAN_POINT('',(5.823691883056,3.068404028665,-13.45978839622 - )); -#792 = DIRECTION('',(0.93629059846,0.342020143326,-7.988827695448E-02)); -#793 = DIRECTION('',(-0.340781908463,0.939692620786,2.907695487824E-02) - ); -#794 = ADVANCED_FACE('',(#795),#805,.T.); -#795 = FACE_BOUND('',#796,.T.); -#796 = EDGE_LOOP('',(#797,#803,#804)); -#797 = ORIENTED_EDGE('',*,*,#798,.T.); -#798 = EDGE_CURVE('',#469,#775,#799,.T.); -#799 = LINE('',#800,#801); -#800 = CARTESIAN_POINT('',(-0.871390517001,8.635298785064, - -47.66759924779)); -#801 = VECTOR('',#802,1.); -#802 = DIRECTION('',(0.299648208284,0.896513522642,0.326304236859)); -#803 = ORIENTED_EDGE('',*,*,#784,.F.); -#804 = ORIENTED_EDGE('',*,*,#476,.T.); -#805 = PLANE('',#806); -#806 = AXIS2_PLACEMENT_3D('',#807,#808,#809); -#807 = CARTESIAN_POINT('',(2.828438300639,3.068404028665,-49.69378323641 - )); -#808 = DIRECTION('',(0.,0.342020143326,-0.939692620786)); -#809 = DIRECTION('',(0.,0.939692620786,0.342020143326)); -#810 = ADVANCED_FACE('',(#811),#817,.T.); -#811 = FACE_BOUND('',#812,.T.); -#812 = EDGE_LOOP('',(#813,#814,#815,#816)); -#813 = ORIENTED_EDGE('',*,*,#468,.T.); -#814 = ORIENTED_EDGE('',*,*,#759,.F.); -#815 = ORIENTED_EDGE('',*,*,#774,.F.); -#816 = ORIENTED_EDGE('',*,*,#798,.F.); -#817 = PLANE('',#818); -#818 = AXIS2_PLACEMENT_3D('',#819,#820,#821); -#819 = CARTESIAN_POINT('',(-5.782806207227,3.068404028665, - -13.93896851312)); -#820 = DIRECTION('',(-0.93629059846,0.342020143326,-7.988827695448E-02) - ); -#821 = DIRECTION('',(0.340781908463,0.939692620786,2.907695487824E-02)); -#822 = ADVANCED_FACE('',(#823),#834,.F.); -#823 = FACE_BOUND('',#824,.F.); -#824 = EDGE_LOOP('',(#825,#831,#832,#833)); -#825 = ORIENTED_EDGE('',*,*,#826,.F.); -#826 = EDGE_CURVE('',#217,#485,#827,.T.); -#827 = LINE('',#828,#829); -#828 = CARTESIAN_POINT('',(-11.6927539352,-22.,-48.00951684793)); -#829 = VECTOR('',#830,1.); -#830 = DIRECTION('',(0.,1.,0.)); -#831 = ORIENTED_EDGE('',*,*,#216,.T.); -#832 = ORIENTED_EDGE('',*,*,#826,.T.); -#833 = ORIENTED_EDGE('',*,*,#484,.F.); -#834 = CYLINDRICAL_SURFACE('',#835,4.735522705283); -#835 = AXIS2_PLACEMENT_3D('',#836,#837,#838); -#836 = CARTESIAN_POINT('',(-16.42827664048,-22.,-48.00951684793)); -#837 = DIRECTION('',(0.,1.,0.)); -#838 = DIRECTION('',(1.,0.,0.)); -#839 = ADVANCED_FACE('',(#840),#851,.F.); -#840 = FACE_BOUND('',#841,.F.); -#841 = EDGE_LOOP('',(#842,#848,#849,#850)); -#842 = ORIENTED_EDGE('',*,*,#843,.F.); -#843 = EDGE_CURVE('',#72,#315,#844,.T.); -#844 = LINE('',#845,#846); -#845 = CARTESIAN_POINT('',(21.163799345768,-22.,-48.00951684793)); -#846 = VECTOR('',#847,1.); -#847 = DIRECTION('',(0.,1.,0.)); -#848 = ORIENTED_EDGE('',*,*,#71,.T.); -#849 = ORIENTED_EDGE('',*,*,#843,.T.); -#850 = ORIENTED_EDGE('',*,*,#314,.F.); -#851 = CYLINDRICAL_SURFACE('',#852,4.735522705283); -#852 = AXIS2_PLACEMENT_3D('',#853,#854,#855); -#853 = CARTESIAN_POINT('',(16.428276640485,-22.,-48.00951684793)); -#854 = DIRECTION('',(0.,1.,0.)); -#855 = DIRECTION('',(1.,0.,0.)); -#856 = ADVANCED_FACE('',(#857),#873,.F.); -#857 = FACE_BOUND('',#858,.F.); -#858 = EDGE_LOOP('',(#859,#865,#866,#872)); -#859 = ORIENTED_EDGE('',*,*,#860,.F.); -#860 = EDGE_CURVE('',#24,#265,#861,.T.); -#861 = LINE('',#862,#863); -#862 = CARTESIAN_POINT('',(16.626582997737,-22.,-8.940188245231)); -#863 = VECTOR('',#864,1.); -#864 = DIRECTION('',(0.,1.,0.)); -#865 = ORIENTED_EDGE('',*,*,#63,.T.); -#866 = ORIENTED_EDGE('',*,*,#867,.T.); -#867 = EDGE_CURVE('',#56,#267,#868,.T.); -#868 = LINE('',#869,#870); -#869 = CARTESIAN_POINT('',(-5.329070518201E-15,-22.,-8.903751135252)); -#870 = VECTOR('',#871,1.); -#871 = DIRECTION('',(0.,1.,0.)); -#872 = ORIENTED_EDGE('',*,*,#264,.F.); -#873 = PLANE('',#874); -#874 = AXIS2_PLACEMENT_3D('',#875,#876,#877); -#875 = CARTESIAN_POINT('',(8.237581109188,-22.,-8.921803528809)); -#876 = DIRECTION('',(-2.191520817069E-03,0.,-0.999997598615)); -#877 = DIRECTION('',(-0.999997598615,0.,2.191520817069E-03)); -#878 = ADVANCED_FACE('',(#879),#890,.F.); -#879 = FACE_BOUND('',#880,.F.); -#880 = EDGE_LOOP('',(#881,#887,#888,#889)); -#881 = ORIENTED_EDGE('',*,*,#882,.T.); -#882 = EDGE_CURVE('',#48,#299,#883,.T.); -#883 = LINE('',#884,#885); -#884 = CARTESIAN_POINT('',(-21.72552223146,-22.,-8.903751135252)); -#885 = VECTOR('',#886,1.); -#886 = DIRECTION('',(0.,1.,0.)); -#887 = ORIENTED_EDGE('',*,*,#306,.F.); -#888 = ORIENTED_EDGE('',*,*,#867,.F.); -#889 = ORIENTED_EDGE('',*,*,#55,.T.); -#890 = PLANE('',#891); -#891 = AXIS2_PLACEMENT_3D('',#892,#893,#894); -#892 = CARTESIAN_POINT('',(-10.96276111573,-22.,-8.903751135252)); -#893 = DIRECTION('',(0.,0.,-1.)); -#894 = DIRECTION('',(0.,1.,0.)); -#895 = ADVANCED_FACE('',(#896),#907,.F.); -#896 = FACE_BOUND('',#897,.F.); -#897 = EDGE_LOOP('',(#898,#904,#905,#906)); -#898 = ORIENTED_EDGE('',*,*,#899,.T.); -#899 = EDGE_CURVE('',#40,#291,#900,.T.); -#900 = LINE('',#901,#902); -#901 = CARTESIAN_POINT('',(-21.72552223146,-22.,-3.734519760785)); -#902 = VECTOR('',#903,1.); -#903 = DIRECTION('',(0.,1.,0.)); -#904 = ORIENTED_EDGE('',*,*,#298,.F.); -#905 = ORIENTED_EDGE('',*,*,#882,.F.); -#906 = ORIENTED_EDGE('',*,*,#47,.T.); -#907 = PLANE('',#908); -#908 = AXIS2_PLACEMENT_3D('',#909,#910,#911); -#909 = CARTESIAN_POINT('',(-21.72552223146,-22.,-6.319135448019)); -#910 = DIRECTION('',(-1.,0.,0.)); -#911 = DIRECTION('',(0.,1.,0.)); -#912 = ADVANCED_FACE('',(#913),#924,.F.); -#913 = FACE_BOUND('',#914,.F.); -#914 = EDGE_LOOP('',(#915,#921,#922,#923)); -#915 = ORIENTED_EDGE('',*,*,#916,.T.); -#916 = EDGE_CURVE('',#32,#283,#917,.T.); -#917 = LINE('',#918,#919); -#918 = CARTESIAN_POINT('',(22.059435554995,-22.,-3.734519760785)); -#919 = VECTOR('',#920,1.); -#920 = DIRECTION('',(0.,1.,0.)); -#921 = ORIENTED_EDGE('',*,*,#290,.F.); -#922 = ORIENTED_EDGE('',*,*,#899,.F.); -#923 = ORIENTED_EDGE('',*,*,#39,.T.); -#924 = PLANE('',#925); -#925 = AXIS2_PLACEMENT_3D('',#926,#927,#928); -#926 = CARTESIAN_POINT('',(0.19111316645,-22.,-3.734519760785)); -#927 = DIRECTION('',(0.,0.,1.)); -#928 = DIRECTION('',(0.,-1.,0.)); -#929 = ADVANCED_FACE('',(#930),#941,.F.); -#930 = FACE_BOUND('',#931,.F.); -#931 = EDGE_LOOP('',(#932,#938,#939,#940)); -#932 = ORIENTED_EDGE('',*,*,#933,.T.); -#933 = EDGE_CURVE('',#22,#275,#934,.T.); -#934 = LINE('',#935,#936); -#935 = CARTESIAN_POINT('',(19.029295926024,-22.,-17.63009960955)); -#936 = VECTOR('',#937,1.); -#937 = DIRECTION('',(0.,1.,0.)); -#938 = ORIENTED_EDGE('',*,*,#282,.F.); -#939 = ORIENTED_EDGE('',*,*,#916,.F.); -#940 = ORIENTED_EDGE('',*,*,#31,.T.); -#941 = PLANE('',#942); -#942 = AXIS2_PLACEMENT_3D('',#943,#944,#945); -#943 = CARTESIAN_POINT('',(20.484588228021,-22.,-10.95643672209)); -#944 = DIRECTION('',(0.977039526026,0.,-0.213058124893)); -#945 = DIRECTION('',(-0.213058124893,0.,-0.977039526026)); -#946 = ADVANCED_FACE('',(#947),#953,.F.); -#947 = FACE_BOUND('',#948,.F.); -#948 = EDGE_LOOP('',(#949,#950,#951,#952)); -#949 = ORIENTED_EDGE('',*,*,#21,.T.); -#950 = ORIENTED_EDGE('',*,*,#860,.T.); -#951 = ORIENTED_EDGE('',*,*,#274,.F.); -#952 = ORIENTED_EDGE('',*,*,#933,.F.); -#953 = PLANE('',#954); -#954 = AXIS2_PLACEMENT_3D('',#955,#956,#957); -#955 = CARTESIAN_POINT('',(17.956031892536,-22.,-13.7484168618)); -#956 = DIRECTION('',(-0.963836182336,0.,-0.26649542889)); -#957 = DIRECTION('',(-0.26649542889,0.,0.963836182336)); -#958 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3) -GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#962)) GLOBAL_UNIT_ASSIGNED_CONTEXT -((#959,#960,#961)) REPRESENTATION_CONTEXT('Context #1', - '3D Context with UNIT and UNCERTAINTY') ); -#959 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) ); -#960 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) ); -#961 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() ); -#962 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-05),#959, - 'distance_accuracy_value','confusion accuracy'); -#963 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7)); -ENDSEC; -END-ISO-10303-21; diff --git a/docs/CAD/design/mate-connectors/BearConnector_Female.step b/docs/CAD/design/mate-connectors/BearConnector_Female.step deleted file mode 100644 index 18e31c4b47..0000000000 --- a/docs/CAD/design/mate-connectors/BearConnector_Female.step +++ /dev/null @@ -1,1173 +0,0 @@ -ISO-10303-21; -HEADER; -FILE_DESCRIPTION(('FreeCAD Model'),'2;1'); -FILE_NAME('Open CASCADE Shape Model','2026-08-05T10:54:26',('FreeCAD'),( - 'FreeCAD'),'Open CASCADE STEP processor 7.8','FreeCAD','Unknown'); -FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }')); -ENDSEC; -DATA; -#1 = APPLICATION_PROTOCOL_DEFINITION('international standard', - 'automotive_design',2000,#2); -#2 = APPLICATION_CONTEXT( - 'core data for automotive mechanical design processes'); -#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10); -#4 = PRODUCT_DEFINITION_SHAPE('','',#5); -#5 = PRODUCT_DEFINITION('design','',#6,#9); -#6 = PRODUCT_DEFINITION_FORMATION('','',#7); -#7 = PRODUCT('Open CASCADE STEP translator 7.8 1', - 'Open CASCADE STEP translator 7.8 1','',(#8)); -#8 = PRODUCT_CONTEXT('',#2,'mechanical'); -#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design'); -#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#15),#1118); -#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14); -#12 = CARTESIAN_POINT('',(0.,0.,0.)); -#13 = DIRECTION('',(0.,0.,1.)); -#14 = DIRECTION('',(1.,0.,-0.)); -#15 = MANIFOLD_SOLID_BREP('',#16); -#16 = CLOSED_SHELL('',(#17,#57,#222,#246,#270,#287,#299,#330,#354,#378, - #402,#426,#451,#475,#500,#524,#548,#573,#597,#622,#646,#670,#687, - #817,#848,#872,#896,#920,#944,#961,#986,#1017,#1033,#1050,#1061, - #1086,#1100,#1109)); -#17 = ADVANCED_FACE('',(#18),#52,.F.); -#18 = FACE_BOUND('',#19,.F.); -#19 = EDGE_LOOP('',(#20,#30,#38,#46)); -#20 = ORIENTED_EDGE('',*,*,#21,.F.); -#21 = EDGE_CURVE('',#22,#24,#26,.T.); -#22 = VERTEX_POINT('',#23); -#23 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#24 = VERTEX_POINT('',#25); -#25 = CARTESIAN_POINT('',(-45.46495478095,0.,4.2)); -#26 = LINE('',#27,#28); -#27 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#28 = VECTOR('',#29,1.); -#29 = DIRECTION('',(0.,0.,1.)); -#30 = ORIENTED_EDGE('',*,*,#31,.T.); -#31 = EDGE_CURVE('',#22,#32,#34,.T.); -#32 = VERTEX_POINT('',#33); -#33 = CARTESIAN_POINT('',(-45.46495478095,20.8572529153,-70.97315781796) - ); -#34 = LINE('',#35,#36); -#35 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#36 = VECTOR('',#37,1.); -#37 = DIRECTION('',(0.,1.,0.)); -#38 = ORIENTED_EDGE('',*,*,#39,.T.); -#39 = EDGE_CURVE('',#32,#40,#42,.T.); -#40 = VERTEX_POINT('',#41); -#41 = CARTESIAN_POINT('',(-45.46495478095,20.8572529153,4.2)); -#42 = LINE('',#43,#44); -#43 = CARTESIAN_POINT('',(-45.46495478095,20.8572529153,-70.97315781796) - ); -#44 = VECTOR('',#45,1.); -#45 = DIRECTION('',(0.,0.,1.)); -#46 = ORIENTED_EDGE('',*,*,#47,.F.); -#47 = EDGE_CURVE('',#24,#40,#48,.T.); -#48 = LINE('',#49,#50); -#49 = CARTESIAN_POINT('',(-45.46495478095,0.,4.2)); -#50 = VECTOR('',#51,1.); -#51 = DIRECTION('',(0.,1.,0.)); -#52 = PLANE('',#53); -#53 = AXIS2_PLACEMENT_3D('',#54,#55,#56); -#54 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#55 = DIRECTION('',(1.,0.,0.)); -#56 = DIRECTION('',(0.,0.,1.)); -#57 = ADVANCED_FACE('',(#58,#83),#217,.F.); -#58 = FACE_BOUND('',#59,.F.); -#59 = EDGE_LOOP('',(#60,#68,#69,#77)); -#60 = ORIENTED_EDGE('',*,*,#61,.F.); -#61 = EDGE_CURVE('',#22,#62,#64,.T.); -#62 = VERTEX_POINT('',#63); -#63 = CARTESIAN_POINT('',(46.298608189024,0.,-70.97315781796)); -#64 = LINE('',#65,#66); -#65 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#66 = VECTOR('',#67,1.); -#67 = DIRECTION('',(1.,0.,0.)); -#68 = ORIENTED_EDGE('',*,*,#21,.T.); -#69 = ORIENTED_EDGE('',*,*,#70,.T.); -#70 = EDGE_CURVE('',#24,#71,#73,.T.); -#71 = VERTEX_POINT('',#72); -#72 = CARTESIAN_POINT('',(46.298608189024,0.,4.2)); -#73 = LINE('',#74,#75); -#74 = CARTESIAN_POINT('',(-45.46495478095,0.,4.2)); -#75 = VECTOR('',#76,1.); -#76 = DIRECTION('',(1.,0.,0.)); -#77 = ORIENTED_EDGE('',*,*,#78,.F.); -#78 = EDGE_CURVE('',#62,#71,#79,.T.); -#79 = LINE('',#80,#81); -#80 = CARTESIAN_POINT('',(46.298608189024,0.,-70.97315781796)); -#81 = VECTOR('',#82,1.); -#82 = DIRECTION('',(0.,0.,1.)); -#83 = FACE_BOUND('',#84,.F.); -#84 = EDGE_LOOP('',(#85,#95,#103,#111,#119,#128,#136,#145,#153,#161,#170 - ,#178,#187,#195,#203,#211)); -#85 = ORIENTED_EDGE('',*,*,#86,.T.); -#86 = EDGE_CURVE('',#87,#89,#91,.T.); -#87 = VERTEX_POINT('',#88); -#88 = CARTESIAN_POINT('',(32.703857168398,0.,-60.78483712899)); -#89 = VERTEX_POINT('',#90); -#90 = CARTESIAN_POINT('',(26.938753236523,0.,-55.20570756731)); -#91 = LINE('',#92,#93); -#92 = CARTESIAN_POINT('',(13.567306766147,0.,-42.26560567724)); -#93 = VECTOR('',#94,1.); -#94 = DIRECTION('',(-0.718602345805,0.,0.695421216677)); -#95 = ORIENTED_EDGE('',*,*,#96,.T.); -#96 = EDGE_CURVE('',#89,#97,#99,.T.); -#97 = VERTEX_POINT('',#98); -#98 = CARTESIAN_POINT('',(42.298608189024,0.,-7.015765476549)); -#99 = LINE('',#100,#101); -#100 = CARTESIAN_POINT('',(25.140315874087,-4.440892098501E-16, - -60.84811712245)); -#101 = VECTOR('',#102,1.); -#102 = DIRECTION('',(0.30368282823,0.,0.952773183837)); -#103 = ORIENTED_EDGE('',*,*,#104,.T.); -#104 = EDGE_CURVE('',#97,#105,#107,.T.); -#105 = VERTEX_POINT('',#106); -#106 = CARTESIAN_POINT('',(35.082842712475,0.,0.2)); -#107 = LINE('',#108,#109); -#108 = CARTESIAN_POINT('',(34.536239068456,0.,0.746603644019)); -#109 = VECTOR('',#110,1.); -#110 = DIRECTION('',(-0.707106781187,0.,0.707106781187)); -#111 = ORIENTED_EDGE('',*,*,#112,.F.); -#112 = EDGE_CURVE('',#113,#105,#115,.T.); -#113 = VERTEX_POINT('',#114); -#114 = CARTESIAN_POINT('',(-32.57517518159,0.,0.2)); -#115 = LINE('',#116,#117); -#116 = CARTESIAN_POINT('',(-30.87627118587,0.,0.2)); -#117 = VECTOR('',#118,1.); -#118 = DIRECTION('',(1.,0.,0.)); -#119 = ORIENTED_EDGE('',*,*,#120,.T.); -#120 = EDGE_CURVE('',#113,#121,#123,.T.); -#121 = VERTEX_POINT('',#122); -#122 = CARTESIAN_POINT('',(-36.85603142851,0.,-1.573188716044)); -#123 = CIRCLE('',#124,6.054044962965); -#124 = AXIS2_PLACEMENT_3D('',#125,#126,#127); -#125 = CARTESIAN_POINT('',(-32.57517518159,0.,-5.854044962965)); -#126 = DIRECTION('',(0.,-1.,0.)); -#127 = DIRECTION('',(-1.,0.,0.)); -#128 = ORIENTED_EDGE('',*,*,#129,.F.); -#129 = EDGE_CURVE('',#130,#121,#132,.T.); -#130 = VERTEX_POINT('',#131); -#131 = CARTESIAN_POINT('',(-39.69176606491,0.,-4.408923352436)); -#132 = LINE('',#133,#134); -#133 = CARTESIAN_POINT('',(-57.0671882012,0.,-21.78434548873)); -#134 = VECTOR('',#135,1.); -#135 = DIRECTION('',(0.707106781187,0.,0.707106781187)); -#136 = ORIENTED_EDGE('',*,*,#137,.T.); -#137 = EDGE_CURVE('',#130,#138,#140,.T.); -#138 = VERTEX_POINT('',#139); -#139 = CARTESIAN_POINT('',(-41.17904151244,0.,-10.52828909594)); -#140 = CIRCLE('',#141,6.054044962965); -#141 = AXIS2_PLACEMENT_3D('',#142,#143,#144); -#142 = CARTESIAN_POINT('',(-35.41090981799,0.,-8.689779599357)); -#143 = DIRECTION('',(0.,-1.,0.)); -#144 = DIRECTION('',(-1.,0.,0.)); -#145 = ORIENTED_EDGE('',*,*,#146,.F.); -#146 = EDGE_CURVE('',#147,#138,#149,.T.); -#147 = VERTEX_POINT('',#148); -#148 = CARTESIAN_POINT('',(-26.93875323652,0.,-55.20570756731)); -#149 = LINE('',#150,#151); -#150 = CARTESIAN_POINT('',(-29.06259699304,-4.440892098501E-16, - -48.54236940733)); -#151 = VECTOR('',#152,1.); -#152 = DIRECTION('',(-0.30368282823,0.,0.952773183837)); -#153 = ORIENTED_EDGE('',*,*,#154,.F.); -#154 = EDGE_CURVE('',#155,#147,#157,.T.); -#155 = VERTEX_POINT('',#156); -#156 = CARTESIAN_POINT('',(-28.96712497021,0.,-57.16864680227)); -#157 = LINE('',#158,#159); -#158 = CARTESIAN_POINT('',(-36.14667143517,0.,-64.11659091489)); -#159 = VECTOR('',#160,1.); -#160 = DIRECTION('',(0.718602345805,0.,0.695421216677)); -#161 = ORIENTED_EDGE('',*,*,#162,.T.); -#162 = EDGE_CURVE('',#155,#163,#165,.T.); -#163 = VERTEX_POINT('',#164); -#164 = CARTESIAN_POINT('',(-29.08766684168,0.,-64.52156932717)); -#165 = CIRCLE('',#166,5.2); -#166 = AXIS2_PLACEMENT_3D('',#167,#168,#169); -#167 = CARTESIAN_POINT('',(-25.35093464349,0.,-60.90537900045)); -#168 = DIRECTION('',(0.,-1.,0.)); -#169 = DIRECTION('',(-1.,0.,0.)); -#170 = ORIENTED_EDGE('',*,*,#171,.F.); -#171 = EDGE_CURVE('',#172,#163,#174,.T.); -#172 = VERTEX_POINT('',#173); -#173 = CARTESIAN_POINT('',(-27.86158468659,0.,-65.78852163128)); -#174 = LINE('',#175,#176); -#175 = CARTESIAN_POINT('',(-31.12923147938,0.,-62.41195129628)); -#176 = VECTOR('',#177,1.); -#177 = DIRECTION('',(-0.695421216677,0.,0.718602345805)); -#178 = ORIENTED_EDGE('',*,*,#179,.T.); -#179 = EDGE_CURVE('',#172,#180,#182,.T.); -#180 = VERTEX_POINT('',#181); -#181 = CARTESIAN_POINT('',(-25.53052705686,0.,-65.82673637491)); -#182 = CIRCLE('',#183,1.648528137424); -#183 = AXIS2_PLACEMENT_3D('',#184,#185,#186); -#184 = CARTESIAN_POINT('',(-26.67694849991,0.,-64.64210018823)); -#185 = DIRECTION('',(0.,-1.,0.)); -#186 = DIRECTION('',(-1.,0.,0.)); -#187 = ORIENTED_EDGE('',*,*,#188,.F.); -#188 = EDGE_CURVE('',#189,#180,#191,.T.); -#189 = VERTEX_POINT('',#190); -#190 = CARTESIAN_POINT('',(-20.53201900137,0.,-60.98947335481)); -#191 = LINE('',#192,#193); -#192 = CARTESIAN_POINT('',(-30.69923804215,0.,-70.82871181101)); -#193 = VECTOR('',#194,1.); -#194 = DIRECTION('',(-0.718602345805,0.,-0.695421216677)); -#195 = ORIENTED_EDGE('',*,*,#196,.T.); -#196 = EDGE_CURVE('',#189,#197,#199,.T.); -#197 = VERTEX_POINT('',#198); -#198 = CARTESIAN_POINT('',(20.532019001374,0.,-60.98947335481)); -#199 = LINE('',#200,#201); -#200 = CARTESIAN_POINT('',(-17.57924046663,0.,-60.98947335481)); -#201 = VECTOR('',#202,1.); -#202 = DIRECTION('',(1.,0.,0.)); -#203 = ORIENTED_EDGE('',*,*,#204,.T.); -#204 = EDGE_CURVE('',#197,#205,#207,.T.); -#205 = VERTEX_POINT('',#206); -#206 = CARTESIAN_POINT('',(26.715163243538,0.,-66.97315781796)); -#207 = LINE('',#208,#209); -#208 = CARTESIAN_POINT('',(7.481849370247,-4.440892098501E-16, - -48.36028435244)); -#209 = VECTOR('',#210,1.); -#210 = DIRECTION('',(0.718602345805,0.,-0.695421216677)); -#211 = ORIENTED_EDGE('',*,*,#212,.T.); -#212 = EDGE_CURVE('',#205,#87,#213,.T.); -#213 = LINE('',#214,#215); -#214 = CARTESIAN_POINT('',(9.75933652063,0.,-84.4941890519)); -#215 = VECTOR('',#216,1.); -#216 = DIRECTION('',(0.695421216677,0.,0.718602345805)); -#217 = PLANE('',#218); -#218 = AXIS2_PLACEMENT_3D('',#219,#220,#221); -#219 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#220 = DIRECTION('',(0.,1.,0.)); -#221 = DIRECTION('',(0.,0.,1.)); -#222 = ADVANCED_FACE('',(#223),#241,.T.); -#223 = FACE_BOUND('',#224,.T.); -#224 = EDGE_LOOP('',(#225,#226,#227,#235)); -#225 = ORIENTED_EDGE('',*,*,#47,.F.); -#226 = ORIENTED_EDGE('',*,*,#70,.T.); -#227 = ORIENTED_EDGE('',*,*,#228,.T.); -#228 = EDGE_CURVE('',#71,#229,#231,.T.); -#229 = VERTEX_POINT('',#230); -#230 = CARTESIAN_POINT('',(46.298608189024,20.8572529153,4.2)); -#231 = LINE('',#232,#233); -#232 = CARTESIAN_POINT('',(46.298608189024,0.,4.2)); -#233 = VECTOR('',#234,1.); -#234 = DIRECTION('',(0.,1.,0.)); -#235 = ORIENTED_EDGE('',*,*,#236,.F.); -#236 = EDGE_CURVE('',#40,#229,#237,.T.); -#237 = LINE('',#238,#239); -#238 = CARTESIAN_POINT('',(-45.46495478095,20.8572529153,4.2)); -#239 = VECTOR('',#240,1.); -#240 = DIRECTION('',(1.,0.,0.)); -#241 = PLANE('',#242); -#242 = AXIS2_PLACEMENT_3D('',#243,#244,#245); -#243 = CARTESIAN_POINT('',(-45.46495478095,0.,4.2)); -#244 = DIRECTION('',(0.,0.,1.)); -#245 = DIRECTION('',(1.,0.,0.)); -#246 = ADVANCED_FACE('',(#247),#265,.T.); -#247 = FACE_BOUND('',#248,.T.); -#248 = EDGE_LOOP('',(#249,#257,#258,#259)); -#249 = ORIENTED_EDGE('',*,*,#250,.F.); -#250 = EDGE_CURVE('',#32,#251,#253,.T.); -#251 = VERTEX_POINT('',#252); -#252 = CARTESIAN_POINT('',(46.298608189024,20.8572529153,-70.97315781796 - )); -#253 = LINE('',#254,#255); -#254 = CARTESIAN_POINT('',(-45.46495478095,20.8572529153,-70.97315781796 - )); -#255 = VECTOR('',#256,1.); -#256 = DIRECTION('',(1.,0.,0.)); -#257 = ORIENTED_EDGE('',*,*,#39,.T.); -#258 = ORIENTED_EDGE('',*,*,#236,.T.); -#259 = ORIENTED_EDGE('',*,*,#260,.F.); -#260 = EDGE_CURVE('',#251,#229,#261,.T.); -#261 = LINE('',#262,#263); -#262 = CARTESIAN_POINT('',(46.298608189024,20.8572529153,-70.97315781796 - )); -#263 = VECTOR('',#264,1.); -#264 = DIRECTION('',(0.,0.,1.)); -#265 = PLANE('',#266); -#266 = AXIS2_PLACEMENT_3D('',#267,#268,#269); -#267 = CARTESIAN_POINT('',(-45.46495478095,20.8572529153,-70.97315781796 - )); -#268 = DIRECTION('',(0.,1.,0.)); -#269 = DIRECTION('',(0.,0.,1.)); -#270 = ADVANCED_FACE('',(#271),#282,.F.); -#271 = FACE_BOUND('',#272,.F.); -#272 = EDGE_LOOP('',(#273,#274,#275,#281)); -#273 = ORIENTED_EDGE('',*,*,#31,.F.); -#274 = ORIENTED_EDGE('',*,*,#61,.T.); -#275 = ORIENTED_EDGE('',*,*,#276,.T.); -#276 = EDGE_CURVE('',#62,#251,#277,.T.); -#277 = LINE('',#278,#279); -#278 = CARTESIAN_POINT('',(46.298608189024,0.,-70.97315781796)); -#279 = VECTOR('',#280,1.); -#280 = DIRECTION('',(0.,1.,0.)); -#281 = ORIENTED_EDGE('',*,*,#250,.F.); -#282 = PLANE('',#283); -#283 = AXIS2_PLACEMENT_3D('',#284,#285,#286); -#284 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#285 = DIRECTION('',(0.,0.,1.)); -#286 = DIRECTION('',(1.,0.,0.)); -#287 = ADVANCED_FACE('',(#288),#294,.T.); -#288 = FACE_BOUND('',#289,.T.); -#289 = EDGE_LOOP('',(#290,#291,#292,#293)); -#290 = ORIENTED_EDGE('',*,*,#78,.F.); -#291 = ORIENTED_EDGE('',*,*,#276,.T.); -#292 = ORIENTED_EDGE('',*,*,#260,.T.); -#293 = ORIENTED_EDGE('',*,*,#228,.F.); -#294 = PLANE('',#295); -#295 = AXIS2_PLACEMENT_3D('',#296,#297,#298); -#296 = CARTESIAN_POINT('',(46.298608189024,0.,-70.97315781796)); -#297 = DIRECTION('',(1.,0.,0.)); -#298 = DIRECTION('',(0.,0.,1.)); -#299 = ADVANCED_FACE('',(#300),#325,.T.); -#300 = FACE_BOUND('',#301,.T.); -#301 = EDGE_LOOP('',(#302,#310,#311,#319)); -#302 = ORIENTED_EDGE('',*,*,#303,.F.); -#303 = EDGE_CURVE('',#87,#304,#306,.T.); -#304 = VERTEX_POINT('',#305); -#305 = CARTESIAN_POINT('',(32.703857168398,3.2,-60.78483712899)); -#306 = LINE('',#307,#308); -#307 = CARTESIAN_POINT('',(32.703857168398,3.,-60.78483712899)); -#308 = VECTOR('',#309,1.); -#309 = DIRECTION('',(0.,1.,0.)); -#310 = ORIENTED_EDGE('',*,*,#86,.T.); -#311 = ORIENTED_EDGE('',*,*,#312,.F.); -#312 = EDGE_CURVE('',#313,#89,#315,.T.); -#313 = VERTEX_POINT('',#314); -#314 = CARTESIAN_POINT('',(26.938753236523,3.2,-55.20570756731)); -#315 = LINE('',#316,#317); -#316 = CARTESIAN_POINT('',(26.938753236523,3.,-55.20570756731)); -#317 = VECTOR('',#318,1.); -#318 = DIRECTION('',(0.,-1.,0.)); -#319 = ORIENTED_EDGE('',*,*,#320,.F.); -#320 = EDGE_CURVE('',#304,#313,#321,.T.); -#321 = LINE('',#322,#323); -#322 = CARTESIAN_POINT('',(16.008242280412,3.2,-44.62779994931)); -#323 = VECTOR('',#324,1.); -#324 = DIRECTION('',(-0.718602345805,0.,0.695421216677)); -#325 = PLANE('',#326); -#326 = AXIS2_PLACEMENT_3D('',#327,#328,#329); -#327 = CARTESIAN_POINT('',(29.704873980143,3.,-57.88259703786)); -#328 = DIRECTION('',(-0.695421216677,0.,-0.718602345805)); -#329 = DIRECTION('',(-0.718602345805,0.,0.695421216677)); -#330 = ADVANCED_FACE('',(#331),#349,.T.); -#331 = FACE_BOUND('',#332,.T.); -#332 = EDGE_LOOP('',(#333,#341,#342,#343)); -#333 = ORIENTED_EDGE('',*,*,#334,.F.); -#334 = EDGE_CURVE('',#205,#335,#337,.T.); -#335 = VERTEX_POINT('',#336); -#336 = CARTESIAN_POINT('',(26.715163243538,3.2,-66.97315781796)); -#337 = LINE('',#338,#339); -#338 = CARTESIAN_POINT('',(26.715163243538,3.,-66.97315781796)); -#339 = VECTOR('',#340,1.); -#340 = DIRECTION('',(0.,1.,0.)); -#341 = ORIENTED_EDGE('',*,*,#212,.T.); -#342 = ORIENTED_EDGE('',*,*,#303,.T.); -#343 = ORIENTED_EDGE('',*,*,#344,.F.); -#344 = EDGE_CURVE('',#335,#304,#345,.T.); -#345 = LINE('',#346,#347); -#346 = CARTESIAN_POINT('',(30.25240665457,3.2,-63.31800414721)); -#347 = VECTOR('',#348,1.); -#348 = DIRECTION('',(0.695421216677,0.,0.718602345805)); -#349 = PLANE('',#350); -#350 = AXIS2_PLACEMENT_3D('',#351,#352,#353); -#351 = CARTESIAN_POINT('',(29.709510205968,3.,-63.87899747347)); -#352 = DIRECTION('',(-0.718602345805,0.,0.695421216677)); -#353 = DIRECTION('',(0.695421216677,0.,0.718602345805)); -#354 = ADVANCED_FACE('',(#355),#373,.T.); -#355 = FACE_BOUND('',#356,.T.); -#356 = EDGE_LOOP('',(#357,#365,#371,#372)); -#357 = ORIENTED_EDGE('',*,*,#358,.F.); -#358 = EDGE_CURVE('',#359,#335,#361,.T.); -#359 = VERTEX_POINT('',#360); -#360 = CARTESIAN_POINT('',(20.532019001374,3.2,-60.98947335481)); -#361 = LINE('',#362,#363); -#362 = CARTESIAN_POINT('',(9.922784884512,3.2,-50.72247862452)); -#363 = VECTOR('',#364,1.); -#364 = DIRECTION('',(0.718602345805,0.,-0.695421216677)); -#365 = ORIENTED_EDGE('',*,*,#366,.T.); -#366 = EDGE_CURVE('',#359,#197,#367,.T.); -#367 = LINE('',#368,#369); -#368 = CARTESIAN_POINT('',(20.532019001374,3.,-60.98947335481)); -#369 = VECTOR('',#370,1.); -#370 = DIRECTION('',(0.,-1.,0.)); -#371 = ORIENTED_EDGE('',*,*,#204,.T.); -#372 = ORIENTED_EDGE('',*,*,#334,.T.); -#373 = PLANE('',#374); -#374 = AXIS2_PLACEMENT_3D('',#375,#376,#377); -#375 = CARTESIAN_POINT('',(23.522653113203,3.,-63.8836336993)); -#376 = DIRECTION('',(0.695421216677,0.,0.718602345805)); -#377 = DIRECTION('',(0.718602345805,0.,-0.695421216677)); -#378 = ADVANCED_FACE('',(#379),#397,.T.); -#379 = FACE_BOUND('',#380,.T.); -#380 = EDGE_LOOP('',(#381,#389,#390,#391)); -#381 = ORIENTED_EDGE('',*,*,#382,.F.); -#382 = EDGE_CURVE('',#189,#383,#385,.T.); -#383 = VERTEX_POINT('',#384); -#384 = CARTESIAN_POINT('',(-20.53201900137,3.2,-60.98947335481)); -#385 = LINE('',#386,#387); -#386 = CARTESIAN_POINT('',(-20.53201900137,3.,-60.98947335481)); -#387 = VECTOR('',#388,1.); -#388 = DIRECTION('',(0.,1.,0.)); -#389 = ORIENTED_EDGE('',*,*,#196,.T.); -#390 = ORIENTED_EDGE('',*,*,#366,.F.); -#391 = ORIENTED_EDGE('',*,*,#392,.F.); -#392 = EDGE_CURVE('',#383,#359,#393,.T.); -#393 = LINE('',#394,#395); -#394 = CARTESIAN_POINT('',(5.354765181569,3.2,-60.98947335481)); -#395 = VECTOR('',#396,1.); -#396 = DIRECTION('',(1.,0.,0.)); -#397 = PLANE('',#398); -#398 = AXIS2_PLACEMENT_3D('',#399,#400,#401); -#399 = CARTESIAN_POINT('',(10.306473847682,3.,-60.98947335481)); -#400 = DIRECTION('',(0.,0.,1.)); -#401 = DIRECTION('',(0.,-1.,0.)); -#402 = ADVANCED_FACE('',(#403),#421,.F.); -#403 = FACE_BOUND('',#404,.F.); -#404 = EDGE_LOOP('',(#405,#413,#419,#420)); -#405 = ORIENTED_EDGE('',*,*,#406,.F.); -#406 = EDGE_CURVE('',#407,#180,#409,.T.); -#407 = VERTEX_POINT('',#408); -#408 = CARTESIAN_POINT('',(-25.53052705686,3.2,-65.82673637491)); -#409 = LINE('',#410,#411); -#410 = CARTESIAN_POINT('',(-25.53052705686,3.,-65.82673637491)); -#411 = VECTOR('',#412,1.); -#412 = DIRECTION('',(0.,-1.,0.)); -#413 = ORIENTED_EDGE('',*,*,#414,.F.); -#414 = EDGE_CURVE('',#383,#407,#415,.T.); -#415 = LINE('',#416,#417); -#416 = CARTESIAN_POINT('',(-9.454421870603,3.2,-50.26922436104)); -#417 = VECTOR('',#418,1.); -#418 = DIRECTION('',(-0.718602345805,0.,-0.695421216677)); -#419 = ORIENTED_EDGE('',*,*,#382,.F.); -#420 = ORIENTED_EDGE('',*,*,#188,.T.); -#421 = PLANE('',#422); -#422 = AXIS2_PLACEMENT_3D('',#423,#424,#425); -#423 = CARTESIAN_POINT('',(-23.00219525444,3.,-63.37996509944)); -#424 = DIRECTION('',(0.695421216677,0.,-0.718602345805)); -#425 = DIRECTION('',(-0.718602345805,0.,-0.695421216677)); -#426 = ADVANCED_FACE('',(#427),#446,.F.); -#427 = FACE_BOUND('',#428,.F.); -#428 = EDGE_LOOP('',(#429,#437,#444,#445)); -#429 = ORIENTED_EDGE('',*,*,#430,.F.); -#430 = EDGE_CURVE('',#431,#172,#433,.T.); -#431 = VERTEX_POINT('',#432); -#432 = CARTESIAN_POINT('',(-27.86158468659,3.2,-65.78852163128)); -#433 = LINE('',#434,#435); -#434 = CARTESIAN_POINT('',(-27.86158468659,3.,-65.78852163128)); -#435 = VECTOR('',#436,1.); -#436 = DIRECTION('',(0.,-1.,0.)); -#437 = ORIENTED_EDGE('',*,*,#438,.T.); -#438 = EDGE_CURVE('',#431,#407,#439,.T.); -#439 = CIRCLE('',#440,1.648528137424); -#440 = AXIS2_PLACEMENT_3D('',#441,#442,#443); -#441 = CARTESIAN_POINT('',(-26.67694849991,3.2,-64.64210018823)); -#442 = DIRECTION('',(0.,-1.,0.)); -#443 = DIRECTION('',(-1.,0.,0.)); -#444 = ORIENTED_EDGE('',*,*,#406,.T.); -#445 = ORIENTED_EDGE('',*,*,#179,.F.); -#446 = CYLINDRICAL_SURFACE('',#447,1.648528137424); -#447 = AXIS2_PLACEMENT_3D('',#448,#449,#450); -#448 = CARTESIAN_POINT('',(-26.67694849991,3.,-64.64210018823)); -#449 = DIRECTION('',(0.,-1.,0.)); -#450 = DIRECTION('',(-1.,0.,0.)); -#451 = ADVANCED_FACE('',(#452),#470,.F.); -#452 = FACE_BOUND('',#453,.F.); -#453 = EDGE_LOOP('',(#454,#462,#468,#469)); -#454 = ORIENTED_EDGE('',*,*,#455,.F.); -#455 = EDGE_CURVE('',#456,#163,#458,.T.); -#456 = VERTEX_POINT('',#457); -#457 = CARTESIAN_POINT('',(-29.08766684168,3.2,-64.52156932717)); -#458 = LINE('',#459,#460); -#459 = CARTESIAN_POINT('',(-29.08766684168,3.,-64.52156932717)); -#460 = VECTOR('',#461,1.); -#461 = DIRECTION('',(0.,-1.,0.)); -#462 = ORIENTED_EDGE('',*,*,#463,.F.); -#463 = EDGE_CURVE('',#431,#456,#464,.T.); -#464 = LINE('',#465,#466); -#465 = CARTESIAN_POINT('',(-29.44004200272,3.2,-64.15744811364)); -#466 = VECTOR('',#467,1.); -#467 = DIRECTION('',(-0.695421216677,0.,0.718602345805)); -#468 = ORIENTED_EDGE('',*,*,#430,.T.); -#469 = ORIENTED_EDGE('',*,*,#171,.T.); -#470 = PLANE('',#471); -#471 = AXIS2_PLACEMENT_3D('',#472,#473,#474); -#472 = CARTESIAN_POINT('',(-28.47462576413,3.,-65.15504547923)); -#473 = DIRECTION('',(-0.718602345805,0.,-0.695421216677)); -#474 = DIRECTION('',(-0.695421216677,0.,0.718602345805)); -#475 = ADVANCED_FACE('',(#476),#495,.F.); -#476 = FACE_BOUND('',#477,.F.); -#477 = EDGE_LOOP('',(#478,#486,#493,#494)); -#478 = ORIENTED_EDGE('',*,*,#479,.F.); -#479 = EDGE_CURVE('',#480,#155,#482,.T.); -#480 = VERTEX_POINT('',#481); -#481 = CARTESIAN_POINT('',(-28.96712497021,3.2,-57.16864680227)); -#482 = LINE('',#483,#484); -#483 = CARTESIAN_POINT('',(-28.96712497021,3.,-57.16864680227)); -#484 = VECTOR('',#485,1.); -#485 = DIRECTION('',(0.,-1.,0.)); -#486 = ORIENTED_EDGE('',*,*,#487,.T.); -#487 = EDGE_CURVE('',#480,#456,#488,.T.); -#488 = CIRCLE('',#489,5.2); -#489 = AXIS2_PLACEMENT_3D('',#490,#491,#492); -#490 = CARTESIAN_POINT('',(-25.35093464349,3.2,-60.90537900045)); -#491 = DIRECTION('',(0.,-1.,0.)); -#492 = DIRECTION('',(-1.,0.,0.)); -#493 = ORIENTED_EDGE('',*,*,#455,.T.); -#494 = ORIENTED_EDGE('',*,*,#162,.F.); -#495 = CYLINDRICAL_SURFACE('',#496,5.2); -#496 = AXIS2_PLACEMENT_3D('',#497,#498,#499); -#497 = CARTESIAN_POINT('',(-25.35093464349,3.,-60.90537900045)); -#498 = DIRECTION('',(0.,-1.,0.)); -#499 = DIRECTION('',(-1.,0.,0.)); -#500 = ADVANCED_FACE('',(#501),#519,.F.); -#501 = FACE_BOUND('',#502,.F.); -#502 = EDGE_LOOP('',(#503,#511,#517,#518)); -#503 = ORIENTED_EDGE('',*,*,#504,.F.); -#504 = EDGE_CURVE('',#505,#147,#507,.T.); -#505 = VERTEX_POINT('',#506); -#506 = CARTESIAN_POINT('',(-26.93875323652,3.2,-55.20570756731)); -#507 = LINE('',#508,#509); -#508 = CARTESIAN_POINT('',(-26.93875323652,3.,-55.20570756731)); -#509 = VECTOR('',#510,1.); -#510 = DIRECTION('',(0.,-1.,0.)); -#511 = ORIENTED_EDGE('',*,*,#512,.F.); -#512 = EDGE_CURVE('',#480,#505,#513,.T.); -#513 = LINE('',#514,#515); -#514 = CARTESIAN_POINT('',(-14.90185526362,3.2,-43.55710346492)); -#515 = VECTOR('',#516,1.); -#516 = DIRECTION('',(0.718602345805,0.,0.695421216677)); -#517 = ORIENTED_EDGE('',*,*,#479,.T.); -#518 = ORIENTED_EDGE('',*,*,#154,.T.); -#519 = PLANE('',#520); -#520 = AXIS2_PLACEMENT_3D('',#521,#522,#523); -#521 = CARTESIAN_POINT('',(-27.90836811563,3.,-56.14404399617)); -#522 = DIRECTION('',(-0.695421216677,0.,0.718602345805)); -#523 = DIRECTION('',(0.718602345805,0.,0.695421216677)); -#524 = ADVANCED_FACE('',(#525),#543,.F.); -#525 = FACE_BOUND('',#526,.F.); -#526 = EDGE_LOOP('',(#527,#535,#541,#542)); -#527 = ORIENTED_EDGE('',*,*,#528,.F.); -#528 = EDGE_CURVE('',#529,#138,#531,.T.); -#529 = VERTEX_POINT('',#530); -#530 = CARTESIAN_POINT('',(-41.17904151244,3.2,-10.52828909594)); -#531 = LINE('',#532,#533); -#532 = CARTESIAN_POINT('',(-41.17904151244,3.,-10.52828909594)); -#533 = VECTOR('',#534,1.); -#534 = DIRECTION('',(0.,-1.,0.)); -#535 = ORIENTED_EDGE('',*,*,#536,.F.); -#536 = EDGE_CURVE('',#505,#529,#537,.T.); -#537 = LINE('',#538,#539); -#538 = CARTESIAN_POINT('',(-32.39120436163,3.2,-38.09921113329)); -#539 = VECTOR('',#540,1.); -#540 = DIRECTION('',(-0.30368282823,0.,0.952773183837)); -#541 = ORIENTED_EDGE('',*,*,#504,.T.); -#542 = ORIENTED_EDGE('',*,*,#146,.T.); -#543 = PLANE('',#544); -#544 = AXIS2_PLACEMENT_3D('',#545,#546,#547); -#545 = CARTESIAN_POINT('',(-34.04006158346,3.,-32.92609366041)); -#546 = DIRECTION('',(-0.952773183837,0.,-0.30368282823)); -#547 = DIRECTION('',(-0.30368282823,0.,0.952773183837)); -#548 = ADVANCED_FACE('',(#549),#568,.F.); -#549 = FACE_BOUND('',#550,.F.); -#550 = EDGE_LOOP('',(#551,#559,#566,#567)); -#551 = ORIENTED_EDGE('',*,*,#552,.F.); -#552 = EDGE_CURVE('',#553,#130,#555,.T.); -#553 = VERTEX_POINT('',#554); -#554 = CARTESIAN_POINT('',(-39.69176606491,3.2,-4.408923352436)); -#555 = LINE('',#556,#557); -#556 = CARTESIAN_POINT('',(-39.69176606491,3.,-4.408923352436)); -#557 = VECTOR('',#558,1.); -#558 = DIRECTION('',(0.,-1.,0.)); -#559 = ORIENTED_EDGE('',*,*,#560,.T.); -#560 = EDGE_CURVE('',#553,#529,#561,.T.); -#561 = CIRCLE('',#562,6.054044962965); -#562 = AXIS2_PLACEMENT_3D('',#563,#564,#565); -#563 = CARTESIAN_POINT('',(-35.41090981799,3.2,-8.689779599357)); -#564 = DIRECTION('',(0.,-1.,0.)); -#565 = DIRECTION('',(-1.,0.,0.)); -#566 = ORIENTED_EDGE('',*,*,#528,.T.); -#567 = ORIENTED_EDGE('',*,*,#137,.F.); -#568 = CYLINDRICAL_SURFACE('',#569,6.054044962965); -#569 = AXIS2_PLACEMENT_3D('',#570,#571,#572); -#570 = CARTESIAN_POINT('',(-35.41090981799,3.,-8.689779599357)); -#571 = DIRECTION('',(0.,-1.,0.)); -#572 = DIRECTION('',(-1.,0.,0.)); -#573 = ADVANCED_FACE('',(#574),#592,.F.); -#574 = FACE_BOUND('',#575,.F.); -#575 = EDGE_LOOP('',(#576,#584,#590,#591)); -#576 = ORIENTED_EDGE('',*,*,#577,.F.); -#577 = EDGE_CURVE('',#578,#121,#580,.T.); -#578 = VERTEX_POINT('',#579); -#579 = CARTESIAN_POINT('',(-36.85603142851,3.2,-1.573188716044)); -#580 = LINE('',#581,#582); -#581 = CARTESIAN_POINT('',(-36.85603142851,3.,-1.573188716044)); -#582 = VECTOR('',#583,1.); -#583 = DIRECTION('',(0.,-1.,0.)); -#584 = ORIENTED_EDGE('',*,*,#585,.F.); -#585 = EDGE_CURVE('',#553,#578,#586,.T.); -#586 = LINE('',#587,#588); -#587 = CARTESIAN_POINT('',(-36.19319006079,3.2,-0.910347348321)); -#588 = VECTOR('',#589,1.); -#589 = DIRECTION('',(0.707106781187,0.,0.707106781187)); -#590 = ORIENTED_EDGE('',*,*,#552,.T.); -#591 = ORIENTED_EDGE('',*,*,#129,.T.); -#592 = PLANE('',#593); -#593 = AXIS2_PLACEMENT_3D('',#594,#595,#596); -#594 = CARTESIAN_POINT('',(-38.27389874671,3.,-2.99105603424)); -#595 = DIRECTION('',(-0.707106781187,0.,0.707106781187)); -#596 = DIRECTION('',(0.707106781187,0.,0.707106781187)); -#597 = ADVANCED_FACE('',(#598),#617,.F.); -#598 = FACE_BOUND('',#599,.F.); -#599 = EDGE_LOOP('',(#600,#608,#615,#616)); -#600 = ORIENTED_EDGE('',*,*,#601,.F.); -#601 = EDGE_CURVE('',#602,#113,#604,.T.); -#602 = VERTEX_POINT('',#603); -#603 = CARTESIAN_POINT('',(-32.57517518159,3.2,0.2)); -#604 = LINE('',#605,#606); -#605 = CARTESIAN_POINT('',(-32.57517518159,3.,0.2)); -#606 = VECTOR('',#607,1.); -#607 = DIRECTION('',(0.,-1.,0.)); -#608 = ORIENTED_EDGE('',*,*,#609,.T.); -#609 = EDGE_CURVE('',#602,#578,#610,.T.); -#610 = CIRCLE('',#611,6.054044962965); -#611 = AXIS2_PLACEMENT_3D('',#612,#613,#614); -#612 = CARTESIAN_POINT('',(-32.57517518159,3.2,-5.854044962965)); -#613 = DIRECTION('',(0.,-1.,0.)); -#614 = DIRECTION('',(-1.,0.,0.)); -#615 = ORIENTED_EDGE('',*,*,#577,.T.); -#616 = ORIENTED_EDGE('',*,*,#120,.F.); -#617 = CYLINDRICAL_SURFACE('',#618,6.054044962965); -#618 = AXIS2_PLACEMENT_3D('',#619,#620,#621); -#619 = CARTESIAN_POINT('',(-32.57517518159,3.,-5.854044962965)); -#620 = DIRECTION('',(0.,-1.,0.)); -#621 = DIRECTION('',(-1.,0.,0.)); -#622 = ADVANCED_FACE('',(#623),#641,.F.); -#623 = FACE_BOUND('',#624,.F.); -#624 = EDGE_LOOP('',(#625,#633,#639,#640)); -#625 = ORIENTED_EDGE('',*,*,#626,.F.); -#626 = EDGE_CURVE('',#627,#105,#629,.T.); -#627 = VERTEX_POINT('',#628); -#628 = CARTESIAN_POINT('',(35.082842712475,3.2,0.2)); -#629 = LINE('',#630,#631); -#630 = CARTESIAN_POINT('',(35.082842712475,3.,0.2)); -#631 = VECTOR('',#632,1.); -#632 = DIRECTION('',(0.,-1.,0.)); -#633 = ORIENTED_EDGE('',*,*,#634,.F.); -#634 = EDGE_CURVE('',#602,#627,#635,.T.); -#635 = LINE('',#636,#637); -#636 = CARTESIAN_POINT('',(-7.942265537672,3.2,0.2)); -#637 = VECTOR('',#638,1.); -#638 = DIRECTION('',(1.,0.,0.)); -#639 = ORIENTED_EDGE('',*,*,#601,.T.); -#640 = ORIENTED_EDGE('',*,*,#112,.T.); -#641 = PLANE('',#642); -#642 = AXIS2_PLACEMENT_3D('',#643,#644,#645); -#643 = CARTESIAN_POINT('',(-16.28758759079,3.,0.2)); -#644 = DIRECTION('',(0.,0.,1.)); -#645 = DIRECTION('',(0.,-1.,0.)); -#646 = ADVANCED_FACE('',(#647),#665,.T.); -#647 = FACE_BOUND('',#648,.T.); -#648 = EDGE_LOOP('',(#649,#657,#658,#659)); -#649 = ORIENTED_EDGE('',*,*,#650,.T.); -#650 = EDGE_CURVE('',#651,#97,#653,.T.); -#651 = VERTEX_POINT('',#652); -#652 = CARTESIAN_POINT('',(42.298608189024,3.2,-7.015765476549)); -#653 = LINE('',#654,#655); -#654 = CARTESIAN_POINT('',(42.298608189024,3.,-7.015765476549)); -#655 = VECTOR('',#656,1.); -#656 = DIRECTION('',(0.,-1.,0.)); -#657 = ORIENTED_EDGE('',*,*,#104,.T.); -#658 = ORIENTED_EDGE('',*,*,#626,.F.); -#659 = ORIENTED_EDGE('',*,*,#660,.F.); -#660 = EDGE_CURVE('',#651,#627,#661,.T.); -#661 = LINE('',#662,#663); -#662 = CARTESIAN_POINT('',(36.596246576251,3.2,-1.313403863776)); -#663 = VECTOR('',#664,1.); -#664 = DIRECTION('',(-0.707106781187,0.,0.707106781187)); -#665 = PLANE('',#666); -#666 = AXIS2_PLACEMENT_3D('',#667,#668,#669); -#667 = CARTESIAN_POINT('',(38.67695526217,3.,-3.394112549695)); -#668 = DIRECTION('',(-0.707106781187,0.,-0.707106781187)); -#669 = DIRECTION('',(-0.707106781187,0.,0.707106781187)); -#670 = ADVANCED_FACE('',(#671),#682,.T.); -#671 = FACE_BOUND('',#672,.T.); -#672 = EDGE_LOOP('',(#673,#679,#680,#681)); -#673 = ORIENTED_EDGE('',*,*,#674,.F.); -#674 = EDGE_CURVE('',#313,#651,#675,.T.); -#675 = LINE('',#676,#677); -#676 = CARTESIAN_POINT('',(32.699020781567,3.2,-37.13346923684)); -#677 = VECTOR('',#678,1.); -#678 = DIRECTION('',(0.30368282823,0.,0.952773183837)); -#679 = ORIENTED_EDGE('',*,*,#312,.T.); -#680 = ORIENTED_EDGE('',*,*,#96,.T.); -#681 = ORIENTED_EDGE('',*,*,#650,.F.); -#682 = PLANE('',#683); -#683 = AXIS2_PLACEMENT_3D('',#684,#685,#686); -#684 = CARTESIAN_POINT('',(34.581352051556,3.,-31.22785130119)); -#685 = DIRECTION('',(-0.952773183837,0.,0.30368282823)); -#686 = DIRECTION('',(0.30368282823,0.,0.952773183837)); -#687 = ADVANCED_FACE('',(#688,#738,#749,#767,#801),#812,.F.); -#688 = FACE_BOUND('',#689,.F.); -#689 = EDGE_LOOP('',(#690,#700,#708,#716,#724,#732)); -#690 = ORIENTED_EDGE('',*,*,#691,.F.); -#691 = EDGE_CURVE('',#692,#694,#696,.T.); -#692 = VERTEX_POINT('',#693); -#693 = CARTESIAN_POINT('',(16.626582997737,3.2,-8.940188245231)); -#694 = VERTEX_POINT('',#695); -#695 = CARTESIAN_POINT('',(4.383041634064E-04,3.2,-8.903751615529)); -#696 = LINE('',#697,#698); -#697 = CARTESIAN_POINT('',(4.347099726942,3.2,-8.913277437397)); -#698 = VECTOR('',#699,1.); -#699 = DIRECTION('',(-0.999997598615,0.,2.191520817069E-03)); -#700 = ORIENTED_EDGE('',*,*,#701,.F.); -#701 = EDGE_CURVE('',#702,#692,#704,.T.); -#702 = VERTEX_POINT('',#703); -#703 = CARTESIAN_POINT('',(19.029295926024,3.2,-17.63009960955)); -#704 = LINE('',#705,#706); -#705 = CARTESIAN_POINT('',(19.849519003668,3.2,-20.59660707692)); -#706 = VECTOR('',#707,1.); -#707 = DIRECTION('',(-0.26649542889,0.,0.963836182336)); -#708 = ORIENTED_EDGE('',*,*,#709,.F.); -#709 = EDGE_CURVE('',#710,#702,#712,.T.); -#710 = VERTEX_POINT('',#711); -#711 = CARTESIAN_POINT('',(22.059435554995,3.2,-3.734519760785)); -#712 = LINE('',#713,#714); -#713 = CARTESIAN_POINT('',(17.698510515043,3.2,-23.73280021221)); -#714 = VECTOR('',#715,1.); -#715 = DIRECTION('',(-0.213058124893,0.,-0.977039526026)); -#716 = ORIENTED_EDGE('',*,*,#717,.F.); -#717 = EDGE_CURVE('',#718,#710,#720,.T.); -#718 = VERTEX_POINT('',#719); -#719 = CARTESIAN_POINT('',(-21.72552223146,3.2,-3.734519760785)); -#720 = LINE('',#721,#722); -#721 = CARTESIAN_POINT('',(0.297084840953,3.2,-3.734519760785)); -#722 = VECTOR('',#723,1.); -#723 = DIRECTION('',(1.,0.,0.)); -#724 = ORIENTED_EDGE('',*,*,#725,.F.); -#725 = EDGE_CURVE('',#726,#718,#728,.T.); -#726 = VERTEX_POINT('',#727); -#727 = CARTESIAN_POINT('',(-21.72552223146,3.2,-8.903751135252)); -#728 = LINE('',#729,#730); -#729 = CARTESIAN_POINT('',(-21.72552223146,3.2,-19.83215600037)); -#730 = VECTOR('',#731,1.); -#731 = DIRECTION('',(0.,0.,1.)); -#732 = ORIENTED_EDGE('',*,*,#733,.F.); -#733 = EDGE_CURVE('',#694,#726,#734,.T.); -#734 = LINE('',#735,#736); -#735 = CARTESIAN_POINT('',(-5.279852300138,3.2,-8.903751135252)); -#736 = VECTOR('',#737,1.); -#737 = DIRECTION('',(-1.,0.,0.)); -#738 = FACE_BOUND('',#739,.F.); -#739 = EDGE_LOOP('',(#740)); -#740 = ORIENTED_EDGE('',*,*,#741,.F.); -#741 = EDGE_CURVE('',#742,#742,#744,.T.); -#742 = VERTEX_POINT('',#743); -#743 = CARTESIAN_POINT('',(21.163799345768,3.2,-48.00951684793)); -#744 = CIRCLE('',#745,4.735522705283); -#745 = AXIS2_PLACEMENT_3D('',#746,#747,#748); -#746 = CARTESIAN_POINT('',(16.428276640485,3.2,-48.00951684793)); -#747 = DIRECTION('',(-0.,1.,0.)); -#748 = DIRECTION('',(1.,0.,0.)); -#749 = FACE_BOUND('',#750,.F.); -#750 = EDGE_LOOP('',(#751,#752,#753,#754,#755,#756,#757,#758,#759,#760, - #761,#762,#763,#764,#765,#766)); -#751 = ORIENTED_EDGE('',*,*,#536,.T.); -#752 = ORIENTED_EDGE('',*,*,#560,.F.); -#753 = ORIENTED_EDGE('',*,*,#585,.T.); -#754 = ORIENTED_EDGE('',*,*,#609,.F.); -#755 = ORIENTED_EDGE('',*,*,#634,.T.); -#756 = ORIENTED_EDGE('',*,*,#660,.F.); -#757 = ORIENTED_EDGE('',*,*,#674,.F.); -#758 = ORIENTED_EDGE('',*,*,#320,.F.); -#759 = ORIENTED_EDGE('',*,*,#344,.F.); -#760 = ORIENTED_EDGE('',*,*,#358,.F.); -#761 = ORIENTED_EDGE('',*,*,#392,.F.); -#762 = ORIENTED_EDGE('',*,*,#414,.T.); -#763 = ORIENTED_EDGE('',*,*,#438,.F.); -#764 = ORIENTED_EDGE('',*,*,#463,.T.); -#765 = ORIENTED_EDGE('',*,*,#487,.F.); -#766 = ORIENTED_EDGE('',*,*,#512,.T.); -#767 = FACE_BOUND('',#768,.F.); -#768 = EDGE_LOOP('',(#769,#779,#787,#795)); -#769 = ORIENTED_EDGE('',*,*,#770,.F.); -#770 = EDGE_CURVE('',#771,#773,#775,.T.); -#771 = VERTEX_POINT('',#772); -#772 = CARTESIAN_POINT('',(5.809375885494,3.2,-13.06417917474)); -#773 = VERTEX_POINT('',#774); -#774 = CARTESIAN_POINT('',(2.688069798796,3.2,-49.64588621989)); -#775 = LINE('',#776,#777); -#776 = CARTESIAN_POINT('',(4.914157977861,3.2,-23.55613296875)); -#777 = VECTOR('',#778,1.); -#778 = DIRECTION('',(-8.501532861635E-02,0.,-0.996379643459)); -#779 = ORIENTED_EDGE('',*,*,#780,.F.); -#780 = EDGE_CURVE('',#781,#771,#783,.T.); -#781 = VERTEX_POINT('',#782); -#782 = CARTESIAN_POINT('',(-5.809375885494,3.2,-13.06417917474)); -#783 = LINE('',#784,#785); -#784 = CARTESIAN_POINT('',(1.615747408047,3.2,-13.06417917474)); -#785 = VECTOR('',#786,1.); -#786 = DIRECTION('',(1.,0.,-3.066574716487E-16)); -#787 = ORIENTED_EDGE('',*,*,#788,.F.); -#788 = EDGE_CURVE('',#789,#781,#791,.T.); -#789 = VERTEX_POINT('',#790); -#790 = CARTESIAN_POINT('',(-2.688069798796,3.2,-49.64588621989)); -#791 = LINE('',#792,#793); -#792 = CARTESIAN_POINT('',(-4.890802006217,3.2,-23.82986495424)); -#793 = VECTOR('',#794,1.); -#794 = DIRECTION('',(-8.501532861635E-02,0.,0.996379643459)); -#795 = ORIENTED_EDGE('',*,*,#796,.F.); -#796 = EDGE_CURVE('',#773,#789,#797,.T.); -#797 = LINE('',#798,#799); -#798 = CARTESIAN_POINT('',(1.615747408047,3.2,-49.64588621989)); -#799 = VECTOR('',#800,1.); -#800 = DIRECTION('',(-1.,0.,0.)); -#801 = FACE_BOUND('',#802,.F.); -#802 = EDGE_LOOP('',(#803)); -#803 = ORIENTED_EDGE('',*,*,#804,.F.); -#804 = EDGE_CURVE('',#805,#805,#807,.T.); -#805 = VERTEX_POINT('',#806); -#806 = CARTESIAN_POINT('',(-11.6927539352,3.2,-48.00951684793)); -#807 = CIRCLE('',#808,4.735522705283); -#808 = AXIS2_PLACEMENT_3D('',#809,#810,#811); -#809 = CARTESIAN_POINT('',(-16.42827664048,3.2,-48.00951684793)); -#810 = DIRECTION('',(-0.,1.,0.)); -#811 = DIRECTION('',(1.,0.,0.)); -#812 = PLANE('',#813); -#813 = AXIS2_PLACEMENT_3D('',#814,#815,#816); -#814 = CARTESIAN_POINT('',(0.403056515455,3.2,-33.34517655273)); -#815 = DIRECTION('',(0.,1.,0.)); -#816 = DIRECTION('',(1.,0.,0.)); -#817 = ADVANCED_FACE('',(#818),#843,.T.); -#818 = FACE_BOUND('',#819,.T.); -#819 = EDGE_LOOP('',(#820,#828,#836,#842)); -#820 = ORIENTED_EDGE('',*,*,#821,.F.); -#821 = EDGE_CURVE('',#822,#692,#824,.T.); -#822 = VERTEX_POINT('',#823); -#823 = CARTESIAN_POINT('',(16.626582997737,0.,-8.940188245231)); -#824 = LINE('',#825,#826); -#825 = CARTESIAN_POINT('',(16.626582997737,-22.,-8.940188245231)); -#826 = VECTOR('',#827,1.); -#827 = DIRECTION('',(0.,1.,0.)); -#828 = ORIENTED_EDGE('',*,*,#829,.T.); -#829 = EDGE_CURVE('',#822,#830,#832,.T.); -#830 = VERTEX_POINT('',#831); -#831 = CARTESIAN_POINT('',(-5.329070518201E-15,0.,-8.903751135252)); -#832 = LINE('',#833,#834); -#833 = CARTESIAN_POINT('',(-18.54556462154,1.7763568394E-15, - -8.863107566442)); -#834 = VECTOR('',#835,1.); -#835 = DIRECTION('',(-0.999997598615,0.,2.191520817069E-03)); -#836 = ORIENTED_EDGE('',*,*,#837,.T.); -#837 = EDGE_CURVE('',#830,#694,#838,.T.); -#838 = LINE('',#839,#840); -#839 = CARTESIAN_POINT('',(-5.329070518201E-15,-22.,-8.903751135252)); -#840 = VECTOR('',#841,1.); -#841 = DIRECTION('',(0.,1.,0.)); -#842 = ORIENTED_EDGE('',*,*,#691,.F.); -#843 = PLANE('',#844); -#844 = AXIS2_PLACEMENT_3D('',#845,#846,#847); -#845 = CARTESIAN_POINT('',(8.237581109188,-22.,-8.921803528809)); -#846 = DIRECTION('',(-2.191520817069E-03,0.,-0.999997598615)); -#847 = DIRECTION('',(-0.999997598615,0.,2.191520817069E-03)); -#848 = ADVANCED_FACE('',(#849),#867,.T.); -#849 = FACE_BOUND('',#850,.T.); -#850 = EDGE_LOOP('',(#851,#859,#865,#866)); -#851 = ORIENTED_EDGE('',*,*,#852,.F.); -#852 = EDGE_CURVE('',#853,#702,#855,.T.); -#853 = VERTEX_POINT('',#854); -#854 = CARTESIAN_POINT('',(19.029295926024,0.,-17.63009960955)); -#855 = LINE('',#856,#857); -#856 = CARTESIAN_POINT('',(19.029295926024,-22.,-17.63009960955)); -#857 = VECTOR('',#858,1.); -#858 = DIRECTION('',(0.,1.,0.)); -#859 = ORIENTED_EDGE('',*,*,#860,.T.); -#860 = EDGE_CURVE('',#853,#822,#861,.T.); -#861 = LINE('',#862,#863); -#862 = CARTESIAN_POINT('',(23.053273013696,0.,-32.18365022821)); -#863 = VECTOR('',#864,1.); -#864 = DIRECTION('',(-0.26649542889,0.,0.963836182336)); -#865 = ORIENTED_EDGE('',*,*,#821,.T.); -#866 = ORIENTED_EDGE('',*,*,#701,.F.); -#867 = PLANE('',#868); -#868 = AXIS2_PLACEMENT_3D('',#869,#870,#871); -#869 = CARTESIAN_POINT('',(17.956031892536,-22.,-13.7484168618)); -#870 = DIRECTION('',(-0.963836182336,0.,-0.26649542889)); -#871 = DIRECTION('',(-0.26649542889,0.,0.963836182336)); -#872 = ADVANCED_FACE('',(#873),#891,.T.); -#873 = FACE_BOUND('',#874,.T.); -#874 = EDGE_LOOP('',(#875,#883,#884,#885)); -#875 = ORIENTED_EDGE('',*,*,#876,.T.); -#876 = EDGE_CURVE('',#877,#853,#879,.T.); -#877 = VERTEX_POINT('',#878); -#878 = CARTESIAN_POINT('',(22.059435554995,0.,-3.734519760785)); -#879 = LINE('',#880,#881); -#880 = CARTESIAN_POINT('',(12.741012548745,1.7763568394E-15, - -46.46683800444)); -#881 = VECTOR('',#882,1.); -#882 = DIRECTION('',(-0.213058124893,0.,-0.977039526026)); -#883 = ORIENTED_EDGE('',*,*,#852,.T.); -#884 = ORIENTED_EDGE('',*,*,#709,.F.); -#885 = ORIENTED_EDGE('',*,*,#886,.F.); -#886 = EDGE_CURVE('',#877,#710,#887,.T.); -#887 = LINE('',#888,#889); -#888 = CARTESIAN_POINT('',(22.059435554995,-22.,-3.734519760785)); -#889 = VECTOR('',#890,1.); -#890 = DIRECTION('',(0.,1.,0.)); -#891 = PLANE('',#892); -#892 = AXIS2_PLACEMENT_3D('',#893,#894,#895); -#893 = CARTESIAN_POINT('',(20.484588228021,-22.,-10.95643672209)); -#894 = DIRECTION('',(0.977039526026,0.,-0.213058124893)); -#895 = DIRECTION('',(-0.213058124893,0.,-0.977039526026)); -#896 = ADVANCED_FACE('',(#897),#915,.T.); -#897 = FACE_BOUND('',#898,.T.); -#898 = EDGE_LOOP('',(#899,#907,#908,#909)); -#899 = ORIENTED_EDGE('',*,*,#900,.T.); -#900 = EDGE_CURVE('',#901,#877,#903,.T.); -#901 = VERTEX_POINT('',#902); -#902 = CARTESIAN_POINT('',(-21.72552223146,0.,-3.734519760785)); -#903 = LINE('',#904,#905); -#904 = CARTESIAN_POINT('',(-22.63692080725,0.,-3.734519760785)); -#905 = VECTOR('',#906,1.); -#906 = DIRECTION('',(1.,0.,0.)); -#907 = ORIENTED_EDGE('',*,*,#886,.T.); -#908 = ORIENTED_EDGE('',*,*,#717,.F.); -#909 = ORIENTED_EDGE('',*,*,#910,.F.); -#910 = EDGE_CURVE('',#901,#718,#911,.T.); -#911 = LINE('',#912,#913); -#912 = CARTESIAN_POINT('',(-21.72552223146,-22.,-3.734519760785)); -#913 = VECTOR('',#914,1.); -#914 = DIRECTION('',(0.,1.,0.)); -#915 = PLANE('',#916); -#916 = AXIS2_PLACEMENT_3D('',#917,#918,#919); -#917 = CARTESIAN_POINT('',(0.19111316645,-22.,-3.734519760785)); -#918 = DIRECTION('',(0.,0.,1.)); -#919 = DIRECTION('',(0.,-1.,0.)); -#920 = ADVANCED_FACE('',(#921),#939,.T.); -#921 = FACE_BOUND('',#922,.T.); -#922 = EDGE_LOOP('',(#923,#931,#932,#933)); -#923 = ORIENTED_EDGE('',*,*,#924,.T.); -#924 = EDGE_CURVE('',#925,#901,#927,.T.); -#925 = VERTEX_POINT('',#926); -#926 = CARTESIAN_POINT('',(-21.72552223146,0.,-8.903751135252)); -#927 = LINE('',#928,#929); -#928 = CARTESIAN_POINT('',(-21.72552223146,0.,-38.64614663299)); -#929 = VECTOR('',#930,1.); -#930 = DIRECTION('',(0.,0.,1.)); -#931 = ORIENTED_EDGE('',*,*,#910,.T.); -#932 = ORIENTED_EDGE('',*,*,#725,.F.); -#933 = ORIENTED_EDGE('',*,*,#934,.F.); -#934 = EDGE_CURVE('',#925,#726,#935,.T.); -#935 = LINE('',#936,#937); -#936 = CARTESIAN_POINT('',(-21.72552223146,-22.,-8.903751135252)); -#937 = VECTOR('',#938,1.); -#938 = DIRECTION('',(0.,1.,0.)); -#939 = PLANE('',#940); -#940 = AXIS2_PLACEMENT_3D('',#941,#942,#943); -#941 = CARTESIAN_POINT('',(-21.72552223146,-22.,-6.319135448019)); -#942 = DIRECTION('',(-1.,0.,0.)); -#943 = DIRECTION('',(0.,1.,0.)); -#944 = ADVANCED_FACE('',(#945),#956,.T.); -#945 = FACE_BOUND('',#946,.T.); -#946 = EDGE_LOOP('',(#947,#953,#954,#955)); -#947 = ORIENTED_EDGE('',*,*,#948,.T.); -#948 = EDGE_CURVE('',#830,#925,#949,.T.); -#949 = LINE('',#950,#951); -#950 = CARTESIAN_POINT('',(-28.21385794834,0.,-8.903751135252)); -#951 = VECTOR('',#952,1.); -#952 = DIRECTION('',(-1.,0.,0.)); -#953 = ORIENTED_EDGE('',*,*,#934,.T.); -#954 = ORIENTED_EDGE('',*,*,#733,.F.); -#955 = ORIENTED_EDGE('',*,*,#837,.F.); -#956 = PLANE('',#957); -#957 = AXIS2_PLACEMENT_3D('',#958,#959,#960); -#958 = CARTESIAN_POINT('',(-10.96276111573,-22.,-8.903751135252)); -#959 = DIRECTION('',(0.,0.,-1.)); -#960 = DIRECTION('',(0.,1.,0.)); -#961 = ADVANCED_FACE('',(#962),#981,.T.); -#962 = FACE_BOUND('',#963,.T.); -#963 = EDGE_LOOP('',(#964,#973,#979,#980)); -#964 = ORIENTED_EDGE('',*,*,#965,.T.); -#965 = EDGE_CURVE('',#966,#966,#968,.T.); -#966 = VERTEX_POINT('',#967); -#967 = CARTESIAN_POINT('',(21.163799345768,0.,-48.00951684793)); -#968 = CIRCLE('',#969,4.735522705283); -#969 = AXIS2_PLACEMENT_3D('',#970,#971,#972); -#970 = CARTESIAN_POINT('',(16.428276640485,0.,-48.00951684793)); -#971 = DIRECTION('',(-0.,1.,0.)); -#972 = DIRECTION('',(1.,0.,0.)); -#973 = ORIENTED_EDGE('',*,*,#974,.T.); -#974 = EDGE_CURVE('',#966,#742,#975,.T.); -#975 = LINE('',#976,#977); -#976 = CARTESIAN_POINT('',(21.163799345768,-22.,-48.00951684793)); -#977 = VECTOR('',#978,1.); -#978 = DIRECTION('',(0.,1.,0.)); -#979 = ORIENTED_EDGE('',*,*,#741,.F.); -#980 = ORIENTED_EDGE('',*,*,#974,.F.); -#981 = CYLINDRICAL_SURFACE('',#982,4.735522705283); -#982 = AXIS2_PLACEMENT_3D('',#983,#984,#985); -#983 = CARTESIAN_POINT('',(16.428276640485,-22.,-48.00951684793)); -#984 = DIRECTION('',(0.,1.,0.)); -#985 = DIRECTION('',(1.,0.,0.)); -#986 = ADVANCED_FACE('',(#987),#1012,.F.); -#987 = FACE_BOUND('',#988,.F.); -#988 = EDGE_LOOP('',(#989,#999,#1005,#1006)); -#989 = ORIENTED_EDGE('',*,*,#990,.T.); -#990 = EDGE_CURVE('',#991,#993,#995,.T.); -#991 = VERTEX_POINT('',#992); -#992 = CARTESIAN_POINT('',(1.480297366167E-15,11.242400581089, - -46.71869179633)); -#993 = VERTEX_POINT('',#994); -#994 = CARTESIAN_POINT('',(3.256654205567E-15,17.8572529153, - -18.39898295202)); -#995 = LINE('',#996,#997); -#996 = CARTESIAN_POINT('',(2.6645352591E-15,18.133069549222, - -17.21814831317)); -#997 = VECTOR('',#998,1.); -#998 = DIRECTION('',(5.275122655166E-17,0.227455280238,0.97378852709)); -#999 = ORIENTED_EDGE('',*,*,#1000,.F.); -#1000 = EDGE_CURVE('',#771,#993,#1001,.T.); -#1001 = LINE('',#1002,#1003); -#1002 = CARTESIAN_POINT('',(5.649679875255,3.602918464526, - -13.21082950266)); -#1003 = VECTOR('',#1004,1.); -#1004 = DIRECTION('',(-0.349023821871,0.880598971639,-0.320511814002)); -#1005 = ORIENTED_EDGE('',*,*,#770,.T.); -#1006 = ORIENTED_EDGE('',*,*,#1007,.T.); -#1007 = EDGE_CURVE('',#773,#991,#1008,.T.); -#1008 = LINE('',#1009,#1010); -#1009 = CARTESIAN_POINT('',(1.103762571829,7.940067898719, - -47.92064259636)); -#1010 = VECTOR('',#1011,1.); -#1011 = DIRECTION('',(-0.299648208284,0.896513522642,0.326304236859)); -#1012 = PLANE('',#1013); -#1013 = AXIS2_PLACEMENT_3D('',#1014,#1015,#1016); -#1014 = CARTESIAN_POINT('',(5.823691883056,3.068404028665, - -13.45978839622)); -#1015 = DIRECTION('',(0.93629059846,0.342020143326,-7.988827695448E-02) - ); -#1016 = DIRECTION('',(-0.340781908463,0.939692620786,2.907695487824E-02) - ); -#1017 = ADVANCED_FACE('',(#1018),#1028,.F.); -#1018 = FACE_BOUND('',#1019,.F.); -#1019 = EDGE_LOOP('',(#1020,#1021,#1027)); -#1020 = ORIENTED_EDGE('',*,*,#1000,.T.); -#1021 = ORIENTED_EDGE('',*,*,#1022,.T.); -#1022 = EDGE_CURVE('',#993,#781,#1023,.T.); -#1023 = LINE('',#1024,#1025); -#1024 = CARTESIAN_POINT('',(-5.275833888477,4.546144602338, - -13.55413574101)); -#1025 = VECTOR('',#1026,1.); -#1026 = DIRECTION('',(-0.349023821871,-0.880598971639,0.320511814002)); -#1027 = ORIENTED_EDGE('',*,*,#780,.T.); -#1028 = PLANE('',#1029); -#1029 = AXIS2_PLACEMENT_3D('',#1030,#1031,#1032); -#1030 = CARTESIAN_POINT('',(2.828438300639,3.068404028665, - -13.01628215822)); -#1031 = DIRECTION('',(2.881637632171E-16,0.342020143326,0.939692620786) - ); -#1032 = DIRECTION('',(-1.048830324052E-16,0.939692620786,-0.342020143326 - )); -#1033 = ADVANCED_FACE('',(#1034),#1045,.F.); -#1034 = FACE_BOUND('',#1035,.F.); -#1035 = EDGE_LOOP('',(#1036,#1037,#1038,#1039)); -#1036 = ORIENTED_EDGE('',*,*,#788,.T.); -#1037 = ORIENTED_EDGE('',*,*,#1022,.F.); -#1038 = ORIENTED_EDGE('',*,*,#990,.F.); -#1039 = ORIENTED_EDGE('',*,*,#1040,.F.); -#1040 = EDGE_CURVE('',#789,#991,#1041,.T.); -#1041 = LINE('',#1042,#1043); -#1042 = CARTESIAN_POINT('',(-0.871390517001,8.635298785064, - -47.66759924779)); -#1043 = VECTOR('',#1044,1.); -#1044 = DIRECTION('',(0.299648208284,0.896513522642,0.326304236859)); -#1045 = PLANE('',#1046); -#1046 = AXIS2_PLACEMENT_3D('',#1047,#1048,#1049); -#1047 = CARTESIAN_POINT('',(-5.782806207227,3.068404028665, - -13.93896851312)); -#1048 = DIRECTION('',(-0.93629059846,0.342020143326,-7.988827695448E-02) - ); -#1049 = DIRECTION('',(0.340781908463,0.939692620786,2.907695487824E-02) - ); -#1050 = ADVANCED_FACE('',(#1051),#1056,.F.); -#1051 = FACE_BOUND('',#1052,.F.); -#1052 = EDGE_LOOP('',(#1053,#1054,#1055)); -#1053 = ORIENTED_EDGE('',*,*,#1040,.T.); -#1054 = ORIENTED_EDGE('',*,*,#1007,.F.); -#1055 = ORIENTED_EDGE('',*,*,#796,.T.); -#1056 = PLANE('',#1057); -#1057 = AXIS2_PLACEMENT_3D('',#1058,#1059,#1060); -#1058 = CARTESIAN_POINT('',(2.828438300639,3.068404028665, - -49.69378323641)); -#1059 = DIRECTION('',(0.,0.342020143326,-0.939692620786)); -#1060 = DIRECTION('',(0.,0.939692620786,0.342020143326)); -#1061 = ADVANCED_FACE('',(#1062),#1081,.T.); -#1062 = FACE_BOUND('',#1063,.T.); -#1063 = EDGE_LOOP('',(#1064,#1073,#1079,#1080)); -#1064 = ORIENTED_EDGE('',*,*,#1065,.T.); -#1065 = EDGE_CURVE('',#1066,#1066,#1068,.T.); -#1066 = VERTEX_POINT('',#1067); -#1067 = CARTESIAN_POINT('',(-11.6927539352,0.,-48.00951684793)); -#1068 = CIRCLE('',#1069,4.735522705283); -#1069 = AXIS2_PLACEMENT_3D('',#1070,#1071,#1072); -#1070 = CARTESIAN_POINT('',(-16.42827664048,0.,-48.00951684793)); -#1071 = DIRECTION('',(-0.,1.,0.)); -#1072 = DIRECTION('',(1.,0.,0.)); -#1073 = ORIENTED_EDGE('',*,*,#1074,.T.); -#1074 = EDGE_CURVE('',#1066,#805,#1075,.T.); -#1075 = LINE('',#1076,#1077); -#1076 = CARTESIAN_POINT('',(-11.6927539352,-22.,-48.00951684793)); -#1077 = VECTOR('',#1078,1.); -#1078 = DIRECTION('',(0.,1.,0.)); -#1079 = ORIENTED_EDGE('',*,*,#804,.F.); -#1080 = ORIENTED_EDGE('',*,*,#1074,.F.); -#1081 = CYLINDRICAL_SURFACE('',#1082,4.735522705283); -#1082 = AXIS2_PLACEMENT_3D('',#1083,#1084,#1085); -#1083 = CARTESIAN_POINT('',(-16.42827664048,-22.,-48.00951684793)); -#1084 = DIRECTION('',(0.,1.,0.)); -#1085 = DIRECTION('',(1.,0.,0.)); -#1086 = ADVANCED_FACE('',(#1087),#1095,.F.); -#1087 = FACE_BOUND('',#1088,.F.); -#1088 = EDGE_LOOP('',(#1089,#1090,#1091,#1092,#1093,#1094)); -#1089 = ORIENTED_EDGE('',*,*,#860,.T.); -#1090 = ORIENTED_EDGE('',*,*,#829,.T.); -#1091 = ORIENTED_EDGE('',*,*,#948,.T.); -#1092 = ORIENTED_EDGE('',*,*,#924,.T.); -#1093 = ORIENTED_EDGE('',*,*,#900,.T.); -#1094 = ORIENTED_EDGE('',*,*,#876,.T.); -#1095 = PLANE('',#1096); -#1096 = AXIS2_PLACEMENT_3D('',#1097,#1098,#1099); -#1097 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#1098 = DIRECTION('',(0.,1.,0.)); -#1099 = DIRECTION('',(0.,0.,1.)); -#1100 = ADVANCED_FACE('',(#1101),#1104,.F.); -#1101 = FACE_BOUND('',#1102,.F.); -#1102 = EDGE_LOOP('',(#1103)); -#1103 = ORIENTED_EDGE('',*,*,#965,.T.); -#1104 = PLANE('',#1105); -#1105 = AXIS2_PLACEMENT_3D('',#1106,#1107,#1108); -#1106 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#1107 = DIRECTION('',(0.,1.,0.)); -#1108 = DIRECTION('',(0.,0.,1.)); -#1109 = ADVANCED_FACE('',(#1110),#1113,.F.); -#1110 = FACE_BOUND('',#1111,.F.); -#1111 = EDGE_LOOP('',(#1112)); -#1112 = ORIENTED_EDGE('',*,*,#1065,.T.); -#1113 = PLANE('',#1114); -#1114 = AXIS2_PLACEMENT_3D('',#1115,#1116,#1117); -#1115 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#1116 = DIRECTION('',(0.,1.,0.)); -#1117 = DIRECTION('',(0.,0.,1.)); -#1118 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3) -GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#1122)) -GLOBAL_UNIT_ASSIGNED_CONTEXT((#1119,#1120,#1121)) REPRESENTATION_CONTEXT -('Context #1','3D Context with UNIT and UNCERTAINTY') ); -#1119 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) ); -#1120 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) ); -#1121 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() ); -#1122 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-05),#1119, - 'distance_accuracy_value','confusion accuracy'); -#1123 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7)); -ENDSEC; -END-ISO-10303-21; diff --git a/docs/CAD/design/mate-connectors/BearConnector_Female_Trimmed.step b/docs/CAD/design/mate-connectors/BearConnector_Female_Trimmed.step deleted file mode 100644 index e6575701e4..0000000000 --- a/docs/CAD/design/mate-connectors/BearConnector_Female_Trimmed.step +++ /dev/null @@ -1,1048 +0,0 @@ -ISO-10303-21; -HEADER; -FILE_DESCRIPTION(('FreeCAD Model'),'2;1'); -FILE_NAME('Open CASCADE Shape Model','2026-08-05T11:06:23',('FreeCAD'),( - 'FreeCAD'),'Open CASCADE STEP processor 7.8','FreeCAD','Unknown'); -FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }')); -ENDSEC; -DATA; -#1 = APPLICATION_PROTOCOL_DEFINITION('international standard', - 'automotive_design',2000,#2); -#2 = APPLICATION_CONTEXT( - 'core data for automotive mechanical design processes'); -#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10); -#4 = PRODUCT_DEFINITION_SHAPE('','',#5); -#5 = PRODUCT_DEFINITION('design','',#6,#9); -#6 = PRODUCT_DEFINITION_FORMATION('','',#7); -#7 = PRODUCT('Open CASCADE STEP translator 7.8 1', - 'Open CASCADE STEP translator 7.8 1','',(#8)); -#8 = PRODUCT_CONTEXT('',#2,'mechanical'); -#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design'); -#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#15),#976); -#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14); -#12 = CARTESIAN_POINT('',(0.,0.,0.)); -#13 = DIRECTION('',(0.,0.,1.)); -#14 = DIRECTION('',(1.,0.,-0.)); -#15 = MANIFOLD_SOLID_BREP('',#16); -#16 = CLOSED_SHELL('',(#17,#157,#188,#212,#236,#261,#285,#310,#334,#358, - #383,#407,#432,#456,#480,#504,#528,#545,#675,#706,#730,#754,#778, - #802,#819,#850,#866,#882,#894,#919,#944,#958,#967)); -#17 = ADVANCED_FACE('',(#18),#152,.T.); -#18 = FACE_BOUND('',#19,.T.); -#19 = EDGE_LOOP('',(#20,#30,#38,#46,#54,#62,#70,#79,#87,#96,#104,#112, - #121,#129,#138,#146)); -#20 = ORIENTED_EDGE('',*,*,#21,.F.); -#21 = EDGE_CURVE('',#22,#24,#26,.T.); -#22 = VERTEX_POINT('',#23); -#23 = CARTESIAN_POINT('',(26.824640127118,20.8572529153,-55.23443461327) - ); -#24 = VERTEX_POINT('',#25); -#25 = CARTESIAN_POINT('',(42.184838000445,20.8572529153,-7.043416644207) - ); -#26 = LINE('',#27,#28); -#27 = CARTESIAN_POINT('',(28.89442018934,20.8572529153,-48.74071565254) - ); -#28 = VECTOR('',#29,1.); -#29 = DIRECTION('',(0.30368282823,0.,0.952773183837)); -#30 = ORIENTED_EDGE('',*,*,#31,.F.); -#31 = EDGE_CURVE('',#32,#22,#34,.T.); -#32 = VERTEX_POINT('',#33); -#33 = CARTESIAN_POINT('',(32.56245481215,20.8572529153,-60.7871552419)); -#34 = LINE('',#35,#36); -#35 = CARTESIAN_POINT('',(12.092418778801,20.8572529153,-40.97745458223) - ); -#36 = VECTOR('',#37,1.); -#37 = DIRECTION('',(-0.718602345805,0.,0.695421216677)); -#38 = ORIENTED_EDGE('',*,*,#39,.F.); -#39 = EDGE_CURVE('',#40,#32,#42,.T.); -#40 = VERTEX_POINT('',#41); -#41 = CARTESIAN_POINT('',(26.712845130625,20.8572529153,-66.83175546171) - ); -#42 = LINE('',#43,#44); -#43 = CARTESIAN_POINT('',(11.149878706433,20.8572529153,-82.91349687528) - ); -#44 = VECTOR('',#45,1.); -#45 = DIRECTION('',(0.695421216677,0.,0.718602345805)); -#46 = ORIENTED_EDGE('',*,*,#47,.F.); -#47 = EDGE_CURVE('',#48,#40,#50,.T.); -#48 = VERTEX_POINT('',#49); -#49 = CARTESIAN_POINT('',(20.57248334837,20.8572529153,-60.88947335481) - ); -#50 = LINE('',#51,#52); -#51 = CARTESIAN_POINT('',(9.111716439792,20.8572529153,-49.79841511636) - ); -#52 = VECTOR('',#53,1.); -#53 = DIRECTION('',(0.718602345805,0.,-0.695421216677)); -#54 = ORIENTED_EDGE('',*,*,#55,.F.); -#55 = EDGE_CURVE('',#56,#48,#58,.T.); -#56 = VERTEX_POINT('',#57); -#57 = CARTESIAN_POINT('',(-20.57248334837,20.8572529153,-60.88947335481) - ); -#58 = LINE('',#59,#60); -#59 = CARTESIAN_POINT('',(-12.44623571629,20.8572529153,-60.88947335481) - ); -#60 = VECTOR('',#61,1.); -#61 = DIRECTION('',(1.,0.,0.)); -#62 = ORIENTED_EDGE('',*,*,#63,.T.); -#63 = EDGE_CURVE('',#56,#64,#66,.T.); -#64 = VERTEX_POINT('',#65); -#65 = CARTESIAN_POINT('',(-25.60006917852,20.8572529153,-65.75487614033) - ); -#66 = LINE('',#67,#68); -#67 = CARTESIAN_POINT('',(-32.03294606502,20.8572529153,-71.98023721416) - ); -#68 = VECTOR('',#69,1.); -#69 = DIRECTION('',(-0.718602345805,0.,-0.695421216677)); -#70 = ORIENTED_EDGE('',*,*,#71,.F.); -#71 = EDGE_CURVE('',#72,#64,#74,.T.); -#72 = VERTEX_POINT('',#73); -#73 = CARTESIAN_POINT('',(-27.78972445201,20.8572529153,-65.71897950961) - ); -#74 = CIRCLE('',#75,1.548528137424); -#75 = AXIS2_PLACEMENT_3D('',#76,#77,#78); -#76 = CARTESIAN_POINT('',(-26.67694849991,20.8572529153,-64.64210018823) - ); -#77 = DIRECTION('',(0.,-1.,0.)); -#78 = DIRECTION('',(1.,0.,0.)); -#79 = ORIENTED_EDGE('',*,*,#80,.T.); -#80 = EDGE_CURVE('',#72,#81,#83,.T.); -#81 = VERTEX_POINT('',#82); -#82 = CARTESIAN_POINT('',(-29.01580660709,20.8572529153,-64.4520272055) - ); -#83 = LINE('',#84,#85); -#84 = CARTESIAN_POINT('',(-31.36389178357,20.8572529153,-62.02567109858) - ); -#85 = VECTOR('',#86,1.); -#86 = DIRECTION('',(-0.695421216677,0.,0.718602345805)); -#87 = ORIENTED_EDGE('',*,*,#88,.F.); -#88 = EDGE_CURVE('',#89,#81,#91,.T.); -#89 = VERTEX_POINT('',#90); -#90 = CARTESIAN_POINT('',(-28.89758284854,20.8572529153,-57.24050703685) - ); -#91 = CIRCLE('',#92,5.1); -#92 = AXIS2_PLACEMENT_3D('',#93,#94,#95); -#93 = CARTESIAN_POINT('',(-25.35093464349,20.8572529153,-60.90537900045) - ); -#94 = DIRECTION('',(0.,-1.,0.)); -#95 = DIRECTION('',(1.,0.,0.)); -#96 = ORIENTED_EDGE('',*,*,#97,.T.); -#97 = EDGE_CURVE('',#89,#98,#100,.T.); -#98 = VERTEX_POINT('',#99); -#99 = CARTESIAN_POINT('',(-26.82464012711,20.8572529153,-55.23443461327) - ); -#100 = LINE('',#101,#102); -#101 = CARTESIAN_POINT('',(-35.57003638008,20.8572529153,-63.69771634073 - )); -#102 = VECTOR('',#103,1.); -#103 = DIRECTION('',(0.718602345805,0.,0.695421216677)); -#104 = ORIENTED_EDGE('',*,*,#105,.T.); -#105 = EDGE_CURVE('',#98,#106,#108,.T.); -#106 = VERTEX_POINT('',#107); -#107 = CARTESIAN_POINT('',(-41.08376419406,20.8572529153,-10.49792081311 - )); -#108 = LINE('',#109,#110); -#109 = CARTESIAN_POINT('',(-32.53680963915,20.8572529153,-37.31309884227 - )); -#110 = VECTOR('',#111,1.); -#111 = DIRECTION('',(-0.30368282823,0.,0.952773183837)); -#112 = ORIENTED_EDGE('',*,*,#113,.F.); -#113 = EDGE_CURVE('',#114,#106,#116,.T.); -#114 = VERTEX_POINT('',#115); -#115 = CARTESIAN_POINT('',(-39.62105538679,20.8572529153,-4.479634030555 - )); -#116 = CIRCLE('',#117,5.954044962965); -#117 = AXIS2_PLACEMENT_3D('',#118,#119,#120); -#118 = CARTESIAN_POINT('',(-35.41090981799,20.8572529153,-8.689779599357 - )); -#119 = DIRECTION('',(0.,-1.,0.)); -#120 = DIRECTION('',(1.,0.,0.)); -#121 = ORIENTED_EDGE('',*,*,#122,.T.); -#122 = EDGE_CURVE('',#114,#123,#125,.T.); -#123 = VERTEX_POINT('',#124); -#124 = CARTESIAN_POINT('',(-36.7853207504,20.8572529153,-1.643899394163) - ); -#125 = LINE('',#126,#127); -#126 = CARTESIAN_POINT('',(-56.28754386398,20.8572529153,-21.14612250775 - )); -#127 = VECTOR('',#128,1.); -#128 = DIRECTION('',(0.707106781187,0.,0.707106781187)); -#129 = ORIENTED_EDGE('',*,*,#130,.F.); -#130 = EDGE_CURVE('',#131,#123,#133,.T.); -#131 = VERTEX_POINT('',#132); -#132 = CARTESIAN_POINT('',(-32.57517518159,20.8572529153, - 9.999999999998E-02)); -#133 = CIRCLE('',#134,5.954044962965); -#134 = AXIS2_PLACEMENT_3D('',#135,#136,#137); -#135 = CARTESIAN_POINT('',(-32.57517518159,20.8572529153,-5.854044962965 - )); -#136 = DIRECTION('',(0.,-1.,0.)); -#137 = DIRECTION('',(1.,0.,0.)); -#138 = ORIENTED_EDGE('',*,*,#139,.T.); -#139 = EDGE_CURVE('',#131,#140,#142,.T.); -#140 = VERTEX_POINT('',#141); -#141 = CARTESIAN_POINT('',(35.041421356237,20.8572529153,0.1)); -#142 = LINE('',#143,#144); -#143 = CARTESIAN_POINT('',(-5.211766712356,20.8572529153,0.1)); -#144 = VECTOR('',#145,1.); -#145 = DIRECTION('',(1.,0.,0.)); -#146 = ORIENTED_EDGE('',*,*,#147,.F.); -#147 = EDGE_CURVE('',#24,#140,#148,.T.); -#148 = LINE('',#149,#150); -#149 = CARTESIAN_POINT('',(32.68311677643,20.8572529153,2.458304579807) - ); -#150 = VECTOR('',#151,1.); -#151 = DIRECTION('',(-0.707106781187,0.,0.707106781187)); -#152 = PLANE('',#153); -#153 = AXIS2_PLACEMENT_3D('',#154,#155,#156); -#154 = CARTESIAN_POINT('',(-45.46495478095,20.8572529153,-70.97315781796 - )); -#155 = DIRECTION('',(0.,1.,0.)); -#156 = DIRECTION('',(0.,0.,1.)); -#157 = ADVANCED_FACE('',(#158),#183,.F.); -#158 = FACE_BOUND('',#159,.F.); -#159 = EDGE_LOOP('',(#160,#170,#176,#177)); -#160 = ORIENTED_EDGE('',*,*,#161,.T.); -#161 = EDGE_CURVE('',#162,#164,#166,.T.); -#162 = VERTEX_POINT('',#163); -#163 = CARTESIAN_POINT('',(26.824640127118,3.2,-55.23443461327)); -#164 = VERTEX_POINT('',#165); -#165 = CARTESIAN_POINT('',(42.184838000445,3.2,-7.043416644207)); -#166 = LINE('',#167,#168); -#167 = CARTESIAN_POINT('',(36.45312509682,3.2,-25.02606776693)); -#168 = VECTOR('',#169,1.); -#169 = DIRECTION('',(0.30368282823,0.,0.952773183837)); -#170 = ORIENTED_EDGE('',*,*,#171,.T.); -#171 = EDGE_CURVE('',#164,#24,#172,.T.); -#172 = LINE('',#173,#174); -#173 = CARTESIAN_POINT('',(42.184838000445,-5.,-7.043416644207)); -#174 = VECTOR('',#175,1.); -#175 = DIRECTION('',(0.,1.,0.)); -#176 = ORIENTED_EDGE('',*,*,#21,.F.); -#177 = ORIENTED_EDGE('',*,*,#178,.F.); -#178 = EDGE_CURVE('',#162,#22,#179,.T.); -#179 = LINE('',#180,#181); -#180 = CARTESIAN_POINT('',(26.824640127118,-5.,-55.23443461327)); -#181 = VECTOR('',#182,1.); -#182 = DIRECTION('',(0.,1.,0.)); -#183 = PLANE('',#184); -#184 = AXIS2_PLACEMENT_3D('',#185,#186,#187); -#185 = CARTESIAN_POINT('',(42.184838000445,-5.,-7.043416644207)); -#186 = DIRECTION('',(-0.952773183837,0.,0.30368282823)); -#187 = DIRECTION('',(-0.30368282823,0.,-0.952773183837)); -#188 = ADVANCED_FACE('',(#189),#207,.F.); -#189 = FACE_BOUND('',#190,.F.); -#190 = EDGE_LOOP('',(#191,#199,#205,#206)); -#191 = ORIENTED_EDGE('',*,*,#192,.T.); -#192 = EDGE_CURVE('',#164,#193,#195,.T.); -#193 = VERTEX_POINT('',#194); -#194 = CARTESIAN_POINT('',(35.041421356237,3.2,0.1)); -#195 = LINE('',#196,#197); -#196 = CARTESIAN_POINT('',(34.743124284224,3.2,0.398297072013)); -#197 = VECTOR('',#198,1.); -#198 = DIRECTION('',(-0.707106781187,0.,0.707106781187)); -#199 = ORIENTED_EDGE('',*,*,#200,.T.); -#200 = EDGE_CURVE('',#193,#140,#201,.T.); -#201 = LINE('',#202,#203); -#202 = CARTESIAN_POINT('',(35.041421356237,-5.,0.1)); -#203 = VECTOR('',#204,1.); -#204 = DIRECTION('',(0.,1.,0.)); -#205 = ORIENTED_EDGE('',*,*,#147,.F.); -#206 = ORIENTED_EDGE('',*,*,#171,.F.); -#207 = PLANE('',#208); -#208 = AXIS2_PLACEMENT_3D('',#209,#210,#211); -#209 = CARTESIAN_POINT('',(35.041421356237,-5.,9.999999999998E-02)); -#210 = DIRECTION('',(-0.707106781187,0.,-0.707106781187)); -#211 = DIRECTION('',(0.707106781187,0.,-0.707106781187)); -#212 = ADVANCED_FACE('',(#213),#231,.T.); -#213 = FACE_BOUND('',#214,.T.); -#214 = EDGE_LOOP('',(#215,#223,#224,#225)); -#215 = ORIENTED_EDGE('',*,*,#216,.T.); -#216 = EDGE_CURVE('',#217,#193,#219,.T.); -#217 = VERTEX_POINT('',#218); -#218 = CARTESIAN_POINT('',(-32.57517518159,3.2,9.999999999998E-02)); -#219 = LINE('',#220,#221); -#220 = CARTESIAN_POINT('',(17.722238935846,3.2,0.1)); -#221 = VECTOR('',#222,1.); -#222 = DIRECTION('',(1.,0.,0.)); -#223 = ORIENTED_EDGE('',*,*,#200,.T.); -#224 = ORIENTED_EDGE('',*,*,#139,.F.); -#225 = ORIENTED_EDGE('',*,*,#226,.F.); -#226 = EDGE_CURVE('',#217,#131,#227,.T.); -#227 = LINE('',#228,#229); -#228 = CARTESIAN_POINT('',(-32.57517518159,-5.,9.999999999998E-02)); -#229 = VECTOR('',#230,1.); -#230 = DIRECTION('',(0.,1.,0.)); -#231 = PLANE('',#232); -#232 = AXIS2_PLACEMENT_3D('',#233,#234,#235); -#233 = CARTESIAN_POINT('',(35.041421356237,-5.,0.1)); -#234 = DIRECTION('',(0.,0.,1.)); -#235 = DIRECTION('',(-1.,0.,0.)); -#236 = ADVANCED_FACE('',(#237),#256,.T.); -#237 = FACE_BOUND('',#238,.F.); -#238 = EDGE_LOOP('',(#239,#248,#254,#255)); -#239 = ORIENTED_EDGE('',*,*,#240,.T.); -#240 = EDGE_CURVE('',#217,#241,#243,.T.); -#241 = VERTEX_POINT('',#242); -#242 = CARTESIAN_POINT('',(-36.7853207504,3.2,-1.643899394163)); -#243 = CIRCLE('',#244,5.954044962965); -#244 = AXIS2_PLACEMENT_3D('',#245,#246,#247); -#245 = CARTESIAN_POINT('',(-32.57517518159,3.2,-5.854044962965)); -#246 = DIRECTION('',(0.,-1.,0.)); -#247 = DIRECTION('',(1.,0.,0.)); -#248 = ORIENTED_EDGE('',*,*,#249,.T.); -#249 = EDGE_CURVE('',#241,#123,#250,.T.); -#250 = LINE('',#251,#252); -#251 = CARTESIAN_POINT('',(-36.7853207504,-5.,-1.643899394163)); -#252 = VECTOR('',#253,1.); -#253 = DIRECTION('',(0.,1.,0.)); -#254 = ORIENTED_EDGE('',*,*,#130,.F.); -#255 = ORIENTED_EDGE('',*,*,#226,.F.); -#256 = CYLINDRICAL_SURFACE('',#257,5.954044962965); -#257 = AXIS2_PLACEMENT_3D('',#258,#259,#260); -#258 = CARTESIAN_POINT('',(-32.57517518159,-5.,-5.854044962965)); -#259 = DIRECTION('',(0.,-1.,0.)); -#260 = DIRECTION('',(1.,0.,0.)); -#261 = ADVANCED_FACE('',(#262),#280,.T.); -#262 = FACE_BOUND('',#263,.T.); -#263 = EDGE_LOOP('',(#264,#272,#273,#274)); -#264 = ORIENTED_EDGE('',*,*,#265,.T.); -#265 = EDGE_CURVE('',#266,#241,#268,.T.); -#266 = VERTEX_POINT('',#267); -#267 = CARTESIAN_POINT('',(-39.62105538679,3.2,-4.479634030555)); -#268 = LINE('',#269,#270); -#269 = CARTESIAN_POINT('',(-35.41354572357,3.2,-0.272124367341)); -#270 = VECTOR('',#271,1.); -#271 = DIRECTION('',(0.707106781187,0.,0.707106781187)); -#272 = ORIENTED_EDGE('',*,*,#249,.T.); -#273 = ORIENTED_EDGE('',*,*,#122,.F.); -#274 = ORIENTED_EDGE('',*,*,#275,.F.); -#275 = EDGE_CURVE('',#266,#114,#276,.T.); -#276 = LINE('',#277,#278); -#277 = CARTESIAN_POINT('',(-39.62105538679,-5.,-4.479634030555)); -#278 = VECTOR('',#279,1.); -#279 = DIRECTION('',(0.,1.,0.)); -#280 = PLANE('',#281); -#281 = AXIS2_PLACEMENT_3D('',#282,#283,#284); -#282 = CARTESIAN_POINT('',(-36.7853207504,-5.,-1.643899394163)); -#283 = DIRECTION('',(-0.707106781187,0.,0.707106781187)); -#284 = DIRECTION('',(-0.707106781187,0.,-0.707106781187)); -#285 = ADVANCED_FACE('',(#286),#305,.T.); -#286 = FACE_BOUND('',#287,.F.); -#287 = EDGE_LOOP('',(#288,#297,#303,#304)); -#288 = ORIENTED_EDGE('',*,*,#289,.T.); -#289 = EDGE_CURVE('',#266,#290,#292,.T.); -#290 = VERTEX_POINT('',#291); -#291 = CARTESIAN_POINT('',(-41.08376419406,3.2,-10.49792081311)); -#292 = CIRCLE('',#293,5.954044962965); -#293 = AXIS2_PLACEMENT_3D('',#294,#295,#296); -#294 = CARTESIAN_POINT('',(-35.41090981799,3.2,-8.689779599357)); -#295 = DIRECTION('',(0.,-1.,0.)); -#296 = DIRECTION('',(1.,0.,0.)); -#297 = ORIENTED_EDGE('',*,*,#298,.T.); -#298 = EDGE_CURVE('',#290,#106,#299,.T.); -#299 = LINE('',#300,#301); -#300 = CARTESIAN_POINT('',(-41.08376419406,-5.,-10.49792081311)); -#301 = VECTOR('',#302,1.); -#302 = DIRECTION('',(0.,1.,0.)); -#303 = ORIENTED_EDGE('',*,*,#113,.F.); -#304 = ORIENTED_EDGE('',*,*,#275,.F.); -#305 = CYLINDRICAL_SURFACE('',#306,5.954044962965); -#306 = AXIS2_PLACEMENT_3D('',#307,#308,#309); -#307 = CARTESIAN_POINT('',(-35.41090981799,-5.,-8.689779599357)); -#308 = DIRECTION('',(0.,-1.,0.)); -#309 = DIRECTION('',(1.,0.,0.)); -#310 = ADVANCED_FACE('',(#311),#329,.T.); -#311 = FACE_BOUND('',#312,.T.); -#312 = EDGE_LOOP('',(#313,#321,#322,#323)); -#313 = ORIENTED_EDGE('',*,*,#314,.T.); -#314 = EDGE_CURVE('',#315,#290,#317,.T.); -#315 = VERTEX_POINT('',#316); -#316 = CARTESIAN_POINT('',(-26.82464012711,3.2,-55.23443461327)); -#317 = LINE('',#318,#319); -#318 = CARTESIAN_POINT('',(-35.86541700774,3.2,-26.86994056823)); -#319 = VECTOR('',#320,1.); -#320 = DIRECTION('',(-0.30368282823,0.,0.952773183837)); -#321 = ORIENTED_EDGE('',*,*,#298,.T.); -#322 = ORIENTED_EDGE('',*,*,#105,.F.); -#323 = ORIENTED_EDGE('',*,*,#324,.F.); -#324 = EDGE_CURVE('',#315,#98,#325,.T.); -#325 = LINE('',#326,#327); -#326 = CARTESIAN_POINT('',(-26.82464012711,-5.,-55.23443461327)); -#327 = VECTOR('',#328,1.); -#328 = DIRECTION('',(0.,1.,0.)); -#329 = PLANE('',#330); -#330 = AXIS2_PLACEMENT_3D('',#331,#332,#333); -#331 = CARTESIAN_POINT('',(-41.08376419406,-5.,-10.49792081311)); -#332 = DIRECTION('',(-0.952773183837,0.,-0.30368282823)); -#333 = DIRECTION('',(0.30368282823,0.,-0.952773183837)); -#334 = ADVANCED_FACE('',(#335),#353,.T.); -#335 = FACE_BOUND('',#336,.T.); -#336 = EDGE_LOOP('',(#337,#345,#346,#347)); -#337 = ORIENTED_EDGE('',*,*,#338,.T.); -#338 = EDGE_CURVE('',#339,#315,#341,.T.); -#339 = VERTEX_POINT('',#340); -#340 = CARTESIAN_POINT('',(-28.89758284854,3.2,-57.24050703685)); -#341 = LINE('',#342,#343); -#342 = CARTESIAN_POINT('',(-14.32522020853,3.2,-43.13822889076)); -#343 = VECTOR('',#344,1.); -#344 = DIRECTION('',(0.718602345805,0.,0.695421216677)); -#345 = ORIENTED_EDGE('',*,*,#324,.T.); -#346 = ORIENTED_EDGE('',*,*,#97,.F.); -#347 = ORIENTED_EDGE('',*,*,#348,.F.); -#348 = EDGE_CURVE('',#339,#89,#349,.T.); -#349 = LINE('',#350,#351); -#350 = CARTESIAN_POINT('',(-28.89758284854,-5.,-57.24050703685)); -#351 = VECTOR('',#352,1.); -#352 = DIRECTION('',(0.,1.,0.)); -#353 = PLANE('',#354); -#354 = AXIS2_PLACEMENT_3D('',#355,#356,#357); -#355 = CARTESIAN_POINT('',(-26.82464012711,-5.,-55.23443461327)); -#356 = DIRECTION('',(-0.695421216677,0.,0.718602345805)); -#357 = DIRECTION('',(-0.718602345805,0.,-0.695421216677)); -#358 = ADVANCED_FACE('',(#359),#378,.T.); -#359 = FACE_BOUND('',#360,.F.); -#360 = EDGE_LOOP('',(#361,#370,#376,#377)); -#361 = ORIENTED_EDGE('',*,*,#362,.T.); -#362 = EDGE_CURVE('',#339,#363,#365,.T.); -#363 = VERTEX_POINT('',#364); -#364 = CARTESIAN_POINT('',(-29.01580660709,3.2,-64.4520272055)); -#365 = CIRCLE('',#366,5.1); -#366 = AXIS2_PLACEMENT_3D('',#367,#368,#369); -#367 = CARTESIAN_POINT('',(-25.35093464349,3.2,-60.90537900045)); -#368 = DIRECTION('',(0.,-1.,0.)); -#369 = DIRECTION('',(1.,0.,0.)); -#370 = ORIENTED_EDGE('',*,*,#371,.T.); -#371 = EDGE_CURVE('',#363,#81,#372,.T.); -#372 = LINE('',#373,#374); -#373 = CARTESIAN_POINT('',(-29.01580660709,-5.,-64.4520272055)); -#374 = VECTOR('',#375,1.); -#375 = DIRECTION('',(0.,1.,0.)); -#376 = ORIENTED_EDGE('',*,*,#88,.F.); -#377 = ORIENTED_EDGE('',*,*,#348,.F.); -#378 = CYLINDRICAL_SURFACE('',#379,5.1); -#379 = AXIS2_PLACEMENT_3D('',#380,#381,#382); -#380 = CARTESIAN_POINT('',(-25.35093464349,-5.,-60.90537900045)); -#381 = DIRECTION('',(0.,-1.,0.)); -#382 = DIRECTION('',(1.,0.,0.)); -#383 = ADVANCED_FACE('',(#384),#402,.T.); -#384 = FACE_BOUND('',#385,.T.); -#385 = EDGE_LOOP('',(#386,#394,#395,#396)); -#386 = ORIENTED_EDGE('',*,*,#387,.T.); -#387 = EDGE_CURVE('',#388,#363,#390,.T.); -#388 = VERTEX_POINT('',#389); -#389 = CARTESIAN_POINT('',(-27.78972445201,3.2,-65.71897950961)); -#390 = LINE('',#391,#392); -#391 = CARTESIAN_POINT('',(-29.67470230692,3.2,-63.77116791594)); -#392 = VECTOR('',#393,1.); -#393 = DIRECTION('',(-0.695421216677,0.,0.718602345805)); -#394 = ORIENTED_EDGE('',*,*,#371,.T.); -#395 = ORIENTED_EDGE('',*,*,#80,.F.); -#396 = ORIENTED_EDGE('',*,*,#397,.F.); -#397 = EDGE_CURVE('',#388,#72,#398,.T.); -#398 = LINE('',#399,#400); -#399 = CARTESIAN_POINT('',(-27.78972445201,-5.,-65.71897950961)); -#400 = VECTOR('',#401,1.); -#401 = DIRECTION('',(0.,1.,0.)); -#402 = PLANE('',#403); -#403 = AXIS2_PLACEMENT_3D('',#404,#405,#406); -#404 = CARTESIAN_POINT('',(-29.01580660709,-5.,-64.4520272055)); -#405 = DIRECTION('',(-0.718602345805,0.,-0.695421216677)); -#406 = DIRECTION('',(0.695421216677,0.,-0.718602345805)); -#407 = ADVANCED_FACE('',(#408),#427,.T.); -#408 = FACE_BOUND('',#409,.F.); -#409 = EDGE_LOOP('',(#410,#419,#425,#426)); -#410 = ORIENTED_EDGE('',*,*,#411,.T.); -#411 = EDGE_CURVE('',#388,#412,#414,.T.); -#412 = VERTEX_POINT('',#413); -#413 = CARTESIAN_POINT('',(-25.60006917852,3.2,-65.75487614033)); -#414 = CIRCLE('',#415,1.548528137424); -#415 = AXIS2_PLACEMENT_3D('',#416,#417,#418); -#416 = CARTESIAN_POINT('',(-26.67694849991,3.2,-64.64210018823)); -#417 = DIRECTION('',(0.,-1.,0.)); -#418 = DIRECTION('',(1.,0.,0.)); -#419 = ORIENTED_EDGE('',*,*,#420,.T.); -#420 = EDGE_CURVE('',#412,#64,#421,.T.); -#421 = LINE('',#422,#423); -#422 = CARTESIAN_POINT('',(-25.60006917852,-5.,-65.75487614033)); -#423 = VECTOR('',#424,1.); -#424 = DIRECTION('',(0.,1.,0.)); -#425 = ORIENTED_EDGE('',*,*,#71,.F.); -#426 = ORIENTED_EDGE('',*,*,#397,.F.); -#427 = CYLINDRICAL_SURFACE('',#428,1.548528137424); -#428 = AXIS2_PLACEMENT_3D('',#429,#430,#431); -#429 = CARTESIAN_POINT('',(-26.67694849991,-5.,-64.64210018823)); -#430 = DIRECTION('',(0.,-1.,0.)); -#431 = DIRECTION('',(1.,0.,0.)); -#432 = ADVANCED_FACE('',(#433),#451,.T.); -#433 = FACE_BOUND('',#434,.T.); -#434 = EDGE_LOOP('',(#435,#443,#444,#445)); -#435 = ORIENTED_EDGE('',*,*,#436,.T.); -#436 = EDGE_CURVE('',#437,#412,#439,.T.); -#437 = VERTEX_POINT('',#438); -#438 = CARTESIAN_POINT('',(-20.57248334837,3.2,-60.88947335481)); -#439 = LINE('',#440,#441); -#440 = CARTESIAN_POINT('',(-10.78812989347,3.2,-51.42074976419)); -#441 = VECTOR('',#442,1.); -#442 = DIRECTION('',(-0.718602345805,0.,-0.695421216677)); -#443 = ORIENTED_EDGE('',*,*,#420,.T.); -#444 = ORIENTED_EDGE('',*,*,#63,.F.); -#445 = ORIENTED_EDGE('',*,*,#446,.F.); -#446 = EDGE_CURVE('',#437,#56,#447,.T.); -#447 = LINE('',#448,#449); -#448 = CARTESIAN_POINT('',(-20.57248334837,-5.,-60.88947335481)); -#449 = VECTOR('',#450,1.); -#450 = DIRECTION('',(0.,1.,0.)); -#451 = PLANE('',#452); -#452 = AXIS2_PLACEMENT_3D('',#453,#454,#455); -#453 = CARTESIAN_POINT('',(-25.60006917852,-5.,-65.75487614033)); -#454 = DIRECTION('',(0.695421216677,0.,-0.718602345805)); -#455 = DIRECTION('',(0.718602345805,0.,0.695421216677)); -#456 = ADVANCED_FACE('',(#457),#475,.F.); -#457 = FACE_BOUND('',#458,.F.); -#458 = EDGE_LOOP('',(#459,#467,#473,#474)); -#459 = ORIENTED_EDGE('',*,*,#460,.T.); -#460 = EDGE_CURVE('',#437,#461,#463,.T.); -#461 = VERTEX_POINT('',#462); -#462 = CARTESIAN_POINT('',(20.57248334837,3.2,-60.88947335481)); -#463 = LINE('',#464,#465); -#464 = CARTESIAN_POINT('',(10.487769931912,3.2,-60.88947335481)); -#465 = VECTOR('',#466,1.); -#466 = DIRECTION('',(1.,0.,0.)); -#467 = ORIENTED_EDGE('',*,*,#468,.T.); -#468 = EDGE_CURVE('',#461,#48,#469,.T.); -#469 = LINE('',#470,#471); -#470 = CARTESIAN_POINT('',(20.57248334837,-5.,-60.88947335481)); -#471 = VECTOR('',#472,1.); -#472 = DIRECTION('',(0.,1.,0.)); -#473 = ORIENTED_EDGE('',*,*,#55,.F.); -#474 = ORIENTED_EDGE('',*,*,#446,.F.); -#475 = PLANE('',#476); -#476 = AXIS2_PLACEMENT_3D('',#477,#478,#479); -#477 = CARTESIAN_POINT('',(20.57248334837,-5.,-60.88947335481)); -#478 = DIRECTION('',(0.,0.,1.)); -#479 = DIRECTION('',(-1.,0.,0.)); -#480 = ADVANCED_FACE('',(#481),#499,.F.); -#481 = FACE_BOUND('',#482,.F.); -#482 = EDGE_LOOP('',(#483,#491,#497,#498)); -#483 = ORIENTED_EDGE('',*,*,#484,.T.); -#484 = EDGE_CURVE('',#461,#485,#487,.T.); -#485 = VERTEX_POINT('',#486); -#486 = CARTESIAN_POINT('',(26.712845130625,3.2,-66.83175546171)); -#487 = LINE('',#488,#489); -#488 = CARTESIAN_POINT('',(11.552651954056,3.2,-52.16060938843)); -#489 = VECTOR('',#490,1.); -#490 = DIRECTION('',(0.718602345805,0.,-0.695421216677)); -#491 = ORIENTED_EDGE('',*,*,#492,.T.); -#492 = EDGE_CURVE('',#485,#40,#493,.T.); -#493 = LINE('',#494,#495); -#494 = CARTESIAN_POINT('',(26.712845130625,-5.,-66.83175546171)); -#495 = VECTOR('',#496,1.); -#496 = DIRECTION('',(0.,1.,0.)); -#497 = ORIENTED_EDGE('',*,*,#47,.F.); -#498 = ORIENTED_EDGE('',*,*,#468,.F.); -#499 = PLANE('',#500); -#500 = AXIS2_PLACEMENT_3D('',#501,#502,#503); -#501 = CARTESIAN_POINT('',(26.712845130625,-5.,-66.83175546171)); -#502 = DIRECTION('',(0.695421216677,0.,0.718602345805)); -#503 = DIRECTION('',(-0.718602345805,0.,0.695421216677)); -#504 = ADVANCED_FACE('',(#505),#523,.F.); -#505 = FACE_BOUND('',#506,.F.); -#506 = EDGE_LOOP('',(#507,#515,#521,#522)); -#507 = ORIENTED_EDGE('',*,*,#508,.T.); -#508 = EDGE_CURVE('',#485,#509,#511,.T.); -#509 = VERTEX_POINT('',#510); -#510 = CARTESIAN_POINT('',(32.56245481215,3.2,-60.7871552419)); -#511 = LINE('',#512,#513); -#512 = CARTESIAN_POINT('',(31.642948840372,3.2,-61.73731197059)); -#513 = VECTOR('',#514,1.); -#514 = DIRECTION('',(0.695421216677,0.,0.718602345805)); -#515 = ORIENTED_EDGE('',*,*,#516,.T.); -#516 = EDGE_CURVE('',#509,#32,#517,.T.); -#517 = LINE('',#518,#519); -#518 = CARTESIAN_POINT('',(32.56245481215,-5.,-60.7871552419)); -#519 = VECTOR('',#520,1.); -#520 = DIRECTION('',(0.,1.,0.)); -#521 = ORIENTED_EDGE('',*,*,#39,.F.); -#522 = ORIENTED_EDGE('',*,*,#492,.F.); -#523 = PLANE('',#524); -#524 = AXIS2_PLACEMENT_3D('',#525,#526,#527); -#525 = CARTESIAN_POINT('',(32.56245481215,-5.,-60.7871552419)); -#526 = DIRECTION('',(-0.718602345805,0.,0.695421216677)); -#527 = DIRECTION('',(-0.695421216677,0.,-0.718602345805)); -#528 = ADVANCED_FACE('',(#529),#540,.F.); -#529 = FACE_BOUND('',#530,.F.); -#530 = EDGE_LOOP('',(#531,#537,#538,#539)); -#531 = ORIENTED_EDGE('',*,*,#532,.T.); -#532 = EDGE_CURVE('',#509,#162,#533,.T.); -#533 = LINE('',#534,#535); -#534 = CARTESIAN_POINT('',(14.533354293065,3.2,-43.3396488543)); -#535 = VECTOR('',#536,1.); -#536 = DIRECTION('',(-0.718602345805,0.,0.695421216677)); -#537 = ORIENTED_EDGE('',*,*,#178,.T.); -#538 = ORIENTED_EDGE('',*,*,#31,.F.); -#539 = ORIENTED_EDGE('',*,*,#516,.F.); -#540 = PLANE('',#541); -#541 = AXIS2_PLACEMENT_3D('',#542,#543,#544); -#542 = CARTESIAN_POINT('',(26.824640127118,-5.,-55.23443461327)); -#543 = DIRECTION('',(-0.695421216677,0.,-0.718602345805)); -#544 = DIRECTION('',(0.718602345805,0.,-0.695421216677)); -#545 = ADVANCED_FACE('',(#546,#564,#614,#648,#659),#670,.F.); -#546 = FACE_BOUND('',#547,.F.); -#547 = EDGE_LOOP('',(#548,#549,#550,#551,#552,#553,#554,#555,#556,#557, - #558,#559,#560,#561,#562,#563)); -#548 = ORIENTED_EDGE('',*,*,#161,.F.); -#549 = ORIENTED_EDGE('',*,*,#532,.F.); -#550 = ORIENTED_EDGE('',*,*,#508,.F.); -#551 = ORIENTED_EDGE('',*,*,#484,.F.); -#552 = ORIENTED_EDGE('',*,*,#460,.F.); -#553 = ORIENTED_EDGE('',*,*,#436,.T.); -#554 = ORIENTED_EDGE('',*,*,#411,.F.); -#555 = ORIENTED_EDGE('',*,*,#387,.T.); -#556 = ORIENTED_EDGE('',*,*,#362,.F.); -#557 = ORIENTED_EDGE('',*,*,#338,.T.); -#558 = ORIENTED_EDGE('',*,*,#314,.T.); -#559 = ORIENTED_EDGE('',*,*,#289,.F.); -#560 = ORIENTED_EDGE('',*,*,#265,.T.); -#561 = ORIENTED_EDGE('',*,*,#240,.F.); -#562 = ORIENTED_EDGE('',*,*,#216,.T.); -#563 = ORIENTED_EDGE('',*,*,#192,.F.); -#564 = FACE_BOUND('',#565,.F.); -#565 = EDGE_LOOP('',(#566,#576,#584,#592,#600,#608)); -#566 = ORIENTED_EDGE('',*,*,#567,.F.); -#567 = EDGE_CURVE('',#568,#570,#572,.T.); -#568 = VERTEX_POINT('',#569); -#569 = CARTESIAN_POINT('',(16.626582997737,3.2,-8.940188245231)); -#570 = VERTEX_POINT('',#571); -#571 = CARTESIAN_POINT('',(4.383041634064E-04,3.2,-8.903751615529)); -#572 = LINE('',#573,#574); -#573 = CARTESIAN_POINT('',(4.347099726942,3.2,-8.913277437397)); -#574 = VECTOR('',#575,1.); -#575 = DIRECTION('',(-0.999997598615,0.,2.19152081707E-03)); -#576 = ORIENTED_EDGE('',*,*,#577,.F.); -#577 = EDGE_CURVE('',#578,#568,#580,.T.); -#578 = VERTEX_POINT('',#579); -#579 = CARTESIAN_POINT('',(19.029295926024,3.2,-17.63009960955)); -#580 = LINE('',#581,#582); -#581 = CARTESIAN_POINT('',(19.849519003668,3.2,-20.59660707692)); -#582 = VECTOR('',#583,1.); -#583 = DIRECTION('',(-0.26649542889,0.,0.963836182336)); -#584 = ORIENTED_EDGE('',*,*,#585,.F.); -#585 = EDGE_CURVE('',#586,#578,#588,.T.); -#586 = VERTEX_POINT('',#587); -#587 = CARTESIAN_POINT('',(22.059435554995,3.2,-3.734519760785)); -#588 = LINE('',#589,#590); -#589 = CARTESIAN_POINT('',(17.698510515043,3.2,-23.73280021221)); -#590 = VECTOR('',#591,1.); -#591 = DIRECTION('',(-0.213058124893,0.,-0.977039526026)); -#592 = ORIENTED_EDGE('',*,*,#593,.F.); -#593 = EDGE_CURVE('',#594,#586,#596,.T.); -#594 = VERTEX_POINT('',#595); -#595 = CARTESIAN_POINT('',(-21.72552223146,3.2,-3.734519760785)); -#596 = LINE('',#597,#598); -#597 = CARTESIAN_POINT('',(0.297084840953,3.2,-3.734519760785)); -#598 = VECTOR('',#599,1.); -#599 = DIRECTION('',(1.,0.,0.)); -#600 = ORIENTED_EDGE('',*,*,#601,.F.); -#601 = EDGE_CURVE('',#602,#594,#604,.T.); -#602 = VERTEX_POINT('',#603); -#603 = CARTESIAN_POINT('',(-21.72552223146,3.2,-8.903751135252)); -#604 = LINE('',#605,#606); -#605 = CARTESIAN_POINT('',(-21.72552223146,3.2,-19.83215600037)); -#606 = VECTOR('',#607,1.); -#607 = DIRECTION('',(0.,0.,1.)); -#608 = ORIENTED_EDGE('',*,*,#609,.F.); -#609 = EDGE_CURVE('',#570,#602,#610,.T.); -#610 = LINE('',#611,#612); -#611 = CARTESIAN_POINT('',(-5.279852300138,3.2,-8.903751135252)); -#612 = VECTOR('',#613,1.); -#613 = DIRECTION('',(-1.,0.,0.)); -#614 = FACE_BOUND('',#615,.F.); -#615 = EDGE_LOOP('',(#616,#626,#634,#642)); -#616 = ORIENTED_EDGE('',*,*,#617,.F.); -#617 = EDGE_CURVE('',#618,#620,#622,.T.); -#618 = VERTEX_POINT('',#619); -#619 = CARTESIAN_POINT('',(5.809375885494,3.2,-13.06417917474)); -#620 = VERTEX_POINT('',#621); -#621 = CARTESIAN_POINT('',(2.688069798796,3.2,-49.64588621989)); -#622 = LINE('',#623,#624); -#623 = CARTESIAN_POINT('',(4.914157977861,3.2,-23.55613296875)); -#624 = VECTOR('',#625,1.); -#625 = DIRECTION('',(-8.501532861638E-02,0.,-0.996379643459)); -#626 = ORIENTED_EDGE('',*,*,#627,.F.); -#627 = EDGE_CURVE('',#628,#618,#630,.T.); -#628 = VERTEX_POINT('',#629); -#629 = CARTESIAN_POINT('',(-5.809375885494,3.2,-13.06417917474)); -#630 = LINE('',#631,#632); -#631 = CARTESIAN_POINT('',(1.615747408047,3.2,-13.06417917474)); -#632 = VECTOR('',#633,1.); -#633 = DIRECTION('',(1.,0.,-3.066574716487E-16)); -#634 = ORIENTED_EDGE('',*,*,#635,.F.); -#635 = EDGE_CURVE('',#636,#628,#638,.T.); -#636 = VERTEX_POINT('',#637); -#637 = CARTESIAN_POINT('',(-2.688069798796,3.2,-49.64588621989)); -#638 = LINE('',#639,#640); -#639 = CARTESIAN_POINT('',(-4.890802006217,3.2,-23.82986495424)); -#640 = VECTOR('',#641,1.); -#641 = DIRECTION('',(-8.501532861638E-02,0.,0.996379643459)); -#642 = ORIENTED_EDGE('',*,*,#643,.F.); -#643 = EDGE_CURVE('',#620,#636,#644,.T.); -#644 = LINE('',#645,#646); -#645 = CARTESIAN_POINT('',(1.615747408047,3.2,-49.64588621989)); -#646 = VECTOR('',#647,1.); -#647 = DIRECTION('',(-1.,0.,0.)); -#648 = FACE_BOUND('',#649,.F.); -#649 = EDGE_LOOP('',(#650)); -#650 = ORIENTED_EDGE('',*,*,#651,.F.); -#651 = EDGE_CURVE('',#652,#652,#654,.T.); -#652 = VERTEX_POINT('',#653); -#653 = CARTESIAN_POINT('',(-11.6927539352,3.2,-48.00951684793)); -#654 = CIRCLE('',#655,4.735522705283); -#655 = AXIS2_PLACEMENT_3D('',#656,#657,#658); -#656 = CARTESIAN_POINT('',(-16.42827664048,3.2,-48.00951684793)); -#657 = DIRECTION('',(-0.,1.,0.)); -#658 = DIRECTION('',(1.,0.,0.)); -#659 = FACE_BOUND('',#660,.F.); -#660 = EDGE_LOOP('',(#661)); -#661 = ORIENTED_EDGE('',*,*,#662,.F.); -#662 = EDGE_CURVE('',#663,#663,#665,.T.); -#663 = VERTEX_POINT('',#664); -#664 = CARTESIAN_POINT('',(21.163799345768,3.2,-48.00951684793)); -#665 = CIRCLE('',#666,4.735522705283); -#666 = AXIS2_PLACEMENT_3D('',#667,#668,#669); -#667 = CARTESIAN_POINT('',(16.428276640485,3.2,-48.00951684793)); -#668 = DIRECTION('',(-0.,1.,0.)); -#669 = DIRECTION('',(1.,0.,0.)); -#670 = PLANE('',#671); -#671 = AXIS2_PLACEMENT_3D('',#672,#673,#674); -#672 = CARTESIAN_POINT('',(0.403056515455,3.2,-33.34517655273)); -#673 = DIRECTION('',(0.,1.,0.)); -#674 = DIRECTION('',(1.,0.,0.)); -#675 = ADVANCED_FACE('',(#676),#701,.T.); -#676 = FACE_BOUND('',#677,.T.); -#677 = EDGE_LOOP('',(#678,#686,#694,#700)); -#678 = ORIENTED_EDGE('',*,*,#679,.F.); -#679 = EDGE_CURVE('',#680,#568,#682,.T.); -#680 = VERTEX_POINT('',#681); -#681 = CARTESIAN_POINT('',(16.626582997737,0.,-8.940188245231)); -#682 = LINE('',#683,#684); -#683 = CARTESIAN_POINT('',(16.626582997737,-22.,-8.940188245231)); -#684 = VECTOR('',#685,1.); -#685 = DIRECTION('',(0.,1.,0.)); -#686 = ORIENTED_EDGE('',*,*,#687,.T.); -#687 = EDGE_CURVE('',#680,#688,#690,.T.); -#688 = VERTEX_POINT('',#689); -#689 = CARTESIAN_POINT('',(-5.329070518201E-15,0.,-8.903751135252)); -#690 = LINE('',#691,#692); -#691 = CARTESIAN_POINT('',(-18.54556462154,1.7763568394E-15, - -8.863107566442)); -#692 = VECTOR('',#693,1.); -#693 = DIRECTION('',(-0.999997598615,0.,2.19152081707E-03)); -#694 = ORIENTED_EDGE('',*,*,#695,.T.); -#695 = EDGE_CURVE('',#688,#570,#696,.T.); -#696 = LINE('',#697,#698); -#697 = CARTESIAN_POINT('',(-5.329070518201E-15,-22.,-8.903751135252)); -#698 = VECTOR('',#699,1.); -#699 = DIRECTION('',(0.,1.,0.)); -#700 = ORIENTED_EDGE('',*,*,#567,.F.); -#701 = PLANE('',#702); -#702 = AXIS2_PLACEMENT_3D('',#703,#704,#705); -#703 = CARTESIAN_POINT('',(8.237581109188,-22.,-8.921803528809)); -#704 = DIRECTION('',(-2.19152081707E-03,0.,-0.999997598615)); -#705 = DIRECTION('',(-0.999997598615,0.,2.19152081707E-03)); -#706 = ADVANCED_FACE('',(#707),#725,.T.); -#707 = FACE_BOUND('',#708,.T.); -#708 = EDGE_LOOP('',(#709,#717,#723,#724)); -#709 = ORIENTED_EDGE('',*,*,#710,.F.); -#710 = EDGE_CURVE('',#711,#578,#713,.T.); -#711 = VERTEX_POINT('',#712); -#712 = CARTESIAN_POINT('',(19.029295926024,0.,-17.63009960955)); -#713 = LINE('',#714,#715); -#714 = CARTESIAN_POINT('',(19.029295926024,-22.,-17.63009960955)); -#715 = VECTOR('',#716,1.); -#716 = DIRECTION('',(0.,1.,0.)); -#717 = ORIENTED_EDGE('',*,*,#718,.T.); -#718 = EDGE_CURVE('',#711,#680,#719,.T.); -#719 = LINE('',#720,#721); -#720 = CARTESIAN_POINT('',(23.053273013696,0.,-32.18365022821)); -#721 = VECTOR('',#722,1.); -#722 = DIRECTION('',(-0.26649542889,0.,0.963836182336)); -#723 = ORIENTED_EDGE('',*,*,#679,.T.); -#724 = ORIENTED_EDGE('',*,*,#577,.F.); -#725 = PLANE('',#726); -#726 = AXIS2_PLACEMENT_3D('',#727,#728,#729); -#727 = CARTESIAN_POINT('',(17.956031892536,-22.,-13.7484168618)); -#728 = DIRECTION('',(-0.963836182336,0.,-0.26649542889)); -#729 = DIRECTION('',(-0.26649542889,0.,0.963836182336)); -#730 = ADVANCED_FACE('',(#731),#749,.T.); -#731 = FACE_BOUND('',#732,.T.); -#732 = EDGE_LOOP('',(#733,#741,#747,#748)); -#733 = ORIENTED_EDGE('',*,*,#734,.T.); -#734 = EDGE_CURVE('',#688,#735,#737,.T.); -#735 = VERTEX_POINT('',#736); -#736 = CARTESIAN_POINT('',(-21.72552223146,0.,-8.903751135252)); -#737 = LINE('',#738,#739); -#738 = CARTESIAN_POINT('',(-28.21385794834,0.,-8.903751135252)); -#739 = VECTOR('',#740,1.); -#740 = DIRECTION('',(-1.,0.,0.)); -#741 = ORIENTED_EDGE('',*,*,#742,.T.); -#742 = EDGE_CURVE('',#735,#602,#743,.T.); -#743 = LINE('',#744,#745); -#744 = CARTESIAN_POINT('',(-21.72552223146,-22.,-8.903751135252)); -#745 = VECTOR('',#746,1.); -#746 = DIRECTION('',(0.,1.,0.)); -#747 = ORIENTED_EDGE('',*,*,#609,.F.); -#748 = ORIENTED_EDGE('',*,*,#695,.F.); -#749 = PLANE('',#750); -#750 = AXIS2_PLACEMENT_3D('',#751,#752,#753); -#751 = CARTESIAN_POINT('',(-10.96276111573,-22.,-8.903751135252)); -#752 = DIRECTION('',(0.,0.,-1.)); -#753 = DIRECTION('',(0.,1.,0.)); -#754 = ADVANCED_FACE('',(#755),#773,.T.); -#755 = FACE_BOUND('',#756,.T.); -#756 = EDGE_LOOP('',(#757,#765,#766,#767)); -#757 = ORIENTED_EDGE('',*,*,#758,.T.); -#758 = EDGE_CURVE('',#759,#711,#761,.T.); -#759 = VERTEX_POINT('',#760); -#760 = CARTESIAN_POINT('',(22.059435554995,0.,-3.734519760785)); -#761 = LINE('',#762,#763); -#762 = CARTESIAN_POINT('',(12.741012548745,1.7763568394E-15, - -46.46683800444)); -#763 = VECTOR('',#764,1.); -#764 = DIRECTION('',(-0.213058124893,0.,-0.977039526026)); -#765 = ORIENTED_EDGE('',*,*,#710,.T.); -#766 = ORIENTED_EDGE('',*,*,#585,.F.); -#767 = ORIENTED_EDGE('',*,*,#768,.F.); -#768 = EDGE_CURVE('',#759,#586,#769,.T.); -#769 = LINE('',#770,#771); -#770 = CARTESIAN_POINT('',(22.059435554995,-22.,-3.734519760785)); -#771 = VECTOR('',#772,1.); -#772 = DIRECTION('',(0.,1.,0.)); -#773 = PLANE('',#774); -#774 = AXIS2_PLACEMENT_3D('',#775,#776,#777); -#775 = CARTESIAN_POINT('',(20.484588228021,-22.,-10.95643672209)); -#776 = DIRECTION('',(0.977039526026,0.,-0.213058124893)); -#777 = DIRECTION('',(-0.213058124893,0.,-0.977039526026)); -#778 = ADVANCED_FACE('',(#779),#797,.T.); -#779 = FACE_BOUND('',#780,.T.); -#780 = EDGE_LOOP('',(#781,#789,#795,#796)); -#781 = ORIENTED_EDGE('',*,*,#782,.T.); -#782 = EDGE_CURVE('',#735,#783,#785,.T.); -#783 = VERTEX_POINT('',#784); -#784 = CARTESIAN_POINT('',(-21.72552223146,0.,-3.734519760785)); -#785 = LINE('',#786,#787); -#786 = CARTESIAN_POINT('',(-21.72552223146,0.,-38.64614663299)); -#787 = VECTOR('',#788,1.); -#788 = DIRECTION('',(0.,0.,1.)); -#789 = ORIENTED_EDGE('',*,*,#790,.T.); -#790 = EDGE_CURVE('',#783,#594,#791,.T.); -#791 = LINE('',#792,#793); -#792 = CARTESIAN_POINT('',(-21.72552223146,-22.,-3.734519760785)); -#793 = VECTOR('',#794,1.); -#794 = DIRECTION('',(0.,1.,0.)); -#795 = ORIENTED_EDGE('',*,*,#601,.F.); -#796 = ORIENTED_EDGE('',*,*,#742,.F.); -#797 = PLANE('',#798); -#798 = AXIS2_PLACEMENT_3D('',#799,#800,#801); -#799 = CARTESIAN_POINT('',(-21.72552223146,-22.,-6.319135448019)); -#800 = DIRECTION('',(-1.,0.,0.)); -#801 = DIRECTION('',(0.,1.,0.)); -#802 = ADVANCED_FACE('',(#803),#814,.T.); -#803 = FACE_BOUND('',#804,.T.); -#804 = EDGE_LOOP('',(#805,#811,#812,#813)); -#805 = ORIENTED_EDGE('',*,*,#806,.T.); -#806 = EDGE_CURVE('',#783,#759,#807,.T.); -#807 = LINE('',#808,#809); -#808 = CARTESIAN_POINT('',(-22.63692080725,0.,-3.734519760785)); -#809 = VECTOR('',#810,1.); -#810 = DIRECTION('',(1.,0.,0.)); -#811 = ORIENTED_EDGE('',*,*,#768,.T.); -#812 = ORIENTED_EDGE('',*,*,#593,.F.); -#813 = ORIENTED_EDGE('',*,*,#790,.F.); -#814 = PLANE('',#815); -#815 = AXIS2_PLACEMENT_3D('',#816,#817,#818); -#816 = CARTESIAN_POINT('',(0.19111316645,-22.,-3.734519760785)); -#817 = DIRECTION('',(0.,0.,1.)); -#818 = DIRECTION('',(0.,-1.,0.)); -#819 = ADVANCED_FACE('',(#820),#845,.F.); -#820 = FACE_BOUND('',#821,.F.); -#821 = EDGE_LOOP('',(#822,#832,#838,#839)); -#822 = ORIENTED_EDGE('',*,*,#823,.T.); -#823 = EDGE_CURVE('',#824,#826,#828,.T.); -#824 = VERTEX_POINT('',#825); -#825 = CARTESIAN_POINT('',(1.480297366167E-15,11.242400581089, - -46.71869179633)); -#826 = VERTEX_POINT('',#827); -#827 = CARTESIAN_POINT('',(3.256654205567E-15,17.8572529153, - -18.39898295202)); -#828 = LINE('',#829,#830); -#829 = CARTESIAN_POINT('',(2.6645352591E-15,18.133069549222, - -17.21814831317)); -#830 = VECTOR('',#831,1.); -#831 = DIRECTION('',(5.275122655165E-17,0.227455280238,0.97378852709)); -#832 = ORIENTED_EDGE('',*,*,#833,.F.); -#833 = EDGE_CURVE('',#618,#826,#834,.T.); -#834 = LINE('',#835,#836); -#835 = CARTESIAN_POINT('',(5.649679875255,3.602918464526,-13.21082950266 - )); -#836 = VECTOR('',#837,1.); -#837 = DIRECTION('',(-0.349023821871,0.880598971639,-0.320511814002)); -#838 = ORIENTED_EDGE('',*,*,#617,.T.); -#839 = ORIENTED_EDGE('',*,*,#840,.T.); -#840 = EDGE_CURVE('',#620,#824,#841,.T.); -#841 = LINE('',#842,#843); -#842 = CARTESIAN_POINT('',(1.103762571829,7.940067898719,-47.92064259636 - )); -#843 = VECTOR('',#844,1.); -#844 = DIRECTION('',(-0.299648208284,0.896513522642,0.326304236859)); -#845 = PLANE('',#846); -#846 = AXIS2_PLACEMENT_3D('',#847,#848,#849); -#847 = CARTESIAN_POINT('',(5.823691883056,3.068404028665,-13.45978839622 - )); -#848 = DIRECTION('',(0.93629059846,0.342020143326,-7.988827695448E-02)); -#849 = DIRECTION('',(-0.340781908463,0.939692620786,2.907695487825E-02) - ); -#850 = ADVANCED_FACE('',(#851),#861,.F.); -#851 = FACE_BOUND('',#852,.F.); -#852 = EDGE_LOOP('',(#853,#854,#860)); -#853 = ORIENTED_EDGE('',*,*,#833,.T.); -#854 = ORIENTED_EDGE('',*,*,#855,.T.); -#855 = EDGE_CURVE('',#826,#628,#856,.T.); -#856 = LINE('',#857,#858); -#857 = CARTESIAN_POINT('',(-5.275833888477,4.546144602338, - -13.55413574101)); -#858 = VECTOR('',#859,1.); -#859 = DIRECTION('',(-0.349023821871,-0.880598971639,0.320511814002)); -#860 = ORIENTED_EDGE('',*,*,#627,.T.); -#861 = PLANE('',#862); -#862 = AXIS2_PLACEMENT_3D('',#863,#864,#865); -#863 = CARTESIAN_POINT('',(2.828438300639,3.068404028665,-13.01628215822 - )); -#864 = DIRECTION('',(2.88163763217E-16,0.342020143326,0.939692620786)); -#865 = DIRECTION('',(-1.048830324052E-16,0.939692620786,-0.342020143326) - ); -#866 = ADVANCED_FACE('',(#867),#877,.F.); -#867 = FACE_BOUND('',#868,.F.); -#868 = EDGE_LOOP('',(#869,#875,#876)); -#869 = ORIENTED_EDGE('',*,*,#870,.T.); -#870 = EDGE_CURVE('',#636,#824,#871,.T.); -#871 = LINE('',#872,#873); -#872 = CARTESIAN_POINT('',(-0.871390517001,8.635298785064, - -47.66759924779)); -#873 = VECTOR('',#874,1.); -#874 = DIRECTION('',(0.299648208284,0.896513522642,0.326304236859)); -#875 = ORIENTED_EDGE('',*,*,#840,.F.); -#876 = ORIENTED_EDGE('',*,*,#643,.T.); -#877 = PLANE('',#878); -#878 = AXIS2_PLACEMENT_3D('',#879,#880,#881); -#879 = CARTESIAN_POINT('',(2.828438300639,3.068404028665,-49.69378323641 - )); -#880 = DIRECTION('',(0.,0.342020143326,-0.939692620786)); -#881 = DIRECTION('',(0.,0.939692620786,0.342020143326)); -#882 = ADVANCED_FACE('',(#883),#889,.F.); -#883 = FACE_BOUND('',#884,.F.); -#884 = EDGE_LOOP('',(#885,#886,#887,#888)); -#885 = ORIENTED_EDGE('',*,*,#635,.T.); -#886 = ORIENTED_EDGE('',*,*,#855,.F.); -#887 = ORIENTED_EDGE('',*,*,#823,.F.); -#888 = ORIENTED_EDGE('',*,*,#870,.F.); -#889 = PLANE('',#890); -#890 = AXIS2_PLACEMENT_3D('',#891,#892,#893); -#891 = CARTESIAN_POINT('',(-5.782806207227,3.068404028665, - -13.93896851312)); -#892 = DIRECTION('',(-0.93629059846,0.342020143326,-7.988827695448E-02) - ); -#893 = DIRECTION('',(0.340781908463,0.939692620786,2.907695487825E-02)); -#894 = ADVANCED_FACE('',(#895),#914,.T.); -#895 = FACE_BOUND('',#896,.T.); -#896 = EDGE_LOOP('',(#897,#906,#912,#913)); -#897 = ORIENTED_EDGE('',*,*,#898,.T.); -#898 = EDGE_CURVE('',#899,#899,#901,.T.); -#899 = VERTEX_POINT('',#900); -#900 = CARTESIAN_POINT('',(-11.6927539352,0.,-48.00951684793)); -#901 = CIRCLE('',#902,4.735522705283); -#902 = AXIS2_PLACEMENT_3D('',#903,#904,#905); -#903 = CARTESIAN_POINT('',(-16.42827664048,0.,-48.00951684793)); -#904 = DIRECTION('',(-0.,1.,0.)); -#905 = DIRECTION('',(1.,0.,0.)); -#906 = ORIENTED_EDGE('',*,*,#907,.T.); -#907 = EDGE_CURVE('',#899,#652,#908,.T.); -#908 = LINE('',#909,#910); -#909 = CARTESIAN_POINT('',(-11.6927539352,-22.,-48.00951684793)); -#910 = VECTOR('',#911,1.); -#911 = DIRECTION('',(0.,1.,0.)); -#912 = ORIENTED_EDGE('',*,*,#651,.F.); -#913 = ORIENTED_EDGE('',*,*,#907,.F.); -#914 = CYLINDRICAL_SURFACE('',#915,4.735522705283); -#915 = AXIS2_PLACEMENT_3D('',#916,#917,#918); -#916 = CARTESIAN_POINT('',(-16.42827664048,-22.,-48.00951684793)); -#917 = DIRECTION('',(0.,1.,0.)); -#918 = DIRECTION('',(1.,0.,0.)); -#919 = ADVANCED_FACE('',(#920),#939,.T.); -#920 = FACE_BOUND('',#921,.T.); -#921 = EDGE_LOOP('',(#922,#931,#937,#938)); -#922 = ORIENTED_EDGE('',*,*,#923,.T.); -#923 = EDGE_CURVE('',#924,#924,#926,.T.); -#924 = VERTEX_POINT('',#925); -#925 = CARTESIAN_POINT('',(21.163799345768,0.,-48.00951684793)); -#926 = CIRCLE('',#927,4.735522705283); -#927 = AXIS2_PLACEMENT_3D('',#928,#929,#930); -#928 = CARTESIAN_POINT('',(16.428276640485,0.,-48.00951684793)); -#929 = DIRECTION('',(-0.,1.,0.)); -#930 = DIRECTION('',(1.,0.,0.)); -#931 = ORIENTED_EDGE('',*,*,#932,.T.); -#932 = EDGE_CURVE('',#924,#663,#933,.T.); -#933 = LINE('',#934,#935); -#934 = CARTESIAN_POINT('',(21.163799345768,-22.,-48.00951684793)); -#935 = VECTOR('',#936,1.); -#936 = DIRECTION('',(0.,1.,0.)); -#937 = ORIENTED_EDGE('',*,*,#662,.F.); -#938 = ORIENTED_EDGE('',*,*,#932,.F.); -#939 = CYLINDRICAL_SURFACE('',#940,4.735522705283); -#940 = AXIS2_PLACEMENT_3D('',#941,#942,#943); -#941 = CARTESIAN_POINT('',(16.428276640485,-22.,-48.00951684793)); -#942 = DIRECTION('',(0.,1.,0.)); -#943 = DIRECTION('',(1.,0.,0.)); -#944 = ADVANCED_FACE('',(#945),#953,.F.); -#945 = FACE_BOUND('',#946,.F.); -#946 = EDGE_LOOP('',(#947,#948,#949,#950,#951,#952)); -#947 = ORIENTED_EDGE('',*,*,#718,.T.); -#948 = ORIENTED_EDGE('',*,*,#687,.T.); -#949 = ORIENTED_EDGE('',*,*,#734,.T.); -#950 = ORIENTED_EDGE('',*,*,#782,.T.); -#951 = ORIENTED_EDGE('',*,*,#806,.T.); -#952 = ORIENTED_EDGE('',*,*,#758,.T.); -#953 = PLANE('',#954); -#954 = AXIS2_PLACEMENT_3D('',#955,#956,#957); -#955 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#956 = DIRECTION('',(0.,1.,0.)); -#957 = DIRECTION('',(0.,0.,1.)); -#958 = ADVANCED_FACE('',(#959),#962,.F.); -#959 = FACE_BOUND('',#960,.F.); -#960 = EDGE_LOOP('',(#961)); -#961 = ORIENTED_EDGE('',*,*,#898,.T.); -#962 = PLANE('',#963); -#963 = AXIS2_PLACEMENT_3D('',#964,#965,#966); -#964 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#965 = DIRECTION('',(0.,1.,0.)); -#966 = DIRECTION('',(0.,0.,1.)); -#967 = ADVANCED_FACE('',(#968),#971,.F.); -#968 = FACE_BOUND('',#969,.F.); -#969 = EDGE_LOOP('',(#970)); -#970 = ORIENTED_EDGE('',*,*,#923,.T.); -#971 = PLANE('',#972); -#972 = AXIS2_PLACEMENT_3D('',#973,#974,#975); -#973 = CARTESIAN_POINT('',(-45.46495478095,0.,-70.97315781796)); -#974 = DIRECTION('',(0.,1.,0.)); -#975 = DIRECTION('',(0.,0.,1.)); -#976 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3) -GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#980)) GLOBAL_UNIT_ASSIGNED_CONTEXT -((#977,#978,#979)) REPRESENTATION_CONTEXT('Context #1', - '3D Context with UNIT and UNCERTAINTY') ); -#977 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) ); -#978 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) ); -#979 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() ); -#980 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(5.E-06),#977, - 'distance_accuracy_value','confusion accuracy'); -#981 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7)); -ENDSEC; -END-ISO-10303-21; diff --git a/docs/CAD/design/mate-connectors/BearConnector_Female_Trimmed.stl b/docs/CAD/design/mate-connectors/BearConnector_Female_Trimmed.stl deleted file mode 100644 index 940dab90f7e3431fd6281ba9c866a27a04922029..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74884 zcmb`Qci3fBk*}BJ3e%h>Yh)|-}|a_M@RguZXffWb4UMcpZk3O z2FIT}%EY>l-Rnn|-gNGWbT_frTPktwo#&27cN1GZc8_bU^3b^>(%r-g3oG$&e>rzV zx|`Va7yscJcdos7M7o>!`F@qydZWc7(%rQL=>2Bf; z|NMO?&N*c9=>8X1`u^819???viUeDAYkXz7sd}K2k4SeD%eMWV z6Sur(@#tN*)a^SCSUjSo+z%3L)va;$@^`q#vvyfLdhNw^`xgrqk7y}tkYKBBjm!7^ zu4_E9-r^DI>4+ySyxr+{?vUy16*Yc$Z`Ipq%f%zonfTxTc$?cj{14}jPFYsBS6FTF zh?cquw(9Q3!(aH0YdrHm&mB>(n|RT7mDustb4R4RiQj(tR@b=YOXrSAcN34?a*Gq^ zyzSi4n+BEs(Iw}OXesxc1ns_lqX*p5@%InM-t8nlcG~Z~)s1_cGWyMj>-P8dIJt+| zZs*$S(%nxTk?!{D?%(~+HU8seZyAy9CT@IpCGL9ZX(Q6z#D=R?;*bA$`iOKlvG)m& zxK~$gb;gKvH}Tn}zjb1_4bB{q?k2AI<8PdJ#cGR2q`Qf0pI(V)JhEs+x|`T{lmBv! z=iPDEh;%ox-P3>V#G{`*dqldM_|+eO<-|k7b4H}Q3Er=~E4zuUzy6SGd~>tmh3%*oKCcpo?{Q)xXen!~zF#HQd&LQf zpruSawss|6y5RUk&{8H=zrH>bC%$rFB4{ZS@7kdfd+v8!B4{ZSU;Si#ijFzt&559; zOg#FR`V_r>;hPdcOPRQGUM2qRt#3>OEoEY-BkNPN#YM*^f|fF|%W3r~+VIQAB!ZSQ zapDD^@VoDh_vO2Umb!_XKV09=Pq_Ao(Mz|!-!1L-_~B#kU1~h`P`%q<{iY*Fq`Ng9 zd0u^L|9!ioMx?umKOFF|d-C9NM~_H%6AMnR?~-kQc=U*LH^K8mx|`tn`O!z~m~oxo z9yoTc9zSz~k>)sC?0^2!FFt7_-+uV8OPBs?!G^xQ?)Dch%^D=`xTO-$y6+Q-?jg?D z*zNx1vd=Ch-A(MW{4~8^=~+g(d4OBi&6rZJ$cK_|$cmk?tl=cupnm+Y{_N%&AKiYVsCDfp|nK5 zaUSV~zgx-ozhs3s-I$3@PFvoOT)qC1dDI}?O|TWUy9u_s@QM{&d$)BzI4=`_Ua*`W zx%HM?=J9T)hus8Qv47nJpF8Sh!dm%Lt1tZnU--Un-}~wF^aQt0b2mW^_OF|uUDBC& zbg&5=CGeGosM9u+>hTqaFWM%U%BXGOJw)f zTNb=D>}p%*je4bw6Y9q}LA_Fbzru^8F?`hiSN)LP=ttog*VBwg?nWUw#v^S2_2DF zdOGQ0CLaC#pIqZvPd%*HWkcL(-+ZYP(;$#-X1%e4uZ*T8)n^_-*ee z!o%#<{s;ZHYdn6_!2^vZ)Jmfz+RdYkJ|a9MUCO5)Tg69NyYK$Gfks|xrI8oy<`G#R z5gw8*<;6!o*)_J?bDx36YHFo%BJJieTptl0lD4<)InQ*BSKYqHKx0m|(&&|T^H{Wx z2oFh@^11Cdca2T%oV<)A2?7Q(>-PNljN&_LH69(xai2aS^s>ea z-g&HRY&q}N8AXKDYCN8lBSn2gc$mF9a=(SHan`k8no$f%t;QozIkwbCgooLy&!2yi zYpinhr89~;snvMQDo3OGi109bb?a7dagDoY&YMx3O0CADTseN#M}&vjtEWEy4A=Pf zD*MkUvZYqzaj_in>LbF#?A2)>T;v+-FTeSWVqt0(k+jgu8hwP`SMo4N)o$DOY}YvX z`47&c23zH*8VMbdClRd+4>PgDY3I1c@ayN!Q@l;BB3c*p^o05$y&Spw!yl{0bJsY2 zCq?LFH{yGtH$5Q@d6;8;H{Mk>_J9A{Hz}qkyAcr#z3B;Q$io~R96qIL+;Hl(NrSC& zbdZFO$diaVhKHG0`TDc`dVKm-t1eO8P_4$Jjyay#M}%J1`21(ibdBG9Y{w;vG^*8j zoH9os`-t!`d-blBPIrx6j(GDD#U|BiJhGW%mVHEcn7!KP>!-NJ(_eZ15=BzfYCIO2 zBP@MHc$mGisDW!J&S24jWoo6^0`;;+AEEn79?FuoYaZvRD{t!>2R^>TK-nH?C>ut- z>4~t*yiqCFoVC4cZ1%402FjmFBeHLyH$5Q@d6+YCUw-nyHBLQv%Yia(vOAuy%bB>N zUbLGNYu&t2BG@Wt;`%hAPuVMK$d+0~PB1*o8vk_9UhdVExBtUHSv|EH&k5#i-=WLB z)`?9Qt&s?}%Gtg?jqosgMGe_ftH?WshgsvcPaNo8t#tbn2Fg3Cl`_0+#e8HbeMIPy zws_C%p{{Y)XMR1SY_VD??@YTni`++qhono{b+4mc+{ zrQE;gn_c7IpSW~JnRT^Nj-Gm1qmR(@ArEto{wH5M!8PuD@6|KP_e(>S18kM+0Q!jV zkaQ_)f2h6__r3i6Gpa&RE7dd5Zmv$~Bf>+{rCjm7)4U&heChZZ)kdh5sw`+X*G$}Y zM*ZSkcEKPKY?bRW`ZS_X)U$gR8nUHUsurP#S>u7X*Kh5qe_3-zbtEEGk-}EFzNC)` zJ<_Fo^6@%``|dNoGEdblYNa|E+RarjeMERjx|FBgT*oELe|+e!o9Tc=jzm3g`5u8#;0 zvsb@(Z@nI?@BhP_RE?)rN(r}4t1l4N1!Z6nk-g4l{{$_aa z(vK#Bt#a*PpGNd4_k$X;rB+eJ7#?PgbzXhEd$rav*Dp~WqFO~2W6;wRdbdk2S2V8q zr8m09e|_s)NrSC&MI#9vktb0p86IZheditN8XJA~z9p)4RIBky$y@{3M}%J1xa3=J zaE-sM{MZszN2=9$ZDp>S{QfxyIPu8gE6$Q&t6VkNrxAV1{h)?ysa4c(hKE_>;_F}S zUVZ1$wP#gNsaE6lo4L;Nj)NCCamRDlO9WfxI?Fzd@GyHt4cStwsQ3&Iv&PBi@9JJ{ zviv5qs@YVl@rut}%X#X{cd9)C*@gvscuREwxgmH9gE4uYG(6_i6*1 z>yTipTq#RJN2H;ZI;1mk+q<`Ojg=Q|KTCqGxcXhls=8< zQ|<>fWJ|4LwNrSQHCA7=hI@6A&5pEQNv+0LJ8>OTDSbrfWsQSweS&K|{>cMpwZciQ z#@9!2hm-b(d=to4LrAT4@ay?Q*SD_KF1i zM7k7e$d)u>)mG?bjm7IMb+0zx>&>%TccoUbYAfjJ34I5p#}!DL?xY%)AJ|++>)_OCd=(wn$Cc7YgooLyBMv-vo;0qsxsKMs zsnz(pJFbr_rH=>?vsVY6^F!A-#O69$ou^jgtMj;OuatKUH<*!Eo7>EV1Y2=MT`7GU z(Wl%GYRHya#rnbUFl+4p+r8bZ57=BsD*)AMd<7s^29|Qe!l4uA+RTLnTXCIVDSaB@ zVfN~mSA5hpRZ{(Dnqr3 zRfa)NPw3q)J+4rjKOxcV>?IwDWB){=B4e)+lkT;r3AkDS%oM-k&| zAGs#d&Q%W)dRgO;)gN<>>%aMiS*?9ktMRp#ToYN!TMl1wpyy-5;Q@(YE3SzwrB5UJ zl>4#jV{5y{i8gbQEw$3>Oxoot%Ip;h_K9>U)Q~M{#Cp!q%NqARXFd07jYAg9YE_$B zjj!kAYPVAQh|tR#$E>)CYrOxxyUuFWr&^7#`s8ZRQu>JSFnhJqDO>qJ0uZD#t6?xRbu0sj~eb{{b22}wxu^&hujb)2)2TU@ai>|>ylS?24-1RtZfnF z6~^|h9V6H(^yqDSc84M@f9Ad`<3!Dav#@eq$ z3|Z;KiX>J&B_e6CHja9U7}755i#j!iY{mMbPNL8gRvxlKFndM2tQMM1V{KGWQZH-J zE~|y6(_kei^^!)q9wc~15;3GFtVrtgYRFcsFX|@Piq%4$L}6_$>xES*tEWqKy{R9C zy(K|Qi5Suo))#eqMGe+HQ!i`KF6)arHHK`(YN2j|tyq`SNfg#;v(}we?71Jb%SxVZ z4W1v?&{I2UB(F%&!$b_}39E&=z2ZBO>kW8ilSaC)NU(pI;1Lq+QzC}+ofS#l{a`EB z7j+YC#oC{4f~{El(@n4yD|xyJwqjjQH^Ek{dg&yFJVJu^Lb@L5E&AhIuk~@z*H2k% z8Lwt0XqQB{25pgu?bk22aB0>!`j=l_s(0T{SK4QpZQI(Py8VJ%e(_>$%fn32tG8}@ znfK$g&9@uf^OH+``_MDC{rkFYA2aaC>(9Q#TmAa`+obxUZjCceyxfUp_ii_$UM6Vw zH>-cliMPFET8%$n_Hie^ebEa?)XN&Qd+NI{a$@n-FPO3)yMFBoC)PjsMI-8E4W8YH z{`3(iUb)c=MsWovuLnILK}+2lmoL52HFnry`w>0N1nur|@P$t7bj9;~yrTLspRc3- zOa8R|hZ_u2IWPJHvIJ`Fy1Gs~YhU(en0tFOJR`x&I&2hKX*HJ-9=p9Wjq{<-%$ z@!eau>gjphae8Nb_4MoAD{b=~lY36P+urjY*VycXeHv`F{S)5p#JofM2)0`7+3$4X zg)@CbYBm4uPHenc9}zv*v-V>Ud{^e4v(+yTUhEoAS#PURH^Ek~KJ#r;5Nvh8M~7`c zx;5D9pySSRLg%WRV5=2Bf4UR89^C|6t@+neoX|DzCfMq>*PQ5t?!-=F_}ufa_EsPH z_gzxG==+~t_wV!NUGo=b`0W3zN6xr+=S0v_x`M-(ZTM-|_`t*S6G2OvIC_^#obuAZgH2CPMw;&1MooOPPosF2BRPM9@+u;w=7h_ni_! zOPPo(wbem8CW4kS@sZ0v<=5lQC%+^Sw3LYpR;t8~=f5}+w3G?H&sm2|OPSzTjRY-a zf~~@nCtmilQS@Wt-YtA4KK_;C{4D6*-uZkKYJ~5*EPnZjdf6-5z3sVgbPau@rqhVt z?t8>;BkE-h+P&oZqg+GZ+UYdn`})=SyN{@sHE4I<9*4Vz#thSG#5mz+PurvYo=Y!l z(C&}_@CMh=*k?M881L-%&>ro$M0#0+b}wCWkZWkvH=Ra|+YbHGE86ju^s)x+o_E;+ zuA$NEbQ&>Y{le-0)Q%ISmo;ejLznF58XAR9rx7FX8@FH3j*z97HE8z>|N0u&&}?El zjhI2K^zedqq%Xa!LAy8nZXegsykA5 zVATsNM@Z+MvlS~xx(QZ`u;PMr)?n=i30leoZIS5qibqIvpDVUvRYy0$R;-_xAcm2Z zVI>C%TJoN^1S=p|sX#gtY{jg8H^Ek{3Fs!+iZub<1Y0rV-c2w^&nzn=K8NpaD@{!Xekq1A4P(eGQrhOBxorUT)RYqmNLP0J|t)<6I>-kf|fGD zRYD|aDHB}tLxPqv!4*6tXekq1k3)i%GQrg~BxorUTq{F@mNLPWFC=Iw6I{naf|fGD zRVgHBDHB{VLV}hu!4)GUXekqXwn^}5&IH$gkf3%Zxc-9#EoFjhGf2=nb|$#qnFK9mf@_gU&{8J27MTPs zWrFL3NzhUvhB4cof5I#M*FyFEP*qUOkTu^)MB$f(8q`k2Fro%O|9RG+b|xa0am$|j zB(G>G6A@wg*K0CCOPPqc&oO_^1TAGEqEXkq>{ZDtTFOMkclUd1CTJ-W5kr3Izhr`z zG7)j=J-(j_TFOMk!Vmm)CTJ-W5pO^C|73!eG7&NTae|gI5qX1guV^U|h0iwcMBbr^ z7)JhM{9I8x6Om&XzaF%diOA!OUvpZ@MC6Lb?<-o$L}akW?_FBTL}bavpATBfMC9(q zpFvv6L}V1lpL1HuMC3Wg-#)aI3C3Y~&1oqSDI!dQmNLP&J_$ytGI7OLJNhVG`H$DG z`aG{9IPAkOADy>u-LEW1CLTDY9#M{k1TAG^%m1o`@;D@DDHH2oxRZOOToDOc%EYVQ zJkJS5;z`g_CU!Y##tFsoNzhUz_W0Dm2}R{e&{8HYTV=izim{WRrA!=o;m%Gdf=z;! zGO=*)y`50?1_?$yGjZA8yE>r=HVJBHV$p32oKW=!32J9zo9$ligd*4^sGW&3Pkx0H zs@@<$?Mys0*xd<5ut`um6Fes*x+B7&p?VJz^fqh65yiYogr&)Nd-$vBjkySEXN@=~ zig}Zub|&Jyt9pY3wKEadRxxi9)XqfQKdRm!LG4V$^-z?Y1hq2}&w;8pNKiWykp)mx zo&>cs5l^RbkR+&`iO6Osj!%NxnTU6WGNUA@or%cGDDqE&+L?$qnew$HsGSLpok>tT z6Y+jk7MTRKGZF7_WvWTgQYPX%p=>w_TFOLxiXn-*N1vpgLdtiaH7dtPN89MPAByBp>qEV)LAw?ya6*-5(`nc@ z!wG$TsFyWp*J2A!s2*-Q4g1PCq3;#-vIgy1G{Om0=}o6$-z6vXg`-~9pk0e!IH4NI z=``$1=7he9)XN&QYY`78R0BDkhJ8hy(5#VqS%Y>hM)Lot5q33WrC!#cU5k;p#`NbZ z&X4A})XN&QYf%-~nEsl_b=ItydRc>ZEe_)v)8D&sziJ*$y{tjI7P)ba>7T)P?lkkJ zUe=&pi}kq1^zV{*e`u~wy{tjI79Dbp>EDU*9@OlfdRc>ZEuQ2W)4$u}eXe)_^|A)- zS_H~9`oC)Ye#Cc45d`XG4cfJsm22qhJl%61Ut~oPsFyWp*P>jmp;5+k8Zoj^1c7>4 zgLW-0<{BC;O{WnfB+bdGmo;eDDyUpTqrT}hVx*^d0QIs4?OI)zYiRU3okomU6%U|Z z)}URhHggS)!l%=Sk+yvrq(q9%h1e zttQpI(u}3sD{92dLfJR!WewW3+FIApjHO$Ht*qkL3C%yc3AVC2WG6J6=qA|8s+FD4 zNWYt4&u!-IM3aN`?a3S@=jSA7DW9u&!!?K915--)(H$y+rms#?NUJNfoxM$y(N`H^PTR!h)Q z_R7{Md8=k+SWD1SCTxw8w`x|OwFE6?!q)b9E6r=zb6U!Tt<-TsvlJ4vlnGmRarv|k%5zlk8 z4zkstb|&JT(X6#>30lfTyqB8woGn31nTU5^vu3m^^xrJ;39Eis&nh*sGv?XCZmDf@dr)N}3k zwdnZO5N|v5|61ie`!P-{D_4ItV%H7N7>M}ka}QX;d7pBE+OO69+OO@}k3K?nMI_qD zn(31bUbXRl=(y|I|MnAV6_$b!t$)J-^ODXV&P1i4+-Q}5|TXO0p zSI*-Mi`kxi_x=86KmN&f^B?)~OYEH(#3@Fw)is|vcJoi&^S=2TZ1bUInULD9rt$gn zkDujTO@fxxzLa@JeBu1#2lR?|ZU0hT8|7}@s{#7a9w9;PzY|SAoY1eskf4Y5OSiu| z$M36W4{o{9V&2Q7-o)7b-fd#~zODWJglG}UtE<0t)VFN+s&U%t7u{qrZ`WvI?5$nx z7oq*7^bz5qycJQsI%4^=y&rG;)L(D1n73;*G4|H3_KVPd``y|eLK^ZUd-dA&7rDlv zw{Nq=V&1ON#MoQA+Aj_5FQt!=hCIn${rrN{U1Pg@4_jg}Z`WvI?5$nxmxlJ2(nm-` zo@B2+c-*P3vGAIAFR_@nYcw(T)~@zTL;FkVBcvfuvRC_EbE0e9z0Q?OEavSRO^m&@ ztNqf@{!+fU=W$MKxz-JdU@MDkJE3uKDSaC9O1|fQP(!xVs)@?C9%hYWUw4dqb>Ibe zEU}okw`yYSttZ;AerSIweMIPGjW2xXFxPm=1rIKa@|IK!HjrA_wFln%r#loGa z>(5E(h|X0Lm2W-F#GQ`}Tw|@nx0tnzp^gHP``YcX%vXkzTGUG0~K_LuUX@7=_S z1D~;LBG}5})J|w#Qc9nOypr#^AJmX7wQ8dBt%q6Tkq_425&qFS3uY~X?X8-Kc}q`E zsJGISC2h-6DPMchI^Odm7r%PeVlr;GiI}&ov|mJx?;g3R{;u;|PdFeEY-Ld#Cln9r z*U(vzC)q1%$d+2k7Cp=w2i*6l|339&cf4WNqE1dIe#KVWuYPEMDSbrfkuK$gP3!M$ zuky(wXDt@y8j7mXuJ%hq`}+xL$P?0~JoJ-qdOz;_#T#cWLgyNa_0g{OOGEqn32Dd^ z(xv=m{m;3^tc?>bZs;0{K+>-EOGEqn32Dd^(xv?Vp$lDO!R05CKFu|rxXD?w7LRrfMYd^I`=z1%_Ll@6`nLbIg4cfS>_o7Y z#loFXRJ~tAUdearWewRyEW7r0k%z3!Y@i`_e+EC5?+zxtv5`d|My)SQh3 zTUp)!M4v`@NWD^wxXDz8@=CVUO6s(0*$kYk>XiOFOawh7oi$`DJfU9J_~1j|cCT~= zE&Jkxt}R<>zr3i|ydlb5MCg(BYraIC(0ye&A=l7#rd{oqMtko%p}U;~TUkEJiTXs@ zy(^;3r6I56JN5h-bPYYVvZYqCMZ1Kc0QTCUMG^yZ}AoWyW0BJ9eNw(Hm6Yd`s>Ydm1z z56ghMhQ0vQo0AyMMTA{h(suo-UA5_buJPYj9XV@xS=Z2)jCykt!?}pCD@)pLzs|0q z@401*T|*-P>di?E=OV(cENQ!rOKg-etN*8wj74i*Lt`B3%}EUBBEqgLX}gY=YkA zScZr)7ZG}-Ew=WZ!(8Jki)_BeG{`Z}S*Ix72n=Gs6gd)vsrTywhjfFQvnTrTL(jGncbESB@ za+Tk2(vh8WI8geS73?V2}$ z#!g{ZnNjLZPlVm@N|s9bx2ESmSnZ;D)L^UVx$?M~&=H*<9U+|wT)}y9mne%&dU`_r zkX~GKS!(aZVt4xtHP|ZdeC50|5%;T(kj}(i&GWIrj}DuO=Uh2@($f>_hxFo!lBM=t zqBld*V5@jDs0JYuI-={LBlIv6c*D)adsj6Rq^Bp;59!4_QI<-%d(Y$ii8^u38)oAB zq52EzO;1QecH`Y%D>?ARnThY9YF()}Js}O*jc=4JS*EpqYs=d{{9w{xtN3!NGB*=C zqU)g}^e_|6m_Z|e84_$2BLLM4d;Y`Et_U5GhK`WVL^F~pHd-3Q=tcF-q^BozuA~=Z zt-4kpRx!R(#dkaE(}--KBkG5a(8El;W-}j)mZg_$mCz7ZN%m4IQCg zCeE&7>T=xSTMS}UtV(;*(-S&Z(u)zf*X%3P;8)N_`bmSWVx(XHDhUZ4Q9pEq9%iDM zu@sx*3}Tj|l@Fw+C)5w=#SF>UP$-+>{jizWAm(RU!9l(032De~%1u>aH#9_2j zi*_R><14s&32Dd^(xtrpXEnC;j$gcS5b-3fIHO+H=p%H^P=5bLv|z9>uWoen{W+_?+z5l)t#Z0 zrPQ0AkcR9=MA+A;7DToO5k247RufZidO{kq8?kU%D&<3)dKRF(@49ma5tY|!V(Lv# zNJDlb3*hULwX^v*97L`_N2oVFAyc9w7g9B>AD&}TKO!q5PpCIN5gL&(spksWr9otS zbVjK+Js}O*joehd=E!*sBCn=9gL=~w(vaQAVAXpUxw}E+2X%*2Z+b!+vKzU(`V880 zKBFUg&LhvMCzX2B6S5?`k%O%75_?~{hTd1ofa)348;7kTGupo4ItkITOFCtE2a$Ky zo0E1U!`r^8T|;l`aUwh!`2hCCsiGVX(@1JIX@ zb|d3nzqQEfPY}|OC#3y*?mgG{JgO7)MW)@T9H?Uk8v%3@(vT;leOxlo-Jo$vR6S^9 zLAz0LQO7ud?BeHQq8uEm6(Kyiw%QOt4>O~_x z+Kn2Pc4XN}NJE~GPSrbusQ1x`m3E_&ryU`?hDONagf!#{=~VACh`J?>ylFS;ed^o* z6+#n)G~@~CROvK``m5&Gj64D*yHV-X&RD!3nz4)%(h!loI=HFW`moKTqAE-?A=-_K ztvXlhBcvfuNL&1_sa5-k%?G0%uK6{icBYp#`UpJ*@+2zV>MRvCcY~d;muI4$y7_G)f6hfm z<#LTcT9nhhQk0VfTSYx}jimNzs2|eEUQr_|$z_XrSp${iGg05({5FyILv&P<*NAW* zA+JPa4OGO>i#mNpnrSyG;@eobpDRVrNw8Ja>DPFBpN71W@6_{nd#e#E6l9BfSp#bt zcH%Gmu~lB7&_~EC>5=yQhkK=LhDHA8tCeyomXDaPH-j=5+OO@JW3g;TCm~t{>AVWU z@&m4+ObqR6zcgyDi0dZCiSUqgDVCe4URgfMBLA+T3>NKbzcgx=tdEdJlPiK(mL+qI zCKKlx%F)rTA~mj4SPGzckvMuxm6q;g(=4%K|u2Gu3?> z@~X+cq90s0AzNxCTl6q%SZ>0-Qa0Qo|4t|a&Q{v5e$*U&9}#+_y$--Nl-0M$ziTLy zPrKSLjkeaniKf<|CD_Wc08Z4J39B6F)R0$ARv-P~x(V4*E7_ulS;KM@uF+I-IH4L3 z($f>_ZBwJ+Ue#(5%T2h3YCkOU?;5K2uuMa%r)|;yGZC$1N!xAJi)%DhFRcb!S>C{j zT8s10rgf!-2p!S6(h+)?39H_5ji%nm3Dpj`v8f?v-p&FKb}VIcw2=Cz^`3mY$waZ<}g5??+pu=V!O6>vOxR_>-Q9ree>xC)Z6_ zexN0^EuyJB^zF%Y6C~It>XpKE6P6!v4cQ{C{nD!%Tm@m#ekW9c$yVAgqOHvIScd8> z#|d3Q*(L3{2`5zjY54)yP$entYQHqv3e!$P8uEm+#WI?86RKjh{D5nyB9?ZwUm9(t ztZU3gNJE~GPBAjo2)l+VdudnurO{UWx`yg^#|df36ViDVgyjcZqp2ir?P|X?YIQVM zT8tCYkSEzIuA8v@fNQ7%n|8Hd8nw#1kC28uA)Qx2Sbo4YRAWxN+Aoc^_T0}^Q+u8W zX*4zB@Cq?x)zCYkdUM*ntt<=R#N@h(aSeGT->GNoQn5Bew$w_t zNM{YMX|Vi&6I!9bR@$$AOs<7DXxC!;u2J6%TsJ|2dZe?4Y)M15 zNM{YMo3LoV6MEy2o|Eu(6XS%=sPwd7+wEIaXSe7rYSDhz(3_L=oP@8N7$?H6_G`O- z4Q$<=YiQk_Mf+VtUjWi`62rNOuq#X2u3t6FG`NP=4_dU}HS{H;-kij6E+Xv8lD6Bg zvuo(naU$$$zqTjWO^{$K%K|u2=LTFip(8q1Izl=VO{LRNvk8m#JE0i_TWP<% zs&gFFMokc*N7`4D`xR`~pSK#cYk33LsB<-2f8G*0BD*?54>Mt_;+<&L(L14;B3oJB zz%}X&vLVV`MCg$&#c~s_q2B|r{D5m{{z|*ruX9!BwU%jcqWP7AmS8K(0yt4)0F6eO zOG94Ccj}cw4cSsF*`kM8gX<PH*PP(OU##5fUpq-~boteep9Ojxwv zH59*~UG10E8k1>=G8Z8Yc|zKM{ld?#elNpf_pYHx5$$TfG-@=;aueQ^<6ViFz zghl>cL$NU0)qZKTF*4U^ep@6F(vT;l^STL(-8-SU80~7mG-|95RmJ*Ml|)EGo{;um zRq=DB-(j)HzY~fG(ysPPqsA;P)8PG>i;#vqA)VJvShU|Y6xpO*?UzQ4y;|PDy=s2t zrX|?QBJ@twh%nbpjBCg%`A$9mU7S`!w$w_tNM{Yp8#tlg_p!*o6N)smmG-M2H5T3w zWiBH0NauAE7VUQp#qMcW`=v3tZepB}hCCtdzn$-`CC`RlQc9p@P-tuJLnnr=6luTou?$EAtJs}ZC2kkR0lu}wu&!j zTRG4Y@jcfO(wX482@-4-BY?I7!ZlO@p(A>Vni>Y*ZmJ=?=apziGQ~zqs{Qa*O=U$( zXN^8W=Sp^CtW|42FuqC}Y!&0Hwj#y*VIwkCy|^b$O-buvCamhkiKb4bCDx`~Kis8W)4BbHHXIr|7{G&PW}QN2P;Mm4E^_o?EOb|WTJ zYf}3NX*9K*u2H?RDprrlsO~l5Nvd3>Ue@R%bj{^SM5EejT-Q)FZp6jBMi_tfZF)i) zvKvvgwyM}QREHe#JyjA@Z+b!+O%=1Bt6J;KbrTVFY$~Gt+BB8RQxeh;5mC-sv(0rA z5esc9yIn)ArYEE!B4WL@_8jqD)zG`$rb6Af)oOY|8X_VhTx;kN+16SBx4Utzv~TsM zC!`^}5et{4ylx_*@>+>Nz3B;Q$Zli->Y4`at^bBvo8i7UD<9If+WgN%w2E9vCAe-P zvNH07deal35gC(uu8{fBTAB75P;Yud8nPR?sd~+k^U|6h*U+6oz3B;Q$Zlk?>b=W# z6OkX(9ZtRJ32De~Wd7$`;OCL;T)Hx64xX0(07&B&JC zaN~sD?XpWc<%G2+&aa2woU|Jm-u6xH8hTTY6Vi|;q~jN3S)C-Vc4fXg4zM^{Zwp4qd~t`V)jSAY?ts$MkGqur=sX-Af>p^@b{Aq{y#I#t$aEwW#8jaX?nDtX!wvTJCBJWfbMo{&y8 zMT4k9(#V^3qu!^^4NxI8K}bWMkWRHxTDR@}(42#IqtdCJv3Nf;V;Lu;Ax}t`V(ZVH z(E9VJ3e!x8cB5je&ei$|X~+}OR`=Shn`qY2dp|UTq+ZtOBXqyXlc;p7vsBdF>GuF! zLo?o}Zd0r232BIkD!n>)M(yAr>IF5!rrz{~G-Nlb4QmVl6^;6R2JeR=1=O3KkcR9= z&1H>#*zZjEHP`P@S62ZH8>Am26QjYoPXAzl-C9V!CVPFN+^hcG}*~zTMP?`*zKSldw^dA8D#%eOpy1)T`~I=hU$GpcBg1 zQZEzqmxO(5ovyV8^omDFP&;c-orHZET%+dCsbS+WFMC>f5yzhXl`w zjqbgbs+-uq>=loYpof{DItglLg1(cWrA*j&!hKf;+}^dmtt_&A8GO6u&nr{!T@v=* z^;XL8lFl0R#NJU(DBIiJa~>f zJNtI6Hlzl7Ztq=hrJOMJvWCShyp^h0Ek@$ossyE8Ca6xr=FU#n`b2ugBP47l>_n{u zB*7yj*uU%*{Ut$fGr{v?Um5qL`TF>F&A8JmwhCI`SNq~PQNNrdqLr>eY>S{JZ$FGT zoWfj_R6QRL%FZMz}s7v>SJy#wc;k>nOv_v>F!wbX&TAEY|DWx@s1)^zB;r zN`gm7SOn6ET8(RjApb^%SL%5@+ZtuY{yHw1i_j4g>{D$Y8azTGo~Sxbqy~?Wh<8aH z@2V4_VKHy-q1V0QZ!_>LxI#-FjdYB2?B0)=;pe+*d zMXmEeV}{WW_SSM<-Vcp^CVJiyJVIjPdb9+OBm&Qh`Q-oV-A>lKfXpoiHj zUXK`;Dssm&oeB1X1pAZ;+9E+qnV>BaF(RuG$ja2S%P%Ae_Rrf7<9hJ?SY?HuQH|TX z&lT;)NLJC>81>couBo&dRw3ecH4?V^65rOioqE|Twz9e$Cp2c9ASC1W6}^hHpjbFP zbd8qa5fU+~t?_m>4-I-^RYC5RMy%6$6{8wu0jQU~iX-|?kYN8Z!SN0WT5_W8hgE;M z?;53A6`F5rglyGYzFn(7t4ck)v>RhBWtU=XSFL1dou2ad6FG88M9O zLEo)1&wbYyZlWJ8!6PKBj?C#=TT2bLvf4p!rLo}zA;|AUj@4pYnMz(KYwZ6j!6PJi z)-%B)BzWF3!6PJM22k^y#tfq$9Nm$K?a8qyZAB~gyv^{o8dewTXH+BaIJ+80$96jk zZwVfWZ4v+T^^kh>smX@B_C(Kzam_UrjeAVvXzJOS`un*W#uL?4O1fQ*KS?_w$nTOE zQ|qpd(WIWD7@bc(A0%R2s&@(LtifwQf@dKUJVJunP7IrV4CB0O>>Tezjr8M5Ye(TN z!6PIl&Q(jqd$*~3^s}o`F=_Y8{!`B`eUA~KzELr@tNGf>RDwrH#CU%4YY^vMb0-pU z4ku?tJVJukDffd%NU(oS45Q~9d6A&EnTRW>cVB#|HUEgS+s0%tq%*-* zG1JqWs+))!+E@)Jdzu59rTN;PjOm*#KcZGqxvDu}H^C!O>#TX3oe{Ux&ecq>UO^J} ztaz*O6?O7X|JrNrtzuoOjwljhXV>@ZI%%K&Rf0#N!c+wNRNDtZuT&AQqnfeM!%Xl9 z343mwZl5T_s#iQh!k$ehCi4b%$N7<1Su76}XJVhSSN2tNLbD0`X85*ZSy7W(S1(tl zdc`9ox;3sw;)W8ZGSAJ(R7@a)=Xz*}j~!Lv|(3W7&SP&*SmLV_M< zg01W;<6gxoF}0kG>D%|*Tj@(5m6D3w+Gxr5w~0dop zJYusKZzX~r*7ia0s>Qa(?KYlwT64op@Cb?MQ#=1?y^161Es3x+8QGrezLHl#>mJKq zg@)9lRWtwaen{JiVf5VQ&VEGqkIlS&TX%-doqfB#6I+5uNbpQ&uc99s(Z{o`aa%l5 zlhJdVrMkM_I2MiYZM})8*R2sxutxpV%LLC4iM+zCGWEXV5fbcQw?@2)d?eev`D*o! zF~hhXQ5~uGtHqhT=Xz`NEOcwc7eJ$M>N(MB(5}VaTti>A35}NE5fT&Eqa}F6qK>Yi z@9V@}(h{^wf|iQ@wS+~s+^)XoBxuPAK|ZqN`H4}6M&$8DZO7Ct!6PK%t35GpFBW@s zuQc9?nnaC*=wbGXcH;}DFf;Ea&0d8ckaC+_&2rmX_cV61!6PI>dvcD$BP3$v+|Hwh z(GSbfx$hd+S{~20HDcvi@P4!e&k2c%C#ofAiv%sXhW)3W-58~6ZW$w4MQ&o`Tk9M8}LlKMoaJriHYmc5>^M`8XBKl z)q`(qOl8@A-)`SNEx{ussF%Is5fT$#8CLH^o}ZW}DC!)e^T~_}kC2ENz+^<2M@YoD zeKKRh?{P9o+dZG^rhc!WfF+h!VCBD|71 z32M7n!?=R1J!MsCY`1fRmf(@t)>z%LuYM;^&Ohi$TpQgbFop`Z0`miRMcr;;l59G2sys z)XqJp?=ctAcQ9rE6L0EaymvL1iEpCjIMmA;aX09`in)lsuRMpD;1Lq^HWNHTf_-vg z*j)2rypc6`iZP1j1ME{Kc!WfE&tqnyFK3KiG(w7*OdF?ey^4{H<{2@{)Qp9C*(<7( z=)SLLiv%sXhW)2LAM8iWcQiI+pEAK~5Th`SKWQlwyh}*XQYL7N1U+S_iew~74w59t9bnm2yktZ`5HOHc1W^R` zo!t=$0ul^>AfO_WuOM(G>94-0`ZS+2^Y%FZ=lQ?S<5G6j=X_6f_jFe{RlViL+ibex zmjA!~Gv^hvrRuNUQQ~iZe{Q<|yV~T_yHlQh%F;hS|FrH)oxfZ9^!HEgDqnwvIBJnX zyn6fT-F*)!#N9WZ)}^IV-U;TApk5?yU-hr1{^F~rcYpf&A1%Gp{ik)Q9SP=;pk5@X z@r%QY#%|A@7W5*q@rKWuC$ldxxl4kUnu#Z0deZ2-{&iZQBXP|Zf3Z@_kDuHnLG4KJ zEPnmw=Z&6gu2TkFL0(%DN1XDy(Px}iUGuhIL6-X2!Y^8>zhBqx_7T~&a+ffN#Kh&_ zFnZOQd+)0gw)nHOzbw-oH-;uw(|cOedSy2o^$o&VozJn zub-LR{o=KMu=KJMCUsfASP$lqV4Y$;=*c#}e8n^ld-mjRvscU^@!(hgY4idI^w#74 z8On3;>+etQcIGSfxM{aZUDhwwgE=Hvr$`+3O!-aRxyY36CPzGOo^1E!NnL73f;l9p z7ta-Id(-b;HI3b8Il0?hbLNnkaM#;LKlQ_jJ=gsB+sggA{|ZyO6Mp-cm73$&NnO@2 z)`K}DSf@x_aZb4g2aZhXj;vNR?mA^sm)enF4hibTbH&;|{^0*wJ)T|Sq;7M~nM2~S z=iV`T`AH}DoU4oWD%ZI)eoFVKa~`!)lfO2p%lgH7Foy)|6p6)FFXw0X9j0`5eEIjL zvGLiHy3~#Yb4XAxo-5XNrJGC3;(t~@vD;j8=8(Acg?G)v(J!3TbFNsAtv*-k@vrkH zg*wH0yf|YyqZ{ltrAvaAVm;`|QWIY@PhQyYgl@A}%po!1*Y6p<-N#PssmJXP{myds z{?e50?N^rcjTcPn(%a^GkYJr6L5pBioHaDRt)_Q@VkGw^4&N99gZj zEY9BQ`0o4{yl;9pJ#~DkdE4JOJU=98DSEZbmw#in_S$7i_rmRqhwq*{sY`F8SIi;7 zIz^4E)_ue@*57tY_rV_*y$jEr)TMURU=9iDMS|yKo(IdhT4cN9x~H!5FVnv8vg3Nr z70(X|T8j1f+uRSEt$j9~(*5wfr5-Cxnbc+dqF2lz!8%2a$Deu7G#>rTlZZ6^q8q`%mi9QuK;BB&Zh&)^Phf%DGx; z$78y$F8goOo_B#`dhRQp9}=__>oLob_nWP~-k99|(YX&Q`C5R zr~AzA$-kf6{n*>(9=l+(NnL734d#%bUL<%a<10A`>39~gy)9@ zEya4gy78~99xL85x%-#BNi(WB@1nU$vp8U+crg7Rile>F8Qtrw-mYme3 zcGO@F3F<|H=j7In%DMW;7mn&~enB}`bDVf&&$;6HAwf&A9uro($LjIT{U>+-u~DhV z((~pvHR>0=Vh#z`DQdj1#@*#yjhozk>Gtw_{KH$Pb*UXSm_vekkzj2v-?N;n6}CKb zz`5dCBysb_|$`L(hm-V=Etx}J_{V|W?piZ&o8?S$t)qIcnCU;5DQmhAwy>BhQ z`CWc-TA-uGyW9QJG`4u^^ezcniW(#?J@QVY_q;CBk>L6H^Sr?+<=Mk~&MrMUeWcWU^Q%t_b&6im?#WvhuYSD2SGvs_%pvi&4T@K%U2<5DS1i^3 z@1n8K$l={)f;l8!c&@w$|Go90JsK=E%OA@7oyb2A@QpNi^h_3AJU`2QtKRB zG=8(rq1|SJIV67n>(a9P>Ahd>(O{`BzF*o`_pf(Ix0zrLi9;7G8ttzi)T6So*W2A9c^-2lRNwoHO1o zeYM$lI;gwXa;2C0i`Vw+A!wHbEya4!)(eXjji(Meu>0jV%LvOq?%TIVgLX;KQq*9n zO+Hon=X*>#V1NeI*Zl7?`my+T_wWAudu9A;t{?8x;}z|aprz;)ZS68s8IAhRJ^OWE z{>)qUw7cV?y?Zohmjo?E4ca>B!7|Eu#GCte7r3cBC2snky?Qigmjo?E4cc0KN*NJe zaQS_^doEBtCC=EhM}u}r&{EW(tqnf)f}NkV-#*=^b|`oL+vk0$M}u}r(2{Aiy>D>C z9e*>sH{Gyz_ZySTU2^JGdvr-hub4xk*(+)sw0W6BcxcTp2EC}kH_o0vzuY{0#tm{Q5GsaFM2|PmZDcIb=f>io85JOyW{{3s*^bSnBvtx&R(u~Ro9%Q7Tm6A{B@}% zruPwbJ(xq{D-RZpjqh8sM}wulerD15{r(@F-bd6L%pvjiLZ7m;@X}k0^=PovpMF|2 z=Kk)&)BA{8gE=Ig{#?6mb2gaPBYxy zqrpoj#Qa4_(q}e@Z z);p&)Yp_&nu0kBV-|Yhk+9I*^zm_$MY{o%T4%>;8ueB$_Jj9z!X^LjK`YLz95#=igeooUSkb4ZM@o)Qb~ z^wk~>=DhZ$kDDiR?E0-~n^*7lac`g0L(nb>T1wSlxl3s4_ZKZ;8r#l)!L%QA%Jo0< z-v{+*&@KsDiW)4n&~4?KAN$bG{Tgj=m+W@u;^xV73!gcS1T964p4QU(%2jKLEvG)h zN~ztJOVtD|MPi9}e`lqB`?s%kuer6PH+cQ@ZYF9=kzfu9>P3Pz-)ztSva_)KgoQgi z3(dp@UtZ9N)^7K9NE^}iv%C5^AF@&-OZ}+xk1s4@>Bo<_c9e9~U=E2sjf|E1iW(cO zwXkXIx6d6@Nk^|9xOM?6wbS?uJ0xf+dPU-;`4%#I?=5B@Assbd{`ve?>dyO4>X4wN zs6pbgKbG^h){Hlfkd7M5OrOs*&iU8Y9TKz@HAsBq%5oMSUv$nE>8P>Lw0TYABjcCr zkf5cgLE__IEN5Z4&wZvvI%;hDvw2M8*av3nkf5bVoN@KsMqIkuFGf$CyPSpd_u8{X zOVKOlkf2`F*!A4GOk?l=IeqlqTZ)JCtuU!Y?Wn;V64Z+t#~(GPXV z8R@})<{S*@z>kuG>yY&JVN6oH600hMS|Lq_@K7qM}D{M z+@{fc-i&DbTaD^>t-72M%bxPgwB~w{xc8iujri+gznw-pdc~Zpu9~;<>i!lzj9$?$ z30jI8v_;~BGje|`rPrT7BLADx7f$@vC^dTPboCQgt*<)u6P`mtX&u+~Pw5{suVEVc zuRKxC1NYx=X=y7hZMW1FTAz*0IcLeH+jge=J}>68|{=^fKpdcl)h ze3H?wj!Q$&;K776 z2yFw}RhvN?YNHG$q#;j8r?mb?x0=Sqj~&uti-mS|TpDU?4JM=^Pe`Y9|hOfCEuwRHQ45)hf(9>yS`^$%{IDQ zXgjl%+SKw&ZTOT15%NmDlTPXBH)roC=aZZ682SLTtGP3w~gnJ@ON^7(w-z7({Z5n$Vy-JJYJJcJVaJ#akbQvjHW`+$+<0G>z z+v122OX;|XGJZ9P(7BRb(kUIZz~-j0?uv`GII=~%IxdYesy2v_hCCsi(vDYdZ5l`Y z{X;E|+|jO%OQVbqUU|cgM*L*^*#p5+8fO$y#w-VE$Se6yz5IlO2FEPvVboY^<=xGz zrS5-cgd?derQ`CdjMn~b$$gA?Z_8%`!BQIc6;Z~i2WhAtsx9^Msy1kF44EEAjW?zo zXkM)|^1ukkyje=e<&{R&QyN6bEBQ`3rJw9@sA>H9`ddag-cGwZE{!q^Fo=+bJRzOZ z&(1!=G+wy+vJuWS(5{Y4qs(RuBBUWtNT+ngipQA7(w{wjgfk|ztK-rrvofo$e7q5_ z&AL+{SW5FdBFbFRAPspX->H`pH8@v952MCm=bdO?JvGy^Bb>n^Jv`yQ%aWa4n^${q zyOT|0|CL`DM-7(J44sHF+ed6Bjn(Mfha?%8gRQO>y2u8vEi%<2y!q#;j8r}W5mCz!@T7q2_Y z$N}2bacPw3gh7Nf(UnA)V4bcOGRL7fv{P zl#v{?tK-rr@g;)@X~+}ODZTc5c_wb1#~JeIz;u<;=m7Pi#vsByktLfZ~tOk>I2mTxg? zilua1#JLvZHHgremR-^*?Xbq@Oyd*xe!Ru#FWS{{X_Q#C&pfe}5u*<;5(t)3teS`t zK{rT4Udearr9=(Jz0t#{vG0uAn^%jUICqQDcr2yk@~XrR4kF~0d?%epo4&V;Y5eJ- z8Cr}gq+K1CMv0mnL`XxPkWOi<&Yq_6h3j7#VLT=6>bNvY^yfZX?Qg{Rk3Sj+mQwVm zh!SBsNJC!9ck1OQ12h;(N)MyP=#~eYSLc6d+6d!bNe@rB@3N$HiQj$Uw!=;1duLuV z!Z=-)(s2ap`J zhmA0vns#+u8YO1?x5t+^&gK`i0>M&>{uWUp$p>l3EBQ{nl&HZ-a(WmwUY%UtwYR-D z&j{n&SxU#{Rf(Y=M93@oPCBK_-zsgm&f>R@W1K$i>bNw@>VZLoG~@~Cl>Y19(k_|$ zon^;yg#zvBxHQW8k3obq~jy?AE19$($z@u^&EMY}pKjk3aR5FrhDLOP|7yjc1M zcbu~LDA&Hxu8vEitREaiNJE~GPU*PoO0Q_0*C&i}jUw&pxHQTN(DxoJJ+HftXa|C& zv;tH_SzkLyLte>u>g84jG`O0S9!8DFcRA9YCFfms(J0sIvXqX?tFqpC5FxMRJL!Bq zwmHl+p4tD#QLd?`T^*N3SsOlxkcK=VozfqFa*$~}aL?VNTz^ixIxdZ}R(}v74S7O3 zrNd9(*EBA^;i*yX2SB?zE{(Ex!XQE#@`Q9Ap}%_%(|CT?H%GZY1MTX#G|FBUg9vHJ z6ViFqY~s$QvClrUbhuvz?drHR%Dx|i2x-U@(kcD?#%)aFiZ|x#aBmUX)p2Q*eOLw& z(vT;lQ~K}S$D78!pZaKrd%e)Ej!UELSu==`hCCsiM<&kR$TW_eX{iqP#i3mtmqyup zXAmI`c|tm+37f2E8gC!9Vu$(UnA)S9QZ>?n-i{-u!_jICO)EGp# zC$glpcA!e>+A~%&jTLiWhkH6vZ+OD(%97IBvnr)scU#^xewF(=)L<#?=_EosP(^~f zJ&}&Y=kNWvX{?(2I^4U7rF2{x+D|H_l^$Nih%0knhXhM$$0HHi9V(?k8oGk=oqAD& zyC+dEYCL)T+~(EVGi~2F0XsCYl#a_Q?c9{o|2{H<5kLCwE`eYv?M5U*dnBbaNJI5d zZK;>iF4wZHsmvMdW>u{$T+SPGsXuq7ngf!#{=^ULfaaq$?IrnwA0}bu!xHPo; z&0sddhh?_jS0}3+?K-G_;q^U_zE`AD-aV$Px#RQ_d5)ufx4wXjjLj zp`B?`8brvFJRzOZdIvmi8b8Q=9qz+IyE-ln?MyS6kcK=VosUdfd_*}Z_jR}r3+?K- zG_-5XU_yrE3F(v$d0~QSyqfzu+zW+vbzB{9*Wvypw5#LN z&~7V(30aaSq*GesS9hDnxw)^yy+vqO$EBg&Rt6K&kSC;5y87CuOk>%}`*ygu2<_^) zG_(`SU_u)5gmg;t%>JfnT$KAd-1mcabzBPBC4&iBk|(5d|N9^FnZ}ks-loI7H)vPKtsZ%olEH*D(P2=Oaufx4J zXjjLjq5VJx6IPGx3F#bVG}BV1acJ)Aa2F5S)p2QPKajzMG~@~Cyx!*M6-{HI+}Gg> zY1-9sX=vAw!GtvA3F(xUnC;W1u}$vlaHS6QqQ)S?J&`4)wH8Ra{@0d<#TzS!dO9A6 zc3}$PIXwh4vR54u+va*Oa+!KME=yW(WHk?j=a7hAZJp=3r16QY#fWI?>9{N@wmPL= z!gEMOud;1vv_F4-hQ{|nz!h?R-8$rS`%m2N$C%MQDYI(P^nn+H>+5+*YbZANiMtjf1ys% ztBPokMMy(ufidYi=6~&0kMGk_51T74^{C3X6|>Ftb-lF8)kH3x^8ebQ9`xkh9Odox z%Vq1oc858=go^2mq@=MJ{l7fYBCl%Sxl@FWt4#4~5Ft;rvk~b$S2X9n)~3#}=jN4c zNxgSv#|QQ3B|L|O(mJlyDk-gg?S^%K&NItf}z>7VnjoVk>iTKB{bZE?kr($dhHrW`f-#XXF;;_l-* z%psvQO(L|?s$WBT@|Og)`!z&Jool6{#?fbe)x65F=N;y71((wDN-M@v>LsKhe@W0% zO25D43e(tpp(8r9#T92tOG9hrQu^LTw;D0|u|qn{A)&Q$BD8|9UqfEWUlP>r*AO9f zuEC2MIriMV%CYAi=5S@8((+1c7xVLAwdai3{<^(8%psw*iz2i>(qhkphP+Y@Nl?3A zLxj}1CNgTwxa)hSG5_dp9p-RFrqc3CD??N2C8QyLNzhVCH@-Q$-sfrKO?R|XZi_2R zmDX{saP22VOPxgYYK{FDG>tX4+}cXHEv}GNTF13gH}`RR2x&;2MD*(W6Bjd$H6Gr~ zO1Ukr^i^8NwcobX*$x_C!h* zk(}@x5=!g1zCDrB$8x-qJeg&oR*O0O7KG9w^d*U!XgeWVwn)%YN-O7hC6(G@jnB1c zi{FA!S{nM2L`}4v5G{2Qw3Nq`9$LgSo_b=d7H#pH21-jqUy{gUc{%z>cHf_2`xbLZ z=t}}3^gV@IqwN}^?*AY^BYw<^oxMi#TTg)M$9r#7)I|Q{x+co5s z{3Su{ehm>)=g$67Sg-w4IQK)Jf1%N{7vUtF?EZdh6sCZE;_CrKO>r z@@t~)gfyg1f|hct@3Li0W6^sjwrGnx$tx`l?P6aOZ6~B5brQ6c-}X<=e1}Rs_Wr~c zZE??brKO==?E4APQYS%6`RpEiU~a?acJu`kTeQVJ+m)7vc9`!cL`$6nE#;bzJZ>7T zvrcZ&7WY$ES{mA6KBd{tSbS7=?|$LL7IR2wr*sk8%e`MiwEQJO?S2grQs)lrQRBY1 zCzw|YUw3?qIo!WpX?dmH-}4HcquWND|KKq#=8(`X<|4GudP=<-@=7%%LG6AG5mM*w z=uu;fOD;06cG>mF7IU}{xzh4VJG1A=n*DAxV&iQNYcYp}b{H3-JfE_JYJB(3yUnZ4EeExj!#%>4mRH&}J*8ek8uFI}Eu}Q)O;4G|5|j3A(H8dz zS6Uj{(Y&9KhSW*WQcC;H^rmS%dCwj#+T#A*N=rjKny2*H{b%XO?n5W-++q$1?aVDg zdx!UHh?c)3sNJt2Lh9V{J8I1I+I;5KN;hxQVh(rXR$5+ZxAFXpUE!lf9KYN67IR2w zmu?Z-*E^+N4SA&+lAw0Kh6t&1_wT52Z;n?|sh7^$sKp%a<*l^5(oWzh{W?b*J`D`ZD@*n$r3r8~Z-}gy>#^-(Ab|f=91y8h0PPiq%7e>cL&h zSigS4YfeIG9oMdGTJ5)iY5Z~4WjoB_u53z+&|Y!gkG)|>BW~S(_6~DMXy-o> z+EK7yLte>W64dV35FvHhRa$nV#=I-Lh3>Kd-)eplQ4@?txL-;x3X(OGA5G=J>-M4mF~4 z!!4uCA)%cyMQF#(ehqmge@RfgUqgh{x#MQkxclrQ%&V_oec32;xDTk(@=AM+=C)f8 zAr1LUf|l~zdBtN)W5Lg!K1y5M&s1q?XwTGsLK;#hK}$LAb>-ttW0u)>8l^4n%c`_A zwD)U{OFHjFBMzN;*-_?@(9W?Uv?Fc5hP;x$B&gl5Awufhkv3{Pw%y6*)hjE!FqNfx z_sRXht6su$NGPr2+PycA4jwzvH1_}HX;Ya)LVN#;(4NBmgz6z%+#xs;|NLjsn0vOv z?qI3jeTzTP=p{Udgwi^$-IPKRreD*SF**Orz0`rjVIal*le3QMwr9>u$7jE_T0|VVAq{s#FsB# zcZ4}4v}3mj?F61jwu?sFHRP52B|+_e4G~i3PT)~v_gRlKuioA7;1T9#>$vvs&vU|glvc#Qc3r;19PX{Jz5GS!s{;KRqNPECmh#+P9;H>O zeR3o=ZSflc`X+%i^mT-OuSCmV610@#mhvdA3N4dkw`q&tI?%Tjq@k}o^m`>*H6%ex zx#lx&ZyL9pICqD(_{|1=BSISbT1CHC(ohXa&{Cew&7-s`HOIp*H6%exc@8a)(yG)l*S#`ITl|KGzS$uSeG8=DE77VU30g|&(|KH1r9SzIM@MOk z-vZH>M5Li_n)G`mS~Vm=OZ^%m#Ee;XI?YP91{9^j5HR>U!}?8KIfMw!?`&Un>qZ(jJ{AKLf^{C?W-OQ z>B(Obw3K7f@+hrJ&Ggg5Mrn&*zR|aNq@i#A^m`>*{*s`j+=o5BJljXlZ;jFxzxktY z3`s-ZF6#G68mb`)T1x5JJW8uluf9LeC~fiEMfz5fH1rLoey>EUh9qdIUqgianL|R~t`ecIXyqCF9u3i|Aqi^R zDpS*#@f)+OM_c?RmcH604Sff#-z#azUlO#G(hu?|txBD`^?~DPi(doNch{t$@6z>q zC0aEkK}&fY?#yy_Ke^-M<7kWDbJJJtq@nKw_Io7_)sO@&rL^darA6>(B@NY(1TFPzh_LS(;(L%$Bd-~?v%AUb6GoWBZ$m0Auk_{3yz|{drN^?` z5$zG?kkEHKMd-Vxw)RyTZP$=jsv!w#_iKodI==@RHGZ|rkyej4a_ly9`0Z4s<(0ln zn`e*nxUTGOcEF7z%psw#)QZq|e^aV7+O8p5H6%gpehm>)=htte#{d1~AoFU%J$H{V zhhHOBT3+d^%6Vks^nHyu`G%)Pm_tI}WEP=sR;N^Hv|U49sfHw|-LD}+>ipJo)VS;J zJ#TIt=p|T6X?YR}+02hbs|(jVat$ZfsFw0$5nMA$Iucxw zNxfzbu3qG7Nm_~;T#Lw3%^D=AM>=Y7)gM+v|d&N~|T-8SHs6m3Inl-orj4RG)DQa-771wsrQY5&l ziL0n+DH2>i#Pv$F)J(9{a2hPtTo0}W;_4%M8@=M%9@5POSL(1-vsWajM>={%f_9s) zIaliNUWz*i6qWMaB|*L6G`N1KjEsgQ0axGxInW`et%kZvZpX9?+M zf;*6qZYKCE;_tDU;0`3zYbN+BA{~jmlC54X$K46z{)vStPX-d)IgoU-26rkX-Ar&F zK+??wcl9IPOmN3N(#-^S_#@p+aK}B;%>;MDBi&4JZ#&Y>1oxdI-Ar(wJJQVrcabCA zOmI&)(#-^Scq83R@SQ=rnc#0C?qOP({@(}RaMX($+|ic1!p2=_slnajNzhW%Ai*8j z;K!C*4eNA9B*o1a}oD-Ar(gaMH~L_wOd%OmM$& z(#-_-??(^Cf!VMr)<*A1oyor-Ar&#YSPUFe?_F53GTDZ-(xeueU?c_ zf?uK_!C!ME_ze!y@hcdGDbFi@SA+yDMGb!8gmnDI2sQXE8Pf54EF}2-7QuH;x~9$4}Qsrbo}~I6TzBOFM34{mZDxH_?;-y@!L-HieImySEQo` z36`REB;LiBzIeA&ubE&eYBv*nhf^;S+&h7LGURr!?Y6+ZEXps61WU2y+^j*bct)BD z+9e$c?)AWW(A!AxTW$Q(+i(O+jaiRQR%}ip?{r?xNYE|` zT8ad1k?{2HGafoE68k^*^J%(&{xs{>({oz)l%-d>|F3H+Ee|6>uTJ^&&-Zy9mw zI>Tzbd-;_{+;{QnUFt;*+CBP=i;b9c%_>9IW24)?W5lAnt5cBvON zX!nv^E-;NtRvK1g*0aBF#GMbX)umq4pxu{dINykye!0?+^>|^stBm;M4?okTUeuu7 ztv5Q?h`aV1q`}|a$V{iNuixEFbImus`8P(^%%bK^iQz<_u>TF>cR61WV1i0xArs$J}YC*S?amnCYi=UA6>EAOt928C!9J2!BRV4)UNB%tie*d9&(Zq zI#JXn*SLYpm2af3=~-5$SGMOwl<@mg8IhbfftG zXfOGfl5_l%8w7%u!u4oxyu`Jpaqi3O2ZEL&vHymJIO^Tc27;C%arA^jbmoWzEd?TZ z&Kw_Z1r1t?gsZ>2%t#<;DH2}8nbsN?2wIASpT*}kT`v%{6bZjlEAF~(AZRHP-@M{# zyB-H0xlSNxDH0dVT8MSeUON!96bU|^8BIq^k>ERu1T95^cld+be#Ppfr)cBdo@m#7 zU*PhMoA35SyI0LAn5J8C!$KR?^;^SLhdq6Y0= zdgFekp?B?Y8vcI$`TCo7sTVb9cid)sn}*s9!)bUs;m;r5tbU(MFKW>4)Bm@JX{hZp zoQAh|KKJ5gb-P4*QG<3b8{O43)an~f!`p3p-m-b!zLH+lpxx6i-`O4%|F}LG}N0IPQ!Z;v%WmOZs|)e zYS8ZOuWxM{>emdX;r*ROFW$25u}Cj!@X6r4mvhH$RrjMrHxu3$dGE2Ux}=*4?ans(outZ zn~|WUNYECEX0N#K821Mw9W|IkqFIA`X>rf1xQ|y+Dfbmiac8S$f;lAOeq>prUCm7- z?sG+g+U8SDuoQP?>eG;n&C2lnkZ3+vv_*oJqF3DKiUhrl1ozk?K}(U~ZdTm+iF71b ziu*V<6D-9&mYNBc;{Hy}1WR!TqGp1-B$1${Saa^sM1qzg!5xf9(2^1PpK@Pu?HhM9 zifj7{QwXlRBSA}1gX_{r&{8D0CXNIxMS^R_NYGLwxL%C}Ek%Osxk%7bB)FE01T95^ zYpqDoQY5&}iv%r2f@`fv&{8D0a*6~kMS?4*NYGLw_{$;D_k?Tvy~{hC1T94k+9E-3 zBf&N9B)Gml5?q;1f|eq|b><{!DH2>YPJ)&q!8PC{Xekn0ZB2rfBEgm3BxorTTy0H) zmLkEm(~KmXXekmtF8bLYM}n3j;bX6x{Vo!;6bT>S-Q|@?&{8CP4EeyfBSA}%@Nw#1f|erT zW8uAC(NZ81e-peDo1cl?F^2?yH$kKAvjDwkm)-^E;*L4g zpmreIK4a2*CsI2Qi96;{gW7>;`#etX?}OTbNZc`p8q^L%+vlTtf3?((gwJ{PJ{f2! z5Q%&2&?|Zyh_=t&^**nt9f-s|cBnz^K(u`hviB)U?LZ{{4yZxxK(u`>wfEUh?MU!F zK@D08L}ID!=UBm_2=@BMCf&yON8(5tiRMwY@2t44jlF4BhQ#R$m*bjyiC#T_RLRlI z4+&a|#PY8eLbFRGXeknlUbvolr5P*|v=oUi96Zhl&25pOrATae#E21^D)OmBvxH*6C*TAPJ-HzIN`|6jZlOL32H~;#nz@qXq21;wZ|qDAwq)M zk?XV?QNbv1Nf|eq|ITn5)k@GE);JgY6T8c!^``l(|^ggGDk;pl9seFg8uX&tmmh6N1 zoeHT&2SMcDoe_F7kd7M6$#=LB)okAo8u|HQgx(C)iyE|>?{FikS<4|b@^jD#y&0$% zHE1{A;YL)muS00$=eZGjI#Vxd&~E;98Bxt5522Bt&PM3XK)t9zySd+QL^T^eghqZk z8=*G?^`Zvt=8*y;s#*OZH1gBg2)!Ao7d2=%k1ZHcMJF_9aMqFpe`!&JzmNPpHx0c@ z=wT#iH;-Q!p}36UH1hM@2)#?F7d2=%k9ZiN_>$o?^7GsXy-TPUHE1`Fkr<&knc+0@ z^V|r%OQ;t$Xg80l7@^3s;WY9azzDrds24S8H;=;@p?J9AH1a#c2)%u%7d2=%kK7oc zD81n{@>|9Ty|1VjHE1`F^%$WT$l)~dd&vmB;iwliXg7}z8KD@+;WY9)(FpbEs24S8 zH;*U%f7Ech>P=EFYS3;T9WssK&y}Aa^*^Z>HE1`F4w=UA*W9nOdaTro8nl~7hfHJm zd)Mz*^>L{eHE1`F4w=UAzd`@qsaH(Bs6o4VbjUP@f0p?3L;Yy#MGe}`<4LA5{4>#? zgX(!xFKWfKW>YS3;TPcjX?orlx# zH?n&7)QcLln@6BbL#>SAG`wY@-aYlA2JPn2FVj$KX*dmUA*pvyy{JLEIfBYG)an~f z!&`dl-BT}W&~A?FG7Ys}htu#Dt9tj;iyE|>qs>f1t?=PAyydOlJ@ujn?dAwN)3CKf zh!Bee+gILCQ1704k)Yii31}MXe+*X-?fKW>YS31W z%rp)4KiJcY1U(@^OOfE&^`3>s1L$ESXg9~Cnpf(vGsc5`g4X{g82 ztie(_;@1fEKbi@a%5lg>s5j9}uvCs#HbO1^W`Z@(J#QoQoqL{zSaW|~Rb%=!;m?@9 z`P$@9(Q2llCTJ;Y__Mv5|ELLCiiE#$s#%$upruIod$5}MsR>$&guk7u`KX$prAT<2 zp_(PD30jJTw`8ihyPBY-NO=3Ino+C?T8f0X`l^}HnxLgfcw4lZeXR*viiEe2dkI>K zgtyy!y`rT^cyFS&=Cl+E@8k5IU0RBS_q=+qAT340`-8oA2`xp!d#Sy5A}vM2`_8?0 zJ1s@Rd-T1(C|Zhyk4*IbYH2AFK3>xMWT2%;@F~Nm4=qK4qk|;)9E${>gE>0I?p>|( zYJMi>RZUi^T7y*+d3}_n`)B*|swT^+)_K(gEk&>LswOK{ttG1oT8czo)nuir^=dUi zOOeQ{nyi%iHLN);MIx`%F+#l*60{VFyza&b^*Kn;QY8F(s0TrUmLlPAHT4Hb&{8D) z&8gNs30jJTzn#@qCqYY*@D_kt-Xv%#65cLRJDLP7MZ#N4Y88{9rATk#+f|erTEo8MnNzhUx{MuHl=<0jdub|o}k?@wj+Mm>*b|n0ctJe0_8q|)2_gK`L zqz1Jk;lG1w#bK>M?MSdyL=9?3!hfCBI>=gs+L7=lK(*GgCTJ-V{*rOk8v8@64+WUOzot%|Ntim&CM(X52vYMmH>}ulD4> zwnyV~^*dK&$7qpxFQ4yrVUPZMVqVuU?P{g9Zkua~l-6;rsn1_(x?;ZD|Gey#t3Q?r zYLK9%l>RyOXj?P!k*|DpG?Ze`G^eGZbxbwU9*gi&(W{M?DBrI6)2DVGWuHv*bs}u% zj-WA!&>8i0`!%gZ_1A839?;LO2&GlX^y=dIni%0Zy|f?Kf6GjtQf}AQ(N}hvc8$S= zd*yYKW&acT!8Lc^T|r1B{*+PM zhjw*b8ah6hkcK=Voxh;FX!#n{b`P#P>d!v4mS|VUrJ>`432Dd^()n9B7nE;KuJ+_! zqy9uz`-*mTTpBt)n2?4%A)W8sLym5VfBE_|M*YoFt<|pW>bT12_+UaB@+5k-!}VV= zjmPHy?x?>ZtF_v-T^*N(j;HjKEe|nb`FU>-1WW0CE<$a)lm=Oq9Ok{;=Nul#y1)A;@cFO0H1NV_^Njk2bC#-HqJ z#3BFwcOY0w?L-k}h4df|c_rVem%p$I4YoPyVbu8BTsxRoyFNQ-hiy*M!xQeiEGb=9 zVK4N=1k>2*u!TEprBZKr!tKhE(q$F)i%V~68XI1=M9^R?+I2f@JJYU?OGEAXl>Tz&Qbz3Zv5f-3QfeWKQ0siKhP;yR)QcKyZPUZ3@!EMG zHLw0S|M(8u&ZLJY+;>@0T0MaLedjgiH;sKKZPQ_!lcjWAL>cAW_rzI@IOXu21Hn=n zRh++ac)@`Q9s|9J62(-?onksXfw(yoq6L!*X+32Dd^()n9- z*Pmz_moI#5hhxRGtK-tpSmt0t8uEm6{_^7Uo14bumyhpobeVQ_TpAi-9ZX0=o{&yy z&C5P)8Vf9SQitQww5#LN(710(=e>B}IQ_rvZ#y{DZhIOa_c zqsDP-jyJDP`QgM4$Glle$K{pA$n{_QYpdr%f~7PTu2O{~ z701? zaJ#akba@-R_{?3V@l<|)aMpr)V-oGL2)8RsN|(3V;Y&Yd8na)uZ-?_7)Ekp%k43m$ zSyH-wJDY~y=bRa#-k3ytEW+)|lG3GJl3N)a{Xey2I4eWFF^TqAgxi%RrAuomx38qp zR{M$?ETtKD5p|2KCO8*QIuf~^X!TG#kr4u<$0XWg5$=gBDP7vTxrJ;RY9TW=f_h^T z?Xd{AD@#h3R(Ni=Tg}yOrv^(Y_CrMJ8<1dB2eGnXzmX(c8H~PTpFbZSrKV0 zLiLa*q*Gesi3z5$V(!;628wodTpFb(eD7R47;$FqZIfUrMQn*EV*rcRc7$#?3d zL=8rNQ7>xbQ3LZzBNL2CV<{b%SM^wih%^=qxsgE{%GO z%+A$Vgf!#{X&WOm4UNMwW|MYxTpDGpFORC3##n?jh~bhCCr{qnuU`jf*mdmUeYq8fENt`$bPQV$X+H4FpRm;#Wi&5zb?;R`anm zgVDqEFly{G)5+%5saOANDr1^iO2_3@84It7G!`MR8C{pccRg3EK4*fMQ^6Zo|wPaFmEw#M$qL^LM!xK7J zzJ5^YvYH(2tDxa6GGEmwLQ%|ht4}SmmbWaGLl3=0R*0iYTQq(1(j@}HQr@z(XzWBn zIXYL$pU~cIL4R5#mYFQEGOB*D- z|Dhau=zW7iR6UlI`#3G{r6`)8^zejx;yol=7ogb;t4Hp6wY-0(RSDD^o{)y@dhgEG zHzf2qTi(yq>I&)&Pe?;{y_ah1G&DD1^~inamiKTwc3SteGI@>!3-p%Ax}u>k(npou(6E&ezQl*$1m*b8r5io?D|-St>zg> zNJB*Q3S%-YABWNEBii*b8C#W<1`*PbC!|yQ>7UEk(n)_ksO95HT17>@s4J3jw!#u3U zENy*U+TcegT0M4K_}G?@I%;(u^?W?>KNHeWDIevub$*(gFpWG~+w!qctumzE@PssE z*T;Hoy`ttOOe2r)wtUoCt0k#7JRuF)^$}rPcbPD<-SW|Mt@@(u%SYw4LY8{N6Vi}fp9QeBwmMk-wOc+{pd9KAPso&Lp9?7(xgIvhqI&qO zj69*<@Puplj7d3Hm|bf5Y>&<;^@b;;A-g^|RjxVayjni5raOar!xPewU7x`!_b%q{ zT0TFhJDhsM6Vi}fpSvr+!TdWPQI3A+KF_IND)ojZWJz{?4zfHM^7G0x^t|%69k_xy)@($?p%JKi3<8 zc73M0yrTvY(vT;l!;E{&;{fy~qg|hIFYj8+>h}@SkSC<=eQq_^``n`w^hTy#j~pm% zhTH;ZB%~ovNL#z4rMp4x5|4UN%Yt@2;-a*Da!aO>kcK=V9pW-t9$%ss6770iM%^Ma z4YkO632Dd^(n;+^Bl1i`%i~tm(xY9EVX0e|jf6Dh3F#17)ADEI8o>6?aH`n`lSEtTL>yX@!@~ANNglN|zwn|@Z5FrhDLOMsWE>mLFrse*iN1LgqNWG{rh|sS< zo_M5N>2o6HuH|ud>dR7ZctRSo>rr~8?~K^Nmd6XKhfTfV32Df#M;q3o26hECYT!|f z8iAnR@PssE*JCcrSjO&EEa$0tyo4Go<#Co}goT81be)w$IueL59q}kujoh%5N0^q8 zqCte~k+$(GJ69UN^5|HNQ_-$R=9cj*MB@g6r92w9jNIihvPKPgCEuxMvl&L@xrq^v zT$U|*7&Q>q7Wco5-wq(emfIFHlUNHgtvM0`CKZs$s)=OkFlM z!8%dT#@lNR*^=+FMZKtj6$+bzyY{xDU0V8psJt>l-^e9FJ^7T|=Gm*8b6b=(Cy|fW+BR2YS5PxQd6s0lywbc= zp1YYYYntuRaiz=c;jgP z+%z;hO}jcSje7pvG^+XYnqa9sV`4;^)gPoGud119)PrkGWJ{%FiylUeJda~uDRLms zk{F?A0hZEn)uY5S3?f{Qw8c!Ah9V&HEQx6->VbB3TpD#;h7na8QaK zBY9@T2t}*1l#Z+Bbrh?O`zR*0mvBAONwYFWC{i}hrI?1IZfRG?rBO%YHWJd1C!|x# zF~UYD;y2Hwn1-T*)an+-LjR^^s$}=V)25Gp5)Qjss zWJ{%_PP=)=1m{Zrl|NhgQWNx$bkvY7_k?;;BhTa1YfSQ7iV?cDET!Y}qg?Zf@O4Fj za6Qs-jY*zMF+$gwc6D4D^}Rcq|4+MGQB@Nxm1j(h=wD;ft0AxCJN4rF57|;F*`nP% z|6v;aYfMN`k95?KEosOW>8QaqCV6JW2t9E~k4e}XlU_n+RC+qDbo~_7*-d(i=9v-G z(36w&n1rn{=_TB*jw@Z>26>I5X=sgNo*6L>y#Yv%Nwmiz+^#GsUEXSWR>m~6)-unG zn1i1T;dXUg>C#%_8WR#Mm1j(hs9R*6jN7Ud<>=m34(UiB-lvmiMvPEPkMx*?tug5( zi1T;dXUg>Hak)Bv>lXm>AK&#zZ+fSIQwBi7G0r zt=>eQ88JdV2$s@uc~$y2h$HJGT#s~K=UlBZsn+V(8nm0|SWKhz)kr8uc9la9Bk|(W zWsOO-=YSFFDY8_aV=;}=gRF=&7U6oNbA&^+#zcE3@7@;u>meO(6qaMppJ#39h zFX4KmZBGuXx%TA9Gb5&<@eA73aak>6GI@s$(-@18hCCsiQl6DD4ec$G=Q&J6BSo~U zUq08k1fPc_rVeXL|_M8nUHQvPC*-sIB{4!H%`Bzksz(_MuZT1j;d-Rw8k0OTVj3E|r(GSFM*kXv=V+ zc{Q(QHCGPlNZ<;N^SeazfGVZ4D|&cB^$_9Lyq-}sukzilIY_gs9G3Dszs!tQzmFus z?^orJjs(}3_^(#;vZRM6RF7(2)vkG&;l-0dGrU$0<@oPTX@4@5nQ9XLyiyM7NZ<*l z*>Ka)JUQv%3Du*T=eBzEuNd+7hvv+w=TFi9Oh`ke{MlYd4%ju%Z=4Z-4=Or=dczaa zkX?UA)e#V;p_mD3u#~?!>nMww@b|fLNJoNeOh~Ylw*cyB5z|n#h;sBRs^UE?T}6)A z^(aKul1aI>q&OKXRYjrHbkrC`=v>LJx3x-~4BA&ggQdKERY&hwJ#vdo5klrk6@OEE z7>OJqWJDEHR1+-aEz1(OL_#?_S5+KQO-CX}I+e4VW23xPtmr1v!xK7J((@L19kFE^ zRm7HgQbkbNRa4qq`X!DGeFMdj8KE3{7zwU1@m`9e%}5VVs2fCFBDZuyFQjt;$8<4(x~D^ zO`~{)F&V}0+MTHASlab5nG(M{h>%7V_i7r&s~qubV={^n_VFY|`BE=x3?g*R<%y3* z)sf4lp~z()7gMw{^@b;;A-g`RR!3l)hGMpTd{0r=)El0VhV1(2V2KOo8WSINtfI&5 z+Eh{Ac6R?WAq|!CQO*+I&NU`J7FtEYn}$jaPe?to@vl)@SlAC=dt4eAX~NJDmg7ND&E(9!bO z);bpRy;>y_(kk8j)bca)KctRSo>vMPIH^?<6K5MC8D)ojZq#?UL z2U(sBGh>a3&%Wx3!%{voT0h}NWJ^!DUP8ZM*(Dw3gtdOquDPC^wCgjx^^@8(^rY@3 zq#;j8+Zsh9v_{eA&-DhNU7x8g@2I>+v5}C5JRu$C=(V2H>Y=w6?fQ&+d8=VozmJfH zJRu#|n0R!8-omu&kpt!J%rz#xgf!#{>A1$kqaM_npk0r+D6I^xG3h0wAx}uhH6|X5 zp;i&?dR#``B5UcsQj4sYkcK=V9oLw6+=^OywCgb}b<5H;)Uxa)q#;j8$2BG%O`{eo z?Rq3n-9k1EwUB!WX~+}OA*QJ1F+OT})2_$+l)gc3>6?bunDi3TkSC-=Y?Rix+r6tk z2km;KQ{7`R4fR-h32Dd^(s?ah72~C~`W_Xgo)GPN#8&C64I-o=Pe|wSyDIXm+H=6_ zp&lglqQ)RX_nSQNNVn2UMa-S{PB0Dics;sJrG_V@A;P2dO5Yi=gDsCXR1cea!xPew zU5_>_V*rR~)Ls@=4~-O1Z+Jo)vgkc+;NQw z3D${vagB*bF3T3_sDa33?O9`9X$+C2Jnp%SKyv*D36}DB>N1i#NJD2=9#Su^F_A5m zk}c{*4aA;n4gYA^Qe1`G}Erf)9ZL#|3QMKJWjuiwWi^@B}91_$k#hqX&_po^K!PiQRs5SE2YkeKjtbTr5T3XQp`Hf@g zGDlySQV*8O?{F)n7=~toIr*Jogyx;|JIc~!j-DDM@*BWPRWaK&UCMjSbF0X56cv(N zVU|`DN^Z$my2O4^gE=Hvr>H@%@{`l-YKE5{MuIsc@*BtKGDlCZm_s7J8H_0N=Ol8w z)^ZfrliSgjRvZ!Q6urtVdMl-frrd+Dv?8cTM}j#dnl-0W(0IzI<3tr_0@)V6e) z+h#p@cJnjQN@@0$XCZpU91_%y1anBx!$`1Heny#Bn(fU`QA?M3XVxS4sx3#6dAS#F zX~lKsp0K4$Bp?aqoqGJj6Ob5vUrZmGOENsM)0RqsLbszfJ{a1E(b!;eeb%D4UO=CMh$ zrTZt3o?2RWMjlVJbbTk*1anC6Oc$SA!|S1Tl>fHX0`y;0Y4v5M*2p8jrmiPW9%;6; zoxsW(|J>sLw$?BWexW&GR3oq4!{)Moln>MBnwO3FhRvBGb_OweK#e3ECw= zOG$q`iZuHNS)Xvqjc)*|Ei@m7ZVoc=~F{lUT%f;lAotzCMl5^#+?<7Qr|y_0A7 zEUnfOJuC`N(5}DX)T8&dPZWzRsR`zg;MI&8d6wO5sl}RS z^)0QoDCtNrheV%-9J70u*TY+~8tda(FpZjE4he6amvOkl6bY1^6B2#DsG6WH60~F*`JYmAZ>4I4)?2a~b@rBX z8K+ipCvx11`L5P|j;67+TC&s|TSL!UZ(nQl+|OZ&@-9sOH7CI{9W~fiBjNQcQTNoK zt+9wQdQQS^DTf5lTeDa0q0OFDy^J!Ok(sui-5djDXH`4?rOrHCF(Dc@TXE4r&cAZdbF8Cg4(&f{gs;2ckhenJ?K4vz9)6tpS$YI_&ZU3 z9O^|4zZ-O4d0#~DSDwR2Foy)ajRbQ@uui4gey;q9T*Zjm??e3o)+rLq@m7t9=9+uY zL~l-Sy{LucJ()6#oK@QHmA7Qn&+t~JdMwn7UQwMy^L<5IBxuPrPNYEAudT2!3>*4;YXG((FfoOZXU41g|S*WkZ^A-u_kZ8Of z$?rt9ib>F0)5!mn>%klntW)qRQJn;}qXtWP4@rH_K8@Ndk3ChpHhVZ-_Np^&>9XRm zsFZqSTXhZzkH)S0YPAM)NMvu#QhzTsUk}SsUoGGHmiD!4@};bzqgNh>>g!UKr*<^; zN_oE?)W}Z>(@;NwdXb>NB=Y@cbpQMZb4XA-YEYd-zUHRUKW~tKYnJ0Hi)BwEQ~8%> z$NPH|JSX{`VWl*3$NELDm_vddMuO@js2vH9h?VaoXeko;t!BQfN0;BbmRA2GKNBt8 zKQ}=lzjv*adJv?e20h8YM~9WNKiX!&=V551z>vpa}%^hf|jC2emk43-hB-` z?zMkzf;G>rFr(E1q+ZnUxKPak`sY8GLn5~&jOg#N zFoy)|7rmmtBCBB*CxwOu3k zqfFg*BT=rdLGDM{@%~vE+V#7yYUvxH_I%XHy;RH5{geB+mey6vy;Mv0&rL9gMDD8@ z(SMh?SE{-Hw$%#v%`e#gX-`Q%Ydo+*9 zSX$3ro`raJ^JtBg(lgQH9`&3j9qZvw2DSA4ouSsUKb6$#j~aQT$Takx$m2|w)?1W% zQNz!!u19YB8lko?&q5^V6$yG63ECn-OOc>068=W5=MCC^%~@NIlvLZNujVzu91?xk zqb8UW2>e!JJ?Oi)AJmfdHiLQ=k>K_4H?`Wkv=j;Ekf5FsZ9i9eoVr}+9AA=W1T3v~ zTON(Jblpy@3FeT*4KE^%{AmBf)x*V4WgCTO?>H60}9aTV(YtK*q}1 zt`BmCwPAHtel-)wcDG|743RUR^trb>Z?b#YmGcpWp>pP&hw|1R=b^g(JPk9 zbHYZb&Dcj|tkj%d`B~7Y3OzK9nqUqIZ`GDKeHC{NdZHLe*;9*kIIp}_qwy>1$%_8^ zuOJE5uatL!?Hv-Xq4!l0aKhsd)k@7Xd{z&&kn=2{rRx#;+AG@iww6X6y=~V&dd?gY z-XiNC>thZHZwL2}p3`^5C#sfu!}Zmp?KNi(36B?5Uqn_uxaKUC<2_76ZNol7kd5N7 zt>$TsfAcz-Moln>1kZXTm_vf+EfUNj;XQ!9k?q9R9SKkOw?%2oOR?tlEI`}O72`tv z?5Z8@>ADqO6U_0ni2r;&r0#Xnz3Xl5zM8lFnyZ!S_n6wz)U&heUbX!fK)(P55(HEo6T)s8vkbyvqNSvrFH-1*mtFx9#fj_L^W032)E$ zzYYAVsqaL>&tZSBh&d#9onk$hLxS})qU|+j%ZmiPjf7u8J^TDkt^S9f-Fi&MHQKyV zB$|I8ylNy|Lw8K@s?8h{)DA>)LjIEIdpmnQ68$B?GZHk~-WFAh-dmY!>3b`xFK(&L z91`wTUmvH<91`>}c$L^zBf)zi60}9awd?5U`dl%G1hq{g|5JX0y!*Uoq1L_kUh8&Z zO)!Uq_xkF2oSI+`39os73z<12yeHEii{pg)nIu?0t9jdN&htZpmW-(DLBh|d`h%n+ N!BXDSQ=cjl{|^|eER6sF diff --git a/docs/CAD/design/mate-connectors/DESIGN_MATE_CONNECTORS.md b/docs/CAD/design/mate-connectors/DESIGN_MATE_CONNECTORS.md deleted file mode 100644 index fe1a7a5fee..0000000000 --- a/docs/CAD/design/mate-connectors/DESIGN_MATE_CONNECTORS.md +++ /dev/null @@ -1,922 +0,0 @@ -# Mate connectors: aligning with the mainstream CAD systems - -Research date: 2026-08-05. Written against `orca_cad` / `Snapmaker` at the M8 state -(`CadDocument.{hpp,cpp}`, `apply_mate`, `datum_frame`, the `Mate` card in `DesignPanel.cpp`). - -**Brief:** align with the mate-connector concept as the main CAD programs actually implement it, -and be simple, unequivocal, unconfusing. Alignment is the organising principle of this document: -every recommendation is labelled either **[INDUSTRY]** — do what they all do — or **[DEVIATION]** — -we would be departing, here is why and what it costs. - ---- - -## 0. The answer in ten lines - -1. Seven systems surveyed. **Five of the seven use the same model**; two are the old world. -2. The model: a joint is defined between **two local coordinate frames**, one rigidly attached to - each part, plus **one type** naming which DOF stay free. -3. The frame is called a mate connector (Onshape), a **joint origin** (Fusion, Inventor), a joint - connector (FreeCAD 1.0). Same object, three names. -4. **Every one of them expresses every DOF about the frame's Z axis.** One axis, one convention. -5. **Five types appear in every frame-based system with identical names and identical DOF**: - Fastened/Rigid, Revolute, Slider, Cylindrical, Planar. Ball is in four of five. -6. That is not fashion — those are the classical **lower kinematic pairs**. The vocabulary converged - because the mechanics converged. -7. Our kernel is already on the right side of the line: frame-based, five types, Z-relative, - superimpose-then-relax. **The architecture needs no revisiting.** -8. Where we are out of step: connectors that are not attached to a body; an origin that can only be - a face centroid; no live preview of the two Z arrows; a mate card of abstract dropdowns. -9. Where we would knowingly deviate: refusing a second mate per body (no vendor does this — it is - forced on us by having no solver) and possibly inverting the default mate direction. -10. Biggest single win for the stated goal, and it costs no kernel work: **draw both frames and - ghost the result before Confirm.** The convention stops needing to be remembered. - ---- - -## 1. The two families - -**Constraint-based ("old CAD").** The user states pairwise *geometric relations* between raw -topology — this face coincident with that face, this axis concentric with that axis, this plane -parallel at 12 mm. Each relation removes some DOF; a numerical solver satisfies all of them at once. -Fully positioning one part typically takes **three or more mates**, and the set can be -over-constrained, under-constrained, or satisfiable in several configurations. - -**Frame-based ("mate connectors").** The user places a *local coordinate system* on each part and -states **one** relation between the two frames. The relation is not "these surfaces touch" but -"these frames coincide, except for the following DOF, which stay free." - -Onshape's help page opens by drawing exactly this line: - -> *"Mates in Onshape are different than mates in old CAD systems. Many assemblies require only one -> Onshape Mate between any two instances, as the movement (degrees of freedom) between those two -> instances is embedded in the Mate."* - -The frame-based model won for three reasons, all of which matter here: - -- **One mate per pair.** No mental arithmetic about which three constraints add up to a hinge. -- **The DOF are declared, not deduced.** A revolute mate *is* one rotation. You do not discover the - remaining freedom by dragging. -- **It needs no simultaneous solver for the common case.** Frame-to-frame alignment is a matrix - composition — precisely what `apply_mate` already does. - -> **Caveat — several vendors ship both, and "align with X" is therefore ambiguous.** **Inventor** -> kept its legacy constraints *and* added frame-based Joints in 2012; many Inventor users still build -> assemblies entirely with the old constraint stack. **Creo** has placement constraints *and* -> Mechanism connections. **FreeCAD** had constraint-based Assembly2/3 add-ons before the frame-based -> Assembly workbench shipped in 1.0. So copying "what Inventor does" means copying **one of two -> coexisting workflows**. **Onshape and Fusion 360 are the only pure frame-based examples**, and they -> are the ones to weight most heavily when the evidence conflicts. - ---- - -## 2. Field survey — seven systems - -| | Onshape | Fusion 360 | Inventor | FreeCAD 1.0 | Creo | Siemens NX | SOLIDWORKS | -|---|---|---|---|---|---|---|---| -| **Family** | Frame | Frame | Frame (+ legacy constraints) | Frame (+ legacy add-ons) | Both | Constraint | Constraint | -| **Frame object** | Mate connector | Joint origin | Joint origin | Joint connector (`Placement1/2`) | CSYS on `Weld`/`6DOF` | — | — (nearest: **mate reference**) | -| **Where it lives** | Part Studio **and** Assembly; in the feature list | Component, inside the joint | Component / inside the joint | Inside the Joint object | Part | — | Part (up to 3 named entities) | -| **Origin placement** | Inferred family on hover; `Shift` locks | Discrete **snap points**; `Ctrl` cycles | Snap points + explicit origins | Inferred, previewed on hover | Picked CSYS | Picked entities | Picked entities | -| **Orientation control** | Primary axis (Z) + secondary axis; flip + 90° reorient | Flip, angle, offsets | Flip, angle, offsets | `Placement1/2` + `Offset1/2` | CSYS + offset | — | — | -| **Type inference** | No — explicit | No — explicit | **Yes — "Automatic"** from picked geometry | No | No | No | Partial (mate reference type) | -| **Solver** | Yes, simultaneous — *"order won't affect a Mate"* | Yes | Yes | Yes (Ondsel) | Yes | Yes | Yes | -| **Reuse across instances** | **Yes** — a Part Studio connector exists on every instance | Weak | Partial | Per-joint | Interfaces | Product Interface | Mate references auto-mate on insert | - -Three observations that shape everything below. - -- **Every frame-based system reduced the type list by an order of magnitude** relative to SOLIDWORKS - (7–13 vs ~25) and lost nothing. That is not simplification-by-omission; it is what happens when the - DOF live in the mate instead of being assembled from constraints. -- **Every one of them defines its types relative to a single axis.** Slider translates along Z, - Revolute rotates about Z, Cylindrical does both, Planar translates in X/Y and rotates about Z. - One axis carries the whole vocabulary. -- **Onshape alone treats the connector as a first-class, reusable, named object** — and that is also - where its worst usability complaints come from (§4). - ---- - -## 3. The type vocabulary — cross-system table - -DOF = degrees of freedom left **free**, stated about/along the connector Z. - -| DOF | Onshape | Fusion 360 | Inventor | FreeCAD 1.0 | Creo | **Ours today** | -|---|---|---|---|---|---|---| -| 0 | Fastened | Rigid | Rigid | Fixed | Rigid / Weld | **Fastened** ✅ | -| 1 — rot Z | Revolute | Revolute | Rotational | Revolute | Pin | **Revolute** ✅ | -| 1 — trans Z | Slider | Slider | Slider | Slider | Slider | **Slider** ✅ | -| 2 — rot + trans Z | Cylindrical | Cylindrical | Cylindrical | Cylindrical | Cylinder | **Cylindrical** ✅ | -| 3 — trans XY + rot Z | Planar | Planar | Planar | *(Parallel+Distance)* | Planar | **Planar** ✅ | -| 3 — rot XYZ | Ball | Ball | Ball | Ball | Ball | — | -| 2 — different axes | Pin slot | Pin-Slot | — | — | Slot / Bearing | — | -| 1 — coupled | Screw | — | — | Screw | — | — | -| 4 | Parallel | — | — | Parallel | — | — | -| other | Tangent, Width, Group | As-built | Automatic | Perpendicular, Angle, Distance, Gears, Belt, RackPinion | General, 6DOF | — | - -**Five types appear in every frame-based system, with the same name and the same DOF.** Those five -are the industry's common denominator, and they are exactly `mate_kind` 0–4 as already implemented. -Ball is in four of five. Everything past that is a long tail no two vendors agree on. - -### Why the convergence is a fact, not a fashion - -A rigid-body placement is an element of SE(3). A mate leaves some set of relative motions free. For -the mate to behave the same throughout its range — for a hinge to be a hinge at every angle — that -free set must be **closed under composition**: two allowed motions must compose to an allowed motion. -A closed set of motions is a **subgroup** of SE(3). - -The subgroups corresponding to physical surface-on-surface contact are the classical **six lower -pairs** (Reuleaux): - -| Pair | Free motion relative to Z | DOF | -|---|---|---| -| Revolute (R) | rotation about Z | 1 | -| Prismatic / slider (P) | translation along Z | 1 | -| Helical / screw (H) | coupled rotation + translation | 1 | -| Cylindrical (C) | rotation about **and** translation along Z | 2 | -| Planar (E/G) | translation in X,Y + rotation about Z | 3 | -| Spherical / ball (S) | rotation about X, Y, Z | 3 | - -Plus the two trivial ends: identity (0 DOF — **fastened**) and all of SE(3) (6 DOF — floating, i.e. -no mate). Hervé's Lie-subgroup analysis of the displacement group is the standard reference for -treating these as the algebraic building blocks of mechanism synthesis. - -**Consequence.** Anything outside this table is either (a) a *composition* needing a solver, or -(b) not a joint at all but a *measurement*: - -- Onshape's **Parallel** (4 DOF), **Tangent**, **Width**, **Pin slot**, and FreeCAD's **Distance / - Angle / Perpendicular** are constraints, not pairs — their free set is not a subgroup, so they only - make sense alongside a simultaneous solver. -- **Gear, Belt, Rack-and-pinion** are *relations between two mates*, a different object entirely. -- **Screw (H)** is a legitimate lower pair but needs a pitch parameter and is rare in printed parts. - -So the vendors' shared five, the lower pairs, and our `mate_kind` 0–4 are the same list arrived at -three ways. **[INDUSTRY] Stop looking for missing types and spend the budget on the connector.** - ---- - -## 4. What they all agree on — adopt verbatim - -Deviating from any of these makes an experienced user's intuition *wrong*, which is the operational -definition of "confusing". - -**A1 [INDUSTRY] — The connector is a full right-handed frame.** -Origin + Z (primary) + X (secondary). Onshape and Fusion expose exactly these two axis controls and -nothing else. A point cannot express spin; an axis cannot express clocking. -*Status: we comply* — `DatumCoordSys` carries origin/x/y and derives Z. - -**A2 [INDUSTRY] — Z is the joint axis; every DOF is about or along Z.** -Revolute rotates about Z. Slider translates along Z. Planar's free plane is normal to Z. Offsets run -along Z. This single rule is what makes the system learnable: **one axis to look at, and its meaning -never changes.** -*Status: we comply* — `mate_offset` along A's z, `mate_angle` about A's z. - -**A3 [INDUSTRY] — Mating superimposes the two frames; the type then relaxes specific DOF.** -FreeCAD states it most plainly: *"the second connector is superimposed on the first connector by -default and may change its position according to the joint type."* Fastened is not a special case — -it is the base case with nothing relaxed. -*Status: we comply* — `T = M_A · Rz · Tz · F · M_B⁻¹`, looser kinds relaxing from there. - -**A4 [INDUSTRY] — The connector belongs to a part and moves with it.** -Onshape: a connector defined in a Part Studio *"is available for reuse on every instance of that part -in every assembly in which it is instanced."* It is part geometry, not assembly geometry. -*Status: **violated**.* `CoordSysType::PointWorld` is a bare world XYZ with `X = world X` and no -`coordsys_body`. Such a connector does not follow its part. See §6 G1. - -**A5 [INDUSTRY] — Selection order is meaningful and must be visible.** -One connector is the reference; the other is driven onto it. Onshape spells out that offsets are -measured *"from the second Mate connector selected to the first"*, and that reversing the order -flips the sign. -*Status: complied with in the data model* (`mate_cs_a` fixed, `mate_cs_b` moves) *but not in the UI* — -two dropdowns labelled A and B do not tell the user which part is about to jump. - -**A6 [INDUSTRY] — Flip and re-clock live in the mate dialog, always.** -Onshape: *"Click the arrow icon to flip the direction of the primary axis. Click the Reorient -secondary axis icon to rotate the secondary axis in 90-degree increments."* -*Status: partial.* We have `mate_flip` (Z reversal). We have `mate_angle` as a free number — strictly -more powerful than 90° steps, and much worse to *use*: the common case is "it came in a quarter turn -out", and typing 90 is a worse gesture than pressing a button. - -**A7 [INDUSTRY] — DOF are shown, not inferred by the user.** -Onshape animates each mate's remaining DOF on demand; Fusion and Inventor name the DOF in the type -list. Our dropdown text already does this in words ("free spin + axial slide"). Keep it. - -**A8 [INDUSTRY] — Free DOF are preserved from the current placement, not zeroed.** -Onshape: a Planar mate aligns the frames *"but they are not restricted to this location with respect -to their degrees of freedom."* -*Status: we comply* — and it must be *said*, because a Planar mate that leaves the part where it was -looks like a mate that did nothing. - ---- - -## 5. Where they diverge — who to copy, and why - -### D1 — Where the connector's origin comes from - -| | Behaviour | -|---|---| -| **Fusion 360** | Discrete **snap points** only: vertex, edge midpoint, face centre, arc centre. `Ctrl` cycles the candidates under the cursor. A circle icon denotes a vertex, a triangle a midpoint. "Between two faces" is a separate explicit option. | -| **Onshape** | Infers a *family* on hover — centroid, every vertex, every edge midpoint, every arc centre, the centroids of interior regions (holes, slots), and the virtual sharps of conical faces. `Shift` locks the current candidate. | -| **Inventor** | Snap points, plus explicit joint origins for awkward cases. | -| **FreeCAD 1.0** | Hovering previews where the connector will land before you commit. | -| **Ours** | Always the **face centroid**. No alternative exists. | - -Onshape's richness has a cost its own documentation admits: *"The suggested locations are based on -the underlying geometry of the part and changing the geometry will change the location of the Mate. -This can be undesirable in certain situations."* On the forum this shows up as connectors that move -or break on edit — the classic topological-naming failure. Fusion's discrete set is poorer and far -more predictable. - -> **[INDUSTRY] Copy Fusion's candidate *set*.** A small, closed, enumerable set — **face centroid, -> vertex, edge midpoint, arc/circle centre** — each drawn before commit, with the card naming which is -> in use ("Origin: edge midpoint"). This is our largest expressiveness gap: a face centroid alone -> cannot place a hinge pin on a corner boss. It is also the one place where copying the *simpler* -> vendor is clearly right. -> -> **Open sub-choice — how the candidate is chosen.** Three options, in increasing order of magic: -> (1) **explicit dropdown** in the card after picking the face — no hover behaviour at all; -> (2) **Fusion's `Ctrl` cycling** through candidates under the cursor; (3) **Onshape's hover -> inference**. Kimi's independent review argued for (1) on the grounds that hover is exactly where -> both vendors' instability complaints originate, and that a dropdown gets ~90% of the expressiveness -> with none of the hover-guess debugging. That is a fair reading and (1) is the cheapest to build and -> the easiest to make unequivocal. **Recommendation: build (1) first; if hover is added later, let it -> *pre-fill the dropdown* rather than silently create an implicit connector** — which also keeps R2 -> (one kind of connector) intact. - -### D2 — Explicit type, or inferred from the geometry? - -Inventor is the only surveyed system that infers: *"Rotational is selected if the two selected -origins are circular. Cylindrical if the two selected origins are points on a cylinder. Ball if -points on a sphere. Rigid for all other origin selections."* Onshape and Fusion require an explicit -choice. - -> **[INDUSTRY, Inventor] Do both, in Inventor's order.** Infer a *default* type from what was picked, -> then show it in an editable control. Inference is what makes the tool feel like it understands the -> geometry; the visible, editable result is what keeps it unequivocal. Pure inference with no visible -> type is the confusing option; a pure dropdown with no default is the tedious one. This also fits -> the Design tab's geometry-first charter exactly: point at a bore, get Revolute offered. - -### D3 — How the Z-direction ambiguity is resolved - -This is the specific failure the brief is aimed at. A former IT trainer stated it precisely on the -Onshape forum: - -> *"There is always the risk that users will build their own conceptual models of how software works -> which may not match the designer's concept. The result is usually a poor user experience and many -> mistakes… for a good (say) Fixed mate to occur do the Z axes of the two mates have to be pointing -> in the same direction… Alternatively, should they be facing each other?"* - -He is asking the right question and **no vendor's documentation answers it.** Onshape's own advice — -*"if the behavior is not what you expected, try flipping the primary and/or secondary axis"* — is -trial and error. This is a gap in the industry, not a convention to copy. - -> **[INDUSTRY, method] Resolve it with live preview, not documentation.** FreeCAD previews the -> connector on hover; Onshape and Fusion both draw the frames. Draw **both** Z arrows the moment the -> second connector is picked, and ghost the resulting placement *before* Confirm. The convention then -> never has to be remembered because it is on screen. -> -> **[DEVIATION, optional] Name the two cases in the user's words** rather than in axis-speak: -> "the two faces come together" vs "the axes run the same way". No surveyed vendor does this — they -> all ship a flip arrow. It is a small, low-risk improvement on the state of the art, and it is -> separable from the default-direction question in §8 D1. - -### D4 — Named, reusable connectors on the part - -Onshape: connectors created in the Part Studio are reused on every instance in every assembly. -SOLIDWORKS' **mate reference** reaches the same end by another route: up to three named entities -(primary/secondary/tertiary) baked into the part so it auto-mates on drag-and-drop — and a *named* -mate reference seeks out a matching name on insertion. That naming trick is how a library of -fasteners assembles itself. - -> **[INDUSTRY] Out of scope now, but do not preclude it.** Give connectors a stable, user-visible -> name at creation. One string today; expensive to add once documents exist in the wild. - ---- - -## 6. Confusion catalogue - -Documented ways real implementations confuse people. Each is a requirement in disguise. - -**C1 — Which way does Z point?** See D3. If a user has to ask once, they will mis-predict a hundred -times. - -**C2 — The roll is unspecified.** Aligning Z leaves one rotation about Z undetermined. Something must -pin it, and if that something is world-derived, the frame does not rotate with its part. **This -codebase shipped exactly this bug** (`en4`): a face-only connector took Z from the face -normal but X from `coordsys_x_hint`, a world constant, so Fastened and Slider claimed to lock an -orientation the frame could not see. Fixed 2026-07-26 by deriving X from the face's own first usable -edge — but note the fix's own caveat: *"replaying an older document whose face-only connector fed a -mate can now place that body differently."* Roll conventions are load-bearing, and changing one is a -document-format change. - -**C3 — The origin drifts.** See D1. - -**C4 — Implicit and explicit connectors are not the same thing.** On the Onshape forum, implicit -connectors are reported to change their query structure when a feature is edited and re-accepted, and -are unusable in places explicit ones work. Two things called by one name that behave differently is a -permanent tax. - -**C5 — Which part moves?** A frame alignment is asymmetric. If the UI does not say which frame is -driven, the user finds out by watching the wrong part jump. - -**C6 — Which direction is a positive offset?** Onshape measures *"from the second Mate connector -selected to the first"* — the sign depends on pick order, and swapping the picks flips it. Documented -behaviour, documented surprise. - -**C7 — One intent, several mates.** The SOLIDWORKS failure: expressing "this shaft is in this hole, -resting on this shoulder" as three constraints, then discovering the solver picked the mirror -configuration. Frame-based systems fix this by construction; the requirement is not to reintroduce it. - -**C8 — Degenerate frames.** A circular face has no usable in-plane edge direction; a cylinder seam -projects to nothing; a picked edge parallel to Z gives a zero cross product. `datum_frame` handles all -three with fallbacks — the requirement is that a fallback be *visible*, because a silent fallback is -C2 wearing a different hat. - -**C9 — Order dependence without a solver.** Onshape can say *"Onshape solves Mates simultaneously so -order won't affect a Mate."* A system that composes transforms in tree order cannot say that. Two -mates driving one body means the second wins and the first is a lie on screen. - -**C10 — Mirrors and patterns.** A mirrored instance has a left-handed frame. Blindly mirroring a -connector gives a frame whose Z still points "out" but whose handedness flipped, so every rotation -runs backwards. Cheap to handle now, miserable to retrofit. - ---- - -## 7. Requirements - -Labelled **[INDUSTRY]** (what the frame-based systems do) or **[DEVIATION]** (we would depart). - -### Definition - -**R1 [INDUSTRY] — A mate connector is a frame attached to exactly one body.** No body, no connector. -*Test:* creating a connector without a body is rejected at creation, not at mate time. -→ **`CoordSysType::PointWorld` violates this.** It is a datum wearing a connector's name. - -**R2 [INDUSTRY] — One kind of connector, not two.** No "implicit" connector that behaves differently -from an explicit one. If hover inference is offered, hovering *creates* an ordinary connector. -*Why:* C4. *Test:* everything that accepts a connector accepts any connector. - -**R3 [INDUSTRY] — A mate names exactly one subgroup of free motion.** Fastened (0), Revolute (1), -Slider (1), Cylindrical (2), Planar (3), optionally Ball (3). *Why:* §3. *Test:* every type's free -set is closed; no type is "A and also B". - -### Orientation - -**R4 [INDUSTRY] — Everything is about Z. Say so once, in the UI.** *Test:* no mate parameter refers -to any other axis. - -**R5 [DEVIATION] — Z is the outward material direction, and mates default to FACING.** -A mate would drive B's Z onto **−A's Z** by default, so picking two faces that should touch makes -them touch with no options changed. *Why:* it is the whole of C1. -**Cost and caveat:** this inverts today's default (`mate_flip=false` currently *aligns*), and I could -not establish from any vendor's documentation what their default actually is — the forum question in -D3 went unanswered precisely because it is undocumented. So this is marked a deviation on the honest -grounds that **I cannot prove the industry agrees with it.** If D3's live preview lands first, the -default matters much less, because the user sees the outcome before committing. See §9 D1. - -**R6 [DEVIATION] — Name the two directions; do not ship a boolean called "flip".** -`Direction: Facing | Aligned`. Every surveyed vendor ships a flip arrow instead. A boolean requires -remembering what unticked means; two named values do not. Low risk, small improvement on the state of -the art. - -**R7 [INDUSTRY] — Roll is picked, or a stored quarter turn. Never world-derived.** -X from a referenced edge or in-plane direction; failing that, a deterministic body-attached seed, with -**Rotate 90°** offered as a stored integer 0–3 on top (this is Onshape's "reorient secondary axis", -A6). *Why:* C2 and the world-constant bug this project already shipped. *Test:* rotate the parent -body by any angle; the connector's X rotates with it — *this test already exists* ("a face-only frame -rotates with its body"). - -**R8 [INDUSTRY] — A degenerate roll is reported, not absorbed.** *Test:* a connector on a full -cylindrical face reports "roll undefined — pick a direction" rather than silently taking a fallback. - -### Placement - -**R9 [INDUSTRY, Fusion] — Origin comes from a small closed set of named candidates.** -**Face centroid, arc/circle centre, edge midpoint, vertex.** Four. Each stored as -`(kind, topological reference)` and resolved at rebuild. *Why:* D1. *Test:* the stored kind is visible -in the card; a rebuild either resolves it or raises an error. - -**R10 [INDUSTRY] — An unresolvable reference is an error, never a silent relocation.** -*Test:* delete the referenced face; the mate reports "connector A: face not found" and the body stays -where it was. - -### Semantics without a solver - -**R11 [DEVIATION] — A body is driven by at most one mate. The second is refused.** -**No surveyed system does this** — they all have solvers and all accept many mates per body. It is -forced on us by tree-order composition: a second mate on the same body silently overrides the first -and the screen shows a configuration satisfying only one stated intent (C9). *Test:* creating a -second mate whose moving body already has one is rejected, naming the existing mate. -This is the single largest departure in this document. See §9 D4. - -> **A tempting misreading, checked and rejected.** It is easy to find the claim that Onshape mandates -> *"exactly one Mate between any two instances"*, which would make R11 an industry agreement rather -> than a deviation. **The Onshape page does not say that.** It says *"**Many assemblies require only** -> one Onshape Mate between any two instances"* and then lists, as an explicit remedy, *"**Use more -> than one Mate if necessary.**"* One mate per pair is Onshape's *typical case*, not its rule. R11 -> remains a deviation and must be justified on our own architecture, not on theirs. - -**R11a [DEVIATION] — The refusal list.** With no solver, these are unsupportable and must be refused -rather than half-done: a second mate on an already-driven body; cycles (A→B, B→A); closed loops -(A→B, A→C, B→C); relations *between* mates (gear, belt, rack-and-pinion, screw coupling); **joint -limits**, which nothing can enforce without a solver; and **dragging a body to exercise a free DOF**, -which requires keeping the body on the allowed manifold. Motion analysis and animation follow from the -same lack. *Requirement:* none of these may appear in the UI as something that half-works. - -**R12 [DEVIATION] — The mate graph is an acyclic forest rooted at fixed bodies.** A body reached by -no mate is fixed; cycles are refused. Same root cause as R11. *Test:* A→B, B→A rejected at creation. - -**R13 [INDUSTRY] — Free DOF are preserved from the current placement, and the user is told.** -Behaviour already matches Onshape (A8); the telling does not. *Test:* the card for any type with -DOF > 0 says which motions remain and that dragging exercises them. - -**R14 [INDUSTRY] — State what mirroring does to a connector.** -*Checked in the code:* `datum_frame` ends with a Gram-Schmidt forcing a right-handed frame -(`ds.x = Y.cross(Z)`), so a connector resolved on a mirrored body comes out **right-handed, not -mirror-imaged**. Z follows the mirrored face's outward normal, X follows a mirrored edge, handedness -is re-imposed. Defensible — a mate on the mirrored part still turns the way its type says — but it -means a mirrored sub-assembly is *not* the mirror image of the original in its rotation sense. -*Requirement:* document it and pin it with a test. *Why:* C10. - -### Feedback — the part that actually removes confusion - -**R15 [INDUSTRY] — Before Confirm, the card answers four questions in words.** Which body moves; -which way Z points on each connector; how many DOF remain; what the offset is measured from. - -**R16 [INDUSTRY] — Draw both frames live, with Z distinguishable, and ghost the result.** -Two triads with Z rendered differently from X/Y (length, arrowhead, colour). *Why:* D3 — the fastest -way to make a convention unequivocal is to show it. *Test:* both Z directions are readable in a -screenshot. - -**R17 [INDUSTRY] — Show the DOF budget per body.** "Body 2: 1 of 6 DOF free (rotation about Z)." -The most educational readout in any assembly system, and free to compute here — the type *is* the DOF -count. *Test:* the number changes when the type changes. - -**R18 [DEVIATION] — Refuse loudly and name the alternative.** Where something is out of scope (a -second mate, a tangency, a gear ratio), say what is unsupported and what to do instead. Vendors do not -need this because their solvers accept the input. *Test:* no refusal message ends without a suggested -next action. - ---- - -## 8. Minimal specification, and gap analysis - -### The connector - -``` -MateConnector - body int required, ≥ 0 (R1) - origin_kind enum FaceCentroid | ArcCentre | EdgeMidpoint | Vertex (R9) - origin_ref topo ref face / edge / vertex index on that body - z_source implied by origin_kind: face normal, arc axis, edge tangent - roll_ref topo ref optional in-plane edge; else deterministic seed (R7) - roll_quarters int 0..3 stored quarter turns on top of the seed (R7, A6) - flip_z bool reverse Z at the connector - name string stable, user-visible (D4) -``` - -`flip_z` is a property of the **connector**, chosen once when it is made — not a per-mate -afterthought. Keeping connector-flip and mate-direction separate is what stops the "which flip do I -tick?" question. - -### The mate - -``` -Mate - kind enum Fastened | Revolute | Slider | Cylindrical | Planar [| Ball] (R3) - fixed connector A — its body does not move - moving connector B — its body is driven (A5, C5) - direction enum Facing | Aligned (R5, R6) - offset mm along A's Z, measured A → B — state this in the label (C6) - angle deg about A's Z (R4) -``` - -Within one field of what exists. - -### Gaps against today - -Source of record: `CadDocument.hpp:26,247-252,298-310`; `CadDocument.cpp:1669` (`datum_frame`), -`:2961` (`apply_mate`), `:1302` (`add_mate`); `DesignPanel.cpp:2671-2709` (the Mate card). - -| # | Gap | Severity | Ref | -|---|---|---|---| -| G1 | `PointWorld` connectors are not attached to a body and their X is a world constant | **High — data model** | A4/R1 | -| G2 | Origin is always the face centroid; no vertex / edge-midpoint / arc-centre snap | **High — expressiveness** | D1/R9 | -| G3 | No live preview of the two Z arrows or of the resulting placement | **High — this is the brief** | D3/R16 | -| G4 | Mate card is two abstract dropdowns; nothing says which body moves | High — charter + A5 | R15 | -| G5 | No joint-type inference from the picked geometry | Medium — feel | D2 | -| G6 | `add_mate` validates nothing — no one-mate-per-body, no cycle check | Medium | R11/R12 | -| G7 | No `Ball` type | Low | §3 | -| G8 | Re-clocking needs a typed angle; no 90° step control | Low, cheap | A6/R7 | -| G9 | Degenerate roll falls back silently | Low | C8/R8 | -| G10 | Connectors have no stable user-facing name | Low now, expensive later | D4 | - -**Already aligned — do not "fix" these:** the five types and their DOF; the frame definition (A1); -Z as the joint axis (A2); superimpose-then-relax (A3); the fixed/moving asymmetry in the data model -(A5); DOF wording in the type list (A7); free-DOF preservation (A8); right-handed frames under mirror -(R14); and `en4`'s fix, which put roll derivation on the body where it belongs (C2). - -**The pattern worth naming: the kernel is in good shape and the concept is under-explained.** Half the -requirements here are wording and drawing, not geometry. The two real engineering items are R9 (origin -candidates) and R11/R12 (the mate-graph rules). - -### Expensive-to-retrofit decisions — get these right in the data model now - -Changing any of these after documents exist in the wild costs a migration, not an edit. - -1. **Topological reference stability.** Storing raw face/edge indices is brittle — editing a body - renumbers faces. Either persistent topology IDs, or store the named origin *kind* plus a - deterministic search that re-finds the same geometric intent on rebuild. The latter is cheaper and - probably sufficient here; it is also what makes R10's "error, never silent relocation" enforceable. -2. **Connector ownership** (R1). Remove `PointWorld` or bind it to a body. Do this first. -3. **Mate direction semantics** (R5/D1). Inverting the default rewrites the meaning of every saved - mate. -4. **Roll representation** (R7). "First usable edge" is better than world-X but still fragile. Store - an explicit roll reference plus quarter turns. -5. **Coordinate convention** — Z = joint axis, X = roll reference. Changing this after release - invalidates every mate. -6. **Units** — offset in mm, angle in degrees. Never change. -7. **Mirror handedness** (R14) — document the decision, do not let it stay an accident. -8. **Flat body index vs. a component tree.** Mates currently reference bodies in a flat vector. If - **sub-assemblies** are ever in scope, mates must reference nodes in a tree instead. Retrofitting - this is painful and it is the one item on this list not already implied elsewhere in the document — - **decide now whether nested assemblies are in scope.** -9. **Serialization field semantics.** Adding fields is easy; redefining `mate_flip` or - `coordsys_x_hint` is not. -10. **The one-mate-per-body rule** (R11). Enforce at creation. Relaxing it later by adding a solver is - straightforward; allowing many mates now and discovering later that they silently conflict is not. - ---- - -## 8b. The visual shape of the connector — polarity and verse - -Researched separately (2026-08-05) by downloading and **looking at** the vendors' own figures, not -by reading their prose. Files kept alongside this document in `doc/design/mate-connectors/`. - -### What the systems actually draw - -**Onshape** — verified from `planarfacemateconnectors.png`, `cylindricalmateconnectors.png`, -`linearedgemateconnectors.png`, `mateconnector-planarpoints.png`, `matepointiconLG.png`: - -> **A small circle with one quadrant filled, plus three short coloured axis arms (X red, Y green, -> Z blue).** - -Three parts, each doing one job: - -| Element | What it says | -|---|---| -| The **circle** | "I am a frame, and this is my XY plane." | -| The **filled quadrant** | **The roll.** The shaded sector is the +X/+Y quadrant. | -| The **coloured arms** | The three axis directions, Z distinguished by colour. | - -The quadrant is the cleverest part of the whole design and it is easy to miss. The figure -`matepointreorientsecondaryaxis.png` shows three connectors side by side with the quadrant in three -different rotations — **it is the live readout of "reorient secondary axis in 90° increments" (A6).** -One glyph element makes the otherwise-invisible clocking visible, and makes the 90° button's effect -legible before you commit. The toolbar icon `matepointiconLG.png` is that same circle-with-a-quadrant, -so the symbol is consistent from toolbar to viewport. - -Candidate snap points, before you choose one, are drawn as **plain small white dots** on the model -(clear in `mateconnector-planarpoints.png`: dots at every corner and edge midpoint). Candidate and -committed are deliberately different weights — dots propose, the circle-and-triad commits. - -**FreeCAD 1.0** — verbatim from the wiki: *"Connectors are local coordinate systems and are marked by -a symbol with three axes (X, Y, Z) and a circle representing the XY-plane."* Same core as Onshape — -circle plus triad — **without** the quadrant. - -**Fusion 360** — the joint origin glyph, plus a documented icon language for *candidates*: *"A circle -denotes a vertex, and a triangle denotes a midpoint."* Shape encodes what kind of point it is. - -**Convergent core:** *circle for the XY plane + coloured triad*. Onshape alone adds the roll quadrant. - -### What none of them draw — and it is exactly what was asked for - -**Nothing in any vendor's glyph says which connector is the reference and which one is about to -move.** Both ends of a mate are drawn identically. That is confusion C5 ("which part moves?") left -unsolved in the visual language, and it is why the honest recommendation earlier was a live ghost — -the ghost compensates for a glyph that does not carry the information. - -So the two things asked for split cleanly, and only one of them is solved upstream: - -- **Verse** (*verso* — which way it points): **solved**. Z has a colour and a direction. -- **Polarity** (which end receives, which end inserts; who is anchored, who travels): **unsolved - everywhere.** This is open ground, and getting it right is a genuine improvement rather than a - deviation to justify. - -### Our starting point - -**We draw nothing.** `resolve_datum_coordsys()` (`CadDocument.cpp:1749`) has exactly one consumer in -the entire tree — `McpControl.cpp:1310`, the agent socket. A mate connector is today visible only to -a program. The glyph is unbuilt, so there is no migration cost to designing it properly now. - -### Proposed glyph: the magnet - -Adopt Onshape's proven core, then add the missing polarity with a metaphor that carries its own -instructions. - -``` - ▲ solid cone on +Z ONLY ← verse - | - ────●──── ← the disc = XY plane, ● = exact origin - ▨ quadrant filled ← roll / clocking, steps 90° -``` - -**Rule 1 — verse: draw +Z and never −Z.** A single stem with a cone head, on the positive side only. -No stem below the disc. A double-headed axis is the one thing that guarantees the question gets asked; -an arrow that exists on one side only cannot be misread. Length is asymmetric on purpose. - -**Rule 2 — roll: keep Onshape's quadrant.** Filled sector = the +X/+Y quadrant. It rotates in 90° -steps with the reorient control (A6/R7). This is aligned *and* it is the only in-glyph answer to -"where is X?", which matters because Fastened and Slider lock the clocking. - -**Rule 3 — polarity: solid cone travels, open collar receives.** -- The **driven** connector (B, on the body that will move) draws a **solid filled cone** — the plug. -- The **fixed** connector (A) draws an **open ring / hollow cone outline** — the socket. - -Same silhouette, so they read as a matched pair; opposite fill, so which one is about to jump is -answerable at a glance and without a legend. Plug-into-socket is the one mechanical metaphor every -user of this tool already has in their hands. - -**Rule 4 — the pair reads as a magnet.** Draw a dashed line joining the two origins the moment both -are picked. Two poles, one field line. And because a magnet's north seeks a south, **"facing" becomes -the self-evident default** — which quietly settles open decision D1 (§9) on visual grounds rather than -on a convention nobody can look up. If the glyph looks like a magnet, nobody has to be told that two -faces which touch have opposed normals. - -**Rule 5 — three states, three weights.** - -| State | Drawing | -|---|---| -| **Candidate** (hover) | small dot only — Onshape's white dots; shape may encode kind, Fusion-style | -| **Picked** | full glyph: disc + quadrant + cone | -| **Degenerate roll** (C8/R8) | the quadrant is drawn **hollow/hatched** — "roll undefined, pick a direction" | - -That last row is worth the trouble: it turns R8 from a message nobody reads into a mark you cannot -miss, and it costs one branch in the renderer. - -**Rule 6 — do not reuse the existing triad.** The bed-centre world triad -(`DesignCanvas.cpp:65`, `set_axes_at_bed_center`) and the move gizmo are already three-coloured arrows. -The connector must not be a fourth set of RGB arrows or the viewport becomes unreadable. The disc and -the quadrant are what distinguish it; keep the arms short, and consider drawing only Z on the -committed glyph, with X/Y implied by the quadrant. - -### Built and judged in the viewport, not in a mock - -The browser mock that first accompanied this section was the wrong instrument and its proportions -were meaningless: **every gizmo in this codebase is sized in SCREEN PIXELS** via `upp = 1/zoom` -(`render_shell_gizmo` uses `15.0 * upp`, `render_hole_gizmo` `9.0 * upp` for its cube). A connector -is a symbol, not a part — it must not shrink with the model. Nothing about that is visible in SVG. - -The glyph was therefore implemented and driven on the rig. Screenshots: `g-0*.png`, left in the workspace `artifacts/shots/` and not moved into the repo. -Five findings, none of which a mock could have produced: - -**F1 — Three axis arms lose to one.** Rendered side by side (`ORCA_CAD_GLYPH=A` vs default), the -Onshape-style RGB trio crowds a 22 px disc: the arrowheads are as large as the disc, they bury the -gold quadrant, and at an oblique angle the three heads pile into a coloured smudge. Worse, **it is -indistinguishable from the move gizmo and the bed triad**, which are already RGB arrow trios in this -viewport. One-sided Z wins on evidence, not taste. (`g-01-zoom.png` vs `g-02-zoom.png`.) - -**F2 — Polarity works, and colour does more of the work than fill.** A filled blue head against an -open grey outline head is readable instantly at 22 px (`g-03-zoom.png`). But the fill difference is -the *second* cue; the colour split carries it. Keep both — fill survives greyscale and colour-blind -palettes, colour survives small size. - -**F3 — Depth off floats, depth on tears.** With `GL_DEPTH_TEST` off, connectors on faces pointing -*away* from the camera still drew their discs over the solid, so the part looked covered in frames -that were really on its back. Turning depth on fixed that and immediately caused **z-fighting**: the -disc is exactly coplanar with its face, and came out as a broken dotted arc. The fix is depth **on** -plus a sub-pixel lift along Z (`0.7 * upp`), scaled by `upp` so it never becomes a visible gap on -zoom-in. Both failure modes are in the images (`g-03` torn, `g-04` clean). - -**F4 — The quadrant is the first thing to die at a grazing angle.** On a face seen nearly edge-on the -disc foreshortens to a sliver and the fan collapses into a blob (`g-01-zoom.png`, lower-right glyph). -The roll is exactly the information that is hardest to read when you most need it. Not yet solved — -see the open item below. - -**F5 — Roll-undefined in red is too loud.** It works, but it makes the *least* important connector -the most eye-catching thing on screen. Amber, or the same grey with a hatched quadrant, is enough. - -Also surfaced while testing, and unrelated to the glyph: `add_mate` accepted a mate between two -connectors **on the same body**, which is meaningless, and duly transformed the body relative to -itself. Concrete instance of gap G6. - -**Still untested:** a true grazing view (the view-cube click missed), a connector on a curved face, -and behaviour when a connector overlaps the move gizmo. F4 is the open design question — the disc may -need to billboard its *quadrant* while keeping the disc in-plane, which is a compromise no surveyed -vendor makes and which should be tried before being adopted. - -### What this costs - -A renderer for `resolve_datum_coordsys()` — which does not exist and has to be written whatever glyph -is chosen — plus one dashed line and three fill states. No kernel work. It is the same piece of work -as G3 (live preview), and doing them together is what makes the mate card honest. - ---- - -## 8c. The "faceted ridge dome" proposal — built, rendered, judged - -A colleague proposed replacing the flat disc with an **asymmetric low-poly solid**: a faceted -prismatic wedge with a dominant longitudinal ridge that **slopes** from a tall steep back to a long -shallow front, plus a male protrusion / female pocket pair with a 0.2 mm clearance. - -It was built rather than discussed. `faceted_ridge_key.scad` (this folder) (6 vertices, 7 faces), -verified as a closed manifold, exported through OpenSCAD, and flat-shaded from five directions with -`render_key.py` / `render_stl.py`. Sheets: `rk-sheet.png`, `cmp-sheet.png`. - -### The verdict: the shape is right, the male/female polarity cue is not - -**It solves F4, decisively.** The grazing view — where the flat disc dies, its quadrant collapsing to -a blob — is the view where this shape is *most* legible: the tall back and long shallow front are -unmistakable in silhouette. At a grazing angle the silhouette IS the information, and this solid's -silhouette is maximally informative there. That is a real, evidence-backed win over what is currently -in the code. - -**Down the mating axis (+Z) it also reads well**, which matters because that is the natural viewing -direction when you are looking at a face you intend to mate. - -**One degenerate view, and it is not the one I predicted.** I expected the ±X views (along the ridge) -to be silhouette-ambiguous, resolved only by shading. Wrong: front and back are clearly *different* — -the front shows several facets, the back is a **single flat featureless triangle**. So they are not -confusable, but the view from directly behind the tall end tells you nothing about roll or slope. -A second blind spot remains untested: from below the base, where the protrusion is hidden behind its -own face. - -**The female half fails, and much harder than expected.** Rendered with flat shading and no outlines — -the honest test, since a viewport draws no black edges — a recessed pocket is *invisible*: iso and -grazing show a plain block with a hairline; straight down the axis shows a **completely blank -rectangle**. The interior faces are lit almost identically to the top face and are occluded by the rim -from most angles. As a polarity cue, male/female therefore works in exactly one direction and returns -nothing in the other. - -> **Conclusion: do not overload shape with all three jobs.** Let the solid carry **verse and roll**, -> where it is excellent, and carry **polarity on a second channel** — colour plus the filled/open head -> that already tested well at 22 px (F2). Drawing the fixed connector as an outline/wireframe of the -> same solid is the variant worth trying; drawing it as a pocket is not. - -### Two premises in the brief are wrong - -**"Avoid curved surfaces to optimise rendering computations / rapid mesh processing."** Not a reason -for a viewport glyph. There are 2–20 connectors on screen, the renderer pushes `GLModel` triangles -directly, and it performs no CSG or mesh processing at all. **The real argument for flat facets is -legibility**: hard normals give distinct value steps between adjacent facets, and the renders confirm -that is exactly what makes the shape readable from an arbitrary angle. Keep the constraint, fix the -justification. (For a *printed* part the original justification is sound for a different reason: flat -facets slice without the stair-stepping a tessellated curve produces.) - -**"0.2 mm clearance for smooth mechanical mating."** Meaningless for a glyph. A symbol mates with -nothing, and every gizmo here is sized in screen pixels via `upp`, so a millimetre tolerance has no -referent. This is the strongest signal that **the brief was written for a physical printed part**, -not for a viewport symbol — as are "scannable" and "mechanical mating". See the open question below. - -### Two defects the build caught that discussion would not have - -1. **The flank quads are not planar.** Written as `[0,3,5,4]` and `[1,4,5,2]` the base edge and the - ridge edge are skew, so the four corners do not share a plane — my own first draft asserted the - opposite in a comment. Left as quads, the tessellator picks the fold direction, the "flat facet" - promise is broken by an unspecified crease, and two exporters can disagree about the shape. Fixed - by triangulating explicitly (7 faces, Euler 6 − 11 + 7 = 2). -2. **The pocket punched through its own plate.** A 4.5 mm key against a 3 mm demo plate gives a - through-hole, not a pocket. Minimum stock = height + clearance + pocket depth + a wall. - -Also worth recording: the first female render was misleading because the debug renderer outlined -*every* triangle, so a flat top face triangulated by CGAL looked like a faceted dome. The instrument -lied before the geometry did. Conclusions were only drawn after outlines were removed. - -### Second opinion, and the one disagreement worth resolving - -Kimi reviewed the proposal independently and **rejected it for the viewport**. It agreed on the two -wrong premises, agreed the female pocket is unreadable, and added the useful framing that a -screen-constant symbol and a model-constant part feature are two different design spaces that cannot -be served by one geometry. It also noted correctly that there is **no single scalar** that removes -ambiguity from every view: you need one asymmetry in the base plane (for top-down roll) and one out -of plane (the ridge slope, for front/back). Our base is scalene, so it has both. - -Its central objection was numeric and testable: *"at 22 px with 6–8 facets each facet is 3–7 px wide, -that is at the aliasing limit … minimum useful size is roughly 32–48 px, which is not compatible with -a 22 px screen-constant symbol."* My own renders were ~300 px, so the claim was unaddressed by my -evidence and would have killed the concept if true. - -**Rendered at 22, 32 and 48 px (`size-test.png`), it is false for this shape.** At 22 px all three -views still read: the grazing view shows the tall back and shallow front unmistakably, and the -down-axis view keeps a strong dark/light split. The reason Kimi's arithmetic does not apply is that -this solid presents only **four or five large facets with high value contrast**, not eight small ones — -the silhouette does most of the work, and silhouettes survive downsampling far better than facet -detail does. - -*Honest limit on that result:* the test renderer has no anti-aliasing, no perspective, one directional -light, and no background. Readable at 22 px against white is not the same as readable at 22 px on top -of a shaded gold part next to the move gizmo. That case still needs the rig. - -**Where I do not follow Kimi:** its recommendation is to **billboard** the existing flat glyph so it -never turns edge-on. That kills F4 by construction, but a billboarded frame cannot show the frame's -orientation *in place* — which is the entire reason the disc is a disc and not a dot — and it is what -no surveyed CAD system does; Onshape, Fusion and FreeCAD all draw the frame in the geometry. Worth -prototyping as an option, not worth adopting on argument. - -### Open question for Tommaso - -**Is this a viewport glyph or a printable alignment feature?** The vertex logic is identical either -way; only the units and the clearance change, and the `.scad` file states both readings. But the -answer decides whether `clr`/`depth` are real millimetres or meaningless, and whether the geometry -scales with the model or stays screen-constant. The brief's own language points at "physical", the -conversation it arrived in points at "glyph". - ---- - -## 9. Decisions for you - -**D1 — Invert the default direction to Facing?** [DEVIATION, R5] -It changes the meaning of every stored document containing a mate. Options: (a) invert and migrate, -writing `direction=Aligned` where `mate_flip` was false; (b) invert only for new mates and store -`direction` explicitly from now on. (b) is safer and costs one field. Note this project has taken one -such semantic hit knowingly before — the `en4` fix — and the golden fixture survived, so the -migration path is a known quantity. **If G3 (live preview) lands first, this matters much less.** - -**D2 — How far to take origin candidates?** [R9] -Four kinds is the Fusion-aligned recommendation. Two (face centroid + arc centre) would cover "sit on -a face" and "go down a hole" — most printed-part assembly — at a third of the work. Where do you want -to stop? - -**D3 — Ball mate: in or out?** -In four of five frame-based systems, so including it is the aligned choice. Out is defensible for -printable mechanical parts. Cheap either way — align origins, leave orientation free. Kimi's review -argued **out**: a true ball joint is hard to print and hard to use without a roll reference, and a -Fastened connector at the ball centre approximates it. - -**D3a — Should Planar be dropped?** [dissent worth recording] -Kimi's independent review recommended **removing Planar** and shipping four types, on the grounds that -"slide on a flat surface" is rarely how printed mechanisms work — you usually want a rail or a hinge — -and that Planar is the type most likely to confuse a user who expected "put this flat on that" and got -a part free to slide. It further ranked the honest minimum as **three**: Fastened, Revolute, Slider, -with Cylindrical useful and decomposable. -**I do not agree, and the reason is alignment.** Planar appears in every frame-based system surveyed, -it is a genuine lower pair, it is already implemented and tested, and removing it is a document-format -change made in exchange for nothing. The confusion Kimi names is real but it is a *feedback* problem — -it is exactly what R17 (show the DOF budget) and R13 (say that free DOF are preserved) exist to fix. -Recorded here because it is a legitimate reading of the same evidence and the call is yours. - -**D4 — Is refusing a second mate per body acceptable?** [DEVIATION, R11 — the big one] -It is the honest consequence of having no solver, and it is what makes the tool predictable. But **no -mainstream system behaves this way**, so it is the point where an experienced user's intuition will -break. It means a part cannot be constrained by two independent relationships — "in this hole *and* -resting on this shoulder" must be expressed by placing one connector correctly rather than by two -mates. If that trade is unacceptable, the answer is a solver, and the scope of this document changes -entirely. - -There is a strong argument that the trade is not merely acceptable but *correct for this product*: -the Design tab lives inside a slicer, and most of its users are positioning parts for printing rather -than building working mechanisms. For layout-and-export, tree-order composition is genuinely enough, -and adding a solver to look like Onshape would buy complexity nobody asked for. The rule to publish is -then simple and defensible: **one mate per moving body, acyclic, no relations between mates** — with -R18's loud refusals carrying the honesty. - ---- - -## Sources - -**Onshape** — [Mate Connector](https://cad.onshape.com/help/Content/PartStudio/mate_connector.htm) · -[Mates](https://cad.onshape.com/help/Content/Assembly/mates.htm) · -[Fastened](https://cad.onshape.com/help/Content/Assembly/fastened_mate.htm) · -[Revolute](https://cad.onshape.com/help/Content/Assembly/revolute_mate.htm) · -[Slider](https://cad.onshape.com/help/Content/Assembly/slider_mate.htm) · -[Cylindrical](https://cad.onshape.com/help/Content/Assembly/cylindrical_mate.htm) · -[Planar](https://cad.onshape.com/help/Content/Assembly/planar_mate.htm) · -[Ball](https://cad.onshape.com/help/Content/Assembly/ball_mate.htm) · -[Parallel](https://cad.onshape.com/help/Content/Assembly/parallel_mate.htm) · -[Tangent](https://cad.onshape.com/help/Content/Assembly/tangent_mate.htm) · -[Pin Slot](https://cad.onshape.com/help/Content/Assembly/pin_slot_mate.htm) · -[5 things you can do with mate connectors in Part Studios](https://www.onshape.com/en/resource-center/tech-tips/tech-tip-5-things-you-can-do-with-mate-connectors-in-onshape-part-studios) - -**Onshape forum** — [The concept behind Mates Z Axes](https://forum.onshape.com/discussion/22828/the-concept-behind-mates-z-axes) (C1/D3) · -[Implicit mate connectors act differently than explicit ones](https://forum.onshape.com/discussion/15736/implicit-mate-connectors-act-differently-than-explicit-ones) (C4) · -[Efficiently set mate connectors](https://forum.onshape.com/discussion/13133/efficiently-set-mate-connectors) - -**Fusion 360** — [Joint types](https://help.autodesk.com/cloudhelp/ENU/Fusion-Assemble/files/GUID-8818AE31-958A-4A59-989B-9875A174C67A.htm) · -[Joint origins](https://help.autodesk.com/view/fusion360/ENU/?guid=ASM-JOINT-ORIGIN) · -[Joints vs. Mates in Fusion](https://www.autodesk.com/products/fusion-360/blog/joints-mates-moving-fusion/) · -[Joint tips — snap points and Ctrl cycling](https://mgfx.co.za/blog/engineering-manufacturing-design/fusion-360-joint-tips/) - -**Inventor** — [Create Joints Reference](https://help.autodesk.com/cloudhelp/2026/ENU/Inventor-Help/files/GUID-6AA68E8F-7C97-4806-8483-3941DE915E70.htm) · -[Use Joint to define and manage relationships](https://knowledge.autodesk.com/support/inventor-products/learn-explore/caas/CloudHelp/cloudhelp/2014/ENU/Inventor/files/GUID-21DC3336-5C51-42C1-90FB-4299CD66E0C6-htm.html) (type inference, D2) - -**FreeCAD 1.0** — [Assembly Workbench](https://wiki.freecad.org/Assembly_Workbench) · -[Fixed Joint properties](https://wiki.freecad.org/Assembly_CreateJointFixed) - -**Creo** — [About Predefined Constraint Sets](https://support.ptc.com/help/creo/creo_pma/r12/usascii/assembly/asm/About_Predefined_Constraint_Sets.html) - -**Siemens NX** — [Assembly constraints](https://learnnx.com/lesson/siemens-nx-assemblies-assembly-constraints/) - -**SOLIDWORKS** — [Mate References](https://help.solidworks.com/2025/English/SolidWorks/sldworks/c_Mate_References_Overview_SWassy.htm) · -[Creating and using mate references](https://blogs.solidworks.com/tech/2019/07/creating-and-using-mate-references.html) - -**Theory** — [Hervé, The Lie group of rigid body displacements, a fundamental tool for mechanism design](https://www.sciencedirect.com/science/article/abs/pii/S0094114X98000512) · -[Joint kinematics — the six lower pairs and their DOF](https://erc-bpgc.github.io/handbook/mechanical/Joint%20Kinematics/) · -[ISO 10303-105 — Kinematics (STEP integrated resource)](https://www.iso.org/standard/78589.html) - -**Internal** — `en4` (closed 2026-07-26, fixes C2 here) · `CadDocument.cpp:1669` -`datum_frame` · `CadDocument.cpp:2961` `apply_mate` · `CadDocument.cpp:1302` `add_mate` - -**Second opinion** — an independent review by Kimi Code (2026-08-05) contributed the -vendors-ship-both caveat (§1), the explicit-dropdown option for origin choice (D1), the expanded -refusal list (R11a), the retrofit list (§8), and the dissents recorded at D3/D3a. One of its claims — -that Onshape mandates *"exactly one Mate between any two instances"* — **was checked against the -source and is wrong**; the correction is recorded at R11 because it is a misreading that would -otherwise turn our largest deviation into a false agreement. diff --git a/docs/CAD/design/mate-connectors/bear-01-view.png b/docs/CAD/design/mate-connectors/bear-01-view.png deleted file mode 100644 index eb57aaeefeb05111f1dd73b2c4ac263889f67a79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 100101 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%0AuQeqlYF3LNtKh+lZ@ zW(VMB85=Ts;Cs%dF0xZze5iC#$mo3IFb|$a^)vO0j1qsQtyUT`DOI#>@11x1cWk%% zkYxqkUP`So_o3HRq5muB{r|xA?TLaA?ovJ~!`)t&2c+z7_DJ5d3KwGOng)CaO+f+_ zxe!5_4uT&0w8lw^?NzxoKU_(RBB1-Ni4CC@ADIN1oX_aW@72-kqoXN{Fs=$a@=l|T z$&7}t%VDQ8KV|t7V28t2+iWRfmqqkB`&~FAGkTmhEK|Y6O27oeW{ig;_#bX6a0nmyH9zn82^_}Yf85K^S=y{kTQx$N+n(|9b_=bjM}p0GTRD?#1pw|U&_psS^==8gL&KqTl-Z5v+^p@bQIEKg?w&KIcph2p1UAYOH^Tw~0#aAC z5cIO;fUM3~KD1hvT`B4^85H7jQ|48J@VG;;4FXCP;jB@0wd#BHGgf?1SNl87#?oB) zK2WI45$TfysBR4VFBDI1I(_*m%~(sER+!CSL6X! zU4BJ@%+My&R~f6-qOLb0uQz&B5_<~`bH{JhLXZ!D!#29yMR$NzK%lTU)mBkafsiB| zNq<(*5To@+3Vj#B%%3t~)F|8fSXufUvWohbPvyP;_7IH;-@$)Q9m_Q1RxmLNiHcl1 zG*~AUG@I06B-0wUDR(N1ziM*syrsSJMh}h4O2FMddX{(u786vy{pb$yYUW^v%&VhB zn}-F&x?+z@YNEVOg>V`&9EcA-hNv62L43D#)|!ir z;QQ}tvRC!D>`GZ7Z_|u>U0I4)r{OaRnmi>9;y-8h8FJGJs!>D08qB`|M#KrIG6GKRRb}(fFm|)jR(O=u+Bs diff --git a/docs/CAD/design/mate-connectors/bear-02-view.png b/docs/CAD/design/mate-connectors/bear-02-view.png deleted file mode 100644 index 46b6d834fa6befe33a26c983e5c0b0b6d18c7b4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96449 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

tH?fQ1=5)ch9`_(g`ZyOc@0!c#v}+} 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

G{N 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�x0Sm6~&D|6y0PApNkeatD8t_u1hi6rMbQK5)H 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ĘP7kcKg-36EBVJA4xyFli3NkA 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 #g3XNC7BP%J5}T3Q~E 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 diff --git a/docs/CAD/design/mate-connectors/bear-flat.png b/docs/CAD/design/mate-connectors/bear-flat.png deleted file mode 100644 index 16375b9d5c5a624e8c6a095efa86a98f7009071b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20664 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 diff --git a/docs/CAD/design/mate-connectors/bear-sil-cmp.png b/docs/CAD/design/mate-connectors/bear-sil-cmp.png deleted file mode 100644 index eb2f283f95fc4f888e55a9f750280eaab9fcd4bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1330 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 i(&g6_0Iy+$5dH%|#O}qQmyHPk0000 tail (short) - {-0.0048, -0.1793, +0.2073}, - {-0.0048, +0.1605, +0.1279}, -}; diff --git a/docs/CAD/design/mate-connectors/bear_mesh.json b/docs/CAD/design/mate-connectors/bear_mesh.json deleted file mode 100644 index 4bfb6aa361..0000000000 --- a/docs/CAD/design/mate-connectors/bear_mesh.json +++ /dev/null @@ -1 +0,0 @@ -{"v": [[-36.7146, 0.0, -1.7146], [-36.0624, 0.0, -1.152], [-36.5065, 0.0, -1.5165], [-36.2889, 0.0, -1.3288], [-39.9409, 0.0, -4.9818], [-40.1196, 0.0, -5.2115], [-39.751, 0.0, -4.7612], [-39.5503, 0.0, -4.5503], [-35.8275, 0.0, -0.9866], [-35.3348, 0.0, -0.6912], [-35.5848, 0.0, -0.8329], [-40.4417, 0.0, -5.6962], [-40.5842, 0.0, -5.95], [-40.2867, 0.0, -5.4499], [-35.0781, 0.0, -0.5621], [-40.714, 0.0, -6.2105], [-34.5473, 0.0, -0.3422], [-34.8154, 0.0, -0.4456], [-40.8307, 0.0, -6.4772], [-40.934, 0.0, -6.7493], [-41.0236, 0.0, -7.0262], [-33.9976, 0.0, -0.1754], [-34.2745, 0.0, -0.2521], [-41.161, 0.0, -7.5917], [-41.2085, 0.0, -7.8789], [-41.0994, 0.0, -7.3072], [-33.149, 0.0, -0.0282], [-33.7172, 0.0, -0.1125], [-33.4341, 0.0, -0.0634], [-32.8624, 0.0, -0.0071], [-32.5752, 0.0, -0.0], [-41.2544, 0.0, -9.0404], [-41.2298, 0.0, -9.3304], [-41.2646, 0.0, -8.7495], [-41.2604, 0.0, -8.4585], [-41.2417, 0.0, -8.168], [-41.1908, 0.0, -9.6189], [-41.1374, 0.0, -9.905], [-41.07, 0.0, -10.1881], [-40.9885, 0.0, -10.4676], [-21.9255, 0.0, -9.1038], [-21.9255, 0.0, -3.5345], [-20.0463, 0.0, -44.6525], [-19.8744, 0.0, -44.4763], [-19.694, 0.0, -44.309], [-20.2091, 0.0, -44.837], [-19.5055, 0.0, -44.1508], [-20.3625, 0.0, -45.0294], [-19.3094, 0.0, -44.0022], [-20.5062, 0.0, -45.2292], [-19.106, 0.0, -43.8635], [-20.6397, 0.0, -45.436], [-18.896, 0.0, -43.7352], [-20.7628, 0.0, -45.6491], [-18.6799, 0.0, -43.6175], [-20.875, 0.0, -45.8681], [-18.4582, 0.0, -43.5108], [-20.9762, 0.0, -46.0924], [-18.2314, 0.0, -43.4152], [-21.0662, 0.0, -46.3215], [-18.0002, 0.0, -43.331], [-17.765, 0.0, -43.2585], [-17.5265, 0.0, -43.1977], [22.3077, 0.0, -3.5345], [-17.2853, 0.0, -43.149], [-17.042, 0.0, -43.1123], [-16.7971, 0.0, -43.0878], [-16.5513, 0.0, -43.0755], [-16.3052, 0.0, -43.0755], [-16.0594, 0.0, -43.0878], [-15.8146, 0.0, -43.1123], [-15.5712, 0.0, -43.149], [-15.33, 0.0, -43.1977], [-0.0, 0.0, -9.1038], [-15.0915, 0.0, -43.2585], [-14.8564, 0.0, -43.331], [-21.2112, 0.0, -49.2274], [-26.7105, 0.0, -55.2632], [-21.1445, 0.0, -49.4643], [-21.2659, 0.0, -48.9875], [-21.3087, 0.0, -48.7451], [-21.3393, 0.0, -48.5009], [-21.3577, 0.0, -48.2555], [-21.3638, 0.0, -48.0095], [-21.3577, 0.0, -47.7635], [-21.3393, 0.0, -47.5181], [-21.3087, 0.0, -47.2739], [-21.2659, 0.0, -47.0316], [-21.2112, 0.0, -46.7916], [-21.1445, 0.0, -46.5547], [-21.0662, 0.0, -49.6976], [-14.6251, 0.0, -43.4152], [-20.9762, 0.0, -49.9266], [-14.3984, 0.0, -43.5108], [-20.875, 0.0, -50.151], [-14.1766, 0.0, -43.6175], [-20.7628, 0.0, -50.37], [-13.9605, 0.0, -43.7352], [-20.6397, 0.0, -50.5831], [-13.7505, 0.0, -43.8635], [-20.5062, 0.0, -50.7898], [-13.5472, 0.0, -44.0022], [-20.3625, 0.0, -50.9896], [-13.351, 0.0, -44.1508], [-20.2091, 0.0, -51.182], [-13.1625, 0.0, -44.309], [-20.0463, 0.0, -51.3665], [-19.8744, 0.0, -51.5427], [-19.694, 0.0, -51.7101], [-19.5055, 0.0, -51.8683], [-19.3094, 0.0, -52.0169], [-19.106, 0.0, -52.1555], [35.0, 0.0, 0.0], [19.0522, 0.0, -18.4636], [16.4743, 0.0, -9.1399], [-27.6672, 0.0, -65.6993], [-27.6141, 0.0, -65.7466], [-27.7179, 0.0, -65.6494], [-27.5588, 0.0, -65.7913], [-27.4419, 0.0, -65.8722], [-27.3806, 0.0, -65.9082], [-27.5013, 0.0, -65.8332], [-27.3177, 0.0, -65.9412], [-27.2531, 0.0, -65.9711], [-27.1872, 0.0, -65.9978], [-27.1201, 0.0, -66.0212], [-27.0519, 0.0, -66.0413], [-26.9828, 0.0, -66.058], [-26.9129, 0.0, -66.0713], [-26.8425, 0.0, -66.0811], [-26.7007, 0.0, -66.0904], [-26.7717, 0.0, -66.0875], [-26.6296, 0.0, -66.0899], [-26.4879, 0.0, -66.0782], [-26.4177, 0.0, -66.0672], [-26.5586, 0.0, -66.0858], [-26.3481, 0.0, -66.0528], [-26.2792, 0.0, -66.035], [-26.2114, 0.0, -66.0138], [-26.1446, 0.0, -65.9893], [-26.0792, 0.0, -65.9615], [-26.0151, 0.0, -65.9306], [-25.9527, 0.0, -65.8966], [-25.892, 0.0, -65.8595], [-25.8332, 0.0, -65.8196], [-25.7765, 0.0, -65.7767], [-25.7219, 0.0, -65.7312], [-25.6696, 0.0, -65.683], [-28.9439, 0.0, -64.3825], [-29.1102, 0.0, -64.202], [-29.2675, 0.0, -64.0136], [-29.4153, 0.0, -63.8176], [-29.5533, 0.0, -63.6147], [-29.6811, 0.0, -63.4052], [-29.7986, 0.0, -63.1898], [-29.9053, 0.0, -62.9688], [-30.0011, 0.0, -62.7428], [-30.0856, 0.0, -62.5124], [-30.1588, 0.0, -62.2782], [-30.2204, 0.0, -62.0406], [-30.2702, 0.0, -61.8003], [-30.3082, 0.0, -61.5579], [-30.3342, 0.0, -61.3138], [-30.3483, 0.0, -61.0688], [-30.3503, 0.0, -60.8234], [-30.3402, 0.0, -60.5782], [-30.3182, 0.0, -60.3338], [-30.2841, 0.0, -60.0908], [-30.2382, 0.0, -59.8497], [-30.1805, 0.0, -59.6111], [-30.1112, 0.0, -59.3757], [-30.0304, 0.0, -59.144], [-29.9383, 0.0, -58.9165], [-29.8352, 0.0, -58.6938], [-29.7213, 0.0, -58.4764], [-29.5969, 0.0, -58.2649], [-13.7505, 0.0, -52.1555], [-20.6129, 0.0, -60.7895], [-13.5472, 0.0, -52.0169], [-13.9605, 0.0, -52.2838], [-14.1766, 0.0, -52.4015], [-14.3984, 0.0, -52.5083], [-14.6251, 0.0, -52.6039], [-14.8564, 0.0, -52.688], [-15.0915, 0.0, -52.7606], [-15.33, 0.0, -52.8213], [-15.5712, 0.0, -52.8701], [-15.8146, 0.0, -52.9067], [-16.0594, 0.0, -52.9312], [-16.3052, 0.0, -52.9435], [-16.5513, 0.0, -52.9435], [-16.7971, 0.0, -52.9312], [-17.042, 0.0, -52.9067], [-17.2853, 0.0, -52.8701], [-17.5265, 0.0, -52.8213], [-17.765, 0.0, -52.7606], [-18.0002, 0.0, -52.688], [-18.2314, 0.0, -52.6039], [-18.4582, 0.0, -52.5083], [-18.6799, 0.0, -52.4015], [-18.896, 0.0, -52.2838], [-29.4622, 0.0, -58.0598], [-29.3176, 0.0, -57.8615], [-29.1635, 0.0, -57.6705], [-29.0002, 0.0, -57.4873], [-28.828, 0.0, -57.3124], [42.0711, 0.0, -7.0711], [-12.9821, 0.0, -44.4763], [12.9821, 0.0, -44.4763], [-12.8103, 0.0, -44.6525], [12.8103, 0.0, -44.6525], [-12.6474, 0.0, -44.837], [13.1625, 0.0, -44.309], [12.6474, 0.0, -44.837], [13.351, 0.0, -44.1508], [-12.494, 0.0, -45.0294], [12.494, 0.0, -45.0294], [13.5472, 0.0, -44.0022], [-12.3504, 0.0, -45.2292], [12.3504, 0.0, -45.2292], [-12.2168, 0.0, -45.436], [13.7505, 0.0, -43.8635], [-12.0938, 0.0, -45.6491], [12.2168, 0.0, -45.436], [13.9605, 0.0, -43.7352], [12.0938, 0.0, -45.6491], [14.1766, 0.0, -43.6175], [-11.9815, 0.0, -45.8681], [11.9815, 0.0, -45.8681], [14.3984, 0.0, -43.5108], [-11.8803, 0.0, -46.0924], [11.8803, 0.0, -46.0924], [14.6251, 0.0, -43.4152], [11.7904, 0.0, -46.3215], [-11.7904, 0.0, -46.3215], [-11.712, 0.0, -46.5547], [14.8564, 0.0, -43.331], [11.712, 0.0, -46.5547], [15.0915, 0.0, -43.2585], [-11.6454, 0.0, -46.7916], [11.6454, 0.0, -46.7916], [15.33, 0.0, -43.1977], [-11.5906, 0.0, -47.0316], [11.5906, 0.0, -47.0316], [15.5712, 0.0, -43.149], [-11.5479, 0.0, -47.2739], [11.5479, 0.0, -47.2739], [15.8146, 0.0, -43.1123], [-11.5173, 0.0, -47.5181], [11.5173, 0.0, -47.5181], [16.0594, 0.0, -43.0878], [11.4989, 0.0, -47.7635], [-11.4989, 0.0, -47.7635], [16.3052, 0.0, -43.0755], [11.4928, 0.0, -48.0095], [-11.4928, 0.0, -48.0095], [16.5513, 0.0, -43.0755], [-11.4989, 0.0, -48.2555], [11.4989, 0.0, -48.2555], [-11.5173, 0.0, -48.5009], [16.7971, 0.0, -43.0878], [11.5173, 0.0, -48.5009], [-11.5479, 0.0, -48.7451], [17.042, 0.0, -43.1123], [11.5479, 0.0, -48.7451], [17.2853, 0.0, -43.149], [11.5906, 0.0, -48.9875], [-11.5906, 0.0, -48.9875], [17.5265, 0.0, -43.1977], [11.6454, 0.0, -49.2274], [-11.6454, 0.0, -49.2274], [17.765, 0.0, -43.2585], [-11.712, 0.0, -49.4643], [11.712, 0.0, -49.4643], [-11.7904, 0.0, -49.6976], [18.0002, 0.0, -43.331], [11.7904, 0.0, -49.6976], [18.2314, 0.0, -43.4152], [-11.8803, 0.0, -49.9266], [11.8803, 0.0, -49.9266], [18.4582, 0.0, -43.5108], [-11.9815, 0.0, -50.151], [11.9815, 0.0, -50.151], [18.6799, 0.0, -43.6175], [-12.0938, 0.0, -50.37], [12.0938, 0.0, -50.37], [18.896, 0.0, -43.7352], [-12.2168, 0.0, -50.5831], [12.2168, 0.0, -50.5831], [-12.3504, 0.0, -50.7898], [19.106, 0.0, -43.8635], [12.3504, 0.0, -50.7898], [19.3094, 0.0, -44.0022], [-12.494, 0.0, -50.9896], [12.494, 0.0, -50.9896], [19.5055, 0.0, -44.1508], [-12.6474, 0.0, -51.182], [12.6474, 0.0, -51.182], [-12.8103, 0.0, -51.3665], [19.694, 0.0, -44.309], [12.8103, 0.0, -51.3665], [-12.9821, 0.0, -51.5427], [19.8744, 0.0, -44.4763], [-13.1625, 0.0, -51.7101], [12.9821, 0.0, -51.5427], [20.0463, 0.0, -44.6525], [13.1625, 0.0, -51.7101], [20.2091, 0.0, -44.837], [13.351, 0.0, -51.8683], [-13.351, 0.0, -51.8683], [20.3625, 0.0, -45.0294], [13.5472, 0.0, -52.0169], [20.5062, 0.0, -45.2292], [20.6397, 0.0, -45.436], [20.7628, 0.0, -45.6491], [20.875, 0.0, -45.8681], [20.9762, 0.0, -46.0924], [20.6129, 0.0, -60.7895], [18.896, 0.0, -52.2838], [19.106, 0.0, -52.1555], [18.6799, 0.0, -52.4015], [18.4582, 0.0, -52.5083], [18.2314, 0.0, -52.6039], [18.0002, 0.0, -52.688], [17.765, 0.0, -52.7606], [17.5265, 0.0, -52.8213], [17.2853, 0.0, -52.8701], [17.042, 0.0, -52.9067], [16.7971, 0.0, -52.9312], [16.5513, 0.0, -52.9435], [16.3052, 0.0, -52.9435], [16.0594, 0.0, -52.9312], [15.8146, 0.0, -52.9067], [15.5712, 0.0, -52.8701], [15.33, 0.0, -52.8213], [15.0915, 0.0, -52.7606], [14.8564, 0.0, -52.688], [14.6251, 0.0, -52.6039], [14.3984, 0.0, -52.5083], [14.1766, 0.0, -52.4015], [13.9605, 0.0, -52.2838], [13.7505, 0.0, -52.1555], [26.7105, 0.0, -55.2632], [21.3577, 0.0, -48.2555], [21.3638, 0.0, -48.0095], [21.3393, 0.0, -48.5009], [21.3087, 0.0, -48.7451], [21.2659, 0.0, -48.9875], [21.2112, 0.0, -49.2274], [21.1445, 0.0, -49.4643], [21.0662, 0.0, -49.6976], [20.9762, 0.0, -49.9266], [20.875, 0.0, -50.151], [20.7628, 0.0, -50.37], [20.6397, 0.0, -50.5831], [20.5062, 0.0, -50.7898], [20.3625, 0.0, -50.9896], [20.2091, 0.0, -51.182], [20.0463, 0.0, -51.3665], [19.8744, 0.0, -51.5427], [19.694, 0.0, -51.7101], [21.0662, 0.0, -46.3215], [19.5055, 0.0, -51.8683], [21.1445, 0.0, -46.5547], [19.3094, 0.0, -52.0169], [21.2112, 0.0, -46.7916], [21.2659, 0.0, -47.0316], [21.3087, 0.0, -47.2739], [21.3393, 0.0, -47.5181], [21.3577, 0.0, -47.7635], [26.7105, 0.0, -66.6904], [32.4211, 0.0, -60.7895], [-40.1196, 3.0, -5.2115], [-39.9409, 3.0, -4.9818], [-39.751, 3.0, -4.7612], [-39.5503, 3.0, -4.5503], [-35.3348, 3.0, -0.6912], [-35.8275, 3.0, -0.9866], [-35.5848, 3.0, -0.8329], [-36.0624, 3.0, -1.152], [-40.714, 3.0, -6.2105], [-40.5842, 3.0, -5.95], [-40.4417, 3.0, -5.6962], [-40.2867, 3.0, -5.4499], [-34.5473, 3.0, -0.3422], [-35.0781, 3.0, -0.5621], [-34.8154, 3.0, -0.4456], [-41.0236, 3.0, -7.0262], [-40.934, 3.0, -6.7493], [-40.8307, 3.0, -6.4772], [-33.9976, 3.0, -0.1754], [-34.2745, 3.0, -0.2521], [-36.5065, 3.0, -1.5165], [-36.2889, 3.0, -1.3288], [-41.2085, 3.0, -7.8789], [-41.161, 3.0, -7.5917], [-41.0994, 3.0, -7.3072], [-33.149, 3.0, -0.0282], [-33.7172, 3.0, -0.1125], [-33.4341, 3.0, -0.0634], [-36.7146, 3.0, -1.7146], [-41.2417, 3.0, -8.168], [-32.8624, 3.0, -0.0071], [-32.5752, 3.0, -0.0], [-41.2298, 3.0, -9.3304], [-41.2544, 3.0, -9.0404], [-41.2646, 3.0, -8.7495], [-41.2604, 3.0, -8.4585], [-41.07, 3.0, -10.1881], [-41.1374, 3.0, -9.905], [-41.1908, 3.0, -9.6189], [-40.9885, 3.0, -10.4676], [-21.9255, 3.0, -9.1038], [-21.9255, 3.0, -3.5345], [5.6569, 3.0, -13.2042], [-5.6569, 3.0, -13.2042], [-0.0, 3.0, -9.1038], [-20.0463, 3.0, -44.6525], [-19.8744, 3.0, -44.4763], [-19.694, 3.0, -44.309], [-20.2091, 3.0, -44.837], [-19.5055, 3.0, -44.1508], [-20.3625, 3.0, -45.0294], [-19.3094, 3.0, -44.0022], [-20.5062, 3.0, -45.2292], [-19.106, 3.0, -43.8635], [-20.6397, 3.0, -45.436], [-18.896, 3.0, -43.7352], [-20.7628, 3.0, -45.6491], [-18.6799, 3.0, -43.6175], [-20.875, 3.0, -45.8681], [-18.4582, 3.0, -43.5108], [-20.9762, 3.0, -46.0924], [-18.2314, 3.0, -43.4152], [-21.0662, 3.0, -46.3215], [-18.0002, 3.0, -43.331], [-17.765, 3.0, -43.2585], [16.4743, 3.0, -9.1399], [-17.5265, 3.0, -43.1977], [22.3077, 3.0, -3.5345], [-17.2853, 3.0, -43.149], [-17.042, 3.0, -43.1123], [-16.7971, 3.0, -43.0878], [-16.5513, 3.0, -43.0755], [-16.3052, 3.0, -43.0755], [-16.0594, 3.0, -43.0878], [-15.8146, 3.0, -43.1123], [-15.5712, 3.0, -43.149], [-15.33, 3.0, -43.1977], [-15.0915, 3.0, -43.2585], [-14.8564, 3.0, -43.331], [-26.7105, 3.0, -55.2632], [-21.2112, 3.0, -49.2274], [-21.1445, 3.0, -49.4643], [-21.2659, 3.0, -48.9875], [-21.3087, 3.0, -48.7451], [-21.3393, 3.0, -48.5009], [-21.3577, 3.0, -48.2555], [-21.3638, 3.0, -48.0095], [-21.3577, 3.0, -47.7635], [-21.3393, 3.0, -47.5181], [-21.3087, 3.0, -47.2739], [-21.2659, 3.0, -47.0316], [-21.2112, 3.0, -46.7916], [-21.1445, 3.0, -46.5547], [-21.0662, 3.0, -49.6976], [-14.6251, 3.0, -43.4152], [-20.9762, 3.0, -49.9266], [-14.3984, 3.0, -43.5108], [-20.875, 3.0, -50.151], [-14.1766, 3.0, -43.6175], [-20.7628, 3.0, -50.37], [-13.9605, 3.0, -43.7352], [-20.6397, 3.0, -50.5831], [-13.7505, 3.0, -43.8635], [-20.5062, 3.0, -50.7898], [-13.5472, 3.0, -44.0022], [-20.3625, 3.0, -50.9896], [-13.351, 3.0, -44.1508], [-20.2091, 3.0, -51.182], [-13.1625, 3.0, -44.309], [-20.0463, 3.0, -51.3665], [-12.9821, 3.0, -44.4763], [-19.8744, 3.0, -51.5427], [-19.694, 3.0, -51.7101], [-19.5055, 3.0, -51.8683], [-19.3094, 3.0, -52.0169], [-19.106, 3.0, -52.1555], [35.0, 3.0, 0.0], [19.0522, 3.0, -18.4636], [-27.6141, 3.0, -65.7466], [-27.6672, 3.0, -65.6993], [-27.7179, 3.0, -65.6494], [-27.5588, 3.0, -65.7913], [-27.3806, 3.0, -65.9082], [-27.4419, 3.0, -65.8722], [-27.5013, 3.0, -65.8332], [-27.2531, 3.0, -65.9711], [-27.3177, 3.0, -65.9412], [-27.1872, 3.0, -65.9978], [-27.0519, 3.0, -66.0413], [-27.1201, 3.0, -66.0212], [-26.9129, 3.0, -66.0713], [-26.9828, 3.0, -66.058], [-26.8425, 3.0, -66.0811], [-26.7007, 3.0, -66.0904], [-26.7717, 3.0, -66.0875], [-26.5586, 3.0, -66.0858], [-26.6296, 3.0, -66.0899], [-26.2792, 3.0, -66.035], [-26.3481, 3.0, -66.0528], [-26.4177, 3.0, -66.0672], [-26.4879, 3.0, -66.0782], [-26.1446, 3.0, -65.9893], [-26.2114, 3.0, -66.0138], [-26.0792, 3.0, -65.9615], [-25.9527, 3.0, -65.8966], [-26.0151, 3.0, -65.9306], [-25.8332, 3.0, -65.8196], [-25.892, 3.0, -65.8595], [-25.7765, 3.0, -65.7767], [-25.6696, 3.0, -65.683], [-25.7219, 3.0, -65.7312], [-28.9439, 3.0, -64.3825], [-29.1102, 3.0, -64.202], [-29.2675, 3.0, -64.0136], [-29.4153, 3.0, -63.8176], [-29.5533, 3.0, -63.6147], [-29.6811, 3.0, -63.4052], [-29.7986, 3.0, -63.1898], [-29.9053, 3.0, -62.9688], [-30.0011, 3.0, -62.7428], [-30.0856, 3.0, -62.5124], [-30.1588, 3.0, -62.2782], [-30.2204, 3.0, -62.0406], [-30.2702, 3.0, -61.8003], [-30.3082, 3.0, -61.5579], [-30.3342, 3.0, -61.3138], [-30.3483, 3.0, -61.0688], [-30.3503, 3.0, -60.8234], [-30.3402, 3.0, -60.5782], [-30.3182, 3.0, -60.3338], [-30.2841, 3.0, -60.0908], [-30.2382, 3.0, -59.8497], [-30.1805, 3.0, -59.6111], [-30.1112, 3.0, -59.3757], [-30.0304, 3.0, -59.144], [-29.9383, 3.0, -58.9165], [-29.8352, 3.0, -58.6938], [-29.7213, 3.0, -58.4764], [-29.5969, 3.0, -58.2649], [-20.6129, 3.0, -60.7895], [-13.9605, 3.0, -52.2838], [-13.7505, 3.0, -52.1555], [-14.1766, 3.0, -52.4015], [-14.3984, 3.0, -52.5083], [-14.6251, 3.0, -52.6039], [-14.8564, 3.0, -52.688], [-15.0915, 3.0, -52.7606], [-15.33, 3.0, -52.8213], [-15.5712, 3.0, -52.8701], [-15.8146, 3.0, -52.9067], [-16.0594, 3.0, -52.9312], [-16.3052, 3.0, -52.9435], [-16.5513, 3.0, -52.9435], [-16.7971, 3.0, -52.9312], [-17.042, 3.0, -52.9067], [-17.2853, 3.0, -52.8701], [-17.5265, 3.0, -52.8213], [-17.765, 3.0, -52.7606], [-18.0002, 3.0, -52.688], [-18.2314, 3.0, -52.6039], [-18.4582, 3.0, -52.5083], [-18.6799, 3.0, -52.4015], [-18.896, 3.0, -52.2838], [-29.4622, 3.0, -58.0598], [-29.3176, 3.0, -57.8615], [-29.1635, 3.0, -57.6705], [-29.0002, 3.0, -57.4873], [-28.828, 3.0, -57.3124], [-2.5595, 3.0, -49.5058], [-11.4989, 3.0, -48.2555], [-11.4928, 3.0, -48.0095], [-11.5173, 3.0, -48.5009], [-11.5479, 3.0, -48.7451], [-11.5906, 3.0, -48.9875], [-11.6454, 3.0, -49.2274], [-11.712, 3.0, -49.4643], [-11.7904, 3.0, -49.6976], [-11.8803, 3.0, -49.9266], [-11.9815, 3.0, -50.151], [-12.8103, 3.0, -44.6525], [-12.0938, 3.0, -50.37], [-12.6474, 3.0, -44.837], [-12.2168, 3.0, -50.5831], [-12.494, 3.0, -45.0294], [-12.3504, 3.0, -50.7898], [-12.3504, 3.0, -45.2292], [-12.494, 3.0, -50.9896], [-12.2168, 3.0, -45.436], [-12.6474, 3.0, -51.182], [-12.0938, 3.0, -45.6491], [-12.8103, 3.0, -51.3665], [-11.9815, 3.0, -45.8681], [-12.9821, 3.0, -51.5427], [-11.8803, 3.0, -46.0924], [-13.1625, 3.0, -51.7101], [-11.7904, 3.0, -46.3215], [-13.351, 3.0, -51.8683], [-11.712, 3.0, -46.5547], [-13.5472, 3.0, -52.0169], [-11.6454, 3.0, -46.7916], [-11.5906, 3.0, -47.0316], [-11.5479, 3.0, -47.2739], [-11.5173, 3.0, -47.5181], [-11.4989, 3.0, -47.7635], [42.0711, 3.0, -7.0711], [2.5595, 3.0, -49.5058], [12.9821, 3.0, -44.4763], [12.8103, 3.0, -44.6525], [13.1625, 3.0, -44.309], [12.6474, 3.0, -44.837], [13.351, 3.0, -44.1508], [12.494, 3.0, -45.0294], [13.5472, 3.0, -44.0022], [12.3504, 3.0, -45.2292], [13.7505, 3.0, -43.8635], [12.2168, 3.0, -45.436], [13.9605, 3.0, -43.7352], [12.0938, 3.0, -45.6491], [14.1766, 3.0, -43.6175], [11.9815, 3.0, -45.8681], [14.3984, 3.0, -43.5108], [11.8803, 3.0, -46.0924], [14.6251, 3.0, -43.4152], [11.7904, 3.0, -46.3215], [14.8564, 3.0, -43.331], [11.712, 3.0, -46.5547], [15.0915, 3.0, -43.2585], [11.6454, 3.0, -46.7916], [15.33, 3.0, -43.1977], [11.5906, 3.0, -47.0316], [15.5712, 3.0, -43.149], [11.5479, 3.0, -47.2739], [15.8146, 3.0, -43.1123], [11.5173, 3.0, -47.5181], [16.0594, 3.0, -43.0878], [11.4989, 3.0, -47.7635], [16.3052, 3.0, -43.0755], [11.4928, 3.0, -48.0095], [16.5513, 3.0, -43.0755], [11.4989, 3.0, -48.2555], [16.7971, 3.0, -43.0878], [11.5173, 3.0, -48.5009], [17.042, 3.0, -43.1123], [11.5479, 3.0, -48.7451], [17.2853, 3.0, -43.149], [11.5906, 3.0, -48.9875], [17.5265, 3.0, -43.1977], [11.6454, 3.0, -49.2274], [17.765, 3.0, -43.2585], [11.712, 3.0, -49.4643], [18.0002, 3.0, -43.331], [11.7904, 3.0, -49.6976], [18.2314, 3.0, -43.4152], [11.8803, 3.0, -49.9266], [18.4582, 3.0, -43.5108], [11.9815, 3.0, -50.151], [18.6799, 3.0, -43.6175], [12.0938, 3.0, -50.37], [18.896, 3.0, -43.7352], [12.2168, 3.0, -50.5831], [19.106, 3.0, -43.8635], [12.3504, 3.0, -50.7898], [19.3094, 3.0, -44.0022], [12.494, 3.0, -50.9896], [19.5055, 3.0, -44.1508], [12.6474, 3.0, -51.182], [19.694, 3.0, -44.309], [12.8103, 3.0, -51.3665], [19.8744, 3.0, -44.4763], [12.9821, 3.0, -51.5427], [20.0463, 3.0, -44.6525], [13.1625, 3.0, -51.7101], [20.2091, 3.0, -44.837], [13.351, 3.0, -51.8683], [20.3625, 3.0, -45.0294], [13.5472, 3.0, -52.0169], [20.5062, 3.0, -45.2292], [13.7505, 3.0, -52.1555], [20.6397, 3.0, -45.436], [20.7628, 3.0, -45.6491], [20.875, 3.0, -45.8681], [20.9762, 3.0, -46.0924], [20.6129, 3.0, -60.7895], [13.9605, 3.0, -52.2838], [14.1766, 3.0, -52.4015], [14.3984, 3.0, -52.5083], [14.6251, 3.0, -52.6039], [14.8564, 3.0, -52.688], [15.0915, 3.0, -52.7606], [15.33, 3.0, -52.8213], [15.5712, 3.0, -52.8701], [15.8146, 3.0, -52.9067], [16.0594, 3.0, -52.9312], [16.3052, 3.0, -52.9435], [16.5513, 3.0, -52.9435], [16.7971, 3.0, -52.9312], [17.042, 3.0, -52.9067], [17.2853, 3.0, -52.8701], [17.5265, 3.0, -52.8213], [17.765, 3.0, -52.7606], [18.0002, 3.0, -52.688], [18.2314, 3.0, -52.6039], [18.4582, 3.0, -52.5083], [18.6799, 3.0, -52.4015], [18.896, 3.0, -52.2838], [19.106, 3.0, -52.1555], [26.7105, 3.0, -55.2632], [21.3638, 3.0, -48.0095], [21.3577, 3.0, -47.7635], [21.3393, 3.0, -47.5181], [21.3087, 3.0, -47.2739], [21.2659, 3.0, -47.0316], [21.2112, 3.0, -46.7916], [21.1445, 3.0, -46.5547], [19.3094, 3.0, -52.0169], [21.0662, 3.0, -46.3215], [19.5055, 3.0, -51.8683], [19.694, 3.0, -51.7101], [19.8744, 3.0, -51.5427], [20.0463, 3.0, -51.3665], [20.2091, 3.0, -51.182], [20.3625, 3.0, -50.9896], [20.5062, 3.0, -50.7898], [20.6397, 3.0, -50.5831], [20.7628, 3.0, -50.37], [20.875, 3.0, -50.151], [20.9762, 3.0, -49.9266], [21.0662, 3.0, -49.6976], [21.1445, 3.0, -49.4643], [21.2112, 3.0, -49.2274], [21.2659, 3.0, -48.9875], [21.3087, 3.0, -48.7451], [21.3393, 3.0, -48.5009], [21.3577, 3.0, -48.2555], [26.7105, 3.0, -66.6904], [32.4211, 3.0, -60.7895], [-0.0, 17.2725, -18.399], [-0.0, 10.6576, -46.7187]], "f": [[0, 1, 2], [2, 1, 3], [4, 5, 6], [6, 5, 7], [8, 9, 10], [11, 12, 13], [13, 12, 5], [8, 14, 9], [1, 14, 8], [12, 15, 5], [14, 16, 17], [18, 19, 15], [19, 20, 15], [5, 20, 7], [15, 20, 5], [16, 21, 22], [14, 21, 16], [1, 21, 14], [23, 24, 25], [25, 24, 20], [21, 26, 27], [27, 26, 28], [1, 26, 21], [0, 26, 1], [0, 29, 26], [0, 30, 29], [31, 32, 33], [33, 32, 34], [34, 32, 35], [36, 37, 32], [24, 38, 20], [32, 38, 35], [37, 38, 32], [35, 38, 24], [20, 39, 7], [38, 39, 20], [7, 39, 0], [0, 40, 30], [39, 40, 0], [30, 40, 41], [39, 42, 43], [43, 44, 39], [39, 45, 42], [44, 46, 39], [39, 47, 45], [46, 48, 39], [39, 49, 47], [39, 50, 40], [48, 50, 39], [39, 51, 49], [50, 52, 40], [39, 53, 51], [52, 54, 40], [39, 55, 53], [54, 56, 40], [39, 57, 55], [56, 58, 40], [39, 59, 57], [58, 60, 40], [60, 61, 40], [61, 62, 40], [41, 63, 30], [62, 64, 40], [64, 65, 40], [65, 66, 40], [66, 67, 40], [67, 68, 40], [68, 69, 40], [69, 70, 40], [70, 71, 40], [71, 72, 40], [40, 72, 73], [72, 74, 73], [74, 75, 73], [76, 77, 78], [79, 77, 76], [80, 77, 79], [81, 77, 80], [82, 77, 81], [83, 77, 82], [84, 77, 83], [85, 77, 84], [86, 77, 85], [87, 77, 86], [88, 77, 87], [89, 77, 88], [59, 77, 89], [39, 77, 59], [77, 90, 78], [75, 91, 73], [77, 92, 90], [91, 93, 73], [77, 94, 92], [93, 95, 73], [77, 96, 94], [95, 97, 73], [77, 98, 96], [97, 99, 73], [77, 100, 98], [99, 101, 73], [77, 102, 100], [101, 103, 73], [77, 104, 102], [103, 105, 73], [77, 106, 104], [77, 107, 106], [77, 108, 107], [77, 109, 108], [77, 110, 109], [77, 111, 110], [63, 112, 30], [73, 113, 114], [115, 116, 117], [116, 118, 117], [119, 120, 121], [121, 120, 118], [122, 123, 120], [118, 123, 117], [120, 123, 118], [123, 124, 117], [125, 126, 124], [124, 126, 117], [127, 128, 126], [128, 129, 126], [129, 130, 126], [131, 130, 129], [130, 132, 126], [133, 134, 135], [135, 134, 132], [132, 136, 126], [134, 136, 132], [137, 138, 136], [138, 139, 136], [139, 140, 136], [140, 141, 136], [142, 143, 141], [141, 143, 136], [143, 144, 136], [136, 145, 126], [144, 145, 136], [146, 147, 145], [145, 147, 126], [117, 147, 148], [148, 147, 149], [149, 147, 150], [150, 147, 151], [151, 147, 152], [152, 147, 153], [153, 147, 154], [154, 147, 155], [155, 147, 156], [156, 147, 157], [157, 147, 158], [158, 147, 159], [159, 147, 160], [160, 147, 161], [161, 147, 162], [162, 147, 163], [163, 147, 164], [164, 147, 165], [165, 147, 166], [166, 147, 167], [167, 147, 168], [168, 147, 169], [169, 147, 170], [170, 147, 171], [171, 147, 172], [172, 147, 173], [173, 147, 174], [174, 147, 175], [126, 147, 117], [176, 177, 178], [179, 177, 176], [180, 177, 179], [181, 177, 180], [182, 177, 181], [183, 177, 182], [184, 177, 183], [185, 177, 184], [186, 177, 185], [187, 177, 186], [188, 177, 187], [189, 177, 188], [190, 177, 189], [191, 177, 190], [192, 177, 191], [193, 177, 192], [194, 177, 193], [195, 177, 194], [77, 177, 111], [196, 177, 195], [197, 177, 196], [198, 177, 197], [199, 177, 198], [200, 177, 199], [111, 177, 200], [175, 177, 201], [201, 177, 202], [202, 177, 203], [203, 177, 204], [204, 177, 205], [205, 177, 77], [147, 177, 175], [113, 206, 63], [63, 206, 112], [207, 208, 105], [209, 210, 207], [211, 210, 209], [207, 210, 208], [105, 212, 73], [208, 212, 105], [211, 213, 210], [212, 214, 73], [215, 216, 211], [211, 216, 213], [73, 217, 113], [214, 217, 73], [218, 219, 215], [220, 219, 218], [215, 219, 216], [217, 221, 113], [222, 223, 220], [220, 223, 219], [221, 224, 113], [222, 225, 223], [224, 226, 113], [227, 228, 222], [222, 228, 225], [226, 229, 113], [230, 231, 227], [227, 231, 228], [229, 232, 113], [230, 233, 231], [234, 233, 230], [235, 233, 234], [232, 236, 113], [235, 237, 233], [236, 238, 113], [239, 240, 235], [235, 240, 237], [238, 241, 113], [242, 243, 239], [239, 243, 240], [241, 244, 113], [245, 246, 242], [242, 246, 243], [244, 247, 113], [248, 249, 245], [245, 249, 246], [247, 250, 113], [248, 251, 249], [252, 251, 248], [250, 253, 113], [252, 254, 251], [255, 254, 252], [253, 256, 113], [257, 258, 255], [259, 258, 257], [255, 258, 254], [256, 260, 113], [259, 261, 258], [262, 261, 259], [260, 263, 113], [262, 264, 261], [263, 265, 113], [262, 266, 264], [267, 266, 262], [265, 268, 113], [267, 269, 266], [270, 269, 267], [268, 271, 113], [272, 273, 270], [274, 273, 272], [270, 273, 269], [271, 275, 113], [274, 276, 273], [275, 277, 113], [278, 279, 274], [274, 279, 276], [277, 280, 113], [281, 282, 278], [278, 282, 279], [280, 283, 113], [284, 285, 281], [281, 285, 282], [283, 286, 113], [287, 288, 284], [289, 288, 287], [284, 288, 285], [286, 290, 113], [289, 291, 288], [290, 292, 113], [293, 294, 289], [289, 294, 291], [292, 295, 113], [296, 297, 293], [298, 297, 296], [293, 297, 294], [295, 299, 113], [298, 300, 297], [301, 300, 298], [299, 302, 113], [303, 304, 301], [301, 304, 300], [302, 305, 113], [303, 306, 304], [305, 307, 113], [303, 308, 306], [309, 308, 303], [178, 308, 309], [113, 310, 206], [307, 310, 113], [178, 311, 308], [310, 312, 206], [312, 313, 206], [313, 314, 206], [314, 315, 206], [315, 316, 206], [177, 317, 178], [318, 317, 319], [320, 317, 318], [321, 317, 320], [322, 317, 321], [323, 317, 322], [324, 317, 323], [325, 317, 324], [326, 317, 325], [327, 317, 326], [178, 317, 311], [328, 317, 327], [329, 317, 328], [330, 317, 329], [331, 317, 330], [332, 317, 331], [333, 317, 332], [334, 317, 333], [335, 317, 334], [336, 317, 335], [337, 317, 336], [338, 317, 337], [339, 317, 338], [340, 317, 339], [341, 317, 340], [311, 317, 341], [316, 342, 206], [343, 342, 344], [345, 342, 343], [346, 342, 345], [347, 342, 346], [348, 342, 347], [349, 342, 348], [350, 342, 349], [351, 342, 350], [352, 342, 351], [353, 342, 352], [354, 342, 353], [355, 342, 354], [356, 342, 355], [357, 342, 356], [317, 342, 319], [358, 342, 357], [359, 342, 358], [360, 342, 359], [361, 342, 316], [362, 342, 360], [363, 342, 361], [364, 342, 362], [365, 342, 363], [319, 342, 364], [366, 342, 365], [367, 342, 366], [368, 342, 367], [369, 342, 368], [344, 342, 369], [342, 370, 371], [317, 370, 342], [372, 373, 374], [372, 374, 375], [376, 377, 378], [376, 379, 377], [380, 381, 382], [380, 382, 383], [380, 383, 372], [384, 385, 386], [387, 380, 372], [387, 388, 389], [387, 389, 380], [387, 372, 375], [390, 379, 376], [390, 376, 385], [390, 384, 391], [390, 385, 384], [390, 392, 393], [390, 393, 379], [394, 395, 396], [394, 396, 387], [397, 390, 398], [397, 398, 399], [397, 392, 390], [397, 400, 392], [401, 387, 375], [401, 394, 387], [402, 400, 397], [403, 400, 402], [404, 405, 406], [404, 406, 407], [404, 407, 401], [408, 401, 375], [408, 404, 401], [408, 409, 410], [408, 410, 404], [411, 408, 375], [411, 375, 400], [412, 411, 400], [412, 400, 403], [412, 403, 413], [414, 415, 416], [417, 411, 418], [419, 418, 411], [420, 411, 417], [421, 419, 411], [422, 411, 420], [423, 421, 411], [424, 411, 422], [425, 411, 412], [425, 423, 411], [426, 411, 424], [427, 425, 412], [428, 411, 426], [429, 427, 412], [430, 411, 428], [431, 429, 412], [432, 411, 430], [433, 431, 412], [434, 411, 432], [435, 433, 412], [436, 435, 412], [437, 414, 416], [438, 436, 412], [439, 413, 403], [440, 438, 412], [441, 440, 412], [442, 441, 412], [443, 442, 412], [443, 412, 415], [444, 443, 415], [445, 444, 415], [446, 445, 415], [447, 446, 415], [448, 447, 415], [449, 448, 415], [450, 449, 415], [451, 411, 434], [451, 452, 453], [451, 454, 452], [451, 455, 454], [451, 456, 455], [451, 457, 456], [451, 458, 457], [451, 459, 458], [451, 460, 459], [451, 461, 460], [451, 462, 461], [451, 463, 462], [451, 464, 463], [451, 434, 464], [465, 451, 453], [466, 450, 415], [467, 451, 465], [468, 466, 415], [469, 451, 467], [470, 468, 415], [471, 451, 469], [472, 470, 415], [473, 451, 471], [474, 472, 415], [475, 451, 473], [476, 474, 415], [477, 451, 475], [478, 476, 415], [479, 451, 477], [480, 478, 415], [481, 451, 479], [482, 480, 415], [483, 451, 481], [484, 451, 483], [485, 451, 484], [486, 451, 485], [487, 451, 486], [488, 439, 403], [489, 414, 437], [490, 491, 492], [493, 490, 492], [494, 495, 496], [494, 496, 493], [497, 493, 492], [497, 498, 494], [497, 494, 493], [499, 497, 492], [500, 501, 499], [500, 499, 492], [502, 503, 500], [504, 502, 500], [505, 506, 504], [505, 504, 500], [507, 508, 505], [509, 510, 511], [509, 511, 512], [509, 512, 507], [513, 514, 509], [515, 507, 505], [515, 509, 507], [515, 513, 509], [516, 517, 515], [516, 515, 505], [518, 519, 516], [518, 516, 505], [520, 505, 500], [520, 518, 505], [521, 522, 520], [521, 500, 492], [521, 492, 523], [521, 520, 500], [521, 523, 524], [521, 524, 525], [521, 525, 526], [521, 526, 527], [521, 527, 528], [521, 528, 529], [521, 529, 530], [521, 530, 531], [521, 531, 532], [521, 532, 533], [521, 533, 534], [521, 534, 535], [521, 535, 536], [521, 536, 537], [521, 537, 538], [521, 538, 539], [521, 539, 540], [521, 540, 541], [521, 541, 542], [521, 542, 543], [521, 543, 544], [521, 544, 545], [521, 545, 546], [521, 546, 547], [521, 547, 548], [521, 548, 549], [521, 549, 550], [551, 552, 553], [551, 554, 552], [551, 555, 554], [551, 556, 555], [551, 557, 556], [551, 558, 557], [551, 559, 558], [551, 560, 559], [551, 561, 560], [551, 562, 561], [551, 563, 562], [551, 564, 563], [551, 451, 487], [551, 565, 564], [551, 566, 565], [551, 567, 566], [551, 568, 567], [551, 569, 568], [551, 570, 569], [551, 571, 570], [551, 572, 571], [551, 573, 572], [551, 574, 573], [551, 487, 574], [551, 550, 575], [551, 575, 576], [551, 576, 577], [551, 577, 578], [551, 578, 579], [551, 579, 451], [551, 521, 550], [580, 482, 415], [580, 581, 582], [580, 583, 581], [580, 584, 583], [580, 585, 584], [580, 586, 585], [580, 587, 586], [580, 588, 587], [580, 589, 588], [580, 590, 589], [580, 591, 482], [580, 592, 590], [580, 593, 591], [580, 594, 592], [580, 595, 593], [580, 596, 594], [580, 597, 595], [580, 598, 596], [580, 599, 597], [580, 600, 598], [580, 601, 599], [580, 602, 600], [580, 603, 601], [580, 604, 602], [580, 605, 603], [580, 606, 604], [580, 607, 605], [580, 608, 606], [580, 609, 607], [580, 610, 608], [580, 611, 609], [580, 553, 610], [580, 612, 611], [580, 613, 612], [580, 614, 613], [580, 615, 614], [580, 582, 615], [580, 551, 553], [616, 439, 488], [616, 489, 439], [617, 551, 580], [618, 617, 414], [619, 617, 618], [620, 618, 414], [621, 617, 619], [622, 620, 414], [623, 617, 621], [624, 622, 414], [625, 617, 623], [626, 624, 414], [627, 617, 625], [628, 626, 414], [629, 617, 627], [630, 414, 489], [630, 628, 414], [631, 617, 629], [632, 630, 489], [633, 617, 631], [634, 632, 489], [635, 617, 633], [636, 634, 489], [637, 617, 635], [638, 636, 489], [639, 617, 637], [640, 638, 489], [641, 617, 639], [642, 640, 489], [643, 617, 641], [644, 642, 489], [645, 617, 643], [646, 644, 489], [647, 617, 645], [648, 646, 489], [649, 617, 647], [650, 648, 489], [651, 617, 649], [652, 650, 489], [653, 617, 651], [654, 652, 489], [655, 617, 653], [656, 654, 489], [657, 617, 655], [658, 656, 489], [659, 617, 657], [660, 658, 489], [661, 617, 659], [662, 660, 489], [663, 617, 661], [664, 662, 489], [665, 617, 663], [666, 664, 489], [667, 617, 665], [668, 666, 489], [669, 617, 667], [670, 668, 489], [671, 617, 669], [672, 670, 489], [673, 617, 671], [674, 672, 489], [675, 617, 673], [676, 674, 489], [677, 617, 675], [678, 676, 489], [679, 617, 677], [680, 678, 489], [681, 617, 679], [682, 680, 489], [683, 617, 681], [684, 682, 489], [685, 617, 683], [686, 489, 616], [686, 684, 489], [687, 617, 685], [688, 686, 616], [689, 617, 687], [690, 688, 616], [691, 690, 616], [692, 691, 616], [693, 692, 616], [694, 617, 689], [694, 689, 695], [694, 695, 696], [694, 696, 697], [694, 697, 698], [694, 698, 699], [694, 699, 700], [694, 700, 701], [694, 701, 702], [694, 702, 703], [694, 703, 704], [694, 704, 705], [694, 705, 706], [694, 706, 707], [694, 707, 708], [694, 708, 709], [694, 709, 710], [694, 710, 711], [694, 711, 712], [694, 712, 713], [694, 713, 714], [694, 714, 715], [694, 715, 716], [694, 716, 717], [694, 551, 617], [718, 693, 616], [718, 694, 717], [718, 719, 720], [718, 720, 721], [718, 721, 722], [718, 722, 723], [718, 723, 724], [718, 724, 725], [718, 717, 726], [718, 725, 727], [718, 726, 728], [718, 727, 693], [718, 728, 729], [718, 729, 730], [718, 730, 731], [718, 731, 732], [718, 732, 733], [718, 733, 734], [718, 734, 735], [718, 735, 736], [718, 736, 737], [718, 737, 738], [718, 738, 739], [718, 739, 740], [718, 740, 741], [718, 741, 742], [718, 742, 743], [718, 743, 744], [718, 744, 745], [718, 745, 719], [746, 718, 747], [746, 694, 718], [412, 416, 415], [718, 616, 342], [342, 616, 206], [747, 718, 371], [371, 718, 342], [746, 747, 370], [370, 747, 371], [694, 746, 317], [317, 746, 370], [694, 317, 551], [551, 317, 177], [147, 551, 177], [521, 551, 147], [115, 117, 492], [115, 492, 491], [116, 491, 490], [116, 115, 491], [118, 490, 493], [118, 116, 490], [121, 493, 496], [121, 118, 493], [119, 496, 495], [119, 121, 496], [120, 495, 494], [120, 119, 495], [122, 494, 498], [122, 120, 494], [123, 122, 498], [123, 498, 497], [124, 123, 497], [124, 499, 501], [124, 497, 499], [125, 124, 501], [126, 125, 501], [126, 501, 500], [127, 126, 500], [127, 503, 502], [127, 500, 503], [128, 127, 502], [129, 128, 502], [129, 502, 504], [131, 129, 504], [131, 504, 506], [130, 131, 506], [130, 506, 505], [132, 130, 505], [132, 505, 508], [135, 132, 508], [135, 507, 512], [135, 508, 507], [133, 512, 511], [133, 135, 512], [134, 133, 511], [136, 510, 509], [136, 511, 510], [136, 134, 511], [137, 509, 514], [137, 136, 509], [138, 137, 514], [139, 514, 513], [139, 138, 514], [140, 513, 515], [140, 139, 513], [141, 515, 517], [141, 140, 515], [142, 516, 519], [142, 517, 516], [142, 141, 517], [143, 142, 519], [144, 519, 518], [144, 143, 519], [145, 518, 520], [145, 144, 518], [146, 520, 522], [146, 145, 520], [147, 522, 521], [147, 146, 522], [492, 117, 148], [523, 492, 148], [204, 205, 579], [204, 579, 578], [203, 578, 577], [203, 204, 578], [202, 577, 576], [202, 203, 577], [201, 576, 575], [201, 202, 576], [175, 575, 550], [175, 201, 575], [174, 550, 549], [174, 175, 550], [173, 549, 548], [173, 174, 549], [172, 173, 548], [172, 548, 547], [171, 172, 547], [171, 547, 546], [170, 171, 546], [170, 546, 545], [169, 170, 545], [169, 545, 544], [168, 169, 544], [168, 544, 543], [167, 168, 543], [167, 542, 541], [167, 543, 542], [166, 167, 541], [165, 166, 541], [165, 541, 540], [164, 165, 540], [164, 540, 539], [163, 164, 539], [163, 539, 538], [162, 163, 538], [162, 538, 537], [161, 537, 536], [161, 162, 537], [160, 536, 535], [160, 161, 536], [159, 160, 535], [159, 535, 534], [158, 533, 532], [158, 534, 533], [158, 159, 534], [157, 158, 532], [156, 532, 531], [156, 157, 532], [155, 531, 530], [155, 156, 531], [154, 529, 528], [154, 530, 529], [154, 155, 530], [153, 154, 528], [152, 528, 527], [152, 153, 528], [151, 527, 526], [151, 152, 527], [150, 526, 525], [150, 151, 526], [149, 525, 524], [149, 150, 525], [148, 524, 523], [148, 149, 524], [451, 205, 77], [451, 579, 205], [411, 77, 39], [411, 451, 77], [6, 7, 375], [6, 375, 374], [4, 374, 373], [4, 6, 374], [5, 372, 383], [5, 373, 372], [5, 4, 373], [13, 5, 383], [11, 383, 382], [11, 13, 383], [12, 382, 381], [12, 11, 382], [15, 381, 380], [15, 12, 381], [18, 389, 388], [18, 380, 389], [18, 15, 380], [19, 18, 388], [20, 388, 387], [20, 19, 388], [25, 387, 396], [25, 20, 387], [23, 396, 395], [23, 25, 396], [24, 23, 395], [24, 394, 401], [24, 395, 394], [35, 24, 401], [34, 35, 401], [34, 401, 407], [33, 34, 407], [33, 407, 406], [31, 33, 406], [31, 405, 404], [31, 406, 405], [32, 31, 404], [36, 404, 410], [36, 32, 404], [37, 36, 410], [37, 410, 409], [38, 409, 408], [38, 37, 409], [39, 408, 411], [39, 38, 408], [400, 7, 0], [400, 375, 7], [29, 30, 403], [29, 403, 402], [26, 402, 397], [26, 29, 402], [28, 397, 399], [28, 26, 397], [27, 398, 390], [27, 399, 398], [27, 28, 399], [21, 27, 390], [22, 390, 391], [22, 21, 390], [16, 391, 384], [16, 22, 391], [17, 384, 386], [17, 16, 384], [14, 386, 385], [14, 17, 386], [9, 385, 376], [9, 14, 385], [10, 376, 378], [10, 9, 376], [8, 377, 379], [8, 378, 377], [8, 10, 378], [1, 8, 379], [3, 379, 393], [3, 1, 379], [2, 393, 392], [2, 3, 393], [0, 392, 400], [0, 2, 392], [112, 488, 403], [112, 403, 30], [616, 112, 206], [616, 488, 112], [748, 415, 414], [414, 617, 749], [748, 414, 749], [749, 617, 580], [749, 415, 748], [749, 580, 415], [582, 581, 255], [255, 581, 257], [257, 583, 259], [259, 583, 262], [581, 583, 257], [583, 584, 262], [262, 585, 267], [267, 585, 270], [584, 585, 262], [270, 586, 272], [585, 586, 270], [586, 587, 272], [272, 588, 274], [587, 588, 272], [274, 589, 278], [278, 589, 281], [588, 589, 274], [589, 590, 281], [281, 592, 284], [590, 592, 281], [592, 594, 284], [284, 594, 287], [594, 596, 287], [287, 596, 289], [596, 598, 289], [289, 598, 293], [598, 600, 293], [293, 600, 296], [600, 602, 296], [296, 602, 298], [602, 604, 298], [298, 604, 301], [604, 606, 301], [301, 606, 303], [606, 608, 303], [303, 608, 309], [608, 610, 309], [309, 610, 178], [610, 553, 178], [178, 553, 176], [176, 553, 179], [553, 552, 179], [552, 554, 179], [179, 554, 180], [180, 554, 181], [554, 555, 181], [181, 556, 182], [182, 556, 183], [555, 556, 181], [556, 557, 183], [183, 558, 184], [557, 558, 183], [184, 559, 185], [185, 559, 186], [558, 559, 184], [186, 560, 187], [559, 560, 186], [560, 561, 187], [187, 562, 188], [188, 562, 189], [561, 562, 187], [562, 563, 189], [189, 564, 190], [190, 564, 191], [563, 564, 189], [564, 565, 191], [191, 566, 192], [192, 566, 193], [565, 566, 191], [566, 567, 193], [567, 568, 193], [193, 568, 194], [568, 569, 194], [194, 569, 195], [569, 570, 195], [195, 570, 196], [570, 571, 196], [196, 571, 197], [571, 572, 197], [197, 572, 198], [198, 572, 199], [572, 573, 199], [573, 574, 199], [199, 574, 200], [574, 487, 200], [200, 487, 111], [487, 486, 111], [111, 486, 110], [486, 485, 110], [110, 485, 109], [485, 484, 109], [109, 484, 108], [484, 483, 108], [108, 483, 107], [483, 481, 107], [107, 481, 106], [481, 479, 106], [106, 479, 104], [479, 477, 104], [104, 477, 102], [477, 475, 102], [102, 475, 100], [475, 473, 100], [100, 473, 98], [473, 471, 98], [98, 471, 96], [471, 469, 96], [96, 469, 94], [469, 467, 94], [94, 467, 92], [467, 465, 92], [92, 465, 90], [465, 453, 90], [90, 453, 78], [78, 452, 76], [453, 452, 78], [76, 454, 79], [452, 454, 76], [79, 455, 80], [454, 455, 79], [80, 456, 81], [455, 456, 80], [456, 457, 81], [81, 457, 82], [457, 458, 82], [82, 458, 83], [458, 459, 83], [83, 459, 84], [459, 460, 84], [84, 460, 85], [460, 461, 85], [85, 461, 86], [461, 462, 86], [86, 462, 87], [462, 463, 87], [87, 463, 88], [463, 464, 88], [88, 464, 89], [464, 434, 89], [89, 434, 59], [434, 432, 59], [59, 432, 57], [432, 430, 57], [57, 430, 55], [430, 428, 55], [55, 428, 53], [428, 426, 53], [53, 426, 51], [51, 424, 49], [426, 424, 51], [49, 422, 47], [424, 422, 49], [47, 420, 45], [422, 420, 47], [45, 417, 42], [420, 417, 45], [42, 418, 43], [417, 418, 42], [43, 419, 44], [418, 419, 43], [44, 421, 46], [419, 421, 44], [46, 423, 48], [421, 423, 46], [48, 425, 50], [423, 425, 48], [50, 427, 52], [425, 427, 50], [52, 429, 54], [427, 429, 52], [54, 431, 56], [429, 431, 54], [56, 433, 58], [431, 433, 56], [433, 435, 58], [58, 435, 60], [435, 436, 60], [60, 436, 61], [436, 438, 61], [61, 438, 62], [438, 440, 62], [62, 440, 64], [64, 441, 65], [440, 441, 64], [65, 442, 66], [66, 442, 67], [441, 442, 65], [442, 443, 67], [67, 444, 68], [443, 444, 67], [68, 445, 69], [444, 445, 68], [69, 446, 70], [445, 446, 69], [70, 447, 71], [446, 447, 70], [71, 448, 72], [72, 448, 74], [447, 448, 71], [448, 449, 74], [74, 450, 75], [449, 450, 74], [75, 466, 91], [450, 466, 75], [91, 468, 93], [93, 468, 95], [466, 468, 91], [468, 470, 95], [95, 472, 97], [470, 472, 95], [97, 474, 99], [472, 474, 97], [99, 476, 101], [101, 476, 103], [474, 476, 99], [476, 478, 103], [103, 480, 105], [105, 480, 207], [478, 480, 103], [480, 482, 207], [207, 591, 209], [482, 591, 207], [591, 593, 209], [209, 593, 211], [593, 595, 211], [211, 595, 215], [595, 597, 215], [215, 597, 218], [597, 599, 218], [218, 599, 220], [599, 601, 220], [220, 601, 222], [601, 603, 222], [222, 603, 227], [603, 605, 227], [227, 605, 230], [605, 607, 230], [230, 607, 234], [607, 609, 234], [234, 609, 235], [609, 611, 235], [235, 611, 239], [611, 612, 239], [239, 612, 242], [612, 613, 242], [242, 613, 245], [245, 614, 248], [613, 614, 245], [614, 615, 248], [248, 615, 252], [252, 582, 255], [615, 582, 252], [719, 745, 344], [344, 745, 343], [343, 744, 345], [745, 744, 343], [345, 743, 346], [744, 743, 345], [346, 742, 347], [743, 742, 346], [347, 741, 348], [742, 741, 347], [348, 740, 349], [741, 740, 348], [349, 739, 350], [740, 739, 349], [350, 738, 351], [739, 738, 350], [351, 737, 352], [738, 737, 351], [352, 736, 353], [737, 736, 352], [736, 735, 353], [353, 735, 354], [735, 734, 354], [354, 734, 355], [734, 733, 355], [355, 733, 356], [733, 732, 356], [356, 732, 357], [732, 731, 357], [357, 731, 358], [731, 730, 358], [358, 730, 359], [730, 729, 359], [359, 729, 360], [729, 728, 360], [360, 728, 362], [728, 726, 362], [362, 726, 364], [726, 717, 364], [364, 717, 319], [717, 716, 319], [319, 716, 318], [716, 715, 318], [318, 715, 320], [320, 714, 321], [715, 714, 320], [321, 713, 322], [714, 713, 321], [322, 712, 323], [713, 712, 322], [323, 711, 324], [712, 711, 323], [324, 710, 325], [711, 710, 324], [325, 709, 326], [710, 709, 325], [326, 708, 327], [709, 708, 326], [327, 707, 328], [708, 707, 327], [328, 706, 329], [707, 706, 328], [329, 705, 330], [706, 705, 329], [330, 704, 331], [705, 704, 330], [331, 703, 332], [704, 703, 331], [332, 702, 333], [703, 702, 332], [333, 701, 334], [702, 701, 333], [701, 700, 334], [334, 700, 335], [700, 699, 335], [335, 699, 336], [699, 698, 336], [336, 698, 337], [698, 697, 337], [337, 697, 338], [697, 696, 338], [338, 696, 339], [696, 695, 339], [339, 695, 340], [695, 689, 340], [340, 689, 341], [689, 687, 341], [341, 687, 311], [687, 685, 311], [311, 685, 308], [685, 683, 308], [308, 683, 306], [683, 681, 306], [306, 681, 304], [681, 679, 304], [304, 679, 300], [679, 677, 300], [300, 677, 297], [677, 675, 297], [297, 675, 294], [675, 673, 294], [294, 673, 291], [673, 671, 291], [291, 671, 288], [671, 669, 288], [288, 669, 285], [669, 667, 285], [285, 667, 282], [667, 665, 282], [282, 665, 279], [665, 663, 279], [279, 663, 276], [663, 661, 276], [276, 661, 273], [273, 659, 269], [661, 659, 273], [269, 657, 266], [659, 657, 269], [266, 655, 264], [657, 655, 266], [264, 653, 261], [655, 653, 264], [653, 651, 261], [261, 651, 258], [651, 649, 258], [258, 649, 254], [649, 647, 254], [254, 647, 251], [647, 645, 251], [251, 645, 249], [645, 643, 249], [249, 643, 246], [643, 641, 246], [246, 641, 243], [641, 639, 243], [243, 639, 240], [639, 637, 240], [240, 637, 237], [637, 635, 237], [237, 635, 233], [635, 633, 233], [233, 633, 231], [633, 631, 231], [231, 631, 228], [631, 629, 228], [228, 629, 225], [223, 627, 219], [629, 627, 225], [225, 627, 223], [627, 625, 219], [219, 623, 216], [625, 623, 219], [216, 621, 213], [623, 621, 216], [213, 619, 210], [210, 619, 208], [621, 619, 213], [619, 618, 208], [208, 620, 212], [618, 620, 208], [212, 622, 214], [214, 622, 217], [620, 622, 212], [622, 624, 217], [217, 626, 221], [624, 626, 217], [221, 628, 224], [626, 628, 221], [224, 630, 226], [628, 630, 224], [226, 632, 229], [229, 632, 232], [630, 632, 226], [632, 634, 232], [634, 636, 232], [232, 636, 236], [236, 636, 238], [636, 638, 238], [238, 638, 241], [638, 640, 241], [241, 642, 244], [640, 642, 241], [244, 644, 247], [642, 644, 244], [247, 646, 250], [644, 646, 247], [250, 648, 253], [646, 648, 250], [253, 650, 256], [648, 650, 253], [256, 652, 260], [260, 652, 263], [650, 652, 256], [652, 654, 263], [263, 656, 265], [654, 656, 263], [265, 658, 268], [268, 658, 271], [656, 658, 265], [658, 660, 271], [271, 662, 275], [660, 662, 271], [275, 664, 277], [662, 664, 275], [277, 666, 280], [664, 666, 277], [280, 668, 283], [666, 668, 280], [283, 670, 286], [668, 670, 283], [286, 672, 290], [670, 672, 286], [290, 674, 292], [672, 674, 290], [292, 676, 295], [674, 676, 292], [295, 678, 299], [299, 678, 302], [676, 678, 295], [678, 680, 302], [302, 682, 305], [305, 682, 307], [680, 682, 302], [682, 684, 307], [684, 686, 307], [307, 686, 310], [686, 688, 310], [310, 688, 312], [688, 690, 312], [312, 690, 313], [690, 691, 313], [313, 691, 314], [691, 692, 314], [314, 692, 315], [315, 692, 316], [692, 693, 316], [693, 727, 316], [316, 727, 361], [727, 725, 361], [361, 725, 363], [725, 724, 363], [363, 724, 365], [724, 723, 365], [365, 723, 366], [723, 722, 366], [366, 722, 367], [722, 721, 367], [367, 721, 368], [368, 720, 369], [721, 720, 368], [369, 719, 344], [720, 719, 369], [437, 73, 114], [437, 416, 73], [40, 73, 412], [73, 416, 412], [40, 412, 41], [41, 412, 413], [439, 63, 413], [413, 63, 41], [63, 489, 113], [439, 489, 63], [113, 437, 114], [489, 437, 113]]} \ No newline at end of file diff --git a/docs/CAD/design/mate-connectors/circularmateconnectors.png b/docs/CAD/design/mate-connectors/circularmateconnectors.png deleted file mode 100644 index 3150a5667b67a24c6182218f6c04f0625bb31cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13095 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 - - - - -Mate connector glyph — polarity and verse - - - -

- -
-
Orca Design · assembly
-

Mate connector glyph — polarity and verse

-

- Onshape's core (disc + roll quadrant + Z arrow) is adopted unchanged because it is proven and - aligned. The addition is polarity — which connector is anchored and - which one travels — which no surveyed CAD system encodes in its glyph. -

-
- -

The three jobs of the glyph

-
-
-
- - - - -
-
Disc — the XY plane
-

Says “I am a frame, and this is the plane I sit in.” The dot is the exact origin.

-
- -
-
- - - - - -
-
Quadrant — the roll
-

- The filled sector is the +X/+Y quadrant. It steps 90° with the reorient control, so the - clocking that Fastened and Slider lock is visible before you commit. -

-
- -
-
- - - - - - - -
-
Arrow — the verse
-

- Drawn on +Z only. Nothing below the disc. A double-headed axis is what - makes people ask which way it points; a one-sided arrow cannot be misread. -

-
-
- -

Polarity — the part nobody else draws

-
-
-
- - - - - - - - -
-
Fixed — the socket
-

- Hollow head, muted colour. This body does not move. It receives. -

-
- -
-
- - - - - - - -
-
Driven — the plug
-

- Solid head, active colour. This body is the one that jumps. It inserts. -

-
- -
-
- - - - - - - - - - - - -
-
Roll undefined
-

- Hatched quadrant, dashed disc: a circular face or a seam gave no usable direction. Says - “pick a direction” without a dialog. -

-
-
- -

The pair reads as a magnet

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - fixed · receives - driven · inserts - -
-

- Two arrows nose to nose. Because a magnet's north seeks a south, “facing” is the - self-evident default — which settles open decision D1 on visual grounds instead of a - convention nobody can look up. Nothing has to be remembered: the picture is the rule. - The dashed line is what makes the two glyphs read as one object. -

-
- -

States

-
- - - - - - - - - - - - - - - - - - - - - -
StateDrawingWhy
Candidate (hover)small dot onlyOnshape draws plain white dots at every corner and midpoint. Dots propose; the full glyph commits.
Pickeddisc + quadrant + coneThe committed frame, with roll and verse both readable.
Roll undefinedhatched quadrant, dashed discTurns requirement R8 from a message nobody reads into a mark you cannot miss.
-
- -

Constraints on the drawing

-
-

- Do not make it a fourth RGB triad. The bed-centre world triad - (DesignCanvas.cpp:65) and the move gizmo are already three coloured arrows. The disc - and the quadrant are what tell a connector apart from those — keep the arms short, and consider - drawing only Z on the committed glyph, with X and Y implied by the quadrant. -

-
- roll quadrant - Z / driven - fixed - roll undefined -
-
- -
- - diff --git a/docs/CAD/design/mate-connectors/coplanar_test.py b/docs/CAD/design/mate-connectors/coplanar_test.py deleted file mode 100644 index ea02ed3c80..0000000000 --- a/docs/CAD/design/mate-connectors/coplanar_test.py +++ /dev/null @@ -1,68 +0,0 @@ -# Does the connector pair let two hosts sit COPLANAR, or does it hold them apart? -# -# The male's flat back is the plane Y=0 and all its relief rises to +Y. So Y=0 is the natural -# mating datum: everything the male adds lives on one side of it. The test below builds two dummy -# host plates that meet on that plane -- one with the male FUSED on, one with the cavity CUT in -- -# and measures whether they touch, interfere, or stand apart. -# -# It also emits the artifact that makes this work in practice: a CUTTER solid (the male grown by -# the clearance) that you subtract from any host. A standalone female block cannot keep two hosts -# coplanar, because its own floor material stands between them; a cavity can. -# -# Run: /snap/bin/freecad.cmd coplanar_test.py - -import os -import FreeCAD as App -import Part -from FreeCAD import Vector - -HERE = os.path.dirname(os.path.abspath(__file__)) -MALE = os.path.join(HERE, "bear.step") -CLEAR = 0.20 - -male = Part.Shape(); male.read(MALE); male = male.Solids[0] -bb = male.BoundBox -print(f"male relief: Y {bb.YMin:.3f} .. {bb.YMax:.3f} -> datum plane Y=0, all relief on +Y") - -# the flat back face, and proof it is the whole silhouette sitting on Y=0 -back = max((f for f in male.Faces - if abs(f.CenterOfMass.y) < 1e-6 and abs(abs(f.normalAt(0, 0).y) - 1) < 1e-6), - key=lambda f: f.Area) -print(f"back face : {back.Area:.1f} mm2 on Y=0 -- this is the contact surface") - -# ---- the cutter: the male grown by the clearance, poking 0.2 mm proud so the boolean is clean -cutter = male.makeOffsetShape(CLEAR, 1e-6, False, False, 0, 2, False).Solids[0] -cb = cutter.BoundBox -print(f"cutter : Y {cb.YMin:.3f} .. {cb.YMax:.3f}, {cutter.Volume/1000:.2f} cm3") - -# ---- two dummy hosts meeting on Y = 0 -W, H = 120.0, 100.0 -hostA = Part.makeBox(W, 10.0, H, Vector(-W/2, -10.0, -15.0)) # occupies Y -10..0 -hostB = Part.makeBox(W, 30.0, H, Vector(-W/2, 0.0, -15.0)) # occupies Y 0..30 - -partA = hostA.fuse(male) # male stands proud of A's face -partB = hostB.cut(cutter) # cavity sunk into B from its face - -print(f"\npart A (host + male) : {partA.Volume/1000:.2f} cm3") -print(f"part B (host - cutter) : {partB.Volume/1000:.2f} cm3") - -# ---- the question ------------------------------------------------------------------ -inter = partA.common(partB) -iv = inter.Volume if inter.Solids else 0.0 -gap = partA.distToShape(partB)[0] -print(f"\nRESULT interference A vs B : {iv:.6f} mm3 (0 = they do not collide)") -print(f"RESULT closest approach : {gap:.4f} mm (0 = the host faces are touching)") - -# are the two host faces actually on the same plane? -fa = [f for f in partA.Faces if abs(f.CenterOfMass.y) < 1e-9 and abs(abs(f.normalAt(0,0).y)-1) < 1e-6] -fb = [f for f in partB.Faces if abs(f.CenterOfMass.y) < 1e-9 and abs(abs(f.normalAt(0,0).y)-1) < 1e-6] -print(f"RESULT A has {len(fa)} face(s) lying exactly on Y=0, total {sum(f.Area for f in fa):.1f} mm2") -print(f"RESULT B has {len(fb)} face(s) lying exactly on Y=0, total {sum(f.Area for f in fb):.1f} mm2") -print("RESULT -> the hosts meet on Y=0: COPLANAR" if fa and fb and iv < 1e-3 - else "RESULT -> NOT coplanar") - -doc = App.newDocument("Cutter") -o = doc.addObject("Part::Feature", "BearConnector_Cutter"); o.Shape = cutter -doc.recompute() -Part.export([o], os.path.join(HERE, "BearConnector_Cutter.step")) -print(f"\nwrote BearConnector_Cutter.step -- subtract this from any host to get the socket") diff --git a/docs/CAD/design/mate-connectors/cylindricalmateconnectors.png b/docs/CAD/design/mate-connectors/cylindricalmateconnectors.png deleted file mode 100644 index e4c1cebcf9af5b7a037323cb17c05a94e81dd593..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17385 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 diff --git a/docs/CAD/design/mate-connectors/faceted_ridge_key.scad b/docs/CAD/design/mate-connectors/faceted_ridge_key.scad deleted file mode 100644 index 3eb60064fb..0000000000 --- a/docs/CAD/design/mate-connectors/faceted_ridge_key.scad +++ /dev/null @@ -1,140 +0,0 @@ -// Faceted ridge key — asymmetric male/female alignment feature, flat facets only. -// -// 6 vertices, 7 faces, one closed manifold. Euler check: V - E + F = 6 - 11 + 7 = 2. -// No spheres, no cylinders, no splines, no fillets. -// -// THE FLANKS ARE TRIANGULATED EXPLICITLY, and that is not cosmetic. Written as quads -// [0,3,5,4] and [1,4,5,2] they are NOT planar — the base edge and the ridge edge are -// skew, so the four corners do not share a plane. A checker caught this after the first -// draft claimed the opposite. Left as quads, the tessellator picks the fold direction for -// you, which means the "flat facet" promise is broken by an unspecified crease and two -// exporters can disagree about the shape. Splitting them here fixes the crease at -// back-bottom -> front-ridge, which keeps the rear peak's triangle large and clean. -// -// FRAME CONVENTION (matches the CAD mate connector it is derived from): -// +Z the mating axis — the feature protrudes along it -// +X the roll reference — the ridge runs along it, low end forward -// +Y completes the right-handed frame -// -// WHAT BREAKS WHICH SYMMETRY -// rotational about Z ....... the ridge (elongation along X) -// 180 deg about Z .......... the ridge SLOPE: tall steep back, long shallow front -// mirror across XZ ......... deliberately NOT broken. Handedness is fixed by convention, -// so +Y is implied once Z and X are known. Breaking it would -// add a facet and buy nothing. -// -// KNOWN AMBIGUITY, stated rather than hidden: viewed exactly ALONG the ridge (+/-X, -// orthographic), the silhouette is the same isoceles triangle from front and back. Front -// and back are then distinguished by SHADING only — the long shallow front face catches -// light differently from the steep back face. If the target renderer is flat-shaded with a -// single headlight, verify this case before committing to the shape. - -// ---------------------------------------------------------------- parameters -L = 12.0; // overall length along the ridge (X) -W = 4.0; // half-width at the BACK -tf = 0.45; // front taper: front half-width = W * tf -H = 4.5; // peak height at the rear <-- the single dimension controlling asymmetry -pr = 0.22; // rear ridge position, fraction of L from the back -pf = 0.62; // front ridge position, fraction of L from the back -hf = 0.35; // front ridge height, fraction of H - -// Clearance is a PHYSICAL quantity and only means anything if this is a printed part. -// See the note at the bottom: for a viewport glyph it is meaningless. -clr = 0.20; // per-face clearance, mm -depth = 0.40; // extra pocket depth so the male never bottoms out before it seats - -Wf = W * tf; -xr0 = -L/2 + L * pr; -xr1 = -L/2 + L * pf; -Hf = H * hf; - -// ---------------------------------------------------------------- geometry -// Vertex order is fixed and referenced by the face table; do not reorder. -// 0 back-left 1 back-right 2 front-right 3 front-left -// 4 REAR PEAK (tall) 5 front ridge (low) -function ridge_pts(l, w, wf, h, hfr, x0, x1) = [ - [-l/2, -w, 0 ], // 0 - [-l/2, w, 0 ], // 1 - [ l/2, wf, 0 ], // 2 - [ l/2, -wf, 0 ], // 3 - [ x0, 0, h ], // 4 rear peak - [ x1, 0, hfr] // 5 front ridge, low -]; - -// OpenSCAD wants each face wound CLOCKWISE seen from OUTSIDE. The right-hand-rule -// outward-normal (CCW) form is given in the comment for anyone porting to STL/OCC, -// where the opposite convention is the usual one. -RIDGE_FACES = [ - [3, 2, 1, 0], // base (CCW-outward: [0,1,2,3]) planar, all z=0 - [1, 4, 0], // back (CCW-outward: [0,4,1]) steep - [5, 3, 0], // flank -Y a (CCW-outward: [0,3,5]) - [4, 5, 0], // flank -Y b (CCW-outward: [0,5,4]) - [5, 4, 1], // flank +Y a (CCW-outward: [1,4,5]) - [2, 5, 1], // flank +Y b (CCW-outward: [1,5,2]) - [5, 2, 3] // front (CCW-outward: [3,2,5]) long, shallow -]; - -module ridge_key(l = L, w = W, wf = Wf, h = H, hfr = Hf, x0 = xr0, x1 = xr1) { - polyhedron(points = ridge_pts(l, w, wf, h, hfr, x0, x1), - faces = RIDGE_FACES, - convexity = 3); -} - -// MALE: the protrusion, nominal size. -module ridge_key_male() { ridge_key(); } - -// FEMALE: the pocket. Grown by `clr` on every side and sunk `depth` deeper. -// -// HONEST LIMITATION: this grows the key by scaling its defining dimensions, which is NOT a -// true uniform surface offset — on the shallow front face the normal clearance comes out -// smaller than `clr`, because that face is far from perpendicular to every axis it is -// scaled along. A true offset needs minkowski() with a small cube, which is exact and slow, -// or an explicit per-face plane push, which is exact and fiddly. For a keying feature whose -// job is angular registration rather than a press fit, the approximation is the right trade -// — but do not quote this pocket as holding 0.2 mm everywhere, because it does not. -module ridge_key_female() { - translate([0, 0, -depth]) - ridge_key(l = L + 2*clr, - w = W + clr, - wf = Wf + clr, - h = H + clr + depth, - hfr = Hf + clr + depth, - x0 = xr0, - x1 = xr1); -} - -// ---------------------------------------------------------------- demo -// Left: the male key on its plate. Right: the plate with the pocket cut. -PLATE = [30, 18, 3]; - -module plate_with_male() { - translate([-PLATE[0]/2, -PLATE[1]/2, -PLATE[2]]) cube(PLATE); - ridge_key_male(); -} - -module plate_with_female() { - difference() { - translate([-PLATE[0]/2, -PLATE[1]/2, -PLATE[2]]) cube(PLATE); - ridge_key_female(); - } -} - -translate([-20, 0, 0]) plate_with_male(); -translate([ 20, 0, 0]) plate_with_female(); - -// ---------------------------------------------------------------- note on the two readings -// This file is written for the PHYSICAL reading: a printable alignment key, where `clr` and -// `depth` are real millimetres and flat facets genuinely help — they slice without the -// stair-stepping a tessellated curve produces, and they print without support on the -// shallow front face. -// -// If the intent is instead the VIEWPORT GLYPH for a CAD mate connector, then: -// - `clr` and `depth` are meaningless: a symbol does not mate with anything; -// - all dimensions must become SCREEN PIXELS scaled by upp = 1/zoom, because every gizmo -// in that viewport is screen-constant and must not shrink with the model; -// - "low-poly for rendering performance" is not a real reason at ~2-20 glyphs per frame. -// The real reason to keep flat facets there is LEGIBILITY: hard normals give distinct -// value steps between facets, and that is what lets a 22-px solid read as an oriented -// object instead of a grey blob. -// The vertex logic above is identical under both readings. Only the units and the clearance -// change. diff --git a/docs/CAD/design/mate-connectors/fem-0.png b/docs/CAD/design/mate-connectors/fem-0.png deleted file mode 100644 index 391881e72a61a4577e4acea81ae044ee7314f4d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5985 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 jT`dg(s^3N{N} diff --git a/docs/CAD/design/mate-connectors/fem-1.png b/docs/CAD/design/mate-connectors/fem-1.png deleted file mode 100644 index c329d20bea00914d58df757d21ffd31ae33fcb42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4068 zcmeHK`&*J(9)GPZn;dl-EvGEmnRd&`%(AiazLu<0rbeaaC{m+ldBO1lqJq>^mNug` z=4BKkF|91M@`3_lrA2CL<`qyd6%bKLP*kpaoL_dIefk4-pNXGd&ikJ8p6}=OJ#YFk zZx6$@o7O@QWO(>{_u~+>A_{`^-mF;xMno$^^dQLi;bC{TlQ`1!aNspRK?!TN^h8HK z{ciTH^{ZBM4&QhByr{^nyUD@mc(kF7!?(A*f=v&HMZn=6N2W=q*kYfsy8&nQt)8Z5 zd}CtUv!?mWD_Cqo8HsHcDa~_>f68zAZBpLwgLJPn`L>-z*e>rZ_8Y{fcP<$@EDXh4-6IjTOZr<9jtKcGH&t9Efrz!i2B`uqD2ynVYH zOv6a}qifq}7|Cj53&xq|v(>E~NHS|0sPBl$U)TZTF`F9pMCZ?c< z6-EiRA{*vX;&;)U8W|U69~W8`+^Bt93u#x^saP9QyV3M%#hbld2`e(8bydB2`|h1>>}X3Nn)V>P>o&L2 z_Q#fM6wBm=nc-M`lSX0gg2(y#_I0`H7`WNdBPz!N`*`7aYc`eGmBA#a6w_R4_Wr-b zYuzfD%1PM>FGX5WQK6CBKqFNG5Mx=UEUY}|XcqW~i+N(_#Dx!%ss4vy#@7=R^D6uWBX zP6%2&cQu3#)yHa73;fyIOlQR)ps68VWMsbYQQW9zf$xg@FhZlzNjV>Qb8EGOCPmu4u&*#5O6WU+DjxsCZ0%ittI z&htz9{;yEr0_?rn$T12a1JGoTS2bys2#+nb5q;&U8l~@Q)x2b3mIbQ{YIr&}QbFa> zfW~uK3&V*h3}$|Dc8u8b15i$RWo0TDGd?hK0YK8UnXx1+G}{$~>vCRjS{lzjQFc_! zYgC!|I3Y~oo}TUbdqTDy&EFl~NwTV9#6ACc6(D8i0C!H@V{UFvCMei9w|ao}bedLb zZf-949}b?07j-s{W>^8%4urF+(~M!vB)O1cS>{!^@9dlWD+XqnfJxRju<{=vahrf?7^<}nNe3dJ89V#EpBO0ryNWo1Gjz(_QDmrXsOm!!UN z$cP;w^R#J9n49Qc7AvrTP2=3Vw#9d)p42F|ZRqP8-ZP&;rF>mD1&)}i9SBC!vDRb> zJ0#1w?KubturpzwKgv4eM=_X^yVD+Elo*wo^$F=h}GpTuVp_Q&v+ zEohNA6bnbxP^tUv>~d0`gDT8W_}QuIu2hhToeq*m-hS+Uu>L;m)Sy`~uGrQ#vj34# zC`_4Y5IwkeyTC5eFASfD#ELn!kqA=}DZcYQq684csJOkoJc`NXZ~)#`eE+551cWJ) zmcUl5sB?Z8hZbKp@vavZMDkKq({7hmgEZO+_Fq~W1}Ko=WdN-wgTnA<*4EbkD8l^K zET^Yi6IWkwo%9cyoSdwr&(MGw8*F6?ZVW7G{H7olNGhOb@b2Mkpf|)!<-TZ6LZ7cc z68HYf@}A}gP$)kUFhBbI`EU6_AV*miDQ`&j^4vgl0Ps_0l?*sBipNN5xwcNyUlG`p zNG#c1)8i0mr9o82}B#5oNF z^w6&&B0Aa{b{(+(_KsUmnHQILIdG#B?gMB7Y@j|Qa+t$$IP>DN?I1Z-d7ePfxTvP8 zrA$?BVMRu8V=@xc=WDXvJLqX>t5Ky4B=`IjTfXca@FqTCVVGj60PUg}XdJ{U5GsDI ztga5LUYyNxQJxb#s_t@8p>)cDn;sSoqT4BTb#*g!T(n@(ZXjsy`FD3!(k9}w3wpz# zv5r?_N!4-v&n~WP7m>o>6ltSsL8|Kyj6q`A zVMRxI-9TjyOpr^oWjT&_Qr& z_Pq6&I!GdHemE0nNA@m_H^ivrA&#!Ph%< N81C&}dGPel{{omNlnej> diff --git a/docs/CAD/design/mate-connectors/fem-2.png b/docs/CAD/design/mate-connectors/fem-2.png deleted file mode 100644 index a32f2ffbf1407d5aa775e2be7bf814bfe04f7313..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4274 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 Hu0;IMR-$c)p>C}k+BPqZbKisaOkdM2jBG_uKI zdNQOkZ8<0GN<=xQHj#3cQ_jihThHu&zV~{+@9+1Y@4Cii?zQf9pVs~Rtu>3!a@f5T zQiBKt!cv9}!XXgkj}r)T?|zjBBi#x|K`-SR8}XqY=Gi}@zHHbpj?UFd z4SG$@J{%{4G@T(R^P_#khG;gBv7MdhtxCI3wTs)c+)OI<4{#N(SaQ6K4 zOr({c+?x_DFoBQ>@_d-E;9pl>`7t{bR(z~JrFedJ$jWa)IK^HcdlE9>6Ej=<$*;Ju z_g1~5`Rsa)sW&~ez7OeUvt#wU!=8^;nYoX~eO$9W@Xz5RettvAwoyBZf~Q8Pws&Sm zVv6mA9nylqZ+(5P&jU}nySKIkw6+8`=J^dF9OLf6^=Z+3+MMNa<<(=Bgg6 zlr!0)KElSdG*6EYk6k8P0^EPzN=!^NpZU6c_l>m!gM({TRj1y%&(+n}8~yBmwcNb9 zwRQf!#;kY4!@a68s{&?ni)RO-_Z^%{a}4=bWj54Y6f)hM@1GPlH?=&ZXIE-s(6={l z-b@U9(g^%=u`$!jeB{mgzK>5@fm)C9FAEnM6wi#fp2+X)xb2vjS0SX@3blF%2GlRi zG&~6%Z+jhCP*8AV&9;%6UCRYI^M74bn4O(%{G54bi*M7JhQ0eb{Z|qaYHoeXAML92 zSrS=1H~yq`A}3d{HmnoG6gt(}@MzyqQ?~ApHouTQ*QE1Zv7b&9_O+CM z!A9KCcl|v5dGdN~W4HX%y>$}ZebVhG zzgP&h3JVMO9X#JTGc}rfqU9O*WLEE-Ufuemv?sdwdxXLBu{v9gnJ)`hn}-hiClz!E zZL7@ZXTWBfGlaXYE=zaK9Rw{lUnjAh9BeTgew>;V8dNT%E?%j(>Ov(y?qF(eZf-_1 zQN^uh|KOJwmmckV652ZbIX86nXTRSvf;ztkvx)@i-PS4}Y$;Ir&24K?FfbIkQs1;a z$|w<(=H9(~e@4!arJ9FKe`+oAYb`oI+9g;UZ{qiPD0H#_ch~>IAZ+Sw*&2Vl_F?sa z=GyxD%%JgawDhx0$5O|~#A^ZkvBsb3ki|f7d(JHEY~54qJtV z%`Y`+{`2tbYoPSf{TYW}U0KQyuGty?Tg#?vilKT;Wn8#kV8^o$9apKdLlo%r;m=;t@S zm+$B2Q}s!6V?eR=pg)%KQB_UtcfH90zp#Rg<||8A-!X8XyQDGWG2Hh0>T-?5I@bk? znQ1Lm>S}7-oZ~L-!|A!7gGyDsqIR5~9sii*U$u=PbPN0%ao5DJXrjp{Y$7u${XoIF zv9YUbgL=|Z6Pxn=yYbEZ`^b$0K&{s{>fp(-AE57t%QT8lwU5u`8DyvU}Lt=$HfNEYku!eml|!~-n@Ts{7G2Ik^J2nA!pi41t85c zwN~vbH0FPl;eQNaKfv=x=>hxoroCR+ zQoqpAyGg+#<+VR{Om*D$JO9>ona0kU_fC!pArk;pAil@$#YLMQoDB$=|9;inE%RvQ zhjdq*Xi^^=)*c9G$v)j#aaAKUuQ$`ktEwM()iXKuN}ySpxT9b=&Ka~*L!Lo`R(X`F zkx*;f>5ex6#nUZ-s=j{xnlwMtt@JAA+(47WHs;{DMo-tAvW{VZ*d{OuW@&0M)-sNv6wg&M&pU+{>6vNFK-4DEz0fMEdY(vOF4Yn|^lYd!yJ zsY%weFIyL@{5JEcbuI^V6{sK3n46ok{u4+3`1p82U>jdM;Y^)M@sHOTsqyEUj+uN8 zZ10S5FY@^)je6AIQZNa)I^k7(5+%pv=gUFK_8mCekXtmKzOV4xUcvOU+onU$ zN1dM>oJ|-C_G)uVn(g<(kqJnwDt_CjuKQF)NhUaN{D)tw;H+Gx5jIm(Q}fs*^Yf2V zlM~H(!|kBOdM=IiMV+m;H^H~1b*9UtddFmQhP&h!&6cR{Wt-<&m90t<*pCYsOL^`! z<^MRxxpRpR3>p>c5yDDH!olc?4W`rf(~P=q2Ib1KFMdtegvH@32IM4qMVJG~$U z3%R(t>dQ}G?ukYC=qBT+JWE{)Q;(cAP@;^8VA%VFDEa{kkEe<~HKp%`D90b-o^E4G z`0&{jaRXJlhlg@pA+C7^vk_<|i`k@o*C^Qgo4h(r>6ti`V*)WvJtYD}#AUO1d|f$2 z^pnLhrMofKVec>Vgxa1GI+CV9lxUS~-a=t^(%?>Q=~*ha=Pr+Ppn|JFVMfs4Y%QrW z1=|zNTe13-{yLIGNG7X^viDP%<1n23Y5MLZjoEjDSO6(PikT2B%m!vFUtqb4B$DKC z)V-UQbpH+59zM?!B334$990OSk+T#j%*|jes|G`1Ww972E!M(B3AmE}9hCFCOz9+r zd7g$V87i`wO!~SA-kr-R#~On0oJ}F9H|k#JI!qMFvos~@E0ZJ$nOsNn24(QPC%)-G zx3xiNL}J7li0eRM4$J0_@nND9%LdeP14d@h*Cp`o#G{;5GNn)obCXPoiozDXu=idR zCT_d}0!mjTQT=2AaS@T5ET9S!d*nVI^&=akT$Wlo*wy7SrDurfx>HR~-h}N9l|rJa zC0*C|Xzglo7x~Oj>(R!n`?+qT(%{@(W;E{xKCGTBt`8W9S?gGx{M0#y_aPjuqd}(j z0#k>$S1AT8=Qmf;IuaPTy-eK8P0*Eg9$J#VkR+KUpD{IDCO#CEPQ542UWaAH@*K*H z7uIZ4EpvA9RUxJ;kt7LZ+K~0&MZbG8Q^Bkh(jJH7C3lwM@ z(6*)@(Zm)QUODPT5ZvThTOy0@i5pcd)+dXH71=3vbR)y?QU?7#1+x?Ij{X-xaGO_Z zjl{|mD_;`S^ofF-s4$v$^bkRChgZr#$`QJ@CqWR)v$jQIfu{+5`LX zJZlGpabMiKM68$(y3FD{%^OmH`J2cji89gt5P`~O3AISV^@#_k-TSNwh+uYq)%9M72~;DZL?6kzg6TX6USUB$rb0@;YXg}!RsBMwnh^mV zWI0px9t0awA?5|XcL>6%LAEZEh_e#RBb=20R$jtzf+ltrAk%@~t4K=UjzfgmjlwK1 zh`F2ZovpeHz_IQag?k8ZdeIuxdoPX**2p?6IvGbsxd3&(f#aF#Na1dhDaG@>qjAu) zMzpbL08vXi?K0|o30IQ96s{Zuu^cGeb24C)Z4g^~9H1OMlD>h=`&T3@83!orsfl?4 z9}aZdB9gwkZ0f5qgt3n zBhC996lX-zPuxW2!1#A_lKvkunsT5oUWZw6ST>-cY2IukYa<@Nu^CbSCc1JLPDDpj z#jjV?%F8IMpPY4)5@$k;AVYD0P%fffGOprmqr_?8gQnEd8lhrsyxt%4&wPr<0&5Eh z-z?~FkCggfJqQOaXYb!Qe>OewS!Y~8XTB1=?g$8lt3!#qEkd_Q9+&K*XFAn9CDq2; zZN}13-Ocy$kQVX19h#JDolBy1I5oh3JYuY!aNXOff*Ffv#pQzKmXxMzd7Q0yx^L|e zf6hZRha&BenG-72#_yqE_B7oA32tr*n0vSyH%GuzaOR<1$)O})E4O1-xc7%3_!6bs z6p=D+Ocz1?SDZ&&Ngr+Ss&VxxaZ03w6{pgbWV5>we+pKg`gm_LZdn;vMzZ`%va|5= z0g|lbqg{?}kutYuWs}=WHZPQUjYGQ-#MZ&(la0*jU6-+6#ANAgUAz5_$i6)^n7xVL zRiec;YHJel9Qd2EezC(8vEGh{kN+!GJnpfglFu+cdaI;i70BSj72HUZ6tO|TGl_4p|Z)VOPW^wGYRTQ z!9#o)dWzQw5~5S%N3 z_4goTu`k(p3k9kH9K4Q1^j07WZlm&cF*x_hrC8CAZ20Hd=%wne_1YNWBAHAg{(4WW zMS&Cnl!37{Hp`k1|3!x3BYfDx8LHSyfo}3~4wJWYpdVu*avSc6vnUwh7+FLHeuO-s z10oma#M(f`2=*}Sg2I0VLQHE4^xY4z6GHytP8QKf?C>xSD+R0;BJ@RIHN*sOR)lo-fG8yVwRM3F@~ifm0&h*^NbiYhp_kWrLZcaQ>=SkjM~k%*XB ze1?b0gB#2l6qVl${#{4q!R6(Q2$j#4h`}XA9^6|xERc(89GpKaT3|Sz#1=b~Mcer> zO9Nt7A>;>(h}19&^!_4`^XoqkX?qekiNNiF%1nwxDtt6rjgHBT* z5g+cxDT1hDO{Xmf5<;gb18EnXwi-xuIt>(G$AV7NCJ`$Dndp;ShnYJNe5Y^rdZp9 z>UxUB?=PVvj)RMCuO>>iAR^;Z=Ozbwni7e6Id6qEJxvuH@XbmaB6u8g3bRbrl|dH8 ztzbjzrNb=NHzq<;pS2x1I9WO(W>Ok0+(f$NxmQ*f+$dix1XCbc?F3mk1sQR z7dTk6*lI}b!4+#4SNkkQunXZ92dw-EgomK_{`IZ&KlYKXOrZihT29n5D2A1(-FhK#vmiGvpI@_XZ+QqLD*DR+)~?A`r4EUXXO zO$P&&i3+|4R|;x76Env#+QN<7+BSzqR8v!LI7rRRP3xWR!JaioC;7iRa6WJ4d=ogs z%~42hMX}2^?N*k`1JTB+-4&ny`Kn0lQ(PJTVz+CfxDlZb#zC2RJj2(maI_re`(=Hj zGRd`3Zc*?dWWNbs zG|sFepd(8fcSlDAm&dBt?1ckHI7m|*SN&&VJN7(Ao$~*wSDL)RC%>R69J~=(?T#n) zsGOg8`kfAcI9RPr)p9jp`Af^yv3==NFX)$5usfe-&-lzHnhw4}L42M zeho^I`oOqt$cIDt-KuJ<;DcFpwr6mvsc>#o&GBXjBtjE$#T~igY!d)R6h`l}Hc2#A z+%Z<%|90NNWyO07^BH(=(l?IdZ@$>037h>; z8!FD_@!BnPq|o!B`bqDjmFi5NQKxPsX^XrcL#{cDdy;wd$vp`2CjSLs^&;$Hk$%8v zpTpi}0Cq3H@%^)3ZG4%>xR_w^5D{ehmW?+~mH9r0*|1HfSFa{{k4(bTHw%;g3OCJ3 zlu~n!dvpi_19f~n`s8*@DeQOGdz$v+nW{g)!lW%l@)mwS8jm2sPw4br<1}A+s7wWP zn(wF!MjsojBd?7rjA#-FuxD)@mQ*QxIr4NC^TIL_KlWoxJNkWVKgS-RMdh6slh?lMmQE9!lU|M$NVJs5qJ&oLTM4C+aF(zAjwDEwCVcR#Z+mp;*c9*uOn`7@WZ zz7IN%66^8<*~9gNKL)wJW5tdj@_URC;ULGmu3;FEW{Q($FOhSt4tN6&9F!eQ=c}35o-IW2U zfD!v-;&;X4?-n33j@Gb95~Ew~Y?4^l9qy2)i-q}aK+U_YMfclfeOHe`K$aiFlCV!~ zk&PfKEZiG+cE{YwzT{Or`*T9}dAaqkpQr1aWuU68Z=tZ z@SQx6LT*WMhCt!=i`6}Fiq>X$M#v=i*a^gkYxRWB@kWa&d9P~BO?G$Kjw5jWn=taA zquamBQ<>__Ikp;*wLT6RL&<0eNgSF75gRbM^}*&K(WbCA*&gKlGrOx3M-lpgj}8RBgP%|p5xX*sX3|L65N8UB!`lVCOhd3`<; zNg6PmkQM6JngU3QLBJWM zr=!F^9F5OI>61Ler+84VcvL=QN{9GbN__3*FO#2cYy85OF5wBdu&!`|d3 zuensmRWK*n9&AwaygZ?5^Q$teyymJOzpexh8kX?ddm=$Vms8WdH6D z@*qbQ2+7;5dvSJRU5e6bMJCy`JRfzqrWl+Hma+0-X*>m!$2f|uI`N?i`O zPl(tWkNrnsd>LN7AYE>e_OQs^`;d!jTW`iW%1vL12^gOSk|V)ve~$x6T(Ti=0z*TS z&_)Xfa)0X0kX+GLwe-Ib<~N*aLXGxC8Vc^-KnOwOJA zH80q3<@ea+ZdK+%YGy4+MIXsWpkplQ*-4%8DtCMUxt+Ye&mAncslq8iUTZq@E0_vq zR(5`6LY3LdYbumkfw-Vg{>zN@TgXkeMiO`I6uExQ&{NX%cyrOfL@Ktr70G`ig%y@R ze+QCdMLtnpQ|Cn&&(5m7@L5)|H8^LO1yivqrUKQU$$wu!4va=#0_EgadY%7SRjGUt z276Hu^1oj_{=Wmkp%ZMij-4u0{cN5holN=E}`hR>hK z;xM)J>>fVPX$QIupSd3pQ;Zs$;eS^cx?f!7P%fA*?y48wJoxdFJ(D@i%a9!{$W{?e z%z{-_y-(gsFC9sWj;1m!5JMWpvr!TdgXnTL@9on$g$fGuik&{i?3^@3_eZ3W1^0-h z(Dy#$4!J__62dm5$!(H0pQsJ}5=N-Js?5y%vWpJ)fSeWlO0=}|24;n{QO>hw85VA+ zv&!|1=yl;a^{l1e*thQDG(BC%ZRqkC3TYbnm(}OWol*)nE0~opE?)2oRw>`SA1F=J z)9ev>TAJpp!xX*SLAV=c?90o=Pl^Iv%ff6gDPo8vv=g|UCr0@&yz zA9J-=i0kw)757sP_qS8lZILBf4Ic^!7;n9GUR`irvK0`g=WM^{l9sh%V)LMowntc+ z=2lTmcMpE9*<*Z)%8+KLBO+^S(FU6tgu?MFG3rWw{1$;x`mz5Wt3LJ~GMJdUa88K&BZ-D{XT$tgXDYOkNIWB#zaGf*M&GILs1meNOV%yo zJe}j-(GVqfY+2(x_O3OA-)xa+UUM7Rut#?2_A7CTm~QV(`j<59q4YdruS=SvtFC|! z9_IR};6{c{M=o7EVfJJGnET7m%b)>kVFuOcEW*i$_M4W~3-zTM??eo}P3KW@@o@#5 zBuS^-!UNZ|s<>Zc=Nbj{PRL+Xw?)w9r!AaSWFA(C(hH9(=~v4V*eSbI4*Ia(tN;dm7(?;#ABdYb9 zIJM2fA((EY#1f{u5?MYA?drb_yba@ngJWHE*>CZ(SxELU{3#)W0l-a`n5@W0mZ zq>vHbi%v+a``Uk2{$j?2_UD3_3h$V1U91Q*$zzKPzF>ApE%WY#B_mB1Po9ON zdi8`C?#xcj%OPLb)1$?WlwSQ}AkXBofNv>T_gtukZlfcDr^`E{6Mo>WZin!D(^!Pc zb_ymZ)+rSl-4Vk-4x&xgPI&$!Bkmr5>Z=JkZ6GYC zYEXt;r=z>Cx_g!#N9(+$7sNki^zw3QlC#WaSTBOFE)iCGObMn13if|GEm2 zux4DUS$Ym}yw7%sLU5Mam~%0rono`B9tXWMgb8?`IGsp}z}!ynSr-esK`x0q$(cq& zEkpU!J@!*SWnAKsffsKjqq=%TH`Si1y8UtOZGKJ}QNCBZJj4|%pEbl#8gTJ6ttU8< zNp#B{C>>p;-5=4tYb2cTPLxxIh1;MVn!)o#&BcFo;`T8b-@A1#ZSAR2D0@*{sPL_I z*?H@KyNMH6qd%egj(rW=rA(J6MrWkG_~6rDXp)^k;@8lMEv4S|X2%#B^|X(je1OuX zRTe+Z-E%k5SI+P{xYFz52s(SkJL*?-cZzSj(%WNau9KWHx2W4+8N~#rLFp7hXS#?Q zco~*Co4RaBH$jcN`yiAxy3@u7IIoexZpSMDPp|F$s6g!5P7h0&3ut+Shy9<|jknptiW5v55eqwcNRloPT@eeb` zjxjKgFrb|^4P?wOzwEk4En`6~iA}T#zVZAo_9bZgLmb;@W?dq;Vb$ATU=YR&XO?CV ze`e;wWXg*e$h2KGEt)I7uI)1q^3IRj8`*?cv+}Xx8DMMi2PYi)gDPQ_g_qDUQW7+Q zq&uAh?)L?OvzKr`EPI#tq59-Lk&lIwLu&Zfe&wXV2{x4;5+ zW~rDRtGZ+A>p~-^1mB7;&{xtVuO_Kym6Oi0lAcKTU$Kro4b=Ve%G<$$R8+}vxOivi zY|W4A|0?D#zhpq5jAU;SZr?3(|72yM=ihA@wB~WK>@%q88mjIUu6UWVuCh8+pDIg} zy8H9ED*q2C+MYe`?U~_4Ul2E5aN$Y@<>G2L<<=0+HQ3l#A)-G5^EIn?WGAOESjL=u zUra3zvlY2I%L40jJ@x&1@h{3uE6>_VdbS5z0 zk9|cQN{FJf^ie~8a^t)^eN)jeTg=h1=@M?moZZ z*|OMyoQLL|=7koyuDIiBa41l)_w znWC<@&b{F$WAEYh-isgJaNc=#@mJiAG+IphYG3|AO8M~6MG;ett$Z8F`EW@`uDXJs zS`&g-QmaIM1i75RRHaUtH#4GY_D>pZr5;`3}cwK_}VYUX&Ej*4j^&j~8= z$Z{pA-XR_~DfyU+hIkDtW9DlNX76Q3Eq^<2&F#EBMZt*EjjjX+=J9q~5?pAGf7fwy zoJIG;+qI|!WqM7J^M!`i9AtRTI_H!k7*a;~AA&_k1sD(TyxFWH}e^NJcL zsb*7HpBr&&R?wZIE^EqAFS6R(i3IzI6c_G2`7d&E?6{<5%5hJug+t#-aE|r@7Zx44 z<#1cnwMjk$d3+xkR(8S5xSY7Hgb&{0n%Iu&6Yo^R+(n&Sy7E>{tdcFmG(OKb;3j{R zCpxb%HO7pz~KSZU4J7 z=iY>tjof)BkKvy85s34>Hict92;6;#^0^EM6`b`4yq8+qT1Pd=p%yoVca0=x7YHZC z{L6UZN3j#Y^Kw0t3MnSMq!|6H`#)=xTbDWK3K`h>pcQVn;odUw*eF-%w^;*Q?Qr*@ z@SE*?u0G7P#1Z1^Td8)p!hJ4pU8{%ew?&wwS&k251vy8tq9%)OPBpm=9mFfVk|(VO zA90BNbt{}=t$TdfBn*TQhZb+=NSS(BMty)OPBC@QyTBRwzF!-wp zCH(6eC}ryq!i#omp!{gHyaA-uYWM7zEw>8xllE2%{aXu2($8P--K31~1owN~GFJWL z>6Yu6<{wpIy2XLQNe?ONtdrSwjAZk}3R635;X8L``TGU-S1g#WS2ofu$bU%j7>TJU zUp-{W4aU~4?+SLNhW`@zmRP6zTmP$dpt;4 zzoP@U6Jxh>vc*U%-rBM3T&nTa^qKF^k?a%h{o`%$z+HwB_ip%?WZ@Yx^rlC}9N*F# z1upfIP#2OLjOs&Xekb%3&Rrj2)=K}aQGO?)ANbz8^)ZHC6HcsrC1JY9@|J(^KH z+4bP$sEe`FF~_M%ZYr%#gVElpj<^8fGKXY12&~ZqTEhK%AyBv38d2aeZ&|CEv5jp|-XklJK^D zwK?%i&*0mc3&H0#-_h&FCe|hL4hfl)LMZZWRS3Q@aYv*Z7%nh9@bd4uF85!2k9n8i z6iNW8N@MrADs6H9rwV7>yTyf9Hw32dIAOSSIn^uqmD1WfaH2lj36H_tMW^^)IfTQ( zS_mYkBU$^z3hvaQqpP_Md>OP0tR;LWAJApV*J*ps53lkq?b#wq(#FOs zlq=%wP?LdUFW*fviR4*NR!WDi6NZ%PXZZH?J+0%p|E;}um&+rCaESyCwWft2+ z+@;3Neozb=5*Vr$Tt)QB9EMNI;?ZnH)9DMV)YyzwYK8lPNJgp(^1h zMKkQ>Qw-=%T^YE5^c*2U9Yd@_XgoA?@HMZ9q=>wSGP=$w!|2~96DoM{K+Q(IZ=;0h zcJ?kbN05JleKkD=H;YSt9^%g$s|m|R${)Jtvt;8ImUE2+i~>YyxZ(3Z$1C(4#T1gh zpg5i%v>omcGA4XImABPD;t03YRd`ipIuq+oRU%p?)hiJ(RkwOm-o$)EBp6ZQ{FN)@ zHexsq58Dpu7Lw^*-OSgx%}Wn+Q)dzTQ?0i**yImtTvMxznZGwO4;~+dg+|C1E#2$( zN%>uFxaE)&iZljBNEWVW5S{%R8=8~XXy2=(EJx-oIpolCaBQdg4@`1LtDPwO^XEjG2*X74vDU;p z6;#+lv>NwsRY$4&MDCIK!N?_C@v(jQ7VF<1hNAXdb#Xs${bUtV4t%&rJ8S0N7M{^Fa4WR8JY=8^Qs6tmw{+N?tCz6WK0R4t8Xg~&)Du0jR zJUFr7rH0u6o>Of-WN|X8Bu`_O-_=Oox!@clGOjU zO#Q~YUt(;vswyma?^j8YUNy;r%exwx^q=@{=H(ro>tAAaDpPmU3$Lp15NGjd#r>5M z;0BInr^JGsxAna*tQX4?9e0Pbjz>U{uRt3mQ-4Pnbm5NgHQHgVdXI^~4t0QPOdvQ! zV$~w*N1$R%S_}Qbm6YYNI`MmbnewC$_oV&`TEj%+Hdz~oYsJJFaaW5i3t2C8;Y400 z)=8vy-6*zXx9_$E^`&y`uEI45cAmSuXx*A~iwUmJfB%sm>wU~M`ksEJr7p3K^MFmz zZ#qsRGAwp*=?U)EEjFL+qoPMP8b%=ql}R>_ziIOYOEdXxUDeXso=0EYa^3v4A5F}X zWlG9`OWnE=(J*>Fbf@mwgJ0!^-i$cwx7%R%yTXY1nlQD{V5O(yHC{B)qS~lBZtg|J zpTxR_->UO#QfJ97F*V(v>z}x0#cO=(=b_|9cka|*s?_Z=B??_5Z3h42zb9{i+(gN z#q9bhi;bOV!Ad-F(at`$-B6SZE;TT;+vihreTx-o%A*rc4>t3EeV#>#=cHkHT%CK} zaU#FR{GzU+X*o2f{JTbvg*B6HE(d6VJq5w-GCGbRb)_}6S{Cwg<5BcS+dZK9XucLU zK^O5f)kqVX2MH2KOiWk5dZcybY#lph2LR`|0Q$&3l9VfKWNo}G1%GYx(fpr-T+r{d zX!xD8Yb`u*ihr(I?1~jq?psl{lqvRU0jcsuT6*WI|K-v4z2_qY-@HR{_l=Y9%F-%T z?brt%dnu{5>%d#S%tl}m!8+$>alh_Cv#NkMa!RaguX6L*ZopKFH~Gf5%u)WjTOqqN zLMmbmuAipE#5=86mt5+2I5s4lvE~9JnZPRQeUEe9crw@o9dg}#1kIfq%dZ@!fAoJC z%j#!Vrr3G6QUH{pR}$z~&>dCcvqTnKr^t!Jn_lMP3{WJ=h)T;O=~#$YbmCq%6$*0v zCg{)oV69XIx&~|WdV2KF%d0HG|J67xBuv0t<&kvSkq;l9i7vf#Xk}0KiQig;Vs!kI zeqEbq)f~CL$`zPFi~b){KkXv}7xup-EgZ6^JDvf0)>YlYAvAc^X)i}ROrR6z0y3H= zPiIX{?0Cmu`1i{+JHf&rA($GsI_i3FYvSU3L9%z%1jlP8cCaJSb>Gt%HEj$WR#Hey-TtUWoZYF()JN!y$V09) z^qhdVz>Tw@lq|IPYFSY>`*ws0ub1q+JGZmE!_}gef&49jl{QZR)k*-)O>8xFO*L(# zR_{1DhM0GM`F1)ZI${)&>t?^r*7-cw?Y5w~S@qEfAz-2Ae8+}A>%GHha~n_}_Quk$ zbmUo>T1*cbJ#ZW|oXKpmsk#Q2;)KWZs@s`Y@BH>Hsw=ym9w^(`5S6yJ#bjfYpEJne z*JfzWh?XDSzOULH$mmZem)?ht1(Wc5?zD=L2u$a957IWy(8J5;FTy81+k5`rOhC!c z;{{Ciw{w*pH!bTl78Cjp<24-lP@^3u%>*Q5lkdN|%{eHQvS!G*ks3ai{SzS`CC}dJ zC2l@x>E%$xVNS9R8&;i`Tc$(VO+;Rq$B z&Iq<>%pWE??M~^Qq8xiUQ!`eyVKSJXdqL8&`YyO4F4fw1#m@~Msdfzx6m>ANomQ9l z48O!v@4T2afwtYhU-MJWP<3&#fL8TMzBZ$Bg|D)gnvZ0@>Klj5VbFn5Jq}@Ce8R~G zgS$~dc#7A!-QC;o@_ZyGw7YsKH;kTL`TJ7$rQP2hU8qMNM9n8l@kt<02$>}0j;Ka8 z&uK~z4n#RH-=;}j7HO~dVQ?wjv5&twpw@QoGCH+YsBIyGn3iF|FqQhgwUmD)CETGD zEHT={`*4<$nv~tDZ;qH~{5*yeaQqgc4D78*rRxsWx{~WryrXMsKG+Z|kop|C(i)u= z0*#jv>$vsQJ+DNkMJh9vSDzU^m|4U7@7dZZvZcHmv9ZZ}JRgp3ERV$Sf``vMgfb(k zs07TFPyK?Gs-DQ>9J{0}J4O1Gf`+R3!4Y1bIZ>+z*WewuJ8(X?r86;{Cb4sI82^=N zPSgl4hawcmQ=cii{#!W&pZOT6dL@=#P#0|0Z`oY(RFdhyB}nNDNM%)h?2zL$en>%6 zd|h_yl5J`UhL?#7chcLk&>{kBEk$Gm#iWLT#vuO@_mYeDg?|m^sBw?}HF%F%DD59W z>%`x$c^8_!r+)f_vBLW#-~t0jKj@G2ba=5#FoY1iwm~bfrJ7*6_{5Ng_b#XN zijNv?z?eWuts_(Ju5-Ku;bk<2H-u2}uVGMW^5cIkr{MuMCAb@CBn~xd2V)LR&6qYVokDpB>(m|JeJe${fkCC^>4fXuvjM^WY;Y;B)<|lJ z4uL(dfSODcPo-wlc4MDtu~!;l^bI(0iWK z1$0Qyp4J3CkGio%VUeoldx}u%kGpW(ZsM;TW04+FD|KDQ>+plvVAI908tJs=BclxBCpt<1w36r2P|JqJmm)69MYW~98Cqvtw zkWjZeG6!YtdX4D5=nNV5fd_XcgrHov%cMr@UNg}3l?;l|w$W{NGvyx&;=F?0^pHVx zTmHR%F&y*KYe$wPLyGWxBHdk9Eo<5+Z{_r~=E~R&>sMe46TBY+Dy&j?RimA*mht{% z;qBSl0#M-cqFr3Vofo^#dOe$XR$5rU{`^Do9)gcZLCJNQ$VIjFD=mQ~9z4wS^~sh~ zPDl!~D73ipsBtx1z`K&)O2gpV^`xwhM{0%IY`LW^zCO?T@7?6o3ixhi?CSt(7JHi# z)sY0oyDzo=QrEd8@JsOO8rZ1IqSD^Zuo)iS2D>_r&#`GZem)ehUlSs^P9n-wPSo9 zCi&D=bzL3?^tMY6P{Z$e1i>g|)1bGp~>CjX2wvg`e_?I8i2_vcd-zsi;8 z;xeTzCl--yyqT$ zAHlB@0gA`o>E{zE0^ajBy<3};UJpWPZ>29^^U_w)*NWCu#}Plbyfi)$!vFm()YKI9 z%M!@Km)p<4-xEsV>ej>WE$Q@l0{GR__6&NSFsh;cj0Jg+;U+W<9;Q;PaOnYio%?t+ z;KhE&zEc_vzqIuO*VFfC;*PtN%<2JJa24l+jc_Vy_D1mwNUM9j{6tU+(^j|Zo$@;N z)L1NSlSYG`kPG*JC)Q=>FCgA0*3F?qfhGoTQ?d6yjR%^Ty0e)I9(7YaD(R<_ZyeGN zNvt!kIBTreBfF;H0^UE@jL(XtyO`U>%dVVkti~>3-`;Ea>!N?ITkiA=FG6A+rWjW# zUR@7Fpwr%PlE1K|6X;Yzq1#T+t!vlYRH5)yW2W^-@AzB@|8zWt_HQ*IZ!fO3N`Zlq z?PXWXd>0=Qdx+hw?@#v#A9cucQMjg}AK+uxiz;(7tt}L=;d68pV`@u|-$N&RpJ5`U$pZIJMAfW`(SER+hfu ziFG*hih~vXa%EcU(E6v9*@<|}{TeT+So(}PCXXj!;YL_ViP6Ryu|WO!=mOBDXgn;V{?yooXZv4(QweGMEK&y-^PRXl1Xw90o$(#+Bk` zdlA*J0t)*KYjl5#mszA@U2BUVD4I<>dCp@jjeYYbetI7@=B+N@d^&A?u4I`;ko&F$ zc<+OGG)P&+-u?VSL2}XBZU8l26kZ?B&5#lvm--CkbCW(Cm`%=s#f{%{QZEwBjT9YgvNCStOCA$<}HZ?sKZzqv7)@S)Yc+OX_bB zHFawB;XM4W7e6HQvBkS>zqv9m^6Q^rCXHHgpQm0_1fb6Kqm8L>%O+F1movAd5FDC; zW^WKDEEGjWIdZcHjvT8fP4|;($2nY-T2><@S2D$87BH5m z672_ijyh?2|Iu@(#$zB@8xDoRO4jkwL3woK(!^JTt0;0yQl8?_+_wK16#C+6H-lxBouAX~xK%;(txMKk-^cGUXD5APmX3Ndf9Ib2^B(B`zeMXTKOaesvd~5-!RK z1hwy2Amm|ii!~qjvJpmaoVj@sA=(p?fv^h+%hx3sydTntrK>sQ+4UmkIl%K5N3Qme zdt;$sE#oH5o)N94QZ7g;R*u(dv5xZQB81y@&%Uw6pJvxIc<^~4N4)R06EWK`_KeoK)wAdRD{_N7YwGVK_eYg^QA9hK_ThywM`e68~oS z8)={iEom7=cDkag!yx@Q`(3;L+fv-&74N0YZ#OLo_g7g}!rdow#EWIlwb|>KBij)t z&gl6wpmj`Nfr-r$S;F>C(xx!oJ5tReG~GPVBuXh;{d9KEM6->7u4~Efp&Sxy3IFI? z->G~ZYQsFCN-)p$zgHMtPpgxzLWD3N(l*RuvwFCxX-+x$nLD-H^`>Dk4d>pM&9NEo zGds;8>?am5x+R76hLJ)@^9t9Er_?fsxo*{uF9{jbZ?PFt(_j!6-fKo{I#*q?ntbtf zvVoHkx)2o4#px(Xv%>U!ORuc^H8*19=dZj}Uh5qn{B}`1HGx-x_mf#(`tnO-%@BS$ ztul~EU$b=DiJGQSj4IU2NK*p13l`I`^YXp4B#F^P$h-b<#7 zl5%j1;kk2Ps8?^_-Fn;o@RAUtB%#-mnx?jZBLsDa*Q)x;$hH!|Tfn}dZ6zqd!z9*r zjIc)4&u!f?D^A!;qeC~xm;aW7(q83V*1P0G&4qBg-ov5=Q3^)GVdcLVT>IvWZLkqv zEPJ5P@76aj^)1L1C5uw(!;TurQ`6X{L=>o~@vC*-65mXzm=0yth1PyO8Yt;Huqzc5 zxyy?F#3;VEe->1?m>pISNd}*J4P^+Kjk5mOMg8{iaYf_zEkX$4cXe*5dMnTeNdSt375!bVuC6W@f3o>3Ib|bBI2(N9*!h2 zi!VBZ0J{~c&LKO(wWA!a9558PpSP`!v0Vtn-BhoyMi3*iC@ozJK2U^aYZ_!_A12nZ zx8%^~-*Un=-BG{z{GBSQ>-XuLDI6=|V8w;Vy_Wt5PM#rH$!#V~dCxr_hIW+i^!5Oew=)e_nZL)W$Gyw{QW z#~_=NlP0L$S)66g!>dWvv?<7BC+4O^J|pB)6B5*huMMuDLY>(=T@Ig*c>E_NZwuqu z=(PD}yVbIcBOyoH{-^-v2a%*i!_k@0@Ny~`E?iXJMt~>~`HD&J? z!-Nvcae;p=vX<2_Q#fgvLdYVQTifZqW%%C}&+@x&K1us$y+36gIB3mLNl{6|99BgS zoI*)$5#u%Wp10)!oj-@+Wy?zWcDLMBIOyQA9V?q9%f=2|G#fLbEE%ov zd#{vr22P7;7-U@&=)U2Nz`cv${~^X29meKSaSwP~&0wSRNm%?NE}Cf#POv71To`ivYaOJvRGS9@5K^lj_QZJz2RT!#aBHk3- zuYtk9i+b-~wo0eD{A-(VH(Xf~{i$D95FV{`0tOyYaPmD++ygj0>_s61+8`WM6u+nq zi=Li<@oI-FOCPdCpJPX>0g)K_;Mw96o?bQo1f<}k$z3cns@?4-+rSMNFgbaGh;GA|GeKV+bZGiSg=t1j`9*`lRU;mMx(M0fi zKP-jmKY#uod$;_5ikkkv9{w-C<6rk5Mr3aK;a?Iy=zJ8B9`@XhgU0R|{6wt?!~X{Y z{$E(;g&*KYNmBrafdDhH-BTwX>ZFGf^pAh&zduk}^J|ec37$!@LmlXo7r<*;GtIQZ za&swm4&*&RxxSUh)Taitb+DNKyu%C$UnJHrW?e-XBQB=zg ztEw1r`}0oa+(E0lf)P2EUDhWMB2NLb15mW!KVM%W5A%@_C%KFb{>zwI^XC<USav!8~W0K^{pJTJ_1P9 zG9Yb<{rL(I7HSo+Qdw>>4EpH+#1cUK6y-Ksxs%ssEBf?kAq*9)810=Ud=cz&^EGYH z7}?dDMam)QpZdtdK1U8R;E2eOiUGx1yYY+F0>A$cdIH=Ma%^nU=U_IO0}O#Pgd26| z?`DTEatQ!+yVY1?Wogg~kb*^d7?4Z)#D0C^pEHUnK~wIeId@1S5blG0^ z0)le@BCG(k`#(ZvKb~LxUy-)@5dI8k#NUvs+|c)LUygSq?>FL)8Ugz_4OrQ45Qye2 zb?&QILgV?y!^I*FzdtV_ei4GhU55O-<@bOTd)s~bc!2%_lABR^xYl!Cz1bk&)GU0gyn`%~UjQVp<%;H7I+ANhQ!#Hu_7C_EDaRzDo} z=LZ7?;{l#p&x$>mOV2#`?Kt_Yg}nu^y34PjPY3|S2Oq6$aRhFqL+(f zfL8)jNwZE28LG2a+O?n{AQ`B{_ygd@O*tBt%Q|+KN489nJEp=x3)+w(7GDE=RCcMj zTnM-5rXMTKpVgfDh7Zs zs9*hcA_Y~pmkqIQ5Zw;0m;sb)=86n}-h0ctWR?z>N)@4A0MAR`{2Cya9oXXz4$}IY ze}MszcIbD66<~IFYy^(igyX z9Lj#cK7J5!Jm^jwglOKaE?M{8S*h`--y!%C!kHnw8i3c-UWf{` zdiL{@uz#EO9Cq>R%Tr@ju23czl3#{2h$oF9>t`Ep9RU{cz!SI^6haWcx5y3Qp_fc8 z8Ipuh5&EQ5CF3h%(|ZsLR{z;@Qx+3ZW+Mh zHUY%Gv?21;nRE97dNX8wO#*r$2$y`ENj4@9K{5`=%pVAw)#h7QZTc+NoH*9@oiv^^86NnS2hc(jbTGI4WfBnYZ_OUP1 zrwJVf?s$*(8~F_YM7?t`6Yt1z07&1xC`UQW;hrX6#=R9?i-3a;8*ns1vZ=7zq%S@Q zqDld!8@$r6)pZ2X^iPu4v(E=10DL{b2jylcwI`ZdALL0jE`U=I5W&Lu<2fmaa)#0u z6jJ~oFFOC>X)`qkdA~)43IcnRe5C>Up4O=dKQP!US|=0pztE)z}8lPuOR$ zysSS#1g(D|38ukNdgx~JK(D%P$AD~w5cuXad8bN>1>?-adlE&K`~Q`iT2QQ6!Wlvz zu3l|anBSj{!Op~imrXMP_Ui8Uos|T5e4pJ^NSI9W@w_P+nkONU7D`D+duydamCVX_ zHhwTdf$l9;^AKb+fH{*Jz~l|m(J~%Oenl%(;4}R1Tm)cNKDjAs{lLGBfQ^>wLbicK zwFAM;(D@Uj+OK&62mF<350QVo4tW=MAz72|5Zy2ue4H_~Ue> zCqVie7Vnp)5ium6xlW&PzV}a?k9klh3ZS>gOBC+@-B3UFn|Tu#87Wjp0JTma$nd0} z;3SP+ZQ+$YcJ(uj8wo`0k!311|C=EGsGkg!Gw3C_*Q+j&<08Qk37{Mq!y?Jew7(+o zcR&?R12%wP4GF)D8-COLfXz@zD6H-Q1E|W2um5*w?Z--H0dRcF3-Ds_e|L(@|Du5V zA0PYj|1S8yT8sbxU`+(=x58lW8XE(AI!QsDZ`@fnVqsJ@EdLIe~4!Rur0hbwJ|6J_Osy9jPO21S-sh#CysNa*af zguzA&ZdM$v6gMy|1!TJb2YwO#si;SgnIKuQ%0zK9;TzOOQp_fMpm{XZZ8=v}>QRvJ z%uhIINGEOkwD2utK(E)bE?_B;VYJhl&YJ4h0ot|F)v zuZ04j6$Z1gFwM3l{&}`5eWj8h>W-vQ`793B`zLZgt5wr31=vzSHxu06Ny_FK;Rk}= z`uDZW(#?uAPs#T_4)PaQx(=-$JOt4FSM#7wWlx)|vDlr)&tD;_1{MV>+7jO;VIP+X z)-f>Nd+P`_qfvR!EVEII@YGYv_2UKVyER&-EG2W-Yj()T&r9y@#tmsvuy*@n7}lg# zQj5U8f1!nfL{T5B&_)kiyv8HS2pnEHT!FSyN$SSn z-i1`4=7;Yu)tZY34ep6TTLL29+#i@fs)|)^syhT~d4iEW?3Mh~GYK|rnZ6%dr!fLq z`Vw0uJSq_*#cTG^4N?k(E)|BeXkoDUzB=VI?B*2d7;+ytJUpP*|V7I-Bd(1f0s8j{D zbO3;^zdsdu4K02S5&3^W*?{-<5z2A!+g`wI33y=Sl!DgAD5cF%4py`K%U*jkMpu?g|J-!TIHcf@s-z4&!^=?C!l z3;tVsSN;$6_x*<`X7DCkw#q)pzEhSl)~FdqLYAz9RMts_!l3MtH8Ex;LrC_ercxPe z#u8D(yDW_*%903uufCt}>FoP6c#dnVe zh4_2aAnI%X!$O0j+Vd2PzGD0H24w0K2$o8lKGoeWx5VFyC#h(e1m1S?ev?!b(gkM9rChQ~Lucitl-N7PsiUGH6wIHWiqruw+rWPVJt0UmAQuqt>iSr&{ zmnbi-7n?s%cq4=SJr*L5j+G$VUGbOO<>t)-?|DX{r>#$x&1V=0&x9`N8pqGCN;?n$ z>+jA&o!mtR_q)B|^EN9!$}da5IAJYdAxW8Tk>^714D=x45-_) zb#uA?;R+R5jgYlI#K!XJdY0MY);H6lA1_)b)Kv)m z=GAJhDG{O4wTZ2sV&i_S2+S=EkoM&SoM8MMw*S<-nMQK_dh1~g)Riiz+ff<6=k~f& zmx`QT;?)1Rj`Us`;T2q{Y#q!`o+CY3JQ938LzDdyi=@J%MicIO`f|)-#^qDn`Ngnf z6?^J`e)yDp_2PMc{aC`mdioCM;LduC1Tk&-`p+w`?XB!o4L8PLi1}5!<8M>w%QY=? z4*^T7_3C;Zom`)x8morKNh$w}^Gs®{*IHQ;a_`P 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 diff --git a/docs/CAD/design/mate-connectors/fem-sheet2.png b/docs/CAD/design/mate-connectors/fem-sheet2.png deleted file mode 100644 index 879b4295fb66582d7ad20bca17b69b4903f7ffd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26906 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

^jsJJutE!pK$OL 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 diff --git a/docs/CAD/design/mate-connectors/female.stl b/docs/CAD/design/mate-connectors/female.stl deleted file mode 100644 index 57b0ef6d20..0000000000 --- a/docs/CAD/design/mate-connectors/female.stl +++ /dev/null @@ -1,226 +0,0 @@ -solid OpenSCAD_Model - facet normal 1 -0 0 - outer loop - vertex 15 -9 0 - vertex 15 9 -8 - vertex 15 9 0 - endloop - endfacet - facet normal 1 0 0 - outer loop - vertex 15 9 -8 - vertex 15 -9 0 - vertex 15 -9 -8 - endloop - endfacet - facet normal 0 0 1 - outer loop - vertex 15 9 0 - vertex 5.3246 1.63218 0 - vertex 15 -9 0 - endloop - endfacet - facet normal 0 0 1 - outer loop - vertex 15 9 0 - vertex -4.79494 3.42759 0 - vertex 5.3246 1.63218 0 - endloop - endfacet - facet normal 0 0 1 - outer loop - vertex 15 9 0 - vertex -5.97725 3.87059 0 - vertex -4.79494 3.42759 0 - endloop - endfacet - facet normal 0 0 1 - outer loop - vertex -5.97725 3.87059 0 - vertex -15 9 0 - vertex -5.97725 -3.87059 0 - endloop - endfacet - facet normal -0 0 1 - outer loop - vertex -15 9 0 - vertex -5.97725 3.87059 0 - vertex 15 9 0 - endloop - endfacet - facet normal -0 0 1 - outer loop - vertex 5.3246 -1.63218 0 - vertex 15 -9 0 - vertex 5.3246 1.63218 0 - endloop - endfacet - facet normal -0 0 1 - outer loop - vertex -4.79494 -3.42759 0 - vertex 15 -9 0 - vertex 5.3246 -1.63218 0 - endloop - endfacet - facet normal -0 0 1 - outer loop - vertex -5.97725 -3.87059 0 - vertex 15 -9 0 - vertex -4.79494 -3.42759 0 - endloop - endfacet - facet normal 0 0 1 - outer loop - vertex -5.97725 -3.87059 0 - vertex -15 -9 0 - vertex 15 -9 0 - endloop - endfacet - facet normal 0 0 1 - outer loop - vertex -15 -9 0 - vertex -5.97725 -3.87059 0 - vertex -15 9 0 - endloop - endfacet - facet normal 0 0 -1 - outer loop - vertex -15 -9 -8 - vertex 15 9 -8 - vertex 15 -9 -8 - endloop - endfacet - facet normal -0 0 -1 - outer loop - vertex 15 9 -8 - vertex -15 -9 -8 - vertex -15 9 -8 - endloop - endfacet - facet normal -1 0 0 - outer loop - vertex -15 -9 -8 - vertex -15 9 0 - vertex -15 9 -8 - endloop - endfacet - facet normal -1 -0 0 - outer loop - vertex -15 9 0 - vertex -15 -9 -8 - vertex -15 -9 0 - endloop - endfacet - facet normal 0 1 -0 - outer loop - vertex 15 9 -8 - vertex -15 9 0 - vertex 15 9 0 - endloop - endfacet - facet normal 0 1 0 - outer loop - vertex -15 9 0 - vertex 15 9 -8 - vertex -15 9 -8 - endloop - endfacet - facet normal 0 -1 0 - outer loop - vertex -15 -9 -8 - vertex 15 -9 0 - vertex -15 -9 0 - endloop - endfacet - facet normal 0 -1 -0 - outer loop - vertex 15 -9 0 - vertex -15 -9 -8 - vertex 15 -9 -8 - endloop - endfacet - facet normal 0 0 1 - outer loop - vertex -6.2 4.2 -0.4 - vertex 6.2 -2 -0.4 - vertex 6.2 2 -0.4 - endloop - endfacet - facet normal 0 0 1 - outer loop - vertex 6.2 -2 -0.4 - vertex -6.2 4.2 -0.4 - vertex -6.2 -4.2 -0.4 - endloop - endfacet - facet normal 0.873667 0 -0.486524 - outer loop - vertex -5.97725 -3.87059 0 - vertex -6.2 4.2 -0.4 - vertex -5.97725 3.87059 0 - endloop - endfacet - facet normal 0.873667 0 -0.486524 - outer loop - vertex -6.2 4.2 -0.4 - vertex -5.97725 -3.87059 0 - vertex -6.2 -4.2 -0.4 - endloop - endfacet - facet normal -0.107146 0.603912 -0.789816 - outer loop - vertex 6.2 -2 -0.4 - vertex -4.79494 -3.42759 0 - vertex 5.3246 -1.63218 0 - endloop - endfacet - facet normal -0.107147 0.603918 -0.789812 - outer loop - vertex -4.79494 -3.42759 0 - vertex 6.2 -2 -0.4 - vertex -6.2 -4.2 -0.4 - endloop - endfacet - facet normal -0.304068 0.811519 -0.498978 - outer loop - vertex -4.79494 -3.42759 0 - vertex -6.2 -4.2 -0.4 - vertex -5.97725 -3.87059 0 - endloop - endfacet - facet normal -0.304068 -0.811519 -0.498978 - outer loop - vertex -5.97725 3.87059 0 - vertex -6.2 4.2 -0.4 - vertex -4.79494 3.42759 0 - endloop - endfacet - facet normal -0.107146 -0.603912 -0.789816 - outer loop - vertex -4.79494 3.42759 0 - vertex 6.2 2 -0.4 - vertex 5.3246 1.63218 0 - endloop - endfacet - facet normal -0.107147 -0.603918 -0.789812 - outer loop - vertex 6.2 2 -0.4 - vertex -4.79494 3.42759 0 - vertex -6.2 4.2 -0.4 - endloop - endfacet - facet normal -0.415603 0 -0.909546 - outer loop - vertex 5.3246 -1.63218 0 - vertex 6.2 2 -0.4 - vertex 6.2 -2 -0.4 - endloop - endfacet - facet normal -0.415603 0 -0.909546 - outer loop - vertex 6.2 2 -0.4 - vertex 5.3246 -1.63218 0 - vertex 5.3246 1.63218 0 - endloop - endfacet -endsolid OpenSCAD_Model diff --git a/docs/CAD/design/mate-connectors/female_only.scad b/docs/CAD/design/mate-connectors/female_only.scad deleted file mode 100644 index 89f83b4655..0000000000 --- a/docs/CAD/design/mate-connectors/female_only.scad +++ /dev/null @@ -1,12 +0,0 @@ -// Female half alone, for the legibility test: is a recessed faceted pocket readable in a -// shaded view, or does a concave feature just read as a dark hole with no orientation? -use - -// The plate must be THICKER than the key is tall, or the "pocket" is a through-hole. The -// first version used 3 mm against a 4.5 mm key and cut straight through — caught only by -// rendering it. Minimum stock = H + clearance + pocket depth + a wall to print against. -PLATE = [30, 18, 8]; -difference() { - translate([-PLATE[0]/2, -PLATE[1]/2, -PLATE[2]]) cube(PLATE); - ridge_key_female(); -} diff --git a/docs/CAD/design/mate-connectors/fit_check.py b/docs/CAD/design/mate-connectors/fit_check.py deleted file mode 100644 index 5a9ee01063..0000000000 --- a/docs/CAD/design/mate-connectors/fit_check.py +++ /dev/null @@ -1,20 +0,0 @@ -# Measure the assembled fit between the supplied male and the generated female. -# This is the number that matters: the minimum gap in the seated position. -# Run: /snap/bin/freecad.cmd fit_check.py -import os -import Part - -HERE = os.path.dirname(os.path.abspath(__file__)) -male = Part.Shape(); male.read(os.path.join(HERE, "bear.step")) -fem = Part.Shape(); fem.read(os.path.join(HERE, "BearConnector_Female.step")) -male, fem = male.Solids[0], fem.Solids[0] - -d = male.distToShape(fem) -print(f"RESULT minimum gap male<->female, seated: {d[0]:.4f} mm (design clearance 0.20)") - -c = male.common(fem) -print(f"RESULT interference volume: {(c.Volume if c.Solids else 0.0):.6f} mm3") - -p = d[1][0][0] -print(f"RESULT tightest point on the male: ({p.x:.2f}, {p.y:.2f}, {p.z:.2f})") -print(f"RESULT male {male.Volume/1000:.2f} cm3 / female {fem.Volume/1000:.2f} cm3") diff --git a/docs/CAD/design/mate-connectors/glyph-preview.png b/docs/CAD/design/mate-connectors/glyph-preview.png deleted file mode 100644 index 5e62c9a17f30ea871711d5e867b27cf48311bfbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12890 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 diff --git a/docs/CAD/design/mate-connectors/trim_female.py b/docs/CAD/design/mate-connectors/trim_female.py deleted file mode 100644 index 2753e4a3a8..0000000000 --- a/docs/CAD/design/mate-connectors/trim_female.py +++ /dev/null @@ -1,74 +0,0 @@ -# Trim the boxy frame off the female so its outer shape is the bear face itself. -# -# Method: take the male's flat back face (the plane Y=0 -- that face IS the bear silhouette), -# offset its OUTER wire outward in 2D, extrude the result along the insertion axis, and keep only -# the part of the female inside it. Everything outside is the block frame and goes away. -# -# NOTE ON THE NUMBER. The pocket's side walls stand at +0.20 mm from the male outline, because that -# is the clearance. A trim boundary at +0.10 mm therefore falls INSIDE them by 0.10 mm and removes -# the side wall entirely rather than leaving a thin one. The script runs the requested value and -# then measures what is actually left, so the outcome is a number rather than an opinion; it also -# emits a second variant at an offset that leaves a printable wall, for comparison. -# -# Run: /snap/bin/freecad.cmd trim_female.py - -import os, sys -import FreeCAD as App -import Part -from FreeCAD import Vector - -HERE = os.path.dirname(os.path.abspath(__file__)) -MALE = os.path.join(HERE, "bear.step") -FEMALE = os.path.join(HERE, "BearConnector_Female.step") - -REQUESTED = 0.10 # as asked -CLEARANCE = 0.20 # what the pocket was built with -SAFE_WALL = 1.60 # a wall that survives an FDM nozzle: clearance + ~1.4 mm - -male = Part.Shape(); male.read(MALE); male = male.Solids[0] -fem = Part.Shape(); fem.read(FEMALE); fem = fem.Solids[0] -print(f"female in : {fem.Volume/1000:.2f} cm3, {len(fem.Faces)} faces") - -# --- the bear silhouette: the male's flat back face at Y = 0 -back = None -for f in male.Faces: - n = f.normalAt(0, 0) - if abs(f.CenterOfMass.y) < 1e-6 and abs(abs(n.y) - 1.0) < 1e-6: - if back is None or f.Area > back.Area: - back = f -if back is None: - print("FAIL: could not find the flat back face at Y=0"); sys.exit(1) -print(f"silhouette: back face area {back.Area:.1f} mm2, {len(back.Wires)} wires " - f"(outer + {len(back.Wires)-1} holes: eyes and mouth)") - -fb = fem.BoundBox -y0, y1 = fb.YMin - 5.0, fb.YMax + 5.0 - -def trimmed(offset): - """keep only the part of the female inside the silhouette grown by `offset`""" - wire = back.OuterWire - grown = wire.makeOffset2D(offset, join=2, fill=False, openResult=False, intersection=True) - face = Part.Face(Part.Wire(grown.Edges)) - prism = face.extrude(Vector(0, y1 - y0, 0)) - prism.translate(Vector(0, y0 - face.CenterOfMass.y, 0)) - return fem.common(prism) - -for tag, off, out in (("requested", REQUESTED, "BearConnector_Female_Trimmed.step"), - ("safe wall", SAFE_WALL, "BearConnector_Female_Trimmed_wall.step")): - r = trimmed(off) - if not r.Solids: - print(f"\n{tag} (+{off:.2f} mm): NOTHING LEFT"); continue - wall = off - CLEARANCE - # is there any material left at the level of the pocket's side wall? - sec = r.section(Part.makePlane(400, 400, Vector(-200, 1.5, -200), Vector(0, 1, 0))) - perim = sum(e.Length for e in sec.Edges) - print(f"\n{tag} (+{off:.2f} mm) wall = {wall:+.2f} mm") - print(f" volume {r.Volume/1000:.2f} cm3, {len(r.Solids)} solid(s), {len(r.Faces)} faces") - print(f" section through the pocket wall at Y=1.5: {perim:.1f} mm of edge") - if wall <= 0: - print(f" -> the trim cuts {abs(wall):.2f} mm INSIDE the pocket wall: no side wall remains") - doc = App.newDocument(tag.replace(" ", "_")) - o = doc.addObject("Part::Feature", "Female") - o.Shape = r; doc.recompute() - Part.export([o], os.path.join(HERE, out)) - print(f" wrote {out}") diff --git a/docs/CAD/design/mate-connectors/verify_trimmed.py b/docs/CAD/design/mate-connectors/verify_trimmed.py deleted file mode 100644 index 81f6f3fea1..0000000000 --- a/docs/CAD/design/mate-connectors/verify_trimmed.py +++ /dev/null @@ -1,25 +0,0 @@ -# Check both trimmed females still fit the male, and export STLs for a visual comparison. -# Run: /snap/bin/freecad.cmd verify_trimmed.py -import os -import Mesh, Part - -HERE = os.path.dirname(os.path.abspath(__file__)) -male = Part.Shape(); male.read(os.path.join(HERE, "bear.step")); male = male.Solids[0] - -for name in ("BearConnector_Female_Trimmed", "BearConnector_Female_Trimmed_wall"): - p = os.path.join(HERE, name + ".step") - s = Part.Shape(); s.read(p); s = s.Solids[0] - d = male.distToShape(s)[0] - c = male.common(s) - cv = c.Volume if c.Solids else 0.0 - bb = s.BoundBox - print(f"{name}") - print(f" {bb.XLength:.2f} x {bb.YLength:.2f} x {bb.ZLength:.2f} mm, {s.Volume/1000:.2f} cm3, " - f"{len(s.Faces)} faces, valid={s.isValid()}") - print(f" gap to male {d:.4f} mm, interference {cv:.6f} mm3") - m = Mesh.Mesh(); m.addFacets([tuple(t) for t in s.tessellate(0.12)[1]] and - [(s.tessellate(0.12)[0][a], s.tessellate(0.12)[0][b], - s.tessellate(0.12)[0][c2]) - for a, b, c2 in s.tessellate(0.12)[1]]) - m.write(os.path.join(HERE, name + ".stl")) - print(f" wrote {name}.stl ({m.CountFacets} facets)") diff --git a/docs/CAD/design_tab.md b/docs/CAD/design_tab.md deleted file mode 100644 index 1be0a2ea33..0000000000 --- a/docs/CAD/design_tab.md +++ /dev/null @@ -1,333 +0,0 @@ -# The Design tab - -Object-driven parametric CAD inside the slicer. Point at geometry; the geometry offers the -verbs that apply to it. Selection comes first and the tool consumes it. Draw a sketch, -constrain it, turn it into a solid, refine it, and send it straight to Prepare — without -leaving for another application and coming back through an STL. - -The model is a **recipe**, not a mesh. Every action becomes a feature in a tree that is -replayed from the start whenever anything changes, so editing a dimension you set twenty -steps ago rebuilds everything downstream. The geometry kernel is OCCT, which the slicer -already ships for STEP import. - ---- - -## Getting started - -1. Open the **Design** tab. -2. Click a face or a reference plane in the viewport, then press `Shift+S` (Sketch). The offer - opens with the sketch tools on it. -3. Draw a closed profile, then press **✓ Confirm** in the floating action bar. -4. With the sketch selected, press `Shift+E` (Extrude). -5. Press **Commit to Plate** to hand the solid to Prepare. - -The status line under the toolbar is the thing to watch: it says what the current tool is -waiting for. When no plane is picked it reads *"Click a face or a reference plane in the -viewport, then a sketch tool"*; once one is picked it reads *"Sketching on — pick a -tool"*. It is also where a refusal explains itself. - ---- - -## Selecting - -- One left-click selects what is under the cursor. There is no click-cycling through - face → edge → body. -- A click near a corner takes the corner, not the face behind it. -- Left-drag sweeps a rubber band, and a rubber band takes the whole body. -- An open sketch line can be clicked, even where it bounds a region. -- Double-click a sketch stroke to edit it — the gesture belongs on the geometry. -- Editing a dimension's value **updates** that dimension instead of adding a second one next - to it. -- The floating chrome that belongs to a sketch leaves with the sketch when it ends. -- Sketching happens on the face you clicked, first click. -- A sketch whose entities form no wire **fails** instead of extruding a default box. A - subtraction that removes nothing is reported as an error instead of a silent success. - ---- - -## The offer - -Right-click on the geometry, released without moving the mouse (an 8 px budget — a -right-drag that orbits the camera does not open it). Left-click still only selects, so -pointing at things stays quiet. - -The offer also opens by itself the moment you press Sketch on a face or plane, showing the -sketch tools — the app hands you the tools directly. - -**Eight families, always in this fixed order:** Create, Add material, Remove, Dress-up, -Repeat, Transform, Reference, Modify. - -- A family with at least one applicable verb shows it. Several applicable verbs collapse - into a submenu under the family name. -- A family with nothing applicable is **shown greyed in place, with the reason** — e.g. - *"Create — Click a face or a reference plane in the viewport, then a sketch tool"*. It is - not hidden. A control that cannot be used still says what it is and what you would have to - do first. -- Inside a sketch the offer shows the sketch verbs; outside it shows the feature verbs. - -**Document-level actions never enter the offer**, because they act on the document and not -on a selection: Import STEP, Import mesh, Text, SVG, Export STEP, Commit to Plate, Undo, -Redo, Variables, Section view, Origin planes, World axes. They live in the toolbar. - ---- - -## Keyboard - -Single letters drive sketch tools **while a sketch is open**; Shift+letter drives feature -tools and single letters drive view toggles **when no sketch is open**. The two maps are -selected by the mode, not by whether a sketch session is running. - -### Sketch (while a sketch is open) - -| Key | Tool | -|---|---| -| `L` | Line — click start, then end | -| `R` | Rectangle — click two opposite corners | -| `C` | Circle — click centre, then radius | -| `A` | Arc — click start, end, then a point | -| `S` | Slot — two centreline ends, then width | -| `E` | Ellipse — centre, major end, minor point | -| `B` | Spline — click control points | -| `P` | Point — click to place | -| `G` | Polygon — click centre, then a vertex | -| `D` | Dimension — click 2 points or an entity | -| `T` | Trim — click a segment to trim it | -| `X` | Extend — click a line/arc to extend it | -| `O` | Offset — pick an entity, drag the distance | -| `M` | Mirror — pick axis, then entities | -| `F` | Fillet — pick two lines, set the radius | -| `H` | Chamfer — pick two lines, set the distance | -| `K` | Constrain — finish the live sketch and enter constrain | -| `Q` | Construction toggle — draw the next entity as construction geometry | -| `Del` | Delete the selected sketch entity | -| `Esc` | Cancel the live tool | - -### Feature (when no sketch is open) - -| Key | Tool | -|---|---| -| `Shift+S` | Sketch | -| `Shift+E` | Extrude — extrude a profile, or push/pull a picked face | -| `Shift+R` | Revolve | -| `Shift+W` | Sweep | -| `Shift+L` | Loft | -| `Shift+N` | Pattern | -| `Shift+G` | Surface Extrude | -| `Shift+J` | Surface Revolve | -| `Shift+O` | Surface Loft | -| `Shift+Q` | Surface Fill | -| `Shift+U` | Surface Offset | -| `Shift+V` | Thicken Surface | -| `Shift+P` | Plane | -| `Shift+A` | Axis | -| `Shift+C` | Coord Sys | -| `Shift+Y` | Transform | -| `Shift+Z` | Mirror | -| `Shift+B` | Boolean | -| `Shift+X` | Cut | -| `Shift+F` | Fillet / Chamfer | -| `Shift+D` | Draft | -| `Shift+K` | Shell | -| `Shift+H` | Hole | -| `Shift+T` | Thread | -| `Shift+I` | Import STEP | -| `Shift+M` | Import mesh | - -### View toggles (single letters, when no sketch is open) - -| Key | Action | -|---|---| -| `Home` | Axonometric view, fitted to the model | -| `P` | Origin planes on/off | -| `A` | World axes on/off | -| `X` | Section view on/off | - -While the section is on: `PageUp` / `PageDown` move the cut plane, `F` flips which half is -kept. With no section on, `F` is Place on Face — lay the picked face flat on the bed. - ---- - -## Sketching - -A sketch is a closed (or open) 2D profile on a plane or on a flat face of an existing body. -Press `Shift+S`, click the face or plane you want to sketch on, and draw. The toolbar and -the offer both carry the sketch tools. - -**Entities:** line, polyline, rectangle (corner / centre / oblique / rounded), circle -(centre-radius / 2-point / 3-point), arc (centre-point / 3-point / tangent), ellipse and -elliptical arc, polygon (inscribed / circumscribed), slot (straight / arc), spline, point, -and text. - -**Editing:** move, rotate, scale, trim, extend, offset, mirror, and linear or polar arrays. - -**Constraints:** coincident, horizontal, vertical, parallel, perpendicular, tangent, equal, -concentric, midpoint, symmetric, fix, plus dimensional radius, diameter, distance and angle. -The solver reports the remaining degrees of freedom and tells you when a sketch is fully -constrained — or when a constraint conflicts with one already there. - -Sketches stay editable. Selecting one in the feature tree reopens it with its dimensions -live. - ---- - -## Building solids - -Grouped in the toolbar by what they do, one concept per drawer. - -### Add material -| Tool | Shortcut | What it does | -|---|---|---| -| Extrude | `Shift+E` | Extrude a profile, or push/pull a face already on a body | -| Revolve | `Shift+R` | Revolve a profile about an axis | -| Sweep | `Shift+W` | Sweep a profile along a path — including a helix, for springs and augers | -| Loft | `Shift+L` | Skin between two or more profiles | -| Thicken | — | Offset a solid face into a thin plate as a new body | -| Rib | — | Grow a stiffening wall from an open sketch line, fused to a body | - -Extrude offers blind, symmetric, two-sided, through-all and up-to-face end conditions, plus -a draft angle on the side wall, and can add, subtract, intersect or start a new body. - -### Surface -Sheet bodies — surfaces with no thickness — for shapes that are easier to build as skins and -solidify afterwards. - -| Tool | Shortcut | -|---|---| -| Surface Extrude | `Shift+G` | -| Surface Revolve | `Shift+J` | -| Surface Loft | `Shift+O` | -| Surface Fill | `Shift+Q` | -| Surface Offset | `Shift+U` | -| Thicken Surface | `Shift+V` | - -Thicken Surface is how a sheet becomes a printable solid. - -### Dress-up -| Tool | Shortcut | -|---|---| -| Fillet / Chamfer | `Shift+F` | -| Draft (taper a face) | `Shift+D` | -| Shell | `Shift+K` | -| Delete Face | — | - -Delete Face removes faces and heals the solid — useful for stripping a feature off an -imported part. - -### Holes -**Hole** (`Shift+H`) drills simple, counterbored or countersunk holes, with an ISO/ANSI -standards table so you can ask for an M6 clearance hole instead of computing a diameter. -**Thread** (`Shift+T`) cuts a real helical thread into a bore or onto a shaft. - -### Placement -Operations that move a body without changing its shape: **Transform** (`Shift+Y`), -**Mirror** (`Shift+Z`), and **Mate** for assemblies. - -### Combining -**Boolean** (`Shift+B`) unions, subtracts or intersects two bodies. **Cut** (`Shift+X`) -splits a body with a plane. **Pattern** (`Shift+N`) repeats a body linearly, in a circle, or -along a curve. - ---- - -## Reference geometry - -Datum features carry no material; they exist to give later features something to attach to. - -- **Plane** (`Shift+P`) — offset, tilted, midplane, tangent, through two edges, or coincident -- **Axis** (`Shift+A`) — two points, a face normal, a cylinder centreline, the intersection of - two planes, or along an edge -- **Coord Sys** (`Shift+C`) — a full frame, from a world point or from a face plus a - direction edge -- **Helix** — a helical curve to sweep along -- **Project** — project a body's edges onto a plane as sketch geometry - -On the Coord Sys tool, picking a direction **edge** is worth the extra click: without one the -frame takes its X from the face's first edge, which is deterministic but not necessarily the -direction you meant. - ---- - -## Assemblies - -**Mate** aligns two coordinate systems and moves one body onto the other. Five kinds: - -| Kind | Leaves free | -|---|---| -| Fastened | nothing — 6 DOF locked | -| Planar | sliding in the plane | -| Revolute | rotation about the axis | -| Slider | sliding along the axis | -| Cylindrical | rotation *and* sliding | - -**Check interference** reports every overlapping pair of solids with the overlapping volume, -so a clash is a number rather than an impression. Bodies that merely touch enclose no volume -and are not reported. - ---- - -## Variables and expressions - -Define named variables and drive dimensions from them. Any numeric field accepts an -expression — `width/2`, `wall*3` — and everything re-evaluates on recompute. Change one -variable and the whole model follows. - ---- - -## Import and export - -**Import STEP** brings in a real B-rep solid, not a mesh: its faces and edges can be filleted, -shelled and cut like anything modelled here. - -**Import mesh** (STL/OBJ) converts triangles to a B-rep body and tells you honestly what it -got — whether the result is a closed solid or an open shell, with the boundary and -non-manifold edge counts. A large mesh becomes a large number of faces, which is slow to -edit; the importer warns before you commit to it. - -**Export STEP** writes the model out for another CAD tool. - -**Commit to Plate** sends the solid to Prepare for slicing. The whole feature recipe is saved -inside the 3MF, so reopening the project restores the editable model rather than a frozen -mesh. - ---- - -## View controls - -**Section view** (`X`) hides half the model so you can see inside — `PageUp`/`PageDown` move -the plane, `F` flips which half is kept. **Place on Face** (`F`, when section is off) lays a -picked face flat on the bed. Origin planes (`P`) and world axes (`A`) can be toggled on while -you orient yourself. - ---- - -## Known limitations - -Being straight about the edges, so nobody discovers them the hard way: - -- **Rib** needs a sketch containing an explicit open line. A parametric rectangle sketch - carries no individual entities, so Rib cannot use one. -- **Surface Loft** and **Surface Fill** have kernel tests but have not been exercised by hand. -- Card wiring for 9 of the 16 late-wired tools has never been click-tested. -- Mate resolves by composing transforms directly. There is no 3D assembly solver, so mates - are applied in order rather than solved simultaneously, and mate limits are not implemented. -- Move-face and replace-face are not implemented — OCCT offers no clean primitive for them. -- There is no automated GUI test in CI. Every behaviour above is traced to code and to a - hand pass, not to a synthetic click. - ---- - -## Where the code lives - -| Path | Role | -|---|---| -| `src/libslic3r/CAD/CadDocument.*` | the feature recipe and its replay | -| `src/libslic3r/CAD/GeometryEngine.*` | OCCT wrapper — faces, edges, booleans, healing | -| `src/libslic3r/CAD/SketchEngine.*` | profile → wire → solid | -| `src/libslic3r/CAD/SketchSolver.*` | constraint solving, over the vendored solver | -| `src/libslic3r/slvs/` | vendored 2D constraint solver (GPLv3) | -| `src/slic3r/GUI/CAD/DesignPanel.*` | the tab: toolbar, cards, feature tree | -| `src/slic3r/GUI/CAD/DesignCanvas.*` | viewport integration | -| `src/slic3r/GUI/CAD/DesignSketchTool.*` | in-canvas sketching | - -Build with `-DSLIC3R_CAD=ON` (the default). With it OFF the tab is not compiled and the deps -prefix matches upstream exactly — see [cad_dependency_weight.md](cad_dependency_weight.md). diff --git a/docs/CAD/design_tab_pr_description.md b/docs/CAD/design_tab_pr_description.md deleted file mode 100644 index a6d67f0e7c..0000000000 --- a/docs/CAD/design_tab_pr_description.md +++ /dev/null @@ -1,73 +0,0 @@ -# Design (CAD) tab — upstream pull request - -## What this adds - -A sketch-first parametric CAD tab inside the slicer. The workflow is direct: -sketch → constrain → solid features → commit to plate. The whole feature recipe is -persisted inside the 3MF, so reopening restores an editable model rather than a frozen mesh. - -- Kernel: OCCT, which upstream already links for STEP import — see - [cad_dependency_weight.md](docs/cad_dependency_weight.md) -- Constraint solver: vendored SolveSpace `libslvs` subset -- Interaction model: object-driven — point at geometry, the geometry offers the verbs that - apply to it; see [cad_ux_guidelines.md](docs/cad_ux_guidelines.md) -- Full user-facing documentation: [design_tab.md](docs/design_tab.md) - -## Why it belongs in the slicer - -Every round trip through an external CAD tool costs a file export, a re-import, and the -design intent that both steps discard. A part modified after slicing should return to its -feature history, not to a mesh. Keeping the CAD model inside the slicer preserves that -loop — the nozzle diameter, the build volume and the material are known at design time. - -For the integration case in full: [design_tab_upstream_portability.md](docs/design_tab_upstream_portability.md). - -## How it is built - -The `SLIC3R_CAD` CMake flag (default ON) gates the entire tab. With it OFF the tab is not -compiled and the deps prefix matches upstream exactly — the dependency diff is one line in -OCCT's CMake: `BUILD_MODULE_ModelingAlgorithms=OFF → ON`. - -Measured cost table: [cad_dependency_weight.md](docs/cad_dependency_weight.md). - -## Diff shape - - - -Against merge-base `d6cb667b894f`: - - 306 files changed, 83032 insertions(+), 777 deletions(-) - -350 commits, of which 284 are new files and 37 modify upstream files. 99.3 % of the diff -is new code. The negotiable surface is the 37 modified files. - -## Tests - -205 `TEST_CASE` blocks across 6 new test source files. This counts assertions written, not -assertions passed — a run needs a build. - -`scripts/CAD/run-kernel-tests.sh` is the headless verification contract: it builds only -`libslic3r_tests` (not the GUI app), needs no display, and exit 0 means the CAD suite -passed. It now runs with **no exclusions** — both cases that used to be quarantined (the -circle-line tangency solver abort and the internal-thread reference) are fixed. - -## Licensing - -The vendored solver in `src/libslic3r/slvs/` is **GPL-3.0** (see `src/libslic3r/slvs/LICENSE`), -not LGPL. The combined work is distributable under AGPL-3.0. See the Licensing section of -[design_tab_upstream_portability.md](docs/design_tab_upstream_portability.md) for the -AGPLv3/GPLv3 compatibility argument; this point should be confirmed with upstream explicitly. - -## Not verified - -- Card wiring for 9 of the 16 late-wired tools was never click-tested. -- There is no automated GUI test in CI. A green kernel run says nothing about the GUI — - synthetic clicks never drift, so the test suite and the viewport are two separate realities. -- The click-test defect rate has **not converged**: a second pass found no new defects, but - four further days of work found five more. The earlier pass is not evidence of stability. - -## Reviewer's map - -See the [Where the code lives](docs/design_tab.md#where-the-code-lives) table in the user -doc for the file-to-role mapping, and [docs/ux/tool_atlas.json](docs/ux/tool_atlas.json) as -the generated-from source of `src/slic3r/GUI/CAD/DesignOffer.hpp`. diff --git a/docs/CAD/design_tab_upstream_portability.md b/docs/CAD/design_tab_upstream_portability.md deleted file mode 100644 index a7e9cd84ed..0000000000 --- a/docs/CAD/design_tab_upstream_portability.md +++ /dev/null @@ -1,162 +0,0 @@ -# Design (CAD) tab — upstream integration brief - -**Question:** can the Design tab (sketch-first parametric CAD: sketch → constrain → -extrude/revolve/fillet/hole/thread/shell, multi-body, undo, 3MF persistence) land in -mainline OrcaSlicer? - -**Answer: yes, and the ask is far smaller than previously believed.** OCCT is *already* -an OrcaSlicer dependency. We are not asking upstream to adopt a new library; we are -asking it to widen one it already builds, at a measured cost of **3.77 MiB on Windows**. - -> ### Corrections to the 2026-06-21 assessment -> That revision was written before the persistence work landed and got two load-bearing -> facts wrong. Both are corrected here from direct measurement of the branch: -> -> 1. **"The real blocker: OCCT … a dependency mainline OrcaSlicer has never carried."** -> **False.** `deps/OCCT/` exists at the merge-base and upstream links it from -> `Format/STEP.cpp`, `Format/svg.cpp`, and `Shape/TextShape.cpp`. Our entire -> dependency diff is **one line**: `BUILD_MODULE_ModelingAlgorithms=OFF → ON`. -> 2. **"vendored SolveSpace solver … LGPL."** **False.** `src/libslic3r/slvs/LICENSE` is -> **GPL-3.0**, not LGPL. This is fine (see Licensing) but must not be misstated. -> -> It also claimed "no changes to Model" — no longer true; 3MF recipe persistence adds one -> `std::string` to `Model`. - -## Measured shape of the change - -Against merge-base `449a4cf9fc` (34 commits ahead): - -| | files | lines | -|---|---:|---:| -| **New files** | 138 | +61,720 | -| **Modified upstream files** | 23 | +457 / −75 | -| **Deleted upstream files** | 0 | — | - -The 62 kLOC headline is inflated by localization. The feature itself: - -| area | LOC | files | -|---|---:|---:| -| kernel (`src/libslic3r/`) | 15,828 | 37 | -| GUI (`src/slic3r/`) | 19,544 | 14 | -| tests (Catch2) | 2,567 | 6 | -| i18n (unrelated; strip from the CAD PR) | 23,438 | 77 | - -**99.3 % of the diff is new files.** The negotiable surface is 457 added lines across 23 -files, and nothing upstream is deleted. The largest single hook is `GLCanvas3D.cpp` -(+110/−2): an `m_design_sketch_tool` member plus render/mouse/key hooks, **every one -already null-guarded** — which is why the compile-time gate below is cheap. - -No changes to the slicing pipeline (Print/PrintObject/Layer/GCode), Tab, or the -printer-profile/config system. - -## The dependency ask, precisely - -Not "adopt OCCT" — **widen the existing OCCT build**: - -```diff -- -DBUILD_MODULE_ModelingAlgorithms=OFF -+ -DBUILD_MODULE_ModelingAlgorithms=ON -``` - -Cost, measured from the shipped Windows artifact (42 OCCT DLLs, 45.43 MiB total): - -| toolkit | size | note | -|---|---:|---| -| `TKFillet.dll` | 2.02 MiB | only exists with the flag ON | -| `TKOffset.dll` | 1.75 MiB | only exists with the flag ON | -| **delta** | **3.77 MiB** | Windows only (OCCT is Shared on Win, Static elsewhere) | - -`TKBool` is *not* part of the delta — upstream's `DataExchange` already pulls it in -transitively. On macOS/Linux OCCT links statically, so the cost is only the code actually -referenced, not a 3.77 MiB floor. - -**Unmeasured, and we should measure before the call:** clean-deps build-time delta with -the flag ON vs OFF, and the resulting CI runner-minute cost. Do not guess these at him. - -## Licensing - -- Vendored solver `src/libslic3r/slvs/` — **GPL-3.0**, 9,339 LOC, © Jonathan Westhues, - a self-contained subset of SolveSpace (`libslvs`). No external dependencies. -- OrcaSlicer — **AGPL-3.0** (`LICENSE.txt`). - -GPLv3 §13 expressly permits combining a GPLv3 work with an AGPLv3 work; AGPLv3 §13 grants -the converse. The combined work is distributable under AGPL-3.0 with the solver's GPLv3 -terms preserved. This is a favourable direction (GPLv3 → into an AGPLv3 project), but it -is a point to **confirm explicitly with upstream**, not to assert unilaterally. - -Open question for SoftFever: keep the solver **vendored** (current: pinned, no submodule, -no external build) or move it to `deps/` as a fetched external? Vendoring costs us -upstream-sync burden; `deps/` costs build complexity. - -## The one irreversible decision: the 3MF format - -Persistence adds an **optional** archive entry and one field: - -```cpp -// Model.hpp -std::string cad_recipe; // empty for non-CAD projects -``` - -``` -Metadata/orca_cad.bin // written only when cad_recipe is non-empty -``` - -Readers that do not know the entry ignore it; writers skip it entirely when empty. So -existing projects are bit-identical and old readers are unaffected. Good. - -**But the moment upstream ships this, it owns forward-compatibility forever.** Three -things should be settled *before* the first release, because none can be changed after: - -1. **Name.** Renamed to `Metadata/orca_cad.bin`. -2. **Encoding.** The recipe is an opaque **cereal `PortableBinaryArchive`** blob whose - layout is the field order of `CadFeature::serialize`. Portable across endianness and - word size — *not* across a field reorder. Append-only is currently a convention held by - discipline, not by any check. -3. **Embedded BRep.** `Import` features embed OCCT's ASCII BRep for the imported solid, - which couples saved project files to an OCCT BRep revision. Alternative: re-import from - the source STEP and store only a reference. Worth deciding deliberately. - -**Concrete gap we should close before the call.** `test_caddocument.cpp` covers the -in-memory round-trip and correctly refuses a version-999 blob — but there is **no -checked-in v1 fixture on disk**. A reordered field in `CadFeature::serialize` would pass -the entire suite while silently breaking every previously-saved project. Ship a golden -`.bin` fixture generated today plus a test that loads it; that is the only thing that will -hold the format still once real users have files. - -## Proposed PR decomposition - -35 kLOC in one PR is not reviewable. Behind the flag, slices 1–4 are behaviour-neutral for -existing users: - -1. **Build gate + OCCT flag + Windows packaging guard.** `-DSLIC3R_CAD=ON/OFF`, default - **OFF**. Flips `ModelingAlgorithms=ON`. Includes the guard that asserts every linked - OCCT toolkit has a shipped DLL (already on both forks: `546cef5f42`). ← *this is what - makes SoftFever's "parallel build" a one-line CI matrix entry.* -2. **Vendored `slvs` solver** + its Catch2 tests. No GUI, no OCCT. -3. **CAD kernel** (`CadDocument`, `SketchEngine`, `GeometryEngine`, `Sketch*`) + kernel - tests. Headless, no GUI. -4. **3MF recipe persistence** + golden-fixture regression test. -5. **GUI Design tab** (`DesignPanel`, `DesignCanvas`, `DesignSketchTool`, `GLGizmoSketch`) - + the 23 upstream hooks. - -## Agenda for the call - -Questions only SoftFever can answer: - -- Does OrcaSlicer *want* to be a CAD-integrated slicer? (Strategic; everything else is mechanical.) -- Default of `SLIC3R_CAD` at merge time, and when it flips ON. -- Vendored solver vs `deps/` external; and confirmation of the GPLv3/AGPLv3 combination. -- Project-file format: neutral name, encoding, embedded-BRep policy, and who owns v1 forward-compat. -- Undo/redo: the Design tab has its own stack; integrate with Orca's snapshot system or keep separate? -- Does he want the i18n work (Romanian, +23 kLOC) as a wholly separate PR? (Yes, almost certainly.) - -## Verdict - -Portability **high**. The prior "does upstream want OCCT" framing was wrong — OCCT is -already there. What remains is a 3.77 MiB dependency widening, a compile-time gate that -the existing null-guards make cheap, and one file-format decision that must be made before -the first release rather than after. - ---- -*Revised 2026-07-10 from direct measurement of `cad-mainline` @ `546cef5f42` vs upstream -merge-base `449a4cf9fc`. Supersedes the 2026-06-21 read-only assessment.* diff --git a/docs/CAD/ux/interaction-model.md b/docs/CAD/ux/interaction-model.md deleted file mode 100644 index 2041978ea0..0000000000 --- a/docs/CAD/ux/interaction-model.md +++ /dev/null @@ -1,144 +0,0 @@ -# Design tab — interaction model - -The contract for Esc, the right mouse button, and the states between them. Code that changes any -of the three changes this file in the same commit. - -## 1. The state machine - -`src/slic3r/GUI/CAD/DesignInteraction.hpp` — a four-level LIFO stack. The enum value *is* the -depth, so "which level does this press belong to" is a comparison rather than a chain of -special cases spread over three files. - -```cpp -enum class CadLevel : int { - Idle = 0, // nothing transient is up: Esc clears the selection - Tool = 1, // a feature card / armed sketch tool / constrain session: Esc exits it - Gesture = 2, // an uncommitted delta (entity being drawn, body being dragged): Esc reverts it - Transient = 3, // a value field or a popup menu: Esc closes just that -}; - -struct CadInteractionState { // the four bits routing actually needs - bool value_field_open{false}; - bool gesture_active{false}; - bool tool_armed{false}; - bool has_selection{false}; -}; - -constexpr CadLevel cad_escape_level(const CadInteractionState& s) -{ - if (s.value_field_open) return CadLevel::Transient; - if (s.gesture_active) return CadLevel::Gesture; - if (s.tool_armed) return CadLevel::Tool; - return CadLevel::Idle; -} -``` - -The rule is a `constexpr` free function over a POD, not a method on the panel, so the ordering -that is the entire contract is checkable without a window, a GL context or an event loop. Five -`static_assert`s in the header do exactly that, at compile time. - -**Strict invariant.** No level of Esc deletes a feature, discards a sketch that holds geometry, -or rolls history back. Destroying work needs a gesture that says so: - -| To destroy | Gesture | -|---|---| -| a feature | Delete / Backspace on an explicit selection | -| a drawn sketch | the ribbon's ✗ Cancel, which asks first | -| the last committed change | Ctrl+Z | - -## 2. Event routing - -**`OnKeyDown(WXK_ESCAPE)`** — `DesignPanel`'s `wxEVT_CHAR_HOOK`, one line: - -```cpp -if (key == WXK_ESCAPE) { escape(); return; } -``` - -Every Esc in the tab goes through it, whatever holds focus. `DesignPanel::escape_level()` answers -the four questions of `CadInteractionState` about this panel; `DesignPanel::escape()` acts on the -one level that answer names, and on no other: - -| Level | What one press does | What it must not touch | -|---|---|---| -| `Transient` | close the value field (`cancel_value` / `inline_cancel`) | the tool, which stays armed | -| `Gesture` | drop the clicks of the entity being drawn, or put a moved body back at the pose it had when the gizmo appeared | everything already committed | -| `Tool` | discard a feature card's *candidate*; drop an armed sketch tool to Select; end Constrain | committed features; entities already drawn | -| `Idle` | clear the selection (model and sketch); leave a sketch session **only if it is empty** | a sketch holding geometry — it is left through Finish or Cancel | - -A sketch *session* is deliberately not a `Tool`. It is the environment the Idle level lives in, -which is what makes the destructive path unrepresentable rather than merely unlikely. - -**`OnRightDown` / `OnRightUp`** — `DesignCanvas::set_on_context_menu`, bound after `GLCanvas3D`'s -own handlers so it can consume the event before them: - -```cpp -RIGHT_DOWN: remember the press position and the clock, then Skip() // the canvas still seeds the orbit - -RIGHT_UP: terminated = sketch_tool.take_right_consumed(); // read-and-clear, always - is_click = drift <= 3 px && dt <= 200 ms; // both budgets, or it was navigation - if (callback && !terminated && !inline_busy && is_click) { - select_at_screen(press.x, press.y); // raycast at the PRESS, not the release - on_context_menu(ClientToScreen(press)); - return; // consumed - } - Skip(); // orbit / pan / the handlers underneath -``` - -Two independent budgets because the two failure modes are independent: drift alone still popped a -menu at the end of a slow, careful orbit. `take_right_consumed()` is how a right-click that -already meant something to the armed sketch tool (terminate a chain, drop an edit-op) declines to -also mean "open a menu". - -## 3. Transition table - -`sel` = something is picked. Blank = the input does nothing at that state. - -| State | Left-click | Right-click | Esc | Enter | -|---|---|---|---|---| -| **Idle — model view** | pick / escalate the pick | offer menu for what is under the cursor | clear the selection | — | -| **Idle — sketch, empty** | pick | sketch offer menu | leave the session (nothing to lose) | Finish sketch | -| **Idle — sketch, drawn** | pick | sketch offer menu | clear the selection; status says the sketch is kept | Finish sketch | -| **Tool — feature card** | pick the card's next reference | offer menu | discard the candidate, close the card | commit the feature | -| **Tool — sketch tool armed** | place the first point | drop the tool to Select | drop the tool to Select | — | -| **Tool — constrain** | pick an entity | offer menu | end the session | apply | -| **Gesture — drawing** | place the next point | terminate the chain (keep what is drawn) | drop the in-progress entity, tool stays armed | commit the entity as drawn | -| **Gesture — moving a body** | drop the body here | end the move | revert to the pose at move-start | keep the placement | -| **Transient — value field** | — | — | close the field, tool stays armed | commit the value, advance the chain | -| **Transient — popup menu** | run the entry | — | close the menu | run the highlighted entry | -| **any** | — | — | *never* deletes, discards or rolls back | — | - -Right-hold-and-drag is not in the table on purpose: past 3 px or 200 ms it is navigation, and -navigation does not transition the state machine. - -## 4. Visual scaffolding - -Entering a sketch changes three things at once, so the state is legible from across the room: - -- **Banner.** A teal strip across the top of the viewport: `Editing: Sketch N · N = look normal to - the plane · Finish or Cancel in the toolbar`. Indicator only — Confirm and Cancel stay on the one - ribbon action bar, per the Design UX contract. It is a sibling above the canvas, not a floating - child over it: a child window over a `wxGLCanvas` is a native window on GTK and does not reliably - stack over GL, and this banner's job is to be unmissable rather than clever. -- **The printer bed is muted.** A plate grid and a sketch grid are the same visual language, and - reading one as the other is how a sketch gets drawn against the wrong reference. The view - checkbox remains the stored preference and is restored on the way out; ticking it mid-sketch - still shows the bed, because that is a deliberate act and this is only a default. -- **`N` looks normal to the plane**, keeping the current zoom, with the plane's own y axis as up. - Sketch key map only — in Feature mode the navigator orb owns orientation. - -## 5. Context menu content - -The offer is generated from `docs/CAD/ux/tool_atlas.json`; its 8-row shape and permanent row -indices are ratified and are not changed here. Checked against the per-context vocabularies asked -for in the 2026-09-05 interaction brief, the atlas already carries all of them except two, both on -a planar face: - -| Asked for | Status | -|---|---| -| Revolve on a planar face | **not offered, and should not be**: `revolve` accepts `sk_loop` only, because the kernel takes a sketch profile — a face is not one | -| Offset Face | offered as **Thicken** (`thicken`, accepts `face_planar`); `surf_offset` is the sheet-body verb and accepts `body_sheet` | - -View and document actions — Zoom to Fit, View Isometric, Clear Selection, Finish Sketch, Normal to -Sketch — stay in chrome by the atlas's own rule: the offer describes verbs that consume a -*selection*, and these act on the document or the camera. Esc covers Clear Selection, `N` covers -Normal to Sketch, and the ribbon covers Finish. diff --git a/docs/CAD/ux/mockups/cmp_12__edge_str.svg b/docs/CAD/ux/mockups/cmp_12__edge_str.svg deleted file mode 100644 index 3e641e90ec..0000000000 --- a/docs/CAD/ux/mockups/cmp_12__edge_str.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Straight edge selected — pick what to do with itWorking document · 12 slotsDress-up2Pattern on CurveReference3Straight edge \ No newline at end of file diff --git a/docs/CAD/ux/mockups/cmp_12__face_planar.svg b/docs/CAD/ux/mockups/cmp_12__face_planar.svg deleted file mode 100644 index 85809d5bf6..0000000000 --- a/docs/CAD/ux/mockups/cmp_12__face_planar.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Planar face selected — pick what to do with itWorking document · 12 slotsSketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2Planar face \ No newline at end of file diff --git a/docs/CAD/ux/mockups/cmp_12__sk_line.svg b/docs/CAD/ux/mockups/cmp_12__sk_line.svg deleted file mode 100644 index 6535c9fe77..0000000000 --- a/docs/CAD/ux/mockups/cmp_12__sk_line.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch line selected — pick what to do with itWorking document · 12 slotsCreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2Sketch line \ No newline at end of file diff --git a/docs/CAD/ux/mockups/cmp_8__edge_str.svg b/docs/CAD/ux/mockups/cmp_8__edge_str.svg deleted file mode 100644 index 2c41ce74fc..0000000000 --- a/docs/CAD/ux/mockups/cmp_8__edge_str.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Straight edge selected — pick what to do with itWorking document · 8 slotsDress-up2Pattern on CurveReference3Straight edge \ No newline at end of file diff --git a/docs/CAD/ux/mockups/cmp_8__face_planar.svg b/docs/CAD/ux/mockups/cmp_8__face_planar.svg deleted file mode 100644 index 5a58e2fb72..0000000000 --- a/docs/CAD/ux/mockups/cmp_8__face_planar.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Planar face selected — pick what to do with itWorking document · 8 slotsSketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2Planar face \ No newline at end of file diff --git a/docs/CAD/ux/mockups/cmp_8__sk_line.svg b/docs/CAD/ux/mockups/cmp_8__sk_line.svg deleted file mode 100644 index 4ca4000d59..0000000000 --- a/docs/CAD/ux/mockups/cmp_8__sk_line.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch line selected — pick what to do with itWorking document · 8 slotsCreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2Sketch line \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__art.svg b/docs/CAD/ux/mockups/fresh__art.svg deleted file mode 100644 index a54dd49b43..0000000000 --- a/docs/CAD/ux/mockups/fresh__art.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetText / imported art selected — pick what to do with itFresh document · 8 slotsABCModify2Text / imported art \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__art__modify.svg b/docs/CAD/ux/mockups/fresh__art__modify.svg deleted file mode 100644 index 184611c682..0000000000 --- a/docs/CAD/ux/mockups/fresh__art__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetModify — pick oneFresh document · 8 slotsABCDeleteDelEditText / imported art · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__bodies_2.svg b/docs/CAD/ux/mockups/fresh__bodies_2.svg deleted file mode 100644 index f282bef03a..0000000000 --- a/docs/CAD/ux/mockups/fresh__bodies_2.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetTwo bodies selected — pick what to do with itFresh document · 8 slotsMeasureDeleteDelTwo bodies \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__body_sheet.svg b/docs/CAD/ux/mockups/fresh__body_sheet.svg deleted file mode 100644 index 635b2f6aa6..0000000000 --- a/docs/CAD/ux/mockups/fresh__body_sheet.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetSheet body selected — pick what to do with itFresh document · 8 slotsMeasureModify2Sheet body \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__body_sheet__modify.svg b/docs/CAD/ux/mockups/fresh__body_sheet__modify.svg deleted file mode 100644 index 0cd330046d..0000000000 --- a/docs/CAD/ux/mockups/fresh__body_sheet__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetModify — pick oneFresh document · 8 slotsDeleteDelEditSheet body · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__body_solid.svg b/docs/CAD/ux/mockups/fresh__body_solid.svg deleted file mode 100644 index ef21e2b8e2..0000000000 --- a/docs/CAD/ux/mockups/fresh__body_solid.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetSolid body selected — pick what to do with itFresh document · 8 slotsMeasureModify2Solid body \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__body_solid__modify.svg b/docs/CAD/ux/mockups/fresh__body_solid__modify.svg deleted file mode 100644 index 8b5d1485a0..0000000000 --- a/docs/CAD/ux/mockups/fresh__body_solid__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetModify — pick oneFresh document · 8 slotsDeleteDelEditSolid body · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__coordsys.svg b/docs/CAD/ux/mockups/fresh__coordsys.svg deleted file mode 100644 index e20d08d000..0000000000 --- a/docs/CAD/ux/mockups/fresh__coordsys.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetCoordinate system selected — pick what to do with itFresh document · 8 slotsModify2Coordinate system \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__coordsys__modify.svg b/docs/CAD/ux/mockups/fresh__coordsys__modify.svg deleted file mode 100644 index b6b4871d16..0000000000 --- a/docs/CAD/ux/mockups/fresh__coordsys__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetModify — pick oneFresh document · 8 slotsDeleteDelEditCoordinate system · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__datum_axis.svg b/docs/CAD/ux/mockups/fresh__datum_axis.svg deleted file mode 100644 index 8174807ceb..0000000000 --- a/docs/CAD/ux/mockups/fresh__datum_axis.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetDatum axis selected — pick what to do with itFresh document · 8 slotsModify2Datum axis \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__datum_axis__modify.svg b/docs/CAD/ux/mockups/fresh__datum_axis__modify.svg deleted file mode 100644 index cba7005358..0000000000 --- a/docs/CAD/ux/mockups/fresh__datum_axis__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetModify — pick oneFresh document · 8 slotsDeleteDelEditDatum axis · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__datum_plane.svg b/docs/CAD/ux/mockups/fresh__datum_plane.svg deleted file mode 100644 index ffabd59969..0000000000 --- a/docs/CAD/ux/mockups/fresh__datum_plane.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetDatum plane selected — pick what to do with itFresh document · 8 slotsSketchShift+SReference2Modify2Datum plane \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__datum_plane__modify.svg b/docs/CAD/ux/mockups/fresh__datum_plane__modify.svg deleted file mode 100644 index 9ac26e4ce1..0000000000 --- a/docs/CAD/ux/mockups/fresh__datum_plane__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetModify — pick oneFresh document · 8 slotsDeleteDelEditDatum plane · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__datum_plane__reference.svg b/docs/CAD/ux/mockups/fresh__datum_plane__reference.svg deleted file mode 100644 index b42479441a..0000000000 --- a/docs/CAD/ux/mockups/fresh__datum_plane__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetReference — pick oneFresh document · 8 slotsPlaneShift+PHelixDatum plane · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__edge_circ.svg b/docs/CAD/ux/mockups/fresh__edge_circ.svg deleted file mode 100644 index 3e72544c04..0000000000 --- a/docs/CAD/ux/mockups/fresh__edge_circ.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetCircular edge selected — pick what to do with itFresh document · 8 slotsMeasureCircular edge \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__edge_str.svg b/docs/CAD/ux/mockups/fresh__edge_str.svg deleted file mode 100644 index c4e54d5335..0000000000 --- a/docs/CAD/ux/mockups/fresh__edge_str.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetStraight edge selected — pick what to do with itFresh document · 8 slotsReference3Straight edge \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__edge_str__reference.svg b/docs/CAD/ux/mockups/fresh__edge_str__reference.svg deleted file mode 100644 index 80dd7a877a..0000000000 --- a/docs/CAD/ux/mockups/fresh__edge_str__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetReference — pick oneFresh document · 8 slotsMeasurePlaneShift+PAxisShift+AStraight edge · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__face_cyl.svg b/docs/CAD/ux/mockups/fresh__face_cyl.svg deleted file mode 100644 index d2335e4177..0000000000 --- a/docs/CAD/ux/mockups/fresh__face_cyl.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetCylindrical face selected — pick what to do with itFresh document · 8 slotsReference3EditCylindrical face \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__face_cyl__reference.svg b/docs/CAD/ux/mockups/fresh__face_cyl__reference.svg deleted file mode 100644 index 7cb83afb56..0000000000 --- a/docs/CAD/ux/mockups/fresh__face_cyl__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetReference — pick oneFresh document · 8 slotsMeasureAxisShift+AHelixCylindrical face · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__face_other.svg b/docs/CAD/ux/mockups/fresh__face_other.svg deleted file mode 100644 index d8dcfb96ab..0000000000 --- a/docs/CAD/ux/mockups/fresh__face_other.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetCurved face selected — pick what to do with itFresh document · 8 slotsMeasureEditCurved face \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__face_planar.svg b/docs/CAD/ux/mockups/fresh__face_planar.svg deleted file mode 100644 index a66268fc9a..0000000000 --- a/docs/CAD/ux/mockups/fresh__face_planar.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetPlanar face selected — pick what to do with itFresh document · 8 slotsSketchShift+SExtrudeShift+EReference4EditPlanar face \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__face_planar__reference.svg b/docs/CAD/ux/mockups/fresh__face_planar__reference.svg deleted file mode 100644 index 9aa608a5e7..0000000000 --- a/docs/CAD/ux/mockups/fresh__face_planar__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetReference — pick oneFresh document · 8 slotsCoord SysShift+CMeasurePlaneShift+PAxisShift+APlanar face · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__none.svg b/docs/CAD/ux/mockups/fresh__none.svg deleted file mode 100644 index 56ac028b74..0000000000 --- a/docs/CAD/ux/mockups/fresh__none.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetClick a face or a reference plane, then a toolFresh document · 8 slotsSketchShift+SReference4Nothing selected \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__none__reference.svg b/docs/CAD/ux/mockups/fresh__none__reference.svg deleted file mode 100644 index 0492aa6de0..0000000000 --- a/docs/CAD/ux/mockups/fresh__none__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetReference — pick oneFresh document · 8 slotsCoord SysShift+CHelixPlaneShift+PAxisShift+ANothing selected · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_2ent.svg b/docs/CAD/ux/mockups/fresh__sk_2ent.svg deleted file mode 100644 index dd02122149..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_2ent.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetTwo sketch entities selected — pick what to do with itFresh document · 8 slotsCreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2Two sketch entities \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_2ent__create.svg b/docs/CAD/ux/mockups/fresh__sk_2ent__create.svg deleted file mode 100644 index 98e984bff2..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_2ent__create.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetCreate — pick oneFresh document · 8 slotsLineLRectangleRCircleCArcASlotSEllipseESplineBMore2Two sketch entities · Create \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_2ent__dressup.svg b/docs/CAD/ux/mockups/fresh__sk_2ent__dressup.svg deleted file mode 100644 index 926594bb86..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_2ent__dressup.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetDress-up — pick oneFresh document · 8 slotsFilletFChamferHTwo sketch entities · Dress-up \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_2ent__modify.svg b/docs/CAD/ux/mockups/fresh__sk_2ent__modify.svg deleted file mode 100644 index 242332967e..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_2ent__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetModify — pick oneFresh document · 8 slotsDeleteDelExtendXTwo sketch entities · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_2ent__reference.svg b/docs/CAD/ux/mockups/fresh__sk_2ent__reference.svg deleted file mode 100644 index aa105c3a9e..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_2ent__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetReference — pick oneFresh document · 8 slotsConstructionQDimensionDConstrainKTwo sketch entities · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_arc.svg b/docs/CAD/ux/mockups/fresh__sk_arc.svg deleted file mode 100644 index 0712508411..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_arc.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetSketch arc or circle selected — pick what to do with itFresh document · 8 slotsCreate9OffsetOTrimTMirrorMMoveReference3Modify2Sketch arc or circle \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_arc__create.svg b/docs/CAD/ux/mockups/fresh__sk_arc__create.svg deleted file mode 100644 index d22d916e60..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_arc__create.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetCreate — pick oneFresh document · 8 slotsLineLRectangleRCircleCArcASlotSEllipseESplineBMore2Sketch arc or circle · Create \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_arc__modify.svg b/docs/CAD/ux/mockups/fresh__sk_arc__modify.svg deleted file mode 100644 index cf3fc2450f..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_arc__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetModify — pick oneFresh document · 8 slotsDeleteDelExtendXSketch arc or circle · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_arc__reference.svg b/docs/CAD/ux/mockups/fresh__sk_arc__reference.svg deleted file mode 100644 index cfbe0a1042..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_arc__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetReference — pick oneFresh document · 8 slotsConstructionQDimensionDConstrainKSketch arc or circle · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_line.svg b/docs/CAD/ux/mockups/fresh__sk_line.svg deleted file mode 100644 index e40d823e13..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_line.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetSketch line selected — pick what to do with itFresh document · 8 slotsCreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2Sketch line \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_line__create.svg b/docs/CAD/ux/mockups/fresh__sk_line__create.svg deleted file mode 100644 index 1a6f7b561c..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_line__create.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetCreate — pick oneFresh document · 8 slotsLineLRectangleRCircleCArcASlotSEllipseESplineBMore2Sketch line · Create \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_line__dressup.svg b/docs/CAD/ux/mockups/fresh__sk_line__dressup.svg deleted file mode 100644 index 3fce662ba1..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_line__dressup.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetDress-up — pick oneFresh document · 8 slotsFilletFChamferHSketch line · Dress-up \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_line__modify.svg b/docs/CAD/ux/mockups/fresh__sk_line__modify.svg deleted file mode 100644 index 26318535ef..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_line__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetModify — pick oneFresh document · 8 slotsDeleteDelExtendXSketch line · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_line__reference.svg b/docs/CAD/ux/mockups/fresh__sk_line__reference.svg deleted file mode 100644 index 2abc91fc99..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_line__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetReference — pick oneFresh document · 8 slotsConstructionQDimensionDConstrainKSketch line · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_loop.svg b/docs/CAD/ux/mockups/fresh__sk_loop.svg deleted file mode 100644 index 58eaa9e4fb..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_loop.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetClosed sketch loop selected — pick what to do with itFresh document · 8 slotsAdd material5Modify2Closed sketch loop \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_loop__add.svg b/docs/CAD/ux/mockups/fresh__sk_loop__add.svg deleted file mode 100644 index c6e6c90701..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_loop__add.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetAdd material — pick oneFresh document · 8 slotsExtrudeShift+ERevolveShift+RSurface ExtrudeShift+GSurface RevolveSurface FillClosed sketch loop · Add material \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_loop__modify.svg b/docs/CAD/ux/mockups/fresh__sk_loop__modify.svg deleted file mode 100644 index 416e62d7cd..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_loop__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetModify — pick oneFresh document · 8 slotsDeleteDelEditClosed sketch loop · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_none.svg b/docs/CAD/ux/mockups/fresh__sk_none.svg deleted file mode 100644 index ce7604c47d..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_none.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetSketch, nothing picked selected — pick what to do with itFresh document · 8 slotsCreate9Reference2Sketch, nothing picked \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_none__create.svg b/docs/CAD/ux/mockups/fresh__sk_none__create.svg deleted file mode 100644 index 06994f9517..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_none__create.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetCreate — pick oneFresh document · 8 slotsLineLRectangleRCircleCArcASlotSEllipseESplineBMore2Sketch, nothing picked · Create \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_none__reference.svg b/docs/CAD/ux/mockups/fresh__sk_none__reference.svg deleted file mode 100644 index e4e558bbd9..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_none__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetReference — pick oneFresh document · 8 slotsDimensionDConstructionQSketch, nothing picked · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_point.svg b/docs/CAD/ux/mockups/fresh__sk_point.svg deleted file mode 100644 index 65acf94ac7..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_point.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetSketch point selected — pick what to do with itFresh document · 8 slotsCreate9MoveReference2DeleteDelSketch point \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_point__create.svg b/docs/CAD/ux/mockups/fresh__sk_point__create.svg deleted file mode 100644 index fbe094d147..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_point__create.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetCreate — pick oneFresh document · 8 slotsLineLRectangleRCircleCArcASlotSEllipseESplineBMore2Sketch point · Create \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__sk_point__reference.svg b/docs/CAD/ux/mockups/fresh__sk_point__reference.svg deleted file mode 100644 index 8b02eba29f..0000000000 --- a/docs/CAD/ux/mockups/fresh__sk_point__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetReference — pick oneFresh document · 8 slotsDimensionDConstrainKSketch point · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__vertex.svg b/docs/CAD/ux/mockups/fresh__vertex.svg deleted file mode 100644 index 1e1661d6aa..0000000000 --- a/docs/CAD/ux/mockups/fresh__vertex.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetVertex selected — pick what to do with itFresh document · 8 slotsReference4Vertex \ No newline at end of file diff --git a/docs/CAD/ux/mockups/fresh__vertex__reference.svg b/docs/CAD/ux/mockups/fresh__vertex__reference.svg deleted file mode 100644 index aad3d46b6e..0000000000 --- a/docs/CAD/ux/mockups/fresh__vertex__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetReference — pick oneFresh document · 8 slotsCoord SysShift+CMeasurePlaneShift+PAxisShift+AVertex · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/gen_offer_mockups.py b/docs/CAD/ux/mockups/gen_offer_mockups.py deleted file mode 100644 index 6ddd4caae4..0000000000 --- a/docs/CAD/ux/mockups/gen_offer_mockups.py +++ /dev/null @@ -1,884 +0,0 @@ -#!/usr/bin/env python3 -"""Render the object-driven tool offer from docs/ux/tool_atlas.json. - -Fork-neutral on purpose: this file and everything it emits name no product, so the -two forks carry byte-identical copies (the charter itself is the only doc that -substitutes the product name). - -Every mockup in docs/ux/mockups/ and the review page docs/ux/offer_atlas.html are -generated by this script. Nothing is hand-drawn: the point of the offer is that a -tool's address never changes, and a human drawing 40-odd states by hand is exactly -how an address quietly changes. - - python3 docs/ux/mockups/gen_offer_mockups.py - -It fails loudly rather than rendering a broken map: an unknown slot, an unknown -selection id in `accepts`, or one address holding two different verbs for the same -selection is an error, not a warning. - -SVG on purpose. There is no rsvg/inkscape/cairosvg on the build box and -ImageMagick would rasterise through its own weak internal renderer; SVG renders -exactly in a browser at any zoom and stays diffable in git. Rasterise later if -slides need it. -""" - -import json -import re -import math -import os -import sys -from collections import defaultdict - -HERE = os.path.dirname(os.path.abspath(__file__)) -UX = os.path.dirname(HERE) -ATLAS = os.path.join(UX, "tool_atlas.json") - -# The reach target from the charter (L11 / 6.1): if the ring plus a real part do not -# fit here, the design has failed before it is built. -W, H = 1366, 768 - -C = { - "app": "#1a1d21", "chrome": "#23272d", "panel": "#20242a", "line": "#31363d", - "vp0": "#2f353d", "vp1": "#242930", "grid": "#333a43", - "text": "#e7ecf1", "muted": "#8e9aa7", "dim": "#5f6a76", - "top": "#93a1b1", "left": "#6f7b89", "right": "#5a6472", "edge": "#39414b", - "hi": "#f5a623", "hi_soft": "#f5a62333", "sheet": "#7fa8c9", - "ring": "#171a1e", "chip": "#2e343c", "chip_line": "#3c444e", - "key": "#454f5b", "accent": "#4f9bd9", "empty": "#2a2f36", -} - - -# ---------------------------------------------------------------- glyphs -# Each glyph draws inside a 24x24 box centred on (0,0) — i.e. -12..12. -def _g(body): - return body - - -GLYPHS = { - # model - "sketch": '', - "extrude": '', - "revolve": '', - "sweep": '', - "loft": '', - "thicken": '', - "rib": '', - "boolean": '', - "hole": '', - "thread": '', - "shell": '', - "cut": '', - "split": '', - "fillet": '', - "chamfer": '', - "draft": '', - "surf_off": '', - "pattern": '', - "mirror": '', - "pat_curve": '', - "move": '', - "mate": '', - "align": '', - "plane": '', - "axis": '', - "csys": '', - "helix": '', - "project": '', - "measure": '', - "mass": '', - "interfere": '', - "edit": '', - "del_face": '', - "colour": '', - "delete": '', - # sketch primitives - "sk_line": '', - "sk_rect": '', - "sk_circle": '', - "sk_arc": '', - "sk_slot": '', - "sk_ell": '', - "sk_spline": '', - "sk_poly": '', - "sk_point": '', - "sk_offset": '', - "sk_trim": '', - "sk_ext": '', - "sk_mir": '', - "sk_move": '', - "sk_dim": '', - "sk_lock": '', - "sk_constr": '', - # families (used when a slot holds more than one verb) - "fam_create": '', - "fam_add": '', - "fam_remove": '', - "fam_dressup": '', - "fam_repeat": '', - "fam_transform": '', - "fam_reference": '', - # Deliberately NOT the bare pencil: create/sketch already owns that shape and the two slots - # are adjacent (N and NW). A pencil over a solid reads as "change the thing that exists". - "fam_modify": '' - '', -} - -VERB_GLYPH = { - "sketch": "sketch", "extrude": "extrude", "revolve": "revolve", "sweep": "sweep", - "loft": "loft", "thicken": "thicken", "rib": "rib", "boolean": "boolean", - "surf_extrude": "extrude", "surf_revolve": "revolve", "surf_loft": "loft", - "surf_fill": "surf_off", "thicken_surf": "thicken", "hole": "hole", "thread": "thread", - "shell": "shell", "cut": "cut", "split": "split", "fillet": "fillet", - "chamfer": "chamfer", "draft": "draft", "surf_offset": "surf_off", - "pattern": "pattern", "mirror": "mirror", "pat_curve": "pat_curve", - "transform": "move", "mate": "mate", "align": "align", "plane": "plane", - "axis": "axis", "coordsys_v": "csys", "helix": "helix", "project": "project", - "measure": "measure", "mass_props": "mass", "interference": "interfere", - "edit_feature": "edit", "delete_face": "del_face", "colour": "colour", "delete": "delete", - "sk_line_t": "sk_line", "sk_rect": "sk_rect", "sk_circle": "sk_circle", - "sk_arc_t": "sk_arc", "sk_slot": "sk_slot", "sk_ellipse": "sk_ell", - "sk_spline": "sk_spline", "sk_polygon": "sk_poly", "sk_point_t": "sk_point", - "sk_offset": "sk_offset", "sk_trim": "sk_trim", "sk_fillet": "fillet", - "sk_chamfer": "chamfer", "sk_mirror": "sk_mir", "sk_move": "sk_move", - "sk_dimension": "sk_dim", "sk_constrain": "sk_lock", "sk_construct": "sk_constr", - "sk_extend": "sk_ext", "sk_delete": "delete", -} - - -def glyph(name, cx, cy, colour, scale=1.0, sw=1.6): - body = GLYPHS.get(name, '') - return (f'{body}') - - -# ---------------------------------------------------------------- iso scene -COS30, SIN30 = math.cos(math.radians(30)), math.sin(math.radians(30)) - - -def iso(x, y, z, ox, oy, s=1.0): - return (ox + (x - y) * COS30 * s, oy + (x + y) * SIN30 * s - z * s) - - -def poly(pts, fill, stroke=None, extra=""): - d = " ".join(f"{x:.1f},{y:.1f}" for x, y in pts) - # Only emit our own stroke-width when the caller has not supplied one: a duplicate - # attribute is last-one-wins in a browser but invalid markup, and it is exactly the - # kind of thing a stricter renderer refuses outright. - if stroke: - st = f' stroke="{stroke}"' + ("" if "stroke-width" in extra else ' stroke-width="1"') - else: - st = ' stroke="none"' - return f'' - - -def box(ox, oy, w=150, d=105, h=52, s=1.0, hi=None): - """Three visible faces of a box. `hi` names the face/edge to highlight.""" - P = lambda x, y, z: iso(x, y, z, ox, oy, s) - top = [P(0, 0, h), P(w, 0, h), P(w, d, h), P(0, d, h)] - left = [P(0, d, h), P(w, d, h), P(w, d, 0), P(0, d, 0)] - right = [P(w, 0, h), P(w, d, h), P(w, d, 0), P(w, 0, 0)] - out = [ - poly(left, C["left"], C["edge"]), - poly(right, C["right"], C["edge"]), - poly(top, C["hi"] if hi == "top" else C["top"], C["edge"]), - ] - if hi == "top": - out.append(poly(top, "none", "#ffd07a", ' stroke-width="2.5"')) - if hi == "front_edge": - a, b = P(0, d, h), P(w, d, h) - out.append(f'') - if hi == "vertex": - v = P(w, d, h) - out.append(f'') - if hi == "whole": - out.append(poly(top, "none", "#ffd07a", ' stroke-width="2.5"')) - out.append(poly(left, "none", "#ffd07a", ' stroke-width="2.5"')) - out.append(poly(right, "none", "#ffd07a", ' stroke-width="2.5"')) - return "".join(out), P - - -def bore(P, cx, cy, h, r=22, s=1.0, hi=False): - """A cylindrical bore through the top face, drawn as ellipse + wall.""" - c = P(cx, cy, h) - rx, ry = r * COS30 * 2 * s, r * SIN30 * 2 * s - col = C["hi"] if hi else "#20252b" - wall = C["hi"] if hi == "wall" else "#3b434d" - return (f'' - f'' - + (f'' if hi else "")), c - - -def scene(kind, ox, oy): - """Return (svg, anchor) for a selection kind. Anchor = where the ring centres.""" - s = 1.0 - if kind == "origin": - # A document with nothing in it: the three origin planes are all there is to click. - P = lambda x, y, z: iso(x, y, z, ox, oy, s) - g = poly([P(-70, -70, 0), P(70, -70, 0), P(70, 70, 0), P(-70, 70, 0)], - "#4f9bd91f", "#4f9bd977", ' stroke-width="1.5" stroke-dasharray="6 4"') - g += poly([P(-70, 0, -70), P(70, 0, -70), P(70, 0, 70), P(-70, 0, 70)], - "#e05c5c14", "#e05c5c66", ' stroke-width="1.5" stroke-dasharray="6 4"') - g += poly([P(0, -70, -70), P(0, 70, -70), P(0, 70, 70), P(0, -70, 70)], - "#5ce07a14", "#5ce07a66", ' stroke-width="1.5" stroke-dasharray="6 4"') - return g, P(0, 0, 0) - if kind == "empty": - g, P = box(ox, oy, s=s) - return g, P(75, 52, 26) - if kind == "box_top": - g, P = box(ox, oy, s=s, hi="top") - return g, P(75, 52, 52) - if kind == "box_whole": - g, P = box(ox, oy, s=s, hi="whole") - return g, P(75, 52, 26) - if kind == "box_edge": - g, P = box(ox, oy, s=s, hi="front_edge") - return g, P(75, 105, 52) - if kind == "box_vertex": - g, P = box(ox, oy, s=s, hi="vertex") - return g, P(150, 105, 52) - if kind in ("box_bore", "box_bore_rim"): - g, P = box(ox, oy, s=s) - b, c = bore(P, 75, 52, 52, hi=("wall" if kind == "box_bore" else True)) - return g + b, c - if kind == "box_fillet_face": - g, P = box(ox, oy, s=s) - a, b = P(0, 0, 52), P(0, 105, 52) - g += (f'') - return g, P(0, 52, 52) - if kind == "sheet": - P = lambda x, y, z: iso(x, y, z, ox, oy, s) - pts = [P(0, 0, 40), P(150, 0, 55), P(150, 105, 30), P(0, 105, 18)] - return (poly(pts, C["sheet"], "#ffd07a", ' stroke-width="2.5" opacity="0.85"'), - P(75, 52, 36)) - if kind == "two_boxes": - g1, P1 = box(ox - 60, oy - 10, w=110, d=80, h=44, hi="whole") - g2, P2 = box(ox + 70, oy + 26, w=95, d=70, h=60, hi="whole") - return g1 + g2, (ox + 42, oy + 6) - if kind == "datum": - P = lambda x, y, z: iso(x, y, z, ox, oy, s) - g, _ = box(ox, oy, s=s) - pts = [P(-25, -25, 70), P(175, -25, 70), P(175, 130, 70), P(-25, 130, 70)] - g += poly(pts, "#4f9bd933", C["hi"], ' stroke-width="2.5" stroke-dasharray="6 4"') - return g, P(75, 52, 70) - if kind == "axis": - g, P = box(ox, oy, s=s) - a, b = P(-30, 52, 52), P(180, 52, 52) - g += (f'') - return g, P(75, 52, 52) - if kind == "csys": - P = lambda x, y, z: iso(x, y, z, ox, oy, s) - g, _ = box(ox, oy, s=s) - o = P(0, 0, 52) - for tgt, col in ((P(60, 0, 52), "#e05c5c"), (P(0, 60, 52), "#5ce07a"), (P(0, 0, 112), "#5c9ce0")): - g += (f'') - g += f'' - return g, o - if kind == "art": - P = lambda x, y, z: iso(x, y, z, ox, oy, s) - g, _ = box(ox, oy, s=s) - c = P(75, 52, 52) - g += (f'' - f'ABC') - return g, c - if kind == "loop": - P = lambda x, y, z: iso(x, y, z, ox, oy, s) - g, _ = box(ox, oy, s=s) - pts = [P(28, 22, 52), P(122, 22, 52), P(122, 83, 52), P(28, 83, 52)] - g += poly(pts, C["hi_soft"], C["hi"], ' stroke-width="3"') - return g, P(75, 52, 52) - # --- sketch mode: flat, camera normal to the plane - gx, gy = ox - 30, oy - 10 - grid = "".join( - f'' - for i in range(11)) + "".join( - f'' - for i in range(16)) - if kind == "sk_empty": - return grid, (gx, gy) - if kind == "sk_line": - return (grid + f'' - f'' - f'', (gx, gy + 10)) - if kind == "sk_arc": - return (grid + f'', (gx, gy)) - if kind == "sk_point": - return (grid + f'', (gx, gy)) - if kind == "sk_two": - return (grid + f'' - f'', (gx, gy + 5)) - return grid, (gx, gy) - - -# ---------------------------------------------------------------- app chrome -def chrome(title, status, mode="model", empty_doc=False): - tabs = ["Prepare", "Preview", "Design"] - g = [f'', - f'' - f'' - f'', - f'', - f''] - x = 24 - for t in tabs: - on = (t == "Design") - g.append(f'{t}') - if on: - g.append(f'') - x += len(t) * 9 + 34 - # toolbar - g.append(f'' - f'') - fams = (["sketch", "extrude", "hole", "fillet", "pattern", "boolean", "plane", "move", "measure"] - if mode == "model" else - ["sk_line", "sk_rect", "sk_circle", "sk_arc", "sk_slot", "sk_spline", "sk_dim", "sk_trim", "sk_lock"]) - for i, gl in enumerate(fams): - cx = 40 + i * 46 - g.append(f'') - g.append(glyph(gl, cx, 68, C["muted"], 0.62, 1.7)) - # left rail - g.append(f'' - f'') - g.append(f'FEATURES') - # A "fresh document" mockup that shows six existing features is a lie, and it is the one - # state the group will look at hardest — it is the first-run picture (B5). - tree = ([] if empty_doc else - [("Sketch1", 0), ("Extrude1", 0), ("Hole1", 1), ("Fillet1", 1), ("Sketch2", 0), ("Extrude2", 0)]) - if empty_doc: - g.append(f'nothing yet') - for i, (n, ind) in enumerate(tree): - yy = 150 + i * 26 - g.append(glyph("sketch" if n.startswith("Sketch") else "extrude", 30 + ind * 14, yy - 4, C["dim"], 0.42, 1.8)) - g.append(f'{n}') - # status bar - g.append(f'' - f'{status}') - g.append(f'{title}') - return "".join(g) - - -# ---------------------------------------------------------------- the ring -def ring(cx, cy, items, capacity, sel_name, radius=134): - """items: dict angle_index -> (glyph, label, key, count) or None for an empty slot.""" - # The scrim is deliberately faint. §4.1: the offer "never blocks the view of what it acts - # on" — at 0.55 the disc swallowed the very face the ring was opened on, which is the rule - # failing in its own mockup. - g = [f'', - f'', - f''] - step = 360.0 / capacity - for i in range(capacity): - ang = math.radians(-90 + i * step) - px, py = cx + radius * math.cos(ang), cy + radius * math.sin(ang) - it = items.get(i) - if not it: - g.append(f'') - continue - gl, label, key, count = it - g.append(f'') - g.append(glyph(gl, px, py - 2, C["text"], 0.92, 1.7)) - g.append(f'{label}') - if key: - kw = 13 + len(key) * 6.6 - g.append(f'' - f'{key}') - if count and count > 1: - g.append(f'' - f'{count}') - # The selection name sits on the LOWER rim of the centre hole, not above it: above, it - # landed on top of the north slot's shortcut chip and hid it. Below, the pick stays - # visible through the hole and the pill is clear of the south chip at cy+104. - w = 13 + len(sel_name) * 6.9 - g.append(f'' - f'{sel_name}') - return "".join(g) - - -# ---------------------------------------------------------------- enumeration -def load(): - with open(ATLAS, encoding="utf-8") as f: - return json.load(f) - - -def validate(A): - slot_ids = {s["id"] for s in A["slots"]} - sel_ids = {s["id"] for s in A["selections"]} - errs = [] - seen = {} - for v in A["verbs"]: - if v["slot"] not in slot_ids: - errs.append(f'{v["id"]}: unknown slot {v["slot"]!r}') - for a in v["accepts"]: - if a not in sel_ids: - errs.append(f'{v["id"]}: accepts unknown selection {a!r}') - if v["id"] in seen: - errs.append(f'{v["id"]}: duplicate verb id') - seen[v["id"]] = v["slot"] - if errs: - sys.exit("tool_atlas.json is inconsistent:\n " + "\n ".join(errs)) - return slot_ids, sel_ids - - -def eligible(A, sel_id, doc): - sel = next(s for s in A["selections"] if s["id"] == sel_id) - out = [] - for v in A["verbs"]: - if v.get("mode", "model") != sel["mode"]: - continue - if sel_id not in v["accepts"]: - continue - n = v.get("needs") or {} - if n.get("bodies", 0) > doc["bodies"]: - continue - if n.get("sketches", 0) > doc["sketches"]: - continue - if n.get("sheet") and not doc["sheet"]: - continue - out.append(v) - return out - - -def by_slot(A, verbs): - order = [s["id"] for s in A["slots"]] - d = defaultdict(list) - for v in verbs: - d[v["slot"]].append(v) - return {k: d[k] for k in order if d[k]} - - -def ring_items(A, grouped): - order = [s["id"] for s in A["slots"]] - slot_label = {s["id"]: s["label"] for s in A["slots"]} - items = {} - for i, sid in enumerate(order): - vs = grouped.get(sid) - if not vs: - continue - if len(vs) == 1: - v = vs[0] - items[i] = (VERB_GLYPH.get(v["id"], "fam_" + sid), v["name"], v.get("key"), 1) - else: - items[i] = ("fam_" + sid, slot_label[sid], None, len(vs)) - return items - - -# ---------------------------------------------------------------- rendering -def menu(cx, cy, header, rows, submenu=None, sub_at=None): - """Vertical list form of the offer. rows: (glyph, name, key, count, enabled, reason). - - The invariant is unchanged — a verb has one permanent row index, and rows that do not - apply are DISABLED IN PLACE, never removed. What changes against the ring is what an - unavailable slot can say: an empty circle says nothing, a greyed row says its own name - and the reason it is grey, in the words the product already ships. - """ - RW, RH, HD = 324, 34, 38 - # The reason line is the whole point of a disabled row, so it must FIT: at 10px italic a - # glyph is ~4.9px, and anything past the box edge is a promise the layout does not keep. - fit = int((RW - 62) / 4.9) - x, y = cx + 26, cy - 30 - h = HD + len(rows) * RH + 10 - if y + h > H - 44: - y = max(100, H - 44 - h) - g = [f'', - f'', - f'{header.upper()}', - f''] - # a leader from the pick point to the menu, so the list is visibly ABOUT that geometry - g.insert(0, f'') - g.insert(0, f'') - for i, (gl, name, key, count, on, reason) in enumerate(rows): - ry = y + HD + i * RH - op = "1" if on else "0.34" - if on and i == 0: - g.append(f'') - g.append(f'') - g.append(glyph(gl, x + 26, ry + RH / 2, C["text"], 0.72, 1.7)) - g.append(f'{name}') - if key: - kw = 13 + len(key) * 6.6 - g.append(f'' - f'{key}') - elif count and count > 1: - g.append(f'') - g.append(f'{count}') - g.append('') - if not on and reason: - r = reason if len(reason) <= fit else reason[:fit - 1].rstrip(" ,—-") + "…" - g.append(f'{r}') - if submenu: - sy = y + HD + (sub_at or 0) * RH - 6 - sh = 12 + len(submenu) * RH - sx = x + RW + 8 - g.append(f'') - g.append(f'') - for i, (gl, name, key, _c, on, _r) in enumerate(submenu): - ry = sy + 6 + i * RH - g.append(f'') - g.append(glyph(gl, sx + 24, ry + RH / 2, C["text"], 0.72, 1.7)) - g.append(f'{name}') - if key: - kw = 13 + len(key) * 6.6 - g.append(f'' - f'{key}') - g.append('') - return "".join(g) - - -OVERFLOWS = [] # (selection, doc state, family, verbs that did not fit the sub-ring) - - -def svg_doc(inner): - return (f'{inner}') - - -def render_list_state(A, sel, doc, expand=None): - """The vertical-list form: every family row always present, in the same order, with the - ones that do not apply disabled and carrying their reason.""" - verbs = eligible(A, sel["id"], doc) - grouped = by_slot(A, verbs) - fresh = doc["bodies"] == 0 and doc["sketches"] == 0 - shape = "origin" if (fresh and sel["shape"] == "empty") else sel["shape"] - art, (ax, ay) = scene(shape, 620, 360) - rows, sub, sub_at = [], None, None - for idx, s in enumerate(A["slots"]): - vs = grouped.get(s["id"], []) - if len(vs) == 1: - v = vs[0] - rows.append((VERB_GLYPH.get(v["id"], "fam_" + s["id"]), v["name"], v.get("key"), 1, True, None)) - elif len(vs) > 1: - rows.append(("fam_" + s["id"], s["label"], None, len(vs), True, None)) - if expand == s["id"]: - sub_at = idx - sub = [(VERB_GLYPH.get(v["id"], "fam_" + s["id"]), v["name"], v.get("key"), 1, True, None) - for v in vs] - else: - # Disabled in place, with the product's own refusal text — the thing an empty - # slot in the ring could never say. - cands = [v for v in A["verbs"] - if v["slot"] == s["id"] and v.get("mode", "model") == sel["mode"]] - why = next((v["refusal"] for v in cands if v.get("refusal")), None) - rows.append(("fam_" + s["id"], s["label"], None, 0, False, why)) - status = ("Right-click the geometry to see what you can do with it" if not expand - else f'{sel["name"]} — pick one') - inner = chrome(f'{doc["name"]} · vertical list', status, sel["mode"], empty_doc=fresh) - inner += art - inner += menu(ax, ay, sel["name"], rows, sub, sub_at) - return svg_doc(inner) - - -def render_state(A, sel, doc, capacity=8, secondary=None): - verbs = eligible(A, sel["id"], doc) - grouped = by_slot(A, verbs) - anchor_x, anchor_y = 810, 400 - fresh = doc["bodies"] == 0 and doc["sketches"] == 0 - shape = "origin" if (fresh and sel["shape"] == "empty") else sel["shape"] - art, (ax, ay) = scene(shape, anchor_x - 90, anchor_y - 40) - if secondary: - vs = grouped[secondary] - # A sub-ring ANCHORS ON ITS PARENT'S DIRECTION. Fanning from north instead put Extrude - # at N — Create's address in the primary map — so the second level contradicted the - # first. Anchored, an address is two consistent strokes: Add material is NE, and the - # first verb of Add material is NE again. - base = [s["id"] for s in A["slots"]].index(secondary) - overflow = [] - if len(vs) > capacity: - # Do NOT wrap: (base+i) % capacity would quietly put verb 9 on top of verb 1, which - # is the invariant failing silently — the one outcome worse than an ugly ring. Show - # what fits, mark the rest, and report it. - overflow = vs[capacity - 1:] - vs = vs[:capacity - 1] - items = {(base + i) % capacity: - (VERB_GLYPH.get(v["id"], "fam_" + secondary), v["name"], v.get("key"), 1) - for i, v in enumerate(vs)} - if overflow: - items[(base + capacity - 1) % capacity] = ("fam_" + secondary, "More", None, len(overflow)) - OVERFLOWS.append((sel["name"], doc["id"], secondary, [v["name"] for v in overflow])) - label = next(s["label"] for s in A["slots"] if s["id"] == secondary) - sel_name = f'{sel["name"]} · {label}' - status = f'{label} — pick one' - else: - items = ring_items(A, grouped) - sel_name = sel["name"] - status = ("Click a face or a reference plane, then a tool" - if sel["id"] == "none" else f'{sel["name"]} selected — pick what to do with it') - inner = chrome(f'{doc["name"]} · {capacity} slots', status, sel["mode"], empty_doc=fresh) - inner += art - inner += ring(ax, ay, items, capacity, sel_name) - return svg_doc(inner), verbs, grouped - - -def main(): - A = load() - validate(A) - docs = {d["id"]: d for d in A["doc_states"]} - outdir = HERE - rows, files = [], [] - total_primary = total_secondary = 0 - fill_sum = fill_n = 0 - - for d in A["doc_states"]: - for sel in A["selections"]: - svg, verbs, grouped = render_state(A, sel, d) - name = f'{d["id"]}__{sel["id"]}.svg' - with open(os.path.join(outdir, name), "w", encoding="utf-8") as f: - f.write(svg) - files.append((name, f'{sel["name"]} — {d["name"]}')) - total_primary += 1 - fill_sum += len(grouped) - fill_n += 1 - secondaries = [] - for sid, vs in grouped.items(): - if len(vs) > 1: - s2, _, _ = render_state(A, sel, d, secondary=sid) - n2 = f'{d["id"]}__{sel["id"]}__{sid}.svg' - with open(os.path.join(outdir, n2), "w", encoding="utf-8") as f: - f.write(s2) - label = next(s["label"] for s in A["slots"] if s["id"] == sid) - files.append((n2, f'{sel["name"]} — {label} ring')) - secondaries.append(sid) - total_secondary += 1 - rows.append({ - "doc": d["name"], "sel": sel["name"], "mode": sel["mode"], - "verbs": len(verbs), "slots": len(grouped), - "second": len(secondaries), - "detail": {sid: [v["name"] for v in vs] for sid, vs in grouped.items()}, - }) - - # Form-factor comparison: the SAME state as a ring and as a vertical list. - for sid in ("face_planar", "edge_str", "body_solid", "sk_line", "none"): - sel = next(s for s in A["selections"] if s["id"] == sid) - d = docs["fresh"] if sid == "none" else docs["rich"] - with open(os.path.join(outdir, f"list__{sid}.svg"), "w", encoding="utf-8") as f: - f.write(render_list_state(A, sel, d)) - for sid, fam in (("face_planar", "add"), ("sk_none", "create")): - sel = next(s for s in A["selections"] if s["id"] == sid) - with open(os.path.join(outdir, f"list__{sid}__{fam}.svg"), "w", encoding="utf-8") as f: - f.write(render_list_state(A, sel, docs["rich"], expand=fam)) - - # comparison sheet: 8 vs 12 slots on the same three selections - for cap in (8, 12): - for sid in ("face_planar", "edge_str", "sk_line"): - sel = next(s for s in A["selections"] if s["id"] == sid) - svg, _, _ = render_state(A, sel, docs["rich"], capacity=cap) - n = f'cmp_{cap}__{sid}.svg' - with open(os.path.join(outdir, n), "w", encoding="utf-8") as f: - f.write(svg) - - write_atlas(A, rows, files, total_primary, total_secondary, - fill_sum / max(1, fill_n)) - print(f"{total_primary} primary + {total_secondary} secondary = " - f"{total_primary+total_secondary} offer states rendered") - print(f"mean populated slots per primary ring: {fill_sum/max(1,fill_n):.2f} of 8") - if OVERFLOWS: - seen = {(f, tuple(v)) for _, _, f, v in OVERFLOWS} - print(f"OVERFLOW: {len(OVERFLOWS)} sub-rings did not fit 8 slots — " - f"{len(seen)} distinct case(s):") - for f, v in sorted(seen): - print(f" {f}: {', '.join(v)} pushed behind 'More'") - - -def write_atlas(A, rows, files, n_prim, n_sec, mean_fill): - gui_missing = [v["name"] for v in A["verbs"] if not v.get("gui", True)] - esc = lambda s: (s.replace("&", "&").replace("<", "<").replace(">", ">")) - cards = "".join( - f'

{esc(t)}
{esc(t)}
' - for n, t in files) - cmp8 = "".join(f'
' - f'
8 slots — {s}
' - for s in ("face_planar", "edge_str", "sk_line")) - cmp12 = "".join(f'
' - f'
12 slots — {s}
' - for s in ("face_planar", "edge_str", "sk_line")) - if OVERFLOWS: - cases = sorted({(f, tuple(v)) for _, _, f, v in OVERFLOWS}) - over_html = ("Measured, not predicted: " + "; ".join( - f'the {esc(f)} sub-ring needs {8 + len(v)} addresses, so ' - f'{esc(", ".join(v))} {"is" if len(v) == 1 else "are"} pushed behind a “More” slot' - for f, v in cases) + - ". This is the decision the ring size actually turns on — either a verb moves to " - "another family, or the tail goes to a third level, or the ring is not eight. " - "It affects sketch mode only; every model-mode family fits.") - else: - over_html = "No sub-ring exceeds the ring capacity. Eight slots is sufficient everywhere." - trs = "".join( - f'{esc(r["sel"])}{esc(r["doc"])}{r["verbs"]}' - f'{r["slots"]}/8{r["second"]}' - f'{esc(" · ".join(k + ": " + ", ".join(v) for k, v in r["detail"].items()))}' - for r in rows) - html = f"""Design tab — offer atlas - -
-

The offer — atlas of every state

-

Every state of the object-driven tool offer, generated from - docs/ux/tool_atlas.json. The invariant under test: a verb has one address, that - address is the same in every selection where it appears, and slots that do not apply are drawn - empty rather than compacted.

-
-
{len(A["verbs"])}verbs mapped
-
{len(A["selections"])}selection kinds
-
{n_prim}primary rings
-
{n_sec}secondary rings
-
{n_prim+n_sec}states total
-
{mean_fill:.1f}/8mean slots filled
-
{len(gui_missing)}verbs with no GUI yet
-
-
-
-

Decision 0 — ring or vertical list

-

The live question. Both forms carry the SAME map and the same invariant — fixed order, never - re-sorted, nothing compacted; only the geometry differs. Left-click selects; right-click opens the - offer. What the list buys: a disabled row can state its own reason in the words the product - already ships, where an empty slot in a ring is mute; nine sketch primitives fit without an - overflow; shortcuts line up in a readable column; long translated names fit; and it is navigable - by arrow key and by screen reader, which a radial is not. What it costs: no equidistant flick - gesture, and travel to the last row is longer than to the nearest direction. Pairs below — - list first, the same state as a ring second.

- -

Decision 1 — ring capacity

-

The same three selections at eight and at twelve. Eight keeps 45° between neighbours, which is - the reliable eyes-free pointing threshold and maps 1:1 to the numpad; twelve buys direct addresses - for more verbs at 30° spacing and positions that stop being nameable.

-
{cmp8}
-
{cmp12}
- -

Decision 2 — one map or one per mode

-

These renders use ONE shared map: sketch verbs occupy the same eight families as model verbs, - so dress-up is south-east whether you picked a solid edge or a sketch line. Compare the sketch-mode - states below against the model-mode ones — if a verb that exists in both modes ever appears at two - addresses, the shared map has failed and the split map is the answer.

- -

The matrix — every selection, every document state

-
- - {trs} -
SelectionDocumentVerbsSlots2nd ringsWhat lands where
- -

Overflow — the one place eight slots is not enough

-

{over_html}

- -

Not in the offer

-

{esc(", ".join(A["chrome_only"]["items"]))} — these act on the document, not on a selection, - so they stay in chrome. Verbs with kernel support but no GUI today, which still hold an address: - {esc(", ".join(gui_missing))}.

- -

All states

-
{cards}
-
-""" - with open(os.path.join(UX, "offer_atlas.html"), "w", encoding="utf-8") as f: - f.write(html) - - # A second, SELF-CONTAINED copy for review outside the repo: the artifact host blocks - # every external request, so relative would silently show nothing. Curated - # rather than all 113 states — the whole set inlined is 1.5 MB, which is a poor thing to - # send to the low-end machine this design is meant to serve (6.1). - def inline(name): - p = os.path.join(HERE, name) - if not os.path.exists(p): - return "" - s = open(p, encoding="utf-8").read() - return s.replace("{inline(n)}
{esc(t)}
' - for n, t in picks if inline(n)) - head, _, tail = html.partition('

All states

') - # The comparison grids in the head use , which resolves to nothing - # once the page is served on its own. Inline those too rather than shipping empty frames. - head = re.sub(r']*>', - lambda m: inline(m.group(1)), head) - inline_html = head + '

The states

\n
' + figs + '
\n' - with open(os.path.join(UX, "offer_atlas_inline.html"), "w", encoding="utf-8") as f: - f.write(inline_html) - - -if __name__ == "__main__": - main() diff --git a/docs/CAD/ux/mockups/list__body_solid.svg b/docs/CAD/ux/mockups/list__body_solid.svg deleted file mode 100644 index ec3b3a84f4..0000000000 --- a/docs/CAD/ux/mockups/list__body_solid.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Right-click the geometry to see what you can do with itWorking document · vertical listSOLID BODYCreateClick a face or a reference plane in the viewport, t…Add materialCreate a sketch, or pick a solid face, firstRemove3Dress-up2Repeat3MoveShift+YReference3Modify3 \ No newline at end of file diff --git a/docs/CAD/ux/mockups/list__edge_str.svg b/docs/CAD/ux/mockups/list__edge_str.svg deleted file mode 100644 index 3942000d25..0000000000 --- a/docs/CAD/ux/mockups/list__edge_str.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Right-click the geometry to see what you can do with itWorking document · vertical listSTRAIGHT EDGECreateClick a face or a reference plane in the viewport, t…Add materialCreate a sketch, or pick a solid face, firstRemovePick a face or a plane to drill intoDress-up2Pattern on CurveTransformTransform needs a body — add or import one firstReference3ModifyDelete Face needs a body — add or import one first \ No newline at end of file diff --git a/docs/CAD/ux/mockups/list__face_planar.svg b/docs/CAD/ux/mockups/list__face_planar.svg deleted file mode 100644 index 5ff6892902..0000000000 --- a/docs/CAD/ux/mockups/list__face_planar.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Right-click the geometry to see what you can do with itWorking document · vertical listPLANAR FACESketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2 \ No newline at end of file diff --git a/docs/CAD/ux/mockups/list__face_planar__add.svg b/docs/CAD/ux/mockups/list__face_planar__add.svg deleted file mode 100644 index fb80f2a5c7..0000000000 --- a/docs/CAD/ux/mockups/list__face_planar__add.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Planar face — pick oneWorking document · vertical listPLANAR FACESketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2ExtrudeShift+EThicken \ No newline at end of file diff --git a/docs/CAD/ux/mockups/list__none.svg b/docs/CAD/ux/mockups/list__none.svg deleted file mode 100644 index 1e68606383..0000000000 --- a/docs/CAD/ux/mockups/list__none.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESnothing yetRight-click the geometry to see what you can do with itFresh document · vertical listNOTHING SELECTEDSketchShift+SAdd materialCreate a sketch, or pick a solid face, firstRemovePick a face or a plane to drill intoDress-upPick an edge to roundRepeatCreate a solid body to pattern firstTransformTransform needs a body — add or import one firstReference4ModifyDelete Face needs a body — add or import one first \ No newline at end of file diff --git a/docs/CAD/ux/mockups/list__sk_line.svg b/docs/CAD/ux/mockups/list__sk_line.svg deleted file mode 100644 index 1b1c2e9cd2..0000000000 --- a/docs/CAD/ux/mockups/list__sk_line.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Right-click the geometry to see what you can do with itWorking document · vertical listSKETCH LINECreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2 \ No newline at end of file diff --git a/docs/CAD/ux/mockups/list__sk_none__create.svg b/docs/CAD/ux/mockups/list__sk_none__create.svg deleted file mode 100644 index 0b41268fa6..0000000000 --- a/docs/CAD/ux/mockups/list__sk_none__create.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch, nothing picked — pick oneWorking document · vertical listSKETCH, NOTHING PICKEDCreate9Add materialRemoveDress-upRepeatTransformReference2ModifyLineLRectangleRCircleCArcASlotSEllipseESplineBPolygonGPointP \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__art.svg b/docs/CAD/ux/mockups/rich__art.svg deleted file mode 100644 index e835ed1ce0..0000000000 --- a/docs/CAD/ux/mockups/rich__art.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Text / imported art selected — pick what to do with itWorking document · 8 slotsABCPatternShift+NMoveShift+YModify2Text / imported art \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__art__modify.svg b/docs/CAD/ux/mockups/rich__art__modify.svg deleted file mode 100644 index 4c8cdef2b5..0000000000 --- a/docs/CAD/ux/mockups/rich__art__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsABCDeleteDelEditText / imported art · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__bodies_2.svg b/docs/CAD/ux/mockups/rich__bodies_2.svg deleted file mode 100644 index a977b7f1de..0000000000 --- a/docs/CAD/ux/mockups/rich__bodies_2.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Two bodies selected — pick what to do with itWorking document · 8 slotsCombineShift+BMateReference2DeleteDelTwo bodies \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__bodies_2__reference.svg b/docs/CAD/ux/mockups/rich__bodies_2__reference.svg deleted file mode 100644 index 1bfbac0db0..0000000000 --- a/docs/CAD/ux/mockups/rich__bodies_2__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsMeasureInterferenceTwo bodies · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__body_sheet.svg b/docs/CAD/ux/mockups/rich__body_sheet.svg deleted file mode 100644 index a1cc27808d..0000000000 --- a/docs/CAD/ux/mockups/rich__body_sheet.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sheet body selected — pick what to do with itWorking document · 8 slotsThicken SurfaceSurface OffsetMoveShift+YMeasureModify3Sheet body \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__body_sheet__modify.svg b/docs/CAD/ux/mockups/rich__body_sheet__modify.svg deleted file mode 100644 index 7ce8dbee54..0000000000 --- a/docs/CAD/ux/mockups/rich__body_sheet__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsColourDeleteDelEditSheet body · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__body_solid.svg b/docs/CAD/ux/mockups/rich__body_solid.svg deleted file mode 100644 index e958532347..0000000000 --- a/docs/CAD/ux/mockups/rich__body_solid.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Solid body selected — pick what to do with itWorking document · 8 slotsRemove3Dress-up2Repeat3MoveShift+YReference3Modify3Solid body \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__body_solid__dressup.svg b/docs/CAD/ux/mockups/rich__body_solid__dressup.svg deleted file mode 100644 index 7a9ceed334..0000000000 --- a/docs/CAD/ux/mockups/rich__body_solid__dressup.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Dress-up — pick oneWorking document · 8 slotsFilletShift+FChamferSolid body · Dress-up \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__body_solid__modify.svg b/docs/CAD/ux/mockups/rich__body_solid__modify.svg deleted file mode 100644 index 7a6340d40f..0000000000 --- a/docs/CAD/ux/mockups/rich__body_solid__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsColourDeleteDelEditSolid body · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__body_solid__reference.svg b/docs/CAD/ux/mockups/rich__body_solid__reference.svg deleted file mode 100644 index 27639836c6..0000000000 --- a/docs/CAD/ux/mockups/rich__body_solid__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsMassProjectMeasureSolid body · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__body_solid__remove.svg b/docs/CAD/ux/mockups/rich__body_solid__remove.svg deleted file mode 100644 index edbddb7115..0000000000 --- a/docs/CAD/ux/mockups/rich__body_solid__remove.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Remove — pick oneWorking document · 8 slotsShellShift+KCutShift+XSplitSolid body · Remove \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__body_solid__repeat.svg b/docs/CAD/ux/mockups/rich__body_solid__repeat.svg deleted file mode 100644 index 74dd67c7de..0000000000 --- a/docs/CAD/ux/mockups/rich__body_solid__repeat.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Repeat — pick oneWorking document · 8 slotsPatternShift+NMirrorShift+ZPattern on CurveSolid body · Repeat \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__coordsys.svg b/docs/CAD/ux/mockups/rich__coordsys.svg deleted file mode 100644 index 8cef106106..0000000000 --- a/docs/CAD/ux/mockups/rich__coordsys.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Coordinate system selected — pick what to do with itWorking document · 8 slotsMateModify2Coordinate system \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__coordsys__modify.svg b/docs/CAD/ux/mockups/rich__coordsys__modify.svg deleted file mode 100644 index 92eb28d3dc..0000000000 --- a/docs/CAD/ux/mockups/rich__coordsys__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsDeleteDelEditCoordinate system · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__datum_axis.svg b/docs/CAD/ux/mockups/rich__datum_axis.svg deleted file mode 100644 index c9da648ab7..0000000000 --- a/docs/CAD/ux/mockups/rich__datum_axis.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Datum axis selected — pick what to do with itWorking document · 8 slotsModify2Datum axis \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__datum_axis__modify.svg b/docs/CAD/ux/mockups/rich__datum_axis__modify.svg deleted file mode 100644 index fd0ae80c52..0000000000 --- a/docs/CAD/ux/mockups/rich__datum_axis__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsDeleteDelEditDatum axis · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__datum_plane.svg b/docs/CAD/ux/mockups/rich__datum_plane.svg deleted file mode 100644 index 8ef88bd9a3..0000000000 --- a/docs/CAD/ux/mockups/rich__datum_plane.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Datum plane selected — pick what to do with itWorking document · 8 slotsSketchShift+SRemove2MirrorShift+ZReference3Modify2Datum plane \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__datum_plane__modify.svg b/docs/CAD/ux/mockups/rich__datum_plane__modify.svg deleted file mode 100644 index 39e5cad41e..0000000000 --- a/docs/CAD/ux/mockups/rich__datum_plane__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsDeleteDelEditDatum plane · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__datum_plane__reference.svg b/docs/CAD/ux/mockups/rich__datum_plane__reference.svg deleted file mode 100644 index bae3119b6b..0000000000 --- a/docs/CAD/ux/mockups/rich__datum_plane__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsProjectPlaneShift+PHelixDatum plane · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__datum_plane__remove.svg b/docs/CAD/ux/mockups/rich__datum_plane__remove.svg deleted file mode 100644 index c98b33ad31..0000000000 --- a/docs/CAD/ux/mockups/rich__datum_plane__remove.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Remove — pick oneWorking document · 8 slotsHoleShift+HCutShift+XDatum plane · Remove \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__edge_circ.svg b/docs/CAD/ux/mockups/rich__edge_circ.svg deleted file mode 100644 index 1d07291623..0000000000 --- a/docs/CAD/ux/mockups/rich__edge_circ.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Circular edge selected — pick what to do with itWorking document · 8 slotsThreadShift+TDress-up2MeasureCircular edge \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__edge_circ__dressup.svg b/docs/CAD/ux/mockups/rich__edge_circ__dressup.svg deleted file mode 100644 index b315fc2e22..0000000000 --- a/docs/CAD/ux/mockups/rich__edge_circ__dressup.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Dress-up — pick oneWorking document · 8 slotsFilletShift+FChamferCircular edge · Dress-up \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__edge_str.svg b/docs/CAD/ux/mockups/rich__edge_str.svg deleted file mode 100644 index 2c41ce74fc..0000000000 --- a/docs/CAD/ux/mockups/rich__edge_str.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Straight edge selected — pick what to do with itWorking document · 8 slotsDress-up2Pattern on CurveReference3Straight edge \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__edge_str__dressup.svg b/docs/CAD/ux/mockups/rich__edge_str__dressup.svg deleted file mode 100644 index 8e674c044f..0000000000 --- a/docs/CAD/ux/mockups/rich__edge_str__dressup.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Dress-up — pick oneWorking document · 8 slotsFilletShift+FChamferStraight edge · Dress-up \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__edge_str__reference.svg b/docs/CAD/ux/mockups/rich__edge_str__reference.svg deleted file mode 100644 index bc99ca6a13..0000000000 --- a/docs/CAD/ux/mockups/rich__edge_str__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsMeasurePlaneShift+PAxisShift+AStraight edge · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__face_cyl.svg b/docs/CAD/ux/mockups/rich__face_cyl.svg deleted file mode 100644 index 3c2860373f..0000000000 --- a/docs/CAD/ux/mockups/rich__face_cyl.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Cylindrical face selected — pick what to do with itWorking document · 8 slotsThreadShift+TReference3Modify2Cylindrical face \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__face_cyl__modify.svg b/docs/CAD/ux/mockups/rich__face_cyl__modify.svg deleted file mode 100644 index 9f573682ad..0000000000 --- a/docs/CAD/ux/mockups/rich__face_cyl__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsDelete FaceEditCylindrical face · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__face_cyl__reference.svg b/docs/CAD/ux/mockups/rich__face_cyl__reference.svg deleted file mode 100644 index a62be81de8..0000000000 --- a/docs/CAD/ux/mockups/rich__face_cyl__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsMeasureAxisShift+AHelixCylindrical face · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__face_other.svg b/docs/CAD/ux/mockups/rich__face_other.svg deleted file mode 100644 index e5cff4eee5..0000000000 --- a/docs/CAD/ux/mockups/rich__face_other.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Curved face selected — pick what to do with itWorking document · 8 slotsThickenDraftShift+DMeasureModify2Curved face \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__face_other__modify.svg b/docs/CAD/ux/mockups/rich__face_other__modify.svg deleted file mode 100644 index 8b5fcfd63f..0000000000 --- a/docs/CAD/ux/mockups/rich__face_other__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsDelete FaceEditCurved face · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__face_planar.svg b/docs/CAD/ux/mockups/rich__face_planar.svg deleted file mode 100644 index 5a58e2fb72..0000000000 --- a/docs/CAD/ux/mockups/rich__face_planar.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Planar face selected — pick what to do with itWorking document · 8 slotsSketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2Planar face \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__face_planar__add.svg b/docs/CAD/ux/mockups/rich__face_planar__add.svg deleted file mode 100644 index 82acc85038..0000000000 --- a/docs/CAD/ux/mockups/rich__face_planar__add.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Add material — pick oneWorking document · 8 slotsExtrudeShift+EThickenPlanar face · Add material \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__face_planar__dressup.svg b/docs/CAD/ux/mockups/rich__face_planar__dressup.svg deleted file mode 100644 index 8d32b9f212..0000000000 --- a/docs/CAD/ux/mockups/rich__face_planar__dressup.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Dress-up — pick oneWorking document · 8 slotsFilletShift+FChamferDraftShift+DPlanar face · Dress-up \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__face_planar__modify.svg b/docs/CAD/ux/mockups/rich__face_planar__modify.svg deleted file mode 100644 index bffd218131..0000000000 --- a/docs/CAD/ux/mockups/rich__face_planar__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsDelete FaceEditPlanar face · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__face_planar__reference.svg b/docs/CAD/ux/mockups/rich__face_planar__reference.svg deleted file mode 100644 index 4682f1e7b1..0000000000 --- a/docs/CAD/ux/mockups/rich__face_planar__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsCoord SysShift+CProjectMeasurePlaneShift+PAxisShift+APlanar face · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__face_planar__remove.svg b/docs/CAD/ux/mockups/rich__face_planar__remove.svg deleted file mode 100644 index b77a5a0c50..0000000000 --- a/docs/CAD/ux/mockups/rich__face_planar__remove.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Remove — pick oneWorking document · 8 slotsHoleShift+HShellShift+KPlanar face · Remove \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__face_planar__transform.svg b/docs/CAD/ux/mockups/rich__face_planar__transform.svg deleted file mode 100644 index 2e27983c8b..0000000000 --- a/docs/CAD/ux/mockups/rich__face_planar__transform.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Transform — pick oneWorking document · 8 slotsMateAlign toPlanar face · Transform \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__none.svg b/docs/CAD/ux/mockups/rich__none.svg deleted file mode 100644 index 4e8a9dc640..0000000000 --- a/docs/CAD/ux/mockups/rich__none.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Click a face or a reference plane, then a toolWorking document · 8 slotsSketchShift+SReference4Nothing selected \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__none__reference.svg b/docs/CAD/ux/mockups/rich__none__reference.svg deleted file mode 100644 index 14c47c4b27..0000000000 --- a/docs/CAD/ux/mockups/rich__none__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsCoord SysShift+CHelixPlaneShift+PAxisShift+ANothing selected · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_2ent.svg b/docs/CAD/ux/mockups/rich__sk_2ent.svg deleted file mode 100644 index 738b01ecf2..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_2ent.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Two sketch entities selected — pick what to do with itWorking document · 8 slotsCreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2Two sketch entities \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_2ent__create.svg b/docs/CAD/ux/mockups/rich__sk_2ent__create.svg deleted file mode 100644 index 6d27ffa34a..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_2ent__create.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Create — pick oneWorking document · 8 slotsLineLRectangleRCircleCArcASlotSEllipseESplineBMore2Two sketch entities · Create \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_2ent__dressup.svg b/docs/CAD/ux/mockups/rich__sk_2ent__dressup.svg deleted file mode 100644 index 929520b8d2..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_2ent__dressup.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Dress-up — pick oneWorking document · 8 slotsFilletFChamferHTwo sketch entities · Dress-up \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_2ent__modify.svg b/docs/CAD/ux/mockups/rich__sk_2ent__modify.svg deleted file mode 100644 index d8be7c2d4f..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_2ent__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsDeleteDelExtendXTwo sketch entities · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_2ent__reference.svg b/docs/CAD/ux/mockups/rich__sk_2ent__reference.svg deleted file mode 100644 index 9ffc2592a8..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_2ent__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsConstructionQDimensionDConstrainKTwo sketch entities · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_arc.svg b/docs/CAD/ux/mockups/rich__sk_arc.svg deleted file mode 100644 index d470369f1a..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_arc.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch arc or circle selected — pick what to do with itWorking document · 8 slotsCreate9OffsetOTrimTMirrorMMoveReference3Modify2Sketch arc or circle \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_arc__create.svg b/docs/CAD/ux/mockups/rich__sk_arc__create.svg deleted file mode 100644 index 1f901596ec..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_arc__create.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Create — pick oneWorking document · 8 slotsLineLRectangleRCircleCArcASlotSEllipseESplineBMore2Sketch arc or circle · Create \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_arc__modify.svg b/docs/CAD/ux/mockups/rich__sk_arc__modify.svg deleted file mode 100644 index 52f9d7d3e5..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_arc__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsDeleteDelExtendXSketch arc or circle · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_arc__reference.svg b/docs/CAD/ux/mockups/rich__sk_arc__reference.svg deleted file mode 100644 index 53e949c184..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_arc__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsConstructionQDimensionDConstrainKSketch arc or circle · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_line.svg b/docs/CAD/ux/mockups/rich__sk_line.svg deleted file mode 100644 index 4ca4000d59..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_line.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch line selected — pick what to do with itWorking document · 8 slotsCreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2Sketch line \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_line__create.svg b/docs/CAD/ux/mockups/rich__sk_line__create.svg deleted file mode 100644 index c37f2d0e45..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_line__create.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Create — pick oneWorking document · 8 slotsLineLRectangleRCircleCArcASlotSEllipseESplineBMore2Sketch line · Create \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_line__dressup.svg b/docs/CAD/ux/mockups/rich__sk_line__dressup.svg deleted file mode 100644 index 29e86762d2..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_line__dressup.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Dress-up — pick oneWorking document · 8 slotsFilletFChamferHSketch line · Dress-up \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_line__modify.svg b/docs/CAD/ux/mockups/rich__sk_line__modify.svg deleted file mode 100644 index b9775b7d94..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_line__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsDeleteDelExtendXSketch line · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_line__reference.svg b/docs/CAD/ux/mockups/rich__sk_line__reference.svg deleted file mode 100644 index 6eb1ef621f..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_line__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsConstructionQDimensionDConstrainKSketch line · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_loop.svg b/docs/CAD/ux/mockups/rich__sk_loop.svg deleted file mode 100644 index c07d1c09c3..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_loop.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Closed sketch loop selected — pick what to do with itWorking document · 8 slotsAdd material8PatternShift+NModify2Closed sketch loop \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_loop__add.svg b/docs/CAD/ux/mockups/rich__sk_loop__add.svg deleted file mode 100644 index bf9f429b81..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_loop__add.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Add material — pick oneWorking document · 8 slotsSurface FillExtrudeShift+ERevolveShift+RSweepShift+WLoftShift+LSurface ExtrudeShift+GSurface RevolveSurface LoftClosed sketch loop · Add material \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_loop__modify.svg b/docs/CAD/ux/mockups/rich__sk_loop__modify.svg deleted file mode 100644 index c466379c9e..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_loop__modify.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Modify — pick oneWorking document · 8 slotsDeleteDelEditClosed sketch loop · Modify \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_none.svg b/docs/CAD/ux/mockups/rich__sk_none.svg deleted file mode 100644 index ffe49ab9a0..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_none.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch, nothing picked selected — pick what to do with itWorking document · 8 slotsCreate9Reference2Sketch, nothing picked \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_none__create.svg b/docs/CAD/ux/mockups/rich__sk_none__create.svg deleted file mode 100644 index c9a2bc698d..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_none__create.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Create — pick oneWorking document · 8 slotsLineLRectangleRCircleCArcASlotSEllipseESplineBMore2Sketch, nothing picked · Create \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_none__reference.svg b/docs/CAD/ux/mockups/rich__sk_none__reference.svg deleted file mode 100644 index cc9d465fa4..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_none__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsDimensionDConstructionQSketch, nothing picked · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_point.svg b/docs/CAD/ux/mockups/rich__sk_point.svg deleted file mode 100644 index f089787bca..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_point.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch point selected — pick what to do with itWorking document · 8 slotsCreate9MoveReference2DeleteDelSketch point \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_point__create.svg b/docs/CAD/ux/mockups/rich__sk_point__create.svg deleted file mode 100644 index 5d2070a6c5..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_point__create.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Create — pick oneWorking document · 8 slotsLineLRectangleRCircleCArcASlotSEllipseESplineBMore2Sketch point · Create \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__sk_point__reference.svg b/docs/CAD/ux/mockups/rich__sk_point__reference.svg deleted file mode 100644 index e090382a24..0000000000 --- a/docs/CAD/ux/mockups/rich__sk_point__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsDimensionDConstrainKSketch point · Reference \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__vertex.svg b/docs/CAD/ux/mockups/rich__vertex.svg deleted file mode 100644 index f37bbd7d47..0000000000 --- a/docs/CAD/ux/mockups/rich__vertex.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Vertex selected — pick what to do with itWorking document · 8 slotsReference4Vertex \ No newline at end of file diff --git a/docs/CAD/ux/mockups/rich__vertex__reference.svg b/docs/CAD/ux/mockups/rich__vertex__reference.svg deleted file mode 100644 index ceabba032e..0000000000 --- a/docs/CAD/ux/mockups/rich__vertex__reference.svg +++ /dev/null @@ -1 +0,0 @@ -PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsCoord SysShift+CMeasurePlaneShift+PAxisShift+AVertex · Reference \ No newline at end of file diff --git a/docs/CAD/ux/offer_atlas.html b/docs/CAD/ux/offer_atlas.html deleted file mode 100644 index 35c4f5860c..0000000000 --- a/docs/CAD/ux/offer_atlas.html +++ /dev/null @@ -1,78 +0,0 @@ -Design tab — offer atlas - -
-

The offer — atlas of every state

-

Every state of the object-driven tool offer, generated from - docs/ux/tool_atlas.json. The invariant under test: a verb has one address, that - address is the same in every selection where it appears, and slots that do not apply are drawn - empty rather than compacted.

-
-
60verbs mapped
-
20selection kinds
-
40primary rings
-
73secondary rings
-
113states total
-
3.5/8mean slots filled
-
5verbs with no GUI yet
-
-
-
-

Decision 0 — ring or vertical list

-

The live question. Both forms carry the SAME map and the same invariant — fixed order, never - re-sorted, nothing compacted; only the geometry differs. Left-click selects; right-click opens the - offer. What the list buys: a disabled row can state its own reason in the words the product - already ships, where an empty slot in a ring is mute; nine sketch primitives fit without an - overflow; shortcuts line up in a readable column; long translated names fit; and it is navigable - by arrow key and by screen reader, which a radial is not. What it costs: no equidistant flick - gesture, and travel to the last row is longer than to the nearest direction. Pairs below — - list first, the same state as a ring second.

- -

Decision 1 — ring capacity

-

The same three selections at eight and at twelve. Eight keeps 45° between neighbours, which is - the reliable eyes-free pointing threshold and maps 1:1 to the numpad; twelve buys direct addresses - for more verbs at 30° spacing and positions that stop being nameable.

-
8 slots — face_planar
8 slots — edge_str
8 slots — sk_line
-
12 slots — face_planar
12 slots — edge_str
12 slots — sk_line
- -

Decision 2 — one map or one per mode

-

These renders use ONE shared map: sketch verbs occupy the same eight families as model verbs, - so dress-up is south-east whether you picked a solid edge or a sketch line. Compare the sketch-mode - states below against the model-mode ones — if a verb that exists in both modes ever appears at two - addresses, the shared map has failed and the split map is the answer.

- -

The matrix — every selection, every document state

-
- - -
SelectionDocumentVerbsSlots2nd ringsWhat lands where
Nothing selectedWorking document52/81create: Sketch · reference: Plane, Axis, Coord Sys, Helix
Planar faceWorking document188/86create: Sketch · add: Extrude, Thicken · remove: Hole, Shell · dressup: Fillet, Chamfer, Draft · repeat: Pattern · transform: Mate, Align to · reference: Plane, Axis, Coord Sys, Project, Measure · modify: Edit, Delete Face
Cylindrical faceWorking document63/82remove: Thread · reference: Axis, Helix, Measure · modify: Edit, Delete Face
Curved faceWorking document54/81add: Thicken · dressup: Draft · reference: Measure · modify: Edit, Delete Face
Straight edgeWorking document63/82dressup: Fillet, Chamfer · repeat: Pattern on Curve · reference: Plane, Axis, Measure
Circular edgeWorking document43/81remove: Thread · dressup: Fillet, Chamfer · reference: Measure
VertexWorking document41/81reference: Plane, Axis, Coord Sys, Measure
Solid bodyWorking document156/85remove: Shell, Cut, Split · dressup: Fillet, Chamfer · repeat: Pattern, Mirror, Pattern on Curve · transform: Move · reference: Project, Measure, Mass · modify: Edit, Colour, Delete
Sheet bodyWorking document75/81add: Thicken Surface · dressup: Surface Offset · transform: Move · reference: Measure · modify: Edit, Colour, Delete
Two bodiesWorking document54/81add: Combine · transform: Mate · reference: Measure, Interference · modify: Delete
Datum planeWorking document95/83create: Sketch · remove: Hole, Cut · repeat: Mirror · reference: Plane, Helix, Project · modify: Edit, Delete
Datum axisWorking document21/81modify: Edit, Delete
Coordinate systemWorking document32/81transform: Mate · modify: Edit, Delete
Text / imported artWorking document43/81repeat: Pattern · transform: Move · modify: Edit, Delete
Closed sketch loopWorking document113/82add: Extrude, Revolve, Sweep, Loft, Surface Extrude, Surface Revolve, Surface Loft, Surface Fill · repeat: Pattern · modify: Edit, Delete
Sketch, nothing pickedWorking document112/82create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · reference: Dimension, Construction
Sketch lineWorking document208/84create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · add: Offset · remove: Trim · dressup: Fillet, Chamfer · repeat: Mirror · transform: Move · reference: Dimension, Constrain, Construction · modify: Extend, Delete
Sketch arc or circleWorking document187/83create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · add: Offset · remove: Trim · repeat: Mirror · transform: Move · reference: Dimension, Constrain, Construction · modify: Extend, Delete
Sketch pointWorking document134/82create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · transform: Move · reference: Dimension, Constrain · modify: Delete
Two sketch entitiesWorking document208/84create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · add: Offset · remove: Trim · dressup: Fillet, Chamfer · repeat: Mirror · transform: Move · reference: Dimension, Constrain, Construction · modify: Extend, Delete
Nothing selectedFresh document52/81create: Sketch · reference: Plane, Axis, Coord Sys, Helix
Planar faceFresh document74/81create: Sketch · add: Extrude · reference: Plane, Axis, Coord Sys, Measure · modify: Edit
Cylindrical faceFresh document42/81reference: Axis, Helix, Measure · modify: Edit
Curved faceFresh document22/80reference: Measure · modify: Edit
Straight edgeFresh document31/81reference: Plane, Axis, Measure
Circular edgeFresh document11/80reference: Measure
VertexFresh document41/81reference: Plane, Axis, Coord Sys, Measure
Solid bodyFresh document32/81reference: Measure · modify: Edit, Delete
Sheet bodyFresh document32/81reference: Measure · modify: Edit, Delete
Two bodiesFresh document22/80reference: Measure · modify: Delete
Datum planeFresh document53/82create: Sketch · reference: Plane, Helix · modify: Edit, Delete
Datum axisFresh document21/81modify: Edit, Delete
Coordinate systemFresh document21/81modify: Edit, Delete
Text / imported artFresh document21/81modify: Edit, Delete
Closed sketch loopFresh document72/82add: Extrude, Revolve, Surface Extrude, Surface Revolve, Surface Fill · modify: Edit, Delete
Sketch, nothing pickedFresh document112/82create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · reference: Dimension, Construction
Sketch lineFresh document208/84create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · add: Offset · remove: Trim · dressup: Fillet, Chamfer · repeat: Mirror · transform: Move · reference: Dimension, Constrain, Construction · modify: Extend, Delete
Sketch arc or circleFresh document187/83create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · add: Offset · remove: Trim · repeat: Mirror · transform: Move · reference: Dimension, Constrain, Construction · modify: Extend, Delete
Sketch pointFresh document134/82create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · transform: Move · reference: Dimension, Constrain · modify: Delete
Two sketch entitiesFresh document208/84create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · add: Offset · remove: Trim · dressup: Fillet, Chamfer · repeat: Mirror · transform: Move · reference: Dimension, Constrain, Construction · modify: Extend, Delete
- -

Overflow — the one place eight slots is not enough

-

Measured, not predicted: the create sub-ring needs 10 addresses, so Polygon, Point are pushed behind a “More” slot. This is the decision the ring size actually turns on — either a verb moves to another family, or the tail goes to a third level, or the ring is not eight. It affects sketch mode only; every model-mode family fits.

- -

Not in the offer

-

Import STEP, Import mesh, Text, SVG, Export STEP, Commit to Plate, Undo, Redo, Variables, Section view, Origin planes, World axes — these act on the document, not on a selection, - so they stay in chrome. Verbs with kernel support but no GUI today, which still hold an address: - Split, Pattern on Curve, Align to, Measure, Interference.

- -

All states

-
Nothing selected — Working document
Nothing selected — Working document
Nothing selected — Reference ring
Nothing selected — Reference ring
Planar face — Working document
Planar face — Working document
Planar face — Add material ring
Planar face — Add material ring
Planar face — Remove ring
Planar face — Remove ring
Planar face — Dress-up ring
Planar face — Dress-up ring
Planar face — Transform ring
Planar face — Transform ring
Planar face — Reference ring
Planar face — Reference ring
Planar face — Modify ring
Planar face — Modify ring
Cylindrical face — Working document
Cylindrical face — Working document
Cylindrical face — Reference ring
Cylindrical face — Reference ring
Cylindrical face — Modify ring
Cylindrical face — Modify ring
Curved face — Working document
Curved face — Working document
Curved face — Modify ring
Curved face — Modify ring
Straight edge — Working document
Straight edge — Working document
Straight edge — Dress-up ring
Straight edge — Dress-up ring
Straight edge — Reference ring
Straight edge — Reference ring
Circular edge — Working document
Circular edge — Working document
Circular edge — Dress-up ring
Circular edge — Dress-up ring
Vertex — Working document
Vertex — Working document
Vertex — Reference ring
Vertex — Reference ring
Solid body — Working document
Solid body — Working document
Solid body — Remove ring
Solid body — Remove ring
Solid body — Dress-up ring
Solid body — Dress-up ring
Solid body — Repeat ring
Solid body — Repeat ring
Solid body — Reference ring
Solid body — Reference ring
Solid body — Modify ring
Solid body — Modify ring
Sheet body — Working document
Sheet body — Working document
Sheet body — Modify ring
Sheet body — Modify ring
Two bodies — Working document
Two bodies — Working document
Two bodies — Reference ring
Two bodies — Reference ring
Datum plane — Working document
Datum plane — Working document
Datum plane — Remove ring
Datum plane — Remove ring
Datum plane — Reference ring
Datum plane — Reference ring
Datum plane — Modify ring
Datum plane — Modify ring
Datum axis — Working document
Datum axis — Working document
Datum axis — Modify ring
Datum axis — Modify ring
Coordinate system — Working document
Coordinate system — Working document
Coordinate system — Modify ring
Coordinate system — Modify ring
Text / imported art — Working document
Text / imported art — Working document
Text / imported art — Modify ring
Text / imported art — Modify ring
Closed sketch loop — Working document
Closed sketch loop — Working document
Closed sketch loop — Add material ring
Closed sketch loop — Add material ring
Closed sketch loop — Modify ring
Closed sketch loop — Modify ring
Sketch, nothing picked — Working document
Sketch, nothing picked — Working document
Sketch, nothing picked — Create ring
Sketch, nothing picked — Create ring
Sketch, nothing picked — Reference ring
Sketch, nothing picked — Reference ring
Sketch line — Working document
Sketch line — Working document
Sketch line — Create ring
Sketch line — Create ring
Sketch line — Dress-up ring
Sketch line — Dress-up ring
Sketch line — Reference ring
Sketch line — Reference ring
Sketch line — Modify ring
Sketch line — Modify ring
Sketch arc or circle — Working document
Sketch arc or circle — Working document
Sketch arc or circle — Create ring
Sketch arc or circle — Create ring
Sketch arc or circle — Reference ring
Sketch arc or circle — Reference ring
Sketch arc or circle — Modify ring
Sketch arc or circle — Modify ring
Sketch point — Working document
Sketch point — Working document
Sketch point — Create ring
Sketch point — Create ring
Sketch point — Reference ring
Sketch point — Reference ring
Two sketch entities — Working document
Two sketch entities — Working document
Two sketch entities — Create ring
Two sketch entities — Create ring
Two sketch entities — Dress-up ring
Two sketch entities — Dress-up ring
Two sketch entities — Reference ring
Two sketch entities — Reference ring
Two sketch entities — Modify ring
Two sketch entities — Modify ring
Nothing selected — Fresh document
Nothing selected — Fresh document
Nothing selected — Reference ring
Nothing selected — Reference ring
Planar face — Fresh document
Planar face — Fresh document
Planar face — Reference ring
Planar face — Reference ring
Cylindrical face — Fresh document
Cylindrical face — Fresh document
Cylindrical face — Reference ring
Cylindrical face — Reference ring
Curved face — Fresh document
Curved face — Fresh document
Straight edge — Fresh document
Straight edge — Fresh document
Straight edge — Reference ring
Straight edge — Reference ring
Circular edge — Fresh document
Circular edge — Fresh document
Vertex — Fresh document
Vertex — Fresh document
Vertex — Reference ring
Vertex — Reference ring
Solid body — Fresh document
Solid body — Fresh document
Solid body — Modify ring
Solid body — Modify ring
Sheet body — Fresh document
Sheet body — Fresh document
Sheet body — Modify ring
Sheet body — Modify ring
Two bodies — Fresh document
Two bodies — Fresh document
Datum plane — Fresh document
Datum plane — Fresh document
Datum plane — Reference ring
Datum plane — Reference ring
Datum plane — Modify ring
Datum plane — Modify ring
Datum axis — Fresh document
Datum axis — Fresh document
Datum axis — Modify ring
Datum axis — Modify ring
Coordinate system — Fresh document
Coordinate system — Fresh document
Coordinate system — Modify ring
Coordinate system — Modify ring
Text / imported art — Fresh document
Text / imported art — Fresh document
Text / imported art — Modify ring
Text / imported art — Modify ring
Closed sketch loop — Fresh document
Closed sketch loop — Fresh document
Closed sketch loop — Add material ring
Closed sketch loop — Add material ring
Closed sketch loop — Modify ring
Closed sketch loop — Modify ring
Sketch, nothing picked — Fresh document
Sketch, nothing picked — Fresh document
Sketch, nothing picked — Create ring
Sketch, nothing picked — Create ring
Sketch, nothing picked — Reference ring
Sketch, nothing picked — Reference ring
Sketch line — Fresh document
Sketch line — Fresh document
Sketch line — Create ring
Sketch line — Create ring
Sketch line — Dress-up ring
Sketch line — Dress-up ring
Sketch line — Reference ring
Sketch line — Reference ring
Sketch line — Modify ring
Sketch line — Modify ring
Sketch arc or circle — Fresh document
Sketch arc or circle — Fresh document
Sketch arc or circle — Create ring
Sketch arc or circle — Create ring
Sketch arc or circle — Reference ring
Sketch arc or circle — Reference ring
Sketch arc or circle — Modify ring
Sketch arc or circle — Modify ring
Sketch point — Fresh document
Sketch point — Fresh document
Sketch point — Create ring
Sketch point — Create ring
Sketch point — Reference ring
Sketch point — Reference ring
Two sketch entities — Fresh document
Two sketch entities — Fresh document
Two sketch entities — Create ring
Two sketch entities — Create ring
Two sketch entities — Dress-up ring
Two sketch entities — Dress-up ring
Two sketch entities — Reference ring
Two sketch entities — Reference ring
Two sketch entities — Modify ring
Two sketch entities — Modify ring
-
diff --git a/docs/CAD/ux/offer_atlas_inline.html b/docs/CAD/ux/offer_atlas_inline.html deleted file mode 100644 index 3bbfb1b303..0000000000 --- a/docs/CAD/ux/offer_atlas_inline.html +++ /dev/null @@ -1,77 +0,0 @@ -Design tab — offer atlas - -
-

The offer — atlas of every state

-

Every state of the object-driven tool offer, generated from - docs/ux/tool_atlas.json. The invariant under test: a verb has one address, that - address is the same in every selection where it appears, and slots that do not apply are drawn - empty rather than compacted.

-
-
60verbs mapped
-
20selection kinds
-
40primary rings
-
73secondary rings
-
113states total
-
3.5/8mean slots filled
-
5verbs with no GUI yet
-
-
-
-

Decision 0 — ring or vertical list

-

The live question. Both forms carry the SAME map and the same invariant — fixed order, never - re-sorted, nothing compacted; only the geometry differs. Left-click selects; right-click opens the - offer. What the list buys: a disabled row can state its own reason in the words the product - already ships, where an empty slot in a ring is mute; nine sketch primitives fit without an - overflow; shortcuts line up in a readable column; long translated names fit; and it is navigable - by arrow key and by screen reader, which a radial is not. What it costs: no equidistant flick - gesture, and travel to the last row is longer than to the nearest direction. Pairs below — - list first, the same state as a ring second.

- -

Decision 1 — ring capacity

-

The same three selections at eight and at twelve. Eight keeps 45° between neighbours, which is - the reliable eyes-free pointing threshold and maps 1:1 to the numpad; twelve buys direct addresses - for more verbs at 30° spacing and positions that stop being nameable.

-
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Planar face selected — pick what to do with itWorking document · 8 slotsSketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2Planar face
8 slots — face_planar
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Straight edge selected — pick what to do with itWorking document · 8 slotsDress-up2Pattern on CurveReference3Straight edge
8 slots — edge_str
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch line selected — pick what to do with itWorking document · 8 slotsCreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2Sketch line
8 slots — sk_line
-
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Planar face selected — pick what to do with itWorking document · 12 slotsSketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2Planar face
12 slots — face_planar
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Straight edge selected — pick what to do with itWorking document · 12 slotsDress-up2Pattern on CurveReference3Straight edge
12 slots — edge_str
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch line selected — pick what to do with itWorking document · 12 slotsCreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2Sketch line
12 slots — sk_line
- -

Decision 2 — one map or one per mode

-

These renders use ONE shared map: sketch verbs occupy the same eight families as model verbs, - so dress-up is south-east whether you picked a solid edge or a sketch line. Compare the sketch-mode - states below against the model-mode ones — if a verb that exists in both modes ever appears at two - addresses, the shared map has failed and the split map is the answer.

- -

The matrix — every selection, every document state

-
- - -
SelectionDocumentVerbsSlots2nd ringsWhat lands where
Nothing selectedWorking document52/81create: Sketch · reference: Plane, Axis, Coord Sys, Helix
Planar faceWorking document188/86create: Sketch · add: Extrude, Thicken · remove: Hole, Shell · dressup: Fillet, Chamfer, Draft · repeat: Pattern · transform: Mate, Align to · reference: Plane, Axis, Coord Sys, Project, Measure · modify: Edit, Delete Face
Cylindrical faceWorking document63/82remove: Thread · reference: Axis, Helix, Measure · modify: Edit, Delete Face
Curved faceWorking document54/81add: Thicken · dressup: Draft · reference: Measure · modify: Edit, Delete Face
Straight edgeWorking document63/82dressup: Fillet, Chamfer · repeat: Pattern on Curve · reference: Plane, Axis, Measure
Circular edgeWorking document43/81remove: Thread · dressup: Fillet, Chamfer · reference: Measure
VertexWorking document41/81reference: Plane, Axis, Coord Sys, Measure
Solid bodyWorking document156/85remove: Shell, Cut, Split · dressup: Fillet, Chamfer · repeat: Pattern, Mirror, Pattern on Curve · transform: Move · reference: Project, Measure, Mass · modify: Edit, Colour, Delete
Sheet bodyWorking document75/81add: Thicken Surface · dressup: Surface Offset · transform: Move · reference: Measure · modify: Edit, Colour, Delete
Two bodiesWorking document54/81add: Combine · transform: Mate · reference: Measure, Interference · modify: Delete
Datum planeWorking document95/83create: Sketch · remove: Hole, Cut · repeat: Mirror · reference: Plane, Helix, Project · modify: Edit, Delete
Datum axisWorking document21/81modify: Edit, Delete
Coordinate systemWorking document32/81transform: Mate · modify: Edit, Delete
Text / imported artWorking document43/81repeat: Pattern · transform: Move · modify: Edit, Delete
Closed sketch loopWorking document113/82add: Extrude, Revolve, Sweep, Loft, Surface Extrude, Surface Revolve, Surface Loft, Surface Fill · repeat: Pattern · modify: Edit, Delete
Sketch, nothing pickedWorking document112/82create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · reference: Dimension, Construction
Sketch lineWorking document208/84create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · add: Offset · remove: Trim · dressup: Fillet, Chamfer · repeat: Mirror · transform: Move · reference: Dimension, Constrain, Construction · modify: Extend, Delete
Sketch arc or circleWorking document187/83create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · add: Offset · remove: Trim · repeat: Mirror · transform: Move · reference: Dimension, Constrain, Construction · modify: Extend, Delete
Sketch pointWorking document134/82create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · transform: Move · reference: Dimension, Constrain · modify: Delete
Two sketch entitiesWorking document208/84create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · add: Offset · remove: Trim · dressup: Fillet, Chamfer · repeat: Mirror · transform: Move · reference: Dimension, Constrain, Construction · modify: Extend, Delete
Nothing selectedFresh document52/81create: Sketch · reference: Plane, Axis, Coord Sys, Helix
Planar faceFresh document74/81create: Sketch · add: Extrude · reference: Plane, Axis, Coord Sys, Measure · modify: Edit
Cylindrical faceFresh document42/81reference: Axis, Helix, Measure · modify: Edit
Curved faceFresh document22/80reference: Measure · modify: Edit
Straight edgeFresh document31/81reference: Plane, Axis, Measure
Circular edgeFresh document11/80reference: Measure
VertexFresh document41/81reference: Plane, Axis, Coord Sys, Measure
Solid bodyFresh document32/81reference: Measure · modify: Edit, Delete
Sheet bodyFresh document32/81reference: Measure · modify: Edit, Delete
Two bodiesFresh document22/80reference: Measure · modify: Delete
Datum planeFresh document53/82create: Sketch · reference: Plane, Helix · modify: Edit, Delete
Datum axisFresh document21/81modify: Edit, Delete
Coordinate systemFresh document21/81modify: Edit, Delete
Text / imported artFresh document21/81modify: Edit, Delete
Closed sketch loopFresh document72/82add: Extrude, Revolve, Surface Extrude, Surface Revolve, Surface Fill · modify: Edit, Delete
Sketch, nothing pickedFresh document112/82create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · reference: Dimension, Construction
Sketch lineFresh document208/84create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · add: Offset · remove: Trim · dressup: Fillet, Chamfer · repeat: Mirror · transform: Move · reference: Dimension, Constrain, Construction · modify: Extend, Delete
Sketch arc or circleFresh document187/83create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · add: Offset · remove: Trim · repeat: Mirror · transform: Move · reference: Dimension, Constrain, Construction · modify: Extend, Delete
Sketch pointFresh document134/82create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · transform: Move · reference: Dimension, Constrain · modify: Delete
Two sketch entitiesFresh document208/84create: Line, Rectangle, Circle, Arc, Slot, Ellipse, Spline, Polygon, Point · add: Offset · remove: Trim · dressup: Fillet, Chamfer · repeat: Mirror · transform: Move · reference: Dimension, Constrain, Construction · modify: Extend, Delete
- -

Overflow — the one place eight slots is not enough

-

Measured, not predicted: the create sub-ring needs 10 addresses, so Polygon, Point are pushed behind a “More” slot. This is the decision the ring size actually turns on — either a verb moves to another family, or the tail goes to a third level, or the ring is not eight. It affects sketch mode only; every model-mode family fits.

- -

Not in the offer

-

Import STEP, Import mesh, Text, SVG, Export STEP, Commit to Plate, Undo, Redo, Variables, Section view, Origin planes, World axes — these act on the document, not on a selection, - so they stay in chrome. Verbs with kernel support but no GUI today, which still hold an address: - Split, Pattern on Curve, Align to, Measure, Interference.

- -

The states

-
PreparePreviewDesignFEATURESnothing yetClick a face or a reference plane, then a toolFresh document · 8 slotsSketchShift+SReference4Nothing selected
Fresh document, nothing selected — the first-run picture
PreparePreviewDesignFEATURESnothing yetRight-click the geometry to see what you can do with itFresh document · vertical listNOTHING SELECTEDSketchShift+SAdd materialCreate a sketch, or pick a solid face, firstRemovePick a face or a plane to drill intoDress-upPick an edge to roundRepeatCreate a solid body to pattern firstTransformTransform needs a body — add or import one firstReference4ModifyDelete Face needs a body — add or import one first
LIST · fresh document — every family present, the unavailable ones say why
PreparePreviewDesignFEATURESnothing yetClick a face or a reference plane, then a toolFresh document · 8 slotsSketchShift+SReference4Nothing selected
RING · the same state — an empty slot cannot say anything
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Right-click the geometry to see what you can do with itWorking document · vertical listPLANAR FACESketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2
LIST · planar face
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Planar face selected — pick what to do with itWorking document · 8 slotsSketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2Planar face
RING · planar face
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch, nothing picked — pick oneWorking document · vertical listSKETCH, NOTHING PICKEDCreate9Add materialRemoveDress-upRepeatTransformReference2ModifyLineLRectangleRCircleCArcASlotSEllipseESplineBPolygonGPointP
LIST · sketch Create submenu — all 9 primitives fit, no overflow
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Create — pick oneWorking document · 8 slotsLineLRectangleRCircleCArcASlotSEllipseESplineBMore2Sketch, nothing picked · Create
RING · the same submenu — 2 verbs pushed behind “More”
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Planar face — pick oneWorking document · vertical listPLANAR FACESketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2ExtrudeShift+EThicken
LIST · planar face, Add material submenu
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Add material — pick oneWorking document · 8 slotsExtrudeShift+EThickenPlanar face · Add material
RING · planar face, Add material sub-ring
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Right-click the geometry to see what you can do with itWorking document · vertical listSOLID BODYCreateClick a face or a reference plane in the viewport, t…Add materialCreate a sketch, or pick a solid face, firstRemove3Dress-up2Repeat3MoveShift+YReference3Modify3
LIST · solid body
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Right-click the geometry to see what you can do with itWorking document · vertical listSTRAIGHT EDGECreateClick a face or a reference plane in the viewport, t…Add materialCreate a sketch, or pick a solid face, firstRemovePick a face or a plane to drill intoDress-up2Pattern on CurveTransformTransform needs a body — add or import one firstReference3ModifyDelete Face needs a body — add or import one first
LIST · straight edge
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Right-click the geometry to see what you can do with itWorking document · vertical listSKETCH LINECreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2
LIST · sketch line
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Click a face or a reference plane, then a toolWorking document · 8 slotsSketchShift+SReference4Nothing selected
RING · Nothing selected
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Planar face selected — pick what to do with itWorking document · 8 slotsSketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2Planar face
RING · Planar face
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Cylindrical face selected — pick what to do with itWorking document · 8 slotsThreadShift+TReference3Modify2Cylindrical face
RING · Cylindrical face
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Curved face selected — pick what to do with itWorking document · 8 slotsThickenDraftShift+DMeasureModify2Curved face
RING · Curved face
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Straight edge selected — pick what to do with itWorking document · 8 slotsDress-up2Pattern on CurveReference3Straight edge
RING · Straight edge
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Circular edge selected — pick what to do with itWorking document · 8 slotsThreadShift+TDress-up2MeasureCircular edge
RING · Circular edge
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Vertex selected — pick what to do with itWorking document · 8 slotsReference4Vertex
RING · Vertex
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Solid body selected — pick what to do with itWorking document · 8 slotsRemove3Dress-up2Repeat3MoveShift+YReference3Modify3Solid body
RING · Solid body
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sheet body selected — pick what to do with itWorking document · 8 slotsThicken SurfaceSurface OffsetMoveShift+YMeasureModify3Sheet body
RING · Sheet body
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Two bodies selected — pick what to do with itWorking document · 8 slotsCombineShift+BMateReference2DeleteDelTwo bodies
RING · Two bodies
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Datum plane selected — pick what to do with itWorking document · 8 slotsSketchShift+SRemove2MirrorShift+ZReference3Modify2Datum plane
RING · Datum plane
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Datum axis selected — pick what to do with itWorking document · 8 slotsModify2Datum axis
RING · Datum axis
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Coordinate system selected — pick what to do with itWorking document · 8 slotsMateModify2Coordinate system
RING · Coordinate system
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Text / imported art selected — pick what to do with itWorking document · 8 slotsABCPatternShift+NMoveShift+YModify2Text / imported art
RING · Text / imported art
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Closed sketch loop selected — pick what to do with itWorking document · 8 slotsAdd material8PatternShift+NModify2Closed sketch loop
RING · Closed sketch loop
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch, nothing picked selected — pick what to do with itWorking document · 8 slotsCreate9Reference2Sketch, nothing picked
RING · Sketch, nothing picked
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch line selected — pick what to do with itWorking document · 8 slotsCreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2Sketch line
RING · Sketch line
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch arc or circle selected — pick what to do with itWorking document · 8 slotsCreate9OffsetOTrimTMirrorMMoveReference3Modify2Sketch arc or circle
RING · Sketch arc or circle
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch point selected — pick what to do with itWorking document · 8 slotsCreate9MoveReference2DeleteDelSketch point
RING · Sketch point
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Two sketch entities selected — pick what to do with itWorking document · 8 slotsCreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2Two sketch entities
RING · Two sketch entities
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Add material — pick oneWorking document · 8 slotsExtrudeShift+EThickenPlanar face · Add material
Planar face · Add material sub-ring
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Reference — pick oneWorking document · 8 slotsCoord SysShift+CProjectMeasurePlaneShift+PAxisShift+APlanar face · Reference
Planar face · Reference sub-ring
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Create — pick oneWorking document · 8 slotsLineLRectangleRCircleCArcASlotSEllipseESplineBMore2Sketch, nothing picked · Create
Sketch · Create sub-ring (the overflow case)
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Remove — pick oneWorking document · 8 slotsShellShift+KCutShift+XSplitSolid body · Remove
Solid body · Remove sub-ring
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Planar face selected — pick what to do with itWorking document · 8 slotsSketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2Planar face
8 slots — planar face
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Planar face selected — pick what to do with itWorking document · 12 slotsSketchShift+SAdd material2Remove2Dress-up3PatternShift+NTransform2Reference5Modify2Planar face
12 slots — planar face
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch line selected — pick what to do with itWorking document · 8 slotsCreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2Sketch line
8 slots — sketch line
PreparePreviewDesignFEATURESSketch1Extrude1Hole1Fillet1Sketch2Extrude2Sketch line selected — pick what to do with itWorking document · 12 slotsCreate9OffsetOTrimTDress-up2MirrorMMoveReference3Modify2Sketch line
12 slots — sketch line
diff --git a/docs/HLSD/design-tab.md b/docs/HLSD/design-tab.md new file mode 100644 index 0000000000..67589be328 --- /dev/null +++ b/docs/HLSD/design-tab.md @@ -0,0 +1,213 @@ +# Design tab — High Level Design + +## Purpose and scope + +The Design tab is a parametric CAD environment inside the slicer: sketch, constrain, build +solid features, commit the result to the plate. It exists because the alternative is a round +trip through an external CAD application, and that round trip discards design intent at both +ends — a part edited after slicing comes back as a mesh rather than as the feature history +that produced it. Keeping the model in the project means a dimension can be changed after the +part has been sliced, with the nozzle diameter, the build volume and the material already +known. + +Its coupling to the rest of the application is deliberately narrow. It adds no stage to the +slicing pipeline and touches neither the preset system nor `Tab`. It reaches the rest of Orca +in two places: **Commit to Plate**, which hands finished solids to Prepare as ordinary model +objects, and one optional 3MF archive entry that carries the recipe. Everything else is +contained in `src/libslic3r/CAD/` and `src/slic3r/GUI/CAD/`. + +The user-facing manual lives in the wiki +([Design Tab](https://www.orcaslicer.com/wiki/design_tab)), not here. This document covers the +parts of the design that the code does not make evident. + +## The model is a recipe + +`CadDocument` holds an ordered list of `CadFeature` and nothing else that matters. Bodies, +meshes and display geometry are **derived**: `recompute()` replays the feature list from the +start and rebuilds them. Editing a dimension set twenty features ago is therefore an ordinary +edit — everything downstream is rebuilt by the same replay that built it the first time. + +Two consequences follow from deriving rather than storing: + +- **Undo is a snapshot of `features` alone.** The caller calls `checkpoint()` before the + mutations that make up one user action; undo restores that snapshot and recomputes. Because + everything else is derived, one checkpoint is exactly one `Ctrl+Z` step and the restored + state is exact rather than approximately reconstructed. The tab keeps this stack itself; it + is not Orca's project snapshot system, which operates on `Model` objects the Design tab does + not own until Commit. +- **Face and edge ids are session-scoped.** They are indices into `TopExp::MapShapes`, so a + rebuild invalidates every one of them. `CadDocument::topo_generation` is bumped on every + rebuild so a holder of an id can discover that it is stale instead of silently addressing a + different edge. The counter is deliberately not serialized: an id means nothing outside the + run that produced it. + +## Geometry kernel and dependency surface + +The kernel is OCCT, which OrcaSlicer **already** links — `Format/STEP.cpp`, `Format/svg.cpp` +and `Shape/TextShape.cpp` use it upstream. The Design tab adds no third-party dependency; it +widens the existing OCCT build by one module flag in `deps/OCCT/OCCT.cmake`: + +```cmake +-DBUILD_MODULE_ModelingAlgorithms=${SLIC3R_CAD} +``` + +Most of that module's twelve toolkits were already being built, because `DataExchange` — the +STEP path upstream ships — depends on them. The delta is `TKFillet` (used through +`BRepFilletAPI`), `TKOffset` (`BRepOffsetAPI`) and `TKFeat`, which nothing here references but +which the module flag builds anyway, because OCCT's module flags are all-or-nothing. On macOS +and Linux OCCT links statically, so an unreferenced toolkit costs build time and no shipped +bytes; on Windows OCCT builds shared, so the cost there is real DLL bytes. That Windows figure +has not been measured, and `OCCT.cmake` says so rather than carrying a number that was derived +from an incomplete toolkit list. + +On Windows the packaging step asserts that every linked OCCT toolkit has a shipped DLL and +fails the configure with the name of any that is missing, because the alternative failure — a +deps prefix built with a different `SLIC3R_CAD` setting than the app — otherwise surfaces as a +missing DLL at first launch. + +## The sketch constraint solver + +`src/libslic3r/slvs/` is a vendored subset of SolveSpace's `libslvs`: self-contained, no +external dependencies, **GPL-3.0**, with its `LICENSE` preserved verbatim in the directory. +`SketchSolver.cpp` is its only consumer and drives every sketch constraint in the tab. + +OrcaSlicer is AGPL-3.0. GPLv3 §13 permits combining a GPLv3 work with an AGPLv3 work and +AGPLv3 §13 grants the converse, so the combined work is distributable under AGPL-3.0 with the +solver's GPLv3 terms preserved. The solver is vendored rather than fetched as a dependency +because it is a pinned subset with no build system of its own; the cost of that choice is +upstream-sync burden, paid deliberately to keep `deps/` unchanged. + +## The SLIC3R_CAD gate + +`SLIC3R_CAD` (default ON) compiles the tab and selects the OCCT module flag above. With it OFF +the tab is not built and the deps prefix matches upstream exactly. The gate is cheap because +the hooks the Design tab adds to shared GUI code — chiefly the `m_design_sketch_tool` member +and the render, mouse and key hooks in `GLCanvas3D` — are null-guarded on the path they extend, +so removing the tab removes behaviour rather than requiring the host code to be rewritten. + +The flag has to agree between the dependencies and the application; that is what the DLL +assertion above is checking. + +## Project persistence + +A project stores the recipe as one optional archive entry, `Metadata/orca_cad.bin`, backed by +a single `std::string cad_recipe` on `Model`. The entry is written only when the string is +non-empty, and readers that do not know it ignore it, so projects that contain no CAD model are +byte-identical to what upstream would have written and older readers are unaffected. + +The blob is a cereal binary archive whose layout is the field order of `CadFeature`'s +save/load. That makes the format the one irreversible decision in the subsystem, and the rules +that keep it survivable are: + +- **Append only, never reorder.** Enums serialize positionally as their underlying integer, so + inserting a value in the middle of `SketchConstraintType` or `CadFeatureType` reinterprets + every constraint in every saved project. New fields go at the end. +- **Features are length-framed.** Since v5 each feature is a length-prefixed, self-contained + cereal stream, so a reader can skip a feature written by a newer build and stop cleanly on an + older one. This is what makes appending a field a non-breaking change from here on. v4 and + earlier still open through the pre-framing flat path; v1 is deliberately not loadable and has + no migration path. +- **A newer stamp is refused, not guessed at.** `deserialize_recipe` rejects a blob whose + version exceeds `ORCA_CAD_RECIPE_VERSION` with a message naming both versions. +- **The rules are held by fixtures, not by discipline.** `tests/data/cad_recipe_v{3,4,5}.bin` + are checked-in blobs from the builds that wrote them, and the tests that load them fail if a + field is reordered — which the in-memory round-trip test cannot detect. A regeneration test + (`[.regen]`, not run by default) produces a fresh fixture when a new version is stamped. + +`Import` features embed the imported solid as an OCCT BRep string inside the recipe rather than +referencing the source file, so a project opens without the STEP or mesh it was built from. +The cost is that saved projects are coupled to an OCCT BRep revision. + +## The interaction contract + +Three inputs carry the whole modelling loop — left click, right click and `Esc` — and the +contract between them is stated in code rather than spread across handlers. + +`DesignInteraction.hpp` defines a four-level LIFO stack whose enum value *is* the depth, so +"which level does this press belong to" is a comparison: + +| Level | Holds | One `Esc` press | +| --- | --- | --- | +| `Transient` | a value field or a popup menu | closes it; the tool stays armed | +| `Gesture` | an uncommitted delta — an entity being drawn, a body being dragged | reverts it; committed work is untouched | +| `Tool` | a feature card, an armed sketch tool, a constrain session | exits it; drawn entities survive | +| `Idle` | nothing transient | clears the selection; leaves a sketch session only if it is empty | + +`cad_escape_level()` is a `constexpr` free function over a POD of four booleans rather than a +method on the panel, so the ordering that is the entire contract is checkable without a window, +a GL context or an event loop — five `static_assert`s in the header do exactly that at compile +time. + +**The strict invariant: no level of `Esc` deletes a feature, discards a sketch that holds +geometry, or rolls history back.** Destroying work needs a gesture that says so — `Del` on an +explicit selection, the sketch ribbon's Cancel, which asks first, or `Ctrl+Z`. A sketch +*session* is deliberately not a `Tool` level; it is the environment the `Idle` level lives in, +which makes the destructive path unrepresentable rather than merely unlikely. + +Right-click is read at button-up against two independent budgets — 3 px of drift and 200 ms — +because drift alone still popped a menu at the end of a slow, careful orbit. The raycast uses +the press position, not the release. An armed sketch tool that already consumed the right +button (to terminate a chain, say) declines to also open a menu, through a read-and-clear flag. +Past either budget the event is navigation, and navigation does not transition the state +machine. + +Entering a sketch changes three things at once so the mode is legible: a banner above the +canvas (a sibling of the canvas, not a child over it — on GTK a child window over a +`wxGLCanvas` is a native window and does not reliably stack over GL), the printer bed muted so +a plate grid is never read as a sketch grid, and `N` to look normal to the plane. Code that +changes any of the three belongs with a change to this section. + +## The offer is generated, not hand-written + +Right-clicking geometry opens the *offer*: eight families in a fixed order, each verb at a +permanent row index, verbs that do not apply shown disabled **in place with their reason** +rather than removed. The invariant is that a verb's row index is identical in every selection +where it appears and that adding a verb never moves an existing one — the hand learns the +position, so the menu is never re-sorted, compacted or adaptively ordered. + +An invariant across 92 verbs and 20 selection kinds does not survive by review, so the map +exists once, as data: `scripts/CAD/tool_atlas.json` carries every verb with its row, key, icon, +accepted selections, preconditions and refusal string, and `scripts/CAD/gen_offer_table.py` +emits `src/slic3r/GUI/CAD/DesignOffer.hpp` from it. The header is checked in and never +hand-edited; `scripts/CAD/run-all-checks.sh` runs the generator with `--check` as its first +rung, which is what makes "GENERATED — DO NOT EDIT" a fact rather than a request. The generator +also refuses an atlas with a duplicate verb id, since `mcp_run_verb` resolves a verb by id and +would make the second one unreachable. + +The atlas and its generator sit in `scripts/CAD/` rather than in `docs/`: they are build inputs +for a checked-in header, not documentation. + +## Automation surface + +`McpControl` exposes the document over JSON-RPC when `ORCA_CAD_MCP` is set in the environment, +with `tools/orca_cad_mcp_bridge.py` as the client side. It describes the scene, queries +topology, measures, and runs the same verbs the offer does — it re-implements nothing, so a +scripted action and a clicked one cannot diverge. It is off unless the variable is set. + +## Where the code lives + +| Path | Role | +| --- | --- | +| `src/libslic3r/CAD/CadDocument.*` | the feature recipe, its replay, undo and serialization | +| `src/libslic3r/CAD/GeometryEngine.*` | OCCT wrapper — faces, edges, booleans, healing | +| `src/libslic3r/CAD/SketchEngine.*` | profile → wire → solid | +| `src/libslic3r/CAD/SketchSolver.*` | constraint solving, over the vendored solver | +| `src/libslic3r/slvs/` | vendored 2D constraint solver (GPLv3) | +| `src/slic3r/GUI/CAD/DesignPanel.*` | the tab: toolbar, feature cards, tree, key maps | +| `src/slic3r/GUI/CAD/DesignCanvas.*` | viewport integration | +| `src/slic3r/GUI/CAD/DesignSketchTool.*` | in-canvas sketching | +| `src/slic3r/GUI/CAD/DesignInteraction.hpp` | the Esc level contract | +| `src/slic3r/GUI/CAD/DesignOffer.hpp` | generated offer table | +| `scripts/CAD/tool_atlas.json` | source of truth for the offer | + +## Verification + +The kernel is covered by Catch2 suites in `tests/libslic3r/` (`test_caddocument`, +`test_sketchconstraints`, `test_sketchedit`, `test_sketchimport`, `test_sketchinference`, +`test_sketchprofile`, `test_slvs_constraints`), which need no display; +`scripts/CAD/run-kernel-tests.sh` builds only `libslic3r_tests` and runs them headless. + +The GUI half is not covered by CI, which has no OpenGL canvas or synthetic input: the ladders +in `scripts/CAD/` drive a running application in a local rig instead, and +`scripts/CAD/run-all-checks.sh` is the gate that runs all of them. A green kernel run says +nothing about the viewport, so the two are reported separately rather than as one number. diff --git a/scripts/CAD/README.md b/scripts/CAD/README.md index 861b77a976..a5c6836e29 100644 --- a/scripts/CAD/README.md +++ b/scripts/CAD/README.md @@ -33,6 +33,21 @@ part of `libslic3r_tests` and run under `ctest` on every platform like any other unit test. This script exists for the local loop, where it is a two-minute round trip instead of a full application build. +## Generated sources + +Two checked-in files are emitted from data that lives here, so the source and the thing +compiled against it cannot drift apart: + +| Generator | Source | Output | +|---|---|---| +| `gen_offer_table.py` | `tool_atlas.json` — every offer verb with its row, key, icon, accepted selections and refusal string | `src/slic3r/GUI/CAD/DesignOffer.hpp` | +| `mate-glyph/emit_glyph_table.py` | `mate-glyph/bear_outline.json`, measured off `mate-glyph/bear.step` by `extract_outline.py` (needs FreeCAD) | the `kBear…` tables in `DesignSketchTool.cpp` | + +`python3 scripts/CAD/gen_offer_table.py` rewrites the header and `--check` proves the +checked-in one matches the atlas — `run-all-checks.sh` runs the check as its first rung, +which is what makes the header's "GENERATED — DO NOT EDIT" enforceable. Edit the atlas, +never the header. + ## Build and run | Script | Purpose | @@ -48,6 +63,6 @@ Two constraints that are not obvious and have each cost a session: - **A window manager is required.** Without one, windows are never focused, and an unfocused GTK app ignores synthetic keys — which looks exactly like a code bug. -`docs/rig_build_traps.md` documents these and three more, with symptoms and exact +`scripts/CAD/rig_build_traps.md` documents these and three more, with symptoms and exact recovery commands. Read it before debugging a configure or link failure one of these scripts reports. diff --git a/scripts/CAD/build-gui-incremental.sh b/scripts/CAD/build-gui-incremental.sh index 539a21c973..f87113fdaf 100755 --- a/scripts/CAD/build-gui-incremental.sh +++ b/scripts/CAD/build-gui-incremental.sh @@ -15,7 +15,7 @@ # On success the binary is inside the persistent volume at # /OrcaSlicer/build/package/bin/orca-slicer (copy it out with a follow-up # `docker run --rm -v orcacad_buildcache:/b alpine cp ...` or via this script's tail). -# Rig build traps already paid for once each (stale project, NLopt cache, pybind11, OCCT_LIBS, SLIC3R_CAD gate): docs/rig_build_traps.md +# Rig build traps already paid for once each (stale project, NLopt cache, pybind11, OCCT_LIBS, SLIC3R_CAD gate): scripts/CAD/rig_build_traps.md set -euo pipefail REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" diff --git a/scripts/CAD/build-gui.sh b/scripts/CAD/build-gui.sh index 0a1a86dd5c..edf93a5ca0 100755 --- a/scripts/CAD/build-gui.sh +++ b/scripts/CAD/build-gui.sh @@ -5,7 +5,7 @@ # NEVER build inside the GUI container (snapmaker-gui / orcacad-gui). Its baked /OrcaSlicer tree # is the Jun-13 Snapmaker-derived source, so a `cmake .` in there silently reconfigures the # shared build dir as project(Snapmaker_Orca) and this fork's targets vanish. That is Trap 1 of -# five; all of them, with symptoms and exact recovery commands, are in docs/rig_build_traps.md. +# five; all of them, with symptoms and exact recovery commands, are in scripts/CAD/rig_build_traps.md. # Read that file before debugging a configure or link failure this script reports. # # Usage: @@ -92,7 +92,7 @@ docker run --rm \ # A target-only build writes src/Release/, but this fork's start-headless-gui.sh may default BIN to the # PACKAGED path that only build_linux.sh refreshes — launching with the default would then run a -# stale binary. Pass BIN explicitly. See docs/rig_build_traps.md. +# stale binary. Pass BIN explicitly. See scripts/CAD/rig_build_traps.md. echo "=== launch the rig on the binary just built ===" echo " docker exec -e BIN=/OrcaSlicer/build/src/Release/$BIN ${PREFIX}-gui /OrcaSlicer/scripts/CAD/start-headless-gui.sh" exit "$rc" diff --git a/docs/CAD/ux/mockups/gen_offer_table.py b/scripts/CAD/gen_offer_table.py similarity index 80% rename from docs/CAD/ux/mockups/gen_offer_table.py rename to scripts/CAD/gen_offer_table.py index 9744f9288b..442be27697 100644 --- a/docs/CAD/ux/mockups/gen_offer_table.py +++ b/scripts/CAD/gen_offer_table.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 -"""Emit the C++ offer table from docs/ux/tool_atlas.json. +"""Emit the C++ offer table from scripts/CAD/tool_atlas.json. - python3 docs/ux/mockups/gen_offer_table.py + python3 scripts/CAD/gen_offer_table.py # rewrite the header + python3 scripts/CAD/gen_offer_table.py --check # verify the checked-in header -The map exists ONCE. The mockups and the shipping menu read the same rows in the same -order from the same file, so a drawing and the product cannot drift apart — which is the -only way row constancy (charter 4.1) survives contact with a codebase. +The map exists ONCE: the atlas is the only place a verb's row, key, icon and refusal +string are written down, so the menu the user sees cannot drift from the table the code +is compiled against. Output: src/slic3r/GUI/CAD/DesignOffer.hpp, checked in and never hand-edited. """ @@ -14,15 +15,9 @@ import json import os import sys -HERE = os.path.dirname(os.path.abspath(__file__)) -UX = os.path.dirname(HERE) -# One dirname more than you would expect: this generator lives at docs/CAD/ux/mockups/, -# not docs/ux/mockups/, since the design docs moved into the CAD subfolder (bbd1989e1e). -# With the old count REPO resolved to docs/, so OUT pointed at docs/src/.../DesignOffer.hpp, -# which does not exist -- and --check then diffed the real generated table against an empty -# file and reported the whole 189-line header as a difference. -REPO = os.path.dirname(os.path.dirname(os.path.dirname(UX))) -ATLAS = os.path.join(UX, "tool_atlas.json") +HERE = os.path.dirname(os.path.abspath(__file__)) # scripts/CAD +REPO = os.path.dirname(os.path.dirname(HERE)) +ATLAS = os.path.join(HERE, "tool_atlas.json") OUT = os.path.join(REPO, "src", "slic3r", "GUI", "CAD", "DesignOffer.hpp") # selection id -> C++ enumerator @@ -42,15 +37,40 @@ def cstr(s): return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"' +def validate(A): + """Refuse an atlas the header cannot represent, naming every fault at once. + + An unknown slot or selection would surface as a bare ValueError from .index() below, and a + duplicate verb id would not surface at all: DesignPanel::mcp_run_verb looks a verb up by id + and takes the first match, so the second row would be unreachable from the offer. + """ + slot_ids = {s["id"] for s in A["slots"]} + sel_ids = {s["id"] for s in A["selections"]} + errs, seen = [], set() + for v in A["verbs"]: + if v["slot"] not in slot_ids: + errs.append(f'{v["id"]}: unknown slot {v["slot"]!r}') + for a in v["accepts"]: + if a not in sel_ids: + errs.append(f'{v["id"]}: accepts unknown selection {a!r}') + if v["id"] in seen: + errs.append(f'{v["id"]}: duplicate verb id') + seen.add(v["id"]) + for s in sel_ids - set(ENUM): + errs.append(f"selection {s!r} has no OfferSel enumerator") + if errs: + sys.exit("tool_atlas.json is inconsistent:\n " + "\n ".join(errs)) + + def main(): A = json.load(open(ATLAS, encoding="utf-8")) + validate(A) sels = [s["id"] for s in A["selections"]] - assert all(s in ENUM for s in sels), [s for s in sels if s not in ENUM] slots = [s["id"] for s in A["slots"]] lines = [ "// GENERATED FILE — DO NOT EDIT.", - "// Source: docs/ux/tool_atlas.json Generator: docs/ux/mockups/gen_offer_table.py", + "// Source: scripts/CAD/tool_atlas.json Generator: scripts/CAD/gen_offer_table.py", "//", "// The object-driven tool offer (charter 4.1): every verb has ONE row index, that index", "// is the same in every selection it appears in, and verbs that do not apply are shown", diff --git a/docs/CAD/design/mate-connectors/bear.step b/scripts/CAD/mate-glyph/bear.step similarity index 100% rename from docs/CAD/design/mate-connectors/bear.step rename to scripts/CAD/mate-glyph/bear.step diff --git a/docs/CAD/design/mate-connectors/bear_outline.json b/scripts/CAD/mate-glyph/bear_outline.json similarity index 100% rename from docs/CAD/design/mate-connectors/bear_outline.json rename to scripts/CAD/mate-glyph/bear_outline.json diff --git a/docs/CAD/design/mate-connectors/emit_glyph_table.py b/scripts/CAD/mate-glyph/emit_glyph_table.py similarity index 100% rename from docs/CAD/design/mate-connectors/emit_glyph_table.py rename to scripts/CAD/mate-glyph/emit_glyph_table.py diff --git a/docs/CAD/design/mate-connectors/extract_outline.py b/scripts/CAD/mate-glyph/extract_outline.py similarity index 100% rename from docs/CAD/design/mate-connectors/extract_outline.py rename to scripts/CAD/mate-glyph/extract_outline.py diff --git a/docs/CAD/rig_build_traps.md b/scripts/CAD/rig_build_traps.md similarity index 100% rename from docs/CAD/rig_build_traps.md rename to scripts/CAD/rig_build_traps.md diff --git a/scripts/CAD/run-all-checks.sh b/scripts/CAD/run-all-checks.sh index 1c0388ac89..8a99a3b6cb 100755 --- a/scripts/CAD/run-all-checks.sh +++ b/scripts/CAD/run-all-checks.sh @@ -54,10 +54,9 @@ run_in_rig() { # copy the script in fresh, then run it ther # says. The header calls itself GENERATED and had been hand-edited anyway — which cost four rows # that existed only in the header, one row wired to the wrong action, and a count of 91 for a # 92-row array, so the last verb was unreachable (z8rs, ziam). -# docs/CAD/, not docs/: SoftFever moved the design docs into the CAD subfolder -# (bbd1989e1e) and this line kept the old path, so the rung failed on a missing file -# rather than on anything about the table. The other fork still has docs/ux/. -step "offer table matches the atlas" python3 docs/CAD/ux/mockups/gen_offer_table.py --check +# The atlas and its generator live beside this script, in scripts/CAD/: they are build inputs +# for a checked-in header, not documentation, so they do not belong under docs/. +step "offer table matches the atlas" python3 scripts/CAD/gen_offer_table.py --check step "kernel suite" scripts/CAD/run-kernel-tests.sh --vol "${KVOL:-orcacad_kerneltest}" diff --git a/scripts/CAD/run-kernel-tests.sh b/scripts/CAD/run-kernel-tests.sh index ef4356cb65..cc52a2a00b 100755 --- a/scripts/CAD/run-kernel-tests.sh +++ b/scripts/CAD/run-kernel-tests.sh @@ -19,7 +19,7 @@ # working tree to a per-volume staging dir on that host and re-runs this same script # there, so the verification contract is identical either way. Drop --host once the image # is present locally. -# Rig build traps already paid for once each (stale project, NLopt cache, pybind11, OCCT_LIBS, SLIC3R_CAD gate): docs/rig_build_traps.md +# Rig build traps already paid for once each (stale project, NLopt cache, pybind11, OCCT_LIBS, SLIC3R_CAD gate): scripts/CAD/rig_build_traps.md set -euo pipefail REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" diff --git a/docs/CAD/ux/tool_atlas.json b/scripts/CAD/tool_atlas.json similarity index 99% rename from docs/CAD/ux/tool_atlas.json rename to scripts/CAD/tool_atlas.json index 94709f2057..224b118c9e 100644 --- a/docs/CAD/ux/tool_atlas.json +++ b/scripts/CAD/tool_atlas.json @@ -1,6 +1,6 @@ { "_comment": [ - "Source of truth for the object-driven tool offer (see docs/cad_ux_guidelines.md 4.1).", + "Source of truth for the object-driven tool offer (see docs/HLSD/design-tab.md).", "Every verb, the selections it accepts, the document state it needs, and its fixed", "compass address. Extracted from the code, not from memory:", " verbs + shortcuts + refusal messages -> src/slic3r/GUI/CAD/DesignPanel.cpp (feat_dropdown", @@ -8,8 +8,8 @@ " kernel coverage -> src/libslic3r/CAD/CadDocument.hpp (enum CadFeatureType)", " headless coverage -> src/slic3r/GUI/CAD/McpControl.cpp (method == \"...\")", " selection kinds -> src/slic3r/GUI/CAD/DesignCanvas.hpp (the on_*_selected callbacks)", - "gen_offer_mockups.py reads this file; the eventual C++ offer table is generated from it", - "too, so the map exists once." + "gen_offer_table.py emits src/slic3r/GUI/CAD/DesignOffer.hpp from this file, so the map", + "exists once. Edit the atlas and regenerate; never hand-edit the header." ], "slots": [ { diff --git a/src/slic3r/GUI/CAD/DesignOffer.hpp b/src/slic3r/GUI/CAD/DesignOffer.hpp index ad1998d5c5..fd15f537d3 100644 --- a/src/slic3r/GUI/CAD/DesignOffer.hpp +++ b/src/slic3r/GUI/CAD/DesignOffer.hpp @@ -1,5 +1,5 @@ // GENERATED FILE — DO NOT EDIT. -// Source: docs/ux/tool_atlas.json Generator: docs/ux/mockups/gen_offer_table.py +// Source: scripts/CAD/tool_atlas.json Generator: scripts/CAD/gen_offer_table.py // // The object-driven tool offer (charter 4.1): every verb has ONE row index, that index // is the same in every selection it appears in, and verbs that do not apply are shown diff --git a/src/slic3r/GUI/CAD/DesignPanel.cpp b/src/slic3r/GUI/CAD/DesignPanel.cpp index d247ddfd26..5f64025f2b 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.cpp +++ b/src/slic3r/GUI/CAD/DesignPanel.cpp @@ -1,7 +1,7 @@ #include "slic3r/GUI/CAD/DesignPanel.hpp" #include "slic3r/GUI/CAD/DesignCanvas.hpp" #include "slic3r/GUI/CAD/DesignSketchTool.hpp" -#include "slic3r/GUI/CAD/DesignOffer.hpp" // generated offer table — see docs/ux/tool_atlas.json +#include "slic3r/GUI/CAD/DesignOffer.hpp" // generated offer table — see scripts/CAD/tool_atlas.json #include "libslic3r/CAD/GeometryEngine.hpp" // face_by_index for face-extrude gizmo anchor #include "libslic3r/TriangleMesh.hpp" // mesh import: STL/OBJ -> indexed_triangle_set #include "libslic3r/Format/OBJ.hpp" @@ -6067,7 +6067,7 @@ bool DesignPanel::sketch_plane_target(wxString& what) const // --------------------------------------------------------------------------------------------- // The object-driven offer (charter §4.1). Right-click the geometry and get a vertical list in the // ratified row order, with the verbs that do not apply DISABLED IN PLACE carrying their reason. -// The rows come from DesignOffer.hpp, generated from docs/ux/tool_atlas.json — the same file the +// The rows come from DesignOffer.hpp, generated from scripts/CAD/tool_atlas.json — the same file the // mockups are drawn from, so a drawing and the product cannot drift apart. // --------------------------------------------------------------------------------------------- diff --git a/src/slic3r/GUI/CAD/DesignSketchTool.cpp b/src/slic3r/GUI/CAD/DesignSketchTool.cpp index 716df658ed..c195d6a877 100644 --- a/src/slic3r/GUI/CAD/DesignSketchTool.cpp +++ b/src/slic3r/GUI/CAD/DesignSketchTool.cpp @@ -4034,7 +4034,7 @@ void DesignSketchTool::render_mate_connectors() // the information. The same bear rendered flat vs in relief gives 164 vs 210 lit pixels at 16 // degrees and 66 vs 120 at 6. So the glyph is a small shaded solid, not an outline. // -// The geometry is EMITTED from the real part by doc/design/mate-connectors/emit_glyph_table.py, +// The geometry is EMITTED from the real part by scripts/CAD/mate-glyph/emit_glyph_table.py, // not hand-drawn, so the glyph and the printed connector cannot drift apart. Two vertices stand // above the 3 mm plate in the actual B-rep, which is why the snout here is a tent with one crest // edge and four flanks drafted at 20 degrees rather than anything more elaborate. @@ -4042,7 +4042,7 @@ void DesignSketchTool::render_mate_connectors() // The cheek dot is the handedness mark. Without it the glyph differs from its own mirror by only // 5-9 % of its lit pixels, which is not enough to read; the dot roughly doubles that and, unlike // making the eyes uneven, identifies the side from that cheek alone instead of by comparison. -// Emitted by doc/design/mate-connectors/emit_glyph_table.py from bear.step — do not hand-edit. +// Emitted by scripts/CAD/mate-glyph/emit_glyph_table.py from bear.step — do not hand-edit. // Normalised to the part's bounding span and centred: the renderer scales by one radius. static const Vec2d kBearOutline[] = { // 12 verts, RDP eps 0.030, CCW {+0.3842, +0.3294}, {+0.3156, +0.4002}, {+0.2424, +0.3294},

yop5!?__o)S6go;_vH41NZ5Tl}^sNko^ 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 the grazing silhouette - a, b = OUT[i], OUT[(i+1) % n] - F.append(([(a[0],a[1],0.0),(b[0],b[1],0.0),(b[0],b[1],PLATE),(a[0],a[1],PLATE)], "body", True)) - F.append(([(x,y,PLATE) for x,y in OUT], "body", True)) # plate top - zm = PLATE + 0.004 - for cx,cy,r in MARKS: # eyes + cheek dot - F.append(([(cx+r*math.cos(2*math.pi*i/12), cy+r*math.sin(2*math.pi*i/12), zm) for i in range(12)], "mark", False)) - F.append(([(x,y,zm) for x,y in CHIN], "mark", False)) # chin bar - A, B = CREST # THE MUZZLE: base quad + crest - nl=(SBASE[0][0],SBASE[0][1],PLATE); nr=(SBASE[1][0],SBASE[1][1],PLATE) - tr=(SBASE[2][0],SBASE[2][1],PLATE); tl=(SBASE[3][0],SBASE[3][1],PLATE) - F += [([nl,tl,B,A],"body",True), # left flank - ([nr,A,B,tr],"body",True), # right flank - ([nl,A,nr],"body",True), # nose cap, sloping because the base overhangs the crest - ([tr,B,tl],"body",True)] # tail cap - return F -FACETS = facets() - -BODY=(0.42,0.46,0.52); MARK=(0.126,0.138,0.156) -def render(px, elev_deg, ss=8): - S=px*ss; a=math.radians(elev_deg); ca,sa=math.cos(a),math.sin(a) - # camera orbits down; the connector's +Z (relief) tips toward the horizon - xf=lambda p:(p[0], p[1]*sa + p[2]*ca, -p[1]*ca + p[2]*sa) - light=(-0.70,0.30,0.45) - img=Image.new("RGB",(S,S),(24,27,32)); d=ImageDraw.Draw(img) - tris=[] - for pts,kind,shade in FACETS: - q=[xf(p) for p in pts] - tris.append((sum(v[2] for v in q)/len(q), q, kind, shade)) - tris.sort(key=lambda t:t[0]) # far first - for _,q,kind,shade in tris: - (x0,y0,z0),(x1,y1,z1),(x2,y2,z2)=q[0],q[1],q[2] - ux,uy,uz=x1-x0,y1-y0,z1-z0; vx,vy,vz=x2-x0,y2-y0,z2-z0 - nx,ny,nz=uy*vz-uz*vy, uz*vx-ux*vz, ux*vy-uy*vx - nn=math.sqrt(nx*nx+ny*ny+nz*nz) or 1.0 - nx,ny,nz=nx/nn,ny/nn,nz/nn - if nz<0: nx,ny,nz=-nx,-ny,-nz - base=BODY if kind=="body" else MARK - k=(0.42+0.58*max(0.0,nx*light[0]+ny*light[1]+nz*light[2])) if shade else 1.0 - col=tuple(min(255,int(255*c*k)) for c in base) - d.polygon([(S/2+p[0]*S*0.92, S/2-p[1]*S*0.92) for p in q], fill=col) - return img.resize((px,px), Image.LANCZOS) - -SIZES=[22,32,48]; ELEVS=[(90,"flat on"),(47,"47"),(16,"16"),(6,"6")] -pad,cell=8,58 -W=pad+len(SIZES)*len(ELEVS)*cell+pad; H=pad+cell+pad -sheet=Image.new("RGB",(W,H),(24,27,32)) -for ci,(e,_) in enumerate(ELEVS): - for si,px in enumerate(SIZES): - g=render(px,e) - sheet.paste(g, (pad+(ci*len(SIZES)+si)*cell+(cell-px)//2, pad+(cell-px)//2)) -sheet.resize((W*2,H*2), Image.NEAREST).save(os.path.join(HERE,"glyph-preview.png")) - -# how much of the glyph is the snout: render with and without the tent and diff -def render_no_tent(px, elev): - global FACETS - keep=FACETS; FACETS=FACETS[:-4] - try: return render(px, elev) - finally: FACETS=keep -print(f"{'elev':>8} {'lit px@32':>10} {'snout px':>9} {'snout share':>12}") -for e,_ in ELEVS: - a=render(32,e); b=render_no_tent(32,e) - la=sum(1 for p in a.get_flattened_data() if p!=(24,27,32)) - diff=sum(1 for p,q in zip(a.get_flattened_data(), b.get_flattened_data()) if p!=q) - print(f"{e:>8} {la:>10} {diff:>9} {100.0*diff/max(1,la):>11.1f}%") -print("WROTE glyph-preview.png") diff --git a/docs/CAD/design/mate-connectors/glyph_probe.py b/docs/CAD/design/mate-connectors/glyph_probe.py deleted file mode 100644 index f4d48fdd6c..0000000000 --- a/docs/CAD/design/mate-connectors/glyph_probe.py +++ /dev/null @@ -1,143 +0,0 @@ -# Mate-connector glyph probe — built as REAL solids on REAL mechanical geometry, -# so the shape can be judged in a 3D viewport instead of in a browser mock. -# -# Four polarity treatments, side by side on one bracket: -# A Onshape baseline ...... ring + roll quadrant + three short axis arms -# B solid cone ............ ring + quadrant + one-sided Z arrow, filled head (driven) -# C hollow collar ......... ring + quadrant + one-sided Z arrow, shell head (fixed) -# D pin / cup ............. polarity by RELIEF: a raised pin vs a sunk cup -# -# D is the one that only a 3D test can settle: in a shaded viewport, solid-vs-hollow is a -# weak cue that depends on angle and lighting, while convex-vs-concave is a strong one -- -# and male/female is the mechanical language for polarity anyway. -# -# Scale note: in the real viewport gizmos are screen-constant (~15-40 px via upp = 1/zoom). -# At a zoom where a 60 mm part fills ~600 px, 40 px is about 4 mm, so R = 4.5 mm here. - -import FreeCAD as App -import FreeCADGui as Gui -import Part -from FreeCAD import Vector - -DOC = "GlyphProbe" -for d in list(App.listDocuments()): - App.closeDocument(d) -doc = App.newDocument(DOC) - -R = 4.5 # disc radius, the module everything scales from -GOLD = (0.93, 0.66, 0.09) -BLUE = (0.18, 0.44, 0.93) -GREY = (0.42, 0.46, 0.52) -RED = (0.85, 0.29, 0.24) -GREEN = (0.23, 0.65, 0.35) - -def add(name, shape, color, transparency=0): - o = doc.addObject("Part::Feature", name) - o.Shape = shape - o.ViewObject.ShapeColor = color - o.ViewObject.LineColor = color - o.ViewObject.PointColor = color - o.ViewObject.Transparency = transparency - return o - -def frame(origin, zdir, xdir): - """Right-handed placement matrix from origin + Z + X (X orthonormalised against Z).""" - z = Vector(*zdir); z.normalize() - xr = Vector(*xdir) - x = xr.sub(Vector(z).multiply(z.dot(xr))); x.normalize() - y = z.cross(x) - return App.Matrix(x.x, y.x, z.x, origin[0], - x.y, y.y, z.y, origin[1], - x.z, y.z, z.z, origin[2], - 0, 0, 0, 1) - -# ---------------------------------------------------------------- the bracket -plate = Part.makeBox(120, 46, 8) -bore = Part.makeCylinder(7, 40, Vector(96, 23, -6)) # a real bore, curved face -boss = Part.makeCylinder(11, 7, Vector(96, 23, 8)) -part = plate.fuse(boss).cut(bore) -add("Bracket", part, (0.60, 0.63, 0.66)) - -# ---------------------------------------------------------------- glyph pieces -def ring(t=None): - t = t or R * 0.10 - return Part.makeCylinder(R, t).cut(Part.makeCylinder(R * 0.84, t)) - -def quadrant(t=None): - t = t or R * 0.10 - return Part.makeCylinder(R * 0.84, t, Vector(0, 0, 0), Vector(0, 0, 1), 90) - -def stem(L=None, r=None): - return Part.makeCylinder(r or R * 0.09, L or R * 2.3) - -def solid_head(): - return Part.makeCone(R * 0.32, 0, R * 0.80, Vector(0, 0, R * 2.3)) - -def shell_head(): - outer = Part.makeCone(R * 0.32, 0, R * 0.80, Vector(0, 0, R * 2.3)) - inner = Part.makeCone(R * 0.22, 0, R * 0.62, Vector(0, 0, R * 2.3)) - return outer.cut(inner) - -def short_axis(direction, L=None): - L = L or R * 1.15 - return Part.makeCylinder(R * 0.07, L, Vector(0, 0, 0), Vector(*direction)) - -def place(shape, m): - s = shape.copy() - s.transformShape(m) - return s - -# ---------------------------------------------------------------- the variants -def variant_A(tag, origin): # Onshape baseline - m = frame(origin, (0, 0, 1), (1, 0, 0)) - add(tag + "_ring", place(ring(), m), GREY) - add(tag + "_quad", place(quadrant(), m), GOLD) - add(tag + "_x", place(short_axis((1, 0, 0)), m), RED) - add(tag + "_y", place(short_axis((0, 1, 0)), m), GREEN) - add(tag + "_z", place(short_axis((0, 0, 1), R * 1.6), m), BLUE) - -def variant_B(tag, origin, zdir=(0, 0, 1)): # solid cone = driven - m = frame(origin, zdir, (1, 0, 0)) - add(tag + "_ring", place(ring(), m), BLUE) - add(tag + "_quad", place(quadrant(), m), GOLD) - add(tag + "_body", place(stem().fuse(solid_head()), m), BLUE) - -def variant_C(tag, origin, zdir=(0, 0, 1)): # hollow collar = fixed - m = frame(origin, zdir, (1, 0, 0)) - add(tag + "_ring", place(ring(), m), GREY) - add(tag + "_quad", place(quadrant(), m), GOLD) - add(tag + "_body", place(stem().fuse(shell_head()), m), GREY) - -def variant_D_pin(tag, origin, zdir=(0, 0, 1)): # polarity by relief: raised PIN - m = frame(origin, zdir, (1, 0, 0)) - pin = Part.makeCylinder(R * 0.30, R * 1.5).fuse( - Part.makeCone(R * 0.30, 0, R * 0.55, Vector(0, 0, R * 1.5))) - add(tag + "_ring", place(ring(), m), BLUE) - add(tag + "_quad", place(quadrant(), m), GOLD) - add(tag + "_pin", place(pin, m), BLUE) - -def variant_D_cup(tag, origin, zdir=(0, 0, 1)): # polarity by relief: sunk CUP - m = frame(origin, zdir, (1, 0, 0)) - cup = Part.makeCylinder(R * 0.62, R * 0.9).cut( - Part.makeCylinder(R * 0.40, R * 0.9, Vector(0, 0, -0.01))) - add(tag + "_ring", place(ring(), m), GREY) - add(tag + "_quad", place(quadrant(), m), GOLD) - add(tag + "_cup", place(cup, m), GREY) - -# four treatments across the plate, all on the same flat face, same Z -variant_A("A", (14, 30, 8)) -variant_B("B", (40, 30, 8)) -variant_C("C", (64, 30, 8)) -variant_D_pin("Dpin", (14, 10, 8)) -variant_D_cup("Dcup", (40, 10, 8)) - -# the hard cases, which is the whole reason for doing this in 3D: -variant_B("Bore", (96, 23, 15)) # on the boss above a bore -variant_B("Edge", (64, 0, 8), (0, -0.7071, 0.7071)) # tilted, on an edge, oblique Z - -doc.recompute() - -v = Gui.activeDocument().activeView() -v.viewIsometric() -Gui.SendMsgToActiveView("ViewFit") -App.Console.PrintMessage("glyph probe built: %d objects\n" % len(doc.Objects)) diff --git a/docs/CAD/design/mate-connectors/handedness-sheet.png b/docs/CAD/design/mate-connectors/handedness-sheet.png deleted file mode 100644 index b44c37032835822a0aaab0a0ddbeff029c2e643b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35843 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 diff --git a/docs/CAD/design/mate-connectors/handedness.py b/docs/CAD/design/mate-connectors/handedness.py deleted file mode 100644 index 68fa07bb55..0000000000 --- a/docs/CAD/design/mate-connectors/handedness.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Give the bear a handedness mark that survives rasterisation — wi3z, Tommaso's call 2. - -The study showed the left/right cue lives in sub-millimetre corner radii and is therefore invisible -at glyph size: one pixel is 2.6 mm at 32 px. Roll and verse are safe; handedness is not. - -THE MEASURE IS THE QUESTION ITSELF. Render the glyph, render its mirror image, and count how many -pixels differ. If a human is to tell left from right, the two must differ on screen; a candidate -that scores near zero is invisible however elegant it looks in CAD. Reported as a percentage of the -glyph's own lit area, so the sizes are comparable. -""" -import json, math, os -from PIL import Image, ImageDraw, ImageChops - -HERE = os.path.dirname(os.path.abspath(__file__)) -D = json.load(open(os.path.join(HERE, "bear_outline.json"))) -def unit(pts): - p = [(x, -z) for x, z in pts] - return p -outer = unit(D["outer"]); holes = [unit(h["pts"]) for h in D["holes"]] -ALL = outer + [p for h in holes for p in h] -xs=[p[0] for p in ALL]; ys=[p[1] for p in ALL] -CX,CY = (min(xs)+max(xs))/2,(min(ys)+max(ys))/2 -SPAN = max(max(xs)-min(xs), max(ys)-min(ys)) -U = lambda pts: [((x-CX)/SPAN,(y-CY)/SPAN) for x,y in pts] -OUT = U(outer) -EYES = [U(h) for h,m in zip(holes, D["holes"]) if m["d"] < 20] -MUZ = U([h for h,m in zip(holes, D["holes"]) if m["d"] >= 20][0]) - -def rdp(pts, eps): - if len(pts) < 3: return pts - ax,ay=pts[0]; bx,by=pts[-1]; dx,dy=bx-ax,by-ay - n=math.hypot(dx,dy); best,bi=-1.0,0 - for i in range(1,len(pts)-1): - px,py=pts[i] - d=abs(dx*(ay-py)-(ax-px)*dy)/n if n>1e-12 else math.hypot(px-ax,py-ay) - if d>best: best,bi=d,i - if best<=eps: return [pts[0],pts[-1]] - return rdp(pts[:bi+1],eps)[:-1]+rdp(pts[bi:],eps) -def simp(pts,eps): - r=rdp(pts+[pts[0]],eps); return r[:-1] - -BASE = simp(OUT, .030) # the 22-vertex outline the study settled on -def centroid(p): return (sum(q[0] for q in p)/len(p), sum(q[1] for q in p)/len(p)) -def circ(cx,cy,r,n=16): return [(cx+r*math.cos(2*math.pi*i/n), cy+r*math.sin(2*math.pi*i/n)) for i in range(n)] -EYE_D = [] -for e in EYES: - c=centroid(e); r=(max(p[0] for p in e)-min(p[0] for p in e))/2 - EYE_D.append((c[0],c[1],r)) -EYE_D.sort() # [0] = left (x<0), [1] = right - -TOP = max(p[1] for p in BASE) -H = TOP - min(p[1] for p in BASE) -def ear_tip(sign): - cands=[p for p in BASE if p[1] > TOP-0.18*H and (p[0]*sign) > 0] - return max(cands, key=lambda p: p[0]*sign) if cands else None -LT, RT = ear_tip(-1), ear_tip(+1) - -def notch(tip, sign, k=0.085): - """A wedge bitten out of one ear — background-filled, exactly how the eyes are already drawn.""" - x,y = tip - return [(x, y+0.02), (x - sign*k, y - k*0.55), (x + sign*k*0.15, y - k*1.05)] - -CANDS = { - "H0 none": dict(cuts=[], eyes=EYE_D), - "H1 notch R ear": dict(cuts=[notch(RT, +1)], eyes=EYE_D), - "H2 notch both": dict(cuts=[notch(RT, +1), notch(LT, -1, 0.045)], eyes=EYE_D), - "H3 cheek dot": dict(cuts=[circ(EYE_D[1][0]+0.085, EYE_D[1][1]-0.10, 0.038)], eyes=EYE_D), - "H4 uneven eyes": dict(cuts=[], eyes=[EYE_D[0], (EYE_D[1][0], EYE_D[1][1], EYE_D[1][2]*1.55)]), -} - -def render(c, px, ss=8, mirror=False): - S=px*ss; img=Image.new("L",(S,S),0); d=ImageDraw.Draw(img) - m = lambda p: (S/2 + (-p[0] if mirror else p[0])*S*0.92, S/2 - p[1]*S*0.92) - d.polygon([m(p) for p in BASE], fill=255) - d.polygon([m(p) for p in MUZ], fill=0) - for cx,cy,r in c["eyes"]: - a=m((cx-r,cy+r)); b=m((cx+r,cy-r)) - d.ellipse([min(a[0],b[0]), min(a[1],b[1]), max(a[0],b[0]), max(a[1],b[1])], fill=0) - for cut in c["cuts"]: - d.polygon([m(p) for p in cut], fill=0) - return img.resize((px,px), Image.LANCZOS) - -SIZES=[22,32,48] -print(f"{'candidate':16} " + " ".join(f"{s}px" for s in SIZES) + " (pixels differing from own mirror, % of lit area)") -print("-"*84) -scores={} -for name,c in CANDS.items(): - row=[] - for px in SIZES: - a=render(c,px); b=render(c,px,mirror=True) - diff=ImageChops.difference(a,b) - nd=sum(1 for v in diff.getdata() if v>40) - lit=sum(1 for v in a.getdata() if v>40) or 1 - row.append(100.0*nd/lit) - scores[name]=row - print(f"{name:16} " + " ".join(f"{v:5.1f}" for v in row)) - -pad,cell=8,58 -W=pad+len(SIZES)*2*cell+pad; Hh=pad+len(CANDS)*cell+pad -sheet=Image.new("RGB",(W,Hh),(24,27,32)) -for r,(name,c) in enumerate(CANDS.items()): - for mi,mir in enumerate((False,True)): - for si,px in enumerate(SIZES): - g=render(c,px,mirror=mir) - tile=Image.new("RGB",(px,px),(24,27,32)) - tile.paste(Image.new("RGB",(px,px),(237,168,23)),(0,0),g) - x=pad+(mi*len(SIZES)+si)*cell+(cell-px)//2 - y=pad+r*cell+(cell-px)//2 - sheet.paste(tile,(x,y)) -sheet.resize((W*2,Hh*2), Image.NEAREST).save(os.path.join(HERE,"handedness-sheet.png")) -print("\nleft block = as drawn, right block = mirrored. rows: " + ", ".join(CANDS)) -print("WROTE handedness-sheet.png") diff --git a/docs/CAD/design/mate-connectors/linearedgemateconnectors.png b/docs/CAD/design/mate-connectors/linearedgemateconnectors.png deleted file mode 100644 index fd5e64190db6b9b824fe9d1e8a0d10e0eb10e892..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20301 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 insertion along +Y, mouth at Y={bb.YMin:.2f}") - -# ---- 1. can the male even be withdrawn along the insertion axis? ---------------------- -# Ray-cast a grid along +Y through the tessellated male and count crossings. A straight pull is -# possible only if no ray enters the solid more than once; a second entry is an undercut. -verts, facets = male.tessellate(0.15) -V = [(v.x, v.y, v.z) for v in verts] -worst, undercut_pts = 0, 0 -NX = NZ = 90 -for i in range(NX): - x = bb.XMin + (i + 0.5) * bb.XLength / NX - for j in range(NZ): - z = bb.ZMin + (j + 0.5) * bb.ZLength / NZ - hits = 0 - for (ia, ib, ic) in facets: # ray (x, *, z) along +Y vs triangle - ax, ay, az = V[ia]; bx, by, bz = V[ib]; cx, cy, cz = V[ic] - # 2D point-in-triangle in the XZ plane - d = (bz - cz) * (ax - cx) + (cx - bx) * (az - cz) - if abs(d) < 1e-12: continue - u = ((bz - cz) * (x - cx) + (cx - bx) * (z - cz)) / d - v = ((cz - az) * (x - cx) + (ax - cx) * (z - cz)) / d - if u < 0 or v < 0 or u + v > 1: continue - hits += 1 - worst = max(worst, hits) - if hits > 2: undercut_pts += 1 -print(f"pull : max crossings along +Y = {worst}, undercut samples = {undercut_pts}/{NX*NZ}") -if undercut_pts: - print("FAIL: the male has an undercut along +Y; a straight pocket cannot release it") - sys.exit(1) -print(" no undercut -> a straight-pull pocket works") - -# ---- 2. grow the male by the clearance ----------------------------------------------- -grown = None -for join, name in ((2, "Intersection"), (1, "Tangent"), (0, "Arc")): - try: - g = male.makeOffsetShape(CLEAR, 1e-6, False, False, 0, join, False) - if g.isValid() and g.Solids: - grown = g.Solids[0]; print(f"offset: join={name}, {grown.Volume/1000:.2f} cm3"); break - except Exception as e: - print(f"offset: join={name} failed -- {e}") -if grown is None: - print("FAIL: could not offset the male; refusing to emit a zero-clearance pocket"); sys.exit(1) - -# ---- 3. the block: walls in X and Z, depth in +Y, OPEN at the Y=0 mouth --------------- -gb = grown.BoundBox -y_mouth = bb.YMin # the male's flat back plane -depth = gb.YMax - y_mouth -block = Part.makeBox(gb.XLength + 2*WALL, depth + FLOOR, gb.ZLength + 2*WALL, - App.Vector(gb.XMin - WALL, y_mouth, gb.ZMin - WALL)) -print(f"block : {gb.XLength + 2*WALL:.2f} x {depth + FLOOR:.2f} x {gb.ZLength + 2*WALL:.2f} mm, " - f"mouth on the Y={y_mouth:.2f} plane") - -female = block.cut(grown) - -# ---- 4. verify -------------------------------------------------------------------------- -ok = True -if not female.isValid(): print("FAIL: invalid shape"); ok = False -if len(female.Solids) != 1: print(f"FAIL: {len(female.Solids)} solids"); ok = False - -clash = male.common(female) -cv = clash.Volume if clash.Solids else 0.0 -print(f"check : male ∩ female = {cv:.6f} mm3 (seated fit, must be ~0)") -if cv > 1e-3: print("FAIL: male collides with female"); ok = False - -# the mouth must actually be open: the pocket has to reach the Y=y_mouth face of the block -mouth_face_area = 0.0 -for f in female.Faces: - c = f.CenterOfMass - if abs(c.y - y_mouth) < 1e-6: - mouth_face_area += f.Area -solid_mouth = (gb.XLength + 2*WALL) * (gb.ZLength + 2*WALL) -open_area = solid_mouth - mouth_face_area -print(f"check : mouth plane -- material {mouth_face_area:.1f} mm2, opening {open_area:.1f} mm2 " - f"({100*open_area/solid_mouth:.1f}% of the face)") -if open_area < 100: - print("FAIL: the pocket is sealed -- the male cannot be inserted"); ok = False - -cavity = block.Volume - female.Volume -print(f"check : cavity {cavity/1000:.2f} cm3 vs male {male.Volume/1000:.2f} cm3 " - f"-> clearance shell {(cavity-male.Volume)/1000:.2f} cm3") -if cavity < male.Volume: print("FAIL: cavity smaller than the male"); ok = False - -if not ok: - print("\nREFUSING to write the STEP"); sys.exit(1) - -doc = App.newDocument("Female") -obj = doc.addObject("Part::Feature", "BearConnector_Female") -obj.Shape = female -doc.recompute() -Part.export([obj], OUT_STEP) -fb = female.BoundBox -print(f"\nwrote {OUT_STEP}") -print(f"female: {fb.XLength:.2f} x {fb.YLength:.2f} x {fb.ZLength:.2f} mm, " - f"{len(female.Faces)} faces, {female.Volume/1000:.2f} cm3") diff --git a/docs/CAD/design/mate-connectors/mate-connector-virtual-sharp.png b/docs/CAD/design/mate-connectors/mate-connector-virtual-sharp.png deleted file mode 100644 index f9a80c1e1089d1afa6581293c4556967cb6b47a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31835 zcmd>l_cvVs_q8^JQKHQ#5iR=YLP8=+2u81?k506RmT1v~QGysPqW6+fGl*_<(L!`W z2+>0H`rN#~pa0_dVca#dmbLEdo^$rzXPONDztNMCp6Zk^pAg>{hhgY2dzO(`X-$`Gn7<=O3QFi_N;P<+h*y7>& z|51G^um8$oCkuqN#igI9*34|%=iAzJtK3q(h0f_%AW>#;m^WweW$js5cuU>0K+zMi z&p1yQa;v6Ywt4BI<>qtQ*HS(61@pjPi>f?xzg}NcbqQ`X25Me?EjRF%|G{q-A@H_* zsis9=TOL%Hs$7T%mj~G;|NnHAE!!J^tOobdWBv3oxw;je`*?ALx8$@yhp%0Cyt81p z(UHVEvpL$QDpOTv>TAx9bJ4`{q@L5(0AHOq?%r%o0EerE0%-M{J%#ehl zBb4%3Qv_#=$;fQ`D)2*Y9K2`X=Iza!zXzlTuT70Ki(Jc#4wl5@ZoY0_`i|sOUCJ!` zcI^GUFb5yN^L2yQ%p#=xKPkyL&8|3X9BC+SOdb(#6ix#&&et39%B4j~k5_|s^4m_%9#)?1xe2s^ zgLp2pPMiPSv^IE%DBh3ogH3B*k)ipOm{JH}oAqzGE9akj8des~2pEo6*bO?JCpkEt zt@Mi}ijIjiS|Y~6zW$Cr;m|sL%v?0Fs8Q>*gl?>RvlSqX3B+fauYMQ5W3Kw_q#}pQ z|6{T0NQ)Y+PmB7h&leS&hOFGNOs%oLI3CB-$W;lK1s$fE^;;AcflWBT0?|{A7;O+ESWE<~Tw@Thb1k-dQb)O+zMHSq@eC ziGNcuDd`Gx(@Cy2s9Nu}dt(`|6&)wHL#XQ<7$pO!F8@ZPwEXnfG{ywm3z-j`AS32c zKL&kY?M^=VEWr;##3I>y1dTa`z-DP0@ZPapuB)Gd6bu&Dw=Ik(-dxf>Irqkf!rl~YUM%I6 z`Y)-pi}}nMGqtQq#Lat!Dl;Iaib5jfMKOmohz4=k91J0;_xT|)n&O*XQE1`c*!piC z=S;sJxZd|pYl@lGGFIK;`IO_6oT@h$J?OYH|4_o?t4oH614JY3F-lo0{izI;Di1o- z+47}`)wJ?%0IRgPzPaOfamDG$%)Y9h60`L|kfr7~LGS{dvo;Di)knuqwct=G-o=XE z>BpfcFCQl(KmWgM=cl8#W3_v46l9ie(tI!twGB0rH=8!Ir@aYIS97dm8B$O33_b-u z6|gvqLbr5dK3u(nWZ5G$FAb3CokcA{@91Qo~bt2BrYh!B;GH>B=*Cz;b%iB{-7+xx9E=! zzBEF2&4!Jp9xI8XVf%}#Z-|}ruc_KQlU;Uy-r9?T*kzudEr|F?Gp& z>Tn{M_gTyR_RiTd?U-AX{OkC76h6KPDPqwMv3M3W)p|r-8Fa;Ia#j{<*AEU}Q#5&z zWc9EJneM%@a0}f((xOZ|(NWXNfV6^=W4s!ythC3wYVL&=9$}Tp2NgmJ3){t>gchFt zdHBs`+U2?Qd%=NIzLlQJw3%$pnxm8lQL9{7kMh(Jy6^OE6({MJVXFBDBjCYWW_vjI zEyg@?cysjP>?6c{iP6c|qAuY(MSAWZAS`}+fwLFCZt?xq zM_A2V-mMsXRu^A6_*;qr;%bf-gP~^VOBLYI1zqLn4vhUO))U09b|)mK`XkxO@o}JoYLC~v}a@A zaM$ox_KLt}%kUuhc0^&F7`*pIiVnKT?BXT+%bVX`jY=>*^ms(UER_)%dsir$7+b{y z^|h92_+)$G;~Lz&{zmiW)_BRKXUBME;Af|L>}}B~vxDSl z)sr{;e)09s;A~bi7+&Lc1m?5?j8yf&-To{B)0ywY3_Uuf)L1%!Xv|zmYgi+=9T7bu zuzWMb)HG(BB9e=8mjdQ|g~1q5BkBzBVbuo445svi;EI74b_N^>hWE;0MY{PRdZj<~ zl1n@El1t=oqV+%R5&3yv$@PC_$sDN?d;-Qc)uM$@WIqR_DzC9JKt4XAHFEh$f(bQ* zW%aDLL0f7XSX=7Kq_XB^Q!E~~B*wSBOl!DY)0^#??LcnZ7Iq$#wp-UN5rT<~h8)Z# z8I5ACLImu41;JW@8||GQVC2kddrgYf%!hD06-o>*lssL85Zo)Ui4^U4Y$g(^Bo_iE zMj#Ewu}BR60Em#cqJs%STsYRwM^4r?{)T39j|9=9*CM-G;+9=4;nAsWBHNOUb>ewA z^*Tgwq{Exmakp&p`d7wV;AyXRgjP&`JrZ{stcmt|;o)8i{nFw})KVKrwDWqCd?)aX z-1YUvNZobIh~vaX_8276N}#JI%MSS(U7u+e>|rHJAVSPU-~3=oso9#jPM{&uTojSI zCT!{fCOP|?aAn*pD9zcx!}v2{#xK(9AwA_FMf11Z`3 z6!#!y=f&&TsjGc)lbcr#c2}Nv_s~kKt#c^Xd4_gz&>h>*$yFs+3Iz-kaQ({P5h11tRZhtLf}Ba=zA* zGFI|a3g-E}A{UT!JI10h?3;YAZ#bEYM@zeyoksS%>P`zsHq(t{iwi#mX%1T6#2Si- zlry61$p@t)?FhSSqOE2QQb%a27?2?X#7Gw@a?Bm13L6yNLarvgMPr8}hkbha?uLWp zj2yfIAw9@`_Rig*+ZxfsjeOXHd6Z3!UX(@Lo_9txF(A}mJXwe_DoTT;g@6o|y7DSx z$aY&%QU8{+Yr(rvNY6#3b^ z3sh$rk0(lg^1!f8yp*kd`y_5Zo$tW%DsBFbCD;A@c5!%^uI1D*GvfFIl?@hwGo;yJ zgviM&Q24}E)5<lFZisRaHv;xYsCZiPQl{(;H|Wqd`rYC zM3ucfHccw<1HoV-1EiHl6|o&!5DMCl$s#3$RfE@i1jy)0KRq?XeKXYmvI2*4T13n1 z7;?oM(DA@#^WQq;?iB+Rr>THEbXBtCBy@TmLA(-8FS9J^2?BYg-#IZelSwAE$~vUL2KuWY^{ z8~eR4BbRbL6Z%HWN&kQZ?Opz>RP9_hXt07&?d?Z%HSvV2lq0DO?@L8u?nt}EO|AFd zR)}%q)cbq)Dp3?D4_r-GUT_FGjNd1^5{%TTV(7A_=fy{TdJuiMU)R!aGKeUxr{jTb zaUxbEp@gWN&`nrc@$DHiq?I-Uq?^Ch%fr@y2$F@Lj8HPB$0F}H8)F!>r738%%fxsM zW{(^Y=emWGfsPXucel~r@9n>-mLK$XI_h6vV_@dB`X&QE+;XX$CU>vu4oATfs~eWa zZxY`SKtSZMp}Y_$+xPn`L3OyiUiPB3^)PrAga;|Y}*OpS~I z!7xXU8tPbT2%vNjWqHuY*z0-fy%AdXu~$9wytj3fG~$JP5W6*h({l&{$&i#JB*`eU78-rAX4_69 z-vYoZfI&=jh?E$uV|#?3PK4p((g4)17lHZcw$)EKT0Q^ZB*bn!Chl;c3IC-cWo5R? zv*QXn6_=UzTd;T;;;lSusH^#-YB=2nXPgV@TnP38hZhrq+jvKa^5{JlV&Y)urj)-_ zgW+q;8+|p!`U2ez#u6vXTYNbT2qFfGVfDezd#g}lO8ok}%E8Rw(+PRs(^@ zdn^#6a-@h@FgJ!5z&XeA@gAe7Z$gL#?Xs@K>S0Mt-^#b46*0n^AqLTMR^_@? z#zy>eb>Lp1EIaBC?vz>XPWoSkVTd1Z(QXoREhqCt8Gff>DvTlfSQ|WLYbb&tM5!<- ze>%cSK6l)7?I+pU-hs;&4GWSfY$r>xZyhL z@!L_RF#DOs7fK`f68yhNAQF!T$`mQrp*xS|^(b3}GstEzwgf#`n~$fsZx%G|D)#E2 z3)!tu2t#`f%Fe8s0YDezLvm7)>?pip{74*e4+4s{&V(5abql`0X}c$~-E3Tl!tQ)d zI}|msn7=O=gzyoAl`L*8-*vJfE;Rk-T45&gnPIa5t1|FFgIz39sDX)lfjsR~C64H* zs3<8`@AfFCCO)rp&UcTPmu(F1dssHBBX^v-Vyg}hW{bapLngB<$zI{74b7juS6;hp zKR95j&+W|i(JJ60xuNUe0A%hh5hrA{U@UzzW<5%V7@ThdBzkBd0!q7WIt9A2<`(C! zN&}&`Vn}%_IA@y}kg?t~#a5z9YAN(6K$Jm4Kt~Ys4^I=O>lQwZcMI}3&NmTC_$LP^nEi7P=9aR^x$hSBO>ql;YAz2>IZ@oxLun%aM`LR-q~m^i?eNIBQ1!@%;!3Ojj{JU?}=ElyDGl^ zb(W~S777lq|7A8Aw?yE2sB0kcq_x_Pfn`ZHlbet(gp0VRUT7xd3_go|bDfT@X-l(# z>Xp=0qB4~wG1JDPpuu7%E72)jd+0Q-oxn(4w@+FC0_B;ZnET5iIO7o|`J_49hc)11 z!|t@|$j-vEUagNst|L47#^nv8Q3n2;2;!J%4y=}=+@kaNsMmgOWY=nxID^3+! zJQ!>xCU64pu&%M>FEKJynime{FjOKriqcySx<8R4u`PFE=p)UzYVhh}d1rr)c`s+s4QoDYNLi63r`M33(@(N!gIv^Ww+-QD5$R!pGq;I|SesWM zml}j#;MBLRixXXqU?2!ZY^ZX_OO8YI9+%(D8)*9Iq0!jGncvVo=x2w6I$s< zhYN209f4OKmaQ`+A&Ui#tnpXA*;WjzH>jT5Y72ZmnG8YL2tlrTDjF$LT&kXwDjM{2XW26`-GX} z20+kh?q5(NGsP#(+hyU5YCLJ3te3IFGxy^kHlMjQ{YLw9oy2F+TUyK|%t}_s87*B0 zhlrC!ITsq}yc_Z?a9uxBkTOd|7S4Gc|6oW|_W!1U!dM zp~`=l;F7GD+$K`8C+#12SnKuskqyOyUgO7kZC`S#7o)$s>U`%2W>oj1;|Vu-+L}HH zHD62ie~Gn5(g+#Hi>eba5Na?X=Oxp^y~ilfFK{32X83IFjOj!qVQSg8Bmil32mkM# zu$3lj-46lA-%Y*r3Y{hs#(SGBG%K7`sldJAex8r}Z*LM0yyB}ael5mjcgK{zou;x{ zs{rLqOnzY7K4e0#|2sd&!l5lM-daX;%*mSQu;+>fHC{lQLW9zc7P{<6u=VG<7Y1>g z=DiWlPzt1-rsoc#XmC{g&iXfgj;Z>r?()i&=g}&QFQ~jyr*WCUA5WfjmM#)RL-s_R zHuu8!k>cJi)3dA8ogbMK(bqfWHy!vB6bB8Nm|v9zaKrbfO7o)aG`+%(Yf|;lx=yxf zEok%fl#-b!dMAbtA!~ra#EbFXMus5G$zj|+I#tup>OfTtrclxYMo8@{__ttR6xVpK z1%mPyB{=kA69U8VYobu5QR*%#RwT!GxZ&U#`IfqR$CE*#Bg1Fa!JUR&lp6;7V{v-B zRr;x|C#xu@rT5(9DqwBpgYX`31COcZ(wCPJsZZ}W*eg!49K|;)1WaVKwYd+`!?4O+ zq~HM4+tMyw``tIo6Hyw)rzqc)Dbl3}w#f9_*9n&|`<@1zS>719jRqUMbsWEb65|~m z!uR}=00G+y)fej(E~DUfGXGKybRw{=BvN!f69kbOMt&<;6tn%(OqA%XFN(cSN~Sd@ zmuAak#RL_~Widg62d~=@h&O6_kk!)zqD5519VSKeJpkKRkj$2f(_5qJX@!fE${s@%;!14LuXIT zN@4H3&Fp(bbQ2Uc7GP8LV&p!*Dxzt~OrXt{Hy2B)WzY7l{Ux&3TGD}eV`|)Bb9`w* z1!{iaTsG$nqgZ&MfS>%83m$EyYo*i9HtjnvN_uRAyq7XGWjC|69sjxl#k?_BHBF6f z@02dG5^cY;js>-2s~9R!SnDWLiwo@)h%vcO#wt3$%z$;FcU=2yTYwDhsDT23Lro*O zsus(KkLH)}!lQRyfl@9W*j1RA-4^Enjvixb>cn9Pz?WN^mpD>Kd&CfDq9^PTL(Ijc zDKGb_*3#$4en-aeMuxxISg8Dw?O@q=N%A6N=Ipq2N{iVSn>%OkHPc3eJ=xlV16Qk_ zzjT?5Z7T<2>_a_Xa?fhB!@bPx7A~$MvtP>I7AcQBm#gP~+4aVISmSVK&+?vonU%{+ z5F`O!G+(WaB!`hPG#}FxCh3%@+nB#oj`S9-Lf9j$k@1cQmn~~#$UibWKcaGKEdA*@ z9~yxqKyAtr!=~9jSS5b7Ic}Ng2wh%mPud1rd7m#yvnwkQj^vO)G4pNvcJ+E$HTujP zi|_jSd#N@2MW<;c#R!i9#d_0Qyd?ILse+#P1KMA5UJIqY4hv>MkAH-3hF|DK+ z-ulYm8=mz$?FF9wFB2?dz0X99XTDE?s!^oJR_5~(VrHUJZXQa$dh?q-i=twgRl%M~Z+U;~%y&Y=zPtP!i*8aZ{~lR(C#QyyX)Z zrs~;%5^Qo6E+Hd`78bk=|0V#f2(SXiTB)5_?w8c%D z&^H%Zo0v?O)k)o_2a%0;yDb@QD(`H595~uce72i^zw5c`y7TO;D_@S>5yMwX@@7Ci zL{zw6?Q=@^XTL&1^1#f2;({2zI&fGGuapk%4_{Grq zn%iG;BsG(;7K|Kkv?YL3LbxYcYGYE8)C_mi%uCb}P@XuKW@|FE7AGQJoL8+B)815q zUCZZnGd`JmJ6^@w@p47DMli{?8*}%G5h71L&4R@VkO(3&cCq(jO`~G1R5hSHM)L8g z8Gu4yZMO8;7ahCj1jLKkJLlq~);P&)dT)#QXI}E71_y;QtsPD}qie zCOH@}^;FXl8>@+)qx3=+7!v6~Ciy_z7AXV*= z9EKCP+?b(~m}Q*IXo~@}?wQ)!#%KkEVBiNuvK_~H1=+V(%XHPDt)fPl+rfn+c`S<_ zMz3Bk`{Ri%VJm-J&M>9igPpEfeO+dqd!of7v80+Q&z8I3?_DJInH>6=JY9@X-6VCW zTf|Asw)K5YwNU@56247?wi<;0Ny6;Os>FfA3SfJ1nf=rJlrT{djL=&~8obpe01n?# zE6#`df%(tui=p*8r_RxnNqvz&MDlMzBy{rpSPSFyjGl34@OTF*Rm5})R2!(K3Ppm^ zN%%I}>cm5IY{|P&IUPeBY+Vg&*`$!*_TzwlaIC00&UOS8EatH)zxPPA6UD&naM}|< zfN6nX;J+9>BmBb=&Kpe_jv|2CP#kq_UpoFJU1$$dVa$HCfHp2&`XSkQA{YEu62-6V z;si?=a=b^F96X4%@v(cuPIBR~eb9)2O2xVK3f?+&HQIi#75=@cJES!PR5i`b@)Pbs zqme=mCQIh-d*X{&(X^RdAt<*tF-wY2!6Nr1pY@czsK~R@VZ^9z2YFN^-TJLjcsDL- zU+hQUm6%47=7$57g(o;$mFDV(iY%u-OJ`P;1Jl7&!;_R-Zr1bs&-Q{kPQlVZ;nl z(^4sc1xY&~Yi=KW7)GcW(L)dKJa8gHjq?x;=|ryGQUp4(e~mH`+IQ?Y4=Jz5^<40| z>AiX#ECo3Jqe_I*NyHg4y(Q6HM1Oati~-RN(gQlLzNfCZR3RU6XKm{sep>U2L(&xU21VwsL~phc#ots*9r8+K%D4KXHu;X(2CcmIsEaauSuf=+S`f45E!$ zWMTKxV94#us9PqnyJj&KBaGcjx;pTcB5JaoS%EGLjL>Z5Er=$oHOwKmo516JN%S@y z^ooWsD`O4>oyb5FFW_|@i}L!+hx!B(f@6~Z9yO?llfz`J5u?oOVYq@g5QG>+zgwkQ zT)ps*N+%=f$Wc7z6=KLJpaCK}@&nm&lsqf6h)LKEazfA?(Rl!-g)&P)VX05Om{*#7 z$JlFTXo5Y)uNF!}43}w$Il72Di9aKfqGss?P@(VG&}q)d_~8f;7d3vE3Mnz|02PKm z)2f30B(7P(Nc`@*t6DE1WAh;GrL!LicAyVCNu)4I5#tr8o_}>%U~^4^eoeIbXeTS^ zTsjU`#W0Aq-b7MwbpoOYY@K>V9s}5-4XL*v+lP71fL8|1r)>ws)kQ?9`m|39>ECVv zQCL}P9&#)~rQAu^m|nnq+qQir!u_pa@bN};RPEQk6 zVy}~;#-!mcB8nZ#5LAL%v-=QFa2$knDNO)td20U7?l7)-yriI0x7?r7l~{o{Ce12* zYVEUuO^^sDb)U%-U-KI8Imy0m?2nKd=}1-YemapEy#bF)8evGSv#<}wUCgGZ32=DL zL}dhg)CoPu>xIZs)k4(*$BBs4IB7TIQdBapdft>6Epx=)>&LS{K+YzZW)uZ@K9T{F zB`FDvm(*DWtUhB4ARMb+qH>9m{UI&}Uc?n2@gvMdWJ*3SeB$3B={9A2n1gis6R0Io zgc5GfIM#CJ-`bFJMLgw{{P=z^5#GU!s(oH-@Hg&^$T$ z2>X@+@GkK_A}YZ^zH6K~ep^Bpe-O=>kr!6VkZ^EK%Q~vKF=! za>R0c`ZP|Hb1o!^e6W~ckdGJ?W2G|%!fe-@iR$&I`k^B2;$E4K4|`HU!T@l~vHO=PA2N2`atm}NFjtjM&P zR)@7OvrI=zyiqI|LeKFdH2rbMwpVq3uqZ6?0))uB7A}a{$%rBSZICg27^oV9T}<}+ zoBzCi^{;34`^ornsZVhhu?TYB`rFsQ^EoNKnAf1;1W>&5j??hegw#5jl2;ihP=~R=8Q;Tx5%NwmhbMv(Ob#_Ph|vC&4FYY#6~nv2r@{PgNiY*d!h`r%oJZLOnRiQ7R-3=8RQS|c#`L9i!Z*IYn;VhN zvW+8a4r~?87I|V~(x305P(vzI={}SKKq91@F1Nf-j@J#zo7?x|+cw1T{p<*YxOb(c z@injDLK7$ahggHi02iFK4G&V<*4~5}O?N3yn$$#R7yol{F95niA+5HO$l%*RL|>!j z<=?ekSGJ9X?)Y!cIAj;v^u~|P)H-aA<|@|N^ioO(p4T~^>@G-n;|#&*#}Jq_mIUTg zT#ZGNL1u6`kB%Y9XKw%8P@JovjyBLlVfe@Ws7?l}iZ>pBP*^uXJq^i>_IMbE*Se2qCHkfpQmw=XW0UtQuXlpW@T2D|2EMWayU)L&8lJFX}kX@XdHYKMp_A-tcV$y9U zS(&gcL;Tr=lrXvUMY^maI?P+N)Rsrwe!Q{SqHZ_I$K}>%uevvpfaH2=$96^uK*JSdfjLuJ;1fq`Jn1t3+s&IcA&Zx$(cWkg^;D0trO=-C)MgXK`AEZlaeD)A6A zFu_Tc(oF2M;W35;3@;RJY~p+WjA~{ad6ayt6qUm?Q>pz4WJhW{qHs>hj?JQ&Q9JJm zP&mjDFtz{RVPDa9JZ%QF?xg+g^|vd-QJD$I5vg6WA{uzasq~i{LJeA=z?}Z?mEmAiWsnHVCnV2k$!)%n7M~89P3vU9?I0sL zy6>&+E*+zBTu#$g*L@Wux2Zt&-AU=Yz44ACc#60%Y`x3=b+0+Q6A?logN3~yPLv8j zRs8v{;+ef?40FkY+Y1pCC=2%xJX8g1G4;m>zKDOXr+ zMu(o;8_ZkQ&DK1|l7L3!_c=qDiR%B26R@APFcgRBSRMTFXS5QX^e04ZlGe(^83f|0 zX3G@{78Yp=Q7w!j@dkN0i`!R)3&I~UI&+VE`DjMJ2PWO+H41@{mkC7lCU0Gqv$%F- z0j(5kLkQp<>Vl~+h}#f4yK*l(rPgW5KK9)`?=f=%YN~^x7cI3rGEpk2wJ|D5_$Z(r z3#*7S{9X)AX%m#M+>Cu{EP_QEBHhPv zI>h9=fU#0fUH)`8mFzd?$)GB&O)Y7K&!d6;etF7`YRR=$wWE7gHQA=p?2W;3ynr-c zez@#=%iJ^~gSH|M&{40nrln@2*46Tp$iTNM7dVX+27W1V&M-C%Y%!p1V zCo`)iWP;dsONzpr@dx44it1f8OH|<5=;bB?h_SD5x=1lYdX$kD`*>O9^ybP_;#hfk za%$5Q>_5BHN2(fM$x!C2i~I%DtC(GUeLU=bhFZhdKv~(`Xb34gP~Uppi==mqp0~=aydsKSK=!i2_S9#lCIoN$GaY(2D#lIGvWUo_*(k1WJ@r? z0L@7XcoCaqXjxzN^BxQR4%vU}MXq+pkCRo?@{qDKez!CIiWmZ5fhrO-1v0KyBnO*` zk{&Ew0q&oKzL0d`S7-TOqCT$}c`zST+BqtPf|bOq%6Nt(w7AQU4cW(uj`5Uw`E`h4 z)Zcn{Q#X0zSKm)xCDC~tagz_M);XEkRP)Mw*MzFvt(sJ*_;?Wz5L#b8UzFfGl)`6i zEqmT$u1XTX7InMA72Ns+kc9sd?EDpYXG+N`<@h?OWE z&*$H4tj#Sj=`0HLtDV62r0@`~&CnTU-R^(LWmEp&U_rV>%e&3%F$h0!Lg-NLsK^U9 z>|5>)thk{g^D(iXpTvi@@2&YuR^f@uiWm402ovGGJ?6_$2kc26n6cZy3GRQ%HcPIc z!4+P{w}=GT_v6NGi|%kZqGf_DCFwAhnGyVV{CDf&xA0O;v`gJu5|^p93$o}*wpTC5 zR-P0VV*{-(#W|1yc+gH?3)m|m0>_WS2#m-XMGg9m9$TXr(XXcm?z6z|%rPQpT!rkl zfPJ=-SmPPL|sL`a=m zsh<|vm)2h^WWrCpxE+2|NOUgYzf<&m)$urq!@k`?5!3E?vLDy@uk#31x@o)})8MXu z1ptJ>9-T#ZH?x54TlLi>E1S&;X63@+B#n48vG%-R4|B0(P04>*wy9=uH1#XdTb}8) zF!(++nLZiIMX;XS*94GirLb=Qn_r41ILsqYV$;STIdQ5lf-+yGF*%dC?cdf+yc%nF z_hWvOh#UAPgKEcdO;%#*_;BJQQC^RIY7a7>X5n7^z|9_(Qq5K6y4L};4;>tCepgEb z1%Aj1^^c`b%pzF2-dN^cz+`=|^ogi7T;`7Kp?}_@-j*{GiN|iH`#M;d#6(fojdF`e$d_qMiT&1H&=M`C@u^)Pyi81fumzB>msU=)vM5n^jQ6xOvW(p znaWC_ROml8?OW`pkn%7p?HNdAu)@F?ns3~?F z<+S3-W>4T#k}v;53?kQYdu@Vng$*h)LF+ufN}!1ZM`B3!zD9)QJs^hk{7Q zveGHFttVofvRlin24cq;()w?_Nru}Yep!p>NMe-6QjOkw*%*EIuo|NHkWK$ z6GbVKy>8)qUnBr>kN_IZ63MVu<5eO~_V=uzK+!shBcTA1lVysn`jRfPCojZK5h_na zvv{DlXD9|(RI#?q^>7Hipq}2qgXZxDp=oO3Qd=4ENI6#eKwI2#%MpI1ok-D>+9W?p zCe8cI61mU#4fAykwzNxsQASh>}&e&3J;?=hmh(Z$TywDEV%@R&!lF)k1P~@DteEV zxJ#hgx4)TssBvK}rGa_M8>2$MZ>>)Bq#Kq9>Q6q03!dtu+D4?iKNTueg zscO>H*qh90E|g&GPLgW%xNWaynPkw+vVt`J#qNSv1}QCe?^o(qz(gxmgLE2hDYN%y_}e*6^TlpQu{@=iRn?9c zjH>!_cK7_p$Qvc-)yj95hyghN0|K39O9Th(h|c`g(-EWma|5m%sjXiVAq45ykAVU% z=wL0Id35^Pdg5$UffJ@iPx(i2kY*!PPGyJ$>i;*J+lpc7m}$ ziD7E%;fpw8uPvLDhmJdJQ}k?Xd?8<#9X$-`VolQpB0-`md3VLU3@I^(^~jFn5(q3F z)6ddyujydQe-mnCF(@>zcqkWFwBl1LA^HZ`<0D=hBmUjj#93=veKDEQP?`4req*S^ zlK#n_9j)$vGkLP~lt5-hCMC}Y<&$JLAdmpixYiU}FI=~%D-iB< z)teHfQoh3k4>BFh+}gHUd(j3A^!#L748$t`NA{y_g=n)#*E$k~>hEdpL&e--U326w zlHZM!SKfAzjuOXliMQCUG-T~}3lB6Q$7sYJJowJ)Zb?UYAvD7&6Lx5Y`p(SKfm)$x zc136hN@CI)=nQCe912xxZEnLj3tsU4y046R5!;b$w!`y*1!DSQBFOW~_3*DiRU&6S z{+wW8`Dpm={mnnyXx&$4n0OxZ_IpQ1dxf;~&kefC2O~LNhx^|(>F%x_@#-n9EQwP1}pL*+!O6;ev#k%A#-TH(sP zQ>SIw?PXr$FYrA-`)a+ckBgZ&m0Sp_7;I7UY^y#o%MDNYP5ciI1V+aff*9HrpoWz+ z_#<^F63D@RSWJv41x!bS=)%Y2PI1iJ#|*ahdM#8@E`B-6;xi9l&IWUjD+SoAN@dhq zM+5|U9PTwL{AQeKA8m}dIA&;mt4Mh4BKIMt?g6aceV%>=88GthitQgzFT>%P_!pDA zX z_?BZik6wY?UR7(3EL0sscIe!$eOsT@74gF4&!G~kfJ$rSY2=dco>|1<9wR$Edg=2* zHy1F0!oC3tiEFo8rHA z*G&BZfJi^56P-uUy;4Fm0$aqRAJ<%(61i3xh=mx(%NzwZyF?hYV_4ApsEyM8znh!% z&YLqWBpcn)v^8CW>EaF>-%9oB>{kt$?t4>Crn;o<{!jyhCFA>8AO)XUOZ7D%9zR@q z&%h1?Of=mjTw`q{KWYUUe)Bnb0^J^ViOMs8Uf1zG2X|8$;V<;8M!hh_Eku1efSjOk z4hp8(y$F7pA>$3r^A}1AbL4CVnmVpWagCiCxV=!-9p=v#UXE z%*0+cKe=w4KeQkZ4zA&#M+X7>6CbxR-2zM$u$`Wv;d4`YgwE7E#`4E_4$Il*~jw_d3bslpvCv+$R#0#2GbEB{=Q{5~766g;z1|Xzf z$#{^<3BZK8dT|;6e7|f}L5E{AW9V7~!VpT3raHtd`RR`cA$nyQ-;Hry6_#$s^;M2V zU8SLR7O{uesxEhq558w#k9EB4ouvF~4g~cZ%`G2NJdlAkKgM*U>V}kG@qz2_wOhhO z;R2Mqru#pgM7@abvpmw!t**%!GvT!SfbrUzNtYb{%|3a?qGvjBzwL_8-2a4=;4R-e z+Uv$5Sw2Kbz4as*1nB{869S(4CxFm@qA3Z_jP11UI_Y0xko1fJw!qC3O|a29Y+Ur1 z1ODzG4sQNgt$2!g_GDH`H%JmCVEp#2X0nL=sx%SAENe6eC_kZs!9pVJIzoFU@;dXT zK2Do=?$0JW!VTKu;8I2r-b+7?>qQog@6nd^T2>tqvd zc@BN;k#?@Hda`%_&bzB{=UZ;~tWgFz0o4;rAdA|qD^MX3DZvpp zi+ArZI7Hw~2tKqHE>eLPBVVz1V@l#01A`e_koW{Y*yhJC|2Fe~ORRkHnkmZJ7TMqO zM))$^x%tfquzsBl?1Hff3ELq*BxHf7NTNLj^1wCHE3M!FVw#u{DdGEc?zL2mL zoFykFaj4Kn-EtKV9xZ_WURBsApa`V>!Jcygr2=7=Z+|R1u@uC7sdpuEoyDfDe=<)c zhJa$JAm99lgkZ*29oCg*$T9OiAy|ne!-M7LjJ|_-HR(5(Lj}8}QR5viWiL*|NRQ~h z8B2rQBEKi@3GXU4#QkE&ZewzI?M{1K0mJ@)qEjRIH5K>!g)+(zc`Q*l;+hf3&;I50 z7{@B}U)7y=jIV}RwOkM!6`w>fe*|-b!p_NHz-zgJvn@_{ANP{I<+7Me3%29$qVc|K ziovYCPYx4AyhF+_(Mz@y!ls|zdtMGjtPraZg6Co1Cqt2{&+B5W28iK8b42vdZY-p8 zUnY>r@}B|O@}hK<9ohPfyTk6;FMR3S0jmu}bY`vcwyp)kY>LheVqta~}0b#V24_oEDb!~@L%3dlqe6=G$W zD+v>u`d9bc-R`FJ@zAvh;1y+@!R3fSpstx9f*Nb`4jio`jkvbB@~`@%fE#3!=m@(9 z@*7!ZI`47)QEMeXsp@ro)1xBSvBs<)-av7#fJgT0$t+2^J#Y_DGFq&;oI%4Ttsm@#OqEw5pC}`7 zE|I@F#R4uFn55n4C>X_)h!dDJ!;~p=cfcflGdwS7Elntn3KdZoqe*;^L6jv*y?=%f z|97{1E2MFnd!xhMIx0#=p4yCIPnb2zk7oS*dj&~(u>oOy2rHJSDgwc`VS(EUp@w~_7zk`$w=O|6x6`=0Q`%Pfw{zk9+>A)BOQ8HPaw}H7~ zc+LYaLQnyfm9h4r0{h?H7;oE^9G?&uG!=xt79lA7qRUI1YhHwK$kg=N(@t4awXTi0 z;Zf~E;Ei?f{J5~q`;J{~)N;n+Z%VLMyeoND#rZbAcp1k6W>cv5UV916Ix<>&W#XYQR9^q0B4ORr8;EG)bXP{7d>a$ zYoCx5hq%seh$&x&*UD*+365Ov8?{|)7&AoJKf_BDfEE2k%!|~T=w|DE@@ekPfXq?o zIh*K8b?cRLQNZ}^83#n+mu-zY_3nafz^wSh;CELHgFB+tNiQJ z@uhKS+o7l;bUmtWiXA z$ma;)ZpG;PHo~jW$sF7_C;zlDMLJ(2K!sX=vNhfcN}`%iKer|K>!lL5sbkVQZL=N3 zI`YoD<~6oe$WRP^a_5^hi@k8GgMC%N+QBPHWkFAJ=}iRpOa(pgZVEh zTgGl!UI&eoUK1wGUhO9S1OmIGJxCw|h4@4ir0afiDvkfTN(A$(x267nJ$-dtl+X7) zT}#Iz9nvfypn#wtASke;AWQE8(y1s)cZ&3qf^^HOv~-9FNK1*7ASEpwzgga&@9$sn zwa+up%-lKmoOAD>+W5R?>sR>KP7m0Xga7%Nf1O)0xinvKP}_JJ`Bn@B|Lw+j{vP|%CN(pwyu zM4_I95}u>DFvW{p3u6i7w_9ec5c>{l6p)WndR<_B7>U>;hx(IL5F(Mu2`$Z=(Bo$5 zyQW;F$-f;hvw(9({fSXhQ%vw7?f=P!n6w@x5!d>XwXF&~fG8=9S8mX6PZ_nOhzhm8jXDA@c&P@RQ zR7l8A%!mtx;dJ;}9yKG3oq-$f-`37u$#ZXjvLfyx?Um{7%p5bR(MW1An_%N*XD4hr z|9T4{KIz+fsR$jC!*l|9)X`xw0yy%n<+d-~3F z_H2E2FXnUmu~^Q7cQ}Ylu|yF`{&#b`6hS&ploWI;ES{liG)e*?1FFUn9+K|#K^NLP($>D8D!Q%~0hg1@ z4=?z&oV$!piIPJdT3nBviE>;oUxxQ#3JPS%q2cXV1<&gTYp0PDW*fi8P(uiz-y1Cp zyG5$8s0dlDm3E;+h<-*vE@x1fIk(Y-0A7pJLjZN-EUC?Z@P=|)ttp*@xlgxetsE^MeV#nqivZr5S zu8wLFrU;;I!C8Uc*eq^h_m^PPQ1o+5YhyU{A=mAQXZt?K&x{e=I!{Bob)PPQfY55zhK|68c%`O8!I-`1GRo}4F=+VcS;xO|E6*N zReM87l6csA1)<0irwZAtuN&e77Y$h`4vOo#q}Hkms$|=p{2GK}3x5?i8PXG_WdP#Y zQ|h7vclQy|`Zce#JBal+1K&s>ErHRL8a`EK(ec_Nf0uxnQlak1sPu=u)+R=#j8{2{ z5?$;shWVD|efg`C`yX}@d+4rFzM_vxd3Gyr! zB#JCQp#U9KE8DcZlZJ%!Od(DIn>DYs>zw1b<^6g}+l+}4qR^+07)?jhBlgqLE3992 zwMXt6{2+nNp6|Ja* zJ-MGjQ#-_a{e7OpIT0^Pyo^_a8tt<_t4squ90kdVpjUpOvggP?bY`4 z4TT-|6F`E_u0b<}SZi0OG4CprkZD<_A|*&&@Ypd6L5$D(XYZ9?QpziiR-*a6VY8~Y zPADEZpKJ~ zciS;c_WHveLA9^BZ({lbtf&)%`uRGgBHXFh0WhEc%}`!GV(l`3ims5%=^_6BfdM|pnD3biqLDj$)E>RIB{8bO)dL@c0FHZbM!gh(k^|LwZU5r3f> zLvJbH9Sh2#7GGy+GYcD-+4x7%nV~ASnb|u2yQBGx$@VkZLY~+1YnAmd_xS^=_5W1S zKdI))!UyMS38%0M&FG(Jm*!HDwrwpMh(Ob7xtv#H(l+^62tktbKJc!a@-!Ut^A<~v zYH>Gb+!t#Q=Yt^7bm#bBnPU;+@vqXG-!?H6;h|<9SW&Ao&J@+xKBYQ{U!$sC(LR*g zP_G@cQ*7~XlrrU($cS$geit>P+OnsYv058_7qp@1R)p7kX)e*#|*`lz}pjs!c`xrLaR()u(XxR-sE2x5X8LR z?cjDku{6_3-EaH&7Nz^P8>gUcu%7(uBM+U7>Y7%%GqstMvZg&-(dM z6zoMTy^nLt)W-|-Vq{5SDn=s4-aQJm~zoM19;9B@wsUL*^nm zCr$X{*1N-AklW(qPSd#}u@n(Lh;Jah2I%Rr=%^FE(5T4vpJ#!DXmU*IcdORnS3iin zFiSWMXCI75i>=s7rwgUbEZz&9H~Uz5B?TXy;M=Nyp>UAhw|}FMg^D106k~JC{`+gM zo3>tTM1|1)Oua-Kt@nx}{EzeO7t4l6cMR;lO{m}WvnCXJ^y#LVZmCT?W56-8tw}%~ zpf1zfDHlyn?UEtM5a(E>5ng29C~?u%s^L2NV!`~=CV)`mOsXAlJ}!>i>3<6Panc}Q z47VdbsT?V0k_5@Y`*%3CE}pr!55M>()KL9cu5{w{VgO3B*mSYM!>am)e%=P$@zb~)m>DbcE z{RWc~+Lq#v*mKXEl5XGh1Vj#Zx;xyNqJO~wNH;OZ@MLP&;A(LNT?$GE@B_Cr!ve!m zJ=fdPBPVeD{(y%E)WKSA1v|)}6*%sK@_A}2eil+K>9<1c29u61(O;%biUExYfH zQlpI~$|uS(l%ozmX&L6$rF;pajYo2kY09H+MMSk&s=og63m=@5? z=+|?ZnU^xpnAY6XWHtyit`*ZT{3K`Cpk`ZYSb*E#{9AgzPeq}it6W9lgh}LNr>km? zU!kbT{{)hL`ANgBZ%kSznYsUCJN962ar+L4fX6!!29-W@NQZIdjoLxN7~3tDaC$+X zW1^hYK>#YAQ`-&}_MOdg_A{-8bJukc4RQT};ypg!zVMn zN>sz}j{cj?@*(w~HyU-mjk#((Iabj}UmAsN8Lbli}Am^`)7AZA9jcg%Ed zW&{wj@c67+G{Y6nx+*)X3FwK?B&ncM;E^!~lPqCyh9>`Fu#};9PWjJn?rWj`dtJ?j z+Ka^fiA4Njn@f9r|3R3;9VgLSY~r!$yC#i2n90$XU*QfnkV0dqd!>i08p*Phc@h<; z!)DLrKC0MvLi4%J4iQFP`ChjhDwI?r4K~ZBv)CUG}jNF~&In!MK zkix`u63-FB*aMVVP^kkhT-tP_LeYD|1=xHB%AJW+U(%s!)7~l3DVYA=)`VzCrTwGb z{yZO*a}=70xj1*6eW&+tIwDzs40iO^L=&MEE5YhH?xK45qgaiB+3-)#F~N*sndppp z6OEZ+38~rn%SR_{%FmxwqxVnAKy>>^vAXv0-|FGg+F;C5*$4-F?Ni|^XWFlTqJPjhVs6Dj(6Ck$5wNmtABk?v2~zU)mSm^ zgaPr`I-F4YSD1=}2)TtEG%2f{;RvuR z=g%sf$e0}M-4ms&0k3RsIa2psA5@pEwtYsEHRhh|#&__B4o1 zVUgLxH&1WLggxr|>GO9MLSy-7j!l9$G37W_0hchvb+Gs`@ZfMJx!$vbX#CBxlO&n` zjblN8=H36Pjwmm^6Axr{eHuEpns41A60kcxqv_Bwcp6!>!z;y%=G(aC{RCBwGU3bx znK*@qchI*)@)mB1bP+)Gj-=)HWqfxuEOOU3U+Lc=gJ{jRi!A`Q1sNdVTG}!P|Sa6*4jwaobzg9jGkg>#{kH5 z;qyy@Yb-EeLsATV&OEXEf*6IfGj!q$`;Ngo+@w*Ludl1 zZVS^Y;VG;0Ff~H2KJvpUa=ekxh_pfy16)ff6=x#cP*u2H=6z^GGhX5c_#!4)W@i?M zQvd1B7Ab=L3F2i!QEU5;M*(V(UO+556lSt{vfBm|cA4GAC7-S{#>{mw{4I1v&Lf1w zRK#$Em3kpH?oGeHt^bzGbBOg0sP(&Al~3?DIQ{}sg!C=^^Z^N$Mp0e);*`g)E0uFi zz!j2p17uou7OnZ@)4OA=K&1$`)C*(pjFg7(<=&2v5B_5JQ$OW&ODSQD z)1p$biP}3FkQ|K{-_!Q!E9ghQ1%3uNhh^FDVAsQ!EZi~_GhNj3?MOH<8N!%YDUfnXVKw`wvytZd#dv)qtR&n_y%Q^h>Jc} z#LSr&=kYrsyy%6oVIP}KPe|R2p~wkKw72E{Z#+%%^{kyGBouNHbnDh``0XJ4h^*>^ zm;+M^Vu4N(M9nc9n4U46+fG!jYgk-JnE9UK-^ocKrME}T6Q>2J816hmMV1|rp^xhB zcxy&y;)H!)Y?J)h(T-+1yfLN8QN~-4HcX`=dyMQE2VM<;!j@w#UIHR;RNQ|x)=(FF zn%-0re$@l=$fo{Qs1}#Y_Gsrj99k+k+aBYO@i0;ppu($PHjho4&gT$-Ssx2{BO3EI^CtXc~{uX-QM zZHnf$fKK-g;Kdomr7rej6t7+;9&7;qn?Uw#?9%Nd_cg&$3UK7};NzO+CU&>kR(75* z;OsuDFtpz=MJjGHby%hgMsq;NUO9D0rq)X>xQSx0ws zEIcg;9(2JYnc+#3E&MM=&MEzVLH%bR$-yX1I+;0WC4}*Pvk}LnliVHrnAknc=yu}= zeVRvvMS$#FB8zqzNm<;L0LfHriZ<4x@S$BF6io8F66r? zH=AsuUdMy^el^Ov%TyCCP9=oW+meWqE3P>N&q_2TH-OXva07`jQHT%Sk{30fhrnbs z6Qh&z_y=G0eMkYmg@#W)&L;Hk)W#FwIKL@po$j2igrZs3<3S9<)4Zr1X`!Dr6)0eZ znzMlg0&lxL4T|a7g1%E+oqN57&1YxZKLN$lo@HGa+`<3fKBZ!(vbx`Tpi1rH!lz`` zR+XE&1jO!w;+4C?k-;*HlqShe0<^GlIHpx1S96xNGfKM5*EFj&$VQzC_EZa$=^M|} zIU1{zpuy4FTX>y6N9ScX1 z(ft4%V6V;?AMs=bw_d>t=GM-mO^g6i4>fd-N<+pAHGeW5_F$jJY-dnC&<1?!Ae^Z! zcfelI27x5q1A#h2XT7A)1+hYzhI#t`V|CQMR9Mcri5+!cNq>O-_fZ=pBvkcGv`x-> zx4(-ALZ)?mIxk{Nxg{tBb71q6#ge19abb612%Io9BMY<%hdi{W3bQhnuyRL~53yU} zFXqu}Xk<8W$Xm4>%*a%;v-^Mhz$+tVUojKKLdtx}0H~IAxl| z*)wPS>8f~|Ko)2NJww`!0G1@@$#-v#B{MqbUnc;`7#$gWLO8X78^HlBOB)Ug(#_CLa6aV9}ok1|WL#2F5-*0pu zj2N4B4hpzP3giUKyi)q@L2Vnp-Cs1c7Q`m8Gw8cmvyHRO2@e+;es|O)+VFdP7#>L) zoof}3Oso8EPa3RJVXupsa6U=osWa$Lg2Fb?b5Dp2b+vFh($SV7Prfz)47B%pTIql? zCMl;E;`NC?0PZMD{a;9-=0~=Nx`gwDEMvke^40KtO&><32D);mA_LePi10Rv4sJ_b z{&N3DhIQ%p{%$j2OA`$YtM1j&icF(gwyNf!&`4Xc9M!X+#rMZh-I?aary#hv0O_CuQx3dFuUJW&Kf#4eKFndG(MP=%v-AB+9Gwk-vPb8pnMUa%c`&VH=~5bpzo714uI9QoUP#d9;~ zJQWUk)Blh2`-U_`2`Z4nZQ}x9Xllzvf-0y2RqF3HuTO!%3U8EvHk-?Vp(1SQf**Dz zNP9nCgW8YS0?}m4M5F|iclidYRH9#B2}l7;%?>fEMJ@H}&8|rW3n}dK3LIF|Kz7V; z-4!8=4HzfQ*L7J_a_adL&HKB3tnnMWMgq5KdRu1jOu)d%v&NdUb*g@Z@WSL zkLLKSg=(|d)W03RZeQy0l$K!M%HdU?tL-L{17VCG3}iuz#&iAeMeHR>es0cE8$Ts> ze-rXpA!+OFsTRAj3X>^xdI$V0piIqD-QYQtti-{KxbaCb$=$=X+r#)Z;OgezH&n9 z0?rq5lIw#>1OVR>3ntWv?v{id zIP|m)e+FXtN1SqykAoJRSI6>0R?a}`-N|hwj19++U(%x3iJPKTIwk_*b50@d2qU0M zB9k;|J{{`-P_VqHl=?77&YIgD=kjMhRWw<&ARK70-xn>xD(3uduM25)LPYs#X~<&b zC$S9K?Ov*q)i^VlE_WcIW^Lm=tjJhJSX-pj>!ySfRDx(0LTN3-azM8$qSHF+HEQtN zmZa4)sP$mN0FoQHnAE-PA^4=T%g;iNj*samMad`R5G|iCc$nqu%cQWQ_kzwRP}s-9 zE;EtHJpL#e=vmY81o&L3!Qg{o(zV}a<^5J!V!6uIopU%NZ_#q?2kQ_x=MeY2>&Kyz z&A*G+ZXB6Oxv&vrb8xak2jsCD@*h_P}rwX2fEFrFYDW^tfu7BPYb5hZt!81%fcth1tc1 zI_E45Cnv|9kPjNP9XAkmqvQtGgMv!%T<=*8m)QZK{P0l@=&eT%2%WA0B4988QPS1Qr`E2T$K=m7sIQiWc$;4Cx%Wu)FEDz$ zXfGbOaJY|D!UKxz-2svRUc&h1Jpmt8;BDdpDtq9$`L^@?Vi+6o`yjVQTd3Wemr)TS`x`l>pMQ`zS9T{oy9EcI`?jFl9 z-t3b@9iM7!KsvMl=U!i5&Uf_tqX$b^VP(9Ne+bkZV{sQ4<{ro%luA!>>UNrjQP|DB! zlW}Oj?6_{KS+2eOGf}sk?JX2$BNnNs3P+;2o}oV)(^jKdkw8NrLYL6MK=93yIl#N| zeE+_f(5&9u5xu0b1ghO<%~<<6#Nr5V#-u=AVef82MK6eaaHT+2IqL&57#k;@QRG&( z*m{|64<&?C*O>@K$1E?ZhXLbqa5n<1xkGs0Pmq=Vb4M}!4RZL^*6w=fRlFdFd50`d z7o>pQ9mT_Nx9(CCcLJ5<8ejN^Vt}+iRwWsZtFC9?s|~>*3D= zamQaw)qtxvC&z|`P`-{0e9EUYTy$-PxYC4Z-Bl#Y5Z*!i)|j;|S2p%KLgrnU?tB1Y z8Yc+p!`!4AyE=JahKA_s+w_v8d^uppKU-4Nj+}rPnwqTa6zv_B9xYc0@&h(EgDT)w z43^P_tbTPxfVfRZ9p)ny!u@E3t9~Oca!#K?Tp4>teh?La7q$m2-(i7TGw$H%rrvy@ zjeI)kX#88}Ix)19Vwl6G4KGC>8t~ycTI!qxom*kQ)0QxB?wuAQL9`&Xk-G@8YsPDD zj#<`9js>ul@uq^HH4F9Q%GM0MDe@pRIxB9Zo%%ii8EL^X%D-TPiHlF~E{px*`ecpL zH8{g6w9sdG2uUNH<<_8Q#=!ouv62>|;>pf<9rp4SmPO$M$jpaB3G!Q}m5e3h^vA+~ zk3lI};oH`c1N7h~hN+Rw7lOQ-qYB?_jD<4*pW1Y!5;)0-0Y{M?XD=xhx(5LSO74x- z)^`7v{cv8SxJLAI$tT8$D7XC(V)W0`Lz^greUL_`-nvQRiCv|fkDbO6E1n~Qh&QEQ zWGGI|zqzXWznZ$%96!5J8|tG_9qZ0%tj#r$(`Rom`Q^6qd=2PcQ`)f^v+Afp9F5CI z2YblTjd8L?EZC?&)6&30t2t;!I~3l;OgI|jfb5sGyM=#hp8{(F2th`k9-bZc8N5ID zPCdKQD$6U{B%HyqUnR#Czhn|!a5b@^q%q&z?8s`m&qS0~IA^Sq!TiJV=zn4j5^~-7 zB>la;;{F?p03^4ckfnN^F=3FnLxf-poUf#$sGT7@XmJy9p(F5Do0wEWBs0kF=eHO- zBy`$>2$5&yz=u;mX6%Fcja4XtK1=0m^&azq8`uCasN9X9!{r_>R5%`E*zdnQYo`$TrVF88*1~}Uq)B~+u{euJP zehL5s0*HV?TmR7suaZ~exJ?A&@S%Mco^SxY<>u({8kalNmw|yVVz5JEM>Remj=utI zAg|a#+6BPZIrAQfZoia4+za8J`MXpa9W992j_D_ZT)ruc5-WldcUVsC_ot)N@~8Hh zpjC}^nC(v`Q9|-9?oTu!Cms8En1ujd5RE>TDEdF|78;R5KgEH2bu&Qw zNU6StDMZWeC}~>+d;>U*Ec)Zar9vnIS|xo z0|><=>|ePf1;3?3IbZ_n9bLWMSj{d|WlX^?9DcE(_uQlxGQ>uw;|4w@S6+Vl^-pwB zR;fcabJs_URyef=Qh{EVIP+#u(kw2L>@Qz{F}dNY@!podHX?s|91?9bA`yjB%<$W3 z+jV>U*rNk~sWahXK4EXXV$#dW(YY1pB_2K7O#KwBAY>yQ7u zZ9Lzcd&|trf)C(6&u?0G(R8Dd@Sj`&3#}`#!%pdk3zj_7_;0d*Jq0pr5ko7317|&3 zXQUK9pjx*lSv{xg2T}M*0PA3a?uUbZGg!A^ zFg-|+zdUXzF5}HPExVNy{f}&@VMA-49qn^zK1VvP1r~IUSM2L+5*fh1^)d(jCj~ye zQ?kAd(jGA10%+7QK}GViS|tg^p7M}4y&v@-wH@~kX`4qXeE@O%43?q8+({>bzIX)u z48|JbfMfUu{7XDhD8VVb8!Lg#{VNH=m5E!gYb8vxKtNI7#5$$LG8%S$Rkuuao(7>x z_VX{V_+YTS;fYs8XEPapCz?Q8(x6Gz48^Yww|%4*_#5rP*$CEKRYa&L;c03MNavPM zk>a4A3&=8zkczjM+iv{wa})kch$Mo-^MB*rAcUA-1BU|X|H$VMG$SK)l;i2vEX31y zpa8ss+w71vvlq26%ueGV_h&Y+3;x8-rmmX*ON*AjBZBMwGNVmOjf4QOb-1nF6lNBI zeXM#hIsArg&&$WIZ(=|>$y7rpW@9cKTHELWx=C%>3cw1}^u%zi7{v*pSzl#}3P6Li z^ed8uZz*gsWE}%%XIAyqDE{x`K5Se5VH4M(m>I3x>7=DiXk(JT!1V&N?BE3K>?X$bEtw@g8jeYv?IuT94JmaC@!+ zJpzLR!;P*k1YK=^4ABumFJ`Q(c{+l&N|37}7-Y3IV8YE1H-@ed_zjB!(T)LZg+R~S zu=7WrSl(+=0nUH+#wM)K^fhBz>k z)9_Uu7Qa0U=3h29)aXAxW_GCUh>Dy=P7i-=#BIBOT%`l9V`6?g z8Xo<3W7TnKgs%gG!Z}JM_=(>eEK6h~?Q0~G=Kbdb1T2%Qv_;_7oF}RLa;%k#6VU>i z8N_w$phj#4#kPswITIAh@q7ghyk?FHfe^se#NtE!Cv1^6IAqiE8iV*h+m5X-@+~cO z3(SI}IYGaoxgJ6wv)N#gN{$U%HYwHxhMv1T!@DLM+fRzzOh+H-{g>)BxOi5H)7=PVImGNq3D9!;Pp#C@K z_ttbOVBwS=_n1)q$5UfoH&ly$FO*39S%jNS@ZEhyKO)4@WeigBM1}=64Dt*B44I)= zW)4}+bJFph?g}Ys3fWNHouBgW!`ehIP_j9-M2RH!iU1wyE5`pIPakyJ9R+lmpMV5c zEMUebM<$M%=7YnaO>@(h?UUIto%rcw$B%WFUidy4&X$9fE)^hc!)N1~2fRPO!s-3<$2C;rpp^t~vADV8^{#fQlMVm8`2oM}Xg!3pD`m(-! zvF+#%T?KX;JvnPWss$LhBj_$>ds<3XbRaC$DyJrr5DB>jlQE$^M@?w@mpT+~*viI& z>wXF~{UZpYU8AyD zVD5k1efIC;L1%!Z7FY)BPZIHVK*)Lre&xCO=$C0Jt?&8tY>xlw+IYSNW&ivCp5NG9 zV}}0xEWUeQ0rS$X48KWuZavr-Xlt z8KD_AelJ`woZy}pAP;oD?N$j26)B*CKHn$kuzWG%`pW?5Rrjpvnzw4350#I<7dy@) z-(e|Uz5te~QAhOkIu$rb+^|oy?|LDQoeF{@5>0+<&Ov-YAU^?FmZlU=`{0ZkPAx(w z>*um9DPlrJ`LQq|bTP^vF~|Y!_?U=_JnbNWu2Mb1%n{t6Urt!x#_b(}!d_(fF(Z!T z7P7iA^i6w5@5!F8OwYDFY?hQj3E(XQz$%rqoU(Z&XXw_l{kiTciv{n2m&5yni{07!_N=AR%an=b4bK@ zTPzqepZ^8gth6W}INE z1yfHrE#1K(4AA6m2z~!l-{SaI?*1flz*B)57EG(|x=c|QRW!KP<{C4H_YwGb$Bm!TEZJsi8bM#FHgpvE^ zbkGD&#m71gnTT7twO~Snb=#aRkp5Y(@7(UdYSemlYp>rnsgf~roL^S6d z->aDUP#f$dE?|&~L_{Hm!-2lXUQVpjj|L-*IdbO!qQtSeX^t*jmvt2LtN#&=<~wyTe-84Vk}} zib5TZf5w4#00ICP7+rv(lpz?6(|gx|dC=2;_t|lC1l3&oGDy()o~H$}NeukOg39q@ zo||t6ZSY2-%A>)~FFjAap{N3X6FgWNKSG(-pV)o&Bs{bLv@#XPhhT5r7&X>IDXMt! z5dXn1_UX;iFSGJ5ohYVAes7+wuArOoO}|3rNvVF3L~^NaT9kp2K`~4}M_M(x%JJE2j{ z<3G*Ro3|?cE{S*M_v-z{61RWOFM(vaU1P!-k3q*Rv?d?P1Y-ww(xMaJ)WOUt@{?U2 zh?rN7ky!plMebH zTpl^hG$~ZO%%+{}b3DBMAhtjy8ajj)F|oV6IQKX^a+mc!fHm!Y6W(9Wa2=N0YE<58 zJ{3J*)%MRlob|T|QMlZR_0O-J^~*5#KY#AET9AS*Gxk^+~i^s!PdSdTC_D%<2ye*A((o^5PD+$T(q zGa)dy66zZkPsjdwtXI_P?k)G*FZZS4;qzTwo()_M9yiLLtXvOt`{Cp_KU$%Ya&{td zJmVr}cpVoawbFXXfH*qEx{mpB@a2oRmoY}8?P~0Rk#Ca6*TJQB)iFI;t?E z8xl8*BO-my7H_leg@1P#rQf*u;s&Uq=n_4mm{cjB%9XrpULjTOGLsITf8Es!5wu6~ zpm2R}K%t1y!Y3XoW)25e4d%#k_~>)iZ$+wtWFPzB61bzNO5dx|q($XZTRgJ&gEihp zd3r(4MD?kU>p~{vU^jgu^c{}G{nt3PMxlKJO2{^e*$1W4`D_1}N8(V|?bd%(>`ysX z4)JIEC|zz{o-5qFyLWi9ZgHu8dhj<-bf#gXsX(}}^@^OzfAMvLW8YiZ?pAwP*uemh zd2?9Mq&aK=b~%X$k@n~G47ClsSpzmVQgHx8ED%xwAuhq9 zWT#fmv>kj;7Hi;Ayy?eCkO0m`U}4O|R@WoQ1uZ{@iYu)5xi)97OKQZ?bh-#Fft^hV zfj*R4%l`Wjbv)_%KIuM17XRjL2{Qp{Yg%YzTZNJ$wyY|2lDJfwMtF{;Hgw?2R4M3d zzVm5{zxob*+rvT1n&_@hMPvv8c-li&t~RSnK;0PHwHR)KIpyeNntBygdtWrPgoxEm z<52M)4yj{kYS%A$V(6Ci_>2%H$@xd}LG~+y;i}7WMx^2t6UOJVs3neDWMRx(XpDi8 zP%O(ysYnW%4Pa^x<cYwLZlzj_WYy0zqxXojfv(j=Lzpt zT->g_yx6)#@9$QRn`>O2Bv%efH~#~s34g!X=HtJR)%GpF>}ER7v)b8)ZG|SQ_o8Q; zhb!x|XOFjS{ny*Kw*UA)xOg@BBxNX=O8#MiMvAC7_GLYbou3Yq#<<)_#@xp;o4yz( z_KVjSxpLng7YV!8XS{D%GOq;9G#ncCJ*B<6P0if*X7=+m#PVi;xqbHhIVJ`i*M48J z{g*@zUv@i#znXz-aLyC_p7-|;4T!d^ik|dTiW*{mx_2wwg?;_yd$!YE=Paa&iCVLD+uly!n-wkofC|T-41JS^ z$1UC2my0a3xtB*%<15~?r|f1PLn@bUC-YSP0xhS~EytRXk1o=Jj^T{3zU<30+mpFq zs@=zz|12&G{HGH@|8*y$J2pi;H{Is$O?|Km*MEPh`+R#&@ub({Lh16f<+9AxYW}3f z&0b%$uGRL1y3$tDVHUxgV&=>1>l`_)h`Ol5;bIJ(c^#SI{I{3(`eYdT_wC7lhl}Zv zg5VzQ=>qJP(cq6Y*eje1+!)?FU0tXRF4>+GjZw!%w;R-bG{at*99rh~1iTF)0{cTR kzz-Q>|4$`E^yo62B-CH0+JP8c2#%wsqILhh5;Exj0h$HZ)Bpeg diff --git a/docs/CAD/design/mate-connectors/mateconnector-planarcentroid.png b/docs/CAD/design/mate-connectors/mateconnector-planarcentroid.png deleted file mode 100644 index 16ca1c01e6a937095b077246ec74a7a116658b59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26069 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 Tz3ksR00000NkvXXu0mjfGevF( diff --git a/docs/CAD/design/mate-connectors/mateconnector-planarpoints.png b/docs/CAD/design/mate-connectors/mateconnector-planarpoints.png deleted file mode 100644 index d952ddc1f0f424267f5b55caf42aff4d78497638..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27133 zcmX_Hby$<{_ogHz2dIFQbVzqeNssPEa&#)A1nJJfC?&@T=`Jbh9-WFbh?GeG-p_S? zuiqcZaNZF2Sfn)*5mYJv1~H0@<$QoS&IF zJD96@#$zEF@^H7-{ysiaV8U_B;~=av==Se=XMo{Xv+M0lOSkn7|HJtznTLOvXzyah zsL#ItKp8u`xjkGiSXNtf-kfcZUNxYrW%5`?3J#z)U52QmbRCX+KH-SCty=m1`MuFi z438E2-+jV1zwXBe32&=9R?W}m-JWs(|Ao%%)?gAGGfgV^&uG=?EgW+qvcprxa_P@qVb|sK#Qe^JI^jfQ)_>2Kn3&S7w*J}bnHe>7&waeUOL-MV zMpoFLsuhvy9v{QbF~U9}M7v)8XeV*9lr(Dk`N%oQ`;*h~QZgSAV#0KElpwWlPBt>j zv6WGd4IefnXN|+88?I(+E$8WIqpBKgExSdzR8a2QeAUt|+1YY=pEm!VI1A(Mne$-uuH;XZXSC|qzdJeM&r zH^3(&T-n*Z@OQkiY`)pPxn7jI<@=8d>i};>&q|24jtOOawm3X{IZX~v>YGOj7W88n zDt>h)weo~h-W`<`^x^fvvA(Ob8tD9$76k3Y4q_lMTae= zY#$%uQ36vKR78}zO15y`{uACWd0Q>wu+tf-v^@{PIE98nb*J5P|3 zVvP}>HMYV7E-6<)c3AXuz;vW(q4>^BXn(=H-gJV;F=)`@`-E|~5D|jt7_t{Sa*53G zrWcZBu}+yvkkexLidDFoj?w^b#&L#->Tml7+@cJh^>b^y&$5iE4|bseAFospgdDvv zH!-f(iPNf%A9zEI7o4m|$pQ=CyErRGjVD~15*d7LYjr)VfAza6#Kqk;X!H)6dnvQ! z0a;kL(5G`kA&L=dcE_(j?J*2fq?fgSo8g7aWo4&i%1lMKn;=T(y&W#!z_i@FRg zZR3Zh`_WfdhbEsNhwgfFFEO_~>fGGjnDWGrh86>ttL=lgsq=h(@(@n@>2G@*do!?m^9(KwLaa(?#Itth%4z8#ha zbD9$5BKu!)dL4J`?IC%$V0;i1RdjuM3~V&GOlW-H(jwt3>V`o&icxZBM;Z9rO1fx5ckK2w!~{r(L-OLw>7YZq{Oi*E}j|cxuBWx z!}u{Jg91p^7zrpZ&$On!_RrwiQc`f%5safoQDJ{uAJSm?r1VqMhNeGtk)DK zVij<9YV&UOgZkQ~sLLv{a{A$fYjve6BL|O!plhY5^w_%G*#u|zk!|2ZOs@gp)=Lgspv|DY;TUlFq%v5@aknyTDOMSb{L(kVdp zf=9&tc$LgEC}-Esw&=?ddDSrrWu>bX)~lUP##Eka4J}sI97Q z@|S<#+4L>k=4-d2KFfrhmhNi=$(x^*_RsW&-^4bI5+-)j{eixh=)T-$e{Jjw41q1c zCvf^f+*iF)_HQi@2bGbWxpXa)0!N%OSY>E*a%P{x#;Hu1%q@DR#QvAC0rLqvVH;}i z>es*C_g59BISfgi4iB}W&Ksh+CZ?A8t&5u`I=Lk}YS=g77KT_M>_Rf=d8SA%n#i~s z&3D+hft}h#aj_ii6HBHgEj8~3jxj4XEMgsl&M?n;z8gyj|MG+c-~T--l-joTqz3J| ztxxqJHE@}p8^GbP$V)}Ecbe%m0tX8*7+T3^GEGSoAZK@5nDkc{~$#ZP?ka5+z-nNn12c3uDnbLOZ_NXxxDH!;LC$CCFk zG8gU=y>;<0pjn{g{3)TfmfwF-%N}nSEA@r8GMQ7BGEFKx?K4bQ+cYJ(bmnxOc!H<8 zqyfVac+7F@;tpXdaWv9oOT0<}m10OQQnqem(m6;opz3U^`Jm?Nbn*89C9(2WN}{v$ z@-%e5d9@uDDGcVB{JLbwzM7RPjti5$$kpm%RJcTAuJb4m4SeUN<7YAbRdqXMg!%nn z(XKYOf`uyLH9J-L=ZN>+7S#`hWo()yl*FZWBtUz4H6(gvIAwZxMK(o0Q*}7ibQ0$) z+uPnBpQjOI)6qeKd{eI9)2a%Gd$!eBIYe+U2i3x;ggoY*#ZNlu!D%0lGH5ShDQfB$iFafv2>T0XGV z$&*{Uhov~&pef4?`xc^)QKfBZx=_=p^C;i=FKMj^2sce#*9om8pC%$1!PVdvx^lz4 zrw}NrpNJGp$NH3FneU~}E6x}}GbRd`ks+?v>|$DtKOl1=nmo+i_l=H!JcTYipq>{u z;OfG{z!KG=l2W&4jQ9ldPr__V=0?+I>($~?n<%0_SDm+dJFewaM9x2xY3{oH)GCg- z8c72l@7_NCn%vLaKYgxE1770^PsU@am)}FjFyu&3r?VSd&8@2veq5brHuS3w3ih%% zEOH&Apc)eH_kemoUY+cs+*Gv6+73r^bEWAG>IwxYpseaG6JPihlyoI_3?Y zS6^S^I5SoohTPv2YaVvG>?{+KP|E=g<^_C(82HVz#H5=_u4l?=S!-({|9LU9$#D>8 z4l$u)5G7cuAEIbR-Ih;yHDn6_(i3}JW726 zEo4KzHoH^3lJPuj@SbDJ+*KoVlpur$Ws8fcKaYYMBS$5k-8F?wuW;7QXF9+~A#Nu? zhH?AXm9ER*yKM8~;#>UkS^Q$S(ak(}ly0}KX_%bI{64oCyVxaJoClndpXIf zEADW!*MsZwJ-RXko0X}>R?zSwNUo8MU8t%-bW6E&FL0}_{q}y{a-sXfhlk#himTsi z0rxjTwg^_ue6x1H;~#y}|46n-zU|-FJKRPdBQ>V>GFHowmINen?b?Wv<`@e{zEptU_1X6NV_47TAoBX^26S05TJDFd7`n!9cMyhD@u??NnUTU%dz*WSCO?{xa~5)nnQ z3t98RkF`5vxSy$hc-`haXcO}-hS<~$ZWHqr6TElpbU6cJT~cGS==$J!zB7Jx5^y`- z_=yVfp7`@i&#DTJO~4sC8~dHBH(0^+An259vDJl&0GREW98nE8E#xNc{BX+HUGrzN z2XoAmXW8mI^13Z%VUp%xp6f=qK<{h}cD3Q;3=36+i4DFU{8R~7CmRkn#SShcKT&5&Tib6l<5yK%Txitp zwdZwomHdaP)O<8GwX~9h3YSB?S33yh-nA$~3bDZKil|83VHef~+~8lwE6rJN_R*<* z0N(@+>a}yoSSP94eXDV=O_vqStEu4vd-E}kM4?J*frCg;;Uz~FniGdY;k!)+V@An9 zxLZ)xP|rZ$WXSIv5e@YQpJqbwT-p;!!(9V26J|0TxVa+uUUnx)ath}x^@TEhB(t){ zM=ej|ft0fW;$Nm7Ue&Smj3v?YmU0&t>=loEMm zXM==WJfKFzTt*5lb z+@Ffa)c@b;N|SK7dJGD72ATSgoW{hAMO|ENpU#d8;FM}`6{<(QLEUXT`+xnaKR4&T z?z3X(jZF;ZuKxPa_RikPxb=;))I$IG{^hHnndIU0OcJn3ff21qUCG5DzJ*1H^B1;Q z8zklr!3aB!pPUy2uqxkQt)(R;e0&$f!XG@KLE3(WZst(?CcW>SR~0gNzUO1cVO23D ziMqO`bd%}Xj79GV+nXtACLZLNG9y$Qo)aH3>Vi^@pB$&oh0eO#BLE+u{Rs=PlezHq z#JHut;0?#_Z=p+8-QB9`;5#I3rhk&7Il)P?*9MVIdpj1y+hjWm%^V zDmnYj0*-?6ca7YUT_x#|2W0gbHIry(OR&`l}6ibg_KLMZo-dK`uYGZoq ztmQ30a<806$B(XNFVBJ&v$x~K2K&es!NDlZ?rG^ibzSE^Pf*nNk+4FPffY8VgO#B| z%5NU6CHAgK-pXo|E+p`dGZ`Bbd=kPbYSj2zpc(Bo!@}$#&q1I_b>}HrPb!s17|K=; zmXTq$A}(MimypetYt)NttydF8{(686Q+pY=b$)&6-N1;x%>4xjWNO|JnlzguarJks zY_(3sLCaIK=IWU*YP??97V-|y)^4bHUT4yVsf3BH41YOOg4w9}-OgYNE=T257<-)w zz8tU_^~#j@e{P@r*!KRtI!Ge4=?V4bJa<{^Kvl{`q~_3^&>e87)wp8D>}?_UPg4!P zU}&4Es5bw~E&t^z_!XawYS{>*R;wopCP!g}(Y>!nu5FVM^(d$5e$p33#3wuNM7=56 z8RX$Ud2}=KG~?|=LiSDESd@BResImqAZGHOTfQ=qvY&~GrfS!HGM9ouALuW=BrlkW z`oo5ff!ehHp8`D)a_W}LOxpKm>}RSC%Z#`KCN<(p1e9oaW54q&hNh)k`<&ZF@51DlXGSC2}nuE=ra z#%h*$3XhN!i*k6^i|6#e8aN!oDhdQP<&OmQu?r2JoFc|^GxGB%uvVj7=(l|Q*QN{M z!!P{FoT8T$w^6M8CtEtm!XiNf7O(-|`w**gsXnjCji;l=9Td zM4YnliBRn-1F?q2mxX2)Ca=hxWsi}8)uQW5%GkNjN zfH($PpB3%>F@B@m<+SwQG;kd**6oGHI$9UXKJlH=hkqyCj}zfvgs8Jbh1UN0R^R4- zXAEU~t^>-QI| z!=8;*%KtS+(iU{25L|~F(65(>PswI4(JJ2F^5C~i1d5^*rU%Sc|7xpgO2Y|B=EPRa zLWJl__qX65-2WtB>StW&F@x(1Rq>qbs%H+-Othe2<8OM>#y+y-lFW$LlVR*@B&YF5 z8WN_!lY`d%slB;9YIhjKnq?%|j2=DP-cAhcFLnTtk?(D9(SU=HU?MnL@!u$QSdb)# z5c@TCTH57{G11b)9zx1roiK9KvF< z#w0-mDB?Kx3e;$4;mIo=5T6_FyPq=L%t7c2AXi;cN^=S>gw4p-!?A*o?N1PE#!1?VB>1pAcLt~ zP$xZGH1|f`JUiGiig^(O{7a8^vpCeYBvySn{YHKu^40aL1p;-=ns+JM;2Z8ai{zdgXi#129m_8&i z>Y4#`u+un6=6n-6qDPgY%FVHo8qftXi4QifF>&!@pLs1TOdpqzgg&Hi=Napag7-r}`rj%`A zz=E5PC z8)yKkzP$pZ2LH2Pc;2ha%czUe^0wT=olE%g@eaDO0_5NTU7dsG2H%2v=U+ zbPJb)%>x%UJI(e#5?$-qfF=)+fd-QqM85?ihyzEYIz8Q%g;7Ho&EsY(NycrpWm=u< zv;}&pYSs~YyXv}8_6jydq(Ry{oXR|;Hm1sm7PG%oAWdtdQboy9Qu-U2u$C>(uJe|M zR*5uT4o*&hNlCmjJsCaMU+(eJKxo!F+^kt#)N9_Qom%9p0I5?Q`p4Nu=LJ9~SyJaZ z;O6Fx>`HD{(Ez)mSvemusNh>QbxR^Bi8G#}4;kazWd?I9jT&SO}2_@j7&W zkGy>O{`czizTL&)ofr&iPRZ2+Q`W!&Jfe}DsPW0xw5YZ}p3#isKyu?xk?*ulsH5Th z4ls#)ElTAnSHy~q?+#O?fh}keLDYy&YQtmsD1gu``p4vIrLNc@Ddtn~OD1--nSx?x zXV;;C-Rp104r-QPQ-e~T1*C?{Dgd)`KG@;os?|x$Qq>SoF&nN%R!MgZa1Xbj>3}Hu zVb*J*TK(^=Io_0^>i7>yY?rXE_aT9;>mjH3p>L+{9;85d;e=0f^FJFmS4;CTbL%q^ z{WLnDMpv$3HsS|B%6TdUOkLOCwK>bfLJk>YkJsjJlA^~Tx%j+WgD7xxRLzyL-Sdc_+)>KZ$LkC$8? zsY1xX&rfN+pG8GP@)kH*W1=AthbIt#tw-0A*$NYhB%lT_CDw<^G6SWPom&wh3 zb6w!+cn$T!5(v6@BL(J3v_$6V=;RhX=ch|BV1Gry(WT11$D_c|ga?auqfN;cw~Ya- zx^j^QNkWDcG*T)eknn3I&S!z2C5A`J8T-*<&;BaNg*^#|4eG#rr;-HTzvZ}))C4j$ zUKai;W0}Ey^VwG4b`GH!_T=oyesh*D$_4RRG*WI~*VAI&#ff)vTo45E@&HM$5eBOi zq|i`ib00^XN4w(e&&4MLC0RS(a%T>&2gkc}?1I{33(!1{F~$#Z+rFf<0e?o@<;x#7 z5;*JE=ezL52K#$z#DbyQQMReRc|ozxY_btrfp+Y?BjuyJ%42()CwPOE%^Ls+f{M7T z-Y_e*It2uP;%NBcHy`vwl19{gL^LZhzQOAWnrNyBxYmA=k-V^D7W2VoO}6WEBa6@O z*x^nj$6Ytgnd+NQ@`U)`-d!w%d=H=U)jBp-EM?HfV+1oDOilCkJxcwU{BdvzVQ#Ex z<9Q+quSdnW(bn|)oTt0a{%pyKGLj3DtcBUfsOm!f&HYEVV|nzbq%ZBjSlIY>3b@hz zrI^OT4IF0Z`VYx+%;jQ4U{)}VLsDOM+D}N8y2hOV9HUI?w|qhb+TH8nTvuG^1qh37Us#cy;2c6ogzhh;mxu>5=I!G(wD$qH*Q~viW!FU$9al zjlEob&|x)@8@3pTG`Xn3OgO0-i&(03I?N3$i`Qa;wQz-O|@WB zLYgWC(tx0WDiqAAFeA5!d_vV|rFZn_#dO+sxd#M1S!jI{qYO|FJxx-uZ+*oqcYmJz zVm@a8^^yHz&a)_-k51M3skuO%OiBoXvJ@D8Ix8?DC?}o z#jlt-b9Z=rvzvX^%Nu1$8OV%_O%y$!bX?kL5(hWnv05Ps)+9k{7k$t4EabQfkdil) z07_61gvJo*q!D3*>((U7yLo&vIs4@4g?@;BxB3SAh4u2M_)t<_3srf{`yrOa4KYP! z-^AB$bNc$~j=@7WbYLFvMj5rfW&4w=sdtt~q3p3F`nHa?dtq}Gr2#jSf4$C3th)U= zi$rHE#@=68IG$`C{@Og*Zg=r57NrTxa9ODUfCVWK$E@(0T!FXm!#BxDS7uV7`#1H) zvnpSPOXK7Fgk(*12hX?5wQzlOL`!RNtlmi05Dkmla_WV;$QkT~|Kndf^X*Z~GAq+NGaG@q@U2<`C zw34d_%})Xu`f29(ebW&DnVoKCO<}?mW^k_tIL&ujnf7zhauTQz8P0i<&)Uv*{7-kT zKfFFWGuo@nJoVdcMYD&*q$MCmXFj6g8qXu4W-n`Pd-04QhrkrVH)>rE0r)hgmmf~W zug#^h@0QOiP`zj6YxM4WF`BiYcXbT;-!Cs08F_h`=sg$NJtwvY4P+oG!|ki$Za(_-s>YTQ=VKgmFmk{#=A zE3?|;4O0$Wk|maITcJFr49Qv{R9`S+2%5z6JPHrZN}`*&M_YsI2^<5Oyp?Ox60QPM z;*+q=I~isApQ&-*L#+2Cu0sM@eH3ul{T_5ru_9XjrmF*V6vX z!ddJofq{SZP}F#6s2LluD2n_V^(8Qu7~f6^UYDgA8ld6}v-j{j*Q z1(Yhm%2#g5zW>Dqrq6jX>aN9LUMs`5>gS}mE1P#U z5|h@7&hXouk;Fd|32jrDQHC`R-b)(<`9n<^yMPPz*v6mb7l$IEQZl~Ij|Z`_HmzQ~ zk_%YyC?0%jx05_KpVibO$(6Wf4mOX|p9)F{7r5D%`ENDJBa&odA%!b}MKhkJlzn6~ ze`%fti;}cxRUc-E;Q>`B1i8ybVXi9}xSKfj3MH@NK{hcX%7SW8Ak;zAXt zx)(wRL}WuVwfIg2oB|tuxUsSa=-k+$XwZL z#y(Uk>j2Qvn%Hua0hEzl)?xdF z<-%s`(vG4L5%*9q9nM$8!e3YWZJ04sKa^Y?kMhG%lTjJ2NbSY!1TNg~E`8tpr4Yj* zooHvkhk(KKnL?%u#*q3rDKy2BmoGNv-v?KYAVpI|1{YY($XU{2{5!@U?!dwVQ?LMtWPs>O&%}kP zSDa}<_-l)WwALi^EVA=6yeqw|HOdLk(fw#PxQrvEU8?4lN{Vs3d&$Hx$wp%)`OsDJw*4zFp3TJCFQ?Nit=A9~>U9 zhp>N-Hnt7VAH@$ z>9RT$z%q^+v&7_bdLo*I3!_%l)O7lW`F^D?g>P8!QnGYFFQ$%P%B{ZA(;}r5?|1Rx zs$ZrY+@X21iP=w6wPXS*;mc5{mt^J#U6ji zI5Zq99BBgjEhBQ;{n&l`I2s~mztyTvzTfgHCp*BZ7Hb)PXtU+i&C$Z@$!JvfE#`k= z#vZSU1-7gM8X!CW=(=LgLXWSLCX4+LAD<3t5efN)#Ec@pofh@?hbpGVwS|5X~4{f@1=uAS5mg( zOaD>vo0re#b~n`$gZ?fq4r3sQ+Wjj#Y=v#*)+Fcqo=uGx8HX_d^BSLhG5x(r=&&LU zp)iCpkdBqbmfB|`PQgX1G^CH<>?2~2!za}_zTqW}&j#}K9xR82s{cSd^ek|@|KNZl zls7r36Q_~{oSgTU;?LV4I^x^Ua zYB^e=YW8`4b7C;OsJGJf{ZL^Jj;XMkOD?d|`6ET1boG%DzPP|0{uDD=})j}dmZ zCCgfdx6NTD1yMyW2qJ`D?9u;8j#B0lHYza^WYT-mAdfHRDRh&}GuwVMmXyfPUvRBG zKPY-%dw3|7p(F2e@x}NLK87-7rExpUH#@_x^&2|*gEK-ext#Q;FByVP6@R>rGl*}v z!hPzqoVQyS*!XGX=e9|w@>fc>HDKlMi8KR;9iB+Bvm9iZ zR=s#bq$;mmf_9i4k<7m)&dr`xnOD#H z2^OJEKQ&iq9^kyoY|$$bueR9T6AYi76R zrw$t+>qRCo9|~-tl81dVW8lGGj4)!KVvD)F+j_d<%>UXjkd#3`N_DKeCow#Xc2I=u zY*W6%Mo1wynL>;G%rw@Gf?@V60qLTIxMj&n>9u)nWqUL&CC9d7x!aFVX#_^Ofgd`08)N0>B%-E(;F?mymiEZGnMnQy} zEg|vuI#mJ;Z(OrRx)*sp*Y}Em_(a*D3`zVM9sDVtP3S&r7WcNb5O*+7?DbFO!P<$g zxP_|B3PxX&biGX-a@n^!&R7+n!b<%oyz_Q-x?(a($LlYDauebz)Iak|z^$1+U)XI~ z6nfaI?|a-0Svp5N!s`|_zm|BBUxOq#LE!mwvfdc$;qbI27l64a+s6^iQ6hv-^-8`R zAoI-xfYoql*<$$K%S$mC%3v04)g>g}px}Dgkd7KFyB>P~SsJ6J4Le(^le~f|wdu-A zv%5ggHQv04sL;l6Ysa{h439h`VU!y!*=;Ye2Rbon*dKR?c9wk_01_N-OHylQ@^!R! zaV|VLm47urde|!$XjnxYAuTE;CWMoyX{i#y$J+eu<6;2!5o2=y(f&IU#k4?er`%zAq-G@s%D+61Va`Y%HIZwu!a#Y{tCGgUt97 z=Hw-K4NikC2o`YO$Qyi44prq&c9O9!BsOByNu$VJ^an(T{T*~QJrQCfz$aG_oKL+p zZOdU@P?GgaEiPuBpEJnkxZ}{Gp}_y){NhmA$5w!mbF+mtuMA+^n{3(DTIz%pl^dPq zvCI2hOcX%xJX)1OoSkIaqB;3b+ zu*0bYLK!Wr88t}&lR1%|o|EBa)LZ#Mh1)+jSc3(5kHT88c0?N_bB`?qS-)R zkj{=5A?Uv3IHdOpbC|$$EPc8lZ!HSlzf3*>`|I+hu3(~I+R=GYcU&+0 zr@yL<4(mKhDrP0$5BjT7RuPb>_7<>IxHbdYA*pDi2m}LWHCoJa=E~tCl9V4xzmYY_ z*tK95jNK2&yJKWx6(q!Ad!dS;1faW}cNZhd^ic zz$z3`{dl@X_aP-@x2hQkYc2QR{i4QJxj7CLALO?Pt1l#5bht@W8HE+Xxh$I3z4!4y zZ*xBTYTnJr#w*H5Zu3$DTw~&ks4Ql&8V*FEzLTY859KtKx>>9&OYS6cPmc>salJ_3 zmrplyPo|)>&qH1gFNun1T`=Sh>GY!&Vnj&$)rEiTA=DPqn}q~?n3W0kv`SAjZcQjTmd>mx1fj$(#|U^KEK?8|AeUYHfQ?Ej z;=12qYl3Fo=A?Fcx)=JlOhy=JQ!w%m z%g&?aP7HuFYLSSLOqm8*&mth zg_CTM$EDnJawKTwkGB}d>N zycT_0dV9fj zLv?r@{CV7RrI(f+Hka8#>b8ELFKNN!h-2r`R1uZ|;vQ|?&p?dmnOB8`amRq_u`%8h zLZLuWI4Xfryc;sV^wUmc}WE5X+K#;=9EkRfQD&#j?#KV1QD&zxz$jyP&9#BY?9$?F3lzcWm3Hj6<#jes>Z3S zc^TC(_7S~|zw(W?9m&^E5vl`JU2-z+`@o5m1wkPpzMiF^c}`KGZon{yiC5}{#OJhH zrcss(7Z>VW#+$$vaPbsLvcdbIF!?5Ht-&=Q2R|qiPzl68;45pO>AQ3UpCrci`S%Ur zdzKlEV)4D07p?PL{e(joR%w9Q!*G@yY?kIz7!*kBji>kB6U2J5(w>Po;nrx;vmZR& znD?LDsRviZdH3=ROMvcQ>+}(6SB_{BGa7|Osz0DPc14X1*&ak zU>2VcbzH~hLqqWastQn_QFKzzd%MNoh`#msIBj#Tl8;`Gxijs-Yh^ za^T?g7?7n0c&E6JrC6j%P0B+tq6|IsS1JXNGKz{SyOdT&+qTKBe+R3rpR=Zp+z$P7 zvn^LwkMrA&c`}F}fLbKQoi)kDY#=F@K*oH>(~z;s6{K2h{xCByC;(W=&D%7_*n^$n zq9O`0MSTY~rA$N{HtxPQ2p9bu{vlEBbG+60@-L`5ZgvCqPM`*${d&T(DewshE@dT^ zINL4}p)_*u?Io}s+tcdOAyZC(91aJq&A!qF2^9eB#HE5g4{PDghp8p`A{K~qd@2jm zpnG+Er^V&c*`I{5U!whd7??`JL**cX#+xt8&WZgTc0`Rma;=~0Z~xL}0StdVkh)wS zuhXt9*C|xVN{M#y3Gn_$e)0dXogk|~EHq*Fre~^ypz0z?z?$7V(sv3cj^pLb z&(u3Q=RlppXIe!pmbg*innka?4+$!W~kx8It)` z=Ltb!si?3{jHm`(>`mv00A(@vm;KY7$eeR|EPiZOgEywMsl1yy4Vb0|L24-uZ=!5T zwMPv1o>eW^c+DMboIFloIZPT6|Ge07>l@DYf1(&+qk=oA!vn}z{2mYjU}uR-=>oKhdWi00bU3zlG4% zP~o%!5C9PTV}H8y79@a)jvQ{t$3;^E$Amm~!CTj>s!rCsxY=G4#-|$(;z_J-k7rJ_NxFRdo=~ucRady0B{Z41>T7|_GiVJ@}ACOw^=}E|K ziT1(3Y{7jgM>-mFS3FyXK_=a*2jVag3%Iejce4*3&2OLuAog(nkL|gH+EY}ODG|aV z_0o-&4^pSBu>*nvfWkz_)+8TMqWd4~0JVk6CAChIgDTc@Tla@jUL`&JD*{S5A5M5) zSK(41dzm2o*)Mp-N#aPDP_g>qf3->7;;yRjbY^2ePDRAQR-pje3a3yrK5 zlKkhYia+1HmblDU7lpU^QzC)n=Z{g|RNqPHm;Bz3`Ls7*s+Rs6EF;HTT8g5N;ReZ> zwe4J93yI}xw|_b5g<>>B8OV~yLSEf7rbD|h4~8zHUUlHK6LYnaC!y}~x5ifneDc+4QkZLUY?x9lthECbaAT#JWIMc!FEtwu zWJ@;ww3oEFH-6lmUS_m}NOqHgBv^*QiL|*mgG7&v))xfODQy8cM6A=zIJuBX?_wx3FPaXLmy5*W!tgi{Q7qA!>r4grG1z$e%EkF#a?@e>@ zfuJZRtb6XK7qqyusf;)5PjANWFQAME>`(W06bFHm0+}fpU7akKwhxwJ1K+j81DEQa z`uyr=InhO$V9Y_?za*+I%rg%=x`C!#VgaeQHB(HT^8eZm+65 z#Qa6dR8w&@Gyjx|uht8{p=_f{-2#DRQ>HW=o!Pp}0_m;?Vp>V7uzJHv2~X&)^KZCo zj}tt?QJA=2L+&E7GAY|?^ZOqn3AH&fXSR+hpvujWm-f4`*Z9=jb?(Uzd7i1oars*8kB4^t- zR6`&}$!Tc}Dg0Nittgb*fu1I@5kyVnD4%wKtX_^-bB~Y5Pe@}=TYzL*b@6-uVXUfi zFjr1KO{btjp+mlvk{r{fJk3oO$s)s{X@=bs6iws6?vb)dfWy^zjsOtK6%U1TX^-;s zEom!~X1q`C5A*>Q<=#Ux$$q%wKV$5HwJs=BPYD1`q>*4Fo#cok)lpBe9Cg3%NX?CO z!ngXEAwYS8^=zn?P#pzXVZ^GEjS$Wc$UHg2+*c#IorB;0z{-)dT5I9d9MjVRm zc7`ZcsiN02E<@Ai@@c)>@u2UYv$6v~Z?wrfVa+!}mOwzF)_o zkS_e*ASap7n4;!0c?A)JvW9cmldrTE2yniwT>bkh36C~ANlPIqp$cW7#KfK+s=os$ zi$JlkCxriVcn!ZyqQ#A3LiUtecUd#}G?z9eqRc2lT2;G^`PLTh>ek`#WoSr2_{Ee# z5zr9qxsfo#p^t~0S(H|>*~#zrHx;v&Qvu!5j-TUo&+B)ij++*CAwKv^R(Fm*-3FOZ zsO#ZiDBHJ~d^(5uq}h|HB6kruWyi==n4wKXKBF+-->IgAfI3z@TYh^~KmH-{+iqrT zn8vqHPbiE^l*@oa#Wt5TIP0hH)~*xaua{W2d{{4!=C@lTnAwDCt3?p`X<}-n`LsPc zroj5gWX*m;_2<(eo^?P7buFJ^e`Bl5KkEY>_KQG8PNZZ%oK%*=^S4N3j60c;W6 zuZ0GJa=3CfAuvUwc`yZ%_rpJ)*l1h_#(JJ3UC*1XC^6Ft{y88#dmQKosYB}c@j~a5 zbSG`JKu|r14Q;C;u;h0w464~McvJoB`)@ms{ZO11? zSQys?UQy{6<$-AJBKNhnfpB}vMZ^!3&n4t|F}~7;y}7vTY7QOjT2v~W`;nQ`8ww}| z;WE6R@~uR)nscV&V)wLl8yH!@<~psrK1&zHdnKWAMaF{lR&Ov^$-d5qR_3D<>DY z)rFQ{;qKtRk{dFEgx4ZA5!ScJ&cJMEnehj*^k@QlF&KFJG(w0h(N||aY`tw+0+USd zBe!%;6#ZU5csdgqc|#yHD^z~jVbB16gAG?q}X zi?rPG#Ky-1wtR9_1RTWkwD1V5pqKUsHGk-EM=|F<2S|#Gi%OY9*^X2)9njOyW!IiX z5L5l1zvF+T@IN`f79igcov(_G4{k_OO#YT$w`?*}M^S*=8l0VQ90Ll<&Hg4$hJfwv zR-c$JYo)Oo#{R;0BP(2Z!d2^u0h5jf@QD;csf%}MdF%PbjpyD@sDroL!*tu98S*PM zYjV(19y%Jv%O+bpFB~k2DWboo`15*8>psb$T!Eg;TKq~}`ws;-S9DJVG%h%_Qy(p`c`4Lx+1bcb|zN(l_z-Q5k+-67o_(%*gmfbYjyv)0@@ z_nh;@-uu~??^Vyq)-xz=5lsNkpICka8mO9L2bjg7Nx(KxT4V=R{nPpW1Pf0#(+)Y=b|Y z0|bjoMMa-rGIBgC-<~l9y#?1hmXdoclw|$iYQSgklO-XOiFS(_aaHv7ZX$GFZD7=jV)JYg*Al8Z~qrY;qLnZxS07lt@F; zQc5?)0HILEAemiuVod7knyB3PbtReS9bVaV`0R`_n0RgTLy0cBfB;>)9lK`aKa6bB zc~fG8toq749{m9RLR4wTI{DD0#h#M#@&VoL^`A!+5ZU=N%jfEHI#r6C=AT;ThlJ_} z2Y{#_B0ECOOA8x8hFH1SD9A-?X!bHNWT$r~SjkB8=-{{VS>Wgv=R(1MX{h_qTzdWU z1|PP`@h(%jzg)%bQTHYR^*y>qyZ!8Rfxy4}nsSYKTk{8=V|f)l4X9u)6?@hI_UtkM z-z~3oqSiCOMifEssE18$iqc6oaEVLG%gO{b_*VK!+15sN)fc3juUkzuF+JQ;k#1o=-t40^ z41Q@Z9=tB)FN)B}8pz9US1?6^K-b=PRR$nKT)!ZGn>4gf4gv{PfLZEKQzzHX^D<14QJQW zZJ#mc5JiY7yJ?lbEsj*tuTGsQg486qSKB=a)%+0qh#KnKy;hYKe=*~+DQ#<+R$$J6 zVWqA7$pqY8f;Gy~DRYK`fmHr72}<&<9T43lR-W%hxu?qp5H+|B&uWb0IIl}AKnZCS zn*t^=<&fMBs(CKgnAQO@eU8IU$;gIxbE79espch^RLvlhK=m@5K4-e}OAOCu+PG zs6)I^Byg!pI=j>AB0T-wh%$Z)C&3*FS=HdeQA@oehM_<t|Xnnu19BuH27~XO_CCL;Wfb0welCrqIXB!lP-Gal2BiuGoZ!rQSh52$BE&%Zh zgkdT=ASIJ=H3m=$w=@Dfxv68d`E%JP*v1`l%!ZKvFkng0;YAz*Z@N`~^7_wwA%chB zM(B=zv_W12H(~>)A^%gls^?r5FIhYACyT zAvlJ$%~|aa@{^VcyuAz@b@)eW-1`6pc=(SJ+Q(ZXV|aF)o|y6{K+n4_x(n437;$A{ zvIZ55IXYg|*rf^z_1#{ewZA7wg_5z)!3JqJi!6NaL z9G|Sb%d*Lh-|Ob11ulFx{20vP{L27I056;rpvu#*1=*evAD8re%i4PWn=$Ft1+LeN zdb~Xpx3(s@MJ>Da^WP=^)3MgmYHO>OzzV0N#HsZ2FredbrxU+PAV{5>-mhr?4<5n14-hO>jnB3o~U&a@5uCm{YhGA8q36s4q-`+8nB!RDht|O!LWc zI*;{Gh4Gl??~EkSZ)>D+bCMr^Z?kpp7o(@YW+D86f_h&CIy21T-IlD(P%)x~8tX~s zYvWTnI^DH;9N>VQpBDsCsVjB8pE(#9j+g(gi$uX^!-^P@W{LKi_+8djq<1O928fwc z&JCY0+cXow31@4+VU((ulbVJ+<;|t1{gP#1lRZ45Vl|Q+ZEH_aOmR+vok1Hk7y53e z!IVPQl0ikhgii^`avex$dB0E_U#v31KnuVQjr`W*{c_@fqRNaf7es?41-&hAFG*ag z(6sKL-Vqv8k$G;HQq)jL)~yvcRi1Y7ZL(Q#6km;q6D!9$HfLy8d&$!v#HN@_7UxV9 z4{6}R7GkJ+Dx|_Y7!ZO_!{QmG7o&T>9!cVUe`!p$^w8jh02%b58C0k_PLs?g6Y(X| z(l^@S3g@!v;x9-W@?Io(eT9|VueLC9@iDJ|C<~Mcv{9?d2?j~82FF2}gFtu*b42Aj zDzNQpkq^c@bBvcU7m);=BwA8vv%SE$8{fY2zGWgu-}%4?E8^Ab=JtEmJqfjjzyD>k z?d|xBxO_N;#(4=eJC0TT_V8diKfQl1qq?x7T8qMpkbL^Td#920Gc;DoPi(0bQ(&IABph$vK5bMJ6h(CncUBg9W;XMfjXIF)bKdab%IO46?)3NK<+ zmH$;Orq=nSe+@QggPz?KzzRuEzyNkb)=U&VT$FYfH+aw!J$!pO+Zv~T=mA2 zz4@wJ#70FP(SW0hPMP#Xa|0Kk1Rlrc53hmPs-JC6rWU<{=zKIMiy~2=U`Wl~QgdI0 zOG)p8#_JnZF8HhSpxZ}XqUL4GD8a?h*u%@NNu$6o7VM@MORV!%7AT)^#ryU@TGU|T zj{TxXB0p*ST7gg(40LY2#WpI1RJEdyy^>@cNaBBBtNc-EuKrmQs8BSy* z<+Nv?=-wPC&P)NK!TS=+pDz$=!R(v3JJY9uWT{|JjtAl!q)@YD#{QC zfWlE27vQj{Ichcr%5 zbN<}Et8MN!p2D5yv4&|{ZDo+VI%;aJFnUZIIq~xu+>3dkB=1MVJZZDhyTztJ+9Fy5 z)GLPbn62Ux?&S3TK2$WATTXL3>y6C8ckZttY7*!$Tbei#OoKk&g8_&#%6zX=j4j7y zjHNX>dluJntjq2Q0aXNY)C~hfPgb@<2tf0`+mDwxVk1~2uH`G73>LP1x4mz=N};iPZz38^lvrSKtmyb1kNhv%_e84;a@i;+$-0+t==Hrsj?kDJ5p|_%g^sv{gj|N;^jue- zv>5rM*8Qj>OsGfSdLbnl+$==QuzOOW=IN>mH8;1E@gRDNrjH9o2basB{s{ny#3Ml* zy)kY5h{U&Y@jhg;qZ<>-;d__J_Y@8}R)nG%hysZ$D;h(AcrGhXp4NJhPSoq2h&ftF zOr!ff;4YUDn*PSP%@?$AG+%LL?;sIDWNbJ;$ygMHq&4cCessFirXMJ){I9aQF*NBT zQN2q4-uBMNY{T~o<_GewM2*ATb?6z}QeGy|-KrFyKgtC+ejn)khG+&@G3{#hsk`)u zt7mlRmBkWq0Oj^oQaJ_&s^`lS0f-mjj~gnr5fTy-h*~zCb9k(8I3rMMFEB_2a_R-B z7HNMe@Nq?QYj6Fm^e|RYheEA=#h5bj)!2XMQiXQhGOFgCP>EGKey(Us50oTEiHMkBTrV{ za6g5(xV1+{hS&F04q0T^aKL+Z4eu9lVtt!k(d+F_ZGq~sT%vIjULe}y?=&)s4}n4a`zz*Y-M~4uI0%7_A znYz+qzMN6F-J?660Ajtyz$pk3#>LTS5KP@OeM@@xb@SCO(_pqMEjg9TMn)=3&~cya zX@9;#_x0aa^gYs|TN`}HN4-@cKuV!N1;bLnLq#hVA0YU6bZImDwD+qLcbC3_JO1E@ zNpG0bcJJ)q6lSsiL!xQi4}XdsCDRdJ*O+Eug!zZ})RichBn;}!j@ON-80G2O%|pus zJWY>F+(0Qg{+0B0buHqo-g=kVTz>tv?Sv5qMB&j9DG6dB=fPXkH;_u>Er4jliPxY} z3E11-dsRD8`E@)}I$>z%*V~P%BCI*}8Y;TS{;D36@`T5rTVtaA>hYg;h1m|i1)$%^ z6j=;rK1f_q|7?+tDH7IMvrbQUPJ$%1*_e1SsZFQeeN0oNFid~T`EJpRDZ2w@`4)s5 z7$*uYidM8K8{^esm&(Qp&UD%T`#Xr3;R?%!zK?G(`u0M9pgW;zVsXTqeS_UIadoc0 zY^08$63|!rwz=Oi@$lR)W9OLgH9ERS!He9Dul2d#r*sa;jaVDQ_*W`FOK2#^6N5-m zp4)t{S-uy5aJ403(v|Fsr`pIS*}fZTAp0VIi45re=xmEk#}VkRIU3bxZmm-4Fm(s~ z$8xm}ujg>ztKWL=b9danb+xr(5)#pAEM|!U<9%0KHj?V#@zsIJQTMK2U`?sva@~%} z6&d3~_d8On8Aj$v60f2$Yt;m22OVR_QV|Mtab}Hf)_0p;c^FibSPJYAG?f5BDJ9Kx z`}%FdCOTH-Y>uAL2+LL^eUb@%YNVSXHWxrnDg32p&TB77ECl z$MyiD*rA$md|YZnNz!Yvldt4ZDlluRuA_6Xy?eBKK&2-1?D(SVRa{$C@rSvjaFlt8 z;#a`mJJCDc?9#hCz2zX#&ivczOX;Y2q&)GTf!{-B#DWBQ#k$b>8VjfSSt!Os$9jtO zWMfkBO4asuPL^k`Q&0naEuWF7jrd%SVcceyBRmiW5<%?{8h2AnO%zoWd^x#}pAhA_ z%e1Xcp*3gyY*(4D#~I_fuhVPI|5lg76cytPwsMp|q6*K-QBRj$-(dlPhgR9+Yka+P z_=VPmDT|*$qDYL04acb+Qep?2sOeYJ$|WLImIOfYU9RKR_k8vrq4NBWy3%U-qm}7$ zB#}UR%IT5s5Na=YoOs(mt-iHM=@`FU^KU%O)1}zo{pY?@lp_^Qsmm>#O%)9u#2?-s zgT?+YB$0741XX5APeaWrd6KO+e*5)5zSa7~pdj($mHXTdWkb;Fs@+Vkdfp~m-!Em9n80?bO_6eptoBA0=o*<&^LUH z7S3cP99^N~G8sf}Zslt)2M1;&fu#TDNbTS)%*+-WY%)Z%Z0((sS&Wz13HkO5x;BE5 zgd5(md}Y?~gXrz2B>T-8-W9wqXW@)LYi?E7aEbTz9YvV`6#6-`2BcKwWlKbs3XUHb z)^ZPmHbH9S`t4u*vsZB5U&g$5)5>7zd(;M-Tk!JS)NjjYMOUo1J>Dgk2%eW^YzCAT z6&-8_zAG|22v1@lV6)zI*`k_~A2t3m0m?3ze>T0?9nILwMkCQB;k&(r?w!%YAJKDu zF4+4JA@y)&JF+C?oOu^DV&})mLr9(&v<=3~DBlKKUS|Vzf!zpv5ALFs*Mk1N0`_et zZ{ejx2zQgPG0gZ3mYh7Kbu6O){E^zy`{`of+ecSS8&2b(4p`Z5uk3D9)34S# z`dM?C$S}a7^dU98%@=P2QPTm2`^VN$W+r{9@kk|u8FPKG(?`reIJhpE)Lk!lhdeM$ zP-A}>_9lzTn&j?u&9Ho5uj)-Gs5&t*5dj55?EF5CN|sp2?9fruT_TAjS+k*dam+!R z-{a}oHDzNmnDh0!x+GTyvvSXrK@EgM*Dm%^QA6j(p-f5YTJ$JG zD912msrsoD(3<4^AU)fdipE;yR5Y%1xmc(*138eR#G>oLDh1rGY1rr;?!&-rmL{?= z6T_oU2m7njKnU5xW)!1Md0c8Vol&=M{HulO(Ml&stwfs;lXEWLbRzfeZ( z4FsUNL&KLebA*1~mB~Xq*i%AG%)`06x&tyPs;P!Uoq0bgCtHf137cyW6CIo$+&ucf zycmx~S9bY;s!=e#*4x}&Y`+(Niso`#>7`}c6CNnV-<=cL*N^%9PJ5m)Et%%UTD5;+ z8r`BH>9T|_EPD%7>qh%rXb`>nwx7G`A6+C_OXaRipse}I6uRi9)ev{7o$V(f?EMi8 zbYjLG|T^GIQfilW^29Sv-g$K!SA#i zg1|m;JHjnJ%dZuhXLDx4pQU88ySrD0(QkQWg_r4RhkW|~Mun?+X_3C0H&rp@iR}y( zOZP1#qJlQL-uF>k8Zk{})`*0I>K%lhjo3e?rD?yu?5F}Y&2H1HO0jAYP zNtk~u{d^RdqV9gkHdQTEeTk;>u?qKBf*Hp7^yIIho-F`!QU3NC!^NXjj1hQVhc^*% zPJYnP3pZ5FXiq_oxh6r8n)7Fdp4O>^GvM)IJdnOyD^i-&j%M!43A92O8CVG#|rR48z5p95-+s*S3sc1;F3LCOW|R z5Dl_02IB#Jz}?Jet}2>r=u%iZ8+SS|PB5E%8wqoOci^8A?|s zQ-LD8lMY1{xbk`NS=#EVX1v@`)S52d|1%T*)YF%~)^0im zGb!)Yt0fNxsYy#;0YJTjgM;VO6E!5A(|T%VO{;Eu|C^_i`-%O)VBOX`$DU|h8UBUL z654ohQal_E-J$1^O!Xn9hCF)8_U>Lr;rU?Xm64FWbni9F49N%R^5!2ST5I@jNHcsw z$}S_6HevL7dcuDeuytGjn(PM8N9Wk@>}G#v^gj*AG}N7?#e%QlCb{s_1X}Np@~I@L zZR8%F3N)fmz$viq>fentRqRA>V>N#UmVre^_tkplnm3=-btMyUcEw+~Q$l~g_^^=A zxn|aX&y8Do6FaJ4?1rMK6eG;}b15PmpNZx5+J7d99kKxU{iUQP9i0ieJOvB^;9~05 z_BUXAs+Uhx{sCZ{ezWp|=z%}toB*_7pFMIK%xh((KDB%NRQQ4T`DbI4j&{&5HEWTf zMYQEo#MgST8%;`;ZLbSiVbD_bm`~WCxhqPjWnkdp1YfL_e|VC&nk)C!xPU9*-#rRA zjz>wauIEA2kgknLek&rMwI*^0bz*YA?4M>NKMHtDQ+p zBlSS%^Wh5fzw3rtF^-M`w(9aU@#~&khgZxUelmSEAus7zl=L5d&bL<=PQ&u= zU-J%YuJUwG%b?Ktvp|wevHk)f%;3hbm_MT8n=x;_gst`ax)`r;(611Nz;30P4Y=Jt zrTy>-KwdnS;HV-!+d|v*o7p~Xusx{GV=*y)2}P*@?079~)?15;%hTBgtT%IM;|j+8 zjApgZyshPcCP+a0i_6sU{kvTrj{fHZwKdg8^SWS{yuIDrKC0e9+?e=(NP$hjKJ~iT zoQ<@=mphrGovGG~O$pP3#y3za`ywG*M`6SUe^w4C`I}PNQ~b@Hj4v{k#cU!vCB}~1 zvs0(dqsit>=R1_M-hoOs`Ag)(nx+lEQJ~S8l;<$JIF>OUePPs)LIpln!Uvx?4dNL0$@3{I0F?jqz@aO%5jv4s9^Olm1IH5-0qD{HZBJC zhDBst9(2M5DO(|W~87&6=cEi|L>~E|S%sk!n$n^eQw4PTVxgHUO)X*j+g-NS1 z=GU*ytvw;j?ZUHuh@5749_SjeSzbzlf(nR2S2yw8&5xI?2ghfa6Vs9AJ(y(;PGg^#PY=11o z0jB@Dc5ea0_`8L+78ez%9hXlXz^X4fYN7zhHXiEgXLIMtRZY_nujepH`3l{Rv5`Ly zhs%F~iQvnfW?R~eH416uP6Y8E!MNsrgw=;NForC)-+Wo)2OG!r7V}Kk(iqr)Q#+&u zh*8$or&D>u{ZSd*W+|gY)T-$cTooFAh=IFD=n7wUb`lb1XQd*?mTkHKG_?J}b2zQB&@Xmh(UU#{;5 zynq(R(q#pjepsA|31Q19UeJn)WjQpvJpk(0zgW)wa_KKS^XqP-nw0V%_4ob!Q*nBE zX@@DKVYVFh8K0HK>a(A-C~hoK=7S#Gc6E&gfro>+1h^z|lDN)PhLl?R? ziZvVIvt{HJ)|udZr)P(~4*Vktd9}+D@5gm??b&{zrf>Z3cZ>j)n z*JV0CUcJ7i1K1t`MQnTLD~0Y|t6g9$q?}s)Pv6i9d3ec04Zh*R!#QJ-FGPp)#(ak4 zXMfMrKh8Dd;Fuj*=e&;SDe72AW-IvGBbo-&rh=q>F-&Z0s~ITxDt<@){4I*$b@V(I zV0h&la=#<9;HZV*fk@{1%-;pq0tb9lw|;JRH0|C!C>OY)Pk@E)Niv&hHEYf8SgMdy z(C9v@(y{C545%yPPr|y3A+-me{q;&-37Id;Ld^HfuM;Thq7u=e9gu4Yyt}d&qjfWGx*(`fQ zCby0SZxOnDHb)ktUjJRDs^5-H92sKfv5>3%CQ=q7q414^$kKBKFss%6yFFPRvEf&a z>@n+9bGUlU7)wV-MSp%+)@Q+jrzC0-Y`_rcS9fmhcse<|9V7Y68it5WT_Rdp74s?Z zaF9KQlZ}=2x31$`!rHcNgd6@~9>w(GglHgVX=7s@9_+}rw-^k544xLQ3&tenELS3l>R~rLk>W@WjbAd|7txFFl6!d6OvT8F^G~A zs-CShor2Zg(lFlS;D1ZVrp<~`AYvdZD+@q6+Y;Z#s?*p^p0)`9Hg84ne48R3-nDbO zp{FCY!P04&kv0I7J%~d|c|Ki{>v($^9TjHa6-sVH1o8g^R}6Tt5=1IkpOWZ$@)=w@ z5*A>3ku9v6uH>5Odfc33tGw}ON*6`i61)702A1StIZXi|RrVHeN zbhmT7c?mQ@F8gDXX92!4vyke`>r~d&UEtYzzErEg=N%x-5^qt?zEx4+j4G$g`*j}+ zv*Sj0YjHd&kk<1Aykjc=u01#+#f##zjsz0;tr|}2%-uKIUOgb+fO;OFC11rgC zyR#)^yF~lEUv_Hp^XEt8HY0k>LKb$UlcuJocbV0=-JW_wl}r|Rxe-K5W5GV`PWdP6 zg0I0X>+s(NaTR<(RA?uu{5~+C{F7la^@2041#W`epG42e$w^**;yvN=U*Lq)IBg`$ zsdl~qIGOk3W6~`Xi9SxkqO1-phh*JRkC-z?D-apK6zoQDViaWkXs}t8l7u98*iOzI zaBdZhkaZXz9&)%uEWM%!&Bd-dSKa#S=?y|K4Sd8^(P!xCWSo10K`oAt82K{jW#B zG2E;LEUd3*El%gkfcJK69{1cn+#CTrY@ojv5G-u~vIF3)M(==cN^9m;R-Sq{*{oo( u{eOE2Ojm)t0JEF3`oAA!0jvF21WD6|&J;vY4DiY(I7xAcSh=X4-~Rv{2Q>Tu diff --git a/docs/CAD/design/mate-connectors/matepointiconLG.png b/docs/CAD/design/mate-connectors/matepointiconLG.png deleted file mode 100644 index 2f3022d116119c9e5db54cef99de0a292b3695ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2368 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 RkmrC%Bw|`Xwh2`Er=))y|Y9OqD2d< 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 E0*GbF-2eap diff --git a/docs/CAD/design/mate-connectors/relief_sheet.py b/docs/CAD/design/mate-connectors/relief_sheet.py deleted file mode 100644 index 4b8bc6aa74..0000000000 --- a/docs/CAD/design/mate-connectors/relief_sheet.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Flat glyph vs 3D relief, at the elevations that killed the disc — wi3z. - -The flat study collapsed at 16 deg because anything drawn IN the connector's plane foreshortens by -sin(elevation). This renders the SAME bear as its real relief (1508 facets off the supplied male) -with a simple lambert shade, so the silhouette does the work at a grazing angle. Two rows, same -sizes, same elevations, so the comparison is direct. -""" -import json, math, os -from PIL import Image, ImageDraw - -HERE = os.path.dirname(os.path.abspath(__file__)) -M = json.load(open(os.path.join(HERE, "bear_mesh.json"))) -V, F = M["v"], M["f"] - -# Part frame: face carried by X (right) and Z (down-negative), relief along +Y. -P = [(v[0], -v[2], v[1]) for v in V] # -> (x right, y up, z out of the face) -xs=[p[0] for p in P]; ys=[p[1] for p in P]; zs=[p[2] for p in P] -CX,CY,CZ = (min(xs)+max(xs))/2, (min(ys)+max(ys))/2, (min(zs)+max(zs))/2 -SPAN = max(max(xs)-min(xs), max(ys)-min(ys)) -P = [((x-CX)/SPAN, (y-CY)/SPAN, (z-CZ)/SPAN) for x,y,z in P] - -def shade(px, elev_deg, supersample=8): - """Camera orbits down from straight-on (90) to grazing (small). Rotate about the screen x-axis.""" - S = px*supersample - a = math.radians(elev_deg) - ca, sa = math.cos(a), math.sin(a) - # view: rotate the model so the face normal tips away from the camera - def xf(p): - x,y,z = p - return (x, y*sa + z*ca, -y*ca + z*sa) # third component = depth toward camera - Q = [xf(p) for p in P] - img = Image.new("L", (S,S), 0) - d = ImageDraw.Draw(img) - order = [] - for tri in F: - a3 = [Q[i] for i in tri] - order.append((sum(v[2] for v in a3)/3.0, tri, a3)) - order.sort(key=lambda t: t[0]) # painter: far first - light = (-0.35, 0.55, 0.76) - for _, tri, a3 in order: - (x0,y0,z0),(x1,y1,z1),(x2,y2,z2) = a3 - ux,uy,uz = x1-x0, y1-y0, z1-z0 - vx,vy,vz = x2-x0, y2-y0, z2-z0 - nx,ny,nz = uy*vz-uz*vy, uz*vx-ux*vz, ux*vy-uy*vx - n = math.sqrt(nx*nx+ny*ny+nz*nz) or 1.0 - nx,ny,nz = nx/n, ny/n, nz/n - if nz < 0: nx,ny,nz = -nx,-ny,-nz # face the camera - lam = max(0.0, nx*light[0] + ny*light[1] + nz*light[2]) - val = int(70 + 185*lam) - pts = [(S/2 + x*S*0.92, S/2 - y*S*0.92) for x,y,_ in a3] - d.polygon(pts, fill=val) - return img.resize((px,px), Image.LANCZOS) - -# flat outline, for the side-by-side -D = json.load(open(os.path.join(HERE, "bear_outline.json"))) -def unit(pts): - p=[(x,-z) for x,z in pts] - return [((x-CX)/SPAN,(y-CY)/SPAN) for x,y in p] -OUT = unit(D["outer"]) -HOLES = [unit(h["pts"]) for h in D["holes"]] - -def flat(px, elev_deg, supersample=8): - S=px*supersample - img=Image.new("L",(S,S),0); d=ImageDraw.Draw(img) - k=math.sin(math.radians(elev_deg)) - m=lambda p:(S/2+p[0]*S*0.92, S/2-p[1]*S*0.92*k) - d.polygon([m(p) for p in OUT], fill=255) - for h in HOLES: d.polygon([m(p) for p in h], fill=0) - return img.resize((px,px), Image.LANCZOS) - -SIZES=[22,32,48]; ELEVS=[(90,"flat on"),(47,"47"),(16,"16"),(6,"6")] -pad,cell=8,58 -W=pad+len(SIZES)*len(ELEVS)*cell+pad; H=pad+2*cell+pad -sheet=Image.new("RGB",(W,H),(24,27,32)) -for r,fn in enumerate((flat, shade)): - for ci,(elev,_) in enumerate(ELEVS): - for si,px in enumerate(SIZES): - g=fn(px,elev) - tile=Image.new("RGB",(px,px),(24,27,32)) - if fn is flat: - tile.paste(Image.new("RGB",(px,px),(237,168,23)),(0,0),g) - else: - gg=g.convert("L") - tile=Image.merge("RGB",(gg.point(lambda v:min(255,int(v*1.00))), - gg.point(lambda v:int(v*0.71)), - gg.point(lambda v:int(v*0.16)))) - x=pad+(ci*len(SIZES)+si)*cell+(cell-px)//2 - y=pad+r*cell+(cell-px)//2 - sheet.paste(tile,(x,y)) -sheet.resize((W*2,H*2), Image.NEAREST).save(os.path.join(HERE,"relief-sheet.png")) - -# how much ink survives — the same measure used on the disc glyph -print(f"{'elev':>6} {'flat px@32':>11} {'relief px@32':>13}") -for elev,_ in ELEVS: - f32=flat(32,elev); s32=shade(32,elev) - fi=sum(1 for v in f32.getdata() if v>40) - si=sum(1 for v in s32.getdata() if v>40) - print(f"{elev:>6} {fi:>11} {si:>13}") -print("WROTE relief-sheet.png") diff --git a/docs/CAD/design/mate-connectors/relief_test.py b/docs/CAD/design/mate-connectors/relief_test.py deleted file mode 100644 index 3fb83b2a8e..0000000000 --- a/docs/CAD/design/mate-connectors/relief_test.py +++ /dev/null @@ -1,9 +0,0 @@ -# Export the real male's relief as a triangle mesh, so the grazing test uses the actual geometry. -import os, json -import Part -HERE = os.path.dirname(os.path.abspath(__file__)) -s = Part.Shape(); s.read(os.path.join(HERE, "bear.step")) -verts, facets = s.Solids[0].tessellate(0.25) -V = [[round(p.x,4), round(p.y,4), round(p.z,4)] for p in verts] -json.dump({"v": V, "f": facets}, open(os.path.join(HERE, "bear_mesh.json"), "w")) -print(f"verts {len(V)} facets {len(facets)}") diff --git a/docs/CAD/design/mate-connectors/render_key.py b/docs/CAD/design/mate-connectors/render_key.py deleted file mode 100644 index eb4bcdbd99..0000000000 --- a/docs/CAD/design/mate-connectors/render_key.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 -"""Flat-shade the faceted ridge key from several camera directions. - -The point is not a pretty picture. It is one question: does a low-poly solid, flat-shaded, -let a human read its orientation from an arbitrary viewpoint -- and specifically, is the -view ALONG the ridge ambiguous between front and back, as the geometry suggests it must be -in silhouette? - -Flat shading (one normal per facet, no smoothing) is deliberate: it is what the concept -claims to rely on, and it is what a CAD viewport with hard normals actually produces. -""" -import numpy as np -from PIL import Image, ImageDraw - -# ---- the key, same numbers as faceted_ridge_key.scad -L, W, tf, H, pr, pf, hf = 12.0, 4.0, 0.45, 4.5, 0.22, 0.62, 0.35 -Wf, xr0, xr1, Hf = W * tf, -L / 2 + L * pr, -L / 2 + L * pf, H * hf - -V = np.array([(-L/2, -W, 0), (-L/2, W, 0), (L/2, Wf, 0), (L/2, -Wf, 0), - (xr0, 0, H), (xr1, 0, Hf)], dtype=float) -F = [[0, 1, 2, 3], [0, 4, 1], [0, 3, 5], [0, 5, 4], [1, 4, 5], [1, 5, 2], [3, 2, 5]] - -LIGHT = np.array([0.35, -0.5, 0.78]) # a headlight-ish key light -LIGHT /= np.linalg.norm(LIGHT) - - -def look_at(eye, target, up=(0, 0, 1)): - f = np.array(target, float) - np.array(eye, float) - f /= np.linalg.norm(f) - up = np.array(up, float) - if abs(np.dot(f, up)) > 0.999: - up = np.array([0, 1, 0], float) - r = np.cross(f, up); r /= np.linalg.norm(r) - u = np.cross(r, f) - return r, u, f - - -def render(eye, target, path, size=(620, 460), scale=26.0, label=""): - r, u, f = look_at(eye, target) - eye = np.array(eye, float) - cam = np.stack([r, u, f]) # world -> camera rows - P = (V - eye) @ cam.T # orthographic: x,y screen, z depth - - w, h = size - img = Image.new("RGB", size, (238, 240, 243)) - d = ImageDraw.Draw(img) - - def to_px(p): - return (w / 2 + p[0] * scale, h / 2 - p[1] * scale) - - faces = [] - for face in F: - pts = V[face] - n = np.cross(pts[1] - pts[0], pts[2] - pts[0]) - n /= np.linalg.norm(n) - centre = pts.mean(axis=0) - if np.dot(n, centre - eye) > 0: # back-face cull - continue - depth = P[face][:, 2].mean() - lam = max(0.0, float(np.dot(n, LIGHT))) - shade = 0.22 + 0.78 * lam # flat: ONE value for the whole facet - col = tuple(int(255 * shade * c) for c in (0.86, 0.72, 0.35)) - faces.append((depth, [to_px(P[i]) for i in face], col)) - - for _, poly, col in sorted(faces, key=lambda t: -t[0]): # painter's algorithm - d.polygon(poly, fill=col) - - if label: - d.rectangle([8, 8, 8 + 9 * len(label), 30], fill=(255, 255, 255)) - d.text((14, 14), label, fill=(20, 20, 20)) - img.save(path) - return path - - -if __name__ == "__main__": - t = (0, 0, H * 0.35) - views = [ - ((26, -22, 20), "iso: the reference view"), - ((30, 0, 6), "ALONG +X (from the FRONT, low end)"), - ((-30, 0, 6), "ALONG -X (from the BACK, tall end)"), - ((0, 0, 34), "ALONG +Z (straight down the mating axis)"), - ((2, -32, 5), "ALONG -Y (broadside, grazing)"), - ] - for i, (eye, lab) in enumerate(views): - print(render(eye, t, f"rk-{i}.png", label=lab)) diff --git a/docs/CAD/design/mate-connectors/render_stl.py b/docs/CAD/design/mate-connectors/render_stl.py deleted file mode 100644 index 16ad0e92a9..0000000000 --- a/docs/CAD/design/mate-connectors/render_stl.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -"""Flat-shade an ASCII/binary STL from several directions. - -Used to answer one question with a picture instead of an argument: does a RECESSED faceted -pocket read as an oriented feature, or does a concave feature collapse into a dark hole? -""" -import struct -import sys -import numpy as np -from PIL import Image, ImageDraw - -LIGHT = np.array([0.35, -0.5, 0.78]); LIGHT /= np.linalg.norm(LIGHT) - - -def load_stl(path): - data = open(path, "rb").read() - if data[:5] == b"solid" and b"facet" in data[:2000]: - tris, cur = [], [] - for line in data.decode("ascii", "ignore").splitlines(): - s = line.split() - if s and s[0] == "vertex": - cur.append([float(x) for x in s[1:4]]) - if len(cur) == 3: - tris.append(cur); cur = [] - return np.array(tris, dtype=float) - n = struct.unpack(" 0.999: up = np.array([0, 1.0, 0]) - r = np.cross(f, up); r /= np.linalg.norm(r) - u = np.cross(r, f) - cam = np.stack([r, u, f]) - - w, h = size - img = Image.new("RGB", size, (238, 240, 243)); d = ImageDraw.Draw(img) - faces = [] - for t in tris: - n = np.cross(t[1] - t[0], t[2] - t[0]) - ln = np.linalg.norm(n) - if ln < 1e-12: continue - n /= ln - c = t.mean(axis=0) - if np.dot(n, c - eye) > 0: continue # cull back faces - P = (t - eye) @ cam.T - lam = max(0.0, float(np.dot(n, LIGHT))) - shade = 0.20 + 0.80 * lam - col = tuple(int(255 * shade * ch) for ch in (0.86, 0.72, 0.35)) - poly = [(w / 2 + p[0] * scale, h / 2 - p[1] * scale) for p in P] - faces.append((P[:, 2].mean(), poly, col)) - for _, poly, col in sorted(faces, key=lambda x: -x[0]): - d.polygon(poly, fill=col) - if label: - d.rectangle([8, 8, 8 + 9 * len(label), 30], fill=(255, 255, 255)) - d.text((14, 14), label, fill=(20, 20, 20)) - img.save(path) - - -if __name__ == "__main__": - tris = load_stl(sys.argv[1]) - print("triangles:", len(tris)) - views = [((26, -22, 20), "iso"), ((0, 0, 34), "straight down +Z"), - ((4, -30, 9), "grazing"), ((-28, -10, 12), "from the tall end")] - for i, (eye, lab) in enumerate(views): - render(tris, eye, (0, 0, 0), f"fem-{i}.png", label=f"FEMALE POCKET — {lab}") - print(f"fem-{i}.png") diff --git a/docs/CAD/design/mate-connectors/rk-0.png b/docs/CAD/design/mate-connectors/rk-0.png deleted file mode 100644 index 1d7208238a4477cbd925fdaf8c6b3043177edd28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4974 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 diff --git a/docs/CAD/design/mate-connectors/rk-1.png b/docs/CAD/design/mate-connectors/rk-1.png deleted file mode 100644 index 282bd0a2fe8f3ba5153a7b69547bb4e87a2c18c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5548 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 diff --git a/docs/CAD/design/mate-connectors/rk-2.png b/docs/CAD/design/mate-connectors/rk-2.png deleted file mode 100644 index 75d83e140210e9e56247b2a712e1f69fa1228667..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4940 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 M#>4_DH~Ks3AGV$yfdBvi diff --git a/docs/CAD/design/mate-connectors/rk-4.png b/docs/CAD/design/mate-connectors/rk-4.png deleted file mode 100644 index 794fd4bd97a550fad6e87346368fb13cf32bc5db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4992 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 QBm?#HSI*_B{q^9#0rC@2KmY&$ diff --git a/docs/CAD/design/mate-connectors/simplify-sheet.png b/docs/CAD/design/mate-connectors/simplify-sheet.png deleted file mode 100644 index 9c05180c598898c565e68cc8813631b6e935925b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50634 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%0DD=}CcvK452WfgiGQJ_RcnYhTPPZ&JL8 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 ah5T*u`mvYAah!O-OYO;1CHP~DH~$MqeTaVm diff --git a/docs/CAD/design/mate-connectors/simplify_study.py b/docs/CAD/design/mate-connectors/simplify_study.py deleted file mode 100644 index c0bbcf63ab..0000000000 --- a/docs/CAD/design/mate-connectors/simplify_study.py +++ /dev/null @@ -1,142 +0,0 @@ -"""Reduce the bear face to the fewest marks that still read at glyph size — wi3z. - -Geometry comes from bear_outline.json, which extract_outline.py pulled off the supplied male -B-rep's back plate: the outer wire IS the silhouette, the inner wires are the two eyes and the -muzzle opening. Nothing here is traced by eye. - -The glyph is drawn IN the connector's plane, so a grazing view foreshortens it along one axis by -sin(elevation) — exactly what collapsed the disc's roll quadrant to 3 pixels at 10 deg. Every -candidate is therefore rendered at three elevations as well as three pixel sizes. -""" -import json, math, os -from PIL import Image, ImageDraw - -HERE = os.path.dirname(os.path.abspath(__file__)) -D = json.load(open(os.path.join(HERE, "bear_outline.json"))) - -def norm(pts): - """Part frame (X right, Z down-negative) -> glyph frame (x right, y up), centred, unit height.""" - p = [(x, -z) for x, z in pts] - return p - -outer = norm(D["outer"]) -holes = [norm(h["pts"]) for h in D["holes"]] -# the two Ø9.8 wires are the eyes; the wide one is the muzzle -eyes = [h for h, meta in zip(holes, D["holes"]) if meta["d"] < 20] -muzzle = [h for h, meta in zip(holes, D["holes"]) if meta["d"] >= 20] - -ALL = outer + [p for h in holes for p in h] -xs = [p[0] for p in ALL]; ys = [p[1] for p in ALL] -CX, CY = (min(xs)+max(xs))/2, (min(ys)+max(ys))/2 -SPAN = max(max(xs)-min(xs), max(ys)-min(ys)) -def to_unit(pts): return [((x-CX)/SPAN, (y-CY)/SPAN) for x, y in pts] - -def rdp(pts, eps): - """Douglas-Peucker. Vertex count is the honest measure of 'how simplified'.""" - if len(pts) < 3: return pts - ax, ay = pts[0]; bx, by = pts[-1] - dx, dy = bx-ax, by-ay - n = math.hypot(dx, dy) - best, bi = -1.0, 0 - for i in range(1, len(pts)-1): - px, py = pts[i] - d = abs(dx*(ay-py) - (ax-px)*dy)/n if n > 1e-12 else math.hypot(px-ax, py-ay) - if d > best: best, bi = d, i - if best <= eps: - return [pts[0], pts[-1]] - return rdp(pts[:bi+1], eps)[:-1] + rdp(pts[bi:], eps) - -def simp_closed(pts, eps): - r = rdp(pts + [pts[0]], eps) - return r[:-1] - -def centroid(pts): - return (sum(p[0] for p in pts)/len(pts), sum(p[1] for p in pts)/len(pts)) - -U_OUT = to_unit(outer) -U_EYE = [to_unit(e) for e in eyes] -U_MUZ = [to_unit(m) for m in muzzle] - -def eye_dots(scale=1.0): - out = [] - for e in U_EYE: - cx, cy = centroid(e) - r = max(max(p[0] for p in e)-min(p[0] for p in e), - max(p[1] for p in e)-min(p[1] for p in e))/2*scale - out.append((cx, cy, r)) - return out - -def muzzle_tri(): - """The muzzle reduced to one filled triangle: its two lower corners and its apex.""" - m = U_MUZ[0] - lo = min(p[1] for p in m); hi = max(p[1] for p in m) - bottom = [p for p in m if p[1] < lo + 0.06*(hi-lo)] - apex = max(m, key=lambda p: p[1]) - return [min(bottom), max(bottom), apex] - -CANDIDATES = { - "C0 full": dict(out=U_OUT, eyes=eye_dots(), muz=U_MUZ[0]), - "C1 eps .004": dict(out=simp_closed(U_OUT, .004), eyes=eye_dots(), muz=simp_closed(U_MUZ[0], .004)), - "C2 eps .012": dict(out=simp_closed(U_OUT, .012), eyes=eye_dots(), muz=muzzle_tri()), - "C3 eps .030": dict(out=simp_closed(U_OUT, .030), eyes=eye_dots(1.15), muz=muzzle_tri()), - "C4 no eyes": dict(out=simp_closed(U_OUT, .012), eyes=[], muz=muzzle_tri()), -} - -def sym_report(pts, tol=0.02): - """Trivial symmetry group is the property doing the work. If a simplification restores a - mirror or a 180 deg rotation, that simplification is wrong.""" - def match(tf): - t = [tf(p) for p in pts] - hit = 0 - for q in t: - if min(math.hypot(q[0]-p[0], q[1]-p[1]) for p in pts) <= tol: hit += 1 - return hit, len(pts) - return { - "mirror-x": match(lambda p: (-p[0], p[1])), - "mirror-y": match(lambda p: ( p[0], -p[1])), - "rot-180": match(lambda p: (-p[0], -p[1])), - } - -def render(c, px, elev_deg, supersample=8): - S = px*supersample - img = Image.new("L", (S, S), 0) - d = ImageDraw.Draw(img) - k = math.sin(math.radians(elev_deg)) - def m(p): - return (S/2 + p[0]*S*0.92, S/2 - p[1]*S*0.92*k) - d.polygon([m(p) for p in c["out"]], fill=255) - if c["muz"]: d.polygon([m(p) for p in c["muz"]], fill=0) - for cx, cy, r in c["eyes"]: - a = m((cx-r, cy+r)); b = m((cx+r, cy-r)) - d.ellipse([a[0], a[1], b[0], b[1]], fill=0) - return img.resize((px, px), Image.LANCZOS) - -print(f"{'candidate':14} {'verts':>6} {'marks':>6} symmetry (matched/total, lower is better)") -print("-"*78) -for name, c in CANDIDATES.items(): - s = sym_report(c["out"]) - marks = 1 + (1 if c["muz"] else 0) + len(c["eyes"]) - sym = " ".join(f"{k} {v[0]}/{v[1]}" for k, v in s.items()) - print(f"{name:14} {len(c['out']):6} {marks:6} {sym}") - -SIZES = [22, 32, 48] -ELEVS = [(90, "flat on"), (47, "47 deg"), (16, "16 deg"), (6, "6 deg")] -pad, cell = 8, 56 -W = pad + len(SIZES)*len(ELEVS)*cell + pad -H = pad + len(CANDIDATES)*cell + pad -sheet = Image.new("RGB", (W, H), (24, 27, 32)) -for r, (name, c) in enumerate(CANDIDATES.items()): - for ci, (elev, _) in enumerate(ELEVS): - for si, px in enumerate(SIZES): - g = render(c, px, elev) - tile = Image.new("RGB", (px, px), (24, 27, 32)) - gold = Image.new("RGB", (px, px), (237, 168, 23)) - tile.paste(gold, (0, 0), g) - x = pad + (ci*len(SIZES)+si)*cell + (cell-px)//2 - y = pad + r*cell + (cell-px)//2 - sheet.paste(tile, (x, y)) -sheet = sheet.resize((W*2, H*2), Image.NEAREST) -sheet.save(os.path.join(HERE, "simplify-sheet.png")) -print("\ncolumns: " + " | ".join(f"{e[1]} @ 22/32/48px" for e in ELEVS)) -print("rows: " + ", ".join(CANDIDATES)) -print("WROTE simplify-sheet.png") diff --git a/docs/CAD/design/mate-connectors/size-test.png b/docs/CAD/design/mate-connectors/size-test.png deleted file mode 100644 index 941f76ffb8c29111c31f04d21e5858d8a9d46e56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9289 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 diff --git a/docs/CAD/design/mate-connectors/sz-22-down+Z.png b/docs/CAD/design/mate-connectors/sz-22-down+Z.png deleted file mode 100644 index 85cbf9ba4e1e384dc7fa443fb61613b0609d754e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 293 zcmV+=0owkFP)q^RQ{m5oL=9Ssm~ z8M@I73=DA7@mN5NO$3Z4#&l%hZY`_ry9B~!!Ib{<=O41^CoaAs#sYV1Su_zWMq>qd zEWk7rpV4RzN45ZNIxZ98faEN5;^M0l7he%HmjRcnd&@0xY2aXEhO^&&{7JIW1Pz4% zA%1o|c3?3Y)5(~I5@QEpSK~KV(rzva7m1B-G_sSi8j5BX9)R7|a6^eV2os=W^!s}f rIM|peF%GBE)CI$!0vk1&rbYt*>*Y&$ZV_*~00000NkvXXu0mjf1!Ql} diff --git a/docs/CAD/design/mate-connectors/sz-22-grazing.png b/docs/CAD/design/mate-connectors/sz-22-grazing.png deleted file mode 100644 index 047f2c6e0607da99d50f350acb37206ccd5b559c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1SGcvS$+jllRaG=Ln`JZ*L;6y%$&<0+^e`6`zqJ&HJv zl<=87E0?b~GBC*at#@$oB*C3~jy=`jGfRv4b-(sc=V_)n>y9PM7zsQ|udmaPe8kDo zaJ!}8>u2YaM`pCjd)zwPx>?d)dqd!2_g=YwZp;kqugsdo!#DT>oyXwm>gTe~DWM4f DTD?$7 diff --git a/docs/CAD/design/mate-connectors/sz-22-iso.png b/docs/CAD/design/mate-connectors/sz-22-iso.png deleted file mode 100644 index 55b8a13eed4a4f7361c252503061356aa2853a50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 238 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1SGcvS$+jlTRmMILn`JZ*L;6y%$&<0+hW_5~4@-l{-AAevp mC1^2c)5ECKJyO293=HQiOa2;uy!Hj?0R~T3KbLh*2~7ZmbYDUM diff --git a/docs/CAD/design/mate-connectors/sz-32-down+Z.png b/docs/CAD/design/mate-connectors/sz-32-down+Z.png deleted file mode 100644 index 683fc3d0aa8fb0f223dc82c511ce134878ffe0a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 359 zcmV-t0hs=YP)C`X{P}HLT&U!J~e4J?|z1r}#J5uvf`u(*V${m!3P* z*$5xKhXNLC3Ll~e2@c9rs;J?3Z5FF-)&|?x`GKCaonr{zYMb(= zC+xa+<HqIj+38ut$j=W7FNiaS%vjJc+X%I;gyqdLqk1%o>wmw~M@xXNQ znPMSNEP=tuxH2h+l~6YDguPS>Nof$^szZ zl}CZy?z(bYD`rI1)cYS|h~U{cRjf={`pWn3DgoE4YQ&Onxw!83^ozOj$;z+$<7~g3 zcY7kD6^_ha{bEytO}FOR<0;j$-TfyGx|@4{-aS5_hll6ZKJOD-W>iF5-k7nmYya;b zlOxWP;Idc;fR*aeM!|l&V&Q@6E5rzFWxUuBJjDr*!?5a} zDKWpt_)W9?$cBfFNmUNJIUDXZW*p$TD1{K){f+wIw=wV9mU$*v)l(O>J5)Q7Rr9`Y zTu52B*T+k(@n_z@%*{;MM#;RwU?0RXz5@M^?{B>(^b M07*qoM6N<$g5$r35dZ)H diff --git a/docs/CAD/design/mate-connectors/sz-48-down+Z.png b/docs/CAD/design/mate-connectors/sz-48-down+Z.png deleted file mode 100644 index ec451714ff626fc437e50aa464aef49673bfe441..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 502 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 sq*kfk-s zY)h4uE$KTUseKr^%Oe%}slO;Te(d_qmPGd0=(ukZo#k7m9qs{@rozvU`5FPF~YS;cs%}< zE#>@&%T>couND^;;*uA-?+WX9wCnlJjEd%L3QbB%vUG1BIFsd;jqNI`koSAdcEqsI zK3#Yp18T%@g~FSgow&K)6_stLm2cA%ea_$N2|APGQP_QFt^L3d8U1RYHom#3N&9EC zxxs9^ck^qzMy73tH`TDmMePJWV#S4@rb?~2zz_JvnH`jZ4`ed@p3Es`yR)1ljTQ!5 zvs_W_UR_1_BhfqUGBGqwe`Y9|EbrU*fZ{*>s<5z7vuN&8yxd9jjlue7Wua1xwj4&~ z1LcusibD}@z9IF952N!Oy2+1G=+kkd`P|JGCpldYH+q`Et%>gHCp%(xoy+s;ufDrV zIQzh+f{Fq0hQ>x#V=`xI@~dX?ASpE4qocK8cD`-v4evSr;|9ITn&nS*jpZMqY8A@833zx$VM^NES7@dVSFOCi|7|8}c9?IJfdEoLi zif0QM8Ze)_1pUyB8Ve&1+}q>1^zqiLkwl-)+>y8W)U!*yBFM%y4W1fisUSoc<0Gm<>iOUHp7+J%rBI0Y9DX-d4mO!xpn=+CFp7j(mSJ zGB#E?5a*hNpJ|bDz%jqQw5b(Vz)h4y8|rYwGMsA{Xp#Pz9vykQowpoYJ~(L>PBm!x zDGZ>~3}RO);NJCTxg_Ifj_G#reAtV#nhr%vsWIneaVuBO+?r`|>4L(sJ?mKp1u$E> zR`Y1GQQ>@Tety26pC2V}nCz0|>pVN&f#A+lQ3rA~xY8+JZ3v;gc|?EiVi#e$$<}AQ zeLhtae+5F3)8)VTLwTVmSiz&SU_Pt){9?Z*VZK}r>(>79J%xg;jnj6RueNP%Nl8ni zLq55sQbq5sHj&9>C`q3>efqxhb9Dxc(I4*}IZN||g~sF;mWj6~`>I+pTom-Kr7%a| z3X8}~o(kn|JEuN!Gq=rih%$RxY`)j+!Sq8SCjSll2A0HpkWeg8P z!8b$D5uCZ+=cvfK*xim_yxTfDG)S2u*7v~!7 z;fHM)MT_I`QoqL4DyKiEX!=d+k(jsV^0S$tI zvKE5XI%tPRhil%o%9-tP(5~r3;l{f6YQ1AV-e$8>-ZEZi>`RDvEq0n&p`gpD@8~de zEDtL|3~4de9e?qjbBR_ajWwE*vXFEh(LRwd9UqIAtYaHouEk;^D>fwOD@} zbQ6^JVYd@#^jfK1?H`f(J6w-~k5 zBo9c>g-;LS$@wbs`Z3ge!f z$1N^<7BOFC)d>Z@*SY32EOw}I-FGFQyt=wI)4X(vlY?a+p141S5Lq_<)c4)A=jJEfpwZYKM+q~k2qNUEScjxT%$;maJt?V zvd7-F`7OId*k233Fx6WTsas^dQc>)mbx8-X^$%>-s_X+0C;d{Kiqle+C1I zq6fzNsV#~C)e6B!c%c35#$08W+M={rN+Lj`-RGDL_~)=GGWdBS(72xYl(^xT5uYUr zwTd`cTyGh0a}aa4tB$QED2RC!*wSs0NnUaVt33^eEl!&7-Kn9HC`kMgaI~GA#^l