Skip to content

Commit

Permalink
Merge pull request #550 from eyal0/normalize_svg
Browse files Browse the repository at this point in the history
refactor: Normalize svg outputs
  • Loading branch information
eyal0 committed Feb 18, 2021
2 parents 66f1de8 + da3d0f3 commit 440e996
Show file tree
Hide file tree
Showing 372 changed files with 5,008 additions and 5,043 deletions.
50 changes: 0 additions & 50 deletions integration_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,57 +151,11 @@ def colored(text, **color):

class IntegrationTests(unittest2.TestCase):
"""Run integration tests."""
def rotate_pathstring(self, pathstring):
"""Parse a string representing an SVG path.
Parse it into an array of array of points. Each array is a series of line
segments. They are drawn in order and the evenodd rule determines which is
the hole and which is the solid part.
It's intentionally not very flexible in what it supports, in case we
accidentally parse something that we've never seen before and do it
incorrectly.
"""
def string_to_paths(pathstring):
"""Returns an array of paths where each path starts with an absolute move
(M) and the rest are absolute lineTo (L) until the next moveTo (M).
"""
pathstring = pathstring[2:-3] # Remove the first M and the final z
paths = [[tuple(point.split(","))
for point in pathstring_text.strip().split(" L ")]
for pathstring_text in pathstring.split("M ")]
# Now paths is a list. Each element is an array of points.
# Each point is a pair of strings, x,y.
return paths

def rotate_path(path):
"""Rotate the path so that the least element is first.
Only rotate is the first and last element match.
"""
self.assertEqual(path[0], path[-1])
index_of_smallest = min(enumerate(path),
key=lambda x: (float(x[1][0]), float(x[1][1]), x[0]))[0]
rotated_points = path[index_of_smallest:-1] + path[:index_of_smallest+1]
return rotated_points

def paths_to_string(paths):
"""Return the path string that represents these paths."""
return "M " + "M ".join(" L ".join(','.join(point) for point in path)
for path in paths) + " z "
self.assertEqual(pathstring, paths_to_string(string_to_paths(pathstring)))
return paths_to_string(rotate_path(p) for p in string_to_paths(pathstring))

def fix_up_expected(self, path):
"""Fix up any files made in the output directory
This will enlarge all SVG by a factor of 10 in each direction until they are
at least 1000 in each dimension. This makes them easier to view on github.
Also adjust the order of all SVG paths that start and end at the same place
to start on the smallest element. This will make github diffs smaller in
some cases where a no-effect union or intersection of polygons chnaged the
order of points.
"""
def bigger(matchobj):
width = float(matchobj.group('width'))
Expand All @@ -221,10 +175,6 @@ def bigger(matchobj):
re.sub('width="(?P<width>[^"]*)" height="(?P<height>[^"]*)" ',
bigger,
line))
elif line.startswith('<g fill-rule="evenodd"><path d="M '):
etree = xml.etree.ElementTree.fromstring(line)
pathstring = etree[0].attrib['d']
svg_file.write(line.replace(pathstring, self.rotate_pathstring(pathstring)))
else:
svg_file.write(line)

Expand Down
2 changes: 1 addition & 1 deletion surface_vectorial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ using boost::make_optional;
#include "units.hpp"
#include "path_finding.hpp"
#include "trim_paths.hpp"
#include "svg_writer.hpp"

