diff --git a/CHANGELOG.md b/CHANGELOG.md index 17a0e805..1b6f0dba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,8 @@ New export/import features: exported. This format allows to easily hack on triangle mesh data created in SolveSpace, supports colour information and is more space efficient than most other formats. + * Export 2d section: custom styled entities that lie in the same + plane as the exported section are included. New rendering features: * The "Show/hide hidden lines" button is now a tri-state button that allows diff --git a/src/entity.cpp b/src/entity.cpp index 4f4eabe1..4b3d0990 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -784,6 +784,47 @@ Vector EntityBase::EndpointFinish() const { return SK.GetEntity(point[2])->PointGetNum(); } else ssassert(false, "Unexpected entity type"); } +static bool PointInPlane(hEntity h, Vector norm, double distance) { + Vector p = SK.GetEntity(h)->PointGetNum(); + return (fabs(norm.Dot(p) - distance) < LENGTH_EPS); +} +bool EntityBase::IsInPlane(Vector norm, double distance) const { + switch(type) { + case Type::LINE_SEGMENT: { + return PointInPlane(point[0], norm, distance) + && PointInPlane(point[1], norm, distance); + } + case Type::CUBIC: + case Type::CUBIC_PERIODIC: { + bool periodic = type == Type::CUBIC_PERIODIC; + int n = periodic ? 3 + extraPoints : extraPoints; + int i; + for (i=0; iNormalN(); + if (!norm.Equals(n) && !norm.Equals(n.Negated())) return false; + return PointInPlane(point[0], norm, distance); + } + + case Type::TTF_TEXT: { + Vector n = Normal()->NormalN(); + if (!norm.Equals(n) && !norm.Equals(n.Negated())) return false; + return PointInPlane(point[0], norm, distance) + && PointInPlane(point[1], norm, distance); + } + + default: + return false; + } +} void EntityBase::RectGetPointsExprs(ExprVector *eb, ExprVector *ec) const { ssassert(type == Type::TTF_TEXT || type == Type::IMAGE, diff --git a/src/export.cpp b/src/export.cpp index 01020e14..7ff59f0e 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -78,9 +78,8 @@ void SolveSpaceUI::ExportSectionTo(const Platform::Path &filename) { g->runningMesh.MakeEdgesInPlaneInto(&el, n, d); // If there's a shell, then grab the edges and possibly Beziers. - g->runningShell.MakeSectionEdgesInto(n, d, - &el, - (SS.exportPwlCurves || fabs(SS.exportOffset) > LENGTH_EPS) ? NULL : &bl); + bool export_as_pwl = SS.exportPwlCurves || fabs(SS.exportOffset) > LENGTH_EPS; + g->runningShell.MakeSectionEdgesInto(n, d, &el, export_as_pwl ? NULL : &bl); // All of these are solid model edges, so use the appropriate style. SEdge *se; @@ -92,8 +91,28 @@ void SolveSpaceUI::ExportSectionTo(const Platform::Path &filename) { sb->auxA = Style::SOLID_EDGE; } - el.CullExtraneousEdges(); - bl.CullIdenticalBeziers(); + // Remove all overlapping edges/beziers to merge the areas they describe. + el.CullExtraneousEdges(/*both=*/true); + bl.CullIdenticalBeziers(/*both=*/true); + + // Collect lines and beziers with custom style & export. + int i; + for(i = 0; i < SK.entity.n; i++) { + Entity *e = &(SK.entity.elem[i]); + if (!e->IsVisible()) continue; + if (e->style.v < Style::FIRST_CUSTOM) continue; + if (!Style::Exportable(e->style.v)) continue; + if (!e->IsInPlane(n,d)) continue; + if (export_as_pwl) { + e->GenerateEdges(&el); + } else { + e->GenerateBezierCurves(&bl); + } + } + + // Only remove half of the overlapping edges/beziers to support TTF Stick Fonts. + el.CullExtraneousEdges(/*both=*/false); + bl.CullIdenticalBeziers(/*both=*/false); // And write the edges. VectorFileWriter *out = VectorFileWriter::ForFile(filename); @@ -573,7 +592,7 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s // If possible, then we will assemble these output curves into loops. They // will then get exported as closed paths. SBezierLoopSetSet sblss = {}; - SBezierList leftovers = {}; + SBezierLoopSet leftovers = {}; SSurface srf = SSurface::FromPlane(Vector::From(0, 0, 0), Vector::From(1, 0, 0), Vector::From(0, 1, 0)); @@ -586,14 +605,11 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s &allClosed, ¬ClosedAt, NULL, NULL, &leftovers); - for(b = leftovers.l.First(); b; b = leftovers.l.NextAfter(b)) { - sblss.AddOpenPath(b); - } + sblss.l.Add(&leftovers); // Now write the lines and triangles to the output file out->OutputLinesAndMesh(&sblss, &sms); - leftovers.Clear(); spxyz.Clear(); sblss.Clear(); smp.Clear(); diff --git a/src/polygon.cpp b/src/polygon.cpp index a4fd55cf..39879dbf 100644 --- a/src/polygon.cpp +++ b/src/polygon.cpp @@ -327,10 +327,13 @@ bool SEdgeList::ContainsEdge(const SEdge *set) const { } //----------------------------------------------------------------------------- -// Remove unnecessary edges: if two are anti-parallel then remove both, and if -// two are parallel then remove one. +// Remove unnecessary edges: +// - if two are anti-parallel then +// if both=true, remove both +// else remove only one. +// - if two are parallel then remove one. //----------------------------------------------------------------------------- -void SEdgeList::CullExtraneousEdges() { +void SEdgeList::CullExtraneousEdges(bool both) { l.ClearTags(); int i, j; for(i = 0; i < l.n; i++) { @@ -342,8 +345,9 @@ void SEdgeList::CullExtraneousEdges() { set->tag = 1; } if((set->a).Equals(se->b) && (set->b).Equals(se->a)) { - // Two anti-parallel edges exist; so keep neither. - se->tag = 1; + // Two anti-parallel edges exist; if both=true, keep neither, + // otherwise keep only one. + if (both) se->tag = 1; set->tag = 1; } } diff --git a/src/polygon.h b/src/polygon.h index 9855a9d5..c340e136 100644 --- a/src/polygon.h +++ b/src/polygon.h @@ -58,7 +58,7 @@ public: Vector *pi=NULL, SPointList *spl=NULL) const; bool ContainsEdgeFrom(const SEdgeList *sel) const; bool ContainsEdge(const SEdge *se) const; - void CullExtraneousEdges(); + void CullExtraneousEdges(bool both=true); void MergeCollinearSegments(Vector a, Vector b); }; diff --git a/src/sketch.h b/src/sketch.h index 7bf49ff9..cc5c0d23 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -185,7 +185,7 @@ public: SPolygon polyLoops; SBezierLoopSetSet bezierLoops; - SBezierList bezierOpens; + SBezierLoopSet bezierOpens; struct { PolyError how; @@ -483,6 +483,7 @@ public: bool HasEndpoints() const; Vector EndpointStart() const; Vector EndpointFinish() const; + bool IsInPlane(Vector norm, double distance) const; void RectGetPointsExprs(ExprVector *eap, ExprVector *ebp) const; diff --git a/src/srf/curve.cpp b/src/srf/curve.cpp index b2186da2..ef30dd5c 100644 --- a/src/srf/curve.cpp +++ b/src/srf/curve.cpp @@ -231,9 +231,10 @@ void SBezierList::ScaleSelfBy(double s) { //----------------------------------------------------------------------------- // If our list contains multiple identical Beziers (in either forward or -// reverse order), then cull them. +// reverse order), then cull them. If both is true, both beziers are removed. +// Otherwise only one of them is removed. //----------------------------------------------------------------------------- -void SBezierList::CullIdenticalBeziers() { +void SBezierList::CullIdenticalBeziers(bool both) { int i, j; l.ClearTags(); @@ -247,7 +248,7 @@ void SBezierList::CullIdenticalBeziers() { if(bj->Equals(bi) || bj->Equals(&bir)) { - bi->tag = 1; + if (both) bi->tag = 1; bj->tag = 1; } } @@ -491,7 +492,7 @@ bool SBezierLoop::IsClosed() const { SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly, double chordTol, bool *allClosed, SEdge *errorAt, - SBezierList *openContours) + SBezierLoopSet *openContours) { SBezierLoopSet ret = {}; @@ -504,12 +505,10 @@ SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly, // Record open loops in a separate list, if requested. *allClosed = false; if(openContours) { - SBezier *sb; - for(sb = loop.l.First(); sb; sb = loop.l.NextAfter(sb)) { - openContours->l.Add(sb); - } + openContours->l.Add(&loop); + } else { + loop.Clear(); } - loop.Clear(); } else { ret.l.Add(&loop); poly->AddEmptyContour(); @@ -576,7 +575,7 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, double chordTol, bool *allClosed, SEdge *notClosedAt, bool *allCoplanar, Vector *notCoplanarAt, - SBezierList *openContours) + SBezierLoopSet *openContours) { SSurface srfPlane; if(!srfuv) { @@ -589,7 +588,9 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, if(openContours) { SBezier *sb; for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { - openContours->l.Add(sb); + SBezierLoop sbl={}; + sbl.l.Add(sb); + openContours->l.Add(&sbl); } } return; @@ -705,12 +706,10 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, if(loop->tag == USED_LOOP) continue; if(openContours) { - SBezier *sb; - for(sb = loop->l.First(); sb; sb = loop->l.NextAfter(sb)) { - openContours->l.Add(sb); - } + openContours->l.Add(loop); + } else { + loop->Clear(); } - loop->Clear(); // but don't free the used loops, since we shallow-copied them to // ourself } diff --git a/src/srf/surface.h b/src/srf/surface.h index 42dd8ba6..26cd7ef4 100644 --- a/src/srf/surface.h +++ b/src/srf/surface.h @@ -125,7 +125,7 @@ public: void Clear(); void ScaleSelfBy(double s); - void CullIdenticalBeziers(); + void CullIdenticalBeziers(bool both=true); void AllIntersectionsWith(SBezierList *sblb, SPointList *spl) const; bool GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v, Vector *notCoplanarAt) const; @@ -156,7 +156,7 @@ public: static SBezierLoopSet From(SBezierList *spcl, SPolygon *poly, double chordTol, bool *allClosed, SEdge *errorAt, - SBezierList *openContours); + SBezierLoopSet *openContours); void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) const; double SignedArea(); @@ -172,7 +172,7 @@ public: double chordTol, bool *allClosed, SEdge *notClosedAt, bool *allCoplanar, Vector *notCoplanarAt, - SBezierList *openContours); + SBezierLoopSet *openContours); void AddOpenPath(SBezier *sb); void Clear(); };