1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    vtkGLTFExporter.cxx
5 
6   Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7   All rights reserved.
8   See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9 
10      This software is distributed WITHOUT ANY WARRANTY; without even
11      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12      PURPOSE.  See the above copyright notice for more information.
13 
14 =========================================================================*/
15 #include "vtkGLTFExporter.h"
16 #include "vtkGLTFWriterUtils.h"
17 
18 #include <cstdio>
19 #include <memory>
20 #include <sstream>
21 
22 #include "vtk_jsoncpp.h"
23 
24 #include "vtkAssemblyPath.h"
25 #include "vtkBase64OutputStream.h"
26 #include "vtkCamera.h"
27 #include "vtkCollectionRange.h"
28 #include "vtkCompositeDataIterator.h"
29 #include "vtkCompositeDataSet.h"
30 #include "vtkFloatArray.h"
31 #include "vtkImageData.h"
32 #include "vtkImageFlip.h"
33 #include "vtkMapper.h"
34 #include "vtkMatrix4x4.h"
35 #include "vtkObjectFactory.h"
36 #include "vtkPNGWriter.h"
37 #include "vtkPointData.h"
38 #include "vtkPolyData.h"
39 #include "vtkProperty.h"
40 #include "vtkRenderWindow.h"
41 #include "vtkRendererCollection.h"
42 #include "vtkTexture.h"
43 #include "vtkTriangleFilter.h"
44 #include "vtkTrivialProducer.h"
45 #include "vtkUnsignedCharArray.h"
46 #include "vtkUnsignedIntArray.h"
47 
48 #include "vtksys/FStream.hxx"
49 #include "vtksys/SystemTools.hxx"
50 
51 vtkStandardNewMacro(vtkGLTFExporter);
52 
vtkGLTFExporter()53 vtkGLTFExporter::vtkGLTFExporter()
54 {
55   this->FileName = nullptr;
56   this->InlineData = false;
57   this->SaveNormal = false;
58   this->SaveBatchId = false;
59 }
60 
~vtkGLTFExporter()61 vtkGLTFExporter::~vtkGLTFExporter()
62 {
63   delete[] this->FileName;
64 }
65 
66 namespace
67 {
68 
findPolyData(vtkDataObject * input)69 vtkPolyData* findPolyData(vtkDataObject* input)
70 {
71   // do we have polydata?
72   vtkPolyData* pd = vtkPolyData::SafeDownCast(input);
73   if (pd)
74   {
75     return pd;
76   }
77   vtkCompositeDataSet* cd = vtkCompositeDataSet::SafeDownCast(input);
78   if (cd)
79   {
80     vtkSmartPointer<vtkCompositeDataIterator> iter;
81     iter.TakeReference(cd->NewIterator());
82     for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
83     {
84       pd = vtkPolyData::SafeDownCast(iter->GetCurrentDataObject());
85       if (pd)
86       {
87         return pd;
88       }
89     }
90   }
91   return nullptr;
92 }
93 
WriteMesh(Json::Value & accessors,Json::Value & buffers,Json::Value & bufferViews,Json::Value & meshes,Json::Value & nodes,vtkPolyData * pd,vtkActor * aPart,const char * fileName,bool inlineData,bool saveNormal,bool saveBatchId)94 void WriteMesh(Json::Value& accessors, Json::Value& buffers, Json::Value& bufferViews,
95   Json::Value& meshes, Json::Value& nodes, vtkPolyData* pd, vtkActor* aPart, const char* fileName,
96   bool inlineData, bool saveNormal, bool saveBatchId)
97 {
98   vtkNew<vtkTriangleFilter> trif;
99   trif->SetInputData(pd);
100   trif->Update();
101   vtkPolyData* tris = trif->GetOutput();
102 
103   // write the point locations
104   int pointAccessor = 0;
105   {
106     vtkDataArray* da = tris->GetPoints()->GetData();
107     vtkGLTFWriterUtils::WriteBufferAndView(da, fileName, inlineData, buffers, bufferViews);
108 
109     // write the accessor
110     Json::Value acc;
111     acc["bufferView"] = bufferViews.size() - 1;
112     acc["byteOffset"] = 0;
113     acc["type"] = "VEC3";
114     acc["componentType"] = GL_FLOAT;
115     acc["count"] = static_cast<Json::Value::Int64>(da->GetNumberOfTuples());
116     double range[6];
117     tris->GetPoints()->GetBounds(range);
118     Json::Value mins;
119     mins.append(range[0]);
120     mins.append(range[2]);
121     mins.append(range[4]);
122     Json::Value maxs;
123     maxs.append(range[1]);
124     maxs.append(range[3]);
125     maxs.append(range[5]);
126     acc["min"] = mins;
127     acc["max"] = maxs;
128     pointAccessor = accessors.size();
129     accessors.append(acc);
130   }
131 
132   std::vector<vtkDataArray*> arraysToSave;
133   if (saveBatchId)
134   {
135     vtkDataArray* a;
136     if ((a = pd->GetPointData()->GetArray("_BATCHID")))
137     {
138       arraysToSave.push_back(a);
139     }
140   }
141   if (saveNormal)
142   {
143     vtkDataArray* a;
144     if ((a = pd->GetPointData()->GetArray("NORMAL")))
145     {
146       arraysToSave.push_back(a);
147     }
148   }
149   int userAccessorsStart = accessors.size();
150   for (size_t i = 0; i < arraysToSave.size(); ++i)
151   {
152     vtkDataArray* da = arraysToSave[i];
153     vtkGLTFWriterUtils::WriteBufferAndView(da, fileName, inlineData, buffers, bufferViews);
154 
155     // write the accessor
156     Json::Value acc;
157     acc["bufferView"] = bufferViews.size() - 1;
158     acc["byteOffset"] = 0;
159     acc["type"] = da->GetNumberOfComponents() == 3 ? "VEC3" : "SCALAR";
160     acc["componentType"] = GL_FLOAT;
161     acc["count"] = static_cast<Json::Value::Int64>(da->GetNumberOfTuples());
162     accessors.append(acc);
163   }
164 
165   // if we have vertex colors then write them out
166   int vertColorAccessor = -1;
167   aPart->GetMapper()->MapScalars(tris, 1.0);
168   if (aPart->GetMapper()->GetColorMapColors())
169   {
170     vtkUnsignedCharArray* da = aPart->GetMapper()->GetColorMapColors();
171     vtkGLTFWriterUtils::WriteBufferAndView(da, fileName, inlineData, buffers, bufferViews);
172 
173     // write the accessor
174     Json::Value acc;
175     acc["bufferView"] = bufferViews.size() - 1;
176     acc["byteOffset"] = 0;
177     acc["type"] = "VEC4";
178     acc["componentType"] = GL_UNSIGNED_BYTE;
179     acc["normalized"] = true;
180     acc["count"] = static_cast<Json::Value::Int64>(da->GetNumberOfTuples());
181     vertColorAccessor = accessors.size();
182     accessors.append(acc);
183   }
184 
185   // if we have tcoords then write them out
186   // first check for colortcoords
187   int tcoordAccessor = -1;
188   vtkFloatArray* tcoords = aPart->GetMapper()->GetColorCoordinates();
189   if (!tcoords)
190   {
191     tcoords = vtkFloatArray::SafeDownCast(tris->GetPointData()->GetTCoords());
192   }
193   if (tcoords)
194   {
195     vtkFloatArray* da = tcoords;
196     vtkGLTFWriterUtils::WriteBufferAndView(tcoords, fileName, inlineData, buffers, bufferViews);
197 
198     // write the accessor
199     Json::Value acc;
200     acc["bufferView"] = bufferViews.size() - 1;
201     acc["byteOffset"] = 0;
202     acc["type"] = da->GetNumberOfComponents() == 3 ? "VEC3" : "VEC2";
203     acc["componentType"] = GL_FLOAT;
204     acc["normalized"] = false;
205     acc["count"] = static_cast<Json::Value::Int64>(da->GetNumberOfTuples());
206     tcoordAccessor = accessors.size();
207     accessors.append(acc);
208   }
209 
210   // to store the primitives
211   Json::Value prims;
212 
213   // write out the verts
214   if (tris->GetVerts() && tris->GetVerts()->GetNumberOfCells())
215   {
216     Json::Value aprim;
217     aprim["mode"] = 0;
218     Json::Value attribs;
219 
220     vtkCellArray* da = tris->GetVerts();
221     vtkGLTFWriterUtils::WriteBufferAndView(da, fileName, inlineData, buffers, bufferViews);
222 
223     // write the accessor
224     Json::Value acc;
225     acc["bufferView"] = bufferViews.size() - 1;
226     acc["byteOffset"] = 0;
227     acc["type"] = "SCALAR";
228     acc["componentType"] = GL_UNSIGNED_INT;
229     acc["count"] = static_cast<Json::Value::Int64>(da->GetNumberOfCells());
230     aprim["indices"] = accessors.size();
231     accessors.append(acc);
232 
233     attribs["POSITION"] = pointAccessor;
234     int userAccessor = userAccessorsStart;
235     for (size_t i = 0; i < arraysToSave.size(); ++i)
236     {
237       attribs[arraysToSave[i]->GetName()] = userAccessor++;
238     }
239     if (vertColorAccessor >= 0)
240     {
241       attribs["COLOR_0"] = vertColorAccessor;
242     }
243     if (tcoordAccessor >= 0)
244     {
245       attribs["TEXCOORD_0"] = tcoordAccessor;
246     }
247     aprim["attributes"] = attribs;
248     prims.append(aprim);
249   }
250 
251   // write out the lines
252   if (tris->GetLines() && tris->GetLines()->GetNumberOfCells())
253   {
254     Json::Value aprim;
255     aprim["mode"] = 1;
256     Json::Value attribs;
257 
258     vtkCellArray* da = tris->GetLines();
259     vtkGLTFWriterUtils::WriteBufferAndView(da, fileName, inlineData, buffers, bufferViews);
260 
261     // write the accessor
262     Json::Value acc;
263     acc["bufferView"] = bufferViews.size() - 1;
264     acc["byteOffset"] = 0;
265     acc["type"] = "SCALAR";
266     acc["componentType"] = GL_UNSIGNED_INT;
267     acc["count"] = static_cast<Json::Value::Int64>(da->GetNumberOfCells() * 2);
268     aprim["indices"] = accessors.size();
269     accessors.append(acc);
270 
271     attribs["POSITION"] = pointAccessor;
272     int userAccessor = userAccessorsStart;
273     for (size_t i = 0; i < arraysToSave.size(); ++i)
274     {
275       attribs[arraysToSave[i]->GetName()] = userAccessor++;
276     }
277     if (vertColorAccessor >= 0)
278     {
279       attribs["COLOR_0"] = vertColorAccessor;
280     }
281     if (tcoordAccessor >= 0)
282     {
283       attribs["TEXCOORD_0"] = tcoordAccessor;
284     }
285     aprim["attributes"] = attribs;
286     prims.append(aprim);
287   }
288 
289   // write out the triangles
290   if (tris->GetPolys() && tris->GetPolys()->GetNumberOfCells())
291   {
292     Json::Value aprim;
293     aprim["mode"] = 4;
294     Json::Value attribs;
295 
296     vtkCellArray* da = tris->GetPolys();
297     vtkGLTFWriterUtils::WriteBufferAndView(da, fileName, inlineData, buffers, bufferViews);
298 
299     // write the accessor
300     Json::Value acc;
301     acc["bufferView"] = bufferViews.size() - 1;
302     acc["byteOffset"] = 0;
303     acc["type"] = "SCALAR";
304     acc["componentType"] = GL_UNSIGNED_INT;
305     acc["count"] = static_cast<Json::Value::Int64>(da->GetNumberOfCells() * 3);
306     aprim["indices"] = accessors.size();
307     accessors.append(acc);
308 
309     attribs["POSITION"] = pointAccessor;
310     int userAccessor = userAccessorsStart;
311     for (size_t i = 0; i < arraysToSave.size(); ++i)
312     {
313       attribs[arraysToSave[i]->GetName()] = userAccessor++;
314     }
315     if (vertColorAccessor >= 0)
316     {
317       attribs["COLOR_0"] = vertColorAccessor;
318     }
319     if (tcoordAccessor >= 0)
320     {
321       attribs["TEXCOORD_0"] = tcoordAccessor;
322     }
323     aprim["attributes"] = attribs;
324     prims.append(aprim);
325   }
326 
327   Json::Value amesh;
328   char meshNameBuffer[32];
329   sprintf(meshNameBuffer, "mesh%d", meshes.size());
330   amesh["name"] = meshNameBuffer;
331   amesh["primitives"] = prims;
332   meshes.append(amesh);
333 
334   // write out an actor
335   Json::Value child;
336   vtkMatrix4x4* amat = aPart->GetMatrix();
337   if (!amat->IsIdentity())
338   {
339     for (int i = 0; i < 4; ++i)
340     {
341       for (int j = 0; j < 4; ++j)
342       {
343         child["matrix"].append(amat->GetElement(j, i));
344       }
345     }
346   }
347   child["mesh"] = meshes.size() - 1;
348   child["name"] = meshNameBuffer;
349   nodes.append(child);
350 }
351 
WriteCamera(Json::Value & cameras,vtkRenderer * ren)352 void WriteCamera(Json::Value& cameras, vtkRenderer* ren)
353 {
354   vtkCamera* cam = ren->GetActiveCamera();
355   Json::Value acamera;
356   Json::Value camValues;
357   camValues["znear"] = cam->GetClippingRange()[0];
358   camValues["zfar"] = cam->GetClippingRange()[1];
359   if (cam->GetParallelProjection())
360   {
361     acamera["type"] = "orthographic";
362     camValues["xmag"] = cam->GetParallelScale() * ren->GetTiledAspectRatio();
363     camValues["ymag"] = cam->GetParallelScale();
364     acamera["orthographic"] = camValues;
365   }
366   else
367   {
368     acamera["type"] = "perspective";
369     camValues["yfov"] = vtkMath::RadiansFromDegrees(cam->GetViewAngle());
370     camValues["aspectRatio"] = ren->GetTiledAspectRatio();
371     acamera["perspective"] = camValues;
372   }
373   cameras.append(acamera);
374 }
375 
WriteTexture(Json::Value & buffers,Json::Value & bufferViews,Json::Value & textures,Json::Value & samplers,Json::Value & images,vtkPolyData * pd,vtkActor * aPart,const char * fileName,bool inlineData,std::map<vtkUnsignedCharArray *,unsigned int> & textureMap)376 void WriteTexture(Json::Value& buffers, Json::Value& bufferViews, Json::Value& textures,
377   Json::Value& samplers, Json::Value& images, vtkPolyData* pd, vtkActor* aPart,
378   const char* fileName, bool inlineData, std::map<vtkUnsignedCharArray*, unsigned int>& textureMap)
379 {
380   // do we have a texture
381   aPart->GetMapper()->MapScalars(pd, 1.0);
382   vtkImageData* id = aPart->GetMapper()->GetColorTextureMap();
383   vtkTexture* t = nullptr;
384   if (!id && aPart->GetTexture())
385   {
386     t = aPart->GetTexture();
387     id = t->GetInput();
388   }
389 
390   vtkUnsignedCharArray* da = nullptr;
391   if (id && id->GetPointData()->GetScalars())
392   {
393     da = vtkUnsignedCharArray::SafeDownCast(id->GetPointData()->GetScalars());
394   }
395   if (!da)
396   {
397     return;
398   }
399 
400   unsigned int textureSource = 0;
401 
402   if (textureMap.find(da) == textureMap.end())
403   {
404     textureMap[da] = textures.size();
405 
406     // flip Y
407     vtkNew<vtkTrivialProducer> triv;
408     triv->SetOutput(id);
409     vtkNew<vtkImageFlip> flip;
410     flip->SetFilteredAxis(1);
411     flip->SetInputConnection(triv->GetOutputPort());
412 
413     // convert to png
414     vtkNew<vtkPNGWriter> png;
415     png->SetCompressionLevel(5);
416     png->SetInputConnection(flip->GetOutputPort());
417     png->WriteToMemoryOn();
418     png->Write();
419     da = png->GetResult();
420 
421     vtkGLTFWriterUtils::WriteBufferAndView(da, fileName, inlineData, buffers, bufferViews);
422 
423     // write the image
424     Json::Value img;
425     img["bufferView"] = bufferViews.size() - 1;
426     img["mimeType"] = "image/png";
427     images.append(img);
428 
429     textureSource = images.size() - 1;
430   }
431   else
432   {
433     textureSource = textureMap[da];
434   }
435 
436   // write the sampler
437   Json::Value smp;
438   smp["magFilter"] = GL_NEAREST;
439   smp["minFilter"] = GL_NEAREST;
440   smp["wrapS"] = GL_CLAMP_TO_EDGE;
441   smp["wrapT"] = GL_CLAMP_TO_EDGE;
442   if (t)
443   {
444     smp["wrapS"] = t->GetRepeat() ? GL_REPEAT : GL_CLAMP_TO_EDGE;
445     smp["wrapT"] = t->GetRepeat() ? GL_REPEAT : GL_CLAMP_TO_EDGE;
446     smp["magFilter"] = t->GetInterpolate() ? GL_LINEAR : GL_NEAREST;
447     smp["minFilter"] = t->GetInterpolate() ? GL_LINEAR : GL_NEAREST;
448   }
449   samplers.append(smp);
450 
451   Json::Value texture;
452   texture["source"] = textureSource;
453   texture["sampler"] = samplers.size() - 1;
454   textures.append(texture);
455 }
456 
WriteMaterial(Json::Value & materials,int textureIndex,bool haveTexture,vtkActor * aPart)457 void WriteMaterial(Json::Value& materials, int textureIndex, bool haveTexture, vtkActor* aPart)
458 {
459   Json::Value mat;
460   Json::Value model;
461 
462   if (haveTexture)
463   {
464     Json::Value tex;
465     tex["texCoord"] = 0; // TEXCOORD_0
466     tex["index"] = textureIndex;
467     model["baseColorTexture"] = tex;
468   }
469 
470   vtkProperty* prop = aPart->GetProperty();
471   double dcolor[3];
472   prop->GetDiffuseColor(dcolor);
473   model["baseColorFactor"].append(dcolor[0]);
474   model["baseColorFactor"].append(dcolor[1]);
475   model["baseColorFactor"].append(dcolor[2]);
476   model["baseColorFactor"].append(prop->GetOpacity());
477   model["metallicFactor"] = prop->GetSpecular();
478   model["roughnessFactor"] = 1.0 / (1.0 + prop->GetSpecular() * 0.2 * prop->GetSpecularPower());
479   mat["pbrMetallicRoughness"] = model;
480   materials.append(mat);
481 }
482 
483 }
484 
WriteToString()485 std::string vtkGLTFExporter::WriteToString()
486 {
487   std::ostringstream result;
488 
489   this->WriteToStream(result);
490 
491   return result.str();
492 }
493 
WriteData()494 void vtkGLTFExporter::WriteData()
495 {
496   vtksys::ofstream output;
497 
498   // make sure the user specified a FileName or FilePointer
499   if (this->FileName == nullptr)
500   {
501     vtkErrorMacro(<< "Please specify FileName to use");
502     return;
503   }
504 
505   // try opening the files
506   output.open(this->FileName);
507   if (!output.is_open())
508   {
509     vtkErrorMacro("Unable to open file for gltf output.");
510     return;
511   }
512 
513   this->WriteToStream(output);
514   output.close();
515 }
516 
WriteToStream(ostream & output)517 void vtkGLTFExporter::WriteToStream(ostream& output)
518 {
519   Json::Value cameras;
520   Json::Value bufferViews;
521   Json::Value buffers;
522   Json::Value accessors;
523   Json::Value nodes;
524   Json::Value meshes;
525   Json::Value textures;
526   Json::Value images;
527   Json::Value samplers;
528   Json::Value materials;
529 
530   std::vector<unsigned int> topNodes;
531 
532   // support sharing texture maps
533   std::map<vtkUnsignedCharArray*, unsigned int> textureMap;
534 
535   for (auto ren : vtk::Range(this->RenderWindow->GetRenderers()))
536   {
537     if (this->ActiveRenderer && ren != this->ActiveRenderer)
538     {
539       // If ActiveRenderer is specified then ignore all other renderers
540       continue;
541     }
542     if (!ren->GetDraw())
543     {
544       continue;
545     }
546 
547     // setup the camera data in case we need to use it later
548     Json::Value anode;
549     anode["camera"] = cameras.size(); // camera node
550     vtkMatrix4x4* mat = ren->GetActiveCamera()->GetModelViewTransformMatrix();
551     for (int i = 0; i < 4; ++i)
552     {
553       for (int j = 0; j < 4; ++j)
554       {
555         anode["matrix"].append(mat->GetElement(j, i));
556       }
557     }
558     anode["name"] = "Camera Node";
559 
560     // setup renderer group node
561     Json::Value rendererNode;
562     rendererNode["name"] = "Renderer Node";
563 
564     vtkPropCollection* pc;
565     vtkProp* aProp;
566     pc = ren->GetViewProps();
567     vtkCollectionSimpleIterator pit;
568     bool foundVisibleProp = false;
569     for (pc->InitTraversal(pit); (aProp = pc->GetNextProp(pit));)
570     {
571       if (!aProp->GetVisibility())
572       {
573         continue;
574       }
575       vtkNew<vtkActorCollection> ac;
576       aProp->GetActors(ac);
577       vtkActor* anActor;
578       vtkCollectionSimpleIterator ait;
579       for (ac->InitTraversal(ait); (anActor = ac->GetNextActor(ait));)
580       {
581         vtkAssemblyPath* apath;
582         vtkActor* aPart;
583         for (anActor->InitPathTraversal(); (apath = anActor->GetNextPath());)
584         {
585           aPart = static_cast<vtkActor*>(apath->GetLastNode()->GetViewProp());
586           if (aPart->GetVisibility() && aPart->GetMapper() &&
587             aPart->GetMapper()->GetInputAlgorithm())
588           {
589             aPart->GetMapper()->GetInputAlgorithm()->Update();
590             vtkPolyData* pd = findPolyData(aPart->GetMapper()->GetInputDataObject(0, 0));
591             if (pd && pd->GetNumberOfCells() > 0)
592             {
593               foundVisibleProp = true;
594               WriteMesh(accessors, buffers, bufferViews, meshes, nodes, pd, aPart, this->FileName,
595                 this->InlineData, this->SaveNormal, this->SaveBatchId);
596               rendererNode["children"].append(nodes.size() - 1);
597               unsigned int oldTextureCount = textures.size();
598               WriteTexture(buffers, bufferViews, textures, samplers, images, pd, aPart,
599                 this->FileName, this->InlineData, textureMap);
600               meshes[meshes.size() - 1]["primitives"][0]["material"] = materials.size();
601               WriteMaterial(materials, oldTextureCount, oldTextureCount != textures.size(), aPart);
602             }
603           }
604         }
605       }
606     }
607     // only write the camera if we had visible nodes
608     if (foundVisibleProp)
609     {
610       WriteCamera(cameras, ren);
611       nodes.append(anode);
612       rendererNode["children"].append(nodes.size() - 1);
613       nodes.append(rendererNode);
614       topNodes.push_back(nodes.size() - 1);
615     }
616   }
617 
618   Json::Value root;
619   Json::Value asset;
620   asset["generator"] = "VTK";
621   asset["version"] = "2.0";
622   root["asset"] = asset;
623 
624   root["scene"] = 0;
625   root["cameras"] = cameras;
626   root["nodes"] = nodes;
627   root["meshes"] = meshes;
628   root["buffers"] = buffers;
629   root["bufferViews"] = bufferViews;
630   root["accessors"] = accessors;
631   if (!images.empty())
632     root["images"] = images;
633   if (!textures.empty())
634     root["textures"] = textures;
635   if (!samplers.empty())
636     root["samplers"] = samplers;
637   root["materials"] = materials;
638 
639   Json::Value ascene;
640   ascene["name"] = "Layer 0";
641   Json::Value noderefs;
642   for (auto i : topNodes)
643   {
644     noderefs.append(i);
645   }
646   ascene["nodes"] = noderefs;
647   Json::Value scenes;
648   scenes.append(ascene);
649   root["scenes"] = scenes;
650 
651   Json::StreamWriterBuilder builder;
652   builder["commentStyle"] = "None";
653   builder["indentation"] = "   ";
654   std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
655   writer->write(root, &output);
656 }
657 
PrintSelf(ostream & os,vtkIndent indent)658 void vtkGLTFExporter::PrintSelf(ostream& os, vtkIndent indent)
659 {
660   this->Superclass::PrintSelf(os, indent);
661 
662   os << "InlineData: " << this->InlineData << "\n";
663   if (this->FileName)
664   {
665     os << indent << "FileName: " << this->FileName << "\n";
666   }
667   else
668   {
669     os << indent << "FileName: (null)\n";
670   }
671 }
672