1 /* Copyright (C) 1992-1998 The Geometry Center
2  * Copyright (C) 1998-2000 Stuart Levy, Tamara Munzner, Mark Phillips
3  *
4  * This file is part of Geomview.
5  *
6  * Geomview is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published
8  * by the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * Geomview is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with Geomview; see the file COPYING.  If not, write
18  * to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
19  * USA, or visit http://www.gnu.org.
20  */
21 
22 #if HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25 
26 #if 0
27 static char copyright[] = "Copyright (C) 1992-1998 The Geometry Center\n\
28 Copyright (C) 1998-2000 Stuart Levy, Tamara Munzner, Mark Phillips";
29 #endif
30 
31 
32 /* Authors: Stuart Levy, Tamara Munzner, Mark Phillips,
33  * Celeste Fowler */
34 
35 #include "mg.h"
36 #include "mgbuf.h"
37 #include "drawer.h"
38 #include "ui.h"
39 #include "geom.h"
40 #include "geomclass.h"
41 #include "instP.h"
42 #include "listP.h"
43 #include "streampool.h"
44 #include "comm.h"
45 #include "lang.h"
46 #include "main.h"
47 #include "transobj.h"
48 #include "ntransobj.h"
49 #include "worldio.h"
50 #include <string.h>
51 #include <stdlib.h>
52 #ifdef NeXT
53 #include <bsd/libc.h>
54 #else /* any other reasonable UNIX */
55 #include <unistd.h>
56 #endif
57 
58 
59 void 	set_emodule_path(char **dirs);
60 void 	set_load_path(char **dirs);
61 
62 static void	save_dgeom(Pool *p, DGeom *dg, int doaliens);
63 static void	save_dgeom_geom(Pool *p, DGeom *dg, bool world, bool wrap, int cum);
64 static void	save_dview(Pool *p, DView *dv);
65 static int	nonidentity(Transform T);
66 static void	maybe_save_xform(Pool *p, char *cmd, char *name, Handle *, Transform T);
67 static char *	normalization_string(int n);
68 static char *	onoff_string(int n);
69 
70 int
save_world(Pool * p,int id,bool comm,int wrap,int to_coords)71 save_world(Pool *p, int id, bool comm, int wrap, int to_coords)
72 {
73   Appearance *ap;
74   int i, closeme = 0;
75   FILE *fp;
76   Transform T;
77   DGeom *dg = NULL;
78   Handle *h = NULL;
79 
80   if ((fp = PoolOutputFile(p)) == NULL) {
81     return 0;
82   }
83 
84 #if 0
85   /* Hack -- don't let StreamOut routines write Handle names. */
86   /* "Temporary" workaround for library reading bug. -slevy */
87   PoolSetOType(p, PO_DATA);
88 #endif
89 
90   if (id == NOID) {
91     /* cH: hack, but otherwise we cannot save the universe, no aliens. */
92     id = UNIVERSE;
93   }
94 
95   if (!comm) {
96     /* Write geometry */
97 
98     if (wrap) {
99       if (id == UNIVERSE || ISCAM(id)) {
100 	PoolFPrint(p, fp, "{ # Base appearance\n");
101 	PoolIncLevel(p, 1);
102 	ApStreamOut(p, NULL, drawerstate.ap);
103 	PoolFPrint(p, fp, "# end base appearance\n");
104 	if (ISCAM(id)) {
105 	  PoolIncLevel(p, -1);
106 	  PoolFPrint(p, fp, "}\n");
107 	  return !ferror(fp);
108 	} else closeme = 1;
109       }
110     }
111     if (id == UNIVERSE) {
112       id = WORLDGEOM;
113     }
114     if (ISGEOM(id))
115       dg = (DGeom *)drawer_get_object(id);
116     if (id == WORLDGEOM) {
117       if (wrap) {
118 	save_dgeom_geom(p, dg, true, wrap, to_coords);
119       }
120       PoolFPrint(p, fp, "{ LIST # World list \n");
121       PoolIncLevel(p, 1);
122       for(i = 1; i < dgeom_max; i++) {
123 	dg = (DGeom *)drawer_get_object(GEOMID(i));
124 	if (dg != NULL && dg->citizenship != ALIEN)
125 	  save_dgeom_geom(p, dg, false, true, WORLDGEOM);
126       }
127       PoolIncLevel(p, -1);
128       PoolFPrint(p, fp, "} #end of World List\n");
129       if (wrap) {
130 	PoolIncLevel(p, -1);
131 	PoolFPrint(p, fp, "} # end of World and INST\n");
132 	if (closeme) {
133 	  PoolIncLevel(p, -1);
134 	  PoolFPrint(p, fp, "}\n");
135 	}
136       }
137     } else {
138       save_dgeom_geom(p, dg, false, wrap, to_coords);
139     }
140     return !ferror(fp);
141   }
142 
143   /* Write command language */
144 
145 
146   PoolFPrint(p, fp, "(progn\n");
147   PoolIncLevel(p, 1); /* Ensure embedded objects have { braces } */
148   if (id == UNIVERSE || id == WORLDGEOM) {
149     PoolFPrint(p, fp, "(merge-baseap ");
150     ApStreamOut(p, NULL, drawerstate.ap);
151     PoolFPrint(p, fp, ") # end base appearance\n");
152     if (drawerstate.NDim > 0)
153       PoolFPrint(p, fp, "(dimension %d)\n", drawerstate.NDim-1);
154   }
155 
156   if (id != UNIVERSE && id != WORLDGEOM) {
157     DObject *dobj = drawer_get_object(id);
158     if (dobj) {
159       if (ISGEOM(dobj->id))
160 	save_dgeom(p, (DGeom *)dobj, 1);
161       else
162 	save_dview(p, (DView *)dobj);
163     }
164   } else {
165 
166     /* save data associated with the World dgeom */
167     GeomGet( dgeom[0]->Item, CR_AXIS, T );
168     GeomGet( dgeom[0]->Item, CR_AXISHANDLE, &h );
169     maybe_save_xform(p, "xform-set", "worldgeom", h, T);
170     maybe_save_xform(p, "xform-incr", "worldgeom",
171 		     dgeom[0]->incrhandle, dgeom[0]->Incr);
172 
173     GeomGet( dgeom[0]->Item, CR_APPEAR, &ap );
174     if (ap != NULL) {
175       PoolFPrint(p, fp, "(merge-ap \"worldgeom\" ");
176       ApStreamOut( p, NULL, ap );
177       PoolFPrint(p, fp, ") # end appearance \"worldgeom\"\n");
178     }
179 
180     /* save the dgeoms.  Include aliens if id == UNIVERSE. */
181     for (i=1; i<dgeom_max; ++i)
182       if (dgeom[i] != NULL) save_dgeom(p, dgeom[i], id == UNIVERSE);
183 
184     /* save the dviews */
185     for (i=0; i<dview_max; ++i)
186       if (dview[i] != NULL) save_dview( p, dview[i] );
187 
188     /* save other stuff here ... */
189 
190     dg = (DGeom *)drawer_get_object(uistate.targetid);
191     if (dg) {
192       PoolFPrint(p, fp, "(ui-target \"%s\")\n", dg->name[1]);
193     }
194   }
195   PoolIncLevel(p, -1);
196   PoolFPrint(p, fp, ")\n");		/* terminates the (progn... */
197   return !ferror(fp);
198 }
199 
200 
201 /*
202  * Save a DGeom as geometry.  Emit appearance and transform if wrap.
203  * if not 'world', also add geometry and close the braces.
204  */
205 static void
save_dgeom_geom(Pool * p,DGeom * dg,bool world,bool wrap,int to_coords)206 save_dgeom_geom(Pool *p, DGeom *dg, bool world, bool wrap, int to_coords)
207 {
208   FILE *fp = PoolOutputFile(p);
209   Transform T;
210   Handle *h = NULL;
211   Geom *g = NULL;
212   Appearance *ap = NULL;
213 
214   if (dg == NULL || dg->Item == NULL)
215     return;
216 
217   if (wrap) {
218 
219     PoolFPrint(p, fp, "{ # %s\n", dg->name[1]);
220     PoolIncLevel(p, 1);
221     PoolFPrint(p, fp, "INST\n");
222 
223     drawer_get_transform(dg->id, T, to_coords);
224     if (nonidentity(T) || h) {	/* Could say "transform" here */
225       PoolFPrint(p, fp, "");
226       TransStreamOut(p, h, T);	/* but TransStreamOut does that */
227     }
228     PoolFPrint(p, fp, "geom ");
229     GeomGet(dg->Item, CR_APPEAR, &ap);
230     if (ap) {
231       ap = ApCopy(ap, NULL);
232       ap->override = 0;
233       if (ap->mat) ap->mat->override = 0;
234       if (ap->lighting) ap->lighting->override = 0;
235       PoolFPrint(p, fp, "");
236       ApStreamOut(p, NULL, ap);
237       ApDelete(ap);
238     }
239   }
240   if (!world) {
241     h = NULL;
242     GeomGet(dg->Lgeom, CR_GEOM, &g);
243     GeomGet(dg->Lgeom, CR_GEOMHANDLE, &h);
244     GeomStreamOut(p, h, g);
245     if (wrap) {
246       PoolIncLevel(p, -1);
247       PoolFPrint(p, fp, "} # end (geom and INST) %s\n", dg->name[1]);
248     }
249   }
250 }
251 
252 static void
maybe_save_xform(Pool * p,char * cmd,char * name,Handle * h,Transform T)253 maybe_save_xform(Pool *p, char *cmd, char *name, Handle *h, Transform T)
254 {
255   if (h || nonidentity(T)) {
256     fprintf(PoolOutputFile(p), "(%s \"%s\" ", cmd, name);
257     TransStreamOut(p, h, T);
258     fprintf(PoolOutputFile(p), ")\n");
259   }
260 }
261 
262 static void
save_dgeom(Pool * p,DGeom * dg,int doaliens)263 save_dgeom(Pool *p, DGeom *dg, int doaliens)
264 {
265   Geom *g=NULL;
266   Appearance *ap=NULL;
267   FILE *fp = PoolOutputFile(p);
268   Handle *h = NULL;
269   Transform T;
270   char name[32];
271   char *truename = dg->name[1];
272 
273   sprintf(name, "[%s]", dg->name[0]);	/* Temporary name -- unlikely to conflict */
274 
275   if (dg->citizenship == ALIEN) {
276     if (!doaliens)
277       return;
278     PoolFPrint(p, fp, "(new-alien");
279   } else {
280     PoolFPrint(p, fp, "(new-geometry");
281   }
282   fprintf(fp, " \"%s\"	# %s\n", name, truename);
283   PoolIncLevel(p, 1);
284   PoolFPrint(p, fp, "");
285   GeomGet( dg->Lgeom, CR_GEOM, &g );
286   GeomGet( dg->Lgeom, CR_GEOMHANDLE, &h );
287   GeomStreamOut( p, h, g );
288   PoolIncLevel(p, -1);
289   PoolFPrint(p, fp, ") # end geometry \"%s\" %s\n", name, truename);
290 
291   GeomGet( dg->Item, CR_APPEAR, &ap );
292   if (ap != NULL) {
293     PoolFPrint(p, fp, "(merge-ap \"%s\" ", name);
294     ApStreamOut( p, NULL, ap );
295     PoolFPrint(p, fp, ") # end appearance \"%s\" %s\n", name, truename);
296   }
297 
298   TmIdentity(T);
299   GeomGet( dg->Item, CR_AXIS, T );
300   GeomGet( dg->Item, CR_AXISHANDLE, &h );
301   maybe_save_xform(p, "xform-set", name, h, T);
302 
303   if (dg->NDT) {
304     PoolFPrint(p, fp, "(ND-xform-set \"%s\" ", name);
305     NTransStreamOut(p, NULL, dg->NDT);
306     PoolFPrint(p, fp, ")\n");
307   }
308 
309   PoolFPrint(p, fp, "(bbox-draw \"%s\" %s)\n",
310 	     name, onoff_string(dg->bboxdraw));
311   if (dg->bboxap && dg->bboxap->mat) {
312     Color *c = &(dg->bboxap->mat->edgecolor);
313     PoolFPrint(p, fp, "(bbox-color \"%s\" %f %f %f)\n", name, c->r, c->g, c->b);
314   }
315 
316   PoolFPrint(p, fp, "(normalization \"%s\" %s)\n", name,
317 	     normalization_string(dg->normalization));
318   PoolFPrint(p, fp, "(name-object \"%s\" \"%s\")\n\n", name, truename);
319 }
320 
321 static void
save_dview(Pool * p,DView * dv)322 save_dview(Pool *p, DView *dv)
323 {
324   char *name = dv->name[1];
325   FILE *fp = PoolOutputFile(p);
326   WnPosition wp;
327 
328   if (WnGet(dv->win, WN_CURPOS, &wp) > 0) {
329     WnSet(dv->win, WN_PREFPOS, &wp, WN_END);
330     PoolFPrint(p, fp, "(window default { position %d %d %d %d })\n",
331 	       wp.xmin,wp.xmax,wp.ymin,wp.ymax);
332   }
333 
334   PoolFPrint(p, fp, "(camera \"%s\" ", name);
335   CamStreamOut(p, dv->camhandle, dv->cam);
336   PoolFPrint(p, fp, ") # camera \"%s\" ...\n", name);
337 
338   PoolFPrint(p, fp, "(backcolor \"%s\" %f %f %f)\n",
339 	     name, dv->backcolor.r,dv->backcolor.g,dv->backcolor.b);
340   if (dv->cameradraw) {
341     PoolFPrint(p, fp, "(camera-draw \"%s\" yes)\n", name);
342   }
343 
344   if (dv->cluster != NULL) {
345     cmap *cm;
346     cent *ce;
347     int i, j;
348     PoolFPrint(p, fp, "(dimension %d)\n", drawerstate.NDim-1);
349 
350     PoolFPrint(p, fp, "(ND-axes \"%s\" \"%s\" %d %d %d %d)\n",
351 	    name, dv->cluster->name,
352 	    dv->NDPerm[0], dv->NDPerm[1], dv->NDPerm[2], dv->NDPerm[3]);
353 
354     if (dv->cluster->C2W != NULL) {
355       PoolFPrint(p, fp, "(ND-xform-set \"%s\" ", name);
356       TmNPrint(fp, dv->cluster->C2W);
357       PoolFPrint(p, fp, ")\n");
358     }
359 
360     PoolFPrint(p, fp, "(ND-color \"%s\" (\n", name);
361     for(i = 0, cm = dv->NDcmap; i < dv->nNDcmap; i++, cm++) {
362       PoolFPrint(p, fp, "\t( (");
363       fputnf(fp, cm->axis->dim, cm->axis->v, 0);
364       PoolFPrint(p, fp, ")\n");
365       for(j = VVCOUNT(cm->cents), ce = VVEC(cm->cents,cent); --j >= 0; ce++) {
366 	PoolFPrint(p, fp, "\t\t%g\t", ce->v);
367 	fputnf(fp, 4, (float *)&ce->c, 0);
368 	fputc('\n', fp);
369       }
370       PoolFPrint(p, fp, "\t)\n");
371     }
372     PoolFPrint(p, fp, " ) ) # end ND-color \"%s\"\n", name);
373   }
374 
375   PoolFPrint(p, fp, "(window \"%s\" ", name);
376   WnStreamOut(p, NULL, dv->win);
377   PoolFPrint(p, fp, ")\n");
378 
379   PoolFPrint(p, fp, "# end camera %s\n\n", name);
380 }
381 
nonidentity(Transform T)382 static int nonidentity(Transform T)
383 {
384   return memcmp(T, TM_IDENTITY, sizeof(TM_IDENTITY));
385 }
386 
387 static char *
normalization_string(int n)388 normalization_string(int n)
389 {
390   switch (n) {
391   case NONE: return "none";
392   case ALL: return "all";
393   case EACH: return "each";
394   default: return "???";
395   }
396 }
397 
398 static char *
onoff_string(int n)399 onoff_string(int n)
400 {
401   if (n) return "on";
402   else return "off";
403 }
404 
405 
worldio(HandleOps * ops,Pool * p,int to_coords,int id)406 int worldio(HandleOps *ops, Pool *p, int to_coords, int id)
407 {
408   int ok = -1;
409   Transform T;
410   TransformN *TN;
411   bool wrap = true;
412 
413   /* no data attached to a handle has been saved yet */
414   HandlesSetObjSaved(false);
415 
416   if (to_coords == SELF) {
417     wrap = false;
418   }
419   if (ops == &CommandOps) {
420     ok = save_world(p, id, true, wrap, to_coords);
421   } else if (ops == &GeomOps) {
422     if (ISTYPE(T_NONE, id)) {
423       /* bounding-box requested */
424       DGeom *dg;
425 
426       id = GEOMID(INDEXOF(id));
427       if ((dg = (DGeom *)drawer_get_object(id)) != NULL && dg->bboxvalid) {
428 	Geom *bbox;
429 	GeomGet(dg->Lbbox, CR_GEOM, &bbox);
430 	if (bbox != NULL) {
431 	  ok = GeomStreamOut(p, NULL, bbox);
432 	}
433       }
434     } else {
435       ok = save_world(p, id, false, wrap, to_coords);
436     }
437   } else if (ops == &CamOps && ISCAM(id)) {
438     DView *dv = (DView *)drawer_get_object(id);
439     if (dv) ok = CamStreamOut(p, dv->camhandle, dv->cam);
440   } else if (ops == &WindowOps) {
441     DView *dv = (DView *)drawer_get_object(ISCAM(id) ? id : FOCUSID);
442     WnWindow *win;
443     if (dv && dv->mgctx) {
444       mgctxselect(dv->mgctx);
445       if ((ok = mgctxget(MG_WINDOW, &win)) > 0)
446 	ok = WnStreamOut(p, NULL, win);
447     }
448   } else if (ops == &TransOps) {
449     drawer_get_transform(id, T, to_coords);
450     ok = TransStreamOut(p, NULL, T);
451   } else if (ops == &NTransOps) {
452     TN = drawer_get_ND_transform(id, to_coords);
453     ok = NTransStreamOut(p, NULL, TN);
454     TmNDelete(TN);
455   }
456   fflush(PoolOutputFile(p));
457 
458   return ok;
459 }
460 
461 static int
unique(char * new,char ** already,int nalready)462 unique(char *new, char **already, int nalready)
463 {
464   int i;
465   if (new == NULL)
466     return 0;
467   for(i = 0; i < nalready; i++)
468     if (strcmp(new, already[i]) == 0)
469       return 0;
470   return 1;
471 }
472 
473 static void
setapath(LList * list,vvec * vvpath,void (* setfunc)(char **))474 setapath(LList *list, vvec *vvpath, void (*setfunc)(char **))
475 {
476 #define MAXDIRS 100
477   int i, k;
478   char *td, *dirs[MAXDIRS+1];
479   for(i = 0; i < MAXDIRS && list; list = list->cdr) {
480     if (list->car == NULL)
481       continue;
482     td = LSTRINGVAL(list->car);
483     if (strcmp(td, "+") == 0) {
484       for(k = 0; k < VVCOUNT(*vvpath) && i < MAXDIRS; k++) {
485 	td = VVEC(*vvpath, char *)[k];
486 	if (unique(td, dirs, i))
487 	  dirs[i++] = strdup(td);
488       }
489     } else {
490       if (unique(td, dirs, i))
491 	dirs[i++] = td;
492     }
493   }
494   dirs[i] = NULL;
495   (*setfunc)(dirs);
496 #undef MAXDIRS
497 }
498 
499 static void
set_vvpath(vvec * vv,char ** ents)500 set_vvpath(vvec *vv, char **ents)
501 {
502   int i;
503   for(i = VVCOUNT(*vv); --i >= 0; )
504     free(VVEC(*vv, char *)[i]);
505 
506   VVCOUNT(*vv) = 0;
507   while(ents && *ents)
508     *VVAPPEND(*vv, char *) = strdup(*ents++);
509   vvtrim(vv);
510 }
511 
512 LDEFINE(set_load_path, LVOID,
513 	"(set-load-path      (PATH1 ... PATHN))\n\
514 	Sets search path for command, geometry, etc. files.  The PATHi\n\
515 	are strings giving the pathnames of directories to be searched.\n\
516 	The special directory name \"+\" is replaced by the existing path,\n\
517 	so e.g. (set-load-path (mydir +)) prepends mydir to the path.")
518 {
519   LList *list;
520   LDECLARE(("set-load-path", LBEGIN,
521 	    LLITERAL, LLIST, &list,
522 	    LEND));
523   setapath(list, &vv_load_path, set_load_path);
524   return Lt;
525 }
526 
set_load_path(char ** dirs)527 void set_load_path(char **dirs)
528 {
529   int filepanel;
530 
531   set_vvpath(&vv_load_path, dirs);
532 
533   filedirs(dirs);			/* Inform OOGL/refcomm library */
534 
535   filepanel = ui_name2panel("Files");	/* Inform UI */
536   if (ui_panelshown(filepanel))
537     ui_showpanel(filepanel, 1);
538 }
539 
540 LDEFINE(load_path, LLIST,
541 	"(load-path)\n\
542 	Returns the current search path for command, geometry, etc. files.\n\
543 	Note: to actually see the value returned by this function\n\
544 	you should wrap it in a call to echo: (echo (load-path)).\n\
545 	See also set-load-path.")
546 {
547   /*
548     For backward compatibility this command takes an optional argument
549     which specifies a new load-path.  This will disappear in a future version.
550   */
551   LList *list = NULL;
552   int i;
553   LDECLARE(("load-path", LBEGIN,
554 	    LOPTIONAL,
555 	    LLITERAL, LLIST, &list,
556 	    LEND));
557 
558   if (list != NULL) {
559     gv_set_load_path(list);
560     return Lt;
561   }
562 
563   for (i=0; i<load_path_count; ++i) {
564     char *dir = strdup(load_path[i]);
565     list = LListAppend(list, LNew(LSTRING, &dir));
566   }
567   return LNew(LLIST, &list);
568 }
569 
570 LDEFINE(set_emodule_path, LVOID,
571 	"(set-emodule-path      (PATH1 ... PATHN))\n\
572 	Sets the search path for external modules.  The PATHi should\n\
573 	be pathnames of directories containing, for each module, the\n\
574 	module's executable file and a .geomview-<modulename> file\n\
575 	which contains an (emodule-define ...) command for that\n\
576 	module.  This command implicitly calls (rehash-emodule-path)\n\
577 	to rebuild the application brower from the new path setting.\n\
578 	The special directory name \"+\" is replaced by the existing path,\n\
579 	so e.g. (set-emodule-path (mydir +)) prepends mydir to the path.")
580 {
581 #define MAXDIRS 100
582   LList *list;
583   LDECLARE(("set-emodule-path", LBEGIN,
584 	    LLITERAL, LLIST, &list,
585 	    LEND));
586   setapath(list, &vv_emodule_path, set_emodule_path);
587   return Lt;
588 }
589 
set_emodule_path(char ** dirs)590 void set_emodule_path(char **dirs)
591 {
592   set_vvpath(&vv_emodule_path, dirs);
593   gv_rehash_emodule_path();
594 }
595 
596 LDEFINE(rehash_emodule_path, LLIST,
597 	"(rehash-emodule-path)\n\
598 	Rebuilds the application (external module) browser by reading\n\
599 	all .geomview-* files in all directories on the emodule-path.\n\
600 	Primarily intended for internal use; any applications defined\n\
601 	by (emodule-define ...) commands outside of the .geomview-*\n\
602 	files on the emodule-path will be lost.  Does not sort the\n\
603 	entries in the brower; see (emodule-sort) for that.")
604 {
605   int i;
606   char pat[512], **files, **fp;
607   LDECLARE(("rehash-emodule-path", LBEGIN,
608 	    LEND));
609 
610   gv_emodule_clear();
611   for (i=0; i<emodule_path_count; ++i) {
612     sprintf(pat, "%s/.geomview-*", emodule_path[i]);
613     fp = files = ooglglob(pat);
614     while (fp && *fp) {
615       if (access(*fp, R_OK) == 0) {
616 	char *emod_dir_saved = uistate.emod_dir;
617 	uistate.emod_dir = emodule_path[i];
618 	loadfile(*fp, &CommandOps, 0);
619 	uistate.emod_dir = emod_dir_saved;
620       }
621       ++fp;
622     }
623     ooglblkfree(files);
624     OOGLFree(files);
625   }
626   return Lt;
627 }
628 
629 LDEFINE(emodule_path, LLIST,
630 	"(emodule-path)\n\
631 	Returns the current search path for external modules.\n\
632 	Note: to actually see the value returned by this function\n\
633 	you should wrap it in a call to echo: (echo (emodule-path)).\n\
634         See also set-emodule-path.")
635 {
636   LList *list = NULL;
637   int i;
638   LDECLARE(("emodule-path", LBEGIN,
639 	    LEND));
640 
641   for (i=0; i<emodule_path_count; ++i) {
642     char *dir = strdup(emodule_path[i]);
643     list = LListAppend(list, LNew(LSTRING, &dir));
644   }
645   return LNew(LLIST, &list);
646 }
647 
648 LDEFINE(emodule_defined, LSTRING,
649 	"(emodule-defined \"modulename\")\n\
650 	If the given external-module name is known, returns the name of\n\
651 	the program invoked when it's run as a quoted string; otherwise\n\
652 	returns nil.  ``(echo (emodule-defined \"name\"))'' prints the string.")
653 {
654   char *name;
655   emodule *em;
656   LDECLARE(("emodule-defined", LBEGIN,
657 	    LSTRING, &name,
658 	    LEND));
659   return ui_emodule_index(name, &em) < 0 ? Lnil : LTOOBJ(LSTRING)(&em->text);
660 }
661 
662 LDEFINE(all, LLIST,
663 	"(all geometry)  returns a list of names of all geometry objects.\n\
664 (all camera)	returns a list of names of all cameras.\n\
665 (all emodule defined)  returns a list of all defined external modules.\n\
666 (all emodule running)  returns a list of all running external modules.\n\
667 Use e.g. ``(echo (all geometry))'' to print such a list.")
668 {
669   char *type, *subtype = "";
670   int i, wantrunning;
671   DGeom *dg;
672   DView *dv;
673   emodule *em;
674   LList *l = NULL;
675 
676   LDECLARE(("all", LBEGIN,
677 	    LSTRING, &type,
678 	    LOPTIONAL,
679 	    LSTRING, &subtype,
680 	    LEND));
681 
682   if (strncmp(type, "geom", 4) == 0) {
683     LOOPGEOMS(i, dg)
684       l = LListAppend(l, LTOOBJ(LSTRING)(&dg->name[1]));
685   } else if (strncmp(type,  "cam", 3) == 0) {
686     LOOPVIEWS(i, dv)
687       l = LListAppend(l, LTOOBJ(LSTRING)(&dv->name[1]));
688   } else if (strncmp(type, "emodule", 7) == 0) {
689     wantrunning = (strncmp(subtype, "run", 3) == 0);
690     em = VVEC(uistate.emod, emodule);
691     for(i = VVCOUNT(uistate.emod); --i >= 0; em++)
692       if ((em->pid != 0) == wantrunning)
693 	l = LListAppend(l, LTOOBJ(LSTRING)(&em->name));
694   } else {
695     OOGLError(0, "all: expected \"geometry\" or \"camera\" or \"emodule defined\" or \"emodule running\", got: %s %s\n",
696 	      type, subtype);
697     return Lnil;
698   }
699   return LNew(LLIST, &l);
700 }
701 
702 LDEFINE(camera_prop, LVOID,
703 	"(camera-prop { geometry object }   [projective])\n\
704 	Specify the object to be shown when drawing other cameras.\n\
705 	By default, this object is drawn with its origin at the camera,\n\
706 	and with the camera looking toward the object's -Z axis.\n\
707 	With the \"projective\" keyword, the camera's viewing projection is\n\
708 	also applied to the object; this places the object's Z=-1 and Z=+1 at\n\
709 	near and far clipping planes, with the viewing area -1<={X,Y}<=+1.\n\
710 	Example:  (camera-prop { < cube } projective)")
711 {
712   Keyword proj = NO_KEYWORD;
713   GeomStruct *gs = NULL;
714 
715   LDECLARE(("camera-prop", LBEGIN,
716 	    LGEOM, &gs,
717 	    LOPTIONAL,
718 	    LKEYWORD, &proj,
719 	    LEND));
720 
721   if (proj != NO_KEYWORD && proj != PROJECTIVE_KEYWORD) {
722     OOGLError(1, "Expected either \"%s\" or nothing, but got \"%s\".\n",
723 	      keywordname(PROJECTIVE_KEYWORD), keywordname(proj));
724     return Lnil;
725   }
726 
727   if (gs && gs->geom) {
728     REFINCR(gs->geom);
729     REFINCR(gs->h);
730     GeomDelete(drawerstate.camgeom);
731     drawerstate.camgeom = gs->geom;
732     drawerstate.camproj = (proj == PROJECTIVE_KEYWORD);
733     drawerstate.changed = true;
734     return Lt;
735   }
736 
737   return Lnil;
738 }
739 
740 LDEFINE(write, LVOID,
741 	"(write {command|geometry|camera|transform|ntransform|window|bbox} FILENAME [ID|(ID ...)] [self|world|universe|otherID])\n\
742 	write description of ID in given format to FILENAME.  Last\n\
743 	parameter chooses coordinate system for geometry & transform:\n\
744 	self: just the object, no transformation or appearance (geometry only)\n\
745 	world: the object as positioned within the World.\n\
746 	universe: object's position in universal coordinates;\n\
747 	includes Worldtransform\n\
748 	other ID: the object transformed to otherID's coordinate system.\n\
749 \n\
750 	A filename of \"-\" is a special case: data are written to the\n\
751 	stream from which the 'write' command was read.  For external\n\
752 	modules, the data are sent to the module's standard input.\n\
753 	For commands not read from an external program, \"-\" means\n\
754 	geomview's standard output.  (See also the \"command\"\n\
755 	command.)\n\
756 \n\
757 	The ID can either be a single id or a parenthesized list of\n\
758 	ids, like \"g0\" or \"(g2 g1 dodec.off)\".")
759 {
760   Pool *op, *p;
761   HandleOps *ops;
762   char *opsname;
763   LObject *idobj;
764   char *fname;
765   Lake *hiawatha;
766   int coords = UNIVERSE, val = 1, id;
767   bool temppool = false;
768   bool do_bbox = false;
769 
770   LDECLARE(("write", LBEGIN,
771 	    LLAKE, &hiawatha,
772 	    LSTRING, &opsname,
773 	    LSTRING, &fname,
774 	    LHOLD, LLOBJECT, &idobj,
775 	    LOPTIONAL,
776 	    LID, &coords,
777 	    LEND));
778 
779   p = POOL(hiawatha);
780 
781   if (strcmp(opsname, "bbox") == 0) {
782     do_bbox = true;
783     ops = &GeomOps;
784   }
785   if (!do_bbox && (ops = str2ops(opsname)) == NULL) {
786     OOGLError(0, "write: expected one of command|geometry|camera|transform|ntransform|window, got \"%s\"", opsname);
787     return Lnil;
788   }
789 
790   if (fname[0] == '-') {
791     if (PoolOutputFile(p)) {
792       op = p;
793     } else {
794       op = PoolStreamTemp(fname, NULL,  stdout, 1, &CommandOps);
795       temppool = true;
796     }
797   } else {
798     op = PoolStreamTemp(fname, NULL, NULL, 1, &CommandOps);
799     temppool = true;
800   }
801 
802   if (op == NULL || PoolOutputFile(op) == NULL) {
803     fprintf(stderr, "write: cannot open \"%s\": %s\n", fname, sperror());
804     return Lnil;
805   }
806 
807   if (idobj->type == LSTRING || idobj->type == LSYMBOL) {
808     if (!LFROMOBJ(LID)(idobj, &id)) {
809       fprintf(stderr, "write: expects ID or list of IDs in arg position 4\n");
810       return Lnil;
811     }
812     if (do_bbox) {
813       id = ID(T_NONE, INDEXOF(id));
814     }
815     val = (worldio(ops, op, coords, id) == 1);
816   } else if (idobj->type == LLIST) {
817     LList *list;
818     for( list = LLISTVAL(idobj); list && list->car; list = list->cdr ) {
819       if (!LFROMOBJ(LID)(list->car, &id)) {
820 	fprintf(stderr, "write: expects ID or list of IDs in arg position 2\n");
821 	return Lnil;
822       }
823       if (do_bbox) {
824 	id = ID(T_NONE, INDEXOF(id));
825       }
826       val &= (worldio(ops, op, coords, id) == 1);
827     }
828   }
829   if (temppool) {
830     PoolClose(op);
831     PoolDelete(op);
832   }
833   if (!val) {
834     fprintf(stderr, "write failed\n");
835     return Lnil;
836   } else
837     return Lt;
838 }
839 
840 
841 /***************************************************/
842 /* Snapshot code ***********************************/
843 /***************************************************/
844 
845 extern snap_entry snapshot_table[];
846 
847 int
general_snapshot(char * fname,int id,DView * view,WnWindow * win,WnPosition * wp,int (* function)(void))848 general_snapshot(char *fname, int id, DView *view,
849 		 WnWindow *win, WnPosition *wp,
850 		 int (*function)(void))
851 {
852   Camera *cam = NULL;
853   const Appearance *ap;
854   mgcontext *ctx;
855   FILE *f = NULL;
856   WnPosition vp;
857   int mgspace;
858   int failed;
859 
860   f = (fname[0] == '|') ? popen(fname+1, "wb") : fopen(fname, "wb");
861   if (f == NULL)
862     return -1;
863 
864   mgctxget(MG_CAMERA, &cam);
865   mgctxget(MG_SPACE, &mgspace);
866 #if 0
867   /* Copy so that changed flags are set */
868   ap = ApCopy(mggetappearance(NULL), NULL);
869 #else
870   ap = mggetappearance();
871 #endif
872   mgctxget(MG_WINDOW, &win);
873   win = WnCopy(win);
874   vp.xmin = vp.ymin = 0;
875   vp.xmax = wp->xmax - wp->xmin + 1;
876   vp.ymax = wp->ymax - wp->ymin + 1;
877   WnSet(win, WN_CURPOS, wp, WN_VIEWPORT, &vp, WN_END);
878 
879   function();
880   ctx = mgctxcreate(MG_CAMERA, cam,
881 		    MG_APPEAR, ap,
882 		    MG_WINDOW, win,
883 		    MG_BACKGROUND, &view->backcolor,
884 		    MG_BUFFILE, f,
885 		    MG_SPACE, mgspace,
886 		    MG_END);
887   mgreshapeviewport();	/* Make camera aspect match window */
888   {
889     /* Try to be as realistic as possible when dumping a snapshot.
890      * Create a drawing context, install (hopefully) all the
891      * attributes that draw_view() doesn't reset on its own,
892      * plug the ctx into the camera, and force a redraw.
893      * Then undo the subterfuge.
894      */
895     bool oldredraw = view->redraw;
896     bool oldchanged = view->changed;
897     mgcontext *oldctx = view->mgctx;
898     view->mgctx = ctx;
899     view->redraw = true;
900     gv_draw(view->id);
901     view->redraw = oldredraw, view->changed = oldchanged;
902     view->mgctx = oldctx;
903   }
904   fflush(f);
905 
906   mgctxdelete(ctx);
907 #if 0
908   ApDelete(ap);
909 #endif
910   mgctxselect(view->mgctx);	/* Revert to previous device */
911 
912   failed = (fname[0] != '|') ? fclose(f) : pclose(f);
913   return failed;
914 }
915 
916 
917 LDEFINE(snapshot, LINT,
918 	"(snapshot       CAM-ID     FILENAME [FORMAT [XSIZE [YSIZE]]])\n"
919 	"Save a snapshot of CAM-ID in the FILENAME (a string).")
920 {
921   DView *dv;
922   WnWindow *wn = NULL;
923   WnPosition wp;
924   int id, xsize = 0, ysize = 0;
925   char *fname, *format;
926   int i, failed = -1;
927 
928   if (snapshot_table[0].name)
929     format = snapshot_table[0].name;
930   else
931     format = "ppm";
932   LDECLARE(("snapshot", LBEGIN,
933 	    LID, &id,
934 	    LSTRING, &fname,
935 	    LOPTIONAL, LSTRING, &format,
936 	    LINT, &xsize,
937 	    LINT, &ysize,
938 	    LEND));
939 
940   if (!ISCAM(id) ||
941       (dv = (DView *)drawer_get_object(id)) == NULL
942       || dv->mgctx == NULL)
943     {
944       OOGLError(0, "snapshot: id %d: no such camera", id);
945       return LCopy(L1);
946     }
947   mgctxselect(dv->mgctx);
948   mgctxget(MG_WINDOW, &wn);
949   wp.xmin = wp.ymin = 0;  wp.xmax = wp.ymax = 399;  /* Avoid catastrophe */
950   WnGet(wn, WN_CURPOS, &wp);
951 
952   if (xsize > 0 || ysize > 0) {
953     int dx = wp.xmax - wp.xmin + 1;
954     int dy = wp.ymax - wp.ymin + 1;
955     if (xsize <= 0)
956       xsize = (dx * ysize + dy/2) / dy;
957     else if (ysize <= 0)
958       ysize = (dy * xsize + dx/2) / dx;
959     wp.xmax = wp.xmin + xsize - 1;
960     wp.ymax = wp.ymin + ysize - 1;
961   }
962 
963   for (i=0; snapshot_table[i].function!=NULL; i++)
964     if (!strcmp(format, snapshot_table[i].name))
965       {
966 	failed = snapshot_table[i].function(fname, id, dv, wn, &wp);
967 	break;
968       }
969   if (!strcmp(format, "ppm")) {
970     failed = general_snapshot(fname, id, dv, wn, &wp, mgdevice_BUF);
971   } else if (!strcmp(format, "ps")) {
972     failed = general_snapshot(fname, id, dv, wn, &wp, mgdevice_PS);
973   }
974   if (failed == -1)
975     OOGLError(0, "snapshot: unknown file format %s", format);
976   return failed ? Lnil : Lt;
977 }
978 
979 /*
980  * Local Variables: ***
981  * mode: c ***
982  * c-basic-offset: 2 ***
983  * End: ***
984  */
985