diff --git a/draw.cpp b/draw.cpp
index db18be5..bad26ba 100644
--- a/draw.cpp
+++ b/draw.cpp
@@ -490,8 +490,15 @@ void GraphicsWindow::Paint(int w, int h) {
nogrid:;
}
- // Draw the active group; this fills the polygons in a drawing group, and
- // draws the solid mesh.
+ // Draw filled paths in all groups, when those filled paths were requested
+ // specially by assigning a style with a fill color.
+ Group *g;
+ for(g = SK.group.First(); g; g = SK.group.NextAfter(g)) {
+ if(!(g->IsVisible())) continue;
+ g->DrawFilledPaths();
+ }
+
+ // Draw the active group; this does stuff like the mesh and edges.
(SK.GetGroup(activeGroup))->Draw();
// Now draw the entities
diff --git a/drawentity.cpp b/drawentity.cpp
index c704358..862d7d6 100644
--- a/drawentity.cpp
+++ b/drawentity.cpp
@@ -53,8 +53,7 @@ void Entity::DrawAll(void) {
for(i = 0; i < SK.entity.n; i++) {
Entity *e = &(SK.entity.elem[i]);
if(!e->IsPoint()) continue;
- if(!(SK.GetGroup(e->group)->visible)) continue;
- if(SS.GroupsInOrder(SS.GW.activeGroup, e->group)) continue;
+ if(!(SK.GetGroup(e->group)->IsVisible())) continue;
if(e->forceHidden) continue;
Vector v = e->PointGetNum();
@@ -164,8 +163,7 @@ bool Entity::IsVisible(void) {
// The reference normals are always shown
return true;
}
- if(!g->visible) return false;
- if(SS.GroupsInOrder(SS.GW.activeGroup, group)) return false;
+ if(!(g->IsVisible())) return false;
// Don't check if points are hidden; this gets called only for
// selected or hovered points, and those should always be shown.
diff --git a/dsc.h b/dsc.h
index 9c2727e..a5413d5 100644
--- a/dsc.h
+++ b/dsc.h
@@ -202,6 +202,12 @@ public:
// and elemsAllocated is untouched, because we didn't resize
}
+ void RemoveLast(int cnt) {
+ if(n < cnt) oops();
+ n -= cnt;
+ // and elemsAllocated is untouched, same as in RemoveTagged
+ }
+
void Reverse(void) {
int i;
for(i = 0; i < (n/2); i++) {
diff --git a/exportstep.cpp b/exportstep.cpp
index 8bfac1e..3007c1e 100644
--- a/exportstep.cpp
+++ b/exportstep.cpp
@@ -212,7 +212,19 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) {
// along with its inner faces, so do that now.
SBezierLoopSetSet sblss;
ZERO(&sblss);
- sblss.FindOuterFacesFrom(sbl, ss);
+ SPolygon spxyz;
+ ZERO(&spxyz);
+ bool allClosed;
+ SEdge notClosedAt;
+ // We specify a surface, so it doesn't check for coplanarity; and we
+ // don't want it to give us any open contours. The polygon and chord
+ // tolerance are required, because they are used to calculate the
+ // contour directions and determine inner vs. outer contours.
+ sblss.FindOuterFacesFrom(sbl, &spxyz, ss,
+ SS.ChordTolMm() / SS.exportScale,
+ &allClosed, ¬ClosedAt,
+ NULL, NULL,
+ NULL);
// So in our list of SBezierLoopSet, each set contains at least one loop
// (the outer boundary), plus any inner loops associated with that outer
@@ -253,6 +265,7 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) {
listOfLoops.Clear();
}
sblss.Clear();
+ spxyz.Clear();
}
void StepFileWriter::WriteFooter(void) {
diff --git a/graphicswin.cpp b/graphicswin.cpp
index 406e1fb..b7ff561 100644
--- a/graphicswin.cpp
+++ b/graphicswin.cpp
@@ -273,8 +273,8 @@ void GraphicsWindow::LoopOverPoints(Point2d *pmax, Point2d *pmin, double *wmin,
HandlePointForZoomToFit(tr->b, pmax, pmin, wmin, div);
HandlePointForZoomToFit(tr->c, pmax, pmin, wmin, div);
}
- for(i = 0; i < g->poly.l.n; i++) {
- SContour *sc = &(g->poly.l.elem[i]);
+ for(i = 0; i < g->polyLoops.l.n; i++) {
+ SContour *sc = &(g->polyLoops.l.elem[i]);
for(j = 0; j < sc->l.n; j++) {
HandlePointForZoomToFit(sc->l.elem[j].p, pmax, pmin, wmin, div);
}
diff --git a/group.cpp b/group.cpp
index fa087f9..89b17c4 100644
--- a/group.cpp
+++ b/group.cpp
@@ -16,6 +16,12 @@ void Group::AddParam(IdList *param, hParam hp, double v) {
param->Add(&pa);
}
+bool Group::IsVisible(void) {
+ if(!visible) return false;
+ if(SS.GroupsInOrder(SS.GW.activeGroup, h)) return false;
+ return true;
+}
+
void Group::MenuGroup(int id) {
Group g;
ZERO(&g);
@@ -610,7 +616,7 @@ void Group::MakeExtrusionTopBottomFaces(IdList *el, hEntity pt)
{
if(pt.v == 0) return;
Group *src = SK.GetGroup(opA);
- Vector n = src->poly.normal;
+ Vector n = src->polyLoops.normal;
Entity en;
ZERO(&en);
diff --git a/groupmesh.cpp b/groupmesh.cpp
index baaaca4..66ab8b9 100644
--- a/groupmesh.cpp
+++ b/groupmesh.cpp
@@ -2,7 +2,7 @@
#define gs (SS.GW.gs)
-bool Group::AssembleLoops(void) {
+void Group::AssembleLoops(bool *allClosed, bool *allCoplanar) {
SBezierList sbl;
ZERO(&sbl);
@@ -16,38 +16,40 @@ bool Group::AssembleLoops(void) {
e->GenerateBezierCurves(&sbl);
}
- bool allClosed;
- bezierLoopSet = SBezierLoopSet::From(&sbl, &poly,
- &allClosed, &(polyError.notClosedAt));
+ // Try to assemble all these Beziers into loops. The closed loops go into
+ // bezierLoops, with the outer loops grouped with their holes. The
+ // leftovers, if any, go in bezierOpens.
+ bezierLoops.FindOuterFacesFrom(&sbl, &polyLoops, NULL,
+ SS.ChordTolMm(),
+ allClosed, &(polyError.notClosedAt),
+ allCoplanar, &(polyError.errorPointAt),
+ &bezierOpens);
sbl.Clear();
- return allClosed;
}
void Group::GenerateLoops(void) {
- poly.Clear();
- bezierLoopSet.Clear();
+ polyLoops.Clear();
+ bezierLoops.Clear();
+ bezierOpens.Clear();
if(type == DRAWING_3D || type == DRAWING_WORKPLANE ||
type == ROTATE || type == TRANSLATE || type == IMPORTED)
{
- if(AssembleLoops()) {
- polyError.how = POLY_GOOD;
-
- if(!poly.AllPointsInPlane(&(polyError.errorPointAt))) {
- // The edges aren't all coplanar; so not a good polygon
- polyError.how = POLY_NOT_COPLANAR;
- poly.Clear();
- bezierLoopSet.Clear();
- }
- if(poly.SelfIntersecting(&(polyError.errorPointAt))) {
- polyError.how = POLY_SELF_INTERSECTING;
- poly.Clear();
- bezierLoopSet.Clear();
- }
- } else {
+ bool allClosed, allCoplanar;
+ AssembleLoops(&allClosed, &allCoplanar);
+ if(!allCoplanar) {
+ polyError.how = POLY_NOT_COPLANAR;
+ } else if(!allClosed) {
polyError.how = POLY_NOT_CLOSED;
- poly.Clear();
- bezierLoopSet.Clear();
+ } else {
+ polyError.how = POLY_GOOD;
+ // The self-intersecting check is kind of slow, so don't run it
+ // unless requested.
+ if(SS.checkClosedContour) {
+ if(polyLoops.SelfIntersecting(&(polyError.errorPointAt))) {
+ polyError.how = POLY_SELF_INTERSECTING;
+ }
+ }
}
}
}
@@ -159,6 +161,16 @@ void Group::GenerateShellAndMesh(void) {
runningShell.Clear();
runningMesh.Clear();
+ // Don't attempt a lathe or extrusion unless the source section is good:
+ // planar and not self-intersecting.
+ bool haveSrc = true;
+ if(type == EXTRUDE || type == LATHE) {
+ Group *src = SK.GetGroup(opA);
+ if(src->polyError.how != POLY_GOOD) {
+ haveSrc = false;
+ }
+ }
+
if(type == TRANSLATE || type == ROTATE) {
// A step and repeat gets merged against the group's prevous group,
// not our own previous group.
@@ -166,7 +178,7 @@ void Group::GenerateShellAndMesh(void) {
GenerateForStepAndRepeat(&(srcg->thisShell), &thisShell);
GenerateForStepAndRepeat (&(srcg->thisMesh), &thisMesh);
- } else if(type == EXTRUDE) {
+ } else if(type == EXTRUDE && haveSrc) {
Group *src = SK.GetGroup(opA);
Vector translate = Vector::From(h.param(0), h.param(1), h.param(2));
@@ -176,65 +188,76 @@ void Group::GenerateShellAndMesh(void) {
} else {
tbot = translate.ScaledBy(-1); ttop = translate.ScaledBy(1);
}
-
- thisShell.MakeFromExtrusionOf(&(src->bezierLoopSet), tbot, ttop, color);
- Vector onOrig = src->bezierLoopSet.point;
- // And for any plane faces, annotate the model with the entity for
- // that face, so that the user can select them with the mouse.
- int i;
- for(i = 0; i < thisShell.surface.n; i++) {
- SSurface *ss = &(thisShell.surface.elem[i]);
- hEntity face = Entity::NO_ENTITY;
+
+ SBezierLoopSetSet *sblss = &(src->bezierLoops);
+ SBezierLoopSet *sbls;
+ for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
+ int is = thisShell.surface.n;
+ // Extrude this outer contour (plus its inner contours, if present)
+ thisShell.MakeFromExtrusionOf(sbls, tbot, ttop, color);
- Vector p = ss->PointAt(0, 0),
- n = ss->NormalAt(0, 0).WithMagnitude(1);
- double d = n.Dot(p);
+ // And for any plane faces, annotate the model with the entity for
+ // that face, so that the user can select them with the mouse.
+ Vector onOrig = sbls->point;
+ int i;
+ for(i = is; i < thisShell.surface.n; i++) {
+ SSurface *ss = &(thisShell.surface.elem[i]);
+ hEntity face = Entity::NO_ENTITY;
- if(i == 0 || i == 1) {
- // These are the top and bottom of the shell.
- if(fabs((onOrig.Plus(ttop)).Dot(n) - d) < LENGTH_EPS) {
- face = Remap(Entity::NO_ENTITY, REMAP_TOP);
- ss->face = face.v;
+ Vector p = ss->PointAt(0, 0),
+ n = ss->NormalAt(0, 0).WithMagnitude(1);
+ double d = n.Dot(p);
+
+ if(i == is || i == (is + 1)) {
+ // These are the top and bottom of the shell.
+ if(fabs((onOrig.Plus(ttop)).Dot(n) - d) < LENGTH_EPS) {
+ face = Remap(Entity::NO_ENTITY, REMAP_TOP);
+ ss->face = face.v;
+ }
+ if(fabs((onOrig.Plus(tbot)).Dot(n) - d) < LENGTH_EPS) {
+ face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM);
+ ss->face = face.v;
+ }
+ continue;
}
- if(fabs((onOrig.Plus(tbot)).Dot(n) - d) < LENGTH_EPS) {
- face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM);
- ss->face = face.v;
- }
- continue;
- }
- // So these are the sides
- if(ss->degm != 1 || ss->degn != 1) continue;
+ // So these are the sides
+ if(ss->degm != 1 || ss->degn != 1) continue;
- Entity *e;
- for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
- if(e->group.v != opA.v) continue;
- if(e->type != Entity::LINE_SEGMENT) continue;
+ Entity *e;
+ for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
+ if(e->group.v != opA.v) continue;
+ if(e->type != Entity::LINE_SEGMENT) continue;
- Vector a = SK.GetEntity(e->point[0])->PointGetNum(),
- b = SK.GetEntity(e->point[1])->PointGetNum();
- a = a.Plus(ttop);
- b = b.Plus(ttop);
- // Could get taken backwards, so check all cases.
- if((a.Equals(ss->ctrl[0][0]) && b.Equals(ss->ctrl[1][0])) ||
- (b.Equals(ss->ctrl[0][0]) && a.Equals(ss->ctrl[1][0])) ||
- (a.Equals(ss->ctrl[0][1]) && b.Equals(ss->ctrl[1][1])) ||
- (b.Equals(ss->ctrl[0][1]) && a.Equals(ss->ctrl[1][1])))
- {
- face = Remap(e->h, REMAP_LINE_TO_FACE);
- ss->face = face.v;
- break;
+ Vector a = SK.GetEntity(e->point[0])->PointGetNum(),
+ b = SK.GetEntity(e->point[1])->PointGetNum();
+ a = a.Plus(ttop);
+ b = b.Plus(ttop);
+ // Could get taken backwards, so check all cases.
+ if((a.Equals(ss->ctrl[0][0]) && b.Equals(ss->ctrl[1][0])) ||
+ (b.Equals(ss->ctrl[0][0]) && a.Equals(ss->ctrl[1][0])) ||
+ (a.Equals(ss->ctrl[0][1]) && b.Equals(ss->ctrl[1][1])) ||
+ (b.Equals(ss->ctrl[0][1]) && a.Equals(ss->ctrl[1][1])))
+ {
+ face = Remap(e->h, REMAP_LINE_TO_FACE);
+ ss->face = face.v;
+ break;
+ }
}
}
}
- } else if(type == LATHE) {
+ } else if(type == LATHE && haveSrc) {
Group *src = SK.GetGroup(opA);
Vector pt = SK.GetEntity(predef.origin)->PointGetNum(),
axis = SK.GetEntity(predef.entityB)->VectorGetNum();
axis = axis.WithMagnitude(1);
- thisShell.MakeFromRevolutionOf(&(src->bezierLoopSet), pt, axis, color);
+ SBezierLoopSetSet *sblss = &(src->bezierLoops);
+ SBezierLoopSet *sbls;
+ for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
+ thisShell.MakeFromRevolutionOf(sbls, pt, axis, color);
+ }
} else if(type == IMPORTED) {
// The imported shell or mesh are copied over, with the appropriate
// transformation applied. We also must remap the face entities.
@@ -465,10 +488,88 @@ void Group::Draw(void) {
glEnable(GL_DEPTH_TEST);
}
} else {
- glxColorRGBa(Style::Color(Style::CONTOUR_FILL), 0.5);
- glxDepthRangeOffset(1);
- glxFillPolygon(&poly);
- glxDepthRangeOffset(0);
+ // The contours will get filled in DrawFilledPaths.
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Verify that the Beziers in this loop set all have the same auxA, and return
+// that value. If they don't, then set allSame to be false, and indicate a
+// point on the non-matching curve.
+//-----------------------------------------------------------------------------
+DWORD Group::GetLoopSetFillColor(SBezierLoopSet *sbls,
+ bool *allSame, Vector *errorAt)
+{
+ bool first = true;
+ DWORD fillRgb = (DWORD)-1;
+
+ SBezierLoop *sbl;
+ for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
+ SBezier *sb;
+ for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
+ DWORD thisRgb = (DWORD)-1;
+ if(sb->auxA != 0) {
+ hStyle hs = { sb->auxA };
+ Style *s = Style::Get(hs);
+ if(s->filled) {
+ thisRgb = s->fillColor;
+ }
+ }
+ if(first) {
+ fillRgb = thisRgb;
+ first = false;
+ } else {
+ if(fillRgb != thisRgb) {
+ *allSame = false;
+ *errorAt = sb->Start();
+ return fillRgb;
+ }
+ }
+ }
+ }
+ *allSame = true;
+ return fillRgb;
+}
+
+void Group::FillLoopSetAsPolygon(SBezierLoopSet *sbls) {
+ SPolygon sp;
+ ZERO(&sp);
+ sbls->MakePwlInto(&sp);
+ glxDepthRangeOffset(1);
+ glxFillPolygon(&sp);
+ glxDepthRangeOffset(0);
+ sp.Clear();
+}
+
+void Group::DrawFilledPaths(void) {
+ SBezierLoopSet *sbls;
+ SBezierLoopSetSet *sblss = &bezierLoops;
+ for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
+ bool allSame;
+ Vector errorPt;
+ DWORD fillRgb = GetLoopSetFillColor(sbls, &allSame, &errorPt);
+ if(allSame && fillRgb != (DWORD)-1) {
+ glxColorRGBa(fillRgb, 1);
+ FillLoopSetAsPolygon(sbls);
+ } else if(!allSame) {
+ glDisable(GL_DEPTH_TEST);
+ glxColorRGB(Style::Color(Style::DRAW_ERROR));
+ glxWriteText("not all same fill color!", DEFAULT_TEXT_HEIGHT,
+ errorPt, SS.GW.projRight, SS.GW.projUp, NULL, NULL);
+ glEnable(GL_DEPTH_TEST);
+ } else {
+ if(h.v == SS.GW.activeGroup.v && SS.checkClosedContour &&
+ polyError.how == POLY_GOOD)
+ {
+ // If this is the active group, and we are supposed to check
+ // for closed contours, and we do indeed have a closed and
+ // non-intersecting contour, then fill it dimly.
+ glxColorRGBa(Style::Color(Style::CONTOUR_FILL), 0.5);
+ glxDepthRangeOffset(1);
+ FillLoopSetAsPolygon(sbls);
+ glxDepthRangeOffset(0);
+ }
+ }
}
}
diff --git a/mouse.cpp b/mouse.cpp
index 886655a..86644bd 100644
--- a/mouse.cpp
+++ b/mouse.cpp
@@ -37,6 +37,10 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
}
}
+ if(!leftDown && pending.operation == DRAGGING_POINT) {
+ ClearPending();
+ }
+
Point2d mp = { x, y };
if(rightDown && orig.mouse.DistanceTo(mp) < 5 && !orig.startedMoving) {
@@ -980,9 +984,6 @@ void GraphicsWindow::MouseLeave(void) {
toolbarHovered = 0;
PaintGraphics();
}
- if(pending.operation == DRAGGING_POINT) {
- ClearPending();
- }
}
void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz,
diff --git a/polygon.cpp b/polygon.cpp
index 8e99db6..040eb22 100644
--- a/polygon.cpp
+++ b/polygon.cpp
@@ -447,18 +447,6 @@ bool SContour::ContainsPointProjdToNormal(Vector n, Vector p) {
return inside;
}
-bool SContour::AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt) {
- for(int i = 0; i < l.n; i++) {
- Vector p = l.elem[i].p;
- double dd = n.Dot(p) - d;
- if(fabs(dd) > 10*LENGTH_EPS) {
- *notCoplanarAt = p;
- return false;
- }
- }
- return true;
-}
-
void SContour::Reverse(void) {
l.Reverse();
}
@@ -549,20 +537,6 @@ Vector SPolygon::AnyPoint(void) {
return l.elem[0].l.elem[0].p;
}
-bool SPolygon::AllPointsInPlane(Vector *notCoplanarAt) {
- if(IsEmpty()) return true;
-
- Vector p0 = AnyPoint();
- double d = normal.Dot(p0);
-
- for(int i = 0; i < l.n; i++) {
- if(!(l.elem[i]).AllPointsInPlane(normal, d, notCoplanarAt)) {
- return false;
- }
- }
- return true;
-}
-
bool SPolygon::SelfIntersecting(Vector *intersectsAt) {
SEdgeList el;
ZERO(&el);
diff --git a/polygon.h b/polygon.h
index 790f6df..6744561 100644
--- a/polygon.h
+++ b/polygon.h
@@ -71,7 +71,6 @@ public:
Vector ComputeNormal(void);
bool IsClockwiseProjdToNormal(Vector n);
bool ContainsPointProjdToNormal(Vector n, Vector p);
- bool AllPointsInPlane(Vector n, double d, Vector *notCoplanarAt);
void OffsetInto(SContour *dest, double r);
void CopyInto(SContour *dest);
void FindPointWithMinX(void);
@@ -102,7 +101,6 @@ public:
void TriangulateInto(SMesh *m);
void TriangulateInto(SMesh *m, STriMeta meta);
void Clear(void);
- bool AllPointsInPlane(Vector *notCoplanarAt);
bool SelfIntersecting(Vector *intersectsAt);
bool IsEmpty(void);
Vector AnyPoint(void);
diff --git a/sketch.h b/sketch.h
index 525a557..80f3112 100644
--- a/sketch.h
+++ b/sketch.h
@@ -141,8 +141,9 @@ public:
bool negateV;
} predef;
- SPolygon poly;
- SBezierLoopSet bezierLoopSet;
+ SPolygon polyLoops;
+ SBezierLoopSetSet bezierLoops;
+ SBezierList bezierOpens;
static const int POLY_GOOD = 0;
static const int POLY_NOT_CLOSED = 1;
static const int POLY_NOT_COPLANAR = 2;
@@ -211,10 +212,11 @@ public:
void AddEq(IdList *l, Expr *expr, int index);
void GenerateEquations(IdList *l);
+ bool IsVisible(void);
// Assembling the curves into loops, and into a piecewise linear polygon
// at the same time.
- bool AssembleLoops(void);
+ void AssembleLoops(bool *allClosed, bool *allCoplanar);
void GenerateLoops(void);
// And the mesh stuff
Group *PreviousGroup(void);
@@ -225,6 +227,10 @@ public:
void GenerateDisplayItems(void);
void DrawDisplayItems(int t);
void Draw(void);
+ DWORD GetLoopSetFillColor(SBezierLoopSet *sbls,
+ bool *allSame, Vector *errorAt);
+ void FillLoopSetAsPolygon(SBezierLoopSet *sbls);
+ void DrawFilledPaths(void);
SPolygon GetPolygon(void);
diff --git a/srf/curve.cpp b/srf/curve.cpp
index dbf697f..4bac32c 100644
--- a/srf/curve.cpp
+++ b/srf/curve.cpp
@@ -299,6 +299,95 @@ void SBezier::AllIntersectionsWith(SBezier *sbb, SPointList *spl) {
splRaw.Clear();
}
+//-----------------------------------------------------------------------------
+// Find a plane that contains all of the curves in this list. If the curves
+// are all colinear (or coincident, or empty), then that plane is not exactly
+// determined but we choose the additional degree(s) of freedom arbitrarily.
+// Returns true if all the curves are coplanar, otherwise false.
+//-----------------------------------------------------------------------------
+bool SBezierList::GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v,
+ Vector *notCoplanarAt)
+{
+ Vector pt, ptFar, ptOffLine, dp, n;
+ double farMax, offLineMax;
+ int i;
+ SBezier *sb;
+
+ // Get any point on any Bezier; or an arbitrary point if list is empty.
+ if(l.n > 0) {
+ pt = l.elem[0].Start();
+ } else {
+ pt = Vector::From(0, 0, 0);
+ }
+ ptFar = ptOffLine = pt;
+
+ // Get the point farthest from our arbitrary point.
+ farMax = VERY_NEGATIVE;
+ for(sb = l.First(); sb; sb = l.NextAfter(sb)) {
+ for(i = 0; i <= sb->deg; i++) {
+ double m = (pt.Minus(sb->ctrl[i])).Magnitude();
+ if(m > farMax) {
+ ptFar = sb->ctrl[i];
+ farMax = m;
+ }
+ }
+ }
+ if(ptFar.Equals(pt)) {
+ // The points are all coincident. So neither basis vector matters.
+ *p = pt;
+ *u = Vector::From(1, 0, 0);
+ *v = Vector::From(0, 1, 0);
+ return true;
+ }
+
+ // Get the point farthest from the line between pt and ptFar
+ dp = ptFar.Minus(pt);
+ offLineMax = VERY_NEGATIVE;
+ for(sb = l.First(); sb; sb = l.NextAfter(sb)) {
+ for(i = 0; i <= sb->deg; i++) {
+ double m = (sb->ctrl[i]).DistanceToLine(pt, dp);
+ if(m > offLineMax) {
+ ptOffLine = sb->ctrl[i];
+ offLineMax = m;
+ }
+ }
+ }
+
+ *p = pt;
+ if(offLineMax < LENGTH_EPS) {
+ // The points are all colinear; so choose the second basis vector
+ // arbitrarily.
+ *u = (ptFar.Minus(pt)).WithMagnitude(1);
+ *v = (u->Normal(0)).WithMagnitude(1);
+ } else {
+ // The points actually define a plane.
+ n = (ptFar.Minus(pt)).Cross(ptOffLine.Minus(pt));
+ *u = (n.Normal(0)).WithMagnitude(1);
+ *v = (n.Normal(1)).WithMagnitude(1);
+ }
+
+ // So we have a plane; but check whether all of the points lie in that
+ // plane.
+ n = u->Cross(*v);
+ n = n.WithMagnitude(1);
+ double d = p->Dot(n);
+ for(sb = l.First(); sb; sb = l.NextAfter(sb)) {
+ for(i = 0; i <= sb->deg; i++) {
+ if(fabs(n.Dot(sb->ctrl[i]) - d) > LENGTH_EPS) {
+ if(notCoplanarAt) *notCoplanarAt = sb->ctrl[i];
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Assemble curves in sbl into a single loop. The curves may appear in any
+// direction (start to finish, or finish to start), and will be reversed if
+// necessary. The curves in the returned loop are removed from sbl, even if
+// the loop cannot be closed.
+//-----------------------------------------------------------------------------
SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl,
bool *allClosed, SEdge *errorAt)
{
@@ -375,24 +464,20 @@ void SBezierLoop::GetBoundingProjd(Vector u, Vector orig,
}
}
-void SBezierLoop::MakePwlInto(SContour *sc) {
- List lv;
- ZERO(&lv);
-
- int i, j;
- for(i = 0; i < l.n; i++) {
- SBezier *sb = &(l.elem[i]);
- sb->MakePwlInto(&lv);
-
- // Each curve's piecewise linearization includes its endpoints,
- // which we don't want to duplicate (creating zero-len edges).
- for(j = (i == 0 ? 0 : 1); j < lv.n; j++) {
- sc->AddPoint(lv.elem[j]);
+void SBezierLoop::MakePwlInto(SContour *sc, double chordTol) {
+ SBezier *sb;
+ for(sb = l.First(); sb; sb = l.NextAfter(sb)) {
+ sb->MakePwlInto(sc, chordTol);
+ // Avoid double points at join between Beziers; except that
+ // first and last points should be identical.
+ if(l.NextAfter(sb) != NULL) {
+ sc->l.RemoveLast(1);
}
- lv.Clear();
}
// Ensure that it's exactly closed, not just within a numerical tolerance.
- sc->l.elem[sc->l.n - 1] = sc->l.elem[0];
+ if((sc->l.elem[sc->l.n - 1].p).Equals(sc->l.elem[0].p)) {
+ sc->l.elem[sc->l.n - 1] = sc->l.elem[0];
+ }
}
bool SBezierLoop::IsClosed(void) {
@@ -403,26 +488,40 @@ bool SBezierLoop::IsClosed(void) {
}
+//-----------------------------------------------------------------------------
+// Assemble the curves in sbl into multiple loops, and piecewise linearize the
+// curves into poly. If we can't close a contour, then we add it to
+// openContours (if that isn't NULL) and keep going; so this works even if the
+// input contains a mix of open and closed curves.
+//-----------------------------------------------------------------------------
SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly,
- bool *allClosed, SEdge *errorAt)
+ double chordTol,
+ bool *allClosed, SEdge *errorAt,
+ SBezierList *openContours)
{
- int i;
SBezierLoopSet ret;
ZERO(&ret);
+ *allClosed = true;
while(sbl->l.n > 0) {
bool thisClosed;
SBezierLoop loop;
loop = SBezierLoop::FromCurves(sbl, &thisClosed, errorAt);
if(!thisClosed) {
- ret.Clear();
+ // Record open loops in a separate list, if requested.
*allClosed = false;
- return ret;
+ if(openContours) {
+ SBezier *sb;
+ for(sb = loop.l.First(); sb; sb = loop.l.NextAfter(sb)) {
+ openContours->l.Add(sb);
+ }
+ }
+ loop.Clear();
+ } else {
+ ret.l.Add(&loop);
+ poly->AddEmptyContour();
+ loop.MakePwlInto(&(poly->l.elem[poly->l.n-1]), chordTol);
}
-
- ret.l.Add(&loop);
- poly->AddEmptyContour();
- loop.MakePwlInto(&(poly->l.elem[poly->l.n-1]));
}
poly->normal = poly->ComputeNormal();
@@ -432,17 +531,7 @@ SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly,
} else {
ret.point = Vector::From(0, 0, 0);
}
- poly->FixContourDirections();
- for(i = 0; i < poly->l.n; i++) {
- if(poly->l.elem[i].tag) {
- // We had to reverse this contour in order to fix the poly
- // contour directions; so need to do the same with the curves.
- ret.l.elem[i].Reverse();
- }
- }
-
- *allClosed = true;
return ret;
}
@@ -455,6 +544,18 @@ void SBezierLoopSet::GetBoundingProjd(Vector u, Vector orig,
}
}
+//-----------------------------------------------------------------------------
+// Convert all the Beziers into piecewise linear form, and assemble that into
+// a polygon, one contour per loop.
+//-----------------------------------------------------------------------------
+void SBezierLoopSet::MakePwlInto(SPolygon *sp) {
+ SBezierLoop *sbl;
+ for(sbl = l.First(); sbl; sbl = l.NextAfter(sbl)) {
+ sp->AddEmptyContour();
+ sbl->MakePwlInto(&(sp->l.elem[sp->l.n - 1]));
+ }
+}
+
void SBezierLoopSet::Clear(void) {
int i;
for(i = 0; i < l.n; i++) {
@@ -468,44 +569,73 @@ void SBezierLoopSet::Clear(void) {
// assemble them into loops. We find the outer loops, and find the outer loops'
// inner loops, and group them accordingly.
//-----------------------------------------------------------------------------
-void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SSurface *srfuv) {
+void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz,
+ SSurface *srfuv,
+ double chordTol,
+ bool *allClosed, SEdge *notClosedAt,
+ bool *allCoplanar, Vector *notCoplanarAt,
+ SBezierList *openContours)
+{
+ SSurface srfPlane;
+ if(!srfuv) {
+ Vector p, u, v;
+ *allCoplanar =
+ sbl->GetPlaneContainingBeziers(&p, &u, &v, notCoplanarAt);
+ if(!*allCoplanar) {
+ // Don't even try to assemble them into loops if they're not
+ // all coplanar.
+ if(openContours) {
+ SBezier *sb;
+ for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
+ openContours->l.Add(sb);
+ }
+ }
+ return;
+ }
+ // All the curves lie in a plane through p with basis vectors u and v.
+ srfPlane = SSurface::FromPlane(p, u, v);
+ srfuv = &srfPlane;
+ }
+
int i, j;
- bool allClosed;
- SEdge errorAt;
- SPolygon sp;
- ZERO(&sp);
// Assemble the Bezier trim curves into closed loops; we also get the
- // piecewise linearization of the curves (in the SPolygon sp), as a
+ // piecewise linearization of the curves (in the SPolygon spxyz), as a
// calculation aid for the loop direction.
- SBezierLoopSet sbls = SBezierLoopSet::From(sbl, &sp, &allClosed, &errorAt);
+ SBezierLoopSet sbls = SBezierLoopSet::From(sbl, spxyz, chordTol,
+ allClosed, notClosedAt,
+ openContours);
+ if(sbls.l.n != spxyz->l.n) return;
// Convert the xyz piecewise linear to uv piecewise linear.
- SContour *contour;
- for(contour = sp.l.First(); contour; contour = sp.l.NextAfter(contour)) {
+ SPolygon spuv;
+ ZERO(&spuv);
+ SContour *sc;
+ for(sc = spxyz->l.First(); sc; sc = spxyz->l.NextAfter(sc)) {
+ spuv.AddEmptyContour();
SPoint *pt;
- for(pt = contour->l.First(); pt; pt = contour->l.NextAfter(pt)) {
+ for(pt = sc->l.First(); pt; pt = sc->l.NextAfter(pt)) {
double u, v;
srfuv->ClosestPointTo(pt->p, &u, &v);
- pt->p = Vector::From(u, v, 0);
+ spuv.l.elem[spuv.l.n - 1].AddPoint(Vector::From(u, v, 0));
}
}
- sp.normal = Vector::From(0, 0, 1);
+ spuv.normal = Vector::From(0, 0, 1); // must be, since it's in xy plane now
static const int OUTER_LOOP = 10;
static const int INNER_LOOP = 20;
static const int USED_LOOP = 30;
- // Fix the contour directions; SBezierLoopSet::From() works only for
- // planes, since it uses the polygon xyz space.
- sp.FixContourDirections();
- for(i = 0; i < sp.l.n; i++) {
- SContour *contour = &(sp.l.elem[i]);
+ // Fix the contour directions; we do this properly, in uv space, so it
+ // works for curved surfaces too (important for STEP export).
+ spuv.FixContourDirections();
+ for(i = 0; i < spuv.l.n; i++) {
+ SContour *contour = &(spuv.l.elem[i]);
SBezierLoop *bl = &(sbls.l.elem[i]);
if(contour->tag) {
// This contour got reversed in the polygon to make the directions
// consistent, so the same must be necessary for the Bezier loop.
bl->Reverse();
}
- if(contour->IsClockwiseProjdToNormal(sp.normal)) {
+ if(contour->IsClockwiseProjdToNormal(spuv.normal)) {
bl->tag = INNER_LOOP;
} else {
bl->tag = OUTER_LOOP;
@@ -528,8 +658,8 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SSurface *srfuv) {
if(i == j) continue;
if(outer->tag != OUTER_LOOP) continue;
- Vector p = sp.l.elem[j].AnyEdgeMidpoint();
- if(sp.l.elem[i].ContainsPointProjdToNormal(sp.normal, p)) {
+ Vector p = spuv.l.elem[j].AnyEdgeMidpoint();
+ if(spuv.l.elem[i].ContainsPointProjdToNormal(spuv.normal, p)) {
break;
}
}
@@ -548,17 +678,19 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SSurface *srfuv) {
SBezierLoop *inner = &(sbls.l.elem[j]);
if(inner->tag != INNER_LOOP) continue;
- Vector p = sp.l.elem[j].AnyEdgeMidpoint();
- if(sp.l.elem[i].ContainsPointProjdToNormal(sp.normal, p)) {
+ Vector p = spuv.l.elem[j].AnyEdgeMidpoint();
+ if(spuv.l.elem[i].ContainsPointProjdToNormal(spuv.normal, p)) {
outerAndInners.l.Add(inner);
inner->tag = USED_LOOP;
}
}
-
+
+ outerAndInners.point = srfuv->PointAt(0, 0);
+ outerAndInners.normal = srfuv->NormalAt(0, 0);
l.Add(&outerAndInners);
}
}
- sp.Clear();
+ spuv.Clear();
// Don't free sbls; we've shallow-copied all of its members to ourself.
}
diff --git a/srf/ratpoly.cpp b/srf/ratpoly.cpp
index 5d117cd..f32f86b 100644
--- a/srf/ratpoly.cpp
+++ b/srf/ratpoly.cpp
@@ -250,6 +250,16 @@ void SBezier::MakePwlInto(List *l, double chordTol) {
}
lv.Clear();
}
+void SBezier::MakePwlInto(SContour *sc, double chordTol) {
+ List lv;
+ ZERO(&lv);
+ MakePwlInto(&lv, chordTol);
+ int i;
+ for(i = 0; i < lv.n; i++) {
+ sc->AddPoint(lv.elem[i]);
+ }
+ lv.Clear();
+}
void SBezier::MakePwlInto(List *l, double chordTol) {
if(chordTol == 0) {
// Use the default chord tolerance.
@@ -362,6 +372,19 @@ void SSurface::ClosestPointTo(Vector p, double *u, double *v, bool converge) {
if(p.Equals(ctrl[degm][degn])) { *u = 1; *v = 1; return; }
if(p.Equals(ctrl[0] [degn])) { *u = 0; *v = 1; return; }
+ // And planes are trivial, so don't waste time iterating over those.
+ if(degm == 1 && degn == 1) {
+ Vector orig = ctrl[0][0],
+ bu = (ctrl[1][0]).Minus(orig),
+ bv = (ctrl[0][1]).Minus(orig);
+ if((ctrl[1][1]).Equals(orig.Plus(bu).Plus(bv))) {
+ Vector dp = p.Minus(orig);
+ *u = dp.Dot(bu) / bu.MagSquared();
+ *v = dp.Dot(bv) / bv.MagSquared();
+ return;
+ }
+ }
+
// Try whatever the previous guess was. This is likely to do something
// good if we're working our way along a curve or something else where
// we project successive points that are close to each other; something
@@ -377,22 +400,18 @@ void SSurface::ClosestPointTo(Vector p, double *u, double *v, bool converge) {
// Search for a reasonable initial guess
int i, j;
- if(degm == 1 && degn == 1) {
- *u = *v = 0; // a plane, perfect no matter what the initial guess
- } else {
- double minDist = VERY_POSITIVE;
- int res = (max(degm, degn) == 2) ? 7 : 20;
- for(i = 0; i < res; i++) {
- for(j = 0; j < res; j++) {
- double tryu = (i + 0.5)/res, tryv = (j + 0.5)/res;
-
- Vector tryp = PointAt(tryu, tryv);
- double d = (tryp.Minus(p)).Magnitude();
- if(d < minDist) {
- *u = tryu;
- *v = tryv;
- minDist = d;
- }
+ double minDist = VERY_POSITIVE;
+ int res = (max(degm, degn) == 2) ? 7 : 20;
+ for(i = 0; i < res; i++) {
+ for(j = 0; j < res; j++) {
+ double tryu = (i + 0.5)/res, tryv = (j + 0.5)/res;
+
+ Vector tryp = PointAt(tryu, tryv);
+ double d = (tryp.Minus(p)).Magnitude();
+ if(d < minDist) {
+ *u = tryu;
+ *v = tryv;
+ minDist = d;
}
}
}
diff --git a/srf/surface.cpp b/srf/surface.cpp
index c6051af..15e4283 100644
--- a/srf/surface.cpp
+++ b/srf/surface.cpp
@@ -487,8 +487,6 @@ void SSurface::Clear(void) {
void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1,
int color)
{
- ZERO(this);
-
// Make the extrusion direction consistent with respect to the normal
// of the sketch we're extruding.
if((t0.Minus(t1)).Dot(sbls->normal) < 0) {
@@ -610,7 +608,6 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1,
void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis,
int color)
{
- ZERO(this);
SBezierLoop *sbl;
int i0 = surface.n, i;
diff --git a/srf/surface.h b/srf/surface.h
index bd6ad98..09fdf1a 100644
--- a/srf/surface.h
+++ b/srf/surface.h
@@ -79,6 +79,7 @@ public:
bool Equals(SBezier *b);
void MakePwlInto(SEdgeList *sel, double chordTol=0);
void MakePwlInto(List *l, double chordTol=0);
+ void MakePwlInto(SContour *sc, double chordTol=0);
void MakePwlInto(List *l, double chordTol=0);
void MakePwlWorker(List *l, double ta, double tb, double chordTol);
@@ -111,6 +112,8 @@ public:
void ScaleSelfBy(double s);
void CullIdenticalBeziers(void);
void AllIntersectionsWith(SBezierList *sblb, SPointList *spl);
+ bool GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v,
+ Vector *notCoplanarAt);
};
class SBezierLoop {
@@ -121,7 +124,7 @@ public:
inline void Clear(void) { l.Clear(); }
bool IsClosed(void);
void Reverse(void);
- void MakePwlInto(SContour *sc);
+ void MakePwlInto(SContour *sc, double chordTol=0);
void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax);
static SBezierLoop FromCurves(SBezierList *spcl,
@@ -135,9 +138,13 @@ public:
Vector point;
static SBezierLoopSet From(SBezierList *spcl, SPolygon *poly,
- bool *allClosed, SEdge *errorAt);
+ double chordTol,
+ bool *allClosed, SEdge *errorAt,
+ SBezierList *openContours);
void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax);
+ void MakePwlInto(SPolygon *sp);
+ int GetAuxA(bool *allSame, Vector *errorAt);
void Clear(void);
};
@@ -145,7 +152,11 @@ class SBezierLoopSetSet {
public:
List l;
- void FindOuterFacesFrom(SBezierList *sbl, SSurface *srfuv);
+ void FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, SSurface *srfuv,
+ double chordTol,
+ bool *allClosed, SEdge *notClosedAt,
+ bool *allCoplanar, Vector *notCoplanarAt,
+ SBezierList *openContours);
void Clear(void);
};
diff --git a/style.cpp b/style.cpp
index 8cbe206..b54357a 100644
--- a/style.cpp
+++ b/style.cpp
@@ -142,7 +142,7 @@ void Style::AssignSelectionToStyle(DWORD v) {
hRequest hr = he.request();
Request *r = SK.GetRequest(hr);
r->style.v = v;
- SS.later.generateAll = true;
+ SS.MarkGroupDirty(r->group);
}
for(i = 0; i < SS.GW.gs.constraints; i++) {
hConstraint hc = SS.GW.gs.constraint[i];
@@ -159,6 +159,7 @@ void Style::AssignSelectionToStyle(DWORD v) {
SS.GW.ClearSelection();
InvalidateGraphics();
+ SS.later.generateAll = true;
// And show that style's info screen in the text window.
SS.TW.GoToScreen(TextWindow::SCREEN_STYLE_INFO);
diff --git a/undoredo.cpp b/undoredo.cpp
index f48f59e..ac76a00 100644
--- a/undoredo.cpp
+++ b/undoredo.cpp
@@ -47,8 +47,9 @@ void SolveSpace::PushFromCurrentOnto(UndoStack *uk) {
// and zero out all the dynamic stuff that will get regenerated.
dest.clean = false;
ZERO(&(dest.solved));
- ZERO(&(dest.poly));
- ZERO(&(dest.bezierLoopSet));
+ ZERO(&(dest.polyLoops));
+ ZERO(&(dest.bezierLoops));
+ ZERO(&(dest.bezierOpens));
ZERO(&(dest.polyError));
ZERO(&(dest.thisMesh));
ZERO(&(dest.runningMesh));
@@ -96,8 +97,9 @@ void SolveSpace::PopOntoCurrentFrom(UndoStack *uk) {
// Free everything in the main copy of the program before replacing it
for(i = 0; i < SK.group.n; i++) {
Group *g = &(SK.group.elem[i]);
- g->poly.Clear();
- g->bezierLoopSet.Clear();
+ g->polyLoops.Clear();
+ g->bezierLoops.Clear();
+ g->bezierOpens.Clear();
g->thisMesh.Clear();
g->runningMesh.Clear();
g->thisShell.Clear();
diff --git a/wishlist.txt b/wishlist.txt
index 48abf98..5f618d4 100644
--- a/wishlist.txt
+++ b/wishlist.txt
@@ -1,7 +1,9 @@
multi-drag
+select loop, all in group, others
copy and paste
filled contours for export
+background image
associative entities from solid model, as a special group
-----