Files
OrcaSlicer/src/libslic3r/NSVGUtils.cpp
T
HanifKoh 8a6377f087 Add Missing Includes Across src/libslic3r (#16068)
* Add Missing Includes Across src/libslic3r

Every libslic3r source and header now directly includes the headers declaring what it uses, rather than relying on the precompiled header or transitive includes. Generated with clang-tidy misc-include-cleaner, with libslic3r headers spelled libslic3r/... so they resolve outside the library's private include paths. MultiMaterialSegmentation.hpp, Support/SupportParameters.hpp and Format/STEP.hpp are made self-contained by hand.

* Make the libslic3r Headers Compile on Their Own

Each now includes, or forward-declares, what it uses instead of relying on what its includers happened to include first. Left out: I18N.hpp, which errors on purpose when included from GUI code, and VoxelizeCSGMesh.hpp and SLA/bicubic.h, which nothing includes and which no longer compile at all.

* Add the Includes Missing From the Hand-Fixed libslic3r Headers

clang-tidy would not edit these headers while they failed to compile on their own, so the first pass skipped them. With the headers now self-contained, a second pass adds the rest.

* Keep Windows Setup Ahead of the Added libslic3r Includes

Print.cpp and Thread.cpp open with a _WIN32 block that has to come first; without the precompiled header, Print.cpp otherwise reaches windows.h through OCCT with NONLS defined and boost/regex fails. OpenVDBUtils.cpp and SLA/SupportTreeBuilder.cpp had includes inside #ifndef NOMINMAX, which libslic3r defines on Windows, so those were skipped there. .clang-tidy also ignores the MSVC STL and UCRT internals, Boost.Multiprecision's fwd.hpp and CPython's Windows include directory.

* Re-Add libslic3r Includes After the Clipper2 2.0.1 Migration

Rebasing onto main took main's version of the files the Clipper2 migration rewrote, so their added includes are restored here, along with includes for main's new code. Clipper2's individual headers are now ignored by clang-tidy: they only build the Z variant through clipper2_z.hpp, which defines USINGZ first, so including clipper.core.h and the like directly broke ClipperZUtils.cpp.
2026-10-03 15:31:11 +08:00

557 lines
20 KiB
C++

#include "NSVGUtils.hpp"
#include <array>
#include <cassert>
#include <charconv> // to_chars
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/fstream.hpp>
#include <nanosvg/nanosvg.h>
#include <cstddef>
#include <string>
#include <memory>
#include <sstream>
#include <cstring>
#include <cmath>
#include "ClipperUtils.hpp"
#include "Emboss.hpp" // heal for shape
#include "Polygon.hpp"
#include "Polyline.hpp"
#include "EmbossShape.hpp"
#include "Point.hpp"
#include "libslic3r.h"
namespace {
using namespace Slic3r; // Polygon
// see function nsvg__lineTo(NSVGparser* p, float x, float y)
bool is_line(const float *p, float precision = 1e-4f);
// convert curve in path to lines
struct LinesPath{
Polygons polygons;
Polylines polylines; };
LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams &param);
HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param);
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param);
} // namespace
namespace Slic3r {
ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLineParams &param)
{
ExPolygonsWithIds result;
size_t shape_id = 0;
for (NSVGshape *shape_ptr = image.shapes; shape_ptr != NULL; shape_ptr = shape_ptr->next, ++shape_id) {
const NSVGshape &shape = *shape_ptr;
if (!(shape.flags & NSVG_FLAGS_VISIBLE))
continue;
bool is_fill_used = shape.fill.type != NSVG_PAINT_NONE;
bool is_stroke_used =
shape.stroke.type != NSVG_PAINT_NONE &&
shape.strokeWidth > 1e-5f;
if (!is_fill_used && !is_stroke_used)
continue;
const LinesPath lines_path = linearize_path(shape.paths, param);
if (is_fill_used) {
unsigned unique_id = static_cast<unsigned>(2 * shape_id);
HealedExPolygons expoly = fill_to_expolygons(lines_path, shape, param);
result.push_back({unique_id, expoly.expolygons, expoly.is_healed});
}
if (is_stroke_used) {
unsigned unique_id = static_cast<unsigned>(2 * shape_id + 1);
HealedExPolygons expoly = stroke_to_expolygons(lines_path, shape, param);
result.push_back({unique_id, expoly.expolygons, expoly.is_healed});
}
}
// SVG is used as centered
// Do not disturb user by settings of pivot position
center(result);
return result;
}
Polygons to_polygons(const NSVGimage &image, const NSVGLineParams &param)
{
Polygons result;
for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) {
if (!(shape->flags & NSVG_FLAGS_VISIBLE))
continue;
if (shape->fill.type == NSVG_PAINT_NONE)
continue;
const LinesPath lines_path = linearize_path(shape->paths, param);
polygons_append(result, lines_path.polygons);
// close polyline to create polygon
polygons_append(result, to_polygons(lines_path.polylines));
}
return result;
}
void bounds(const NSVGimage &image, Vec2f& min, Vec2f &max)
{
for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next)
for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) {
if (min.x() > path->bounds[0])
min.x() = path->bounds[0];
if (min.y() > path->bounds[1])
min.y() = path->bounds[1];
if (max.x() < path->bounds[2])
max.x() = path->bounds[2];
if (max.y() < path->bounds[3])
max.y() = path->bounds[3];
}
}
NSVGimage_ptr nsvgParseFromFile(const std::string &filename, const char *units, float dpi)
{
NSVGimage *image = ::nsvgParseFromFile(filename.c_str(), units, dpi);
return {image, &nsvgDelete};
}
std::unique_ptr<std::string> read_from_disk(const std::string &path)
{
boost::nowide::ifstream fs{path};
if (!fs.is_open())
return nullptr;
std::stringstream ss;
ss << fs.rdbuf();
return std::make_unique<std::string>(ss.str());
}
NSVGimage_ptr nsvgParse(const std::string& file_data, const char *units, float dpi){
// NOTE: nsvg parser consume data from input(char *)
size_t size = file_data.size();
// file data could be big, so it is allocated on heap
std::unique_ptr<char[]> data_copy(new char[size+1]);
memcpy(data_copy.get(), file_data.c_str(), size);
data_copy[size] = '\0'; // data for nsvg must be null terminated
NSVGimage *image = ::nsvgParse(data_copy.get(), units, dpi);
return {image, &nsvgDelete};
}
NSVGimage *init_image(EmbossShape::SvgFile &svg_file){
// is already initialized?
if (svg_file.image.get() != nullptr)
return svg_file.image.get();
if (svg_file.file_data == nullptr) {
// chech if path is known
if (svg_file.path.empty())
return nullptr;
svg_file.file_data = read_from_disk(svg_file.path);
if (svg_file.file_data == nullptr)
return nullptr;
}
// init svg image
svg_file.image = nsvgParse(*svg_file.file_data);
if (svg_file.image.get() == NULL)
return nullptr;
return svg_file.image.get();
}
size_t get_shapes_count(const NSVGimage &image)
{
size_t count = 0;
for (NSVGshape * s = image.shapes; s != NULL; s = s->next)
++count;
return count;
}
//void save(const NSVGimage &image, std::ostream &data)
//{
// data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>";
//
// // tl .. top left
// Vec2f tl(std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
// // br .. bottom right
// Vec2f br(std::numeric_limits<float>::min(), std::numeric_limits<float>::min());
// bounds(image, tl, br);
//
// tl.x() = std::floor(tl.x());
// tl.y() = std::floor(tl.y());
//
// br.x() = std::ceil(br.x());
// br.y() = std::ceil(br.y());
// Vec2f s = br - tl;
// Point size = s.cast<Point::coord_type>();
//
// data << "<svg xmlns=\"http://www.w3.org/2000/svg\" "
// << "width=\"" << size.x() << "mm\" "
// << "height=\"" << size.y() << "mm\" "
// << "viewBox=\"0 0 " << size.x() << " " << size.y() << "\" >\n";
// data << "<!-- Created with PrusaSlicer (https://www.prusa3d.com/prusaslicer/) -->\n";
//
// std::array<char, 128> buffer;
// auto write_point = [&tl, &buffer](std::string &d, const float *p) {
// float x = p[0] - tl.x();
// float y = p[1] - tl.y();
// auto to_string = [&buffer](float f) -> std::string {
// auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), f);
// if (ec != std::errc{})
// return "0";
// return std::string(buffer.data(), ptr);
// };
// d += to_string(x) + "," + to_string(y) + " ";
// };
//
// for (const NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next) {
// enum struct Type { move, line, curve, close }; // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
// Type type = Type::move;
// std::string d = "M "; // move on start point
// for (const NSVGpath *path = shape->paths; path != NULL; path = path->next) {
// if (path->npts <= 1)
// continue;
//
// if (type == Type::close) {
// type = Type::move;
// // NOTE: After close must be a space
// d += " M "; // move on start point
// }
// write_point(d, path->pts);
// size_t path_size = static_cast<size_t>(path->npts - 1);
//
// if (path->closed) {
// // Do not use last point in path it is duplicit
// if (path->npts <= 4)
// continue;
// path_size = static_cast<size_t>(path->npts - 4);
// }
//
// for (size_t i = 0; i < path_size; i += 3) {
// const float *p = &path->pts[i * 2];
// if (!::is_line(p)) {
// if (type != Type::curve) {
// type = Type::curve;
// d += "C "; // start sequence of triplets defining curves
// }
// write_point(d, &p[2]);
// write_point(d, &p[4]);
// } else {
//
// if (type != Type::line) {
// type = Type::line;
// d += "L "; // start sequence of line points
// }
// }
// write_point(d, &p[6]);
// }
// if (path->closed) {
// type = Type::close;
// d += "Z"; // start sequence of line points
// }
// }
// if (type != Type::close) {
// //type = Type::close;
// d += "Z"; // closed path
// }
// data << "<path fill=\"#D2D2D2\" d=\"" << d << "\" />\n";
// }
// data << "</svg>\n";
//}
//
//bool save(const NSVGimage &image, const std::string &svg_file_path)
//{
// std::ofstream file{svg_file_path};
// if (!file.is_open())
// return false;
// save(image, file);
// return true;
//}
} // namespace Slic3r
namespace {
using namespace Slic3r; // Polygon + Vec2f
Point::coord_type to_coor(float val, double scale) { return static_cast<Point::coord_type>(std::round(val * scale)); }
bool need_flattening(float tessTol, const Vec2f &p1, const Vec2f &p2, const Vec2f &p3, const Vec2f &p4) {
// f .. first
// s .. second
auto det = [](const Vec2f &f, const Vec2f &s) {
return std::fabs(f.x() * s.y() - f.y() * s.x());
};
Vec2f pd = (p4 - p1);
Vec2f pd2 = (p2 - p4);
float d2 = det(pd2, pd);
Vec2f pd3 = (p3 - p4);
float d3 = det(pd3, pd);
float d23 = d2 + d3;
return (d23 * d23) >= tessTol * pd.squaredNorm();
}
// see function nsvg__lineTo(NSVGparser* p, float x, float y)
bool is_line(const float *p, float precision){
//Vec2f p1(p[0], p[1]);
//Vec2f p2(p[2], p[3]);
//Vec2f p3(p[4], p[5]);
//Vec2f p4(p[6], p[7]);
float dx_3 = (p[6] - p[0]) / 3.f;
float dy_3 = (p[7] - p[1]) / 3.f;
return
is_approx(p[2], p[0] + dx_3, precision) &&
is_approx(p[4], p[6] - dx_3, precision) &&
is_approx(p[3], p[1] + dy_3, precision) &&
is_approx(p[5], p[7] - dy_3, precision);
}
/// <summary>
/// Convert cubic curve to lines
/// Inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez
/// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335
/// </summary>
/// <param name="polygon">Result points</param>
/// <param name="tessTol">Tesselation tolerance</param>
/// <param name="p1">Curve point</param>
/// <param name="p2">Curve point</param>
/// <param name="p3">Curve point</param>
/// <param name="p4">Curve point</param>
/// <param name="level">Actual depth of recursion</param>
void flatten_cubic_bez(Points &points, float tessTol, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4, int level)
{
if (!need_flattening(tessTol, p1, p2, p3, p4)) {
Point::coord_type x = static_cast<Point::coord_type>(std::round(p4.x()));
Point::coord_type y = static_cast<Point::coord_type>(std::round(p4.y()));
points.emplace_back(x, y);
return;
}
--level;
if (level == 0)
return;
Vec2f p12 = (p1 + p2) * 0.5f;
Vec2f p23 = (p2 + p3) * 0.5f;
Vec2f p34 = (p3 + p4) * 0.5f;
Vec2f p123 = (p12 + p23) * 0.5f;
Vec2f p234 = (p23 + p34) * 0.5f;
Vec2f p1234 = (p123 + p234) * 0.5f;
flatten_cubic_bez(points, tessTol, p1, p12, p123, p1234, level);
flatten_cubic_bez(points, tessTol, p1234, p234, p34, p4, level);
}
LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams &param)
{
LinesPath result;
Polygons &polygons = result.polygons;
Polylines &polylines = result.polylines;
// multiple use of allocated memmory for points between paths
Points points;
for (NSVGpath *path = first_path; path != NULL; path = path->next) {
// Flatten path
Point::coord_type x = to_coor(path->pts[0], param.scale);
Point::coord_type y = to_coor(path->pts[1], param.scale);
points.emplace_back(x, y);
size_t path_size = (path->npts > 1) ? static_cast<size_t>(path->npts - 1) : 0;
for (size_t i = 0; i < path_size; i += 3) {
const float *p = &path->pts[i * 2];
if (is_line(p)) {
// point p4
Point::coord_type xx = to_coor(p[6], param.scale);
Point::coord_type yy = to_coor(p[7], param.scale);
points.emplace_back(xx, yy);
continue;
}
Vec2f p1(p[0], p[1]);
Vec2f p2(p[2], p[3]);
Vec2f p3(p[4], p[5]);
Vec2f p4(p[6], p[7]);
flatten_cubic_bez(points, param.tesselation_tolerance,
p1 * param.scale, p2 * param.scale, p3 * param.scale, p4 * param.scale,
param.max_level);
}
assert(!points.empty());
if (points.empty())
continue;
if (param.is_y_negative)
for (Point &p : points)
p.y() = -p.y();
if (path->closed) {
polygons.emplace_back(points);
} else {
polylines.emplace_back(points);
}
// prepare for new path - recycle alocated memory
points.clear();
}
remove_same_neighbor(polygons);
remove_same_neighbor(polylines);
return result;
}
HealedExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param)
{
Polygons fill = lines_path.polygons; // copy
// close polyline to create polygon
polygons_append(fill, to_polygons(lines_path.polylines));
if (fill.empty())
return {};
// if (shape->fillRule == NSVGfillRule::NSVG_FILLRULE_NONZERO)
bool is_non_zero = true;
if (shape.fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD)
is_non_zero = false;
return Emboss::heal_polygons(fill, is_non_zero, param.max_heal_iteration);
}
struct DashesParam{
// first dash length
float dash_length = 1.f; // scaled
// is current dash .. true
// is current space .. false
bool is_line = true;
// current index to array
unsigned char dash_index = 0;
static constexpr size_t max_dash_array_size = 8; // limitation of nanosvg strokeDashArray
std::array<float, max_dash_array_size> dash_array; // scaled
unsigned char dash_count = 0; // count of values in array
explicit DashesParam(const NSVGshape &shape, double scale) :
dash_count(shape.strokeDashCount)
{
assert(dash_count > 0);
assert(dash_count <= max_dash_array_size); // limitation of nanosvg strokeDashArray
for (size_t i = 0; i < dash_count; ++i)
dash_array[i] = static_cast<float>(shape.strokeDashArray[i] * scale);
// Figure out dash offset.
float all_dash_length = 0;
for (unsigned char j = 0; j < dash_count; ++j)
all_dash_length += dash_array[j];
if (dash_count%2 == 1) // (shape.strokeDashCount & 1)
all_dash_length *= 2.0f;
// Find location inside pattern
float dash_offset = fmodf(static_cast<float>(shape.strokeDashOffset * scale), all_dash_length);
if (dash_offset < 0.0f)
dash_offset += all_dash_length;
while (dash_offset > dash_array[dash_index]) {
dash_offset -= dash_array[dash_index];
dash_index = (dash_index + 1) % shape.strokeDashCount;
is_line = !is_line;
}
dash_length = dash_array[dash_index] - dash_offset;
}
};
Polylines to_dashes(const Polyline &polyline, const DashesParam& param)
{
Polylines dashes;
Polyline dash; // cache for one dash in dashed line
Point prev_point;
bool is_line = param.is_line;
unsigned char dash_index = param.dash_index;
float dash_length = param.dash_length; // current rest of dash distance
for (const Point &point : polyline.points) {
if (&point == &polyline.points.front()) {
// is first point
prev_point = point; // copy
continue;
}
Point diff = point - prev_point;
float line_segment_length = diff.cast<float>().norm();
while (dash_length < line_segment_length) {
// Calculate intermediate point
float d = dash_length / line_segment_length;
Point move_point = diff * d;
Point intermediate = prev_point + move_point;
// add Dash in stroke
if (is_line) {
if (dash.empty()) {
dashes.emplace_back(Points{prev_point, intermediate});
} else {
dash.append(prev_point);
dash.append(intermediate);
dashes.push_back(dash);
dash.clear();
}
}
diff -= move_point;
line_segment_length -= dash_length;
prev_point = intermediate;
// Advance dash pattern
is_line = !is_line;
dash_index = (dash_index + 1) % param.dash_count;
dash_length = param.dash_array[dash_index];
}
if (is_line)
dash.append(prev_point);
dash_length -= line_segment_length;
prev_point = point; // copy
}
// add last dash
if (is_line){
assert(!dash.empty());
dash.append(prev_point); // prev_point == polyline.points.back()
dashes.push_back(dash);
}
return dashes;
}
HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param)
{
// convert stroke to polygon
JoinType join_type = jtSquare;
switch (static_cast<NSVGlineJoin>(shape.strokeLineJoin)) {
case NSVGlineJoin::NSVG_JOIN_BEVEL: join_type = jtSquare; break;
case NSVGlineJoin::NSVG_JOIN_MITER: join_type = jtMiter; break;
case NSVGlineJoin::NSVG_JOIN_ROUND: join_type = jtRound; break;
}
double mitter = shape.miterLimit * param.scale;
if (join_type == jtRound) {
// mitter is used as ArcTolerance
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
mitter = std::pow(param.tesselation_tolerance, 1/3.);
}
float stroke_width = static_cast<float>(shape.strokeWidth * param.scale);
EndType end_type = etOpenButt;
switch (static_cast<NSVGlineCap>(shape.strokeLineCap)) {
case NSVGlineCap::NSVG_CAP_BUTT: end_type = etOpenButt; break;
case NSVGlineCap::NSVG_CAP_ROUND: end_type = etOpenRound; break;
case NSVGlineCap::NSVG_CAP_SQUARE: end_type = etOpenSquare; break;
}
Polygons result;
if (shape.strokeDashCount > 0) {
DashesParam params(shape, param.scale);
Polylines dashes;
for (const Polyline &polyline : lines_path.polylines)
polylines_append(dashes, to_dashes(polyline, params));
for (const Polygon &polygon : lines_path.polygons)
polylines_append(dashes, to_dashes(to_polyline(polygon), params));
result = offset(dashes, stroke_width / 2, join_type, mitter, end_type);
} else {
result = contour_to_polygons(lines_path.polygons, stroke_width, join_type, mitter);
polygons_append(result, offset(lines_path.polylines, stroke_width / 2, join_type, mitter, end_type));
}
bool is_non_zero = true;
return Emboss::heal_polygons(result, is_non_zero, param.max_heal_iteration);
}
} // namespace