summaryrefslogtreecommitdiff
path: root/trunk/users/vasile/tags/python_brlcad-0.2/brlcad.py
blob: 0131bd6726526fb4575f333282466218e6b5b6f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
warning = '## This file generated by brlcad.py.  It might get clobbered.'
copyright = '## Copyright (C) 2008 James Vasile <james@hackervisions.org>'
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 *