warning = '## This file generated by brlcad.py. It might get clobbered.' copyright = '## Copyright (C) 2008 James Vasile ' license = '''## This is a freed work; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## any later version. ## This is distributed in the hope that it will be useful, but WITHOUT ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY ## or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public ## License for more details. You should have received a copy of the ## GNU General Public License along with the work; if not, write ## to the Free Software Foundation, Inc., 51 Franklin Street, 5th ## Floor, Boston, MA 02110-1301 USA ''' import sys, os VERSION = 0.2 class Vector: def __init__(self,*args): if len(args) == 1: self.v = args[0] else: self.v = args def __str__(self): return "%s"%(" ".join([str(x)for x in self.v])) def __repr__(self): return "Vector(%s)"%self.v def __mul__(self,other): return Vector( [r*other for r in self.v] ) def __rmul__(self,other): return Vector( [r*other for r in self.v] ) unique_names={} def unique_name(stub, suffix): global unique_names full = stub + suffix if not full in unique_names: unique_names[full]=0 unique_names[full] += 1 return stub + '.' + str(unique_names[full]) + suffix def build_name(stub, ending, **kwargs): '''There are a lot of kwargs options to control how objects are named. If you specify a name with 'name', the object will bear that exact name, with no suffix or unique number. You are responsible for making sure the name is unique. 'suffix' is added to names. It's usually a single letter indicating s for shape or r for region. 'basename' overrides the stub. To this, a unique number and the suffix will be added. 'unique_name' can be set to False, and that will turn off the numbering.''' if 'name' in kwargs: return kwargs['name'] if 'suffix' in kwargs: suffix = kwargs['suffix'] else: suffix = ending if suffix: suffix = '.' + suffix if 'basename' in kwargs: if 'unique_name' in kwargs and kwargs['unique_name']==False: return kwargs['basename'] + suffix else: return unique_name(kwargs['basename'], suffix) else: return unique_name(stub, suffix) class Statement(): isstatement = True name='' # statements don't have names, but this keeps group from complaining def __init__(self, statement, args=[]): self.args=[] if type(args) == str: self.args.append(args) else: for i in range(len(args)): if type(args[i]) == tuple or type(args[i]) == list: self.args.append(Vector(args[i])) else: self.args.append(args[i]) self.statement = statement def __str__(self): return '%s %s\n' % (self.statement, ' '.join([str(s) for s in self.args])) class Shape(): isshape = True def __init__(self, args=[], **kwargs): '''A Shape is any physical item we can depict with brl-cad, from a primitive to a screw to a hole to an entire machine. Each element in args is a Statement or a Shape that makes this item. kwargs['rotate'] = rotate vector or a tuple containing rotate vector and rotate vertex. kwargs['scale'] - not implemented kwargs['translate'] - not implemented Specify the Shape by instantiating it along with the statements and shapes that comprise it. Typically, you'll use rotate or translate after instantiating to move it into position. self.children stores a list of Shapes that comprise this one. TODO: Every shape needs a default vertex for translation and rotation purposes. ''' self.name = build_name('shape', '', **kwargs) if not hasattr(self, 'statements'): self.statements = [] if not hasattr(self, 'children'): self.children = [] # an array of Shapes for a in args: if hasattr(a, 'isstatement'): self.statements.append(a) elif hasattr(a, 'isshape'): self.children.append(a) elif type(a) == type('str'): self.statements.append(Statement(a)) else: print >>sys.stderr, self.name, 'contains ', type(a) sys.exit(2) Shape.guess_vertex(self) ## called this way because we don't want this overridden. if not hasattr(self, 'vertex'): sys.stderr.write('Shape (%s) needs vertex\n' % (self.name)) sys.exit(2) if 'group' in kwargs and kwargs['group']: self.group() if 'rotate' in kwargs: if type(kwargs['rotate'] == tuple) or type(kwargs['rotate'] == list): if len(kwargs['rotate']) == 3: self.rotate(kwargs['rotate']) elif len(kwargs['rotate']) == 2: self.rotate(kwargs['rotate'][0],kwargs['rotate'][1]) else: print >>sys.stderr, ('%s: rotate takes 1 vector or 2 vectors in a tuple.' % (self.name)) sys.exit(2) else: print >>sys.stderr, '%s: rotate takes 1 or 2 tuples/lists.' % (self.name) sys.exit(2) if 'combination' in kwargs: self.combination(kwargs['combination']) def __str__(self): '''Accessing an Object as a string will yield all the statements and the children's statements.''' return ''.join([str(c) for c in self.children]) + \ ''.join([str(s) for s in self.statements]) def _sub_add_union(self, other, op): if type(other)==str: return ' '.join([self.name, op, other]) if hasattr(other, 'isshape') and other.isshape: return ' '.join([self.name, op, other.name]) print >>sys.stderr, 'Shape +/-/union a string or a Shape, but not a ', type(other) sys.exit(2) def __sub__(self, other): return self._sub_add_union(other, '-') def __add__(self, other): return self._sub_add_union(other, '+') def __mul__ (self, other): return self._sub_add_union(other, 'u') def union (self, other): return self._sub_add_union(other, 'u') def _combo_region(self, command, cr): if hasattr(self, 'hasgrc'): print >>sys.stderr, self.name, 'already has a region/combination/group!' sys.exit(2) self.statements.append(Kill(self.name)) self.statements.append(Statement(cr, (self.name, command))) self.hasgrc = 1 return self.name def region(self, command): '''Add a region statement to this Shape.''' return self._combo_region(command, 'r') def combination(self, command): '''Add a combination statement to this Shape.''' return self._combo_region(command, 'c') def group(self): '''Take all the Shapes that comprise this item, and make a group of self.name. Append the group statement to self. If kwargs['group']==True, this will be done as part of __init__''' if hasattr(self, 'hasgrc'): print >>sys.stderr, self.name, 'already has a region/combination/group!' sys.exit(2) self.statements.append(Kill(self.name)) args = [self.name] for c in self.children: if hasattr(c, 'isshape'): args.append(c.name) if len(args) < 2: print >>sys.stderr, self.name, 'needs more items to group' sys.exit(2) self.statements.append(Statement('g', args)) self.hasgrc = 1 return self.name def guess_vertex(self): '''If vertex doesn't exist, adopt vertex of first shape in children. If this doesn't find the right vertex, you'll have to set it manually *before* you call init. Otherwise, init will complain about lack of a vertex and halt.''' if not hasattr(self, 'vertex'): for c in self.children: if hasattr(c, 'vertex'): self.vertex = c.vertex return self.vertex def rotate(self, rotation, vertex=None): if vertex == None: vertex = self.vertex for c in self.children: if hasattr(c, 'rotate'): c.rotate(rotation, vertex) def translate(self, translate, vertex=None): '''translate is a tuple containing the amount to move along each axis. vertex is the point from which we move. All other Shapes and Shapes that make up this Shape are moved relative to the vertex. Not implemented yet. And maybe translate should be more like the mged translate.''' if vertex == None: vertex = self.vertex for c in self.children: if hasattr(c, 'rotate'): c.translate(translate, vertex) ###################### ## Implement some simplistic commands from string import Template commands = { 'Comment':"##", 'Exit':'exit', 'Kill':'kill', 'Killall':'killall', 'Killtree': 'killtree', 'Quit':'quit', 'Source':'source', } for c in commands: exec Template('''class $command(Statement): def __init__(self, arg=''): Statement.__init__(self, '$brl', arg)''').substitute(command = c, brl = commands[c]) class Sed(Statement): ## Enter editing mode pass class Title(Statement): ## TODO: escape quotes def __init__(self, title, args=[]): Statement.__init__(self,"title", title) class Units(Statement): def __init__(self, units): good_units = ['mm', 'millimeter', 'cm', 'centimeter', 'm', 'meter', 'in', 'inch', 'ft', 'foot', 'feet', 'um', 'angstrom', 'decinanometer', 'nanometer', 'nm', 'micron', 'micrometer', 'km', 'kilometer', 'cubit', 'yd', 'yard', 'rd', 'rod', 'mi', 'mile'] if not units in good_units: sys.stdout.write('Unknown units! Ignoring units statement. Defaulting to mm.\n') units = 'mm' Statement.__init__(self,"units", units) class Script(): '''A script is just a list of statements to send to mged. TODO: Maybe this class should derive from list?''' def __init__(self, *statements): self.statements = list(statements) def __str__(self): return ''.join(['%s' % (s) for s in self.statements]) def append(self, *statements): for i in statements: self.statements.append(i) return self class Box_rounded_edge_corners(Shape): def __init__(self, xmin, xmax, ymin, ymax, zmin, zmax, radius, **kwargs): if xmin > xmax: xmin, xmax = xmax, xmin if ymin > ymax: ymin, ymax = ymax, ymin if zmin > zmax: zmin, zmax = zmax, zmin if radius > (xmax-xmin) / 2 or radius > (zmax-zmin)/2: sys.stdout.write('Radius %f too large!\n' % (float(radius))) sys.exit(2) step = radius Shape.__init__(self, [ Box(xmin+step, xmax-step, ymin, ymax, zmin, zmax), Box(xmin, xmax, ymin, ymax, zmin+step, zmax-step), Cylinder((xmin+step, ymin, zmin+step), (0, ymax-ymin, 0), radius), Cylinder((xmin+step, ymin, zmax-step), (0, ymax-ymin, 0), radius), Cylinder((xmax-step, ymin, zmin+step), (0, ymax-ymin, 0), radius), Cylinder((xmax-step, ymin, zmax-step), (0, ymax-ymin, 0), radius) ], group=True, basename='brec', suffix='t') from primitive import *