import math
class SVGGenerator:
"""SVGを作るためのクラス"""
def __init__(self, width_mm=100, height_mm=100):
self.width_mm = width_mm
self.height_mm = height_mm
self.elements = []
def circle(self, radius_mm):
"""半径radius_mmのなるべく細い円を書く"""
self.elements.append(
f''
)
def sector_ring(self, outer_radius_mm, inner_radius_mm, start_degree, end_degree):
"""outer_radius_mmの円から、inner_radius_mmの円を切り取り、start_degreeからend_degreeまでを取り出した扇形環を描く"""
def polar_to_cartesian(radius, angle_in_degrees):
angle_in_radians = (angle_in_degrees - 90) * math.pi / 180.0
return (
(self.width_mm / 2) + (radius * math.cos(angle_in_radians)),
(self.height_mm / 2) + (radius * math.sin(angle_in_radians))
)
start_outer = polar_to_cartesian(outer_radius_mm, end_degree)
end_outer = polar_to_cartesian(outer_radius_mm, start_degree)
start_inner = polar_to_cartesian(inner_radius_mm, end_degree)
end_inner = polar_to_cartesian(inner_radius_mm, start_degree)
large_arc_flag = "1" if end_degree - start_degree > 180 else "0"
path_data = [
f"M {start_outer[0]} {start_outer[1]}",
f"A {outer_radius_mm} {outer_radius_mm} 0 {large_arc_flag} 0 {end_outer[0]} {end_outer[1]}",
f"L {end_inner[0]} {end_inner[1]}",
f"A {inner_radius_mm} {inner_radius_mm} 0 {large_arc_flag} 1 {start_inner[0]} {start_inner[1]}",
"Z"
]
self.elements.append(f'')
def indicator(self, inner_radius_mm, pos_digree):
"""inner_radius_mm、0度の位置に大きさ2mm程度の三角を描く"""
size_mm = 2
def polar_to_cartesian(radius, angle_in_degrees):
angle_in_radians = (angle_in_degrees - 90) * math.pi / 180.0
return (
(self.width_mm / 2) + (radius * math.cos(angle_in_radians)),
(self.height_mm / 2) + (radius * math.sin(angle_in_radians))
)
# Tip of the triangle, pointing towards the center
p1 = polar_to_cartesian(inner_radius_mm, 0 + pos_digree)
# Base of the triangle is further out
base_radius = inner_radius_mm + size_mm
half_base_width = size_mm / 2
half_base_angle_rad = half_base_width / base_radius
half_base_angle_deg = half_base_angle_rad * 180 / math.pi
p2 = polar_to_cartesian(base_radius, -half_base_angle_deg + pos_digree)
p3 = polar_to_cartesian(base_radius, half_base_angle_deg + pos_digree)
self.elements.append(
f''
)
def label(self, text):
"""左上にラベルを書く"""
self.elements.append(
f'{text}'
)
def export_to_svg(self):
"""描いたデータをsvgにして出力する。"""
svg_elements = "\n".join(self.elements)
return (
f'"
)
encoders = [
{
"page": "one_dial",
"x": 10,
"y": 10,
"name": "one_dial",
"bits": 6,
# ダイヤルでは最初の穴の中心が0度の位置、そこから23/3度おきに35個の穴がある。
# コードでは最初の穴位置が20度から(20+23/2)度になってる。
"degrees": [
0, # 最初の遊び
]+[
(i * 23 / 3 + 20) for i in range(33+2)
] + [320],
# 最初の穴の中央位置 = 23/3*.5+20
# センサとエンドの位置のズレ = 23/3+5
"indicator": 23/3*0.5+20 + 23/3+5,
},
{
"page": "nine_dial",
"x": 0,
"y": 10,
"name": "dial_a",
"bits": 6,
# あそびあり35穴
"degrees": [
0, # 最初の遊び
]+[
(i * 23 / 3 + 20) for i in range(33+2)
] + [320],
"indicator": 23/3*0.5+20 + 23/3+5 + 85,
},
{
"page": "nine_dial",
"x": 70,
"y": 10,
"name": "dial_b",
"bits": 2,
# あそびなし3穴
"degrees": [
270/3*0, # 穴1
270/3*1, # 穴2
270/3*2, # 穴3
270/3*3, # 最後
],
"indicator": 75,
},
{
"page": "nine_dial",
"x": 120,
"y": 10,
"name": "dial_c",
"bits": 3,
# あそびなし4穴
"degrees": [
270/4*0, # 穴1
270/4*1, # 穴2
270/4*2, # 穴3
270/4*3, # 穴4
270/4*4, # 最後
],
"indicator": 75-90,
},
{
"page": "nine_dial",
"x": 0,
"y": 110,
"name": "dial_d",
"bits": 3,
# あそびあり1穴
"degrees": [
0, # あそび
20, # 1オンになる所
300, # 最後
],
"indicator": 20+75+90-90,
},
{
"page": "nine_dial",
"x": 60,
"y": 110,
"name": "dial_e",
"bits": 3,
# あそびあり6穴
"degrees": [
0, # あそび
20 + 270/6*0, # 穴1
20 + 270/6*1, # 穴2
20 + 270/6*2, # 穴3
20 + 270/6*3, # 穴4
20 + 270/6*4, # 穴5
20 + 270/6*5, # 穴6
20 + 270/6*6, # 最後
],
"indicator": 20+75+45,
},
{
"page": "nine_dial",
"x": 120,
"y": 110,
"name": "dial_f",
"bits": 3,
# あそびあり4穴
# 十字キーなので値がすごい変則
"degrees": [
0, # あそび
10+360/4*0, # 穴1
10+360/4*1, # 穴2
10+360/4*2, # 穴3
10+360/4*3, # 穴4
10+360/4*4-45, # 最後
],
"indicator": 75+90-15,
},
{
"page": "nine_dial",
"x": 0,
"y": 210,
"name": "dial_g",
"bits": 2,
# あそびなし3穴
"degrees": [ # degrees dial_bと同じ
270/3*0, # 穴1
270/3*1, # 穴2
270/3*2, # 穴3
270/3*3, # 最後
],
"indicator": 75+80,
},
{
"page": "nine_dial",
"x": 50,
"y": 210,
"name": "dial_h",
"bits": 2,
# あそびなし3穴
"degrees": [ # degrees dial_bと同じ
270/3*0, # 穴1
270/3*1, # 穴2
270/3*2, # 穴3
270/3*3, # 最後
],
"indicator": 75+90,
},
{
"page": "nine_dial",
"x": 110,
"y": 210,
"name": "dial_i",
"bits": 4,
# あそびあり10穴
"degrees": [
0, # あそび
20 + 270/10*0, # 穴1
20 + 270/10*1, # 穴2
20 + 270/10*2, # 穴3
20 + 270/10*3, # 穴4
20 + 270/10*4, # 穴5
20 + 270/10*5, # 穴6
20 + 270/10*6, # 穴7
20 + 270/10*7, # 穴8
20 + 270/10*8, # 穴9
20 + 270/10*9, # 穴10
20 + 270/10*10, # 最後
],
"indicator": 20+75+315-30,
},
]
from collections import defaultdict
# Group encoders by page
pages = defaultdict(list)
for encoder in encoders:
pages[encoder["page"]].append(encoder)
# A4 size in mm
A4_WIDTH_MM = 210
A4_HEIGHT_MM = 297
# Process each page
for page_name, page_encoders in pages.items():
page_svg_elements = []
for encoder in page_encoders:
# SVGGeneratorは内部でwidth/height=100を想定して中心座標を計算している
gen = SVGGenerator(width_mm=100, height_mm=100)
bits = encoder["bits"]
templ = encoder["degrees"]
gen.circle(8)
gen.circle(8 + 5 * bits)
gen.indicator(8 + 5 * bits, encoder["indicator"])
for i in range(len(templ)-1):
gen.label(encoder["name"])
num = i+1
gray = num ^ (num>>1)
s = f'{gray:0{bits}b}'
# print(s)
for j in range(bits):
if (s[bits-j-1] == "1"):
gen.sector_ring(8+5+5*j, 8+5*j, templ[i], templ[i+1])
# Get the SVG elements from the public member
encoder_elements = "\n".join(gen.elements)
transform_x = encoder["x"]
transform_y = encoder["y"]
page_svg_elements.append(
f'\n{encoder_elements}\n'
)
# Combine all elements for the page into a single A4 SVG
final_svg_content = (
f'"
)
# Write to the page file
with open(f"{page_name}.svg", "w") as f:
f.write(final_svg_content)