From 9136d86bffed9b32a8905922dbf40a5ee215dd03 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Mon, 7 Jul 2008 23:45:47 -0800 Subject: [PATCH] Split solvespace.cpp; leave the file menu and general-purpose stuff where it was, move the export (DXF, PNG, STL) stuff to export.cpp, and move the regen/solve stuff to generate.cpp. [git-p4: depot-paths = "//depot/solvespace/": change = 1825] --- Makefile | 2 + export.cpp | 311 ++++++++++++++++++++++++ generate.cpp | 327 +++++++++++++++++++++++++ solvespace.cpp | 635 ------------------------------------------------- 4 files changed, 640 insertions(+), 635 deletions(-) create mode 100644 export.cpp create mode 100644 generate.cpp diff --git a/Makefile b/Makefile index 05ba0417..6330b2a8 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,8 @@ SSOBJS = $(OBJDIR)\solvespace.obj \ $(OBJDIR)\mesh.obj \ $(OBJDIR)\bsp.obj \ $(OBJDIR)\ttf.obj \ + $(OBJDIR)\generate.obj \ + $(OBJDIR)\export.obj \ LIBS = user32.lib gdi32.lib comctl32.lib advapi32.lib opengl32.lib glu32.lib \ diff --git a/export.cpp b/export.cpp new file mode 100644 index 00000000..a1a53f86 --- /dev/null +++ b/export.cpp @@ -0,0 +1,311 @@ +#include "solvespace.h" +#include + +void SolveSpace::ExportDxfTo(char *filename) { + SPolygon *sp; + SPolygon spa; + ZERO(&spa); + + Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); + gn = gn.WithMagnitude(1); + + SS.GW.GroupSelection(); +#define gs (SS.GW.gs) + + Group *g = SS.GetGroup(SS.GW.activeGroup); + + // The plane in which the exported section lies; need this because we'll + // reorient from that plane into the xy plane before exporting. + Vector p, u, v, n; + double d; + + if(gs.n == 0 && !(g->poly.IsEmpty())) { + // Easiest case--export the polygon drawn in this group + sp = &(g->poly); + p = sp->AnyPoint(); + n = (sp->ComputeNormal()).WithMagnitude(1); + if(n.Dot(gn) < 0) n = n.ScaledBy(-1); + u = n.Normal(0); + v = n.Normal(1); + d = p.Dot(n); + goto havepoly; + } + + if(g->runningMesh.l.n > 0 && + ((gs.n == 0 && g->activeWorkplane.v != Entity::FREE_IN_3D.v) || + (gs.n == 1 && gs.faces == 1) || + (gs.n == 3 && gs.vectors == 2 && gs.points == 1))) + { + if(gs.n == 0) { + Entity *wrkpl = SS.GetEntity(g->activeWorkplane); + p = wrkpl->WorkplaneGetOffset(); + n = wrkpl->Normal()->NormalN(); + u = wrkpl->Normal()->NormalU(); + v = wrkpl->Normal()->NormalV(); + } else if(gs.n == 1) { + Entity *face = SS.GetEntity(gs.entity[0]); + p = 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) { + Vector ut = SS.GetEntity(gs.entity[0])->VectorGetNum(), + vt = SS.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); + + p = SS.GetEntity(gs.point[0])->PointGetNum(); + n = ut.Cross(vt); + u = ut.WithMagnitude(1); + v = (n.Cross(u)).WithMagnitude(1); + } else oops(); + n = n.WithMagnitude(1); + d = p.Dot(n); + + SMesh m; + ZERO(&m); + m.MakeFromCopy(&(g->runningMesh)); + + 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(); + + SKdNode *root = SKdNode::From(&m); + root->SnapToMesh(&m); + + SEdgeList el; + ZERO(&el); + root->MakeCertainEdgesInto(&el, false); + el.AssemblePolygon(&spa, NULL); + sp = &spa; + + el.Clear(); + m.Clear(); + + SS.GW.ClearSelection(); + goto havepoly; + } + + Error("Geometry to export not specified."); + return; + +havepoly: + + FILE *f = fopen(filename, "wb"); + if(!f) { + Error("Couldn't write to '%s'", filename); + spa.Clear(); + return; + } + + // Some software, like Adobe Illustrator, insists on a header. + fprintf(f, +" 999\n" +"file created by SolveSpace\n" +" 0\n" +"SECTION\n" +" 2\n" +"HEADER\n" +" 9\n" +"$ACADVER\n" +" 1\n" +"AC1006\n" +" 9\n" +"$INSBASE\n" +" 10\n" +"0.0\n" +" 20\n" +"0.0\n" +" 30\n" +"0.0\n" +" 9\n" +"$EXTMIN\n" +" 10\n" +"0.0\n" +" 20\n" +"0.0\n" +" 9\n" +"$EXTMAX\n" +" 10\n" +"10000.0\n" +" 20\n" +"10000.0\n" +" 0\n" +"ENDSEC\n"); + + // Now begin the entities, which are just line segments reproduced from + // our piecewise linear curves. + fprintf(f, +" 0\n" +"SECTION\n" +" 2\n" +"ENTITIES\n"); + + int i, j; + for(i = 0; i < sp->l.n; i++) { + SContour *sc = &(sp->l.elem[i]); + + for(j = 1; j < sc->l.n; j++) { + Vector p0 = sc->l.elem[j-1].p, + p1 = sc->l.elem[j].p; + + Point2d e0 = p0.Project2d(u, v), + e1 = p1.Project2d(u, v); + + double s = SS.exportScale; + + fprintf(f, +" 0\n" +"LINE\n" +" 8\n" // Layer code +"%d\n" +" 10\n" // xA +"%.6f\n" +" 20\n" // yA +"%.6f\n" +" 30\n" // zA +"%.6f\n" +" 11\n" // xB +"%.6f\n" +" 21\n" // yB +"%.6f\n" +" 31\n" // zB +"%.6f\n", + 0, + e0.x/s, e0.y/s, 0.0, + e1.x/s, e1.y/s, 0.0); + } + } + + fprintf(f, +" 0\n" +"ENDSEC\n" +" 0\n" +"EOF\n" ); + + spa.Clear(); + fclose(f); +} + +void SolveSpace::ExportMeshTo(char *filename) { + SMesh *m = &(SS.GetGroup(SS.GW.activeGroup)->runningMesh); + if(m->l.n == 0) { + Error("Active group mesh is empty; nothing to export."); + return; + } + SKdNode *root = SKdNode::From(m); + root->SnapToMesh(m); + SMesh vvm; + ZERO(&vvm); + root->MakeMeshInto(&vvm); + + FILE *f = fopen(filename, "wb"); + if(!f) { + Error("Couldn't write to '%s'", filename); + vvm.Clear(); + return; + } + char str[80]; + memset(str, 0, sizeof(str)); + strcpy(str, "STL exported mesh"); + fwrite(str, 1, 80, f); + + DWORD n = vvm.l.n; + fwrite(&n, 4, 1, f); + + double s = SS.exportScale; + int i; + for(i = 0; i < vvm.l.n; i++) { + STriangle *tr = &(vvm.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); + } + + vvm.Clear(); + fclose(f); +} + +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. + SS.GW.Paint(w, h); + + 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); + return; + +err: + Error("Error writing PNG file '%s'", filename); + if(f) fclose(f); + return; +} + diff --git a/generate.cpp b/generate.cpp new file mode 100644 index 00000000..e58e9a40 --- /dev/null +++ b/generate.cpp @@ -0,0 +1,327 @@ +#include "solvespace.h" + +void SolveSpace::MarkGroupDirtyByEntity(hEntity he) { + Entity *e = SS.GetEntity(he); + MarkGroupDirty(e->group); +} + +void SolveSpace::MarkGroupDirty(hGroup hg) { + int i; + bool go = false; + for(i = 0; i < group.n; i++) { + Group *g = &(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 < request.n; i++) { + Request *r = &(request.elem[i]); + if(GroupExists(r->group)) continue; + + (deleted.requests)++; + request.RemoveById(r->h); + return true; + } + + for(i = 0; i < constraint.n; i++) { + Constraint *c = &(constraint.elem[i]); + if(GroupExists(c->group)) continue; + + (deleted.constraints)++; + 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 < group.n; i++) { + Group *g = &(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 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 entity.FindByIdNoOops(he) ? true : false; +} + +bool SolveSpace::PruneGroups(hGroup hg) { + Group *g = GetGroup(hg); + if(GroupsInOrder(g->opA, hg) && + EntityExists(g->predef.origin) && + EntityExists(g->predef.entityB) && + EntityExists(g->predef.entityC)) + { + return false; + } + (deleted.groups)++; + group.RemoveById(g->h); + return true; +} + +bool SolveSpace::PruneRequests(hGroup hg) { + int i; + for(i = 0; i < entity.n; i++) { + Entity *e = &(entity.elem[i]); + if(e->group.v != hg.v) continue; + + if(EntityExists(e->workplane)) continue; + + if(!e->h.isFromRequest()) oops(); + + (deleted.requests)++; + request.RemoveById(e->h.request()); + return true; + } + return false; +} + +bool SolveSpace::PruneConstraints(hGroup hg) { + int i; + for(i = 0; i < constraint.n; i++) { + Constraint *c = &(constraint.elem[i]); + if(c->group.v != hg.v) continue; + + if(EntityExists(c->workplane) && + EntityExists(c->ptA) && + EntityExists(c->ptB) && + EntityExists(c->ptC) && + EntityExists(c->entityA) && + EntityExists(c->entityB)) + { + continue; + } + + (deleted.constraints)++; + 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 < group.n; i++) { + Group *g = &(group.elem[i]); + if((!g->clean) || (g->solved.how != Group::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 { + GenerateAll(firstDirty, lastVisible); + } +} + +void SolveSpace::GenerateAll(int first, int last) { + int i, j; + + while(PruneOrphans()) + ; + + // Don't lose our numerical guesses when we regenerate. + IdList prev; + param.MoveSelfInto(&prev); + entity.Clear(); + + for(i = 0; i < group.n; i++) { + Group *g = &(group.elem[i]); + + // 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 < request.n; j++) { + Request *r = &(request.elem[j]); + if(r->group.v != g->h.v) continue; + + r->Generate(&entity, ¶m); + } + g->Generate(&entity, ¶m); + + // 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 < param.n; j++) { + Param *newp = &(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 = Group::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); + g->GeneratePolygon(); + g->GenerateMesh(); + 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 < param.n; j++) { + Param *newp = &(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 < constraint.n; i++) { + Constraint *c = &(constraint.elem[i]); + if(c->reference) { + c->ModifyToSatisfy(); + } + } + + 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(); + // 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. + Error("Additional sketch elements were deleted, because they depend " + "on the element that was just deleted explicitly. These " + "include: \r\n" + " %d request%s\r\n" + " %d constraint%s\r\n" + " %d group%s\r\n\r\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 + param.Clear(); + prev.MoveSelfInto(¶m); + // Try again + GenerateAll(first, last); +} + +void SolveSpace::ForceReferences(void) { + // Force the values of the paramters 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 = GetEntity(hr.entity(0)); + // The origin for our coordinate system, always zero + Entity *origin = GetEntity(wrkpl->point[0]); + origin->PointForceTo(Vector::From(0, 0, 0)); + GetParam(origin->param[0])->known = true; + GetParam(origin->param[1])->known = true; + GetParam(origin->param[2])->known = true; + // The quaternion that defines the rotation, from the table. + Entity *normal = GetEntity(wrkpl->normal); + normal->NormalForceTo(Quat[i].q); + GetParam(normal->param[0])->known = true; + GetParam(normal->param[1])->known = true; + GetParam(normal->param[2])->known = true; + GetParam(normal->param[3])->known = true; + } +} + +void SolveSpace::SolveGroup(hGroup hg) { + int i; + // Clear out the system to be solved. + sys.entity.Clear(); + sys.param.Clear(); + sys.eq.Clear(); + // And generate all the params for requests in this group + for(i = 0; i < request.n; i++) { + Request *r = &(request.elem[i]); + if(r->group.v != hg.v) continue; + + r->Generate(&(sys.entity), &(sys.param)); + } + // And for the group itself + Group *g = SS.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 = GetParam(p->h)->val; + } + + sys.Solve(g); + FreeAllTemporary(); +} + diff --git a/solvespace.cpp b/solvespace.cpp index d962a88e..2a9e0242 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -1,5 +1,4 @@ #include "solvespace.h" -#include SolveSpace SS; @@ -153,639 +152,6 @@ void SolveSpace::AfterNewFile(void) { GW.ZoomToFit(); } -void SolveSpace::MarkGroupDirtyByEntity(hEntity he) { - Entity *e = SS.GetEntity(he); - MarkGroupDirty(e->group); -} - -void SolveSpace::MarkGroupDirty(hGroup hg) { - int i; - bool go = false; - for(i = 0; i < group.n; i++) { - Group *g = &(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 < request.n; i++) { - Request *r = &(request.elem[i]); - if(GroupExists(r->group)) continue; - - (deleted.requests)++; - request.RemoveById(r->h); - return true; - } - - for(i = 0; i < constraint.n; i++) { - Constraint *c = &(constraint.elem[i]); - if(GroupExists(c->group)) continue; - - (deleted.constraints)++; - 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 < group.n; i++) { - Group *g = &(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 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 entity.FindByIdNoOops(he) ? true : false; -} - -bool SolveSpace::PruneGroups(hGroup hg) { - Group *g = GetGroup(hg); - if(GroupsInOrder(g->opA, hg) && - EntityExists(g->predef.origin) && - EntityExists(g->predef.entityB) && - EntityExists(g->predef.entityC)) - { - return false; - } - (deleted.groups)++; - group.RemoveById(g->h); - return true; -} - -bool SolveSpace::PruneRequests(hGroup hg) { - int i; - for(i = 0; i < entity.n; i++) { - Entity *e = &(entity.elem[i]); - if(e->group.v != hg.v) continue; - - if(EntityExists(e->workplane)) continue; - - if(!e->h.isFromRequest()) oops(); - - (deleted.requests)++; - request.RemoveById(e->h.request()); - return true; - } - return false; -} - -bool SolveSpace::PruneConstraints(hGroup hg) { - int i; - for(i = 0; i < constraint.n; i++) { - Constraint *c = &(constraint.elem[i]); - if(c->group.v != hg.v) continue; - - if(EntityExists(c->workplane) && - EntityExists(c->ptA) && - EntityExists(c->ptB) && - EntityExists(c->ptC) && - EntityExists(c->entityA) && - EntityExists(c->entityB)) - { - continue; - } - - (deleted.constraints)++; - 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 < group.n; i++) { - Group *g = &(group.elem[i]); - if((!g->clean) || (g->solved.how != Group::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 { - GenerateAll(firstDirty, lastVisible); - } -} - -void SolveSpace::GenerateAll(int first, int last) { - int i, j; - - while(PruneOrphans()) - ; - - // Don't lose our numerical guesses when we regenerate. - IdList prev; - param.MoveSelfInto(&prev); - entity.Clear(); - - for(i = 0; i < group.n; i++) { - Group *g = &(group.elem[i]); - - // 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 < request.n; j++) { - Request *r = &(request.elem[j]); - if(r->group.v != g->h.v) continue; - - r->Generate(&entity, ¶m); - } - g->Generate(&entity, ¶m); - - // 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 < param.n; j++) { - Param *newp = &(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 = Group::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); - g->GeneratePolygon(); - g->GenerateMesh(); - 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 < param.n; j++) { - Param *newp = &(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 < constraint.n; i++) { - Constraint *c = &(constraint.elem[i]); - if(c->reference) { - c->ModifyToSatisfy(); - } - } - - 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(); - // 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. - Error("Additional sketch elements were deleted, because they depend " - "on the element that was just deleted explicitly. These " - "include: \r\n" - " %d request%s\r\n" - " %d constraint%s\r\n" - " %d group%s\r\n\r\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 - param.Clear(); - prev.MoveSelfInto(¶m); - // Try again - GenerateAll(first, last); -} - -void SolveSpace::ForceReferences(void) { - // Force the values of the paramters 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 = GetEntity(hr.entity(0)); - // The origin for our coordinate system, always zero - Entity *origin = GetEntity(wrkpl->point[0]); - origin->PointForceTo(Vector::From(0, 0, 0)); - GetParam(origin->param[0])->known = true; - GetParam(origin->param[1])->known = true; - GetParam(origin->param[2])->known = true; - // The quaternion that defines the rotation, from the table. - Entity *normal = GetEntity(wrkpl->normal); - normal->NormalForceTo(Quat[i].q); - GetParam(normal->param[0])->known = true; - GetParam(normal->param[1])->known = true; - GetParam(normal->param[2])->known = true; - GetParam(normal->param[3])->known = true; - } -} - -void SolveSpace::SolveGroup(hGroup hg) { - int i; - // Clear out the system to be solved. - sys.entity.Clear(); - sys.param.Clear(); - sys.eq.Clear(); - // And generate all the params for requests in this group - for(i = 0; i < request.n; i++) { - Request *r = &(request.elem[i]); - if(r->group.v != hg.v) continue; - - r->Generate(&(sys.entity), &(sys.param)); - } - // And for the group itself - Group *g = SS.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 = GetParam(p->h)->val; - } - - sys.Solve(g); - FreeAllTemporary(); -} - -void SolveSpace::ExportDxfTo(char *filename) { - SPolygon *sp; - SPolygon spa; - ZERO(&spa); - - Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); - gn = gn.WithMagnitude(1); - - SS.GW.GroupSelection(); -#define gs (SS.GW.gs) - - Group *g = SS.GetGroup(SS.GW.activeGroup); - - // The plane in which the exported section lies; need this because we'll - // reorient from that plane into the xy plane before exporting. - Vector p, u, v, n; - double d; - - if(gs.n == 0 && !(g->poly.IsEmpty())) { - // Easiest case--export the polygon drawn in this group - sp = &(g->poly); - p = sp->AnyPoint(); - n = (sp->ComputeNormal()).WithMagnitude(1); - if(n.Dot(gn) < 0) n = n.ScaledBy(-1); - u = n.Normal(0); - v = n.Normal(1); - d = p.Dot(n); - goto havepoly; - } - - if(g->runningMesh.l.n > 0 && - ((gs.n == 0 && g->activeWorkplane.v != Entity::FREE_IN_3D.v) || - (gs.n == 1 && gs.faces == 1) || - (gs.n == 3 && gs.vectors == 2 && gs.points == 1))) - { - if(gs.n == 0) { - Entity *wrkpl = SS.GetEntity(g->activeWorkplane); - p = wrkpl->WorkplaneGetOffset(); - n = wrkpl->Normal()->NormalN(); - u = wrkpl->Normal()->NormalU(); - v = wrkpl->Normal()->NormalV(); - } else if(gs.n == 1) { - Entity *face = SS.GetEntity(gs.entity[0]); - p = 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) { - Vector ut = SS.GetEntity(gs.entity[0])->VectorGetNum(), - vt = SS.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); - - p = SS.GetEntity(gs.point[0])->PointGetNum(); - n = ut.Cross(vt); - u = ut.WithMagnitude(1); - v = (n.Cross(u)).WithMagnitude(1); - } else oops(); - n = n.WithMagnitude(1); - d = p.Dot(n); - - SMesh m; - ZERO(&m); - m.MakeFromCopy(&(g->runningMesh)); - - 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(); - - SKdNode *root = SKdNode::From(&m); - root->SnapToMesh(&m); - - SEdgeList el; - ZERO(&el); - root->MakeCertainEdgesInto(&el, false); - el.AssemblePolygon(&spa, NULL); - sp = &spa; - - el.Clear(); - m.Clear(); - - SS.GW.ClearSelection(); - goto havepoly; - } - - Error("Geometry to export not specified."); - return; - -havepoly: - - FILE *f = fopen(filename, "wb"); - if(!f) { - Error("Couldn't write to '%s'", filename); - spa.Clear(); - return; - } - - // Some software, like Adobe Illustrator, insists on a header. - fprintf(f, -" 999\n" -"file created by SolveSpace\n" -" 0\n" -"SECTION\n" -" 2\n" -"HEADER\n" -" 9\n" -"$ACADVER\n" -" 1\n" -"AC1006\n" -" 9\n" -"$INSBASE\n" -" 10\n" -"0.0\n" -" 20\n" -"0.0\n" -" 30\n" -"0.0\n" -" 9\n" -"$EXTMIN\n" -" 10\n" -"0.0\n" -" 20\n" -"0.0\n" -" 9\n" -"$EXTMAX\n" -" 10\n" -"10000.0\n" -" 20\n" -"10000.0\n" -" 0\n" -"ENDSEC\n"); - - // Now begin the entities, which are just line segments reproduced from - // our piecewise linear curves. - fprintf(f, -" 0\n" -"SECTION\n" -" 2\n" -"ENTITIES\n"); - - int i, j; - for(i = 0; i < sp->l.n; i++) { - SContour *sc = &(sp->l.elem[i]); - - for(j = 1; j < sc->l.n; j++) { - Vector p0 = sc->l.elem[j-1].p, - p1 = sc->l.elem[j].p; - - Point2d e0 = p0.Project2d(u, v), - e1 = p1.Project2d(u, v); - - double s = SS.exportScale; - - fprintf(f, -" 0\n" -"LINE\n" -" 8\n" // Layer code -"%d\n" -" 10\n" // xA -"%.6f\n" -" 20\n" // yA -"%.6f\n" -" 30\n" // zA -"%.6f\n" -" 11\n" // xB -"%.6f\n" -" 21\n" // yB -"%.6f\n" -" 31\n" // zB -"%.6f\n", - 0, - e0.x/s, e0.y/s, 0.0, - e1.x/s, e1.y/s, 0.0); - } - } - - fprintf(f, -" 0\n" -"ENDSEC\n" -" 0\n" -"EOF\n" ); - - spa.Clear(); - fclose(f); -} - -void SolveSpace::ExportMeshTo(char *filename) { - SMesh *m = &(SS.GetGroup(SS.GW.activeGroup)->runningMesh); - if(m->l.n == 0) { - Error("Active group mesh is empty; nothing to export."); - return; - } - SKdNode *root = SKdNode::From(m); - root->SnapToMesh(m); - SMesh vvm; - ZERO(&vvm); - root->MakeMeshInto(&vvm); - - FILE *f = fopen(filename, "wb"); - if(!f) { - Error("Couldn't write to '%s'", filename); - vvm.Clear(); - return; - } - char str[80]; - memset(str, 0, sizeof(str)); - strcpy(str, "STL exported mesh"); - fwrite(str, 1, 80, f); - - DWORD n = vvm.l.n; - fwrite(&n, 4, 1, f); - - double s = SS.exportScale; - int i; - for(i = 0; i < vvm.l.n; i++) { - STriangle *tr = &(vvm.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); - } - - vvm.Clear(); - fclose(f); -} - -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. - SS.GW.Paint(w, h); - - 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); - return; - -err: - Error("Error writing PNG file '%s'", filename); - if(f) fclose(f); - return; -} - void SolveSpace::RemoveFromRecentList(char *file) { int src, dest; dest = 0; @@ -844,7 +210,6 @@ bool SolveSpace::OkayToStartNewFile(void) { } void SolveSpace::MenuFile(int id) { - if(id >= RECENT_OPEN && id < (RECENT_OPEN+MAX_RECENT)) { char newFile[MAX_PATH]; strcpy(newFile, RecentFile[id-RECENT_OPEN]);