diff --git a/constraint.cpp b/constraint.cpp index 47b0b19..9c60c0c 100644 --- a/constraint.cpp +++ b/constraint.cpp @@ -233,6 +233,22 @@ void Constraint::MenuConstrain(int id) { } else { c.type = SYMMETRIC_VERT; } + if(gs.lineSegments == 1) { + // If this line segment is already constrained horiz or + // vert, then auto-remove that redundant constraint. + SS.constraint.ClearTags(); + for(int i = 0; i < SS.constraint.n; i++) { + Constraint *ct = &(SS.constraint.elem[i]); + if(ct->type != HORIZONTAL && ct->type != VERTICAL) { + continue; + } + if(ct->entityA.v != (gs.entity[0]).v) continue; + ct->tag = 1; + } + SS.constraint.RemoveTagged(); + // And no need to do anything special, since nothing + // ever depends on a constraint. + } } else { // Symmetry with a symmetry plane specified explicitly. c.type = SYMMETRIC; @@ -290,7 +306,20 @@ void Constraint::MenuConstrain(int id) { } } Error("Must select an angle constraint."); - break; + return; + + case GraphicsWindow::MNU_REFERENCE: + if(gs.constraints == 1 && gs.n == 0) { + Constraint *c = SS.GetConstraint(gs.constraint[0]); + if(c->HasLabel()) { + (c->reference) = !(c->reference); + SS.GetGroup(c->group)->clean = false; + SS.GenerateAll(); + break; + } + } + Error("Must select a constraint with associated label."); + return; case GraphicsWindow::MNU_ANGLE: if(gs.vectors == 2 && gs.n == 2) { @@ -456,9 +485,9 @@ void Constraint::ModifyToSatisfy(void) { // that means no extra work. IdList l; // An uninit IdList could lead us to free some random address, bad. - memset(&l, 0, sizeof(l)); - - Generate(&l); + ZERO(&l); + // Generate the equations even if this is a reference dimension + GenerateReal(&l); if(l.n != 1) oops(); // These equations are written in the form f(...) - d = 0, where @@ -480,6 +509,11 @@ void Constraint::AddEq(IdList *l, Expr *expr, int index) { } void Constraint::Generate(IdList *l) { + if(!reference) { + GenerateReal(l); + } +} +void Constraint::GenerateReal(IdList *l) { Expr *exA = NULL; if(exprA) exA = exprA->DeepCopy(); diff --git a/draw.cpp b/draw.cpp index b036689..abf67a0 100644 --- a/draw.cpp +++ b/draw.cpp @@ -759,12 +759,12 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) { } } -Vector GraphicsWindow::VectorFromProjs(double right, double up, double fwd) { +Vector GraphicsWindow::VectorFromProjs(Vector rightUpForward) { Vector n = projRight.Cross(projUp); - Vector r = offset.ScaledBy(-1); - r = r.Plus(projRight.ScaledBy(right)); - r = r.Plus(projUp.ScaledBy(up)); - r = r.Plus(n.ScaledBy(fwd)); + + Vector r = (projRight.ScaledBy(rightUpForward.x)); + r = r.Plus(projUp.ScaledBy(rightUpForward.y)); + r = r.Plus(n.ScaledBy(rightUpForward.z)); return r; } @@ -810,21 +810,25 @@ void GraphicsWindow::Paint(int w, int h) { glClearDepth(1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - // Let's use two lights + // Let's use two lights, at the user-specified locations + GLfloat f; glEnable(GL_LIGHT0); - GLfloat li0[] = { 0.8f, 0.8f, 0.8f, 1.0f }; + 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); - GLfloat li1[] = { 0.4f, 0.4f, 0.4f, 1.0f }; + 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 lp; - lp = VectorFromProjs(-0.49*w/scale, 0.49*h/scale, 0); + lp = VectorFromProjs(SS.lightPos[0]); GLfloat lp0[4] = { (GLfloat)lp.x, (GLfloat)lp.y, (GLfloat)lp.z, 0 }; glLightfv(GL_LIGHT0, GL_POSITION, lp0); - lp = VectorFromProjs(0.49*w/scale, 0.10*h/scale, 0); + lp = VectorFromProjs(SS.lightPos[1]); GLfloat lp1[4] = { (GLfloat)lp.x, (GLfloat)lp.y, (GLfloat)lp.z, 0 }; glLightfv(GL_LIGHT1, GL_POSITION, lp1); diff --git a/drawconstraint.cpp b/drawconstraint.cpp index e71951e..9ad7728 100644 --- a/drawconstraint.cpp +++ b/drawconstraint.cpp @@ -40,8 +40,24 @@ double Constraint::EllipticalInterpolation(double rx, double ry, double theta) { return v; } +char *Constraint::Label(void) { + static char Ret[1024]; + if(type == ANGLE) { + sprintf(Ret, "%.2f", exprA->Eval()); + } else if(type == LENGTH_RATIO) { + sprintf(Ret, "%.3f:1", exprA->Eval()); + } else { + // exprA has units of distance + strcpy(Ret, SS.GW.ToString(exprA->Eval())); + } + if(reference) { + strcat(Ret, " REF"); + } + return Ret; +} + void Constraint::DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu) { - char *s = exprA->Print(); + char *s = Label(); if(labelPos) { // labelPos is from the top left corner (for the text box used to // edit things), but ref is from the center. @@ -188,7 +204,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { double theta = atan2(disp.offset.Dot(gu), disp.offset.Dot(gr)); double adj = EllipticalInterpolation( - glxStrWidth(exprA->Print())/2, glxStrHeight()/2, theta); + glxStrWidth(Label())/2, glxStrHeight()/2, theta); Vector mark = ref.Minus(center); mark = mark.WithMagnitude(mark.Magnitude()-r); @@ -333,7 +349,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { double tl = atan2(rm.Dot(gu), rm.Dot(gr)); double adj = EllipticalInterpolation( - glxStrWidth(exprA->Print())/2, glxStrHeight()/2, tl); + glxStrWidth(Label())/2, glxStrHeight()/2, tl); ref = ref.Plus(rm.WithMagnitude(adj + 3/SS.GW.scale)); } else { // The lines are skew; no wonderful way to illustrate that. diff --git a/drawentity.cpp b/drawentity.cpp index 44589ba..a60ad5e 100644 --- a/drawentity.cpp +++ b/drawentity.cpp @@ -4,7 +4,9 @@ void Entity::LineDrawOrGetDistance(Vector a, Vector b) { if(dogd.drawing) { // glPolygonOffset works only on polys, not lines, so do it myself Vector adj = SS.GW.projRight.Cross(SS.GW.projUp); - adj = adj.ScaledBy(5/SS.GW.scale); + // Draw lines from active group in front of those from previous + int delta = (group.v == SS.GW.activeGroup.v) ? 10 : 5; + adj = adj.ScaledBy(delta/SS.GW.scale); glBegin(GL_LINES); glxVertex3v(a.Plus(adj)); glxVertex3v(b.Plus(adj)); @@ -14,6 +16,8 @@ void Entity::LineDrawOrGetDistance(Vector a, Vector b) { 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); @@ -164,9 +168,7 @@ void Entity::DrawOrGetDistance(void) { glPolygonOffset(0, 0); } else { Point2d pp = SS.GW.ProjectPoint(v); - // Make a free point slightly easier to select, so that with - // coincident points, we select the free one. - dogd.dmin = pp.DistanceTo(dogd.mp) - 4; + dogd.dmin = pp.DistanceTo(dogd.mp) - 6; } break; } @@ -292,7 +294,7 @@ void Entity::DrawOrGetDistance(void) { Vector p1 = SS.GetEntity(point[1])->PointGetNum(); Vector p2 = SS.GetEntity(point[2])->PointGetNum(); Vector p3 = SS.GetEntity(point[3])->PointGetNum(); - int i, n = 20; + int i, n = (int)(15/sqrt(SS.meshTol)); Vector prev = p0; for(i = 1; i <= n; i++) { double t = ((double)i)/n; @@ -307,7 +309,6 @@ void Entity::DrawOrGetDistance(void) { break; } -#define CIRCLE_SIDES(r) (7 + (int)(sqrt(r*SS.GW.scale))) case ARC_OF_CIRCLE: { Vector c = SS.GetEntity(point[0])->PointGetNum(); Vector pa = SS.GetEntity(point[1])->PointGetNum(); @@ -321,7 +322,7 @@ void Entity::DrawOrGetDistance(void) { double thetaa, thetab, dtheta; ArcGetAngles(&thetaa, &thetab, &dtheta); - int i, n = 3 + (int)(CIRCLE_SIDES(ra)*dtheta/(2*PI)); + int i, n = 3 + (int)(SS.CircleSides(ra)*dtheta/(2*PI)); Vector prev = pa; for(i = 1; i <= n; i++) { double theta = thetaa + (dtheta*i)/n; @@ -340,7 +341,7 @@ void Entity::DrawOrGetDistance(void) { Vector center = SS.GetEntity(point[0])->PointGetNum(); Vector u = q.RotationU(), v = q.RotationV(); - int i, c = CIRCLE_SIDES(r); + int i, c = SS.CircleSides(r); Vector prev = u.ScaledBy(r).Plus(center); for(i = 1; i <= c; i++) { double phi = (2*PI*i)/c; diff --git a/file.cpp b/file.cpp index db584ed..7eb1f82 100644 --- a/file.cpp +++ b/file.cpp @@ -140,6 +140,7 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { '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.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) }, diff --git a/glhelper.cpp b/glhelper.cpp index c88b1a3..87b2520 100644 --- a/glhelper.cpp +++ b/glhelper.cpp @@ -175,9 +175,7 @@ void glxFillMesh(int specColor, SMesh *m, DWORD h, DWORD s1, DWORD s2) color = specColor; } if(color != prevColor) { - GLfloat mpf[] = { ((color >> 0) & 0xff) / 255.0f, - ((color >> 8) & 0xff) / 255.0f, - ((color >> 16) & 0xff) / 255.0f, 1.0 }; + GLfloat mpf[] = { REDf(color), GREENf(color), BLUEf(color), 1.0 }; glEnd(); glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mpf); prevColor = color; diff --git a/graphicswin.cpp b/graphicswin.cpp index b1454eb..95409c1 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -65,7 +65,6 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "&Cubic Segment\t3", MNU_CUBIC, '3', mReq }, { 1, NULL, 0, NULL }, { 1, "Sym&bolic Variable\tB", 0, 'B', mReq }, -{ 1, "&Import From File...\tI", 0, 'I', mReq }, { 1, NULL, 0, NULL }, { 1, "To&ggle Construction\tG", MNU_CONSTRUCTION, 'G', mReq }, @@ -73,6 +72,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "&Distance / Diameter\tShift+D", MNU_DISTANCE_DIA, 'D'|S, mCon }, { 1, "A&ngle\tShift+N", MNU_ANGLE, 'N'|S, mCon }, { 1, "Other S&upplementary Angle\tShift+U", MNU_OTHER_ANGLE, 'U'|S, mCon }, +{ 1, "Toggle &Reference Dim\tShift+R", MNU_REFERENCE, 'R'|S, mCon }, { 1, NULL, 0, NULL }, { 1, "&Horizontal\tShift+H", MNU_HORIZONTAL, 'H'|S, mCon }, { 1, "&Vertical\tShift+V", MNU_VERTICAL, 'V'|S, mCon }, @@ -83,7 +83,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "At &Midpoint\tShift+M", MNU_AT_MIDPOINT, 'M'|S, mCon }, { 1, "S&ymmetric\tShift+Y", MNU_SYMMETRIC, 'Y'|S, mCon }, { 1, "Para&llel\tShift+L", MNU_PARALLEL, 'L'|S, mCon }, -{ 1, "Same O&rientation\tShift+R", MNU_ORIENTED_SAME, 'R'|S, mCon }, +{ 1, "Same Orient&ation\tShift+A", MNU_ORIENTED_SAME, 'A'|S, mCon }, { 1, NULL, 0, NULL }, { 1, "Sym&bolic Equation\tShift+B", 0, 'B'|S, NULL }, { 1, NULL, 0, NULL }, @@ -187,6 +187,59 @@ void GraphicsWindow::AnimateOntoWorkplane(void) { InvalidateGraphics(); } +void GraphicsWindow::HandlePointForZoomToFit(Vector p, + Point2d *pmax, Point2d *pmin) +{ + Point2d p2 = ProjectPoint(p); + pmax->x = max(pmax->x, p2.x); + pmax->y = max(pmax->y, p2.y); + pmin->x = min(pmin->x, p2.x); + pmin->y = min(pmin->y, p2.y); +} +void GraphicsWindow::ZoomToFit(void) { + int i, j; + Point2d pmax = { -1e12, -1e12 }, pmin = { 1e12, 1e12 }; + + HandlePointForZoomToFit(Vector::From(0, 0, 0), &pmax, &pmin); + + for(i = 0; i < SS.entity.n; i++) { + Entity *e = &(SS.entity.elem[i]); + if(!e->IsPoint()) continue; + if(!e->IsVisible()) continue; + HandlePointForZoomToFit(e->PointGetNum(), &pmax, &pmin); + } + Group *g = SS.GetGroup(activeGroup); + for(i = 0; i < g->mesh.l.n; i++) { + STriangle *tr = &(g->mesh.l.elem[i]); + HandlePointForZoomToFit(tr->a, &pmax, &pmin); + HandlePointForZoomToFit(tr->b, &pmax, &pmin); + HandlePointForZoomToFit(tr->c, &pmax, &pmin); + } + for(i = 0; i < g->poly.l.n; i++) { + SContour *sc = &(g->poly.l.elem[i]); + for(j = 0; j < sc->l.n; j++) { + HandlePointForZoomToFit(sc->l.elem[j].p, &pmax, &pmin); + } + } + + pmax = pmax.ScaledBy(1/scale); + pmin = pmin.ScaledBy(1/scale); + 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)); + + 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(100, min(scalex, scaley)); + } +} + void GraphicsWindow::MenuView(int id) { switch(id) { case MNU_ZOOM_IN: @@ -198,6 +251,7 @@ void GraphicsWindow::MenuView(int id) { break; case MNU_ZOOM_TO_FIT: + SS.GW.ZoomToFit(); break; case MNU_SHOW_TEXT_WND: diff --git a/groupmesh.cpp b/groupmesh.cpp index 11c87da..02653bb 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -123,7 +123,17 @@ void Group::GenerateMesh(void) { Vector axis = SS.GetEntity(predef.entityB)->VectorGetNum(); axis = axis.WithMagnitude(1); - int n = 20; + // Calculate the max radius, to determine fineness of mesh + double r, rmax = 0; + for(i = 0; i < edges.l.n; i++) { + SEdge *edge = &(edges.l.elem[i]); + r = (edge->a).DistanceToLine(orig, axis); + rmax = max(r, rmax); + r = (edge->b).DistanceToLine(orig, axis); + rmax = max(r, rmax); + } + + int n = SS.CircleSides(rmax); for(a = 0; a <= n; a++) { double thetai = (2*PI*WRAP(a-1, n))/n, thetaf = (2*PI*a)/n; for(i = 0; i < edges.l.n; i++) { @@ -139,18 +149,23 @@ void Group::GenerateMesh(void) { Vector ab = (edge->b).Minus(edge->a); Vector out = ((src->poly).normal).Cross(ab); + // This is a vector, not a point, so no origin for rotation out = out.RotatedAbout(axis, thetai); + // The line sweeps out a quad, so two triangles STriangle quad1 = STriangle::From(meta, ai, bi, af), quad2 = STriangle::From(meta, af, bi, bf); + + // Could be only one of the triangles has area; be sure + // to use that one for normal checking, then. Vector n1 = quad1.Normal(), n2 = quad2.Normal(); Vector n = (n1.Magnitude() > n2.Magnitude()) ? n1 : n2; if(n.Dot(out) < 0) { quad1.FlipNormal(); quad2.FlipNormal(); } - // If one of the endpoints lies on the axis of rotation, - // then the quad is just a single triangle + // One or both of the endpoints might lie on the axis of + // rotation, in which case its triangle is zero-area. if(da >= LENGTH_EPS) outm.AddTriangle(&quad1); if(db >= LENGTH_EPS) outm.AddTriangle(&quad2); } diff --git a/mesh.cpp b/mesh.cpp index 18e8279..0f12c75 100644 --- a/mesh.cpp +++ b/mesh.cpp @@ -220,7 +220,6 @@ void SMesh::MakeFromUnion(SMesh *a, SMesh *b) { } void SMesh::MakeFromDifference(SMesh *a, SMesh *b) { - SDWORD in = GetMilliseconds(); SBsp3 *bspa = SBsp3::FromMesh(a); SBsp3 *bspb = SBsp3::FromMesh(b); @@ -231,8 +230,6 @@ void SMesh::MakeFromDifference(SMesh *a, SMesh *b) { flipNormal = false; keepCoplanar = false; AddAgainstBsp(a, bspb); - dbp("dt = %d", GetMilliseconds() - in); - dbp("tris = %d", l.n); } bool SMesh::MakeFromInterferenceCheck(SMesh *srca, SMesh *srcb, SMesh *error) { diff --git a/sketch.h b/sketch.h index 1b899d7..bfff8df 100644 --- a/sketch.h +++ b/sketch.h @@ -461,6 +461,7 @@ public: void LineDrawOrGetDistance(Vector a, Vector b); void DrawOrGetDistance(Vector *labelPos); double EllipticalInterpolation(double rx, double ry, double theta); + char *Label(void); void DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu); void DoProjectedPoint(Vector *p); @@ -472,6 +473,7 @@ public: bool HasLabel(void); void Generate(IdList *l); + void GenerateReal(IdList *l); // Some helpers when generating symbolic constraint equations void ModifyToSatisfy(void); void AddEq(IdList *l, Expr *expr, int index); diff --git a/solvespace.cpp b/solvespace.cpp index f3ce3b6..2b8d00f 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -3,9 +3,41 @@ SolveSpace SS; void SolveSpace::Init(char *cmdLine) { + 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] = ((int)CnfThawDWORD( 700, "LightIntensity_0"))/1000.0; + lightIntensity[1] = ((int)CnfThawDWORD( 400, "LightIntensity_1"))/1000.0; + // Light positions + lightPos[0].x = ((int)CnfThawDWORD(-500, "LightPos_0_Right" ))/1000.0; + lightPos[0].y = ((int)CnfThawDWORD( 500, "LightPos_0_Up" ))/1000.0; + lightPos[0].z = ((int)CnfThawDWORD( 0, "LightPos_0_Forward" ))/1000.0; + lightPos[1].x = ((int)CnfThawDWORD( 500, "LightPos_1_Right" ))/1000.0; + lightPos[1].y = ((int)CnfThawDWORD( 0, "LightPos_1_Up" ))/1000.0; + lightPos[1].z = ((int)CnfThawDWORD( 0, "LightPos_1_Forward" ))/1000.0; + // Mesh tolerance + meshTol = ((int)CnfThawDWORD(1000, "MeshTolerance"))/1000.0; + // 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(); + + // 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); @@ -13,16 +45,49 @@ void SolveSpace::Init(char *cmdLine) { 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 + CnfFreezeDWORD((int)(lightIntensity[0]*1000), "LightIntensity_0"); + CnfFreezeDWORD((int)(lightIntensity[1]*1000), "LightIntensity_1"); + // Light positions + CnfFreezeDWORD((int)(lightPos[0].x*1000), "LightPos_0_Right"); + CnfFreezeDWORD((int)(lightPos[0].y*1000), "LightPos_0_Up"); + CnfFreezeDWORD((int)(lightPos[0].z*1000), "LightPos_0_Forward"); + CnfFreezeDWORD((int)(lightPos[1].x*1000), "LightPos_1_Right"); + CnfFreezeDWORD((int)(lightPos[1].y*1000), "LightPos_1_Up"); + CnfFreezeDWORD((int)(lightPos[1].z*1000), "LightPos_1_Forward"); + // Mesh tolerance + CnfFreezeDWORD((int)(meshTol*1000), "MeshTolerance"); + + ExitNow(); +} + void SolveSpace::DoLater(void) { if(later.generateAll) GenerateAll(); if(later.showTW) TW.Show(); ZERO(&later); } +int SolveSpace::CircleSides(double r) { + int s = 7 + (int)(sqrt(r*SS.GW.scale/meshTol)); + return min(s, 40); +} + void SolveSpace::AfterNewFile(void) { ReloadAllImported(); GenerateAll(-1, -1); @@ -249,6 +314,14 @@ void SolveSpace::GenerateAll(int first, int last) { } } + // 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(); @@ -459,7 +532,7 @@ void SolveSpace::MenuFile(int id) { case GraphicsWindow::MNU_EXIT: if(!SS.OkayToStartNewFile()) break; - ExitNow(); + SS.Exit(); break; default: oops(); diff --git a/solvespace.h b/solvespace.h index 96def22..283e1df 100644 --- a/solvespace.h +++ b/solvespace.h @@ -80,6 +80,10 @@ void dbp(char *str, ...); void Error(char *str, ...); void ExitNow(void); +void CnfFreezeString(char *str, char *name); +void CnfFreezeDWORD(DWORD v, char *name); +void CnfThawString(char *str, int maxLen, char *name); +DWORD CnfThawDWORD(DWORD v, char *name); void *AllocTemporary(int n); void FreeAllTemporary(void); @@ -242,10 +246,21 @@ public: void UndoClearState(UndoState *ut); void UndoClearStack(UndoStack *uk); + // Little bits of extra configuration state + static const int MODEL_COLORS = 8; + int modelColor[MODEL_COLORS]; + Vector lightPos[2]; + double lightIntensity[2]; + double meshTol; + + void Init(char *cmdLine); + void Exit(void); + + int CircleSides(double r); + // File load/save routines, including the additional files that get // loaded when we have import groups. FILE *fh; - void Init(char *cmdLine); void AfterNewFile(void); static void RemoveFromRecentList(char *file); static void AddToRecentList(char *file); diff --git a/textwin.cpp b/textwin.cpp index ea6f65a..c2532a3 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -31,16 +31,6 @@ void TextWindow::ClearSuper(void) { shown = &(showns[shownIndex]); ClearScreen(); Show(); - - // Default list of colors for the model material - modelColor[0] = RGB(150, 150, 150); - modelColor[1] = RGB(100, 100, 100); - modelColor[2] = RGB( 30, 30, 30); - modelColor[3] = RGB(150, 0, 0); - modelColor[4] = RGB( 0, 100, 0); - modelColor[5] = RGB( 0, 80, 80); - modelColor[6] = RGB( 0, 0, 130); - modelColor[7] = RGB( 80, 0, 80); } void TextWindow::ClearScreen(void) { @@ -97,6 +87,16 @@ void TextWindow::Printf(bool halfLine, char *fmt, ...) { sprintf(buf, "%08x", v); break; } + case '@': { + double v = va_arg(vl, double); + sprintf(buf, "%.2f", v); + break; + } + case '2': { + double v = va_arg(vl, double); + sprintf(buf, "%s%.2f", v < 0 ? "" : " ", v); + break; + } case '3': { double v = va_arg(vl, double); sprintf(buf, "%s%.3f", v < 0 ? "" : " ", v); @@ -201,10 +201,12 @@ void TextWindow::Show(void) { if(SS.GW.pending.description) { // A pending operation (that must be completed with the mouse in // the graphics window) will preempt our usual display. + HideTextEditControl(); ShowHeader(false); Printf(false, ""); Printf(false, "%s", SS.GW.pending.description); } else if(gs.n > 0) { + HideTextEditControl(); ShowHeader(false); DescribeSelection(); } else { @@ -216,6 +218,7 @@ void TextWindow::Show(void) { case SCREEN_LIST_OF_GROUPS: ShowListOfGroups(); break; case SCREEN_GROUP_INFO: ShowGroupInfo(); break; case SCREEN_GROUP_SOLVE_INFO: ShowGroupSolveInfo(); break; + case SCREEN_CONFIGURATION: ShowConfiguration(); break; } } InvalidateText(); @@ -437,15 +440,12 @@ void TextWindow::ScreenToggleGroupShown(int link, DWORD v) { } void TextWindow::ScreenShowGroupsSpecial(int link, DWORD v) { int i; - bool before = true; for(i = 0; i < SS.group.n; i++) { Group *g = &(SS.group.elem[i]); - if(g->h.v == SS.GW.activeGroup.v) { - before = false; - } else if(before && link == 's') { + if(link == 's') { g->visible = true; - } else if(!before && link == 'h') { + } else { g->visible = false; } } @@ -471,6 +471,9 @@ void TextWindow::ScreenHowGroupSolved(int link, DWORD v) { SS.TW.OneScreenForwardTo(SCREEN_GROUP_SOLVE_INFO); SS.TW.shown->group.v = v; } +void TextWindow::ScreenShowConfiguration(int link, DWORD v) { + SS.TW.OneScreenForwardTo(SCREEN_CONFIGURATION); +} void TextWindow::ShowListOfGroups(void) { Printf(true, "%Ftactv show ok group-name%E"); int i; @@ -508,10 +511,11 @@ void TextWindow::ShowListOfGroups(void) { if(active) afterActive = true; } - Printf(true, " %Fl%Ls%fshow all groups before active%E", - &(TextWindow::ScreenShowGroupsSpecial)); - Printf(false, " %Fl%Lh%fhide all groups after active%E", + Printf(true, " %Fl%Ls%fshow all%E / %Fl%Lh%fhide all%E", + &(TextWindow::ScreenShowGroupsSpecial), &(TextWindow::ScreenShowGroupsSpecial)); + Printf(false, " %Fl%Ls%fconfiguration%E", + &(TextWindow::ScreenShowConfiguration)); } @@ -569,8 +573,8 @@ void TextWindow::ScreenColor(int link, DWORD v) { SS.UndoRemember(); Group *g = SS.GetGroup(SS.TW.shown->group); - if(v < 0 || v >= MODEL_COLORS) return; - g->color = SS.TW.modelColor[v]; + if(v < 0 || v >= SS.MODEL_COLORS) return; + g->color = SS.modelColor[v]; SS.MarkGroupDirty(g->h); SS.GenerateAll(); SS.GW.ClearSuper(); @@ -610,7 +614,7 @@ void TextWindow::ShowGroupInfo(void) { Printf(true, "%FtGROUP %E%s", g->DescriptionString()); } else { Printf(true, "%FtGROUP %E%s " - "(%Fl%Ll%D%frename%E / %Fl%Ll%D%fdel%E)", + "[%Fl%Ll%D%frename%E/%Fl%Ll%D%fdel%E]", g->DescriptionString(), g->h.v, &TextWindow::ScreenChangeGroupName, g->h.v, &TextWindow::ScreenDeleteGroup); @@ -646,7 +650,7 @@ void TextWindow::ShowGroupInfo(void) { if(g->type == Group::ROTATE || g->type == Group::TRANSLATE) { int times = (int)(g->exprA->Eval()); - Printf(true, "%Ft%s%E %d time%s %Fl%Ll%D%f(change)%E", + Printf(true, "%Ft%s%E %d time%s %Fl%Ll%D%f[change]%E", s2, times, times == 1 ? "" : "s", g->h.v, &TextWindow::ScreenChangeExprA); } @@ -681,14 +685,14 @@ void TextWindow::ShowGroupInfo(void) { if(g->type == Group::EXTRUDE || g->type == Group::LATHE) { #define TWOX(v) v v Printf(true, "%FtM_COLOR%E " TWOX(TWOX(TWOX("%Bp%D%f%Ln %Bd%E "))), - 0x80000000 | modelColor[0], 0, &TextWindow::ScreenColor, - 0x80000000 | modelColor[1], 1, &TextWindow::ScreenColor, - 0x80000000 | modelColor[2], 2, &TextWindow::ScreenColor, - 0x80000000 | modelColor[3], 3, &TextWindow::ScreenColor, - 0x80000000 | modelColor[4], 4, &TextWindow::ScreenColor, - 0x80000000 | modelColor[5], 5, &TextWindow::ScreenColor, - 0x80000000 | modelColor[6], 6, &TextWindow::ScreenColor, - 0x80000000 | modelColor[7], 7, &TextWindow::ScreenColor); + 0x80000000 | SS.modelColor[0], 0, &TextWindow::ScreenColor, + 0x80000000 | SS.modelColor[1], 1, &TextWindow::ScreenColor, + 0x80000000 | SS.modelColor[2], 2, &TextWindow::ScreenColor, + 0x80000000 | SS.modelColor[3], 3, &TextWindow::ScreenColor, + 0x80000000 | SS.modelColor[4], 4, &TextWindow::ScreenColor, + 0x80000000 | SS.modelColor[5], 5, &TextWindow::ScreenColor, + 0x80000000 | SS.modelColor[6], 6, &TextWindow::ScreenColor, + 0x80000000 | SS.modelColor[7], 7, &TextWindow::ScreenColor); } Printf(true, "%Ftrequests in group"); @@ -727,13 +731,15 @@ void TextWindow::ShowGroupInfo(void) { void TextWindow::ShowGroupSolveInfo(void) { Group *g = SS.group.FindById(shown->group); + if(g->solved.how == Group::SOLVED_OKAY) { + // Go back to the default group info screen + shown->screen = SCREEN_GROUP_INFO; + Show(); + return; + } + Printf(true, "%FtGROUP %E%s", g->DescriptionString()); - switch(g->solved.how) { - case Group::SOLVED_OKAY: - Printf(true, " %Fsgroup solved okay%E"); - break; - case Group::DIDNT_CONVERGE: Printf(true, " %FxSOLVE FAILED!%Fd no convergence"); break; @@ -757,6 +763,69 @@ void TextWindow::ShowGroupSolveInfo(void) { } } +void TextWindow::ScreenChangeLightPosition(int link, DWORD v) { + char str[1024]; + sprintf(str, "%.2f, %.2f, %.2f", CO(SS.lightPos[v])); + ShowTextEditControl(29+2*v, 8, str); + SS.TW.edit.meaning = EDIT_LIGHT_POSITION; + SS.TW.edit.i = v; +} +void TextWindow::ScreenChangeLightIntensity(int link, DWORD v) { + char str[1024]; + sprintf(str, "%.2f", SS.lightIntensity[v]); + ShowTextEditControl(29+2*v, 30, str); + SS.TW.edit.meaning = EDIT_LIGHT_INTENSITY; + SS.TW.edit.i = v; +} +void TextWindow::ScreenChangeColor(int link, DWORD v) { + char str[1024]; + sprintf(str, "%.2f, %.2f, %.2f", + REDf(SS.modelColor[v]), + GREENf(SS.modelColor[v]), + BLUEf(SS.modelColor[v])); + ShowTextEditControl(9+2*v, 12, str); + SS.TW.edit.meaning = EDIT_COLOR; + SS.TW.edit.i = v; +} +void TextWindow::ScreenChangeMeshTolerance(int link, DWORD v) { + char str[1024]; + sprintf(str, "%.2f", SS.meshTol); + ShowTextEditControl(37, 3, str); + SS.TW.edit.meaning = EDIT_MESH_TOLERANCE; +} +void TextWindow::ShowConfiguration(void) { + int i; + Printf(true, "%Ft material 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 position 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.lightPos[i]), i, &ScreenChangeLightPosition, + SS.lightIntensity[i], i, &ScreenChangeLightIntensity); + } + + Printf(false, ""); + Printf(false, "%Ft mesh tolerance (smaller is finer)%E"); + Printf(false, "%Ba %2 %Fl%Ll%f%D[change]%E; now %d triangles", + SS.meshTol, + &ScreenChangeMeshTolerance, 0, + SS.group.elem[SS.group.n-1].mesh.l.n); +} + void TextWindow::EditControlDone(char *s) { switch(edit.meaning) { case EDIT_TIMES_REPEATED: { @@ -770,7 +839,6 @@ void TextWindow::EditControlDone(char *s) { SS.MarkGroupDirty(g->h); SS.later.generateAll = true; - SS.later.showTW = true; } else { Error("Not a valid number or expression: '%s'", s); } @@ -792,11 +860,39 @@ void TextWindow::EditControlDone(char *s) { Group *g = SS.GetGroup(edit.group); g->name.strcpy(s); } - SS.later.showTW = true; SS.unsaved = true; break; } + case EDIT_LIGHT_INTENSITY: + SS.lightIntensity[edit.i] = min(1, max(0, atof(s))); + InvalidateGraphics(); + break; + case EDIT_LIGHT_POSITION: { + double x, y, z; + if(sscanf(s, "%lf, %lf, %lf", &x, &y, &z)==3) { + SS.lightPos[edit.i] = Vector::From(x, y, z); + } else { + Error("Bad format: specify coordinates as x, y, z"); + } + InvalidateGraphics(); + break; + } + case EDIT_COLOR: { + double r, g, b; + if(sscanf(s, "%lf, %lf, %lf", &r, &g, &b)==3) { + SS.modelColor[edit.i] = RGB(r*255, g*255, b*255); + } else { + Error("Bad format: specify color as r, g, b"); + } + break; + } + case EDIT_MESH_TOLERANCE: { + SS.meshTol = min(10, max(0.1, atof(s))); + SS.GenerateAll(0, INT_MAX); + break; + } } + SS.later.showTW = true; HideTextEditControl(); edit.meaning = EDIT_NOTHING; } diff --git a/ui.h b/ui.h index 7d0e8dc..09152dd 100644 --- a/ui.h +++ b/ui.h @@ -10,6 +10,9 @@ public: #ifndef RGB #define RGB(r, g, b) ((r) | ((g) << 8) | ((b) << 16)) #endif +#define REDf(v) ((((v) >> 0) & 0xff) / 255.0f) +#define GREENf(v) ((((v) >> 8) & 0xff) / 255.0f) +#define BLUEf(v) ((((v) >> 16) & 0xff) / 255.0f) typedef struct { char c; int color; @@ -17,9 +20,6 @@ public: static const Color fgColors[]; static const Color bgColors[]; - static const int MODEL_COLORS = 8; - int modelColor[MODEL_COLORS]; - BYTE text[MAX_ROWS][MAX_COLS]; typedef void LinkFunction(int link, DWORD v); static const int NOT_A_LINK = 0; @@ -45,6 +45,7 @@ public: static const int SCREEN_LIST_OF_GROUPS = 0; static const int SCREEN_GROUP_INFO = 1; static const int SCREEN_GROUP_SOLVE_INFO = 2; + static const int SCREEN_CONFIGURATION = 3; typedef struct { int screen; hGroup group; @@ -58,8 +59,13 @@ public: static const int EDIT_NOTHING = 0; static const int EDIT_TIMES_REPEATED = 1; static const int EDIT_GROUP_NAME = 2; + static const int EDIT_LIGHT_POSITION = 3; + static const int EDIT_LIGHT_INTENSITY = 4; + static const int EDIT_COLOR = 5; + static const int EDIT_MESH_TOLERANCE = 6; struct { int meaning; + int i; hGroup group; } edit; @@ -73,6 +79,7 @@ public: void ShowListOfGroups(void); void ShowGroupInfo(void); void ShowGroupSolveInfo(void); + void ShowConfiguration(void); // Special screen, based on selection void DescribeSelection(void); @@ -90,18 +97,23 @@ public: static void ScreenHoverRequest(int link, DWORD v); static void ScreenSelectRequest(int link, DWORD v); static void ScreenSelectConstraint(int link, DWORD v); + static void ScreenUnselectAll(int link, DWORD v); static void ScreenChangeOneOrTwoSides(int link, DWORD v); static void ScreenChangeMeshCombine(int link, DWORD v); static void ScreenColor(int link, DWORD v); - static void ScreenUnselectAll(int link, DWORD v); + static void ScreenShowConfiguration(int link, DWORD v); static void ScreenNavigation(int link, DWORD v); // These ones do stuff with the edit control static void ScreenChangeExprA(int link, DWORD v); static void ScreenChangeGroupName(int link, DWORD v); + static void ScreenChangeLightPosition(int link, DWORD v); + static void ScreenChangeLightIntensity(int link, DWORD v); + static void ScreenChangeColor(int link, DWORD v); + static void ScreenChangeMeshTolerance(int link, DWORD v); void EditControlDone(char *s); }; @@ -155,6 +167,7 @@ public: MNU_DISTANCE_DIA, MNU_ANGLE, MNU_OTHER_ANGLE, + MNU_REFERENCE, MNU_EQUAL, MNU_RATIO, MNU_ON_ENTITY, @@ -203,7 +216,9 @@ public: void NormalizeProjectionVectors(void); Point2d ProjectPoint(Vector p); void AnimateOntoWorkplane(void); - Vector VectorFromProjs(double right, double up, double forward); + Vector VectorFromProjs(Vector rightUpForward); + void HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin); + void ZoomToFit(void); typedef enum { UNIT_MM = 0, diff --git a/win32/w32main.cpp b/win32/w32main.cpp index 8eedc66..89f8e49 100644 --- a/win32/w32main.cpp +++ b/win32/w32main.cpp @@ -71,6 +71,23 @@ void ExitNow(void) { PostQuitMessage(0); } +//----------------------------------------------------------------------------- +// Helpers so that we can read/write registry keys from the platform- +// independent code. +//----------------------------------------------------------------------------- +void CnfFreezeString(char *str, char *name) + { FreezeStringF(str, FREEZE_SUBKEY, name); } + +void CnfFreezeDWORD(DWORD v, char *name) + { FreezeDWORDF(v, FREEZE_SUBKEY, name); } + +void CnfThawString(char *str, int maxLen, char *name) + { ThawStringF(str, maxLen, FREEZE_SUBKEY, name); } + +DWORD CnfThawDWORD(DWORD v, char *name) + { return ThawDWORDF(v, FREEZE_SUBKEY, name); } + + //----------------------------------------------------------------------------- // A separate heap, on which we allocate expressions. Maybe a bit faster, // since no fragmentation issues whatsoever, and it also makes it possible @@ -877,13 +894,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, InitCommonControls(); - int i; - for(i = 0; i < MAX_RECENT; i++) { - char name[100]; - sprintf(name, "RecentFile_%d", i); - ThawStringF(RecentFile[i], MAX_PATH, FREEZE_SUBKEY, name); - } - // A monospaced font FixedFont = CreateFont(TEXT_HEIGHT-4, TEXT_WIDTH, 0, 0, FW_REGULAR, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, @@ -936,11 +946,5 @@ done: // Write everything back to the registry FreezeWindowPos(TextWnd); FreezeWindowPos(GraphicsWnd); - for(i = 0; i < MAX_RECENT; i++) { - char name[100]; - sprintf(name, "RecentFile_%d", i); - FreezeStringF(RecentFile[i], FREEZE_SUBKEY, name); - } - return 0; } diff --git a/wishlist.txt b/wishlist.txt index 13b8a0e..72d4886 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,17 +1,15 @@ - STL check for meshes, and T intersection removal STL export better triangle combining (Simplify()) for meshes DXF export compress file format (binary?) partitioned subsystems in the solver -arbitrary color specification -specify tolerance for meshing -specify light positions (all on one configuration screen) TTF font text display with proper formatting/units more measurements -reference dimensions (just to look at, no equations) -some kind of rounding +some kind of rounding / chamfer +auto-constrain translate in then-active workplane +remove back button in browser? +