diff --git a/clipboard.cpp b/clipboard.cpp index d1fc1328..ccd6cd75 100644 --- a/clipboard.cpp +++ b/clipboard.cpp @@ -114,14 +114,14 @@ void GraphicsWindow::CopySelection(void) { } } -void GraphicsWindow::PasteClipboard(Vector trans, double theta, bool mirror) { - SS.UndoRemember(); +void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) { ClearSelection(); Entity *wrkpl = SK.GetEntity(ActiveWorkplane()); Entity *wrkpln = SK.GetEntity(wrkpl->normal); Vector u = wrkpln->NormalU(), v = wrkpln->NormalV(), + n = wrkpln->NormalN(), p = SK.GetEntity(wrkpl->point[0])->PointGetNum(); @@ -144,17 +144,23 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, bool mirror) { NULL, &pts, NULL, &hasDistance); for(int i = 0; i < pts; i++) { Vector pt = cr->point[i]; - if(mirror) pt.x *= -1; - pt = Vector::From( cos(theta)*pt.x + sin(theta)*pt.y, - -sin(theta)*pt.x + cos(theta)*pt.y, - 0); + // We need the reflection to occur within the workplane; it may + // otherwise correspond to just a rotation as projected. + if(scale < 0) { + pt.x *= -1; + } + // Likewise the scale, which could otherwise take us out of the + // workplane. + pt = pt.ScaledBy(fabs(scale)); pt = pt.ScaleOutOfCsys(u, v, Vector::From(0, 0, 0)); pt = pt.Plus(p); + pt = pt.RotatedAbout(n, theta); pt = pt.Plus(trans); SK.GetEntity(hr.entity(i+1))->PointForceTo(pt); } if(hasDistance) { - SK.GetEntity(hr.entity(64))->DistanceForceTo(cr->distance); + SK.GetEntity(hr.entity(64))->DistanceForceTo( + cr->distance*fabs(scale)); } cr->newReq = hr; @@ -181,14 +187,31 @@ void GraphicsWindow::MenuClipboard(int id) { switch(id) { case MNU_PASTE: { + SS.UndoRemember(); Vector trans = SS.GW.projRight.ScaledBy(80/SS.GW.scale).Plus( SS.GW.projUp .ScaledBy(40/SS.GW.scale)); - SS.GW.PasteClipboard(trans, 0, false); + SS.GW.PasteClipboard(trans, 0, 1); break; } - case MNU_PASTE_TRANSFORM: + case MNU_PASTE_TRANSFORM: { + if(SS.clipboard.r.n == 0) { + Error("Clipboard is empty; nothing to paste."); + break; + } + + Entity *wrkpl = SK.GetEntity(SS.GW.ActiveWorkplane()); + Vector p = SK.GetEntity(wrkpl->point[0])->PointGetNum(); + SS.TW.shown.paste.times = 1; + SS.TW.shown.paste.trans = Vector::From(0, 0, 0); + SS.TW.shown.paste.theta = 0; + SS.TW.shown.paste.origin = p; + SS.TW.shown.paste.scale = 1; + SS.TW.GoToScreen(TextWindow::SCREEN_PASTE_TRANSFORMED); + SS.GW.ForceTextWindowShown(); + SS.later.showTW = true; break; + } case MNU_COPY: SS.GW.CopySelection(); @@ -210,3 +233,148 @@ void GraphicsWindow::MenuClipboard(int id) { } } +bool TextWindow::EditControlDoneForPaste(char *s) { + switch(edit.meaning) { + case EDIT_PASTE_TIMES_REPEATED: { + int v = atoi(s); + if(v > 0) { + shown.paste.times = v; + } else { + Error("Number of copies to paste must be at least one."); + } + break; + } + case EDIT_PASTE_ANGLE: + shown.paste.theta = WRAP_SYMMETRIC(atof(s)*PI/180, 2*PI); + break; + + case EDIT_PASTE_SCALE: { + double v = atof(s); + if(fabs(v) > 1e-6) { + shown.paste.scale = v; + } else { + Error("Scale cannot be zero."); + } + break; + } + + default: + return false; + } + return true; +} + +void TextWindow::ScreenChangePasteTransformed(int link, DWORD v) { + char str[300]; + switch(link) { + case 't': + sprintf(str, "%d", SS.TW.shown.paste.times); + ShowTextEditControl(10, 12, str); + SS.TW.edit.meaning = EDIT_PASTE_TIMES_REPEATED; + break; + + case 'r': + sprintf(str, "%.3f", SS.TW.shown.paste.theta*180/PI); + ShowTextEditControl(12, 12, str); + SS.TW.edit.meaning = EDIT_PASTE_ANGLE; + break; + + case 's': + sprintf(str, "%.3f", SS.TW.shown.paste.scale); + ShowTextEditControl(18, 12, str); + SS.TW.edit.meaning = EDIT_PASTE_SCALE; + break; + } +} + +void TextWindow::ScreenPasteTransformed(int link, DWORD v) { + SS.GW.GroupSelection(); + switch(link) { + case 'o': + if(SS.GW.gs.points == 1 && SS.GW.gs.n == 1) { + Entity *e = SK.GetEntity(SS.GW.gs.point[0]); + SS.TW.shown.paste.origin = e->PointGetNum(); + } else { + Error("Select one point to define origin of rotation."); + } + break; + + case 't': + if(SS.GW.gs.points == 2 && SS.GW.gs.n == 2) { + Entity *pa = SK.GetEntity(SS.GW.gs.point[0]), + *pb = SK.GetEntity(SS.GW.gs.point[1]); + SS.TW.shown.paste.trans = + (pb->PointGetNum()).Minus(pa->PointGetNum()); + } else { + Error("Select two points to define translation vector."); + } + break; + + case 'g': { + if(fabs(SS.TW.shown.paste.theta) < LENGTH_EPS && + SS.TW.shown.paste.trans.Magnitude() < LENGTH_EPS && + SS.TW.shown.paste.times != 1) + { + Message("Transformation is identity. So all copies will be " + "exactly on top of each other."); + } + if(SS.TW.shown.paste.times*SS.clipboard.r.n > 100) { + Error("Too many items to paste; split this into smaller " + "pastes."); + break; + } + if(!SS.GW.LockedInWorkplane()) { + Error("No workplane active."); + break; + } + Entity *wrkpl = SK.GetEntity(SS.GW.ActiveWorkplane()); + Entity *wrkpln = SK.GetEntity(wrkpl->normal); + Vector wn = wrkpln->NormalN(); + SS.UndoRemember(); + for(int i = 0; i < SS.TW.shown.paste.times; i++) { + Vector trans = SS.TW.shown.paste.trans.ScaledBy(i+1), + origin = SS.TW.shown.paste.origin; + double theta = SS.TW.shown.paste.theta*(i+1); + // desired transformation is Q*(p - o) + o + t = + // Q*p - Q*o + o + t = Q*p + (t + o - Q*o) + Vector t = trans.Plus( + origin).Minus( + origin.RotatedAbout(wn, theta)); + + SS.GW.PasteClipboard(t, theta, SS.TW.shown.paste.scale); + } + SS.TW.GoToScreen(SCREEN_LIST_OF_GROUPS); + SS.later.showTW = true; + break; + } + } + SS.GW.ClearSelection(); +} + +void TextWindow::ShowPasteTransformed(void) { + Printf(true, "%FtPASTE TRANSFORMED%E"); + Printf(true, "%Ba %FtREPEAT%E %d time%s %Fl%Lt%f[change]%E", + shown.paste.times, (shown.paste.times == 1) ? "" : "s", + &ScreenChangePasteTransformed); + Printf(false, "%Bd %FtROTATE%E %@° %Fl%Lr%f[change]%E", + shown.paste.theta*180/PI, + &ScreenChangePasteTransformed); + Printf(false, "%Ba %FtABOUT PT%E (%s, %s, %s) %Fl%Lo%f[use selected]%E", + SS.MmToString(shown.paste.origin.x), + SS.MmToString(shown.paste.origin.y), + SS.MmToString(shown.paste.origin.z), + &ScreenPasteTransformed); + Printf(false, "%Bd %FtTRANSLATE%E (%s, %s, %s) %Fl%Lt%f[use selected]%E", + SS.MmToString(shown.paste.trans.x), + SS.MmToString(shown.paste.trans.y), + SS.MmToString(shown.paste.trans.z), + &ScreenPasteTransformed); + Printf(false, "%Ba %FtSCALE%E %@ %Fl%Ls%f[change]%E", + shown.paste.scale, + &ScreenChangePasteTransformed); + + Printf(true, " %Fl%Lg%fpaste transformed now%E", &ScreenPasteTransformed); + + Printf(true, "(or %Fl%Ll%fcancel operation%E)", &ScreenHome); +} + diff --git a/dsc.h b/dsc.h index a5413d55..2298d0a6 100644 --- a/dsc.h +++ b/dsc.h @@ -39,7 +39,7 @@ public: Quaternion ToThe(double p); Quaternion Inverse(void); Quaternion Times(Quaternion b); - Quaternion MirrorZ(void); + Quaternion Mirror(void); }; class Vector { diff --git a/file.cpp b/file.cpp index b2d934c8..88083dde 100644 --- a/file.cpp +++ b/file.cpp @@ -93,7 +93,7 @@ const SolveSpace::SaveTable SolveSpace::SAVED[] = { { 'g', "Group.visible", 'b', &(SS.sv.g.visible) }, { 'g', "Group.suppress", 'b', &(SS.sv.g.suppress) }, { 'g', "Group.relaxConstraints", 'b', &(SS.sv.g.relaxConstraints) }, - { 'g', "Group.mirror", 'b', &(SS.sv.g.mirror) }, + { 'g', "Group.scale", 'f', &(SS.sv.g.scale) }, { 'g', "Group.remap", 'M', &(SS.sv.g.remap) }, { 'g', "Group.impFile", 'P', &(SS.sv.g.impFile) }, { 'g', "Group.impFileRel", 'P', &(SS.sv.g.impFileRel) }, @@ -378,11 +378,14 @@ void SolveSpace::LoadUsingTable(char *key, char *val) { break; } } - if(SAVED[i].type == 0) oops(); + if(SAVED[i].type == 0) { + fileLoadError = true; + } } bool SolveSpace::LoadFromFile(char *filename) { allConsistent = false; + fileLoadError = false; fh = fopen(filename, "rb"); if(!fh) { @@ -400,6 +403,7 @@ bool SolveSpace::LoadFromFile(char *filename) { SK.param.Clear(); SK.style.Clear(); memset(&sv, 0, sizeof(sv)); + sv.g.scale = 1; // default is 1, not 0; so legacy files need this char line[1024]; while(fgets(line, sizeof(line), fh)) { @@ -420,6 +424,7 @@ bool SolveSpace::LoadFromFile(char *filename) { } else if(strcmp(line, "AddGroup")==0) { SK.group.Add(&(sv.g)); ZERO(&(sv.g)); + sv.g.scale = 1; // default is 1, not 0; so legacy files need this } else if(strcmp(line, "AddParam")==0) { // params are regenerated, but we want to preload the values // for initial guesses @@ -450,12 +455,17 @@ bool SolveSpace::LoadFromFile(char *filename) { { // ignore the mesh or shell, since we regenerate that } else { - oops(); + fileLoadError = true; } } fclose(fh); + if(fileLoadError) { + Error("Unrecognized data in file. This file may be corrupt, or " + "from a new version of the program."); + } + return true; } diff --git a/generate.cpp b/generate.cpp index 8fa6eed1..39a225f7 100644 --- a/generate.cpp +++ b/generate.cpp @@ -152,6 +152,7 @@ void SolveSpace::GenerateAll(void) { // All clean; so just regenerate the entities, and don't solve anything. GenerateAll(-1, -1); } else { + SS.nakedEdges.Clear(); GenerateAll(firstDirty, lastVisible); } } diff --git a/graphicswin.cpp b/graphicswin.cpp index 0eeeec25..f4af8df8 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -43,7 +43,7 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "Paste &Transformed...\tCtrl+T", MNU_PASTE_TRANSFORM,'T'|C, mClip }, { 1, "&Delete\tDel", MNU_DELETE, 127, mClip }, { 1, NULL, 0, NULL }, -{ 1, "Select Edge Cha&in\tCtrl+I", MNU_SELECT_CHAIN, 'I'|C, mEdit }, +{ 1, "Select &Edge Chain\tCtrl+E", MNU_SELECT_CHAIN, 'E'|C, mEdit }, { 1, "Invert &Selection\tCtrl+A", MNU_INVERT_SEL, 'A'|C, mEdit }, { 1, "&Unselect All\tEsc", MNU_UNSELECT_ALL, 27, mEdit }, diff --git a/group.cpp b/group.cpp index 89b17c40..3426391d 100644 --- a/group.cpp +++ b/group.cpp @@ -27,6 +27,7 @@ void Group::MenuGroup(int id) { ZERO(&g); g.visible = true; g.color = RGB(100, 100, 100); + g.scale = 1; if(id >= RECENT_IMPORT && id < (RECENT_IMPORT + MAX_RECENT)) { strcpy(g.impFile, RecentFile[id-RECENT_IMPORT]); @@ -681,8 +682,7 @@ void Group::CopyEntity(IdList *el, en.param[5] = qvy; en.param[6] = qvz; } - en.numPoint = ep->actPoint; - if(mirror) en.numPoint.z *= -1; + en.numPoint = (ep->actPoint).ScaledBy(scale); break; case Entity::NORMAL_N_COPY: @@ -704,7 +704,7 @@ void Group::CopyEntity(IdList *el, en.param[3] = qvz; } en.numNormal = ep->actNormal; - if(mirror) en.numNormal = en.numNormal.MirrorZ(); + if(scale < 0) en.numNormal = en.numNormal.Mirror(); en.point[0] = Remap(ep->point[0], remap); break; @@ -712,7 +712,7 @@ void Group::CopyEntity(IdList *el, case Entity::DISTANCE_N_COPY: case Entity::DISTANCE: en.type = Entity::DISTANCE_N_COPY; - en.numDistance = ep->actDistance; + en.numDistance = ep->actDistance*fabs(scale); break; case Entity::FACE_NORMAL_PT: @@ -739,13 +739,8 @@ void Group::CopyEntity(IdList *el, en.param[5] = qvy; en.param[6] = qvz; } - en.numPoint = ep->actPoint; - en.numNormal = ep->actNormal; - if(mirror) { - if(en.type != Entity::FACE_N_ROT_TRANS) oops(); - en.numPoint.z *= -1; - en.numNormal.vz *= -1; - } + en.numPoint = (ep->actPoint).ScaledBy(scale); + en.numNormal = (ep->actNormal).ScaledBy(scale); break; default: { diff --git a/groupmesh.cpp b/groupmesh.cpp index d770ce19..f2df6e64 100644 --- a/groupmesh.cpp +++ b/groupmesh.cpp @@ -119,7 +119,7 @@ void Group::GenerateForStepAndRepeat(T *steps, T *outs) { Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); trans = trans.ScaledBy(ap); transd.MakeFromTransformationOf(steps, - trans, Quaternion::IDENTITY, false); + trans, Quaternion::IDENTITY, 1.0); } else { Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); double theta = ap * SK.GetParam(h.param(3))->val; @@ -128,7 +128,7 @@ void Group::GenerateForStepAndRepeat(T *steps, T *outs) { Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z); // Rotation is centered at t; so A(x - t) + t = Ax + (t - At) transd.MakeFromTransformationOf(steps, - trans.Minus(q.Rotate(trans)), q, false); + trans.Minus(q.Rotate(trans)), q, 1.0); } // We need to rewrite any plane face entities to the transformed ones. @@ -292,10 +292,10 @@ void Group::GenerateShellAndMesh(void) { SK.GetParam(h.param(5))->val, SK.GetParam(h.param(6))->val }; - thisMesh.MakeFromTransformationOf(&impMesh, offset, q, mirror); + thisMesh.MakeFromTransformationOf(&impMesh, offset, q, scale); thisMesh.RemapFaces(this, 0); - thisShell.MakeFromTransformationOf(&impShell, offset, q, mirror); + thisShell.MakeFromTransformationOf(&impShell, offset, q, scale); thisShell.RemapFaces(this, 0); } diff --git a/mesh.cpp b/mesh.cpp index 3cf95501..f65f2fd0 100644 --- a/mesh.cpp +++ b/mesh.cpp @@ -293,16 +293,16 @@ void SMesh::MakeFromAssemblyOf(SMesh *a, SMesh *b) { MakeFromCopyOf(b); } -void SMesh::MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q, - bool mirror) +void SMesh::MakeFromTransformationOf(SMesh *a, + Vector trans, Quaternion q, double scale) { STriangle *tr; for(tr = a->l.First(); tr; tr = a->l.NextAfter(tr)) { STriangle tt = *tr; - if(mirror) { - tt.a.z *= -1; - tt.b.z *= -1; - tt.c.z *= -1; + tt.a = (tt.a).ScaledBy(scale); + tt.b = (tt.b).ScaledBy(scale); + tt.c = (tt.c).ScaledBy(scale); + if(scale < 0) { // The mirroring would otherwise turn a closed mesh inside out. SWAP(Vector, tt.a, tt.b); } diff --git a/mouse.cpp b/mouse.cpp index 4ebc9382..117b2a1e 100644 --- a/mouse.cpp +++ b/mouse.cpp @@ -980,10 +980,28 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { } case DRAGGING_NEW_LINE_POINT: { + if(hover.entity.v) { + Entity *e = SK.GetEntity(hover.entity); + if(e->IsPoint()) { + hRequest hrl = pending.point.request(); + Entity *sp = SK.GetEntity(hrl.entity(1)); + if(( e->PointGetNum()).Equals( + (sp->PointGetNum()))) + { + // If we constrained by the hovered point, then we + // would create a zero-length line segment. That's + // not good, so just stop drawing. + ClearPending(); + break; + } + } + } + if(ConstrainPointByHovered(pending.point)) { ClearPending(); break; } + // Create a new line segment, so that we continue drawing. hRequest hr = AddRequest(Request::LINE_SEGMENT); SK.GetEntity(hr.entity(1))->PointForceTo(v); diff --git a/polygon.cpp b/polygon.cpp index 30a9e32f..bc2989d9 100644 --- a/polygon.cpp +++ b/polygon.cpp @@ -70,8 +70,13 @@ bool SEdge::EdgeCrosses(Vector ea, Vector eb, Vector *ppi, SPointList *spl) { Vector dthis = b.Minus(a); double tthis_eps = LENGTH_EPS/dthis.Magnitude(); - if(ea.Equals(a) && eb.Equals(b)) return true; - if(eb.Equals(a) && ea.Equals(b)) return true; + if((ea.Equals(a) && eb.Equals(b)) || + (eb.Equals(a) && ea.Equals(b))) + { + if(ppi) *ppi = a; + if(spl) spl->Add(a); + return true; + } dist_a = a.DistanceToLine(ea, d), dist_b = b.DistanceToLine(ea, d); @@ -87,17 +92,25 @@ bool SEdge::EdgeCrosses(Vector ea, Vector eb, Vector *ppi, SPointList *spl) { } // The edges are coincident. Make sure that neither endpoint lies // on the other + bool inters = false; double t; t = a.Minus(ea).DivPivoting(d); - if(t > t_eps && t < (1 - t_eps)) return true; + if(t > t_eps && t < (1 - t_eps)) inters = true; t = b.Minus(ea).DivPivoting(d); - if(t > t_eps && t < (1 - t_eps)) return true; + if(t > t_eps && t < (1 - t_eps)) inters = true; t = ea.Minus(a).DivPivoting(dthis); - if(t > tthis_eps && t < (1 - tthis_eps)) return true; + if(t > tthis_eps && t < (1 - tthis_eps)) inters = true; t = eb.Minus(a).DivPivoting(dthis); - if(t > tthis_eps && t < (1 - tthis_eps)) return true; - // So coincident but disjoint, okay. - return false; + if(t > tthis_eps && t < (1 - tthis_eps)) inters = true; + + if(inters) { + if(ppi) *ppi = a; + if(spl) spl->Add(a); + return true; + } else { + // So coincident but disjoint, okay. + return false; + } } // Lines are not parallel, so look for an intersection. diff --git a/polygon.h b/polygon.h index 3b455c49..5ba08670 100644 --- a/polygon.h +++ b/polygon.h @@ -231,8 +231,8 @@ public: void MakeFromDifferenceOf(SMesh *a, SMesh *b); void MakeFromCopyOf(SMesh *a); - void MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q, - bool mirror); + void MakeFromTransformationOf(SMesh *a, + Vector trans, Quaternion q, double scale); void MakeFromAssemblyOf(SMesh *a, SMesh *b); void MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d); diff --git a/sketch.h b/sketch.h index a48e4e02..b532d319 100644 --- a/sketch.h +++ b/sketch.h @@ -102,7 +102,7 @@ public: bool visible; bool suppress; bool relaxConstraints; - bool mirror; + double scale; bool clean; bool vvMeshClean; diff --git a/solvespace.h b/solvespace.h index 5f79d8d6..139bbea4 100644 --- a/solvespace.h +++ b/solvespace.h @@ -625,6 +625,7 @@ public: static void RemoveFromRecentList(char *file); static void AddToRecentList(char *file); char saveFile[MAX_PATH]; + bool fileLoadError; bool unsaved; typedef struct { char type; diff --git a/srf/boolean.cpp b/srf/boolean.cpp index 93f9a987..d1d4706f 100644 --- a/srf/boolean.cpp +++ b/srf/boolean.cpp @@ -687,7 +687,7 @@ void SShell::MakeFromAssemblyOf(SShell *a, SShell *b) { for(i = 0; i < 2; i++) { ab = (i == 0) ? a : b; for(c = ab->curve.First(); c; c = ab->curve.NextAfter(c)) { - cn = SCurve::FromTransformationOf(c, t, q, false); + cn = SCurve::FromTransformationOf(c, t, q, 1.0); cn.source = (i == 0) ? SCurve::FROM_A : SCurve::FROM_B; // surfA and surfB are wrong now, and we can't fix them until // we've assigned IDs to the surfaces. So we'll get that later. @@ -700,7 +700,7 @@ void SShell::MakeFromAssemblyOf(SShell *a, SShell *b) { for(i = 0; i < 2; i++) { ab = (i == 0) ? a : b; for(s = ab->surface.First(); s; s = ab->surface.NextAfter(s)) { - sn = SSurface::FromTransformationOf(s, t, q, false, true); + sn = SSurface::FromTransformationOf(s, t, q, 1.0, true); // All the trim curve IDs get rewritten; we know the new handles // to the curves since we recorded them in the previous step. STrimBy *stb; diff --git a/srf/curve.cpp b/srf/curve.cpp index d2e05762..d77fdf73 100644 --- a/srf/curve.cpp +++ b/srf/curve.cpp @@ -95,11 +95,11 @@ void SBezier::GetBoundingProjd(Vector u, Vector orig, } } -SBezier SBezier::TransformedBy(Vector t, Quaternion q, bool mirror) { +SBezier SBezier::TransformedBy(Vector t, Quaternion q, double scale) { SBezier ret = *this; int i; for(i = 0; i <= deg; i++) { - if(mirror) ret.ctrl[i].z *= -1; + ret.ctrl[i] = (ret.ctrl[i]).ScaledBy(scale); ret.ctrl[i] = (q.Rotate(ret.ctrl[i])).Plus(t); } return ret; @@ -189,7 +189,7 @@ SBezier SBezier::InPerspective(Vector u, Vector v, Vector n, Quaternion q = Quaternion::From(u, v); q = q.Inverse(); // we want Q*(p - o) = Q*p - Q*o - SBezier ret = this->TransformedBy(q.Rotate(origin).ScaledBy(-1), q, false); + SBezier ret = this->TransformedBy(q.Rotate(origin).ScaledBy(-1), q, 1.0); int i; for(i = 0; i <= deg; i++) { Vector4 ct = Vector4::From(ret.weight[i], ret.ctrl[i]); @@ -740,22 +740,22 @@ void SBezierLoopSetSet::Clear(void) { l.Clear(); } -SCurve SCurve::FromTransformationOf(SCurve *a, Vector t, Quaternion q, - bool mirror) +SCurve SCurve::FromTransformationOf(SCurve *a, + Vector t, Quaternion q, double scale) { SCurve ret; ZERO(&ret); ret.h = a->h; ret.isExact = a->isExact; - ret.exact = (a->exact).TransformedBy(t, q, mirror); + ret.exact = (a->exact).TransformedBy(t, q, scale); ret.surfA = a->surfA; ret.surfB = a->surfB; SCurvePt *p; for(p = a->pts.First(); p; p = a->pts.NextAfter(p)) { SCurvePt pp = *p; - if(mirror) pp.p.z *= -1; + pp.p = (pp.p).ScaledBy(scale); pp.p = (q.Rotate(pp.p)).Plus(t); ret.pts.Add(&pp); } diff --git a/srf/surface.cpp b/srf/surface.cpp index 15e4283f..79de4949 100644 --- a/srf/surface.cpp +++ b/srf/surface.cpp @@ -132,8 +132,8 @@ SSurface SSurface::FromPlane(Vector pt, Vector u, Vector v) { return ret; } -SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q, - bool mirror, +SSurface SSurface::FromTransformationOf(SSurface *a, + Vector t, Quaternion q, double scale, bool includingTrims) { SSurface ret; @@ -149,7 +149,7 @@ SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q, for(i = 0; i <= 3; i++) { for(j = 0; j <= 3; j++) { ret.ctrl[i][j] = a->ctrl[i][j]; - if(mirror) ret.ctrl[i][j].z *= -1; + ret.ctrl[i][j] = (ret.ctrl[i][j]).ScaledBy(scale); ret.ctrl[i][j] = (q.Rotate(ret.ctrl[i][j])).Plus(t); ret.weight[i][j] = a->weight[i][j]; @@ -160,17 +160,15 @@ SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q, STrimBy *stb; for(stb = a->trim.First(); stb; stb = a->trim.NextAfter(stb)) { STrimBy n = *stb; - if(mirror) { - n.start.z *= -1; - n.finish.z *= -1; - } + n.start = n.start.ScaledBy(scale); + n.finish = n.finish.ScaledBy(scale); n.start = (q.Rotate(n.start)) .Plus(t); n.finish = (q.Rotate(n.finish)).Plus(t); ret.trim.Add(&n); } } - if(mirror) { + if(scale < 0) { // If we mirror every surface of a shell, then it will end up inside // out. So fix that here. ret.Reverse(); @@ -543,7 +541,7 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, SCurve sc; ZERO(&sc); sc.isExact = true; - sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY, false); + sc.exact = sb->TransformedBy(t0, Quaternion::IDENTITY, 1.0); (sc.exact).MakePwlInto(&(sc.pts)); sc.surfA = hs0; sc.surfB = hsext; @@ -551,7 +549,7 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, ZERO(&sc); sc.isExact = true; - sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY, false); + sc.exact = sb->TransformedBy(t1, Quaternion::IDENTITY, 1.0); (sc.exact).MakePwlInto(&(sc.pts)); sc.surfA = hs1; sc.surfB = hsext; @@ -689,7 +687,7 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, if(revs.d[j].v) { ZERO(&sc); sc.isExact = true; - sc.exact = sb->TransformedBy(ts, qs, false); + sc.exact = sb->TransformedBy(ts, qs, 1.0); (sc.exact).MakePwlInto(&(sc.pts)); sc.surfA = revs.d[j]; sc.surfB = revs.d[WRAP(j-1, 4)]; @@ -816,25 +814,25 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, void SShell::MakeFromCopyOf(SShell *a) { MakeFromTransformationOf(a, - Vector::From(0, 0, 0), Quaternion::IDENTITY, false); + Vector::From(0, 0, 0), Quaternion::IDENTITY, 1.0); } void SShell::MakeFromTransformationOf(SShell *a, - Vector t, Quaternion q, bool mirror) + Vector t, Quaternion q, double scale) { booleanFailed = false; SSurface *s; for(s = a->surface.First(); s; s = a->surface.NextAfter(s)) { SSurface n; - n = SSurface::FromTransformationOf(s, t, q, mirror, true); + n = SSurface::FromTransformationOf(s, t, q, scale, true); surface.Add(&n); // keeping the old ID } SCurve *c; for(c = a->curve.First(); c; c = a->curve.NextAfter(c)) { SCurve n; - n = SCurve::FromTransformationOf(c, t, q, mirror); + n = SCurve::FromTransformationOf(c, t, q, scale); curve.Add(&n); // keeping the old ID } } diff --git a/srf/surface.h b/srf/surface.h index 33c30849..a78c3c11 100644 --- a/srf/surface.h +++ b/srf/surface.h @@ -91,7 +91,7 @@ public: bool IsCircle(Vector axis, Vector *center, double *r); bool IsRational(void); - SBezier TransformedBy(Vector t, Quaternion q, bool mirror); + SBezier TransformedBy(Vector t, Quaternion q, double scale); SBezier InPerspective(Vector u, Vector v, Vector n, Vector origin, double cameraTan); void ScaleSelfBy(double s); @@ -190,7 +190,7 @@ public: hSSurface surfB; static SCurve FromTransformationOf(SCurve *a, Vector t, Quaternion q, - bool mirror); + double scale); SCurve MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, SSurface *srfA, SSurface *srfB); void RemoveShortSegments(SSurface *srfA, SSurface *srfB); @@ -260,7 +260,7 @@ public: double thetas, double thetaf); static SSurface FromPlane(Vector pt, Vector u, Vector v); static SSurface FromTransformationOf(SSurface *a, Vector t, Quaternion q, - bool mirror, + double scale, bool includingTrims); void ScaleSelfBy(double s); @@ -384,8 +384,8 @@ public: Vector edge_n_in, Vector edge_n_out, Vector surf_n); void MakeFromCopyOf(SShell *a); - void MakeFromTransformationOf(SShell *a, Vector trans, Quaternion q, - bool mirror); + void MakeFromTransformationOf(SShell *a, + Vector trans, Quaternion q, double scale); void MakeFromAssemblyOf(SShell *a, SShell *b); void MergeCoincidentSurfaces(void); diff --git a/textscreens.cpp b/textscreens.cpp index 991d7de6..a2e96f17 100644 --- a/textscreens.cpp +++ b/textscreens.cpp @@ -235,10 +235,6 @@ void TextWindow::ScreenChangeGroupOption(int link, DWORD v) { g->suppress = !(g->suppress); break; - case 'm': - g->mirror = !(g->mirror); - break; - case 'r': g->relaxConstraints = !(g->relaxConstraints); break; @@ -315,6 +311,15 @@ void TextWindow::ScreenChangeGroupName(int link, DWORD v) { SS.TW.edit.meaning = EDIT_GROUP_NAME; SS.TW.edit.group.v = v; } +void TextWindow::ScreenChangeGroupScale(int link, DWORD v) { + Group *g = SK.GetGroup(SS.TW.shown.group); + + char str[1024]; + sprintf(str, "%.3f", g->scale); + ShowTextEditControl(17, 9, str); + SS.TW.edit.meaning = EDIT_GROUP_SCALE; + SS.TW.edit.group.v = v; +} void TextWindow::ScreenDeleteGroup(int link, DWORD v) { SS.UndoRemember(); @@ -449,11 +454,9 @@ void TextWindow::ShowGroupInfo(void) { &TextWindow::ScreenChangeGroupOption, (!sup ? "" : "no"), (!sup ? "no" : "")); - Printf(false, "%FtMIRROR%E %Fh%f%Lm%s%E%Fs%s%E / %Fh%f%Lm%s%E%Fs%s%E", - &TextWindow::ScreenChangeGroupOption, - (g->mirror ? "" : "yes"), (g->mirror ? "yes" : ""), - &TextWindow::ScreenChangeGroupOption, - (!g->mirror ? "" : "no"), (!g->mirror ? "no" : "")); + Printf(true, "%FtSCALE BY%E %# %Fl%Ll%f%D[change]%E", + g->scale, + &TextWindow::ScreenChangeGroupScale, g->h.v); } bool relax = g->relaxConstraints; @@ -734,6 +737,23 @@ void TextWindow::EditControlDone(char *s) { } break; } + case EDIT_GROUP_SCALE: { + Expr *e = Expr::From(s); + if(e) { + double ev = e->Eval(); + if(fabs(ev) < 1e-6) { + Error("Scale cannot be zero."); + } else { + Group *g = SK.GetGroup(edit.group); + g->scale = ev; + SS.MarkGroupDirty(g->h); + SS.later.generateAll = true; + } + } else { + Error("Not a valid number or expression: '%s'", s); + } + break; + } case EDIT_HELIX_TURNS: case EDIT_HELIX_PITCH: case EDIT_HELIX_DRADIUS: { @@ -783,9 +803,11 @@ void TextWindow::EditControlDone(char *s) { break; default: { - bool st = EditControlDoneForStyles(s), - cf = EditControlDoneForConfiguration(s); - if(st && cf) { + int cnt = 0; + if(EditControlDoneForStyles(s)) cnt++; + if(EditControlDoneForConfiguration(s)) cnt++; + if(EditControlDoneForPaste(s)) cnt++; + if(cnt > 1) { // The identifiers were somehow assigned not uniquely? oops(); } diff --git a/textwin.cpp b/textwin.cpp index a75c6bcd..ea8bf6e5 100644 --- a/textwin.cpp +++ b/textwin.cpp @@ -216,7 +216,9 @@ void TextWindow::Show(void) { Printf(false, "%s", SS.GW.pending.description); Printf(true, "%Fl%f%Ll(cancel operation)%E", &TextWindow::ScreenUnselectAll); - } else if(gs.n > 0 || gs.constraints > 0) { + } else if((gs.n > 0 || gs.constraints > 0) && + shown.screen != SCREEN_PASTE_TRANSFORMED) + { if(edit.meaning != EDIT_TTF_TEXT) HideTextEditControl(); ShowHeader(false); DescribeSelection(); @@ -235,6 +237,7 @@ void TextWindow::Show(void) { case SCREEN_MESH_VOLUME: ShowMeshVolume(); break; case SCREEN_LIST_OF_STYLES: ShowListOfStyles(); break; case SCREEN_STYLE_INFO: ShowStyleInfo(); break; + case SCREEN_PASTE_TRANSFORMED: ShowPasteTransformed(); break; } } Printf(false, ""); diff --git a/ui.h b/ui.h index baee4e9b..e1fe2ab1 100644 --- a/ui.h +++ b/ui.h @@ -54,6 +54,7 @@ public: static const int SCREEN_MESH_VOLUME = 5; static const int SCREEN_LIST_OF_STYLES = 6; static const int SCREEN_STYLE_INFO = 7; + static const int SCREEN_PASTE_TRANSFORMED = 8; typedef struct { int screen; @@ -64,6 +65,14 @@ public: bool dimIsDistance; double dimFinish; int dimSteps; + + struct { + int times; + Vector trans; + double theta; + Vector origin; + double scale; + } paste; double volume; } ShownState; @@ -73,6 +82,7 @@ public: // For multiple groups static const int EDIT_TIMES_REPEATED = 1; static const int EDIT_GROUP_NAME = 2; + static const int EDIT_GROUP_SCALE = 3; // For the configuraiton screen static const int EDIT_LIGHT_DIRECTION = 10; static const int EDIT_LIGHT_INTENSITY = 11; @@ -102,6 +112,10 @@ public: static const int EDIT_STYLE_NAME = 55; static const int EDIT_BACKGROUND_COLOR = 56; static const int EDIT_BACKGROUND_IMG_SCALE = 57; + // For paste transforming + static const int EDIT_PASTE_TIMES_REPEATED = 60; + static const int EDIT_PASTE_ANGLE = 61; + static const int EDIT_PASTE_SCALE = 62; struct { int meaning; int i; @@ -125,6 +139,7 @@ public: void ShowStyleInfo(void); void ShowStepDimension(void); void ShowMeshVolume(void); + void ShowPasteTransformed(void); // Special screen, based on selection void DescribeSelection(void); @@ -178,11 +193,14 @@ public: static void ScreenStepDimFinish(int link, DWORD v); static void ScreenStepDimGo(int link, DWORD v); + static void ScreenPasteTransformed(int link, DWORD v); + static void ScreenHome(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 ScreenChangeGroupScale(int link, DWORD v); static void ScreenChangeLightDirection(int link, DWORD v); static void ScreenChangeLightIntensity(int link, DWORD v); static void ScreenChangeColor(int link, DWORD v); @@ -198,9 +216,11 @@ public: static void ScreenChangeStyleColor(int link, DWORD v); static void ScreenChangeBackgroundColor(int link, DWORD v); static void ScreenChangeBackgroundImageScale(int link, DWORD v); + static void ScreenChangePasteTransformed(int link, DWORD v); bool EditControlDoneForStyles(char *s); bool EditControlDoneForConfiguration(char *s); + bool EditControlDoneForPaste(char *s); void EditControlDone(char *s); }; @@ -317,7 +337,7 @@ public: static void MenuRequest(int id); void DeleteSelection(void); void CopySelection(void); - void PasteClipboard(Vector trans, double theta, bool mirror); + void PasteClipboard(Vector trans, double theta, double scale); static void MenuClipboard(int id); // The width and height (in pixels) of the window. diff --git a/util.cpp b/util.cpp index d0a29016..0fa0d571 100644 --- a/util.cpp +++ b/util.cpp @@ -344,11 +344,11 @@ Quaternion Quaternion::Times(Quaternion b) { return r; } -Quaternion Quaternion::MirrorZ(void) { +Quaternion Quaternion::Mirror(void) { Vector u = RotationU(), v = RotationV(); - u.z *= -1; - v.z *= -1; + u = u.ScaledBy(-1); + v = v.ScaledBy(-1); return Quaternion::From(u, v); } diff --git a/wishlist.txt b/wishlist.txt index 91ce476f..011ebd1d 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,8 +1,8 @@ -paste transformed de-select after left-clicking nothing, keep sel on drag? bbox selection is select-only, not toggle? show and modify view parameters (translate, rotate, scale) +context menu to hide / suppress groups by entity? ----- associative entities from solid model, as a special group