#!/usr/bin/env python # -*- coding: utf-8 -*- import inkex import simplestyle import math import string import simpletransform # Function for calculating a point from the origin when you know the distance # and the angle def calculatePoint(angle, distance): if (angle < 0 or angle > 360): return None else: return [ distance * math.cos(math.radians(angle)), distance * math.sin(math.radians(angle))] class EncoderDiskGenerator(inkex.Effect): def __init__(self): inkex.Effect.__init__(self) self.OptionParser.add_option("--tab", action="store", type="string", dest="tab", default="rotary_enc", help="Selected tab") self.OptionParser.add_option("--diameter", action="store", type="float", dest="diameter", default=0.0, help="Diameter of the encoder disk") self.OptionParser.add_option("--hole_diameter", action="store", type="float", dest="hole_diameter", default=0.0, help="Diameter of the center hole") self.OptionParser.add_option("--segments", action="store", type="int", dest="segments", default=0, help="Number of segments") self.OptionParser.add_option("--outer_encoder_diameter", action="store", type="float", dest="outer_encoder_diameter", default=0.0, help="Diameter of the outer encoder disk") self.OptionParser.add_option("--outer_encoder_width", action="store", type="float", dest="outer_encoder_width", default=0.0, help="Width of the outer encoder disk") self.OptionParser.add_option("--inner_encoder_diameter", action="store", type="float", dest="inner_encoder_diameter", default=0.0, help="Diameter of the inner encoder disk") self.OptionParser.add_option("--inner_encoder_width", action="store", type="float", dest="inner_encoder_width", default=0.0, help="Width of the inner encoder disk") self.OptionParser.add_option("--bits", action="store", type="int", dest="bits", default=1, help="Number of bits/tracks") self.OptionParser.add_option("--encoder_diameter", action="store", type="float", dest="encoder_diameter", default=0.0, help="Outer diameter of the last track") self.OptionParser.add_option("--track_width", action="store", type="float", dest="track_width", default=0.0, help="Width of one track") self.OptionParser.add_option("--track_distance", action="store", type="float", dest="track_distance", default=0.0, help="Distance between tracks") self.OptionParser.add_option("--bm_diameter", action="store", type="float", dest="bm_diameter", default=0.0, help="Diameter of the encoder disk") self.OptionParser.add_option("--bm_hole_diameter", action="store", type="float", dest="bm_hole_diameter", default=0.0, help="Diameter of the center hole") self.OptionParser.add_option("--bm_bits", action="store", type="string", dest="bm_bits", default="", help="Bits of segments") self.OptionParser.add_option("--bm_outer_encoder_diameter", action="store", type="float", dest="bm_outer_encoder_diameter", default=0.0, help="Diameter of the outer encoder disk") self.OptionParser.add_option("--bm_outer_encoder_width", action="store", type="float", dest="bm_outer_encoder_width", default=0.0, help="Width of the outer encoder disk") self.OptionParser.add_option("--brgc_diameter", action="store", type="float", dest="brgc_diameter", default=0.0, help="Diameter of the encoder disk") self.OptionParser.add_option("--stgc_diameter", action="store", type="float", dest="stgc_diameter", default=0.0, help="Diameter of the encoder disk") self.OptionParser.add_option("--brgc_hole_diameter", action="store", type="float", dest="brgc_hole_diameter", default=0.0, help="Diameter of the center hole") self.OptionParser.add_option("--cutouts", action="store", type="int", dest="cutouts", default=1, help="Number of cutouts") self.OptionParser.add_option("--sensors", action="store", type="int", dest="sensors", default=1, help="Number of sensors") self.OptionParser.add_option("--stgc_hole_diameter", action="store", type="float", dest="stgc_hole_diameter", default=0.0, help="Diameter of the center hole") self.OptionParser.add_option("--stgc_encoder_diameter", action="store", type="float", dest="stgc_encoder_diameter", default=0.0, help="Outer diameter of the last track") self.OptionParser.add_option("--stgc_track_width", action="store", type="float", dest="stgc_track_width", default=0.0, help="Width of track") # This function just concatenates the point and the command and returns # the data string def parsePathData(self, command, point): path_data = command + ' %f ' % point[0] + ' %f ' % point[1] return path_data # Creates a gray code of size bits (n >= 1) in the format of a list def createGrayCode(self, bits): gray_code = [[False], [True]] if bits == 1: return gray_code for i in range(bits - 1): temp = [] # Reflect values for j in range(len(gray_code[0]), 0, -1): for k in range(0, len(gray_code)): if j == len(gray_code[0]): temp.append([gray_code[k][-j]]) else: temp[k].append(gray_code[k][-j]) while temp: gray_code.append(temp.pop()) # Add False to the "old" values and true to the new ones for j in range(0, len(gray_code)): if j < len(gray_code) / 2: gray_code[j].insert(0, False) else: gray_code[j].insert(0, True) temp = [] return gray_code # This function returns the segments for a gray encoder def drawGrayEncoder(self, line_style, bits, encoder_diameter, track_width, track_distance): gray_code = self.createGrayCode(bits) segments = [] segment_size = 0 start_angle_position = 0 index = 0 current_encoder_diameter = encoder_diameter previous_item = False position_size = 360.0 / (2 ** bits) for i in range(len(gray_code[0]) - 1, -1, -1): for j in gray_code: if j[i]: segment_size += 1 if segment_size == 1: start_angle_position = index previous_item = True elif not j[i] and previous_item: segments.append( self.drawSegment(line_style, start_angle_position * position_size, segment_size * position_size, current_encoder_diameter, track_width)) segment_size = 0 previous_item = False start_angle_position = 0 index += 1 if previous_item: segments.append(self.drawSegment(line_style, start_angle_position * position_size, segment_size * position_size, current_encoder_diameter, track_width)) segment_size = 0 previous_item = False start_angle_position = 0 current_encoder_diameter -= (2 * track_distance + 2 * track_width) index = 0 return segments # Check if there is too many cutouts compared to number of sensors def validSTGrayEncoder(self, cutouts, sensors): if sensors < 6 and cutouts > 1: pass elif sensors <= 10 and cutouts > 2: pass elif sensors <= 16 and cutouts > 3: pass elif sensors <= 23 and cutouts > 4: pass elif sensors <= 36 and cutouts > 5: pass else: return True return False # This function returns the segments for a single-track gray encoder def drawSTGrayEncoder( self, line_style, cutouts, sensors, encoder_diameter, track_width): segments = [] resolution = 360.0 / (cutouts * 2 * sensors) current_angle = 0.0 added_angle = ((2 * cutouts + 1) * resolution) for n in range(cutouts): current_segment_size = ((n * 2 + 2) * cutouts + 1) * resolution segments.append( self.drawSegment( line_style, current_angle, current_segment_size, encoder_diameter, track_width)) current_angle += added_angle + current_segment_size return segments def drawLabel(self, group, angle, segment_angle, outer_diameter, labelNum): outer_radius = outer_diameter / 2 label_angle = angle + (segment_angle / 2) point = calculatePoint(label_angle, outer_radius) matrix = simpletransform.parseTransform( 'rotate(' + str(label_angle + 90) + ')') matrix_str = str(matrix[0][0]) + "," + str(matrix[0][1]) matrix_str += "," + str(matrix[1][0]) + "," + str(matrix[1][1]) + ",0,0" text = { 'id': 'text' + str(labelNum), #'sodipodi:linespacing': '0%', 'style': 'font-size: 6px;font-style: normal;font-family: Sans', #'transform': 'matrix(' + matrix_str + ')', 'x': str(point[0]), 'y': str(point[1]), #'xml:space': 'preserve' } textElement = inkex.etree.SubElement(group, inkex.addNS('text', 'svg'), text) #tspanElement = inkex.etree.Element( # textElement, '{%s}%s' % (svg_uri, 'tspan'), tspan) textElement.text = string.printable[labelNum % len(string.printable)] self.current_layer.append(textElement) # This function creates the path for one single segment def drawSegment(self, line_style, angle, segment_angle, outer_diameter, width): path = {'style': simplestyle.formatStyle(line_style)} path['d'] = '' outer_radius = outer_diameter / 2 # Go to the first point in the segment path['d'] += self.parsePathData( 'M', calculatePoint(angle, outer_radius - width)) # Go to the second point in the segment path['d'] += self.parsePathData('L', calculatePoint(angle, outer_radius)) # Go to the third point in the segment, draw an arc point = calculatePoint(angle + segment_angle, outer_radius) path['d'] += self.parsePathData('A', [outer_radius, outer_radius]) + \ '0 0 1' + self.parsePathData(' ', point) # Go to the fourth point in the segment point = calculatePoint(angle + segment_angle, outer_radius - width) path['d'] += self.parsePathData('L', point) # Go to the beginning in the segment, draw an arc point = calculatePoint(angle, outer_radius - width) # 'Z' closes the path path['d'] += (self.parsePathData( 'A', [outer_radius - width, outer_radius - width]) + '0 0 0' + self.parsePathData(' ', point) + ' Z') # Return the path return path # This function adds an element to the document def addElement(self, element_type, group, element_attributes): inkex.etree.SubElement( group, inkex.addNS(element_type, 'svg'), element_attributes) def drawCircles(self, hole_diameter, diameter): # Attributes for the center hole, then create it, if diameter is 0, dont # create it circle_elements = [] attributes = { 'style': simplestyle.formatStyle({'stroke': 'none', 'fill': 'black'}), 'r': str(hole_diameter / 2) } if self.options.hole_diameter > 0: circle_elements.append(attributes) # Attributes for the guide hole in the center hole, then create it attributes = { 'style': simplestyle.formatStyle( {'stroke': 'white', 'fill': 'white', 'stroke-width': '0.1'}), 'r': '1' } circle_elements.append(attributes) # Attributes for the outer rim, then create it attributes = { 'style': simplestyle.formatStyle( {'stroke': 'black', 'stroke-width': '1', 'fill': 'none'}), 'r': str(diameter / 2) } if self.options.diameter > 0: circle_elements.append(attributes) return circle_elements def drawCommonCircles(self, group, diameter, hole_diameter): circle_elements = self.drawCircles(hole_diameter, diameter) for circle in circle_elements: self.addElement('circle', group, circle) def effectBrgc(self, group, line_style, diameter, hole_diameter): if (((self.options.encoder_diameter / 2) - (self.options.bits * self.options.track_width + (self.options.bits - 1) * self.options.track_distance)) < self.options.brgc_hole_diameter / 2): inkex.errormsg("Innermost encoder smaller than the center hole!") else: segments = self.drawGrayEncoder( line_style, self.options.bits, self.options.encoder_diameter, self.options.track_width, self.options.track_distance) for item in segments: self.addElement('path', group, item) self.drawCommonCircles(group, diameter, hole_diameter) def effectStgc(self, group, line_style, diameter, hole_diameter): if ((self.options.stgc_encoder_diameter / 2) - self.options.stgc_track_width < self.options.stgc_hole_diameter / 2): inkex.errormsg("Encoder smaller than the center hole!") elif not self.validSTGrayEncoder(self.options.cutouts, self.options.sensors): inkex.errormsg("Too many cutouts compared to number of sensors!") else: segments = self.drawSTGrayEncoder(line_style, self.options.cutouts, self.options.sensors, self.options.stgc_encoder_diameter, self.options.stgc_track_width) for item in segments: self.addElement('path', group, item) self.drawCommonCircles(group, diameter, hole_diameter) def effectRotaryEnc(self, group, line_style, diameter, hole_diameter): # Angle of one single segment segment_angle = 360.0 / (self.options.segments * 2) for segment_number in range(0, self.options.segments): angle = segment_number * (segment_angle * 2) if (self.options.outer_encoder_width > 0 and self.options.outer_encoder_diameter > 0 and self.options.outer_encoder_diameter / 2 > self.options.outer_encoder_width): segment = self.drawSegment(line_style, angle, segment_angle, self.options.outer_encoder_diameter, self.options.outer_encoder_width) self.addElement('path', group, segment) # If the inner encoder diameter is something else than 0; create it if (self.options.outer_encoder_width > 0 and self.options.inner_encoder_diameter > 0 and self.options.inner_encoder_diameter / 2 > self.options.inner_encoder_width): # The inner encoder must be half an encoder segment ahead of the outer one segment = self.drawSegment( line_style, angle + (segment_angle / 2), segment_angle, self.options.inner_encoder_diameter, self.options.inner_encoder_width) self.addElement('path', group, segment) self.drawCommonCircles(group, diameter, hole_diameter) def effectBitmapEnc(self, group, line_style, diameter, hole_diameter): bits = self.options.bm_bits bm_segments = len(bits) # Angle of one single segment segment_angle = 360.0 / bm_segments for segment_number in range(0, bm_segments): angle = segment_number * segment_angle if (self.options.bm_outer_encoder_width > 0 and self.options.bm_outer_encoder_diameter > 0 and self.options.bm_outer_encoder_diameter > self.options.bm_outer_encoder_width): self.drawLabel(group, angle, segment_angle, self.options.bm_diameter, segment_number) # Drawing only the black segments if (bits[segment_number] == '1'): segment = self.drawSegment( line_style, angle, segment_angle, self.options.bm_outer_encoder_diameter, self.options.bm_outer_encoder_width) self.addElement('path', group, segment) self.drawCommonCircles(group, diameter, hole_diameter) def effect(self): # Group to put all the elements in, center set in the middle of the view group = inkex.etree.SubElement(self.current_layer, 'g', { inkex.addNS('label', 'inkscape'): 'Encoder disk', 'transform': 'translate' + str(self.view_center) }) # Line style for the encoder segments line_style = { 'stroke': 'white', 'stroke-width': '0', 'fill': 'black' } if self.options.tab == "\"brgc\"": self.effectBrgc(group, line_style, self.options.brgc_diameter, self.options.brgc_hole_diameter) if self.options.tab == "\"stgc\"": self.effectStgc(group, line_style, self.options.stgc_diameter, self.options.stgc_hole_diameter) if self.options.tab == "\"rotary_enc\"": self.effectRotaryEnc(group, line_style, self.options.diameter, self.options.hole_diameter) if self.options.tab == "\"bitmap_enc\"": self.effectBitmapEnc(group, line_style, self.options.bm_diameter, self.options.bm_hole_diameter) if __name__ == '__main__': # Run the effect effect = EncoderDiskGenerator() effect.affect()