Merge branch 'master' into lm_sla_supports_auto
@ -82,6 +82,13 @@ if(WIN32)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
if (NOT CMAKE_OSX_DEPLOYMENT_TARGET)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "OS X Deployment target (SDK version)" FORCE)
|
||||
endif ()
|
||||
message(STATUS "Mac OS deployment target (SDK version): ${CMAKE_OSX_DEPLOYMENT_TARGET}")
|
||||
endif ()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
# Workaround for an old CMake, which does not understand CMAKE_CXX_STANDARD.
|
||||
add_compile_options(-std=c++11 -Wall -Wno-reorder)
|
||||
|
4
deps/deps-macos.cmake
vendored
@ -23,8 +23,8 @@ ExternalProject_Add(dep_boost
|
||||
variant=release
|
||||
threading=multi
|
||||
boost.locale.icu=off
|
||||
"cflags=cflags=-fPIC -mmacosx-version-min=${DEPS_OSX_TARGET}"
|
||||
"cxxflags=cxxflags=-fPIC -mmacosx-version-min=${DEPS_OSX_TARGET}"
|
||||
"cflags=-fPIC -mmacosx-version-min=${DEPS_OSX_TARGET}"
|
||||
"cxxflags=-fPIC -mmacosx-version-min=${DEPS_OSX_TARGET}"
|
||||
install
|
||||
INSTALL_COMMAND "" # b2 does that already
|
||||
)
|
||||
|
BIN
resources/icons/move_hover.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
resources/icons/move_off.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
resources/icons/move_on.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
resources/icons/scale_hover.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
resources/icons/scale_off.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
resources/icons/scale_on.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 15 KiB |
BIN
resources/localization/ko_KR/Slic3rPE.mo
Normal file
4679
resources/localization/ko_KR/Slic3rPE.po
Normal file
@ -1,10 +1,10 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <libslic3r.h>
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "SLABasePool.hpp"
|
||||
#include "benchmark.h"
|
||||
#include <libslic3r/libslic3r.h>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/SLABasePool.hpp>
|
||||
#include <libnest2d/tools/benchmark.h>
|
||||
|
||||
const std::string USAGE_STR = {
|
||||
"Usage: slabasebed stlfilename.stl"
|
||||
@ -28,7 +28,7 @@ int main(const int argc, const char *argv[]) {
|
||||
ExPolygons ground_slice;
|
||||
TriangleMesh basepool;
|
||||
|
||||
sla::ground_layer(model, ground_slice, 0.1f);
|
||||
sla::base_plate(model, ground_slice, 0.1f);
|
||||
|
||||
bench.start();
|
||||
sla::create_base_pool(ground_slice, basepool);
|
||||
|
104
src/libslic3r/Channel.hpp
Normal file
@ -0,0 +1,104 @@
|
||||
#ifndef slic3r_Channel_hpp_
|
||||
#define slic3r_Channel_hpp_
|
||||
|
||||
#include <deque>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
template<class T> class Channel
|
||||
{
|
||||
private:
|
||||
using UniqueLock = std::unique_lock<std::mutex>;
|
||||
using Queue = std::deque<T>;
|
||||
public:
|
||||
class Guard
|
||||
{
|
||||
public:
|
||||
Guard(UniqueLock lock, const Queue &queue) : m_lock(std::move(lock)), m_queue(queue) {}
|
||||
Guard(const Guard &other) = delete;
|
||||
Guard(Guard &&other) = delete;
|
||||
~Guard() {}
|
||||
|
||||
// Access trampolines
|
||||
size_t size() const noexcept { return m_queue.size(); }
|
||||
bool empty() const noexcept { return m_queue.empty(); }
|
||||
typename Queue::const_iterator begin() const noexcept { return m_queue.begin(); }
|
||||
typename Queue::const_iterator end() const noexcept { return m_queue.end(); }
|
||||
typename Queue::const_reference operator[](size_t i) const { return m_queue[i]; }
|
||||
|
||||
Guard& operator=(const Guard &other) = delete;
|
||||
Guard& operator=(Guard &&other) = delete;
|
||||
private:
|
||||
UniqueLock m_lock;
|
||||
const Queue &m_queue;
|
||||
};
|
||||
|
||||
|
||||
Channel() {}
|
||||
~Channel() {}
|
||||
|
||||
void push(const T& item, bool silent = false)
|
||||
{
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
m_queue.push_back(item);
|
||||
}
|
||||
if (! silent) { m_condition.notify_one(); }
|
||||
}
|
||||
|
||||
void push(T &&item, bool silent = false)
|
||||
{
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
m_queue.push_back(std::forward(item));
|
||||
}
|
||||
if (! silent) { m_condition.notify_one(); }
|
||||
}
|
||||
|
||||
T pop()
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
m_condition.wait(lock, [this]() { return !m_queue.empty(); });
|
||||
auto item = std::move(m_queue.front());
|
||||
m_queue.pop_front();
|
||||
return item;
|
||||
}
|
||||
|
||||
boost::optional<T> try_pop()
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
if (m_queue.empty()) {
|
||||
return boost::none;
|
||||
} else {
|
||||
auto item = std::move(m_queue.front());
|
||||
m_queue.pop();
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
// Unlocked observers
|
||||
// Thread unsafe! Keep in mind you need to re-verify the result after acquiring lock!
|
||||
size_t size() const noexcept { return m_queue.size(); }
|
||||
bool empty() const noexcept { return m_queue.empty(); }
|
||||
|
||||
Guard read() const
|
||||
{
|
||||
return Guard(UniqueLock(m_mutex), m_queue);
|
||||
}
|
||||
|
||||
private:
|
||||
Queue m_queue;
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_Channel_hpp_
|
@ -336,7 +336,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati
|
||||
return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(ratio_over);
|
||||
}
|
||||
|
||||
void ConfigBase::setenv_()
|
||||
void ConfigBase::setenv_() const
|
||||
{
|
||||
t_config_option_keys opt_keys = this->keys();
|
||||
for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) {
|
||||
|
@ -1113,7 +1113,7 @@ public:
|
||||
|
||||
double get_abs_value(const t_config_option_key &opt_key) const;
|
||||
double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const;
|
||||
void setenv_();
|
||||
void setenv_() const;
|
||||
void load(const std::string &file);
|
||||
void load_from_ini(const std::string &file);
|
||||
void load_from_gcode_file(const std::string &file);
|
||||
|
@ -9,7 +9,9 @@
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "SVG.hpp"
|
||||
|
||||
#if 0
|
||||
// Enable debugging and assert in this file.
|
||||
@ -756,8 +758,8 @@ void EdgeGrid::Grid::calculate_sdf()
|
||||
float search_radius = float(m_resolution<<1);
|
||||
m_signed_distance_field.assign(nrows * ncols, search_radius);
|
||||
// For each cell:
|
||||
for (size_t r = 0; r < m_rows; ++ r) {
|
||||
for (size_t c = 0; c < m_cols; ++ c) {
|
||||
for (int r = 0; r < (int)m_rows; ++ r) {
|
||||
for (int c = 0; c < (int)m_cols; ++ c) {
|
||||
const Cell &cell = m_cells[r * m_cols + c];
|
||||
// For each segment in the cell:
|
||||
for (size_t i = cell.begin; i != cell.end; ++ i) {
|
||||
@ -842,6 +844,8 @@ void EdgeGrid::Grid::calculate_sdf()
|
||||
#if 0
|
||||
static int iRun = 0;
|
||||
++ iRun;
|
||||
if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
|
||||
wxImage::AddHandler(new wxPNGHandler);
|
||||
//#ifdef SLIC3R_GUI
|
||||
{
|
||||
wxImage img(ncols, nrows);
|
||||
@ -1356,9 +1360,101 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) co
|
||||
return out;
|
||||
}
|
||||
|
||||
inline int segments_could_intersect(
|
||||
const Slic3r::Point &ip1, const Slic3r::Point &ip2,
|
||||
const Slic3r::Point &jp1, const Slic3r::Point &jp2)
|
||||
{
|
||||
Vec2i64 iv = (ip2 - ip1).cast<int64_t>();
|
||||
Vec2i64 vij1 = (jp1 - ip1).cast<int64_t>();
|
||||
Vec2i64 vij2 = (jp2 - ip1).cast<int64_t>();
|
||||
int64_t tij1 = cross2(iv, vij1);
|
||||
int64_t tij2 = cross2(iv, vij2);
|
||||
int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum
|
||||
int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0);
|
||||
return sij1 * sij2;
|
||||
}
|
||||
|
||||
inline bool segments_intersect(
|
||||
const Slic3r::Point &ip1, const Slic3r::Point &ip2,
|
||||
const Slic3r::Point &jp1, const Slic3r::Point &jp2)
|
||||
{
|
||||
return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 &&
|
||||
segments_could_intersect(jp1, jp2, ip1, ip2) <= 0;
|
||||
}
|
||||
|
||||
std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> EdgeGrid::Grid::intersecting_edges() const
|
||||
{
|
||||
std::vector<std::pair<ContourEdge, ContourEdge>> out;
|
||||
// For each cell:
|
||||
for (int r = 0; r < (int)m_rows; ++ r) {
|
||||
for (int c = 0; c < (int)m_cols; ++ c) {
|
||||
const Cell &cell = m_cells[r * m_cols + c];
|
||||
// For each pair of segments in the cell:
|
||||
for (size_t i = cell.begin; i != cell.end; ++ i) {
|
||||
const Slic3r::Points &ipts = *m_contours[m_cell_data[i].first];
|
||||
size_t ipt = m_cell_data[i].second;
|
||||
// End points of the line segment and their vector.
|
||||
const Slic3r::Point &ip1 = ipts[ipt];
|
||||
const Slic3r::Point &ip2 = ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1];
|
||||
for (size_t j = i + 1; j != cell.end; ++ j) {
|
||||
const Slic3r::Points &jpts = *m_contours[m_cell_data[j].first];
|
||||
size_t jpt = m_cell_data[j].second;
|
||||
// End points of the line segment and their vector.
|
||||
const Slic3r::Point &jp1 = jpts[jpt];
|
||||
const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1];
|
||||
if (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2))
|
||||
// Segments of the same contour share a common vertex.
|
||||
continue;
|
||||
if (segments_intersect(ip1, ip2, jp1, jp2)) {
|
||||
// The two segments intersect. Add them to the output.
|
||||
int jfirst = (&jpts < &ipts) || (&jpts == &ipts && jpt < ipt);
|
||||
out.emplace_back(jfirst ?
|
||||
std::make_pair(std::make_pair(&ipts, ipt), std::make_pair(&jpts, jpt)) :
|
||||
std::make_pair(std::make_pair(&ipts, ipt), std::make_pair(&jpts, jpt)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Slic3r::sort_remove_duplicates(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool EdgeGrid::Grid::has_intersecting_edges() const
|
||||
{
|
||||
// For each cell:
|
||||
for (int r = 0; r < (int)m_rows; ++ r) {
|
||||
for (int c = 0; c < (int)m_cols; ++ c) {
|
||||
const Cell &cell = m_cells[r * m_cols + c];
|
||||
// For each pair of segments in the cell:
|
||||
for (size_t i = cell.begin; i != cell.end; ++ i) {
|
||||
const Slic3r::Points &ipts = *m_contours[m_cell_data[i].first];
|
||||
size_t ipt = m_cell_data[i].second;
|
||||
// End points of the line segment and their vector.
|
||||
const Slic3r::Point &ip1 = ipts[ipt];
|
||||
const Slic3r::Point &ip2 = ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1];
|
||||
for (size_t j = i + 1; j != cell.end; ++ j) {
|
||||
const Slic3r::Points &jpts = *m_contours[m_cell_data[j].first];
|
||||
size_t jpt = m_cell_data[j].second;
|
||||
// End points of the line segment and their vector.
|
||||
const Slic3r::Point &jp1 = jpts[jpt];
|
||||
const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1];
|
||||
if (! (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) &&
|
||||
segments_intersect(ip1, ip2, jp1, jp2))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path)
|
||||
{
|
||||
if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
|
||||
wxImage::AddHandler(new wxPNGHandler);
|
||||
|
||||
unsigned int w = (bbox.max(0) - bbox.min(0) + resolution - 1) / resolution;
|
||||
unsigned int h = (bbox.max(1) - bbox.min(1) + resolution - 1) / resolution;
|
||||
wxImage img(w, h);
|
||||
@ -1450,4 +1546,59 @@ void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coo
|
||||
}
|
||||
#endif /* SLIC3R_GUI */
|
||||
|
||||
// Find all pairs of intersectiong edges from the set of polygons.
|
||||
std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersecting_edges(const Polygons &polygons)
|
||||
{
|
||||
double len = 0;
|
||||
size_t cnt = 0;
|
||||
BoundingBox bbox;
|
||||
for (const Polygon &poly : polygons) {
|
||||
if (poly.points.size() < 2)
|
||||
continue;
|
||||
for (size_t i = 0; i < poly.points.size(); ++ i) {
|
||||
bbox.merge(poly.points[i]);
|
||||
size_t j = (i == 0) ? (poly.points.size() - 1) : i - 1;
|
||||
len += (poly.points[j] - poly.points[i]).cast<double>().norm();
|
||||
++ cnt;
|
||||
}
|
||||
}
|
||||
len /= double(cnt);
|
||||
bbox.offset(20);
|
||||
EdgeGrid::Grid grid;
|
||||
grid.set_bbox(bbox);
|
||||
grid.create(polygons, len);
|
||||
return grid.intersecting_edges();
|
||||
}
|
||||
|
||||
// Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG.
|
||||
void export_intersections_to_svg(const std::string &filename, const Polygons &polygons)
|
||||
{
|
||||
std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersections = intersecting_edges(polygons);
|
||||
BoundingBox bbox = get_extents(polygons);
|
||||
SVG svg(filename.c_str(), bbox);
|
||||
svg.draw(union_ex(polygons), "gray", 0.25f);
|
||||
svg.draw_outline(polygons, "black");
|
||||
std::set<const Points*> intersecting_contours;
|
||||
for (const std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge> &ie : intersections) {
|
||||
intersecting_contours.insert(ie.first.first);
|
||||
intersecting_contours.insert(ie.second.first);
|
||||
}
|
||||
// Highlight the contours with intersections.
|
||||
coord_t line_width = coord_t(scale_(0.01));
|
||||
for (const Points *ic : intersecting_contours) {
|
||||
svg.draw_outline(Polygon(*ic), "green");
|
||||
svg.draw_outline(Polygon(*ic), "black", line_width);
|
||||
}
|
||||
// Paint the intersections.
|
||||
for (const std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge> &intersecting_edges : intersections) {
|
||||
auto edge = [](const EdgeGrid::Grid::ContourEdge &e) {
|
||||
return Line(e.first->at(e.second),
|
||||
e.first->at((e.second + 1 == e.first->size()) ? 0 : e.second + 1));
|
||||
};
|
||||
svg.draw(edge(intersecting_edges.first), "red", line_width);
|
||||
svg.draw(edge(intersecting_edges.second), "red", line_width);
|
||||
}
|
||||
svg.Close();
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -133,7 +133,7 @@ void FillGyroid::_fill_surface_single(
|
||||
// no rotation is supported for this infill pattern (yet)
|
||||
BoundingBox bb = expolygon.contour.bounding_box();
|
||||
// Density adjusted to have a good %of weight.
|
||||
double density_adjusted = std::max(0., params.density * 2.);
|
||||
double density_adjusted = std::max(0., params.density * 2.44);
|
||||
// Distance between the gyroid waves in scaled coordinates.
|
||||
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include "PostProcessor.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
namespace Slic3r {
|
||||
@ -25,9 +27,10 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config
|
||||
{
|
||||
if (config.post_process.values.empty())
|
||||
return;
|
||||
//config.setenv_();
|
||||
|
||||
config.setenv_();
|
||||
auto gcode_file = boost::filesystem::path(path);
|
||||
if (!boost::filesystem::exists(gcode_file))
|
||||
if (! boost::filesystem::exists(gcode_file))
|
||||
throw std::runtime_error(std::string("Post-processor can't find exported gcode file"));
|
||||
|
||||
for (std::string script: config.post_process.values) {
|
||||
|
@ -545,7 +545,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
|
||||
m_print_brim = true;
|
||||
|
||||
// Ask our writer about how much material was consumed:
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
if (m_current_tool < m_used_filament_length.size())
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
ToolChangeResult result;
|
||||
result.priming = true;
|
||||
@ -698,7 +699,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo
|
||||
m_print_brim = false; // Mark the brim as extruded
|
||||
|
||||
// Ask our writer about how much material was consumed:
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
if (m_current_tool < m_used_filament_length.size())
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
ToolChangeResult result;
|
||||
result.priming = false;
|
||||
@ -868,7 +870,8 @@ void WipeTowerPrusaMM::toolchange_Change(
|
||||
material_type new_material)
|
||||
{
|
||||
// Ask the writer about how much of the old filament we consumed:
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
if (m_current_tool < m_used_filament_length.size())
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
// Speed override for the material. Go slow for flex and soluble materials.
|
||||
int speed_override;
|
||||
|
@ -521,10 +521,14 @@ void Model::adjust_min_z()
|
||||
unsigned int Model::get_auto_extruder_id(unsigned int max_extruders)
|
||||
{
|
||||
unsigned int id = s_auto_extruder_id;
|
||||
|
||||
if (++s_auto_extruder_id > max_extruders)
|
||||
if (id > max_extruders) {
|
||||
// The current counter is invalid, likely due to switching the printer profiles
|
||||
// to a profile with a lower number of extruders.
|
||||
reset_auto_extruder_id();
|
||||
|
||||
id = s_auto_extruder_id;
|
||||
} else if (++ s_auto_extruder_id > max_extruders) {
|
||||
reset_auto_extruder_id();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
|
@ -107,6 +107,23 @@ extern BoundingBox get_extents(const MultiPoint &mp);
|
||||
extern BoundingBox get_extents_rotated(const std::vector<Point> &points, double angle);
|
||||
extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle);
|
||||
|
||||
inline double length(const Points &pts) {
|
||||
double total = 0;
|
||||
if (! pts.empty()) {
|
||||
auto it = pts.begin();
|
||||
for (auto it_prev = it ++; it != pts.end(); ++ it, ++ it_prev)
|
||||
total += (*it - *it_prev).cast<double>().norm();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
inline double area(const Points &polygon) {
|
||||
double area = 0.;
|
||||
for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++)
|
||||
area += double(polygon[i](0) + polygon[j](0)) * double(polygon[i](1) - polygon[j](1));
|
||||
return area;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
|
@ -187,6 +187,24 @@ public:
|
||||
m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), std::move(value)));
|
||||
}
|
||||
|
||||
// Erase a data point equal to value. (ValueType has to declare the operator==).
|
||||
// Returns true if the data point equal to value was found and removed.
|
||||
bool erase(const ValueType &value) {
|
||||
const Point *pt = m_point_accessor(value);
|
||||
if (pt != nullptr) {
|
||||
// Range of fragment starts around grid_corner, close to pt.
|
||||
auto range = m_map.equal_range(Point((*pt)(0)>>m_grid_log2, (*pt)(1)>>m_grid_log2));
|
||||
// Remove the first item.
|
||||
for (auto it = range.first; it != range.second; ++ it) {
|
||||
if (it->second == value) {
|
||||
m_map.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return a pair of <ValueType*, distance_squared>
|
||||
std::pair<const ValueType*, double> find(const Vec2crd &pt) {
|
||||
// Iterate over 4 closest grid cells around pt,
|
||||
@ -214,7 +232,7 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
return (value_min != nullptr && dist_min < coordf_t(m_search_radius * m_search_radius)) ?
|
||||
return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ?
|
||||
std::make_pair(value_min, dist_min) :
|
||||
std::make_pair(nullptr, std::numeric_limits<double>::max());
|
||||
}
|
||||
|
@ -81,8 +81,8 @@ extern BoundingBox get_extents(const Polylines &polylines);
|
||||
|
||||
inline double total_length(const Polylines &polylines) {
|
||||
double total = 0;
|
||||
for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it)
|
||||
total += it->length();
|
||||
for (const Polyline &pl : polylines)
|
||||
total += pl.length();
|
||||
return total;
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
#include "PrintExport.hpp"
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
#define L(s) Slic3r::I18N::translate(s)
|
||||
@ -281,16 +283,17 @@ bool Print::is_step_done(PrintObjectStep step) const
|
||||
std::vector<unsigned int> Print::object_extruders() const
|
||||
{
|
||||
std::vector<unsigned int> extruders;
|
||||
extruders.reserve(m_regions.size() * 3);
|
||||
|
||||
for (PrintRegion* region : m_regions) {
|
||||
for (const PrintRegion *region : m_regions) {
|
||||
// these checks reflect the same logic used in the GUI for enabling/disabling
|
||||
// extruder selection fields
|
||||
if (region->config().perimeters.value > 0 || m_config.brim_width.value > 0)
|
||||
extruders.push_back(region->config().perimeter_extruder - 1);
|
||||
extruders.emplace_back(region->config().perimeter_extruder - 1);
|
||||
if (region->config().fill_density.value > 0)
|
||||
extruders.push_back(region->config().infill_extruder - 1);
|
||||
extruders.emplace_back(region->config().infill_extruder - 1);
|
||||
if (region->config().top_solid_layers.value > 0 || region->config().bottom_solid_layers.value > 0)
|
||||
extruders.push_back(region->config().solid_infill_extruder - 1);
|
||||
extruders.emplace_back(region->config().solid_infill_extruder - 1);
|
||||
}
|
||||
|
||||
sort_remove_duplicates(extruders);
|
||||
@ -480,14 +483,6 @@ bool Print::apply_config(DynamicPrintConfig config)
|
||||
PrintObjectConfig new_config = this->default_object_config();
|
||||
// we override the new config with object-specific options
|
||||
normalize_and_apply_config(new_config, object->model_object()->config);
|
||||
// Force a refresh of a variable layer height profile at the PrintObject if it is not valid.
|
||||
if (! object->layer_height_profile_valid) {
|
||||
// The layer_height_profile is not valid for some reason (updated by the user or invalidated due to some option change).
|
||||
// Invalidate the slicing step, which in turn invalidates everything.
|
||||
object->invalidate_step(posSlice);
|
||||
// Trigger recalculation.
|
||||
invalidated = true;
|
||||
}
|
||||
// check whether the new config is different from the current one
|
||||
t_config_option_keys diff = object->config().diff(new_config);
|
||||
object->config_apply_only(new_config, diff, true);
|
||||
@ -567,8 +562,7 @@ exit_for_rearrange_regions:
|
||||
|
||||
// Always make sure that the layer_height_profiles are set, as they should not be modified from the worker threads.
|
||||
for (PrintObject *object : m_objects)
|
||||
if (! object->layer_height_profile_valid)
|
||||
object->update_layer_height_profile();
|
||||
object->update_layer_height_profile();
|
||||
|
||||
return invalidated;
|
||||
}
|
||||
@ -1141,6 +1135,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
|
||||
// Always make sure that the layer_height_profiles are set, as they should not be modified from the worker threads.
|
||||
for (PrintObject *object : m_objects)
|
||||
if (! object->layer_height_profile_valid)
|
||||
// No need to call the next line as the step should already be invalidated above.
|
||||
// update_apply_status(object->invalidate_step(posSlice));
|
||||
object->update_layer_height_profile();
|
||||
|
||||
//FIXME there may be a race condition with the G-code export running at the background thread.
|
||||
@ -1165,6 +1161,7 @@ bool Print::has_skirt() const
|
||||
|| this->has_infinite_skirt();
|
||||
}
|
||||
|
||||
// Precondition: Print::validate() requires the Print::apply() to be called its invocation.
|
||||
std::string Print::validate() const
|
||||
{
|
||||
if (m_objects.empty())
|
||||
@ -1231,8 +1228,8 @@ std::string Print::validate() const
|
||||
}
|
||||
|
||||
if (this->has_wipe_tower() && ! m_objects.empty()) {
|
||||
if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfMarlin)
|
||||
return L("The Wipe Tower is currently only supported for the Marlin and RepRap/Sprinter G-code flavors.");
|
||||
if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin)
|
||||
return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors.");
|
||||
if (! m_config.use_relative_e_distances)
|
||||
return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).");
|
||||
SlicingParameters slicing_params0 = m_objects.front()->slicing_parameters();
|
||||
@ -1253,12 +1250,10 @@ std::string Print::validate() const
|
||||
return L("The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance");
|
||||
if (! equal_layering(slicing_params, slicing_params0))
|
||||
return L("The Wipe Tower is only supported for multiple objects if they are sliced equally.");
|
||||
bool was_layer_height_profile_valid = object->layer_height_profile_valid;
|
||||
object->update_layer_height_profile();
|
||||
object->layer_height_profile_valid = was_layer_height_profile_valid;
|
||||
|
||||
if ( m_config.variable_layer_height ) { // comparing layer height profiles
|
||||
bool failed = false;
|
||||
// layer_height_profile should be set by Print::apply().
|
||||
if (tallest_object->layer_height_profile.size() >= object->layer_height_profile.size() ) {
|
||||
int i = 0;
|
||||
while ( i < object->layer_height_profile.size() && i < tallest_object->layer_height_profile.size()) {
|
||||
@ -1867,5 +1862,96 @@ int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion
|
||||
std::max<int>(region.config().perimeter_extruder.value - 1, 0);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
std::string Print::output_filename() const
|
||||
{
|
||||
// Set the placeholders for the data know first after the G-code export is finished.
|
||||
// These values will be just propagated into the output file name.
|
||||
DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders();
|
||||
return this->PrintBase::output_filename(m_config.output_filename_format.value, "gcode", &config);
|
||||
}
|
||||
|
||||
// Shorten the dhms time by removing the seconds, rounding the dhm to full minutes
|
||||
// and removing spaces.
|
||||
static std::string short_time(const std::string &time)
|
||||
{
|
||||
// Parse the dhms time format.
|
||||
int days = 0;
|
||||
int hours = 0;
|
||||
int minutes = 0;
|
||||
int seconds = 0;
|
||||
if (time.find('d') != std::string::npos)
|
||||
::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds);
|
||||
else if (time.find('h') != std::string::npos)
|
||||
::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds);
|
||||
else if (time.find('m') != std::string::npos)
|
||||
::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds);
|
||||
else if (time.find('s') != std::string::npos)
|
||||
::sscanf(time.c_str(), "%ds", &seconds);
|
||||
// Round to full minutes.
|
||||
if (days + hours + minutes > 0 && seconds >= 30) {
|
||||
if (++ minutes == 60) {
|
||||
minutes = 0;
|
||||
if (++ hours == 24) {
|
||||
hours = 0;
|
||||
++ days;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Format the dhm time.
|
||||
char buffer[64];
|
||||
if (days > 0)
|
||||
::sprintf(buffer, "%dd%dh%dm", days, hours, minutes);
|
||||
else if (hours > 0)
|
||||
::sprintf(buffer, "%dh%dm", hours, minutes);
|
||||
else if (minutes > 0)
|
||||
::sprintf(buffer, "%dm", minutes);
|
||||
else
|
||||
::sprintf(buffer, "%ds", seconds);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
DynamicConfig PrintStatistics::config() const
|
||||
{
|
||||
DynamicConfig config;
|
||||
std::string normal_print_time = short_time(this->estimated_normal_print_time);
|
||||
std::string silent_print_time = short_time(this->estimated_silent_print_time);
|
||||
config.set_key_value("print_time", new ConfigOptionString(normal_print_time));
|
||||
config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time));
|
||||
config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time));
|
||||
config.set_key_value("used_filament", new ConfigOptionFloat (this->total_used_filament));
|
||||
config.set_key_value("extruded_volume", new ConfigOptionFloat (this->total_extruded_volume));
|
||||
config.set_key_value("total_cost", new ConfigOptionFloat (this->total_cost));
|
||||
config.set_key_value("total_weight", new ConfigOptionFloat (this->total_weight));
|
||||
config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat (this->total_wipe_tower_cost));
|
||||
config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat (this->total_wipe_tower_filament));
|
||||
return config;
|
||||
}
|
||||
|
||||
DynamicConfig PrintStatistics::placeholders()
|
||||
{
|
||||
DynamicConfig config;
|
||||
for (const std::string &key : {
|
||||
"print_time", "normal_print_time", "silent_print_time",
|
||||
"used_filament", "extruded_volume", "total_cost", "total_weight",
|
||||
"total_wipe_tower_cost", "total_wipe_tower_filament"})
|
||||
config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}"));
|
||||
return config;
|
||||
}
|
||||
|
||||
std::string PrintStatistics::finalize_output_path(const std::string &path_in) const
|
||||
{
|
||||
std::string final_path;
|
||||
try {
|
||||
boost::filesystem::path path(path_in);
|
||||
DynamicConfig cfg = this->config();
|
||||
PlaceholderParser pp;
|
||||
std::string new_stem = pp.process(path.stem().string(), 0, &cfg);
|
||||
final_path = (path.parent_path() / (new_stem + path.extension().string())).string();
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to apply the print statistics to the export file name: " << ex.what();
|
||||
final_path = path_in;
|
||||
}
|
||||
return final_path;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -78,7 +78,6 @@ private: // Prevents erroneous use by other classes.
|
||||
public:
|
||||
// vector of (vectors of volume ids), indexed by region_id
|
||||
std::vector<std::vector<int>> region_volumes;
|
||||
t_layer_height_ranges layer_height_ranges;
|
||||
|
||||
// Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers.
|
||||
// The pairs of <z, layer_height> are packed into a 1D array to simplify handling by the Perl XS.
|
||||
@ -250,6 +249,13 @@ struct PrintStatistics
|
||||
double total_wipe_tower_filament;
|
||||
std::map<size_t, float> filament_stats;
|
||||
|
||||
// Config with the filled in print statistics.
|
||||
DynamicConfig config() const;
|
||||
// Config with the statistics keys populated with placeholder strings.
|
||||
static DynamicConfig placeholders();
|
||||
// Replace the print statistics placeholders in the path.
|
||||
std::string finalize_output_path(const std::string &path_in) const;
|
||||
|
||||
void clear() {
|
||||
estimated_normal_print_time.clear();
|
||||
estimated_silent_print_time.clear();
|
||||
@ -298,7 +304,10 @@ public:
|
||||
|
||||
// methods for handling state
|
||||
bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); }
|
||||
// Returns true if an object step is done on all objects and there's at least one object.
|
||||
bool is_step_done(PrintObjectStep step) const;
|
||||
// Returns true if the last step was finished with success.
|
||||
bool finished() const override { return this->is_step_done(psGCodeExport); }
|
||||
|
||||
bool has_infinite_skirt() const;
|
||||
bool has_skirt() const;
|
||||
@ -343,8 +352,7 @@ public:
|
||||
bool has_wipe_tower() const;
|
||||
const WipeTowerData& wipe_tower_data() const { return m_wipe_tower_data; }
|
||||
|
||||
std::string output_filename() const override
|
||||
{ return this->PrintBase::output_filename(m_config.output_filename_format.value, "gcode"); }
|
||||
std::string output_filename() const override;
|
||||
|
||||
// Accessed by SupportMaterial
|
||||
const PrintRegion* get_region(size_t idx) const { return m_regions[idx]; }
|
||||
|
@ -48,12 +48,14 @@ void PrintBase::update_object_placeholders()
|
||||
}
|
||||
}
|
||||
|
||||
std::string PrintBase::output_filename(const std::string &format, const std::string &default_ext) const
|
||||
std::string PrintBase::output_filename(const std::string &format, const std::string &default_ext, const DynamicConfig *config_override) const
|
||||
{
|
||||
DynamicConfig cfg_timestamp;
|
||||
PlaceholderParser::update_timestamp(cfg_timestamp);
|
||||
DynamicConfig cfg;
|
||||
if (config_override != nullptr)
|
||||
cfg = *config_override;
|
||||
PlaceholderParser::update_timestamp(cfg);
|
||||
try {
|
||||
boost::filesystem::path filename = this->placeholder_parser().process(format, 0, &cfg_timestamp);
|
||||
boost::filesystem::path filename = this->placeholder_parser().process(format, 0, &cfg);
|
||||
if (filename.extension().empty())
|
||||
filename = boost::filesystem::change_extension(filename, default_ext);
|
||||
return filename.string();
|
||||
|
@ -285,6 +285,8 @@ public:
|
||||
void cancel_internal() { m_cancel_status = CANCELED_INTERNAL; }
|
||||
// Cancel the running computation. Stop execution of all the background threads.
|
||||
void restart() { m_cancel_status = NOT_CANCELED; }
|
||||
// Returns true if the last step was finished with success.
|
||||
virtual bool finished() const = 0;
|
||||
|
||||
const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; }
|
||||
PlaceholderParser& placeholder_parser() { return m_placeholder_parser; }
|
||||
@ -305,7 +307,7 @@ protected:
|
||||
void throw_if_canceled() const { if (m_cancel_status) throw CanceledException(); }
|
||||
|
||||
// To be called by this->output_filename() with the format string pulled from the configuration layer.
|
||||
std::string output_filename(const std::string &format, const std::string &default_ext) const;
|
||||
std::string output_filename(const std::string &format, const std::string &default_ext, const DynamicConfig *config_override = nullptr) const;
|
||||
// Update "scale", "input_filename", "input_filename_base" placeholders from the current printable ModelObjects.
|
||||
void update_object_placeholders();
|
||||
|
||||
|
@ -1297,10 +1297,11 @@ void PrintConfigDef::init_fff_params()
|
||||
def->default_value = new ConfigOptionString("");
|
||||
|
||||
def = this->add("printhost_cafile", coString);
|
||||
def->label = "HTTPS CA file";
|
||||
def->label = "HTTPS CA File";
|
||||
def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
|
||||
"If left blank, the default OS CA certificate repository is used.";
|
||||
def->cli = "printhost-cafile=s";
|
||||
def->mode = comAdvanced;
|
||||
def->default_value = new ConfigOptionString("");
|
||||
|
||||
def = this->add("print_host", coString);
|
||||
@ -2388,6 +2389,15 @@ void PrintConfigDef::init_sla_params()
|
||||
def->min = 100;
|
||||
def->default_value = new ConfigOptionInt(1440);
|
||||
|
||||
def = this->add("display_orientation", coEnum);
|
||||
def->label = L("Display orientation");
|
||||
def->tooltip = L("Display orientation");
|
||||
def->cli = "display-orientation=s";
|
||||
def->enum_keys_map = &ConfigOptionEnum<SLADisplayOrientation>::get_enum_values();
|
||||
def->enum_values.push_back("Landscape");
|
||||
def->enum_values.push_back("Portrait");
|
||||
def->default_value = new ConfigOptionEnum<SLADisplayOrientation>(sladoPortrait);
|
||||
|
||||
def = this->add("printer_correction", coFloats);
|
||||
def->full_label = L("Printer scaling correction");
|
||||
def->tooltip = L("Printer scaling correction");
|
||||
|
@ -56,6 +56,11 @@ enum FilamentType {
|
||||
ftPLA, ftABS, ftPET, ftHIPS, ftFLEX, ftSCAFF, ftEDGE, ftNGEN, ftPVA
|
||||
};
|
||||
|
||||
enum SLADisplayOrientation {
|
||||
sladoLandscape,
|
||||
sladoPortrait
|
||||
};
|
||||
|
||||
template<> inline const t_config_enum_values& ConfigOptionEnum<PrinterTechnology>::get_enum_values() {
|
||||
static t_config_enum_values keys_map;
|
||||
if (keys_map.empty()) {
|
||||
@ -148,6 +153,15 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<FilamentType>::ge
|
||||
return keys_map;
|
||||
}
|
||||
|
||||
template<> inline const t_config_enum_values& ConfigOptionEnum<SLADisplayOrientation>::get_enum_values() {
|
||||
static const t_config_enum_values keys_map = {
|
||||
{ "Landscape", sladoLandscape},
|
||||
{ "Portrait", sladoPortrait}
|
||||
};
|
||||
|
||||
return keys_map;
|
||||
}
|
||||
|
||||
// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs.
|
||||
// Does not store the actual values, but defines default values.
|
||||
class PrintConfigDef : public ConfigDef
|
||||
@ -1035,6 +1049,7 @@ public:
|
||||
ConfigOptionFloat display_height;
|
||||
ConfigOptionInt display_pixels_x;
|
||||
ConfigOptionInt display_pixels_y;
|
||||
ConfigOptionEnum<SLADisplayOrientation> display_orientation;
|
||||
ConfigOptionFloats printer_correction;
|
||||
protected:
|
||||
void initialize(StaticCacheBase &cache, const char *base_ptr)
|
||||
@ -1046,6 +1061,7 @@ protected:
|
||||
OPT_PTR(display_height);
|
||||
OPT_PTR(display_pixels_x);
|
||||
OPT_PTR(display_pixels_y);
|
||||
OPT_PTR(display_orientation);
|
||||
OPT_PTR(printer_correction);
|
||||
}
|
||||
};
|
||||
|
@ -31,8 +31,6 @@ template<FilePrinterFormat format>
|
||||
class FilePrinter {
|
||||
public:
|
||||
|
||||
void print_config(const Print&);
|
||||
|
||||
// Draw an ExPolygon which is a polygon inside a slice on the specified layer.
|
||||
void draw_polygon(const ExPolygon& p, unsigned lyr);
|
||||
|
||||
@ -118,6 +116,7 @@ template<> class FilePrinter<FilePrinterFormat::SLA_PNGZIP>
|
||||
Raster::PixelDim m_pxdim;
|
||||
double m_exp_time_s = .0, m_exp_time_first_s = .0;
|
||||
double m_layer_height = .0;
|
||||
Raster::Origin m_o = Raster::Origin::TOP_LEFT;
|
||||
|
||||
std::string createIniContent(const std::string& projectname) {
|
||||
double layer_height = m_layer_height;
|
||||
@ -147,19 +146,41 @@ template<> class FilePrinter<FilePrinterFormat::SLA_PNGZIP>
|
||||
+layerh_str+"+printer=DWARF3\n";
|
||||
}
|
||||
|
||||
// Change this to TOP_LEFT if you want correct PNG orientation
|
||||
static const Raster::Origin ORIGIN = Raster::Origin::BOTTOM_LEFT;
|
||||
|
||||
public:
|
||||
|
||||
enum RasterOrientation {
|
||||
RO_LANDSCAPE,
|
||||
RO_PORTRAIT
|
||||
};
|
||||
|
||||
// We will play with the raster's coordinate origin parameter. When the
|
||||
// printer should print in landscape mode it should have the Y axis flipped
|
||||
// because the layers should be displayed upside down. PNG has its
|
||||
// coordinate origin in the top-left corner so normally the Raster objects
|
||||
// should be instantiated with the TOP_LEFT flag. However, in landscape mode
|
||||
// we do want the pictures to be upside down so we will make BOTTOM_LEFT
|
||||
// type rasters and the PNG format will do the flipping automatically.
|
||||
|
||||
// In case of portrait images, we have to rotate the image by a 90 degrees
|
||||
// and flip the y axis. To get the correct upside-down orientation of the
|
||||
// slice images, we can flip the x and y coordinates of the input polygons
|
||||
// and do the Y flipping of the image. This will generate the correct
|
||||
// orientation in portrait mode.
|
||||
|
||||
inline FilePrinter(double width_mm, double height_mm,
|
||||
unsigned width_px, unsigned height_px,
|
||||
double layer_height,
|
||||
double exp_time, double exp_time_first):
|
||||
double exp_time, double exp_time_first,
|
||||
RasterOrientation ro = RO_PORTRAIT):
|
||||
m_res(width_px, height_px),
|
||||
m_pxdim(width_mm/width_px, height_mm/height_px),
|
||||
m_exp_time_s(exp_time),
|
||||
m_exp_time_first_s(exp_time_first),
|
||||
m_layer_height(layer_height)
|
||||
m_layer_height(layer_height),
|
||||
|
||||
// Here is the trick with the orientation.
|
||||
m_o(ro == RO_LANDSCAPE? Raster::Origin::BOTTOM_LEFT :
|
||||
Raster::Origin::TOP_LEFT )
|
||||
{
|
||||
}
|
||||
|
||||
@ -179,12 +200,12 @@ public:
|
||||
|
||||
inline void begin_layer(unsigned lyr) {
|
||||
if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1);
|
||||
m_layers_rst[lyr].first.reset(m_res, m_pxdim, ORIGIN);
|
||||
m_layers_rst[lyr].first.reset(m_res, m_pxdim, m_o);
|
||||
}
|
||||
|
||||
inline void begin_layer() {
|
||||
m_layers_rst.emplace_back();
|
||||
m_layers_rst.front().first.reset(m_res, m_pxdim, ORIGIN);
|
||||
m_layers_rst.front().first.reset(m_res, m_pxdim, m_o);
|
||||
}
|
||||
|
||||
inline void finish_layer(unsigned lyr_id) {
|
||||
@ -206,29 +227,35 @@ public:
|
||||
inline void save(const std::string& path) {
|
||||
try {
|
||||
LayerWriter<LyrFmt> writer(path);
|
||||
if(!writer.is_ok()) return;
|
||||
|
||||
std::string project = writer.get_name();
|
||||
|
||||
writer.next_entry("config.ini");
|
||||
if(!writer.is_ok()) return;
|
||||
|
||||
writer << createIniContent(project);
|
||||
|
||||
for(unsigned i = 0; i < m_layers_rst.size(); i++) {
|
||||
for(unsigned i = 0; i < m_layers_rst.size() && writer.is_ok(); i++)
|
||||
{
|
||||
if(m_layers_rst[i].second.rdbuf()->in_avail() > 0) {
|
||||
char lyrnum[6];
|
||||
std::sprintf(lyrnum, "%.5d", i);
|
||||
auto zfilename = project + lyrnum + ".png";
|
||||
writer.next_entry(zfilename);
|
||||
|
||||
if(!writer.is_ok()) break;
|
||||
|
||||
writer << m_layers_rst[i].second.str();
|
||||
// writer << m_layers_rst[i].second.rdbuf();
|
||||
// we can keep the date for later calls of this method
|
||||
//m_layers_rst[i].second.str("");
|
||||
}
|
||||
}
|
||||
|
||||
writer.close();
|
||||
} catch(std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||
return;
|
||||
// Rethrow the exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,6 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta
|
||||
this->set_copies(copies);
|
||||
}
|
||||
|
||||
this->layer_height_ranges = model_object->layer_height_ranges;
|
||||
this->layer_height_profile = model_object->layer_height_profile;
|
||||
}
|
||||
|
||||
@ -1109,7 +1108,7 @@ void PrintObject::discover_vertical_shells()
|
||||
#if 1
|
||||
// Intentionally inflate a bit more than how much the region has been shrunk,
|
||||
// so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill).
|
||||
shell = offset2(shell, - 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare);
|
||||
shell = offset(offset_ex(union_ex(shell), - 0.5f * min_perimeter_infill_spacing), 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare);
|
||||
if (shell.empty())
|
||||
continue;
|
||||
#else
|
||||
@ -1330,7 +1329,7 @@ bool PrintObject::update_layer_height_profile(std::vector<coordf_t> &layer_heigh
|
||||
bool updated = false;
|
||||
|
||||
// If the layer height profile is not set, try to use the one stored at the ModelObject.
|
||||
if (layer_height_profile.empty() && layer_height_profile.data() != this->model_object()->layer_height_profile.data()) {
|
||||
if (layer_height_profile.empty()) {
|
||||
layer_height_profile = this->model_object()->layer_height_profile;
|
||||
updated = true;
|
||||
}
|
||||
@ -1347,10 +1346,9 @@ bool PrintObject::update_layer_height_profile(std::vector<coordf_t> &layer_heigh
|
||||
if (layer_height_profile.empty()) {
|
||||
if (0)
|
||||
// if (this->layer_height_profile.empty())
|
||||
layer_height_profile = layer_height_profile_adaptive(slicing_params, this->layer_height_ranges,
|
||||
this->model_object()->volumes);
|
||||
layer_height_profile = layer_height_profile_adaptive(slicing_params, this->model_object()->layer_height_ranges, this->model_object()->volumes);
|
||||
else
|
||||
layer_height_profile = layer_height_profile_from_ranges(slicing_params, this->layer_height_ranges);
|
||||
layer_height_profile = layer_height_profile_from_ranges(slicing_params, this->model_object()->layer_height_ranges);
|
||||
updated = true;
|
||||
}
|
||||
return updated;
|
||||
|
@ -45,15 +45,20 @@ private:
|
||||
TRawRenderer m_raw_renderer;
|
||||
TRendererAA m_renderer;
|
||||
Origin m_o;
|
||||
std::function<void(agg::path_storage&)> m_flipy = [](agg::path_storage&) {};
|
||||
|
||||
inline void flipy(agg::path_storage& path) const {
|
||||
path.flip_y(0, m_resolution.height_px);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd,
|
||||
Origin o):
|
||||
m_resolution(res), m_pxdim(pd),
|
||||
m_buf(res.pixels()),
|
||||
m_rbuf(reinterpret_cast<TPixelRenderer::value_type*>(m_buf.data()),
|
||||
res.width_px, res.height_px,
|
||||
res.width_px*TPixelRenderer::num_components),
|
||||
int(res.width_px*TPixelRenderer::num_components)),
|
||||
m_pixfmt(m_rbuf),
|
||||
m_raw_renderer(m_pixfmt),
|
||||
m_renderer(m_raw_renderer),
|
||||
@ -65,10 +70,6 @@ public:
|
||||
// ras.gamma(agg::gamma_power(1.0));
|
||||
|
||||
clear();
|
||||
|
||||
if(m_o == Origin::TOP_LEFT) m_flipy = [this](agg::path_storage& path) {
|
||||
path.flip_y(0, m_resolution.height_px);
|
||||
};
|
||||
}
|
||||
|
||||
void draw(const ExPolygon &poly) {
|
||||
@ -76,12 +77,14 @@ public:
|
||||
agg::scanline_p8 scanlines;
|
||||
|
||||
auto&& path = to_path(poly.contour);
|
||||
m_flipy(path);
|
||||
|
||||
if(m_o == Origin::TOP_LEFT) flipy(path);
|
||||
|
||||
ras.add_path(path);
|
||||
|
||||
for(auto h : poly.holes) {
|
||||
auto&& holepath = to_path(h);
|
||||
m_flipy(holepath);
|
||||
if(m_o == Origin::TOP_LEFT) flipy(holepath);
|
||||
ras.add_path(holepath);
|
||||
}
|
||||
|
||||
@ -205,8 +208,9 @@ void Raster::save(std::ostream& stream, Compression comp)
|
||||
<< m_impl->resolution().height_px << " "
|
||||
<< "255 ";
|
||||
|
||||
auto sz = m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type);
|
||||
stream.write(reinterpret_cast<const char*>(m_impl->buffer().data()),
|
||||
m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type));
|
||||
std::streamsize(sz));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,13 @@ public:
|
||||
PNG //!> PNG compression
|
||||
};
|
||||
|
||||
/// The Rasterizer expects the input polygons to have their coordinate
|
||||
/// system origin in the bottom left corner. If the raster is then
|
||||
/// configured with the TOP_LEFT origin parameter (in the constructor) than
|
||||
/// it will flip the Y axis in output to maintain the correct orientation.
|
||||
/// This is the default case with PNG images. They have the origin in the
|
||||
/// top left corner. Without the flipping, the image would be upside down
|
||||
/// with the scaled (clipper) coordinate system of the input polygons.
|
||||
enum class Origin {
|
||||
TOP_LEFT,
|
||||
BOTTOM_LEFT
|
||||
|
186
src/libslic3r/Rasterizer/bicubic.h
Normal file
@ -0,0 +1,186 @@
|
||||
#ifndef BICUBIC_HPP
|
||||
#define BICUBIC_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace BicubicInternal {
|
||||
// Linear kernel, to be able to test cubic methods with hat kernels.
|
||||
template<typename T>
|
||||
struct LinearKernel
|
||||
{
|
||||
typedef T FloatType;
|
||||
|
||||
static T a00() { return T(0.); }
|
||||
static T a01() { return T(0.); }
|
||||
static T a02() { return T(0.); }
|
||||
static T a03() { return T(0.); }
|
||||
static T a10() { return T(1.); }
|
||||
static T a11() { return T(-1.); }
|
||||
static T a12() { return T(0.); }
|
||||
static T a13() { return T(0.); }
|
||||
static T a20() { return T(0.); }
|
||||
static T a21() { return T(1.); }
|
||||
static T a22() { return T(0.); }
|
||||
static T a23() { return T(0.); }
|
||||
static T a30() { return T(0.); }
|
||||
static T a31() { return T(0.); }
|
||||
static T a32() { return T(0.); }
|
||||
static T a33() { return T(0.); }
|
||||
};
|
||||
|
||||
// Interpolation kernel aka Catmul-Rom aka Keyes kernel.
|
||||
template<typename T>
|
||||
struct CubicCatmulRomKernel
|
||||
{
|
||||
typedef T FloatType;
|
||||
|
||||
static T a00() { return 0; }
|
||||
static T a01() { return (T)-0.5; }
|
||||
static T a02() { return (T) 1.; }
|
||||
static T a03() { return (T)-0.5; }
|
||||
static T a10() { return (T) 1.; }
|
||||
static T a11() { return 0; }
|
||||
static T a12() { return (T)-5./2.; }
|
||||
static T a13() { return (T) 3./2.; }
|
||||
static T a20() { return 0; }
|
||||
static T a21() { return (T) 0.5; }
|
||||
static T a22() { return (T) 2.; }
|
||||
static T a23() { return (T)-3./2.; }
|
||||
static T a30() { return 0; }
|
||||
static T a31() { return 0; }
|
||||
static T a32() { return (T)-0.5; }
|
||||
static T a33() { return (T) 0.5; }
|
||||
};
|
||||
|
||||
// B-spline kernel
|
||||
template<typename T>
|
||||
struct CubicBSplineKernel
|
||||
{
|
||||
typedef T FloatType;
|
||||
|
||||
static T a00() { return (T) 1./6.; }
|
||||
static T a01() { return (T) -3./6.; }
|
||||
static T a02() { return (T) 3./6.; }
|
||||
static T a03() { return (T) -1./6.; }
|
||||
static T a10() { return (T) 4./6.; }
|
||||
static T a11() { return 0; }
|
||||
static T a12() { return (T) -6./6.; }
|
||||
static T a13() { return (T) 3./6.; }
|
||||
static T a20() { return (T) 1./6.; }
|
||||
static T a21() { return (T) 3./6.; }
|
||||
static T a22() { return (T) 3./6.; }
|
||||
static T a23() { return (T)- 3./6.; }
|
||||
static T a30() { return 0; }
|
||||
static T a31() { return 0; }
|
||||
static T a32() { return 0; }
|
||||
static T a33() { return (T) 1./6.; }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
inline T clamp(T a, T lower, T upper)
|
||||
{
|
||||
return (a < lower) ? lower :
|
||||
(a > upper) ? upper : a;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename KERNEL>
|
||||
struct CubicKernel
|
||||
{
|
||||
typedef typename KERNEL KernelInternal;
|
||||
typedef typename KERNEL::FloatType FloatType;
|
||||
|
||||
static FloatType kernel(FloatType x)
|
||||
{
|
||||
x = fabs(x);
|
||||
if (x >= (FloatType)2.)
|
||||
return 0.0f;
|
||||
if (x <= (FloatType)1.) {
|
||||
FloatType x2 = x * x;
|
||||
FloatType x3 = x2 * x;
|
||||
return KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3;
|
||||
}
|
||||
assert(x > (FloatType)1. && x < (FloatType)2.);
|
||||
x -= (FloatType)1.;
|
||||
FloatType x2 = x * x;
|
||||
FloatType x3 = x2 * x;
|
||||
return KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3;
|
||||
}
|
||||
|
||||
static FloatType interpolate(FloatType f0, FloatType f1, FloatType f2, FloatType f3, FloatType x)
|
||||
{
|
||||
const FloatType x2 = x*x;
|
||||
const FloatType x3 = x*x*x;
|
||||
return f0*(KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3) +
|
||||
f1*(KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3) +
|
||||
f2*(KERNEL::a20() + KERNEL::a21() * x + KERNEL::a22() * x2 + KERNEL::a23() * x3) +
|
||||
f3*(KERNEL::a30() + KERNEL::a31() * x + KERNEL::a32() * x2 + KERNEL::a33() * x3);
|
||||
}
|
||||
};
|
||||
|
||||
// Linear splines
|
||||
typedef CubicKernel<BicubicInternal::LinearKernel<float>> LinearKernelf;
|
||||
typedef CubicKernel<BicubicInternal::LinearKernel<double>> LinearKerneld;
|
||||
// Catmul-Rom splines
|
||||
typedef CubicKernel<BicubicInternal::CubicCatmulRomKernel<float>> CubicCatmulRomKernelf;
|
||||
typedef CubicKernel<BicubicInternal::CubicCatmulRomKernel<double>> CubicCatmulRomKerneld;
|
||||
typedef CubicKernel<BicubicInternal::CubicCatmulRomKernel<float>> CubicInterpolationKernelf;
|
||||
typedef CubicKernel<BicubicInternal::CubicCatmulRomKernel<double>> CubicInterpolationKerneld;
|
||||
// Cubic B-splines
|
||||
typedef CubicKernel<BicubicInternal::CubicBSplineKernel<float>> CubicBSplineKernelf;
|
||||
typedef CubicKernel<BicubicInternal::CubicBSplineKernel<double>> CubicBSplineKerneld;
|
||||
|
||||
template<typename KERNEL, typename Derived>
|
||||
static float cubic_interpolate(const Eigen::ArrayBase<Derived> &F, const typename KERNEL::FloatType pt, const typename KERNEL::FloatType dx)
|
||||
{
|
||||
typedef typename KERNEL::FloatType T;
|
||||
const int w = int(F.size());
|
||||
const int ix = (int)floor(pt);
|
||||
const T s = pt - (T)ix;
|
||||
|
||||
if (ix > 1 && ix + 2 < w) {
|
||||
// Inside the fully interpolated region.
|
||||
return KERNEL::interpolate(F[ix - 1], F[ix], F[ix + 1], F[ix + 2], s);
|
||||
}
|
||||
// Transition region. Extend with a constant function.
|
||||
auto f = [&F, w](x) { return F[BicubicInternal::clamp(x, 0, w - 1)]; }
|
||||
return KERNEL::interpolate(f(ix - 1), f(ix), f(ix + 1), f(ix + 2), s);
|
||||
}
|
||||
|
||||
template<typename KERNEL, typename Derived>
|
||||
static float bicubic_interpolate(const Eigen::MatrixBase<Derived> &F, const Eigen::Matrix<typename KERNEL::FloatType, 2, 1, Eigen::DontAlign> &pt, const typename KERNEL::FloatType dx)
|
||||
{
|
||||
typedef typename KERNEL::FloatType T;
|
||||
const int w = F.cols();
|
||||
const int h = F.rows();
|
||||
const int ix = (int)floor(pt[0]);
|
||||
const int iy = (int)floor(pt[1]);
|
||||
const T s = pt[0] - (T)ix;
|
||||
const T t = pt[1] - (T)iy;
|
||||
|
||||
if (ix > 1 && ix + 2 < w && iy > 1 && iy + 2 < h) {
|
||||
// Inside the fully interpolated region.
|
||||
return KERNEL::interpolate(
|
||||
KERNEL::interpolate(F(ix-1,iy-1),F(ix ,iy-1),F(ix+1,iy-1),F(ix+2,iy-1),s),
|
||||
KERNEL::interpolate(F(ix-1,iy ),F(ix ,iy ),F(ix+1,iy ),F(ix+2,iy ),s),
|
||||
KERNEL::interpolate(F(ix-1,iy+1),F(ix ,iy+1),F(ix+1,iy+1),F(ix+2,iy+1),s),
|
||||
KERNEL::interpolate(F(ix-1,iy+2),F(ix ,iy+2),F(ix+1,iy+2),F(ix+2,iy+2),s),t);
|
||||
}
|
||||
// Transition region. Extend with a constant function.
|
||||
auto f = [&f, w, h](int x, int y) { return F(BicubicInternal::clamp(x,0,w-1),BicubicInternal::clamp(y,0,h-1)); }
|
||||
return KERNEL::interpolate(
|
||||
KERNEL::interpolate(f(ix-1,iy-1),f(ix ,iy-1),f(ix+1,iy-1),f(ix+2,iy-1),s),
|
||||
KERNEL::interpolate(f(ix-1,iy ),f(ix ,iy ),f(ix+1,iy ),f(ix+2,iy ),s),
|
||||
KERNEL::interpolate(f(ix-1,iy+1),f(ix ,iy+1),f(ix+1,iy+1),f(ix+2,iy+1),s),
|
||||
KERNEL::interpolate(f(ix-1,iy+2),f(ix ,iy+2),f(ix+1,iy+2),f(ix+2,iy+2),s),t);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* BICUBIC_HPP */
|
@ -31,7 +31,9 @@ Contour3D convert(const Polygons& triangles, coord_t z, bool dir) {
|
||||
}
|
||||
|
||||
Contour3D walls(const ExPolygon& floor_plate, const ExPolygon& ceiling,
|
||||
double floor_z_mm, double ceiling_z_mm) {
|
||||
double floor_z_mm, double ceiling_z_mm,
|
||||
ThrowOnCancel thr)
|
||||
{
|
||||
using std::transform; using std::back_inserter;
|
||||
|
||||
ExPolygon poly;
|
||||
@ -61,8 +63,10 @@ Contour3D walls(const ExPolygon& floor_plate, const ExPolygon& ceiling,
|
||||
};
|
||||
|
||||
std::for_each(tri.begin(), tri.end(),
|
||||
[&rp, &rpi, &poly, &idx, is_upper, fz, cz](const Polygon& pp)
|
||||
[&rp, &rpi, thr, &idx, is_upper, fz, cz](const Polygon& pp)
|
||||
{
|
||||
thr(); // may throw if cancellation was requested
|
||||
|
||||
for(auto& p : pp.points)
|
||||
if(is_upper(p))
|
||||
rp.emplace_back(unscale(x(p), y(p), mm(cz)));
|
||||
@ -213,11 +217,12 @@ inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) {
|
||||
|
||||
template<class ExP, class D>
|
||||
Contour3D round_edges(const ExPolygon& base_plate,
|
||||
double radius_mm,
|
||||
double degrees,
|
||||
double ceilheight_mm,
|
||||
bool dir,
|
||||
ExP&& last_offset = ExP(), D&& last_height = D())
|
||||
double radius_mm,
|
||||
double degrees,
|
||||
double ceilheight_mm,
|
||||
bool dir,
|
||||
ThrowOnCancel throw_on_cancel,
|
||||
ExP&& last_offset = ExP(), D&& last_height = D())
|
||||
{
|
||||
auto ob = base_plate;
|
||||
auto ob_prev = ob;
|
||||
@ -231,6 +236,8 @@ Contour3D round_edges(const ExPolygon& base_plate,
|
||||
|
||||
if(degrees >= 90) {
|
||||
for(int i = 1; i <= steps; ++i) {
|
||||
throw_on_cancel();
|
||||
|
||||
ob = base_plate;
|
||||
|
||||
double r2 = radius_mm * radius_mm;
|
||||
@ -242,7 +249,7 @@ Contour3D round_edges(const ExPolygon& base_plate,
|
||||
wh = ceilheight_mm - radius_mm + stepy;
|
||||
|
||||
Contour3D pwalls;
|
||||
pwalls = walls(ob, ob_prev, wh, wh_prev);
|
||||
pwalls = walls(ob, ob_prev, wh, wh_prev, throw_on_cancel);
|
||||
|
||||
curvedwalls.merge(pwalls);
|
||||
ob_prev = ob;
|
||||
@ -254,6 +261,7 @@ Contour3D round_edges(const ExPolygon& base_plate,
|
||||
int tos = int(tox / stepx);
|
||||
|
||||
for(int i = 1; i <= tos; ++i) {
|
||||
throw_on_cancel();
|
||||
ob = base_plate;
|
||||
|
||||
double r2 = radius_mm * radius_mm;
|
||||
@ -264,7 +272,7 @@ Contour3D round_edges(const ExPolygon& base_plate,
|
||||
wh = ceilheight_mm - radius_mm - stepy;
|
||||
|
||||
Contour3D pwalls;
|
||||
pwalls = walls(ob_prev, ob, wh_prev, wh);
|
||||
pwalls = walls(ob_prev, ob, wh_prev, wh, throw_on_cancel);
|
||||
|
||||
curvedwalls.merge(pwalls);
|
||||
ob_prev = ob;
|
||||
@ -348,7 +356,8 @@ inline Point centroid(const ExPolygon& poly) {
|
||||
/// with explicit bridges. Bridges are generated from each shape's centroid
|
||||
/// to the center of the "scene" which is the centroid calculated from the shape
|
||||
/// centroids (a star is created...)
|
||||
ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50)
|
||||
ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50,
|
||||
ThrowOnCancel throw_on_cancel = [](){})
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
using SpatElement = std::pair<BoundingBox, unsigned>;
|
||||
@ -383,9 +392,10 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50)
|
||||
idx = 0;
|
||||
std::transform(centroids.begin(), centroids.end(),
|
||||
std::back_inserter(punion),
|
||||
[&punion, &boxindex, cc, max_dist_mm, &idx](const Point& c)
|
||||
[&punion, &boxindex, cc, max_dist_mm, &idx, throw_on_cancel]
|
||||
(const Point& c)
|
||||
{
|
||||
|
||||
throw_on_cancel();
|
||||
double dx = x(c) - x(cc), dy = y(c) - y(cc);
|
||||
double l = std::sqrt(dx * dx + dy * dy);
|
||||
double nx = dx / l, ny = dy / l;
|
||||
@ -419,7 +429,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50)
|
||||
}
|
||||
|
||||
void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h,
|
||||
float layerh)
|
||||
float layerh, ThrowOnCancel thrfn)
|
||||
{
|
||||
TriangleMesh m = mesh;
|
||||
TriangleMeshSlicer slicer(&m);
|
||||
@ -431,14 +441,17 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h,
|
||||
heights.emplace_back(hi);
|
||||
|
||||
std::vector<ExPolygons> out; out.reserve(size_t(std::ceil(h/layerh)));
|
||||
slicer.slice(heights, &out, [](){});
|
||||
slicer.slice(heights, &out, thrfn);
|
||||
|
||||
size_t count = 0; for(auto& o : out) count += o.size();
|
||||
ExPolygons tmp; tmp.reserve(count);
|
||||
for(auto& o : out) for(auto& e : o) tmp.emplace_back(std::move(e));
|
||||
|
||||
output = unify(tmp);
|
||||
for(auto& o : output) o = o.simplify(0.1/SCALING_FACTOR).front();
|
||||
ExPolygons utmp = unify(tmp);
|
||||
for(auto& o : utmp) {
|
||||
auto&& smp = o.simplify(0.1/SCALING_FACTOR);
|
||||
output.insert(output.end(), smp.begin(), smp.end());
|
||||
}
|
||||
}
|
||||
|
||||
void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
|
||||
@ -447,7 +460,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
|
||||
double mdist = 2*(1.8*cfg.min_wall_thickness_mm + 4*cfg.edge_radius_mm) +
|
||||
cfg.max_merge_distance_mm;
|
||||
|
||||
auto concavehs = concave_hull(ground_layer, mdist);
|
||||
auto concavehs = concave_hull(ground_layer, mdist, cfg.throw_on_cancel);
|
||||
for(ExPolygon& concaveh : concavehs) {
|
||||
if(concaveh.contour.points.empty()) return;
|
||||
concaveh.holes.clear();
|
||||
@ -505,6 +518,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
|
||||
phi, // 170 degrees
|
||||
0, // z position of the input plane
|
||||
true,
|
||||
cfg.throw_on_cancel,
|
||||
ob, wh);
|
||||
|
||||
pool.merge(curvedwalls);
|
||||
@ -512,7 +526,8 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
|
||||
ExPolygon ob_contr = ob;
|
||||
ob_contr.holes.clear();
|
||||
|
||||
auto pwalls = walls(ob_contr, inner_base, wh, -cfg.min_wall_height_mm);
|
||||
auto pwalls = walls(ob_contr, inner_base, wh, -cfg.min_wall_height_mm,
|
||||
cfg.throw_on_cancel);
|
||||
pool.merge(pwalls);
|
||||
|
||||
Polygons top_triangles, bottom_triangles;
|
||||
@ -528,6 +543,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
|
||||
90, // 90 degrees
|
||||
0, // z position of the input plane
|
||||
false,
|
||||
cfg.throw_on_cancel,
|
||||
ob, wh);
|
||||
pool.merge(curvedwalls);
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define SLABASEPOOL_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -11,12 +12,14 @@ class TriangleMesh;
|
||||
namespace sla {
|
||||
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
using ThrowOnCancel = std::function<void(void)>;
|
||||
|
||||
/// Calculate the polygon representing the silhouette from the specified height
|
||||
void base_plate(const TriangleMesh& mesh,
|
||||
ExPolygons& output,
|
||||
float zlevel = 0.1f,
|
||||
float layerheight = 0.05f);
|
||||
void base_plate(const TriangleMesh& mesh, // input mesh
|
||||
ExPolygons& output, // Output will be merged with
|
||||
float zlevel = 0.1f, // Plate creation level
|
||||
float layerheight = 0.05f, // The sampling height
|
||||
ThrowOnCancel thrfn = [](){}); // Will be called frequently
|
||||
|
||||
struct PoolConfig {
|
||||
double min_wall_thickness_mm = 2;
|
||||
@ -24,6 +27,8 @@ struct PoolConfig {
|
||||
double max_merge_distance_mm = 50;
|
||||
double edge_radius_mm = 1;
|
||||
|
||||
ThrowOnCancel throw_on_cancel = [](){};
|
||||
|
||||
inline PoolConfig() {}
|
||||
inline PoolConfig(double wt, double wh, double md, double er):
|
||||
min_wall_thickness_mm(wt),
|
||||
@ -35,8 +40,7 @@ struct PoolConfig {
|
||||
/// Calculate the pool for the mesh for SLA printing
|
||||
void create_base_pool(const ExPolygons& base_plate,
|
||||
TriangleMesh& output_mesh,
|
||||
const PoolConfig& = PoolConfig()
|
||||
);
|
||||
const PoolConfig& = PoolConfig());
|
||||
|
||||
/// TODO: Currently the base plate of the pool will have half the height of the
|
||||
/// whole pool. So the carved out space has also half the height. This is not
|
||||
|
@ -58,7 +58,7 @@ struct Contour3D {
|
||||
points.insert(points.end(), ctr.points.begin(), ctr.points.end());
|
||||
indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end());
|
||||
|
||||
for(auto n = s; n < indices.size(); n++) {
|
||||
for(size_t n = s; n < indices.size(); n++) {
|
||||
auto& idx = indices[n]; x(idx) += s3; y(idx) += s3; z(idx) += s3;
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ std::array<double, 3> find_best_rotation(const ModelObject& modelobj,
|
||||
using libnest2d::opt::Optimizer;
|
||||
using libnest2d::opt::TOptimizer;
|
||||
using libnest2d::opt::StopCriteria;
|
||||
using Quaternion = Eigen::Quaternion<double>;
|
||||
|
||||
static const unsigned MAX_TRIES = 100000;
|
||||
|
||||
|
@ -169,7 +169,7 @@ Contour3D cylinder(double r, double h, size_t ssteps) {
|
||||
auto steps = int(ssteps);
|
||||
auto& points = ret.points;
|
||||
auto& indices = ret.indices;
|
||||
points.reserve(2*steps);
|
||||
points.reserve(2*ssteps);
|
||||
double a = 2*PI/steps;
|
||||
|
||||
Vec3d jp = {0, 0, 0};
|
||||
@ -189,7 +189,7 @@ Contour3D cylinder(double r, double h, size_t ssteps) {
|
||||
points.emplace_back(x, y, jp(Z));
|
||||
}
|
||||
|
||||
indices.reserve(2*steps);
|
||||
indices.reserve(2*ssteps);
|
||||
auto offs = steps;
|
||||
for(int i = 0; i < steps - 1; ++i) {
|
||||
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||
@ -347,19 +347,22 @@ struct Pillar {
|
||||
double radius = 1, size_t st = 45):
|
||||
r(radius), steps(st), endpoint(endp), starts_from_head(false)
|
||||
{
|
||||
assert(steps > 0);
|
||||
int steps_1 = int(steps - 1);
|
||||
|
||||
auto& points = mesh.points;
|
||||
auto& indices = mesh.indices;
|
||||
points.reserve(2*steps);
|
||||
double a = 2*PI/steps;
|
||||
|
||||
for(int i = 0; i < steps; ++i) {
|
||||
for(size_t i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = jp(X) + r*std::cos(phi);
|
||||
double y = jp(Y) + r*std::sin(phi);
|
||||
points.emplace_back(x, y, jp(Z));
|
||||
}
|
||||
|
||||
for(int i = 0; i < steps; ++i) {
|
||||
for(size_t i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double ex = endp(X) + r*std::cos(phi);
|
||||
double ey = endp(Y) + r*std::sin(phi);
|
||||
@ -368,14 +371,13 @@ struct Pillar {
|
||||
|
||||
indices.reserve(2*steps);
|
||||
int offs = int(steps);
|
||||
for(int i = 0; i < steps - 1; ++i) {
|
||||
for(int i = 0; i < steps_1 ; ++i) {
|
||||
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||
}
|
||||
|
||||
int last = int(steps) - 1;
|
||||
indices.emplace_back(0, last, offs);
|
||||
indices.emplace_back(last, offs + last, offs);
|
||||
indices.emplace_back(0, steps_1, offs);
|
||||
indices.emplace_back(steps_1, offs + steps_1, offs);
|
||||
}
|
||||
|
||||
Pillar(const Junction& junc, const Vec3d& endp):
|
||||
@ -390,19 +392,22 @@ struct Pillar {
|
||||
void add_base(double height = 3, double radius = 2) {
|
||||
if(height <= 0) return;
|
||||
|
||||
assert(steps >= 0);
|
||||
auto last = int(steps - 1);
|
||||
|
||||
if(radius < r ) radius = r;
|
||||
|
||||
double a = 2*PI/steps;
|
||||
double z = endpoint(2) + height;
|
||||
|
||||
for(int i = 0; i < steps; ++i) {
|
||||
for(size_t i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = endpoint(0) + r*std::cos(phi);
|
||||
double y = endpoint(1) + r*std::sin(phi);
|
||||
base.points.emplace_back(x, y, z);
|
||||
}
|
||||
|
||||
for(int i = 0; i < steps; ++i) {
|
||||
for(size_t i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = endpoint(0) + radius*std::cos(phi);
|
||||
double y = endpoint(1) + radius*std::sin(phi);
|
||||
@ -417,14 +422,13 @@ struct Pillar {
|
||||
auto hcenter = int(base.points.size() - 1);
|
||||
auto lcenter = int(base.points.size() - 2);
|
||||
auto offs = int(steps);
|
||||
for(int i = 0; i < steps - 1; ++i) {
|
||||
for(int i = 0; i < last; ++i) {
|
||||
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||
indices.emplace_back(i, i + 1, hcenter);
|
||||
indices.emplace_back(lcenter, offs + i + 1, offs + i);
|
||||
}
|
||||
|
||||
auto last = int(steps - 1);
|
||||
indices.emplace_back(0, last, offs);
|
||||
indices.emplace_back(last, offs + last, offs);
|
||||
indices.emplace_back(hcenter, last, 0);
|
||||
@ -462,8 +466,6 @@ struct Bridge {
|
||||
Bridge(const Junction& j1, const Junction& j2, double r_mm = 0.8):
|
||||
Bridge(j1.pos, j2.pos, r_mm, j1.steps) {}
|
||||
|
||||
Bridge(const Junction& j, const Pillar& cl) {}
|
||||
|
||||
};
|
||||
|
||||
// A bridge that spans from model surface to model surface with small connecting
|
||||
@ -515,8 +517,13 @@ struct Pad {
|
||||
zlevel(ground_level + sla::get_pad_elevation(pcfg))
|
||||
{
|
||||
ExPolygons basep;
|
||||
cfg.throw_on_cancel();
|
||||
|
||||
// The 0.1f is the layer height with which the mesh is sampled and then
|
||||
// the layers are unified into one vector of polygons.
|
||||
base_plate(object_support_mesh, basep,
|
||||
float(cfg.min_wall_height_mm)/*,layer_height*/);
|
||||
float(cfg.min_wall_height_mm), 0.1f, pcfg.throw_on_cancel);
|
||||
|
||||
for(auto& bp : baseplate) basep.emplace_back(bp);
|
||||
|
||||
create_base_pool(basep, tmesh, cfg);
|
||||
@ -532,12 +539,12 @@ EigenMesh3D to_eigenmesh(const Contour3D& cntr) {
|
||||
auto& V = emesh.V;
|
||||
auto& F = emesh.F;
|
||||
|
||||
V.resize(cntr.points.size(), 3);
|
||||
F.resize(cntr.indices.size(), 3);
|
||||
V.resize(Eigen::Index(cntr.points.size()), 3);
|
||||
F.resize(Eigen::Index(cntr.indices.size()), 3);
|
||||
|
||||
for (int i = 0; i < V.rows(); ++i) {
|
||||
V.row(i) = cntr.points[i];
|
||||
F.row(i) = cntr.indices[i];
|
||||
V.row(i) = cntr.points[size_t(i)];
|
||||
F.row(i) = cntr.indices[size_t(i)];
|
||||
}
|
||||
|
||||
return emesh;
|
||||
@ -564,18 +571,23 @@ EigenMesh3D to_eigenmesh(const TriangleMesh& tmesh) {
|
||||
|
||||
V.resize(3*stl.stats.number_of_facets, 3);
|
||||
F.resize(stl.stats.number_of_facets, 3);
|
||||
for (unsigned int i=0; i<stl.stats.number_of_facets; ++i) {
|
||||
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
|
||||
const stl_facet* facet = stl.facet_start+i;
|
||||
V(3*i+0, 0) = facet->vertex[0](0); V(3*i+0, 1) =
|
||||
facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2);
|
||||
V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) =
|
||||
facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2);
|
||||
V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) =
|
||||
facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2);
|
||||
V(3*i+0, 0) = double(facet->vertex[0](0));
|
||||
V(3*i+0, 1) = double(facet->vertex[0](1));
|
||||
V(3*i+0, 2) = double(facet->vertex[0](2));
|
||||
|
||||
F(i, 0) = 3*i+0;
|
||||
F(i, 1) = 3*i+1;
|
||||
F(i, 2) = 3*i+2;
|
||||
V(3*i+1, 0) = double(facet->vertex[1](0));
|
||||
V(3*i+1, 1) = double(facet->vertex[1](1));
|
||||
V(3*i+1, 2) = double(facet->vertex[1](2));
|
||||
|
||||
V(3*i+2, 0) = double(facet->vertex[2](0));
|
||||
V(3*i+2, 1) = double(facet->vertex[2](1));
|
||||
V(3*i+2, 2) = double(facet->vertex[2](2));
|
||||
|
||||
F(i, 0) = int(3*i+0);
|
||||
F(i, 1) = int(3*i+1);
|
||||
F(i, 2) = int(3*i+2);
|
||||
}
|
||||
|
||||
return outmesh;
|
||||
@ -622,12 +634,19 @@ class SLASupportTree::Impl {
|
||||
std::vector<Junction> m_junctions;
|
||||
std::vector<Bridge> m_bridges;
|
||||
std::vector<CompactBridge> m_compact_bridges;
|
||||
Controller m_ctl;
|
||||
|
||||
Pad m_pad;
|
||||
mutable TriangleMesh meshcache; mutable bool meshcache_valid;
|
||||
mutable TriangleMesh meshcache; mutable bool meshcache_valid = false;
|
||||
mutable double model_height = 0; // the full height of the model
|
||||
public:
|
||||
double ground_level = 0;
|
||||
|
||||
Impl() = default;
|
||||
inline Impl(const Controller& ctl): m_ctl(ctl) {}
|
||||
|
||||
const Controller& ctl() const { return m_ctl; }
|
||||
|
||||
template<class...Args> Head& add_head(Args&&... args) {
|
||||
m_heads.emplace_back(std::forward<Args>(args)...);
|
||||
m_heads.back().id = long(m_heads.size() - 1);
|
||||
@ -637,7 +656,7 @@ public:
|
||||
|
||||
template<class...Args> Pillar& add_pillar(long headid, Args&&... args) {
|
||||
assert(headid >= 0 && headid < m_heads.size());
|
||||
Head& head = m_heads[headid];
|
||||
Head& head = m_heads[size_t(headid)];
|
||||
m_pillars.emplace_back(head, std::forward<Args>(args)...);
|
||||
Pillar& pillar = m_pillars.back();
|
||||
pillar.id = long(m_pillars.size() - 1);
|
||||
@ -650,17 +669,17 @@ public:
|
||||
|
||||
const Head& pillar_head(long pillar_id) const {
|
||||
assert(pillar_id >= 0 && pillar_id < m_pillars.size());
|
||||
const Pillar& p = m_pillars[pillar_id];
|
||||
const Pillar& p = m_pillars[size_t(pillar_id)];
|
||||
assert(p.starts_from_head && p.start_junction_id >= 0 &&
|
||||
p.start_junction_id < m_heads.size() );
|
||||
return m_heads[p.start_junction_id];
|
||||
return m_heads[size_t(p.start_junction_id)];
|
||||
}
|
||||
|
||||
const Pillar& head_pillar(long headid) const {
|
||||
assert(headid >= 0 && headid < m_heads.size());
|
||||
const Head& h = m_heads[headid];
|
||||
const Head& h = m_heads[size_t(headid)];
|
||||
assert(h.pillar_id >= 0 && h.pillar_id < m_pillars.size());
|
||||
return m_pillars[h.pillar_id];
|
||||
return m_pillars[size_t(h.pillar_id)];
|
||||
}
|
||||
|
||||
template<class...Args> const Junction& add_junction(Args&&... args) {
|
||||
@ -710,27 +729,38 @@ public:
|
||||
meshcache = TriangleMesh();
|
||||
|
||||
for(auto& head : heads()) {
|
||||
if(m_ctl.stopcondition()) break;
|
||||
auto&& m = mesh(head.mesh);
|
||||
meshcache.merge(m);
|
||||
}
|
||||
|
||||
for(auto& stick : pillars()) {
|
||||
if(m_ctl.stopcondition()) break;
|
||||
meshcache.merge(mesh(stick.mesh));
|
||||
meshcache.merge(mesh(stick.base));
|
||||
}
|
||||
|
||||
for(auto& j : junctions()) {
|
||||
if(m_ctl.stopcondition()) break;
|
||||
meshcache.merge(mesh(j.mesh));
|
||||
}
|
||||
|
||||
for(auto& cb : compact_bridges()) {
|
||||
if(m_ctl.stopcondition()) break;
|
||||
meshcache.merge(mesh(cb.mesh));
|
||||
}
|
||||
|
||||
for(auto& bs : bridges()) {
|
||||
if(m_ctl.stopcondition()) break;
|
||||
meshcache.merge(mesh(bs.mesh));
|
||||
}
|
||||
|
||||
if(m_ctl.stopcondition()) {
|
||||
// In case of failure we have to return an empty mesh
|
||||
meshcache = TriangleMesh();
|
||||
return meshcache;
|
||||
}
|
||||
|
||||
// TODO: Is this necessary?
|
||||
meshcache.repair();
|
||||
|
||||
@ -743,8 +773,11 @@ public:
|
||||
|
||||
// WITH THE PAD
|
||||
double full_height() const {
|
||||
if(merged_mesh().empty() && !pad().empty())
|
||||
return pad().cfg.min_wall_height_mm;
|
||||
|
||||
double h = mesh_height();
|
||||
if(!pad().empty()) h += pad().cfg.min_wall_height_mm / 2;
|
||||
if(!pad().empty()) h += sla::get_pad_elevation(pad().cfg);
|
||||
return h;
|
||||
}
|
||||
|
||||
@ -856,8 +889,8 @@ ClusterEl pts_convex_hull(const ClusterEl& inpts,
|
||||
}
|
||||
|
||||
// Find the leftmost (bottom) point
|
||||
int l = 0;
|
||||
for (int i = 1; i < n; i++) {
|
||||
size_t l = 0;
|
||||
for (size_t i = 1; i < n; i++) {
|
||||
if(std::abs(points[i](X) - points[l](X)) < ERR) {
|
||||
if(points[i](Y) < points[l](Y)) l = i;
|
||||
}
|
||||
@ -868,7 +901,6 @@ ClusterEl pts_convex_hull(const ClusterEl& inpts,
|
||||
// fill the output with the spatially ordered set of points.
|
||||
|
||||
// find the direction
|
||||
Vec2d dir = (points[l] - points[(l+1)%n]).normalized();
|
||||
hull = inpts;
|
||||
auto& lp = points[l];
|
||||
std::sort(hull.begin(), hull.end(),
|
||||
@ -886,7 +918,7 @@ ClusterEl pts_convex_hull(const ClusterEl& inpts,
|
||||
// Start from leftmost point, keep moving counterclockwise
|
||||
// until reach the start point again. This loop runs O(h)
|
||||
// times where h is number of points in result or output.
|
||||
int p = l;
|
||||
size_t p = l;
|
||||
do
|
||||
{
|
||||
// Add current point to result
|
||||
@ -897,8 +929,8 @@ ClusterEl pts_convex_hull(const ClusterEl& inpts,
|
||||
// is to keep track of last visited most counterclock-
|
||||
// wise point in q. If any point 'i' is more counterclock-
|
||||
// wise than q, then update q.
|
||||
int q = (p+1)%n;
|
||||
for (int i = 0; i < n; i++)
|
||||
size_t q = (p + 1) % n;
|
||||
for (size_t i = 0; i < n; i++)
|
||||
{
|
||||
// If i is more counterclockwise than current q, then
|
||||
// update q
|
||||
@ -980,7 +1012,10 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
// std::cout << "p " << pn << " " << points.row(pn) << std::endl;
|
||||
// }
|
||||
|
||||
auto filterfn = [] (
|
||||
|
||||
auto& tifcl = ctl.cancelfn;
|
||||
|
||||
auto filterfn = [tifcl] (
|
||||
const SupportConfig& cfg,
|
||||
const PointSet& points,
|
||||
const EigenMesh3D& mesh,
|
||||
@ -990,26 +1025,29 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
PointSet& headless_pos,
|
||||
PointSet& headless_norm)
|
||||
{
|
||||
|
||||
/* ******************************************************** */
|
||||
/* Filtering step */
|
||||
/* ******************************************************** */
|
||||
|
||||
// Get the points that are too close to each other and keep only the
|
||||
// first one
|
||||
auto aliases = cluster(points,
|
||||
[cfg](const SpatElement& p,
|
||||
const SpatElement& se){
|
||||
auto aliases =
|
||||
cluster(points,
|
||||
[tifcl](const SpatElement& p, const SpatElement& se)
|
||||
{
|
||||
tifcl();
|
||||
return distance(p.first, se.first) < D_SP;
|
||||
}, 2);
|
||||
|
||||
filt_pts.resize(aliases.size(), 3);
|
||||
filt_pts.resize(Eigen::Index(aliases.size()), 3);
|
||||
int count = 0;
|
||||
for(auto& a : aliases) {
|
||||
// Here we keep only the front point of the cluster. TODO: centroid
|
||||
// Here we keep only the front point of the cluster.
|
||||
filt_pts.row(count++) = points.row(a.front());
|
||||
}
|
||||
|
||||
tifcl();
|
||||
|
||||
// calculate the normals to the triangles belonging to filtered points
|
||||
auto nmls = sla::normals(filt_pts, mesh);
|
||||
|
||||
@ -1025,6 +1063,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
|
||||
int pcount = 0, hlcount = 0;
|
||||
for(int i = 0; i < count; i++) {
|
||||
tifcl();
|
||||
auto n = nmls.row(i);
|
||||
|
||||
// for all normals we generate the spherical coordinates and
|
||||
@ -1084,7 +1123,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
};
|
||||
|
||||
// Function to write the pinheads into the result
|
||||
auto pinheadfn = [] (
|
||||
auto pinheadfn = [tifcl] (
|
||||
const SupportConfig& cfg,
|
||||
PointSet& head_pos,
|
||||
PointSet& nmls,
|
||||
@ -1097,6 +1136,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
/* ******************************************************** */
|
||||
|
||||
for (int i = 0; i < head_pos.rows(); ++i) {
|
||||
tifcl();
|
||||
result.add_head(
|
||||
cfg.head_back_radius_mm,
|
||||
cfg.head_front_radius_mm,
|
||||
@ -1110,7 +1150,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
|
||||
// &filtered_points, &head_positions, &result, &mesh,
|
||||
// &gndidx, &gndheight, &nogndidx, cfg
|
||||
auto classifyfn = [] (
|
||||
auto classifyfn = [tifcl] (
|
||||
const SupportConfig& cfg,
|
||||
const EigenMesh3D& mesh,
|
||||
PointSet& head_pos,
|
||||
@ -1126,11 +1166,12 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
/* ******************************************************** */
|
||||
|
||||
// We should first get the heads that reach the ground directly
|
||||
gndheight.reserve(head_pos.rows());
|
||||
gndidx.reserve(head_pos.rows());
|
||||
nogndidx.reserve(head_pos.rows());
|
||||
gndheight.reserve(size_t(head_pos.rows()));
|
||||
gndidx.reserve(size_t(head_pos.rows()));
|
||||
nogndidx.reserve(size_t(head_pos.rows()));
|
||||
|
||||
for(unsigned i = 0; i < head_pos.rows(); i++) {
|
||||
tifcl();
|
||||
auto& head = result.heads()[i];
|
||||
|
||||
Vec3d dir(0, 0, -1);
|
||||
@ -1147,18 +1188,22 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
PointSet gnd(gndidx.size(), 3);
|
||||
|
||||
for(size_t i = 0; i < gndidx.size(); i++)
|
||||
gnd.row(i) = head_pos.row(gndidx[i]);
|
||||
gnd.row(long(i)) = head_pos.row(gndidx[i]);
|
||||
|
||||
// We want to search for clusters of points that are far enough from
|
||||
// each other in the XY plane to not cross their pillar bases
|
||||
// These clusters of support points will join in one pillar, possibly in
|
||||
// their centroid support point.
|
||||
auto d_base = 2*cfg.base_radius_mm;
|
||||
ground_clusters = cluster(gnd,
|
||||
[d_base, &cfg](const SpatElement& p, const SpatElement& s){
|
||||
return distance(Vec2d(p.first(X), p.first(Y)),
|
||||
Vec2d(s.first(X), s.first(Y))) < d_base;
|
||||
}, 3); // max 3 heads to connect to one centroid
|
||||
ground_clusters =
|
||||
cluster(
|
||||
gnd,
|
||||
[d_base, tifcl](const SpatElement& p, const SpatElement& s)
|
||||
{
|
||||
tifcl();
|
||||
return distance(Vec2d(p.first(X), p.first(Y)),
|
||||
Vec2d(s.first(X), s.first(Y))) < d_base;
|
||||
}, 3); // max 3 heads to connect to one centroid
|
||||
};
|
||||
|
||||
// Helper function for interconnecting two pillars with zig-zag bridges
|
||||
@ -1171,9 +1216,6 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
const Head& phead = result.pillar_head(pillar.id);
|
||||
const Head& nextphead = result.pillar_head(nextpillar.id);
|
||||
|
||||
// double d = 2*pillar.r;
|
||||
// const Vec3d& pp = pillar.endpoint.cwiseProduct(Vec3d{1, 1, 0});
|
||||
|
||||
Vec3d sj = phead.junction_point();
|
||||
sj(Z) = std::min(sj(Z), nextphead.junction_point()(Z));
|
||||
Vec3d ej = nextpillar.endpoint;
|
||||
@ -1218,7 +1260,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
}
|
||||
};
|
||||
|
||||
auto routing_ground_fn = [gnd_head_pt, interconnect](
|
||||
auto routing_ground_fn = [gnd_head_pt, interconnect, tifcl](
|
||||
const SupportConfig& cfg,
|
||||
const ClusteredPoints& gnd_clusters,
|
||||
const IndexSet& gndidx,
|
||||
@ -1234,22 +1276,27 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
cl_centroids.reserve(gnd_clusters.size());
|
||||
|
||||
SpatIndex pheadindex; // spatial index for the junctions
|
||||
for(auto cl : gnd_clusters) {
|
||||
for(auto& cl : gnd_clusters) { tifcl();
|
||||
// place all the centroid head positions into the index. We will
|
||||
// query for alternative pillar positions. If a sidehead cannot
|
||||
// connect to the cluster centroid, we have to search for another
|
||||
// head with a full pillar. Also when there are two elements in the
|
||||
// cluster, the centroid is arbitrary and the sidehead is allowed to
|
||||
// connect to a nearby pillar to increase structural stability.
|
||||
if(cl.empty()) continue;
|
||||
|
||||
// get the current cluster centroid
|
||||
unsigned cid = cluster_centroid(cl, gnd_head_pt,
|
||||
[](const Vec3d& p1, const Vec3d& p2)
|
||||
long lcid = cluster_centroid(cl, gnd_head_pt,
|
||||
[tifcl](const Vec3d& p1, const Vec3d& p2)
|
||||
{
|
||||
tifcl();
|
||||
return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y)));
|
||||
});
|
||||
|
||||
cl_centroids.push_back(cid);
|
||||
assert(lcid >= 0);
|
||||
auto cid = unsigned(lcid);
|
||||
|
||||
cl_centroids.push_back(unsigned(cid));
|
||||
|
||||
unsigned hid = gndidx[cl[cid]]; // Head index
|
||||
Head& h = result.head(hid);
|
||||
@ -1262,12 +1309,13 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
// now we will go through the clusters ones again and connect the
|
||||
// sidepoints with the cluster centroid (which is a ground pillar)
|
||||
// or a nearby pillar if the centroid is unreachable.
|
||||
long ci = 0;
|
||||
for(auto cl : gnd_clusters) {
|
||||
size_t ci = 0;
|
||||
for(auto cl : gnd_clusters) { tifcl();
|
||||
|
||||
auto cidx = cl_centroids[ci];
|
||||
cl_centroids[ci++] = cl[cidx];
|
||||
|
||||
long index_to_heads = gndidx[cl[cidx]];
|
||||
size_t index_to_heads = gndidx[cl[cidx]];
|
||||
auto& head = result.head(index_to_heads);
|
||||
|
||||
Vec3d startpoint = head.junction_point();
|
||||
@ -1275,7 +1323,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
|
||||
// Create the central pillar of the cluster with its base on the
|
||||
// ground
|
||||
result.add_pillar(index_to_heads, endpoint, pradius)
|
||||
result.add_pillar(long(index_to_heads), endpoint, pradius)
|
||||
.add_base(cfg.base_height_mm, cfg.base_radius_mm);
|
||||
|
||||
// Process side point in current cluster
|
||||
@ -1326,12 +1374,11 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
return nearest_id;
|
||||
};
|
||||
|
||||
for(auto c : cl) {
|
||||
for(auto c : cl) { tifcl();
|
||||
auto& sidehead = result.head(gndidx[c]);
|
||||
sidehead.transform();
|
||||
|
||||
Vec3d jsh = sidehead.junction_point();
|
||||
// Vec3d jp2d = {jsh(X), jsh(Y), gndlvl};
|
||||
SpatIndex spindex = pheadindex;
|
||||
long nearest_id = search_nearest(spindex, jsh);
|
||||
|
||||
@ -1344,11 +1391,11 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
add_base(cfg.base_height_mm, cfg.base_radius_mm);
|
||||
|
||||
// connects to ground, eligible for bridging
|
||||
cl_centroids.emplace_back(sidehead.id);
|
||||
cl_centroids.emplace_back(c);
|
||||
} else {
|
||||
// Creating the bridge to the nearest pillar
|
||||
|
||||
const Head& nearhead = result.heads()[nearest_id];
|
||||
const Head& nearhead = result.heads()[size_t(nearest_id)];
|
||||
Vec3d jp = jsh;
|
||||
Vec3d jh = nearhead.junction_point();
|
||||
|
||||
@ -1393,6 +1440,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
ClusterEl ring;
|
||||
|
||||
while(!rem.empty()) { // loop until all the points belong to some ring
|
||||
tifcl();
|
||||
std::sort(rem.begin(), rem.end());
|
||||
|
||||
auto newring = pts_convex_hull(rem,
|
||||
@ -1406,7 +1454,8 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
SpatIndex innerring;
|
||||
for(unsigned i : newring) {
|
||||
const Pillar& pill = result.head_pillar(gndidx[i]);
|
||||
innerring.insert(pill.endpoint, pill.id);
|
||||
assert(pill.id >= 0);
|
||||
innerring.insert(pill.endpoint, unsigned(pill.id));
|
||||
}
|
||||
|
||||
// For all pillars in the outer ring find the closest in the
|
||||
@ -1452,14 +1501,14 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
}
|
||||
};
|
||||
|
||||
auto routing_nongnd_fn = [](
|
||||
auto routing_nongnd_fn = [tifcl](
|
||||
const SupportConfig& cfg,
|
||||
const std::vector<double>& gndheight,
|
||||
const IndexSet& nogndidx,
|
||||
Result& result)
|
||||
{
|
||||
// TODO: connect these to the ground pillars if possible
|
||||
for(auto idx : nogndidx) {
|
||||
for(auto idx : nogndidx) { tifcl();
|
||||
auto& head = result.head(idx);
|
||||
head.transform();
|
||||
|
||||
@ -1484,7 +1533,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
}
|
||||
};
|
||||
|
||||
auto process_headless = [](
|
||||
auto process_headless = [tifcl](
|
||||
const SupportConfig& cfg,
|
||||
const PointSet& headless_pts,
|
||||
const PointSet& headless_norm,
|
||||
@ -1499,7 +1548,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
|
||||
// We will sink the pins into the model surface for a distance of 1/3 of
|
||||
// HWIDTH_MM
|
||||
for(int i = 0; i < headless_pts.rows(); i++) {
|
||||
for(int i = 0; i < headless_pts.rows(); i++) { tifcl();
|
||||
Vec3d sp = headless_pts.row(i);
|
||||
|
||||
Vec3d n = headless_norm.row(i);
|
||||
@ -1608,6 +1657,7 @@ bool SLASupportTree::generate(const PointSet &points,
|
||||
case HALT: pc = pc_prev; break;
|
||||
case DONE:
|
||||
case ABORT: break;
|
||||
default: ;
|
||||
}
|
||||
ctl.statuscb(stepstate[pc], stepstr[pc]);
|
||||
};
|
||||
@ -1656,22 +1706,19 @@ SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const
|
||||
fullmesh.merge(get_pad());
|
||||
TriangleMeshSlicer slicer(&fullmesh);
|
||||
SlicedSupports ret;
|
||||
slicer.slice(heights, &ret, m_ctl.cancelfn);
|
||||
slicer.slice(heights, &ret, get().ctl().cancelfn);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const TriangleMesh &SLASupportTree::add_pad(const SliceLayer& baseplate,
|
||||
double min_wall_thickness_mm,
|
||||
double min_wall_height_mm,
|
||||
double max_merge_distance_mm,
|
||||
double edge_radius_mm) const
|
||||
const PoolConfig& pcfg) const
|
||||
{
|
||||
PoolConfig pcfg;
|
||||
pcfg.min_wall_thickness_mm = min_wall_thickness_mm;
|
||||
pcfg.min_wall_height_mm = min_wall_height_mm;
|
||||
pcfg.max_merge_distance_mm = max_merge_distance_mm;
|
||||
pcfg.edge_radius_mm = edge_radius_mm;
|
||||
// PoolConfig pcfg;
|
||||
// pcfg.min_wall_thickness_mm = min_wall_thickness_mm;
|
||||
// pcfg.min_wall_height_mm = min_wall_height_mm;
|
||||
// pcfg.max_merge_distance_mm = max_merge_distance_mm;
|
||||
// pcfg.edge_radius_mm = edge_radius_mm;
|
||||
return m_impl->create_pad(merged_mesh(), baseplate, pcfg).tmesh;
|
||||
}
|
||||
|
||||
@ -1684,14 +1731,14 @@ SLASupportTree::SLASupportTree(const PointSet &points,
|
||||
const EigenMesh3D& emesh,
|
||||
const SupportConfig &cfg,
|
||||
const Controller &ctl):
|
||||
m_impl(new Impl()), m_ctl(ctl)
|
||||
m_impl(new Impl(ctl))
|
||||
{
|
||||
m_impl->ground_level = emesh.ground_level - cfg.object_elevation_mm;
|
||||
generate(points, emesh, cfg, ctl);
|
||||
}
|
||||
|
||||
SLASupportTree::SLASupportTree(const SLASupportTree &c):
|
||||
m_impl(new Impl(*c.m_impl)), m_ctl(c.m_ctl) {}
|
||||
m_impl(new Impl(*c.m_impl)) {}
|
||||
|
||||
SLASupportTree &SLASupportTree::operator=(const SLASupportTree &c)
|
||||
{
|
||||
@ -1701,5 +1748,8 @@ SLASupportTree &SLASupportTree::operator=(const SLASupportTree &c)
|
||||
|
||||
SLASupportTree::~SLASupportTree() {}
|
||||
|
||||
SLASupportsStoppedException::SLASupportsStoppedException():
|
||||
std::runtime_error("") {}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +69,8 @@ struct SupportConfig {
|
||||
double object_elevation_mm = 10;
|
||||
};
|
||||
|
||||
struct PoolConfig;
|
||||
|
||||
/// A Control structure for the support calculation. Consists of the status
|
||||
/// indicator callback and the stop condition predicate.
|
||||
struct Controller {
|
||||
@ -112,14 +114,13 @@ PointSet to_point_set(const std::vector<Vec3d>&);
|
||||
class SLASupportsStoppedException: public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
SLASupportsStoppedException(): std::runtime_error("") {}
|
||||
SLASupportsStoppedException();
|
||||
};
|
||||
|
||||
/// The class containing mesh data for the generated supports.
|
||||
class SLASupportTree {
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
Controller m_ctl;
|
||||
|
||||
Impl& get() { return *m_impl; }
|
||||
const Impl& get() const { return *m_impl; }
|
||||
@ -158,10 +159,7 @@ public:
|
||||
|
||||
/// Adding the "pad" (base pool) under the supports
|
||||
const TriangleMesh& add_pad(const SliceLayer& baseplate,
|
||||
double min_wall_thickness_mm,
|
||||
double min_wall_height_mm,
|
||||
double max_merge_distance_mm,
|
||||
double edge_radius_mm) const;
|
||||
const PoolConfig& pcfg) const;
|
||||
|
||||
/// Get the pad geometry
|
||||
const TriangleMesh& get_pad() const;
|
||||
|
@ -89,8 +89,6 @@ PointSet normals(const PointSet& points, const EigenMesh3D& mesh) {
|
||||
#ifdef IGL_COMPATIBLE
|
||||
Eigen::VectorXd dists;
|
||||
Eigen::VectorXi I;
|
||||
// Eigen::Matrix<double, Eigen::Dynamic, 1, Eigen::DontAlign> dists;
|
||||
// Eigen::Matrix<int, Eigen::Dynamic, 1, Eigen::DontAlign> I;
|
||||
PointSet C;
|
||||
|
||||
igl::point_mesh_squared_distance( points, mesh.V, mesh.F, dists, I, C);
|
||||
@ -122,7 +120,7 @@ double ray_mesh_intersect(const Vec3d& s,
|
||||
igl::Hit hit;
|
||||
hit.t = std::numeric_limits<float>::infinity();
|
||||
igl::ray_mesh_intersect(s, dir, m.V, m.F, hit);
|
||||
return hit.t;
|
||||
return double(hit.t);
|
||||
}
|
||||
|
||||
// Clustering a set of points by the given criteria
|
||||
@ -208,7 +206,7 @@ Segments model_boundary(const EigenMesh3D& emesh, double offs)
|
||||
{
|
||||
Segments ret;
|
||||
Polygons pp;
|
||||
pp.reserve(emesh.F.rows());
|
||||
pp.reserve(size_t(emesh.F.rows()));
|
||||
|
||||
for (int i = 0; i < emesh.F.rows(); i++) {
|
||||
auto trindex = emesh.F.row(i);
|
||||
|
@ -19,7 +19,6 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using SlicedModel = SlicedSupports;
|
||||
using SupportTreePtr = std::unique_ptr<sla::SLASupportTree>;
|
||||
|
||||
class SLAPrintObject::SupportData {
|
||||
@ -413,6 +412,12 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) {
|
||||
|
||||
return scfg;
|
||||
}
|
||||
|
||||
void swapXY(ExPolygon& expoly) {
|
||||
for(auto& p : expoly.contour.points) std::swap(p(X), p(Y));
|
||||
for(auto& h : expoly.holes) for(auto& p : h.points) std::swap(p(X), p(Y));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SLAPrint::process()
|
||||
@ -445,7 +450,7 @@ void SLAPrint::process()
|
||||
|
||||
// Slicing the model object. This method is oversimplified and needs to
|
||||
// be compared with the fff slicing algorithm for verification
|
||||
auto slice_model = [this, ilh, ilhd](SLAPrintObject& po) {
|
||||
auto slice_model = [this, ilh](SLAPrintObject& po) {
|
||||
double lh = po.m_config.layer_height.getFloat();
|
||||
|
||||
TriangleMesh mesh = po.transformed_mesh();
|
||||
@ -469,7 +474,7 @@ void SLAPrint::process()
|
||||
for(float h = minZ + ilh; h < maxZ; h += flh)
|
||||
if(h >= gnd) heights.emplace_back(h);
|
||||
|
||||
auto& layers = po.m_model_slices;
|
||||
auto& layers = po.m_model_slices; layers.clear();
|
||||
slicer.slice(heights, &layers, [this](){ throw_if_canceled(); });
|
||||
};
|
||||
|
||||
@ -504,8 +509,6 @@ void SLAPrint::process()
|
||||
return;
|
||||
}
|
||||
|
||||
auto& emesh = po.m_supportdata->emesh;
|
||||
auto& pts = po.m_supportdata->support_points; // nowhere filled yet
|
||||
try {
|
||||
sla::SupportConfig scfg = make_support_cfg(po.m_config);
|
||||
sla::Controller ctl;
|
||||
@ -528,8 +531,14 @@ void SLAPrint::process()
|
||||
ctl.cancelfn = [this]() { throw_if_canceled(); };
|
||||
|
||||
po.m_supportdata->support_tree_ptr.reset(
|
||||
new SLASupportTree(pts, emesh, scfg, ctl));
|
||||
new SLASupportTree(po.m_supportdata->support_points,
|
||||
po.m_supportdata->emesh, scfg, ctl));
|
||||
|
||||
// Create the unified mesh
|
||||
auto rc = SlicingStatus::RELOAD_SCENE;
|
||||
set_status(-1, L("Visualizing supports"));
|
||||
po.m_supportdata->support_tree_ptr->merged_mesh();
|
||||
set_status(-1, L("Visualizing supports"), rc);
|
||||
} catch(sla::SLASupportsStoppedException&) {
|
||||
// no need to rethrow
|
||||
// throw_if_canceled();
|
||||
@ -560,18 +569,17 @@ void SLAPrint::process()
|
||||
auto&& trmesh = po.transformed_mesh();
|
||||
|
||||
// This call can get pretty time consuming
|
||||
if(elevation < pad_h) sla::base_plate(trmesh, bp,
|
||||
float(pad_h), float(lh));
|
||||
auto thrfn = [this](){ throw_if_canceled(); };
|
||||
|
||||
po.m_supportdata->support_tree_ptr->add_pad(bp, wt, h, md, er);
|
||||
if(elevation < pad_h)
|
||||
sla::base_plate(trmesh, bp, float(pad_h), float(lh),
|
||||
thrfn);
|
||||
|
||||
pcfg.throw_on_cancel = thrfn;
|
||||
po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg);
|
||||
}
|
||||
|
||||
// if the base pool (which means also the support tree) is
|
||||
// done, do a refresh when indicating progress. Now the
|
||||
// geometries for the supports and the optional base pad are
|
||||
// ready. We can grant access for the control thread to read
|
||||
// the geometries, but first we have to update the caches:
|
||||
po.support_mesh(); /*po->pad_mesh();*/
|
||||
po.throw_if_canceled();
|
||||
auto rc = SlicingStatus::RELOAD_SCENE;
|
||||
set_status(-1, L("Visualizing supports"), rc);
|
||||
};
|
||||
@ -589,12 +597,11 @@ void SLAPrint::process()
|
||||
|
||||
// We have the layer polygon collection but we need to unite them into
|
||||
// an index where the key is the height level in discrete levels (clipper)
|
||||
auto index_slices = [this, ilh, ilhd](SLAPrintObject& po) {
|
||||
auto index_slices = [ilhd](SLAPrintObject& po) {
|
||||
po.m_slice_index.clear();
|
||||
auto sih = LevelID(scale_(ilh));
|
||||
auto sih = LevelID(scale_(ilhd));
|
||||
|
||||
// For all print objects, go through its initial layers and place them
|
||||
// into the layers hash
|
||||
// Establish the slice grid boundaries
|
||||
auto bb = po.transformed_mesh().bounding_box();
|
||||
double modelgnd = bb.min(Z);
|
||||
double elevation = po.get_elevation();
|
||||
@ -610,49 +617,46 @@ void SLAPrint::process()
|
||||
// It is important that the next levels match the levels in
|
||||
// model_slice method. Only difference is that here it works with
|
||||
// scaled coordinates
|
||||
auto& levelids = po.m_level_ids; levelids.clear();
|
||||
if(sminZ >= smodelgnd) levelids.emplace_back(sminZ);
|
||||
po.m_level_ids.clear();
|
||||
if(sminZ >= smodelgnd) po.m_level_ids.emplace_back(sminZ);
|
||||
for(LevelID h = sminZ + sih; h < smaxZ; h += slh)
|
||||
if(h >= smodelgnd) levelids.emplace_back(h);
|
||||
if(h >= smodelgnd) po.m_level_ids.emplace_back(h);
|
||||
|
||||
SlicedModel & oslices = po.m_model_slices;
|
||||
std::vector<ExPolygons>& oslices = po.m_model_slices;
|
||||
|
||||
// If everything went well this code should not run at all, but
|
||||
// let's be robust...
|
||||
// assert(levelids.size() == oslices.size());
|
||||
if(levelids.size() < oslices.size()) { // extend the levels until...
|
||||
if(po.m_level_ids.size() < oslices.size()) { // extend the levels until...
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Height level mismatch at rasterization!\n";
|
||||
|
||||
LevelID lastlvl = levelids.back();
|
||||
while(levelids.size() < oslices.size()) {
|
||||
LevelID lastlvl = po.m_level_ids.back();
|
||||
while(po.m_level_ids.size() < oslices.size()) {
|
||||
lastlvl += slh;
|
||||
levelids.emplace_back(lastlvl);
|
||||
po.m_level_ids.emplace_back(lastlvl);
|
||||
}
|
||||
}
|
||||
|
||||
// shortcut for empty index into the slice vectors
|
||||
static const auto EMPTY_SLICE = SLAPrintObject::SliceRecord::NONE;
|
||||
|
||||
for(int i = 0; i < oslices.size(); ++i) {
|
||||
LevelID h = levelids[i];
|
||||
for(size_t i = 0; i < oslices.size(); ++i) {
|
||||
LevelID h = po.m_level_ids[i];
|
||||
|
||||
float fh = float(double(h) * SCALING_FACTOR);
|
||||
|
||||
// now for the public slice index:
|
||||
SLAPrintObject::SliceRecord& sr = po.m_slice_index[fh];
|
||||
// There should be only one slice layer for each print object
|
||||
assert(sr.model_slices_idx == EMPTY_SLICE);
|
||||
assert(sr.model_slices_idx == SLAPrintObject::SliceRecord::NONE);
|
||||
sr.model_slices_idx = i;
|
||||
}
|
||||
|
||||
if(po.m_supportdata) { // deal with the support slices if present
|
||||
auto& sslices = po.m_supportdata->support_slices;
|
||||
std::vector<ExPolygons>& sslices = po.m_supportdata->support_slices;
|
||||
po.m_supportdata->level_ids.clear();
|
||||
po.m_supportdata->level_ids.reserve(sslices.size());
|
||||
|
||||
for(int i = 0; i < sslices.size(); ++i) {
|
||||
for(int i = 0; i < int(sslices.size()); ++i) {
|
||||
int a = i == 0 ? 0 : 1;
|
||||
int b = i == 0 ? 0 : i - 1;
|
||||
LevelID h = sminZ + a * sih + b * slh;
|
||||
@ -661,16 +665,14 @@ void SLAPrint::process()
|
||||
float fh = float(double(h) * SCALING_FACTOR);
|
||||
|
||||
SLAPrintObject::SliceRecord& sr = po.m_slice_index[fh];
|
||||
assert(sr.support_slices_idx == EMPTY_SLICE);
|
||||
sr.support_slices_idx = i;
|
||||
assert(sr.support_slices_idx == SLAPrintObject::SliceRecord::NONE);
|
||||
sr.support_slices_idx = SLAPrintObject::SliceRecord::Idx(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto& levels = m_printer_input;
|
||||
|
||||
// Rasterizing the model objects, and their supports
|
||||
auto rasterize = [this, ilh, ilhd, max_objstatus, &levels]() {
|
||||
auto rasterize = [this, max_objstatus]() {
|
||||
if(canceled()) return;
|
||||
|
||||
// clear the rasterizer input
|
||||
@ -678,32 +680,39 @@ void SLAPrint::process()
|
||||
|
||||
for(SLAPrintObject * o : m_objects) {
|
||||
auto& po = *o;
|
||||
SlicedModel & oslices = po.m_model_slices;
|
||||
std::vector<ExPolygons>& oslices = po.m_model_slices;
|
||||
|
||||
// We need to adjust the min Z level of the slices to be zero
|
||||
LevelID smfirst = po.m_supportdata? po.m_supportdata->level_ids.front() : 0;
|
||||
LevelID mfirst = po.m_level_ids.front();
|
||||
LevelID smfirst =
|
||||
po.m_supportdata && !po.m_supportdata->level_ids.empty() ?
|
||||
po.m_supportdata->level_ids.front() : 0;
|
||||
LevelID mfirst = po.m_level_ids.empty()? 0 : po.m_level_ids.front();
|
||||
LevelID gndlvl = -(std::min(smfirst, mfirst));
|
||||
|
||||
// now merge this object's support and object slices with the rest
|
||||
// of the print object slices
|
||||
|
||||
for(int i = 0; i < oslices.size(); ++i) {
|
||||
auto& lyrs = levels[gndlvl + po.m_level_ids[i]];
|
||||
for(size_t i = 0; i < oslices.size(); ++i) {
|
||||
auto& lyrs = m_printer_input[gndlvl + po.m_level_ids[i]];
|
||||
lyrs.emplace_back(oslices[i], po.m_instances);
|
||||
}
|
||||
|
||||
if(!po.m_supportdata) continue;
|
||||
auto& sslices = po.m_supportdata->support_slices;
|
||||
for(int i = 0; i < sslices.size(); ++i) {
|
||||
auto& lyrs = levels[gndlvl + po.m_supportdata->level_ids[i]];
|
||||
std::vector<ExPolygons>& sslices = po.m_supportdata->support_slices;
|
||||
for(size_t i = 0; i < sslices.size(); ++i) {
|
||||
LayerRefs& lyrs =
|
||||
m_printer_input[gndlvl + po.m_supportdata->level_ids[i]];
|
||||
lyrs.emplace_back(sslices[i], po.m_instances);
|
||||
}
|
||||
}
|
||||
|
||||
// collect all the keys
|
||||
std::vector<long long> keys; keys.reserve(levels.size());
|
||||
for(auto& e : levels) keys.emplace_back(e.first);
|
||||
std::vector<long long> keys; keys.reserve(m_printer_input.size());
|
||||
for(auto& e : m_printer_input) keys.emplace_back(e.first);
|
||||
|
||||
// If the raster has vertical orientation, we will flip the coordinates
|
||||
bool flpXY = m_printer_config.display_orientation.getInt() ==
|
||||
SLADisplayOrientation::sladoPortrait;
|
||||
|
||||
{ // create a raster printer for the current print parameters
|
||||
// I don't know any better
|
||||
@ -713,18 +722,22 @@ void SLAPrint::process()
|
||||
|
||||
double w = printcfg.display_width.getFloat();
|
||||
double h = printcfg.display_height.getFloat();
|
||||
unsigned pw = printcfg.display_pixels_x.getInt();
|
||||
unsigned ph = printcfg.display_pixels_y.getInt();
|
||||
auto pw = unsigned(printcfg.display_pixels_x.getInt());
|
||||
auto ph = unsigned(printcfg.display_pixels_y.getInt());
|
||||
double lh = ocfg.layer_height.getFloat();
|
||||
double exp_t = matcfg.exposure_time.getFloat();
|
||||
double iexp_t = matcfg.initial_exposure_time.getFloat();
|
||||
|
||||
m_printer.reset(new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t));
|
||||
if(flpXY) { std::swap(w, h); std::swap(pw, ph); }
|
||||
|
||||
m_printer.reset(new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t,
|
||||
flpXY? SLAPrinter::RO_PORTRAIT :
|
||||
SLAPrinter::RO_LANDSCAPE));
|
||||
}
|
||||
|
||||
// Allocate space for all the layers
|
||||
SLAPrinter& printer = *m_printer;
|
||||
auto lvlcnt = unsigned(levels.size());
|
||||
auto lvlcnt = unsigned(m_printer_input.size());
|
||||
printer.layers(lvlcnt);
|
||||
|
||||
unsigned slot = PRINT_STEP_LEVELS[slapsRasterize];
|
||||
@ -734,12 +747,12 @@ void SLAPrint::process()
|
||||
|
||||
// procedure to process one height level. This will run in parallel
|
||||
auto lvlfn =
|
||||
[this, &slck, &keys, &levels, &printer, slot, sd, ist, &pst]
|
||||
[this, &slck, &keys, &printer, slot, sd, ist, &pst, flpXY]
|
||||
(unsigned level_id)
|
||||
{
|
||||
if(canceled()) return;
|
||||
|
||||
LayerRefs& lrange = levels[keys[level_id]];
|
||||
LayerRefs& lrange = m_printer_input[keys[level_id]];
|
||||
|
||||
// Switch to the appropriate layer in the printer
|
||||
printer.begin_layer(level_id);
|
||||
@ -754,8 +767,9 @@ void SLAPrint::process()
|
||||
for(ExPolygon slice : sl) {
|
||||
// The order is important here:
|
||||
// apply rotation before translation...
|
||||
slice.rotate(cp.rotation);
|
||||
slice.rotate(double(cp.rotation));
|
||||
slice.translate(cp.shift(X), cp.shift(Y));
|
||||
if(flpXY) swapXY(slice);
|
||||
printer.draw_polygon(slice, level_id);
|
||||
}
|
||||
}
|
||||
@ -765,7 +779,7 @@ void SLAPrint::process()
|
||||
printer.finish_layer(level_id);
|
||||
|
||||
// Status indication
|
||||
auto st = ist + unsigned(sd*level_id*slot/levels.size());
|
||||
auto st = ist + unsigned(sd*level_id*slot/m_printer_input.size());
|
||||
{ std::lock_guard<SpinMutex> lck(slck);
|
||||
if( st > pst) {
|
||||
set_status(int(st), PRINT_STEP_LABELS[slapsRasterize]);
|
||||
@ -886,6 +900,7 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
|
||||
"display_height",
|
||||
"display_pixels_x",
|
||||
"display_pixels_y",
|
||||
"display_orientation",
|
||||
"printer_correction"
|
||||
};
|
||||
|
||||
@ -926,6 +941,18 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
// Returns true if an object step is done on all objects and there's at least one object.
|
||||
bool SLAPrint::is_step_done(SLAPrintObjectStep step) const
|
||||
{
|
||||
if (m_objects.empty())
|
||||
return false;
|
||||
tbb::mutex::scoped_lock lock(this->state_mutex());
|
||||
for (const SLAPrintObject *object : m_objects)
|
||||
if (! object->m_state.is_done_unguarded(step))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object):
|
||||
Inherited(print, model_object),
|
||||
m_stepmask(slaposCount, true),
|
||||
@ -1098,7 +1125,9 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const
|
||||
const TriangleMesh& SLAPrintObject::support_mesh() const
|
||||
{
|
||||
if(m_config.supports_enable.getBool() && m_supportdata &&
|
||||
m_supportdata->support_tree_ptr) return m_supportdata->support_tree_ptr->merged_mesh();
|
||||
m_supportdata->support_tree_ptr) {
|
||||
return m_supportdata->support_tree_ptr->merged_mesh();
|
||||
}
|
||||
|
||||
return EMPTY_MESH;
|
||||
}
|
||||
|
@ -177,14 +177,18 @@ private: // Prevents erroneous use by other classes.
|
||||
public:
|
||||
SLAPrint(): m_stepmask(slapsCount, true) {}
|
||||
|
||||
virtual ~SLAPrint() { this->clear(); }
|
||||
virtual ~SLAPrint() override { this->clear(); }
|
||||
|
||||
PrinterTechnology technology() const noexcept { return ptSLA; }
|
||||
PrinterTechnology technology() const noexcept override { return ptSLA; }
|
||||
|
||||
void clear() override;
|
||||
bool empty() const override { return m_objects.empty(); }
|
||||
ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override;
|
||||
void process() override;
|
||||
// Returns true if an object step is done on all objects and there's at least one object.
|
||||
bool is_step_done(SLAPrintObjectStep step) const;
|
||||
// Returns true if the last step was finished with success.
|
||||
bool finished() const override { return this->is_step_done(slaposIndexSlices); }
|
||||
|
||||
template<class Fmt> void export_raster(const std::string& fname) {
|
||||
if(m_printer) m_printer->save<Fmt>(fname);
|
||||
|
@ -458,6 +458,8 @@ Polygons collect_slices_outer(const Layer &layer)
|
||||
class SupportGridPattern
|
||||
{
|
||||
public:
|
||||
// Achtung! The support_polygons need to be trimmed by trimming_polygons, otherwise
|
||||
// the selection by island_samples (see the island_samples() method) will not work!
|
||||
SupportGridPattern(
|
||||
// Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy)
|
||||
const Polygons &support_polygons,
|
||||
@ -485,6 +487,18 @@ public:
|
||||
bbox.align_to_grid(grid_resolution);
|
||||
m_grid.set_bbox(bbox);
|
||||
m_grid.create(*m_support_polygons, grid_resolution);
|
||||
#if 0
|
||||
if (m_grid.has_intersecting_edges()) {
|
||||
// EdgeGrid fails to produce valid signed distance function for self-intersecting polygons.
|
||||
m_support_polygons_rotated = simplify_polygons(*m_support_polygons);
|
||||
m_support_polygons = &m_support_polygons_rotated;
|
||||
m_grid.set_bbox(bbox);
|
||||
m_grid.create(*m_support_polygons, grid_resolution);
|
||||
// assert(! m_grid.has_intersecting_edges());
|
||||
printf("SupportGridPattern: fixing polygons with intersection %s\n",
|
||||
m_grid.has_intersecting_edges() ? "FAILED" : "SUCCEEDED");
|
||||
}
|
||||
#endif
|
||||
m_grid.calculate_sdf();
|
||||
// Sample a single point per input support polygon, keep it as a reference to maintain corresponding
|
||||
// polygons if ever these polygons get split into parts by the trimming polygons.
|
||||
@ -499,9 +513,12 @@ public:
|
||||
{
|
||||
// Generate islands, so each island may be tested for overlap with m_island_samples.
|
||||
assert(std::abs(2 * offset_in_grid) < m_grid.resolution());
|
||||
ExPolygons islands = diff_ex(
|
||||
m_grid.contours_simplified(offset_in_grid, fill_holes),
|
||||
*m_trimming_polygons, false);
|
||||
#ifdef SLIC3R_DEBUG
|
||||
Polygons support_polygons_simplified = m_grid.contours_simplified(offset_in_grid, fill_holes);
|
||||
ExPolygons islands = diff_ex(support_polygons_simplified, *m_trimming_polygons, false);
|
||||
#else
|
||||
ExPolygons islands = diff_ex(m_grid.contours_simplified(offset_in_grid, fill_holes), *m_trimming_polygons, false);
|
||||
#endif
|
||||
|
||||
// Extract polygons, which contain some of the m_island_samples.
|
||||
Polygons out;
|
||||
@ -551,7 +568,10 @@ public:
|
||||
bbox.merge(get_extents(islands));
|
||||
if (!out.empty())
|
||||
bbox.merge(get_extents(out));
|
||||
if (!support_polygons_simplified.empty())
|
||||
bbox.merge(get_extents(support_polygons_simplified));
|
||||
SVG svg(debug_out_path("extract_support_from_grid_trimmed-%d.svg", iRun).c_str(), bbox);
|
||||
svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f);
|
||||
svg.draw(islands, "red", 0.5f);
|
||||
svg.draw(union_ex(out), "green", 0.5f);
|
||||
svg.draw(union_ex(*m_support_polygons), "blue", 0.5f);
|
||||
@ -568,7 +588,121 @@ public:
|
||||
return out;
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
void serialize(const std::string &path)
|
||||
{
|
||||
FILE *file = ::fopen(path.c_str(), "wb");
|
||||
::fwrite(&m_support_spacing, 8, 1, file);
|
||||
::fwrite(&m_support_angle, 8, 1, file);
|
||||
uint32_t n_polygons = m_support_polygons->size();
|
||||
::fwrite(&n_polygons, 4, 1, file);
|
||||
for (uint32_t i = 0; i < n_polygons; ++ i) {
|
||||
const Polygon &poly = (*m_support_polygons)[i];
|
||||
uint32_t n_points = poly.size();
|
||||
::fwrite(&n_points, 4, 1, file);
|
||||
for (uint32_t j = 0; j < n_points; ++ j) {
|
||||
const Point &pt = poly.points[j];
|
||||
::fwrite(&pt.x, sizeof(coord_t), 1, file);
|
||||
::fwrite(&pt.y, sizeof(coord_t), 1, file);
|
||||
}
|
||||
}
|
||||
n_polygons = m_trimming_polygons->size();
|
||||
::fwrite(&n_polygons, 4, 1, file);
|
||||
for (uint32_t i = 0; i < n_polygons; ++ i) {
|
||||
const Polygon &poly = (*m_trimming_polygons)[i];
|
||||
uint32_t n_points = poly.size();
|
||||
::fwrite(&n_points, 4, 1, file);
|
||||
for (uint32_t j = 0; j < n_points; ++ j) {
|
||||
const Point &pt = poly.points[j];
|
||||
::fwrite(&pt.x, sizeof(coord_t), 1, file);
|
||||
::fwrite(&pt.y, sizeof(coord_t), 1, file);
|
||||
}
|
||||
}
|
||||
::fclose(file);
|
||||
}
|
||||
|
||||
static SupportGridPattern deserialize(const std::string &path, int which = -1)
|
||||
{
|
||||
SupportGridPattern out;
|
||||
out.deserialize_(path, which);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Deserialization constructor
|
||||
bool deserialize_(const std::string &path, int which = -1)
|
||||
{
|
||||
FILE *file = ::fopen(path.c_str(), "rb");
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
|
||||
m_support_polygons = &m_support_polygons_deserialized;
|
||||
m_trimming_polygons = &m_trimming_polygons_deserialized;
|
||||
|
||||
::fread(&m_support_spacing, 8, 1, file);
|
||||
::fread(&m_support_angle, 8, 1, file);
|
||||
//FIXME
|
||||
//m_support_spacing *= 0.01 / 2;
|
||||
uint32_t n_polygons;
|
||||
::fread(&n_polygons, 4, 1, file);
|
||||
m_support_polygons_deserialized.reserve(n_polygons);
|
||||
int32_t scale = 1;
|
||||
for (uint32_t i = 0; i < n_polygons; ++ i) {
|
||||
Polygon poly;
|
||||
uint32_t n_points;
|
||||
::fread(&n_points, 4, 1, file);
|
||||
poly.points.reserve(n_points);
|
||||
for (uint32_t j = 0; j < n_points; ++ j) {
|
||||
coord_t x, y;
|
||||
::fread(&x, sizeof(coord_t), 1, file);
|
||||
::fread(&y, sizeof(coord_t), 1, file);
|
||||
poly.points.emplace_back(Point(x * scale, y * scale));
|
||||
}
|
||||
if (which == -1 || which == i)
|
||||
m_support_polygons_deserialized.emplace_back(std::move(poly));
|
||||
printf("Polygon %d, area: %lf\n", i, area(poly.points));
|
||||
}
|
||||
::fread(&n_polygons, 4, 1, file);
|
||||
m_trimming_polygons_deserialized.reserve(n_polygons);
|
||||
for (uint32_t i = 0; i < n_polygons; ++ i) {
|
||||
Polygon poly;
|
||||
uint32_t n_points;
|
||||
::fread(&n_points, 4, 1, file);
|
||||
poly.points.reserve(n_points);
|
||||
for (uint32_t j = 0; j < n_points; ++ j) {
|
||||
coord_t x, y;
|
||||
::fread(&x, sizeof(coord_t), 1, file);
|
||||
::fread(&y, sizeof(coord_t), 1, file);
|
||||
poly.points.emplace_back(Point(x * scale, y * scale));
|
||||
}
|
||||
m_trimming_polygons_deserialized.emplace_back(std::move(poly));
|
||||
}
|
||||
::fclose(file);
|
||||
|
||||
m_support_polygons_deserialized = simplify_polygons(m_support_polygons_deserialized, false);
|
||||
//m_support_polygons_deserialized = to_polygons(union_ex(m_support_polygons_deserialized, false));
|
||||
|
||||
// Create an EdgeGrid, initialize it with projection, initialize signed distance field.
|
||||
coord_t grid_resolution = coord_t(scale_(m_support_spacing));
|
||||
BoundingBox bbox = get_extents(*m_support_polygons);
|
||||
bbox.offset(20);
|
||||
bbox.align_to_grid(grid_resolution);
|
||||
m_grid.set_bbox(bbox);
|
||||
m_grid.create(*m_support_polygons, grid_resolution);
|
||||
m_grid.calculate_sdf();
|
||||
// Sample a single point per input support polygon, keep it as a reference to maintain corresponding
|
||||
// polygons if ever these polygons get split into parts by the trimming polygons.
|
||||
m_island_samples = island_samples(*m_support_polygons);
|
||||
return true;
|
||||
}
|
||||
|
||||
const Polygons& support_polygons() const { return *m_support_polygons; }
|
||||
const Polygons& trimming_polygons() const { return *m_trimming_polygons; }
|
||||
const EdgeGrid::Grid& grid() const { return m_grid; }
|
||||
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
|
||||
private:
|
||||
SupportGridPattern() {}
|
||||
SupportGridPattern& operator=(const SupportGridPattern &rhs);
|
||||
|
||||
#if 0
|
||||
@ -639,6 +773,12 @@ private:
|
||||
// Internal sample points of supporting expolygons. These internal points are used to pick regions corresponding
|
||||
// to the initial supporting regions, after these regions werre grown and possibly split to many by the trimming polygons.
|
||||
Points m_island_samples;
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
// support for deserialization of m_support_polygons, m_trimming_polygons
|
||||
Polygons m_support_polygons_deserialized;
|
||||
Polygons m_trimming_polygons_deserialized;
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
};
|
||||
|
||||
namespace SupportMaterialInternal {
|
||||
@ -783,17 +923,40 @@ namespace SupportMaterialInternal {
|
||||
if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1)
|
||||
polygons_append(bridges, surface.expolygon);
|
||||
//FIXME add the gap filled areas. Extrude the gaps with a bridge flow?
|
||||
contact_polygons = diff(contact_polygons, bridges, true);
|
||||
// Add the bridge anchors into the region.
|
||||
// Remove the unsupported ends of the bridges from the bridged areas.
|
||||
//FIXME add supports at regular intervals to support long bridges!
|
||||
polygons_append(contact_polygons,
|
||||
intersection(
|
||||
bridges = diff(bridges,
|
||||
// Offset unsupported edges into polygons.
|
||||
offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS),
|
||||
bridges));
|
||||
offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
// Remove bridged areas from the supported areas.
|
||||
contact_polygons = diff(contact_polygons, bridges, true);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
static int Test()
|
||||
{
|
||||
// for (int i = 0; i < 30; ++ i)
|
||||
{
|
||||
int i = -1;
|
||||
// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000-prev.bin", i);
|
||||
// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000.bin", i);
|
||||
auto grid = SupportGridPattern::deserialize("d:\\temp\\support-top-contacts-final-run1-layer27-z5.650000.bin", i);
|
||||
std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersections = grid.grid().intersecting_edges();
|
||||
if (! intersections.empty())
|
||||
printf("Intersections between contours!\n");
|
||||
Slic3r::export_intersections_to_svg("d:\\temp\\support_polygon_intersections.svg", grid.support_polygons());
|
||||
Slic3r::SVG::export_expolygons("d:\\temp\\support_polygons.svg", union_ex(grid.support_polygons(), false));
|
||||
Slic3r::SVG::export_expolygons("d:\\temp\\trimming_polygons.svg", union_ex(grid.trimming_polygons(), false));
|
||||
Polygons extracted = grid.extract_support(scale_(0.21 / 2), true);
|
||||
Slic3r::SVG::export_expolygons("d:\\temp\\extracted.svg", union_ex(extracted, false));
|
||||
printf("hu!");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static int run_support_test = Test();
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
|
||||
// Generate top contact layers supporting overhangs.
|
||||
// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined.
|
||||
// If supports over bed surface only are requested, don't generate contact layers over an object.
|
||||
@ -1096,6 +1259,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
||||
}
|
||||
}
|
||||
|
||||
// Achtung! The contact_polygons need to be trimmed by slices_margin_cached, otherwise
|
||||
// the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work!
|
||||
SupportGridPattern support_grid_pattern(
|
||||
// Support islands, to be stretched into a grid.
|
||||
contact_polygons,
|
||||
@ -1114,9 +1279,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
||||
// Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions.
|
||||
Polygons dense_interface_polygons = diff(overhang_polygons,
|
||||
offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
// offset(lower_layer_polygons, no_interface_offset * 0.6f, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
if (! dense_interface_polygons.empty()) {
|
||||
//FIXME do it for the bridges only?
|
||||
dense_interface_polygons =
|
||||
// Achtung! The dense_interface_polygons need to be trimmed by slices_margin_cached, otherwise
|
||||
// the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work!
|
||||
diff(
|
||||
// Regularize the contour.
|
||||
offset(dense_interface_polygons, no_interface_offset * 0.1f),
|
||||
slices_margin_cached);
|
||||
SupportGridPattern support_grid_pattern(
|
||||
// Support islands, to be stretched into a grid.
|
||||
dense_interface_polygons,
|
||||
@ -1126,8 +1296,34 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
||||
m_object_config->support_material_spacing.value + m_support_material_flow.spacing(),
|
||||
Geometry::deg2rad(m_object_config->support_material_angle.value));
|
||||
new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false);
|
||||
#ifdef SLIC3R_DEBUG
|
||||
{
|
||||
support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z));
|
||||
|
||||
BoundingBox bbox = get_extents(contact_polygons);
|
||||
bbox.merge(get_extents(new_layer.polygons));
|
||||
::Slic3r::SVG svg(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z));
|
||||
svg.draw(union_ex(*new_layer.contact_polygons, false), "gray", 0.5f);
|
||||
svg.draw(union_ex(contact_polygons, false), "blue", 0.5f);
|
||||
svg.draw(union_ex(dense_interface_polygons, false), "green", 0.5f);
|
||||
svg.draw(union_ex(new_layer.polygons, true), "red", 0.5f);
|
||||
svg.draw_outline(union_ex(new_layer.polygons, true), "black", "black", scale_(0.1f));
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
}
|
||||
}
|
||||
#ifdef SLIC3R_DEBUG
|
||||
{
|
||||
BoundingBox bbox = get_extents(contact_polygons);
|
||||
bbox.merge(get_extents(new_layer.polygons));
|
||||
::Slic3r::SVG svg(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z));
|
||||
svg.draw(union_ex(*new_layer.contact_polygons, false), "gray", 0.5f);
|
||||
svg.draw(union_ex(contact_polygons, false), "blue", 0.5f);
|
||||
svg.draw(union_ex(overhang_polygons, false), "green", 0.5f);
|
||||
svg.draw(union_ex(new_layer.polygons, true), "red", 0.5f);
|
||||
svg.draw_outline(union_ex(new_layer.polygons, true), "black", "black", scale_(0.1f));
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
|
||||
// Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded.
|
||||
// Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons.
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
// Shows camera target in the 3D scene
|
||||
#define ENABLE_SHOW_CAMERA_TARGET 0
|
||||
// Log debug messages to console when changing selection
|
||||
#define ENABLE_SELECTION_DEBUG_OUTPUT 0
|
||||
|
||||
//=============
|
||||
// 1.42.0 techs
|
||||
|
@ -1212,6 +1212,345 @@ static inline void remove_tangent_edges(std::vector<IntersectionLine> &lines)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct OpenPolyline {
|
||||
OpenPolyline() {};
|
||||
OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) :
|
||||
start(start), end(end), points(std::move(points)), consumed(false) { this->length = Slic3r::length(this->points); }
|
||||
void reverse() {
|
||||
std::swap(start, end);
|
||||
std::reverse(points.begin(), points.end());
|
||||
}
|
||||
IntersectionReference start;
|
||||
IntersectionReference end;
|
||||
Points points;
|
||||
double length;
|
||||
bool consumed;
|
||||
};
|
||||
|
||||
// called by TriangleMeshSlicer::make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity.
|
||||
// Only connects segments crossing triangles of the same orientation.
|
||||
static void chain_lines_by_triangle_connectivity(std::vector<IntersectionLine> &lines, Polygons &loops, std::vector<OpenPolyline> &open_polylines)
|
||||
{
|
||||
// Build a map of lines by edge_a_id and a_id.
|
||||
std::vector<IntersectionLine*> by_edge_a_id;
|
||||
std::vector<IntersectionLine*> by_a_id;
|
||||
by_edge_a_id.reserve(lines.size());
|
||||
by_a_id.reserve(lines.size());
|
||||
for (IntersectionLine &line : lines) {
|
||||
if (! line.skip()) {
|
||||
if (line.edge_a_id != -1)
|
||||
by_edge_a_id.emplace_back(&line);
|
||||
if (line.a_id != -1)
|
||||
by_a_id.emplace_back(&line);
|
||||
}
|
||||
}
|
||||
auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; };
|
||||
auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; };
|
||||
std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower);
|
||||
std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower);
|
||||
// Chain the segments with a greedy algorithm, collect the loops and unclosed polylines.
|
||||
IntersectionLines::iterator it_line_seed = lines.begin();
|
||||
for (;;) {
|
||||
// take first spare line and start a new loop
|
||||
IntersectionLine *first_line = nullptr;
|
||||
for (; it_line_seed != lines.end(); ++ it_line_seed)
|
||||
if (it_line_seed->is_seed_candidate()) {
|
||||
//if (! it_line_seed->skip()) {
|
||||
first_line = &(*it_line_seed ++);
|
||||
break;
|
||||
}
|
||||
if (first_line == nullptr)
|
||||
break;
|
||||
first_line->set_skip();
|
||||
Points loop_pts;
|
||||
loop_pts.emplace_back(first_line->a);
|
||||
IntersectionLine *last_line = first_line;
|
||||
|
||||
/*
|
||||
printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
|
||||
first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id,
|
||||
first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y);
|
||||
*/
|
||||
|
||||
IntersectionLine key;
|
||||
for (;;) {
|
||||
// find a line starting where last one finishes
|
||||
IntersectionLine* next_line = nullptr;
|
||||
if (last_line->edge_b_id != -1) {
|
||||
key.edge_a_id = last_line->edge_b_id;
|
||||
auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower);
|
||||
if (it_begin != by_edge_a_id.end()) {
|
||||
auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower);
|
||||
for (auto it_line = it_begin; it_line != it_end; ++ it_line)
|
||||
if (! (*it_line)->skip()) {
|
||||
next_line = *it_line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_line == nullptr && last_line->b_id != -1) {
|
||||
key.a_id = last_line->b_id;
|
||||
auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower);
|
||||
if (it_begin != by_a_id.end()) {
|
||||
auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower);
|
||||
for (auto it_line = it_begin; it_line != it_end; ++ it_line)
|
||||
if (! (*it_line)->skip()) {
|
||||
next_line = *it_line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_line == nullptr) {
|
||||
// Check whether we closed this loop.
|
||||
if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) ||
|
||||
(first_line->a_id != -1 && first_line->a_id == last_line->b_id)) {
|
||||
// The current loop is complete. Add it to the output.
|
||||
loops.emplace_back(std::move(loop_pts));
|
||||
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
|
||||
printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size());
|
||||
#endif
|
||||
} else {
|
||||
// This is an open polyline. Add it to the list of open polylines. These open polylines will processed later.
|
||||
loop_pts.emplace_back(last_line->b);
|
||||
open_polylines.emplace_back(OpenPolyline(
|
||||
IntersectionReference(first_line->a_id, first_line->edge_a_id),
|
||||
IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
/*
|
||||
printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
|
||||
next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id,
|
||||
next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y);
|
||||
*/
|
||||
loop_pts.emplace_back(next_line->a);
|
||||
last_line = next_line;
|
||||
next_line->set_skip();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<OpenPolyline*> open_polylines_sorted(std::vector<OpenPolyline> &open_polylines, bool update_lengths)
|
||||
{
|
||||
std::vector<OpenPolyline*> out;
|
||||
out.reserve(open_polylines.size());
|
||||
for (OpenPolyline &opl : open_polylines)
|
||||
if (! opl.consumed) {
|
||||
if (update_lengths)
|
||||
opl.length = Slic3r::length(opl.points);
|
||||
out.emplace_back(&opl);
|
||||
}
|
||||
std::sort(out.begin(), out.end(), [](const OpenPolyline *lhs, const OpenPolyline *rhs){ return lhs->length > rhs->length; });
|
||||
return out;
|
||||
}
|
||||
|
||||
// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices.
|
||||
// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation.
|
||||
static void chain_open_polylines_exact(std::vector<OpenPolyline> &open_polylines, Polygons &loops, bool try_connect_reversed)
|
||||
{
|
||||
// Store the end points of open_polylines into vectors sorted
|
||||
struct OpenPolylineEnd {
|
||||
OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {}
|
||||
OpenPolyline *polyline;
|
||||
// Is it the start or end point?
|
||||
bool start;
|
||||
const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; }
|
||||
// Return a unique ID for the intersection point.
|
||||
// Return a positive id for a point, or a negative id for an edge.
|
||||
int id() const { const IntersectionReference &r = ipref(); return (r.point_id >= 0) ? r.point_id : - r.edge_id; }
|
||||
bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; }
|
||||
};
|
||||
auto by_id_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.id() < ope2.id(); };
|
||||
std::vector<OpenPolylineEnd> by_id;
|
||||
by_id.reserve(2 * open_polylines.size());
|
||||
for (OpenPolyline &opl : open_polylines) {
|
||||
if (opl.start.point_id != -1 || opl.start.edge_id != -1)
|
||||
by_id.emplace_back(OpenPolylineEnd(&opl, true));
|
||||
if (try_connect_reversed && (opl.end.point_id != -1 || opl.end.edge_id != -1))
|
||||
by_id.emplace_back(OpenPolylineEnd(&opl, false));
|
||||
}
|
||||
std::sort(by_id.begin(), by_id.end(), by_id_lower);
|
||||
// Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute).
|
||||
auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector<OpenPolylineEnd>::iterator {
|
||||
for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower);
|
||||
it != by_id.end() && it->id() == end.id(); ++ it)
|
||||
if (*it == end)
|
||||
return it;
|
||||
return by_id.end();
|
||||
};
|
||||
// Try to connect the loops.
|
||||
std::vector<OpenPolyline*> sorted_by_length = open_polylines_sorted(open_polylines, false);
|
||||
for (OpenPolyline *opl : sorted_by_length) {
|
||||
if (opl->consumed)
|
||||
continue;
|
||||
opl->consumed = true;
|
||||
OpenPolylineEnd end(opl, false);
|
||||
for (;;) {
|
||||
// find a line starting where last one finishes
|
||||
auto it_next_start = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower);
|
||||
for (; it_next_start != by_id.end() && it_next_start->id() == end.id(); ++ it_next_start)
|
||||
if (! it_next_start->polyline->consumed)
|
||||
goto found;
|
||||
// The current loop could not be closed. Unmark the segment.
|
||||
opl->consumed = false;
|
||||
break;
|
||||
found:
|
||||
// Attach this polyline to the end of the initial polyline.
|
||||
if (it_next_start->start) {
|
||||
auto it = it_next_start->polyline->points.begin();
|
||||
std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl->points));
|
||||
} else {
|
||||
auto it = it_next_start->polyline->points.rbegin();
|
||||
std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl->points));
|
||||
}
|
||||
opl->length += it_next_start->polyline->length;
|
||||
// Mark the next polyline as consumed.
|
||||
it_next_start->polyline->points.clear();
|
||||
it_next_start->polyline->length = 0.;
|
||||
it_next_start->polyline->consumed = true;
|
||||
if (try_connect_reversed) {
|
||||
// Running in a mode, where the polylines may be connected by mixing their orientations.
|
||||
// Update the end point lookup structure after the end point of the current polyline was extended.
|
||||
auto it_end = find_polyline_end(end);
|
||||
auto it_next_end = find_polyline_end(OpenPolylineEnd(it_next_start->polyline, !it_next_start->start));
|
||||
// Swap the end points of the current and next polyline, but keep the polyline ptr and the start flag.
|
||||
std::swap(opl->end, it_next_end->start ? it_next_end->polyline->start : it_next_end->polyline->end);
|
||||
// Swap the positions of OpenPolylineEnd structures in the sorted array to match their respective end point positions.
|
||||
std::swap(*it_end, *it_next_end);
|
||||
}
|
||||
// Check whether we closed this loop.
|
||||
if ((opl->start.edge_id != -1 && opl->start.edge_id == opl->end.edge_id) ||
|
||||
(opl->start.point_id != -1 && opl->start.point_id == opl->end.point_id)) {
|
||||
// The current loop is complete. Add it to the output.
|
||||
//assert(opl->points.front().point_id == opl->points.back().point_id);
|
||||
//assert(opl->points.front().edge_id == opl->points.back().edge_id);
|
||||
// Remove the duplicate last point.
|
||||
opl->points.pop_back();
|
||||
if (opl->points.size() >= 3) {
|
||||
if (try_connect_reversed && area(opl->points) < 0)
|
||||
// The closed polygon is patched from pieces with messed up orientation, therefore
|
||||
// the orientation of the patched up polygon is not known.
|
||||
// Orient the patched up polygons CCW. This heuristic may close some holes and cavities.
|
||||
std::reverse(opl->points.begin(), opl->points.end());
|
||||
loops.emplace_back(std::move(opl->points));
|
||||
}
|
||||
opl->points.clear();
|
||||
break;
|
||||
}
|
||||
// Continue with the current loop.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices,
|
||||
// possibly closing small gaps.
|
||||
// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation.
|
||||
static void chain_open_polylines_close_gaps(std::vector<OpenPolyline> &open_polylines, Polygons &loops, double max_gap, bool try_connect_reversed)
|
||||
{
|
||||
const coord_t max_gap_scaled = (coord_t)scale_(max_gap);
|
||||
|
||||
// Sort the open polylines by their length, so the new loops will be seeded from longer chains.
|
||||
// Update the polyline lengths, return only not yet consumed polylines.
|
||||
std::vector<OpenPolyline*> sorted_by_length = open_polylines_sorted(open_polylines, true);
|
||||
|
||||
// Store the end points of open_polylines into ClosestPointInRadiusLookup<OpenPolylineEnd>.
|
||||
struct OpenPolylineEnd {
|
||||
OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {}
|
||||
OpenPolyline *polyline;
|
||||
// Is it the start or end point?
|
||||
bool start;
|
||||
const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); }
|
||||
bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; }
|
||||
};
|
||||
struct OpenPolylineEndAccessor {
|
||||
const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); }
|
||||
};
|
||||
typedef ClosestPointInRadiusLookup<OpenPolylineEnd, OpenPolylineEndAccessor> ClosestPointLookupType;
|
||||
ClosestPointLookupType closest_end_point_lookup(max_gap_scaled);
|
||||
for (OpenPolyline *opl : sorted_by_length) {
|
||||
closest_end_point_lookup.insert(OpenPolylineEnd(opl, true));
|
||||
if (try_connect_reversed)
|
||||
closest_end_point_lookup.insert(OpenPolylineEnd(opl, false));
|
||||
}
|
||||
// Try to connect the loops.
|
||||
for (OpenPolyline *opl : sorted_by_length) {
|
||||
if (opl->consumed)
|
||||
continue;
|
||||
OpenPolylineEnd end(opl, false);
|
||||
if (try_connect_reversed)
|
||||
// The end point of this polyline will be modified, thus the following entry will become invalid. Remove it.
|
||||
closest_end_point_lookup.erase(end);
|
||||
opl->consumed = true;
|
||||
size_t n_segments_joined = 1;
|
||||
for (;;) {
|
||||
// Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed).
|
||||
std::pair<const OpenPolylineEnd*, double> next_start_and_dist = closest_end_point_lookup.find(end.point());
|
||||
const OpenPolylineEnd *next_start = next_start_and_dist.first;
|
||||
// Check whether we closed this loop.
|
||||
double current_loop_closing_distance2 = (opl->points.back() - opl->points.front()).cast<double>().squaredNorm();
|
||||
bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled);
|
||||
if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) {
|
||||
// Heuristics to decide, whether to close the loop, or connect another polyline.
|
||||
// One should avoid closing loops shorter than max_gap_scaled.
|
||||
loop_closed = sqrt(current_loop_closing_distance2) < 0.3 * length(opl->points);
|
||||
}
|
||||
if (loop_closed) {
|
||||
// Remove the start point of the current polyline from the lookup.
|
||||
// Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail.
|
||||
opl->consumed = false;
|
||||
closest_end_point_lookup.erase(OpenPolylineEnd(opl, true));
|
||||
if (current_loop_closing_distance2 == 0.) {
|
||||
// Remove the duplicate last point.
|
||||
opl->points.pop_back();
|
||||
} else {
|
||||
// The end points are different, keep both of them.
|
||||
}
|
||||
if (opl->points.size() >= 3) {
|
||||
if (try_connect_reversed && n_segments_joined > 1 && area(opl->points) < 0)
|
||||
// The closed polygon is patched from pieces with messed up orientation, therefore
|
||||
// the orientation of the patched up polygon is not known.
|
||||
// Orient the patched up polygons CCW. This heuristic may close some holes and cavities.
|
||||
std::reverse(opl->points.begin(), opl->points.end());
|
||||
loops.emplace_back(std::move(opl->points));
|
||||
}
|
||||
opl->points.clear();
|
||||
opl->consumed = true;
|
||||
break;
|
||||
}
|
||||
if (next_start == nullptr) {
|
||||
// The current loop could not be closed. Unmark the segment.
|
||||
opl->consumed = false;
|
||||
if (try_connect_reversed)
|
||||
// Re-insert the end point.
|
||||
closest_end_point_lookup.insert(OpenPolylineEnd(opl, false));
|
||||
break;
|
||||
}
|
||||
// Attach this polyline to the end of the initial polyline.
|
||||
if (next_start->start) {
|
||||
auto it = next_start->polyline->points.begin();
|
||||
if (*it == opl->points.back())
|
||||
++ it;
|
||||
std::copy(it, next_start->polyline->points.end(), back_inserter(opl->points));
|
||||
} else {
|
||||
auto it = next_start->polyline->points.rbegin();
|
||||
if (*it == opl->points.back())
|
||||
++ it;
|
||||
std::copy(it, next_start->polyline->points.rend(), back_inserter(opl->points));
|
||||
}
|
||||
++ n_segments_joined;
|
||||
// Remove the end points of the consumed polyline segment from the lookup.
|
||||
OpenPolyline *opl2 = next_start->polyline;
|
||||
closest_end_point_lookup.erase(OpenPolylineEnd(opl2, true));
|
||||
if (try_connect_reversed)
|
||||
closest_end_point_lookup.erase(OpenPolylineEnd(opl2, false));
|
||||
opl2->points.clear();
|
||||
opl2->consumed = true;
|
||||
// Continue with the current loop.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const
|
||||
{
|
||||
#if 0
|
||||
@ -1221,231 +1560,83 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo
|
||||
assert(l.a != l.b);
|
||||
#endif /* _DEBUG */
|
||||
|
||||
remove_tangent_edges(lines);
|
||||
// There should be no tangent edges, as the horizontal triangles are ignored and if two triangles touch at a cutting plane,
|
||||
// only the bottom triangle is considered to be cutting the plane.
|
||||
// remove_tangent_edges(lines);
|
||||
|
||||
struct OpenPolyline {
|
||||
OpenPolyline() {};
|
||||
OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) :
|
||||
start(start), end(end), points(std::move(points)), consumed(false) {}
|
||||
void reverse() {
|
||||
std::swap(start, end);
|
||||
std::reverse(points.begin(), points.end());
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
BoundingBox bbox_svg;
|
||||
{
|
||||
static int iRun = 0;
|
||||
for (const Line &line : lines) {
|
||||
bbox_svg.merge(line.a);
|
||||
bbox_svg.merge(line.b);
|
||||
}
|
||||
SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-raw_lines-%d.svg", iRun ++).c_str(), bbox_svg);
|
||||
for (const Line &line : lines)
|
||||
svg.draw(line);
|
||||
svg.Close();
|
||||
}
|
||||
IntersectionReference start;
|
||||
IntersectionReference end;
|
||||
Points points;
|
||||
bool consumed;
|
||||
};
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
std::vector<OpenPolyline> open_polylines;
|
||||
{
|
||||
// Build a map of lines by edge_a_id and a_id.
|
||||
std::vector<IntersectionLine*> by_edge_a_id;
|
||||
std::vector<IntersectionLine*> by_a_id;
|
||||
by_edge_a_id.reserve(lines.size());
|
||||
by_a_id.reserve(lines.size());
|
||||
for (IntersectionLine &line : lines) {
|
||||
if (! line.skip()) {
|
||||
if (line.edge_a_id != -1)
|
||||
by_edge_a_id.emplace_back(&line);
|
||||
if (line.a_id != -1)
|
||||
by_a_id.emplace_back(&line);
|
||||
}
|
||||
chain_lines_by_triangle_connectivity(lines, *loops, open_polylines);
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-%d.svg", iRun ++).c_str(), bbox_svg);
|
||||
svg.draw(union_ex(*loops));
|
||||
for (const OpenPolyline &pl : open_polylines)
|
||||
svg.draw(Polyline(pl.points), "red");
|
||||
svg.Close();
|
||||
}
|
||||
auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; };
|
||||
auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; };
|
||||
std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower);
|
||||
std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower);
|
||||
// Chain the segments with a greedy algorithm, collect the loops and unclosed polylines.
|
||||
IntersectionLines::iterator it_line_seed = lines.begin();
|
||||
for (;;) {
|
||||
// take first spare line and start a new loop
|
||||
IntersectionLine *first_line = nullptr;
|
||||
for (; it_line_seed != lines.end(); ++ it_line_seed)
|
||||
if (it_line_seed->is_seed_candidate()) {
|
||||
//if (! it_line_seed->skip()) {
|
||||
first_line = &(*it_line_seed ++);
|
||||
break;
|
||||
}
|
||||
if (first_line == nullptr)
|
||||
break;
|
||||
first_line->set_skip();
|
||||
Points loop_pts;
|
||||
loop_pts.emplace_back(first_line->a);
|
||||
IntersectionLine *last_line = first_line;
|
||||
|
||||
/*
|
||||
printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
|
||||
first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id,
|
||||
first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y);
|
||||
*/
|
||||
|
||||
IntersectionLine key;
|
||||
for (;;) {
|
||||
// find a line starting where last one finishes
|
||||
IntersectionLine* next_line = nullptr;
|
||||
if (last_line->edge_b_id != -1) {
|
||||
key.edge_a_id = last_line->edge_b_id;
|
||||
auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower);
|
||||
if (it_begin != by_edge_a_id.end()) {
|
||||
auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower);
|
||||
for (auto it_line = it_begin; it_line != it_end; ++ it_line)
|
||||
if (! (*it_line)->skip()) {
|
||||
next_line = *it_line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_line == nullptr && last_line->b_id != -1) {
|
||||
key.a_id = last_line->b_id;
|
||||
auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower);
|
||||
if (it_begin != by_a_id.end()) {
|
||||
auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower);
|
||||
for (auto it_line = it_begin; it_line != it_end; ++ it_line)
|
||||
if (! (*it_line)->skip()) {
|
||||
next_line = *it_line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_line == nullptr) {
|
||||
// Check whether we closed this loop.
|
||||
if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) ||
|
||||
(first_line->a_id != -1 && first_line->a_id == last_line->b_id)) {
|
||||
// The current loop is complete. Add it to the output.
|
||||
loops->emplace_back(std::move(loop_pts));
|
||||
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
|
||||
printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size());
|
||||
#endif
|
||||
} else {
|
||||
// This is an open polyline. Add it to the list of open polylines. These open polylines will processed later.
|
||||
loop_pts.emplace_back(last_line->b);
|
||||
open_polylines.emplace_back(OpenPolyline(
|
||||
IntersectionReference(first_line->a_id, first_line->edge_a_id),
|
||||
IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
/*
|
||||
printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
|
||||
next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id,
|
||||
next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y);
|
||||
*/
|
||||
loop_pts.emplace_back(next_line->a);
|
||||
last_line = next_line;
|
||||
next_line->set_skip();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// Now process the open polylines.
|
||||
if (! open_polylines.empty()) {
|
||||
// Store the end points of open_polylines into vectors sorted
|
||||
struct OpenPolylineEnd {
|
||||
OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {}
|
||||
OpenPolyline *polyline;
|
||||
// Is it the start or end point?
|
||||
bool start;
|
||||
const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; }
|
||||
int point_id() const { return ipref().point_id; }
|
||||
int edge_id () const { return ipref().edge_id; }
|
||||
};
|
||||
auto by_edge_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.edge_id() < ope2.edge_id(); };
|
||||
auto by_point_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.point_id() < ope2.point_id(); };
|
||||
std::vector<OpenPolylineEnd> by_edge_id;
|
||||
std::vector<OpenPolylineEnd> by_point_id;
|
||||
by_edge_id.reserve(2 * open_polylines.size());
|
||||
by_point_id.reserve(2 * open_polylines.size());
|
||||
for (OpenPolyline &opl : open_polylines) {
|
||||
if (opl.start.edge_id != -1)
|
||||
by_edge_id .emplace_back(OpenPolylineEnd(&opl, true));
|
||||
if (opl.end.edge_id != -1)
|
||||
by_edge_id .emplace_back(OpenPolylineEnd(&opl, false));
|
||||
if (opl.start.point_id != -1)
|
||||
by_point_id.emplace_back(OpenPolylineEnd(&opl, true));
|
||||
if (opl.end.point_id != -1)
|
||||
by_point_id.emplace_back(OpenPolylineEnd(&opl, false));
|
||||
}
|
||||
std::sort(by_edge_id .begin(), by_edge_id .end(), by_edge_lower);
|
||||
std::sort(by_point_id.begin(), by_point_id.end(), by_point_lower);
|
||||
// Do it in two rounds, first try to connect in the same direction only,
|
||||
// then try to connect the open polylines in reversed order as well.
|
||||
chain_open_polylines_exact(open_polylines, *loops, false);
|
||||
chain_open_polylines_exact(open_polylines, *loops, true);
|
||||
|
||||
// Try to connect the loops.
|
||||
for (OpenPolyline &opl : open_polylines) {
|
||||
if (opl.consumed)
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg);
|
||||
svg.draw(union_ex(*loops));
|
||||
for (const OpenPolyline &pl : open_polylines) {
|
||||
if (pl.points.empty())
|
||||
continue;
|
||||
opl.consumed = true;
|
||||
OpenPolylineEnd end(&opl, false);
|
||||
for (;;) {
|
||||
// find a line starting where last one finishes
|
||||
OpenPolylineEnd* next_start = nullptr;
|
||||
if (end.edge_id() != -1) {
|
||||
auto it_begin = std::lower_bound(by_edge_id.begin(), by_edge_id.end(), end, by_edge_lower);
|
||||
if (it_begin != by_edge_id.end()) {
|
||||
auto it_end = std::upper_bound(it_begin, by_edge_id.end(), end, by_edge_lower);
|
||||
for (auto it_edge = it_begin; it_edge != it_end; ++ it_edge)
|
||||
if (! it_edge->polyline->consumed) {
|
||||
next_start = &(*it_edge);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_start == nullptr && end.point_id() != -1) {
|
||||
auto it_begin = std::lower_bound(by_point_id.begin(), by_point_id.end(), end, by_point_lower);
|
||||
if (it_begin != by_point_id.end()) {
|
||||
auto it_end = std::upper_bound(it_begin, by_point_id.end(), end, by_point_lower);
|
||||
for (auto it_point = it_begin; it_point != it_end; ++ it_point)
|
||||
if (! it_point->polyline->consumed) {
|
||||
next_start = &(*it_point);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_start == nullptr) {
|
||||
// The current loop could not be closed. Unmark the segment.
|
||||
opl.consumed = false;
|
||||
break;
|
||||
}
|
||||
// Attach this polyline to the end of the initial polyline.
|
||||
if (next_start->start) {
|
||||
auto it = next_start->polyline->points.begin();
|
||||
std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points));
|
||||
//opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.end());
|
||||
} else {
|
||||
auto it = next_start->polyline->points.rbegin();
|
||||
std::copy(++ it, next_start->polyline->points.rend(), back_inserter(opl.points));
|
||||
//opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.rend());
|
||||
}
|
||||
end = *next_start;
|
||||
end.start = !end.start;
|
||||
next_start->polyline->points.clear();
|
||||
next_start->polyline->consumed = true;
|
||||
// Check whether we closed this loop.
|
||||
const IntersectionReference &ip1 = opl.start;
|
||||
const IntersectionReference &ip2 = end.ipref();
|
||||
if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) ||
|
||||
(ip1.point_id != -1 && ip1.point_id == ip2.point_id)) {
|
||||
// The current loop is complete. Add it to the output.
|
||||
//assert(opl.points.front().point_id == opl.points.back().point_id);
|
||||
//assert(opl.points.front().edge_id == opl.points.back().edge_id);
|
||||
// Remove the duplicate last point.
|
||||
opl.points.pop_back();
|
||||
if (opl.points.size() >= 3) {
|
||||
// The closed polygon is patched from pieces with messed up orientation, therefore
|
||||
// the orientation of the patched up polygon is not known.
|
||||
// Orient the patched up polygons CCW. This heuristic may close some holes and cavities.
|
||||
double area = 0.;
|
||||
for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++)
|
||||
area += double(opl.points[j](0) + opl.points[i](0)) * double(opl.points[i](1) - opl.points[j](1));
|
||||
if (area < 0)
|
||||
std::reverse(opl.points.begin(), opl.points.end());
|
||||
loops->emplace_back(std::move(opl.points));
|
||||
}
|
||||
opl.points.clear();
|
||||
break;
|
||||
}
|
||||
// Continue with the current loop.
|
||||
}
|
||||
svg.draw(Polyline(pl.points), "red");
|
||||
svg.draw(pl.points.front(), "blue");
|
||||
svg.draw(pl.points.back(), "blue");
|
||||
}
|
||||
svg.Close();
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// Try to close gaps.
|
||||
// Do it in two rounds, first try to connect in the same direction only,
|
||||
// then try to connect the open polylines in reversed order as well.
|
||||
const double max_gap = 2.; //mm
|
||||
chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false);
|
||||
chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true);
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg);
|
||||
svg.draw(union_ex(*loops));
|
||||
for (const OpenPolyline &pl : open_polylines) {
|
||||
if (pl.points.empty())
|
||||
continue;
|
||||
svg.draw(Polyline(pl.points), "red");
|
||||
svg.draw(pl.points.front(), "blue");
|
||||
svg.draw(pl.points.back(), "blue");
|
||||
}
|
||||
svg.Close();
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
}
|
||||
|
||||
// Only used to cut the mesh into two halves.
|
||||
@ -1580,10 +1771,11 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic
|
||||
// p_slices = diff(p_slices, *loop);
|
||||
//}
|
||||
|
||||
// perform a safety offset to merge very close facets (TODO: find test case for this)
|
||||
double safety_offset = scale_(0.0499);
|
||||
//FIXME see https://github.com/prusa3d/Slic3r/issues/520
|
||||
// double safety_offset = scale_(0.0001);
|
||||
// Perform a safety offset to merge very close facets (TODO: find test case for this)
|
||||
// 0.0499 comes from https://github.com/slic3r/Slic3r/issues/959
|
||||
// double safety_offset = scale_(0.0499);
|
||||
// 0.0001 is set to satisfy GH #520, #1029, #1364
|
||||
double safety_offset = scale_(0.0001);
|
||||
|
||||
/* The following line is commented out because it can generate wrong polygons,
|
||||
see for example issue #661 */
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/Format/3mf.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
@ -105,7 +106,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
// load config files supplied via --load
|
||||
for (const std::string &file : cli_config.load.values) {
|
||||
if (!boost::filesystem::exists(file)) {
|
||||
if (! boost::filesystem::exists(file)) {
|
||||
boost::nowide::cout << "No such file: " << file << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
@ -206,22 +207,35 @@ int main(int argc, char **argv)
|
||||
// lower.mesh().write_binary((outfile + "_lower.stl").c_str());
|
||||
}
|
||||
} else if (cli_config.slice) {
|
||||
PrinterTechnology printer_technology = print_config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value;
|
||||
std::string outfile = cli_config.output.value;
|
||||
Print print;
|
||||
Print fff_print;
|
||||
SLAPrint sla_print;
|
||||
PrintBase *print = (printer_technology == ptFFF) ? static_cast<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&sla_print);
|
||||
if (! cli_config.dont_arrange) {
|
||||
model.arrange_objects(print.config().min_object_distance());
|
||||
//FIXME make the min_object_distance configurable.
|
||||
model.arrange_objects(fff_print.config().min_object_distance());
|
||||
model.center_instances_around_point(cli_config.print_center);
|
||||
}
|
||||
if (outfile.empty())
|
||||
outfile = model.propose_export_file_name() + ".gcode";
|
||||
for (auto* mo : model.objects)
|
||||
print.auto_assign_extruders(mo);
|
||||
if (outfile.empty()) {
|
||||
outfile = model.propose_export_file_name();
|
||||
outfile += (printer_technology == ptFFF) ? ".gcode" : ".zip";
|
||||
}
|
||||
if (printer_technology == ptFFF) {
|
||||
for (auto* mo : model.objects)
|
||||
fff_print.auto_assign_extruders(mo);
|
||||
}
|
||||
print_config.normalize();
|
||||
print.apply(model, print_config);
|
||||
std::string err = print.validate();
|
||||
if (err.empty())
|
||||
print.export_gcode(outfile, nullptr);
|
||||
else
|
||||
print->apply(model, print_config);
|
||||
std::string err = print->validate();
|
||||
if (err.empty()) {
|
||||
if (printer_technology == ptFFF) {
|
||||
fff_print.export_gcode(outfile, nullptr);
|
||||
} else {
|
||||
assert(printer_technology == ptSLA);
|
||||
//FIXME add the output here
|
||||
}
|
||||
} else
|
||||
std::cerr << err << "\n";
|
||||
} else {
|
||||
boost::nowide::cerr << "error: command not supported" << std::endl;
|
||||
|
@ -103,12 +103,12 @@ add_library(libslic3r_gui STATIC
|
||||
GUI/ProgressIndicator.hpp
|
||||
GUI/ProgressStatusBar.hpp
|
||||
GUI/ProgressStatusBar.cpp
|
||||
GUI/PrintHostDialogs.cpp
|
||||
GUI/PrintHostDialogs.hpp
|
||||
Utils/Http.cpp
|
||||
Utils/Http.hpp
|
||||
Utils/FixModelByWin10.cpp
|
||||
Utils/FixModelByWin10.hpp
|
||||
Utils/PrintHostSendDialog.cpp
|
||||
Utils/PrintHostSendDialog.hpp
|
||||
Utils/OctoPrint.cpp
|
||||
Utils/OctoPrint.hpp
|
||||
Utils/Duet.cpp
|
||||
|
@ -870,8 +870,8 @@ void GLVolumeCollection::load_object_auxiliary(
|
||||
// Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance.
|
||||
v.set_convex_hull((&instance_idx == &instances.back()) ? new TriangleMesh(std::move(convex_hull)) : new TriangleMesh(convex_hull), true);
|
||||
v.is_modifier = false;
|
||||
v.shader_outside_printer_detection_enabled = true;
|
||||
v.set_instance_transformation(model_instance.get_transformation());
|
||||
v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree);
|
||||
v.set_instance_transformation(model_instance.get_transformation());
|
||||
// Leave the volume transformation at identity.
|
||||
// v.set_volume_transformation(model_volume->get_transformation());
|
||||
}
|
||||
@ -1039,20 +1039,20 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
|
||||
|
||||
for (GLVolume* volume : this->volumes)
|
||||
{
|
||||
if ((volume != nullptr) && !volume->is_modifier && (!volume->is_wipe_tower || (volume->is_wipe_tower && volume->shader_outside_printer_detection_enabled)))
|
||||
{
|
||||
const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box();
|
||||
bool contained = print_volume.contains(bb);
|
||||
all_contained &= contained;
|
||||
if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled))
|
||||
continue;
|
||||
|
||||
volume->is_outside = !contained;
|
||||
const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box();
|
||||
bool contained = print_volume.contains(bb);
|
||||
all_contained &= contained;
|
||||
|
||||
if ((state == ModelInstance::PVS_Inside) && volume->is_outside)
|
||||
state = ModelInstance::PVS_Fully_Outside;
|
||||
volume->is_outside = !contained;
|
||||
|
||||
if ((state == ModelInstance::PVS_Fully_Outside) && volume->is_outside && print_volume.intersects(bb))
|
||||
state = ModelInstance::PVS_Partly_Outside;
|
||||
}
|
||||
if ((state == ModelInstance::PVS_Inside) && volume->is_outside)
|
||||
state = ModelInstance::PVS_Fully_Outside;
|
||||
|
||||
if ((state == ModelInstance::PVS_Fully_Outside) && volume->is_outside && print_volume.intersects(bb))
|
||||
state = ModelInstance::PVS_Partly_Outside;
|
||||
}
|
||||
|
||||
if (out_state != nullptr)
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -125,8 +126,13 @@ void AppConfig::load()
|
||||
|
||||
void AppConfig::save()
|
||||
{
|
||||
// The config is first written to a file with a PID suffix and then moved
|
||||
// to avoid race conditions with multiple instances of Slic3r
|
||||
const auto path = config_path();
|
||||
std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str();
|
||||
|
||||
boost::nowide::ofstream c;
|
||||
c.open(AppConfig::config_path(), std::ios::out | std::ios::trunc);
|
||||
c.open(path_pid, std::ios::out | std::ios::trunc);
|
||||
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
|
||||
// Make sure the "no" category is written first.
|
||||
for (const std::pair<std::string, std::string> &kvp : m_storage[""])
|
||||
@ -155,6 +161,8 @@ void AppConfig::save()
|
||||
}
|
||||
}
|
||||
c.close();
|
||||
|
||||
rename_file(path_pid, path);
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
|
@ -19,9 +19,13 @@
|
||||
//#undef NDEBUG
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include <cctype>
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
@ -61,6 +65,11 @@ PrinterTechnology BackgroundSlicingProcess::current_printer_technology() const
|
||||
return m_print->technology();
|
||||
}
|
||||
|
||||
static bool isspace(int ch)
|
||||
{
|
||||
return std::isspace(ch) != 0;
|
||||
}
|
||||
|
||||
// This function may one day be merged into the Print, but historically the print was separated
|
||||
// from the G-code generator.
|
||||
void BackgroundSlicingProcess::process_fff()
|
||||
@ -72,11 +81,13 @@ void BackgroundSlicingProcess::process_fff()
|
||||
if (this->set_step_started(bspsGCodeFinalize)) {
|
||||
if (! m_export_path.empty()) {
|
||||
//FIXME localize the messages
|
||||
if (copy_file(m_temp_output_path, m_export_path) != 0)
|
||||
// Perform the final post-processing of the export path by applying the print statistics over the file name.
|
||||
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
|
||||
if (copy_file(m_temp_output_path, export_path) != 0)
|
||||
throw std::runtime_error("Copying of the temporary G-code to the output G-code failed");
|
||||
m_print->set_status(95, "Running post-processing scripts");
|
||||
run_post_process_scripts(m_export_path, m_fff_print->config());
|
||||
m_print->set_status(100, "G-code file exported to " + m_export_path);
|
||||
run_post_process_scripts(export_path, m_fff_print->config());
|
||||
m_print->set_status(100, "G-code file exported to " + export_path);
|
||||
} else {
|
||||
m_print->set_status(100, "Slicing complete");
|
||||
}
|
||||
@ -102,10 +113,17 @@ public:
|
||||
zipstream(zipfile),
|
||||
pngstream(zipstream)
|
||||
{
|
||||
if(!zipfile.IsOk())
|
||||
if(!is_ok())
|
||||
throw std::runtime_error("Cannot create zip file.");
|
||||
}
|
||||
|
||||
~LayerWriter() {
|
||||
// In case of an error (disk space full) zipstream destructor would
|
||||
// crash.
|
||||
pngstream.clear();
|
||||
zipstream.CloseEntry();
|
||||
}
|
||||
|
||||
inline void next_entry(const std::string& fname) {
|
||||
zipstream.PutNextEntry(fname);
|
||||
}
|
||||
@ -118,6 +136,10 @@ public:
|
||||
pngstream << arg; return *this;
|
||||
}
|
||||
|
||||
bool is_ok() const {
|
||||
return pngstream.good() && zipstream.IsOk() && zipfile.IsOk();
|
||||
}
|
||||
|
||||
inline void close() {
|
||||
zipstream.Close();
|
||||
zipfile.Close();
|
||||
@ -345,6 +367,22 @@ void BackgroundSlicingProcess::schedule_export(const std::string &path)
|
||||
m_export_path = path;
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job)
|
||||
{
|
||||
assert(m_export_path.empty());
|
||||
if (! m_export_path.empty())
|
||||
return;
|
||||
|
||||
const boost::filesystem::path path = boost::filesystem::temp_directory_path()
|
||||
/ boost::filesystem::unique_path(".upload.%%%%-%%%%-%%%%-%%%%.gcode");
|
||||
|
||||
// Guard against entering the export step before changing the export path.
|
||||
tbb::mutex::scoped_lock lock(m_print->state_mutex());
|
||||
this->invalidate_step(bspsGCodeFinalize);
|
||||
m_export_path = path.string();
|
||||
m_upload_job = std::move(upload_job);
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::reset_export()
|
||||
{
|
||||
assert(! this->running());
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <wx/event.h>
|
||||
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "slic3r/Utils/PrintHost.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -86,6 +87,9 @@ public:
|
||||
// Set the export path of the G-code.
|
||||
// Once the path is set, the G-code
|
||||
void schedule_export(const std::string &path);
|
||||
// Set print host upload job data to be enqueued to the PrintHostJobQueue
|
||||
// after current print slicing is complete
|
||||
void schedule_upload(Slic3r::PrintHostJob upload_job);
|
||||
// Clear m_export_path.
|
||||
void reset_export();
|
||||
// Once the G-code export is scheduled, the apply() methods will do nothing.
|
||||
@ -110,6 +114,11 @@ public:
|
||||
State state() const { return m_state; }
|
||||
bool idle() const { return m_state == STATE_IDLE; }
|
||||
bool running() const { return m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED; }
|
||||
// Returns true if the last step of the active print was finished with success.
|
||||
// The "finished" flag is reset by the apply() method, if it changes the state of the print.
|
||||
// This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs),
|
||||
// and it does not account for the OctoPrint scheduling.
|
||||
bool finished() const { return m_print->finished(); }
|
||||
|
||||
private:
|
||||
void thread_proc();
|
||||
@ -138,6 +147,9 @@ private:
|
||||
// Output path provided by the user. The output path may be set even if the slicing is running,
|
||||
// but once set, it cannot be re-set.
|
||||
std::string m_export_path;
|
||||
// Print host upload job to schedule after slicing is complete, used by schedule_upload(),
|
||||
// empty by default (ie. no upload to schedule)
|
||||
PrintHostJob m_upload_job;
|
||||
// Thread, on which the background processing is executed. The thread will always be present
|
||||
// and ready to execute the slicing process.
|
||||
std::thread m_thread;
|
||||
|
@ -74,7 +74,16 @@ void Field::on_kill_focus(wxEvent& event)
|
||||
event.Skip();
|
||||
// call the registered function if it is available
|
||||
if (m_on_kill_focus!=nullptr)
|
||||
m_on_kill_focus();
|
||||
m_on_kill_focus(m_opt_id);
|
||||
}
|
||||
|
||||
void Field::on_set_focus(wxEvent& event)
|
||||
{
|
||||
// to allow the default behavior
|
||||
event.Skip();
|
||||
// call the registered function if it is available
|
||||
if (m_on_set_focus!=nullptr)
|
||||
m_on_set_focus(m_opt_id);
|
||||
}
|
||||
|
||||
void Field::on_change_field()
|
||||
@ -125,9 +134,9 @@ void Field::get_value_by_opt_type(wxString& str)
|
||||
case coPercents:
|
||||
case coFloats:
|
||||
case coFloat:{
|
||||
if (m_opt.type == coPercent && str.Last() == '%')
|
||||
if (m_opt.type == coPercent && !str.IsEmpty() && str.Last() == '%')
|
||||
str.RemoveLast();
|
||||
else if (str.Last() == '%') {
|
||||
else if (!str.IsEmpty() && str.Last() == '%') {
|
||||
wxString label = m_Label->GetLabel();
|
||||
if (label.Last() == '\n') label.RemoveLast();
|
||||
while (label.Last() == ' ') label.RemoveLast();
|
||||
@ -162,7 +171,7 @@ void Field::get_value_by_opt_type(wxString& str)
|
||||
}
|
||||
}
|
||||
|
||||
bool TextCtrl::is_defined_input_value()
|
||||
bool TextCtrl::is_defined_input_value() const
|
||||
{
|
||||
if (static_cast<wxTextCtrl*>(window)->GetValue().empty() && m_opt.type != coString && m_opt.type != coStrings)
|
||||
return false;
|
||||
@ -216,10 +225,12 @@ void TextCtrl::BUILD() {
|
||||
break;
|
||||
}
|
||||
|
||||
const long style = m_opt.multiline ? wxTE_MULTILINE : 0 | m_process_enter ? wxTE_PROCESS_ENTER : 0;
|
||||
const long style = m_opt.multiline ? wxTE_MULTILINE : 0;
|
||||
auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, style);
|
||||
|
||||
temp->SetToolTip(get_tooltip_text(text_value));
|
||||
|
||||
temp->Bind(wxEVT_SET_FOCUS, ([this](wxEvent& e) { on_set_focus(e); }), temp->GetId());
|
||||
|
||||
temp->Bind(wxEVT_LEFT_DOWN, ([temp](wxEvent& event)
|
||||
{
|
||||
@ -240,17 +251,13 @@ void TextCtrl::BUILD() {
|
||||
e.Skip();
|
||||
temp->GetToolTip()->Enable(true);
|
||||
#endif // __WXGTK__
|
||||
if (!is_defined_input_value())
|
||||
// if (!is_defined_input_value())
|
||||
if (is_defined_input_value())
|
||||
on_change_field();
|
||||
else
|
||||
on_kill_focus(e);
|
||||
}), temp->GetId());
|
||||
|
||||
if (m_process_enter) {
|
||||
temp->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent& evt) {
|
||||
if(is_defined_input_value())
|
||||
on_change_field();
|
||||
}), temp->GetId());
|
||||
}
|
||||
else {
|
||||
/*
|
||||
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent& evt)
|
||||
{
|
||||
#ifdef __WXGTK__
|
||||
@ -267,8 +274,7 @@ void TextCtrl::BUILD() {
|
||||
temp->Bind(wxEVT_KEY_DOWN, &TextCtrl::change_field_value, this);
|
||||
temp->Bind(wxEVT_KEY_UP, &TextCtrl::change_field_value, this);
|
||||
#endif //__WXGTK__
|
||||
}
|
||||
|
||||
*/
|
||||
// select all text using Ctrl+A
|
||||
temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event)
|
||||
{
|
||||
@ -371,7 +377,15 @@ void SpinCtrl::BUILD() {
|
||||
0, min_val, max_val, default_value);
|
||||
|
||||
// temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { tmp_value = undef_spin_val; on_change_field(); }), temp->GetId());
|
||||
temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { on_kill_focus(e); }), temp->GetId());
|
||||
temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e)
|
||||
{
|
||||
if (tmp_value < 0)
|
||||
on_kill_focus(e);
|
||||
else {
|
||||
e.Skip();
|
||||
on_change_field();
|
||||
}
|
||||
}), temp->GetId());
|
||||
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e)
|
||||
{
|
||||
// # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
|
||||
@ -382,7 +396,8 @@ void SpinCtrl::BUILD() {
|
||||
std::string value = e.GetString().utf8_str().data();
|
||||
if (is_matched(value, "^\\d+$"))
|
||||
tmp_value = std::stoi(value);
|
||||
on_change_field();
|
||||
else tmp_value = -9999;
|
||||
// on_change_field();
|
||||
// # We don't reset tmp_value here because _on_change might put callbacks
|
||||
// # in the CallAfter queue, and we want the tmp value to be available from
|
||||
// # them as well.
|
||||
|
@ -29,8 +29,8 @@ namespace Slic3r { namespace GUI {
|
||||
|
||||
class Field;
|
||||
using t_field = std::unique_ptr<Field>;
|
||||
using t_kill_focus = std::function<void()>;
|
||||
using t_change = std::function<void(t_config_option_key, const boost::any&)>;
|
||||
using t_kill_focus = std::function<void(const std::string&)>;
|
||||
using t_change = std::function<void(const t_config_option_key&, const boost::any&)>;
|
||||
using t_back_to_init = std::function<void(const std::string&)>;
|
||||
|
||||
wxString double_to_string(double const value, const int max_precision = 4);
|
||||
@ -76,6 +76,8 @@ protected:
|
||||
//! in another case we can't unfocused control at all
|
||||
void on_kill_focus(wxEvent& event);
|
||||
/// Call the attached on_change method.
|
||||
void on_set_focus(wxEvent& event);
|
||||
/// Call the attached on_change method.
|
||||
void on_change_field();
|
||||
/// Call the attached m_back_to_initial_value method.
|
||||
void on_back_to_initial_value();
|
||||
@ -89,6 +91,9 @@ public:
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_kill_focus m_on_kill_focus {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_kill_focus m_on_set_focus {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_change m_on_change {nullptr};
|
||||
|
||||
@ -139,10 +144,9 @@ public:
|
||||
|
||||
/// Factory method for generating new derived classes.
|
||||
template<class T>
|
||||
static t_field Create(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id, const bool process_enter = false)// interface for creating shared objects
|
||||
static t_field Create(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id)// interface for creating shared objects
|
||||
{
|
||||
auto p = Slic3r::make_unique<T>(parent, opt, id);
|
||||
p->m_process_enter = process_enter;
|
||||
p->PostInitialize();
|
||||
return std::move(p); //!p;
|
||||
}
|
||||
@ -223,9 +227,6 @@ protected:
|
||||
// current value
|
||||
boost::any m_value;
|
||||
|
||||
//this variable shows a mode of a call of the on_change function
|
||||
bool m_process_enter { false };
|
||||
|
||||
friend class OptionsGroup;
|
||||
};
|
||||
|
||||
@ -265,7 +266,7 @@ public:
|
||||
}
|
||||
|
||||
boost::any& get_value() override;
|
||||
bool is_defined_input_value();
|
||||
bool is_defined_input_value() const ;
|
||||
|
||||
virtual void enable();
|
||||
virtual void disable();
|
||||
|
@ -68,8 +68,10 @@ static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f };
|
||||
|
||||
static const float DEFAULT_BG_COLOR[3] = { 10.0f / 255.0f, 98.0f / 255.0f, 144.0f / 255.0f };
|
||||
static const float ERROR_BG_COLOR[3] = { 144.0f / 255.0f, 49.0f / 255.0f, 10.0f / 255.0f };
|
||||
static const float DEFAULT_BG_DARK_COLOR[3] = { 0.478f, 0.478f, 0.478f };
|
||||
static const float DEFAULT_BG_LIGHT_COLOR[3] = { 0.753f, 0.753f, 0.753f };
|
||||
static const float ERROR_BG_DARK_COLOR[3] = { 0.478f, 0.192f, 0.039f };
|
||||
static const float ERROR_BG_LIGHT_COLOR[3] = { 0.753f, 0.192f, 0.039f };
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
@ -579,7 +581,7 @@ void GLCanvas3D::Bed::_render_custom() const
|
||||
|
||||
::glEnableClientState(GL_VERTEX_ARRAY);
|
||||
|
||||
::glColor4f(0.8f, 0.6f, 0.5f, 0.4f);
|
||||
::glColor4f(0.35f, 0.35f, 0.35f, 0.4f);
|
||||
::glNormal3d(0.0f, 0.0f, 1.0f);
|
||||
::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices());
|
||||
::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount);
|
||||
@ -1404,6 +1406,9 @@ bool GLCanvas3D::Selection::is_single_full_instance() const
|
||||
if (m_type == SingleFullInstance)
|
||||
return true;
|
||||
|
||||
if (m_type == SingleFullObject)
|
||||
return get_instance_idx() != -1;
|
||||
|
||||
if (m_list.empty() || m_volumes->empty())
|
||||
return false;
|
||||
|
||||
@ -1531,7 +1536,8 @@ void GLCanvas3D::Selection::rotate(const Vec3d& rotation, bool local)
|
||||
#if ENABLE_WORLD_ROTATIONS
|
||||
{
|
||||
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
|
||||
Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
|
||||
const Transform3d& inst_m = m_cache.volumes_data[i].get_instance_rotation_matrix();
|
||||
Vec3d new_rotation = Geometry::extract_euler_angles(inst_m.inverse() * m * inst_m * m_cache.volumes_data[i].get_volume_rotation_matrix());
|
||||
(*m_volumes)[i]->set_volume_rotation(new_rotation);
|
||||
}
|
||||
#else
|
||||
@ -2110,6 +2116,7 @@ void GLCanvas3D::Selection::_update_type()
|
||||
v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false;
|
||||
}
|
||||
|
||||
#if ENABLE_SELECTION_DEBUG_OUTPUT
|
||||
std::cout << "Selection: ";
|
||||
std::cout << "mode: ";
|
||||
switch (m_mode)
|
||||
@ -2191,6 +2198,7 @@ void GLCanvas3D::Selection::_update_type()
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_SELECTION_DEBUG_OUTPUT
|
||||
}
|
||||
|
||||
void GLCanvas3D::Selection::_set_caches()
|
||||
@ -3173,7 +3181,8 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const
|
||||
}
|
||||
|
||||
const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 };
|
||||
const unsigned char GLCanvas3D::LegendTexture::Background_Color[3] = { 9, 91, 134 };
|
||||
const unsigned char GLCanvas3D::LegendTexture::Default_Background_Color[3] = { (unsigned char)(DEFAULT_BG_LIGHT_COLOR[0] * 255.0f), (unsigned char)(DEFAULT_BG_LIGHT_COLOR[1] * 255.0f), (unsigned char)(DEFAULT_BG_LIGHT_COLOR[2] * 255.0f) };
|
||||
const unsigned char GLCanvas3D::LegendTexture::Error_Background_Color[3] = { (unsigned char)(ERROR_BG_LIGHT_COLOR[0] * 255.0f), (unsigned char)(ERROR_BG_LIGHT_COLOR[1] * 255.0f), (unsigned char)(ERROR_BG_LIGHT_COLOR[2] * 255.0f) };
|
||||
const unsigned char GLCanvas3D::LegendTexture::Opacity = 255;
|
||||
|
||||
GLCanvas3D::LegendTexture::LegendTexture()
|
||||
@ -3183,7 +3192,7 @@ GLCanvas3D::LegendTexture::LegendTexture()
|
||||
{
|
||||
}
|
||||
|
||||
bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas)
|
||||
bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas, bool use_error_colors)
|
||||
{
|
||||
reset();
|
||||
|
||||
@ -3222,8 +3231,11 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c
|
||||
return false;
|
||||
|
||||
wxMemoryDC memDC;
|
||||
wxMemoryDC mask_memDC;
|
||||
|
||||
// select default font
|
||||
memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
|
||||
mask_memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
|
||||
|
||||
// calculates texture size
|
||||
wxCoord w, h;
|
||||
@ -3252,16 +3264,28 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c
|
||||
|
||||
// generates bitmap
|
||||
wxBitmap bitmap(m_width, m_height);
|
||||
wxBitmap mask(m_width, m_height);
|
||||
|
||||
memDC.SelectObject(bitmap);
|
||||
memDC.SetBackground(wxBrush(wxColour(Background_Color[0], Background_Color[1], Background_Color[2])));
|
||||
mask_memDC.SelectObject(mask);
|
||||
|
||||
memDC.SetBackground(wxBrush(use_error_colors ? *wxWHITE : *wxBLACK));
|
||||
mask_memDC.SetBackground(wxBrush(*wxBLACK));
|
||||
|
||||
memDC.Clear();
|
||||
mask_memDC.Clear();
|
||||
|
||||
// draw title
|
||||
memDC.SetTextForeground(*wxWHITE);
|
||||
memDC.SetTextForeground(use_error_colors ? *wxWHITE : *wxBLACK);
|
||||
mask_memDC.SetTextForeground(*wxWHITE);
|
||||
|
||||
int title_x = Px_Border;
|
||||
int title_y = Px_Border;
|
||||
memDC.DrawText(title, title_x, title_y);
|
||||
mask_memDC.DrawText(title, title_x, title_y);
|
||||
|
||||
mask_memDC.SetPen(wxPen(*wxWHITE));
|
||||
mask_memDC.SetBrush(wxBrush(*wxWHITE));
|
||||
|
||||
// draw icons contours as background
|
||||
int squares_contour_x = Px_Border;
|
||||
@ -3277,6 +3301,7 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c
|
||||
memDC.SetPen(pen);
|
||||
memDC.SetBrush(brush);
|
||||
memDC.DrawRectangle(wxRect(squares_contour_x, squares_contour_y, squares_contour_width, squares_contour_height));
|
||||
mask_memDC.DrawRectangle(wxRect(squares_contour_x, squares_contour_y, squares_contour_width, squares_contour_height));
|
||||
|
||||
// draw items (colored icon + text)
|
||||
int icon_x = squares_contour_x + Px_Square_Contour;
|
||||
@ -3313,16 +3338,18 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c
|
||||
|
||||
// draw text
|
||||
memDC.DrawText(GUI::from_u8(item.text), text_x, icon_y + text_y_offset);
|
||||
mask_memDC.DrawText(GUI::from_u8(item.text), text_x, icon_y + text_y_offset);
|
||||
|
||||
// update y
|
||||
icon_y += icon_y_step;
|
||||
}
|
||||
|
||||
memDC.SelectObject(wxNullBitmap);
|
||||
mask_memDC.SelectObject(wxNullBitmap);
|
||||
|
||||
// Convert the bitmap into a linear data ready to be loaded into the GPU.
|
||||
wxImage image = bitmap.ConvertToImage();
|
||||
image.SetMaskColour(Background_Color[0], Background_Color[1], Background_Color[2]);
|
||||
wxImage mask_image = mask.ConvertToImage();
|
||||
|
||||
// prepare buffer
|
||||
std::vector<unsigned char> data(4 * m_width * m_height, 0);
|
||||
@ -3335,7 +3362,7 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c
|
||||
*px_ptr++ = image.GetRed(w, h);
|
||||
*px_ptr++ = image.GetGreen(w, h);
|
||||
*px_ptr++ = image.GetBlue(w, h);
|
||||
*px_ptr++ = image.IsTransparent(w, h) ? 0 : Opacity;
|
||||
*px_ptr++ = (mask_image.GetRed(w, h) + mask_image.GetGreen(w, h) + mask_image.GetBlue(w, h)) / 3;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4088,7 +4115,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
|
||||
m_reload_delayed = ! m_canvas->IsShown() && ! refresh_immediately && ! force_full_scene_refresh;
|
||||
|
||||
PrinterTechnology printer_technology = m_process->current_printer_technology();
|
||||
PrinterTechnology printer_technology = m_process->current_printer_technology();
|
||||
int volume_idx_wipe_tower_old = -1;
|
||||
|
||||
if (m_regenerate_volumes)
|
||||
{
|
||||
@ -4146,6 +4174,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
}
|
||||
if (mvs == nullptr || force_full_scene_refresh) {
|
||||
// This GLVolume will be released.
|
||||
if (volume->is_wipe_tower) {
|
||||
// There is only one wipe tower.
|
||||
assert(volume_idx_wipe_tower_old == -1);
|
||||
volume_idx_wipe_tower_old = (int)volume_id;
|
||||
}
|
||||
volume->release_geometry();
|
||||
if (! m_reload_delayed)
|
||||
delete volume;
|
||||
@ -4313,8 +4346,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
float depth = print->get_wipe_tower_depth();
|
||||
if (!print->is_step_done(psWipeTower))
|
||||
depth = (900.f/w) * (float)(extruders_count - 1) ;
|
||||
m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower),
|
||||
print->config().nozzle_diameter.values[0] * 1.25f * 4.5f);
|
||||
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
|
||||
1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower),
|
||||
print->config().nozzle_diameter.values[0] * 1.25f * 4.5f);
|
||||
if (volume_idx_wipe_tower_old != -1)
|
||||
map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4378,10 +4414,10 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const
|
||||
return;
|
||||
#endif // !ENABLE_USE_UNIQUE_GLCONTEXT
|
||||
|
||||
std::vector<float> tool_colors = _parse_colors(str_tool_colors);
|
||||
|
||||
if (m_volumes.empty())
|
||||
{
|
||||
std::vector<float> tool_colors = _parse_colors(str_tool_colors);
|
||||
|
||||
m_gcode_preview_volume_index.reset();
|
||||
|
||||
_load_gcode_extrusion_paths(preview_data, tool_colors);
|
||||
@ -4389,12 +4425,8 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const
|
||||
_load_gcode_retractions(preview_data);
|
||||
_load_gcode_unretractions(preview_data);
|
||||
|
||||
if (m_volumes.empty())
|
||||
reset_legend_texture();
|
||||
else
|
||||
if (!m_volumes.empty())
|
||||
{
|
||||
_generate_legend_texture(preview_data, tool_colors);
|
||||
|
||||
// removes empty volumes
|
||||
m_volumes.volumes.erase(std::remove_if(m_volumes.volumes.begin(), m_volumes.volumes.end(),
|
||||
[](const GLVolume* volume) { return volume->print_zs.empty(); }), m_volumes.volumes.end());
|
||||
@ -4406,6 +4438,11 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const
|
||||
|
||||
_update_gcode_volumes_visibility(preview_data);
|
||||
_show_warning_texture_if_needed();
|
||||
|
||||
if (m_volumes.empty())
|
||||
reset_legend_texture();
|
||||
else
|
||||
_generate_legend_texture(preview_data, tool_colors);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4994,6 +5031,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
|
||||
#if ENABLE_CONSTRAINED_CAMERA_TARGET
|
||||
m_camera.set_scene_box(scene_bounding_box(), *this);
|
||||
set_camera_zoom(0.0f);
|
||||
#endif // ENABLE_CONSTRAINED_CAMERA_TARGET
|
||||
}
|
||||
|
||||
@ -5801,18 +5839,22 @@ void GLCanvas3D::_render_background() const
|
||||
::glPushMatrix();
|
||||
::glLoadIdentity();
|
||||
|
||||
// Draws a bluish bottom to top gradient over the complete screen.
|
||||
// Draws a bottom to top gradient over the complete screen.
|
||||
::glDisable(GL_DEPTH_TEST);
|
||||
|
||||
::glBegin(GL_QUADS);
|
||||
::glColor3f(0.0f, 0.0f, 0.0f);
|
||||
if (m_dynamic_background_enabled && _is_any_volume_outside())
|
||||
::glColor3fv(ERROR_BG_DARK_COLOR);
|
||||
else
|
||||
::glColor3fv(DEFAULT_BG_DARK_COLOR);
|
||||
|
||||
::glVertex2f(-1.0f, -1.0f);
|
||||
::glVertex2f(1.0f, -1.0f);
|
||||
|
||||
if (m_dynamic_background_enabled && _is_any_volume_outside())
|
||||
::glColor3fv(ERROR_BG_COLOR);
|
||||
::glColor3fv(ERROR_BG_LIGHT_COLOR);
|
||||
else
|
||||
::glColor3fv(DEFAULT_BG_COLOR);
|
||||
::glColor3fv(DEFAULT_BG_LIGHT_COLOR);
|
||||
|
||||
::glVertex2f(1.0f, 1.0f);
|
||||
::glVertex2f(-1.0f, 1.0f);
|
||||
@ -6083,166 +6125,190 @@ void GLCanvas3D::_render_sla_slices() const
|
||||
return;
|
||||
|
||||
const SLAPrint* print = this->sla_print();
|
||||
if (print->objects().empty())
|
||||
const PrintObjects& print_objects = print->objects();
|
||||
if (print_objects.empty())
|
||||
// nothing to render, return
|
||||
return;
|
||||
|
||||
double clip_min_z = -m_clipping_planes[0].get_data()[3];
|
||||
double clip_max_z = m_clipping_planes[1].get_data()[3];
|
||||
for (const SLAPrintObject* obj : print->objects())
|
||||
for (unsigned int i = 0; i < (unsigned int)print_objects.size(); ++i)
|
||||
{
|
||||
if (obj->is_step_done(slaposIndexSlices))
|
||||
const SLAPrintObject* obj = print_objects[i];
|
||||
|
||||
Pointf3s bottom_obj_triangles;
|
||||
Pointf3s bottom_sup_triangles;
|
||||
Pointf3s top_obj_triangles;
|
||||
Pointf3s top_sup_triangles;
|
||||
|
||||
double shift_z = obj->get_current_elevation();
|
||||
double min_z = clip_min_z - shift_z;
|
||||
double max_z = clip_max_z - shift_z;
|
||||
|
||||
if (m_sla_caps[0].matches(min_z))
|
||||
{
|
||||
SlaCap::ObjectIdToTrianglesMap::const_iterator it = m_sla_caps[0].triangles.find(i);
|
||||
if (it != m_sla_caps[0].triangles.end())
|
||||
{
|
||||
bottom_obj_triangles = it->second.object;
|
||||
bottom_sup_triangles = it->second.suppports;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_sla_caps[1].matches(max_z))
|
||||
{
|
||||
SlaCap::ObjectIdToTrianglesMap::const_iterator it = m_sla_caps[1].triangles.find(i);
|
||||
if (it != m_sla_caps[1].triangles.end())
|
||||
{
|
||||
top_obj_triangles = it->second.object;
|
||||
top_sup_triangles = it->second.suppports;
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<SLAPrintObject::Instance>& instances = obj->instances();
|
||||
struct InstanceTransform
|
||||
{
|
||||
Vec3d offset;
|
||||
float rotation;
|
||||
};
|
||||
|
||||
std::vector<InstanceTransform> instance_transforms;
|
||||
for (const SLAPrintObject::Instance& inst : instances)
|
||||
{
|
||||
instance_transforms.push_back({ to_3d(unscale(inst.shift), shift_z), Geometry::rad2deg(inst.rotation) });
|
||||
}
|
||||
|
||||
if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) && obj->is_step_done(slaposIndexSlices))
|
||||
{
|
||||
const std::vector<ExPolygons>& model_slices = obj->get_model_slices();
|
||||
const std::vector<ExPolygons>& support_slices = obj->get_support_slices();
|
||||
const std::vector<SLAPrintObject::Instance>& instances = obj->instances();
|
||||
double shift_z = obj->get_current_elevation();
|
||||
|
||||
struct InstanceTransform
|
||||
const SLAPrintObject::SliceIndex& index = obj->get_slice_index();
|
||||
SLAPrintObject::SliceIndex::const_iterator it_min_z = std::find_if(index.begin(), index.end(), [min_z](const SLAPrintObject::SliceIndex::value_type& id) -> bool { return std::abs(min_z - id.first) < EPSILON; });
|
||||
SLAPrintObject::SliceIndex::const_iterator it_max_z = std::find_if(index.begin(), index.end(), [max_z](const SLAPrintObject::SliceIndex::value_type& id) -> bool { return std::abs(max_z - id.first) < EPSILON; });
|
||||
|
||||
if (it_min_z != index.end())
|
||||
{
|
||||
Vec3d offset;
|
||||
float rotation;
|
||||
};
|
||||
|
||||
std::vector<InstanceTransform> instance_transforms;
|
||||
for (const SLAPrintObject::Instance& inst : instances)
|
||||
{
|
||||
instance_transforms.push_back({ to_3d(unscale(inst.shift), shift_z), Geometry::rad2deg(inst.rotation) });
|
||||
}
|
||||
|
||||
double min_z = clip_min_z - shift_z;
|
||||
double max_z = clip_max_z - shift_z;
|
||||
|
||||
Pointf3s bottom_triangles;
|
||||
Pointf3s top_triangles;
|
||||
|
||||
if (m_sla_caps[0].matches(min_z))
|
||||
bottom_triangles = m_sla_caps[0].triangles;
|
||||
|
||||
if (m_sla_caps[1].matches(max_z))
|
||||
top_triangles = m_sla_caps[1].triangles;
|
||||
|
||||
if (bottom_triangles.empty() || top_triangles.empty())
|
||||
{
|
||||
const SLAPrintObject::SliceIndex& index = obj->get_slice_index();
|
||||
SLAPrintObject::SliceIndex::const_iterator it_min_z = std::find_if(index.begin(), index.end(), [min_z](const SLAPrintObject::SliceIndex::value_type& id) -> bool { return std::abs(min_z - id.first) < EPSILON; });
|
||||
SLAPrintObject::SliceIndex::const_iterator it_max_z = std::find_if(index.begin(), index.end(), [max_z](const SLAPrintObject::SliceIndex::value_type& id) -> bool { return std::abs(max_z - id.first) < EPSILON; });
|
||||
|
||||
if (bottom_triangles.empty() && (it_min_z != index.end()))
|
||||
if (bottom_obj_triangles.empty() && (it_min_z->second.model_slices_idx < model_slices.size()))
|
||||
{
|
||||
// calculate model bottom cap
|
||||
if (it_min_z->second.model_slices_idx < model_slices.size())
|
||||
const ExPolygons& polys = model_slices[it_min_z->second.model_slices_idx];
|
||||
for (const ExPolygon& poly : polys)
|
||||
{
|
||||
const ExPolygons& polys = model_slices[it_min_z->second.model_slices_idx];
|
||||
for (const ExPolygon& poly : polys)
|
||||
Polygons poly_triangles;
|
||||
poly.triangulate(&poly_triangles);
|
||||
for (const Polygon& t : poly_triangles)
|
||||
{
|
||||
Polygons triangles;
|
||||
poly.triangulate(&triangles);
|
||||
for (const Polygon& t : triangles)
|
||||
for (int v = 2; v >= 0; --v)
|
||||
{
|
||||
for (int v = 2; v >= 0; --v)
|
||||
{
|
||||
bottom_triangles.emplace_back(to_3d(unscale(t.points[v]), min_z));
|
||||
}
|
||||
bottom_obj_triangles.emplace_back(to_3d(unscale(t.points[v]), min_z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate support bottom cap
|
||||
if (it_min_z->second.support_slices_idx < support_slices.size())
|
||||
{
|
||||
const ExPolygons& polys = support_slices[it_min_z->second.support_slices_idx];
|
||||
for (const ExPolygon& poly : polys)
|
||||
{
|
||||
Polygons triangles;
|
||||
poly.triangulate(&triangles);
|
||||
for (const Polygon& t : triangles)
|
||||
{
|
||||
for (int v = 2; v >= 0; --v)
|
||||
{
|
||||
bottom_triangles.emplace_back(to_3d(unscale(t.points[v]), min_z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_sla_caps[0].z = min_z;
|
||||
m_sla_caps[0].triangles = bottom_triangles;
|
||||
}
|
||||
|
||||
if (top_triangles.empty() && (it_max_z != index.end()))
|
||||
if (bottom_sup_triangles.empty() && (it_min_z->second.support_slices_idx < support_slices.size()))
|
||||
{
|
||||
// calculate model top cap
|
||||
if (it_max_z->second.model_slices_idx < model_slices.size())
|
||||
// calculate support bottom cap
|
||||
const ExPolygons& polys = support_slices[it_min_z->second.support_slices_idx];
|
||||
for (const ExPolygon& poly : polys)
|
||||
{
|
||||
const ExPolygons& polys = model_slices[it_max_z->second.model_slices_idx];
|
||||
for (const ExPolygon& poly : polys)
|
||||
Polygons poly_triangles;
|
||||
poly.triangulate(&poly_triangles);
|
||||
for (const Polygon& t : poly_triangles)
|
||||
{
|
||||
Polygons triangles;
|
||||
poly.triangulate(&triangles);
|
||||
for (const Polygon& t : triangles)
|
||||
for (int v = 2; v >= 0; --v)
|
||||
{
|
||||
for (int v = 0; v < 3; ++v)
|
||||
{
|
||||
top_triangles.emplace_back(to_3d(unscale(t.points[v]), max_z));
|
||||
}
|
||||
bottom_sup_triangles.emplace_back(to_3d(unscale(t.points[v]), min_z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate support top cap
|
||||
if (it_max_z->second.support_slices_idx < support_slices.size())
|
||||
{
|
||||
const ExPolygons& polys = support_slices[it_max_z->second.support_slices_idx];
|
||||
for (const ExPolygon& poly : polys)
|
||||
{
|
||||
Polygons triangles;
|
||||
poly.triangulate(&triangles);
|
||||
for (const Polygon& t : triangles)
|
||||
{
|
||||
for (int v = 0; v < 3; ++v)
|
||||
{
|
||||
top_triangles.emplace_back(to_3d(unscale(t.points[v]), max_z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_sla_caps[1].z = max_z;
|
||||
m_sla_caps[1].triangles = top_triangles;
|
||||
m_sla_caps[0].triangles.insert(SlaCap::ObjectIdToTrianglesMap::value_type(i, { bottom_obj_triangles, bottom_sup_triangles }));
|
||||
m_sla_caps[0].z = min_z;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bottom_triangles.empty() || !top_triangles.empty())
|
||||
if (it_max_z != index.end())
|
||||
{
|
||||
if (top_obj_triangles.empty() && (it_max_z->second.model_slices_idx < model_slices.size()))
|
||||
{
|
||||
// calculate model top cap
|
||||
const ExPolygons& polys = model_slices[it_max_z->second.model_slices_idx];
|
||||
for (const ExPolygon& poly : polys)
|
||||
{
|
||||
Polygons poly_triangles;
|
||||
poly.triangulate(&poly_triangles);
|
||||
for (const Polygon& t : poly_triangles)
|
||||
{
|
||||
for (int v = 0; v < 3; ++v)
|
||||
{
|
||||
top_obj_triangles.emplace_back(to_3d(unscale(t.points[v]), max_z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (top_sup_triangles.empty() && (it_max_z->second.support_slices_idx < support_slices.size()))
|
||||
{
|
||||
// calculate support top cap
|
||||
const ExPolygons& polys = support_slices[it_max_z->second.support_slices_idx];
|
||||
for (const ExPolygon& poly : polys)
|
||||
{
|
||||
Polygons poly_triangles;
|
||||
poly.triangulate(&poly_triangles);
|
||||
for (const Polygon& t : poly_triangles)
|
||||
{
|
||||
for (int v = 0; v < 3; ++v)
|
||||
{
|
||||
top_sup_triangles.emplace_back(to_3d(unscale(t.points[v]), max_z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_sla_caps[1].triangles.insert(SlaCap::ObjectIdToTrianglesMap::value_type(i, { top_obj_triangles, top_sup_triangles }));
|
||||
m_sla_caps[1].z = max_z;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty())
|
||||
{
|
||||
for (const InstanceTransform& inst : instance_transforms)
|
||||
{
|
||||
::glPushMatrix();
|
||||
::glTranslated(inst.offset(0), inst.offset(1), inst.offset(2));
|
||||
::glRotatef(inst.rotation, 0.0, 0.0, 1.0);
|
||||
|
||||
::glBegin(GL_TRIANGLES);
|
||||
|
||||
::glColor3f(1.0f, 0.37f, 0.0f);
|
||||
|
||||
for (const InstanceTransform& inst : instance_transforms)
|
||||
for (const Vec3d& v : bottom_obj_triangles)
|
||||
{
|
||||
::glPushMatrix();
|
||||
::glTranslated(inst.offset(0), inst.offset(1), inst.offset(2));
|
||||
::glRotatef(inst.rotation, 0.0, 0.0, 1.0);
|
||||
|
||||
::glBegin(GL_TRIANGLES);
|
||||
|
||||
if (!bottom_triangles.empty())
|
||||
{
|
||||
for (const Vec3d& v : bottom_triangles)
|
||||
{
|
||||
::glVertex3dv((GLdouble*)v.data());
|
||||
}
|
||||
}
|
||||
|
||||
if (!top_triangles.empty())
|
||||
{
|
||||
for (const Vec3d& v : top_triangles)
|
||||
{
|
||||
::glVertex3dv((GLdouble*)v.data());
|
||||
}
|
||||
}
|
||||
|
||||
::glEnd();
|
||||
|
||||
::glPopMatrix();
|
||||
::glVertex3dv((GLdouble*)v.data());
|
||||
}
|
||||
|
||||
for (const Vec3d& v : top_obj_triangles)
|
||||
{
|
||||
::glVertex3dv((GLdouble*)v.data());
|
||||
}
|
||||
|
||||
::glColor3f(1.0f, 0.0f, 0.37f);
|
||||
|
||||
for (const Vec3d& v : bottom_sup_triangles)
|
||||
{
|
||||
::glVertex3dv((GLdouble*)v.data());
|
||||
}
|
||||
|
||||
for (const Vec3d& v : top_sup_triangles)
|
||||
{
|
||||
::glVertex3dv((GLdouble*)v.data());
|
||||
}
|
||||
|
||||
::glEnd();
|
||||
|
||||
::glPopMatrix();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7401,6 +7467,9 @@ void GLCanvas3D::_load_shells_sla()
|
||||
int obj_idx = 0;
|
||||
for (const SLAPrintObject* obj : print->objects())
|
||||
{
|
||||
if (!obj->is_step_done(slaposIndexSlices))
|
||||
continue;
|
||||
|
||||
unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size();
|
||||
|
||||
const ModelObject* model_obj = obj->model_object();
|
||||
@ -7451,7 +7520,7 @@ void GLCanvas3D::_load_shells_sla()
|
||||
else
|
||||
v.indexed_vertex_array.load_mesh_flat_shading(mesh);
|
||||
|
||||
v.shader_outside_printer_detection_enabled = true;
|
||||
v.shader_outside_printer_detection_enabled = false;
|
||||
v.composite_id.volume_id = -1;
|
||||
v.set_instance_offset(offset);
|
||||
v.set_instance_rotation(rotation);
|
||||
@ -7608,7 +7677,7 @@ void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data,
|
||||
return;
|
||||
#endif // !ENABLE_USE_UNIQUE_GLCONTEXT
|
||||
|
||||
m_legend_texture.generate(preview_data, tool_colors, *this);
|
||||
m_legend_texture.generate(preview_data, tool_colors, *this, m_dynamic_background_enabled && _is_any_volume_outside());
|
||||
}
|
||||
|
||||
void GLCanvas3D::_generate_warning_texture(const std::string& msg)
|
||||
|
@ -701,8 +701,14 @@ private:
|
||||
|
||||
struct SlaCap
|
||||
{
|
||||
struct Triangles
|
||||
{
|
||||
Pointf3s object;
|
||||
Pointf3s suppports;
|
||||
};
|
||||
typedef std::map<unsigned int, Triangles> ObjectIdToTrianglesMap;
|
||||
double z;
|
||||
Pointf3s triangles;
|
||||
ObjectIdToTrianglesMap triangles;
|
||||
|
||||
SlaCap() { reset(); }
|
||||
void reset() { z = DBL_MAX; triangles.clear(); }
|
||||
@ -733,7 +739,8 @@ private:
|
||||
static const int Px_Square_Contour = 1;
|
||||
static const int Px_Border = Px_Square / 2;
|
||||
static const unsigned char Squares_Border_Color[3];
|
||||
static const unsigned char Background_Color[3];
|
||||
static const unsigned char Default_Background_Color[3];
|
||||
static const unsigned char Error_Background_Color[3];
|
||||
static const unsigned char Opacity;
|
||||
|
||||
int m_original_width;
|
||||
@ -742,7 +749,7 @@ private:
|
||||
public:
|
||||
LegendTexture();
|
||||
|
||||
bool generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas);
|
||||
bool generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas, bool use_error_colors);
|
||||
|
||||
void render(const GLCanvas3D& canvas) const;
|
||||
};
|
||||
@ -965,6 +972,8 @@ public:
|
||||
void viewport_changed();
|
||||
#endif // ENABLE_CONSTRAINED_CAMERA_TARGET
|
||||
|
||||
void handle_sidebar_focus_event(const std::string& opt_key) {}
|
||||
|
||||
private:
|
||||
bool _is_shown_on_screen() const;
|
||||
void _force_zoom_to_bed();
|
||||
|
@ -1292,9 +1292,11 @@ void GLGizmoMove3D::on_render(const GLCanvas3D::Selection& selection) const
|
||||
|
||||
// draw grabbers
|
||||
render_grabbers(box);
|
||||
render_grabber_extension(X, box, false);
|
||||
render_grabber_extension(Y, box, false);
|
||||
render_grabber_extension(Z, box, false);
|
||||
for (unsigned int i = 0; i < 3; ++i)
|
||||
{
|
||||
if (m_grabbers[i].enabled)
|
||||
render_grabber_extension((Axis)i, box, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -147,7 +147,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
|
||||
config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast<std::string>(value)));
|
||||
break;
|
||||
case coStrings:{
|
||||
if (opt_key == "compatible_prints" || opt_key == "compatible_printers") {
|
||||
if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "post_process") {
|
||||
config.option<ConfigOptionStrings>(opt_key)->values =
|
||||
boost::any_cast<std::vector<std::string>>(value);
|
||||
}
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "3DScene.hpp"
|
||||
|
||||
#include "../Utils/PresetUpdater.hpp"
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
#include "ConfigWizard_private.hpp"
|
||||
#include "slic3r/Config/Snapshot.hpp"
|
||||
#include "ConfigSnapshotDialog.hpp"
|
||||
@ -72,6 +73,7 @@ GUI_App::GUI_App()
|
||||
: wxApp()
|
||||
#if ENABLE_IMGUI
|
||||
, m_imgui(new ImGuiWrapper())
|
||||
, m_printhost_queue(new PrintHostJobQueue())
|
||||
#endif // ENABLE_IMGUI
|
||||
{}
|
||||
|
||||
|
@ -27,6 +27,7 @@ class AppConfig;
|
||||
class PresetBundle;
|
||||
class PresetUpdater;
|
||||
class ModelObject;
|
||||
class PrintHostJobQueue;
|
||||
|
||||
namespace GUI
|
||||
{
|
||||
@ -91,6 +92,8 @@ class GUI_App : public wxApp
|
||||
std::unique_ptr<ImGuiWrapper> m_imgui;
|
||||
#endif // ENABLE_IMGUI
|
||||
|
||||
std::unique_ptr<PrintHostJobQueue> m_printhost_queue;
|
||||
|
||||
public:
|
||||
bool OnInit() override;
|
||||
|
||||
@ -161,6 +164,8 @@ public:
|
||||
ImGuiWrapper* imgui() { return m_imgui.get(); }
|
||||
#endif // ENABLE_IMGUI
|
||||
|
||||
PrintHostJobQueue& printhost_queue() { return *m_printhost_queue.get(); }
|
||||
|
||||
};
|
||||
DECLARE_APP(GUI_App)
|
||||
|
||||
|
@ -60,32 +60,30 @@ ObjectList::ObjectList(wxWindow* parent) :
|
||||
#endif //__WXMSW__
|
||||
});
|
||||
|
||||
Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [this](wxDataViewEvent& event) {
|
||||
context_menu();
|
||||
});
|
||||
|
||||
Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX
|
||||
// Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX
|
||||
|
||||
#ifdef __WXMSW__
|
||||
// Extruder value changed
|
||||
Bind(wxEVT_CHOICE, [this](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); });
|
||||
|
||||
GetMainWindow()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) {
|
||||
set_tooltip_for_item(/*event.GetPosition()*/get_mouse_position_in_control());
|
||||
event.Skip();
|
||||
});
|
||||
#else
|
||||
// equivalent to wxEVT_CHOICE on __WXMSW__
|
||||
Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [this](wxDataViewEvent& e) { item_value_change(e); });
|
||||
#endif //__WXMSW__
|
||||
|
||||
Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, [this](wxDataViewEvent& e) {on_begin_drag(e); });
|
||||
Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, [this](wxDataViewEvent& e) {on_drop_possible(e); });
|
||||
Bind(wxEVT_DATAVIEW_ITEM_DROP, [this](wxDataViewEvent& e) {on_drop(e); });
|
||||
Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &ObjectList::OnContextMenu, this);
|
||||
|
||||
Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, [this](wxCommandEvent& e) {last_volume_is_deleted(e.GetInt()); });
|
||||
Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, &ObjectList::OnBeginDrag, this);
|
||||
Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, &ObjectList::OnDropPossible, this);
|
||||
Bind(wxEVT_DATAVIEW_ITEM_DROP, &ObjectList::OnDrop, this);
|
||||
|
||||
Bind(wxEVT_DATAVIEW_ITEM_START_EDITING, &ObjectList::OnStartEditing, this);
|
||||
Bind(wxEVT_DATAVIEW_ITEM_EDITING_DONE, &ObjectList::OnEditingDone, this);
|
||||
|
||||
Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &ObjectList::ItemValueChanged, this);
|
||||
|
||||
Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, [this](wxCommandEvent& e) { last_volume_is_deleted(e.GetInt()); });
|
||||
|
||||
#ifdef __WXOSX__
|
||||
Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this);
|
||||
#endif //__WXOSX__
|
||||
}
|
||||
|
||||
ObjectList::~ObjectList()
|
||||
@ -199,12 +197,54 @@ wxDataViewColumn* ObjectList::create_objects_list_extruder_column(int extruders_
|
||||
return column;
|
||||
}
|
||||
|
||||
void ObjectList::update_extruder_values_for_items(const int max_extruder)
|
||||
{
|
||||
for (int i = 0; i < m_objects->size(); ++i)
|
||||
{
|
||||
wxDataViewItem item = m_objects_model->GetItemById(i);
|
||||
if (!item) continue;
|
||||
|
||||
auto object = (*m_objects)[i];
|
||||
wxString extruder;
|
||||
if (!object->config.has("extruder") ||
|
||||
object->config.option<ConfigOptionInt>("extruder")->value > max_extruder)
|
||||
extruder = "default";
|
||||
else
|
||||
extruder = wxString::Format("%d", object->config.option<ConfigOptionInt>("extruder")->value);
|
||||
|
||||
m_objects_model->SetValue(extruder, item, 1);
|
||||
|
||||
if (object->volumes.size() > 1) {
|
||||
for (auto id = 0; id < object->volumes.size(); id++) {
|
||||
item = m_objects_model->GetItemByVolumeId(i, id);
|
||||
if (!item) continue;
|
||||
if (!object->volumes[id]->config.has("extruder") ||
|
||||
object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value > max_extruder)
|
||||
extruder = "default";
|
||||
else
|
||||
extruder = wxString::Format("%d", object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value);
|
||||
|
||||
m_objects_model->SetValue(extruder, item, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectList::update_objects_list_extruder_column(int extruders_count)
|
||||
{
|
||||
if (!this) return; // #ys_FIXME
|
||||
if (wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA)
|
||||
extruders_count = 1;
|
||||
|
||||
wxDataViewChoiceRenderer* ch_render = dynamic_cast<wxDataViewChoiceRenderer*>(GetColumn(1)->GetRenderer());
|
||||
if (ch_render->GetChoices().GetCount() - 1 == extruders_count)
|
||||
return;
|
||||
|
||||
m_prevent_update_extruder_in_config = true;
|
||||
|
||||
if (m_objects && extruders_count > 1)
|
||||
update_extruder_values_for_items(extruders_count);
|
||||
|
||||
// delete old 2nd column
|
||||
DeleteColumn(GetColumn(1));
|
||||
// insert new created 3rd column
|
||||
@ -213,25 +253,60 @@ void ObjectList::update_objects_list_extruder_column(int extruders_count)
|
||||
set_extruder_column_hidden(extruders_count <= 1);
|
||||
//a workaround for a wrong last column width updating under OSX
|
||||
GetColumn(2)->SetWidth(25);
|
||||
|
||||
m_prevent_update_extruder_in_config = false;
|
||||
}
|
||||
|
||||
void ObjectList::set_extruder_column_hidden(bool hide)
|
||||
void ObjectList::set_extruder_column_hidden(const bool hide) const
|
||||
{
|
||||
GetColumn(1)->SetHidden(hide);
|
||||
}
|
||||
|
||||
void ObjectList::update_extruder_in_config(const wxString& selection)
|
||||
void ObjectList::update_extruder_in_config(const wxDataViewItem& item)
|
||||
{
|
||||
if (m_prevent_update_extruder_in_config)
|
||||
return;
|
||||
if (m_objects_model->GetParent(item) == wxDataViewItem(0)) {
|
||||
const int obj_idx = m_objects_model->GetIdByItem(item);
|
||||
m_config = &(*m_objects)[obj_idx]->config;
|
||||
}
|
||||
else {
|
||||
const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item));
|
||||
const int volume_id = m_objects_model->GetVolumeIdByItem(item);
|
||||
if (obj_idx < 0 || volume_id < 0)
|
||||
return;
|
||||
m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config;
|
||||
}
|
||||
|
||||
wxVariant variant;
|
||||
m_objects_model->GetValue(variant, item, 1);
|
||||
const wxString selection = variant.GetString();
|
||||
|
||||
if (!m_config || selection.empty())
|
||||
return;
|
||||
|
||||
int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str());
|
||||
const int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str());
|
||||
m_config->set_key_value("extruder", new ConfigOptionInt(extruder));
|
||||
|
||||
// update scene
|
||||
wxGetApp().plater()->update();
|
||||
}
|
||||
|
||||
void ObjectList::update_name_in_model(const wxDataViewItem& item)
|
||||
{
|
||||
const int obj_idx = m_objects_model->GetObjectIdByItem(item);
|
||||
if (obj_idx < 0) return;
|
||||
|
||||
if (m_objects_model->GetParent(item) == wxDataViewItem(0)) {
|
||||
(*m_objects)[obj_idx]->name = m_objects_model->GetName(item).ToStdString();
|
||||
return;
|
||||
}
|
||||
|
||||
const int volume_id = m_objects_model->GetVolumeIdByItem(item);
|
||||
if (volume_id < 0) return;
|
||||
(*m_objects)[obj_idx]->volumes[volume_id]->name = m_objects_model->GetName(item).ToStdString();
|
||||
}
|
||||
|
||||
void ObjectList::init_icons()
|
||||
{
|
||||
m_bmp_modifiermesh = wxBitmap(from_u8(var("lambda.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG);
|
||||
@ -275,13 +350,20 @@ void ObjectList::selection_changed()
|
||||
}
|
||||
|
||||
part_selection_changed();
|
||||
|
||||
#ifdef __WXOSX__
|
||||
update_extruder_in_config(m_selected_extruder);
|
||||
#endif //__WXOSX__
|
||||
}
|
||||
|
||||
void ObjectList::context_menu()
|
||||
void ObjectList::OnChar(wxKeyEvent& event)
|
||||
{
|
||||
if (event.GetKeyCode() == WXK_BACK){
|
||||
remove();
|
||||
}
|
||||
else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_SHIFT))
|
||||
select_item_all_children();
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void ObjectList::OnContextMenu(wxDataViewEvent&)
|
||||
{
|
||||
wxDataViewItem item;
|
||||
wxDataViewColumn* col;
|
||||
@ -303,15 +385,14 @@ void ObjectList::context_menu()
|
||||
|
||||
if (title == " ")
|
||||
show_context_menu();
|
||||
|
||||
else if (title == _("Name") && pt.x >15 &&
|
||||
m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.GetRefData())
|
||||
{
|
||||
if (is_windows10()) {
|
||||
const auto obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item));
|
||||
wxGetApp().plater()->fix_through_netfabb(obj_idx);
|
||||
}
|
||||
else if (title == _("Name") && pt.x >15 &&
|
||||
m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.GetRefData())
|
||||
{
|
||||
if (is_windows10()) {
|
||||
const auto obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item));
|
||||
wxGetApp().plater()->fix_through_netfabb(obj_idx);
|
||||
}
|
||||
}
|
||||
#ifndef __WXMSW__
|
||||
GetMainWindow()->SetToolTip(""); // hide tooltip
|
||||
#endif //__WXMSW__
|
||||
@ -358,88 +439,74 @@ void ObjectList::key_event(wxKeyEvent& event)
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void ObjectList::item_value_change(wxDataViewEvent& event)
|
||||
void ObjectList::OnBeginDrag(wxDataViewEvent &event)
|
||||
{
|
||||
if (event.GetColumn() == 1)
|
||||
{
|
||||
wxVariant variant;
|
||||
m_objects_model->GetValue(variant, event.GetItem(), 1);
|
||||
#ifdef __WXOSX__
|
||||
m_selected_extruder = variant.GetString();
|
||||
#else // --> for Linux
|
||||
update_extruder_in_config(variant.GetString());
|
||||
#endif //__WXOSX__
|
||||
}
|
||||
}
|
||||
|
||||
struct draging_item_data
|
||||
{
|
||||
int obj_idx;
|
||||
int vol_idx;
|
||||
};
|
||||
|
||||
void ObjectList::on_begin_drag(wxDataViewEvent &event)
|
||||
{
|
||||
wxDataViewItem item(event.GetItem());
|
||||
const wxDataViewItem item(event.GetItem());
|
||||
|
||||
// only allow drags for item, not containers
|
||||
if (multiple_selection() ||
|
||||
if (multiple_selection() || GetSelection()!=item ||
|
||||
m_objects_model->GetParent(item) == wxDataViewItem(0) ||
|
||||
m_objects_model->GetItemType(item) != itVolume ) {
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
|
||||
m_dragged_data.init(m_objects_model->GetObjectIdByItem(item), m_objects_model->GetVolumeIdByItem(item));
|
||||
|
||||
/* Under MSW or OSX, DnD moves an item to the place of another selected item
|
||||
* But under GTK, DnD moves an item between another two items.
|
||||
* And as a result - call EVT_CHANGE_SELECTION to unselect all items.
|
||||
* To prevent such behavior use g_prevent_list_events
|
||||
* To prevent such behavior use m_prevent_list_events
|
||||
**/
|
||||
m_prevent_list_events = true;//it's needed for GTK
|
||||
|
||||
wxTextDataObject *obj = new wxTextDataObject;
|
||||
obj->SetText(wxString::Format("%d", m_objects_model->GetVolumeIdByItem(item)));
|
||||
/* Under GTK, DnD requires to the wxTextDataObject been initialized with some valid value,
|
||||
* so set some nonempty string
|
||||
*/
|
||||
wxTextDataObject* obj = new wxTextDataObject;
|
||||
obj->SetText("Some text");//it's needed for GTK
|
||||
|
||||
event.SetDataObject(obj);
|
||||
event.SetDragFlags(/*wxDrag_AllowMove*/wxDrag_DefaultMove); // allows both copy and move;
|
||||
event.SetDragFlags(wxDrag_DefaultMove); // allows both copy and move;
|
||||
}
|
||||
|
||||
void ObjectList::on_drop_possible(wxDataViewEvent &event)
|
||||
void ObjectList::OnDropPossible(wxDataViewEvent &event)
|
||||
{
|
||||
wxDataViewItem item(event.GetItem());
|
||||
|
||||
// only allow drags for item or background, not containers
|
||||
if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) ||
|
||||
event.GetDataFormat() != wxDF_UNICODETEXT || m_objects_model->GetItemType(item) != itVolume)
|
||||
if (!item.IsOk() ||
|
||||
m_objects_model->GetParent(item) == wxDataViewItem(0) ||
|
||||
m_objects_model->GetItemType(item) != itVolume ||
|
||||
m_dragged_data.obj_idx() != m_objects_model->GetObjectIdByItem(item))
|
||||
event.Veto();
|
||||
}
|
||||
|
||||
void ObjectList::on_drop(wxDataViewEvent &event)
|
||||
void ObjectList::OnDrop(wxDataViewEvent &event)
|
||||
{
|
||||
wxDataViewItem item(event.GetItem());
|
||||
|
||||
// only allow drops for item, not containers
|
||||
if (m_selected_object_id < 0 ||
|
||||
item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) ||
|
||||
event.GetDataFormat() != wxDF_UNICODETEXT || m_objects_model->GetItemType(item) != itVolume) {
|
||||
if (!item.IsOk() || m_objects_model->GetParent(item) == wxDataViewItem(0) ||
|
||||
m_objects_model->GetItemType(item) != itVolume ||
|
||||
m_dragged_data.obj_idx() != m_objects_model->GetObjectIdByItem(item)) {
|
||||
event.Veto();
|
||||
m_dragged_data.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
wxTextDataObject obj;
|
||||
obj.SetData(wxDF_UNICODETEXT, event.GetDataSize(), event.GetDataBuffer());
|
||||
|
||||
int from_volume_id = std::stoi(obj.GetText().ToStdString());
|
||||
const int from_volume_id = m_dragged_data.vol_idx();
|
||||
int to_volume_id = m_objects_model->GetVolumeIdByItem(item);
|
||||
|
||||
#ifdef __WXGTK__
|
||||
/* Under GTK, DnD moves an item between another two items.
|
||||
* And event.GetItem() return item, which is under "insertion line"
|
||||
* So, if we move item down we should to decrease the to_volume_id value
|
||||
**/
|
||||
if (to_volume_id > from_volume_id) to_volume_id--;
|
||||
#endif // __WXGTK__
|
||||
// It looks like a fixed in current version of the wxWidgets
|
||||
// #ifdef __WXGTK__
|
||||
// /* Under GTK, DnD moves an item between another two items.
|
||||
// * And event.GetItem() return item, which is under "insertion line"
|
||||
// * So, if we move item down we should to decrease the to_volume_id value
|
||||
// **/
|
||||
// if (to_volume_id > from_volume_id) to_volume_id--;
|
||||
// #endif // __WXGTK__
|
||||
|
||||
auto& volumes = (*m_objects)[m_selected_object_id]->volumes;
|
||||
auto& volumes = (*m_objects)[/*m_selected_object_id*/m_dragged_data.obj_idx()]->volumes;
|
||||
auto delta = to_volume_id < from_volume_id ? -1 : 1;
|
||||
int cnt = 0;
|
||||
for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++)
|
||||
@ -449,9 +516,9 @@ void ObjectList::on_drop(wxDataViewEvent &event)
|
||||
m_objects_model->GetParent(item)));
|
||||
|
||||
m_parts_changed = true;
|
||||
parts_changed(m_selected_object_id);
|
||||
parts_changed(/*m_selected_object_id*/m_dragged_data.obj_idx());
|
||||
|
||||
// m_prevent_list_events = false;
|
||||
m_dragged_data.clear();
|
||||
}
|
||||
|
||||
|
||||
@ -571,9 +638,6 @@ void ObjectList::get_settings_choice(const wxString& category_name)
|
||||
const auto settings_item = m_objects_model->GetSettingsItem(item);
|
||||
select_item(settings_item ? settings_item :
|
||||
m_objects_model->AddSettingsChild(item));
|
||||
#ifndef __WXOSX__
|
||||
// part_selection_changed();
|
||||
#endif //no __WXOSX__
|
||||
}
|
||||
else {
|
||||
auto panel = wxGetApp().sidebar().scrolled_panel();
|
||||
@ -747,11 +811,6 @@ void ObjectList::load_subobject(int type)
|
||||
if (i == part_names.size() - 1)
|
||||
select_item(sel_item);
|
||||
}
|
||||
|
||||
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
|
||||
// selection_changed();
|
||||
#endif //no __WXOSX__//__WXMSW__
|
||||
|
||||
}
|
||||
|
||||
void ObjectList::load_part( ModelObject* model_object,
|
||||
@ -1138,7 +1197,9 @@ void ObjectList::add_object_to_list(size_t obj_idx)
|
||||
{
|
||||
auto model_object = (*m_objects)[obj_idx];
|
||||
wxString item_name = model_object->name;
|
||||
auto item = m_objects_model->Add(item_name);
|
||||
const auto item = m_objects_model->Add(item_name,
|
||||
!model_object->config.has("extruder") ? 0 :
|
||||
model_object->config.option<ConfigOptionInt>("extruder")->value);
|
||||
|
||||
// Add error icon if detected auto-repaire
|
||||
auto stats = model_object->volumes[0]->mesh.stl.stats;
|
||||
@ -1152,13 +1213,19 @@ void ObjectList::add_object_to_list(size_t obj_idx)
|
||||
|
||||
// add volumes to the object
|
||||
if (model_object->volumes.size() > 1) {
|
||||
for (auto id = 0; id < model_object->volumes.size(); id++)
|
||||
m_objects_model->AddVolumeChild(item,
|
||||
model_object->volumes[id]->name,
|
||||
ModelVolume::MODEL_PART,
|
||||
!model_object->volumes[id]->config.has("extruder") ? 0 :
|
||||
for (auto id = 0; id < model_object->volumes.size(); id++) {
|
||||
auto vol_item = m_objects_model->AddVolumeChild(item,
|
||||
model_object->volumes[id]->name,
|
||||
model_object->volumes[id]->type()/*ModelVolume::MODEL_PART*/,
|
||||
!model_object->volumes[id]->config.has("extruder") ? 0 :
|
||||
model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value,
|
||||
false);
|
||||
false);
|
||||
auto opt_keys = model_object->volumes[id]->config.keys();
|
||||
if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) {
|
||||
select_item(m_objects_model->AddSettingsChild(vol_item));
|
||||
Collapse(vol_item);
|
||||
}
|
||||
}
|
||||
Expand(item);
|
||||
}
|
||||
|
||||
@ -1391,6 +1458,14 @@ void ObjectList::update_selections()
|
||||
}
|
||||
|
||||
select_items(sels);
|
||||
|
||||
#ifdef __WXMSW__
|
||||
if (GetSelection()) {
|
||||
const int sel_item_row = GetRowByItem(GetSelection());
|
||||
ScrollLines(sel_item_row - m_selected_row);
|
||||
m_selected_row = sel_item_row;
|
||||
}
|
||||
#endif //__WXMSW__
|
||||
}
|
||||
|
||||
void ObjectList::update_selections_on_canvas()
|
||||
@ -1532,7 +1607,24 @@ void ObjectList::change_part_type()
|
||||
ModelVolume* volume = get_selected_model_volume();
|
||||
if (!volume)
|
||||
return;
|
||||
|
||||
const auto type = volume->type();
|
||||
if (type == ModelVolume::MODEL_PART)
|
||||
{
|
||||
const int obj_idx = get_selected_obj_idx();
|
||||
if (obj_idx < 0) return;
|
||||
|
||||
int model_part_cnt = 0;
|
||||
for (auto vol : (*m_objects)[obj_idx]->volumes) {
|
||||
if (vol->type() == ModelVolume::MODEL_PART)
|
||||
++model_part_cnt;
|
||||
}
|
||||
|
||||
if (model_part_cnt == 1) {
|
||||
Slic3r::GUI::show_error(nullptr, _(L("You can't change a type of the last solid part of the object.")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const wxString names[] = { "Part", "Modifier", "Support Enforcer", "Support Blocker" };
|
||||
|
||||
@ -1552,11 +1644,11 @@ void ObjectList::change_part_type()
|
||||
//(we show additional settings for Part and Modifier and hide it for Support Blocker/Enforcer)
|
||||
const auto settings_item = m_objects_model->GetSettingsItem(item);
|
||||
if (settings_item &&
|
||||
new_type == ModelVolume::SUPPORT_ENFORCER || new_type == ModelVolume::SUPPORT_BLOCKER) {
|
||||
(new_type == ModelVolume::SUPPORT_ENFORCER || new_type == ModelVolume::SUPPORT_BLOCKER)) {
|
||||
m_objects_model->Delete(settings_item);
|
||||
}
|
||||
else if (!settings_item &&
|
||||
new_type == ModelVolume::MODEL_PART || new_type == ModelVolume::PARAMETER_MODIFIER) {
|
||||
(new_type == ModelVolume::MODEL_PART || new_type == ModelVolume::PARAMETER_MODIFIER)) {
|
||||
select_item(m_objects_model->AddSettingsChild(item));
|
||||
}
|
||||
}
|
||||
@ -1600,11 +1692,24 @@ void ObjectList::update_settings_items()
|
||||
UnselectAll();
|
||||
}
|
||||
|
||||
void ObjectList::OnStartEditing(wxDataViewEvent &event)
|
||||
void ObjectList::ItemValueChanged(wxDataViewEvent &event)
|
||||
{
|
||||
const auto item_type = m_objects_model->GetItemType(event.GetItem());
|
||||
if ( !(item_type&(itObject|itVolume)) )
|
||||
event.Veto();
|
||||
if (event.GetColumn() == 0)
|
||||
update_name_in_model(event.GetItem());
|
||||
else if (event.GetColumn() == 1)
|
||||
update_extruder_in_config(event.GetItem());
|
||||
}
|
||||
|
||||
void ObjectList::OnEditingDone(wxDataViewEvent &event)
|
||||
{
|
||||
if (event.GetColumn() != 0)
|
||||
return;
|
||||
|
||||
const auto renderer = dynamic_cast<PrusaBitmapTextRenderer*>(GetColumn(0)->GetRenderer());
|
||||
|
||||
if (renderer->WasCanceled())
|
||||
show_error(this, _(L("The supplied name is not valid;")) + "\n" +
|
||||
_(L("the following characters are not allowed:")) + " <>:/\\|?*\"");
|
||||
}
|
||||
|
||||
} //namespace GUI
|
||||
|
@ -53,6 +53,27 @@ struct ItemForDelete
|
||||
|
||||
class ObjectList : public wxDataViewCtrl
|
||||
{
|
||||
|
||||
struct dragged_item_data
|
||||
{
|
||||
void init(const int obj_idx, const int vol_idx) {
|
||||
m_obj_idx = obj_idx;
|
||||
m_vol_idx = vol_idx;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
m_obj_idx = -1;
|
||||
m_vol_idx = -1;
|
||||
}
|
||||
|
||||
int obj_idx() const { return m_obj_idx; }
|
||||
int vol_idx() const { return m_vol_idx; }
|
||||
|
||||
private:
|
||||
int m_obj_idx = -1;
|
||||
int m_vol_idx = -1;
|
||||
} m_dragged_data;
|
||||
|
||||
wxBoxSizer *m_sizer {nullptr};
|
||||
|
||||
DynamicPrintConfig *m_default_config {nullptr};
|
||||
@ -80,12 +101,15 @@ class ObjectList : public wxDataViewCtrl
|
||||
bool m_prevent_list_events = false; // We use this flag to avoid circular event handling Select()
|
||||
// happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler
|
||||
// calls this method again and again and again
|
||||
#ifdef __WXOSX__
|
||||
wxString m_selected_extruder = "";
|
||||
#endif //__WXOSX__
|
||||
|
||||
bool m_prevent_update_extruder_in_config = false; // We use this flag to avoid updating of the extruder value in config
|
||||
// during updating of the extruder count.
|
||||
|
||||
bool m_parts_changed = false;
|
||||
bool m_part_settings_changed = false;
|
||||
|
||||
int m_selected_row = 0;
|
||||
|
||||
public:
|
||||
ObjectList(wxWindow* parent);
|
||||
~ObjectList();
|
||||
@ -103,23 +127,20 @@ public:
|
||||
wxDataViewColumn* create_objects_list_extruder_column(int extruders_count);
|
||||
void update_objects_list_extruder_column(int extruders_count);
|
||||
// show/hide "Extruder" column for Objects List
|
||||
void set_extruder_column_hidden(bool hide);
|
||||
void set_extruder_column_hidden(const bool hide) const;
|
||||
// update extruder in current config
|
||||
void update_extruder_in_config(const wxString& selection);
|
||||
void update_extruder_in_config(const wxDataViewItem& item);
|
||||
// update changed name in the object model
|
||||
void update_name_in_model(const wxDataViewItem& item);
|
||||
void update_extruder_values_for_items(const int max_extruder);
|
||||
|
||||
void init_icons();
|
||||
|
||||
void set_tooltip_for_item(const wxPoint& pt);
|
||||
|
||||
void selection_changed();
|
||||
void context_menu();
|
||||
void show_context_menu();
|
||||
void key_event(wxKeyEvent& event);
|
||||
void item_value_change(wxDataViewEvent& event);
|
||||
|
||||
void on_begin_drag(wxDataViewEvent &event);
|
||||
void on_drop_possible(wxDataViewEvent &event);
|
||||
void on_drop(wxDataViewEvent &event);
|
||||
|
||||
void get_settings_choice(const wxString& category_name);
|
||||
void append_menu_item_add_generic(wxMenuItem* menu, const int type);
|
||||
@ -202,8 +223,15 @@ public:
|
||||
void update_settings_items();
|
||||
|
||||
private:
|
||||
void OnStartEditing(wxDataViewEvent &event);
|
||||
void OnChar(wxKeyEvent& event);
|
||||
void OnContextMenu(wxDataViewEvent &event);
|
||||
|
||||
void OnBeginDrag(wxDataViewEvent &event);
|
||||
void OnDropPossible(wxDataViewEvent &event);
|
||||
void OnDrop(wxDataViewEvent &event);
|
||||
|
||||
void ItemValueChanged(wxDataViewEvent &event);
|
||||
void OnEditingDone(wxDataViewEvent &event);
|
||||
};
|
||||
|
||||
|
||||
|
@ -21,9 +21,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
||||
m_og->set_name(_(L("Object Manipulation")));
|
||||
m_og->label_width = 100;
|
||||
m_og->set_grid_vgap(5);
|
||||
m_og->set_process_enter(); // We need to update new values only after press ENTER
|
||||
|
||||
m_og->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
m_og->m_on_change = [this](const std::string& opt_key, const boost::any& value) {
|
||||
std::vector<std::string> axes{ "_x", "_y", "_z" };
|
||||
|
||||
if (opt_key == "scale_unit") {
|
||||
@ -54,6 +53,29 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
||||
change_scale_value(new_value);
|
||||
};
|
||||
|
||||
m_og->m_fill_empty_value = [this](const std::string& opt_key)
|
||||
{
|
||||
if (opt_key == "scale_unit")
|
||||
return;
|
||||
|
||||
std::string param;
|
||||
std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param));
|
||||
if (param == "position") {
|
||||
int axis = opt_key.back() == 'x' ? 0 :
|
||||
opt_key.back() == 'y' ? 1 : 2;
|
||||
|
||||
m_og->set_value(opt_key, double_to_string(cache_position(axis)));
|
||||
return;
|
||||
}
|
||||
|
||||
m_og->set_value(opt_key, double_to_string(0.0));
|
||||
};
|
||||
|
||||
m_og->m_set_focus = [this](const std::string& opt_key)
|
||||
{
|
||||
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key);
|
||||
};
|
||||
|
||||
ConfigOptionDef def;
|
||||
|
||||
// Objects(sub-objects) name
|
||||
|
@ -17,7 +17,7 @@ protected:
|
||||
wxWindow* m_parent;
|
||||
public:
|
||||
OG_Settings(wxWindow* parent, const bool staticbox);
|
||||
~OG_Settings() {}
|
||||
virtual ~OG_Settings() {}
|
||||
|
||||
virtual bool IsShown();
|
||||
virtual void Show(const bool show);
|
||||
|
@ -794,7 +794,8 @@ void MainFrame::update_ui_from_settings()
|
||||
{
|
||||
bool bp_on = wxGetApp().app_config->get("background_processing") == "1";
|
||||
m_menu_item_reslice_now->Enable(bp_on);
|
||||
m_plater->sidebar().show_button(baReslice, !bp_on);
|
||||
m_plater->sidebar().show_reslice(!bp_on);
|
||||
m_plater->sidebar().Layout();
|
||||
if (m_plater)
|
||||
m_plater->update_ui_from_settings();
|
||||
for (auto tab: wxGetApp().tabs_list)
|
||||
|
@ -44,7 +44,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
|
||||
case coPercents:
|
||||
case coString:
|
||||
case coStrings:
|
||||
m_fields.emplace(id, std::move(TextCtrl::Create<TextCtrl>(parent(), opt, id, process_enter)));
|
||||
m_fields.emplace(id, std::move(TextCtrl::Create<TextCtrl>(parent(), opt, id)));
|
||||
break;
|
||||
case coBool:
|
||||
case coBools:
|
||||
@ -67,16 +67,21 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
|
||||
}
|
||||
// Grab a reference to fields for convenience
|
||||
const t_field& field = m_fields[id];
|
||||
field->m_on_change = [this](std::string opt_id, boost::any value) {
|
||||
field->m_on_change = [this](const std::string& opt_id, const boost::any& value) {
|
||||
//! This function will be called from Field.
|
||||
//! Call OptionGroup._on_change(...)
|
||||
if (!m_disabled)
|
||||
this->on_change_OG(opt_id, value);
|
||||
};
|
||||
field->m_on_kill_focus = [this]() {
|
||||
field->m_on_kill_focus = [this](const std::string& opt_id) {
|
||||
//! This function will be called from Field.
|
||||
if (!m_disabled)
|
||||
this->on_kill_focus();
|
||||
this->on_kill_focus(opt_id);
|
||||
};
|
||||
field->m_on_set_focus = [this](const std::string& opt_id) {
|
||||
//! This function will be called from Field.
|
||||
if (!m_disabled)
|
||||
this->on_set_focus(opt_id);
|
||||
};
|
||||
field->m_parent = parent();
|
||||
|
||||
@ -277,6 +282,12 @@ Line OptionsGroup::create_single_option_line(const Option& option) const {
|
||||
return retval;
|
||||
}
|
||||
|
||||
void OptionsGroup::on_set_focus(const std::string& opt_key)
|
||||
{
|
||||
if (m_set_focus != nullptr)
|
||||
m_set_focus(opt_key);
|
||||
}
|
||||
|
||||
void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value) {
|
||||
if (m_on_change != nullptr)
|
||||
m_on_change(opt_id, value);
|
||||
@ -378,6 +389,15 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config,
|
||||
on_change_OG(opt_key, get_value(opt_key));
|
||||
}
|
||||
|
||||
void ConfigOptionsGroup::on_kill_focus(const std::string& opt_key)
|
||||
{
|
||||
if (m_fill_empty_value) {
|
||||
m_fill_empty_value(opt_key);
|
||||
return;
|
||||
}
|
||||
reload_config();
|
||||
}
|
||||
|
||||
void ConfigOptionsGroup::reload_config() {
|
||||
for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) {
|
||||
auto opt_id = it->first;
|
||||
@ -534,6 +554,9 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
|
||||
else if (opt_key.compare("host_type") == 0) {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<PrintHostType>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("display_orientation") == 0) {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<SLADisplayOrientation>>(opt_key)->value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case coPoints:
|
||||
|
@ -85,7 +85,9 @@ public:
|
||||
size_t label_width {200};
|
||||
wxSizer* sizer {nullptr};
|
||||
column_t extra_column {nullptr};
|
||||
t_change m_on_change {nullptr};
|
||||
t_change m_on_change { nullptr };
|
||||
t_kill_focus m_fill_empty_value { nullptr };
|
||||
t_kill_focus m_set_focus { nullptr };
|
||||
std::function<DynamicPrintConfig()> m_get_initial_config{ nullptr };
|
||||
std::function<DynamicPrintConfig()> m_get_sys_config{ nullptr };
|
||||
std::function<bool()> have_sys_config{ nullptr };
|
||||
@ -94,8 +96,6 @@ public:
|
||||
wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
|
||||
int sidetext_width{ -1 };
|
||||
|
||||
bool process_enter { false };
|
||||
|
||||
/// Returns a copy of the pointer of the parent wxWindow.
|
||||
/// Accessor function is because users are not allowed to change the parent
|
||||
/// but defining it as const means a lot of const_casts to deal with wx functions.
|
||||
@ -154,11 +154,6 @@ public:
|
||||
m_show_modified_btns = show;
|
||||
}
|
||||
|
||||
// The controls inside this option group will generate the event wxEVT_TEXT_ENTER
|
||||
void set_process_enter() {
|
||||
process_enter = true;
|
||||
}
|
||||
|
||||
OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false,
|
||||
column_t extra_clmn = nullptr) :
|
||||
m_parent(_parent), title(title),
|
||||
@ -215,7 +210,8 @@ protected:
|
||||
const t_field& build_field(const Option& opt, wxStaticText* label = nullptr);
|
||||
void add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field);
|
||||
|
||||
virtual void on_kill_focus () {};
|
||||
virtual void on_kill_focus(const std::string& opt_key) {};
|
||||
virtual void on_set_focus(const std::string& opt_key);
|
||||
virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value);
|
||||
virtual void back_to_initial_value(const std::string& opt_key) {}
|
||||
virtual void back_to_sys_value(const std::string& opt_key) {}
|
||||
@ -251,7 +247,7 @@ public:
|
||||
void back_to_initial_value(const std::string& opt_key) override;
|
||||
void back_to_sys_value(const std::string& opt_key) override;
|
||||
void back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key);
|
||||
void on_kill_focus() override{ reload_config();}
|
||||
void on_kill_focus(const std::string& opt_key) override;// { reload_config(); }
|
||||
void reload_config();
|
||||
// return value shows visibility : false => all options are hidden
|
||||
void Hide();
|
||||
|
@ -54,7 +54,9 @@
|
||||
#include "PresetBundle.hpp"
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "ProgressStatusBar.hpp"
|
||||
#include "PrintHostDialogs.hpp"
|
||||
#include "../Utils/ASCIIFolding.hpp"
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
#include "../Utils/FixModelByWin10.hpp"
|
||||
|
||||
#include <wx/glcanvas.h> // Needs to be last because reasons :-/
|
||||
@ -64,6 +66,7 @@ using boost::optional;
|
||||
namespace fs = boost::filesystem;
|
||||
using Slic3r::_3DScene;
|
||||
using Slic3r::Preset;
|
||||
using Slic3r::PrintHostJob;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
@ -323,17 +326,17 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) :
|
||||
double brim_width = config->opt_float("brim_width");
|
||||
if (boost::any_cast<bool>(value) == true)
|
||||
{
|
||||
new_val = m_brim_width == 0.0 ? 10 :
|
||||
new_val = m_brim_width == 0.0 ? 5 :
|
||||
m_brim_width < 0.0 ? m_brim_width * (-1) :
|
||||
m_brim_width;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
m_brim_width = brim_width * (-1);
|
||||
new_val = 0;
|
||||
}
|
||||
new_conf.set_key_value("brim_width", new ConfigOptionFloat(new_val));
|
||||
}
|
||||
else{ //(opt_key == "support")
|
||||
else { //(opt_key == "support")
|
||||
const wxString& selection = boost::any_cast<wxString>(value);
|
||||
|
||||
auto support_material = selection == _("None") ? false : true;
|
||||
@ -447,7 +450,6 @@ struct Sidebar::priv
|
||||
|
||||
wxButton *btn_export_gcode;
|
||||
wxButton *btn_reslice;
|
||||
// wxButton *btn_print; // XXX: remove
|
||||
wxButton *btn_send_gcode;
|
||||
|
||||
priv(Plater *plater) : plater(plater) {}
|
||||
@ -543,13 +545,11 @@ Sidebar::Sidebar(Plater *parent)
|
||||
p->object_settings->Hide();
|
||||
p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxLEFT | wxTOP, 20);
|
||||
|
||||
// Buttons in the scrolled area
|
||||
wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG);
|
||||
p->btn_send_gcode = new wxButton(p->scrolled, wxID_ANY, _(L("Send to printer")));
|
||||
p->btn_send_gcode = new wxButton(this, wxID_ANY, _(L("Send to printer")));
|
||||
p->btn_send_gcode->SetBitmap(arrow_up);
|
||||
p->btn_send_gcode->SetFont(wxGetApp().bold_font());
|
||||
p->btn_send_gcode->Hide();
|
||||
auto *btns_sizer_scrolled = new wxBoxSizer(wxHORIZONTAL);
|
||||
btns_sizer_scrolled->Add(p->btn_send_gcode);
|
||||
|
||||
// Info boxes
|
||||
p->object_info = new ObjectInfo(p->scrolled);
|
||||
@ -559,7 +559,6 @@ Sidebar::Sidebar(Plater *parent)
|
||||
scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, 2);
|
||||
scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND);
|
||||
scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, 20);
|
||||
scrolled_sizer->Add(btns_sizer_scrolled, 0, wxEXPAND, 0);
|
||||
scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, 20);
|
||||
|
||||
// Buttons underneath the scrolled area
|
||||
@ -571,6 +570,7 @@ Sidebar::Sidebar(Plater *parent)
|
||||
|
||||
auto *btns_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, 5);
|
||||
btns_sizer->Add(p->btn_send_gcode, 0, wxEXPAND | wxTOP, 5);
|
||||
btns_sizer->Add(p->btn_export_gcode, 0, wxEXPAND | wxTOP, 5);
|
||||
|
||||
auto *sizer = new wxBoxSizer(wxVERTICAL);
|
||||
@ -820,14 +820,6 @@ void Sidebar::show_sliced_info_sizer(const bool show)
|
||||
p->scrolled->Refresh();
|
||||
}
|
||||
|
||||
void Sidebar::show_buttons(const bool show)
|
||||
{
|
||||
p->btn_reslice->Show(show);
|
||||
TabPrinter *tab = dynamic_cast<TabPrinter*>(wxGetApp().get_tab(Preset::TYPE_PRINTER));
|
||||
if (tab && p->plater->printer_technology() == ptFFF)
|
||||
p->btn_send_gcode->Show(show && !tab->m_config->opt_string("print_host").empty());
|
||||
}
|
||||
|
||||
void Sidebar::enable_buttons(bool enable)
|
||||
{
|
||||
p->btn_reslice->Enable(enable);
|
||||
@ -835,23 +827,8 @@ void Sidebar::enable_buttons(bool enable)
|
||||
p->btn_send_gcode->Enable(enable);
|
||||
}
|
||||
|
||||
void Sidebar::show_button(ButtonAction but_action, bool show)
|
||||
{
|
||||
switch (but_action)
|
||||
{
|
||||
case baReslice:
|
||||
p->btn_reslice->Show(show);
|
||||
break;
|
||||
case baExportGcode:
|
||||
p->btn_export_gcode->Show(show);
|
||||
break;
|
||||
case baSendGcode:
|
||||
p->btn_send_gcode->Show(show);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void Sidebar::show_reslice(bool show) { p->btn_reslice->Show(show); }
|
||||
void Sidebar::show_send(bool show) { p->btn_send_gcode->Show(show); }
|
||||
|
||||
bool Sidebar::is_multifilament()
|
||||
{
|
||||
@ -1008,6 +985,7 @@ struct Plater::priv
|
||||
};
|
||||
// returns bit mask of UpdateBackgroundProcessReturnState
|
||||
unsigned int update_background_process();
|
||||
void export_gcode(fs::path output_path, PrintHostJob upload_job);
|
||||
void async_apply_config();
|
||||
void reload_from_disk();
|
||||
void fix_through_netfabb(const int obj_idx);
|
||||
@ -1453,6 +1431,18 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
}
|
||||
}
|
||||
|
||||
// check multi-part object adding for the SLA-printing
|
||||
if (printer_technology == ptSLA)
|
||||
{
|
||||
for (auto obj : model.objects)
|
||||
if ( obj->volumes.size()>1 ) {
|
||||
Slic3r::GUI::show_error(nullptr,
|
||||
wxString::Format(_(L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part")),
|
||||
filename.string()));
|
||||
return std::vector<size_t>();
|
||||
}
|
||||
}
|
||||
|
||||
if (one_by_one) {
|
||||
auto loaded_idxs = load_model_objects(model.objects);
|
||||
obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
|
||||
@ -2047,6 +2037,45 @@ unsigned int Plater::priv::update_background_process()
|
||||
return return_state;
|
||||
}
|
||||
|
||||
void Plater::priv::export_gcode(fs::path output_path, PrintHostJob upload_job)
|
||||
{
|
||||
wxCHECK_RET(!(output_path.empty() && upload_job.empty()), "export_gcode: output_path and upload_job empty");
|
||||
|
||||
if (model.objects.empty())
|
||||
return;
|
||||
|
||||
if (background_process.is_export_scheduled()) {
|
||||
GUI::show_error(q, _(L("Another export job is currently running.")));
|
||||
return;
|
||||
}
|
||||
|
||||
// bitmask of UpdateBackgroundProcessReturnState
|
||||
unsigned int state = update_background_process();
|
||||
if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
|
||||
#if ENABLE_REMOVE_TABS_FROM_PLATER
|
||||
view3D->reload_scene(false);
|
||||
#else
|
||||
canvas3D->reload_scene(false);
|
||||
#endif // ENABLE_REMOVE_TABS_FROM_PLATER
|
||||
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
|
||||
return;
|
||||
|
||||
if (! output_path.empty()) {
|
||||
background_process.schedule_export(output_path.string());
|
||||
} else {
|
||||
background_process.schedule_upload(std::move(upload_job));
|
||||
}
|
||||
|
||||
if (! background_process.running()) {
|
||||
// The print is valid and it should be started.
|
||||
if (background_process.start())
|
||||
statusbar()->set_cancel_callback([this]() {
|
||||
statusbar()->set_status_text(L("Cancelling"));
|
||||
background_process.stop();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::priv::async_apply_config()
|
||||
{
|
||||
// bitmask of UpdateBackgroundProcessReturnState
|
||||
@ -2200,6 +2229,7 @@ void Plater::priv::set_current_panel(wxPanel* panel)
|
||||
}
|
||||
else if (current_panel == preview)
|
||||
{
|
||||
this->q->reslice();
|
||||
preview->reload_print();
|
||||
preview->set_canvas_as_dirty();
|
||||
}
|
||||
@ -2443,7 +2473,8 @@ void Plater::priv::on_right_click(Vec2dEvent& evt)
|
||||
return;
|
||||
|
||||
wxMenu* menu = printer_technology == ptSLA ? &sla_object_menu :
|
||||
get_selection().is_single_full_object() ? &object_menu : &part_menu;
|
||||
get_selection().is_single_full_instance/*object*/() ? // show "Object menu" for each FullInstance instead of FullObject
|
||||
&object_menu : &part_menu;
|
||||
|
||||
sidebar->obj_list()->append_menu_item_settings(menu);
|
||||
|
||||
@ -2899,42 +2930,26 @@ void Plater::export_gcode(fs::path output_path)
|
||||
if (p->model.objects.empty())
|
||||
return;
|
||||
|
||||
if (this->p->background_process.is_export_scheduled()) {
|
||||
GUI::show_error(this, _(L("Another export job is currently running.")));
|
||||
return;
|
||||
}
|
||||
|
||||
// bitmask of UpdateBackgroundProcessReturnState
|
||||
unsigned int state = this->p->update_background_process();
|
||||
if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
|
||||
#if ENABLE_REMOVE_TABS_FROM_PLATER
|
||||
this->p->view3D->reload_scene(false);
|
||||
#else
|
||||
this->p->canvas3D->reload_scene(false);
|
||||
#endif // ENABLE_REMOVE_TABS_FROM_PLATER
|
||||
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
|
||||
return;
|
||||
|
||||
// select output file
|
||||
if (output_path.empty()) {
|
||||
// XXX: take output path from CLI opts? Ancient Slic3r versions used to do that...
|
||||
|
||||
// If possible, remove accents from accented latin characters.
|
||||
// This function is useful for generating file names to be processed by legacy firmwares.
|
||||
fs::path default_output_file;
|
||||
fs::path default_output_file;
|
||||
try {
|
||||
default_output_file = this->p->background_process.current_print()->output_filepath(output_path.string());
|
||||
default_output_file = this->p->background_process.current_print()->output_filepath(output_path.string());
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(this, ex.what());
|
||||
return;
|
||||
}
|
||||
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
|
||||
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
|
||||
auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string());
|
||||
|
||||
wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip file as:")),
|
||||
wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip file as:")),
|
||||
start_dir,
|
||||
default_output_file.filename().string(),
|
||||
GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()),
|
||||
GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()),
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
|
||||
);
|
||||
|
||||
@ -2945,23 +2960,15 @@ void Plater::export_gcode(fs::path output_path)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
output_path = this->p->background_process.current_print()->output_filepath(output_path.string());
|
||||
output_path = this->p->background_process.current_print()->output_filepath(output_path.string());
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(this, ex.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (! output_path.empty())
|
||||
this->p->background_process.schedule_export(output_path.string());
|
||||
|
||||
if ((! output_path.empty() || this->p->background_processing_enabled()) && ! this->p->background_process.running()) {
|
||||
// The print is valid and it should be started.
|
||||
if (this->p->background_process.start())
|
||||
this->p->statusbar()->set_cancel_callback([this]() {
|
||||
this->p->statusbar()->set_status_text(L("Cancelling"));
|
||||
this->p->background_process.stop();
|
||||
});
|
||||
if (! output_path.empty()) {
|
||||
p->export_gcode(std::move(output_path), PrintHostJob());
|
||||
}
|
||||
}
|
||||
|
||||
@ -3052,7 +3059,7 @@ void Plater::reslice()
|
||||
#else
|
||||
this->p->canvas3D->reload_scene(false);
|
||||
#endif // ENABLE_REMOVE_TABS_FROM_PLATER
|
||||
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 && !this->p->background_process.running()) {
|
||||
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 && !this->p->background_process.running() && !this->p->background_process.finished()) {
|
||||
// The print is valid and it can be started.
|
||||
if (this->p->background_process.start())
|
||||
this->p->statusbar()->set_cancel_callback([this]() {
|
||||
@ -3064,7 +3071,28 @@ void Plater::reslice()
|
||||
|
||||
void Plater::send_gcode()
|
||||
{
|
||||
// p->send_gcode_file = export_gcode();
|
||||
if (p->model.objects.empty()) { return; }
|
||||
|
||||
PrintHostJob upload_job(p->config);
|
||||
if (upload_job.empty()) { return; }
|
||||
|
||||
// Obtain default output path
|
||||
fs::path default_output_file;
|
||||
try {
|
||||
default_output_file = this->p->background_process.current_print()->output_filepath("");
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(this, ex.what());
|
||||
return;
|
||||
}
|
||||
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
|
||||
|
||||
Slic3r::PrintHostSendDialog dlg(default_output_file);
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
upload_job.upload_data.upload_path = dlg.filename();
|
||||
upload_job.upload_data.start_print = dlg.start_print();
|
||||
|
||||
p->export_gcode(fs::path(), std::move(upload_job));
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::on_extruders_change(int num_extruders)
|
||||
@ -3114,14 +3142,6 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
|
||||
opt_key == "single_extruder_multi_material") {
|
||||
update_scheduled = true;
|
||||
}
|
||||
// else if(opt_key == "serial_port") {
|
||||
// sidebar()->p->btn_print->Show(config.get("serial_port")); // ???: btn_print is removed
|
||||
// Layout();
|
||||
// }
|
||||
else if (opt_key == "print_host") {
|
||||
sidebar().show_button(baReslice, !p->config->option<ConfigOptionString>(opt_key)->value.empty());
|
||||
Layout();
|
||||
}
|
||||
else if(opt_key == "variable_layer_height") {
|
||||
if (p->config->opt_bool("variable_layer_height") != true) {
|
||||
#if ENABLE_REMOVE_TABS_FROM_PLATER
|
||||
@ -3161,6 +3181,11 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const auto prin_host_opt = p->config->option<ConfigOptionString>("print_host");
|
||||
p->sidebar->show_send(prin_host_opt != nullptr && !prin_host_opt->value.empty());
|
||||
}
|
||||
|
||||
if (update_scheduled)
|
||||
update();
|
||||
|
||||
|
@ -55,14 +55,6 @@ private:
|
||||
int extruder_idx = -1;
|
||||
};
|
||||
|
||||
enum ButtonAction
|
||||
{
|
||||
baUndef,
|
||||
baReslice,
|
||||
baExportGcode,
|
||||
baSendGcode
|
||||
};
|
||||
|
||||
class Sidebar : public wxPanel
|
||||
{
|
||||
/*ConfigMenuIDs*/int m_mode;
|
||||
@ -88,9 +80,9 @@ public:
|
||||
void update_objects_list_extruder_column(int extruders_count);
|
||||
void show_info_sizer();
|
||||
void show_sliced_info_sizer(const bool show);
|
||||
void show_buttons(const bool show);
|
||||
void show_button(ButtonAction but_action, bool show);
|
||||
void enable_buttons(bool enable);
|
||||
void show_reslice(bool show);
|
||||
void show_send(bool show);
|
||||
bool is_multifilament();
|
||||
void set_mode_value(const /*ConfigMenuIDs*/int mode) { m_mode = mode; }
|
||||
|
||||
@ -103,6 +95,8 @@ private:
|
||||
class Plater: public wxPanel
|
||||
{
|
||||
public:
|
||||
using fs_path = boost::filesystem::path;
|
||||
|
||||
Plater(wxWindow *parent, MainFrame *main_frame);
|
||||
Plater(Plater &&) = delete;
|
||||
Plater(const Plater &) = delete;
|
||||
|
@ -5,6 +5,12 @@
|
||||
#include "BitmapCache.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#endif /* _MSC_VER */
|
||||
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <boost/format.hpp>
|
||||
@ -13,6 +19,7 @@
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <boost/nowide/cenv.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
@ -446,6 +453,7 @@ const std::vector<std::string>& Preset::sla_printer_options()
|
||||
"printer_technology",
|
||||
"bed_shape", "max_print_height",
|
||||
"display_width", "display_height", "display_pixels_x", "display_pixels_y",
|
||||
"display_orientation",
|
||||
"printer_correction",
|
||||
"printer_notes",
|
||||
"inherits"
|
||||
@ -498,6 +506,16 @@ void PresetCollection::add_default_preset(const std::vector<std::string> &keys,
|
||||
++ m_num_default_presets;
|
||||
}
|
||||
|
||||
bool is_file_plain(const std::string &path)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
DWORD attributes = GetFileAttributesW(boost::nowide::widen(path).c_str());
|
||||
return (attributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) == 0;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Load all presets found in dir_path.
|
||||
// Throws an exception on error.
|
||||
void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir)
|
||||
@ -506,7 +524,10 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
|
||||
m_dir_path = dir.string();
|
||||
std::string errors_cummulative;
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
|
||||
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) {
|
||||
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini") &&
|
||||
// Ignore system and hidden files, which may be created by the DropBox synchronisation process.
|
||||
// https://github.com/prusa3d/Slic3r/issues/1298
|
||||
is_file_plain(dir_entry.path().string())) {
|
||||
std::string name = dir_entry.path().filename().string();
|
||||
// Remove the .ini suffix.
|
||||
name.erase(name.size() - 4);
|
||||
|
@ -1313,7 +1313,7 @@ void PresetBundle::update_compatible(bool select_other_if_incompatible)
|
||||
}
|
||||
}
|
||||
|
||||
void PresetBundle::export_configbundle(const std::string &path) //, const DynamicPrintConfig &settings
|
||||
void PresetBundle::export_configbundle(const std::string &path, bool export_system_settings)
|
||||
{
|
||||
boost::nowide::ofstream c;
|
||||
c.open(path, std::ios::out | std::ios::trunc);
|
||||
@ -1323,14 +1323,15 @@ void PresetBundle::export_configbundle(const std::string &path) //, const Dynami
|
||||
|
||||
// Export the print, filament and printer profiles.
|
||||
|
||||
// #ys_FIXME_SLA_PRINT
|
||||
for (size_t i_group = 0; i_group < 3; ++ i_group) {
|
||||
const PresetCollection &presets = (i_group == 0) ? this->prints : (i_group == 1) ? this->filaments : this->printers;
|
||||
for (const Preset &preset : presets()) {
|
||||
if (preset.is_default || preset.is_external)
|
||||
for (const PresetCollection *presets : {
|
||||
(const PresetCollection*)&this->prints, (const PresetCollection*)&this->filaments,
|
||||
(const PresetCollection*)&this->sla_prints, (const PresetCollection*)&this->sla_materials,
|
||||
(const PresetCollection*)&this->printers }) {
|
||||
for (const Preset &preset : (*presets)()) {
|
||||
if (preset.is_default || preset.is_external || (preset.is_system && ! export_system_settings))
|
||||
// Only export the common presets, not external files or the default preset.
|
||||
continue;
|
||||
c << std::endl << "[" << presets.name() << ":" << preset.name << "]" << std::endl;
|
||||
c << std::endl << "[" << presets->name() << ":" << preset.name << "]" << std::endl;
|
||||
for (const std::string &opt_key : preset.config.keys())
|
||||
c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl;
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ public:
|
||||
size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE);
|
||||
|
||||
// Export a config bundle file containing all the presets and the names of the active presets.
|
||||
void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings);
|
||||
void export_configbundle(const std::string &path, bool export_system_settings = false);
|
||||
|
||||
// Update a filament selection combo box on the platter for an idx_extruder.
|
||||
void update_platter_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui);
|
||||
|
49
src/slic3r/GUI/PrintHostDialogs.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include "PrintHostDialogs.hpp"
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path)
|
||||
: MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE)
|
||||
, txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring()))
|
||||
, box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload"))))
|
||||
{
|
||||
auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
|
||||
label_dir_hint->Wrap(CONTENT_WIDTH);
|
||||
|
||||
content_sizer->Add(txt_filename, 0, wxEXPAND);
|
||||
content_sizer->Add(label_dir_hint);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
|
||||
|
||||
btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
|
||||
|
||||
txt_filename->SetFocus();
|
||||
wxString stem(path.stem().wstring());
|
||||
txt_filename->SetSelection(0, stem.Length());
|
||||
|
||||
Fit();
|
||||
}
|
||||
|
||||
fs::path PrintHostSendDialog::filename() const
|
||||
{
|
||||
return fs::path(txt_filename->GetValue().wx_str());
|
||||
}
|
||||
|
||||
bool PrintHostSendDialog::start_print() const
|
||||
{
|
||||
return box_print->GetValue(); }
|
||||
}
|
@ -20,19 +20,30 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
class PrintHostSendDialog : public GUI::MsgDialog
|
||||
{
|
||||
private:
|
||||
wxTextCtrl *txt_filename;
|
||||
wxCheckBox *box_print;
|
||||
bool can_start_print;
|
||||
|
||||
public:
|
||||
PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print);
|
||||
boost::filesystem::path filename() const;
|
||||
bool print() const;
|
||||
PrintHostSendDialog(const boost::filesystem::path &path);
|
||||
boost::filesystem::path filename() const;
|
||||
bool start_print() const;
|
||||
|
||||
private:
|
||||
wxTextCtrl *txt_filename;
|
||||
wxCheckBox *box_print;
|
||||
bool can_start_print;
|
||||
};
|
||||
|
||||
|
||||
class PrintHostQueueDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
PrintHostQueueDialog();
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -270,7 +270,7 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str
|
||||
auto panel = this;
|
||||
#endif
|
||||
PageShp page(new Page(panel, title, icon_idx));
|
||||
page->SetScrollbars(1, 1, 1, 1);
|
||||
page->SetScrollbars(1, 1, 1, 2);
|
||||
page->Hide();
|
||||
m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5);
|
||||
|
||||
@ -1883,6 +1883,7 @@ void TabPrinter::build_sla()
|
||||
line.append_option(option);
|
||||
line.append_option(optgroup->get_option("display_pixels_y"));
|
||||
optgroup->append_line(line);
|
||||
optgroup->append_single_option_line("display_orientation");
|
||||
|
||||
optgroup = page->new_optgroup(_(L("Corrections")));
|
||||
line = Line{ m_config->def()->get("printer_correction")->full_label, "" };
|
||||
|
@ -454,9 +454,10 @@ PrusaObjectDataViewModel::~PrusaObjectDataViewModel()
|
||||
m_bitmap_cache = nullptr;
|
||||
}
|
||||
|
||||
wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name)
|
||||
wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int extruder)
|
||||
{
|
||||
auto root = new PrusaObjectDataViewModelNode(name);
|
||||
const wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder);
|
||||
auto root = new PrusaObjectDataViewModelNode(name, extruder_str);
|
||||
m_objects.push_back(root);
|
||||
// notify control
|
||||
wxDataViewItem child((void*)root);
|
||||
@ -890,7 +891,7 @@ wxDataViewItem PrusaObjectDataViewModel::GetItemByInstanceId(int obj_idx, int in
|
||||
return wxDataViewItem(0);
|
||||
}
|
||||
|
||||
int PrusaObjectDataViewModel::GetIdByItem(const wxDataViewItem& item)
|
||||
int PrusaObjectDataViewModel::GetIdByItem(const wxDataViewItem& item) const
|
||||
{
|
||||
wxASSERT(item.IsOk());
|
||||
|
||||
@ -912,6 +913,11 @@ int PrusaObjectDataViewModel::GetIdByItemAndType(const wxDataViewItem& item, con
|
||||
return node->GetIdx();
|
||||
}
|
||||
|
||||
int PrusaObjectDataViewModel::GetObjectIdByItem(const wxDataViewItem& item) const
|
||||
{
|
||||
return GetIdByItem(GetTopParent(item));
|
||||
}
|
||||
|
||||
int PrusaObjectDataViewModel::GetVolumeIdByItem(const wxDataViewItem& item) const
|
||||
{
|
||||
return GetIdByItemAndType(item, itVolume);
|
||||
@ -1275,6 +1281,52 @@ wxSize PrusaBitmapTextRenderer::GetSize() const
|
||||
}
|
||||
|
||||
|
||||
wxWindow* PrusaBitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value)
|
||||
{
|
||||
wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner();
|
||||
PrusaObjectDataViewModel* const model = dynamic_cast<PrusaObjectDataViewModel*>(dv_ctrl->GetModel());
|
||||
|
||||
if ( !(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itObject)) )
|
||||
return nullptr;
|
||||
|
||||
PrusaDataViewBitmapText data;
|
||||
data << value;
|
||||
m_bmp_from_editing_item = data.GetBitmap();
|
||||
m_was_unusable_symbol = false;
|
||||
|
||||
wxPoint position = labelRect.GetPosition();
|
||||
if (m_bmp_from_editing_item.IsOk()) {
|
||||
const int bmp_width = m_bmp_from_editing_item.GetWidth();
|
||||
position.x += bmp_width;
|
||||
labelRect.SetWidth(labelRect.GetWidth() - bmp_width);
|
||||
}
|
||||
|
||||
wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(),
|
||||
position, labelRect.GetSize(), wxTE_PROCESS_ENTER);
|
||||
text_editor->SetInsertionPointEnd();
|
||||
|
||||
return text_editor;
|
||||
}
|
||||
|
||||
bool PrusaBitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value)
|
||||
{
|
||||
wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl);
|
||||
if (!text_editor || text_editor->GetValue().IsEmpty())
|
||||
return false;
|
||||
|
||||
std::string chosen_name = Slic3r::normalize_utf8_nfc(text_editor->GetValue().ToUTF8());
|
||||
const char* unusable_symbols = "<>:/\\|?*\"";
|
||||
for (size_t i = 0; i < std::strlen(unusable_symbols); i++) {
|
||||
if (chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) {
|
||||
m_was_unusable_symbol = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
value << PrusaDataViewBitmapText(text_editor->GetValue(), m_bmp_from_editing_item);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// PrusaDoubleSlider
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -222,7 +222,8 @@ class PrusaObjectDataViewModelNode
|
||||
size_t m_volumes_cnt = 0;
|
||||
std::vector< std::string > m_opt_categories;
|
||||
public:
|
||||
PrusaObjectDataViewModelNode(const wxString &name) {
|
||||
PrusaObjectDataViewModelNode(const wxString &name,
|
||||
const wxString& extruder) {
|
||||
m_parent = NULL;
|
||||
m_name = name;
|
||||
m_type = itObject;
|
||||
@ -232,6 +233,7 @@ public:
|
||||
// it will be produce "segmentation fault"
|
||||
m_container = true;
|
||||
#endif //__WXGTK__
|
||||
m_extruder = extruder;
|
||||
set_object_action_icon();
|
||||
}
|
||||
|
||||
@ -438,7 +440,7 @@ public:
|
||||
PrusaObjectDataViewModel();
|
||||
~PrusaObjectDataViewModel();
|
||||
|
||||
wxDataViewItem Add(const wxString &name);
|
||||
wxDataViewItem Add(const wxString &name, const int extruder);
|
||||
wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item,
|
||||
const wxString &name,
|
||||
const int volume_type,
|
||||
@ -455,8 +457,9 @@ public:
|
||||
wxDataViewItem GetItemById(int obj_idx);
|
||||
wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx);
|
||||
wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx);
|
||||
int GetIdByItem(const wxDataViewItem& item);
|
||||
int GetIdByItem(const wxDataViewItem& item) const;
|
||||
int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const;
|
||||
int GetObjectIdByItem(const wxDataViewItem& item) const;
|
||||
int GetVolumeIdByItem(const wxDataViewItem& item) const;
|
||||
int GetInstanceIdByItem(const wxDataViewItem& item) const;
|
||||
void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx);
|
||||
@ -519,7 +522,7 @@ public:
|
||||
class PrusaBitmapTextRenderer : public wxDataViewCustomRenderer
|
||||
{
|
||||
public:
|
||||
PrusaBitmapTextRenderer( wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT,
|
||||
PrusaBitmapTextRenderer( wxDataViewCellMode mode = wxDATAVIEW_CELL_EDITABLE,
|
||||
int align = wxDVR_DEFAULT_ALIGNMENT):
|
||||
wxDataViewCustomRenderer(wxT("PrusaDataViewBitmapText"), mode, align) {}
|
||||
|
||||
@ -529,10 +532,18 @@ public:
|
||||
virtual bool Render(wxRect cell, wxDC *dc, int state);
|
||||
virtual wxSize GetSize() const;
|
||||
|
||||
virtual bool HasEditorCtrl() const { return false; }
|
||||
bool HasEditorCtrl() const override { return true; }
|
||||
wxWindow* CreateEditorCtrl(wxWindow* parent,
|
||||
wxRect labelRect,
|
||||
const wxVariant& value) override;
|
||||
bool GetValueFromEditorCtrl( wxWindow* ctrl,
|
||||
wxVariant& value) override;
|
||||
bool WasCanceled() const { return m_was_unusable_symbol; }
|
||||
|
||||
private:
|
||||
PrusaDataViewBitmapText m_value;
|
||||
wxBitmap m_bmp_from_editing_item;
|
||||
bool m_was_unusable_symbol;
|
||||
};
|
||||
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include "Duet.hpp"
|
||||
#include "PrintHostSendDialog.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
@ -21,6 +20,7 @@
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/PrintHostDialogs.hpp" // XXX
|
||||
#include "Http.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
@ -62,10 +62,10 @@ bool Duet::send_gcode(const std::string &filename) const
|
||||
const auto errortitle = _(L("Error while uploading to the Duet"));
|
||||
fs::path filepath(filename);
|
||||
|
||||
PrintHostSendDialog send_dialog(filepath.filename(), true);
|
||||
PrintHostSendDialog send_dialog(filepath.filename());
|
||||
if (send_dialog.ShowModal() != wxID_OK) { return false; }
|
||||
|
||||
const bool print = send_dialog.print();
|
||||
const bool print = send_dialog.start_print();
|
||||
const auto upload_filepath = send_dialog.filename();
|
||||
const auto upload_filename = upload_filepath.filename();
|
||||
const auto upload_parent_path = upload_filepath.parent_path();
|
||||
@ -136,6 +136,11 @@ bool Duet::send_gcode(const std::string &filename) const
|
||||
return res;
|
||||
}
|
||||
|
||||
bool Duet::upload(PrintHostUpload upload_data) const
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
bool Duet::has_auto_discovery() const
|
||||
{
|
||||
return false;
|
||||
|
@ -24,6 +24,7 @@ public:
|
||||
wxString get_test_failed_msg (wxString &msg) const;
|
||||
// Send gcode file to duet, filename is expected to be in UTF-8
|
||||
bool send_gcode(const std::string &filename) const;
|
||||
bool upload(PrintHostUpload upload_data) const;
|
||||
bool has_auto_discovery() const;
|
||||
bool can_test() const;
|
||||
private:
|
||||
|
@ -1,14 +1,14 @@
|
||||
#include "OctoPrint.hpp"
|
||||
#include "PrintHostSendDialog.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/PrintHostDialogs.hpp" // XXX
|
||||
#include "Http.hpp"
|
||||
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
@ -66,10 +66,10 @@ bool OctoPrint::send_gcode(const std::string &filename) const
|
||||
const auto errortitle = _(L("Error while uploading to the OctoPrint server"));
|
||||
fs::path filepath(filename);
|
||||
|
||||
PrintHostSendDialog send_dialog(filepath.filename(), true);
|
||||
PrintHostSendDialog send_dialog(filepath.filename());
|
||||
if (send_dialog.ShowModal() != wxID_OK) { return false; }
|
||||
|
||||
const bool print = send_dialog.print();
|
||||
const bool print = send_dialog.start_print();
|
||||
const auto upload_filepath = send_dialog.filename();
|
||||
const auto upload_filename = upload_filepath.filename();
|
||||
const auto upload_parent_path = upload_filepath.parent_path();
|
||||
@ -101,7 +101,7 @@ bool OctoPrint::send_gcode(const std::string &filename) const
|
||||
auto http = Http::post(std::move(url));
|
||||
set_auth(http);
|
||||
http.form_add("print", print ? "true" : "false")
|
||||
.form_add("path", upload_parent_path.string())
|
||||
.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
|
||||
.form_add_file("file", filename, upload_filename.string())
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body;
|
||||
@ -129,6 +129,11 @@ bool OctoPrint::send_gcode(const std::string &filename) const
|
||||
return res;
|
||||
}
|
||||
|
||||
bool OctoPrint::upload(PrintHostUpload upload_data) const
|
||||
{
|
||||
throw "unimplemented";
|
||||
}
|
||||
|
||||
bool OctoPrint::has_auto_discovery() const
|
||||
{
|
||||
return true;
|
||||
|
@ -24,6 +24,7 @@ public:
|
||||
wxString get_test_failed_msg (wxString &msg) const;
|
||||
// Send gcode file to octoprint, filename is expected to be in UTF-8
|
||||
bool send_gcode(const std::string &filename) const;
|
||||
bool upload(PrintHostUpload upload_data) const;
|
||||
bool has_auto_discovery() const;
|
||||
bool can_test() const;
|
||||
private:
|
||||
|
@ -1,7 +1,15 @@
|
||||
#include "OctoPrint.hpp"
|
||||
#include "Duet.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/Channel.hpp"
|
||||
|
||||
using boost::optional;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -10,13 +18,42 @@ PrintHost::~PrintHost() {}
|
||||
|
||||
PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
|
||||
{
|
||||
PrintHostType kind = config->option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
if (kind == htOctoPrint) {
|
||||
return new OctoPrint(config);
|
||||
} else if (kind == htDuet) {
|
||||
return new Duet(config);
|
||||
}
|
||||
return nullptr;
|
||||
PrintHostType kind = config->option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
if (kind == htOctoPrint) {
|
||||
return new OctoPrint(config);
|
||||
} else if (kind == htDuet) {
|
||||
return new Duet(config);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
struct PrintHostJobQueue::priv
|
||||
{
|
||||
std::vector<PrintHostJob> jobs;
|
||||
Channel<unsigned> channel;
|
||||
|
||||
std::thread bg_thread;
|
||||
optional<PrintHostJob> bg_job;
|
||||
};
|
||||
|
||||
PrintHostJobQueue::PrintHostJobQueue()
|
||||
: p(new priv())
|
||||
{
|
||||
std::shared_ptr<priv> p2 = p;
|
||||
p->bg_thread = std::thread([p2]() {
|
||||
// Wait for commands on the channel:
|
||||
auto cmd = p2->channel.pop();
|
||||
// TODO
|
||||
});
|
||||
}
|
||||
|
||||
PrintHostJobQueue::~PrintHostJobQueue()
|
||||
{
|
||||
// TODO: stop the thread
|
||||
// if (p && p->bg_thread.joinable()) {
|
||||
// p->bg_thread.detach();
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
@ -3,31 +3,87 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
class DynamicPrintConfig;
|
||||
|
||||
|
||||
struct PrintHostUpload
|
||||
{
|
||||
boost::filesystem::path source_path;
|
||||
boost::filesystem::path upload_path;
|
||||
bool start_print = false;
|
||||
};
|
||||
|
||||
|
||||
class PrintHost
|
||||
{
|
||||
public:
|
||||
virtual ~PrintHost();
|
||||
virtual ~PrintHost();
|
||||
|
||||
virtual bool test(wxString &curl_msg) const = 0;
|
||||
virtual wxString get_test_ok_msg () const = 0;
|
||||
virtual wxString get_test_failed_msg (wxString &msg) const = 0;
|
||||
// Send gcode file to print host, filename is expected to be in UTF-8
|
||||
virtual bool send_gcode(const std::string &filename) const = 0;
|
||||
virtual bool has_auto_discovery() const = 0;
|
||||
virtual bool can_test() const = 0;
|
||||
virtual bool test(wxString &curl_msg) const = 0;
|
||||
virtual wxString get_test_ok_msg () const = 0;
|
||||
virtual wxString get_test_failed_msg (wxString &msg) const = 0;
|
||||
// Send gcode file to print host, filename is expected to be in UTF-8
|
||||
virtual bool send_gcode(const std::string &filename) const = 0; // XXX: remove in favor of upload()
|
||||
virtual bool upload(PrintHostUpload upload_data) const = 0;
|
||||
virtual bool has_auto_discovery() const = 0;
|
||||
virtual bool can_test() const = 0;
|
||||
|
||||
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
||||
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
||||
};
|
||||
|
||||
|
||||
struct PrintHostJob
|
||||
{
|
||||
PrintHostUpload upload_data;
|
||||
std::unique_ptr<PrintHost> printhost;
|
||||
|
||||
PrintHostJob() {}
|
||||
PrintHostJob(const PrintHostJob&) = delete;
|
||||
PrintHostJob(PrintHostJob &&other)
|
||||
: upload_data(std::move(other.upload_data))
|
||||
, printhost(std::move(other.printhost))
|
||||
{}
|
||||
|
||||
PrintHostJob(DynamicPrintConfig *config)
|
||||
: printhost(PrintHost::get_print_host(config))
|
||||
{}
|
||||
|
||||
PrintHostJob& operator=(const PrintHostJob&) = delete;
|
||||
PrintHostJob& operator=(PrintHostJob &&other)
|
||||
{
|
||||
upload_data = std::move(other.upload_data);
|
||||
printhost = std::move(other.printhost);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool empty() const { return !printhost; }
|
||||
operator bool() const { return !!printhost; }
|
||||
};
|
||||
|
||||
|
||||
class PrintHostJobQueue
|
||||
{
|
||||
public:
|
||||
PrintHostJobQueue();
|
||||
PrintHostJobQueue(const PrintHostJobQueue &) = delete;
|
||||
PrintHostJobQueue(PrintHostJobQueue &&other) = delete;
|
||||
~PrintHostJobQueue();
|
||||
|
||||
PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete;
|
||||
PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete;
|
||||
|
||||
private:
|
||||
struct priv;
|
||||
std::shared_ptr<priv> p;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,52 +0,0 @@
|
||||
#include "PrintHostSendDialog.hpp"
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print) :
|
||||
MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE),
|
||||
txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())),
|
||||
box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))),
|
||||
can_start_print(can_start_print)
|
||||
{
|
||||
auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
|
||||
label_dir_hint->Wrap(CONTENT_WIDTH);
|
||||
|
||||
content_sizer->Add(txt_filename, 0, wxEXPAND);
|
||||
content_sizer->Add(label_dir_hint);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
|
||||
|
||||
btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
|
||||
|
||||
txt_filename->SetFocus();
|
||||
wxString stem(path.stem().wstring());
|
||||
txt_filename->SetSelection(0, stem.Length());
|
||||
|
||||
box_print->Enable(can_start_print);
|
||||
|
||||
Fit();
|
||||
}
|
||||
|
||||
fs::path PrintHostSendDialog::filename() const
|
||||
{
|
||||
return fs::path(txt_filename->GetValue().wx_str());
|
||||
}
|
||||
|
||||
bool PrintHostSendDialog::print() const
|
||||
{
|
||||
return box_print->GetValue(); }
|
||||
}
|
@ -79,7 +79,9 @@ my $cube = {
|
||||
my $m = Slic3r::TriangleMesh->new;
|
||||
$m->ReadFromPerl($cube->{vertices}, $cube->{facets});
|
||||
$m->repair;
|
||||
my @z = (0,2,4,8,6,8,10,12,14,16,18,20);
|
||||
# The slice at zero height does not belong to the mesh, the slicing considers the vertical structures to be
|
||||
# open intervals at the bottom end, closed at the top end.
|
||||
my @z = (0.0001,2,4,8,6,8,10,12,14,16,18,20);
|
||||
my $result = $m->slice(\@z);
|
||||
my $SCALING_FACTOR = 0.000001;
|
||||
for my $i (0..$#z) {
|
||||
@ -105,7 +107,9 @@ my $cube = {
|
||||
# this second test also checks that performing a second slice on a mesh after
|
||||
# a transformation works properly (shared_vertices is correctly invalidated);
|
||||
# at Z = -10 we have a bottom horizontal surface
|
||||
my $slices = $m->slice([ -5, -10 ]);
|
||||
# (The slice at zero height does not belong to the mesh, the slicing considers the vertical structures to be
|
||||
# open intervals at the bottom end, closed at the top end, so the Z = -10 is shifted a bit up to get a valid slice).
|
||||
my $slices = $m->slice([ -5, -10+0.00001 ]);
|
||||
is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a bottom tangent plane includes its area';
|
||||
}
|
||||
}
|
||||
|
@ -211,11 +211,6 @@ ModelMaterial::attributes()
|
||||
void set_layer_height_ranges(t_layer_height_ranges ranges)
|
||||
%code%{ THIS->layer_height_ranges = ranges; %};
|
||||
|
||||
std::vector<double> layer_height_profile()
|
||||
%code%{ RETVAL = THIS->layer_height_profile_valid ? THIS->layer_height_profile : std::vector<double>(); %};
|
||||
void set_layer_height_profile(std::vector<double> profile)
|
||||
%code%{ THIS->layer_height_profile = profile; THIS->layer_height_profile_valid = true; %};
|
||||
|
||||
Ref<Vec3d> origin_translation()
|
||||
%code%{ RETVAL = &THIS->origin_translation; %};
|
||||
void set_origin_translation(Vec3d* point)
|
||||
|
@ -49,8 +49,6 @@ _constant()
|
||||
Ref<StaticPrintConfig> config()
|
||||
%code%{ RETVAL = &THIS->config(); %};
|
||||
Points copies();
|
||||
t_layer_height_ranges layer_height_ranges()
|
||||
%code%{ RETVAL = THIS->layer_height_ranges; %};
|
||||
std::vector<double> layer_height_profile()
|
||||
%code%{ RETVAL = THIS->layer_height_profile; %};
|
||||
Clone<BoundingBox> bounding_box();
|
||||
@ -58,9 +56,6 @@ _constant()
|
||||
Points _shifted_copies()
|
||||
%code%{ RETVAL = THIS->copies(); %};
|
||||
|
||||
void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges)
|
||||
%code%{ THIS->layer_height_ranges = layer_height_ranges; %};
|
||||
|
||||
size_t layer_count();
|
||||
Ref<Layer> get_layer(int idx);
|
||||
|
||||
|