mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-28 19:21:20 -06:00
Merge remote-tracking branch 'origin/dev_native' into tm_sla_supports
This commit is contained in:
commit
97b3d94760
55 changed files with 1903 additions and 808 deletions
|
|
@ -439,14 +439,16 @@ void ConfigBase::load_from_gcode_file(const std::string &file)
|
|||
ifs.read(data.data(), data_length);
|
||||
ifs.close();
|
||||
|
||||
load_from_gcode_string(data.data());
|
||||
size_t key_value_pairs = load_from_gcode_string(data.data());
|
||||
if (key_value_pairs < 80)
|
||||
throw std::runtime_error((boost::format("Suspiciously low number of configuration values extracted from %1: %2") % file % key_value_pairs).str());
|
||||
}
|
||||
|
||||
// Load the config keys from the given string.
|
||||
void ConfigBase::load_from_gcode_string(const char* str)
|
||||
size_t ConfigBase::load_from_gcode_string(const char* str)
|
||||
{
|
||||
if (str == nullptr)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
// Walk line by line in reverse until a non-configuration key appears.
|
||||
char *data_start = const_cast<char*>(str);
|
||||
|
|
@ -497,11 +499,8 @@ void ConfigBase::load_from_gcode_string(const char* str)
|
|||
}
|
||||
end = start;
|
||||
}
|
||||
if (num_key_value_pairs < 90) {
|
||||
char msg[80];
|
||||
sprintf(msg, "Suspiciously low number of configuration values extracted: %d", num_key_value_pairs);
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
return num_key_value_pairs;
|
||||
}
|
||||
|
||||
void ConfigBase::save(const std::string &file) const
|
||||
|
|
|
|||
|
|
@ -1119,7 +1119,8 @@ public:
|
|||
void load(const std::string &file);
|
||||
void load_from_ini(const std::string &file);
|
||||
void load_from_gcode_file(const std::string &file);
|
||||
void load_from_gcode_string(const char* str);
|
||||
// Returns number of key/value pairs extracted.
|
||||
size_t load_from_gcode_string(const char* str);
|
||||
void load(const boost::property_tree::ptree &tree);
|
||||
void save(const std::string &file) const;
|
||||
|
||||
|
|
@ -1237,6 +1238,7 @@ public:
|
|||
ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override;
|
||||
// Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store.
|
||||
t_config_option_keys keys() const override;
|
||||
bool empty() const { return options.empty(); }
|
||||
|
||||
// Set a value for an opt_key. Returns true if the value did not exist yet.
|
||||
// This DynamicConfig will take ownership of opt.
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
|
|||
// Only concentric fills are not sorted.
|
||||
eec->no_sort = f->no_sort();
|
||||
extrusion_entities_append_paths(
|
||||
eec->entities, STDMOVE(polylines),
|
||||
eec->entities, std::move(polylines),
|
||||
is_bridge ?
|
||||
erBridgeInfill :
|
||||
(surface.is_solid() ?
|
||||
|
|
|
|||
|
|
@ -813,7 +813,7 @@ namespace Slic3r {
|
|||
std::vector<Vec3f> sla_support_points;
|
||||
|
||||
for (unsigned int i=0; i<object_data_points.size(); i+=3)
|
||||
sla_support_points.push_back(Vec3f(std::atof(object_data_points[i+0].c_str()), std::atof(object_data_points[i+1].c_str()), std::atof(object_data_points[i+2].c_str())));
|
||||
sla_support_points.push_back(Vec3d(std::atof(object_data_points[i+0].c_str()), std::atof(object_data_points[i+1].c_str()), std::atof(object_data_points[i+2].c_str())).cast<float>());
|
||||
|
||||
if (!sla_support_points.empty())
|
||||
m_sla_support_points.insert(IdToSlaSupportPointsMap::value_type(object_id, sla_support_points));
|
||||
|
|
@ -1608,10 +1608,10 @@ namespace Slic3r {
|
|||
IdToObjectDataMap m_objects_data;
|
||||
|
||||
public:
|
||||
bool save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config);
|
||||
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config);
|
||||
|
||||
private:
|
||||
bool _save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config);
|
||||
bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config);
|
||||
bool _add_content_types_file_to_archive(mz_zip_archive& archive);
|
||||
bool _add_relationships_file_to_archive(mz_zip_archive& archive);
|
||||
bool _add_model_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
|
|
@ -1620,17 +1620,17 @@ namespace Slic3r {
|
|||
bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items);
|
||||
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print);
|
||||
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config);
|
||||
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model);
|
||||
};
|
||||
|
||||
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config)
|
||||
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config)
|
||||
{
|
||||
clear_errors();
|
||||
return _save_model_to_file(filename, model, print, export_print_config);
|
||||
return _save_model_to_file(filename, model, config);
|
||||
}
|
||||
|
||||
bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config)
|
||||
bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config)
|
||||
{
|
||||
mz_zip_archive archive;
|
||||
mz_zip_zero_struct(&archive);
|
||||
|
|
@ -1685,9 +1685,9 @@ namespace Slic3r {
|
|||
}
|
||||
|
||||
// adds slic3r print config file
|
||||
if (export_print_config)
|
||||
if (config != nullptr)
|
||||
{
|
||||
if (!_add_print_config_file_to_archive(archive, print))
|
||||
if (!_add_print_config_file_to_archive(archive, *config))
|
||||
{
|
||||
mz_zip_writer_end(&archive);
|
||||
boost::filesystem::remove(filename);
|
||||
|
|
@ -2017,13 +2017,15 @@ namespace Slic3r {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print)
|
||||
bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config)
|
||||
{
|
||||
char buffer[1024];
|
||||
sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str());
|
||||
std::string out = buffer;
|
||||
|
||||
GCode::append_full_config(print, out);
|
||||
for (const std::string &key : config.keys())
|
||||
if (key != "compatible_printers")
|
||||
out += "; " + key + " = " + config.serialize(key) + "\n";
|
||||
|
||||
if (!out.empty())
|
||||
{
|
||||
|
|
@ -2123,13 +2125,13 @@ namespace Slic3r {
|
|||
return res;
|
||||
}
|
||||
|
||||
bool store_3mf(const char* path, Model* model, Print* print, bool export_print_config)
|
||||
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config)
|
||||
{
|
||||
if ((path == nullptr) || (model == nullptr) || (print == nullptr))
|
||||
if ((path == nullptr) || (model == nullptr))
|
||||
return false;
|
||||
|
||||
_3MF_Exporter exporter;
|
||||
bool res = exporter.save_model_to_file(path, *model, *print, export_print_config);
|
||||
bool res = exporter.save_model_to_file(path, *model, config);
|
||||
|
||||
if (!res)
|
||||
exporter.log_errors();
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
class Print;
|
||||
class DynamicPrintConfig;
|
||||
|
||||
// Load the content of a 3mf file into the given model and preset bundle.
|
||||
extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model);
|
||||
|
||||
// Save the given model and the config data contained in the given Print into a 3mf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
extern bool store_3mf(const char* path, Model* model, Print* print, bool export_print_config);
|
||||
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config);
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
|
|
|
|||
|
|
@ -834,9 +834,9 @@ bool load_amf(const char *path, DynamicPrintConfig *config, Model *model)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool store_amf(const char *path, Model *model, Print* print, bool export_print_config)
|
||||
bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
|
||||
{
|
||||
if ((path == nullptr) || (model == nullptr) || (print == nullptr))
|
||||
if ((path == nullptr) || (model == nullptr))
|
||||
return false;
|
||||
|
||||
// forces ".zip.amf" extension
|
||||
|
|
@ -857,11 +857,13 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
|
|||
stream << "<metadata type=\"cad\">Slic3r " << SLIC3R_VERSION << "</metadata>\n";
|
||||
stream << "<metadata type=\"" << SLIC3RPE_AMF_VERSION << "\">" << VERSION_AMF << "</metadata>\n";
|
||||
|
||||
if (export_print_config)
|
||||
if (config != nullptr)
|
||||
{
|
||||
std::string config = "\n";
|
||||
GCode::append_full_config(*print, config);
|
||||
stream << "<metadata type=\"" << SLIC3R_CONFIG_TYPE << "\">" << xml_escape(config) << "</metadata>\n";
|
||||
std::string str_config = "\n";
|
||||
for (const std::string &key : config->keys())
|
||||
if (key != "compatible_printers")
|
||||
str_config += "; " + key + " = " + config->serialize(key) + "\n";
|
||||
stream << "<metadata type=\"" << SLIC3R_CONFIG_TYPE << "\">" << xml_escape(str_config) << "</metadata>\n";
|
||||
}
|
||||
|
||||
for (const auto &material : model->materials) {
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
class Print;
|
||||
class DynamicPrintConfig;
|
||||
|
||||
// Load the content of an amf file into the given model and configuration.
|
||||
extern bool load_amf(const char *path, DynamicPrintConfig *config, Model *model);
|
||||
|
||||
// Save the given model and the config data contained in the given Print into an amf file.
|
||||
// Save the given model and the config data into an amf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
extern bool store_amf(const char *path, Model *model, Print* print, bool export_print_config);
|
||||
extern bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config);
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
|
|
|
|||
|
|
@ -445,7 +445,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
|
|||
boost::nowide::remove(path_tmp.c_str());
|
||||
throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
|
||||
}
|
||||
} catch (std::exception &ex) {
|
||||
} catch (std::exception & /* ex */) {
|
||||
// Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown.
|
||||
// Close and remove the file.
|
||||
fclose(file);
|
||||
|
|
@ -601,15 +601,18 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
this->apply_print_config(print.config());
|
||||
this->set_extruders(print.extruders());
|
||||
|
||||
// Initialize colorprint.
|
||||
m_colorprint_heights = cast<float>(print.config().colorprint_heights.values);
|
||||
|
||||
// Initialize autospeed.
|
||||
{
|
||||
// get the minimum cross-section used in the print
|
||||
std::vector<double> mm3_per_mm;
|
||||
for (auto object : printable_objects) {
|
||||
for (size_t region_id = 0; region_id < print.regions().size(); ++region_id) {
|
||||
auto region = print.regions()[region_id];
|
||||
for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) {
|
||||
const PrintRegion* region = print.regions()[region_id];
|
||||
for (auto layer : object->layers()) {
|
||||
auto layerm = layer->regions()[region_id];
|
||||
const LayerRegion* layerm = layer->regions()[region_id];
|
||||
if (region->config().get_abs_value("perimeter_speed" ) == 0 ||
|
||||
region->config().get_abs_value("small_perimeter_speed" ) == 0 ||
|
||||
region->config().get_abs_value("external_perimeter_speed" ) == 0 ||
|
||||
|
|
@ -673,8 +676,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
|
|||
const PrintObject *first_object = printable_objects.front();
|
||||
const double layer_height = first_object->config().layer_height.value;
|
||||
const double first_layer_height = first_object->config().first_layer_height.get_abs_value(layer_height);
|
||||
for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) {
|
||||
auto region = print.regions()[region_id];
|
||||
for (const PrintRegion* region : print.regions()) {
|
||||
_write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width);
|
||||
_write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, false, -1., *first_object).width);
|
||||
_write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(frInfill, layer_height, false, false, -1., *first_object).width);
|
||||
|
|
@ -1319,6 +1321,18 @@ void GCode::process_layer(
|
|||
m_second_layer_things_done = true;
|
||||
}
|
||||
|
||||
// Let's issue a filament change command if requested at this layer.
|
||||
// In case there are more toolchange requests that weren't done yet and should happen simultaneously, erase them all.
|
||||
// (Layers can be close to each other, model could have been resliced with bigger layer height, ...).
|
||||
bool colorprint_change = false;
|
||||
while (!m_colorprint_heights.empty() && m_colorprint_heights.front()-EPSILON < layer.print_z) {
|
||||
m_colorprint_heights.erase(m_colorprint_heights.begin());
|
||||
colorprint_change = true;
|
||||
}
|
||||
if (colorprint_change)
|
||||
gcode += "M600\n";
|
||||
|
||||
|
||||
// Extrude skirt at the print_z of the raft layers and normal object layers
|
||||
// not at the print_z of the interlaced support material layers.
|
||||
bool extrude_skirt =
|
||||
|
|
@ -1442,7 +1456,7 @@ void GCode::process_layer(
|
|||
};
|
||||
|
||||
for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) {
|
||||
const LayerRegion *layerm = layer.regions()[region_id];
|
||||
const LayerRegion *layerm = (region_id < layer.regions().size()) ? layer.regions()[region_id] : nullptr;
|
||||
if (layerm == nullptr)
|
||||
continue;
|
||||
const PrintRegion ®ion = *print.regions()[region_id];
|
||||
|
|
|
|||
|
|
@ -317,6 +317,9 @@ protected:
|
|||
bool m_second_layer_things_done;
|
||||
// Index of a last object copy extruded.
|
||||
std::pair<const PrintObject*, Point> m_last_obj_copy;
|
||||
// Layer heights for colorprint - updated before the export and erased during the process
|
||||
// so no toolchange occurs twice.
|
||||
std::vector<float> m_colorprint_heights;
|
||||
|
||||
// Time estimators
|
||||
GCodeTimeEstimator m_normal_time_estimator;
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object)
|
|||
for (auto layer : object.layers()) {
|
||||
LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
|
||||
// What extruders are required to print this object layer?
|
||||
for (size_t region_id = 0; region_id < object.print()->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < object.region_volumes.size(); ++ region_id) {
|
||||
const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr;
|
||||
if (layerm == nullptr)
|
||||
continue;
|
||||
|
|
@ -479,7 +479,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
|||
|
||||
for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
|
||||
|
||||
for (size_t region_id = 0; region_id < object->print()->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) {
|
||||
const auto& region = *object->print()->regions()[region_id];
|
||||
|
||||
if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
|
||||
|
|
@ -557,7 +557,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
|||
unsigned int num_of_copies = object->copies().size();
|
||||
|
||||
for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
|
||||
for (size_t region_id = 0; region_id < object->print()->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) {
|
||||
const auto& region = *object->print()->regions()[region_id];
|
||||
|
||||
if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
|
||||
|
|
|
|||
|
|
@ -32,9 +32,8 @@ void Layer::make_slices()
|
|||
slices = m_regions.front()->slices;
|
||||
} else {
|
||||
Polygons slices_p;
|
||||
FOREACH_LAYERREGION(this, layerm) {
|
||||
polygons_append(slices_p, to_polygons((*layerm)->slices));
|
||||
}
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
polygons_append(slices_p, to_polygons(layerm->slices));
|
||||
slices = union_ex(slices_p);
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +52,7 @@ void Layer::make_slices()
|
|||
|
||||
// populate slices vector
|
||||
for (size_t i : order)
|
||||
this->slices.expolygons.push_back(STDMOVE(slices[i]));
|
||||
this->slices.expolygons.push_back(std::move(slices[i]));
|
||||
}
|
||||
|
||||
void Layer::merge_slices()
|
||||
|
|
@ -63,10 +62,9 @@ void Layer::merge_slices()
|
|||
// but use the non-split islands of a layer. For a single region print, these shall be equal.
|
||||
m_regions.front()->slices.set(this->slices.expolygons, stInternal);
|
||||
} else {
|
||||
FOREACH_LAYERREGION(this, layerm) {
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
// without safety offset, artifacts are generated (GH #2494)
|
||||
(*layerm)->slices.set(union_ex(to_polygons(STDMOVE((*layerm)->slices.surfaces)), true), stInternal);
|
||||
}
|
||||
layerm->slices.set(union_ex(to_polygons(std::move(layerm->slices.surfaces)), true), stInternal);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +78,7 @@ void Layer::make_perimeters()
|
|||
// keep track of regions whose perimeters we have already generated
|
||||
std::set<size_t> done;
|
||||
|
||||
FOREACH_LAYERREGION(this, layerm) {
|
||||
for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) {
|
||||
size_t region_id = layerm - m_regions.begin();
|
||||
if (done.find(region_id) != done.end()) continue;
|
||||
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id;
|
||||
|
|
@ -137,7 +135,7 @@ void Layer::make_perimeters()
|
|||
// Separate the fill surfaces.
|
||||
ExPolygons expp = intersection_ex(to_polygons(fill_surfaces), (*l)->slices);
|
||||
(*l)->fill_expolygons = expp;
|
||||
(*l)->fill_surfaces.set(STDMOVE(expp), fill_surfaces.surfaces.front());
|
||||
(*l)->fill_surfaces.set(std::move(expp), fill_surfaces.surfaces.front());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
|
|||
// in place. However we're now only using its boundaries (which are invariant)
|
||||
// so we're safe. This guarantees idempotence of prepare_infill() also in case
|
||||
// that combine_infill() turns some fill_surface into VOID surfaces.
|
||||
// Polygons fill_boundaries = to_polygons(STDMOVE(this->fill_surfaces));
|
||||
// Polygons fill_boundaries = to_polygons(std::move(this->fill_surfaces));
|
||||
Polygons fill_boundaries = to_polygons(this->fill_expolygons);
|
||||
// Collect polygons per surface type.
|
||||
std::vector<Polygons> polygons_by_surface;
|
||||
|
|
@ -133,9 +133,9 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
|||
if (internal_surface)
|
||||
// Make a copy as the following line uses the move semantics.
|
||||
internal.push_back(surface);
|
||||
polygons_append(fill_boundaries, STDMOVE(surface.expolygon));
|
||||
polygons_append(fill_boundaries, std::move(surface.expolygon));
|
||||
} else if (internal_surface)
|
||||
internal.push_back(STDMOVE(surface));
|
||||
internal.push_back(std::move(surface));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -192,7 +192,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
|||
polys = intersection(polys, to_polygons(fill_boundaries_ex[idx_island]));
|
||||
}
|
||||
bridge_bboxes.push_back(get_extents(polys));
|
||||
bridges_grown.push_back(STDMOVE(polys));
|
||||
bridges_grown.push_back(std::move(polys));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -243,7 +243,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
|||
for (size_t i = 0; i < bridges.size(); ++ i) {
|
||||
if (bridge_group[i] != group_id)
|
||||
continue;
|
||||
initial.push_back(STDMOVE(bridges[i].expolygon));
|
||||
initial.push_back(std::move(bridges[i].expolygon));
|
||||
polygons_append(grown, bridges_grown[i]);
|
||||
}
|
||||
// detect bridge direction before merging grown surfaces otherwise adjacent bridges
|
||||
|
|
@ -269,7 +269,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
|||
surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]);
|
||||
}
|
||||
|
||||
fill_boundaries = STDMOVE(to_polygons(fill_boundaries_ex));
|
||||
fill_boundaries = std::move(to_polygons(fill_boundaries_ex));
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
|
||||
}
|
||||
|
||||
|
|
@ -284,7 +284,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
|||
Surfaces new_surfaces;
|
||||
{
|
||||
// Merge top and bottom in a single collection.
|
||||
surfaces_append(top, STDMOVE(bottom));
|
||||
surfaces_append(top, std::move(bottom));
|
||||
// Intersect the grown surfaces with the actual fill boundaries.
|
||||
Polygons bottom_polygons = to_polygons(bottom);
|
||||
for (size_t i = 0; i < top.size(); ++ i) {
|
||||
|
|
@ -292,11 +292,11 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
|||
if (s1.empty())
|
||||
continue;
|
||||
Polygons polys;
|
||||
polygons_append(polys, STDMOVE(s1));
|
||||
polygons_append(polys, std::move(s1));
|
||||
for (size_t j = i + 1; j < top.size(); ++ j) {
|
||||
Surface &s2 = top[j];
|
||||
if (! s2.empty() && surfaces_could_merge(s1, s2)) {
|
||||
polygons_append(polys, STDMOVE(s2));
|
||||
polygons_append(polys, std::move(s2));
|
||||
s2.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -306,7 +306,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
|||
surfaces_append(
|
||||
new_surfaces,
|
||||
// Don't use a safety offset as fill_boundaries were already united using the safety offset.
|
||||
STDMOVE(intersection_ex(polys, fill_boundaries, false)),
|
||||
std::move(intersection_ex(polys, fill_boundaries, false)),
|
||||
s1);
|
||||
}
|
||||
}
|
||||
|
|
@ -318,20 +318,20 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer)
|
|||
if (s1.empty())
|
||||
continue;
|
||||
Polygons polys;
|
||||
polygons_append(polys, STDMOVE(s1));
|
||||
polygons_append(polys, std::move(s1));
|
||||
for (size_t j = i + 1; j < internal.size(); ++ j) {
|
||||
Surface &s2 = internal[j];
|
||||
if (! s2.empty() && surfaces_could_merge(s1, s2)) {
|
||||
polygons_append(polys, STDMOVE(s2));
|
||||
polygons_append(polys, std::move(s2));
|
||||
s2.clear();
|
||||
}
|
||||
}
|
||||
ExPolygons new_expolys = diff_ex(polys, new_polygons);
|
||||
polygons_append(new_polygons, to_polygons(new_expolys));
|
||||
surfaces_append(new_surfaces, STDMOVE(new_expolys), s1);
|
||||
surfaces_append(new_surfaces, std::move(new_expolys), s1);
|
||||
}
|
||||
|
||||
this->fill_surfaces.surfaces = STDMOVE(new_surfaces);
|
||||
this->fill_surfaces.surfaces = std::move(new_surfaces);
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final");
|
||||
|
|
|
|||
|
|
@ -21,26 +21,58 @@ namespace Slic3r {
|
|||
|
||||
unsigned int Model::s_auto_extruder_id = 1;
|
||||
|
||||
ModelID ModelBase::s_last_id = 0;
|
||||
size_t ModelBase::s_last_id = 0;
|
||||
|
||||
Model::Model(const Model &rhs)
|
||||
Model& Model::assign_copy(const Model &rhs)
|
||||
{
|
||||
*this = rhs;
|
||||
this->copy_id(rhs);
|
||||
// copy materials
|
||||
this->clear_materials();
|
||||
this->materials = rhs.materials;
|
||||
for (std::pair<const t_model_material_id, ModelMaterial*> &m : this->materials) {
|
||||
// Copy including the ID and m_model.
|
||||
m.second = new ModelMaterial(*m.second);
|
||||
m.second->set_model(this);
|
||||
}
|
||||
// copy objects
|
||||
this->clear_objects();
|
||||
this->objects.reserve(rhs.objects.size());
|
||||
for (const ModelObject *model_object : rhs.objects) {
|
||||
// Copy including the ID, leave ID set to invalid (zero).
|
||||
auto mo = ModelObject::new_copy(*model_object);
|
||||
mo->set_model(this);
|
||||
this->objects.emplace_back(mo);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Model& Model::operator=(const Model &rhs)
|
||||
Model& Model::assign_copy(Model &&rhs)
|
||||
{
|
||||
m_id = rhs.m_id;
|
||||
// copy materials
|
||||
for (const auto &m : rhs.materials)
|
||||
this->add_material(m.first, *m.second);
|
||||
// copy objects
|
||||
this->objects.reserve(rhs.objects.size());
|
||||
for (const ModelObject *o : rhs.objects)
|
||||
this->add_object(*o, true);
|
||||
this->copy_id(rhs);
|
||||
// Move materials, adjust the parent pointer.
|
||||
this->clear_materials();
|
||||
this->materials = std::move(rhs.materials);
|
||||
for (std::pair<const t_model_material_id, ModelMaterial*> &m : this->materials)
|
||||
m.second->set_model(this);
|
||||
rhs.materials.clear();
|
||||
// Move objects, adjust the parent pointer.
|
||||
this->clear_objects();
|
||||
this->objects = std::move(rhs.objects);
|
||||
for (ModelObject *model_object : this->objects)
|
||||
model_object->set_model(this);
|
||||
rhs.objects.clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Model::assign_new_unique_ids_recursive()
|
||||
{
|
||||
this->set_new_unique_id();
|
||||
for (std::pair<const t_model_material_id, ModelMaterial*> &m : this->materials)
|
||||
m.second->assign_new_unique_ids_recursive();
|
||||
for (ModelObject *model_object : this->objects)
|
||||
model_object->assign_new_unique_ids_recursive();
|
||||
}
|
||||
|
||||
Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances)
|
||||
{
|
||||
Model model;
|
||||
|
|
@ -150,9 +182,10 @@ ModelObject* Model::add_object(const char *name, const char *path, TriangleMesh
|
|||
return new_object;
|
||||
}
|
||||
|
||||
ModelObject* Model::add_object(const ModelObject &other, bool copy_volumes)
|
||||
ModelObject* Model::add_object(const ModelObject &other)
|
||||
{
|
||||
ModelObject* new_object = new ModelObject(this, other, copy_volumes);
|
||||
ModelObject* new_object = ModelObject::new_clone(other);
|
||||
new_object->set_model(this);
|
||||
this->objects.push_back(new_object);
|
||||
return new_object;
|
||||
}
|
||||
|
|
@ -164,21 +197,36 @@ void Model::delete_object(size_t idx)
|
|||
this->objects.erase(i);
|
||||
}
|
||||
|
||||
void Model::delete_object(ModelObject* object)
|
||||
bool Model::delete_object(ModelObject* object)
|
||||
{
|
||||
if (object == nullptr)
|
||||
return;
|
||||
|
||||
for (ModelObjectPtrs::iterator it = objects.begin(); it != objects.end(); ++it)
|
||||
{
|
||||
ModelObject* obj = *it;
|
||||
if (obj == object)
|
||||
{
|
||||
delete obj;
|
||||
objects.erase(it);
|
||||
return;
|
||||
if (object != nullptr) {
|
||||
size_t idx = 0;
|
||||
for (ModelObject *model_object : objects) {
|
||||
if (model_object == object) {
|
||||
delete model_object;
|
||||
objects.erase(objects.begin() + idx);
|
||||
return true;
|
||||
}
|
||||
++ idx;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Model::delete_object(ModelID id)
|
||||
{
|
||||
if (id.id != 0) {
|
||||
size_t idx = 0;
|
||||
for (ModelObject *model_object : objects) {
|
||||
if (model_object->id() == id) {
|
||||
delete model_object;
|
||||
objects.erase(objects.begin() + idx);
|
||||
return true;
|
||||
}
|
||||
++ idx;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Model::clear_objects()
|
||||
|
|
@ -206,6 +254,7 @@ void Model::clear_materials()
|
|||
|
||||
ModelMaterial* Model::add_material(t_model_material_id material_id)
|
||||
{
|
||||
assert(! material_id.empty());
|
||||
ModelMaterial* material = this->get_material(material_id);
|
||||
if (material == nullptr)
|
||||
material = this->materials[material_id] = new ModelMaterial(this);
|
||||
|
|
@ -214,11 +263,13 @@ ModelMaterial* Model::add_material(t_model_material_id material_id)
|
|||
|
||||
ModelMaterial* Model::add_material(t_model_material_id material_id, const ModelMaterial &other)
|
||||
{
|
||||
assert(! material_id.empty());
|
||||
// delete existing material if any
|
||||
ModelMaterial* material = this->get_material(material_id);
|
||||
delete material;
|
||||
// set new material
|
||||
material = new ModelMaterial(this, other);
|
||||
material = new ModelMaterial(other);
|
||||
material->set_model(this);
|
||||
this->materials[material_id] = material;
|
||||
return material;
|
||||
}
|
||||
|
|
@ -421,6 +472,7 @@ void Model::convert_multipart_object(unsigned int max_extruders)
|
|||
ModelObject* object = new ModelObject(this);
|
||||
object->input_file = this->objects.front()->input_file;
|
||||
object->name = this->objects.front()->name;
|
||||
//FIXME copy the config etc?
|
||||
|
||||
reset_auto_extruder_id();
|
||||
|
||||
|
|
@ -483,53 +535,90 @@ void Model::reset_auto_extruder_id()
|
|||
s_auto_extruder_id = 1;
|
||||
}
|
||||
|
||||
ModelObject::ModelObject(Model *model, const ModelObject &rhs, bool copy_volumes) :
|
||||
m_model(model)
|
||||
{
|
||||
this->assign(&rhs, copy_volumes);
|
||||
}
|
||||
|
||||
ModelObject::~ModelObject()
|
||||
{
|
||||
this->clear_volumes();
|
||||
this->clear_instances();
|
||||
}
|
||||
|
||||
// Clone this ModelObject including its volumes and instances, keep the IDs of the copies equal to the original.
|
||||
// Called by Print::apply() to clone the Model / ModelObject hierarchy to the back end for background processing.
|
||||
ModelObject* ModelObject::clone(Model *parent)
|
||||
// maintains the m_model pointer
|
||||
ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
|
||||
{
|
||||
return new ModelObject(parent, *this, true);
|
||||
}
|
||||
this->copy_id(rhs);
|
||||
|
||||
ModelObject& ModelObject::assign(const ModelObject *rhs, bool copy_volumes)
|
||||
{
|
||||
m_id = rhs->m_id;
|
||||
name = rhs->name;
|
||||
input_file = rhs->input_file;
|
||||
config = rhs->config;
|
||||
sla_support_points = rhs->sla_support_points;
|
||||
layer_height_ranges = rhs->layer_height_ranges;
|
||||
layer_height_profile = rhs->layer_height_profile;
|
||||
layer_height_profile_valid = rhs->layer_height_profile_valid;
|
||||
origin_translation = rhs->origin_translation;
|
||||
m_bounding_box = rhs->m_bounding_box;
|
||||
m_bounding_box_valid = rhs->m_bounding_box_valid;
|
||||
this->name = rhs.name;
|
||||
this->input_file = rhs.input_file;
|
||||
this->config = rhs.config;
|
||||
this->sla_support_points = rhs.sla_support_points;
|
||||
this->layer_height_ranges = rhs.layer_height_ranges;
|
||||
this->layer_height_profile = rhs.layer_height_profile;
|
||||
this->layer_height_profile_valid = rhs.layer_height_profile_valid;
|
||||
this->origin_translation = rhs.origin_translation;
|
||||
m_bounding_box = rhs.m_bounding_box;
|
||||
m_bounding_box_valid = rhs.m_bounding_box_valid;
|
||||
|
||||
volumes.clear();
|
||||
instances.clear();
|
||||
if (copy_volumes) {
|
||||
this->volumes.reserve(rhs->volumes.size());
|
||||
for (ModelVolume *model_volume : rhs->volumes)
|
||||
this->add_volume(*model_volume);
|
||||
this->instances.reserve(rhs->instances.size());
|
||||
for (const ModelInstance *model_instance : rhs->instances)
|
||||
this->add_instance(*model_instance);
|
||||
this->clear_volumes();
|
||||
this->volumes.reserve(rhs.volumes.size());
|
||||
for (ModelVolume *model_volume : rhs.volumes) {
|
||||
this->volumes.emplace_back(new ModelVolume(*model_volume));
|
||||
this->volumes.back()->set_model_object(this);
|
||||
}
|
||||
this->clear_instances();
|
||||
this->instances.reserve(rhs.instances.size());
|
||||
for (const ModelInstance *model_instance : rhs.instances) {
|
||||
this->instances.emplace_back(new ModelInstance(*model_instance));
|
||||
this->instances.back()->set_model_object(this);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// maintains the m_model pointer
|
||||
ModelObject& ModelObject::assign_copy(ModelObject &&rhs)
|
||||
{
|
||||
this->copy_id(rhs);
|
||||
|
||||
this->name = std::move(rhs.name);
|
||||
this->input_file = std::move(rhs.input_file);
|
||||
this->config = std::move(rhs.config);
|
||||
this->sla_support_points = std::move(rhs.sla_support_points);
|
||||
this->layer_height_ranges = std::move(rhs.layer_height_ranges);
|
||||
this->layer_height_profile = std::move(rhs.layer_height_profile);
|
||||
this->layer_height_profile_valid = std::move(rhs.layer_height_profile_valid);
|
||||
this->origin_translation = std::move(rhs.origin_translation);
|
||||
m_bounding_box = std::move(rhs.m_bounding_box);
|
||||
m_bounding_box_valid = std::move(rhs.m_bounding_box_valid);
|
||||
|
||||
this->clear_volumes();
|
||||
this->volumes = std::move(rhs.volumes);
|
||||
rhs.volumes.clear();
|
||||
for (ModelVolume *model_volume : this->volumes)
|
||||
model_volume->set_model_object(this);
|
||||
this->clear_instances();
|
||||
this->instances = std::move(rhs.instances);
|
||||
rhs.instances.clear();
|
||||
for (ModelInstance *model_instance : this->instances)
|
||||
model_instance->set_model_object(this);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ModelObject::assign_new_unique_ids_recursive()
|
||||
{
|
||||
this->set_new_unique_id();
|
||||
for (ModelVolume *model_volume : this->volumes)
|
||||
model_volume->assign_new_unique_ids_recursive();
|
||||
for (ModelInstance *model_instance : this->instances)
|
||||
model_instance->assign_new_unique_ids_recursive();
|
||||
}
|
||||
|
||||
// Clone this ModelObject including its volumes and instances, keep the IDs of the copies equal to the original.
|
||||
// Called by Print::apply() to clone the Model / ModelObject hierarchy to the back end for background processing.
|
||||
//ModelObject* ModelObject::clone(Model *parent)
|
||||
//{
|
||||
// return new ModelObject(parent, *this, true);
|
||||
//}
|
||||
|
||||
ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh)
|
||||
{
|
||||
ModelVolume* v = new ModelVolume(this, mesh);
|
||||
|
|
@ -554,6 +643,14 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other)
|
|||
return v;
|
||||
}
|
||||
|
||||
ModelVolume* ModelObject::add_volume(const ModelVolume &other, TriangleMesh &&mesh)
|
||||
{
|
||||
ModelVolume* v = new ModelVolume(this, other, std::move(mesh));
|
||||
this->volumes.push_back(v);
|
||||
this->invalidate_bounding_box();
|
||||
return v;
|
||||
}
|
||||
|
||||
void ModelObject::delete_volume(size_t idx)
|
||||
{
|
||||
ModelVolumePtrs::iterator i = this->volumes.begin() + idx;
|
||||
|
|
@ -624,8 +721,16 @@ const BoundingBoxf3& ModelObject::bounding_box() const
|
|||
BoundingBoxf3 raw_bbox;
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->is_model_part())
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
{
|
||||
TriangleMesh m = v->mesh;
|
||||
m.transform(v->get_matrix());
|
||||
raw_bbox.merge(m.bounding_box());
|
||||
}
|
||||
#else
|
||||
// mesh.bounding_box() returns a cached value.
|
||||
raw_bbox.merge(v->mesh.bounding_box());
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
BoundingBoxf3 bb;
|
||||
for (const ModelInstance *i : this->instances)
|
||||
bb.merge(i->transform_bounding_box(raw_bbox));
|
||||
|
|
@ -656,7 +761,15 @@ TriangleMesh ModelObject::raw_mesh() const
|
|||
TriangleMesh mesh;
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->is_model_part())
|
||||
mesh.merge(v->mesh);
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
{
|
||||
TriangleMesh vol_mesh(v->mesh);
|
||||
vol_mesh.transform(v->get_matrix());
|
||||
mesh.merge(vol_mesh);
|
||||
}
|
||||
#else
|
||||
mesh.merge(v->mesh);
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
|
@ -699,12 +812,14 @@ void ModelObject::center_around_origin()
|
|||
this->translate(shift);
|
||||
this->origin_translation += shift;
|
||||
|
||||
#if !ENABLE_MODELVOLUME_TRANSFORM
|
||||
if (!this->instances.empty()) {
|
||||
for (ModelInstance *i : this->instances) {
|
||||
i->set_offset(i->get_offset() - shift);
|
||||
}
|
||||
this->invalidate_bounding_box();
|
||||
}
|
||||
#endif // !ENABLE_MODELVOLUME_TRANSFORM
|
||||
}
|
||||
|
||||
void ModelObject::ensure_on_bed()
|
||||
|
|
@ -731,8 +846,7 @@ void ModelObject::translate(double x, double y, double z)
|
|||
{
|
||||
for (ModelVolume *v : this->volumes)
|
||||
{
|
||||
v->mesh.translate(float(x), float(y), float(z));
|
||||
v->m_convex_hull.translate(float(x), float(y), float(z));
|
||||
v->translate(x, y, z);
|
||||
}
|
||||
|
||||
if (m_bounding_box_valid)
|
||||
|
|
@ -743,51 +857,55 @@ void ModelObject::scale(const Vec3d &versor)
|
|||
{
|
||||
for (ModelVolume *v : this->volumes)
|
||||
{
|
||||
v->mesh.scale(versor);
|
||||
v->m_convex_hull.scale(versor);
|
||||
v->scale(versor);
|
||||
}
|
||||
#if !ENABLE_MODELVOLUME_TRANSFORM
|
||||
// reset origin translation since it doesn't make sense anymore
|
||||
this->origin_translation = Vec3d::Zero();
|
||||
#endif // !ENABLE_MODELVOLUME_TRANSFORM
|
||||
this->invalidate_bounding_box();
|
||||
}
|
||||
|
||||
void ModelObject::rotate(float angle, const Axis& axis)
|
||||
void ModelObject::rotate(double angle, Axis axis)
|
||||
{
|
||||
for (ModelVolume *v : this->volumes)
|
||||
{
|
||||
v->mesh.rotate(angle, axis);
|
||||
v->m_convex_hull.rotate(angle, axis);
|
||||
v->rotate(angle, axis);
|
||||
}
|
||||
|
||||
center_around_origin();
|
||||
|
||||
#if !ENABLE_MODELVOLUME_TRANSFORM
|
||||
this->origin_translation = Vec3d::Zero();
|
||||
#endif // !ENABLE_MODELVOLUME_TRANSFORM
|
||||
this->invalidate_bounding_box();
|
||||
}
|
||||
|
||||
void ModelObject::rotate(float angle, const Vec3d& axis)
|
||||
void ModelObject::rotate(double angle, const Vec3d& axis)
|
||||
{
|
||||
for (ModelVolume *v : this->volumes)
|
||||
{
|
||||
v->mesh.rotate(angle, axis);
|
||||
v->m_convex_hull.rotate(angle, axis);
|
||||
v->rotate(angle, axis);
|
||||
}
|
||||
|
||||
center_around_origin();
|
||||
|
||||
#if !ENABLE_MODELVOLUME_TRANSFORM
|
||||
this->origin_translation = Vec3d::Zero();
|
||||
#endif // !ENABLE_MODELVOLUME_TRANSFORM
|
||||
this->invalidate_bounding_box();
|
||||
}
|
||||
|
||||
void ModelObject::mirror(const Axis &axis)
|
||||
void ModelObject::mirror(Axis axis)
|
||||
{
|
||||
for (ModelVolume *v : this->volumes)
|
||||
{
|
||||
v->mesh.mirror(axis);
|
||||
v->m_convex_hull.mirror(axis);
|
||||
v->mirror(axis);
|
||||
}
|
||||
|
||||
#if !ENABLE_MODELVOLUME_TRANSFORM
|
||||
this->origin_translation = Vec3d::Zero();
|
||||
#endif // !ENABLE_MODELVOLUME_TRANSFORM
|
||||
this->invalidate_bounding_box();
|
||||
}
|
||||
|
||||
|
|
@ -864,7 +982,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
|
|||
if (this->volumes.size() > 1) {
|
||||
// We can't split meshes if there's more than one volume, because
|
||||
// we can't group the resulting meshes by object afterwards
|
||||
new_objects->push_back(this);
|
||||
new_objects->emplace_back(this);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -876,16 +994,14 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
|
|||
|
||||
mesh->repair();
|
||||
|
||||
ModelObject* new_object = m_model->add_object(*this, false);
|
||||
new_object->sla_support_points.clear();
|
||||
new_object->input_file = "";
|
||||
ModelVolume* new_volume = new_object->add_volume(*mesh);
|
||||
new_volume->name = volume->name;
|
||||
new_volume->config = volume->config;
|
||||
new_volume->set_type(volume->type());
|
||||
new_volume->set_material_id(volume->material_id());
|
||||
|
||||
new_objects->push_back(new_object);
|
||||
ModelObject* new_object = m_model->add_object();
|
||||
new_object->name = this->name;
|
||||
new_object->config = this->config;
|
||||
new_object->instances.reserve(this->instances.size());
|
||||
for (const ModelInstance *model_instance : this->instances)
|
||||
new_object->add_instance(*model_instance);
|
||||
new_object->add_volume(*volume, std::move(*mesh));
|
||||
new_objects->emplace_back(new_object);
|
||||
delete mesh;
|
||||
}
|
||||
|
||||
|
|
@ -918,17 +1034,32 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const
|
|||
double min_z = DBL_MAX;
|
||||
|
||||
ModelInstance* inst = instances[instance_idx];
|
||||
const Transform3d& m = inst->get_matrix(true);
|
||||
const Transform3d& mi = inst->get_matrix(true);
|
||||
|
||||
for (ModelVolume *v : volumes)
|
||||
for (const ModelVolume* v : volumes)
|
||||
{
|
||||
if (!v->is_model_part())
|
||||
continue;
|
||||
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
Transform3d mv = mi * v->get_matrix();
|
||||
const TriangleMesh& hull = v->get_convex_hull();
|
||||
for (uint32_t f = 0; f < hull.stl.stats.number_of_facets; ++f)
|
||||
{
|
||||
const stl_facet* facet = hull.stl.facet_start + f;
|
||||
min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[0].cast<double>()));
|
||||
min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[1].cast<double>()));
|
||||
min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[2].cast<double>()));
|
||||
}
|
||||
#else
|
||||
for (uint32_t f = 0; f < v->mesh.stl.stats.number_of_facets; ++f)
|
||||
{
|
||||
const stl_facet* facet = v->mesh.stl.facet_start + f;
|
||||
min_z = std::min(min_z, Vec3d::UnitZ().dot(m * facet->vertex[0].cast<double>()));
|
||||
min_z = std::min(min_z, Vec3d::UnitZ().dot(m * facet->vertex[1].cast<double>()));
|
||||
min_z = std::min(min_z, Vec3d::UnitZ().dot(m * facet->vertex[2].cast<double>()));
|
||||
min_z = std::min(min_z, Vec3d::UnitZ().dot(mi * facet->vertex[0].cast<double>()));
|
||||
min_z = std::min(min_z, Vec3d::UnitZ().dot(mi * facet->vertex[1].cast<double>()));
|
||||
min_z = std::min(min_z, Vec3d::UnitZ().dot(mi * facet->vertex[2].cast<double>()));
|
||||
}
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
}
|
||||
|
||||
return min_z + inst->get_offset(Z);
|
||||
|
|
@ -945,7 +1076,11 @@ unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3
|
|||
unsigned int inside_outside = 0;
|
||||
for (const ModelVolume *vol : this->volumes)
|
||||
if (vol->is_model_part()) {
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(model_instance->get_matrix() * vol->get_matrix());
|
||||
#else
|
||||
BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(model_instance->get_matrix());
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
if (print_volume.contains(bb))
|
||||
inside_outside |= INSIDE;
|
||||
else if (print_volume.intersects(bb))
|
||||
|
|
@ -1007,9 +1142,9 @@ void ModelObject::print_info() const
|
|||
void ModelVolume::set_material_id(t_model_material_id material_id)
|
||||
{
|
||||
m_material_id = material_id;
|
||||
|
||||
// ensure m_material_id references an existing material
|
||||
(void)this->object->get_model()->add_material(material_id);
|
||||
if (! material_id.empty())
|
||||
this->object->get_model()->add_material(material_id);
|
||||
}
|
||||
|
||||
ModelMaterial* ModelVolume::material() const
|
||||
|
|
@ -1020,17 +1155,17 @@ ModelMaterial* ModelVolume::material() const
|
|||
void ModelVolume::set_material(t_model_material_id material_id, const ModelMaterial &material)
|
||||
{
|
||||
m_material_id = material_id;
|
||||
(void)this->object->get_model()->add_material(material_id, material);
|
||||
if (! material_id.empty())
|
||||
this->object->get_model()->add_material(material_id, material);
|
||||
}
|
||||
|
||||
ModelMaterial* ModelVolume::assign_unique_material()
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
void ModelVolume::translate_geometry(const Vec3d& displacement)
|
||||
{
|
||||
Model* model = this->get_object()->get_model();
|
||||
|
||||
// as material-id "0" is reserved by the AMF spec we start from 1
|
||||
m_material_id = 1 + model->materials.size(); // watchout for implicit cast
|
||||
return model->add_material(m_material_id);
|
||||
mesh.translate((float)displacement(0), (float)displacement(1), (float)displacement(2));
|
||||
m_convex_hull.translate((float)displacement(0), (float)displacement(1), (float)displacement(2));
|
||||
}
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
|
||||
void ModelVolume::calculate_convex_hull()
|
||||
{
|
||||
|
|
@ -1108,15 +1243,66 @@ size_t ModelVolume::split(unsigned int max_extruders)
|
|||
return idx;
|
||||
}
|
||||
|
||||
void ModelVolume::translate(double x, double y, double z)
|
||||
{
|
||||
translate(Vec3d(x, y, z));
|
||||
}
|
||||
|
||||
void ModelVolume::translate(const Vec3d& displacement)
|
||||
{
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
set_offset(get_offset() + displacement);
|
||||
#else
|
||||
mesh.translate((float)displacement(0), (float)displacement(1), (float)displacement(2));
|
||||
m_convex_hull.translate((float)displacement(0), (float)displacement(1), (float)displacement(2));
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
}
|
||||
|
||||
void ModelVolume::scale(const Vec3d& scaling_factors)
|
||||
{
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
set_scaling_factor(get_scaling_factor().cwiseProduct(scaling_factors));
|
||||
#else
|
||||
mesh.scale(scaling_factors);
|
||||
m_convex_hull.scale(scaling_factors);
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
}
|
||||
|
||||
void ModelVolume::rotate(double angle, Axis axis)
|
||||
{
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
switch (axis)
|
||||
{
|
||||
case X: { rotate(angle, Vec3d::UnitX()); break; }
|
||||
case Y: { rotate(angle, Vec3d::UnitY()); break; }
|
||||
case Z: { rotate(angle, Vec3d::UnitZ()); break; }
|
||||
}
|
||||
#else
|
||||
mesh.rotate(angle, axis);
|
||||
m_convex_hull.rotate(angle, axis);
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
}
|
||||
|
||||
void ModelVolume::rotate(double angle, const Vec3d& axis)
|
||||
{
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
set_rotation(get_rotation() + Geometry::extract_euler_angles(Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis)).toRotationMatrix()));
|
||||
#else
|
||||
mesh.rotate(angle, axis);
|
||||
m_convex_hull.rotate(angle, axis);
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
}
|
||||
|
||||
void ModelVolume::mirror(Axis axis)
|
||||
{
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
Vec3d mirror = get_mirror();
|
||||
switch (axis)
|
||||
{
|
||||
case X: { mirror(0) *= -1.0; break; }
|
||||
case Y: { mirror(1) *= -1.0; break; }
|
||||
case Z: { mirror(2) *= -1.0; break; }
|
||||
}
|
||||
set_mirror(mirror);
|
||||
#else
|
||||
mesh.mirror(axis);
|
||||
m_convex_hull.mirror(axis);
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
}
|
||||
|
||||
#if !ENABLE_MODELVOLUME_TRANSFORM
|
||||
|
|
@ -1174,14 +1360,14 @@ void ModelInstance::set_mirror(Axis axis, double mirror)
|
|||
|
||||
void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const
|
||||
{
|
||||
mesh->transform(get_matrix(dont_translate).cast<float>());
|
||||
mesh->transform(get_matrix(dont_translate));
|
||||
}
|
||||
|
||||
BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mesh, bool dont_translate) const
|
||||
{
|
||||
// Rotate around mesh origin.
|
||||
TriangleMesh copy(*mesh);
|
||||
copy.transform(get_matrix(true, false, true, true).cast<float>());
|
||||
copy.transform(get_matrix(true, false, true, true));
|
||||
BoundingBoxf3 bbox = copy.bounding_box();
|
||||
|
||||
if (!empty(bbox)) {
|
||||
|
|
@ -1189,10 +1375,10 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes
|
|||
for (unsigned int i = 0; i < 3; ++i)
|
||||
{
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
if (std::abs(m_transformation.get_scaling_factor((Axis)i)-1.0) > EPSILON)
|
||||
if (std::abs(get_scaling_factor((Axis)i)-1.0) > EPSILON)
|
||||
{
|
||||
bbox.min(i) *= m_transformation.get_scaling_factor((Axis)i);
|
||||
bbox.max(i) *= m_transformation.get_scaling_factor((Axis)i);
|
||||
bbox.min(i) *= get_scaling_factor((Axis)i);
|
||||
bbox.max(i) *= get_scaling_factor((Axis)i);
|
||||
#else
|
||||
if (std::abs(this->m_scaling_factor(i) - 1.0) > EPSILON)
|
||||
{
|
||||
|
|
@ -1205,8 +1391,8 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes
|
|||
// Translate the bounding box.
|
||||
if (! dont_translate) {
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
bbox.min += m_transformation.get_offset();
|
||||
bbox.max += m_transformation.get_offset();
|
||||
bbox.min += get_offset();
|
||||
bbox.max += get_offset();
|
||||
#else
|
||||
bbox.min += this->m_offset;
|
||||
bbox.max += this->m_offset;
|
||||
|
|
@ -1230,9 +1416,9 @@ void ModelInstance::transform_polygon(Polygon* polygon) const
|
|||
{
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
// CHECK_ME -> Is the following correct or it should take in account all three rotations ?
|
||||
polygon->rotate(m_transformation.get_rotation(Z)); // rotate around polygon origin
|
||||
polygon->rotate(get_rotation(Z)); // rotate around polygon origin
|
||||
// CHECK_ME -> Is the following correct ?
|
||||
polygon->scale(m_transformation.get_scaling_factor(X), m_transformation.get_scaling_factor(Y)); // scale around polygon origin
|
||||
polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin
|
||||
#else
|
||||
// CHECK_ME -> Is the following correct or it should take in account all three rotations ?
|
||||
polygon->rotate(this->m_rotation(2)); // rotate around polygon origin
|
||||
|
|
|
|||
|
|
@ -22,21 +22,35 @@ class ModelInstance;
|
|||
class ModelMaterial;
|
||||
class ModelObject;
|
||||
class ModelVolume;
|
||||
class PresetBundle;
|
||||
class Print;
|
||||
|
||||
typedef std::string t_model_material_id;
|
||||
typedef std::string t_model_material_attribute;
|
||||
typedef std::map<t_model_material_attribute,std::string> t_model_material_attributes;
|
||||
typedef std::map<t_model_material_attribute, std::string> t_model_material_attributes;
|
||||
|
||||
typedef std::map<t_model_material_id,ModelMaterial*> ModelMaterialMap;
|
||||
typedef std::map<t_model_material_id, ModelMaterial*> ModelMaterialMap;
|
||||
typedef std::vector<ModelObject*> ModelObjectPtrs;
|
||||
typedef std::vector<ModelVolume*> ModelVolumePtrs;
|
||||
typedef std::vector<ModelInstance*> ModelInstancePtrs;
|
||||
|
||||
// Unique identifier of a Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial.
|
||||
// Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject)
|
||||
typedef size_t ModelID;
|
||||
// Valid IDs are strictly positive (non zero).
|
||||
// It is declared as an object, as some compilers (notably msvcc) consider a typedef size_t equivalent to size_t
|
||||
// for parameter overload.
|
||||
struct ModelID
|
||||
{
|
||||
ModelID(size_t id) : id(id) {}
|
||||
|
||||
bool operator==(const ModelID &rhs) const { return this->id == rhs.id; }
|
||||
bool operator!=(const ModelID &rhs) const { return this->id != rhs.id; }
|
||||
bool operator< (const ModelID &rhs) const { return this->id < rhs.id; }
|
||||
bool operator> (const ModelID &rhs) const { return this->id > rhs.id; }
|
||||
bool operator<=(const ModelID &rhs) const { return this->id <= rhs.id; }
|
||||
bool operator>=(const ModelID &rhs) const { return this->id >= rhs.id; }
|
||||
|
||||
size_t id;
|
||||
};
|
||||
|
||||
// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID
|
||||
// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject).
|
||||
|
|
@ -45,22 +59,69 @@ typedef size_t ModelID;
|
|||
class ModelBase
|
||||
{
|
||||
public:
|
||||
ModelID id() const { return m_id; }
|
||||
ModelID id() const { return m_id; }
|
||||
|
||||
protected:
|
||||
// Constructor to be only called by derived classes.
|
||||
ModelBase() {}
|
||||
ModelID m_id = generate_new_id();
|
||||
// Constructors to be only called by derived classes.
|
||||
// Default constructor to assign a unique ID.
|
||||
ModelBase() : m_id(generate_new_id()) {}
|
||||
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
|
||||
// by an existing ID copied from elsewhere.
|
||||
ModelBase(int) : m_id(ModelID(0)) {}
|
||||
|
||||
// Use with caution!
|
||||
void set_new_unique_id() { m_id = generate_new_id(); }
|
||||
void set_invalid_id() { m_id = 0; }
|
||||
// Use with caution!
|
||||
void copy_id(const ModelBase &rhs) { m_id = rhs.id(); }
|
||||
|
||||
// Override this method if a ModelBase derived class owns other ModelBase derived instances.
|
||||
void assign_new_unique_ids_recursive() { this->set_new_unique_id(); }
|
||||
|
||||
private:
|
||||
static inline ModelID generate_new_id() { return s_last_id ++; }
|
||||
static ModelID s_last_id;
|
||||
ModelID m_id;
|
||||
|
||||
static inline ModelID generate_new_id() { return ModelID(++ s_last_id); }
|
||||
static size_t s_last_id;
|
||||
};
|
||||
|
||||
#define MODELBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \
|
||||
/* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \
|
||||
/* to make a private copy for background processing. */ \
|
||||
static TYPE* new_copy(const TYPE &rhs) { return new TYPE(rhs); } \
|
||||
static TYPE* new_copy(TYPE &&rhs) { return new TYPE(std::move(rhs)); } \
|
||||
static TYPE make_copy(const TYPE &rhs) { return TYPE(rhs); } \
|
||||
static TYPE make_copy(TYPE &&rhs) { return TYPE(std::move(rhs)); } \
|
||||
TYPE& assign_copy(const TYPE &rhs); \
|
||||
TYPE& assign_copy(TYPE &&rhs); \
|
||||
/* Copy a TYPE, generate new IDs. The front end will use this call. */ \
|
||||
static TYPE* new_clone(const TYPE &rhs) { \
|
||||
/* Default constructor assigning an invalid ID. */ \
|
||||
auto obj = new TYPE(-1); \
|
||||
obj->assign_clone(rhs); \
|
||||
return obj; \
|
||||
} \
|
||||
TYPE make_clone(const TYPE &rhs) { \
|
||||
/* Default constructor assigning an invalid ID. */ \
|
||||
TYPE obj(-1); \
|
||||
obj.assign_clone(rhs); \
|
||||
return obj; \
|
||||
} \
|
||||
TYPE& assign_clone(const TYPE &rhs) { \
|
||||
this->assign_copy(rhs); \
|
||||
this->assign_new_unique_ids_recursive(); \
|
||||
return *this; \
|
||||
}
|
||||
|
||||
#define MODELBASE_DERIVED_PRIVATE_COPY_MOVE(TYPE) \
|
||||
private: \
|
||||
/* Private constructor with an unused int parameter will create a TYPE instance with an invalid ID. */ \
|
||||
explicit TYPE(int) : ModelBase(-1) {}; \
|
||||
void assign_new_unique_ids_recursive();
|
||||
|
||||
// Material, which may be shared across multiple ModelObjects of a single Model.
|
||||
class ModelMaterial : public ModelBase
|
||||
{
|
||||
friend class Model;
|
||||
public:
|
||||
// Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose.
|
||||
t_model_material_attributes attributes;
|
||||
|
|
@ -71,14 +132,22 @@ public:
|
|||
void apply(const t_model_material_attributes &attributes)
|
||||
{ this->attributes.insert(attributes.begin(), attributes.end()); }
|
||||
|
||||
protected:
|
||||
friend class Model;
|
||||
// Constructor, which assigns a new unique ID.
|
||||
ModelMaterial(Model *model) : m_model(model) {}
|
||||
// Copy constructor copies the ID and m_model!
|
||||
ModelMaterial(const ModelMaterial &rhs) = default;
|
||||
void set_model(Model *model) { m_model = model; }
|
||||
|
||||
private:
|
||||
// Parent, owning this material.
|
||||
Model *m_model;
|
||||
|
||||
ModelMaterial(Model *model) : m_model(model) {}
|
||||
ModelMaterial(Model *model, const ModelMaterial &other) : attributes(other.attributes), config(other.config), m_model(model) {}
|
||||
explicit ModelMaterial(ModelMaterial &rhs) = delete;
|
||||
ModelMaterial& operator=(ModelMaterial &rhs) = delete;
|
||||
ModelMaterial() = delete;
|
||||
ModelMaterial(ModelMaterial &&rhs) = delete;
|
||||
ModelMaterial& operator=(const ModelMaterial &rhs) = delete;
|
||||
ModelMaterial& operator=(ModelMaterial &&rhs) = delete;
|
||||
};
|
||||
|
||||
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
|
||||
|
|
@ -119,15 +188,12 @@ public:
|
|||
when user expects that. */
|
||||
Vec3d origin_translation;
|
||||
|
||||
// Assign a ModelObject to this object while keeping the original pointer to the parent Model.
|
||||
// Make a deep copy.
|
||||
ModelObject& assign(const ModelObject *rhs, bool copy_volumes = true);
|
||||
|
||||
Model* get_model() const { return m_model; };
|
||||
|
||||
ModelVolume* add_volume(const TriangleMesh &mesh);
|
||||
ModelVolume* add_volume(TriangleMesh &&mesh);
|
||||
ModelVolume* add_volume(const ModelVolume &volume);
|
||||
ModelVolume* add_volume(const ModelVolume &volume, TriangleMesh &&mesh);
|
||||
void delete_volume(size_t idx);
|
||||
void clear_volumes();
|
||||
bool is_multiparts() const { return volumes.size() > 1; }
|
||||
|
|
@ -163,9 +229,10 @@ public:
|
|||
void translate(double x, double y, double z);
|
||||
void scale(const Vec3d &versor);
|
||||
void scale(const double s) { this->scale(Vec3d(s, s, s)); }
|
||||
void rotate(float angle, const Axis &axis);
|
||||
void rotate(float angle, const Vec3d& axis);
|
||||
void mirror(const Axis &axis);
|
||||
void scale(double x, double y, double z) { this->scale(Vec3d(x, y, z)); }
|
||||
void rotate(double angle, Axis axis);
|
||||
void rotate(double angle, const Vec3d& axis);
|
||||
void mirror(Axis axis);
|
||||
size_t materials_count() const;
|
||||
size_t facets_count() const;
|
||||
bool needed_repair() const;
|
||||
|
|
@ -184,22 +251,27 @@ public:
|
|||
|
||||
protected:
|
||||
friend class Print;
|
||||
// Clone this ModelObject including its volumes and instances, keep the IDs of the copies equal to the original.
|
||||
// Called by Print::apply() to clone the Model / ModelObject hierarchy to the back end for background processing.
|
||||
ModelObject* clone(Model *parent);
|
||||
void set_model(Model *model) { m_model = model; }
|
||||
// Called by Print::apply() to set the model pointer after making a copy.
|
||||
void set_model(Model *model) { m_model = model; }
|
||||
|
||||
private:
|
||||
ModelObject(Model *model) : layer_height_profile_valid(false), m_model(model), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false) {}
|
||||
ModelObject(Model *model, const ModelObject &rhs, bool copy_volumes = true);
|
||||
explicit ModelObject(ModelObject &rhs) = delete;
|
||||
~ModelObject();
|
||||
ModelObject& operator=(ModelObject &rhs) = default;
|
||||
|
||||
// Parent object, owning this ModelObject.
|
||||
Model *m_model;
|
||||
/* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */
|
||||
/* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */
|
||||
ModelObject(const ModelObject &rhs) : ModelBase(-1), m_model(rhs.m_model) { this->assign_copy(rhs); }
|
||||
explicit ModelObject(ModelObject &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); }
|
||||
ModelObject& operator=(const ModelObject &rhs) { this->assign_copy(rhs); m_model = rhs.m_model; return *this; }
|
||||
ModelObject& operator=(ModelObject &&rhs) { this->assign_copy(std::move(rhs)); m_model = rhs.m_model; return *this; }
|
||||
|
||||
MODELBASE_DERIVED_COPY_MOVE_CLONE(ModelObject)
|
||||
MODELBASE_DERIVED_PRIVATE_COPY_MOVE(ModelObject)
|
||||
|
||||
// Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized.
|
||||
Model *m_model = nullptr;
|
||||
|
||||
// Bounding box, cached.
|
||||
|
||||
mutable BoundingBoxf3 m_bounding_box;
|
||||
mutable bool m_bounding_box_valid;
|
||||
};
|
||||
|
|
@ -208,8 +280,6 @@ private:
|
|||
// ModelVolume instances are owned by a ModelObject.
|
||||
class ModelVolume : public ModelBase
|
||||
{
|
||||
friend class ModelObject;
|
||||
|
||||
public:
|
||||
std::string name;
|
||||
// The triangular model.
|
||||
|
|
@ -226,9 +296,6 @@ public:
|
|||
SUPPORT_BLOCKER,
|
||||
};
|
||||
|
||||
// Clone this ModelVolume, keep the ID identical, set the parent to the cloned volume.
|
||||
ModelVolume* clone(ModelObject *parent) { return new ModelVolume(parent, *this); }
|
||||
|
||||
// A parent object owning this modifier volume.
|
||||
ModelObject* get_object() const { return this->object; };
|
||||
Type type() const { return m_type; }
|
||||
|
|
@ -246,11 +313,19 @@ public:
|
|||
// Return the number of volumes created from this one.
|
||||
// This is useful to assign different materials to different volumes of an object.
|
||||
size_t split(unsigned int max_extruders);
|
||||
void translate(double x, double y, double z);
|
||||
void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); }
|
||||
void translate(const Vec3d& displacement);
|
||||
void scale(const Vec3d& scaling_factors);
|
||||
void scale(double x, double y, double z) { scale(Vec3d(x, y, z)); }
|
||||
void scale(double s) { scale(Vec3d(s, s, s)); }
|
||||
void rotate(double angle, Axis axis);
|
||||
void rotate(double angle, const Vec3d& axis);
|
||||
void mirror(Axis axis);
|
||||
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
void translate_geometry(const Vec3d& displacement);
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
|
||||
ModelMaterial* assign_unique_material();
|
||||
|
||||
void calculate_convex_hull();
|
||||
const TriangleMesh& get_convex_hull() const;
|
||||
|
||||
|
|
@ -289,6 +364,13 @@ public:
|
|||
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
|
||||
protected:
|
||||
friend class Print;
|
||||
friend class ModelObject;
|
||||
|
||||
explicit ModelVolume(ModelVolume &rhs) = default;
|
||||
void set_model_object(ModelObject *model_object) { object = model_object; }
|
||||
|
||||
private:
|
||||
// Parent object owning this ModelVolume.
|
||||
ModelObject* object;
|
||||
|
|
@ -306,24 +388,45 @@ private:
|
|||
if (mesh.stl.stats.number_of_facets > 1)
|
||||
calculate_convex_hull();
|
||||
}
|
||||
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) :
|
||||
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) :
|
||||
mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {}
|
||||
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
|
||||
ModelVolume(ModelObject *object, const ModelVolume &other) :
|
||||
ModelBase(other), // copy the ID
|
||||
name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object)
|
||||
name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
|
||||
{
|
||||
this->set_material_id(other.material_id());
|
||||
}
|
||||
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
|
||||
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
|
||||
ModelBase(other), // copy the ID
|
||||
name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object)
|
||||
name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
|
||||
{
|
||||
this->set_material_id(other.material_id());
|
||||
if (mesh.stl.stats.number_of_facets > 1)
|
||||
calculate_convex_hull();
|
||||
}
|
||||
#else
|
||||
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
|
||||
ModelVolume(ModelObject *object, const ModelVolume &other) :
|
||||
ModelBase(other), // copy the ID
|
||||
name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object)
|
||||
{
|
||||
if (! other.material_id().empty())
|
||||
this->set_material_id(other.material_id());
|
||||
}
|
||||
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
|
||||
ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) :
|
||||
name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object)
|
||||
{
|
||||
if (! other.material_id().empty())
|
||||
this->set_material_id(other.material_id());
|
||||
if (mesh.stl.stats.number_of_facets > 1)
|
||||
calculate_convex_hull();
|
||||
}
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
|
||||
explicit ModelVolume(ModelVolume &rhs) = delete;
|
||||
ModelVolume& operator=(ModelVolume &rhs) = delete;
|
||||
};
|
||||
|
||||
|
|
@ -340,8 +443,6 @@ public:
|
|||
Num_BedStates
|
||||
};
|
||||
|
||||
friend class ModelObject;
|
||||
|
||||
private:
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
Geometry::Transformation m_transformation;
|
||||
|
|
@ -430,22 +531,33 @@ public:
|
|||
|
||||
bool is_printable() const { return print_volume_state == PVS_Inside; }
|
||||
|
||||
protected:
|
||||
friend class Print;
|
||||
friend class ModelObject;
|
||||
|
||||
explicit ModelInstance(const ModelInstance &rhs) = default;
|
||||
void set_model_object(ModelObject *model_object) { object = model_object; }
|
||||
|
||||
private:
|
||||
// Parent object, owning this instance.
|
||||
ModelObject* object;
|
||||
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) {}
|
||||
ModelInstance(ModelObject *object, const ModelInstance &other) :
|
||||
// Constructor, which assigns a new unique ID.
|
||||
explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) {}
|
||||
// Constructor, which assigns a new unique ID.
|
||||
explicit ModelInstance(ModelObject *object, const ModelInstance &other) :
|
||||
m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) {}
|
||||
#else
|
||||
ModelInstance(ModelObject *object) : m_offset(Vec3d::Zero()), m_rotation(Vec3d::Zero()), m_scaling_factor(Vec3d::Ones()), m_mirror(Vec3d::Ones()), object(object), print_volume_state(PVS_Inside) {}
|
||||
ModelInstance(ModelObject *object, const ModelInstance &other) :
|
||||
explicit ModelInstance(ModelObject *object) : m_offset(Vec3d::Zero()), m_rotation(Vec3d::Zero()), m_scaling_factor(Vec3d::Ones()), m_mirror(Vec3d::Ones()), object(object), print_volume_state(PVS_Inside) {}
|
||||
explicit ModelInstance(ModelObject *object, const ModelInstance &other) :
|
||||
m_offset(other.m_offset), m_rotation(other.m_rotation), m_scaling_factor(other.m_scaling_factor), m_mirror(other.m_mirror), object(object), print_volume_state(PVS_Inside) {}
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
|
||||
explicit ModelInstance(ModelInstance &rhs) = delete;
|
||||
ModelInstance& operator=(ModelInstance &rhs) = delete;
|
||||
ModelInstance() = delete;
|
||||
explicit ModelInstance(ModelInstance &&rhs) = delete;
|
||||
ModelInstance& operator=(const ModelInstance &rhs) = delete;
|
||||
ModelInstance& operator=(ModelInstance &&rhs) = delete;
|
||||
};
|
||||
|
||||
// The print bed content.
|
||||
|
|
@ -460,30 +572,39 @@ class Model : public ModelBase
|
|||
public:
|
||||
// Materials are owned by a model and referenced by objects through t_model_material_id.
|
||||
// Single material may be shared by multiple models.
|
||||
ModelMaterialMap materials;
|
||||
ModelMaterialMap materials;
|
||||
// Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation).
|
||||
ModelObjectPtrs objects;
|
||||
ModelObjectPtrs objects;
|
||||
|
||||
// Default constructor assigns a new ID to the model.
|
||||
Model() {}
|
||||
Model(const Model &rhs);
|
||||
Model& operator=(const Model &rhs);
|
||||
~Model() { this->clear_objects(); this->clear_materials(); }
|
||||
|
||||
// XXX: use fs::path ?
|
||||
/* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */
|
||||
/* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */
|
||||
Model(const Model &rhs) : ModelBase(-1) { this->assign_copy(rhs); }
|
||||
explicit Model(Model &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); }
|
||||
Model& operator=(const Model &rhs) { this->assign_copy(rhs); return *this; }
|
||||
Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); return *this; }
|
||||
|
||||
MODELBASE_DERIVED_COPY_MOVE_CLONE(Model)
|
||||
|
||||
static Model read_from_file(const std::string &input_file, DynamicPrintConfig *config = nullptr, bool add_default_instances = true);
|
||||
static Model read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances = true);
|
||||
|
||||
/// Repair the ModelObjects of the current Model.
|
||||
/// This function calls repair function on each TriangleMesh of each model object volume
|
||||
void repair();
|
||||
void repair();
|
||||
|
||||
// Add a new ModelObject to this Model, generate a new ID for this ModelObject.
|
||||
ModelObject* add_object();
|
||||
ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh);
|
||||
ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh);
|
||||
ModelObject* add_object(const ModelObject &other, bool copy_volumes = true);
|
||||
void delete_object(size_t idx);
|
||||
void delete_object(ModelObject* object);
|
||||
void clear_objects();
|
||||
ModelObject* add_object(const ModelObject &other);
|
||||
void delete_object(size_t idx);
|
||||
bool delete_object(ModelID id);
|
||||
bool delete_object(ModelObject* object);
|
||||
void clear_objects();
|
||||
|
||||
ModelMaterial* add_material(t_model_material_id material_id);
|
||||
ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other);
|
||||
|
|
@ -492,9 +613,9 @@ public:
|
|||
return (i == this->materials.end()) ? nullptr : i->second;
|
||||
}
|
||||
|
||||
void delete_material(t_model_material_id material_id);
|
||||
void clear_materials();
|
||||
bool add_default_instances();
|
||||
void delete_material(t_model_material_id material_id);
|
||||
void clear_materials();
|
||||
bool add_default_instances();
|
||||
// Returns approximate axis aligned bounding box of this model
|
||||
BoundingBoxf3 bounding_box() const;
|
||||
// Set the print_volume_state of PrintObject::instances,
|
||||
|
|
@ -520,8 +641,14 @@ public:
|
|||
static unsigned int get_auto_extruder_id(unsigned int max_extruders);
|
||||
static std::string get_auto_extruder_id_as_string(unsigned int max_extruders);
|
||||
static void reset_auto_extruder_id();
|
||||
|
||||
private:
|
||||
MODELBASE_DERIVED_PRIVATE_COPY_MOVE(Model)
|
||||
};
|
||||
|
||||
#undef MODELBASE_DERIVED_COPY_MOVE_CLONE
|
||||
#undef MODELBASE_DERIVED_PRIVATE_COPY_MOVE
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); }
|
|||
|
||||
inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); }
|
||||
inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); }
|
||||
inline Vec3i64 to_3d(const Vec2i64 &v, float z) { return Vec3i64(v(0), v(1), z); }
|
||||
inline Vec3i64 to_3d(const Vec2i64 &v, float z) { return Vec3i64(int64_t(v(0)), int64_t(v(1)), int64_t(z)); }
|
||||
inline Vec3crd to_3d(const Vec3crd &p, coord_t z) { return Vec3crd(p(0), p(1), z); }
|
||||
|
||||
inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale<double>(x), unscale<double>(y)); }
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
|||
"between_objects_gcode",
|
||||
"bridge_acceleration",
|
||||
"bridge_fan_speed",
|
||||
"colorprint_heights",
|
||||
"cooling",
|
||||
"default_acceleration",
|
||||
"deretract_speed",
|
||||
|
|
@ -603,7 +604,7 @@ static inline bool model_volume_list_changed(const ModelObject &model_object_old
|
|||
size_t i_old, i_new;
|
||||
for (i_old = 0, i_new = 0; i_old < model_object_old.volumes.size() && i_new < model_object_new.volumes.size();) {
|
||||
const ModelVolume &mv_old = *model_object_old.volumes[i_old];
|
||||
const ModelVolume &mv_new = *model_object_new.volumes[i_old];
|
||||
const ModelVolume &mv_new = *model_object_new.volumes[i_new];
|
||||
if (mv_old.type() != type) {
|
||||
++ i_old;
|
||||
continue;
|
||||
|
|
@ -615,8 +616,12 @@ static inline bool model_volume_list_changed(const ModelObject &model_object_old
|
|||
if (mv_old.id() != mv_new.id())
|
||||
return true;
|
||||
//FIXME test for the content of the mesh!
|
||||
//FIXME test for the transformation matrices!
|
||||
++ i_old;
|
||||
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
if (!mv_old.get_matrix().isApprox(mv_new.get_matrix()))
|
||||
return true;
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
++i_old;
|
||||
++ i_new;
|
||||
}
|
||||
for (; i_old < model_object_old.volumes.size(); ++ i_old) {
|
||||
|
|
@ -634,7 +639,7 @@ static inline bool model_volume_list_changed(const ModelObject &model_object_old
|
|||
return false;
|
||||
}
|
||||
|
||||
static inline void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src)
|
||||
void Print::model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src)
|
||||
{
|
||||
// 1) Delete the support volumes from model_object_dst.
|
||||
{
|
||||
|
|
@ -650,8 +655,10 @@ static inline void model_volume_list_update_supports(ModelObject &model_object_d
|
|||
}
|
||||
// 2) Copy the support volumes from model_object_src to the end of model_object_dst.
|
||||
for (ModelVolume *vol : model_object_src.volumes) {
|
||||
if (vol->is_support_modifier())
|
||||
model_object_dst.volumes.emplace_back(vol->clone(&model_object_dst));
|
||||
if (vol->is_support_modifier()) {
|
||||
model_object_dst.volumes.emplace_back(new ModelVolume(*vol));
|
||||
model_object_dst.volumes.back()->set_model_object(&model_object_dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -709,8 +716,60 @@ static std::vector<PrintInstances> print_objects_from_model_object(const ModelOb
|
|||
return std::vector<PrintInstances>(trafos.begin(), trafos.end());
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
|
||||
static inline void check_model_ids_validity(const Model &model)
|
||||
{
|
||||
std::set<ModelID> ids;
|
||||
auto check = [&ids](ModelID id) {
|
||||
assert(id.id > 0);
|
||||
assert(ids.find(id) == ids.end());
|
||||
ids.insert(id);
|
||||
};
|
||||
for (const ModelObject *model_object : model.objects) {
|
||||
check(model_object->id());
|
||||
for (const ModelVolume *model_volume : model_object->volumes)
|
||||
check(model_volume->id());
|
||||
for (const ModelInstance *model_instance : model_object->instances)
|
||||
check(model_instance->id());
|
||||
}
|
||||
for (const auto mm : model.materials)
|
||||
check(mm.second->id());
|
||||
}
|
||||
|
||||
static inline void check_model_ids_equal(const Model &model1, const Model &model2)
|
||||
{
|
||||
// Verify whether the IDs of model1 and model match.
|
||||
assert(model1.objects.size() == model2.objects.size());
|
||||
for (size_t idx_model = 0; idx_model < model2.objects.size(); ++ idx_model) {
|
||||
const ModelObject &model_object1 = *model1.objects[idx_model];
|
||||
const ModelObject &model_object2 = * model2.objects[idx_model];
|
||||
assert(model_object1.id() == model_object2.id());
|
||||
assert(model_object1.volumes.size() == model_object2.volumes.size());
|
||||
assert(model_object1.instances.size() == model_object2.instances.size());
|
||||
for (size_t i = 0; i < model_object1.volumes.size(); ++ i)
|
||||
assert(model_object1.volumes[i]->id() == model_object2.volumes[i]->id());
|
||||
for (size_t i = 0; i < model_object1.instances.size(); ++ i)
|
||||
assert(model_object1.instances[i]->id() == model_object2.instances[i]->id());
|
||||
}
|
||||
assert(model1.materials.size() == model2.materials.size());
|
||||
{
|
||||
auto it1 = model1.materials.begin();
|
||||
auto it2 = model2.materials.begin();
|
||||
for (; it1 != model1.materials.end(); ++ it1, ++ it2) {
|
||||
assert(it1->first == it2->first); // compare keys
|
||||
assert(it1->second->id() == it2->second->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* _DEBUG */
|
||||
|
||||
Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &config_in)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
check_model_ids_validity(model);
|
||||
#endif /* _DEBUG */
|
||||
|
||||
// Make a copy of the config, normalize it.
|
||||
DynamicPrintConfig config(config_in);
|
||||
config.normalize();
|
||||
|
|
@ -774,7 +833,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
|
|||
for (PrintRegion *region : m_regions)
|
||||
delete region;
|
||||
m_regions.clear();
|
||||
m_model = model;
|
||||
m_model.assign_copy(model);
|
||||
for (const ModelObject *model_object : m_model.objects)
|
||||
model_object_status.emplace(model_object->id(), ModelObjectStatus::New);
|
||||
} else {
|
||||
|
|
@ -789,7 +848,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
|
|||
model_object_status.emplace(model_object->id(), ModelObjectStatus::Old);
|
||||
for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) {
|
||||
model_object_status.emplace(model.objects[i]->id(), ModelObjectStatus::New);
|
||||
m_model.objects.emplace_back(model.objects[i]->clone(&m_model));
|
||||
m_model.objects.emplace_back(ModelObject::new_copy(*model.objects[i]));
|
||||
m_model.objects.back()->set_model(&m_model);
|
||||
}
|
||||
} else {
|
||||
// Reorder the objects, add new objects.
|
||||
|
|
@ -806,7 +866,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
|
|||
auto it = std::lower_bound(model_objects_old.begin(), model_objects_old.end(), mobj, by_id_lower);
|
||||
if (it == model_objects_old.end() || (*it)->id() != mobj->id()) {
|
||||
// New ModelObject added.
|
||||
m_model.objects.emplace_back((*it)->clone(&m_model));
|
||||
m_model.objects.emplace_back(ModelObject::new_copy(*mobj));
|
||||
m_model.objects.back()->set_model(&m_model);
|
||||
model_object_status.emplace(mobj->id(), ModelObjectStatus::New);
|
||||
} else {
|
||||
// Existing ModelObject re-added (possibly moved in the list).
|
||||
|
|
@ -899,8 +960,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
|
|||
update_apply_status(it->print_object->invalidate_all_steps());
|
||||
const_cast<PrintObjectStatus&>(*it).status = PrintObjectStatus::Deleted;
|
||||
}
|
||||
// Copy content of the ModelObject including its ID, reset the parent.
|
||||
model_object.assign(&model_object_new);
|
||||
// Copy content of the ModelObject including its ID, do not change the parent.
|
||||
model_object.assign_copy(model_object_new);
|
||||
} else if (support_blockers_differ || support_enforcers_differ) {
|
||||
// First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list.
|
||||
m_cancel_callback();
|
||||
|
|
@ -933,8 +994,11 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
|
|||
model_object.name = model_object_new.name;
|
||||
model_object.input_file = model_object_new.input_file;
|
||||
model_object.clear_instances();
|
||||
for (const ModelInstance *model_instance : model_object_new.instances)
|
||||
model_object.add_instance(*model_instance);
|
||||
model_object.instances.reserve(model_object_new.instances.size());
|
||||
for (const ModelInstance *model_instance : model_object_new.instances) {
|
||||
model_object.instances.emplace_back(new ModelInstance(*model_instance));
|
||||
model_object.instances.back()->set_model_object(&model_object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1136,6 +1200,11 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
|
|||
object->update_layer_height_profile();
|
||||
|
||||
this->update_object_placeholders();
|
||||
|
||||
#ifdef _DEBUG
|
||||
check_model_ids_equal(m_model, model);
|
||||
#endif /* _DEBUG */
|
||||
|
||||
return static_cast<ApplyStatus>(apply_status);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -564,6 +564,9 @@ private:
|
|||
void _make_wipe_tower();
|
||||
void _simplify_slices(double distance);
|
||||
|
||||
// Declared here to have access to Model / ModelObject / ModelInstance
|
||||
static void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src);
|
||||
|
||||
PrintState<PrintStep, psCount> m_state;
|
||||
// Mutex used for synchronization of the worker thread with the UI thread:
|
||||
// The mutex will be used to guard the worker thread against entering a stage
|
||||
|
|
@ -601,12 +604,6 @@ private:
|
|||
friend class PrintObject;
|
||||
};
|
||||
|
||||
|
||||
#define FOREACH_BASE(type, container, iterator) for (type::const_iterator iterator = (container).begin(); iterator != (container).end(); ++iterator)
|
||||
#define FOREACH_OBJECT(print, object) FOREACH_BASE(PrintObjectPtrs, (print)->m_objects, object)
|
||||
#define FOREACH_LAYER(object, layer) FOREACH_BASE(LayerPtrs, (object)->m_layers, layer)
|
||||
#define FOREACH_LAYERREGION(layer, layerm) FOREACH_BASE(LayerRegionPtrs, (layer)->m_regions, layerm)
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -192,6 +192,12 @@ void PrintConfigDef::init_fff_params()
|
|||
def->mode = comExpert;
|
||||
def->default_value = new ConfigOptionBool(false);
|
||||
|
||||
def = this->add("colorprint_heights", coFloats);
|
||||
def->label = L("Colorprint height");
|
||||
def->tooltip = L("Heights at which a filament change is to occur. ");
|
||||
def->cli = "colorprint-heights=f@";
|
||||
def->default_value = new ConfigOptionFloats { };
|
||||
|
||||
def = this->add("compatible_printers", coStrings);
|
||||
def->label = L("Compatible printers");
|
||||
def->mode = comAdvanced;
|
||||
|
|
|
|||
|
|
@ -704,6 +704,7 @@ public:
|
|||
ConfigOptionInts bridge_fan_speed;
|
||||
ConfigOptionFloat brim_width;
|
||||
ConfigOptionBool complete_objects;
|
||||
ConfigOptionFloats colorprint_heights;
|
||||
ConfigOptionBools cooling;
|
||||
ConfigOptionFloat default_acceleration;
|
||||
ConfigOptionInts disable_fan_first_layers;
|
||||
|
|
@ -781,6 +782,7 @@ protected:
|
|||
OPT_PTR(bridge_fan_speed);
|
||||
OPT_PTR(brim_width);
|
||||
OPT_PTR(complete_objects);
|
||||
OPT_PTR(colorprint_heights);
|
||||
OPT_PTR(cooling);
|
||||
OPT_PTR(default_acceleration);
|
||||
OPT_PTR(disable_fan_first_layers);
|
||||
|
|
|
|||
|
|
@ -170,8 +170,8 @@ void PrintObject::make_perimeters()
|
|||
|
||||
// merge slices if they were split into types
|
||||
if (this->typed_slices) {
|
||||
FOREACH_LAYER(this, layer_it) {
|
||||
(*layer_it)->merge_slices();
|
||||
for (Layer *layer : m_layers) {
|
||||
layer->merge_slices();
|
||||
m_print->throw_if_canceled();
|
||||
}
|
||||
this->typed_slices = false;
|
||||
|
|
@ -184,8 +184,8 @@ void PrintObject::make_perimeters()
|
|||
// but we don't generate any extra perimeter if fill density is zero, as they would be floating
|
||||
// inside the object - infill_only_where_needed should be the method of choice for printing
|
||||
// hollow objects
|
||||
for (size_t region_id = 0; region_id < m_print->regions().size(); ++ region_id) {
|
||||
const PrintRegion ®ion = *m_print->regions()[region_id];
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
const PrintRegion ®ion = *m_print->regions()[region_id];
|
||||
if (! region.config().extra_perimeters || region.config().perimeters == 0 || region.config().fill_density == 0 || this->layer_count() < 2)
|
||||
continue;
|
||||
|
||||
|
|
@ -313,7 +313,7 @@ void PrintObject::prepare_infill()
|
|||
|
||||
// Debugging output.
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
for (const Layer *layer : m_layers) {
|
||||
LayerRegion *layerm = layer->m_regions[region_id];
|
||||
layerm->export_region_slices_to_svg_debug("6_discover_vertical_shells-final");
|
||||
|
|
@ -332,7 +332,7 @@ void PrintObject::prepare_infill()
|
|||
m_print->throw_if_canceled();
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
for (const Layer *layer : m_layers) {
|
||||
LayerRegion *layerm = layer->m_regions[region_id];
|
||||
layerm->export_region_slices_to_svg_debug("7_discover_horizontal_shells-final");
|
||||
|
|
@ -351,7 +351,7 @@ void PrintObject::prepare_infill()
|
|||
m_print->throw_if_canceled();
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
for (const Layer *layer : m_layers) {
|
||||
LayerRegion *layerm = layer->m_regions[region_id];
|
||||
layerm->export_region_slices_to_svg_debug("8_clip_surfaces-final");
|
||||
|
|
@ -370,7 +370,7 @@ void PrintObject::prepare_infill()
|
|||
m_print->throw_if_canceled();
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
for (const Layer *layer : m_layers) {
|
||||
LayerRegion *layerm = layer->m_regions[region_id];
|
||||
layerm->export_region_slices_to_svg_debug("9_prepare_infill-final");
|
||||
|
|
@ -633,7 +633,7 @@ void PrintObject::detect_surfaces_type()
|
|||
// should be visible.
|
||||
bool interface_shells = m_config.interface_shells.value;
|
||||
|
||||
for (int idx_region = 0; idx_region < m_print->m_regions.size(); ++ idx_region) {
|
||||
for (int idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " in parallel - start";
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
for (Layer *layer : m_layers)
|
||||
|
|
@ -818,7 +818,7 @@ void PrintObject::process_external_surfaces()
|
|||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "Processing external surfaces...";
|
||||
|
||||
for (size_t region_id = 0; region_id < m_print->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++region_id) {
|
||||
const PrintRegion ®ion = *m_print->regions()[region_id];
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start";
|
||||
|
|
@ -851,13 +851,13 @@ void PrintObject::discover_vertical_shells()
|
|||
Polygons holes;
|
||||
};
|
||||
std::vector<DiscoverVerticalShellsCacheEntry> cache_top_botom_regions(m_layers.size(), DiscoverVerticalShellsCacheEntry());
|
||||
bool top_bottom_surfaces_all_regions = m_print->regions().size() > 1 && ! m_config.interface_shells.value;
|
||||
bool top_bottom_surfaces_all_regions = this->region_volumes.size() > 1 && ! m_config.interface_shells.value;
|
||||
if (top_bottom_surfaces_all_regions) {
|
||||
// This is a multi-material print and interface_shells are disabled, meaning that the vertical shell thickness
|
||||
// is calculated over all materials.
|
||||
// Is the "ensure vertical wall thickness" applicable to any region?
|
||||
bool has_extra_layers = false;
|
||||
for (size_t idx_region = 0; idx_region < m_print->regions().size(); ++ idx_region) {
|
||||
for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) {
|
||||
const PrintRegion ®ion = *m_print->get_region(idx_region);
|
||||
if (region.config().ensure_vertical_shell_thickness.value &&
|
||||
(region.config().top_solid_layers.value > 1 || region.config().bottom_solid_layers.value > 1)) {
|
||||
|
|
@ -874,7 +874,7 @@ void PrintObject::discover_vertical_shells()
|
|||
tbb::blocked_range<size_t>(0, m_layers.size(), grain_size),
|
||||
[this, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
|
||||
const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge };
|
||||
const size_t num_regions = m_print->regions().size();
|
||||
const size_t num_regions = this->region_volumes.size();
|
||||
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
||||
m_print->throw_if_canceled();
|
||||
const Layer &layer = *m_layers[idx_layer];
|
||||
|
|
@ -935,7 +935,7 @@ void PrintObject::discover_vertical_shells()
|
|||
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom";
|
||||
}
|
||||
|
||||
for (size_t idx_region = 0; idx_region < m_print->regions().size(); ++ idx_region) {
|
||||
for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) {
|
||||
PROFILE_BLOCK(discover_vertical_shells_region);
|
||||
|
||||
const PrintRegion ®ion = *m_print->get_region(idx_region);
|
||||
|
|
@ -1227,7 +1227,7 @@ void PrintObject::bridge_over_infill()
|
|||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "Bridge over infill...";
|
||||
|
||||
for (size_t region_id = 0; region_id < m_print->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
const PrintRegion ®ion = *m_print->regions()[region_id];
|
||||
|
||||
// skip bridging in case there are no voids
|
||||
|
|
@ -1243,9 +1243,10 @@ void PrintObject::bridge_over_infill()
|
|||
*this
|
||||
);
|
||||
|
||||
FOREACH_LAYER(this, layer_it) {
|
||||
for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++ layer_it) {
|
||||
// skip first layer
|
||||
if (layer_it == m_layers.begin()) continue;
|
||||
if (layer_it == m_layers.begin())
|
||||
continue;
|
||||
|
||||
Layer* layer = *layer_it;
|
||||
LayerRegion* layerm = layer->m_regions[region_id];
|
||||
|
|
@ -1271,8 +1272,8 @@ void PrintObject::bridge_over_infill()
|
|||
|
||||
// iterate through regions and collect internal surfaces
|
||||
Polygons lower_internal;
|
||||
FOREACH_LAYERREGION(lower_layer, lower_layerm_it)
|
||||
(*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal);
|
||||
for (LayerRegion *lower_layerm : lower_layer->m_regions)
|
||||
lower_layerm->fill_surfaces.filter_by_type(stInternal, &lower_internal);
|
||||
|
||||
// intersect such lower internal surfaces with the candidate solid surfaces
|
||||
to_bridge_pp = intersection(to_bridge_pp, lower_internal);
|
||||
|
|
@ -1443,14 +1444,14 @@ void PrintObject::_slice()
|
|||
layer->lower_layer = prev;
|
||||
}
|
||||
// Make sure all layers contain layer region objects for all regions.
|
||||
for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id)
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id)
|
||||
layer->add_region(this->print()->regions()[region_id]);
|
||||
prev = layer;
|
||||
}
|
||||
}
|
||||
|
||||
// Slice all non-modifier volumes.
|
||||
for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - region " << region_id;
|
||||
std::vector<ExPolygons> expolygons_by_layer = this->_slice_region(region_id, slice_zs, false);
|
||||
m_print->throw_if_canceled();
|
||||
|
|
@ -1462,14 +1463,14 @@ void PrintObject::_slice()
|
|||
}
|
||||
|
||||
// Slice all modifier volumes.
|
||||
if (this->print()->regions().size() > 1) {
|
||||
for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
|
||||
if (this->region_volumes.size() > 1) {
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - region " << region_id;
|
||||
std::vector<ExPolygons> expolygons_by_layer = this->_slice_region(region_id, slice_zs, true);
|
||||
m_print->throw_if_canceled();
|
||||
// loop through the other regions and 'steal' the slices belonging to this one
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - stealing " << region_id << " start";
|
||||
for (size_t other_region_id = 0; other_region_id < this->print()->regions().size(); ++ other_region_id) {
|
||||
for (size_t other_region_id = 0; other_region_id < this->region_volumes.size(); ++ other_region_id) {
|
||||
if (region_id == other_region_id)
|
||||
continue;
|
||||
for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) {
|
||||
|
|
@ -1496,7 +1497,7 @@ void PrintObject::_slice()
|
|||
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - removing top empty layers";
|
||||
while (! m_layers.empty()) {
|
||||
const Layer *layer = m_layers.back();
|
||||
for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id)
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id)
|
||||
if (layer->m_regions[region_id] != nullptr && ! layer->m_regions[region_id]->slices.empty())
|
||||
// Non empty layer.
|
||||
goto end;
|
||||
|
|
@ -1601,9 +1602,17 @@ std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z,
|
|||
//FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
|
||||
TriangleMesh mesh;
|
||||
for (const ModelVolume *v : volumes)
|
||||
mesh.merge(v->mesh);
|
||||
#if ENABLE_MODELVOLUME_TRANSFORM
|
||||
{
|
||||
TriangleMesh vol_mesh(v->mesh);
|
||||
vol_mesh.transform(v->get_matrix());
|
||||
mesh.merge(vol_mesh);
|
||||
}
|
||||
#else
|
||||
mesh.merge(v->mesh);
|
||||
#endif // ENABLE_MODELVOLUME_TRANSFORM
|
||||
if (mesh.stl.stats.number_of_facets > 0) {
|
||||
mesh.transform(m_trafo.cast<float>());
|
||||
mesh.transform(m_trafo);
|
||||
// apply XY shift
|
||||
mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), 0);
|
||||
// perform actual slicing
|
||||
|
|
@ -1734,8 +1743,8 @@ void PrintObject::_make_perimeters()
|
|||
|
||||
// merge slices if they were split into types
|
||||
if (this->typed_slices) {
|
||||
FOREACH_LAYER(this, layer_it)
|
||||
(*layer_it)->merge_slices();
|
||||
for (Layer *layer : m_layers)
|
||||
layer->merge_slices();
|
||||
this->typed_slices = false;
|
||||
this->invalidate_step(posPrepareInfill);
|
||||
}
|
||||
|
|
@ -1747,7 +1756,7 @@ void PrintObject::_make_perimeters()
|
|||
// but we don't generate any extra perimeter if fill density is zero, as they would be floating
|
||||
// inside the object - infill_only_where_needed should be the method of choice for printing
|
||||
// hollow objects
|
||||
for (size_t region_id = 0; region_id < m_print->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
const PrintRegion ®ion = *m_print->regions()[region_id];
|
||||
if (! region.config().extra_perimeters || region.config().perimeters == 0 || region.config().fill_density == 0 || this->layer_count() < 2)
|
||||
continue;
|
||||
|
|
@ -1919,7 +1928,7 @@ void PrintObject::discover_horizontal_shells()
|
|||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()";
|
||||
|
||||
for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
for (int i = 0; i < int(m_layers.size()); ++ i) {
|
||||
m_print->throw_if_canceled();
|
||||
LayerRegion *layerm = m_layers[i]->regions()[region_id];
|
||||
|
|
@ -2093,7 +2102,7 @@ void PrintObject::discover_horizontal_shells()
|
|||
} // for each region
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
for (const Layer *layer : m_layers) {
|
||||
const LayerRegion *layerm = layer->m_regions[region_id];
|
||||
layerm->export_region_slices_to_svg_debug("5_discover_horizontal_shells");
|
||||
|
|
@ -2109,7 +2118,7 @@ void PrintObject::discover_horizontal_shells()
|
|||
void PrintObject::combine_infill()
|
||||
{
|
||||
// Work on each region separately.
|
||||
for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) {
|
||||
const PrintRegion *region = this->print()->regions()[region_id];
|
||||
const int every = region->config().infill_every_layers.value;
|
||||
if (every < 2 || region->config().fill_density == 0.)
|
||||
|
|
|
|||
|
|
@ -2450,7 +2450,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
|
|||
// Transform loops into ExtrusionPath objects.
|
||||
extrusion_entities_append_paths(
|
||||
top_contact_layer.extrusions,
|
||||
STDMOVE(loop_lines),
|
||||
std::move(loop_lines),
|
||||
erSupportMaterialInterface, flow.mm3_per_mm(), flow.width, flow.height);
|
||||
}
|
||||
|
||||
|
|
@ -2827,7 +2827,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
to_infill = offset_ex(to_infill, float(- 0.4 * flow.scaled_spacing()));
|
||||
extrusion_entities_append_paths(
|
||||
support_layer.support_fills.entities,
|
||||
to_polylines(STDMOVE(to_infill_polygons)),
|
||||
to_polylines(std::move(to_infill_polygons)),
|
||||
erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height);
|
||||
}
|
||||
if (! to_infill.empty()) {
|
||||
|
|
@ -2841,7 +2841,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
// Destination
|
||||
support_layer.support_fills.entities,
|
||||
// Regions to fill
|
||||
STDMOVE(to_infill),
|
||||
std::move(to_infill),
|
||||
// Filler and its parameters
|
||||
filler, float(support_density),
|
||||
// Extrusion parameters
|
||||
|
|
@ -3037,14 +3037,14 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing()));
|
||||
extrusion_entities_append_paths(
|
||||
base_layer.extrusions,
|
||||
to_polylines(STDMOVE(to_infill_polygons)),
|
||||
to_polylines(std::move(to_infill_polygons)),
|
||||
erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height);
|
||||
}
|
||||
fill_expolygons_generate_paths(
|
||||
// Destination
|
||||
base_layer.extrusions,
|
||||
// Regions to fill
|
||||
STDMOVE(to_infill),
|
||||
std::move(to_infill),
|
||||
// Filler and its parameters
|
||||
filler, density,
|
||||
// Extrusion parameters
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
// Uses a unique opengl context
|
||||
#define ENABLE_USE_UNIQUE_GLCONTEXT (1 && ENABLE_1_42_0)
|
||||
// Disable synchronization of unselected instances
|
||||
#define DISABLE_INSTANCES_SYNCH (1 && ENABLE_1_42_0)
|
||||
#define DISABLE_INSTANCES_SYNCH (0 && ENABLE_1_42_0)
|
||||
// Modified camera target behavior
|
||||
#define ENABLE_MODIFIED_CAMERA_TARGET (1 && ENABLE_1_42_0)
|
||||
// Add Geometry::Transformation class and use it into ModelInstance, ModelVolume and GLVolume
|
||||
|
|
|
|||
|
|
@ -272,9 +272,9 @@ void TriangleMesh::rotate(float angle, const Vec3d& axis)
|
|||
if (angle == 0.f)
|
||||
return;
|
||||
|
||||
Vec3f axis_norm = axis.cast<float>().normalized();
|
||||
Transform3f m = Transform3f::Identity();
|
||||
m.rotate(Eigen::AngleAxisf(angle, axis_norm));
|
||||
Vec3d axis_norm = axis.normalized();
|
||||
Transform3d m = Transform3d::Identity();
|
||||
m.rotate(Eigen::AngleAxisd(angle, axis_norm));
|
||||
stl_transform(&stl, m);
|
||||
}
|
||||
|
||||
|
|
@ -290,7 +290,7 @@ void TriangleMesh::mirror(const Axis &axis)
|
|||
stl_invalidate_shared_vertices(&this->stl);
|
||||
}
|
||||
|
||||
void TriangleMesh::transform(const Transform3f& t)
|
||||
void TriangleMesh::transform(const Transform3d& t)
|
||||
{
|
||||
stl_transform(&stl, t);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public:
|
|||
void mirror_x() { this->mirror(X); }
|
||||
void mirror_y() { this->mirror(Y); }
|
||||
void mirror_z() { this->mirror(Z); }
|
||||
void transform(const Transform3f& t);
|
||||
void transform(const Transform3d& t);
|
||||
void align_to_origin();
|
||||
void rotate(double angle, Point* center);
|
||||
TriangleMeshPtrs split() const;
|
||||
|
|
|
|||
|
|
@ -47,19 +47,6 @@ typedef double coordf_t;
|
|||
#define scale_(val) ((val) / SCALING_FACTOR)
|
||||
#define SCALED_EPSILON scale_(EPSILON)
|
||||
|
||||
// Which C++ version is supported?
|
||||
// For example, could optimized functions with move semantics be used?
|
||||
#if __cplusplus==201402L
|
||||
#define SLIC3R_CPPVER 14
|
||||
#define STDMOVE(WHAT) std::move(WHAT)
|
||||
#elif __cplusplus==201103L
|
||||
#define SLIC3R_CPPVER 11
|
||||
#define STDMOVE(WHAT) std::move(WHAT)
|
||||
#else
|
||||
#define SLIC3R_CPPVER 0
|
||||
#define STDMOVE(WHAT) (WHAT)
|
||||
#endif
|
||||
|
||||
#define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/"
|
||||
|
||||
inline std::string debug_out_path(const char *name, ...)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue