
separate groups. The section is swept normal to the trajectory, producing a mesh. I'm doing the triangles only now, not copying over any entities. Also fix a bug in the PNG export; rows are 4-aligned, so that was breaking when the width of the image wasn't divisible by four. Also fix a bug in lathes, where it generated overlapping triangles for one segment. And change the groups to record both "this mesh", the contribution due to the extrude/lathe/whatever, and the "running mesh", that we get after applying the requested Boolean op between "this mesh" and the previous group's "running mesh". I'll use that to make step and repeats step the mesh too. [git-p4: depot-paths = "//depot/solvespace/": change = 1801]
412 lines
15 KiB
C++
412 lines
15 KiB
C++
#include "solvespace.h"
|
|
|
|
#define VERSION_STRING "±²³SolveSpaceREVa"
|
|
|
|
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("draw-in-plane");
|
|
group.AddAndAssignId(&g);
|
|
SS.GetGroup(g.h)->activeWorkplane = g.h.entity(0);
|
|
return g.h;
|
|
}
|
|
|
|
void SolveSpace::NewFile(void) {
|
|
UndoClearStack(&redo);
|
|
UndoClearStack(&undo);
|
|
|
|
constraint.Clear();
|
|
request.Clear();
|
|
group.Clear();
|
|
|
|
entity.Clear();
|
|
param.Clear();
|
|
|
|
// 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;
|
|
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;
|
|
request.Add(&r);
|
|
|
|
r.h = Request::HREQUEST_REFERENCE_YZ;
|
|
request.Add(&r);
|
|
|
|
r.h = Request::HREQUEST_REFERENCE_ZX;
|
|
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.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.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.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.remap", 'M', &(SS.sv.g.remap) },
|
|
{ 'g', "Group.impFile", 'P', &(SS.sv.g.impFile) },
|
|
|
|
{ '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.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) },
|
|
|
|
{ '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.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.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.ptC.v", 'x', &(SS.sv.c.ptC.v) },
|
|
{ 'c', "Constraint.entityA.v", 'x', &(SS.sv.c.entityA.v) },
|
|
{ 'c', "Constraint.entityB.v", 'x', &(SS.sv.c.entityB.v) },
|
|
{ 'c', "Constraint.otherAngle", 'b', &(SS.sv.c.otherAngle) },
|
|
{ '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) },
|
|
|
|
{ 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;
|
|
|
|
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;
|
|
for(i = 0; i < group.n; i++) {
|
|
sv.g = group.elem[i];
|
|
SaveUsingTable('g');
|
|
fprintf(fh, "AddGroup\n\n");
|
|
}
|
|
|
|
for(i = 0; i < param.n; i++) {
|
|
sv.p = param.elem[i];
|
|
SaveUsingTable('p');
|
|
fprintf(fh, "AddParam\n\n");
|
|
}
|
|
|
|
for(i = 0; i < request.n; i++) {
|
|
sv.r = request.elem[i];
|
|
SaveUsingTable('r');
|
|
fprintf(fh, "AddRequest\n\n");
|
|
}
|
|
|
|
for(i = 0; i < entity.n; i++) {
|
|
(entity.elem[i]).CalculateNumerical(true);
|
|
sv.e = entity.elem[i];
|
|
SaveUsingTable('e');
|
|
fprintf(fh, "AddEntity\n\n");
|
|
}
|
|
|
|
for(i = 0; i < constraint.n; i++) {
|
|
sv.c = constraint.elem[i];
|
|
SaveUsingTable('c');
|
|
fprintf(fh, "AddConstraint\n\n");
|
|
}
|
|
|
|
SMesh *m = &(group.elem[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));
|
|
}
|
|
|
|
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) oops();
|
|
}
|
|
|
|
bool SolveSpace::LoadFromFile(char *filename) {
|
|
fh = fopen(filename, "rb");
|
|
if(!fh) {
|
|
Error("Couldn't read from file '%s'", filename);
|
|
return false;
|
|
}
|
|
|
|
UndoClearStack(&redo);
|
|
UndoClearStack(&undo);
|
|
|
|
constraint.Clear();
|
|
request.Clear();
|
|
group.Clear();
|
|
entity.Clear();
|
|
param.Clear();
|
|
memset(&sv, 0, sizeof(sv));
|
|
|
|
char line[1024];
|
|
while(fgets(line, sizeof(line), fh)) {
|
|
char *s = strchr(line, '\n');
|
|
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) {
|
|
SS.group.Add(&(sv.g));
|
|
memset(&(sv.g), 0, sizeof(sv.g));
|
|
} else if(strcmp(line, "AddParam")==0) {
|
|
// params are regenerated, but we want to preload the values
|
|
// for initial guesses
|
|
SS.param.Add(&(sv.p));
|
|
memset(&(sv.p), 0, sizeof(sv.p));
|
|
} else if(strcmp(line, "AddEntity")==0) {
|
|
// entities are regenerated
|
|
} else if(strcmp(line, "AddRequest")==0) {
|
|
SS.request.Add(&(sv.r));
|
|
memset(&(sv.r), 0, sizeof(sv.r));
|
|
} else if(strcmp(line, "AddConstraint")==0) {
|
|
SS.constraint.Add(&(sv.c));
|
|
memset(&(sv.c), 0, sizeof(sv.c));
|
|
} else if(strcmp(line, VERSION_STRING)==0) {
|
|
// do nothing, version string
|
|
} else if(memcmp(line, "Triangle", 8)==0) {
|
|
// likewise ignore the triangles; we generate those
|
|
} else {
|
|
oops();
|
|
}
|
|
}
|
|
|
|
fclose(fh);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SolveSpace::LoadEntitiesFromFile(char *file, EntityList *le, SMesh *m) {
|
|
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';
|
|
|
|
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, VERSION_STRING)==0) {
|
|
|
|
} else if(memcmp(line, "Triangle", 8)==0) {
|
|
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 {
|
|
oops();
|
|
}
|
|
}
|
|
|
|
fclose(fh);
|
|
return true;
|
|
}
|
|
|
|
void SolveSpace::ReloadAllImported(void) {
|
|
int i;
|
|
for(i = 0; i < group.n; i++) {
|
|
Group *g = &(group.elem[i]);
|
|
if(g->type != Group::IMPORTED) continue;
|
|
|
|
g->impEntity.Clear();
|
|
g->impMesh.Clear();
|
|
if(!LoadEntitiesFromFile(g->impFile, &(g->impEntity), &(g->impMesh))) {
|
|
Error("Failed to load imported file '%s'", g->impFile);
|
|
}
|
|
}
|
|
}
|
|
|