Index: src/libged/ged_private.h
===================================================================
--- src/libged/ged_private.h	(revision 59226)
+++ src/libged/ged_private.h	(working copy)
@@ -471,6 +471,8 @@
 
 extern int _ged_results_clear(struct ged *gedp);
 
+/* defined in track.c */
+extern int ged_track_g(struct bu_vls *log_str, struct rt_wdb *wdbp, const char *argv[]);
 
 __END_DECLS
 
Index: src/libged/track.c
===================================================================
--- src/libged/track.c	(revision 59226)
+++ src/libged/track.c	(working copy)
@@ -74,11 +74,11 @@
 static int track_mk_comb();
 
 
-/*	==== I T O A ()
+/*	==== I T O A  ()
  * convert integer to ascii wd format
  */
 static void
-itoa(struct ged *gedp,
+itoa(struct bu_vls *log_str,
      int n,
      char s[],
      int w) {
@@ -93,7 +93,7 @@
      */
     for (j = i; j < w; j++) s[j] = ' ';
     if (i > w)
-	bu_vls_printf(gedp->ged_result_str, "itoa: field length too small\n");
+	bu_vls_printf(log_str, "itoa: field length too small\n");
     s[w] = '\0';
     /* reverse the array
      */
@@ -106,22 +106,21 @@
 
 
 static void
-crname(struct ged *gedp,
+crname(struct bu_vls *log_str,
        char name[],
        int pos,
        int maxlen)
 {
     char temp[4];
 
-    itoa(gedp, pos, temp, 1);
+    itoa(log_str, pos, temp, 1);
     bu_strlcat(name, temp, maxlen);
-
-    return;
 }
 
 
 static void
-crregion(struct ged *gedp,
+crregion(struct bu_vls *log_str,
+	 struct rt_wdb *wdbp,
 	 char region[],
 	 char op[],
 	 int members[],
@@ -132,18 +131,21 @@
     int i;
     struct bu_list head;
 
+    if (wdbp->dbip == DBI_NULL)
+	return;
+
     BU_LIST_INIT(&head);
 
     for (i = 0; i < number; i++) {
 	solidname[grpname_len + extraTypeChars] = '\0';
-	crname(gedp, solidname, members[i], maxlen);
-	if (db_lookup(gedp->ged_wdbp->dbip, solidname, LOOKUP_QUIET) == RT_DIR_NULL) {
-	    bu_vls_printf(gedp->ged_result_str, "region: %s will skip member: %s\n", region, solidname);
+	crname(log_str, solidname, members[i], maxlen);
+	if (db_lookup(wdbp->dbip, solidname, LOOKUP_QUIET) == RT_DIR_NULL) {
+	    bu_vls_printf(log_str, "region: %s will skip member: %s\n", region, solidname);
 	    continue;
 	}
 	track_mk_addmember(solidname, &head, NULL, op[i]);
     }
-    (void)track_mk_comb(gedp->ged_wdbp, region, &head,
+    (void)track_mk_comb(wdbp, region, &head,
 			1, NULL, NULL, NULL,
 			500+Trackpos+i, 0, mat_default, los_default,
 			0, 1, 1);
@@ -152,11 +154,13 @@
 
 /*
  *
- * Adds track given "wheel" info
+ * Adds track given "wheel" info. This only needs a logging VLS and a
+ * database, so it can be used as a replacement for wdb_track_cmd when
+ * no struct ged is available.
  *
  */
 int
-ged_track(struct ged *gedp, int argc, const char *argv[])
+_ged_track(struct bu_vls *log_str, struct rt_wdb *wdbp, const char *argv[])
 {
     fastf_t fw[3], lw[3], iw[3], dw[3], tr[3];
     char *solname = NULL;
@@ -170,26 +174,7 @@
     int edit_result = GED_OK;
     struct bu_list head;
     int len;
-    static const char *usage = "basename rX1 rX2 rZ rR dX dZ dR iX iZ iR minX minY th";
 
-    GED_CHECK_DATABASE_OPEN(gedp, GED_ERROR);
-    GED_CHECK_READ_ONLY(gedp, GED_ERROR);
-    GED_CHECK_ARGC_GT_0(gedp, argc, GED_ERROR);
-
-    /* initialize result */
-    bu_vls_trunc(gedp->ged_result_str, 0);
-
-    /* must be wanting help */
-    if (argc == 1) {
-	bu_vls_printf(gedp->ged_result_str, "Usage: %s %s", argv[0], usage);
-	return GED_HELP;
-    }
-
-    if (argc != 15) {
-	bu_vls_printf(gedp->ged_result_str, "Usage: %s %s", argv[0], usage);
-	return GED_ERROR;
-    }
-
     BU_LIST_INIT(&head);
 
     oper[0] = oper[2] = WMOP_INTERSECT;
@@ -206,105 +191,105 @@
 
     /* first road wheel X */
     ++arg;
-    fw[0] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    fw[0] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     /* last road wheel X */
     ++arg;
-    lw[0] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    lw[0] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     if (fw[0] <= lw[0]) {
-	bu_vls_printf(gedp->ged_result_str, "First wheel after last wheel - STOP\n");
+	bu_vls_printf(log_str, "First wheel after last wheel - STOP\n");
 	edit_result = GED_ERROR;
 	goto end;
     }
 
     /* road wheel Z */
     ++arg;
-    fw[1] = lw[1] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    fw[1] = lw[1] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     /* roadwheel radius */
     ++arg;
-    fw[2] = lw[2] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    fw[2] = lw[2] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     if (fw[2] <= 0) {
-	bu_vls_printf(gedp->ged_result_str, "Radius <= 0 - STOP\n");
+	bu_vls_printf(log_str, "Radius <= 0 - STOP\n");
 	edit_result = GED_ERROR;
 	goto end;
     }
 
     /* drive sprocket X */
     ++arg;
-    dw[0] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    dw[0] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     if (dw[0] >= lw[0]) {
-	bu_vls_printf(gedp->ged_result_str, "DRIVE wheel not in the rear - STOP \n");
+	bu_vls_printf(log_str, "DRIVE wheel not in the rear - STOP \n");
 	edit_result = GED_ERROR;
 	goto end;
     }
 
     /* drive sprocket Z */
     ++arg;
-    dw[1] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    dw[1] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     /* drive sprocket radius */
     ++arg;
-    dw[2] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    dw[2] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     if (dw[2] <= 0) {
-	bu_vls_printf(gedp->ged_result_str, "Radius <= 0 - STOP\n");
+	bu_vls_printf(log_str, "Radius <= 0 - STOP\n");
 	edit_result = GED_ERROR;
 	goto end;
     }
 
     /* idler wheel X */
     ++arg;
-    iw[0] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    iw[0] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     if (iw[0] <= fw[0]) {
-	bu_vls_printf(gedp->ged_result_str, "IDLER wheel not in the front - STOP \n");
+	bu_vls_printf(log_str, "IDLER wheel not in the front - STOP \n");
 	edit_result = GED_ERROR;
 	goto end;
     }
 
     /* idler wheel Z */
     ++arg;
-    iw[1] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    iw[1] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     /* idler wheel radius */
     ++arg;
-    iw[2] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    iw[2] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     if (iw[2] <= 0) {
-	bu_vls_printf(gedp->ged_result_str, "Radius <= 0 - STOP\n");
+	bu_vls_printf(log_str, "Radius <= 0 - STOP\n");
 	edit_result = GED_ERROR;
 	goto end;
     }
 
     /* track MIN Y */
     ++arg;
-    tr[2] = tr[0] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    tr[2] = tr[0] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     /* track MAX Y */
     ++arg;
-    tr[1] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    tr[1] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     if (EQUAL(tr[0], tr[1])) {
-	bu_vls_printf(gedp->ged_result_str, "MIN == MAX ... STOP\n");
+	bu_vls_printf(log_str, "MIN == MAX ... STOP\n");
 	edit_result = GED_ERROR;
 	goto end;
     }
     if (tr[0] > tr[1]) {
-	bu_vls_printf(gedp->ged_result_str, "MIN > MAX .... will switch\n");
+	bu_vls_printf(log_str, "MIN > MAX .... will switch\n");
 	tr[1] = tr[0];
 	tr[0] = tr[2];
     }
 
     /* track thickness */
     ++arg;
-    tr[2] = atof(argv[arg]) * gedp->ged_wdbp->dbip->dbi_local2base;
+    tr[2] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
 
     if (tr[2] <= 0) {
-	bu_vls_printf(gedp->ged_result_str, "Track thickness <= 0 - STOP\n");
+	bu_vls_printf(log_str, "Track thickness <= 0 - STOP\n");
 	edit_result = GED_ERROR;
 	goto end;
     }
@@ -343,12 +328,12 @@
  */
 
     for (i = 0; i < 10; i++) {
-	crname(gedp, solname, i, len);
-	crname(gedp, regname, i, len);
-	if ((db_lookup(gedp->ged_wdbp->dbip, solname, LOOKUP_QUIET) != RT_DIR_NULL) ||
-	    (db_lookup(gedp->ged_wdbp->dbip, regname, LOOKUP_QUIET) != RT_DIR_NULL)) {
+	crname(log_str, solname, i, len);
+	crname(log_str, regname, i, len);
+	if ((db_lookup(wdbp->dbip, solname, LOOKUP_QUIET) != RT_DIR_NULL) ||
+	    (db_lookup(wdbp->dbip, regname, LOOKUP_QUIET) != RT_DIR_NULL)) {
 	    /* name already exists */
-	    bu_vls_printf(gedp->ged_result_str, "Track: naming error -- STOP\n");
+	    bu_vls_printf(log_str, "Track: naming error -- STOP\n");
 	    edit_result = GED_ERROR;
 	    goto end;
 	}
@@ -363,13 +348,13 @@
 
     /* add the solids */
     /* solid 0 */
-    slope(gedp, fw, iw, tr);
+    slope(log_str, fw, iw, tr);
     VMOVE(temp2, &sol.s_values[0]);
-    crname(gedp, solname, 0, len);
+    crname(log_str, solname, 0, len);
     bu_strlcpy(sol.s_name, solname, len);
 
     sol.s_type = ID_ARB8;
-    if (wrobj(gedp, solname, RT_DIR_SOLID))
+    if (wrobj(log_str, wdbp, solname, RT_DIR_SOLID))
 	return GED_ERROR;
 
     solname[grpname_len + extraTypeChars] = '\0';
@@ -380,9 +365,9 @@
 	sol.s_values[i] = 0.0;
     sol.s_type = ID_TGC;
     trcurve(iw, tr);
-    crname(gedp, solname, 1, len);
+    crname(log_str, solname, 1, len);
     bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(gedp, solname, RT_DIR_SOLID))
+    if (wrobj(log_str, wdbp, solname, RT_DIR_SOLID))
 	return GED_ERROR;
     solname[grpname_len + extraTypeChars] = '\0';
     /* idler dummy rcc */
@@ -391,9 +376,9 @@
     VMOVE(&sol.s_values[12], &sol.s_values[6]);
     VMOVE(&sol.s_values[15], &sol.s_values[9]);
     /* solid 2 */
-    crname(gedp, solname, 2, len);
+    crname(log_str, solname, 2, len);
     bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(gedp, solname, RT_DIR_SOLID))
+    if (wrobj(log_str, wdbp, solname, RT_DIR_SOLID))
 	return GED_ERROR;
     solname[grpname_len + extraTypeChars] = '\0';
 
@@ -401,11 +386,11 @@
     /* find idler track dummy arb8 */
     for (i = 0; i < 24; i++)
 	sol.s_values[i] = 0.0;
-    crname(gedp, solname, 3, len);
+    crname(log_str, solname, 3, len);
     bu_strlcpy(sol.s_name, solname, len);
     sol.s_type = ID_ARB8;
     crdummy(iw, tr, 1);
-    if (wrobj(gedp, solname, RT_DIR_SOLID))
+    if (wrobj(log_str, wdbp, solname, RT_DIR_SOLID))
 	return GED_ERROR;
     solname[grpname_len + extraTypeChars] = '\0';
 
@@ -413,11 +398,11 @@
     /* track slope to drive */
     for (i = 0; i < 24; i++)
 	sol.s_values[i] = 0.0;
-    slope(gedp, lw, dw, tr);
+    slope(log_str, lw, dw, tr);
     VMOVE(temp1, &sol.s_values[0]);
-    crname(gedp, solname, 4, len);
+    crname(log_str, solname, 4, len);
     bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(gedp, solname, RT_DIR_SOLID))
+    if (wrobj(log_str, wdbp, solname, RT_DIR_SOLID))
 	return GED_ERROR;
     solname[grpname_len + extraTypeChars] = '\0';
 
@@ -427,9 +412,9 @@
 	sol.s_values[i] = 0.0;
     sol.s_type = ID_TGC;
     trcurve(dw, tr);
-    crname(gedp, solname, 5, len);
+    crname(log_str, solname, 5, len);
     bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(gedp, solname, RT_DIR_SOLID))
+    if (wrobj(log_str, wdbp, solname, RT_DIR_SOLID))
 	return GED_ERROR;
     solname[grpname_len + extraTypeChars] = '\0';
 
@@ -439,9 +424,9 @@
     sol.s_values[11] = dw[2];
     VMOVE(&sol.s_values[12], &sol.s_values[6]);
     VMOVE(&sol.s_values[15], &sol.s_values[9]);
-    crname(gedp, solname, 6, len);
+    crname(log_str, solname, 6, len);
     bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(gedp, solname, RT_DIR_SOLID))
+    if (wrobj(log_str, wdbp, solname, RT_DIR_SOLID))
 	return GED_ERROR;
     solname[grpname_len + extraTypeChars] = '\0';
 
@@ -449,11 +434,11 @@
     /* drive dummy arb8 */
     for (i = 0; i < 24; i++)
 	sol.s_values[i] = 0.0;
-    crname(gedp, solname, 7, len);
+    crname(log_str, solname, 7, len);
     bu_strlcpy(sol.s_name, solname, len);
     sol.s_type = ID_ARB8;
     crdummy(dw, tr, 2);
-    if (wrobj(gedp, solname, RT_DIR_SOLID))
+    if (wrobj(log_str, wdbp, solname, RT_DIR_SOLID))
 	return GED_ERROR;
     solname[grpname_len + extraTypeChars] = '\0';
 
@@ -461,9 +446,9 @@
     /* track bottom */
     temp1[1] = temp2[1] = tr[0];
     bottom(temp1, temp2, tr);
-    crname(gedp, solname, 8, len);
+    crname(log_str, solname, 8, len);
     bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(gedp, solname, RT_DIR_SOLID))
+    if (wrobj(log_str, wdbp, solname, RT_DIR_SOLID))
 	return GED_ERROR;
     solname[grpname_len + extraTypeChars] = '\0';
 
@@ -475,9 +460,9 @@
     temp2[0] = iw[0];
     temp2[2] = iw[1] + iw[2];
     top(temp1, temp2, tr);
-    crname(gedp, solname, 9, len);
+    crname(log_str, solname, 9, len);
     bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(gedp, solname, RT_DIR_SOLID))
+    if (wrobj(log_str, wdbp, solname, RT_DIR_SOLID))
 	return GED_ERROR;
     solname[grpname_len + extraTypeChars] = '\0';
 
@@ -492,53 +477,53 @@
     /* region 1 */
     memb[0] = 0;
     memb[1] = 3;
-    crname(gedp, regname, 0, len);
-    crregion(gedp, regname, oper, memb, 2, solname, len);
+    crname(log_str, regname, 0, len);
+    crregion(log_str, wdbp, regname, oper, memb, 2, solname, len);
     solname[grpname_len + extraTypeChars] = '\0';
     regname[grpname_len + extraTypeChars] = '\0';
 
     /* region 1 */
-    crname(gedp, regname, 1, len);
+    crname(log_str, regname, 1, len);
     memb[0] = 1;
     memb[1] = 2;
     memb[2] = 3;
-    crregion(gedp, regname, oper, memb, 3, solname, len);
+    crregion(log_str, wdbp, regname, oper, memb, 3, solname, len);
     solname[grpname_len + extraTypeChars] = '\0';
     regname[grpname_len + extraTypeChars] = '\0';
 
     /* region 4 */
-    crname(gedp, regname, 4, len);
+    crname(log_str, regname, 4, len);
     memb[0] = 4;
     memb[1] = 7;
-    crregion(gedp, regname, oper, memb, 2, solname, len);
+    crregion(log_str, wdbp, regname, oper, memb, 2, solname, len);
     solname[grpname_len + extraTypeChars] = '\0';
     regname[grpname_len + extraTypeChars] = '\0';
 
     /* region 5 */
-    crname(gedp, regname, 5, len);
+    crname(log_str, regname, 5, len);
     memb[0] = 5;
     memb[1] = 6;
     memb[2] = 7;
-    crregion(gedp, regname, oper, memb, 3, solname, len);
+    crregion(log_str, wdbp, regname, oper, memb, 3, solname, len);
     solname[grpname_len + extraTypeChars] = '\0';
     regname[grpname_len + extraTypeChars] = '\0';
 
     /* region 8 */
-    crname(gedp, regname, 8, len);
+    crname(log_str, regname, 8, len);
     memb[0] = 8;
     memb[1] = 0;
     memb[2] = 4;
     oper[2] = WMOP_SUBTRACT;
-    crregion(gedp, regname, oper, memb, 3, solname, len);
+    crregion(log_str, wdbp, regname, oper, memb, 3, solname, len);
     solname[grpname_len + extraTypeChars] = '\0';
     regname[grpname_len + extraTypeChars] = '\0';
 
     /* region 9 */
-    crname(gedp, regname, 9, len);
+    crname(log_str, regname, 9, len);
     memb[0] = 9;
     memb[1] = 3;
     memb[2] = 7;
-    crregion(gedp, regname, oper, memb, 3, solname, len);
+    crregion(log_str, wdbp, regname, oper, memb, 3, solname, len);
     solname[grpname_len + extraTypeChars] = '\0';
     regname[grpname_len + extraTypeChars] = '\0';
 
@@ -547,20 +532,20 @@
 	if (i == 2 || i == 3 || i == 6 || i == 7)
 	    continue;
 	regname[grpname_len + extraTypeChars] = '\0';
-	crname(gedp, regname, i, len);
-	if (db_lookup(gedp->ged_wdbp->dbip, regname, LOOKUP_QUIET) == RT_DIR_NULL) {
-	    bu_vls_printf(gedp->ged_result_str, "group: %s will skip member: %s\n", grpname, regname);
+	crname(log_str, regname, i, len);
+	if (db_lookup(wdbp->dbip, regname, LOOKUP_QUIET) == RT_DIR_NULL) {
+	    bu_vls_printf(log_str, "group: %s will skip member: %s\n", grpname, regname);
 	    continue;
 	}
 	track_mk_addmember(regname, &head, NULL, WMOP_UNION);
     }
 
     /* Add them all at once */
-    if (track_mk_comb(gedp->ged_wdbp, grpname, &head,
+    if (track_mk_comb(wdbp, grpname, &head,
 		      0, NULL, NULL, NULL,
 		      0, 0, 0, 0,
 		      0, 1, 1) < 0) {
-	bu_vls_printf(gedp->ged_result_str, "An error has occurred while adding '%s' to the database.\n", grpname);
+	bu_vls_printf(log_str, "An error has occurred while adding '%s' to the database.\n", grpname);
     }
 
     Trackpos += 10;
@@ -585,25 +570,57 @@
 }
 
 
+/*
+ *
+ * Adds track given "wheel" info.
+ *
+ */
+int
+ged_track(struct ged *gedp, int argc, const char *argv[])
+{
+    GED_CHECK_DATABASE_OPEN(gedp, GED_ERROR);
+    GED_CHECK_READ_ONLY(gedp, GED_ERROR);
+    GED_CHECK_ARGC_GT_0(gedp, argc, GED_ERROR);
+    static const char *usage = "basename rX1 rX2 rZ rR dX dZ dR iX iZ iR minX minY th";
+
+    /* initialize result */
+    bu_vls_trunc(gedp->ged_result_str, 0);
+
+    /* must be wanting help */
+    if (argc == 1) {
+	bu_vls_printf(gedp->ged_result_str, "Usage: %s %s", argv[0], usage);
+	return GED_HELP;
+    }
+
+    if (argc != 15) {
+	bu_vls_printf(gedp->ged_result_str, "Usage: %s %s", argv[0], usage);
+	return GED_ERROR;
+    }
+
+    return _ged_track(gedp->ged_result_str, gedp->ged_wdbp, argv);
+}
+
 static int
-wrobj(struct ged *gedp,
-      char name[],
-      int flags) {
+wrobj(struct bu_vls *log_str,
+      struct rt_wdb *wdbp,
+      const char name[],
+      int flags)
+{
     struct directory *tdp;
     struct rt_db_internal intern;
     int i;
 
-    if (gedp->ged_wdbp->dbip == DBI_NULL)
-	return 0;
+    if (wdbp->dbip == DBI_NULL)
+	return GED_OK;
 
-    if (db_lookup(gedp->ged_wdbp->dbip, name, LOOKUP_QUIET) != RT_DIR_NULL) {
-	bu_vls_printf(gedp->ged_result_str, "track naming error: %s already exists\n", name);
-	return GED_ERROR;
+    if (db_lookup(wdbp->dbip, name, LOOKUP_QUIET) != RT_DIR_NULL) {
+	bu_vls_printf(log_str, "track naming error: %s already exists\n", name);
+	return -1;
     }
 
     if (flags != RT_DIR_SOLID) {
-	bu_vls_printf(gedp->ged_result_str, "wrobj can only write solids, aborting\n");
-	return GED_ERROR;
+	bu_vls_printf(log_str, "wrobj can only write solids, aborting\n");
+	return -1;
     }
 
     RT_DB_INTERNAL_INIT(&intern);
@@ -646,30 +663,27 @@
 	}
 	    break;
 	default:
-	    bu_vls_printf(gedp->ged_result_str, "Unrecognized solid type in 'wrobj', aborting\n");
+	    bu_vls_printf(log_str, "Unrecognized solid type in 'wrobj', aborting\n");
 	    return GED_ERROR;
     }
 
-    tdp = db_diradd(gedp->ged_wdbp->dbip, name, RT_DIR_PHONY_ADDR, 0, flags, (genptr_t)&intern.idb_type);
-    if (tdp == RT_DIR_NULL) {
+    if ((tdp = db_diradd(wdbp->dbip, name, RT_DIR_PHONY_ADDR, 0, flags, (genptr_t)&intern.idb_type)) == RT_DIR_NULL) {
 	rt_db_free_internal(&intern);
-	bu_vls_printf(gedp->ged_result_str, "Cannot add '%s' to directory, aborting\n", name);
+	bu_vls_printf(log_str, "Cannot add '%s' to directory, aborting\n", name);
 	return GED_ERROR;
     }
 
-    if (rt_db_put_internal(tdp, gedp->ged_wdbp->dbip, &intern, &rt_uniresource) < 0) {
+    if (rt_db_put_internal(tdp, wdbp->dbip, &intern, &rt_uniresource) < 0) {
 	rt_db_free_internal(&intern);
-	bu_vls_printf(gedp->ged_result_str, "wrobj(gedp, %s):  write error\n", name);
-	bu_vls_printf(gedp->ged_result_str, "The in-memory table of contents may not match the status of the on-disk\ndatabase.  The on-disk database should still be intact.  For safety, \nyou should exit now, and resolve the I/O problem, before continuing.\n");
-
+	bu_vls_printf(log_str, "write error\n");
+	bu_vls_printf(log_str, "The in-memory table of contents may not match the status of the on-disk\ndatabase.  The on-disk database should still be intact.  For safety, \nyou should exit now, and resolve the I/O problem, before continuing.\n");
 	return GED_ERROR;
     }
-    return 0;
+    return GED_OK;
 }
 
-
 static void
-tancir(struct ged *gedp,
+tancir(struct bu_vls *log_str,
        fastf_t cir1[],
        fastf_t cir2[]) {
     static fastf_t mag;
@@ -684,7 +698,7 @@
     if (mag > 1.0e-20 || mag < -1.0e-20) {
 	f = 1.0/mag;
     } else {
-	bu_vls_printf(gedp->ged_result_str, "tancir():  0-length vector!\n");
+	bu_vls_printf(log_str, "tancir():  0-length vector!\n");
 	return;
     }
     VSCALE(work, work, f);
@@ -707,7 +721,7 @@
 
 
 static void
-slope(struct ged *gedp,
+slope(struct bu_vls *log_str,
       fastf_t wh1[],
       fastf_t wh2[],
       fastf_t t[]) {
@@ -726,7 +740,7 @@
 	    wh2[i] = temp;
 	}
     }
-    tancir(gedp, wh1, wh2);
+    tancir(log_str, wh1, wh2);
     if (switchs) {
 	for (i = 0; i < 3; i++) {
 	    temp = wh1[i];
@@ -769,9 +783,9 @@
     work[1] = t[1] - t[0];
     VMOVE(&sol.s_values[12], work);
     for (i = 3; i <= 9; i += 3) {
-		j = i + 12;
-		VADD2(&sol.s_values[j], &sol.s_values[i], work);
-	}
+	j = i + 12;
+	VADD2(&sol.s_values[j], &sol.s_values[i], work);
+    }
 
     return;
 }
Index: src/libged/wdb_track.c
===================================================================
--- src/libged/wdb_track.c	(revision 59226)
+++ src/libged/wdb_track.c	(working copy)
@@ -47,1169 +47,41 @@
 #include "raytrace.h"
 #include "wdb.h"
 
-/*XXX The following WDB_ defines need to go inside of a header file */
-#define WDB_TCL_CHECK_READ_ONLY		 \
-    if (wdbp->dbip->dbi_read_only) {				\
-	bu_log("Sorry, this database is READ-ONLY\n");		\
-	return TCL_ERROR;					\
-    }
-#define WDB_TCL_ERROR_RECOVERY_SUGGESTION	\
-    bu_log("\
-The in-memory table of contents may not match the status of the on-disk\n\
-database.  The on-disk database should still be intact.  For safety, \n\
-you should exit now, and resolve the I/O problem, before continuing.\n")
+#include "./ged_private.h"
 
+extern int ged_track_g(struct bu_vls *log_str, struct rt_wdb *wdbp, const char *argv[]);
 
-static int Trackpos = 0;
-static int mat_default = 1;
-static int los_default = 50;
-static int item_default = 500;
-static fastf_t plano[4], plant[4];
-static int grpname_len;
-static int extraTypeChars = 3;
-static int extraChars = 4;
-
-static struct track_solid {
-    int s_type;
-    char *s_name;
-    fastf_t s_values[24];
-} sol;
-
-
-static int
-wrobj(struct rt_wdb *wdbp,
-      const char name[],
-      int flags)
-{
-    struct directory *tdp;
-    struct rt_db_internal intern;
-    int i;
-
-    if (wdbp->dbip == DBI_NULL)
-	return 0;
-
-    if (db_lookup(wdbp->dbip, name, LOOKUP_QUIET) != RT_DIR_NULL) {
-	bu_log("track naming error: %s already exists\n", name);
-	return -1;
-    }
-
-    if (flags != RT_DIR_SOLID) {
-	bu_log("wrobj can only write solids, aborting\n");
-	return -1;
-    }
-
-    RT_DB_INTERNAL_INIT(&intern);
-    switch (sol.s_type) {
-	case ID_ARB8: {
-	    struct rt_arb_internal *arb;
-
-	    BU_ALLOC(arb, struct rt_arb_internal);
-
-	    arb->magic = RT_ARB_INTERNAL_MAGIC;
-
-	    VMOVE(arb->pt[0], &sol.s_values[0]);
-	    for (i = 1; i < 8; i++)
-		VADD2(arb->pt[i], &sol.s_values[i*3], arb->pt[0]);
-
-	    intern.idb_ptr = (genptr_t)arb;
-	    intern.idb_major_type = DB5_MAJORTYPE_BRLCAD;
-	    intern.idb_type = ID_ARB8;
-	    intern.idb_meth = &OBJ[ID_ARB8];
-	}
-	    break;
-	case ID_TGC: {
-	    struct rt_tgc_internal *tgc;
-
-	    BU_ALLOC(tgc, struct rt_tgc_internal);
-
-	    tgc->magic = RT_TGC_INTERNAL_MAGIC;
-
-	    VMOVE(tgc->v, &sol.s_values[0]);
-	    VMOVE(tgc->h, &sol.s_values[3]);
-	    VMOVE(tgc->a, &sol.s_values[6]);
-	    VMOVE(tgc->b, &sol.s_values[9]);
-	    VMOVE(tgc->c, &sol.s_values[12]);
-	    VMOVE(tgc->d, &sol.s_values[15]);
-
-	    intern.idb_ptr = (genptr_t)tgc;
-	    intern.idb_major_type = DB5_MAJORTYPE_BRLCAD;
-	    intern.idb_type = ID_TGC;
-	    intern.idb_meth = &OBJ[ID_TGC];
-	}
-	    break;
-	default:
-	    bu_log("Unrecognized solid type in 'wrobj', aborting\n");
-	    return -1;
-    }
-
-    if ((tdp = db_diradd(wdbp->dbip, name, RT_DIR_PHONY_ADDR, 0, flags, (genptr_t)&intern.idb_type)) == RT_DIR_NULL) {
-	rt_db_free_internal(&intern);
-	bu_log("Cannot add '%s' to directory, aborting\n", name);
-	return -1;
-    }
-
-    if (rt_db_put_internal(tdp, wdbp->dbip, &intern, &rt_uniresource) < 0) {
-	rt_db_free_internal(&intern);
-	bu_log("write error\n");
-	WDB_TCL_ERROR_RECOVERY_SUGGESTION;
-	return -1;
-    }
-    return 0;
-}
-
-
-static void
-tancir(fastf_t cir1[],
-       fastf_t cir2[]) {
-    static fastf_t mag;
-    vect_t work;
-    fastf_t f;
-    static fastf_t temp, tempp, ang, angc;
-
-    work[0] = cir2[0] - cir1[0];
-    work[2] = cir2[1] - cir1[1];
-    work[1] = 0.0;
-    mag = MAGNITUDE(work);
-    if (mag > 1.0e-20 || mag < -1.0e-20) {
-	f = 1.0/mag;
-    } else {
-	bu_log("tancir():  0-length vector!\n");
-	return;
-    }
-    VSCALE(work, work, f);
-    temp = acos(work[0]);
-    if (work[2] < 0.0)
-	temp = 6.28318512717958646 - temp;
-    tempp = acos((cir1[2] - cir2[2]) * f);
-    ang = temp + tempp;
-    angc = temp - tempp;
-    if ((cir1[1] + cir1[2] * sin(ang)) >
-	(cir1[1] + cir1[2] * sin(angc)))
-	ang = angc;
-    plano[0] = cir1[0] + cir1[2] * cos(ang);
-    plano[1] = cir1[1] + cir1[2] * sin(ang);
-    plant[0] = cir2[0] + cir2[2] * cos(ang);
-    plant[1] = cir2[1] + cir2[2] * sin(ang);
-
-    return;
-}
-
-
-static void
-slope(fastf_t wh1[],
-      fastf_t wh2[],
-      fastf_t t[]) {
-    int i, j, switchs;
-    fastf_t temp;
-    fastf_t mag;
-    fastf_t z, r, b;
-    vect_t del, work;
-
-    switchs = 0;
-    if (wh1[2] < wh2[2]) {
-	switchs++;
-	for (i = 0; i < 3; i++) {
-	    temp = wh1[i];
-	    wh1[i] = wh2[i];
-	    wh2[i] = temp;
-	}
-    }
-    tancir(wh1, wh2);
-    if (switchs) {
-	for (i = 0; i < 3; i++) {
-	    temp = wh1[i];
-	    wh1[i] = wh2[i];
-	    wh2[i] = temp;
-	}
-    }
-    if (plano[1] <= plant[1]) {
-	for (i = 0; i < 2; i++) {
-	    temp = plano[i];
-	    plano[i] = plant[i];
-	    plant[i] = temp;
-	}
-    }
-    del[1] = 0.0;
-    del[0] = plano[0] - plant[0];
-    del[2] = plano[1] - plant[1];
-    mag = MAGNITUDE(del);
-    work[0] = -1.0 * t[2] * del[2] / mag;
-    if (del[0] < 0.0)
-	work[0] *= -1.0;
-    work[1] = 0.0;
-    work[2] = t[2] * fabs(del[0]) / mag;
-    b = (plano[1] - work[2]) - (del[2]/del[0]*(plano[0] - work[0]));
-    z = wh1[1];
-    r = wh1[2];
-    if (wh1[1] >= wh2[1]) {
-	z = wh2[1];
-	r = wh2[2];
-    }
-    sol.s_values[2] = z - r - t[2];
-    sol.s_values[1] = t[0];
-    sol.s_values[0] = (sol.s_values[2] - b) / (del[2] / del[0]);
-    sol.s_values[3] = plano[0] + (del[0]/mag) - work[0] - sol.s_values[0];
-    sol.s_values[4] = 0.0;
-    sol.s_values[5] = plano[1] + (del[2]/mag) - work[2] - sol.s_values[2];
-    VADD2(&sol.s_values[6], &sol.s_values[3], work);
-    VMOVE(&sol.s_values[9], work);
-    work[0] = work[2] = 0.0;
-    work[1] = t[1] - t[0];
-    VMOVE(&sol.s_values[12], work);
-    for (i = 3; i <= 9; i += 3) {
-	j = i + 12;
-	VADD2(&sol.s_values[j], &sol.s_values[i], work);
-    }
-
-    return;
-}
-
-
-static void
-crdummy(fastf_t w[3], fastf_t t[3], int flag)
-{
-    fastf_t temp;
-    vect_t vec;
-    int i, j;
-
-    vec[1] = 0.0;
-    if (plano[1] <= plant[1]) {
-	for (i = 0; i < 2; i++) {
-	    temp = plano[i];
-	    plano[i] = plant[i];
-	    plant[i] = temp;
-	}
-    }
-
-    vec[0] = w[2] + t[2] + 1.0;
-    vec[2] = ((plano[1] - w[1]) * vec[0]) / (plano[0] - w[0]);
-    if (flag > 1)
-	vec[0] *= -1.0;
-    if (vec[2] >= 0.0)
-	vec[2] *= -1.0;
-    sol.s_values[0] = w[0];
-    sol.s_values[1] = t[0] -1.0;
-    sol.s_values[2] = w[1];
-    VMOVE(&sol.s_values[3], vec);
-    vec[2] = w[2] + t[2] + 1.0;
-    VMOVE(&sol.s_values[6], vec);
-    vec[0] = 0.0;
-    VMOVE(&sol.s_values[9], vec);
-    vec[2] = 0.0;
-    vec[1] = t[1] - t[0] + 2.0;
-    VMOVE(&sol.s_values[12], vec);
-    for (i = 3; i <= 9; i += 3) {
-	j = i + 12;
-	VADD2(&sol.s_values[j], &sol.s_values[i], vec);
-    }
-
-    return;
-
-}
-
-
-static void
-trcurve(fastf_t wh[], fastf_t t[])
-{
-    sol.s_values[0] = wh[0];
-    sol.s_values[1] = t[0];
-    sol.s_values[2] = wh[1];
-    sol.s_values[4] = t[1] - t[0];
-    sol.s_values[6] = wh[2] + t[2];
-    sol.s_values[11] = wh[2] + t[2];
-    VMOVE(&sol.s_values[12], &sol.s_values[6]);
-    VMOVE(&sol.s_values[15], &sol.s_values[9]);
-}
-
-
-static void
-bottom(vect_t vec1, vect_t vec2, fastf_t t[])
-{
-    vect_t tvec;
-    int i, j;
-
-    VMOVE(&sol.s_values[0], vec1);
-    tvec[0] = vec2[0] - vec1[0];
-    tvec[1] = tvec[2] = 0.0;
-    VMOVE(&sol.s_values[3], tvec);
-    tvec[0] = tvec[1] = 0.0;
-    tvec[2] = t[2];
-    VADD2(&sol.s_values[6], &sol.s_values[3], tvec);
-    VMOVE(&sol.s_values[9], tvec);
-    tvec[0] = tvec[2] = 0.0;
-    tvec[1] = t[1] - t[0];
-    VMOVE(&sol.s_values[12], tvec);
-
-    for (i = 3; i <= 9; i += 3) {
-	j = i + 12;
-	VADD2(&sol.s_values[j], &sol.s_values[i], tvec);
-    }
-}
-
-
-static void
-top(vect_t vec1, vect_t vec2, fastf_t t[])
-{
-    fastf_t tooch, mag;
-    vect_t del, tvec;
-    int i, j;
-
-    tooch = t[2] * .25;
-    del[0] = vec2[0] - vec1[0];
-    del[1] = 0.0;
-    del[2] = vec2[2] - vec1[2];
-    mag = MAGNITUDE(del);
-    VSCALE(tvec, del, tooch/mag);
-    VSUB2(&sol.s_values[0], vec1, tvec);
-    VADD2(del, del, tvec);
-    VADD2(&sol.s_values[3], del, tvec);
-    tvec[0] = tvec[2] = 0.0;
-    tvec[1] = t[1] - t[0];
-    VCROSS(del, tvec, &sol.s_values[3]);
-    mag = MAGNITUDE(del);
-    if (del[2] < 0)
-	mag *= -1.0;
-    VSCALE(&sol.s_values[9], del, t[2]/mag);
-    VADD2(&sol.s_values[6], &sol.s_values[3], &sol.s_values[9]);
-    VMOVE(&sol.s_values[12], tvec);
-
-    for (i = 3; i <= 9; i += 3) {
-	j = i + 12;
-	VADD2(&sol.s_values[j], &sol.s_values[i], tvec);
-    }
-}
-
-
-/*	==== I T O A ()
- * convert integer to ascii wd format
- */
-static void
-itoa(int n,
-     char s[],
-     int w) {
-    int c, i, j, sign;
-
-    if ((sign = n) < 0) n = -n;
-    i = 0;
-    do s[i++] = n % 10 + '0';	while ((n /= 10) > 0);
-    if (sign < 0) s[i++] = '-';
-
-    /* blank fill array
-     */
-    for (j = i; j < w; j++) s[j] = ' ';
-    if (i > w)
-	bu_log("itoa: field length too small\n");
-    s[w] = '\0';
-    /* reverse the array
-     */
-    for (i = 0, j = w - 1; i < j; i++, j--) {
-	c    = s[i];
-	s[i] = s[j];
-	s[j] =    c;
-    }
-}
-
-
-static void
-crname(char name[],
-       int pos,
-       int maxlen)
-{
-    char temp[4];
-
-    itoa(pos, temp, 1);
-    bu_strlcat(name, temp, maxlen);
-
-    return;
-}
-
-
-/*
- * M K _ T R E E _ G I F T
- *
- * Add some nodes to a new or existing combination's tree,
- * with GIFT precedence and semantics.
- *
- * NON-PARALLEL due to rt_uniresource
- *
- * Returns -
- * -1 ERROR
- * 0 OK
- */
-static int
-track_mk_tree_gift(struct rt_comb_internal *comb, struct bu_list *member_hd)
-{
-    struct wmember *wp;
-    union tree *tp;
-    struct rt_tree_array *tree_list;
-    size_t node_count;
-    size_t actual_count;
-    int new_nodes;
-
-    if ((new_nodes = bu_list_len(member_hd)) <= 0)
-	return 0;	/* OK, nothing to do */
-
-    if (comb->tree && db_ck_v4gift_tree(comb->tree) < 0) {
-	db_non_union_push(comb->tree, &rt_uniresource);
-	if (db_ck_v4gift_tree(comb->tree) < 0) {
-	    bu_log("track_mk_tree_gift() Cannot flatten tree for editing\n");
-	    return -1;
-	}
-    }
-
-    /* make space for an extra leaf */
-    node_count = db_tree_nleaves(comb->tree);
-    tree_list = (struct rt_tree_array *)bu_calloc(node_count + (size_t)new_nodes,
-						  sizeof(struct rt_tree_array), "tree list");
-
-    /* flatten tree */
-    if (comb->tree) {
-	/* Release storage for non-leaf nodes, steal leaves */
-	actual_count = (struct rt_tree_array *)db_flatten_tree(tree_list, comb->tree, OP_UNION, 1, &rt_uniresource) - tree_list;
-	BU_ASSERT_SIZE_T(actual_count, ==, node_count);
-	comb->tree = TREE_NULL;
-    } else {
-	actual_count = 0;
-    }
-
-    /* Add new members to the array */
-    for (BU_LIST_FOR(wp, wmember, member_hd)) {
-	WDB_CK_WMEMBER(wp);
-
-	switch (wp->wm_op) {
-	    case WMOP_INTERSECT:
-		tree_list[node_count].tl_op = OP_INTERSECT;
-		break;
-	    case WMOP_SUBTRACT:
-		tree_list[node_count].tl_op = OP_SUBTRACT;
-		break;
-	    default:
-		bu_log("track_mk_tree_gift() unrecognized relation %c (assuming UNION)\n", wp->wm_op);
-		/* Fall through */
-	    case WMOP_UNION:
-		tree_list[node_count].tl_op = OP_UNION;
-		break;
-	}
-
-	/* make new leaf node, and insert at end of array */
-	BU_ALLOC(tp, union tree);
-	RT_TREE_INIT(tp);
-	tree_list[node_count++].tl_tree = tp;
-	tp->tr_l.tl_op = OP_DB_LEAF;
-	tp->tr_l.tl_name = bu_strdup(wp->wm_name);
-	if (!bn_mat_is_identity(wp->wm_mat)) {
-	    tp->tr_l.tl_mat = bn_mat_dup(wp->wm_mat);
-	} else {
-	    tp->tr_l.tl_mat = (matp_t)NULL;
-	}
-    }
-    BU_ASSERT_SIZE_T(node_count, ==, actual_count + (size_t)new_nodes);
-
-    /* rebuild the tree with GIFT semantics */
-    comb->tree = (union tree *)db_mkgift_tree(tree_list, node_count, &rt_uniresource);
-
-    bu_free((char *)tree_list, "track_mk_tree_gift: tree_list");
-
-    return 0;	/* OK */
-}
-
-
-/*
- * M K _ F R E E M E M B E R S
- */
-static void
-track_mk_freemembers(struct bu_list *headp)
-{
-    struct wmember *wp;
-
-    while (BU_LIST_WHILE(wp, wmember, headp)) {
-	WDB_CK_WMEMBER(wp);
-	BU_LIST_DEQUEUE(&wp->l);
-	bu_free((char *)wp->wm_name, "wm_name");
-	bu_free((char *)wp, "wmember");
-    }
-}
-
-
-/*
- * M K _ T R E E _ P U R E
- *
- * Given a list of wmember structures, build a tree that performs
- * the boolean operations in the given sequence.
- * No GIFT semantics or precedence is provided.
- * For that, use mk_tree_gift().
- */
-static void
-track_mk_tree_pure(struct rt_comb_internal *comb, struct bu_list *member_hd)
-{
-    struct wmember *wp;
-
-    for (BU_LIST_FOR(wp, wmember, member_hd)) {
-	union tree *leafp, *nodep;
-
-	WDB_CK_WMEMBER(wp);
-
-	BU_ALLOC(leafp, union tree);
-	RT_TREE_INIT(leafp);
-	leafp->tr_l.tl_op = OP_DB_LEAF;
-	leafp->tr_l.tl_name = bu_strdup(wp->wm_name);
-	if (!bn_mat_is_identity(wp->wm_mat)) {
-	    leafp->tr_l.tl_mat = bn_mat_dup(wp->wm_mat);
-	}
-
-	if (!comb->tree) {
-	    comb->tree = leafp;
-	    continue;
-	}
-	/* Build a left-heavy tree */
-	BU_ALLOC(nodep, union tree);
-	RT_TREE_INIT(nodep);
-	switch (wp->wm_op) {
-	    case WMOP_UNION:
-		nodep->tr_b.tb_op = OP_UNION;
-		break;
-	    case WMOP_INTERSECT:
-		nodep->tr_b.tb_op = OP_INTERSECT;
-		break;
-	    case WMOP_SUBTRACT:
-		nodep->tr_b.tb_op = OP_SUBTRACT;
-		break;
-	    default:
-		bu_bomb("track_mk_tree_pure() bad wm_op");
-	}
-	nodep->tr_b.tb_left = comb->tree;
-	nodep->tr_b.tb_right = leafp;
-	comb->tree = nodep;
-    }
-}
-
-
-/*
- * M K _ C O M B
- *
- * Make a combination, where the
- * members are described by a linked list of wmember structs.
- *
- * The linked list is freed when it has been output.
- *
- * Has many operating modes.
- *
- * Returns -
- * -1 ERROR
- * 0 OK
- */
-static int
-track_mk_comb(
-    struct rt_wdb *wdbp,
-    const char *combname,
-    struct bu_list *headp,		/* Made by mk_addmember() */
-    int region_kind,	/* 1 => region.  'P' and 'V' for FASTGEN */
-    const char *shadername,	/* shader name, or NULL */
-    const char *shaderargs,	/* shader args, or NULL */
-    const unsigned char *rgb,		/* NULL => no color */
-    int id,		/* region_id */
-    int air,		/* aircode */
-    int material,	/* GIFTmater */
-    int los,
-    int inherit,
-    int append_ok,	/* 0 = obj must not exit */
-    int gift_semantics)	/* 0 = pure, 1 = gift */
-{
-    struct rt_db_internal intern;
-    struct rt_comb_internal *comb;
-    int fresh_combination;
-
-    RT_CK_WDB(wdbp);
-
-    RT_DB_INTERNAL_INIT(&intern);
-
-    if (append_ok &&
-	wdb_import(wdbp, &intern, combname, (matp_t)NULL) >= 0) {
-	/* We retrieved an existing object, append to it */
-	comb = (struct rt_comb_internal *)intern.idb_ptr;
-	RT_CK_COMB(comb);
-
-	fresh_combination = 0;
-    } else {
-	/* Create a fresh new object for export */
-	BU_ALLOC(comb, struct rt_comb_internal);
-	RT_COMB_INTERNAL_INIT(comb);
-
-	intern.idb_major_type = DB5_MAJORTYPE_BRLCAD;
-	intern.idb_type = ID_COMBINATION;
-	intern.idb_ptr = (genptr_t)comb;
-	intern.idb_meth = &OBJ[ID_COMBINATION];
-
-	fresh_combination = 1;
-    }
-
-    if (gift_semantics)
-	track_mk_tree_gift(comb, headp);
-    else
-	track_mk_tree_pure(comb, headp);
-
-    /* Release the wmember list dynamic storage */
-    track_mk_freemembers(headp);
-
-    /* Don't change these things when appending to existing combination */
-    if (fresh_combination) {
-	if (region_kind) {
-	    comb->region_flag = 1;
-	    switch (region_kind) {
-		case 'P':
-		    comb->is_fastgen = REGION_FASTGEN_PLATE;
-		    break;
-		case 'V':
-		    comb->is_fastgen = REGION_FASTGEN_VOLUME;
-		    break;
-		case 'R':
-		case 1:
-		    /* Regular non-FASTGEN Region */
-		    break;
-		default:
-		    bu_log("mk_comb(%s) unknown region_kind=%d (%c), assuming normal non-FASTGEN\n",
-			   combname, region_kind, region_kind);
-	    }
-	}
-	if (shadername) bu_vls_strcat(&comb->shader, shadername);
-	if (shaderargs) {
-	    bu_vls_strcat(&comb->shader, " ");
-	    bu_vls_strcat(&comb->shader, shaderargs);
-	    /* Convert to Tcl form if necessary.  Use heuristics */
-	    if (strchr(shaderargs, '=') != NULL	&& strchr(shaderargs, '{') == NULL) {
-		struct bu_vls old = BU_VLS_INIT_ZERO;
-		bu_vls_vlscatzap(&old, &comb->shader);
-		if (bu_shader_to_list(bu_vls_addr(&old), &comb->shader))
-		    bu_log("Unable to convert shader string '%s %s'\n", shadername, shaderargs);
-		bu_vls_free(&old);
-	    }
-	}
-
-	if (rgb) {
-	    comb->rgb_valid = 1;
-	    comb->rgb[0] = rgb[0];
-	    comb->rgb[1] = rgb[1];
-	    comb->rgb[2] = rgb[2];
-	}
-
-	comb->region_id = id;
-	comb->aircode = air;
-	comb->GIFTmater = material;
-	comb->los = los;
-
-	comb->inherit = inherit;
-    }
-
-    /* The internal representation will be freed */
-    return wdb_put_internal(wdbp, combname, &intern, 1.0);
-}
-
-
-/*
- * M K _ A D D M E M B E R
- *
- * Obtain dynamic storage for a new wmember structure, fill in the
- * name, default the operation and matrix, and add to doubly linked
- * list.  In typical use, a one-line call is sufficient.  To change
- * the defaults, catch the pointer that is returned, and adjust the
- * structure to taste.
- *
- * The caller is responsible for initializing the header structures
- * forward and backward links.
- */
-static struct wmember *
-track_mk_addmember(
-    const char *name,
-    struct bu_list *headp,
-    mat_t mat,
-    int op)
-{
-    struct wmember *wp;
-
-    BU_ALLOC(wp, struct wmember);
-    wp->l.magic = WMEMBER_MAGIC;
-    wp->wm_name = bu_strdup(name);
-    switch (op) {
-	case WMOP_UNION:
-	case WMOP_INTERSECT:
-	case WMOP_SUBTRACT:
-	    wp->wm_op = op;
-	    break;
-	default:
-	    bu_log("mk_addmember() op=x%x is bad\n", op);
-	    return WMEMBER_NULL;
-    }
-
-    /* if the user gave a matrix, use it.  otherwise use identity matrix*/
-    if (mat) {
-	MAT_COPY(wp->wm_mat, mat);
-    } else {
-	MAT_IDN(wp->wm_mat);
-    }
-
-    /* Append to end of doubly linked list */
-    BU_LIST_INSERT(headp, &wp->l);
-    return wp;
-}
-
-
-static void
-crregion(struct rt_wdb *wdbp,
-	 char region[],
-	 char op[],
-	 int members[],
-	 int number,
-	 char solidname[],
-	 int maxlen)
-{
-    int i;
-    struct bu_list head;
-
-    if (wdbp->dbip == DBI_NULL)
-	return;
-
-    BU_LIST_INIT(&head);
-
-    for (i = 0; i < number; i++) {
-	solidname[grpname_len + extraTypeChars] = '\0';
-	crname(solidname, members[i], maxlen);
-	if (db_lookup(wdbp->dbip, solidname, LOOKUP_QUIET) == RT_DIR_NULL) {
-	    bu_log("region: %s will skip member: %s\n", region, solidname);
-	    continue;
-	}
-	track_mk_addmember(solidname, &head, NULL, op[i]);
-    }
-    (void)track_mk_comb(wdbp, region, &head,
-			1, NULL, NULL, NULL,
-			500+Trackpos+i, 0, mat_default, los_default,
-			0, 1, 1);
-}
-
-
-/*
- *
- * Adds track given "wheel" info
- *
- */
 int
 wdb_track_cmd(void *data,
 	      int argc,
 	      const char *argv[])
 {
+    struct bu_vls log_str = BU_VLS_INIT_ZERO;
     struct rt_wdb *wdbp = (struct rt_wdb *)data;
-    fastf_t fw[3], lw[3], iw[3], dw[3], tr[3];
-    char *solname = NULL;
-    char *regname = NULL;
-    char *grpname = NULL;
-    char oper[3];
-    int i, memb[4];
-    vect_t temp1, temp2;
-    int item, mat, los;
-    int arg;
-    int edit_result = TCL_OK;
-    struct bu_list head;
-    int len;
+    int retval;
 
-    BU_LIST_INIT(&head);
-
     if (argc != 15) {
 	bu_log("ERROR: expecting 15 arguments\n");
 	return TCL_ERROR;
     }
 
-    oper[0] = oper[2] = WMOP_INTERSECT;
-    oper[1] = WMOP_SUBTRACT;
+    retval = ged_track_g(&log_str, wdbp, argv);
 
-    /* base name */
-    arg = 1;
-    grpname = bu_strdup(argv[arg]);
-    grpname_len = (int)strlen(grpname);
-    len = grpname_len + 1 + extraChars;
-    solname = bu_malloc(len, "solid name");
-    regname = bu_malloc(len, "region name");
-    sol.s_name = bu_malloc(len, "sol.s_name");
-
-    /* first road wheel X */
-    ++arg;
-    fw[0] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    /* last road wheel X */
-    ++arg;
-    lw[0] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    if (fw[0] <= lw[0]) {
-	bu_log("First wheel after last wheel - STOP\n");
-	edit_result = TCL_ERROR;
-	goto end;
+    if (bu_vls_strlen(&log_str) > 0) {
+	bu_log("%s", bu_vls_addr(&log_str));
     }
 
-    /* road wheel Z */
-    ++arg;
-    fw[1] = lw[1] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    /* roadwheel radius */
-    ++arg;
-    fw[2] = lw[2] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    if (fw[2] <= 0) {
-	bu_log("Radius <= 0 - STOP\n");
-	edit_result = TCL_ERROR;
-	goto end;
+    switch(retval) {
+	case GED_OK:
+	    return TCL_OK;
+	case GED_ERROR:
+	    return TCL_ERROR;
     }
 
-    /* drive sprocket X */
-    ++arg;
-    dw[0] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    if (dw[0] >= lw[0]) {
-	bu_log("DRIVE wheel not in the rear - STOP \n");
-	edit_result = TCL_ERROR;
-	goto end;
-    }
-
-    /* drive sprocket Z */
-    ++arg;
-    dw[1] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    /* drive sprocket radius */
-    ++arg;
-    dw[2] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    if (dw[2] <= 0) {
-	bu_log("Radius <= 0 - STOP\n");
-	edit_result = TCL_ERROR;
-	goto end;
-    }
-
-    /* idler wheel X */
-    ++arg;
-    iw[0] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    if (iw[0] <= fw[0]) {
-	bu_log("IDLER wheel not in the front - STOP \n");
-	edit_result = TCL_ERROR;
-	goto end;
-    }
-
-    /* idler wheel Z */
-    ++arg;
-    iw[1] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    /* idler wheel radius */
-    ++arg;
-    iw[2] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    if (iw[2] <= 0) {
-	bu_log("Radius <= 0 - STOP\n");
-	edit_result = TCL_ERROR;
-	goto end;
-    }
-
-    /* track MIN Y */
-    ++arg;
-    tr[2] = tr[0] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    /* track MAX Y */
-    ++arg;
-    tr[1] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    if (EQUAL(tr[0], tr[1])) {
-	bu_log("MIN == MAX ... STOP\n");
-	edit_result = TCL_ERROR;
-	goto end;
-    }
-    if (tr[0] > tr[1]) {
-	bu_log("MIN > MAX .... will switch\n");
-	tr[1] = tr[0];
-	tr[0] = tr[2];
-    }
-
-    /* track thickness */
-    ++arg;
-    tr[2] = atof(argv[arg]) * wdbp->dbip->dbi_local2base;
-
-    if (tr[2] <= 0) {
-	bu_log("Track thickness <= 0 - STOP\n");
-	edit_result = TCL_ERROR;
-	goto end;
-    }
-
-    for (i = 0; i < grpname_len; ++i) {
-	solname[i] = grpname[i];
-	regname[i] = grpname[i];
-    }
-
-    solname[i] = regname[i] = '.';
-    ++i;
-    solname[i] = 's';
-    regname[i] = 'r';
-    ++i;
-    solname[i] = regname[i] = '.';
-    ++i;
-    solname[i] = regname[i] = '\0';
-/*
-  bu_log("\nX of first road wheel  %10.4f\n", fw[0]);
-  bu_log("X of last road wheel   %10.4f\n", lw[0]);
-  bu_log("Z of road wheels       %10.4f\n", fw[1]);
-  bu_log("radius of road wheels  %10.4f\n", fw[2]);
-  bu_log("\nX of drive wheel       %10.4f\n", dw[0]);
-  bu_log("Z of drive wheel       %10.4f\n", dw[1]);
-  bu_log("radius of drive wheel  %10.4f\n", dw[2]);
-  bu_log("\nX of idler wheel       %10.4f\n", iw[0]);
-  bu_log("Z of idler wheel       %10.4f\n", iw[1]);
-  bu_log("radius of idler wheel  %10.4f\n", iw[2]);
-  bu_log("\nY MIN of track         %10.4f\n", tr[0]);
-  bu_log("Y MAX of track         %10.4f\n", tr[1]);
-  bu_log("thickness of track     %10.4f\n", tr[2]);
-*/
-
-/* Check for names to use:
- * grpname.s.0->9 and grpname.r.0->9
- */
-
-    for (i = 0; i < 10; i++) {
-	crname(solname, i, len);
-	crname(regname, i, len);
-	if ((db_lookup(wdbp->dbip, solname, LOOKUP_QUIET) != RT_DIR_NULL) ||
-	    (db_lookup(wdbp->dbip, regname, LOOKUP_QUIET) != RT_DIR_NULL)) {
-	    /* name already exists */
-	    bu_log("Track: naming error -- STOP\n");
-	    edit_result = TCL_ERROR;
-	    goto end;
-	}
-
-	solname[grpname_len + extraTypeChars] = '\0';
-	regname[grpname_len + extraTypeChars] = '\0';
-    }
-
-    /* find the front track slope to the idler */
-    for (i = 0; i < 24; i++)
-	sol.s_values[i] = 0.0;
-
-    /* add the solids */
-    /* solid 0 */
-    slope(fw, iw, tr);
-    VMOVE(temp2, &sol.s_values[0]);
-    crname(solname, 0, len);
-    bu_strlcpy(sol.s_name, solname, len);
-
-    sol.s_type = ID_ARB8;
-    if (wrobj(wdbp, solname, RT_DIR_SOLID))
-	return TCL_ERROR;
-
-    solname[grpname_len + extraTypeChars] = '\0';
-
-    /* solid 1 */
-    /* find track around idler */
-    for (i = 0; i < 24; i++)
-	sol.s_values[i] = 0.0;
-    sol.s_type = ID_TGC;
-    trcurve(iw, tr);
-    crname(solname, 1, len);
-    bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(wdbp, solname, RT_DIR_SOLID))
-	return TCL_ERROR;
-    solname[grpname_len + extraTypeChars] = '\0';
-    /* idler dummy rcc */
-    sol.s_values[6] = iw[2];
-    sol.s_values[11] = iw[2];
-    VMOVE(&sol.s_values[12], &sol.s_values[6]);
-    VMOVE(&sol.s_values[15], &sol.s_values[9]);
-    /* solid 2 */
-    crname(solname, 2, len);
-    bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(wdbp, solname, RT_DIR_SOLID))
-	return TCL_ERROR;
-    solname[grpname_len + extraTypeChars] = '\0';
-
-    /* solid 3 */
-    /* find idler track dummy arb8 */
-    for (i = 0; i < 24; i++)
-	sol.s_values[i] = 0.0;
-    crname(solname, 3, len);
-    bu_strlcpy(sol.s_name, solname, len);
-    sol.s_type = ID_ARB8;
-    crdummy(iw, tr, 1);
-    if (wrobj(wdbp, solname, RT_DIR_SOLID))
-	return TCL_ERROR;
-    solname[grpname_len + extraTypeChars] = '\0';
-
-    /* solid 4 */
-    /* track slope to drive */
-    for (i = 0; i < 24; i++)
-	sol.s_values[i] = 0.0;
-    slope(lw, dw, tr);
-    VMOVE(temp1, &sol.s_values[0]);
-    crname(solname, 4, len);
-    bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(wdbp, solname, RT_DIR_SOLID))
-	return TCL_ERROR;
-    solname[grpname_len + extraTypeChars] = '\0';
-
-    /* solid 5 */
-    /* track around drive */
-    for (i = 0; i < 24; i++)
-	sol.s_values[i] = 0.0;
-    sol.s_type = ID_TGC;
-    trcurve(dw, tr);
-    crname(solname, 5, len);
-    bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(wdbp, solname, RT_DIR_SOLID))
-	return TCL_ERROR;
-    solname[grpname_len + extraTypeChars] = '\0';
-
-    /* solid 6 */
-    /* drive dummy rcc */
-    sol.s_values[6] = dw[2];
-    sol.s_values[11] = dw[2];
-    VMOVE(&sol.s_values[12], &sol.s_values[6]);
-    VMOVE(&sol.s_values[15], &sol.s_values[9]);
-    crname(solname, 6, len);
-    bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(wdbp, solname, RT_DIR_SOLID))
-	return TCL_ERROR;
-    solname[grpname_len + extraTypeChars] = '\0';
-
-    /* solid 7 */
-    /* drive dummy arb8 */
-    for (i = 0; i < 24; i++)
-	sol.s_values[i] = 0.0;
-    crname(solname, 7, len);
-    bu_strlcpy(sol.s_name, solname, len);
-    sol.s_type = ID_ARB8;
-    crdummy(dw, tr, 2);
-    if (wrobj(wdbp, solname, RT_DIR_SOLID))
-	return TCL_ERROR;
-    solname[grpname_len + extraTypeChars] = '\0';
-
-    /* solid 8 */
-    /* track bottom */
-    temp1[1] = temp2[1] = tr[0];
-    bottom(temp1, temp2, tr);
-    crname(solname, 8, len);
-    bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(wdbp, solname, RT_DIR_SOLID))
-	return TCL_ERROR;
-    solname[grpname_len + extraTypeChars] = '\0';
-
-    /* solid 9 */
-    /* track top */
-    temp1[0] = dw[0];
-    temp1[1] = temp2[1] = tr[0];
-    temp1[2] = dw[1] + dw[2];
-    temp2[0] = iw[0];
-    temp2[2] = iw[1] + iw[2];
-    top(temp1, temp2, tr);
-    crname(solname, 9, len);
-    bu_strlcpy(sol.s_name, solname, len);
-    if (wrobj(wdbp, solname, RT_DIR_SOLID))
-	return TCL_ERROR;
-    solname[grpname_len + extraTypeChars] = '\0';
-
-    /* add the regions */
-    /* region 0 */
-    item = item_default;
-    mat = mat_default;
-    los = los_default;
-    item_default = 500;
-    mat_default = 1;
-    los_default = 50;
-    /* region 1 */
-    memb[0] = 0;
-    memb[1] = 3;
-    crname(regname, 0, len);
-    crregion(wdbp, regname, oper, memb, 2, solname, len);
-    solname[grpname_len + extraTypeChars] = '\0';
-    regname[grpname_len + extraTypeChars] = '\0';
-
-    /* region 1 */
-    crname(regname, 1, len);
-    memb[0] = 1;
-    memb[1] = 2;
-    memb[2] = 3;
-    crregion(wdbp, regname, oper, memb, 3, solname, len);
-    solname[grpname_len + extraTypeChars] = '\0';
-    regname[grpname_len + extraTypeChars] = '\0';
-
-    /* region 4 */
-    crname(regname, 4, len);
-    memb[0] = 4;
-    memb[1] = 7;
-    crregion(wdbp, regname, oper, memb, 2, solname, len);
-    solname[grpname_len + extraTypeChars] = '\0';
-    regname[grpname_len + extraTypeChars] = '\0';
-
-    /* region 5 */
-    crname(regname, 5, len);
-    memb[0] = 5;
-    memb[1] = 6;
-    memb[2] = 7;
-    crregion(wdbp, regname, oper, memb, 3, solname, len);
-    solname[grpname_len + extraTypeChars] = '\0';
-    regname[grpname_len + extraTypeChars] = '\0';
-
-    /* region 8 */
-    crname(regname, 8, len);
-    memb[0] = 8;
-    memb[1] = 0;
-    memb[2] = 4;
-    oper[2] = WMOP_SUBTRACT;
-    crregion(wdbp, regname, oper, memb, 3, solname, len);
-    solname[grpname_len + extraTypeChars] = '\0';
-    regname[grpname_len + extraTypeChars] = '\0';
-
-    /* region 9 */
-    crname(regname, 9, len);
-    memb[0] = 9;
-    memb[1] = 3;
-    memb[2] = 7;
-    crregion(wdbp, regname, oper, memb, 3, solname, len);
-    solname[grpname_len + extraTypeChars] = '\0';
-    regname[grpname_len + extraTypeChars] = '\0';
-
-    /* group all the track regions */
-    for (i = 0; i < 10; i++) {
-	if (i == 2 || i == 3 || i == 6 || i == 7)
-	    continue;
-	regname[grpname_len + extraTypeChars] = '\0';
-	crname(regname, i, len);
-	if (db_lookup(wdbp->dbip, regname, LOOKUP_QUIET) == RT_DIR_NULL) {
-	    bu_log("group: %s will skip member: %s\n", grpname, regname);
-	    continue;
-	}
-	track_mk_addmember(regname, &head, NULL, WMOP_UNION);
-    }
-
-    /* Add them all at once */
-    if (track_mk_comb(wdbp, grpname, &head,
-		      0, NULL, NULL, NULL,
-		      0, 0, 0, 0,
-		      0, 1, 1) < 0) {
-	bu_log("An error has occurred while adding '%s' to the database.\n", grpname);
-    }
-
-    Trackpos += 10;
-    item_default = item;
-    mat_default = mat;
-    los_default = los;
-
-    bu_free((genptr_t)solname, "solid name");
-    bu_free((genptr_t)regname, "region name");
-    bu_free((genptr_t)grpname, "group name");
-    bu_free((genptr_t)sol.s_name, "sol.s_name");
-
-    return edit_result;
-
-end:
-    bu_free((genptr_t)solname, "solid name");
-    bu_free((genptr_t)regname, "region name");
-    bu_free((genptr_t)grpname, "group name");
-    bu_free((genptr_t)sol.s_name, "sol.s_name");
-
-    return edit_result;
+    /* This should never happen */
+    return TCL_ERROR;
 }
 
-
 /*
  * Local Variables:
  * mode: C
