mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	slaprint with dummy backround processing in plater.
This commit is contained in:
		
							parent
							
								
									91a79e0343
								
							
						
					
					
						commit
						3b373a55e6
					
				
					 6 changed files with 143 additions and 65 deletions
				
			
		|  | @ -137,6 +137,7 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent) | ||||||
|     : m_parent(parent) |     : m_parent(parent) | ||||||
|     , m_group_id(-1) |     , m_group_id(-1) | ||||||
|     , m_state(Off) |     , m_state(Off) | ||||||
|  |     , m_prev_state(Off) | ||||||
|     , m_hover_id(-1) |     , m_hover_id(-1) | ||||||
|     , m_dragging(false) |     , m_dragging(false) | ||||||
| { | { | ||||||
|  | @ -1621,32 +1622,6 @@ void GLGizmoSlaSupports::update_mesh() | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GLGizmoSlaSupports::on_deactivate() { |  | ||||||
|     if(!m_model_object) return; |  | ||||||
| 
 |  | ||||||
| //    sla::Controller supportctl;
 |  | ||||||
| //    std::cout << "Generating supports:" << std::endl;
 |  | ||||||
| 
 |  | ||||||
| //    // TODO: somehow get the global status indicator
 |  | ||||||
| //    supportctl.statuscb = [] (unsigned st, const std::string& msg) {
 |  | ||||||
| //        std::cout << st << "% "  << msg << std::endl;
 |  | ||||||
| //    };
 |  | ||||||
| 
 |  | ||||||
| //    TriangleMesh&& m = m_model_object->raw_mesh();
 |  | ||||||
| //    m.transform(m_model_object_matrix);
 |  | ||||||
| //    auto emesh = sla::to_eigenmesh(m);
 |  | ||||||
| 
 |  | ||||||
| //    sla::SupportConfig cfg;
 |  | ||||||
| //    sla::PointSet input = sla::support_points(*m_model_object, 0 /*instance*/);
 |  | ||||||
| 
 |  | ||||||
| //    sla::SLASupportTree stree(input, emesh, cfg, supportctl);
 |  | ||||||
| 
 |  | ||||||
| //    TriangleMesh output;
 |  | ||||||
| //    stree.merged_mesh(output);
 |  | ||||||
| 
 |  | ||||||
| //    _3DScene::reload_scene(m_parent.get_wxglcanvas(), false);
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) | Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) | ||||||
| { | { | ||||||
|     // if the gizmo doesn't have the V, F structures for igl, calculate them first:
 |     // if the gizmo doesn't have the V, F structures for igl, calculate them first:
 | ||||||
|  |  | ||||||
|  | @ -56,7 +56,7 @@ protected: | ||||||
|     GLCanvas3D& m_parent; |     GLCanvas3D& m_parent; | ||||||
| 
 | 
 | ||||||
|     int m_group_id; |     int m_group_id; | ||||||
|     EState m_state; |     EState m_state, m_prev_state; | ||||||
|     // textures are assumed to be square and all with the same size in pixels, no internal check is done
 |     // textures are assumed to be square and all with the same size in pixels, no internal check is done
 | ||||||
|     GLTexture m_textures[Num_States]; |     GLTexture m_textures[Num_States]; | ||||||
|     int m_hover_id; |     int m_hover_id; | ||||||
|  | @ -79,12 +79,14 @@ public: | ||||||
| 
 | 
 | ||||||
|     EState get_state() const { return m_state; } |     EState get_state() const { return m_state; } | ||||||
|     void set_state(EState state) { |     void set_state(EState state) { | ||||||
|         // FIXME: this is my workaround to react on the disabling event (Tamas)
 |         bool call_deactivate = | ||||||
|         bool call_deactivate = ((m_state == On || m_state == Hover) && |                 m_prev_state == On && state == Off && m_state == Hover; | ||||||
|                                 state == Off); | 
 | ||||||
|  |         if(state == On || state == Off) m_prev_state = state; | ||||||
| 
 | 
 | ||||||
|         m_state = state; on_set_state(); |         m_state = state; on_set_state(); | ||||||
| 
 | 
 | ||||||
|  |         // FIXME: this is my workaround to react on the disabling event (Tamas)
 | ||||||
|         if(call_deactivate) { |         if(call_deactivate) { | ||||||
|             on_deactivate(); |             on_deactivate(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -842,6 +842,42 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi | ||||||
| 
 | 
 | ||||||
| // Plater / private
 | // Plater / private
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | // For the SLAPrint: No multi-threading here
 | ||||||
|  | class DummyBackgroundProcess: public BackgroundProcess { | ||||||
|  | public: | ||||||
|  | 
 | ||||||
|  |     /// schedule a task on the background
 | ||||||
|  |     virtual void schedule(std::function<void()> fn) override { | ||||||
|  |         /*std::asynch*/ fn(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Report status change, used inside the worker thread
 | ||||||
|  |     virtual void status(unsigned st, const std::string& msg) { | ||||||
|  |         // TODO would use the statusbar gauge
 | ||||||
|  |         std::cout << "Processing " << st << "% " << msg << std::endl; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Check whether the calculation was canceled from the UI. Called by the
 | ||||||
|  |     /// worker thread
 | ||||||
|  |     virtual bool is_canceled() { | ||||||
|  |         // this would be connected to the statusbar's cancel button
 | ||||||
|  |         // and return true if that was pushed during the processing
 | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Determine the state of the background process. If something is running
 | ||||||
|  |     /// returns true. If no job is running, returns false.
 | ||||||
|  |     virtual bool is_running() { return false; } | ||||||
|  | 
 | ||||||
|  |     virtual void input_changed() override { | ||||||
|  |         /*lock();*/ | ||||||
|  |         BackgroundProcess::input_changed(); | ||||||
|  |         /*unlock();*/ | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| struct Plater::priv | struct Plater::priv | ||||||
| { | { | ||||||
|     // PIMPL back pointer ("Q-Pointer")
 |     // PIMPL back pointer ("Q-Pointer")
 | ||||||
|  | @ -854,16 +890,19 @@ struct Plater::priv | ||||||
|     // Data
 |     // Data
 | ||||||
|     Slic3r::DynamicPrintConfig *config; |     Slic3r::DynamicPrintConfig *config; | ||||||
|     Slic3r::Print print; |     Slic3r::Print print; | ||||||
|     Slic3r::SLAPrint slaprint; |  | ||||||
|     Slic3r::Model model; |     Slic3r::Model model; | ||||||
|     Slic3r::GCodePreviewData gcode_preview_data; |     Slic3r::GCodePreviewData gcode_preview_data; | ||||||
| 
 | 
 | ||||||
|  |     // Will live only in this branch:
 | ||||||
|  |     Slic3r::SLAPrint slaprint; | ||||||
|  | 
 | ||||||
|     // GUI elements
 |     // GUI elements
 | ||||||
|     wxNotebook *notebook; |     wxNotebook *notebook; | ||||||
|     Sidebar *sidebar; |     Sidebar *sidebar; | ||||||
|     wxGLCanvas *canvas3D;    // TODO: Use GLCanvas3D when we can
 |     wxGLCanvas *canvas3D;    // TODO: Use GLCanvas3D when we can
 | ||||||
|     Preview *preview; |     Preview *preview; | ||||||
|     BackgroundSlicingProcess    background_process; |     BackgroundSlicingProcess    background_process; | ||||||
|  | 
 | ||||||
|     wxTimer                     background_process_timer; |     wxTimer                     background_process_timer; | ||||||
| 
 | 
 | ||||||
|     static const std::regex pattern_bundle; |     static const std::regex pattern_bundle; | ||||||
|  | @ -943,7 +982,6 @@ private: | ||||||
| const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); | const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); | ||||||
| const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase); | const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase); | ||||||
| const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::icase); | const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::icase); | ||||||
| 
 |  | ||||||
| Plater::priv::priv(Plater *q, MainFrame *main_frame) : | Plater::priv::priv(Plater *q, MainFrame *main_frame) : | ||||||
|     q(q), |     q(q), | ||||||
|     main_frame(main_frame), |     main_frame(main_frame), | ||||||
|  | @ -959,6 +997,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : | ||||||
|     canvas3D(GLCanvas3DManager::create_wxglcanvas(notebook)), |     canvas3D(GLCanvas3DManager::create_wxglcanvas(notebook)), | ||||||
|     slaprint(&model) |     slaprint(&model) | ||||||
| { | { | ||||||
|  |     slaprint.set_scheduler(std::make_shared<DummyBackgroundProcess>()); | ||||||
|  |     // TODO: background_process.set_print(&slaprint);
 | ||||||
|     background_process.set_print(&print); |     background_process.set_print(&print); | ||||||
|     background_process.set_gcode_preview_data(&gcode_preview_data); |     background_process.set_gcode_preview_data(&gcode_preview_data); | ||||||
|     background_process.set_sliced_event(EVT_SLICING_COMPLETED); |     background_process.set_sliced_event(EVT_SLICING_COMPLETED); | ||||||
|  | @ -1526,6 +1566,10 @@ void Plater::priv::async_apply_config() | ||||||
| 
 | 
 | ||||||
|     // Apply new config to the possibly running background task.
 |     // Apply new config to the possibly running background task.
 | ||||||
|     Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), std::move(config)); |     Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), std::move(config)); | ||||||
|  | 
 | ||||||
|  |     // Thread safe invalidation of the SLAPrint data cache
 | ||||||
|  |     this->slaprint.synch(); | ||||||
|  | 
 | ||||||
|     // Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
 |     // Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile.
 | ||||||
|     if (Slic3r::_3DScene::is_layers_editing_enabled(this->canvas3D)) |     if (Slic3r::_3DScene::is_layers_editing_enabled(this->canvas3D)) | ||||||
|         this->canvas3D->Refresh(); |         this->canvas3D->Refresh(); | ||||||
|  | @ -1936,6 +1980,11 @@ Sidebar& Plater::sidebar() { return *p->sidebar; } | ||||||
| Model& Plater::model()  { return p->model; } | Model& Plater::model()  { return p->model; } | ||||||
| Print& Plater::print()  { return p->print; } | Print& Plater::print()  { return p->print; } | ||||||
| 
 | 
 | ||||||
|  | SLAPrint &Plater::sla_print() | ||||||
|  | { | ||||||
|  |     return p->slaprint; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void Plater::add() | void Plater::add() | ||||||
| { | { | ||||||
|     wxArrayString input_files; |     wxArrayString input_files; | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ namespace Slic3r { | ||||||
| 
 | 
 | ||||||
| class Model; | class Model; | ||||||
| class Print; | class Print; | ||||||
|  | class SLAPrint; | ||||||
| 
 | 
 | ||||||
| namespace GUI { | namespace GUI { | ||||||
| 
 | 
 | ||||||
|  | @ -104,6 +105,7 @@ public: | ||||||
|     Sidebar& sidebar(); |     Sidebar& sidebar(); | ||||||
|     Model& model(); |     Model& model(); | ||||||
|     Print& print(); |     Print& print(); | ||||||
|  |     SLAPrint& sla_print(); | ||||||
| 
 | 
 | ||||||
|     void add(); |     void add(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,9 @@ | ||||||
| #include "SLAPrint.hpp" | #include "SLAPrint.hpp" | ||||||
| #include "GUI.hpp" | #include "GUI.hpp" | ||||||
|  | #include "GLGizmo.hpp" | ||||||
|  | #include "Plater.hpp" | ||||||
|  | 
 | ||||||
|  | #include "GUI_App.hpp" | ||||||
| 
 | 
 | ||||||
| namespace Slic3r { | namespace Slic3r { | ||||||
| 
 | 
 | ||||||
|  | @ -16,20 +20,57 @@ const std::string SLAPrint::m_stage_labels[] = { | ||||||
|     L("SLA print preparation aborted")  // ABORT,
 |     L("SLA print preparation aborted")  // ABORT,
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| void SLAPrint::synch() { | void SLAPrint::_start() | ||||||
|     m_gcfg = m_config_reader(); | { | ||||||
|     // TODO: check model objects and instances
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool SLAPrint::start(std::shared_ptr<BackgroundProcess> scheduler) { | void SLAPrint::_synch() { | ||||||
|     if(!m_process || !m_process->is_running()) set_scheduler(scheduler); |     m_gcfg = m_config_reader(); | ||||||
|  |     // TODO: fill PrintObject cache
 | ||||||
|  |     m_dirty.store(false); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool SLAPrint::start() { | ||||||
|     if(!m_process) return false; |     if(!m_process) return false; | ||||||
| 
 | 
 | ||||||
|     m_process->schedule([this, scheduler](){ |     m_process->schedule([this](){ _start(); }); | ||||||
| 
 |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | namespace GUI { | ||||||
|  | 
 | ||||||
|  | void GLGizmoSlaSupports::on_deactivate() { | ||||||
|  |     std::cout << "gizmo deactivated " << std::endl; | ||||||
|  |     if(!m_model_object) return; | ||||||
|  | 
 | ||||||
|  |     SLAPrint& print = wxGetApp().plater()->sla_print(); | ||||||
|  |     print.synch(); | ||||||
|  | 
 | ||||||
|  |     print.start(); | ||||||
|  | 
 | ||||||
|  | //    sla::Controller supportctl;
 | ||||||
|  | //    std::cout << "Generating supports:" << std::endl;
 | ||||||
|  | 
 | ||||||
|  | //    // TODO: somehow get the global status indicator
 | ||||||
|  | //    supportctl.statuscb = [] (unsigned st, const std::string& msg) {
 | ||||||
|  | //        std::cout << st << "% "  << msg << std::endl;
 | ||||||
|  | //    };
 | ||||||
|  | 
 | ||||||
|  | //    TriangleMesh&& m = m_model_object->raw_mesh();
 | ||||||
|  | //    m.transform(m_model_object_matrix);
 | ||||||
|  | //    auto emesh = sla::to_eigenmesh(m);
 | ||||||
|  | 
 | ||||||
|  | //    sla::SupportConfig cfg;
 | ||||||
|  | //    sla::PointSet input = sla::support_points(*m_model_object, 0 /*instance*/);
 | ||||||
|  | 
 | ||||||
|  | //    sla::SLASupportTree stree(input, emesh, cfg, supportctl);
 | ||||||
|  | 
 | ||||||
|  | //    TriangleMesh output;
 | ||||||
|  | //    stree.merged_mesh(output);
 | ||||||
|  | 
 | ||||||
|  | //    _3DScene::reload_scene(m_parent.get_wxglcanvas(), false);
 | ||||||
|  | } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ class DynamicPrintConfig; | ||||||
| // implemented using a BackgroundSlicingProcess or something derived from that
 | // implemented using a BackgroundSlicingProcess or something derived from that
 | ||||||
| // The methods should be thread safe, obviously...
 | // The methods should be thread safe, obviously...
 | ||||||
| class BackgroundProcess { | class BackgroundProcess { | ||||||
|  |     std::function<void()> m_synchfn; | ||||||
| public: | public: | ||||||
| 
 | 
 | ||||||
|     virtual ~BackgroundProcess() {} |     virtual ~BackgroundProcess() {} | ||||||
|  | @ -33,14 +34,21 @@ public: | ||||||
|     /// worker thread
 |     /// worker thread
 | ||||||
|     virtual bool is_canceled() = 0; |     virtual bool is_canceled() = 0; | ||||||
| 
 | 
 | ||||||
|     /// Setting up a callback that transfers the input parameters to the worker
 |  | ||||||
|     /// thread. Appropriate synchronization has to be implemented here. A simple
 |  | ||||||
|     /// condition variable and mutex pair should do the job.
 |  | ||||||
|     virtual void on_input_changed(std::function<void()> synchfn) = 0; |  | ||||||
| 
 |  | ||||||
|     /// Determine the state of the background process. If something is running
 |     /// Determine the state of the background process. If something is running
 | ||||||
|     /// returns true. If no job is running, returns false.
 |     /// returns true. If no job is running, returns false.
 | ||||||
|     virtual bool is_running() = 0; |     virtual bool is_running() = 0; | ||||||
|  | 
 | ||||||
|  |     /// Trigger the synchronization of frontend/backend data
 | ||||||
|  |     virtual void input_changed() { | ||||||
|  |         m_synchfn();  // will just call the provided synch function itself.
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Setting up a callback that transfers the input parameters to the worker
 | ||||||
|  |     /// thread. Appropriate synchronization has to be implemented here. A simple
 | ||||||
|  |     /// condition variable and mutex pair should do the job.
 | ||||||
|  |     void on_input_changed(std::function<void()> synchfn) { | ||||||
|  |         m_synchfn = synchfn; | ||||||
|  |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  | @ -78,21 +86,20 @@ public: | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 
 | 
 | ||||||
|     // There will be a support tree for every instance of every ModelObject
 |     // Caching instance transformations and slices
 | ||||||
|     // because if the object is rotated differently, the support should also be
 |  | ||||||
|     // very different
 |  | ||||||
|     struct PrintObjectInstance { |     struct PrintObjectInstance { | ||||||
|         Transform3f tr; |         Transform3f tr; | ||||||
|         std::unique_ptr<sla::SLASupportTree> support_tree_ptr; |  | ||||||
|         SlicedSupports slice_cache; |         SlicedSupports slice_cache; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     using InstanceMap = std::unordered_map<ModelInstance*, PrintObjectInstance>; |     using InstanceMap = std::unordered_map<ModelInstance*, PrintObjectInstance>; | ||||||
| 
 | 
 | ||||||
|     // Every ModelObject will have its PrintObject here. It will contain the
 |     // Every ModelObject will have its PrintObject here. It will contain the
 | ||||||
|     // cached index-triangle structure (emesh) and the map of the instance cache
 |     // support tree geometry, the cached index-triangle structure (emesh) and
 | ||||||
|  |     // the map of the instance cache
 | ||||||
|     struct PrintObject { |     struct PrintObject { | ||||||
|         sla::EigenMesh3D emesh; |         sla::EigenMesh3D emesh; | ||||||
|  |         std::unique_ptr<sla::SLASupportTree> support_tree_ptr; | ||||||
|         InstanceMap instances; |         InstanceMap instances; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  | @ -113,19 +120,8 @@ private: | ||||||
|     std::shared_ptr<BackgroundProcess> m_process; // The scheduler
 |     std::shared_ptr<BackgroundProcess> m_process; // The scheduler
 | ||||||
| 
 | 
 | ||||||
|     // For now it will just stop the whole process and invalidate everything
 |     // For now it will just stop the whole process and invalidate everything
 | ||||||
|     void synch(); |  | ||||||
|     std::atomic<bool> m_dirty; |     std::atomic<bool> m_dirty; | ||||||
| 
 | 
 | ||||||
|     void set_scheduler(std::shared_ptr<BackgroundProcess> scheduler) { |  | ||||||
|         if(scheduler && !scheduler->is_running()) { |  | ||||||
|             m_process = scheduler; |  | ||||||
|             m_process->on_input_changed([this] { |  | ||||||
|                 /*synch(); */ |  | ||||||
|                 m_dirty.store(true); |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     enum Stages { |     enum Stages { | ||||||
|         IDLE, |         IDLE, | ||||||
|         FIND_ROTATION, |         FIND_ROTATION, | ||||||
|  | @ -142,22 +138,35 @@ private: | ||||||
| 
 | 
 | ||||||
|     static const std::string m_stage_labels[NUM_STAGES]; |     static const std::string m_stage_labels[NUM_STAGES]; | ||||||
| 
 | 
 | ||||||
|     bool run(); |     void _start(); | ||||||
|  |     void _synch(); | ||||||
|  | 
 | ||||||
| public: | public: | ||||||
| 
 | 
 | ||||||
|  |     void set_scheduler(std::shared_ptr<BackgroundProcess> scheduler) { | ||||||
|  |         if(scheduler && !scheduler->is_running()) { | ||||||
|  |             m_process = scheduler; | ||||||
|  |             m_process->on_input_changed([this] { | ||||||
|  |                 _synch(); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     SLAPrint(const Model * model, |     SLAPrint(const Model * model, | ||||||
|              std::function<SLAPrint::GlobalConfig(void)> cfgreader = |              std::function<SLAPrint::GlobalConfig(void)> cfgreader = | ||||||
|                     [](){ return SLAPrint::GlobalConfig(); }, |                     [](){ return SLAPrint::GlobalConfig(); }, | ||||||
|              std::shared_ptr<BackgroundProcess> scheduler = {}): |              std::shared_ptr<BackgroundProcess> scheduler = {}): | ||||||
|         m_model(model), m_config_reader(cfgreader) |         m_model(model), m_config_reader(cfgreader) | ||||||
|     { |     { | ||||||
|         synch(); |  | ||||||
|         m_dirty.store(false); |  | ||||||
|         set_scheduler(scheduler); |         set_scheduler(scheduler); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     void synch() { | ||||||
|  |         if(m_process) m_process->input_changed(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // This will start the calculation using the
 |     // This will start the calculation using the
 | ||||||
|     bool start(std::shared_ptr<BackgroundProcess> scheduler); |     bool start(); | ||||||
| 
 | 
 | ||||||
|     // Get the full support structure (including the base pool)
 |     // Get the full support structure (including the base pool)
 | ||||||
|     // This should block until the supports are not ready?
 |     // This should block until the supports are not ready?
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 tamasmeszaros
						tamasmeszaros