Add option to mirror imported geometry, including the shell, mesh,

and parametric entities. Also consolidate the text screen functions
to change group options into a single function for everything.

[git-p4: depot-paths = "//depot/solvespace/": change = 2051]
This commit is contained in:
Jonathan Westhues 2009-10-09 04:57:10 -08:00
parent ce37b1cf72
commit c153e23f49
15 changed files with 146 additions and 105 deletions

1
dsc.h
View File

@ -39,6 +39,7 @@ public:
Quaternion ToThe(double p);
Quaternion Inverse(void);
Quaternion Times(Quaternion b);
Quaternion MirrorZ(void);
};
class Vector {

View File

@ -93,6 +93,7 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = {
{ 'g', "Group.visible", 'b', &(SS.sv.g.visible) },
{ 'g', "Group.suppress", 'b', &(SS.sv.g.suppress) },
{ 'g', "Group.relaxConstraints", 'b', &(SS.sv.g.relaxConstraints) },
{ 'g', "Group.mirror", 'b', &(SS.sv.g.mirror) },
{ 'g', "Group.remap", 'M', &(SS.sv.g.remap) },
{ 'g', "Group.impFile", 'P', &(SS.sv.g.impFile) },
{ 'g', "Group.impFileRel", 'P', &(SS.sv.g.impFileRel) },

View File

@ -706,6 +706,7 @@ void Group::CopyEntity(IdList<Entity,hEntity> *el,
en.param[6] = qvz;
}
en.numPoint = ep->actPoint;
if(mirror) en.numPoint.z *= -1;
break;
case Entity::NORMAL_N_COPY:
@ -727,6 +728,8 @@ void Group::CopyEntity(IdList<Entity,hEntity> *el,
en.param[3] = qvz;
}
en.numNormal = ep->actNormal;
if(mirror) en.numNormal = en.numNormal.MirrorZ();
en.point[0] = Remap(ep->point[0], remap);
break;
@ -762,6 +765,11 @@ void Group::CopyEntity(IdList<Entity,hEntity> *el,
}
en.numPoint = ep->actPoint;
en.numNormal = ep->actNormal;
if(mirror) {
if(en.type != Entity::FACE_N_ROT_TRANS) oops();
en.numPoint.z *= -1;
en.numNormal.vz *= -1;
}
break;
default:

View File

@ -95,7 +95,8 @@ void Group::GenerateForStepAndRepeat(T *steps, T *outs) {
if(type == TRANSLATE) {
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
trans = trans.ScaledBy(ap);
transd.MakeFromTransformationOf(steps, trans, Quaternion::IDENTITY);
transd.MakeFromTransformationOf(steps,
trans, Quaternion::IDENTITY, false);
} else {
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
double theta = ap * SK.GetParam(h.param(3))->val;
@ -104,7 +105,7 @@ void Group::GenerateForStepAndRepeat(T *steps, T *outs) {
Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z);
// Rotation is centered at t; so A(x - t) + t = Ax + (t - At)
transd.MakeFromTransformationOf(steps,
trans.Minus(q.Rotate(trans)), q);
trans.Minus(q.Rotate(trans)), q, false);
}
// We need to rewrite any plane face entities to the transformed ones.
@ -247,10 +248,10 @@ void Group::GenerateShellAndMesh(void) {
SK.GetParam(h.param(5))->val,
SK.GetParam(h.param(6))->val };
thisMesh.MakeFromTransformationOf(&impMesh, offset, q);
thisMesh.MakeFromTransformationOf(&impMesh, offset, q, mirror);
thisMesh.RemapFaces(this, 0);
thisShell.MakeFromTransformationOf(&impShell, offset, q);
thisShell.MakeFromTransformationOf(&impShell, offset, q, mirror);
thisShell.RemapFaces(this, 0);
}

View File

@ -293,10 +293,19 @@ void SMesh::MakeFromAssemblyOf(SMesh *a, SMesh *b) {
MakeFromCopyOf(b);
}
void SMesh::MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q) {
void SMesh::MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q,
bool mirror)
{
STriangle *tr;
for(tr = a->l.First(); tr; tr = a->l.NextAfter(tr)) {
STriangle tt = *tr;
if(mirror) {
tt.a.z *= -1;
tt.b.z *= -1;
tt.c.z *= -1;
// The mirroring would otherwise turn a closed mesh inside out.
SWAP(Vector, tt.a, tt.b);
}
tt.a = (q.Rotate(tt.a)).Plus(trans);
tt.b = (q.Rotate(tt.b)).Plus(trans);
tt.c = (q.Rotate(tt.c)).Plus(trans);

View File

@ -204,7 +204,8 @@ public:
void MakeFromDifferenceOf(SMesh *a, SMesh *b);
void MakeFromCopyOf(SMesh *a);
void MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q);
void MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q,
bool mirror);
void MakeFromAssemblyOf(SMesh *a, SMesh *b);
void MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d);

