diff --git a/CHANGELOG.md b/CHANGELOG.md index ca6e623c..19f43ba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,15 +39,19 @@ New rendering features: or not drawing them at all. * The "Show/hide outlines" button is now independent from "Show/hide edges". -Other new features: - * New command-line interface, for batch exporting and more. +New measurement/analysis features: * New command for measuring total length of selected entities, "Analyze → Measure Perimeter". + * New command for measuring center of mass, with live updates as the sketch + changes, "Analyze → Center of Mass". + * When selecting a point and a line, projected distance to to current + workplane is displayed. + +Other new features: + * New command-line interface, for batch exporting and more. * New link to match the on-screen size of the sketch with its actual size, "view → set to full scale". * When zooming to fit, constraints are also considered. - * When selecting a point and a line, projected distance to to current - workplane is displayed. * When clicking on an entity that shares a place with other entities, the entity from the current group is selected. * When dragging an entity that shares a place with other entities, diff --git a/src/draw.cpp b/src/draw.cpp index 7d316cea..b6776173 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -770,6 +770,33 @@ void GraphicsWindow::Draw(Canvas *canvas) { canvas->DrawLine(SS.extraLine.ptA, SS.extraLine.ptB, hcsDatum); } + if(SS.centerOfMass.draw && !SS.centerOfMass.dirty) { + Vector p = SS.centerOfMass.position; + Vector u = camera.projRight; + Vector v = camera.projUp; + + const double size = 10.0; + const int subdiv = 16; + double h = Style::DefaultTextHeight() / camera.scale; + canvas->DrawVectorText(ssprintf("%.3f, %.3f, %.3f", p.x, p.y, p.z), h, + p.Plus(u.ScaledBy((size + 5.0)/scale)).Minus(v.ScaledBy(h / 2.0)), + u, v,hcsDatum); + u = u.WithMagnitude(size / scale); + v = v.WithMagnitude(size / scale); + + canvas->DrawLine(p.Minus(u), p.Plus(u), hcsDatum); + canvas->DrawLine(p.Minus(v), p.Plus(v), hcsDatum); + Vector prev; + for(int i = 0; i <= subdiv; i++) { + double a = (double)i / subdiv * 2.0 * PI; + Vector point = p.Plus(u.ScaledBy(cos(a))).Plus(v.ScaledBy(sin(a))); + if(i > 0) { + canvas->DrawLine(point, prev, hcsDatum); + } + prev = point; + } + } + // A note to indicate the origin in the just-exported file. if(SS.justExportedInfo.draw) { Vector p, u, v; diff --git a/src/generate.cpp b/src/generate.cpp index 96030bc3..00ec3708 100644 --- a/src/generate.cpp +++ b/src/generate.cpp @@ -359,6 +359,7 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) FreeAllTemporary(); allConsistent = true; SS.GW.persistentDirty = true; + SS.centerOfMass.dirty = true; endMillis = GetMilliseconds(); @@ -418,6 +419,12 @@ void SolveSpaceUI::ForceReferences() { } } +void SolveSpaceUI::UpdateCenterOfMass() { + SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); + SS.centerOfMass.position = m->GetCenterOfMass(); + SS.centerOfMass.dirty = false; +} + void SolveSpaceUI::MarkDraggedParams() { sys.dragged.Clear(); diff --git a/src/graphicswin.cpp b/src/graphicswin.cpp index 526c3283..7f229c71 100644 --- a/src/graphicswin.cpp +++ b/src/graphicswin.cpp @@ -152,6 +152,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, N_("Measure &Perimeter"), Command::PERIMETER, C|S|'P', TN, mAna }, { 1, N_("Show &Interfering Parts"), Command::INTERFERENCE, C|S|'I', TN, mAna }, { 1, N_("Show &Naked Edges"), Command::NAKED_EDGES, C|S|'N', TN, mAna }, +{ 1, N_("Show &Center of Mass"), Command::CENTER_OF_MASS, C|S|'C', TN, mAna }, { 1, NULL, Command::NONE, 0, TN, NULL }, { 1, N_("Show Degrees of &Freedom"), Command::SHOW_DOF, C|S|'F', TN, mAna }, { 1, NULL, Command::NONE, 0, TN, NULL }, @@ -793,6 +794,7 @@ void GraphicsWindow::MenuEdit(Command id) { SS.TW.HideEditControl(); SS.nakedEdges.Clear(); SS.justExportedInfo.draw = false; + SS.centerOfMass.draw = false; // This clears the marks drawn to indicate which points are // still free to drag. Param *p; diff --git a/src/groupmesh.cpp b/src/groupmesh.cpp index addac645..f62a59c4 100644 --- a/src/groupmesh.cpp +++ b/src/groupmesh.cpp @@ -421,6 +421,10 @@ void Group::GenerateDisplayItems() { // work correctly. displayMesh.PrecomputeTransparency(); + // Recalculate mass center if needed + if(SS.centerOfMass.draw && SS.centerOfMass.dirty && h.v == SS.GW.activeGroup.v) { + SS.UpdateCenterOfMass(); + } displayDirty = false; } } diff --git a/src/mesh.cpp b/src/mesh.cpp index d8bf9591..daeec4cc 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -345,6 +345,18 @@ uint32_t SMesh::FirstIntersectionWith(Point2d mp) const { return face; } +Vector SMesh::GetCenterOfMass() const { + Vector center = {}; + double vol = 0.0; + for(int i = 0; i < l.n; i++) { + STriangle &tr = l.elem[i]; + double tvol = tr.SignedVolume(); + center = center.Plus(tr.a.Plus(tr.b.Plus(tr.c)).ScaledBy(tvol / 4.0)); + vol += tvol; + } + return center.ScaledBy(1.0 / vol); +} + STriangleLl *STriangleLl::Alloc() { return (STriangleLl *)AllocTemporary(sizeof(STriangleLl)); } SKdNode *SKdNode::Alloc() diff --git a/src/polygon.cpp b/src/polygon.cpp index 20a8614f..9fc8be69 100644 --- a/src/polygon.cpp +++ b/src/polygon.cpp @@ -83,6 +83,10 @@ bool STriangle::Raytrace(const Vector &rayPoint, const Vector &rayDir, return true; } +double STriangle::SignedVolume() const { + return a.Dot(b.Cross(c)) / 6.0; +} + void STriangle::FlipNormal() { swap(a, b); swap(an, bn); diff --git a/src/polygon.h b/src/polygon.h index 072cd9c1..abb39fee 100644 --- a/src/polygon.h +++ b/src/polygon.h @@ -190,6 +190,7 @@ public: STriangle Transform(Vector o, Vector u, Vector v) const; bool Raytrace(const Vector &rayPoint, const Vector &rayDir, double *t, Vector *inters) const; + double SignedVolume() const; }; class SBsp2 { @@ -282,6 +283,8 @@ public: void RemapFaces(Group *g, int remap); uint32_t FirstIntersectionWith(Point2d mp) const; + + Vector GetCenterOfMass() const; }; // A linked list of triangles diff --git a/src/solvespace.cpp b/src/solvespace.cpp index 217f5ff3..f18c1be9 100644 --- a/src/solvespace.cpp +++ b/src/solvespace.cpp @@ -311,6 +311,7 @@ void SolveSpaceUI::AfterNewFile() { // Quit export mode justExportedInfo.draw = false; + centerOfMass.draw = false; exportMode = false; // GenerateAll() expects the view to be valid, because it uses that to @@ -616,6 +617,13 @@ void SolveSpaceUI::MenuAnalyze(Command id) { break; } + case Command::CENTER_OF_MASS: { + SS.UpdateCenterOfMass(); + SS.centerOfMass.draw = true; + InvalidateGraphics(); + break; + } + case Command::VOLUME: { SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); diff --git a/src/solvespace.h b/src/solvespace.h index e650fb84..7468eed3 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -810,6 +810,11 @@ public: bool draw, showOrigin; Vector pt, u, v; } justExportedInfo; + struct { + bool draw; + bool dirty; + Vector position; + } centerOfMass; class Clipboard { public: @@ -857,6 +862,7 @@ public: void WriteEqSystemForGroup(hGroup hg); void MarkDraggedParams(); void ForceReferences(); + void UpdateCenterOfMass(); bool ActiveGroupsOkay(); diff --git a/src/ui.h b/src/ui.h index fab86715..ee00960f 100644 --- a/src/ui.h +++ b/src/ui.h @@ -213,6 +213,7 @@ enum class Command : uint32_t { INTERFERENCE, NAKED_EDGES, SHOW_DOF, + CENTER_OF_MASS, TRACE_PT, STOP_TRACING, STEP_DIM,