Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
# Copyright 2015-2016 Scott Bezek and the splitflap contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from svg.path import Path
from svg.path import parse_path
from xml.dom import minidom
class SvgProcessor(object):
"""
Processes SVG files generated by OpenSCAD to prepare for laser cutting
"""
def __init__(self, input_file):
self.dom = minidom.parse(input_file)
self.svg_node = self.dom.documentElement
def fix_dimens(self):
# Add mm units to the document dimensions
self.svg_node.attributes['width'].value += 'mm'
self.svg_node.attributes['height'].value += 'mm'
def apply_laser_cut_style(self):
# Set fill and stroke for laser cutting
for path in self.svg_node.getElementsByTagName('path'):
SvgProcessor._apply_attributes(path, {
'fill': 'none',
'stroke': '#0000ff',
'stroke-width': '0.01',
})
def apply_laser_etch_style(self):
# Set fill and stroke for laser etching
for path in self.svg_node.getElementsByTagName('path'):
SvgProcessor._apply_attributes(path, {
'fill': '#000000',
'stroke': 'none',
})
def apply_raster_render_style(self):
# Set fill and stroke for rasterized rendering
for path in self.svg_node.getElementsByTagName('path'):
SvgProcessor._apply_attributes(path, {
'fill': 'none',
'stroke': '#000000',
'stroke-width': '0.2',
})
def import_paths(self, from_svg_processor):
for path in from_svg_processor.svg_node.getElementsByTagName('path'):
output_node = self.dom.importNode(path, True)
self.svg_node.appendChild(output_node)
def remove_redundant_lines(self):
eps = 0.0001
def get_slope_intersect(p1, p2):
if p1.real == p2.real:
# Vertical; no slope and return x-intercept
return None, p1.real
m = (p2.imag - p1.imag) / (p2.real - p1.real)
b1 = p1.imag - m * p1.real
b2 = p2.imag - m * p2.real
assert abs(b1 - b2) < eps
return m, b1
lines = []
paths = self.svg_node.getElementsByTagName('path')
for path_index, path in enumerate(paths):
path_text = path.attributes['d'].value
path_obj = parse_path(path_text)
for line_index, line in enumerate(path_obj):
lines.append((path_index, line_index, line))
# Naive N^2 search for overlapping lines
to_remove = {}
for i in range(len(lines)):
path_index1 = lines[i][0]
line_index1 = lines[i][1]
line1 = lines[i][2]
eq1 = get_slope_intersect(line1.start, line1.end)
for j in range(i + 1, len(lines)):
path_index2 = lines[j][0]
line_index2 = lines[j][1]
line2 = lines[j][2]
eq2 = get_slope_intersect(line2.start, line2.end)
# Compare slopes
if (eq1[0] is None and eq2[0] is None)\
or (eq1[0] is not None and eq2[0] is not None and abs(eq1[0]-eq2[0]) < eps):
# Compare intersects
if abs(eq1[1] - eq2[1]) < eps:
# Must be collinear, check for overlap
l1x1 = min(line1.start.real, line1.end.real)
l1x2 = max(line1.start.real, line1.end.real)
l1y1 = min(line1.start.imag, line1.end.imag)
l1y2 = max(line1.start.imag, line1.end.imag)
l2x1 = min(line2.start.real, line2.end.real)
l2x2 = max(line2.start.real, line2.end.real)
l2y1 = min(line2.start.imag, line2.end.imag)
l2y2 = max(line2.start.imag, line2.end.imag)
if l1x1 <= l2x1 and l1x2 >= l2x2 and l1y1 <= l2y1 and l1y2 >= l2y2:
# Overlapping, line 1 is bigger
assert line1.length() >= line2.length()
to_remove[j] = (path_index2, line_index2, line2)
elif l1x1 >= l2x1 and l1x2 <= l2x2 and l1y1 >= l2y1 and l1y2 <= l2y2:
# Overlapping, line 2 is bigger
assert line2.length() >= line1.length()
to_remove[i] = (path_index1, line_index1, line1)
i = 0
for path_index, path in enumerate(paths):
path_text = path.attributes['d'].value
path_obj = parse_path(path_text)
filtered_path = Path()
for line_index, line in enumerate(path_obj):
if i in to_remove:
assert path_index == to_remove[i][0]
assert line_index == to_remove[i][1]
assert line == to_remove[i][2]
else:
filtered_path.append(line)
i += 1
# Update the path data with the filtered path data
path.attributes['d'] = filtered_path.d()
return [to_remove[k][2] for k in to_remove]
def add_highlight_lines(self, lines):
for line in lines:
new_path_node = self.dom.createElement("path")
new_path_node.setAttribute('d', Path(line).d())
new_path_node.setAttribute('fill', 'none')
new_path_node.setAttribute('stroke', '#FF0000')
new_path_node.setAttribute('stroke-width', '1')
self.svg_node.appendChild(new_path_node)
def write(self, filename):
with open(filename, 'wb') as output_file:
self.svg_node.writexml(output_file)
@staticmethod
def _apply_attributes(node, values):
for (k, v) in values.items():
node.attributes[k] = v
2ad1dbb2bThis file is pinned to the selected release. Download the original to open it in your design tool.