1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 /*!
34   \class SoWriteAction SoWriteAction.h Inventor/actions/SoWriteAction.h
35   \brief The SoWriteAction class writes a scene graph to file.
36 
37   \ingroup actions
38 
39   When applied to a scene, this action writes its contents to the
40   stream contained within an SoOutput instance. This can be a file, a
41   memory buffer or a system filehandle like \c stdout, for instance.
42 
43   \e All information considered part of the scene graph should be
44   written out, including not only nodes, but also the nodes' field
45   values, global fields (at least those with connections inside the
46   scene the action is applied to), engines in the scene, paths, etc.
47 
48   The scene is written in the Open Inventor file format. Files in this
49   format can be parsed into their scene graph structures by using the
50   SoDB::readAll() method (SoDB also contains a few other import
51   methods you can use).
52 
53   Here's a complete, stand-alone usage example which shows how to
54   write a scene graph to a memory buffer:
55 
56   \code
57   #include <Inventor/SoDB.h>
58   #include <Inventor/actions/SoWriteAction.h>
59   #include <Inventor/nodes/SoCone.h>
60   #include <Inventor/nodes/SoSeparator.h>
61 
62   static char * buffer;
63   static size_t buffer_size = 0;
64 
65   static void *
66   buffer_realloc(void * bufptr, size_t size)
67   {
68     buffer = (char *)realloc(bufptr, size);
69     buffer_size = size;
70     return buffer;
71   }
72 
73   static SbString
74   buffer_writeaction(SoNode * root)
75   {
76     SoOutput out;
77     buffer = (char *)malloc(1024);
78     buffer_size = 1024;
79     out.setBuffer(buffer, buffer_size, buffer_realloc);
80 
81     SoWriteAction wa(&out);
82     wa.apply(root);
83 
84     SbString s(buffer);
85     free(buffer);
86     return s;
87   }
88 
89   int
90   main(int argc, char ** argv)
91   {
92     SoDB::init();
93 
94     SoSeparator * root = new SoSeparator;
95     root->ref();
96 
97     root->addChild(new SoCone);
98 
99     SbString s = buffer_writeaction(root);
100     (void)fprintf(stdout, "%s\n", s.getString());
101 
102     root->unref();
103     return 0;
104   }
105   \endcode
106 
107   \sa SoOutput
108 */
109 
110 #include <Inventor/actions/SoWriteAction.h>
111 
112 #include <Inventor/SoOutput.h>
113 #include <Inventor/nodes/SoNode.h>
114 #include <Inventor/sensors/SoNodeSensor.h>
115 #include <Inventor/errors/SoDebugError.h>
116 
117 #include "coindefs.h"
118 #include "actions/SoSubActionP.h"
119 #include "io/SoWriterefCounter.h"
120 
121 class SoWriteActionP {
122 public:
123 };
124 
125 SO_ACTION_SOURCE(SoWriteAction);
126 
127 
128 // Overridden from parent class.
129 void
initClass(void)130 SoWriteAction::initClass(void)
131 {
132   SO_ACTION_INTERNAL_INIT_CLASS(SoWriteAction, SoAction);
133 }
134 
135 /*!
136   Default constructor. Output will be written to \c stdout in ASCII
137   format.
138 */
SoWriteAction(void)139 SoWriteAction::SoWriteAction(void)
140 {
141   this->commonConstructor(new SoOutput);
142   this->localoutputalloc = TRUE;
143 }
144 
145 /*!
146   Constructor. Output will be written via the \a out object.
147 */
SoWriteAction(SoOutput * out)148 SoWriteAction::SoWriteAction(SoOutput * out)
149 {
150   this->commonConstructor(out);
151   this->localoutputalloc = FALSE;
152 }
153 
154 void
commonConstructor(SoOutput * out)155 SoWriteAction::commonConstructor(SoOutput * out)
156 {
157   SO_ACTION_CONSTRUCTOR(SoWriteAction);
158 
159   this->outobj = out;
160   this->continuing = FALSE;
161 }
162 
163 /*!
164   Destructor.
165 */
~SoWriteAction(void)166 SoWriteAction::~SoWriteAction(void)
167 {
168   if (this->localoutputalloc) delete this->outobj;
169 }
170 
171 /*!
172   Returns a pointer to the SoOutput object we're using when writing
173   the scene graph.
174  */
175 SoOutput *
getOutput(void) const176 SoWriteAction::getOutput(void) const
177 {
178   return this->outobj;
179 }
180 
181 /*!
182   Applies the write method to the subgraph starting at \a node with
183   the current SoOutput instance, without resetting any of the internal
184   state of the action instance.
185 
186   This should normally be for internal use only.
187 */
188 void
continueToApply(SoNode * node)189 SoWriteAction::continueToApply(SoNode * node)
190 {
191   SbBool wascontinuing = this->continuing;
192   this->continuing = TRUE;
193   this->apply(node);
194   this->continuing = wascontinuing;
195 }
196 
197 /*!
198   Applies the write method to \a path with the current SoOutput
199   instance, without resetting any of the internal state of the action
200   instance.
201 
202   This should normally be for internal use only.
203 */
204 void
continueToApply(SoPath * path)205 SoWriteAction::continueToApply(SoPath * path)
206 {
207   SbBool wascontinuing = this->continuing;
208   this->continuing = TRUE;
209   this->apply(path);
210   this->continuing = wascontinuing;
211 }
212 
213 #if COIN_DEBUG
sensorCB(void * COIN_UNUSED_ARG (data),SoSensor * COIN_UNUSED_ARG (sensor))214 static void sensorCB(void * COIN_UNUSED_ARG(data), SoSensor * COIN_UNUSED_ARG(sensor))
215 {
216   SoDebugError::postWarning("SoWriteAction::SoWriteAction",
217                             "Scenegraph changed during SoWriteAction().");
218 }
219 #endif
220 
221 // Documented for Doxygen in superclass.
222 //
223 // Overridden from parent class, as the write action is actually done
224 // in two passes.
225 //
226 // The first pass is done to count the references of the objects in
227 // the scene graph and otherwise prepare instance in the scene for
228 // export.  The second pass does the actual writing.
229 void
beginTraversal(SoNode * node)230 SoWriteAction::beginTraversal(SoNode * node)
231 {
232 #if COIN_DEBUG
233   SoNodeSensor *sensor = NULL;
234 #endif
235   if (this->continuing == FALSE) { // Run through both stages.
236     // call SoWriterefCounter::instance() before traversing to set the
237     // "current" pointer in SoWriterefCounter. This is needed to be
238     // backwards compatible with old code that uses the writeref
239     // system in SoBase.
240 
241 #if COIN_DEBUG
242     if (SoWriterefCounter::debugWriterefs()) {
243       sensor = new SoNodeSensor(sensorCB, NULL);
244       sensor->setPriority(0);
245       sensor->attach(node);
246     }
247 #endif
248 
249     (void) SoWriterefCounter::instance(this->getOutput());
250     this->outobj->setStage(SoOutput::COUNT_REFS);
251     this->traverse(node);
252     this->outobj->setStage(SoOutput::WRITE);
253   }
254   this->traverse(node);
255   if (!this->outobj->isBinary() && !this->continuing) {
256     outobj->write('\n');
257     outobj->resolveRoutes();
258   }
259   if (!this->continuing) {
260     SoWriterefCounter::instance(this->getOutput())->debugCleanup();
261 #if COIN_DEBUG
262     delete sensor;
263 #endif
264   }
265 }
266 
267 /*!
268   \COININTERNAL
269 
270   Compact path lists are not implemented in Coin (yet), but if they
271   are, SoWriteAction should return \c FALSE here -- it would only be
272   extra overhead for the SoWriteAction to have pathlists compacted
273   before traversal.
274 
275   Seems like a silly optimization to me, though.. :^/  20000306 mortene.
276 */
277 SbBool
shouldCompactPathLists(void) const278 SoWriteAction::shouldCompactPathLists(void) const
279 {
280   return FALSE;
281 }
282 
283 #ifdef COIN_TEST_SUITE
284 
285 // check that the realTime GlobalField is written if it has any
286 // forward connections.
287 
288 #include <Inventor/SoDB.h>
289 #include <Inventor/SoInput.h>
290 #include <Inventor/SoOutput.h>
291 #include <Inventor/fields/SoSFTime.h>
292 #include <Inventor/nodes/SoSeparator.h>
293 #include <Inventor/nodes/SoText2.h>
294 #include <Inventor/C/tidbits.h>
295 
BOOST_AUTO_TEST_CASE(GlobalField)296 BOOST_AUTO_TEST_CASE(GlobalField)
297 {
298   SoDB::init();
299 
300   //Store away the state before we mess with the global realTime field
301   SoSFTime * realtime = static_cast<SoSFTime *>(SoDB::getGlobalField("realTime"));
302   assert(realtime);
303   SbTime realTimeStorage = realtime->getValue();
304 
305   static const char inlinescenegraph[] =
306     "#Inventor V2.1 ascii\n"
307     "\n"
308     "\n"
309     "Separator {\n"
310     "\n"
311     "  Text2 { \n"
312     "    string \"\" = GlobalField { \n"
313     "      type \"SFTime\" realTime 0 \n"
314     "\n"
315     "    } . realTime \n"
316     "  }\n"
317     "}\n";
318 
319   // read scene
320   SoInput in;
321   in.setBuffer((void *) inlinescenegraph, strlen(inlinescenegraph));
322   SoSeparator * top = SoDB::readAll(&in);
323   BOOST_REQUIRE(top);
324   top->ref();
325 
326   // write scene
327   SoOutput out;
328   const int buffer_size = 1024;
329   char * buffer = (char *)malloc(buffer_size);
330   out.setBuffer(buffer, buffer_size, NULL);
331 
332   SoWriteAction wa(&out);
333   wa.apply((SoNode *)top);
334 
335   top->unref();
336   top = NULL;
337 
338   // read scene again to check if realTime field was written
339   in.setBuffer((void *)buffer, strlen(buffer));
340   top = SoDB::readAll(&in);
341   BOOST_REQUIRE(top);
342   top->ref();
343 
344   SoText2 * text = (SoText2 *)top->getChild(0);
345   BOOST_REQUIRE(text);
346 
347   SoField * string = text->getField("string");
348   BOOST_REQUIRE(string);
349   BOOST_CHECK_MESSAGE(string->isConnected(), "String field not connected to realTime field in written scene graph");
350 
351   free(buffer);
352 
353   top->unref();
354 
355   //Restore state
356   realtime->setValue(realTimeStorage);
357 
358 }
359 
360 #endif // COIN_TEST_SUITE
361