using std::max;
using std::max_element;
Expand Down Expand Up @@ -146,7 +147,6 @@ multi_linestring_type_fp mirror_toolpath(
vector<polygon_type_fp> find_thermal_reliefs(const multi_polygon_type_fp& milling_surface,
const coordinate_type_fp tolerance) {
// For each shape, see if it has any holes that are empty.
optional<svg_writer> image;
vector<polygon_type_fp> holes;
for (const auto& p : milling_surface) {
for (const auto& inner : p.inners()) {
Expand Down
17 changes: 0 additions & 17 deletions surface_vectorial.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,21 +112,4 @@ class Surface_vectorial: private boost::noncopyable {
coordinate_type_fp tolerance, bool find_contentions) const;
};

class svg_writer {
public:
svg_writer(std::string filename, box_type_fp bounding_box);
template <typename multi_polygon_type_t>
void add(const multi_polygon_type_t& geometry, double opacity, bool stroke);
void add(const multi_linestring_type_fp& mls, coordinate_type_fp width, bool stroke);
void add(const std::vector<polygon_type_fp>& geometries, double opacity,
int r = -1, int g = -1, int b = -1);
void add(const linestring_type_fp& paths, coordinate_type_fp width, unsigned int r, unsigned int g, unsigned int b);
void add(const multi_linestring_type_fp& paths, coordinate_type_fp width, unsigned int r, unsigned int g, unsigned int b);

protected:
std::ofstream output_file;
const box_type_fp bounding_box;
std::unique_ptr<bg::svg_mapper<point_type_fp> > mapper;
};

#endif // SURFACE_VECTORIAL_H
68 changes: 51 additions & 17 deletions svg_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,61 @@ svg_writer::svg_writer(string filename, box_type_fp bounding_box) :
mapper->add(bounding_box);
}

void normalize(ring_type_fp& ring) {
// Subtract 1 because the first and last of a ring are the same point.
auto min = std::min_element(ring.begin(), ring.end()-1,
[](const point_type_fp& a, const point_type_fp& b) {
return a < b;
});
std::rotate(ring.begin(), min, ring.end()-1);
ring.back() = ring.front(); // The first and last of a ring should be the same point.
}

void normalize(polygon_type_fp& poly) {
normalize(poly.outer());
auto (*n)(ring_type_fp&) = normalize;
std::for_each(poly.inners().begin(), poly.inners().end(), n);
std::sort(poly.inners().begin(), poly.inners().end());
}

void normalize(multi_polygon_type_fp& mpoly) {
auto (*n)(polygon_type_fp&) = normalize;
std::for_each(mpoly.begin(), mpoly.end(), n);
std::sort(mpoly.begin(), mpoly.end(),
[](const polygon_type_fp& a, const polygon_type_fp& b) {
return a.outer() < b.outer();
});
}

void normalize(multi_linestring_type_fp& mls) {
std::sort(mls.begin(), mls.end());
}

template <typename multi_polygon_type_t>
void svg_writer::add(const multi_polygon_type_t& geometry, double opacity, bool stroke)
{
string stroke_str = stroke ? "stroke:rgb(0,0,0);stroke-width:2" : "";
void svg_writer::add(multi_polygon_type_t geometry, double opacity, bool stroke) {
// Sort the geometry so that we'll have fewer diffs.
normalize(geometry);
string stroke_str = stroke ? "stroke:rgb(0,0,0);stroke-width:2" : "";

for (const auto& poly : geometry)
{
const unsigned int r = rand() % 256;
const unsigned int g = rand() % 256;
const unsigned int b = rand() % 256;
for (const auto& poly : geometry) {
const unsigned int r = rand() % 256;
const unsigned int g = rand() % 256;
const unsigned int b = rand() % 256;

multi_polygon_type_t new_bounding_box;
bg::convert(bounding_box, new_bounding_box);
multi_polygon_type_t new_bounding_box;
bg::convert(bounding_box, new_bounding_box);

mapper->map(poly & new_bounding_box,
str(boost::format("fill-opacity:%f;fill:rgb(%u,%u,%u);" + stroke_str) %
opacity % r % g % b));
}
mapper->map(poly & new_bounding_box,
str(boost::format("fill-opacity:%f;fill:rgb(%u,%u,%u);" + stroke_str) %
opacity % r % g % b));
}
}

template void svg_writer::add<multi_polygon_type_fp>(const multi_polygon_type_fp&, double, bool);
template void svg_writer::add<multi_polygon_type_fp>(multi_polygon_type_fp, double, bool);

void svg_writer::add(const multi_linestring_type_fp& mls, coordinate_type_fp width, bool stroke) {
void svg_writer::add(multi_linestring_type_fp mls, coordinate_type_fp width, bool stroke) {
// Sort the geometry so that we'll have fewer diffs.
normalize(mls);
string stroke_str = stroke ? "stroke:rgb(0,0,0);stroke-width:2" : "";

for (const auto& ls : mls) {
Expand All @@ -76,7 +108,9 @@ void svg_writer::add(const linestring_type_fp& path, coordinate_type_fp width, u
"stroke-opacity:1;stroke-linecap:round;stroke-linejoin:round;");
}

void svg_writer::add(const multi_linestring_type_fp& path, coordinate_type_fp width, unsigned int r, unsigned int g, unsigned int b) {
void svg_writer::add(multi_linestring_type_fp path, coordinate_type_fp width, unsigned int r, unsigned int g, unsigned int b) {
// Sort the geometry so that we'll have fewer diffs.
normalize(path);
for (const auto& p : path) {
add(p, width, r, g, b);
}
Expand Down
10 changes: 4 additions & 6 deletions svg_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ class svg_writer {
public:
svg_writer(std::string filename, box_type_fp bounding_box);
template <typename multi_polygon_type_t>
void add(const multi_polygon_type_t& geometry, double opacity, bool stroke);
void add(const multi_linestring_type_fp& mls, coordinate_type_fp width, bool stroke);
void add(const std::vector<polygon_type_fp>& geometries, double opacity,
int r = -1, int g = -1, int b = -1);
void add(const linestring_type_fp& paths, coordinate_type_fp width, unsigned int r, unsigned int g, unsigned int b);
void add(const multi_linestring_type_fp& paths, coordinate_type_fp width, unsigned int r, unsigned int g, unsigned int b);
void add(multi_polygon_type_t geometry, double opacity, bool stroke);
void add(multi_linestring_type_fp mls, coordinate_type_fp width, bool stroke);
void add(const linestring_type_fp& path, coordinate_type_fp width, unsigned int r, unsigned int g, unsigned int b);
void add(multi_linestring_type_fp paths, coordinate_type_fp width, unsigned int r, unsigned int g, unsigned int b);

protected:
std::ofstream output_file;
Expand Down
Loading

0 comments on commit 440e996

Please sign in to comment.