summaryrefslogtreecommitdiff
path: root/cad/src/graphics/drawing/c_renderer.py
blob: 8d70cc0bcbd2f0f30f3d1016f9923a1d37481ee6 (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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# Copyright 2004-2009 Nanorex, Inc.  See LICENSE file for details.
"""
c_renderer.py - Interface to the experimental C++ renderer:

- quux_module_import_succeeded (boolean)

- classes for the ColorSorter's arrays of C++ primitives to draw;
  does some memoization as a speedup.

WARNING: this has not been maintained for a long time, and has not
been tested after at least two major refactorings, one on 090303.
But it once worked and might be useful as example code, so it's
being kept around for now.

@author: Brad G, Will
@version: $Id$
@copyright: 2004-2009 Nanorex, Inc.  See LICENSE file for details.

History:

Brad G wrote much of this in drawer.py.

I think Will modified the quux init code at some point.

080519 russ pulled the globals into a drawing_globals module and broke drawer.py
into 10 smaller chunks: glprefs.py setup_draw.py shape_vertices.py
ColorSorter.py CS_workers.py c_renderer.py CS_draw_primitives.py drawers.py
gl_lighting.py gl_buffers.py

090303 Bruce refactored it.
"""

import foundation.env as env #bruce 051126
import utilities.EndUser as EndUser
import sys
import os
import Numeric

# these are used only by the test code at the bottom;
# if these ever cause an import cycle, move that code to a separate module.
from OpenGL.GL import glPopMatrix
from OpenGL.GL import glPushMatrix
from OpenGL.GL import glTranslate
from utilities.Log import redmsg
from utilities.debug import print_compact_stack

# ==

# Machinery to load the C renderer, from either of two places,
# one for developers and one for end users.

if EndUser.getAlternateSourcePath() != None:
    sys.path.append(os.path.join( EndUser.getAlternateSourcePath(),
                                  "experimental/pyrex-opengl"))
else:
    sys.path.append("./experimental/pyrex-opengl")

binPath = os.path.normpath(os.path.dirname(os.path.abspath(sys.argv[0]))
                           + '/../bin')
if binPath not in sys.path:
    sys.path.append(binPath)

quux_module_import_succeeded = False

try:
    import quux # can't be toplevel
    quux_module_import_succeeded = True
    if "experimental" in os.path.dirname(quux.__file__):
        # Should never happen for end users, but if it does we want to print the
        # warning.
        if env.debug() or not EndUser.enableDeveloperFeatures():
            print "debug: fyi:", \
                  "Loaded experimental version of C rendering code:", \
                  quux.__file__
except:
    quux = None
    quux_module_import_succeeded = False
    if env.debug(): #bruce 060323 added condition
        print "WARNING: unable to import C rendering code (quux module).", \
              "Only Python rendering will be available."
    pass

# ==

class ShapeList_inplace:
    """
    Records sphere and cylinder data and invokes it through the native C++
    rendering system.

    This has the benefit over ShapeList that shapes aren't first stored in
    lots of Python lists, and then turned into lots of Numeric arrays.
    Instead, it stores directly in a list of fixed-size Numeric arrays.
    It shows some speedup, but not a lot.  And tons of memory is being
    used.  I'm not sure where. -grantham
    """
    __author__ = "grantham@plunk.org"

    _blocking = 512     # balance between memory zeroing and drawing efficiency

    def __init__(self):

        #
        # Lists of lists, each list containing a Numeric array and the
        # number of objects in that array.  E.g. Each element in
        # sphere is [l, n], where l is array((m, 9), 'f'), n is the
        # number of valid 9 element slices in l that represent
        # spheres, and m is equal to or more than n+1.
        #
        self.spheres = []
        self.cylinders = []

        # If this is true, disallow future adds.
        self.petrified = False


    def draw(self):

        """
        Draw all the objects represented in this shape list.
        """

        for (spheres, count) in self.spheres:
            quux.shapeRendererDrawSpheresIlvd(count, spheres)
        for (cylinders, count) in self.cylinders:
            quux.shapeRendererDrawCylindersIlvd(count, cylinders)


    def add_sphere(self, color4, pos, radius, name = 0):
        """
        Add a sphere to this shape list.

        "color4" must have 4 elements.  "name" is the GL selection name.
        """

        if self.petrified:
            raise ValueError, \
                  "Tried to add a sphere to a petrified ShapeList_inplace"

        # struct Sphere {
        #     float m_color[4];
        #     float m_nameUInt;
        #     float m_center[3];
        #     float m_radius;
        # };

        if (len(self.spheres) == 0 or
            self.spheres[-1][1] == ShapeList_inplace._blocking):
            # size of struct Sphere in floats is 9
            block = Numeric.zeros((ShapeList_inplace._blocking, 9), 'f')
            self.spheres.append([block, 0])

        (block, count) = self.spheres[-1]

        block[count] = (\
            color4[0], color4[1], color4[2], color4[3],
            float(name),
            pos[0], pos[1], pos[2],
            radius)

        self.spheres[-1][1] += 1


    def add_cylinder(self, color4, pos1, pos2, radius, name = 0, capped=0):
        """
        Add a cylinder to this shape list.

        "color4" must have 4 elements.  "name" is the GL selection name.
        """

        if self.petrified:
            raise ValueError, \
                  "Tried to add a cylinder to a petrified ShapeList_inplace"

        # struct Cylinder {
        #     float m_color[4];
        #     float m_nameUInt;
        #     float m_cappedBool;
        #     float m_pos1[3];
        #     float m_pos2[3];
        #     float m_radius;
        # };

        if (len(self.cylinders) == 0 or
            self.cylinders[-1][1] == ShapeList_inplace._blocking):
            # size of struct Cylinder in floats is 13
            block = Numeric.zeros((ShapeList_inplace._blocking, 13), 'f')
            self.cylinders.append([block, 0])

        (block, count) = self.cylinders[-1]

        block[count] = (\
            color4[0], color4[1], color4[2], color4[3],
            float(name),
            float(capped),
            pos1[0], pos1[1], pos1[2],
            pos2[0], pos2[1], pos2[2],
            radius)

        self.cylinders[-1][1] += 1


    def petrify(self):
        """
        Make this object

        Since the last block of shapes might not be full, this
        function copies them to a new block exactly big enough to hold
        the shapes in that block.  The gc has a chance to release the
        old block and reduce memory use.  After this point, shapes
        must not be added to this ShapeList.
        """

        self.petrified = True

        if len(self.spheres) > 0:
            count = self.spheres[-1][1]
            if count < ShapeList_inplace._blocking:
                block = self.spheres[-1][0]
                newblock = Numeric.array(block[0:count], 'f')
                self.spheres[-1][0] = newblock

        if len(self.cylinders) > 0:
            count = self.cylinders[-1][1]
            if count < ShapeList_inplace._blocking:
                block = self.cylinders[-1][0]
                newblock = Numeric.array(block[0:count], 'f')
                self.cylinders[-1][0] = newblock

    pass

# ==

class ShapeList: # not used as of before 090303
    """
    Records sphere and cylinder data and invokes it through the native C++
    rendering system.

    Probably better to use "ShapeList_inplace".
    """
    __author__ = "grantham@plunk.org"

    def __init__(self):

        self.memoized = False

        self.sphere_colors = []
        self.sphere_radii = []
        self.sphere_centers = []
        self.sphere_names = []

        self.cylinder_colors = []
        self.cylinder_radii = []
        self.cylinder_pos1 = []
        self.cylinder_pos2 = []
        self.cylinder_cappings = []
        self.cylinder_names = []


    def _memoize(self):

        """
        Internal function that creates Numeric arrays from the data stored
        in add_sphere and add-cylinder.
        """

        self.memoized = True

        # GL Names are uint32.  Numeric.array appears to have only
        # int32.  Winging it...

        self.sphere_colors_array = Numeric.array(self.sphere_colors, 'f')
        self.sphere_radii_array = Numeric.array(self.sphere_radii, 'f')
        self.sphere_centers_array = Numeric.array(self.sphere_centers, 'f')
        self.sphere_names_array = Numeric.array(self.sphere_names, 'i')

        self.cylinder_colors_array = Numeric.array(self.cylinder_colors, 'f')
        self.cylinder_radii_array = Numeric.array(self.cylinder_radii, 'f')
        self.cylinder_pos1_array = Numeric.array(self.cylinder_pos1, 'f')
        self.cylinder_pos2_array = Numeric.array(self.cylinder_pos2, 'f')
        self.cylinder_cappings_array = Numeric.array(self.cylinder_cappings,'f')
        self.cylinder_names_array = Numeric.array(self.cylinder_names, 'i')


    def draw(self):

        """
        Draw all the objects represented in this shape list.
        """

        # ICK - SLOW - Probably no big deal in a display list.

        if len(self.sphere_radii) > 0:
            if not self.memoized:
                self._memoize()
            quux.shapeRendererDrawSpheres(len(self.sphere_radii),
                                          self.sphere_centers_array,
                                          self.sphere_radii_array,
                                          self.sphere_colors_array,
                                          self.sphere_names_array)

        if len(self.cylinder_radii) > 0:
            if not self.memoized:
                self._memoize()
            quux.shapeRendererDrawCylinders(len(self.cylinder_radii),
                                            self.cylinder_pos1_array,
                                            self.cylinder_pos2_array,
                                            self.cylinder_radii_array,
                                            self.cylinder_cappings_array,
                                            self.cylinder_colors_array,
                                            self.cylinder_names_array)


    def add_sphere(self, color4, pos, radius, name = 0):
        """
        Add a sphere to this shape list.

        "color4" must have 4 elements.  "name" is the GL selection name.
        """

        self.sphere_colors.append(color4)
        self.sphere_centers.append(list(pos))
        self.sphere_radii.append(radius)
        self.sphere_names.append(name)
        self.memoized = False


    def add_cylinder(self, color4, pos1, pos2, radius, name = 0, capped=0):
        """
        Add a cylinder to this shape list.

        "color4" must have 4 elements.  "name" is the GL selection name.
        """

        self.cylinder_colors.append(color4)
        self.cylinder_radii.append(radius)
        self.cylinder_pos1.append(list(pos1))
        self.cylinder_pos2.append(list(pos2))
        self.cylinder_cappings.append(capped)
        self.cylinder_names.append(name)
        self.memoized = False


    def petrify(self):
        """
        Delete all but the cached Numeric arrays.

        Call this when you're sure you don't have any more shapes to store
        in the shape list and you want to release the python lists of data
        back to the heap.  Additional shapes must not be added to this shape
        list.
        """
        if not self.memoized:
            self._memoize()

        del self.sphere_colors
        del self.sphere_radii
        del self.sphere_centers
        del self.sphere_names

        del self.cylinder_colors
        del self.cylinder_radii
        del self.cylinder_pos1
        del self.cylinder_pos2
        del self.cylinder_cappings
        del self.cylinder_names

    pass

# ==

def test_pyrex_opengl(test_type): # not tested since major refactoring
    try:
        print_compact_stack("selectMode Draw: " )###
        ### BUG: if import quux fails, we get into some sort of infinite
        ###   loop of Draw calls. [bruce 070917 comment]

        #self.w.win_update()
        ## sys.path.append("./experimental/pyrex-opengl") # no longer
        ##needed here -- always done in drawer.py
        binPath = os.path.normpath(os.path.dirname(
            os.path.abspath(sys.argv[0])) + '/../bin')
        if binPath not in sys.path:
            sys.path.append(binPath)
        import quux
        if "experimental" in os.path.dirname(quux.__file__):
            print "WARNING: Using experimental version of quux module"
        # quux.test()
        quux.shapeRendererInit()
        quux.shapeRendererSetUseDynamicLOD(0)
        quux.shapeRendererStartDrawing()
        if test_type == 1:
            center = Numeric.array((Numeric.array((0, 0, 0), 'f'),
                                    Numeric.array((0, 0, 1), 'f'),
                                    Numeric.array((0, 1, 0), 'f'),
                                    Numeric.array((0, 1, 1), 'f'),
                                    Numeric.array((1, 0, 0), 'f'),
                                    Numeric.array((1, 0, 1), 'f'),
                                    Numeric.array((1, 1, 0), 'f'),
                                    Numeric.array((1, 1, 1), 'f')), 'f')
            radius = Numeric.array((0.2, 0.4, 0.6, 0.8,
                                    1.2, 1.4, 1.6, 1.8), 'f')
            color = Numeric.array((Numeric.array((0, 0, 0, 0.5), 'f'),
                                   Numeric.array((0, 0, 1, 0.5), 'f'),
                                   Numeric.array((0, 1, 0, 0.5), 'f'),
                                   Numeric.array((0, 1, 1, 0.5), 'f'),
                                   Numeric.array((1, 0, 0, 0.5), 'f'),
                                   Numeric.array((1, 0, 1, 0.5), 'f'),
                                   Numeric.array((1, 1, 0, 0.5), 'f'),
                                   Numeric.array((1, 1, 1, 0.5), 'f')), 'f')
            result = quux.shapeRendererDrawSpheres(8, center, radius, color)
        elif test_type == 2:
            # grantham - I'm pretty sure the actual compilation, init,
            # etc happens once
            from bearing_data import sphereCenters, sphereRadii
            from bearing_data import sphereColors, cylinderPos1
            from bearing_data import cylinderPos2, cylinderRadii
            from bearing_data import cylinderCapped, cylinderColors
            glPushMatrix()
            glTranslate(-0.001500, -0.000501, 151.873627)
            result = quux.shapeRendererDrawSpheres(1848,
                                                   sphereCenters,
                                                   sphereRadii,
                                                   sphereColors)
            result = quux.shapeRendererDrawCylinders(5290,
                                                     cylinderPos1,
                                                     cylinderPos2,
                                                     cylinderRadii,
                                                     cylinderCapped,
                                                     cylinderColors)
            glPopMatrix()
        quux.shapeRendererFinishDrawing()

    except ImportError:
        env.history.message(redmsg(
            "Can't import Pyrex OpenGL or maybe bearing_data.py, rebuild it"))

    return

# end