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
348
349
350
351
352
353
354
355
356
357
358
359
|
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
from ctypes import *
VERSION = 0.3
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.
See rotate, translate and scalar functions for some keywords
that will let you produce a scaled, rotate or translated
object via keyword args to __init__.
kwargs['scale'] - not implemented
Specify the Shape by instantiating it along with the
statements and shapes that comprise it. For many shapes, it
might be easiest to assemble them at the origin and then use
translate and rotate to move them into position. Do this with
the translate, rotate and scale keywords or have the __init__
function of your shape just do a translate and rotate based on
the given vertex and height_vector.
self.children stores a list of Shapes that comprise this one.'''
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)
## Handle some kwarg options
if 'group' in kwargs and kwargs['group']:
self.group()
if 'combination' in kwargs and kwargs['combination']:
self.combination(kwargs['combination'])
if 'region' in kwargs and kwargs['region']:
self.combination(kwargs['region'])
if 'rotate' in kwargs:
self._do_init_rst('rotate', **kwargs)
if 'translate' in kwargs:
self._do_init_rst('translate', **kwargs)
if 'scale' in kwargs:
self._do_init_rst('scale', **kwargs)
def _do_init_rst(self, arg, **kwargs):
'''Handle a rotate, translate, or scale kwarg passed to init by
checking that the params are correct and calling the
appropriate function.
We should probably do all this via object editing, but our
current model doesn't require combinations, so this is
easier.'''
absolute=''
if 'absolute' in kwargs:
absolute=', absolute=True'
if type(kwargs[arg] == tuple) or type(kwargs[arg] == list):
if len(kwargs[arg]) == 3:
exec('self.%s (kwargs[arg]%s)' % (arg, absolute))
elif len(kwargs[arg]) == 2:
exec('self.%s (kwargs[arg][0], kwargs[arg][1]%s)' % (arg, absolute))
else:
print >>sys.stderr, ('%s: %s takes 1 vector or 2 vectors in a tuple.' %
(self.name, arg))
sys.exit(2)
else:
print >>sys.stderr, '%s: %s takes 1 or 2 tuples/lists.' % (self.name, arg)
sys.exit(2)
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):
## Do combinatin or region
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, **kwargs):
'''translate is a tuple containing the amount to move along each axis.
vertex is the point from which we move (defaults to
self.vertex). All other Shapes and Shapes that make up this
Shape are moved relative to the vertex.
If __init__ is called with kwarg translate=(x,y,z), this will
be run during init.
If keyword absolute=True is present, the shape will be moved
relative to the origin.'''
if vertex == None:
vertex = self.vertex
for c in self.children:
if hasattr(c, 'rotate'):
c.translate(translate, vertex, **kwargs)
######################
## 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 Group(Shape):
'''For convenience, this will probably look cleaner than group=True.
OTOH, there's no equivalent for region or combination, so it's not as
elegant.'''
def __init__(self, args=[], **kwargs):
if 'group' in kwargs:
del kwargs['group']
Shape.__init__(self, args, **kwargs)
self.group()
from primitive import *
|