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 MenuItemRef; // A native menu. class Menu { public: - virtual ~Menu() {} + virtual ~Menu() = default; virtual std::shared_ptr AddItem( const std::string &label, std::function onTrigger = std::function(), @@ -188,7 +188,7 @@ typedef std::shared_ptr MenuRef; // A native menu bar. class MenuBar { public: - virtual ~MenuBar() {} + virtual ~MenuBar() = default; virtual std::shared_ptr AddSubMenu(const std::string &label) = 0; @@ -222,7 +222,7 @@ public: std::function onScrollbarAdjusted; std::function onRender; - virtual ~Window() {} + virtual ~Window() = default; // Returns physical display DPI. virtual double GetPixelDensity() = 0; @@ -299,7 +299,7 @@ public: std::function onResponse; - virtual ~MessageDialog() {} + virtual ~MessageDialog() = default; virtual void SetType(Type type) = 0; virtual void SetTitle(std::string title) = 0; @@ -347,7 +347,7 @@ extern std::vector CsvFileFilters; // A native dialog that asks to choose a file. class FileDialog { public: - virtual ~FileDialog() {} + virtual ~FileDialog() = default; virtual void SetTitle(std::string title) = 0; virtual void SetCurrentName(std::string name) = 0; @@ -380,6 +380,7 @@ void OpenInBrowser(const std::string &url); void InitGui(int argc, char **argv); void RunGui(); void ExitGui(); +void ClearGui(); } diff --git a/src/platform/guigtk.cpp b/src/platform/guigtk.cpp index fbcc701a..61181482 100644 --- a/src/platform/guigtk.cpp +++ b/src/platform/guigtk.cpp @@ -124,7 +124,7 @@ public: } } - ~SettingsImplGtk() { + ~SettingsImplGtk() override { if(!_path.IsEmpty()) { // json-c <0.12 has the first argument non-const if(json_object_to_file_ext((char *)_path.raw.c_str(), _json, @@ -1292,7 +1292,8 @@ public: void FilterChanged() { std::string extension = GetExtension(); - if(extension == "") return; + if(extension.empty()) + return; Platform::Path path = GetFilename(); SetCurrentName(path.WithExtension(extension).FileName()); @@ -1470,11 +1471,14 @@ void InitGui(int argc, char **argv) { } void RunGui() { - gtkMain->run(); + Gtk::Main::run(); } void ExitGui() { - gtkMain->quit(); + Gtk::Main::quit(); +} + +void ClearGui() { delete gtkMain; } diff --git a/src/platform/guimac.mm b/src/platform/guimac.mm index 6ed9d4c1..77f2d565 100644 --- a/src/platform/guimac.mm +++ b/src/platform/guimac.mm @@ -1479,5 +1479,7 @@ void ExitGui() { [NSApp terminate:nil]; } +void ClearGui() {} + } } diff --git a/src/platform/guinone.cpp b/src/platform/guinone.cpp index eada3842..31f37dcd 100644 --- a/src/platform/guinone.cpp +++ b/src/platform/guinone.cpp @@ -53,22 +53,24 @@ void FatalError(std::string message) { class SettingsImplDummy final : public Settings { public: - void FreezeInt(const std::string &key, uint32_t value) {} + void FreezeInt(const std::string &key, uint32_t value) override { + } - uint32_t ThawInt(const std::string &key, uint32_t defaultValue = 0) { + uint32_t ThawInt(const std::string &key, uint32_t defaultValue = 0) override { return defaultValue; } - void FreezeFloat(const std::string &key, double value) {} + void FreezeFloat(const std::string &key, double value) override { + } - double ThawFloat(const std::string &key, double defaultValue = 0.0) { + double ThawFloat(const std::string &key, double defaultValue = 0.0) override { return defaultValue; } - void FreezeString(const std::string &key, const std::string &value) {} + void FreezeString(const std::string &key, const std::string &value) override { + } - std::string ThawString(const std::string &key, - const std::string &defaultValue = "") { + std::string ThawString(const std::string &key, const std::string &defaultValue = "") override { return defaultValue; } }; @@ -154,6 +156,8 @@ void ExitGui() { exit(0); } +void ClearGui() {} + } } diff --git a/src/platform/guiwin.cpp b/src/platform/guiwin.cpp index ab5fb4b8..40f20c51 100644 --- a/src/platform/guiwin.cpp +++ b/src/platform/guiwin.cpp @@ -1369,7 +1369,9 @@ public: } }; +#if HAVE_OPENGL == 3 EGLDisplay WindowImplWin32::eglDisplay = EGL_NO_DISPLAY; +#endif WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { return std::make_shared(kind, @@ -1675,5 +1677,7 @@ void ExitGui() { PostQuitMessage(0); } +void ClearGui() {} + } } diff --git a/src/platform/platform.cpp b/src/platform/platform.cpp index ab08828d..e2382d41 100644 --- a/src/platform/platform.cpp +++ b/src/platform/platform.cpp @@ -203,7 +203,7 @@ static void FindPrefix(const std::string &raw, size_t *pos) { } } #else - if(raw.size() >= 1 && raw[0] == '/') { + if(!raw.empty() && raw[0] == '/') { *pos = 1; } #endif @@ -295,7 +295,7 @@ Path Path::Expand(bool fromCurrentDirectory) const { if(expanded.IsEmpty()) { if(expandedComponents.empty()) { - expandedComponents.push_back("."); + expandedComponents.emplace_back("."); } expanded = From(Concat(expandedComponents, SEPARATOR)); } else if(!expandedComponents.empty()) { @@ -371,12 +371,12 @@ Path Path::RelativeTo(const Path &base) const { std::vector resultComponents; for(size_t i = common; i < baseComponents.size(); i++) { - resultComponents.push_back(".."); + resultComponents.emplace_back(".."); } resultComponents.insert(resultComponents.end(), components.begin() + common, components.end()); if(resultComponents.empty()) { - resultComponents.push_back("."); + resultComponents.emplace_back("."); } return From(Concat(resultComponents, SEPARATOR)); } diff --git a/src/platform/utilunix.cpp b/src/platform/utilunix.cpp index 0275bd60..646affcc 100644 --- a/src/platform/utilunix.cpp +++ b/src/platform/utilunix.cpp @@ -54,8 +54,7 @@ void *AllocTemporary(size_t n) return (void *)&h[1]; } -void FreeAllTemporary(void) -{ +void FreeAllTemporary() { AllocTempHeader *h = Head; while(h) { AllocTempHeader *f = h; @@ -77,8 +76,9 @@ void MemFree(void *p) { std::vector InitPlatform(int argc, char **argv) { std::vector args; + args.reserve(argc); for(int i = 0; i < argc; i++) { - args.push_back(argv[i]); + args.emplace_back(argv[i]); } return args; } diff --git a/src/polygon.cpp b/src/polygon.cpp index 39879dbf..4589f81a 100644 --- a/src/polygon.cpp +++ b/src/polygon.cpp @@ -87,6 +87,12 @@ double STriangle::SignedVolume() const { return a.Dot(b.Cross(c)) / 6.0; } +double STriangle::Area() const { + Vector ab = a.Minus(b); + Vector cb = c.Minus(b); + return ab.Cross(cb).Magnitude() / 2.0; +} + bool STriangle::IsDegenerate() const { return a.OnLineSegment(b, c) || b.OnLineSegment(a, c) || @@ -158,13 +164,13 @@ bool SEdge::EdgeCrosses(Vector ea, Vector eb, Vector *ppi, SPointList *spl) cons // on the other bool inters = false; double t; - t = a.Minus(ea).DivPivoting(d); + t = a.Minus(ea).DivProjected(d); if(t > t_eps && t < (1 - t_eps)) inters = true; - t = b.Minus(ea).DivPivoting(d); + t = b.Minus(ea).DivProjected(d); if(t > t_eps && t < (1 - t_eps)) inters = true; - t = ea.Minus(a).DivPivoting(dthis); + t = ea.Minus(a).DivProjected(dthis); if(t > tthis_eps && t < (1 - tthis_eps)) inters = true; - t = eb.Minus(a).DivPivoting(dthis); + t = eb.Minus(a).DivProjected(dthis); if(t > tthis_eps && t < (1 - tthis_eps)) inters = true; if(inters) { @@ -227,7 +233,8 @@ bool SEdgeList::AssembleContour(Vector first, Vector last, SContour *dest, do { for(i = 0; i < l.n; i++) { - SEdge *se = &(l.elem[i]); + /// @todo fix const! + SEdge *se = const_cast(&(l[i])); if(se->tag) continue; if(se->a.Equals(last)) { @@ -267,10 +274,11 @@ bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir) co Vector last = Vector::From(0, 0, 0); int i; for(i = 0; i < l.n; i++) { - if(!l.elem[i].tag) { - first = l.elem[i].a; - last = l.elem[i].b; - l.elem[i].tag = 1; + if(!l[i].tag) { + first = l[i].a; + last = l[i].b; + /// @todo fix const! + const_cast(&(l[i]))->tag = 1; break; } } @@ -281,9 +289,7 @@ bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir) co // Create a new empty contour in our polygon, and finish assembling // into that contour. dest->AddEmptyContour(); - if(!AssembleContour(first, last, &(dest->l.elem[dest->l.n-1]), - errorAt, keepDir)) - { + if(!AssembleContour(first, last, dest->l.Last(), errorAt, keepDir)) { allClosed = false; } // But continue assembling, even if some of the contours are open @@ -335,11 +341,10 @@ bool SEdgeList::ContainsEdge(const SEdge *set) const { //----------------------------------------------------------------------------- void SEdgeList::CullExtraneousEdges(bool both) { l.ClearTags(); - int i, j; - for(i = 0; i < l.n; i++) { - SEdge *se = &(l.elem[i]); - for(j = i+1; j < l.n; j++) { - SEdge *set = &(l.elem[j]); + for(int i = 0; i < l.n; i++) { + SEdge *se = &(l[i]); + for(int j = i + 1; j < l.n; j++) { + SEdge *set = &(l[j]); if((set->a).Equals(se->a) && (set->b).Equals(se->b)) { // Two parallel edges exist; so keep only the first one. set->tag = 1; @@ -491,33 +496,27 @@ int SKdNodeEdges::AnyEdgeCrossings(Vector a, Vector b, int cnt, // We have an edge list that contains only collinear edges, maybe with more // splits than necessary. Merge any collinear segments that join. //----------------------------------------------------------------------------- -static Vector LineStart, LineDirection; -static int ByTAlongLine(const void *av, const void *bv) -{ - SEdge *a = (SEdge *)av, - *b = (SEdge *)bv; - - double ta = (a->a.Minus(LineStart)).DivPivoting(LineDirection), - tb = (b->a.Minus(LineStart)).DivPivoting(LineDirection); - - return (ta > tb) ? 1 : -1; -} void SEdgeList::MergeCollinearSegments(Vector a, Vector b) { - LineStart = a; - LineDirection = b.Minus(a); - qsort(l.elem, l.n, sizeof(l.elem[0]), ByTAlongLine); + const Vector lineStart = a; + const Vector lineDirection = b.Minus(a); + std::sort(l.begin(), l.end(), [&](const SEdge &a, const SEdge &b) { + double ta = (a.a.Minus(lineStart)).DivProjected(lineDirection); + double tb = (b.a.Minus(lineStart)).DivProjected(lineDirection); + + return (ta < tb); + }); l.ClearTags(); - int i; - for(i = 1; i < l.n; i++) { - SEdge *prev = &(l.elem[i-1]), - *now = &(l.elem[i]); - - if((prev->b).Equals(now->a) && prev->auxA == now->auxA) { - // The previous segment joins up to us; so merge it into us. - prev->tag = 1; - now->a = prev->a; + SEdge *prev = nullptr; + for(auto &now : l) { + if(prev != nullptr) { + if((prev->b).Equals(now.a) && prev->auxA == now.auxA) { + // The previous segment joins up to us; so merge it into us. + prev->tag = 1; + now.a = prev->a; + } } + prev = &now; } l.RemoveTagged(); } @@ -533,7 +532,7 @@ bool SPointList::ContainsPoint(Vector pt) const { int SPointList::IndexForPoint(Vector pt) const { int i; for(i = 0; i < l.n; i++) { - SPoint *p = &(l.elem[i]); + const SPoint *p = &(l[i]); if(pt.Equals(p->p)) { return i; } @@ -573,7 +572,7 @@ void SContour::AddPoint(Vector p) { void SContour::MakeEdgesInto(SEdgeList *el) const { int i; for(i = 0; i < (l.n - 1); i++) { - el->AddEdge(l.elem[i].p, l.elem[i+1].p); + el->AddEdge(l[i].p, l[i+1].p); } } @@ -596,8 +595,8 @@ Vector SContour::ComputeNormal() const { Vector n = Vector::From(0, 0, 0); for(int i = 0; i < l.n - 2; i++) { - Vector u = (l.elem[i+1].p).Minus(l.elem[i+0].p).WithMagnitude(1); - Vector v = (l.elem[i+2].p).Minus(l.elem[i+1].p).WithMagnitude(1); + Vector u = (l[i+1].p).Minus(l[i+0].p).WithMagnitude(1); + Vector v = (l[i+2].p).Minus(l[i+1].p).WithMagnitude(1); Vector nt = u.Cross(v); if(nt.Magnitude() > n.Magnitude()) { n = nt; @@ -608,7 +607,7 @@ Vector SContour::ComputeNormal() const { Vector SContour::AnyEdgeMidpoint() const { ssassert(l.n >= 2, "Need two points to find a midpoint"); - return ((l.elem[0].p).Plus(l.elem[1].p)).ScaledBy(0.5); + return ((l[0].p).Plus(l[1].p)).ScaledBy(0.5); } bool SContour::IsClockwiseProjdToNormal(Vector n) const { @@ -626,10 +625,10 @@ double SContour::SignedAreaProjdToNormal(Vector n) const { double area = 0; for(int i = 0; i < (l.n - 1); i++) { - double u0 = (l.elem[i ].p).Dot(u); - double v0 = (l.elem[i ].p).Dot(v); - double u1 = (l.elem[i+1].p).Dot(u); - double v1 = (l.elem[i+1].p).Dot(v); + double u0 = (l[i ].p).Dot(u); + double v0 = (l[i ].p).Dot(v); + double u1 = (l[i+1].p).Dot(u); + double v1 = (l[i+1].p).Dot(v); area += ((v0 + v1)/2)*(u1 - u0); } @@ -645,11 +644,11 @@ bool SContour::ContainsPointProjdToNormal(Vector n, Vector p) const { bool inside = false; for(int i = 0; i < (l.n - 1); i++) { - double ua = (l.elem[i ].p).Dot(u); - double va = (l.elem[i ].p).Dot(v); + double ua = (l[i ].p).Dot(u); + double va = (l[i ].p).Dot(v); // The curve needs to be exactly closed; approximation is death. - double ub = (l.elem[(i+1)%(l.n-1)].p).Dot(u); - double vb = (l.elem[(i+1)%(l.n-1)].p).Dot(v); + double ub = (l[(i+1)%(l.n-1)].p).Dot(u); + double vb = (l[(i+1)%(l.n-1)].p).Dot(v); if ((((va <= vp) && (vp < vb)) || ((vb <= vp) && (vp < va))) && @@ -670,7 +669,7 @@ void SContour::Reverse() { void SPolygon::Clear() { int i; for(i = 0; i < l.n; i++) { - (l.elem[i]).l.Clear(); + (l[i]).l.Clear(); } l.Clear(); } @@ -683,13 +682,14 @@ void SPolygon::AddEmptyContour() { void SPolygon::MakeEdgesInto(SEdgeList *el) const { int i; for(i = 0; i < l.n; i++) { - (l.elem[i]).MakeEdgesInto(el); + (l[i]).MakeEdgesInto(el); } } Vector SPolygon::ComputeNormal() const { - if(l.n < 1) return Vector::From(0, 0, 0); - return (l.elem[0]).ComputeNormal(); + if(l.IsEmpty()) + return Vector::From(0, 0, 0); + return (l[0]).ComputeNormal(); } double SPolygon::SignedArea() const { @@ -710,7 +710,7 @@ int SPolygon::WindingNumberForPoint(Vector p) const { int winding = 0; int i; for(i = 0; i < l.n; i++) { - SContour *sc = &(l.elem[i]); + const SContour *sc = &(l[i]); if(sc->ContainsPointProjdToNormal(normal, p)) { winding++; } @@ -725,18 +725,18 @@ void SPolygon::FixContourDirections() { // Outside curve looks counterclockwise, projected against our normal. int i, j; for(i = 0; i < l.n; i++) { - SContour *sc = &(l.elem[i]); + SContour *sc = &(l[i]); if(sc->l.n < 2) continue; // The contours may not intersect, but they may share vertices; so // testing a vertex for point-in-polygon may fail, but the midpoint // of an edge is okay. - Vector pt = (((sc->l.elem[0]).p).Plus(sc->l.elem[1].p)).ScaledBy(0.5); + Vector pt = (((sc->l[0]).p).Plus(sc->l[1].p)).ScaledBy(0.5); sc->timesEnclosed = 0; bool outer = true; for(j = 0; j < l.n; j++) { if(i == j) continue; - SContour *sct = &(l.elem[j]); + SContour *sct = &(l[j]); if(sct->ContainsPointProjdToNormal(normal, pt)) { outer = !outer; (sc->timesEnclosed)++; @@ -752,13 +752,14 @@ void SPolygon::FixContourDirections() { } bool SPolygon::IsEmpty() const { - if(l.n == 0 || l.elem[0].l.n == 0) return true; + if(l.IsEmpty() || l[0].l.IsEmpty()) + return true; return false; } Vector SPolygon::AnyPoint() const { ssassert(!IsEmpty(), "Need at least one point"); - return l.elem[0].l.elem[0].p; + return l[0].l[0].p; } bool SPolygon::SelfIntersecting(Vector *intersectsAt) const { @@ -804,9 +805,9 @@ void SPolygon::OffsetInto(SPolygon *dest, double r) const { int i; dest->Clear(); for(i = 0; i < l.n; i++) { - SContour *sc = &(l.elem[i]); + const SContour *sc = &(l[i]); dest->AddEmptyContour(); - sc->OffsetInto(&(dest->l.elem[dest->l.n-1]), r); + sc->OffsetInto(&(dest->l[dest->l.n-1]), r); } } //----------------------------------------------------------------------------- @@ -861,9 +862,9 @@ void SContour::OffsetInto(SContour *dest, double r) const { Vector dp, dn; double thetan, thetap; - a = l.elem[WRAP(i-1, (l.n-1))].p; - b = l.elem[WRAP(i, (l.n-1))].p; - c = l.elem[WRAP(i+1, (l.n-1))].p; + a = l[WRAP(i-1, (l.n-1))].p; + b = l[WRAP(i, (l.n-1))].p; + c = l[WRAP(i+1, (l.n-1))].p; dp = a.Minus(b); thetap = atan2(dp.y, dp.x); diff --git a/src/polygon.h b/src/polygon.h index c340e136..b7c5a71e 100644 --- a/src/polygon.h +++ b/src/polygon.h @@ -191,6 +191,7 @@ public: bool Raytrace(const Vector &rayPoint, const Vector &rayDir, double *t, Vector *inters) const; double SignedVolume() const; + double Area() const; bool IsDegenerate() const; }; @@ -280,6 +281,8 @@ public: void PrecomputeTransparency(); void RemoveDegenerateTriangles(); + double CalculateVolume() const; + double CalculateSurfaceArea(const std::vector &faces) const; bool IsEmpty() const; void RemapFaces(Group *g, int remap); diff --git a/src/render/gl3shader.cpp b/src/render/gl3shader.cpp index 4d4bac36..8ad45c7a 100644 --- a/src/render/gl3shader.cpp +++ b/src/render/gl3shader.cpp @@ -244,7 +244,7 @@ MeshRenderer::Handle MeshRenderer::Add(const SMesh &m, bool dynamic) { MeshVertex *vertices = new MeshVertex[m.l.n * 3]; for(int i = 0; i < m.l.n; i++) { - const STriangle &t = m.l.elem[i]; + const STriangle &t = m.l[i]; vertices[i * 3 + 0].pos = Vector3f::From(t.a); vertices[i * 3 + 1].pos = Vector3f::From(t.b); vertices[i * 3 + 2].pos = Vector3f::From(t.c); @@ -395,7 +395,7 @@ GLuint Generate(const std::vector &pattern) { int dashI = 0; double dashT = 0.0; for(int i = 0; i < size; i++) { - if(pattern.size() == 0) { + if(pattern.empty()) { textureData[i] = EncodeLengthAsFloat(0.0); continue; } @@ -485,8 +485,8 @@ EdgeRenderer::Handle EdgeRenderer::Add(const SEdgeList &edges, bool dynamic) { uint32_t curVertex = 0; uint32_t curIndex = 0; for(int i = 0; i < edges.l.n; i++) { - const SEdge &curr = edges.l.elem[i]; - const SEdge &next = edges.l.elem[(i + 1) % edges.l.n]; + const SEdge &curr = edges.l[i]; + const SEdge &next = edges.l[(i + 1) % edges.l.n]; // 3d positions Vector3f a = Vector3f::From(curr.a); @@ -674,8 +674,8 @@ OutlineRenderer::Handle OutlineRenderer::Add(const SOutlineList &outlines, bool uint32_t curIndex = 0; for(int i = 0; i < outlines.l.n; i++) { - const SOutline &curr = outlines.l.elem[i]; - const SOutline &next = outlines.l.elem[(i + 1) % outlines.l.n]; + const SOutline &curr = outlines.l[i]; + const SOutline &next = outlines.l[(i + 1) % outlines.l.n]; // 3d positions Vector3f a = Vector3f::From(curr.a); diff --git a/src/render/render.h b/src/render/render.h index 714180d2..5c77ebf5 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -142,6 +142,7 @@ public: BitmapFont bitmapFont = {}; virtual void Clear(); + virtual ~Canvas() = default; hStroke GetStroke(const Stroke &stroke); hFill GetFill(const Fill &fill); @@ -172,6 +173,13 @@ public: virtual std::shared_ptr CreateBatch(); }; +template<> +struct IsHandleOracle : std::true_type {}; + +template<> +struct IsHandleOracle : std::true_type {}; + + // An interface for view-dependent visualization. class ViewportCanvas : public Canvas { public: @@ -276,7 +284,7 @@ public: const Camera &GetCamera() const override { return camera; } // ViewportCanvas interface. - void SetCamera(const Camera &camera) override { this->camera = camera; }; + void SetCamera(const Camera &camera) override { this->camera = camera; } void SetLighting(const Lighting &lighting) override { this->lighting = lighting; } void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override; @@ -353,7 +361,7 @@ public: void OutputEnd() override; }; -class CairoPixmapRenderer : public CairoRenderer { +class CairoPixmapRenderer final : public CairoRenderer { public: std::shared_ptr pixmap; diff --git a/src/render/render2d.cpp b/src/render/render2d.cpp index 9a8995cd..710e795d 100644 --- a/src/render/render2d.cpp +++ b/src/render/render2d.cpp @@ -243,7 +243,7 @@ void SurfaceRenderer::ConvertBeziersToEdges() { List lv = {}; b.MakePwlInto(&lv, chordTolerance); for(int i = 1; i < lv.n; i++) { - el.AddEdge(lv.elem[i-1], lv.elem[i]); + el.AddEdge(lv[i-1], lv[i]); } lv.Clear(); } @@ -255,7 +255,8 @@ void SurfaceRenderer::ConvertBeziersToEdges() { void SurfaceRenderer::CullOccludedStrokes() { // Perform occlusion testing, if necessary. - if(mesh.l.n == 0) return; + if(mesh.l.IsEmpty()) + return; // We can't perform hidden line removal on exact curves. ConvertBeziersToEdges(); diff --git a/src/render/rendercairo.cpp b/src/render/rendercairo.cpp index 3091b792..e59de319 100644 --- a/src/render/rendercairo.cpp +++ b/src/render/rendercairo.cpp @@ -58,7 +58,7 @@ void CairoRenderer::OutputEnd() { } void CairoRenderer::SelectStroke(hStroke hcs) { - if(current.hcs.v == hcs.v) return; + if(current.hcs == hcs) return; FinishPath(); Stroke *stroke = strokes.FindById(hcs); diff --git a/src/render/rendergl1.cpp b/src/render/rendergl1.cpp index 0877c3c8..f0d41309 100644 --- a/src/render/rendergl1.cpp +++ b/src/render/rendergl1.cpp @@ -249,7 +249,7 @@ void OpenGl1Renderer::UnSelectPrimitive() { } Canvas::Stroke *OpenGl1Renderer::SelectStroke(hStroke hcs) { - if(current.hcs.v == hcs.v) return current.stroke; + if(current.hcs == hcs) return current.stroke; Stroke *stroke = strokes.FindById(hcs); UnSelectPrimitive(); @@ -270,7 +270,7 @@ Canvas::Stroke *OpenGl1Renderer::SelectStroke(hStroke hcs) { } Canvas::Fill *OpenGl1Renderer::SelectFill(hFill hcf) { - if(current.hcf.v == hcf.v) return current.fill; + if(current.hcf == hcf) return current.fill; Fill *fill = fills.FindById(hcf); UnSelectPrimitive(); diff --git a/src/render/rendergl3.cpp b/src/render/rendergl3.cpp index 98045926..61e1084d 100644 --- a/src/render/rendergl3.cpp +++ b/src/render/rendergl3.cpp @@ -195,7 +195,7 @@ static void ssglDepthRange(Canvas::Layer layer, int zIndex) { //----------------------------------------------------------------------------- Canvas::Stroke *OpenGl3Renderer::SelectStroke(hStroke hcs) { - if(current.hcs.v == hcs.v) return current.stroke; + if(current.hcs == hcs) return current.stroke; Stroke *stroke = strokes.FindById(hcs); ssglDepthRange(stroke->layer, stroke->zIndex); @@ -241,7 +241,7 @@ void OpenGl3Renderer::SelectMask(FillPattern pattern) { } Canvas::Fill *OpenGl3Renderer::SelectFill(hFill hcf) { - if(current.hcf.v == hcf.v) return current.fill; + if(current.hcf == hcf) return current.fill; Fill *fill = fills.FindById(hcf); ssglDepthRange(fill->layer, fill->zIndex); @@ -458,7 +458,8 @@ void OpenGl3Renderer::DrawEdges(const SEdgeList &el, hStroke hcs) { } void OpenGl3Renderer::DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs mode) { - if(ol.l.n == 0) return; + if(ol.l.IsEmpty()) + return; Stroke *stroke = SelectStroke(hcs); ssassert(stroke->stipplePattern != StipplePattern::ZIGZAG && @@ -726,8 +727,8 @@ public: // Data EdgeRenderer::Handle handle; - virtual Canvas::Layer GetLayer() const override { return stroke.layer; }; - virtual int GetZIndex() const override { return stroke.zIndex; }; + Canvas::Layer GetLayer() const override { return stroke.layer; } + int GetZIndex() const override { return stroke.zIndex; } static std::shared_ptr Create(OpenGl3Renderer *renderer, const SEdgeList &el, Canvas::Stroke *stroke) { @@ -756,8 +757,8 @@ public: OutlineRenderer::Handle handle; Canvas::DrawOutlinesAs drawAs; - virtual Canvas::Layer GetLayer() const override { return stroke.layer; }; - virtual int GetZIndex() const override { return stroke.zIndex; }; + Canvas::Layer GetLayer() const override { return stroke.layer; } + int GetZIndex() const override { return stroke.zIndex; } static std::shared_ptr Create(OpenGl3Renderer *renderer, const SOutlineList &ol, Canvas::Stroke *stroke, @@ -787,8 +788,8 @@ public: // Data IndexedMeshRenderer::Handle handle; - virtual Canvas::Layer GetLayer() const override { return stroke.layer; }; - virtual int GetZIndex() const override { return stroke.zIndex; }; + Canvas::Layer GetLayer() const override { return stroke.layer; } + int GetZIndex() const override { return stroke.zIndex; } static std::shared_ptr Create(OpenGl3Renderer *renderer, const SIndexedMesh &mesh, Canvas::Stroke *stroke) { @@ -816,8 +817,8 @@ public: // Data IndexedMeshRenderer::Handle handle; - virtual Canvas::Layer GetLayer() const override { return fill.layer; }; - virtual int GetZIndex() const override { return fill.zIndex; }; + Canvas::Layer GetLayer() const override { return fill.layer; } + int GetZIndex() const override { return fill.zIndex; } static std::shared_ptr Create(OpenGl3Renderer *renderer, const SIndexedMesh &mesh, Canvas::Fill *fill) { @@ -855,8 +856,8 @@ public: bool hasFillBack; bool isShaded; - virtual Canvas::Layer GetLayer() const override { return fillFront.layer; }; - virtual int GetZIndex() const override { return fillFront.zIndex; }; + Canvas::Layer GetLayer() const override { return fillFront.layer; } + int GetZIndex() const override { return fillFront.zIndex; } static std::shared_ptr Create(OpenGl3Renderer *renderer, const SMesh &m, Canvas::Fill *fillFront, Canvas::Fill *fillBack = NULL, @@ -1020,7 +1021,7 @@ public: void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack = {}) override { drawCalls.emplace(MeshDrawCall::Create(renderer, m, fills.FindById(hcfFront), fills.FindByIdNoOops(hcfBack), - /*lighting=*/true)); + /*isShaded=*/true)); } void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) override { diff --git a/src/request.cpp b/src/request.cpp index 13a77c38..3614cec9 100644 --- a/src/request.cpp +++ b/src/request.cpp @@ -146,7 +146,7 @@ void Request::Generate(IdList *entity, p.group = group; p.style = style; p.construction = e.construction; - if(workplane.v == Entity::FREE_IN_3D.v) { + if(workplane == Entity::FREE_IN_3D) { p.type = Entity::Type::POINT_IN_3D; // params for x y z p.param[0] = AddParam(param, h.param(16 + 3*i + 0)); @@ -168,7 +168,7 @@ void Request::Generate(IdList *entity, n.group = group; n.style = style; n.construction = e.construction; - if(workplane.v == Entity::FREE_IN_3D.v) { + if(workplane == Entity::FREE_IN_3D) { n.type = Entity::Type::NORMAL_IN_3D; n.param[0] = AddParam(param, h.param(32+0)); n.param[1] = AddParam(param, h.param(32+1)); @@ -203,11 +203,11 @@ void Request::Generate(IdList *entity, std::string Request::DescriptionString() const { const char *s = ""; - if(h.v == Request::HREQUEST_REFERENCE_XY.v) { + if(h == Request::HREQUEST_REFERENCE_XY) { s = "#XY"; - } else if(h.v == Request::HREQUEST_REFERENCE_YZ.v) { + } else if(h == Request::HREQUEST_REFERENCE_YZ) { s = "#YZ"; - } else if(h.v == Request::HREQUEST_REFERENCE_ZX.v) { + } else if(h == Request::HREQUEST_REFERENCE_ZX) { s = "#ZX"; } else { switch(type) { @@ -228,10 +228,10 @@ std::string Request::DescriptionString() const { int Request::IndexOfPoint(hEntity he) const { if(type == Type::DATUM_POINT) { - return (he.v == h.entity(0).v) ? 0 : -1; + return (he == h.entity(0)) ? 0 : -1; } for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) { - if(he.v == h.entity(i + 1).v) { + if(he == h.entity(i + 1)) { return i; } } diff --git a/src/resource.cpp b/src/resource.cpp index 2722cf82..6e9ce727 100644 --- a/src/resource.cpp +++ b/src/resource.cpp @@ -540,7 +540,7 @@ void BitmapFont::AddGlyph(char32_t codepoint, std::shared_ptr pixm BitmapFont::Glyph glyph = {}; glyph.advanceCells = (uint8_t)(pixmap->width / 8); glyph.position = nextPosition++; - glyphs.emplace(codepoint, std::move(glyph)); + glyphs.emplace(codepoint, glyph); for(size_t y = 0; y < pixmap->height; y++) { uint8_t *row = BitmapFontTextureRow(texture, glyph.position, y); @@ -623,7 +623,8 @@ const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) { } } - it = glyphs.emplace(codepoint, std::move(glyph)).first; + it = glyphs.emplace(codepoint, glyph).first; + textureUpdated = true; return (*it).second; } @@ -1147,7 +1148,7 @@ PluralExpr::Token PluralExpr::Lex() { } PluralExpr::Token PluralExpr::PopToken() { - ssassert(stack.size() > 0, "Expected a non-empty stack"); + ssassert(!stack.empty(), "Expected a non-empty stack"); Token t = stack.back(); stack.pop_back(); return t; @@ -1406,7 +1407,7 @@ void GettextParser::Parse() { } } - if(key.ident == "") { + if(key.ident.empty()) { ssassert(msgstrs.size() == 1, "Expected exactly one header msgstr"); ParseHeader(msgstrs[0]); diff --git a/src/sketch.h b/src/sketch.h index b61ee529..6cd4acaa 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -59,6 +59,10 @@ public: inline hParam param(int i) const; inline hEquation equation(int i) const; }; + +template<> +struct IsHandleOracle
: std::true_type {}; + class hRequest { public: // bits 15: 0 -- request index @@ -69,6 +73,10 @@ public: inline bool IsFromReferences() const; }; + +template<> +struct IsHandleOracle : std::true_type {}; + class hEntity { public: // bits 15: 0 -- entity index @@ -80,6 +88,10 @@ public: inline hGroup group() const; inline hEquation equation(int i) const; }; + +template<> +struct IsHandleOracle : std::true_type {}; + class hParam { public: // bits 15: 0 -- param index @@ -89,14 +101,24 @@ public: inline hRequest request() const; }; +template<> +struct IsHandleOracle : std::true_type {}; + class hStyle { public: uint32_t v; }; +template<> +struct IsHandleOracle : std::true_type {}; + struct EntityId { uint32_t v; // entity ID, starting from 0 }; + +template<> +struct IsHandleOracle : std::true_type {}; + struct EntityKey { hEntity input; int copyNumber; @@ -111,7 +133,7 @@ struct EntityKeyHash { }; struct EntityKeyEqual { bool operator()(const EntityKey &a, const EntityKey &b) const { - return std::tie(a.input.v, a.copyNumber) == std::tie(b.input.v, b.copyNumber); + return std::tie(a.input, a.copyNumber) == std::tie(b.input, b.copyNumber); } }; typedef std::unordered_map EntityMap; @@ -129,6 +151,7 @@ public: N_TRANS, N_ROT_AA, N_ROT_TRANS, + N_ROT_AXIS_TRANS, }; enum class Type : uint32_t { @@ -136,6 +159,8 @@ public: DRAWING_WORKPLANE = 5001, EXTRUDE = 5100, LATHE = 5101, + REVOLVE = 5102, + HELIX = 5103, ROTATE = 5200, TRANSLATE = 5201, LINKED = 5300 @@ -258,11 +283,12 @@ public: hEntity Remap(hEntity in, int copyNumber); void MakeExtrusionLines(EntityList *el, hEntity in); void MakeLatheCircles(IdList *el, IdList *param, hEntity in, Vector pt, Vector axis, int ai); + void MakeLatheSurfacesSelectable(IdList *el, hEntity in, Vector axis); void MakeExtrusionTopBottomFaces(EntityList *el, hEntity pt); void CopyEntity(EntityList *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); void AddEq(IdList *l, Expr *expr, int index); @@ -363,6 +389,7 @@ public: POINT_N_ROT_TRANS = 2011, POINT_N_COPY = 2012, POINT_N_ROT_AA = 2013, + POINT_N_ROT_AXIS_TRANS = 2014, NORMAL_IN_3D = 3000, NORMAL_IN_2D = 3001, @@ -402,7 +429,7 @@ public: hEntity distance; // The only types that have their own params are points, normals, // and directions. - hParam param[7]; + hParam param[8]; // Transformed points/normals/distances have their numerical base Vector numPoint; @@ -593,6 +620,9 @@ public: inline hParam param(int i) const; }; +template<> +struct IsHandleOracle : std::true_type {}; + class ConstraintBase { public: int tag; @@ -660,10 +690,10 @@ public: std::string comment; // since comments are represented as constraints bool Equals(const ConstraintBase &c) const { - return type == c.type && group.v == c.group.v && workplane.v == c.workplane.v && - valA == c.valA && valP.v == c.valP.v && ptA.v == c.ptA.v && ptB.v == c.ptB.v && - entityA.v == c.entityA.v && entityB.v == c.entityB.v && - entityC.v == c.entityC.v && entityD.v == c.entityD.v && + return type == c.type && group == c.group && workplane == c.workplane && + valA == c.valA && valP == c.valP && ptA == c.ptA && ptB == c.ptB && + entityA == c.entityA && entityB == c.entityB && + entityC == c.entityC && entityD == c.entityD && other == c.other && other2 == c.other2 && reference == c.reference && comment == c.comment; } @@ -761,6 +791,9 @@ public: inline hConstraint constraint() const; }; +template<> +struct IsHandleOracle : std::true_type {}; + class Equation { public: int tag; @@ -887,9 +920,9 @@ inline hEquation hGroup::equation(int i) const { hEquation r; r.v = (v << 16) | 0x80000000 | (uint32_t)i; return r; } inline bool hRequest::IsFromReferences() const { - if(v == Request::HREQUEST_REFERENCE_XY.v) return true; - if(v == Request::HREQUEST_REFERENCE_YZ.v) return true; - if(v == Request::HREQUEST_REFERENCE_ZX.v) return true; + if(*this == Request::HREQUEST_REFERENCE_XY) return true; + if(*this == Request::HREQUEST_REFERENCE_YZ) return true; + if(*this == Request::HREQUEST_REFERENCE_ZX) return true; return false; } inline hEntity hRequest::entity(int i) const diff --git a/src/solvespace.cpp b/src/solvespace.cpp index c09ba03e..2c0157d0 100644 --- a/src/solvespace.cpp +++ b/src/solvespace.cpp @@ -772,70 +772,48 @@ void SolveSpaceUI::MenuAnalyze(Command id) { } case Command::VOLUME: { - SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); + Group *g = SK.GetGroup(SS.GW.activeGroup); + double totalVol = g->displayMesh.CalculateVolume(); + std::string msg = ssprintf( + _("The volume of the solid model is:\n\n" + " %s"), + SS.MmToStringSI(totalVol, /*dim=*/3).c_str()); - double vol = 0; - int i; - for(i = 0; i < m->l.n; i++) { - STriangle tr = m->l.elem[i]; - - // 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; + SMesh curMesh = {}; + g->thisShell.TriangulateInto(&curMesh); + double curVol = curMesh.CalculateVolume(); + if(curVol > 0.0) { + msg += ssprintf( + _("\nThe volume of current group mesh is:\n\n" + " %s"), + SS.MmToStringSI(curVol, /*dim=*/3).c_str()); } - Message(_("The volume of the solid model is:\n\n" - " %s\n\n" - "Curved surfaces have been approximated as triangles.\n" - "This introduces error, typically of around 1%%."), - SS.MmToStringSI(vol, /*dim=*/3).c_str()); + + msg += _("\n\nCurved surfaces have been approximated as triangles.\n" + "This introduces error, typically of around 1%."); + Message("%s", msg.c_str()); break; } case Command::AREA: { Group *g = SK.GetGroup(SS.GW.activeGroup); + SS.GW.GroupSelection(); + auto const &gs = SS.GW.gs; + double scale = SS.MmPerUnit(); + + if(gs.faces > 0) { + std::vector faces; + faces.push_back(gs.face[0].v); + if(gs.faces > 1) faces.push_back(gs.face[1].v); + double area = g->displayMesh.CalculateSurfaceArea(faces); + Message(_("The surface area of the selected faces is:\n\n" + " %s\n\n" + "Curves have been approximated as piecewise linear.\n" + "This introduces error, typically of around 1%%."), + SS.MmToStringSI(area, /*dim=*/2).c_str()); + break; + } + if(g->polyError.how != PolyError::GOOD) { Error(_("This group does not contain a correctly-formed " "2d closed area. It is open, not coplanar, or self-" @@ -907,7 +885,7 @@ void SolveSpaceUI::MenuAnalyze(Command id) { int i; SContour *sc = &(SS.traced.path); for(i = 0; i < sc->l.n; i++) { - Vector p = sc->l.elem[i].p; + Vector p = sc->l[i].p; double s = SS.exportScale; fprintf(f, "%.10f, %.10f, %.10f\r\n", p.x/s, p.y/s, p.z/s); @@ -938,7 +916,7 @@ void SolveSpaceUI::ShowNakedEdges(bool reportOnlyWhenNotOkay) { root->MakeCertainEdgesInto(&(SS.nakedEdges), EdgeKind::NAKED_OR_SELF_INTER, /*coplanarIsInter=*/true, &inters, &leaks); - if(reportOnlyWhenNotOkay && !inters && !leaks && SS.nakedEdges.l.n == 0) { + if(reportOnlyWhenNotOkay && !inters && !leaks && SS.nakedEdges.l.IsEmpty()) { return; } SS.GW.Invalidate(); @@ -954,7 +932,7 @@ void SolveSpaceUI::ShowNakedEdges(bool reportOnlyWhenNotOkay) { _("\n\nThe model contains %d triangles, from %d surfaces."), g->displayMesh.l.n, g->runningShell.surface.n); - if(SS.nakedEdges.l.n == 0) { + if(SS.nakedEdges.l.IsEmpty()) { Message(_("%s\n\n%s\n\nZero problematic edges, good.%s"), intersMsg, leaksMsg, cntMsg.c_str()); } else { @@ -1045,7 +1023,7 @@ BBox Sketch::CalculateEntityBBox(bool includingInvisible) { // arc center point shouldn't be included in bounding box calculation if(e.IsPoint() && e.h.isFromRequest()) { Request *r = SK.GetRequest(e.h.request()); - if(r->type == Request::Type::ARC_OF_CIRCLE && e.h.v == r->h.entity(1).v) { + if(r->type == Request::Type::ARC_OF_CIRCLE && e.h == r->h.entity(1)) { continue; } } diff --git a/src/solvespace.h b/src/solvespace.h index 4e18a742..64397414 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -165,13 +165,8 @@ enum class Unit : uint32_t { METERS }; -template -struct CompareHandle { - bool operator()(T lhs, T rhs) const { return lhs.v < rhs.v; } -}; - template -using handle_map = std::map>; +using handle_map = std::map; class Group; class SSurface; @@ -219,7 +214,7 @@ class ReadUTF8 { public: ReadUTF8(const std::string &str) : str(str) {} utf8_iterator begin() const { return utf8_iterator(&str[0]); } - utf8_iterator end() const { return utf8_iterator(&str[str.length()]); } + utf8_iterator end() const { return utf8_iterator(&str[0] + str.length()); } }; @@ -703,6 +698,7 @@ public: void ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm); void ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename, SMesh *sm, SOutlineList *sol); + void ExportMeshAsVrmlTo(FILE *f, const Platform::Path &filename, SMesh *sm); void ExportViewOrWireframeTo(const Platform::Path &filename, bool exportWireframe); void ExportSectionTo(const Platform::Path &filename); void ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl, diff --git a/src/srf/boolean.cpp b/src/srf/boolean.cpp index cc763a26..487ed755 100644 --- a/src/srf/boolean.cpp +++ b/src/srf/boolean.cpp @@ -24,17 +24,6 @@ void SShell::MakeFromDifferenceOf(SShell *a, SShell *b) { // the intersection of srfA and srfB.) Return a new pwl curve with everything // split. //----------------------------------------------------------------------------- -static Vector LineStart, LineDirection; -static int ByTAlongLine(const void *av, const void *bv) -{ - SInter *a = (SInter *)av, - *b = (SInter *)bv; - - double ta = (a->p.Minus(LineStart)).DivPivoting(LineDirection), - tb = (b->p.Minus(LineStart)).DivPivoting(LineDirection); - - return (ta > tb) ? 1 : -1; -} SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, SSurface *srfA, SSurface *srfB) const { @@ -59,7 +48,7 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, agnstB->AllPointsIntersecting(prev.p, p->p, &il, /*asSegment=*/true, /*trimmed=*/false, /*inclTangent=*/true); - if(il.n > 0) { + if(!il.IsEmpty()) { // The intersections were generated by intersecting the pwl // edge against a surface; so they must be refined to lie // exactly on the original curve. @@ -104,9 +93,14 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, // And now sort them in order along the line. Note that we must // do that after refining, in case the refining would make two // points switch places. - LineStart = prev.p; - LineDirection = (p->p).Minus(prev.p); - qsort(il.elem, il.n, sizeof(il.elem[0]), ByTAlongLine); + const Vector lineStart = prev.p; + const Vector lineDirection = (p->p).Minus(prev.p); + std::sort(il.begin(), il.end(), [&](const SInter &a, const SInter &b) { + double ta = (a.p.Minus(lineStart)).DivProjected(lineDirection); + double tb = (b.p.Minus(lineStart)).DivProjected(lineDirection); + + return (ta < tb); + }); // And now uses the intersections to generate our split pwl edge(s) Vector prev = Vector::From(VERY_POSITIVE, 0, 0); @@ -297,18 +291,18 @@ static void DEBUGEDGELIST(SEdgeList *sel, SSurface *surf) { void SSurface::FindChainAvoiding(SEdgeList *src, SEdgeList *dest, SPointList *avoid) { - ssassert(src->l.n > 0, "Need at least one edge"); + ssassert(!src->l.IsEmpty(), "Need at least one edge"); // Start with an arbitrary edge. - dest->l.Add(&(src->l.elem[0])); + dest->l.Add(src->l.First()); src->l.ClearTags(); - src->l.elem[0].tag = 1; + src->l.First()->tag = 1; bool added; do { added = false; // The start and finish of the current edge chain - Vector s = dest->l.elem[0].a, - f = dest->l.elem[dest->l.n - 1].b; + Vector s = dest->l.First()->a, + f = dest->l.Last()->b; // We can attach a new edge at the start or finish, as long as that // start or finish point isn't in the list of points to avoid. @@ -444,15 +438,15 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) { if(sc->source != SCurve::Source::INTERSECTION) continue; if(opA) { - if(sc->surfA.v != h.v || sc->surfB.v != ss->h.v) continue; + if(sc->surfA != h || sc->surfB != ss->h) continue; } else { - if(sc->surfB.v != h.v || sc->surfA.v != ss->h.v) continue; + if(sc->surfB != h || sc->surfA != ss->h) continue; } int i; for(i = 1; i < sc->pts.n; i++) { - Vector a = sc->pts.elem[i-1].p, - b = sc->pts.elem[i].p; + Vector a = sc->pts[i-1].p, + b = sc->pts[i].p; Point2d auv, buv; ss->ClosestPointTo(a, &(auv.x), &(auv.y)); @@ -512,13 +506,13 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, // our original and intersecting edge lists. SEdgeList final = {}; - while(orig.l.n > 0) { + while(!orig.l.IsEmpty()) { SEdgeList chain = {}; FindChainAvoiding(&orig, &chain, &choosing); // Arbitrarily choose an edge within the chain to classify; they // should all be the same, though. - se = &(chain.l.elem[chain.l.n/2]); + se = &(chain.l[chain.l.n/2]); Point2d auv = (se->a).ProjectXy(), buv = (se->b).ProjectXy(); @@ -546,12 +540,12 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, chain.Clear(); } - while(inter.l.n > 0) { + while(!inter.l.IsEmpty()) { SEdgeList chain = {}; FindChainAvoiding(&inter, &chain, &choosing); // Any edge in the chain, same as above. - se = &(chain.l.elem[chain.l.n/2]); + se = &(chain.l[chain.l.n/2]); Point2d auv = (se->a).ProjectXy(), buv = (se->b).ProjectXy(); @@ -731,7 +725,7 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type) { a->MakeClassifyingBsps(this); b->MakeClassifyingBsps(this); - if(b->surface.n == 0 || a->surface.n == 0) { + if(b->surface.IsEmpty() || a->surface.IsEmpty()) { I = 1000000; } else { I = 0; @@ -775,19 +769,6 @@ SBspUv *SBspUv::Alloc() { return (SBspUv *)AllocTemporary(sizeof(SBspUv)); } -static int ByLength(const void *av, const void *bv) -{ - SEdge *a = (SEdge *)av, - *b = (SEdge *)bv; - - double la = (a->a).Minus(a->b).Magnitude(), - lb = (b->a).Minus(b->b).Magnitude(); - - // Sort in descending order, longest first. This improves numerical - // stability for the normals. - return (la < lb) ? 1 : -1; -} - SBspUv *SBspUv::From(SEdgeList *el, SSurface *srf) { SEdgeList work = {}; @@ -795,8 +776,12 @@ SBspUv *SBspUv::From(SEdgeList *el, SSurface *srf) { for(se = el->l.First(); se; se = el->l.NextAfter(se)) { work.AddEdge(se->a, se->b, se->auxA, se->auxB); } - qsort(work.l.elem, work.l.n, sizeof(work.l.elem[0]), ByLength); - + std::sort(work.l.begin(), work.l.end(), [](SEdge const &a, SEdge const &b) { + double la = (a.a).Minus(a.b).Magnitude(), lb = (b.a).Minus(b.b).Magnitude(); + // Sort in descending order, longest first. This improves numerical + // stability for the normals. + return la > lb; + }); SBspUv *bsp = NULL; for(se = work.l.First(); se; se = work.l.NextAfter(se)) { bsp = InsertOrCreateEdge(bsp, (se->a).ProjectXy(), (se->b).ProjectXy(), srf); diff --git a/src/srf/curve.cpp b/src/srf/curve.cpp index ef30dd5c..9bdcca28 100644 --- a/src/srf/curve.cpp +++ b/src/srf/curve.cpp @@ -239,12 +239,12 @@ void SBezierList::CullIdenticalBeziers(bool both) { l.ClearTags(); for(i = 0; i < l.n; i++) { - SBezier *bi = &(l.elem[i]), bir; + SBezier *bi = &(l[i]), bir; bir = *bi; bir.Reverse(); for(j = i + 1; j < l.n; j++) { - SBezier *bj = &(l.elem[j]); + SBezier *bj = &(l[j]); if(bj->Equals(bi) || bj->Equals(&bir)) { @@ -311,8 +311,8 @@ bool SBezierList::GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v, int i; // Get any point on any Bezier; or an arbitrary point if list is empty. - if(l.n > 0) { - pt = l.elem[0].Start(); + if(!l.IsEmpty()) { + pt = l[0].Start(); } else { pt = Vector::From(0, 0, 0); } @@ -393,7 +393,7 @@ SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl, if(sbl->l.n < 1) return loop; sbl->l.ClearTags(); - SBezier *first = &(sbl->l.elem[0]); + SBezier *first = &(sbl->l[0]); first->tag = 1; loop.l.Add(first); Vector start = first->Start(); @@ -402,11 +402,11 @@ SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl, sbl->l.RemoveTagged(); - while(sbl->l.n > 0 && !hanging.Equals(start)) { + while(!sbl->l.IsEmpty() && !hanging.Equals(start)) { int i; bool foundNext = false; for(i = 0; i < sbl->l.n; i++) { - SBezier *test = &(sbl->l.elem[i]); + SBezier *test = &(sbl->l[i]); if((test->Finish()).Equals(hanging) && test->auxA == auxA) { test->Reverse(); @@ -470,15 +470,15 @@ void SBezierLoop::MakePwlInto(SContour *sc, double chordTol) const { } } // Ensure that it's exactly closed, not just within a numerical tolerance. - if((sc->l.elem[sc->l.n - 1].p).Equals(sc->l.elem[0].p)) { - sc->l.elem[sc->l.n - 1] = sc->l.elem[0]; + if((sc->l.Last()->p).Equals(sc->l.First()->p)) { + *sc->l.Last() = *sc->l.First(); } } bool SBezierLoop::IsClosed() const { - if(l.n < 1) return false; - Vector s = l.elem[0].Start(), - f = l.elem[l.n-1].Finish(); + if(l.IsEmpty()) return false; + Vector s = l.First()->Start(), + f = l.Last()->Finish(); return s.Equals(f); } @@ -497,7 +497,7 @@ SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly, SBezierLoopSet ret = {}; *allClosed = true; - while(sbl->l.n > 0) { + while(!sbl->l.IsEmpty()) { bool thisClosed; SBezierLoop loop; loop = SBezierLoop::FromCurves(sbl, &thisClosed, errorAt); @@ -512,7 +512,7 @@ SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly, } else { ret.l.Add(&loop); poly->AddEmptyContour(); - loop.MakePwlInto(&(poly->l.elem[poly->l.n-1]), chordTol); + loop.MakePwlInto(poly->l.Last(), chordTol); } } @@ -553,14 +553,14 @@ double SBezierLoopSet::SignedArea() { void SBezierLoopSet::MakePwlInto(SPolygon *sp) const { for(const SBezierLoop *sbl = l.First(); sbl; sbl = l.NextAfter(sbl)) { sp->AddEmptyContour(); - sbl->MakePwlInto(&(sp->l.elem[sp->l.n - 1])); + sbl->MakePwlInto(sp->l.Last()); } } void SBezierLoopSet::Clear() { int i; for(i = 0; i < l.n; i++) { - (l.elem[i]).Clear(); + (l[i]).Clear(); } l.Clear(); } @@ -618,7 +618,7 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, for(pt = sc->l.First(); pt; pt = sc->l.NextAfter(pt)) { double u, v; srfuv->ClosestPointTo(pt->p, &u, &v); - spuv.l.elem[spuv.l.n - 1].AddPoint(Vector::From(u, v, 0)); + spuv.l.Last()->AddPoint(Vector::From(u, v, 0)); } } spuv.normal = Vector::From(0, 0, 1); // must be, since it's in xy plane now @@ -630,8 +630,8 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, // works for curved surfaces too (important for STEP export). spuv.FixContourDirections(); for(i = 0; i < spuv.l.n; i++) { - SContour *contour = &(spuv.l.elem[i]); - SBezierLoop *bl = &(sbls.l.elem[i]); + SContour *contour = &(spuv.l[i]); + SBezierLoop *bl = &(sbls.l[i]); if(contour->tag) { // This contour got reversed in the polygon to make the directions // consistent, so the same must be necessary for the Bezier loop. @@ -648,7 +648,7 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, while(loopsRemaining) { loopsRemaining = false; for(i = 0; i < sbls.l.n; i++) { - SBezierLoop *loop = &(sbls.l.elem[i]); + SBezierLoop *loop = &(sbls.l[i]); if(loop->tag != OUTER_LOOP) continue; // Check if this contour contains any outer loops; if it does, then @@ -656,12 +656,12 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, // will steal their holes, since their holes also lie inside this // contour. for(j = 0; j < sbls.l.n; j++) { - SBezierLoop *outer = &(sbls.l.elem[j]); + SBezierLoop *outer = &(sbls.l[j]); if(i == j) continue; if(outer->tag != OUTER_LOOP) continue; - Vector p = spuv.l.elem[j].AnyEdgeMidpoint(); - if(spuv.l.elem[i].ContainsPointProjdToNormal(spuv.normal, p)) { + Vector p = spuv.l[j].AnyEdgeMidpoint(); + if(spuv.l[i].ContainsPointProjdToNormal(spuv.normal, p)) { break; } } @@ -675,16 +675,16 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, loop->tag = USED_LOOP; outerAndInners.l.Add(loop); int auxA = 0; - if(loop->l.n > 0) auxA = loop->l.elem[0].auxA; + if(loop->l.n > 0) auxA = loop->l[0].auxA; for(j = 0; j < sbls.l.n; j++) { - SBezierLoop *inner = &(sbls.l.elem[j]); + SBezierLoop *inner = &(sbls.l[j]); if(inner->tag != INNER_LOOP) continue; if(inner->l.n < 1) continue; - if(inner->l.elem[0].auxA != auxA) continue; + if(inner->l[0].auxA != auxA) continue; - Vector p = spuv.l.elem[j].AnyEdgeMidpoint(); - if(spuv.l.elem[i].ContainsPointProjdToNormal(spuv.normal, p)) { + Vector p = spuv.l[j].AnyEdgeMidpoint(); + if(spuv.l[i].ContainsPointProjdToNormal(spuv.normal, p)) { outerAndInners.l.Add(inner); inner->tag = USED_LOOP; } @@ -702,7 +702,7 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, // to screw up on that stuff. So just add them onto the open curve list. // Very ugly, but better than losing curves. for(i = 0; i < sbls.l.n; i++) { - SBezierLoop *loop = &(sbls.l.elem[i]); + SBezierLoop *loop = &(sbls.l[i]); if(loop->tag == USED_LOOP) continue; if(openContours) { @@ -804,11 +804,11 @@ void SCurve::RemoveShortSegments(SSurface *srfA, SSurface *srfB) { if(pts.n <= 3) return; pts.ClearTags(); - Vector prev = pts.elem[0].p; + Vector prev = pts[0].p; int i, a; for(i = 1; i < pts.n - 1; i++) { - SCurvePt *sct = &(pts.elem[i]), - *scn = &(pts.elem[i+1]); + SCurvePt *sct = &(pts[i]), + *scn = &(pts[i+1]); if(sct->vertex) { prev = sct->p; continue; @@ -849,12 +849,12 @@ STrimBy STrimBy::EntireCurve(SShell *shell, hSCurve hsc, bool backwards) { SCurve *sc = shell->curve.FindById(hsc); if(backwards) { - stb.finish = sc->pts.elem[0].p; - stb.start = sc->pts.elem[sc->pts.n - 1].p; + stb.finish = sc->pts[0].p; + stb.start = sc->pts.Last()->p; stb.backwards = true; } else { - stb.start = sc->pts.elem[0].p; - stb.finish = sc->pts.elem[sc->pts.n - 1].p; + stb.start = sc->pts[0].p; + stb.finish = sc->pts.Last()->p; stb.backwards = false; } diff --git a/src/srf/merge.cpp b/src/srf/merge.cpp index 575b1ea2..a91a307f 100644 --- a/src/srf/merge.cpp +++ b/src/srf/merge.cpp @@ -13,11 +13,12 @@ void SShell::MergeCoincidentSurfaces() { SSurface *si, *sj; for(i = 0; i < surface.n; i++) { - si = &(surface.elem[i]); + si = &(surface[i]); if(si->tag) continue; // Let someone else clean up the empty surfaces; we can certainly merge // them, but we don't know how to calculate a reasonable bounding box. - if(si->trim.n == 0) continue; + if(si->trim.IsEmpty()) + continue; // And for now we handle only coincident planes, so no sense wasting // time on other surfaces. if(si->degm != 1 || si->degn != 1) continue; @@ -30,7 +31,7 @@ void SShell::MergeCoincidentSurfaces() { mergedThisTime = false; for(j = i + 1; j < surface.n; j++) { - sj = &(surface.elem[j]); + sj = &(surface[j]); if(sj->tag) continue; if(!sj->CoincidentWith(si, /*sameNormal=*/true)) continue; if(!sj->color.Equals(si->color)) continue; @@ -59,8 +60,8 @@ void SShell::MergeCoincidentSurfaces() { // new srf SCurve *sc; for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) { - if(sc->surfA.v == sj->h.v) sc->surfA = si->h; - if(sc->surfB.v == sj->h.v) sc->surfB = si->h; + if(sc->surfA == sj->h) sc->surfA = si->h; + if(sc->surfB == sj->h) sc->surfB = si->h; } } diff --git a/src/srf/ratpoly.cpp b/src/srf/ratpoly.cpp index 17618260..d4fd703f 100644 --- a/src/srf/ratpoly.cpp +++ b/src/srf/ratpoly.cpp @@ -155,7 +155,7 @@ void SBezier::ClosestPointTo(Vector p, double *t, bool mustConverge) const { Vector dp = TangentAt(*t); Vector pc = p.ClosestPointOnLine(p0, dp); - *t += (pc.Minus(p0)).DivPivoting(dp); + *t += (pc.Minus(p0)).DivProjected(dp); } if(mustConverge) { dbp("didn't converge (closest point on bezier curve)"); @@ -231,7 +231,7 @@ void SBezier::MakePwlInto(SEdgeList *sel, double chordTol) const { MakePwlInto(&lv, chordTol); int i; for(i = 1; i < lv.n; i++) { - sel->AddEdge(lv.elem[i-1], lv.elem[i]); + sel->AddEdge(lv[i-1], lv[i]); } lv.Clear(); } @@ -242,7 +242,7 @@ void SBezier::MakePwlInto(List *l, double chordTol) const { for(i = 0; i < lv.n; i++) { SCurvePt scpt; scpt.tag = 0; - scpt.p = lv.elem[i]; + scpt.p = lv[i]; scpt.vertex = (i == 0) || (i == (lv.n - 1)); l->Add(&scpt); } @@ -253,7 +253,7 @@ void SBezier::MakePwlInto(SContour *sc, double chordTol) const { MakePwlInto(&lv, chordTol); int i; for(i = 0; i < lv.n; i++) { - sc->AddPoint(lv.elem[i]); + sc->AddPoint(lv[i]); } lv.Clear(); } diff --git a/src/srf/raycast.cpp b/src/srf/raycast.cpp index f3153cfd..4d653377 100644 --- a/src/srf/raycast.cpp +++ b/src/srf/raycast.cpp @@ -311,7 +311,7 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b, } int i; for(i = 0; i < ip_n; i++) { - double t = (ip[i].Minus(ap)).DivPivoting(bp.Minus(ap)); + double t = (ip[i].Minus(ap)).DivProjected(bp.Minus(ap)); // This is a point on the circle; but is it on the arc? Point2d pp = ap.Plus((bp.Minus(ap)).ScaledBy(t)); double theta = atan2(pp.y, pp.x); @@ -342,19 +342,19 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b, int i, j; for(i = 0; i < inters.n; i++) { for(j = i + 1; j < inters.n; j++) { - if(inters.elem[i].p.Equals(inters.elem[j].p)) { - inters.elem[j].tag = 1; + if(inters[i].p.Equals(inters[j].p)) { + inters[j].tag = 1; } } } inters.RemoveTagged(); for(i = 0; i < inters.n; i++) { - Point2d puv = inters.elem[i].p; + Point2d puv = inters[i].p; // Make sure the point lies within the finite line segment Vector pxyz = PointAt(puv.x, puv.y); - double t = (pxyz.Minus(a)).DivPivoting(ba); + double t = (pxyz.Minus(a)).DivProjected(ba); if(asSegment && (t > 1 - LENGTH_EPS/bam || t < LENGTH_EPS/bam)) { continue; } @@ -463,7 +463,7 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, } if(edge_inters == 2) { - // TODO, make this use the appropriate curved normals + //! @todo make this use the appropriate curved normals double dotp[2]; for(int i = 0; i < 2; i++) { dotp[i] = edge_n_out.DirectionCosineWith(inter_surf_n[i]); @@ -566,7 +566,7 @@ bool SShell::ClassifyEdge(Class *indir, Class *outdir, SInter *si; for(si = l.First(); si; si = l.NextAfter(si)) { - double t = ((si->p).Minus(p)).DivPivoting(ray); + double t = ((si->p).Minus(p)).DivProjected(ray); if(t*ray.Magnitude() < -LENGTH_EPS) { // wrong side, doesn't count continue; diff --git a/src/srf/surface.cpp b/src/srf/surface.cpp index fcd9623c..b90fb55f 100644 --- a/src/srf/surface.cpp +++ b/src/srf/surface.cpp @@ -63,18 +63,19 @@ bool SSurface::IsCylinder(Vector *axis, Vector *center, double *r, return true; } -SSurface SSurface::FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, - double thetas, double thetaf) -{ +// Create a surface patch by revolving and possibly translating a curve. +// Works for sections up to but not including 180 degrees. +SSurface SSurface::FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, double thetas, + double thetaf, double dists, + double distf) { // s is start, f is finish SSurface ret = {}; - - ret.degm = sb->deg; ret.degn = 2; double dtheta = fabs(WRAP_SYMMETRIC(thetaf - thetas, 2*PI)); + double w = cos(dtheta / 2); - // We now wish to revolve the curve about the z axis + // Revolve the curve about the z axis int i; for(i = 0; i <= ret.degm; i++) { Vector p = sb->ctrl[i]; @@ -82,32 +83,26 @@ SSurface SSurface::FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, Vector ps = p.RotatedAbout(pt, axis, thetas), pf = p.RotatedAbout(pt, axis, thetaf); - Vector ct; + // The middle control point should be at the intersection of the tangents at ps and pf. + // This is equivalent but works for 0 <= angle < 180 degrees. + Vector mid = ps.Plus(pf).ScaledBy(0.5); + Vector c = ps.ClosestPointOnLine(pt, axis); + Vector ct = mid.Minus(c).ScaledBy(1 / (w * w)).Plus(c); + + // not sure this is needed if(ps.Equals(pf)) { - // Degenerate case: a control point lies on the axis of revolution, - // so we get three coincident control points. - ct = ps; - } else { - // Normal case, the control point sweeps out a circle. - Vector c = ps.ClosestPointOnLine(pt, axis); - - Vector rs = ps.Minus(c), - rf = pf.Minus(c); - - Vector ts = axis.Cross(rs), - tf = axis.Cross(rf); - - ct = Vector::AtIntersectionOfLines(ps, ps.Plus(ts), - pf, pf.Plus(tf), - NULL, NULL, NULL); + ps = c; + ct = c; + pf = c; } - - ret.ctrl[i][0] = ps; - ret.ctrl[i][1] = ct; - ret.ctrl[i][2] = pf; + // moving along the axis can create hilical surfaces (or straight extrusion if + // thetas==thetaf) + ret.ctrl[i][0] = ps.Plus(axis.ScaledBy(dists)); + ret.ctrl[i][1] = ct.Plus(axis.ScaledBy((dists + distf) / 2)); + ret.ctrl[i][2] = pf.Plus(axis.ScaledBy(distf)); ret.weight[i][0] = sb->weight[i]; - ret.weight[i][1] = sb->weight[i]*cos(dtheta/2); + ret.weight[i][1] = sb->weight[i] * w; ret.weight[i][2] = sb->weight[i]; } @@ -240,7 +235,7 @@ void SSurface::MakeTrimEdgesInto(SEdgeList *sel, MakeAs flags, increment = 1; } for(i = first; i != (last + increment); i += increment) { - Vector tpt, *pt = &(sc->pts.elem[i].p); + Vector tpt, *pt = &(sc->pts[i].p); if(flags == MakeAs::UV) { ClosestPointTo(*pt, &u, &v); @@ -348,11 +343,11 @@ void SSurface::MakeSectionEdgesInto(SShell *shell, SEdgeList *sel, SBezierList * int sp, fp; for(sp = 0; sp < sc->pts.n; sp++) { - if(s.Equals(sc->pts.elem[sp].p)) break; + if(s.Equals(sc->pts[sp].p)) break; } if(sp >= sc->pts.n) return; for(fp = sp; fp < sc->pts.n; fp++) { - if(f.Equals(sc->pts.elem[fp].p)) break; + if(f.Equals(sc->pts[fp].p)) break; } if(fp >= sc->pts.n) return; // So now the curve we want goes from elem[sp] to elem[fp] @@ -365,8 +360,8 @@ void SSurface::MakeSectionEdgesInto(SShell *shell, SEdgeList *sel, SBezierList * for(;;) { // So construct a cubic Bezier with the correct endpoints // and tangents for the current span. - Vector st = sc->pts.elem[sp].p, - ft = sc->pts.elem[fpt].p, + Vector st = sc->pts[sp].p, + ft = sc->pts[fpt].p, sf = ft.Minus(st); double m = sf.Magnitude() / 3; @@ -383,7 +378,7 @@ void SSurface::MakeSectionEdgesInto(SShell *shell, SEdgeList *sel, SBezierList * int i; bool tooFar = false; for(i = sp + 1; i <= (fpt - 1); i++) { - Vector p = sc->pts.elem[i].p; + Vector p = sc->pts[i].p; double t; sb.ClosestPointTo(p, &t, /*mustConverge=*/false); Vector pp = sb.PointAt(t); @@ -440,7 +435,7 @@ void SSurface::TriangulateInto(SShell *shell, SMesh *sm) { STriMeta meta = { face, color }; for(i = start; i < sm->l.n; i++) { - STriangle *st = &(sm->l.elem[i]); + STriangle *st = &(sm->l[i]); st->meta = meta; st->an = NormalAt(st->a.x, st->a.y); st->bn = NormalAt(st->b.x, st->b.y); @@ -593,10 +588,10 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, Rgb int i; for(i = 0; i < trimLines.n; i++) { - TrimLine *tl = &(trimLines.elem[i]); + TrimLine *tl = &(trimLines[i]); SSurface *ss = surface.FindById(tl->hs); - TrimLine *tlp = &(trimLines.elem[WRAP(i-1, trimLines.n)]); + TrimLine *tlp = &(trimLines[WRAP(i-1, trimLines.n)]); STrimBy stb; stb = STrimBy::EntireCurve(this, tl->hc, /*backwards=*/true); @@ -611,20 +606,11 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, Rgb } } - -typedef struct { - hSSurface d[4]; -} Revolved; - -void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color, Group *group) +bool SShell::CheckNormalAxisRelationship(SBezierLoopSet *sbls, Vector pt, Vector axis, double da, double dx) +// Check that the direction of revolution/extrusion ends up parallel to the normal of +// the sketch, on the side of the axis where the sketch is. { SBezierLoop *sbl; - - int i0 = surface.n, i; - - // Normalize the axis direction so that the direction of revolution - // ends up parallel to the normal of the sketch, on the side of the - // axis where the sketch is. Vector pto; double md = VERY_NEGATIVE; for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { @@ -634,80 +620,151 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, // if we choose a point that lies on the axis, for example. // (And our surface will be self-intersecting if the sketch // spans the axis, so don't worry about that.) - for(i = 0; i <= sb->deg; i++) { + for(int i = 0; i <= sb->deg; i++) { Vector p = sb->ctrl[i]; double d = p.DistanceToLine(pt, axis); if(d > md) { - md = d; + md = d; pto = p; } } } } Vector ptc = pto.ClosestPointOnLine(pt, axis), - up = (pto.Minus(ptc)).WithMagnitude(1), - vp = (sbls->normal).Cross(up); - if(vp.Dot(axis) < 0) { - axis = axis.ScaledBy(-1); + up = axis.Cross(pto.Minus(ptc)).ScaledBy(da), + vp = up.Plus(axis.ScaledBy(dx)); + + return (vp.Dot(sbls->normal) > 0); +} + +// sketch must not contain the axis of revolution as a non-construction line for helix +void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, + RgbaColor color, Group *group, double angles, + double anglef, double dists, double distf) { + int i0 = surface.n; // number of pre-existing surfaces + SBezierLoop *sbl; + // for testing - hard code the axial distance, and number of sections. + // distance will need to be parameters in the future. + double dist = distf - dists; + int sections = fabs(anglef - angles) / (PI / 2) + 1; + double wedge = (anglef - angles) / sections; + + if(CheckNormalAxisRelationship(sbls, pt, axis, anglef-angles, distf-dists)) { + swap(angles, anglef); + swap(dists, distf); + dist = -dist; + wedge = -wedge; } - // Now we actually build and trim the surfaces. + // Define a coordinate system to contain the original sketch, and get + // a bounding box in that csys + Vector n = sbls->normal.ScaledBy(-1); + Vector u = n.Normal(0), v = n.Normal(1); + Vector orig = sbls->point; + double umax = 1e-10, umin = 1e10; + sbls->GetBoundingProjd(u, orig, &umin, &umax); + double vmax = 1e-10, vmin = 1e10; + sbls->GetBoundingProjd(v, orig, &vmin, &vmax); + // and now fix things up so that all u and v lie between 0 and 1 + orig = orig.Plus(u.ScaledBy(umin)); + orig = orig.Plus(v.ScaledBy(vmin)); + u = u.ScaledBy(umax - umin); + v = v.ScaledBy(vmax - vmin); + + // So we can now generate the end caps of the extrusion within + // a translated and rotated (and maybe mirrored) version of that csys. + SSurface s0, s1; + s0 = SSurface::FromPlane(orig.RotatedAbout(pt, axis, angles).Plus(axis.ScaledBy(dists)), + u.RotatedAbout(axis, angles), v.RotatedAbout(axis, angles)); + s0.color = color; + s1 = SSurface::FromPlane( + orig.Plus(u).RotatedAbout(pt, axis, anglef).Plus(axis.ScaledBy(distf)), + u.ScaledBy(-1).RotatedAbout(axis, anglef), v.RotatedAbout(axis, anglef)); + s1.color = color; + hSSurface hs0 = surface.AddAndAssignId(&s0), hs1 = surface.AddAndAssignId(&s1); + + // Now we actually build and trim the swept surfaces. One loop at a time. for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { int i, j; SBezier *sb; - List hsl = {}; + List> hsl = {}; + // This is where all the NURBS are created and Remapped to the generating curve for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { - Revolved revs; - for(j = 0; j < 4; j++) { - if(sb->deg == 1 && - (sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS && - (sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS) - { + std::vector revs(sections); + for(j = 0; j < sections; j++) { + if((dist == 0) && sb->deg == 1 && + (sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS && + (sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS) { // This is a line on the axis of revolution; it does // not contribute a surface. - revs.d[j].v = 0; + revs[j].v = 0; } else { - SSurface ss = SSurface::FromRevolutionOf(sb, pt, axis, - (PI/2)*j, - (PI/2)*(j+1)); + SSurface ss = SSurface::FromRevolutionOf( + sb, pt, axis, angles + (wedge)*j, angles + (wedge) * (j + 1), + dists + j * dist / sections, dists + (j + 1) * dist / sections); ss.color = color; if(sb->entity != 0) { hEntity he; - he.v = sb->entity; + he.v = sb->entity; hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE); if(SK.entity.FindByIdNoOops(hface) != NULL) { ss.face = hface.v; } } - revs.d[j] = surface.AddAndAssignId(&ss); + revs[j] = surface.AddAndAssignId(&ss); } } hsl.Add(&revs); } - + // Still the same loop. Need to create trim curves for(i = 0; i < sbl->l.n; i++) { - Revolved revs = hsl.elem[i], - revsp = hsl.elem[WRAP(i-1, sbl->l.n)]; + std::vector revs = hsl[i], revsp = hsl[WRAP(i - 1, sbl->l.n)]; - sb = &(sbl->l.elem[i]); + sb = &(sbl->l[i]); - for(j = 0; j < 4; j++) { + // we generate one more curve than we did surfaces + for(j = 0; j <= sections; j++) { SCurve sc; - Quaternion qs = Quaternion::From(axis, (PI/2)*j); + Quaternion qs = Quaternion::From(axis, angles + wedge * j); // we want Q*(x - p) + p = Q*x + (p - Q*p) - Vector ts = pt.Minus(qs.Rotate(pt)); + Vector ts = + pt.Minus(qs.Rotate(pt)).Plus(axis.ScaledBy(dists + j * dist / sections)); - // If this input curve generate a surface, then trim that + // If this input curve generated a surface, then trim that // surface with the rotated version of the input curve. - if(revs.d[j].v) { - sc = {}; + if(revs[0].v) { // not d[j] because crash on j==sections + sc = {}; sc.isExact = true; - sc.exact = sb->TransformedBy(ts, qs, 1.0); + sc.exact = sb->TransformedBy(ts, qs, 1.0); (sc.exact).MakePwlInto(&(sc.pts)); - sc.surfA = revs.d[j]; - sc.surfB = revs.d[WRAP(j-1, 4)]; + // the surfaces already exist so trim with this curve + if(j < sections) { + sc.surfA = revs[j]; + } else { + sc.surfA = hs1; // end cap + } + + if(j > 0) { + sc.surfB = revs[j - 1]; + } else { + sc.surfB = hs0; // staring cap + } + hSCurve hcb = curve.AddAndAssignId(&sc); + + STrimBy stb; + stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true); + (surface.FindById(sc.surfA))->trim.Add(&stb); + stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false); + (surface.FindById(sc.surfB))->trim.Add(&stb); + } else if(j == 0) { // curve was on the rotation axis and is shared by the end caps. + sc = {}; + sc.isExact = true; + sc.exact = sb->TransformedBy(ts, qs, 1.0); + (sc.exact).MakePwlInto(&(sc.pts)); + sc.surfA = hs1; // end cap + sc.surfB = hs0; // staring cap hSCurve hcb = curve.AddAndAssignId(&sc); STrimBy stb; @@ -719,19 +776,17 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, // And if this input curve and the one after it both generated // surfaces, then trim both of those by the appropriate - // circle. - if(revs.d[j].v && revsp.d[j].v) { - SSurface *ss = surface.FindById(revs.d[j]); + // curve based on the control points. + if((j < sections) && revs[j].v && revsp[j].v) { + SSurface *ss = surface.FindById(revs[j]); - sc = {}; + sc = {}; sc.isExact = true; - sc.exact = SBezier::From(ss->ctrl[0][0], - ss->ctrl[0][1], - ss->ctrl[0][2]); + sc.exact = SBezier::From(ss->ctrl[0][0], ss->ctrl[0][1], ss->ctrl[0][2]); sc.exact.weight[1] = ss->weight[0][1]; (sc.exact).MakePwlInto(&(sc.pts)); - sc.surfA = revs.d[j]; - sc.surfB = revsp.d[j]; + sc.surfA = revs[j]; + sc.surfB = revsp[j]; hSCurve hcc = curve.AddAndAssignId(&sc); @@ -747,8 +802,123 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, hsl.Clear(); } + if(dist == 0) { + MakeFirstOrderRevolvedSurfaces(pt, axis, i0); + } +} + +void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color, + Group *group) { + int i0 = surface.n; // number of pre-existing surfaces + SBezierLoop *sbl; + + if(CheckNormalAxisRelationship(sbls, pt, axis, 1.0, 0.0)) { + axis = axis.ScaledBy(-1); + } + + // Now we actually build and trim the surfaces. + for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { + int i, j; + SBezier *sb; + List> hsl = {}; + + for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { + std::vector revs(4); + for(j = 0; j < 4; j++) { + if(sb->deg == 1 && + (sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS && + (sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS) + { + // This is a line on the axis of revolution; it does + // not contribute a surface. + revs[j].v = 0; + } else { + SSurface ss = SSurface::FromRevolutionOf(sb, pt, axis, (PI / 2) * j, + (PI / 2) * (j + 1), 0.0, 0.0); + ss.color = color; + if(sb->entity != 0) { + hEntity he; + he.v = sb->entity; + hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE); + if(SK.entity.FindByIdNoOops(hface) != NULL) { + ss.face = hface.v; + } + } + revs[j] = surface.AddAndAssignId(&ss); + } + } + hsl.Add(&revs); + } + + for(i = 0; i < sbl->l.n; i++) { + std::vector revs = hsl[i], + revsp = hsl[WRAP(i-1, sbl->l.n)]; + + sb = &(sbl->l[i]); + + for(j = 0; j < 4; j++) { + SCurve sc; + Quaternion qs = Quaternion::From(axis, (PI/2)*j); + // we want Q*(x - p) + p = Q*x + (p - Q*p) + Vector ts = pt.Minus(qs.Rotate(pt)); + + // If this input curve generate a surface, then trim that + // surface with the rotated version of the input curve. + if(revs[j].v) { + sc = {}; + sc.isExact = true; + sc.exact = sb->TransformedBy(ts, qs, 1.0); + (sc.exact).MakePwlInto(&(sc.pts)); + sc.surfA = revs[j]; + sc.surfB = revs[WRAP(j-1, 4)]; + + hSCurve hcb = curve.AddAndAssignId(&sc); + + STrimBy stb; + stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true); + (surface.FindById(sc.surfA))->trim.Add(&stb); + stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false); + (surface.FindById(sc.surfB))->trim.Add(&stb); + } + + // And if this input curve and the one after it both generated + // surfaces, then trim both of those by the appropriate + // circle. + if(revs[j].v && revsp[j].v) { + SSurface *ss = surface.FindById(revs[j]); + + sc = {}; + sc.isExact = true; + sc.exact = SBezier::From(ss->ctrl[0][0], + ss->ctrl[0][1], + ss->ctrl[0][2]); + sc.exact.weight[1] = ss->weight[0][1]; + (sc.exact).MakePwlInto(&(sc.pts)); + sc.surfA = revs[j]; + sc.surfB = revsp[j]; + + hSCurve hcc = curve.AddAndAssignId(&sc); + + STrimBy stb; + stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false); + (surface.FindById(sc.surfA))->trim.Add(&stb); + stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true); + (surface.FindById(sc.surfB))->trim.Add(&stb); + } + } + } + + hsl.Clear(); + } + + MakeFirstOrderRevolvedSurfaces(pt, axis, i0); +} + +void SShell::MakeFirstOrderRevolvedSurfaces(Vector pt, Vector axis, int i0) { + int i; + for(i = i0; i < surface.n; i++) { - SSurface *srf = &(surface.elem[i]); + SSurface *srf = &(surface[i]); // Revolution of a line; this is potentially a plane, which we can // rewrite to have degree (1, 1). @@ -823,9 +993,7 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, continue; } } - } - } void SShell::MakeFromCopyOf(SShell *a) { @@ -880,7 +1048,7 @@ void SShell::TriangulateInto(SMesh *sm) { } bool SShell::IsEmpty() const { - return (surface.n == 0); + return surface.IsEmpty(); } void SShell::Clear() { @@ -896,4 +1064,3 @@ void SShell::Clear() { } curve.Clear(); } - diff --git a/src/srf/surface.h b/src/srf/surface.h index 26cd7ef4..99418c53 100644 --- a/src/srf/surface.h +++ b/src/srf/surface.h @@ -63,11 +63,17 @@ public: uint32_t v; }; +template<> +struct IsHandleOracle : std::true_type {}; + class hSCurve { public: uint32_t v; }; +template<> +struct IsHandleOracle : std::true_type {}; + // Stuff for rational polynomial curves, of degree one to three. These are // our inputs, and are also calculated for certain exact surface-surface // intersections. @@ -282,8 +288,8 @@ public: Point2d cached; static SSurface FromExtrusionOf(SBezier *spc, Vector t0, Vector t1); - static SSurface FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, - double thetas, double thetaf); + static SSurface FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, double thetas, + double thetaf, double dists, double distf); static SSurface FromPlane(Vector pt, Vector u, Vector v); static SSurface FromTransformationOf(SSurface *a, Vector t, Quaternion q, double scale, @@ -376,9 +382,12 @@ public: void MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, RgbaColor color); + bool CheckNormalAxisRelationship(SBezierLoopSet *sbls, Vector pt, Vector axis, double da, double dx); void MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color, Group *group); - + void MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color, + Group *group, double angles, double anglef, double dists, double distf); + void MakeFirstOrderRevolvedSurfaces(Vector pt, Vector axis, int i0); void MakeFromUnionOf(SShell *a, SShell *b); void MakeFromDifferenceOf(SShell *a, SShell *b); void MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type); diff --git a/src/srf/surfinter.cpp b/src/srf/surfinter.cpp index 91a91e8d..9f6c85cc 100644 --- a/src/srf/surfinter.cpp +++ b/src/srf/surfinter.cpp @@ -277,7 +277,7 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, int i; for(i = 0; i < lv.n - 1; i++) { - Vector pa = lv.elem[i], pb = lv.elem[i+1]; + Vector pa = lv[i], pb = lv[i+1]; pa = pa.Minus(axis.ScaledBy(pa.Dot(axis))); pb = pb.Minus(axis.ScaledBy(pb.Dot(axis))); pa = pa.Plus(axisc); @@ -326,12 +326,13 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, srfB->AllPointsIntersecting(se->a, se->b, &lsi, /*asSegment=*/true, /*trimmed=*/true, /*inclTangent=*/false); - if(lsi.n == 0) continue; + if(lsi.IsEmpty()) + continue; // Find the other surface that this curve trims. hSCurve hsc = { (uint32_t)se->auxA }; SCurve *sc = shA->curve.FindById(hsc); - hSSurface hother = (sc->surfA.v == srfA->h.v) ? + hSSurface hother = (sc->surfA == srfA->h) ? sc->surfB : sc->surfA; SSurface *other = shA->surface.FindById(hother); @@ -368,10 +369,10 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, sc.isExact = false; sc.source = SCurve::Source::INTERSECTION; - Vector start = spl.l.elem[0].p, - startv = spl.l.elem[0].auxv; + Vector start = spl.l[0].p, + startv = spl.l[0].auxv; spl.l.ClearTags(); - spl.l.elem[0].tag = 1; + spl.l[0].tag = 1; spl.l.RemoveTagged(); // Our chord tolerance is whatever the user specified diff --git a/src/srf/triangulate.cpp b/src/srf/triangulate.cpp index c5faa5e2..895c9409 100644 --- a/src/srf/triangulate.cpp +++ b/src/srf/triangulate.cpp @@ -36,7 +36,7 @@ void SPolygon::UvTriangulateInto(SMesh *m, SSurface *srf) { SContour merged = {}; top->tag = 1; top->CopyInto(&merged); - (merged.l.n)--; + merged.l.RemoveLast(1); // List all of the edges, for testing whether bridges work. SEdgeList el = {}; @@ -114,7 +114,7 @@ bool SContour::BridgeToContour(SContour *sc, // point. int sco = 0; for(i = 0; i < (sc->l.n - 1); i++) { - if((sc->l.elem[i].p).EqualsExactly(sc->xminPt)) { + if((sc->l[i].p).EqualsExactly(sc->xminPt)) { sco = i; } } @@ -124,7 +124,7 @@ bool SContour::BridgeToContour(SContour *sc, int thiso = 0; double dmin = 1e10; for(i = 0; i < l.n; i++) { - Vector p = l.elem[i].p; + Vector p = l[i].p; double d = (p.Minus(sc->xminPt)).MagSquared(); if(d < dmin) { dmin = d; @@ -140,7 +140,7 @@ bool SContour::BridgeToContour(SContour *sc, // merge them there, without a bridge. for(i = 0; i < l.n; i++) { thisp = WRAP(i+thiso, l.n); - a = l.elem[thisp].p; + a = l[thisp].p; for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) { if(f->Equals(a)) break; @@ -149,7 +149,7 @@ bool SContour::BridgeToContour(SContour *sc, for(j = 0; j < (sc->l.n - 1); j++) { scp = WRAP(j+sco, (sc->l.n - 1)); - b = sc->l.elem[scp].p; + b = sc->l[scp].p; if(a.Equals(b)) { goto haveEdge; @@ -160,7 +160,7 @@ bool SContour::BridgeToContour(SContour *sc, // If that fails, look for a bridge that does not intersect any edges. for(i = 0; i < l.n; i++) { thisp = WRAP(i+thiso, l.n); - a = l.elem[thisp].p; + a = l[thisp].p; for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) { if(f->Equals(a)) break; @@ -169,7 +169,7 @@ bool SContour::BridgeToContour(SContour *sc, for(j = 0; j < (sc->l.n - 1); j++) { scp = WRAP(j+sco, (sc->l.n - 1)); - b = sc->l.elem[scp].p; + b = sc->l[scp].p; for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) { if(f->Equals(b)) break; @@ -190,15 +190,15 @@ bool SContour::BridgeToContour(SContour *sc, haveEdge: SContour merged = {}; for(i = 0; i < l.n; i++) { - merged.AddPoint(l.elem[i].p); + merged.AddPoint(l[i].p); if(i == thisp) { // less than or equal; need to duplicate the join point for(j = 0; j <= (sc->l.n - 1); j++) { int jp = WRAP(j + scp, (sc->l.n - 1)); - merged.AddPoint((sc->l.elem[jp]).p); + merged.AddPoint((sc->l[jp]).p); } // and likewise duplicate join point for the outer curve - merged.AddPoint(l.elem[i].p); + merged.AddPoint(l[i].p); } } @@ -218,9 +218,9 @@ bool SContour::IsEar(int bp, double scaledEps) const { cp = WRAP(bp+1, l.n); STriangle tr = {}; - tr.a = l.elem[ap].p; - tr.b = l.elem[bp].p; - tr.c = l.elem[cp].p; + tr.a = l[ap].p; + tr.b = l[bp].p; + tr.c = l[cp].p; if((tr.a).Equals(tr.c)) { // This is two coincident and anti-parallel edges. Zero-area, so @@ -244,7 +244,7 @@ bool SContour::IsEar(int bp, double scaledEps) const { for(i = 0; i < l.n; i++) { if(i == ap || i == bp || i == cp) continue; - Vector p = l.elem[i].p; + Vector p = l[i].p; if(p.OutsideAndNotOn(maxv, minv)) continue; // A point on the edge of the triangle is considered to be inside, @@ -266,9 +266,9 @@ void SContour::ClipEarInto(SMesh *m, int bp, double scaledEps) { cp = WRAP(bp+1, l.n); STriangle tr = {}; - tr.a = l.elem[ap].p; - tr.b = l.elem[bp].p; - tr.c = l.elem[cp].p; + tr.a = l[ap].p; + tr.b = l[bp].p; + tr.c = l[cp].p; if(tr.Normal().MagSquared() < scaledEps*scaledEps) { // A vertex with more than two edges will cause us to generate // zero-area triangles, which must be culled. @@ -278,11 +278,11 @@ void SContour::ClipEarInto(SMesh *m, int bp, double scaledEps) { // By deleting the point at bp, we may change the ear-ness of the points // on either side. - l.elem[ap].ear = EarType::UNKNOWN; - l.elem[cp].ear = EarType::UNKNOWN; + l[ap].ear = EarType::UNKNOWN; + l[cp].ear = EarType::UNKNOWN; l.ClearTags(); - l.elem[bp].tag = 1; + l[bp].tag = 1; l.RemoveTagged(); } @@ -299,23 +299,23 @@ void SContour::UvTriangulateInto(SMesh *m, SSurface *srf) { // Clean the original contour by removing any zero-length edges. l.ClearTags(); for(i = 1; i < l.n; i++) { - if((l.elem[i].p).Equals(l.elem[i-1].p)) { - l.elem[i].tag = 1; + if((l[i].p).Equals(l[i-1].p)) { + l[i].tag = 1; } } l.RemoveTagged(); // Now calculate the ear-ness of each vertex for(i = 0; i < l.n; i++) { - (l.elem[i]).ear = IsEar(i, scaledEps) ? EarType::EAR : EarType::NOT_EAR; + (l[i]).ear = IsEar(i, scaledEps) ? EarType::EAR : EarType::NOT_EAR; } bool toggle = false; while(l.n > 3) { // Some points may have changed ear-ness, so recalculate for(i = 0; i < l.n; i++) { - if(l.elem[i].ear == EarType::UNKNOWN) { - (l.elem[i]).ear = IsEar(i, scaledEps) ? + if(l[i].ear == EarType::UNKNOWN) { + (l[i]).ear = IsEar(i, scaledEps) ? EarType::EAR : EarType::NOT_EAR; } } @@ -328,7 +328,7 @@ void SContour::UvTriangulateInto(SMesh *m, SSurface *srf) { int offset = toggle ? -1 : 0; for(i = 0; i < l.n; i++) { int ear = WRAP(i+offset, l.n); - if(l.elem[ear].ear == EarType::EAR) { + if(l[ear].ear == EarType::EAR) { if(srf->degm == 1 && srf->degn == 1) { // This is a plane; any ear is a good ear. bestEar = ear; @@ -337,8 +337,8 @@ void SContour::UvTriangulateInto(SMesh *m, SSurface *srf) { // If we are triangulating a curved surface, then try to // clip ears that have a small chord tolerance from the // surface. - Vector prev = l.elem[WRAP((i+offset-1), l.n)].p, - next = l.elem[WRAP((i+offset+1), l.n)].p; + Vector prev = l[WRAP((i+offset-1), l.n)].p, + next = l[WRAP((i+offset+1), l.n)].p; double tol = srf->ChordToleranceForEdge(prev, next); if(tol < bestChordTol - scaledEps) { bestEar = ear; @@ -444,8 +444,8 @@ void SPolygon::UvGridTriangulateInto(SMesh *mesh, SSurface *srf) { int i, j; for(i = 0; i < (li.n - 1); i++) { for(j = 0; j < (lj.n - 1); j++) { - double us = li.elem[i], uf = li.elem[i+1], - vs = lj.elem[j], vf = lj.elem[j+1]; + double us = li[i], uf = li[i+1], + vs = lj[j], vf = lj[j+1]; Vector a = Vector::From(us, vs, 0), b = Vector::From(us, vf, 0), diff --git a/src/style.cpp b/src/style.cpp index c3fcff58..a60740a4 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -59,7 +59,7 @@ void Style::CreateDefaultStyle(hStyle h) { bool isDefaultStyle = true; const Default *d; for(d = &(Defaults[0]); d->h.v; d++) { - if(d->h.v == h.v) break; + if(d->h == h) break; } if(!d->h.v) { // Not a default style; so just create it the same as our default @@ -337,7 +337,7 @@ hStyle Style::ForEntity(hEntity he) { // Otherwise, we use the default rules. hStyle hs; - if(e->group.v != SS.GW.activeGroup.v) { + if(e->group != SS.GW.activeGroup) { hs.v = INACTIVE_GRP; } else if(e->construction) { hs.v = CONSTRUCTION; diff --git a/src/system.cpp b/src/system.cpp index 3d1a947a..08752600 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -20,28 +20,29 @@ const double System::RANK_MAG_TOLERANCE = 1e-4; const double System::CONVERGE_TOLERANCE = (LENGTH_EPS/(1e2)); bool System::WriteJacobian(int tag) { - int a, i, j; - j = 0; - for(a = 0; a < param.n; a++) { - if(j >= MAX_UNKNOWNS) return false; + int j = 0; + for(auto &p : param) { + if(j >= MAX_UNKNOWNS) + return false; - Param *p = &(param.elem[a]); - if(p->tag != tag) continue; - mat.param[j] = p->h; + if(p.tag != tag) + continue; + mat.param[j] = p.h; j++; } mat.n = j; - i = 0; - for(a = 0; a < eq.n; a++) { + int i = 0; + + for(auto &e : eq) { if(i >= MAX_UNKNOWNS) return false; - Equation *e = &(eq.elem[a]); - if(e->tag != tag) continue; + if(e.tag != tag) + continue; - mat.eq[i] = e->h; - Expr *f = e->e->DeepCopyWithParamsAsPointers(¶m, &(SK.param)); + mat.eq[i] = e.h; + Expr *f = e.e->DeepCopyWithParamsAsPointers(¶m, &(SK.param)); f = f->FoldConstants(); // Hash table (61 bits) to accelerate generation of zero partials. @@ -79,16 +80,14 @@ void System::EvalJacobian() { bool System::IsDragged(hParam p) { hParam *pp; for(pp = dragged.First(); pp; pp = dragged.NextAfter(pp)) { - if(p.v == pp->v) return true; + if(p == *pp) return true; } return false; } void System::SolveBySubstitution() { - int i; - for(i = 0; i < eq.n; i++) { - Equation *teq = &(eq.elem[i]); - Expr *tex = teq->e; + for(auto &teq : eq) { + Expr *tex = teq.e; if(tex->op == Expr::Op::MINUS && tex->a->op == Expr::Op::PARAM && @@ -108,22 +107,19 @@ void System::SolveBySubstitution() { std::swap(a, b); } - int j; - for(j = 0; j < eq.n; j++) { - Equation *req = &(eq.elem[j]); - (req->e)->Substitute(a, b); // A becomes B, B unchanged + for(auto &req : eq) { + req.e->Substitute(a, b); // A becomes B, B unchanged } - for(j = 0; j < param.n; j++) { - Param *rp = &(param.elem[j]); - if(rp->substd.v == a.v) { - rp->substd = b; + for(auto &rp : param) { + if(rp.substd == a) { + rp.substd = b; } } Param *ptr = param.FindById(a); ptr->tag = VAR_SUBSTITUTED; ptr->substd = b; - teq->tag = EQ_SUBSTITUTED; + teq.tag = EQ_SUBSTITUTED; } } } @@ -324,12 +320,11 @@ bool System::NewtonSolve(int tag) { } void System::WriteEquationsExceptFor(hConstraint hc, Group *g) { - int i; // Generate all the equations from constraints in this group - for(i = 0; i < SK.constraint.n; i++) { - ConstraintBase *c = &(SK.constraint.elem[i]); - if(c->group.v != g->h.v) continue; - if(c->h.v == hc.v) continue; + for(auto &con : SK.constraint) { + ConstraintBase *c = &con; + if(c->group != g->h) continue; + if(c->h == hc) continue; if(c->HasLabel() && c->type != Constraint::Type::COMMENT && g->allDimsReference) @@ -349,9 +344,9 @@ void System::WriteEquationsExceptFor(hConstraint hc, Group *g) { c->GenerateEquations(&eq); } // And the equations from entities - for(i = 0; i < SK.entity.n; i++) { - EntityBase *e = &(SK.entity.elem[i]); - if(e->group.v != g->h.v) continue; + for(auto &ent : SK.entity) { + EntityBase *e = &ent; + if(e->group != g->h) continue; e->GenerateEquations(&eq); } @@ -360,12 +355,12 @@ void System::WriteEquationsExceptFor(hConstraint hc, Group *g) { } void System::FindWhichToRemoveToFixJacobian(Group *g, List *bad, bool forceDofCheck) { - int a, i; + int a; for(a = 0; a < 2; a++) { - for(i = 0; i < SK.constraint.n; i++) { - ConstraintBase *c = &(SK.constraint.elem[i]); - if(c->group.v != g->h.v) continue; + for(auto &con : SK.constraint) { + ConstraintBase *c = &con; + if(c->group != g->h) continue; if((c->type == Constraint::Type::POINTS_COINCIDENT && a == 0) || (c->type != Constraint::Type::POINTS_COINCIDENT && a == 1)) { @@ -409,11 +404,11 @@ SolveResult System::Solve(Group *g, int *rank, int *dof, List *bad, /* dbp("%d equations", eq.n); for(i = 0; i < eq.n; i++) { - dbp(" %.3f = %s = 0", eq.elem[i].e->Eval(), eq.elem[i].e->Print()); + dbp(" %.3f = %s = 0", eq[i].e->Eval(), eq[i].e->Print()); } dbp("%d parameters", param.n); for(i = 0; i < param.n; i++) { - dbp(" param %08x at %.3f", param.elem[i].h.v, param.elem[i].val); + dbp(" param %08x at %.3f", param[i].h.v, param[i].val); } */ // All params and equations are assigned to group zero. @@ -431,18 +426,18 @@ SolveResult System::Solve(Group *g, int *rank, int *dof, List *bad, // the system is consistent yet, but if it isn't then we'll catch that // later. int alone = 1; - for(i = 0; i < eq.n; i++) { - Equation *e = &(eq.elem[i]); - if(e->tag != 0) continue; + for(auto &e : eq) { + if(e.tag != 0) + continue; - hParam hp = e->e->ReferencedParams(¶m); - if(hp.v == Expr::NO_PARAMS.v) continue; - if(hp.v == Expr::MULTIPLE_PARAMS.v) continue; + hParam hp = e.e->ReferencedParams(¶m); + if(hp == Expr::NO_PARAMS) continue; + if(hp == Expr::MULTIPLE_PARAMS) continue; Param *p = param.FindById(hp); if(p->tag != 0) continue; // let rank test catch inconsistency - e->tag = alone; + e.tag = alone; p->tag = alone; WriteJacobian(alone); if(!NewtonSolve(alone)) { @@ -480,23 +475,23 @@ SolveResult System::Solve(Group *g, int *rank, int *dof, List *bad, } // System solved correctly, so write the new values back in to the // main parameter table. - for(i = 0; i < param.n; i++) { - Param *p = &(param.elem[i]); + for(auto &p : param) { double val; - if(p->tag == VAR_SUBSTITUTED) { - val = param.FindById(p->substd)->val; + if(p.tag == VAR_SUBSTITUTED) { + val = param.FindById(p.substd)->val; } else { - val = p->val; + val = p.val; } - Param *pp = SK.GetParam(p->h); + Param *pp = SK.GetParam(p.h); pp->val = val; pp->known = true; - pp->free = p->free; + pp->free = p.free; } return rankOk ? SolveResult::OKAY : SolveResult::REDUNDANT_OKAY; didnt_converge: SK.constraint.ClearTags(); + // Not using range-for here because index is used in additional ways for(i = 0; i < eq.n; i++) { if(ffabs(mat.B.num[i]) > CONVERGE_TOLERANCE || isnan(mat.B.num[i])) { // This constraint is unsatisfied. @@ -553,20 +548,19 @@ void System::MarkParamsFree(bool find) { // If requested, find all the free (unbound) variables. This might be // more than the number of degrees of freedom. Don't always do this, // because the display would get annoying and it's slow. - for(int i = 0; i < param.n; i++) { - Param *p = &(param.elem[i]); - p->free = false; + for(auto &p : param) { + p.free = false; if(find) { - if(p->tag == 0) { - p->tag = VAR_DOF_TEST; + if(p.tag == 0) { + p.tag = VAR_DOF_TEST; WriteJacobian(0); EvalJacobian(); int rank = CalculateRank(); if(rank == mat.m) { - p->free = true; + p.free = true; } - p->tag = 0; + p.tag = 0; } } } diff --git a/src/textscreens.cpp b/src/textscreens.cpp index b7e6ca27..7e942393 100644 --- a/src/textscreens.cpp +++ b/src/textscreens.cpp @@ -57,8 +57,9 @@ void TextWindow::ScreenToggleGroupShown(int link, uint32_t v) { } void TextWindow::ScreenShowGroupsSpecial(int link, uint32_t v) { bool state = link == 's'; - 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->visible = state; } SS.GW.persistentDirty = true; @@ -71,7 +72,7 @@ void TextWindow::ScreenActivateGroup(int link, uint32_t v) { void TextWindow::ReportHowGroupSolved(hGroup hg) { SS.GW.ClearSuper(); SS.TW.GoToScreen(Screen::GROUP_SOLVE_INFO); - SS.TW.shown.group.v = hg.v; + SS.TW.shown.group = hg; SS.ScheduleShowTW(); } void TextWindow::ScreenHowGroupSolved(int link, uint32_t v) { @@ -98,12 +99,13 @@ void TextWindow::ShowListOfGroups() { Printf(true, "%Ft active"); Printf(false, "%Ft shown dof group-name%E"); - int i; bool afterActive = false; - for(i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); + bool backgroundParity = false; + for(hGroup hg : SK.groupOrder) { + Group *g = SK.GetGroup(hg); + std::string s = g->DescriptionString(); - bool active = (g->h.v == SS.GW.activeGroup.v); + bool active = (g->h == SS.GW.activeGroup); bool shown = g->visible; bool ok = g->IsSolvedOkay(); bool warn = (g->type == Group::Type::DRAWING_WORKPLANE && @@ -120,31 +122,33 @@ void TextWindow::ShowListOfGroups() { sprintf(sdof, "%-3d", dof); } } - bool ref = (g->h.v == Group::HGROUP_REFERENCES.v); - Printf(false, "%Bp%Fd " + bool ref = (g->h == Group::HGROUP_REFERENCES); + Printf(false, + "%Bp%Fd " "%Ft%s%Fb%D%f%Ll%s%E " "%Fb%s%D%f%Ll%s%E " "%Fp%D%f%s%Ll%s%E " "%Fl%Ll%D%f%s", - // Alternate between light and dark backgrounds, for readability - (i & 1) ? 'd' : 'a', - // Link that activates the group - ref ? " " : "", - g->h.v, (&TextWindow::ScreenActivateGroup), - ref ? "" : (active ? radioTrue : radioFalse), - // Link that hides or shows the group - afterActive ? " - " : "", - g->h.v, (&TextWindow::ScreenToggleGroupShown), - afterActive ? "" : (shown ? checkTrue : checkFalse), - // Link to the errors, if a problem occurred while solving - ok ? (warn ? 'm' : (dof > 0 ? 'i' : 's')) : 'x', - g->h.v, (&TextWindow::ScreenHowGroupSolved), - ok ? (warn ? "err" : sdof) : "", - ok ? "" : "ERR", - // Link to a screen that gives more details on the group - g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str()); + // Alternate between light and dark backgrounds, for readability + backgroundParity ? 'd' : 'a', + // Link that activates the group + ref ? " " : "", + g->h.v, (&TextWindow::ScreenActivateGroup), + ref ? "" : (active ? radioTrue : radioFalse), + // Link that hides or shows the group + afterActive ? " - " : "", + g->h.v, (&TextWindow::ScreenToggleGroupShown), + afterActive ? "" : (shown ? checkTrue : checkFalse), + // Link to the errors, if a problem occurred while solving + ok ? (warn ? 'm' : (dof > 0 ? 'i' : 's')) : 'x', + g->h.v, (&TextWindow::ScreenHowGroupSolved), + ok ? (warn ? "err" : sdof) : "", + ok ? "" : "ERR", + // Link to a screen that gives more details on the group + g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str()); if(active) afterActive = true; + backgroundParity = !backgroundParity; } Printf(true, " %Fl%Ls%fshow all%E / %Fl%Lh%fhide all%E", @@ -245,7 +249,7 @@ void TextWindow::ScreenOpacity(int link, uint32_t v) { SS.TW.ShowEditControl(11, ssprintf("%.2f", g->color.alphaF())); SS.TW.edit.meaning = Edit::GROUP_OPACITY; - SS.TW.edit.group.v = g->h.v; + SS.TW.edit.group = g->h; } void TextWindow::ScreenChangeExprA(int link, uint32_t v) { Group *g = SK.GetGroup(SS.TW.shown.group); @@ -271,7 +275,7 @@ void TextWindow::ScreenDeleteGroup(int link, uint32_t v) { SS.UndoRemember(); hGroup hg = SS.TW.shown.group; - if(hg.v == SS.GW.activeGroup.v) { + if(hg == SS.GW.activeGroup) { SS.GW.activeGroup = SK.GetGroup(SS.GW.activeGroup)->PreviousGroup()->h; } @@ -291,7 +295,7 @@ void TextWindow::ShowGroupInfo() { Group *g = SK.GetGroup(shown.group); const char *s = "???"; - if(shown.group.v == Group::HGROUP_REFERENCES.v) { + if(shown.group == Group::HGROUP_REFERENCES) { Printf(true, "%FtGROUP %E%s", g->DescriptionString().c_str()); goto list_items; } else { @@ -304,14 +308,18 @@ void TextWindow::ShowGroupInfo() { if(g->type == Group::Type::LATHE) { Printf(true, " %Ftlathe plane sketch"); } else if(g->type == Group::Type::EXTRUDE || g->type == Group::Type::ROTATE || - g->type == Group::Type::TRANSLATE) - { + g->type == Group::Type::TRANSLATE || g->type == Group::Type::REVOLVE || + g->type == Group::Type::HELIX) { if(g->type == Group::Type::EXTRUDE) { s = "extrude plane sketch"; } else if(g->type == Group::Type::TRANSLATE) { s = "translate original sketch"; + } else if(g->type == Group::Type::HELIX) { + s = "create helical extrusion"; } else if(g->type == Group::Type::ROTATE) { s = "rotate original sketch"; + } else if(g->type == Group::Type::REVOLVE) { + s = "revolve original sketch"; } Printf(true, " %Ft%s%E", s); @@ -362,10 +370,9 @@ void TextWindow::ShowGroupInfo() { } Printf(false, ""); - if(g->type == Group::Type::EXTRUDE || - g->type == Group::Type::LATHE || - g->type == Group::Type::LINKED) - { + if(g->type == Group::Type::EXTRUDE || g->type == Group::Type::LATHE || + g->type == Group::Type::REVOLVE || g->type == Group::Type::LINKED || + g->type == Group::Type::HELIX) { bool un = (g->meshCombine == Group::CombineAs::UNION); bool diff = (g->meshCombine == Group::CombineAs::DIFFERENCE); bool asy = (g->meshCombine == Group::CombineAs::ASSEMBLE); @@ -384,9 +391,8 @@ void TextWindow::ShowGroupInfo() { Group::CombineAs::ASSEMBLE, (asy ? RADIO_TRUE : RADIO_FALSE)); - if(g->type == Group::Type::EXTRUDE || - g->type == Group::Type::LATHE) - { + if(g->type == Group::Type::EXTRUDE || g->type == Group::Type::LATHE || + g->type == Group::Type::REVOLVE || g->type == Group::Type::HELIX) { Printf(false, "%Bd %Ftcolor %E%Bz %Bd (%@, %@, %@) %f%D%Lf%Fl[change]%E", &g->color, @@ -397,9 +403,9 @@ void TextWindow::ShowGroupInfo() { &TextWindow::ScreenOpacity); } - if(g->type == Group::Type::EXTRUDE || - g->type == Group::Type::LATHE || - g->type == Group::Type::LINKED) { + if(g->type == Group::Type::EXTRUDE || g->type == Group::Type::LATHE || + g->type == Group::Type::REVOLVE || g->type == Group::Type::LINKED || + g->type == Group::Type::HELIX) { Printf(false, " %Fd%f%LP%s suppress this group's solid model", &TextWindow::ScreenChangeGroupOption, g->suppress ? CHECK_TRUE : CHECK_FALSE); @@ -443,16 +449,17 @@ list_items: Printf(false, ""); Printf(false, "%Ft requests in group"); - int i, a = 0; - for(i = 0; i < SK.request.n; i++) { - Request *r = &(SK.request.elem[i]); + int a = 0; + for(auto &r : SK.request) { - if(r->group.v == shown.group.v) { - std::string s = r->DescriptionString(); + if(r.group == shown.group) { + std::string s = r.DescriptionString(); Printf(false, "%Bp %Fl%Ll%D%f%h%s%E", - (a & 1) ? 'd' : 'a', - r->h.v, (&TextWindow::ScreenSelectRequest), - &(TextWindow::ScreenHoverRequest), s.c_str()); + (a & 1) ? 'd' : 'a', + r.h.v, + (&TextWindow::ScreenSelectRequest), + &(TextWindow::ScreenHoverRequest), + s.c_str()); a++; } } @@ -461,16 +468,17 @@ list_items: a = 0; Printf(false, ""); Printf(false, "%Ft constraints in group (%d DOF)", g->solved.dof); - for(i = 0; i < SK.constraint.n; i++) { - Constraint *c = &(SK.constraint.elem[i]); + for(auto &c : SK.constraint) { - if(c->group.v == shown.group.v) { - std::string s = c->DescriptionString(); + if(c.group == shown.group) { + std::string s = c.DescriptionString(); Printf(false, "%Bp %Fl%Ll%D%f%h%s%E %s", - (a & 1) ? 'd' : 'a', - c->h.v, (&TextWindow::ScreenSelectConstraint), - (&TextWindow::ScreenHoverConstraint), s.c_str(), - c->reference ? "(ref)" : ""); + (a & 1) ? 'd' : 'a', + c.h.v, + (&TextWindow::ScreenSelectConstraint), + (&TextWindow::ScreenHoverConstraint), + s.c_str(), + c.reference ? "(ref)" : ""); a++; } } @@ -526,7 +534,7 @@ void TextWindow::ShowGroupSolveInfo() { } for(int i = 0; i < g->solved.remove.n; i++) { - hConstraint hc = g->solved.remove.elem[i]; + hConstraint hc = g->solved.remove[i]; Constraint *c = SK.constraint.FindByIdNoOops(hc); if(!c) continue; @@ -750,7 +758,7 @@ void TextWindow::EditControlDone(std::string s) { Group *g = SK.group.FindByIdNoOops(SS.TW.shown.group); if(!g) break; - g->color = RGBf(rgb.x, rgb.y, rgb.z); + g->color = RgbaColor::FromFloat(rgb.x, rgb.y, rgb.z, g->color.alphaF()); SS.MarkGroupDirty(g->h); SS.GW.ClearSuper(); diff --git a/src/textwin.cpp b/src/textwin.cpp index 15b10eba..1406a3e7 100644 --- a/src/textwin.cpp +++ b/src/textwin.cpp @@ -195,8 +195,8 @@ static Button *buttons[] = { /** Foreground color codes. */ const TextWindow::Color TextWindow::fgColors[] = { { 'd', RGBi(255, 255, 255) }, // Default : white - { 'l', RGBi(100, 100, 255) }, - { 't', RGBi(255, 200, 0) }, + { 'l', RGBi(100, 200, 255) }, // links : blue + { 't', RGBi(255, 200, 100) }, // tree/text : yellow { 'h', RGBi( 90, 90, 90) }, { 's', RGBi( 40, 255, 40) }, // Ok : green { 'm', RGBi(200, 200, 0) }, diff --git a/src/toolbar.cpp b/src/toolbar.cpp index 4ca478c2..b1718204 100644 --- a/src/toolbar.cpp +++ b/src/toolbar.cpp @@ -151,6 +151,7 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, UiCanvas *canvas, int x = 17, y = (int)(height - 52); + // When changing these values, also change the asReference drawing code in drawentity.cpp. int fudge = 8; int h = 34*16 + 3*16 + fudge; int aleft = 0, aright = 66, atop = y+16+fudge/2, abot = y+16-h; @@ -174,7 +175,7 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, UiCanvas *canvas, bool leftpos = true; for(ToolIcon &icon : Toolbar) { - if(icon.name == "") { // spacer + if(icon.name.empty()) { // spacer if(!leftpos) { leftpos = true; y -= 32; diff --git a/src/ttf.cpp b/src/ttf.cpp index 3dc44c24..ebf7cc56 100644 --- a/src/ttf.cpp +++ b/src/ttf.cpp @@ -72,17 +72,17 @@ void TtfFontList::LoadAll() { } // Sort fonts according to their actual name, not filename. - std::sort(&l.elem[0], &l.elem[l.n], + std::sort(l.begin(), l.end(), [](const TtfFont &a, const TtfFont &b) { return a.name < b.name; }); // Filter out fonts with the same family and style name. This is not // strictly necessarily the exact same font, but it will almost always be. - TtfFont *it = std::unique(&l.elem[0], &l.elem[l.n], - [](const TtfFont &a, const TtfFont &b) { return a.name == b.name; }); - l.RemoveLast(&l.elem[l.n] - it); + TtfFont *it = std::unique(l.begin(), l.end(), + [](const TtfFont &a, const TtfFont &b) { return a.name == b.name; }); + l.RemoveLast(&l[l.n] - it); - // TODO: identify fonts by their name and not filename, which may change - // between OSes. + //! @todo identify fonts by their name and not filename, which may change + //! between OSes. loaded = true; } diff --git a/src/ui.h b/src/ui.h index 408d6534..284e9d60 100644 --- a/src/ui.h +++ b/src/ui.h @@ -125,7 +125,9 @@ enum class Command : uint32_t { GROUP_3D, GROUP_WRKPL, GROUP_EXTRUDE, + GROUP_HELIX, GROUP_LATHE, + GROUP_REVOLVE, GROUP_ROT, GROUP_TRANS, GROUP_LINK, diff --git a/src/undoredo.cpp b/src/undoredo.cpp index 05473306..8ea8a067 100644 --- a/src/undoredo.cpp +++ b/src/undoredo.cpp @@ -36,8 +36,6 @@ void SolveSpaceUI::UndoEnableMenus() { } void SolveSpaceUI::PushFromCurrentOnto(UndoStack *uk) { - int i; - if(uk->cnt == MAX_UNDO) { UndoClearState(&(uk->d[uk->write])); // And then write in to this one again @@ -48,9 +46,9 @@ void SolveSpaceUI::PushFromCurrentOnto(UndoStack *uk) { UndoState *ut = &(uk->d[uk->write]); *ut = {}; ut->group.ReserveMore(SK.group.n); - for(i = 0; i < SK.group.n; i++) { - Group *src = &(SK.group.elem[i]); - Group dest = *src; + for(Group &src : SK.group) { + // Shallow copy + Group dest(src); // And then clean up all the stuff that needs to be a deep copy, // and zero out all the dynamic stuff that will get regenerated. dest.clean = false; @@ -66,42 +64,32 @@ void SolveSpaceUI::PushFromCurrentOnto(UndoStack *uk) { dest.displayMesh = {}; dest.displayOutlines = {}; - dest.remap = src->remap; + dest.remap = src.remap; dest.impMesh = {}; dest.impShell = {}; dest.impEntity = {}; ut->group.Add(&dest); } - for(i = 0; i < SK.groupOrder.n; i++) { - ut->groupOrder.Add(&(SK.groupOrder.elem[i])); - } + for(auto &src : SK.groupOrder) { ut->groupOrder.Add(&src); } ut->request.ReserveMore(SK.request.n); - for(i = 0; i < SK.request.n; i++) { - ut->request.Add(&(SK.request.elem[i])); - } + for(auto &src : SK.request) { ut->request.Add(&src); } ut->constraint.ReserveMore(SK.constraint.n); - for(i = 0; i < SK.constraint.n; i++) { - Constraint *src = &(SK.constraint.elem[i]); - Constraint dest = *src; + for(auto &src : SK.constraint) { + // Shallow copy + Constraint dest(src); ut->constraint.Add(&dest); } ut->param.ReserveMore(SK.param.n); - for(i = 0; i < SK.param.n; i++) { - ut->param.Add(&(SK.param.elem[i])); - } + for(auto &src : SK.param) { ut->param.Add(&src); } ut->style.ReserveMore(SK.style.n); - for(i = 0; i < SK.style.n; i++) { - ut->style.Add(&(SK.style.elem[i])); - } + for(auto &src : SK.style) { ut->style.Add(&src); } ut->activeGroup = SS.GW.activeGroup; uk->write = WRAP(uk->write + 1, MAX_UNDO); } void SolveSpaceUI::PopOntoCurrentFrom(UndoStack *uk) { - int i; - ssassert(uk->cnt > 0, "Cannot pop from empty undo stack"); (uk->cnt)--; uk->write = WRAP(uk->write - 1, MAX_UNDO); @@ -109,8 +97,8 @@ void SolveSpaceUI::PopOntoCurrentFrom(UndoStack *uk) { UndoState *ut = &(uk->d[uk->write]); // Free everything in the main copy of the program before replacing it - for(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(); } SK.group.Clear(); @@ -122,8 +110,7 @@ void SolveSpaceUI::PopOntoCurrentFrom(UndoStack *uk) { // And then do a shallow copy of the state from the undo list ut->group.MoveSelfInto(&(SK.group)); - for(i = 0; i < ut->groupOrder.n; i++) - SK.groupOrder.Add(&ut->groupOrder.elem[i]); + for(auto &gh : ut->groupOrder) { SK.groupOrder.Add(&gh); } ut->request.MoveSelfInto(&(SK.request)); ut->constraint.MoveSelfInto(&(SK.constraint)); ut->param.MoveSelfInto(&(SK.param)); @@ -156,12 +143,7 @@ void SolveSpaceUI::UndoClearStack(UndoStack *uk) { } void SolveSpaceUI::UndoClearState(UndoState *ut) { - int i; - for(i = 0; i < ut->group.n; i++) { - Group *g = &(ut->group.elem[i]); - - g->remap.clear(); - } + for(auto &g : ut->group) { g.remap.clear(); } ut->group.Clear(); ut->request.Clear(); ut->constraint.Clear(); diff --git a/src/util.cpp b/src/util.cpp index 36fd3ffe..6840257f 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -143,7 +143,17 @@ static void MessageBox(const char *fmt, va_list va, bool error, description = description.substr(it - description.begin()); Platform::MessageDialogRef dialog = CreateMessageDialog(SS.GW.window); - + if (!dialog) { + if (error) { + fprintf(stderr, "Error: %s\n", message.c_str()); + } else { + fprintf(stderr, "Message: %s\n", message.c_str()); + } + if(onDismiss) { + onDismiss(); + } + return; + } using Platform::MessageDialog; if(error) { dialog->SetType(MessageDialog::Type::ERROR); @@ -597,7 +607,7 @@ bool Vector::OnLineSegment(Vector a, Vector b, double tol) const { if(distsq >= tol*tol) return false; - double t = (this->Minus(a)).DivPivoting(d); + double t = (this->Minus(a)).DivProjected(d); // On-endpoint already tested if(t < 0 || t > 1) return false; return true; @@ -686,16 +696,9 @@ Vector4 Vector::Project4d() const { return Vector4::From(1, x, y, z); } -double Vector::DivPivoting(Vector delta) const { - double mx = fabs(delta.x), my = fabs(delta.y), mz = fabs(delta.z); - - if(mx > my && mx > mz) { - return x/delta.x; - } else if(my > mz) { - return y/delta.y; - } else { - return z/delta.z; - } +double Vector::DivProjected(Vector delta) const { + return (x*delta.x + y*delta.y + z*delta.z) + / (delta.x*delta.x + delta.y*delta.y + delta.z*delta.z); } Vector Vector::ClosestOrtho() const { @@ -985,12 +988,8 @@ Point2d Point2d::ScaledBy(double s) const { return { x * s, y * s }; } -double Point2d::DivPivoting(Point2d delta) const { - if(fabs(delta.x) > fabs(delta.y)) { - return x/delta.x; - } else { - return y/delta.y; - } +double Point2d::DivProjected(Point2d delta) const { + return (x*delta.x + y*delta.y) / (delta.x*delta.x + delta.y*delta.y); } double Point2d::MagSquared() const { diff --git a/test/harness.cpp b/test/harness.cpp index ced5d6ae..1927ca2a 100644 --- a/test/harness.cpp +++ b/test/harness.cpp @@ -363,6 +363,7 @@ int main(int argc, char **argv) { } SS.Init(); + SS.showToolbar = false; SS.checkClosedContour = false; Test::Helper helper = {};