Merge branch 'master' into lm_sla_supports_auto

This commit is contained in:
Lukas Matena 2018-12-14 09:19:18 +01:00
commit 054c1ca3ae
93 changed files with 7430 additions and 1048 deletions

View File

@ -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)

View File

@ -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
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
resources/icons/move_on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

View 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
View 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_

View File

@ -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) {

View File

@ -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);

View 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

View File

@ -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);

View File

@ -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) {

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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());
}

View File

@ -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;
}

View File

@ -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

View File

@ -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]; }

View File

@ -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();

View File

@ -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();

View File

@ -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");

View File

@ -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);
}
};

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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));
}
}
}

View File

@ -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

View 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 */

View File

@ -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);

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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("") {}
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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.

View File

@ -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

View File

@ -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 */

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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;
}

View File

@ -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());

View File

@ -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;

View File

@ -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.

View File

@ -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();

View File

@ -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)

View File

@ -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();

View File

@ -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
{

View File

@ -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);
}

View File

@ -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
{}

View File

@ -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)

View File

@ -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

View File

@ -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);
};

View File

@ -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

View File

@ -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);

View File

@ -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)

View File

@ -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:

View File

@ -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();

View File

@ -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();

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View 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(); }
}

View File

@ -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

View File

@ -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, "" };

View File

@ -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
// ----------------------------------------------------------------------------

View File

@ -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;
};

View File

@ -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;

View File

@ -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:

View File

@ -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;

View File

@ -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:

View File

@ -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();
// }
}

View File

@ -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;
};
}

View File

@ -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(); }
}

View File

@ -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';
}
}

View File

@ -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)

View File

@ -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);