Revision: 45719
          http://brlcad.svn.sourceforge.net/brlcad/?rev=45719&view=rev
Author:   r_weiss
Date:     2011-07-28 20:10:59 +0000 (Thu, 28 Jul 2011)

Log Message:
-----------
Updated the prototype version of function 'cut_unimonotone' within file 
'nmg_tri.c'. This function supports the prototype version of function 
'nmg_triangulate_fu'. Improved the error checking and the logic to cleanup 
problem loopuse. Also did some code cleanup. This change is disabled by 
default. This is a work in progress.

Modified Paths:
--------------
    brlcad/trunk/src/librt/primitives/nmg/nmg_tri.c

Modified: brlcad/trunk/src/librt/primitives/nmg/nmg_tri.c
===================================================================
--- brlcad/trunk/src/librt/primitives/nmg/nmg_tri.c     2011-07-28 20:00:52 UTC 
(rev 45718)
+++ brlcad/trunk/src/librt/primitives/nmg/nmg_tri.c     2011-07-28 20:10:59 UTC 
(rev 45719)
@@ -3330,22 +3330,25 @@
     int loop_count = 0;
     int status;
     int ccw_result;
-    int inside_triangle = 0;  /* boolean */
+    int tmp_vert_cnt = 0;
 
+    /* boolean variables */
+    int inside_triangle = 0;
+    int isect_vertex = 0;
+
     vect_t fu_normal;
     vect_t v0, v1, v2;
     fastf_t dot00, dot01, dot02, dot11, dot12;
     fastf_t invDenom, u, v;
     fastf_t dist;
 
-    struct pt2d *min, *max, *new, *first, *prev, *next, *current;
+    struct pt2d *min, *max, *new, *first, *prev, *next, *current, *tmp;
     struct pt2d *prev_orig, *next_orig, *pt, *t;
 
     struct model *m;
     struct faceuse *fu;
     struct loopuse *lu2, *orig_lu_p;
     struct edgeuse *eu;
-    struct vertexuse *vu;
     struct vertex_g *prev_vg_p = NULL; 
 
     static const int cut_color[3] = { 90, 255, 90};
@@ -3359,29 +3362,12 @@
     NMG_CK_LOOPUSE(lu);
 
     min = max = new = first = prev = next = current = (struct pt2d *)NULL;
