Merge branch 'master' into python
commit
813217bf00
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit fe71514837af1c627e9e1054d1aa0193a06d3488
|
||||
Subproject commit 880db1d34706778f216a2308fd82a9a3adacb314
|
|
@ -0,0 +1,3 @@
|
|||
*.snap
|
||||
solvespace-snap-src
|
||||
squashfs-root
|
|
@ -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 "$@"
|
|
@ -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
|
|
@ -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
|
||||
|
@ -157,6 +180,13 @@ else()
|
|||
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
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}/apps
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path d="m116 152h84v12h-84z" fill="#cbcbcb"/>
|
||||
<path d="m152 80h12v74h-12z" fill="#cbcbcb"/>
|
||||
<path d="m104 140h84v12h-84z" fill="#e40cf2"/>
|
||||
<path d="m140 68h12v74h-12z" fill="#e40cf2"/>
|
||||
<path d="m56 164h36v36h-36z" fill="#43f20c"/>
|
||||
<path d="m68 32h12v192h-12z"/>
|
||||
<path d="m32 176h192v12h-192z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 444 B |
|
@ -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;
|
15
src/bsp.cpp
15
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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<Equation,hEquation> *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<Param,hParam> *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<Equation,hEquation> *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<Equation,hEquation> *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<Equation,hEquation> *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<Equation,hEquation> *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<Equation,hEquation> *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<Equation,hEquation> *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<Equation,hEquation> *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<Equation,hEquation> *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();
|
||||
|
||||
|
|
|
@ -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<hConstraint> 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()) {
|
||||
|
|
16
src/draw.cpp
16
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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<Vector> 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<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 {
|
||||
|
|
279
src/dsc.h
279
src/dsc.h
|
@ -9,6 +9,34 @@
|
|||
|
||||
#include "solvespace.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
/// 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<typename T>
|
||||
struct IsHandleOracle : std::false_type {};
|
||||
|
||||
// Equality-compare any two instances of a handle type.
|
||||
template<typename T>
|
||||
static inline typename std::enable_if<IsHandleOracle<T>::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<typename T>
|
||||
static inline typename std::enable_if<IsHandleOracle<T>::value, bool>::type
|
||||
operator!=(T const &lhs, T const &rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
// Less-than-compare any two instances of a handle type.
|
||||
template<typename T>
|
||||
static inline typename std::enable_if<IsHandleOracle<T>::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 <class T>
|
||||
template<class T>
|
||||
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 <class T, class H>
|
||||
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 T, class H>
|
||||
class IdList {
|
||||
T *elem = nullptr;
|
||||
int elemsAllocated = 0;
|
||||
public:
|
||||
T *elem;
|
||||
int n;
|
||||
int elemsAllocated;
|
||||
int n = 0;
|
||||
|
||||
using Compare = CompareId<T, H>;
|
||||
|
||||
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<int>(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<typename F>
|
||||
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<T,H> *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<T,H> *l) {
|
||||
|
@ -467,9 +542,9 @@ public:
|
|||
elem[i].Clear();
|
||||
elem[i].~T();
|
||||
}
|
||||
elemsAllocated = n = 0;
|
||||
if(elem) MemFree(elem);
|
||||
elem = NULL;
|
||||
elemsAllocated = n = 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -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]);
|
||||
|
@ -870,19 +922,16 @@ void EntityBase::GenerateEquations(IdList<Equation,hEquation> *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]);
|
||||
|
|
174
src/export.cpp
174
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();
|
||||
|
@ -96,9 +96,8 @@ void SolveSpaceUI::ExportSectionTo(const Platform::Path &filename) {
|
|||
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<Vector> 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<std::uint8_t, std::vector<STriangleSpan>> 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<int> triangle_colour_ids;
|
||||
std::vector<RgbaColor> 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.
|
||||
|
|
|
@ -121,7 +121,7 @@ int StepFileWriter::ExportCurveLoop(SBezierLoop *loop, bool inner) {
|
|||
|
||||
List<int> 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
|
||||
|
|
|
@ -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);
|
||||
|
|
16
src/expr.cpp
16
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;
|
||||
|
|
|
@ -45,7 +45,7 @@ public:
|
|||
Expr *b;
|
||||
};
|
||||
|
||||
Expr() { }
|
||||
Expr() = default;
|
||||
Expr(double val) : op(Op::CONSTANT) { v = val; }
|
||||
|
||||
static inline Expr *AllocExpr()
|
||||
|
|
49
src/file.cpp
49
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();
|
||||
}
|
||||
}
|
||||
|
|
134
src/generate.cpp
134
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;
|
||||
|
|
|
@ -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<Entity *> &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<Entity *> &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<Selection> *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);
|
||||
}
|
||||
|
||||
|
|
257
src/group.cpp
257
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,hEntity> *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,hEntity> *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,hEntity> *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,hEntity> *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,hEntity> *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,hEntity> *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,hEntity> *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<Equation,hEquation> *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<Equation,hEquation> *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<Equation,hEquation> *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<Entity,hEntity> *el, IdList<Param,hParam> *p
|
|||
el->Add(&n);
|
||||
en.normal = n.h;
|
||||
el->Add(&en);
|
||||
} else if(ep->type == Entity::Type::LINE_SEGMENT) {
|
||||
}
|
||||
}
|
||||
|
||||
void Group::MakeLatheSurfacesSelectable(IdList<Entity, hEntity> *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<Entity,hEntity> *el, hEntity pt)
|
|||
void Group::CopyEntity(IdList<Entity,hEntity> *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<Entity,hEntity> *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<Entity,hEntity> *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<Entity,hEntity> *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<Entity,hEntity> *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<Entity,hEntity> *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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
96
src/mesh.cpp
96
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<uint32_t> &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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -24,7 +24,7 @@ void GraphicsWindow::AddPointToDraggedList(hEntity hp) {
|
|||
// twice as far as the mouse pointer...
|
||||
List<hEntity> *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<Constraint,hConstraint> *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();
|
||||
}
|
||||
|
|
|
@ -312,7 +312,7 @@ static bool RunCommand(const std::vector<std::string> args) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if(inputFiles.size() == 0) {
|
||||
if(inputFiles.empty()) {
|
||||
fprintf(stderr, "At least one input file must be specified.\n");
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@ int main(int argc, char** argv) {
|
|||
|
||||
Platform::Close3DConnexion();
|
||||
SS.Clear();
|
||||
|
||||
SK.Clear();
|
||||
Platform::ClearGui();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -95,6 +95,7 @@ std::vector<FileFilter> 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<FileFilter> SurfaceFileFilters = {
|
||||
|
|
|
@ -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<void()> onTimeout;
|
||||
|
||||
virtual ~Timer() {}
|
||||
virtual ~Timer() = default;
|
||||
|
||||
virtual void RunAfter(unsigned milliseconds) = 0;
|
||||
virtual void RunAfterNextFrame() { RunAfter(1); }
|
||||
|
@ -157,7 +157,7 @@ public:
|
|||
|
||||
std::function<void()> 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<MenuItem> MenuItemRef;
|
|||
// A native menu.
|
||||
class Menu {
|
||||
public:
|
||||
virtual ~Menu() {}
|
||||
virtual ~Menu() = default;
|
||||
|
||||
virtual std::shared_ptr<MenuItem> AddItem(
|
||||
const std::string &label, std::function<void()> onTrigger = std::function<void()>(),
|
||||
|
@ -188,7 +188,7 @@ typedef std::shared_ptr<Menu> MenuRef;
|
|||
// A native menu bar.
|
||||
class MenuBar {
|
||||
public:
|
||||
virtual ~MenuBar() {}
|
||||
virtual ~MenuBar() = default;
|
||||
|
||||
virtual std::shared_ptr<Menu> AddSubMenu(const std::string &label) = 0;
|
||||
|
||||
|
@ -222,7 +222,7 @@ public:
|
|||
std::function<void(double)> onScrollbarAdjusted;
|
||||
std::function<void()> onRender;
|
||||
|
||||
virtual ~Window() {}
|
||||
virtual ~Window() = default;
|
||||
|
||||
// Returns physical display DPI.
|
||||
virtual double GetPixelDensity() = 0;
|
||||
|
@ -299,7 +299,7 @@ public:
|
|||
|
||||
std::function<void(Response)> 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<FileFilter> 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();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1479,5 +1479,7 @@ void ExitGui() {
|
|||
[NSApp terminate:nil];
|
||||
}
|
||||
|
||||
void ClearGui() {}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<WindowImplWin32>(kind,
|
||||
|
@ -1675,5 +1677,7 @@ void ExitGui() {
|
|||
PostQuitMessage(0);
|
||||
}
|
||||
|
||||
void ClearGui() {}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<std::string> 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));
|
||||
}
|
||||
|
|
|
@ -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<std::string> InitPlatform(int argc, char **argv) {
|
||||
std::vector<std::string> args;
|
||||
args.reserve(argc);
|
||||
for(int i = 0; i < argc; i++) {
|
||||
args.push_back(argv[i]);
|
||||
args.emplace_back(argv[i]);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
|
137
src/polygon.cpp
137
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<SEdge*>(&(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<SEdge*>(&(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);
|
||||
|
|
|
@ -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<uint32_t> &faces) const;
|
||||
|
||||
bool IsEmpty() const;
|
||||
void RemapFaces(Group *g, int remap);
|
||||
|
|
|
@ -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<double> &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);
|
||||
|
|
|
@ -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<BatchCanvas> CreateBatch();
|
||||
};
|
||||
|
||||
template<>
|
||||
struct IsHandleOracle<Canvas::hStroke> : std::true_type {};
|
||||
|
||||
template<>
|
||||
struct IsHandleOracle<Canvas::hFill> : 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> pixmap;
|
||||
|
||||
|
|
|
@ -243,7 +243,7 @@ void SurfaceRenderer::ConvertBeziersToEdges() {
|
|||
List<Vector> 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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<DrawCall> 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<DrawCall> 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<DrawCall> 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<DrawCall> 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<DrawCall> 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<uint32_t> &faces, hFill hcf) override {
|
||||
|
|
|
@ -146,7 +146,7 @@ void Request::Generate(IdList<Entity,hEntity> *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,hEntity> *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,hEntity> *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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -540,7 +540,7 @@ void BitmapFont::AddGlyph(char32_t codepoint, std::shared_ptr<const Pixmap> 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]);
|
||||
|
|
53
src/sketch.h
53
src/sketch.h
|
@ -59,6 +59,10 @@ public:
|
|||
inline hParam param(int i) const;
|
||||
inline hEquation equation(int i) const;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct IsHandleOracle<hGroup> : std::true_type {};
|
||||
|
||||
class hRequest {
|
||||
public:
|
||||
// bits 15: 0 -- request index
|
||||
|
@ -69,6 +73,10 @@ public:
|
|||
|
||||
inline bool IsFromReferences() const;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct IsHandleOracle<hRequest> : 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<hEntity> : std::true_type {};
|
||||
|
||||
class hParam {
|
||||
public:
|
||||
// bits 15: 0 -- param index
|
||||
|
@ -89,14 +101,24 @@ public:
|
|||
inline hRequest request() const;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct IsHandleOracle<hParam> : std::true_type {};
|
||||
|
||||
class hStyle {
|
||||
public:
|
||||
uint32_t v;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct IsHandleOracle<hStyle> : std::true_type {};
|
||||
|
||||
struct EntityId {
|
||||
uint32_t v; // entity ID, starting from 0
|
||||
};
|
||||
|
||||
template<>
|
||||
struct IsHandleOracle<EntityId> : 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<EntityKey, EntityId, EntityKeyHash, EntityKeyEqual> 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<Entity,hEntity> *el, IdList<Param,hParam> *param, hEntity in, Vector pt, Vector axis, int ai);
|
||||
void MakeLatheSurfacesSelectable(IdList<Entity, hEntity> *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<Equation,hEquation> *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<hConstraint> : 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<hEquation> : 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
|
||||
|
|
|
@ -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<uint32_t> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,13 +165,8 @@ enum class Unit : uint32_t {
|
|||
METERS
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct CompareHandle {
|
||||
bool operator()(T lhs, T rhs) const { return lhs.v < rhs.v; }
|
||||
};
|
||||
|
||||
template<class Key, class T>
|
||||
using handle_map = std::map<Key, T, CompareHandle<Key>>;
|
||||
using handle_map = std::map<Key, T>;
|
||||
|
||||
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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<SCurvePt> *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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Revolved> hsl = {};
|
||||
List<std::vector<hSSurface>> 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<hSSurface> 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<hSSurface> 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<std::vector<hSSurface>> hsl = {};
|
||||
|
||||
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
|
||||
std::vector<hSSurface> 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<hSSurface> 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -63,11 +63,17 @@ public:
|
|||
uint32_t v;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct IsHandleOracle<hSSurface> : std::true_type {};
|
||||
|
||||
class hSCurve {
|
||||
public:
|
||||
uint32_t v;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct IsHandleOracle<hSCurve> : 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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
118
src/system.cpp
118
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<hConstraint> *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<hConstraint> *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<hConstraint> *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<hConstraint> *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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) },
|
||||
|
|
|
@ -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;
|
||||
|
|
12
src/ttf.cpp
12
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;
|
||||
}
|
||||
|
|
2
src/ui.h
2
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,
|
||||
|
|
|
@ -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();
|
||||
|
|
35
src/util.cpp
35
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 {
|
||||
|
|
|
@ -363,6 +363,7 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
|
||||
SS.Init();
|
||||
SS.showToolbar = false;
|
||||
SS.checkClosedContour = false;
|
||||
|
||||
Test::Helper helper = {};
|
||||
|
|
Loading…
Reference in New Issue