From aa2907f730ee2f5aa2008f79fd44aea47859dbcc Mon Sep 17 00:00:00 2001 From: Tommaso Bianchi Date: Tue, 30 Jun 2026 08:21:03 +0200 Subject: [PATCH] Design: STEP export of the CAD model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an "Export STEP…" button to the Design panel that writes every body to a .step file as native B-rep (not mesh). - CadDocument::export_step: compound all bodies (applying their per-body Move display transform so the STEP matches what Commit ships) and write via OCCT STEPControl_Writer (AsIs). Full error handling incl. OCCT Standard_Failure. - DesignPanel::on_export_step: bake any open preview, wxFileDialog save, export at the displayed body positions, status feedback. Kernel write path verified with a standalone OCCT box->STEP->readback check (1 solid, non-null) against the same OCCT build. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q --- src/libslic3r/CadDocument.cpp | 47 ++++++++++++++++++++++++++++++++++ src/libslic3r/CadDocument.hpp | 7 +++++ src/slic3r/GUI/DesignPanel.cpp | 31 +++++++++++++++++++++- src/slic3r/GUI/DesignPanel.hpp | 1 + 4 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/CadDocument.cpp b/src/libslic3r/CadDocument.cpp index a6c331e9e0..695d838d07 100644 --- a/src/libslic3r/CadDocument.cpp +++ b/src/libslic3r/CadDocument.cpp @@ -39,6 +39,9 @@ #include #include #include // pattern: rigid copy transforms +#include // STEP export (native B-rep) +#include +#include #include // pattern: rotation axis (circular) #include #include @@ -1780,4 +1783,48 @@ bool CadDocument::deserialize_recipe(const std::string& blob) } } +bool CadDocument::export_step(const std::string& path, + const std::vector& body_xforms, + std::string& err) const +{ + err.clear(); + if (bodies.empty()) { err = "nothing to export"; return false; } + try { + // Compound every body at its displayed (Move-gizmo) position so the STEP matches + // what Commit ships. Move transforms are rigid, so gp_Trsf::SetValues is valid. + BRep_Builder bld; + TopoDS_Compound comp; + bld.MakeCompound(comp); + for (size_t i = 0; i < bodies.size(); ++i) { + if (bodies[i].shape.IsNull()) continue; + TopoDS_Shape s = bodies[i].shape; + if (i < body_xforms.size() && !body_xforms[i].isApprox(Transform3d::Identity())) { + const Transform3d& m = body_xforms[i]; + gp_Trsf t; + t.SetValues(m(0,0), m(0,1), m(0,2), m(0,3), + m(1,0), m(1,1), m(1,2), m(1,3), + m(2,0), m(2,1), m(2,2), m(2,3)); + s = BRepBuilderAPI_Transform(s, t, true).Shape(); + } + bld.Add(comp, s); + } + STEPControl_Writer writer; + if (writer.Transfer(comp, STEPControl_AsIs) != IFSelect_RetDone) { + err = "STEP transfer failed"; + return false; + } + if (writer.Write(path.c_str()) != IFSelect_RetDone) { + err = "cannot write STEP file"; + return false; + } + } catch (const Standard_Failure& e) { + err = e.GetMessageString() ? e.GetMessageString() : "OCCT failed to write STEP"; + return false; + } catch (const std::exception& e) { + err = e.what(); + return false; + } + return true; +} + } // namespace Slic3r diff --git a/src/libslic3r/CadDocument.hpp b/src/libslic3r/CadDocument.hpp index 2c45f765ee..d8e0a57cd5 100644 --- a/src/libslic3r/CadDocument.hpp +++ b/src/libslic3r/CadDocument.hpp @@ -360,6 +360,13 @@ public: std::string serialize_recipe() const; bool deserialize_recipe(const std::string& blob); + // Export every body to a STEP file as native B-rep (not mesh). body_xforms is the + // per-body display transform (Move gizmo); when supplied the bodies are written at + // those positions so the STEP matches what Commit ships. false + err on failure. + bool export_step(const std::string& path, + const std::vector& body_xforms, + std::string& err) const; + // Undo/redo of the feature recipe (Onshape-style Ctrl+Z). The caller marks a // user-action boundary by calling checkpoint() BEFORE the mutation(s) for that // action (add/delete/move/replace, or a direct features edit). undo()/redo() then diff --git a/src/slic3r/GUI/DesignPanel.cpp b/src/slic3r/GUI/DesignPanel.cpp index 27049bf301..6a242b8d97 100644 --- a/src/slic3r/GUI/DesignPanel.cpp +++ b/src/slic3r/GUI/DesignPanel.cpp @@ -1514,7 +1514,11 @@ DesignPanel::DesignPanel(wxWindow* parent) auto* commit = new wxButton(m_form, wxID_ANY, _L("Commit to Plate")); commit->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { on_commit(); }); - root->Add(commit, 0, wxALL, 12); + root->Add(commit, 0, wxLEFT | wxRIGHT | wxTOP, 12); + + auto* export_step = new wxButton(m_form, wxID_ANY, _L("Export STEP…")); + export_step->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { on_export_step(); }); + root->Add(export_step, 0, wxALL, 12); m_shape->Bind(wxEVT_CHOICE, [this](wxCommandEvent& e) { on_shape_changed(); e.Skip(); }); on_shape_changed(); @@ -4766,6 +4770,31 @@ void DesignPanel::on_edit_feature() } } +void DesignPanel::on_export_step() +{ + // Bake any open feature preview first, so the STEP matches what is shown (mirrors on_commit). + if (m_active != Tool::None) + confirm_tool(); + if (m_doc.bodies.empty()) { + m_status->SetForegroundColour(wxNullColour); + m_status->SetLabel(_L("Nothing to export — add a feature first")); + m_status->Refresh(); + return; + } + wxFileDialog dlg(this, _L("Export STEP"), wxEmptyString, "model.step", + "STEP files (*.step;*.stp)|*.step;*.stp", + wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if (dlg.ShowModal() != wxID_OK) + return; + sync_body_xform(); // export bodies at their displayed Move-gizmo positions + std::string err; + const bool ok = m_doc.export_step(dlg.GetPath().ToUTF8().data(), m_body_xform, err); + m_status->SetForegroundColour(ok ? wxColour(120, 210, 120) : wxColour(235, 110, 110)); + m_status->SetLabel(ok ? _L("Exported STEP") + : _L("STEP export failed: ") + wxString::FromUTF8(err)); + m_status->Refresh(); +} + void DesignPanel::on_commit() { // A feature tool open with a live preview ghost (e.g. a fillet being previewed) is diff --git a/src/slic3r/GUI/DesignPanel.hpp b/src/slic3r/GUI/DesignPanel.hpp index c224c6a9bf..60f52af58e 100644 --- a/src/slic3r/GUI/DesignPanel.hpp +++ b/src/slic3r/GUI/DesignPanel.hpp @@ -97,6 +97,7 @@ private: // a modal dialog editing the feature's placement transform in place. void on_transform_imported(int feat_idx); void on_commit(); + void on_export_step(); // write all bodies to a .step file (native B-rep) // Rehydrate the parametric model from a project's saved recipe (3MF // Metadata/SnapOrca_cad.bin): deserialize -> recompute -> refresh viewport + tree. void load_recipe(const std::string& blob);