-    prev_orig = next_orig = pt = t = (struct pt2d *)NULL;
+    prev_orig = next_orig = pt = t = tmp = (struct pt2d *)NULL;
 
     orig_lu_p = lu;
 
     if (rt_g.NMG_debug & DEBUG_TRI) {
-        verts = 1;
-        for (BU_LIST_FOR(eu, edgeuse, &lu->down_hd)) {
-            NMG_CK_EDGEUSE(eu);
-            if (!(new = find_pt2d(tbl2d, eu->vu_p))) {
-                bu_bomb("cut_unimonotone(): missing entry in pt2d table\n");
-            } else {
-                if (eu->up.lu_p != new->vu_p->up.eu_p->up.lu_p) {
-                    bu_bomb("cut_unimonotone(): (1) outdated/corrupt entry in 
pt2d table\n");
-                    if (eu->up.lu_p != lu) {
-                        bu_bomb("cut_unimonotone(): (2) outdated/corrupt entry 
in pt2d table\n");
-                    }
-                }
-            }
-            bu_log("cut_unimonotone(): lu_p = %x vertex %d 3D coord = %g %g 
%g\n", 
-                   eu->up.lu_p, verts, V3ARGS(eu->vu_p->v_p->vg_p->coord));
-
-            verts++;
-        }
+       validate_tbl2d("start of function cut_unimonotone()", tbl2d, 
lu->up.fu_p);
     }
 
     /* find min/max points & count vertex points */
@@ -3417,13 +3403,11 @@
     }
 
     excess_loop_count = verts * verts;
-
     current = PT2D_NEXT(tbl2d, first);
-
     while (verts > 3) {
        loop_count++;
        if (loop_count > excess_loop_count) {
-           if (1 || rt_g.NMG_debug & DEBUG_TRI) {
+           if (rt_g.NMG_debug & DEBUG_TRI) {
                eu = BU_LIST_FIRST(edgeuse, 
&(current->vu_p->up.eu_p->up.lu_p->down_hd));
                nmg_plot_lu_around_eu("cut_unimonotone_infinite_loopuse", eu, 
tol);
                m = nmg_find_model(current->vu_p->up.eu_p->up.lu_p->up.magic_p);
@@ -3431,6 +3415,7 @@
                nmg_pr_lu(current->vu_p->up.eu_p->up.lu_p, 
"cut_unimonotone_loopuse");
                nmg_plot_fu("cut_unimonotone_infinite_loopuse", 
current->vu_p->up.eu_p->up.lu_p->up.fu_p, tol);
            }
+           bu_log("cut_unimonotone(): infinite loop %x\n", 
current->vu_p->up.eu_p->up.lu_p);
            bu_bomb("cut_unimonotone(): infinite loop\n");
        }
 
@@ -3442,24 +3427,32 @@
         VSETALL(v2, 0.0);
         dot00 = dot01 = dot02 = dot11 = dot12 = 0.0;
         invDenom = u = v = 0.0;
-        inside_triangle = 0;
         prev_vg_p = (struct vertex_g *)NULL; 
 
-        /* test if any of the faceuse vertices are within the
-         * triangle to be created. if within the triangle, 
-         * do not create the triangle. the tbl2d contains
-         * vertexuse records and since an individual vertex
-         * is used multiple times, all vertex outside the
-         * triangle will be tested multiple times.
+        /* test if any of the loopuse vertices are within the triangle
+         * to be created. it is assumed none of the loopuse within the
+         * faceuse intersects any of the other loopuse with the exception
+         * of having a common edge or vertex
          */
+        inside_triangle = 0;
         for (BU_LIST_FOR(pt, pt2d, tbl2d)) {
-            /* skip testing vertices not in the current loopuse, i.e.
-             * the loopuse which is being ear-clipped. 
-             * this assumes none of the loopuse within the faceuse,
-             * intersects any of the other loopuse with the exception
-             * of having a common edge or vertex
-             */
+
+            if (inside_triangle) {
+                break;
+            }
+
+            if (pt->vu_p == (struct vertexuse *)NULL) {
+                /* skip tbl2d entries with a null vertexuse */
+                continue;
+            }
+
+            NMG_CK_EDGEUSE(pt->vu_p->up.eu_p);
+            NMG_CK_LOOPUSE(pt->vu_p->up.eu_p->up.lu_p);
+
             if (pt->vu_p->up.eu_p->up.lu_p == current->vu_p->up.eu_p->up.lu_p) 
{ 
+                /* true when the vertexuse to be tested is in the
+                 * same loopuse as the potential cut
+                 */
 
                 /* skips processing the same vertex */
                 if (prev_vg_p != pt->vu_p->v_p->vg_p) {
@@ -3475,10 +3468,10 @@
                     u = (dot11 * dot02 - dot01 * dot12) * invDenom;
                     v = (dot00 * dot12 - dot01 * dot02) * invDenom;
 
-                    /* evaluates to true if point inside triangle */
                     if ((u > SMALL_FASTF) && (v > SMALL_FASTF) && ((u + v) < 
(1.0 - SMALL_FASTF))) {
+                        /* true if point inside triangle */
                        if (rt_g.NMG_debug & DEBUG_TRI) {
-                           bu_log("point inside triangle, lu_p = %x, point = 
%g %g %g\n", 
+                           bu_log("cut_unimonotone(): point inside triangle, 
lu_p = %x, point = %g %g %g\n", 
                                     lu, V3ARGS(pt->vu_p->v_p->vg_p->coord));
                        }
                         inside_triangle = 1;
@@ -3497,45 +3490,55 @@
             } /* end of if-statement to skip testing vertices not in the 
current loopuse */
         }
 
-       /* test if the line segment to be cut intersects with any
-        * vertices in the loopuse. 
-        */
+        /* test if the potential cut would intersect a vertex which
+         * would not belong to the resulting triangle to be created
+         * by the cut
+         */
+        isect_vertex = 0;
        for (BU_LIST_FOR(eu, edgeuse, &next->vu_p->up.eu_p->up.lu_p->down_hd)) 
{ 
-           /* skips testing line segment end points */
+
+            if (isect_vertex) {
+                break;
+            }
+
            if ((prev->vu_p->v_p != eu->vu_p->v_p) && 
                 (current->vu_p->v_p != eu->vu_p->v_p) && 
                (next->vu_p->v_p != eu->vu_p->v_p)) {
+                /* true when the vertex tested would not belong to
+                 * the resulting triangle
+                 */
                status = bn_isect_pt_lseg(&dist, prev->vu_p->v_p->vg_p->coord, 
                                           next->vu_p->v_p->vg_p->coord,
                                          eu->vu_p->v_p->vg_p->coord, tol);
-               if (status == 3) { /* true when line segment is intersected, 
not on end points */
-                   inside_triangle = 1;
+               if (status == 3) { 
+                    /* true when the potential cut intersects a vertex */
+                    isect_vertex = 1;
                    if (rt_g.NMG_debug & DEBUG_TRI) {
-                       bu_log("cut prev %g %g %g -> next %g %g %g point = %g 
%g %g\n",
+                       bu_log("cut_unimonotone(): cut prev %g %g %g -> next %g 
%g %g point = %g %g %g\n",
                                V3ARGS(prev->vu_p->v_p->vg_p->coord), 
                                V3ARGS(next->vu_p->v_p->vg_p->coord), 
                                V3ARGS(eu->vu_p->v_p->vg_p->coord)); 
-                       bu_log("cut and vertex intersect\n");
+                       bu_log("cut_unimonotone(): cut and vertex intersect\n");
                    }
-                   break;
                }
            }
-            if (inside_triangle) {
-                break;
-            }
         }
 
-       if (is_convex(prev, current, next, tol) && !inside_triangle) {
-            /* continue if this angle is convex and there are no vertices 
within
-             * the triangle to be created by these two edges
+       if (is_convex(prev, current, next, tol) && !inside_triangle && 
!isect_vertex) {
+            /* true if the angle is convex and there are no vertices
+             * from this loopuse within the triangle to be created
+             * and the potential cut does not intersect any vertices,
+             * from this loopuse, which are not part of the triangle
+             * to be created. convex here is between 0 and 180 degrees
+             * including 0 and 180.
              */
            if (rt_g.NMG_debug & DEBUG_TRI) {
-               bu_log("Before cut loop:\n");
+               bu_log("cut_unimonotone(): before cut loop:\n");
                nmg_pr_fu_briefly(lu->up.fu_p, "");
            }
 
            if (rt_g.NMG_debug & DEBUG_TRI) {
-               bu_log("cut_unimonotone(): ---before cut orig_lu_p = %x prev 
lu_p = %x 3D %g %g %g curr lu_p = %x 3D %g %g %g next lu_p = %x 3D %g %g %g\n", 
+               bu_log("cut_unimonotone(): before cut orig_lu_p = %x prev lu_p 
= %x 3D %g %g %g curr lu_p = %x 3D %g %g %g next lu_p = %x 3D %g %g %g\n", 
                        orig_lu_p, prev->vu_p->up.eu_p->up.lu_p,
                        V3ARGS(prev->vu_p->v_p->vg_p->coord),
                        current->vu_p->up.eu_p->up.lu_p,
@@ -3544,6 +3547,16 @@
                        V3ARGS(next->vu_p->v_p->vg_p->coord));
            }
 
+
+            if (next->vu_p == prev->vu_p) {
+                bu_bomb("cut_unimonotone(): trying to cut to/from same 
vertexuse\n");
+            }
+
+           if (rt_g.NMG_debug & DEBUG_TRI) {
+                validate_tbl2d("before cut_unimonotone -- cut_mapped_loop", 
+                                tbl2d, 
current->vu_p->up.eu_p->up.lu_p->up.fu_p);
+            }
+
             prev_orig = prev;
             next_orig = next;
 
@@ -3553,48 +3566,148 @@
             prev = prev_orig;
             next = next_orig;
 
-            /* sanity check */
+            if (!current) {
+                bu_bomb("cut_unimonotone(): function cut_mapped_loop returned 
null\n");
+            }
+            if (current == next) {
+                bu_bomb("cut_unimonotone(): current == next\n");
+            }
+
+           if (rt_g.NMG_debug & DEBUG_TRI) {
+                validate_tbl2d("after cut_unimonotone -- cut_mapped_loop", 
+                                tbl2d, 
current->vu_p->up.eu_p->up.lu_p->up.fu_p);
+            }
+
             if (BU_LIST_FIRST_MAGIC(&next->vu_p->up.eu_p->up.lu_p->down_hd) != 
NMG_EDGEUSE_MAGIC) {
                 bu_bomb("cut_unimonotone(): next loopuse contains no 
edgeuse\n");
             }
 
+            /* check direction (cw/ccw) of loopuse after cut */
             NMG_GET_FU_NORMAL(fu_normal, 
next->vu_p->up.eu_p->up.lu_p->up.fu_p);
             ccw_result = nmg_loop_is_ccw(next->vu_p->up.eu_p->up.lu_p, 
fu_normal, tol);
 
-            /* sanity check */
             if (ccw_result == 0) {
-                bu_bomb("cut_unimonotone(): can not determine loopuse 
cw/ccw\n");
-            }
+                /* true if could not determine direction */
 
-            if (ccw_result == -1) {
+                /* need to save the lu pointer since the vu pointer
+                 * may become invalid after nmg_tri_kill_accordions 
+                 * removes the accordions
+                 */
+                lu2 = next->vu_p->up.eu_p->up.lu_p;
+
+               if (rt_g.NMG_debug & DEBUG_TRI) {
+                    validate_tbl2d("cut_unimonotone() before 
nmg_tri_kill_accordions", 
+                                    tbl2d, lu2->up.fu_p);
+                }
+
+                nmg_tri_kill_accordions(lu2, tbl2d);
+
+                if (BU_LIST_FIRST_MAGIC(&lu2->down_hd) != NMG_EDGEUSE_MAGIC) {
+                    bu_bomb("cut_unimonotone(): after nmg_tri_kill_accordions, 
loopuse has no edgeuse\n");
+                }
+
+               if (rt_g.NMG_debug & DEBUG_TRI) {
+                    validate_tbl2d("cut_unimonotone() after 
nmg_tri_kill_accordions", 
+                                    tbl2d, lu2->up.fu_p);
+                }
+
+                NMG_CK_LOOPUSE(lu2);
+                /* find the pt2d table entry for the first vu within
+                 * the resulting lu
+                 */
+                eu = BU_LIST_FIRST(edgeuse, &lu2->down_hd);
+                if (!(next = find_pt2d(tbl2d, eu->vu_p))) {
+                    bu_bomb("cut_unimonotone(): unable to find next 
vertexuse\n");
+                }
+
+                /* count the number of vertexuse in the 'next' loopuse */
+                for (BU_LIST_FOR(eu, edgeuse, 
&next->vu_p->up.eu_p->up.lu_p->down_hd)) {
+                    tmp_vert_cnt++;
+                }
+
+                /* set the vertexuse counter (i.e. verts) to the current
+                 * number of vertexuse in the loopuse. this is needed 
+                 * since nmg_tri_kill_accordions, if it kills any 
+                 * accordions in the loopuse, will reduce the number of
+                 * vertexuse in the loopuse
+                 */
+                verts = tmp_vert_cnt;
+
+                if (verts > 3) {
+                    bu_bomb("cut_unimonotone(): can not determine loopuse 
cw/ccw and loopuse contains > 3 vertices\n");
+                }
+
+            } else if (ccw_result == -1) {
+                /* true if the loopuse has a cw rotation */
                 bu_log("cut_unimonotone(): after cut_mapped_loop, next loopuse 
was cw.\n");
 
+               if (rt_g.NMG_debug & DEBUG_TRI) {
+                    validate_tbl2d("cut_unimonotone() before flip direction", 
+                                    tbl2d, 
next->vu_p->up.eu_p->up.lu_p->up.fu_p);
+                }
                 fu = next->vu_p->up.eu_p->up.lu_p->up.fu_p;
                 lu2 = next->vu_p->up.eu_p->up.lu_p;
 
-                /* loop is in wrong direction, exchange lu and lu_mate */
-                BU_LIST_DEQUEUE(&lu2->l);
-                BU_LIST_DEQUEUE(&lu2->lumate_p->l);
-                BU_LIST_APPEND(&fu->lu_hd, &lu2->lumate_p->l);
-                lu2->lumate_p->up.fu_p = fu;
-                BU_LIST_APPEND(&fu->fumate_p->lu_hd, &lu2->l);
-                lu2->up.fu_p = fu->fumate_p;
+                NMG_CK_LOOPUSE(lu2);
+                NMG_CK_FACEUSE(fu);
+                if (*lu2->up.magic_p == NMG_FACEUSE_MAGIC) {
+                    /* loop is in wrong direction, exchange lu and lu_mate */
+                    BU_LIST_DEQUEUE(&lu2->l);
+                    BU_LIST_DEQUEUE(&lu2->lumate_p->l);
+                    BU_LIST_APPEND(&fu->lu_hd, &lu2->lumate_p->l);
+                    lu2->lumate_p->up.fu_p = fu;
+                    BU_LIST_APPEND(&fu->fumate_p->lu_hd, &lu2->l);
+                    lu2->up.fu_p = fu->fumate_p;
+                } else if (*lu2->up.magic_p == NMG_SHELL_MAGIC) {
+                    bu_bomb("cut_unimonotone(): loopuse parent is shell\n");
+                } else {
+                    bu_bomb("cut_unimonotone(): loopuse parent is unknown\n");
+                }
 
-                orig_lu_p = next->vu_p->up.eu_p->up.lu_p->lumate_p;
-
-                /* need to find the correct next vertexuse which is 
-                 * associated with the loopuse mate
+                /* add the new vertexuse to the pt2d table. these new
+                 * vertexuse are from loopuse-mate before the switch.
                  */
-                for (BU_LIST_FOR(vu, vertexuse, &next->vu_p->v_p->vu_hd)) {
-                    if (vu->up.eu_p->up.lu_p == 
next->vu_p->up.eu_p->up.lu_p->lumate_p) {
-                        next->vu_p = vu;
-                        break;
+#if 1
+                lu2 = BU_LIST_FIRST(loopuse, &fu->lu_hd);
+#else
+                lu2 = next->vu_p->up.eu_p->up.lu_p;
+#endif
+                for (BU_LIST_FOR(eu, edgeuse, &lu2->down_hd)) {
+                    NMG_CK_EDGEUSE(eu);
+                    map_new_vertexuse(tbl2d, eu->vu_p); 
+                   if (rt_g.NMG_debug & DEBUG_TRI) {
+                        if (!(tmp = find_pt2d(tbl2d, eu->vu_p))) {
+                            bu_bomb("cut_unimonotone(): vertexuse not added to 
tbl2d table\n");
+                        }
                     }
                 }
+
+                eu = BU_LIST_FIRST(edgeuse, &lu2->down_hd);
+                if (!(next = find_pt2d(tbl2d, eu->vu_p))) {
+                    bu_bomb("cut_unimonotone(): unable to find next vertexuse 
in tbl2d table\n");
+                }
+
+                /* make sure loopuse orientation is marked OT_SAME */
+                nmg_set_lu_orientation(lu2, 0);
+
+               current = PT2D_PREV(tbl2d, next);
+               prev = PT2D_PREV(tbl2d, current);
+
+                orig_lu_p = next->vu_p->up.eu_p->up.lu_p;
+
+               if (rt_g.NMG_debug & DEBUG_TRI) {
+                    validate_tbl2d("cut_unimonotone() after flip direction", 
+                                    tbl2d, 
next->vu_p->up.eu_p->up.lu_p->up.fu_p);
+                }
+            } else if (ccw_result == 1) {
+                /* make sure loopuse orientation is marked OT_SAME */
+                nmg_set_lu_orientation(next->vu_p->up.eu_p->up.lu_p, 0);
+            } else {
+                bu_bomb("cut_unimonotone(): function 'nmg_loop_is_ccw' 
returned an invalid result\n"); 
             }
 
             if (rt_g.NMG_debug & DEBUG_TRI) {
-                bu_log("cut_unimonotone(): ----after cut orig_lu_p = %x lu_p = 
%x 3D %g %g %g --> lu_p = %x 3D %g %g %g\n", 
+                bu_log("cut_unimonotone(): after cut orig_lu_p = %x lu_p = %x 
3D %g %g %g --> lu_p = %x 3D %g %g %g\n", 
                     orig_lu_p, prev->vu_p->up.eu_p->up.lu_p,
                     V3ARGS(prev->vu_p->v_p->vg_p->coord),
                     next->vu_p->up.eu_p->up.lu_p,
@@ -3602,36 +3715,11 @@
             }
 
             if (orig_lu_p != next->vu_p->up.eu_p->up.lu_p) {
-                /* dump contents of pt2d table */
-                bu_log("cut_unimonotone(): next pt2d table entry, next ptr = 
%x 2D coord = %g %g 3D coord = %g %g %g vu_p = %x eu_p = %x lu_p = %x lu_orient 
= %d fu_p = %x vg_p = %x\n",
-                   next, next->coord[X], next->coord[Y],
-                  V3ARGS(next->vu_p->v_p->vg_p->coord),
-                   next->vu_p, next->vu_p->up.eu_p, 
-                  next->vu_p->up.eu_p->up.lu_p,
-                   next->vu_p->up.eu_p->up.lu_p->orientation,
-                   next->vu_p->up.eu_p->up.lu_p->up.fu_p,
-                  next->vu_p->v_p->vg_p);
-
-               for (BU_LIST_FOR(pt, pt2d, tbl2d)) {
-                   if 
((DIST_PT_PT_SQ(next->vu_p->v_p->vg_p->coord,pt->vu_p->v_p->vg_p->coord) < 
tol->dist_sq) &&
-                      (pt->vu_p->v_p->vg_p != next->vu_p->v_p->vg_p)) {
-                      bu_bomb("cut_unimonotone(): found a non-fused vertex\n");
-                   }
-
-                   if (pt->vu_p->v_p->vg_p == next->vu_p->v_p->vg_p) {
-                       bu_log("cut_unimonotone(): start, raw pt2d table, pt2d 
ptr = %x 2D coord = %g %g 3D coord = %g %g %g vu_p = %x eu_p = %x lu_p = %x 
lu_orient = %d fu_p = %x vg_p = %x\n",
-                          pt, pt->coord[X], pt->coord[Y],
-                          V3ARGS(pt->vu_p->v_p->vg_p->coord), pt->vu_p,
-                          pt->vu_p->up.eu_p, pt->vu_p->up.eu_p->up.lu_p,
-                          pt->vu_p->up.eu_p->up.lu_p->orientation,
-                          pt->vu_p->up.eu_p->up.lu_p->up.fu_p,
-                          pt->vu_p->v_p->vg_p);
-                   }
-               }
+                bu_bomb("cut_unimonotone(): next loopuse to cut is not the 
original loopuse\n");
             }
 
            if (rt_g.NMG_debug & DEBUG_TRI) {
-               bu_log("After cut loop:\n");
+               bu_log("cut_unimonotone(): after cut loop:\n");
                nmg_pr_fu_briefly(lu->up.fu_p, "");
            }
             if (next->vu_p->v_p == prev->vu_p->v_p) {
@@ -3652,17 +3740,20 @@
            if (current->vu_p == first->vu_p) {
                t = PT2D_NEXT(tbl2d, first);
                if (rt_g.NMG_debug & DEBUG_TRI) {
-                   bu_log("\tfirst(0x%08x -> %g %g\n", first, t->coord[X], 
t->coord[Y]);
+                   bu_log("cut_unimonotone(): first(0x%08x -> %g %g\n", 
+                            first, t->coord[X], t->coord[Y]);
                 }
 
                t = PT2D_NEXT(tbl2d, current);
                if (rt_g.NMG_debug & DEBUG_TRI) {
-                   bu_log("\tcurrent(0x%08x) -> %g %g\n", current, 
t->coord[X], t->coord[Y]);
+                   bu_log("cut_unimonotone(): current(0x%08x) -> %g %g\n", 
+                            current, t->coord[X], t->coord[Y]);
                 }
 
                current = PT2D_NEXT(tbl2d, current);
                if (rt_g.NMG_debug & DEBUG_TRI) {
-                   bu_log("\tcurrent(0x%08x) -> %g %g\n", current, 
t->coord[X], t->coord[Y]);
+                   bu_log("cut_unimonotone(): current(0x%08x) -> %g %g\n", 
+                            current, t->coord[X], t->coord[Y]);
                 }
            }
        } else {
@@ -3675,7 +3766,7 @@
             }
 
            if (rt_g.NMG_debug & DEBUG_TRI) {
-               bu_log("\tConcave, moving ahead\n");
+               bu_log("cut_unimonotone(): cut not performed, moving to next 
potential cut\n");
             }
            current = next;
        }


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

------------------------------------------------------------------------------
Got Input?   Slashdot Needs You.
Take our quick survey online.  Come on, we don't ask for help often.
Plus, you'll get a chance to win $100 to spend on ThinkGeek.
http://p.sf.net/sfu/slashdot-survey
_______________________________________________
BRL-CAD Source Commits mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/brlcad-commits

Reply via email to