1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import types
24 import warnings
25 import string
26 import Numeric, sys, os
27 from math import sqrt, atan, pi
28
29 from NetworkEditor.items import NetworkNode
30 from NetworkEditor.macros import MacroNode
31 from NetworkEditor.macros import MacroOutputNode
32 from Vision import UserLibBuild
33
34 from DejaVu.VisionInterface.DejaVuWidgets import NEColorMap, NEColorWheel, NEColorEditor, NEDejaVuGeomOptions
35 from DejaVu.Geom import Geom
36 from DejaVu.Insert2d import Insert2d
37 from DejaVu.colorMap import ColorMap
38
39
41 try:
42 from symserv.VisionInterface.SymservNodes import symlib
43 net.editor.addLibraryInstance(
44 symlib, 'symserv.VisionInterface.SymservNodes', 'symlib')
45 except:
46 warnings.warn(
47 'Warning! Could not import symlib from symserv.VisionInterface.SymservNodes.py', stacklevel=2)
48
49
51 try:
52 from Vision.PILNodes import imagelib
53 net.editor.addLibraryInstance(imagelib, 'Vision.PILNodes', 'imagelib')
54 except:
55 warnings.warn(
56 'Warning! Could not import imagelib from Vision.PILNodes.py', stacklevel=2)
57
58
60 try:
61 from Volume.VisionInterface.VolumeNodes import vollib
62 net.editor.addLibraryInstance(
63 vollib, 'Volume.VisionInterface.VolumeNodes', 'vollib')
64 except:
65 warnings.warn(
66 'Warning! Could not import vollib from Volume.VisionInterface.VolumeNodes.py')
67
68
70 - def __init__(self, name='Set Geom Options', viewer=None, **kw):
71 kw['name'] = name
72 apply( NetworkNode.__init__, (self,), kw )
73
74 ip = self.inputPortsDescr
75 ip.append(name='geomOptions', datatype='dict')
76
77 self.widgetDescr['geomOptions'] = {
78 'class':'NEDejaVuGeomOptions', 'lockedOnPort':True
79 }
80
81 op = self.outputPortsDescr
82 op.append(datatype='dict', name='geomOptions')
83
84 code = """def doit(self, geomOptions):
85 self.outputData(geomOptions=geomOptions)
86 """
87
88 self.setFunction(code)
89
90
92 """Set a Viewer's state from a file
93
94 Input Ports:
95 Viewer: (required) the Viewer for which to restore the state
96 filename: (required) the file containing the state
97 mode: (optional) can be 'Viewer', 'objects' or 'both'
98 defaults to 'both'. When this value is 'viewer',
99 the state is restored only for objects always present in the
100 viewer (i.e. root, lights, clipping planes, fog, etc).
101 'objects': means restoring the state of geometries in the viewer.
102 'both': does both.
103
104 Output Ports:
105 """
106 from DejaVu.Geom import Geom
107
108 - def __init__(self, name='RestoreState', viewer=None, **kw):
109 kw['name'] = name
110 apply( NetworkNode.__init__, (self,), kw )
111
112 ip = self.inputPortsDescr
113 ip.append(name='viewer', datatype='viewer')
114 ip.append(name='filename', datatype='string')
115 ip.append(name='mode', datatype='string', required=False)
116
117 self.widgetDescr['filename'] = {
118 'class':'NEEntryWithFileBrowser', 'master':'node', 'width':10,
119 'initialValue':'',
120 'filetypes':[('state files','*_state.py'),('all','*')],
121 'labelCfg':{'text':'Filename: '}
122 }
123
124 self.widgetDescr['mode'] = {
125 'class':'NEComboBox', 'master':'node',
126 'choices':['viewer', 'objects', 'both'],
127 'initialValue': 'both',
128 'entryfield_entry_width':8,
129 'labelCfg':{'text':'mode:'},
130 }
131
132 code = """def doit(self, viewer, filename, mode='both'):
133 execfile(filename, {'vi':viewer, 'mode':mode})
134 viewer.Redraw()
135 """
136 self.setFunction(code)
137
138
140 """Set the rotation center of for the scene to the picked vertex
141
142 Input Ports:
143 Viewer: (required) the Viewer for which to restore the state
144
145 Output Ports:
146 """
147 from DejaVu.Geom import Geom
148
149 - def __init__(self, name='CenterOnPick', viewer=None, **kw):
150 kw['name'] = name
151 apply( NetworkNode.__init__, (self,), kw )
152
153 ip = self.inputPortsDescr
154 ip.append(name='viewer', datatype='viewer')
155
156 code = """def doit(self, viewer):
157 if viewer.lastPick is None:
158 return
159 hits = viewer.lastPick.hits
160 if len(hits)==1:
161 g = hits.keys()[0]
162 index = hits[g][0][0]
163 point = g.vertexSet.vertices.array[index]
164 viewer.rootObject.SetPivot(point)"""
165
166 self.setFunction(code)
167
168
170 """Set the rotation center of for the scene to a vertex
171
172 Input Ports:
173 Viewer: (required) the Viewer for which to restore the state
174 vertex:
175
176 Output Ports:
177 """
178 from DejaVu.Geom import Geom
179
180 - def __init__(self, name='CenterOnPick', viewer=None, **kw):
181 kw['name'] = name
182 apply( NetworkNode.__init__, (self,), kw )
183
184 ip = self.inputPortsDescr
185 ip.append(name='viewer', datatype='viewer')
186 ip.append(name='point', datatype='coord3')
187
188 code = """def doit(self, viewer, point):
189 viewer.rootObject.SetPivot(point)"""
190
191 self.setFunction(code)
192
193
195 """Create an instance of a DejaVu Viewer object.
196 This object provides a full fledged 3D geometry viewer with support for
197 the OpenGL material and lighting model with multiple light source,
198 arbitrary clipping planes, a hierarchy of geometries with property
199 inheritance, a material editor, etc... .
200
201 Input Ports:
202 Geometries: (required) Accepts any object that is a DejaVu.Geom instance
203 or a list thereof from each parent port and adds the geometry
204 objects to the viewer
205 Output Ports:
206 lastPick: outputs a DejaVu.Camera.PickObject
207 DejaVu: outputs the DejaVu Viewer instance
208 Redraw: fires at each redraw event
209 """
210 from DejaVu.Geom import Geom
211
212 - def __init__(self, name='Viewer', viewer=None, **kw):
213 kw['name'] = name
214 apply( NetworkNode.__init__, (self,), kw )
215
216 self.vi = viewer
217
218
219
220 codeBeforeDisconnect = """def beforeDisconnect(self, c):
221 #print "Viewer Node beforeDisconnect"
222 self.node.deleteGeometries(c)
223 """
224
225 ip = self.inputPortsDescr
226 ip.append(name='geometries', datatype='geom(0)', required=False,
227 singleConnection=False,
228 beforeDisconnect=codeBeforeDisconnect)
229
230 op = self.outputPortsDescr
231 op.append(datatype='None', name='lastPick')
232 op.append(datatype='viewer', name='dejaVuViewer')
233 op.append(datatype='None', name='redraw')
234
235 code = """def doit(self, geometries=None):
236 self.addGeometriesToViewer(geometries)
237
238 self.outputData(lastPick=self.vi.lastPick)
239 self.outputData(dejaVuViewer=self.vi)
240 self.outputData(redraw=0)
241 """
242 self.setFunction(code)
243
244
251
252
254 """ this geometries a list, but not a list from a geom node
255 """
256 if self.vi is None:
257 return
258
259 if geometries:
260 for g in geometries:
261 self.addGeometryiesToViewer(g)
262
263
265 """ we don't know if geometryies is a Geom or a list from a geom node
266 """
267 if geometryies and geometryies != [None]:
268 if isinstance(geometryies, Geom) or isinstance(geometryies, Insert2d):
269 if hasattr(geometryies, 'node') and \
270 hasattr(geometryies.node(),'geoms'):
271 self.addMultipleGeomsToViewer(geometryies.node().geoms)
272 else:
273 self.addSingleGeomToViewer(geometryies)
274 self.addGeometryiesToViewer(geometryies.parent)
275 elif isinstance(geometryies, list):
276 self.addMultipleGeomsToViewer(geometryies)
277 else:
278 assert False
279
280 self.vi.Redraw()
281
282
294
295
297 """ geometries is a Geom
298 """
299
300
301
302 if aGeom.viewer is not None:
303 return
304
305
306 lHighestModifiedParent = \
307 self.vi.AddObjectAndParents(aGeom, parent=aGeom.parent, local=False)
308
309
310
311 if hasattr(aGeom, 'node') :
312 aGeom.node().ensureNameOfNodeAndDescendants(lHighestModifiedParent)
313
314
344
345
368
369
371
372 return
373
374
376 if self.vi:
377 apply( self.vi.__class__.afterRedraw, (self.vi,))
378
379 if len(self.outputPorts):
380 self.outputData(redraw=1)
381 self.scheduleChildren(portList=[self.outputPorts[2]])
382
383
385 if pick:
386 if self.vi:
387 self.outputData(lastPick=self.vi.lastPick)
388
389 self.scheduleChildren(portList=[self.outputPorts[0]])
390
391
402
403
405
406 if self.paramPanel.visible:
407 self.paramPanel.hide()
408 else:
409 self.paramPanel.show()
410
411
412
414 """Class to accumulate picked vertices
415
416 Input Ports:
417 pick: DejaVu pick object instance
418 viewer: DejaVu viewer object instance
419 reset: resets the list of vertices to empty
420 remove: check button to remove vertices form the list
421
422 Output Ports:
423 pickedVertices: list of 3D vertices
424 """
426 apply( NetworkNode.__init__, (self,), kw )
427
428 self.vertices = []
429
430
431 ip = self.inputPortsDescr
432 ip.append(name='pick', datatype='None')
433 ip.append(name='viewer', datatype='viewer')
434 ip.append(name='reset', datatype='boolean')
435 ip.append(name='remove', datatype='boolean')
436
437 self.widgetDescr['reset'] = {
438 'class':'NEButton',
439 'master':'node',
440 'initialValue':False,
441 'labelGridCfg':{'sticky':'we'},
442 'labelCfg':{'text':'empty list'},
443 }
444
445 self.widgetDescr['remove'] = {
446 'class':'NECheckButton',
447 'master':'node',
448 'initialValue':False,
449 'labelGridCfg':{'sticky':'we'},
450 'labelCfg':{'text':'remove'},
451 }
452
453 op = self.outputPortsDescr
454 op.append(datatype='list', name='pickedVertices')
455
456 code = """def doit(self, pick, viewer, reset, remove):
457 if self.inputPortByName['reset'].hasNewData():
458 self.vertices = []
459
460 if self.inputPortByName['pick'].hasNewData():
461 vertices = viewer.transformedCoordinatesWithInstances(pick.hits)
462 if remove:
463 for v1 in vertices:
464 vstr = '%.3f %.3f %.3f'%tuple(v1)
465 for v2 in self.vertices:
466 if '%.3f %.3f %.3f'%tuple(v2)==vstr:
467 self.vertices.remove(v2)
468 else:
469 self.vertices.extend(vertices)
470
471 self.outputData(pickedVertices=self.vertices)
472 """
473
474 self.setFunction(code)
475
476
477
479 """compute the numerical volume and surface area of a polyhedron
480
481 Input Ports:
482 geometry: (required) Accepts any IndexedPolygon geometry from DejaVu
483
484 Output Ports:
485 volume: float
486 area: float
487 compactness: area/volume (float)
488 ."""
489
491 """Compute the surface area of a triangle.
492 """
493 x1,y1,z1 = p1
494 x2,y2,z2 = p2
495 x3,y3,z3 = p3
496 dx, dy, dz = x1-x2, y1-y2, z1-z2
497 a = sqrt( dx*dx + dy*dy + dz*dz )
498 dx, dy, dz = x2-x3, y2-y3, z2-z3
499 b = sqrt( dx*dx + dy*dy + dz*dz )
500 dx, dy, dz = x1-x3, y1-y3, z1-z3
501 c = sqrt( dx*dx + dy*dy + dz*dz )
502 s = .5*(a+b+c)
503 area = s*(s-a)*(s-b)*(s-c)
504 if area <= 0.:
505 return 0.
506 return sqrt(area)
507
508
510 """Compute the Volume and surface area of a mesh specified by vertices,
511 indices of triangular faces and face normals
512 """
513 assert len(tri)==len(norm)
514 volSum = 0.0
515 areaSum = 0.0
516 oneThird = 1./3.
517 for t,n in zip(tri, norm):
518 s1 = verts[t[0]]
519 s2 = verts[t[1]]
520 s3 = verts[t[2]]
521 area = self.triangleArea(s1,s2,s3)
522 areaSum += area
523
524 g = [ (s1[0]+s2[0]+s3[0])*oneThird,
525 (s1[1]+s2[1]+s3[1])*oneThird,
526 (s1[2]+s2[2]+s3[2])*oneThird ]
527 volSum += (g[0]*n[0] + g[1]*n[1] + g[2]*n[2])*area
528 return volSum*oneThird, areaSum
529
530
531 - def __init__(self, name='PolyhedronVolumeArea', **kw):
532 kw['name'] = name
533 apply( NetworkNode.__init__, (self,), kw )
534
535
536 ip = self.inputPortsDescr
537 ip.append(datatype='geom', name='geometry')
538
539 op = self.outputPortsDescr
540 op.append(datatype='float', name='volume')
541 op.append(datatype='float', name='area')
542 op.append(datatype='float', name='compactness')
543
544 code = """def doit(self, geometry):
545 vert = geometry.getVertices()
546 tri = geometry.getFaces()
547 #from opengltk.extent.utillib import glTriangleNormals
548 from geomutils.geomalgorithms import TriangleNormals
549 norm = TriangleNormals( vert, tri, 'PER_FACE')
550 # FIXME getFNormals() returns old set of normals after sendity has changed
551 # on MSMS surface
552 # norm = geometry.getFNormals()
553
554 vol, area = self.meshVolume(vert, tri, norm)
555 self.outputData(volume=vol, area=area, compactness=area/vol)
556 """
557
558 self.setFunction(code)
559
560
562 """write the vertices, normals and faces of an IndexedPolygon to a file.
563
564 Input Ports:
565 geometry: (required) Accepts any IndexedPolgon geometry from DejaVu
566 filename: (required) string, no extension. filename.vert and filename.face
567 will be created
568 Output Ports:
569 ."""
570
571 - def __init__(self, name='writeIndexedPolygon', **kw):
572 kw['name'] = name
573 apply( NetworkNode.__init__, (self,), kw )
574
575
576 self.widgetDescr['filename'] = {
577 'class':'NEEntryWithFileSaver', 'master':'node', 'width':10,
578 'initialValue':'',
579 'labelCfg':{'text':'Filename: '}
580 }
581
582 ip = self.inputPortsDescr
583 ip.append(datatype='geom', name='geometry')
584 ip.append(datatype='string', name='filename')
585
586 code = """def doit(self, geometry, filename):
587
588 from DejaVu.IndexedPolygons import IndexedPolygons
589 assert isinstance(geometry, IndexedPolygons)
590 geometry.writeToFile(filename)
591 """
592
593 self.setFunction(code)
594
595
597 """write the vertices and triangles of an IndexedPolygon to a file in the
598 format used by the setCurvature program (which ressembles ply2)
599
600 Input Ports:
601 geometry: (required) Accepts any IndexedPolgon geometry from DejaVu
602 filename: (required) string, no extension. filename.vert and filename.face
603 will be created
604 Output Ports:
605 filename: outputs the filename after it wrote the file
606 """
607 - def writeFile(self, filename, verts, faces, neighborhoodSize, crestLines):
608 f = open(filename, 'w')
609
610
611 assert crestLines is True or crestLines is False
612
613 f.write('%d\n%d\n%d\n%d\n'%( len(verts), len(faces), neighborhoodSize,
614 crestLines ))
615
616
617
618
619 dum = map(lambda x, f=f: f.write('%f\n'%x), verts)
620 dum = map(lambda x, f=f: f.write('%d\n'%x), faces)
621 f.close()
622
623 - def __init__(self, name='writeCurvPly', **kw):
624 kw['name'] = name
625 apply( NetworkNode.__init__, (self,), kw )
626
627
628 self.widgetDescr['filename'] = {
629 'class':'NEEntryWithFileSaver',
630 'master':'node', 'width':10,
631 'initialValue':'',
632 'labelCfg':{'text':'Filename: '}
633 }
634
635 self.widgetDescr['neighborhoodSize'] = {
636 'class':'NEThumbWheel',
637 'master':'node',
638 'initialValue':4,
639 'labelCfg':{'text':'neighborhoodSize:'},
640 'width':80, 'height':20,
641 'type':'int', 'oneTurn':10, 'lockBMin':1, 'min':1,
642 'wheelPad':2 }
643
644 self.widgetDescr['crestLines'] = {
645 'class':'NECheckButton',
646 'master':'node',
647 'initialValue':False,
648 'labelGridCfg':{'sticky':'we'},
649 'labelCfg':{'text':'crest Lines:'},
650 }
651
652 ip = self.inputPortsDescr
653 ip.append(datatype='geom', name='geometry')
654 ip.append(datatype='string', name='filename')
655 ip.append(datatype='int', name='neighborhoodSize')
656 ip.append(datatype='boolean', name='crestLines')
657
658 op = self.outputPortsDescr
659 op.append(datatype='string', name='filename')
660
661 code = """def doit(self, geometry, filename, neighborhoodSize,
662 crestLines):
663
664 from DejaVu.IndexedPolygons import IndexedPolygons
665 assert isinstance(geometry, IndexedPolygons)
666 verts = geometry.getVertices()
667 faces = geometry.getFaces()
668 self.writeFile(filename, verts, faces, neighborhoodSize, crestLines)
669 geometry.writeToFile(filename)
670 """
671
672 self.setFunction(code)
673
674
676 """run the setCurvature program
677
678 Input Ports:
679 geometry: (required) Accepts any IndexedPolgon geometry from DejaVu
680 neighborhoodSize: size of the neighborhood use to fit quadric
681 crestLines: when set to ture crest liens are traced
682
683 Output Ports:
684 maxCurv: max curvature for each vertex
685 minCurv: min curvature for each vertex
686 vmaxCurv: max curvature vector
687 vminCurv: min curvature vector
688 GaussianCurv: Gaussian curvature vector (Kmax*Kmin)
689 meanCurv: mean curvature vector for (Kmax+Kmin)
690 shapeIndex: shapeIndex -2/pi*arctan(Kmax+Kmin/Kmax-Kmin)
691 curvedness: curvedness sqrt(Kmax**2 + Kmin**2)/2
692 normals: normals to the surface
693 """
694 - def writeFile(self, filename, verts, faces, neighborhoodSize, crestLines):
695 f = open(filename, 'w')
696
697
698 assert crestLines is True or crestLines is False
699
700 f.write('%d\n%d\n%d\n%d\n'%( len(verts)/3, len(faces)/3,
701 neighborhoodSize,
702 crestLines ))
703
704
705 dum = map(lambda x, f=f: f.write('%f\n'%x), verts)
706 dum = map(lambda x, f=f: f.write('%d\n'%x), faces)
707 f.close()
708
709
711 f = open(filename)
712 data = f.readlines()
713 f.close()
714 nbv = int(data[0])
715 nbt = int(data[1])
716 import string
717 dataf = map(string.split, data[2:])
718 curvData = []
719 for l in dataf:
720 curvData.append(map(float, l))
721 curvData = Numeric.array(curvData, 'f')
722 return curvData
723
724
725 - def __init__(self, name='curvature', **kw):
726 kw['name'] = name
727 apply( NetworkNode.__init__, (self,), kw )
728
729 self.widgetDescr['neighborhoodSize'] = {
730 'class':'NEThumbWheel',
731 'master':'node',
732 'initialValue':4,
733 'labelCfg':{'text':'neighborhoodSize:'},
734 'width':80, 'height':20,
735 'type':'int', 'oneTurn':10, 'lockBMin':1, 'min':1,
736 'wheelPad':2 }
737
738 self.widgetDescr['crestLines'] = {
739 'class':'NECheckButton',
740 'master':'node',
741 'initialValue':False,
742 'labelGridCfg':{'sticky':'we'},
743 'labelCfg':{'text':'crest Lines:'},
744 }
745
746 ip = self.inputPortsDescr
747 ip.append(datatype='geom', name='geometry')
748 ip.append(datatype='int', name='neighborhoodSize')
749 ip.append(datatype='boolean', name='crestLines')
750
751 op = self.outputPortsDescr
752 op.append(datatype='list', name='maxCurv')
753 op.append(datatype='list', name='minCurv')
754 op.append(datatype='list', name='vmaxCurv')
755 op.append(datatype='list', name='vminCurv')
756 op.append(datatype='list', name='gaussian')
757 op.append(datatype='list', name='mean')
758 op.append(datatype='list', name='shapeIndex')
759 op.append(datatype='list', name='curvedness')
760 op.append(datatype='list', name='normals')
761
762 code = """def doit(self, geometry, neighborhoodSize, crestLines):
763
764 from DejaVu.IndexedPolygons import IndexedPolygons
765 assert isinstance(geometry, IndexedPolygons)
766 verts = Numeric.reshape(geometry.getVertices(), (-1,))
767 faces = Numeric.reshape(geometry.getFaces(), (-1,))
768 self.writeFile('curv.txt' , verts, faces, neighborhoodSize, crestLines)
769 os.system('setCurvature curv.txt curv.out')
770 curvData = self.readCurvature('curv.out')
771 #os.system('rm curv.out curv.txt')
772 Kmax = curvData[:,0]
773 Kmin = curvData[:,1]
774 gaussian = Kmax*Kmin
775 mean = (Kmax+Kmin)*0.5
776 shapeIndex = (-2/pi)*Numeric.arctan((Kmax+Kmin)/(Kmax-Kmin))
777 curvedness = Numeric.sqrt(Kmax*Kmax+Kmin*Kmin)*0.5
778 self.outputData(maxCurv=Kmax,
779 minCurv=Kmin,
780 vmaxCurv=curvData[:,2:5].tolist(),
781 vminCurv=curvData[:,5:8].tolist(),
782 gaussian=gaussian, mean=mean,
783 shapeIndex=shapeIndex, curvedness=curvedness,
784 normals=curvData[:,8:].tolist()
785 )
786 """
787 self.setFunction(code)
788
789
790
792 """ ************* DEPRECATED, USE GeomsFromFile instead **************
793
794 read the vertices, normals and faces of an IndexedPolygon from a file.
795
796 Input Ports:
797 filename: (required) string, no extension. filename.vert and filename.face
798 will be created
799 name: (optional) name for the geoemtry. Defaults to filename
800
801 Output Ports:
802 geometry: DejaVu.IndexedPolgons geometry
803 ."""
804
805 - def __init__(self, name='readIndexedPolygon', **kw):
806
807
808
809 kw['name'] = name
810 apply( NetworkNode.__init__, (self,), kw )
811
812
813 wdescr = self.widgetDescr
814 wdescr['filename'] = {
815 'class':'NEEntryWithFileBrowser', 'master':'node', 'width':10,
816 'initialValue':'',
817 'labelCfg':{'text':'Filename: '},
818 'filetypes': [('vert','*.vert'), ('all', '*')]}
819 wdescr['name'] = {
820 'class':'NEEntry', 'master':'node', 'width':10,
821 'labelCfg':{'text':'name:'},
822 }
823
824 ip = self.inputPortsDescr
825 ip.append(datatype='string', name='filename')
826 ip.append(datatype='string', required=False, name='name')
827
828 op = self.outputPortsDescr
829 op.append(datatype='geom', name='geom')
830
831 code = """def doit(self, filename, name=None):
832 if filename is None or filename == '':
833 return
834 from DejaVu.IndexedPolygons import IndexedPolygonFromFile
835 from os.path import splitext
836 if name == '':
837 name=None
838 filename = splitext(filename)[0]
839 self.outputData(geom=IndexedPolygonFromFile(filename, name))
840 """
841
842 self.setFunction(code)
843
844
845
847 """Remove duplicated vertices and re-index the polygonal faces such that
848 they share vertices
849
850 input:
851 geom -- an IndexedGeom object in which vertices are suplicated
852 newGeom -- when true a new geoemtry is created, else the incomming
853 geometry is modified
854
855 output:
856 geom -- a new IndexedPolgons without duplicated vertices
857 """
858 - def __init__(self, name='removeDupVert', **kw):
859 kw['name'] = name
860 apply(NetworkNode.__init__, (self,), kw)
861
862 ip = self.inputPortsDescr = []
863 ip.append({'datatype':'geom', 'name':'geom'})
864 ip.append({'datatype':'boolean', 'name':'newGeom'})
865
866 self.widgetDescr['newGeom'] = {
867 'class':'NECheckButton',
868 'initialValue':False,
869 'master':'node',
870 'labelGridCfg':{'sticky':'we'},
871 'labelCfg':{'text':'new geometry'},
872 }
873
874 op = self.outputPortsDescr = []
875 op.append({'datatype':'geom', 'name':'geom'})
876
877 code = """def doit(self, geom, newGeom):
878 results = []
879 from DejaVu.IndexedPolygons import IndexedPolygons, Geom
880 from DejaVu.utils import RemoveDuplicatedVertices
881
882 if isinstance(geom, Geom):
883 geom = [geom]
884
885 for g in geom:
886 vertices = g.vertexSet.vertices.array
887 faces = g.faceSet.faces.array
888 vertList, faceList = RemoveDuplicatedVertices(vertices, faces)
889 if newGeom:
890 results.append( IndexedPolygons('%sMesh'%g.name,
891 vertices=vertList, faces=faceList))
892 else:
893 g.Set(vertices=vertList, faces=faceList)
894 results.append(g)
895
896 self.outputData(geom=results)
897 """
898 self.configure(function=code)
899
900
902 """Remove duplicated vertices and re-index the polygonal faces such that
903 they share vertices (uses the C++ function from opengltk.extent.utillib)
904
905 input:
906 geom -- an IndexedGeom object in which vertices are suplicated
907 newGeom -- when true a new geoemtry is created, else the incomming
908 geometry is modified
909 output:
910 geom -- a new IndexedPolygons without duplicated vertices
911 """
912 - def __init__(self, name='removeDupVertC', **kw):
913 kw['name'] = name
914 apply(NetworkNode.__init__, (self,), kw)
915
916 ip = self.inputPortsDescr = []
917 ip.append({'datatype':'geom', 'name':'geom'})
918 ip.append({'datatype':'boolean', 'name':'newGeom'})
919
920 self.widgetDescr['newGeom'] = {
921 'class':'NECheckButton',
922 'initialValue':False,
923 'master':'node',
924 'labelGridCfg':{'sticky':'we'},
925 'labelCfg':{'text':'new geometry'},
926 }
927
928 op = self.outputPortsDescr = []
929 op.append({'datatype':'geom', 'name':'geom'})
930
931 code = """def doit(self, geom, newGeom):
932 results = []
933 from DejaVu.IndexedPolygons import IndexedPolygons, Geom
934 #from opengltk.extent.utillib import removeDuplicatedVertices
935 from geomutils.geomalgorithms import removeDuplicatedVertices
936
937 if isinstance(geom, Geom):
938 geom = [geom]
939
940 for g in geom:
941 vertices = g.vertexSet.vertices.array
942 faces = g.faceSet.faces.array
943 norms = g.getVNormals()
944 if len(vertices) and len(faces):
945 vertList, faceList, normList = removeDuplicatedVertices(
946 vertices, faces, norms )
947 if newGeom:
948 results.append( IndexedPolygons('%sMesh'%g.name,
949 vertices=vertList, faces=faceList, vnormals=normList))
950 else:
951 g.Set(vertices=vertList, faces=faceList, vnormals=normList)
952 results.append(g)
953 self.outputData(geom=results)
954 """
955 self.configure(function=code)
956
958 """Invoked the Qslim algorithm as an external process to decimate a surface.
959 Input Ports
960 geometry: Any DejaVu.IndexedPolgons geometry. (required)
961 persent: percentage of faces to be retained in decimated model
962
963 Output Ports:
964 geom: decimated geometry
965 ."""
966
967 - def __init__(self, name='write SMF', **kw):
968 kw['name'] = name
969 apply( NetworkNode.__init__, (self,), kw )
970
971 self.widgetDescr['percent'] = {
972 'class':'NEDial', 'size':50,
973 'oneTurn':100, 'min':0., 'max':100., 'type':'float',
974 'showLabel':1, 'continuous':0,
975 'initialValue':50.,
976 'labelCfg':{'text':'percent'},
977 }
978 ip = self.inputPortsDescr
979 ip.append(name='lines', datatype='list')
980 ip.append(name='percent', datatype='float')
981 ip.append(name='nbf', datatype='int', required=False)
982
983 op = self.outputPortsDescr
984 op.append({'name':'lines', 'datatype':'list'})
985
986 code = """def doit(self, lines, percent, nbf=None):
987
988 if nbf is None:
989 nbf = 0
990 for l in lines:
991 if l[0]=='f':
992 nbf+=1
993
994 targetFaces = int(nbf*percent/100.)
995
996 from mglutil import process
997 # start propslim process
998 p = process.Popen('propslim %d'%targetFaces,
999 stdin=process.PIPE, stdout=process.PIPE)
1000 inp, out, err = (p.stdin, p.stdout, p.stderr)
1001
1002 # send input
1003 map( lambda x, f=inp: f.write('%s'%x), lines)
1004 inp.close()
1005
1006 # FIXME we should select on out and err and handle errors
1007 # FIXME we should run popslim from the Binaries package
1008 # FIXME we should have a way to detect if the Binary package contains
1009 # popslim and if not not load the node
1010 # read results
1011 data = out.readlines()
1012 print 'propslim reduced from %d to %d faces'%(nbf,targetFaces)
1013
1014 self.outputData(lines=data)
1015 """
1016
1017 self.setFunction(code)
1018
1019
1020
1022 """create the source of an SMF file from an DejaVu.IndexedPolygons
1023 geometry
1024
1025 Input Ports:
1026 geometry: Any DejaVu.IndexedPolgons geometry. (required)
1027 filename: name of the SMF file to be created (required, string)
1028
1029 Output Ports:
1030 lines: list of text lines in SMF format
1031 ."""
1032
1033 - def __init__(self, name='GeomToSMF', **kw):
1034 kw['name'] = name
1035 apply( NetworkNode.__init__, (self,), kw )
1036
1037 ip = self.inputPortsDescr
1038 ip.append(name='geometry', datatype='geom')
1039
1040 self.outputPortsDescr.append(name='lines', datatype='list')
1041
1042 code = """def doit(self, geometry):
1043
1044 from DejaVu.IndexedPolygons import IndexedPolygons
1045 assert isinstance(geometry, IndexedPolygons)
1046 from DejaVu.DataOutput import IndexedPolgonsAsSMFString
1047 lines = IndexedPolgonsAsSMFString(geometry)
1048 self.outputData(lines = lines)
1049 """
1050
1051 self.setFunction(code)
1052
1053
1055 """converts a list of strings describing an SMF file into a
1056 DejaVu.IndexedPolygons
1057
1058 Input Ports:
1059 lines: list of lines
1060 geomname: name for the geoemtry
1061 parent: parent geometry
1062
1063 Output Ports:
1064 geometry: DejaVu.IndexedPolgons geometry
1065 ."""
1066
1067 - def __init__(self, name='SMFtoGeom', **kw):
1068 kw['name'] = name
1069 apply( NetworkNode.__init__, (self,), kw )
1070
1071 from DejaVu.IndexedPolygons import IndexedPolygons
1072 self.geom = IndexedPolygons()
1073
1074 self.widgetDescr['geomname'] = {
1075 'class':'NEEntry', 'master':'node', 'width':10,
1076 'labelCfg':{'text':'name:'},
1077 'initialValue':'smfModel',
1078 }
1079
1080 ip = self.inputPortsDescr
1081 ip.append(name='lines', datatype='list')
1082 ip.append(name='geomname', datatype='string', required=False)
1083 ip.append(name='parent', datatype='geom', required=False)
1084
1085 op = self.outputPortsDescr
1086 op.append(name='geom', datatype='geom')
1087
1088 code = """def doit(self, lines, geomname=None, parent=None):
1089 if len(lines)==0:
1090 return
1091
1092 if not geomname: # i.e. None or ''
1093 geomname = 'smfModel'
1094
1095 from DejaVu.DataOutput import ParseSMFString
1096 v,f,n,c,r = ParseSMFString(lines)
1097
1098 self.geom.Set(name=geomname, vertices=v, faces=f, redo=0)
1099
1100 if len(n):
1101 if len(n)==len(v):
1102 self.geom.Set(vnormals=n, redo=0)
1103 elif len(n)==len(f):
1104 geom.Set(fnormals=n, redo=0)
1105 else:
1106 errstr = 'number of normals %d does not not match number of vertices (%d) or number of faces(%d)'%(len(v), len(f))
1107 warnings.warn(errstr)
1108
1109 if len(c):
1110 self.geom.Set(materials=c, inheritMaterial=False, redo=0)
1111
1112 if parent is not None:
1113 self.geom.viewer.ReparentGeom(self.geom. parent)
1114
1115 self.geom.Set(redo=1)
1116
1117 self.outputData(geom=self.geom)
1118 """
1119
1120 self.setFunction(code)
1121
1122
1123 from DejaVu.IndexedPolygons import IndexedPolygons
1124 import Numeric
1125 try:
1126 from QSlimLib import qslimlib
1127 except:
1128 pass
1129
1130 -class QSlim(NetworkNode):
1131 """Uses QSlim library to decimate a surface.
1132 Creates a QSlim model from the input geometry, decimates it
1133 and outputs a geometry with new number of faces, vertices, colors.
1134 Input Ports
1135 geometry: any DejaVu.IndexedPolgons geometry (required).
1136 percent: percentage of faces to be retained in decimated model (required).
1137 targetfaces: corresponding to persent number of faces.
1138 newgeom: (checkbutton) if checked - the node outputs a new IndexedPolygons
1139 geometry, if unchecked - modified input geometry is output.
1140 rebuild: (boolean), when true, rebuilds the QSlim model (takes
1141 current parameters of selected geometry).
1142
1143 Output Ports:
1144 geom: decimated geometry
1145 ."""
1146
1147 - def __init__(self, name='QSlim', **kw):
1148
1149 kw['name'] = name
1150 apply( NetworkNode.__init__, (self,), kw )
1151
1152 self.widgetDescr['percent'] = {
1153 'class':'NEDial', 'master':'ParamPanel', 'size':50,
1154 'oneTurn':100, 'min':0., 'max':100., 'type':'float',
1155 'showLabel':1, 'continuous':0,
1156 'initialValue':50.,
1157 'labelCfg':{'text':'percent'},
1158 }
1159
1160 self.widgetDescr['targetfaces'] = {
1161 'class':'NEEntry', 'master':'ParamPanel', 'width':10,
1162 'labelCfg':{'text':'num of faces:'},
1163 }
1164
1165 self.widgetDescr['newgeom'] = {
1166 'class':'NECheckButton', 'initialValue':0,
1167 'master':'ParamPanel',
1168 'labelGridCfg':{'sticky':'we'},
1169 'labelCfg':{'text':'create new geometry'},
1170 }
1171
1172 self.widgetDescr['rebuild'] = {
1173
1174 'class':'NEButton',
1175 'master':'ParamPanel',
1176 'labelGridCfg':{'sticky':'we'},
1177 'labelCfg':{'text':'rebuild Qslim model'}
1178 }
1179
1180 ip = self.inputPortsDescr
1181 ip.append(datatype='geom', name='geometry')
1182 ip.append(name='percent', datatype='float')
1183 ip.append(name='targetfaces', datatype = "int", required=False)
1184 ip.append (name='rebuild', datatype='boolean', required=False)
1185 ip.append(datatype='None', name='u', required=False)
1186 ip.append(datatype='None', name='v', required=False)
1187
1188 op = self.outputPortsDescr
1189 op.append(datatype='geom', name='geometry')
1190 op.append(datatype='None', name='u')
1191 op.append(datatype='None', name='v')
1192
1193 self.model = None
1194 self.geometry = None
1195 self.newverts = None
1196 self.newfaces = None
1197 self.newcolors = None
1198 self.newnorms = None
1199 self.numColors = 0
1200 self.colorBind = None
1201 self.maxFaces = 0
1202
1203
1204 code = """def doit(self, geometry , percent, targetfaces=None, rebuild=None, u=None, v=None):
1205 #print 'QSlim Node Running QSlim', percent, targetfaces, rebuild, self.inputPortByName['rebuild'].hasNewData()
1206 # QSlim node can only handle 1 geom so far, so print out a message if there
1207 # are more comming in
1208 if type(geometry)==types.ListType:
1209 if len(geometry)>1:
1210 warnings.warn('Only first geometry is being processed by QSlim node')
1211 geometry = geometry[0]
1212
1213 # check that the incomming geom can be represented as IndexedPolygons
1214 if not geometry.asIndexedPolygons(run=0):
1215 warnings.warn(geometry.name , 'QSlim Node: %s can not be represented as IndexedPolygons', geometry.name)
1216 return
1217
1218 # if there is new data on the first input port (geom) rebuild the model
1219 #if self.inputPortByName['geometry'].hasNewData() or \
1220 #(self.inputPortByName['rebuild'].hasNewData() and rebuild):
1221 if self.inputPortByName['geometry'].hasNewData() or \
1222 self.inputPortByName['rebuild'].hasNewData():
1223 if len(geometry.vertexSet.vertices):
1224 print 'QSlim Node BUILDING MODEL'
1225 self.build_model(geometry, u, v)
1226 else:
1227 return
1228
1229 # compute the number of triangles to be kept
1230 if self.inputPortByName['percent'].hasNewData(): #percent has changed
1231 targetfaces = int(self.maxFaces*percent/100.)
1232 elif self.inputPortByName['targetfaces'].hasNewData() and targetfaces:
1233 #targetfaces has changed
1234 targetfaces = int(targetfaces)
1235 else: # use the percentage
1236 targetfaces = int(self.maxFaces*percent/100.)
1237 print 'ELSE', self.maxFaces*percent, targetfaces
1238
1239 # make sure targetfaces is OK
1240 if targetfaces > self.maxFaces:
1241 targetfaces = self.maxFaces
1242 if self.inputPortByName['percent'].widget:
1243 self.inputPortByName['percent'].widget.set(100, 0)
1244 if self.inputPortByName['targetfaces'].widget:
1245 self.inputPortByName['targetfaces'].widget.set(self.maxFaces, 0)
1246
1247 # decimate
1248 if self.model:
1249 decimVerts, decimFaces, decimNormals, decimColors, decimTex = self.decimate(targetfaces)
1250 redo = self.newcolors is None
1251 geometry.Set(vertices=decimVerts, faces=decimFaces, vnormals=decimNormals, redo=redo)
1252 #print 'len decimated verts = %d, faces = %d, normals = %d' % (len(decimVerts), len(decimFaces), len(decimNormals))
1253 geometry.vertexSet.vertices.array = decimVerts
1254 geometry.faceSet.faces.array = decimFaces
1255 #geometry.Set(faces=decimFaces, vertices=decimVerts, fnormals = decimNormals, redo=redo)
1256 numFaces = len(decimFaces)
1257 nvert = len(decimVerts)
1258 clen = len(decimFaces)
1259 if self.newcolors:
1260 if self.colorBind == 'vertex':
1261 clen = nvert
1262 #print 'len colors:', clen
1263 if clen > 0:
1264 geometry.Set(materials=decimColors[:clen],
1265 inheritMaterial=False, redo=True)
1266 else:
1267 geometry.Set(inheritMaterial=False, redo=True)
1268 else:
1269 geometry.Set(faces=decimFaces, vertices=decimVerts, fnormals = decimNormals)
1270 # set the target face widget to the actual number of faces after
1271 # decimation
1272 if self.inputPortByName['targetfaces'].widget:
1273 self.inputPortByName['targetfaces'].widget.set(numFaces, run=0)
1274
1275 realpercent = 100.*(float(numFaces)/self.maxFaces)
1276 if percent != realpercent and self.inputPortByName['percent'].widget:
1277 self.inputPortByName['percent'].widget.set(realpercent, run=0)
1278 if self.newtexcoords:
1279 if hasattr(geometry.vertexSet, 'texCoords'):
1280 geometry.Set(textureCoords=decimTex[:nvert])
1281 self.outputData(geometry=geometry, u=decimTex[:nvert,0], v = decimTex[:nvert,1] )
1282 else:
1283 self.outputData(geometry=geometry)
1284 """
1285 self.setFunction(code)
1286
1287
1289 """Build new QSlim model."""
1290
1291 verts = geometry.getVertices()
1292 faces = geometry.getFaces()
1293 colors = None
1294 self.colorBind = None
1295 binding = geometry.materials[1028].binding[1]
1296 bind_to_face = False
1297 if binding==11:
1298
1299 bind_to_face = False
1300 self.colorBind = 'vertex'
1301 elif binding==12:
1302
1303 bind_to_face = True
1304 self.colorBind = 'face'
1305 if self.colorBind:
1306 colors = geometry.materials[1028].prop[1][:,:3].astype('f')
1307 self.newcolors = Numeric.zeros(colors.shape, 'f')
1308 else:
1309 self.newcolors = None
1310 normals = geometry.getVNormals()
1311
1312
1313 texcoords = None
1314 self.newtexcoords = None
1315
1316 if t1 and t2:
1317 assert len(t1) == len(t2)
1318 texcoords = Numeric.zeros((len(t1),2), "f")
1319 texcoords[:,0] = t1[:]
1320 texcoords[:,1] = t2[:]
1321 elif t1:
1322 texcoords = Numeric.zeros((len(t1),2), "f")
1323 texcoords[:,0] = t1[:]
1324 elif t2:
1325 texcoords = Numeric.zeros((len(t2),2), "f")
1326 texcoords[:,0] = t2[:]
1327 else:
1328 if hasattr(geometry.vertexSet, 'texCoords'):
1329 texcoords = geometry.vertexSet.texCoords.array
1330 if texcoords:
1331
1332
1333 assert len(texcoords) == len(verts)
1334 self.newtexcoords = Numeric.zeros(texcoords.shape, 'f')
1335 self.model = qslimlib.QSlimModel(verts, faces, bindtoface=bind_to_face,
1336 colors=colors, norms=normals, texcoords=texcoords)
1337 nverts = self.model.get_vert_count()
1338 self.maxFaces = nfaces = self.model.get_face_count()
1339
1340 self.newverts = Numeric.zeros((nverts, 3)).astype('f')
1341 self.newfaces = Numeric.zeros((nfaces, 3)).astype('i')
1342 self.newnorms = Numeric.zeros((nverts, 3)).astype('f')
1343
1345
1346 print 'DECIMATING TO', targetfaces
1347 self.model.slim_to_target(targetfaces)
1348
1349
1350 numFaces = self.model.num_valid_faces()
1351
1352 self.model.outmodel(self.newverts, self.newfaces,
1353 outcolors=self.newcolors, outnorms=self.newnorms,outtexcoords=self.newtexcoords )
1354 numVertices = max(self.newfaces.flat)+1
1355
1356 d = {}
1357 for t in self.newfaces[:numFaces]:
1358 d[int(t[0])] = 1
1359 d[int(t[1])] = 1
1360 d[int(t[2])] = 1
1361 vl = {}
1362 decimVerts = Numeric.zeros((numVertices, 3)).astype('f')
1363 decimFaces = Numeric.zeros((numFaces, 3)).astype('i')
1364 decimNormals = Numeric.zeros((numVertices, 3)).astype('f')
1365 decimColors = None
1366 decimTex = None
1367
1368 if self.newcolors:
1369 decimColors = {'vertex': Numeric.zeros((numVertices, 3)).astype('f'),
1370 'face':self.newcolors[:numFaces]}.get(self.colorBind)
1371 if self.newtexcoords:
1372 decimTex = Numeric.zeros((numVertices, 2)).astype('f')
1373
1374
1375 nvert = 0
1376 for j, t in enumerate(self.newfaces[:numFaces]):
1377 for i in (0,1,2):
1378 n = int(t[i])
1379 if not vl.has_key(n):
1380 vl[n] = nvert
1381 decimVerts[nvert] = self.newverts[n,:]
1382 decimNormals[nvert] = self.newnorms[n,:]
1383
1384 if self.newcolors:
1385 if self.colorBind == 'vertex':
1386 decimColors[nvert] = self.newcolors[n, :]
1387 if self.newtexcoords:
1388 decimTex[nvert] = self.newtexcoords[n,:]
1389 nvert += 1
1390 decimFaces[j] = (vl[int(t[0])], vl[int(t[1])], vl[int(t[2])])
1391
1392 return (decimVerts[:nvert], decimFaces, decimNormals[:nvert], decimColors, decimTex)
1393
1394
1395
1396 try:
1397 bhtreelibFound = True
1398 from bhtree import bhtreelib
1399
1401 """loops over a list of 3D points and removes any point to close to
1402 an already seen one
1403
1404 Input Ports:
1405 points: (required) 3D points to be decimated
1406 cutoff: distance below which a point in cut
1407
1408 Output Ports:
1409 points: decimated set of points
1410 ."""
1411
1412 - def __init__(self, name='getSurfaceVFN', **kw):
1413 kw['name'] = name
1414 apply( NetworkNode.__init__, (self,), kw )
1415
1416
1417 ip = self.inputPortsDescr
1418 ip.append(datatype='coordinates3D', name='point')
1419 ip.append(datatype='float', name='cutoff')
1420
1421 op = self.outputPortsDescr
1422 op.append(datatype='coordinates3D', name='points')
1423
1424 code = """def doit(self, points, cutoff):
1425 import Numeric
1426 ids = Numeric.arrayrange(len(points)).astype('i')
1427 bht = bhtreelib.TBHTree( points, ids, 10, 10, 9999.0 )
1428 result = Numeric.zeros( (len(points),) ).astype('i')
1429 dist = Numeric.zeros( (len(points),) ).astype('f')
1430 pts = []
1431 removed = {}
1432 for p in points:
1433 if removed.has_key('%9.6f,%9.6f,%9.6f'%tuple(p)):
1434 continue
1435 pts.append(p)
1436 nb = bht.ClosePointsDist2( tuple(p), cutoff, result, dist )
1437 for close in result:
1438 removed['%9.6f,%9.6f,%9.6f'%tuple(points[close])] = True
1439
1440 self.outputData(points=pts)
1441 """
1442
1443 self.setFunction(code)
1444
1445
1447 """assign to every sphere the distance to the closest surface vertex.
1448
1449 Input ports:
1450 centers: a list of sphere centers
1451 radii: list of sphere radii
1452 geom: a polygonal geometry
1453
1454 OutputPort:
1455 dist: list of distances
1456 """
1457 - def __init__(self, name='DistFromSphereToGeom', **kw):
1458 kw['name'] = name
1459 apply( NetworkNode.