4a09512247815a881e1876cf9960742aa5fcb775
1 /*
2 * A simple utility for exporting Inkscape svg Shapes as PovRay bezier
3 * prisms. Note that this is output-only, and would thus seem to be
4 * better placed as an 'export' rather than 'output'. However, Export
5 * handles all or partial documents, while this outputs ALL shapes in
6 * the current SVG document.
7 *
8 * For information on the PovRay file format, see:
9 * http://www.povray.org
10 *
11 * Authors:
12 * Bob Jamison <ishmal@inkscape.org>
13 *
14 * Copyright (C) 2004-2008 Authors
15 *
16 * Released under GNU GPL, read the file 'COPYING' for more information
17 */
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23 #include "pov-out.h"
24 #include <inkscape.h>
25 #include <inkscape_version.h>
26 #include <sp-path.h>
27 #include <style.h>
28 #include <display/curve.h>
29 #include <libnr/n-art-bpath.h>
30 #include <extension/system.h>
31 #include <2geom/pathvector.h>
32 #include <2geom/rect.h>
33 #include <2geom/bezier-curve.h>
34 #include <2geom/hvlinesegment.h>
35 #include "helper/geom.h"
36 #include <io/sys.h>
38 #include <string>
39 #include <stdio.h>
40 #include <stdarg.h>
43 namespace Inkscape
44 {
45 namespace Extension
46 {
47 namespace Internal
48 {
53 //########################################################################
54 //# U T I L I T Y
55 //########################################################################
59 /**
60 * This function searches the Repr tree recursively from the given node,
61 * and adds refs to all nodes with the given name, to the result vector
62 */
63 static void
64 findElementsByTagName(std::vector<Inkscape::XML::Node *> &results,
65 Inkscape::XML::Node *node,
66 char const *name)
67 {
68 if ( !name || strcmp(node->name(), name) == 0 )
69 results.push_back(node);
71 for (Inkscape::XML::Node *child = node->firstChild() ; child ;
72 child = child->next())
73 findElementsByTagName( results, child, name );
75 }
81 static double
82 effective_opacity(SPItem const *item)
83 {
84 double ret = 1.0;
85 for (SPObject const *obj = item; obj; obj = obj->parent)
86 {
87 SPStyle const *const style = SP_OBJECT_STYLE(obj);
88 g_return_val_if_fail(style, ret);
89 ret *= SP_SCALE24_TO_FLOAT(style->opacity.value);
90 }
91 return ret;
92 }
98 //########################################################################
99 //# OUTPUT FORMATTING
100 //########################################################################
103 /**
104 * We want to control floating output format
105 */
106 static PovOutput::String dstr(double d)
107 {
108 char dbuf[G_ASCII_DTOSTR_BUF_SIZE+1];
109 g_ascii_formatd(dbuf, G_ASCII_DTOSTR_BUF_SIZE,
110 "%.8f", (gdouble)d);
111 PovOutput::String s = dbuf;
112 return s;
113 }
118 /**
119 * Output data to the buffer, printf()-style
120 */
121 void PovOutput::out(const char *fmt, ...)
122 {
123 va_list args;
124 va_start(args, fmt);
125 gchar *output = g_strdup_vprintf(fmt, args);
126 va_end(args);
127 outbuf.append(output);
128 g_free(output);
129 }
135 /**
136 * Output a 2d vector
137 */
138 void PovOutput::vec2(double a, double b)
139 {
140 out("<%s, %s>", dstr(a).c_str(), dstr(b).c_str());
141 }
145 /**
146 * Output a 3d vector
147 */
148 void PovOutput::vec3(double a, double b, double c)
149 {
150 out("<%s, %s, %s>", dstr(a).c_str(), dstr(b).c_str(), dstr(c).c_str());
151 }
155 /**
156 * Output a v4d ector
157 */
158 void PovOutput::vec4(double a, double b, double c, double d)
159 {
160 out("<%s, %s, %s, %s>", dstr(a).c_str(), dstr(b).c_str(),
161 dstr(c).c_str(), dstr(d).c_str());
162 }
166 /**
167 * Output an rgbf color vector
168 */
169 void PovOutput::rgbf(double r, double g, double b, double f)
170 {
171 //"rgbf < %1.3f, %1.3f, %1.3f %1.3f>"
172 out("rgbf ");
173 vec4(r, g, b, f);
174 }
178 /**
179 * Output one bezier's start, start-control, end-control, and end nodes
180 */
181 void PovOutput::segment(int segNr,
182 double startX, double startY,
183 double startCtrlX, double startCtrlY,
184 double endCtrlX, double endCtrlY,
185 double endX, double endY)
186 {
187 //" /*%4d*/ <%f, %f>, <%f, %f>, <%f,%f>, <%f,%f>"
188 out(" /*%4d*/ ", segNr);
189 vec2(startX, startY);
190 out(", ");
191 vec2(startCtrlX, startCtrlY);
192 out(", ");
193 vec2(endCtrlX, endCtrlY);
194 out(", ");
195 vec2(endX, endY);
196 }
202 /**
203 * Output the file header
204 */
205 bool PovOutput::doHeader()
206 {
207 time_t tim = time(NULL);
208 out("/*###################################################################\n");
209 out("### This PovRay document was generated by Inkscape\n");
210 out("### http://www.inkscape.org\n");
211 out("### Created: %s", ctime(&tim));
212 out("### Version: %s\n", INKSCAPE_VERSION);
213 out("#####################################################################\n");
214 out("### NOTES:\n");
215 out("### ============\n");
216 out("### POVRay information can be found at\n");
217 out("### http://www.povray.org\n");
218 out("###\n");
219 out("### The 'AllShapes' objects at the bottom are provided as a\n");
220 out("### preview of how the output would look in a trace. However,\n");
221 out("### the main intent of this file is to provide the individual\n");
222 out("### shapes for inclusion in a POV project.\n");
223 out("###\n");
224 out("### For an example of how to use this file, look at\n");
225 out("### share/examples/istest.pov\n");
226 out("###\n");
227 out("### If you have any problems with this output, please see the\n");
228 out("### Inkscape project at http://www.inkscape.org, or visit\n");
229 out("### the #inkscape channel on irc.freenode.net . \n");
230 out("###\n");
231 out("###################################################################*/\n");
232 out("\n\n");
233 out("/*###################################################################\n");
234 out("## Exports in this file\n");
235 out("##==========================\n");
236 out("## Shapes : %d\n", nrShapes);
237 out("## Segments : %d\n", nrSegments);
238 out("## Nodes : %d\n", nrNodes);
239 out("###################################################################*/\n");
240 out("\n\n\n");
241 return true;
242 }
246 /**
247 * Output the file footer
248 */
249 bool PovOutput::doTail()
250 {
251 out("\n\n");
252 out("/*###################################################################\n");
253 out("### E N D F I L E\n");
254 out("###################################################################*/\n");
255 out("\n\n");
256 return true;
257 }
261 /**
262 * Output the curve data to buffer
263 */
264 bool PovOutput::doCurves(SPDocument *doc)
265 {
266 using Geom::X;
267 using Geom::Y;
269 std::vector<Inkscape::XML::Node *>results;
270 //findElementsByTagName(results, SP_ACTIVE_DOCUMENT->rroot, "path");
271 findElementsByTagName(results, SP_ACTIVE_DOCUMENT->rroot, NULL);
272 if (results.size() == 0)
273 return true;
275 double bignum = 1000000.0;
276 double minx = bignum;
277 double maxx = -bignum;
278 double miny = bignum;
279 double maxy = -bignum;
281 for (unsigned int indx = 0; indx < results.size() ; indx++)
282 {
283 //### Fetch the object from the repr info
284 Inkscape::XML::Node *rpath = results[indx];
285 char *str = (char *) rpath->attribute("id");
286 if (!str)
287 continue;
289 String id = str;
290 SPObject *reprobj = SP_ACTIVE_DOCUMENT->getObjectByRepr(rpath);
291 if (!reprobj)
292 continue;
294 //### Get the transform of the item
295 if (!SP_IS_ITEM(reprobj))
296 continue;
298 SPItem *item = SP_ITEM(reprobj);
299 Geom::Matrix tf = sp_item_i2d_affine(item);
301 //### Get the Shape
302 if (!SP_IS_SHAPE(reprobj))//Bulia's suggestion. Allow all shapes
303 continue;
305 SPShape *shape = SP_SHAPE(reprobj);
306 SPCurve *curve = shape->curve;
307 if (curve->is_empty())
308 continue;
310 nrShapes++;
312 PovShapeInfo shapeInfo;
313 shapeInfo.id = id;
314 shapeInfo.color = "";
316 //Try to get the fill color of the shape
317 SPStyle *style = SP_OBJECT_STYLE(shape);
318 /* fixme: Handle other fill types, even if this means translating gradients to a single
319 flat colour. */
320 if (style && (style->fill.isColor()))
321 {
322 // see color.h for how to parse SPColor
323 float rgb[3];
324 sp_color_get_rgb_floatv(&style->fill.value.color, rgb);
325 double const dopacity = ( SP_SCALE24_TO_FLOAT(style->fill_opacity.value)
326 * effective_opacity(shape) );
327 //gchar *str = g_strdup_printf("rgbf < %1.3f, %1.3f, %1.3f %1.3f>",
328 // rgb[0], rgb[1], rgb[2], 1.0 - dopacity);
329 String rgbf = "rgbf <";
330 rgbf.append(dstr(rgb[0])); rgbf.append(", ");
331 rgbf.append(dstr(rgb[1])); rgbf.append(", ");
332 rgbf.append(dstr(rgb[2])); rgbf.append(", ");
333 rgbf.append(dstr(1.0 - dopacity)); rgbf.append(">");
334 shapeInfo.color += rgbf;
335 }
337 povShapes.push_back(shapeInfo); //passed all tests. save the info
339 // convert the path to only lineto's and cubic curveto's:
340 Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( curve->get_pathvector() * tf );
342 //Count the NR_CURVETOs/LINETOs (including closing line segment)
343 guint segmentCount = 0;
344 for(Geom::PathVector::const_iterator it = pathv.begin(); it != pathv.end(); ++it) {
345 segmentCount += (*it).size();
346 if (it->closed())
347 segmentCount += 1;
348 }
350 out("/*###################################################\n");
351 out("### PRISM: %s\n", id.c_str());
352 out("###################################################*/\n");
353 out("#declare %s = prism {\n", id.c_str());
354 out(" linear_sweep\n");
355 out(" bezier_spline\n");
356 out(" 1.0, //top\n");
357 out(" 0.0, //bottom\n");
358 out(" %d //nr points\n", segmentCount * 4);
359 int segmentNr = 0;
361 nrSegments += segmentCount;
363 Geom::Rect cminmax( pathv.front().initialPoint(), pathv.front().initialPoint() ); // at moment of writing, 2geom lacks proper initialization of empty intervals in rect...
366 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit)
367 {
369 cminmax.expandTo(pit->initialPoint());
371 for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit)
372 {
374 if( dynamic_cast<Geom::LineSegment const *> (&*cit) ||
375 dynamic_cast<Geom::HLineSegment const *>(&*cit) ||
376 dynamic_cast<Geom::VLineSegment const *>(&*cit) )
377 {
378 Geom::Point p0 = cit->initialPoint() * tf;
379 Geom::Point p1 = cit->finalPoint() * tf;
380 segment(segmentNr++,
381 p0[X], p0[Y], p0[X], p0[Y], p1[X], p1[Y], p1[X], p1[Y] );
382 nrNodes += 8;
383 }
384 else if(Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit))
385 {
386 std::vector<Geom::Point> points = cubic->points();
387 Geom::Point p0 = points[0] * tf;
388 Geom::Point p1 = points[1] * tf;
389 Geom::Point p2 = points[2] * tf;
390 Geom::Point p3 = points[3] * tf;
391 segment(segmentNr++,
392 p0[X],p0[Y], p1[X],p1[Y], p2[X],p2[Y], p3[X],p3[Y]);
393 nrNodes += 8;
394 }
395 else
396 {
397 g_warning("logical error, because pathv_to_linear_and_cubic_beziers was used");
398 return false;
399 }
401 if (segmentNr <= static_cast<int>(segmentCount))
402 out(",\n");
403 else
404 out("\n");
406 cminmax.expandTo(cit->finalPoint());
408 }
409 }
411 out("}\n");
413 double cminx = cminmax.min()[X];
414 double cmaxx = cminmax.max()[X];
415 double cminy = cminmax.min()[Y];
416 double cmaxy = cminmax.max()[Y];
418 //# prefix for following declarations
419 char *pfx = (char *)id.c_str();
421 out("#declare %s_MIN_X = %s;\n", pfx, dstr(cminx).c_str());
422 out("#declare %s_CENTER_X = %s;\n", pfx, dstr((cmaxx+cminx)/2.0).c_str());
423 out("#declare %s_MAX_X = %s;\n", pfx, dstr(cmaxx).c_str());
424 out("#declare %s_WIDTH = %s;\n", pfx, dstr(cmaxx-cminx).c_str());
425 out("#declare %s_MIN_Y = %s;\n", pfx, dstr(cminy).c_str());
426 out("#declare %s_CENTER_Y = %s;\n", pfx, dstr((cmaxy+cminy)/2.0).c_str());
427 out("#declare %s_MAX_Y = %s;\n", pfx, dstr(cmaxy).c_str());
428 out("#declare %s_HEIGHT = %s;\n", pfx, dstr(cmaxy-cminy).c_str());
429 if (shapeInfo.color.length()>0)
430 out("#declare %s_COLOR = %s;\n",
431 pfx, shapeInfo.color.c_str());
432 out("/*###################################################\n");
433 out("### end %s\n", id.c_str());
434 out("###################################################*/\n\n\n\n");
435 if (cminx < minx)
436 minx = cminx;
437 if (cmaxx > maxx)
438 maxx = cmaxx;
439 if (cminy < miny)
440 miny = cminy;
441 if (cmaxy > maxy)
442 maxy = cmaxy;
444 }//for
448 //## Let's make a union of all of the Shapes
449 if (povShapes.size()>0)
450 {
451 String id = "AllShapes";
452 char *pfx = (char *)id.c_str();
453 out("/*###################################################\n");
454 out("### UNION OF ALL SHAPES IN DOCUMENT\n");
455 out("###################################################*/\n");
456 out("\n\n");
457 out("/**\n");
458 out(" * Allow the user to redefine the finish{}\n");
459 out(" * by declaring it before #including this file\n");
460 out(" */\n");
461 out("#ifndef (%s_Finish)\n", pfx);
462 out("#declare %s_Finish = finish {\n", pfx);
463 out(" phong 0.5\n");
464 out(" reflection 0.3\n");
465 out(" specular 0.5\n");
466 out("}\n");
467 out("#end\n");
468 out("\n\n");
469 out("#declare %s = union {\n", id.c_str());
470 for (unsigned i = 0 ; i < povShapes.size() ; i++)
471 {
472 out(" object { %s\n", povShapes[i].id.c_str());
473 out(" texture { \n");
474 if (povShapes[i].color.length()>0)
475 out(" pigment { %s }\n", povShapes[i].color.c_str());
476 else
477 out(" pigment { rgb <0,0,0> }\n");
478 out(" finish { %s_Finish }\n", pfx);
479 out(" } \n");
480 out(" } \n");
481 }
482 out("}\n\n\n\n");
485 double zinc = 0.2 / (double)povShapes.size();
486 out("/*#### Same union, but with Z-diffs (actually Y in pov) ####*/\n");
487 out("\n\n");
488 out("/**\n");
489 out(" * Allow the user to redefine the Z-Increment\n");
490 out(" */\n");
491 out("#ifndef (AllShapes_Z_Increment)\n");
492 out("#declare AllShapes_Z_Increment = %s;\n", dstr(zinc).c_str());
493 out("#end\n");
494 out("\n");
495 out("#declare AllShapes_Z_Scale = 1.0;\n");
496 out("\n\n");
497 out("#declare %s_Z = union {\n", pfx);
499 for (unsigned i = 0 ; i < povShapes.size() ; i++)
500 {
501 out(" object { %s\n", povShapes[i].id.c_str());
502 out(" texture { \n");
503 if (povShapes[i].color.length()>0)
504 out(" pigment { %s }\n", povShapes[i].color.c_str());
505 else
506 out(" pigment { rgb <0,0,0> }\n");
507 out(" finish { %s_Finish }\n", pfx);
508 out(" } \n");
509 out(" scale <1, %s_Z_Scale, 1>\n", pfx);
510 out(" } \n");
511 out("#declare %s_Z_Scale = %s_Z_Scale + %s_Z_Increment;\n\n",
512 pfx, pfx, pfx);
513 }
515 out("}\n");
517 out("#declare %s_MIN_X = %s;\n", pfx, dstr(minx).c_str());
518 out("#declare %s_CENTER_X = %s;\n", pfx, dstr((maxx+minx)/2.0).c_str());
519 out("#declare %s_MAX_X = %s;\n", pfx, dstr(maxx).c_str());
520 out("#declare %s_WIDTH = %s;\n", pfx, dstr(maxx-minx).c_str());
521 out("#declare %s_MIN_Y = %s;\n", pfx, dstr(miny).c_str());
522 out("#declare %s_CENTER_Y = %s;\n", pfx, dstr((maxy+miny)/2.0).c_str());
523 out("#declare %s_MAX_Y = %s;\n", pfx, dstr(maxy).c_str());
524 out("#declare %s_HEIGHT = %s;\n", pfx, dstr(maxy-miny).c_str());
525 out("/*##############################################\n");
526 out("### end %s\n", id.c_str());
527 out("##############################################*/\n");
528 out("\n\n");
529 }
531 return true;
532 }
537 //########################################################################
538 //# M A I N O U T P U T
539 //########################################################################
543 /**
544 * Set values back to initial state
545 */
546 void PovOutput::reset()
547 {
548 nrNodes = 0;
549 nrSegments = 0;
550 nrShapes = 0;
551 outbuf.clear();
552 povShapes.clear();
553 }
557 /**
558 * Saves the Shapes of an Inkscape SVG file as PovRay spline definitions
559 */
560 void PovOutput::saveDocument(SPDocument *doc, gchar const *uri)
561 {
562 reset();
564 //###### SAVE IN POV FORMAT TO BUFFER
565 //# Lets do the curves first, to get the stats
566 if (!doCurves(doc))
567 {
568 g_warning("Could not output curves for %s\n", uri);
569 return;
570 }
572 String curveBuf = outbuf;
573 outbuf.clear();
575 if (!doHeader())
576 {
577 g_warning("Could not write header for %s\n", uri);
578 return;
579 }
581 outbuf.append(curveBuf);
583 if (!doTail())
584 {
585 g_warning("Could not write footer for %s\n", uri);
586 return;
587 }
592 //###### WRITE TO FILE
593 Inkscape::IO::dump_fopen_call(uri, "L");
594 FILE *f = Inkscape::IO::fopen_utf8name(uri, "w");
595 if (!f)
596 return;
598 for (String::iterator iter = outbuf.begin() ; iter!=outbuf.end(); iter++)
599 {
600 int ch = *iter;
601 fputc(ch, f);
602 }
604 fclose(f);
605 }
610 //########################################################################
611 //# EXTENSION API
612 //########################################################################
616 #include "clear-n_.h"
620 /**
621 * API call to save document
622 */
623 void
624 PovOutput::save(Inkscape::Extension::Output *mod,
625 SPDocument *doc, gchar const *uri)
626 {
627 saveDocument(doc, uri);
628 }
632 /**
633 * Make sure that we are in the database
634 */
635 bool PovOutput::check (Inkscape::Extension::Extension *module)
636 {
637 /* We don't need a Key
638 if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_POV))
639 return FALSE;
640 */
642 return true;
643 }
647 /**
648 * This is the definition of PovRay output. This function just
649 * calls the extension system with the memory allocated XML that
650 * describes the data.
651 */
652 void
653 PovOutput::init()
654 {
655 Inkscape::Extension::build_from_mem(
656 "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
657 "<name>" N_("PovRay Output") "</name>\n"
658 "<id>org.inkscape.output.pov</id>\n"
659 "<output>\n"
660 "<extension>.pov</extension>\n"
661 "<mimetype>text/x-povray-script</mimetype>\n"
662 "<filetypename>" N_("PovRay (*.pov) (export splines)") "</filetypename>\n"
663 "<filetypetooltip>" N_("PovRay Raytracer File") "</filetypetooltip>\n"
664 "</output>\n"
665 "</inkscape-extension>",
666 new PovOutput());
667 }
673 } // namespace Internal
674 } // namespace Extension
675 } // namespace Inkscape
678 /*
679 Local Variables:
680 mode:c++
681 c-file-style:"stroustrup"
682 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
683 indent-tabs-mode:nil
684 fill-column:99
685 End:
686 */
687 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :