Remove all UI-related files, and move the rest to match master.

This commit is contained in:
whitequark 2016-10-25 12:53:58 +00:00
parent defeb6411a
commit 15446e8f0a
125 changed files with 0 additions and 37334 deletions

102
Makefile
View File

@ -1,102 +0,0 @@
DEFINES = /D_WIN32_WINNT=0x500 /DISOLATION_AWARE_ENABLED /D_WIN32_IE=0x500 /DWIN32_LEAN_AND_MEAN /DWIN32
# Use the multi-threaded static libc because libpng and zlib do; not sure if anything bad
# happens if those mix, but don't want to risk it.
CFLAGS = /W3 /nologo -MT -Iextlib -I..\common\win32 /D_DEBUG /D_CRT_SECURE_NO_WARNINGS /I. /Zi /EHs # /O2
HEADERS = ..\common\win32\freeze.h ui.h solvespace.h dsc.h sketch.h expr.h polygon.h srf\surface.h
OBJDIR = obj
FREEZE = $(OBJDIR)\freeze.obj
W32OBJS = $(OBJDIR)\w32main.obj \
$(OBJDIR)\w32util.obj \
SSOBJS = $(OBJDIR)\solvespace.obj \
$(OBJDIR)\textwin.obj \
$(OBJDIR)\textscreens.obj \
$(OBJDIR)\confscreen.obj \
$(OBJDIR)\describescreen.obj \
$(OBJDIR)\graphicswin.obj \
$(OBJDIR)\modify.obj \
$(OBJDIR)\clipboard.obj \
$(OBJDIR)\view.obj \
$(OBJDIR)\util.obj \
$(OBJDIR)\style.obj \
$(OBJDIR)\entity.obj \
$(OBJDIR)\drawentity.obj \
$(OBJDIR)\group.obj \
$(OBJDIR)\groupmesh.obj \
$(OBJDIR)\request.obj \
$(OBJDIR)\glhelper.obj \
$(OBJDIR)\expr.obj \
$(OBJDIR)\constraint.obj \
$(OBJDIR)\constrainteq.obj \
$(OBJDIR)\mouse.obj \
$(OBJDIR)\draw.obj \
$(OBJDIR)\toolbar.obj \
$(OBJDIR)\drawconstraint.obj \
$(OBJDIR)\file.obj \
$(OBJDIR)\undoredo.obj \
$(OBJDIR)\system.obj \
$(OBJDIR)\polygon.obj \
$(OBJDIR)\mesh.obj \
$(OBJDIR)\bsp.obj \
$(OBJDIR)\ttf.obj \
$(OBJDIR)\generate.obj \
$(OBJDIR)\export.obj \
$(OBJDIR)\exportvector.obj \
$(OBJDIR)\exportstep.obj \
SRFOBJS = $(OBJDIR)\ratpoly.obj \
$(OBJDIR)\curve.obj \
$(OBJDIR)\surface.obj \
$(OBJDIR)\triangulate.obj \
$(OBJDIR)\boolean.obj \
$(OBJDIR)\surfinter.obj \
$(OBJDIR)\raycast.obj \
$(OBJDIR)\merge.obj \
RES = $(OBJDIR)\resource.res
LIBS = user32.lib gdi32.lib comctl32.lib advapi32.lib shell32.lib opengl32.lib glu32.lib \
extlib\libpng.lib extlib\zlib.lib extlib\si\siapp.lib
all: $(OBJDIR)/solvespace.exe
@cp $(OBJDIR)/solvespace.exe .
clean:
rm -f obj/*
$(OBJDIR)/solvespace.exe: $(SRFOBJS) $(SSOBJS) $(W32OBJS) $(FREEZE) $(RES)
@$(CC) $(DEFINES) $(CFLAGS) -Fe$(OBJDIR)/solvespace.exe $(SSOBJS) $(SRFOBJS) $(W32OBJS) $(FREEZE) $(RES) $(LIBS)
editbin /nologo /STACK:8388608 $(OBJDIR)/solvespace.exe
@echo solvespace.exe
$(SSOBJS): $(@B).cpp $(HEADERS)
@$(CC) $(CFLAGS) $(DEFINES) -c -Fo$(OBJDIR)/$(@B).obj $(@B).cpp
$(SRFOBJS): srf\$(@B).cpp $(HEADERS)
@$(CC) $(CFLAGS) $(DEFINES) -c -Fo$(OBJDIR)/$(@B).obj srf\$(@B).cpp
$(W32OBJS): win32/$(@B).cpp $(HEADERS)
@$(CC) $(CFLAGS) $(DEFINES) -c -Fo$(OBJDIR)/$(@B).obj win32/$(@B).cpp
$(FREEZE): ..\common\win32\$(@B).cpp $(HEADERS)
@$(CC) $(CFLAGS) $(DEFINES) -c -Fo$(OBJDIR)/$(@B).obj ..\common\win32\$(@B).cpp
$(RES): win32/$(@B).rc icon.ico
rc win32/$(@B).rc
mv win32/$(@B).res $(OBJDIR)/$(@B).res
toolbar.cpp: $(OBJDIR)/icons.h
textwin.cpp: $(OBJDIR)/icons.h
glhelper.cpp: bitmapfont.table font.table bitmapextra.table
$(OBJDIR)/icons.h: icons/* png2c.pl
perl png2c.pl $(OBJDIR)/icons.h $(OBJDIR)/icons-proto.h

View File

@ -1,68 +0,0 @@
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0,
0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0,
0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0,
0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 255, 255, 255, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 255, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 255, 255, 255, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0,
0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0,
0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0,
0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0,
0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0,
0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0,
0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0,
0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0,
0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0,
0, 0, 255, 255, 0, 0, 0, 255, 255, 0, 0, 0, 255, 255, 0, 0,
0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0,
0, 0, 255, 255, 0, 0, 255, 255, 255, 255, 0, 0, 255, 255, 0, 0,
0, 0, 255, 255, 0, 0, 0, 255, 255, 0, 0, 0, 255, 255, 0, 0,
0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0,
0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0,
0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

File diff suppressed because it is too large Load Diff

680
bsp.cpp
View File

@ -1,680 +0,0 @@
//-----------------------------------------------------------------------------
// Binary space partitioning tree, used to represent a volume in 3-space
// bounded by a triangle mesh. These are used to compute Boolean operations
// on meshes. These aren't used for anything relating to an SShell of
// ratpoly surfaces.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
SBsp2 *SBsp2::Alloc(void) { return (SBsp2 *)AllocTemporary(sizeof(SBsp2)); }
SBsp3 *SBsp3::Alloc(void) { return (SBsp3 *)AllocTemporary(sizeof(SBsp3)); }
SBsp3 *SBsp3::FromMesh(SMesh *m) {
SBsp3 *bsp3 = NULL;
int i;
SMesh mc; ZERO(&mc);
for(i = 0; i < m->l.n; i++) {
mc.AddTriangle(&(m->l.elem[i]));
}
srand(0); // Let's be deterministic, at least!
int n = mc.l.n;
while(n > 1) {
int k = rand() % n;
n--;
SWAP(STriangle, mc.l.elem[k], mc.l.elem[n]);
}
for(i = 0; i < mc.l.n; i++) {
bsp3 = bsp3->Insert(&(mc.l.elem[i]), NULL);
}
mc.Clear();
return bsp3;
}
Vector SBsp3::IntersectionWith(Vector a, Vector b) {
double da = a.Dot(n) - d;
double db = b.Dot(n) - d;
if(da*db > 0) oops();
double dab = (db - da);
return (a.ScaledBy(db/dab)).Plus(b.ScaledBy(-da/dab));
}
void SBsp3::InsertInPlane(bool pos2, STriangle *tr, SMesh *m) {
Vector tc = ((tr->a).Plus(tr->b).Plus(tr->c)).ScaledBy(1.0/3);
bool onFace = false;
bool sameNormal;
double maxNormalMag = -1;
Vector lln, trn = tr->Normal();
SBsp3 *ll = this;
while(ll) {
if((ll->tri).ContainsPoint(tc)) {
onFace = true;
// If the mesh contains almost-zero-area triangles, and we're
// just on the edge of one of those, then don't trust its normal.
lln = (ll->tri).Normal();
if(lln.Magnitude() > maxNormalMag) {
sameNormal = trn.Dot(lln) > 0;
maxNormalMag = lln.Magnitude();
}
}
ll = ll->more;
}
if(m->flipNormal && ((!pos2 && !onFace) ||
(onFace && !sameNormal && m->keepCoplanar)))
{
m->AddTriangle(tr->meta, tr->c, tr->b, tr->a);
} else if(!(m->flipNormal) && ((pos2 && !onFace) ||
(onFace && sameNormal && m->keepCoplanar)))
{
m->AddTriangle(tr->meta, tr->a, tr->b, tr->c);
} else {
m->atLeastOneDiscarded = true;
}
}
void SBsp3::InsertHow(int how, STriangle *tr, SMesh *instead) {
switch(how) {
case POS:
if(instead && !pos) goto alt;
pos = pos->Insert(tr, instead);
break;
case NEG:
if(instead && !neg) goto alt;
neg = neg->Insert(tr, instead);
break;
case COPLANAR: {
if(instead) goto alt;
SBsp3 *m = Alloc();
m->n = n;
m->d = d;
m->tri = *tr;
m->more = more;
more = m;
break;
}
default: oops();
}
return;
alt:
if(how == POS && !(instead->flipNormal)) {
instead->AddTriangle(tr->meta, tr->a, tr->b, tr->c);
} else if(how == NEG && instead->flipNormal) {
instead->AddTriangle(tr->meta, tr->c, tr->b, tr->a);
} else if(how == COPLANAR) {
if(edges) {
edges->InsertTriangle(tr, instead, this);
} else {
// I suppose this actually is allowed to happen, if the coplanar
// face is the leaf, and all of its neighbors are earlier in tree?
InsertInPlane(false, tr, instead);
}
} else {
instead->atLeastOneDiscarded = true;
}
}
void SBsp3::InsertConvexHow(int how, STriMeta meta, Vector *vertex, int n,
SMesh *instead)
{
switch(how) {
case POS:
if(pos) {
pos = pos->InsertConvex(meta, vertex, n, instead);
return;
}
break;
case NEG:
if(neg) {
neg = neg->InsertConvex(meta, vertex, n, instead);
return;
}
break;
default: oops();
}
int i;
for(i = 0; i < n - 2; i++) {
STriangle tr = STriangle::From(meta,
vertex[0], vertex[i+1], vertex[i+2]);
InsertHow(how, &tr, instead);
}
}
SBsp3 *SBsp3::InsertConvex(STriMeta meta, Vector *vertex, int cnt,
SMesh *instead)
{
Vector e01 = (vertex[1]).Minus(vertex[0]);
Vector e12 = (vertex[2]).Minus(vertex[1]);
Vector out = e01.Cross(e12);
#define MAX_VERTICES 50
if(cnt+1 >= MAX_VERTICES) goto triangulate;
int i;
Vector on[2];
bool isPos[MAX_VERTICES];
bool isNeg[MAX_VERTICES];
bool isOn[MAX_VERTICES];
int posc = 0, negc = 0, onc = 0;
for(i = 0; i < cnt; i++) {
double dt = n.Dot(vertex[i]);
isPos[i] = isNeg[i] = isOn[i] = false;
if(fabs(dt - d) < LENGTH_EPS) {
isOn[i] = true;
if(onc < 2) {
on[onc] = vertex[i];
}
onc++;
} else if(dt > d) {
isPos[i] = true;
posc++;
} else {
isNeg[i] = true;
negc++;
}
}
if(onc != 2 && onc != 1 && onc != 0) goto triangulate;
if(onc == 2) {
if(!instead) {
SEdge se = SEdge::From(on[0], on[1]);
edges = edges->InsertEdge(&se, n, out);
}
}
if(posc == 0) {
InsertConvexHow(NEG, meta, vertex, cnt, instead);
return this;
}
if(negc == 0) {
InsertConvexHow(POS, meta, vertex, cnt, instead);
return this;
}
Vector vpos[MAX_VERTICES];
Vector vneg[MAX_VERTICES];
int npos = 0, nneg = 0;
Vector inter[2];
int inters = 0;
for(i = 0; i < cnt; i++) {
int ip = WRAP((i + 1), cnt);
if(isPos[i]) {
vpos[npos++] = vertex[i];
}
if(isNeg[i]) {
vneg[nneg++] = vertex[i];
}
if(isOn[i]) {
vneg[nneg++] = vertex[i];
vpos[npos++] = vertex[i];
}
if((isPos[i] && isNeg[ip]) || (isNeg[i] && isPos[ip])) {
Vector vi = IntersectionWith(vertex[i], vertex[ip]);
vpos[npos++] = vi;
vneg[nneg++] = vi;
if(inters >= 2) goto triangulate; // XXX shouldn't happen but does
inter[inters++] = vi;
}
}
if(npos > cnt + 1 || nneg > cnt + 1) oops();
if(!instead) {
if(inters == 2) {
SEdge se = SEdge::From(inter[0], inter[1]);
edges = edges->InsertEdge(&se, n, out);
} else if(inters == 1 && onc == 1) {
SEdge se = SEdge::From(inter[0], on[0]);
edges = edges->InsertEdge(&se, n, out);
} else if(inters == 0 && onc == 2) {
// We already handled this on-plane existing edge
} else {
goto triangulate;
}
}
if(nneg < 3 || npos < 3) goto triangulate; // XXX
InsertConvexHow(NEG, meta, vneg, nneg, instead);
InsertConvexHow(POS, meta, vpos, npos, instead);
return this;
triangulate:
// We don't handle the special case for this; do it as triangles
SBsp3 *r = this;
for(i = 0; i < cnt - 2; i++) {
STriangle tr = STriangle::From(meta,
vertex[0], vertex[i+1], vertex[i+2]);
r = r->Insert(&tr, instead);
}
return r;
}
SBsp3 *SBsp3::Insert(STriangle *tr, SMesh *instead) {
if(!this) {
if(instead) {
if(instead->flipNormal) {
instead->atLeastOneDiscarded = true;
} else {
instead->AddTriangle(tr->meta, tr->a, tr->b, tr->c);
}
return NULL;
}
// Brand new node; so allocate for it, and fill us in.
SBsp3 *r = Alloc();
r->n = (tr->Normal()).WithMagnitude(1);
r->d = (tr->a).Dot(r->n);
r->tri = *tr;
return r;
}
double dt[3] = { (tr->a).Dot(n), (tr->b).Dot(n), (tr->c).Dot(n) };
int inc = 0, posc = 0, negc = 0;
bool isPos[3], isNeg[3], isOn[3];
ZERO(&isPos); ZERO(&isNeg); ZERO(&isOn);
// Count vertices in the plane
for(int i = 0; i < 3; i++) {
if(fabs(dt[i] - d) < LENGTH_EPS) {
inc++;
isOn[i] = true;
} else if(dt[i] > d) {
posc++;
isPos[i] = true;
} else {
negc++;
isNeg[i] = true;
}
}
// All vertices in-plane
if(inc == 3) {
InsertHow(COPLANAR, tr, instead);
return this;
}
// No split required
if(posc == 0 || negc == 0) {
if(inc == 2) {
Vector a, b;
if (!isOn[0]) { a = tr->b; b = tr->c; }
else if(!isOn[1]) { a = tr->c; b = tr->a; }
else if(!isOn[2]) { a = tr->a; b = tr->b; }
else oops();
if(!instead) {
SEdge se = SEdge::From(a, b);
edges = edges->InsertEdge(&se, n, tr->Normal());
}
}
if(posc > 0) {
InsertHow(POS, tr, instead);
} else {
InsertHow(NEG, tr, instead);
}
return this;
}
// The polygon must be split into two pieces, one above, one below.
Vector a, b, c;
if(posc == 1 && negc == 1 && inc == 1) {
bool bpos;
// Standardize so that a is on the plane
if (isOn[0]) { a = tr->a; b = tr->b; c = tr->c; bpos = isPos[1];
} else if(isOn[1]) { a = tr->b; b = tr->c; c = tr->a; bpos = isPos[2];
} else if(isOn[2]) { a = tr->c; b = tr->a; c = tr->b; bpos = isPos[0];
} else oops();
Vector bPc = IntersectionWith(b, c);
STriangle btri = STriangle::From(tr->meta, a, b, bPc);
STriangle ctri = STriangle::From(tr->meta, c, a, bPc);
if(bpos) {
InsertHow(POS, &btri, instead);
InsertHow(NEG, &ctri, instead);
} else {
InsertHow(POS, &ctri, instead);
InsertHow(NEG, &btri, instead);
}
if(!instead) {
SEdge se = SEdge::From(a, bPc);
edges = edges->InsertEdge(&se, n, tr->Normal());
}
return this;
}
if(posc == 2 && negc == 1) {
// Standardize so that a is on one side, and b and c are on the other.
if (isNeg[0]) { a = tr->a; b = tr->b; c = tr->c;
} else if(isNeg[1]) { a = tr->b; b = tr->c; c = tr->a;
} else if(isNeg[2]) { a = tr->c; b = tr->a; c = tr->b;
} else oops();
} else if(posc == 1 && negc == 2) {
if (isPos[0]) { a = tr->a; b = tr->b; c = tr->c;
} else if(isPos[1]) { a = tr->b; b = tr->c; c = tr->a;
} else if(isPos[2]) { a = tr->c; b = tr->a; c = tr->b;
} else oops();
} else oops();
Vector aPb = IntersectionWith(a, b);
Vector cPa = IntersectionWith(c, a);
STriangle alone = STriangle::From(tr->meta, a, aPb, cPa);
Vector quad[4] = { aPb, b, c, cPa };
if(posc == 2 && negc == 1) {
InsertConvexHow(POS, tr->meta, quad, 4, instead);
InsertHow(NEG, &alone, instead);
} else {
InsertConvexHow(NEG, tr->meta, quad, 4, instead);
InsertHow(POS, &alone, instead);
}
if(!instead) {
SEdge se = SEdge::From(aPb, cPa);
edges = edges->InsertEdge(&se, n, alone.Normal());
}
return this;
}
void SBsp3::GenerateInPaintOrder(SMesh *m) {
if(!this) return;
// Doesn't matter which branch we take if the normal has zero z
// component, so don't need a separate case for that.
if(n.z < 0) {
pos->GenerateInPaintOrder(m);
} else {
neg->GenerateInPaintOrder(m);
}
SBsp3 *flip = this;
while(flip) {
m->AddTriangle(&(flip->tri));
flip = flip->more;
}
if(n.z < 0) {
neg->GenerateInPaintOrder(m);
} else {
pos->GenerateInPaintOrder(m);
}
}
void SBsp3::DebugDraw(void) {
if(!this) return;
pos->DebugDraw();
Vector norm = tri.Normal();
glNormal3d(norm.x, norm.y, norm.z);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glBegin(GL_TRIANGLES);
glxVertex3v(tri.a);
glxVertex3v(tri.b);
glxVertex3v(tri.c);
glEnd();
glDisable(GL_LIGHTING);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glxDepthRangeOffset(2);
glBegin(GL_TRIANGLES);
glxVertex3v(tri.a);
glxVertex3v(tri.b);
glxVertex3v(tri.c);
glEnd();
glDisable(GL_LIGHTING);
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
glPointSize(10);
glxDepthRangeOffset(2);
glBegin(GL_TRIANGLES);
glxVertex3v(tri.a);
glxVertex3v(tri.b);
glxVertex3v(tri.c);
glEnd();
glxDepthRangeOffset(0);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
more->DebugDraw();
neg->DebugDraw();
edges->DebugDraw(n, d);
}
/////////////////////////////////
Vector SBsp2::IntersectionWith(Vector a, Vector b) {
double da = a.Dot(no) - d;
double db = b.Dot(no) - d;
if(da*db > 0) oops();
double dab = (db - da);
return (a.ScaledBy(db/dab)).Plus(b.ScaledBy(-da/dab));
}
SBsp2 *SBsp2::InsertEdge(SEdge *nedge, Vector nnp, Vector out) {
if(!this) {
// Brand new node; so allocate for it, and fill us in.
SBsp2 *r = Alloc();
r->np = nnp;
r->no = ((r->np).Cross((nedge->b).Minus(nedge->a))).WithMagnitude(1);
if(out.Dot(r->no) < 0) {
r->no = (r->no).ScaledBy(-1);
}
r->d = (nedge->a).Dot(r->no);
r->edge = *nedge;
return r;
}
double dt[2] = { (nedge->a).Dot(no), (nedge->b).Dot(no) };
bool isPos[2], isNeg[2], isOn[2];
ZERO(&isPos); ZERO(&isNeg); ZERO(&isOn);
for(int i = 0; i < 2; i++) {
if(fabs(dt[i] - d) < LENGTH_EPS) {
isOn[i] = true;
} else if(dt[i] > d) {
isPos[i] = true;
} else {
isNeg[i] = true;
}
}
if((isPos[0] && isPos[1])||(isPos[0] && isOn[1])||(isOn[0] && isPos[1])) {
pos = pos->InsertEdge(nedge, nnp, out);
return this;
}
if((isNeg[0] && isNeg[1])||(isNeg[0] && isOn[1])||(isOn[0] && isNeg[1])) {
neg = neg->InsertEdge(nedge, nnp, out);
return this;
}
if(isOn[0] && isOn[1]) {
SBsp2 *m = Alloc();
m->np = nnp;
m->no = ((m->np).Cross((nedge->b).Minus(nedge->a))).WithMagnitude(1);
if(out.Dot(m->no) < 0) {
m->no = (m->no).ScaledBy(-1);
}
m->d = (nedge->a).Dot(m->no);
m->edge = *nedge;
m->more = more;
more = m;
return this;
}
if((isPos[0] && isNeg[1]) || (isNeg[0] && isPos[1])) {
Vector aPb = IntersectionWith(nedge->a, nedge->b);
SEdge ea = SEdge::From(nedge->a, aPb);
SEdge eb = SEdge::From(aPb, nedge->b);
if(isPos[0]) {
pos = pos->InsertEdge(&ea, nnp, out);
neg = neg->InsertEdge(&eb, nnp, out);
} else {
neg = neg->InsertEdge(&ea, nnp, out);
pos = pos->InsertEdge(&eb, nnp, out);
}
return this;
}
oops();
}
void SBsp2::InsertTriangleHow(int how, STriangle *tr, SMesh *m, SBsp3 *bsp3) {
switch(how) {
case POS:
if(pos) {
pos->InsertTriangle(tr, m, bsp3);
} else {
bsp3->InsertInPlane(true, tr, m);
}
break;
case NEG:
if(neg) {
neg->InsertTriangle(tr, m, bsp3);
} else {
bsp3->InsertInPlane(false, tr, m);
}
break;
default: oops();
}
}
void SBsp2::InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3) {
double dt[3] = { (tr->a).Dot(no), (tr->b).Dot(no), (tr->c).Dot(no) };
bool isPos[3], isNeg[3], isOn[3];
int inc = 0, posc = 0, negc = 0;
ZERO(&isPos); ZERO(&isNeg); ZERO(&isOn);
for(int i = 0; i < 3; i++) {
if(fabs(dt[i] - d) < LENGTH_EPS) {
isOn[i] = true;
inc++;
} else if(dt[i] > d) {
isPos[i] = true;
posc++;
} else {
isNeg[i] = true;
negc++;
}
}
if(inc == 3) {
// All vertices on-line; so it's a degenerate triangle, to ignore.
return;
}
// No split required
if(posc == 0 || negc == 0) {
if(posc > 0) {
InsertTriangleHow(POS, tr, m, bsp3);
} else {
InsertTriangleHow(NEG, tr, m, bsp3);
}
return;
}
// The polygon must be split into two pieces, one above, one below.
Vector a, b, c;
if(posc == 1 && negc == 1 && inc == 1) {
bool bpos;
// Standardize so that a is on the plane
if (isOn[0]) { a = tr->a; b = tr->b; c = tr->c; bpos = isPos[1];
} else if(isOn[1]) { a = tr->b; b = tr->c; c = tr->a; bpos = isPos[2];
} else if(isOn[2]) { a = tr->c; b = tr->a; c = tr->b; bpos = isPos[0];
} else oops();
Vector bPc = IntersectionWith(b, c);
STriangle btri = STriangle::From(tr->meta, a, b, bPc);
STriangle ctri = STriangle::From(tr->meta, c, a, bPc);
if(bpos) {
InsertTriangleHow(POS, &btri, m, bsp3);
InsertTriangleHow(NEG, &ctri, m, bsp3);
} else {
InsertTriangleHow(POS, &ctri, m, bsp3);
InsertTriangleHow(NEG, &btri, m, bsp3);
}
return;
}
if(posc == 2 && negc == 1) {
// Standardize so that a is on one side, and b and c are on the other.
if (isNeg[0]) { a = tr->a; b = tr->b; c = tr->c;
} else if(isNeg[1]) { a = tr->b; b = tr->c; c = tr->a;
} else if(isNeg[2]) { a = tr->c; b = tr->a; c = tr->b;
} else oops();
} else if(posc == 1 && negc == 2) {
if (isPos[0]) { a = tr->a; b = tr->b; c = tr->c;
} else if(isPos[1]) { a = tr->b; b = tr->c; c = tr->a;
} else if(isPos[2]) { a = tr->c; b = tr->a; c = tr->b;
} else oops();
} else oops();
Vector aPb = IntersectionWith(a, b);
Vector cPa = IntersectionWith(c, a);
STriangle alone = STriangle::From(tr->meta, a, aPb, cPa);
STriangle quad1 = STriangle::From(tr->meta, aPb, b, c );
STriangle quad2 = STriangle::From(tr->meta, aPb, c, cPa);
if(posc == 2 && negc == 1) {
InsertTriangleHow(POS, &quad1, m, bsp3);
InsertTriangleHow(POS, &quad2, m, bsp3);
InsertTriangleHow(NEG, &alone, m, bsp3);
} else {
InsertTriangleHow(NEG, &quad1, m, bsp3);
InsertTriangleHow(NEG, &quad2, m, bsp3);
InsertTriangleHow(POS, &alone, m, bsp3);
}
return;
}
void SBsp2::DebugDraw(Vector n, double d) {
if(!this) return;
if(fabs((edge.a).Dot(n) - d) > LENGTH_EPS) oops();
if(fabs((edge.b).Dot(n) - d) > LENGTH_EPS) oops();
glLineWidth(10);
glBegin(GL_LINES);
glxVertex3v(edge.a);
glxVertex3v(edge.b);
glEnd();
pos->DebugDraw(n, d);
neg->DebugDraw(n, d);
more->DebugDraw(n, d);
glLineWidth(1);
}

View File

@ -1,395 +0,0 @@
//-----------------------------------------------------------------------------
// The clipboard that gets manipulated when the user selects Edit -> Cut,
// Copy, Paste, etc.; may contain entities only, not constraints.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
void SolveSpace::Clipboard::Clear(void) {
c.Clear();
r.Clear();
}
hEntity SolveSpace::Clipboard::NewEntityFor(hEntity he) {
ClipboardRequest *cr;
for(cr = r.First(); cr; cr = r.NextAfter(cr)) {
if(cr->oldEnt.v == he.v) {
return cr->newReq.entity(0);
}
for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) {
if(cr->oldPointEnt[i].v == he.v) {
return cr->newReq.entity(1+i);
}
}
}
return Entity::NO_ENTITY;
}
bool SolveSpace::Clipboard::ContainsEntity(hEntity he) {
hEntity hen = NewEntityFor(he);
if(hen.v) {
return true;
} else {
return false;
}
}
void GraphicsWindow::DeleteSelection(void) {
SK.request.ClearTags();
SK.constraint.ClearTags();
List<Selection> *ls = &(selection);
for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {
hRequest r = { 0 };
if(s->entity.v && s->entity.isFromRequest()) {
r = s->entity.request();
}
if(r.v && !r.IsFromReferences()) {
SK.request.Tag(r, 1);
}
if(s->constraint.v) {
SK.constraint.Tag(s->constraint, 1);
}
}
SK.constraint.RemoveTagged();
// Note that this regenerates and clears the selection, to avoid
// lingering references to the just-deleted items.
DeleteTaggedRequests();
}
void GraphicsWindow::CopySelection(void) {
SS.clipboard.Clear();
Entity *wrkpl = SK.GetEntity(ActiveWorkplane());
Entity *wrkpln = SK.GetEntity(wrkpl->normal);
Vector u = wrkpln->NormalU(),
v = wrkpln->NormalV(),
n = wrkpln->NormalN(),
p = SK.GetEntity(wrkpl->point[0])->PointGetNum();
List<Selection> *ls = &(selection);
for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {
if(!s->entity.v) continue;
// Work only on entities that have requests that will generate them.
Entity *e = SK.GetEntity(s->entity);
bool hasDistance;
int req, pts;
if(!EntReqTable::GetEntityInfo(e->type, e->extraPoints,
&req, &pts, NULL, &hasDistance))
{
continue;
}
if(req == Request::WORKPLANE) continue;
ClipboardRequest cr;
ZERO(&cr);
cr.type = req;
cr.extraPoints = e->extraPoints;
cr.style = e->style;
cr.str.strcpy( e->str.str);
cr.font.strcpy( e->font.str);
cr.construction = e->construction;
for(int i = 0; i < pts; i++) {
Vector pt = SK.GetEntity(e->point[i])->PointGetNum();
pt = pt.Minus(p);
pt = pt.DotInToCsys(u, v, n);
cr.point[i] = pt;
}
if(hasDistance) {
cr.distance = SK.GetEntity(e->distance)->DistanceGetNum();
}
cr.oldEnt = e->h;
for(int i = 0; i < pts; i++) {
cr.oldPointEnt[i] = e->point[i];
}
SS.clipboard.r.Add(&cr);
}
Constraint *c;
for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) {
if(c->type == Constraint::POINTS_COINCIDENT) {
if(!SS.clipboard.ContainsEntity(c->ptA)) continue;
if(!SS.clipboard.ContainsEntity(c->ptB)) continue;
} else {
continue;
}
SS.clipboard.c.Add(c);
}
}
void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
Entity *wrkpl = SK.GetEntity(ActiveWorkplane());
Entity *wrkpln = SK.GetEntity(wrkpl->normal);
Vector u = wrkpln->NormalU(),
v = wrkpln->NormalV(),
n = wrkpln->NormalN(),
p = SK.GetEntity(wrkpl->point[0])->PointGetNum();
ClipboardRequest *cr;
for(cr = SS.clipboard.r.First(); cr; cr = SS.clipboard.r.NextAfter(cr)) {
hRequest hr = AddRequest(cr->type, false);
Request *r = SK.GetRequest(hr);
r->extraPoints = cr->extraPoints;
r->style = cr->style;
r->str.strcpy( cr->str.str);
r->font.strcpy( cr->font.str);
r->construction = cr->construction;
// Need to regen to get the right number of points, if extraPoints
// changed.
SS.GenerateAll(-1, -1);
SS.MarkGroupDirty(r->group);
bool hasDistance;
int i, pts;
EntReqTable::GetRequestInfo(r->type, r->extraPoints,
NULL, &pts, NULL, &hasDistance);
for(i = 0; i < pts; i++) {
Vector pt = cr->point[i];
// We need the reflection to occur within the workplane; it may
// otherwise correspond to just a rotation as projected.
if(scale < 0) {
pt.x *= -1;
}
// Likewise the scale, which could otherwise take us out of the
// workplane.
pt = pt.ScaledBy(fabs(scale));
pt = pt.ScaleOutOfCsys(u, v, Vector::From(0, 0, 0));
pt = pt.Plus(p);
pt = pt.RotatedAbout(n, theta);
pt = pt.Plus(trans);
SK.GetEntity(hr.entity(i+1))->PointForceTo(pt);
}
if(hasDistance) {
SK.GetEntity(hr.entity(64))->DistanceForceTo(
cr->distance*fabs(scale));
}
cr->newReq = hr;
MakeSelected(hr.entity(0));
for(i = 0; i < pts; i++) {
MakeSelected(hr.entity(i+1));
}
}
Constraint *c;
for(c = SS.clipboard.c.First(); c; c = SS.clipboard.c.NextAfter(c)) {
if(c->type == Constraint::POINTS_COINCIDENT) {
Constraint::ConstrainCoincident(SS.clipboard.NewEntityFor(c->ptA),
SS.clipboard.NewEntityFor(c->ptB));
}
}
SS.later.generateAll = true;
}
void GraphicsWindow::MenuClipboard(int id) {
if(id != MNU_DELETE && !SS.GW.LockedInWorkplane()) {
Error("Cut, paste, and copy work only in a workplane.\n\n"
"Select one with Sketch -> In Workplane.");
return;
}
switch(id) {
case MNU_PASTE: {
SS.UndoRemember();
Vector trans = SS.GW.projRight.ScaledBy(80/SS.GW.scale).Plus(
SS.GW.projUp .ScaledBy(40/SS.GW.scale));
SS.GW.ClearSelection();
SS.GW.PasteClipboard(trans, 0, 1);
break;
}
case MNU_PASTE_TRANSFORM: {
if(SS.clipboard.r.n == 0) {
Error("Clipboard is empty; nothing to paste.");
break;
}
Entity *wrkpl = SK.GetEntity(SS.GW.ActiveWorkplane());
Vector p = SK.GetEntity(wrkpl->point[0])->PointGetNum();
SS.TW.shown.paste.times = 1;
SS.TW.shown.paste.trans = Vector::From(0, 0, 0);
SS.TW.shown.paste.theta = 0;
SS.TW.shown.paste.origin = p;
SS.TW.shown.paste.scale = 1;
SS.TW.GoToScreen(TextWindow::SCREEN_PASTE_TRANSFORMED);
SS.GW.ForceTextWindowShown();
SS.later.showTW = true;
break;
}
case MNU_COPY:
SS.GW.CopySelection();
SS.GW.ClearSelection();
break;
case MNU_CUT:
SS.UndoRemember();
SS.GW.CopySelection();
SS.GW.DeleteSelection();
break;
case MNU_DELETE:
SS.UndoRemember();
SS.GW.DeleteSelection();
break;
default: oops();
}
}
bool TextWindow::EditControlDoneForPaste(char *s) {
Expr *e;
switch(edit.meaning) {
case EDIT_PASTE_TIMES_REPEATED: {
e = Expr::From(s, true);
if(!e) break;
int v = (int)e->Eval();
if(v > 0) {
shown.paste.times = v;
} else {
Error("Number of copies to paste must be at least one.");
}
break;
}
case EDIT_PASTE_ANGLE:
e = Expr::From(s, true);
if(!e) break;
shown.paste.theta = WRAP_SYMMETRIC((e->Eval())*PI/180, 2*PI);
break;
case EDIT_PASTE_SCALE: {
e = Expr::From(s, true);
double v = e->Eval();
if(fabs(v) > 1e-6) {
shown.paste.scale = v;
} else {
Error("Scale cannot be zero.");
}
break;
}
default:
return false;
}
return true;
}
void TextWindow::ScreenChangePasteTransformed(int link, DWORD v) {
char str[300];
switch(link) {
case 't':
sprintf(str, "%d", SS.TW.shown.paste.times);
SS.TW.ShowEditControl(10, 13, str);
SS.TW.edit.meaning = EDIT_PASTE_TIMES_REPEATED;
break;
case 'r':
sprintf(str, "%.3f", SS.TW.shown.paste.theta*180/PI);
SS.TW.ShowEditControl(12, 13, str);
SS.TW.edit.meaning = EDIT_PASTE_ANGLE;
break;
case 's':
sprintf(str, "%.3f", SS.TW.shown.paste.scale);
SS.TW.ShowEditControl(18, 13, str);
SS.TW.edit.meaning = EDIT_PASTE_SCALE;
break;
}
}
void TextWindow::ScreenPasteTransformed(int link, DWORD v) {
SS.GW.GroupSelection();
switch(link) {
case 'o':
if(SS.GW.gs.points == 1 && SS.GW.gs.n == 1) {
Entity *e = SK.GetEntity(SS.GW.gs.point[0]);
SS.TW.shown.paste.origin = e->PointGetNum();
} else {
Error("Select one point to define origin of rotation.");
}
SS.GW.ClearSelection();
break;
case 't':
if(SS.GW.gs.points == 2 && SS.GW.gs.n == 2) {
Entity *pa = SK.GetEntity(SS.GW.gs.point[0]),
*pb = SK.GetEntity(SS.GW.gs.point[1]);
SS.TW.shown.paste.trans =
(pb->PointGetNum()).Minus(pa->PointGetNum());
} else {
Error("Select two points to define translation vector.");
}
SS.GW.ClearSelection();
break;
case 'g': {
if(fabs(SS.TW.shown.paste.theta) < LENGTH_EPS &&
SS.TW.shown.paste.trans.Magnitude() < LENGTH_EPS &&
SS.TW.shown.paste.times != 1)
{
Message("Transformation is identity. So all copies will be "
"exactly on top of each other.");
}
if(SS.TW.shown.paste.times*SS.clipboard.r.n > 100) {
Error("Too many items to paste; split this into smaller "
"pastes.");
break;
}
if(!SS.GW.LockedInWorkplane()) {
Error("No workplane active.");
break;
}
Entity *wrkpl = SK.GetEntity(SS.GW.ActiveWorkplane());
Entity *wrkpln = SK.GetEntity(wrkpl->normal);
Vector wn = wrkpln->NormalN();
SS.UndoRemember();
SS.GW.ClearSelection();
for(int i = 0; i < SS.TW.shown.paste.times; i++) {
Vector trans = SS.TW.shown.paste.trans.ScaledBy(i+1),
origin = SS.TW.shown.paste.origin;
double theta = SS.TW.shown.paste.theta*(i+1);
// desired transformation is Q*(p - o) + o + t =
// Q*p - Q*o + o + t = Q*p + (t + o - Q*o)
Vector t = trans.Plus(
origin).Minus(
origin.RotatedAbout(wn, theta));
SS.GW.PasteClipboard(t, theta, SS.TW.shown.paste.scale);
}
SS.TW.GoToScreen(SCREEN_LIST_OF_GROUPS);
SS.later.showTW = true;
break;
}
}
}
void TextWindow::ShowPasteTransformed(void) {
Printf(true, "%FtPASTE TRANSFORMED%E");
Printf(true, "%Ba %Ftrepeat%E %d time%s %Fl%Lt%f[change]%E",
shown.paste.times, (shown.paste.times == 1) ? "" : "s",
&ScreenChangePasteTransformed);
Printf(false, "%Bd %Ftrotate%E %@ degrees %Fl%Lr%f[change]%E",
shown.paste.theta*180/PI,
&ScreenChangePasteTransformed);
Printf(false, "%Ba %Ftabout pt%E (%s, %s, %s) %Fl%Lo%f[use selected]%E",
SS.MmToString(shown.paste.origin.x),
SS.MmToString(shown.paste.origin.y),
SS.MmToString(shown.paste.origin.z),
&ScreenPasteTransformed);
Printf(false, "%Bd %Fttranslate%E (%s, %s, %s) %Fl%Lt%f[use selected]%E",
SS.MmToString(shown.paste.trans.x),
SS.MmToString(shown.paste.trans.y),
SS.MmToString(shown.paste.trans.z),
&ScreenPasteTransformed);
Printf(false, "%Ba %Ftscale%E %@ %Fl%Ls%f[change]%E",
shown.paste.scale,
&ScreenChangePasteTransformed);
Printf(true, " %Fl%Lg%fpaste transformed now%E", &ScreenPasteTransformed);
Printf(true, "(or %Fl%Ll%fcancel operation%E)", &ScreenHome);
}

View File

@ -1,433 +0,0 @@
//-----------------------------------------------------------------------------
// For the configuration screen, setup items that are not specific to the
// file being edited right now.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
void TextWindow::ScreenChangeLightDirection(int link, DWORD v) {
char str[1024];
sprintf(str, "%.2f, %.2f, %.2f", CO(SS.lightDir[v]));
SS.TW.ShowEditControl(29+2*v, 8, str);
SS.TW.edit.meaning = EDIT_LIGHT_DIRECTION;
SS.TW.edit.i = v;
}
void TextWindow::ScreenChangeLightIntensity(int link, DWORD v) {
char str[1024];
sprintf(str, "%.2f", SS.lightIntensity[v]);
SS.TW.ShowEditControl(29+2*v, 31, str);
SS.TW.edit.meaning = EDIT_LIGHT_INTENSITY;
SS.TW.edit.i = v;
}
void TextWindow::ScreenChangeColor(int link, DWORD v) {
SS.TW.ShowEditControlWithColorPicker(9+2*v, 13, SS.modelColor[v]);
SS.TW.edit.meaning = EDIT_COLOR;
SS.TW.edit.i = v;
}
void TextWindow::ScreenChangeChordTolerance(int link, DWORD v) {
char str[1024];
sprintf(str, "%.2f", SS.chordTol);
SS.TW.ShowEditControl(37, 3, str);
SS.TW.edit.meaning = EDIT_CHORD_TOLERANCE;
}
void TextWindow::ScreenChangeMaxSegments(int link, DWORD v) {
char str[1024];
sprintf(str, "%d", SS.maxSegments);
SS.TW.ShowEditControl(41, 3, str);
SS.TW.edit.meaning = EDIT_MAX_SEGMENTS;
}
void TextWindow::ScreenChangeCameraTangent(int link, DWORD v) {
char str[1024];
sprintf(str, "%.3f", 1000*SS.cameraTangent);
SS.TW.ShowEditControl(47, 3, str);
SS.TW.edit.meaning = EDIT_CAMERA_TANGENT;
}
void TextWindow::ScreenChangeGridSpacing(int link, DWORD v) {
SS.TW.ShowEditControl(51, 3, SS.MmToString(SS.gridSpacing));
SS.TW.edit.meaning = EDIT_GRID_SPACING;
}
void TextWindow::ScreenChangeDigitsAfterDecimal(int link, DWORD v) {
char buf[128];
sprintf(buf, "%d", SS.UnitDigitsAfterDecimal());
SS.TW.ShowEditControl(55, 3, buf);
SS.TW.edit.meaning = EDIT_DIGITS_AFTER_DECIMAL;
}
void TextWindow::ScreenChangeExportScale(int link, DWORD v) {
char str[1024];
sprintf(str, "%.3f", (double)SS.exportScale);
SS.TW.ShowEditControl(61, 5, str);
SS.TW.edit.meaning = EDIT_EXPORT_SCALE;
}
void TextWindow::ScreenChangeExportOffset(int link, DWORD v) {
SS.TW.ShowEditControl(65, 3, SS.MmToString(SS.exportOffset));
SS.TW.edit.meaning = EDIT_EXPORT_OFFSET;
}
void TextWindow::ScreenChangeFixExportColors(int link, DWORD v) {
SS.fixExportColors = !SS.fixExportColors;
}
void TextWindow::ScreenChangeBackFaces(int link, DWORD v) {
SS.drawBackFaces = !SS.drawBackFaces;
InvalidateGraphics();
}
void TextWindow::ScreenChangeCheckClosedContour(int link, DWORD v) {
SS.checkClosedContour = !SS.checkClosedContour;
InvalidateGraphics();
}
void TextWindow::ScreenChangeShadedTriangles(int link, DWORD v) {
SS.exportShadedTriangles = !SS.exportShadedTriangles;
InvalidateGraphics();
}
void TextWindow::ScreenChangePwlCurves(int link, DWORD v) {
SS.exportPwlCurves = !SS.exportPwlCurves;
InvalidateGraphics();
}
void TextWindow::ScreenChangeCanvasSizeAuto(int link, DWORD v) {
if(link == 't') {
SS.exportCanvasSizeAuto = true;
} else {
SS.exportCanvasSizeAuto = false;
}
InvalidateGraphics();
}
void TextWindow::ScreenChangeCanvasSize(int link, DWORD v) {
double d;
switch(v) {
case 0: d = SS.exportMargin.left; break;
case 1: d = SS.exportMargin.right; break;
case 2: d = SS.exportMargin.bottom; break;
case 3: d = SS.exportMargin.top; break;
case 10: d = SS.exportCanvas.width; break;
case 11: d = SS.exportCanvas.height; break;
case 12: d = SS.exportCanvas.dx; break;
case 13: d = SS.exportCanvas.dy; break;
default: return;
}
int row = 81, col;
if(v < 10) {
row += v*2;
col = 11;
} else {
row += (v - 10)*2;
col = 13;
}
SS.TW.ShowEditControl(row, col, SS.MmToString(d));
SS.TW.edit.meaning = EDIT_CANVAS_SIZE;
SS.TW.edit.i = v;
}
void TextWindow::ScreenChangeGCodeParameter(int link, DWORD v) {
char buf[1024] = "";
int row = 93;
switch(link) {
case 'd':
SS.TW.edit.meaning = EDIT_G_CODE_DEPTH;
strcpy(buf, SS.MmToString(SS.gCode.depth));
row += 0;
break;
case 's':
SS.TW.edit.meaning = EDIT_G_CODE_PASSES;
sprintf(buf, "%d", SS.gCode.passes);
row += 2;
break;
case 'F':
SS.TW.edit.meaning = EDIT_G_CODE_FEED;
strcpy(buf, SS.MmToString(SS.gCode.feed));
row += 4;
break;
case 'P':
SS.TW.edit.meaning = EDIT_G_CODE_PLUNGE_FEED;
strcpy(buf, SS.MmToString(SS.gCode.plungeFeed));
row += 6;
break;
}
SS.TW.ShowEditControl(row, 14, buf);
}
void TextWindow::ShowConfiguration(void) {
int i;
Printf(true, "%Ft user color (r, g, b)");
for(i = 0; i < SS.MODEL_COLORS; i++) {
Printf(false, "%Bp #%d: %Bp %Bp (%@, %@, %@) %f%D%Ll%Fl[change]%E",
(i & 1) ? 'd' : 'a',
i, 0x80000000 | SS.modelColor[i],
(i & 1) ? 'd' : 'a',
REDf(SS.modelColor[i]),
GREENf(SS.modelColor[i]),
BLUEf(SS.modelColor[i]),
&ScreenChangeColor, i);
}
Printf(false, "");
Printf(false, "%Ft light direction intensity");
for(i = 0; i < 2; i++) {
Printf(false, "%Bp #%d (%2,%2,%2)%Fl%D%f%Ll[c]%E "
"%2 %Fl%D%f%Ll[c]%E",
(i & 1) ? 'd' : 'a', i,
CO(SS.lightDir[i]), i, &ScreenChangeLightDirection,
SS.lightIntensity[i], i, &ScreenChangeLightIntensity);
}
Printf(false, "");
Printf(false, "%Ft chord tolerance (in screen pixels)%E");
Printf(false, "%Ba %@ %Fl%Ll%f%D[change]%E; now %d triangles",
SS.chordTol,
&ScreenChangeChordTolerance, 0,
SK.GetGroup(SS.GW.activeGroup)->displayMesh.l.n);
Printf(false, "%Ft max piecewise linear segments%E");
Printf(false, "%Ba %d %Fl%Ll%f[change]%E",
SS.maxSegments,
&ScreenChangeMaxSegments);
Printf(false, "");
Printf(false, "%Ft perspective factor (0 for parallel)%E");
Printf(false, "%Ba %# %Fl%Ll%f%D[change]%E",
SS.cameraTangent*1000,
&ScreenChangeCameraTangent, 0);
Printf(false, "%Ft snap grid spacing%E");
Printf(false, "%Ba %s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.gridSpacing),
&ScreenChangeGridSpacing, 0);
Printf(false, "%Ft digits after decimal point to show%E");
Printf(false, "%Ba %d %Fl%Ll%f%D[change]%E (e.g. '%s')",
SS.UnitDigitsAfterDecimal(),
&ScreenChangeDigitsAfterDecimal, 0,
SS.MmToString(SS.StringToMm("1.23456789")));
Printf(false, "");
Printf(false, "%Ft export scale factor (1:1=mm, 1:25.4=inch)");
Printf(false, "%Ba 1:%# %Fl%Ll%f%D[change]%E",
(double)SS.exportScale,
&ScreenChangeExportScale, 0);
Printf(false, "%Ft cutter radius offset (0=no offset) ");
Printf(false, "%Ba %s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportOffset),
&ScreenChangeExportOffset, 0);
Printf(false, "");
Printf(false, " %Fd%f%Ll%c export shaded 2d triangles%E",
&ScreenChangeShadedTriangles,
SS.exportShadedTriangles ? CHECK_TRUE : CHECK_FALSE);
if(fabs(SS.exportOffset) > LENGTH_EPS) {
Printf(false, " %Fd%c curves as piecewise linear%E "
"(since cutter radius is not zero)", CHECK_TRUE);
} else {
Printf(false, " %Fd%f%Ll%c export curves as piecewise linear%E",
&ScreenChangePwlCurves,
SS.exportPwlCurves ? CHECK_TRUE : CHECK_FALSE);
}
Printf(false, " %Fd%f%Ll%c fix white exported lines%E",
&ScreenChangeFixExportColors,
SS.fixExportColors ? CHECK_TRUE : CHECK_FALSE);
Printf(false, "");
Printf(false, "%Ft export canvas size: "
"%f%Fd%Lf%c fixed%E "
"%f%Fd%Lt%c auto%E",
&ScreenChangeCanvasSizeAuto,
!SS.exportCanvasSizeAuto ? RADIO_TRUE : RADIO_FALSE,
&ScreenChangeCanvasSizeAuto,
SS.exportCanvasSizeAuto ? RADIO_TRUE : RADIO_FALSE);
if(SS.exportCanvasSizeAuto) {
Printf(false, "%Ft (by margins around exported geometry)");
Printf(false, "%Ba%Ft left: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportMargin.left), &ScreenChangeCanvasSize, 0);
Printf(false, "%Bd%Ft right: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportMargin.right), &ScreenChangeCanvasSize, 1);
Printf(false, "%Ba%Ft bottom: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportMargin.bottom), &ScreenChangeCanvasSize, 2);
Printf(false, "%Bd%Ft top: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportMargin.top), &ScreenChangeCanvasSize, 3);
} else {
Printf(false, "%Ft (by absolute dimensions and offsets)");
Printf(false, "%Ba%Ft width: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportCanvas.width), &ScreenChangeCanvasSize, 10);
Printf(false, "%Bd%Ft height: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportCanvas.height), &ScreenChangeCanvasSize, 11);
Printf(false, "%Ba%Ft offset x: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportCanvas.dx), &ScreenChangeCanvasSize, 12);
Printf(false, "%Bd%Ft offset y: %Fd%s %Fl%Ll%f%D[change]%E",
SS.MmToString(SS.exportCanvas.dy), &ScreenChangeCanvasSize, 13);
}
Printf(false, "");
Printf(false, "%Ft exported g code parameters");
Printf(false, "%Ba%Ft depth: %Fd%s %Fl%Ld%f[change]%E",
SS.MmToString(SS.gCode.depth), &ScreenChangeGCodeParameter);
Printf(false, "%Bd%Ft passes: %Fd%d %Fl%Ls%f[change]%E",
SS.gCode.passes, &ScreenChangeGCodeParameter);
Printf(false, "%Ba%Ft feed: %Fd%s %Fl%LF%f[change]%E",
SS.MmToString(SS.gCode.feed), &ScreenChangeGCodeParameter);
Printf(false, "%Bd%Ft plunge fd: %Fd%s %Fl%LP%f[change]%E",
SS.MmToString(SS.gCode.plungeFeed), &ScreenChangeGCodeParameter);
Printf(false, "");
Printf(false, " %Fd%f%Ll%c draw triangle back faces in red%E",
&ScreenChangeBackFaces,
SS.drawBackFaces ? CHECK_TRUE : CHECK_FALSE);
Printf(false, " %Fd%f%Ll%c check sketch for closed contour%E",
&ScreenChangeCheckClosedContour,
SS.checkClosedContour ? CHECK_TRUE : CHECK_FALSE);
Printf(false, "");
Printf(false, " %Ftgl vendor %E%s", glGetString(GL_VENDOR));
Printf(false, " %Ft renderer %E%s", glGetString(GL_RENDERER));
Printf(false, " %Ft version %E%s", glGetString(GL_VERSION));
}
bool TextWindow::EditControlDoneForConfiguration(char *s) {
switch(edit.meaning) {
case EDIT_LIGHT_INTENSITY:
SS.lightIntensity[edit.i] = min(1, max(0, atof(s)));
InvalidateGraphics();
break;
case EDIT_LIGHT_DIRECTION: {
double x, y, z;
if(sscanf(s, "%lf, %lf, %lf", &x, &y, &z)==3) {
SS.lightDir[edit.i] = Vector::From(x, y, z);
} else {
Error("Bad format: specify coordinates as x, y, z");
}
InvalidateGraphics();
break;
}
case EDIT_COLOR: {
Vector rgb;
if(sscanf(s, "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) {
rgb = rgb.ClampWithin(0, 1);
SS.modelColor[edit.i] = RGBf(rgb.x, rgb.y, rgb.z);
} else {
Error("Bad format: specify color as r, g, b");
}
break;
}
case EDIT_CHORD_TOLERANCE: {
SS.chordTol = min(10, max(0.1, atof(s)));
SS.GenerateAll(0, INT_MAX);
break;
}
case EDIT_MAX_SEGMENTS: {
SS.maxSegments = min(1000, max(7, atoi(s)));
SS.GenerateAll(0, INT_MAX);
break;
}
case EDIT_CAMERA_TANGENT: {
SS.cameraTangent = (min(2, max(0, atof(s))))/1000.0;
if(!SS.usePerspectiveProj) {
Message("The perspective factor will have no effect until you "
"enable View -> Use Perspective Projection.");
}
InvalidateGraphics();
break;
}
case EDIT_GRID_SPACING: {
SS.gridSpacing = (float)min(1e4, max(1e-3, SS.StringToMm(s)));
InvalidateGraphics();
break;
}
case EDIT_DIGITS_AFTER_DECIMAL: {
int v = atoi(s);
if(v < 0 || v > 8) {
Error("Specify between 0 and 8 digits after the decimal.");
} else {
SS.SetUnitDigitsAfterDecimal(v);
}
InvalidateGraphics();
break;
}
case EDIT_EXPORT_SCALE: {
Expr *e = Expr::From(s, true);
if(e) {
double ev = e->Eval();
if(fabs(ev) < 0.001 || isnan(ev)) {
Error("Export scale must not be zero!");
} else {
SS.exportScale = (float)ev;
}
}
break;
}
case EDIT_EXPORT_OFFSET: {
Expr *e = Expr::From(s, true);
if(e) {
double ev = SS.ExprToMm(e);
if(isnan(ev) || ev < 0) {
Error("Cutter radius offset must not be negative!");
} else {
SS.exportOffset = (float)ev;
}
}
break;
}
case EDIT_CANVAS_SIZE: {
Expr *e = Expr::From(s, true);
if(!e) {
break;
}
float d = (float)SS.ExprToMm(e);
switch(edit.i) {
case 0: SS.exportMargin.left = d; break;
case 1: SS.exportMargin.right = d; break;
case 2: SS.exportMargin.bottom = d; break;
case 3: SS.exportMargin.top = d; break;
case 10: SS.exportCanvas.width = d; break;
case 11: SS.exportCanvas.height = d; break;
case 12: SS.exportCanvas.dx = d; break;
case 13: SS.exportCanvas.dy = d; break;
}
break;
}
case EDIT_G_CODE_DEPTH: {
Expr *e = Expr::From(s, true);
if(e) SS.gCode.depth = (float)SS.ExprToMm(e);
break;
}
case EDIT_G_CODE_PASSES: {
Expr *e = Expr::From(s, true);
if(e) SS.gCode.passes = (int)(e->Eval());
SS.gCode.passes = max(1, min(1000, SS.gCode.passes));
break;
}
case EDIT_G_CODE_FEED: {
Expr *e = Expr::From(s, true);
if(e) SS.gCode.feed = (float)SS.ExprToMm(e);
break;
}
case EDIT_G_CODE_PLUNGE_FEED: {
Expr *e = Expr::From(s, true);
if(e) SS.gCode.plungeFeed = (float)SS.ExprToMm(e);
break;
}
default: return false;
}
return true;
}

View File

@ -1,339 +0,0 @@
//-----------------------------------------------------------------------------
// The screens when an entity is selected, that show some description of it--
// endpoints of the lines, diameter of the circle, etc.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
void TextWindow::ScreenUnselectAll(int link, DWORD v) {
GraphicsWindow::MenuEdit(GraphicsWindow::MNU_UNSELECT_ALL);
}
void TextWindow::ScreenEditTtfText(int link, DWORD v) {
hRequest hr = { v };
Request *r = SK.GetRequest(hr);
SS.TW.ShowEditControl(13, 10, r->str.str);
SS.TW.edit.meaning = EDIT_TTF_TEXT;
SS.TW.edit.request = hr;
}
#define gs (SS.GW.gs)
void TextWindow::ScreenSetTtfFont(int link, DWORD v) {
int i = (int)v;
if(i < 0) return;
if(i >= SS.fonts.l.n) return;
SS.GW.GroupSelection();
if(gs.entities != 1 || gs.n != 1) return;
Entity *e = SK.entity.FindByIdNoOops(gs.entity[0]);
if(!e || e->type != Entity::TTF_TEXT || !e->h.isFromRequest()) return;
Request *r = SK.request.FindByIdNoOops(e->h.request());
if(!r) return;
SS.UndoRemember();
r->font.strcpy(SS.fonts.l.elem[i].FontFileBaseName());
SS.MarkGroupDirty(r->group);
SS.later.generateAll = true;
SS.later.showTW = true;
}
void TextWindow::DescribeSelection(void) {
Entity *e;
Vector p;
int i;
Printf(false, "");
if(gs.n == 1 && (gs.points == 1 || gs.entities == 1)) {
e = SK.GetEntity(gs.points == 1 ? gs.point[0] : gs.entity[0]);
#define COSTR(p) \
SS.MmToString((p).x), SS.MmToString((p).y), SS.MmToString((p).z)
#define PT_AS_STR "(%Fi%s%E, %Fi%s%E, %Fi%s%E)"
#define PT_AS_NUM "(%Fi%3%E, %Fi%3%E, %Fi%3%E)"
switch(e->type) {
case Entity::POINT_IN_3D:
case Entity::POINT_IN_2D:
case Entity::POINT_N_TRANS:
case Entity::POINT_N_ROT_TRANS:
case Entity::POINT_N_COPY:
case Entity::POINT_N_ROT_AA:
p = e->PointGetNum();
Printf(false, "%FtPOINT%E at " PT_AS_STR, COSTR(p));
break;
case Entity::NORMAL_IN_3D:
case Entity::NORMAL_IN_2D:
case Entity::NORMAL_N_COPY:
case Entity::NORMAL_N_ROT:
case Entity::NORMAL_N_ROT_AA: {
Quaternion q = e->NormalGetNum();
p = q.RotationN();
Printf(false, "%FtNORMAL / COORDINATE SYSTEM%E");
Printf(true, " basis n = " PT_AS_NUM, CO(p));
p = q.RotationU();
Printf(false, " u = " PT_AS_NUM, CO(p));
p = q.RotationV();
Printf(false, " v = " PT_AS_NUM, CO(p));
break;
}
case Entity::WORKPLANE: {
p = SK.GetEntity(e->point[0])->PointGetNum();
Printf(false, "%FtWORKPLANE%E");
Printf(true, " origin = " PT_AS_STR, COSTR(p));
Quaternion q = e->Normal()->NormalGetNum();
p = q.RotationN();
Printf(true, " normal = " PT_AS_NUM, CO(p));
break;
}
case Entity::LINE_SEGMENT: {
Vector p0 = SK.GetEntity(e->point[0])->PointGetNum();
p = p0;
Printf(false, "%FtLINE SEGMENT%E");
Printf(true, " thru " PT_AS_STR, COSTR(p));
Vector p1 = SK.GetEntity(e->point[1])->PointGetNum();
p = p1;
Printf(false, " " PT_AS_STR, COSTR(p));
Printf(true, " len = %Fi%s%E",
SS.MmToString((p1.Minus(p0).Magnitude())));
break;
}
case Entity::CUBIC_PERIODIC:
case Entity::CUBIC:
int pts;
if(e->type == Entity::CUBIC_PERIODIC) {
Printf(false, "%FtPERIODIC C2 CUBIC SPLINE%E");
pts = (3 + e->extraPoints);
} else if(e->extraPoints > 0) {
Printf(false, "%FtINTERPOLATING C2 CUBIC SPLINE%E");
pts = (4 + e->extraPoints);
} else {
Printf(false, "%FtCUBIC BEZIER CURVE%E");
pts = 4;
}
for(i = 0; i < pts; i++) {
p = SK.GetEntity(e->point[i])->PointGetNum();
Printf((i==0), " p%d = " PT_AS_STR, i, COSTR(p));
}
break;
case Entity::ARC_OF_CIRCLE: {
Printf(false, "%FtARC OF A CIRCLE%E");
p = SK.GetEntity(e->point[0])->PointGetNum();
Printf(true, " center = " PT_AS_STR, COSTR(p));
p = SK.GetEntity(e->point[1])->PointGetNum();
Printf(true, " endpoints = " PT_AS_STR, COSTR(p));
p = SK.GetEntity(e->point[2])->PointGetNum();
Printf(false, " " PT_AS_STR, COSTR(p));
double r = e->CircleGetRadiusNum();
Printf(true, " diameter = %Fi%s", SS.MmToString(r*2));
Printf(false, " radius = %Fi%s", SS.MmToString(r));
double thetas, thetaf, dtheta;
e->ArcGetAngles(&thetas, &thetaf, &dtheta);
Printf(false, " arc len = %Fi%s", SS.MmToString(dtheta*r));
break;
}
case Entity::CIRCLE: {
Printf(false, "%FtCIRCLE%E");
p = SK.GetEntity(e->point[0])->PointGetNum();
Printf(true, " center = " PT_AS_STR, COSTR(p));
double r = e->CircleGetRadiusNum();
Printf(true, " diameter = %Fi%s", SS.MmToString(r*2));
Printf(false, " radius = %Fi%s", SS.MmToString(r));
break;
}
case Entity::FACE_NORMAL_PT:
case Entity::FACE_XPROD:
case Entity::FACE_N_ROT_TRANS:
case Entity::FACE_N_ROT_AA:
case Entity::FACE_N_TRANS:
Printf(false, "%FtPLANE FACE%E");
p = e->FaceGetNormalNum();
Printf(true, " normal = " PT_AS_NUM, CO(p));
p = e->FaceGetPointNum();
Printf(false, " thru = " PT_AS_STR, COSTR(p));
break;
case Entity::TTF_TEXT: {
Printf(false, "%FtTRUETYPE FONT TEXT%E");
Printf(true, " font = '%Fi%s%E'", e->font.str);
if(e->h.isFromRequest()) {
Printf(false, " text = '%Fi%s%E' %Fl%Ll%f%D[change]%E",
e->str.str, &ScreenEditTtfText, e->h.request());
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]);
if(strcmp(e->font.str, tf->FontFileBaseName())==0) {
Printf(false, "%Bp %s",
(i & 1) ? 'd' : 'a',
tf->name.str);
} else {
Printf(false, "%Bp %f%D%Fl%Ll%s%E%Bp",
(i & 1) ? 'd' : 'a',
&ScreenSetTtfFont, i,
tf->name.str,
(i & 1) ? 'd' : 'a');
}
}
} else {
Printf(false, " text = '%Fi%s%E'", e->str.str);
}
break;
}
default:
Printf(true, "%Ft?? ENTITY%E");
break;
}
Group *g = SK.GetGroup(e->group);
Printf(false, "");
Printf(false, "%FtIN GROUP%E %s", g->DescriptionString());
if(e->workplane.v == Entity::FREE_IN_3D.v) {
Printf(false, "%FtNOT LOCKED IN WORKPLANE%E");
} else {
Entity *w = SK.GetEntity(e->workplane);
Printf(false, "%FtIN WORKPLANE%E %s", w->DescriptionString());
}
if(e->style.v) {
Style *s = Style::Get(e->style);
Printf(false, "%FtIN STYLE%E %s", s->DescriptionString());
} else {
Printf(false, "%FtIN STYLE%E none");
}
if(e->construction) {
Printf(false, "%FtCONSTRUCTION");
}
} else if(gs.n == 2 && gs.points == 2) {
Printf(false, "%FtTWO POINTS");
Vector p0 = SK.GetEntity(gs.point[0])->PointGetNum();
Printf(true, " at " PT_AS_STR, COSTR(p0));
Vector p1 = SK.GetEntity(gs.point[1])->PointGetNum();
Printf(false, " " PT_AS_STR, COSTR(p1));
double d = (p1.Minus(p0)).Magnitude();
Printf(true, " d = %Fi%s", SS.MmToString(d));
} else if(gs.n == 2 && gs.faces == 1 && gs.points == 1) {
Printf(false, "%FtA POINT AND A PLANE FACE");
Vector pt = SK.GetEntity(gs.point[0])->PointGetNum();
Printf(true, " point = " PT_AS_STR, COSTR(pt));
Vector n = SK.GetEntity(gs.face[0])->FaceGetNormalNum();
Printf(true, " plane normal = " PT_AS_NUM, CO(n));
Vector pl = SK.GetEntity(gs.face[0])->FaceGetPointNum();
Printf(false, " plane thru = " PT_AS_STR, COSTR(pl));
double dd = n.Dot(pl) - n.Dot(pt);
Printf(true, " distance = %Fi%s", SS.MmToString(dd));
} else if(gs.n == 3 && gs.points == 2 && gs.vectors == 1) {
Printf(false, "%FtTWO POINTS AND A VECTOR");
Vector p0 = SK.GetEntity(gs.point[0])->PointGetNum();
Printf(true, " pointA = " PT_AS_STR, COSTR(p0));
Vector p1 = SK.GetEntity(gs.point[1])->PointGetNum();
Printf(false, " pointB = " PT_AS_STR, COSTR(p1));
Vector v = SK.GetEntity(gs.vector[0])->VectorGetNum();
v = v.WithMagnitude(1);
Printf(true, " vector = " PT_AS_NUM, CO(v));
double d = (p1.Minus(p0)).Dot(v);
Printf(true, " proj_d = %Fi%s", SS.MmToString(d));
} else if(gs.n == 2 && gs.lineSegments == 1 && gs.points == 1) {
Entity *ln = SK.GetEntity(gs.entity[0]);
Vector lp0 = SK.GetEntity(ln->point[0])->PointGetNum(),
lp1 = SK.GetEntity(ln->point[1])->PointGetNum();
Printf(false, "%FtLINE SEGMENT AND POINT%E");
Printf(true, " ln thru " PT_AS_STR, COSTR(lp0));
Printf(false, " " PT_AS_STR, COSTR(lp1));
Vector pp = SK.GetEntity(gs.point[0])->PointGetNum();
Printf(true, " point " PT_AS_STR, COSTR(pp));
Printf(true, " pt-ln distance = %Fi%s%E",
SS.MmToString(pp.DistanceToLine(lp0, lp1.Minus(lp0))));
} else if(gs.n == 2 && gs.vectors == 2) {
Printf(false, "%FtTWO VECTORS");
Vector v0 = SK.GetEntity(gs.entity[0])->VectorGetNum(),
v1 = SK.GetEntity(gs.entity[1])->VectorGetNum();
v0 = v0.WithMagnitude(1);
v1 = v1.WithMagnitude(1);
Printf(true, " vectorA = " PT_AS_NUM, CO(v0));
Printf(false, " vectorB = " PT_AS_NUM, CO(v1));
double theta = acos(v0.Dot(v1));
Printf(true, " angle = %Fi%2%E degrees", theta*180/PI);
while(theta < PI/2) theta += PI;
while(theta > PI/2) theta -= PI;
Printf(false, " or angle = %Fi%2%E (mod 180)", theta*180/PI);
} else if(gs.n == 2 && gs.faces == 2) {
Printf(false, "%FtTWO PLANE FACES");
Vector n0 = SK.GetEntity(gs.face[0])->FaceGetNormalNum();
Printf(true, " planeA normal = " PT_AS_NUM, CO(n0));
Vector p0 = SK.GetEntity(gs.face[0])->FaceGetPointNum();
Printf(false, " planeA thru = " PT_AS_STR, COSTR(p0));
Vector n1 = SK.GetEntity(gs.face[1])->FaceGetNormalNum();
Printf(true, " planeB normal = " PT_AS_NUM, CO(n1));
Vector p1 = SK.GetEntity(gs.face[1])->FaceGetPointNum();
Printf(false, " planeB thru = " PT_AS_STR, COSTR(p1));
double theta = acos(n0.Dot(n1));
Printf(true, " angle = %Fi%2%E degrees", theta*180/PI);
while(theta < PI/2) theta += PI;
while(theta > PI/2) theta -= PI;
Printf(false, " or angle = %Fi%2%E (mod 180)", theta*180/PI);
if(fabs(theta) < 0.01) {
double d = (p1.Minus(p0)).Dot(n0);
Printf(true, " distance = %Fi%s", SS.MmToString(d));
}
} else if(gs.n == 0 && gs.stylables > 0) {
Printf(false, "%FtSELECTED:%E comment text");
} else if(gs.n == 0 && gs.constraints == 1) {
Printf(false, "%FtSELECTED:%E %s",
SK.GetConstraint(gs.constraint[0])->DescriptionString());
} else {
int n = SS.GW.selection.n;
Printf(false, "%FtSELECTED:%E %d item%s", n, n == 1 ? "" : "s");
}
if(shown.screen == SCREEN_STYLE_INFO &&
shown.style.v >= Style::FIRST_CUSTOM && gs.stylables > 0)
{
// If we are showing a screen for a particular style, then offer the
// option to assign our selected entities to that style.
Style *s = Style::Get(shown.style);
Printf(true, "%Fl%D%f%Ll(assign to style %s)%E",
shown.style.v,
&ScreenAssignSelectionToStyle,
s->DescriptionString());
}
// If any of the selected entities have an assigned style, then offer
// the option to remove that style.
bool styleAssigned = false;
for(i = 0; i < gs.entities; i++) {
Entity *e = SK.GetEntity(gs.entity[i]);
if(e->style.v != 0) {
styleAssigned = true;
}
}
for(i = 0; i < gs.constraints; i++) {
Constraint *c = SK.GetConstraint(gs.constraint[i]);
if(c->type == Constraint::COMMENT && c->disp.style.v != 0) {
styleAssigned = true;
}
}
if(styleAssigned) {
Printf(true, "%Fl%D%f%Ll(remove assigned style)%E",
0,
&ScreenAssignSelectionToStyle);
}
Printf(true, "%Fl%f%Ll(unselect all)%E", &TextWindow::ScreenUnselectAll);
}
void TextWindow::GoToScreen(int screen) {
shown.screen = screen;
}

820
draw.cpp
View File

@ -1,820 +0,0 @@
//-----------------------------------------------------------------------------
// The root function to paint our graphics window, after setting up all the
// views and such appropriately. Also contains all the stuff to manage the
// selection.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#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;
return true;
}
bool GraphicsWindow::Selection::IsEmpty(void) {
if(entity.v) return false;
if(constraint.v) return false;
return true;
}
bool GraphicsWindow::Selection::IsStylable(void) {
if(entity.v) return true;
if(constraint.v) {
Constraint *c = SK.GetConstraint(constraint);
if(c->type == Constraint::COMMENT) return true;
}
return false;
}
bool GraphicsWindow::Selection::HasEndpoints(void) {
if(!entity.v) return false;
Entity *e = SK.GetEntity(entity);
return e->HasEndpoints();
}
void GraphicsWindow::Selection::Clear(void) {
entity.v = constraint.v = 0;
emphasized = false;
}
void GraphicsWindow::Selection::Draw(void) {
Vector refp;
if(entity.v) {
Entity *e = SK.GetEntity(entity);
e->Draw();
if(emphasized) refp = e->GetReferencePos();
}
if(constraint.v) {
Constraint *c = SK.GetConstraint(constraint);
c->Draw();
if(emphasized) refp = c->GetReferencePos();
}
if(emphasized && (constraint.v || entity.v)) {
// We want to emphasize this constraint or entity, by drawing a thick
// line from the top left corner of the screen to the reference point
// of that entity or constraint.
double s = 0.501/SS.GW.scale;
Vector topLeft = SS.GW.projRight.ScaledBy(-SS.GW.width*s);
topLeft = topLeft.Plus(SS.GW.projUp.ScaledBy(SS.GW.height*s));
topLeft = topLeft.Minus(SS.GW.offset);
glLineWidth(40);
DWORD rgb = Style::Color(Style::HOVERED);
glColor4d(REDf(rgb), GREENf(rgb), BLUEf(rgb), 0.2);
glBegin(GL_LINES);
glxVertex3v(topLeft);
glxVertex3v(refp);
glEnd();
glLineWidth(1);
}
}
void GraphicsWindow::ClearSelection(void) {
selection.Clear();
SS.later.showTW = true;
InvalidateGraphics();
}
void GraphicsWindow::ClearNonexistentSelectionItems(void) {
bool change = false;
Selection *s;
selection.ClearTags();
for(s = selection.First(); s; s = selection.NextAfter(s)) {
if(s->constraint.v && !(SK.constraint.FindByIdNoOops(s->constraint))) {
s->tag = 1;
change = true;
}
if(s->entity.v && !(SK.entity.FindByIdNoOops(s->entity))) {
s->tag = 1;
change = true;
}
}
selection.RemoveTagged();
if(change) InvalidateGraphics();
}
//-----------------------------------------------------------------------------
// Is this entity/constraint selected?
//-----------------------------------------------------------------------------
bool GraphicsWindow::IsSelected(hEntity he) {
Selection s;
ZERO(&s);
s.entity = he;
return IsSelected(&s);
}
bool GraphicsWindow::IsSelected(Selection *st) {
Selection *s;
for(s = selection.First(); s; s = selection.NextAfter(s)) {
if(s->Equals(st)) {
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Unselect an item, if it is selected. We can either unselect just that item,
// or also unselect any coincident points. The latter is useful if the user
// somehow selects two coincident points (like with select all), because it
// would otherwise be impossible to de-select the lower of the two.
//-----------------------------------------------------------------------------
void GraphicsWindow::MakeUnselected(hEntity he, bool coincidentPointTrick) {
Selection stog;
ZERO(&stog);
stog.entity = he;
MakeUnselected(&stog, coincidentPointTrick);
}
void GraphicsWindow::MakeUnselected(Selection *stog, bool coincidentPointTrick){
if(stog->IsEmpty()) return;
Selection *s;
// If an item was selected, then we just un-select it.
bool wasSelected = false;
selection.ClearTags();
for(s = selection.First(); s; s = selection.NextAfter(s)) {
if(s->Equals(stog)) {
s->tag = 1;
}
}
// If two points are coincident, then it's impossible to hover one of
// them. But make sure to deselect both, to avoid mysterious seeming
// inability to deselect if the bottom one did somehow get selected.
if(stog->entity.v && coincidentPointTrick) {
Entity *e = SK.GetEntity(stog->entity);
if(e->IsPoint()) {
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;
Entity *se = SK.GetEntity(s->entity);
if(!se->IsPoint()) continue;
if(ep.Equals(se->PointGetNum())) {
s->tag = 1;
}
}
}
}
selection.RemoveTagged();
}
//-----------------------------------------------------------------------------
// Select an item, if it isn't selected already.
//-----------------------------------------------------------------------------
void GraphicsWindow::MakeSelected(hEntity he) {
Selection stog;
ZERO(&stog);
stog.entity = he;
MakeSelected(&stog);
}
void GraphicsWindow::MakeSelected(Selection *stog) {
if(stog->IsEmpty()) return;
if(IsSelected(stog)) return;
if(stog->entity.v != 0 && SK.GetEntity(stog->entity)->IsFace()) {
// In the interest of speed for the triangle drawing code,
// only two faces may be selected at a time.
int c = 0;
Selection *s;
selection.ClearTags();
for(s = selection.First(); s; s = selection.NextAfter(s)) {
hEntity he = s->entity;
if(he.v != 0 && SK.GetEntity(he)->IsFace()) {
c++;
if(c >= 2) s->tag = 1;
}
}
selection.RemoveTagged();
}
selection.Add(stog);
}
//-----------------------------------------------------------------------------
// Select everything that lies within the marquee view-aligned rectangle. For
// points, we test by the point location. For normals, we test by the normal's
// associated point. For anything else, we test by any piecewise linear edge.
//-----------------------------------------------------------------------------
void GraphicsWindow::SelectByMarquee(void) {
Point2d begin = ProjectPoint(orig.marqueePoint);
double xmin = min(orig.mouse.x, begin.x),
xmax = max(orig.mouse.x, begin.x),
ymin = min(orig.mouse.y, begin.y),
ymax = max(orig.mouse.y, begin.y);
Entity *e;
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
if(e->group.v != SS.GW.activeGroup.v) continue;
if(e->IsFace() || e->IsDistance()) continue;
if(!e->IsVisible()) continue;
if(e->IsPoint() || e->IsNormal()) {
Vector p = e->IsPoint() ? e->PointGetNum() :
SK.GetEntity(e->point[0])->PointGetNum();
Point2d pp = ProjectPoint(p);
if(pp.x >= xmin && pp.x <= xmax &&
pp.y >= ymin && pp.y <= ymax)
{
MakeSelected(e->h);
}
} else {
// Use the 3d bounding box test routines, to avoid duplication;
// so let our bounding square become a bounding box that certainly
// includes the z = 0 plane.
Vector ptMin = Vector::From(xmin, ymin, -1),
ptMax = Vector::From(xmax, ymax, 1);
SEdgeList sel;
ZERO(&sel);
e->GenerateEdges(&sel, true);
SEdge *se;
for(se = sel.l.First(); se; se = sel.l.NextAfter(se)) {
Point2d ppa = ProjectPoint(se->a),
ppb = ProjectPoint(se->b);
Vector ptA = Vector::From(ppa.x, ppa.y, 0),
ptB = Vector::From(ppb.x, ppb.y, 0);
if(Vector::BoundingBoxIntersectsLine(ptMax, ptMin,
ptA, ptB, true) ||
!ptA.OutsideAndNotOn(ptMax, ptMin) ||
!ptB.OutsideAndNotOn(ptMax, ptMin))
{
MakeSelected(e->h);
break;
}
}
sel.Clear();
}
}
}
//-----------------------------------------------------------------------------
// Sort the selection according to various critieria: the entities and
// constraints separately, counts of certain types of entities (circles,
// lines, etc.), and so on.
//-----------------------------------------------------------------------------
void GraphicsWindow::GroupSelection(void) {
memset(&gs, 0, sizeof(gs));
int i;
for(i = 0; i < selection.n && i < MAX_SELECTED; i++) {
Selection *s = &(selection.elem[i]);
if(s->entity.v) {
(gs.n)++;
Entity *e = SK.entity.FindById(s->entity);
// A list of points, and a list of all entities that aren't points.
if(e->IsPoint()) {
gs.point[(gs.points)++] = s->entity;
} else {
gs.entity[(gs.entities)++] = s->entity;
(gs.stylables)++;
}
// And an auxiliary list of normals, including normals from
// workplanes.
if(e->IsNormal()) {
gs.anyNormal[(gs.anyNormals)++] = s->entity;
} else if(e->IsWorkplane()) {
gs.anyNormal[(gs.anyNormals)++] = e->Normal()->h;
}
// And of vectors (i.e., stuff with a direction to constrain)
if(e->HasVector()) {
gs.vector[(gs.vectors)++] = s->entity;
}
// Faces (which are special, associated/drawn with triangles)
if(e->IsFace()) {
gs.face[(gs.faces)++] = s->entity;
}
if(e->HasEndpoints()) {
(gs.withEndpoints)++;
}
// And some aux counts too
switch(e->type) {
case Entity::WORKPLANE: (gs.workplanes)++; break;
case Entity::LINE_SEGMENT: (gs.lineSegments)++; break;
case Entity::CUBIC: (gs.cubics)++; break;
case Entity::CUBIC_PERIODIC: (gs.periodicCubics)++; break;
case Entity::ARC_OF_CIRCLE:
(gs.circlesOrArcs)++;
(gs.arcs)++;
break;
case Entity::CIRCLE: (gs.circlesOrArcs)++; break;
}
}
if(s->constraint.v) {
gs.constraint[(gs.constraints)++] = s->constraint;
Constraint *c = SK.GetConstraint(s->constraint);
if(c->type == Constraint::COMMENT) {
(gs.stylables)++;
(gs.comments)++;
}
}
}
}
void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
int i;
double d, dmin = 1e12;
Selection s;
ZERO(&s);
// Always do the entities; we might be dragging something that should
// be auto-constrained, and we need the hover for that.
for(i = 0; i < SK.entity.n; i++) {
Entity *e = &(SK.entity.elem[i]);
// Don't hover whatever's being dragged.
if(e->h.request().v == pending.point.request().v) {
// The one exception is when we're creating a new cubic; we
// want to be able to hover the first point, because that's
// how we turn it into a periodic spline.
if(!e->IsPoint()) continue;
if(!e->h.isFromRequest()) continue;
Request *r = SK.GetRequest(e->h.request());
if(r->type != Request::CUBIC) continue;
if(r->extraPoints < 2) continue;
if(e->h.v != r->h.entity(1).v) continue;
}
d = e->GetDistance(mp);
if(d < 10 && d < dmin) {
memset(&s, 0, sizeof(s));
s.entity = e->h;
dmin = d;
}
}
// The constraints and faces happen only when nothing's in progress.
if(pending.operation == 0) {
// Constraints
for(i = 0; i < SK.constraint.n; i++) {
d = SK.constraint.elem[i].GetDistance(mp);
if(d < 10 && d < dmin) {
memset(&s, 0, sizeof(s));
s.constraint = SK.constraint.elem[i].h;
dmin = d;
}
}
// Faces, from the triangle mesh; these are lowest priority
if(s.constraint.v == 0 && s.entity.v == 0 && showShaded && showFaces) {
Group *g = SK.GetGroup(activeGroup);
SMesh *m = &(g->displayMesh);
DWORD v = m->FirstIntersectionWith(mp);
if(v) {
s.entity.v = v;
}
}
}
if(!s.Equals(&hover)) {
hover = s;
InvalidateGraphics();
}
}
//-----------------------------------------------------------------------------
// Project a point in model space to screen space, exactly as gl would; return
// units are pixels.
//-----------------------------------------------------------------------------
Point2d GraphicsWindow::ProjectPoint(Vector p) {
Vector p3 = ProjectPoint3(p);
Point2d p2 = { p3.x, p3.y };
return p2;
}
//-----------------------------------------------------------------------------
// Project a point in model space to screen space, exactly as gl would; return
// units are pixels. The z coordinate is also returned, also in pixels.
//-----------------------------------------------------------------------------
Vector GraphicsWindow::ProjectPoint3(Vector p) {
double w;
Vector r = ProjectPoint4(p, &w);
return r.ScaledBy(scale/w);
}
//-----------------------------------------------------------------------------
// Project a point in model space halfway into screen space. The scale is
// not applied, and the perspective divide isn't applied; instead the w
// coordinate is returned separately.
//-----------------------------------------------------------------------------
Vector GraphicsWindow::ProjectPoint4(Vector p, double *w) {
p = p.Plus(offset);
Vector r;
r.x = p.Dot(projRight);
r.y = p.Dot(projUp);
r.z = p.Dot(projUp.Cross(projRight));
*w = 1 + r.z*SS.CameraTangent()*scale;
return r;
}
//-----------------------------------------------------------------------------
// Return a point in the plane parallel to the screen and through the offset,
// that projects onto the specified (x, y) coordinates.
//-----------------------------------------------------------------------------
Vector GraphicsWindow::UnProjectPoint(Point2d p) {
Vector orig = offset.ScaledBy(-1);
// Note that we ignoring the effects of perspective. Since our returned
// point has the same component normal to the screen as the offset, it
// will have z = 0 after the rotation is applied, thus w = 1. So this is
// correct.
orig = orig.Plus(projRight.ScaledBy(p.x / scale)).Plus(
projUp. ScaledBy(p.y / scale));
return orig;
}
void GraphicsWindow::NormalizeProjectionVectors(void) {
if(projRight.Magnitude() < LENGTH_EPS) {
projRight = Vector::From(1, 0, 0);
}
Vector norm = projRight.Cross(projUp);
// If projRight and projUp somehow ended up parallel, then pick an
// arbitrary projUp normal to projRight.
if(norm.Magnitude() < LENGTH_EPS) {
norm = projRight.Normal(0);
}
projUp = norm.Cross(projRight);
projUp = projUp.WithMagnitude(1);
projRight = projRight.WithMagnitude(1);
}
Vector GraphicsWindow::VectorFromProjs(Vector rightUpForward) {
Vector n = projRight.Cross(projUp);
Vector r = (projRight.ScaledBy(rightUpForward.x));
r = r.Plus(projUp.ScaledBy(rightUpForward.y));
r = r.Plus(n.ScaledBy(rightUpForward.z));
return r;
}
void GraphicsWindow::Paint(void) {
int i;
havePainted = true;
int w, h;
GetGraphicsWindowSize(&w, &h);
width = w; height = h;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glScaled(scale*2.0/w, scale*2.0/h, scale*1.0/30000);
double mat[16];
// Last thing before display is to apply the perspective
double clp = SS.CameraTangent()*scale;
MakeMatrix(mat, 1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, clp, 1);
glMultMatrixd(mat);
// Before that, we apply the rotation
Vector n = projUp.Cross(projRight);
MakeMatrix(mat, projRight.x, projRight.y, projRight.z, 0,
projUp.x, projUp.y, projUp.z, 0,
n.x, n.y, n.z, 0,
0, 0, 0, 1);
glMultMatrixd(mat);
// And before that, the translation
MakeMatrix(mat, 1, 0, 0, offset.x,
0, 1, 0, offset.y,
0, 0, 1, offset.z,
0, 0, 0, 1);
glMultMatrixd(mat);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glShadeModel(GL_SMOOTH);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glEnable(GL_LINE_SMOOTH);
// don't enable GL_POLYGON_SMOOTH; that looks ugly on some graphics cards,
// drawn with leaks in the mesh
glEnable(GL_POLYGON_OFFSET_LINE);
glEnable(GL_POLYGON_OFFSET_FILL);
glEnable(GL_DEPTH_TEST);
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
glEnable(GL_NORMALIZE);
// At the same depth, we want later lines drawn over earlier.
glDepthFunc(GL_LEQUAL);
if(SS.AllGroupsOkay()) {
glClearColor(REDf(SS.backgroundColor),
GREENf(SS.backgroundColor),
BLUEf(SS.backgroundColor), 1.0f);
} else {
// Draw a different background whenever we're having solve problems.
DWORD rgb = Style::Color(Style::DRAW_ERROR);
glClearColor(0.4f*REDf(rgb), 0.4f*GREENf(rgb), 0.4f*BLUEf(rgb), 1.0f);
// And show the text window, which has info to debug it
ForceTextWindowShown();
}
glClear(GL_COLOR_BUFFER_BIT);
glClearDepth(1.0);
glClear(GL_DEPTH_BUFFER_BIT);
if(SS.bgImage.fromFile) {
// If a background image is loaded, then we draw it now as a texture.
// This handles the resizing for us nicely.
glBindTexture(GL_TEXTURE_2D, TEXTURE_BACKGROUND_IMG);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
SS.bgImage.rw, SS.bgImage.rh,
0,
GL_RGB, GL_UNSIGNED_BYTE,
SS.bgImage.fromFile);
double tw = ((double)SS.bgImage.w) / SS.bgImage.rw,
th = ((double)SS.bgImage.h) / SS.bgImage.rh;
double mmw = SS.bgImage.w / SS.bgImage.scale,
mmh = SS.bgImage.h / SS.bgImage.scale;
Vector origin = SS.bgImage.origin;
origin = origin.DotInToCsys(projRight, projUp, n);
// Place the depth of our origin at the point that corresponds to
// w = 1, so that it's unaffected by perspective.
origin.z = (offset.ScaledBy(-1)).Dot(n);
origin = origin.ScaleOutOfCsys(projRight, projUp, n);
// Place the background at the very back of the Z order, though, by
// mucking with the depth range.
glDepthRange(1, 1);
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
glTexCoord2d(0, 0);
glxVertex3v(origin);
glTexCoord2d(0, th);
glxVertex3v(origin.Plus(projUp.ScaledBy(mmh)));
glTexCoord2d(tw, th);
glxVertex3v(origin.Plus(projRight.ScaledBy(mmw).Plus(
projUp. ScaledBy(mmh))));
glTexCoord2d(tw, 0);
glxVertex3v(origin.Plus(projRight.ScaledBy(mmw)));
glEnd();
glDisable(GL_TEXTURE_2D);
}
glxDepthRangeOffset(0);
// Nasty case when we're reloading the imported files; could be that
// we get an error, so a dialog pops up, and a message loop starts, and
// we have to get called to paint ourselves. If the sketch is screwed
// up, then we could trigger an oops trying to draw.
if(!SS.allConsistent) return;
// Let's use two lights, at the user-specified locations
GLfloat f;
glEnable(GL_LIGHT0);
f = (GLfloat)SS.lightIntensity[0];
GLfloat li0[] = { f, f, f, 1.0f };
glLightfv(GL_LIGHT0, GL_DIFFUSE, li0);
glLightfv(GL_LIGHT0, GL_SPECULAR, li0);
glEnable(GL_LIGHT1);
f = (GLfloat)SS.lightIntensity[1];
GLfloat li1[] = { f, f, f, 1.0f };
glLightfv(GL_LIGHT1, GL_DIFFUSE, li1);
glLightfv(GL_LIGHT1, GL_SPECULAR, li1);
Vector ld;
ld = VectorFromProjs(SS.lightDir[0]);
GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 };
glLightfv(GL_LIGHT0, GL_POSITION, ld0);
ld = VectorFromProjs(SS.lightDir[1]);
GLfloat ld1[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 };
glLightfv(GL_LIGHT1, GL_POSITION, ld1);
if(SS.drawBackFaces) {
// For debugging, draw the backs of the triangles in red, so that we
// notice when a shell is open
glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1);
} else {
glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 0);
}
GLfloat ambient[4] = { (float)SS.ambientIntensity,
(float)SS.ambientIntensity,
(float)SS.ambientIntensity, 1 };
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
glxUnlockColor();
if(showSnapGrid && LockedInWorkplane()) {
hEntity he = ActiveWorkplane();
EntityBase *wrkpl = SK.GetEntity(he),
*norm = wrkpl->Normal();
Vector wu, wv, wn, wp;
wp = SK.GetEntity(wrkpl->point[0])->PointGetNum();
wu = norm->NormalU();
wv = norm->NormalV();
wn = norm->NormalN();
double g = SS.gridSpacing;
double umin = VERY_POSITIVE, umax = VERY_NEGATIVE,
vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE;
int a;
for(a = 0; a < 4; a++) {
// Ideally, we would just do +/- half the width and height; but
// allow some extra slop for rounding.
Vector horiz = projRight.ScaledBy((0.6*width)/scale + 2*g),
vert = projUp. ScaledBy((0.6*height)/scale + 2*g);
if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1);
if(a == 1 || a == 3) vert = vert. ScaledBy(-1);
Vector tp = horiz.Plus(vert).Minus(offset);
// Project the point into our grid plane, normal to the screen
// (not to the grid plane). If the plane is on edge then this is
// impossible so don't try to draw the grid.
bool parallel;
Vector tpp = Vector::AtIntersectionOfPlaneAndLine(
wn, wn.Dot(wp),
tp, tp.Plus(n),
&parallel);
if(parallel) goto nogrid;
tpp = tpp.Minus(wp);
double uu = tpp.Dot(wu),
vv = tpp.Dot(wv);
umin = min(uu, umin);
umax = max(uu, umax);
vmin = min(vv, vmin);
vmax = max(vv, vmax);
}
int i, j, i0, i1, j0, j1;
i0 = (int)(umin / g);
i1 = (int)(umax / g);
j0 = (int)(vmin / g);
j1 = (int)(vmax / g);
if(i0 > i1 || i1 - i0 > 400) goto nogrid;
if(j0 > j1 || j1 - j0 > 400) goto nogrid;
glLineWidth(1);
glxColorRGBa(Style::Color(Style::DATUM), 0.3);
glBegin(GL_LINES);
for(i = i0 + 1; i < i1; i++) {
glxVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g)));
glxVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g)));
}
for(j = j0 + 1; j < j1; j++) {
glxVertex3v(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g)));
glxVertex3v(wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g)));
}
glEnd();
// Clear the depth buffer, so that the grid is at the very back of
// the Z order.
glClear(GL_DEPTH_BUFFER_BIT);
nogrid:;
}
// Draw the active group; this does stuff like the mesh and edges.
(SK.GetGroup(activeGroup))->Draw();
// Now draw the entities
if(showHdnLines) glDisable(GL_DEPTH_TEST);
Entity::DrawAll();
// Draw filled paths in all groups, when those filled paths were requested
// specially by assigning a style with a fill color, or when the filled
// paths are just being filled by default. This should go last, to make
// the transparency work.
Group *g;
for(g = SK.group.First(); g; g = SK.group.NextAfter(g)) {
if(!(g->IsVisible())) continue;
g->DrawFilledPaths();
}
glDisable(GL_DEPTH_TEST);
// Draw the constraints
for(i = 0; i < SK.constraint.n; i++) {
SK.constraint.elem[i].Draw();
}
// Draw the traced path, if one exists
glLineWidth(Style::Width(Style::ANALYZE));
glxColorRGB(Style::Color(Style::ANALYZE));
SContour *sc = &(SS.traced.path);
glBegin(GL_LINE_STRIP);
for(i = 0; i < sc->l.n; i++) {
glxVertex3v(sc->l.elem[i].p);
}
glEnd();
// And the naked edges, if the user did Analyze -> Show Naked Edges.
glLineWidth(Style::Width(Style::DRAW_ERROR));
glxColorRGB(Style::Color(Style::DRAW_ERROR));
glxDrawEdges(&(SS.nakedEdges), true);
// Then redraw whatever the mouse is hovering over, highlighted.
glDisable(GL_DEPTH_TEST);
glxLockColorTo(Style::Color(Style::HOVERED));
hover.Draw();
// And finally draw the selection, same mechanism.
glxLockColorTo(Style::Color(Style::SELECTED));
for(Selection *s = selection.First(); s; s = selection.NextAfter(s)) {
s->Draw();
}
glxUnlockColor();
// If a marquee selection is in progress, then draw the selection
// rectangle, as an outline and a transparent fill.
if(pending.operation == DRAGGING_MARQUEE) {
Point2d begin = ProjectPoint(orig.marqueePoint);
double xmin = min(orig.mouse.x, begin.x),
xmax = max(orig.mouse.x, begin.x),
ymin = min(orig.mouse.y, begin.y),
ymax = max(orig.mouse.y, begin.y);
Vector tl = UnProjectPoint(Point2d::From(xmin, ymin)),
tr = UnProjectPoint(Point2d::From(xmax, ymin)),
br = UnProjectPoint(Point2d::From(xmax, ymax)),
bl = UnProjectPoint(Point2d::From(xmin, ymax));
glLineWidth((GLfloat)1.3);
glxColorRGB(Style::Color(Style::HOVERED));
glBegin(GL_LINE_LOOP);
glxVertex3v(tl);
glxVertex3v(tr);
glxVertex3v(br);
glxVertex3v(bl);
glEnd();
glxColorRGBa(Style::Color(Style::HOVERED), 0.10);
glBegin(GL_QUADS);
glxVertex3v(tl);
glxVertex3v(tr);
glxVertex3v(br);
glxVertex3v(bl);
glEnd();
}
// An extra line, used to indicate the origin when rotating within the
// plane of the monitor.
if(SS.extraLine.draw) {
glLineWidth(1);
glxLockColorTo(Style::Color(Style::DATUM));
glBegin(GL_LINES);
glxVertex3v(SS.extraLine.ptA);
glxVertex3v(SS.extraLine.ptB);
glEnd();
}
// A note to indicate the origin in the just-exported file.
if(SS.justExportedInfo.draw) {
glxColorRGB(Style::Color(Style::DATUM));
Vector p = SS.justExportedInfo.pt,
u = SS.justExportedInfo.u,
v = SS.justExportedInfo.v;
glLineWidth(1.5);
glBegin(GL_LINES);
glxVertex3v(p.Plus(u.WithMagnitude(-15/scale)));
glxVertex3v(p.Plus(u.WithMagnitude(30/scale)));
glxVertex3v(p.Plus(v.WithMagnitude(-15/scale)));
glxVertex3v(p.Plus(v.WithMagnitude(30/scale)));
glEnd();
glxWriteText("(x, y) = (0, 0) for file just exported",
DEFAULT_TEXT_HEIGHT,
p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)),
u, v, NULL, NULL);
glxWriteText("press Esc to clear this message",
DEFAULT_TEXT_HEIGHT,
p.Plus(u.ScaledBy(40/scale)).Plus(
v.ScaledBy(-(DEFAULT_TEXT_HEIGHT)/scale)),
u, v, NULL, NULL);
}
// And finally the toolbar.
if(SS.showToolbar) {
ToolbarDraw();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,648 +0,0 @@
//-----------------------------------------------------------------------------
// Draw a representation of an entity on-screen, in the case of curves up
// to our chord tolerance, or return the distance from the user's mouse pointer
// to the entity for selection.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
char *Entity::DescriptionString(void) {
if(h.isFromRequest()) {
Request *r = SK.GetRequest(h.request());
return r->DescriptionString();
} else {
Group *g = SK.GetGroup(h.group());
return g->DescriptionString();
}
}
void Entity::LineDrawOrGetDistance(Vector a, Vector b, bool maybeFat) {
if(dogd.drawing) {
// Draw lines from active group in front of those from previous
glxDepthRangeOffset((group.v == SS.GW.activeGroup.v) ? 4 : 3);
// Narrow lines are drawn as lines, but fat lines must be drawn as
// filled polygons, to get the line join style right.
if(!maybeFat || dogd.lineWidth < 3) {
glBegin(GL_LINES);
glxVertex3v(a);
glxVertex3v(b);
glEnd();
} else {
glxFatLine(a, b, dogd.lineWidth/SS.GW.scale);
}
glxDepthRangeOffset(0);
} else {
Point2d ap = SS.GW.ProjectPoint(a);
Point2d bp = SS.GW.ProjectPoint(b);
double d = dogd.mp.DistanceToLine(ap, bp.Minus(ap), true);
// A little bit easier to select in the active group
if(group.v == SS.GW.activeGroup.v) d -= 1;
dogd.dmin = min(dogd.dmin, d);
}
dogd.refp = (a.Plus(b)).ScaledBy(0.5);
}
void Entity::DrawAll(void) {
// This handles points and line segments as a special case, because I
// seem to be able to get a huge speedup that way, by consolidating
// stuff to gl.
int i;
if(SS.GW.showPoints) {
double s = 3.5/SS.GW.scale;
Vector r = SS.GW.projRight.ScaledBy(s);
Vector d = SS.GW.projUp.ScaledBy(s);
glxColorRGB(Style::Color(Style::DATUM));
glxDepthRangeOffset(6);
glBegin(GL_QUADS);
for(i = 0; i < SK.entity.n; i++) {
Entity *e = &(SK.entity.elem[i]);
if(!e->IsPoint()) continue;
if(!(SK.GetGroup(e->group)->IsVisible())) continue;
if(e->forceHidden) continue;
Vector v = e->PointGetNum();
// If we're analyzing the sketch to show the degrees of freedom,
// then we draw big colored squares over the points that are
// free to move.
bool free = false;
if(e->type == POINT_IN_3D) {
Param *px = SK.GetParam(e->param[0]),
*py = SK.GetParam(e->param[1]),
*pz = SK.GetParam(e->param[2]);
free = (px->free) || (py->free) || (pz->free);
} else if(e->type == POINT_IN_2D) {
Param *pu = SK.GetParam(e->param[0]),
*pv = SK.GetParam(e->param[1]);
free = (pu->free) || (pv->free);
}
if(free) {
Vector re = r.ScaledBy(2.5), de = d.ScaledBy(2.5);
glxColorRGB(Style::Color(Style::ANALYZE));
glxVertex3v(v.Plus (re).Plus (de));
glxVertex3v(v.Plus (re).Minus(de));
glxVertex3v(v.Minus(re).Minus(de));
glxVertex3v(v.Minus(re).Plus (de));
glxColorRGB(Style::Color(Style::DATUM));
}
glxVertex3v(v.Plus (r).Plus (d));
glxVertex3v(v.Plus (r).Minus(d));
glxVertex3v(v.Minus(r).Minus(d));
glxVertex3v(v.Minus(r).Plus (d));
}
glEnd();
glxDepthRangeOffset(0);
}
for(i = 0; i < SK.entity.n; i++) {
Entity *e = &(SK.entity.elem[i]);
if(e->IsPoint())
{
continue; // already handled
}
e->Draw();
}
}
void Entity::Draw(void) {
hStyle hs = Style::ForEntity(h);
dogd.lineWidth = Style::Width(hs);
glLineWidth((float)dogd.lineWidth);
glxColorRGB(Style::Color(hs));
dogd.drawing = true;
DrawOrGetDistance();
}
void Entity::GenerateEdges(SEdgeList *el, bool includingConstruction) {
if(construction && !includingConstruction) return;
SBezierList sbl;
ZERO(&sbl);
GenerateBezierCurves(&sbl);
int i, j;
for(i = 0; i < sbl.l.n; i++) {
SBezier *sb = &(sbl.l.elem[i]);
List<Vector> lv;
ZERO(&lv);
sb->MakePwlInto(&lv);
for(j = 1; j < lv.n; j++) {
el->AddEdge(lv.elem[j-1], lv.elem[j], style.v);
}
lv.Clear();
}
sbl.Clear();
}
double Entity::GetDistance(Point2d mp) {
dogd.drawing = false;
dogd.mp = mp;
dogd.dmin = 1e12;
DrawOrGetDistance();
return dogd.dmin;
}
Vector Entity::GetReferencePos(void) {
dogd.drawing = false;
dogd.refp = SS.GW.offset.ScaledBy(-1);
DrawOrGetDistance();
return dogd.refp;
}
bool Entity::IsVisible(void) {
Group *g = SK.GetGroup(group);
if(g->h.v == Group::HGROUP_REFERENCES.v && IsNormal()) {
// The reference normals are always shown
return true;
}
if(!(g->IsVisible())) return false;
// Don't check if points are hidden; this gets called only for
// selected or hovered points, and those should always be shown.
if(IsNormal() && !SS.GW.showNormals) return false;
if(!SS.GW.showWorkplanes) {
if(IsWorkplane() && !h.isFromRequest()) {
if(g->h.v != SS.GW.activeGroup.v) {
// The group-associated workplanes are hidden outside
// their group.
return false;
}
}
}
if(style.v) {
Style *s = Style::Get(style);
if(!s->visible) return false;
}
if(forceHidden) return false;
return true;
}
void Entity::CalculateNumerical(bool forExport) {
if(IsPoint()) actPoint = PointGetNum();
if(IsNormal()) actNormal = NormalGetNum();
if(type == DISTANCE || type == DISTANCE_N_COPY) {
actDistance = DistanceGetNum();
}
if(IsFace()) {
actPoint = FaceGetPointNum();
Vector n = FaceGetNormalNum();
actNormal = Quaternion::From(0, n.x, n.y, n.z);
}
if(forExport) {
// Visibility in copied import entities follows source file
actVisible = IsVisible();
} else {
// Copied entities within a file are always visible
actVisible = true;
}
}
bool Entity::PointIsFromReferences(void) {
return h.request().IsFromReferences();
}
//-----------------------------------------------------------------------------
// Compute a cubic, second derivative continuous, interpolating spline. Same
// routine for periodic splines (in a loop) or open splines (with specified
// end tangents).
//-----------------------------------------------------------------------------
void Entity::ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) {
static const int MAX_N = BandedMatrix::MAX_UNKNOWNS;
int ep = extraPoints;
// The number of unknowns to solve for.
int n = periodic ? 3 + ep : ep;
if(n >= MAX_N) oops();
// The number of on-curve points, one more than the number of segments.
int pts = periodic ? 4 + ep : 2 + ep;
int i, j, a;
// The starting and finishing control points that define our end tangents
// (if the spline isn't periodic), and the on-curve points.
Vector ctrl_s, ctrl_f, pt[MAX_N+4];
if(periodic) {
for(i = 0; i < ep + 3; i++) {
pt[i] = SK.GetEntity(point[i])->PointGetNum();
}
pt[i++] = SK.GetEntity(point[0])->PointGetNum();
} else {
ctrl_s = SK.GetEntity(point[1])->PointGetNum();
ctrl_f = SK.GetEntity(point[ep+2])->PointGetNum();
j = 0;
pt[j++] = SK.GetEntity(point[0])->PointGetNum();
for(i = 2; i <= ep + 1; i++) {
pt[j++] = SK.GetEntity(point[i])->PointGetNum();
}
pt[j++] = SK.GetEntity(point[ep+3])->PointGetNum();
}
// The unknowns that we will be solving for, a set for each coordinate.
double Xx[MAX_N], Xy[MAX_N], Xz[MAX_N];
// For a cubic Bezier section f(t) as t goes from 0 to 1,
// f' (0) = 3*(P1 - P0)
// f' (1) = 3*(P3 - P2)
// f''(0) = 6*(P0 - 2*P1 + P2)
// f''(1) = 6*(P3 - 2*P2 + P1)
for(a = 0; a < 3; a++) {
BandedMatrix bm;
ZERO(&bm);
bm.n = n;
for(i = 0; i < n; i++) {
int im, it, ip;
if(periodic) {
im = WRAP(i - 1, n);
it = i;
ip = WRAP(i + 1, n);
} else {
im = i;
it = i + 1;
ip = i + 2;
}
// All of these are expressed in terms of a constant part, and
// of X[i-1], X[i], and X[i+1]; so let these be the four
// components of that vector;
Vector4 A, B, C, D, E;
// The on-curve interpolated point
C = Vector4::From((pt[it]).Element(a), 0, 0, 0);
// control point one back, C - X[i]
B = C.Plus(Vector4::From(0, 0, -1, 0));
// control point one forward, C + X[i]
D = C.Plus(Vector4::From(0, 0, 1, 0));
// control point two back
if(i == 0 && !periodic) {
A = Vector4::From(ctrl_s.Element(a), 0, 0, 0);
} else {
// pt[im] + X[i-1]
A = Vector4::From(pt[im].Element(a), 1, 0, 0);
}
// control point two forward
if(i == (n - 1) && !periodic) {
E = Vector4::From(ctrl_f.Element(a), 0, 0, 0);
} else {
// pt[ip] - X[i+1]
E = Vector4::From((pt[ip]).Element(a), 0, 0, -1);
}
// Write the second derivatives of each segment, dropping constant
Vector4 fprev_pp = (C.Minus(B.ScaledBy(2))).Plus(A),
fnext_pp = (C.Minus(D.ScaledBy(2))).Plus(E),
eq = fprev_pp.Minus(fnext_pp);
bm.B[i] = -eq.w;
if(periodic) {
bm.A[i][WRAP(i-2, n)] = eq.x;
bm.A[i][WRAP(i-1, n)] = eq.y;
bm.A[i][i] = eq.z;
} else {
// The wrapping would work, except when n = 1 and everything
// wraps to zero...
if(i > 0) bm.A[i][i - 1] = eq.x;
bm.A[i][i] = eq.y;
if(i < (n-1)) bm.A[i][i + 1] = eq.z;
}
}
bm.Solve();
double *X = (a == 0) ? Xx :
(a == 1) ? Xy :
Xz;
memcpy(X, bm.X, n*sizeof(double));
}
for(i = 0; i < pts - 1; i++) {
Vector p0, p1, p2, p3;
if(periodic) {
p0 = pt[i];
int iw = WRAP(i - 1, n);
p1 = p0.Plus(Vector::From(Xx[iw], Xy[iw], Xz[iw]));
} else if(i == 0) {
p0 = pt[0];
p1 = ctrl_s;
} else {
p0 = pt[i];
p1 = p0.Plus(Vector::From(Xx[i-1], Xy[i-1], Xz[i-1]));
}
if(periodic) {
p3 = pt[i+1];
int iw = WRAP(i, n);
p2 = p3.Minus(Vector::From(Xx[iw], Xy[iw], Xz[iw]));
} else if(i == (pts - 2)) {
p3 = pt[pts-1];
p2 = ctrl_f;
} else {
p3 = pt[i+1];
p2 = p3.Minus(Vector::From(Xx[i], Xy[i], Xz[i]));
}
SBezier sb = SBezier::From(p0, p1, p2, p3);
sbl->l.Add(&sb);
}
}
void Entity::GenerateBezierCurves(SBezierList *sbl) {
SBezier sb;
int i = sbl->l.n;
switch(type) {
case LINE_SEGMENT: {
Vector a = SK.GetEntity(point[0])->PointGetNum();
Vector b = SK.GetEntity(point[1])->PointGetNum();
sb = SBezier::From(a, b);
sbl->l.Add(&sb);
break;
}
case CUBIC:
ComputeInterpolatingSpline(sbl, false);
break;
case CUBIC_PERIODIC:
ComputeInterpolatingSpline(sbl, true);
break;
case CIRCLE:
case ARC_OF_CIRCLE: {
Vector center = SK.GetEntity(point[0])->PointGetNum();
Quaternion q = SK.GetEntity(normal)->NormalGetNum();
Vector u = q.RotationU(), v = q.RotationV();
double r = CircleGetRadiusNum();
double thetaa, thetab, dtheta;
if(r < LENGTH_EPS) {
// If a circle or an arc gets dragged through zero radius,
// then we just don't generate anything.
break;
}
if(type == CIRCLE) {
thetaa = 0;
thetab = 2*PI;
dtheta = 2*PI;
} else {
ArcGetAngles(&thetaa, &thetab, &dtheta);
}
int i, n;
if(dtheta > (3*PI/2 + 0.01)) {
n = 4;
} else if(dtheta > (PI + 0.01)) {
n = 3;
} else if(dtheta > (PI/2 + 0.01)) {
n = 2;
} else {
n = 1;
}
dtheta /= n;
for(i = 0; i < n; i++) {
double s, c;
c = cos(thetaa);
s = sin(thetaa);
// The start point of the curve, and the tangent vector at
// that start point.
Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
thetaa += dtheta;
c = cos(thetaa);
s = sin(thetaa);
Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)),
t2 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c));
// The control point must lie on both tangents.
Vector p1 = Vector::AtIntersectionOfLines(p0, p0.Plus(t0),
p2, p2.Plus(t2),
NULL);
SBezier sb = SBezier::From(p0, p1, p2);
sb.weight[1] = cos(dtheta/2);
sbl->l.Add(&sb);
}
break;
}
case TTF_TEXT: {
Vector topLeft = SK.GetEntity(point[0])->PointGetNum();
Vector botLeft = SK.GetEntity(point[1])->PointGetNum();
Vector n = Normal()->NormalN();
Vector v = topLeft.Minus(botLeft);
Vector u = (v.Cross(n)).WithMagnitude(v.Magnitude());
SS.fonts.PlotString(font.str, str.str, 0, sbl, botLeft, u, v);
break;
}
default:
// Not a problem, points and normals and such don't generate curves
break;
}
// 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;
}
}
void Entity::DrawOrGetDistance(void) {
if(!IsVisible()) return;
Group *g = SK.GetGroup(group);
switch(type) {
case POINT_N_COPY:
case POINT_N_TRANS:
case POINT_N_ROT_TRANS:
case POINT_N_ROT_AA:
case POINT_IN_3D:
case POINT_IN_2D: {
Vector v = PointGetNum();
dogd.refp = v;
if(dogd.drawing) {
double s = 3.5;
Vector r = SS.GW.projRight.ScaledBy(s/SS.GW.scale);
Vector d = SS.GW.projUp.ScaledBy(s/SS.GW.scale);
glxColorRGB(Style::Color(Style::DATUM));
glxDepthRangeOffset(6);
glBegin(GL_QUADS);
glxVertex3v(v.Plus (r).Plus (d));
glxVertex3v(v.Plus (r).Minus(d));
glxVertex3v(v.Minus(r).Minus(d));
glxVertex3v(v.Minus(r).Plus (d));
glEnd();
glxDepthRangeOffset(0);
} else {
Point2d pp = SS.GW.ProjectPoint(v);
dogd.dmin = pp.DistanceTo(dogd.mp) - 6;
}
break;
}
case NORMAL_N_COPY:
case NORMAL_N_ROT:
case NORMAL_N_ROT_AA:
case NORMAL_IN_3D:
case NORMAL_IN_2D: {
int i;
for(i = 0; i < 2; i++) {
if(i == 0 && !SS.GW.showNormals) {
// When the normals are hidden, we will continue to show
// the coordinate axes at the bottom left corner, but
// not at the origin.
continue;
}
hRequest hr = h.request();
// Always draw the x, y, and z axes in red, green, and blue;
// brighter for the ones at the bottom left of the screen,
// dimmer for the ones at the model origin.
int f = (i == 0 ? 100 : 255);
if(hr.v == Request::HREQUEST_REFERENCE_XY.v) {
glxColorRGB(RGB(0, 0, f));
} else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) {
glxColorRGB(RGB(f, 0, 0));
} else if(hr.v == Request::HREQUEST_REFERENCE_ZX.v) {
glxColorRGB(RGB(0, f, 0));
} else {
glxColorRGB(Style::Color(Style::NORMALS));
if(i > 0) break;
}
Quaternion q = NormalGetNum();
Vector tail;
if(i == 0) {
tail = SK.GetEntity(point[0])->PointGetNum();
glLineWidth(1);
} else {
// Draw an extra copy of the x, y, and z axes, that's
// always in the corner of the view and at the front.
// So those are always available, perhaps useful.
double s = SS.GW.scale;
double h = 60 - SS.GW.height/2;
double w = 60 - SS.GW.width/2;
tail = SS.GW.projRight.ScaledBy(w/s).Plus(
SS.GW.projUp. ScaledBy(h/s)).Minus(SS.GW.offset);
glxDepthRangeLockToFront(true);
glLineWidth(2);
}
Vector v = (q.RotationN()).WithMagnitude(50/SS.GW.scale);
Vector tip = tail.Plus(v);
LineDrawOrGetDistance(tail, tip);
v = v.WithMagnitude(12/SS.GW.scale);
Vector axis = q.RotationV();
LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis, 0.6)));
LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis,-0.6)));
}
glxDepthRangeLockToFront(false);
break;
}
case DISTANCE:
case DISTANCE_N_COPY:
// These are used only as data structures, nothing to display.
break;
case WORKPLANE: {
Vector p;
p = SK.GetEntity(point[0])->PointGetNum();
Vector u = Normal()->NormalU();
Vector v = Normal()->NormalV();
double s = (min(SS.GW.width, SS.GW.height))*0.45/SS.GW.scale;
Vector us = u.ScaledBy(s);
Vector vs = v.ScaledBy(s);
Vector pp = p.Plus (us).Plus (vs);
Vector pm = p.Plus (us).Minus(vs);
Vector mm = p.Minus(us).Minus(vs), mm2 = mm;
Vector mp = p.Minus(us).Plus (vs);
glLineWidth(1);
glxColorRGB(Style::Color(Style::NORMALS));
glEnable(GL_LINE_STIPPLE);
glLineStipple(3, 0x1111);
if(!h.isFromRequest()) {
mm = mm.Plus(v.ScaledBy(60/SS.GW.scale));
mm2 = mm2.Plus(u.ScaledBy(60/SS.GW.scale));
LineDrawOrGetDistance(mm2, mm);
}
LineDrawOrGetDistance(pp, pm);
LineDrawOrGetDistance(pm, mm2);
LineDrawOrGetDistance(mm, mp);
LineDrawOrGetDistance(mp, pp);
glDisable(GL_LINE_STIPPLE);
char *str = DescriptionString()+5;
double th = DEFAULT_TEXT_HEIGHT;
if(dogd.drawing) {
glxWriteText(str, th, mm2, u, v, NULL, NULL);
} else {
Vector pos = mm2.Plus(u.ScaledBy(glxStrWidth(str, th)/2)).Plus(
v.ScaledBy(glxStrHeight(th)/2));
Point2d pp = SS.GW.ProjectPoint(pos);
dogd.dmin = min(dogd.dmin, pp.DistanceTo(dogd.mp) - 10);
// If a line lies in a plane, then select the line, not
// the plane.
dogd.dmin += 3;
}
break;
}
case LINE_SEGMENT:
case CIRCLE:
case ARC_OF_CIRCLE:
case CUBIC:
case CUBIC_PERIODIC:
case TTF_TEXT:
// Nothing but the curve(s).
break;
case FACE_NORMAL_PT:
case FACE_XPROD:
case FACE_N_ROT_TRANS:
case FACE_N_TRANS:
case FACE_N_ROT_AA:
// Do nothing; these are drawn with the triangle mesh
break;
default:
oops();
}
// And draw the curves; generate the rational polynomial curves for
// everything, then piecewise linearize them, and display those.
SEdgeList sel;
ZERO(&sel);
GenerateEdges(&sel, true);
int i;
for(i = 0; i < sel.l.n; i++) {
SEdge *se = &(sel.l.elem[i]);
LineDrawOrGetDistance(se->a, se->b, true);
}
sel.Clear();
}

View File

@ -1,732 +0,0 @@
//-----------------------------------------------------------------------------
// The 2d vector output stuff that isn't specific to any particular file
// format: getting the appropriate lines and curves, performing hidden line
// removal, calculating bounding boxes, and so on. Also raster and triangle
// mesh output.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
#include <png.h>
void SolveSpace::ExportSectionTo(char *filename) {
Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
gn = gn.WithMagnitude(1);
Group *g = SK.GetGroup(SS.GW.activeGroup);
g->GenerateDisplayItems();
if(g->displayMesh.IsEmpty()) {
Error("No solid model present; draw one with extrudes and revolves, "
"or use Export 2d View to export bare lines and curves.");
return;
}
// The plane in which the exported section lies; need this because we'll
// reorient from that plane into the xy plane before exporting.
Vector origin, u, v, n;
double d;
SS.GW.GroupSelection();
#define gs (SS.GW.gs)
if((gs.n == 0 && g->activeWorkplane.v != Entity::FREE_IN_3D.v)) {
Entity *wrkpl = SK.GetEntity(g->activeWorkplane);
origin = wrkpl->WorkplaneGetOffset();
n = wrkpl->Normal()->NormalN();
u = wrkpl->Normal()->NormalU();
v = wrkpl->Normal()->NormalV();
} else if(gs.n == 1 && gs.faces == 1) {
Entity *face = SK.GetEntity(gs.entity[0]);
origin = face->FaceGetPointNum();
n = face->FaceGetNormalNum();
if(n.Dot(gn) < 0) n = n.ScaledBy(-1);
u = n.Normal(0);
v = n.Normal(1);
} else if(gs.n == 3 && gs.vectors == 2 && gs.points == 1) {
Vector ut = SK.GetEntity(gs.entity[0])->VectorGetNum(),
vt = SK.GetEntity(gs.entity[1])->VectorGetNum();
ut = ut.WithMagnitude(1);
vt = vt.WithMagnitude(1);
if(fabs(SS.GW.projUp.Dot(vt)) < fabs(SS.GW.projUp.Dot(ut))) {
SWAP(Vector, ut, vt);
}
if(SS.GW.projRight.Dot(ut) < 0) ut = ut.ScaledBy(-1);
if(SS.GW.projUp. Dot(vt) < 0) vt = vt.ScaledBy(-1);
origin = SK.GetEntity(gs.point[0])->PointGetNum();
n = ut.Cross(vt);
u = ut.WithMagnitude(1);
v = (n.Cross(u)).WithMagnitude(1);
} else {
Error("Bad selection for export section. Please select:\n\n"
" * nothing, with an active workplane "
"(workplane is section plane)\n"
" * a face (section plane through face)\n"
" * a point and two line segments "
"(plane through point and parallel to lines)\n");
return;
}
SS.GW.ClearSelection();
n = n.WithMagnitude(1);
d = origin.Dot(n);
SEdgeList el;
ZERO(&el);
SBezierList bl;
ZERO(&bl);
// If there's a mesh, then grab the edges from it.
g->runningMesh.MakeEdgesInPlaneInto(&el, n, d);
// If there's a shell, then grab the edges and possibly Beziers.
g->runningShell.MakeSectionEdgesInto(n, d,
&el,
(SS.exportPwlCurves || fabs(SS.exportOffset) > LENGTH_EPS) ? NULL : &bl);
// All of these are solid model edges, so use the appropriate style.
SEdge *se;
for(se = el.l.First(); se; se = el.l.NextAfter(se)) {
se->auxA = Style::SOLID_EDGE;
}
SBezier *sb;
for(sb = bl.l.First(); sb; sb = bl.l.NextAfter(sb)) {
sb->auxA = Style::SOLID_EDGE;
}
el.CullExtraneousEdges();
bl.CullIdenticalBeziers();
// And write the edges.
VectorFileWriter *out = VectorFileWriter::ForFile(filename);
if(out) {
// parallel projection (no perspective), and no mesh
ExportLinesAndMesh(&el, &bl, NULL,
u, v, n, origin, 0,
out);
}
el.Clear();
bl.Clear();
}
void SolveSpace::ExportViewOrWireframeTo(char *filename, bool wireframe) {
int i;
SEdgeList edges;
ZERO(&edges);
SBezierList beziers;
ZERO(&beziers);
SMesh *sm = NULL;
if(SS.GW.showShaded) {
Group *g = SK.GetGroup(SS.GW.activeGroup);
g->GenerateDisplayItems();
sm = &(g->displayMesh);
}
if(sm && sm->IsEmpty()) {
sm = NULL;
}
for(i = 0; i < SK.entity.n; i++) {
Entity *e = &(SK.entity.elem[i]);
if(!e->IsVisible()) continue;
if(e->construction) continue;
if(SS.exportPwlCurves || (sm && !SS.GW.showHdnLines) ||
fabs(SS.exportOffset) > LENGTH_EPS)
{
// We will be doing hidden line removal, which we can't do on
// exact curves; so we need things broken down to pwls. Same
// problem with cutter radius compensation.
e->GenerateEdges(&edges);
} else {
e->GenerateBezierCurves(&beziers);
}
}
if(SS.GW.showEdges) {
Group *g = SK.GetGroup(SS.GW.activeGroup);
g->GenerateDisplayItems();
SEdgeList *selr = &(g->displayEdges);
SEdge *se;
for(se = selr->l.First(); se; se = selr->l.NextAfter(se)) {
edges.AddEdge(se->a, se->b, Style::SOLID_EDGE);
}
}
if(SS.GW.showConstraints) {
Constraint *c;
for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) {
c->GetEdges(&edges);
}
}
if(wireframe) {
VectorFileWriter *out = VectorFileWriter::ForFile(filename);
if(out) {
ExportWireframeCurves(&edges, &beziers, out);
}
} else {
Vector u = SS.GW.projRight,
v = SS.GW.projUp,
n = u.Cross(v),
origin = SS.GW.offset.ScaledBy(-1);
VectorFileWriter *out = VectorFileWriter::ForFile(filename);
if(out) {
ExportLinesAndMesh(&edges, &beziers, sm,
u, v, n, origin, SS.CameraTangent()*SS.GW.scale,
out);
}
if(out && !out->HasCanvasSize()) {
// These file formats don't have a canvas size, so they just
// get exported in the raw coordinate system. So indicate what
// that was on-screen.
SS.justExportedInfo.draw = true;
SS.justExportedInfo.pt = origin;
SS.justExportedInfo.u = u;
SS.justExportedInfo.v = v;
InvalidateGraphics();
}
}
edges.Clear();
beziers.Clear();
}
void SolveSpace::ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl,
VectorFileWriter *out)
{
SBezierLoopSetSet sblss;
ZERO(&sblss);
SEdge *se;
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
SBezier sb = SBezier::From(
(se->a).ScaledBy(1.0 / SS.exportScale),
(se->b).ScaledBy(1.0 / SS.exportScale));
sblss.AddOpenPath(&sb);
}
sbl->ScaleSelfBy(1.0/SS.exportScale);
SBezier *sb;
for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) {
sblss.AddOpenPath(sb);
}
out->Output(&sblss, NULL);
sblss.Clear();
}
void SolveSpace::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm,
Vector u, Vector v, Vector n,
Vector origin, double cameraTan,
VectorFileWriter *out)
{
double s = 1.0 / SS.exportScale;
// 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)) {
// 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)) {
*b = b->InPerspective(u, v, n, origin, cameraTan);
int i;
for(i = 0; i <= b->deg; i++) {
b->ctrl[i] = (b->ctrl[i]).ScaledBy(s);
}
}
}
// If cutter radius compensation is requested, then perform it now
if(fabs(SS.exportOffset) > LENGTH_EPS) {
// assemble those edges into a polygon, and clear the edge list
SPolygon sp;
ZERO(&sp);
sel->AssemblePolygon(&sp, NULL);
sel->Clear();
SPolygon compd;
ZERO(&compd);
sp.normal = Vector::From(0, 0, -1);
sp.FixContourDirections();
sp.OffsetInto(&compd, SS.exportOffset*s);
sp.Clear();
compd.MakeEdgesInto(sel);
compd.Clear();
}
// Now the triangle mesh; project, then build a BSP to perform
// occlusion testing and generated the shaded surfaces.
SMesh smp;
ZERO(&smp);
if(sm) {
Vector l0 = (SS.lightDir[0]).WithMagnitude(1),
l1 = (SS.lightDir[1]).WithMagnitude(1);
STriangle *tr;
for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
STriangle tt = *tr;
tt.a = (tt.a).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
tt.b = (tt.b).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
tt.c = (tt.c).InPerspective(u, v, n, origin, cameraTan).ScaledBy(s);
// And calculate lighting for the triangle
Vector n = tt.Normal().WithMagnitude(1);
double lighting = SS.ambientIntensity +
max(0, (SS.lightIntensity[0])*(n.Dot(l0))) +
max(0, (SS.lightIntensity[1])*(n.Dot(l1)));
double r = min(1, REDf (tt.meta.color)*lighting),
g = min(1, GREENf(tt.meta.color)*lighting),
b = min(1, BLUEf (tt.meta.color)*lighting);
tt.meta.color = RGBf(r, g, b);
smp.AddTriangle(&tt);
}
}
// Use the BSP routines to generate the split triangles in paint order.
SBsp3 *bsp = SBsp3::FromMesh(&smp);
SMesh sms;
ZERO(&sms);
bsp->GenerateInPaintOrder(&sms);
// And cull the back-facing triangles
STriangle *tr;
sms.l.ClearTags();
for(tr = sms.l.First(); tr; tr = sms.l.NextAfter(tr)) {
Vector n = tr->Normal();
if(n.z < 0) {
tr->tag = 1;
}
}
sms.l.RemoveTagged();
// And now we perform hidden line removal if requested
SEdgeList hlrd;
ZERO(&hlrd);
if(sm && !SS.GW.showHdnLines) {
SKdNode *root = SKdNode::From(&smp);
// Generate the edges where a curved surface turns from front-facing
// to back-facing.
if(SS.GW.showEdges) {
root->MakeCertainEdgesInto(sel, SKdNode::TURNING_EDGES,
false, NULL, NULL);
}
root->ClearTags();
int cnt = 1234;
SEdge *se;
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
if(se->auxA == Style::CONSTRAINT) {
// Constraints should not get hidden line removed; they're
// always on top.
hlrd.AddEdge(se->a, se->b, se->auxA);
continue;
}
SEdgeList out;
ZERO(&out);
// Split the original edge against the mesh
out.AddEdge(se->a, se->b, se->auxA);
root->OcclusionTestLine(*se, &out, cnt);
// the occlusion test splits unnecessarily; so fix those
out.MergeCollinearSegments(se->a, se->b);
cnt++;
// And add the results to our output
SEdge *sen;
for(sen = out.l.First(); sen; sen = out.l.NextAfter(sen)) {
hlrd.AddEdge(sen->a, sen->b, sen->auxA);
}
out.Clear();
}
sel = &hlrd;
}
// 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)) {
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(int i = 0; i <= b->deg; i++) {
b->ctrl[i].z = 0;
}
}
// If possible, then we will assemble these output curves into loops. They
// will then get exported as closed paths.
SBezierLoopSetSet sblss;
ZERO(&sblss);
SBezierList leftovers;
ZERO(&leftovers);
SSurface srf = SSurface::FromPlane(Vector::From(0, 0, 0),
Vector::From(1, 0, 0),
Vector::From(0, 1, 0));
SPolygon spxyz;
ZERO(&spxyz);
bool allClosed;
SEdge notClosedAt;
sbl->l.ClearTags();
sblss.FindOuterFacesFrom(sbl, &spxyz, &srf,
SS.ChordTolMm()*s,
&allClosed, &notClosedAt,
NULL, NULL,
&leftovers);
for(b = leftovers.l.First(); b; b = leftovers.l.NextAfter(b)) {
sblss.AddOpenPath(b);
}
// Now write the lines and triangles to the output file
out->Output(&sblss, &sms);
leftovers.Clear();
spxyz.Clear();
sblss.Clear();
smp.Clear();
sms.Clear();
hlrd.Clear();
}
double VectorFileWriter::MmToPts(double mm) {
// 72 points in an inch
return (mm/25.4)*72;
}
VectorFileWriter *VectorFileWriter::ForFile(char *filename) {
VectorFileWriter *ret;
if(StringEndsIn(filename, ".dxf")) {
static DxfFileWriter DxfWriter;
ret = &DxfWriter;
} else if(StringEndsIn(filename, ".ps") || StringEndsIn(filename, ".eps")) {
static EpsFileWriter EpsWriter;
ret = &EpsWriter;
} else if(StringEndsIn(filename, ".pdf")) {
static PdfFileWriter PdfWriter;
ret = &PdfWriter;
} else if(StringEndsIn(filename, ".svg")) {
static SvgFileWriter SvgWriter;
ret = &SvgWriter;
} else if(StringEndsIn(filename, ".plt")||StringEndsIn(filename, ".hpgl")) {
static HpglFileWriter HpglWriter;
ret = &HpglWriter;
} else if(StringEndsIn(filename, ".step")||StringEndsIn(filename, ".stp")) {
static Step2dFileWriter Step2dWriter;
ret = &Step2dWriter;
} else if(StringEndsIn(filename, ".txt")) {
static GCodeFileWriter GCodeWriter;
ret = &GCodeWriter;
} else {
Error("Can't identify output file type from file extension of "
"filename '%s'; try "
".step, .stp, .dxf, .svg, .plt, .hpgl, .pdf, .txt, "
".eps, or .ps.",
filename);
return NULL;
}
FILE *f = fopen(filename, "wb");
if(!f) {
Error("Couldn't write to '%s'", filename);
return NULL;
}
ret->f = f;
return ret;
}
void VectorFileWriter::Output(SBezierLoopSetSet *sblss, SMesh *sm) {
STriangle *tr;
SBezier *b;
// First calculate the bounding box.
ptMin = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE);
ptMax = Vector::From(VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE);
if(sm) {
for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
(tr->a).MakeMaxMin(&ptMax, &ptMin);
(tr->b).MakeMaxMin(&ptMax, &ptMin);
(tr->c).MakeMaxMin(&ptMax, &ptMin);
}
}
if(sblss) {
SBezierLoopSet *sbls;
for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
SBezierLoop *sbl;
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) {
for(int i = 0; i <= b->deg; i++) {
(b->ctrl[i]).MakeMaxMin(&ptMax, &ptMin);
}
}
}
}
}
// And now we compute the canvas size.
double s = 1.0 / SS.exportScale;
if(SS.exportCanvasSizeAuto) {
// It's based on the calculated bounding box; we grow it along each
// boundary by the specified amount.
ptMin.x -= s*SS.exportMargin.left;
ptMax.x += s*SS.exportMargin.right;
ptMin.y -= s*SS.exportMargin.bottom;
ptMax.y += s*SS.exportMargin.top;
} else {
ptMin.x = -(s*SS.exportCanvas.dx);
ptMin.y = -(s*SS.exportCanvas.dy);
ptMax.x = ptMin.x + (s*SS.exportCanvas.width);
ptMax.y = ptMin.y + (s*SS.exportCanvas.height);
}
StartFile();
if(sm && SS.exportShadedTriangles) {
for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
Triangle(tr);
}
}
if(sblss) {
SBezierLoopSet *sbls;
for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
SBezierLoop *sbl;
sbl = sbls->l.First();
if(!sbl) continue;
b = sbl->l.First();
if(!b || !Style::Exportable(b->auxA)) continue;
hStyle hs = { b->auxA };
Style *stl = Style::Get(hs);
double lineWidth = Style::WidthMm(b->auxA)*s;
DWORD strokeRgb = Style::Color(hs, true);
DWORD fillRgb = Style::FillColor(hs, true);
StartPath(strokeRgb, lineWidth, stl->filled, fillRgb);
for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) {
for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) {
Bezier(b);
}
}
FinishPath(strokeRgb, lineWidth, stl->filled, fillRgb);
}
}
FinishAndCloseFile();
}
void VectorFileWriter::BezierAsPwl(SBezier *sb) {
List<Vector> lv;
ZERO(&lv);
sb->MakePwlInto(&lv, SS.ChordTolMm() / SS.exportScale);
int i;
for(i = 1; i < lv.n; i++) {
SBezier sb = SBezier::From(lv.elem[i-1], lv.elem[i]);
Bezier(&sb);
}
lv.Clear();
}
void VectorFileWriter::BezierAsNonrationalCubic(SBezier *sb, int depth) {
Vector t0 = sb->TangentAt(0), t1 = sb->TangentAt(1);
// The curve is correct, and the first derivatives are correct, at the
// endpoints.
SBezier bnr = SBezier::From(
sb->Start(),
sb->Start().Plus(t0.ScaledBy(1.0/3)),
sb->Finish().Minus(t1.ScaledBy(1.0/3)),
sb->Finish());
double tol = SS.ChordTolMm() / SS.exportScale;
// Arbitrary choice, but make it a little finer than pwl tolerance since
// it should be easier to achieve that with the smooth curves.
tol /= 2;
bool closeEnough = true;
int i;
for(i = 1; i <= 3; i++) {
double t = i/4.0;
Vector p0 = sb->PointAt(t),
pn = bnr.PointAt(t);
double d = (p0.Minus(pn)).Magnitude();
if(d > tol) {
closeEnough = false;
}
}
if(closeEnough || depth > 3) {
Bezier(&bnr);
} else {
SBezier bef, aft;
sb->SplitAt(0.5, &bef, &aft);
BezierAsNonrationalCubic(&bef, depth+1);
BezierAsNonrationalCubic(&aft, depth+1);
}
}
//-----------------------------------------------------------------------------
// Export a triangle mesh, in the requested format.
//-----------------------------------------------------------------------------
void SolveSpace::ExportMeshTo(char *filename) {
SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh);
if(m->IsEmpty()) {
Error("Active group mesh is empty; nothing to export.");
return;
}
FILE *f = fopen(filename, "wb");
if(!f) {
Error("Couldn't write to '%s'", filename);
return;
}
if(StringEndsIn(filename, ".stl")) {
ExportMeshAsStlTo(f, m);
} else if(StringEndsIn(filename, ".obj")) {
ExportMeshAsObjTo(f, m);
} else {
Error("Can't identify output file type from file extension of "
"filename '%s'; try .stl, .obj.", filename);
}
fclose(f);
}
//-----------------------------------------------------------------------------
// Export the mesh as an STL file; it should always be vertex-to-vertex and
// not self-intersecting, so not much to do.
//-----------------------------------------------------------------------------
void SolveSpace::ExportMeshAsStlTo(FILE *f, SMesh *sm) {
char str[80];
memset(str, 0, sizeof(str));
strcpy(str, "STL exported mesh");
fwrite(str, 1, 80, f);
DWORD n = sm->l.n;
fwrite(&n, 4, 1, f);
double s = SS.exportScale;
int i;
for(i = 0; i < sm->l.n; i++) {
STriangle *tr = &(sm->l.elem[i]);
Vector n = tr->Normal().WithMagnitude(1);
float w;
w = (float)n.x; fwrite(&w, 4, 1, f);
w = (float)n.y; fwrite(&w, 4, 1, f);
w = (float)n.z; fwrite(&w, 4, 1, f);
w = (float)((tr->a.x)/s); fwrite(&w, 4, 1, f);
w = (float)((tr->a.y)/s); fwrite(&w, 4, 1, f);
w = (float)((tr->a.z)/s); fwrite(&w, 4, 1, f);
w = (float)((tr->b.x)/s); fwrite(&w, 4, 1, f);
w = (float)((tr->b.y)/s); fwrite(&w, 4, 1, f);
w = (float)((tr->b.z)/s); fwrite(&w, 4, 1, f);
w = (float)((tr->c.x)/s); fwrite(&w, 4, 1, f);
w = (float)((tr->c.y)/s); fwrite(&w, 4, 1, f);
w = (float)((tr->c.z)/s); fwrite(&w, 4, 1, f);
fputc(0, f);
fputc(0, f);
}
}
//-----------------------------------------------------------------------------
// Export the mesh as Wavefront OBJ format. This requires us to reduce all the
// identical vertices to the same identifier, so do that first.
//-----------------------------------------------------------------------------
void SolveSpace::ExportMeshAsObjTo(FILE *f, SMesh *sm) {
SPointList spl;
ZERO(&spl);
STriangle *tr;
for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
spl.IncrementTagFor(tr->a);
spl.IncrementTagFor(tr->b);
spl.IncrementTagFor(tr->c);
}
// Output all the vertices.
SPoint *sp;
for(sp = spl.l.First(); sp; sp = spl.l.NextAfter(sp)) {
fprintf(f, "v %.10f %.10f %.10f\r\n",
sp->p.x / SS.exportScale,
sp->p.y / SS.exportScale,
sp->p.z / SS.exportScale);
}
// And now all the triangular faces, in terms of those vertices. The
// file format counts from 1, not 0.
for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) {
fprintf(f, "f %d %d %d\r\n",
spl.IndexForPoint(tr->a) + 1,
spl.IndexForPoint(tr->b) + 1,
spl.IndexForPoint(tr->c) + 1);
}
spl.Clear();
}
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
void SolveSpace::ExportAsPngTo(char *filename) {
int w = (int)SS.GW.width, h = (int)SS.GW.height;
// No guarantee that the back buffer contains anything valid right now,
// so repaint the scene. And hide the toolbar too.
int prevShowToolbar = SS.showToolbar;
SS.showToolbar = false;
SS.GW.Paint();
SS.showToolbar = prevShowToolbar;
FILE *f = fopen(filename, "wb");
if(!f) goto err;
png_struct *png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if(!png_ptr) goto err;
png_info *info_ptr = png_create_info_struct(png_ptr);
if(!png_ptr) goto err;
if(setjmp(png_jmpbuf(png_ptr))) goto err;
png_init_io(png_ptr, f);
// glReadPixels wants to align things on 4-boundaries, and there's 3
// bytes per pixel. As long as the row width is divisible by 4, all
// works out.
w &= ~3; h &= ~3;
png_set_IHDR(png_ptr, info_ptr, w, h,
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
png_write_info(png_ptr, info_ptr);
// Get the pixel data from the framebuffer
BYTE *pixels = (BYTE *)AllocTemporary(3*w*h);
BYTE **rowptrs = (BYTE **)AllocTemporary(h*sizeof(BYTE *));
glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pixels);
int y;
for(y = 0; y < h; y++) {
// gl puts the origin at lower left, but png puts it top left
rowptrs[y] = pixels + ((h - 1) - y)*(3*w);
}
png_write_image(png_ptr, rowptrs);
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(f);
return;
err:
Error("Error writing PNG file '%s'", filename);
if(f) fclose(f);
return;
}

View File

@ -1,363 +0,0 @@
//-----------------------------------------------------------------------------
// Export a STEP file describing our ratpoly shell.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
void StepFileWriter::WriteHeader(void) {
fprintf(f,
"ISO-10303-21;\n"
"HEADER;\n"
"\n"
"FILE_DESCRIPTION((''), '2;1');\n"
"\n"
"FILE_NAME(\n"
" 'output_file',\n"
" '2009-06-07T17:44:47-07:00',\n"
" (''),\n"
" (''),\n"
" 'SolveSpace',\n"
" '',\n"
" ''\n"
");\n"
"\n"
"FILE_SCHEMA (('CONFIG_CONTROL_DESIGN'));\n"
"ENDSEC;\n"
"\n"
"DATA;\n"
"\n"
"/**********************************************************\n"
" * This defines the units and tolerances for the file. It\n"
" * is always the same, independent of the actual data.\n"
" **********************************************************/\n"
"#158=(\n"
"LENGTH_UNIT()\n"
"NAMED_UNIT(*)\n"
"SI_UNIT(.MILLI.,.METRE.)\n"
");\n"
"#161=(\n"
"NAMED_UNIT(*)\n"
"PLANE_ANGLE_UNIT()\n"
"SI_UNIT($,.RADIAN.)\n"
");\n"
"#166=(\n"
"NAMED_UNIT(*)\n"
"SI_UNIT($,.STERADIAN.)\n"
"SOLID_ANGLE_UNIT()\n"
");\n"
"#167=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.001),#158,\n"
"'DISTANCE_ACCURACY_VALUE',\n"
"'string');\n"
"#168=(\n"
"GEOMETRIC_REPRESENTATION_CONTEXT(3)\n"
"GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#167))\n"
"GLOBAL_UNIT_ASSIGNED_CONTEXT((#166,#161,#158))\n"
"REPRESENTATION_CONTEXT('ID1','3D')\n"
");\n"
"#169=SHAPE_REPRESENTATION('',(#170),#168);\n"
"#170=AXIS2_PLACEMENT_3D('',#173,#171,#172);\n"
"#171=DIRECTION('',(0.,0.,1.));\n"
"#172=DIRECTION('',(1.,0.,0.));\n"
"#173=CARTESIAN_POINT('',(0.,0.,0.));\n"
"\n"
);
// Start the ID somewhere beyond the header IDs.
id = 200;
}
int StepFileWriter::ExportCurve(SBezier *sb) {
int i, ret = id;
fprintf(f, "#%d=(\n", ret);
fprintf(f, "BOUNDED_CURVE()\n");
fprintf(f, "B_SPLINE_CURVE(%d,(", sb->deg);
for(i = 0; i <= sb->deg; i++) {
fprintf(f, "#%d", ret + i + 1);
if(i != sb->deg) fprintf(f, ",");
}
fprintf(f, "),.UNSPECIFIED.,.F.,.F.)\n");
fprintf(f, "B_SPLINE_CURVE_WITH_KNOTS((%d,%d),",
(sb->deg + 1), (sb-> deg + 1));
fprintf(f, "(0.000,1.000),.UNSPECIFIED.)\n");
fprintf(f, "CURVE()\n");
fprintf(f, "GEOMETRIC_REPRESENTATION_ITEM()\n");
fprintf(f, "RATIONAL_B_SPLINE_CURVE((");
for(i = 0; i <= sb->deg; i++) {
fprintf(f, "%.10f", sb->weight[i]);
if(i != sb->deg) fprintf(f, ",");
}
fprintf(f, "))\n");
fprintf(f, "REPRESENTATION_ITEM('')\n);\n");
for(i = 0; i <= sb->deg; i++) {
fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n",
id + 1 + i,
CO(sb->ctrl[i]));
}
fprintf(f, "\n");
id = ret + 1 + (sb->deg + 1);
return ret;
}
int StepFileWriter::ExportCurveLoop(SBezierLoop *loop, bool inner) {
if(loop->l.n < 1) oops();
List<int> listOfTrims;
ZERO(&listOfTrims);
SBezier *sb = &(loop->l.elem[loop->l.n - 1]);
// 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
// the finish of the last Bezier in the loop before we start our process.
fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n",
id, CO(sb->Finish()));
fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id+1, id);
int lastFinish = id + 1, prevFinish = lastFinish;
id += 2;
for(sb = loop->l.First(); sb; sb = loop->l.NextAfter(sb)) {
int curveId = ExportCurve(sb);
int thisFinish;
if(loop->l.NextAfter(sb) != NULL) {
fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n",
id, CO(sb->Finish()));
fprintf(f, "#%d=VERTEX_POINT('',#%d);\n", id+1, id);
thisFinish = id + 1;
id += 2;
} else {
thisFinish = lastFinish;
}
fprintf(f, "#%d=EDGE_CURVE('',#%d,#%d,#%d,%s);\n",
id, prevFinish, thisFinish, curveId, ".T.");
fprintf(f, "#%d=ORIENTED_EDGE('',*,*,#%d,.T.);\n",
id+1, id);
int oe = id+1;
listOfTrims.Add(&oe);
id += 2;
prevFinish = thisFinish;
}
fprintf(f, "#%d=EDGE_LOOP('',(", id);
int *oe;
for(oe = listOfTrims.First(); oe; oe = listOfTrims.NextAfter(oe)) {
fprintf(f, "#%d", *oe);
if(listOfTrims.NextAfter(oe) != NULL) fprintf(f, ",");
}
fprintf(f, "));\n");
int fb = id + 1;
fprintf(f, "#%d=%s('',#%d,.T.);\n",
fb, inner ? "FACE_BOUND" : "FACE_OUTER_BOUND", id);
id += 2;
listOfTrims.Clear();
return fb;
}
void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) {
int i, j, srfid = id;
// First, we create the untrimmed surface. We always specify a rational
// B-spline surface (in fact, just a Bezier surface).
fprintf(f, "#%d=(\n", srfid);
fprintf(f, "BOUNDED_SURFACE()\n");
fprintf(f, "B_SPLINE_SURFACE(%d,%d,(", ss->degm, ss->degn);
for(i = 0; i <= ss->degm; i++) {
fprintf(f, "(");
for(j = 0; j <= ss->degn; j++) {
fprintf(f, "#%d", srfid + 1 + j + i*(ss->degn + 1));
if(j != ss->degn) fprintf(f, ",");
}
fprintf(f, ")");
if(i != ss->degm) fprintf(f, ",");
}
fprintf(f, "),.UNSPECIFIED.,.F.,.F.,.F.)\n");
fprintf(f, "B_SPLINE_SURFACE_WITH_KNOTS((%d,%d),(%d,%d),",
(ss->degm + 1), (ss->degm + 1),
(ss->degn + 1), (ss->degn + 1));
fprintf(f, "(0.000,1.000),(0.000,1.000),.UNSPECIFIED.)\n");
fprintf(f, "GEOMETRIC_REPRESENTATION_ITEM()\n");
fprintf(f, "RATIONAL_B_SPLINE_SURFACE((");
for(i = 0; i <= ss->degm; i++) {
fprintf(f, "(");
for(j = 0; j <= ss->degn; j++) {
fprintf(f, "%.10f", ss->weight[i][j]);
if(j != ss->degn) fprintf(f, ",");
}
fprintf(f, ")");
if(i != ss->degm) fprintf(f, ",");
}
fprintf(f, "))\n");
fprintf(f, "REPRESENTATION_ITEM('')\n");
fprintf(f, "SURFACE()\n");
fprintf(f, ");\n");
// The control points for the untrimmed surface.
for(i = 0; i <= ss->degm; i++) {
for(j = 0; j <= ss->degn; j++) {
fprintf(f, "#%d=CARTESIAN_POINT('',(%.10f,%.10f,%.10f));\n",
srfid + 1 + j + i*(ss->degn + 1),
CO(ss->ctrl[i][j]));
}
}
fprintf(f, "\n");
id = srfid + 1 + (ss->degm + 1)*(ss->degn + 1);
// Now we do the trim curves. We must group each outer loop separately
// along with its inner faces, so do that now.
SBezierLoopSetSet sblss;
ZERO(&sblss);
SPolygon spxyz;
ZERO(&spxyz);
bool allClosed;
SEdge notClosedAt;
// We specify a surface, so it doesn't check for coplanarity; and we
// don't want it to give us any open contours. The polygon and chord
// tolerance are required, because they are used to calculate the
// contour directions and determine inner vs. outer contours.
sblss.FindOuterFacesFrom(sbl, &spxyz, ss,
SS.ChordTolMm() / SS.exportScale,
&allClosed, &notClosedAt,
NULL, NULL,
NULL);
// So in our list of SBezierLoopSet, each set contains at least one loop
// (the outer boundary), plus any inner loops associated with that outer
// loop.
SBezierLoopSet *sbls;
for(sbls = sblss.l.First(); sbls; sbls = sblss.l.NextAfter(sbls)) {
SBezierLoop *loop = sbls->l.First();
List<int> listOfLoops;
ZERO(&listOfLoops);
// Create the face outer boundary from the outer loop.
int fob = ExportCurveLoop(loop, false);
listOfLoops.Add(&fob);
// And create the face inner boundaries from any inner loops that
// lie within this contour.
loop = sbls->l.NextAfter(loop);
for(; loop; loop = sbls->l.NextAfter(loop)) {
int fib = ExportCurveLoop(loop, true);
listOfLoops.Add(&fib);
}
// And now create the face that corresponds to this outer loop
// and all of its holes.
int advFaceId = id;
fprintf(f, "#%d=ADVANCED_FACE('',(", advFaceId);
int *fb;
for(fb = listOfLoops.First(); fb; fb = listOfLoops.NextAfter(fb)) {
fprintf(f, "#%d", *fb);
if(listOfLoops.NextAfter(fb) != NULL) fprintf(f, ",");
}
fprintf(f, "),#%d,.T.);\n", srfid);
fprintf(f, "\n");
advancedFaces.Add(&advFaceId);
id++;
listOfLoops.Clear();
}
sblss.Clear();
spxyz.Clear();
}
void StepFileWriter::WriteFooter(void) {
fprintf(f,
"\n"
"ENDSEC;\n"
"\n"
"END-ISO-10303-21;\n"
);
}
void StepFileWriter::ExportSurfacesTo(char *file) {
Group *g = SK.GetGroup(SS.GW.activeGroup);
SShell *shell = &(g->runningShell);
if(shell->surface.n == 0) {
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." : "");
return;
}
f = fopen(file, "wb");
if(!f) {
Error("Couldn't write to '%s'", file);
return;
}
WriteHeader();
ZERO(&advancedFaces);
SSurface *ss;
for(ss = shell->surface.First(); ss; ss = shell->surface.NextAfter(ss)) {
if(ss->trim.n == 0) 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
// the piecewise linearization of those loops in xyz space.
SBezierList sbl;
ZERO(&sbl);
ss->MakeSectionEdgesInto(shell, NULL, &sbl);
// Apply the export scale factor.
ss->ScaleSelfBy(1.0/SS.exportScale);
sbl.ScaleSelfBy(1.0/SS.exportScale);
ExportSurface(ss, &sbl);
sbl.Clear();
}
fprintf(f, "#%d=CLOSED_SHELL('',(", id);
int *af;
for(af = advancedFaces.First(); af; af = advancedFaces.NextAfter(af)) {
fprintf(f, "#%d", *af);
if(advancedFaces.NextAfter(af) != NULL) fprintf(f, ",");
}
fprintf(f, "));\n");
fprintf(f, "#%d=MANIFOLD_SOLID_BREP('brep',#%d);\n", id+1, id);
fprintf(f, "#%d=ADVANCED_BREP_SHAPE_REPRESENTATION('',(#%d,#170),#168);\n",
id+2, id+1);
fprintf(f, "#%d=SHAPE_REPRESENTATION_RELATIONSHIP($,$,#169,#%d);\n",
id+3, id+2);
WriteFooter();
fclose(f);
advancedFaces.Clear();
}
void StepFileWriter::WriteWireframe(void) {
fprintf(f, "#%d=GEOMETRIC_CURVE_SET('curves',(", id);
int *c;
for(c = curves.First(); c; c = curves.NextAfter(c)) {
fprintf(f, "#%d", *c);
if(curves.NextAfter(c) != NULL) fprintf(f, ",");
}
fprintf(f, "));\n");
fprintf(f, "#%d=GEOMETRICALLY_BOUNDED_WIREFRAME_SHAPE_REPRESENTATION"
"('',(#%d,#170),#168);\n", id+1, id);
fprintf(f, "#%d=SHAPE_REPRESENTATION_RELATIONSHIP($,$,#169,#%d);\n",
id+2, id+1);
id += 3;
curves.Clear();
}

View File

@ -1,713 +0,0 @@
//-----------------------------------------------------------------------------
// The file format-specific stuff for all of the 2d vector output formats.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
//-----------------------------------------------------------------------------
// Routines for DXF export
//-----------------------------------------------------------------------------
void DxfFileWriter::StartFile(void) {
// Some software, like Adobe Illustrator, insists on a header.
fprintf(f,
" 999\r\n"
"file created by SolveSpace\r\n"
" 0\r\n"
"SECTION\r\n"
" 2\r\n"
"HEADER\r\n"
" 9\r\n"
"$ACADVER\r\n"
" 1\r\n"
"AC1006\r\n"
" 9\r\n"
"$ANGDIR\r\n"
" 70\r\n"
"0\r\n"
" 9\r\n"
"$AUNITS\r\n"
" 70\r\n"
"0\r\n"
" 9\r\n"
"$AUPREC\r\n"
" 70\r\n"
"0\r\n"
" 9\r\n"
"$INSBASE\r\n"
" 10\r\n"
"0.0\r\n"
" 20\r\n"
"0.0\r\n"
" 30\r\n"
"0.0\r\n"
" 9\r\n"
"$EXTMIN\r\n"
" 10\r\n"
"0.0\r\n"
" 20\r\n"
"0.0\r\n"
" 9\r\n"
"$EXTMAX\r\n"
" 10\r\n"
"10000.0\r\n"
" 20\r\n"
"10000.0\r\n"
" 0\r\n"
"ENDSEC\r\n");
// Then start the entities.
fprintf(f,
" 0\r\n"
"SECTION\r\n"
" 2\r\n"
"ENTITIES\r\n");
}
void DxfFileWriter::StartPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
}
void DxfFileWriter::FinishPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
}
void DxfFileWriter::Triangle(STriangle *tr) {
}
void DxfFileWriter::Bezier(SBezier *sb) {
Vector c, n = Vector::From(0, 0, 1);
double r;
if(sb->deg == 1) {
fprintf(f,
" 0\r\n"
"LINE\r\n"
" 8\r\n" // Layer code
"%d\r\n"
" 10\r\n" // xA
"%.6f\r\n"
" 20\r\n" // yA
"%.6f\r\n"
" 30\r\n" // zA
"%.6f\r\n"
" 11\r\n" // xB
"%.6f\r\n"
" 21\r\n" // yB
"%.6f\r\n"
" 31\r\n" // zB
"%.6f\r\n",
0,
sb->ctrl[0].x, sb->ctrl[0].y, sb->ctrl[0].z,
sb->ctrl[1].x, sb->ctrl[1].y, sb->ctrl[1].z);
} else if(sb->IsInPlane(n, 0) && sb->IsCircle(n, &c, &r)) {
double theta0 = atan2(sb->ctrl[0].y - c.y, sb->ctrl[0].x - c.x),
theta1 = atan2(sb->ctrl[2].y - c.y, sb->ctrl[2].x - c.x),
dtheta = WRAP_SYMMETRIC(theta1 - theta0, 2*PI);
if(dtheta < 0) {
SWAP(double, theta0, theta1);
}
fprintf(f,
" 0\r\n"
"ARC\r\n"
" 8\r\n" // Layer code
"%d\r\n"
" 10\r\n" // x
"%.6f\r\n"
" 20\r\n" // y
"%.6f\r\n"
" 30\r\n" // z
"%.6f\r\n"
" 40\r\n" // radius
"%.6f\r\n"
" 50\r\n" // start angle
"%.6f\r\n"
" 51\r\n" // end angle
"%.6f\r\n",
0,
c.x, c.y, 0.0,
r,
theta0*180/PI, theta1*180/PI);
} else {
BezierAsPwl(sb);
}
}
void DxfFileWriter::FinishAndCloseFile(void) {
fprintf(f,
" 0\r\n"
"ENDSEC\r\n"
" 0\r\n"
"EOF\r\n" );
fclose(f);
}
//-----------------------------------------------------------------------------
// Routines for EPS output
//-----------------------------------------------------------------------------
void EpsFileWriter::StartFile(void) {
fprintf(f,
"%%!PS-Adobe-2.0\r\n"
"%%%%Creator: SolveSpace\r\n"
"%%%%Title: title\r\n"
"%%%%Pages: 0\r\n"
"%%%%PageOrder: Ascend\r\n"
"%%%%BoundingBox: 0 0 %d %d\r\n"
"%%%%HiResBoundingBox: 0 0 %.3f %.3f\r\n"
"%%%%EndComments\r\n"
"\r\n"
"gsave\r\n"
"\r\n",
(int)ceil(MmToPts(ptMax.x - ptMin.x)),
(int)ceil(MmToPts(ptMax.y - ptMin.y)),
MmToPts(ptMax.x - ptMin.x),
MmToPts(ptMax.y - ptMin.y));
}
void EpsFileWriter::StartPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
fprintf(f, "newpath\r\n");
prevPt = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE);
}
void EpsFileWriter::FinishPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
fprintf(f, " %.3f setlinewidth\r\n"
" %.3f %.3f %.3f setrgbcolor\r\n"
" 1 setlinejoin\r\n" // rounded
" 1 setlinecap\r\n" // rounded
" gsave stroke grestore\r\n",
MmToPts(lineWidth),
REDf(strokeRgb), GREENf(strokeRgb), BLUEf(strokeRgb));
if(filled) {
fprintf(f, " %.3f %.3f %.3f setrgbcolor\r\n"
" gsave fill grestore\r\n",
REDf(fillRgb), GREENf(fillRgb), BLUEf(fillRgb));
}
}
void EpsFileWriter::MaybeMoveTo(Vector st, Vector fi) {
if(!prevPt.Equals(st)) {
fprintf(f, " %.3f %.3f moveto\r\n",
MmToPts(st.x - ptMin.x), MmToPts(st.y - ptMin.y));
}
prevPt = fi;
}
void EpsFileWriter::Triangle(STriangle *tr) {
fprintf(f,
"%.3f %.3f %.3f setrgbcolor\r\n"
"newpath\r\n"
" %.3f %.3f moveto\r\n"
" %.3f %.3f lineto\r\n"
" %.3f %.3f lineto\r\n"
" closepath\r\n"
"gsave fill grestore\r\n",
REDf(tr->meta.color), GREENf(tr->meta.color), BLUEf(tr->meta.color),
MmToPts(tr->a.x - ptMin.x), MmToPts(tr->a.y - ptMin.y),
MmToPts(tr->b.x - ptMin.x), MmToPts(tr->b.y - ptMin.y),
MmToPts(tr->c.x - ptMin.x), MmToPts(tr->c.y - ptMin.y));
// same issue with cracks, stroke it to avoid them
double sw = max(ptMax.x - ptMin.x, ptMax.y - ptMin.y) / 1000;
fprintf(f,
"1 setlinejoin\r\n"
"1 setlinecap\r\n"
"%.3f setlinewidth\r\n"
"gsave stroke grestore\r\n",
MmToPts(sw));
}
void EpsFileWriter::Bezier(SBezier *sb) {
Vector c, n = Vector::From(0, 0, 1);
double r;
if(sb->deg == 1) {
MaybeMoveTo(sb->ctrl[0], sb->ctrl[1]);
fprintf(f, " %.3f %.3f lineto\r\n",
MmToPts(sb->ctrl[1].x - ptMin.x),
MmToPts(sb->ctrl[1].y - ptMin.y));
} else if(sb->IsCircle(n, &c, &r)) {
Vector p0 = sb->ctrl[0], p1 = sb->ctrl[2];
double theta0 = atan2(p0.y - c.y, p0.x - c.x),
theta1 = atan2(p1.y - c.y, p1.x - c.x),
dtheta = WRAP_SYMMETRIC(theta1 - theta0, 2*PI);
MaybeMoveTo(p0, p1);
fprintf(f,
" %.3f %.3f %.3f %.3f %.3f %s\r\n",
MmToPts(c.x - ptMin.x), MmToPts(c.y - ptMin.y),
MmToPts(r),
theta0*180/PI, theta1*180/PI,
dtheta < 0 ? "arcn" : "arc");
} else if(sb->deg == 3 && !sb->IsRational()) {
MaybeMoveTo(sb->ctrl[0], sb->ctrl[3]);
fprintf(f,
" %.3f %.3f %.3f %.3f %.3f %.3f curveto\r\n",
MmToPts(sb->ctrl[1].x - ptMin.x), MmToPts(sb->ctrl[1].y - ptMin.y),
MmToPts(sb->ctrl[2].x - ptMin.x), MmToPts(sb->ctrl[2].y - ptMin.y),
MmToPts(sb->ctrl[3].x - ptMin.x), MmToPts(sb->ctrl[3].y - ptMin.y));
} else {
BezierAsNonrationalCubic(sb);
}
}
void EpsFileWriter::FinishAndCloseFile(void) {
fprintf(f,
"\r\n"
"grestore\r\n"
"\r\n");
fclose(f);
}
//-----------------------------------------------------------------------------
// Routines for PDF output, some extra complexity because we have to generate
// a correct xref table.
//-----------------------------------------------------------------------------
void PdfFileWriter::StartFile(void) {
if((ptMax.x - ptMin.x) > 200*25.4 ||
(ptMax.y - ptMin.y) > 200*25.4)
{
Message("PDF page size exceeds 200 by 200 inches; many viewers may "
"reject this file.");
}
fprintf(f,
"%%PDF-1.1\r\n"
"%%%c%c%c%c\r\n",
0xe2, 0xe3, 0xcf, 0xd3);
xref[1] = ftell(f);
fprintf(f,
"1 0 obj\r\n"
" << /Type /Catalog\r\n"
" /Outlines 2 0 R\r\n"
" /Pages 3 0 R\r\n"
" >>\r\n"
"endobj\r\n");
xref[2] = ftell(f);
fprintf(f,
"2 0 obj\r\n"
" << /Type /Outlines\r\n"
" /Count 0\r\n"
" >>\r\n"
"endobj\r\n");
xref[3] = ftell(f);
fprintf(f,
"3 0 obj\r\n"
" << /Type /Pages\r\n"
" /Kids [4 0 R]\r\n"
" /Count 1\r\n"
" >>\r\n"
"endobj\r\n");
xref[4] = ftell(f);
fprintf(f,
"4 0 obj\r\n"
" << /Type /Page\r\n"
" /Parent 3 0 R\r\n"
" /MediaBox [0 0 %.3f %.3f]\r\n"
" /Contents 5 0 R\r\n"
" /Resources << /ProcSet 7 0 R\r\n"
" /Font << /F1 8 0 R >>\r\n"
" >>\r\n"
" >>\r\n"
"endobj\r\n",
MmToPts(ptMax.x - ptMin.x),
MmToPts(ptMax.y - ptMin.y));
xref[5] = ftell(f);
fprintf(f,
"5 0 obj\r\n"
" << /Length 6 0 R >>\r\n"
"stream\r\n");
bodyStart = ftell(f);
}
void PdfFileWriter::FinishAndCloseFile(void) {
DWORD bodyEnd = ftell(f);
fprintf(f,
"endstream\r\n"
"endobj\r\n");
xref[6] = ftell(f);
fprintf(f,
"6 0 obj\r\n"
" %d\r\n"
"endobj\r\n",
bodyEnd - bodyStart);
xref[7] = ftell(f);
fprintf(f,
"7 0 obj\r\n"
" [/PDF /Text]\r\n"
"endobj\r\n");
xref[8] = ftell(f);
fprintf(f,
"8 0 obj\r\n"
" << /Type /Font\r\n"
" /Subtype /Type1\r\n"
" /Name /F1\r\n"
" /BaseFont /Helvetica\r\n"
" /Encoding /WinAnsiEncoding\r\n"
" >>\r\n"
"endobj\r\n");
xref[9] = ftell(f);
fprintf(f,
"9 0 obj\r\n"
" << /Creator (SolveSpace)\r\n"
" >>\r\n");
DWORD xrefStart = ftell(f);
fprintf(f,
"xref\r\n"
"0 10\r\n"
"0000000000 65535 f\r\n");
int i;
for(i = 1; i <= 9; i++) {
fprintf(f, "%010d %05d n\r\n", xref[i], 0);
}
fprintf(f,
"\r\n"
"trailer\r\n"
" << /Size 10\r\n"
" /Root 1 0 R\r\n"
" /Info 9 0 R\r\n"
" >>\r\n"
"startxref\r\n"
"%d\r\n"
"%%%%EOF\r\n",
xrefStart);
fclose(f);
}
void PdfFileWriter::StartPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
fprintf(f, "1 J 1 j " // round endcaps and joins
"%.3f w "
"%.3f %.3f %.3f RG\r\n",
MmToPts(lineWidth),
REDf(strokeRgb), GREENf(strokeRgb), BLUEf(strokeRgb));
if(filled) {
fprintf(f, "%.3f %.3f %.3f rg\r\n",
REDf(fillRgb), GREENf(fillRgb), BLUEf(fillRgb));
}
prevPt = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE);
}
void PdfFileWriter::FinishPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
if(filled) {
fprintf(f, "b\r\n");
} else {
fprintf(f, "S\r\n");
}
}
void PdfFileWriter::MaybeMoveTo(Vector st, Vector fi) {
if(!prevPt.Equals(st)) {
fprintf(f, "%.3f %.3f m\r\n",
MmToPts(st.x - ptMin.x), MmToPts(st.y - ptMin.y));
}
prevPt = fi;
}
void PdfFileWriter::Triangle(STriangle *tr) {
double sw = max(ptMax.x - ptMin.x, ptMax.y - ptMin.y) / 1000;
fprintf(f,
"1 J 1 j\r\n"
"%.3f %.3f %.3f RG\r\n"
"%.3f %.3f %.3f rg\r\n"
"%.3f w\r\n"
"%.3f %.3f m\r\n"
"%.3f %.3f l\r\n"
"%.3f %.3f l\r\n"
"b\r\n",
REDf(tr->meta.color), GREENf(tr->meta.color), BLUEf(tr->meta.color),
REDf(tr->meta.color), GREENf(tr->meta.color), BLUEf(tr->meta.color),
MmToPts(sw),
MmToPts(tr->a.x - ptMin.x), MmToPts(tr->a.y - ptMin.y),
MmToPts(tr->b.x - ptMin.x), MmToPts(tr->b.y - ptMin.y),
MmToPts(tr->c.x - ptMin.x), MmToPts(tr->c.y - ptMin.y));
}
void PdfFileWriter::Bezier(SBezier *sb) {
if(sb->deg == 1) {
MaybeMoveTo(sb->ctrl[0], sb->ctrl[1]);
fprintf(f,
"%.3f %.3f l\r\n",
MmToPts(sb->ctrl[1].x - ptMin.x), MmToPts(sb->ctrl[1].y - ptMin.y));
} else if(sb->deg == 3 && !sb->IsRational()) {
MaybeMoveTo(sb->ctrl[0], sb->ctrl[3]);
fprintf(f,
"%.3f %.3f %.3f %.3f %.3f %.3f c\r\n",
MmToPts(sb->ctrl[1].x - ptMin.x), MmToPts(sb->ctrl[1].y - ptMin.y),
MmToPts(sb->ctrl[2].x - ptMin.x), MmToPts(sb->ctrl[2].y - ptMin.y),
MmToPts(sb->ctrl[3].x - ptMin.x), MmToPts(sb->ctrl[3].y - ptMin.y));
} else {
BezierAsNonrationalCubic(sb);
}
}
//-----------------------------------------------------------------------------
// Routines for SVG output
//-----------------------------------------------------------------------------
void SvgFileWriter::StartFile(void) {
fprintf(f,
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" "
"\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\r\n"
"<svg xmlns=\"http://www.w3.org/2000/svg\" "
"xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
"width='%.3fmm' height='%.3fmm' "
"viewBox=\"0 0 %.3f %.3f\">\r\n"
"\r\n"
"<title>Exported SVG</title>\r\n"
"\r\n",
(ptMax.x - ptMin.x) + 1, (ptMax.y - ptMin.y) + 1,
(ptMax.x - ptMin.x) + 1, (ptMax.y - ptMin.y) + 1);
// A little bit of extra space for the stroke width.
}
void SvgFileWriter::StartPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
fprintf(f, "<path d='");
prevPt = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE);
}
void SvgFileWriter::FinishPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
char fill[100];
if(filled) {
sprintf(fill, "#%02x%02x%02x",
RED(fillRgb), GREEN(fillRgb), BLUE(fillRgb));
} else {
strcpy(fill, "none");
}
fprintf(f, "' stroke-width='%.3f' stroke='#%02x%02x%02x' "
"stroke-linecap='round' stroke-linejoin='round' "
"fill='%s' />\r\n",
lineWidth, RED(strokeRgb), GREEN(strokeRgb), BLUE(strokeRgb),
fill);
}
void SvgFileWriter::MaybeMoveTo(Vector st, Vector fi) {
// SVG uses a coordinate system with the origin at top left, +y down
if(!prevPt.Equals(st)) {
fprintf(f, "M%.3f %.3f ", (st.x - ptMin.x), (ptMax.y - st.y));
}
prevPt = fi;
}
void SvgFileWriter::Triangle(STriangle *tr) {
// crispEdges turns of anti-aliasing, which tends to cause hairline
// cracks between triangles; but there still is some cracking, so
// specify a stroke width too, hope for around a pixel
double sw = max(ptMax.x - ptMin.x, ptMax.y - ptMin.y) / 1000;
fprintf(f,
"<polygon points='%.3f,%.3f %.3f,%.3f %.3f,%.3f' "
"stroke='#%02x%02x%02x' stroke-width='%.3f' "
"style='fill:#%02x%02x%02x' shape-rendering='crispEdges'/>\r\n",
(tr->a.x - ptMin.x), (ptMax.y - tr->a.y),
(tr->b.x - ptMin.x), (ptMax.y - tr->b.y),
(tr->c.x - ptMin.x), (ptMax.y - tr->c.y),
RED(tr->meta.color), GREEN(tr->meta.color), BLUE(tr->meta.color),
sw,
RED(tr->meta.color), GREEN(tr->meta.color), BLUE(tr->meta.color));
}
void SvgFileWriter::Bezier(SBezier *sb) {
Vector c, n = Vector::From(0, 0, 1);
double r;
if(sb->deg == 1) {
MaybeMoveTo(sb->ctrl[0], sb->ctrl[1]);
fprintf(f, "L%.3f,%.3f ",
(sb->ctrl[1].x - ptMin.x), (ptMax.y - sb->ctrl[1].y));
} else if(sb->IsCircle(n, &c, &r)) {
Vector p0 = sb->ctrl[0], p1 = sb->ctrl[2];
double theta0 = atan2(p0.y - c.y, p0.x - c.x),
theta1 = atan2(p1.y - c.y, p1.x - c.x),
dtheta = WRAP_SYMMETRIC(theta1 - theta0, 2*PI);
// The arc must be less than 180 degrees, or else it couldn't have
// been represented as a single rational Bezier. So large-arc-flag
// must be false. sweep-flag is determined by the sign of dtheta.
// Note that clockwise and counter-clockwise are backwards in SVG's
// mirrored csys.
MaybeMoveTo(p0, p1);
fprintf(f, "A%.3f,%.3f 0 0,%d %.3f,%.3f ",
r, r,
(dtheta < 0) ? 1 : 0,
p1.x - ptMin.x, ptMax.y - p1.y);
} else if(!sb->IsRational()) {
if(sb->deg == 2) {
MaybeMoveTo(sb->ctrl[0], sb->ctrl[2]);
fprintf(f, "Q%.3f,%.3f %.3f,%.3f ",
sb->ctrl[1].x - ptMin.x, ptMax.y - sb->ctrl[1].y,
sb->ctrl[2].x - ptMin.x, ptMax.y - sb->ctrl[2].y);
} else if(sb->deg == 3) {
MaybeMoveTo(sb->ctrl[0], sb->ctrl[3]);
fprintf(f, "C%.3f,%.3f %.3f,%.3f %.3f,%.3f ",
sb->ctrl[1].x - ptMin.x, ptMax.y - sb->ctrl[1].y,
sb->ctrl[2].x - ptMin.x, ptMax.y - sb->ctrl[2].y,
sb->ctrl[3].x - ptMin.x, ptMax.y - sb->ctrl[3].y);
}
} else {
BezierAsNonrationalCubic(sb);
}
}
void SvgFileWriter::FinishAndCloseFile(void) {
fprintf(f, "\r\n</svg>\r\n");
fclose(f);
}
//-----------------------------------------------------------------------------
// Routines for HPGL output
//-----------------------------------------------------------------------------
double HpglFileWriter::MmToHpglUnits(double mm) {
return mm*40;
}
void HpglFileWriter::StartFile(void) {
fprintf(f, "IN;\r\n");
fprintf(f, "SP1;\r\n");
}
void HpglFileWriter::StartPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
}
void HpglFileWriter::FinishPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
}
void HpglFileWriter::Triangle(STriangle *tr) {
}
void HpglFileWriter::Bezier(SBezier *sb) {
if(sb->deg == 1) {
fprintf(f, "PU%d,%d;\r\n",
(int)MmToHpglUnits(sb->ctrl[0].x),
(int)MmToHpglUnits(sb->ctrl[0].y));
fprintf(f, "PD%d,%d;\r\n",
(int)MmToHpglUnits(sb->ctrl[1].x),
(int)MmToHpglUnits(sb->ctrl[1].y));
} else {
BezierAsPwl(sb);
}
}
void HpglFileWriter::FinishAndCloseFile(void) {
fclose(f);
}
//-----------------------------------------------------------------------------
// Routines for G Code output. Slightly complicated by our ability to generate
// multiple passes, and to specify the feeds and depth; those parameters get
// set in the configuration screen.
//-----------------------------------------------------------------------------
void GCodeFileWriter::StartFile(void) {
ZERO(&sel);
}
void GCodeFileWriter::StartPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
}
void GCodeFileWriter::FinishPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
}
void GCodeFileWriter::Triangle(STriangle *tr) {
}
void GCodeFileWriter::Bezier(SBezier *sb) {
if(sb->deg == 1) {
sel.AddEdge(sb->ctrl[0], sb->ctrl[1]);
} else {
BezierAsPwl(sb);
}
}
void GCodeFileWriter::FinishAndCloseFile(void) {
SPolygon sp;
ZERO(&sp);
sel.AssemblePolygon(&sp, NULL);
int i;
for(i = 0; i < SS.gCode.passes; i++) {
double depth = (SS.gCode.depth / SS.gCode.passes)*(i+1);
SContour *sc;
for(sc = sp.l.First(); sc; sc = sp.l.NextAfter(sc)) {
if(sc->l.n < 2) continue;
SPoint *pt = sc->l.First();
fprintf(f, "G00 X%s Y%s\r\n",
SS.MmToString(pt->p.x), SS.MmToString(pt->p.y));
fprintf(f, "G01 Z%s F%s\r\n",
SS.MmToString(depth), SS.MmToString(SS.gCode.plungeFeed));
pt = sc->l.NextAfter(pt);
for(; pt; pt = sc->l.NextAfter(pt)) {
fprintf(f, "G01 X%s Y%s F%s\r\n",
SS.MmToString(pt->p.x), SS.MmToString(pt->p.y),
SS.MmToString(SS.gCode.feed));
}
// Move up to a clearance plane 5mm above the work.
fprintf(f, "G00 Z%s\r\n",
SS.MmToString(SS.gCode.depth < 0 ? +5 : -5));
}
}
sp.Clear();
sel.Clear();
fclose(f);
}
//-----------------------------------------------------------------------------
// Routine for STEP output; just a wrapper around the general STEP stuff that
// can also be used for surfaces or 3d curves.
//-----------------------------------------------------------------------------
void Step2dFileWriter::StartFile(void) {
ZERO(&sfw);
sfw.f = f;
sfw.WriteHeader();
}
void Step2dFileWriter::Triangle(STriangle *tr) {
}
void Step2dFileWriter::StartPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
}
void Step2dFileWriter::FinishPath(DWORD strokeRgb, double lineWidth,
bool filled, DWORD fillRgb)
{
}
void Step2dFileWriter::Bezier(SBezier *sb) {
int c = sfw.ExportCurve(sb);
sfw.curves.Add(&c);
}
void Step2dFileWriter::FinishAndCloseFile(void) {
sfw.WriteWireframe();
sfw.WriteFooter();
fclose(f);
}

View File

@ -1,50 +0,0 @@
DEFINES = /D_WIN32_WINNT=0x500 /DISOLATION_AWARE_ENABLED /D_WIN32_IE=0x500 /DWIN32_LEAN_AND_MEAN /DWIN32 /DLIBRARY
# Use the multi-threaded static libc because libpng and zlib do; not sure if anything bad
# happens if those mix, but don't want to risk it.
CFLAGS = /W3 /nologo -MT -Iextlib -I..\..\common\win32 /D_DEBUG /D_CRT_SECURE_NO_WARNINGS /I. /I.. /Zi /EHs /O2 /GS-
HEADERS = ..\solvespace.h ..\dsc.h ..\sketch.h ..\expr.h slvs.h
OBJDIR = obj
SSOBJS = $(OBJDIR)\util.obj \
$(OBJDIR)\entity.obj \
$(OBJDIR)\expr.obj \
$(OBJDIR)\constrainteq.obj \
$(OBJDIR)\system.obj \
W32OBJS = $(OBJDIR)\w32util.obj \
LIBOBJS = $(OBJDIR)\lib.obj \
LIBS = user32.lib gdi32.lib comctl32.lib advapi32.lib shell32.lib
all: $(OBJDIR)/CDemo.exe
@cp $(OBJDIR)/CDemo.exe .
CDemo.exe
clean:
rm -f obj/*
$(OBJDIR)/slvs.dll: $(SSOBJS) $(LIBOBJS) $(W32OBJS)
@$(CC) /LD -Fe$(OBJDIR)/slvs.dll $(SSOBJS) $(LIBOBJS) $(W32OBJS) $(LIBS)
@cp $(OBJDIR)/slvs.dll .
@echo slvs.dll
$(OBJDIR)/CDemo.exe: CDemo.c $(OBJDIR)/slvs.dll
@$(CC) $(CFLAGS) -Fe$(OBJDIR)/CDemo.exe CDemo.c $(OBJDIR)/slvs.lib $(LIBS)
@echo CDemo.exe
$(SSOBJS): ..\$(@B).cpp $(HEADERS)
@$(CC) $(CFLAGS) $(DEFINES) -c -Fo$(OBJDIR)/$(@B).obj ..\$(@B).cpp
$(W32OBJS): ..\win32\$(@B).cpp $(HEADERS)
@$(CC) $(CFLAGS) $(DEFINES) -c -Fo$(OBJDIR)/$(@B).obj ..\win32\$(@B).cpp
$(LIBOBJS): $(@B).cpp $(HEADERS)
@$(CC) $(CFLAGS) $(DEFINES) -c -Fo$(OBJDIR)/$(@B).obj $(@B).cpp

View File

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,373 +0,0 @@
/*----------------------------------------------------------------------
* si.h -- SpaceWare input library header
*
* $Id: si.h,v 1.22 1998/03/12 11:07:03 mfarr Exp $
*
* SpaceWare input library
*
*----------------------------------------------------------------------
*
* (C) 1998-2001 3Dconnexion. All rights reserved.
* Permission to use, copy, modify, and distribute this software for all
* purposes and without fees is hereby grated provided that this copyright
* notice appears in all copies. Permission to modify this software is granted
* and 3Dconnexion will support such modifications only is said modifications are
* approved by 3Dconnexion.
*
*/
#ifndef _SI_H_
#define _SI_H_
static char incFileNameCvsId[]="(C) 1996 Spacetec IMC Corporation: $Id: si.h,v 1.22 1998/03/12 11:07:03 mfarr Exp $";
#include <windows.h>
#include "spwmacro.h"
#include "spwdata.h"
#include "siSync.h"
#include <stdio.h>
#include "spwerror.h"
/*
* UI modes
*/
#define SI_UI_ALL_CONTROLS 0xffffffffL
#define SI_UI_NO_CONTROLS 0x00000000L
/*
* These UI modes are left here for legacy applications.
*/
#define SI_UI_FILTERS 0x00000001L
#define SI_UI_FUNC_BUTTONS 0x00000002L
#define SI_UI_RESET_BUTTONS 0x00000004L
#define SI_UI_SENSITIVITY 0x00000008L
#define SI_UI_TUNING 0x00000010L
#define SI_UI_DIALOG_POPUP 0x00000020L
/*
* Device types and classes
*/
typedef enum
{
SI_ALL_TYPES = -1,
SI_UNKNOWN_DEVICE = 0,
SI_SPACEBALL_2003 = 1,
SI_SPACEBALL_3003 = 2,
SI_SPACE_CONTROLLER = 3,
SI_AVENGER = 4,
SI_SPACEORB_360 = 5,
SI_NAVIGATOR = 6,
SI_SPACEBALL_2003A = 7,
SI_SPACEBALL_2003B = 8,
SI_SPACEBALL_2003C = 9,
SI_SPACEBALL_3003A = 10,
SI_SPACEBALL_3003B = 11,
SI_SPACEBALL_3003C = 12,
SI_SPACEBALL_4000 = 13,
SI_SPACEMOUSE_CLASSIC = 14,
SI_SPACEMOUSE_PLUS = 15,
SI_SPACEMOUSE_XT = 16,
SI_CYBERMAN = 17,
SI_CADMAN = 18,
SI_SPACEMOUSE_CLASSIC_PROMO = 19,
SI_SERIAL_CADMAN = 20,
SI_SPACEBALL_5000 = 21,
SI_TEST_NO_DEVICE = 22,
SI_3DX_KEYBOARD_BLACK = 23,
SI_3DX_KEYBOARD_WHITE = 24,
SI_TRAVELER = 25,
SI_TRAVELER1 = 26,
SI_SPACEBALL_5000A = 27,
SI_SPACEDRAGON = 28,
SI_SPACEPILOT = 29,
SI_NUM_DEV_TYPES /* Leave this last, add before it */
} SiDevType;
/*
* These defintions of device classes are left in for legacy applications.
*/
#define SI_HIGH_END 63
#define SI_MED_END 62
#define SI_LOW_END 61
/*
* Data retrieval mode, only SI_EVENT is currently supported.
*/
#define SI_EVENT 0x0001
#define SI_POLL 0x0002
/*
* Get event flags
*/
#define SI_AVERAGE_EVENTS 0x0001
/*
* This is an INTERNAL flag used by the polling mechanism, user applications
* should NOT send this flag.
*/
#define SI_POLLED_REQUEST 0x0100
/*
* SpaceWare event types
*/
typedef enum
{
SI_BUTTON_EVENT = 1,
SI_MOTION_EVENT,
SI_COMBO_EVENT, /* Not implemented */
SI_ZERO_EVENT,
SI_EXCEPTION_EVENT,
SI_OUT_OF_BAND,
SI_ORIENTATION_EVENT,
SI_KEYBOARD_EVENT,
SI_LPFK_EVENT,
SI_APP_EVENT, /* Application functions */
SI_SYNC_EVENT, /* GUI synchronization events */
SI_BUTTON_PRESS_EVENT, /* Single button events (replace SI_BUTTON_EVENT) */
SI_BUTTON_RELEASE_EVENT /* Single button events (replace SI_BUTTON_EVENT) */
} SiEventType;
/*
* Data modes
*/
#define SI_MODE_NORMALIZE 0x0001
#define SI_MODE_COMPRESS 0x0002
#define SI_MODE_SENSITIVITY 0x0004
#define SI_MODE_TUNING 0x0008
/*
* Motion data offsets
*/
#define SI_TX 0 /* Translation X value */
#define SI_TY 1 /* Translation Y value */
#define SI_TZ 2 /* Translation Z value */
#define SI_RX 3 /* Rotation X value */
#define SI_RY 4 /* Rotation Y value */
#define SI_RZ 5 /* Rotation Z value */
/*
* Reserved buttons
*/
#define SI_RESET_DEVICE_BIT 0x00000001L
#define SI_APP_FIT_BIT 0x80000000L
#define SI_APP_DIALOG_BIT 0x40000000L
#define SI_RESET_DEVICE_BUTTON 0
#define SI_APP_FIT_BUTTON 31
#define SI_APP_DIALOG_BUTTON 30
/*
* Miscellaneous
*/
#define SI_END_ARGS 0
#define SI_NO_HANDLE ((SiHdl) NULL)
#define SI_ALL_HANDLES ((SiHdl) NULL)
#define SI_ANY_HANDLE ((SiHdl) NULL)
#define SI_NO_TRANSCTL ((SiTransCtl) NULL)
#define SI_NO_MASK ((SiTypeMask *) NULL)
#define SI_ANY_DEVICE -1
#define SI_NO_DEVICE -1
#define SI_NO_TYPE -1
#define SI_NO_LIST -1
#define SI_NO_BUTTON -1
#define SI_STRSIZE 128
#define SI_MAXBUF 128
#define SI_KEY_MAXBUF 5120
typedef int SiDevID; /* Device ID */
typedef void *SiHdl; /* SpaceWare handle */
typedef void *SiTransCtl; /* SpaceWare transport control handle */
typedef struct /* Open data */
{
HWND hWnd; /* Window handle for SpaceWare messages. */
SiTransCtl transCtl; /* SpaceWare transport control handle. Reserved */
/* for the s80 transport mechanism. */
DWORD processID; /* The process ID for this application. */
char exeFile[MAX_PATH]; /* The executable name of the process. */
SPWint32 libFlag; /* Library version flag. */
} SiOpenData;
typedef struct /* Get event Data */
{
UINT msg;
WPARAM wParam;
LPARAM lParam;
} SiGetEventData;
typedef struct /* Device type mask */
{
unsigned char mask[8];
} SiTypeMask;
typedef struct /* Device port information */
{
SiDevID devID; /* Device ID */
int devType; /* Device type */
int devClass; /* Device class */
char devName[SI_STRSIZE]; /* Device name */
char portName[SI_STRSIZE]; /* Port name */
} SiDevPort;
typedef struct /* Device information */
{
char firmware[SI_STRSIZE]; /* Firmware version */
int devType; /* Device type */
int numButtons; /* Number of buttons */
int numDegrees; /* Number of degrees of freedom */
SPWbool canBeep; /* Device beeps */
int majorVersion; /* Major version number */
int minorVersion; /* Minor version number */
} SiDevInfo;
typedef struct /* Button information */
{
char name[SI_STRSIZE]; /* Contains the name of a button for display in an app's GUI */
} SiButtonName;
typedef struct /* Button information */
{
char name[SI_STRSIZE]; /* Contains the name of a device for display in an app's GUI */
} SiDeviceName;
typedef struct /* Version information */
{
int major; /* Major version number */
int minor; /* Minor version number */
int build; /* Build number */
char version[SI_STRSIZE]; /* Version string */
char date[SI_STRSIZE]; /* Date string */
} SiVerInfo;
typedef struct /* Sensitivity parameters */
{
char dummy;
} SiSensitivity;
typedef struct /* Tuning parameters */
{
char dummy;
} SiTuning;
typedef struct
{
SPWuint8 code; /* Out of band message code */
union {
SPWuint8 message[SI_MAXBUF-1]; /* The actual message */
void *pvoid[SI_MAXBUF/8]; /* void ptrs. Enough room for 64bit ptrs */
};
} SiSpwOOB;
typedef struct
{
SPWuint8 string[SI_KEY_MAXBUF]; /* String for keyboard data */
} SiKeyboardData;
typedef struct
{
SPWuint32 lpfk; /* LPFK number to send */
} SiLpfkData;
typedef enum
{
SI_LEFT = 0,
SI_RIGHT
} SiOrientation;
typedef struct /* Bitmasks of button states */
{
SPWuint32 last; /* Buttons pressed as of last event */
SPWuint32 current; /* Buttons pressed as of this event */
SPWuint32 pressed; /* Buttons pressed this event */
SPWuint32 released; /* Buttons released this event */
} SiButtonData;
/*
* SI_BUTTON_PRESS_EVENT & SI_BUTTON_RELEASE_EVENT are hardware button
* events. Meaning that they are meant to be sent when a specific hardware
* button is pressed. The correlation between the actual hardware button
* and the resulting button number could be broken by careful editing of
* a config file, but it is intended that the correlation be intact.
* This is basically the same as SI_BUTTON_EVENT, but allows
* more than 29 buttons because it isn't limited to a 32-bit mask.
* Different entries in the config file determine whether SI_BUTTON_PRESS/RELEASE_EVENTs
* or SI_BUTTON_EVENTs will be generated.
* This event was introduced in 3DxWare 5.2.
*/
typedef struct /* Data for SI_BUTTON_PRESS/RELEASE_EVENT */
{
SPWuint32 buttonNumber; /* The button number that went down/up in a *
* SI_BUTTON_PRESS/RELEASE_EVENT event */
} SiHWButtonData;
typedef struct /* Data for SI_APP_EVENT */
{
SPWuint32 functionNumber; /* The Application-specific function number
* invoked by the user in a SI_APP_EVENT */
} SiAppEventData;
typedef struct /* SpaceWare data */
{
SiButtonData bData; /* Button data */
long mData[6]; /* Motion data (index via SI_TX, etc) */
long period; /* Period (milliseconds) */
} SiSpwData;
typedef struct /* SpaceWare event */
{
int type; /* Event type */
union
{
SiSpwData spwData; /* Button, motion, or combo data */
SiSpwOOB spwOOB; /* Out of band message */
SiOrientation spwOrientation;/* Which hand orientation is the device */
char exData[SI_MAXBUF]; /* Exception data */
SiKeyboardData spwKeyData; /* String for keyboard data */
SiLpfkData spwLpfkData; /* LPFK data */
SiSyncPacket siSyncPacket; /* GUI SyncPacket sent to applications */
SiHWButtonData hwButtonEvent;/* ButtonNumber that goes with *
* SI_BUTTON_PRESS/RELEASE_EVENT */
SiAppEventData appEventData; /* Application event function data that *
* goes with an SI_APP_EVENT event */
} u;
} SiSpwEvent;
typedef struct /* Event handler (for SiDispatch) */
{
int (*func) (SiOpenData *, SiGetEventData *, SiSpwEvent *, void *);
void *data;
} SiEventHandler;
typedef struct /* SpaceWare event handlers */
{
SiEventHandler button; /* Button event handler */
SiEventHandler motion; /* Motion event handler */
SiEventHandler combo; /* Combo event handler */
SiEventHandler zero; /* Zero event handler */
SiEventHandler exception; /* Exception event handler */
} SiSpwHandlers;
#ifdef __cplusplus
extern "C" {
#endif
enum SpwRetVal SiAndTypeMask (SiTypeMask *pTMaskA, SiTypeMask *pTMaskB);
int SiGetPortList (SiDevPort **ppPort);
void SiFreePortList (SiDevPort *pPort);
void SiTune2003 (SiSpwEvent *pEvent);
void SiTuneSC (SiSpwEvent *pEvent);
#ifdef __cplusplus
}
#endif
#endif /* _SI_H_ */

View File

@ -1,206 +0,0 @@
/*----------------------------------------------------------------------
* siSync.h -- 3DxWare GUI Synchronization header
*
* Written: September 2004
* Author: Jim Wick
*
*----------------------------------------------------------------------
*
* (c) 1998-2005 3Dconnexion. All rights reserved.
* Permission to use, copy, modify, and distribute this software for all
* purposes and without fees is hereby grated provided that this copyright
* notice appears in all copies. Permission to modify this software is granted
* and 3Dconnexion will support such modifications only is said modifications are
* approved by 3Dconnexion.
*
*/
#ifndef _SISYNC_H_
#define _SISYNC_H_
#ifdef __cplusplus
extern "C" {
#endif
/*
* Constants
*/
#define SI_SYNC_PACKET_ID 27711
#define SI_SYNC_VERSION_MAJOR 1
#define SI_SYNC_VERSION_MINOR 0
/*
* Absolute Internal Function Numbers
* These are function numbers that will never change.
* For use with Set BUTTON_ASSIGNMENT_ABSOLUTE packets, and some INVOKE items.
* Some functions (keys) can not be INVOKED because there is a separate
* press and release and that difference is not exposed.
*/
typedef enum
{
SI_SYNC_FUNCTION_MENU_TOGGLE = 12,
SI_SYNC_FUNCTION_TRANS_TOGGLE = 13,
SI_SYNC_FUNCTION_ROT_TOGGLE = 14,
SI_SYNC_FUNCTION_HPV_TOGGLE = 15,
SI_SYNC_FUNCTION_DEC_SENS = 16,
SI_SYNC_FUNCTION_INC_SENS = 17,
SI_SYNC_FUNCTION_RESTORE_DEF = 18,
SI_SYNC_FUNCTION_PAN = 19,
SI_SYNC_FUNCTION_ZOOM = 20,
SI_SYNC_FUNCTION_TX = 21,
SI_SYNC_FUNCTION_TY = 22,
SI_SYNC_FUNCTION_TZ = 23,
SI_SYNC_FUNCTION_RX = 24,
SI_SYNC_FUNCTION_RY = 25,
SI_SYNC_FUNCTION_RZ = 26,
SI_SYNC_FUNCTION_REZERO_DEVICE = 27,
SI_SYNC_FUNCTION_SAVE = 33,
SI_SYNC_FUNCTION_RELOAD = 57,
SI_SYNC_FUNCTION_SHIFT_KEY = 60,
SI_SYNC_FUNCTION_CTRL_KEY = 61,
SI_SYNC_FUNCTION_ALT_KEY = 62,
SI_SYNC_FUNCTION_RESTORE_SENS = 63,
SI_SYNC_FUNCTION_SPACE_KEY = 64,
SI_SYNC_FUNCTION_CTRL_SHIFT_KEY = 65,
SI_SYNC_FUNCTION_CTRL_ALT_KEY = 66,
SI_SYNC_FUNCTION_SHIFT_ALT_KEY = 67,
SI_SYNC_FUNCTION_TAB_KEY = 68,
SI_SYNC_FUNCTION_RETURN_KEY = 69,
SI_SYNC_FUNCTION_DEC_TRANS_SENS = 70,
SI_SYNC_FUNCTION_INC_TRANS_SENS = 71,
SI_SYNC_FUNCTION_DEC_ROT_SENS = 72,
SI_SYNC_FUNCTION_INC_ROT_SENS = 73,
SI_SYNC_FUNCTION_DEC_PAN_SENS = 74,
SI_SYNC_FUNCTION_INC_PAN_SENS = 75,
SI_SYNC_FUNCTION_DEC_ZOOM_SENS = 76,
SI_SYNC_FUNCTION_INC_ZOOM_SENS = 77,
SI_SYNC_FUNCTION_ESC_KEY = 78,
SI_SYNC_FUNCTION_3DX_HELP = 94,
SI_SYNC_FUNCTION_APP_HELP = 95,
SI_SYNC_FUNCTION_DIALOG_TOGGLE_FN= 96,
SI_SYNC_FUNCTION_FIT_FN = 97
} SiSyncAbsFunctionNumber;
/*
* Sync Op Codes
*/
typedef enum
{
SI_SYNC_OP_COMMAND = 1,
SI_SYNC_OP_GET = 2,
SI_SYNC_OP_SET = 3
} SiSyncOpCode;
/*
* Sync Item Codes
*/
typedef enum
{
SI_SYNC_ITEM_VERSION = 1,
SI_SYNC_ITEM_QUERY = 2,
SI_SYNC_ITEM_SAVE_CONFIG = 3,
SI_SYNC_ITEM_NUMBER_OF_FUNCTIONS = 4,
SI_SYNC_ITEM_FUNCTION = 5,
SI_SYNC_ITEM_BUTTON_ASSIGNMENT = 6,
SI_SYNC_ITEM_BUTTON_ASSIGNMENT_ABSOLUTE = 7,
SI_SYNC_ITEM_BUTTON_NAME = 8,
SI_SYNC_ITEM_AXIS_LABEL = 9,
SI_SYNC_ITEM_ORIENTATION = 10,
SI_SYNC_ITEM_FILTER = 11,
SI_SYNC_ITEM_AXES_STATE = 12,
SI_SYNC_ITEM_INFO_LINE = 13,
SI_SYNC_ITEM_SCALE_OVERALL = 14,
SI_SYNC_ITEM_SCALE_TX = 15,
SI_SYNC_ITEM_SCALE_TY = 16,
SI_SYNC_ITEM_SCALE_TZ = 17,
SI_SYNC_ITEM_SCALE_RX = 18,
SI_SYNC_ITEM_SCALE_RY = 19,
SI_SYNC_ITEM_SCALE_RZ = 20,
SI_SYNC_ITEM_INVOKE_ABSOLUTE_FUNCTION = 21,
SI_SYNC_ITEM_BUTTON_STATE = 22
} SiSyncItemCode;
/*
* Filters
*/
typedef enum
{
SI_SYNC_FILTER_TRANSLATIONS = 1,
SI_SYNC_FILTER_ROTATIONS = 2,
SI_SYNC_FILTER_DOMINANT = 3
} SiSyncFilter;
typedef enum
{
SI_SYNC_FILTER_OFF = 0,
SI_SYNC_FILTER_ON = 1,
SI_SYNC_FILTER_IN_BETWEEN = 2
} SiSyncFilterValue;
/*
* Axes State
*/
typedef enum
{
SI_SYNC_AXES_STATE_TX = (1<<0),
SI_SYNC_AXES_STATE_TY = (1<<1),
SI_SYNC_AXES_STATE_TZ = (1<<2),
SI_SYNC_AXES_STATE_RX = (1<<3),
SI_SYNC_AXES_STATE_RY = (1<<4),
SI_SYNC_AXES_STATE_RZ = (1<<5)
} SiSyncAxesStateBits;
typedef struct
{
int state; /* VURZYX (Tx = LSB (& 1<<0) */
} SiSyncAxesState;
/*
* Button State
* For indicating the state of whatever the button sets (in the LCD at this point).
* E.g., to show that Translations are currently OFF for the Translations Toggle button.
* OFF: reverse video, flag is not set
* ON: normal video, flag is set
* DISABLED: (greyed), status of flag is invalid at this time
*/
typedef enum
{
SI_SYNC_BUTTON_STATE_OFF = 0,
SI_SYNC_BUTTON_STATE_ON = 1,
SI_SYNC_BUTTON_STATE_DISABLED = 2,
} SiSyncButtonState;
/*
* Private / implementation structures
*
* We suggest you leave these hidden and use the accessor functions rather than
* directly accessing the structures.
*/
#include "siSyncPriv.h"
/*
* Accessor Function headers
*/
SPWuint32 SiSyncGetSize(SiSyncPacket p);
void SiSyncSetSize(SiSyncPacket *p, SPWuint32 size);
SPWuint32 SiSyncGetHashCode(SiSyncPacket p);
void SiSyncSetHashCode(SiSyncPacket *p, SPWuint32 hashCode);
SiSyncOpCode SiSyncGetOpCode(SiSyncPacket p);
void SiSyncSetOpCode(SiSyncPacket *p, SPWuint32 opCode);
SiSyncItemCode SiSyncGetItemCode(SiSyncPacket p);
void SiSyncSetItemCode(SiSyncPacket *p, SPWuint32 itemCode);
#ifdef __cplusplus
}
#endif
#endif /* _SI_SYNC_H_ */

View File

@ -1,127 +0,0 @@
/*----------------------------------------------------------------------
* siSyncPriv.h -- 3DxWare GUI Synchronization Private header
*
* Written: June 2005
* Author: Jim Wick
*
*----------------------------------------------------------------------
*
* (c) 1998-2005 3Dconnexion. All rights reserved.
* Permission to use, copy, modify, and distribute this software for all
* purposes and without fees is hereby grated provided that this copyright
* notice appears in all copies. Permission to modify this software is granted
* and 3Dconnexion will support such modifications only is said modifications are
* approved by 3Dconnexion.
*
*/
#ifndef _SISYNCPRIV_H_
#define _SISYNCPRIV_H_
/*
* All packets start with the same fields.
* Many packets have data following the itemCode.
*/
typedef struct /* Sync Packet */
{
SPWuint32 size; /* total packet size */
SPWuint32 hashCode; /* Hash code that syncs a question with an answer */
SiSyncOpCode opCode; /* OpCode */
SiSyncItemCode itemCode; /* itemCode */
/* There will, generally, be more data starting here.
* There will not be any pointers, the data will be in here.
*/
} SiSyncPacketHeader;
/*
* I've enumerated all the possible packets here, not because they are all different,
* but mostly just for documentation. So the developer knows what parameters are
* expected with which packet type.
*/
typedef struct { SiSyncPacketHeader h; } SiSyncGetVersionPacket;
typedef struct { SiSyncPacketHeader h; SPWint32 major; SPWint32 minor; } SiSyncSetVersionPacket;
typedef struct { SiSyncPacketHeader h; } SiSyncCommandQueryPacket;
typedef struct { SiSyncPacketHeader h; } SiSyncCommandSaveConfigPacket;
typedef struct { SiSyncPacketHeader h; } SiSyncGetNumberOfFunctionsPacket;
typedef struct { SiSyncPacketHeader h; SPWint32 n; } SiSyncSetNumberOfFunctionsPacket;
typedef struct { SiSyncPacketHeader h; SPWint32 i; } SiSyncGetFunctionPacket;
typedef struct { SiSyncPacketHeader h; SPWint32 i; SPWint32 n; WCHAR name[1];} SiSyncSetFunctionPacket;
typedef struct { SiSyncPacketHeader h; SPWint32 i; } SiSyncGetButtonAssignmentPacket;
typedef struct { SiSyncPacketHeader h; SPWint32 i; SPWint32 n; } SiSyncSetButtonAssignmentPacket;
typedef struct { SiSyncPacketHeader h; SPWint32 i; SPWint32 n; } SiSyncSetButtonAssignmentAbsolutePacket;
typedef struct { SiSyncPacketHeader h; SPWint32 i; WCHAR name[1]; } SiSyncSetButtonNamePacket;
typedef struct { SiSyncPacketHeader h; SPWint32 i; } SiSyncGetAxisLabelPacket;
typedef struct { SiSyncPacketHeader h; SPWint32 i; WCHAR name[1]; } SiSyncSetAxisLabelPacket;
typedef struct { SiSyncPacketHeader h; } SiSyncGetOrientationPacket;
typedef struct { SiSyncPacketHeader h; SPWint32 a[6]; } SiSyncSetOrientationPacket;
typedef struct { SiSyncPacketHeader h; SiSyncFilter i; } SiSyncGetFilterPacket;
typedef struct { SiSyncPacketHeader h; SiSyncFilter i; SiSyncFilterValue v; } SiSyncSetFilterPacket;
typedef struct { SiSyncPacketHeader h; } SiSyncGetAxesStatePacket;
typedef struct { SiSyncPacketHeader h; SiSyncAxesState a; } SiSyncSetAxesStatePacket;
typedef struct { SiSyncPacketHeader h; SPWint32 duration; WCHAR s[1]; } SiSyncSetInfoLinePacket;
typedef struct { SiSyncPacketHeader h; } SiSyncGetScaleOverallPacket;
typedef struct { SiSyncPacketHeader h; SPWfloat32 v; } SiSyncSetScaleOverallPacket;
typedef struct { SiSyncPacketHeader h; } SiSyncGetScaleTxPacket;
typedef struct { SiSyncPacketHeader h; SPWfloat32 v; } SiSyncSetScaleTxPacket;
typedef struct { SiSyncPacketHeader h; } SiSyncGetScaleTyPacket;
typedef struct { SiSyncPacketHeader h; SPWfloat32 v; } SiSyncSetScaleTyPacket;
typedef struct { SiSyncPacketHeader h; } SiSyncGetScaleTzPacket;
typedef struct { SiSyncPacketHeader h; SPWfloat32 v; } SiSyncSetScaleTzPacket;
typedef struct { SiSyncPacketHeader h; } SiSyncGetScaleRxPacket;
typedef struct { SiSyncPacketHeader h; SPWfloat32 v; } SiSyncSetScaleRxPacket;
typedef struct { SiSyncPacketHeader h; } SiSyncGetScaleRyPacket;
typedef struct { SiSyncPacketHeader h; SPWfloat32 v; } SiSyncSetScaleRyPacket;
typedef struct { SiSyncPacketHeader h; } SiSyncGetScaleRzPacket;
typedef struct { SiSyncPacketHeader h; SPWfloat32 v; } SiSyncSetScaleRzPacket;
typedef struct { SiSyncPacketHeader h; SiSyncAbsFunctionNumber i; } SiSyncAbsFunctionPacket;
typedef struct { SiSyncPacketHeader h; SPWint32 i; SPWbool state; } SiSyncSetButtonStatePacket;
typedef struct
{
union
{
SiSyncPacketHeader h;
SiSyncGetVersionPacket gv;
SiSyncSetVersionPacket sv;
SiSyncCommandQueryPacket cq;
SiSyncCommandSaveConfigPacket cs;
SiSyncGetNumberOfFunctionsPacket gnf;
SiSyncSetNumberOfFunctionsPacket snf;
SiSyncGetFunctionPacket gf;
SiSyncSetFunctionPacket sf;
SiSyncGetButtonAssignmentPacket gba;
SiSyncSetButtonAssignmentPacket sba;
SiSyncSetButtonAssignmentAbsolutePacket sbaa;
SiSyncSetButtonNamePacket sbn;
SiSyncGetAxisLabelPacket ga;
SiSyncSetAxisLabelPacket sa;
SiSyncGetOrientationPacket go;
SiSyncSetOrientationPacket so;
SiSyncGetFilterPacket gfi;
SiSyncSetFilterPacket sfi;
SiSyncGetAxesStatePacket gas;
SiSyncSetAxesStatePacket sas;
SiSyncSetInfoLinePacket si;
SiSyncGetScaleOverallPacket gso;
SiSyncSetScaleOverallPacket sso;
SiSyncGetScaleTxPacket gtx;
SiSyncSetScaleTxPacket stx;
SiSyncGetScaleTyPacket gty;
SiSyncSetScaleTyPacket sty;
SiSyncGetScaleTzPacket gtz;
SiSyncSetScaleTzPacket stz;
SiSyncGetScaleRxPacket grx;
SiSyncSetScaleRxPacket srx;
SiSyncGetScaleRyPacket gry;
SiSyncSetScaleRyPacket sry;
SiSyncGetScaleRzPacket grz;
SiSyncSetScaleRzPacket srz;
SiSyncAbsFunctionPacket absf;
SiSyncSetButtonStatePacket sbs;
};
} SiSyncPacket;
#endif /* _SI_SYNCPRIV_H_ */

View File

@ -1,121 +0,0 @@
/*-----------------------------------------------------------------------------
*
* siapp.h -- Si static library interface header file
*
* $Id: siapp.h,v 1.3 2001/01/16 01:18:49 HJin Exp $
*
* Contains function headers and type definitions for siapp.c.
*
*-----------------------------------------------------------------------------
*
* (c) 1998-2005 3Dconnexion. All rights reserved.
* Permission to use, copy, modify, and distribute this software for all
* purposes and without fees is hereby grated provided that this copyright
* notice appears in all copies. Permission to modify this software is granted
* and 3Dconnexion will support such modifications only if said modifications are
* approved by 3Dconnexion.
*
*/
#ifndef SIAPP_H
#define SIAPP_H
static char SiAppCvsId[]="(c) 1998-2005 3Dconnexion: $Id: siapp.h,v 1.3 2001/01/16 01:18:49 HJin Exp $";
#ifdef __cplusplus
extern "C" {
#endif
/* some enumerated types used in siapp.c */
enum InitResult
{
NOT_LOADED,
FAILED,
LOADED
};
enum ErrorCode
{
NO_DLL_ERROR=0,
DLL_LOAD_FAILURE,
DLL_FUNCTION_LOAD_FAILURE,
DLL_VAR_LOAD_FAILURE
};
/* externally used functions */
enum SpwRetVal SiInitialize(void);
void SiTerminate(void);
int SiGetNumDevices (void);
SiDevID SiDeviceIndex (int idx);
int SiDispatch (SiHdl hdl, SiGetEventData *pData,
SiSpwEvent *pEvent, SiSpwHandlers *pDHandlers);
void SiOpenWinInit (SiOpenData *pData, HWND hWnd);
SiHdl SiOpen (char *pAppName, SiDevID devID, SiTypeMask *pTMask, int mode,
SiOpenData *pData);
enum SpwRetVal SiClose (SiHdl hdl);
void SiGetEventWinInit (SiGetEventData *pData,
UINT msg, WPARAM wParam, LPARAM lParam);
enum SpwRetVal SiGetEvent (SiHdl hdl, int flags, SiGetEventData *pData,
SiSpwEvent *pEvent);
enum SpwRetVal SiBeep (SiHdl hdl, char *string);
enum SpwRetVal SiRezero (SiHdl hdl);
enum SpwRetVal SiGrabDevice (SiHdl hdl, SPWbool exclusive);
enum SpwRetVal SiReleaseDevice (SiHdl hdl);
int SiButtonPressed (SiSpwEvent *pEvent);
int SiButtonReleased (SiSpwEvent *pEvent);
enum SpwRetVal SiSetUiMode (SiHdl hdl, SPWuint32 mode);
enum SpwRetVal SiSetTypeMask (SiTypeMask *pTMask, int type1, ...);
enum SpwRetVal SiGetDevicePort (SiDevID devID, SiDevPort *pPort);
enum SpwRetVal SiGetDriverInfo (SiVerInfo *pInfo);
void SiGetLibraryInfo (SiVerInfo *pInfo);
enum SpwRetVal SiGetDeviceInfo (SiHdl hdl, SiDevInfo *pInfo);
char * SpwErrorString (enum SpwRetVal val);
enum SpwRetVal SiSyncSendQuery(SiHdl hdl);
enum SpwRetVal SiSyncGetVersion(SiHdl hdl, SPWuint32 *pmajor, SPWuint32 *pminor);
enum SpwRetVal SiSyncGetNumberOfFunctions(SiHdl hdl, SPWuint32 *pnumberOfFunctions);
enum SpwRetVal SiSyncGetFunction(SiHdl hdl, SPWuint32 index, SPWint32 *pabsoluteFunctionNumber, WCHAR name[], SPWuint32 *pmaxNameLen);
enum SpwRetVal SiSyncGetButtonAssignment(SiHdl hdl, SPWuint32 buttonNumber, SPWint32 *passignedFunctionIndex);
enum SpwRetVal SiSyncSetButtonAssignment(SiHdl hdl, SPWuint32 buttonNumber, SPWint32 functionIndex);
enum SpwRetVal SiSyncSetButtonAssignmentAbsolute(SiHdl hdl, SPWuint32 buttonNumber, SPWint32 absoluteFunctionNumber );
enum SpwRetVal SiSyncSetButtonName(SiHdl hdl, SPWuint32 buttonNumber, WCHAR name[]);
enum SpwRetVal SiSyncGetAxisLabel (SiHdl hdl, SPWuint32 axisNumber, WCHAR name[], SPWuint32 *pmaxNameLen );
enum SpwRetVal SiSyncSetAxisLabel (SiHdl hdl, SPWuint32 axisNumber, WCHAR name[] );
enum SpwRetVal SiSyncGetOrientation (SiHdl hdl, SPWint32 axes[6] );
enum SpwRetVal SiSyncSetOrientation (SiHdl hdl, SPWint32 axes[6] );
enum SpwRetVal SiSyncGetFilter (SiHdl hdl, SiSyncFilter i, SiSyncFilterValue *pv );
enum SpwRetVal SiSyncSetFilter (SiHdl hdl, SiSyncFilter i, SiSyncFilterValue v );
enum SpwRetVal SiSyncGetAxesState (SiHdl hdl, SiSyncAxesState *pa );
enum SpwRetVal SiSyncSetAxesState (SiHdl hdl, SiSyncAxesState a );
enum SpwRetVal SiSyncSetInfoLine (SiHdl hdl, SPWint32 duration, WCHAR text[] );
enum SpwRetVal SiSyncGetScaleOverall (SiHdl hdl, SPWfloat32 *pv );
enum SpwRetVal SiSyncSetScaleOverall (SiHdl hdl, SPWfloat32 v );
enum SpwRetVal SiSyncGetScaleTx (SiHdl hdl, SPWfloat32 *pv );
enum SpwRetVal SiSyncSetScaleTx (SiHdl hdl, SPWfloat32 v );
enum SpwRetVal SiSyncGetScaleTy (SiHdl hdl, SPWfloat32 *pv );
enum SpwRetVal SiSyncSetScaleTy (SiHdl hdl, SPWfloat32 v );
enum SpwRetVal SiSyncGetScaleTz (SiHdl hdl, SPWfloat32 *pv );
enum SpwRetVal SiSyncSetScaleTz (SiHdl hdl, SPWfloat32 v );
enum SpwRetVal SiSyncGetScaleRx (SiHdl hdl, SPWfloat32 *pv );
enum SpwRetVal SiSyncSetScaleRx (SiHdl hdl, SPWfloat32 v );
enum SpwRetVal SiSyncGetScaleRy (SiHdl hdl, SPWfloat32 *pv );
enum SpwRetVal SiSyncSetScaleRy (SiHdl hdl, SPWfloat32 v );
enum SpwRetVal SiSyncGetScaleRz (SiHdl hdl, SPWfloat32 *pv );
enum SpwRetVal SiSyncSetScaleRz (SiHdl hdl, SPWfloat32 v );
enum SpwRetVal SiSyncInvokeAbsoluteFunction (SiHdl hdl, SiSyncAbsFunctionNumber i );
enum SpwRetVal SiSyncSetButtonState (SiHdl hdl, SPWuint32 buttonNumber, SiSyncButtonState state );
enum SpwRetVal SiGetButtonName (SiHdl hdl, SPWuint32 buttonNumber, SiButtonName *pname);
enum SpwRetVal SiGetDeviceName (SiHdl hdl, SiDeviceName *pname);
enum SpwRetVal SiGetDeviceImageFileName (SiHdl hdl, char name[], SPWuint32 *pmaxNameLen);
HICON SiGetCompanyIcon(void);
enum SpwRetVal SiGetCompanyLogoFileName (char name[], SPWuint32 *pmaxNameLen);
#ifdef __cplusplus
}
#endif
#endif /* #ifndef SIAPP_H */

Binary file not shown.

View File

@ -1,63 +0,0 @@
/*----------------------------------------------------------------------
* spwdata.h -- datatypes
*
*
* $Id: spwdata.h,v 1.4 1996/10/08 23:01:39 chris Exp $
*
* This contains the only acceptable type definitions for 3Dconnexion
* products. Needs more work.
*
*----------------------------------------------------------------------
*
* (c) 1996-2005 3Dconnexion. All rights reserved.
*
* The computer codes included in this file, including source code and
* object code, constitutes the proprietary and confidential information of
* 3Dconnexion, and are provided pursuant to a license
* agreement. These computer codes are protected by international, federal
* and state law, including United States Copyright Law and international
* treaty provisions. Except as expressly authorized by the license
* agreement, or as expressly permitted under applicable laws of member
* states of the European Union and then only to the extent so permitted,
* no part of these computer codes may be reproduced or transmitted in any
* form or by any means, electronic or mechanical, modified, decompiled,
* disassembled, reverse engineered, sold, transferred, rented or utilized
* for any unauthorized purpose without the express written permission of
* 3Dconnexion.
*
*----------------------------------------------------------------------
*
*/
#ifndef SPWDATA_H
#define SPWDATA_H
static char spwdataCvsId[]="(C) 1996-2005 3Dconnexion: $Id: spwdata.h,v 1.4 1996/10/08 23:01:39 chris Exp $";
#include <tchar.h>
#define tchar_t _TCHAR
#define char_t char
#define uint32_t unsigned long
#define sint32_t long
#define boolean_t unsigned char
#define void_t void
#define window_handle_t HWND
typedef long SPWint32;
typedef short SPWint16;
typedef char SPWint8;
typedef int SPWbool;
typedef unsigned long SPWuint32;
typedef unsigned short SPWuint16;
typedef unsigned char SPWuint8;
typedef _TCHAR SPWchar;
typedef _TCHAR* SPWstring;
typedef float SPWfloat32;
typedef double SPWfloat64;
#endif /* SPWDATA_H */

View File

@ -1,64 +0,0 @@
/*----------------------------------------------------------------------
* spwerror.h -- Standard Spacetec IMC function return values
*
* $Id: spwerror.h,v 1.10.4.1 1998/05/26 17:30:21 equerze Exp $
*
* This file contains all the Spacetec IMC standard error return
* return values for functions
*
*----------------------------------------------------------------------
*
* (C) 1998-2001 3Dconnexion. All rights reserved.
* Permission to use, copy, modify, and distribute this software for all
* purposes and without fees is hereby grated provided that this copyright
* notice appears in all copies. Permission to modify this software is granted
* and 3Dconnexion will support such modifications only is said modifications are
* approved by 3Dconnexion.
*
*/
#ifndef _SPWERROR_H_
#define _SPWERROR_H_
#include "spwdata.h"
static char spwerrorCvsId[]="(C) 1996 Spacetec IMC Corporation: $Id: spwerror.h,v 1.10.4.1 1998/05/26 17:30:21 equerze Exp $";
enum SpwRetVal /* Error return values. */
{
SPW_NO_ERROR, /* No error. */
SPW_ERROR, /* Error -- function failed. */
SI_BAD_HANDLE, /* Invalid SpaceWare handle. */
SI_BAD_ID, /* Invalid device ID. */
SI_BAD_VALUE, /* Invalid argument value. */
SI_IS_EVENT, /* Event is a SpaceWare event. */
SI_SKIP_EVENT, /* Skip this SpaceWare event. */
SI_NOT_EVENT, /* Event is not a SpaceWare event. */
SI_NO_DRIVER, /* SpaceWare driver is not running. */
SI_NO_RESPONSE, /* SpaceWare driver is not responding. */
SI_UNSUPPORTED, /* The function is unsupported by this version. */
SI_UNINITIALIZED, /* SpaceWare input library is uninitialized. */
SI_WRONG_DRIVER, /* Driver is incorrect for this SpaceWare version.*/
SI_INTERNAL_ERROR, /* Internal SpaceWare error. */
SI_BAD_PROTOCOL, /* The transport protocol is unknown. */
SI_OUT_OF_MEMORY, /* Unable to malloc space required. */
SPW_DLL_LOAD_ERROR, /* Could not load siapp dlls */
SI_NOT_OPEN, /* Spaceball device not open */
SI_ITEM_NOT_FOUND, /* Item not found */
SI_UNSUPPORTED_DEVICE, /* The device is not supported */
SI_NOT_ENOUGH_MEMORY, /* Not enough memory (but not a malloc problem) */
SI_SYNC_WRONG_HASHCODE /* Wrong hash code sent to a Sync function */
};
typedef enum SpwRetVal SpwReturnValue;
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* _SPWERROR_H_ */

View File

@ -1,48 +0,0 @@
/*----------------------------------------------------------------------
* spwmacro.h -- cpp macros we ALWAYS use.
*
<<<<<<< spwmacro.h
* $Id: spwmacro.h,v 1.3 2001/01/16 01:18:40 HJin Exp $
=======
* $Id: spwmacro.h,v 1.3 2001/01/16 01:18:40 HJin Exp $
>>>>>>> 1.1.1.1.4.1
*
* We always seem to use the same macros.
* This is the place we define them.
*
*----------------------------------------------------------------------
*/
#ifndef SPWMACRO_H
#define SPWMACRO_H
#define SPW_FALSE (0)
#define SPW_TRUE (!SPW_FALSE)
#define SPW_MAX(a,b) (((a)>(b))?(a):(b))
#define SPW_MIN(a,b) (((a)<(b))?(a):(b))
#define SPW_ABS(a) (((a)<0)?(-(a)):(a))
#define SPW_SIGN(a) ((a)>=0?1:-1)
#define SPW_BIND(min,n,max) (SPW_MIN((max),SPW_MAX((min),(n))))
#define SPW_NUM_ELEMENTS_IN(a) (sizeof(a)/sizeof((a)[0]))
#define SPW_PI 3.14159265358979324f
#define SPW_DEG_TO_RAD(d) ((d)*SPW_PI/180.0f)
#define SPW_RAD_TO_DEG(r) ((r)*180.0f/SPW_PI)
#define SPW_LENGTH_OF(a) (sizeof(a)/sizeof((a)[0]))
#define SPW_END_OF(a) (&(a)[SPW_LENGTH_OF(a)-1])
#define SPW_SQ(a) ((a)*(a))
#define SPW_ABSDIFF(a, b) (fabs((double) (a) - (b)))
#endif

View File

@ -1,332 +0,0 @@
/* zconf.h -- configuration of the zlib compression library
* Copyright (C) 1995-2005 Jean-loup Gailly.
* For conditions of distribution and use, see copyright notice in zlib.h
*/
/* @(#) $Id$ */
#ifndef ZCONF_H
#define ZCONF_H
/*
* If you *really* need a unique prefix for all types and library functions,
* compile with -DZ_PREFIX. The "standard" zlib should be compiled without it.
*/
#ifdef Z_PREFIX
# define deflateInit_ z_deflateInit_
# define deflate z_deflate
# define deflateEnd z_deflateEnd
# define inflateInit_ z_inflateInit_
# define inflate z_inflate
# define inflateEnd z_inflateEnd
# define deflateInit2_ z_deflateInit2_
# define deflateSetDictionary z_deflateSetDictionary
# define deflateCopy z_deflateCopy
# define deflateReset z_deflateReset
# define deflateParams z_deflateParams
# define deflateBound z_deflateBound
# define deflatePrime z_deflatePrime
# define inflateInit2_ z_inflateInit2_
# define inflateSetDictionary z_inflateSetDictionary
# define inflateSync z_inflateSync
# define inflateSyncPoint z_inflateSyncPoint
# define inflateCopy z_inflateCopy
# define inflateReset z_inflateReset
# define inflateBack z_inflateBack
# define inflateBackEnd z_inflateBackEnd
# define compress z_compress
# define compress2 z_compress2
# define compressBound z_compressBound
# define uncompress z_uncompress
# define adler32 z_adler32
# define crc32 z_crc32
# define get_crc_table z_get_crc_table
# define zError z_zError
# define alloc_func z_alloc_func
# define free_func z_free_func
# define in_func z_in_func
# define out_func z_out_func
# define Byte z_Byte
# define uInt z_uInt
# define uLong z_uLong
# define Bytef z_Bytef
# define charf z_charf
# define intf z_intf
# define uIntf z_uIntf
# define uLongf z_uLongf
# define voidpf z_voidpf
# define voidp z_voidp
#endif
#if defined(__MSDOS__) && !defined(MSDOS)
# define MSDOS
#endif
#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2)
# define OS2
#endif
#if defined(_WINDOWS) && !defined(WINDOWS)
# define WINDOWS
#endif
#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__)
# ifndef WIN32
# define WIN32
# endif
#endif
#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32)
# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__)
# ifndef SYS16BIT
# define SYS16BIT
# endif
# endif
#endif
/*
* Compile with -DMAXSEG_64K if the alloc function cannot allocate more
* than 64k bytes at a time (needed on systems with 16-bit int).
*/
#ifdef SYS16BIT
# define MAXSEG_64K
#endif
#ifdef MSDOS
# define UNALIGNED_OK
#endif
#ifdef __STDC_VERSION__
# ifndef STDC
# define STDC
# endif
# if __STDC_VERSION__ >= 199901L
# ifndef STDC99
# define STDC99
# endif
# endif
#endif
#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus))
# define STDC
#endif
#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__))
# define STDC
#endif
#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32))
# define STDC
#endif
#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__))
# define STDC
#endif
#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */
# define STDC
#endif
#ifndef STDC
# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */
# define const /* note: need a more gentle solution here */
# endif
#endif
/* Some Mac compilers merge all .h files incorrectly: */
#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__)
# define NO_DUMMY_DECL
#endif
/* Maximum value for memLevel in deflateInit2 */
#ifndef MAX_MEM_LEVEL
# ifdef MAXSEG_64K
# define MAX_MEM_LEVEL 8
# else
# define MAX_MEM_LEVEL 9
# endif
#endif
/* Maximum value for windowBits in deflateInit2 and inflateInit2.
* WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
* created by gzip. (Files created by minigzip can still be extracted by
* gzip.)
*/
#ifndef MAX_WBITS
# define MAX_WBITS 15 /* 32K LZ77 window */
#endif
/* The memory requirements for deflate are (in bytes):
(1 << (windowBits+2)) + (1 << (memLevel+9))
that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values)
plus a few kilobytes for small objects. For example, if you want to reduce
the default memory requirements from 256K to 128K, compile with
make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
Of course this will generally degrade compression (there's no free lunch).
The memory requirements for inflate are (in bytes) 1 << windowBits
that is, 32K for windowBits=15 (default value) plus a few kilobytes
for small objects.
*/
/* Type declarations */
#ifndef OF /* function prototypes */
# ifdef STDC
# define OF(args) args
# else
# define OF(args) ()
# endif
#endif
/* The following definitions for FAR are needed only for MSDOS mixed
* model programming (small or medium model with some far allocations).
* This was tested only with MSC; for other MSDOS compilers you may have
* to define NO_MEMCPY in zutil.h. If you don't need the mixed model,
* just define FAR to be empty.
*/
#ifdef SYS16BIT
# if defined(M_I86SM) || defined(M_I86MM)
/* MSC small or medium model */
# define SMALL_MEDIUM
# ifdef _MSC_VER
# define FAR _far
# else
# define FAR far
# endif
# endif
# if (defined(__SMALL__) || defined(__MEDIUM__))
/* Turbo C small or medium model */
# define SMALL_MEDIUM
# ifdef __BORLANDC__
# define FAR _far
# else
# define FAR far
# endif
# endif
#endif
#if defined(WINDOWS) || defined(WIN32)
/* If building or using zlib as a DLL, define ZLIB_DLL.
* This is not mandatory, but it offers a little performance increase.
*/
# ifdef ZLIB_DLL
# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500))
# ifdef ZLIB_INTERNAL
# define ZEXTERN extern __declspec(dllexport)
# else
# define ZEXTERN extern __declspec(dllimport)
# endif
# endif
# endif /* ZLIB_DLL */
/* If building or using zlib with the WINAPI/WINAPIV calling convention,
* define ZLIB_WINAPI.
* Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
*/
# ifdef ZLIB_WINAPI
# ifdef FAR
# undef FAR
# endif
# include <windows.h>
/* No need for _export, use ZLIB.DEF instead. */
/* For complete Windows compatibility, use WINAPI, not __stdcall. */
# define ZEXPORT WINAPI
# ifdef WIN32
# define ZEXPORTVA WINAPIV
# else
# define ZEXPORTVA FAR CDECL
# endif
# endif
#endif
#if defined (__BEOS__)
# ifdef ZLIB_DLL
# ifdef ZLIB_INTERNAL
# define ZEXPORT __declspec(dllexport)
# define ZEXPORTVA __declspec(dllexport)
# else
# define ZEXPORT __declspec(dllimport)
# define ZEXPORTVA __declspec(dllimport)
# endif
# endif
#endif
#ifndef ZEXTERN
# define ZEXTERN extern
#endif
#ifndef ZEXPORT
# define ZEXPORT
#endif
#ifndef ZEXPORTVA
# define ZEXPORTVA
#endif
#ifndef FAR
# define FAR
#endif
#if !defined(__MACTYPES__)
typedef unsigned char Byte; /* 8 bits */
#endif
typedef unsigned int uInt; /* 16 bits or more */
typedef unsigned long uLong; /* 32 bits or more */
#ifdef SMALL_MEDIUM
/* Borland C/C++ and some old MSC versions ignore FAR inside typedef */
# define Bytef Byte FAR
#else
typedef Byte FAR Bytef;
#endif
typedef char FAR charf;
typedef int FAR intf;
typedef uInt FAR uIntf;
typedef uLong FAR uLongf;
#ifdef STDC
typedef void const *voidpc;
typedef void FAR *voidpf;
typedef void *voidp;
#else
typedef Byte const *voidpc;
typedef Byte FAR *voidpf;
typedef Byte *voidp;
#endif
#if 0 /* HAVE_UNISTD_H -- this line is updated by ./configure */
# include <sys/types.h> /* for off_t */
# include <unistd.h> /* for SEEK_* and off_t */
# ifdef VMS
# include <unixio.h> /* for off_t */
# endif
# define z_off_t off_t
#endif
#ifndef SEEK_SET
# define SEEK_SET 0 /* Seek from beginning of file. */
# define SEEK_CUR 1 /* Seek from current position. */
# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
#endif
#ifndef z_off_t
# define z_off_t long
#endif
#if defined(__OS400__)
# define NO_vsnprintf
#endif
#if defined(__MVS__)
# define NO_vsnprintf
# ifdef FAR
# undef FAR
# endif
#endif
/* MVS linker does not support external names larger than 8 bytes */
#if defined(__MVS__)
# pragma map(deflateInit_,"DEIN")
# pragma map(deflateInit2_,"DEIN2")
# pragma map(deflateEnd,"DEEND")
# pragma map(deflateBound,"DEBND")
# pragma map(inflateInit_,"ININ")
# pragma map(inflateInit2_,"ININ2")
# pragma map(inflateEnd,"INEND")
# pragma map(inflateSync,"INSY")
# pragma map(inflateSetDictionary,"INSEDI")
# pragma map(compressBound,"CMBND")
# pragma map(inflate_table,"INTABL")
# pragma map(inflate_fast,"INFA")
# pragma map(inflate_copyright,"INCOPY")
#endif
#endif /* ZCONF_H */

File diff suppressed because it is too large Load Diff

Binary file not shown.

676
file.cpp
View File

@ -1,676 +0,0 @@
//-----------------------------------------------------------------------------
// Routines to write and read our .slvs file format.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
#define VERSION_STRING "±²³SolveSpaceREVa"
static int StrStartsWith(char *str, char *start) {
return memcmp(str, start, strlen(start)) == 0;
}
//-----------------------------------------------------------------------------
// Clear and free all the dynamic memory associated with our currently-loaded
// sketch. This does not leave the program in an acceptable state (with the
// references created, and so on), so anyone calling this must fix that later.
//-----------------------------------------------------------------------------
void SolveSpace::ClearExisting(void) {
UndoClearStack(&redo);
UndoClearStack(&undo);
Group *g;
for(g = SK.group.First(); g; g = SK.group.NextAfter(g)) {
g->Clear();
}
SK.constraint.Clear();
SK.request.Clear();
SK.group.Clear();
SK.style.Clear();
SK.entity.Clear();
SK.param.Clear();
}
hGroup SolveSpace::CreateDefaultDrawingGroup(void) {
Group g;
ZERO(&g);
// And an empty group, for the first stuff the user draws.
g.visible = true;
g.type = Group::DRAWING_WORKPLANE;
g.subtype = Group::WORKPLANE_BY_POINT_ORTHO;
g.predef.q = Quaternion::From(1, 0, 0, 0);
hRequest hr = Request::HREQUEST_REFERENCE_XY;
g.predef.origin = hr.entity(1);
g.name.strcpy("sketch-in-plane");
SK.group.AddAndAssignId(&g);
SK.GetGroup(g.h)->activeWorkplane = g.h.entity(0);
return g.h;
}
void SolveSpace::NewFile(void) {
ClearExisting();
// Our initial group, that contains the references.
Group g;
memset(&g, 0, sizeof(g));
g.visible = true;
g.name.strcpy("#references");
g.type = Group::DRAWING_3D;
g.h = Group::HGROUP_REFERENCES;
SK.group.Add(&g);
// Let's create three two-d coordinate systems, for the coordinate
// planes; these are our references, present in every sketch.
Request r;
ZERO(&r);
r.type = Request::WORKPLANE;
r.group = Group::HGROUP_REFERENCES;
r.workplane = Entity::FREE_IN_3D;
r.h = Request::HREQUEST_REFERENCE_XY;
SK.request.Add(&r);
r.h = Request::HREQUEST_REFERENCE_YZ;
SK.request.Add(&r);
r.h = Request::HREQUEST_REFERENCE_ZX;
SK.request.Add(&r);
CreateDefaultDrawingGroup();
}
const SolveSpace::SaveTable SolveSpace::SAVED[] = {
{ 'g', "Group.h.v", 'x', &(SS.sv.g.h.v) },
{ 'g', "Group.type", 'd', &(SS.sv.g.type) },
{ 'g', "Group.order", 'd', &(SS.sv.g.order) },
{ 'g', "Group.name", 'N', &(SS.sv.g.name) },
{ 'g', "Group.activeWorkplane.v", 'x', &(SS.sv.g.activeWorkplane.v) },
{ 'g', "Group.opA.v", 'x', &(SS.sv.g.opA.v) },
{ 'g', "Group.opB.v", 'x', &(SS.sv.g.opB.v) },
{ 'g', "Group.valA", 'f', &(SS.sv.g.valA) },
{ 'g', "Group.valB", 'f', &(SS.sv.g.valB) },
{ 'g', "Group.valC", 'f', &(SS.sv.g.valB) },
{ 'g', "Group.color", 'x', &(SS.sv.g.color) },
{ 'g', "Group.subtype", 'd', &(SS.sv.g.subtype) },
{ 'g', "Group.skipFirst", 'b', &(SS.sv.g.skipFirst) },
{ 'g', "Group.meshCombine", 'd', &(SS.sv.g.meshCombine) },
{ 'g', "Group.forceToMesh", 'd', &(SS.sv.g.forceToMesh) },
{ 'g', "Group.predef.q.w", 'f', &(SS.sv.g.predef.q.w) },
{ 'g', "Group.predef.q.vx", 'f', &(SS.sv.g.predef.q.vx) },
{ 'g', "Group.predef.q.vy", 'f', &(SS.sv.g.predef.q.vy) },
{ 'g', "Group.predef.q.vz", 'f', &(SS.sv.g.predef.q.vz) },
{ 'g', "Group.predef.origin.v", 'x', &(SS.sv.g.predef.origin.v) },
{ 'g', "Group.predef.entityB.v", 'x', &(SS.sv.g.predef.entityB.v) },
{ 'g', "Group.predef.entityC.v", 'x', &(SS.sv.g.predef.entityC.v) },
{ 'g', "Group.predef.swapUV", 'b', &(SS.sv.g.predef.swapUV) },
{ 'g', "Group.predef.negateU", 'b', &(SS.sv.g.predef.negateU) },
{ 'g', "Group.predef.negateV", 'b', &(SS.sv.g.predef.negateV) },
{ '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.allDimsReference", 'b', &(SS.sv.g.allDimsReference) },
{ 'g', "Group.scale", 'f', &(SS.sv.g.scale) },
{ 'g', "Group.remap", 'M', &(SS.sv.g.remap) },
{ 'g', "Group.impFile", 'P', &(SS.sv.g.impFile) },
{ 'g', "Group.impFileRel", 'P', &(SS.sv.g.impFileRel) },
{ 'p', "Param.h.v.", 'x', &(SS.sv.p.h.v) },
{ 'p', "Param.val", 'f', &(SS.sv.p.val) },
{ 'r', "Request.h.v", 'x', &(SS.sv.r.h.v) },
{ 'r', "Request.type", 'd', &(SS.sv.r.type) },
{ 'r', "Request.extraPoints", 'd', &(SS.sv.r.extraPoints) },
{ 'r', "Request.workplane.v", 'x', &(SS.sv.r.workplane.v) },
{ 'r', "Request.group.v", 'x', &(SS.sv.r.group.v) },
{ 'r', "Request.construction", 'b', &(SS.sv.r.construction) },
{ 'r', "Request.style", 'x', &(SS.sv.r.style) },
{ 'r', "Request.str", 'N', &(SS.sv.r.str) },
{ 'r', "Request.font", 'N', &(SS.sv.r.font) },
{ 'e', "Entity.h.v", 'x', &(SS.sv.e.h.v) },
{ 'e', "Entity.type", 'd', &(SS.sv.e.type) },
{ 'e', "Entity.construction", 'b', &(SS.sv.e.construction) },
{ 'e', "Entity.style", 'x', &(SS.sv.e.style) },
{ 'e', "Entity.str", 'N', &(SS.sv.e.str) },
{ 'e', "Entity.font", 'N', &(SS.sv.e.font) },
{ 'e', "Entity.point[0].v", 'x', &(SS.sv.e.point[0].v) },
{ 'e', "Entity.point[1].v", 'x', &(SS.sv.e.point[1].v) },
{ 'e', "Entity.point[2].v", 'x', &(SS.sv.e.point[2].v) },
{ 'e', "Entity.point[3].v", 'x', &(SS.sv.e.point[3].v) },
{ 'e', "Entity.point[4].v", 'x', &(SS.sv.e.point[4].v) },
{ 'e', "Entity.point[5].v", 'x', &(SS.sv.e.point[5].v) },
{ 'e', "Entity.point[6].v", 'x', &(SS.sv.e.point[6].v) },
{ 'e', "Entity.point[7].v", 'x', &(SS.sv.e.point[7].v) },
{ 'e', "Entity.point[8].v", 'x', &(SS.sv.e.point[8].v) },
{ 'e', "Entity.point[9].v", 'x', &(SS.sv.e.point[9].v) },
{ 'e', "Entity.point[10].v", 'x', &(SS.sv.e.point[10].v) },
{ 'e', "Entity.point[11].v", 'x', &(SS.sv.e.point[11].v) },
{ 'e', "Entity.extraPoints", 'd', &(SS.sv.e.extraPoints) },
{ 'e', "Entity.normal.v", 'x', &(SS.sv.e.normal.v) },
{ 'e', "Entity.distance.v", 'x', &(SS.sv.e.distance.v) },
{ 'e', "Entity.workplane.v", 'x', &(SS.sv.e.workplane.v) },
{ 'e', "Entity.actPoint.x", 'f', &(SS.sv.e.actPoint.x) },
{ 'e', "Entity.actPoint.y", 'f', &(SS.sv.e.actPoint.y) },
{ 'e', "Entity.actPoint.z", 'f', &(SS.sv.e.actPoint.z) },
{ 'e', "Entity.actNormal.w", 'f', &(SS.sv.e.actNormal.w) },
{ 'e', "Entity.actNormal.vx", 'f', &(SS.sv.e.actNormal.vx) },
{ 'e', "Entity.actNormal.vy", 'f', &(SS.sv.e.actNormal.vy) },
{ 'e', "Entity.actNormal.vz", 'f', &(SS.sv.e.actNormal.vz) },
{ 'e', "Entity.actDistance", 'f', &(SS.sv.e.actDistance) },
{ 'e', "Entity.actVisible", 'b', &(SS.sv.e.actVisible), },
{ 'c', "Constraint.h.v", 'x', &(SS.sv.c.h.v) },
{ 'c', "Constraint.type", 'd', &(SS.sv.c.type) },
{ 'c', "Constraint.group.v", 'x', &(SS.sv.c.group.v) },
{ 'c', "Constraint.workplane.v", 'x', &(SS.sv.c.workplane.v) },
{ 'c', "Constraint.valA", 'f', &(SS.sv.c.valA) },
{ 'c', "Constraint.ptA.v", 'x', &(SS.sv.c.ptA.v) },
{ 'c', "Constraint.ptB.v", 'x', &(SS.sv.c.ptB.v) },
{ 'c', "Constraint.entityA.v", 'x', &(SS.sv.c.entityA.v) },
{ 'c', "Constraint.entityB.v", 'x', &(SS.sv.c.entityB.v) },
{ 'c', "Constraint.entityC.v", 'x', &(SS.sv.c.entityC.v) },
{ 'c', "Constraint.entityD.v", 'x', &(SS.sv.c.entityD.v) },
{ 'c', "Constraint.other", 'b', &(SS.sv.c.other) },
{ 'c', "Constraint.other2", 'b', &(SS.sv.c.other2) },
{ 'c', "Constraint.reference", 'b', &(SS.sv.c.reference) },
{ 'c', "Constraint.comment", 'N', &(SS.sv.c.comment) },
{ 'c', "Constraint.disp.offset.x", 'f', &(SS.sv.c.disp.offset.x) },
{ 'c', "Constraint.disp.offset.y", 'f', &(SS.sv.c.disp.offset.y) },
{ 'c', "Constraint.disp.offset.z", 'f', &(SS.sv.c.disp.offset.z) },
{ 'c', "Constraint.disp.style", 'x', &(SS.sv.c.disp.style) },
{ 's', "Style.h.v", 'x', &(SS.sv.s.h.v) },
{ 's', "Style.name", 'N', &(SS.sv.s.name) },
{ 's', "Style.width", 'f', &(SS.sv.s.width) },
{ 's', "Style.widthAs", 'd', &(SS.sv.s.widthAs) },
{ 's', "Style.textHeight", 'f', &(SS.sv.s.textHeight) },
{ 's', "Style.textHeightAs", 'd', &(SS.sv.s.textHeightAs) },
{ 's', "Style.textAngle", 'f', &(SS.sv.s.textAngle) },
{ 's', "Style.textOrigin", 'x', &(SS.sv.s.textOrigin) },
{ 's', "Style.color", 'x', &(SS.sv.s.color) },
{ 's', "Style.fillColor", 'x', &(SS.sv.s.fillColor) },
{ 's', "Style.filled", 'b', &(SS.sv.s.filled) },
{ 's', "Style.visible", 'b', &(SS.sv.s.visible) },
{ 's', "Style.exportable", 'b', &(SS.sv.s.exportable) },
{ 0, NULL, NULL, NULL },
};
void SolveSpace::SaveUsingTable(int type) {
int i;
for(i = 0; SAVED[i].type != 0; i++) {
if(SAVED[i].type != type) continue;
int fmt = SAVED[i].fmt;
void *p = SAVED[i].ptr;
// Any items that aren't specified are assumed to be zero
if(fmt == 'd' && *((int *)p) == 0) continue;
if(fmt == 'x' && *((DWORD *)p) == 0) continue;
if(fmt == 'f' && *((double *)p) == 0.0) continue;
if(fmt == 'N' && strlen(((NameStr *)p)->str) == 0) continue;
fprintf(fh, "%s=", SAVED[i].desc);
switch(fmt) {
case 'd': fprintf(fh, "%d", *((int *)p)); break;
case 'b': fprintf(fh, "%d", *((bool *)p) ? 1 : 0); break;
case 'x': fprintf(fh, "%08x", *((DWORD *)p)); break;
case 'f': fprintf(fh, "%.20f", *((double *)p)); break;
case 'N': fprintf(fh, "%s", ((NameStr *)p)->str); break;
case 'P': fprintf(fh, "%s", (char *)p); break;
case 'M': {
int j;
fprintf(fh, "{\n");
IdList<EntityMap,EntityId> *m = (IdList<EntityMap,EntityId> *)p;
for(j = 0; j < m->n; j++) {
EntityMap *em = &(m->elem[j]);
fprintf(fh, " %d %08x %d\n",
em->h.v, em->input.v, em->copyNumber);
}
fprintf(fh, "}");
break;
}
default: oops();
}
fprintf(fh, "\n");
}
}
bool SolveSpace::SaveToFile(char *filename) {
// Make sure all the entities are regenerated up to date, since they
// will be exported.
SS.GenerateAll(0, INT_MAX);
fh = fopen(filename, "wb");
if(!fh) {
Error("Couldn't write to file '%s'", filename);
return false;
}
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];
SaveUsingTable('g');
fprintf(fh, "AddGroup\n\n");
}
for(i = 0; i < SK.param.n; i++) {
sv.p = SK.param.elem[i];
SaveUsingTable('p');
fprintf(fh, "AddParam\n\n");
}
for(i = 0; i < SK.request.n; i++) {
sv.r = SK.request.elem[i];
SaveUsingTable('r');
fprintf(fh, "AddRequest\n\n");
}
for(i = 0; i < SK.entity.n; i++) {
(SK.entity.elem[i]).CalculateNumerical(true);
sv.e = SK.entity.elem[i];
SaveUsingTable('e');
fprintf(fh, "AddEntity\n\n");
}
for(i = 0; i < SK.constraint.n; i++) {
sv.c = SK.constraint.elem[i];
SaveUsingTable('c');
fprintf(fh, "AddConstraint\n\n");
}
for(i = 0; i < SK.style.n; i++) {
sv.s = SK.style.elem[i];
if(sv.s.h.v >= Style::FIRST_CUSTOM) {
SaveUsingTable('s');
fprintf(fh, "AddStyle\n\n");
}
}
// 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.
SMesh *m = &(SK.group.elem[SK.group.n-1].runningMesh);
for(i = 0; i < m->l.n; i++) {
STriangle *tr = &(m->l.elem[i]);
fprintf(fh, "Triangle %08x %08x "
"%.20f %.20f %.20f %.20f %.20f %.20f %.20f %.20f %.20f\n",
tr->meta.face, tr->meta.color,
CO(tr->a), CO(tr->b), CO(tr->c));
}
SShell *s = &(SK.group.elem[SK.group.n-1].runningShell);
SSurface *srf;
for(srf = s->surface.First(); srf; srf = s->surface.NextAfter(srf)) {
fprintf(fh, "Surface %08x %08x %08x %d %d\n",
srf->h.v, srf->color, srf->face, srf->degm, srf->degn);
for(i = 0; i <= srf->degm; i++) {
for(j = 0; j <= srf->degn; j++) {
fprintf(fh, "SCtrl %d %d %.20f %.20f %.20f Weight %20.20f\n",
i, j, CO(srf->ctrl[i][j]), srf->weight[i][j]);
}
}
STrimBy *stb;
for(stb = srf->trim.First(); stb; stb = srf->trim.NextAfter(stb)) {
fprintf(fh, "TrimBy %08x %d %.20f %.20f %.20f %.20f %.20f %.20f\n",
stb->curve.v, stb->backwards ? 1 : 0,
CO(stb->start), CO(stb->finish));
}
fprintf(fh, "AddSurface\n");
}
SCurve *sc;
for(sc = s->curve.First(); sc; sc = s->curve.NextAfter(sc)) {
fprintf(fh, "Curve %08x %d %d %08x %08x\n",
sc->h.v,
sc->isExact ? 1 : 0, sc->exact.deg,
sc->surfA.v, sc->surfB.v);
if(sc->isExact) {
for(i = 0; i <= sc->exact.deg; i++) {
fprintf(fh, "CCtrl %d %.20f %.20f %.20f Weight %.20f\n",
i, CO(sc->exact.ctrl[i]), sc->exact.weight[i]);
}
}
SCurvePt *scpt;
for(scpt = sc->pts.First(); scpt; scpt = sc->pts.NextAfter(scpt)) {
fprintf(fh, "CurvePt %d %.20f %.20f %.20f\n",
scpt->vertex ? 1 : 0, CO(scpt->p));
}
fprintf(fh, "AddCurve\n");
}
fclose(fh);
return true;
}
void SolveSpace::LoadUsingTable(char *key, char *val) {
int i;
for(i = 0; SAVED[i].type != 0; i++) {
if(strcmp(SAVED[i].desc, key)==0) {
void *p = SAVED[i].ptr;
switch(SAVED[i].fmt) {
case 'd': *((int *)p) = atoi(val); break;
case 'b': *((bool *)p) = (atoi(val) != 0); break;
case 'x': sscanf(val, "%x", (DWORD *)p); break;
case 'f': *((double *)p) = atof(val); break;
case 'N': ((NameStr *)p)->strcpy(val); break;
case 'P':
if(strlen(val)+1 < MAX_PATH) strcpy((char *)p, val);
break;
case 'M': {
IdList<EntityMap,EntityId> *m =
(IdList<EntityMap,EntityId> *)p;
// Don't clear this list! When the group gets added, it
// makes a shallow copy, so that would result in us
// freeing memory that we want to keep around. Just
// zero it out so that new memory is allocated.
memset(m, 0, sizeof(*m));
for(;;) {
EntityMap em;
char line2[1024];
fgets(line2, sizeof(line2), fh);
if(sscanf(line2, "%d %x %d", &(em.h.v), &(em.input.v),
&(em.copyNumber)) == 3)
{
m->Add(&em);
} else {
break;
}
}
break;
}
default: oops();
}
break;
}
}
if(SAVED[i].type == 0) {
fileLoadError = true;
}
}
bool SolveSpace::LoadFromFile(char *filename) {
allConsistent = false;
fileLoadError = false;
fh = fopen(filename, "rb");
if(!fh) {
Error("Couldn't read from file '%s'", filename);
return false;
}
ClearExisting();
memset(&sv, 0, sizeof(sv));
sv.g.scale = 1; // default is 1, not 0; so legacy files need this
char line[1024];
while(fgets(line, sizeof(line), fh)) {
char *s = strchr(line, '\n');
if(s) *s = '\0';
// We should never get files with \r characters in them, but mailers
// will sometimes mangle attachments.
s = strchr(line, '\r');
if(s) *s = '\0';
if(*line == '\0') continue;
char *e = strchr(line, '=');
if(e) {
*e = '\0';
char *key = line, *val = e+1;
LoadUsingTable(key, val);
} else if(strcmp(line, "AddGroup")==0) {
SK.group.Add(&(sv.g));
ZERO(&(sv.g));
sv.g.scale = 1; // default is 1, not 0; so legacy files need this
} else if(strcmp(line, "AddParam")==0) {
// params are regenerated, but we want to preload the values
// for initial guesses
SK.param.Add(&(sv.p));
ZERO(&(sv.p));
} else if(strcmp(line, "AddEntity")==0) {
// entities are regenerated
} else if(strcmp(line, "AddRequest")==0) {
SK.request.Add(&(sv.r));
ZERO(&(sv.r));
} else if(strcmp(line, "AddConstraint")==0) {
SK.constraint.Add(&(sv.c));
ZERO(&(sv.c));
} else if(strcmp(line, "AddStyle")==0) {
SK.style.Add(&(sv.s));
ZERO(&(sv.s));
} else if(strcmp(line, VERSION_STRING)==0) {
// do nothing, version string
} else if(StrStartsWith(line, "Triangle ") ||
StrStartsWith(line, "Surface ") ||
StrStartsWith(line, "SCtrl ") ||
StrStartsWith(line, "TrimBy ") ||
StrStartsWith(line, "Curve ") ||
StrStartsWith(line, "CCtrl ") ||
StrStartsWith(line, "CurvePt ") ||
strcmp(line, "AddSurface")==0 ||
strcmp(line, "AddCurve")==0)
{
// ignore the mesh or shell, since we regenerate that
} else {
fileLoadError = true;
}
}
fclose(fh);
if(fileLoadError) {
Error("Unrecognized data in file. This file may be corrupt, or "
"from a new version of the program.");
// At least leave the program in a non-crashing state.
if(SK.group.n == 0) {
NewFile();
}
}
return true;
}
bool SolveSpace::LoadEntitiesFromFile(char *file, EntityList *le,
SMesh *m, SShell *sh)
{
SSurface srf;
ZERO(&srf);
SCurve crv;
ZERO(&crv);
fh = fopen(file, "rb");
if(!fh) return false;
le->Clear();
memset(&sv, 0, sizeof(sv));
char line[1024];
while(fgets(line, sizeof(line), fh)) {
char *s = strchr(line, '\n');
if(s) *s = '\0';
// We should never get files with \r characters in them, but mailers
// will sometimes mangle attachments.
s = strchr(line, '\r');
if(s) *s = '\0';
if(*line == '\0') continue;
char *e = strchr(line, '=');
if(e) {
*e = '\0';
char *key = line, *val = e+1;
LoadUsingTable(key, val);
} else if(strcmp(line, "AddGroup")==0) {
// Don't leak memory; these get allocated whether we want them
// or not.
sv.g.remap.Clear();
} else if(strcmp(line, "AddParam")==0) {
} else if(strcmp(line, "AddEntity")==0) {
le->Add(&(sv.e));
memset(&(sv.e), 0, sizeof(sv.e));
} else if(strcmp(line, "AddRequest")==0) {
} else if(strcmp(line, "AddConstraint")==0) {
} else if(strcmp(line, "AddStyle")==0) {
} else if(strcmp(line, VERSION_STRING)==0) {
} else if(StrStartsWith(line, "Triangle ")) {
STriangle tr; ZERO(&tr);
if(sscanf(line, "Triangle %x %x "
"%lf %lf %lf %lf %lf %lf %lf %lf %lf",
&(tr.meta.face), &(tr.meta.color),
&(tr.a.x), &(tr.a.y), &(tr.a.z),
&(tr.b.x), &(tr.b.y), &(tr.b.z),
&(tr.c.x), &(tr.c.y), &(tr.c.z)) != 11)
{
oops();
}
m->AddTriangle(&tr);
} else if(StrStartsWith(line, "Surface ")) {
if(sscanf(line, "Surface %x %x %x %d %d",
&(srf.h.v), &(srf.color), &(srf.face),
&(srf.degm), &(srf.degn)) != 5)
{
oops();
}
} else if(StrStartsWith(line, "SCtrl ")) {
int i, j;
Vector c;
double w;
if(sscanf(line, "SCtrl %d %d %lf %lf %lf Weight %lf",
&i, &j, &(c.x), &(c.y), &(c.z), &w) != 6)
{
oops();
}
srf.ctrl[i][j] = c;
srf.weight[i][j] = w;
} else if(StrStartsWith(line, "TrimBy ")) {
STrimBy stb;
ZERO(&stb);
int backwards;
if(sscanf(line, "TrimBy %x %d %lf %lf %lf %lf %lf %lf",
&(stb.curve.v), &backwards,
&(stb.start.x), &(stb.start.y), &(stb.start.z),
&(stb.finish.x), &(stb.finish.y), &(stb.finish.z)) != 8)
{
oops();
}
stb.backwards = (backwards != 0);
srf.trim.Add(&stb);
} else if(strcmp(line, "AddSurface")==0) {
sh->surface.Add(&srf);
ZERO(&srf);
} else if(StrStartsWith(line, "Curve ")) {
int isExact;
if(sscanf(line, "Curve %x %d %d %x %x",
&(crv.h.v),
&(isExact),
&(crv.exact.deg),
&(crv.surfA.v), &(crv.surfB.v)) != 5)
{
oops();
}
crv.isExact = (isExact != 0);
} else if(StrStartsWith(line, "CCtrl ")) {
int i;
Vector c;
double w;
if(sscanf(line, "CCtrl %d %lf %lf %lf Weight %lf",
&i, &(c.x), &(c.y), &(c.z), &w) != 5)
{
oops();
}
crv.exact.ctrl[i] = c;
crv.exact.weight[i] = w;
} else if(StrStartsWith(line, "CurvePt ")) {
SCurvePt scpt;
int vertex;
if(sscanf(line, "CurvePt %d %lf %lf %lf",
&vertex,
&(scpt.p.x), &(scpt.p.y), &(scpt.p.z)) != 4)
{
oops();
}
scpt.vertex = (vertex != 0);
crv.pts.Add(&scpt);
} else if(strcmp(line, "AddCurve")==0) {
sh->curve.Add(&crv);
ZERO(&crv);
} else {
oops();
}
}
fclose(fh);
return true;
}
void SolveSpace::ReloadAllImported(void) {
allConsistent = false;
int i;
for(i = 0; i < SK.group.n; i++) {
Group *g = &(SK.group.elem[i]);
if(g->type != Group::IMPORTED) continue;
g->impEntity.Clear();
g->impMesh.Clear();
g->impShell.Clear();
FILE *test = fopen(g->impFile, "rb");
if(test) {
fclose(test); // okay, exists
} else {
// It doesn't exist. Perhaps the entire tree has moved, and we
// can use the relative filename to get us back.
if(SS.saveFile[0]) {
char fromRel[MAX_PATH];
strcpy(fromRel, g->impFileRel);
MakePathAbsolute(SS.saveFile, fromRel);
test = fopen(fromRel, "rb");
if(test) {
fclose(test);
// It worked, this is our new absolute path
strcpy(g->impFile, fromRel);
}
}
}
if(LoadEntitiesFromFile(g->impFile,
&(g->impEntity), &(g->impMesh), &(g->impShell)))
{
if(SS.saveFile[0]) {
// Record the imported file's name relative to our filename;
// if the entire tree moves, then everything will still work
strcpy(g->impFileRel, g->impFile);
MakePathRelative(SS.saveFile, g->impFileRel);
} else {
// We're not yet saved, so can't make it absolute
strcpy(g->impFileRel, g->impFile);
}
} else {
Error("Failed to load imported file '%s'", g->impFile);
}
}
}

1774
font.table

File diff suppressed because it is too large Load Diff

View File

@ -1,489 +0,0 @@
//-----------------------------------------------------------------------------
// Generate our model based on its parametric description, by solving each
// sketch, generating surfaces from the resulting entities, performing any
// requested surface operations (e.g. Booleans) with our model so far, and
// then repeating this process for each subsequent group.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
void SolveSpace::MarkGroupDirtyByEntity(hEntity he) {
Entity *e = SK.GetEntity(he);
MarkGroupDirty(e->group);
}
void SolveSpace::MarkGroupDirty(hGroup hg) {
int i;
bool go = false;
for(i = 0; i < SK.group.n; i++) {
Group *g = &(SK.group.elem[i]);
if(g->h.v == hg.v) {
go = true;
}
if(go) {
g->clean = false;
}
}
unsaved = true;
}
bool SolveSpace::PruneOrphans(void) {
int i;
for(i = 0; i < SK.request.n; i++) {
Request *r = &(SK.request.elem[i]);
if(GroupExists(r->group)) continue;
(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;
(deleted.constraints)++;
(deleted.nonTrivialConstraints)++;
SK.constraint.RemoveById(c->h);
return true;
}
return false;
}
bool SolveSpace::GroupsInOrder(hGroup before, hGroup after) {
if(before.v == 0) return true;
if(after.v == 0) return true;
int beforep = -1, afterp = -1;
int i;
for(i = 0; i < SK.group.n; i++) {
Group *g = &(SK.group.elem[i]);
if(g->h.v == before.v) beforep = i;
if(g->h.v == after.v) afterp = i;
}
if(beforep < 0 || afterp < 0) return false;
if(beforep >= afterp) return false;
return true;
}
bool SolveSpace::GroupExists(hGroup hg) {
// A nonexistent group is not acceptable
return SK.group.FindByIdNoOops(hg) ? true : false;
}
bool SolveSpace::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;
return SK.entity.FindByIdNoOops(he) ? true : false;
}
bool SolveSpace::PruneGroups(hGroup hg) {
Group *g = SK.GetGroup(hg);
if(GroupsInOrder(g->opA, hg) &&
EntityExists(g->predef.origin) &&
EntityExists(g->predef.entityB) &&
EntityExists(g->predef.entityC))
{
return false;
}
(deleted.groups)++;
SK.group.RemoveById(g->h);
return true;
}
bool SolveSpace::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;
if(!e->h.isFromRequest()) oops();
(deleted.requests)++;
SK.request.RemoveById(e->h.request());
return true;
}
return false;
}
bool SolveSpace::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;
if(EntityExists(c->workplane) &&
EntityExists(c->ptA) &&
EntityExists(c->ptB) &&
EntityExists(c->entityA) &&
EntityExists(c->entityB) &&
EntityExists(c->entityC) &&
EntityExists(c->entityD))
{
continue;
}
(deleted.constraints)++;
if(c->type != Constraint::POINTS_COINCIDENT &&
c->type != Constraint::HORIZONTAL &&
c->type != Constraint::VERTICAL)
{
(deleted.nonTrivialConstraints)++;
}
SK.constraint.RemoveById(c->h);
return true;
}
return false;
}
void SolveSpace::GenerateAll(void) {
int i;
int firstDirty = INT_MAX, lastVisible = 0;
// Start from the first dirty group, and solve until the active group,
// since all groups after the active group are hidden.
for(i = 0; i < SK.group.n; i++) {
Group *g = &(SK.group.elem[i]);
g->order = i;
if((!g->clean) || (g->solved.how != System::SOLVED_OKAY)) {
firstDirty = min(firstDirty, i);
}
if(g->h.v == SS.GW.activeGroup.v) {
lastVisible = i;
}
}
if(firstDirty == INT_MAX || lastVisible == 0) {
// All clean; so just regenerate the entities, and don't solve anything.
GenerateAll(-1, -1);
} else {
SS.nakedEdges.Clear();
GenerateAll(firstDirty, lastVisible);
}
}
void SolveSpace::GenerateAll(int first, int last, bool andFindFree) {
int i, j;
// Remove any requests or constraints that refer to a nonexistent
// group; can check those immediately, since we know what the list
// of groups should be.
while(PruneOrphans())
;
// Don't lose our numerical guesses when we regenerate.
IdList<Param,hParam> prev;
SK.param.MoveSelfInto(&prev);
SK.entity.Clear();
SDWORD inTime = GetMilliseconds();
bool displayedStatusMessage = false;
for(i = 0; i < SK.group.n; i++) {
Group *g = &(SK.group.elem[i]);
SDWORD now = GetMilliseconds();
// Display the status message if we've taken more than 400 ms, or
// if we've taken 200 ms but we're not even halfway done, or if
// we've already started displaying the status message.
if( (now - inTime > 400) ||
((now - inTime > 200) && i < (SK.group.n / 2)) ||
displayedStatusMessage)
{
displayedStatusMessage = true;
char msg[1024];
sprintf(msg, "generating group %d/%d", i, SK.group.n);
int w, h;
GetGraphicsWindowSize(&w, &h);
glDrawBuffer(GL_FRONT);
glViewport(0, 0, w, h);
glLoadIdentity();
glTranslated(-1, 1, 0);
glScaled(2.0/w, 2.0/h, 1.0);
glDisable(GL_DEPTH_TEST);
double left = 80, top = -20, width = 240, height = 24;
glColor3d(0.9, 0.8, 0.8);
glxAxisAlignedQuad(left, left+width, top, top-height);
glLineWidth(1);
glColor3d(0.0, 0.0, 0.0);
glxAxisAlignedLineLoop(left, left+width, top, top-height);
glxCreateBitmapFont();
glColor3d(0, 0, 0);
glPushMatrix();
glTranslated(left+8, top-20, 0);
glScaled(1, -1, 1);
glxBitmapText(msg, Vector::From(0, 0, 0));
glPopMatrix();
glFlush();
glDrawBuffer(GL_BACK);
}
// The group may depend on entities or other groups, to define its
// workplane geometry or for its operands. Those must already exist
// in a previous group, so check them before generating.
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;
r->Generate(&(SK.entity), &(SK.param));
}
g->Generate(&(SK.entity), &(SK.param));
// The requests and constraints depend on stuff in this or the
// previous group, so check them after generating.
if(PruneRequests(g->h) || PruneConstraints(g->h))
goto pruned;
// 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]);
if(newp->known) continue;
Param *prevp = prev.FindByIdNoOops(newp->h);
if(prevp) newp->val = prevp->val;
}
if(g->h.v == Group::HGROUP_REFERENCES.v) {
ForceReferences();
g->solved.how = System::SOLVED_OKAY;
g->clean = true;
} else {
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.
SolveGroup(g->h, andFindFree);
g->GenerateLoops();
g->GenerateShellAndMesh();
g->clean = true;
} else {
// 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]);
Param *prevp = prev.FindByIdNoOops(newp->h);
if(prevp) newp->known = true;
}
}
}
}
// And update any reference dimensions with their new values
for(i = 0; i < SK.constraint.n; i++) {
Constraint *c = &(SK.constraint.elem[i]);
if(c->reference) {
c->ModifyToSatisfy();
}
}
// Make sure the point that we're tracing exists.
if(traced.point.v && !SK.entity.FindByIdNoOops(traced.point)) {
traced.point = Entity::NO_ENTITY;
}
// And if we're tracing a point, add its new value to the path
if(traced.point.v) {
Entity *pt = SK.GetEntity(traced.point);
traced.path.AddPoint(pt->PointGetNum());
}
prev.Clear();
InvalidateGraphics();
// Remove nonexistent selection items, for same reason we waited till
// the end to put up a dialog box.
GW.ClearNonexistentSelectionItems();
if(deleted.requests > 0 || deleted.constraints > 0 || deleted.groups > 0) {
// All sorts of interesting things could have happened; for example,
// the active group or active workplane could have been deleted. So
// clear all that out.
if(deleted.groups > 0) {
SS.TW.ClearSuper();
}
later.showTW = true;
GW.ClearSuper();
// People get annoyed if I complain whenever they delete any request,
// and I otherwise will, since those always come with pt-coincident
// constraints.
if(deleted.requests > 0 || deleted.nonTrivialConstraints > 0 ||
deleted.groups > 0)
{
// Don't display any errors until we've regenerated fully. The
// sketch is not necessarily in a consistent state until we've
// pruned any orphaned etc. objects, and the message loop for the
// messagebox could allow us to repaint and crash. But now we must
// be fine.
Message("Additional sketch elements were deleted, because they "
"depend on the element that was just deleted explicitly. "
"These include: \n"
" %d request%s\n"
" %d constraint%s\n"
" %d group%s\n\n"
"Choose Edit -> Undo to undelete all elements.",
deleted.requests, deleted.requests == 1 ? "" : "s",
deleted.constraints, deleted.constraints == 1 ? "" : "s",
deleted.groups, deleted.groups == 1 ? "" : "s");
}
memset(&deleted, 0, sizeof(deleted));
}
FreeAllTemporary();
allConsistent = true;
return;
pruned:
// Restore the numerical guesses
SK.param.Clear();
prev.MoveSelfInto(&(SK.param));
// Try again
GenerateAll(first, last);
}
void SolveSpace::ForceReferences(void) {
// Force the values of the parameters that define the three reference
// coordinate systems.
static const struct {
hRequest hr;
Quaternion q;
} Quat[] = {
{ Request::HREQUEST_REFERENCE_XY, { 1, 0, 0, 0, } },
{ Request::HREQUEST_REFERENCE_YZ, { 0.5, 0.5, 0.5, 0.5, } },
{ Request::HREQUEST_REFERENCE_ZX, { 0.5, -0.5, -0.5, -0.5, } },
};
for(int i = 0; i < 3; i++) {
hRequest hr = Quat[i].hr;
Entity *wrkpl = SK.GetEntity(hr.entity(0));
// The origin for our coordinate system, always zero
Entity *origin = SK.GetEntity(wrkpl->point[0]);
origin->PointForceTo(Vector::From(0, 0, 0));
SK.GetParam(origin->param[0])->known = true;
SK.GetParam(origin->param[1])->known = true;
SK.GetParam(origin->param[2])->known = true;
// The quaternion that defines the rotation, from the table.
Entity *normal = SK.GetEntity(wrkpl->normal);
normal->NormalForceTo(Quat[i].q);
SK.GetParam(normal->param[0])->known = true;
SK.GetParam(normal->param[1])->known = true;
SK.GetParam(normal->param[2])->known = true;
SK.GetParam(normal->param[3])->known = true;
}
}
void SolveSpace::MarkDraggedParams(void) {
sys.dragged.Clear();
for(int i = -1; i < SS.GW.pending.points.n; i++) {
hEntity hp;
if(i == -1) {
hp = SS.GW.pending.point;
} else {
hp = SS.GW.pending.points.elem[i];
}
if(!hp.v) continue;
// The pending point could be one in a group that has not yet
// been processed, in which case the lookup will fail; but
// that's not an error.
Entity *pt = SK.entity.FindByIdNoOops(hp);
if(pt) {
switch(pt->type) {
case Entity::POINT_N_TRANS:
case Entity::POINT_IN_3D:
sys.dragged.Add(&(pt->param[0]));
sys.dragged.Add(&(pt->param[1]));
sys.dragged.Add(&(pt->param[2]));
break;
case Entity::POINT_IN_2D:
sys.dragged.Add(&(pt->param[0]));
sys.dragged.Add(&(pt->param[1]));
break;
}
}
}
if(SS.GW.pending.circle.v) {
Entity *circ = SK.entity.FindByIdNoOops(SS.GW.pending.circle);
if(circ) {
Entity *dist = SK.GetEntity(circ->distance);
switch(dist->type) {
case Entity::DISTANCE:
sys.dragged.Add(&(dist->param[0]));
break;
}
}
}
if(SS.GW.pending.normal.v) {
Entity *norm = SK.entity.FindByIdNoOops(SS.GW.pending.normal);
if(norm) {
switch(norm->type) {
case Entity::NORMAL_IN_3D:
sys.dragged.Add(&(norm->param[0]));
sys.dragged.Add(&(norm->param[1]));
sys.dragged.Add(&(norm->param[2]));
sys.dragged.Add(&(norm->param[3]));
break;
// other types are locked, so not draggable
}
}
}
}
void SolveSpace::SolveGroup(hGroup hg, bool andFindFree) {
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;
r->Generate(&(sys.entity), &(sys.param));
}
// And for the group itself
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]);
p->known = false;
p->val = SK.GetParam(p->h)->val;
}
MarkDraggedParams();
g->solved.remove.Clear();
int how = sys.Solve(g, &(g->solved.dof),
&(g->solved.remove), true, andFindFree);
if((how != System::SOLVED_OKAY) ||
(how == System::SOLVED_OKAY && g->solved.how != System::SOLVED_OKAY))
{
TextWindow::ReportHowGroupSolved(g->h);
}
g->solved.how = how;
FreeAllTemporary();
}
bool SolveSpace::AllGroupsOkay(void) {
int i;
bool allOk = true;
for(i = 0; i < SK.group.n; i++) {
if(SK.group.elem[i].solved.how != System::SOLVED_OKAY) {
allOk = false;
}
}
return allOk;
}

View File

@ -1,607 +0,0 @@
//-----------------------------------------------------------------------------
// Helper functions that ultimately draw stuff with gl.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
// A public-domain Hershey vector font ("Simplex").
#include "font.table"
// A bitmap font.
#include "bitmapfont.table"
static bool ColorLocked;
static bool DepthOffsetLocked;
#define FONT_SCALE(h) ((h)/22.0)
double glxStrWidth(char *str, double h)
{
int w = 0;
for(; *str; str++) {
int c = *str;
if(c < 32 || c > 126) c = 32;
c -= 32;
w += Font[c].width;
}
return w*FONT_SCALE(h)/SS.GW.scale;
}
double glxStrHeight(double h)
{
// The characters have height ~22, as they appear in the table.
return 22.0*FONT_SCALE(h)/SS.GW.scale;
}
void glxWriteTextRefCenter(char *str, double h, Vector t, Vector u, Vector v,
glxLineFn *fn, void *fndata)
{
u = u.WithMagnitude(1);
v = v.WithMagnitude(1);
double scale = FONT_SCALE(h)/SS.GW.scale;
double fh = glxStrHeight(h);
double fw = glxStrWidth(str, h);
t = t.Plus(u.ScaledBy(-fw/2));
t = t.Plus(v.ScaledBy(-fh/2));
// Undo the (+5, +5) offset that glxWriteText applies.
t = t.Plus(u.ScaledBy(-5*scale));
t = t.Plus(v.ScaledBy(-5*scale));
glxWriteText(str, h, t, u, v, fn, fndata);
}
static void LineDrawCallback(void *fndata, Vector a, Vector b)
{
glLineWidth(1);
glBegin(GL_LINES);
glxVertex3v(a);
glxVertex3v(b);
glEnd();
}
void glxWriteText(char *str, double h, Vector t, Vector u, Vector v,
glxLineFn *fn, void *fndata)
{
if(!fn) fn = LineDrawCallback;
u = u.WithMagnitude(1);
v = v.WithMagnitude(1);
double scale = FONT_SCALE(h)/SS.GW.scale;
int xo = 5;
int yo = 5;
for(; *str; str++) {
int c = *str;
if(c < 32 || c > 126) c = 32;
c -= 32;
int j;
Vector prevp = Vector::From(VERY_POSITIVE, 0, 0);
for(j = 0; j < Font[c].points; j++) {
int x = Font[c].coord[j*2];
int y = Font[c].coord[j*2+1];
if(x == PEN_UP && y == PEN_UP) {
prevp.x = VERY_POSITIVE;
} else {
Vector p = t;
p = p.Plus(u.ScaledBy((xo + x)*scale));
p = p.Plus(v.ScaledBy((yo + y)*scale));
if(prevp.x != VERY_POSITIVE) {
fn(fndata, prevp, p);
}
prevp = p;
}
}
xo += Font[c].width;
}
}
void glxVertex3v(Vector u)
{
glVertex3f((GLfloat)u.x, (GLfloat)u.y, (GLfloat)u.z);
}
void glxAxisAlignedQuad(double l, double r, double t, double b)
{
glBegin(GL_QUADS);
glVertex2d(l, t);
glVertex2d(l, b);
glVertex2d(r, b);
glVertex2d(r, t);
glEnd();
}
void glxAxisAlignedLineLoop(double l, double r, double t, double b)
{
glBegin(GL_LINE_LOOP);
glVertex2d(l, t);
glVertex2d(l, b);
glVertex2d(r, b);
glVertex2d(r, t);
glEnd();
}
static void FatLineEndcap(Vector p, Vector u, Vector v)
{
// A table of cos and sin of (pi*i/10 + pi/2), as i goes from 0 to 10
static const double Circle[11][2] = {
{ 0.0000, 1.0000 },
{ -0.3090, 0.9511 },
{ -0.5878, 0.8090 },
{ -0.8090, 0.5878 },
{ -0.9511, 0.3090 },
{ -1.0000, 0.0000 },
{ -0.9511, -0.3090 },
{ -0.8090, -0.5878 },
{ -0.5878, -0.8090 },
{ -0.3090, -0.9511 },
{ 0.0000, -1.0000 },
};
glBegin(GL_TRIANGLE_FAN);
for(int i = 0; i <= 10; i++) {
double c = Circle[i][0], s = Circle[i][1];
glxVertex3v(p.Plus(u.ScaledBy(c)).Plus(v.ScaledBy(s)));
}
glEnd();
}
void glxFatLine(Vector a, Vector b, double width)
{
// The half-width of the line we're drawing.
double hw = width / 2;
Vector ab = b.Minus(a);
Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
Vector abn = (ab.Cross(gn)).WithMagnitude(1);
abn = abn.Minus(gn.ScaledBy(gn.Dot(abn)));
// So now abn is normal to the projection of ab into the screen, so the
// line will always have constant thickness as the view is rotated.
abn = abn.WithMagnitude(hw);
ab = gn.Cross(abn);
ab = ab. WithMagnitude(hw);
// The body of a line is a quad
glBegin(GL_QUADS);
glxVertex3v(a.Minus(abn));
glxVertex3v(b.Minus(abn));
glxVertex3v(b.Plus (abn));
glxVertex3v(a.Plus (abn));
glEnd();
// And the line has two semi-circular end caps.
FatLineEndcap(a, ab, abn);
FatLineEndcap(b, ab.ScaledBy(-1), abn);
}
void glxLockColorTo(DWORD rgb)
{
ColorLocked = false;
glColor3d(REDf(rgb), GREENf(rgb), BLUEf(rgb));
ColorLocked = true;
}
void glxUnlockColor(void)
{
ColorLocked = false;
}
void glxColorRGB(DWORD rgb)
{
// Is there a bug in some graphics drivers where this is not equivalent
// to glColor3d? There seems to be...
glxColorRGBa(rgb, 1.0);
}
void glxColorRGBa(DWORD rgb, double a)
{
if(!ColorLocked) glColor4d(REDf(rgb), GREENf(rgb), BLUEf(rgb), a);
}
static void Stipple(BOOL forSel)
{
static BOOL Init;
const int BYTES = (32*32)/8;
static GLubyte HoverMask[BYTES];
static GLubyte SelMask[BYTES];
if(!Init) {
int x, y;
for(x = 0; x < 32; x++) {
for(y = 0; y < 32; y++) {
int i = y*4 + x/8, b = x % 8;
int ym = y % 4, xm = x % 4;
for(int k = 0; k < 2; k++) {
if(xm >= 1 && xm <= 2 && ym >= 1 && ym <= 2) {
(k == 0 ? SelMask : HoverMask)[i] |= (0x80 >> b);
}
ym = (ym + 2) % 4; xm = (xm + 2) % 4;
}
}
}
Init = TRUE;
}
glEnable(GL_POLYGON_STIPPLE);
if(forSel) {
glPolygonStipple(SelMask);
} else {
glPolygonStipple(HoverMask);
}
}
static void StippleTriangle(STriangle *tr, BOOL s, DWORD rgb)
{
glEnd();
glDisable(GL_LIGHTING);
glxColorRGB(rgb);
Stipple(s);
glBegin(GL_TRIANGLES);
glxVertex3v(tr->a);
glxVertex3v(tr->b);
glxVertex3v(tr->c);
glEnd();
glEnable(GL_LIGHTING);
glDisable(GL_POLYGON_STIPPLE);
glBegin(GL_TRIANGLES);
}
void glxFillMesh(int specColor, SMesh *m, DWORD h, DWORD s1, DWORD s2)
{
DWORD rgbHovered = Style::Color(Style::HOVERED),
rgbSelected = Style::Color(Style::SELECTED);
glEnable(GL_NORMALIZE);
int prevColor = -1;
glBegin(GL_TRIANGLES);
for(int i = 0; i < m->l.n; i++) {
STriangle *tr = &(m->l.elem[i]);
int color;
if(specColor < 0) {
color = tr->meta.color;
} else {
color = specColor;
}
if(color != prevColor) {
GLfloat mpf[] = { REDf(color), GREENf(color), BLUEf(color), 1.0 };
glEnd();
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mpf);
prevColor = color;
glBegin(GL_TRIANGLES);
}
if(0 || tr->an.EqualsExactly(Vector::From(0, 0, 0))) {
// Compute the normal from the vertices
Vector n = tr->Normal();
glNormal3d(n.x, n.y, n.z);
glxVertex3v(tr->a);
glxVertex3v(tr->b);
glxVertex3v(tr->c);
} else {
// Use the exact normals that are specified
glNormal3d((tr->an).x, (tr->an).y, (tr->an).z);
glxVertex3v(tr->a);
glNormal3d((tr->bn).x, (tr->bn).y, (tr->bn).z);
glxVertex3v(tr->b);
glNormal3d((tr->cn).x, (tr->cn).y, (tr->cn).z);
glxVertex3v(tr->c);
}
if((s1 != 0 && tr->meta.face == s1) ||
(s2 != 0 && tr->meta.face == s2))
{
StippleTriangle(tr, TRUE, rgbSelected);
}
if(h != 0 && tr->meta.face == h) {
StippleTriangle(tr, FALSE, rgbHovered);
}
}
glEnd();
}
static void GLX_CALLBACK Vertex(Vector *p)
{
glxVertex3v(*p);
}
void glxFillPolygon(SPolygon *p)
{
GLUtesselator *gt = gluNewTess();
gluTessCallback(gt, GLU_TESS_BEGIN, (glxCallbackFptr *)glBegin);
gluTessCallback(gt, GLU_TESS_END, (glxCallbackFptr *)glEnd);
gluTessCallback(gt, GLU_TESS_VERTEX, (glxCallbackFptr *)Vertex);
glxTesselatePolygon(gt, p);
gluDeleteTess(gt);
}
static void GLX_CALLBACK Combine(double coords[3], void *vertexData[4],
float weight[4], void **outData)
{
Vector *n = (Vector *)AllocTemporary(sizeof(Vector));
n->x = coords[0];
n->y = coords[1];
n->z = coords[2];
*outData = n;
}
void glxTesselatePolygon(GLUtesselator *gt, SPolygon *p)
{
int i, j;
gluTessCallback(gt, GLU_TESS_COMBINE, (glxCallbackFptr *)Combine);
gluTessProperty(gt, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD);
Vector normal = p->normal;
glNormal3d(normal.x, normal.y, normal.z);
gluTessNormal(gt, normal.x, normal.y, normal.z);
gluTessBeginPolygon(gt, NULL);
for(i = 0; i < p->l.n; i++) {
SContour *sc = &(p->l.elem[i]);
gluTessBeginContour(gt);
for(j = 0; j < (sc->l.n-1); j++) {
SPoint *sp = &(sc->l.elem[j]);
double ap[3];
ap[0] = sp->p.x;
ap[1] = sp->p.y;
ap[2] = sp->p.z;
gluTessVertex(gt, ap, &(sp->p));
}
gluTessEndContour(gt);
}
gluTessEndPolygon(gt);
}
void glxDebugPolygon(SPolygon *p)
{
int i, j;
glLineWidth(2);
glPointSize(7);
glDisable(GL_DEPTH_TEST);
for(i = 0; i < p->l.n; i++) {
SContour *sc = &(p->l.elem[i]);
for(j = 0; j < (sc->l.n-1); j++) {
Vector a = (sc->l.elem[j]).p;
Vector b = (sc->l.elem[j+1]).p;
glxLockColorTo(RGB(0, 0, 255));
Vector d = (a.Minus(b)).WithMagnitude(-0);
glBegin(GL_LINES);
glxVertex3v(a.Plus(d));
glxVertex3v(b.Minus(d));
glEnd();
glxLockColorTo(RGB(255, 0, 0));
glBegin(GL_POINTS);
glxVertex3v(a.Plus(d));
glxVertex3v(b.Minus(d));
glEnd();
}
}
}
void glxDrawEdges(SEdgeList *el, bool endpointsToo)
{
SEdge *se;
glBegin(GL_LINES);
for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
glxVertex3v(se->a);
glxVertex3v(se->b);
}
glEnd();
if(endpointsToo) {
glPointSize(12);
glBegin(GL_POINTS);
for(se = el->l.First(); se; se = el->l.NextAfter(se)) {
glxVertex3v(se->a);
glxVertex3v(se->b);
}
glEnd();
}
}
void glxDebugMesh(SMesh *m)
{
int i;
glLineWidth(1);
glPointSize(7);
glxDepthRangeOffset(1);
glxUnlockColor();
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glxColorRGBa(RGB(0, 255, 0), 1.0);
glBegin(GL_TRIANGLES);
for(i = 0; i < m->l.n; i++) {
STriangle *t = &(m->l.elem[i]);
if(t->tag) continue;
glxVertex3v(t->a);
glxVertex3v(t->b);
glxVertex3v(t->c);
}
glEnd();
glxDepthRangeOffset(0);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
void glxMarkPolygonNormal(SPolygon *p)
{
Vector tail = Vector::From(0, 0, 0);
int i, j, cnt = 0;
// Choose some reasonable center point.
for(i = 0; i < p->l.n; i++) {
SContour *sc = &(p->l.elem[i]);
for(j = 0; j < (sc->l.n-1); j++) {
SPoint *sp = &(sc->l.elem[j]);
tail = tail.Plus(sp->p);
cnt++;
}
}
if(cnt == 0) return;
tail = tail.ScaledBy(1.0/cnt);
Vector gn = SS.GW.projRight.Cross(SS.GW.projUp);
Vector tip = tail.Plus((p->normal).WithMagnitude(40/SS.GW.scale));
Vector arrow = (p->normal).WithMagnitude(15/SS.GW.scale);
glColor3d(1, 1, 0);
glBegin(GL_LINES);
glxVertex3v(tail);
glxVertex3v(tip);
glxVertex3v(tip);
glxVertex3v(tip.Minus(arrow.RotatedAbout(gn, 0.6)));
glxVertex3v(tip);
glxVertex3v(tip.Minus(arrow.RotatedAbout(gn, -0.6)));
glEnd();
glEnable(GL_LIGHTING);
}
void glxDepthRangeOffset(int units)
{
if(!DepthOffsetLocked) {
// The size of this step depends on the resolution of the Z buffer; for
// a 16-bit buffer, this should be fine.
double d = units/60000.0;
glDepthRange(0.1-d, 1-d);
}
}
void glxDepthRangeLockToFront(bool yes)
{
if(yes) {
DepthOffsetLocked = true;
glDepthRange(0, 0);
} else {
DepthOffsetLocked = false;
glxDepthRangeOffset(0);
}
}
void glxCreateBitmapFont(void)
{
// Place the font in our texture in a two-dimensional grid; 1d would
// be simpler, but long skinny textures (256*16 = 4096 pixels wide)
// won't work.
static BYTE MappedTexture[4*16*64*16];
int a, i;
for(a = 0; a < 256; a++) {
int row = a / 4, col = a % 4;
for(i = 0; i < 16; i++) {
memcpy(MappedTexture + row*4*16*16 + col*16 + i*4*16,
FontTexture + a*16*16 + i*16,
16);
}
}
glBindTexture(GL_TEXTURE_2D, TEXTURE_BITMAP_FONT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA,
16*4, 64*16,
0,
GL_ALPHA, GL_UNSIGNED_BYTE,
MappedTexture);
}
void glxBitmapCharQuad(char c, double x, double y)
{
BYTE b = (BYTE)c;
int w, h;
if(b & 0x80) {
// Special character, like a checkbox or a radio button
w = h = 16;
x -= 3;
} else {
// Normal character from our font
w = SS.TW.CHAR_WIDTH,
h = SS.TW.CHAR_HEIGHT;
}
if(b != ' ' && b != 0) {
int row = b / 4, col = b % 4;
double s0 = col/4.0,
s1 = (col+1)/4.0,
t0 = row/64.0,
t1 = t0 + (w/16.0)/64;
glTexCoord2d(s1, t0);
glVertex2d(x, y);
glTexCoord2d(s1, t1);
glVertex2d(x + w, y);
glTexCoord2d(s0, t1);
glVertex2d(x + w, y - h);
glTexCoord2d(s0, t0);
glVertex2d(x, y - h);
}
}
void glxBitmapText(char *str, Vector p)
{
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
while(*str) {
glxBitmapCharQuad(*str, p.x, p.y);
str++;
p.x += SS.TW.CHAR_WIDTH;
}
glEnd();
glDisable(GL_TEXTURE_2D);
}
void glxDrawPixelsWithTexture(BYTE *data, int w, int h)
{
#define MAX_DIM 32
static BYTE Texture[MAX_DIM*MAX_DIM*3];
int i, j;
if(w > MAX_DIM || h > MAX_DIM) oops();
for(i = 0; i < w; i++) {
for(j = 0; j < h; j++) {
Texture[(j*MAX_DIM + i)*3 + 0] = data[(j*w + i)*3 + 0];
Texture[(j*MAX_DIM + i)*3 + 1] = data[(j*w + i)*3 + 1];
Texture[(j*MAX_DIM + i)*3 + 2] = data[(j*w + i)*3 + 2];
}
}
glBindTexture(GL_TEXTURE_2D, TEXTURE_DRAW_PIXELS);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, MAX_DIM, MAX_DIM, 0,
GL_RGB, GL_UNSIGNED_BYTE, Texture);
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
glTexCoord2d(0, 0);
glVertex2d(0, h);
glTexCoord2d(((double)w)/MAX_DIM, 0);
glVertex2d(w, h);
glTexCoord2d(((double)w)/MAX_DIM, ((double)h)/MAX_DIM);
glVertex2d(w, 0);
glTexCoord2d(0, ((double)h)/MAX_DIM);
glVertex2d(0, 0);
glEnd();
glDisable(GL_TEXTURE_2D);
}

View File

@ -1,935 +0,0 @@
//-----------------------------------------------------------------------------
// Top-level implementation of the program's main window, in which a graphical
// representation of the model is drawn and edited by the user.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
#define mView (&GraphicsWindow::MenuView)
#define mEdit (&GraphicsWindow::MenuEdit)
#define mClip (&GraphicsWindow::MenuClipboard)
#define mReq (&GraphicsWindow::MenuRequest)
#define mCon (&Constraint::MenuConstrain)
#define mFile (&SolveSpace::MenuFile)
#define mGrp (&Group::MenuGroup)
#define mAna (&SolveSpace::MenuAnalyze)
#define mHelp (&SolveSpace::MenuHelp)
#define S 0x100
#define C 0x200
#define F(k) (0xf0+(k))
const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = {
{ 0, "&File", 0, NULL },
{ 1, "&New\tCtrl+N", MNU_NEW, 'N'|C, mFile },
{ 1, "&Open...\tCtrl+O", MNU_OPEN, 'O'|C, mFile },
{10, "Open &Recent", MNU_OPEN_RECENT, 0, mFile },
{ 1, "&Save\tCtrl+S", MNU_SAVE, 'S'|C, mFile },
{ 1, "Save &As...", MNU_SAVE_AS, 0, mFile },
{ 1, NULL, 0, 0, NULL },
{ 1, "Export &Image...", MNU_EXPORT_PNG, 0, mFile },
{ 1, "Export 2d &View...", MNU_EXPORT_VIEW, 0, mFile },
{ 1, "Export 2d &Section...", MNU_EXPORT_SECTION, 0, mFile },
{ 1, "Export 3d &Wireframe...", MNU_EXPORT_WIREFRAME, 0, mFile },
{ 1, "Export Triangle &Mesh...", MNU_EXPORT_MESH, 0, mFile },
{ 1, "Export &Surfaces...", MNU_EXPORT_SURFACES,0, mFile },
{ 1, NULL, 0, 0, NULL },
{ 1, "E&xit", MNU_EXIT, 0, mFile },
{ 0, "&Edit", 0, NULL },
{ 1, "&Undo\tCtrl+Z", MNU_UNDO, 'Z'|C, mEdit },
{ 1, "&Redo\tCtrl+Y", MNU_REDO, 'Y'|C, mEdit },
{ 1, "Re&generate All\tSpace", MNU_REGEN_ALL, ' ', mEdit },
{ 1, NULL, 0, NULL },
{ 1, "Snap Selection to &Grid\t.", MNU_SNAP_TO_GRID, '.', mEdit },
{ 1, "Rotate Imported &90°\t9", MNU_ROTATE_90, '9', mEdit },
{ 1, NULL, 0, NULL },
{ 1, "Cu&t\tCtrl+X", MNU_CUT, 'X'|C, mClip },
{ 1, "&Copy\tCtrl+C", MNU_COPY, 'C'|C, mClip },
{ 1, "&Paste\tCtrl+V", MNU_PASTE, 'V'|C, mClip },
{ 1, "Paste &Transformed...\tCtrl+T", MNU_PASTE_TRANSFORM,'T'|C, mClip },
{ 1, "&Delete\tDel", MNU_DELETE, 127, mClip },
{ 1, NULL, 0, NULL },
{ 1, "Select &Edge Chain\tCtrl+E", MNU_SELECT_CHAIN, 'E'|C, mEdit },
{ 1, "Select &All\tCtrl+A", MNU_SELECT_ALL, 'A'|C, mEdit },
{ 1, "&Unselect All\tEsc", MNU_UNSELECT_ALL, 27, mEdit },
{ 0, "&View", 0, NULL },
{ 1, "Zoom &In\t+", MNU_ZOOM_IN, '+', mView },
{ 1, "Zoom &Out\t-", MNU_ZOOM_OUT, '-', mView },
{ 1, "Zoom To &Fit\tF", MNU_ZOOM_TO_FIT, 'F', mView },
{ 1, NULL, 0, NULL },
{ 1, "Align View to &Workplane\tW", MNU_ONTO_WORKPLANE, 'W', mView },
{ 1, "Nearest &Ortho View\tF2", MNU_NEAREST_ORTHO, F(2), mView },
{ 1, "Nearest &Isometric View\tF3", MNU_NEAREST_ISO, F(3), mView },
{ 1, "&Center View At Point\tF4", MNU_CENTER_VIEW, F(4), mView },
{ 1, NULL, 0, NULL },
{ 1, "Show Snap &Grid\t>", MNU_SHOW_GRID, '.'|S, mView },
{ 1, "Use &Perspective Projection\t`", MNU_PERSPECTIVE_PROJ,'`', mView },
{ 1, NULL, 0, NULL },
{ 1, "Show Text &Window\tTab", MNU_SHOW_TEXT_WND, '\t', mView },
{ 1, "Show &Toolbar", MNU_SHOW_TOOLBAR, 0, mView },
{ 1, NULL, 0, NULL },
{ 1, "Dimensions in &Inches", MNU_UNITS_INCHES, 0, mView },
{ 1, "Dimensions in &Millimeters", MNU_UNITS_MM, 0, mView },
{ 0, "&New Group", 0, 0, NULL },
{ 1, "Sketch In &3d\tShift+3", MNU_GROUP_3D, '3'|S, mGrp },
{ 1, "Sketch In New &Workplane\tShift+W", MNU_GROUP_WRKPL, 'W'|S, mGrp },
{ 1, NULL, 0, NULL },
{ 1, "Step &Translating\tShift+T", MNU_GROUP_TRANS, 'T'|S, mGrp },
{ 1, "Step &Rotating\tShift+R", MNU_GROUP_ROT, 'R'|S, mGrp },
{ 1, NULL, 0, 0, NULL },
{ 1, "E&xtrude\tShift+X", MNU_GROUP_EXTRUDE, 'X'|S, mGrp },
{ 1, "&Lathe\tShift+L", MNU_GROUP_LATHE, 'L'|S, mGrp },
{ 1, NULL, 0, 0, NULL },
{ 1, "Import / Assemble...\tShift+I", MNU_GROUP_IMPORT, 'I'|S, mGrp },
{11, "Import Recent", MNU_GROUP_RECENT, 0, mGrp },
{ 0, "&Sketch", 0, NULL },
{ 1, "In &Workplane\t2", MNU_SEL_WORKPLANE, '2', mReq },
{ 1, "Anywhere In &3d\t3", MNU_FREE_IN_3D, '3', mReq },
{ 1, NULL, 0, NULL },
{ 1, "Datum &Point\tP", MNU_DATUM_POINT, 'P', mReq },
{ 1, "&Workplane", MNU_WORKPLANE, 0, mReq },
{ 1, NULL, 0, NULL },
{ 1, "Line &Segment\tS", MNU_LINE_SEGMENT, 'S', mReq },
{ 1, "&Rectangle\tR", MNU_RECTANGLE, 'R', mReq },
{ 1, "&Circle\tC", MNU_CIRCLE, 'C', mReq },
{ 1, "&Arc of a Circle\tA", MNU_ARC, 'A', mReq },
{ 1, "&Bezier Cubic Spline\tB", MNU_CUBIC, 'B', mReq },
{ 1, NULL, 0, NULL },
{ 1, "&Text in TrueType Font\tT", MNU_TTF_TEXT, 'T', mReq },
{ 1, NULL, 0, NULL },
{ 1, "To&ggle Construction\tG", MNU_CONSTRUCTION, 'G', mReq },
{ 1, "Tangent &Arc at Point\tShift+A", MNU_TANGENT_ARC, 'A'|S, mReq },
{ 1, "Split Curves at &Intersection\tI", MNU_SPLIT_CURVES, 'I', mReq },
{ 0, "&Constrain", 0, NULL },
{ 1, "&Distance / Diameter\tD", MNU_DISTANCE_DIA, 'D', mCon },
{ 1, "A&ngle\tN", MNU_ANGLE, 'N', mCon },
{ 1, "Other S&upplementary Angle\tU", MNU_OTHER_ANGLE, 'U', mCon },
{ 1, "Toggle R&eference Dim\tE", MNU_REFERENCE, 'E', mCon },
{ 1, NULL, 0, NULL },
{ 1, "&Horizontal\tH", MNU_HORIZONTAL, 'H', mCon },
{ 1, "&Vertical\tV", MNU_VERTICAL, 'V', mCon },
{ 1, NULL, 0, NULL },
{ 1, "&On Point / Curve / Plane\tO", MNU_ON_ENTITY, 'O', mCon },
{ 1, "E&qual Length / Radius / Angle\tQ", MNU_EQUAL, 'Q', mCon },
{ 1, "Length Ra&tio\tZ", MNU_RATIO, 'Z', mCon },
{ 1, "At &Midpoint\tM", MNU_AT_MIDPOINT, 'M', mCon },
{ 1, "S&ymmetric\tY", MNU_SYMMETRIC, 'Y', mCon },
{ 1, "Para&llel / Tangent\tL", MNU_PARALLEL, 'L', mCon },
{ 1, "&Perpendicular\t[", MNU_PERPENDICULAR, '[', mCon },
{ 1, "Same Orient&ation\tX", MNU_ORIENTED_SAME, 'X', mCon },
{ 1, "Lock Point Where &Dragged\t]", MNU_WHERE_DRAGGED, ']', mCon },
{ 1, NULL, 0, NULL },
{ 1, "Comment\t;", MNU_COMMENT, ';', mCon },
{ 0, "&Analyze", 0, NULL },
{ 1, "Measure &Volume\tCtrl+Shift+V", MNU_VOLUME, 'V'|S|C,mAna },
{ 1, "Measure &Area\tCtrl+Shift+A", MNU_AREA, 'A'|S|C,mAna },
{ 1, "Show &Interfering Parts\tCtrl+Shift+I", MNU_INTERFERENCE, 'I'|S|C,mAna },
{ 1, "Show &Naked Edges\tCtrl+Shift+N", MNU_NAKED_EDGES, 'N'|S|C,mAna },
{ 1, NULL, 0, NULL },
{ 1, "Show Degrees of &Freedom\tCtrl+Shift+F", MNU_SHOW_DOF, 'F'|S|C,mAna },
{ 1, NULL, 0, NULL },
{ 1, "&Trace Point\tCtrl+Shift+T", MNU_TRACE_PT, 'T'|S|C,mAna },
{ 1, "&Stop Tracing...\tCtrl+Shift+S", MNU_STOP_TRACING, 'S'|S|C,mAna },
{ 1, "Step &Dimension...\tCtrl+Shift+D", MNU_STEP_DIM, 'D'|S|C,mAna },
{ 0, "&Help", 0, 0, NULL },
{ 1, "&Website / Manual", MNU_WEBSITE, 0, mHelp },
{ 1, "&About", MNU_ABOUT, 0, mHelp },
{ -1 },
};
void GraphicsWindow::Init(void) {
memset(this, 0, sizeof(*this));
scale = 5;
offset = Vector::From(0, 0, 0);
projRight = Vector::From(1, 0, 0);
projUp = Vector::From(0, 1, 0);
// Make sure those are valid; could get a mouse move without a mouse
// down if someone depresses the button, then drags into our window.
orig.projRight = projRight;
orig.projUp = projUp;
// And with the last group active
activeGroup = SK.group.elem[SK.group.n-1].h;
SK.GetGroup(activeGroup)->Activate();
showWorkplanes = false;
showNormals = true;
showPoints = true;
showConstraints = true;
showHdnLines = false;
showShaded = true;
showEdges = true;
showMesh = false;
showTextWindow = true;
ShowTextWindow(showTextWindow);
showSnapGrid = false;
context.active = false;
// Do this last, so that all the menus get updated correctly.
EnsureValidActives();
}
void GraphicsWindow::AnimateOntoWorkplane(void) {
if(!LockedInWorkplane()) return;
Entity *w = SK.GetEntity(ActiveWorkplane());
Quaternion quatf = w->Normal()->NormalGetNum();
Vector offsetf = (SK.GetEntity(w->point[0])->PointGetNum()).ScaledBy(-1);
AnimateOnto(quatf, offsetf);
}
void GraphicsWindow::AnimateOnto(Quaternion quatf, Vector offsetf) {
// Get our initial orientation and translation.
Quaternion quat0 = Quaternion::From(projRight, projUp);
Vector offset0 = offset;
// Make sure we take the shorter of the two possible paths.
double mp = (quatf.Minus(quat0)).Magnitude();
double mm = (quatf.Plus(quat0)).Magnitude();
if(mp > mm) {
quatf = quatf.ScaledBy(-1);
mp = mm;
}
double mo = (offset0.Minus(offsetf)).Magnitude()*scale;
// Animate transition, unless it's a tiny move.
SDWORD dt = (mp < 0.01 && mo < 10) ? (-20) :
(SDWORD)(100 + 1000*mp + 0.4*mo);
// Don't ever animate for longer than 2000 ms; we can get absurdly
// long translations (as measured in pixels) if the user zooms out, moves,
// and then zooms in again.
if(dt > 2000) dt = 2000;
SDWORD tn, t0 = GetMilliseconds();
double s = 0;
Quaternion dq = quatf.Times(quat0.Inverse());
do {
offset = (offset0.ScaledBy(1 - s)).Plus(offsetf.ScaledBy(s));
Quaternion quat = (dq.ToThe(s)).Times(quat0);
quat = quat.WithMagnitude(1);
projRight = quat.RotationU();
projUp = quat.RotationV();
PaintGraphics();
tn = GetMilliseconds();
s = (tn - t0)/((double)dt);
} while((tn - t0) < dt);
projRight = quatf.RotationU();
projUp = quatf.RotationV();
offset = offsetf;
InvalidateGraphics();
// If the view screen is open, then we need to refresh it.
SS.later.showTW = true;
}
void GraphicsWindow::HandlePointForZoomToFit(Vector p,
Point2d *pmax, Point2d *pmin, double *wmin, bool div)
{
double w;
Vector pp = ProjectPoint4(p, &w);
// If div is true, then we calculate a perspective projection of the point.
// If not, then we do a parallel projection regardless of the current
// scale factor.
if(div) {
pp = pp.ScaledBy(1.0/w);
}
pmax->x = max(pmax->x, pp.x);
pmax->y = max(pmax->y, pp.y);
pmin->x = min(pmin->x, pp.x);
pmin->y = min(pmin->y, pp.y);
*wmin = min(*wmin, w);
}
void GraphicsWindow::LoopOverPoints(Point2d *pmax, Point2d *pmin, double *wmin,
bool div, bool includingInvisibles)
{
HandlePointForZoomToFit(Vector::From(0, 0, 0), pmax, pmin, wmin, div);
int i, j;
for(i = 0; i < SK.entity.n; i++) {
Entity *e = &(SK.entity.elem[i]);
if(!(e->IsVisible() || includingInvisibles)) continue;
if(e->IsPoint()) {
HandlePointForZoomToFit(e->PointGetNum(), pmax, pmin, wmin, div);
} else if(e->type == Entity::CIRCLE) {
// Lots of entities can extend outside the bbox of their points,
// but circles are particularly bad. We want to get things halfway
// reasonable without the mesh, because a zoom to fit is used to
// set the zoom level to set the chord tol.
double r = e->CircleGetRadiusNum();
Vector c = SK.GetEntity(e->point[0])->PointGetNum();
Quaternion q = SK.GetEntity(e->normal)->NormalGetNum();
for(j = 0; j < 4; j++) {
Vector p = (j == 0) ? (c.Plus(q.RotationU().ScaledBy( r))) :
(j == 1) ? (c.Plus(q.RotationU().ScaledBy(-r))) :
(j == 2) ? (c.Plus(q.RotationV().ScaledBy( r))) :
(c.Plus(q.RotationV().ScaledBy(-r)));
HandlePointForZoomToFit(p, pmax, pmin, wmin, div);
}
}
}
Group *g = SK.GetGroup(activeGroup);
g->GenerateDisplayItems();
for(i = 0; i < g->displayMesh.l.n; i++) {
STriangle *tr = &(g->displayMesh.l.elem[i]);
HandlePointForZoomToFit(tr->a, pmax, pmin, wmin, div);
HandlePointForZoomToFit(tr->b, pmax, pmin, wmin, div);
HandlePointForZoomToFit(tr->c, pmax, pmin, wmin, div);
}
for(i = 0; i < g->polyLoops.l.n; i++) {
SContour *sc = &(g->polyLoops.l.elem[i]);
for(j = 0; j < sc->l.n; j++) {
HandlePointForZoomToFit(sc->l.elem[j].p, pmax, pmin, wmin, div);
}
}
}
void GraphicsWindow::ZoomToFit(bool includingInvisibles) {
// On the first run, ignore perspective.
Point2d pmax = { -1e12, -1e12 }, pmin = { 1e12, 1e12 };
double wmin = 1;
LoopOverPoints(&pmax, &pmin, &wmin, false, includingInvisibles);
double xm = (pmax.x + pmin.x)/2, ym = (pmax.y + pmin.y)/2;
double dx = pmax.x - pmin.x, dy = pmax.y - pmin.y;
offset = offset.Plus(projRight.ScaledBy(-xm)).Plus(
projUp. ScaledBy(-ym));
// And based on this, we calculate the scale and offset
if(dx == 0 && dy == 0) {
scale = 5;
} else {
double scalex = 1e12, scaley = 1e12;
if(dx != 0) scalex = 0.9*width /dx;
if(dy != 0) scaley = 0.9*height/dy;
scale = min(scalex, scaley);
scale = min(300, scale);
scale = max(0.003, scale);
}
// Then do another run, considering the perspective.
pmax.x = -1e12; pmax.y = -1e12;
pmin.x = 1e12; pmin.y = 1e12;
wmin = 1;
LoopOverPoints(&pmax, &pmin, &wmin, true, includingInvisibles);
// Adjust the scale so that no points are behind the camera
if(wmin < 0.1) {
double k = SS.CameraTangent();
// w = 1+k*scale*z
double zmin = (wmin - 1)/(k*scale);
// 0.1 = 1 + k*scale*zmin
// (0.1 - 1)/(k*zmin) = scale
scale = min(scale, (0.1 - 1)/(k*zmin));
}
}
void GraphicsWindow::MenuView(int id) {
switch(id) {
case MNU_ZOOM_IN:
SS.GW.scale *= 1.2;
SS.later.showTW = true;
break;
case MNU_ZOOM_OUT:
SS.GW.scale /= 1.2;
SS.later.showTW = true;
break;
case MNU_ZOOM_TO_FIT:
SS.GW.ZoomToFit(false);
SS.later.showTW = true;
break;
case MNU_SHOW_GRID:
SS.GW.showSnapGrid = !SS.GW.showSnapGrid;
if(SS.GW.showSnapGrid && !SS.GW.LockedInWorkplane()) {
Message("No workplane is active, so the grid will not "
"appear.");
}
SS.GW.EnsureValidActives();
InvalidateGraphics();
break;
case MNU_PERSPECTIVE_PROJ:
SS.usePerspectiveProj = !SS.usePerspectiveProj;
if(SS.cameraTangent < 1e-6) {
Error("The perspective factor is set to zero, so the view will "
"always be a parallel projection.\n\n"
"For a perspective projection, modify the perspective "
"factor in the configuration screen. A value around 0.3 "
"is typical.");
}
SS.GW.EnsureValidActives();
InvalidateGraphics();
break;
case MNU_ONTO_WORKPLANE:
if(!SS.GW.LockedInWorkplane()) {
Error("No workplane is active.");
break;
}
SS.GW.AnimateOntoWorkplane();
SS.GW.ClearSuper();
SS.later.showTW = true;
break;
case MNU_NEAREST_ORTHO:
case MNU_NEAREST_ISO: {
static const Vector ortho[3] = {
Vector::From(1, 0, 0),
Vector::From(0, 1, 0),
Vector::From(0, 0, 1)
};
double sqrt2 = sqrt(2.0), sqrt6 = sqrt(6.0);
Quaternion quat0 = Quaternion::From(SS.GW.projRight, SS.GW.projUp);
Quaternion quatf = quat0;
double dmin = 1e10;
// There are 24 possible views; 3*2*2*2
int i, j, negi, negj;
for(i = 0; i < 3; i++) {
for(j = 0; j < 3; j++) {
if(i == j) continue;
for(negi = 0; negi < 2; negi++) {
for(negj = 0; negj < 2; negj++) {
Vector ou = ortho[i], ov = ortho[j];
if(negi) ou = ou.ScaledBy(-1);
if(negj) ov = ov.ScaledBy(-1);
Vector on = ou.Cross(ov);
Vector u, v;
if(id == MNU_NEAREST_ORTHO) {
u = ou;
v = ov;
} else {
u =
ou.ScaledBy(1/sqrt2).Plus(
on.ScaledBy(-1/sqrt2));
v =
ou.ScaledBy(-1/sqrt6).Plus(
ov.ScaledBy(2/sqrt6).Plus(
on.ScaledBy(-1/sqrt6)));
}
Quaternion quatt = Quaternion::From(u, v);
double d = min(
(quatt.Minus(quat0)).Magnitude(),
(quatt.Plus(quat0)).Magnitude());
if(d < dmin) {
dmin = d;
quatf = quatt;
}
}
}
}
}
SS.GW.AnimateOnto(quatf, SS.GW.offset);
break;
}
case MNU_CENTER_VIEW:
SS.GW.GroupSelection();
if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) {
Quaternion quat0;
// Offset is the selected point, quaternion is same as before
Vector pt = SK.GetEntity(SS.GW.gs.point[0])->PointGetNum();
quat0 = Quaternion::From(SS.GW.projRight, SS.GW.projUp);
SS.GW.AnimateOnto(quat0, pt.ScaledBy(-1));
SS.GW.ClearSelection();
} else {
Error("Select a point; this point will become the center "
"of the view on screen.");
}
break;
case MNU_SHOW_TEXT_WND:
SS.GW.showTextWindow = !SS.GW.showTextWindow;
SS.GW.EnsureValidActives();
break;
case MNU_SHOW_TOOLBAR:
SS.showToolbar = !SS.showToolbar;
SS.GW.EnsureValidActives();
InvalidateGraphics();
break;
case MNU_UNITS_MM:
SS.viewUnits = SolveSpace::UNIT_MM;
SS.later.showTW = true;
SS.GW.EnsureValidActives();
break;
case MNU_UNITS_INCHES:
SS.viewUnits = SolveSpace::UNIT_INCHES;
SS.later.showTW = true;
SS.GW.EnsureValidActives();
break;
default: oops();
}
InvalidateGraphics();
}
void GraphicsWindow::EnsureValidActives(void) {
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)) {
int i;
for(i = 0; i < SK.group.n; i++) {
if(SK.group.elem[i].h.v != Group::HGROUP_REFERENCES.v) {
break;
}
}
if(i >= SK.group.n) {
// This can happen if the user deletes all the groups in the
// sketch. It's difficult to prevent that, because the last
// group might have been deleted automatically, because it failed
// a dependency. There needs to be something, so create a plane
// drawing group and activate that. They should never be able
// to delete the references, though.
activeGroup = SS.CreateDefaultDrawingGroup();
} else {
activeGroup = SK.group.elem[i].h;
}
SK.GetGroup(activeGroup)->Activate();
change = true;
}
// The active coordinate system must also exist.
if(LockedInWorkplane()) {
Entity *e = SK.entity.FindByIdNoOops(ActiveWorkplane());
if(e) {
hGroup hgw = e->group;
if(hgw.v != activeGroup.v && 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();
change = true;
}
} else {
SetWorkplaneFreeIn3d();
change = true;
}
}
// And update the checked state for various menus
bool locked = LockedInWorkplane();
CheckMenuById(MNU_FREE_IN_3D, !locked);
CheckMenuById(MNU_SEL_WORKPLANE, locked);
SS.UndoEnableMenus();
switch(SS.viewUnits) {
case SolveSpace::UNIT_MM:
case SolveSpace::UNIT_INCHES:
break;
default:
SS.viewUnits = SolveSpace::UNIT_MM;
break;
}
CheckMenuById(MNU_UNITS_MM, SS.viewUnits == SolveSpace::UNIT_MM);
CheckMenuById(MNU_UNITS_INCHES, SS.viewUnits == SolveSpace::UNIT_INCHES);
ShowTextWindow(SS.GW.showTextWindow);
CheckMenuById(MNU_SHOW_TEXT_WND, SS.GW.showTextWindow);
CheckMenuById(MNU_SHOW_TOOLBAR, SS.showToolbar);
CheckMenuById(MNU_PERSPECTIVE_PROJ, SS.usePerspectiveProj);
CheckMenuById(MNU_SHOW_GRID, SS.GW.showSnapGrid);
if(change) SS.later.showTW = true;
}
void GraphicsWindow::SetWorkplaneFreeIn3d(void) {
SK.GetGroup(activeGroup)->activeWorkplane = Entity::FREE_IN_3D;
}
hEntity GraphicsWindow::ActiveWorkplane(void) {
Group *g = SK.group.FindByIdNoOops(activeGroup);
if(g) {
return g->activeWorkplane;
} else {
return Entity::FREE_IN_3D;
}
}
bool GraphicsWindow::LockedInWorkplane(void) {
return (SS.GW.ActiveWorkplane().v != Entity::FREE_IN_3D.v);
}
void GraphicsWindow::ForceTextWindowShown(void) {
if(!showTextWindow) {
showTextWindow = true;
CheckMenuById(MNU_SHOW_TEXT_WND, true);
ShowTextWindow(TRUE);
}
}
void GraphicsWindow::DeleteTaggedRequests(void) {
// Rewrite any point-coincident constraints that were affected by this
// deletion.
Request *r;
for(r = SK.request.First(); r; r = SK.request.NextAfter(r)) {
if(!r->tag) continue;
FixConstraintsForRequestBeingDeleted(r->h);
}
// and then delete the tagged requests.
SK.request.RemoveTagged();
// An edit might be in progress for the just-deleted item. So
// now it's not.
HideGraphicsEditControl();
SS.TW.HideEditControl();
// And clear out the selection, which could contain that item.
ClearSuper();
// And regenerate to get rid of what it generates, plus anything
// that references it (since the regen code checks for that).
SS.GenerateAll(0, INT_MAX);
EnsureValidActives();
SS.later.showTW = true;
}
Vector GraphicsWindow::SnapToGrid(Vector p) {
if(!LockedInWorkplane()) return p;
EntityBase *wrkpl = SK.GetEntity(ActiveWorkplane()),
*norm = wrkpl->Normal();
Vector wo = SK.GetEntity(wrkpl->point[0])->PointGetNum(),
wu = norm->NormalU(),
wv = norm->NormalV(),
wn = norm->NormalN();
Vector pp = (p.Minus(wo)).DotInToCsys(wu, wv, wn);
pp.x = floor((pp.x / SS.gridSpacing) + 0.5)*SS.gridSpacing;
pp.y = floor((pp.y / SS.gridSpacing) + 0.5)*SS.gridSpacing;
pp.z = 0;
return pp.ScaleOutOfCsys(wu, wv, wn).Plus(wo);
}
void GraphicsWindow::MenuEdit(int id) {
switch(id) {
case MNU_UNSELECT_ALL:
SS.GW.GroupSelection();
// If there's nothing selected to de-select, and no operation
// to cancel, then perhaps they want to return to the home
// screen in the text window.
if(SS.GW.gs.n == 0 &&
SS.GW.gs.constraints == 0 &&
SS.GW.pending.operation == 0)
{
if(!(TextEditControlIsVisible() ||
GraphicsEditControlIsVisible()))
{
if(SS.TW.shown.screen == TextWindow::SCREEN_STYLE_INFO) {
SS.TW.GoToScreen(TextWindow::SCREEN_LIST_OF_STYLES);
} else {
SS.TW.ClearSuper();
}
}
}
SS.GW.ClearSuper();
SS.TW.HideEditControl();
SS.nakedEdges.Clear();
SS.justExportedInfo.draw = false;
// This clears the marks drawn to indicate which points are
// still free to drag.
Param *p;
for(p = SK.param.First(); p; p = SK.param.NextAfter(p)) {
p->free = false;
}
break;
case MNU_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->IsFace() || e->IsDistance()) continue;
if(!e->IsVisible()) continue;
SS.GW.MakeSelected(e->h);
}
InvalidateGraphics();
SS.later.showTW = true;
break;
}
case MNU_SELECT_CHAIN: {
Entity *e;
int newlySelected = 0;
bool didSomething;
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->HasEndpoints()) continue;
if(!e->IsVisible()) continue;
Vector st = e->EndpointStart(),
fi = e->EndpointFinish();
bool onChain = false, alreadySelected = false;
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) {
alreadySelected = true;
continue;
}
Entity *se = SK.GetEntity(s->entity);
if(!se->HasEndpoints()) continue;
Vector sst = se->EndpointStart(),
sfi = se->EndpointFinish();
if(sst.Equals(st) || sst.Equals(fi) ||
sfi.Equals(st) || sfi.Equals(fi))
{
onChain = true;
}
}
if(onChain && !alreadySelected) {
SS.GW.MakeSelected(e->h);
newlySelected++;
didSomething = true;
}
}
} while(didSomething);
if(newlySelected == 0) {
Error("No additional entities share endpoints with the "
"selected entities.");
}
InvalidateGraphics();
SS.later.showTW = true;
break;
}
case MNU_ROTATE_90: {
SS.GW.GroupSelection();
Entity *e = NULL;
if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) {
e = SK.GetEntity(SS.GW.gs.point[0]);
} else if(SS.GW.gs.n == 1 && SS.GW.gs.entities == 1) {
e = SK.GetEntity(SS.GW.gs.entity[0]);
}
SS.GW.ClearSelection();
hGroup hg = e ? e->group : SS.GW.activeGroup;
Group *g = SK.GetGroup(hg);
if(g->type != Group::IMPORTED) {
Error("To use this command, select a point or other "
"entity from an imported part, or make an import "
"group the active group.");
break;
}
SS.UndoRemember();
// Rotate by ninety degrees about the coordinate axis closest
// to the screen normal.
Vector norm = SS.GW.projRight.Cross(SS.GW.projUp);
norm = norm.ClosestOrtho();
norm = norm.WithMagnitude(1);
Quaternion qaa = Quaternion::From(norm, PI/2);
g->TransformImportedBy(Vector::From(0, 0, 0), qaa);
// and regenerate as necessary.
SS.MarkGroupDirty(hg);
SS.GenerateAll();
break;
}
case MNU_SNAP_TO_GRID: {
if(!SS.GW.LockedInWorkplane()) {
Error("No workplane is active. Select a workplane to define "
"the plane for the snap grid.");
break;
}
SS.GW.GroupSelection();
if(SS.GW.gs.points == 0 && SS.GW.gs.comments == 0) {
Error("Can't snap these items to grid; select points or "
"text comments. To snap a line, select its endpoints.");
break;
}
SS.UndoRemember();
List<Selection> *ls = &(SS.GW.selection);
for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {
if(s->entity.v) {
hEntity hp = s->entity;
Entity *ep = SK.GetEntity(hp);
if(!ep->IsPoint()) continue;
Vector p = ep->PointGetNum();
ep->PointForceTo(SS.GW.SnapToGrid(p));
SS.GW.pending.points.Add(&hp);
SS.MarkGroupDirty(ep->group);
} else if(s->constraint.v) {
Constraint *c = SK.GetConstraint(s->constraint);
if(c->type != Constraint::COMMENT) continue;
c->disp.offset = SS.GW.SnapToGrid(c->disp.offset);
}
}
// Regenerate, with these points marked as dragged so that they
// get placed as close as possible to our snap grid.
SS.GenerateAll();
SS.GW.ClearPending();
SS.GW.ClearSelection();
InvalidateGraphics();
break;
}
case MNU_UNDO:
SS.UndoUndo();
break;
case MNU_REDO:
SS.UndoRedo();
break;
case MNU_REGEN_ALL:
SS.ReloadAllImported();
SS.GenerateAll(0, INT_MAX);
SS.later.showTW = true;
break;
default: oops();
}
}
void GraphicsWindow::MenuRequest(int id) {
char *s;
switch(id) {
case MNU_SEL_WORKPLANE: {
SS.GW.GroupSelection();
Group *g = SK.GetGroup(SS.GW.activeGroup);
if(SS.GW.gs.n == 1 && SS.GW.gs.workplanes == 1) {
// A user-selected workplane
g->activeWorkplane = SS.GW.gs.entity[0];
} else if(g->type == Group::DRAWING_WORKPLANE) {
// The group's default workplane
g->activeWorkplane = g->h.entity(0);
Message("No workplane selected. Activating default workplane "
"for this group.");
}
if(!SS.GW.LockedInWorkplane()) {
Error("No workplane is selected, and the active group does "
"not have a default workplane. Try selecting a "
"workplane, or activating a sketch-in-new-workplane "
"group.");
break;
}
// Align the view with the selected workplane
SS.GW.AnimateOntoWorkplane();
SS.GW.ClearSuper();
SS.later.showTW = true;
break;
}
case MNU_FREE_IN_3D:
SS.GW.SetWorkplaneFreeIn3d();
SS.GW.EnsureValidActives();
SS.later.showTW = true;
InvalidateGraphics();
break;
case MNU_TANGENT_ARC:
SS.GW.GroupSelection();
if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) {
SS.GW.MakeTangentArc();
} else if(SS.GW.gs.n != 0) {
Error("Bad selection for tangent arc at point. Select a "
"single point, or select nothing to set up arc "
"parameters.");
} else {
SS.TW.GoToScreen(TextWindow::SCREEN_TANGENT_ARC);
SS.GW.ForceTextWindowShown();
SS.later.showTW = true;
}
break;
case MNU_ARC: s = "click point on arc (draws anti-clockwise)"; goto c;
case MNU_DATUM_POINT: s = "click to place datum point"; goto c;
case MNU_LINE_SEGMENT: s = "click first point of line segment"; goto c;
case MNU_CUBIC: s = "click first point of cubic segment"; goto c;
case MNU_CIRCLE: s = "click center of circle"; goto c;
case MNU_WORKPLANE: s = "click origin of workplane"; goto c;
case MNU_RECTANGLE: s = "click one corner of rectangle"; goto c;
case MNU_TTF_TEXT: s = "click top left of text"; goto c;
c:
SS.GW.pending.operation = id;
SS.GW.pending.description = s;
SS.later.showTW = true;
break;
case MNU_CONSTRUCTION: {
SS.UndoRemember();
SS.GW.GroupSelection();
if(SS.GW.gs.entities == 0) {
Error("No entities are selected. Select entities before "
"trying to toggle their construction state.");
}
int i;
for(i = 0; i < SS.GW.gs.entities; i++) {
hEntity he = SS.GW.gs.entity[i];
if(!he.isFromRequest()) continue;
Request *r = SK.GetRequest(he.request());
r->construction = !(r->construction);
SS.MarkGroupDirty(r->group);
}
SS.GW.ClearSelection();
SS.GenerateAll();
break;
}
case MNU_SPLIT_CURVES:
SS.GW.SplitLinesOrCurves();
break;
default: oops();
}
}
void GraphicsWindow::ClearSuper(void) {
HideGraphicsEditControl();
ClearPending();
ClearSelection();
hover.Clear();
EnsureValidActives();
}
void GraphicsWindow::ToggleBool(bool *v) {
*v = !*v;
// The faces are shown as special stippling on the shaded triangle mesh,
// so not meaningful to show them and hide the shaded.
if(!showShaded) showFaces = false;
// We might need to regenerate the mesh and edge list, since the edges
// wouldn't have been generated if they were previously hidden.
if(showEdges) (SK.GetGroup(activeGroup))->displayDirty = true;
SS.GenerateAll();
InvalidateGraphics();
SS.later.showTW = true;
}

760
group.cpp
View File

@ -1,760 +0,0 @@
//-----------------------------------------------------------------------------
// Implementation of the Group class, which represents a set of entities and
// constraints that are solved together, in some cases followed by another
// operation, like to extrude surfaces from the entities or to step and
// repeat them parametrically.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
const hParam Param::NO_PARAM = { 0 };
#define NO_PARAM (Param::NO_PARAM)
const hGroup Group::HGROUP_REFERENCES = { 1 };
#define gs (SS.GW.gs)
//-----------------------------------------------------------------------------
// The group structure includes pointers to other dynamically-allocated
// memory. This clears and frees them all.
//-----------------------------------------------------------------------------
void Group::Clear(void) {
polyLoops.Clear();
bezierLoops.Clear();
bezierOpens.Clear();
thisMesh.Clear();
runningMesh.Clear();
thisShell.Clear();
runningShell.Clear();
displayMesh.Clear();
displayEdges.Clear();
impMesh.Clear();
impShell.Clear();
impEntity.Clear();
// remap is the only one that doesn't get recreated when we regen
remap.Clear();
}
void Group::AddParam(IdList<Param,hParam> *param, hParam hp, double v) {
Param pa;
memset(&pa, 0, sizeof(pa));
pa.h = hp;
pa.val = v;
param->Add(&pa);
}
bool Group::IsVisible(void) {
if(!visible) return false;
if(SS.GroupsInOrder(SS.GW.activeGroup, h)) return false;
return true;
}
void Group::MenuGroup(int id) {
Group g;
ZERO(&g);
g.visible = true;
g.color = RGB(100, 100, 100);
g.scale = 1;
if(id >= RECENT_IMPORT && id < (RECENT_IMPORT + MAX_RECENT)) {
strcpy(g.impFile, RecentFile[id-RECENT_IMPORT]);
id = GraphicsWindow::MNU_GROUP_IMPORT;
}
SS.GW.GroupSelection();
switch(id) {
case GraphicsWindow::MNU_GROUP_3D:
g.type = DRAWING_3D;
g.name.strcpy("sketch-in-3d");
break;
case GraphicsWindow::MNU_GROUP_WRKPL:
g.type = DRAWING_WORKPLANE;
g.name.strcpy("sketch-in-plane");
if(gs.points == 1 && gs.n == 1) {
g.subtype = WORKPLANE_BY_POINT_ORTHO;
Vector u = SS.GW.projRight, v = SS.GW.projUp;
u = u.ClosestOrtho();
v = v.Minus(u.ScaledBy(v.Dot(u)));
v = v.ClosestOrtho();
g.predef.q = Quaternion::From(u, v);
g.predef.origin = gs.point[0];
} else if(gs.points == 1 && gs.lineSegments == 2 && gs.n == 3) {
g.subtype = WORKPLANE_BY_LINE_SEGMENTS;
g.predef.origin = gs.point[0];
g.predef.entityB = gs.entity[0];
g.predef.entityC = gs.entity[1];
Vector ut = SK.GetEntity(g.predef.entityB)->VectorGetNum();
Vector vt = SK.GetEntity(g.predef.entityC)->VectorGetNum();
ut = ut.WithMagnitude(1);
vt = vt.WithMagnitude(1);
if(fabs(SS.GW.projUp.Dot(vt)) < fabs(SS.GW.projUp.Dot(ut))) {
SWAP(Vector, ut, vt);
g.predef.swapUV = true;
}
if(SS.GW.projRight.Dot(ut) < 0) g.predef.negateU = true;
if(SS.GW.projUp. Dot(vt) < 0) g.predef.negateV = true;
} else {
Error("Bad selection for new sketch in workplane. This "
"group can be created with:\n\n"
" * a point (orthogonal to coordinate axes, "
"through the point)\n"
" * a point and two line segments (parallel to the "
"lines, through the point)\n");
return;
}
break;
case GraphicsWindow::MNU_GROUP_EXTRUDE:
if(!SS.GW.LockedInWorkplane()) {
Error("Select a workplane (Sketch -> In Workplane) before "
"extruding. The sketch will be extruded normal to the "
"workplane.");
return;
}
g.type = EXTRUDE;
g.opA = SS.GW.activeGroup;
g.predef.entityB = SS.GW.ActiveWorkplane();
g.subtype = ONE_SIDED;
g.name.strcpy("extrude");
break;
case GraphicsWindow::MNU_GROUP_LATHE:
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 lathe 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 = LATHE;
g.opA = SS.GW.activeGroup;
g.name.strcpy("lathe");
break;
case GraphicsWindow::MNU_GROUP_ROT: {
if(gs.points == 1 && gs.n == 1 && SS.GW.LockedInWorkplane()) {
g.predef.origin = gs.point[0];
Entity *w = SK.GetEntity(SS.GW.ActiveWorkplane());
g.predef.entityB = w->Normal()->h;
g.activeWorkplane = w->h;
} else if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) {
g.predef.origin = gs.point[0];
g.predef.entityB = gs.vector[0];
} else {
Error("Bad selection for new rotation. This group can "
"be created with:\n\n"
" * a point, while locked in workplane (rotate "
"in plane, about that point)\n"
" * a point and a line or a normal (rotate about "
"an axis through the point, and parallel to "
"line / normal)\n");
return;
}
g.type = ROTATE;
g.opA = SS.GW.activeGroup;
g.valA = 3;
g.subtype = ONE_SIDED;
g.name.strcpy("rotate");
break;
}
case GraphicsWindow::MNU_GROUP_TRANS:
g.type = TRANSLATE;
g.opA = SS.GW.activeGroup;
g.valA = 3;
g.subtype = ONE_SIDED;
g.predef.entityB = SS.GW.ActiveWorkplane();
g.activeWorkplane = SS.GW.ActiveWorkplane();
g.name.strcpy("translate");
break;
case GraphicsWindow::MNU_GROUP_IMPORT: {
g.type = IMPORTED;
g.opA = SS.GW.activeGroup;
if(strlen(g.impFile) == 0) {
if(!GetOpenFile(g.impFile, SLVS_EXT, SLVS_PATTERN)) return;
}
// Assign the default name of the group based on the name of
// the imported file.
char groupName[MAX_PATH];
strcpy(groupName, g.impFile);
char *dot = strrchr(groupName, '.');
if(dot) *dot = '\0';
char *s, *start = groupName;
for(s = groupName; *s; s++) {
if(*s == '/' || *s == '\\') {
start = s + 1;
} else if(isalnum(*s)) {
// do nothing, valid character
} else {
// convert invalid characters (like spaces) to dashes
*s = '-';
}
}
if(strlen(start) > 0) {
g.name.strcpy(start);
} else {
g.name.strcpy("import");
}
g.meshCombine = COMBINE_AS_ASSEMBLE;
break;
}
default: oops();
}
SS.GW.ClearSelection();
SS.UndoRemember();
SK.group.AddAndAssignId(&g);
Group *gg = SK.GetGroup(g.h);
if(gg->type == IMPORTED) {
SS.ReloadAllImported();
}
gg->clean = false;
SS.GW.activeGroup = gg->h;
SS.GenerateAll();
if(gg->type == DRAWING_WORKPLANE) {
// Can't set the active workplane for this one until after we've
// regenerated, because the workplane doesn't exist until then.
gg->activeWorkplane = gg->h.entity(0);
}
gg->Activate();
SS.GW.AnimateOntoWorkplane();
TextWindow::ScreenSelectGroup(0, gg->h.v);
SS.later.showTW = true;
}
void Group::TransformImportedBy(Vector t, Quaternion q) {
if(type != IMPORTED) oops();
hParam tx, ty, tz, qw, qx, qy, qz;
tx = h.param(0);
ty = h.param(1);
tz = h.param(2);
qw = h.param(3);
qx = h.param(4);
qy = h.param(5);
qz = h.param(6);
Quaternion qg = Quaternion::From(qw, qx, qy, qz);
qg = q.Times(qg);
Vector tg = Vector::From(tx, ty, tz);
tg = tg.Plus(t);
SK.GetParam(tx)->val = tg.x;
SK.GetParam(ty)->val = tg.y;
SK.GetParam(tz)->val = tg.z;
SK.GetParam(qw)->val = qg.w;
SK.GetParam(qx)->val = qg.vx;
SK.GetParam(qy)->val = qg.vy;
SK.GetParam(qz)->val = qg.vz;
}
char *Group::DescriptionString(void) {
static char ret[100];
if(name.str[0]) {
sprintf(ret, "g%03x-%s", h.v, name.str);
} else {
sprintf(ret, "g%03x-(unnamed)", h.v);
}
return ret;
}
void Group::Activate(void) {
if(type == EXTRUDE || type == IMPORTED) {
SS.GW.showFaces = true;
} else {
SS.GW.showFaces = false;
}
SS.MarkGroupDirty(h); // for good measure; shouldn't be needed
SS.later.generateAll = true;
SS.later.showTW = true;
}
void Group::Generate(IdList<Entity,hEntity> *entity,
IdList<Param,hParam> *param)
{
Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
Vector gp = SS.GW.projRight.Plus(SS.GW.projUp);
Vector gc = (SS.GW.offset).ScaledBy(-1);
gn = gn.WithMagnitude(200/SS.GW.scale);
gp = gp.WithMagnitude(200/SS.GW.scale);
int a, i;
switch(type) {
case DRAWING_3D:
break;
case DRAWING_WORKPLANE: {
Quaternion q;
if(subtype == WORKPLANE_BY_LINE_SEGMENTS) {
Vector u = SK.GetEntity(predef.entityB)->VectorGetNum();
Vector v = SK.GetEntity(predef.entityC)->VectorGetNum();
u = u.WithMagnitude(1);
Vector n = u.Cross(v);
v = (n.Cross(u)).WithMagnitude(1);
if(predef.swapUV) SWAP(Vector, u, v);
if(predef.negateU) u = u.ScaledBy(-1);
if(predef.negateV) v = v.ScaledBy(-1);
q = Quaternion::From(u, v);
} else if(subtype == WORKPLANE_BY_POINT_ORTHO) {
// Already given, numerically.
q = predef.q;
} else oops();
Entity normal;
memset(&normal, 0, sizeof(normal));
normal.type = Entity::NORMAL_N_COPY;
normal.numNormal = q;
normal.point[0] = h.entity(2);
normal.group = h;
normal.h = h.entity(1);
entity->Add(&normal);
Entity point;
memset(&point, 0, sizeof(point));
point.type = Entity::POINT_N_COPY;
point.numPoint = SK.GetEntity(predef.origin)->PointGetNum();
point.group = h;
point.h = h.entity(2);
entity->Add(&point);
Entity wp;
memset(&wp, 0, sizeof(wp));
wp.type = Entity::WORKPLANE;
wp.normal = normal.h;
wp.point[0] = point.h;
wp.group = h;
wp.h = h.entity(0);
entity->Add(&wp);
break;
}
case EXTRUDE: {
AddParam(param, h.param(0), gn.x);
AddParam(param, h.param(1), gn.y);
AddParam(param, h.param(2), gn.z);
int ai, af;
if(subtype == ONE_SIDED) {
ai = 0; af = 2;
} else if(subtype == TWO_SIDED) {
ai = -1; af = 1;
} else oops();
// Get some arbitrary point in the sketch, that will be used
// as a reference when defining top and bottom faces.
hEntity pt = { 0 };
for(i = 0; i < entity->n; i++) {
Entity *e = &(entity->elem[i]);
if(e->group.v != opA.v) continue;
if(e->IsPoint()) pt = e->h;
e->CalculateNumerical(false);
hEntity he = e->h; e = NULL;
// As soon as I call CopyEntity, e may become invalid! That
// 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,
true, false);
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,
true, false);
MakeExtrusionLines(entity, he);
}
// Remapped versions of that arbitrary point will be used to
// provide points on the plane faces.
MakeExtrusionTopBottomFaces(entity, pt);
break;
}
case LATHE: {
break;
}
case TRANSLATE: {
// The translation vector
AddParam(param, h.param(0), gp.x);
AddParam(param, h.param(1), gp.y);
AddParam(param, h.param(2), gp.z);
int n = (int)valA, a0 = 0;
if(subtype == ONE_SIDED && skipFirst) {
a0++; n++;
}
for(a = a0; a < n; a++) {
for(i = 0; i < entity->n; i++) {
Entity *e = &(entity->elem[i]);
if(e->group.v != opA.v) continue;
e->CalculateNumerical(false);
CopyEntity(entity, e,
a*2 - (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,
true, false);
}
}
break;
}
case ROTATE: {
// The center of rotation
AddParam(param, h.param(0), gc.x);
AddParam(param, h.param(1), gc.y);
AddParam(param, h.param(2), gc.z);
// The rotation quaternion
AddParam(param, h.param(3), 30*PI/180);
AddParam(param, h.param(4), gn.x);
AddParam(param, h.param(5), gn.y);
AddParam(param, h.param(6), gn.z);
int n = (int)valA, a0 = 0;
if(subtype == ONE_SIDED && skipFirst) {
a0++; n++;
}
for(a = a0; a < n; a++) {
for(i = 0; i < entity->n; i++) {
Entity *e = &(entity->elem[i]);
if(e->group.v != opA.v) continue;
e->CalculateNumerical(false);
CopyEntity(entity, e,
a*2 - (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),
false, true);
}
}
break;
}
case IMPORTED:
// The translation vector
AddParam(param, h.param(0), gp.x);
AddParam(param, h.param(1), gp.y);
AddParam(param, h.param(2), gp.z);
// The rotation quaternion
AddParam(param, h.param(3), 1);
AddParam(param, h.param(4), 0);
AddParam(param, h.param(5), 0);
AddParam(param, h.param(6), 0);
for(i = 0; i < impEntity.n; i++) {
Entity *ie = &(impEntity.elem[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),
false, false);
}
break;
default: oops();
}
}
void Group::AddEq(IdList<Equation,hEquation> *l, Expr *expr, int index) {
Equation eq;
eq.e = expr;
eq.h = h.equation(index);
l->Add(&eq);
}
void Group::GenerateEquations(IdList<Equation,hEquation> *l) {
Equation eq;
ZERO(&eq);
if(type == IMPORTED) {
// Normalize the quaternion
ExprQuaternion q = {
Expr::From(h.param(3)),
Expr::From(h.param(4)),
Expr::From(h.param(5)),
Expr::From(h.param(6)) };
AddEq(l, (q.Magnitude())->Minus(Expr::From(1)), 0);
} else if(type == ROTATE) {
// The axis and center of rotation are specified numerically
#define EC(x) (Expr::From(x))
#define EP(x) (Expr::From(h.param(x)))
ExprVector orig = SK.GetEntity(predef.origin)->PointGetExprs();
AddEq(l, (orig.x)->Minus(EP(0)), 0);
AddEq(l, (orig.y)->Minus(EP(1)), 1);
AddEq(l, (orig.z)->Minus(EP(2)), 2);
// param 3 is the angle, which is free
Vector axis = SK.GetEntity(predef.entityB)->VectorGetNum();
axis = axis.WithMagnitude(1);
AddEq(l, (EC(axis.x))->Minus(EP(4)), 3);
AddEq(l, (EC(axis.y))->Minus(EP(5)), 4);
AddEq(l, (EC(axis.z))->Minus(EP(6)), 5);
} else if(type == EXTRUDE) {
if(predef.entityB.v != Entity::FREE_IN_3D.v) {
// The extrusion path is locked along a line, normal to the
// specified workplane.
Entity *w = SK.GetEntity(predef.entityB);
ExprVector u = w->Normal()->NormalExprsU();
ExprVector v = w->Normal()->NormalExprsV();
ExprVector extruden = {
Expr::From(h.param(0)),
Expr::From(h.param(1)),
Expr::From(h.param(2)) };
AddEq(l, u.Dot(extruden), 0);
AddEq(l, v.Dot(extruden), 1);
}
} else if(type == TRANSLATE) {
if(predef.entityB.v != Entity::FREE_IN_3D.v) {
Entity *w = SK.GetEntity(predef.entityB);
ExprVector n = w->Normal()->NormalExprsN();
ExprVector trans;
trans = ExprVector::From(h.param(0), h.param(1), h.param(2));
// The translation vector is parallel to the workplane
AddEq(l, trans.Dot(n), 0);
}
}
}
hEntity Group::Remap(hEntity in, int copyNumber) {
// A hash table is used to accelerate the search
int hash = ((unsigned)(in.v*61 + copyNumber)) % REMAP_PRIME;
int i = remapCache[hash];
if(i >= 0 && i < remap.n) {
EntityMap *em = &(remap.elem[i]);
if(em->input.v == in.v && em->copyNumber == copyNumber) {
return h.entity(em->h.v);
}
}
// but if we don't find it in the hash table, then linear search
for(i = 0; i < remap.n; i++) {
EntityMap *em = &(remap.elem[i]);
if(em->input.v == in.v && em->copyNumber == copyNumber) {
// We already have a mapping for this entity.
remapCache[hash] = i;
return h.entity(em->h.v);
}
}
// And if we still don't find it, then create a new entry.
EntityMap em;
em.input = in;
em.copyNumber = copyNumber;
remap.AddAndAssignId(&em);
return h.entity(em.h.v);
}
void Group::MakeExtrusionLines(IdList<Entity,hEntity> *el, hEntity in) {
Entity *ep = SK.GetEntity(in);
Entity en;
ZERO(&en);
if(ep->IsPoint()) {
// A point gets extruded to form a line segment
en.point[0] = Remap(ep->h, REMAP_TOP);
en.point[1] = Remap(ep->h, REMAP_BOTTOM);
en.group = h;
en.construction = ep->construction;
en.style = ep->style;
en.h = Remap(ep->h, REMAP_PT_TO_LINE);
en.type = Entity::LINE_SEGMENT;
el->Add(&en);
} else if(ep->type == Entity::LINE_SEGMENT) {
// A line gets extruded to form a plane face; an endpoint of the
// original line is a point in the plane, and the line is in the plane.
Vector a = SK.GetEntity(ep->point[0])->PointGetNum();
Vector b = SK.GetEntity(ep->point[1])->PointGetNum();
Vector ab = b.Minus(a);
en.param[0] = h.param(0);
en.param[1] = h.param(1);
en.param[2] = h.param(2);
en.numPoint = a;
en.numNormal = Quaternion::From(0, ab.x, ab.y, ab.z);
en.group = h;
en.construction = ep->construction;
en.style = ep->style;
en.h = Remap(ep->h, REMAP_LINE_TO_FACE);
en.type = Entity::FACE_XPROD;
el->Add(&en);
}
}
void Group::MakeExtrusionTopBottomFaces(IdList<Entity,hEntity> *el, hEntity pt)
{
if(pt.v == 0) return;
Group *src = SK.GetGroup(opA);
Vector n = src->polyLoops.normal;
Entity en;
ZERO(&en);
en.type = Entity::FACE_NORMAL_PT;
en.group = h;
en.numNormal = Quaternion::From(0, n.x, n.y, n.z);
en.point[0] = Remap(pt, REMAP_TOP);
en.h = Remap(Entity::NO_ENTITY, REMAP_TOP);
el->Add(&en);
en.point[0] = Remap(pt, REMAP_BOTTOM);
en.h = Remap(Entity::NO_ENTITY, REMAP_BOTTOM);
el->Add(&en);
}
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,
bool asTrans, bool asAxisAngle)
{
Entity en;
ZERO(&en);
en.type = ep->type;
en.extraPoints = ep->extraPoints;
en.h = Remap(ep->h, remap);
en.timesApplied = timesApplied;
en.group = h;
en.construction = ep->construction;
en.style = ep->style;
en.str.strcpy(ep->str.str);
en.font.strcpy(ep->font.str);
switch(ep->type) {
case Entity::WORKPLANE:
// Don't copy these.
return;
case Entity::POINT_N_COPY:
case Entity::POINT_N_TRANS:
case Entity::POINT_N_ROT_TRANS:
case Entity::POINT_N_ROT_AA:
case Entity::POINT_IN_3D:
case Entity::POINT_IN_2D:
if(asTrans) {
en.type = Entity::POINT_N_TRANS;
en.param[0] = dx;
en.param[1] = dy;
en.param[2] = dz;
} else {
if(asAxisAngle) {
en.type = Entity::POINT_N_ROT_AA;
} else {
en.type = Entity::POINT_N_ROT_TRANS;
}
en.param[0] = dx;
en.param[1] = dy;
en.param[2] = dz;
en.param[3] = qw;
en.param[4] = qvx;
en.param[5] = qvy;
en.param[6] = qvz;
}
en.numPoint = (ep->actPoint).ScaledBy(scale);
break;
case Entity::NORMAL_N_COPY:
case Entity::NORMAL_N_ROT:
case Entity::NORMAL_N_ROT_AA:
case Entity::NORMAL_IN_3D:
case Entity::NORMAL_IN_2D:
if(asTrans) {
en.type = Entity::NORMAL_N_COPY;
} else {
if(asAxisAngle) {
en.type = Entity::NORMAL_N_ROT_AA;
} else {
en.type = Entity::NORMAL_N_ROT;
}
en.param[0] = qw;
en.param[1] = qvx;
en.param[2] = qvy;
en.param[3] = qvz;
}
en.numNormal = ep->actNormal;
if(scale < 0) en.numNormal = en.numNormal.Mirror();
en.point[0] = Remap(ep->point[0], remap);
break;
case Entity::DISTANCE_N_COPY:
case Entity::DISTANCE:
en.type = Entity::DISTANCE_N_COPY;
en.numDistance = ep->actDistance*fabs(scale);
break;
case Entity::FACE_NORMAL_PT:
case Entity::FACE_XPROD:
case Entity::FACE_N_ROT_TRANS:
case Entity::FACE_N_TRANS:
case Entity::FACE_N_ROT_AA:
if(asTrans) {
en.type = Entity::FACE_N_TRANS;
en.param[0] = dx;
en.param[1] = dy;
en.param[2] = dz;
} else {
if(asAxisAngle) {
en.type = Entity::FACE_N_ROT_AA;
} else {
en.type = Entity::FACE_N_ROT_TRANS;
}
en.param[0] = dx;
en.param[1] = dy;
en.param[2] = dz;
en.param[3] = qw;
en.param[4] = qvx;
en.param[5] = qvy;
en.param[6] = qvz;
}
en.numPoint = (ep->actPoint).ScaledBy(scale);
en.numNormal = (ep->actNormal).ScaledBy(scale);
break;
default: {
int i, points;
bool hasNormal, hasDistance;
EntReqTable::GetEntityInfo(ep->type, ep->extraPoints,
NULL, &points, &hasNormal, &hasDistance);
for(i = 0; i < points; i++) {
en.point[i] = Remap(ep->point[i], remap);
}
if(hasNormal) en.normal = Remap(ep->normal, remap);
if(hasDistance) en.distance = Remap(ep->distance, remap);
break;
}
}
// If the entity came from an imported file where it was invisible then
// ep->actiVisble will be false, and we should hide it. Or if the entity
// came from a copy (e.g. step and repeat) of a force-hidden imported
// entity, then we also want to hide it.
en.forceHidden = (!ep->actVisible) || ep->forceHidden;
el->Add(&en);
}

View File

@ -1,569 +0,0 @@
//-----------------------------------------------------------------------------
// Routines to generate our watertight brep shells from the operations
// and entities specified by the user in each group; templated to work either
// on an SShell of ratpoly surfaces or on an SMesh of triangles.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
#define gs (SS.GW.gs)
void Group::AssembleLoops(bool *allClosed,
bool *allCoplanar,
bool *allNonZeroLen)
{
SBezierList sbl;
ZERO(&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;
e->GenerateBezierCurves(&sbl);
}
SBezier *sb;
*allNonZeroLen = true;
for(sb = sbl.l.First(); sb; sb = sbl.l.NextAfter(sb)) {
for(i = 1; i <= sb->deg; i++) {
if(!(sb->ctrl[i]).Equals(sb->ctrl[0])) {
break;
}
}
if(i > sb->deg) {
// This is a zero-length edge.
*allNonZeroLen = false;
polyError.errorPointAt = sb->ctrl[0];
return;
}
}
// Try to assemble all these Beziers into loops. The closed loops go into
// bezierLoops, with the outer loops grouped with their holes. The
// leftovers, if any, go in bezierOpens.
bezierLoops.FindOuterFacesFrom(&sbl, &polyLoops, NULL,
SS.ChordTolMm(),
allClosed, &(polyError.notClosedAt),
allCoplanar, &(polyError.errorPointAt),
&bezierOpens);
sbl.Clear();
}
void Group::GenerateLoops(void) {
polyLoops.Clear();
bezierLoops.Clear();
bezierOpens.Clear();
if(type == DRAWING_3D || type == DRAWING_WORKPLANE ||
type == ROTATE || type == TRANSLATE || type == IMPORTED)
{
bool allClosed, allCoplanar, allNonZeroLen;
AssembleLoops(&allClosed, &allCoplanar, &allNonZeroLen);
if(!allCoplanar) {
polyError.how = POLY_NOT_COPLANAR;
} else if(!allClosed) {
polyError.how = POLY_NOT_CLOSED;
} else if(!allNonZeroLen) {
polyError.how = POLY_ZERO_LEN_EDGE;
} else {
polyError.how = POLY_GOOD;
// The self-intersecting check is kind of slow, so don't run it
// unless requested.
if(SS.checkClosedContour) {
if(polyLoops.SelfIntersecting(&(polyError.errorPointAt))) {
polyError.how = POLY_SELF_INTERSECTING;
}
}
}
}
}
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;
face = g->Remap(face, remap);
ss->face = face.v;
}
}
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;
face = g->Remap(face, remap);
tr->meta.face = face.v;
}
}
template<class T>
void Group::GenerateForStepAndRepeat(T *steps, T *outs) {
T workA, workB;
ZERO(&workA);
ZERO(&workB);
T *soFar = &workA, *scratch = &workB;
int n = (int)valA, a0 = 0;
if(subtype == ONE_SIDED && skipFirst) {
a0++; n++;
}
int a;
for(a = a0; a < n; a++) {
int ap = a*2 - (subtype == ONE_SIDED ? 0 : (n-1));
int remap = (a == (n - 1)) ? REMAP_LAST : a;
T transd;
ZERO(&transd);
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, 1.0);
} else {
Vector trans = Vector::From(h.param(0), h.param(1), h.param(2));
double theta = ap * SK.GetParam(h.param(3))->val;
double c = cos(theta), s = sin(theta);
Vector axis = Vector::From(h.param(4), h.param(5), h.param(6));
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, 1.0);
}
// We need to rewrite any plane face entities to the transformed ones.
transd.RemapFaces(this, remap);
// And tack this transformed copy on to the return.
if(soFar->IsEmpty()) {
scratch->MakeFromCopyOf(&transd);
} else {
scratch->MakeFromUnionOf(soFar, &transd);
}
SWAP(T *, scratch, soFar);
scratch->Clear();
transd.Clear();
}
outs->Clear();
*outs = *soFar;
}
template<class T>
void Group::GenerateForBoolean(T *prevs, T *thiss, T *outs, int how) {
// If this group contributes no new mesh, then our running mesh is the
// same as last time, no combining required. Likewise if we have a mesh
// but it's suppressed.
if(thiss->IsEmpty() || suppress) {
outs->MakeFromCopyOf(prevs);
return;
}
// So our group's shell appears in thisShell. Combine this with the
// previous group's shell, using the requested operation.
if(how == COMBINE_AS_UNION) {
outs->MakeFromUnionOf(prevs, thiss);
} else if(how == COMBINE_AS_DIFFERENCE) {
outs->MakeFromDifferenceOf(prevs, thiss);
} else {
outs->MakeFromAssemblyOf(prevs, thiss);
}
}
void Group::GenerateShellAndMesh(void) {
bool prevBooleanFailed = booleanFailed;
booleanFailed = false;
Group *srcg = this;
thisShell.Clear();
thisMesh.Clear();
runningShell.Clear();
runningMesh.Clear();
// Don't attempt a lathe or extrusion unless the source section is good:
// planar and not self-intersecting.
bool haveSrc = true;
if(type == EXTRUDE || type == LATHE) {
Group *src = SK.GetGroup(opA);
if(src->polyError.how != POLY_GOOD) {
haveSrc = false;
}
}
if(type == TRANSLATE || type == ROTATE) {
// A step and repeat gets merged against the group's prevous group,
// not our own previous group.
srcg = SK.GetGroup(opA);
GenerateForStepAndRepeat<SShell>(&(srcg->thisShell), &thisShell);
GenerateForStepAndRepeat<SMesh> (&(srcg->thisMesh), &thisMesh);
} else if(type == EXTRUDE && haveSrc) {
Group *src = SK.GetGroup(opA);
Vector translate = Vector::From(h.param(0), h.param(1), h.param(2));
Vector tbot, ttop;
if(subtype == ONE_SIDED) {
tbot = Vector::From(0, 0, 0); ttop = translate.ScaledBy(2);
} else {
tbot = translate.ScaledBy(-1); ttop = translate.ScaledBy(1);
}
SBezierLoopSetSet *sblss = &(src->bezierLoops);
SBezierLoopSet *sbls;
for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
int is = thisShell.surface.n;
// Extrude this outer contour (plus its inner contours, if present)
thisShell.MakeFromExtrusionOf(sbls, tbot, ttop, color);
// And for any plane faces, annotate the model with the entity for
// that face, so that the user can select them with the mouse.
Vector onOrig = sbls->point;
int i;
for(i = is; i < thisShell.surface.n; i++) {
SSurface *ss = &(thisShell.surface.elem[i]);
hEntity face = Entity::NO_ENTITY;
Vector p = ss->PointAt(0, 0),
n = ss->NormalAt(0, 0).WithMagnitude(1);
double d = n.Dot(p);
if(i == is || i == (is + 1)) {
// These are the top and bottom of the shell.
if(fabs((onOrig.Plus(ttop)).Dot(n) - d) < LENGTH_EPS) {
face = Remap(Entity::NO_ENTITY, REMAP_TOP);
ss->face = face.v;
}
if(fabs((onOrig.Plus(tbot)).Dot(n) - d) < LENGTH_EPS) {
face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM);
ss->face = face.v;
}
continue;
}
// So these are the sides
if(ss->degm != 1 || ss->degn != 1) continue;
Entity *e;
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
if(e->group.v != opA.v) continue;
if(e->type != Entity::LINE_SEGMENT) continue;
Vector a = SK.GetEntity(e->point[0])->PointGetNum(),
b = SK.GetEntity(e->point[1])->PointGetNum();
a = a.Plus(ttop);
b = b.Plus(ttop);
// Could get taken backwards, so check all cases.
if((a.Equals(ss->ctrl[0][0]) && b.Equals(ss->ctrl[1][0])) ||
(b.Equals(ss->ctrl[0][0]) && a.Equals(ss->ctrl[1][0])) ||
(a.Equals(ss->ctrl[0][1]) && b.Equals(ss->ctrl[1][1])) ||
(b.Equals(ss->ctrl[0][1]) && a.Equals(ss->ctrl[1][1])))
{
face = Remap(e->h, REMAP_LINE_TO_FACE);
ss->face = face.v;
break;
}
}
}
}
} else if(type == LATHE && haveSrc) {
Group *src = SK.GetGroup(opA);
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.MakeFromRevolutionOf(sbls, pt, axis, color);
}
} else if(type == IMPORTED) {
// The imported shell or mesh are copied over, with the appropriate
// transformation applied. We also must remap the face entities.
Vector offset = {
SK.GetParam(h.param(0))->val,
SK.GetParam(h.param(1))->val,
SK.GetParam(h.param(2))->val };
Quaternion q = {
SK.GetParam(h.param(3))->val,
SK.GetParam(h.param(4))->val,
SK.GetParam(h.param(5))->val,
SK.GetParam(h.param(6))->val };
thisMesh.MakeFromTransformationOf(&impMesh, offset, q, scale);
thisMesh.RemapFaces(this, 0);
thisShell.MakeFromTransformationOf(&impShell, offset, q, scale);
thisShell.RemapFaces(this, 0);
}
if(srcg->meshCombine != COMBINE_AS_ASSEMBLE) {
thisShell.MergeCoincidentSurfaces();
}
// So now we've got the mesh or shell for this group. Combine it with
// the previous group's mesh or shell with the requested Boolean, and
// we're done.
Group *prevg = srcg->RunningMeshGroup();
if(prevg->runningMesh.IsEmpty() && thisMesh.IsEmpty() && !forceToMesh) {
SShell *prevs = &(prevg->runningShell);
GenerateForBoolean<SShell>(prevs, &thisShell, &runningShell,
srcg->meshCombine);
if(srcg->meshCombine != COMBINE_AS_ASSEMBLE) {
runningShell.MergeCoincidentSurfaces();
}
// If the Boolean failed, then we should note that in the text screen
// for this group.
booleanFailed = runningShell.booleanFailed;
if(booleanFailed != prevBooleanFailed) {
SS.later.showTW = true;
}
} else {
SMesh prevm, thism;
ZERO(&prevm);
ZERO(&thism);
prevm.MakeFromCopyOf(&(prevg->runningMesh));
prevg->runningShell.TriangulateInto(&prevm);
thism.MakeFromCopyOf(&thisMesh);
thisShell.TriangulateInto(&thism);
SMesh outm;
ZERO(&outm);
GenerateForBoolean<SMesh>(&prevm, &thism, &outm, srcg->meshCombine);
// And make sure that the output mesh is vertex-to-vertex.
SKdNode *root = SKdNode::From(&outm);
root->SnapToMesh(&outm);
root->MakeMeshInto(&runningMesh);
outm.Clear();
thism.Clear();
prevm.Clear();
}
displayDirty = true;
}
void Group::GenerateDisplayItems(void) {
// This is potentially slow (since we've got to triangulate a shell, or
// to find the emphasized edges for a mesh), so we will run it only
// if its inputs have changed.
if(displayDirty) {
Group *pg = RunningMeshGroup();
if(pg && thisMesh.IsEmpty() && thisShell.IsEmpty()) {
// We don't contribute any new solid model in this group, so our
// display items are identical to the previous group's; which means
// that we can just display those, and stop ourselves from
// recalculating for those every time we get a change in this group.
//
// Note that this can end up recursing multiple times (if multiple
// groups that contribute no solid model exist in sequence), but
// that's okay.
pg->GenerateDisplayItems();
displayMesh.Clear();
displayMesh.MakeFromCopyOf(&(pg->displayMesh));
displayEdges.Clear();
if(SS.GW.showEdges) {
SEdge *se;
SEdgeList *src = &(pg->displayEdges);
for(se = src->l.First(); se; se = src->l.NextAfter(se)) {
displayEdges.l.Add(se);
}
}
} else {
// We do contribute new solid model, so we have to triangulate the
// shell, and edge-find the mesh.
displayMesh.Clear();
runningShell.TriangulateInto(&displayMesh);
STriangle *t;
for(t = runningMesh.l.First(); t; t = runningMesh.l.NextAfter(t)) {
STriangle trn = *t;
Vector n = trn.Normal();
trn.an = n;
trn.bn = n;
trn.cn = n;
displayMesh.AddTriangle(&trn);
}
displayEdges.Clear();
if(SS.GW.showEdges) {
runningShell.MakeEdgesInto(&displayEdges);
runningMesh.MakeEmphasizedEdgesInto(&displayEdges);
}
}
displayDirty = false;
}
}
Group *Group::PreviousGroup(void) {
int i;
for(i = 0; i < SK.group.n; i++) {
Group *g = &(SK.group.elem[i]);
if(g->h.v == h.v) break;
}
if(i == 0 || i >= SK.group.n) return NULL;
return &(SK.group.elem[i-1]);
}
Group *Group::RunningMeshGroup(void) {
if(type == TRANSLATE || type == ROTATE) {
return SK.GetGroup(opA)->RunningMeshGroup();
} else {
return PreviousGroup();
}
}
void Group::DrawDisplayItems(int t) {
int specColor;
if(t == DRAWING_3D || t == DRAWING_WORKPLANE) {
// force the color to something dim
specColor = Style::Color(Style::DIM_SOLID);
} else {
specColor = -1; // use the model color
}
// The back faces are drawn in red; should never seem them, since we
// draw closed shells, so that's a debugging aid.
GLfloat mpb[] = { 1.0f, 0.1f, 0.1f, 1.0 };
glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, mpb);
// When we fill the mesh, we need to know which triangles are selected
// or hovered, in order to draw them differently.
DWORD mh = 0, ms1 = 0, ms2 = 0;
hEntity he = SS.GW.hover.entity;
if(he.v != 0 && SK.GetEntity(he)->IsFace()) {
mh = he.v;
}
SS.GW.GroupSelection();
if(gs.faces > 0) ms1 = gs.face[0].v;
if(gs.faces > 1) ms2 = gs.face[1].v;
if(SS.GW.showShaded) {
glEnable(GL_LIGHTING);
glxFillMesh(specColor, &displayMesh, mh, ms1, ms2);
glDisable(GL_LIGHTING);
}
if(SS.GW.showEdges) {
glxDepthRangeOffset(2);
glxColorRGB(Style::Color(Style::SOLID_EDGE));
glLineWidth(Style::Width(Style::SOLID_EDGE));
glxDrawEdges(&displayEdges, false);
}
if(SS.GW.showMesh) glxDebugMesh(&displayMesh);
}
void Group::Draw(void) {
// Everything here gets drawn whether or not the group is hidden; we
// can control this stuff independently, with show/hide solids, edges,
// mesh, etc.
GenerateDisplayItems();
DrawDisplayItems(type);
if(!SS.checkClosedContour) return;
// And finally show the polygons too, and any errors if it's not possible
// to assemble the lines into closed polygons.
if(polyError.how == POLY_NOT_CLOSED) {
// Report this error only in sketch-in-workplane groups; otherwise
// it's just a nuisance.
if(type == DRAWING_WORKPLANE) {
glDisable(GL_DEPTH_TEST);
glxColorRGBa(Style::Color(Style::DRAW_ERROR), 0.2);
glLineWidth (Style::Width(Style::DRAW_ERROR));
glBegin(GL_LINES);
glxVertex3v(polyError.notClosedAt.a);
glxVertex3v(polyError.notClosedAt.b);
glEnd();
glxColorRGB(Style::Color(Style::DRAW_ERROR));
glxWriteText("not closed contour, or not all same style!",
DEFAULT_TEXT_HEIGHT,
polyError.notClosedAt.b, SS.GW.projRight, SS.GW.projUp,
NULL, NULL);
glEnable(GL_DEPTH_TEST);
}
} else if(polyError.how == POLY_NOT_COPLANAR ||
polyError.how == POLY_SELF_INTERSECTING ||
polyError.how == POLY_ZERO_LEN_EDGE)
{
// These errors occur at points, not lines
if(type == DRAWING_WORKPLANE) {
glDisable(GL_DEPTH_TEST);
glxColorRGB(Style::Color(Style::DRAW_ERROR));
char *msg;
if(polyError.how == POLY_NOT_COPLANAR) {
msg = "points not all coplanar!";
} else if(polyError.how == POLY_SELF_INTERSECTING) {
msg = "contour is self-intersecting!";
} else {
msg = "zero-length edge!";
}
glxWriteText(msg, DEFAULT_TEXT_HEIGHT,
polyError.errorPointAt, SS.GW.projRight, SS.GW.projUp,
NULL, NULL);
glEnable(GL_DEPTH_TEST);
}
} else {
// The contours will get filled in DrawFilledPaths.
}
}
void Group::FillLoopSetAsPolygon(SBezierLoopSet *sbls) {
SPolygon sp;
ZERO(&sp);
sbls->MakePwlInto(&sp);
glxDepthRangeOffset(1);
glxFillPolygon(&sp);
glxDepthRangeOffset(0);
sp.Clear();
}
void Group::DrawFilledPaths(void) {
SBezierLoopSet *sbls;
SBezierLoopSetSet *sblss = &bezierLoops;
for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) {
if(sbls->l.n == 0 || sbls->l.elem[0].l.n == 0) 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]);
hStyle hs = { sb->auxA };
Style *s = Style::Get(hs);
if(s->filled) {
// This is a filled loop, where the user specified a fill color.
glxColorRGBa(s->fillColor, 1);
FillLoopSetAsPolygon(sbls);
} else {
if(h.v == SS.GW.activeGroup.v && SS.checkClosedContour &&
polyError.how == POLY_GOOD)
{
// If this is the active group, and we are supposed to check
// for closed contours, and we do indeed have a closed and
// non-intersecting contour, then fill it dimly.
glxColorRGBa(Style::Color(Style::CONTOUR_FILL), 0.5);
glxDepthRangeOffset(1);
FillLoopSetAsPolygon(sbls);
glxDepthRangeOffset(0);
}
}
}
}

BIN
icon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 395 B

974
mesh.cpp
View File

@ -1,974 +0,0 @@
//-----------------------------------------------------------------------------
// Operations on triangle meshes, like our mesh Booleans using the BSP, and
// the stuff to check for watertightness.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
void SMesh::Clear(void) {
l.Clear();
}
void SMesh::AddTriangle(STriMeta meta, Vector n, Vector a, Vector b, Vector c) {
Vector ab = b.Minus(a), bc = c.Minus(b);
Vector np = ab.Cross(bc);
if(np.Magnitude() < 1e-10) {
// ugh; gl sometimes tesselates to collinear triangles
return;
}
if(np.Dot(n) > 0) {
AddTriangle(meta, a, b, c);
} else {
AddTriangle(meta, c, b, a);
}
}
void SMesh::AddTriangle(STriMeta meta, Vector a, Vector b, Vector c) {
STriangle t; ZERO(&t);
t.meta = meta;
t.a = a;
t.b = b;
t.c = c;
AddTriangle(&t);
}
void SMesh::AddTriangle(STriangle *st) {
l.Add(st);
}
void SMesh::DoBounding(Vector v, Vector *vmax, Vector *vmin) {
vmax->x = max(vmax->x, v.x);
vmax->y = max(vmax->y, v.y);
vmax->z = max(vmax->z, v.z);
vmin->x = min(vmin->x, v.x);
vmin->y = min(vmin->y, v.y);
vmin->z = min(vmin->z, v.z);
}
void SMesh::GetBounding(Vector *vmax, Vector *vmin) {
int i;
*vmin = Vector::From( 1e12, 1e12, 1e12);
*vmax = Vector::From(-1e12, -1e12, -1e12);
for(i = 0; i < l.n; i++) {
STriangle *st = &(l.elem[i]);
DoBounding(st->a, vmax, vmin);
DoBounding(st->b, vmax, vmin);
DoBounding(st->c, vmax, vmin);
}
}
//----------------------------------------------------------------------------
// Report the edges of the boundary of the region(s) of our mesh that lie
// within the plane n dot p = d.
//----------------------------------------------------------------------------
void SMesh::MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d) {
SMesh m;
ZERO(&m);
m.MakeFromCopyOf(this);
// Delete all triangles in the mesh that do not lie in our export plane.
m.l.ClearTags();
int i;
for(i = 0; i < m.l.n; i++) {
STriangle *tr = &(m.l.elem[i]);
if((fabs(n.Dot(tr->a) - d) >= LENGTH_EPS) ||
(fabs(n.Dot(tr->b) - d) >= LENGTH_EPS) ||
(fabs(n.Dot(tr->c) - d) >= LENGTH_EPS))
{
tr->tag = 1;
}
}
m.l.RemoveTagged();
// Select the naked edges in our resulting open mesh.
SKdNode *root = SKdNode::From(&m);
root->SnapToMesh(&m);
root->MakeCertainEdgesInto(sel, SKdNode::NAKED_OR_SELF_INTER_EDGES,
false, NULL, NULL);
m.Clear();
}
void SMesh::MakeEmphasizedEdgesInto(SEdgeList *sel) {
SKdNode *root = SKdNode::From(this);
root->MakeCertainEdgesInto(sel, SKdNode::EMPHASIZED_EDGES,
false, NULL, NULL);
}
//-----------------------------------------------------------------------------
// When we are called, all of the triangles from l.elem[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.
//
// This is really ugly code; basically it just pastes things together to
// form convex polygons, merging collinear edges when possible, then
// triangulates the convex poly.
//-----------------------------------------------------------------------------
void SMesh::Simplify(int start) {
int maxTriangles = (l.n - start) + 10;
STriMeta meta = l.elem[start].meta;
STriangle *tout = (STriangle *)AllocTemporary(maxTriangles*sizeof(*tout));
int toutc = 0;
Vector n, *conv = (Vector *)AllocTemporary(maxTriangles*3*sizeof(*conv));
int convc = 0;
int start0 = start;
int i, j;
for(i = start; i < l.n; i++) {
STriangle *tr = &(l.elem[i]);
if(tr->MinAltitude() < LENGTH_EPS) {
tr->tag = 1;
} else {
tr->tag = 0;
}
}
for(;;) {
bool didAdd;
convc = 0;
for(i = start; i < l.n; i++) {
STriangle *tr = &(l.elem[i]);
if(tr->tag) continue;
tr->tag = 1;
n = (tr->Normal()).WithMagnitude(1);
conv[convc++] = tr->a;
conv[convc++] = tr->b;
conv[convc++] = tr->c;
start = i+1;
break;
}
if(i >= l.n) break;
do {
didAdd = false;
for(j = 0; j < convc; j++) {
Vector a = conv[WRAP((j-1), convc)],
b = conv[j],
d = conv[WRAP((j+1), convc)],
e = conv[WRAP((j+2), convc)];
Vector c;
for(i = start; i < l.n; i++) {
STriangle *tr = &(l.elem[i]);
if(tr->tag) continue;
if((tr->a).Equals(d) && (tr->b).Equals(b)) {
c = tr->c;
} else if((tr->b).Equals(d) && (tr->c).Equals(b)) {
c = tr->a;
} else if((tr->c).Equals(d) && (tr->a).Equals(b)) {
c = tr->b;
} else {
continue;
}
// The vertex at C must be convex; but the others must
// be tested
Vector ab = b.Minus(a);
Vector bc = c.Minus(b);
Vector cd = d.Minus(c);
Vector de = e.Minus(d);
double bDot = (ab.Cross(bc)).Dot(n);
double dDot = (cd.Cross(de)).Dot(n);
bDot /= min(ab.Magnitude(), bc.Magnitude());
dDot /= min(cd.Magnitude(), de.Magnitude());
if(fabs(bDot) < LENGTH_EPS && fabs(dDot) < LENGTH_EPS) {
conv[WRAP((j+1), convc)] = c;
// and remove the vertex at j, which is a dup
memmove(conv+j, conv+j+1,
(convc - j - 1)*sizeof(conv[0]));
convc--;
} else if(fabs(bDot) < LENGTH_EPS && dDot > 0) {
conv[j] = c;
} else if(fabs(dDot) < LENGTH_EPS && bDot > 0) {
conv[WRAP((j+1), convc)] = c;
} else if(bDot > 0 && dDot > 0) {
// conv[j] is unchanged, conv[j+1] goes to [j+2]
memmove(conv+j+2, conv+j+1,
(convc - j - 1)*sizeof(conv[0]));
conv[j+1] = c;
convc++;
} else {
continue;
}
didAdd = true;
tr->tag = 1;
break;
}
}
} while(didAdd);
// I need to debug why this is required; sometimes the above code
// still generates a convex polygon
for(i = 0; i < convc; i++) {
Vector a = conv[WRAP((i-1), convc)],
b = conv[i],
c = conv[WRAP((i+1), convc)];
Vector ab = b.Minus(a);
Vector bc = c.Minus(b);
double bDot = (ab.Cross(bc)).Dot(n);
bDot /= min(ab.Magnitude(), bc.Magnitude());
if(bDot < 0) return; // XXX, shouldn't happen
}
for(i = 0; i < convc - 2; i++) {
STriangle tr = STriangle::From(meta, conv[0], conv[i+1], conv[i+2]);
if(tr.MinAltitude() > LENGTH_EPS) {
tout[toutc++] = tr;
}
}
}
l.n = start0;
for(i = 0; i < toutc; i++) {
AddTriangle(&(tout[i]));
}
FreeTemporary(tout);
FreeTemporary(conv);
}
void SMesh::AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3) {
int i;
for(i = 0; i < srcm->l.n; i++) {
STriangle *st = &(srcm->l.elem[i]);
int pn = l.n;
atLeastOneDiscarded = false;
bsp3->Insert(st, this);
if(!atLeastOneDiscarded && (l.n != (pn+1))) {
l.n = pn;
if(flipNormal) {
AddTriangle(st->meta, st->c, st->b, st->a);
} else {
AddTriangle(st->meta, st->a, st->b, st->c);
}
}
if(l.n - pn > 1) {
Simplify(pn);
}
}
}
void SMesh::MakeFromUnionOf(SMesh *a, SMesh *b) {
SBsp3 *bspa = SBsp3::FromMesh(a);
SBsp3 *bspb = SBsp3::FromMesh(b);
flipNormal = false;
keepCoplanar = false;
AddAgainstBsp(b, bspa);
flipNormal = false;
keepCoplanar = true;
AddAgainstBsp(a, bspb);
}
void SMesh::MakeFromDifferenceOf(SMesh *a, SMesh *b) {
SBsp3 *bspa = SBsp3::FromMesh(a);
SBsp3 *bspb = SBsp3::FromMesh(b);
flipNormal = true;
keepCoplanar = true;
AddAgainstBsp(b, bspa);
flipNormal = false;
keepCoplanar = false;
AddAgainstBsp(a, bspb);
}
void SMesh::MakeFromCopyOf(SMesh *a) {
int i;
for(i = 0; i < a->l.n; i++) {
AddTriangle(&(a->l.elem[i]));
}
}
void SMesh::MakeFromAssemblyOf(SMesh *a, SMesh *b) {
MakeFromCopyOf(a);
MakeFromCopyOf(b);
}
void SMesh::MakeFromTransformationOf(SMesh *a,
Vector trans, Quaternion q, double scale)
{
STriangle *tr;
for(tr = a->l.First(); tr; tr = a->l.NextAfter(tr)) {
STriangle tt = *tr;
tt.a = (tt.a).ScaledBy(scale);
tt.b = (tt.b).ScaledBy(scale);
tt.c = (tt.c).ScaledBy(scale);
if(scale < 0) {
// 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);
AddTriangle(&tt);
}
}
bool SMesh::IsEmpty(void) {
return (l.n == 0);
}
DWORD SMesh::FirstIntersectionWith(Point2d mp) {
Vector p0 = Vector::From(mp.x, mp.y, 0);
Vector gn = Vector::From(0, 0, 1);
double maxT = -1e12;
DWORD face = 0;
int i;
for(i = 0; i < l.n; i++) {
STriangle tr = l.elem[i];
tr.a = SS.GW.ProjectPoint3(tr.a);
tr.b = SS.GW.ProjectPoint3(tr.b);
tr.c = SS.GW.ProjectPoint3(tr.c);
Vector n = tr.Normal();
if(n.Dot(gn) < LENGTH_EPS) continue; // back-facing or on edge
if(tr.ContainsPointProjd(gn, p0)) {
// Let our line have the form r(t) = p0 + gn*t
double t = -(n.Dot((tr.a).Minus(p0)))/(n.Dot(gn));
if(t > maxT) {
maxT = t;
face = tr.meta.face;
}
}
}
return face;
}
STriangleLl *STriangleLl::Alloc(void)
{ return (STriangleLl *)AllocTemporary(sizeof(STriangleLl)); }
SKdNode *SKdNode::Alloc(void)
{ return (SKdNode *)AllocTemporary(sizeof(SKdNode)); }
SKdNode *SKdNode::From(SMesh *m) {
int i;
STriangle *tra = (STriangle *)AllocTemporary((m->l.n) * sizeof(*tra));
for(i = 0; i < m->l.n; i++) {
tra[i] = m->l.elem[i];
}
srand(0);
int n = m->l.n;
while(n > 1) {
int k = rand() % n;
n--;
SWAP(STriangle, tra[k], tra[n]);
}
STriangleLl *tll = NULL;
for(i = 0; i < m->l.n; i++) {
STriangleLl *tn = STriangleLl::Alloc();
tn->tri = &(tra[i]);
tn->next = tll;
tll = tn;
}
return SKdNode::From(tll);
}
SKdNode *SKdNode::From(STriangleLl *tll) {
int which = 0;
SKdNode *ret = Alloc();
if(!tll) {
goto leaf;
}
int i;
int gtc[3] = { 0, 0, 0 }, ltc[3] = { 0, 0, 0 }, allc = 0;
double badness[3] = { 0, 0, 0 };
double split[3] = { 0, 0, 0 };
for(i = 0; i < 3; i++) {
int tcnt = 0;
STriangleLl *ll;
for(ll = tll; ll; ll = ll->next) {
STriangle *tr = ll->tri;
split[i] += (ll->tri->a).Element(i);
split[i] += (ll->tri->b).Element(i);
split[i] += (ll->tri->c).Element(i);
tcnt++;
}
split[i] /= (tcnt*3);
for(ll = tll; ll; ll = ll->next) {
STriangle *tr = ll->tri;
double a = (tr->a).Element(i),
b = (tr->b).Element(i),
c = (tr->c).Element(i);
if(a < split[i] + KDTREE_EPS ||
b < split[i] + KDTREE_EPS ||
c < split[i] + KDTREE_EPS)
{
ltc[i]++;
}
if(a > split[i] - KDTREE_EPS ||
b > split[i] - KDTREE_EPS ||
c > split[i] - KDTREE_EPS)
{
gtc[i]++;
}
if(i == 0) allc++;
}
badness[i] = pow((double)ltc[i], 4) + pow((double)gtc[i], 4);
}
if(badness[0] < badness[1] && badness[0] < badness[2]) {
which = 0;
} else if(badness[1] < badness[2]) {
which = 1;
} else {
which = 2;
}
if(allc < 3 || allc == gtc[which] || allc == ltc[which]) {
goto leaf;
}
STriangleLl *ll;
STriangleLl *lgt = NULL, *llt = NULL;
for(ll = tll; ll; ll = ll->next) {
STriangle *tr = ll->tri;
double a = (tr->a).Element(which),
b = (tr->b).Element(which),
c = (tr->c).Element(which);
if(a < split[which] + KDTREE_EPS ||
b < split[which] + KDTREE_EPS ||
c < split[which] + KDTREE_EPS)
{
STriangleLl *n = STriangleLl::Alloc();
*n = *ll;
n->next = llt;
llt = n;
}
if(a > split[which] - KDTREE_EPS ||
b > split[which] - KDTREE_EPS ||
c > split[which] - KDTREE_EPS)
{
STriangleLl *n = STriangleLl::Alloc();
*n = *ll;
n->next = lgt;
lgt = n;
}
}
ret->which = which;
ret->c = split[which];
ret->gt = SKdNode::From(lgt);
ret->lt = SKdNode::From(llt);
return ret;
leaf:
ret->tris = tll;
return ret;
}
void SKdNode::ClearTags(void) {
if(gt && lt) {
gt->ClearTags();
lt->ClearTags();
} else {
STriangleLl *ll;
for(ll = tris; ll; ll = ll->next) {
ll->tri->tag = 0;
}
}
}
void SKdNode::AddTriangle(STriangle *tr) {
if(gt && lt) {
double ta = (tr->a).Element(which),
tb = (tr->b).Element(which),
tc = (tr->c).Element(which);
if(ta < c + KDTREE_EPS ||
tb < c + KDTREE_EPS ||
tc < c + KDTREE_EPS)
{
lt->AddTriangle(tr);
}
if(ta > c - KDTREE_EPS ||
tb > c - KDTREE_EPS ||
tc > c - KDTREE_EPS)
{
gt->AddTriangle(tr);
}
} else {
STriangleLl *tn = STriangleLl::Alloc();
tn->tri = tr;
tn->next = tris;
tris = tn;
}
}
void SKdNode::MakeMeshInto(SMesh *m) {
if(gt) gt->MakeMeshInto(m);
if(lt) lt->MakeMeshInto(m);
STriangleLl *ll;
for(ll = tris; ll; ll = ll->next) {
if(ll->tri->tag) continue;
m->AddTriangle(ll->tri);
ll->tri->tag = 1;
}
}
//-----------------------------------------------------------------------------
// If any triangles in the mesh have an edge that goes through v (but not
// a vertex at v), then split those triangles so that they now have a vertex
// there. The existing triangle is modified, and the new triangle appears
// in extras.
//-----------------------------------------------------------------------------
void SKdNode::SnapToVertex(Vector v, SMesh *extras) {
if(gt && lt) {
double vc = v.Element(which);
if(vc < c + KDTREE_EPS) {
lt->SnapToVertex(v, extras);
}
if(vc > c - KDTREE_EPS) {
gt->SnapToVertex(v, extras);
}
// Nothing bad happens if the triangle to be split appears in both
// branches; the first call will split the triangle, so that the
// second call will do nothing, because the modified triangle will
// already contain v
} else {
STriangleLl *ll;
for(ll = tris; ll; ll = ll->next) {
STriangle *tr = ll->tri;
// Do a cheap bbox test first
int k;
bool mightHit = true;
for(k = 0; k < 3; k++) {
if((tr->a).Element(k) < v.Element(k) - KDTREE_EPS &&
(tr->b).Element(k) < v.Element(k) - KDTREE_EPS &&
(tr->c).Element(k) < v.Element(k) - KDTREE_EPS)
{
mightHit = false;
break;
}
if((tr->a).Element(k) > v.Element(k) + KDTREE_EPS &&
(tr->b).Element(k) > v.Element(k) + KDTREE_EPS &&
(tr->c).Element(k) > v.Element(k) + KDTREE_EPS)
{
mightHit = false;
break;
}
}
if(!mightHit) continue;
if(tr->a.Equals(v)) { tr->a = v; continue; }
if(tr->b.Equals(v)) { tr->b = v; continue; }
if(tr->c.Equals(v)) { tr->c = v; continue; }
if(v.OnLineSegment(tr->a, tr->b)) {
STriangle nt = STriangle::From(tr->meta, tr->a, v, tr->c);
extras->AddTriangle(&nt);
tr->a = v;
continue;
}
if(v.OnLineSegment(tr->b, tr->c)) {
STriangle nt = STriangle::From(tr->meta, tr->b, v, tr->a);
extras->AddTriangle(&nt);
tr->b = v;
continue;
}
if(v.OnLineSegment(tr->c, tr->a)) {
STriangle nt = STriangle::From(tr->meta, tr->c, v, tr->b);
extras->AddTriangle(&nt);
tr->c = v;
continue;
}
}
}
}
//-----------------------------------------------------------------------------
// Snap to each vertex of each triangle of the given mesh. If the given mesh
// is identical to the mesh used to make this kd tree, then the result should
// be a vertex-to-vertex mesh.
//-----------------------------------------------------------------------------
void SKdNode::SnapToMesh(SMesh *m) {
int i, j, k;
for(i = 0; i < m->l.n; i++) {
STriangle *tr = &(m->l.elem[i]);
for(j = 0; j < 3; j++) {
Vector v = ((j == 0) ? tr->a :
((j == 1) ? tr->b :
tr->c));
SMesh extra;
ZERO(&extra);
SnapToVertex(v, &extra);
for(k = 0; k < extra.l.n; k++) {
STriangle *tra = (STriangle *)AllocTemporary(sizeof(*tra));
*tra = extra.l.elem[k];
AddTriangle(tra);
}
extra.Clear();
}
}
}
//-----------------------------------------------------------------------------
// For all the edges in sel, split them against the given triangle, and test
// them for occlusion. Keep only the visible segments. sel is both our input
// and our output.
//-----------------------------------------------------------------------------
void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) {
SEdgeList seln;
ZERO(&seln);
Vector tn = tr->Normal().WithMagnitude(1);
double td = tn.Dot(tr->a);
// Consider front-facing triangles only.
if(tn.z > LENGTH_EPS) {
// If the edge crosses our triangle's plane, then split into above
// and below parts. Note that we must preserve auxA, which contains
// the style associated with this line.
SEdge *se;
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
double da = (se->a).Dot(tn) - td,
db = (se->b).Dot(tn) - td;
if((da < -LENGTH_EPS && db > LENGTH_EPS) ||
(db < -LENGTH_EPS && da > LENGTH_EPS))
{
Vector m = Vector::AtIntersectionOfPlaneAndLine(
tn, td,
se->a, se->b, NULL);
seln.AddEdge(m, se->b, se->auxA);
se->b = m;
}
}
for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) {
sel->AddEdge(se->a, se->b, se->auxA);
}
seln.Clear();
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
Vector pt = ((se->a).Plus(se->b)).ScaledBy(0.5);
double dt = pt.Dot(tn) - td;
if(pt.Dot(tn) - td > -LENGTH_EPS) {
// Edge is in front of or on our plane (remember, tn.z > 0)
// so it is exempt from further splitting
se->auxB = 1;
} else {
// Edge is behind our plane, needs further splitting
se->auxB = 0;
}
}
// Considering only the (x, y) coordinates, split the edge against our
// triangle.
Point2d a = (tr->a).ProjectXy(),
b = (tr->b).ProjectXy(),
c = (tr->c).ProjectXy();
Point2d n[3] = { (b.Minus(a)).Normal().WithMagnitude(1),
(c.Minus(b)).Normal().WithMagnitude(1),
(a.Minus(c)).Normal().WithMagnitude(1) };
double d[3] = { n[0].Dot(b),
n[1].Dot(c),
n[2].Dot(a) };
// Split all of the edges where they intersect the triangle edges
int i;
for(i = 0; i < 3; i++) {
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
if(se->auxB) continue;
Point2d ap = (se->a).ProjectXy(),
bp = (se->b).ProjectXy();
double da = n[i].Dot(ap) - d[i],
db = n[i].Dot(bp) - d[i];
if((da < -LENGTH_EPS && db > LENGTH_EPS) ||
(db < -LENGTH_EPS && da > LENGTH_EPS))
{
double dab = (db - da);
Vector spl = ((se->a).ScaledBy( db/dab)).Plus(
(se->b).ScaledBy(-da/dab));
seln.AddEdge(spl, se->b, se->auxA);
se->b = spl;
}
}
for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) {
// The split pieces are all behind the triangle, since only
// edges behind the triangle got split. So their auxB is 0.
sel->AddEdge(se->a, se->b, se->auxA, 0);
}
seln.Clear();
}
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
if(se->auxB) {
// Lies above or on the triangle plane, so triangle doesn't
// occlude it.
se->tag = 0;
} else {
// Test the segment to see if it lies outside the triangle
// (i.e., outside wrt at least one edge), and keep it only
// then.
Point2d pt = ((se->a).Plus(se->b).ScaledBy(0.5)).ProjectXy();
se->tag = 1;
for(i = 0; i < 3; i++) {
// If the test point lies on the boundary of our triangle,
// then we still discard the edge.
if(n[i].Dot(pt) - d[i] > LENGTH_EPS) se->tag = 0;
}
}
}
sel->l.RemoveTagged();
}
}
//-----------------------------------------------------------------------------
// Given an edge orig, occlusion test it against our mesh. We output an edge
// list in sel, containing the visible portions of that edge.
//-----------------------------------------------------------------------------
void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) {
if(gt && lt) {
double ac = (orig.a).Element(which),
bc = (orig.b).Element(which);
// We can ignore triangles that are separated in x or y, but triangles
// that are separated in z may still contribute
if(ac < c + KDTREE_EPS ||
bc < c + KDTREE_EPS ||
which == 2)
{
lt->OcclusionTestLine(orig, sel, cnt);
}
if(ac > c - KDTREE_EPS ||
bc > c - KDTREE_EPS ||
which == 2)
{
gt->OcclusionTestLine(orig, sel, cnt);
}
} else {
STriangleLl *ll;
for(ll = tris; ll; ll = ll->next) {
STriangle *tr = ll->tri;
if(tr->tag == cnt) continue;
SplitLinesAgainstTriangle(sel, tr);
tr->tag = cnt;
}
}
}
//-----------------------------------------------------------------------------
// Search the mesh for a triangle with an edge from b to a (i.e., the mate
// for the edge from a to b), and increment *n each time that we find one.
// If a triangle is found, then report whether it is front- or back-facing
// using *fwd. And regardless of whether a mate is found, report whether
// the edge intersects the mesh with *inter; if coplanarIsInter then we
// count the edge as intersecting if it's coplanar with a triangle in the
// mesh, otherwise not.
//-----------------------------------------------------------------------------
void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int cnt,
bool coplanarIsInter, bool *inter, bool *fwd,
DWORD *face)
{
if(gt && lt) {
double ac = a.Element(which),
bc = b.Element(which);
if(ac < c + KDTREE_EPS ||
bc < c + KDTREE_EPS)
{
lt->FindEdgeOn(a, b, n, cnt, coplanarIsInter, inter, fwd, face);
}
if(ac > c - KDTREE_EPS ||
bc > c - KDTREE_EPS)
{
gt->FindEdgeOn(a, b, n, cnt, coplanarIsInter, inter, fwd, face);
}
return;
}
// We are a leaf node; so we iterate over all the triangles in our
// linked list.
STriangleLl *ll;
for(ll = tris; ll; ll = ll->next) {
STriangle *tr = ll->tri;
if(tr->tag == cnt) continue;
// Test if this triangle matches up with the given edge
if((a.Equals(tr->b) && b.Equals(tr->a)) ||
(a.Equals(tr->c) && b.Equals(tr->b)) ||
(a.Equals(tr->a) && b.Equals(tr->c)))
{
(*n)++;
// Record whether this triangle is front- or back-facing.
if(tr->Normal().z > LENGTH_EPS) {
*fwd = true;
} else {
*fwd = false;
}
// And record the triangle's face
*face = tr->meta.face;
} else if(((a.Equals(tr->a) && b.Equals(tr->b)) ||
(a.Equals(tr->b) && b.Equals(tr->c)) ||
(a.Equals(tr->c) && b.Equals(tr->a))))
{
// It's an edge of this triangle, okay.
} else {
// Check for self-intersection
Vector n = (tr->Normal()).WithMagnitude(1);
double d = (tr->a).Dot(n);
double pa = a.Dot(n) - d, pb = b.Dot(n) - d;
// It's an intersection if neither point lies in-plane,
// and the edge crosses the plane (should handle in-plane
// intersections separately but don't yet).
if((pa < -LENGTH_EPS || pa > LENGTH_EPS) &&
(pb < -LENGTH_EPS || pb > LENGTH_EPS) &&
(pa*pb < 0))
{
// The edge crosses the plane of the triangle; now see if
// it crosses inside the triangle.
if(tr->ContainsPointProjd(b.Minus(a), a)) {
if(coplanarIsInter) {
*inter = true;
} else {
Vector p = Vector::AtIntersectionOfPlaneAndLine(
n, d, a, b, NULL);
Vector ta = tr->a,
tb = tr->b,
tc = tr->c;
if((p.DistanceToLine(ta, tb.Minus(ta)) < LENGTH_EPS) ||
(p.DistanceToLine(tb, tc.Minus(tb)) < LENGTH_EPS) ||
(p.DistanceToLine(tc, ta.Minus(tc)) < LENGTH_EPS))
{
// Intersection lies on edge. This happens when
// our edge is from a triangle coplanar with
// another triangle in the mesh. We don't test
// the edge against triangles whose plane contains
// that edge, but we do end up testing against
// the coplanar triangle's neighbours, which we
// will intersect on their edges.
} else {
*inter = true;
}
}
}
}
}
// Ensure that we don't count this triangle twice if it appears
// in two buckets of the kd tree.
tr->tag = cnt;
}
}
//-----------------------------------------------------------------------------
// Pick certain classes of edges out from our mesh. These might be:
// * naked edges (i.e., edges with no anti-parallel neighbor) and self-
// intersecting edges (i.e., edges that cross another triangle)
// * turning edges (i.e., edges where a front-facing triangle joins
// a back-facing triangle)
// * emphasized edges (i.e., edges where a triangle from one face joins
// a triangle from a different face)
//-----------------------------------------------------------------------------
void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how,
bool coplanarIsInter, bool *inter, bool *leaky)
{
if(inter) *inter = false;
if(leaky) *leaky = false;
SMesh m;
ZERO(&m);
ClearTags();
MakeMeshInto(&m);
int cnt = 1234;
int i, j;
for(i = 0; i < m.l.n; i++) {
STriangle *tr = &(m.l.elem[i]);
for(j = 0; j < 3; j++) {
Vector a = (j == 0) ? tr->a : ((j == 1) ? tr->b : tr->c);
Vector b = (j == 0) ? tr->b : ((j == 1) ? tr->c : tr->a);
int n = 0;
bool thisIntersects = false, fwd;
DWORD face;
FindEdgeOn(a, b, &n, cnt, coplanarIsInter,
&thisIntersects, &fwd, &face);
switch(how) {
case NAKED_OR_SELF_INTER_EDGES:
if(n != 1) {
sel->AddEdge(a, b);
if(leaky) *leaky = true;
}
if(thisIntersects) {
sel->AddEdge(a, b);
if(inter) *inter = true;
}
break;
case SELF_INTER_EDGES:
if(thisIntersects) {
sel->AddEdge(a, b);
if(inter) *inter = true;
}
break;
case TURNING_EDGES:
if((tr->Normal().z < LENGTH_EPS) &&
(n == 1) &&
fwd)
{
// This triangle is back-facing (or on edge), and
// this edge has exactly one mate, and that mate is
// front-facing. So this is a turning edge.
sel->AddEdge(a, b, Style::SOLID_EDGE);
}
break;
case EMPHASIZED_EDGES:
if(tr->meta.face != face && n == 1) {
// The two triangles that join at this edge come from
// different faces; either really different faces,
// or one is from a face and the other is zero (i.e.,
// not from a face).
sel->AddEdge(a, b);
}
break;
default: oops();
}
cnt++;
}
}
m.Clear();
}

View File

@ -1,652 +0,0 @@
//-----------------------------------------------------------------------------
// User-initiated (not parametric) operations to modify our sketch, by
// changing the requests, like to round a corner or split curves where they
// intersect.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
//-----------------------------------------------------------------------------
// Replace a point-coincident constraint on oldpt with that same constraint
// on newpt. Useful when splitting or tangent arcing.
//-----------------------------------------------------------------------------
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->type == Constraint::POINTS_COINCIDENT) {
if(c->ptA.v == oldpt.v) c->ptA = newpt;
if(c->ptB.v == oldpt.v) c->ptB = newpt;
}
}
}
//-----------------------------------------------------------------------------
// Let's say that A is coincident with B, and B is coincident with C. This
// implies that A is coincident with C; but if we delete B, then both
// constraints must be deleted too (since they reference B), and A is no
// longer constrained to C. This routine adds back that constraint.
//-----------------------------------------------------------------------------
void GraphicsWindow::FixConstraintsForRequestBeingDeleted(hRequest hr) {
Request *r = SK.GetRequest(hr);
if(r->group.v != SS.GW.activeGroup.v) 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->type != Entity::POINT_IN_2D &&
e->type != Entity::POINT_IN_3D)
{
continue;
}
// This is a point generated by the request being deleted; so fix
// the constraints for that.
FixConstraintsForPointBeingDeleted(e->h);
}
}
void GraphicsWindow::FixConstraintsForPointBeingDeleted(hEntity hpt) {
List<hEntity> ld;
ZERO(&ld);
Constraint *c;
SK.constraint.ClearTags();
for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) {
if(c->type != Constraint::POINTS_COINCIDENT) continue;
if(c->group.v != SS.GW.activeGroup.v) continue;
if(c->ptA.v == hpt.v) {
ld.Add(&(c->ptB));
c->tag = 1;
}
if(c->ptB.v == hpt.v) {
ld.Add(&(c->ptA));
c->tag = 1;
}
}
// These would get removed anyways when we regenerated, but do it now;
// that way subsequent calls of this function (if multiple coincident
// points are getting deleted) will work correctly.
SK.constraint.RemoveTagged();
// If more than one point was constrained coincident with hpt, then
// 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]);
}
ld.Clear();
}
//-----------------------------------------------------------------------------
// A curve by its parametric equation, helper functions for computing tangent
// arcs by a numerical method.
//-----------------------------------------------------------------------------
void GraphicsWindow::ParametricCurve::MakeFromEntity(hEntity he, bool reverse) {
ZERO(this);
Entity *e = SK.GetEntity(he);
if(e->type == Entity::LINE_SEGMENT) {
isLine = true;
p0 = e->EndpointStart(),
p1 = e->EndpointFinish();
if(reverse) {
SWAP(Vector, p0, p1);
}
} else if(e->type == Entity::ARC_OF_CIRCLE) {
isLine = false;
p0 = SK.GetEntity(e->point[0])->PointGetNum();
Vector pe = SK.GetEntity(e->point[1])->PointGetNum();
r = (pe.Minus(p0)).Magnitude();
e->ArcGetAngles(&theta0, &theta1, &dtheta);
if(reverse) {
SWAP(double, theta0, theta1);
dtheta = -dtheta;
}
EntityBase *wrkpln = SK.GetEntity(e->workplane)->Normal();
u = wrkpln->NormalU();
v = wrkpln->NormalV();
} else {
oops();
}
}
double GraphicsWindow::ParametricCurve::LengthForAuto(void) {
if(isLine) {
// Allow a third of the line to disappear with auto radius
return (p1.Minus(p0)).Magnitude() / 3;
} else {
// But only a twentieth of the arc; shorter means fewer numerical
// problems since the curve is more linear over shorter sections.
return (fabs(dtheta)*r)/20;
}
}
Vector GraphicsWindow::ParametricCurve::PointAt(double t) {
if(isLine) {
return p0.Plus((p1.Minus(p0)).ScaledBy(t));
} else {
double theta = theta0 + dtheta*t;
return p0.Plus(u.ScaledBy(r*cos(theta)).Plus(v.ScaledBy(r*sin(theta))));
}
}
Vector GraphicsWindow::ParametricCurve::TangentAt(double t) {
if(isLine) {
return p1.Minus(p0);
} else {
double theta = theta0 + dtheta*t;
Vector t = u.ScaledBy(-r*sin(theta)).Plus(v.ScaledBy(r*cos(theta)));
t = t.ScaledBy(dtheta);
return t;
}
}
hRequest GraphicsWindow::ParametricCurve::CreateRequestTrimmedTo(double t,
bool extraConstraints, hEntity orig, hEntity arc, bool arcFinish)
{
hRequest hr;
Entity *e;
if(isLine) {
hr = SS.GW.AddRequest(Request::LINE_SEGMENT, false),
e = SK.GetEntity(hr.entity(0));
SK.GetEntity(e->point[0])->PointForceTo(PointAt(t));
SK.GetEntity(e->point[1])->PointForceTo(PointAt(1));
ConstrainPointIfCoincident(e->point[0]);
ConstrainPointIfCoincident(e->point[1]);
if(extraConstraints) {
Constraint::Constrain(Constraint::PT_ON_LINE,
hr.entity(1), Entity::NO_ENTITY, orig);
}
Constraint::Constrain(Constraint::ARC_LINE_TANGENT,
Entity::NO_ENTITY, Entity::NO_ENTITY,
arc, e->h, arcFinish, false);
} else {
hr = SS.GW.AddRequest(Request::ARC_OF_CIRCLE, false),
e = SK.GetEntity(hr.entity(0));
SK.GetEntity(e->point[0])->PointForceTo(p0);
if(dtheta > 0) {
SK.GetEntity(e->point[1])->PointForceTo(PointAt(t));
SK.GetEntity(e->point[2])->PointForceTo(PointAt(1));
} else {
SK.GetEntity(e->point[2])->PointForceTo(PointAt(t));
SK.GetEntity(e->point[1])->PointForceTo(PointAt(1));
}
ConstrainPointIfCoincident(e->point[0]);
ConstrainPointIfCoincident(e->point[1]);
ConstrainPointIfCoincident(e->point[2]);
// The tangency constraint alone is enough to fully constrain it,
// so there's no need for more.
Constraint::Constrain(Constraint::CURVE_CURVE_TANGENT,
Entity::NO_ENTITY, Entity::NO_ENTITY,
arc, e->h, arcFinish, (dtheta < 0));
}
return hr;
}
//-----------------------------------------------------------------------------
// If a point in the same group as hpt, and numerically coincident with hpt,
// happens to exist, then constrain that point coincident to hpt.
//-----------------------------------------------------------------------------
void GraphicsWindow::ParametricCurve::ConstrainPointIfCoincident(hEntity hpt) {
Entity *e, *pt;
pt = SK.GetEntity(hpt);
Vector ev, ptv;
ptv = pt->PointGetNum();
for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) {
if(e->h.v == pt->h.v) continue;
if(!e->IsPoint()) continue;
if(e->group.v != pt->group.v) continue;
if(e->workplane.v != pt->workplane.v) continue;
ev = e->PointGetNum();
if(!ev.Equals(ptv)) continue;
Constraint::ConstrainCoincident(hpt, e->h);
break;
}
}
//-----------------------------------------------------------------------------
// A single point must be selected when this function is called. We find two
// non-construction line segments that join at this point, and create a
// tangent arc joining them.
//-----------------------------------------------------------------------------
void GraphicsWindow::MakeTangentArc(void) {
if(!LockedInWorkplane()) {
Error("Must be sketching in workplane to create tangent "
"arc.");
return;
}
// The point corresponding to the vertex to be rounded.
Vector pshared = SK.GetEntity(gs.point[0])->PointGetNum();
ClearSelection();
// First, find two requests (that are not construction, and that are
// in our group and workplane) that generate entities that have an
// endpoint at our vertex to be rounded.
int i, c = 0;
Entity *ent[2];
Request *req[2];
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::LINE_SEGMENT &&
r->type != Request::ARC_OF_CIRCLE)
{
continue;
}
Entity *e = SK.GetEntity(r->h.entity(0));
Vector ps = e->EndpointStart(),
pf = e->EndpointFinish();
if(ps.Equals(pshared) || pf.Equals(pshared)) {
if(c < 2) {
// We record the entity and request and their handles,
// and whether the vertex to be rounded is the start or
// finish of this entity.
ent[c] = e;
hent[c] = e->h;
req[c] = r;
hreq[c] = r->h;
pointf[c] = (pf.Equals(pshared));
}
c++;
}
}
if(c != 2) {
Error("To create a tangent arc, select a point where two "
"non-construction lines or cicles in this group and "
"workplane join.");
return;
}
Entity *wrkpl = SK.GetEntity(ActiveWorkplane());
Vector wn = wrkpl->Normal()->NormalN();
// Based on these two entities, we make the objects that we'll use to
// numerically find the tangent arc.
ParametricCurve pc[2];
pc[0].MakeFromEntity(ent[0]->h, pointf[0]);
pc[1].MakeFromEntity(ent[1]->h, pointf[1]);
// And thereafter we mustn't touch the entity or req ptrs,
// because the new requests/entities we add might force a
// realloc.
memset(ent, 0, sizeof(ent));
memset(req, 0, sizeof(req));
Vector pinter;
double r, vv;
// We now do Newton iterations to find the tangent arc, and its positions
// t back along the two curves, starting from shared point of the curves
// at t = 0. Lots of iterations helps convergence, and this is still
// ~10 ms for everything.
int iters = 1000;
double t[2] = { 0, 0 }, tp[2];
for(i = 0; i < iters + 20; i++) {
Vector p0 = pc[0].PointAt(t[0]),
p1 = pc[1].PointAt(t[1]),
t0 = pc[0].TangentAt(t[0]),
t1 = pc[1].TangentAt(t[1]);
pinter = Vector::AtIntersectionOfLines(p0, p0.Plus(t0),
p1, p1.Plus(t1),
NULL, NULL, NULL);
// The sign of vv determines whether shortest distance is
// clockwise or anti-clockwise.
Vector v = (wn.Cross(t0)).WithMagnitude(1);
vv = t1.Dot(v);
double dot = (t0.WithMagnitude(1)).Dot(t1.WithMagnitude(1));
double theta = acos(dot);
if(SS.tangentArcManual) {
r = SS.tangentArcRadius;
} else {
r = 200/scale;
// Set the radius so that no more than one third of the
// line segment disappears.
r = min(r, pc[0].LengthForAuto()*tan(theta/2));
r = min(r, pc[1].LengthForAuto()*tan(theta/2));;
}
// We are source-stepping the radius, to improve convergence. So
// ramp that for most of the iterations, and then do a few at
// the end with that constant for polishing.
if(i < iters) {
r *= 0.1 + 0.9*i/((double)iters);
}
// The distance from the intersection of the lines to the endpoint
// of the arc, along each line.
double el = r/tan(theta/2);
// Compute the endpoints of the arc, for each curve
Vector pa0 = pinter.Plus(t0.WithMagnitude(el)),
pa1 = pinter.Plus(t1.WithMagnitude(el));
tp[0] = t[0];
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);
}
// Stupid check for convergence, and for an out of range result (as
// we would get, for example, if the line is too short to fit the
// rounding arc).
if(fabs(tp[0] - t[0]) > 1e-3 || fabs(tp[1] - t[1]) > 1e-3 ||
t[0] < 0.01 || t[1] < 0.01 ||
t[0] > 0.99 || t[1] > 0.99 ||
isnan(t[0]) || isnan(t[1]))
{
Error("Couldn't round this corner. Try a smaller radius, or try "
"creating the desired geometry by hand with tangency "
"constraints.");
return;
}
// Compute the location of the center of the arc
Vector center = pc[0].PointAt(t[0]),
v0inter = pinter.Minus(center);
int a, b;
if(vv < 0) {
a = 1; b = 2;
center = center.Minus(v0inter.Cross(wn).WithMagnitude(r));
} else {
a = 2; b = 1;
center = center.Plus(v0inter.Cross(wn).WithMagnitude(r));
}
SS.UndoRemember();
hRequest harc = AddRequest(Request::ARC_OF_CIRCLE, false);
Entity *earc = SK.GetEntity(harc.entity(0));
hEntity hearc = earc->h;
SK.GetEntity(earc->point[0])->PointForceTo(center);
SK.GetEntity(earc->point[a])->PointForceTo(pc[0].PointAt(t[0]));
SK.GetEntity(earc->point[b])->PointForceTo(pc[1].PointAt(t[1]));
earc = NULL;
pc[0].CreateRequestTrimmedTo(t[0], !SS.tangentArcDeleteOld,
hent[0], hearc, (b == 1));
pc[1].CreateRequestTrimmedTo(t[1], !SS.tangentArcDeleteOld,
hent[1], hearc, (a == 1));
// Now either make the original entities construction, or delete them
// entirely, according to user preference.
Request *re;
SK.request.ClearTags();
for(re = SK.request.First(); re; re = SK.request.NextAfter(re)) {
if(re->h.v == hreq[0].v || re->h.v == hreq[1].v) {
if(SS.tangentArcDeleteOld) {
re->tag = 1;
} else {
re->construction = true;
}
}
}
if(SS.tangentArcDeleteOld) {
DeleteTaggedRequests();
}
SS.later.generateAll = true;
}
hEntity GraphicsWindow::SplitLine(hEntity he, Vector pinter) {
// Save the original endpoints, since we're about to delete this entity.
Entity *e01 = SK.GetEntity(he);
hEntity hep0 = e01->point[0], hep1 = e01->point[1];
Vector p0 = SK.GetEntity(hep0)->PointGetNum(),
p1 = SK.GetEntity(hep1)->PointGetNum();
// Add the two line segments this one gets split into.
hRequest r0i = AddRequest(Request::LINE_SEGMENT, false),
ri1 = AddRequest(Request::LINE_SEGMENT, false);
// Don't get entities till after adding, realloc issues
Entity *e0i = SK.GetEntity(r0i.entity(0)),
*ei1 = SK.GetEntity(ri1.entity(0));
SK.GetEntity(e0i->point[0])->PointForceTo(p0);
SK.GetEntity(e0i->point[1])->PointForceTo(pinter);
SK.GetEntity(ei1->point[0])->PointForceTo(pinter);
SK.GetEntity(ei1->point[1])->PointForceTo(p1);
ReplacePointInConstraints(hep0, e0i->point[0]);
ReplacePointInConstraints(hep1, ei1->point[1]);
Constraint::ConstrainCoincident(e0i->point[1], ei1->point[0]);
return e0i->point[1];
}
hEntity GraphicsWindow::SplitCircle(hEntity he, Vector pinter) {
Entity *circle = SK.GetEntity(he);
if(circle->type == Entity::CIRCLE) {
// Start with an unbroken circle, split it into a 360 degree arc.
Vector center = SK.GetEntity(circle->point[0])->PointGetNum();
circle = NULL; // shortly invalid!
hRequest hr = AddRequest(Request::ARC_OF_CIRCLE, false);
Entity *arc = SK.GetEntity(hr.entity(0));
SK.GetEntity(arc->point[0])->PointForceTo(center);
SK.GetEntity(arc->point[1])->PointForceTo(pinter);
SK.GetEntity(arc->point[2])->PointForceTo(pinter);
Constraint::ConstrainCoincident(arc->point[1], arc->point[2]);
return arc->point[1];
} else {
// Start with an arc, break it in to two arcs
hEntity hc = circle->point[0],
hs = circle->point[1],
hf = circle->point[2];
Vector center = SK.GetEntity(hc)->PointGetNum(),
start = SK.GetEntity(hs)->PointGetNum(),
finish = SK.GetEntity(hf)->PointGetNum();
circle = NULL; // shortly invalid!
hRequest hr0 = AddRequest(Request::ARC_OF_CIRCLE, false),
hr1 = AddRequest(Request::ARC_OF_CIRCLE, false);
Entity *arc0 = SK.GetEntity(hr0.entity(0)),
*arc1 = SK.GetEntity(hr1.entity(0));
SK.GetEntity(arc0->point[0])->PointForceTo(center);
SK.GetEntity(arc0->point[1])->PointForceTo(start);
SK.GetEntity(arc0->point[2])->PointForceTo(pinter);
SK.GetEntity(arc1->point[0])->PointForceTo(center);
SK.GetEntity(arc1->point[1])->PointForceTo(pinter);
SK.GetEntity(arc1->point[2])->PointForceTo(finish);
ReplacePointInConstraints(hs, arc0->point[1]);
ReplacePointInConstraints(hf, arc1->point[2]);
Constraint::ConstrainCoincident(arc0->point[2], arc1->point[1]);
return arc0->point[2];
}
}
hEntity GraphicsWindow::SplitCubic(hEntity he, Vector pinter) {
// Save the original endpoints, since we're about to delete this entity.
Entity *e01 = SK.GetEntity(he);
SBezierList sbl;
ZERO(&sbl);
e01->GenerateBezierCurves(&sbl);
hEntity hep0 = e01->point[0],
hep1 = e01->point[3+e01->extraPoints],
hep0n = Entity::NO_ENTITY, // the new start point
hep1n = Entity::NO_ENTITY, // the new finish point
hepin = Entity::NO_ENTITY; // the intersection point
// The curve may consist of multiple cubic segments. So find which one
// contains the intersection point.
double t;
int i, j;
for(i = 0; i < sbl.l.n; i++) {
SBezier *sb = &(sbl.l.elem[i]);
if(sb->deg != 3) oops();
sb->ClosestPointTo(pinter, &t, false);
if(pinter.Equals(sb->PointAt(t))) {
// Split that segment at the intersection.
SBezier b0i, bi1, b01 = *sb;
b01.SplitAt(t, &b0i, &bi1);
// Add the two cubic segments this one gets split into.
hRequest r0i = AddRequest(Request::CUBIC, false),
ri1 = AddRequest(Request::CUBIC, false);
// Don't get entities till after adding, realloc issues
Entity *e0i = SK.GetEntity(r0i.entity(0)),
*ei1 = SK.GetEntity(ri1.entity(0));
for(j = 0; j <= 3; j++) {
SK.GetEntity(e0i->point[j])->PointForceTo(b0i.ctrl[j]);
}
for(j = 0; j <= 3; j++) {
SK.GetEntity(ei1->point[j])->PointForceTo(bi1.ctrl[j]);
}
Constraint::ConstrainCoincident(e0i->point[3], ei1->point[0]);
if(i == 0) hep0n = e0i->point[0];
hep1n = ei1->point[3];
hepin = e0i->point[3];
} else {
hRequest r = AddRequest(Request::CUBIC, false);
Entity *e = SK.GetEntity(r.entity(0));
for(j = 0; j <= 3; j++) {
SK.GetEntity(e->point[j])->PointForceTo(sb->ctrl[j]);
}
if(i == 0) hep0n = e->point[0];
hep1n = e->point[3];
}
}
sbl.Clear();
ReplacePointInConstraints(hep0, hep0n);
ReplacePointInConstraints(hep1, hep1n);
return hepin;
}
hEntity GraphicsWindow::SplitEntity(hEntity he, Vector pinter) {
Entity *e = SK.GetEntity(he);
int entityType = e->type;
hEntity ret;
if(e->IsCircle()) {
ret = SplitCircle(he, pinter);
} else if(e->type == Entity::LINE_SEGMENT) {
ret = SplitLine(he, pinter);
} else if(e->type == Entity::CUBIC || e->type == Entity::CUBIC_PERIODIC) {
ret = SplitCubic(he, pinter);
} else {
Error("Couldn't split this entity; lines, circles, or cubics only.");
return Entity::NO_ENTITY;
}
// Finally, delete the request that generated the original entity.
int 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;
// 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;
break;
}
}
DeleteTaggedRequests();
return ret;
}
void GraphicsWindow::SplitLinesOrCurves(void) {
if(!LockedInWorkplane()) {
Error("Must be sketching in workplane to split.");
return;
}
GroupSelection();
if(!(gs.n == 2 &&(gs.lineSegments +
gs.circlesOrArcs +
gs.cubics +
gs.periodicCubics) == 2))
{
Error("Select two entities that intersect each other (e.g. two lines "
"or two circles or a circle and a line).");
return;
}
hEntity ha = gs.entity[0],
hb = gs.entity[1];
Entity *ea = SK.GetEntity(ha),
*eb = SK.GetEntity(hb);
// Compute the possibly-rational Bezier curves for each of these entities
SBezierList sbla, sblb;
ZERO(&sbla);
ZERO(&sblb);
ea->GenerateBezierCurves(&sbla);
eb->GenerateBezierCurves(&sblb);
// and then compute the points where they intersect, based on those curves.
SPointList inters;
ZERO(&inters);
sbla.AllIntersectionsWith(&sblb, &inters);
if(inters.l.n > 0) {
Vector pi;
// If there's multiple points, then take the one closest to the
// mouse pointer.
double dmin = VERY_POSITIVE;
SPoint *sp;
for(sp = inters.l.First(); sp; sp = inters.l.NextAfter(sp)) {
double d = ProjectPoint(sp->p).DistanceTo(currentMousePosition);
if(d < dmin) {
dmin = d;
pi = sp->p;
}
}
SS.UndoRemember();
hEntity hia = SplitEntity(ha, pi),
hib = SplitEntity(hb, pi);
// SplitEntity adds the coincident constraints to join the split halves
// of each original entity; and then we add the constraint to join
// the two entities together at the split point.
if(hia.v && hib.v) {
Constraint::ConstrainCoincident(hia, hib);
}
} else {
Error("Can't split; no intersection found.");
}
// All done, clean up and regenerate.
inters.Clear();
sbla.Clear();
sblb.Clear();
ClearSelection();
SS.later.generateAll = true;
}

1331
mouse.cpp

File diff suppressed because it is too large Load Diff

0
obj/t
View File

View File

@ -1,41 +0,0 @@
#!/usr/bin/perl
use GD;
my ($out, $proto) = @ARGV;
open(OUT, ">$out") or die "$out: $!";
open(PROTO, ">$proto") or die "$proto: $!";
for $file (<icons/*.png>) {
next if $file =~ /char-/;
$file =~ m#.*/(.*)\.png#;
$base = "Icon_$1";
$base =~ y/-/_/;
open(PNG, $file) or die "$file: $!\n";
$img = newFromPng GD::Image(\*PNG) or die;
$img->trueColor(1);
close PNG;
($width, $height) = $img->getBounds();
die "$file: $width, $height" if ($width != 24) or ($height != 24);
print PROTO "extern unsigned char $base\[24*24*3\];";
print OUT "unsigned char $base\[24*24*3] = {\n";
for($y = 0; $y < 24; $y++) {
for($x = 0; $x < 24; $x++) {
$index = $img->getPixel($x, 23-$y);
($r, $g, $b) = $img->rgb($index);
if($r + $g + $b < 11) {
($r, $g, $b) = (30, 30, 30);
}
printf OUT " 0x%02x, 0x%02x, 0x%02x,\n", $r, $g, $b;
}
}
print OUT "};\n\n";
}

View File

@ -1,28 +0,0 @@
#!/usr/bin/perl
use GD;
for $file (sort <icons/char-*.png>) {
open(PNG, $file) or die "$file: $!\n";
$img = newFromPng GD::Image(\*PNG) or die;
$img->trueColor(1);
close PNG;
($width, $height) = $img->getBounds();
die "$file: $width, $height" if ($width != 16) or ($height != 16);
for($x = 0; $x < 16; $x++) {
for($y = 0; $y < 16; $y++) {
$index = $img->getPixel($x, $y);
($r, $g, $b) = $img->rgb($index);
if($r + $g + $b < 11) {
print " 0, ";
} else {
print "255, ";
}
}
print "\n";
}
print "\n";
}

View File

@ -1,866 +0,0 @@
//-----------------------------------------------------------------------------
// Operations on polygons (planar, of line segment edges).
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
Vector STriangle::Normal(void) {
Vector ab = b.Minus(a), bc = c.Minus(b);
return ab.Cross(bc);
}
double STriangle::MinAltitude(void) {
double altA = a.DistanceToLine(b, c.Minus(b)),
altB = b.DistanceToLine(c, a.Minus(c)),
altC = c.DistanceToLine(a, b.Minus(a));
return min(altA, min(altB, altC));
}
bool STriangle::ContainsPoint(Vector p) {
Vector n = Normal();
if(MinAltitude() < LENGTH_EPS) {
// shouldn't happen; zero-area triangle
return false;
}
return ContainsPointProjd(n.WithMagnitude(1), p);
}
bool STriangle::ContainsPointProjd(Vector n, Vector p) {
Vector ab = b.Minus(a), bc = c.Minus(b), ca = a.Minus(c);
Vector no_ab = n.Cross(ab);
if(no_ab.Dot(p) < no_ab.Dot(a) - LENGTH_EPS) return false;
Vector no_bc = n.Cross(bc);
if(no_bc.Dot(p) < no_bc.Dot(b) - LENGTH_EPS) return false;
Vector no_ca = n.Cross(ca);
if(no_ca.Dot(p) < no_ca.Dot(c) - LENGTH_EPS) return false;
return true;
}
void STriangle::FlipNormal(void) {
SWAP(Vector, a, b);
SWAP(Vector, an, bn);
}
STriangle STriangle::From(STriMeta meta, Vector a, Vector b, Vector c) {
STriangle tr;
ZERO(&tr);
tr.meta = meta;
tr.a = a;
tr.b = b;
tr.c = c;
return tr;
}
SEdge SEdge::From(Vector a, Vector b) {
SEdge se = { 0, 0, 0, a, b };
return se;
}
bool SEdge::EdgeCrosses(Vector ea, Vector eb, Vector *ppi, SPointList *spl) {
Vector d = eb.Minus(ea);
double t_eps = LENGTH_EPS/d.Magnitude();
double dist_a, dist_b;
double t, tthis;
bool skew;
Vector pi;
bool inOrEdge0, inOrEdge1;
Vector dthis = b.Minus(a);
double tthis_eps = LENGTH_EPS/dthis.Magnitude();
if((ea.Equals(a) && eb.Equals(b)) ||
(eb.Equals(a) && ea.Equals(b)))
{
if(ppi) *ppi = a;
if(spl) spl->Add(a);
return true;
}
dist_a = a.DistanceToLine(ea, d),
dist_b = b.DistanceToLine(ea, d);
// Can't just test if dist_a equals dist_b; they could be on opposite
// sides, since it's unsigned.
double m = sqrt(d.Magnitude()*dthis.Magnitude());
if(sqrt(fabs(d.Dot(dthis))) > (m - LENGTH_EPS)) {
// The edges are parallel.
if(fabs(dist_a) > LENGTH_EPS) {
// and not coincident, so can't be interesecting
return false;
}
// The edges are coincident. Make sure that neither endpoint lies
// on the other
bool inters = false;
double t;
t = a.Minus(ea).DivPivoting(d);
if(t > t_eps && t < (1 - t_eps)) inters = true;
t = b.Minus(ea).DivPivoting(d);
if(t > t_eps && t < (1 - t_eps)) inters = true;
t = ea.Minus(a).DivPivoting(dthis);
if(t > tthis_eps && t < (1 - tthis_eps)) inters = true;
t = eb.Minus(a).DivPivoting(dthis);
if(t > tthis_eps && t < (1 - tthis_eps)) inters = true;
if(inters) {
if(ppi) *ppi = a;
if(spl) spl->Add(a);
return true;
} else {
// So coincident but disjoint, okay.
return false;
}
}
// Lines are not parallel, so look for an intersection.
pi = Vector::AtIntersectionOfLines(ea, eb, a, b,
&skew,
&t, &tthis);
if(skew) return false;
inOrEdge0 = (t > -t_eps) && (t < (1 + t_eps));
inOrEdge1 = (tthis > -tthis_eps) && (tthis < (1 + tthis_eps));
if(inOrEdge0 && inOrEdge1) {
if(a.Equals(ea) || b.Equals(ea) ||
a.Equals(eb) || b.Equals(eb))
{
// Not an intersection if we share an endpoint with an edge
return false;
}
// But it's an intersection if a vertex of one edge lies on the
// inside of the other (or if they cross away from either's
// vertex).
if(ppi) *ppi = pi;
if(spl) spl->Add(pi);
return true;
}
return false;
}
void SEdgeList::Clear(void) {
l.Clear();
}
void SEdgeList::AddEdge(Vector a, Vector b, int auxA, int auxB) {
SEdge e; ZERO(&e);
e.a = a;
e.b = b;
e.auxA = auxA;
e.auxB = auxB;
l.Add(&e);
}
bool SEdgeList::AssembleContour(Vector first, Vector last, SContour *dest,
SEdge *errorAt, bool keepDir)
{
int i;
dest->AddPoint(first);
dest->AddPoint(last);
do {
for(i = 0; i < l.n; i++) {
SEdge *se = &(l.elem[i]);
if(se->tag) continue;
if(se->a.Equals(last)) {
dest->AddPoint(se->b);
last = se->b;
se->tag = 1;
break;
}
// Don't allow backwards edges if keepDir is true.
if(!keepDir && se->b.Equals(last)) {
dest->AddPoint(se->a);
last = se->a;
se->tag = 1;
break;
}
}
if(i >= l.n) {
// Couldn't assemble a closed contour; mark where.
if(errorAt) {
errorAt->a = first;
errorAt->b = last;
}
return false;
}
} while(!last.Equals(first));
return true;
}
bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir) {
dest->Clear();
bool allClosed = true;
for(;;) {
Vector first, last;
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;
break;
}
}
if(i >= l.n) {
return allClosed;
}
// 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))
{
allClosed = false;
}
// But continue assembling, even if some of the contours are open
}
}
//-----------------------------------------------------------------------------
// Test if the specified edge crosses any of the edges in our list. Two edges
// are not considered to cross if they share an endpoint (within LENGTH_EPS),
// but they are considered to cross if they are coincident and overlapping.
// If pi is not NULL, then a crossing is returned in that.
//-----------------------------------------------------------------------------
int SEdgeList::AnyEdgeCrossings(Vector a, Vector b,
Vector *ppi, SPointList *spl)
{
int cnt = 0;
SEdge *se;
for(se = l.First(); se; se = l.NextAfter(se)) {
if(se->EdgeCrosses(a, b, ppi, spl)) {
cnt++;
}
}
return cnt;
}
//-----------------------------------------------------------------------------
// Returns true if the intersecting edge list contains an edge that shares
// an endpoint with one of our edges.
//-----------------------------------------------------------------------------
bool SEdgeList::ContainsEdgeFrom(SEdgeList *sel) {
SEdge *se;
for(se = l.First(); se; se = l.NextAfter(se)) {
if(sel->ContainsEdge(se)) return true;
}
return false;
}
bool SEdgeList::ContainsEdge(SEdge *set) {
SEdge *se;
for(se = l.First(); se; se = l.NextAfter(se)) {
if((se->a).Equals(set->a) && (se->b).Equals(set->b)) return true;
if((se->b).Equals(set->a) && (se->a).Equals(set->b)) return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Remove unnecessary edges: if two are anti-parallel then remove both, and if
// two are parallel then remove one.
//-----------------------------------------------------------------------------
void SEdgeList::CullExtraneousEdges(void) {
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]);
if((set->a).Equals(se->a) && (set->b).Equals(se->b)) {
// Two parallel edges exist; so keep only the first one.
set->tag = 1;
}
if((set->a).Equals(se->b) && (set->b).Equals(se->a)) {
// Two anti-parallel edges exist; so keep neither.
se->tag = 1;
set->tag = 1;
}
}
}
l.RemoveTagged();
}
//-----------------------------------------------------------------------------
// Make a kd-tree of edges. This is used for O(log(n)) implementations of stuff
// that would naively be O(n).
//-----------------------------------------------------------------------------
SKdNodeEdges *SKdNodeEdges::Alloc(void) {
return (SKdNodeEdges *)AllocTemporary(sizeof(SKdNodeEdges));
}
SEdgeLl *SEdgeLl::Alloc(void) {
return (SEdgeLl *)AllocTemporary(sizeof(SEdgeLl));
}
SKdNodeEdges *SKdNodeEdges::From(SEdgeList *sel) {
SEdgeLl *sell = NULL;
SEdge *se;
for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) {
SEdgeLl *n = SEdgeLl::Alloc();
n->se = se;
n->next = sell;
sell = n;
}
return SKdNodeEdges::From(sell);
}
SKdNodeEdges *SKdNodeEdges::From(SEdgeLl *sell) {
SKdNodeEdges *n = SKdNodeEdges::Alloc();
// Compute the midpoints (just mean, though median would be better) of
// each component.
Vector ptAve = Vector::From(0, 0, 0);
SEdgeLl *flip;
int totaln = 0;
for(flip = sell; flip; flip = flip->next) {
ptAve = ptAve.Plus(flip->se->a);
ptAve = ptAve.Plus(flip->se->b);
totaln++;
}
ptAve = ptAve.ScaledBy(1.0 / (2*totaln));
// For each component, see how it splits.
int ltln[3] = { 0, 0, 0 }, gtln[3] = { 0, 0, 0 };
double badness[3];
for(flip = sell; flip; flip = flip->next) {
for(int i = 0; i < 3; i++) {
if(flip->se->a.Element(i) < ptAve.Element(i) + KDTREE_EPS ||
flip->se->b.Element(i) < ptAve.Element(i) + KDTREE_EPS)
{
ltln[i]++;
}
if(flip->se->a.Element(i) > ptAve.Element(i) - KDTREE_EPS ||
flip->se->b.Element(i) > ptAve.Element(i) - KDTREE_EPS)
{
gtln[i]++;
}
}
}
for(int i = 0; i < 3; i++) {
badness[i] = pow((double)ltln[i], 4) + pow((double)gtln[i], 4);
}
// Choose the least bad coordinate to split along.
if(badness[0] < badness[1] && badness[0] < badness[2]) {
n->which = 0;
} else if(badness[1] < badness[2]) {
n->which = 1;
} else {
n->which = 2;
}
n->c = ptAve.Element(n->which);
if(totaln < 3 || totaln == gtln[n->which] || totaln == ltln[n->which]) {
n->edges = sell;
// and we're a leaf node
return n;
}
// Sort the edges according to which side(s) of the split plane they're on.
SEdgeLl *gtl = NULL, *ltl = NULL;
for(flip = sell; flip; flip = flip->next) {
if(flip->se->a.Element(n->which) < n->c + KDTREE_EPS ||
flip->se->b.Element(n->which) < n->c + KDTREE_EPS)
{
SEdgeLl *selln = SEdgeLl::Alloc();
selln->se = flip->se;
selln->next = ltl;
ltl = selln;
}
if(flip->se->a.Element(n->which) > n->c - KDTREE_EPS ||
flip->se->b.Element(n->which) > n->c - KDTREE_EPS)
{
SEdgeLl *selln = SEdgeLl::Alloc();
selln->se = flip->se;
selln->next = gtl;
gtl = selln;
}
}
n->lt = SKdNodeEdges::From(ltl);
n->gt = SKdNodeEdges::From(gtl);
return n;
}
int SKdNodeEdges::AnyEdgeCrossings(Vector a, Vector b, int cnt,
Vector *pi, SPointList *spl)
{
int inters = 0;
if(gt && lt) {
if(a.Element(which) < c + KDTREE_EPS ||
b.Element(which) < c + KDTREE_EPS)
{
inters += lt->AnyEdgeCrossings(a, b, cnt, pi, spl);
}
if(a.Element(which) > c - KDTREE_EPS ||
b.Element(which) > c - KDTREE_EPS)
{
inters += gt->AnyEdgeCrossings(a, b, cnt, pi, spl);
}
} else {
SEdgeLl *sell;
for(sell = edges; sell; sell = sell->next) {
SEdge *se = sell->se;
if(se->tag == cnt) continue;
if(se->EdgeCrosses(a, b, pi, spl)) {
inters++;
}
se->tag = cnt;
}
}
return inters;
}
//-----------------------------------------------------------------------------
// 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);
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)) {
// The previous segment joins up to us; so merge it into us.
prev->tag = 1;
now->a = prev->a;
}
}
l.RemoveTagged();
}
void SPointList::Clear(void) {
l.Clear();
}
bool SPointList::ContainsPoint(Vector pt) {
return (IndexForPoint(pt) >= 0);
}
int SPointList::IndexForPoint(Vector pt) {
int i;
for(i = 0; i < l.n; i++) {
SPoint *p = &(l.elem[i]);
if(pt.Equals(p->p)) {
return i;
}
}
// Not found, so return negative to indicate that.
return -1;
}
void SPointList::IncrementTagFor(Vector pt) {
SPoint *p;
for(p = l.First(); p; p = l.NextAfter(p)) {
if(pt.Equals(p->p)) {
(p->tag)++;
return;
}
}
SPoint pa;
pa.p = pt;
pa.tag = 1;
l.Add(&pa);
}
void SPointList::Add(Vector pt) {
SPoint p;
ZERO(&p);
p.p = pt;
l.Add(&p);
}
void SContour::AddPoint(Vector p) {
SPoint sp;
sp.tag = 0;
sp.p = p;
l.Add(&sp);
}
void SContour::MakeEdgesInto(SEdgeList *el) {
int i;
for(i = 0; i < (l.n - 1); i++) {
el->AddEdge(l.elem[i].p, l.elem[i+1].p);
}
}
void SContour::CopyInto(SContour *dest) {
SPoint *sp;
for(sp = l.First(); sp; sp = l.NextAfter(sp)) {
dest->AddPoint(sp->p);
}
}
void SContour::FindPointWithMinX(void) {
SPoint *sp;
xminPt = Vector::From(1e10, 1e10, 1e10);
for(sp = l.First(); sp; sp = l.NextAfter(sp)) {
if(sp->p.x < xminPt.x) {
xminPt = sp->p;
}
}
}
Vector SContour::ComputeNormal(void) {
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 nt = u.Cross(v);
if(nt.Magnitude() > n.Magnitude()) {
n = nt;
}
}
return n.WithMagnitude(1);
}
Vector SContour::AnyEdgeMidpoint(void) {
if(l.n < 2) oops();
return ((l.elem[0].p).Plus(l.elem[1].p)).ScaledBy(0.5);
}
bool SContour::IsClockwiseProjdToNormal(Vector n) {
// Degenerate things might happen as we draw; doesn't really matter
// what we do then.
if(n.Magnitude() < 0.01) return true;
return (SignedAreaProjdToNormal(n) < 0);
}
double SContour::SignedAreaProjdToNormal(Vector n) {
// An arbitrary 2d coordinate system that has n as its normal
Vector u = n.Normal(0);
Vector v = n.Normal(1);
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);
area += ((v0 + v1)/2)*(u1 - u0);
}
return area;
}
bool SContour::ContainsPointProjdToNormal(Vector n, Vector p) {
Vector u = n.Normal(0);
Vector v = n.Normal(1);
double up = p.Dot(u);
double vp = p.Dot(v);
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);
// 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);
if ((((va <= vp) && (vp < vb)) ||
((vb <= vp) && (vp < va))) &&
(up < (ub - ua) * (vp - va) / (vb - va) + ua))
{
inside = !inside;
}
}
return inside;
}
void SContour::Reverse(void) {
l.Reverse();
}
void SPolygon::Clear(void) {
int i;
for(i = 0; i < l.n; i++) {
(l.elem[i]).l.Clear();
}
l.Clear();
}
void SPolygon::AddEmptyContour(void) {
SContour c;
memset(&c, 0, sizeof(c));
l.Add(&c);
}
void SPolygon::MakeEdgesInto(SEdgeList *el) {
int i;
for(i = 0; i < l.n; i++) {
(l.elem[i]).MakeEdgesInto(el);
}
}
Vector SPolygon::ComputeNormal(void) {
if(l.n < 1) return Vector::From(0, 0, 0);
return (l.elem[0]).ComputeNormal();
}
double SPolygon::SignedArea(void) {
SContour *sc;
double area = 0;
// This returns the true area only if the contours are all oriented
// correctly, with the holes backwards from the outer contours.
for(sc = l.First(); sc; sc = l.NextAfter(sc)) {
area += sc->SignedAreaProjdToNormal(normal);
}
return area;
}
bool SPolygon::ContainsPoint(Vector p) {
return (WindingNumberForPoint(p) % 2) == 1;
}
int SPolygon::WindingNumberForPoint(Vector p) {
int winding = 0;
int i;
for(i = 0; i < l.n; i++) {
SContour *sc = &(l.elem[i]);
if(sc->ContainsPointProjdToNormal(normal, p)) {
winding++;
}
}
return winding;
}
void SPolygon::FixContourDirections(void) {
// At output, the contour's tag will be 1 if we reversed it, else 0.
l.ClearTags();
// Outside curve looks counterclockwise, projected against our normal.
int i, j;
for(i = 0; i < l.n; i++) {
SContour *sc = &(l.elem[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);
sc->timesEnclosed = 0;
bool outer = true;
for(j = 0; j < l.n; j++) {
if(i == j) continue;
SContour *sct = &(l.elem[j]);
if(sct->ContainsPointProjdToNormal(normal, pt)) {
outer = !outer;
(sc->timesEnclosed)++;
}
}
bool clockwise = sc->IsClockwiseProjdToNormal(normal);
if(clockwise && outer || (!clockwise && !outer)) {
sc->Reverse();
sc->tag = 1;
}
}
}
bool SPolygon::IsEmpty(void) {
if(l.n == 0 || l.elem[0].l.n == 0) return true;
return false;
}
Vector SPolygon::AnyPoint(void) {
if(IsEmpty()) oops();
return l.elem[0].l.elem[0].p;
}
bool SPolygon::SelfIntersecting(Vector *intersectsAt) {
SEdgeList el;
ZERO(&el);
MakeEdgesInto(&el);
SKdNodeEdges *kdtree = SKdNodeEdges::From(&el);
int cnt = 1;
el.l.ClearTags();
bool ret = false;
SEdge *se;
for(se = el.l.First(); se; se = el.l.NextAfter(se)) {
int inters = kdtree->AnyEdgeCrossings(se->a, se->b, cnt, intersectsAt);
if(inters != 1) {
ret = true;
break;
}
cnt++;
}
el.Clear();
return ret;
}
//-----------------------------------------------------------------------------
// Low-quality routines to cutter radius compensate a polygon. Assumes the
// polygon is in the xy plane, and the contours all go in the right direction
// with respect to normal (0, 0, -1).
//-----------------------------------------------------------------------------
void SPolygon::OffsetInto(SPolygon *dest, double r) {
int i;
dest->Clear();
for(i = 0; i < l.n; i++) {
SContour *sc = &(l.elem[i]);
dest->AddEmptyContour();
sc->OffsetInto(&(dest->l.elem[dest->l.n-1]), r);
}
}
//-----------------------------------------------------------------------------
// Calculate the intersection point of two lines. Returns true for success,
// false if they're parallel.
//-----------------------------------------------------------------------------
static bool IntersectionOfLines(double x0A, double y0A, double dxA, double dyA,
double x0B, double y0B, double dxB, double dyB,
double *xi, double *yi)
{
double A[2][2];
double b[2];
// The line is given to us in the form:
// (x(t), y(t)) = (x0, y0) + t*(dx, dy)
// so first rewrite it as
// (x - x0, y - y0) dot (dy, -dx) = 0
// x*dy - x0*dy - y*dx + y0*dx = 0
// x*dy - y*dx = (x0*dy - y0*dx)
// So write the matrix, pre-pivoted.
if(fabs(dyA) > fabs(dyB)) {
A[0][0] = dyA; A[0][1] = -dxA; b[0] = x0A*dyA - y0A*dxA;
A[1][0] = dyB; A[1][1] = -dxB; b[1] = x0B*dyB - y0B*dxB;
} else {
A[1][0] = dyA; A[1][1] = -dxA; b[1] = x0A*dyA - y0A*dxA;
A[0][0] = dyB; A[0][1] = -dxB; b[0] = x0B*dyB - y0B*dxB;
}
// Check the determinant; if it's zero then no solution.
if(fabs(A[0][0]*A[1][1] - A[0][1]*A[1][0]) < LENGTH_EPS) {
return false;
}
// Solve
double v = A[1][0] / A[0][0];
A[1][0] -= A[0][0]*v;
A[1][1] -= A[0][1]*v;
b[1] -= b[0]*v;
// Back-substitute.
*yi = b[1] / A[1][1];
*xi = (b[0] - A[0][1]*(*yi)) / A[0][0];
return true;
}
void SContour::OffsetInto(SContour *dest, double r) {
int i;
for(i = 0; i < l.n; i++) {
Vector a, b, c;
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;
dp = a.Minus(b);
thetap = atan2(dp.y, dp.x);
dn = b.Minus(c);
thetan = atan2(dn.y, dn.x);
// A short line segment in a badly-generated polygon might look
// okay but screw up our sense of direction.
if(dp.Magnitude() < LENGTH_EPS || dn.Magnitude() < LENGTH_EPS) {
continue;
}
if(thetan > thetap && (thetan - thetap) > PI) {
thetap += 2*PI;
}
if(thetan < thetap && (thetap - thetan) > PI) {
thetan += 2*PI;
}
if(fabs(thetan - thetap) < (1*PI)/180) {
Vector p = { b.x - r*sin(thetap), b.y + r*cos(thetap) };
dest->AddPoint(p);
} else if(thetan < thetap) {
// This is an inside corner. We have two edges, Ep and En. Move
// out from their intersection by radius, normal to En, and
// then draw a line parallel to En. Move out from their
// intersection by radius, normal to Ep, and then draw a second
// line parallel to Ep. The point that we want to generate is
// the intersection of these two lines--it removes as much
// material as we can without removing any that we shouldn't.
double px0, py0, pdx, pdy;
double nx0, ny0, ndx, ndy;
double x, y;
px0 = b.x - r*sin(thetap);
py0 = b.y + r*cos(thetap);
pdx = cos(thetap);
pdy = sin(thetap);
nx0 = b.x - r*sin(thetan);
ny0 = b.y + r*cos(thetan);
ndx = cos(thetan);
ndy = sin(thetan);
IntersectionOfLines(px0, py0, pdx, pdy,
nx0, ny0, ndx, ndy,
&x, &y);
dest->AddPoint(Vector::From(x, y, 0));
} else {
if(fabs(thetap - thetan) < (6*PI)/180) {
Vector pp = { b.x - r*sin(thetap),
b.y + r*cos(thetap), 0 };
dest->AddPoint(pp);
Vector pn = { b.x - r*sin(thetan),
b.y + r*cos(thetan), 0 };
dest->AddPoint(pn);
} else {
double theta;
for(theta = thetap; theta <= thetan; theta += (6*PI)/180) {
Vector p = { b.x - r*sin(theta),
b.y + r*cos(theta), 0 };
dest->AddPoint(p);
}
}
}
}
}

View File

@ -1,199 +0,0 @@
//-----------------------------------------------------------------------------
// Implementation of our Request class; a request is a user-created thing
// that will generate an entity (line, curve) when the sketch is generated,
// in the same way that other entities are generated automatically, like
// by an extrude or a step and repeat.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
const hRequest Request::HREQUEST_REFERENCE_XY = { 1 };
const hRequest Request::HREQUEST_REFERENCE_YZ = { 2 };
const hRequest Request::HREQUEST_REFERENCE_ZX = { 3 };
const EntReqTable::TableEntry EntReqTable::Table[] = {
// request type entity type pts xtra? norml dist description
{ Request::WORKPLANE, Entity::WORKPLANE, 1, false, true, false, "workplane" },
{ Request::DATUM_POINT, 0, 1, false, false, false, "datum-point" },
{ Request::LINE_SEGMENT, Entity::LINE_SEGMENT, 2, false, false, false, "line-segment" },
{ Request::CUBIC, Entity::CUBIC, 4, true, false, false, "cubic-bezier" },
{ Request::CUBIC_PERIODIC, Entity::CUBIC_PERIODIC, 3, true, false, false, "periodic-cubic" },
{ Request::CIRCLE, Entity::CIRCLE, 1, false, true, true, "circle" },
{ Request::ARC_OF_CIRCLE, Entity::ARC_OF_CIRCLE, 3, false, true, false, "arc-of-circle" },
{ Request::TTF_TEXT, Entity::TTF_TEXT, 2, false, true, false, "ttf-text" },
{ 0 },
};
char *EntReqTable::DescriptionForRequest(int req) {
for(int i = 0; Table[i].reqType; i++) {
if(req == Table[i].reqType) {
return Table[i].description;
}
}
return "???";
}
void EntReqTable::CopyEntityInfo(const TableEntry *te, int extraPoints,
int *ent, int *req, int *pts, bool *hasNormal, bool *hasDistance)
{
int points = te->points;
if(te->useExtraPoints) points += extraPoints;
if(ent) *ent = te->entType;
if(req) *req = te->reqType;
if(pts) *pts = points;
if(hasNormal) *hasNormal = te->hasNormal;
if(hasDistance) *hasDistance = te->hasDistance;
}
bool EntReqTable::GetRequestInfo(int req, int extraPoints,
int *ent, int *pts, bool *hasNormal, bool *hasDistance)
{
for(int i = 0; Table[i].reqType; i++) {
const TableEntry *te = &(Table[i]);
if(req == te->reqType) {
CopyEntityInfo(te, extraPoints,
ent, NULL, pts, hasNormal, hasDistance);
return true;
}
}
return false;
}
bool EntReqTable::GetEntityInfo(int ent, int extraPoints,
int *req, int *pts, bool *hasNormal, bool *hasDistance)
{
for(int i = 0; Table[i].reqType; i++) {
const TableEntry *te = &(Table[i]);
if(ent == te->entType) {
CopyEntityInfo(te, extraPoints,
NULL, req, pts, hasNormal, hasDistance);
return true;
}
}
return false;
}
int EntReqTable::GetRequestForEntity(int ent) {
int req;
GetEntityInfo(ent, 0, &req, NULL, NULL, NULL);
return req;
}
void Request::Generate(IdList<Entity,hEntity> *entity,
IdList<Param,hParam> *param)
{
int points = 0;
int et = 0;
bool hasNormal = false;
bool hasDistance = false;
int i;
Entity e;
ZERO(&e);
EntReqTable::GetRequestInfo(type, extraPoints,
&et, &points, &hasNormal, &hasDistance);
// Generate the entity that's specific to this request.
e.type = et;
e.extraPoints = extraPoints;
e.group = group;
e.style = style;
e.workplane = workplane;
e.construction = construction;
e.str.strcpy(str.str);
e.font.strcpy(font.str);
e.h = h.entity(0);
// And generate entities for the points
for(i = 0; i < points; i++) {
Entity p;
memset(&p, 0, sizeof(p));
p.workplane = workplane;
// points start from entity 1, except for datum point case
p.h = h.entity(i+(et ? 1 : 0));
p.group = group;
p.style = style;
if(workplane.v == Entity::FREE_IN_3D.v) {
p.type = Entity::POINT_IN_3D;
// params for x y z
p.param[0] = AddParam(param, h.param(16 + 3*i + 0));
p.param[1] = AddParam(param, h.param(16 + 3*i + 1));
p.param[2] = AddParam(param, h.param(16 + 3*i + 2));
} else {
p.type = Entity::POINT_IN_2D;
// params for u v
p.param[0] = AddParam(param, h.param(16 + 3*i + 0));
p.param[1] = AddParam(param, h.param(16 + 3*i + 1));
}
entity->Add(&p);
e.point[i] = p.h;
}
if(hasNormal) {
Entity n;
memset(&n, 0, sizeof(n));
n.workplane = workplane;
n.h = h.entity(32);
n.group = group;
n.style = style;
if(workplane.v == Entity::FREE_IN_3D.v) {
n.type = Entity::NORMAL_IN_3D;
n.param[0] = AddParam(param, h.param(32+0));
n.param[1] = AddParam(param, h.param(32+1));
n.param[2] = AddParam(param, h.param(32+2));
n.param[3] = AddParam(param, h.param(32+3));
} else {
n.type = Entity::NORMAL_IN_2D;
// and this is just a copy of the workplane quaternion,
// so no params required
}
if(points < 1) oops();
// The point determines where the normal gets displayed on-screen;
// it's entirely cosmetic.
n.point[0] = e.point[0];
entity->Add(&n);
e.normal = n.h;
}
if(hasDistance) {
Entity d;
memset(&d, 0, sizeof(d));
d.workplane = workplane;
d.h = h.entity(64);
d.group = group;
d.style = style;
d.type = Entity::DISTANCE;
d.param[0] = AddParam(param, h.param(64));
entity->Add(&d);
e.distance = d.h;
}
if(et) entity->Add(&e);
}
char *Request::DescriptionString(void) {
char *s;
if(h.v == Request::HREQUEST_REFERENCE_XY.v) {
s = "#XY";
} else if(h.v == Request::HREQUEST_REFERENCE_YZ.v) {
s = "#YZ";
} else if(h.v == Request::HREQUEST_REFERENCE_ZX.v) {
s = "#ZX";
} else {
s = EntReqTable::DescriptionForRequest(type);
}
static char ret[100];
sprintf(ret, "r%03x-%s", h.v, s);
return ret;
}
hParam Request::AddParam(IdList<Param,hParam> *param, hParam hp) {
Param pa;
memset(&pa, 0, sizeof(pa));
pa.h = hp;
param->Add(&pa);
return hp;
}

View File

@ -1,736 +0,0 @@
//-----------------------------------------------------------------------------
// Entry point in to the program, our registry-stored settings and top-level
// housekeeping when we open, save, and create new files.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"
SolveSpace SS;
Sketch SK;
void SolveSpace::Init(char *cmdLine) {
SS.tangentArcRadius = 10.0;
// Then, load the registry settings.
int i;
// Default list of colors for the model material
modelColor[0] = CnfThawDWORD(RGB(150, 150, 150), "ModelColor_0");
modelColor[1] = CnfThawDWORD(RGB(100, 100, 100), "ModelColor_1");
modelColor[2] = CnfThawDWORD(RGB( 30, 30, 30), "ModelColor_2");
modelColor[3] = CnfThawDWORD(RGB(150, 0, 0), "ModelColor_3");
modelColor[4] = CnfThawDWORD(RGB( 0, 100, 0), "ModelColor_4");
modelColor[5] = CnfThawDWORD(RGB( 0, 80, 80), "ModelColor_5");
modelColor[6] = CnfThawDWORD(RGB( 0, 0, 130), "ModelColor_6");
modelColor[7] = CnfThawDWORD(RGB( 80, 0, 80), "ModelColor_7");
// Light intensities
lightIntensity[0] = CnfThawFloat(1.0f, "LightIntensity_0");
lightIntensity[1] = CnfThawFloat(0.5f, "LightIntensity_1");
ambientIntensity = 0.3; // no setting for that yet
// Light positions
lightDir[0].x = CnfThawFloat(-1.0f, "LightDir_0_Right" );
lightDir[0].y = CnfThawFloat( 1.0f, "LightDir_0_Up" );
lightDir[0].z = CnfThawFloat( 0.0f, "LightDir_0_Forward" );
lightDir[1].x = CnfThawFloat( 1.0f, "LightDir_1_Right" );
lightDir[1].y = CnfThawFloat( 0.0f, "LightDir_1_Up" );
lightDir[1].z = CnfThawFloat( 0.0f, "LightDir_1_Forward" );
// Chord tolerance
chordTol = CnfThawFloat(2.0f, "ChordTolerance");
// Max pwl segments to generate
maxSegments = CnfThawDWORD(10, "MaxSegments");
// View units
viewUnits = (Unit)CnfThawDWORD((DWORD)UNIT_MM, "ViewUnits");
// Number of digits after the decimal point
afterDecimalMm = CnfThawDWORD(2, "AfterDecimalMm");
afterDecimalInch = CnfThawDWORD(3, "AfterDecimalInch");
// Camera tangent (determines perspective)
cameraTangent = CnfThawFloat(0.3f/1e3f, "CameraTangent");
// Grid spacing
gridSpacing = CnfThawFloat(5.0f, "GridSpacing");
// Export scale factor
exportScale = CnfThawFloat(1.0f, "ExportScale");
// Export offset (cutter radius comp)
exportOffset = CnfThawFloat(0.0f, "ExportOffset");
// Rewrite exported colors close to white into black (assuming white bg)
fixExportColors = CnfThawDWORD(1, "FixExportColors");
// Draw back faces of triangles (when mesh is leaky/self-intersecting)
drawBackFaces = CnfThawDWORD(1, "DrawBackFaces");
// Check that contours are closed and not self-intersecting
checkClosedContour = CnfThawDWORD(1, "CheckClosedContour");
// Export shaded triangles in a 2d view
exportShadedTriangles = CnfThawDWORD(1, "ExportShadedTriangles");
// Export pwl curves (instead of exact) always
exportPwlCurves = CnfThawDWORD(0, "ExportPwlCurves");
// Background color on-screen
backgroundColor = CnfThawDWORD(RGB(0, 0, 0), "BackgroundColor");
// Whether export canvas size is fixed or derived from bbox
exportCanvasSizeAuto = CnfThawDWORD(1, "ExportCanvasSizeAuto");
// Margins for automatic canvas size
exportMargin.left = CnfThawFloat(5.0f, "ExportMargin_Left");
exportMargin.right = CnfThawFloat(5.0f, "ExportMargin_Right");
exportMargin.bottom = CnfThawFloat(5.0f, "ExportMargin_Bottom");
exportMargin.top = CnfThawFloat(5.0f, "ExportMargin_Top");
// Dimensions for fixed canvas size
exportCanvas.width = CnfThawFloat(100.0f, "ExportCanvas_Width");
exportCanvas.height = CnfThawFloat(100.0f, "ExportCanvas_Height");
exportCanvas.dx = CnfThawFloat( 5.0f, "ExportCanvas_Dx");
exportCanvas.dy = CnfThawFloat( 5.0f, "ExportCanvas_Dy");
// Extra parameters when exporting G code
gCode.depth = CnfThawFloat(10.0f, "GCode_Depth");
gCode.passes = CnfThawDWORD(1, "GCode_Passes");
gCode.feed = CnfThawFloat(10.0f, "GCode_Feed");
gCode.plungeFeed = CnfThawFloat(10.0f, "GCode_PlungeFeed");
// Show toolbar in the graphics window
showToolbar = CnfThawDWORD(1, "ShowToolbar");
// Recent files menus
for(i = 0; i < MAX_RECENT; i++) {
char name[100];
sprintf(name, "RecentFile_%d", i);
strcpy(RecentFile[i], "");
CnfThawString(RecentFile[i], MAX_PATH, name);
}
RefreshRecentMenus();
// The default styles (colors, line widths, etc.) are also stored in the
// configuration file, but we will automatically load those as we need
// them.
// Start with either an empty file, or the file specified on the
// command line.
NewFile();
AfterNewFile();
if(strlen(cmdLine) != 0) {
if(LoadFromFile(cmdLine)) {
strcpy(saveFile, cmdLine);
} else {
NewFile();
}
}
AfterNewFile();
}
void SolveSpace::Exit(void) {
int i;
char name[100];
// Recent files
for(i = 0; i < MAX_RECENT; i++) {
sprintf(name, "RecentFile_%d", i);
CnfFreezeString(RecentFile[i], name);
}
// Model colors
for(i = 0; i < MODEL_COLORS; i++) {
sprintf(name, "ModelColor_%d", i);
CnfFreezeDWORD(modelColor[i], name);
}
// Light intensities
CnfFreezeFloat((float)lightIntensity[0], "LightIntensity_0");
CnfFreezeFloat((float)lightIntensity[1], "LightIntensity_1");
// Light directions
CnfFreezeFloat((float)lightDir[0].x, "LightDir_0_Right");
CnfFreezeFloat((float)lightDir[0].y, "LightDir_0_Up");
CnfFreezeFloat((float)lightDir[0].z, "LightDir_0_Forward");
CnfFreezeFloat((float)lightDir[1].x, "LightDir_1_Right");
CnfFreezeFloat((float)lightDir[1].y, "LightDir_1_Up");
CnfFreezeFloat((float)lightDir[1].z, "LightDir_1_Forward");
// Chord tolerance
CnfFreezeFloat((float)chordTol, "ChordTolerance");
// Max pwl segments to generate
CnfFreezeDWORD((DWORD)maxSegments, "MaxSegments");
// View units
CnfFreezeDWORD((DWORD)viewUnits, "ViewUnits");
// Number of digits after the decimal point
CnfFreezeDWORD((DWORD)afterDecimalMm, "AfterDecimalMm");
CnfFreezeDWORD((DWORD)afterDecimalInch, "AfterDecimalInch");
// Camera tangent (determines perspective)
CnfFreezeFloat((float)cameraTangent, "CameraTangent");
// Grid spacing
CnfFreezeFloat(gridSpacing, "GridSpacing");
// Export scale (a float, stored as a DWORD)
CnfFreezeFloat(exportScale, "ExportScale");
// Export offset (cutter radius comp)
CnfFreezeFloat(exportOffset, "ExportOffset");
// Rewrite exported colors close to white into black (assuming white bg)
CnfFreezeDWORD(fixExportColors, "FixExportColors");
// Draw back faces of triangles (when mesh is leaky/self-intersecting)
CnfFreezeDWORD(drawBackFaces, "DrawBackFaces");
// Check that contours are closed and not self-intersecting
CnfFreezeDWORD(checkClosedContour, "CheckClosedContour");
// Export shaded triangles in a 2d view
CnfFreezeDWORD(exportShadedTriangles, "ExportShadedTriangles");
// Export pwl curves (instead of exact) always
CnfFreezeDWORD(exportPwlCurves, "ExportPwlCurves");
// Background color on-screen
CnfFreezeDWORD(backgroundColor, "BackgroundColor");
// Whether export canvas size is fixed or derived from bbox
CnfFreezeDWORD(exportCanvasSizeAuto, "ExportCanvasSizeAuto");
// Margins for automatic canvas size
CnfFreezeFloat(exportMargin.left, "ExportMargin_Left");
CnfFreezeFloat(exportMargin.right, "ExportMargin_Right");
CnfFreezeFloat(exportMargin.bottom, "ExportMargin_Bottom");
CnfFreezeFloat(exportMargin.top, "ExportMargin_Top");
// Dimensions for fixed canvas size
CnfFreezeFloat(exportCanvas.width, "ExportCanvas_Width");
CnfFreezeFloat(exportCanvas.height, "ExportCanvas_Height");
CnfFreezeFloat(exportCanvas.dx, "ExportCanvas_Dx");
CnfFreezeFloat(exportCanvas.dy, "ExportCanvas_Dy");
// Extra parameters when exporting G code
CnfFreezeFloat(gCode.depth, "GCode_Depth");
CnfFreezeDWORD(gCode.passes, "GCode_Passes");
CnfFreezeFloat(gCode.feed, "GCode_Feed");
CnfFreezeFloat(gCode.plungeFeed, "GCode_PlungeFeed");
// Show toolbar in the graphics window
CnfFreezeDWORD(showToolbar, "ShowToolbar");
// And the default styles, colors and line widths and such.
Style::FreezeDefaultStyles();
ExitNow();
}
void SolveSpace::DoLater(void) {
if(later.generateAll) GenerateAll();
if(later.showTW) TW.Show();
ZERO(&later);
}
double SolveSpace::MmPerUnit(void) {
if(viewUnits == UNIT_INCHES) {
return 25.4;
} else {
return 1.0;
}
}
char *SolveSpace::UnitName(void) {
if(viewUnits == UNIT_INCHES) {
return "inch";
} else {
return "mm";
}
}
char *SolveSpace::MmToString(double v) {
static int WhichBuf;
static char Bufs[8][128];
char fmt[128];
WhichBuf++;
if(WhichBuf >= 8 || WhichBuf < 0) WhichBuf = 0;
char *s = Bufs[WhichBuf];
if(viewUnits == UNIT_INCHES) {
sprintf(fmt, "%%.%df", afterDecimalInch);
sprintf(s, fmt, v/25.4);
} else {
sprintf(fmt, "%%.%df", afterDecimalMm);
sprintf(s, fmt, v);
}
return s;
}
double SolveSpace::ExprToMm(Expr *e) {
return (e->Eval()) * MmPerUnit();
}
double SolveSpace::StringToMm(char *str) {
return atof(str) * MmPerUnit();
}
double SolveSpace::ChordTolMm(void) {
return SS.chordTol / SS.GW.scale;
}
int SolveSpace::UnitDigitsAfterDecimal(void) {
return (viewUnits == UNIT_INCHES) ? afterDecimalInch : afterDecimalMm;
}
void SolveSpace::SetUnitDigitsAfterDecimal(int v) {
if(viewUnits == UNIT_INCHES) {
afterDecimalInch = v;
} else {
afterDecimalMm = v;
}
}
double SolveSpace::CameraTangent(void) {
if(!usePerspectiveProj) {
return 0;
} else {
return cameraTangent;
}
}
void SolveSpace::AfterNewFile(void) {
// Clear out the traced point, which is no longer valid
traced.point = Entity::NO_ENTITY;
traced.path.l.Clear();
// and the naked edges
nakedEdges.Clear();
// GenerateAll() expects the view to be valid, because it uses that to
// fill in default values for extrusion depths etc. (which won't matter
// here, but just don't let it work on garbage)
SS.GW.offset = Vector::From(0, 0, 0);
SS.GW.projRight = Vector::From(1, 0, 0);
SS.GW.projUp = Vector::From(0, 1, 0);
ReloadAllImported();
GenerateAll(-1, -1);
TW.Init();
GW.Init();
unsaved = false;
int w, h;
GetGraphicsWindowSize(&w, &h);
GW.width = w;
GW.height = h;
// The triangles haven't been generated yet, but zoom to fit the entities
// roughly in the window, since that sets the mesh tolerance. Consider
// invisible entities, so we still get something reasonable if the only
// thing visible is the not-yet-generated surfaces.
GW.ZoomToFit(true);
GenerateAll(0, INT_MAX);
later.showTW = true;
// Then zoom to fit again, to fit the triangles
GW.ZoomToFit(false);
// Create all the default styles; they'll get created on the fly anyways,
// but can't hurt to do it now.
Style::CreateAllDefaultStyles();
UpdateWindowTitle();
}
void SolveSpace::RemoveFromRecentList(char *file) {
int src, dest;
dest = 0;
for(src = 0; src < MAX_RECENT; src++) {
if(strcmp(file, RecentFile[src]) != 0) {
if(src != dest) strcpy(RecentFile[dest], RecentFile[src]);
dest++;
}
}
while(dest < MAX_RECENT) strcpy(RecentFile[dest++], "");
RefreshRecentMenus();
}
void SolveSpace::AddToRecentList(char *file) {
RemoveFromRecentList(file);
int src;
for(src = MAX_RECENT - 2; src >= 0; src--) {
strcpy(RecentFile[src+1], RecentFile[src]);
}
strcpy(RecentFile[0], file);
RefreshRecentMenus();
}
bool SolveSpace::GetFilenameAndSave(bool saveAs) {
char newFile[MAX_PATH];
strcpy(newFile, saveFile);
if(saveAs || strlen(newFile)==0) {
if(!GetSaveFile(newFile, SLVS_EXT, SLVS_PATTERN)) return false;
}
if(SaveToFile(newFile)) {
AddToRecentList(newFile);
strcpy(saveFile, newFile);
unsaved = false;
return true;
} else {
return false;
}
}
bool SolveSpace::OkayToStartNewFile(void) {
if(!unsaved) return true;
switch(SaveFileYesNoCancel()) {
case IDYES:
return GetFilenameAndSave(false);
case IDNO:
return true;
case IDCANCEL:
return false;
default: oops();
}
}
void SolveSpace::UpdateWindowTitle(void) {
if(strlen(saveFile) == 0) {
SetWindowTitle("SolveSpace - (not yet saved)");
} else {
char buf[MAX_PATH+100];
sprintf(buf, "SolveSpace - %s", saveFile);
SetWindowTitle(buf);
}
}
void SolveSpace::MenuFile(int id) {
if(id >= RECENT_OPEN && id < (RECENT_OPEN+MAX_RECENT)) {
if(!SS.OkayToStartNewFile()) return;
char newFile[MAX_PATH];
strcpy(newFile, RecentFile[id-RECENT_OPEN]);
RemoveFromRecentList(newFile);
if(SS.LoadFromFile(newFile)) {
strcpy(SS.saveFile, newFile);
AddToRecentList(newFile);
} else {
strcpy(SS.saveFile, "");
SS.NewFile();
}
SS.AfterNewFile();
return;
}
switch(id) {
case GraphicsWindow::MNU_NEW:
if(!SS.OkayToStartNewFile()) break;
strcpy(SS.saveFile, "");
SS.NewFile();
SS.AfterNewFile();
break;
case GraphicsWindow::MNU_OPEN: {
if(!SS.OkayToStartNewFile()) break;
char newFile[MAX_PATH] = "";
if(GetOpenFile(newFile, SLVS_EXT, SLVS_PATTERN)) {
if(SS.LoadFromFile(newFile)) {
strcpy(SS.saveFile, newFile);
AddToRecentList(newFile);
} else {
strcpy(SS.saveFile, "");
SS.NewFile();
}
SS.AfterNewFile();
}
break;
}
case GraphicsWindow::MNU_SAVE:
SS.GetFilenameAndSave(false);
break;
case GraphicsWindow::MNU_SAVE_AS:
SS.GetFilenameAndSave(true);
break;
case GraphicsWindow::MNU_EXPORT_PNG: {
char exportFile[MAX_PATH] = "";
if(!GetSaveFile(exportFile, PNG_EXT, PNG_PATTERN)) break;
SS.ExportAsPngTo(exportFile);
break;
}
case GraphicsWindow::MNU_EXPORT_VIEW: {
char exportFile[MAX_PATH] = "";
if(!GetSaveFile(exportFile, VEC_EXT, VEC_PATTERN)) break;
// If the user is exporting something where it would be
// inappropriate to include the constraints, then warn.
if(SS.GW.showConstraints &&
(StringEndsIn(exportFile, ".txt") ||
fabs(SS.exportOffset) > LENGTH_EPS))
{
Message("Constraints are currently shown, and will be exported "
"in the toolpath. This is probably not what you want; "
"hide them by clicking the link at the top of the "
"text window.");
}
SS.ExportViewOrWireframeTo(exportFile, false);
break;
}
case GraphicsWindow::MNU_EXPORT_WIREFRAME: {
char exportFile[MAX_PATH] = "";
if(!GetSaveFile(exportFile, V3D_EXT, V3D_PATTERN)) break;
SS.ExportViewOrWireframeTo(exportFile, true);
break;
}
case GraphicsWindow::MNU_EXPORT_SECTION: {
char exportFile[MAX_PATH] = "";
if(!GetSaveFile(exportFile, VEC_EXT, VEC_PATTERN)) break;
SS.ExportSectionTo(exportFile);
break;
}
case GraphicsWindow::MNU_EXPORT_MESH: {
char exportFile[MAX_PATH] = "";
if(!GetSaveFile(exportFile, MESH_EXT, MESH_PATTERN)) break;
SS.ExportMeshTo(exportFile);
break;
}
case GraphicsWindow::MNU_EXPORT_SURFACES: {
char exportFile[MAX_PATH] = "";
if(!GetSaveFile(exportFile, SRF_EXT, SRF_PATTERN)) break;
StepFileWriter sfw;
ZERO(&sfw);
sfw.ExportSurfacesTo(exportFile);
break;
}
case GraphicsWindow::MNU_EXIT:
if(!SS.OkayToStartNewFile()) break;
SS.Exit();
break;
default: oops();
}
SS.UpdateWindowTitle();
}
void SolveSpace::MenuAnalyze(int id) {
SS.GW.GroupSelection();
#define gs (SS.GW.gs)
switch(id) {
case GraphicsWindow::MNU_STEP_DIM:
if(gs.constraints == 1 && gs.n == 0) {
Constraint *c = SK.GetConstraint(gs.constraint[0]);
if(c->HasLabel() && !c->reference) {
SS.TW.shown.dimFinish = c->valA;
SS.TW.shown.dimSteps = 10;
SS.TW.shown.dimIsDistance =
(c->type != Constraint::ANGLE) &&
(c->type != Constraint::LENGTH_RATIO);
SS.TW.shown.constraint = c->h;
SS.TW.shown.screen = TextWindow::SCREEN_STEP_DIMENSION;
// The step params are specified in the text window,
// so force that to be shown.
SS.GW.ForceTextWindowShown();
SS.later.showTW = true;
SS.GW.ClearSelection();
} else {
Error("Constraint must have a label, and must not be "
"a reference dimension.");
}
} else {
Error("Bad selection for step dimension; select a constraint.");
}
break;
case GraphicsWindow::MNU_NAKED_EDGES: {
SS.nakedEdges.Clear();
Group *g = SK.GetGroup(SS.GW.activeGroup);
SMesh *m = &(g->displayMesh);
SKdNode *root = SKdNode::From(m);
bool inters, leaks;
root->MakeCertainEdgesInto(&(SS.nakedEdges),
SKdNode::NAKED_OR_SELF_INTER_EDGES, true, &inters, &leaks);
InvalidateGraphics();
char *intersMsg = inters ?
"The mesh is self-intersecting (NOT okay, invalid)." :
"The mesh is not self-intersecting (okay, valid).";
char *leaksMsg = leaks ?
"The mesh has naked edges (NOT okay, invalid)." :
"The mesh is watertight (okay, valid).";
char cntMsg[1024];
sprintf(cntMsg, "\n\nThe model contains %d triangles, from "
"%d surfaces.",
g->displayMesh.l.n, g->runningShell.surface.n);
if(SS.nakedEdges.l.n == 0) {
Message("%s\n\n%s\n\nZero problematic edges, good.%s",
intersMsg, leaksMsg, cntMsg);
} else {
Error("%s\n\n%s\n\n%d problematic edges, bad.%s",
intersMsg, leaksMsg, SS.nakedEdges.l.n, cntMsg);
}
break;
}
case GraphicsWindow::MNU_INTERFERENCE: {
SS.nakedEdges.Clear();
SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh);
SKdNode *root = SKdNode::From(m);
bool inters, leaks;
root->MakeCertainEdgesInto(&(SS.nakedEdges),
SKdNode::SELF_INTER_EDGES, false, &inters, &leaks);
InvalidateGraphics();
if(inters) {
Error("%d edges interfere with other triangles, bad.",
SS.nakedEdges.l.n);
} else {
Message("The assembly does not interfere, good.");
}
break;
}
case GraphicsWindow::MNU_VOLUME: {
SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh);
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;
}
char msg[1024];
sprintf(msg, "The volume of the solid model is:\n\n"
" %.3f %s^3",
vol / pow(SS.MmPerUnit(), 3),
SS.UnitName());
if(SS.viewUnits == SolveSpace::UNIT_MM) {
sprintf(msg+strlen(msg), "\n %.2f mL", vol/(10*10*10));
}
strcpy(msg+strlen(msg),
"\n\nCurved surfaces have been approximated as triangles.\n"
"This introduces error, typically of around 1%.");
Message("%s", msg);
break;
}
case GraphicsWindow::MNU_AREA: {
Group *g = SK.GetGroup(SS.GW.activeGroup);
if(g->polyError.how != Group::POLY_GOOD) {
Error("This group does not contain a correctly-formed "
"2d closed area. It is open, not coplanar, or self-"
"intersecting.");
break;
}
SEdgeList sel;
ZERO(&sel);
g->polyLoops.MakeEdgesInto(&sel);
SPolygon sp;
ZERO(&sp);
sel.AssemblePolygon(&sp, NULL, true);
sp.normal = sp.ComputeNormal();
sp.FixContourDirections();
double area = sp.SignedArea();
double scale = SS.MmPerUnit();
Message("The area of the region sketched in this group is:\n\n"
" %.3f %s^2\n\n"
"Curves have been approximated as piecewise linear.\n"
"This introduces error, typically of around 1%%.",
area / (scale*scale),
SS.UnitName());
sel.Clear();
sp.Clear();
break;
}
case GraphicsWindow::MNU_SHOW_DOF:
// This works like a normal solve, except that it calculates
// which variables are free/bound at the same time.
SS.GenerateAll(0, INT_MAX, true);
break;
case GraphicsWindow::MNU_TRACE_PT:
if(gs.points == 1 && gs.n == 1) {
SS.traced.point = gs.point[0];
SS.GW.ClearSelection();
} else {
Error("Bad selection for trace; select a single point.");
}
break;
case GraphicsWindow::MNU_STOP_TRACING: {
char exportFile[MAX_PATH] = "";
if(GetSaveFile(exportFile, CSV_EXT, CSV_PATTERN)) {
FILE *f = fopen(exportFile, "wb");
if(f) {
int i;
SContour *sc = &(SS.traced.path);
for(i = 0; i < sc->l.n; i++) {
Vector p = sc->l.elem[i].p;
double s = SS.exportScale;
fprintf(f, "%.10f, %.10f, %.10f\r\n",
p.x/s, p.y/s, p.z/s);
}
fclose(f);
} else {
Error("Couldn't write to '%s'", exportFile);
}
}
// Clear the trace, and stop tracing
SS.traced.point = Entity::NO_ENTITY;
SS.traced.path.l.Clear();
InvalidateGraphics();
break;
}
default: oops();
}
}
void SolveSpace::MenuHelp(int id) {
switch(id) {
case GraphicsWindow::MNU_WEBSITE:
OpenWebsite("http://solvespace.com/helpmenu");
break;
case GraphicsWindow::MNU_ABOUT:
Message("This is SolveSpace version 2.0.\n\n"
"For more information, see http://solvespace.com/\n\n"
"Built " __TIME__ " " __DATE__ ".\n\n"
"Copyright 2008-2013 Jonathan Westhues.\n"
"All Rights Reserved.");
break;
default: oops();
}
}

View File

View File

Some files were not shown because too many files have changed in this diff Show More