GCodeViewer -> Export of extrude toolpaths to obj files
This commit is contained in:
parent
feb4857cf8
commit
0b1086f390
6 changed files with 300 additions and 10 deletions
src/slic3r/GUI
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include <GL/glew.h>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
|
@ -487,6 +488,284 @@ void GCodeViewer::set_layers_z_range(const std::array<double, 2>& layers_z_range
|
|||
wxGetApp().plater()->update_preview_moves_slider();
|
||||
}
|
||||
|
||||
void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
|
||||
{
|
||||
if (filename == nullptr)
|
||||
return;
|
||||
|
||||
if (!has_data())
|
||||
return;
|
||||
|
||||
wxBusyCursor busy;
|
||||
|
||||
// the data needed is contained into the Extrude TBuffer
|
||||
const TBuffer& buffer = m_buffers[buffer_id(GCodeProcessor::EMoveType::Extrude)];
|
||||
if (buffer.vertices.id == 0 || buffer.indices.id == 0)
|
||||
return;
|
||||
|
||||
// collect color information to generate materials
|
||||
std::vector<Color> colors;
|
||||
for (const RenderPath& path : buffer.render_paths) {
|
||||
colors.push_back(path.color);
|
||||
}
|
||||
|
||||
// save materials file
|
||||
boost::filesystem::path mat_filename(filename);
|
||||
mat_filename.replace_extension("mtl");
|
||||
FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w");
|
||||
if (fp == nullptr) {
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing";
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(fp, "# G-Code Toolpaths Materials\n");
|
||||
fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID);
|
||||
|
||||
unsigned int colors_count = 1;
|
||||
for (const Color& color : colors)
|
||||
{
|
||||
fprintf(fp, "\nnewmtl material_%d\n", colors_count++);
|
||||
fprintf(fp, "Ka 1 1 1\n");
|
||||
fprintf(fp, "Kd %f %f %f\n", color[0], color[1], color[2]);
|
||||
fprintf(fp, "Ks 0 0 0\n");
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
// save geometry file
|
||||
fp = boost::nowide::fopen(filename, "w");
|
||||
if (fp == nullptr) {
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << filename << " for writing";
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(fp, "# G-Code Toolpaths\n");
|
||||
fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID);
|
||||
fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str());
|
||||
|
||||
// get vertices data from vertex buffer on gpu
|
||||
size_t floats_per_vertex = buffer.vertices.vertex_size_floats();
|
||||
std::vector<float> vertices = std::vector<float>(buffer.vertices.count * floats_per_vertex);
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id));
|
||||
glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer.vertices.data_size_bytes(), vertices.data()));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
|
||||
auto get_vertex = [&vertices, floats_per_vertex](size_t id) {
|
||||
// extract vertex from vector of floats
|
||||
size_t base_id = id * floats_per_vertex;
|
||||
return Vec3f(vertices[base_id + 0], vertices[base_id + 1], vertices[base_id + 2]);
|
||||
};
|
||||
|
||||
struct Segment
|
||||
{
|
||||
Vec3f v1;
|
||||
Vec3f v2;
|
||||
Vec3f dir;
|
||||
Vec3f right;
|
||||
Vec3f up;
|
||||
Vec3f rl_displacement;
|
||||
Vec3f tb_displacement;
|
||||
float length;
|
||||
};
|
||||
|
||||
auto generate_segment = [get_vertex](size_t start_id, float half_width, float half_height) {
|
||||
auto local_basis = [](const Vec3f& dir) {
|
||||
// calculate local basis (dir, right, up) on given segment
|
||||
std::array<Vec3f, 3> ret;
|
||||
ret[0] = dir.normalized();
|
||||
if (std::abs(ret[0][2]) < EPSILON) {
|
||||
// segment parallel to XY plane
|
||||
ret[1] = { ret[0][1], -ret[0][0], 0.0f };
|
||||
ret[2] = Vec3f::UnitZ();
|
||||
}
|
||||
else if (std::abs(std::abs(ret[0].dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) {
|
||||
// segment parallel to Z axis
|
||||
ret[1] = Vec3f::UnitX();
|
||||
ret[2] = Vec3f::UnitY();
|
||||
}
|
||||
else {
|
||||
ret[0] = dir.normalized();
|
||||
ret[1] = ret[0].cross(Vec3f::UnitZ()).normalized();
|
||||
ret[2] = ret[1].cross(ret[0]);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
Vec3f v1 = get_vertex(start_id);
|
||||
Vec3f v2 = get_vertex(start_id + 1);
|
||||
float length = (v2 - v1).norm();
|
||||
const auto&& [dir, right, up] = local_basis(v2 - v1);
|
||||
return Segment({ v1, v2, dir, right, up, half_width * right, half_height * up, length });
|
||||
};
|
||||
|
||||
size_t out_vertices_count = 0;
|
||||
|
||||
for (size_t i = 0; i < buffer.render_paths.size(); ++i) {
|
||||
// get paths segments from buffer paths
|
||||
const RenderPath& render_path = buffer.render_paths[i];
|
||||
const Path& path = buffer.paths[render_path.path_id];
|
||||
float half_width = 0.5f * path.width;
|
||||
float half_height = 0.5f * path.height;
|
||||
|
||||
// generates vertices/normals/triangles
|
||||
std::vector<Vec3f> out_vertices;
|
||||
std::vector<Vec3f> out_normals;
|
||||
using Triangle = std::array<size_t, 3>;
|
||||
std::vector<Triangle> out_triangles;
|
||||
for (size_t j = 0; j < render_path.offsets.size(); ++j) {
|
||||
unsigned int start = static_cast<unsigned int>(render_path.offsets[j] / sizeof(unsigned int));
|
||||
unsigned int end = start + render_path.sizes[j];
|
||||
|
||||
for (size_t k = start; k < end; k += 2) {
|
||||
Segment curr = generate_segment(k, half_width, half_height);
|
||||
|
||||
if (k == start) {
|
||||
// starting endpoint vertices/normals
|
||||
out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right
|
||||
out_vertices.push_back(curr.v1 + curr.tb_displacement); out_normals.push_back(curr.up); // top
|
||||
out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left
|
||||
out_vertices.push_back(curr.v1 - curr.tb_displacement); out_normals.push_back(-curr.up); // bottom
|
||||
out_vertices_count += 4;
|
||||
|
||||
// starting cap triangles
|
||||
size_t base_id = out_vertices_count - 4 + 1;
|
||||
out_triangles.push_back({ base_id + 0, base_id + 1, base_id + 2 });
|
||||
out_triangles.push_back({ base_id + 0, base_id + 2, base_id + 3 });
|
||||
}
|
||||
else {
|
||||
// for the endpoint shared by the current and the previous segments
|
||||
// we keep the top and bottom vertices of the previous vertices
|
||||
// and add new left/right vertices for the current segment
|
||||
out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right
|
||||
out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left
|
||||
out_vertices_count += 2;
|
||||
|
||||
Segment prev = generate_segment(k - 2, half_width, half_height);
|
||||
Vec3f med_dir = (prev.dir + curr.dir).normalized();
|
||||
float disp = half_width * ::tan(::acos(std::clamp(curr.dir.dot(med_dir), -1.0f, 1.0f)));
|
||||
Vec3f disp_vec = disp * prev.dir;
|
||||
|
||||
bool is_right_turn = prev.up.dot(prev.dir.cross(curr.dir)) <= 0.0f;
|
||||
if (prev.dir.dot(curr.dir) < 0.7071068f) {
|
||||
// if the angle between two consecutive segments is greater than 45 degrees
|
||||
// we add a cap in the outside corner
|
||||
// and displace the vertices in the inside corner to the same position, if possible
|
||||
if (is_right_turn) {
|
||||
// corner cap triangles (left)
|
||||
size_t base_id = out_vertices_count - 6 + 1;
|
||||
out_triangles.push_back({ base_id + 5, base_id + 2, base_id + 1 });
|
||||
out_triangles.push_back({ base_id + 5, base_id + 3, base_id + 2 });
|
||||
|
||||
// update right vertices
|
||||
if (disp < prev.length) {
|
||||
base_id = out_vertices.size() - 6;
|
||||
out_vertices[base_id + 0] -= disp_vec;
|
||||
out_vertices[base_id + 4] = out_vertices[base_id + 0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// corner cap triangles (right)
|
||||
size_t base_id = out_vertices_count - 6 + 1;
|
||||
out_triangles.push_back({ base_id + 0, base_id + 4, base_id + 1 });
|
||||
out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 4 });
|
||||
|
||||
// update left vertices
|
||||
if (disp < prev.length) {
|
||||
base_id = out_vertices.size() - 6;
|
||||
out_vertices[base_id + 2] -= disp_vec;
|
||||
out_vertices[base_id + 5] = out_vertices[base_id + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if the angle between two consecutive segments is lesser than 45 degrees
|
||||
// displace the vertices to the same position
|
||||
if (is_right_turn) {
|
||||
size_t base_id = out_vertices.size() - 6;
|
||||
// right
|
||||
out_vertices[base_id + 0] -= disp_vec;
|
||||
out_vertices[base_id + 4] = out_vertices[base_id + 0];
|
||||
// left
|
||||
out_vertices[base_id + 2] += disp_vec;
|
||||
out_vertices[base_id + 5] = out_vertices[base_id + 2];
|
||||
}
|
||||
else {
|
||||
size_t base_id = out_vertices.size() - 6;
|
||||
// right
|
||||
out_vertices[base_id + 0] += disp_vec;
|
||||
out_vertices[base_id + 4] = out_vertices[base_id + 0];
|
||||
// left
|
||||
out_vertices[base_id + 2] -= disp_vec;
|
||||
out_vertices[base_id + 5] = out_vertices[base_id + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// current second endpoint vertices/normals
|
||||
out_vertices.push_back(curr.v2 + curr.rl_displacement); out_normals.push_back(curr.right); // right
|
||||
out_vertices.push_back(curr.v2 + curr.tb_displacement); out_normals.push_back(curr.up); // top
|
||||
out_vertices.push_back(curr.v2 - curr.rl_displacement); out_normals.push_back(-curr.right); // left
|
||||
out_vertices.push_back(curr.v2 - curr.tb_displacement); out_normals.push_back(-curr.up); // bottom
|
||||
out_vertices_count += 4;
|
||||
|
||||
// sides triangles
|
||||
if (k == start) {
|
||||
size_t base_id = out_vertices_count - 8 + 1;
|
||||
out_triangles.push_back({ base_id + 0, base_id + 4, base_id + 5 });
|
||||
out_triangles.push_back({ base_id + 0, base_id + 5, base_id + 1 });
|
||||
out_triangles.push_back({ base_id + 1, base_id + 5, base_id + 6 });
|
||||
out_triangles.push_back({ base_id + 1, base_id + 6, base_id + 2 });
|
||||
out_triangles.push_back({ base_id + 2, base_id + 6, base_id + 7 });
|
||||
out_triangles.push_back({ base_id + 2, base_id + 7, base_id + 3 });
|
||||
out_triangles.push_back({ base_id + 3, base_id + 7, base_id + 4 });
|
||||
out_triangles.push_back({ base_id + 3, base_id + 4, base_id + 0 });
|
||||
}
|
||||
else {
|
||||
size_t base_id = out_vertices_count - 10 + 1;
|
||||
out_triangles.push_back({ base_id + 4, base_id + 6, base_id + 7 });
|
||||
out_triangles.push_back({ base_id + 4, base_id + 7, base_id + 1 });
|
||||
out_triangles.push_back({ base_id + 1, base_id + 7, base_id + 8 });
|
||||
out_triangles.push_back({ base_id + 1, base_id + 8, base_id + 5 });
|
||||
out_triangles.push_back({ base_id + 5, base_id + 8, base_id + 9 });
|
||||
out_triangles.push_back({ base_id + 5, base_id + 9, base_id + 3 });
|
||||
out_triangles.push_back({ base_id + 3, base_id + 9, base_id + 6 });
|
||||
out_triangles.push_back({ base_id + 3, base_id + 6, base_id + 4 });
|
||||
}
|
||||
|
||||
if (k + 2 == end) {
|
||||
// ending cap triangles
|
||||
size_t base_id = out_vertices_count - 4 + 1;
|
||||
out_triangles.push_back({ base_id + 0, base_id + 2, base_id + 1 });
|
||||
out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 2 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save to file
|
||||
fprintf(fp, "\n# vertices path %lld\n", i + 1);
|
||||
for (const Vec3f& v : out_vertices) {
|
||||
fprintf(fp, "v %g %g %g\n", v[0], v[1], v[2]);
|
||||
}
|
||||
|
||||
fprintf(fp, "\n# normals path %lld\n", i + 1);
|
||||
for (const Vec3f& n : out_normals) {
|
||||
fprintf(fp, "vn %g %g %g\n", n[0], n[1], n[2]);
|
||||
}
|
||||
|
||||
fprintf(fp, "\n# material path %lld\n", i + 1);
|
||||
fprintf(fp, "usemtl material_%lld\n", i + 1);
|
||||
|
||||
fprintf(fp, "\n# triangles path %lld\n", i + 1);
|
||||
for (const Triangle& t : out_triangles) {
|
||||
fprintf(fp, "f %lld//%lld %lld//%lld %lld//%lld\n", t[0], t[0], t[1], t[1], t[2], t[2]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// fprintf(fp, "\n#vertices count %lld\n", out_vertices_count);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void GCodeViewer::init_shaders()
|
||||
{
|
||||
unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract);
|
||||
|
@ -641,7 +920,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
|
|||
const std::vector<unsigned int>& buffer_indices = indices[i];
|
||||
buffer.indices.count = buffer_indices.size();
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
m_statistics.indices_size += SLIC3R_STDVEC_MEMSIZE(buffer_indices, unsigned int);
|
||||
m_statistics.indices_gpu_size += buffer.indices.count * sizeof(unsigned int);
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
|
||||
|
@ -876,6 +1154,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
|||
if (it == buffer->render_paths.end()) {
|
||||
it = buffer->render_paths.insert(buffer->render_paths.end(), RenderPath());
|
||||
it->color = color;
|
||||
it->path_id = id;
|
||||
}
|
||||
|
||||
unsigned int size = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1;
|
||||
|
@ -1482,12 +1761,6 @@ void GCodeViewer::render_statistics() const
|
|||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
|
||||
imgui.text(std::string("Indices CPU:"));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine(offset);
|
||||
imgui.text(std::to_string(m_statistics.indices_size) + " bytes");
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
|
||||
imgui.text(std::string("Paths CPU:"));
|
||||
ImGui::PopStyleColor();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue