mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-28 12:29:43 +00:00
Compare commits
23 Commits
feat/print
...
hanif/cli-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bf2b0c832 | ||
|
|
2a2fe3d660 | ||
|
|
9f8d943b05 | ||
|
|
b0554a9447 | ||
|
|
88b9ad3d81 | ||
|
|
b2836c48ac | ||
|
|
334b8acdd1 | ||
|
|
cd94c46df2 | ||
|
|
3256e20865 | ||
|
|
13d7bda6f8 | ||
|
|
739ae609aa | ||
|
|
d5fb17e563 | ||
|
|
6d1584844e | ||
|
|
6fdd4945c1 | ||
|
|
cbd1bf2c37 | ||
|
|
4c71ec4770 | ||
|
|
3ab5157a49 | ||
|
|
390e47a8c6 | ||
|
|
58b17f08e0 | ||
|
|
274150af27 | ||
|
|
2e246341d1 | ||
|
|
9513300835 | ||
|
|
f7caf0db07 |
@@ -1793,8 +1793,9 @@ int CLI::run(int argc, char **argv)
|
||||
old_printable_area = config.option<ConfigOptionPoints>("printable_area", true)->values;
|
||||
old_exclude_area = config.option<ConfigOptionPoints>("bed_exclude_area", true)->values;
|
||||
if (old_printable_area.size() >= 4) {
|
||||
old_printable_width = (int)(old_printable_area[2].x() - old_printable_area[0].x());
|
||||
old_printable_depth = (int)(old_printable_area[2].y() - old_printable_area[0].y());
|
||||
BoundingBoxf old_printable_bbox(old_printable_area);
|
||||
old_printable_width = static_cast<int>(old_printable_bbox.size().x());
|
||||
old_printable_depth = static_cast<int>(old_printable_bbox.size().y());
|
||||
}
|
||||
old_printable_height = (int)(config.opt_float("printable_height"));
|
||||
|
||||
@@ -2343,8 +2344,9 @@ int CLI::run(int argc, char **argv)
|
||||
Pointfs orig_printable_area;
|
||||
orig_printable_area = config.option<ConfigOptionPoints>("printable_area", true)->values;
|
||||
if (orig_printable_area.size() >= 4) {
|
||||
orig_printable_width = (int)(orig_printable_area[2].x() - orig_printable_area[0].x());
|
||||
orig_printable_depth = (int)(orig_printable_area[2].y() - orig_printable_area[0].y());
|
||||
BoundingBoxf orig_printable_bbox(orig_printable_area);
|
||||
orig_printable_width = static_cast<int>(orig_printable_bbox.size().x());
|
||||
orig_printable_depth = static_cast<int>(orig_printable_bbox.size().y());
|
||||
}
|
||||
orig_printable_height = (int)(config.opt_float("printable_height"));
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(":%1%, check printable size: old_printable_width=%2%, orig_printable_width=%3%, old_printable_depth=%4%, orig_printable_depth=%5%, old_printable_height=%6%, orig_printable_height=%7%")
|
||||
@@ -3138,7 +3140,22 @@ int CLI::run(int argc, char **argv)
|
||||
std::vector<int> old_variant_counts(filament_count, 1), new_variant_counts;
|
||||
|
||||
ConfigOptionInts* filament_self_index_opt = m_print_config.option<ConfigOptionInts>("filament_self_index");
|
||||
if (!filament_self_index_opt) {
|
||||
bool need_regenerate_self_index = !filament_self_index_opt;
|
||||
if (filament_self_index_opt) {
|
||||
// a filament_self_index carried over from a project with a different
|
||||
// filament_count can imply more distinct filament groups than currently exist.
|
||||
// old_start_indice/old_variant_counts below are sized to filament_count, so an
|
||||
// unreconciled index walks old_start_indice[++k] past its bounds (heap corruption).
|
||||
int max_self_index = 0;
|
||||
for (int v : filament_self_index_opt->values)
|
||||
max_self_index = std::max(max_self_index, v);
|
||||
if (max_self_index > filament_count) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("filament_self_index implies %1% filament groups but filament_count is %2%, regenerating")
|
||||
% max_self_index % filament_count;
|
||||
need_regenerate_self_index = true;
|
||||
}
|
||||
}
|
||||
if (need_regenerate_self_index) {
|
||||
filament_self_index_opt = m_print_config.option<ConfigOptionInts>("filament_self_index", true);
|
||||
std::vector<int>& filament_self_indice = filament_self_index_opt->values;
|
||||
filament_self_indice.resize(filament_count);
|
||||
@@ -3732,6 +3749,8 @@ int CLI::run(int argc, char **argv)
|
||||
double height_to_lid = m_print_config.opt_float("extruder_clearance_height_to_lid");
|
||||
double height_to_rod = m_print_config.opt_float("extruder_clearance_height_to_rod");
|
||||
double clearance_radius = m_print_config.opt_float("extruder_clearance_radius");
|
||||
double nozzle_height = m_print_config.opt_float("nozzle_height");
|
||||
Vec2d align_center = m_print_config.option<ConfigOptionPoint>("best_object_pos")->value;
|
||||
int shared_printable_width = 0, shared_printable_depth = 0, shared_printable_height = 0, shared_center_x = 0, shared_center_y = 0;
|
||||
//double plate_stride;
|
||||
std::string bed_texture;
|
||||
@@ -3742,8 +3761,11 @@ int CLI::run(int argc, char **argv)
|
||||
if (m_print_config.opt<ConfigOptionFloatsNullable>("extruder_printable_height")) {
|
||||
current_extruder_print_heights = m_print_config.opt<ConfigOptionFloatsNullable>("extruder_printable_height")->values;
|
||||
}
|
||||
current_printable_width = current_printable_area[2].x() - current_printable_area[0].x();
|
||||
current_printable_depth = current_printable_area[2].y() - current_printable_area[0].y();
|
||||
{
|
||||
BoundingBoxf current_printable_bbox(current_printable_area);
|
||||
current_printable_width = static_cast<int>(current_printable_bbox.size().x());
|
||||
current_printable_depth = static_cast<int>(current_printable_bbox.size().y());
|
||||
}
|
||||
current_printable_height = print_height;
|
||||
if (old_printable_width == 0)
|
||||
old_printable_width = current_printable_width;
|
||||
@@ -3944,6 +3966,13 @@ int CLI::run(int argc, char **argv)
|
||||
ConfigOptionFloats *wipe_x_option = dynamic_cast<ConfigOptionFloats *>(print_config.option("wipe_tower_x"));
|
||||
ConfigOptionFloats *wipe_y_option = dynamic_cast<ConfigOptionFloats *>(print_config.option("wipe_tower_y"));
|
||||
|
||||
// get_at() clamps an out-of-range index to entry 0 instead of erroring, which
|
||||
// would silently reuse another plate's wipe tower position here. Warn so a mismatched
|
||||
// wipe_tower_x/y array (e.g. from a project saved before this plate was added) is visible.
|
||||
if (static_cast<size_t>(plate_index) >= wipe_x_option->values.size() || static_cast<size_t>(plate_index) >= wipe_y_option->values.size()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("plate %1%: wipe_tower_x/y only has %2%/%3% entries, reusing entry 0's position")
|
||||
%(plate_index+1) %wipe_x_option->values.size() %wipe_y_option->values.size();
|
||||
}
|
||||
plate_obj_size_info.wipe_x = wipe_x_option->get_at(plate_index);
|
||||
plate_obj_size_info.wipe_y = wipe_y_option->get_at(plate_index);
|
||||
|
||||
@@ -4139,8 +4168,9 @@ int CLI::run(int argc, char **argv)
|
||||
temp_extruder_print_heights = config.option<ConfigOptionFloatsNullable>("extruder_printable_height", true)->values;
|
||||
|
||||
if (temp_printable_area.size() >= 4) {
|
||||
printer_plate.printable_width = (int)(temp_printable_area[2].x() - temp_printable_area[0].x());
|
||||
printer_plate.printable_depth = (int)(temp_printable_area[2].y() - temp_printable_area[0].y());
|
||||
BoundingBoxf temp_printable_bbox(temp_printable_area);
|
||||
printer_plate.printable_width = static_cast<int>(temp_printable_bbox.size().x());
|
||||
printer_plate.printable_depth = static_cast<int>(temp_printable_bbox.size().y());
|
||||
printer_plate.printable_height = (int)(config.opt_float("printable_height"));
|
||||
}
|
||||
if (temp_exclude_area.size() >= 4) {
|
||||
@@ -4788,6 +4818,8 @@ int CLI::run(int argc, char **argv)
|
||||
arrange_cfg.clearance_height_to_rod = height_to_rod;
|
||||
arrange_cfg.clearance_height_to_lid = height_to_lid;
|
||||
arrange_cfg.clearance_radius = clearance_radius;
|
||||
arrange_cfg.nozzle_height = nozzle_height;
|
||||
arrange_cfg.align_center = align_center;
|
||||
arrange_cfg.printable_height = print_height;
|
||||
arrange_cfg.min_obj_distance = 0;
|
||||
if (arrange_cfg.is_seq_print) {
|
||||
@@ -5238,6 +5270,8 @@ int CLI::run(int argc, char **argv)
|
||||
arrange_cfg.clearance_height_to_rod = height_to_rod;
|
||||
arrange_cfg.clearance_height_to_lid = height_to_lid;
|
||||
arrange_cfg.clearance_radius = clearance_radius;
|
||||
arrange_cfg.nozzle_height = nozzle_height;
|
||||
arrange_cfg.align_center = align_center;
|
||||
arrange_cfg.printable_height = print_height;
|
||||
arrange_cfg.min_obj_distance = 0;
|
||||
if (arrange_cfg.is_seq_print) {
|
||||
@@ -5529,6 +5563,26 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
finished_arrange = true;
|
||||
}
|
||||
// CLI has no m_plater, so PartPlateList::create_plate() never backfills
|
||||
// wipe_tower_x/y for plates created here during arrange overflow (unlike GUI's
|
||||
// set_default_wipe_tower_pos_for_plate()). Keep both arrays sized to the actual
|
||||
// plate count so a later per-plate get_at() never silently reuses another plate's
|
||||
// wipe tower position via ConfigOptionVector's out-of-range clamp.
|
||||
{
|
||||
int final_plate_count = partplate_list.get_plate_count();
|
||||
ConfigOptionFloats* wipe_x_opt = m_print_config.option<ConfigOptionFloats>("wipe_tower_x");
|
||||
ConfigOptionFloats* wipe_y_opt = m_print_config.option<ConfigOptionFloats>("wipe_tower_y");
|
||||
if (wipe_x_opt && !wipe_x_opt->values.empty() && wipe_x_opt->values.size() < static_cast<size_t>(final_plate_count)) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("wipe_tower_x had %1% entries for %2% plates, backfilling with entry 0")
|
||||
% wipe_x_opt->values.size() % final_plate_count;
|
||||
wipe_x_opt->values.resize(final_plate_count, wipe_x_opt->values.front());
|
||||
}
|
||||
if (wipe_y_opt && !wipe_y_opt->values.empty() && wipe_y_opt->values.size() < static_cast<size_t>(final_plate_count)) {
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("wipe_tower_y had %1% entries for %2% plates, backfilling with entry 0")
|
||||
% wipe_y_opt->values.size() % final_plate_count;
|
||||
wipe_y_opt->values.resize(final_plate_count, wipe_y_opt->values.front());
|
||||
}
|
||||
}
|
||||
original_model.clear_objects();
|
||||
original_model.clear_materials();
|
||||
}
|
||||
@@ -6516,10 +6570,20 @@ int CLI::run(int argc, char **argv)
|
||||
plate_data->nozzle_diameters = nozzle_diameter_str;
|
||||
|
||||
for (auto it = plate_data->slice_filaments_info.begin(); it != plate_data->slice_filaments_info.end(); it++) {
|
||||
// ConfigOptionVector::get_at() falls back to values.front() when the index is out of
|
||||
// range, but that is undefined behavior when values is empty outright (e.g. filament_ids
|
||||
// is never populated on a from-scratch slice with no --load-filaments) - guard every
|
||||
// get_at() here on the vector actually having an entry at it->id before calling it.
|
||||
bool valid_id = it->id >= 0;
|
||||
std::string display_filament_type;
|
||||
it->type = m_print_config.get_filament_type(display_filament_type, it->id);
|
||||
it->color = filament_color ? filament_color->get_at(it->id) : "#FFFFFF";
|
||||
it->filament_id = filament_id?filament_id->get_at(it->id):"";
|
||||
if (valid_id && filament_types && static_cast<size_t>(it->id) < filament_types->values.size())
|
||||
it->type = m_print_config.get_filament_type(display_filament_type, it->id);
|
||||
it->color = (valid_id && filament_color && static_cast<size_t>(it->id) < filament_color->values.size()) ?
|
||||
filament_color->get_at(it->id) :
|
||||
"#FFFFFF";
|
||||
it->filament_id = (valid_id && filament_id && static_cast<size_t>(it->id) < filament_id->values.size()) ?
|
||||
filament_id->get_at(it->id) :
|
||||
"";
|
||||
}
|
||||
|
||||
if (!plate_data->plate_thumbnail.is_valid()) {
|
||||
@@ -7311,6 +7375,10 @@ bool CLI::setup(int argc, char **argv)
|
||||
m_config.option(optdef.first, true);
|
||||
|
||||
set_data_dir(m_config.opt_string("datadir"));
|
||||
if (!data_dir().empty() && !boost::filesystem::exists(data_dir())) {
|
||||
boost::nowide::cerr << "Could not create data directory: " << data_dir() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
//FIXME Validating at this stage most likely does not make sense, as the config is not fully initialized yet.
|
||||
if (!validity.empty()) {
|
||||
@@ -7384,7 +7452,7 @@ void CLI::print_help(bool include_print_options, PrinterTechnology printer_techn
|
||||
<< std::endl
|
||||
<< "Print setting priorities:" << std::endl
|
||||
<< "\t1) setting values from the command line (highest priority)"<< std::endl
|
||||
<< "\t2) setting values loaded with --load_settings and --load_filaments" << std::endl
|
||||
<< "\t2) setting values loaded with --load-settings and --load-filaments" << std::endl
|
||||
<< "\t3) setting values loaded from 3mf(lowest priority)" << std::endl;
|
||||
|
||||
/*if (include_print_options) {
|
||||
@@ -7423,6 +7491,10 @@ bool CLI::export_models(IO::ExportFormat format, std::string path_dir)
|
||||
for (ModelObject* model_object : model.objects)
|
||||
{
|
||||
const std::string path = this->output_filepath(*model_object, index++, format, path_dir);
|
||||
if (path.empty()) {
|
||||
boost::nowide::cerr << "Could not create output directory for STL export" << std::endl;
|
||||
return false;
|
||||
}
|
||||
success = Slic3r::store_stl(path.c_str(), model_object, true);
|
||||
if (success)
|
||||
BOOST_LOG_TRIVIAL(info) << "Model successfully exported to " << path << std::endl;
|
||||
@@ -7548,8 +7620,19 @@ std::string CLI::output_filepath(const ModelObject &object, unsigned int index,
|
||||
output_path = subdir + "/"+file_name;
|
||||
|
||||
boost::filesystem::path subdir_path(subdir);
|
||||
if (!boost::filesystem::exists(subdir_path))
|
||||
boost::filesystem::create_directory(subdir_path);
|
||||
if (!boost::filesystem::exists(subdir_path)) {
|
||||
try {
|
||||
boost::filesystem::create_directories(subdir_path);
|
||||
} catch (const boost::filesystem::filesystem_error &ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to create output directory " << subdir_path.string() << ": " << ex.what();
|
||||
}
|
||||
if (!boost::filesystem::exists(subdir_path)) {
|
||||
// Directory creation failed and won't succeed on a retry (same path, same cause) -
|
||||
// signal failure now instead of letting every object in the model repeat the same
|
||||
// doomed attempt and fail with a less specific "export failed" error later.
|
||||
return std::string();
|
||||
}
|
||||
}
|
||||
return output_path;
|
||||
}
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ public:
|
||||
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
|
||||
size_t idx() const { return m_idx; }
|
||||
const BoundingBox& bbox() const { return m_bbox; }
|
||||
Point centroid() const { return (m_bbox.min() + m_bbox.max() / 2); }
|
||||
Point centroid() const { return (m_bbox.min() + m_bbox.max()) / 2; }
|
||||
private:
|
||||
size_t m_idx;
|
||||
BoundingBox m_bbox;
|
||||
|
||||
@@ -13,7 +13,6 @@ BuildVolume::BuildVolume(const std::vector<Vec2d> &printable_area, const double
|
||||
: m_bed_shape(printable_area), m_max_print_height(printable_height), m_extruder_shapes(extruder_areas), m_extruder_printable_height(extruder_printable_heights)
|
||||
{
|
||||
assert(printable_height >= 0);
|
||||
//assert(extruder_printable_heights.size() == extruder_areas.size());
|
||||
|
||||
m_polygon = Polygon::new_scale(printable_area);
|
||||
assert(m_polygon.is_counter_clockwise());
|
||||
@@ -100,7 +99,12 @@ BuildVolume::BuildVolume(const std::vector<Vec2d> &printable_area, const double
|
||||
return;
|
||||
}
|
||||
|
||||
if ((extruder_shape == printable_area)&&(extruder_printable_heights[index] == printable_height)) {
|
||||
if (index >= extruder_printable_heights.size())
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("extruder_printable_height has only %1% entries but extruder_printable_area has %2%; using bed printable_height for extruder %3%")
|
||||
% extruder_printable_heights.size() % m_extruder_shapes.size() % index;
|
||||
const double extruder_height = index < extruder_printable_heights.size() ? extruder_printable_heights[index] : printable_height;
|
||||
|
||||
if ((extruder_shape == printable_area)&&(extruder_height == printable_height)) {
|
||||
extruder_volume.same_with_bed = true;
|
||||
extruder_volume.type = m_type;
|
||||
extruder_volume.bbox = m_bbox;
|
||||
@@ -113,7 +117,7 @@ BuildVolume::BuildVolume(const std::vector<Vec2d> &printable_area, const double
|
||||
double poly_area = poly.area();
|
||||
extruder_volume.bbox = get_extents(poly);
|
||||
BoundingBoxf temp_bboxf = get_extents(extruder_shape);
|
||||
extruder_volume.bboxf = BoundingBoxf3{ to_3d(temp_bboxf.min, 0.), to_3d(temp_bboxf.max, extruder_printable_heights[index]) };
|
||||
extruder_volume.bboxf = BoundingBoxf3{ to_3d(temp_bboxf.min, 0.), to_3d(temp_bboxf.max, extruder_height) };
|
||||
|
||||
if (extruder_shape.size() >= 4 && std::abs((poly_area - double(extruder_volume.bbox.size().x()) * double(extruder_volume.bbox.size().y()))) < sqr(SCALED_EPSILON))
|
||||
{
|
||||
|
||||
@@ -1812,17 +1812,31 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
|
||||
// to the end of the value.
|
||||
if (opt_base->type() == coBools && value.empty())
|
||||
static_cast<ConfigOptionBools*>(opt_base)->values.push_back(!no);
|
||||
else
|
||||
else {
|
||||
// Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way
|
||||
// they get deserialized from an .ini file. For ConfigOptionStrings, that means that the C-style unescape
|
||||
// will be applied for values enclosed in quotes, while values non-enclosed in quotes are left to be
|
||||
// unescaped by the calling shell.
|
||||
opt_vector->deserialize(value, true);
|
||||
bool deserialized = false;
|
||||
try {
|
||||
deserialized = opt_vector->deserialize(value, true);
|
||||
} catch (const std::exception &ex) {
|
||||
// e.g. "nil" deserialized into a non-nullable vector option throws instead of
|
||||
// returning false - treat that the same as any other invalid value here.
|
||||
deserialized = false;
|
||||
}
|
||||
if (! deserialized) {
|
||||
boost::nowide::cerr << "Invalid value for option --" << token.c_str() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (opt_base->type() == coBool) {
|
||||
if (value.empty())
|
||||
static_cast<ConfigOptionBool*>(opt_base)->value = !no;
|
||||
else
|
||||
opt_base->deserialize(value);
|
||||
else if (! opt_base->deserialize(value)) {
|
||||
boost::nowide::cerr << "Invalid value for option --" << token.c_str() << std::endl;
|
||||
return false;
|
||||
}
|
||||
} else if (opt_base->type() == coString) {
|
||||
// Do not unescape single string values, the unescaping is left to the calling shell.
|
||||
static_cast<ConfigOptionString*>(opt_base)->value = value;
|
||||
|
||||
@@ -55,9 +55,9 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
|
||||
boost::filesystem::path temp_mtl_path(mtl_file);
|
||||
mtl_path = temp_mtl_path;
|
||||
}
|
||||
auto _mtl_path = mtl_name_is_path ? mtl_abs_path.string().c_str() : mtl_path.string().c_str();
|
||||
const std::string _mtl_path = (mtl_name_is_path ? mtl_abs_path : mtl_path).string();
|
||||
if (boost::filesystem::exists(mtl_name_is_path ? mtl_abs_path : mtl_path)) {
|
||||
if (!ObjParser::mtlparse(_mtl_path, mtl_data)) {
|
||||
if (!ObjParser::mtlparse(_mtl_path.c_str(), mtl_data)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj:load_mtl: failed to parse " << _mtl_path;
|
||||
message = _L("load mtl in obj: failed to parse");
|
||||
return false;
|
||||
|
||||
@@ -111,14 +111,19 @@ bool StepPreProcessor::isUtf8File(const char* path)
|
||||
bool StepPreProcessor::isUtf8(const std::string str)
|
||||
{
|
||||
size_t num = 0;
|
||||
int i = 0;
|
||||
size_t i = 0;
|
||||
while (i < str.length()) {
|
||||
if ((str[i] & 0x80) == 0x00) {
|
||||
const unsigned char lead = static_cast<unsigned char>(str[i]);
|
||||
if ((lead & 0x80) == 0x00) {
|
||||
i++;
|
||||
} else if ((num = preNum(str[i])) > 2) {
|
||||
// preNum() counts the leading 1 bits, and a multi-byte sequence is 2 to 4
|
||||
// bytes long, so anything outside that range is not a lead byte.
|
||||
} else if ((num = preNum(lead)) >= 2 && num <= 4) {
|
||||
if (i + num > str.length())
|
||||
return false;
|
||||
i++;
|
||||
for (int j = 0; j < num - 1; j++) {
|
||||
if ((str[i] & 0xc0) != 0x80)
|
||||
for (size_t j = 0; j < num - 1; j++) {
|
||||
if ((static_cast<unsigned char>(str[i]) & 0xc0) != 0x80)
|
||||
return false;
|
||||
i++;
|
||||
}
|
||||
@@ -132,15 +137,20 @@ bool StepPreProcessor::isUtf8(const std::string str)
|
||||
bool StepPreProcessor::isGBK(const std::string str) {
|
||||
size_t i = 0;
|
||||
while (i < str.length()) {
|
||||
if (str[i] <= 0x7f) {
|
||||
// char is signed here, so every byte compares <= 0x7f unless widened first.
|
||||
const unsigned char lead = static_cast<unsigned char>(str[i]);
|
||||
if (lead <= 0x7f) {
|
||||
i++;
|
||||
continue;
|
||||
} else {
|
||||
if (str[i] >= 0x81 &&
|
||||
str[i] <= 0xfe &&
|
||||
str[i + 1] >= 0x40 &&
|
||||
str[i + 1] <= 0xfe &&
|
||||
str[i + 1] != 0xf7) {
|
||||
if (i + 1 >= str.length())
|
||||
return false;
|
||||
const unsigned char trail = static_cast<unsigned char>(str[i + 1]);
|
||||
if (lead >= 0x81 &&
|
||||
lead <= 0xfe &&
|
||||
trail >= 0x40 &&
|
||||
trail <= 0xfe &&
|
||||
trail != 0xf7) {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -11948,13 +11948,11 @@ CLIActionsConfigDef::CLIActionsConfigDef()
|
||||
def = this->add("load_defaultfila", coBool);
|
||||
def->label = L("Load default filaments");
|
||||
def->tooltip = L("Load first filament as default for those not loaded.");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("min_save", coBool);
|
||||
def->label = L("Minimum save");
|
||||
def->tooltip = L("Export 3MF with minimum size.");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("mtcpp", coInt);
|
||||
@@ -11980,7 +11978,6 @@ CLIActionsConfigDef::CLIActionsConfigDef()
|
||||
def = this->add("normative_check", coBool);
|
||||
def->label = L("Normative check");
|
||||
def->tooltip = L("Check the normative items.");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
/*def = this->add("help_fff", coBool);
|
||||
@@ -12247,7 +12244,7 @@ CLIMiscConfigDef::CLIMiscConfigDef()
|
||||
def->cli_params = "level";
|
||||
def->set_default_value(new ConfigOptionInt(1));
|
||||
|
||||
def = this->add("logfile", coInt);
|
||||
def = this->add("logfile", coString);
|
||||
def->label = L("Log file");
|
||||
def->tooltip = L("Redirects debug logging to file.\n");
|
||||
def->cli_params = "file";
|
||||
@@ -12295,7 +12292,6 @@ CLIMiscConfigDef::CLIMiscConfigDef()
|
||||
def = this->add("skip_modified_gcodes", coBool);
|
||||
def->label = L("Skip modified G-code in 3MF");
|
||||
def->tooltip = L("Skip the modified G-code in 3MF from printer or filament presets.");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("makerlab_name", coString);
|
||||
@@ -12325,14 +12321,12 @@ CLIMiscConfigDef::CLIMiscConfigDef()
|
||||
def = this->add("allow_newer_file", coBool);
|
||||
def->label = L("Allow 3MF with newer version to be sliced");
|
||||
def->tooltip = L("Allow 3MF with newer version to be sliced.");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("allow_mix_temp", coBool);
|
||||
// internal use only, don't need translation
|
||||
def->label = "Allow filaments with high/low temperature to be printed together";
|
||||
def->tooltip = "Allow filaments with high/low temperature to be printed together.";
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
}
|
||||
|
||||
|
||||
@@ -310,7 +310,11 @@ void set_data_dir(const std::string &dir)
|
||||
{
|
||||
g_data_dir = dir;
|
||||
if (!g_data_dir.empty() && !boost::filesystem::exists(g_data_dir)) {
|
||||
boost::filesystem::create_directory(g_data_dir);
|
||||
try {
|
||||
boost::filesystem::create_directories(g_data_dir);
|
||||
} catch (const boost::filesystem::filesystem_error &ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "set_data_dir: failed to create data directory " << g_data_dir << ": " << ex.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1757,26 +1757,25 @@ std::vector<int> PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D
|
||||
else
|
||||
obj_support = glb_support;
|
||||
|
||||
if (!obj_support)
|
||||
continue;
|
||||
if (obj_support) {
|
||||
int obj_support_intf_extr = 0;
|
||||
const ConfigOption* support_intf_extr_opt = object->config.option("support_interface_filament");
|
||||
if (support_intf_extr_opt != nullptr)
|
||||
obj_support_intf_extr = support_intf_extr_opt->getInt();
|
||||
if (obj_support_intf_extr != 0)
|
||||
plate_extruders.push_back(obj_support_intf_extr);
|
||||
else if (glb_support_intf_extr != 0)
|
||||
plate_extruders.push_back(glb_support_intf_extr);
|
||||
|
||||
int obj_support_intf_extr = 0;
|
||||
const ConfigOption* support_intf_extr_opt = object->config.option("support_interface_filament");
|
||||
if (support_intf_extr_opt != nullptr)
|
||||
obj_support_intf_extr = support_intf_extr_opt->getInt();
|
||||
if (obj_support_intf_extr != 0)
|
||||
plate_extruders.push_back(obj_support_intf_extr);
|
||||
else if (glb_support_intf_extr != 0)
|
||||
plate_extruders.push_back(glb_support_intf_extr);
|
||||
|
||||
int obj_support_extr = 0;
|
||||
const ConfigOption* support_extr_opt = object->config.option("support_filament");
|
||||
if (support_extr_opt != nullptr)
|
||||
obj_support_extr = support_extr_opt->getInt();
|
||||
if (obj_support_extr != 0)
|
||||
plate_extruders.push_back(obj_support_extr);
|
||||
else if (glb_support_extr != 0)
|
||||
plate_extruders.push_back(glb_support_extr);
|
||||
int obj_support_extr = 0;
|
||||
const ConfigOption* support_extr_opt = object->config.option("support_filament");
|
||||
if (support_extr_opt != nullptr)
|
||||
obj_support_extr = support_extr_opt->getInt();
|
||||
if (obj_support_extr != 0)
|
||||
plate_extruders.push_back(obj_support_extr);
|
||||
else if (glb_support_extr != 0)
|
||||
plate_extruders.push_back(glb_support_extr);
|
||||
}
|
||||
|
||||
int obj_outer_wall_extr = 0;
|
||||
if (const ConfigOption* wall_opt = object->config.option("outer_wall_filament_id"); wall_opt != nullptr)
|
||||
@@ -2771,19 +2770,16 @@ bool PartPlate::check_outside(int obj_id, int instance_id, BoundingBoxf3* boundi
|
||||
plate_box.min.z() += instance_box.min.z(); // not considering outsize if sinking
|
||||
|
||||
if (instance_box.min.z() < SINKING_Z_THRESHOLD) {
|
||||
// Orca: For sinking object, we use a more expensive algorithm so part below build plate won't be considered
|
||||
// m_plater is null in CLI mode.
|
||||
if (m_plater && plate_box.intersects(instance_box)) {
|
||||
// TODO: FIXME: this does not take exclusion area into account
|
||||
const BuildVolume build_volume(get_shape(), m_plater->build_volume().printable_height(), m_extruder_areas, m_extruder_heights);
|
||||
const auto state = instance->calc_print_volume_state(build_volume);
|
||||
outside = state == ModelInstancePVS_Partly_Outside;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (plate_box.contains(instance_box))
|
||||
{
|
||||
if (m_exclude_bounding_box.size() > 0)
|
||||
// For sinking object, we use a more expensive algorithm so part below build plate won't be considered.
|
||||
// m_height mirrors the printer's printable height independent of m_plater, so this runs in CLI too.
|
||||
if (plate_box.intersects(instance_box)) {
|
||||
// TODO: FIXME: this does not take exclusion area into account
|
||||
const BuildVolume build_volume(get_shape(), m_height, m_extruder_areas, m_extruder_heights);
|
||||
const auto state = instance->calc_print_volume_state(build_volume);
|
||||
outside = state == ModelInstancePVS_Partly_Outside;
|
||||
}
|
||||
} else if (plate_box.contains(instance_box)) {
|
||||
if (m_exclude_bounding_box.size() > 0)
|
||||
{
|
||||
Polygon hull = instance->convex_hull_2d();
|
||||
int index;
|
||||
@@ -2801,9 +2797,9 @@ bool PartPlate::check_outside(int obj_id, int instance_id, BoundingBoxf3* boundi
|
||||
}
|
||||
else
|
||||
outside = false;
|
||||
}
|
||||
}
|
||||
|
||||
return outside;
|
||||
return outside;
|
||||
}
|
||||
|
||||
//judge whether instance is intesected with plate or not
|
||||
@@ -6470,7 +6466,7 @@ int PartPlateList::store_to_3mf_structure(PlateDataPtrs& plate_data_list, bool w
|
||||
plate_data_item->filament_change_sequence = m_plate_list[i]->m_gcode_result->filament_change_sequence;
|
||||
plate_data_item->nozzle_change_sequence = m_plate_list[i]->m_gcode_result->nozzle_change_sequence;
|
||||
plate_data_item->optimal_assignment = m_plate_list[i]->m_gcode_result->optimal_assignment;
|
||||
plate_data_item->first_layer_time = std::to_string(m_plate_list[i]->cali_bboxes_data.first_layer_time);
|
||||
plate_data_item->first_layer_time = std::to_string(m_plate_list[i]->m_gcode_result->initial_layer_time);
|
||||
Print *print = nullptr;
|
||||
m_plate_list[i]->get_print((PrintBase **) &print, nullptr, nullptr);
|
||||
if (print) {
|
||||
|
||||
@@ -681,7 +681,7 @@ SearchDialog::SearchDialog(OptionsSearcher *searcher, Preset::Type type, wxWindo
|
||||
|
||||
SearchDialog::~SearchDialog() {}
|
||||
|
||||
void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
|
||||
void SearchDialog::Popup(wxWindow *focus /*= nullptr*/)
|
||||
{
|
||||
/* const std::string& line = searcher->search_string();
|
||||
search_line->SetValue(line.empty() ? default_string : from_u8(line));
|
||||
@@ -696,17 +696,19 @@ void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
|
||||
search_line2->SetValue(wxString(""));
|
||||
//const std::string &line = searcher->search_string();
|
||||
//searcher->search(into_u8(line), true);
|
||||
PopupWindow::Popup();
|
||||
PopupWindow::Popup(focus);
|
||||
search_line2->SetFocus();
|
||||
update_list();
|
||||
}
|
||||
|
||||
|
||||
#ifdef __WXMSW__
|
||||
void SearchDialog::MSWDismissUnfocusedPopup()
|
||||
{
|
||||
Dismiss();
|
||||
OnDismiss();
|
||||
}
|
||||
#endif // __WXMSW__
|
||||
|
||||
void SearchDialog::OnDismiss() { }
|
||||
|
||||
@@ -926,7 +928,7 @@ SearchObjectDialog::SearchObjectDialog(GUI::ObjectList* object_list, wxWindow* p
|
||||
|
||||
SearchObjectDialog::~SearchObjectDialog() {}
|
||||
|
||||
void SearchObjectDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
|
||||
void SearchObjectDialog::Popup(wxWindow *focus /*= nullptr*/)
|
||||
{
|
||||
if (m_is_dismissing || this->IsShown()) {
|
||||
return;
|
||||
@@ -937,7 +939,7 @@ void SearchObjectDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
|
||||
// dropdown list, otherwise the text input won't be usable
|
||||
m_object_list->SetFocus();
|
||||
#endif
|
||||
PopupWindow::Popup();
|
||||
PopupWindow::Popup(focus);
|
||||
search_line2->SetFocus();
|
||||
|
||||
m_object_list->assembly_plate_object_name();
|
||||
@@ -945,11 +947,13 @@ void SearchObjectDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
|
||||
update_list();
|
||||
}
|
||||
|
||||
#ifdef __WXMSW__
|
||||
void SearchObjectDialog::MSWDismissUnfocusedPopup()
|
||||
{
|
||||
Dismiss();
|
||||
OnDismiss();
|
||||
}
|
||||
#endif // __WXMSW__
|
||||
|
||||
void SearchObjectDialog::OnDismiss() {}
|
||||
|
||||
|
||||
@@ -216,10 +216,12 @@ public:
|
||||
SearchDialog(OptionsSearcher *searcher, Preset::Type type, wxWindow *parent, TextInput *input, wxWindow *search_btn);
|
||||
~SearchDialog();
|
||||
|
||||
void MSWDismissUnfocusedPopup();
|
||||
void Popup(wxPoint position = wxDefaultPosition);
|
||||
void OnDismiss();
|
||||
void Dismiss();
|
||||
#ifdef __WXMSW__
|
||||
void MSWDismissUnfocusedPopup() override;
|
||||
#endif // __WXMSW__
|
||||
void Popup(wxWindow *focus = nullptr) override;
|
||||
void OnDismiss() override;
|
||||
void Dismiss() override;
|
||||
void Die();
|
||||
void msw_rescale();
|
||||
|
||||
@@ -260,10 +262,12 @@ public:
|
||||
SearchObjectDialog(GUI::ObjectList* object_list, wxWindow* parent, TextInput* input);
|
||||
~SearchObjectDialog();
|
||||
|
||||
void MSWDismissUnfocusedPopup();
|
||||
void Popup(wxPoint position = wxDefaultPosition);
|
||||
void OnDismiss();
|
||||
void Dismiss();
|
||||
#ifdef __WXMSW__
|
||||
void MSWDismissUnfocusedPopup() override;
|
||||
#endif // __WXMSW__
|
||||
void Popup(wxWindow *focus = nullptr) override;
|
||||
void OnDismiss() override;
|
||||
void Dismiss() override;
|
||||
void Die();
|
||||
|
||||
void OnInputText(wxCommandEvent& event);
|
||||
|
||||
@@ -98,7 +98,7 @@ void LabeledStaticBox::SetBorderColor(StateColor const &color)
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void LabeledStaticBox::SetFont(wxFont set_font)
|
||||
bool LabeledStaticBox::SetFont(const wxFont &set_font)
|
||||
{
|
||||
m_font = set_font;
|
||||
|
||||
@@ -109,6 +109,7 @@ void LabeledStaticBox::SetFont(wxFont set_font)
|
||||
m_label_width = tW;
|
||||
|
||||
Refresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LabeledStaticBox::Enable(bool enable)
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
|
||||
void SetBorderColor(StateColor const &color);
|
||||
|
||||
void SetFont(wxFont set_font);
|
||||
bool SetFont(const wxFont &set_font) override;
|
||||
|
||||
bool Enable(bool enable) override;
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ ScrolledWindow::ScrolledWindow(wxWindow *parent, wxWindowID id, wxPoint position
|
||||
m_bottomScrollbar = NULL;
|
||||
m_verticalSplitter = NULL;
|
||||
m_horizontalSplitter = NULL;
|
||||
m_userPanel = NULL;
|
||||
m_scroll_win = NULL;
|
||||
|
||||
m_marginWidth = marginWidth;
|
||||
|
||||
@@ -110,12 +112,13 @@ void ScrolledWindow::SetTipColor(wxColour color)
|
||||
if (m_bottomScrollbar) m_bottomScrollbar->SetTipColor(color);
|
||||
}
|
||||
|
||||
void ScrolledWindow::SetBackgroundColour(wxColour color)
|
||||
bool ScrolledWindow::SetBackgroundColour(const wxColour &color)
|
||||
{
|
||||
wxWindow::SetBackgroundColour(color);
|
||||
const bool result = wxWindow::SetBackgroundColour(color);
|
||||
m_verticalSplitter->SetBackgroundColour(color);
|
||||
m_userPanel->SetBackgroundColour(color);
|
||||
m_scroll_win->SetBackgroundColour(color);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ScrolledWindow::SetMarginColor(wxColour color)
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
ScrolledWindow(wxWindow *parent, wxWindowID id, wxPoint position, wxSize size, long style, int marginWidth = 0, int scrollbarWidth = 4, int tipLength = 0);
|
||||
void OnMouseWheel(wxMouseEvent &event);
|
||||
void SetTipColor(wxColour color);
|
||||
void SetBackgroundColour(wxColour color);
|
||||
bool SetBackgroundColour(const wxColour &color) override;
|
||||
|
||||
void SetMarginColor(wxColour color);
|
||||
void SetScrollbarColor(wxColour color);
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
// wxSplitterWindow* GetVerticalSplitter() { return m_verticalSplitter; }
|
||||
// wxSplitterWindow* GetHorizontalSplitter() { return m_horizontalSplitter; }
|
||||
bool IsBothDirections() { return m_bothDirections; }
|
||||
virtual void SetScrollbars(int pixelsPerUnitX, int pixelsPerUnitY, int noUnitsX, int noUnitsY, int xPos = 0, int yPos = 0, bool noRefresh = false);
|
||||
virtual void SetScrollbars(int pixelsPerUnitX, int pixelsPerUnitY, int noUnitsX, int noUnitsY, int xPos = 0, int yPos = 0, bool noRefresh = false) override;
|
||||
|
||||
private:
|
||||
wxPanel * m_userPanel; // the panel targeted by the scrolled window
|
||||
|
||||
@@ -631,7 +631,7 @@ void parse_metadata_rfc822(const std::string& content,
|
||||
bool is_ignored_plugin_directory(const boost::filesystem::path& path)
|
||||
{
|
||||
const std::string name = path.filename().string();
|
||||
return name.empty() || name[0] == '.' || name.rfind("__", 0) == 0 || name == PLUGIN_SUBSCRIBED_DIR;
|
||||
return name.empty() || name[0] == '.' || name.rfind("__", 0) == 0 || name == PLUGIN_SUBSCRIBED_DIR || name == PLUGIN_DATA_DIR;
|
||||
}
|
||||
|
||||
bool is_safe_relative_path(const boost::filesystem::path& path)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <vector>
|
||||
|
||||
#define PLUGIN_SUBSCRIBED_DIR "_subscribed"
|
||||
#define PLUGIN_DATA_DIR "plugin_data"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -522,6 +522,38 @@ bool PluginManager::try_get_plugin_descriptor_for_capability(const std::string&
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string PluginManager::get_storage_dir(const std::string& plugin_key) const
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
PluginDescriptor descriptor;
|
||||
if (!try_get_plugin_descriptor(plugin_key, descriptor))
|
||||
throw std::runtime_error("The current plugin is not registered");
|
||||
|
||||
const fs::path base_storage_dir = fs::path(get_orca_plugins_dir()) / PLUGIN_DATA_DIR;
|
||||
|
||||
if (!descriptor.is_cloud_plugin()) {
|
||||
const fs::path local_storage_dir = base_storage_dir / plugin_key;
|
||||
fs::create_directories(local_storage_dir);
|
||||
return local_storage_dir.string();
|
||||
}
|
||||
|
||||
auto agent = m_cloud_service.get_cloud_agent();
|
||||
if (!agent)
|
||||
throw std::runtime_error("Cloud plugin storage is unavailable before networking is initialized");
|
||||
|
||||
const std::string user_id = agent->get_user_id();
|
||||
if (user_id.empty())
|
||||
throw std::runtime_error("Cloud plugin storage is unavailable without a logged-in user");
|
||||
|
||||
if (!is_valid_plugin_id(plugin_key))
|
||||
throw std::runtime_error("The current cloud plugin key is not a valid folder name");
|
||||
|
||||
const fs::path cloud_storage_dir = base_storage_dir / PLUGIN_SUBSCRIBED_DIR / user_id / plugin_key;
|
||||
fs::create_directories(cloud_storage_dir);
|
||||
return cloud_storage_dir.string();
|
||||
}
|
||||
|
||||
// ── Capability instances ────────────────────────────────────────────────────────────────────
|
||||
|
||||
std::vector<std::shared_ptr<PluginCapabilityInterface>> PluginManager::get_plugin_capabilities(const std::string& plugin_key,
|
||||
|
||||
@@ -143,6 +143,10 @@ public:
|
||||
bool try_get_plugin_descriptor_for_capability(const std::string& capability_name,
|
||||
PluginCapabilityType type,
|
||||
PluginDescriptor& out) const;
|
||||
// Per-plugin storage directory under orca_plugins/plugin_data, created if missing. Throws
|
||||
// std::runtime_error if the plugin is unregistered, the key is invalid, or (cloud plugins)
|
||||
// no user is logged in yet.
|
||||
std::string get_storage_dir(const std::string& plugin_key) const;
|
||||
|
||||
std::vector<std::shared_ptr<PluginCapabilityInterface>> get_plugin_capabilities(
|
||||
const std::string& plugin_key = "", // "" => all plugins
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
#include "PluginHost.hpp"
|
||||
#include "PluginHostBindings.hpp"
|
||||
#include "PluginHostUi.hpp"
|
||||
#include <slic3r/plugin/PluginAuditManager.hpp>
|
||||
#include <slic3r/plugin/PluginManager.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace host_bindings {
|
||||
void register_plugin(pybind11::module_& host)
|
||||
{
|
||||
auto plugin_host = host.def_submodule("plugin", "Plugin host API");
|
||||
|
||||
plugin_host.def(
|
||||
"storage",
|
||||
[]() -> std::string {
|
||||
const std::string plugin_key = PluginAuditManager::instance().current_plugin();
|
||||
if (plugin_key.empty())
|
||||
throw std::runtime_error("plugin.storage() must be called from a plugin callback");
|
||||
|
||||
return PluginManager::instance().get_storage_dir(plugin_key);
|
||||
},
|
||||
"Return the installed folder of the current plugin.");
|
||||
}
|
||||
} // namespace host_bindings
|
||||
|
||||
void PluginHost::RegisterBindings(pybind11::module_& module)
|
||||
{
|
||||
auto host = module.def_submodule("host", "Host application API");
|
||||
@@ -15,6 +37,7 @@ void PluginHost::RegisterBindings(pybind11::module_& module)
|
||||
host_bindings::register_presets(host);
|
||||
host_bindings::register_model(host);
|
||||
host_bindings::register_app(host);
|
||||
host_bindings::register_plugin(host);
|
||||
|
||||
// UI: native dialogs and interactive HTML windows for plugins.
|
||||
PluginHostUi::RegisterBindings(host);
|
||||
|
||||
@@ -12,5 +12,5 @@ void register_presets(pybind11::module_& host); // PluginHostPresets.cpp
|
||||
void register_model(pybind11::module_& host); // PluginHostModel.cpp
|
||||
void register_app(pybind11::module_& host); // PluginHostApp.cpp
|
||||
void register_slicing(pybind11::module_& host); // PluginHostSlicing.cpp
|
||||
|
||||
void register_plugin(pybind11::module_& host); // PluginHost.cpp
|
||||
} // namespace Slic3r::host_bindings
|
||||
|
||||
1207
tests/data/utf8_part_names.step
Normal file
1207
tests/data/utf8_part_names.step
Normal file
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_mutable_polygon.cpp
|
||||
test_mutable_priority_queue.cpp
|
||||
test_nozzle_volume_type.cpp
|
||||
test_step.cpp
|
||||
test_stl.cpp
|
||||
test_triangle_selector.cpp
|
||||
test_meshboolean.cpp
|
||||
|
||||
93
tests/libslic3r/test_step.cpp
Normal file
93
tests/libslic3r/test_step.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Format/STEP.hpp"
|
||||
#include "test_utils.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
static void write_step_line(const std::string &path, const std::string &line)
|
||||
{
|
||||
boost::nowide::ofstream file(path, std::ios::binary);
|
||||
file << "ISO-10303-21;\n" << line << "\nEND-ISO-10303-21;\n";
|
||||
}
|
||||
|
||||
// preprocess() hands back the input path unless it transcoded into a temporary.
|
||||
static std::string preprocess_result(const std::string &line)
|
||||
{
|
||||
ScopedSlic3rTemporaryDir scratch;
|
||||
|
||||
ScopedTemporaryFile step(".step");
|
||||
write_step_line(step.string(), line);
|
||||
|
||||
std::string output_path;
|
||||
StepPreProcessor preprocessor;
|
||||
REQUIRE(preprocessor.preprocess(step.string().c_str(), output_path));
|
||||
|
||||
return output_path == step.string() ? "untouched" : "transcoded";
|
||||
}
|
||||
|
||||
// data/utf8_part_names.step is three boxes written by OCCT's own STEP writer, whose
|
||||
// PRODUCT names were then patched to raw UTF-8. Most CAD exporters write non-ASCII names
|
||||
// that way rather than in the \X2\ escape form. The third part is ASCII, as a control.
|
||||
TEST_CASE("Part names with multi-byte UTF-8 survive import", "[Step]")
|
||||
{
|
||||
// getNamedSolids() replaces a name that isUtf8() rejects with a running number.
|
||||
const std::string path = TEST_DATA_DIR PATH_SEPARATOR "utf8_part_names.step";
|
||||
|
||||
Model model;
|
||||
bool cancel = false;
|
||||
Step step(path); // no isUtf8Fn, matching how Model::read_from_step builds it
|
||||
|
||||
REQUIRE(step.load() == Step::Step_Status::LOAD_SUCCESS);
|
||||
REQUIRE(step.mesh(&model, cancel, false) == Step::Step_Status::MESH_SUCCESS);
|
||||
|
||||
REQUIRE(model.objects.size() == 1);
|
||||
const ModelObject *object = model.objects.front();
|
||||
REQUIRE(object->volumes.size() == 3);
|
||||
// "ce" is split off, or the hex escape would swallow it as further hex digits.
|
||||
CHECK(object->volumes[0]->name == "pi\xC3\xA8" "ce");
|
||||
CHECK(object->volumes[1]->name == "Geh\xC3\xA4use");
|
||||
CHECK(object->volumes[2]->name == "bracket");
|
||||
}
|
||||
|
||||
TEST_CASE("isUtf8 recognises two, three and four byte sequences", "[Step]")
|
||||
{
|
||||
CHECK(StepPreProcessor::isUtf8("\xC3\xA9")); // U+00E9
|
||||
CHECK(StepPreProcessor::isUtf8("\xE4\xB8\xAD")); // U+4E2D
|
||||
CHECK(StepPreProcessor::isUtf8("\xF0\x9F\x94\xA9")); // U+1F529
|
||||
CHECK_FALSE(StepPreProcessor::isUtf8("\x81\x30")); // 0x81 is not a lead byte
|
||||
CHECK_FALSE(StepPreProcessor::isUtf8("\xC3")); // truncated sequence
|
||||
}
|
||||
|
||||
// The only caller of isGBK is preprocess(), which nothing calls today.
|
||||
TEST_CASE("Encoding detection decides whether a step file is transcoded", "[Step]")
|
||||
{
|
||||
SECTION("UTF-8, so left alone")
|
||||
{
|
||||
// A two byte sequence also satisfies every GBK range, so misdetecting it as
|
||||
// not-UTF-8 sends it to be transcoded.
|
||||
const std::string sequence = GENERATE(std::string("\xC3\xA9"), // U+00E9
|
||||
std::string("\xE4\xB8\xAD"), // U+4E2D
|
||||
std::string("\xF0\x9F\x94\xA9")); // U+1F529
|
||||
|
||||
CHECK(preprocess_result("NAME('" + sequence + "');") == "untouched");
|
||||
}
|
||||
|
||||
SECTION("neither UTF-8 nor GBK, so left alone")
|
||||
{
|
||||
// 0x81 is not a UTF-8 lead byte, and 0x30 is below the 0x40 floor for a GBK trail.
|
||||
CHECK(preprocess_result("NAME('\x81\x30');") == "untouched");
|
||||
}
|
||||
|
||||
SECTION("GBK, so transcoded")
|
||||
{
|
||||
// U+554A in GBK, whose lead byte is not valid UTF-8. Pins the other direction,
|
||||
// since a detector that never reports GBK would pass every case above.
|
||||
CHECK(preprocess_result("NAME('\xB0\xA1');") == "transcoded");
|
||||
}
|
||||
|
||||
SECTION("plain ASCII, so left alone") { CHECK(preprocess_result("NAME('bracket');") == "untouched"); }
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/Format/OBJ.hpp>
|
||||
#include <libslic3r/SVG.hpp>
|
||||
#include <libslic3r/Utils.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
@@ -32,7 +33,7 @@ inline Slic3r::TriangleMesh load_model(const std::string &obj_filename)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Owns a unique path under the system temp dir, "<prefix>-<unique>[<extension>]"
|
||||
// (parallel-safe, cross-platform). Shared base for the two RAII temp guards below.
|
||||
// (parallel-safe, cross-platform). Shared base for the RAII temp guards below.
|
||||
class ScopedTemporaryPath
|
||||
{
|
||||
public:
|
||||
@@ -70,6 +71,24 @@ public:
|
||||
~ScopedTemporaryDir() { boost::system::error_code ec; boost::filesystem::remove_all(m_path, ec); }
|
||||
};
|
||||
|
||||
// A temp directory that is also Slic3r::temporary_dir() for its lifetime. No test
|
||||
// process sets that global, so code under test which writes there (for example
|
||||
// StepPreProcessor::preprocess) lands at the filesystem root. Restored on scope exit
|
||||
// even when an assertion throws, so it cannot leak into later tests.
|
||||
class ScopedSlic3rTemporaryDir : public ScopedTemporaryDir
|
||||
{
|
||||
public:
|
||||
explicit ScopedSlic3rTemporaryDir(const std::string &prefix = "orca")
|
||||
: ScopedTemporaryDir(prefix), m_previous(Slic3r::temporary_dir())
|
||||
{ Slic3r::set_temporary_dir(string()); }
|
||||
// Runs before ~ScopedTemporaryDir, so the setting goes back while the directory
|
||||
// it names still exists.
|
||||
~ScopedSlic3rTemporaryDir() { Slic3r::set_temporary_dir(m_previous); }
|
||||
|
||||
private:
|
||||
const std::string m_previous;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Debug-only test artifacts
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user