MuesliMix/prototype/svg_utils.py

227 lines
7.5 KiB
Python

import numpy as np
import math
# scale in inkscape
# 1 unit = 0.28222 mm
svg_scale = 1000.0 / 282.222
def svg_circle(id, name, c, r):
# create circle object centered at point c with radius r
text = [' <circle\n',
' id="circle{}"\n'.format(id),
' inkscape:label="{}"\n'.format(name),
' style="fill:none;stroke:#000000;stroke-width:0.1mm"\n',
' r="{}mm"\n'.format(r),
' cy="{}mm"\n'.format(c[1]),
' cx="{}mm" />\n'.format(c[0])]
return text
def svg_puzzle(p, size, angle):
# convert angle to radians
angle = angle / 360.0 * 2.0 * np.pi
# compute points
"""
v1 and v2 are orthogonal vectors
construction of points (starting at p):
p3 <------ -2 v1 ------ p2
^
|
v2
|
|
p4 <-- -v1 -- p -- v1 --> p1
then between points p2 and p3 with draw an arc
"""
v1 = np.array([np.cos(angle), np.sin(angle)])
v2 = np.array([v1[1], -v1[0]])
p1 = p + size * v1
p2 = p1 + size * v2
p3 = p2 - 2.0 * size * v1
p4 = p - size * v1
# convert to svg units
p1 *= svg_scale
p2 *= svg_scale
p3 *= svg_scale
p4 *= svg_scale
radius_scaled = 1.25 * size * svg_scale
text = [' <path \n '
' id="path666" \n '
' style="fill:none;stroke:#ff0000;stroke-width:1.60000002" \n'
' d="M {} {} L {} {} A {} {} 0 1 0 {} {} L {} {}"'
' />\n'.format(p1[0], p1[1], p2[0], p2[1], radius_scaled, radius_scaled, p3[0], p3[1], p4[0], p4[1])]
return text
def svg_line_puzzle(start, end, puzzle_scale=1.0, linewidth=0.50, placement=0.5):
# draws a line from start to end with a simple jigsaw puzzle style cutout in the middle
# the size of the cutout can be controlled with the puzzle_scale parameter
# compute points
"""
v1 and v2 are orthogonal vectors
construction of points (starting at p (middle between start and end)):
p2 ------- 2 v1 -----> p3
^
|
v2
|
|
start --- p1 <-- -v1 -- p -- v1 --> p4 --- end
then between points p2 and p3 with draw an arc
"""
v = end - start
dist = np.linalg.norm(v)
size = dist / 10.0 * puzzle_scale # size of the cutout
v = v / dist
angle = math.atan2(v[1], v[0]) # angle of v
# midpoint between start and end
p = (1.0 - placement) * start + placement * end
#p = np.mean([start, end], axis=0)
v1 = np.array([np.cos(angle), np.sin(angle)])
v2 = np.array([v1[1], -v1[0]])
p1 = p - size * v1
p2 = p1 + size * v2
p3 = p2 + 2.0 * size * v1
p4 = p + size * v1
# convert to svg units
p1 *= svg_scale
p2 *= svg_scale
p3 *= svg_scale
p4 *= svg_scale
start *= svg_scale
end *= svg_scale
radius_scaled = 1.25 * size * svg_scale
text = [' <path \n '
' id="path666" \n '
' style="fill:none;stroke:#000000;stroke-width:{}mm" \n'
' d="M {} {} L {} {} L {} {} A {} {} 0 1 1 {} {} L {} {} L {} {}"'
' />\n'.format(linewidth, start[0], start[1], p1[0], p1[1], p2[0], p2[1], radius_scaled, radius_scaled,
p3[0], p3[1], p4[0], p4[1], end[0], end[1])]
return text
def svg_half_circle(id, name, c, r, angle, orientation_flag=1):
# draws half a circle centered at c with radius r
# angle specifies how the half circle should be rotated
# the orientation flag determines if the upper or the lower half of the circle is drawn
# convert angle to radians
angle = angle / 360.0 * 2.0 * np.pi
# compute starting point
v = np.array([np.cos(angle), np.sin(angle)])
begin = c + r * v # in millimeters
begin *= svg_scale # in svg units
# compute end point
end = c - r * v # in millimeters
end *= svg_scale # in svg units
radius_scaled = r * svg_scale # radius in svg units
text = [' <path \n '
' id="path666" \n '
' style="fill:none;stroke:#000000;stroke-width:0.60000002" \n'
' d="M {} {} A {} {} 0 {} {} {} {}"'
' />\n'.format(begin[0], begin[1], radius_scaled, radius_scaled, orientation_flag, orientation_flag,
end[0], end[1])]
return text
def svg_arc(p1, p2, r, large_arc, sweep):
begin = p1 * svg_scale
end = p2 * svg_scale
radius_scaled = r * svg_scale
text = [' <path \n '
' id="path666" \n '
' style="fill:none;stroke:#000000;stroke-width:0.60000002" \n'
' d="M {} {} A {} {} 0 {} {} {} {}"'
' />\n'.format(begin[0], begin[1], radius_scaled, radius_scaled, large_arc, sweep,
end[0], end[1])]
return text
def svg_rect(x, y, width, height, angle=0.0):
text = ['<g transform="rotate({})">\n '
'<rect x="{}mm" y="{}mm" width="{}mm" height="{}mm" style="fill:none;stroke-width:0.1mm;stroke:rgb(0,0,0)" />\n '
'</g>\n'
.format(angle, x, y, width, height)]
return text
def svg_rect_trans(id, name, c):
center = c['center']
width = c['length']
height = c['width']
angle = c['angle_deg']
x = np.sqrt(center[0] ** 2 + center[1] ** 2) - width / 2
y = - height
return svg_rect(x, y, width, height, angle)
def svg_line(p1, p2, width=1.0):
text = ['<line x1="{}mm" y1="{}mm" x2="{}mm" y2="{}mm" style="stroke:rgb(0,0,0);stroke-width:{}mm" />'.format(p1[0],
p1[1],
p2[0],
p2[1],
width)]
return text
def svg_gear_marking(tangent_coord, circle_midpoint, marking_length=5.0):
c = tangent_coord
v = np.array(c[0]) - np.array(circle_midpoint)
v = v / np.linalg.norm(v)
p1 = c[0]
p2 = c[0] + v * marking_length
text = svg_line(p1, p2)
return text
def svg_segment_border_inner(angle, center_hole_radius, circle_pos, circle_radius, puzzle_scale=1.0, placement=0.5):
a = angle
a = a / 360.0 * 2.0 * np.pi
r1 = np.linalg.norm(np.array(circle_pos)) - circle_radius
vunit = np.array([np.cos(a), np.sin(a)])
p1 = vunit * center_hole_radius
p2 = vunit * r1
text = svg_line_puzzle(p1, p2, puzzle_scale=puzzle_scale, placement=placement)
return text
def svg_segment_border_outer(angle, plate_pitch_radius, plate_gear_module, circle_pos, circle_radius, puzzle_scale=1.0,
placement=0.5):
a = angle
a = a / 360.0 * 2.0 * np.pi
vunit = np.array([np.cos(a), np.sin(a)])
r2 = np.linalg.norm(np.array(circle_pos)) + circle_radius
p3 = vunit * r2
r3 = plate_pitch_radius - plate_gear_module
p4 = vunit * r3
text = svg_line_puzzle(p3, p4, puzzle_scale=puzzle_scale, placement=placement)
return text