Source code for foldable_robotics.laminate

# -*- coding: utf-8 -*-
"""
Written by Daniel M. Aukes and CONTRIBUTORS
Email: danaukes<at>asu.edu.
Please see LICENSE for full license.
"""

from .class_algebra import ClassAlgebra
from . import geometry
import matplotlib.pyplot as plt
#from .iterable import Iterable
from .layer import Layer
import numpy
from idealab_tools.iterable import Iterable
import foldable_robotics
import foldable_robotics.jupyter_support as fj

[docs]class WrongNumLayers(Exception): '''Custom exception for when two laminates of the wrong number of layers interact.''' pass
[docs]class Laminate(Iterable,ClassAlgebra): '''The Laminate class holds a list of layers and can be operated upon with CSG-style functions.''' def __init__(self, *layers): ''' Initializes the class :param layers: A list of layers which compose the laminate :type layers: list :rtype: Laminate ''' self.layers = list(layers) self.id = id(self)
[docs] def copy(self,identical = True): ''' creates a copy of the instance :param identical: whether to use the same id or not. :type identical: boolean :rtype: Laminate ''' new = type(self)(*[layer.copy(identical) for layer in self.layers]) if identical: new.id = self.id return new
[docs] def export_dict(self): ''' converts the laminate to a dict. :rtype: dict ''' d = {} d['layers'] = [layer.export_dict() for layer in self.layers] d['id'] = self.id return d
[docs] @classmethod def import_dict(cls,d): ''' converts a dict to a Laminate class :param d: the laminate in dict form :type d: dict :rtype: Laminate ''' new = cls(*[Layer.import_dict(item) for item in d['layers']]) new.id = d['id'] return new
[docs] def is_null(self): results = [item.is_null() for item in self] return all(results)
[docs] def plot(self,new=False): ''' plots the laminate using matplotlib. :param new: whether to create a new figure :type new: boolean ''' colors = self.gen_colors(.25) try: d,e=self.bounding_box_coords() if new: plt.figure() for layer,color in zip(self.layers,colors): layer.plot(color = color) ax = plt.gca() ax.axis([d[0],e[0],d[1],e[1]]) except foldable_robotics.layer.NoGeoms: pass
[docs] def gen_colors(self,alpha=None): import matplotlib.cm cm = matplotlib.cm.plasma l = len(self.layers) if l>1: colors = numpy.array([cm(ii/(l-1)) for ii in range(l)]) else: colors = numpy.array([cm(1)]) if alpha is not None: colors[:,3] = alpha colors = [tuple(item) for item in colors] return colors
[docs] def plot_layers(self): ''' plots each layer of the laminate in a new figure using matplotlib. ''' colors = self.gen_colors() for color,geom in zip(colors,self.layers): plt.figure() geom.plot(color = color)
def _repr_svg_(self): """SVG representation for iPython notebook""" svg_top = '<svg xmlns="http://www.w3.org/2000/svg" ' \ 'xmlns:xlink="http://www.w3.org/1999/xlink" ' if all([not item.geoms for item in self.layers]): return svg_top + '/>' else: (xmin, ymin), (xmax, ymax) = self.bounding_box_coords() if xmin == xmax and ymin == ymax: (xmin, ymin), (xmax, ymax) = self.buffer(1).bounds else: expand = 0.04 widest_part = max([xmax - xmin, ymax - ymin]) expand_amount = widest_part * expand xmin -= expand_amount ymin -= expand_amount xmax += expand_amount ymax += expand_amount dx = xmax - xmin dy = ymax - ymin width = min([max([100., dx]), 300]) height = min([max([100., dy]), 300]) try: scale_factor = max([dx, dy]) / max([width, height]) except ZeroDivisionError: scale_factor = 1. view_box = "{} {} {} {}".format(xmin, ymin, dx, dy) transform = "matrix(1,0,0,-1,0,{})".format(ymax + ymin) return svg_top + ('width="{1}" height="{2}" viewBox="{0}" ' 'preserveAspectRatio="xMinYMin meet">' '<g transform="{3}">{4}</g></svg>').format(view_box, width, height, transform,self.svg(scale_factor))
[docs] def svg(self, scale_factor=1.): ''' returns the svg ''' if all([not layer.geoms for layer in self.layers]): return '<g />' s = '<g>' for layer,color in zip(self.layers,self.gen_colors()): c = fj.color_tuple_to_hex(color) for geom in layer.geoms: s+=geom.svg(scale_factor, c) s+='</g>' return s
[docs] def get_dimensions(self): min1,max1 = self.bounding_box_coords() min1=numpy.array(min1) max1=numpy.array(max1) width,height = max1-min1 return width, height
[docs] def plot_3d(self,material_properties=None): import idealab_tools.plot_tris as pt material_properties = self.create_material_properties() mi = self.mesh_items(material_properties) pt.plot_mi(mi)
[docs] def create_material_properties(self): colors = self.gen_colors() m = [] for color,layer in zip(colors,self): m.append(layer.create_material_property(color=color)) return m
@property def list(self): '''converts the laminate to a list.''' return self.layers
[docs] def binary_operation(self,function_name,other,*args,**kwargs): ''' performs a binary operation between self and other. :param function_name: the layer-based function to be performed :type function_name: string :param other: the layer-based function to be performed :type other: Laminate :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' if len(self.layers)!=len(other.layers): raise(WrongNumLayers()) else: layers = [] for layer1,layer2 in zip(self.layers,other.layers): function = getattr(layer1,function_name) layers.append(function(layer2,*args,**kwargs)) return type(self)(*layers)
[docs] def unary_operation(self,function_name,*args,**kwargs): ''' performs a unary operation on the laminate. :param function_name: the layer-based function to be performed :type function_name: string :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' layers = [] for layer1 in self.layers: function = getattr(layer1,function_name) layers.append(function(*args,**kwargs)) return type(self)(*layers)
[docs] def union(self,other): ''' unions two laminates together. :param other: the other laminate :type other: Laminate :rtype: Laminate ''' return self.binary_operation('union',other)
[docs] def difference(self,other): ''' takes the difference of self - other. :param other:the other laminate :type other: Laminate :rtype: Laminate ''' return self.binary_operation('difference',other)
[docs] def symmetric_difference(self,other): ''' takes the symmetric difference of self and other. :param other: the other laminate :type other: Laminate :rtype: Laminate ''' return self.binary_operation('symmetric_difference',other)
[docs] def intersection(self,other): ''' takes the intersection of self and other. :param other: the other laminate :type other: Laminate :rtype: Laminate ''' return self.binary_operation('intersection',other)
[docs] def buffer(self,*args,**kwargs): ''' dilate or erode the laminate based on the arguments sent. see the corresponding layer function for arguments and keyword-arguments :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' return self.unary_operation('buffer',*args,**kwargs)
[docs] def dilate(self,*args,**kwargs): ''' dilate the laminate. see the corresponding layer function for arguments and keyword-arguments :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' return self.unary_operation('dilate',*args,**kwargs)
[docs] def erode(self,*args,**kwargs): ''' erode the laminate. see the corresponding layer function for arguments and keyword-arguments :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' return self.unary_operation('erode',*args,**kwargs)
[docs] def translate(self,*args,**kwargs): ''' translate the laminate. see the corresponding layer function for arguments and keyword-arguments :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' return self.unary_operation('translate',*args,**kwargs)
[docs] def rotate(self,*args,**kwargs): ''' rotate the laminate. see the corresponding layer function for arguments and keyword-arguments :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' return self.unary_operation('rotate',*args,**kwargs)
[docs] def scale(self,*args,**kwargs): ''' scale the laminate. see the corresponding layer function for arguments and keyword-arguments :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' return self.unary_operation('scale',*args,**kwargs)
[docs] def affine_transform(self,*args,**kwargs): ''' affine transform the laminate. see the corresponding layer function for arguments and keyword-arguments :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' return self.unary_operation('affine_transform',*args,**kwargs)
[docs] def simplify(self,*args,**kwargs): ''' simplify the laminate. see the corresponding layer function for arguments and keyword-arguments :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' return self.unary_operation('simplify',*args,**kwargs)
[docs] def map_line_stretch(self,*args,**kwargs): ''' transform the laminate based on two lines. see foldable_robotics.manufacturing.map_line_stretch for arguments and keyword-arguments :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' import foldable_robotics.manufacturing return foldable_robotics.manufacturing.map_line_stretch(self,*args,**kwargs)
[docs] def map_line_scale(self,*args,**kwargs): ''' transform the laminate based on two lines. see foldable_robotics.manufacturing.map_line_stretch for arguments and keyword-arguments :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' import foldable_robotics.manufacturing return foldable_robotics.manufacturing.map_line_scale(self,*args,**kwargs)
[docs] def map_line_place(self,*args,**kwargs): ''' transform the laminate based on two lines. see foldable_robotics.manufacturing.map_line_stretch for arguments and keyword-arguments :param args: tuple of arguments passed to subfunction :type args: tuple :param kwargs: keyword arguments passed to subfunction :type kwargs: dict :rtype: Laminate ''' import foldable_robotics.manufacturing return foldable_robotics.manufacturing.map_line_place(self,*args,**kwargs)
[docs] def export_dxf(self,name): ''' export the laminate to a .dxf file see the corresponding layer function for arguments and keyword-arguments :param name: filename to export :type name: string ''' for ii,layer in enumerate(self.layers): layername = name+str(ii) layer.export_dxf(layername)
[docs] def mesh_items(self,material_properties): ''' create a mesh for 3d rendering using pyqtgraph :param material_properties: tuple of arguments passed to subfunction :type material_properties: foldable_robotics.dynamics_info.MaterialProperty :rtype: GLMeshItem ''' import matplotlib.cm as cm import pyqtgraph.opengl as gl z = 0 vs = [] cs = [] for ii,(layer,mp) in enumerate(zip(self,material_properties)): v,c = layer.mesh_items_inner(z+mp.thickness/2,mp.color) vs.append(v) cs.append(c) z+=mp.thickness verts_outer = numpy.vstack(vs) colors_outer = numpy.vstack(cs) mi= gl.GLMeshItem(vertexes=verts_outer,vertexColors=colors_outer,smooth=False,shader='balloon',drawEdges=False) return mi
[docs] def mass_properties(laminate,material_properties): ''' calculate mass properties :param material_properties: tuple of arguments passed to subfunction :type material_properties: foldable_robotics.dynamics_info.MaterialProperty :rtype: tuple of mass properties ''' volume = 0 mass = 0 z=0 centroid_x=0 centroid_y=0 centroid_z=0 for ii,layer in enumerate(laminate): bottom = z top = z+material_properties[ii].thickness area=0 area_i,volume_i,mass_i,centroid_i = layer.mass_props(material_properties[ii],bottom,top) centroid_x_i,centroid_y_i,centroid_z_i = centroid_i area+=area_i volume+=volume_i mass+=mass_i centroid_x += centroid_x_i*mass_i centroid_y += centroid_y_i*mass_i centroid_z += centroid_z_i*mass_i z=top centroid_x /= mass centroid_y /= mass centroid_z /= mass centroid = (centroid_x,centroid_y,centroid_z) I=numpy.zeros((3,3)) bottom = 0 for ii,layer in enumerate(laminate): cn = numpy.array(centroid) I+=layer.inertia(cn,bottom,material_properties[ii]) bottom += material_properties[ii].thickness return mass,volume,centroid,I
[docs] def bounding_box(self): '''create a bounding box of the layer and return as a layer''' import foldable_robotics.manufacturing l = foldable_robotics.manufacturing.unary_union(self) box = l.bounding_box() box = box.to_laminate(len(self)) return box
[docs] def bounding_box_coords(self): '''compute the lower left hand and upper right coordinates for computing a bounding box of the layer''' import foldable_robotics.manufacturing l = foldable_robotics.manufacturing.unary_union(self) val = l.bounding_box_coords() return val
[docs] def unary_union(self,*others): l = [len(item) for item in [self]+list(others)] if len(set(l))!=1: raise(WrongNumLayers()) l = l[0] layers = [] for ii in range(l): other_ii = [item[ii] for item in others] layer = self[ii].unary_union(*other_ii) layers.append(layer) return type(self)(*layers)
[docs] def to_mesh(self,layer_thickness,characteristic_len_min = None,characteristic_len_max = None): '''create a mesh object with gmsh''' import foldable_robotics.gmsh_support as gs geofile= gs.laminate_to_geo(self,layer_thickness,characteristic_len_min,characteristic_len_max) mesh_data = geofile.make_mesh() return mesh_data
if __name__=='__main__': from layer import Layer import shapely.geometry as sg l=Layer(sg.Polygon([(0,0),(1,0),(0,1)])) l2 = l | l.translate(2,0) lam=Laminate(l,l.translate(.1,.1)) l1 = Layer(sg.LineString([(0,0),(1,0),(0,1)])) l1.plot(new=True) lam._repr_svg_()