mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-11 17:28:59 +08:00
Add dashed line loading from svg file
Note: Inaccurate offset of dashes on curves. (length is calculated after flattening -> on line segments)
This commit is contained in:
parent
d9b0fad80c
commit
0ac8afa75c
@ -382,6 +382,112 @@ ExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shap
|
||||
return union_ex(fill, fill_type);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams ¶m)
|
||||
{
|
||||
// convert stroke to polygon
|
||||
@ -394,11 +500,11 @@ ExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &sh
|
||||
|
||||
double mitter = shape.miterLimit * param.scale;
|
||||
if (join_type == ClipperLib::JoinType::jtRound) {
|
||||
// mitter is ArcTolerance
|
||||
// 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);
|
||||
Polygons result = contour_to_polygons(lines_path.polygons, stroke_width, join_type, mitter);
|
||||
|
||||
ClipperLib::EndType end_type = ClipperLib::EndType::etOpenButt;
|
||||
switch (static_cast<NSVGlineCap>(shape.strokeLineCap)) {
|
||||
@ -406,7 +512,21 @@ ExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &sh
|
||||
case NSVGlineCap::NSVG_CAP_ROUND: end_type = ClipperLib::EndType::etOpenRound; break;
|
||||
case NSVGlineCap::NSVG_CAP_SQUARE: end_type = ClipperLib::EndType::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));
|
||||
}
|
||||
|
||||
return union_ex(result, ClipperLib::pftNonZero);
|
||||
}
|
||||
|
||||
|
@ -920,24 +920,28 @@ void add_comma_separated(std::string &result, const std::string &add){
|
||||
|
||||
const float warning_preccission = 1e-4f;
|
||||
std::string create_fill_warning(const NSVGshape &shape) {
|
||||
if (!(shape.flags & NSVG_FLAGS_VISIBLE) ||
|
||||
shape.fill.type == NSVG_PAINT_NONE)
|
||||
return {}; // not visible
|
||||
|
||||
std::string warning;
|
||||
if (shape.opacity <= warning_preccission)
|
||||
if ((shape.opacity - 1.f) <= warning_preccission)
|
||||
add_comma_separated(warning, GUI::format(_L("Opacity (%1%)"), shape.opacity));
|
||||
|
||||
// if(shape->flags != NSVG_FLAGS_VISIBLE) add_warning(_u8L("Visibility flag"));
|
||||
bool is_fill_gradient = shape.fillGradient[0] != '\0';
|
||||
if (is_fill_gradient)
|
||||
add_comma_separated(warning, GUI::format(_L("Fill gradient (%1%)"), shape.fillGradient));
|
||||
add_comma_separated(warning, GUI::format(_L("Color gradient (%1%)"), shape.fillGradient));
|
||||
|
||||
switch (shape.fill.type) {
|
||||
case NSVG_PAINT_UNDEF: add_comma_separated(warning, _u8L("Undefined fill type")); break;
|
||||
case NSVG_PAINT_LINEAR_GRADIENT:
|
||||
if (!is_fill_gradient)
|
||||
add_comma_separated(warning, _u8L("Linear fill gradient"));
|
||||
add_comma_separated(warning, _u8L("Linear gradient"));
|
||||
break;
|
||||
case NSVG_PAINT_RADIAL_GRADIENT:
|
||||
if (!is_fill_gradient)
|
||||
add_comma_separated(warning, _u8L("Radial fill gradient"));
|
||||
add_comma_separated(warning, _u8L("Radial gradient"));
|
||||
break;
|
||||
// case NSVG_PAINT_NONE:
|
||||
// case NSVG_PAINT_COLOR:
|
||||
@ -951,25 +955,37 @@ std::string create_fill_warning(const NSVGshape &shape) {
|
||||
}
|
||||
|
||||
std::string create_stroke_warning(const NSVGshape &shape) {
|
||||
|
||||
std::string warning;
|
||||
if (!(shape.flags & NSVG_FLAGS_VISIBLE) ||
|
||||
shape.stroke.type == NSVG_PAINT_NONE ||
|
||||
shape.strokeWidth <= 1e-5f)
|
||||
return {}; // not visible
|
||||
|
||||
if ((shape.opacity - 1.f) <= warning_preccission)
|
||||
add_comma_separated(warning, GUI::format(_L("Opacity (%1%)"), shape.opacity));
|
||||
|
||||
bool is_stroke_gradient = shape.strokeGradient[0] != '\0';
|
||||
if (is_stroke_gradient)
|
||||
add_comma_separated(warning, GUI::format(_L("Stroke gradient (%1%)"), shape.strokeGradient));
|
||||
add_comma_separated(warning, GUI::format(_L("Color gradient (%1%)"), shape.strokeGradient));
|
||||
|
||||
switch (shape.stroke.type) {
|
||||
case NSVG_PAINT_UNDEF: add_comma_separated(warning, _u8L("Undefined stroke type")); break;
|
||||
case NSVG_PAINT_LINEAR_GRADIENT:
|
||||
if (!is_stroke_gradient)
|
||||
add_comma_separated(warning, _u8L("Linear stroke gradient"));
|
||||
add_comma_separated(warning, _u8L("Linear gradient"));
|
||||
break;
|
||||
case NSVG_PAINT_RADIAL_GRADIENT:
|
||||
if (!is_stroke_gradient)
|
||||
add_comma_separated(warning, _u8L("Radial stroke gradient"));
|
||||
add_comma_separated(warning, _u8L("Radial gradient"));
|
||||
break;
|
||||
// case NSVG_PAINT_COLOR:
|
||||
// case NSVG_PAINT_NONE:
|
||||
// default: break;
|
||||
}
|
||||
|
||||
if (shape.opacity)
|
||||
|
||||
return warning;
|
||||
}
|
||||
|
||||
@ -979,23 +995,33 @@ std::string create_stroke_warning(const NSVGshape &shape) {
|
||||
/// <param name="image">Input svg loaded to shapes</param>
|
||||
/// <returns>Vector of warnings with same size as EmbossShape::shapes_with_ids
|
||||
/// or Empty when no warnings -> for fast checking that every thing is all right(more common case) </returns>
|
||||
std::vector<std::string> create_shape_warnings(const NSVGimage &image){
|
||||
std::vector<std::string> create_shape_warnings(const NSVGimage &image, float scale){
|
||||
std::vector<std::string> result;
|
||||
auto add_warning = [&result, &image](size_t index, const std::string &message) {
|
||||
if (result.empty())
|
||||
result = std::vector<std::string>(get_shapes_count(image) * 2);
|
||||
result[index] = message;
|
||||
};
|
||||
|
||||
size_t shape_index = 0;
|
||||
for (NSVGshape *shape = image.shapes; shape != NULL; shape = shape->next, ++shape_index) {
|
||||
std::string fill_warning = create_fill_warning(*shape);
|
||||
if (!fill_warning.empty()) {
|
||||
if (result.empty())
|
||||
result = std::vector<std::string>(get_shapes_count(image) * 2);
|
||||
result[shape_index * 2] = GUI::format(_L("Fill of shape(%1%) contain unsupported: %2% "), shape->id, fill_warning);
|
||||
if (!(shape->flags & NSVG_FLAGS_VISIBLE)){
|
||||
add_warning(shape_index * 2, GUI::format(_L("Shape(%1%) is marked as invisible "), shape->id));
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string stroke_warning = create_stroke_warning(*shape);
|
||||
if (!stroke_warning.empty()) {
|
||||
if (result.empty())
|
||||
result = std::vector<std::string>(get_shapes_count(image) * 2);
|
||||
result[shape_index * 2 + 1] = GUI::format(_L("Stroke of shape(%1%) contain unsupported: %2% "), shape->id, stroke_warning);
|
||||
std::string fill_warning = create_fill_warning(*shape);
|
||||
if (!fill_warning.empty())
|
||||
add_warning(shape_index * 2, GUI::format(_L("Fill of shape(%1%) contain unsupported: %2% "), shape->id, fill_warning));
|
||||
|
||||
float minimal_width_in_mm = 1e-3f;
|
||||
if (shape->strokeWidth <= minimal_width_in_mm * scale) {
|
||||
add_warning(shape_index * 2, GUI::format(_L("Stroke of shape(%1%) is too thin (minimal width is %2% mm)"), shape->id, minimal_width_in_mm));
|
||||
continue;
|
||||
}
|
||||
std::string stroke_warning = create_stroke_warning(*shape);
|
||||
if (!stroke_warning.empty())
|
||||
add_warning(shape_index * 2 + 1, GUI::format(_L("Stroke of shape(%1%) contain unsupported: %2% "), shape->id, stroke_warning));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -1044,7 +1070,7 @@ void GLGizmoSVG::set_volume_by_selection()
|
||||
NSVGimage* image = init_image(es.svg_file);
|
||||
assert(image != nullptr);
|
||||
if (image != nullptr){
|
||||
NSVGLineParams params{get_tesselation_tolerance(std::max(m_scale_width.value_or(1.f), m_scale_height.value_or(1.f)))};
|
||||
NSVGLineParams params{get_tesselation_tolerance(get_scale_for_tolerance())};
|
||||
shape_ids = create_shape_with_ids(*image, params);
|
||||
}
|
||||
}
|
||||
@ -1055,7 +1081,7 @@ void GLGizmoSVG::set_volume_by_selection()
|
||||
m_volume_id = volume->id();
|
||||
m_volume_shape = *volume->emboss_shape; // copy
|
||||
assert(es.svg_file.image.get() != nullptr);
|
||||
m_shape_warnings = create_shape_warnings(*es.svg_file.image);
|
||||
m_shape_warnings = create_shape_warnings(*es.svg_file.image, get_scale_for_tolerance());
|
||||
|
||||
// Calculate current angle of up vector
|
||||
m_angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit);
|
||||
@ -1107,6 +1133,9 @@ void GLGizmoSVG::calculate_scale() {
|
||||
calc(Vec3d::UnitZ(), m_scale_depth);
|
||||
}
|
||||
|
||||
float GLGizmoSVG::get_scale_for_tolerance(){
|
||||
return std::max(m_scale_width.value_or(1.f), m_scale_height.value_or(1.f)); }
|
||||
|
||||
bool GLGizmoSVG::process()
|
||||
{
|
||||
// no volume is selected -> selection from right panel
|
||||
@ -1430,11 +1459,12 @@ void GLGizmoSVG::draw_filename(){
|
||||
}
|
||||
|
||||
if (file_changed) {
|
||||
double tes_tol = get_tesselation_tolerance(std::max(m_scale_width.value_or(1.f), m_scale_height.value_or(1.f)));
|
||||
float scale = get_scale_for_tolerance();
|
||||
double tes_tol = get_tesselation_tolerance(scale);
|
||||
EmbossShape es_ = select_shape(m_volume_shape.svg_file.path, tes_tol);
|
||||
m_volume_shape.svg_file.image = std::move(es_.svg_file.image);
|
||||
m_volume_shape.shapes_with_ids = std::move(es_.shapes_with_ids);
|
||||
m_shape_warnings = create_shape_warnings(*m_volume_shape.svg_file.image);
|
||||
m_shape_warnings = create_shape_warnings(*m_volume_shape.svg_file.image, scale);
|
||||
init_texture(m_texture, m_volume_shape.shapes_with_ids, m_gui_cfg->texture_max_size_px);
|
||||
process();
|
||||
}
|
||||
@ -1584,7 +1614,7 @@ void GLGizmoSVG::draw_size()
|
||||
NSVGimage *img = m_volume_shape.svg_file.image.get();
|
||||
assert(img != NULL);
|
||||
if (img != NULL){
|
||||
NSVGLineParams params{get_tesselation_tolerance(std::max(m_scale_width.value_or(1.f), m_scale_height.value_or(1.f)))};
|
||||
NSVGLineParams params{get_tesselation_tolerance(get_scale_for_tolerance())};
|
||||
m_volume_shape.shapes_with_ids = create_shape_with_ids(*img, params);
|
||||
process();
|
||||
}
|
||||
|
@ -179,6 +179,7 @@ private:
|
||||
std::optional<float> m_scale_height;
|
||||
std::optional<float> m_scale_depth;
|
||||
void calculate_scale();
|
||||
float get_scale_for_tolerance();
|
||||
|
||||
// keep SVG data rendered on GPU
|
||||
Texture m_texture;
|
||||
|
Loading…
x
Reference in New Issue
Block a user