diff --git a/src/board/step_export_settings.cpp b/src/board/step_export_settings.cpp index 10ddd9f19..04466033c 100644 --- a/src/board/step_export_settings.cpp +++ b/src/board/step_export_settings.cpp @@ -6,8 +6,16 @@ namespace horizon { STEPExportSettings::STEPExportSettings(const json &j) : filename(j.at("filename").get()), prefix(j.at("prefix").get()), - include_3d_models(j.at("include_3d_models")) + include_3d_models(j.at("include_3d_models")), min_diameter(j.value("min_diameter", 0_mm)) { + auto excllist = j.value("pkgs_excluded", json::array()); + + for (const auto &excluuid : excllist) { + if (!excluuid.is_string()) + continue; + + pkgs_excluded.emplace(excluuid.get()); + } } json STEPExportSettings::serialize() const @@ -16,6 +24,17 @@ json STEPExportSettings::serialize() const j["filename"] = filename; j["prefix"] = prefix; j["include_3d_models"] = include_3d_models; + if (min_diameter) + j["min_diameter"] = min_diameter; + if (pkgs_excluded.size()) { + auto excllist = json::array(); + + for (const auto &uuid : pkgs_excluded) { + excllist.push_back(uuid); + } + + j["pkgs_excluded"] = excllist; + } return j; } diff --git a/src/board/step_export_settings.hpp b/src/board/step_export_settings.hpp index 982f82109..3db09d023 100644 --- a/src/board/step_export_settings.hpp +++ b/src/board/step_export_settings.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include "common/common.hpp" #include "common/lut.hpp" #include "nlohmann/json_fwd.hpp" @@ -18,5 +19,7 @@ class STEPExportSettings { std::string filename; std::string prefix; bool include_3d_models = true; + uint64_t min_diameter; + std::set pkgs_excluded; }; } // namespace horizon diff --git a/src/export_step/export_step.cpp b/src/export_step/export_step.cpp index 3d274ed21..cea7cbc68 100644 --- a/src/export_step/export_step.cpp +++ b/src/export_step/export_step.cpp @@ -97,13 +97,15 @@ static TopoDS_Shape face_from_polyon(const Polygon &poly) class CanvasHole : public Canvas { public: - CanvasHole(TopTools_ListOfShape &cs) : cutouts(cs) + CanvasHole(TopTools_ListOfShape &cs, uint64_t mdia = 0) : cutouts(cs), min_diameter(mdia) { img_mode = true; } private: TopTools_ListOfShape &cutouts; + uint64_t min_diameter; + void img_hole(const class Hole &hole) override { if (hole.diameter == 0) @@ -115,6 +117,9 @@ class CanvasHole : public Canvas { Placement tr = transform; tr.accumulate(hole.placement); if (hole.shape == Hole::Shape::ROUND) { + if (hole.diameter < min_diameter) + return; + auto ax = gp_Ax2(gp_Pnt(tr.shift.x / 1e6, tr.shift.y / 1e6, 0), gp_Dir(0, 0, 1)); auto circ = gp_Circ(ax, hole.diameter / 2e6); auto edge = BRepBuilderAPI_MakeEdge(circ); @@ -402,7 +407,7 @@ static bool getModelLabel(const std::string &aFileName, TDF_Label &aLabel, Handl void export_step(const std::string &filename, const Board &brd, class IPool &pool, bool include_models, std::function progress_cb, const BoardColors *colors, - const std::string &prefix) + const std::string &prefix, uint64_t min_diameter, std::set *pkgs_excluded) { try { auto app = XCAFApp_Application::GetApplication(); @@ -430,7 +435,7 @@ void export_step(const std::string &filename, const Board &brd, class IPool &poo progress_cb("Holes…"); { - CanvasHole canvas_hole(cutouts); + CanvasHole canvas_hole(cutouts, min_diameter); canvas_hole.update(brd); } for (const auto &hole : outline.holes) { @@ -498,6 +503,9 @@ void export_step(const std::string &filename, const Board &brd, class IPool &poo if (package.component && package.component->nopopulate) { continue; } + if (pkgs_excluded && (pkgs_excluded->find(package.package.uuid) != pkgs_excluded->end())) { + continue; + } pkgs.push_back(&package); } auto n_pkg = std::to_string(pkgs.size()); diff --git a/src/export_step/export_step.hpp b/src/export_step/export_step.hpp index d12e00572..de7a60986 100644 --- a/src/export_step/export_step.hpp +++ b/src/export_step/export_step.hpp @@ -1,9 +1,12 @@ #pragma once #include #include +#include +#include "util/uuid.hpp" +#include "common/common.hpp" namespace horizon { void export_step(const std::string &filename, const class Board &brd, class IPool &pool, bool include_models, std::function progress_cb, const class BoardColors *colors = nullptr, - const std::string &prefix = ""); + const std::string &prefix = "", uint64_t min_diameter = 0_mm, std::set *pkgs_excluded = nullptr); } diff --git a/src/imp/step_export.ui b/src/imp/step_export.ui index ce206a0d0..5f7b91886 100644 --- a/src/imp/step_export.ui +++ b/src/imp/step_export.ui @@ -1,7 +1,17 @@ - + + + True + False + go-next-symbolic + + + True + False + go-previous-symbolic + False 400 @@ -45,6 +55,7 @@ vertical 20 + True False @@ -80,7 +91,7 @@ 0 - 2 + 3 @@ -93,7 +104,7 @@ 1 - 2 + 3 @@ -160,6 +171,37 @@ 1 + + + True + False + end + Minimum hole/via ø + 1 + + + + 0 + 2 + + + + + True + False + Exclude circular holes or vias from STEP export if they are smaller than the given diameter. Holes/vias of this size or larger are included (i.e. greater-or-equal comparison.) Has no effect on non-circular structures like slots or manually drawn outline polygons. + +Setting this to zero (the default) includes all holes. + start + vertical + + + 1 + 2 + + False @@ -167,6 +209,153 @@ 0 + + + + True + False + 10 + + + True + False + 5 + False + Included packages + 0 + + + + + + + 0 + 0 + + + + + True + False + 5 + Excluded packages + 0 + + + + + + + 2 + 0 + + + + + True + False + center + center + 20 + 20 + vertical + 10 + + + True + True + True + image1 + + + False + True + 0 + + + + + True + True + True + 5 + image2 + + + False + True + 1 + + + + + 1 + 1 + + + + + True + True + True + True + never + in + + + True + True + 0 + + + multiple + + + + + + + 0 + 1 + + + + + True + True + True + never + in + + + True + True + False + 0 + + + multiple + + + + + + + 2 + 1 + + + + + False + True + 1 + + True diff --git a/src/imp/step_export_window.cpp b/src/imp/step_export_window.cpp index cff71476a..603b4fb60 100644 --- a/src/imp/step_export_window.cpp +++ b/src/imp/step_export_window.cpp @@ -1,8 +1,10 @@ #include "step_export_window.hpp" #include "export_step/export_step.hpp" +#include "board/board.hpp" #include "board/step_export_settings.hpp" #include "util/util.hpp" #include "util/gtk_util.hpp" +#include "widgets/spin_button_dim.hpp" #include "document/idocument_board.hpp" #include @@ -47,6 +49,20 @@ StepExportWindow::StepExportWindow(BaseObjectType *cobject, const Glib::RefPtrset_width_chars(width_chars); + min_dia_spin_button->set_range(0.00_mm, 99_mm); + min_dia_spin_button->set_value(0.00_mm); + min_dia_spin_button->show(); + + min_dia_box->pack_start(*min_dia_spin_button, true, true, 0); export_button->signal_clicked().connect(sigc::mem_fun(*this, &StepExportWindow::generate)); @@ -60,9 +76,49 @@ StepExportWindow::StepExportWindow(BaseObjectType *cobject, const Glib::RefPtrproperty_active().signal_changed().connect([this] { s_signal_changed.emit(); }); prefix_entry->signal_changed().connect([this] { s_signal_changed.emit(); }); + min_dia_spin_button->signal_changed().connect([this] { s_signal_changed.emit(); }); + + pkgs_store = Gtk::ListStore::create(list_columns); + pkgs_fill(); + + pkgs_included = Gtk::TreeModelFilter::create(pkgs_store); + pkgs_included->set_visible_func([this](const Gtk::TreeModel::const_iterator &it) -> bool { + Gtk::TreeModel::Row row = *it; + const UUID &uuid = row[list_columns.uuid]; + auto search = settings.pkgs_excluded.find(uuid); + return search == settings.pkgs_excluded.end(); + }); + auto pkgs_included_sort = Gtk::TreeModelSort::create(pkgs_included); + pkgs_included_tv->set_model(pkgs_included_sort); + pkgs_included_tv->append_column("Package", list_columns.pkg_name); + pkgs_included_tv->append_column("Count", list_columns.pkg_count); + pkgs_included_sort->set_sort_column(0, Gtk::SortType::SORT_ASCENDING); + + pkgs_excluded = Gtk::TreeModelFilter::create(pkgs_store); + pkgs_excluded->set_visible_func([this](const Gtk::TreeModel::const_iterator &it) -> bool { + Gtk::TreeModel::Row row = *it; + const UUID &uuid = row[list_columns.uuid]; + auto search = settings.pkgs_excluded.find(uuid); + return search != settings.pkgs_excluded.end(); + }); + auto pkgs_excluded_sort = Gtk::TreeModelSort::create(pkgs_excluded); + pkgs_excluded_tv->set_model(pkgs_excluded_sort); + pkgs_excluded_tv->append_column("Package", list_columns.pkg_name); + pkgs_excluded_tv->append_column("Count", list_columns.pkg_count); + pkgs_excluded_sort->set_sort_column(0, Gtk::SortType::SORT_ASCENDING); + + pkg_incl_button->signal_clicked().connect([this] { pkg_incl_excl(true); }); + pkg_excl_button->signal_clicked().connect([this] { pkg_incl_excl(false); }); + + pkgs_included_tv->get_selection()->signal_changed().connect( + sigc::mem_fun(*this, &StepExportWindow::update_pkg_incl_excl_sensitivity)); + pkgs_excluded_tv->get_selection()->signal_changed().connect( + sigc::mem_fun(*this, &StepExportWindow::update_pkg_incl_excl_sensitivity)); + update_pkg_incl_excl_sensitivity(); tag = log_textview->get_buffer()->create_tag(); tag->property_font_features() = "tnum 1"; @@ -91,6 +147,64 @@ StepExportWindow::StepExportWindow(BaseObjectType *cobject, const Glib::RefPtr summary; + + for (const auto &it : brd->packages) { + const auto pkg = it.second; + const auto sum_it = summary.try_emplace(pkg.package.uuid, pkg.package.name).first; + sum_it->second.count++; + } + + for (const auto &it : summary) { + Gtk::TreeModel::Row row = *pkgs_store->append(); + row[list_columns.pkg_name] = it.second.name; + row[list_columns.pkg_count] = it.second.count; + row[list_columns.uuid] = it.first; + } +} + +void StepExportWindow::update_pkg_incl_excl_sensitivity() +{ + pkg_incl_button->set_sensitive(pkgs_excluded_tv->get_selection()->count_selected_rows()); + pkg_excl_button->set_sensitive(pkgs_included_tv->get_selection()->count_selected_rows()); +} + +void StepExportWindow::pkg_incl_excl(bool incl) +{ + Gtk::TreeView *tv; + Glib::RefPtr model; + + if (incl) + tv = pkgs_excluded_tv; + else + tv = pkgs_included_tv; + + for (const auto &path : tv->get_selection()->get_selected_rows(model)) { + const auto &it = model->get_iter(path); + const auto &uuid = (*it)[list_columns.uuid]; + + if (incl) + settings.pkgs_excluded.erase(uuid); + else + settings.pkgs_excluded.emplace(uuid); + } + + pkgs_excluded->refilter(); + pkgs_included->refilter(); + s_signal_changed.emit(); +} + void StepExportWindow::generate() { if (export_running) @@ -119,7 +233,7 @@ void StepExportWindow::export_thread(STEPExportSettings my_settings) } export_dispatcher.emit(); }, - &core.get_colors(), my_settings.prefix); + &core.get_colors(), my_settings.prefix, my_settings.min_diameter, &my_settings.pkgs_excluded); } catch (const std::exception &e) { { diff --git a/src/imp/step_export_window.hpp b/src/imp/step_export_window.hpp index 175504465..43d284e9b 100644 --- a/src/imp/step_export_window.hpp +++ b/src/imp/step_export_window.hpp @@ -2,6 +2,7 @@ #include #include #include +#include "util/uuid.hpp" #include "util/export_file_chooser.hpp" #include "util/changeable.hpp" @@ -24,7 +25,31 @@ class StepExportWindow : public Gtk::Window, public Changeable { Gtk::Button *filename_button = nullptr; Gtk::Button *export_button = nullptr; Gtk::Switch *include_3d_models_switch = nullptr; + Gtk::Box *min_dia_box = nullptr; + class SpinButtonDim *min_dia_spin_button = nullptr; Gtk::Entry *prefix_entry = nullptr; + Gtk::Button *pkg_incl_button = nullptr; + Gtk::Button *pkg_excl_button = nullptr; + Gtk::TreeView *pkgs_included_tv = nullptr; + Gtk::TreeView *pkgs_excluded_tv = nullptr; + + class ListColumns : public Gtk::TreeModelColumnRecord { + public: + ListColumns() + { + Gtk::TreeModelColumnRecord::add(pkg_name); + Gtk::TreeModelColumnRecord::add(pkg_count); + Gtk::TreeModelColumnRecord::add(uuid); + } + Gtk::TreeModelColumn pkg_name; + Gtk::TreeModelColumn pkg_count; + Gtk::TreeModelColumn uuid; + }; + ListColumns list_columns; + + Glib::RefPtr pkgs_store; + Glib::RefPtr pkgs_included; + Glib::RefPtr pkgs_excluded; Gtk::TextView *log_textview = nullptr; Gtk::Spinner *spinner = nullptr; @@ -44,6 +69,10 @@ class StepExportWindow : public Gtk::Window, public Changeable { std::deque msg_queue; bool export_running = false; + void pkgs_fill(); + void update_pkg_incl_excl_sensitivity(); + void pkg_incl_excl(bool incl); + void set_is_busy(bool v); void export_thread(STEPExportSettings settings); diff --git a/src/python_module/board.cpp b/src/python_module/board.cpp index d7fb2441d..05f79c065 100644 --- a/src/python_module/board.cpp +++ b/src/python_module/board.cpp @@ -286,7 +286,8 @@ static PyObject *PyBoard_export_step(PyObject *pself, PyObject *args) horizon::STEPExportSettings settings(settings_json); horizon::export_step( settings.filename, self->board->board, self->board->pool, settings.include_3d_models, - [py_callback](const std::string &s) { callback_wrapper(py_callback, s); }, nullptr, settings.prefix); + [py_callback](const std::string &s) { callback_wrapper(py_callback, s); }, nullptr, settings.prefix, + settings.min_diameter); } catch (const py_exception &e) { return NULL;