mightyscape-1.2/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical.py

322 lines
12 KiB
Python
Raw Normal View History

2022-11-14 23:27:13 +01:00
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
from abc import abstractmethod
from math import pi, sin, cos, tan, asin, acos, atan, sqrt
from itertools import accumulate
import inkex
from Path import Path
from Pattern import Pattern
# Select name of class, inherits from Pattern
# TODO:
# 1) Implement __init__ method to get all custom options and then call Pattern's __init__
# 2) Implement generate_path_tree to define all of the desired strokes
def generate_slot_line(n, slot_position,
slot_height, slot_width,
base_height, base_width):
if slot_height == 0 and slot_width == 0:
return []
slot_height = min(slot_height, base_height)
slot_width = min(slot_width, base_width)
rect = [ (0, 0),
(0, slot_height),
(slot_width, slot_height),
(slot_width, 0)]
dx = (base_width - slot_width) / 2
if slot_position == -1:
dy = 0
elif slot_position == 0:
dy = (base_height - slot_height) / 2
elif slot_position == +1:
dy = base_height - slot_height
slot = Path(rect, 'c', closed=True) + (dx, dy)
slots = [slot + (base_width * i, 0) for i in range(n)]
divider = Path([(base_width, 0), (base_width, base_height)], style='m')
dividers = [divider + (base_width*i, 0) for i in range(n-1)]
return slots + dividers
class Cylindrical(Pattern):
def __init__(self):
""" Constructor
"""
Pattern.__init__(self) # Must be called in order to parse common options
# save all custom parameters defined on .inx file
self.add_argument('--radius', type=self.float, default=10.0)
self.add_argument('--sides', type=self.int, default=6)
self.add_argument('--rows', type=self.int, default=3)
self.add_argument('--extra_column', type=self.bool, default=False)
# slot options for support ring
self.add_argument('--add_base_slot', type=self.bool, default=False)
self.add_argument('--base_slot_position', type=self.str, default="1")
self.add_argument('--base_height', type=self.float, default=5.0)
self.add_argument('--base_slot_height', type=self.float, default=3.0)
self.add_argument('--base_slot_width', type=self.float, default=3.0)
self.add_argument('--add_middle_slot', type=self.bool, default=False)
self.add_argument('--middle_slot_position', type=self.str, default="0")
self.add_argument('--distance', type=self.float, default=3.0)
self.add_argument('--middle_slot_height', type=self.float, default=3.0)
self.add_argument('--middle_slot_width', type=self.float, default=3.0)
@abstractmethod
def parse_parameters(self):
"""
"""
pass
@abstractmethod
def generate_cell(self):
""" Generate the the origami cell
"""
pass
def generate_path_tree(self):
""" Specialized path generation for your origami pattern
"""
# zero distances when slot option not selected
if not self.options.add_base_slot:
self.options.base_height = 0
self.options.base_slot_height = 0
if not self.options.add_middle_slot:
self.options.distance = 0
self.options.middle_slot_height = 0
if self.options.base_height == 0:
self.options.add_base_slot = False
if self.options.distance == 0:
self.options.add_middle_slot = False
self.parse_parameters()
# pre-calculate width before adding one to sides, for easier attachment
self.options.width = 2 * self.options.radius * sin(pi / self.options.sides)
self.options.cols = self.options.sides + self.options.extra_column
# get cell definitions
cell_data = self.generate_cell()
# calculate divider if it doesn't exist
if 'divider' not in cell_data:
points = Path.get_points(cell_data['interior'])
x = [p[0] for p in points if p[1] == 0]
cell_data['divider'] = Path([(min(x), 0),
(max(x), 0)], style='m')
points = Path.get_points(cell_data['divider'])
x = [p[0] for p in points]
DX = max(x) - min(x)
# DX = 0
# if 'edge_right' not in cell_data and 'edge_left' not in cell_data:
# cell_data['edge_left'] = []
# for interior in cell_data['interior']:
# points = Path.get_points(interior)
# x = [p[0] for p in points]
# y = [p[1] for p in points]
# top = [p for p in points if p[1] == min(y)]
# top_x = [p[0] for p in top]
# # top_y = [p[1] for p in top]
# bot = [p for p in points if p[1] == max(y)]
# bot_x = [p[0] for p in bot]
# # bot_y = [p[1] for p in bot]
# top_left = [p for p in top if p[0] == min(top_x)][0]
# bot_left = [p for p in bot if p[0] == min(bot_x)][0]
# cell_data['edge_left'].append(Path([top_left, bot_left], 'e'))
if 'edge_right' not in cell_data and 'edge_left' in cell_data:
cell_data['edge_right'] = []
for edge_left in cell_data['edge_left']:
edge_right = edge_left + (DX, 0)
edge_right.invert()
cell_data['edge_right'].append(edge_right)
if 'edge_right' in cell_data and 'edge_left' not in cell_data:
cell_data['edge_left'] = []
for edge_right in cell_data['edge_right']:
edge_left = edge_right + (-DX, 0)
edge_left.invert()
cell_data['edge_left'].append(edge_left)
cell_data['dx'], cell_data['dy'] = self.get_dxdy(cell_data)
# get all slots and vertical dividers between slots
base, middle = self.generate_all_slots(cell_data)
slots = [[base['slots'], middle['slots']]]
# get horizontal dividers between cells
dividers = self.generate_horizontal_dividers(cell_data)
# finish by replicating the actual interior
interior = self.generate_interior(cell_data)
# use slots and cell data to create the full edge paths
self.edge_points = self.generate_fused_edge_points(base, middle, cell_data)
self.path_tree = [dividers, interior]
if len(self.vertex_points) == 0:
self.vertex_points = Path.get_points(self.path_tree)
self.path_tree.append(slots)
def get_dxdy(self, cell_data):
dx = [0]
dy = [0]
for edge in cell_data['edge_left']:
dx.append(edge.points[1][0] - edge.points[0][0])
dy.append(edge.points[1][1] - edge.points[0][1])
return list(accumulate(dx)), list(accumulate(dy))
def generate_interior(self, cell_data):
# retrieve conversion factor for selected unit
unit_factor = self.calc_unit_factor()
rows = self.options.rows
base_height = self.options.base_height * unit_factor
distance = self.options.distance * unit_factor
interiors = []
for i in range(rows):
dx = cell_data['dx'][i]
dy = cell_data['dy'][i] + base_height + i * distance
pattern = cell_data['interior'][i]
interiors.append(Path.list_add(pattern, (dx, dy)))
return interiors
def generate_horizontal_dividers(self, cell_data):
# retrieve conversion factor for selected unit
unit_factor = self.calc_unit_factor()
rows = self.options.rows
base_height = self.options.base_height * unit_factor
distance = self.options.distance * unit_factor
divider = cell_data['divider']
dividers = []
if self.options.add_base_slot:
dividers.append(divider + (0, base_height))
for i in range(1, rows):
dx = cell_data['dx'][i]
dy = cell_data['dy'][i] + base_height + (i - 1) * distance
dividers.append(divider + (dx, dy))
if self.options.add_middle_slot:
dividers.append(divider + (dx, dy + distance))
if self.options.add_base_slot:
dx = cell_data['dx'][-1]
dy = cell_data['dy'][-1] + base_height + (rows - 1) * distance
dividers.append(divider + (dx, dy))
return dividers
# pass
def generate_all_slots(self, cell_data):
dx_ = cell_data['dx']
dy_ = cell_data['dy']
# retrieve conversion factor for selected unit
unit_factor = self.calc_unit_factor()
# retrieve saved parameters, and apply unit factor where needed
cols = self.options.cols
rows = self.options.rows
width = self.options.width * unit_factor
dist = self.options.distance * unit_factor
base_height = self.options.base_height * unit_factor
height = [dy_[i] + base_height + (i - 1) * dist for i in range(rows+1)]
base = {'left': [],
'right': [],
'slots': []}
if self.options.add_base_slot:
base_slot_height = self.options.base_slot_height * unit_factor
base_slot_width = self.options.base_slot_width * unit_factor
base_slot_sizes = [base_slot_height, base_slot_width, base_height, width]
base_slot_top = generate_slot_line(cols, +int(self.options.base_slot_position), *base_slot_sizes)
base_slot_bot = generate_slot_line(cols, -int(self.options.base_slot_position), *base_slot_sizes)
base['slots'] = [base_slot_top, Path.list_add(base_slot_bot, (dx_[-1], height[-1]))]
base['left'] = [Path([(0, 0), (0, base_height)], style = 'e'),
Path([(dx_[-1], height[-1]),
(dx_[-1], height[-1] + base_height)], style = 'e')]
base['right'] = [Path([(dx_[-1] + cols*width, height[-1] + base_height),
(dx_[-1] + cols*width, height[-1] + base_height)], style = 'e'),
Path([(cols*width, base_height),
(cols*width, 0)], style = 'e')]
middle = {'left': [],
'right': [],
'slots': []}
if self.options.add_middle_slot:
middle_slot_height = self.options.middle_slot_height * unit_factor
middle_slot_width = self.options.middle_slot_width * unit_factor
middle_slot_sizes = [middle_slot_height, middle_slot_width, dist, width]
middle_slot = generate_slot_line(cols, +int(self.options.middle_slot_position), *middle_slot_sizes)
middle['slots'] = [Path.list_add(middle_slot,
(dx_[i], height[i]))
for i in range(1, rows)]
middle['left'] = [Path([(0, 0),
(0, dist)], style='e') +
(dx_[i+1], dy_[1] + dist + height[i]) for i in range(rows-1)]
middle['right'] = [
Path([(0, height[rows-2] + base_height + (rows - 1) * dist),
(0, dy_[-2] + base_height + (rows - 2) * dist)], style='e') +
(dx_[-(i+2)] + cols*width, -dy_[i] - i*dist) for i in range(rows - 1)]
return base, middle
def generate_fused_edge_points(self, base, middle, cell_data):
unit_factor = self.calc_unit_factor()
base_height = self.options.base_height * unit_factor
distance = self.options.distance * unit_factor
rows = self.options.rows
edges = []
if self.options.add_base_slot: edges.append(base['left'][0])
for i in range(rows):
cell_left = cell_data['edge_left'][i]
dx = cell_data['dx'][i]
edges.append(cell_left + (dx, cell_data['dy'][i] + base_height + i * distance))
if self.options.add_middle_slot and i < rows - 1:
edges.append(middle['left'][i] + (0, 0))
if self.options.add_base_slot: edges.append(base['left'][1])
if self.options.add_base_slot: edges.append(base['right'][0])
for i in range(rows):
cell_right = cell_data['edge_right'][-(i + 1)]
dx = cell_data['dx'][-(i + 2)]
edges.append(cell_right + (dx, cell_data['dy'][rows - i - 1] + base_height + (rows - i - 1) * distance))
if self.options.add_middle_slot and i < rows - 1:
edges.append(middle['right'][i] + (0, 0))
if self.options.add_base_slot: edges.append(base['right'][1])
return Path.get_points(edges)