diff --git a/.clang-format b/.clang-format
index 30c11aa0..5cb786f0 100644
--- a/.clang-format
+++ b/.clang-format
@@ -10,22 +10,19 @@ ColumnLimit: 100
# Based on minimizing diff when applying clang-format
AccessModifierOffset: -4
-DerivePointerAlignment: true
AlignConsecutiveAssignments: true
-FixNamespaceComments: true
-NamespaceIndentation: Inner
-AllowShortLoopsOnASingleLine: true
-AlwaysBreakTemplateDeclarations: Yes # MultiLine
-SpaceAfterTemplateKeyword: false
-MaxEmptyLinesToKeep: 2
-IndentPPDirectives: AfterHash
AlignEscapedNewlines: DontAlign
-BreakConstructorInitializers: BeforeColon
+AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
-
-# This one is mixed in its effect: it permits some single-line functions,
-# but also tends to put, e.g., enums on a single line.
-AllowShortBlocksOnASingleLine: true
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakTemplateDeclarations: Yes # MultiLine
+BreakConstructorInitializers: BeforeColon
+DerivePointerAlignment: true
+FixNamespaceComments: true
+IndentPPDirectives: AfterHash
+MaxEmptyLinesToKeep: 2
+NamespaceIndentation: Inner
+SpaceAfterTemplateKeyword: false
# No way to remove all space around operators as seen in much of the code I looked at.
#SpaceBeforeAssignmentOperators: false
@@ -41,3 +38,4 @@ SortUsingDeclarations: false
# Hard to tell what the desired config here was.
# AllowShortFunctionsOnASingleLine: Inline
+AllowShortFunctionsOnASingleLine: None
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d285fef4..b7f6d7a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ Changelog
---
New sketch features:
+ * New groups, revolution and helical extrusion.
* Extrude, lathe, translate and rotate groups can use the "assembly"
boolean operation, to increase performance.
* The solid model of extrude and lathe groups can be suppressed,
@@ -52,6 +53,7 @@ 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.
+ * VRML (WRL) triangle meshes can now be exported, useful for e.g. [KiCAD](http://kicad.org).
* Export 2d section: custom styled entities that lie in the same
plane as the exported section are included.
@@ -68,6 +70,10 @@ New measurement/analysis features:
* 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 calculating volume of the mesh, volume of the solid from the current
+ group is now shown alongside total volume of all solids.
+ * When calculating area, and faces are selected, calculate area of those faces
+ instead of the closed contour in the sketch.
* When selecting a point and a line, projected distance to current
workplane is displayed.
diff --git a/extlib/q3d b/extlib/q3d
index fe715148..880db1d3 160000
--- a/extlib/q3d
+++ b/extlib/q3d
@@ -1 +1 @@
-Subproject commit fe71514837af1c627e9e1054d1aa0193a06d3488
+Subproject commit 880db1d34706778f216a2308fd82a9a3adacb314
diff --git a/pkg/snap/.gitignore b/pkg/snap/.gitignore
new file mode 100644
index 00000000..9f938db4
--- /dev/null
+++ b/pkg/snap/.gitignore
@@ -0,0 +1,3 @@
+*.snap
+solvespace-snap-src
+squashfs-root
diff --git a/pkg/snap/build.sh b/pkg/snap/build.sh
new file mode 100755
index 00000000..abd7a86b
--- /dev/null
+++ b/pkg/snap/build.sh
@@ -0,0 +1,12 @@
+#!/bin/sh -xe
+
+dir="$(dirname "$(readlink -f "$0")")"
+solvespace_snap_src="$dir/solvespace-snap-src"
+trap "rm -rf $solvespace_snap_src" EXIT
+
+cd "$dir"
+
+git_root="$(git rev-parse --show-toplevel)"
+rsync --filter=":- .gitignore" -r "$git_root"/ "$solvespace_snap_src"
+
+snapcraft "$@"
diff --git a/pkg/snap/snap/snapcraft.yaml b/pkg/snap/snap/snapcraft.yaml
new file mode 100644
index 00000000..e17ecda1
--- /dev/null
+++ b/pkg/snap/snap/snapcraft.yaml
@@ -0,0 +1,76 @@
+name: solvespace
+base: core18
+summary: Parametric 2d/3d CAD
+adopt-info: solvespace
+description: |
+ SOLVESPACE is a free (GPLv3) parametric 3d CAD tool.
+ Applications include
+ * modeling 3d parts — draw with extrudes, revolves, and Boolean (union / difference) operations
+ * modeling 2d parts — draw the part as a single section, and export DXF, PDF, SVG; use 3d assembly to verify fit
+ * 3d-printed parts — export the STL or other triangle mesh expected by most 3d printers
+ * preparing CAM data — export 2d vector art for a waterjet machine or laser cutter; or generate STEP or STL, for import into third-party CAM software for machining
+ * mechanism design — use the constraint solver to simulate planar or spatial linkages, with pin, ball, or slide joints
+ * plane and solid geometry — replace hand-solved trigonometry and spreadsheets with a live dimensioned drawing
+
+confinement: strict
+license: GPL-3.0
+
+layout:
+ /usr/share/solvespace:
+ bind: $SNAP/usr/share/solvespace
+
+apps:
+ solvespace:
+ command: usr/bin/solvespace
+ desktop: solvespace.desktop
+ extensions: [gnome-3-28]
+ plugs: [opengl, unity7, home, removable-media, gsettings, network]
+ environment:
+ __EGL_VENDOR_LIBRARY_DIRS: $SNAP/usr/share/glvnd/egl_vendor.d
+ cli:
+ command: usr/bin/solvespace-cli
+ plugs: [home, removable-media, network]
+
+parts:
+ solvespace:
+ plugin: cmake
+ source: ./solvespace-snap-src
+ source-type: local
+ override-pull: |
+ snapcraftctl pull
+ version_major=$(grep "solvespace_VERSION_MAJOR" CMakeLists.txt | tr -d "()" | cut -d" " -f2)
+ version_minor=$(grep "solvespace_VERSION_MINOR" CMakeLists.txt | tr -d "()" | cut -d" " -f2)
+ version="$version_major.$version_minor~$(git rev-parse --short=8 HEAD)"
+ snapcraftctl set-version "$version"
+ git describe --exact-match HEAD && grade="stable" || grade="devel"
+ snapcraftctl set-grade "$grade"
+ git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d
+ configflags:
+ - -DCMAKE_INSTALL_PREFIX=/usr
+ - -DCMAKE_BUILD_TYPE=Release
+ - -DENABLE_TESTS=OFF
+ - -DSNAP=ON
+ build-packages:
+ - zlib1g-dev
+ - libpng-dev
+ - libcairo2-dev
+ - libfreetype6-dev
+ - libjson-c-dev
+ - libfontconfig1-dev
+ - libgtkmm-3.0-dev
+ - libpangomm-1.4-dev
+ - libgl-dev
+ - libglu-dev
+ - libspnav-dev
+ - git
+ stage-packages:
+ - libspnav0
+ - libatkmm-1.6-1v5
+ - libcairomm-1.0-1v5
+ - libgtkmm-3.0-1v5
+ - libglibmm-2.4-1v5
+ - libpangomm-1.4-1v5
+ - libsigc++-2.0-0v5
+ - libglew2.0
+ - libegl-mesa0
+ - libdrm2
diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt
index 4c96b476..3783f242 100644
--- a/res/CMakeLists.txt
+++ b/res/CMakeLists.txt
@@ -138,6 +138,13 @@ else()
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mime/packages
RENAME com.solvespace.SolveSpace-slvs.xml)
+ install(FILES freedesktop/solvespace-scalable.svg
+ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps
+ RENAME com.solvespace.SolveSpace.svg)
+ install(FILES freedesktop/solvespace-scalable.svg
+ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/mimetypes
+ RENAME com.solvespace.SolveSpace.svg)
+
foreach(SIZE 16x16 24x24 32x32 48x48)
install(FILES freedesktop/solvespace-${SIZE}.png
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}/apps
@@ -146,6 +153,22 @@ else()
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}/mimetypes
RENAME com.solvespace.SolveSpace.png)
endforeach()
+ elseif(SNAP)
+ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/freedesktop/solvespace-snap.desktop
+ DESTINATION /
+ RENAME solvespace.desktop)
+
+ # snapd does not support registering new mime types
+
+ install(FILES freedesktop/solvespace-scalable.svg
+ DESTINATION /meta/icons/hicolor/scalable/apps
+ RENAME snap.solvespace.svg)
+
+ foreach(SIZE 16x16 24x24 32x32 48x48)
+ install(FILES freedesktop/solvespace-${SIZE}.png
+ DESTINATION /meta/icons/hicolor/${SIZE}/apps
+ RENAME snap.solvespace.png)
+ endforeach()
else()
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/freedesktop/solvespace.desktop.in
@@ -156,6 +179,13 @@ else()
install(FILES freedesktop/solvespace-mime.xml
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mime/packages
RENAME solvespace-slvs.xml)
+
+ install(FILES freedesktop/solvespace-scalable.svg
+ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps
+ RENAME solvespace.svg)
+ install(FILES freedesktop/solvespace-scalable.svg
+ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/mimetypes
+ RENAME application.x-solvespace.svg)
foreach(SIZE 16x16 24x24 32x32 48x48)
install(FILES freedesktop/solvespace-${SIZE}.png
diff --git a/res/freedesktop/solvespace-scalable.svg b/res/freedesktop/solvespace-scalable.svg
new file mode 100644
index 00000000..7be996e0
--- /dev/null
+++ b/res/freedesktop/solvespace-scalable.svg
@@ -0,0 +1,12 @@
+
+
diff --git a/res/freedesktop/solvespace-snap.desktop b/res/freedesktop/solvespace-snap.desktop
new file mode 100644
index 00000000..8441258c
--- /dev/null
+++ b/res/freedesktop/solvespace-snap.desktop
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Version=1.0
+Name=SolveSpace
+Comment=A parametric 2d/3d CAD
+Exec=solvespace
+MimeType=application/x-solvespace
+Icon=${SNAP}/meta/icons/hicolor/scalable/apps/snap.solvespace.svg
+Type=Application
+Categories=Graphics
+Keywords=parametric;cad;2d;3d;
diff --git a/src/bsp.cpp b/src/bsp.cpp
index 2a9ce612..9dcaace9 100644
--- a/src/bsp.cpp
+++ b/src/bsp.cpp
@@ -12,26 +12,19 @@ SBsp2 *SBsp2::Alloc() { return (SBsp2 *)AllocTemporary(sizeof(SBsp2)); }
SBsp3 *SBsp3::Alloc() { return (SBsp3 *)AllocTemporary(sizeof(SBsp3)); }
SBsp3 *SBsp3::FromMesh(const SMesh *m) {
- SBsp3 *bsp3 = NULL;
- int i;
-
SMesh mc = {};
- for(i = 0; i < m->l.n; i++) {
- mc.AddTriangle(&(m->l.elem[i]));
- }
+ for(auto const &elt : m->l) { mc.AddTriangle(&elt); }
srand(0); // Let's be deterministic, at least!
int n = mc.l.n;
while(n > 1) {
int k = rand() % n;
n--;
- swap(mc.l.elem[k], mc.l.elem[n]);
- }
-
- for(i = 0; i < mc.l.n; i++) {
- bsp3 = InsertOrCreate(bsp3, &(mc.l.elem[i]), NULL);
+ swap(mc.l[k], mc.l[n]);
}
+ SBsp3 *bsp3 = NULL;
+ for(auto &elt : mc.l) { bsp3 = InsertOrCreate(bsp3, &elt, NULL); }
mc.Clear();
return bsp3;
}
diff --git a/src/clipboard.cpp b/src/clipboard.cpp
index 24a63a00..011b658d 100644
--- a/src/clipboard.cpp
+++ b/src/clipboard.cpp
@@ -12,16 +12,16 @@ void SolveSpaceUI::Clipboard::Clear() {
}
bool SolveSpaceUI::Clipboard::ContainsEntity(hEntity he) {
- if(he.v == Entity::NO_ENTITY.v)
+ if(he == Entity::NO_ENTITY)
return true;
ClipboardRequest *cr;
for(cr = r.First(); cr; cr = r.NextAfter(cr)) {
- if(cr->oldEnt.v == he.v)
+ if(cr->oldEnt == he)
return true;
for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) {
- if(cr->oldPointEnt[i].v == he.v)
+ if(cr->oldPointEnt[i] == he)
return true;
}
}
@@ -29,16 +29,16 @@ bool SolveSpaceUI::Clipboard::ContainsEntity(hEntity he) {
}
hEntity SolveSpaceUI::Clipboard::NewEntityFor(hEntity he) {
- if(he.v == Entity::NO_ENTITY.v)
+ if(he == Entity::NO_ENTITY)
return Entity::NO_ENTITY;
ClipboardRequest *cr;
for(cr = r.First(); cr; cr = r.NextAfter(cr)) {
- if(cr->oldEnt.v == he.v)
+ if(cr->oldEnt == he)
return cr->newReq.entity(0);
for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) {
- if(cr->oldPointEnt[i].v == he.v)
+ if(cr->oldPointEnt[i] == he)
return cr->newReq.entity(1+i);
}
}
@@ -170,9 +170,9 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
hRequest hr = he.request();
Request *r = SK.GetRequest(hr);
if(r->type == Request::Type::ARC_OF_CIRCLE) {
- if(he.v == hr.entity(2).v) {
+ if(he == hr.entity(2)) {
return hr.entity(3);
- } else if(he.v == hr.entity(3).v) {
+ } else if(he == hr.entity(3)) {
return hr.entity(2);
}
}
@@ -287,7 +287,7 @@ void GraphicsWindow::MenuClipboard(Command id) {
}
case Command::PASTE_TRANSFORM: {
- if(SS.clipboard.r.n == 0) {
+ if(SS.clipboard.r.IsEmpty()) {
Error(_("Clipboard is empty; nothing to paste."));
break;
}
diff --git a/src/constraint.cpp b/src/constraint.cpp
index 083a48f7..8ac09d49 100644
--- a/src/constraint.cpp
+++ b/src/constraint.cpp
@@ -59,12 +59,12 @@ std::string Constraint::DescriptionString() const {
void Constraint::DeleteAllConstraintsFor(Constraint::Type type, hEntity entityA, hEntity ptA)
{
SK.constraint.ClearTags();
- for(int i = 0; i < SK.constraint.n; i++) {
- ConstraintBase *ct = &(SK.constraint.elem[i]);
+ for(auto &constraint : SK.constraint) {
+ ConstraintBase *ct = &constraint;
if(ct->type != type) continue;
- if(ct->entityA.v != entityA.v) continue;
- if(ct->ptA.v != ptA.v) continue;
+ if(ct->entityA != entityA) continue;
+ if(ct->ptA != ptA) continue;
ct->tag = 1;
}
SK.constraint.RemoveTagged();
@@ -406,7 +406,7 @@ void Constraint::MenuConstrain(Command id) {
Entity *l0 = SK.GetEntity(gs.entity[0]),
*l1 = SK.GetEntity(gs.entity[1]);
- if((l1->group.v != SS.GW.activeGroup.v) ||
+ if((l1->group != SS.GW.activeGroup) ||
(l1->construction && !(l0->construction)))
{
swap(l0, l1);
@@ -433,10 +433,10 @@ void Constraint::MenuConstrain(Command id) {
"(symmetric about workplane)\n"));
return;
}
- if(c.entityA.v == Entity::NO_ENTITY.v) {
+ if(c.entityA == Entity::NO_ENTITY) {
// Horizontal / vertical symmetry, implicit symmetry plane
// normal to the workplane
- if(c.workplane.v == Entity::FREE_IN_3D.v) {
+ if(c.workplane == Entity::FREE_IN_3D) {
Error(_("A workplane must be active when constraining "
"symmetric without an explicit symmetry plane."));
return;
@@ -466,7 +466,7 @@ void Constraint::MenuConstrain(Command id) {
case Command::VERTICAL:
case Command::HORIZONTAL: {
hEntity ha, hb;
- if(c.workplane.v == Entity::FREE_IN_3D.v) {
+ if(c.workplane == Entity::FREE_IN_3D) {
Error(_("Activate a workplane (with Sketch -> In Workplane) before "
"applying a horizontal or vertical constraint."));
return;
@@ -510,12 +510,10 @@ void Constraint::MenuConstrain(Command id) {
Entity *nfree = SK.GetEntity(c.entityA);
Entity *nref = SK.GetEntity(c.entityB);
- if(nref->group.v == SS.GW.activeGroup.v) {
+ if(nref->group == SS.GW.activeGroup) {
swap(nref, nfree);
}
- if(nfree->group.v == SS.GW.activeGroup.v &&
- nref ->group.v != SS.GW.activeGroup.v)
- {
+ if(nfree->group == SS.GW.activeGroup && nref->group != SS.GW.activeGroup) {
// nfree is free, and nref is locked (since it came from a
// previous group); so let's force nfree aligned to nref,
// and make convergence easy
@@ -746,7 +744,7 @@ void Constraint::MenuConstrain(Command id) {
}
for(const Constraint &cc : SK.constraint) {
- if(c.h.v != cc.h.v && c.Equals(cc)) {
+ if(c.h != cc.h && c.Equals(cc)) {
// Oops, we already have this exact constraint. Remove the one we just added.
SK.constraint.RemoveById(c.h);
SS.GW.ClearSelection();
diff --git a/src/constrainteq.cpp b/src/constrainteq.cpp
index 86ed312f..985545c7 100644
--- a/src/constrainteq.cpp
+++ b/src/constrainteq.cpp
@@ -40,7 +40,7 @@ Expr *ConstraintBase::PointLineDistance(hEntity wrkpl, hEntity hpt, hEntity hln)
EntityBase *p = SK.GetEntity(hpt);
- if(wrkpl.v == EntityBase::FREE_IN_3D.v) {
+ if(wrkpl == EntityBase::FREE_IN_3D) {
ExprVector ep = p->PointGetExprs();
ExprVector ea = a->PointGetExprs();
@@ -82,7 +82,7 @@ Expr *ConstraintBase::Distance(hEntity wrkpl, hEntity hpa, hEntity hpb) {
ssassert(pa->IsPoint() && pb->IsPoint(),
"Expected two points to measure projected distance between");
- if(wrkpl.v == EntityBase::FREE_IN_3D.v) {
+ if(wrkpl == EntityBase::FREE_IN_3D) {
// This is true distance
ExprVector ea, eb, eab;
ea = pa->PointGetExprs();
@@ -111,7 +111,7 @@ Expr *ConstraintBase::Distance(hEntity wrkpl, hEntity hpa, hEntity hpb) {
Expr *ConstraintBase::DirectionCosine(hEntity wrkpl,
ExprVector ae, ExprVector be)
{
- if(wrkpl.v == EntityBase::FREE_IN_3D.v) {
+ if(wrkpl == EntityBase::FREE_IN_3D) {
Expr *mags = (ae.Magnitude())->Times(be.Magnitude());
return (ae.Dot(be))->Div(mags);
} else {
@@ -146,7 +146,7 @@ void ConstraintBase::ModifyToSatisfy() {
Vector a = SK.GetEntity(entityA)->VectorGetNum();
Vector b = SK.GetEntity(entityB)->VectorGetNum();
if(other) a = a.ScaledBy(-1);
- if(workplane.v != EntityBase::FREE_IN_3D.v) {
+ if(workplane != EntityBase::FREE_IN_3D) {
a = a.ProjectVectorInto(workplane);
b = b.ProjectVectorInto(workplane);
}
@@ -172,7 +172,7 @@ void ConstraintBase::ModifyToSatisfy() {
// These equations are written in the form f(...) - d = 0, where
// d is the value of the valA.
- valA += (l.elem[0].e)->Eval();
+ valA += (l[0].e)->Eval();
l.Clear();
}
@@ -190,7 +190,7 @@ void ConstraintBase::AddEq(IdList *l, const ExprVector &v,
int baseIndex) const {
AddEq(l, v.x, baseIndex);
AddEq(l, v.y, baseIndex + 1);
- if(workplane.v == EntityBase::FREE_IN_3D.v) {
+ if(workplane == EntityBase::FREE_IN_3D) {
AddEq(l, v.z, baseIndex + 2);
}
}
@@ -200,7 +200,7 @@ void ConstraintBase::Generate(IdList *l) {
case Type::PARALLEL:
case Type::CUBIC_LINE_TANGENT:
// Add new parameter only when we operate in 3d space
- if(workplane.v != EntityBase::FREE_IN_3D.v) break;
+ if(workplane != EntityBase::FREE_IN_3D) break;
// fallthrough
case Type::SAME_ORIENTATION:
case Type::PT_ON_LINE: {
@@ -361,7 +361,7 @@ void ConstraintBase::GenerateEquations(IdList *l,
case Type::POINTS_COINCIDENT: {
EntityBase *a = SK.GetEntity(ptA);
EntityBase *b = SK.GetEntity(ptB);
- if(workplane.v == EntityBase::FREE_IN_3D.v) {
+ if(workplane == EntityBase::FREE_IN_3D) {
ExprVector pa = a->PointGetExprs();
ExprVector pb = b->PointGetExprs();
AddEq(l, pa.x->Minus(pb.x), 0);
@@ -430,7 +430,7 @@ void ConstraintBase::GenerateEquations(IdList *l,
}
case Type::AT_MIDPOINT:
- if(workplane.v == EntityBase::FREE_IN_3D.v) {
+ if(workplane == EntityBase::FREE_IN_3D) {
EntityBase *ln = SK.GetEntity(entityA);
ExprVector a = SK.GetEntity(ln->point[0])->PointGetExprs();
ExprVector b = SK.GetEntity(ln->point[1])->PointGetExprs();
@@ -469,7 +469,7 @@ void ConstraintBase::GenerateEquations(IdList *l,
return;
case Type::SYMMETRIC:
- if(workplane.v == EntityBase::FREE_IN_3D.v) {
+ if(workplane == EntityBase::FREE_IN_3D) {
EntityBase *plane = SK.GetEntity(entityA);
EntityBase *ea = SK.GetEntity(ptA);
EntityBase *eb = SK.GetEntity(ptB);
@@ -521,7 +521,7 @@ void ConstraintBase::GenerateEquations(IdList *l,
case Type::SYMMETRIC_HORIZ:
case Type::SYMMETRIC_VERT: {
- ssassert(workplane.v != Entity::FREE_IN_3D.v,
+ ssassert(workplane != Entity::FREE_IN_3D,
"Unexpected horizontal/vertical symmetric constraint in 3d");
EntityBase *a = SK.GetEntity(ptA);
@@ -576,7 +576,7 @@ void ConstraintBase::GenerateEquations(IdList *l,
case Type::HORIZONTAL:
case Type::VERTICAL: {
- ssassert(workplane.v != Entity::FREE_IN_3D.v,
+ ssassert(workplane != Entity::FREE_IN_3D,
"Unexpected horizontal/vertical constraint in 3d");
hEntity ha, hb;
@@ -701,7 +701,7 @@ void ConstraintBase::GenerateEquations(IdList *l,
ExprVector b = line->VectorGetExprs();
- if(workplane.v == EntityBase::FREE_IN_3D.v) {
+ if(workplane == EntityBase::FREE_IN_3D) {
ExprVector eq = VectorsParallel3d(a, b, valP);
AddEq(l, eq);
} else {
@@ -755,7 +755,7 @@ void ConstraintBase::GenerateEquations(IdList *l,
ExprVector a = ea->VectorGetExprsInWorkplane(workplane);
ExprVector b = eb->VectorGetExprsInWorkplane(workplane);
- if(workplane.v == EntityBase::FREE_IN_3D.v) {
+ if(workplane == EntityBase::FREE_IN_3D) {
ExprVector eq = VectorsParallel3d(a, b, valP);
AddEq(l, eq);
} else {
@@ -775,7 +775,7 @@ void ConstraintBase::GenerateEquations(IdList *l,
case Type::WHERE_DRAGGED: {
EntityBase *ep = SK.GetEntity(ptA);
- if(workplane.v == EntityBase::FREE_IN_3D.v) {
+ if(workplane == EntityBase::FREE_IN_3D) {
ExprVector ev = ep->PointGetExprs();
Vector v = ep->PointGetNum();
diff --git a/src/describescreen.cpp b/src/describescreen.cpp
index 454cedfb..4ebe20e5 100644
--- a/src/describescreen.cpp
+++ b/src/describescreen.cpp
@@ -35,7 +35,7 @@ void TextWindow::ScreenSetTtfFont(int link, uint32_t v) {
if(!r) return;
SS.UndoRemember();
- r->font = SS.fonts.l.elem[i].FontFileBaseName();
+ r->font = SS.fonts.l[i].FontFileBaseName();
SS.MarkGroupDirty(r->group);
SS.ScheduleShowTW();
}
@@ -175,9 +175,9 @@ void TextWindow::DescribeSelection() {
e->str.c_str(), &ScreenEditTtfText, e->h.request().v);
Printf(true, " select new font");
SS.fonts.LoadAll();
- int i;
- for(i = 0; i < SS.fonts.l.n; i++) {
- TtfFont *tf = &(SS.fonts.l.elem[i]);
+ // Not using range-for here because we use i inside the output.
+ for(int i = 0; i < SS.fonts.l.n; i++) {
+ TtfFont *tf = &(SS.fonts.l[i]);
if(e->font == tf->FontFileBaseName()) {
Printf(false, "%Bp %s",
(i & 1) ? 'd' : 'a',
@@ -214,7 +214,7 @@ void TextWindow::DescribeSelection() {
Group *g = SK.GetGroup(e->group);
Printf(false, "");
Printf(false, "%FtIN GROUP%E %s", g->DescriptionString().c_str());
- if(e->workplane.v == Entity::FREE_IN_3D.v) {
+ if(e->workplane == Entity::FREE_IN_3D) {
Printf(false, "%FtNOT LOCKED IN WORKPLANE%E");
} else {
Entity *w = SK.GetEntity(e->workplane);
@@ -232,12 +232,13 @@ void TextWindow::DescribeSelection() {
std::vector lhc = {};
for(const Constraint &c : SK.constraint) {
- if(!(c.ptA.v == e->h.v ||
- c.ptB.v == e->h.v ||
- c.entityA.v == e->h.v ||
- c.entityB.v == e->h.v ||
- c.entityC.v == e->h.v ||
- c.entityD.v == e->h.v)) continue;
+ if(!(c.ptA == e->h ||
+ c.ptB == e->h ||
+ c.entityA == e->h ||
+ c.entityB == e->h ||
+ c.entityC == e->h ||
+ c.entityD == e->h))
+ continue;
lhc.push_back(c.h);
}
@@ -314,8 +315,7 @@ void TextWindow::DescribeSelection() {
Printf(true, " pt-ln distance = %Fi%s%E",
SS.MmToString(pp.DistanceToLine(lp0, lp1.Minus(lp0))).c_str());
hEntity wrkpl = SS.GW.ActiveWorkplane();
- if(wrkpl.v != Entity::FREE_IN_3D.v &&
- !(p->workplane.v == wrkpl.v && ln->workplane.v == wrkpl.v)) {
+ if(wrkpl != Entity::FREE_IN_3D && !(p->workplane == wrkpl && ln->workplane == wrkpl)) {
Vector ppw = pp.ProjectInto(wrkpl);
Vector lp0w = lp0.ProjectInto(wrkpl);
Vector lp1w = lp1.ProjectInto(wrkpl);
@@ -385,10 +385,9 @@ void TextWindow::DescribeSelection() {
lhe.push_back(c->entityC);
lhe.push_back(c->entityD);
- auto it = std::remove_if(lhe.begin(), lhe.end(),
- [](hEntity he) {
- return he.v == Entity::NO_ENTITY.v || !he.isFromRequest();
- });
+ auto it = std::remove_if(lhe.begin(), lhe.end(), [](hEntity he) {
+ return he == Entity::NO_ENTITY || !he.isFromRequest();
+ });
lhe.erase(it, lhe.end());
if(!lhe.empty()) {
diff --git a/src/draw.cpp b/src/draw.cpp
index b9ddcb45..ee3966b2 100644
--- a/src/draw.cpp
+++ b/src/draw.cpp
@@ -8,8 +8,8 @@
#include "solvespace.h"
bool GraphicsWindow::Selection::Equals(Selection *b) {
- if(entity.v != b->entity.v) return false;
- if(constraint.v != b->constraint.v) return false;
+ if(entity != b->entity) return false;
+ if(constraint != b->constraint) return false;
return true;
}
@@ -151,7 +151,8 @@ void GraphicsWindow::MakeUnselected(Selection *stog, bool coincidentPointTrick){
Vector ep = e->PointGetNum();
for(s = selection.First(); s; s = selection.NextAfter(s)) {
if(!s->entity.v) continue;
- if(s->entity.v == stog->entity.v) continue;
+ if(s->entity == stog->entity)
+ continue;
Entity *se = SK.GetEntity(s->entity);
if(!se->IsPoint()) continue;
if(ep.Equals(se->PointGetNum())) {
@@ -211,7 +212,7 @@ void GraphicsWindow::SelectByMarquee() {
Entity *e;
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
- if(e->group.v != SS.GW.activeGroup.v) continue;
+ if(e->group != SS.GW.activeGroup) continue;
if(e->IsFace() || e->IsDistance()) continue;
if(!e->IsVisible()) continue;
@@ -232,7 +233,7 @@ void GraphicsWindow::GroupSelection() {
gs = {};
int i;
for(i = 0; i < selection.n; i++) {
- Selection *s = &(selection.elem[i]);
+ Selection *s = &(selection[i]);
if(s->entity.v) {
(gs.n)++;
@@ -328,7 +329,8 @@ Lighting GraphicsWindow::GetLighting() const {
GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() {
Selection sel = {};
- if(hoverList.n == 0) return sel;
+ if(hoverList.IsEmpty())
+ return sel;
Group *activeGroup = SK.GetGroup(SS.GW.activeGroup);
int bestOrder = -1;
@@ -701,7 +703,7 @@ void GraphicsWindow::Draw(Canvas *canvas) {
if(SS.showContourAreas) {
for(hGroup hg : SK.groupOrder) {
Group *g = SK.GetGroup(hg);
- if(g->h.v != activeGroup.v) continue;
+ if(g->h != activeGroup) continue;
if(!(g->IsVisible())) continue;
g->DrawContourAreaLabels(canvas);
}
diff --git a/src/drawconstraint.cpp b/src/drawconstraint.cpp
index 9beb9ead..2cdb9afc 100644
--- a/src/drawconstraint.cpp
+++ b/src/drawconstraint.cpp
@@ -148,7 +148,7 @@ int Constraint::DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs,
}
if(j < 4) continue;
- double t = (p.Minus(a)).DivPivoting(dl);
+ double t = (p.Minus(a)).DivProjected(dl);
tmin = min(t, tmin);
tmax = max(t, tmax);
}
@@ -298,7 +298,7 @@ void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs,
Vector gr = camera.projRight.ScaledBy(1.0);
Vector gu = camera.projUp.ScaledBy(1.0);
- if(workplane.v != Entity::FREE_IN_3D.v) {
+ if(workplane != Entity::FREE_IN_3D) {
a0 = a0.ProjectInto(workplane);
b0 = b0.ProjectInto(workplane);
da = da.ProjectVectorInto(workplane);
@@ -452,7 +452,7 @@ bool Constraint::IsVisible() const {
if(!(g->visible)) return false;
// And likewise if the group is not the active group; except for comments
// with an assigned style.
- if(g->h.v != SS.GW.activeGroup.v && !(type == Type::COMMENT && disp.style.v)) {
+ if(g->h != SS.GW.activeGroup && !(type == Type::COMMENT && disp.style.v)) {
return false;
}
if(disp.style.v) {
@@ -529,7 +529,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
Vector ap = SK.GetEntity(ptA)->PointGetNum();
Vector bp = SK.GetEntity(ptB)->PointGetNum();
- if(workplane.v != Entity::FREE_IN_3D.v) {
+ if(workplane != Entity::FREE_IN_3D) {
DoProjectedPoint(canvas, hcs, &ap);
DoProjectedPoint(canvas, hcs, &bp);
}
@@ -596,7 +596,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
Vector lB = SK.GetEntity(line->point[1])->PointGetNum();
Vector dl = lB.Minus(lA);
- if(workplane.v != Entity::FREE_IN_3D.v) {
+ if(workplane != Entity::FREE_IN_3D) {
lA = lA.ProjectInto(workplane);
lB = lB.ProjectInto(workplane);
DoProjectedPoint(canvas, hcs, &pt);
@@ -638,11 +638,11 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
}
}
- if(workplane.v != Entity::FREE_IN_3D.v) {
+ if(workplane != Entity::FREE_IN_3D) {
// Draw the projection marker from the closest point on the
// projected line to the projected point on the real line.
Vector lAB = (lA.Minus(lB));
- double t = (lA.Minus(closest)).DivPivoting(lAB);
+ double t = (lA.Minus(closest)).DivProjected(lAB);
Vector lA = SK.GetEntity(line->point[0])->PointGetNum();
Vector lB = SK.GetEntity(line->point[1])->PointGetNum();
@@ -829,7 +829,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
case Type::PERPENDICULAR: {
Vector u = Vector::From(0, 0, 0), v = Vector::From(0, 0, 0);
Vector rn, ru;
- if(workplane.v == Entity::FREE_IN_3D.v) {
+ if(workplane == Entity::FREE_IN_3D) {
rn = gn;
ru = gu;
} else {
@@ -882,7 +882,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
v = norm->NormalV();
} else if(type == Type::CUBIC_LINE_TANGENT) {
Vector n;
- if(workplane.v == Entity::FREE_IN_3D.v) {
+ if(workplane == Entity::FREE_IN_3D) {
u = gr;
v = gu;
n = gn;
@@ -985,7 +985,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
a = SK.GetEntity(e->point[0])->PointGetNum();
b = SK.GetEntity(e->point[1])->PointGetNum();
- if(workplane.v != Entity::FREE_IN_3D.v) {
+ if(workplane != Entity::FREE_IN_3D) {
DoProjectedPoint(canvas, hcs, &a);
DoProjectedPoint(canvas, hcs, &b);
}
@@ -1005,7 +1005,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
Entity *forLen = SK.GetEntity(entityA);
Vector a = SK.GetEntity(forLen->point[0])->PointGetNum(),
b = SK.GetEntity(forLen->point[1])->PointGetNum();
- if(workplane.v != Entity::FREE_IN_3D.v) {
+ if(workplane != Entity::FREE_IN_3D) {
DoProjectedPoint(canvas, hcs, &a);
DoProjectedPoint(canvas, hcs, &b);
}
@@ -1017,7 +1017,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
Vector la = SK.GetEntity(ln->point[0])->PointGetNum(),
lb = SK.GetEntity(ln->point[1])->PointGetNum();
Vector pt = SK.GetEntity(ptA)->PointGetNum();
- if(workplane.v != Entity::FREE_IN_3D.v) {
+ if(workplane != Entity::FREE_IN_3D) {
DoProjectedPoint(canvas, hcs, &pt);
la = la.ProjectInto(workplane);
lb = lb.ProjectInto(workplane);
@@ -1039,7 +1039,7 @@ void Constraint::DoLayout(DrawAs how, Canvas *canvas,
Entity *pte = SK.GetEntity(i == 0 ? ptA : ptB);
Vector pt = pte->PointGetNum();
- if(workplane.v != Entity::FREE_IN_3D.v) {
+ if(workplane != Entity::FREE_IN_3D) {
DoProjectedPoint(canvas, hcs, &pt);
la = la.ProjectInto(workplane);
lb = lb.ProjectInto(workplane);
@@ -1104,7 +1104,7 @@ s:
case Type::VERTICAL:
if(entityA.v) {
Vector r, u, n;
- if(workplane.v == Entity::FREE_IN_3D.v) {
+ if(workplane == Entity::FREE_IN_3D) {
r = gr; u = gu; n = gn;
} else {
r = SK.GetEntity(workplane)->Normal()->NormalU();
@@ -1171,7 +1171,7 @@ s:
case Type::COMMENT: {
Vector u, v;
- if(workplane.v == Entity::FREE_IN_3D.v) {
+ if(workplane == Entity::FREE_IN_3D) {
u = gr;
v = gu;
} else {
diff --git a/src/drawentity.cpp b/src/drawentity.cpp
index 0a1d0cd7..de93d730 100644
--- a/src/drawentity.cpp
+++ b/src/drawentity.cpp
@@ -20,32 +20,31 @@ std::string Entity::DescriptionString() const {
void Entity::GenerateEdges(SEdgeList *el) {
SBezierList *sbl = GetOrGenerateBezierCurves();
- int i, j;
- for(i = 0; i < sbl->l.n; i++) {
- SBezier *sb = &(sbl->l.elem[i]);
+ for(int i = 0; i < sbl->l.n; i++) {
+ SBezier *sb = &(sbl->l[i]);
List lv = {};
sb->MakePwlInto(&lv);
- for(j = 1; j < lv.n; j++) {
- el->AddEdge(lv.elem[j-1], lv.elem[j], style.v, i);
+ for(int j = 1; j < lv.n; j++) {
+ el->AddEdge(lv[j-1], lv[j], style.v, i);
}
lv.Clear();
}
}
SBezierList *Entity::GetOrGenerateBezierCurves() {
- if(beziers.l.n == 0)
+ if(beziers.l.IsEmpty())
GenerateBezierCurves(&beziers);
return &beziers;
}
SEdgeList *Entity::GetOrGenerateEdges() {
- if(edges.l.n != 0) {
+ if(!edges.l.IsEmpty()) {
if(EXACT(edgesChordTol == SS.ChordTolMm()))
return &edges;
edges.l.Clear();
}
- if(edges.l.n == 0)
+ if(edges.l.IsEmpty())
GenerateEdges(&edges);
edgesChordTol = SS.ChordTolMm();
return &edges;
@@ -55,7 +54,7 @@ BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) {
SBezierList *sbl = GetOrGenerateBezierCurves();
// We don't bother with bounding boxes for workplanes, etc.
- *hasBBox = (IsPoint() || IsNormal() || sbl->l.n > 0);
+ *hasBBox = (IsPoint() || IsNormal() || !sbl->l.IsEmpty());
if(!*hasBBox) return {};
if(screenBBoxValid)
@@ -67,16 +66,14 @@ BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) {
} else if(IsNormal()) {
Vector proj = SK.GetEntity(point[0])->PointGetNum();
screenBBox = BBox::From(proj, proj);
- } else if(sbl->l.n > 0) {
- Vector first = SS.GW.ProjectPoint3(sbl->l.elem[0].ctrl[0]);
+ } else if(!sbl->l.IsEmpty()) {
+ Vector first = SS.GW.ProjectPoint3(sbl->l[0].ctrl[0]);
screenBBox = BBox::From(first, first);
- for(int i = 0; i < sbl->l.n; i++) {
- SBezier *sb = &sbl->l.elem[i];
- for(int i = 0; i <= sb->deg; i++) {
- screenBBox.Include(SS.GW.ProjectPoint3(sb->ctrl[i]));
- }
+ for(auto &sb : sbl->l) {
+ for(int i = 0; i < sb.deg; ++i) { screenBBox.Include(SS.GW.ProjectPoint3(sb.ctrl[i])); }
}
- } else ssassert(false, "Expected entity to be a point or have beziers");
+ } else
+ ssassert(false, "Expected entity to be a point or have beziers");
screenBBoxValid = true;
return screenBBox;
@@ -88,6 +85,7 @@ void Entity::GetReferencePoints(std::vector *refs) {
case Type::POINT_N_TRANS:
case Type::POINT_N_ROT_TRANS:
case Type::POINT_N_ROT_AA:
+ case Type::POINT_N_ROT_AXIS_TRANS:
case Type::POINT_IN_3D:
case Type::POINT_IN_2D:
refs->push_back(PointGetNum());
@@ -150,7 +148,7 @@ bool Entity::IsStylable() const {
bool Entity::IsVisible() const {
Group *g = SK.GetGroup(group);
- if(g->h.v == Group::HGROUP_REFERENCES.v && IsNormal()) {
+ if(g->h == Group::HGROUP_REFERENCES && IsNormal()) {
// The reference normals are always shown
return true;
}
@@ -162,7 +160,7 @@ bool Entity::IsVisible() const {
if(!SS.GW.showWorkplanes) {
if(IsWorkplane() && !h.isFromRequest()) {
- if(g->h.v != SS.GW.activeGroup.v) {
+ if(g->h != SS.GW.activeGroup) {
// The group-associated workplanes are hidden outside
// their group.
return false;
@@ -440,7 +438,7 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) const {
// Record our style for all of the Beziers that we just created.
for(; i < sbl->l.n; i++) {
- sbl->l.elem[i].auxA = style.v;
+ sbl->l[i].auxA = style.v;
}
}
@@ -453,7 +451,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
zIndex = 5;
} else if(how == DrawAs::HIDDEN) {
zIndex = 2;
- } else if(group.v != SS.GW.activeGroup.v) {
+ } else if(group != SS.GW.activeGroup) {
zIndex = 3;
} else {
zIndex = 4;
@@ -502,6 +500,7 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
case Type::POINT_N_TRANS:
case Type::POINT_N_ROT_TRANS:
case Type::POINT_N_ROT_AA:
+ case Type::POINT_N_ROT_AXIS_TRANS:
case Type::POINT_IN_3D:
case Type::POINT_IN_2D: {
if(how == DrawAs::HIDDEN) return;
@@ -568,11 +567,11 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
// dimmer for the ones at the model origin.
hRequest hr = h.request();
uint8_t luma = (asReference) ? 255 : 100;
- if(hr.v == Request::HREQUEST_REFERENCE_XY.v) {
+ if(hr == Request::HREQUEST_REFERENCE_XY) {
stroke.color = RgbaColor::From(0, 0, luma);
- } else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) {
+ } else if(hr == Request::HREQUEST_REFERENCE_YZ) {
stroke.color = RgbaColor::From(luma, 0, 0);
- } else if(hr.v == Request::HREQUEST_REFERENCE_ZX.v) {
+ } else if(hr == Request::HREQUEST_REFERENCE_ZX) {
stroke.color = RgbaColor::From(0, luma, 0);
}
}
@@ -587,6 +586,11 @@ void Entity::Draw(DrawAs how, Canvas *canvas) {
double s = camera.scale;
double h = 60 - camera.height / 2.0;
double w = 60 - camera.width / 2.0;
+ // Shift the axis to the right if they would overlap with the toolbar.
+ if(SS.showToolbar) {
+ if(h + 30 > -(34*16 + 3*16 + 8) / 2)
+ w += 60;
+ }
tail = camera.projRight.ScaledBy(w/s).Plus(
camera.projUp. ScaledBy(h/s)).Minus(camera.offset);
} else {
diff --git a/src/dsc.h b/src/dsc.h
index 56c61674..087a664f 100644
--- a/src/dsc.h
+++ b/src/dsc.h
@@ -9,6 +9,34 @@
#include "solvespace.h"
+#include
+
+/// Trait indicating which types are handle types and should get the associated operators.
+/// Specialize for each handle type and inherit from std::true_type.
+template
+struct IsHandleOracle : std::false_type {};
+
+// Equality-compare any two instances of a handle type.
+template
+static inline typename std::enable_if::value, bool>::type
+operator==(T const &lhs, T const &rhs) {
+ return lhs.v == rhs.v;
+}
+
+// Inequality-compare any two instances of a handle type.
+template
+static inline typename std::enable_if::value, bool>::type
+operator!=(T const &lhs, T const &rhs) {
+ return !(lhs == rhs);
+}
+
+// Less-than-compare any two instances of a handle type.
+template
+static inline typename std::enable_if::value, bool>::type
+operator<(T const &lhs, T const &rhs) {
+ return lhs.v < rhs.v;
+}
+
class Vector;
class Vector4;
class Point2d;
@@ -92,7 +120,7 @@ public:
Vector ScaledBy(double s) const;
Vector ProjectInto(hEntity wrkpl) const;
Vector ProjectVectorInto(hEntity wrkpl) const;
- double DivPivoting(Vector delta) const;
+ double DivProjected(Vector delta) const;
Vector ClosestOrtho() const;
void MakeMaxMin(Vector *maxv, Vector *minv) const;
Vector ClampWithin(double minv, double maxv) const;
@@ -159,7 +187,7 @@ public:
Point2d Plus(const Point2d &b) const;
Point2d Minus(const Point2d &b) const;
Point2d ScaledBy(double s) const;
- double DivPivoting(Point2d delta) const;
+ double DivProjected(Point2d delta) const;
double Dot(Point2d p) const;
double DistanceTo(const Point2d &p) const;
double DistanceToLine(const Point2d &p0, const Point2d &dp, bool asSegment) const;
@@ -174,17 +202,20 @@ public:
};
// A simple list
-template
+template
class List {
+ T *elem = nullptr;
+ int elemsAllocated = 0;
+
public:
- T *elem;
- int n;
- int elemsAllocated;
+ int n = 0;
+
+ bool IsEmpty() const { return n == 0; }
void ReserveMore(int howMuch) {
if(n + howMuch > elemsAllocated) {
elemsAllocated = n + howMuch;
- T *newElem = (T *)MemAlloc((size_t)elemsAllocated*sizeof(elem[0]));
+ T *newElem = (T *)MemAlloc((size_t)elemsAllocated*sizeof(T));
for(int i = 0; i < n; i++) {
new(&newElem[i]) T(std::move(elem[i]));
elem[i].~T();
@@ -214,31 +245,41 @@ public:
}
T *First() {
- return (n == 0) ? NULL : &(elem[0]);
+ return IsEmpty() ? nullptr : &(elem[0]);
}
const T *First() const {
- return (n == 0) ? NULL : &(elem[0]);
+ return IsEmpty() ? nullptr : &(elem[0]);
}
+
+ T *Last() { return IsEmpty() ? nullptr : &(elem[n - 1]); }
+ const T *Last() const { return IsEmpty() ? nullptr : &(elem[n - 1]); }
+
T *NextAfter(T *prev) {
- if(!prev) return NULL;
- if(prev - elem == (n - 1)) return NULL;
+ if(IsEmpty() || !prev) return NULL;
+ if(prev - First() == (n - 1)) return NULL;
return prev + 1;
}
const T *NextAfter(const T *prev) const {
- if(!prev) return NULL;
- if(prev - elem == (n - 1)) return NULL;
+ if(IsEmpty() || !prev) return NULL;
+ if(prev - First() == (n - 1)) return NULL;
return prev + 1;
}
- T *begin() { return &elem[0]; }
- T *end() { return &elem[n]; }
- const T *begin() const { return &elem[0]; }
- const T *end() const { return &elem[n]; }
+ T &Get(size_t i) { return elem[i]; }
+ T const &Get(size_t i) const { return elem[i]; }
+ T &operator[](size_t i) { return Get(i); }
+ T const &operator[](size_t i) const { return Get(i); }
+
+ T *begin() { return IsEmpty() ? nullptr : &elem[0]; }
+ T *end() { return IsEmpty() ? nullptr : &elem[n]; }
+ const T *begin() const { return IsEmpty() ? nullptr : &elem[0]; }
+ const T *end() const { return IsEmpty() ? nullptr : &elem[n]; }
+ const T *cbegin() const { return begin(); }
+ const T *cend() const { return end(); }
void ClearTags() {
- int i;
- for(i = 0; i < n; i++) {
- elem[i].tag = 0;
+ for(auto & elt : *this) {
+ elt.tag = 0;
}
}
@@ -251,21 +292,20 @@ public:
}
void RemoveTagged() {
- int src, dest;
- dest = 0;
- for(src = 0; src < n; src++) {
- if(elem[src].tag) {
- // this item should be deleted
- } else {
- if(src != dest) {
- elem[dest] = elem[src];
- }
- dest++;
+ auto newEnd = std::remove_if(this->begin(), this->end(), [](T &t) {
+ if(t.tag) {
+ return true;
+ }
+ return false;
+ });
+ auto oldEnd = this->end();
+ n = newEnd - begin();
+ if (newEnd != nullptr && oldEnd != nullptr) {
+ while(newEnd != oldEnd) {
+ newEnd->~T();
+ ++newEnd;
}
}
- for(int i = dest; i < n; i++)
- elem[i].~T();
- n = dest;
// and elemsAllocated is untouched, because we didn't resize
}
@@ -285,21 +325,44 @@ public:
}
};
+// Comparison functor used by IdList and related classes
+template
+struct CompareId {
+ bool operator()(T const& lhs, T const& rhs) const {
+ return lhs.h.v < rhs.h.v;
+ }
+ bool operator()(T const& lhs, H rhs) const {
+ return lhs.h.v < rhs.v;
+ }
+};
+
// A list, where each element has an integer identifier. The list is kept
// sorted by that identifier, and items can be looked up in log n time by
// id.
template
class IdList {
+ T *elem = nullptr;
+ int elemsAllocated = 0;
public:
- T *elem;
- int n;
- int elemsAllocated;
+ int n = 0;
+
+ using Compare = CompareId;
+
+ bool IsEmpty() const {
+ return n == 0;
+ }
+
+ void AllocForOneMore() {
+ if(n >= elemsAllocated) {
+ ReserveMore((elemsAllocated + 32)*2 - n);
+ }
+ }
uint32_t MaximumId() {
- if(n == 0) {
+ if(IsEmpty()) {
return 0;
} else {
- return elem[n - 1].h.v;
+ return Last()->h.v;
}
}
@@ -310,10 +373,35 @@ public:
return t->h;
}
+ T * LowerBound(T const& t) {
+ if(IsEmpty()) {
+ return nullptr;
+ }
+ auto it = std::lower_bound(begin(), end(), t, Compare());
+ return it;
+ }
+
+ T * LowerBound(H const& h) {
+ if(IsEmpty()) {
+ return nullptr;
+ }
+ auto it = std::lower_bound(begin(), end(), h, Compare());
+ return it;
+ }
+
+ int LowerBoundIndex(T const& t) {
+ if(IsEmpty()) {
+ return 0;
+ }
+ auto it = LowerBound(t);
+ auto idx = std::distance(begin(), it);
+ auto i = static_cast(idx);
+ return i;
+ }
void ReserveMore(int howMuch) {
if(n + howMuch > elemsAllocated) {
elemsAllocated = n + howMuch;
- T *newElem = (T *)MemAlloc((size_t)elemsAllocated*sizeof(elem[0]));
+ T *newElem = (T *)MemAlloc((size_t)elemsAllocated*sizeof(T));
for(int i = 0; i < n; i++) {
new(&newElem[i]) T(std::move(elem[i]));
elem[i].~T();
@@ -324,28 +412,16 @@ public:
}
void Add(T *t) {
- if(n >= elemsAllocated) {
- ReserveMore((elemsAllocated + 32)*2 - n);
- }
+ AllocForOneMore();
- int first = 0, last = n;
- // We know that we must insert within the closed interval [first,last]
- while(first != last) {
- int mid = (first + last)/2;
- H hm = elem[mid].h;
- ssassert(hm.v != t->h.v, "Handle isn't unique");
- if(hm.v > t->h.v) {
- last = mid;
- } else if(hm.v < t->h.v) {
- first = mid + 1;
- }
- }
+ // Look to see if we already have something with the same handle value.
+ ssassert(FindByIdNoOops(t->h) == nullptr, "Handle isn't unique");
- int i = first;
- new(&elem[n]) T();
- std::move_backward(elem + i, elem + n, elem + n + 1);
- elem[i] = *t;
- n++;
+ // Copy-construct at the end of the list.
+ new(&elem[n]) T(*t);
+ ++n;
+ // The item we just added is trivially sorted, so "merge"
+ std::inplace_merge(begin(), end() - 1, end(), Compare());
}
T *FindById(H h) {
@@ -355,50 +431,54 @@ public:
}
int IndexOf(H h) {
- int first = 0, last = n-1;
- while(first <= last) {
- int mid = (first + last)/2;
- H hm = elem[mid].h;
- if(hm.v > h.v) {
- last = mid-1; // and first stays the same
- } else if(hm.v < h.v) {
- first = mid+1; // and last stays the same
- } else {
- return mid;
- }
+ if(IsEmpty()) {
+ return -1;
+ }
+ auto it = LowerBound(h);
+ auto idx = std::distance(begin(), it);
+ if (idx < n) {
+ return idx;
}
return -1;
}
T *FindByIdNoOops(H h) {
- int first = 0, last = n-1;
- while(first <= last) {
- int mid = (first + last)/2;
- H hm = elem[mid].h;
- if(hm.v > h.v) {
- last = mid-1; // and first stays the same
- } else if(hm.v < h.v) {
- first = mid+1; // and last stays the same
- } else {
- return &(elem[mid]);
- }
+ if(IsEmpty()) {
+ return nullptr;
}
- return NULL;
+ auto it = LowerBound(h);
+ if (it == nullptr || it == end()) {
+ return nullptr;
+ }
+ if (it->h.v == h.v) {
+ return it;
+ }
+ return nullptr;
}
T *First() {
- return (n == 0) ? NULL : &(elem[0]);
+ return (IsEmpty()) ? NULL : &(elem[0]);
+ }
+ T *Last() {
+ return (IsEmpty()) ? NULL : &(elem[n-1]);
}
T *NextAfter(T *prev) {
- if(!prev) return NULL;
- if(prev - elem == (n - 1)) return NULL;
+ if(IsEmpty() || !prev) return NULL;
+ if(prev - First() == (n - 1)) return NULL;
return prev + 1;
}
- T *begin() { return &elem[0]; }
- T *end() { return &elem[n]; }
- const T *begin() const { return &elem[0]; }
- const T *end() const { return &elem[n]; }
+ T &Get(size_t i) { return elem[i]; }
+ T const &Get(size_t i) const { return elem[i]; }
+ T &operator[](size_t i) { return Get(i); }
+ T const &operator[](size_t i) const { return Get(i); }
+
+ T *begin() { return IsEmpty() ? nullptr : &elem[0]; }
+ T *end() { return IsEmpty() ? nullptr : &elem[0] + n; }
+ const T *begin() const { return IsEmpty() ? nullptr : &elem[0]; }
+ const T *end() const { return IsEmpty() ? nullptr : &elem[0] + n; }
+ const T *cbegin() const { return begin(); }
+ const T *cend() const { return end(); }
template
size_t CountIf(F &&predicate) const {
@@ -406,18 +486,13 @@ public:
}
void ClearTags() {
- int i;
- for(i = 0; i < n; i++) {
- elem[i].tag = 0;
- }
+ for(auto &elt : *this) { elt.tag = 0; }
}
void Tag(H h, int tag) {
- int i;
- for(i = 0; i < n; i++) {
- if(elem[i].h.v == h.v) {
- elem[i].tag = tag;
- }
+ auto it = FindByIdNoOops(h);
+ if (it != nullptr) {
+ it->tag = tag;
}
}
@@ -448,9 +523,9 @@ public:
void MoveSelfInto(IdList *l) {
l->Clear();
- *l = *this;
- elemsAllocated = n = 0;
- elem = NULL;
+ std::swap(l->elem, elem);
+ std::swap(l->elemsAllocated, elemsAllocated);
+ std::swap(l->n, n);
}
void DeepCopyInto(IdList *l) {
@@ -467,9 +542,9 @@ public:
elem[i].Clear();
elem[i].~T();
}
- elemsAllocated = n = 0;
if(elem) MemFree(elem);
elem = NULL;
+ elemsAllocated = n = 0;
}
};
diff --git a/src/entity.cpp b/src/entity.cpp
index 4b3d0990..38800ab6 100644
--- a/src/entity.cpp
+++ b/src/entity.cpp
@@ -37,7 +37,7 @@ ExprVector EntityBase::VectorGetExprsInWorkplane(hEntity wrkpl) const {
case Type::NORMAL_N_ROT:
case Type::NORMAL_N_ROT_AA: {
ExprVector ev = NormalExprsN();
- if(wrkpl.v == EntityBase::FREE_IN_3D.v) {
+ if(wrkpl == EntityBase::FREE_IN_3D) {
return ev;
}
// Get the offset and basis vectors for this weird exotic csys.
@@ -245,6 +245,7 @@ bool EntityBase::IsPoint() const {
case Type::POINT_N_TRANS:
case Type::POINT_N_ROT_TRANS:
case Type::POINT_N_ROT_AA:
+ case Type::POINT_N_ROT_AXIS_TRANS:
return true;
default:
@@ -454,10 +455,38 @@ void EntityBase::PointForceTo(Vector p) {
// in order to avoid jumps when you cross from +pi to -pi
while(dtheta < -PI) dtheta += 2*PI;
while(dtheta > PI) dtheta -= 2*PI;
+ // this extra *2 explains the mystery *4
SK.GetParam(param[3])->val = (thetai + dtheta)/(timesApplied*2);
break;
}
+ case Type::POINT_N_ROT_AXIS_TRANS: {
+ if(timesApplied == 0) break;
+ // is the point on the rotation axis?
+ Vector offset = Vector::From(param[0], param[1], param[2]);
+ Vector normal = Vector::From(param[4], param[5], param[6]).WithMagnitude(1.0);
+ Vector check = numPoint.Minus(offset).Cross(normal);
+ if (check.Dot(check) < LENGTH_EPS) { // if so, do extrusion style drag
+ Vector trans = (p.Minus(numPoint));
+ SK.GetParam(param[7])->val = trans.Dot(normal)/timesApplied;
+ } else { // otherwise do rotation style
+ Vector u = normal.Normal(0), v = normal.Normal(1);
+ Vector po = p.Minus(offset), numo = numPoint.Minus(offset);
+ double thetap = atan2(v.Dot(po), u.Dot(po));
+ double thetan = atan2(v.Dot(numo), u.Dot(numo));
+ double thetaf = (thetap - thetan);
+ double thetai = (SK.GetParam(param[3])->val)*timesApplied*2;
+ double dtheta = thetaf - thetai;
+ // Take the smallest possible change in the actual step angle,
+ // in order to avoid jumps when you cross from +pi to -pi
+ while(dtheta < -PI) dtheta += 2*PI;
+ while(dtheta > PI) dtheta -= 2*PI;
+ // this extra *2 explains the mystery *4
+ SK.GetParam(param[3])->val = (thetai + dtheta)/(timesApplied*2);
+ }
+ break;
+ }
+
case Type::POINT_N_COPY:
// Nothing to do; it's a static copy
break;
@@ -506,6 +535,17 @@ Vector EntityBase::PointGetNum() const {
break;
}
+ case Type::POINT_N_ROT_AXIS_TRANS: {
+ Vector offset = Vector::From(param[0], param[1], param[2]);
+ Vector displace = Vector::From(param[4], param[5], param[6])
+ .WithMagnitude(SK.GetParam(param[7])->val).ScaledBy(timesApplied);
+ Quaternion q = PointGetQuaternion();
+ p = numPoint.Minus(offset);
+ p = q.Rotate(p);
+ p = p.Plus(offset).Plus(displace);
+ break;
+ }
+
case Type::POINT_N_COPY:
p = numPoint;
break;
@@ -555,6 +595,18 @@ ExprVector EntityBase::PointGetExprs() const {
r = orig.Plus(trans);
break;
}
+ case Type::POINT_N_ROT_AXIS_TRANS: {
+ ExprVector orig = ExprVector::From(numPoint);
+ ExprVector trans = ExprVector::From(param[0], param[1], param[2]);
+ ExprVector displace = ExprVector::From(param[4], param[5], param[6])
+ .WithMagnitude(Expr::From(1.0)).ScaledBy(Expr::From(timesApplied)).ScaledBy(Expr::From(param[7]));
+
+ ExprQuaternion q = GetAxisAngleQuaternionExprs(3);
+ orig = orig.Minus(trans);
+ orig = q.Rotate(orig);
+ r = orig.Plus(trans).Plus(displace);
+ break;
+ }
case Type::POINT_N_COPY:
r = ExprVector::From(numPoint);
break;
@@ -565,7 +617,7 @@ ExprVector EntityBase::PointGetExprs() const {
}
void EntityBase::PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) const {
- if(type == Type::POINT_IN_2D && workplane.v == wrkpl.v) {
+ if(type == Type::POINT_IN_2D && workplane == wrkpl) {
// They want our coordinates in the form that we've written them,
// very nice.
*u = Expr::From(param[0]);
@@ -587,7 +639,7 @@ void EntityBase::PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) con
}
ExprVector EntityBase::PointGetExprsInWorkplane(hEntity wrkpl) const {
- if(wrkpl.v == Entity::FREE_IN_3D.v) {
+ if(wrkpl == Entity::FREE_IN_3D) {
return PointGetExprs();
}
@@ -633,7 +685,7 @@ ExprQuaternion EntityBase::GetAxisAngleQuaternionExprs(int param0) const {
Quaternion EntityBase::PointGetQuaternion() const {
Quaternion q;
- if(type == Type::POINT_N_ROT_AA) {
+ if(type == Type::POINT_N_ROT_AA || type == Type::POINT_N_ROT_AXIS_TRANS) {
q = GetAxisAngleQuaternion(3);
} else if(type == Type::POINT_N_ROT_TRANS) {
q = Quaternion::From(param[3], param[4], param[5], param[6]);
@@ -807,7 +859,7 @@ bool EntityBase::IsInPlane(Vector norm, double distance) const {
case Type::CIRCLE:
case Type::ARC_OF_CIRCLE: {
- // If it is an (arc of) a circle, check whether the normals
+ // If it is an (arc of) a circle, check whether the normals
// are parallel and the mid point is in the plane.
Vector n = Normal()->NormalN();
if (!norm.Equals(n) && !norm.Equals(n.Negated())) return false;
@@ -870,19 +922,16 @@ void EntityBase::GenerateEquations(IdList *l) const {
// If the two endpoints of the arc are constrained coincident
// (to make a complete circle), then our distance constraint
// would be redundant and therefore overconstrain things.
- int i;
- for(i = 0; i < SK.constraint.n; i++) {
- ConstraintBase *c = &(SK.constraint.elem[i]);
- if(c->group.v != group.v) continue;
- if(c->type != Constraint::Type::POINTS_COINCIDENT) continue;
-
- if((c->ptA.v == point[1].v && c->ptB.v == point[2].v) ||
- (c->ptA.v == point[2].v && c->ptB.v == point[1].v))
- {
- break;
- }
+ auto it = std::find_if(SK.constraint.begin(), SK.constraint.end(),
+ [&](ConstraintBase const &con) {
+ return (con.group == group) &&
+ (con.type == Constraint::Type::POINTS_COINCIDENT) &&
+ ((con.ptA == point[1] && con.ptB == point[2]) ||
+ (con.ptA == point[2] && con.ptB == point[1]));
+ });
+ if(it != SK.constraint.end()) {
+ break;
}
- if(i < SK.constraint.n) break;
Expr *ra = Constraint::Distance(workplane, point[0], point[1]);
Expr *rb = Constraint::Distance(workplane, point[0], point[2]);
diff --git a/src/export.cpp b/src/export.cpp
index 7ff59f0e..621fa2e6 100644
--- a/src/export.cpp
+++ b/src/export.cpp
@@ -28,7 +28,7 @@ void SolveSpaceUI::ExportSectionTo(const Platform::Path &filename) {
SS.GW.GroupSelection();
auto const &gs = SS.GW.gs;
- if((gs.n == 0 && g->activeWorkplane.v != Entity::FREE_IN_3D.v)) {
+ if((gs.n == 0 && g->activeWorkplane != Entity::FREE_IN_3D)) {
Entity *wrkpl = SK.GetEntity(g->activeWorkplane);
origin = wrkpl->WorkplaneGetOffset();
n = wrkpl->Normal()->NormalN();
@@ -94,11 +94,10 @@ void SolveSpaceUI::ExportSectionTo(const Platform::Path &filename) {
// 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]);
+ for(auto &ent : SK.entity) {
+ Entity *e = &ent;
if (!e->IsVisible()) continue;
if (e->style.v < Style::FIRST_CUSTOM) continue;
if (!Style::Exportable(e->style.v)) continue;
@@ -186,7 +185,6 @@ public:
};
void SolveSpaceUI::ExportViewOrWireframeTo(const Platform::Path &filename, bool exportWireframe) {
- int i;
SEdgeList edges = {};
SBezierList beziers = {};
@@ -206,8 +204,8 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const Platform::Path &filename, bool
sm = NULL;
}
- for(i = 0; i < SK.entity.n; i++) {
- Entity *e = &(SK.entity.elem[i]);
+ for(auto &entity : SK.entity) {
+ Entity *e = &entity;
if(!e->IsVisible()) continue;
if(e->construction) continue;
@@ -321,16 +319,14 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s
// Project into the export plane; so when we're done, z doesn't matter,
// and x and y are what goes in the DXF.
- SEdge *e;
- for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) {
+ for(SEdge *e = sel->l.First(); e; e = sel->l.NextAfter(e)) {
// project into the specified csys, and apply export scale
(e->a) = e->a.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
(e->b) = e->b.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
}
- SBezier *b;
if(sbl) {
- for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) {
+ for(SBezier *b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) {
*b = b->InPerspective(u, v, n, origin, cameraTan);
int i;
for(i = 0; i <= b->deg; i++) {
@@ -459,7 +455,7 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s
// segments with zero-length projections.
sel->l.ClearTags();
for(int i = 0; i < sel->l.n; ++i) {
- SEdge *sei = &sel->l.elem[i];
+ SEdge *sei = &sel->l[i];
hStyle hsi = { (uint32_t)sei->auxA };
Style *si = Style::Get(hsi);
if(sei->tag != 0) continue;
@@ -476,7 +472,7 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s
}
for(int j = i + 1; j < sel->l.n; ++j) {
- SEdge *sej = &sel->l.elem[j];
+ SEdge *sej = &sel->l[j];
if(sej->tag != 0) continue;
Vector *pAj = &sej->a;
@@ -578,12 +574,13 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s
// We kept the line segments and Beziers separate until now; but put them
// all together, and also project everything into the xy plane, since not
// all export targets ignore the z component of the points.
- for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) {
+ ssassert(sbl != nullptr, "Adding line segments to beziers assumes bezier list is non-null.");
+ for(SEdge *e = sel->l.First(); e; e = sel->l.NextAfter(e)) {
SBezier sb = SBezier::From(e->a, e->b);
sb.auxA = e->auxA;
sbl->l.Add(&sb);
}
- for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) {
+ for(SBezier *b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) {
for(int i = 0; i <= b->deg; i++) {
b->ctrl[i].z = 0;
}
@@ -762,9 +759,9 @@ void VectorFileWriter::OutputLinesAndMesh(SBezierLoopSetSet *sblss, SMesh *sm) {
void VectorFileWriter::BezierAsPwl(SBezier *sb) {
List lv = {};
sb->MakePwlInto(&lv, SS.ExportChordTolMm());
- int i;
- for(i = 1; i < lv.n; i++) {
- SBezier sb = SBezier::From(lv.elem[i-1], lv.elem[i]);
+
+ for(int i = 1; i < lv.n; i++) {
+ SBezier sb = SBezier::From(lv[i-1], lv[i]);
Bezier(&sb);
}
lv.Clear();
@@ -849,6 +846,8 @@ void SolveSpaceUI::ExportMeshTo(const Platform::Path &filename) {
filename.HasExtension("html")) {
SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines);
ExportMeshAsThreeJsTo(f, filename, m, e);
+ } else if(filename.HasExtension("wrl")) {
+ ExportMeshAsVrmlTo(f, filename, m);
} else {
Error("Can't identify output file type from file extension of "
"filename '%s'; try .stl, .obj, .js, .html.", filename.raw.c_str());
@@ -876,7 +875,7 @@ void SolveSpaceUI::ExportMeshAsStlTo(FILE *f, SMesh *sm) {
double s = SS.exportScale;
int i;
for(i = 0; i < sm->l.n; i++) {
- STriangle *tr = &(sm->l.elem[i]);
+ STriangle *tr = &(sm->l[i]);
Vector n = tr->Normal().WithMagnitude(1);
float w;
w = (float)n.x; fwrite(&w, 4, 1, f);
@@ -900,7 +899,7 @@ void SolveSpaceUI::ExportMeshAsStlTo(FILE *f, SMesh *sm) {
// Export the mesh as a Q3DO (https://github.com/q3k/q3d) file.
//-----------------------------------------------------------------------------
-#include "object_generated.h"
+#include "q3d_object_generated.h"
void SolveSpaceUI::ExportMeshAsQ3doTo(FILE *f, SMesh *sm) {
flatbuffers::FlatBufferBuilder builder(1024);
double s = SS.exportScale;
@@ -982,7 +981,7 @@ void SolveSpaceUI::ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm) {
RgbaColor currentColor = {};
for(int i = 0; i < sm->l.n; i++) {
- const STriangle &t = sm->l.elem[i];
+ const STriangle &t = sm->l[i];
if(!currentColor.Equals(t.meta.color)) {
currentColor = t.meta.color;
fprintf(fObj, "usemtl %s\n", colors[currentColor].c_str());
@@ -1168,6 +1167,139 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename
spl.Clear();
}
+//-----------------------------------------------------------------------------
+// Export the mesh as a VRML text file / WRL.
+//-----------------------------------------------------------------------------
+void SolveSpaceUI::ExportMeshAsVrmlTo(FILE *f, const Platform::Path &filename, SMesh *sm) {
+ struct STriangleSpan {
+ STriangle *first, *past_last;
+
+ STriangle *begin() const { return first; }
+ STriangle *end() const { return past_last; }
+ };
+
+
+ std::string basename = filename.FileStem();
+ for(auto & c : basename) {
+ if(!(isalnum(c) || ((unsigned)c >= 0x80))) {
+ c = '_';
+ }
+ }
+
+ fprintf(f, "#VRML V2.0 utf8\n"
+ "#Exported from SolveSpace %s\n"
+ "\n"
+ "DEF %s Transform {\n"
+ " children [",
+ PACKAGE_VERSION,
+ basename.c_str());
+
+
+ std::map> opacities;
+ STriangle *start = sm->l.begin();
+ std::uint8_t last_opacity = start->meta.color.alpha;
+ for(auto & tr : sm->l) {
+ if(tr.meta.color.alpha != last_opacity) {
+ opacities[last_opacity].push_back(STriangleSpan{start, &tr});
+ start = &tr;
+ last_opacity = start->meta.color.alpha;
+ }
+ }
+ opacities[last_opacity].push_back(STriangleSpan{start, sm->l.end()});
+
+ for(auto && op : opacities) {
+ fprintf(f, "\n"
+ " Shape {\n"
+ " appearance Appearance {\n"
+ " material DEF %s_material_%u Material {\n"
+ " diffuseColor %f %f %f\n"
+ " ambientIntensity %f\n"
+ " transparency %f\n"
+ " }\n"
+ " }\n"
+ " geometry IndexedFaceSet {\n"
+ " colorPerVertex TRUE\n"
+ " coord Coordinate { point [\n",
+ basename.c_str(),
+ (unsigned)op.first,
+ SS.ambientIntensity,
+ SS.ambientIntensity,
+ SS.ambientIntensity,
+ SS.ambientIntensity,
+ 1.f - ((float)op.first / 255.0f));
+
+ SPointList spl = {};
+
+ for(const auto & sp : op.second) {
+ for(const auto & tr : sp) {
+ spl.IncrementTagFor(tr.a);
+ spl.IncrementTagFor(tr.b);
+ spl.IncrementTagFor(tr.c);
+ }
+ }
+
+ // Output all the vertices.
+ for(auto sp : spl.l) {
+ fprintf(f, " %f %f %f,\n",
+ sp.p.x / SS.exportScale,
+ sp.p.y / SS.exportScale,
+ sp.p.z / SS.exportScale);
+ }
+
+ fputs(" ] }\n"
+ " coordIndex [\n", f);
+ // And now all the triangular faces, in terms of those vertices.
+ for(const auto & sp : op.second) {
+ for(const auto & tr : sp) {
+ fprintf(f, " %d, %d, %d, -1,\n",
+ spl.IndexForPoint(tr.a),
+ spl.IndexForPoint(tr.b),
+ spl.IndexForPoint(tr.c));
+ }
+ }
+
+ fputs(" ]\n"
+ " color Color { color [\n", f);
+ // Output triangle colors.
+ std::vector triangle_colour_ids;
+ std::vector colours_present;
+ for(const auto & sp : op.second) {
+ for(const auto & tr : sp) {
+ const auto colour_itr = std::find_if(colours_present.begin(), colours_present.end(),
+ [&](const RgbaColor & c) {
+ return c.Equals(tr.meta.color);
+ });
+ if(colour_itr == colours_present.end()) {
+ fprintf(f, " %.10f %.10f %.10f,\n",
+ tr.meta.color.redF(),
+ tr.meta.color.greenF(),
+ tr.meta.color.blueF());
+ triangle_colour_ids.push_back(colours_present.size());
+ colours_present.insert(colours_present.end(), tr.meta.color);
+ } else {
+ triangle_colour_ids.push_back(colour_itr - colours_present.begin());
+ }
+ }
+ }
+
+ fputs(" ] }\n"
+ " colorIndex [\n", f);
+
+ for(auto colour_idx : triangle_colour_ids) {
+ fprintf(f, " %d, %d, %d, -1,\n", colour_idx, colour_idx, colour_idx);
+ }
+
+ fputs(" ]\n"
+ " }\n"
+ " }\n", f);
+
+ spl.Clear();
+ }
+
+ fputs(" ]\n"
+ "}\n", f);
+}
+
//-----------------------------------------------------------------------------
// Export a view of the model as an image; we just take a screenshot, by
// rendering the view in the usual way and then copying the pixels.
diff --git a/src/exportstep.cpp b/src/exportstep.cpp
index 970c925f..d5c9d406 100644
--- a/src/exportstep.cpp
+++ b/src/exportstep.cpp
@@ -121,7 +121,7 @@ int StepFileWriter::ExportCurveLoop(SBezierLoop *loop, bool inner) {
List listOfTrims = {};
- SBezier *sb = &(loop->l.elem[loop->l.n - 1]);
+ SBezier *sb = loop->l.Last();
// Generate "exactly closed" contours, with the same vertex id for the
// finish of a previous edge and the start of the next one. So we need
@@ -296,12 +296,13 @@ void StepFileWriter::ExportSurfacesTo(const Platform::Path &filename) {
Group *g = SK.GetGroup(SS.GW.activeGroup);
SShell *shell = &(g->runningShell);
- if(shell->surface.n == 0) {
+ if(shell->surface.IsEmpty()) {
Error("The model does not contain any surfaces to export.%s",
- g->runningMesh.l.n > 0 ?
- "\n\nThe model does contain triangles from a mesh, but "
- "a triangle mesh cannot be exported as a STEP file. Try "
- "File -> Export Mesh... instead." : "");
+ !g->runningMesh.l.IsEmpty()
+ ? "\n\nThe model does contain triangles from a mesh, but "
+ "a triangle mesh cannot be exported as a STEP file. Try "
+ "File -> Export Mesh... instead."
+ : "");
return;
}
@@ -318,7 +319,8 @@ void StepFileWriter::ExportSurfacesTo(const Platform::Path &filename) {
SSurface *ss;
for(ss = shell->surface.First(); ss; ss = shell->surface.NextAfter(ss)) {
- if(ss->trim.n == 0) continue;
+ if(ss->trim.IsEmpty())
+ continue;
// Get all of the loops of Beziers that trim our surface (with each
// Bezier split so that we use the section as t goes from 0 to 1), and
diff --git a/src/exportvector.cpp b/src/exportvector.cpp
index 871e22ff..4de45f56 100644
--- a/src/exportvector.cpp
+++ b/src/exportvector.cpp
@@ -325,7 +325,7 @@ public:
void assignEntityDefaults(DRW_Entity *entity, hStyle hs) {
Style *s = Style::Get(hs);
- RgbaColor color = s->Color(hs, /*forExport=*/true);
+ RgbaColor color = Style::Color(hs, /*forExport=*/true);
entity->color24 = color.ToPackedIntBGRA();
entity->color = findDxfColor(color);
entity->layer = s->DescriptionString();
@@ -370,7 +370,7 @@ public:
DRW_Polyline polyline;
assignEntityDefaults(&polyline, hs);
for(int i = 0; i < lv.n; i++) {
- Vector *v = &lv.elem[i];
+ Vector *v = &lv[i];
DRW_Vertex *vertex = new DRW_Vertex(v->x, v->y, v->z, 0.0);
polyline.vertlist.push_back(vertex);
}
@@ -1027,8 +1027,9 @@ void SvgFileWriter::StartFile() {
double sw = max(ptMax.x - ptMin.x, ptMax.y - ptMin.y) / 1000;
fprintf(f, "stroke-width:%f;\r\n", sw);
fprintf(f, "}\r\n");
- for(int i = 0; i < SK.style.n; i++) {
- Style *s = &SK.style.elem[i];
+ for(auto &style : SK.style) {
+ Style *s = &style;
+
RgbaColor strokeRgb = Style::Color(s->h, /*forExport=*/true);
StipplePattern pattern = Style::PatternType(s->h);
double stippleScale = Style::StippleScaleMm(s->h);
diff --git a/src/expr.cpp b/src/expr.cpp
index 700d6b69..99d68ec4 100644
--- a/src/expr.cpp
+++ b/src/expr.cpp
@@ -361,8 +361,8 @@ Expr *Expr::PartialWrt(hParam p) const {
Expr *da, *db;
switch(op) {
- case Op::PARAM_PTR: return From(p.v == parp->h.v ? 1 : 0);
- case Op::PARAM: return From(p.v == parh.v ? 1 : 0);
+ case Op::PARAM_PTR: return From(p == parp->h ? 1 : 0);
+ case Op::PARAM: return From(p == parh ? 1 : 0);
case Op::CONSTANT: return From(0.0);
case Op::VARIABLE: ssassert(false, "Not supported yet");
@@ -412,8 +412,8 @@ uint64_t Expr::ParamsUsed() const {
}
bool Expr::DependsOn(hParam p) const {
- if(op == Op::PARAM) return (parh.v == p.v);
- if(op == Op::PARAM_PTR) return (parp->h.v == p.v);
+ if(op == Op::PARAM) return (parh == p);
+ if(op == Op::PARAM_PTR) return (parp->h == p);
int c = Children();
if(c == 1) return a->DependsOn(p);
@@ -494,7 +494,7 @@ Expr *Expr::FoldConstants() {
void Expr::Substitute(hParam oldh, hParam newh) {
ssassert(op != Op::PARAM_PTR, "Expected an expression that refer to params via handles");
- if(op == Op::PARAM && parh.v == oldh.v) {
+ if(op == Op::PARAM && parh == oldh) {
parh = newh;
}
int c = Children();
@@ -528,11 +528,11 @@ hParam Expr::ReferencedParams(ParamList *pl) const {
hParam pa, pb;
pa = a->ReferencedParams(pl);
pb = b->ReferencedParams(pl);
- if(pa.v == NO_PARAMS.v) {
+ if(pa == NO_PARAMS) {
return pb;
- } else if(pb.v == NO_PARAMS.v) {
+ } else if(pb == NO_PARAMS) {
return pa;
- } else if(pa.v == pb.v) {
+ } else if(pa == pb) {
return pa; // either, doesn't matter
} else {
return MULTIPLE_PARAMS;
diff --git a/src/expr.h b/src/expr.h
index 7383d993..7109cf65 100644
--- a/src/expr.h
+++ b/src/expr.h
@@ -45,7 +45,7 @@ public:
Expr *b;
};
- Expr() { }
+ Expr() = default;
Expr(double val) : op(Op::CONSTANT) { v = val; }
static inline Expr *AllocExpr()
diff --git a/src/file.cpp b/src/file.cpp
index 99cd234f..657a4b2a 100644
--- a/src/file.cpp
+++ b/src/file.cpp
@@ -20,8 +20,8 @@ void SolveSpaceUI::ClearExisting() {
UndoClearStack(&redo);
UndoClearStack(&undo);
- for(int i = 0; i < SK.groupOrder.n; i++) {
- Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
+ for(hGroup hg : SK.groupOrder) {
+ Group *g = SK.GetGroup(hg);
g->Clear();
}
@@ -301,39 +301,39 @@ bool SolveSpaceUI::SaveToFile(const Platform::Path &filename) {
fprintf(fh, "%s\n\n\n", VERSION_STRING);
int i, j;
- for(i = 0; i < SK.group.n; i++) {
- sv.g = SK.group.elem[i];
+ for(auto &g : SK.group) {
+ sv.g = g;
SaveUsingTable(filename, 'g');
fprintf(fh, "AddGroup\n\n");
}
- for(i = 0; i < SK.param.n; i++) {
- sv.p = SK.param.elem[i];
+ for(auto &p : SK.param) {
+ sv.p = p;
SaveUsingTable(filename, 'p');
fprintf(fh, "AddParam\n\n");
}
- for(i = 0; i < SK.request.n; i++) {
- sv.r = SK.request.elem[i];
+ for(auto &r : SK.request) {
+ sv.r = r;
SaveUsingTable(filename, 'r');
fprintf(fh, "AddRequest\n\n");
}
- for(i = 0; i < SK.entity.n; i++) {
- (SK.entity.elem[i]).CalculateNumerical(/*forExport=*/true);
- sv.e = SK.entity.elem[i];
+ for(auto &e : SK.entity) {
+ e.CalculateNumerical(/*forExport=*/true);
+ sv.e = e;
SaveUsingTable(filename, 'e');
fprintf(fh, "AddEntity\n\n");
}
- for(i = 0; i < SK.constraint.n; i++) {
- sv.c = SK.constraint.elem[i];
+ for(auto &c : SK.constraint) {
+ sv.c = c;
SaveUsingTable(filename, 'c');
fprintf(fh, "AddConstraint\n\n");
}
- for(i = 0; i < SK.style.n; i++) {
- sv.s = SK.style.elem[i];
+ for(auto &s : SK.style) {
+ sv.s = s;
if(sv.s.h.v >= Style::FIRST_CUSTOM) {
SaveUsingTable(filename, 's');
fprintf(fh, "AddStyle\n\n");
@@ -343,10 +343,10 @@ bool SolveSpaceUI::SaveToFile(const Platform::Path &filename) {
// A group will have either a mesh or a shell, but not both; but the code
// to print either of those just does nothing if the mesh/shell is empty.
- Group *g = SK.GetGroup(SK.groupOrder.elem[SK.groupOrder.n - 1]);
+ Group *g = SK.GetGroup(*SK.groupOrder.Last());
SMesh *m = &g->runningMesh;
for(i = 0; i < m->l.n; i++) {
- STriangle *tr = &(m->l.elem[i]);
+ STriangle *tr = &(m->l[i]);
fprintf(fh, "Triangle %08x %08x "
"%.20f %.20f %.20f %.20f %.20f %.20f %.20f %.20f %.20f\n",
tr->meta.face, tr->meta.color.ToPackedInt(),
@@ -436,8 +436,17 @@ void SolveSpaceUI::LoadUsingTable(const Platform::Path &filename, char *key, cha
if (fgets(line2, (int)sizeof(line2), fh) == NULL)
break;
if(sscanf(line2, "%d %x %d", &(ei.v), &(ek.input.v),
- &(ek.copyNumber)) == 3)
- {
+ &(ek.copyNumber)) == 3) {
+ if(ei.v == Entity::NO_ENTITY.v) {
+ // Commit bd84bc1a mistakenly introduced code that would remap
+ // some entities to NO_ENTITY. This was fixed in commit bd84bc1a,
+ // but files created meanwhile are corrupt, and can cause crashes.
+ //
+ // To fix this, we skip any such remaps when loading; they will be
+ // recreated on the next regeneration. Any resulting orphans will
+ // be pruned in the usual way, recovering to a well-defined state.
+ continue;
+ }
p->M().insert({ ek, ei });
} else {
break;
@@ -540,7 +549,7 @@ bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel)
Error(_("Unrecognized data in file. This file may be corrupt, or "
"from a newer version of the program."));
// At least leave the program in a non-crashing state.
- if(SK.group.n == 0) {
+ if(SK.group.IsEmpty()) {
NewFile();
}
}
diff --git a/src/generate.cpp b/src/generate.cpp
index edcd0abb..a4016443 100644
--- a/src/generate.cpp
+++ b/src/generate.cpp
@@ -14,11 +14,10 @@ void SolveSpaceUI::MarkGroupDirtyByEntity(hEntity he) {
}
void SolveSpaceUI::MarkGroupDirty(hGroup hg, bool onlyThis) {
- int i;
bool go = false;
- for(i = 0; i < SK.groupOrder.n; i++) {
- Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
- if(g->h.v == hg.v) {
+ for(auto const &gh : SK.groupOrder) {
+ Group *g = SK.GetGroup(gh);
+ if(g->h == hg) {
go = true;
}
if(go) {
@@ -31,23 +30,20 @@ void SolveSpaceUI::MarkGroupDirty(hGroup hg, bool onlyThis) {
}
bool SolveSpaceUI::PruneOrphans() {
- int i;
- for(i = 0; i < SK.request.n; i++) {
- Request *r = &(SK.request.elem[i]);
- if(GroupExists(r->group)) continue;
+ auto r = std::find_if(SK.request.begin(), SK.request.end(),
+ [&](Request &r) { return !GroupExists(r.group); });
+ if(r != SK.request.end()) {
(deleted.requests)++;
SK.request.RemoveById(r->h);
return true;
}
- for(i = 0; i < SK.constraint.n; i++) {
- Constraint *c = &(SK.constraint.elem[i]);
- if(GroupExists(c->group)) continue;
-
+ auto c = std::find_if(SK.constraint.begin(), SK.constraint.end(),
+ [&](Constraint &c) { return !GroupExists(c.group); });
+ if(c != SK.constraint.end()) {
(deleted.constraints)++;
(deleted.nonTrivialConstraints)++;
-
SK.constraint.RemoveById(c->h);
return true;
}
@@ -72,7 +68,7 @@ bool SolveSpaceUI::GroupExists(hGroup hg) {
bool SolveSpaceUI::EntityExists(hEntity he) {
// A nonexstient entity is acceptable, though, usually just means it
// doesn't apply.
- if(he.v == Entity::NO_ENTITY.v) return true;
+ if(he == Entity::NO_ENTITY) return true;
return SK.entity.FindByIdNoOops(he) ? true : false;
}
@@ -91,44 +87,38 @@ bool SolveSpaceUI::PruneGroups(hGroup hg) {
}
bool SolveSpaceUI::PruneRequests(hGroup hg) {
- int i;
- for(i = 0; i < SK.entity.n; i++) {
- Entity *e = &(SK.entity.elem[i]);
- if(e->group.v != hg.v) continue;
-
- if(EntityExists(e->workplane)) continue;
-
- ssassert(e->h.isFromRequest(), "Only explicitly created entities can be pruned");
-
+ auto e = std::find_if(SK.entity.begin(), SK.entity.end(),
+ [&](Entity &e) { return e.group == hg && !EntityExists(e.workplane); });
+ if(e != SK.entity.end()) {
(deleted.requests)++;
- SK.request.RemoveById(e->h.request());
+ SK.entity.RemoveById(e->h);
return true;
}
return false;
}
bool SolveSpaceUI::PruneConstraints(hGroup hg) {
- int i;
- for(i = 0; i < SK.constraint.n; i++) {
- Constraint *c = &(SK.constraint.elem[i]);
- if(c->group.v != hg.v) continue;
+ auto c = std::find_if(SK.constraint.begin(), SK.constraint.end(), [&](Constraint &c) {
+ if(c.group != hg)
+ return false;
- if(EntityExists(c->workplane) &&
- EntityExists(c->ptA) &&
- EntityExists(c->ptB) &&
- EntityExists(c->entityA) &&
- EntityExists(c->entityB) &&
- EntityExists(c->entityC) &&
- EntityExists(c->entityD))
- {
- continue;
+ if(EntityExists(c.workplane) &&
+ EntityExists(c.ptA) &&
+ EntityExists(c.ptB) &&
+ EntityExists(c.entityA) &&
+ EntityExists(c.entityB) &&
+ EntityExists(c.entityC) &&
+ EntityExists(c.entityD)) {
+ return false;
}
+ return true;
+ });
+ if(c != SK.constraint.end()) {
(deleted.constraints)++;
if(c->type != Constraint::Type::POINTS_COINCIDENT &&
c->type != Constraint::Type::HORIZONTAL &&
- c->type != Constraint::Type::VERTICAL)
- {
+ c->type != Constraint::Type::VERTICAL) {
(deleted.nonTrivialConstraints)++;
}
@@ -139,14 +129,13 @@ bool SolveSpaceUI::PruneConstraints(hGroup hg) {
}
void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) {
- int first = 0, last = 0, i, j;
+ int first = 0, last = 0, i;
uint64_t startMillis = GetMilliseconds(),
endMillis;
SK.groupOrder.Clear();
- for(int i = 0; i < SK.group.n; i++)
- SK.groupOrder.Add(&SK.group.elem[i].h);
+ for(auto &g : SK.group) { SK.groupOrder.Add(&g.h); }
std::sort(SK.groupOrder.begin(), SK.groupOrder.end(),
[](const hGroup &ha, const hGroup &hb) {
return SK.GetGroup(ha)->order < SK.GetGroup(hb)->order;
@@ -159,12 +148,13 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox)
// Start from the first dirty group, and solve until the active group,
// since all groups after the active group are hidden.
+ // Not using range-for because we're tracking the indices.
for(i = 0; i < SK.groupOrder.n; i++) {
- Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
+ Group *g = SK.GetGroup(SK.groupOrder[i]);
if((!g->clean) || !g->IsSolvedOkay()) {
first = min(first, i);
}
- if(g->h.v == SS.GW.activeGroup.v) {
+ if(g->h == SS.GW.activeGroup) {
last = i;
}
}
@@ -190,7 +180,7 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox)
case Generate::UNTIL_ACTIVE: {
for(i = 0; i < SK.groupOrder.n; i++) {
- if(SK.groupOrder.elem[i].v == SS.GW.activeGroup.v)
+ if(SK.groupOrder[i] == SS.GW.activeGroup)
break;
}
@@ -224,8 +214,9 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox)
SK.entity.Clear();
SK.entity.ReserveMore(oldEntityCount);
+ // Not using range-for because we're using the index inside the loop.
for(i = 0; i < SK.groupOrder.n; i++) {
- Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
+ Group *g = SK.GetGroup(SK.groupOrder[i]);
// The group may depend on entities or other groups, to define its
// workplane geometry or for its operands. Those must already exist
@@ -233,15 +224,15 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox)
if(PruneGroups(g->h))
goto pruned;
- for(j = 0; j < SK.request.n; j++) {
- Request *r = &(SK.request.elem[j]);
- if(r->group.v != g->h.v) continue;
+ for(auto &req : SK.request) {
+ Request *r = &req;
+ if(r->group != g->h) continue;
r->Generate(&(SK.entity), &(SK.param));
}
- for(j = 0; j < SK.constraint.n; j++) {
- Constraint *c = &SK.constraint.elem[j];
- if(c->group.v != g->h.v) continue;
+ for(auto &con : SK.constraint) {
+ Constraint *c = &con;
+ if(c->group != g->h) continue;
c->Generate(&(SK.param));
}
@@ -254,8 +245,8 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox)
// Use the previous values for params that we've seen before, as
// initial guesses for the solver.
- for(j = 0; j < SK.param.n; j++) {
- Param *newp = &(SK.param.elem[j]);
+ for(auto &p : SK.param) {
+ Param *newp = &p;
if(newp->known) continue;
Param *prevp = prev.FindByIdNoOops(newp->h);
@@ -265,11 +256,12 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox)
}
}
- if(g->h.v == Group::HGROUP_REFERENCES.v) {
+ if(g->h == Group::HGROUP_REFERENCES) {
ForceReferences();
g->solved.how = SolveResult::OKAY;
g->clean = true;
} else {
+ // this i is an index in groupOrder
if(i >= first && i <= last) {
// The group falls inside the range, so really solve it,
// and then regenerate the mesh based on the solved stuff.
@@ -284,8 +276,8 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox)
// The group falls outside the range, so just assume that
// it's good wherever we left it. The mesh is unchanged,
// and the parameters must be marked as known.
- for(j = 0; j < SK.param.n; j++) {
- Param *newp = &(SK.param.elem[j]);
+ for(auto &p : SK.param) {
+ Param *newp = &p;
Param *prevp = prev.FindByIdNoOops(newp->h);
if(prevp) newp->known = true;
@@ -295,8 +287,8 @@ void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox)
}
// And update any reference dimensions with their new values
- for(i = 0; i < SK.constraint.n; i++) {
- Constraint *c = &(SK.constraint.elem[i]);
+ for(auto &con : SK.constraint) {
+ Constraint *c = &con;
if(c->reference) {
c->ModifyToSatisfy();
}
@@ -433,7 +425,7 @@ void SolveSpaceUI::MarkDraggedParams() {
if(i == -1) {
hp = SS.GW.pending.point;
} else {
- hp = SS.GW.pending.points.elem[i];
+ hp = SS.GW.pending.points[i];
}
if(!hp.v) continue;
@@ -445,6 +437,7 @@ void SolveSpaceUI::MarkDraggedParams() {
switch(pt->type) {
case Entity::Type::POINT_N_TRANS:
case Entity::Type::POINT_IN_3D:
+ case Entity::Type::POINT_N_ROT_AXIS_TRANS:
sys.dragged.Add(&(pt->param[0]));
sys.dragged.Add(&(pt->param[1]));
sys.dragged.Add(&(pt->param[2]));
@@ -504,21 +497,20 @@ void SolveSpaceUI::SolveGroupAndReport(hGroup hg, bool andFindFree) {
}
void SolveSpaceUI::WriteEqSystemForGroup(hGroup hg) {
- int i;
// Clear out the system to be solved.
sys.entity.Clear();
sys.param.Clear();
sys.eq.Clear();
// And generate all the params for requests in this group
- for(i = 0; i < SK.request.n; i++) {
- Request *r = &(SK.request.elem[i]);
- if(r->group.v != hg.v) continue;
+ for(auto &req : SK.request) {
+ Request *r = &req;
+ if(r->group != hg) continue;
r->Generate(&(sys.entity), &(sys.param));
}
- for(i = 0; i < SK.constraint.n; i++) {
- Constraint *c = &SK.constraint.elem[i];
- if(c->group.v != hg.v) continue;
+ for(auto &con : SK.constraint) {
+ Constraint *c = &con;
+ if(c->group != hg) continue;
c->Generate(&(sys.param));
}
@@ -526,8 +518,8 @@ void SolveSpaceUI::WriteEqSystemForGroup(hGroup hg) {
Group *g = SK.GetGroup(hg);
g->Generate(&(sys.entity), &(sys.param));
// Set the initial guesses for all the params
- for(i = 0; i < sys.param.n; i++) {
- Param *p = &(sys.param.elem[i]);
+ for(auto ¶m : sys.param) {
+ Param *p = ¶m;
p->known = false;
p->val = SK.GetParam(p->h)->val;
}
@@ -562,10 +554,10 @@ SolveResult SolveSpaceUI::TestRankForGroup(hGroup hg, int *rank) {
bool SolveSpaceUI::ActiveGroupsOkay() {
for(int i = 0; i < SK.groupOrder.n; i++) {
- Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
+ Group *g = SK.GetGroup(SK.groupOrder[i]);
if(!g->IsSolvedOkay())
return false;
- if(g->h.v == SS.GW.activeGroup.v)
+ if(g->h == SS.GW.activeGroup)
break;
}
return true;
diff --git a/src/graphicswin.cpp b/src/graphicswin.cpp
index 92744399..0d520dd2 100644
--- a/src/graphicswin.cpp
+++ b/src/graphicswin.cpp
@@ -105,7 +105,9 @@ const MenuEntry Menu[] = {
{ 1, N_("Step &Rotating"), Command::GROUP_ROT, S|'r', KN, mGrp },
{ 1, NULL, Command::NONE, 0, KN, NULL },
{ 1, N_("E&xtrude"), Command::GROUP_EXTRUDE, S|'x', KN, mGrp },
+{ 1, N_("&Helix"), Command::GROUP_HELIX, S|'h', KN, mGrp },
{ 1, N_("&Lathe"), Command::GROUP_LATHE, S|'l', KN, mGrp },
+{ 1, N_("Re&volve"), Command::GROUP_REVOLVE, S|'v', KN, mGrp },
{ 1, NULL, Command::NONE, 0, KN, NULL },
{ 1, N_("Link / Assemble..."), Command::GROUP_LINK, S|'i', KN, mGrp },
{ 1, N_("Link Recent"), Command::GROUP_RECENT, 0, KN, mGrp },
@@ -379,7 +381,9 @@ void GraphicsWindow::Init() {
orig.projUp = projUp;
// And with the last group active
- activeGroup = SK.groupOrder.elem[SK.groupOrder.n - 1];
+ ssassert(!SK.groupOrder.IsEmpty(),
+ "Group order can't be empty since we will activate the last group.");
+ activeGroup = *SK.groupOrder.Last();
SK.GetGroup(activeGroup)->Activate();
showWorkplanes = false;
@@ -571,7 +575,7 @@ void GraphicsWindow::LoopOverPoints(const std::vector &entities,
Group *g = SK.GetGroup(activeGroup);
g->GenerateDisplayItems();
for(int i = 0; i < g->displayMesh.l.n; i++) {
- STriangle *tr = &(g->displayMesh.l.elem[i]);
+ STriangle *tr = &(g->displayMesh.l[i]);
if(!includeMesh) {
bool found = false;
for(const hEntity &face : faces) {
@@ -587,9 +591,9 @@ void GraphicsWindow::LoopOverPoints(const std::vector &entities,
}
if(!includeMesh) return;
for(int i = 0; i < g->polyLoops.l.n; i++) {
- SContour *sc = &(g->polyLoops.l.elem[i]);
+ SContour *sc = &(g->polyLoops.l[i]);
for(int j = 0; j < sc->l.n; j++) {
- HandlePointForZoomToFit(sc->l.elem[j].p, pmax, pmin, wmin, usePerspective, camera);
+ HandlePointForZoomToFit(sc->l[j].p, pmax, pmin, wmin, usePerspective, camera);
}
}
}
@@ -606,7 +610,7 @@ double GraphicsWindow::ZoomToFit(const Camera &camera,
if(useSelection) {
for(int i = 0; i < selection.n; i++) {
- Selection *s = &selection.elem[i];
+ Selection *s = &selection[i];
if(s->entity.v != 0) {
Entity *e = SK.entity.FindById(s->entity);
if(e->IsFace()) {
@@ -844,10 +848,11 @@ void GraphicsWindow::EnsureValidActives() {
bool change = false;
// The active group must exist, and not be the references.
Group *g = SK.group.FindByIdNoOops(activeGroup);
- if((!g) || (g->h.v == Group::HGROUP_REFERENCES.v)) {
+ if((!g) || (g->h == Group::HGROUP_REFERENCES)) {
+ // Not using range-for because this is used to find an index.
int i;
for(i = 0; i < SK.groupOrder.n; i++) {
- if(SK.groupOrder.elem[i].v != Group::HGROUP_REFERENCES.v) {
+ if(SK.groupOrder[i] != Group::HGROUP_REFERENCES) {
break;
}
}
@@ -863,7 +868,7 @@ void GraphicsWindow::EnsureValidActives() {
// do it now so that drawing mode isn't switched to "Free in 3d".
SS.GenerateAll(SolveSpaceUI::Generate::ALL);
} else {
- activeGroup = SK.groupOrder.elem[i];
+ activeGroup = SK.groupOrder[i];
}
SK.GetGroup(activeGroup)->Activate();
change = true;
@@ -874,7 +879,7 @@ void GraphicsWindow::EnsureValidActives() {
Entity *e = SK.entity.FindByIdNoOops(ActiveWorkplane());
if(e) {
hGroup hgw = e->group;
- if(hgw.v != activeGroup.v && SS.GroupsInOrder(activeGroup, hgw)) {
+ if(hgw != activeGroup && SS.GroupsInOrder(activeGroup, hgw)) {
// The active workplane is in a group that comes after the
// active group; so any request or constraint will fail.
SetWorkplaneFreeIn3d();
@@ -931,7 +936,7 @@ hEntity GraphicsWindow::ActiveWorkplane() {
}
}
bool GraphicsWindow::LockedInWorkplane() {
- return (SS.GW.ActiveWorkplane().v != Entity::FREE_IN_3D.v);
+ return (SS.GW.ActiveWorkplane() != Entity::FREE_IN_3D);
}
void GraphicsWindow::ForceTextWindowShown() {
@@ -1026,7 +1031,7 @@ void GraphicsWindow::MenuEdit(Command id) {
case Command::SELECT_ALL: {
Entity *e;
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
- if(e->group.v != SS.GW.activeGroup.v) continue;
+ if(e->group != SS.GW.activeGroup) continue;
if(e->IsFace() || e->IsDistance()) continue;
if(!e->IsVisible()) continue;
@@ -1044,7 +1049,7 @@ void GraphicsWindow::MenuEdit(Command id) {
do {
didSomething = false;
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
- if(e->group.v != SS.GW.activeGroup.v) continue;
+ if(e->group != SS.GW.activeGroup) continue;
if(!e->HasEndpoints()) continue;
if(!e->IsVisible()) continue;
@@ -1055,7 +1060,7 @@ void GraphicsWindow::MenuEdit(Command id) {
List *ls = &(SS.GW.selection);
for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {
if(!s->entity.v) continue;
- if(s->entity.v == e->h.v) {
+ if(s->entity == e->h) {
alreadySelected = true;
continue;
}
@@ -1302,7 +1307,7 @@ void GraphicsWindow::ToggleBool(bool *v) {
// If the mesh or edges were previously hidden, they haven't been generated,
// and if we are going to show them, we need to generate them first.
Group *g = SK.GetGroup(SS.GW.activeGroup);
- if(*v && (g->displayOutlines.l.n == 0 && (v == &showEdges || v == &showOutlines))) {
+ if(*v && (g->displayOutlines.l.IsEmpty() && (v == &showEdges || v == &showOutlines))) {
SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE);
}
diff --git a/src/group.cpp b/src/group.cpp
index 30b9270a..3e863f55 100644
--- a/src/group.cpp
+++ b/src/group.cpp
@@ -49,8 +49,8 @@ bool Group::IsVisible() {
return true;
}
-size_t Group::GetNumConstraints(void) {
- return SK.constraint.CountIf([&](Constraint const & c) { return c.group.v == h.v; });
+size_t Group::GetNumConstraints() {
+ return SK.constraint.CountIf([&](Constraint const & c) { return c.group == h; });
}
Vector Group::ExtrusionGetVector() {
@@ -163,6 +163,10 @@ void Group::MenuGroup(Command id, Platform::Path linkFile) {
break;
case Command::GROUP_LATHE:
+ if(!SS.GW.LockedInWorkplane()) {
+ Error(_("Lathe operation can only be applied to planar sketches."));
+ return;
+ }
if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) {
g.predef.origin = gs.point[0];
g.predef.entityB = gs.vector[0];
@@ -184,6 +188,62 @@ void Group::MenuGroup(Command id, Platform::Path linkFile) {
g.name = C_("group-name", "lathe");
break;
+ case Command::GROUP_REVOLVE:
+ if(!SS.GW.LockedInWorkplane()) {
+ Error(_("Revolve operation can only be applied to planar sketches."));
+ return;
+ }
+ if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) {
+ g.predef.origin = gs.point[0];
+ g.predef.entityB = gs.vector[0];
+ } else if(gs.lineSegments == 1 && gs.n == 1) {
+ g.predef.origin = SK.GetEntity(gs.entity[0])->point[0];
+ g.predef.entityB = gs.entity[0];
+ // since a line segment is a vector
+ } else {
+ Error(_("Bad selection for new revolve group. This group can "
+ "be created with:\n\n"
+ " * a point and a line segment or normal "
+ "(revolved about an axis parallel to line / "
+ "normal, through point)\n"
+ " * a line segment (revolved about line segment)\n"));
+ return;
+ }
+ g.type = Type::REVOLVE;
+ g.opA = SS.GW.activeGroup;
+ g.valA = 2;
+ g.subtype = Subtype::ONE_SIDED;
+ g.name = C_("group-name", "revolve");
+ break;
+
+ case Command::GROUP_HELIX:
+ if(!SS.GW.LockedInWorkplane()) {
+ Error(_("Helix operation can only be applied to planar sketches."));
+ return;
+ }
+ if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) {
+ g.predef.origin = gs.point[0];
+ g.predef.entityB = gs.vector[0];
+ } else if(gs.lineSegments == 1 && gs.n == 1) {
+ g.predef.origin = SK.GetEntity(gs.entity[0])->point[0];
+ g.predef.entityB = gs.entity[0];
+ // since a line segment is a vector
+ } else {
+ Error(_("Bad selection for new helix group. This group can "
+ "be created with:\n\n"
+ " * a point and a line segment or normal "
+ "(revolved about an axis parallel to line / "
+ "normal, through point)\n"
+ " * a line segment (revolved about line segment)\n"));
+ return;
+ }
+ g.type = Type::HELIX;
+ g.opA = SS.GW.activeGroup;
+ g.valA = 2;
+ g.subtype = Subtype::ONE_SIDED;
+ g.name = C_("group-name", "helix");
+ break;
+
case Command::GROUP_ROT: {
if(gs.points == 1 && gs.n == 1 && SS.GW.LockedInWorkplane()) {
g.predef.origin = gs.point[0];
@@ -249,7 +309,7 @@ void Group::MenuGroup(Command id, Platform::Path linkFile) {
}
// Copy color from the previous mesh-contributing group.
- if(g.IsMeshGroup() && SK.groupOrder.n > 0) {
+ if(g.IsMeshGroup() && !SK.groupOrder.IsEmpty()) {
Group *running = SK.GetRunningMeshGroupFor(SS.GW.activeGroup);
if(running != NULL) {
g.color = running->color;
@@ -260,11 +320,11 @@ void Group::MenuGroup(Command id, Platform::Path linkFile) {
SS.UndoRemember();
bool afterActive = false;
- for(int i = 0; i < SK.groupOrder.n; i++) {
- Group *gi = SK.GetGroup(SK.groupOrder.elem[i]);
+ for(hGroup hg : SK.groupOrder) {
+ Group *gi = SK.GetGroup(hg);
if(afterActive)
gi->order += 1;
- if(gi->h.v == SS.GW.activeGroup.v) {
+ if(gi->h == SS.GW.activeGroup) {
g.order = gi->order + 1;
afterActive = true;
}
@@ -344,7 +404,7 @@ std::string Group::DescriptionString() {
void Group::Activate() {
if(type == Type::EXTRUDE || type == Type::LINKED || type == Type::LATHE ||
- type == Type::TRANSLATE || type == Type::ROTATE) {
+ type == Type::REVOLVE || type == Type::HELIX || type == Type::TRANSLATE || type == Type::ROTATE) {
SS.GW.showFaces = true;
} else {
SS.GW.showFaces = false;
@@ -424,9 +484,10 @@ void Group::Generate(IdList *entity,
// Get some arbitrary point in the sketch, that will be used
// as a reference when defining top and bottom faces.
hEntity pt = { 0 };
+ // Not using range-for here because we're changing the size of entity in the loop.
for(i = 0; i < entity->n; i++) {
- Entity *e = &(entity->elem[i]);
- if(e->group.v != opA.v) continue;
+ Entity *e = &(entity->Get(i));
+ if(e->group != opA) continue;
if(e->IsPoint()) pt = e->h;
@@ -436,11 +497,11 @@ void Group::Generate(IdList *entity,
// adds entities, which may cause a realloc.
CopyEntity(entity, SK.GetEntity(he), ai, REMAP_BOTTOM,
h.param(0), h.param(1), h.param(2),
- NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
+ NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
CopyAs::N_TRANS);
CopyEntity(entity, SK.GetEntity(he), af, REMAP_TOP,
h.param(0), h.param(1), h.param(2),
- NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
+ NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
CopyAs::N_TRANS);
MakeExtrusionLines(entity, he);
}
@@ -457,9 +518,10 @@ void Group::Generate(IdList *entity,
// Remapped entity index.
int ai = 1;
+ // Not using range-for here because we're changing the size of entity in the loop.
for(i = 0; i < entity->n; i++) {
- Entity *e = &(entity->elem[i]);
- if(e->group.v != opA.v) continue;
+ Entity *e = &(entity->Get(i));
+ if(e->group != opA) continue;
e->CalculateNumerical(/*forExport=*/false);
hEntity he = e->h;
@@ -468,20 +530,133 @@ void Group::Generate(IdList *entity,
// adds entities, which may cause a realloc.
CopyEntity(entity, SK.GetEntity(predef.origin), 0, ai,
NO_PARAM, NO_PARAM, NO_PARAM,
- NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
+ NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
CopyAs::NUMERIC);
CopyEntity(entity, SK.GetEntity(he), 0, REMAP_LATHE_START,
NO_PARAM, NO_PARAM, NO_PARAM,
- NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
+ NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
CopyAs::NUMERIC);
CopyEntity(entity, SK.GetEntity(he), 0, REMAP_LATHE_END,
NO_PARAM, NO_PARAM, NO_PARAM,
- NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
+ NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
CopyAs::NUMERIC);
MakeLatheCircles(entity, param, he, axis_pos, axis_dir, ai);
+ MakeLatheSurfacesSelectable(entity, he, axis_dir);
+ ai++;
+ }
+ return;
+ }
+
+ case Type::REVOLVE: {
+ // this was borrowed from LATHE and ROTATE
+ Vector axis_pos = SK.GetEntity(predef.origin)->PointGetNum();
+ Vector axis_dir = SK.GetEntity(predef.entityB)->VectorGetNum();
+
+ // The center of rotation
+ AddParam(param, h.param(0), axis_pos.x);
+ AddParam(param, h.param(1), axis_pos.y);
+ AddParam(param, h.param(2), axis_pos.z);
+ // The rotation quaternion
+ AddParam(param, h.param(3), 30 * PI / 180);
+ AddParam(param, h.param(4), axis_dir.x);
+ AddParam(param, h.param(5), axis_dir.y);
+ AddParam(param, h.param(6), axis_dir.z);
+
+ int ai = 1;
+
+ // Not using range-for here because we're changing the size of entity in the loop.
+ for(i = 0; i < entity->n; i++) {
+ Entity *e = &(entity->Get(i));
+ if(e->group != opA)
+ continue;
+
+ e->CalculateNumerical(/*forExport=*/false);
+ hEntity he = e->h;
+
+ CopyEntity(entity, SK.GetEntity(predef.origin), 0, ai, NO_PARAM, NO_PARAM,
+ NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, CopyAs::NUMERIC);
+
+ for(a = 0; a < 2; a++) {
+ //! @todo is this check redundant?
+ Entity *e = &(entity->Get(i));
+ if(e->group != opA)
+ continue;
+
+ e->CalculateNumerical(false);
+ CopyEntity(entity, e, a * 2 - (subtype == Subtype::ONE_SIDED ? 0 : 1),
+ (a == 1) ? REMAP_LATHE_END : REMAP_LATHE_START, h.param(0),
+ h.param(1), h.param(2), h.param(3), h.param(4), h.param(5),
+ h.param(6), NO_PARAM, CopyAs::N_ROT_AA);
+ }
+ // Arcs are not generated for revolve groups, for now, because our current arc
+ // entity is not chiral, and dragging a revolve may break the arc by inverting it.
+ // MakeLatheCircles(entity, param, he, axis_pos, axis_dir, ai);
+ MakeLatheSurfacesSelectable(entity, he, axis_dir);
+ ai++;
+ }
+
+ return;
+ }
+
+ case Type::HELIX: {
+ Vector axis_pos = SK.GetEntity(predef.origin)->PointGetNum();
+ Vector axis_dir = SK.GetEntity(predef.entityB)->VectorGetNum();
+
+ // The center of rotation
+ AddParam(param, h.param(0), axis_pos.x);
+ AddParam(param, h.param(1), axis_pos.y);
+ AddParam(param, h.param(2), axis_pos.z);
+ // The rotation quaternion
+ AddParam(param, h.param(3), 30 * PI / 180);
+ AddParam(param, h.param(4), axis_dir.x);
+ AddParam(param, h.param(5), axis_dir.y);
+ AddParam(param, h.param(6), axis_dir.z);
+ // distance to translate along the rotation axis
+ AddParam(param, h.param(7), 20);
+
+ int ai = 1;
+
+ // Not using range-for here because we're changing the size of entity in the loop.
+ for(i = 0; i < entity->n; i++) {
+ Entity *e = &(entity->Get(i));
+ if((e->group.v != opA.v) && !(e->h == predef.origin))
+ continue;
+
+ e->CalculateNumerical(/*forExport=*/false);
+
+ CopyEntity(entity, SK.GetEntity(predef.origin), 0, ai, NO_PARAM, NO_PARAM,
+ NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, CopyAs::NUMERIC);
+
+ for(a = 0; a < 2; a++) {
+ Entity *e = &(entity->Get(i));
+ e->CalculateNumerical(false);
+ CopyEntity(entity, e, a * 2 - (subtype == Subtype::ONE_SIDED ? 0 : 1),
+ (a == 1) ? REMAP_LATHE_END : REMAP_LATHE_START, h.param(0),
+ h.param(1), h.param(2), h.param(3), h.param(4), h.param(5),
+ h.param(6), h.param(7), CopyAs::N_ROT_AXIS_TRANS);
+ }
+ // For point entities on the axis, create a construction line
+ e = &(entity->Get(i));
+ if(e->IsPoint()) {
+ Vector check = e->PointGetNum().Minus(axis_pos).Cross(axis_dir);
+ if (check.Dot(check) < LENGTH_EPS) {
+ //! @todo isn't this the same as &(ent[i])?
+ Entity *ep = SK.GetEntity(e->h);
+ Entity en = {};
+ // A point gets extruded to form a line segment
+ en.point[0] = Remap(ep->h, REMAP_LATHE_START);
+ en.point[1] = Remap(ep->h, REMAP_LATHE_END);
+ en.group = h;
+ en.construction = ep->construction;
+ en.style = ep->style;
+ en.h = Remap(ep->h, REMAP_PT_TO_LINE);
+ en.type = Entity::Type::LINE_SEGMENT;
+ entity->Add(&en);
+ }
+ }
ai++;
}
return;
@@ -502,16 +677,17 @@ void Group::Generate(IdList *entity,
}
for(a = a0; a < n; a++) {
+ // Not using range-for here because we're changing the size of entity in the loop.
for(i = 0; i < entity->n; i++) {
- Entity *e = &(entity->elem[i]);
- if(e->group.v != opA.v) continue;
+ Entity *e = &(entity->Get(i));
+ if(e->group != opA) continue;
e->CalculateNumerical(/*forExport=*/false);
CopyEntity(entity, e,
a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1)),
(a == (n - 1)) ? REMAP_LAST : a,
h.param(0), h.param(1), h.param(2),
- NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
+ NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM,
CopyAs::N_TRANS);
}
}
@@ -537,16 +713,17 @@ void Group::Generate(IdList *entity,
}
for(a = a0; a < n; a++) {
+ // Not using range-for here because we're changing the size of entity in the loop.
for(i = 0; i < entity->n; i++) {
- Entity *e = &(entity->elem[i]);
- if(e->group.v != opA.v) continue;
+ Entity *e = &(entity->Get(i));
+ if(e->group != opA) continue;
e->CalculateNumerical(/*forExport=*/false);
CopyEntity(entity, e,
a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1)),
(a == (n - 1)) ? REMAP_LAST : a,
h.param(0), h.param(1), h.param(2),
- h.param(3), h.param(4), h.param(5), h.param(6),
+ h.param(3), h.param(4), h.param(5), h.param(6), NO_PARAM,
CopyAs::N_ROT_AA);
}
}
@@ -563,11 +740,12 @@ void Group::Generate(IdList *entity,
AddParam(param, h.param(5), 0);
AddParam(param, h.param(6), 0);
+ // Not using range-for here because we're changing the size of entity in the loop.
for(i = 0; i < impEntity.n; i++) {
- Entity *ie = &(impEntity.elem[i]);
+ Entity *ie = &(impEntity[i]);
CopyEntity(entity, ie, 0, 0,
h.param(0), h.param(1), h.param(2),
- h.param(3), h.param(4), h.param(5), h.param(6),
+ h.param(3), h.param(4), h.param(5), h.param(6), NO_PARAM,
CopyAs::N_ROT_TRANS);
}
return;
@@ -596,7 +774,7 @@ void Group::GenerateEquations(IdList *l) {
Expr::From(h.param(5)),
Expr::From(h.param(6)) };
AddEq(l, (q.Magnitude())->Minus(Expr::From(1)), 0);
- } else if(type == Type::ROTATE) {
+ } else if(type == Type::ROTATE || type == Type::REVOLVE || type == Type::HELIX) {
// The axis and center of rotation are specified numerically
#define EC(x) (Expr::From(x))
#define EP(x) (Expr::From(h.param(x)))
@@ -613,7 +791,7 @@ void Group::GenerateEquations(IdList *l) {
#undef EC
#undef EP
} else if(type == Type::EXTRUDE) {
- if(predef.entityB.v != Entity::FREE_IN_3D.v) {
+ if(predef.entityB != Entity::FREE_IN_3D) {
// The extrusion path is locked along a line, normal to the
// specified workplane.
Entity *w = SK.GetEntity(predef.entityB);
@@ -628,7 +806,7 @@ void Group::GenerateEquations(IdList *l) {
AddEq(l, v.Dot(extruden), 1);
}
} else if(type == Type::TRANSLATE) {
- if(predef.entityB.v != Entity::FREE_IN_3D.v) {
+ if(predef.entityB != Entity::FREE_IN_3D) {
Entity *w = SK.GetEntity(predef.entityB);
ExprVector n = w->Normal()->NormalExprsN();
ExprVector trans;
@@ -644,7 +822,7 @@ hEntity Group::Remap(hEntity in, int copyNumber) {
auto it = remap.find({ in, copyNumber });
if(it == remap.end()) {
std::tie(it, std::ignore) =
- remap.insert({ { in, copyNumber }, { (uint32_t)remap.size() } });
+ remap.insert({ { in, copyNumber }, { (uint32_t)remap.size() + 1 } });
}
return h.entity(it->second.v);
}
@@ -729,7 +907,14 @@ void Group::MakeLatheCircles(IdList *el, IdList *p
el->Add(&n);
en.normal = n.h;
el->Add(&en);
- } else if(ep->type == Entity::Type::LINE_SEGMENT) {
+ }
+}
+
+void Group::MakeLatheSurfacesSelectable(IdList *el, hEntity in, Vector axis) {
+ Entity *ep = SK.GetEntity(in);
+
+ Entity en = {};
+ if(ep->type == Entity::Type::LINE_SEGMENT) {
// An axis-perpendicular line gets revolved to form a face.
Vector a = SK.GetEntity(ep->point[0])->PointGetNum();
Vector b = SK.GetEntity(ep->point[1])->PointGetNum();
@@ -786,7 +971,7 @@ void Group::MakeExtrusionTopBottomFaces(IdList *el, hEntity pt)
void Group::CopyEntity(IdList *el,
Entity *ep, int timesApplied, int remap,
hParam dx, hParam dy, hParam dz,
- hParam qw, hParam qvx, hParam qvy, hParam qvz,
+ hParam qw, hParam qvx, hParam qvy, hParam qvz, hParam dist,
CopyAs as)
{
Entity en = {};
@@ -810,6 +995,7 @@ void Group::CopyEntity(IdList *el,
case Entity::Type::POINT_N_TRANS:
case Entity::Type::POINT_N_ROT_TRANS:
case Entity::Type::POINT_N_ROT_AA:
+ case Entity::Type::POINT_N_ROT_AXIS_TRANS:
case Entity::Type::POINT_IN_3D:
case Entity::Type::POINT_IN_2D:
if(as == CopyAs::N_TRANS) {
@@ -822,6 +1008,8 @@ void Group::CopyEntity(IdList *el,
} else {
if(as == CopyAs::N_ROT_AA) {
en.type = Entity::Type::POINT_N_ROT_AA;
+ } else if (as == CopyAs::N_ROT_AXIS_TRANS) {
+ en.type = Entity::Type::POINT_N_ROT_AXIS_TRANS;
} else {
en.type = Entity::Type::POINT_N_ROT_TRANS;
}
@@ -832,6 +1020,9 @@ void Group::CopyEntity(IdList *el,
en.param[4] = qvx;
en.param[5] = qvy;
en.param[6] = qvz;
+ if (as == CopyAs::N_ROT_AXIS_TRANS) {
+ en.param[7] = dist;
+ }
}
en.numPoint = (ep->actPoint).ScaledBy(scale);
break;
@@ -843,8 +1034,8 @@ void Group::CopyEntity(IdList *el,
case Entity::Type::NORMAL_IN_2D:
if(as == CopyAs::N_TRANS || as == CopyAs::NUMERIC) {
en.type = Entity::Type::NORMAL_N_COPY;
- } else {
- if(as == CopyAs::N_ROT_AA) {
+ } else { // N_ROT_AXIS_TRANS probably doesn't warrant a new entity Type
+ if(as == CopyAs::N_ROT_AA || as == CopyAs::N_ROT_AXIS_TRANS) {
en.type = Entity::Type::NORMAL_N_ROT_AA;
} else {
en.type = Entity::Type::NORMAL_N_ROT;
@@ -879,7 +1070,7 @@ void Group::CopyEntity(IdList *el,
} else if (as == CopyAs::NUMERIC) {
en.type = Entity::Type::FACE_NORMAL_PT;
} else {
- if(as == CopyAs::N_ROT_AA) {
+ if(as == CopyAs::N_ROT_AA || as == CopyAs::N_ROT_AXIS_TRANS) {
en.type = Entity::Type::FACE_N_ROT_AA;
} else {
en.type = Entity::Type::FACE_N_ROT_TRANS;
diff --git a/src/groupmesh.cpp b/src/groupmesh.cpp
index 19fe18f7..b2e390e1 100644
--- a/src/groupmesh.cpp
+++ b/src/groupmesh.cpp
@@ -14,13 +14,15 @@ void Group::AssembleLoops(bool *allClosed,
SBezierList sbl = {};
int i;
- for(i = 0; i < SK.entity.n; i++) {
- Entity *e = &(SK.entity.elem[i]);
- if(e->group.v != h.v) continue;
- if(e->construction) continue;
- if(e->forceHidden) continue;
+ for(auto &e : SK.entity) {
+ if(e.group != h)
+ continue;
+ if(e.construction)
+ continue;
+ if(e.forceHidden)
+ continue;
- e->GenerateBezierCurves(&sbl);
+ e.GenerateBezierCurves(&sbl);
}
SBezier *sb;
@@ -84,7 +86,7 @@ void SShell::RemapFaces(Group *g, int remap) {
SSurface *ss;
for(ss = surface.First(); ss; ss = surface.NextAfter(ss)){
hEntity face = { ss->face };
- if(face.v == Entity::NO_ENTITY.v) continue;
+ if(face == Entity::NO_ENTITY) continue;
face = g->Remap(face, remap);
ss->face = face.v;
@@ -95,7 +97,7 @@ void SMesh::RemapFaces(Group *g, int remap) {
STriangle *tr;
for(tr = l.First(); tr; tr = l.NextAfter(tr)) {
hEntity face = { tr->meta.face };
- if(face.v == Entity::NO_ENTITY.v) continue;
+ if(face == Entity::NO_ENTITY) continue;
face = g->Remap(face, remap);
tr->meta.face = face.v;
@@ -191,7 +193,7 @@ void Group::GenerateShellAndMesh() {
// Don't attempt a lathe or extrusion unless the source section is good:
// planar and not self-intersecting.
bool haveSrc = true;
- if(type == Type::EXTRUDE || type == Type::LATHE) {
+ if(type == Type::EXTRUDE || type == Type::LATHE || type == Type::REVOLVE) {
Group *src = SK.GetGroup(opA);
if(src->polyError.how != PolyError::GOOD) {
haveSrc = false;
@@ -235,8 +237,10 @@ void Group::GenerateShellAndMesh() {
// that face, so that the user can select them with the mouse.
Vector onOrig = sbls->point;
int i;
+ // Not using range-for here because we're starting at a different place and using
+ // indices for meaning.
for(i = is; i < thisShell.surface.n; i++) {
- SSurface *ss = &(thisShell.surface.elem[i]);
+ SSurface *ss = &(thisShell.surface[i]);
hEntity face = Entity::NO_ENTITY;
Vector p = ss->PointAt(0, 0),
@@ -261,7 +265,7 @@ void Group::GenerateShellAndMesh() {
Entity *e;
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
- if(e->group.v != opA.v) continue;
+ if(e->group != opA) continue;
if(e->type != Entity::Type::LINE_SEGMENT) continue;
Vector a = SK.GetEntity(e->point[0])->PointGetNum(),
@@ -293,6 +297,51 @@ void Group::GenerateShellAndMesh() {
for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
thisShell.MakeFromRevolutionOf(sbls, pt, axis, color, this);
}
+ } else if(type == Type::REVOLVE && haveSrc) {
+ Group *src = SK.GetGroup(opA);
+ double anglef = SK.GetParam(h.param(3))->val * 4; // why the 4 is needed?
+ double dists = 0, distf = 0;
+ double angles = 0.0;
+ if(subtype != Subtype::ONE_SIDED) {
+ anglef *= 0.5;
+ angles = -anglef;
+ }
+ Vector pt = SK.GetEntity(predef.origin)->PointGetNum(),
+ axis = SK.GetEntity(predef.entityB)->VectorGetNum();
+ axis = axis.WithMagnitude(1);
+
+ SBezierLoopSetSet *sblss = &(src->bezierLoops);
+ SBezierLoopSet *sbls;
+ for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
+ if(fabs(anglef - angles) < 2 * PI) {
+ thisShell.MakeFromHelicalRevolutionOf(sbls, pt, axis, color, this,
+ angles, anglef, dists, distf);
+ } else {
+ thisShell.MakeFromRevolutionOf(sbls, pt, axis, color, this);
+ }
+ }
+ } else if(type == Type::HELIX && haveSrc) {
+ Group *src = SK.GetGroup(opA);
+ double anglef = SK.GetParam(h.param(3))->val * 4; // why the 4 is needed?
+ double dists = 0, distf = 0;
+ double angles = 0.0;
+ distf = SK.GetParam(h.param(7))->val * 2; // dist is applied twice
+ if(subtype != Subtype::ONE_SIDED) {
+ anglef *= 0.5;
+ angles = -anglef;
+ distf *= 0.5;
+ dists = -distf;
+ }
+ Vector pt = SK.GetEntity(predef.origin)->PointGetNum(),
+ axis = SK.GetEntity(predef.entityB)->VectorGetNum();
+ axis = axis.WithMagnitude(1);
+
+ SBezierLoopSetSet *sblss = &(src->bezierLoops);
+ SBezierLoopSet *sbls;
+ for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
+ thisShell.MakeFromHelicalRevolutionOf(sbls, pt, axis, color, this,
+ angles, anglef, dists, distf);
+ }
} else if(type == Type::LINKED) {
// The imported shell or mesh are copied over, with the appropriate
// transformation applied. We also must remap the face entities.
@@ -417,7 +466,7 @@ void Group::GenerateDisplayItems() {
if(SS.GW.showEdges || SS.GW.showOutlines) {
SOutlineList rawOutlines = {};
- if(runningMesh.l.n > 0) {
+ if(!runningMesh.l.IsEmpty()) {
// Triangle mesh only; no shell or emphasized edges.
runningMesh.MakeOutlinesInto(&rawOutlines, EdgeKind::EMPHASIZED);
} else {
@@ -437,7 +486,7 @@ void Group::GenerateDisplayItems() {
displayMesh.PrecomputeTransparency();
// Recalculate mass center if needed
- if(SS.centerOfMass.draw && SS.centerOfMass.dirty && h.v == SS.GW.activeGroup.v) {
+ if(SS.centerOfMass.draw && SS.centerOfMass.dirty && h == SS.GW.activeGroup) {
SS.UpdateCenterOfMass();
}
displayDirty = false;
@@ -445,13 +494,15 @@ void Group::GenerateDisplayItems() {
}
Group *Group::PreviousGroup() const {
- int i;
- for(i = 0; i < SK.groupOrder.n; i++) {
- Group *g = SK.GetGroup(SK.groupOrder.elem[i]);
- if(g->h.v == h.v) break;
+ Group *prev = nullptr;
+ for(auto const &gh : SK.groupOrder) {
+ Group *g = SK.GetGroup(gh);
+ if(g->h == h) {
+ return prev;
+ }
+ prev = g;
}
- if(i == 0 || i >= SK.groupOrder.n) return NULL;
- return SK.GetGroup(SK.groupOrder.elem[i - 1]);
+ return nullptr;
}
Group *Group::RunningMeshGroup() const {
@@ -466,6 +517,8 @@ bool Group::IsMeshGroup() {
switch(type) {
case Group::Type::EXTRUDE:
case Group::Type::LATHE:
+ case Group::Type::REVOLVE:
+ case Group::Type::HELIX:
case Group::Type::ROTATE:
case Group::Type::TRANSLATE:
return true;
@@ -653,11 +706,12 @@ void Group::DrawPolyError(Canvas *canvas) {
void Group::DrawFilledPaths(Canvas *canvas) {
for(const SBezierLoopSet &sbls : bezierLoops.l) {
- if(sbls.l.n == 0 || sbls.l.elem[0].l.n == 0) continue;
+ if(sbls.l.IsEmpty() || sbls.l[0].l.IsEmpty())
+ continue;
// In an assembled loop, all the styles should be the same; so doesn't
// matter which one we grab.
- SBezier *sb = &(sbls.l.elem[0].l.elem[0]);
+ const SBezier *sb = &(sbls.l[0].l[0]);
Style *s = Style::Get({ (uint32_t)sb->auxA });
Canvas::Fill fill = {};
@@ -665,7 +719,7 @@ void Group::DrawFilledPaths(Canvas *canvas) {
if(s->filled) {
// This is a filled loop, where the user specified a fill color.
fill.color = s->fillColor;
- } else if(h.v == SS.GW.activeGroup.v && SS.checkClosedContour &&
+ } else if(h == SS.GW.activeGroup && SS.checkClosedContour &&
polyError.how == PolyError::GOOD) {
// If this is the active group, and we are supposed to check
// for closed contours, and we do indeed have a closed and
@@ -687,9 +741,10 @@ void Group::DrawContourAreaLabels(Canvas *canvas) {
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;
+ if(sbls.l.IsEmpty() || sbls.l[0].l.IsEmpty())
+ continue;
- Vector min = sbls.l.elem[0].l.elem[0].ctrl[0];
+ Vector min = sbls.l[0].l[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);
diff --git a/src/importdxf.cpp b/src/importdxf.cpp
index a6bb305d..2db8836f 100644
--- a/src/importdxf.cpp
+++ b/src/importdxf.cpp
@@ -340,7 +340,7 @@ public:
hStyle styleFor(const DRW_Entity *e) {
// Color.
- // TODO: which color to choose: index or RGB one?
+ //! @todo which color to choose: index or RGB one?
int col = getColor(e);
RgbaColor c = RgbaColor::From(DRW::dxfColors[col][0],
DRW::dxfColors[col][1],
@@ -352,7 +352,7 @@ public:
if(width < 0.0) width = 1.0;
// Line stipple.
- // TODO: Probably, we can load default autocad patterns and match it with ours.
+ //! @todo Probably, we can load default autocad patterns and match it with ours.
std::string lineType = getLineType(e);
StipplePattern stipple = StipplePattern::CONTINUOUS;
for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) {
@@ -455,8 +455,8 @@ public:
Entity *e = SK.GetEntity(he);
Vector pos = e->PointGetNum();
hEntity p = findPoint(pos);
- if(p.v == he.v) return;
- if(p.v != Entity::NO_ENTITY.v) {
+ if(p == he) return;
+ if(p != Entity::NO_ENTITY) {
if(constrain) {
Constraint::ConstrainCoincident(he, p);
}
@@ -475,7 +475,7 @@ public:
hEntity createOrGetPoint(const Vector &p) {
hEntity he = findPoint(p);
- if(he.v != Entity::NO_ENTITY.v) return he;
+ if(he != Entity::NO_ENTITY) return he;
hRequest hr = SS.GW.AddRequest(Request::Type::DATUM_POINT, /*rememberForUndo=*/false);
he = hr.entity(0);
diff --git a/src/lib.cpp b/src/lib.cpp
index a8009248..052768d2 100644
--- a/src/lib.cpp
+++ b/src/lib.cpp
@@ -182,7 +182,7 @@ default: dbp("bad constraint type %d", sc->type); return;
c.other2 = (sc->other2) ? true : false;
c.Generate(¶ms);
- if(params.n > 0) {
+ if(!params.IsEmpty()) {
for(Param &p : params) {
p.h = SK.param.AddAndAssignId(&p);
c.valP = p.h;
@@ -240,7 +240,7 @@ default: dbp("bad constraint type %d", sc->type); return;
if(ssys->failed) {
// Copy over any the list of problematic constraints.
for(i = 0; i < ssys->faileds && i < bad.n; i++) {
- ssys->failed[i] = bad.elem[i].v;
+ ssys->failed[i] = bad[i].v;
}
ssys->faileds = bad.n;
}
diff --git a/src/mesh.cpp b/src/mesh.cpp
index 52f8c0b8..57aa87b4 100644
--- a/src/mesh.cpp
+++ b/src/mesh.cpp
@@ -51,7 +51,7 @@ void SMesh::GetBounding(Vector *vmax, Vector *vmin) const {
*vmin = Vector::From( 1e12, 1e12, 1e12);
*vmax = Vector::From(-1e12, -1e12, -1e12);
for(i = 0; i < l.n; i++) {
- STriangle *st = &(l.elem[i]);
+ const STriangle *st = &(l[i]);
DoBounding(st->a, vmax, vmin);
DoBounding(st->b, vmax, vmin);
DoBounding(st->c, vmax, vmin);
@@ -70,7 +70,7 @@ void SMesh::MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d) {
m.l.ClearTags();
int i;
for(i = 0; i < m.l.n; i++) {
- STriangle *tr = &(m.l.elem[i]);
+ STriangle *tr = &(m.l[i]);
if((fabs(n.Dot(tr->a) - d) >= LENGTH_EPS) ||
(fabs(n.Dot(tr->b) - d) >= LENGTH_EPS) ||
@@ -96,7 +96,7 @@ void SMesh::MakeOutlinesInto(SOutlineList *sol, EdgeKind edgeKind) {
}
//-----------------------------------------------------------------------------
-// When we are called, all of the triangles from l.elem[start] to the end must
+// When we are called, all of the triangles from l[start] to the end must
// be coplanar. So we try to find a set of fewer triangles that covers the
// exact same area, in order to reduce the number of triangles in the mesh.
// We use this after a triangle has been split against the BSP.
@@ -108,7 +108,7 @@ void SMesh::MakeOutlinesInto(SOutlineList *sol, EdgeKind edgeKind) {
void SMesh::Simplify(int start) {
int maxTriangles = (l.n - start) + 10;
- STriMeta meta = l.elem[start].meta;
+ STriMeta meta = l[start].meta;
STriangle *tout = (STriangle *)MemAlloc(maxTriangles*sizeof(*tout));
int toutc = 0;
@@ -121,7 +121,7 @@ void SMesh::Simplify(int start) {
int i, j;
for(i = start; i < l.n; i++) {
- STriangle *tr = &(l.elem[i]);
+ STriangle *tr = &(l[i]);
if(tr->MinAltitude() < LENGTH_EPS) {
tr->tag = 1;
} else {
@@ -133,7 +133,7 @@ void SMesh::Simplify(int start) {
bool didAdd;
convc = 0;
for(i = start; i < l.n; i++) {
- STriangle *tr = &(l.elem[i]);
+ STriangle *tr = &(l[i]);
if(tr->tag) continue;
tr->tag = 1;
@@ -158,7 +158,7 @@ void SMesh::Simplify(int start) {
Vector c;
for(i = start; i < l.n; i++) {
- STriangle *tr = &(l.elem[i]);
+ STriangle *tr = &(l[i]);
if(tr->tag) continue;
if((tr->a).Equals(d) && (tr->b).Equals(b)) {
@@ -242,7 +242,7 @@ void SMesh::AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3) {
int i;
for(i = 0; i < srcm->l.n; i++) {
- STriangle *st = &(srcm->l.elem[i]);
+ STriangle *st = &(srcm->l[i]);
int pn = l.n;
atLeastOneDiscarded = false;
SBsp3::InsertOrCreate(bsp3, st, this);
@@ -289,7 +289,7 @@ void SMesh::MakeFromDifferenceOf(SMesh *a, SMesh *b) {
void SMesh::MakeFromCopyOf(SMesh *a) {
ssassert(this != a, "Can't make from copy of self");
for(int i = 0; i < a->l.n; i++) {
- AddTriangle(&(a->l.elem[i]));
+ AddTriangle(&(a->l[i]));
}
}
@@ -318,9 +318,7 @@ void SMesh::MakeFromTransformationOf(SMesh *a, Vector trans,
}
}
-bool SMesh::IsEmpty() const {
- return (l.n == 0);
-}
+bool SMesh::IsEmpty() const { return (l.IsEmpty()); }
uint32_t SMesh::FirstIntersectionWith(Point2d mp) const {
Vector rayPoint = SS.GW.UnProjectPoint3(Vector::From(mp.x, mp.y, 0.0));
@@ -329,7 +327,7 @@ uint32_t SMesh::FirstIntersectionWith(Point2d mp) const {
uint32_t face = 0;
double faceT = VERY_NEGATIVE;
for(int i = 0; i < l.n; i++) {
- const STriangle &tr = l.elem[i];
+ const STriangle &tr = l[i];
if(tr.meta.face == 0) continue;
double t;
@@ -347,7 +345,7 @@ Vector SMesh::GetCenterOfMass() const {
Vector center = {};
double vol = 0.0;
for(int i = 0; i < l.n; i++) {
- STriangle &tr = l.elem[i];
+ const STriangle &tr = l[i];
double tvol = tr.SignedVolume();
center = center.Plus(tr.a.Plus(tr.b.Plus(tr.c)).ScaledBy(tvol / 4.0));
vol += tvol;
@@ -365,7 +363,7 @@ SKdNode *SKdNode::From(SMesh *m) {
STriangle *tra = (STriangle *)AllocTemporary((m->l.n) * sizeof(*tra));
for(i = 0; i < m->l.n; i++) {
- tra[i] = m->l.elem[i];
+ tra[i] = m->l[i];
}
srand(0);
@@ -637,7 +635,7 @@ void SKdNode::SnapToVertex(Vector v, SMesh *extras) {
void SKdNode::SnapToMesh(SMesh *m) {
int i, j, k;
for(i = 0; i < m->l.n; i++) {
- STriangle *tr = &(m->l.elem[i]);
+ STriangle *tr = &(m->l[i]);
if(tr->IsDegenerate()) {
continue;
}
@@ -649,7 +647,7 @@ void SKdNode::SnapToMesh(SMesh *m) {
for(k = 0; k < extra.l.n; k++) {
STriangle *tra = (STriangle *)AllocTemporary(sizeof(*tra));
- *tra = extra.l.elem[k];
+ *tra = extra.l[k];
AddTriangle(tra);
}
extra.Clear();
@@ -1130,3 +1128,67 @@ void SMesh::RemoveDegenerateTriangles() {
}
l.RemoveTagged();
}
+
+double SMesh::CalculateVolume() const {
+ double vol = 0;
+ for(STriangle tr : l) {
+ // Translate to place vertex A at (x, y, 0)
+ Vector trans = Vector::From(tr.a.x, tr.a.y, 0);
+ tr.a = (tr.a).Minus(trans);
+ tr.b = (tr.b).Minus(trans);
+ tr.c = (tr.c).Minus(trans);
+
+ // Rotate to place vertex B on the y-axis. Depending on
+ // whether the triangle is CW or CCW, C is either to the
+ // right or to the left of the y-axis. This handles the
+ // sign of our normal.
+ Vector u = Vector::From(-tr.b.y, tr.b.x, 0);
+ u = u.WithMagnitude(1);
+ Vector v = Vector::From(tr.b.x, tr.b.y, 0);
+ v = v.WithMagnitude(1);
+ Vector n = Vector::From(0, 0, 1);
+
+ tr.a = (tr.a).DotInToCsys(u, v, n);
+ tr.b = (tr.b).DotInToCsys(u, v, n);
+ tr.c = (tr.c).DotInToCsys(u, v, n);
+
+ n = tr.Normal().WithMagnitude(1);
+
+ // Triangles on edge don't contribute
+ if(fabs(n.z) < LENGTH_EPS) continue;
+
+ // The plane has equation p dot n = a dot n
+ double d = (tr.a).Dot(n);
+ // nx*x + ny*y + nz*z = d
+ // nz*z = d - nx*x - ny*y
+ double A = -n.x/n.z, B = -n.y/n.z, C = d/n.z;
+
+ double mac = tr.c.y/tr.c.x, mbc = (tr.c.y - tr.b.y)/tr.c.x;
+ double xc = tr.c.x, yb = tr.b.y;
+
+ // I asked Maple for
+ // int(int(A*x + B*y +C, y=mac*x..(mbc*x + yb)), x=0..xc);
+ double integral =
+ (1.0/3)*(
+ A*(mbc-mac)+
+ (1.0/2)*B*(mbc*mbc-mac*mac)
+ )*(xc*xc*xc)+
+ (1.0/2)*(A*yb+B*yb*mbc+C*(mbc-mac))*xc*xc+
+ C*yb*xc+
+ (1.0/2)*B*yb*yb*xc;
+
+ vol += integral;
+ }
+ return vol;
+}
+
+double SMesh::CalculateSurfaceArea(const std::vector &faces) const {
+ double area = 0.0;
+ for(uint32_t f : faces) {
+ for(const STriangle &t : l) {
+ if(f != t.meta.face) continue;
+ area += t.Area();
+ }
+ }
+ return area;
+}
diff --git a/src/modify.cpp b/src/modify.cpp
index 82a6e973..48c10957 100644
--- a/src/modify.cpp
+++ b/src/modify.cpp
@@ -12,11 +12,11 @@
// Useful when splitting, tangent arcing, or removing bezier points.
//-----------------------------------------------------------------------------
void GraphicsWindow::ReplacePointInConstraints(hEntity oldpt, hEntity newpt) {
- int i;
- for(i = 0; i < SK.constraint.n; i++) {
- Constraint *c = &(SK.constraint.elem[i]);
- if(c->ptA.v == oldpt.v) c->ptA = newpt;
- if(c->ptB.v == oldpt.v) c->ptB = newpt;
+ for(auto &c : SK.constraint) {
+ if(c.ptA == oldpt)
+ c.ptA = newpt;
+ if(c.ptB == oldpt)
+ c.ptB = newpt;
}
}
@@ -25,14 +25,13 @@ void GraphicsWindow::ReplacePointInConstraints(hEntity oldpt, hEntity newpt) {
//-----------------------------------------------------------------------------
void GraphicsWindow::RemoveConstraintsForPointBeingDeleted(hEntity hpt) {
SK.constraint.ClearTags();
- for(int i = 0; i < SK.constraint.n; i++) {
- Constraint *c = &(SK.constraint.elem[i]);
- if(c->ptA.v == hpt.v || c->ptB.v == hpt.v) {
- c->tag = 1;
+ for(auto &c : SK.constraint) {
+ if(c.ptA == hpt || c.ptB == hpt) {
+ c.tag = 1;
(SS.deleted.constraints)++;
- if(c->type != Constraint::Type::POINTS_COINCIDENT &&
- c->type != Constraint::Type::HORIZONTAL &&
- c->type != Constraint::Type::VERTICAL)
+ if(c.type != Constraint::Type::POINTS_COINCIDENT &&
+ c.type != Constraint::Type::HORIZONTAL &&
+ c.type != Constraint::Type::VERTICAL)
{
(SS.deleted.nonTrivialConstraints)++;
}
@@ -49,12 +48,12 @@ void GraphicsWindow::RemoveConstraintsForPointBeingDeleted(hEntity hpt) {
//-----------------------------------------------------------------------------
void GraphicsWindow::FixConstraintsForRequestBeingDeleted(hRequest hr) {
Request *r = SK.GetRequest(hr);
- if(r->group.v != SS.GW.activeGroup.v) return;
+ if(r->group != SS.GW.activeGroup) return;
Entity *e;
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
if(!(e->h.isFromRequest())) continue;
- if(e->h.request().v != hr.v) continue;
+ if(e->h.request() != hr) continue;
if(e->type != Entity::Type::POINT_IN_2D &&
e->type != Entity::Type::POINT_IN_3D)
@@ -74,13 +73,13 @@ void GraphicsWindow::FixConstraintsForPointBeingDeleted(hEntity hpt) {
SK.constraint.ClearTags();
for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) {
if(c->type != Constraint::Type::POINTS_COINCIDENT) continue;
- if(c->group.v != SS.GW.activeGroup.v) continue;
+ if(c->group != SS.GW.activeGroup) continue;
- if(c->ptA.v == hpt.v) {
+ if(c->ptA == hpt) {
ld.Add(&(c->ptB));
c->tag = 1;
}
- if(c->ptB.v == hpt.v) {
+ if(c->ptB == hpt) {
ld.Add(&(c->ptA));
c->tag = 1;
}
@@ -97,9 +96,8 @@ void GraphicsWindow::FixConstraintsForPointBeingDeleted(hEntity hpt) {
// those two points were implicitly coincident with each other. By
// deleting hpt (and all constraints that mention it), we will delete
// that relationship. So put it back here now.
- int i;
- for(i = 1; i < ld.n; i++) {
- Constraint::ConstrainCoincident(ld.elem[i-1], ld.elem[i]);
+ for(int i = 1; i < ld.n; i++) {
+ Constraint::ConstrainCoincident(ld[i-1], ld[i]);
}
ld.Clear();
}
@@ -233,10 +231,10 @@ void GraphicsWindow::ParametricCurve::ConstrainPointIfCoincident(hEntity hpt) {
ptv = pt->PointGetNum();
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
- if(e->h.v == pt->h.v) continue;
+ if(e->h == pt->h) continue;
if(!e->IsPoint()) continue;
- if(e->group.v != pt->group.v) continue;
- if(e->workplane.v != pt->workplane.v) continue;
+ if(e->group != pt->group) continue;
+ if(e->workplane != pt->workplane) continue;
ev = e->PointGetNum();
if(!ev.Equals(ptv)) continue;
@@ -270,18 +268,18 @@ void GraphicsWindow::MakeTangentArc() {
hRequest hreq[2];
hEntity hent[2];
bool pointf[2];
- for(i = 0; i < SK.request.n; i++) {
- Request *r = &(SK.request.elem[i]);
- if(r->group.v != activeGroup.v) continue;
- if(r->workplane.v != ActiveWorkplane().v) continue;
- if(r->construction) continue;
- if(r->type != Request::Type::LINE_SEGMENT &&
- r->type != Request::Type::ARC_OF_CIRCLE)
- {
+ for(auto &r : SK.request) {
+ if(r.group != activeGroup)
+ continue;
+ if(r.workplane != ActiveWorkplane())
+ continue;
+ if(r.construction)
+ continue;
+ if(r.type != Request::Type::LINE_SEGMENT && r.type != Request::Type::ARC_OF_CIRCLE) {
continue;
}
- Entity *e = SK.GetEntity(r->h.entity(0));
+ Entity *e = SK.GetEntity(r.h.entity(0));
Vector ps = e->EndpointStart(),
pf = e->EndpointFinish();
@@ -292,8 +290,8 @@ void GraphicsWindow::MakeTangentArc() {
// finish of this entity.
ent[c] = e;
hent[c] = e->h;
- req[c] = r;
- hreq[c] = r->h;
+ req[c] = &r;
+ hreq[c] = r.h;
pointf[c] = (pf.Equals(pshared));
}
c++;
@@ -375,8 +373,8 @@ void GraphicsWindow::MakeTangentArc() {
tp[1] = t[1];
// And convert those points to parameter values along the curve.
- t[0] += (pa0.Minus(p0)).DivPivoting(t0);
- t[1] += (pa1.Minus(p1)).DivPivoting(t1);
+ t[0] += (pa0.Minus(p0)).DivProjected(t0);
+ t[1] += (pa1.Minus(p1)).DivProjected(t1);
}
// Stupid check for convergence, and for an out of range result (as
@@ -411,9 +409,9 @@ void GraphicsWindow::MakeTangentArc() {
// Delete the coincident constraint for the removed point.
SK.constraint.ClearTags();
for(i = 0; i < SK.constraint.n; i++) {
- Constraint *cs = &(SK.constraint.elem[i]);
- if(cs->group.v != activeGroup.v) continue;
- if(cs->workplane.v != ActiveWorkplane().v) continue;
+ Constraint *cs = &(SK.constraint[i]);
+ if(cs->group != activeGroup) continue;
+ if(cs->workplane != ActiveWorkplane()) continue;
if(cs->type != Constraint::Type::POINTS_COINCIDENT) continue;
if (SK.GetEntity(cs->ptA)->PointGetNum().Equals(pshared)) {
cs->tag = 1;
@@ -536,7 +534,7 @@ hEntity GraphicsWindow::SplitCubic(hEntity he, Vector pinter) {
double t;
int i, j;
for(i = 0; i < sbl.l.n; i++) {
- SBezier *sb = &(sbl.l.elem[i]);
+ SBezier *sb = &(sbl.l[i]);
ssassert(sb->deg == 3, "Expected a cubic bezier");
sb->ClosestPointTo(pinter, &t, /*mustConverge=*/false);
@@ -603,15 +601,16 @@ hEntity GraphicsWindow::SplitEntity(hEntity he, Vector pinter) {
// Finally, delete the request that generated the original entity.
Request::Type reqType = EntReqTable::GetRequestForEntity(entityType);
SK.request.ClearTags();
- for(int i = 0; i < SK.request.n; i++) {
- Request *r = &(SK.request.elem[i]);
- if(r->group.v != activeGroup.v) continue;
- if(r->type != reqType) continue;
+ for(auto &r : SK.request) {
+ if(r.group != activeGroup)
+ continue;
+ if(r.type != reqType)
+ continue;
// If the user wants to keep the old entities around, they can just
// mark them construction first.
- if(he.v == r->h.entity(0).v && !r->construction) {
- r->tag = 1;
+ if(he == r.h.entity(0) && !r.construction) {
+ r.tag = 1;
break;
}
}
@@ -659,8 +658,8 @@ void GraphicsWindow::SplitLinesOrCurves() {
}
for(Constraint &c : SK.constraint) {
- if(c.ptA.request().v == hb.request().v &&
- c.entityA.request().v == ha.request().v) {
+ if(c.ptA.request() == hb.request() &&
+ c.entityA.request() == ha.request()) {
pi = SK.GetEntity(c.ptA)->PointGetNum();
if(ea->type == Entity::Type::LINE_SEGMENT && !pi.OnLineSegment(p0, p1)) {
@@ -682,7 +681,7 @@ void GraphicsWindow::SplitLinesOrCurves() {
sbla.AllIntersectionsWith(&sblb, &inters);
// If there's multiple points, then take the one closest to the mouse pointer.
- if(inters.l.n > 0) {
+ if(!inters.l.IsEmpty()) {
double dmin = VERY_POSITIVE;
SPoint *sp;
for(sp = inters.l.First(); sp; sp = inters.l.NextAfter(sp)) {
@@ -713,7 +712,7 @@ void GraphicsWindow::SplitLinesOrCurves() {
// Remove datum point, as it has now been superseded by the split point.
SK.request.ClearTags();
for(Request &r : SK.request) {
- if(r.h.v == hb.request().v) {
+ if(r.h == hb.request()) {
if(r.type == Request::Type::DATUM_POINT) {
// Delete datum point.
r.tag = 1;
diff --git a/src/mouse.cpp b/src/mouse.cpp
index d5edbed7..0e101a78 100644
--- a/src/mouse.cpp
+++ b/src/mouse.cpp
@@ -24,7 +24,7 @@ void GraphicsWindow::AddPointToDraggedList(hEntity hp) {
// twice as far as the mouse pointer...
List *lhe = &(pending.points);
for(hEntity *hee = lhe->First(); hee; hee = lhe->NextAfter(hee)) {
- if(hee->v == hp.v) {
+ if(*hee == hp) {
// Exact same point.
return;
}
@@ -32,7 +32,7 @@ void GraphicsWindow::AddPointToDraggedList(hEntity hp) {
if(pe->type == p->type &&
pe->type != Entity::Type::POINT_IN_2D &&
pe->type != Entity::Type::POINT_IN_3D &&
- pe->group.v == p->group.v)
+ pe->group == p->group)
{
// Transform-type point, from the same group. So it handles the
// same unknowns.
@@ -75,7 +75,7 @@ void GraphicsWindow::StartDraggingBySelection() {
// the hovered item too, and they'll always have it.
if(hover.entity.v) {
hEntity dragEntity = ChooseFromHoverToDrag().entity;
- if(dragEntity.v != Entity::NO_ENTITY.v) {
+ if(dragEntity != Entity::NO_ENTITY) {
StartDraggingByEntity(dragEntity);
}
}
@@ -383,7 +383,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
HitTestMakeSelection(mp);
hRequest hr = pending.point.request();
- if(pending.point.v == hr.entity(4).v) {
+ if(pending.point == hr.entity(4)) {
// The very first segment; dragging final point drags both
// tangent points.
Vector p0 = SK.GetEntity(hr.entity(1))->PointGetNum(),
@@ -486,7 +486,7 @@ void GraphicsWindow::ClearPending(bool scheduleShowTW) {
bool GraphicsWindow::IsFromPending(hRequest r) {
for(auto &req : pending.requests) {
- if(req.v == r.v) return true;
+ if(req == r) return true;
}
return false;
}
@@ -497,8 +497,8 @@ void GraphicsWindow::AddToPending(hRequest r) {
void GraphicsWindow::ReplacePending(hRequest before, hRequest after) {
for(auto &req : pending.requests) {
- if(req.v == before.v) {
- req.v = after.v;
+ if(req == before) {
+ req = after;
}
}
}
@@ -721,7 +721,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
IdList *lc = &(SK.constraint);
for(c = lc->First(); c; c = lc->NextAfter(c)) {
if(c->type != Constraint::Type::POINTS_COINCIDENT) continue;
- if(c->ptA.v == p->h.v || c->ptB.v == p->h.v) {
+ if(c->ptA == p->h || c->ptB == p->h) {
break;
}
}
@@ -734,7 +734,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
Constraint *c;
for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) {
if(c->type != Constraint::Type::POINTS_COINCIDENT) continue;
- if(c->ptA.v == p->h.v || c->ptB.v == p->h.v) {
+ if(c->ptA == p->h || c->ptB == p->h) {
c->tag = 1;
}
}
@@ -755,7 +755,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
[&]() { MenuEdit(Command::SELECT_ALL); });
}
- if((SS.clipboard.r.n > 0 || SS.clipboard.c.n > 0) && LockedInWorkplane()) {
+ if((!SS.clipboard.r.IsEmpty() || !SS.clipboard.c.IsEmpty()) && LockedInWorkplane()) {
menu->AddItem(_("Paste"),
[&]() { MenuClipboard(Command::PASTE); });
menu->AddItem(_("Paste Transformed..."),
@@ -1178,7 +1178,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my, bool shiftDown, bool ct
hRequest hr = pending.point.request();
Request *r = SK.GetRequest(hr);
- if(hover.entity.v == hr.entity(1).v && r->extraPoints >= 2) {
+ if(hover.entity == hr.entity(1) && r->extraPoints >= 2) {
// They want the endpoints coincident, which means a periodic
// spline instead.
r->type = Request::Type::CUBIC_PERIODIC;
@@ -1543,7 +1543,7 @@ void GraphicsWindow::SixDofEvent(Platform::SixDofEvent event) {
// point.
int64_t now = GetMilliseconds();
if(now - last6DofTime > 5000 ||
- last6DofGroup.v != g->h.v)
+ last6DofGroup != g->h)
{
SS.UndoRemember();
}
diff --git a/src/platform/entrycli.cpp b/src/platform/entrycli.cpp
index 6859712a..b6e1cc01 100644
--- a/src/platform/entrycli.cpp
+++ b/src/platform/entrycli.cpp
@@ -312,7 +312,7 @@ static bool RunCommand(const std::vector args) {
return false;
}
- if(inputFiles.size() == 0) {
+ if(inputFiles.empty()) {
fprintf(stderr, "At least one input file must be specified.\n");
return false;
}
diff --git a/src/platform/entrygui.cpp b/src/platform/entrygui.cpp
index 56f199ee..654d25ea 100644
--- a/src/platform/entrygui.cpp
+++ b/src/platform/entrygui.cpp
@@ -29,8 +29,8 @@ int main(int argc, char** argv) {
Platform::Close3DConnexion();
SS.Clear();
-
SK.Clear();
+ Platform::ClearGui();
return 0;
}
diff --git a/src/platform/gui.cpp b/src/platform/gui.cpp
index e4475c7d..b86d2ecd 100644
--- a/src/platform/gui.cpp
+++ b/src/platform/gui.cpp
@@ -95,6 +95,7 @@ std::vector MeshFileFilters = {
{ CN_("file-type", "Three.js-compatible mesh, with viewer"), { "html" } },
{ CN_("file-type", "Three.js-compatible mesh, mesh only"), { "js" } },
{ CN_("file-type", "Q3D Object file"), { "q3do" } },
+ { CN_("file-type", "VRML text file"), { "wrl" } },
};
std::vector SurfaceFileFilters = {
diff --git a/src/platform/gui.h b/src/platform/gui.h
index cef74f6d..50bebc3e 100644
--- a/src/platform/gui.h
+++ b/src/platform/gui.h
@@ -107,7 +107,7 @@ void FatalError(std::string message);
// A native settings store.
class Settings {
public:
- virtual ~Settings() {}
+ virtual ~Settings() = default;
virtual void FreezeInt(const std::string &key, uint32_t value) = 0;
virtual uint32_t ThawInt(const std::string &key, uint32_t defaultValue = 0) = 0;
@@ -135,7 +135,7 @@ class Timer {
public:
std::function onTimeout;
- virtual ~Timer() {}
+ virtual ~Timer() = default;
virtual void RunAfter(unsigned milliseconds) = 0;
virtual void RunAfterNextFrame() { RunAfter(1); }
@@ -157,7 +157,7 @@ public:
std::function onTrigger;
- virtual ~MenuItem() {}
+ virtual ~MenuItem() = default;
virtual void SetAccelerator(KeyboardEvent accel) = 0;
virtual void SetIndicator(Indicator type) = 0;
@@ -170,7 +170,7 @@ typedef std::shared_ptr