bool SolveSpaceUI::LoadAutosaveFor(const std::string &filename) { std::string autosaveFile = filename + AUTOSAVE_SUFFIX; FILE *f = ssfopen(autosaveFile, "rb"); if(!f) return false; fclose(f); if(LoadAutosaveYesNo() == DIALOG_YES) { unsaved = true; return LoadFromFile(autosaveFile); } return false; }
void TextWindow::ScreenBackgroundImage(int link, uint32_t v) { SS.bgImage.pixmap.Clear(); if(link == 'l') { std::string bgImageFile; if(GetOpenFile(&bgImageFile, "", PngFileFilter)) { FILE *f = ssfopen(bgImageFile, "rb"); if(f) { SS.bgImage.pixmap = Pixmap::FromPNG(f); SS.bgImage.scale = SS.GW.scale; SS.bgImage.origin = SS.GW.offset.ScaledBy(-1); } } } SS.ScheduleShowTW(); }
void SolveSpaceUI::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) && (c->type != Constraint::LENGTH_DIFFERENCE); 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.ScheduleShowTW(); 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(); const char *intersMsg = inters ? "The mesh is self-intersecting (NOT okay, invalid)." : "The mesh is not self-intersecting (okay, valid)."; const char *leaksMsg = leaks ? "The mesh has naked edges (NOT okay, invalid)." : "The mesh is watertight (okay, valid)."; std::string cntMsg = ssprintf("\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.c_str()); } else { Error("%s\n\n%s\n\n%d problematic edges, bad.%s", intersMsg, leaksMsg, SS.nakedEdges.l.n, cntMsg.c_str()); } 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; } std::string msg = ssprintf("The volume of the solid model is:\n\n"" %.3f %s^3", vol / pow(SS.MmPerUnit(), 3), SS.UnitName()); if(SS.viewUnits == SolveSpaceUI::UNIT_MM) { msg += ssprintf("\n %.2f mL", vol/(10*10*10)); } msg += "\n\nCurved surfaces have been approximated as triangles.\n" "This introduces error, typically of around 1%."; Message("%s", msg.c_str()); 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 = {}; g->polyLoops.MakeEdgesInto(&sel); SPolygon 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(SolveSpaceUI::GENERATE_ALL, 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: { std::string exportFile; if(GetSaveFile(&exportFile, "", CsvFileFilter)) { FILE *f = ssfopen(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.c_str()); } } // Clear the trace, and stop tracing SS.traced.point = Entity::NO_ENTITY; SS.traced.path.l.Clear(); InvalidateGraphics(); break; } default: oops(); } }
bool SolveSpaceUI::ReloadAllImported(bool canCancel) { std::map<std::string, std::string> importMap; allConsistent = false; int i; for(i = 0; i < SK.group.n; i++) { Group *g = &(SK.group.elem[i]); if(g->type != Group::IMPORTED) continue; if(isalpha(g->impFile[0]) && g->impFile[1] == ':') { // Make sure that g->impFileRel always contains a relative path // in an UNIX format, even after we load an old file which had // the path in Windows format PathSepNormalize(g->impFileRel); } g->impEntity.Clear(); g->impMesh.Clear(); g->impShell.Clear(); if(importMap.count(g->impFile)) { std::string newPath = importMap[g->impFile]; if(!newPath.empty()) g->impFile = newPath; } FILE *test = ssfopen(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.empty()) { std::string rel = PathSepUNIXToPlatform(g->impFileRel); std::string fromRel = MakePathAbsolute(SS.saveFile, rel); test = ssfopen(fromRel, "rb"); if(test) { fclose(test); // It worked, this is our new absolute path g->impFile = fromRel; } } } try_load_file: if(LoadEntitiesFromFile(g->impFile, &(g->impEntity), &(g->impMesh), &(g->impShell))) { if(!SS.saveFile.empty()) { // Record the imported file's name relative to our filename; // if the entire tree moves, then everything will still work std::string rel = MakePathRelative(SS.saveFile, g->impFile); g->impFileRel = PathSepPlatformToUNIX(rel); } else { // We're not yet saved, so can't make it absolute. // This will only be used for display purposes, as SS.saveFile // is always nonempty when we are actually writing anything. g->impFileRel = g->impFile; } } else if(!importMap.count(g->impFile)) { switch(LocateImportedFileYesNoCancel(g->impFileRel, canCancel)) { case DIALOG_YES: { std::string oldImpFile = g->impFile; if(!GetOpenFile(g->impFile, "", SLVS_PATTERN)) { if(canCancel) return false; break; } else { importMap[oldImpFile] = g->impFile; goto try_load_file; } } case DIALOG_NO: importMap[g->impFile] = ""; /* Geometry will be pruned by GenerateAll(). */ break; case DIALOG_CANCEL: return false; } } else { // User was already asked to and refused to locate a missing // imported file. } } return true; }
bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList *le, SMesh *m, SShell *sh) { SSurface srf = {}; SCurve crv = {}; fh = ssfopen(filename, "rb"); if(!fh) return false; le->Clear(); sv = {}; char line[1024]; while(fgets(line, (int)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)); 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 = {}; unsigned int rgba = 0; if(sscanf(line, "Triangle %x %x " "%lf %lf %lf %lf %lf %lf %lf %lf %lf", &(tr.meta.face), &rgba, &(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(); } tr.meta.color = RgbaColor::FromPackedInt((uint32_t)rgba); m->AddTriangle(&tr); } else if(StrStartsWith(line, "Surface ")) { unsigned int rgba = 0; if(sscanf(line, "Surface %x %x %x %d %d", &(srf.h.v), &rgba, &(srf.face), &(srf.degm), &(srf.degn)) != 5) { oops(); } srf.color = RgbaColor::FromPackedInt((uint32_t)rgba); } 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 = {}; 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); 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); crv = {}; } else { oops(); } } fclose(fh); return true; }
bool SolveSpaceUI::LoadFromFile(const std::string &filename) { allConsistent = false; fileLoadError = false; fh = ssfopen(filename, "rb"); if(!fh) { Error("Couldn't read from file '%s'", filename.c_str()); return false; } ClearExisting(); sv = {}; sv.g.scale = 1; // default is 1, not 0; so legacy files need this Style::FillDefaultStyle(&sv.s); char line[1024]; while(fgets(line, (int)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) { // legacy files have a spurious dependency between imported groups // and their parent groups, remove if(sv.g.type == Group::IMPORTED) sv.g.opA.v = 0; SK.group.Add(&(sv.g)); 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)); sv.p = {}; } else if(strcmp(line, "AddEntity")==0) { // entities are regenerated } else if(strcmp(line, "AddRequest")==0) { SK.request.Add(&(sv.r)); sv.r = {}; } else if(strcmp(line, "AddConstraint")==0) { SK.constraint.Add(&(sv.c)); sv.c = {}; } else if(strcmp(line, "AddStyle")==0) { SK.style.Add(&(sv.s)); sv.s = {}; Style::FillDefaultStyle(&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 SolveSpaceUI::SaveToFile(const std::string &filename) { // Make sure all the entities are regenerated up to date, since they // will be exported. We reload the imported files because that rewrites // the impFileRel for our possibly-new filename. SS.ScheduleShowTW(); SS.ReloadAllImported(); SS.GenerateAll(SolveSpaceUI::GENERATE_ALL); fh = ssfopen(filename, "wb"); if(!fh) { Error("Couldn't write to file '%s'", filename.c_str()); 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. Group *g = SK.GetGroup(SK.groupOrder.elem[SK.groupOrder.n - 1]); SMesh *m = &g->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.ToPackedInt(), CO(tr->a), CO(tr->b), CO(tr->c)); } SShell *s = &g->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.ToPackedInt(), 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; }