From 2c84a21c64dbbe706c6e78fb09b0df15c9563186 Mon Sep 17 00:00:00 2001 From: Mario Voigt Date: Mon, 14 Nov 2022 23:27:13 +0100 Subject: [PATCH] some fixes --- .../colorize_path_lengths.inx | 14 +- .../j_tech_photonics_laser_tool.inx | 72 ++ .../j_tech_photonics_laser_tool.py | 312 +++++++ .../j_tech_photonics_laser_tool/meta.json | 30 + .../origami_patterns/OrigamiPatterns/Bendy.py | 378 +++++++++ .../OrigamiPatterns/Boxes_Masu.py | 98 +++ .../OrigamiPatterns/Boxes_Masu_Traditional.py | 54 ++ .../OrigamiPatterns/Cylindrical.py | 321 +++++++ .../OrigamiPatterns/Cylindrical_Bendy.py | 377 +++++++++ .../OrigamiPatterns/Cylindrical_Kresling.py | 163 ++++ .../OrigamiPatterns/Cylindrical_Template.py | 73 ++ .../origami_patterns/OrigamiPatterns/Hypar.py | 122 +++ .../OrigamiPatterns/Kresling.py | 162 ++++ .../OrigamiPatterns/Kresling_full.py | 92 ++ .../origami_patterns/OrigamiPatterns/Path.py | 787 ++++++++++++++++++ .../OrigamiPatterns/Pattern.py | 378 +++++++++ .../OrigamiPatterns/Pleat_Circular.py | 101 +++ .../OrigamiPatterns/SupportRing.py | 129 +++ .../OrigamiPatterns/Template.py | 113 +++ .../OrigamiPatterns/Waterbomb.py | 112 +++ .../OrigamiPatterns/__init__.py | 0 .../Support_Ring_Belt/Belt.scad | 31 + .../Support_Ring_Belt/Belt_main.scad | 13 + .../Support_Ring_Belt/README.md | 5 + .../fablabchemnitz/origami_patterns/logo.svg | 384 +++++++++ .../fablabchemnitz/origami_patterns/meta.json | 21 + .../origami_patterns_boxes_masu.inx | 69 ++ ...rigami_patterns_boxes_masu_traditional.inx | 65 ++ .../origami_patterns_cylindrical_bendy.inx | 111 +++ .../origami_patterns_cylindrical_kresling.inx | 122 +++ .../origami_patterns_cylindrical_template.inx | 95 +++ .../origami_patterns_misc_support_ring.inx | 53 ++ .../origami_patterns_old_bendy.inx | 107 +++ .../origami_patterns_old_kresling.inx | 95 +++ .../origami_patterns_pleat_circular.inx | 78 ++ .../origami_patterns_pleat_hypar.inx | 82 ++ .../origami_patterns_template.inx | 72 ++ .../origami_patterns_waterbomb.inx | 77 ++ 38 files changed, 5361 insertions(+), 7 deletions(-) create mode 100644 extensions/fablabchemnitz/j_tech_photonics_laser_tool/j_tech_photonics_laser_tool.inx create mode 100644 extensions/fablabchemnitz/j_tech_photonics_laser_tool/j_tech_photonics_laser_tool.py create mode 100644 extensions/fablabchemnitz/j_tech_photonics_laser_tool/meta.json create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Bendy.py create mode 100644 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Boxes_Masu.py create mode 100644 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Boxes_Masu_Traditional.py create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical.py create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical_Bendy.py create mode 100644 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical_Kresling.py create mode 100644 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical_Template.py create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Hypar.py create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Kresling.py create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Kresling_full.py create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Path.py create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Pattern.py create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Pleat_Circular.py create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/SupportRing.py create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Template.py create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Waterbomb.py create mode 100755 extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/__init__.py create mode 100755 extensions/fablabchemnitz/origami_patterns/Support_Ring_Belt/Belt.scad create mode 100755 extensions/fablabchemnitz/origami_patterns/Support_Ring_Belt/Belt_main.scad create mode 100755 extensions/fablabchemnitz/origami_patterns/Support_Ring_Belt/README.md create mode 100755 extensions/fablabchemnitz/origami_patterns/logo.svg create mode 100644 extensions/fablabchemnitz/origami_patterns/meta.json create mode 100644 extensions/fablabchemnitz/origami_patterns/origami_patterns_boxes_masu.inx create mode 100644 extensions/fablabchemnitz/origami_patterns/origami_patterns_boxes_masu_traditional.inx create mode 100755 extensions/fablabchemnitz/origami_patterns/origami_patterns_cylindrical_bendy.inx create mode 100644 extensions/fablabchemnitz/origami_patterns/origami_patterns_cylindrical_kresling.inx create mode 100755 extensions/fablabchemnitz/origami_patterns/origami_patterns_cylindrical_template.inx create mode 100755 extensions/fablabchemnitz/origami_patterns/origami_patterns_misc_support_ring.inx create mode 100755 extensions/fablabchemnitz/origami_patterns/origami_patterns_old_bendy.inx create mode 100755 extensions/fablabchemnitz/origami_patterns/origami_patterns_old_kresling.inx create mode 100755 extensions/fablabchemnitz/origami_patterns/origami_patterns_pleat_circular.inx create mode 100755 extensions/fablabchemnitz/origami_patterns/origami_patterns_pleat_hypar.inx create mode 100755 extensions/fablabchemnitz/origami_patterns/origami_patterns_template.inx create mode 100755 extensions/fablabchemnitz/origami_patterns/origami_patterns_waterbomb.inx diff --git a/extensions/fablabchemnitz/colorize_path_lengths/colorize_path_lengths.inx b/extensions/fablabchemnitz/colorize_path_lengths/colorize_path_lengths.inx index ba3f60b..6b45e1e 100644 --- a/extensions/fablabchemnitz/colorize_path_lengths/colorize_path_lengths.inx +++ b/extensions/fablabchemnitz/colorize_path_lengths/colorize_path_lengths.inx @@ -7,13 +7,13 @@ - 12 - 25 - 40 - 60 - 60 - 0.1 - 10 + 12 + 25 + 40 + 60 + 60 + 0.1 + 10 path diff --git a/extensions/fablabchemnitz/j_tech_photonics_laser_tool/j_tech_photonics_laser_tool.inx b/extensions/fablabchemnitz/j_tech_photonics_laser_tool/j_tech_photonics_laser_tool.inx new file mode 100644 index 0000000..6a77bd0 --- /dev/null +++ b/extensions/fablabchemnitz/j_tech_photonics_laser_tool/j_tech_photonics_laser_tool.inx @@ -0,0 +1,72 @@ + + + J Tech Photonics Laser Tool + fablabchemnitz.de.j_tech_photonics_laser_tool + + + + + + + 3000 + 750 + + 1 + 1 + + -- Choose Output Directory -- + output.gcode + false + true + + + M3 S255; + M5; + 0 + + true + 0.5 + 1.0 + + 0.01 + + + + + + + false + 0 + + false + + true + true + + + + + + + + false + 200 + 200 + + 0 + 0 + 1 + + + + path + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/j_tech_photonics_laser_tool/j_tech_photonics_laser_tool.py b/extensions/fablabchemnitz/j_tech_photonics_laser_tool/j_tech_photonics_laser_tool.py new file mode 100644 index 0000000..8b20c73 --- /dev/null +++ b/extensions/fablabchemnitz/j_tech_photonics_laser_tool/j_tech_photonics_laser_tool.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 + +import os +from lxml import etree +from xml.etree import ElementTree as xml_tree +from inkex import EffectExtension, Boolean + +from svg_to_gcode.svg_parser import parse_root, Transformation, debug_methods +from svg_to_gcode.geometry import LineSegmentChain +from svg_to_gcode.compiler import Compiler, interfaces +from svg_to_gcode import TOLERANCES + +svg_name_space = "http://www.w3.org/2000/svg" +inkscape_name_space = "http://www.inkscape.org/namespaces/inkscape" +sodipodi_name_space = "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + +inx_filename = "j_tech_photonics_laser_tool.inx" + + +def generate_custom_interface(laser_off_command, laser_power_command): + """Wrapper function for generating a Gcode interface with a custom laser power command""" + + class CustomInterface(interfaces.Gcode): + """A Gcode interface with a custom laser power command""" + + def __init__(self): + super().__init__() + + def laser_off(self): + return f"{laser_off_command}" + + def set_laser_power(self, _): + return f"{laser_power_command}" + + return CustomInterface + + +class JTechPhotonicsLaserTool(EffectExtension): + """Inkscape Effect Extension.""" + + def __init__(self): + EffectExtension.__init__(self) + + def effect(self): + """Takes the SVG from Inkscape, generates gcode, returns the SVG after adding debug lines.""" + + root = self.document.getroot() + + # Change svg_to_gcode's approximation tolerance + TOLERANCES["approximation"] = float(self.options.approximation_tolerance.replace(',', '.')) + + try: + assert os.path.isdir(self.options.directory) + except: + self.debug(f"{self.options.directory} is not a directory") + exit(2) + + # Construct output path + if self.options.filename: + filename = self.options.filename + if '.' not in filename: + filename += ".gcode" + elif self.document_path(): + filename, extension = self.document_path().split('.') + filename = filename.split('/')[-1] + '.gcode' + else: + filename = "untitled.gcode" + + output_path = os.path.join(self.options.directory, filename) + + if self.options.filename_suffix: + filename, extension = output_path.split('.') + + n = 1 + while os.path.isfile(output_path): + output_path = filename + str(n) + '.' + extension + n += 1 + + # Load header and footer files + header = [] + if self.options.header_path is not None: + if os.path.isfile(self.options.header_path): + with open(self.options.header_path, 'r') as header_file: + header = header_file.read().splitlines() + elif self.options.header_path != os.getcwd(): # The Inkscape file selector defaults to the working directory + self.debug(f"Header file does not exist at {self.options.header_path}") + exit(2) + + footer = [] + if self.options.footer_path is not None: + if os.path.isfile(self.options.footer_path): + with open(self.options.footer_path, 'r') as footer_file: + footer = footer_file.read().splitlines() + elif self.options.footer_path != os.getcwd(): + self.debug(f"Footer file does not exist at {self.options.footer_path}") + exit(2) + + # Customize header/footer + custom_interface = generate_custom_interface(self.options.tool_off_command, self.options.tool_power_command) + interface_instance = custom_interface() + + if self.options.do_laser_off_start: + header.append(interface_instance.laser_off()) + if self.options.do_laser_off_end: + footer.append(interface_instance.laser_off()) + + header.append(interface_instance.set_movement_speed(self.options.travel_speed)) + if self.options.do_z_axis_start: + header.append(interface_instance.linear_move(z=self.options.z_axis_start)) + if self.options.move_to_origin_end: + footer.append(interface_instance.linear_move(x=0, y=0)) + + # Generate gcode + gcode_compiler = Compiler(custom_interface, self.options.travel_speed, self.options.cutting_speed, + self.options.pass_depth, dwell_time=self.options.dwell_time, custom_header=header, + custom_footer=footer, unit=self.options.unit) + + transformation = Transformation() + + transformation.add_translation(self.options.horizontal_offset, self.options.vertical_offset) + transformation.add_scale(self.options.scaling_factor) + + if self.options.machine_origin == "center": + transformation.add_translation(-self.options.bed_width / 2, self.options.bed_height / 2) + elif self.options.machine_origin == "top-left": + transformation.add_translation(0, self.options.bed_height) + + self.clear_debug() + curves = parse_root(root, transform_origin=not self.options.invert_y_axis, root_transformation=transformation, + canvas_height=self.options.bed_height) + + gcode_compiler.append_curves(curves) + gcode_compiler.compile_to_file(output_path, passes=self.options.passes) + + # Draw debug lines + if self.options.draw_debug: + self.draw_debug_traces(curves) + self.draw_unit_reference() + self.select_non_debug_layer() + + return self.document + + def draw_debug_traces(self, curves): + """Traces arrows over all parsed paths""" + + root = self.document.getroot() + origin = self.options.machine_origin + bed_width = self.options.bed_width + bed_height = self.options.bed_height + + group = etree.Element("{%s}g" % svg_name_space) + group.set("id", "debug_traces") + group.set("{%s}groupmode" % inkscape_name_space, "layer") + group.set("{%s}label" % inkscape_name_space, "debug traces") + + group.append( + etree.fromstring(xml_tree.tostring(debug_methods.arrow_defs(arrow_scale=self.options.debug_arrow_scale)))) + + for curve in curves: + approximation = LineSegmentChain.line_segment_approximation(curve) + + change_origin = Transformation() + + if not self.options.invert_y_axis: + change_origin.add_scale(1, -1) + change_origin.add_translation(0, -bed_height) + + if origin == "center": + change_origin.add_translation(bed_width / 2, bed_height / 2) + + path_string = xml_tree.tostring( + debug_methods.to_svg_path(approximation, color="red", opacity="0.5", + stroke_width=f"{self.options.debug_line_width}px", + transformation=change_origin, draw_arrows=True) + ) + + group.append(etree.fromstring(path_string)) + + root.append(group) + + def draw_unit_reference(self): + """Draws reference points to mark the bed's four corners""" + root = self.document.getroot() + unit = self.options.unit + origin = self.options.machine_origin + bed_width = self.options.bed_width + bed_height = self.options.bed_height + + group = etree.Element("{%s}g" % svg_name_space) + group.set("id", "debug_references") + group.set("{%s}groupmode" % inkscape_name_space, "layer") + group.set("{%s}label" % inkscape_name_space, "debug reference points") + + reference_points_svg = [(0, 0), (0, bed_height), (bed_width, 0), (bed_width, bed_height)] + reference_points_gcode = { + "bottom-left": [(0, bed_height), (0, 0), (bed_width, bed_height), (bed_width, 0)], + "top-left": [(0, 0), (0, bed_height), (bed_width, 0), (bed_width, bed_height)], + "center": [(-bed_width / 2, bed_height / 2), (-bed_width / 2, -bed_height / 2), + (bed_width / 2, bed_height / 2), + (bed_width / 2, -bed_height / 2)] + }[origin] + for i, (x, y) in enumerate(reference_points_svg): + reference_point = etree.Element("{%s}g" % svg_name_space) + + stroke_width = 2 + size = 7 + + x_direction = -1 if x > 0 else 1 + plus_sign = etree.Element("{%s}g" % svg_name_space) + horizontal = etree.Element("{%s}line" % svg_name_space) + horizontal.set("x1", str(x - x_direction * stroke_width / 2)) + horizontal.set("y1", str(y)) + horizontal.set("x2", str(x + x_direction * size)) + horizontal.set("y2", str(y)) + horizontal.set("style", f"stroke:black;stroke-width:{stroke_width}") + plus_sign.append(horizontal) + + y_direction = -1 if y > 0 else 1 + vertical = etree.Element("{%s}line" % svg_name_space) + vertical.set("x1", str(x)) + vertical.set("y1", str(y + stroke_width / 2)) + vertical.set("x2", str(x)) + vertical.set("y2", str(y + y_direction * size)) + vertical.set("style", f"stroke:black;stroke-width:{stroke_width}") + plus_sign.append(vertical) + + reference_point.append(plus_sign) + + text_box = etree.Element("{%s}text" % svg_name_space) + text_box.set("x", str(x - 28)) + text_box.set("y", str(y - (y <= 0) * 6 + (y > 0) * 9)) + text_box.set("font-size", "6") + text_box.text = f"{reference_points_gcode[i][0]}{unit}, {reference_points_gcode[i][1]}{unit}" + reference_point.append(text_box) + + group.append(reference_point) + + root.append(group) + + def select_non_debug_layer(self): + """ + Select content_layer and create one if it doesn't exist. This helps stop the user from accidentally placing new + objects in debug layers. + """ + + root = self.document.getroot() + + unique_id = "layer89324" + content_layer = root.find("{%s}g[@id='%s']" % (svg_name_space, unique_id)) + + if content_layer is None: + content_layer = etree.Element("{%s}g" % svg_name_space) + content_layer.set("id", unique_id) + content_layer.set("{%s}groupmode" % inkscape_name_space, "layer") + content_layer.set("{%s}label" % inkscape_name_space, "content layer") + + sodipodi = root.find("{%s}namedview" % sodipodi_name_space) + if sodipodi is not None: + sodipodi.set("{%s}current-layer" % inkscape_name_space, unique_id) + + root.append(content_layer) + + def clear_debug(self): + """Removes debug groups. Used before parsing paths for gcode.""" + root = self.document.getroot() + + debug_traces = root.find("{%s}g[@id='debug_traces']" % svg_name_space) + debug_references = root.find("{%s}g[@id='debug_references']" % svg_name_space) + + if debug_traces is not None: + root.remove(debug_traces) + + if debug_references is not None: + root.remove(debug_references) + + def add_arguments(self, arg_parser): + """Tell inkscape what arguments to stick in self.options (behind the hood it's more complicated, see docs)""" + arguments = self.read_arguments() + + for arg in arguments: + arg_parser.add_argument("--" + arg["name"], type=arg["type"], dest=arg["name"]) + + @staticmethod + def read_arguments(): + """ + This method reads arguments off of the inx file so you don't have to explicitly declare them in self.add_arguments() + """ + root = etree.parse(inx_filename).getroot() + + arguments = [] # [{name, type, ...}] + namespace = "http://www.inkscape.org/namespace/inkscape/extension" + for arg in root.iter("{%s}param" % namespace): + + name = arg.attrib["name"] + + arg_type = arg.attrib["type"] + + if arg_type in ["description", "notebook"]: + continue + + types = {"int": int, "float": float, "bool": Boolean, "string": str, "optiongroup": str, "path": str} + + arguments.append({"name": name, "type": types[arg_type]}) + + if next(root.iter("{%s}page" % namespace)) is not None: + arguments.append({"name": "tabs", "type": str}) + + return arguments + + +if __name__ == '__main__': + JTechPhotonicsLaserTool().run() diff --git a/extensions/fablabchemnitz/j_tech_photonics_laser_tool/meta.json b/extensions/fablabchemnitz/j_tech_photonics_laser_tool/meta.json new file mode 100644 index 0000000..c309d31 --- /dev/null +++ b/extensions/fablabchemnitz/j_tech_photonics_laser_tool/meta.json @@ -0,0 +1,30 @@ +[ + { + "name": "J Tech Photonics Laser Tool", + "id": "fablabchemnitz.de.j_tech_photonics_laser_tool", + "path": "j_tech_photonics_laser_tool", + "dependent_extensions": null, + "original_name": "J Tech Community Laser Tool", + "original_id": "community.jtechphotonics.com", + "license": "GNU GPL v2", + "license_url": "https://jtechphotonics.com/Downloads/Inkscape/JTP-Laser-Tool_v1.1-beta_ink1.0.zip", + "comment": "", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/j_tech_photonics_laser_tool", + "fork_url": "https://github.com/JTechPhotonics/J-Tech-Photonics-Laser-Tool", + "documentation_url": "https://stadtfabrikanten.org/display/IFM/J+Tech+Photonics+Laser+Tool", + "inkscape_gallery_url": null, + "main_authors": [ + "github.com/JTechPhotonics", + "github.com/PadLex", + "github.com/odaki", + "github.com/themanyone", + "github.com/drewler", + "github.com/nineff", + "github.com/4cello", + "github.com/dapperfu", + "github.com/nmeurer", + "github.com/aspeteRakete", + "github.com/vmario89" + ] + } +] \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Bendy.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Bendy.py new file mode 100755 index 0000000..d954e36 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Bendy.py @@ -0,0 +1,378 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +import numpy as np +from math import pi, sin, cos, tan, asin, acos, atan, sqrt + +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 +MIN = 0.0001 + + +class Bendy_Straw(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('--pattern', type=self.str, default='bendy_straw') + self.add_argument('--pattern_type', type=self.str, default='origami') + self.add_argument('--parameter_type', type=self.str, default='angles') + self.add_argument('--n', type=self.int, default=6) + self.add_argument('--lines', type=self.int, default=3) + self.add_argument('--radius', type=self.float, default=25.0) + # self.add_argument('--attachment_length', type=self.float, default=3.0) + # self.add_argument('--attachment_length', type=self.int, default=20) + self.add_argument('--radial_ratio', type=self.float, default=0.75) + # self.add_argument('--alpha1', type=self.float, default=45.0) + # self.add_argument('--alpha2', type=self.float, default=45.0) + self.add_argument('--alpha1', type=self.int, default=45) + self.add_argument('--alpha2', type=self.int, default=35) + self.add_argument('--h1', type=self.float, default=1) + self.add_argument('--h2', type=self.float, default=2) + + self.add_argument('--vertex_base_outer_bool', type=self.bool, default=False) + self.add_argument('--vertex_base_inner_bool', type=self.bool, default=False) + self.add_argument('--vertex_radius_outer_bool', type=self.bool, default=False) + self.add_argument('--vertex_radius_inner_bool', type=self.bool, default=False) + + self.add_argument('--add_attachment', type=self.bool, default=False) + + # slot options for support ring + self.add_argument('--base_height', type=self.float, default=5.0) + self.add_argument('--add_base_slot', type=self.bool, default=False) + self.add_argument('--center_base_slot', type=self.bool, default=False) + 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('--distance', type=self.float, default=3.0) + self.add_argument('--add_distance_slot', type=self.bool, default=False) + self.add_argument('--distance_slot_height', type=self.float, default=3.0) + self.add_argument('--distance_slot_width', type=self.float, default=3.0) + + def generate_path_tree(self): + """ Specialized path generation for your origami pattern + """ + # retrieve conversion factor for selected unit + unit_factor = self.calc_unit_factor() + vertex_radius = self.options.vertex_radius * unit_factor + + # retrieve saved parameters, and apply unit factor where needed + pattern_type = self.options.pattern_type + n = self.options.n + lines = self.options.lines + radial_ratio = self.options.radial_ratio + R = self.options.radius * unit_factor + distance = self.options.distance * unit_factor + base_height = self.options.base_height * unit_factor + # add_attachment = self.options.add_attachment + # attachment_length = self.options.attachment_length * unit_factor + r = R * radial_ratio + + if (self.options.parameter_type == 'angles'): + alpha1 = self.options.alpha1 * pi / 180 + alpha2 = self.options.alpha2 * pi / 180 + elif (self.options.parameter_type == 'heights'): + alpha1 = atan(self.options.h1 * unit_factor / (R - r)) + alpha2 = atan(self.options.h2 * unit_factor / (R - r)) + + # calculating pattern parameters + l1 = (R - r) / cos(alpha1) + l2 = (R - r) / cos(alpha2) + A = 2 * R * sin(pi / n) + # attachment_length = 0.01 * self.options.attachment_length * A + a = A * radial_ratio + dx = (A - a) / 2 + beta1 = acos(cos(alpha1) * sin(pi / n)) + beta2 = acos(cos(alpha2) * sin(pi / n)) + b1 = l1 * sin(beta1) + b2 = l2 * sin(beta2) + height = (b1 + b2) * lines + distance * (lines - 1) + + if self.options.add_attachment: + n = n+1 + + # + # big horizontal mountains grid + # + mountain_horizontal_stroke = Path([(0, base_height), (A * n, base_height)], 'm') + horizontal_grid_mountain = [] + for i in range(1, lines): + horizontal_grid_mountain.append( + mountain_horizontal_stroke + (0, distance * (i - 1) + b1 * (i + 0) + b2 * (i + 0))) + horizontal_grid_mountain.append( + mountain_horizontal_stroke + (0, distance * (i + 0) + b1 * (i + 0) + b2 * (i + 0))) + if distance < MIN: + horizontal_grid_mountain = horizontal_grid_mountain[::2] + if base_height > MIN: + horizontal_grid_mountain.insert(0, mountain_horizontal_stroke) + horizontal_grid_mountain.append( + mountain_horizontal_stroke + (0, distance * (lines - 1) + b1 * lines + b2 * lines)) + + # reverse every other horizontal stroke for faster laser-cutting + for i in range(len(horizontal_grid_mountain)): + if (i % 2 == 0): + horizontal_grid_mountain[i].points.reverse() + + # + # diamond shapes + # + + # full diamond patterns styles, depending on pattern type + style_diag_left = 'm' + style_diag_right = 'm' + style_diag = 'v' + style_vert = 'm' + style_hori_left = 'm' + style_hori_right = 'm' + if pattern_type == 'origami' or pattern_type == 'origami_bent': + style_hori_left = 'v' + style_diag_left = 'n' + style_diag_right = 'v' + elif pattern_type == 'origami2': + style_hori_right = 'v' + style_diag_left = 'v' + style_diag_right = 'n' + elif pattern_type == 'kirigami1': + style_vert = 'v' + style_hori_left = 'c' + style_hori_right = 'c' + elif pattern_type == 'kirigami2': + style_diag_left = 'c' + style_diag_right = 'c' + style_hori_left = 'n' + style_hori_right = 'n' + style_vert = 'n' + + # diamond pattern with strokes of different styles + stroke_base = Path([(0, 0), (0, base_height)], 'm') + diamond_diagonals_left = Path([(0, base_height), (-dx, base_height + b1), (0, base_height + b1 + b2)], + style_diag_left) + diamond_diagonals_right = Path([(0, base_height + b1 + b2), (dx, base_height + b1), (0, base_height)], + style_diag_right) + diamond_vertical = Path([(0, base_height), (0, base_height + b1 + b2)], style_vert) + stroke_distance = Path([(0, base_height + b1 + b2), (0, distance + base_height + b1 + b2)], 'm') + diamond_horizontal_left = Path([(-dx, 0), (0, 0)], style_hori_left) + diamond_horizontal_right = Path([(0, 0), (dx, 0)], style_hori_right) + + diamond = [diamond_diagonals_left, diamond_diagonals_right, diamond_vertical] + + if pattern_type == 'origami_bent': + bent_diagonal = Path([(0, base_height + b1 + b2), (dx, base_height + b1), (0, base_height)], 'm') + bent_horizontal = Path([(0, 0), (dx, 0)], 'v') + line_bent = [] + + + # drawing lines with the diamond shapes + line_left = [] + line_middle = [] + line_right = [] + if base_height > MIN: + line_middle.append(stroke_base) + if pattern_type == 'origami_bent': + line_bent.append(stroke_base) + for i in range(lines): + delta = (0, (distance + b1 + b2) * i) + if pattern_type != 'kirigami2': + line_left.append(diamond_diagonals_right + delta) + line_middle = line_middle + Path.list_add(diamond, delta) + if pattern_type != 'kirigami2': + line_right.append(diamond_diagonals_left + delta) + if distance > MIN and i < lines - 1: + line_middle.append(stroke_distance + delta) + + if pattern_type == 'origami_bent': + line_bent = line_bent + [bent_diagonal + delta] + if distance > MIN and i < lines - 1: + line_bent.append(stroke_distance + delta) + + if base_height > MIN: + line_middle.append(stroke_base + (0, base_height + height)) + if pattern_type == 'origami_bent': + line_bent.append(stroke_base + (0, base_height + height)) + + # creating full diamond patterns + line_left = line_left[::-1] + diamond_patterns_full = [line_left] + for i in range(n - 1): + delta = (A * (i + 1), 0) + if pattern_type == 'origami_bent' and i == 2: + diamond_patterns_full.append(Path.list_add(line_bent, delta)) + else: + diamond_patterns_full.append(Path.list_add(line_middle, delta)) + diamond_patterns_full.append(Path.list_add(line_right, (A * n, 0))) + + # + # small horizontal alternate style grid + # + valley_points = [(dx, 0), + (dx + a, 0)] + valley_stroke = Path(valley_points, 'v') + if pattern_type == 'kirigami2': + horizontal_line = [] + else: + horizontal_line = [diamond_horizontal_right + (0, 0)] + for i in range(n): + if not (pattern_type == 'origami_bent' and i == 3): + horizontal_line.append(valley_stroke + ((a + 2 * dx) * i, 0)) + if (pattern_type != 'kirigami2'): + if not (pattern_type == 'origami_bent' and i == 3): + horizontal_line.append(diamond_horizontal_left + (A + (a + 2 * dx) * i, 0)) + if pattern_type == 'origami_bent' and i==2: + horizontal_line.append(bent_horizontal + (A + (a + 2 * dx) * i, 0)) + elif i < n - 1: + horizontal_line.append(diamond_horizontal_right + (A + (a + 2 * dx) * i, 0)) + horizontal_grid_alternate = [] + for i in range(lines): + horizontal_grid_alternate.append( + Path.list_add(horizontal_line, (0, base_height + distance * (i + 0) + b1 * (i + 1) + b2 * (i + 0)))) + + # reverse every other horizontal stroke for faster laser-cutting + for i in range(len(horizontal_grid_alternate)): + if (i % 2 == 0): + horizontal_grid_alternate[i] = Path.list_invert(horizontal_grid_alternate[i]) + + # for i in range(len(horizontal_grid_alternate)): + # inkex.debug(i) + # Path.debug_points(horizontal_grid_alternate[i]) + # inkex.debug('\n') + + # + # edge drawing + # + self.edge_points = [(0, 0)] + self.edge_points.append((A * n, 0)) + + # rectangles for attachments at base and between cells + + # add upper base attachment + self.edge_points.append((A * n, base_height)) + + # draw attachment between cells and inside cells + for i in range(lines): + self.edge_points.append((A * n + 0, base_height + (b1 + b2) * (i + 0) + distance * i)) + if pattern_type == 'kirigami2': + self.edge_points.append((A * n - dx, base_height + b1 * (i + 1) + (b2 + distance) * i)) + self.edge_points.append((A * n + 0, base_height + (b1 + b2) * (i + 1) + distance * i)) + + self.edge_points.append((A * n, height + 2 * base_height)) + self.edge_points.append((0, height + 2 * base_height)) + + # if full kirigami selected, cut left side next to cells + if pattern_type == 'kirigami2': + for i in range(lines): + self.edge_points.append((0, height + base_height - (b1 + b2) * (i + 0) - distance * i)) + self.edge_points.append((dx, height + base_height - b2 * (i + 1) - (b1 + distance) * i)) + self.edge_points.append((0, height + base_height - (b1 + b2) * (i + 1) - distance * i)) + + # + # slots drawing + # + center_slot = self.options.center_base_slot + base_slots = [] + if self.options.add_base_slot: + base_slot_height = self.options.base_slot_height + base_slot_width = self.options.base_slot_width + if base_slot_height > base_height or base_slot_width > A: + inkex.debug('Base slot dimensions are too big') + base_slot_height = min(base_height, base_slot_height) + base_slot_width = min(A, base_slot_width) + if base_slot_height > 0 and base_slot_width > 0: + points = [(0, 0), + (0, base_slot_height), + (base_slot_width, base_slot_height), + (base_slot_width, 0,)] + base_slot = Path(points, 'c', closed=True) + ((A - base_slot_width)/2, (base_height - base_slot_height)/(1+center_slot)) + base_slots_line = [] + for i in range(n): + base_slots_line.append(base_slot + (A*i, 0)) + base_slots = [base_slots_line] + base_slots.append(Path.list_add(base_slots_line, (0, height+base_slot_height + (base_height - base_slot_height)*center_slot))) + + dist_slots = [] + if self.options.add_distance_slot: + dist_slot_height = self.options.distance_slot_height + dist_slot_width = self.options.distance_slot_width + if dist_slot_height > distance or dist_slot_width > A: + inkex.debug('Dimensions of slots between cells are too big') + dist_slot_height = min(distance, dist_slot_height) + dist_slot_width = min(A, dist_slot_width) + + if dist_slot_height > 0 and dist_slot_width > 0: + points = [(0, 0), + (0, dist_slot_height), + (dist_slot_width, dist_slot_height), + (dist_slot_width, 0,)] + dist_slot = Path(points, 'c', closed=True) + ((A - dist_slot_width)/2, base_height+b1+b2 + (distance - dist_slot_height)/2) + dist_slots_line = [] + for i in range(n): + dist_slots_line.append(dist_slot + (A*i, 0)) + + for i in range(lines-1): + dist_slots.append(Path.list_add(dist_slots_line, (0, i*(b1+b2+distance)))) + + + + # sending lines to draw + self.path_tree = [horizontal_grid_mountain, horizontal_grid_alternate, diamond_patterns_full, base_slots, dist_slots] + + # + # vertices drawing + # + # outer base vertices? + if self.options.vertex_base_outer_bool: + self.vertex_points = self.vertex_points + [(A * i, height + base_height * 2) for i in range(n + 1)] + self.vertex_points = self.vertex_points + [(A*i, 0) for i in range(n+1)] + + # inner base vertices? + if self.options.vertex_base_inner_bool: + self.vertex_points = self.vertex_points + [(A*i, base_height) for i in range(n+1)] + self.vertex_points = self.vertex_points + [(A*i, height+base_height) for i in range(n+1)] + for j in range(lines-1): + self.vertex_points = self.vertex_points + [(A*i, base_height+((b1+b2)*(j+1))+distance*j) for i in range(n+1)] + \ + [(A*i, base_height+((b1+b2)*(j+1))+distance*(j+1)) for i in range(n+1)] + + # radius vertices? + if self.options.vertex_radius_outer_bool and pattern_type != 'kirigami2': + for j in range(lines): + i_range = list(range(3)) + list(range(3+(pattern_type=='origami_bent'), n + 1)) + self.vertex_points = self.vertex_points + [(A * i, base_height + b1*(j+1) + (b2+distance)*j) for i in i_range] + if self.options.vertex_radius_inner_bool: + for j in range(lines): + if pattern_type != 'origami2': + # pass + self.vertex_points = self.vertex_points + [(dx + A * i, base_height + b1*(j+1) + (b2+distance)*j) for i in range(n)] + if pattern_type[:7] != 'origami': + # pass + self.vertex_points = self.vertex_points + [(-dx + A * (i + 1), base_height + b1 * (j + 1) + (b2 + distance) * j) for i in range(n)] + # for j in range(lines): + + + + + # self.vertex_points = self.vertex_points + vertices_base + + # self.vertex_points = self.vertex_points + self.edge_points + # diamond_patterns_full_simple = Path.list_simplify(diamond_patterns_full) + # for path in diamond_patterns_full: + # # path = Path.list_simplify(path) + # # path = Path.list_simplify(path[0]) + # if path.style != 'n': + # self.vertex_points = self.vertex_points + path.points + + + +# + +# Main function, creates an instance of the Class and calls self.draw() to draw the origami on inkscape +# self.draw() is either a call to inkex.affect() or to svg.run(), depending on python version +if __name__ == '__main__': + e = Bendy_Straw() # remember to put the name of your Class here! + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Boxes_Masu.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Boxes_Masu.py new file mode 100644 index 0000000..25c33e7 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Boxes_Masu.py @@ -0,0 +1,98 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +import math +import numpy as np +from math import pi + +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 reflections_diag(path): + new_paths = [path] + new_paths = new_paths + Path.list_reflect(new_paths, (0, 0), (1, 1)) + return new_paths + Path.list_reflect(new_paths, (0, 0), (1, -1)) + +def reflections_rect(path): + new_paths = [path] + new_paths = new_paths + Path.list_reflect(new_paths, (0, 0), (0, 1)) + return new_paths + Path.list_reflect(new_paths, (0, 0), (1, 0)) + +def recenter(paths, dist): + # paths_new = Path.list_simplify(paths) + paths_new = Path.list_rotate(paths, pi/4) + return Path.list_add(paths_new, (dist, dist)) + +class MasuBox(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('--pattern', type=self.str, default='boxes_masu') + self.add_argument('--width', type=self.float, default=10.0) + self.add_argument('--height', type=self.float, default=10.0) + self.add_argument('--width_delta', type=self.float, default=0.0) + self.add_argument('--width_delta_bool', type=self.bool, default=False) + + def generate_path_tree(self): + """ Specialized path generation for your origami pattern + """ + # retrieve conversion factor for selected unit + unit_factor = self.calc_unit_factor() + + # retrieve saved parameters, and apply unit factor where needed + width = self.options.width * unit_factor + height = self.options.height * unit_factor + if self.options.width_delta_bool: + width_delta = self.options.width_delta * unit_factor + width += width_delta + height -= width_delta/2 + length = math.sqrt(2)*(width + 2*height) + half_fold = 90 if self.options.simulation_mode else 180 + + # bottom of box + ring_inner = Path.generate_square(width, width, center = [0,0], style = 'm', fold_angle=half_fold) + + # ring making the corners + lengths = [height, width, height] + points = Path.get_square_points(sum(lengths), sum(lengths), [0,0]) + styles = ['vmv','mmm'] + ring_middle = [Path([points[i], points[(i + 1)%4]], 'm'). + break_path(lengths, styles[i % 2]) for i in range(4)] + + # arms along width and length + points = [(-width / 2, -(width / 2 + 0 * height)), + (-width / 2, -(width / 2 + 1 * height)), + (-width / 2, -(width / 2 + 2 * height)), + (+width / 2, -(width / 2 + 2 * height)), + (+width / 2, -(width / 2 + 1 * height)), + (+width / 2, -(width / 2 + 0 * height))] + + arms_ = [Path.list_create_from_points(points, 'mmvmm', fold_angles = [180, 180, half_fold, 180, 180]), + Path.list_create_from_points(points, 'mvvvm', half_fold)] + arms = [Path.list_rotate(arms_[i % 2], i * pi / 2) for i in range(4)] + + # tiny corner diagonals + diag = Path([(width/2, width/2), (width/2 + height, width/2 + height)], 'v') + corner_diagonals = [diag * (1, i*pi/2) for i in range(4)] + + self.edge_points = Path.get_square_points(length, length) + + self.path_tree = [ring_inner, ring_middle, arms, corner_diagonals] + self.path_tree = recenter(self.path_tree, length/2) + +# Main function, creates an instance of the Class and calls self.draw() to draw the origami on inkscape +# self.draw() is either a call to inkex.affect() or to svg.run(), depending on python version +if __name__ == '__main__': + e = MasuBox() # remember to put the name of your Class here! + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Boxes_Masu_Traditional.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Boxes_Masu_Traditional.py new file mode 100644 index 0000000..e4f1d28 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Boxes_Masu_Traditional.py @@ -0,0 +1,54 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +import numpy as np +from math import pi + +import inkex +import math +from Path import Path +from Boxes_Masu import MasuBox +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 reflections_diag(path): + new_paths = [path] + new_paths = new_paths + Path.list_reflect(new_paths, (0, 0), (1, 1)) + return new_paths + Path.list_reflect(new_paths, (0, 0), (1, -1)) + +def reflections_rect(path): + new_paths = [path] + new_paths = new_paths + Path.list_reflect(new_paths, (0, 0), (0, 1)) + return new_paths + Path.list_reflect(new_paths, (0, 0), (1, 0)) + + +class MasuBoxSquare(MasuBox): + + def __init__(self): + """ Constructor + """ + MasuBox.__init__(self) # Must be called in order to parse common options + + # save all custom parameters defined on .inx file + self.add_argument('--length', type=self.float, default=10.0) + + def generate_path_tree(self): + """ Specialized path generation for your origami pattern + """ + # retrieve saved parameters, and apply unit factor where needed + self.options.width = self.options.length / (2 * math.sqrt(2)) + self.options.height = self.options.width/2 + self.options.width_delta = 0.0 + self.options.width_delta_bool = False + MasuBox.generate_path_tree(self) + + +# Main function, creates an instance of the Class and calls self.draw() to draw the origami on inkscape +# self.draw() is either a call to inkex.affect() or to svg.run(), depending on python version +if __name__ == '__main__': + e = MasuBoxSquare() # remember to put the name of your Class here! + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical.py new file mode 100755 index 0000000..d308b95 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical.py @@ -0,0 +1,321 @@ +#! /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) + diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical_Bendy.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical_Bendy.py new file mode 100755 index 0000000..3355f50 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical_Bendy.py @@ -0,0 +1,377 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +import numpy as np +from math import pi, sin, cos, tan, asin, acos, atan, sqrt + +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 +MIN = 0.0001 + + +class Bendy_Straw(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('--pattern', type=self.str, default='bendy_straw') + self.add_argument('--pattern_type', type=self.str, default='origami') + self.add_argument('--parameter_type', type=self.str, default='angles') + self.add_argument('--n', type=self.int, default=6) + self.add_argument('--lines', type=self.int, default=3) + self.add_argument('--radius', type=self.float, default=25.0) + # self.add_argument('--attachment_length', type=self.float, default=3.0) + # self.add_argument('--attachment_length', type=self.int, default=20) + self.add_argument('--radial_ratio', type=self.float, default=0.75) + # self.add_argument('--alpha1', type=self.float, default=45.0) + # self.add_argument('--alpha2', type=self.float, default=45.0) + self.add_argument('--alpha1', type=self.int, default=45) + self.add_argument('--alpha2', type=self.int, default=35) + self.add_argument('--h1', type=self.float, default=1) + self.add_argument('--h2', type=self.float, default=2) + + self.add_argument('--vertex_base_outer_bool', type=self.bool, default=False) + self.add_argument('--vertex_base_inner_bool', type=self.bool, default=False) + self.add_argument('--vertex_radius_outer_bool', type=self.bool, default=False) + self.add_argument('--vertex_radius_inner_bool', type=self.bool, default=False) + + self.add_argument('--add_attachment', type=self.bool, default=False) + + # slot options for support ring + self.add_argument('--base_height', type=self.float, default=5.0) + self.add_argument('--add_base_slot', type=self.bool, default=False) + self.add_argument('--center_base_slot', type=self.bool, default=False) + 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('--distance', type=self.float, default=3.0) + self.add_argument('--add_distance_slot', type=self.bool, default=False) + self.add_argument('--distance_slot_height', type=self.float, default=3.0) + self.add_argument('--distance_slot_width', type=self.float, default=3.0) + + def generate_path_tree(self): + """ Specialized path generation for your origami pattern + """ + # retrieve conversion factor for selected unit + unit_factor = self.calc_unit_factor() + vertex_radius = self.options.vertex_radius * unit_factor + + # retrieve saved parameters, and apply unit factor where needed + pattern_type = self.options.pattern_type + n = self.options.n + lines = self.options.lines + radial_ratio = self.options.radial_ratio + R = self.options.radius * unit_factor + distance = self.options.distance * unit_factor + base_height = self.options.base_height * unit_factor + # add_attachment = self.options.add_attachment + # attachment_length = self.options.attachment_length * unit_factor + r = R * radial_ratio + + if (self.options.parameter_type == 'angles'): + alpha1 = self.options.alpha1 * pi / 180 + alpha2 = self.options.alpha2 * pi / 180 + elif (self.options.parameter_type == 'heights'): + alpha1 = atan(self.options.h1 * unit_factor / (R - r)) + alpha2 = atan(self.options.h2 * unit_factor / (R - r)) + + # calculating pattern parameters + l1 = (R - r) / cos(alpha1) + l2 = (R - r) / cos(alpha2) + A = 2 * R * sin(pi / n) + # attachment_length = 0.01 * self.options.attachment_length * A + a = A * radial_ratio + dx = (A - a) / 2 + beta1 = acos(cos(alpha1) * sin(pi / n)) + beta2 = acos(cos(alpha2) * sin(pi / n)) + b1 = l1 * sin(beta1) + b2 = l2 * sin(beta2) + height = (b1 + b2) * lines + distance * (lines - 1) + + if self.options.add_attachment: n = n+1 + + # + # big horizontal mountains grid + # + mountain_horizontal_stroke = Path([(0, base_height), (A * n, base_height)], 'm') + horizontal_grid_mountain = [] + for i in range(1, lines): + horizontal_grid_mountain.append( + mountain_horizontal_stroke + (0, distance * (i - 1) + b1 * (i + 0) + b2 * (i + 0))) + horizontal_grid_mountain.append( + mountain_horizontal_stroke + (0, distance * (i + 0) + b1 * (i + 0) + b2 * (i + 0))) + if distance < MIN: + horizontal_grid_mountain = horizontal_grid_mountain[::2] + if base_height > MIN: + horizontal_grid_mountain.insert(0, mountain_horizontal_stroke) + horizontal_grid_mountain.append( + mountain_horizontal_stroke + (0, distance * (lines - 1) + b1 * lines + b2 * lines)) + + # reverse every other horizontal stroke for faster laser-cutting + for i in range(len(horizontal_grid_mountain)): + if (i % 2 == 0): + horizontal_grid_mountain[i].points.reverse() + + # + # diamond shapes + # + + # full diamond patterns styles, depending on pattern type + style_diag_left = 'm' + style_diag_right = 'm' + style_diag = 'v' + style_vert = 'm' + style_hori_left = 'm' + style_hori_right = 'm' + if pattern_type == 'origami' or pattern_type == 'origami_bent': + style_hori_left = 'v' + style_diag_left = 'n' + style_diag_right = 'v' + elif pattern_type == 'origami2': + style_hori_right = 'v' + style_diag_left = 'v' + style_diag_right = 'n' + elif pattern_type == 'kirigami1': + style_vert = 'v' + style_hori_left = 'c' + style_hori_right = 'c' + elif pattern_type == 'kirigami2': + style_diag_left = 'c' + style_diag_right = 'c' + style_hori_left = 'n' + style_hori_right = 'n' + style_vert = 'n' + + # diamond pattern with strokes of different styles + stroke_base = Path([(0, 0), (0, base_height)], 'm') + diamond_diagonals_left = Path([(0, base_height), (-dx, base_height + b1), (0, base_height + b1 + b2)], + style_diag_left) + diamond_diagonals_right = Path([(0, base_height + b1 + b2), (dx, base_height + b1), (0, base_height)], + style_diag_right) + diamond_vertical = Path([(0, base_height), (0, base_height + b1 + b2)], style_vert) + stroke_distance = Path([(0, base_height + b1 + b2), (0, distance + base_height + b1 + b2)], 'm') + diamond_horizontal_left = Path([(-dx, 0), (0, 0)], style_hori_left) + diamond_horizontal_right = Path([(0, 0), (dx, 0)], style_hori_right) + + diamond = [diamond_diagonals_left, diamond_diagonals_right, diamond_vertical] + + if pattern_type == 'origami_bent': + bent_diagonal = Path([(0, base_height + b1 + b2), (dx, base_height + b1), (0, base_height)], 'm') + bent_horizontal = Path([(0, 0), (dx, 0)], 'v') + line_bent = [] + + + # drawing lines with the diamond shapes + line_left = [] + line_middle = [] + line_right = [] + if base_height > MIN: + line_middle.append(stroke_base) + if pattern_type == 'origami_bent': + line_bent.append(stroke_base) + for i in range(lines): + delta = (0, (distance + b1 + b2) * i) + if pattern_type != 'kirigami2': + line_left.append(diamond_diagonals_right + delta) + line_middle = line_middle + Path.list_add(diamond, delta) + if pattern_type != 'kirigami2': + line_right.append(diamond_diagonals_left + delta) + if distance > MIN and i < lines - 1: + line_middle.append(stroke_distance + delta) + + if pattern_type == 'origami_bent': + line_bent = line_bent + [bent_diagonal + delta] + if distance > MIN and i < lines - 1: + line_bent.append(stroke_distance + delta) + + if base_height > MIN: + line_middle.append(stroke_base + (0, base_height + height)) + if pattern_type == 'origami_bent': + line_bent.append(stroke_base + (0, base_height + height)) + + # creating full diamond patterns + line_left = line_left[::-1] + diamond_patterns_full = [line_left] + for i in range(n - 1): + delta = (A * (i + 1), 0) + if pattern_type == 'origami_bent' and i == 2: + diamond_patterns_full.append(Path.list_add(line_bent, delta)) + else: + diamond_patterns_full.append(Path.list_add(line_middle, delta)) + diamond_patterns_full.append(Path.list_add(line_right, (A * n, 0))) + + # + # small horizontal alternate style grid + # + valley_points = [(dx, 0), + (dx + a, 0)] + valley_stroke = Path(valley_points, 'v') + if pattern_type == 'kirigami2': + horizontal_line = [] + else: + horizontal_line = [diamond_horizontal_right + (0, 0)] + for i in range(n): + if not (pattern_type == 'origami_bent' and i == 3): + horizontal_line.append(valley_stroke + ((a + 2 * dx) * i, 0)) + if (pattern_type != 'kirigami2'): + if not (pattern_type == 'origami_bent' and i == 3): + horizontal_line.append(diamond_horizontal_left + (A + (a + 2 * dx) * i, 0)) + if pattern_type == 'origami_bent' and i==2: + horizontal_line.append(bent_horizontal + (A + (a + 2 * dx) * i, 0)) + elif i < n - 1: + horizontal_line.append(diamond_horizontal_right + (A + (a + 2 * dx) * i, 0)) + horizontal_grid_alternate = [] + for i in range(lines): + horizontal_grid_alternate.append( + Path.list_add(horizontal_line, (0, base_height + distance * (i + 0) + b1 * (i + 1) + b2 * (i + 0)))) + + # reverse every other horizontal stroke for faster laser-cutting + for i in range(len(horizontal_grid_alternate)): + if (i % 2 == 0): + horizontal_grid_alternate[i] = Path.list_invert(horizontal_grid_alternate[i]) + + # for i in range(len(horizontal_grid_alternate)): + # inkex.debug(i) + # Path.debug_points(horizontal_grid_alternate[i]) + # inkex.debug('\n') + + # + # edge drawing + # + self.edge_points = [(0, 0)] + self.edge_points.append((A * n, 0)) + + # rectangles for attachments at base and between cells + + # add upper base attachment + self.edge_points.append((A * n, base_height)) + + # draw attachment between cells and inside cells + for i in range(lines): + self.edge_points.append((A * n + 0, base_height + (b1 + b2) * (i + 0) + distance * i)) + if pattern_type == 'kirigami2': + self.edge_points.append((A * n - dx, base_height + b1 * (i + 1) + (b2 + distance) * i)) + self.edge_points.append((A * n + 0, base_height + (b1 + b2) * (i + 1) + distance * i)) + + self.edge_points.append((A * n, height + 2 * base_height)) + self.edge_points.append((0, height + 2 * base_height)) + + # if full kirigami selected, cut left side next to cells + if pattern_type == 'kirigami2': + for i in range(lines): + self.edge_points.append((0, height + base_height - (b1 + b2) * (i + 0) - distance * i)) + self.edge_points.append((dx, height + base_height - b2 * (i + 1) - (b1 + distance) * i)) + self.edge_points.append((0, height + base_height - (b1 + b2) * (i + 1) - distance * i)) + + # + # slots drawing + # + center_slot = self.options.center_base_slot + base_slots = [] + if self.options.add_base_slot: + base_slot_height = self.options.base_slot_height + base_slot_width = self.options.base_slot_width + if base_slot_height > base_height or base_slot_width > A: + inkex.debug('Base slot dimensions are too big') + base_slot_height = min(base_height, base_slot_height) + base_slot_width = min(A, base_slot_width) + if base_slot_height > 0 and base_slot_width > 0: + points = [(0, 0), + (0, base_slot_height), + (base_slot_width, base_slot_height), + (base_slot_width, 0,)] + base_slot = Path(points, 'c', closed=True) + ((A - base_slot_width)/2, (base_height - base_slot_height)/(1+center_slot)) + base_slots_line = [] + for i in range(n): + base_slots_line.append(base_slot + (A*i, 0)) + base_slots = [base_slots_line] + base_slots.append(Path.list_add(base_slots_line, (0, height+base_slot_height + (base_height - base_slot_height)*center_slot))) + + dist_slots = [] + if self.options.add_distance_slot: + dist_slot_height = self.options.distance_slot_height + dist_slot_width = self.options.distance_slot_width + if dist_slot_height > distance or dist_slot_width > A: + inkex.debug('Dimensions of slots between cells are too big') + dist_slot_height = min(distance, dist_slot_height) + dist_slot_width = min(A, dist_slot_width) + + if dist_slot_height > 0 and dist_slot_width > 0: + points = [(0, 0), + (0, dist_slot_height), + (dist_slot_width, dist_slot_height), + (dist_slot_width, 0,)] + dist_slot = Path(points, 'c', closed=True) + ((A - dist_slot_width)/2, base_height+b1+b2 + (distance - dist_slot_height)/2) + dist_slots_line = [] + for i in range(n): + dist_slots_line.append(dist_slot + (A*i, 0)) + + for i in range(lines-1): + dist_slots.append(Path.list_add(dist_slots_line, (0, i*(b1+b2+distance)))) + + + + # sending lines to draw + self.path_tree = [horizontal_grid_mountain, horizontal_grid_alternate, diamond_patterns_full, base_slots, dist_slots] + + # + # vertices drawing + # + # outer base vertices? + if self.options.vertex_base_outer_bool: + self.vertex_points = self.vertex_points + [(A * i, height + base_height * 2) for i in range(n + 1)] + self.vertex_points = self.vertex_points + [(A*i, 0) for i in range(n+1)] + + # inner base vertices? + if self.options.vertex_base_inner_bool: + self.vertex_points = self.vertex_points + [(A*i, base_height) for i in range(n+1)] + self.vertex_points = self.vertex_points + [(A*i, height+base_height) for i in range(n+1)] + for j in range(lines-1): + self.vertex_points = self.vertex_points + [(A*i, base_height+((b1+b2)*(j+1))+distance*j) for i in range(n+1)] + \ + [(A*i, base_height+((b1+b2)*(j+1))+distance*(j+1)) for i in range(n+1)] + + # radius vertices? + if self.options.vertex_radius_outer_bool and pattern_type != 'kirigami2': + for j in range(lines): + i_range = list(range(3)) + list(range(3+(pattern_type=='origami_bent'), n + 1)) + self.vertex_points = self.vertex_points + [(A * i, base_height + b1*(j+1) + (b2+distance)*j) for i in i_range] + if self.options.vertex_radius_inner_bool: + for j in range(lines): + if pattern_type != 'origami2': + # pass + self.vertex_points = self.vertex_points + [(dx + A * i, base_height + b1*(j+1) + (b2+distance)*j) for i in range(n)] + if pattern_type[:7] != 'origami': + # pass + self.vertex_points = self.vertex_points + [(-dx + A * (i + 1), base_height + b1 * (j + 1) + (b2 + distance) * j) for i in range(n)] + # for j in range(lines): + + + + + # self.vertex_points = self.vertex_points + vertices_base + + # self.vertex_points = self.vertex_points + self.edge_points + # diamond_patterns_full_simple = Path.list_simplify(diamond_patterns_full) + # for path in diamond_patterns_full: + # # path = Path.list_simplify(path) + # # path = Path.list_simplify(path[0]) + # if path.style != 'n': + # self.vertex_points = self.vertex_points + path.points + + + +# + +# Main function, creates an instance of the Class and calls self.draw() to draw the origami on inkscape +# self.draw() is either a call to inkex.affect() or to svg.run(), depending on python version +if __name__ == '__main__': + e = Bendy_Straw() # remember to put the name of your Class here! + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical_Kresling.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical_Kresling.py new file mode 100644 index 0000000..c407327 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical_Kresling.py @@ -0,0 +1,163 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +from math import pi, sin, asin, cos, tan, acos, sqrt +import inkex +import os + +from Path import Path +from Pattern import Pattern +from Cylindrical import Cylindrical + + +class Kresling(Cylindrical): + + def __init__(self): + """ Constructor + """ + Cylindrical.__init__(self) # Must be called in order to parse common options + + self.add_argument('--pattern', type=self.str, default="kresling") + + self.add_argument('--measure_value', type=self.float, default=10.0) + self.add_argument('--measure_type', type=self.str, default=60) + self.add_argument('--parameter_type', type=self.str, default=60) + self.add_argument('--radial_ratio', type=self.float, default=0.5) + self.add_argument('--angle_ratio', type=self.float, default=0.5) + self.add_argument('--lambdatheta', type=self.float, default=45) + + def parse_parameters(self): + n = self.options.sides + theta = pi * (n - 2) / (2 * n) + # define ratio parameter + parameter = self.options.parameter_type + if parameter == 'radial_ratio': + radial_ratio = self.options.radial_ratio + max_radial_ratio = sin((pi / 4) * (1. - 2. / n)) + if radial_ratio > max_radial_ratio: + inkex.errormsg( + _("For polygon of {} sides, the maximal radial ratio is = {}".format(n, max_radial_ratio))) + radial_ratio = max_radial_ratio + self.options.angle_ratio = 1 - 2 * n * asin(radial_ratio) / ((n - 2) * pi) + + elif parameter == 'lambdatheta': + lambdatheta = self.options.lambdatheta + angle_min = 45. * (1 - 2. / n) + angle_max = 2 * angle_min + if lambdatheta < angle_min: + inkex.errormsg(_( + "For polygon of {} sides, phi must be between {} and {} degrees, \nsetting lambda*theta = {}\n".format( + n, angle_min, angle_max, angle_min))) + lambdatheta = angle_min + elif lambdatheta > angle_max: + inkex.errormsg(_( + "For polygon of {} sides, phi must be between {} and {} degrees, \nsetting lambda*theta = {}\n".format( + n, angle_min, angle_max, angle_max))) + lambdatheta = angle_max + self.options.angle_ratio = lambdatheta * n / (90. * (n - 2.)) + + # define some length + mtype = self.options.measure_type + mvalue = self.options.measure_value + angle_ratio = self.options.angle_ratio + if mtype == 'a': + radius = 0.5 * mvalue / (sin(pi / n)) + if mtype == 'b': + A = cos(theta * (1 - angle_ratio)) + B = sin(pi / n) + C = cos(theta * angle_ratio) + radius = 0.5 * mvalue / sqrt(A ** 2 + B ** 2 - 2 * A * B * C) + elif mtype == 'l': + radius = 0.5 * mvalue / cos(theta * (1 - angle_ratio)) + elif mtype == 'radius_external': + radius = mvalue + elif mtype == 'radius_internal': + radius = mvalue / (sin(theta * (1 - angle_ratio))) + elif mtype == 'diameter_external': + radius = 0.5 * mvalue + elif mtype == 'diameter_internal': + radius = 0.5 * mvalue / sin(theta * (1 - angle_ratio)) + + if self.options.pattern == 'mirrowed': + self.options.mirror_cells = True + else: + self.options.mirror_cells = False + self.options.radius = radius + + def generate_cell(self): + """ Generate the the origami cell + """ + # retrieve conversion factor for selected unit + unit_factor = self.calc_unit_factor() + rows = self.options.rows + sides = self.options.sides + cols = self.options.cols + radius = self.options.radius * unit_factor + width = self.options.width * unit_factor + # vertex_radius = self.options.vertex_radius * unit_factor + + angle_ratio = self.options.angle_ratio + mirror_cells = self.options.mirror_cells + + theta = (pi/2.)*(1 - 2./sides) + l = 2.*radius*cos(theta*(1.-angle_ratio)) + dy = l * sin(theta * angle_ratio) + dx = l * cos(theta * angle_ratio) - width + + # init dict that holds everything + cell_data = {} + + # divider (supposed to be the same) + cell_data['divider'] = Path([(0, 0), (width * cols, 0)], style='m') + + # IMPORTANT: left edges from TOP to BOTTOM + edge_left = [Path([(0, 0), (dx, dy)], style='e')] + if mirror_cells: + edge_left.append(Path([(0, 0), (-dx, dy)], style='e')) + cell_data['edge_left'] = [edge_left[i % (1 + mirror_cells)] for i in range(rows)] + + # IMPORTANT: right edges from BOTTOM to TOP + edge_right = [Path([(cols * width + dx, dy), (cols * width, 0)], style='e')] + if mirror_cells: + edge_right.append(Path([(cols * width - dx, dy), (cols * width, 0)], style='e')) + cell_data['edge_right'] = [edge_right[i % (1 + mirror_cells)] for i in range(rows)] + + # rest of cell + zigzags = [Kresling.generate_kresling_zigzag(sides, cols, radius, angle_ratio)] + if mirror_cells: + zigzags.append(Path.list_reflect(zigzags[0], (0, dy / 2), (dx, dy / 2))) + zigzags[1] = Path.list_add(zigzags[1], (-dx, 0)) + + cell_data['interior'] = [zigzags[i % (1 + mirror_cells)] for i in range(rows)] + + return cell_data + + @staticmethod + def generate_kresling_zigzag(sides, cols, radius, angle_ratio): + # def generate_kresling_zigzag(sides, radius, angle_ratio, add_attachment): + + theta = (pi / 2.) * (1 - 2. / sides) + l = 2. * radius * cos(theta * (1. - angle_ratio)) + a = 2. * radius * sin(pi / sides) + dy = l * sin(theta * angle_ratio) + dx = l * cos(theta * angle_ratio) - a + + points = [] + styles = [] + + for i in range(cols): + points.append((i * a, 0)) + points.append(((i + 1) * a + dx, dy)) + styles.append('v') + if i != cols - 1: + styles.append('m') + # elif add_attachment: + # points.append((sides * a, 0)) + # styles.append('m') + + path = Path.generate_separated_paths(points, styles) + return path + +if __name__ == '__main__': + + e = Kresling() + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical_Template.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical_Template.py new file mode 100644 index 0000000..c384740 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Cylindrical_Template.py @@ -0,0 +1,73 @@ +#! /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 + +import inkex + +from Path import Path +from Pattern import Pattern +from Cylindrical import Cylindrical + + +# 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 + +class Template(Cylindrical): + + def __init__(self): + """ Constructor + """ + Cylindrical.__init__(self) # Must be called in order to parse common options + + # save all custom parameters defined on .inx file + self.add_argument('--pattern', type=self.str, default='cylindrical_template') + self.add_argument('--length', type=self.float, default=10.) + self.add_argument('--angle', type=self.int, default=0) + + def generate_cell(self): + """ Generate the the origami cell + """ + # retrieve conversion factor for selected unit + unit_factor = self.calc_unit_factor() + sides = self.options.sides + cols = self.options.cols + rows = self.options.rows + + length = self.options.length * unit_factor + angle = self.options.angle * unit_factor + width = self.options.width * unit_factor # use pre-calculated width + + dx = length * sin(pi * angle / 180) + dy = length * cos(pi * angle / 180) + + # init dict that holds everything + cell_data = {} + + # divider can be set for performance, or it can be calculated automatically, supposing a mountain fold + # cell_data['divider'] = Path([(0,0), (width*cols, 0)], style='m') + + # Only left or right edges can be implemented, or both for performance + # IMPORTANT: left edges from TOP to BOTTOM + cell_data['edge_left'] = [Path([(0,0), (dx, dy)], style='e')]*rows + + # # IMPORTANT: right edges from BOTTOM to TOP + # cell_data['edge_right'] = [Path([(cols*width + dx, dy), (cols*width, 0)], style='e')]*rows + + # rest of cell + single = [Path([(0, 0), (width + dx, dy)], 'v'), Path([(width + dx, dy), (width, 0)], 'm')] + pattern = [Path.list_add(single, (width*i, 0)) for i in range(cols)] + pattern = Path.list_simplify(pattern) + cell_data['interior'] = [pattern]*rows + + return cell_data + + +# Main function, creates an instance of the Class and calls self.draw() to draw the origami on inkscape +# self.draw() is either a call to inkex.affect() or to svg.run(), depending on python version +if __name__ == '__main__': + e = Template() # remember to put the name of your Class here! + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Hypar.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Hypar.py new file mode 100755 index 0000000..6b8675b --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Hypar.py @@ -0,0 +1,122 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +import numpy as np + +from math import pi, tan, sqrt, sin, cos + +import inkex + +from Path import Path +from Pattern import Pattern + + +class Hypar(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('--pattern', type=self.str, default='template1') + self.add_argument('--radius', type=self.float, default=10.0) + self.add_argument('--sides', type=self.int, default=4) + self.add_argument('--rings', type=self.int, default=7) + self.add_argument('--simplify_center', type=self.bool, default=0) + + def generate_path_tree(self): + """ Specialized path generation for your origami pattern + """ + # retrieve saved parameters + unit_factor = self.calc_unit_factor() + vertex_radius = self.options.vertex_radius * unit_factor + pattern = self.options.pattern + radius = self.options.radius * unit_factor + sides = self.options.sides + rings = self.options.rings + simplify_center = self.options.simplify_center + sin_ = sin(pi / float(sides)) + a = radius*sin_ # half of length of polygon side + H = radius*sqrt(1 - sin_**2) + + polygon = Path.generate_polygon(sides, radius, 'e') + + # # OLD diagonals generation with universal creases + # diagonals = [] + # for i in range(sides): + # diagonals.append(Path([(0, 0), polygon.points[i]], 'u')) + # points = [(x, y) for x, y in polygon.points] + # diagonals = diagonals + [Path.generate_separated_paths(points, 'm')] + + # # modify center if needed + # if simplify_center: + # for i in range(sides): + # if i % 2 == 0: + # p2 = diagonals[i].points[1] + # diagonals[i].points[0] = (1. / (rings + 1) * p2[0], 1. / (rings + 1) * p2[1]) + + # separate generic closed ring to create edges + self.edge_points = polygon.points + + # vertex and diagonal lines creation + vertex_line = [] + diagonal_line = [] + for i in range(1, rings + 2): + y1 = a * (float(i - 1) / (rings + 1.)) + x1 = H * float(i - 1) / (rings + 1.) + y2 = a * (float(i) / (rings + 1.)) + x2 = H * float(i) / (rings + 1.) + vertex_line.append((Path((x2, y2), style='p', radius=vertex_radius))) + diagonal_line.append((Path([(x1, y1), (x2, y2)], style='m' if i % 2 else 'v'))) + + # rotation of vertices and diagonals for completing the drawing + diagonals = [] + vertices = [Path((0, 0), style='p', radius=vertex_radius)] + for i in range(sides): + vertices = vertices+Path.list_rotate(vertex_line, i * 2 * pi / float(sides)) + diagonals = diagonals+[Path.list_rotate(diagonal_line, i * 2 * pi / float(sides))] + + # modify center if needed + if simplify_center: + for i in range(sides): + if i % 2 == 0: + del diagonals[i][0] + + # inkex.debug(len(diagonals)) + # inkex.debug(len(diagonals[0])) + # diagonals = diagonals + diagonal + + # scale generic closed ring to create inner rings + inner_rings = [] + for i in range(rings + 1): + inner_rings.append(polygon * (float(i)/(rings+1))) + inner_rings[i].style = 'v' if i % 2 else 'm' + + # create points for zig zag pattern + zig_zags = [] + if pattern != "classic": + zig_zag = [] + for i in range(1, rings + 1): + y_out = a * ((i + 1.) / (rings + 1.)) + y_in = a * (float(i) / (rings + 1.)) + x_out = H * (i + 1.) / (rings + 1.) + x_in = H * float(i) / (rings + 1.) + + if pattern == "alternate_asymmetric" and i % 2: + zig_zag.append(Path([(x_in, -y_in), (x_out, +y_out)], style='u')) + else: + zig_zag.append(Path([(x_in, +y_in), (x_out, -y_out)], style='u')) + + # reflect zig zag pattern to create all sides + zig_zags.append(zig_zag) + for i in range(sides - 1): + points = diagonals[i][0].points + zig_zags.append(Path.list_reflect(zig_zags[i], points[0], points[1])) + + self.translate = (radius, radius) + self.path_tree = [diagonals, zig_zags, inner_rings, vertices] + +# Main function, creates an instance of the Class and calls inkex.affect() to draw the origami on inkscape +if __name__ == '__main__': + e = Hypar() # remember to put the name of your Class here! + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Kresling.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Kresling.py new file mode 100755 index 0000000..614006e --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Kresling.py @@ -0,0 +1,162 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +from math import pi, sin, cos, tan, acos, sqrt +import inkex +import os + +from Path import Path +from Pattern import Pattern + + +class Kresling(Pattern): + + def __init__(self): + """ Constructor + """ + Pattern.__init__(self) # Must be called in order to parse common options + + self.add_argument('--pattern', type=self.str, default="kresling") + self.add_argument('--lines', type=self.int, default=1) + self.add_argument('--sides', type=self.int, default=3) + self.add_argument('--add_attachment', type=self.bool, default=False) + self.add_argument('--attachment_percentage', type=self.float, default=100.) + self.add_argument('--mirror_cells', type=self.bool, default=False) + + @staticmethod + def generate_kresling_zigzag(sides, radius, angle_ratio, add_attachment): + + theta = (pi / 2.) * (1 - 2. / sides) + l = 2. * radius * cos(theta * (1. - angle_ratio)) + a = 2. * radius * sin(pi / sides) + # b = sqrt(a * a + l * l - 2 * a * l * cos(angle_ratio * theta)) + # phi = abs(acos((l * l + b * b - a * a) / (2 * l * b))) + # gamma = pi / 2 - angle_ratio * theta - phi + dy = l * sin(theta * angle_ratio) + dx = l * cos(theta * angle_ratio) - a + + points = [] + styles = [] + + for i in range(sides): + points.append((i * a, 0)) + points.append(((i + 1) * a + dx, -dy)) + styles.append('v') + if i != sides - 1: + styles.append('m') + elif add_attachment: + points.append((sides * a, 0)) + styles.append('m') + + path = Path.generate_separated_paths(points, styles) + return path + + def generate_path_tree(self): + """ Specialized path generation for Waterbomb tesselation pattern + """ + unit_factor = self.calc_unit_factor() + vertex_radius = self.options.vertex_radius * unit_factor + lines = self.options.lines + sides = self.options.sides + radius = self.options.radius * unit_factor + angle_ratio = self.options.angle_ratio + mirror_cells = self.options.mirror_cells + + theta = (pi/2.)*(1 - 2./sides) + l = 2.*radius*cos(theta*(1.-angle_ratio)) + a = 2.*radius*sin(pi/sides) + # b = sqrt(a*a + l*l - 2*a*l*cos(angle_ratio*theta)) + # phi = abs(acos((l*l + b*b - a*a)/(2*l*b))) + # gamma = pi/2 - angle_ratio*theta - phi + # dy = b*cos(gamma) + # dx = b*sin(gamma) + dy = l * sin(theta * angle_ratio) + dx = l * cos(theta * angle_ratio) - a + + add_attachment = self.options.add_attachment + attachment_percentage = self.options.attachment_percentage/100. + attachment_height = a*(attachment_percentage-1)*tan(angle_ratio*theta) + + vertices = [] + for i in range(sides + 1): + for j in range(lines + 1): + if mirror_cells: + vertices.append(Path((dx*((lines - j)%2) + a*i, dy*j), style='p', radius=vertex_radius)) + else: + vertices.append(Path((dx*(lines - j) + a*i, dy*j), style='p', radius=vertex_radius)) + + # create a horizontal grid, then offset each line according to angle + grid_h = Path.generate_hgrid([0, a * sides], [0, dy * lines], lines, 'm') + + if not mirror_cells: + # shift every mountain line of the grid to the right by increasing amounts + grid_h = Path.list_add(grid_h, [(i * dx, 0) for i in range(lines - 1, 0, -1)]) + else: + # shift every OTHER mountain line of the grid a bit to the right + grid_h = Path.list_add(grid_h, [((i%2)*dx, 0) for i in range(lines-1, 0, -1)]) + if add_attachment: + for i in range(lines%2, lines-1, 2): + # hacky solution, changes length of every other mountain line + grid_h[i].points[1-i%2] = (grid_h[i].points[1-i%2][0] + a*attachment_percentage, grid_h[i].points[1-i%2][1]) + + # create MV zigzag for Kresling pattern + zigzag = Kresling.generate_kresling_zigzag(sides, radius, angle_ratio, add_attachment) + zigzags = [] + + # duplicate zigzag pattern for desired number of cells + if not mirror_cells: + for i in range(lines): + zigzags.append(Path.list_add(zigzag, (i * dx, (lines - i) * dy))) + else: + zigzag_mirror = Path.list_reflect(zigzag, (0, lines * dy / 2), (dx, lines * dy / 2)) + for i in range(lines): + if i % 2 == 1: + zigzags.append(Path.list_add(zigzag_mirror, (0, -(lines - i + (lines-1)%2) * dy))) + else: + zigzags.append(Path.list_add(zigzag, (0, (lines - i) * dy))) + + # create edge strokes + if not mirror_cells: + self.edge_points = [ + (a * sides , dy * lines), # bottom right + (0 , dy * lines), # bottom left + (dx * lines , 0), # top left + (dx * lines + a * sides, 0)] # top right + + if add_attachment: + for i in range(lines): + x = dx * (lines - i) + a * (sides + attachment_percentage) + self.edge_points.append((x, dy * i)) + self.edge_points.append((x, dy * i - attachment_height)) + if i != lines - 1: + self.edge_points.append((x-dx-a*attachment_percentage, dy * (i + 1))) + pass + + else: + self.edge_points = [(a * sides + (lines % 2)*dx, 0)] + + for i in range(lines+1): + self.edge_points.append([((lines+i) % 2)*dx, dy*i]) + + self.edge_points.append([a * sides + ((lines+i) %2)*dx, lines*dy]) + + if add_attachment: + for i in range(lines + 1): + + if not i%2 == 0: + self.edge_points.append([a*sides + (i%2)*(dx+a*attachment_percentage), dy*(lines - i) - (i%2)*attachment_height]) + self.edge_points.append([a*sides + (i%2)*(dx+a*attachment_percentage), dy*(lines - i)]) + if (i != lines): + self.edge_points.append([a * sides + (i % 2) * (dx + a * attachment_percentage), dy * (lines - i) + (i % 2) * attachment_height]) + else: + self.edge_points.append([a * sides + (i % 2) * (dx + a * attachment_percentage), dy * (lines - i)]) + else: + for i in range(lines + 1): + self.edge_points.append([a*sides + (i%2)*dx, dy*(lines - i)]) + + self.path_tree = [grid_h, zigzags, vertices] + + +if __name__ == '__main__': + + e = Kresling() + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Kresling_full.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Kresling_full.py new file mode 100755 index 0000000..daacf7e --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Kresling_full.py @@ -0,0 +1,92 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +from math import sin, cos, sqrt, asin, pi, ceil +import inkex + +from Path import Path +from Pattern import Pattern +from Kresling import Kresling + + +class Kresling_Full(Kresling): + + def __init__(self): + """ Constructor + """ + Kresling.__init__(self) # Must be called in order to parse common options + + self.add_argument('--measure_value', type=self.float, default=10.0) + self.add_argument('--measure_type', type=self.str, default=60) + self.add_argument('--parameter_type', type=self.str, default=60) + self.add_argument('--radial_ratio', type=self.float, default=0.5) + self.add_argument('--angle_ratio', type=self.float, default=0.5) + self.add_argument('--lambdatheta', type=self.float, default=45) + + def generate_path_tree(self): + """ Convert radial to angular ratio, then call regular Kresling constructor + """ + n = self.options.sides + theta = pi*(n-2)/(2*n) + # define ratio parameter + parameter = self.options.parameter_type + if parameter == 'radial_ratio': + radial_ratio = self.options.radial_ratio + max_radial_ratio = sin((pi/4)*(1. - 2./n)) + if radial_ratio > max_radial_ratio: + inkex.errormsg(_("For polygon of {} sides, the maximal radial ratio is = {}".format(n, max_radial_ratio))) + radial_ratio = max_radial_ratio + self.options.angle_ratio = 1 - 2*n*asin(radial_ratio)/((n-2)*pi) + + elif parameter == 'lambdatheta': + lambdatheta = self.options.lambdatheta + angle_min = 45. * (1 - 2. / n) + angle_max = 2 * angle_min + if lambdatheta < angle_min: + inkex.errormsg(_( + "For polygon of {} sides, phi must be between {} and {} degrees, \nsetting lambda*theta = {}\n".format( + n, angle_min, angle_max, angle_min))) + lambdatheta = angle_min + elif lambdatheta > angle_max: + inkex.errormsg(_( + "For polygon of {} sides, phi must be between {} and {} degrees, \nsetting lambda*theta = {}\n".format( + n, angle_min, angle_max, angle_max))) + lambdatheta = angle_max + self.options.angle_ratio = lambdatheta * n / (90. * (n - 2.)) + + + # define some length + mtype = self.options.measure_type + mvalue = self.options.measure_value + angle_ratio = self.options.angle_ratio + if mtype == 'a': + radius = 0.5*mvalue / (sin(pi/n)) + if mtype == 'b': + A = cos(theta*(1-angle_ratio)) + B = sin(pi/n) + C = cos(theta*angle_ratio) + radius = 0.5*mvalue / sqrt(A**2 + B**2 - 2*A*B*C) + elif mtype == 'l': + radius = 0.5*mvalue/cos(theta*(1-angle_ratio)) + elif mtype == 'radius_external': + radius = mvalue + elif mtype == 'radius_internal': + radius = mvalue/(sin(theta*(1-angle_ratio))) + elif mtype == 'diameter_external': + radius = 0.5*mvalue + elif mtype == 'diameter_internal': + radius = 0.5*mvalue/sin(theta*(1-angle_ratio)) + + # inkex.errormsg(_("Value = {}, Mode = {}, Radius = {}".format(mvalue, mtype, radius))) + + if self.options.pattern == 'mirrowed': + self.options.mirror_cells = True + else: + self.options.mirror_cells = False + self.options.radius = radius + + Kresling.generate_path_tree(self) + + +if __name__ == '__main__': + e = Kresling_Full() + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Path.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Path.py new file mode 100755 index 0000000..18d29a0 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Path.py @@ -0,0 +1,787 @@ +""" +Path Class + +Defines a path and what it is supposed to be (mountain, valley, edge) + +""" + +import inkex # Required +import simplestyle # will be needed here for styles support + +# compatibility hack +try: + from lxml import etree + inkex.etree = etree +except: + pass + +from math import sin, cos, pi, sqrt + +# compatibility hack for formatStyle +def format_style(style): + try: + return str(inkex.Style(style)) # new + except: + return simplestyle.formatStyle(style) # old +# def format_style(style): + # return simplestyle.formatStyle(style) + + + +class Path: + """ Class that defines an svg stroke to be drawn in Inkscape + + Attributes + --------- + points: tuple or list of tuples + Points defining stroke lines. + style: str + Single character defining style of stroke. Default values are: + 'm' for mountain creases + 'v' for valley creases + 'e' for edge borders + Extra possible values: + 'u' for universal creases + 's' for semicreases + 'c' for kirigami cuts + angle: float + From 0 to 180 degrees, converted to an opacity level from 0 to 1. This is how OrigamiSimulator encodes maximum + fold angles + closed: bool + Tells if desired path should contain a last stroke from the last point to the first point, closing the path + radius: float + If only one point is given, it's assumed to be a circle and radius sets the radius + + + Methods + --------- + invert(self) + Inverts path + + Overloaded Operators + --------- + __add__(self, offsets) + Adding a tuple to a Path returns a new path with all points having an offset defined by the tuple + + __mul__(self, transform) + Define multiplication of a Path to a vector in complex exponential representation + + + Static Methods + --------- + draw_paths_recursively(path_tree, group, styles_dict) + Draws strokes defined on "path_tree" to "group". Styles dict maps style of path_tree element to the definition + of the style. Ex.: + if path_tree[i].style = 'm', styles_dict must have an element 'm'. + + + generate_hgrid(cls, xlims, ylims, nb_of_divisions, style, include_edge=False) + Generate list of Path instances, in which each Path is a stroke defining a horizontal grid dividing the space + xlims * ylims nb_of_divisions times. + + generate_vgrid(cls, xlims, ylims, nb_of_divisions, style, include_edge=False) + Generate list of Path instances, in which each Path is a stroke defining a vertical grid dividing the space + xlims * ylims nb_of_divisions times. + + generate_separated_paths(cls, points, styles, closed=False) + Generate list of Path instances, in which each Path is the stroke between each two point tuples, in case each + stroke must be handled separately + + reflect(cls, path, p1, p2) + Reflects each point of path on line defined by two points and return new Path instance with new reflected points + + list_reflect(cls, paths, p1, p2) + Generate list of new Path instances, rotation each path by transform + + list_rotate(cls, paths, theta, translation=(0, 0)) + Generate list of new Path instances, rotation each path by transform + + list_add(cls, paths, offsets) + Generate list of new Path instances, adding a different tuple for each list + + list_mul(cls, paths, transf) + Generate list of new Path instances, multiplying a different tuple for each list + + list_simplify(cls, paths) + Gets complicated path-tree list and converts it into + a simple list. + + list_invert(cls, paths) + Invert list of paths and points of each path. + + debug_points(cls, paths): + Plots points of path tree in drawing order. + """ + + def __init__(self, points, style, closed=False, invert=False, radius=0.1, separated=False, fold_angle = 180.0): + """ Constructor + + Parameters + --------- + points: list of 2D tuples + stroke will connect all points + style: str + Single character defining style of stroke. For use with the OrigamiPatterns class (probably the only + project that will ever use this file) the default values are: + 'm' for mountain creases + 'v' for valley creases + 'e' for edge borders + closed: bool + if true, last point will be connected to first point at the end + invert: bool + if true, stroke will start at the last point and go all the way to the first one + """ + if type(points) == list and len(points) != 1: + self.type = 'linear' + if invert: + self.points = points[::-1] + else: + self.points = points + + elif (type(points) == list and len(points) == 1): + self.type = 'circular' + self.points = points + self.radius = radius + + elif (type(points) == tuple and len(points) == 2): + self.type = 'circular' + self.points = [points] + self.radius = radius + + else: + raise TypeError("Points must be tuple of length 2 (for a circle) or a list of tuples of length 2 each") + + self.fold_angle = max(min(fold_angle, 180.), 0.) + self.style = style + self.closed = closed + + def invert(self): + """ Inverts path + """ + self.points = self.points[::-1] + + """ + Draw path recursively + - Static method + - Draws strokes defined on "path_tree" to "group" + - Inputs: + -- path_tree [nested list] of Path instances + -- group [inkex.etree.SubElement] + -- styles_dict [dict] containing all styles for path_tree + """ + @staticmethod + def draw_paths_recursively(path_tree, group, styles_dict): + """ Static method, draw list of Path instances recursively + """ + for subpath in path_tree: + if type(subpath) == list: + if len(subpath) == 1: + subgroup = group + else: + subgroup = inkex.etree.SubElement(group, 'g') + Path.draw_paths_recursively(subpath, subgroup, styles_dict) + else: + # ~ if subpath.style != 'n': + if subpath.style != 'n' and styles_dict[subpath.style]['draw']: + if subpath.type == 'linear': + + points = subpath.points + path = 'M{},{} '.format(*points[0]) + for i in range(1, len(points)): + path = path + 'L{},{} '.format(*points[i]) + if subpath.closed: + path = path + 'L{},{} Z'.format(*points[0]) + + attribs = {'style': format_style(styles_dict[subpath.style]), + 'd': path, + 'opacity': str(subpath.fold_angle/180)} + inkex.etree.SubElement(group, inkex.addNS('path', 'svg'), attribs) + else: + attribs = {'style': format_style(styles_dict[subpath.style]), + 'cx': str(subpath.points[0][0]), 'cy': str(subpath.points[0][1]), + 'r': str(subpath.radius), + 'opacity': str(subpath.fold_angle/180)} + inkex.etree.SubElement(group, inkex.addNS('circle', 'svg'), attribs) + + @classmethod + def get_average_point(cls, paths): + points = cls.get_points(paths) + n = len(points) + x, y = 0, 0 + for p in points: + x += p[0] + y += p[1] + return (x/n, y/n) + + + @classmethod + def get_square_points(cls, width, height, center = None, rotation = 1): + """ Get points of a square at given center or origin + + Parameters + --------- + width: float + height: float + center: list of floats + rotation: float + rotation in degrees + + Returns + --------- + points: list of tuples + """ + if center is None: + center = [width/2, height/2] + + #TODO: Implement rotation + # c = cos(rotation * pi / 180) + # s = sin(rotation * pi / 180) + + points = [ + (center[0] - 0.5*width, center[1] + 0.5*height), # top left + (center[0] + 0.5*width, center[1] + 0.5*height), # top right + (center[0] + 0.5*width, center[1] - 0.5*height), # bottom right + (center[0] - 0.5*width, center[1] - 0.5*height)] # bottom left + # points = [ + # (center[0] + (-0.5*width*c - 0.5*height*s), center[1] + (-0.5*width*s + 0.5*height*c)), # top left + # (center[0] + (+0.5*width*c - 0.5*height*s), center[1] + (+0.5*width*s + 0.5*height*c)), # top right + # (center[0] + (+0.5*width*c + 0.5*height*s), center[1] - (+0.5*width*s - 0.5*height*c)), # bottom right + # (center[0] + (-0.5*width*c + 0.5*height*s), center[1] - (-0.5*width*s - 0.5*height*c))] # bottom left + return points + + @classmethod + def generate_square(cls, width, height, style ='e', fold_angle=180, center = None, rotation = 0): + """ Generate a closed square at given center or origin + + Parameters + --------- + width: float + height: float + style: str + fold_angle: float + center: list of floats + rotation: float + rotation in degrees + + Returns + --------- + path: path instance + """ + points = cls.get_square_points(width, height, center, rotation) + return Path(points, style, fold_angle=fold_angle, closed=True) + + @classmethod + def generate_hgrid(cls, xlims, ylims, nb_of_divisions, style, include_edge=False, fold_angle = 180): + """ Generate list of Path instances, in which each Path is a stroke defining + a horizontal grid dividing the space xlims * ylims nb_of_divisions times. + + All lines are alternated, to minimize Laser Cutter unnecessary movements + + Parameters + --------- + xlims: tuple + Defines x_min and x_max for space that must be divided. + ylims: tuple + Defines y_min and y_max for space that must be divided. + nb_of_divisions: int + Defines how many times it should be divided. + style: str + Single character defining style of stroke. + include_edge: bool + Defines if edge should be drawn or not. + fold_angle: float + + Returns + --------- + paths: list of Path instances + """ + rect_len = (ylims[1] - ylims[0])/nb_of_divisions + hgrid = [] + for i in range(1 - include_edge, nb_of_divisions + include_edge): + hgrid.append(cls([(xlims[0], ylims[0]+i*rect_len), + (xlims[1], ylims[0]+i*rect_len)], + style=style, invert=i % 2 == 0, fold_angle = fold_angle)) + return hgrid + + @classmethod + def generate_vgrid(cls, xlims, ylims, nb_of_divisions, style, include_edge=False, fold_angle = 180): + """ Generate list of Path instances, in which each Path is a stroke defining + a vertical grid dividing the space xlims * ylims nb_of_divisions times. + + All lines are alternated, to minimize Laser Cutter unnecessary movements + + Parameters + --------- + -> refer to generate_hgrid + + Returns + --------- + paths: list of Path instances + """ + rect_len = (xlims[1] - xlims[0])/nb_of_divisions + vgrid = [] + for i in range(1 - include_edge, nb_of_divisions + include_edge): + vgrid.append(cls([(xlims[0]+i*rect_len, ylims[0]), + (xlims[0]+i*rect_len, ylims[1])], + style=style, invert=i % 2 == 0, fold_angle = fold_angle)) + return vgrid + + @classmethod + def generate_polygon(cls, sides, radius, style, center=(0, 0), fold_angle = 180): + points = [] + for i in range(sides): + points.append((radius * cos((1 + i * 2) * pi / sides), + radius * sin((1 + i * 2) * pi / sides))) + return Path(points, style, closed=True, fold_angle = fold_angle) + + @classmethod + def generate_separated_paths(cls, points, styles, closed=False, fold_angle = 180): + """ Generate list of Path instances, in which each Path is the stroke + between each two point tuples, in case each stroke must be handled separately. + + Returns + --------- + paths: list + list of Path instances + """ + paths = [] + if type(styles) == str: + styles = [styles] * (len(points) - 1 + int(closed)) + elif len(styles) != len(points) - 1 + int(closed): + raise TypeError("Number of paths and styles don't match") + for i in range(len(points) - 1 + int(closed)): + j = (i+1)%len(points) + paths.append(cls([points[i], points[j]], + styles[i], fold_angle = fold_angle)) + return paths + + + def __add__(self, offsets): + """ " + " operator overload. + Adding a tuple to a Path returns a new path with all points having an offset + defined by the tuple + """ + if type(offsets) == list: + if len(offsets) != 1 or len(offsets) != len(self.points): + raise TypeError("Paths can only be added by a tuple of a list of N tuples, " + "where N is the same number of points") + + elif type(offsets) != tuple: + raise TypeError("Paths can only be added by tuples") + else: + offsets = [offsets] * len(self.points) + + # if type(self.points) == list: + points_new = [] + for point, offset in zip(self.points, offsets): + points_new.append((point[0]+offset[0], + point[1]+offset[1])) + + if self.type == 'circular': + radius = self.radius + else: + radius = 0.2 + + # if self.type == 'circular' else 0.1 + + return Path(points_new, self.style, self.closed, radius=radius, fold_angle=self.fold_angle) + + @classmethod + def list_add(cls, paths, offsets): + """ Generate list of new Path instances, adding a different tuple for each list + + Parameters + --------- + paths: Path or list + list of N Path instances + offsets: tuple or list + list of N tuples + + Returns + --------- + paths_new: list + list of N Path instances + """ + if type(paths) == Path and type(offsets) == tuple: + paths = [paths] + offsets = [offsets] + elif type(paths) == list and type(offsets) == tuple: + offsets = [offsets] * len(paths) + elif type(paths) == Path and type(offsets) == list: + paths = [paths] * len(offsets) + elif type(paths) == list and type(offsets) == list: + if len(paths) == 1: + paths = [paths[0]] * len(offsets) + elif len(offsets) == 1: + offsets = [offsets[0]] * len(paths) + elif len(offsets) != len(paths): + raise TypeError("List of paths and list of tuples must have same length. {} paths and {} offsets " + " where given".format(len(paths), len(offsets))) + else: + pass + + paths_new = [] + for path, offset in zip(paths, offsets): + if type(path) == Path: + paths_new.append(path+offset) + elif type(path) == list: + paths_new.append( + cls.list_add(path, offset) + ) + + return paths_new + + @classmethod + def list_mul(cls, paths, offsets): + """ Generate list of new Path instances, multiplying a different tuple for each list + + Parameters + --------- + paths: Path or list + list of N Path instances + offsets: tuple or list + list of N tuples + + Returns + --------- + paths_new: list + list of N Path instances + """ + if type(paths) == Path and type(offsets) == tuple: + paths = [paths] + offsets = [offsets] + elif type(paths) == list and type(offsets) == tuple: + offsets = [offsets] * len(paths) + elif type(paths) == Path and type(offsets) == list: + paths = [paths] * len(offsets) + elif type(paths) == list and type(offsets) == list: + if len(paths) == 1: + paths = [paths[0]] * len(offsets) + elif len(offsets) == 1: + offsets = [offsets[0]] * len(paths) + elif len(offsets) != len(paths): + raise TypeError("List of paths and list of tuples must have same length. {} paths and {} offsets " + " where given".format(len(paths), len(offsets))) + else: + pass + + paths_new = [] + for path, offset in zip(paths, offsets): + paths_new.append(path*offset) + + return paths_new + + def break_path(self, lengths, styles = None): + if len(self.points) != 2: + raise ValueError('Path breaking only implemented for straight lines with 2 points') + + if styles is None: + styles = [self.style]*len(lengths) + elif len(styles) != len(lengths): + raise ValueError('Different number of lenghts and styles') + + p0 = self.points[0] + p1 = self.points[1] + d = (p1[0]-p0[0], p1[1]-p0[1]) + L = sqrt(d[0]**2 + d[1]**2) + dx = d[0] / L + dy = d[1] / L + paths = [] + start = 0 + p0_ = p0 + for l, s in zip(lengths, styles): + p1_ = (p0_[0] + dx*l, p0_[1] + dy*l) + paths.append(Path([p0_, p1_], style = s)) + p0_ = p1_ + return paths + + + + def __mul__(self, transform): + """ " * " operator overload. + Define multiplication of a Path to a vector in complex exponential representation + + Parameters + --------- + transform: float of tuple of length 2 or 4 + if float, transform represents magnitude + Example: path * 3 + if tuple length 2, transform[0] represents magnitude and transform[1] represents angle of rotation + Example: path * (3, pi) + if tuple length 4, transform[2],transform[3] define a different axis of rotation + Example: path * (3, pi, 1, 1) + """ + points_new = [] + + # "temporary" (probably permanent) compatibility hack + try: + long_ = long + except: + long_ = int + + if isinstance(transform, (int, long_, float)): + for p in self.points: + points_new.append((transform * p[0], + transform * p[1])) + + elif isinstance(transform, (list, tuple)): + if len(transform) == 2: + u = transform[0]*cos(transform[1]) + v = transform[0]*sin(transform[1]) + x_, y_ = 0, 0 + elif len(transform) == 4: + u = transform[0]*cos(transform[1]) + v = transform[0]*sin(transform[1]) + x_, y_ = transform[2:] + else: + raise IndexError('Paths can only be multiplied by a number or a tuple/list of length 2 or 4') + + for p in self.points: + x, y = p[0]-x_, p[1]-y_ + points_new.append((x_ + x * u - y * v, + y_ + x * v + y * u)) + else: + raise TypeError('Paths can only be multiplied by a number or a tuple/list of length 2 or 4') + + if self.type == 'circular': + radius = self.radius + else: + radius = 0.2 + + return Path(points_new, self.style, self.closed, radius=radius, fold_angle=self.fold_angle) + + def shape(self): + points = self.points + x = [p[0] for p in points] + y = [p[1] for p in points] + return [min(x), max(x), min(y), max(y)] + + @classmethod + def list_create_from_points(cls, points, styles, fold_angles = None): + """ Generate list of new Path instances, between each two points + + Parameters + --------- + points: list of tuples + list of points + styles: str or list of str + styles + fold_angles: list of floats + list of maximum fold angle values + + Returns + --------- + paths_new: list + list of N Path instances + """ + if fold_angles is None: + fold_angles = [180] + elif type(fold_angles) != list: + fold_angles = [fold_angles] + + return [Path([points[i], points[i+1]], + styles[i % len(styles)], + fold_angle=fold_angles[i % len(fold_angles)]) for i in range(len(points)-1)] + + @classmethod + def list_rotate_symmetry(cls, paths, n, translation=(0,0)): + """ Generate list of new Path instances, rotation each path by transform + + Parameters + --------- + paths: Path or list + list of N Path instances + n: int + number of rotations + translation: tuple or list 2 + axis of rotation + + Returns + --------- + paths_new: list + list of N Path instances + """ + + theta = 2*pi/n + paths_new = [] + for i in range(n): + ith_rotation = cls.list_rotate(paths, theta, translation=translation) + if type(paths) == list: + paths_new += ith_rotation + else: + paths_new.append(ith_rotation) + return paths_new + + + @classmethod + def list_rotate(cls, paths, theta, translation=(0, 0)): + """ Generate list of new Path instances, rotation each path by transform + + Parameters + --------- + paths: Path or list + list of N Path instances + theta: float (radians) + angle of rotation + translation: tuple or list 2 + axis of rotation + + Returns + --------- + paths_new: list + list of N Path instances + """ + if len(translation) != 2: + TypeError("Translation must have length 2") + + if type(paths) != list: + paths = [paths] + + paths_new = [] + for path in paths: + if type(path) == Path: + paths_new.append(path*(1, theta, translation[0], translation[1])) + elif type(path) == list: + paths_new.append(cls.list_rotate(path, theta, translation)) + + if len(paths_new) == 1: + paths_new = paths_new[0] + return paths_new + + # TODO: + # Apparently it's not working properly, must be debugged and tested + @classmethod + def reflect(cls, path, p1, p2): + """ Reflects each point of path on line defined by two points and return new Path instance with new reflected points + + Parameters + --------- + path: Path + p1: tuple or list of size 2 + p2: tuple or list of size 2 + + Returns + --------- + path_reflected: Path + """ + + (x1, y1) = p1 + (x2, y2) = p2 + + if x1 == x2 and y1 == y2: + ValueError("Duplicate points don't define a line") + elif x1 == x2: + t_x = [-1, 0, 2*x1, 1] + t_y = [0, 1, 0, 1] + else: + m = (y2 - y1)/(x2 - x1) + t = y1 - m*x1 + t_x = [1 - m**2, 2*m, -2*m*t, m**2 + 1] + t_y = [2*m, m**2 - 1, +2*t, m**2 + 1] + + points_new = [] + for p in path.points: + x_ = (t_x[0]*p[0] + t_x[1]*p[1] + t_x[2]) / t_x[3] + y_ = (t_y[0]*p[0] + t_y[1]*p[1] + t_y[2]) / t_y[3] + points_new.append((x_, y_)) + + return Path(points_new, path.style, path.closed, fold_angle=path.fold_angle) + + # TODO: + # Apparently it's not working properly, must be debugged and tested + @classmethod + def list_reflect(cls, paths, p1, p2): + """ Generate list of new Path instances, rotation each path by transform + + Parameters + --------- + paths: Path or list + list of N Path instances + p1: tuple or list of size 2 + p2: tuple or list of size 2 + + Returns + --------- + paths_new: list + list of N Path instances + """ + + if type(paths) == Path: + paths = [paths] + + paths_new = [] + for path in paths: + paths_new.append(Path.reflect(path, p1, p2)) + + return paths_new + + @classmethod + def list_simplify(cls, paths): + """ Gets complicated path-tree list and converts it into + a simple list. + + Returns + --------- + paths: list + list of Path instances + """ + if type(paths) == Path: + return paths + + simple_list = [] + for i in range(len(paths)): + if type(paths[i]) == Path: + simple_list.append(paths[i]) + elif type(paths[i]) == list: + simple_list = simple_list + Path.list_simplify(paths[i]) + return simple_list + + @classmethod + def list_invert(cls, paths): + """ Invert list of paths and points of each path. + + Returns + --------- + paths: list + list of Path instances + """ + + if type(paths) == Path: + # return Path(paths.points[::-1], paths.style, paths.closed, paths.invert) + return Path(paths.points, paths.style, paths.closed, True) + elif type(paths) == list: + paths_inverted = [] + # n = len(paths) + # for i in range(n): + # # paths_inverted.append(Path.list_invert(paths[n-1-i])) + # paths_inverted.append(Path.list_invert(paths[i])) + for path in paths: + # paths_inverted.append(Path.list_invert(paths[n-1-i])) + paths_inverted.append(Path.list_invert(path)) + return paths_inverted[::-1] + + @classmethod + def debug_points(cls, paths): + """ Plots points of path tree in drawing order. + + """ + if type(paths) == Path: + inkex.debug(paths.points) + elif type(paths) == list: + for sub_path in paths: + Path.debug_points(sub_path) + + @classmethod + def get_points(cls, paths): + """ Get points of path tree in drawing order. + + """ + points = [] + if type(paths) == Path: + points = points + paths.points + elif type(paths) == list: + for sub_path in paths: + points = points + Path.get_points(sub_path) + return points + + diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Pattern.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Pattern.py new file mode 100755 index 0000000..fafdffc --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Pattern.py @@ -0,0 +1,378 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +""" +Helper functions + +""" +import os +from abc import abstractmethod + +from Path import Path, inkex, simplestyle + +class Pattern(inkex.Effect): + """ Class that inherits inkex.Effect and further specializes it for different + Patterns generation + + Attributes + --------- + styles_dict: dict + defines styles for every possible stroke. Default values are: + styles_dict = {'m' : mountain_style, + 'v' : valley_style, + 'e' : edge_style} + + topgroup: inkex.etree.SubElement + Top Inkscape group element + + path_tree: nested list + Contains "tree" of Path instances, defining new groups for each + sublist + + translate: 2 sized tuple + Defines translation to be added when drawing to Inkscape (default: 0,0) + + Methods + --------- + effect(self) + Main function, called when the extension is run. + + create_styles_dict(self) + Get stroke style parameters and use them to create the styles dictionary. + + calc_unit_factor(self) + Return the scale factor for all dimension conversions + + add_text(self, node, text, position, text_height=12) + Create and insert a single line of text into the svg under node. + + get_color_string(self, longColor, verbose=False) + Convert the long into a #RRGGBB color value + + Abstract Methods + --------- + __init__(self) + Parse all options + + generate_path_tree(self) + Generate nested list of Path + + + """ + + @abstractmethod + def generate_path_tree(self): + """ Generate nested list of Path instances + Abstract method, must be defined in all child classes + """ + pass + + @abstractmethod + def __init__(self): + """ Parse all common options + + Must be reimplemented in child classes to parse specialized options + """ + + inkex.Effect.__init__(self) # initialize the super class + + # backwards compatibility + try: + self.add_argument = self.arg_parser.add_argument + self.str = str + self.int = int + self.float = float + self.bool = inkex.Boolean + except: + self.add_argument = self.OptionParser.add_option + self.str = "string" + self.int = "int" + self.float = "float" + self.bool = "inkbool" + + # Two ways to get debug info: + # OR just use inkex.debug(string) instead... + try: + self.tty = open("/dev/tty", 'w') + except: + self.tty = open(os.devnull, 'w') # '/dev/null' for POSIX, 'nul' for Windows. + + self.add_argument('-u', '--units', type=self.str, default='mm') + + # bypass most style options for OrigamiSimulator + self.add_argument('--simulation_mode', type=self.bool, default=False) + + # mountain options + self.add_argument('--mountain_stroke_color', type=self.str, default=4278190335) # Red + self.add_argument('--mountain_stroke_width', type=self.float, default=0.1) + self.add_argument('--mountain_dashes_len', type=self.float, default=1.0) + self.add_argument('--mountain_dashes_duty', type=self.float, default=0.5) + self.add_argument('--mountain_dashes_bool', type=self.bool, default=True) + self.add_argument('--mountain_bool', type=self.bool, default=True) + self.add_argument('--mountain_bool_only', type=self.bool, default=False) + + # valley options + self.add_argument('--valley_stroke_color', type=self.str, default=65535) # Blue + self.add_argument('--valley_stroke_width', type=self.float, default=0.1) + self.add_argument('--valley_dashes_len', type=self.float, default=1.0) + self.add_argument('--valley_dashes_duty', type=self.float, default=0.25) + self.add_argument('--valley_dashes_bool', type=self.bool, default=True) + self.add_argument('--valley_bool', type=self.bool, default=True) + self.add_argument('--valley_bool_only', type=self.bool, default=False) + + # edge options + self.add_argument('--edge_stroke_color', type=self.str, default=255) # Black + self.add_argument('--edge_stroke_width', type=self.float, default=0.1) + self.add_argument('--edge_dashes_len', type=self.float, default=1.0) + self.add_argument('--edge_dashes_duty', type=self.float, default=0.25) + self.add_argument('--edge_dashes_bool', type=self.bool, default=False) + self.add_argument('--edge_bool', type=self.bool, default=True) + self.add_argument('--edge_bool_only', type=self.bool, default=False) + self.add_argument('--edge_single_path', type=self.bool, default=True) + + # universal crease options + self.add_argument('--universal_stroke_color', type=self.str, default=4278255615) # Magenta + self.add_argument('--universal_stroke_width', type=self.float, default=0.1) + self.add_argument('--universal_dashes_len', type=self.float, default=1.0) + self.add_argument('--universal_dashes_duty', type=self.float, default=0.25) + self.add_argument('--universal_dashes_bool', type=self.bool, default=False) + self.add_argument('--universal_bool', type=self.bool, default=True) + self.add_argument('--universal_bool_only', type=self.bool, default=False) + + # semicrease options + self.add_argument('--semicrease_stroke_color', type=self.str, default=4294902015) # Yellow + self.add_argument('--semicrease_stroke_width', type=self.float, default=0.1) + self.add_argument('--semicrease_dashes_len', type=self.float, default=1.0) + self.add_argument('--semicrease_dashes_duty', type=self.float, default=0.25) + self.add_argument('--semicrease_dashes_bool', type=self.bool, default=False) + self.add_argument('--semicrease_bool', type=self.bool, default=True) + self.add_argument('--semicrease_bool_only', type=self.bool, default=False) + + # cut options + self.add_argument('--cut_stroke_color', type=self.str, default=16711935) # Green + self.add_argument('--cut_stroke_width', type=self.float, default=0.1) + self.add_argument('--cut_dashes_len', type=self.float, default=1.0) + self.add_argument('--cut_dashes_duty', type=self.float, default=0.25) + self.add_argument('--cut_dashes_bool', type=self.bool, default=False) + self.add_argument('--cut_bool', type=self.bool, default=True) + self.add_argument('--cut_bool_only', type=self.bool, default=False) + + # vertex options + self.add_argument('--vertex_stroke_color', type=self.str, default=255) # Black + self.add_argument('--vertex_stroke_width', type=self.float, default=0.1) + self.add_argument('--vertex_radius', type=self.float, default=0.1) + self.add_argument('--vertex_dashes_bool', type=self.bool, default=False) + self.add_argument('--vertex_bool', type=self.bool, default=True) + self.add_argument('--vertex_bool_only', type=self.bool, default=False) + + # here so we can have tabs - but we do not use it directly - else error + self.add_argument('--active-tab', type=self.str, default='title') # use a legitimate default + + self.path_tree = [] + self.edge_points = [] + self.vertex_points = [] + self.translate = (0, 0) + + def effect(self): + """ Main function, called when the extension is run. + """ + # bypass most style options if simulation mode is choosen + self.check_simulation_mode() + + # check if any selected to print only some of the crease types: + bool_only_list = [self.options.mountain_bool_only, + self.options.valley_bool_only, + self.options.edge_bool_only, + self.options.universal_bool_only, + self.options.semicrease_bool_only, + self.options.cut_bool_only, + self.options.vertex_bool_only] + if sum(bool_only_list) > 0: + self.options.mountain_bool = self.options.mountain_bool and self.options.mountain_bool_only + self.options.valley_bool = self.options.valley_bool and self.options.valley_bool_only + self.options.edge_bool = self.options.edge_bool and self.options.edge_bool_only + self.options.universal_bool = self.options.universal_bool and self.options.universal_bool_only + self.options.semicrease_bool = self.options.semicrease_bool and self.options.semicrease_bool_only + self.options.cut_bool = self.options.cut_bool and self.options.cut_bool_only + self.options.vertex_bool = self.options.vertex_bool and self.options.vertex_bool_only + + # construct dictionary containing styles + self.create_styles_dict() + + # get paths for selected origami pattern + self.generate_path_tree() + + # ~ accuracy = self.options.accuracy + # ~ unit_factor = self.calc_unit_factor() + # what page are we on + # page_id = self.options.active_tab # sometimes wrong the very first time + + # get vertex points and add them to path tree + vertex_radius = self.options.vertex_radius * self.calc_unit_factor() + vertices = [] + self.vertex_points = list(set([i for i in self.vertex_points])) # remove duplicates + for vertex_point in self.vertex_points: + vertices.append(Path(vertex_point, style='p', radius=vertex_radius)) + self.path_tree.append(vertices) + + + # Translate according to translate attribute + g_attribs = {inkex.addNS('label', 'inkscape'): '{} Origami pattern'.format(self.options.pattern), + # inkex.addNS('transform-center-x','inkscape'): str(-bbox_center[0]), + # inkex.addNS('transform-center-y','inkscape'): str(-bbox_center[1]), + inkex.addNS('transform-center-x', 'inkscape'): str(0), + inkex.addNS('transform-center-y', 'inkscape'): str(0), + 'transform': 'translate(%s,%s)' % self.translate} + + # add the group to the document's current layer + if type(self.path_tree) == list and len(self.path_tree) != 1: + self.topgroup = inkex.etree.SubElement(self.get_layer(), 'g', g_attribs) + else: + self.topgroup = self.get_layer() + + if len(self.edge_points) == 0: + Path.draw_paths_recursively(self.path_tree, self.topgroup, self.styles_dict) + elif self.options.edge_single_path: + edges = Path(self.edge_points, 'e', closed=True) + Path.draw_paths_recursively(self.path_tree + [edges], self.topgroup, self.styles_dict) + else: + edges = Path.generate_separated_paths(self.edge_points, 'e', closed=True) + Path.draw_paths_recursively(self.path_tree + edges, self.topgroup, self.styles_dict) + + # self.draw_paths_recursively(self.path_tree, self.topgroup, self.styles_dict) + + # compatibility hack, "affect()" is replaced by "run()" + def draw(self): + try: + self.run() # new + except: + self.affect() # old + # close(self.tty) + self.tty.close() + + # compatibility hack + def get_layer(self): + try: + return self.svg.get_current_layer() # new + except: + return self.current_layer # old + + def check_simulation_mode(self): + if not self.options.simulation_mode: + pass + else: + self.options.mountain_stroke_color = 4278190335 + self.options.mountain_dashes_len = 0 + self.options.mountain_dashes_bool = False + self.options.mountain_bool_only = False + self.options.mountain_bool = True + + self.options.valley_stroke_color = 65535 + self.options.valley_dashes_len = 0 + self.options.valley_dashes_bool = False + self.options.valley_bool_only = False + self.options.valley_bool = True + + self.options.edge_stroke_color = 255 + self.options.edge_dashes_len = 0 + self.options.edge_dashes_bool = False + self.options.edge_bool_only = False + self.options.edge_bool = True + + self.options.universal_stroke_color = 4278255615 + self.options.universal_dashes_len = 0 + self.options.universal_dashes_bool = False + self.options.universal_bool_only = False + self.options.universal_bool = True + + self.options.cut_stroke_color = 16711935 + self.options.cut_dashes_len = 0 + self.options.cut_dashes_bool = False + self.options.cut_bool_only = False + self.options.cut_bool = True + + self.options.vertex_bool = False + + + def create_styles_dict(self): + """ Get stroke style parameters and use them to create the styles dictionary, used for the Path generation + """ + unit_factor = self.calc_unit_factor() + + def create_style(type): + style = {'draw': getattr(self.options,type+"_bool"), + 'stroke': self.get_color_string(getattr(self.options,type+"_stroke_color")), + 'fill': 'none', + 'stroke-width': getattr(self.options,type+"_stroke_width") * unit_factor} + if getattr(self.options,type+"_dashes_bool"): + dash_gap_len = getattr(self.options,type+"_dashes_len") + duty = getattr(self.options,type+"_dashes_duty") + dash = (dash_gap_len * unit_factor) * duty + gap = (dash_gap_len * unit_factor) * (1 - duty) + style['stroke-dasharray'] = "{} {}".format(dash, gap) + return style + + self.styles_dict = {'m': create_style("mountain"), + 'v': create_style("valley"), + 'u': create_style("universal"), + 's': create_style("semicrease"), + 'c': create_style("cut"), + 'e': create_style("edge"), + 'p': create_style("vertex")} + + def get_color_string(self, longColor, verbose=False): + """ Convert the long into a #RRGGBB color value + - verbose=true pops up value for us in defaults + conversion back is A + B*256^1 + G*256^2 + R*256^3 + """ + # compatibility hack, no "long" in Python 3 + try: + longColor = long(longColor) + if longColor < 0: longColor = long(longColor) & 0xFFFFFFFF + hexColor = hex(longColor)[2:-3] + except: + longColor = int(longColor) + hexColor = hex(longColor)[2:-2] + inkex.debug = inkex.utils.debug + + hexColor = '#' + hexColor.rjust(6, '0').upper() + if verbose: inkex.debug("longColor = {}, hex = {}".format(longColor,hexColor)) + + return hexColor + + def add_text(self, node, text, position, text_height=12): + """ Create and insert a single line of text into the svg under node. + """ + line_style = {'font-size': '%dpx' % text_height, 'font-style':'normal', 'font-weight': 'normal', + 'fill': '#F6921E', 'font-family': 'Bitstream Vera Sans,sans-serif', + 'text-anchor': 'middle', 'text-align': 'center'} + line_attribs = {inkex.addNS('label','inkscape'): 'Annotation', + 'style': simplestyle.formatStyle(line_style), + 'x': str(position[0]), + 'y': str((position[1] + text_height) * 1.2) + } + line = inkex.etree.SubElement(node, inkex.addNS('text','svg'), line_attribs) + line.text = text + + + def calc_unit_factor(self): + """ Return the scale factor for all dimension conversions. + + - The document units are always irrelevant as + everything in inkscape is expected to be in 90dpi pixel units + """ + # namedView = self.document.getroot().find(inkex.addNS('namedview', 'sodipodi')) + # doc_units = self.getUnittouu(str(1.0) + namedView.get(inkex.addNS('document-units', 'inkscape'))) + # backwards compatibility + try: + return self.svg.unittouu(str(1.0) + self.options.units) + except: + try: + return inkex.unittouu(str(1.0) + self.options.units) + except AttributeError: + return self.unittouu(str(1.0) + self.options.units) + + + + + + diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Pleat_Circular.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Pleat_Circular.py new file mode 100755 index 0000000..d129dad --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Pleat_Circular.py @@ -0,0 +1,101 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +import numpy as np + +from math import pi, sin, cos + +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 + + +class PleatCircular(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('--pattern', type=self.str, default='pleat_circular') + self.add_argument('--radius', type=self.float, default=55.0) + self.add_argument('--ratio', type=self.float, default=0.4) + self.add_argument('--rings', type=self.int, default=15) + self.add_argument('--sides', type=self.int, default=20) + + def generate_path_tree(self): + """ Specialized path generation for your origami pattern + """ + # retrieve saved parameters + unit_factor = self.calc_unit_factor() + R = self.options.radius * unit_factor + ratio = self.options.ratio + r = R * ratio + rings = self.options.rings + dr = (1.-ratio)*R/rings + self.translate = (R, R) + + if not self.options.simulation_mode: + inner_circles = [] + for i in range(1, rings): + inner_circles.append(Path((0, 0), radius=r + i*dr, style='m' if i % 2 else 'v')) + + edges = [Path((0, 0), radius=R, style='e'), + Path((0, 0), radius=r, style='e')] + + self.path_tree = [inner_circles, edges] + + # append semicreases for simulation + else: + sides = self.options.sides + dtheta = pi / sides + # create diagonals + diagonals = [] + for i in range(sides): + p1 = (0, 0) + p2 = (R * cos((1 + i * 2) * dtheta), R * sin((1 + i * 2) * dtheta)) + diagonals.append(Path([p1, p2], 'u')) + + s = sin(dtheta) + c = cos(dtheta) + + # Edge + paths = [Path([(c * R, -s * R), (R, 0), (c * R, s * R)], style='e'), + Path([(c * r, -s * r), (r, 0), (c * r, s * r)], style='e')] + + # MV circles + for i in range(1, rings): + r_i = r + i * dr + paths.append(Path([(c * r_i, -s * r_i), (r_i, 0), (c * r_i, s * r_i)], + style='m' if i % 2 else 'v')) + + # Semicreases + top = [] + bottom = [] + for i in range(rings + 1): + r_i = r + i*dr + top.append((r_i*(1 + (i % 2)*(c-1)), -(i % 2)*s*r_i)) + bottom.append((r_i*(1 + (i % 2)*(c-1)), (i % 2)*s*r_i)) + paths = paths + [Path([(r, 0), (R, 0)], 's'), # straight line 1 + Path([(r*c, r*s), (R*c, R*s)], 's', invert=True), # straight line 2 + Path(top, 's'), # top half of semicrease pattern + Path(bottom, 's')] # bottom half of semicrease pattern + + all_paths = [paths] + for i in range(1, sides): + all_paths.append(Path.list_rotate(all_paths[0], i*2*dtheta)) + + self.path_tree = all_paths + + + + +# Main function, creates an instance of the Class and calls inkex.affect() to draw the origami on inkscape +if __name__ == '__main__': + e = PleatCircular() # remember to put the name of your Class here! + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/SupportRing.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/SupportRing.py new file mode 100755 index 0000000..ccbbd31 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/SupportRing.py @@ -0,0 +1,129 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +import numpy as np +from math import pi, sin, cos + +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 + + +class SupportRing(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('--sides', type=self.int, default=3) + self.add_argument('--radius_external', type=self.float, default=10.0) + self.add_argument('--inverted', type=self.bool, default=False) + self.add_argument('--single_stroke', type=self.bool, default=True) + self.add_argument('--radius_ratio', type=self.float, default=0.5) + self.add_argument('--radius_type', type=self.str, default='polygonal') + self.add_argument('--radius_draw', type=self.bool, default=True) + self.add_argument('--connector_length', type=self.float, default=3.0) + self.add_argument('--connector_thickness', type=self.float, default=3.0) + self.add_argument('--head_length', type=self.float, default=1.0) + self.add_argument('--head_thickness', type=self.float, default=1.0) + self.add_argument('--pattern', type=self.str, default='support ring') + + def generate_path_tree(self): + """ Specialized path generation for your origami pattern + """ + # retrieve conversion factor for selected unit + unit_factor = self.calc_unit_factor() + + # retrieve saved parameters, and apply unit factor where needed + + inverted = self.options.inverted + sign = -1 if inverted else 1 + single_stroke = self.options.single_stroke + radius_external = self.options.radius_external * unit_factor + radius_type = self.options.radius_type + radius_ratio = self.options.radius_ratio + radius_internal = radius_external / radius_ratio if inverted else radius_external * radius_ratio + # dradius = abs(radius_external-radius_internal) + sides = self.options.sides + connector_length = self.options.connector_length * unit_factor + connector_thickness = self.options.connector_thickness * unit_factor + head_length = self.options.head_length * unit_factor + head_thickness = self.options.head_thickness * unit_factor + + angle = pi / sides + length_external = 2 * radius_external * sin(angle) + length_internal = length_external / radius_ratio if inverted else length_external * radius_ratio + + external_points = [(-length_external/2, 0), + (-connector_thickness / 2, 0), + (-connector_thickness / 2, -connector_length*sign), + (-connector_thickness / 2 - head_thickness / 2, -connector_length*sign), + (-connector_thickness / 2, -(connector_length + head_length)*sign), + (0, -(connector_length + head_length)*sign), + (+connector_thickness / 2, -(connector_length + head_length)*sign), + (+connector_thickness / 2 + head_thickness / 2, -connector_length*sign), + (+connector_thickness / 2, -connector_length*sign), + (+connector_thickness / 2, 0), + (length_external/2, 0)] + + internal_points = [(0, 0), (length_internal, 0)] + + external_lines_0 = Path(external_points, 'm') + (length_external / 2, 0) + external_lines = [external_lines_0] + + for i in range(sides-1): + x, y = external_lines[-1].points[-1] + external_lines.append(external_lines_0*(1, 2*(i+1)*angle) + (x, y)) + + if single_stroke: + external_lines = Path(Path.get_points(external_lines), 'm') + + self.path_tree = [external_lines] + + if self.options.radius_draw == True: + + # center point of main strokes + outer_average = Path.get_average_point(external_lines) + + if radius_type == 'polygonal': + internal_lines_0 = Path(internal_points, 'm') + internal_lines = [internal_lines_0] + for i in range(sides - 1): + x, y = internal_lines[-1].points[-1] + internal_lines.append(internal_lines_0*(1, 2*(i+1)*angle) + (x, y)) + + # move to center + inner_average = Path.get_average_point(internal_lines) + delta = ((outer_average[0] - inner_average[0]), + (outer_average[1] - inner_average[1])) + + if single_stroke: + internal_lines = Path(Path.get_points(internal_lines), 'm') + + internal_lines = Path.list_add(internal_lines, delta) + elif radius_type == 'circular': + + internal_lines = Path(outer_average, radius=radius_internal, style='m') + + self.path_tree.append(internal_lines) + + + + + + + + +# Main function, creates an instance of the Class and calls self.draw() to draw the origami on inkscape +# self.draw() is either a call to inkex.affect() or to svg.run(), depending on python version +if __name__ == '__main__': + e = SupportRing() # remember to put the name of your Class here! + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Template.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Template.py new file mode 100755 index 0000000..288bc89 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Template.py @@ -0,0 +1,113 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +import numpy as np +from math import pi + +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 + + +class Template(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('--pattern', type=self.str, default='template1') + self.add_argument('--length', type=self.float, default=10.0) + self.add_argument('--angle', type=self.int, default=0) + self.add_argument('--fold_angle_valley', type=self.int, default=180) + + def generate_path_tree(self): + """ Specialized path generation for your origami pattern + """ + # retrieve conversion factor for selected unit + unit_factor = self.calc_unit_factor() + + # retrieve saved parameters, and apply unit factor where needed + length = self.options.length * unit_factor + vertex_radius = self.options.vertex_radius * unit_factor + pattern = self.options.pattern + angle = self.options.angle * pi / 180 + fold_angle_valley = self.options.fold_angle_valley + + # create all Path instances defining strokes + # first define its points as a list of tuples... + left_right_stroke_points = [(length / 2, 0), + (length / 2, length)] + up_down_stroke_points = [(0, length / 2), + (length, length / 2)] + + # doing the same for diagonals + diagonal_1_stroke_points = [(0, 0), + (length, length)] + diagonal_2_stroke_points = [(0, length), + (length, 0)] + + # ... and then create the Path instances, defining its type ('m' for mountain, etc...) + if pattern == 'template1': + up_down = [Path(left_right_stroke_points, 'm', fold_angle = 180.), + Path(up_down_stroke_points, 'm', fold_angle = 180.)] + + diagonals = [Path(diagonal_1_stroke_points, 'v', fold_angle = fold_angle_valley), + Path(diagonal_2_stroke_points, 'v', fold_angle = fold_angle_valley)] + + else: + up_down = [Path(left_right_stroke_points, 'v', fold_angle = fold_angle_valley), + Path(up_down_stroke_points, 'v', fold_angle = fold_angle_valley)] + + diagonals = [Path(diagonal_1_stroke_points, 'm', fold_angle = 180.), + Path(diagonal_2_stroke_points, 'm', fold_angle = 180. )] + + vertices = [] + for i in range(3): + for j in range(3): + vertices.append(Path(((i/2.) * length, (j/2.) * length), style='p', radius=vertex_radius)) + + # multiplication is implemented as a rotation, and list_rotate implements rotation for list of Path instances + vertices = Path.list_rotate(vertices, angle, (1 * length, 1 * length)) + up_down = Path.list_rotate(up_down, angle, (1 * length, 1 * length)) + diagonals = Path.list_rotate(diagonals, angle, (1 * length, 1 * length)) + + # if Path constructor is called with more than two points, a single stroke connecting all of then will be + # created. Using method generate_separated_paths, you can instead return a list of separated strokes + # linking each two points + + # create a list for edge strokes + # create path from points to be able to use the already built rotate method + edges = Path.generate_square(length, length, 'e', rotation = angle) + edges = Path.list_rotate(edges, angle, (1 * length, 1 * length)) + + # IMPORTANT: the attribute "path_tree" must be created at the end, saving all strokes + self.path_tree = [up_down, diagonals, vertices] + + # IMPORTANT: at the end, save edge points as "self.edge_points", to simplify selection of single or multiple + # strokes for the edge + self.edge_points = edges.points + + # if you decide not to declare "self.edge_points", then the edge must be explicitly created in the path_tree: + # self.path_tree = [mountains, valleys, vertices, edges] + + # FINAL REMARKS: + # division is implemented as a reflection, and list_reflect implements it for a list of Path instances + # here's a commented example: + # line_reflect = (0 * length, 2 * length, 1 * length, 1 * length) + # mountains = Path.list_reflect(mountains, line_reflect) + # valleys = Path.list_reflect(valleys, line_reflect) + # edges = Path.list_reflect(edges, line_reflect) + +# Main function, creates an instance of the Class and calls self.draw() to draw the origami on inkscape +# self.draw() is either a call to inkex.affect() or to svg.run(), depending on python version +if __name__ == '__main__': + e = Template() # remember to put the name of your Class here! + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Waterbomb.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Waterbomb.py new file mode 100755 index 0000000..b8db86b --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/Waterbomb.py @@ -0,0 +1,112 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +import math +import numpy as np + +import inkex + +from Path import Path +from Pattern import Pattern + +# TODO: +# Add fractional column number option + + +class Waterbomb(Pattern): + + def __init__(self): + """ Constructor + """ + Pattern.__init__(self) # Must be called in order to parse common options + + self.add_argument('--pattern', type=self.str, default='waterbomb') + self.add_argument('--pattern_first_line', type=self.str, default='waterbomb') + self.add_argument('--pattern_last_line', type=self.str, default='waterbomb') + self.add_argument('--lines', type=self.int, default=8) + self.add_argument('--columns', type=self.int, default=16) + self.add_argument('--length', type=self.float, default=10.0) + self.add_argument('--phase_shift', type=self.bool, default=True) + + def generate_path_tree(self): + """ Specialized path generation for Waterbomb tesselation pattern + """ + unit_factor = self.calc_unit_factor() + length = self.options.length * unit_factor + vertex_radius = self.options.vertex_radius * unit_factor + cols = self.options.columns + lines = self.options.lines + phase_shift = self.options.phase_shift + pattern_first_line = self.options.pattern_first_line + pattern_last_line = self.options.pattern_last_line + + # create vertices + vertex_line_types = [[Path(((i / 2.) * length, 0), style='p', radius=vertex_radius) for i in range(2*cols + 1)], + [Path((i * length, 0), style='p', radius=vertex_radius) for i in range(cols + 1)], + [Path(((i + 0.5) * length, 0), style='p', radius=vertex_radius) for i in range(cols)]] + + vertices = [] + for i in range(2*lines + 1): + if i % 2 == 0 or (pattern_first_line == 'magic_ball' and i == 1) or (pattern_last_line == 'magic_ball' and i == 2*lines - 1): + type = 0 + elif(int(i/2 + phase_shift)) % 2 == 0: + type = 1 + else: + type = 2 + vertices = vertices + Path.list_add(vertex_line_types[type], (0, 0.5*i*length)) + + # create a list for the horizontal creases and another for the vertical creases + # alternate strokes to minimize laser cutter path + corr_fist_line = length/2 if pattern_first_line == 'magic_ball' else 0 + corr_last_line = length/2 if pattern_last_line == 'magic_ball' else 0 + grid = [Path.generate_hgrid([0, length*cols], [0, length*lines], lines, 'm'), + Path.generate_vgrid([0, length*cols], [corr_fist_line, length*lines-corr_last_line], 2*cols, 'm')] + + vgrid_a = Path.generate_vgrid([0, length * cols], [0, length / 2], 2 * cols, 'v') + vgrid_b = Path.list_add(vgrid_a, (0, (lines - 0.5) * length)) + if pattern_first_line == 'magic_ball' and pattern_last_line == 'magic_ball': + grid[1] = [[vgrid_a[i], grid[1][i], vgrid_b[i]] if i % 2 == 0 else + [vgrid_b[i], grid[1][i], vgrid_a[i]] for i in range(len(grid[1]))] + elif pattern_first_line == 'magic_ball': + grid[1] = [[vgrid_a[i], grid[1][i]] if i % 2 == 0 else + [grid[1][i], vgrid_a[i]] for i in range(len(grid[1]))] + elif pattern_last_line == 'magic_ball': + grid[1] = [[grid[1][i], vgrid_b[i]] if i % 2 == 0 else + [vgrid_b[i], grid[1][i]] for i in range(len(grid[1]))] + + # create generic valley Path lines, one pointing up and other pointing down + valley_types = [Path([(i * length / 2, (1 - i % 2) * length / 2) for i in range(2 * cols + 1)], 'v'), + Path([( i*length/2, (i % 2)*length/2) for i in range(2 * cols + 1)], 'v')] + + # define which lines must be of which type, according to parity and options + senses = np.array([bool((i % 2+i)/2 % 2) for i in range(2*lines)]) + senses = 1*senses # converts bool array to 0's and 1's + if phase_shift: + senses = np.invert(senses) + if pattern_first_line == "magic_ball": + senses[0] = ~senses[0] + if pattern_last_line == "magic_ball": + senses[-1] = ~senses[-1] + valleys = [valley_types[senses[i]] + (0, i * length / 2) for i in range(2*lines)] + + # convert first and last lines to mountains if magic_ball + if pattern_first_line == "magic_ball": + valleys[0].style = 'm' + if pattern_last_line == "magic_ball": + valleys[-1].style = 'm' + + # invert every two lines to minimize laser cutter movements + for i in range(1, 2*lines, 2): + valleys[i].invert() + + self.edge_points = [(0*length*cols, 0*length*lines), # top left + (1*length*cols, 0*length*lines), # top right + (1*length*cols, 1*length*lines), # bottom right + (0*length*cols, 1*length*lines)] # bottom left + + self.path_tree = [grid, valleys, vertices] + + +if __name__ == '__main__': + + e = Waterbomb() + e.draw() diff --git a/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/__init__.py b/extensions/fablabchemnitz/origami_patterns/OrigamiPatterns/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/extensions/fablabchemnitz/origami_patterns/Support_Ring_Belt/Belt.scad b/extensions/fablabchemnitz/origami_patterns/Support_Ring_Belt/Belt.scad new file mode 100755 index 0000000..a103439 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/Support_Ring_Belt/Belt.scad @@ -0,0 +1,31 @@ +module draw_cone(R,n,h,thickness){ + difference(){ + cylinder(h=h, r1=R+thickness, r2=R+thickness, center=true, $fn=n); + cylinder(h=h, r1=R, r2=R, center=true, $fn=n); + cylinder(h=h, r1=R, r2=R, center=true, $fn=n); + } +} + +module draw_cut_boxes(R, n, thickness, slot_height, slot_width){ + union(){ + for (i=[0: n/2]){ + rotate(a=i*360/n, v=[0,0,1]) + cube([2*(R+thickness),slot_height,slot_width], center=true); + } + } +} + +module draw_belt(R, n, h, thickness, slot_height, slot_width){ + difference(){ + rotate(a=180/n, v=[0,0,1]) draw_cone(R, n, h, thickness); + draw_cut_boxes(R, n, thickness, slot_height, slot_width); + } +} + + + + + + + + diff --git a/extensions/fablabchemnitz/origami_patterns/Support_Ring_Belt/Belt_main.scad b/extensions/fablabchemnitz/origami_patterns/Support_Ring_Belt/Belt_main.scad new file mode 100755 index 0000000..55eda7b --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/Support_Ring_Belt/Belt_main.scad @@ -0,0 +1,13 @@ +include + +// Input parameters +R = 35; +n = 8; +height = 7; // height of support +thickness = 1; // thickness of belt + +// square cuts to be pierced by support ring +slot_height=3; // must be smaller than height +slot_width=3; + +draw_belt(R, n, height, thickness, slot_height, slot_width); \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/Support_Ring_Belt/README.md b/extensions/fablabchemnitz/origami_patterns/Support_Ring_Belt/README.md new file mode 100755 index 0000000..04b6568 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/Support_Ring_Belt/README.md @@ -0,0 +1,5 @@ +# Origami_Patterns_Support_Belt + +Designed to be used with the [Origami Patterns Inkscape extension](https://github.com/evbernardes/Origami_Patterns). + +3D Print this with TPU (or any other flexible material) and use together with the support rings. diff --git a/extensions/fablabchemnitz/origami_patterns/logo.svg b/extensions/fablabchemnitz/origami_patterns/logo.svg new file mode 100755 index 0000000..c55ee6a --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/logo.svg @@ -0,0 +1,384 @@ + + + Inkscape Logo + + + + + + + + image/svg+xml + + + + Andy Fitzsimon + + + + + Andrew Michael Fitzsimon + + + + + Fitzsimon IT Consulting Pty Ltd + + + http://andy.fitzsimon.com.au + 2006 + + Inkscape Logo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/fablabchemnitz/origami_patterns/meta.json b/extensions/fablabchemnitz/origami_patterns/meta.json new file mode 100644 index 0000000..0d68585 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/meta.json @@ -0,0 +1,21 @@ +[ + { + "name": "Origami Pattern - ", + "id": "fablabchemnitz.de.origami_patterns.", + "path": "origami_patterns", + "dependent_extensions": null, + "original_name": "", + "original_id": "org.inkscape.Origami_patterns.", + "license": "MIT License", + "license_url": "https://github.com/evbernardes/Origami_Patterns/blob/master/LICENSE", + "comment": "", + "source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/origami_patterns", + "fork_url": "https://github.com/evbernardes/Origami_Patterns", + "documentation_url": "https://stadtfabrikanten.org/display/IFM/Origami+Patterns", + "inkscape_gallery_url": null, + "main_authors": [ + "github.com/evbernardes", + "github.com/vmario89" + ] + } +] \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/origami_patterns_boxes_masu.inx b/extensions/fablabchemnitz/origami_patterns/origami_patterns_boxes_masu.inx new file mode 100644 index 0000000..664e149 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/origami_patterns_boxes_masu.inx @@ -0,0 +1,69 @@ + + + Masu box (width and height) + org.inkscape.Origami_patterns.boxes_masu + + + 10.0 + 10.0 + + 0.0 + false + + + + + + + + + true + + + true + false + true + 1 + 0.5 + 0.1 + 4278190335 + + + true + false + true + 1 + 0.25 + 0.1 + 65535 + + + true + false + true + false + 1 + 0.25 + 0.1 + 255 + + + true + false + 0.1 + 0.1 + 255 + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/origami_patterns_boxes_masu_traditional.inx b/extensions/fablabchemnitz/origami_patterns/origami_patterns_boxes_masu_traditional.inx new file mode 100644 index 0000000..28db195 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/origami_patterns_boxes_masu_traditional.inx @@ -0,0 +1,65 @@ + + + Masu box (traditional) + org.inkscape.Origami_patterns.boxes_masu_traditional + + + 10.0 + + + + + + + + + true + + + true + false + true + 1 + 0.5 + 0.1 + 4278190335 + + + true + false + true + 1 + 0.25 + 0.1 + 65535 + + + true + false + true + false + 1 + 0.25 + 0.1 + 255 + + + true + false + 0.1 + 0.1 + 255 + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/origami_patterns_cylindrical_bendy.inx b/extensions/fablabchemnitz/origami_patterns/origami_patterns_cylindrical_bendy.inx new file mode 100755 index 0000000..73b7231 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/origami_patterns_cylindrical_bendy.inx @@ -0,0 +1,111 @@ + + + Bendy Straw + org.inkscape.Origami_patterns.cylindrical_bendy + + + + + + + + + + 6 + 3 + + 25.0 + 0.75 + + + + + + 45 + 35 + 1 + 2 + + + + + + + + + true + + + false + + 5.0 + false + false + 3.0 + 3.0 + + 3.0 + false + 3.0 + 3.0 + + + true + false + true + 1 + 0.5 + 0.1 + 4278190335 + + + true + false + true + 1 + 0.25 + 0.1 + 65535 + + + true + false + 1 + 0.25 + 0.1 + 16711935 + + + true + false + true + false + 1 + 0.25 + 0.1 + 255 + + + true + false + true + true + true + true + 0.1 + 0.1 + 255 + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/origami_patterns_cylindrical_kresling.inx b/extensions/fablabchemnitz/origami_patterns/origami_patterns_cylindrical_kresling.inx new file mode 100644 index 0000000..26a983f --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/origami_patterns_cylindrical_kresling.inx @@ -0,0 +1,122 @@ + + + Kresling tower + org.inkscape.Origami_patterns.cylindrical_kresling + + + + + + + + 3 + 6 + + 10.0 + + + + + + + + + + + + + + + + + true + + + + + + + 0.5 + 0.5 + 60.0 + + + false + + false + + + + + + 5.0 + 3.0 + 3.0 + + false + + + + + + 3.0 + 3.0 + 3.0 + + + true + true + false + 1 + 0.25 + 0.1 + 16711935 + + + true + false + true + 1 + 0.5 + 0.1 + 4278190335 + + + true + false + true + 1 + 0.25 + 0.1 + 65535 + + + true + false + true + false + 1 + 0.25 + 0.1 + 255 + + + true + false + 0.1 + 0.1 + 255 + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/origami_patterns_cylindrical_template.inx b/extensions/fablabchemnitz/origami_patterns/origami_patterns_cylindrical_template.inx new file mode 100755 index 0000000..31c06e8 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/origami_patterns_cylindrical_template.inx @@ -0,0 +1,95 @@ + + + * Cylindrical template effect + org.inkscape.Origami_patterns.cylindrical_template + + + + + 10.0 + 6 + 3 + + 10.0 + 0 + + + + + + + + + true + + + false + + false + + + + + + 5.0 + 3.0 + 3.0 + + false + + + + + + 3.0 + 3.0 + 3.0 + + + true + false + true + 1 + 0.5 + 0.1 + 4278190335 + + + true + false + true + 1 + 0.25 + 0.1 + 65535 + + + true + false + true + false + 1 + 0.25 + 0.1 + 255 + + + true + false + 0.1 + 0.1 + 255 + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/origami_patterns_misc_support_ring.inx b/extensions/fablabchemnitz/origami_patterns/origami_patterns_misc_support_ring.inx new file mode 100755 index 0000000..854d460 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/origami_patterns_misc_support_ring.inx @@ -0,0 +1,53 @@ + + + * Support ring + org.inkscape.Origami_patterns.support_ring + + + 6 + 10.0 + false + true + + 3.0 + 3.0 + 1.0 + 1.0 + + true + 0.5 + + + + + + + + + + + + + + + + + false + 1 + 0.5 + 0.1 + 4278190335 + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/origami_patterns_old_bendy.inx b/extensions/fablabchemnitz/origami_patterns/origami_patterns_old_bendy.inx new file mode 100755 index 0000000..d6ace9e --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/origami_patterns_old_bendy.inx @@ -0,0 +1,107 @@ + + + Bendy Straw (old, faster cutting) + org.inkscape.Origami_patterns.old_bendy + + + + + + + + + + 6 + 3 + + 25.0 + 0.75 + + + + + + 45 + 35 + 1 + 2 + + + + + + + + + true + + + false + + 5.0 + false + false + 3.0 + 3.0 + + 3.0 + false + 3.0 + 3.0 + + + true + true + 1 + 0.5 + 0.1 + 4278190335 + + + true + true + 1 + 0.25 + 0.1 + 65535 + + + true + false + 1 + 0.25 + 0.1 + 16711935 + + + true + true + false + 1 + 0.25 + 0.1 + 255 + + + true + true + true + true + true + 0.1 + 0.1 + 255 + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/origami_patterns_old_kresling.inx b/extensions/fablabchemnitz/origami_patterns/origami_patterns_old_kresling.inx new file mode 100755 index 0000000..9ecaa07 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/origami_patterns_old_kresling.inx @@ -0,0 +1,95 @@ + + + Kresling tower (old) + org.inkscape.Origami_patterns.old_kresling + + + + + + + + 3 + 6 + + 10.0 + + + + + + + + + + + + + + + + + + + + + + + 0.5 + 0.5 + 60.0 + true + + + false + 100 + + + + true + false + true + 1 + 0.5 + 0.1 + 4278190335 + + + true + false + true + 1 + 0.25 + 0.1 + 65535 + + + true + false + true + false + 1 + 0.25 + 0.1 + 255 + + + true + false + 0.1 + 0.1 + 255 + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/origami_patterns_pleat_circular.inx b/extensions/fablabchemnitz/origami_patterns/origami_patterns_pleat_circular.inx new file mode 100755 index 0000000..196882f --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/origami_patterns_pleat_circular.inx @@ -0,0 +1,78 @@ + + + Circular + org.inkscape.Origami_patterns.pleat_circular + + + 55.0 + + + + + + + + 0.4 + 15 + + true + + 20 + + + true + false + true + 1 + 0.5 + 0.1 + 4278190335 + + + true + false + true + 1 + 0.25 + 0.1 + 65535 + + + true + false + true + false + 1 + 0.25 + 0.1 + 255 + + + true + false + 0.1 + 0.1 + 255 + + + true + false + false + 1 + 0.25 + 0.1 + 4294902015 + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/origami_patterns_pleat_hypar.inx b/extensions/fablabchemnitz/origami_patterns/origami_patterns_pleat_hypar.inx new file mode 100755 index 0000000..250d605 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/origami_patterns_pleat_hypar.inx @@ -0,0 +1,82 @@ + + + N-sided Hypar + org.inkscape.Origami_patterns.pleat_hypar + + + + + + + + 100.0 + + + + + + + + 4 + 7 + false + + true + + + true + false + true + 1 + 0.5 + 0.1 + 4278190335 + + + true + false + true + 1 + 0.25 + 0.1 + 65535 + + + true + false + false + 1 + 0.25 + 0.1 + 4278255615 + + + true + false + true + false + 1 + 0.25 + 0.1 + 255 + + + true + false + 0.1 + 0.1 + 255 + + + + all + + + + + + + + diff --git a/extensions/fablabchemnitz/origami_patterns/origami_patterns_template.inx b/extensions/fablabchemnitz/origami_patterns/origami_patterns_template.inx new file mode 100755 index 0000000..1c2b0c2 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/origami_patterns_template.inx @@ -0,0 +1,72 @@ + + + Template effect + org.inkscape.Origami_patterns.template + + + + + + + 10.0 + + + + + + + + 0 + 180 + + true + + + + true + false + true + 1 + 0.5 + 0.1 + 4278190335 + + + true + false + true + 1 + 0.25 + 0.1 + 65535 + + + true + false + true + false + 1 + 0.25 + 0.1 + 255 + + + true + false + 0.1 + 0.1 + 255 + + + + all + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/origami_patterns/origami_patterns_waterbomb.inx b/extensions/fablabchemnitz/origami_patterns/origami_patterns_waterbomb.inx new file mode 100755 index 0000000..46f5337 --- /dev/null +++ b/extensions/fablabchemnitz/origami_patterns/origami_patterns_waterbomb.inx @@ -0,0 +1,77 @@ + + + Waterbomb + org.inkscape.Origami_patterns.magic_ball + + + + + + + + + + + false + + 8 + 16 + + 10.0 + + + + + + + + + true + + + true + false + true + 1 + 0.5 + 0.1 + 4278190335 + + + true + false + true + 1 + 0.25 + 0.1 + 65535 + + + true + false + true + false + 1 + 0.25 + 0.1 + 255 + + + true + false + 0.1 + 0.1 + 255 + + + + all + + + + + + \ No newline at end of file