mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-12 07:09:05 +08:00
Fixed conflicts after merge with master
This commit is contained in:
commit
fee31084bd
@ -139,7 +139,6 @@ sub mesh {
|
||||
|
||||
my $mesh = Slic3r::TriangleMesh->new;
|
||||
$mesh->ReadFromPerl($vertices, $facets);
|
||||
$mesh->repair;
|
||||
$mesh->scale_xyz(Slic3r::Pointf3->new(@{$params{scale_xyz}})) if $params{scale_xyz};
|
||||
$mesh->translate(@{$params{translate}}) if $params{translate};
|
||||
return $mesh;
|
||||
|
17
resources/icons/exclamation_manifold.svg
Normal file
17
resources/icons/exclamation_manifold.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#808080" d="M8,11c-0.55,0-1-0.45-1-1V7c0-0.55,0.45-1,1-1s1,0.45,1,1v3C9,10.55,8.55,11,8,11z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle fill="#808080" cx="8" cy="13" r="1"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#808080" d="M15,15.5H1c-0.18,0-0.34-0.09-0.43-0.24c-0.09-0.15-0.09-0.34-0.01-0.49l7-13c0.17-0.32,0.71-0.32,0.88,0
|
||||
l7,13c0.08,0.16,0.08,0.34-0.01,0.49C15.34,15.41,15.18,15.5,15,15.5z M1.84,14.5h12.33L8,3.05L1.84,14.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 781 B |
17
resources/icons/white/exclamation_manifold.svg
Normal file
17
resources/icons/white/exclamation_manifold.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M8,11c-0.55,0-1-0.45-1-1V7c0-0.55,0.45-1,1-1s1,0.45,1,1v3C9,10.55,8.55,11,8,11z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle fill="#FFFFFF" cx="8" cy="13" r="1"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M15,15.5H1c-0.18,0-0.34-0.09-0.43-0.24c-0.09-0.15-0.09-0.34-0.01-0.49l7-13c0.17-0.32,0.71-0.32,0.88,0
|
||||
l7,13c0.08,0.16,0.08,0.34-0.01,0.49C15.34,15.41,15.18,15.5,15,15.5z M1.84,14.5h12.33L8,3.05L1.84,14.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 781 B |
@ -212,8 +212,7 @@ int main(const int argc, const char *argv[])
|
||||
return -1;
|
||||
}
|
||||
|
||||
mesh.repair();
|
||||
if (mesh.facets_count() == 0) {
|
||||
if (mesh.empty()) {
|
||||
std::cerr << "Error loading " << argv[1] << " . It is empty." << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ int main(const int argc, const char * argv[])
|
||||
TriangleMesh input;
|
||||
|
||||
input.ReadSTLFile(argv[1]);
|
||||
input.repair();
|
||||
|
||||
Benchmark bench;
|
||||
|
||||
|
@ -409,7 +409,6 @@ void CSGDisplay::on_scene_updated(const Scene &scene)
|
||||
interior.transform(po->trafo().inverse());
|
||||
|
||||
mshinst.merge(interior);
|
||||
mshinst.require_shared_vertices();
|
||||
|
||||
mi->transform_mesh(&mshinst);
|
||||
|
||||
@ -417,14 +416,12 @@ void CSGDisplay::on_scene_updated(const Scene &scene)
|
||||
auto center = bb.center().cast<float>();
|
||||
mshinst.translate(-center);
|
||||
|
||||
mshinst.require_shared_vertices();
|
||||
m_scene_cache.add_mesh(mshinst, OpenCSG::Intersection,
|
||||
m_csgsettings.get_convexity());
|
||||
}
|
||||
|
||||
for (const sla::DrainHole &holept : holedata) {
|
||||
TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh());
|
||||
holemesh.require_shared_vertices();
|
||||
m_scene_cache.add_mesh(holemesh, OpenCSG::Subtraction, 1);
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,6 @@ void ShaderCSGDisplay::on_scene_updated(const Scene &scene)
|
||||
interior.transform(po->trafo().inverse());
|
||||
|
||||
mshinst.merge(interior);
|
||||
mshinst.require_shared_vertices();
|
||||
|
||||
mi->transform_mesh(&mshinst);
|
||||
|
||||
@ -51,15 +50,11 @@ void ShaderCSGDisplay::on_scene_updated(const Scene &scene)
|
||||
auto center = bb.center().cast<float>();
|
||||
mshinst.translate(-center);
|
||||
|
||||
mshinst.require_shared_vertices();
|
||||
add_mesh(mshinst);
|
||||
}
|
||||
|
||||
for (const sla::DrainHole &holept : holedata) {
|
||||
TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh());
|
||||
holemesh.require_shared_vertices();
|
||||
add_mesh(holemesh);
|
||||
}
|
||||
for (const sla::DrainHole &holept : holedata)
|
||||
add_mesh(sla::to_triangle_mesh(holept.to_mesh()));
|
||||
}
|
||||
|
||||
repaint();
|
||||
|
@ -397,7 +397,7 @@ int CLI::run(int argc, char **argv)
|
||||
TriangleMesh mesh = model.mesh();
|
||||
mesh.repair();
|
||||
|
||||
TriangleMeshPtrs meshes = mesh.cut_by_grid(m_config.option<ConfigOptionPoint>("cut_grid")->value);
|
||||
std::vector<TriangleMesh> meshes = mesh.cut_by_grid(m_config.option<ConfigOptionPoint>("cut_grid")->value);
|
||||
size_t i = 0;
|
||||
for (TriangleMesh* m : meshes) {
|
||||
Model out;
|
||||
|
@ -239,6 +239,7 @@ private:
|
||||
return edge_a.facet_number != edge_b.facet_number && edge_a == edge_b;
|
||||
}
|
||||
|
||||
// Connect edge_a with edge_b, update edge connection statistics.
|
||||
static void record_neighbors(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b)
|
||||
{
|
||||
// Facet a's neighbor is facet b
|
||||
@ -249,7 +250,7 @@ private:
|
||||
stl->neighbors_start[edge_b.facet_number].neighbor[edge_b.which_edge % 3] = edge_a.facet_number; /* sets the .neighbor part */
|
||||
stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] = (edge_a.which_edge + 2) % 3; /* sets the .which_vertex_not part */
|
||||
|
||||
if (((edge_a.which_edge < 3) && (edge_b.which_edge < 3)) || ((edge_a.which_edge > 2) && (edge_b.which_edge > 2))) {
|
||||
if ((edge_a.which_edge < 3 && edge_b.which_edge < 3) || (edge_a.which_edge > 2 && edge_b.which_edge > 2)) {
|
||||
// These facets are oriented in opposite directions, their normals are probably messed up.
|
||||
stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] += 3;
|
||||
stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] += 3;
|
||||
@ -479,12 +480,13 @@ void stl_check_facets_exact(stl_file *stl)
|
||||
|
||||
void stl_check_facets_nearby(stl_file *stl, float tolerance)
|
||||
{
|
||||
if ( (stl->stats.connected_facets_1_edge == stl->stats.number_of_facets)
|
||||
&& (stl->stats.connected_facets_2_edge == stl->stats.number_of_facets)
|
||||
&& (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets)) {
|
||||
assert(stl->stats.connected_facets_3_edge <= stl->stats.connected_facets_2_edge);
|
||||
assert(stl->stats.connected_facets_2_edge <= stl->stats.connected_facets_1_edge);
|
||||
assert(stl->stats.connected_facets_1_edge <= stl->stats.number_of_facets);
|
||||
|
||||
if (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets)
|
||||
// No need to check any further. All facets are connected.
|
||||
return;
|
||||
}
|
||||
|
||||
HashTableEdges hash_table(stl->stats.number_of_facets);
|
||||
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
|
||||
@ -514,22 +516,12 @@ void stl_remove_unconnected_facets(stl_file *stl)
|
||||
/* Update list of connected edges */
|
||||
stl_neighbors &neighbors = stl->neighbors_start[facet_number];
|
||||
// Update statistics on unconnected triangle edges.
|
||||
switch ((neighbors.neighbor[0] == -1) + (neighbors.neighbor[1] == -1) + (neighbors.neighbor[2] == -1)) {
|
||||
case 0: // Facet has 3 neighbors
|
||||
-- stl->stats.connected_facets_3_edge;
|
||||
-- stl->stats.connected_facets_2_edge;
|
||||
-- stl->stats.connected_facets_1_edge;
|
||||
break;
|
||||
case 1: // Facet has 2 neighbors
|
||||
-- stl->stats.connected_facets_2_edge;
|
||||
-- stl->stats.connected_facets_1_edge;
|
||||
break;
|
||||
case 2: // Facet has 1 neighbor
|
||||
-- stl->stats.connected_facets_1_edge;
|
||||
case 3: // Facet has 0 neighbors
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
switch (neighbors.num_neighbors()) {
|
||||
case 3: -- stl->stats.connected_facets_3_edge; // fall through
|
||||
case 2: -- stl->stats.connected_facets_2_edge; // fall through
|
||||
case 1: -- stl->stats.connected_facets_1_edge; // fall through
|
||||
case 0: break;
|
||||
default: assert(false);
|
||||
}
|
||||
|
||||
if (facet_number < int(-- stl->stats.number_of_facets)) {
|
||||
@ -555,20 +547,14 @@ void stl_remove_unconnected_facets(stl_file *stl)
|
||||
|
||||
auto remove_degenerate = [stl, remove_facet](int facet)
|
||||
{
|
||||
// Update statistics on face connectivity.
|
||||
auto stl_update_connects_remove_1 = [stl](int facet_num) {
|
||||
//FIXME when decreasing 3_edge, should I increase 2_edge etc?
|
||||
switch ((stl->neighbors_start[facet_num].neighbor[0] == -1) + (stl->neighbors_start[facet_num].neighbor[1] == -1) + (stl->neighbors_start[facet_num].neighbor[2] == -1)) {
|
||||
case 0: // Facet has 3 neighbors
|
||||
-- stl->stats.connected_facets_3_edge; break;
|
||||
case 1: // Facet has 2 neighbors
|
||||
-- stl->stats.connected_facets_2_edge; break;
|
||||
case 2: // Facet has 1 neighbor
|
||||
-- stl->stats.connected_facets_1_edge; break;
|
||||
case 3: // Facet has 0 neighbors
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
// Update statistics on face connectivity after one edge was disconnected on the facet "facet_num".
|
||||
auto update_connects_remove_1 = [stl](int facet_num) {
|
||||
switch (stl->neighbors_start[facet_num].num_neighbors()) {
|
||||
case 0: assert(false); break;
|
||||
case 1: -- stl->stats.connected_facets_1_edge; break;
|
||||
case 2: -- stl->stats.connected_facets_2_edge; break;
|
||||
case 3: -- stl->stats.connected_facets_3_edge; break;
|
||||
default: assert(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -604,9 +590,9 @@ void stl_remove_unconnected_facets(stl_file *stl)
|
||||
|
||||
// Update statistics on edge connectivity.
|
||||
if ((neighbor[0] == -1) && (neighbor[1] != -1))
|
||||
stl_update_connects_remove_1(neighbor[1]);
|
||||
update_connects_remove_1(neighbor[1]);
|
||||
if ((neighbor[1] == -1) && (neighbor[0] != -1))
|
||||
stl_update_connects_remove_1(neighbor[0]);
|
||||
update_connects_remove_1(neighbor[0]);
|
||||
|
||||
if (neighbor[0] >= 0) {
|
||||
if (neighbor[1] >= 0) {
|
||||
@ -634,7 +620,7 @@ void stl_remove_unconnected_facets(stl_file *stl)
|
||||
stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = vnot[0];
|
||||
}
|
||||
if (neighbor[2] >= 0) {
|
||||
stl_update_connects_remove_1(neighbor[2]);
|
||||
update_connects_remove_1(neighbor[2]);
|
||||
stl->neighbors_start[neighbor[2]].neighbor[(vnot[2] + 1) % 3] = -1;
|
||||
}
|
||||
|
||||
@ -652,11 +638,9 @@ void stl_remove_unconnected_facets(stl_file *stl)
|
||||
++ i;
|
||||
|
||||
if (stl->stats.connected_facets_1_edge < (int)stl->stats.number_of_facets) {
|
||||
// remove completely unconnected facets
|
||||
// There are some faces with no connected edge at all. Remove completely unconnected facets.
|
||||
for (uint32_t i = 0; i < stl->stats.number_of_facets;)
|
||||
if (stl->neighbors_start[i].neighbor[0] == -1 &&
|
||||
stl->neighbors_start[i].neighbor[1] == -1 &&
|
||||
stl->neighbors_start[i].neighbor[2] == -1) {
|
||||
if (stl->neighbors_start[i].num_neighbors() == 0) {
|
||||
// This facet is completely unconnected. Remove it.
|
||||
remove_facet(i);
|
||||
assert(stl_validate(stl));
|
||||
|
@ -79,8 +79,7 @@ struct stl_neighbors {
|
||||
which_vertex_not[1] = -1;
|
||||
which_vertex_not[2] = -1;
|
||||
}
|
||||
int num_neighbors_missing() const { return (this->neighbor[0] == -1) + (this->neighbor[1] == -1) + (this->neighbor[2] == -1); }
|
||||
int num_neighbors() const { return 3 - this->num_neighbors_missing(); }
|
||||
int num_neighbors() const { return 3 - ((this->neighbor[0] == -1) + (this->neighbor[1] == -1) + (this->neighbor[2] == -1)); }
|
||||
|
||||
// Index of a neighbor facet.
|
||||
int neighbor[3];
|
||||
@ -92,28 +91,44 @@ struct stl_stats {
|
||||
stl_stats() { memset(&header, 0, 81); }
|
||||
char header[81];
|
||||
stl_type type = (stl_type)0;
|
||||
// Should always match the number of facets stored inside stl_file::facet_start.
|
||||
uint32_t number_of_facets = 0;
|
||||
// Bounding box.
|
||||
stl_vertex max = stl_vertex::Zero();
|
||||
stl_vertex min = stl_vertex::Zero();
|
||||
stl_vertex size = stl_vertex::Zero();
|
||||
float bounding_diameter = 0.f;
|
||||
float shortest_edge = 0.f;
|
||||
// After repair, the volume shall always be positive.
|
||||
float volume = -1.f;
|
||||
// Number of face edges connected to another face.
|
||||
// Don't use this statistics after repair, use the connected_facets_1/2/3_edge instead!
|
||||
int connected_edges = 0;
|
||||
// Faces with >=1, >=2 and 3 edges connected to another face.
|
||||
int connected_facets_1_edge = 0;
|
||||
int connected_facets_2_edge = 0;
|
||||
int connected_facets_3_edge = 0;
|
||||
// Faces with 1, 2 and 3 open edges after exact chaining, but before repair.
|
||||
int facets_w_1_bad_edge = 0;
|
||||
int facets_w_2_bad_edge = 0;
|
||||
int facets_w_3_bad_edge = 0;
|
||||
// Number of faces read form an STL file.
|
||||
int original_num_facets = 0;
|
||||
// Number of edges connected one to another by snapping their end vertices.
|
||||
int edges_fixed = 0;
|
||||
// Number of faces removed because they were degenerated.
|
||||
int degenerate_facets = 0;
|
||||
// Total number of facets removed: Degenerate faces and unconnected faces.
|
||||
int facets_removed = 0;
|
||||
// Number of faces added by hole filling.
|
||||
int facets_added = 0;
|
||||
// Number of faces reversed because of negative volume or because one patch was connected to another patch with incompatible normals.
|
||||
int facets_reversed = 0;
|
||||
// Number of incompatible edges remaining after the patches were connected together and possibly their normals flipped.
|
||||
int backwards_edges = 0;
|
||||
// Number of triangles, which were flipped during the fixing process.
|
||||
int normals_fixed = 0;
|
||||
// Number of connected triangle patches.
|
||||
int number_of_parts = 0;
|
||||
|
||||
void clear() { *this = stl_stats(); }
|
||||
@ -135,13 +150,11 @@ struct stl_file {
|
||||
std::vector<stl_facet> facet_start;
|
||||
std::vector<stl_neighbors> neighbors_start;
|
||||
// Statistics
|
||||
stl_stats stats;
|
||||
stl_stats stats;
|
||||
};
|
||||
|
||||
struct indexed_triangle_set
|
||||
{
|
||||
indexed_triangle_set() {}
|
||||
|
||||
void clear() { indices.clear(); vertices.clear(); }
|
||||
|
||||
size_t memsize() const {
|
||||
@ -149,9 +162,7 @@ struct indexed_triangle_set
|
||||
}
|
||||
|
||||
std::vector<stl_triangle_vertex_indices> indices;
|
||||
std::vector<stl_vertex> vertices;
|
||||
//FIXME add normals once we get rid of the stl_file from TriangleMesh completely.
|
||||
//std::vector<stl_normal> normals
|
||||
std::vector<stl_vertex> vertices;
|
||||
|
||||
bool empty() const { return indices.empty() || vertices.empty(); }
|
||||
};
|
||||
|
@ -205,11 +205,12 @@ bool stl_write_quad_object(stl_file *stl, char *file)
|
||||
|
||||
fprintf(fp, "CQUAD\n");
|
||||
for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
|
||||
switch (stl->neighbors_start[i].num_neighbors_missing()) {
|
||||
case 0: color = connect_color; break;
|
||||
case 1: color = uncon_1_color; break;
|
||||
case 2: color = uncon_2_color; break;
|
||||
default: color = uncon_3_color;
|
||||
switch (stl->neighbors_start[i].num_neighbors()) {
|
||||
case 0:
|
||||
default: color = uncon_3_color; break;
|
||||
case 1: color = uncon_2_color; break;
|
||||
case 2: color = uncon_1_color; break;
|
||||
case 3: color = connect_color; break;
|
||||
}
|
||||
fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2), color(0), color(1), color(2));
|
||||
fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2), color(0), color(1), color(2));
|
||||
|
@ -139,6 +139,9 @@ void AppConfig::set_defaults()
|
||||
if (get("default_action_on_select_preset").empty())
|
||||
set("default_action_on_select_preset", "none"); // , "transfer", "discard" or "save"
|
||||
|
||||
if (get("default_action_on_new_project").empty())
|
||||
set("default_action_on_new_project", "none"); // , "keep(transfer)", "discard" or "save"
|
||||
|
||||
if (get("color_mapinulation_panel").empty())
|
||||
set("color_mapinulation_panel", "0");
|
||||
|
||||
|
@ -12,7 +12,7 @@ namespace Slic3r {
|
||||
#ifdef WIN32
|
||||
|
||||
//only dll name with .dll suffix - currently case sensitive
|
||||
const std::vector<std::wstring> BlacklistedLibraryCheck::blacklist({ L"NahimicOSD.dll", L"SS2OSD.dll" });
|
||||
const std::vector<std::wstring> BlacklistedLibraryCheck::blacklist({ L"NahimicOSD.dll", L"SS2OSD.dll", L"amhook.dll", L"AMHook.dll" });
|
||||
|
||||
bool BlacklistedLibraryCheck::get_blacklisted(std::vector<std::wstring>& names)
|
||||
{
|
||||
|
@ -305,8 +305,8 @@ namespace Slic3r {
|
||||
|
||||
struct Geometry
|
||||
{
|
||||
std::vector<float> vertices;
|
||||
std::vector<unsigned int> triangles;
|
||||
std::vector<Vec3f> vertices;
|
||||
std::vector<Vec3i> triangles;
|
||||
std::vector<std::string> custom_supports;
|
||||
std::vector<std::string> custom_seam;
|
||||
std::vector<std::string> mmu_segmentation;
|
||||
@ -720,7 +720,7 @@ namespace Slic3r {
|
||||
}
|
||||
|
||||
// use the geometry to create the volumes in the new model objects
|
||||
ObjectMetadata::VolumeMetadataList volumes(1, { 0, (unsigned int)geometry->triangles.size() / 3 - 1 });
|
||||
ObjectMetadata::VolumeMetadataList volumes(1, { 0, (unsigned int)geometry->triangles.size() - 1 });
|
||||
|
||||
// for each instance after the 1st, create a new model object containing only that instance
|
||||
// and copy into it the geometry
|
||||
@ -793,7 +793,7 @@ namespace Slic3r {
|
||||
// config data not found, this model was not saved using slic3r pe
|
||||
|
||||
// add the entire geometry as the single volume to generate
|
||||
volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() / 3 - 1);
|
||||
volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() - 1);
|
||||
|
||||
// select as volumes
|
||||
volumes_ptr = &volumes;
|
||||
@ -1559,9 +1559,10 @@ namespace Slic3r {
|
||||
{
|
||||
// appends the vertex coordinates
|
||||
// missing values are set equal to ZERO
|
||||
m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, X_ATTR));
|
||||
m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, Y_ATTR));
|
||||
m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, Z_ATTR));
|
||||
m_curr_object.geometry.vertices.emplace_back(
|
||||
m_unit_factor * get_attribute_value_float(attributes, num_attributes, X_ATTR),
|
||||
m_unit_factor * get_attribute_value_float(attributes, num_attributes, Y_ATTR),
|
||||
m_unit_factor * get_attribute_value_float(attributes, num_attributes, Z_ATTR));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1595,9 +1596,10 @@ namespace Slic3r {
|
||||
|
||||
// appends the triangle's vertices indices
|
||||
// missing values are set equal to ZERO
|
||||
m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V1_ATTR));
|
||||
m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V2_ATTR));
|
||||
m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR));
|
||||
m_curr_object.geometry.triangles.emplace_back(
|
||||
get_attribute_value_int(attributes, num_attributes, V1_ATTR),
|
||||
get_attribute_value_int(attributes, num_attributes, V2_ATTR),
|
||||
get_attribute_value_int(attributes, num_attributes, V3_ATTR));
|
||||
|
||||
m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
|
||||
m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
|
||||
@ -1886,7 +1888,7 @@ namespace Slic3r {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int geo_tri_count = (unsigned int)geometry.triangles.size() / 3;
|
||||
unsigned int geo_tri_count = (unsigned int)geometry.triangles.size();
|
||||
unsigned int renamed_volumes_count = 0;
|
||||
|
||||
for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) {
|
||||
@ -1897,77 +1899,50 @@ namespace Slic3r {
|
||||
|
||||
Transform3d volume_matrix_to_object = Transform3d::Identity();
|
||||
bool has_transform = false;
|
||||
#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
|
||||
bool is_left_handed = false;
|
||||
#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
|
||||
// extract the volume transformation from the volume's metadata, if present
|
||||
for (const Metadata& metadata : volume_data.metadata) {
|
||||
if (metadata.key == MATRIX_KEY) {
|
||||
volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value);
|
||||
has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10);
|
||||
#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
|
||||
is_left_handed = Slic3r::Geometry::Transformation(volume_matrix_to_object).is_left_handed();
|
||||
#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// splits volume out of imported geometry
|
||||
TriangleMesh triangle_mesh;
|
||||
stl_file &stl = triangle_mesh.stl;
|
||||
unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1;
|
||||
stl.stats.type = inmemory;
|
||||
stl.stats.number_of_facets = (uint32_t)triangles_count;
|
||||
stl.stats.original_num_facets = (int)stl.stats.number_of_facets;
|
||||
stl_allocate(&stl);
|
||||
|
||||
unsigned int src_start_id = volume_data.first_triangle_id * 3;
|
||||
|
||||
for (unsigned int i = 0; i < triangles_count; ++i) {
|
||||
unsigned int ii = i * 3;
|
||||
stl_facet& facet = stl.facet_start[i];
|
||||
for (unsigned int v = 0; v < 3; ++v) {
|
||||
unsigned int tri_id = geometry.triangles[src_start_id + ii + v] * 3;
|
||||
if (tri_id + 2 >= geometry.vertices.size()) {
|
||||
add_error("Malformed triangle mesh");
|
||||
std::vector<Vec3i> faces(geometry.triangles.begin() + volume_data.first_triangle_id, geometry.triangles.begin() + volume_data.last_triangle_id + 1);
|
||||
const size_t triangles_count = faces.size();
|
||||
for (Vec3i face : faces)
|
||||
for (unsigned int tri_id : face)
|
||||
if (tri_id < 0 || tri_id >= geometry.vertices.size()) {
|
||||
add_error("Found invalid vertex id");
|
||||
return false;
|
||||
}
|
||||
facet.vertex[v] = Vec3f(geometry.vertices[tri_id + 0], geometry.vertices[tri_id + 1], geometry.vertices[tri_id + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
stl_get_size(&stl);
|
||||
triangle_mesh.repair();
|
||||
|
||||
#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
|
||||
// PrusaSlicer older than 2.4.0 saved mirrored volumes with reversed winding of the triangles
|
||||
// This caused the call to TriangleMesh::repair() to reverse all the facets because the calculated volume was negative
|
||||
if (is_left_handed && stl.stats.facets_reversed > 0 && stl.stats.facets_reversed == stl.stats.original_num_facets)
|
||||
stl.stats.facets_reversed = 0;
|
||||
#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
|
||||
TriangleMesh triangle_mesh(std::move(geometry.vertices), std::move(faces));
|
||||
|
||||
if (m_version == 0) {
|
||||
// if the 3mf was not produced by PrusaSlicer and there is only one instance,
|
||||
// bake the transformation into the geometry to allow the reload from disk command
|
||||
// to work properly
|
||||
if (object.instances.size() == 1) {
|
||||
triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix());
|
||||
triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix(), false);
|
||||
object.instances.front()->set_transformation(Slic3r::Geometry::Transformation());
|
||||
//FIXME do the mesh fixing?
|
||||
}
|
||||
}
|
||||
if (triangle_mesh.volume() < 0)
|
||||
triangle_mesh.flip_triangles();
|
||||
|
||||
ModelVolume* volume = object.add_volume(std::move(triangle_mesh));
|
||||
// stores the volume matrix taken from the metadata, if present
|
||||
if (has_transform)
|
||||
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
|
||||
volume->calculate_convex_hull();
|
||||
|
||||
// recreate custom supports, seam and mmu segmentation from previously loaded attribute
|
||||
volume->supported_facets.reserve(triangles_count);
|
||||
volume->seam_facets.reserve(triangles_count);
|
||||
volume->mmu_segmentation_facets.reserve(triangles_count);
|
||||
for (unsigned i=0; i<triangles_count; ++i) {
|
||||
size_t index = src_start_id/3 + i;
|
||||
for (size_t i=0; i<triangles_count; ++i) {
|
||||
size_t index = volume_data.first_triangle_id + i;
|
||||
assert(index < geometry.custom_supports.size());
|
||||
assert(index < geometry.custom_seam.size());
|
||||
assert(index < geometry.mmu_segmentation.size());
|
||||
@ -2543,11 +2518,6 @@ namespace Slic3r {
|
||||
if (volume == nullptr)
|
||||
continue;
|
||||
|
||||
if (!volume->mesh().repaired)
|
||||
throw Slic3r::FileIOError("store_3mf() requires repair()");
|
||||
if (!volume->mesh().has_shared_vertices())
|
||||
throw Slic3r::FileIOError("store_3mf() requires shared vertices");
|
||||
|
||||
volumes_offsets.insert({ volume, Offsets(vertices_count) });
|
||||
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
@ -2588,10 +2558,7 @@ namespace Slic3r {
|
||||
if (volume == nullptr)
|
||||
continue;
|
||||
|
||||
#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
|
||||
bool is_left_handed = volume->is_left_handed();
|
||||
#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
|
||||
|
||||
VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume);
|
||||
assert(volume_it != volumes_offsets.end());
|
||||
|
||||
@ -2606,7 +2573,6 @@ namespace Slic3r {
|
||||
{
|
||||
const Vec3i &idx = its.indices[i];
|
||||
char *ptr = buf;
|
||||
#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
|
||||
boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG <<
|
||||
" v1=\"" << boost::spirit::int_ <<
|
||||
"\" v2=\"" << boost::spirit::int_ <<
|
||||
@ -2614,15 +2580,6 @@ namespace Slic3r {
|
||||
idx[is_left_handed ? 2 : 0] + volume_it->second.first_vertex_id,
|
||||
idx[1] + volume_it->second.first_vertex_id,
|
||||
idx[is_left_handed ? 0 : 2] + volume_it->second.first_vertex_id);
|
||||
#else
|
||||
boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG <<
|
||||
" v1=\"" << boost::spirit::int_ <<
|
||||
"\" v2=\"" << boost::spirit::int_ <<
|
||||
"\" v3=\"" << boost::spirit::int_ << "\"",
|
||||
idx[0] + volume_it->second.first_vertex_id,
|
||||
idx[1] + volume_it->second.first_vertex_id,
|
||||
idx[2] + volume_it->second.first_vertex_id);
|
||||
#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
|
||||
*ptr = '\0';
|
||||
output_buffer += buf;
|
||||
}
|
||||
|
@ -244,11 +244,11 @@ struct AMFParserContext
|
||||
// Map from obect name to object idx & instances.
|
||||
std::map<std::string, Object> m_object_instances_map;
|
||||
// Vertices parsed for the current m_object.
|
||||
std::vector<float> m_object_vertices;
|
||||
std::vector<Vec3f> m_object_vertices;
|
||||
// Current volume allocated for an amf/object/mesh/volume subtree.
|
||||
ModelVolume *m_volume { nullptr };
|
||||
// Faces collected for the current m_volume.
|
||||
std::vector<int> m_volume_facets;
|
||||
std::vector<Vec3i> m_volume_facets;
|
||||
// Transformation matrix of a volume mesh from its coordinate system to Object's coordinate system.
|
||||
Transform3d m_volume_transform;
|
||||
// Current material allocated for an amf/metadata subtree.
|
||||
@ -598,9 +598,7 @@ void AMFParserContext::endElement(const char * /* name */)
|
||||
case NODE_TYPE_VERTEX:
|
||||
assert(m_object);
|
||||
// Parse the vertex data
|
||||
m_object_vertices.emplace_back((float)atof(m_value[0].c_str()));
|
||||
m_object_vertices.emplace_back((float)atof(m_value[1].c_str()));
|
||||
m_object_vertices.emplace_back((float)atof(m_value[2].c_str()));
|
||||
m_object_vertices.emplace_back(float(atof(m_value[0].c_str())), float(atof(m_value[1].c_str())), float(atof(m_value[1].c_str())));
|
||||
m_value[0].clear();
|
||||
m_value[1].clear();
|
||||
m_value[2].clear();
|
||||
@ -609,9 +607,7 @@ void AMFParserContext::endElement(const char * /* name */)
|
||||
// Faces of the current volume:
|
||||
case NODE_TYPE_TRIANGLE:
|
||||
assert(m_object && m_volume);
|
||||
m_volume_facets.emplace_back(atoi(m_value[0].c_str()));
|
||||
m_volume_facets.emplace_back(atoi(m_value[1].c_str()));
|
||||
m_volume_facets.emplace_back(atoi(m_value[2].c_str()));
|
||||
m_volume_facets.emplace_back(atoi(m_value[0].c_str()), atoi(m_value[1].c_str()), atoi(m_value[2].c_str()));
|
||||
m_value[0].clear();
|
||||
m_value[1].clear();
|
||||
m_value[2].clear();
|
||||
@ -621,44 +617,36 @@ void AMFParserContext::endElement(const char * /* name */)
|
||||
case NODE_TYPE_VOLUME:
|
||||
{
|
||||
assert(m_object && m_volume);
|
||||
TriangleMesh mesh;
|
||||
stl_file &stl = mesh.stl;
|
||||
stl.stats.type = inmemory;
|
||||
stl.stats.number_of_facets = int(m_volume_facets.size() / 3);
|
||||
stl.stats.original_num_facets = stl.stats.number_of_facets;
|
||||
stl_allocate(&stl);
|
||||
|
||||
bool has_transform = ! m_volume_transform.isApprox(Transform3d::Identity(), 1e-10);
|
||||
for (size_t i = 0; i < m_volume_facets.size();) {
|
||||
stl_facet &facet = stl.facet_start[i/3];
|
||||
for (unsigned int v = 0; v < 3; ++v)
|
||||
{
|
||||
unsigned int tri_id = m_volume_facets[i++] * 3;
|
||||
if (tri_id < 0 || tri_id + 2 >= m_object_vertices.size()) {
|
||||
// Verify validity of face indices.
|
||||
for (Vec3i face : m_volume_facets)
|
||||
for (unsigned int tri_id : face)
|
||||
if (tri_id < 0 || tri_id >= m_object_vertices.size()) {
|
||||
this->stop("Malformed triangle mesh");
|
||||
return;
|
||||
}
|
||||
facet.vertex[v] = Vec3f(m_object_vertices[tri_id + 0], m_object_vertices[tri_id + 1], m_object_vertices[tri_id + 2]);
|
||||
}
|
||||
}
|
||||
stl_get_size(&stl);
|
||||
mesh.repair();
|
||||
m_volume->set_mesh(std::move(mesh));
|
||||
// stores the volume matrix taken from the metadata, if present
|
||||
if (has_transform)
|
||||
m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform);
|
||||
if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART))
|
||||
|
||||
{
|
||||
TriangleMesh triangle_mesh { std::move(m_object_vertices), std::move(m_volume_facets) };
|
||||
if (triangle_mesh.volume() < 0)
|
||||
triangle_mesh.flip_triangles();
|
||||
m_volume->set_mesh(std::move(triangle_mesh));
|
||||
}
|
||||
|
||||
// stores the volume matrix taken from the metadata, if present
|
||||
if (bool has_transform = !m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); has_transform)
|
||||
m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform);
|
||||
|
||||
if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART)) {
|
||||
m_volume->source.object_idx = (int)m_model.objects.size() - 1;
|
||||
m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1;
|
||||
m_volume->center_geometry_after_creation();
|
||||
}
|
||||
else
|
||||
} else
|
||||
// pass false if the mesh offset has been already taken from the data
|
||||
m_volume->center_geometry_after_creation(m_volume->source.input_file.empty());
|
||||
|
||||
m_volume->calculate_convex_hull();
|
||||
m_volume_facets.clear();
|
||||
m_object_vertices.clear();
|
||||
m_volume = nullptr;
|
||||
break;
|
||||
}
|
||||
@ -1187,10 +1175,6 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
|
||||
int num_vertices = 0;
|
||||
for (ModelVolume *volume : object->volumes) {
|
||||
vertices_offsets.push_back(num_vertices);
|
||||
if (! volume->mesh().repaired)
|
||||
throw Slic3r::FileIOError("store_amf() requires repair()");
|
||||
if (! volume->mesh().has_shared_vertices())
|
||||
throw Slic3r::FileIOError("store_amf() requires shared vertices");
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
const Transform3d& matrix = volume->get_matrix();
|
||||
for (size_t i = 0; i < its.vertices.size(); ++i) {
|
||||
|
@ -19,7 +19,8 @@ namespace Slic3r {
|
||||
|
||||
bool load_obj(const char *path, TriangleMesh *meshptr)
|
||||
{
|
||||
if(meshptr == nullptr) return false;
|
||||
if (meshptr == nullptr)
|
||||
return false;
|
||||
|
||||
// Parse the OBJ file.
|
||||
ObjParser::ObjData data;
|
||||
@ -31,84 +32,69 @@ bool load_obj(const char *path, TriangleMesh *meshptr)
|
||||
// Count the faces and verify, that all faces are triangular.
|
||||
size_t num_faces = 0;
|
||||
size_t num_quads = 0;
|
||||
for (size_t i = 0; i < data.vertices.size(); ) {
|
||||
for (size_t i = 0; i < data.vertices.size(); ++ i) {
|
||||
// Find the end of face.
|
||||
size_t j = i;
|
||||
for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ;
|
||||
if (i == j)
|
||||
continue;
|
||||
size_t face_vertices = j - i;
|
||||
if (face_vertices != 3 && face_vertices != 4) {
|
||||
// Non-triangular and non-quad faces are not supported as of now.
|
||||
return false;
|
||||
if (size_t num_face_vertices = j - i; num_face_vertices > 0) {
|
||||
if (num_face_vertices > 4) {
|
||||
// Non-triangular and non-quad faces are not supported as of now.
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains polygons with more than 4 vertices.";
|
||||
return false;
|
||||
} else if (num_face_vertices < 3) {
|
||||
// Non-triangular and non-quad faces are not supported as of now.
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains polygons with less than 2 vertices.";
|
||||
return false;
|
||||
}
|
||||
if (num_face_vertices == 4)
|
||||
++ num_quads;
|
||||
++ num_faces;
|
||||
i = j;
|
||||
}
|
||||
if (face_vertices == 4)
|
||||
++ num_quads;
|
||||
++ num_faces;
|
||||
i = j + 1;
|
||||
}
|
||||
|
||||
// Convert ObjData into STL.
|
||||
TriangleMesh &mesh = *meshptr;
|
||||
stl_file &stl = mesh.stl;
|
||||
stl.stats.type = inmemory;
|
||||
stl.stats.number_of_facets = uint32_t(num_faces + num_quads);
|
||||
stl.stats.original_num_facets = int(num_faces + num_quads);
|
||||
// stl_allocate clears all the allocated data to zero, all normals are set to zeros as well.
|
||||
stl_allocate(&stl);
|
||||
size_t i_face = 0;
|
||||
for (size_t i = 0; i < data.vertices.size(); ++ i) {
|
||||
if (data.vertices[i].coordIdx == -1)
|
||||
continue;
|
||||
stl_facet &facet = stl.facet_start[i_face ++];
|
||||
size_t num_normals = 0;
|
||||
stl_normal normal(stl_normal::Zero());
|
||||
for (unsigned int v = 0; v < 3; ++ v) {
|
||||
const ObjParser::ObjVertex &vertex = data.vertices[i++];
|
||||
memcpy(facet.vertex[v].data(), &data.coordinates[vertex.coordIdx*4], 3 * sizeof(float));
|
||||
if (vertex.normalIdx != -1) {
|
||||
normal(0) += data.normals[vertex.normalIdx*3];
|
||||
normal(1) += data.normals[vertex.normalIdx*3+1];
|
||||
normal(2) += data.normals[vertex.normalIdx*3+2];
|
||||
++ num_normals;
|
||||
}
|
||||
}
|
||||
// Result of obj_parseline() call is not checked, thus not all vertices are necessarily finalized with coord_Idx == -1.
|
||||
if (i < data.vertices.size() && data.vertices[i].coordIdx != -1) {
|
||||
// This is a quad. Produce the other triangle.
|
||||
stl_facet &facet2 = stl.facet_start[i_face++];
|
||||
facet2.vertex[0] = facet.vertex[0];
|
||||
facet2.vertex[1] = facet.vertex[2];
|
||||
const ObjParser::ObjVertex &vertex = data.vertices[i++];
|
||||
memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float));
|
||||
if (vertex.normalIdx != -1) {
|
||||
normal(0) += data.normals[vertex.normalIdx*3];
|
||||
normal(1) += data.normals[vertex.normalIdx*3+1];
|
||||
normal(2) += data.normals[vertex.normalIdx*3+2];
|
||||
++ num_normals;
|
||||
}
|
||||
if (num_normals == 4) {
|
||||
// Normalize an average normal of a quad.
|
||||
float len = facet.normal.norm();
|
||||
if (len > EPSILON) {
|
||||
normal /= len;
|
||||
facet.normal = normal;
|
||||
facet2.normal = normal;
|
||||
}
|
||||
}
|
||||
} else if (num_normals == 3) {
|
||||
// Normalize an average normal of a triangle.
|
||||
float len = facet.normal.norm();
|
||||
if (len > EPSILON)
|
||||
facet.normal = normal / len;
|
||||
}
|
||||
// Convert ObjData into indexed triangle set.
|
||||
indexed_triangle_set its;
|
||||
size_t num_vertices = data.coordinates.size() / 4;
|
||||
its.vertices.reserve(num_vertices);
|
||||
its.indices.reserve(num_faces + num_quads);
|
||||
for (size_t i = 0; i < num_vertices; ++ i) {
|
||||
size_t j = i << 2;
|
||||
its.vertices.emplace_back(data.coordinates[j], data.coordinates[j + 1], data.coordinates[j + 2]);
|
||||
}
|
||||
stl_get_size(&stl);
|
||||
mesh.repair();
|
||||
if (mesh.facets_count() == 0) {
|
||||
int indices[4];
|
||||
for (size_t i = 0; i < data.vertices.size();)
|
||||
if (data.vertices[i].coordIdx == -1)
|
||||
++ i;
|
||||
else {
|
||||
int cnt = 0;
|
||||
while (i < data.vertices.size())
|
||||
if (const ObjParser::ObjVertex &vertex = data.vertices[i ++]; vertex.coordIdx == -1) {
|
||||
break;
|
||||
} else {
|
||||
assert(cnt < 4);
|
||||
if (vertex.coordIdx < 0 || vertex.coordIdx >= its.vertices.size()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains invalid vertex index.";
|
||||
return false;
|
||||
}
|
||||
indices[cnt ++] = vertex.coordIdx;
|
||||
}
|
||||
if (cnt) {
|
||||
assert(cnt == 3 || cnt == 4);
|
||||
// Insert one or two faces (triangulate a quad).
|
||||
its.indices.emplace_back(indices[0], indices[1], indices[2]);
|
||||
if (cnt == 4)
|
||||
its.indices.emplace_back(indices[0], indices[2], indices[3]);
|
||||
}
|
||||
}
|
||||
|
||||
*meshptr = TriangleMesh(std::move(its));
|
||||
if (meshptr->empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: This OBJ file couldn't be read because it's empty. " << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (meshptr->volume() < 0)
|
||||
meshptr->flip_triangles();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,333 +0,0 @@
|
||||
#include <string.h>
|
||||
#include <exception>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "miniz_extension.hpp"
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
|
||||
#include "PRUS.hpp"
|
||||
|
||||
#if 0
|
||||
// Enable debugging and assert in this file.
|
||||
#define DEBUG
|
||||
#define _DEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
struct StlHeader
|
||||
{
|
||||
char comment[80];
|
||||
uint32_t nTriangles;
|
||||
};
|
||||
|
||||
static_assert(sizeof(StlHeader) == 84, "StlHeader size not correct");
|
||||
|
||||
// Buffered line reader to a string buffer.
|
||||
class LineReader
|
||||
{
|
||||
public:
|
||||
LineReader(std::vector<char> &data) : m_buffer(data), m_pos(0), m_len((int)data.size()) {}
|
||||
|
||||
const char* next_line() {
|
||||
// Skip empty lines.
|
||||
while (m_pos < m_len && (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n'))
|
||||
++ m_pos;
|
||||
if (m_pos == m_len) {
|
||||
// End of file.
|
||||
return nullptr;
|
||||
}
|
||||
// The buffer is nonempty and it does not start with end of lines. Find the first end of line.
|
||||
int end = m_pos + 1;
|
||||
while (end < m_len && m_buffer[end] != '\r' && m_buffer[end] != '\n')
|
||||
++ end;
|
||||
char *ptr_out = m_buffer.data() + m_pos;
|
||||
m_pos = end + 1;
|
||||
m_buffer[end] = 0;
|
||||
return ptr_out;
|
||||
}
|
||||
|
||||
int next_line_scanf(const char *format, ...)
|
||||
{
|
||||
const char *line = next_line();
|
||||
if (line == nullptr)
|
||||
return -1;
|
||||
int result;
|
||||
va_list arglist;
|
||||
va_start(arglist, format);
|
||||
result = vsscanf(line, format, arglist);
|
||||
va_end(arglist);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<char> &m_buffer;
|
||||
int m_pos;
|
||||
int m_len;
|
||||
};
|
||||
|
||||
static void extract_model_from_archive(
|
||||
// name of the model file
|
||||
const char *name,
|
||||
// path to the archive
|
||||
const char *path,
|
||||
// content of scene.xml
|
||||
const std::vector<char> &scene_xml_data,
|
||||
// loaded data of this STL
|
||||
std::vector<char> &data,
|
||||
// Model, to which the newly loaded objects will be added
|
||||
Model *model,
|
||||
// To map multiple STLs into a single model object for multi-material prints.
|
||||
std::map<int, ModelObject*> &group_to_model_object)
|
||||
{
|
||||
// Find the model entry in the XML data.
|
||||
char model_name_tag[1024];
|
||||
sprintf(model_name_tag, "<model name=\"%s\">", name);
|
||||
const char *model_xml = strstr(scene_xml_data.data(), model_name_tag);
|
||||
const char *zero_tag = "<zero>";
|
||||
const char *zero_xml = strstr(scene_xml_data.data(), zero_tag);
|
||||
Vec3d instance_rotation = Vec3d::Zero();
|
||||
Vec3d instance_scaling_factor = Vec3d::Ones();
|
||||
Vec3d instance_offset = Vec3d::Zero();
|
||||
bool trafo_set = false;
|
||||
unsigned int group_id = (unsigned int)-1;
|
||||
unsigned int extruder_id = (unsigned int)-1;
|
||||
ModelObject *model_object = nullptr;
|
||||
if (model_xml != nullptr) {
|
||||
model_xml += strlen(model_name_tag);
|
||||
const char *position_tag = "<position>";
|
||||
const char *position_xml = strstr(model_xml, position_tag);
|
||||
const char *rotation_tag = "<rotation>";
|
||||
const char *rotation_xml = strstr(model_xml, rotation_tag);
|
||||
const char *scale_tag = "<scale>";
|
||||
const char *scale_xml = strstr(model_xml, scale_tag);
|
||||
float position[3], rotation[3], scale[3], zero[3];
|
||||
if (position_xml != nullptr && rotation_xml != nullptr && scale_xml != nullptr && zero_xml != nullptr &&
|
||||
sscanf(position_xml+strlen(position_tag),
|
||||
"[%f, %f, %f]", position, position+1, position+2) == 3 &&
|
||||
sscanf(rotation_xml+strlen(rotation_tag),
|
||||
"[%f, %f, %f]", rotation, rotation+1, rotation+2) == 3 &&
|
||||
sscanf(scale_xml+strlen(scale_tag),
|
||||
"[%f, %f, %f]", scale, scale+1, scale+2) == 3 &&
|
||||
sscanf(zero_xml+strlen(zero_tag),
|
||||
"[%f, %f, %f]", zero, zero+1, zero+2) == 3) {
|
||||
instance_scaling_factor = Vec3d((double)scale[0], (double)scale[1], (double)scale[2]);
|
||||
instance_rotation = Vec3d(-(double)rotation[0], -(double)rotation[1], -(double)rotation[2]);
|
||||
instance_offset = Vec3d((double)(position[0] - zero[0]), (double)(position[1] - zero[1]), (double)(position[2] - zero[2]));
|
||||
trafo_set = true;
|
||||
}
|
||||
const char *group_tag = "<group>";
|
||||
const char *group_xml = strstr(model_xml, group_tag);
|
||||
const char *extruder_tag = "<extruder>";
|
||||
const char *extruder_xml = strstr(model_xml, extruder_tag);
|
||||
if (group_xml != nullptr) {
|
||||
int group = atoi(group_xml + strlen(group_tag));
|
||||
if (group > 0) {
|
||||
group_id = group;
|
||||
auto it = group_to_model_object.find(group_id);
|
||||
if (it != group_to_model_object.end())
|
||||
model_object = it->second;
|
||||
}
|
||||
}
|
||||
if (extruder_xml != nullptr) {
|
||||
int e = atoi(extruder_xml + strlen(extruder_tag));
|
||||
if (e > 0)
|
||||
extruder_id = e;
|
||||
}
|
||||
}
|
||||
if (! trafo_set)
|
||||
throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name);
|
||||
|
||||
// Extract the STL.
|
||||
StlHeader header;
|
||||
TriangleMesh mesh;
|
||||
bool mesh_valid = false;
|
||||
bool stl_ascii = false;
|
||||
if (data.size() > sizeof(StlHeader)) {
|
||||
memcpy((char*)&header, data.data(), sizeof(StlHeader));
|
||||
if (strncmp(header.comment, "solid ", 6) == 0)
|
||||
stl_ascii = true;
|
||||
else {
|
||||
// Header has been extracted. Now read the faces.
|
||||
stl_file &stl = mesh.stl;
|
||||
stl.stats.type = inmemory;
|
||||
stl.stats.number_of_facets = header.nTriangles;
|
||||
stl.stats.original_num_facets = header.nTriangles;
|
||||
stl_allocate(&stl);
|
||||
if (header.nTriangles > 0 && data.size() == 50 * header.nTriangles + sizeof(StlHeader)) {
|
||||
memcpy((char*)stl.facet_start.data(), data.data() + sizeof(StlHeader), 50 * header.nTriangles);
|
||||
if (sizeof(stl_facet) > SIZEOF_STL_FACET) {
|
||||
// The stl.facet_start is not packed tightly. Unpack the array of stl_facets.
|
||||
unsigned char *data = (unsigned char*)stl.facet_start.data();
|
||||
for (size_t i = header.nTriangles - 1; i > 0; -- i)
|
||||
memmove(data + i * sizeof(stl_facet), data + i * SIZEOF_STL_FACET, SIZEOF_STL_FACET);
|
||||
}
|
||||
// All the faces have been read.
|
||||
stl_get_size(&stl);
|
||||
mesh.repair();
|
||||
if (std::abs(stl.stats.min(2)) < EPSILON)
|
||||
stl.stats.min(2) = 0.;
|
||||
// Add a mesh to a model.
|
||||
if (mesh.facets_count() > 0)
|
||||
mesh_valid = true;
|
||||
}
|
||||
}
|
||||
} else
|
||||
stl_ascii = true;
|
||||
|
||||
if (stl_ascii) {
|
||||
// Try to parse ASCII STL.
|
||||
char normal_buf[3][32];
|
||||
stl_facet facet;
|
||||
std::vector<stl_facet> facets;
|
||||
LineReader line_reader(data);
|
||||
std::string solid_name;
|
||||
facet.extra[0] = facet.extra[1] = 0;
|
||||
for (;;) {
|
||||
const char *line = line_reader.next_line();
|
||||
if (line == nullptr)
|
||||
// End of file.
|
||||
break;
|
||||
if (strncmp(line, "solid", 5) == 0) {
|
||||
// Opening the "solid" block.
|
||||
if (! solid_name.empty()) {
|
||||
// Error, solid block is already open.
|
||||
facets.clear();
|
||||
break;
|
||||
}
|
||||
solid_name = line + 5;
|
||||
if (solid_name.empty())
|
||||
solid_name = "unknown";
|
||||
continue;
|
||||
}
|
||||
if (strncmp(line, "endsolid", 8) == 0) {
|
||||
// Closing the "solid" block.
|
||||
if (solid_name.empty()) {
|
||||
// Error, no solid block is open.
|
||||
facets.clear();
|
||||
break;
|
||||
}
|
||||
solid_name.clear();
|
||||
continue;
|
||||
}
|
||||
// Line has to start with the word solid.
|
||||
int res_normal = sscanf(line, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]);
|
||||
assert(res_normal == 3);
|
||||
int res_outer_loop = line_reader.next_line_scanf(" outer loop");
|
||||
assert(res_outer_loop == 0);
|
||||
int res_vertex1 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2));
|
||||
assert(res_vertex1 == 3);
|
||||
int res_vertex2 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2));
|
||||
assert(res_vertex2 == 3);
|
||||
int res_vertex3 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2));
|
||||
assert(res_vertex3 == 3);
|
||||
int res_endloop = line_reader.next_line_scanf(" endloop");
|
||||
assert(res_endloop == 0);
|
||||
int res_endfacet = line_reader.next_line_scanf(" endfacet");
|
||||
if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) {
|
||||
// perror("Something is syntactically very wrong with this ASCII STL!");
|
||||
facets.clear();
|
||||
break;
|
||||
}
|
||||
// The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition.
|
||||
if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 ||
|
||||
sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 ||
|
||||
sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) {
|
||||
// Normal was mangled. Maybe denormals or "not a number" were stored?
|
||||
// Just reset the normal and silently ignore it.
|
||||
facet.normal = stl_normal::Zero();
|
||||
}
|
||||
facets.emplace_back(facet);
|
||||
}
|
||||
if (! facets.empty() && solid_name.empty()) {
|
||||
stl_file &stl = mesh.stl;
|
||||
stl.stats.type = inmemory;
|
||||
stl.stats.number_of_facets = (uint32_t)facets.size();
|
||||
stl.stats.original_num_facets = (int)facets.size();
|
||||
stl_allocate(&stl);
|
||||
memcpy((void*)stl.facet_start.data(), facets.data(), facets.size() * 50);
|
||||
stl_get_size(&stl);
|
||||
mesh.repair();
|
||||
// Add a mesh to a model.
|
||||
if (mesh.facets_count() > 0)
|
||||
mesh_valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! mesh_valid)
|
||||
throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid mesh for " + name);
|
||||
|
||||
// Add this mesh to the model.
|
||||
ModelVolume *volume = nullptr;
|
||||
if (model_object == nullptr) {
|
||||
// This is a first mesh of a group. Create a new object & volume.
|
||||
model_object = model->add_object(name, path, std::move(mesh));
|
||||
volume = model_object->volumes.front();
|
||||
ModelInstance *instance = model_object->add_instance();
|
||||
instance->set_rotation(instance_rotation);
|
||||
instance->set_scaling_factor(instance_scaling_factor);
|
||||
instance->set_offset(instance_offset);
|
||||
if (group_id != (unsigned int)(-1))
|
||||
group_to_model_object[group_id] = model_object;
|
||||
} else {
|
||||
// This is not the 1st mesh of a group. Add it to the ModelObject.
|
||||
volume = model_object->add_volume(std::move(mesh));
|
||||
volume->name = name;
|
||||
}
|
||||
// Set the extruder to the volume.
|
||||
if (extruder_id != (unsigned int)-1)
|
||||
volume->config.set("extruder", int(extruder_id));
|
||||
}
|
||||
|
||||
// Load a PrusaControl project file into a provided model.
|
||||
bool load_prus(const char *path, Model *model)
|
||||
{
|
||||
mz_zip_archive archive;
|
||||
mz_zip_zero_struct(&archive);
|
||||
|
||||
size_t n_models_initial = model->objects.size();
|
||||
mz_bool res = MZ_FALSE;
|
||||
try {
|
||||
if (!open_zip_reader(&archive, path))
|
||||
throw Slic3r::FileIOError(std::string("Unable to init zip reader for ") + path);
|
||||
std::vector<char> scene_xml_data;
|
||||
// For grouping multiple STLs into a single ModelObject for multi-material prints.
|
||||
std::map<int, ModelObject*> group_to_model_object;
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
|
||||
for (mz_uint i = 0; i < num_entries; ++ i) {
|
||||
mz_zip_archive_file_stat stat;
|
||||
if (! mz_zip_reader_file_stat(&archive, i, &stat))
|
||||
continue;
|
||||
std::vector<char> buffer;
|
||||
buffer.assign((size_t)stat.m_uncomp_size, 0);
|
||||
res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (char*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
||||
if (res == MZ_FALSE)
|
||||
throw Slic3r::FileIOError(std::string("Error while extracting a file from ") + path);
|
||||
if (strcmp(stat.m_filename, "scene.xml") == 0) {
|
||||
if (! scene_xml_data.empty())
|
||||
throw Slic3r::FileIOError(std::string("Multiple scene.xml were found in the archive.") + path);
|
||||
scene_xml_data = std::move(buffer);
|
||||
} else if (boost::iends_with(stat.m_filename, ".stl")) {
|
||||
// May throw std::exception
|
||||
extract_model_from_archive(stat.m_filename, path, scene_xml_data, buffer, model, group_to_model_object);
|
||||
}
|
||||
}
|
||||
} catch (std::exception &ex) {
|
||||
close_zip_reader(&archive);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
close_zip_reader(&archive);
|
||||
return model->objects.size() > n_models_initial;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
@ -1,11 +0,0 @@
|
||||
#define slic3r_Format_PRUS_hpp_
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
class Model;
|
||||
|
||||
// Load a PrusaControl project file into a provided model.
|
||||
extern bool load_prus(const char *path, Model *model);
|
||||
|
||||
}; // namespace Slic3r
|
@ -21,8 +21,7 @@ bool load_stl(const char *path, Model *model, const char *object_name_in)
|
||||
// die "Failed to open $file\n" if !-e $path;
|
||||
return false;
|
||||
}
|
||||
mesh.repair();
|
||||
if (mesh.facets_count() == 0) {
|
||||
if (mesh.empty()) {
|
||||
// die "This STL file couldn't be read because it's empty.\n"
|
||||
return false;
|
||||
}
|
||||
|
@ -343,18 +343,6 @@ void GCodeProcessor::TimeProcessor::reset()
|
||||
machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true;
|
||||
}
|
||||
|
||||
struct FilePtr {
|
||||
FilePtr(FILE *f) : f(f) {}
|
||||
~FilePtr() { this->close(); }
|
||||
void close() {
|
||||
if (this->f) {
|
||||
::fclose(this->f);
|
||||
this->f = nullptr;
|
||||
}
|
||||
}
|
||||
FILE* f = nullptr;
|
||||
};
|
||||
|
||||
void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector<MoveVertex>& moves, std::vector<size_t>& lines_ends)
|
||||
{
|
||||
FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") };
|
||||
@ -755,11 +743,11 @@ void GCodeProcessor::Result::reset() {
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
|
||||
const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> GCodeProcessor::Producers = {
|
||||
{ EProducer::PrusaSlicer, "PrusaSlicer" },
|
||||
{ EProducer::Slic3rPE, "Slic3r Prusa Edition" },
|
||||
{ EProducer::Slic3r, "Slic3r" },
|
||||
{ EProducer::PrusaSlicer, "generated by PrusaSlicer" },
|
||||
{ EProducer::Slic3rPE, "generated by Slic3r Prusa Edition" },
|
||||
{ EProducer::Slic3r, "generated by Slic3r" },
|
||||
{ EProducer::Cura, "Cura_SteamEngine" },
|
||||
{ EProducer::Simplify3D, "Simplify3D" },
|
||||
{ EProducer::Simplify3D, "G-Code generated by Simplify3D(R)" },
|
||||
{ EProducer::CraftWare, "CraftWare" },
|
||||
{ EProducer::ideaMaker, "ideaMaker" },
|
||||
{ EProducer::KissSlicer, "KISSlicer" }
|
||||
@ -841,26 +829,16 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
||||
m_result.extruders_count = extruders_count;
|
||||
|
||||
m_extruder_offsets.resize(extruders_count);
|
||||
for (size_t i = 0; i < extruders_count; ++i) {
|
||||
Vec2f offset = config.extruder_offset.get_at(i).cast<float>();
|
||||
m_extruder_offsets[i] = { offset(0), offset(1), 0.0f };
|
||||
}
|
||||
|
||||
m_extruder_colors.resize(extruders_count);
|
||||
for (size_t i = 0; i < extruders_count; ++i) {
|
||||
m_extruder_colors[i] = static_cast<unsigned char>(i);
|
||||
}
|
||||
|
||||
m_result.filament_diameters.resize(extruders_count);
|
||||
m_result.filament_densities.resize(extruders_count);
|
||||
m_extruder_temps.resize(extruders_count);
|
||||
|
||||
m_result.filament_diameters.resize(config.filament_diameter.values.size());
|
||||
for (size_t i = 0; i < config.filament_diameter.values.size(); ++i) {
|
||||
m_result.filament_diameters[i] = static_cast<float>(config.filament_diameter.values[i]);
|
||||
}
|
||||
|
||||
m_result.filament_densities.resize(config.filament_density.values.size());
|
||||
for (size_t i = 0; i < config.filament_density.values.size(); ++i) {
|
||||
m_result.filament_densities[i] = static_cast<float>(config.filament_density.values[i]);
|
||||
for (size_t i = 0; i < extruders_count; ++ i) {
|
||||
m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast<float>().eval(), 0.f);
|
||||
m_extruder_colors[i] = static_cast<unsigned char>(i);
|
||||
m_result.filament_diameters[i] = static_cast<float>(config.filament_diameter.get_at(i));
|
||||
m_result.filament_densities[i] = static_cast<float>(config.filament_density.get_at(i));
|
||||
}
|
||||
|
||||
if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) {
|
||||
@ -1201,6 +1179,16 @@ void GCodeProcessor::reset()
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
}
|
||||
|
||||
static inline const char* skip_whitespaces(const char *begin, const char *end) {
|
||||
for (; begin != end && (*begin == ' ' || *begin == '\t'); ++ begin);
|
||||
return begin;
|
||||
}
|
||||
|
||||
static inline const char* remove_eols(const char *begin, const char *end) {
|
||||
for (; begin != end && (*(end - 1) == '\r' || *(end - 1) == '\n'); -- end);
|
||||
return end;
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_file(const std::string& filename, std::function<void()> cancel_callback)
|
||||
{
|
||||
CNumericLocalesSetter locales_setter;
|
||||
@ -1212,14 +1200,16 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
|
||||
// pre-processing
|
||||
// parse the gcode file to detect its producer
|
||||
{
|
||||
m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
|
||||
const std::string_view cmd = line.cmd();
|
||||
if (cmd.empty()) {
|
||||
const std::string_view comment = line.comment();
|
||||
if (comment.length() > 1 && detect_producer(comment))
|
||||
m_parser.parse_file_raw(filename, [this](GCodeReader& reader, const char *begin, const char *end) {
|
||||
begin = skip_whitespaces(begin, end);
|
||||
if (begin != end && *begin == ';') {
|
||||
// Comment.
|
||||
begin = skip_whitespaces(++ begin, end);
|
||||
end = remove_eols(begin, end);
|
||||
if (begin != end && detect_producer(std::string_view(begin, end - begin)))
|
||||
m_parser.quit_parsing();
|
||||
}
|
||||
});
|
||||
});
|
||||
m_parser.reset();
|
||||
|
||||
// if the gcode was produced by PrusaSlicer,
|
||||
@ -1384,9 +1374,11 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename)
|
||||
};
|
||||
|
||||
BedSize bed_size;
|
||||
bool producer_detected = false;
|
||||
|
||||
m_parser.parse_file(filename, [this, &bed_size](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
|
||||
auto extract_double = [](const std::string& cmt, const std::string& key, double& out) {
|
||||
m_parser.parse_file_raw(filename, [this, &bed_size, &producer_detected](GCodeReader& reader, const char* begin, const char* end) {
|
||||
|
||||
auto extract_double = [](const std::string_view cmt, const std::string& key, double& out) {
|
||||
size_t pos = cmt.find(key);
|
||||
if (pos != cmt.npos) {
|
||||
pos = cmt.find(',', pos);
|
||||
@ -1398,12 +1390,12 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename)
|
||||
return false;
|
||||
};
|
||||
|
||||
auto extract_floats = [](const std::string& cmt, const std::string& key, std::vector<float>& out) {
|
||||
auto extract_floats = [](const std::string_view cmt, const std::string& key, std::vector<float>& out) {
|
||||
size_t pos = cmt.find(key);
|
||||
if (pos != cmt.npos) {
|
||||
pos = cmt.find(',', pos);
|
||||
if (pos != cmt.npos) {
|
||||
std::string data_str = cmt.substr(pos + 1);
|
||||
const std::string_view data_str = cmt.substr(pos + 1);
|
||||
std::vector<std::string> values_str;
|
||||
boost::split(values_str, data_str, boost::is_any_of("|,"), boost::token_compress_on);
|
||||
for (const std::string& s : values_str) {
|
||||
@ -1414,28 +1406,39 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename)
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const std::string& comment = line.raw();
|
||||
if (comment.length() > 2 && comment.front() == ';') {
|
||||
if (bed_size.x == 0.0 && comment.find("strokeXoverride") != comment.npos)
|
||||
extract_double(comment, "strokeXoverride", bed_size.x);
|
||||
else if (bed_size.y == 0.0 && comment.find("strokeYoverride") != comment.npos)
|
||||
extract_double(comment, "strokeYoverride", bed_size.y);
|
||||
else if (comment.find("filamentDiameters") != comment.npos) {
|
||||
m_result.filament_diameters.clear();
|
||||
extract_floats(comment, "filamentDiameters", m_result.filament_diameters);
|
||||
|
||||
begin = skip_whitespaces(begin, end);
|
||||
end = remove_eols(begin, end);
|
||||
if (begin != end)
|
||||
if (*begin == ';') {
|
||||
// Comment.
|
||||
begin = skip_whitespaces(++ begin, end);
|
||||
if (begin != end) {
|
||||
std::string_view comment(begin, end - begin);
|
||||
if (producer_detected) {
|
||||
if (bed_size.x == 0.0 && comment.find("strokeXoverride") != comment.npos)
|
||||
extract_double(comment, "strokeXoverride", bed_size.x);
|
||||
else if (bed_size.y == 0.0 && comment.find("strokeYoverride") != comment.npos)
|
||||
extract_double(comment, "strokeYoverride", bed_size.y);
|
||||
else if (comment.find("filamentDiameters") != comment.npos) {
|
||||
m_result.filament_diameters.clear();
|
||||
extract_floats(comment, "filamentDiameters", m_result.filament_diameters);
|
||||
} else if (comment.find("filamentDensities") != comment.npos) {
|
||||
m_result.filament_densities.clear();
|
||||
extract_floats(comment, "filamentDensities", m_result.filament_densities);
|
||||
} else if (comment.find("extruderDiameter") != comment.npos) {
|
||||
std::vector<float> extruder_diameters;
|
||||
extract_floats(comment, "extruderDiameter", extruder_diameters);
|
||||
m_result.extruders_count = extruder_diameters.size();
|
||||
}
|
||||
} else if (boost::starts_with(comment, "G-Code generated by Simplify3D(R)"))
|
||||
producer_detected = true;
|
||||
}
|
||||
} else {
|
||||
// Some non-empty G-code line detected, stop parsing config comments.
|
||||
reader.quit_parsing();
|
||||
}
|
||||
else if (comment.find("filamentDensities") != comment.npos) {
|
||||
m_result.filament_densities.clear();
|
||||
extract_floats(comment, "filamentDensities", m_result.filament_densities);
|
||||
}
|
||||
else if (comment.find("extruderDiameter") != comment.npos) {
|
||||
std::vector<float> extruder_diameters;
|
||||
extract_floats(comment, "extruderDiameter", extruder_diameters);
|
||||
m_result.extruders_count = extruder_diameters.size();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (m_result.extruders_count == 0)
|
||||
m_result.extruders_count = std::max<size_t>(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size()));
|
||||
|
@ -2,9 +2,11 @@
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include "LocalesUtils.hpp"
|
||||
|
||||
@ -126,44 +128,92 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair<const char*, co
|
||||
}
|
||||
}
|
||||
|
||||
bool GCodeReader::parse_file(const std::string &file, callback_t callback)
|
||||
template<typename ParseLineCallback, typename LineEndCallback>
|
||||
bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback)
|
||||
{
|
||||
boost::nowide::ifstream f(file);
|
||||
f.sync_with_stdio(false);
|
||||
FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") };
|
||||
|
||||
// Read the input stream 64kB at a time, extract lines and process them.
|
||||
std::vector<char> buffer(65536 * 10, 0);
|
||||
std::string line;
|
||||
// Line buffer.
|
||||
std::string gcode_line;
|
||||
size_t file_pos = 0;
|
||||
m_parsing = true;
|
||||
GCodeLine gline;
|
||||
while (m_parsing && ! f.eof()) {
|
||||
f.read(buffer.data(), buffer.size());
|
||||
if (! f.eof() && ! f.good())
|
||||
// Reading the input file failed.
|
||||
for (;;) {
|
||||
size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f);
|
||||
if (::ferror(in.f))
|
||||
return false;
|
||||
bool eof = cnt_read == 0;
|
||||
auto it = buffer.begin();
|
||||
auto it_bufend = buffer.begin() + f.gcount();
|
||||
while (it != it_bufend) {
|
||||
bool eol = false;
|
||||
auto it_bufend = buffer.begin() + cnt_read;
|
||||
while (it != it_bufend || (eof && ! gcode_line.empty())) {
|
||||
// Find end of line.
|
||||
bool eol = false;
|
||||
auto it_end = it;
|
||||
for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ;
|
||||
eol |= f.eof() && it_end == it_bufend;
|
||||
for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end)
|
||||
if (*it_end == '\n')
|
||||
line_end_callback((it_end - buffer.begin()) + 1);
|
||||
// End of line is indicated also if end of file was reached.
|
||||
eol |= eof && it_end == it_bufend;
|
||||
if (eol) {
|
||||
gline.reset();
|
||||
if (line.empty())
|
||||
this->parse_line(&(*it), &(*it_end), gline, callback);
|
||||
if (gcode_line.empty())
|
||||
parse_line_callback(&(*it), &(*it_end));
|
||||
else {
|
||||
line.insert(line.end(), it, it_end);
|
||||
this->parse_line(line.c_str(), line.c_str() + line.size(), gline, callback);
|
||||
line.clear();
|
||||
gcode_line.insert(gcode_line.end(), it, it_end);
|
||||
parse_line_callback(gcode_line.c_str(), gcode_line.c_str() + gcode_line.size());
|
||||
gcode_line.clear();
|
||||
}
|
||||
if (! m_parsing)
|
||||
// The callback wishes to exit.
|
||||
return true;
|
||||
} else
|
||||
line.insert(line.end(), it, it_end);
|
||||
// Skip all the empty lines.
|
||||
for (it = it_end; it != it_bufend && (*it == '\r' || *it == '\n'); ++ it) ;
|
||||
gcode_line.insert(gcode_line.end(), it, it_end);
|
||||
// Skip EOL.
|
||||
it = it_end;
|
||||
if (it != it_bufend && *it == '\r')
|
||||
++ it;
|
||||
if (it != it_bufend && *it == '\n') {
|
||||
line_end_callback((it - buffer.begin()) + 1);
|
||||
++ it;
|
||||
}
|
||||
}
|
||||
if (eof)
|
||||
break;
|
||||
file_pos += cnt_read;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename ParseLineCallback, typename LineEndCallback>
|
||||
bool GCodeReader::parse_file_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback)
|
||||
{
|
||||
GCodeLine gline;
|
||||
return this->parse_file_raw_internal(filename,
|
||||
[this, &gline, parse_line_callback](const char *begin, const char *end) {
|
||||
gline.reset();
|
||||
this->parse_line(begin, end, gline, parse_line_callback);
|
||||
},
|
||||
line_end_callback);
|
||||
}
|
||||
|
||||
bool GCodeReader::parse_file(const std::string &file, callback_t callback)
|
||||
{
|
||||
return this->parse_file_internal(file, callback, [](size_t){});
|
||||
}
|
||||
|
||||
bool GCodeReader::parse_file(const std::string &file, callback_t callback, std::vector<size_t> &lines_ends)
|
||||
{
|
||||
lines_ends.clear();
|
||||
return this->parse_file_internal(file, callback, [&lines_ends](size_t file_pos){ lines_ends.emplace_back(file_pos); });
|
||||
}
|
||||
|
||||
bool GCodeReader::parse_file_raw(const std::string &filename, raw_line_callback_t line_callback)
|
||||
{
|
||||
return this->parse_file_raw_internal(filename,
|
||||
[this, line_callback](const char *begin, const char *end) { line_callback(*this, begin, end); },
|
||||
[](size_t){});
|
||||
}
|
||||
|
||||
bool GCodeReader::GCodeLine::has(char axis) const
|
||||
{
|
||||
const char *c = m_raw.c_str();
|
||||
|
@ -76,6 +76,7 @@ public:
|
||||
};
|
||||
|
||||
typedef std::function<void(GCodeReader&, const GCodeLine&)> callback_t;
|
||||
typedef std::function<void(GCodeReader&, const char*, const char*)> raw_line_callback_t;
|
||||
|
||||
GCodeReader() : m_verbose(false), m_extrusion_axis('E') { this->reset(); }
|
||||
void reset() { memset(m_position, 0, sizeof(m_position)); }
|
||||
@ -114,6 +115,13 @@ public:
|
||||
|
||||
// Returns false if reading the file failed.
|
||||
bool parse_file(const std::string &file, callback_t callback);
|
||||
// Collect positions of line ends in the binary G-code to be used by the G-code viewer when memory mapping and displaying section of G-code
|
||||
// as an overlay in the 3D scene.
|
||||
bool parse_file(const std::string &file, callback_t callback, std::vector<size_t> &lines_ends);
|
||||
// Just read the G-code file line by line, calls callback (const char *begin, const char *end). Returns false if reading the file failed.
|
||||
bool parse_file_raw(const std::string &file, raw_line_callback_t callback);
|
||||
|
||||
// To be called by the callback to stop parsing.
|
||||
void quit_parsing() { m_parsing = false; }
|
||||
|
||||
float& x() { return m_position[X]; }
|
||||
@ -132,6 +140,11 @@ public:
|
||||
// void set_extrusion_axis(char axis) { m_extrusion_axis = axis; }
|
||||
|
||||
private:
|
||||
template<typename ParseLineCallback, typename LineEndCallback>
|
||||
bool parse_file_raw_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback);
|
||||
template<typename ParseLineCallback, typename LineEndCallback>
|
||||
bool parse_file_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback);
|
||||
|
||||
const char* parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair<const char*, const char*> &command);
|
||||
void update_coordinates(GCodeLine &gline, std::pair<const char*, const char*> &command);
|
||||
|
||||
@ -154,6 +167,7 @@ private:
|
||||
char m_extrusion_axis;
|
||||
float m_position[NUM_AXES];
|
||||
bool m_verbose;
|
||||
// To be set by the callback to stop parsing.
|
||||
bool m_parsing{ false };
|
||||
};
|
||||
|
||||
|
@ -51,7 +51,7 @@ bool is_decimal_separator_point()
|
||||
}
|
||||
|
||||
|
||||
double string_to_double_decimal_point(const std::string& str, size_t* pos /* = nullptr*/)
|
||||
double string_to_double_decimal_point(const std::string_view str, size_t* pos /* = nullptr*/)
|
||||
{
|
||||
double out;
|
||||
size_t p = fast_float::from_chars(str.data(), str.data() + str.size(), out).ptr - str.data();
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <clocale>
|
||||
#include <iomanip>
|
||||
#include <cassert>
|
||||
#include <string_view>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <xlocale.h>
|
||||
@ -40,7 +41,7 @@ bool is_decimal_separator_point();
|
||||
// (We use user C locales and "C" C++ locales in most of the code.)
|
||||
std::string float_to_string_decimal_point(double value, int precision = -1);
|
||||
//std::string float_to_string_decimal_point(float value, int precision = -1);
|
||||
double string_to_double_decimal_point(const std::string& str, size_t* pos = nullptr);
|
||||
double string_to_double_decimal_point(const std::string_view str, size_t* pos = nullptr);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
|
@ -29,18 +29,17 @@ TriangleMesh eigen_to_triangle_mesh(const EigenMesh &emesh)
|
||||
{
|
||||
auto &VC = emesh.first; auto &FC = emesh.second;
|
||||
|
||||
Pointf3s points(size_t(VC.rows()));
|
||||
std::vector<Vec3i> facets(size_t(FC.rows()));
|
||||
indexed_triangle_set its;
|
||||
its.vertices.reserve(size_t(VC.rows()));
|
||||
its.indices.reserve(size_t(FC.rows()));
|
||||
|
||||
for (Eigen::Index i = 0; i < VC.rows(); ++i)
|
||||
points[size_t(i)] = VC.row(i);
|
||||
its.vertices.emplace_back(VC.row(i).cast<float>());
|
||||
|
||||
for (Eigen::Index i = 0; i < FC.rows(); ++i)
|
||||
facets[size_t(i)] = FC.row(i);
|
||||
its.indices.emplace_back(FC.row(i));
|
||||
|
||||
TriangleMesh out{points, facets};
|
||||
out.require_shared_vertices();
|
||||
return out;
|
||||
return TriangleMesh { std::move(its) };
|
||||
}
|
||||
|
||||
EigenMesh triangle_mesh_to_eigen(const TriangleMesh &mesh)
|
||||
@ -131,28 +130,27 @@ void triangle_mesh_to_cgal(const std::vector<stl_vertex> & V,
|
||||
out.add_face(VI(f(0)), VI(f(1)), VI(f(2)));
|
||||
}
|
||||
|
||||
inline Vec3d to_vec3d(const _EpicMesh::Point &v)
|
||||
inline Vec3f to_vec3f(const _EpicMesh::Point& v)
|
||||
{
|
||||
return {v.x(), v.y(), v.z()};
|
||||
return { float(v.x()), float(v.y()), float(v.z()) };
|
||||
}
|
||||
|
||||
inline Vec3d to_vec3d(const _EpecMesh::Point &v)
|
||||
inline Vec3f to_vec3f(const _EpecMesh::Point& v)
|
||||
{
|
||||
CGAL::Cartesian_converter<EpecKernel, EpicKernel> cvt;
|
||||
auto iv = cvt(v);
|
||||
return {iv.x(), iv.y(), iv.z()};
|
||||
return { float(iv.x()), float(iv.y()), float(iv.z()) };
|
||||
}
|
||||
|
||||
template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
|
||||
{
|
||||
Pointf3s points;
|
||||
std::vector<Vec3i> facets;
|
||||
points.reserve(cgalmesh.num_vertices());
|
||||
facets.reserve(cgalmesh.num_faces());
|
||||
indexed_triangle_set its;
|
||||
its.vertices.reserve(cgalmesh.num_vertices());
|
||||
its.indices.reserve(cgalmesh.num_faces());
|
||||
|
||||
for (auto &vi : cgalmesh.vertices()) {
|
||||
auto &v = cgalmesh.point(vi); // Don't ask...
|
||||
points.emplace_back(to_vec3d(v));
|
||||
its.vertices.emplace_back(to_vec3f(v));
|
||||
}
|
||||
|
||||
for (auto &face : cgalmesh.faces()) {
|
||||
@ -166,12 +164,10 @@ template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
|
||||
}
|
||||
|
||||
if (i == 3)
|
||||
facets.emplace_back(facet);
|
||||
its.indices.emplace_back(facet);
|
||||
}
|
||||
|
||||
TriangleMesh out{points, facets};
|
||||
out.repair();
|
||||
return out;
|
||||
return TriangleMesh(std::move(its));
|
||||
}
|
||||
|
||||
std::unique_ptr<CGALMesh, CGALMeshDeleter>
|
||||
|
@ -28,65 +28,84 @@ template<> struct ItsWithNeighborsIndex_<indexed_triangle_set> {
|
||||
}
|
||||
};
|
||||
|
||||
// Visit all unvisited neighboring facets that are reachable from the first unvisited facet,
|
||||
// and return them.
|
||||
// Discover connected patches of facets one by one.
|
||||
template<class NeighborIndex>
|
||||
std::vector<size_t> its_find_unvisited_neighbors(
|
||||
const indexed_triangle_set &its,
|
||||
const NeighborIndex & neighbor_index,
|
||||
std::vector<char> & visited)
|
||||
{
|
||||
using stack_el = size_t;
|
||||
|
||||
auto facestack = reserve_vector<stack_el>(its.indices.size());
|
||||
auto push = [&facestack] (const stack_el &s) { facestack.emplace_back(s); };
|
||||
auto pop = [&facestack] () -> stack_el {
|
||||
stack_el ret = facestack.back();
|
||||
facestack.pop_back();
|
||||
return ret;
|
||||
};
|
||||
|
||||
// find the next unvisited facet and push the index
|
||||
auto facet = std::find(visited.begin(), visited.end(), false);
|
||||
std::vector<size_t> ret;
|
||||
|
||||
if (facet != visited.end()) {
|
||||
ret.reserve(its.indices.size());
|
||||
auto idx = size_t(facet - visited.begin());
|
||||
push(idx);
|
||||
ret.emplace_back(idx);
|
||||
visited[idx] = true;
|
||||
struct NeighborVisitor {
|
||||
NeighborVisitor(const indexed_triangle_set &its, const NeighborIndex &neighbor_index) :
|
||||
its(its), neighbor_index(neighbor_index) {
|
||||
m_visited.assign(its.indices.size(), false);
|
||||
m_facestack.reserve(its.indices.size());
|
||||
}
|
||||
NeighborVisitor(const indexed_triangle_set &its, NeighborIndex &&aneighbor_index) :
|
||||
its(its), neighbor_index(m_neighbor_index_data), m_neighbor_index_data(std::move(aneighbor_index)) {
|
||||
m_visited.assign(its.indices.size(), false);
|
||||
m_facestack.reserve(its.indices.size());
|
||||
}
|
||||
|
||||
while (!facestack.empty()) {
|
||||
size_t facet_idx = pop();
|
||||
const auto &neighbors = neighbor_index[facet_idx];
|
||||
for (auto neighbor_idx : neighbors) {
|
||||
if (size_t(neighbor_idx) < visited.size() && !visited[size_t(neighbor_idx)]) {
|
||||
visited[size_t(neighbor_idx)] = true;
|
||||
push(stack_el(neighbor_idx));
|
||||
ret.emplace_back(size_t(neighbor_idx));
|
||||
template<typename Visitor>
|
||||
void visit(Visitor visitor)
|
||||
{
|
||||
// find the next unvisited facet and push the index
|
||||
auto facet = std::find(m_visited.begin() + m_seed, m_visited.end(), false);
|
||||
m_seed = facet - m_visited.begin();
|
||||
|
||||
if (facet != m_visited.end()) {
|
||||
// Skip this element in the next round.
|
||||
auto idx = m_seed ++;
|
||||
if (! visitor(idx))
|
||||
return;
|
||||
this->push(idx);
|
||||
m_visited[idx] = true;
|
||||
while (! m_facestack.empty()) {
|
||||
size_t facet_idx = this->pop();
|
||||
for (auto neighbor_idx : neighbor_index[facet_idx]) {
|
||||
assert(neighbor_idx < int(m_visited.size()));
|
||||
if (neighbor_idx >= 0 && !m_visited[neighbor_idx]) {
|
||||
if (! visitor(size_t(neighbor_idx)))
|
||||
return;
|
||||
m_visited[neighbor_idx] = true;
|
||||
this->push(stack_el(neighbor_idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
const indexed_triangle_set &its;
|
||||
const NeighborIndex &neighbor_index;
|
||||
|
||||
private:
|
||||
// If initialized with &&neighbor_index, take the ownership of the data.
|
||||
const NeighborIndex m_neighbor_index_data;
|
||||
|
||||
std::vector<char> m_visited;
|
||||
|
||||
using stack_el = size_t;
|
||||
std::vector<stack_el> m_facestack;
|
||||
void push(const stack_el &s) { m_facestack.emplace_back(s); }
|
||||
stack_el pop() { stack_el ret = m_facestack.back(); m_facestack.pop_back(); return ret; }
|
||||
|
||||
// Last face visited.
|
||||
size_t m_seed { 0 };
|
||||
};
|
||||
|
||||
} // namespace meshsplit_detail
|
||||
|
||||
// Funky wrapper for timinig of its_split() using various neighbor index creating methods, see sandboxes/its_neighbor_index/main.cpp
|
||||
template<class IndexT> struct ItsNeighborsWrapper
|
||||
{
|
||||
using Index = IndexT;
|
||||
const indexed_triangle_set *its;
|
||||
IndexT index;
|
||||
const indexed_triangle_set &its;
|
||||
const IndexT &index_ref;
|
||||
const IndexT index;
|
||||
|
||||
ItsNeighborsWrapper(const indexed_triangle_set &m, IndexT &&idx)
|
||||
: its{&m}, index{std::move(idx)}
|
||||
{}
|
||||
// Keeping a reference to index, the caller is responsible for keeping the index alive.
|
||||
ItsNeighborsWrapper(const indexed_triangle_set &its, const IndexT &index) : its{its}, index_ref{index} {}
|
||||
// Taking ownership of the index.
|
||||
ItsNeighborsWrapper(const indexed_triangle_set &its, IndexT &&aindex) : its{its}, index_ref{index}, index(std::move(aindex)) {}
|
||||
|
||||
const auto& get_its() const noexcept { return *its; }
|
||||
const auto& get_index() const noexcept { return index; }
|
||||
const auto& get_its() const noexcept { return its; }
|
||||
const auto& get_index() const noexcept { return index_ref; }
|
||||
};
|
||||
|
||||
// Splits a mesh into multiple meshes when possible.
|
||||
@ -97,20 +116,19 @@ void its_split(const Its &m, OutputIt out_it)
|
||||
|
||||
const indexed_triangle_set &its = ItsWithNeighborsIndex_<Its>::get_its(m);
|
||||
|
||||
std::vector<char> visited(its.indices.size(), false);
|
||||
|
||||
struct VertexConv {
|
||||
size_t part_id = std::numeric_limits<size_t>::max();
|
||||
size_t vertex_image;
|
||||
};
|
||||
std::vector<VertexConv> vidx_conv(its.vertices.size());
|
||||
|
||||
const auto& neighbor_index = ItsWithNeighborsIndex_<Its>::get_index(m);
|
||||
|
||||
meshsplit_detail::NeighborVisitor visitor(its, meshsplit_detail::ItsWithNeighborsIndex_<Its>::get_index(m));
|
||||
|
||||
std::vector<size_t> facets;
|
||||
for (size_t part_id = 0;; ++part_id) {
|
||||
std::vector<size_t> facets =
|
||||
its_find_unvisited_neighbors(its, neighbor_index, visited);
|
||||
|
||||
// Collect all faces of the next patch.
|
||||
facets.clear();
|
||||
visitor.visit([&facets](size_t idx) { facets.emplace_back(idx); return true; });
|
||||
if (facets.empty())
|
||||
break;
|
||||
|
||||
@ -150,17 +168,34 @@ std::vector<indexed_triangle_set> its_split(const Its &its)
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class Its> bool its_is_splittable(const Its &m)
|
||||
template<class Its>
|
||||
bool its_is_splittable(const Its &m)
|
||||
{
|
||||
using namespace meshsplit_detail;
|
||||
const indexed_triangle_set &its = ItsWithNeighborsIndex_<Its>::get_its(m);
|
||||
const auto& neighbor_index = ItsWithNeighborsIndex_<Its>::get_index(m);
|
||||
meshsplit_detail::NeighborVisitor visitor(meshsplit_detail::ItsWithNeighborsIndex_<Its>::get_its(m), meshsplit_detail::ItsWithNeighborsIndex_<Its>::get_index(m));
|
||||
bool has_some = false;
|
||||
bool has_some2 = false;
|
||||
// Traverse the 1st patch fully.
|
||||
visitor.visit([&has_some](size_t idx) { has_some = true; return true; });
|
||||
if (has_some)
|
||||
// Just check whether there is any face of the 2nd patch.
|
||||
visitor.visit([&has_some2](size_t idx) { has_some2 = true; return false; });
|
||||
return has_some && has_some2;
|
||||
}
|
||||
|
||||
std::vector<char> visited(its.indices.size(), false);
|
||||
its_find_unvisited_neighbors(its, neighbor_index, visited);
|
||||
auto faces = its_find_unvisited_neighbors(its, neighbor_index, visited);
|
||||
|
||||
return !faces.empty();
|
||||
template<class Its>
|
||||
size_t its_number_of_patches(const Its &m)
|
||||
{
|
||||
meshsplit_detail::NeighborVisitor visitor(meshsplit_detail::ItsWithNeighborsIndex_<Its>::get_its(m), meshsplit_detail::ItsWithNeighborsIndex_<Its>::get_index(m));
|
||||
size_t num_patches = 0;
|
||||
for (;;) {
|
||||
bool has_some = false;
|
||||
// Traverse the 1st patch fully.
|
||||
visitor.visit([&has_some](size_t idx) { has_some = true; return true; });
|
||||
if (! has_some)
|
||||
break;
|
||||
++ num_patches;
|
||||
}
|
||||
return num_patches;
|
||||
}
|
||||
|
||||
template<class ExPolicy>
|
||||
|
@ -475,10 +475,10 @@ bool Model::looks_like_imperial_units() const
|
||||
|
||||
void Model::convert_from_imperial_units(bool only_small_volumes)
|
||||
{
|
||||
static constexpr const double in_to_mm = 25.4;
|
||||
static constexpr const float in_to_mm = 25.4f;
|
||||
for (ModelObject* obj : this->objects)
|
||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_inches) {
|
||||
obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
|
||||
obj->scale_mesh_after_creation(in_to_mm);
|
||||
for (ModelVolume* v : obj->volumes) {
|
||||
assert(! v->source.is_converted_from_meters);
|
||||
v->source.is_converted_from_inches = true;
|
||||
@ -505,7 +505,7 @@ void Model::convert_from_meters(bool only_small_volumes)
|
||||
static constexpr const double m_to_mm = 1000;
|
||||
for (ModelObject* obj : this->objects)
|
||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_meters) {
|
||||
obj->scale_mesh_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm));
|
||||
obj->scale_mesh_after_creation(m_to_mm);
|
||||
for (ModelVolume* v : obj->volumes) {
|
||||
assert(! v->source.is_converted_from_inches);
|
||||
v->source.is_converted_from_meters = true;
|
||||
@ -1062,11 +1062,11 @@ void ModelObject::mirror(Axis axis)
|
||||
}
|
||||
|
||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||
void ModelObject::scale_mesh_after_creation(const Vec3d &versor)
|
||||
void ModelObject::scale_mesh_after_creation(const float scale)
|
||||
{
|
||||
for (ModelVolume *v : this->volumes) {
|
||||
v->scale_geometry_after_creation(versor);
|
||||
v->set_offset(versor.cwiseProduct(v->get_offset()));
|
||||
v->scale_geometry_after_creation(scale);
|
||||
v->set_offset(Vec3d(scale, scale, scale).cwiseProduct(v->get_offset()));
|
||||
}
|
||||
this->invalidate_bounding_box();
|
||||
}
|
||||
@ -1077,9 +1077,8 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
|
||||
|
||||
ModelObject* new_object = new_clone(*this);
|
||||
|
||||
double koef = conv_type == ConversionType::CONV_FROM_INCH ? 25.4 : conv_type == ConversionType::CONV_TO_INCH ? 0.0393700787 :
|
||||
conv_type == ConversionType::CONV_FROM_METER ? 1000 : conv_type == ConversionType::CONV_TO_METER ? 0.001 : 1;
|
||||
const Vec3d versor = Vec3d(koef, koef, koef);
|
||||
float koef = conv_type == ConversionType::CONV_FROM_INCH ? 25.4f : conv_type == ConversionType::CONV_TO_INCH ? 0.0393700787f :
|
||||
conv_type == ConversionType::CONV_FROM_METER ? 1000.f : conv_type == ConversionType::CONV_TO_METER ? 0.001f : 1.f;
|
||||
|
||||
new_object->set_model(nullptr);
|
||||
new_object->sla_support_points.clear();
|
||||
@ -1092,7 +1091,6 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
|
||||
for (ModelVolume* volume : volumes) {
|
||||
if (!volume->mesh().empty()) {
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
ModelVolume* vol = new_object->add_volume(mesh);
|
||||
vol->name = volume->name;
|
||||
@ -1118,8 +1116,8 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
|
||||
if (//vol->source.is_converted_from_inches != from_imperial &&
|
||||
(volume_idxs.empty() ||
|
||||
std::find(volume_idxs.begin(), volume_idxs.end(), vol_idx) != volume_idxs.end())) {
|
||||
vol->scale_geometry_after_creation(versor);
|
||||
vol->set_offset(versor.cwiseProduct(volume->get_offset()));
|
||||
vol->scale_geometry_after_creation(koef);
|
||||
vol->set_offset(Vec3d(koef, koef, koef).cwiseProduct(volume->get_offset()));
|
||||
if (conv_type == ConversionType::CONV_FROM_INCH || conv_type == ConversionType::CONV_TO_INCH)
|
||||
vol->source.is_converted_from_inches = conv_type == ConversionType::CONV_FROM_INCH;
|
||||
if (conv_type == ConversionType::CONV_FROM_METER || conv_type == ConversionType::CONV_TO_METER)
|
||||
@ -1164,14 +1162,6 @@ size_t ModelObject::parts_count() const
|
||||
return num;
|
||||
}
|
||||
|
||||
bool ModelObject::needed_repair() const
|
||||
{
|
||||
for (const ModelVolume *v : this->volumes)
|
||||
if (v->is_model_part() && v->mesh().needed_repair())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes)
|
||||
{
|
||||
if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
@ -1253,21 +1243,14 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
{
|
||||
indexed_triangle_set upper_its, lower_its;
|
||||
mesh.require_shared_vertices();
|
||||
cut_mesh(mesh.its, float(z), &upper_its, &lower_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
upper_mesh.repair();
|
||||
upper_mesh.reset_repair_stats();
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
lower_mesh.repair();
|
||||
lower_mesh.reset_repair_stats();
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper_mesh.facets_count() > 0) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && ! upper_mesh.empty()) {
|
||||
ModelVolume* vol = upper->add_volume(upper_mesh);
|
||||
vol->name = volume->name;
|
||||
// Don't copy the config's ID.
|
||||
@ -1276,7 +1259,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
|
||||
assert(vol->config.id() != volume->config.id());
|
||||
vol->set_material(volume->material_id(), *volume->material());
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower_mesh.facets_count() > 0) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && ! lower_mesh.empty()) {
|
||||
ModelVolume* vol = lower->add_volume(lower_mesh);
|
||||
vol->name = volume->name;
|
||||
// Don't copy the config's ID.
|
||||
@ -1346,24 +1329,22 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
|
||||
if (volume->type() != ModelVolumeType::MODEL_PART)
|
||||
continue;
|
||||
|
||||
TriangleMeshPtrs meshptrs = volume->mesh().split();
|
||||
std::vector<TriangleMesh> meshes = volume->mesh().split();
|
||||
size_t counter = 1;
|
||||
for (TriangleMesh* mesh : meshptrs) {
|
||||
|
||||
for (TriangleMesh &mesh : meshes) {
|
||||
// FIXME: crashes if not satisfied
|
||||
if (mesh->facets_count() < 3) continue;
|
||||
|
||||
mesh->repair();
|
||||
if (mesh.facets_count() < 3)
|
||||
continue;
|
||||
|
||||
// XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed?
|
||||
ModelObject* new_object = m_model->add_object();
|
||||
if (meshptrs.size() == 1) {
|
||||
if (meshes.size() == 1) {
|
||||
new_object->name = volume->name;
|
||||
// Don't copy the config's ID.
|
||||
new_object->config.assign_config(this->config.size() > 0 ? this->config : volume->config);
|
||||
}
|
||||
else {
|
||||
new_object->name = this->name + (meshptrs.size() > 1 ? "_" + std::to_string(counter++) : "");
|
||||
new_object->name = this->name + (meshes.size() > 1 ? "_" + std::to_string(counter++) : "");
|
||||
// Don't copy the config's ID.
|
||||
new_object->config.assign_config(this->config);
|
||||
}
|
||||
@ -1372,7 +1353,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
|
||||
new_object->instances.reserve(this->instances.size());
|
||||
for (const ModelInstance* model_instance : this->instances)
|
||||
new_object->add_instance(*model_instance);
|
||||
ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh));
|
||||
ModelVolume* new_vol = new_object->add_volume(*volume, std::move(mesh));
|
||||
|
||||
for (ModelInstance* model_instance : new_object->instances)
|
||||
{
|
||||
@ -1384,7 +1365,6 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
|
||||
// reset the source to disable reload from disk
|
||||
new_vol->source = ModelVolume::Source();
|
||||
new_objects->emplace_back(new_object);
|
||||
delete mesh;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1402,7 +1382,6 @@ void ModelObject::merge()
|
||||
for (ModelVolume* volume : volumes)
|
||||
if (!volume->mesh().empty())
|
||||
mesh.merge(volume->mesh());
|
||||
mesh.repair();
|
||||
|
||||
this->clear_volumes();
|
||||
ModelVolume* vol = this->add_volume(mesh);
|
||||
@ -1569,7 +1548,6 @@ void ModelObject::print_info() const
|
||||
boost::nowide::cout << "[" << boost::filesystem::path(this->input_file).filename().string() << "]" << endl;
|
||||
|
||||
TriangleMesh mesh = this->raw_mesh();
|
||||
mesh.check_topology();
|
||||
BoundingBoxf3 bb = mesh.bounding_box();
|
||||
Vec3d size = bb.size();
|
||||
cout << "size_x = " << size(0) << endl;
|
||||
@ -1582,19 +1560,18 @@ void ModelObject::print_info() const
|
||||
cout << "max_y = " << bb.max(1) << endl;
|
||||
cout << "max_z = " << bb.max(2) << endl;
|
||||
cout << "number_of_facets = " << mesh.facets_count() << endl;
|
||||
cout << "manifold = " << (mesh.is_manifold() ? "yes" : "no") << endl;
|
||||
|
||||
cout << "manifold = " << (mesh.stats().manifold() ? "yes" : "no") << endl;
|
||||
if (! mesh.stats().manifold())
|
||||
cout << "open_edges = " << mesh.stats().open_edges << endl;
|
||||
|
||||
mesh.repair(); // this calculates number_of_parts
|
||||
if (mesh.needed_repair()) {
|
||||
mesh.repair();
|
||||
if (mesh.stats().repaired()) {
|
||||
if (mesh.stats().degenerate_facets > 0)
|
||||
cout << "degenerate_facets = " << mesh.stats().degenerate_facets << endl;
|
||||
if (mesh.stats().edges_fixed > 0)
|
||||
cout << "edges_fixed = " << mesh.stats().edges_fixed << endl;
|
||||
if (mesh.stats().facets_removed > 0)
|
||||
cout << "facets_removed = " << mesh.stats().facets_removed << endl;
|
||||
if (mesh.stats().facets_added > 0)
|
||||
cout << "facets_added = " << mesh.stats().facets_added << endl;
|
||||
if (mesh.stats().facets_reversed > 0)
|
||||
cout << "facets_reversed = " << mesh.stats().facets_reversed << endl;
|
||||
if (mesh.stats().backwards_edges > 0)
|
||||
@ -1624,24 +1601,24 @@ std::string ModelObject::get_export_filename() const
|
||||
return ret;
|
||||
}
|
||||
|
||||
stl_stats ModelObject::get_object_stl_stats() const
|
||||
TriangleMeshStats ModelObject::get_object_stl_stats() const
|
||||
{
|
||||
if (this->volumes.size() == 1)
|
||||
return this->volumes[0]->mesh().stats();
|
||||
|
||||
stl_stats full_stats;
|
||||
TriangleMeshStats full_stats;
|
||||
full_stats.volume = 0.f;
|
||||
|
||||
// fill full_stats from all objet's meshes
|
||||
for (ModelVolume* volume : this->volumes)
|
||||
{
|
||||
const stl_stats& stats = volume->mesh().stats();
|
||||
const TriangleMeshStats& stats = volume->mesh().stats();
|
||||
|
||||
// initialize full_stats (for repaired errors)
|
||||
full_stats.open_edges += stats.open_edges;
|
||||
full_stats.degenerate_facets += stats.degenerate_facets;
|
||||
full_stats.edges_fixed += stats.edges_fixed;
|
||||
full_stats.facets_removed += stats.facets_removed;
|
||||
full_stats.facets_added += stats.facets_added;
|
||||
full_stats.facets_reversed += stats.facets_reversed;
|
||||
full_stats.backwards_edges += stats.backwards_edges;
|
||||
|
||||
@ -1660,10 +1637,10 @@ int ModelObject::get_mesh_errors_count(const int vol_idx /*= -1*/) const
|
||||
if (vol_idx >= 0)
|
||||
return this->volumes[vol_idx]->get_mesh_errors_count();
|
||||
|
||||
const stl_stats& stats = get_object_stl_stats();
|
||||
const TriangleMeshStats& stats = get_object_stl_stats();
|
||||
|
||||
return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
|
||||
stats.facets_added + stats.facets_reversed + stats.backwards_edges;
|
||||
stats.facets_reversed + stats.backwards_edges;
|
||||
}
|
||||
|
||||
void ModelVolume::set_material_id(t_model_material_id material_id)
|
||||
@ -1727,14 +1704,15 @@ void ModelVolume::center_geometry_after_creation(bool update_source_offset)
|
||||
void ModelVolume::calculate_convex_hull()
|
||||
{
|
||||
m_convex_hull = std::make_shared<TriangleMesh>(this->mesh().convex_hull_3d());
|
||||
assert(m_convex_hull.get());
|
||||
}
|
||||
|
||||
int ModelVolume::get_mesh_errors_count() const
|
||||
{
|
||||
const stl_stats &stats = this->mesh().stats();
|
||||
const TriangleMeshStats &stats = this->mesh().stats();
|
||||
|
||||
return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
|
||||
stats.facets_added + stats.facets_reversed + stats.backwards_edges;
|
||||
stats.facets_reversed + stats.backwards_edges;
|
||||
}
|
||||
|
||||
const TriangleMesh& ModelVolume::get_convex_hull() const
|
||||
@ -1782,11 +1760,9 @@ std::string ModelVolume::type_to_string(const ModelVolumeType t)
|
||||
// This is useful to assign different materials to different volumes of an object.
|
||||
size_t ModelVolume::split(unsigned int max_extruders)
|
||||
{
|
||||
TriangleMeshPtrs meshptrs = this->mesh().split();
|
||||
if (meshptrs.size() <= 1) {
|
||||
delete meshptrs.front();
|
||||
std::vector<TriangleMesh> meshes = this->mesh().split();
|
||||
if (meshes.size() <= 1)
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin();
|
||||
@ -1795,15 +1771,14 @@ size_t ModelVolume::split(unsigned int max_extruders)
|
||||
unsigned int extruder_counter = 0;
|
||||
Vec3d offset = this->get_offset();
|
||||
|
||||
for (TriangleMesh *mesh : meshptrs) {
|
||||
mesh->repair();
|
||||
if (mesh->empty())
|
||||
for (TriangleMesh &mesh : meshes) {
|
||||
if (mesh.empty())
|
||||
// Repair may have removed unconnected triangles, thus emptying the mesh.
|
||||
continue;
|
||||
|
||||
if (idx == 0)
|
||||
{
|
||||
this->set_mesh(std::move(*mesh));
|
||||
this->set_mesh(std::move(mesh));
|
||||
this->calculate_convex_hull();
|
||||
// Assign a new unique ID, so that a new GLVolume will be generated.
|
||||
this->set_new_unique_id();
|
||||
@ -1811,7 +1786,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
|
||||
this->source = ModelVolume::Source();
|
||||
}
|
||||
else
|
||||
this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(*mesh)));
|
||||
this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(mesh)));
|
||||
|
||||
this->object->volumes[ivolume]->set_offset(Vec3d::Zero());
|
||||
this->object->volumes[ivolume]->center_geometry_after_creation();
|
||||
@ -1819,7 +1794,6 @@ size_t ModelVolume::split(unsigned int max_extruders)
|
||||
this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1);
|
||||
this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
||||
this->object->volumes[ivolume]->m_is_splittable = 0;
|
||||
delete mesh;
|
||||
++ idx;
|
||||
}
|
||||
|
||||
@ -1888,7 +1862,7 @@ void ModelVolume::mirror(Axis axis)
|
||||
}
|
||||
|
||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||
void ModelVolume::scale_geometry_after_creation(const Vec3d& versor)
|
||||
void ModelVolume::scale_geometry_after_creation(const Vec3f& versor)
|
||||
{
|
||||
const_cast<TriangleMesh*>(m_mesh.get())->scale(versor);
|
||||
const_cast<TriangleMesh*>(m_convex_hull.get())->scale(versor);
|
||||
@ -1921,8 +1895,7 @@ void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_hand
|
||||
void ModelVolume::convert_from_imperial_units()
|
||||
{
|
||||
assert(! this->source.is_converted_from_meters);
|
||||
double in_to_mm = 25.4;
|
||||
this->scale_geometry_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
|
||||
this->scale_geometry_after_creation(25.4f);
|
||||
this->set_offset(Vec3d(0, 0, 0));
|
||||
this->source.is_converted_from_inches = true;
|
||||
}
|
||||
@ -1930,8 +1903,7 @@ void ModelVolume::convert_from_imperial_units()
|
||||
void ModelVolume::convert_from_meters()
|
||||
{
|
||||
assert(! this->source.is_converted_from_inches);
|
||||
double m_to_mm = 1000;
|
||||
this->scale_geometry_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm));
|
||||
this->scale_geometry_after_creation(1000.f);
|
||||
this->set_offset(Vec3d(0, 0, 0));
|
||||
this->source.is_converted_from_meters = true;
|
||||
}
|
||||
|
@ -346,13 +346,12 @@ public:
|
||||
void mirror(Axis axis);
|
||||
|
||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||
void scale_mesh_after_creation(const Vec3d& versor);
|
||||
void scale_mesh_after_creation(const float scale);
|
||||
void convert_units(ModelObjectPtrs&new_objects, ConversionType conv_type, std::vector<int> volume_idxs);
|
||||
|
||||
size_t materials_count() const;
|
||||
size_t facets_count() const;
|
||||
size_t parts_count() const;
|
||||
bool needed_repair() const;
|
||||
ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes);
|
||||
void split(ModelObjectPtrs* new_objects);
|
||||
void merge();
|
||||
@ -376,7 +375,7 @@ public:
|
||||
std::string get_export_filename() const;
|
||||
|
||||
// Get full stl statistics for all object's meshes
|
||||
stl_stats get_object_stl_stats() const;
|
||||
TriangleMeshStats get_object_stl_stats() const;
|
||||
// Get count of errors in the mesh( or all object's meshes, if volume index isn't defined)
|
||||
int get_mesh_errors_count(const int vol_idx = -1) const;
|
||||
|
||||
@ -620,6 +619,8 @@ public:
|
||||
const TriangleMesh& mesh() const { return *m_mesh.get(); }
|
||||
void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
|
||||
void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
|
||||
void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
|
||||
void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
|
||||
void set_mesh(std::shared_ptr<const TriangleMesh> &mesh) { m_mesh = mesh; }
|
||||
void set_mesh(std::unique_ptr<const TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
|
||||
void reset_mesh() { m_mesh = std::make_shared<const TriangleMesh>(); }
|
||||
@ -670,7 +671,8 @@ public:
|
||||
void mirror(Axis axis);
|
||||
|
||||
// This method could only be called before the meshes of this ModelVolumes are not shared!
|
||||
void scale_geometry_after_creation(const Vec3d& versor);
|
||||
void scale_geometry_after_creation(const Vec3f &versor);
|
||||
void scale_geometry_after_creation(const float scale) { this->scale_geometry_after_creation(Vec3f(scale, scale, scale)); }
|
||||
|
||||
// Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box.
|
||||
// Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared!
|
||||
|
@ -418,7 +418,7 @@ template<class Tout = double,
|
||||
class = FloatingOnly<Tout>>
|
||||
inline constexpr Tout unscaled(const Tin &v) noexcept
|
||||
{
|
||||
return Tout(v * Tout(SCALING_FACTOR));
|
||||
return Tout(v) * Tout(SCALING_FACTOR);
|
||||
}
|
||||
|
||||
// Unscaling for Eigen vectors. Input base type can be arithmetic, output base
|
||||
@ -432,7 +432,7 @@ template<class Tout = double,
|
||||
inline constexpr Eigen::Matrix<Tout, N, EigenArgs...>
|
||||
unscaled(const Eigen::Matrix<Tin, N, EigenArgs...> &v) noexcept
|
||||
{
|
||||
return v.template cast<Tout>() * SCALING_FACTOR;
|
||||
return v.template cast<Tout>() * Tout(SCALING_FACTOR);
|
||||
}
|
||||
|
||||
// Align a coordinate to a grid. The coordinate may be negative,
|
||||
|
@ -1300,8 +1300,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
num_extruders,
|
||||
painting_extruders,
|
||||
*print_object_regions,
|
||||
[&print_object, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) {
|
||||
update_apply_status(print_object.invalidate_state_by_config_options(old_config, new_config, diff_keys));
|
||||
[&print_object, it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) {
|
||||
for (auto it = it_print_object; it != it_print_object_end; ++it)
|
||||
if ((*it)->m_shared_regions != nullptr)
|
||||
update_apply_status((*it)->invalidate_state_by_config_options(old_config, new_config, diff_keys));
|
||||
})) {
|
||||
// Regions are valid, just keep them.
|
||||
} else {
|
||||
|
@ -1447,7 +1447,7 @@ void PrintObject::bridge_over_infill()
|
||||
Polygons to_bridge_pp = internal_solid;
|
||||
|
||||
// iterate through lower layers spanned by bridge_flow
|
||||
double bottom_z = layer->print_z - bridge_flow.height();
|
||||
double bottom_z = layer->print_z - bridge_flow.height() - EPSILON;
|
||||
for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) {
|
||||
const Layer* lower_layer = m_layers[i];
|
||||
|
||||
|
@ -286,8 +286,6 @@ void cut_drainholes(std::vector<ExPolygons> & obj_slices,
|
||||
|
||||
if (mesh.empty()) return;
|
||||
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
std::vector<ExPolygons> hole_slices = slice_mesh_ex(mesh.its, slicegrid, closing_radius, thr);
|
||||
|
||||
if (obj_slices.size() != hole_slices.size())
|
||||
@ -316,7 +314,6 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags)
|
||||
remove_inside_triangles(mesh, interior);
|
||||
|
||||
mesh.merge(TriangleMesh{interior.mesh});
|
||||
mesh.require_shared_vertices();
|
||||
}
|
||||
|
||||
// Get the distance of p to the interior's zero iso_surface. Interior should
|
||||
@ -557,8 +554,7 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
|
||||
new_faces = {};
|
||||
|
||||
mesh = TriangleMesh{mesh.its};
|
||||
mesh.repaired = true;
|
||||
mesh.require_shared_vertices();
|
||||
//FIXME do we want to repair the mesh? Are there duplicate vertices or flipped triangles?
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
@ -33,7 +33,6 @@ inline void reproject_points_and_holes(ModelObject *object)
|
||||
if (!object || (!has_holes && !has_sppoints)) return;
|
||||
|
||||
TriangleMesh rmsh = object->raw_mesh();
|
||||
rmsh.require_shared_vertices();
|
||||
IndexedMesh emesh{rmsh};
|
||||
|
||||
if (has_sppoints)
|
||||
|
@ -205,7 +205,6 @@ inline bool is_on_floor(const SLAPrintObjectConfig &cfg)
|
||||
std::vector<XYRotation> get_chull_rotations(const TriangleMesh &mesh, size_t max_count)
|
||||
{
|
||||
TriangleMesh chull = mesh.convex_hull_3d();
|
||||
chull.require_shared_vertices();
|
||||
double chull2d_area = chull.convex_hull().area();
|
||||
double area_threshold = chull2d_area / (scaled<double>(1e3) * scaled(1.));
|
||||
|
||||
@ -299,7 +298,6 @@ struct RotfinderBoilerplate {
|
||||
static TriangleMesh get_mesh_to_rotate(const ModelObject &mo)
|
||||
{
|
||||
TriangleMesh mesh = mo.raw_mesh();
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
ModelInstance *mi = mo.instances[0];
|
||||
auto rotation = Vec3d::Zero();
|
||||
@ -437,7 +435,6 @@ Vec2d find_min_z_height_rotation(const ModelObject &mo,
|
||||
RotfinderBoilerplate<1000> bp{mo, params};
|
||||
|
||||
TriangleMesh chull = bp.mesh.convex_hull_3d();
|
||||
chull.require_shared_vertices();
|
||||
auto inputs = reserve_vector<XYRotation>(chull.its.indices.size());
|
||||
auto rotcmp = [](const XYRotation &r1, const XYRotation &r2) {
|
||||
double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y];
|
||||
|
@ -896,7 +896,6 @@ SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object)
|
||||
obj = m_model_object->raw_mesh();
|
||||
if (!obj.empty()) {
|
||||
obj.transform(m_trafo);
|
||||
obj.require_shared_vertices();
|
||||
}
|
||||
})
|
||||
{}
|
||||
|
@ -323,7 +323,6 @@ private:
|
||||
{
|
||||
support_tree_ptr = sla::SupportTree::create(*this, ctl);
|
||||
tree_mesh = TriangleMesh{support_tree_ptr->retrieve_mesh(sla::MeshType::Support)};
|
||||
tree_mesh.require_shared_vertices();
|
||||
return support_tree_ptr;
|
||||
}
|
||||
|
||||
|
@ -526,7 +526,6 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
|
||||
}
|
||||
auto thr = [this]() { m_print->throw_if_canceled(); };
|
||||
auto &slice_grid = po.m_model_height_levels;
|
||||
assert(mesh.has_shared_vertices());
|
||||
po.m_model_slices = slice_mesh_ex(mesh.its, slice_grid, params, thr);
|
||||
|
||||
sla::Interior *interior = po.m_hollowing_data ?
|
||||
|
@ -14,10 +14,8 @@ void simplify_mesh(indexed_triangle_set &);
|
||||
|
||||
template<class...Args> void simplify_mesh(TriangleMesh &m, Args &&...a)
|
||||
{
|
||||
m.require_shared_vertices();
|
||||
simplify_mesh(m.its, std::forward<Args>(a)...);
|
||||
m = TriangleMesh{m.its};
|
||||
m.require_shared_vertices();
|
||||
m = TriangleMesh{ std::move(m.its) };
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -721,9 +721,9 @@ public:
|
||||
#ifdef SUPPORT_USE_AGG_RASTERIZER
|
||||
m_bbox = bbox;
|
||||
// Oversample the grid to avoid leaking of supports through or around the object walls.
|
||||
int oversampling = std::min(8, int(scale_(m_support_spacing) / (scale_(params.extrusion_width) + 100)));
|
||||
m_pixel_size = scale_(m_support_spacing / oversampling);
|
||||
assert(scale_(params.extrusion_width) + 20 < m_pixel_size);
|
||||
int extrusion_width_scaled = scale_(params.extrusion_width);
|
||||
int oversampling = std::clamp(int(scale_(m_support_spacing) / (extrusion_width_scaled + 100)), 1, 8);
|
||||
m_pixel_size = std::max<double>(extrusion_width_scaled + 21, scale_(m_support_spacing / oversampling));
|
||||
// Add one empty column / row boundaries.
|
||||
m_bbox.offset(m_pixel_size);
|
||||
// Grid size fitting the support polygons plus one pixel boundary around the polygons.
|
||||
@ -2600,8 +2600,6 @@ void PrintObjectSupportMaterial::generate_base_layers(
|
||||
// No top contacts -> no intermediate layers will be produced.
|
||||
return;
|
||||
|
||||
// coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_base_layers() in parallel - start";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, intermediate_layers.size()),
|
||||
@ -2696,6 +2694,7 @@ void PrintObjectSupportMaterial::generate_base_layers(
|
||||
layer_intermediate.layer_type = sltBase;
|
||||
|
||||
#if 0
|
||||
// coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing);
|
||||
// Fillet the base polygons and trim them again with the top, interface and contact layers.
|
||||
$base->{$i} = diff(
|
||||
offset2(
|
||||
@ -3784,7 +3783,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||
// Prepare fillers.
|
||||
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
|
||||
bool with_sheath = m_object_config->support_material_with_sheath;
|
||||
InfillPattern infill_pattern = (support_pattern == smpHoneycomb ? ipHoneycomb : ipSupportBase);
|
||||
InfillPattern infill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (support_density < 1.05 ? ipRectilinear : ipSupportBase);
|
||||
std::vector<float> angles;
|
||||
angles.push_back(base_angle);
|
||||
|
||||
|
@ -41,8 +41,8 @@
|
||||
//====================
|
||||
#define ENABLE_2_4_0_ALPHA1 1
|
||||
|
||||
// Enable the fix for exporting and importing to/from 3mf file of mirrored volumes
|
||||
#define ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT (1 && ENABLE_2_4_0_ALPHA1)
|
||||
// Enable implementation of retract acceleration in gcode processor
|
||||
#define ENABLE_RETRACT_ACCELERATION (1 && ENABLE_2_4_0_ALPHA1)
|
||||
// Enable rendering seams (and other options) in preview using models
|
||||
#define ENABLE_SEAMS_USING_MODELS (1 && ENABLE_2_4_0_ALPHA1)
|
||||
// Enable save and save as commands to be enabled also when the plater is empty and allow to load empty projects
|
||||
@ -60,6 +60,8 @@
|
||||
#define ENABLE_FIX_PREVIEW_OPTIONS_Z (1 && ENABLE_SEAMS_USING_MODELS && ENABLE_2_4_0_ALPHA2)
|
||||
// Enable replacing a missing file during reload from disk command
|
||||
#define ENABLE_RELOAD_FROM_DISK_REPLACE_FILE (1 && ENABLE_2_4_0_ALPHA2)
|
||||
// Enable fixing the synchronization of seams with the horizontal slider in preview
|
||||
#define ENABLE_FIX_SEAMS_SYNCH (1 && ENABLE_2_4_0_ALPHA2)
|
||||
// Enable changes in preview layout
|
||||
#define ENABLE_PREVIEW_LAYOUT (1 && ENABLE_2_4_0_ALPHA2)
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -15,25 +15,79 @@ namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
class TriangleMeshSlicer;
|
||||
typedef std::vector<TriangleMesh*> TriangleMeshPtrs;
|
||||
|
||||
struct TriangleMeshStats {
|
||||
// Mesh metrics.
|
||||
uint32_t number_of_facets = 0;
|
||||
stl_vertex max = stl_vertex::Zero();
|
||||
stl_vertex min = stl_vertex::Zero();
|
||||
stl_vertex size = stl_vertex::Zero();
|
||||
float volume = -1.f;
|
||||
int number_of_parts = 0;
|
||||
|
||||
// Mesh errors, remaining.
|
||||
int open_edges = 0;
|
||||
|
||||
// Mesh errors, fixed.
|
||||
// How many edges were united by merging their end points with some other end points in epsilon neighborhood?
|
||||
int edges_fixed = 0;
|
||||
// How many degenerate faces were removed?
|
||||
int degenerate_facets = 0;
|
||||
// How many faces were removed during fixing? Includes degenerate_faces and disconnected faces.
|
||||
int facets_removed = 0;
|
||||
// New faces could only be created with stl_fill_holes() and we ditched stl_fill_holes(), because mostly it does more harm than good.
|
||||
//int facets_added = 0;
|
||||
// How many facets were revesed? Faces are reversed by admesh while it connects patches of triangles togeter and a flipped triangle is encountered.
|
||||
// Also the facets are reversed when a negative volume is corrected by flipping all facets.
|
||||
int facets_reversed = 0;
|
||||
// Edges shared by two triangles, oriented incorrectly.
|
||||
int backwards_edges = 0;
|
||||
|
||||
void clear() { *this = TriangleMeshStats(); }
|
||||
|
||||
TriangleMeshStats merge(const TriangleMeshStats &rhs) const {
|
||||
if (this->number_of_facets == 0)
|
||||
return rhs;
|
||||
else if (rhs.number_of_facets == 0)
|
||||
return *this;
|
||||
else {
|
||||
TriangleMeshStats out;
|
||||
out.number_of_facets = this->number_of_facets + rhs.number_of_facets;
|
||||
out.min = this->min.cwiseMin(rhs.min);
|
||||
out.max = this->max.cwiseMax(rhs.max);
|
||||
out.size = out.max - out.min;
|
||||
out.number_of_parts = this->number_of_parts + rhs.number_of_parts;
|
||||
out.open_edges = this->open_edges + rhs.open_edges;
|
||||
out.volume = this->volume + rhs.volume;
|
||||
out.edges_fixed = this->edges_fixed + rhs.edges_fixed;
|
||||
out.degenerate_facets = this->degenerate_facets + rhs.degenerate_facets;
|
||||
out.facets_removed = this->facets_removed + rhs.facets_removed;
|
||||
out.facets_reversed = this->facets_reversed + rhs.facets_reversed;
|
||||
out.backwards_edges = this->backwards_edges + rhs.backwards_edges;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
bool manifold() const { return open_edges == 0; }
|
||||
bool repaired() const { return degenerate_facets > 0 || edges_fixed > 0 || facets_removed > 0 || facets_reversed > 0 || backwards_edges > 0; }
|
||||
};
|
||||
|
||||
class TriangleMesh
|
||||
{
|
||||
public:
|
||||
TriangleMesh() : repaired(false) {}
|
||||
TriangleMesh(const Pointf3s &points, const std::vector<Vec3i> &facets);
|
||||
TriangleMesh() = default;
|
||||
TriangleMesh(const std::vector<Vec3f> &vertices, const std::vector<Vec3i> &faces);
|
||||
TriangleMesh(std::vector<Vec3f> &&vertices, const std::vector<Vec3i> &&faces);
|
||||
explicit TriangleMesh(const indexed_triangle_set &M);
|
||||
void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; }
|
||||
bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); }
|
||||
bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); }
|
||||
bool write_binary(const char* output_file) { return stl_write_binary(&this->stl, output_file, ""); }
|
||||
void repair(bool update_shared_vertices = true);
|
||||
explicit TriangleMesh(indexed_triangle_set &&M);
|
||||
void clear() { this->its.clear(); this->m_stats.clear(); }
|
||||
bool ReadSTLFile(const char* input_file, bool repair = true);
|
||||
bool write_ascii(const char* output_file);
|
||||
bool write_binary(const char* output_file);
|
||||
float volume();
|
||||
void check_topology();
|
||||
bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; }
|
||||
void WriteOBJFile(const char* output_file) const;
|
||||
void scale(float factor);
|
||||
void scale(const Vec3d &versor);
|
||||
void scale(const Vec3f &versor);
|
||||
void translate(float x, float y, float z);
|
||||
void translate(const Vec3f &displacement);
|
||||
void rotate(float angle, const Axis &axis);
|
||||
@ -41,15 +95,17 @@ public:
|
||||
void rotate_x(float angle) { this->rotate(angle, X); }
|
||||
void rotate_y(float angle) { this->rotate(angle, Y); }
|
||||
void rotate_z(float angle) { this->rotate(angle, Z); }
|
||||
void mirror(const Axis &axis);
|
||||
void mirror(const Axis axis);
|
||||
void mirror_x() { this->mirror(X); }
|
||||
void mirror_y() { this->mirror(Y); }
|
||||
void mirror_z() { this->mirror(Z); }
|
||||
void transform(const Transform3d& t, bool fix_left_handed = false);
|
||||
void transform(const Matrix3d& t, bool fix_left_handed = false);
|
||||
// Flip triangles, negate volume.
|
||||
void flip_triangles();
|
||||
void align_to_origin();
|
||||
void rotate(double angle, Point* center);
|
||||
TriangleMeshPtrs split() const;
|
||||
std::vector<TriangleMesh> split() const;
|
||||
void merge(const TriangleMesh &mesh);
|
||||
ExPolygons horizontal_projection() const;
|
||||
// 2D convex hull of a 3D mesh projected into the Z=0 plane.
|
||||
@ -58,37 +114,33 @@ public:
|
||||
// Returns the bbox of this TriangleMesh transformed by the given transformation
|
||||
BoundingBoxf3 transformed_bounding_box(const Transform3d &trafo) const;
|
||||
// Return the size of the mesh in coordinates.
|
||||
Vec3d size() const { return stl.stats.size.cast<double>(); }
|
||||
Vec3d size() const { return m_stats.size.cast<double>(); }
|
||||
/// Return the center of the related bounding box.
|
||||
Vec3d center() const { return this->bounding_box().center(); }
|
||||
// Returns the convex hull of this TriangleMesh
|
||||
TriangleMesh convex_hull_3d() const;
|
||||
// Slice this mesh at the provided Z levels and return the vector
|
||||
std::vector<ExPolygons> slice(const std::vector<double>& z) const;
|
||||
void reset_repair_stats();
|
||||
bool needed_repair() const;
|
||||
void require_shared_vertices();
|
||||
bool has_shared_vertices() const { return ! this->its.vertices.empty(); }
|
||||
size_t facets_count() const { return this->stl.stats.number_of_facets; }
|
||||
size_t facets_count() const { assert(m_stats.number_of_facets == this->its.indices.size()); return m_stats.number_of_facets; }
|
||||
bool empty() const { return this->facets_count() == 0; }
|
||||
bool is_splittable() const;
|
||||
bool repaired() const;
|
||||
bool is_splittable() const;
|
||||
// Estimate of the memory occupied by this structure, important for keeping an eye on the Undo / Redo stack allocation.
|
||||
size_t memsize() const;
|
||||
// Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released.
|
||||
size_t release_optional();
|
||||
// Restore optional data possibly released by release_optional().
|
||||
void restore_optional();
|
||||
|
||||
const stl_stats& stats() const { return this->stl.stats; }
|
||||
// Used by the Undo / Redo stack, legacy interface. As of now there is nothing cached at TriangleMesh,
|
||||
// but we may decide to cache some data in the future (for example normals), thus we keep the interface in place.
|
||||
// Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released.
|
||||
size_t release_optional() { return 0; }
|
||||
// Restore optional data possibly released by release_optional().
|
||||
void restore_optional() {}
|
||||
|
||||
const TriangleMeshStats& stats() const { return m_stats; }
|
||||
|
||||
indexed_triangle_set its;
|
||||
bool repaired;
|
||||
|
||||
//private:
|
||||
stl_file stl;
|
||||
|
||||
private:
|
||||
std::deque<uint32_t> find_unvisited_neighbors(std::vector<unsigned char> &facet_visited) const;
|
||||
TriangleMeshStats m_stats;
|
||||
};
|
||||
|
||||
// Index of face indices incident with a vertex index.
|
||||
@ -148,8 +200,18 @@ bool its_store_triangle(const indexed_triangle_set &its, const char *obj_filenam
|
||||
bool its_store_triangles(const indexed_triangle_set &its, const char *obj_filename, const std::vector<size_t>& triangles);
|
||||
|
||||
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its);
|
||||
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its, std::vector<Vec3i> &face_neighbors);
|
||||
|
||||
// Number of disconnected patches (faces are connected if they share an edge, shared edge defined with 2 shared vertex indices).
|
||||
bool its_number_of_patches(const indexed_triangle_set &its);
|
||||
bool its_number_of_patches(const indexed_triangle_set &its, const std::vector<Vec3i> &face_neighbors);
|
||||
// Same as its_number_of_patches(its) > 1, but faster.
|
||||
bool its_is_splittable(const indexed_triangle_set &its);
|
||||
bool its_is_splittable(const indexed_triangle_set &its, const std::vector<Vec3i> &face_neighbors);
|
||||
|
||||
// Calculate number of unconnected face edges. There should be no unconnected edge in a manifold mesh.
|
||||
size_t its_num_open_edges(const indexed_triangle_set &its);
|
||||
size_t its_num_open_edges(const std::vector<Vec3i> &face_neighbors);
|
||||
|
||||
// Shrink the vectors of its.vertices and its.faces to a minimum size by reallocating the two vectors.
|
||||
void its_shrink_to_fit(indexed_triangle_set &its);
|
||||
@ -217,13 +279,23 @@ inline Vec3f its_face_normal(const indexed_triangle_set &its, const int face_idx
|
||||
{ return its_face_normal(its, its.indices[face_idx]); }
|
||||
|
||||
indexed_triangle_set its_make_cube(double x, double y, double z);
|
||||
TriangleMesh make_cube(double x, double y, double z);
|
||||
indexed_triangle_set its_make_prism(float width, float length, float height);
|
||||
indexed_triangle_set its_make_cylinder(double r, double h, double fa=(2*PI/360));
|
||||
TriangleMesh make_cylinder(double r, double h, double fa=(2*PI/360));
|
||||
indexed_triangle_set its_make_cone(double r, double h, double fa=(2*PI/360));
|
||||
TriangleMesh make_cone(double r, double h, double fa=(2*PI/360));
|
||||
indexed_triangle_set its_make_pyramid(float base, float height);
|
||||
indexed_triangle_set its_make_sphere(double radius, double fa);
|
||||
TriangleMesh make_sphere(double rho, double fa=(2*PI/360));
|
||||
|
||||
inline TriangleMesh make_cube(double x, double y, double z) { return TriangleMesh(its_make_cube(x, y, z)); }
|
||||
inline TriangleMesh make_prism(float width, float length, float height) { return TriangleMesh(its_make_prism(width, length, height)); }
|
||||
inline TriangleMesh make_cylinder(double r, double h, double fa=(2*PI/360)) { return TriangleMesh{its_make_cylinder(r, h, fa)}; }
|
||||
inline TriangleMesh make_cone(double r, double h, double fa=(2*PI/360)) { return TriangleMesh(its_make_cone(r, h, fa)); }
|
||||
inline TriangleMesh make_pyramid(float base, float height) { return TriangleMesh(its_make_pyramid(base, height)); }
|
||||
inline TriangleMesh make_sphere(double rho, double fa=(2*PI/360)) { return TriangleMesh(its_make_sphere(rho, fa)); }
|
||||
|
||||
bool its_write_stl_ascii(const char *file, const char *label, const std::vector<stl_triangle_vertex_indices> &indices, const std::vector<stl_vertex> &vertices);
|
||||
inline bool its_write_stl_ascii(const char *file, const char *label, const indexed_triangle_set &its) { return its_write_stl_ascii(file, label, its.indices, its.vertices); }
|
||||
bool its_write_stl_binary(const char *file, const char *label, const std::vector<stl_triangle_vertex_indices> &indices, const std::vector<stl_vertex> &vertices);
|
||||
inline bool its_write_stl_binary(const char *file, const char *label, const indexed_triangle_set &its) { return its_write_stl_binary(file, label, its.indices, its.vertices); }
|
||||
|
||||
inline BoundingBoxf3 bounding_box(const TriangleMesh &m) { return m.bounding_box(); }
|
||||
inline BoundingBoxf3 bounding_box(const indexed_triangle_set& its)
|
||||
@ -248,18 +320,12 @@ inline BoundingBoxf3 bounding_box(const indexed_triangle_set& its)
|
||||
namespace cereal {
|
||||
template <class Archive> struct specialize<Archive, Slic3r::TriangleMesh, cereal::specialization::non_member_load_save> {};
|
||||
template<class Archive> void load(Archive &archive, Slic3r::TriangleMesh &mesh) {
|
||||
stl_file &stl = mesh.stl;
|
||||
stl.stats.type = inmemory;
|
||||
archive(stl.stats.number_of_facets, stl.stats.original_num_facets);
|
||||
stl_allocate(&stl);
|
||||
archive.loadBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50);
|
||||
stl_get_size(&stl);
|
||||
mesh.repair();
|
||||
archive.loadBinary(reinterpret_cast<char*>(const_cast<Slic3r::TriangleMeshStats*>(&mesh.stats())), sizeof(Slic3r::TriangleMeshStats));
|
||||
archive(mesh.its.indices, mesh.its.vertices);
|
||||
}
|
||||
template<class Archive> void save(Archive &archive, const Slic3r::TriangleMesh &mesh) {
|
||||
const stl_file& stl = mesh.stl;
|
||||
archive(stl.stats.number_of_facets, stl.stats.original_num_facets);
|
||||
archive.saveBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50);
|
||||
archive.saveBinary(reinterpret_cast<const char*>(&mesh.stats()), sizeof(Slic3r::TriangleMeshStats));
|
||||
archive(mesh.its.indices, mesh.its.vertices);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1967,7 +1967,8 @@ static void triangulate_slice(
|
||||
int num_original_vertices,
|
||||
// Z height of the slice.
|
||||
float z,
|
||||
bool triangulate)
|
||||
bool triangulate,
|
||||
bool normals_down)
|
||||
{
|
||||
sort_remove_duplicates(slice_vertices);
|
||||
|
||||
@ -1988,7 +1989,7 @@ static void triangulate_slice(
|
||||
std::vector<int> map_duplicate_vertex(int(its.vertices.size()) - num_original_vertices, -1);
|
||||
int i = 0;
|
||||
int k = 0;
|
||||
for (; i < int(map_vertex_to_index.size()); ++ i) {
|
||||
for (; i < int(map_vertex_to_index.size());) {
|
||||
map_vertex_to_index[k ++] = map_vertex_to_index[i];
|
||||
const Vec2f &ipos = map_vertex_to_index[i].first;
|
||||
const int iidx = map_vertex_to_index[i].second;
|
||||
@ -2003,6 +2004,7 @@ static void triangulate_slice(
|
||||
// map to the first vertex
|
||||
map_duplicate_vertex[jidx - num_original_vertices] = iidx;
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
map_vertex_to_index.erase(map_vertex_to_index.begin() + k, map_vertex_to_index.end());
|
||||
for (stl_triangle_vertex_indices &f : its.indices)
|
||||
@ -2013,7 +2015,7 @@ static void triangulate_slice(
|
||||
|
||||
if (triangulate) {
|
||||
size_t idx_vertex_new_first = its.vertices.size();
|
||||
Pointf3s triangles = triangulate_expolygons_3d(make_expolygons_simple(lines), z, true);
|
||||
Pointf3s triangles = triangulate_expolygons_3d(make_expolygons_simple(lines), z, normals_down);
|
||||
for (size_t i = 0; i < triangles.size(); ) {
|
||||
stl_triangle_vertex_indices facet;
|
||||
for (size_t j = 0; j < 3; ++ j) {
|
||||
@ -2049,6 +2051,33 @@ static void triangulate_slice(
|
||||
// its_remove_degenerate_faces(its);
|
||||
}
|
||||
|
||||
void project_mesh(
|
||||
const indexed_triangle_set &mesh,
|
||||
const Transform3d &trafo,
|
||||
Polygons *out_top,
|
||||
Polygons *out_bottom,
|
||||
std::function<void()> throw_on_cancel)
|
||||
{
|
||||
std::vector<Polygons> top, bottom;
|
||||
std::vector<float> zs { -1e10, 1e10 };
|
||||
slice_mesh_slabs(mesh, zs, trafo, out_top ? &top : nullptr, out_bottom ? &bottom : nullptr, throw_on_cancel);
|
||||
if (out_top)
|
||||
*out_top = std::move(top.front());
|
||||
if (out_bottom)
|
||||
*out_bottom = std::move(bottom.back());
|
||||
}
|
||||
|
||||
Polygons project_mesh(
|
||||
const indexed_triangle_set &mesh,
|
||||
const Transform3d &trafo,
|
||||
std::function<void()> throw_on_cancel)
|
||||
{
|
||||
std::vector<Polygons> top, bottom;
|
||||
std::vector<float> zs { -1e10, 1e10 };
|
||||
slice_mesh_slabs(mesh, zs, trafo, &top, &bottom, throw_on_cancel);
|
||||
return union_(top.front(), bottom.back());
|
||||
}
|
||||
|
||||
void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *upper, indexed_triangle_set *lower, bool triangulate_caps)
|
||||
{
|
||||
assert(upper || lower);
|
||||
@ -2069,6 +2098,10 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u
|
||||
lower->indices.reserve(mesh.indices.size());
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t num_open_edges_old = triangulate_caps ? its_num_open_edges(mesh) : 0;
|
||||
#endif // NDEBUG
|
||||
|
||||
// To triangulate the caps after slicing.
|
||||
IntersectionLines upper_lines, lower_lines;
|
||||
std::vector<int> upper_slice_vertices, lower_slice_vertices;
|
||||
@ -2135,13 +2168,14 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u
|
||||
// get vertices starting from the isolated one
|
||||
int iv = isolated_vertex;
|
||||
stl_vertex v0v1, v2v0;
|
||||
assert(facets_edge_ids[facet_idx](iv) == line.edge_a_id ||facets_edge_ids[facet_idx](iv) == line.edge_b_id);
|
||||
assert(facets_edge_ids[facet_idx](iv) == line.edge_a_id || facets_edge_ids[facet_idx](iv) == line.edge_b_id);
|
||||
if (facets_edge_ids[facet_idx](iv) == line.edge_a_id) {
|
||||
v0v1 = to_3d(unscaled<float>(line.a), z);
|
||||
v2v0 = to_3d(unscaled<float>(line.b), z);
|
||||
// Unscale to doubles first, then to floats to reach the same accuracy as triangulate_expolygons_2d().
|
||||
v0v1 = to_3d(unscaled<double>(line.a).cast<float>().eval(), z);
|
||||
v2v0 = to_3d(unscaled<double>(line.b).cast<float>().eval(), z);
|
||||
} else {
|
||||
v0v1 = to_3d(unscaled<float>(line.b), z);
|
||||
v2v0 = to_3d(unscaled<float>(line.a), z);
|
||||
v0v1 = to_3d(unscaled<double>(line.b).cast<float>().eval(), z);
|
||||
v2v0 = to_3d(unscaled<double>(line.a).cast<float>().eval(), z);
|
||||
}
|
||||
const stl_vertex &v0 = vertices[iv];
|
||||
const int iv0 = facet[iv];
|
||||
@ -2195,11 +2229,25 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u
|
||||
}
|
||||
}
|
||||
|
||||
if (upper != nullptr)
|
||||
triangulate_slice(*upper, upper_lines, upper_slice_vertices, int(mesh.vertices.size()), z, triangulate_caps);
|
||||
if (upper != nullptr) {
|
||||
triangulate_slice(*upper, upper_lines, upper_slice_vertices, int(mesh.vertices.size()), z, triangulate_caps, NORMALS_DOWN);
|
||||
#ifndef NDEBUG
|
||||
if (triangulate_caps) {
|
||||
size_t num_open_edges_new = its_num_open_edges(*upper);
|
||||
assert(num_open_edges_new <= num_open_edges_old);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
|
||||
if (lower != nullptr)
|
||||
triangulate_slice(*lower, lower_lines, lower_slice_vertices, int(mesh.vertices.size()), z, triangulate_caps);
|
||||
if (lower != nullptr) {
|
||||
triangulate_slice(*lower, lower_lines, lower_slice_vertices, int(mesh.vertices.size()), z, triangulate_caps, NORMALS_UP);
|
||||
#ifndef NDEBUG
|
||||
if (triangulate_caps) {
|
||||
size_t num_open_edges_new = its_num_open_edges(*lower);
|
||||
assert(num_open_edges_new <= num_open_edges_old);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Slic3r
|
||||
|
@ -98,7 +98,21 @@ void slice_mesh_slabs(
|
||||
std::vector<Polygons> *out_bottom,
|
||||
std::function<void()> throw_on_cancel);
|
||||
|
||||
void cut_mesh(
|
||||
// Project mesh upwards pointing surfaces / downwards pointing surfaces into 2D polygons.
|
||||
void project_mesh(
|
||||
const indexed_triangle_set &mesh,
|
||||
const Transform3d &trafo,
|
||||
Polygons *out_top,
|
||||
Polygons *out_bottom,
|
||||
std::function<void()> throw_on_cancel);
|
||||
|
||||
// Project mesh into 2D polygons.
|
||||
Polygons project_mesh(
|
||||
const indexed_triangle_set &mesh,
|
||||
const Transform3d &trafo,
|
||||
std::function<void()> throw_on_cancel);
|
||||
|
||||
void cut_mesh(
|
||||
const indexed_triangle_set &mesh,
|
||||
float z,
|
||||
indexed_triangle_set *upper,
|
||||
|
@ -255,6 +255,19 @@ template<typename T> struct IsTriviallyCopyable { static constexpr bool value =
|
||||
template<typename T> struct IsTriviallyCopyable : public std::is_trivially_copyable<T> {};
|
||||
#endif
|
||||
|
||||
// A very lightweight ROII wrapper around C FILE.
|
||||
// The old C file API is much faster than C++ streams, thus they are recommended for processing large / huge files.
|
||||
struct FilePtr {
|
||||
FilePtr(FILE *f) : f(f) {}
|
||||
~FilePtr() { this->close(); }
|
||||
void close() {
|
||||
if (this->f) {
|
||||
::fclose(this->f);
|
||||
this->f = nullptr;
|
||||
}
|
||||
}
|
||||
FILE* f = nullptr;
|
||||
};
|
||||
|
||||
class ScopeGuard
|
||||
{
|
||||
|
@ -889,7 +889,7 @@ std::string string_printf(const char *format, ...)
|
||||
|
||||
std::string header_slic3r_generated()
|
||||
{
|
||||
return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();
|
||||
return std::string("generated by PrusaSlicer " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();
|
||||
}
|
||||
|
||||
std::string header_gcodeviewer_generated()
|
||||
@ -906,6 +906,7 @@ unsigned get_current_pid()
|
||||
#endif
|
||||
}
|
||||
|
||||
//FIXME this has potentially O(n^2) time complexity!
|
||||
std::string xml_escape(std::string text, bool is_marked/* = false*/)
|
||||
{
|
||||
std::string::size_type pos = 0;
|
||||
|
@ -179,6 +179,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Jobs/SLAImportJob.hpp
|
||||
GUI/Jobs/SLAImportJob.cpp
|
||||
GUI/Jobs/ProgressIndicator.hpp
|
||||
GUI/Jobs/NotificationProgressIndicator.hpp
|
||||
GUI/Jobs/NotificationProgressIndicator.cpp
|
||||
GUI/ProgressStatusBar.hpp
|
||||
GUI/ProgressStatusBar.cpp
|
||||
GUI/Mouse3DController.cpp
|
||||
|
@ -313,7 +313,6 @@ void GLVolume::SinkingContours::update()
|
||||
m_shift = Vec3d::Zero();
|
||||
|
||||
const TriangleMesh& mesh = model.objects[object_idx]->volumes[m_parent.volume_idx()]->mesh();
|
||||
assert(mesh.has_shared_vertices());
|
||||
|
||||
m_model.reset();
|
||||
GUI::GLModel::InitializationData init_data;
|
||||
@ -519,7 +518,7 @@ const BoundingBoxf3& GLVolume::transformed_convex_hull_bounding_box() const
|
||||
|
||||
BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &trafo) const
|
||||
{
|
||||
return (m_convex_hull && m_convex_hull->facets_count() > 0) ?
|
||||
return (m_convex_hull && ! m_convex_hull->empty()) ?
|
||||
m_convex_hull->transformed_bounding_box(trafo) :
|
||||
bounding_box().transformed(trafo);
|
||||
}
|
||||
@ -719,21 +718,20 @@ int GLVolumeCollection::load_wipe_tower_preview(
|
||||
float min_width = 30.f;
|
||||
// We'll now create the box with jagged edge. y-coordinates of the pre-generated model
|
||||
// are shifted so that the front edge has y=0 and centerline of the back edge has y=depth:
|
||||
Pointf3s points;
|
||||
std::vector<Vec3i> facets;
|
||||
float out_points_idx[][3] = { { 0, -depth, 0 }, { 0, 0, 0 }, { 38.453f, 0, 0 }, { 61.547f, 0, 0 }, { 100.0f, 0, 0 }, { 100.0f, -depth, 0 }, { 55.7735f, -10.0f, 0 }, { 44.2265f, 10.0f, 0 },
|
||||
{ 38.453f, 0, 1 }, { 0, 0, 1 }, { 0, -depth, 1 }, { 100.0f, -depth, 1 }, { 100.0f, 0, 1 }, { 61.547f, 0, 1 }, { 55.7735f, -10.0f, 1 }, { 44.2265f, 10.0f, 1 } };
|
||||
int out_facets_idx[][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 5, 0 }, { 3, 5, 6 }, { 6, 2, 7 }, { 6, 0, 2 }, { 8, 9, 10 }, { 11, 12, 13 }, { 10, 11, 14 }, { 14, 11, 13 }, { 15, 8, 14 },
|
||||
{8, 10, 14}, {3, 12, 4}, {3, 13, 12}, {6, 13, 3}, {6, 14, 13}, {7, 14, 6}, {7, 15, 14}, {2, 15, 7}, {2, 8, 15}, {1, 8, 2}, {1, 9, 8},
|
||||
{0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11} };
|
||||
static constexpr const int out_facets_idx[][3] = {
|
||||
{ 0, 1, 2 }, { 3, 4, 5 }, { 6, 5, 0 }, { 3, 5, 6 }, { 6, 2, 7 }, { 6, 0, 2 }, { 8, 9, 10 }, { 11, 12, 13 }, { 10, 11, 14 }, { 14, 11, 13 }, { 15, 8, 14 },
|
||||
{ 8, 10, 14 }, { 3, 12, 4 }, { 3, 13, 12 }, { 6, 13, 3 }, { 6, 14, 13 }, { 7, 14, 6 }, { 7, 15, 14 }, { 2, 15, 7 }, { 2, 8, 15 }, { 1, 8, 2 }, { 1, 9, 8 },
|
||||
{ 0, 9, 1 }, { 0, 10, 9 }, { 5, 10, 0 }, { 5, 11, 10 }, { 4, 11, 5 }, { 4, 12, 11 } };
|
||||
indexed_triangle_set its;
|
||||
for (int i = 0; i < 16; ++i)
|
||||
points.emplace_back(out_points_idx[i][0] / (100.f / min_width),
|
||||
out_points_idx[i][1] + depth, out_points_idx[i][2]);
|
||||
for (int i = 0; i < 28; ++i)
|
||||
facets.emplace_back(out_facets_idx[i][0],
|
||||
out_facets_idx[i][1],
|
||||
out_facets_idx[i][2]);
|
||||
TriangleMesh tooth_mesh(points, facets);
|
||||
its.vertices.emplace_back(out_points_idx[i][0] / (100.f / min_width),
|
||||
out_points_idx[i][1] + depth, out_points_idx[i][2]);
|
||||
its.indices.reserve(28);
|
||||
for (const int *face : out_facets_idx)
|
||||
its.indices.emplace_back(face);
|
||||
TriangleMesh tooth_mesh(std::move(its));
|
||||
|
||||
// We have the mesh ready. It has one tooth and width of min_width. We will now
|
||||
// append several of these together until we are close to the required width
|
||||
@ -744,7 +742,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
|
||||
tooth_mesh.translate(min_width, 0.f, 0.f);
|
||||
}
|
||||
|
||||
mesh.scale(Vec3d(width / (n * min_width), 1.f, height)); // Scaling to proper width
|
||||
mesh.scale(Vec3f(width / (n * min_width), 1.f, height)); // Scaling to proper width
|
||||
}
|
||||
else
|
||||
mesh = make_cube(width, depth, height);
|
||||
@ -753,7 +751,6 @@ int GLVolumeCollection::load_wipe_tower_preview(
|
||||
TriangleMesh brim_mesh = make_cube(width + 2.f * brim_width, depth + 2.f * brim_width, 0.2f);
|
||||
brim_mesh.translate(-brim_width, -brim_width, 0.f);
|
||||
mesh.merge(brim_mesh);
|
||||
mesh.repair();
|
||||
|
||||
volumes.emplace_back(new GLVolume(color));
|
||||
GLVolume& v = *volumes.back();
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "format.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "UnsavedChangesDialog.hpp"
|
||||
|
||||
#if defined(__linux__) && defined(__WXGTK3__)
|
||||
#define wxLinux_gtk3 true
|
||||
@ -741,10 +742,10 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s
|
||||
const auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
|
||||
const auto text_clr = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
const auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
|
||||
wxString first_line = _L("Filaments marked with <b>*</b> are <b>not</b> compatible with some installed printers.");
|
||||
wxString first_line = format_wxstr(_L("%1% marked with <b>*</b> are <b>not</b> compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials"));
|
||||
wxString text;
|
||||
if (all_printers) {
|
||||
wxString second_line = _L("All installed printers are compatible with the selected filament.");
|
||||
wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material"));
|
||||
text = wxString::Format(
|
||||
"<html>"
|
||||
"<style>"
|
||||
@ -764,7 +765,7 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s
|
||||
, second_line
|
||||
);
|
||||
} else {
|
||||
wxString second_line = _L("Only the following installed printers are compatible with the selected filament:");
|
||||
wxString second_line = printer_names.empty() ? "" : format_wxstr(_L("Only the following installed printers are compatible with the selected %1%:"), materials->technology == T_FFF ? _L("filament") : _L("SLA material"));
|
||||
text = wxString::Format(
|
||||
"<html>"
|
||||
"<style>"
|
||||
@ -2473,8 +2474,16 @@ static std::string get_first_added_preset(const std::map<std::string, std::strin
|
||||
return *diff.begin();
|
||||
}
|
||||
|
||||
bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
|
||||
bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes)
|
||||
{
|
||||
wxString header, caption = _L("Configuration is editing from ConfigWizard");
|
||||
bool check_unsaved_preset_changes = page_welcome->reset_user_profile();
|
||||
if (check_unsaved_preset_changes)
|
||||
header = _L("All user presets will be deleted.");
|
||||
int act_btns = UnsavedChangesDialog::ActionButtons::KEEP;
|
||||
if (!check_unsaved_preset_changes)
|
||||
act_btns |= UnsavedChangesDialog::ActionButtons::SAVE;
|
||||
|
||||
const auto enabled_vendors = appconfig_new.vendors();
|
||||
|
||||
// Install bundles from resources if needed:
|
||||
@ -2500,6 +2509,9 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
install_bundles.emplace_back(pair.first);
|
||||
}
|
||||
}
|
||||
if (!check_unsaved_preset_changes)
|
||||
if ((check_unsaved_preset_changes = install_bundles.size() > 0))
|
||||
header = _L_PLURAL("New vendor was installed and one of its printer will be activated", "New vendors were installed and one of theirs printer will be activated", install_bundles.size());
|
||||
|
||||
#ifdef __linux__
|
||||
// Desktop integration on Linux
|
||||
@ -2531,6 +2543,10 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Continue with applying configuration changes?")))
|
||||
return false;
|
||||
|
||||
if (check_unsaved_preset_changes &&
|
||||
!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
|
||||
return false;
|
||||
|
||||
if (install_bundles.size() > 0) {
|
||||
// Install bundles from resources.
|
||||
// Don't create snapshot - we've already done that above if applicable.
|
||||
@ -2586,17 +2602,52 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
}
|
||||
}
|
||||
|
||||
// if unsaved changes was not cheched till this moment
|
||||
if (!check_unsaved_preset_changes) {
|
||||
if ((check_unsaved_preset_changes = !preferred_model.empty())) {
|
||||
header = _L("A new Printer was installed and it will be activated.");
|
||||
if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
|
||||
return false;
|
||||
}
|
||||
else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) {
|
||||
header = _L("Some Printers were uninstalled.");
|
||||
if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string first_added_filament, first_added_sla_material;
|
||||
auto apply_section = [this, app_config](const std::string& section_name, std::string& first_added_preset) {
|
||||
auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) {
|
||||
if (appconfig_new.has_section(section_name)) {
|
||||
// get first of new added preset names
|
||||
const std::map<std::string, std::string>& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map<std::string, std::string>();
|
||||
first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name));
|
||||
app_config->set_section(section_name, appconfig_new.get_section(section_name));
|
||||
}
|
||||
};
|
||||
apply_section(AppConfig::SECTION_FILAMENTS, first_added_filament);
|
||||
apply_section(AppConfig::SECTION_MATERIALS, first_added_sla_material);
|
||||
get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament);
|
||||
get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material);
|
||||
|
||||
// if unsaved changes was not cheched till this moment
|
||||
if (!check_unsaved_preset_changes) {
|
||||
if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) {
|
||||
header = format_wxstr(_L("A new %1% was installed and it will be activated."), !first_added_filament.empty() ? _L("Filament") : _L("SLA material"));
|
||||
if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
bool is_filaments_changed = app_config->get_section(AppConfig::SECTION_FILAMENTS) != appconfig_new.get_section(AppConfig::SECTION_FILAMENTS);
|
||||
bool is_sla_materials_changed = app_config->get_section(AppConfig::SECTION_MATERIALS) != appconfig_new.get_section(AppConfig::SECTION_MATERIALS);
|
||||
if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) {
|
||||
header = format_wxstr(_L("Some %1% were uninstalled."), is_filaments_changed ? _L("Filaments") : _L("SLA materials"));
|
||||
if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply materials in app_config
|
||||
for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS})
|
||||
app_config->set_section(section_name, appconfig_new.get_section(section_name));
|
||||
|
||||
app_config->set_vendors(appconfig_new);
|
||||
|
||||
@ -2624,10 +2675,16 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
|
||||
page_mode->serialize_mode(app_config);
|
||||
|
||||
preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem,
|
||||
{preferred_model, preferred_variant, first_added_filament, first_added_sla_material});
|
||||
if (check_unsaved_preset_changes)
|
||||
preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem,
|
||||
{preferred_model, preferred_variant, first_added_filament, first_added_sla_material});
|
||||
|
||||
if (page_custom->custom_wanted()) {
|
||||
// if unsaved changes was not cheched till this moment
|
||||
if (!check_unsaved_preset_changes &&
|
||||
!wxGetApp().check_and_keep_current_preset_changes(caption, _L("Custom printer was installed and it will be activated."), act_btns, &apply_keeped_changes))
|
||||
return false;
|
||||
|
||||
page_firmware->apply_custom_config(*custom_config);
|
||||
page_bed->apply_custom_config(*custom_config);
|
||||
page_diams->apply_custom_config(*custom_config);
|
||||
@ -2851,8 +2908,13 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page)
|
||||
p->set_start_page(start_page);
|
||||
|
||||
if (ShowModal() == wxID_OK) {
|
||||
if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater))
|
||||
bool apply_keeped_changes = false;
|
||||
if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes))
|
||||
return false;
|
||||
|
||||
if (apply_keeped_changes)
|
||||
app.apply_keeped_preset_modifications();
|
||||
|
||||
app.app_config->set_legacy_datadir(false);
|
||||
app.update_mode();
|
||||
app.obj_manipul()->update_ui_from_settings();
|
||||
|
@ -611,7 +611,7 @@ struct ConfigWizard::priv
|
||||
|
||||
bool on_bnt_finish();
|
||||
bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string());
|
||||
bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
|
||||
bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes);
|
||||
// #ys_FIXME_alise
|
||||
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
|
||||
#ifdef __linux__
|
||||
|
@ -323,15 +323,19 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR
|
||||
|
||||
c_editor->SetSelection(atoi(data.GetText().c_str()));
|
||||
|
||||
// to avoid event propagation to other sidebar items
|
||||
c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
|
||||
evt.StopPropagation();
|
||||
|
||||
#ifdef __linux__
|
||||
// FinishEditing grabs new selection and triggers config update. We better call
|
||||
// it explicitly, automatic update on KILL_FOCUS didn't work on Linux.
|
||||
this->FinishEditing();
|
||||
#endif
|
||||
c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
|
||||
// to avoid event propagation to other sidebar items
|
||||
evt.StopPropagation();
|
||||
// FinishEditing grabs new selection and triggers config update. We better call
|
||||
// it explicitly, automatic update on KILL_FOCUS didn't work on Linux.
|
||||
this->FinishEditing();
|
||||
});
|
||||
#else
|
||||
// to avoid event propagation to other sidebar items
|
||||
c_editor->Bind(wxEVT_COMBOBOX, [](wxCommandEvent& evt) { evt.StopPropagation(); });
|
||||
#endif
|
||||
|
||||
return c_editor;
|
||||
}
|
||||
|
@ -267,7 +267,12 @@ void GCodeViewer::SequentialView::Marker::render() const
|
||||
imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Tool position") + ":");
|
||||
ImGui::SameLine();
|
||||
char buf[1024];
|
||||
sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", m_world_position(0), m_world_position(1), m_world_position(2));
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
const Vec3f position = m_world_position + m_world_offset;
|
||||
sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z());
|
||||
#else
|
||||
sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", m_world_position.x(), m_world_position.y(), m_world_position.z());
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
imgui.text(std::string(buf));
|
||||
|
||||
// force extra frame to automatically update window size
|
||||
@ -862,6 +867,9 @@ void GCodeViewer::render()
|
||||
render_legend(legend_height);
|
||||
if (m_sequential_view.current.last != m_sequential_view.endpoints.last) {
|
||||
m_sequential_view.marker.set_world_position(m_sequential_view.current_position);
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
m_sequential_view.render(legend_height);
|
||||
}
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
@ -1500,10 +1508,18 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
m_max_bounding_box = m_paths_bounding_box;
|
||||
m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ());
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
m_sequential_view.gcode_ids.clear();
|
||||
for (size_t i = 0; i < gcode_result.moves.size(); ++i) {
|
||||
const GCodeProcessor::MoveVertex& move = gcode_result.moves[i];
|
||||
if (move.type != EMoveType::Seam)
|
||||
m_sequential_view.gcode_ids.push_back(move.gcode_id);
|
||||
}
|
||||
#else
|
||||
for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) {
|
||||
m_sequential_view.gcode_ids.push_back(move.gcode_id);
|
||||
}
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
std::vector<MultiVertexBuffer> vertices(m_buffers.size());
|
||||
std::vector<MultiIndexBuffer> indices(m_buffers.size());
|
||||
@ -1511,11 +1527,27 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
std::vector<InstanceBuffer> instances(m_buffers.size());
|
||||
std::vector<InstanceIdBuffer> instances_ids(m_buffers.size());
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
std::vector<InstancesOffsets> instances_offsets(m_buffers.size());
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
std::vector<float> options_zs;
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
size_t seams_count = 0;
|
||||
std::vector<size_t> seams_ids;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
// toolpaths data -> extract vertices from result
|
||||
for (size_t i = 0; i < m_moves_count; ++i) {
|
||||
const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
if (curr.type == EMoveType::Seam) {
|
||||
++seams_count;
|
||||
seams_ids.push_back(i);
|
||||
}
|
||||
|
||||
size_t move_id = i - seams_count;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
// skip first vertex
|
||||
if (i == 0)
|
||||
@ -1538,6 +1570,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
InstanceBuffer& inst_buffer = instances[id];
|
||||
InstanceIdBuffer& inst_id_buffer = instances_ids[id];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
InstancesOffsets& inst_offsets = instances_offsets[id];
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
|
||||
// ensure there is at least one vertex buffer
|
||||
@ -1556,7 +1591,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
|
||||
Path& last_path = t_buffer.paths.back();
|
||||
if (prev.type == curr.type && last_path.matches(curr))
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
last_path.add_sub_path(prev, static_cast<unsigned int>(v_multibuffer.size()) - 1, 0, move_id - 1);
|
||||
#else
|
||||
last_path.add_sub_path(prev, static_cast<unsigned int>(v_multibuffer.size()) - 1, 0, i - 1);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
|
||||
@ -1566,12 +1605,21 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
{
|
||||
case TBuffer::ERenderPrimitiveType::Point: { add_vertices_as_point(curr, v_buffer); break; }
|
||||
case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; }
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast<unsigned int>(v_multibuffer.size()) - 1, v_buffer, move_id); break; }
|
||||
#else
|
||||
case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast<unsigned int>(v_multibuffer.size()) - 1, v_buffer, i); break; }
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
case TBuffer::ERenderPrimitiveType::InstancedModel:
|
||||
{
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
add_model_instance(curr, inst_buffer, inst_id_buffer, move_id);
|
||||
inst_offsets.push_back(prev.position - curr.position);
|
||||
#else
|
||||
add_model_instance(curr, inst_buffer, inst_id_buffer, i);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
++m_statistics.instances_count;
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
@ -1579,7 +1627,12 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
}
|
||||
case TBuffer::ERenderPrimitiveType::BatchedModel:
|
||||
{
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
add_vertices_as_model_batch(curr, t_buffer.model.data, v_buffer, inst_buffer, inst_id_buffer, move_id);
|
||||
inst_offsets.push_back(prev.position - curr.position);
|
||||
#else
|
||||
add_vertices_as_model_batch(curr, t_buffer.model.data, v_buffer, inst_buffer, inst_id_buffer, i);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
++m_statistics.batched_count;
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
@ -1588,7 +1641,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
#else
|
||||
case TBuffer::ERenderPrimitiveType::Model:
|
||||
{
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
add_model_instance(curr, inst_buffer, inst_id_buffer, move_id);
|
||||
#else
|
||||
add_model_instance(curr, inst_buffer, inst_id_buffer, i);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
++m_statistics.instances_count;
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
@ -1607,7 +1664,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
}
|
||||
|
||||
// smooth toolpaths corners for the given TBuffer using triangles
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
auto smooth_triangle_toolpaths_corners = [&gcode_result, &seams_ids](const TBuffer& t_buffer, MultiVertexBuffer& v_multibuffer) {
|
||||
#else
|
||||
auto smooth_triangle_toolpaths_corners = [&gcode_result](const TBuffer& t_buffer, MultiVertexBuffer& v_multibuffer) {
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
auto extract_position_at = [](const VertexBuffer& vertices, size_t offset) {
|
||||
return Vec3f(vertices[offset + 0], vertices[offset + 1], vertices[offset + 2]);
|
||||
};
|
||||
@ -1681,6 +1742,16 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
}
|
||||
};
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
auto extract_move_id = [&seams_ids](size_t id) {
|
||||
for (int i = seams_ids.size() - 1; i >= 0; --i) {
|
||||
if (seams_ids[i] < id + i + 1)
|
||||
return id + (size_t)i + 1;
|
||||
}
|
||||
return id;
|
||||
};
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
size_t vertex_size_floats = t_buffer.vertices.vertex_size_floats();
|
||||
for (const Path& path : t_buffer.paths) {
|
||||
// the two segments of the path sharing the current vertex may belong
|
||||
@ -1691,9 +1762,16 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
const float half_width = 0.5f * path.width;
|
||||
for (size_t j = 1; j < path_vertices_count - 1; ++j) {
|
||||
size_t curr_s_id = path.sub_paths.front().first.s_id + j;
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
size_t move_id = extract_move_id(curr_s_id);
|
||||
const Vec3f& prev = gcode_result.moves[move_id - 1].position;
|
||||
const Vec3f& curr = gcode_result.moves[move_id].position;
|
||||
const Vec3f& next = gcode_result.moves[move_id + 1].position;
|
||||
#else
|
||||
const Vec3f& prev = gcode_result.moves[curr_s_id - 1].position;
|
||||
const Vec3f& curr = gcode_result.moves[curr_s_id].position;
|
||||
const Vec3f& next = gcode_result.moves[curr_s_id + 1].position;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
// select the subpaths which contains the previous/next segments
|
||||
if (!path.sub_paths[prev_sub_path_id].contains(curr_s_id))
|
||||
@ -1760,6 +1838,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
// dismiss, no more needed
|
||||
std::vector<size_t>().swap(seams_ids);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
for (MultiVertexBuffer& v_multibuffer : vertices) {
|
||||
for (VertexBuffer& v_buffer : v_multibuffer) {
|
||||
v_buffer.shrink_to_fit();
|
||||
@ -1784,6 +1867,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
if (!inst_buffer.empty()) {
|
||||
t_buffer.model.instances.buffer = inst_buffer;
|
||||
t_buffer.model.instances.s_ids = instances_ids[i];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
t_buffer.model.instances.offsets = instances_offsets[i];
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -1792,6 +1878,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
if (!inst_buffer.empty()) {
|
||||
t_buffer.model.instances.buffer = inst_buffer;
|
||||
t_buffer.model.instances.s_ids = instances_ids[i];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
t_buffer.model.instances.offsets = instances_offsets[i];
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
#else
|
||||
@ -1800,6 +1889,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
if (!inst_buffer.empty()) {
|
||||
t_buffer.model.instances.buffer = inst_buffer;
|
||||
t_buffer.model.instances.s_ids = instances_ids[i];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
t_buffer.model.instances.offsets = instances_offsets[i];
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -1860,8 +1952,18 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
using VboIndexList = std::vector<unsigned int>;
|
||||
std::vector<VboIndexList> vbo_indices(m_buffers.size());
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
seams_count = 0;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
for (size_t i = 0; i < m_moves_count; ++i) {
|
||||
const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
if (curr.type == EMoveType::Seam)
|
||||
++seams_count;
|
||||
|
||||
size_t move_id = i - seams_count;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
// skip first vertex
|
||||
if (i == 0)
|
||||
@ -1912,7 +2014,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) {
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
Path& last_path = t_buffer.paths.back();
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
last_path.add_sub_path(prev, static_cast<unsigned int>(i_multibuffer.size()) - 1, 0, move_id - 1);
|
||||
#else
|
||||
last_path.add_sub_path(prev, static_cast<unsigned int>(i_multibuffer.size()) - 1, 0, i - 1);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
|
||||
@ -1937,7 +2043,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) {
|
||||
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
Path& last_path = t_buffer.paths.back();
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
last_path.add_sub_path(prev, static_cast<unsigned int>(i_multibuffer.size()) - 1, 0, move_id - 1);
|
||||
#else
|
||||
last_path.add_sub_path(prev, static_cast<unsigned int>(i_multibuffer.size()) - 1, 0, i - 1);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
|
||||
@ -1946,17 +2056,29 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
switch (t_buffer.render_primitive_type)
|
||||
{
|
||||
case TBuffer::ERenderPrimitiveType::Point: {
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
add_indices_as_point(curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id);
|
||||
#else
|
||||
add_indices_as_point(curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, i);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
curr_vertex_buffer.second += t_buffer.max_vertices_per_segment();
|
||||
break;
|
||||
}
|
||||
case TBuffer::ERenderPrimitiveType::Line: {
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
add_indices_as_line(prev, curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id);
|
||||
#else
|
||||
add_indices_as_line(prev, curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, i);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
curr_vertex_buffer.second += t_buffer.max_vertices_per_segment();
|
||||
break;
|
||||
}
|
||||
case TBuffer::ERenderPrimitiveType::Triangle: {
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id);
|
||||
#else
|
||||
add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, i);
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
break;
|
||||
}
|
||||
#if ENABLE_SEAMS_USING_BATCHED_MODELS
|
||||
@ -2051,16 +2173,34 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
|
||||
// layers zs / roles / extruder ids -> extract from result
|
||||
size_t last_travel_s_id = 0;
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
seams_count = 0;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
for (size_t i = 0; i < m_moves_count; ++i) {
|
||||
const GCodeProcessor::MoveVertex& move = gcode_result.moves[i];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
if (move.type == EMoveType::Seam)
|
||||
++seams_count;
|
||||
|
||||
size_t move_id = i - seams_count;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
if (move.type == EMoveType::Extrude) {
|
||||
// layers zs
|
||||
const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back();
|
||||
const double z = static_cast<double>(move.position.z());
|
||||
if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z)
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
m_layers.append(z, { last_travel_s_id, move_id });
|
||||
#else
|
||||
m_layers.append(z, { last_travel_s_id, i });
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
else
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
m_layers.get_endpoints().back().last = move_id;
|
||||
#else
|
||||
m_layers.get_endpoints().back().last = i;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
// extruder ids
|
||||
m_extruder_ids.emplace_back(move.extruder_id);
|
||||
// roles
|
||||
@ -2068,10 +2208,17 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
||||
m_roles.emplace_back(move.extrusion_role);
|
||||
}
|
||||
else if (move.type == EMoveType::Travel) {
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
if (move_id - last_travel_s_id > 1 && !m_layers.empty())
|
||||
m_layers.get_endpoints().back().last = move_id;
|
||||
|
||||
last_travel_s_id = move_id;
|
||||
#else
|
||||
if (i - last_travel_s_id > 1 && !m_layers.empty())
|
||||
m_layers.get_endpoints().back().last = i;
|
||||
|
||||
|
||||
last_travel_s_id = i;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
}
|
||||
}
|
||||
|
||||
@ -2342,6 +2489,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
sequential_view->current_position.x() = buffer.model.instances.buffer[offset + 0];
|
||||
sequential_view->current_position.y() = buffer.model.instances.buffer[offset + 1];
|
||||
sequential_view->current_position.z() = buffer.model.instances.buffer[offset + 2];
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
sequential_view->current_offset = buffer.model.instances.offsets[i];
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
found = true;
|
||||
break;
|
||||
@ -2381,6 +2531,10 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast<GLintptr>(index * buffer.vertices.vertex_size_bytes()), static_cast<GLsizeiptr>(3 * sizeof(float)), static_cast<void*>(sequential_view->current_position.data())));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
sequential_view->current_offset = Vec3f::Zero();
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
@ -31,6 +31,9 @@ class GCodeViewer
|
||||
using InstanceBuffer = std::vector<float>;
|
||||
using InstanceIdBuffer = std::vector<size_t>;
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
using InstancesOffsets = std::vector<Vec3f>;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
static const std::vector<Color> Extrusion_Role_Colors;
|
||||
static const std::vector<Color> Options_Colors;
|
||||
@ -151,6 +154,10 @@ class GCodeViewer
|
||||
InstanceBuffer buffer;
|
||||
// indices of the moves for all instances
|
||||
std::vector<size_t> s_ids;
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
// position offsets, used to show the correct value of the tool position
|
||||
InstancesOffsets offsets;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
Ranges render_ranges;
|
||||
|
||||
size_t data_size_bytes() const { return s_ids.size() * instance_size_bytes(); }
|
||||
@ -665,6 +672,12 @@ public:
|
||||
GLModel m_model;
|
||||
Vec3f m_world_position;
|
||||
Transform3f m_world_transform;
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
// for seams, the position of the marker is on the last endpoint of the toolpath containing it
|
||||
// the offset is used to show the correct value of tool position in the "ToolPosition" window
|
||||
// see implementation of render() method
|
||||
Vec3f m_world_offset;
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
float m_z_offset{ 0.5f };
|
||||
bool m_visible{ true };
|
||||
|
||||
@ -674,6 +687,9 @@ public:
|
||||
const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); }
|
||||
|
||||
void set_world_position(const Vec3f& position);
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
void set_world_offset(const Vec3f& offset) { m_world_offset = offset; }
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
|
||||
bool is_visible() const { return m_visible; }
|
||||
void set_visible(bool visible) { m_visible = visible; }
|
||||
@ -731,6 +747,9 @@ public:
|
||||
Endpoints global;
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
Vec3f current_position{ Vec3f::Zero() };
|
||||
#if ENABLE_FIX_SEAMS_SYNCH
|
||||
Vec3f current_offset{ Vec3f::Zero() };
|
||||
#endif // ENABLE_FIX_SEAMS_SYNCH
|
||||
Marker marker;
|
||||
GCodeWindow gcode_window;
|
||||
std::vector<unsigned int> gcode_ids;
|
||||
|
@ -4427,12 +4427,11 @@ bool GLCanvas3D::_init_main_toolbar()
|
||||
arrow_data.top = 0;
|
||||
arrow_data.right = 0;
|
||||
arrow_data.bottom = 0;
|
||||
|
||||
if (!m_main_toolbar.init_arrow(arrow_data))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Main toolbar failed to load arrow texture.";
|
||||
}
|
||||
|
||||
// m_gizmos is created at constructor, thus we can init arrow here.
|
||||
if (!m_gizmos.init_arrow(arrow_data))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Gizmos manager failed to load arrow texture.";
|
||||
@ -4643,6 +4642,18 @@ bool GLCanvas3D::_init_undoredo_toolbar()
|
||||
return true;
|
||||
}
|
||||
|
||||
// init arrow
|
||||
BackgroundTexture::Metadata arrow_data;
|
||||
arrow_data.filename = "toolbar_arrow.svg";
|
||||
arrow_data.left = 0;
|
||||
arrow_data.top = 0;
|
||||
arrow_data.right = 0;
|
||||
arrow_data.bottom = 0;
|
||||
if (!m_undoredo_toolbar.init_arrow(arrow_data))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Undo/Redo toolbar failed to load arrow texture.";
|
||||
}
|
||||
|
||||
// m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
|
||||
m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Horizontal);
|
||||
m_undoredo_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left);
|
||||
@ -5376,6 +5387,10 @@ void GLCanvas3D::_render_undoredo_toolbar()
|
||||
|
||||
m_undoredo_toolbar.set_position(top, left);
|
||||
m_undoredo_toolbar.render(*this);
|
||||
if (m_toolbar_highlighter.m_render_arrow)
|
||||
{
|
||||
m_undoredo_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item);
|
||||
}
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_collapse_toolbar() const
|
||||
|
@ -160,7 +160,6 @@ bool GLModel::init_from_file(const std::string& filename)
|
||||
}
|
||||
|
||||
TriangleMesh mesh = model.mesh();
|
||||
mesh.require_shared_vertices();
|
||||
init_from(mesh.its, mesh.bounding_box());
|
||||
|
||||
m_filename = filename;
|
||||
|
@ -1143,6 +1143,10 @@ void GLToolbar::render_background(float left, float top, float right, float bott
|
||||
|
||||
void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighted_item)
|
||||
{
|
||||
// arrow texture not initialized
|
||||
if (m_arrow_texture.texture.get_id() == 0)
|
||||
return;
|
||||
|
||||
float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
|
||||
float factor = inv_zoom * m_layout.scale;
|
||||
|
||||
@ -1157,6 +1161,7 @@ void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighte
|
||||
float left = m_layout.left;
|
||||
float top = m_layout.top - icon_stride;
|
||||
|
||||
bool found = false;
|
||||
for (const GLToolbarItem* item : m_items) {
|
||||
if (!item->is_visible())
|
||||
continue;
|
||||
@ -1164,11 +1169,15 @@ void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighte
|
||||
if (item->is_separator())
|
||||
left += separator_stride;
|
||||
else {
|
||||
if (item->get_name() == highlighted_item->get_name())
|
||||
if (item->get_name() == highlighted_item->get_name()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
left += icon_stride;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
left += border;
|
||||
top -= separator_stride;
|
||||
|
@ -694,7 +694,6 @@ GUI_App::GUI_App(EAppMode mode)
|
||||
, m_app_mode(mode)
|
||||
, m_em_unit(10)
|
||||
, m_imgui(new ImGuiWrapper())
|
||||
, m_wizard(nullptr)
|
||||
, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
|
||||
, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
|
||||
{
|
||||
@ -1333,8 +1332,6 @@ void GUI_App::recreate_GUI(const wxString& msg_name)
|
||||
|
||||
dlg.Update(30, _L("Recreating") + dots);
|
||||
old_main_frame->Destroy();
|
||||
// For this moment ConfigWizard is deleted, invalidate it.
|
||||
m_wizard = nullptr;
|
||||
|
||||
dlg.Update(80, _L("Loading of current presets") + dots);
|
||||
m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
|
||||
@ -1403,17 +1400,15 @@ void GUI_App::force_colors_update()
|
||||
void GUI_App::update_ui_from_settings()
|
||||
{
|
||||
update_label_colours();
|
||||
mainframe->update_ui_from_settings();
|
||||
|
||||
#ifdef _WIN32
|
||||
// Upadte UU colors before Update UI from settings
|
||||
if (m_force_colors_update) {
|
||||
m_force_colors_update = false;
|
||||
mainframe->force_color_changed();
|
||||
mainframe->diff_dialog.force_color_changed();
|
||||
if (m_wizard)
|
||||
m_wizard->force_color_changed();
|
||||
}
|
||||
#endif
|
||||
mainframe->update_ui_from_settings();
|
||||
}
|
||||
|
||||
void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized)
|
||||
@ -1871,8 +1866,9 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
||||
#endif
|
||||
case ConfigMenuTakeSnapshot:
|
||||
// Take a configuration snapshot.
|
||||
if (check_and_save_current_preset_changes()) {
|
||||
wxTextEntryDialog dlg(nullptr, _L("Taking configuration snapshot"), _L("Snapshot name"));
|
||||
if (wxString action_name = _L("Taking a configuration snapshot");
|
||||
check_and_save_current_preset_changes(action_name, _L("Some presets are modified and the unsaved changes will not be captured by the configuration snapshot."), false, true)) {
|
||||
wxTextEntryDialog dlg(nullptr, action_name, _L("Snapshot name"));
|
||||
UpdateDlgDarkUI(&dlg);
|
||||
|
||||
// set current normal font for dialog children,
|
||||
@ -1888,7 +1884,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
||||
}
|
||||
break;
|
||||
case ConfigMenuSnapshots:
|
||||
if (check_and_save_current_preset_changes()) {
|
||||
if (check_and_save_current_preset_changes(_L("Loading a configuration snapshot"), "", false)) {
|
||||
std::string on_snapshot;
|
||||
if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
|
||||
on_snapshot = app_config->get("on_snapshot");
|
||||
@ -1910,9 +1906,6 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
||||
|
||||
// Load the currently selected preset into the GUI, update the preset selection box.
|
||||
load_current_presets();
|
||||
|
||||
// update config wizard in respect to the new config
|
||||
update_wizard_from_config();
|
||||
} catch (std::exception &ex) {
|
||||
GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what()));
|
||||
}
|
||||
@ -2081,13 +2074,28 @@ std::vector<std::pair<unsigned int, std::string>> GUI_App::get_selected_presets(
|
||||
return ret;
|
||||
}
|
||||
|
||||
// This is called when closing the application, when loading a config file or when starting the config wizard
|
||||
// to notify the user whether he is aware that some preset changes will be lost.
|
||||
bool GUI_App::check_and_save_current_preset_changes(const wxString& header, const wxString& caption)
|
||||
// To notify the user whether he is aware that some preset changes will be lost,
|
||||
// UnsavedChangesDialog: "Discard / Save / Cancel"
|
||||
// This is called when:
|
||||
// - Close Application & Current project isn't saved
|
||||
// - Load Project & Current project isn't saved
|
||||
// - Undo / Redo with change of print technologie
|
||||
// - Loading snapshot
|
||||
// - Loading config_file/bundle
|
||||
// UnsavedChangesDialog: "Don't save / Save / Cancel"
|
||||
// This is called when:
|
||||
// - Exporting config_bundle
|
||||
// - Taking snapshot
|
||||
bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice/* = true*/, bool dont_save_insted_of_discard/* = false*/)
|
||||
{
|
||||
if (/*this->plater()->model().objects.empty() && */has_current_preset_changes()) {
|
||||
UnsavedChangesDialog dlg(header, caption);
|
||||
if (wxGetApp().app_config->get("default_action_on_close_application") == "none" && dlg.ShowModal() == wxID_CANCEL)
|
||||
if (has_current_preset_changes()) {
|
||||
const std::string app_config_key = remember_choice ? "default_action_on_close_application" : "";
|
||||
int act_buttons = UnsavedChangesDialog::ActionButtons::SAVE;
|
||||
if (dont_save_insted_of_discard)
|
||||
act_buttons |= UnsavedChangesDialog::ActionButtons::DONT_SAVE;
|
||||
UnsavedChangesDialog dlg(caption, header, app_config_key, act_buttons);
|
||||
std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key);
|
||||
if (act == "none" && dlg.ShowModal() == wxID_CANCEL)
|
||||
return false;
|
||||
|
||||
if (dlg.save_preset()) // save selected changes
|
||||
@ -2095,18 +2103,122 @@ bool GUI_App::check_and_save_current_preset_changes(const wxString& header, cons
|
||||
for (const std::pair<std::string, Preset::Type>& nt : dlg.get_names_and_types())
|
||||
preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second));
|
||||
|
||||
load_current_presets(false);
|
||||
|
||||
// if we saved changes to the new presets, we should to
|
||||
// synchronize config.ini with the current selections.
|
||||
preset_bundle->export_selections(*app_config);
|
||||
|
||||
wxMessageBox(_L_PLURAL("The preset modifications are successfully saved",
|
||||
"The presets modifications are successfully saved", dlg.get_names_and_types().size()));
|
||||
MessageDialog(nullptr, _L_PLURAL("The preset modifications are successfully saved",
|
||||
"The presets modifications are successfully saved", dlg.get_names_and_types().size())).ShowModal();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GUI_App::apply_keeped_preset_modifications()
|
||||
{
|
||||
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
|
||||
for (Tab* tab : tabs_list) {
|
||||
if (tab->supports_printer_technology(printer_technology))
|
||||
tab->apply_config_from_cache();
|
||||
}
|
||||
load_current_presets(false);
|
||||
}
|
||||
|
||||
// This is called when creating new project or load another project
|
||||
// OR close ConfigWizard
|
||||
// to ask the user what should we do with unsaved changes for presets.
|
||||
// New Project => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Cancel"
|
||||
// => Current project isn't saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel"
|
||||
// Close ConfigWizard => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel"
|
||||
// Note: no_nullptr postponed_apply_of_keeped_changes indicates that thie function is called after ConfigWizard is closed
|
||||
bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes/* = nullptr*/)
|
||||
{
|
||||
if (has_current_preset_changes()) {
|
||||
bool is_called_from_configwizard = postponed_apply_of_keeped_changes != nullptr;
|
||||
|
||||
const std::string app_config_key = is_called_from_configwizard ? "" : "default_action_on_new_project";
|
||||
UnsavedChangesDialog dlg(caption, header, app_config_key, action_buttons);
|
||||
std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key);
|
||||
if (act == "none" && dlg.ShowModal() == wxID_CANCEL)
|
||||
return false;
|
||||
|
||||
auto reset_modifications = [this, is_called_from_configwizard]() {
|
||||
if (is_called_from_configwizard)
|
||||
return; // no need to discared changes. It will be done fromConfigWizard closing
|
||||
|
||||
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
|
||||
for (const Tab* const tab : tabs_list) {
|
||||
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty())
|
||||
tab->m_presets->discard_current_changes();
|
||||
}
|
||||
load_current_presets(false);
|
||||
};
|
||||
|
||||
if (dlg.discard())
|
||||
reset_modifications();
|
||||
else // save selected changes
|
||||
{
|
||||
const auto& preset_names_and_types = dlg.get_names_and_types();
|
||||
if (dlg.save_preset()) {
|
||||
for (const std::pair<std::string, Preset::Type>& nt : preset_names_and_types)
|
||||
preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second));
|
||||
|
||||
// if we saved changes to the new presets, we should to
|
||||
// synchronize config.ini with the current selections.
|
||||
preset_bundle->export_selections(*app_config);
|
||||
|
||||
wxString text = _L_PLURAL("The preset modifications are successfully saved",
|
||||
"The presets modifications are successfully saved", preset_names_and_types.size());
|
||||
if (!is_called_from_configwizard)
|
||||
text += "\n\n" + _L("For new project all modifications will be reseted");
|
||||
|
||||
MessageDialog(nullptr, text).ShowModal();
|
||||
reset_modifications();
|
||||
}
|
||||
else if (dlg.transfer_changes() && (dlg.has_unselected_options() || is_called_from_configwizard)) {
|
||||
// execute this part of code only if not all modifications are keeping to the new project
|
||||
// OR this function is called when ConfigWizard is closed and "Keep modifications" is selected
|
||||
for (const std::pair<std::string, Preset::Type>& nt : preset_names_and_types) {
|
||||
Preset::Type type = nt.second;
|
||||
Tab* tab = get_tab(type);
|
||||
std::vector<std::string> selected_options = dlg.get_selected_options(type);
|
||||
if (type == Preset::TYPE_PRINTER) {
|
||||
auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count");
|
||||
if (it != selected_options.end()) {
|
||||
// erase "extruders_count" option from the list
|
||||
selected_options.erase(it);
|
||||
// cache the extruders count
|
||||
static_cast<TabPrinter*>(tab)->cache_extruder_cnt();
|
||||
}
|
||||
}
|
||||
tab->cache_config_diff(selected_options);
|
||||
if (!is_called_from_configwizard)
|
||||
tab->m_presets->discard_current_changes();
|
||||
}
|
||||
if (is_called_from_configwizard)
|
||||
*postponed_apply_of_keeped_changes = true;
|
||||
else
|
||||
apply_keeped_preset_modifications();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GUI_App::can_load_project()
|
||||
{
|
||||
int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified."));
|
||||
if (saved_project == wxID_CANCEL ||
|
||||
(plater()->is_project_dirty() && saved_project == wxID_NO &&
|
||||
!check_and_save_current_preset_changes(_L("Project is loading"), _L("Loading a new project while some presets are modified."))))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GUI_App::check_print_host_queue()
|
||||
{
|
||||
wxString dirty;
|
||||
@ -2164,17 +2276,6 @@ void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/)
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::update_wizard_from_config()
|
||||
{
|
||||
if (!m_wizard)
|
||||
return;
|
||||
// If ConfigWizard was created before changing of the configuration,
|
||||
// we have to destroy it to have possibility to create it again in respect to the new config's parameters
|
||||
m_wizard->Reparent(nullptr);
|
||||
m_wizard->Destroy();
|
||||
m_wizard = nullptr;
|
||||
}
|
||||
|
||||
bool GUI_App::OnExceptionInMainLoop()
|
||||
{
|
||||
generic_exception_handle();
|
||||
@ -2279,7 +2380,7 @@ wxBookCtrlBase* GUI_App::tab_panel() const
|
||||
return mainframe->m_tabpanel;
|
||||
}
|
||||
|
||||
std::shared_ptr<NotificationManager> GUI_App::notification_manager()
|
||||
NotificationManager * GUI_App::notification_manager()
|
||||
{
|
||||
return plater_->get_notification_manager();
|
||||
}
|
||||
@ -2336,19 +2437,12 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
|
||||
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
|
||||
|
||||
if (reason == ConfigWizard::RR_USER) {
|
||||
wxString header = _L("Updates to Configuration Wizard may cause an another preset selection and lost of preset modification as a result.\n"
|
||||
"So, check unsaved changes and save them if necessary.") + "\n";
|
||||
if (!check_and_save_current_preset_changes(header, _L("ConfigWizard is opening")) ||
|
||||
preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED)
|
||||
if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! m_wizard) {
|
||||
wxBusyCursor wait;
|
||||
m_wizard = new ConfigWizard(mainframe);
|
||||
}
|
||||
|
||||
const bool res = m_wizard->run(reason, start_page);
|
||||
auto wizard = new ConfigWizard(mainframe);
|
||||
const bool res = wizard->run(reason, start_page);
|
||||
|
||||
if (res) {
|
||||
load_current_presets();
|
||||
|
@ -150,7 +150,6 @@ private:
|
||||
|
||||
std::unique_ptr<ImGuiWrapper> m_imgui;
|
||||
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
|
||||
ConfigWizard* m_wizard; // Managed by wxWindow tree
|
||||
std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
|
||||
std::unique_ptr <wxSingleInstanceChecker> m_single_instance_checker;
|
||||
std::string m_instance_hash_string;
|
||||
@ -246,11 +245,13 @@ public:
|
||||
bool has_current_preset_changes() const;
|
||||
void update_saved_preset_from_current_preset();
|
||||
std::vector<std::pair<unsigned int, std::string>> get_selected_presets() const;
|
||||
bool check_and_save_current_preset_changes(const wxString& header = wxString(), const wxString& caption = wxString());
|
||||
bool check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice = true, bool use_dont_save_insted_of_discard = false);
|
||||
void apply_keeped_preset_modifications();
|
||||
bool check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes = nullptr);
|
||||
bool can_load_project();
|
||||
bool check_print_host_queue();
|
||||
bool checked_tab(Tab* tab);
|
||||
void load_current_presets(bool check_printer_presets = true);
|
||||
void update_wizard_from_config();
|
||||
|
||||
wxString current_language_code() const { return m_wxLocale->GetCanonicalName(); }
|
||||
// Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US".
|
||||
@ -275,7 +276,7 @@ public:
|
||||
ObjectLayers* obj_layers();
|
||||
Plater* plater();
|
||||
Model& model();
|
||||
std::shared_ptr<NotificationManager> notification_manager();
|
||||
NotificationManager * notification_manager();
|
||||
|
||||
// Parameters extracted from the command line to be passed to GUI after initialization.
|
||||
GUI_InitParams* init_params { nullptr };
|
||||
|
@ -382,19 +382,24 @@ int ObjectList::get_mesh_errors_count(const int obj_idx, const int vol_idx /*= -
|
||||
return (*m_objects)[obj_idx]->get_mesh_errors_count(vol_idx);
|
||||
}
|
||||
|
||||
wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx /*= -1*/) const
|
||||
static std::string get_warning_icon_name(const TriangleMeshStats& stats)
|
||||
{
|
||||
return stats.repaired() ? (stats.manifold() ? "exclamation_manifold" : "exclamation") : "";
|
||||
}
|
||||
|
||||
std::pair<wxString, std::string> ObjectList::get_mesh_errors(const int obj_idx, const int vol_idx /*= -1*/, bool from_plater /*= false*/) const
|
||||
{
|
||||
const int errors = get_mesh_errors_count(obj_idx, vol_idx);
|
||||
|
||||
if (errors == 0)
|
||||
return ""; // hide tooltip
|
||||
return { "", "" }; // hide tooltip
|
||||
|
||||
// Create tooltip string, if there are errors
|
||||
wxString tooltip = format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors) + ":\n";
|
||||
|
||||
const stl_stats& stats = vol_idx == -1 ?
|
||||
(*m_objects)[obj_idx]->get_object_stl_stats() :
|
||||
(*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stats();
|
||||
const TriangleMeshStats& stats = vol_idx == -1 ?
|
||||
(*m_objects)[obj_idx]->get_object_stl_stats() :
|
||||
(*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stats();
|
||||
|
||||
if (stats.degenerate_facets > 0)
|
||||
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d degenerate facet", "%1$d degenerate facets", stats.degenerate_facets), stats.degenerate_facets) + "\n";
|
||||
@ -402,28 +407,31 @@ wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx /
|
||||
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d edge fixed", "%1$d edges fixed", stats.edges_fixed), stats.edges_fixed) + "\n";
|
||||
if (stats.facets_removed > 0)
|
||||
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d facet removed", "%1$d facets removed", stats.facets_removed), stats.facets_removed) + "\n";
|
||||
if (stats.facets_added > 0)
|
||||
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d facet added", "%1$d facets added", stats.facets_added), stats.facets_added) + "\n";
|
||||
if (stats.facets_reversed > 0)
|
||||
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d facet reversed", "%1$d facets reversed", stats.facets_reversed), stats.facets_reversed) + "\n";
|
||||
if (stats.backwards_edges > 0)
|
||||
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d backwards edge", "%1$d backwards edges", stats.backwards_edges), stats.backwards_edges) + "\n";
|
||||
|
||||
if (is_windows10())
|
||||
tooltip += _L("Right button click the icon to fix STL through Netfabb");
|
||||
if (!stats.manifold()) {
|
||||
tooltip += _L("Remaning errors") + ":\n";
|
||||
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d open edge", "%1$d open edges", stats.open_edges), stats.open_edges) + "\n";
|
||||
}
|
||||
|
||||
return tooltip;
|
||||
if (is_windows10() && !from_plater)
|
||||
tooltip += "\n" + _L("Right button click the icon to fix STL through Netfabb");
|
||||
|
||||
return { tooltip, get_warning_icon_name(stats) };
|
||||
}
|
||||
|
||||
wxString ObjectList::get_mesh_errors_list()
|
||||
std::pair<wxString, std::string> ObjectList::get_mesh_errors(bool from_plater /*= false*/)
|
||||
{
|
||||
if (!GetSelection())
|
||||
return "";
|
||||
return { "", "" };
|
||||
|
||||
int obj_idx, vol_idx;
|
||||
get_selected_item_indexes(obj_idx, vol_idx);
|
||||
|
||||
return get_mesh_errors_list(obj_idx, vol_idx);
|
||||
return get_mesh_errors(obj_idx, vol_idx, from_plater);
|
||||
}
|
||||
|
||||
void ObjectList::set_tooltip_for_item(const wxPoint& pt)
|
||||
@ -461,7 +469,7 @@ void ObjectList::set_tooltip_for_item(const wxPoint& pt)
|
||||
{
|
||||
int obj_idx, vol_idx;
|
||||
get_selected_item_indexes(obj_idx, vol_idx, item);
|
||||
tooltip = get_mesh_errors_list(obj_idx, vol_idx);
|
||||
tooltip = get_mesh_errors(obj_idx, vol_idx).first;
|
||||
}
|
||||
|
||||
GetMainWindow()->SetToolTip(tooltip);
|
||||
@ -1535,7 +1543,6 @@ void ObjectList::load_modifier(ModelObject& model_object, std::vector<ModelVolum
|
||||
}
|
||||
|
||||
TriangleMesh mesh = model.mesh();
|
||||
mesh.repair();
|
||||
// Mesh will be centered when loading.
|
||||
ModelVolume* new_volume = model_object.add_volume(std::move(mesh), type);
|
||||
new_volume->name = boost::filesystem::path(input_file).filename().string();
|
||||
@ -1558,27 +1565,24 @@ void ObjectList::load_modifier(ModelObject& model_object, std::vector<ModelVolum
|
||||
|
||||
static TriangleMesh create_mesh(const std::string& type_name, const BoundingBoxf3& bb)
|
||||
{
|
||||
TriangleMesh mesh;
|
||||
|
||||
const double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1);
|
||||
|
||||
indexed_triangle_set mesh;
|
||||
if (type_name == "Box")
|
||||
// Sitting on the print bed, left front front corner at (0, 0).
|
||||
mesh = make_cube(side, side, side);
|
||||
mesh = its_make_cube(side, side, side);
|
||||
else if (type_name == "Cylinder")
|
||||
// Centered around 0, sitting on the print bed.
|
||||
// The cylinder has the same volume as the box above.
|
||||
mesh = make_cylinder(0.564 * side, side);
|
||||
mesh = its_make_cylinder(0.564 * side, side);
|
||||
else if (type_name == "Sphere")
|
||||
// Centered around 0, half the sphere below the print bed, half above.
|
||||
// The sphere has the same volume as the box above.
|
||||
mesh = make_sphere(0.62 * side, PI / 18);
|
||||
mesh = its_make_sphere(0.62 * side, PI / 18);
|
||||
else if (type_name == "Slab")
|
||||
// Sitting on the print bed, left front front corner at (0, 0).
|
||||
mesh = make_cube(bb.size().x() * 1.5, bb.size().y() * 1.5, bb.size().z() * 0.5);
|
||||
mesh.repair();
|
||||
|
||||
return mesh;
|
||||
mesh = its_make_cube(bb.size().x() * 1.5, bb.size().y() * 1.5, bb.size().z() * 0.5);
|
||||
return TriangleMesh(mesh);
|
||||
}
|
||||
|
||||
void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type)
|
||||
@ -1781,8 +1785,12 @@ void ObjectList::del_subobject_item(wxDataViewItem& item)
|
||||
return;
|
||||
|
||||
// If last volume item with warning was deleted, unmark object item
|
||||
if (type & itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0)
|
||||
m_objects_model->DeleteWarningIcon(parent);
|
||||
if (type & itVolume) {
|
||||
if (auto obj = object(obj_idx); obj->get_mesh_errors_count() == 0)
|
||||
m_objects_model->DeleteWarningIcon(parent);
|
||||
else
|
||||
m_objects_model->AddWarningIcon(parent, get_warning_icon_name(obj->mesh().stats()));
|
||||
}
|
||||
|
||||
m_objects_model->Delete(item);
|
||||
update_info_items(obj_idx);
|
||||
@ -1991,7 +1999,7 @@ void ObjectList::split()
|
||||
for (const ModelVolume* volume : model_object->volumes) {
|
||||
const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(parent, from_u8(volume->name),
|
||||
volume->type(),// is_modifier() ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART,
|
||||
volume->get_mesh_errors_count()>0,
|
||||
get_warning_icon_name(volume->mesh().stats()),
|
||||
volume->config.has("extruder") ? volume->config.extruder() : 0,
|
||||
false);
|
||||
// add settings to the part, if it has those
|
||||
@ -2490,7 +2498,7 @@ void ObjectList::part_selection_changed()
|
||||
if (item) {
|
||||
// wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item));
|
||||
wxGetApp().obj_manipul()->update_item_name(m_objects_model->GetName(item));
|
||||
wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_list(obj_idx, volume_id));
|
||||
wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors(obj_idx, volume_id));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2633,7 +2641,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed)
|
||||
const wxString& item_name = from_u8(model_object->name);
|
||||
const auto item = m_objects_model->Add(item_name,
|
||||
model_object->config.has("extruder") ? model_object->config.extruder() : 0,
|
||||
get_mesh_errors_count(obj_idx) > 0);
|
||||
get_warning_icon_name(model_object->mesh().stats()));
|
||||
|
||||
update_info_items(obj_idx, nullptr, call_selection_changed);
|
||||
|
||||
@ -2643,7 +2651,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed)
|
||||
const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(item,
|
||||
from_u8(volume->name),
|
||||
volume->type(),
|
||||
volume->get_mesh_errors_count()>0,
|
||||
get_warning_icon_name(volume->mesh().stats()),
|
||||
volume->config.has("extruder") ? volume->config.extruder() : 0,
|
||||
false);
|
||||
add_settings_item(vol_item, &volume->config.get());
|
||||
@ -2752,6 +2760,8 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
|
||||
// If last volume item with warning was deleted, unmark object item
|
||||
if (obj->get_mesh_errors_count() == 0)
|
||||
m_objects_model->DeleteWarningIcon(parent);
|
||||
else
|
||||
m_objects_model->AddWarningIcon(parent, get_warning_icon_name(obj->mesh().stats()));
|
||||
}
|
||||
wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx, printer_technology() != ptSLA);
|
||||
}
|
||||
@ -4061,7 +4071,7 @@ void ObjectList::fix_through_netfabb()
|
||||
msg += ": " + from_u8(model_name) + "\n";
|
||||
else {
|
||||
msg += ":\n";
|
||||
for (size_t i = 0; i < model_names.size(); ++i)
|
||||
for (int i = 0; i < int(model_names.size()); ++i)
|
||||
msg += (i == model_idx ? " > " : " ") + from_u8(model_names[i]) + "\n";
|
||||
msg += "\n";
|
||||
}
|
||||
@ -4168,8 +4178,10 @@ void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) co
|
||||
// unmark fixed item only
|
||||
m_objects_model->DeleteWarningIcon(item);
|
||||
}
|
||||
else
|
||||
m_objects_model->AddWarningIcon(item);
|
||||
else {
|
||||
auto obj = object(obj_idx);
|
||||
m_objects_model->AddWarningIcon(item, get_warning_icon_name(vol_idx < 0 ? obj->mesh().stats() : obj->volumes[vol_idx]->mesh().stats()));
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectList::msw_rescale()
|
||||
@ -4300,7 +4312,7 @@ wxDataViewItemArray ObjectList::reorder_volumes_and_get_selection(int obj_idx, s
|
||||
for (const ModelVolume* volume : object->volumes) {
|
||||
wxDataViewItem vol_item = m_objects_model->AddVolumeChild(object_item, from_u8(volume->name),
|
||||
volume->type(),
|
||||
volume->get_mesh_errors_count() > 0,
|
||||
get_warning_icon_name(volume->mesh().stats()),
|
||||
volume->config.has("extruder") ? volume->config.extruder() : 0,
|
||||
false);
|
||||
// add settings to the part, if it has those
|
||||
|
@ -210,12 +210,12 @@ public:
|
||||
void get_selection_indexes(std::vector<int>& obj_idxs, std::vector<int>& vol_idxs);
|
||||
// Get count of errors in the mesh
|
||||
int get_mesh_errors_count(const int obj_idx, const int vol_idx = -1) const;
|
||||
/* Get list of errors in the mesh. Return value is a string, used for the tooltip
|
||||
* Function without parameters is for a call from Manipulation panel,
|
||||
* when we don't know parameters of selected item
|
||||
*/
|
||||
wxString get_mesh_errors_list(const int obj_idx, const int vol_idx = -1) const;
|
||||
wxString get_mesh_errors_list();
|
||||
// Get list of errors in the mesh and name of the warning icon
|
||||
// Return value is a pair <Tooltip, warning_icon_name>, used for the tooltip and related warning icon
|
||||
// Function without parameters is for a call from Manipulation panel,
|
||||
// when we don't know parameters of selected item
|
||||
std::pair<wxString, std::string> get_mesh_errors(const int obj_idx, const int vol_idx = -1, bool from_plater = false) const;
|
||||
std::pair<wxString, std::string> get_mesh_errors(bool from_plater = false);
|
||||
void set_tooltip_for_item(const wxPoint& pt);
|
||||
|
||||
void selection_changed();
|
||||
|
@ -132,7 +132,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
||||
return;
|
||||
|
||||
wxGetApp().obj_list()->fix_through_netfabb();
|
||||
update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors_list());
|
||||
update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors());
|
||||
});
|
||||
|
||||
sizer->Add(m_fix_throught_netfab_bitmap);
|
||||
@ -776,8 +776,12 @@ void ObjectManipulation::update_item_name(const wxString& item_name)
|
||||
m_item_name->SetLabel(item_name);
|
||||
}
|
||||
|
||||
void ObjectManipulation::update_warning_icon_state(const wxString& tooltip)
|
||||
{
|
||||
void ObjectManipulation::update_warning_icon_state(const std::pair<wxString, std::string>& warning)
|
||||
{
|
||||
if (const std::string& warning_icon_name = warning.second;
|
||||
!warning_icon_name.empty())
|
||||
m_manifold_warning_bmp = ScalableBitmap(m_parent, warning_icon_name);
|
||||
const wxString& tooltip = warning.first;
|
||||
m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp());
|
||||
m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0,0) : m_manifold_warning_bmp.bmp().GetSize());
|
||||
m_fix_throught_netfab_bitmap->SetToolTip(tooltip);
|
||||
|
@ -194,7 +194,7 @@ public:
|
||||
#endif // __APPLE__
|
||||
|
||||
void update_item_name(const wxString &item_name);
|
||||
void update_warning_icon_state(const wxString& tooltip);
|
||||
void update_warning_icon_state(const std::pair<wxString, std::string>& warning);
|
||||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
void on_change(const std::string& opt_key, int axis, double new_value);
|
||||
|
@ -747,7 +747,7 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
|
||||
|
||||
double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices);
|
||||
if( bottom_area - top_area > delta_area) {
|
||||
std::shared_ptr<NotificationManager> notif_mngr = wxGetApp().plater()->get_notification_manager();
|
||||
NotificationManager *notif_mngr = wxGetApp().plater()->get_notification_manager();
|
||||
notif_mngr->push_notification(
|
||||
NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotificationLevel,
|
||||
_u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n",
|
||||
|
@ -282,10 +282,8 @@ void GLGizmoCut::update_contours()
|
||||
if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() || m_cut_contours.instance_idx != instance_idx) {
|
||||
m_cut_contours.cut_z = m_cut_z;
|
||||
|
||||
if (m_cut_contours.object_id != model_object->id()) {
|
||||
if (m_cut_contours.object_id != model_object->id())
|
||||
m_cut_contours.mesh = model_object->raw_mesh();
|
||||
m_cut_contours.mesh.repair();
|
||||
}
|
||||
|
||||
m_cut_contours.position = box.center();
|
||||
m_cut_contours.shift = Vec3d::Zero();
|
||||
|
@ -313,9 +313,8 @@ void GLGizmoSimplify::process()
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::set_its(indexed_triangle_set &its) {
|
||||
auto tm = std::make_unique<TriangleMesh>(its);
|
||||
tm->repair();
|
||||
m_volume->set_mesh(std::move(tm));
|
||||
m_volume->set_mesh(its);
|
||||
m_volume->calculate_convex_hull();
|
||||
m_volume->set_new_unique_id();
|
||||
m_volume->get_object()->invalidate_bounding_box();
|
||||
m_need_reload = true;
|
||||
|
@ -270,11 +270,10 @@ void HollowedMesh::on_update()
|
||||
m_drainholes = print_object->model_object()->sla_drain_holes;
|
||||
m_old_hollowing_timestamp = timestamp;
|
||||
|
||||
const indexed_triangle_set &interior = print_object->hollowed_interior_mesh();
|
||||
indexed_triangle_set interior = print_object->hollowed_interior_mesh();
|
||||
if (!interior.empty()) {
|
||||
m_hollowed_interior_transformed = std::make_unique<TriangleMesh>(interior);
|
||||
m_hollowed_interior_transformed->repaired = false;
|
||||
m_hollowed_interior_transformed->repair(true);
|
||||
its_flip_triangles(interior);
|
||||
m_hollowed_interior_transformed = std::make_unique<TriangleMesh>(std::move(interior));
|
||||
m_hollowed_interior_transformed->transform(trafo_inv);
|
||||
}
|
||||
}
|
||||
|
@ -352,10 +352,16 @@ bool instance_check(int argc, char** argv, bool app_config_single_instance)
|
||||
if (instance_check_internal::get_lock(lock_name + ".lock", data_dir() + "/cache/") && *cla.should_send) {
|
||||
#endif
|
||||
instance_check_internal::send_message(cla.cl_string, lock_name);
|
||||
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate.";
|
||||
BOOST_LOG_TRIVIAL(error) << "Instance check: Another instance found. This instance will terminate. Lock file of current running instance is located at " << data_dir() <<
|
||||
#ifdef _WIN32
|
||||
"\\cache\\"
|
||||
#else // mac & linx
|
||||
"/cache/"
|
||||
#endif
|
||||
<< lock_name << ".lock";
|
||||
return true;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set.";
|
||||
BOOST_LOG_TRIVIAL(info) << "Instance check: Another instance not found or single-instance not set.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ namespace Slic3r {
|
||||
class ModelInstance;
|
||||
|
||||
namespace GUI {
|
||||
class NotificationManager;
|
||||
|
||||
class ArrangeJob : public PlaterJob
|
||||
{
|
||||
@ -18,21 +17,21 @@ class ArrangeJob : public PlaterJob
|
||||
|
||||
ArrangePolygons m_selected, m_unselected, m_unprintable;
|
||||
std::vector<ModelInstance*> m_unarranged;
|
||||
|
||||
|
||||
// clear m_selected and m_unselected, reserve space for next usage
|
||||
void clear_input();
|
||||
|
||||
// Prepare all objects on the bed regardless of the selection
|
||||
void prepare_all();
|
||||
|
||||
|
||||
// Prepare the selected and unselected items separately. If nothing is
|
||||
// selected, behaves as if everything would be selected.
|
||||
void prepare_selected();
|
||||
|
||||
ArrangePolygon get_arrange_poly_(ModelInstance *mi);
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
void prepare() override;
|
||||
|
||||
void on_exception(const std::exception_ptr &) override;
|
||||
@ -40,15 +39,15 @@ protected:
|
||||
void process() override;
|
||||
|
||||
public:
|
||||
ArrangeJob(std::shared_ptr<NotificationManager> nm, Plater *plater)
|
||||
: PlaterJob{nm, plater}
|
||||
ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: PlaterJob{std::move(pri), plater}
|
||||
{}
|
||||
|
||||
|
||||
int status_range() const override
|
||||
{
|
||||
return int(m_selected.size() + m_unprintable.size());
|
||||
}
|
||||
|
||||
|
||||
void finalize() override;
|
||||
};
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class Plater;
|
||||
class NotificationManager;
|
||||
|
||||
class FillBedJob : public PlaterJob
|
||||
{
|
||||
@ -28,8 +27,8 @@ protected:
|
||||
void process() override;
|
||||
|
||||
public:
|
||||
FillBedJob(std::shared_ptr<NotificationManager> nm, Plater *plater)
|
||||
: PlaterJob{nm, plater}
|
||||
FillBedJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: PlaterJob{std::move(pri), plater}
|
||||
{}
|
||||
|
||||
int status_range() const override
|
||||
|
@ -2,11 +2,9 @@
|
||||
#include <exception>
|
||||
|
||||
#include "Job.hpp"
|
||||
#include "../NotificationManager.hpp"
|
||||
#include <libslic3r/Thread.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void GUI::Job::run(std::exception_ptr &eptr)
|
||||
@ -19,7 +17,7 @@ void GUI::Job::run(std::exception_ptr &eptr)
|
||||
}
|
||||
|
||||
m_running.store(false);
|
||||
|
||||
|
||||
// ensure to call the last status to finalize the job
|
||||
update_status(status_range(), "");
|
||||
}
|
||||
@ -32,8 +30,8 @@ void GUI::Job::update_status(int st, const wxString &msg)
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
GUI::Job::Job(std::shared_ptr<NotificationManager> nm)
|
||||
: m_notifications(nm)
|
||||
GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
|
||||
: m_progress(std::move(pri))
|
||||
{
|
||||
m_thread_evt_id = wxNewId();
|
||||
|
||||
@ -42,21 +40,21 @@ GUI::Job::Job(std::shared_ptr<NotificationManager> nm)
|
||||
|
||||
auto msg = evt.GetString();
|
||||
if (!msg.empty() && !m_worker_error)
|
||||
m_notifications->progress_indicator_set_status_text(msg.ToUTF8().data());
|
||||
m_progress->set_status_text(msg.ToUTF8().data());
|
||||
|
||||
if (m_finalized) return;
|
||||
|
||||
m_notifications->progress_indicator_set_progress(evt.GetInt());
|
||||
m_progress->set_progress(evt.GetInt());
|
||||
if (evt.GetInt() == status_range() || m_worker_error) {
|
||||
// set back the original range and cancel callback
|
||||
m_notifications->progress_indicator_set_range(m_range);
|
||||
m_notifications->progress_indicator_set_cancel_callback();
|
||||
m_progress->set_range(m_range);
|
||||
m_progress->set_cancel_callback();
|
||||
wxEndBusyCursor();
|
||||
|
||||
|
||||
if (m_worker_error) {
|
||||
m_finalized = true;
|
||||
m_notifications->progress_indicator_set_status_text("");
|
||||
m_notifications->progress_indicator_set_progress(m_range);
|
||||
m_progress->set_status_text("");
|
||||
m_progress->set_progress(m_range);
|
||||
on_exception(m_worker_error);
|
||||
}
|
||||
else {
|
||||
@ -86,22 +84,22 @@ void GUI::Job::start()
|
||||
{ // Start the job. No effect if the job is already running
|
||||
if (!m_running.load()) {
|
||||
prepare();
|
||||
|
||||
|
||||
// Save the current status indicatior range and push the new one
|
||||
m_range = m_notifications->progress_indicator_get_range();
|
||||
m_notifications->progress_indicator_set_range(status_range());
|
||||
|
||||
m_range = m_progress->get_range();
|
||||
m_progress->set_range(status_range());
|
||||
|
||||
// init cancellation flag and set the cancel callback
|
||||
m_canceled.store(false);
|
||||
m_notifications->progress_indicator_set_cancel_callback(
|
||||
m_progress->set_cancel_callback(
|
||||
[this]() { m_canceled.store(true); });
|
||||
|
||||
|
||||
m_finalized = false;
|
||||
m_finalizing = false;
|
||||
|
||||
|
||||
// Changing cursor to busy
|
||||
wxBeginBusyCursor();
|
||||
|
||||
|
||||
try { // Execute the job
|
||||
m_worker_error = nullptr;
|
||||
m_thread = create_thread([this] { this->run(m_worker_error); });
|
||||
@ -110,7 +108,7 @@ void GUI::Job::start()
|
||||
_(L("ERROR: not enough resources to "
|
||||
"execute a new job.")));
|
||||
}
|
||||
|
||||
|
||||
// The state changes will be undone when the process hits the
|
||||
// last status value, in the status update handler (see ctor)
|
||||
}
|
||||
@ -119,12 +117,12 @@ void GUI::Job::start()
|
||||
bool GUI::Job::join(int timeout_ms)
|
||||
{
|
||||
if (!m_thread.joinable()) return true;
|
||||
|
||||
|
||||
if (timeout_ms <= 0)
|
||||
m_thread.join();
|
||||
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
|
||||
return false;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -137,10 +135,10 @@ void GUI::ExclusiveJobGroup::start(size_t jid) {
|
||||
void GUI::ExclusiveJobGroup::join_all(int wait_ms)
|
||||
{
|
||||
std::vector<bool> aborted(m_jobs.size(), false);
|
||||
|
||||
|
||||
for (size_t jid = 0; jid < m_jobs.size(); ++jid)
|
||||
aborted[jid] = m_jobs[jid]->join(wait_ms);
|
||||
|
||||
|
||||
if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; }))
|
||||
BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
|
||||
}
|
||||
|
@ -8,13 +8,14 @@
|
||||
|
||||
#include <slic3r/GUI/I18N.hpp>
|
||||
|
||||
#include "ProgressIndicator.hpp"
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class NotificationManager;
|
||||
// A class to handle UI jobs like arranging and optimizing rotation.
|
||||
// These are not instant jobs, the user has to be informed about their
|
||||
// state in the status progress indicator. On the other hand they are
|
||||
@ -32,7 +33,7 @@ class Job : public wxEvtHandler
|
||||
boost::thread m_thread;
|
||||
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||
bool m_finalized = false, m_finalizing = false;
|
||||
std::shared_ptr<NotificationManager> m_notifications;
|
||||
std::shared_ptr<ProgressIndicator> m_progress;
|
||||
std::exception_ptr m_worker_error = nullptr;
|
||||
|
||||
void run(std::exception_ptr &);
|
||||
@ -64,7 +65,7 @@ protected:
|
||||
}
|
||||
|
||||
public:
|
||||
Job(std::shared_ptr<NotificationManager> nm);
|
||||
Job(std::shared_ptr<ProgressIndicator> pri);
|
||||
|
||||
bool is_finalized() const { return m_finalized; }
|
||||
|
||||
|
33
src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp
Normal file
33
src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "NotificationProgressIndicator.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
NotificationProgressIndicator::NotificationProgressIndicator(NotificationManager *nm): m_nm{nm} {}
|
||||
|
||||
void NotificationProgressIndicator::set_range(int range)
|
||||
{
|
||||
m_nm->progress_indicator_set_range(range);
|
||||
}
|
||||
|
||||
void NotificationProgressIndicator::set_cancel_callback(CancelFn fn)
|
||||
{
|
||||
m_nm->progress_indicator_set_cancel_callback(std::move(fn));
|
||||
}
|
||||
|
||||
void NotificationProgressIndicator::set_progress(int pr)
|
||||
{
|
||||
m_nm->progress_indicator_set_progress(pr);
|
||||
}
|
||||
|
||||
void NotificationProgressIndicator::set_status_text(const char *msg)
|
||||
{
|
||||
m_nm->progress_indicator_set_status_text(msg);
|
||||
}
|
||||
|
||||
int NotificationProgressIndicator::get_range() const
|
||||
{
|
||||
return m_nm->progress_indicator_get_range();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
26
src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp
Normal file
26
src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef NOTIFICATIONPROGRESSINDICATOR_HPP
|
||||
#define NOTIFICATIONPROGRESSINDICATOR_HPP
|
||||
|
||||
#include "ProgressIndicator.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class NotificationManager;
|
||||
|
||||
class NotificationProgressIndicator: public ProgressIndicator {
|
||||
NotificationManager *m_nm = nullptr;
|
||||
|
||||
public:
|
||||
|
||||
explicit NotificationProgressIndicator(NotificationManager *nm);
|
||||
|
||||
void set_range(int range) override;
|
||||
void set_cancel_callback(CancelFn = CancelFn()) override;
|
||||
void set_progress(int pr) override;
|
||||
void set_status_text(const char *) override; // utf8 char array
|
||||
int get_range() const override;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // NOTIFICATIONPROGRESSINDICATOR_HPP
|
@ -6,7 +6,6 @@
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class Plater;
|
||||
class NotificationManager;
|
||||
|
||||
class PlaterJob : public Job {
|
||||
protected:
|
||||
@ -16,8 +15,8 @@ protected:
|
||||
|
||||
public:
|
||||
|
||||
PlaterJob(std::shared_ptr<NotificationManager> nm, Plater *plater):
|
||||
Job{nm}, m_plater{plater} {}
|
||||
PlaterJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater):
|
||||
Job{std::move(pri)}, m_plater{plater} {}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
@ -10,8 +10,6 @@ namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class NotificationManager;
|
||||
|
||||
class RotoptimizeJob : public PlaterJob
|
||||
{
|
||||
using FindFn = std::function<Vec2d(const ModelObject & mo,
|
||||
@ -54,8 +52,8 @@ protected:
|
||||
|
||||
public:
|
||||
|
||||
RotoptimizeJob(std::shared_ptr<NotificationManager> nm, Plater *plater)
|
||||
: PlaterJob{nm, plater}
|
||||
RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: PlaterJob{std::move(pri), plater}
|
||||
{}
|
||||
|
||||
void finalize() override;
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
@ -20,79 +19,79 @@
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
enum class Sel { modelAndProfile, profileOnly, modelOnly};
|
||||
|
||||
|
||||
class ImportDlg: public wxDialog {
|
||||
wxFilePickerCtrl *m_filepicker;
|
||||
wxComboBox *m_import_dropdown, *m_quality_dropdown;
|
||||
|
||||
|
||||
public:
|
||||
ImportDlg(Plater *plater)
|
||||
: wxDialog{plater, wxID_ANY, "Import SLA archive"}
|
||||
{
|
||||
auto szvert = new wxBoxSizer{wxVERTICAL};
|
||||
auto szfilepck = new wxBoxSizer{wxHORIZONTAL};
|
||||
|
||||
|
||||
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
|
||||
"SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP",
|
||||
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
|
||||
szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER);
|
||||
szfilepck->Add(m_filepicker, 1);
|
||||
szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
|
||||
auto szchoices = new wxBoxSizer{wxHORIZONTAL};
|
||||
|
||||
|
||||
static const std::vector<wxString> inp_choices = {
|
||||
_(L("Import model and profile")),
|
||||
_(L("Import profile only")),
|
||||
_(L("Import model only"))
|
||||
};
|
||||
|
||||
|
||||
m_import_dropdown = new wxComboBox(
|
||||
this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize,
|
||||
inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
|
||||
|
||||
|
||||
szchoices->Add(m_import_dropdown);
|
||||
szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
|
||||
static const std::vector<wxString> qual_choices = {
|
||||
_(L("Accurate")),
|
||||
_(L("Balanced")),
|
||||
_(L("Quick"))
|
||||
};
|
||||
|
||||
|
||||
m_quality_dropdown = new wxComboBox(
|
||||
this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize,
|
||||
qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
|
||||
szchoices->Add(m_quality_dropdown);
|
||||
|
||||
|
||||
m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||
if (get_selection() == Sel::profileOnly)
|
||||
m_quality_dropdown->Disable();
|
||||
else m_quality_dropdown->Enable();
|
||||
});
|
||||
|
||||
|
||||
szvert->Add(szchoices, 0, wxALL, 5);
|
||||
szvert->AddStretchSpacer(1);
|
||||
auto szbtn = new wxBoxSizer(wxHORIZONTAL);
|
||||
szbtn->Add(new wxButton{this, wxID_CANCEL});
|
||||
szbtn->Add(new wxButton{this, wxID_OK});
|
||||
szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5);
|
||||
|
||||
|
||||
SetSizerAndFit(szvert);
|
||||
}
|
||||
|
||||
|
||||
Sel get_selection() const
|
||||
{
|
||||
int sel = m_import_dropdown->GetSelection();
|
||||
return Sel(std::min(int(Sel::modelOnly), std::max(0, sel)));
|
||||
}
|
||||
|
||||
|
||||
Vec2i get_marchsq_windowsize() const
|
||||
{
|
||||
enum { Accurate, Balanced, Fast};
|
||||
|
||||
|
||||
switch(m_quality_dropdown->GetSelection())
|
||||
{
|
||||
case Fast: return {8, 8};
|
||||
@ -102,7 +101,7 @@ public:
|
||||
return {2, 2};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
wxString get_path() const
|
||||
{
|
||||
return m_filepicker->GetPath();
|
||||
@ -112,7 +111,7 @@ public:
|
||||
class SLAImportJob::priv {
|
||||
public:
|
||||
Plater *plater;
|
||||
|
||||
|
||||
Sel sel = Sel::modelAndProfile;
|
||||
|
||||
indexed_triangle_set mesh;
|
||||
@ -125,8 +124,8 @@ public:
|
||||
priv(Plater *plt) : plater{plt} {}
|
||||
};
|
||||
|
||||
SLAImportJob::SLAImportJob(std::shared_ptr<NotificationManager> nm, Plater *plater)
|
||||
: PlaterJob{nm, plater}, p{std::make_unique<priv>(plater)}
|
||||
SLAImportJob::SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: PlaterJob{std::move(pri), plater}, p{std::make_unique<priv>(plater)}
|
||||
{}
|
||||
|
||||
SLAImportJob::~SLAImportJob() = default;
|
||||
@ -138,9 +137,9 @@ void SLAImportJob::process()
|
||||
update_status(int(s), _(L("Importing SLA archive")));
|
||||
return !was_canceled();
|
||||
};
|
||||
|
||||
|
||||
if (p->path.empty()) return;
|
||||
|
||||
|
||||
std::string path = p->path.ToUTF8().data();
|
||||
try {
|
||||
switch (p->sel) {
|
||||
@ -154,11 +153,11 @@ void SLAImportJob::process()
|
||||
p->config_substitutions = import_sla_archive(path, p->profile);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
} catch (std::exception &ex) {
|
||||
p->err = ex.what();
|
||||
}
|
||||
|
||||
|
||||
update_status(100, was_canceled() ? _(L("Importing canceled.")) :
|
||||
_(L("Importing done.")));
|
||||
}
|
||||
@ -175,9 +174,9 @@ void SLAImportJob::reset()
|
||||
void SLAImportJob::prepare()
|
||||
{
|
||||
reset();
|
||||
|
||||
|
||||
ImportDlg dlg{p->plater};
|
||||
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
auto path = dlg.get_path();
|
||||
auto nm = wxFileName(path);
|
||||
@ -194,15 +193,15 @@ void SLAImportJob::finalize()
|
||||
{
|
||||
// Ignore the arrange result if aborted.
|
||||
if (was_canceled()) return;
|
||||
|
||||
|
||||
if (!p->err.empty()) {
|
||||
show_error(p->plater, p->err);
|
||||
p->err = "";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
std::string name = wxFileName(p->path).GetName().ToUTF8().data();
|
||||
|
||||
|
||||
if (!p->profile.empty()) {
|
||||
const ModelObjectPtrs& objects = p->plater->model().objects;
|
||||
for (auto object : objects)
|
||||
@ -214,15 +213,15 @@ void SLAImportJob::finalize()
|
||||
_(L("Attention!")) );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
DynamicPrintConfig config = {};
|
||||
config.apply(SLAFullPrintConfig::defaults());
|
||||
config += std::move(p->profile);
|
||||
|
||||
|
||||
wxGetApp().preset_bundle->load_config_model(name, std::move(config));
|
||||
wxGetApp().load_current_presets();
|
||||
}
|
||||
|
||||
|
||||
if (!p->mesh.empty()) {
|
||||
bool is_centered = false;
|
||||
p->plater->sidebar().obj_list()->load_mesh_object(TriangleMesh{p->mesh},
|
||||
|
@ -5,20 +5,18 @@
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class NotificationManager;
|
||||
|
||||
class SLAImportJob : public PlaterJob {
|
||||
class priv;
|
||||
|
||||
|
||||
std::unique_ptr<priv> p;
|
||||
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void process() override;
|
||||
void finalize() override;
|
||||
|
||||
public:
|
||||
SLAImportJob(std::shared_ptr<NotificationManager> nm, Plater *plater);
|
||||
SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater);
|
||||
~SLAImportJob();
|
||||
|
||||
void reset();
|
||||
|
@ -222,13 +222,14 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
||||
}
|
||||
|
||||
if (m_plater != nullptr) {
|
||||
int saved_project = m_plater->save_project_if_dirty();
|
||||
int saved_project = m_plater->save_project_if_dirty(_L("Closing PrusaSlicer. Current project is modified."));
|
||||
if (saved_project == wxID_CANCEL) {
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
// check unsaved changes only if project wasn't saved
|
||||
else if (saved_project == wxID_NO && event.CanVeto() && !wxGetApp().check_and_save_current_preset_changes()) {
|
||||
else if (saved_project == wxID_NO && event.CanVeto() &&
|
||||
!wxGetApp().check_and_save_current_preset_changes(_L("PrusaSlicer is closing"), _L("Closing PrusaSlicer while some presets are modified."))) {
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
@ -434,8 +435,9 @@ void MainFrame::update_layout()
|
||||
{
|
||||
m_plater->Reparent(m_tabpanel);
|
||||
#ifdef _MSW_DARK_MODE
|
||||
m_plater->Layout();
|
||||
if (!wxGetApp().tabs_as_menu())
|
||||
dynamic_cast<Notebook*>(m_tabpanel)->InsertPage(0, m_plater, _L("Plater"), std::string("plater"));
|
||||
dynamic_cast<Notebook*>(m_tabpanel)->InsertPage(0, m_plater, _L("Plater"), std::string("plater"), true);
|
||||
else
|
||||
#endif
|
||||
m_tabpanel->InsertPage(0, m_plater, _L("Plater"));
|
||||
@ -460,7 +462,7 @@ void MainFrame::update_layout()
|
||||
m_plater_page = new wxPanel(m_tabpanel);
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (!wxGetApp().tabs_as_menu())
|
||||
dynamic_cast<Notebook*>(m_tabpanel)->InsertPage(0, m_plater_page, _L("Plater"), std::string("plater"));
|
||||
dynamic_cast<Notebook*>(m_tabpanel)->InsertPage(0, m_plater_page, _L("Plater"), std::string("plater"), true);
|
||||
else
|
||||
#endif
|
||||
m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */
|
||||
@ -820,7 +822,10 @@ bool MainFrame::is_active_and_shown_tab(Tab* tab)
|
||||
|
||||
bool MainFrame::can_start_new_project() const
|
||||
{
|
||||
return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || GetTitle().StartsWith('*') || !m_plater->model().objects.empty());
|
||||
return m_plater && (!m_plater->get_project_filename(".3mf").IsEmpty() ||
|
||||
GetTitle().StartsWith('*')||
|
||||
wxGetApp().has_current_preset_changes() ||
|
||||
!m_plater->model().objects.empty() );
|
||||
}
|
||||
|
||||
bool MainFrame::can_save() const
|
||||
@ -852,13 +857,14 @@ void MainFrame::save_project()
|
||||
save_project_as(m_plater->get_project_filename(".3mf"));
|
||||
}
|
||||
|
||||
void MainFrame::save_project_as(const wxString& filename)
|
||||
bool MainFrame::save_project_as(const wxString& filename)
|
||||
{
|
||||
bool ret = (m_plater != nullptr) ? m_plater->export_3mf(into_path(filename)) : false;
|
||||
if (ret) {
|
||||
// wxGetApp().update_saved_preset_from_current_preset();
|
||||
m_plater->reset_project_dirty_after_save();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MainFrame::can_export_model() const
|
||||
@ -1151,8 +1157,10 @@ void MainFrame::init_menubar_as_editor()
|
||||
Bind(wxEVT_MENU, [this](wxCommandEvent& evt) {
|
||||
size_t file_id = evt.GetId() - wxID_FILE1;
|
||||
wxString filename = m_recent_projects.GetHistoryFile(file_id);
|
||||
if (wxFileExists(filename))
|
||||
m_plater->load_project(filename);
|
||||
if (wxFileExists(filename)) {
|
||||
if (wxGetApp().can_load_project())
|
||||
m_plater->load_project(filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
//wxMessageDialog msg(this, _L("The selected project is no longer available.\nDo you want to remove it from the recent projects list?"), _L("Error"), wxYES_NO | wxYES_DEFAULT);
|
||||
@ -1740,7 +1748,6 @@ void MainFrame::repair_stl()
|
||||
|
||||
Slic3r::TriangleMesh tmesh;
|
||||
tmesh.ReadSTLFile(input_file.ToUTF8().data());
|
||||
tmesh.repair();
|
||||
tmesh.WriteOBJFile(output_file.ToUTF8().data());
|
||||
Slic3r::GUI::show_info(this, L("Your file was repaired."), L("Repair"));
|
||||
}
|
||||
@ -1773,7 +1780,7 @@ void MainFrame::export_config()
|
||||
// Load a config file containing a Print, Filament & Printer preset.
|
||||
void MainFrame::load_config_file()
|
||||
{
|
||||
if (!wxGetApp().check_and_save_current_preset_changes())
|
||||
if (!wxGetApp().check_and_save_current_preset_changes(_L("Loading of a configuration file"), "", false))
|
||||
return;
|
||||
wxFileDialog dlg(this, _L("Select configuration to load:"),
|
||||
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
|
||||
@ -1804,7 +1811,8 @@ bool MainFrame::load_config_file(const std::string &path)
|
||||
|
||||
void MainFrame::export_configbundle(bool export_physical_printers /*= false*/)
|
||||
{
|
||||
if (!wxGetApp().check_and_save_current_preset_changes())
|
||||
if (!wxGetApp().check_and_save_current_preset_changes(_L("Exporting configuration bundle"),
|
||||
_L("Some presets are modified and the unsaved changes will not be exported into configuration bundle."), false, true))
|
||||
return;
|
||||
// validate current configuration in case it's dirty
|
||||
auto err = wxGetApp().preset_bundle->full_config().validate();
|
||||
@ -1836,7 +1844,7 @@ void MainFrame::export_configbundle(bool export_physical_printers /*= false*/)
|
||||
// but that behavior was not documented and likely buggy.
|
||||
void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool reset_user_profile*/)
|
||||
{
|
||||
if (!wxGetApp().check_and_save_current_preset_changes())
|
||||
if (!wxGetApp().check_and_save_current_preset_changes(_L("Loading of a configuration bundle"), "", false))
|
||||
return;
|
||||
if (file.IsEmpty()) {
|
||||
wxFileDialog dlg(this, _L("Select configuration to load:"),
|
||||
|
@ -190,7 +190,7 @@ public:
|
||||
bool can_save() const;
|
||||
bool can_save_as() const;
|
||||
void save_project();
|
||||
void save_project_as(const wxString& filename = wxString());
|
||||
bool save_project_as(const wxString& filename = wxString());
|
||||
|
||||
void add_to_recent_projects(const wxString& filename);
|
||||
void technology_changed();
|
||||
|
@ -91,11 +91,9 @@ void MeshClipper::recalculate_triangles()
|
||||
MeshSlicingParams slicing_params;
|
||||
slicing_params.trafo.rotate(Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(up, Vec3d::UnitZ()));
|
||||
|
||||
assert(m_mesh->has_shared_vertices());
|
||||
ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params));
|
||||
|
||||
if (m_negative_mesh && !m_negative_mesh->empty()) {
|
||||
assert(m_negative_mesh->has_shared_vertices());
|
||||
ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params));
|
||||
expolys = diff_ex(expolys, neg_expolys);
|
||||
}
|
||||
@ -255,11 +253,10 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
|
||||
std::vector<unsigned> out;
|
||||
|
||||
const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true);
|
||||
Vec3f direction_to_camera = -camera.get_dir_forward().cast<float>();
|
||||
Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval();
|
||||
Vec3f scaling = trafo.get_scaling_factor().cast<float>();
|
||||
direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2));
|
||||
const Transform3f inverse_trafo = trafo.get_matrix().inverse().cast<float>();
|
||||
Vec3d direction_to_camera = -camera.get_dir_forward();
|
||||
Vec3d direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera).normalized().eval();
|
||||
direction_to_camera_mesh = direction_to_camera_mesh.cwiseProduct(trafo.get_scaling_factor());
|
||||
const Transform3d inverse_trafo = trafo.get_matrix().inverse();
|
||||
|
||||
for (size_t i=0; i<points.size(); ++i) {
|
||||
const Vec3f& pt = points[i];
|
||||
@ -270,9 +267,8 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
|
||||
// Cast a ray in the direction of the camera and look for intersection with the mesh:
|
||||
std::vector<sla::IndexedMesh::hit_result> hits;
|
||||
// Offset the start of the ray by EPSILON to account for numerical inaccuracies.
|
||||
hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast<double>(),
|
||||
direction_to_camera.cast<double>());
|
||||
|
||||
hits = m_emesh.query_ray_hits((inverse_trafo * pt.cast<double>() + direction_to_camera_mesh * EPSILON),
|
||||
direction_to_camera_mesh);
|
||||
|
||||
if (! hits.empty()) {
|
||||
// If the closest hit facet normal points in the same direction as the ray,
|
||||
|
@ -165,8 +165,8 @@ public:
|
||||
|
||||
GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name);
|
||||
|
||||
if (!DoSetSelectionAfterInsertion(n, bSelect))
|
||||
page->Hide();
|
||||
if (bSelect)
|
||||
SetSelection(n);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -174,7 +174,14 @@ public:
|
||||
virtual int SetSelection(size_t n) override
|
||||
{
|
||||
GetBtnsListCtrl()->SetSelection(n);
|
||||
return DoSetSelection(n, SetSelection_SendEvent);
|
||||
int ret = DoSetSelection(n, SetSelection_SendEvent);
|
||||
|
||||
// check that only the selected page is visible and others are hidden:
|
||||
for (size_t page = 0; page < m_pages.size(); page++)
|
||||
if (page != n)
|
||||
m_pages[page]->Hide();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual int ChangeSelection(size_t n) override
|
||||
|
@ -37,6 +37,7 @@ void ObjectDataViewModelNode::init_container()
|
||||
static constexpr char LayerRootIcon[] = "edit_layers_all";
|
||||
static constexpr char LayerIcon[] = "edit_layers_some";
|
||||
static constexpr char WarningIcon[] = "exclamation";
|
||||
static constexpr char WarningManifoldIcon[] = "exclamation_manifold";
|
||||
|
||||
struct InfoItemAtributes {
|
||||
std::string name;
|
||||
@ -57,13 +58,15 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* pare
|
||||
Slic3r::ModelVolumeType type,
|
||||
const wxBitmap& bmp,
|
||||
const wxString& extruder,
|
||||
const int idx/* = -1*/) :
|
||||
const int idx/* = -1*/,
|
||||
const std::string& warning_icon_name /*= std::string*/) :
|
||||
m_parent(parent),
|
||||
m_name(sub_obj_name),
|
||||
m_type(itVolume),
|
||||
m_volume_type(type),
|
||||
m_idx(idx),
|
||||
m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : "")
|
||||
m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : ""),
|
||||
m_warning_icon_name(warning_icon_name)
|
||||
{
|
||||
m_bmp = bmp;
|
||||
set_action_and_extruder_icons();
|
||||
@ -170,6 +173,13 @@ void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable)
|
||||
create_scaled_bitmap(m_printable == piPrintable ? "eye_open.png" : "eye_closed.png");
|
||||
}
|
||||
|
||||
void ObjectDataViewModelNode::set_warning_icon(const std::string& warning_icon_name)
|
||||
{
|
||||
m_warning_icon_name = warning_icon_name;
|
||||
if (warning_icon_name.empty())
|
||||
m_bmp = m_empty_bmp;
|
||||
}
|
||||
|
||||
void ObjectDataViewModelNode::update_settings_digest_bitmaps()
|
||||
{
|
||||
m_bmp = m_empty_bmp;
|
||||
@ -316,6 +326,8 @@ ObjectDataViewModel::ObjectDataViewModel()
|
||||
|
||||
m_volume_bmps = MenuFactory::get_volume_bitmaps();
|
||||
m_warning_bmp = create_scaled_bitmap(WarningIcon);
|
||||
m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon);
|
||||
|
||||
for (auto item : INFO_ITEMS)
|
||||
m_info_bmps[item.first] = create_scaled_bitmap(item.second.bmp_name);
|
||||
}
|
||||
@ -328,15 +340,19 @@ ObjectDataViewModel::~ObjectDataViewModel()
|
||||
m_bitmap_cache = nullptr;
|
||||
}
|
||||
|
||||
wxBitmap& ObjectDataViewModel::GetWarningBitmap(const std::string& warning_icon_name)
|
||||
{
|
||||
return warning_icon_name.empty() ? m_empty_bmp : warning_icon_name == WarningIcon ? m_warning_bmp : m_warning_manifold_bmp;
|
||||
}
|
||||
|
||||
wxDataViewItem ObjectDataViewModel::Add(const wxString &name,
|
||||
const int extruder,
|
||||
const bool has_errors/* = false*/)
|
||||
const std::string& warning_icon_name/* = std::string()*/ )
|
||||
{
|
||||
const wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder);
|
||||
const wxString extruder_str = extruder == 0 ? _L("default") : wxString::Format("%d", extruder);
|
||||
auto root = new ObjectDataViewModelNode(name, extruder_str);
|
||||
// Add error icon if detected auto-repaire
|
||||
if (has_errors)
|
||||
root->m_bmp = m_warning_bmp;
|
||||
// Add warning icon if detected auto-repaire
|
||||
root->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name);
|
||||
|
||||
m_objects.push_back(root);
|
||||
// notify control
|
||||
@ -350,7 +366,7 @@ wxDataViewItem ObjectDataViewModel::Add(const wxString &name,
|
||||
wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent_item,
|
||||
const wxString &name,
|
||||
const Slic3r::ModelVolumeType volume_type,
|
||||
const bool has_errors/* = false*/,
|
||||
const std::string& warning_icon_name/* = std::string()*/,
|
||||
const int extruder/* = 0*/,
|
||||
const bool create_frst_child/* = true*/)
|
||||
{
|
||||
@ -364,12 +380,10 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent
|
||||
if (insert_position < 0)
|
||||
insert_position = get_root_idx(root, itInstanceRoot);
|
||||
|
||||
const bool obj_errors = root->m_bmp.IsOk();
|
||||
|
||||
if (create_frst_child && root->m_volumes_cnt == 0)
|
||||
{
|
||||
const Slic3r::ModelVolumeType type = Slic3r::ModelVolumeType::MODEL_PART;
|
||||
const auto node = new ObjectDataViewModelNode(root, root->m_name, type, GetVolumeIcon(type, obj_errors), extruder_str, 0);
|
||||
const auto node = new ObjectDataViewModelNode(root, root->m_name, type, GetVolumeIcon(type, root->m_warning_icon_name), extruder_str, 0, root->m_warning_icon_name);
|
||||
|
||||
insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position);
|
||||
// notify control
|
||||
@ -380,12 +394,13 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent
|
||||
if (insert_position >= 0) insert_position++;
|
||||
}
|
||||
|
||||
const auto node = new ObjectDataViewModelNode(root, name, volume_type, GetVolumeIcon(volume_type, has_errors), extruder_str, root->m_volumes_cnt);
|
||||
const auto node = new ObjectDataViewModelNode(root, name, volume_type, GetVolumeIcon(volume_type, warning_icon_name), extruder_str, root->m_volumes_cnt, warning_icon_name);
|
||||
insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position);
|
||||
|
||||
// if part with errors is added, but object wasn't marked, then mark it
|
||||
if (!obj_errors && has_errors)
|
||||
root->SetBitmap(m_warning_bmp);
|
||||
if (!warning_icon_name.empty() && warning_icon_name != root->m_warning_icon_name &&
|
||||
(root->m_warning_icon_name.empty() || root->m_warning_icon_name == WarningManifoldIcon) )
|
||||
root->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name);
|
||||
|
||||
// notify control
|
||||
const wxDataViewItem child((void*)node);
|
||||
@ -1665,6 +1680,7 @@ void ObjectDataViewModel::Rescale()
|
||||
{
|
||||
m_volume_bmps = MenuFactory::get_volume_bitmaps();
|
||||
m_warning_bmp = create_scaled_bitmap(WarningIcon);
|
||||
m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon);
|
||||
|
||||
wxDataViewItemArray all_items;
|
||||
GetAllChildren(wxDataViewItem(0), all_items);
|
||||
@ -1680,10 +1696,10 @@ void ObjectDataViewModel::Rescale()
|
||||
switch (node->m_type)
|
||||
{
|
||||
case itObject:
|
||||
if (node->m_bmp.IsOk()) node->m_bmp = m_warning_bmp;
|
||||
if (node->m_bmp.IsOk()) node->m_bmp = GetWarningBitmap(node->m_warning_icon_name);
|
||||
break;
|
||||
case itVolume:
|
||||
node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_bmp.GetWidth() != node->m_bmp.GetHeight());
|
||||
node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_warning_icon_name);
|
||||
break;
|
||||
case itLayerRoot:
|
||||
node->m_bmp = create_scaled_bitmap(LayerRootIcon);
|
||||
@ -1697,19 +1713,19 @@ void ObjectDataViewModel::Rescale()
|
||||
}
|
||||
}
|
||||
|
||||
wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked/* = false*/)
|
||||
wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const std::string& warning_icon_name/* = std::string()*/)
|
||||
{
|
||||
if (!is_marked)
|
||||
if (warning_icon_name.empty())
|
||||
return m_volume_bmps[static_cast<int>(vol_type)];
|
||||
|
||||
std::string scaled_bitmap_name = "warning" + std::to_string(static_cast<int>(vol_type));
|
||||
scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit());
|
||||
std::string scaled_bitmap_name = warning_icon_name + std::to_string(static_cast<int>(vol_type));
|
||||
scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : "-lm");
|
||||
|
||||
wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name);
|
||||
if (bmp == nullptr) {
|
||||
std::vector<wxBitmap> bmps;
|
||||
|
||||
bmps.emplace_back(m_warning_bmp);
|
||||
bmps.emplace_back(GetWarningBitmap(warning_icon_name));
|
||||
bmps.emplace_back(m_volume_bmps[static_cast<int>(vol_type)]);
|
||||
|
||||
bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps);
|
||||
@ -1718,20 +1734,20 @@ wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_ty
|
||||
return *bmp;
|
||||
}
|
||||
|
||||
void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item)
|
||||
void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item, const std::string& warning_icon_name)
|
||||
{
|
||||
if (!item.IsOk())
|
||||
return;
|
||||
ObjectDataViewModelNode *node = static_cast<ObjectDataViewModelNode*>(item.GetID());
|
||||
|
||||
if (node->GetType() & itObject) {
|
||||
node->SetBitmap(m_warning_bmp);
|
||||
node->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->GetType() & itVolume) {
|
||||
node->SetBitmap(GetVolumeIcon(node->GetVolumeType(), true));
|
||||
node->GetParent()->SetBitmap(m_warning_bmp);
|
||||
node->SetWarningBitmap(GetVolumeIcon(node->GetVolumeType(), warning_icon_name), warning_icon_name);
|
||||
node->GetParent()->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1747,11 +1763,11 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo
|
||||
return;
|
||||
|
||||
if (node->GetType() & itVolume) {
|
||||
node->SetBitmap(m_volume_bmps[static_cast<int>(node->volume_type())]);
|
||||
node->SetWarningBitmap(m_volume_bmps[static_cast<int>(node->volume_type())], "");
|
||||
return;
|
||||
}
|
||||
|
||||
node->SetBitmap(wxNullBitmap);
|
||||
node->SetWarningBitmap(wxNullBitmap, "");
|
||||
if (unmark_object)
|
||||
{
|
||||
wxDataViewItemArray children;
|
||||
|
@ -78,6 +78,7 @@ class ObjectDataViewModelNode
|
||||
wxBitmap m_action_icon;
|
||||
PrintIndicator m_printable {piUndef};
|
||||
wxBitmap m_printable_icon;
|
||||
std::string m_warning_icon_name{ "" };
|
||||
|
||||
std::string m_action_icon_name = "";
|
||||
ModelVolumeType m_volume_type;
|
||||
@ -100,7 +101,8 @@ public:
|
||||
Slic3r::ModelVolumeType type,
|
||||
const wxBitmap& bmp,
|
||||
const wxString& extruder,
|
||||
const int idx = -1);
|
||||
const int idx = -1,
|
||||
const std::string& warning_icon_name = std::string());
|
||||
|
||||
ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
|
||||
const t_layer_height_range& layer_range,
|
||||
@ -179,6 +181,7 @@ public:
|
||||
void SetVolumeType(ModelVolumeType type) { m_volume_type = type; }
|
||||
void SetBitmap(const wxBitmap &icon) { m_bmp = icon; }
|
||||
void SetExtruder(const wxString &extruder) { m_extruder = extruder; }
|
||||
void SetWarningBitmap(const wxBitmap& icon, const std::string& warning_icon_name) { m_bmp = icon; m_warning_icon_name = warning_icon_name; }
|
||||
const wxBitmap& GetBitmap() const { return m_bmp; }
|
||||
const wxString& GetName() const { return m_name; }
|
||||
ItemType GetType() const { return m_type; }
|
||||
@ -225,6 +228,8 @@ public:
|
||||
void set_extruder_icon();
|
||||
// Set printable icon for node
|
||||
void set_printable_icon(PrintIndicator printable);
|
||||
// Set warning icon for node
|
||||
void set_warning_icon(const std::string& warning_icon);
|
||||
|
||||
void update_settings_digest_bitmaps();
|
||||
bool update_settings_digest(const std::vector<std::string>& categories);
|
||||
@ -253,7 +258,9 @@ class ObjectDataViewModel :public wxDataViewModel
|
||||
std::vector<ObjectDataViewModelNode*> m_objects;
|
||||
std::vector<wxBitmap> m_volume_bmps;
|
||||
std::map<InfoItemType, wxBitmap> m_info_bmps;
|
||||
wxBitmap m_empty_bmp;
|
||||
wxBitmap m_warning_bmp;
|
||||
wxBitmap m_warning_manifold_bmp;
|
||||
|
||||
wxDataViewCtrl* m_ctrl { nullptr };
|
||||
|
||||
@ -263,11 +270,11 @@ public:
|
||||
|
||||
wxDataViewItem Add( const wxString &name,
|
||||
const int extruder,
|
||||
const bool has_errors = false);
|
||||
const std::string& warning_icon_name = std::string());
|
||||
wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item,
|
||||
const wxString &name,
|
||||
const Slic3r::ModelVolumeType volume_type,
|
||||
const bool has_errors = false,
|
||||
const std::string& warning_icon_name = std::string(),
|
||||
const int extruder = 0,
|
||||
const bool create_frst_child = true);
|
||||
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
|
||||
@ -378,8 +385,8 @@ public:
|
||||
void Rescale();
|
||||
|
||||
wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type,
|
||||
const bool is_marked = false);
|
||||
void AddWarningIcon(const wxDataViewItem& item);
|
||||
const std::string& warning_icon_name = std::string());
|
||||
void AddWarningIcon(const wxDataViewItem& item, const std::string& warning_name);
|
||||
void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false);
|
||||
t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const;
|
||||
|
||||
@ -392,6 +399,8 @@ private:
|
||||
wxDataViewItem AddRoot(const wxDataViewItem& parent_item, const ItemType root_type);
|
||||
wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item);
|
||||
void AddAllChildren(const wxDataViewItem& parent);
|
||||
|
||||
wxBitmap& GetWarningBitmap(const std::string& warning_icon_name);
|
||||
};
|
||||
|
||||
|
||||
|
@ -532,9 +532,9 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change)
|
||||
const std::vector<VendorProfile::PrinterModel>& models = preset->vendor->models;
|
||||
auto it = std::find_if(models.begin(), models.end(),
|
||||
[model_id](const VendorProfile::PrinterModel& model) { return model.id == model_id; });
|
||||
if (it != models.end() && it->family == "MK3")
|
||||
if (it != models.end() && (it->family == "MK3" || it->family == "MINI"))
|
||||
continue;
|
||||
} else if (!preset->vendor && model_id.rfind("MK3", 0) == 0) {
|
||||
} else if (!preset->vendor && (boost::ends_with(model_id, "MK3") || boost::ends_with(model_id, "MINI"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -67,8 +67,8 @@
|
||||
#include "Jobs/FillBedJob.hpp"
|
||||
#include "Jobs/RotoptimizeJob.hpp"
|
||||
#include "Jobs/SLAImportJob.hpp"
|
||||
#include "Jobs/NotificationProgressIndicator.hpp"
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "ProgressStatusBar.hpp"
|
||||
#include "PrintHostDialogs.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
#include "../Utils/ASCIIFolding.hpp"
|
||||
@ -125,6 +125,7 @@ wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent);
|
||||
|
||||
class ObjectInfo : public wxStaticBoxSizer
|
||||
{
|
||||
std::string m_warning_icon_name{ "exclamation" };
|
||||
public:
|
||||
ObjectInfo(wxWindow *parent);
|
||||
|
||||
@ -142,6 +143,7 @@ public:
|
||||
bool showing_manifold_warning_icon;
|
||||
void show_sizer(bool show);
|
||||
void msw_rescale();
|
||||
void update_warning_icon(const std::string& warning_icon_name);
|
||||
};
|
||||
|
||||
ObjectInfo::ObjectInfo(wxWindow *parent) :
|
||||
@ -175,7 +177,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) :
|
||||
info_manifold_text->SetFont(wxGetApp().small_font());
|
||||
info_manifold = new wxStaticText(parent, wxID_ANY, "");
|
||||
info_manifold->SetFont(wxGetApp().small_font());
|
||||
manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap("exclamation"));
|
||||
manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(m_warning_icon_name));
|
||||
auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer_manifold->Add(info_manifold_text, 0);
|
||||
sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2);
|
||||
@ -194,7 +196,15 @@ void ObjectInfo::show_sizer(bool show)
|
||||
|
||||
void ObjectInfo::msw_rescale()
|
||||
{
|
||||
manifold_warning_icon->SetBitmap(create_scaled_bitmap("exclamation"));
|
||||
manifold_warning_icon->SetBitmap(create_scaled_bitmap(m_warning_icon_name));
|
||||
}
|
||||
|
||||
void ObjectInfo::update_warning_icon(const std::string& warning_icon_name)
|
||||
{
|
||||
if (warning_icon_name.empty())
|
||||
return;
|
||||
m_warning_icon_name = warning_icon_name;
|
||||
manifold_warning_icon->SetBitmap(create_scaled_bitmap(m_warning_icon_name));
|
||||
}
|
||||
|
||||
enum SlicedInfoIdx
|
||||
@ -1129,32 +1139,19 @@ void Sidebar::show_info_sizer()
|
||||
p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0)*koef, size(1)*koef, size(2)*koef));
|
||||
p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast<int>(model_object->materials_count())));
|
||||
|
||||
const auto& stats = model_object->get_object_stl_stats();//model_object->volumes.front()->mesh.stl.stats;
|
||||
const auto& stats = model_object->get_object_stl_stats();
|
||||
p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume*pow(koef,3)));
|
||||
p->object_info->info_facets->SetLabel(format_wxstr(_L_PLURAL("%1% (%2$d shell)", "%1% (%2$d shells)", stats.number_of_parts),
|
||||
static_cast<int>(model_object->facets_count()), stats.number_of_parts));
|
||||
|
||||
int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
|
||||
stats.facets_added + stats.facets_reversed + stats.backwards_edges;
|
||||
if (errors > 0) {
|
||||
wxString tooltip = format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors);
|
||||
p->object_info->info_manifold->SetLabel(tooltip);
|
||||
if (stats.repaired()) {
|
||||
int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + stats.facets_reversed + stats.backwards_edges;
|
||||
p->object_info->info_manifold->SetLabel(format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors));
|
||||
|
||||
tooltip += ":\n";
|
||||
if (stats.degenerate_facets > 0)
|
||||
tooltip += format_wxstr(_L_PLURAL("%1$d degenerate facet", "%1$d degenerate facets", stats.degenerate_facets), stats.degenerate_facets) + ", ";
|
||||
if (stats.edges_fixed > 0)
|
||||
tooltip += format_wxstr(_L_PLURAL("%1$d edge fixed", "%1$d edges fixed", stats.edges_fixed), stats.edges_fixed) + ", ";
|
||||
if (stats.facets_removed > 0)
|
||||
tooltip += format_wxstr(_L_PLURAL("%1$d facet removed", "%1$d facets removed", stats.facets_removed), stats.facets_removed) + ", ";
|
||||
if (stats.facets_added > 0)
|
||||
tooltip += format_wxstr(_L_PLURAL("%1$d facet added", "%1$d facets added", stats.facets_added), stats.facets_added) + ", ";
|
||||
if (stats.facets_reversed > 0)
|
||||
tooltip += format_wxstr(_L_PLURAL("%1$d facet reversed", "%1$d facets reversed", stats.facets_reversed), stats.facets_reversed) + ", ";
|
||||
if (stats.backwards_edges > 0)
|
||||
tooltip += format_wxstr(_L_PLURAL("%1$d backwards edge", "%1$d backwards edges", stats.backwards_edges), stats.backwards_edges) + ", ";
|
||||
tooltip.RemoveLast(2);//remove last coma
|
||||
auto mesh_errors = obj_list()->get_mesh_errors(true);
|
||||
wxString tooltip = mesh_errors.first;
|
||||
|
||||
p->object_info->update_warning_icon(mesh_errors.second);
|
||||
p->object_info->showing_manifold_warning_icon = true;
|
||||
p->object_info->info_manifold->SetToolTip(tooltip);
|
||||
p->object_info->manifold_warning_icon->SetToolTip(tooltip);
|
||||
@ -1501,7 +1498,7 @@ struct Plater::priv
|
||||
GLToolbar view_toolbar;
|
||||
GLToolbar collapse_toolbar;
|
||||
Preview *preview;
|
||||
std::shared_ptr<NotificationManager> notification_manager;
|
||||
std::unique_ptr<NotificationManager> notification_manager;
|
||||
|
||||
ProjectDirtyStateManager dirty_state;
|
||||
|
||||
@ -1516,16 +1513,19 @@ struct Plater::priv
|
||||
{
|
||||
priv *m;
|
||||
size_t m_arrange_id, m_fill_bed_id, m_rotoptimize_id, m_sla_import_id;
|
||||
std::shared_ptr<NotificationProgressIndicator> m_pri;
|
||||
|
||||
void before_start() override { m->background_process.stop(); }
|
||||
|
||||
public:
|
||||
Jobs(priv *_m) : m(_m)
|
||||
Jobs(priv *_m) :
|
||||
m(_m),
|
||||
m_pri{std::make_shared<NotificationProgressIndicator>(m->notification_manager.get())}
|
||||
{
|
||||
m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->notification_manager, m->q));
|
||||
m_fill_bed_id = add_job(std::make_unique<FillBedJob>(m->notification_manager, m->q));
|
||||
m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->notification_manager, m->q));
|
||||
m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->notification_manager, m->q));
|
||||
m_arrange_id = add_job(std::make_unique<ArrangeJob>(m_pri, m->q));
|
||||
m_fill_bed_id = add_job(std::make_unique<FillBedJob>(m_pri, m->q));
|
||||
m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m_pri, m->q));
|
||||
m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m_pri, m->q));
|
||||
}
|
||||
|
||||
void arrange()
|
||||
@ -1575,16 +1575,22 @@ struct Plater::priv
|
||||
|
||||
bool is_project_dirty() const { return dirty_state.is_dirty(); }
|
||||
void update_project_dirty_from_presets() { dirty_state.update_from_presets(); }
|
||||
int save_project_if_dirty() {
|
||||
int save_project_if_dirty(const wxString& reason) {
|
||||
int res = wxID_NO;
|
||||
if (dirty_state.is_dirty()) {
|
||||
MainFrame* mainframe = wxGetApp().mainframe;
|
||||
if (mainframe->can_save_as()) {
|
||||
//wxMessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL);
|
||||
MessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL);
|
||||
res = dlg.ShowModal();
|
||||
wxString suggested_project_name;
|
||||
wxString project_name = suggested_project_name = get_project_filename(".3mf");
|
||||
if (suggested_project_name.IsEmpty()) {
|
||||
fs::path output_file = get_export_file_path(FT_3MF);
|
||||
suggested_project_name = output_file.empty() ? _L("Untitled") : from_u8(output_file.stem().string());
|
||||
}
|
||||
res = MessageDialog(mainframe, reason + "\n" + format_wxstr(_L("Do you want to save the changes to \"%1%\"?"), suggested_project_name),
|
||||
wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL).ShowModal();
|
||||
if (res == wxID_YES)
|
||||
mainframe->save_project_as(wxGetApp().plater()->get_project_filename());
|
||||
if (!mainframe->save_project_as(project_name))
|
||||
res = wxID_CANCEL;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
@ -1644,6 +1650,7 @@ struct Plater::priv
|
||||
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool used_inches = false);
|
||||
std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false);
|
||||
|
||||
fs::path get_export_file_path(GUI::FileType file_type);
|
||||
wxString get_export_file(GUI::FileType file_type);
|
||||
|
||||
const Selection& get_selection() const;
|
||||
@ -1849,7 +1856,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
"support_material_contact_distance", "support_material_bottom_contact_distance", "raft_layers"
|
||||
}))
|
||||
, sidebar(new Sidebar(q))
|
||||
, notification_manager(std::make_shared<NotificationManager>(q))
|
||||
, notification_manager(std::make_unique<NotificationManager>(q))
|
||||
, m_ui_jobs(this)
|
||||
, delayed_scene_refresh(false)
|
||||
, view_toolbar(GLToolbar::Radio, "View")
|
||||
@ -2544,16 +2551,14 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode
|
||||
if (max_ratio > 10000) {
|
||||
// the size of the object is too big -> this could lead to overflow when moving to clipper coordinates,
|
||||
// so scale down the mesh
|
||||
double inv = 1. / max_ratio;
|
||||
object->scale_mesh_after_creation(inv * Vec3d::Ones());
|
||||
object->scale_mesh_after_creation(1. / max_ratio);
|
||||
object->origin_translation = Vec3d::Zero();
|
||||
object->center_around_origin();
|
||||
scaled_down = true;
|
||||
break;
|
||||
}
|
||||
else if (max_ratio > 5) {
|
||||
const Vec3d inverse = 1.0 / max_ratio * Vec3d::Ones();
|
||||
instance->set_scaling_factor(inverse.cwiseProduct(instance->get_scaling_factor()));
|
||||
instance->set_scaling_factor(instance->get_scaling_factor() / max_ratio);
|
||||
scaled_down = true;
|
||||
}
|
||||
}
|
||||
@ -2601,22 +2606,8 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode
|
||||
return obj_idxs;
|
||||
}
|
||||
|
||||
wxString Plater::priv::get_export_file(GUI::FileType file_type)
|
||||
fs::path Plater::priv::get_export_file_path(GUI::FileType file_type)
|
||||
{
|
||||
wxString wildcard;
|
||||
switch (file_type) {
|
||||
case FT_STL:
|
||||
case FT_AMF:
|
||||
case FT_3MF:
|
||||
case FT_GCODE:
|
||||
case FT_OBJ:
|
||||
wildcard = file_wildcards(file_type);
|
||||
break;
|
||||
default:
|
||||
wildcard = file_wildcards(FT_MODEL);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update printbility state of each of the ModelInstances.
|
||||
this->update_print_volume_state();
|
||||
|
||||
@ -2641,7 +2632,31 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type)
|
||||
if (output_file.empty() && !model.objects.empty())
|
||||
// Find the file name of the first object.
|
||||
output_file = this->model.objects[0]->get_export_filename();
|
||||
|
||||
if (output_file.empty())
|
||||
// Use _L("Untitled") name
|
||||
output_file = into_path(_L("Untitled"));
|
||||
}
|
||||
return output_file;
|
||||
}
|
||||
|
||||
wxString Plater::priv::get_export_file(GUI::FileType file_type)
|
||||
{
|
||||
wxString wildcard;
|
||||
switch (file_type) {
|
||||
case FT_STL:
|
||||
case FT_AMF:
|
||||
case FT_3MF:
|
||||
case FT_GCODE:
|
||||
case FT_OBJ:
|
||||
wildcard = file_wildcards(file_type);
|
||||
break;
|
||||
default:
|
||||
wildcard = file_wildcards(FT_MODEL);
|
||||
break;
|
||||
}
|
||||
|
||||
fs::path output_file = get_export_file_path(file_type);
|
||||
|
||||
wxString dlg_title;
|
||||
switch (file_type) {
|
||||
@ -3752,10 +3767,10 @@ void Plater::priv::set_current_panel(wxPanel* panel)
|
||||
|
||||
if (view3D->is_reload_delayed()) {
|
||||
// Delayed loading of the 3D scene.
|
||||
if (this->printer_technology == ptSLA) {
|
||||
if (printer_technology == ptSLA) {
|
||||
// Update the SLAPrint from the current Model, so that the reload_scene()
|
||||
// pulls the correct data.
|
||||
this->update_restart_background_process(true, false);
|
||||
update_restart_background_process(true, false);
|
||||
} else
|
||||
view3D->reload_scene(true);
|
||||
}
|
||||
@ -3778,8 +3793,14 @@ void Plater::priv::set_current_panel(wxPanel* panel)
|
||||
// FIXME: it may be better to have a single function making this check and let it be called wherever needed
|
||||
bool export_in_progress = this->background_process.is_export_scheduled();
|
||||
bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state() != ModelInstancePVS_Partly_Outside;
|
||||
if (!model.objects.empty() && !export_in_progress && model_fits)
|
||||
this->q->reslice();
|
||||
if (!model.objects.empty() && !export_in_progress && model_fits) {
|
||||
#if ENABLE_SEAMS_USING_MODELS
|
||||
// the following call is needed to ensure that GCodeViewer buffers are initialized
|
||||
// before calling reslice() when background processing is active
|
||||
preview->SetFocusFromKbd();
|
||||
#endif // ENABLE_SEAMS_USING_MODELS
|
||||
q->reslice();
|
||||
}
|
||||
// keeps current gcode preview, if any
|
||||
preview->reload_print(true);
|
||||
|
||||
@ -3920,20 +3941,16 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
|
||||
|
||||
void Plater::priv::on_slicing_completed(wxCommandEvent & evt)
|
||||
{
|
||||
switch (this->printer_technology) {
|
||||
case ptFFF:
|
||||
this->update_fff_scene();
|
||||
break;
|
||||
case ptSLA:
|
||||
if (view3D->is_dragging())
|
||||
delayed_scene_refresh = true;
|
||||
if (view3D->is_dragging()) // updating scene now would interfere with the gizmo dragging
|
||||
delayed_scene_refresh = true;
|
||||
else {
|
||||
if (this->printer_technology == ptFFF)
|
||||
this->update_fff_scene();
|
||||
else
|
||||
this->update_sla_scene();
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Plater::priv::on_export_began(wxCommandEvent& evt)
|
||||
{
|
||||
if (show_warning_dialog)
|
||||
@ -4052,17 +4069,13 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
|
||||
this->object_list_changed();
|
||||
|
||||
// refresh preview
|
||||
switch (this->printer_technology) {
|
||||
case ptFFF:
|
||||
this->update_fff_scene();
|
||||
break;
|
||||
case ptSLA:
|
||||
if (view3D->is_dragging())
|
||||
delayed_scene_refresh = true;
|
||||
if (view3D->is_dragging()) // updating scene now would interfere with the gizmo dragging
|
||||
delayed_scene_refresh = true;
|
||||
else {
|
||||
if (this->printer_technology == ptFFF)
|
||||
this->update_fff_scene();
|
||||
else
|
||||
this->update_sla_scene();
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (evt.cancelled()) {
|
||||
@ -4718,8 +4731,10 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
|
||||
if (printer_technology_changed) {
|
||||
// Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type.
|
||||
std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA";
|
||||
if (!wxGetApp().check_and_save_current_preset_changes(format_wxstr(_L(
|
||||
"%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt)))
|
||||
if (!wxGetApp().check_and_save_current_preset_changes(_L("Undo / Redo is processing"),
|
||||
// format_wxstr(_L("%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt)))
|
||||
format_wxstr(_L("Switching the printer technology from %1% to %2%.\n"
|
||||
"Some %1% presets were modified, which will be lost after switching the printer technology."), s_pt =="FFF" ? "SLA" : "FFF", s_pt), false))
|
||||
// Don't switch the profiles.
|
||||
return;
|
||||
}
|
||||
@ -4897,7 +4912,7 @@ Plater::Plater(wxWindow *parent, MainFrame *main_frame)
|
||||
|
||||
bool Plater::is_project_dirty() const { return p->is_project_dirty(); }
|
||||
void Plater::update_project_dirty_from_presets() { p->update_project_dirty_from_presets(); }
|
||||
int Plater::save_project_if_dirty() { return p->save_project_if_dirty(); }
|
||||
int Plater::save_project_if_dirty(const wxString& reason) { return p->save_project_if_dirty(reason); }
|
||||
void Plater::reset_project_dirty_after_save() { p->reset_project_dirty_after_save(); }
|
||||
void Plater::reset_project_dirty_initial_presets() { p->reset_project_dirty_initial_presets(); }
|
||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
|
||||
@ -4914,8 +4929,20 @@ SLAPrint& Plater::sla_print() { return p->sla_print; }
|
||||
|
||||
void Plater::new_project()
|
||||
{
|
||||
if (p->save_project_if_dirty() == wxID_CANCEL)
|
||||
if (int saved_project = p->save_project_if_dirty(_L("Creating a new project while the current project is modified.")); saved_project == wxID_CANCEL)
|
||||
return;
|
||||
else {
|
||||
wxString header = _L("Creating a new project while some presets are modified.") + "\n" +
|
||||
(saved_project == wxID_YES ? _L("You can keep presets modifications to the new project or discard them") :
|
||||
_L("You can keep presets modifications to the new project, discard them or save changes as new presets.\n"
|
||||
"Note, if changes will be saved than new project wouldn't keep them"));
|
||||
using ab = UnsavedChangesDialog::ActionButtons;
|
||||
int act_buttons = ab::KEEP;
|
||||
if (saved_project == wxID_NO)
|
||||
act_buttons |= ab::SAVE;
|
||||
if (!wxGetApp().check_and_keep_current_preset_changes(_L("New Project is creating"), header, act_buttons))
|
||||
return;
|
||||
}
|
||||
|
||||
p->select_view_3D("3D");
|
||||
take_snapshot(_L("New Project"));
|
||||
@ -4927,7 +4954,7 @@ void Plater::new_project()
|
||||
|
||||
void Plater::load_project()
|
||||
{
|
||||
if (p->save_project_if_dirty() == wxID_CANCEL)
|
||||
if (!wxGetApp().can_load_project())
|
||||
return;
|
||||
|
||||
// Ask user for a project file name.
|
||||
@ -5223,7 +5250,8 @@ bool Plater::load_files(const wxArrayString& filenames)
|
||||
|
||||
switch (load_type) {
|
||||
case LoadType::OpenProject: {
|
||||
load_project(from_path(*it));
|
||||
if (wxGetApp().can_load_project())
|
||||
load_project(from_path(*it));
|
||||
break;
|
||||
}
|
||||
case LoadType::LoadGeometry: {
|
||||
@ -5587,11 +5615,9 @@ void Plater::export_stl(bool extended, bool selection_only)
|
||||
for (const ModelVolume *v : mo->volumes)
|
||||
if (v->is_model_part()) {
|
||||
TriangleMesh vol_mesh(v->mesh());
|
||||
vol_mesh.repair();
|
||||
vol_mesh.transform(v->get_matrix(), true);
|
||||
mesh.merge(vol_mesh);
|
||||
}
|
||||
mesh.repair();
|
||||
if (instances) {
|
||||
TriangleMesh vols_mesh(mesh);
|
||||
mesh = TriangleMesh();
|
||||
@ -5601,7 +5627,6 @@ void Plater::export_stl(bool extended, bool selection_only)
|
||||
mesh.merge(m);
|
||||
}
|
||||
}
|
||||
mesh.repair();
|
||||
return mesh;
|
||||
};
|
||||
|
||||
@ -6511,12 +6536,7 @@ void Plater::search(bool plater_is_active)
|
||||
canvas3D()->on_char(evt);
|
||||
}
|
||||
else
|
||||
{
|
||||
wxPoint pos = this->ClientToScreen(wxPoint(0, 0));
|
||||
pos.x += em_unit(this) * 40;
|
||||
pos.y += em_unit(this) * 4;
|
||||
p->sidebar->get_searcher().search_dialog->Popup(pos);
|
||||
}
|
||||
p->sidebar->get_searcher().show_dialog();
|
||||
}
|
||||
|
||||
void Plater::msw_rescale()
|
||||
@ -6647,9 +6667,14 @@ Mouse3DController& Plater::get_mouse3d_controller()
|
||||
return p->mouse3d_controller;
|
||||
}
|
||||
|
||||
std::shared_ptr<NotificationManager> Plater::get_notification_manager()
|
||||
NotificationManager * Plater::get_notification_manager()
|
||||
{
|
||||
return p->notification_manager;
|
||||
return p->notification_manager.get();
|
||||
}
|
||||
|
||||
const NotificationManager * Plater::get_notification_manager() const
|
||||
{
|
||||
return p->notification_manager.get();
|
||||
}
|
||||
|
||||
void Plater::init_notification_manager()
|
||||
|
@ -140,7 +140,7 @@ public:
|
||||
|
||||
bool is_project_dirty() const;
|
||||
void update_project_dirty_from_presets();
|
||||
int save_project_if_dirty();
|
||||
int save_project_if_dirty(const wxString& reason);
|
||||
void reset_project_dirty_after_save();
|
||||
void reset_project_dirty_initial_presets();
|
||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
|
||||
@ -358,7 +358,9 @@ public:
|
||||
void set_bed_shape() const;
|
||||
void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const;
|
||||
|
||||
std::shared_ptr<NotificationManager> get_notification_manager();
|
||||
NotificationManager * get_notification_manager();
|
||||
const NotificationManager * get_notification_manager() const;
|
||||
|
||||
void init_notification_manager();
|
||||
|
||||
void bring_instance_forward();
|
||||
|
@ -73,7 +73,7 @@ void PreferencesDialog::build(size_t selected_tab)
|
||||
// Add "General" tab
|
||||
m_optgroup_general = create_options_tab(_L("General"), tabs);
|
||||
m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset")
|
||||
if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset" || opt_key == "default_action_on_new_project")
|
||||
m_values[opt_key] = boost::any_cast<bool>(value) ? "none" : "discard";
|
||||
else
|
||||
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
|
||||
@ -186,19 +186,28 @@ void PreferencesDialog::build(size_t selected_tab)
|
||||
option = Option(def, "single_instance");
|
||||
m_optgroup_general->append_single_option_line(option);
|
||||
|
||||
def.label = L("Ask for unsaved changes when closing application");
|
||||
def.label = L("Ask for unsaved changes when closing application or loading new project");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("When closing the application, always ask for unsaved changes");
|
||||
def.tooltip = L("Always ask for unsaved changes, when: \n"
|
||||
"- Closing PrusaSlicer while some presets are modified,\n"
|
||||
"- Loading a new project while some presets are modified");
|
||||
def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_close_application") == "none" });
|
||||
option = Option(def, "default_action_on_close_application");
|
||||
m_optgroup_general->append_single_option_line(option);
|
||||
|
||||
def.label = L("Ask for unsaved changes when selecting new preset");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("Always ask for unsaved changes when selecting new preset");
|
||||
def.tooltip = L("Always ask for unsaved changes when selecting new preset or resetting a preset");
|
||||
def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_select_preset") == "none" });
|
||||
option = Option(def, "default_action_on_select_preset");
|
||||
m_optgroup_general->append_single_option_line(option);
|
||||
|
||||
def.label = L("Ask for unsaved changes when creating new project");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("Always ask for unsaved changes when creating new project");
|
||||
def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_new_project") == "none" });
|
||||
option = Option(def, "default_action_on_new_project");
|
||||
m_optgroup_general->append_single_option_line(option);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
else {
|
||||
|
@ -201,9 +201,7 @@ namespace search_for_drives_internal
|
||||
struct stat buf;
|
||||
stat(path.c_str(), &buf);
|
||||
uid_t uid = buf.st_uid;
|
||||
std::string username(std::getenv("USER"));
|
||||
struct passwd *pw = getpwuid(uid);
|
||||
if (pw != 0 && pw->pw_name == username)
|
||||
if (getuid() == uid)
|
||||
out.emplace_back(DriveData{ boost::filesystem::basename(boost::filesystem::path(path)), path });
|
||||
}
|
||||
}
|
||||
@ -245,7 +243,7 @@ std::vector<DriveData> RemovableDriveManager::search_for_removable_drives() cons
|
||||
search_for_drives_internal::search_path("/media/*", "/media", current_drives);
|
||||
|
||||
//search_path("/Volumes/*", "/Volumes");
|
||||
std::string path(std::getenv("USER"));
|
||||
std::string path = wxGetUserId().ToUTF8().data();
|
||||
std::string pp(path);
|
||||
|
||||
//search /media/USERNAME/* folder
|
||||
|
@ -289,7 +289,6 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/)
|
||||
|
||||
OptionsSearcher::OptionsSearcher()
|
||||
{
|
||||
search_dialog = new SearchDialog(this);
|
||||
}
|
||||
|
||||
OptionsSearcher::~OptionsSearcher()
|
||||
@ -386,6 +385,22 @@ Option OptionsSearcher::get_option(const std::string& opt_key, const wxString& l
|
||||
return create_option(opt_key, label, type, gc);
|
||||
}
|
||||
|
||||
void OptionsSearcher::show_dialog()
|
||||
{
|
||||
if (!search_dialog) {
|
||||
search_dialog = new SearchDialog(this);
|
||||
|
||||
auto parent = search_dialog->GetParent();
|
||||
wxPoint pos = parent->ClientToScreen(wxPoint(0, 0));
|
||||
pos.x += em_unit(parent) * 40;
|
||||
pos.y += em_unit(parent) * 4;
|
||||
|
||||
search_dialog->SetPosition(pos);
|
||||
}
|
||||
|
||||
search_dialog->Popup();
|
||||
}
|
||||
|
||||
void OptionsSearcher::add_key(const std::string& opt_key, Preset::Type type, const wxString& group, const wxString& category)
|
||||
{
|
||||
groups_and_categories[get_key(opt_key, type)] = GroupAndCategory{group, category};
|
||||
@ -405,7 +420,7 @@ static const std::map<const char, int> icon_idxs = {
|
||||
};
|
||||
|
||||
SearchDialog::SearchDialog(OptionsSearcher* searcher)
|
||||
: GUI::DPIDialog(NULL, wxID_ANY, _L("Search"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
|
||||
: GUI::DPIDialog(GUI::wxGetApp().tab_panel(), wxID_ANY, _L("Search"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
|
||||
searcher(searcher)
|
||||
{
|
||||
SetFont(GUI::wxGetApp().normal_font());
|
||||
@ -506,7 +521,8 @@ void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
|
||||
if (check_english)
|
||||
check_english->SetValue(params.english);
|
||||
|
||||
this->SetPosition(position);
|
||||
if (position != wxDefaultPosition)
|
||||
this->SetPosition(position);
|
||||
this->ShowModal();
|
||||
}
|
||||
|
||||
|
@ -134,6 +134,8 @@ public:
|
||||
return o1.key < o2.key; });
|
||||
}
|
||||
void sort_options_by_label() { sort_options(); }
|
||||
|
||||
void show_dialog();
|
||||
};
|
||||
|
||||
|
||||
|
@ -1216,12 +1216,20 @@ void Tab::cache_config_diff(const std::vector<std::string>& selected_options)
|
||||
|
||||
void Tab::apply_config_from_cache()
|
||||
{
|
||||
bool was_applied = false;
|
||||
// check and apply extruders count for printer preset
|
||||
if (m_type == Preset::TYPE_PRINTER)
|
||||
was_applied = static_cast<TabPrinter*>(this)->apply_extruder_cnt_from_cache();
|
||||
|
||||
if (!m_cache_config.empty()) {
|
||||
m_presets->get_edited_preset().config.apply(m_cache_config);
|
||||
m_cache_config.clear();
|
||||
|
||||
update_dirty();
|
||||
was_applied = true;
|
||||
}
|
||||
|
||||
if (was_applied)
|
||||
update_dirty();
|
||||
}
|
||||
|
||||
|
||||
@ -3322,10 +3330,6 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
|
||||
m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL };
|
||||
}
|
||||
|
||||
// check and apply extruders count for printer preset
|
||||
if (m_type == Preset::TYPE_PRINTER)
|
||||
static_cast<TabPrinter*>(this)->apply_extruder_cnt_from_cache();
|
||||
|
||||
// check if there is something in the cache to move to the new selected preset
|
||||
apply_config_from_cache();
|
||||
|
||||
@ -3862,15 +3866,17 @@ void TabPrinter::cache_extruder_cnt()
|
||||
m_cache_extruder_count = m_extruders_count;
|
||||
}
|
||||
|
||||
void TabPrinter::apply_extruder_cnt_from_cache()
|
||||
bool TabPrinter::apply_extruder_cnt_from_cache()
|
||||
{
|
||||
if (m_presets->get_edited_preset().printer_technology() == ptSLA)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (m_cache_extruder_count > 0) {
|
||||
m_presets->get_edited_preset().set_num_extruders(m_cache_extruder_count);
|
||||
m_cache_extruder_count = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Tab::validate_custom_gcodes()
|
||||
|
@ -469,7 +469,7 @@ public:
|
||||
|
||||
wxSizer* create_bed_shape_widget(wxWindow* parent);
|
||||
void cache_extruder_cnt();
|
||||
void apply_extruder_cnt_from_cache();
|
||||
bool apply_extruder_cnt_from_cache();
|
||||
};
|
||||
|
||||
class TabSLAMaterial : public Tab
|
||||
|
@ -727,14 +727,21 @@ void DiffViewCtrl::item_value_changed(wxDataViewEvent& event)
|
||||
m_empty_selection = selected_options().empty();
|
||||
}
|
||||
|
||||
std::vector<std::string> DiffViewCtrl::unselected_options(Preset::Type type)
|
||||
bool DiffViewCtrl::has_unselected_options()
|
||||
{
|
||||
for (auto item : m_items_map)
|
||||
if (!model->IsEnabledItem(item.first))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> DiffViewCtrl::options(Preset::Type type, bool selected)
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
|
||||
for (auto item : m_items_map) {
|
||||
if (item.second.opt_key == "extruders_count")
|
||||
continue;
|
||||
if (item.second.type == type && !model->IsEnabledItem(item.first))
|
||||
if (item.second.type == type && model->IsEnabledItem(item.first) == selected)
|
||||
ret.emplace_back(get_pure_opt_key(item.second.opt_key));
|
||||
}
|
||||
|
||||
@ -757,20 +764,24 @@ std::vector<std::string> DiffViewCtrl::selected_options()
|
||||
// UnsavedChangesDialog
|
||||
//------------------------------------------
|
||||
|
||||
UnsavedChangesDialog::UnsavedChangesDialog(const wxString& header, const wxString& caption/* = wxString()*/)
|
||||
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, (caption.IsEmpty() ? _L("PrusaSlicer is closing") : caption) + ": " + _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
{
|
||||
m_app_config_key = "default_action_on_close_application";
|
||||
static std::string none{"none"};
|
||||
|
||||
UnsavedChangesDialog::UnsavedChangesDialog(const wxString& caption, const wxString& header,
|
||||
const std::string& app_config_key, int act_buttons)
|
||||
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, caption + ": " + _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
|
||||
m_app_config_key(app_config_key),
|
||||
m_buttons(act_buttons)
|
||||
{
|
||||
build(Preset::TYPE_INVALID, nullptr, "", header);
|
||||
|
||||
const std::string& def_action = wxGetApp().app_config->get(m_app_config_key);
|
||||
if (def_action == "none")
|
||||
const std::string& def_action = m_app_config_key.empty() ? none : wxGetApp().app_config->get(m_app_config_key);
|
||||
if (def_action == none)
|
||||
this->CenterOnScreen();
|
||||
else {
|
||||
m_exit_action = def_action == ActSave ? Action::Save : Action::Discard;
|
||||
if (m_exit_action == Action::Save)
|
||||
save(nullptr);
|
||||
m_exit_action = def_action == ActTransfer ? Action::Transfer :
|
||||
def_action == ActSave ? Action::Save : Action::Discard;
|
||||
if (m_exit_action != Action::Discard)
|
||||
save(nullptr, m_exit_action == Action::Save);
|
||||
}
|
||||
}
|
||||
|
||||
@ -782,7 +793,7 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, PresetCollection*
|
||||
build(type, dependent_presets, new_selected_preset);
|
||||
|
||||
const std::string& def_action = wxGetApp().app_config->get(m_app_config_key);
|
||||
if (def_action == "none") {
|
||||
if (def_action == none) {
|
||||
if (wxGetApp().mainframe->is_dlg_layout() && wxGetApp().mainframe->m_settings_dialog.HasFocus())
|
||||
this->SetPosition(wxGetApp().mainframe->m_settings_dialog.GetPosition());
|
||||
this->CenterOnScreen();
|
||||
@ -833,7 +844,8 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_
|
||||
|
||||
(*btn)->Bind(wxEVT_BUTTON, [this, close_act, dependent_presets](wxEvent&) {
|
||||
update_config(close_act);
|
||||
if (close_act == Action::Save && !save(dependent_presets))
|
||||
bool save_names_and_types = close_act == Action::Save || (close_act == Action::Transfer && ActionButtons::KEEP & m_buttons);
|
||||
if (save_names_and_types && !save(dependent_presets, close_act == Action::Save))
|
||||
return;
|
||||
close(close_act);
|
||||
});
|
||||
@ -842,13 +854,26 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_
|
||||
(*btn)->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& e) { show_info_line(Action::Undef); e.Skip(); });
|
||||
};
|
||||
|
||||
const PresetCollection* switched_presets = type == Preset::TYPE_INVALID ? nullptr : wxGetApp().get_tab(type)->get_presets();
|
||||
if (dependent_presets && switched_presets && (type == dependent_presets->type() ?
|
||||
dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology() :
|
||||
switched_presets->get_edited_preset().printer_technology() == switched_presets->find_preset(new_selected_preset)->printer_technology()))
|
||||
add_btn(&m_transfer_btn, m_move_btn_id, "paste_menu", Action::Transfer, _L("Transfer"));
|
||||
add_btn(&m_discard_btn, m_continue_btn_id, dependent_presets ? "switch_presets" : "exit", Action::Discard, _L("Discard"), false);
|
||||
add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, _L("Save"));
|
||||
// "Transfer" / "Keep" button
|
||||
if (ActionButtons::TRANSFER & m_buttons) {
|
||||
const PresetCollection* switched_presets = type == Preset::TYPE_INVALID ? nullptr : wxGetApp().get_tab(type)->get_presets();
|
||||
if (dependent_presets && switched_presets && (type == dependent_presets->type() ?
|
||||
dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology() :
|
||||
switched_presets->get_edited_preset().printer_technology() == switched_presets->find_preset(new_selected_preset)->printer_technology()))
|
||||
add_btn(&m_transfer_btn, m_move_btn_id, "paste_menu", Action::Transfer, switched_presets->get_edited_preset().name == new_selected_preset ? _L("Keep") : _L("Transfer"));
|
||||
}
|
||||
if (!m_transfer_btn && (ActionButtons::KEEP & m_buttons))
|
||||
add_btn(&m_transfer_btn, m_move_btn_id, "paste_menu", Action::Transfer, _L("Keep"));
|
||||
|
||||
{ // "Don't save" / "Discard" button
|
||||
std::string btn_icon = (ActionButtons::DONT_SAVE & m_buttons) ? "" : (dependent_presets || (ActionButtons::KEEP & m_buttons)) ? "switch_presets" : "exit";
|
||||
wxString btn_label = (ActionButtons::DONT_SAVE & m_buttons) ? _L("Don't save") : _L("Discard");
|
||||
add_btn(&m_discard_btn, m_continue_btn_id, btn_icon, Action::Discard, btn_label, false);
|
||||
}
|
||||
|
||||
// "Save" button
|
||||
if (ActionButtons::SAVE & m_buttons)
|
||||
add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, _L("Save"));
|
||||
|
||||
ScalableButton* cancel_btn = new ScalableButton(this, wxID_CANCEL, "cross", _L("Cancel"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true, 24);
|
||||
buttons->Add(cancel_btn, 1, wxLEFT|wxRIGHT, 5);
|
||||
@ -859,34 +884,42 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_
|
||||
m_info_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold());
|
||||
m_info_line->Hide();
|
||||
|
||||
m_remember_choice = new wxCheckBox(this, wxID_ANY, _L("Remember my choice"));
|
||||
m_remember_choice->SetValue(wxGetApp().app_config->get(m_app_config_key) != "none");
|
||||
m_remember_choice->Bind(wxEVT_CHECKBOX, [type, this](wxCommandEvent& evt)
|
||||
{
|
||||
if (!evt.IsChecked())
|
||||
return;
|
||||
wxString preferences_item = type == Preset::TYPE_INVALID ? _L("Ask for unsaved changes when closing application") :
|
||||
_L("Ask for unsaved changes when selecting new preset");
|
||||
wxString msg =
|
||||
_L("PrusaSlicer will remember your action.") + "\n\n" +
|
||||
(type == Preset::TYPE_INVALID ?
|
||||
_L("You will not be asked about the unsaved changes the next time you close PrusaSlicer.") :
|
||||
_L("You will not be asked about the unsaved changes the next time you switch a preset.")) + "\n\n" +
|
||||
format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto be asked about unsaved changes again."), preferences_item);
|
||||
if (!m_app_config_key.empty()) {
|
||||
m_remember_choice = new wxCheckBox(this, wxID_ANY, _L("Remember my choice"));
|
||||
m_remember_choice->SetValue(wxGetApp().app_config->get(m_app_config_key) != none);
|
||||
m_remember_choice->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& evt)
|
||||
{
|
||||
if (!evt.IsChecked())
|
||||
return;
|
||||
wxString preferences_item = m_app_config_key == "default_action_on_new_project" ? _L("Ask for unsaved changes when creating new project") :
|
||||
m_app_config_key == "default_action_on_select_preset" ? _L("Ask for unsaved changes when selecting new preset") :
|
||||
_L("Ask for unsaved changes when ??closing application??") ;
|
||||
wxString action = m_app_config_key == "default_action_on_new_project" ? _L("You will not be asked about the unsaved changes the next time you create new project") :
|
||||
m_app_config_key == "default_action_on_select_preset" ? _L("You will not be asked about the unsaved changes the next time you switch a preset") :
|
||||
_L("You will not be asked about the unsaved changes the next time you: \n"
|
||||
"- close the application,\n"
|
||||
"- load project,\n"
|
||||
"- process Undo / Redo with change of print technologie,\n"
|
||||
"- take/load snapshot,\n"
|
||||
"- load config file/bundle,\n"
|
||||
"- export config_bundle") ;
|
||||
wxString msg = _L("PrusaSlicer will remember your action.") + "\n\n" + action + "\n\n" +
|
||||
format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto be asked about unsaved changes again."), preferences_item);
|
||||
|
||||
//wxMessageDialog dialog(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
|
||||
MessageDialog dialog(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
|
||||
if (dialog.ShowModal() == wxID_CANCEL)
|
||||
m_remember_choice->SetValue(false);
|
||||
});
|
||||
MessageDialog dialog(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
|
||||
if (dialog.ShowModal() == wxID_CANCEL)
|
||||
m_remember_choice->SetValue(false);
|
||||
});
|
||||
}
|
||||
|
||||
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
topSizer->Add(m_action_line,0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
|
||||
topSizer->Add(m_tree, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
|
||||
topSizer->Add(m_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2*border);
|
||||
topSizer->Add(buttons, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
|
||||
topSizer->Add(m_remember_choice, 0, wxEXPAND | wxALL, border);
|
||||
topSizer->Add(buttons, 0, wxEXPAND | wxALL, border);
|
||||
if (m_remember_choice)
|
||||
topSizer->Add(m_remember_choice, 0, wxEXPAND | wxLEFT | wxBOTTOM | wxRIGHT, border);
|
||||
|
||||
update(type, dependent_presets, new_selected_preset, header);
|
||||
|
||||
@ -905,10 +938,12 @@ void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name
|
||||
if (action == Action::Undef)
|
||||
text = _L("Some fields are too long to fit. Right mouse click reveals the full text.");
|
||||
else if (action == Action::Discard)
|
||||
text = _L("All settings changes will be discarded.");
|
||||
text = ActionButtons::DONT_SAVE & m_buttons ? _L("All settings changes will not be saved") :_L("All settings changes will be discarded.");
|
||||
else {
|
||||
if (preset_name.empty())
|
||||
text = action == Action::Save ? _L("Save the selected options.") : _L("Transfer the selected settings to the newly selected preset.");
|
||||
text = action == Action::Save ? _L("Save the selected options.") :
|
||||
ActionButtons::KEEP & m_buttons ? _L("Keep the selected settings.") :
|
||||
_L("Transfer the selected settings to the newly selected preset.");
|
||||
else
|
||||
text = format_wxstr(
|
||||
action == Action::Save ?
|
||||
@ -927,7 +962,7 @@ void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name
|
||||
|
||||
void UnsavedChangesDialog::update_config(Action action)
|
||||
{
|
||||
if (!m_remember_choice->GetValue())
|
||||
if (!m_remember_choice || !m_remember_choice->GetValue())
|
||||
return;
|
||||
|
||||
std::string act = action == Action::Transfer ? ActTransfer :
|
||||
@ -941,7 +976,7 @@ void UnsavedChangesDialog::close(Action action)
|
||||
this->EndModal(wxID_CLOSE);
|
||||
}
|
||||
|
||||
bool UnsavedChangesDialog::save(PresetCollection* dependent_presets)
|
||||
bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_save_preset_dialog/* = true*/)
|
||||
{
|
||||
names_and_types.clear();
|
||||
|
||||
@ -979,7 +1014,7 @@ bool UnsavedChangesDialog::save(PresetCollection* dependent_presets)
|
||||
}
|
||||
|
||||
|
||||
if (!types_for_save.empty()) {
|
||||
if (show_save_preset_dialog && !types_for_save.empty()) {
|
||||
SavePresetDialog save_dlg(this, types_for_save);
|
||||
if (save_dlg.ShowModal() != wxID_OK) {
|
||||
m_exit_action = Action::Discard;
|
||||
@ -1164,16 +1199,24 @@ void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent
|
||||
PresetCollection* presets = dependent_presets;
|
||||
|
||||
// activate buttons and labels
|
||||
m_save_btn ->Bind(wxEVT_ENTER_WINDOW, [this, presets] (wxMouseEvent& e) { show_info_line(Action::Save, presets ? presets->get_selected_preset().name : ""); e.Skip(); });
|
||||
if (m_save_btn)
|
||||
m_save_btn ->Bind(wxEVT_ENTER_WINDOW, [this, presets] (wxMouseEvent& e) { show_info_line(Action::Save, presets ? presets->get_selected_preset().name : ""); e.Skip(); });
|
||||
if (m_transfer_btn) {
|
||||
bool is_empty_name = type != dependent_presets->type();
|
||||
m_transfer_btn ->Bind(wxEVT_ENTER_WINDOW, [this, new_selected_preset, is_empty_name] (wxMouseEvent& e) { show_info_line(Action::Transfer, is_empty_name ? "" : new_selected_preset); e.Skip(); });
|
||||
bool is_empty_name = dependent_presets && type != dependent_presets->type();
|
||||
m_transfer_btn->Bind(wxEVT_ENTER_WINDOW, [this, new_selected_preset, is_empty_name](wxMouseEvent& e) { show_info_line(Action::Transfer, is_empty_name ? "" : new_selected_preset); e.Skip(); });
|
||||
}
|
||||
m_discard_btn ->Bind(wxEVT_ENTER_WINDOW, [this] (wxMouseEvent& e) { show_info_line(Action::Discard); e.Skip(); });
|
||||
|
||||
if (m_discard_btn)
|
||||
m_discard_btn ->Bind(wxEVT_ENTER_WINDOW, [this] (wxMouseEvent& e) { show_info_line(Action::Discard); e.Skip(); });
|
||||
|
||||
if (type == Preset::TYPE_INVALID) {
|
||||
m_action_line->SetLabel(header + "\n" + _L("The following presets were modified:"));
|
||||
PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
|
||||
int presets_cnt = 0;
|
||||
for (Tab* tab : wxGetApp().tabs_list)
|
||||
if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty())
|
||||
presets_cnt++;
|
||||
m_action_line->SetLabel((header.IsEmpty() ? "" : header + "\n\n") + //_L("The following presets were modified:"));
|
||||
+ _L_PLURAL("The following preset was modified",
|
||||
"The following presets were modified", presets_cnt));
|
||||
}
|
||||
else {
|
||||
wxString action_msg;
|
||||
|
@ -220,8 +220,9 @@ public:
|
||||
void context_menu(wxDataViewEvent& event);
|
||||
void item_value_changed(wxDataViewEvent& event);
|
||||
void set_em_unit(int em) { m_em_unit = em; }
|
||||
bool has_unselected_options();
|
||||
|
||||
std::vector<std::string> unselected_options(Preset::Type type);
|
||||
std::vector<std::string> options(Preset::Type type, bool selected);
|
||||
std::vector<std::string> selected_options();
|
||||
};
|
||||
|
||||
@ -261,10 +262,22 @@ class UnsavedChangesDialog : public DPIDialog
|
||||
Action m_exit_action {Action::Undef};
|
||||
// preset names which are modified in SavePresetDialog and related types
|
||||
std::vector<std::pair<std::string, Preset::Type>> names_and_types;
|
||||
// additional action buttons used in dialog
|
||||
int m_buttons { ActionButtons::TRANSFER | ActionButtons::SAVE };
|
||||
|
||||
public:
|
||||
UnsavedChangesDialog(const wxString& header, const wxString& caption = wxString());
|
||||
// Discard and Cancel buttons are always but next buttons are optional
|
||||
enum ActionButtons {
|
||||
TRANSFER = 1,
|
||||
KEEP = 2,
|
||||
SAVE = 4,
|
||||
DONT_SAVE = 8,
|
||||
};
|
||||
|
||||
// show unsaved changes when preset is switching
|
||||
UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset);
|
||||
// show unsaved changes for all another cases
|
||||
UnsavedChangesDialog(const wxString& caption, const wxString& header, const std::string& app_config_key, int act_buttons);
|
||||
~UnsavedChangesDialog() {}
|
||||
|
||||
void build(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header = "");
|
||||
@ -273,7 +286,8 @@ public:
|
||||
void show_info_line(Action action, std::string preset_name = "");
|
||||
void update_config(Action action);
|
||||
void close(Action action);
|
||||
bool save(PresetCollection* dependent_presets);
|
||||
// save information about saved presets and their types to names_and_types and show SavePresetDialog to set the names for new presets
|
||||
bool save(PresetCollection* dependent_presets, bool show_save_preset_dialog = true);
|
||||
|
||||
bool save_preset() const { return m_exit_action == Action::Save; }
|
||||
bool transfer_changes() const { return m_exit_action == Action::Transfer; }
|
||||
@ -284,8 +298,10 @@ public:
|
||||
// short version of the previous function, for the case, when just one preset is modified
|
||||
std::string get_preset_name() { return names_and_types[0].first; }
|
||||
|
||||
std::vector<std::string> get_unselected_options(Preset::Type type) { return m_tree->unselected_options(type); }
|
||||
std::vector<std::string> get_unselected_options(Preset::Type type) { return m_tree->options(type, false); }
|
||||
std::vector<std::string> get_selected_options (Preset::Type type) { return m_tree->options(type, true); }
|
||||
std::vector<std::string> get_selected_options() { return m_tree->selected_options(); }
|
||||
bool has_unselected_options() { return m_tree->has_unselected_options(); }
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
|
@ -672,10 +672,9 @@ void ModeButton::focus_button(const bool focus)
|
||||
Slic3r::GUI::wxGetApp().normal_font();
|
||||
|
||||
SetFont(new_font);
|
||||
//#ifdef _WIN32
|
||||
// GetParent()->Refresh();
|
||||
//#else
|
||||
#ifndef _WIN32
|
||||
#ifdef _WIN32
|
||||
GetParent()->Refresh(); // force redraw a background of the selected mode button
|
||||
#else
|
||||
SetForegroundColour(wxSystemSettings::GetColour(focus ? wxSYS_COLOUR_BTNTEXT :
|
||||
#if defined (__linux__) && defined (__WXGTK3__)
|
||||
wxSYS_COLOUR_GRAYTEXT
|
||||
|
@ -402,6 +402,7 @@ bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxPro
|
||||
}
|
||||
for (size_t i = 0; i < volumes.size(); ++ i) {
|
||||
volumes[i]->set_mesh(std::move(meshes_repaired[i]));
|
||||
volumes[i]->calculate_convex_hull();
|
||||
volumes[i]->set_new_unique_id();
|
||||
}
|
||||
model_object.invalidate_bounding_box();
|
||||
|
@ -720,8 +720,13 @@ void PresetUpdater::slic3r_update_notify()
|
||||
}
|
||||
}
|
||||
|
||||
static void reload_configs_update_gui()
|
||||
static bool reload_configs_update_gui()
|
||||
{
|
||||
wxString header = _L("Configuration Updates causes a lost of preset modification.\n"
|
||||
"So, check unsaved changes and save them if necessary.");
|
||||
if (!GUI::wxGetApp().check_and_save_current_preset_changes(_L("Updater is processing"), header, false ))
|
||||
return false;
|
||||
|
||||
// Reload global configuration
|
||||
auto* app_config = GUI::wxGetApp().app_config;
|
||||
// System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions
|
||||
@ -730,7 +735,8 @@ static void reload_configs_update_gui()
|
||||
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem);
|
||||
GUI::wxGetApp().load_current_presets();
|
||||
GUI::wxGetApp().plater()->set_bed_shape();
|
||||
GUI::wxGetApp().update_wizard_from_config();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, UpdateParams params) const
|
||||
@ -803,9 +809,9 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
||||
const auto res = dlg.ShowModal();
|
||||
if (res == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(info) << "User wants to update...";
|
||||
if (! p->perform_updates(std::move(updates)))
|
||||
if (! p->perform_updates(std::move(updates)) ||
|
||||
! reload_configs_update_gui())
|
||||
return R_INCOMPAT_EXIT;
|
||||
reload_configs_update_gui();
|
||||
return R_UPDATE_INSTALLED;
|
||||
}
|
||||
else {
|
||||
@ -833,9 +839,9 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
||||
const auto res = dlg.ShowModal();
|
||||
if (res == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
|
||||
if (! p->perform_updates(std::move(updates)))
|
||||
if (! p->perform_updates(std::move(updates)) ||
|
||||
! reload_configs_update_gui())
|
||||
return R_ALL_CANCELED;
|
||||
reload_configs_update_gui();
|
||||
return R_UPDATE_INSTALLED;
|
||||
}
|
||||
else {
|
||||
@ -886,8 +892,8 @@ void PresetUpdater::on_update_notification_confirm()
|
||||
const auto res = dlg.ShowModal();
|
||||
if (res == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
|
||||
if (p->perform_updates(std::move(p->waiting_updates))) {
|
||||
reload_configs_update_gui();
|
||||
if (p->perform_updates(std::move(p->waiting_updates)) &&
|
||||
reload_configs_update_gui()) {
|
||||
p->has_waiting_updates = false;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -17,12 +17,13 @@
|
||||
using namespace Slic3r;
|
||||
using namespace std;
|
||||
|
||||
static inline TriangleMesh make_cube() { return make_cube(20., 20, 20); }
|
||||
|
||||
SCENARIO( "TriangleMesh: Basic mesh statistics") {
|
||||
GIVEN( "A 20mm cube, built from constexpr std::array" ) {
|
||||
std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
|
||||
std::vector<Vec3f> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
|
||||
std::vector<Vec3i> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
TriangleMesh cube(vertices, facets);
|
||||
|
||||
THEN( "Volume is appropriate for 20mm square cube.") {
|
||||
REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
|
||||
@ -68,64 +69,11 @@ SCENARIO( "TriangleMesh: Basic mesh statistics") {
|
||||
}
|
||||
|
||||
}
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
|
||||
const std::vector<Vec3i> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
|
||||
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
|
||||
THEN( "Volume is appropriate for 20mm square cube.") {
|
||||
REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
|
||||
THEN( "Vertices array matches input.") {
|
||||
for (size_t i = 0U; i < cube.its.vertices.size(); i++) {
|
||||
REQUIRE(cube.its.vertices.at(i) == vertices.at(i).cast<float>());
|
||||
}
|
||||
for (size_t i = 0U; i < vertices.size(); i++) {
|
||||
REQUIRE(vertices.at(i).cast<float>() == cube.its.vertices.at(i));
|
||||
}
|
||||
}
|
||||
THEN( "Vertex count matches vertex array size.") {
|
||||
REQUIRE(cube.facets_count() == facets.size());
|
||||
}
|
||||
|
||||
THEN( "Facet array matches input.") {
|
||||
for (size_t i = 0U; i < cube.its.indices.size(); i++) {
|
||||
REQUIRE(cube.its.indices.at(i) == facets.at(i));
|
||||
}
|
||||
|
||||
for (size_t i = 0U; i < facets.size(); i++) {
|
||||
REQUIRE(facets.at(i) == cube.its.indices.at(i));
|
||||
}
|
||||
}
|
||||
THEN( "Facet count matches facet array size.") {
|
||||
REQUIRE(cube.facets_count() == facets.size());
|
||||
}
|
||||
|
||||
#if 0
|
||||
THEN( "Number of normals is equal to the number of facets.") {
|
||||
REQUIRE(cube.normals().size() == facets.size());
|
||||
}
|
||||
#endif
|
||||
|
||||
THEN( "center() returns the center of the object.") {
|
||||
REQUIRE(cube.center() == Vec3d(10.0,10.0,10.0));
|
||||
}
|
||||
|
||||
THEN( "Size of cube is (20,20,20)") {
|
||||
REQUIRE(cube.size() == Vec3d(20,20,20));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") {
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
|
||||
const std::vector<Vec3i> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
auto cube = make_cube();
|
||||
|
||||
WHEN( "The cube is scaled 200% uniformly") {
|
||||
cube.scale(2.0);
|
||||
@ -134,7 +82,7 @@ SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") {
|
||||
}
|
||||
}
|
||||
WHEN( "The resulting cube is scaled 200% in the X direction") {
|
||||
cube.scale(Vec3d(2.0, 1, 1));
|
||||
cube.scale(Vec3f(2.0, 1, 1));
|
||||
THEN( "The volume is doubled.") {
|
||||
REQUIRE(abs(cube.volume() - 2*20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
@ -144,7 +92,7 @@ SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") {
|
||||
}
|
||||
|
||||
WHEN( "The cube is scaled 25% in the X direction") {
|
||||
cube.scale(Vec3d(0.25, 1, 1));
|
||||
cube.scale(Vec3f(0.25, 1, 1));
|
||||
THEN( "The volume is 25% of the previous volume.") {
|
||||
REQUIRE(abs(cube.volume() - 0.25*20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
@ -177,7 +125,10 @@ SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") {
|
||||
cube.translate(5.0, 10.0, 0.0);
|
||||
cube.align_to_origin();
|
||||
THEN( "The third vertex is located at 0,0,0") {
|
||||
REQUIRE(cube.its.vertices.at(2) == Vec3f(0.0, 0.0, 0.0));
|
||||
REQUIRE(cube.its.vertices.at(2) == Vec3f::Zero());
|
||||
}
|
||||
THEN( "Size is OK") {
|
||||
REQUIRE(cube.stats().size == Vec3f(20.f, 20.f, 20.f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -185,11 +136,8 @@ SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") {
|
||||
|
||||
SCENARIO( "TriangleMesh: slice behavior.") {
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
|
||||
const std::vector<Vec3i> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
|
||||
auto cube = make_cube();
|
||||
|
||||
WHEN("Cube is sliced with z = [0+EPSILON,2,4,8,6,8,10,12,14,16,18,20]") {
|
||||
std::vector<double> z { 0+EPSILON,2,4,8,6,8,10,12,14,16,18,20 };
|
||||
std::vector<ExPolygons> result = cube.slice(z);
|
||||
@ -206,12 +154,12 @@ SCENARIO( "TriangleMesh: slice behavior.") {
|
||||
}
|
||||
}
|
||||
GIVEN( "A STL with an irregular shape.") {
|
||||
const std::vector<Vec3d> vertices {{0,0,0},{0,0,20},{0,5,0},{0,5,20},{50,0,0},{50,0,20},{15,5,0},{35,5,0},{15,20,0},{50,5,0},{35,20,0},{15,5,10},{50,5,20},{35,5,10},{35,20,10},{15,20,10}};
|
||||
const std::vector<Vec3f> vertices {{0,0,0},{0,0,20},{0,5,0},{0,5,20},{50,0,0},{50,0,20},{15,5,0},{35,5,0},{15,20,0},{50,5,0},{35,20,0},{15,5,10},{50,5,20},{35,5,10},{35,20,10},{15,20,10}};
|
||||
const std::vector<Vec3i> facets {{0,1,2},{2,1,3},{1,0,4},{5,1,4},{0,2,4},{4,2,6},{7,6,8},{4,6,7},{9,4,7},{7,8,10},{2,3,6},{11,3,12},{7,12,9},{13,12,7},{6,3,11},{11,12,13},{3,1,5},{12,3,5},{5,4,9},{12,5,9},{13,7,10},{14,13,10},{8,15,10},{10,15,14},{6,11,8},{8,11,15},{15,11,13},{14,15,13}};
|
||||
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
auto cube = make_cube();
|
||||
WHEN(" a top tangent plane is sliced") {
|
||||
// At Z = 10 we have a top horizontal surface.
|
||||
std::vector<ExPolygons> slices = cube.slice({5.0, 10.0});
|
||||
THEN( "its area is included") {
|
||||
REQUIRE(slices.at(0).at(0).area() > 0);
|
||||
@ -240,9 +188,6 @@ SCENARIO( "make_xxx functions produce meshes.") {
|
||||
THEN("The mesh volume is 20*20*20") {
|
||||
REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
THEN("The resulting mesh is in the repaired state.") {
|
||||
REQUIRE(cube.repaired == true);
|
||||
}
|
||||
THEN("There are 12 facets.") {
|
||||
REQUIRE(cube.its.indices.size() == 12);
|
||||
}
|
||||
@ -266,9 +211,6 @@ SCENARIO( "make_xxx functions produce meshes.") {
|
||||
THEN("Resulting mesh has 2*PI/angle * 4 facets") {
|
||||
REQUIRE(cyl.its.indices.size() == (2*PI/angle)*4);
|
||||
}
|
||||
THEN("The resulting mesh is in the repaired state.") {
|
||||
REQUIRE(cyl.repaired == true);
|
||||
}
|
||||
THEN( "The mesh volume is approximately 10pi * 10^2") {
|
||||
REQUIRE(abs(cyl.volume() - (10.0 * M_PI * std::pow(10,2))) < 1);
|
||||
}
|
||||
@ -283,9 +225,6 @@ SCENARIO( "make_xxx functions produce meshes.") {
|
||||
REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return is_approx(t, Vec3f(0.f, 0.f, 10.f)); } ) == 1);
|
||||
REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return is_approx(t, Vec3f(0.f, 0.f, -10.f)); } ) == 1);
|
||||
}
|
||||
THEN("The resulting mesh is in the repaired state.") {
|
||||
REQUIRE(sph.repaired == true);
|
||||
}
|
||||
THEN( "The mesh volume is approximately 4/3 * pi * 10^3") {
|
||||
REQUIRE(abs(sph.volume() - (4.0/3.0 * M_PI * std::pow(10,3))) < 1); // 1% tolerance?
|
||||
}
|
||||
@ -295,32 +234,25 @@ SCENARIO( "make_xxx functions produce meshes.") {
|
||||
|
||||
SCENARIO( "TriangleMesh: split functionality.") {
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
|
||||
const std::vector<Vec3i> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
|
||||
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
auto cube = make_cube();
|
||||
WHEN( "The mesh is split into its component parts.") {
|
||||
std::vector<TriangleMesh*> meshes = cube.split();
|
||||
std::vector<TriangleMesh> meshes = cube.split();
|
||||
THEN(" The bounding box statistics are propagated to the split copies") {
|
||||
REQUIRE(meshes.size() == 1);
|
||||
REQUIRE((meshes.at(0)->bounding_box() == cube.bounding_box()));
|
||||
REQUIRE((meshes.front().bounding_box() == cube.bounding_box()));
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN( "Two 20mm cubes, each with one corner on the origin, merged into a single TriangleMesh") {
|
||||
const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
|
||||
const std::vector<Vec3i> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
|
||||
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
TriangleMesh cube2(vertices, facets);
|
||||
cube2.repair();
|
||||
auto cube = make_cube();
|
||||
TriangleMesh cube2(cube);
|
||||
|
||||
cube.merge(cube2);
|
||||
cube.repair();
|
||||
WHEN( "The combined mesh is split") {
|
||||
std::vector<TriangleMesh*> meshes = cube.split();
|
||||
THEN( "Number of faces is 2x the source.") {
|
||||
REQUIRE(cube.facets_count() == 2 * cube2.facets_count());
|
||||
}
|
||||
std::vector<TriangleMesh> meshes = cube.split();
|
||||
THEN( "Two meshes are in the output vector.") {
|
||||
REQUIRE(meshes.size() == 2);
|
||||
}
|
||||
@ -330,17 +262,11 @@ SCENARIO( "TriangleMesh: split functionality.") {
|
||||
|
||||
SCENARIO( "TriangleMesh: Mesh merge functions") {
|
||||
GIVEN( "Two 20mm cubes, each with one corner on the origin") {
|
||||
const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
|
||||
const std::vector<Vec3i> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
|
||||
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
TriangleMesh cube2(vertices, facets);
|
||||
cube2.repair();
|
||||
auto cube = make_cube();
|
||||
TriangleMesh cube2(cube);
|
||||
|
||||
WHEN( "The two meshes are merged") {
|
||||
cube.merge(cube2);
|
||||
cube.repair();
|
||||
THEN( "There are twice as many facets in the merged mesh as the original.") {
|
||||
REQUIRE(cube.facets_count() == 2 * cube2.facets_count());
|
||||
}
|
||||
@ -350,11 +276,7 @@ SCENARIO( "TriangleMesh: Mesh merge functions") {
|
||||
|
||||
SCENARIO( "TriangleMeshSlicer: Cut behavior.") {
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
|
||||
const std::vector<Vec3i> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
|
||||
|
||||
TriangleMesh cube(vertices, facets);
|
||||
cube.repair();
|
||||
auto cube = make_cube();
|
||||
WHEN( "Object is cut at the bottom") {
|
||||
indexed_triangle_set upper {};
|
||||
indexed_triangle_set lower {};
|
||||
@ -384,7 +306,6 @@ TEST_CASE("Regression test for issue #4486 - files take forever to slice") {
|
||||
TriangleMesh mesh;
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
mesh.ReadSTLFile(std::string(testfile_dir) + "test_trianglemesh/4486/100_000.stl");
|
||||
mesh.repair();
|
||||
|
||||
config.set("layer_height", 500);
|
||||
config.set("first_layer_height", 250);
|
||||
@ -412,7 +333,6 @@ TEST_CASE("Profile test for issue #4486 - files take forever to slice") {
|
||||
TriangleMesh mesh;
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
mesh.ReadSTLFile(std::string(testfile_dir) + "test_trianglemesh/4486/10_000.stl");
|
||||
mesh.repair();
|
||||
|
||||
config.set("layer_height", 500);
|
||||
config.set("first_layer_height", 250);
|
||||
|
@ -65,10 +65,7 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") {
|
||||
|
||||
// compare meshes
|
||||
TriangleMesh src_mesh = src_model.mesh();
|
||||
src_mesh.repair();
|
||||
|
||||
TriangleMesh dst_mesh = dst_model.mesh();
|
||||
dst_mesh.repair();
|
||||
|
||||
bool res = src_mesh.its.vertices.size() == dst_mesh.its.vertices.size();
|
||||
if (res) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user