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 -----