A big nasty change, originally just to add paste transformed. So it

does that, and adds a scale factor to that transformation (instead
of just mirroring, as before), but also:

    * Replace the "import mirrored" mechanism with a scale factor,
      which if negative corresponds to a reflection as well.

    * Fix self-intersection checker to report a meaningful point
      when edges are collinear.

    * Don't blow an assertion on some types of invalid file;
      instead provide a nice error message to the user.

    * Clear the naked edges before each regen.

    * Don't create zero-length line segments by snapping a line
      segment's end to its beginning.

[git-p4: depot-paths = "//depot/solvespace/": change = 2086]
solver
Jonathan Westhues 2009-12-15 04:26:22 -08:00
parent 9723f4e44f
commit b974a4adeb
22 changed files with 342 additions and 93 deletions

View File

@ -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);
}

2
dsc.h
View File

@ -39,7 +39,7 @@ public:
Quaternion ToThe(double p);
Quaternion Inverse(void);
Quaternion Times(Quaternion b);
Quaternion MirrorZ(void);
Quaternion Mirror(void);
};
class Vector {

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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 },

View File

@ -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<Entity,hEntity> *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<Entity,hEntity> *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<Entity,hEntity> *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<Entity,hEntity> *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: {

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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.

View File

@ -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);

View File

@ -102,7 +102,7 @@ public:
bool visible;
bool suppress;
bool relaxConstraints;
bool mirror;
double scale;
bool clean;
bool vvMeshClean;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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
}
}

View File

@ -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);

View File

@ -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();
}

View File

@ -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, "");

22
ui.h
View File

@ -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;
@ -65,6 +66,14 @@ public:
double dimFinish;
int dimSteps;
struct {
int times;
Vector trans;
double theta;
Vector origin;
double scale;
} paste;
double volume;
} ShownState;
ShownState shown;
@ -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.

View File

@ -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);
}

View File

@ -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