View File

@ -102,6 +102,7 @@ public:
bool visible;
bool suppress;
bool relaxConstraints;
bool mirror;
bool clean;
bool vvMeshClean;

View File

@ -687,7 +687,7 @@ void SShell::MakeFromAssemblyOf(SShell *a, SShell *b) {
for(i = 0; i < 2; i++) {
ab = (i == 0) ? a : b;
for(c = ab->curve.First(); c; c = ab->curve.NextAfter(c)) {
cn = SCurve::FromTransformationOf(c, t, q);
cn = SCurve::FromTransformationOf(c, t, q, false);
cn.source = (i == 0) ? SCurve::FROM_A : SCurve::FROM_B;
// surfA and surfB are wrong now, and we can't fix them until
// we've assigned IDs to the surfaces. So we'll get that later.
@ -700,7 +700,7 @@ void SShell::MakeFromAssemblyOf(SShell *a, SShell *b) {
for(i = 0; i < 2; i++) {
ab = (i == 0) ? a : b;
for(s = ab->surface.First(); s; s = ab->surface.NextAfter(s)) {
sn = SSurface::FromTransformationOf(s, t, q, true);
sn = SSurface::FromTransformationOf(s, t, q, false, true);
// All the trim curve IDs get rewritten; we know the new handles
// to the curves since we recorded them in the previous step.
STrimBy *stb;

View File

@ -88,10 +88,11 @@ void SBezier::GetBoundingProjd(Vector u, Vector orig,
}
}
SBezier SBezier::TransformedBy(Vector t, Quaternion q) {
SBezier SBezier::TransformedBy(Vector t, Quaternion q, bool mirror) {
SBezier ret = *this;
int i;
for(i = 0; i <= deg; i++) {
if(mirror) ret.ctrl[i].z *= -1;
ret.ctrl[i] = (q.Rotate(ret.ctrl[i])).Plus(t);
}
return ret;
@ -167,7 +168,7 @@ SBezier SBezier::InPerspective(Vector u, Vector v, Vector n,
Quaternion q = Quaternion::From(u, v);
q = q.Inverse();
// we want Q*(p - o) = Q*p - Q*o
SBezier ret = this->TransformedBy(q.Rotate(origin).ScaledBy(-1), q);
SBezier ret = this->TransformedBy(q.Rotate(origin).ScaledBy(-1), q, false);
int i;
for(i = 0; i <= deg; i++) {
Vector4 ct = Vector4::From(ret.weight[i], ret.ctrl[i]);
@ -427,20 +428,23 @@ void SBezierLoopSet::Clear(void) {
l.Clear();
}
SCurve SCurve::FromTransformationOf(SCurve *a, Vector t, Quaternion q) {
SCurve SCurve::FromTransformationOf(SCurve *a, Vector t, Quaternion q,
bool mirror)
{
SCurve ret;
ZERO(&ret);
ret.h = a->h;
ret.isExact = a->isExact;
ret.exact = (a->exact).TransformedBy(t, q);
ret.exact = (a->exact).TransformedBy(t, q, mirror);
ret.surfA = a->surfA;
ret.surfB = a->surfB;
SCurvePt *p;
for(p = a->pts.First(); p; p = a->pts.NextAfter(p)) {
SCurvePt pp = *p;
pp.p = (q.Rotate(p->p)).Plus(t);
if(mirror) pp.p.z *= -1;
pp.p = (q.Rotate(pp.p)).Plus(t);
ret.pts.Add(&pp);
}
return ret;

View File

@ -133,6 +133,7 @@ SSurface SSurface::FromPlane(Vector pt, Vector u, Vector v) {
}
SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q,
bool mirror,
bool includingTrims)
{
SSurface ret;
@ -147,7 +148,10 @@ SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q,
int i, j;
for(i = 0; i <= 3; i++) {
for(j = 0; j <= 3; j++) {
ret.ctrl[i][j] = (q.Rotate(a->ctrl[i][j])).Plus(t);
ret.ctrl[i][j] = a->ctrl[i][j];
if(mirror) ret.ctrl[i][j].z *= -1;
ret.ctrl[i][j] = (q.Rotate(ret.ctrl[i][j])).Plus(t);
ret.weight[i][j] = a->weight[i][j];
}
}
@ -156,12 +160,22 @@ SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q,
STrimBy *stb;
for(stb = a->trim.First(); stb; stb = a->trim.NextAfter(stb)) {
STrimBy n = *stb;
if(mirror) {
n.start.z *= -1;
n.finish.z *= -1;
}
n.start = (q.Rotate(n.start)) .Plus(t);
n.finish = (q.Rotate(n.finish)).Plus(t);
ret.trim.Add(&n);
}
}
if(mirror) {
// If we mirror every surface of a shell, then it will end up inside
// out. So fix that here.
ret.Reverse();
}
return ret;
}
@ -522,7 +536,7 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1,
SCurve sc;
ZERO(&sc);
sc.isExact = true;
sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY);
sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY, false);
(sc.exact).MakePwlInto(&(sc.pts));
sc.surfA = hs0;
sc.surfB = hsext;
@ -530,7 +544,7 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1,
ZERO(&sc);
sc.isExact = true;
sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY);
sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY, false);
(sc.exact).MakePwlInto(&(sc.pts));
sc.surfA = hs1;
sc.surfB = hsext;
@ -669,7 +683,7 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis,
if(revs.d[j].v) {
ZERO(&sc);
sc.isExact = true;
sc.exact = sb->TransformedBy(ts, qs);
sc.exact = sb->TransformedBy(ts, qs, false);
(sc.exact).MakePwlInto(&(sc.pts));
sc.surfA = revs.d[j];
sc.surfB = revs.d[WRAP(j-1, 4)];
@ -795,23 +809,26 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis,
}
void SShell::MakeFromCopyOf(SShell *a) {
MakeFromTransformationOf(a, Vector::From(0, 0, 0), Quaternion::IDENTITY);
MakeFromTransformationOf(a,
Vector::From(0, 0, 0), Quaternion::IDENTITY, false);
}
void SShell::MakeFromTransformationOf(SShell *a, Vector t, Quaternion q) {
void SShell::MakeFromTransformationOf(SShell *a,
Vector t, Quaternion q, bool mirror)
{
booleanFailed = false;
SSurface *s;
for(s = a->surface.First(); s; s = a->surface.NextAfter(s)) {
SSurface n;
n = SSurface::FromTransformationOf(s, t, q, true);
n = SSurface::FromTransformationOf(s, t, q, mirror, true);
surface.Add(&n); // keeping the old ID
}
SCurve *c;
for(c = a->curve.First(); c; c = a->curve.NextAfter(c)) {
SCurve n;
n = SCurve::FromTransformationOf(c, t, q);
n = SCurve::FromTransformationOf(c, t, q, mirror);
curve.Add(&n); // keeping the old ID
}
}

View File

@ -89,7 +89,7 @@ public:
bool IsCircle(Vector axis, Vector *center, double *r);
bool IsRational(void);
SBezier TransformedBy(Vector t, Quaternion q);
SBezier TransformedBy(Vector t, Quaternion q, bool mirror);
SBezier InPerspective(Vector u, Vector v, Vector n,
Vector origin, double cameraTan);
@ -166,7 +166,8 @@ public:
hSSurface surfA;
hSSurface surfB;
static SCurve FromTransformationOf(SCurve *a, Vector t, Quaternion q);
static SCurve FromTransformationOf(SCurve *a, Vector t, Quaternion q,
bool mirror);
SCurve MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB,
SSurface *srfA, SSurface *srfB);
void RemoveShortSegments(SSurface *srfA, SSurface *srfB);
@ -235,7 +236,8 @@ public:
static SSurface FromRevolutionOf(SBezier *sb, Vector pt, Vector axis,
double thetas, double thetaf);
static SSurface FromPlane(Vector pt, Vector u, Vector v);
static SSurface FromTransformationOf(SSurface *a, Vector t, Quaternion q,
static SSurface FromTransformationOf(SSurface *a, Vector t, Quaternion q,
bool mirror,
bool includingTrims);
void EdgeNormalsWithinSurface(Point2d auv, Point2d buv,
@ -358,7 +360,8 @@ public:
Vector edge_n_in, Vector edge_n_out, Vector surf_n);
void MakeFromCopyOf(SShell *a);
void MakeFromTransformationOf(SShell *a, Vector trans, Quaternion q);
void MakeFromTransformationOf(SShell *a, Vector trans, Quaternion q,
bool mirror);
void MakeFromAssemblyOf(SShell *a, SShell *b);
void MergeCoincidentSurfaces(void);

View File

@ -203,64 +203,51 @@ void TextWindow::ScreenSelectRequest(int link, DWORD v) {
SS.GW.ClearSelection();
SS.GW.selection[0].entity = hr.entity(0);
}
void TextWindow::ScreenChangeOneOrTwoSides(int link, DWORD v) {
SS.UndoRemember();
void TextWindow::ScreenChangeGroupOption(int link, DWORD v) {
SS.UndoRemember();
Group *g = SK.GetGroup(SS.TW.shown.group);
if(g->subtype == Group::ONE_SIDED) {
g->subtype = Group::TWO_SIDED;
} else if(g->subtype == Group::TWO_SIDED) {
g->subtype = Group::ONE_SIDED;
} else oops();
switch(link) {
case 's':
if(g->subtype == Group::ONE_SIDED) {
g->subtype = Group::TWO_SIDED;
} else {
g->subtype = Group::ONE_SIDED;
}
break;
case 'k':
(g->skipFirst) = !(g->skipFirst);
break;
case 'c':
g->meshCombine = v;
break;
case 'P':
g->suppress = !(g->suppress);
break;
case 'm':
g->mirror = !(g->mirror);
break;
case 'r':
g->relaxConstraints = !(g->relaxConstraints);
break;
case 'f':
g->forceToMesh = !(g->forceToMesh);
break;
}
SS.MarkGroupDirty(g->h);
SS.GenerateAll();
SS.GW.ClearSuper();
}
void TextWindow::ScreenChangeSkipFirst(int link, DWORD v) {
SS.UndoRemember();
Group *g = SK.GetGroup(SS.TW.shown.group);
(g->skipFirst) = !(g->skipFirst);
SS.MarkGroupDirty(g->h);
SS.GenerateAll();
SS.GW.ClearSuper();
}
void TextWindow::ScreenChangeMeshCombine(int link, DWORD v) {
SS.UndoRemember();
Group *g = SK.GetGroup(SS.TW.shown.group);
g->meshCombine = v;
SS.MarkGroupDirty(g->h);
SS.GenerateAll();
SS.GW.ClearSuper();
}
void TextWindow::ScreenChangeMeshOrExact(int link, DWORD v) {
SS.UndoRemember();
Group *g = SK.GetGroup(SS.TW.shown.group);
g->forceToMesh = !(g->forceToMesh);
SS.MarkGroupDirty(g->h);
SS.GenerateAll();
SS.GW.ClearSuper();
}
void TextWindow::ScreenChangeSuppress(int link, DWORD v) {
SS.UndoRemember();
Group *g = SK.GetGroup(SS.TW.shown.group);
g->suppress = !(g->suppress);
SS.MarkGroupDirty(g->h);
SS.GenerateAll();
SS.GW.ClearSuper();
}
void TextWindow::ScreenChangeRelaxConstraints(int link, DWORD v) {
SS.UndoRemember();
Group *g = SK.GetGroup(SS.TW.shown.group);
g->relaxConstraints = !(g->relaxConstraints);
SS.MarkGroupDirty(g->h);
SS.later.generateAll = true;
SS.later.showTW = true;
}
void TextWindow::ScreenChangeRightLeftHanded(int link, DWORD v) {
SS.UndoRemember();
@ -367,10 +354,10 @@ void TextWindow::ShowGroupInfo(void) {
g->type == Group::TRANSLATE)
{
bool one = (g->subtype == Group::ONE_SIDED);
Printf(true, "%Ft%s%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E", s,
&TextWindow::ScreenChangeOneOrTwoSides,
Printf(true, "%Ft%s%E %Fh%f%Ls%s%E%Fs%s%E / %Fh%f%Ls%s%E%Fs%s%E", s,
&TextWindow::ScreenChangeGroupOption,
(one ? "" : "one side"), (one ? "one side" : ""),
&TextWindow::ScreenChangeOneOrTwoSides,
&TextWindow::ScreenChangeGroupOption,
(!one ? "" : "two sides"), (!one ? "two sides" : ""));
}
@ -402,11 +389,11 @@ void TextWindow::ShowGroupInfo(void) {
bool space;
if(g->subtype == Group::ONE_SIDED) {
bool skip = g->skipFirst;
Printf(true, "%Ft%s%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E",
Printf(true, "%Ft%s%E %Fh%f%Lk%s%E%Fs%s%E / %Fh%f%Lk%s%E%Fs%s%E",
s3,
&ScreenChangeSkipFirst,
&ScreenChangeGroupOption,
(!skip ? "" : "with original"), (!skip ? "with original" : ""),
&ScreenChangeSkipFirst,
&ScreenChangeGroupOption,
(skip ? "":"with copy #1"), (skip ? "with copy #1":""));
space = false;
} else {
@ -435,34 +422,40 @@ void TextWindow::ShowGroupInfo(void) {
bool asa = (g->type == Group::IMPORTED);
Printf((g->type == Group::HELICAL_SWEEP),
"%FtMERGE AS%E %Fh%f%D%Ll%s%E%Fs%s%E / %Fh%f%D%Ll%s%E%Fs%s%E %s "
"%Fh%f%D%Ll%s%E%Fs%s%E",
&TextWindow::ScreenChangeMeshCombine,
"%FtMERGE AS%E %Fh%f%D%Lc%s%E%Fs%s%E / %Fh%f%D%Lc%s%E%Fs%s%E %s "
"%Fh%f%D%Lc%s%E%Fs%s%E",
&TextWindow::ScreenChangeGroupOption,
Group::COMBINE_AS_UNION,
(un ? "" : "union"), (un ? "union" : ""),
&TextWindow::ScreenChangeMeshCombine,
&TextWindow::ScreenChangeGroupOption,
Group::COMBINE_AS_DIFFERENCE,
(diff ? "" : "difference"), (diff ? "difference" : ""),
asa ? "/" : "",
&TextWindow::ScreenChangeMeshCombine,
&TextWindow::ScreenChangeGroupOption,
Group::COMBINE_AS_ASSEMBLE,
(asy || !asa ? "" : "assemble"), (asy && asa ? "assemble" : ""));
}
if(g->type == Group::IMPORTED) {
bool sup = g->suppress;
Printf(false, "%FtSUPPRESS%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E",
&TextWindow::ScreenChangeSuppress,
Printf(false, "%FtSUPPRESS%E %Fh%f%LP%s%E%Fs%s%E / %Fh%f%LP%s%E%Fs%s%E",
&TextWindow::ScreenChangeGroupOption,
(sup ? "" : "yes"), (sup ? "yes" : ""),
&TextWindow::ScreenChangeSuppress,
&TextWindow::ScreenChangeGroupOption,
(!sup ? "" : "no"), (!sup ? "no" : ""));
Printf(false, "%FtMIRROR%E %Fh%f%Lm%s%E%Fs%s%E / %Fh%f%Lm%s%E%Fs%s%E",
&TextWindow::ScreenChangeGroupOption,
(g->mirror ? "" : "yes"), (g->mirror ? "yes" : ""),
&TextWindow::ScreenChangeGroupOption,
(!g->mirror ? "" : "no"), (!g->mirror ? "no" : ""));
}
bool relax = g->relaxConstraints;
Printf(true, "%FtSOLVING%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E",
&TextWindow::ScreenChangeRelaxConstraints,
Printf(true, "%FtSOLVING%E %Fh%f%Lr%s%E%Fs%s%E / %Fh%f%Lr%s%E%Fs%s%E",
&TextWindow::ScreenChangeGroupOption,
(!relax ? "" : "with all constraints"),
(!relax ? "with all constraints" : ""),
&TextWindow::ScreenChangeRelaxConstraints,
&TextWindow::ScreenChangeGroupOption,
(relax ? "" : "no"), (relax ? "no" : ""));
if(g->type == Group::EXTRUDE ||
@ -490,10 +483,10 @@ void TextWindow::ShowGroupInfo(void) {
if(pg->runningMesh.IsEmpty() && g->thisMesh.IsEmpty()) {
bool fm = g->forceToMesh;
Printf(true,
"%FtSURFACES%E %Fh%f%Ll%s%E%Fs%s%E / %Fh%f%Ll%s%E%Fs%s%E",
&TextWindow::ScreenChangeMeshOrExact,
"%FtSURFACES%E %Fh%f%Lf%s%E%Fs%s%E / %Fh%f%Lf%s%E%Fs%s%E",
&TextWindow::ScreenChangeGroupOption,
(!fm ? "" : "as NURBS"), (!fm ? "as NURBS" : ""),
&TextWindow::ScreenChangeMeshOrExact,
&TextWindow::ScreenChangeGroupOption,
(fm ? "" : "as mesh"), (fm ? "as mesh" : ""));
} else {
Printf(false,

7
ui.h
View File

@ -147,12 +147,7 @@ public:
static void ScreenSelectRequest(int link, DWORD v);
static void ScreenSelectConstraint(int link, DWORD v);
static void ScreenChangeOneOrTwoSides(int link, DWORD v);
static void ScreenChangeSkipFirst(int link, DWORD v);
static void ScreenChangeMeshCombine(int link, DWORD v);
static void ScreenChangeMeshOrExact(int link, DWORD v);
static void ScreenChangeSuppress(int link, DWORD v);
static void ScreenChangeRelaxConstraints(int link, DWORD v);
static void ScreenChangeGroupOption(int link, DWORD v);
static void ScreenChangeRightLeftHanded(int link, DWORD v);
static void ScreenChangeHelixParameter(int link, DWORD v);
static void ScreenColor(int link, DWORD v);

View File

@ -290,6 +290,14 @@ Quaternion Quaternion::Times(Quaternion b) {
return r;
}
Quaternion Quaternion::MirrorZ(void) {
Vector u = RotationU(),
v = RotationV();
u.z *= -1;
v.z *= -1;
return Quaternion::From(u, v);
}
Vector Vector::From(double x, double y, double z) {
Vector v;

View File

@ -2,17 +2,16 @@
multi-drag
copy and paste
wireframe export
import mirrored
interpolating splines
associative entities from solid model, as a special group
-----
some kind of import
filled contours for export
faster triangulation
interpolating splines
loop detection
IGES export
incremental regen of entities
associative entities from solid model, as a special group
rounding, as a special group