diff --git a/CHANGELOG.md b/CHANGELOG.md index adab5ef1..a44126bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ New measurement/analysis features: "Analyze → Measure Perimeter". * New command for measuring center of mass, with live updates as the sketch changes, "Analyze → Center of Mass". + * New option for displaying areas of closed contours. * When selecting a point and a line, projected distance to to current workplane is displayed. diff --git a/src/confscreen.cpp b/src/confscreen.cpp index 60f53578..698aa857 100644 --- a/src/confscreen.cpp +++ b/src/confscreen.cpp @@ -84,6 +84,11 @@ void TextWindow::ScreenChangeBackFaces(int link, uint32_t v) { InvalidateGraphics(); } +void TextWindow::ScreenChangeShowContourAreas(int link, uint32_t v) { + SS.showContourAreas = !SS.showContourAreas; + InvalidateGraphics(); +} + void TextWindow::ScreenChangeCheckClosedContour(int link, uint32_t v) { SS.checkClosedContour = !SS.checkClosedContour; InvalidateGraphics(); @@ -297,6 +302,9 @@ void TextWindow::ShowConfiguration() { Printf(false, " %Fd%f%Ll%s check sketch for closed contour%E", &ScreenChangeCheckClosedContour, SS.checkClosedContour ? CHECK_TRUE : CHECK_FALSE); + Printf(false, " %Fd%f%Ll%s show areas of closed contours%E", + &ScreenChangeShowContourAreas, + SS.showContourAreas ? CHECK_TRUE : CHECK_FALSE); Printf(false, ""); Printf(false, "%Ft autosave interval (in minutes)%E"); diff --git a/src/draw.cpp b/src/draw.cpp index bbe0b2c9..d79c9bfd 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -693,6 +693,16 @@ void GraphicsWindow::Draw(Canvas *canvas) { c.Draw(Constraint::DrawAs::DEFAULT, canvas); } + // Draw areas + if(SS.showContourAreas) { + for(hGroup hg : SK.groupOrder) { + Group *g = SK.GetGroup(hg); + if(g->h.v != activeGroup.v) continue; + if(!(g->IsVisible())) continue; + g->DrawContourAreaLabels(canvas); + } + } + // Draw the "pending" constraint, i.e. a constraint that would be // placed on a line that is almost horizontal or vertical. if(SS.GW.pending.operation == Pending::DRAGGING_NEW_LINE_POINT && diff --git a/src/groupmesh.cpp b/src/groupmesh.cpp index f33dd0be..0f4bf31d 100644 --- a/src/groupmesh.cpp +++ b/src/groupmesh.cpp @@ -677,3 +677,38 @@ void Group::DrawFilledPaths(Canvas *canvas) { } } +void Group::DrawContourAreaLabels(Canvas *canvas) { + const Camera &camera = canvas->GetCamera(); + Vector gr = camera.projRight.ScaledBy(1 / camera.scale); + Vector gu = camera.projUp.ScaledBy(1 / camera.scale); + + for(SBezierLoopSet &sbls : bezierLoops.l) { + if(sbls.l.n == 0 || sbls.l.elem[0].l.n == 0) continue; + + Vector min = sbls.l.elem[0].l.elem[0].ctrl[0]; + Vector max = min; + Vector zero = Vector::From(0.0, 0.0, 0.0); + sbls.GetBoundingProjd(Vector::From(1.0, 0.0, 0.0), zero, &min.x, &max.x); + sbls.GetBoundingProjd(Vector::From(0.0, 1.0, 0.0), zero, &min.y, &max.y); + sbls.GetBoundingProjd(Vector::From(0.0, 0.0, 1.0), zero, &min.z, &max.z); + + Vector mid = min.Plus(max).ScaledBy(0.5); + + hStyle hs = { Style::CONSTRAINT }; + Canvas::Stroke stroke = Style::Stroke(hs); + stroke.layer = Canvas::Layer::FRONT; + + double scale = SS.MmPerUnit(); + std::string label = ssprintf("%.3f %s²", + fabs(sbls.SignedArea() / (scale * scale)), + SS.UnitName()); + + double fontHeight = Style::TextHeight(hs); + double textWidth = VectorFont::Builtin()->GetWidth(fontHeight, label), + textHeight = VectorFont::Builtin()->GetCapHeight(fontHeight); + Vector pos = mid.Minus(gr.ScaledBy(textWidth / 2.0)) + .Minus(gu.ScaledBy(textHeight / 2.0)); + canvas->DrawVectorText(label, fontHeight, pos, gr, gu, canvas->GetStroke(stroke)); + } +} + diff --git a/src/sketch.h b/src/sketch.h index c677dfe7..694af658 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -287,6 +287,7 @@ public: void Draw(Canvas *canvas); void DrawPolyError(Canvas *canvas); void DrawFilledPaths(Canvas *canvas); + void DrawContourAreaLabels(Canvas *canvas); SPolygon GetPolygon(); diff --git a/src/solvespace.cpp b/src/solvespace.cpp index 9021a1e5..6b1b38fc 100644 --- a/src/solvespace.cpp +++ b/src/solvespace.cpp @@ -70,6 +70,8 @@ void SolveSpaceUI::Init() { drawBackFaces = CnfThawBool(true, "DrawBackFaces"); // Check that contours are closed and not self-intersecting checkClosedContour = CnfThawBool(true, "CheckClosedContour"); + // Draw closed polygons areas + showContourAreas = CnfThawBool(false, "ShowContourAreas"); // Export shaded triangles in a 2d view exportShadedTriangles = CnfThawBool(true, "ExportShadedTriangles"); // Export pwl curves (instead of exact) always @@ -191,6 +193,8 @@ void SolveSpaceUI::Exit() { CnfFreezeBool(fixExportColors, "FixExportColors"); // Draw back faces of triangles (when mesh is leaky/self-intersecting) CnfFreezeBool(drawBackFaces, "DrawBackFaces"); + // Draw closed polygons areas + CnfFreezeBool(showContourAreas, "ShowContourAreas"); // Check that contours are closed and not self-intersecting CnfFreezeBool(checkClosedContour, "CheckClosedContour"); // Export shaded triangles in a 2d view diff --git a/src/solvespace.h b/src/solvespace.h index 3330095b..7e0c68f4 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -651,6 +651,7 @@ public: float exportOffset; bool fixExportColors; bool drawBackFaces; + bool showContourAreas; bool checkClosedContour; bool showToolbar; Platform::Path screenshotFile; diff --git a/src/srf/curve.cpp b/src/srf/curve.cpp index 5a8f1a26..b2186da2 100644 --- a/src/srf/curve.cpp +++ b/src/srf/curve.cpp @@ -536,6 +536,17 @@ void SBezierLoopSet::GetBoundingProjd(Vector u, Vector orig, } } +double SBezierLoopSet::SignedArea() { + if(EXACT(area == 0.0)) { + SPolygon sp = {}; + MakePwlInto(&sp); + sp.normal = sp.ComputeNormal(); + area = sp.SignedArea(); + sp.Clear(); + } + return area; +} + //----------------------------------------------------------------------------- // Convert all the Beziers into piecewise linear form, and assemble that into // a polygon, one contour per loop. diff --git a/src/srf/surface.h b/src/srf/surface.h index 2eae2a3c..98ad8208 100644 --- a/src/srf/surface.h +++ b/src/srf/surface.h @@ -151,6 +151,7 @@ public: List l; Vector normal; Vector point; + double area; static SBezierLoopSet From(SBezierList *spcl, SPolygon *poly, double chordTol, @@ -158,6 +159,7 @@ public: SBezierList *openContours); void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) const; + double SignedArea(); void MakePwlInto(SPolygon *sp) const; void Clear(); }; diff --git a/src/ui.h b/src/ui.h index b9e47e62..38e85390 100644 --- a/src/ui.h +++ b/src/ui.h @@ -517,6 +517,7 @@ public: static void ScreenChangeFixExportColors(int link, uint32_t v); static void ScreenChangeBackFaces(int link, uint32_t v); + static void ScreenChangeShowContourAreas(int link, uint32_t v); static void ScreenChangeCheckClosedContour(int link, uint32_t v); static void ScreenChangePwlCurves(int link, uint32_t v); static void ScreenChangeCanvasSizeAuto(int link, uint32_t v); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bb70d978..ba348a24 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,6 +11,7 @@ endforeach() set(testsuite_SOURCES harness.cpp + analysis/contour_area/test.cpp core/expr/test.cpp core/locale/test.cpp core/path/test.cpp diff --git a/test/analysis/contour_area/normal.png b/test/analysis/contour_area/normal.png new file mode 100644 index 00000000..9daf8eeb Binary files /dev/null and b/test/analysis/contour_area/normal.png differ diff --git a/test/analysis/contour_area/normal.slvs b/test/analysis/contour_area/normal.slvs new file mode 100644 index 00000000..15dc34af --- /dev/null +++ b/test/analysis/contour_area/normal.slvs @@ -0,0 +1,512 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00070010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00070014 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000007 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00070001 +Entity.point[1].v=00070002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040001 +Constraint.ptB.v=00050002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050001 +Constraint.ptB.v=00060002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00060001 +Constraint.ptB.v=00070002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000004 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00070001 +Constraint.ptB.v=00040002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000005 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000006 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000007 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000008 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00070000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/analysis/contour_area/test.cpp b/test/analysis/contour_area/test.cpp new file mode 100644 index 00000000..748f0eb0 --- /dev/null +++ b/test/analysis/contour_area/test.cpp @@ -0,0 +1,7 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + SS.showContourAreas = true; + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); +}