void GraphicsWindow::MouseScroll(double x, double y, int delta) { double offsetRight = offset.Dot(projRight); double offsetUp = offset.Dot(projUp); double righti = x/scale - offsetRight; double upi = y/scale - offsetUp; if(delta > 0) { scale *= 1.2; } else if(delta < 0) { scale /= 1.2; } else return; double rightf = x/scale - offsetRight; double upf = y/scale - offsetUp; offset = offset.Plus(projRight.ScaledBy(rightf - righti)); offset = offset.Plus(projUp.ScaledBy(upf - upi)); if(SS.TW.shown.screen == TextWindow::SCREEN_EDIT_VIEW) { if(havePainted) { SS.ScheduleShowTW(); } } havePainted = false; InvalidateGraphics(); }
void GraphicsWindow::HitTestMakeSelection(Point2d mp) { int i; double d, dmin = 1e12; Selection s; ZERO(&s); // Always do the entities; we might be dragging something that should // be auto-constrained, and we need the hover for that. for(i = 0; i < SK.entity.n; i++) { Entity *e = &(SK.entity.elem[i]); // Don't hover whatever's being dragged. if(e->h.request().v == pending.point.request().v) { // The one exception is when we're creating a new cubic; we // want to be able to hover the first point, because that's // how we turn it into a periodic spline. if(!e->IsPoint()) continue; if(!e->h.isFromRequest()) continue; Request *r = SK.GetRequest(e->h.request()); if(r->type != Request::CUBIC) continue; if(r->extraPoints < 2) continue; if(e->h.v != r->h.entity(1).v) continue; } d = e->GetDistance(mp); if(d < 10 && d < dmin) { memset(&s, 0, sizeof(s)); s.entity = e->h; dmin = d; } } // The constraints and faces happen only when nothing's in progress. if(pending.operation == 0) { // Constraints for(i = 0; i < SK.constraint.n; i++) { d = SK.constraint.elem[i].GetDistance(mp); if(d < 10 && d < dmin) { memset(&s, 0, sizeof(s)); s.constraint = SK.constraint.elem[i].h; dmin = d; } } // Faces, from the triangle mesh; these are lowest priority if(s.constraint.v == 0 && s.entity.v == 0 && showShaded && showFaces) { Group *g = SK.GetGroup(activeGroup); SMesh *m = &(g->displayMesh); uint32_t v = m->FirstIntersectionWith(mp); if(v) { s.entity.v = v; } } } if(!s.Equals(&hover)) { hover = s; InvalidateGraphics(); } }
void TextWindow::ScreenChangeCanvasSizeAuto(int link, uint32_t v) { if(link == 't') { SS.exportCanvasSizeAuto = true; } else { SS.exportCanvasSizeAuto = false; } InvalidateGraphics(); }
void TextWindow::ScreenDeleteStyle(int link, uint32_t v) { SS.UndoRemember(); hStyle hs = { v }; Style *s = SK.style.FindByIdNoOops(hs); if(s) { SK.style.RemoveById(hs); // And it will get recreated automatically if something is still using // the style, so no need to do anything else. } SS.TW.GoToScreen(SCREEN_LIST_OF_STYLES); InvalidateGraphics(); }
void GraphicsWindow::MouseLeftUp(double mx, double my) { orig.mouseDown = false; hoverWasSelectedOnMousedown = false; switch(pending.operation) { case DRAGGING_POINTS: SS.extraLine.draw = false; // fall through case DRAGGING_CONSTRAINT: case DRAGGING_NORMAL: case DRAGGING_RADIUS: ClearPending(); InvalidateGraphics(); break; case DRAGGING_MARQUEE: SelectByMarquee(); ClearPending(); InvalidateGraphics(); break; case 0: // We need to clear the selection here, and not in the mouse down // event, since a mouse down without anything hovered could also // be the start of marquee selection. But don't do that on the // left click to cancel a context menu. The time delay is an ugly // hack. if(hover.IsEmpty() && (contextMenuCancelTime == 0 || (GetMilliseconds() - contextMenuCancelTime) > 200)) { ClearSelection(); } break; default: break; // do nothing } }
void GraphicsWindow::ToggleBool(bool *v) { *v = !*v; // The faces are shown as special stippling on the shaded triangle mesh, // so not meaningful to show them and hide the shaded. if(!showShaded) showFaces = false; // We might need to regenerate the mesh and edge list, since the edges // wouldn't have been generated if they were previously hidden. if(showEdges) (SK.GetGroup(activeGroup))->displayDirty = true; SS.GenerateAll(); InvalidateGraphics(); SS.ScheduleShowTW(); }
void GraphicsWindow::ClearNonexistentSelectionItems(void) { bool change = false; Selection *s; selection.ClearTags(); for(s = selection.First(); s; s = selection.NextAfter(s)) { if(s->constraint.v && !(SK.constraint.FindByIdNoOops(s->constraint))) { s->tag = 1; change = true; } if(s->entity.v && !(SK.entity.FindByIdNoOops(s->entity))) { s->tag = 1; change = true; } } selection.RemoveTagged(); if(change) InvalidateGraphics(); }
void GraphicsWindow::AnimateOnto(Quaternion quatf, Vector offsetf) { // Get our initial orientation and translation. Quaternion quat0 = Quaternion::From(projRight, projUp); Vector offset0 = offset; // Make sure we take the shorter of the two possible paths. double mp = (quatf.Minus(quat0)).Magnitude(); double mm = (quatf.Plus(quat0)).Magnitude(); if(mp > mm) { quatf = quatf.ScaledBy(-1); mp = mm; } double mo = (offset0.Minus(offsetf)).Magnitude()*scale; // Animate transition, unless it's a tiny move. int32_t dt = (mp < 0.01 && mo < 10) ? (-20) : (int32_t)(100 + 1000*mp + 0.4*mo); // Don't ever animate for longer than 2000 ms; we can get absurdly // long translations (as measured in pixels) if the user zooms out, moves, // and then zooms in again. if(dt > 2000) dt = 2000; int64_t tn, t0 = GetMilliseconds(); double s = 0; Quaternion dq = quatf.Times(quat0.Inverse()); do { offset = (offset0.ScaledBy(1 - s)).Plus(offsetf.ScaledBy(s)); Quaternion quat = (dq.ToThe(s)).Times(quat0); quat = quat.WithMagnitude(1); projRight = quat.RotationU(); projUp = quat.RotationV(); PaintGraphics(); tn = GetMilliseconds(); s = (tn - t0)/((double)dt); } while((tn - t0) < dt); projRight = quatf.RotationU(); projUp = quatf.RotationV(); offset = offsetf; InvalidateGraphics(); // If the view screen is open, then we need to refresh it. SS.ScheduleShowTW(); }
void Style::AssignSelectionToStyle(uint32_t v) { bool showError = false; SS.GW.GroupSelection(); SS.UndoRemember(); int i; for(i = 0; i < SS.GW.gs.entities; i++) { hEntity he = SS.GW.gs.entity[i]; Entity *e = SK.GetEntity(he); if(!e->IsStylable()) continue; if(!he.isFromRequest()) { showError = true; continue; } hRequest hr = he.request(); Request *r = SK.GetRequest(hr); r->style.v = v; SS.MarkGroupDirty(r->group); } for(i = 0; i < SS.GW.gs.constraints; i++) { hConstraint hc = SS.GW.gs.constraint[i]; Constraint *c = SK.GetConstraint(hc); if(!c->IsStylable()) continue; c->disp.style.v = v; } if(showError) { Error("Can't assign style to an entity that's derived from another " "entity; try assigning a style to this entity's parent."); } SS.GW.ClearSelection(); InvalidateGraphics(); SS.ScheduleGenerateAll(); // And show that style's info screen in the text window. SS.TW.GoToScreen(TextWindow::Screen::STYLE_INFO); SS.TW.shown.style.v = v; SS.ScheduleShowTW(); }
void TextWindow::ScreenStepDimGo(int link, DWORD v) { hConstraint hc = SS.TW.shown.constraint; Constraint *c = SK.constraint.FindByIdNoOops(hc); if(c) { SS.UndoRemember(); double start = c->valA, finish = SS.TW.shown.dimFinish; int i, n = SS.TW.shown.dimSteps; for(i = 1; i <= n; i++) { c = SK.GetConstraint(hc); c->valA = start + ((finish - start)*i)/n; SS.MarkGroupDirty(c->group); SS.GenerateAll(); if(!SS.AllGroupsOkay()) { // Failed to solve, so quit break; } PaintGraphics(); } } InvalidateGraphics(); SS.TW.GoToScreen(SCREEN_LIST_OF_GROUPS); }
void GraphicsWindow::SpaceNavigatorButtonUp(void) { ZoomToFit(false); InvalidateGraphics(); }
void TextWindow::ScreenChangeStyleYesNo(int link, uint32_t v) { SS.UndoRemember(); hStyle hs = { v }; Style *s = Style::Get(hs); switch(link) { // Units for the width case 'w': if(s->widthAs != Style::UnitsAs::MM) { s->widthAs = Style::UnitsAs::MM; s->width /= SS.GW.scale; s->stippleScale /= SS.GW.scale; } break; case 'W': if(s->widthAs != Style::UnitsAs::PIXELS) { s->widthAs = Style::UnitsAs::PIXELS; s->width *= SS.GW.scale; s->stippleScale *= SS.GW.scale; } break; // Units for the height case 'g': if(s->textHeightAs != Style::UnitsAs::MM) { s->textHeightAs = Style::UnitsAs::MM; s->textHeight /= SS.GW.scale; } break; case 'G': if(s->textHeightAs != Style::UnitsAs::PIXELS) { s->textHeightAs = Style::UnitsAs::PIXELS; s->textHeight *= SS.GW.scale; } break; case 'e': s->exportable = !(s->exportable); break; case 'v': s->visible = !(s->visible); break; case 'f': s->filled = !(s->filled); break; // Horizontal text alignment case 'L': s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::LEFT); s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::RIGHT); break; case 'H': s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::LEFT); s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::RIGHT); break; case 'R': s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::LEFT); s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::RIGHT); break; // Vertical text alignment case 'B': s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::BOT); s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::TOP); break; case 'V': s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::BOT); s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::TOP); break; case 'T': s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::BOT); s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::TOP); break; } InvalidateGraphics(); }
void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz, double rx, double ry, double rz, bool shiftDown) { if(!havePainted) return; Vector out = projRight.Cross(projUp); // rotation vector is axis of rotation, and its magnitude is angle Vector aa = Vector::From(rx, ry, rz); // but it's given with respect to screen projection frame aa = aa.ScaleOutOfCsys(projRight, projUp, out); double aam = aa.Magnitude(); if(aam > 0.0) aa = aa.WithMagnitude(1); // This can either transform our view, or transform an imported part. GroupSelection(); Entity *e = NULL; Group *g = NULL; if(gs.points == 1 && gs.n == 1) e = SK.GetEntity(gs.point [0]); if(gs.entities == 1 && gs.n == 1) e = SK.GetEntity(gs.entity[0]); if(e) g = SK.GetGroup(e->group); if(g && g->type == Group::IMPORTED && !shiftDown) { // Apply the transformation to an imported part. Gain down the Z // axis, since it's hard to see what you're doing on that one since // it's normal to the screen. Vector t = projRight.ScaledBy(tx/scale).Plus( projUp .ScaledBy(ty/scale).Plus( out .ScaledBy(0.1*tz/scale))); Quaternion q = Quaternion::From(aa, aam); // If we go five seconds without SpaceNavigator input, or if we've // switched groups, then consider that a new action and save an undo // point. int64_t now = GetMilliseconds(); if(now - lastSpaceNavigatorTime > 5000 || lastSpaceNavigatorGroup.v != g->h.v) { SS.UndoRemember(); } g->TransformImportedBy(t, q); lastSpaceNavigatorTime = now; lastSpaceNavigatorGroup = g->h; SS.MarkGroupDirty(g->h); SS.ScheduleGenerateAll(); } else { // Apply the transformation to the view of the everything. The // x and y components are translation; but z component is scale, // not translation, or else it would do nothing in a parallel // projection offset = offset.Plus(projRight.ScaledBy(tx/scale)); offset = offset.Plus(projUp.ScaledBy(ty/scale)); scale *= exp(0.001*tz); if(aam > 0.0) { projRight = projRight.RotatedAbout(aa, -aam); projUp = projUp. RotatedAbout(aa, -aam); NormalizeProjectionVectors(); } } havePainted = false; InvalidateGraphics(); }
void Constraint::MenuConstrain(int id) { Constraint c; ZERO(&c); c.group = SS.GW.activeGroup; c.workplane = SS.GW.ActiveWorkplane(); SS.GW.GroupSelection(); #define gs (SS.GW.gs) switch(id) { case GraphicsWindow::MNU_DISTANCE_DIA: { if(gs.points == 2 && gs.n == 2) { c.type = PT_PT_DISTANCE; c.ptA = gs.point[0]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 1 && gs.n == 1) { c.type = PT_PT_DISTANCE; Entity *e = SK.GetEntity(gs.entity[0]); c.ptA = e->point[0]; c.ptB = e->point[1]; } else if(gs.vectors == 1 && gs.points == 2 && gs.n == 3) { c.type = PROJ_PT_DISTANCE; c.ptA = gs.point[0]; c.ptB = gs.point[1]; c.entityA = gs.vector[0]; } else if(gs.workplanes == 1 && gs.points == 1 && gs.n == 2) { c.type = PT_PLANE_DISTANCE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) { c.type = PT_LINE_DISTANCE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.faces == 1 && gs.points == 1 && gs.n == 2) { c.type = PT_FACE_DISTANCE; c.ptA = gs.point[0]; c.entityA = gs.face[0]; } else if(gs.circlesOrArcs == 1 && gs.n == 1) { c.type = DIAMETER; c.entityA = gs.entity[0]; } else { Error( "Bad selection for distance / diameter constraint. This " "constraint can apply to:\n\n" " * two points (distance between points)\n" " * a line segment (length)\n" " * two points and a line segment or normal (projected distance)\n" " * a workplane and a point (minimum distance)\n" " * a line segment and a point (minimum distance)\n" " * a plane face and a point (minimum distance)\n" " * a circle or an arc (diameter)\n"); return; } if(c.type == PT_PT_DISTANCE || c.type == PROJ_PT_DISTANCE) { Vector n = SS.GW.projRight.Cross(SS.GW.projUp); Vector a = SK.GetEntity(c.ptA)->PointGetNum(); Vector b = SK.GetEntity(c.ptB)->PointGetNum(); c.disp.offset = n.Cross(a.Minus(b)); c.disp.offset = (c.disp.offset).WithMagnitude(50/SS.GW.scale); } else { c.disp.offset = Vector::From(0, 0, 0); } c.valA = 0; c.ModifyToSatisfy(); AddConstraint(&c); break; } case GraphicsWindow::MNU_ON_ENTITY: if(gs.points == 2 && gs.n == 2) { c.type = POINTS_COINCIDENT; c.ptA = gs.point[0]; c.ptB = gs.point[1]; } else if(gs.points == 1 && gs.workplanes == 1 && gs.n == 2) { c.type = PT_IN_PLANE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) { c.type = PT_ON_LINE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) { c.type = PT_ON_CIRCLE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.points == 1 && gs.faces == 1 && gs.n == 2) { c.type = PT_ON_FACE; c.ptA = gs.point[0]; c.entityA = gs.face[0]; } else { Error("Bad selection for on point / curve / plane constraint. " "This constraint can apply to:\n\n" " * two points (points coincident)\n" " * a point and a workplane (point in plane)\n" " * a point and a line segment (point on line)\n" " * a point and a circle or arc (point on curve)\n" " * a point and a plane face (point on face)\n"); return; } AddConstraint(&c); break; case GraphicsWindow::MNU_EQUAL: if(gs.lineSegments == 2 && gs.n == 2) { c.type = EQUAL_LENGTH_LINES; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else if(gs.lineSegments == 2 && gs.points == 2 && gs.n == 4) { c.type = EQ_PT_LN_DISTANCES; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.entityB = gs.entity[1]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) { // The same line segment for the distances, but different // points. c.type = EQ_PT_LN_DISTANCES; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.entityB = gs.entity[0]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 2 && gs.points == 1 && gs.n == 3) { c.type = EQ_LEN_PT_LINE_D; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; c.ptA = gs.point[0]; } else if(gs.vectors == 4 && gs.n == 4) { c.type = EQUAL_ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; c.entityC = gs.vector[2]; c.entityD = gs.vector[3]; } else if(gs.vectors == 3 && gs.n == 3) { c.type = EQUAL_ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; c.entityC = gs.vector[1]; c.entityD = gs.vector[2]; } else if(gs.circlesOrArcs == 2 && gs.n == 2) { c.type = EQUAL_RADIUS; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else if(gs.arcs == 1 && gs.lineSegments == 1 && gs.n == 2) { c.type = EQUAL_LINE_ARC_LEN; if(SK.GetEntity(gs.entity[0])->type == Entity::ARC_OF_CIRCLE) { c.entityA = gs.entity[1]; c.entityB = gs.entity[0]; } else { c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } } else { Error("Bad selection for equal length / radius constraint. " "This constraint can apply to:\n\n" " * two line segments (equal length)\n" " * two line segments and two points " "(equal point-line distances)\n" " * a line segment and two points " "(equal point-line distances)\n" " * a line segment, and a point and line segment " "(point-line distance equals length)\n" " * four line segments or normals " "(equal angle between A,B and C,D)\n" " * three line segments or normals " "(equal angle between A,B and B,C)\n" " * two circles or arcs (equal radius)\n" " * a line segment and an arc " "(line segment length equals arc length)\n"); return; } if(c.type == EQUAL_ANGLE) { // Infer the nearest supplementary angle from the sketch. Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(), b1 = SK.GetEntity(c.entityB)->VectorGetNum(), a2 = SK.GetEntity(c.entityC)->VectorGetNum(), b2 = SK.GetEntity(c.entityD)->VectorGetNum(); double d1 = a1.Dot(b1), d2 = a2.Dot(b2); if(d1*d2 < 0) { c.other = true; } } AddConstraint(&c); break; case GraphicsWindow::MNU_RATIO: if(gs.lineSegments == 2 && gs.n == 2) { c.type = LENGTH_RATIO; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else { Error("Bad selection for length ratio constraint. This " "constraint can apply to:\n\n" " * two line segments\n"); return; } c.valA = 0; c.ModifyToSatisfy(); AddConstraint(&c); break; case GraphicsWindow::MNU_AT_MIDPOINT: if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) { c.type = AT_MIDPOINT; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; // If a point is at-midpoint, then no reason to also constrain // it on-line; so auto-remove that. DeleteAllConstraintsFor(PT_ON_LINE, c.entityA, c.ptA); } else if(gs.lineSegments == 1 && gs.workplanes == 1 && gs.n == 2) { c.type = AT_MIDPOINT; int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0; c.entityA = gs.entity[i]; c.entityB = gs.entity[1-i]; } else { Error("Bad selection for at midpoint constraint. This " "constraint can apply to:\n\n" " * a line segment and a point " "(point at midpoint)\n" " * a line segment and a workplane " "(line's midpoint on plane)\n"); return; } AddConstraint(&c); break; case GraphicsWindow::MNU_SYMMETRIC: if(gs.points == 2 && ((gs.workplanes == 1 && gs.n == 3) || (gs.n == 2))) { c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 1 && ((gs.workplanes == 1 && gs.n == 2) || (gs.n == 1))) { int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0; Entity *line = SK.GetEntity(gs.entity[i]); c.entityA = gs.entity[1-i]; c.ptA = line->point[0]; c.ptB = line->point[1]; } else if(SS.GW.LockedInWorkplane() && gs.lineSegments == 2 && gs.n == 2) { Entity *l0 = SK.GetEntity(gs.entity[0]), *l1 = SK.GetEntity(gs.entity[1]); if((l1->group.v != SS.GW.activeGroup.v) || (l1->construction && !(l0->construction))) { SWAP(Entity *, l0, l1); } c.ptA = l1->point[0]; c.ptB = l1->point[1]; c.entityA = l0->h; c.type = SYMMETRIC_LINE; } else if(SS.GW.LockedInWorkplane() && gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) { c.ptA = gs.point[0]; c.ptB = gs.point[1]; c.entityA = gs.entity[0]; c.type = SYMMETRIC_LINE; } else { Error("Bad selection for symmetric constraint. This constraint " "can apply to:\n\n" " * two points or a line segment " "(symmetric about workplane's coordinate axis)\n" " * line segment, and two points or a line segment " "(symmetric about line segment)\n" " * workplane, and two points or a line segment " "(symmetric about workplane)\n"); return; } if(c.type != 0) { // Already done, symmetry about a line segment in a workplane } else if(c.entityA.v == Entity::NO_ENTITY.v) { // Horizontal / vertical symmetry, implicit symmetry plane // normal to the workplane if(c.workplane.v == Entity::FREE_IN_3D.v) { Error("Must be locked in to workplane when constraining " "symmetric without an explicit symmetry plane."); return; } Vector pa = SK.GetEntity(c.ptA)->PointGetNum(); Vector pb = SK.GetEntity(c.ptB)->PointGetNum(); Vector dp = pa.Minus(pb); EntityBase *norm = SK.GetEntity(c.workplane)->Normal();; Vector u = norm->NormalU(), v = norm->NormalV(); if(fabs(dp.Dot(u)) > fabs(dp.Dot(v))) { c.type = SYMMETRIC_HORIZ; } 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. DeleteAllConstraintsFor(HORIZONTAL, (gs.entity[0]), Entity::NO_ENTITY); DeleteAllConstraintsFor(VERTICAL, (gs.entity[0]), Entity::NO_ENTITY); } } else { // Symmetry with a symmetry plane specified explicitly. c.type = SYMMETRIC; } AddConstraint(&c); break; case GraphicsWindow::MNU_VERTICAL: case GraphicsWindow::MNU_HORIZONTAL: { hEntity ha, hb; if(c.workplane.v == Entity::FREE_IN_3D.v) { Error("Select workplane before constraining horiz/vert."); return; } if(gs.lineSegments == 1 && gs.n == 1) { c.entityA = gs.entity[0]; Entity *e = SK.GetEntity(c.entityA); ha = e->point[0]; hb = e->point[1]; } else if(gs.points == 2 && gs.n == 2) { ha = c.ptA = gs.point[0]; hb = c.ptB = gs.point[1]; } else { Error("Bad selection for horizontal / vertical constraint. " "This constraint can apply to:\n\n" " * two points\n" " * a line segment\n"); return; } if(id == GraphicsWindow::MNU_HORIZONTAL) { c.type = HORIZONTAL; } else { c.type = VERTICAL; } AddConstraint(&c); break; } case GraphicsWindow::MNU_ORIENTED_SAME: { if(gs.anyNormals == 2 && gs.n == 2) { c.type = SAME_ORIENTATION; c.entityA = gs.anyNormal[0]; c.entityB = gs.anyNormal[1]; } else { Error("Bad selection for same orientation constraint. This " "constraint can apply to:\n\n" " * two normals\n"); return; } SS.UndoRemember(); Entity *nfree = SK.GetEntity(c.entityA); Entity *nref = SK.GetEntity(c.entityB); if(nref->group.v == SS.GW.activeGroup.v) { SWAP(Entity *, nref, nfree); } if(nfree->group.v == SS.GW.activeGroup.v && nref ->group.v != SS.GW.activeGroup.v) { // nfree is free, and nref is locked (since it came from a // previous group); so let's force nfree aligned to nref, // and make convergence easy Vector ru = nref ->NormalU(), rv = nref ->NormalV(); Vector fu = nfree->NormalU(), fv = nfree->NormalV(); if(fabs(fu.Dot(ru)) < fabs(fu.Dot(rv))) { // There might be an odd*90 degree rotation about the // normal vector; allow that, since the numerical // constraint does SWAP(Vector, ru, rv); } fu = fu.Dot(ru) > 0 ? ru : ru.ScaledBy(-1); fv = fv.Dot(rv) > 0 ? rv : rv.ScaledBy(-1); nfree->NormalForceTo(Quaternion::From(fu, fv)); } AddConstraint(&c, false); break; } case GraphicsWindow::MNU_OTHER_ANGLE: if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->type == ANGLE) { SS.UndoRemember(); c->other = !(c->other); c->ModifyToSatisfy(); break; } if(c->type == EQUAL_ANGLE) { SS.UndoRemember(); c->other = !(c->other); SS.MarkGroupDirty(c->group); SS.ScheduleGenerateAll(); break; } } Error("Must select an angle constraint."); return; case GraphicsWindow::MNU_REFERENCE: if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->HasLabel() && c->type != COMMENT) { (c->reference) = !(c->reference); SK.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) { c.type = ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; c.valA = 0; } else { Error("Bad selection for angle constraint. This constraint " "can apply to:\n\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n"); return; } Entity *ea = SK.GetEntity(c.entityA), *eb = SK.GetEntity(c.entityB); if(ea->type == Entity::LINE_SEGMENT && eb->type == Entity::LINE_SEGMENT) { Vector a0 = SK.GetEntity(ea->point[0])->PointGetNum(), a1 = SK.GetEntity(ea->point[1])->PointGetNum(), b0 = SK.GetEntity(eb->point[0])->PointGetNum(), b1 = SK.GetEntity(eb->point[1])->PointGetNum(); if(a0.Equals(b0) || a1.Equals(b1)) { // okay, vectors should be drawn in same sense } else if(a0.Equals(b1) || a1.Equals(b0)) { // vectors are in opposite sense c.other = true; } else { // no shared point; not clear which intersection to draw } } c.ModifyToSatisfy(); AddConstraint(&c); break; } case GraphicsWindow::MNU_PARALLEL: if(gs.vectors == 2 && gs.n == 2) { c.type = PARALLEL; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; } else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) { Entity *line = SK.GetEntity(gs.entity[0]); Entity *arc = SK.GetEntity(gs.entity[1]); if(line->type == Entity::ARC_OF_CIRCLE) { SWAP(Entity *, line, arc); } Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), l1 = SK.GetEntity(line->point[1])->PointGetNum(); Vector a1 = SK.GetEntity(arc->point[1])->PointGetNum(), a2 = SK.GetEntity(arc->point[2])->PointGetNum(); if(l0.Equals(a1) || l1.Equals(a1)) { c.other = false; } else if(l0.Equals(a2) || l1.Equals(a2)) { c.other = true; } else { Error("The tangent arc and line segment must share an " "endpoint. Constrain them with Constrain -> " "On Point before constraining tangent."); return; } c.type = ARC_LINE_TANGENT; c.entityA = arc->h; c.entityB = line->h; } else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) { Entity *line = SK.GetEntity(gs.entity[0]); Entity *cubic = SK.GetEntity(gs.entity[1]); if(line->type == Entity::CUBIC) { SWAP(Entity *, line, cubic); } Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), l1 = SK.GetEntity(line->point[1])->PointGetNum(); Vector as = cubic->CubicGetStartNum(), af = cubic->CubicGetFinishNum(); if(l0.Equals(as) || l1.Equals(as)) { c.other = false; } else if(l0.Equals(af) || l1.Equals(af)) { c.other = true; } else { Error("The tangent cubic and line segment must share an " "endpoint. Constrain them with Constrain -> " "On Point before constraining tangent."); return; } c.type = CUBIC_LINE_TANGENT; c.entityA = cubic->h; c.entityB = line->h; } else if(gs.cubics + gs.arcs == 2 && gs.n == 2) { if(!SS.GW.LockedInWorkplane()) { Error("Curve-curve tangency must apply in workplane."); return; } Entity *eA = SK.GetEntity(gs.entity[0]), *eB = SK.GetEntity(gs.entity[1]); Vector as = eA->EndpointStart(), af = eA->EndpointFinish(), bs = eB->EndpointStart(), bf = eB->EndpointFinish(); if(as.Equals(bs)) { c.other = false; c.other2 = false; } else if(as.Equals(bf)) { c.other = false; c.other2 = true; } else if(af.Equals(bs)) { c.other = true; c.other2 = false; } else if(af.Equals(bf)) { c.other = true; c.other2 = true; } else { Error("The curves must share an endpoint. Constrain them " "with Constrain -> On Point before constraining " "tangent."); return; } c.type = CURVE_CURVE_TANGENT; c.entityA = eA->h; c.entityB = eB->h; } else { Error("Bad selection for parallel / tangent constraint. This " "constraint can apply to:\n\n" " * two line segments (parallel)\n" " * a line segment and a normal (parallel)\n" " * two normals (parallel)\n" " * two line segments, arcs, or beziers, that share " "an endpoint (tangent)\n"); return; } AddConstraint(&c); break; case GraphicsWindow::MNU_PERPENDICULAR: if(gs.vectors == 2 && gs.n == 2) { c.type = PERPENDICULAR; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; } else { Error("Bad selection for perpendicular constraint. This " "constraint can apply to:\n\n" " * two line segments\n" " * a line segment and a normal\n" " * two normals\n"); return; } AddConstraint(&c); break; case GraphicsWindow::MNU_WHERE_DRAGGED: if(gs.points == 1 && gs.n == 1) { c.type = WHERE_DRAGGED; c.ptA = gs.point[0]; } else { Error("Bad selection for lock point where dragged constraint. " "This constraint can apply to:\n\n" " * a point\n"); return; } AddConstraint(&c); break; case GraphicsWindow::MNU_COMMENT: SS.GW.pending.operation = GraphicsWindow::MNU_COMMENT; SS.GW.pending.description = "click center of comment text"; SS.ScheduleShowTW(); break; default: oops(); } SS.GW.ClearSelection(); InvalidateGraphics(); }
void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) { int first, last, i, j; SK.groupOrder.Clear(); for(int i = 0; i < SK.group.n; i++) SK.groupOrder.Add(&SK.group.elem[i].h); std::sort(&SK.groupOrder.elem[0], &SK.groupOrder.elem[SK.groupOrder.n], [](const hGroup &ha, const hGroup &hb) { return SK.GetGroup(ha)->order < SK.GetGroup(hb)->order; }); switch(type) { case Generate::DIRTY: { first = INT_MAX; last = 0; // Start from the first dirty group, and solve until the active group, // since all groups after the active group are hidden. for(i = 0; i < SK.groupOrder.n; i++) { Group *g = SK.GetGroup(SK.groupOrder.elem[i]); if((!g->clean) || !g->IsSolvedOkay()) { first = min(first, i); } if(g->h.v == SS.GW.activeGroup.v) { last = i; } } if(first == INT_MAX || last == 0) { // All clean; so just regenerate the entities, and don't solve anything. first = -1; last = -1; } else { SS.nakedEdges.Clear(); } break; } case Generate::ALL: first = 0; last = INT_MAX; break; case Generate::REGEN: first = -1; last = -1; break; case Generate::UNTIL_ACTIVE: { for(i = 0; i < SK.groupOrder.n; i++) { if(SK.groupOrder.elem[i].v == SS.GW.activeGroup.v) break; } first = 0; last = i; break; } } // If we're generating entities for display, first we need to find // the bounding box to turn relative chord tolerance to absolute. if(!SS.exportMode && !genForBBox) { GenerateAll(type, andFindFree, /*genForBBox=*/true); BBox box = SK.CalculateEntityBBox(/*includeInvisibles=*/true); Vector size = box.maxp.Minus(box.minp); double maxSize = std::max({ size.x, size.y, size.z }); chordTolCalculated = maxSize * chordTol / 100.0; } // Remove any requests or constraints that refer to a nonexistent // group; can check those immediately, since we know what the list // of groups should be. while(PruneOrphans()) ; // Don't lose our numerical guesses when we regenerate. IdList<Param,hParam> prev = {}; SK.param.MoveSelfInto(&prev); SK.entity.Clear(); for(i = 0; i < SK.groupOrder.n; i++) { Group *g = SK.GetGroup(SK.groupOrder.elem[i]); // The group may depend on entities or other groups, to define its // workplane geometry or for its operands. Those must already exist // in a previous group, so check them before generating. if(PruneGroups(g->h)) goto pruned; for(j = 0; j < SK.request.n; j++) { Request *r = &(SK.request.elem[j]); if(r->group.v != g->h.v) continue; r->Generate(&(SK.entity), &(SK.param)); } g->Generate(&(SK.entity), &(SK.param)); // The requests and constraints depend on stuff in this or the // previous group, so check them after generating. if(PruneRequests(g->h) || PruneConstraints(g->h)) goto pruned; // Use the previous values for params that we've seen before, as // initial guesses for the solver. for(j = 0; j < SK.param.n; j++) { Param *newp = &(SK.param.elem[j]); if(newp->known) continue; Param *prevp = prev.FindByIdNoOops(newp->h); if(prevp) { newp->val = prevp->val; newp->free = prevp->free; } } if(g->h.v == Group::HGROUP_REFERENCES.v) { ForceReferences(); g->solved.how = SolveResult::OKAY; g->clean = true; } else { if(i >= first && i <= last) { // The group falls inside the range, so really solve it, // and then regenerate the mesh based on the solved stuff. if(genForBBox) { SolveGroup(g->h, andFindFree); } else { g->GenerateLoops(); g->GenerateShellAndMesh(); g->clean = true; } } else { // The group falls outside the range, so just assume that // it's good wherever we left it. The mesh is unchanged, // and the parameters must be marked as known. for(j = 0; j < SK.param.n; j++) { Param *newp = &(SK.param.elem[j]); Param *prevp = prev.FindByIdNoOops(newp->h); if(prevp) newp->known = true; } } } } // And update any reference dimensions with their new values for(i = 0; i < SK.constraint.n; i++) { Constraint *c = &(SK.constraint.elem[i]); if(c->reference) { c->ModifyToSatisfy(); } } // Make sure the point that we're tracing exists. if(traced.point.v && !SK.entity.FindByIdNoOops(traced.point)) { traced.point = Entity::NO_ENTITY; } // And if we're tracing a point, add its new value to the path if(traced.point.v) { Entity *pt = SK.GetEntity(traced.point); traced.path.AddPoint(pt->PointGetNum()); } prev.Clear(); InvalidateGraphics(); // Remove nonexistent selection items, for same reason we waited till // the end to put up a dialog box. GW.ClearNonexistentSelectionItems(); if(deleted.requests > 0 || deleted.constraints > 0 || deleted.groups > 0) { // All sorts of interesting things could have happened; for example, // the active group or active workplane could have been deleted. So // clear all that out. if(deleted.groups > 0) { SS.TW.ClearSuper(); } ScheduleShowTW(); GW.ClearSuper(); // People get annoyed if I complain whenever they delete any request, // and I otherwise will, since those always come with pt-coincident // constraints. if(deleted.requests > 0 || deleted.nonTrivialConstraints > 0 || deleted.groups > 0) { // Don't display any errors until we've regenerated fully. The // sketch is not necessarily in a consistent state until we've // pruned any orphaned etc. objects, and the message loop for the // messagebox could allow us to repaint and crash. But now we must // be fine. Message("Additional sketch elements were deleted, because they " "depend on the element that was just deleted explicitly. " "These include: \n" " %d request%s\n" " %d constraint%s\n" " %d group%s" "%s", deleted.requests, deleted.requests == 1 ? "" : "s", deleted.constraints, deleted.constraints == 1 ? "" : "s", deleted.groups, deleted.groups == 1 ? "" : "s", undo.cnt > 0 ? "\n\nChoose Edit -> Undo to undelete all elements." : ""); } deleted = {}; } FreeAllTemporary(); allConsistent = true; return; pruned: // Restore the numerical guesses SK.param.Clear(); prev.MoveSelfInto(&(SK.param)); // Try again GenerateAll(type, andFindFree, genForBBox); }
bool TextWindow::EditControlDoneForConfiguration(const char *s) { switch(edit.meaning) { case EDIT_LIGHT_INTENSITY: SS.lightIntensity[edit.i] = min(1, max(0, atof(s))); InvalidateGraphics(); break; case EDIT_LIGHT_DIRECTION: { double x, y, z; if(sscanf(s, "%lf, %lf, %lf", &x, &y, &z)==3) { SS.lightDir[edit.i] = Vector::From(x, y, z); } else { Error("Bad format: specify coordinates as x, y, z"); } InvalidateGraphics(); break; } case EDIT_COLOR: { Vector rgb; if(sscanf(s, "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) { rgb = rgb.ClampWithin(0, 1); SS.modelColor[edit.i] = RGBf(rgb.x, rgb.y, rgb.z); } else { Error("Bad format: specify color as r, g, b"); } break; } case EDIT_CHORD_TOLERANCE: { SS.chordTol = min(10, max(0.1, atof(s))); SS.GenerateAll(0, INT_MAX); break; } //RT New stuff case EDIT_RENDERDETAIL: { SS.renderDetailWhenSaving = atof(s); if (SS.renderDetailWhenSaving) SS.GW.scaleWin =SS.renderDetailWhenSaving; SS.GenerateAll(0, INT_MAX); break; } //RT End stuff case EDIT_MAX_SEGMENTS: { SS.maxSegments = min(1000, max(7, atoi(s))); SS.GenerateAll(0, INT_MAX); break; } case EDIT_CAMERA_TANGENT: { SS.cameraTangent = (min(2, max(0, atof(s))))/1000.0; if(!SS.usePerspectiveProj) { Message("The perspective factor will have no effect until you " "enable View -> Use Perspective Projection."); } InvalidateGraphics(); break; } case EDIT_GRID_SPACING: { SS.gridSpacing = (float)min(1e4, max(1e-3, SS.StringToMm(s))); InvalidateGraphics(); break; } case EDIT_DIGITS_AFTER_DECIMAL: { int v = atoi(s); if(v < 0 || v > 8) { Error("Specify between 0 and 8 digits after the decimal."); } else { SS.SetUnitDigitsAfterDecimal(v); } InvalidateGraphics(); break; } case EDIT_EXPORT_SCALE: { Expr *e = Expr::From(s, true); if(e) { double ev = e->Eval(); if(fabs(ev) < 0.001 || isnan(ev)) { Error("Export scale must not be zero!"); } else { SS.exportScale = (float)ev; } } break; } case EDIT_EXPORT_OFFSET: { Expr *e = Expr::From(s, true); if(e) { double ev = SS.ExprToMm(e); if(isnan(ev) || ev < 0) { Error("Cutter radius offset must not be negative!"); } else { SS.exportOffset = (float)ev; } } break; } case EDIT_CANVAS_SIZE: { Expr *e = Expr::From(s, true); if(!e) { break; } float d = (float)SS.ExprToMm(e); switch(edit.i) { case 0: SS.exportMargin.left = d; break; case 1: SS.exportMargin.right = d; break; case 2: SS.exportMargin.bottom = d; break; case 3: SS.exportMargin.top = d; break; case 10: SS.exportCanvas.width = d; break; case 11: SS.exportCanvas.height = d; break; case 12: SS.exportCanvas.dx = d; break; case 13: SS.exportCanvas.dy = d; break; } break; } case EDIT_G_CODE_DEPTH: { Expr *e = Expr::From(s, true); if(e) SS.gCode.depth = (float)SS.ExprToMm(e); break; } case EDIT_G_CODE_PASSES: { Expr *e = Expr::From(s, true); if(e) SS.gCode.passes = (int)(e->Eval()); SS.gCode.passes = max(1, min(1000, SS.gCode.passes)); break; } case EDIT_G_CODE_FEED: { Expr *e = Expr::From(s, true); if(e) SS.gCode.feed = (float)SS.ExprToMm(e); break; } case EDIT_G_CODE_PLUNGE_FEED: { Expr *e = Expr::From(s, true); if(e) SS.gCode.plungeFeed = (float)SS.ExprToMm(e); break; } default: return false; } return true; }
void TextWindow::ScreenChangeBackFaces(int link, uint32_t v) { SS.drawBackFaces = !SS.drawBackFaces; InvalidateGraphics(); }
void TextWindow::ScreenChangePwlCurves(int link, uint32_t v) { SS.exportPwlCurves = !SS.exportPwlCurves; InvalidateGraphics(); }
void TextWindow::ScreenChangeStyleYesNo(int link, uint32_t v) { SS.UndoRemember(); hStyle hs = { v }; Style *s = Style::Get(hs); switch(link) { // Units for the width case 'w': if(s->widthAs != Style::UNITS_AS_MM) { s->widthAs = Style::UNITS_AS_MM; s->width /= SS.GW.scale; } break; case 'W': if(s->widthAs != Style::UNITS_AS_PIXELS) { s->widthAs = Style::UNITS_AS_PIXELS; s->width *= SS.GW.scale; } break; // Units for the height case 'g': if(s->textHeightAs != Style::UNITS_AS_MM) { s->textHeightAs = Style::UNITS_AS_MM; s->textHeight /= SS.GW.scale; } break; case 'G': if(s->textHeightAs != Style::UNITS_AS_PIXELS) { s->textHeightAs = Style::UNITS_AS_PIXELS; s->textHeight *= SS.GW.scale; } break; case 'e': s->exportable = !(s->exportable); break; case 'v': s->visible = !(s->visible); break; case 'f': s->filled = !(s->filled); break; // Horizontal text alignment case 'L': s->textOrigin |= Style::ORIGIN_LEFT; s->textOrigin &= ~Style::ORIGIN_RIGHT; break; case 'H': s->textOrigin &= ~Style::ORIGIN_LEFT; s->textOrigin &= ~Style::ORIGIN_RIGHT; break; case 'R': s->textOrigin &= ~Style::ORIGIN_LEFT; s->textOrigin |= Style::ORIGIN_RIGHT; break; // Vertical text alignment case 'B': s->textOrigin |= Style::ORIGIN_BOT; s->textOrigin &= ~Style::ORIGIN_TOP; break; case 'V': s->textOrigin &= ~Style::ORIGIN_BOT; s->textOrigin &= ~Style::ORIGIN_TOP; break; case 'T': s->textOrigin &= ~Style::ORIGIN_BOT; s->textOrigin |= Style::ORIGIN_TOP; break; } InvalidateGraphics(); }
void SolveSpace::ExportViewOrWireframeTo(char *filename, bool wireframe) { int i; SEdgeList edges; ZERO(&edges); SBezierList beziers; ZERO(&beziers); SMesh *sm = NULL; if(SS.GW.showShaded) { Group *g = SK.GetGroup(SS.GW.activeGroup); g->GenerateDisplayItems(); sm = &(g->displayMesh); } if(sm && sm->IsEmpty()) { sm = NULL; } for(i = 0; i < SK.entity.n; i++) { Entity *e = &(SK.entity.elem[i]); if(!e->IsVisible()) continue; if(e->construction) continue; if(SS.exportPwlCurves || (sm && !SS.GW.showHdnLines) || fabs(SS.exportOffset) > LENGTH_EPS) { // We will be doing hidden line removal, which we can't do on // exact curves; so we need things broken down to pwls. Same // problem with cutter radius compensation. e->GenerateEdges(&edges); } else { e->GenerateBezierCurves(&beziers); } } if(SS.GW.showEdges) { Group *g = SK.GetGroup(SS.GW.activeGroup); g->GenerateDisplayItems(); SEdgeList *selr = &(g->displayEdges); SEdge *se; for(se = selr->l.First(); se; se = selr->l.NextAfter(se)) { edges.AddEdge(se->a, se->b, Style::SOLID_EDGE); } } if(SS.GW.showConstraints) { Constraint *c; for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { c->GetEdges(&edges); } } if(wireframe) { VectorFileWriter *out = VectorFileWriter::ForFile(filename); if(out) { ExportWireframeCurves(&edges, &beziers, out); } } else { Vector u = SS.GW.projRight, v = SS.GW.projUp, n = u.Cross(v), origin = SS.GW.offset.ScaledBy(-1); VectorFileWriter *out = VectorFileWriter::ForFile(filename); if(out) { ExportLinesAndMesh(&edges, &beziers, sm, u, v, n, origin, SS.CameraTangent()*SS.GW.scale, out); } if(out && !out->HasCanvasSize()) { // These file formats don't have a canvas size, so they just // get exported in the raw coordinate system. So indicate what // that was on-screen. SS.justExportedInfo.draw = true; SS.justExportedInfo.pt = origin; SS.justExportedInfo.u = u; SS.justExportedInfo.v = v; InvalidateGraphics(); } } edges.Clear(); beziers.Clear(); }
void TextWindow::ScreenChangeShadedTriangles(int link, uint32_t v) { SS.exportShadedTriangles = !SS.exportShadedTriangles; InvalidateGraphics(); }
void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown) { if(GraphicsEditControlIsVisible()) return; if(context.active) return; SS.extraLine.draw = false; if(!orig.mouseDown) { // If someone drags the mouse into our window with the left button // already depressed, then we don't have our starting point; so // don't try. leftDown = false; } if(rightDown) { middleDown = true; shiftDown = !shiftDown; } if(SS.showToolbar) { if(ToolbarMouseMoved((int)x, (int)y)) { hover.Clear(); return; } } if(!leftDown && (pending.operation == DRAGGING_POINTS || pending.operation == DRAGGING_MARQUEE)) { ClearPending(); InvalidateGraphics(); } Point2d mp = Point2d::From(x, y); currentMousePosition = mp; if(rightDown && orig.mouse.DistanceTo(mp) < 5 && !orig.startedMoving) { // Avoid accidentally panning (or rotating if shift is down) if the // user wants a context menu. return; } orig.startedMoving = true; // If the middle button is down, then mouse movement is used to pan and // rotate our view. This wins over everything else. if(middleDown) { hover.Clear(); double dx = (x - orig.mouse.x) / scale; double dy = (y - orig.mouse.y) / scale; if(!(shiftDown || ctrlDown)) { double s = 0.3*(PI/180)*scale; // degrees per pixel projRight = orig.projRight.RotatedAbout(orig.projUp, -s*dx); projUp = orig.projUp.RotatedAbout(orig.projRight, s*dy); NormalizeProjectionVectors(); } else if(ctrlDown) { double theta = atan2(orig.mouse.y, orig.mouse.x); theta -= atan2(y, x); SS.extraLine.draw = true; SS.extraLine.ptA = UnProjectPoint(Point2d::From(0, 0)); SS.extraLine.ptB = UnProjectPoint(mp); Vector normal = orig.projRight.Cross(orig.projUp); projRight = orig.projRight.RotatedAbout(normal, theta); projUp = orig.projUp.RotatedAbout(normal, theta); NormalizeProjectionVectors(); } else { offset.x = orig.offset.x + dx*projRight.x + dy*projUp.x; offset.y = orig.offset.y + dx*projRight.y + dy*projUp.y; offset.z = orig.offset.z + dx*projRight.z + dy*projUp.z; } orig.projRight = projRight; orig.projUp = projUp; orig.offset = offset; orig.mouse.x = x; orig.mouse.y = y; if(SS.TW.shown.screen == TextWindow::SCREEN_EDIT_VIEW) { if(havePainted) { SS.ScheduleShowTW(); } } InvalidateGraphics(); havePainted = false; return; } if(pending.operation == 0) { double dm = orig.mouse.DistanceTo(mp); // If we're currently not doing anything, then see if we should // start dragging something. if(leftDown && dm > 3) { Entity *e = NULL; if(hover.entity.v) e = SK.GetEntity(hover.entity); if(e && e->type != Entity::WORKPLANE) { Entity *e = SK.GetEntity(hover.entity); if(e->type == Entity::CIRCLE && selection.n <= 1) { // Drag the radius. ClearSelection(); pending.circle = hover.entity; pending.operation = DRAGGING_RADIUS; } else if(e->IsNormal()) { ClearSelection(); pending.normal = hover.entity; pending.operation = DRAGGING_NORMAL; } else { if(!hoverWasSelectedOnMousedown) { // The user clicked an unselected entity, which // means they're dragging just the hovered thing, // not the full selection. So clear all the selection // except that entity. ClearSelection(); MakeSelected(e->h); } StartDraggingBySelection(); if(!hoverWasSelectedOnMousedown) { // And then clear the selection again, since they // probably didn't want that selected if they just // were dragging it. ClearSelection(); } hover.Clear(); pending.operation = DRAGGING_POINTS; } } else if(hover.constraint.v && SK.GetConstraint(hover.constraint)->HasLabel()) { ClearSelection(); pending.constraint = hover.constraint; pending.operation = DRAGGING_CONSTRAINT; } if(pending.operation != 0) { // We just started a drag, so remember for the undo before // the drag changes anything. SS.UndoRemember(); } else { if(!hover.constraint.v) { // That's just marquee selection, which should not cause // an undo remember. if(dm > 10) { if(hover.entity.v) { // Avoid accidentally selecting workplanes when // starting drags. MakeUnselected(hover.entity, false); hover.Clear(); } pending.operation = DRAGGING_MARQUEE; orig.marqueePoint = UnProjectPoint(orig.mouseOnButtonDown); } } } } else { // Otherwise, just hit test and give up; but don't hit test // if the mouse is down, because then the user could hover // a point, mouse down (thus selecting it), and drag, in an // effort to drag the point, but instead hover a different // entity before we move far enough to start the drag. if(!leftDown) HitTestMakeSelection(mp); } return; } // If the user has started an operation from the menu, but not // completed it, then just do the selection. if(pending.operation < FIRST_PENDING) { HitTestMakeSelection(mp); return; } // We're currently dragging something; so do that. But if we haven't // painted since the last time we solved, do nothing, because there's // no sense solving a frame and not displaying it. if(!havePainted) { if(pending.operation == DRAGGING_POINTS && ctrlDown) { SS.extraLine.ptA = UnProjectPoint(orig.mouseOnButtonDown); SS.extraLine.ptB = UnProjectPoint(mp); SS.extraLine.draw = true; } return; } switch(pending.operation) { case DRAGGING_CONSTRAINT: { Constraint *c = SK.constraint.FindById(pending.constraint); UpdateDraggedNum(&(c->disp.offset), x, y); orig.mouse = mp; InvalidateGraphics(); break; } case DRAGGING_NEW_LINE_POINT: case DRAGGING_NEW_POINT: UpdateDraggedPoint(pending.point, x, y); HitTestMakeSelection(mp); SS.MarkGroupDirtyByEntity(pending.point); orig.mouse = mp; InvalidateGraphics(); break; case DRAGGING_POINTS: if(shiftDown || ctrlDown) { // Edit the rotation associated with a POINT_N_ROT_TRANS, // either within (ctrlDown) or out of (shiftDown) the plane // of the screen. So first get the rotation to apply, in qt. Quaternion qt; if(ctrlDown) { double d = mp.DistanceTo(orig.mouseOnButtonDown); if(d < 25) { // Don't start dragging the position about the normal // until we're a little ways out, to get a reasonable // reference pos orig.mouse = mp; break; } double theta = atan2(orig.mouse.y-orig.mouseOnButtonDown.y, orig.mouse.x-orig.mouseOnButtonDown.x); theta -= atan2(y-orig.mouseOnButtonDown.y, x-orig.mouseOnButtonDown.x); Vector gn = projRight.Cross(projUp); qt = Quaternion::From(gn, -theta); SS.extraLine.draw = true; SS.extraLine.ptA = UnProjectPoint(orig.mouseOnButtonDown); SS.extraLine.ptB = UnProjectPoint(mp); } else { double dx = -(x - orig.mouse.x); double dy = -(y - orig.mouse.y); double s = 0.3*(PI/180); // degrees per pixel qt = Quaternion::From(projUp, -s*dx).Times( Quaternion::From(projRight, s*dy)); } orig.mouse = mp; // Now apply this rotation to the points being dragged. List<hEntity> *lhe = &(pending.points); for(hEntity *he = lhe->First(); he; he = lhe->NextAfter(he)) { Entity *e = SK.GetEntity(*he); if(e->type != Entity::POINT_N_ROT_TRANS) { if(ctrlDown) { Vector p = e->PointGetNum(); p = p.Minus(SS.extraLine.ptA); p = qt.Rotate(p); p = p.Plus(SS.extraLine.ptA); e->PointForceTo(p); SS.MarkGroupDirtyByEntity(e->h); } continue; } Quaternion q = e->PointGetQuaternion(); Vector p = e->PointGetNum(); q = qt.Times(q); e->PointForceQuaternionTo(q); // Let's rotate about the selected point; so fix up the // translation so that that point didn't move. e->PointForceTo(p); SS.MarkGroupDirtyByEntity(e->h); } } else { List<hEntity> *lhe = &(pending.points); for(hEntity *he = lhe->First(); he; he = lhe->NextAfter(he)) { UpdateDraggedPoint(*he, x, y); SS.MarkGroupDirtyByEntity(*he); } orig.mouse = mp; } break; case DRAGGING_NEW_CUBIC_POINT: { UpdateDraggedPoint(pending.point, x, y); HitTestMakeSelection(mp); hRequest hr = pending.point.request(); if(pending.point.v == hr.entity(4).v) { // The very first segment; dragging final point drags both // tangent points. Vector p0 = SK.GetEntity(hr.entity(1))->PointGetNum(), p3 = SK.GetEntity(hr.entity(4))->PointGetNum(), p1 = p0.ScaledBy(2.0/3).Plus(p3.ScaledBy(1.0/3)), p2 = p0.ScaledBy(1.0/3).Plus(p3.ScaledBy(2.0/3)); SK.GetEntity(hr.entity(1+1))->PointForceTo(p1); SK.GetEntity(hr.entity(1+2))->PointForceTo(p2); } else { // A subsequent segment; dragging point drags only final // tangent point. int i = SK.GetEntity(hr.entity(0))->extraPoints; Vector pn = SK.GetEntity(hr.entity(4+i))->PointGetNum(), pnm2 = SK.GetEntity(hr.entity(2+i))->PointGetNum(), pnm1 = (pn.Plus(pnm2)).ScaledBy(0.5); SK.GetEntity(hr.entity(3+i))->PointForceTo(pnm1); } orig.mouse = mp; SS.MarkGroupDirtyByEntity(pending.point); break; } case DRAGGING_NEW_ARC_POINT: { UpdateDraggedPoint(pending.point, x, y); HitTestMakeSelection(mp); hRequest hr = pending.point.request(); Vector ona = SK.GetEntity(hr.entity(2))->PointGetNum(); Vector onb = SK.GetEntity(hr.entity(3))->PointGetNum(); Vector center = (ona.Plus(onb)).ScaledBy(0.5); SK.GetEntity(hr.entity(1))->PointForceTo(center); orig.mouse = mp; SS.MarkGroupDirtyByEntity(pending.point); break; } case DRAGGING_NEW_RADIUS: case DRAGGING_RADIUS: { Entity *circle = SK.GetEntity(pending.circle); Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); Point2d c2 = ProjectPoint(center); double r = c2.DistanceTo(mp)/scale; SK.GetEntity(circle->distance)->DistanceForceTo(r); SS.MarkGroupDirtyByEntity(pending.circle); break; } case DRAGGING_NORMAL: { Entity *normal = SK.GetEntity(pending.normal); Vector p = SK.GetEntity(normal->point[0])->PointGetNum(); Point2d p2 = ProjectPoint(p); Quaternion q = normal->NormalGetNum(); Vector u = q.RotationU(), v = q.RotationV(); if(ctrlDown) { double theta = atan2(orig.mouse.y-p2.y, orig.mouse.x-p2.x); theta -= atan2(y-p2.y, x-p2.x); Vector normal = projRight.Cross(projUp); u = u.RotatedAbout(normal, -theta); v = v.RotatedAbout(normal, -theta); } else { double dx = -(x - orig.mouse.x); double dy = -(y - orig.mouse.y); double s = 0.3*(PI/180); // degrees per pixel u = u.RotatedAbout(projUp, -s*dx); u = u.RotatedAbout(projRight, s*dy); v = v.RotatedAbout(projUp, -s*dx); v = v.RotatedAbout(projRight, s*dy); } orig.mouse = mp; normal->NormalForceTo(Quaternion::From(u, v)); SS.MarkGroupDirtyByEntity(pending.normal); break; } case DRAGGING_MARQUEE: orig.mouse = mp; InvalidateGraphics(); break; default: oops(); } if(pending.operation != 0 && pending.operation != DRAGGING_CONSTRAINT && pending.operation != DRAGGING_MARQUEE) { SS.GenerateAll(); } havePainted = false; }
void GraphicsWindow::MouseRightUp(double x, double y) { SS.extraLine.draw = false; InvalidateGraphics(); // Don't show a context menu if the user is right-clicking the toolbar, // or if they are finishing a pan. if(ToolbarMouseMoved((int)x, (int)y)) return; if(orig.startedMoving) return; if(context.active) return; if(pending.operation == DRAGGING_NEW_LINE_POINT || pending.operation == DRAGGING_NEW_CUBIC_POINT) { // Special case; use a right click to stop drawing lines, since // a left click would draw another one. This is quicker and more // intuitive than hitting escape. Likewise for new cubic segments. ClearPending(); return; } context.active = true; if(!hover.IsEmpty()) { MakeSelected(&hover); SS.ScheduleShowTW(); } GroupSelection(); bool itemsSelected = (gs.n > 0 || gs.constraints > 0); if(itemsSelected) { if(gs.stylables > 0) { ContextMenuListStyles(); AddContextMenuItem("Assign to Style", CONTEXT_SUBMENU); } if(gs.n + gs.constraints == 1) { AddContextMenuItem("Group Info", CMNU_GROUP_INFO); } if(gs.n + gs.constraints == 1 && gs.stylables == 1) { AddContextMenuItem("Style Info", CMNU_STYLE_INFO); } if(gs.withEndpoints > 0) { AddContextMenuItem("Select Edge Chain", CMNU_SELECT_CHAIN); } if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->HasLabel() && c->type != Constraint::COMMENT) { AddContextMenuItem("Toggle Reference Dimension", CMNU_REFERENCE_DIM); } if(c->type == Constraint::ANGLE || c->type == Constraint::EQUAL_ANGLE) { AddContextMenuItem("Other Supplementary Angle", CMNU_OTHER_ANGLE); } } if(gs.comments > 0 || gs.points > 0) { AddContextMenuItem("Snap to Grid", CMNU_SNAP_TO_GRID); } if(gs.points == 1) { Entity *p = SK.GetEntity(gs.point[0]); Constraint *c; IdList<Constraint,hConstraint> *lc = &(SK.constraint); for(c = lc->First(); c; c = lc->NextAfter(c)) { if(c->type != Constraint::POINTS_COINCIDENT) continue; if(c->ptA.v == p->h.v || c->ptB.v == p->h.v) { break; } } if(c) { AddContextMenuItem("Delete Point-Coincident Constraint", CMNU_DEL_COINCIDENT); } } AddContextMenuItem(NULL, CONTEXT_SEPARATOR); if(LockedInWorkplane()) { AddContextMenuItem("Cut", CMNU_CUT_SEL); AddContextMenuItem("Copy", CMNU_COPY_SEL); } } if(SS.clipboard.r.n > 0 && LockedInWorkplane()) { AddContextMenuItem("Paste", CMNU_PASTE_SEL); } if(itemsSelected) { AddContextMenuItem("Delete", CMNU_DELETE_SEL); AddContextMenuItem(NULL, CONTEXT_SEPARATOR); AddContextMenuItem("Unselect All", CMNU_UNSELECT_ALL); } // If only one item is selected, then it must be the one that we just // selected from the hovered item; in which case unselect all and hovered // are equivalent. if(!hover.IsEmpty() && selection.n > 1) { AddContextMenuItem("Unselect Hovered", CMNU_UNSELECT_HOVERED); } int ret = ShowContextMenu(); switch(ret) { case CMNU_UNSELECT_ALL: MenuEdit(MNU_UNSELECT_ALL); break; case CMNU_UNSELECT_HOVERED: if(!hover.IsEmpty()) { MakeUnselected(&hover, true); } break; case CMNU_SELECT_CHAIN: MenuEdit(MNU_SELECT_CHAIN); break; case CMNU_CUT_SEL: MenuClipboard(MNU_CUT); break; case CMNU_COPY_SEL: MenuClipboard(MNU_COPY); break; case CMNU_PASTE_SEL: MenuClipboard(MNU_PASTE); break; case CMNU_DELETE_SEL: MenuClipboard(MNU_DELETE); break; case CMNU_REFERENCE_DIM: Constraint::MenuConstrain(MNU_REFERENCE); break; case CMNU_OTHER_ANGLE: Constraint::MenuConstrain(MNU_OTHER_ANGLE); break; case CMNU_DEL_COINCIDENT: { SS.UndoRemember(); if(!gs.point[0].v) break; Entity *p = SK.GetEntity(gs.point[0]); if(!p->IsPoint()) break; SK.constraint.ClearTags(); Constraint *c; for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { if(c->type != Constraint::POINTS_COINCIDENT) continue; if(c->ptA.v == p->h.v || c->ptB.v == p->h.v) { c->tag = 1; } } SK.constraint.RemoveTagged(); ClearSelection(); break; } case CMNU_SNAP_TO_GRID: MenuEdit(MNU_SNAP_TO_GRID); break; case CMNU_GROUP_INFO: { hGroup hg; if(gs.entities == 1) { hg = SK.GetEntity(gs.entity[0])->group; } else if(gs.points == 1) { hg = SK.GetEntity(gs.point[0])->group; } else if(gs.constraints == 1) { hg = SK.GetConstraint(gs.constraint[0])->group; } else { break; } ClearSelection(); SS.TW.GoToScreen(TextWindow::SCREEN_GROUP_INFO); SS.TW.shown.group = hg; SS.ScheduleShowTW(); break; } case CMNU_STYLE_INFO: { hStyle hs; if(gs.entities == 1) { hs = Style::ForEntity(gs.entity[0]); } else if(gs.points == 1) { hs = Style::ForEntity(gs.point[0]); } else if(gs.constraints == 1) { hs = SK.GetConstraint(gs.constraint[0])->disp.style; if(!hs.v) hs.v = Style::CONSTRAINT; } else { break; } ClearSelection(); SS.TW.GoToScreen(TextWindow::SCREEN_STYLE_INFO); SS.TW.shown.style = hs; SS.ScheduleShowTW(); break; } case CMNU_NEW_CUSTOM_STYLE: { uint32_t v = Style::CreateCustomStyle(); Style::AssignSelectionToStyle(v); break; } case CMNU_NO_STYLE: Style::AssignSelectionToStyle(0); break; default: if(ret >= CMNU_FIRST_STYLE) { Style::AssignSelectionToStyle(ret - CMNU_FIRST_STYLE); } else { // otherwise it was cancelled, so do nothing contextMenuCancelTime = GetMilliseconds(); } break; } context.active = false; SS.ScheduleShowTW(); }
void GraphicsWindow::MenuRequest(int id) { const char *s; switch(id) { case MNU_SEL_WORKPLANE: { SS.GW.GroupSelection(); Group *g = SK.GetGroup(SS.GW.activeGroup); if(SS.GW.gs.n == 1 && SS.GW.gs.workplanes == 1) { // A user-selected workplane g->activeWorkplane = SS.GW.gs.entity[0]; } else if(g->type == Group::DRAWING_WORKPLANE) { // The group's default workplane g->activeWorkplane = g->h.entity(0); Message("No workplane selected. Activating default workplane " "for this group."); } if(!SS.GW.LockedInWorkplane()) { Error("No workplane is selected, and the active group does " "not have a default workplane. Try selecting a " "workplane, or activating a sketch-in-new-workplane " "group."); break; } // Align the view with the selected workplane SS.GW.AnimateOntoWorkplane(); SS.GW.ClearSuper(); SS.ScheduleShowTW(); break; } case MNU_FREE_IN_3D: SS.GW.SetWorkplaneFreeIn3d(); SS.GW.EnsureValidActives(); SS.ScheduleShowTW(); InvalidateGraphics(); break; case MNU_TANGENT_ARC: SS.GW.GroupSelection(); if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) { SS.GW.MakeTangentArc(); } else if(SS.GW.gs.n != 0) { Error("Bad selection for tangent arc at point. Select a " "single point, or select nothing to set up arc " "parameters."); } else { SS.TW.GoToScreen(TextWindow::SCREEN_TANGENT_ARC); SS.GW.ForceTextWindowShown(); SS.ScheduleShowTW(); InvalidateGraphics(); // repaint toolbar } break; case MNU_ARC: s = "click point on arc (draws anti-clockwise)"; goto c; case MNU_DATUM_POINT: s = "click to place datum point"; goto c; case MNU_LINE_SEGMENT: s = "click first point of line segment"; goto c; case MNU_CONSTR_SEGMENT: s = "click first point of construction line segment"; goto c; case MNU_CUBIC: s = "click first point of cubic segment"; goto c; case MNU_CIRCLE: s = "click center of circle"; goto c; case MNU_WORKPLANE: s = "click origin of workplane"; goto c; case MNU_RECTANGLE: s = "click one corner of rectangle"; goto c; case MNU_TTF_TEXT: s = "click top left of text"; goto c; c: SS.GW.pending.operation = id; SS.GW.pending.description = s; SS.ScheduleShowTW(); InvalidateGraphics(); // repaint toolbar break; case MNU_CONSTRUCTION: { SS.UndoRemember(); SS.GW.GroupSelection(); if(SS.GW.gs.entities == 0) { Error("No entities are selected. Select entities before " "trying to toggle their construction state."); } int i; for(i = 0; i < SS.GW.gs.entities; i++) { hEntity he = SS.GW.gs.entity[i]; if(!he.isFromRequest()) continue; Request *r = SK.GetRequest(he.request()); r->construction = !(r->construction); SS.MarkGroupDirty(r->group); } SS.GW.ClearSelection(); SS.GenerateAll(); break; } case MNU_SPLIT_CURVES: SS.GW.SplitLinesOrCurves(); break; default: oops(); } }
void GraphicsWindow::MenuEdit(int id) { switch(id) { case MNU_UNSELECT_ALL: SS.GW.GroupSelection(); // If there's nothing selected to de-select, and no operation // to cancel, then perhaps they want to return to the home // screen in the text window. if(SS.GW.gs.n == 0 && SS.GW.gs.constraints == 0 && SS.GW.pending.operation == 0) { if(!(TextEditControlIsVisible() || GraphicsEditControlIsVisible())) { if(SS.TW.shown.screen == TextWindow::SCREEN_STYLE_INFO) { SS.TW.GoToScreen(TextWindow::SCREEN_LIST_OF_STYLES); } else { SS.TW.ClearSuper(); } } } SS.GW.ClearSuper(); SS.TW.HideEditControl(); SS.nakedEdges.Clear(); SS.justExportedInfo.draw = false; // This clears the marks drawn to indicate which points are // still free to drag. Param *p; for(p = SK.param.First(); p; p = SK.param.NextAfter(p)) { p->free = false; } if(SS.exportMode) { SS.exportMode = false; SS.GenerateAll(SolveSpaceUI::GENERATE_ALL); } break; case MNU_SELECT_ALL: { Entity *e; for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { if(e->group.v != SS.GW.activeGroup.v) continue; if(e->IsFace() || e->IsDistance()) continue; if(!e->IsVisible()) continue; SS.GW.MakeSelected(e->h); } InvalidateGraphics(); SS.ScheduleShowTW(); break; } case MNU_SELECT_CHAIN: { Entity *e; int newlySelected = 0; bool didSomething; do { didSomething = false; for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { if(e->group.v != SS.GW.activeGroup.v) continue; if(!e->HasEndpoints()) continue; if(!e->IsVisible()) continue; Vector st = e->EndpointStart(), fi = e->EndpointFinish(); bool onChain = false, alreadySelected = false; List<Selection> *ls = &(SS.GW.selection); for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { if(!s->entity.v) continue; if(s->entity.v == e->h.v) { alreadySelected = true; continue; } Entity *se = SK.GetEntity(s->entity); if(!se->HasEndpoints()) continue; Vector sst = se->EndpointStart(), sfi = se->EndpointFinish(); if(sst.Equals(st) || sst.Equals(fi) || sfi.Equals(st) || sfi.Equals(fi)) { onChain = true; } } if(onChain && !alreadySelected) { SS.GW.MakeSelected(e->h); newlySelected++; didSomething = true; } } } while(didSomething); if(newlySelected == 0) { Error("No additional entities share endpoints with the " "selected entities."); } InvalidateGraphics(); SS.ScheduleShowTW(); break; } case MNU_ROTATE_90: { SS.GW.GroupSelection(); Entity *e = NULL; if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) { e = SK.GetEntity(SS.GW.gs.point[0]); } else if(SS.GW.gs.n == 1 && SS.GW.gs.entities == 1) { e = SK.GetEntity(SS.GW.gs.entity[0]); } SS.GW.ClearSelection(); hGroup hg = e ? e->group : SS.GW.activeGroup; Group *g = SK.GetGroup(hg); if(g->type != Group::IMPORTED) { Error("To use this command, select a point or other " "entity from an imported part, or make an import " "group the active group."); break; } SS.UndoRemember(); // Rotate by ninety degrees about the coordinate axis closest // to the screen normal. Vector norm = SS.GW.projRight.Cross(SS.GW.projUp); norm = norm.ClosestOrtho(); norm = norm.WithMagnitude(1); Quaternion qaa = Quaternion::From(norm, PI/2); g->TransformImportedBy(Vector::From(0, 0, 0), qaa); // and regenerate as necessary. SS.MarkGroupDirty(hg); SS.GenerateAll(); break; } case MNU_SNAP_TO_GRID: { if(!SS.GW.LockedInWorkplane()) { Error("No workplane is active. Select a workplane to define " "the plane for the snap grid."); break; } SS.GW.GroupSelection(); if(SS.GW.gs.points == 0 && SS.GW.gs.constraintLabels == 0) { Error("Can't snap these items to grid; select points, " "text comments, or constraints with a label. " "To snap a line, select its endpoints."); break; } SS.UndoRemember(); List<Selection> *ls = &(SS.GW.selection); for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { if(s->entity.v) { hEntity hp = s->entity; Entity *ep = SK.GetEntity(hp); if(!ep->IsPoint()) continue; Vector p = ep->PointGetNum(); ep->PointForceTo(SS.GW.SnapToGrid(p)); SS.GW.pending.points.Add(&hp); SS.MarkGroupDirty(ep->group); } else if(s->constraint.v) { Constraint *c = SK.GetConstraint(s->constraint); c->disp.offset = SS.GW.SnapToGrid(c->disp.offset); } } // Regenerate, with these points marked as dragged so that they // get placed as close as possible to our snap grid. SS.GenerateAll(); SS.GW.ClearPending(); SS.GW.ClearSelection(); InvalidateGraphics(); break; } case MNU_UNDO: SS.UndoUndo(); break; case MNU_REDO: SS.UndoRedo(); break; case MNU_REGEN_ALL: SS.ReloadAllImported(); SS.GenerateAll(SolveSpaceUI::GENERATE_UNTIL_ACTIVE); SS.ScheduleShowTW(); break; default: oops(); } }
void GraphicsWindow::MenuView(int id) { switch(id) { case MNU_ZOOM_IN: SS.GW.scale *= 1.2; SS.ScheduleShowTW(); break; case MNU_ZOOM_OUT: SS.GW.scale /= 1.2; SS.ScheduleShowTW(); break; case MNU_ZOOM_TO_FIT: SS.GW.ZoomToFit(/*includingInvisibles=*/false, /*useSelection=*/true); SS.ScheduleShowTW(); break; case MNU_SHOW_GRID: SS.GW.showSnapGrid = !SS.GW.showSnapGrid; if(SS.GW.showSnapGrid && !SS.GW.LockedInWorkplane()) { Message("No workplane is active, so the grid will not " "appear."); } SS.GW.EnsureValidActives(); InvalidateGraphics(); break; case MNU_PERSPECTIVE_PROJ: SS.usePerspectiveProj = !SS.usePerspectiveProj; if(SS.cameraTangent < 1e-6) { Error("The perspective factor is set to zero, so the view will " "always be a parallel projection.\n\n" "For a perspective projection, modify the perspective " "factor in the configuration screen. A value around 0.3 " "is typical."); } SS.GW.EnsureValidActives(); InvalidateGraphics(); break; case MNU_ONTO_WORKPLANE: if(SS.GW.LockedInWorkplane()) { SS.GW.AnimateOntoWorkplane(); SS.GW.ClearSuper(); SS.ScheduleShowTW(); break; } // if not in 2d mode fall through and use ORTHO logic case MNU_NEAREST_ORTHO: case MNU_NEAREST_ISO: { static const Vector ortho[3] = { Vector::From(1, 0, 0), Vector::From(0, 1, 0), Vector::From(0, 0, 1) }; double sqrt2 = sqrt(2.0), sqrt6 = sqrt(6.0); Quaternion quat0 = Quaternion::From(SS.GW.projRight, SS.GW.projUp); Quaternion quatf = quat0; double dmin = 1e10; // There are 24 possible views; 3*2*2*2 int i, j, negi, negj; for(i = 0; i < 3; i++) { for(j = 0; j < 3; j++) { if(i == j) continue; for(negi = 0; negi < 2; negi++) { for(negj = 0; negj < 2; negj++) { Vector ou = ortho[i], ov = ortho[j]; if(negi) ou = ou.ScaledBy(-1); if(negj) ov = ov.ScaledBy(-1); Vector on = ou.Cross(ov); Vector u, v; if(id == MNU_NEAREST_ORTHO || id == MNU_ONTO_WORKPLANE) { u = ou; v = ov; } else { u = ou.ScaledBy(1/sqrt2).Plus( on.ScaledBy(-1/sqrt2)); v = ou.ScaledBy(-1/sqrt6).Plus( ov.ScaledBy(2/sqrt6).Plus( on.ScaledBy(-1/sqrt6))); } Quaternion quatt = Quaternion::From(u, v); double d = min( (quatt.Minus(quat0)).Magnitude(), (quatt.Plus(quat0)).Magnitude()); if(d < dmin) { dmin = d; quatf = quatt; } } } } } SS.GW.AnimateOnto(quatf, SS.GW.offset); break; } case MNU_CENTER_VIEW: SS.GW.GroupSelection(); if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) { Quaternion quat0; // Offset is the selected point, quaternion is same as before Vector pt = SK.GetEntity(SS.GW.gs.point[0])->PointGetNum(); quat0 = Quaternion::From(SS.GW.projRight, SS.GW.projUp); SS.GW.AnimateOnto(quat0, pt.ScaledBy(-1)); SS.GW.ClearSelection(); } else { Error("Select a point; this point will become the center " "of the view on screen."); } break; case MNU_SHOW_MENU_BAR: ToggleMenuBar(); SS.GW.EnsureValidActives(); InvalidateGraphics(); break; case MNU_SHOW_TOOLBAR: SS.showToolbar = !SS.showToolbar; SS.GW.EnsureValidActives(); InvalidateGraphics(); break; case MNU_SHOW_TEXT_WND: SS.GW.showTextWindow = !SS.GW.showTextWindow; SS.GW.EnsureValidActives(); break; case MNU_UNITS_INCHES: SS.viewUnits = SolveSpaceUI::UNIT_INCHES; SS.ScheduleShowTW(); SS.GW.EnsureValidActives(); break; case MNU_UNITS_MM: SS.viewUnits = SolveSpaceUI::UNIT_MM; SS.ScheduleShowTW(); SS.GW.EnsureValidActives(); break; case MNU_FULL_SCREEN: ToggleFullScreen(); SS.GW.EnsureValidActives(); break; default: oops(); } InvalidateGraphics(); }
void GraphicsWindow::MouseLeftDown(double mx, double my) { orig.mouseDown = true; if(GraphicsEditControlIsVisible()) { orig.mouse = Point2d::From(mx, my); orig.mouseOnButtonDown = orig.mouse; HideGraphicsEditControl(); return; } SS.TW.HideEditControl(); if(SS.showToolbar) { if(ToolbarMouseDown((int)mx, (int)my)) return; } // Make sure the hover is up to date. MouseMoved(mx, my, false, false, false, false, false); orig.mouse.x = mx; orig.mouse.y = my; orig.mouseOnButtonDown = orig.mouse; // The current mouse location Vector v = offset.ScaledBy(-1); v = v.Plus(projRight.ScaledBy(mx/scale)); v = v.Plus(projUp.ScaledBy(my/scale)); hRequest hr; switch(pending.operation) { case MNU_DATUM_POINT: hr = AddRequest(Request::DATUM_POINT); SK.GetEntity(hr.entity(0))->PointForceTo(v); ConstrainPointByHovered(hr.entity(0)); ClearSuper(); break; case MNU_LINE_SEGMENT: hr = AddRequest(Request::LINE_SEGMENT); SK.GetEntity(hr.entity(1))->PointForceTo(v); ConstrainPointByHovered(hr.entity(1)); ClearSuper(); pending.operation = DRAGGING_NEW_LINE_POINT; pending.point = hr.entity(2); pending.description = "click next point of line, or press Esc"; SK.GetEntity(pending.point)->PointForceTo(v); break; case MNU_RECTANGLE: { if(!SS.GW.LockedInWorkplane()) { Error("Can't draw rectangle in 3d; select a workplane first."); ClearSuper(); break; } hRequest lns[4]; int i; SS.UndoRemember(); for(i = 0; i < 4; i++) { lns[i] = AddRequest(Request::LINE_SEGMENT, false); } for(i = 0; i < 4; i++) { Constraint::ConstrainCoincident( lns[i].entity(1), lns[(i+1)%4].entity(2)); SK.GetEntity(lns[i].entity(1))->PointForceTo(v); SK.GetEntity(lns[i].entity(2))->PointForceTo(v); } for(i = 0; i < 4; i++) { Constraint::Constrain( (i % 2) ? Constraint::HORIZONTAL : Constraint::VERTICAL, Entity::NO_ENTITY, Entity::NO_ENTITY, lns[i].entity(0)); } ConstrainPointByHovered(lns[2].entity(1)); pending.operation = DRAGGING_NEW_POINT; pending.point = lns[1].entity(2); pending.description = "click to place other corner of rectangle"; break; } case MNU_CIRCLE: hr = AddRequest(Request::CIRCLE); // Centered where we clicked SK.GetEntity(hr.entity(1))->PointForceTo(v); // Normal to the screen SK.GetEntity(hr.entity(32))->NormalForceTo( Quaternion::From(SS.GW.projRight, SS.GW.projUp)); // Initial radius zero SK.GetEntity(hr.entity(64))->DistanceForceTo(0); ConstrainPointByHovered(hr.entity(1)); ClearSuper(); pending.operation = DRAGGING_NEW_RADIUS; pending.circle = hr.entity(0); pending.description = "click to set radius"; break; case MNU_ARC: { if(!SS.GW.LockedInWorkplane()) { Error("Can't draw arc in 3d; select a workplane first."); ClearPending(); break; } hr = AddRequest(Request::ARC_OF_CIRCLE); // This fudge factor stops us from immediately failing to solve // because of the arc's implicit (equal radius) tangent. Vector adj = SS.GW.projRight.WithMagnitude(2/SS.GW.scale); SK.GetEntity(hr.entity(1))->PointForceTo(v.Minus(adj)); SK.GetEntity(hr.entity(2))->PointForceTo(v); SK.GetEntity(hr.entity(3))->PointForceTo(v); ConstrainPointByHovered(hr.entity(2)); ClearSuper(); pending.operation = DRAGGING_NEW_ARC_POINT; pending.point = hr.entity(3); pending.description = "click to place point"; break; } case MNU_CUBIC: hr = AddRequest(Request::CUBIC); SK.GetEntity(hr.entity(1))->PointForceTo(v); SK.GetEntity(hr.entity(2))->PointForceTo(v); SK.GetEntity(hr.entity(3))->PointForceTo(v); SK.GetEntity(hr.entity(4))->PointForceTo(v); ConstrainPointByHovered(hr.entity(1)); ClearSuper(); pending.operation = DRAGGING_NEW_CUBIC_POINT; pending.point = hr.entity(4); pending.description = "click next point of cubic, or press Esc"; break; case MNU_WORKPLANE: if(LockedInWorkplane()) { Error("Sketching in a workplane already; sketch in 3d before " "creating new workplane."); ClearSuper(); break; } hr = AddRequest(Request::WORKPLANE); SK.GetEntity(hr.entity(1))->PointForceTo(v); SK.GetEntity(hr.entity(32))->NormalForceTo( Quaternion::From(SS.GW.projRight, SS.GW.projUp)); ConstrainPointByHovered(hr.entity(1)); ClearSuper(); break; case MNU_TTF_TEXT: { if(!SS.GW.LockedInWorkplane()) { Error("Can't draw text in 3d; select a workplane first."); ClearSuper(); break; } hr = AddRequest(Request::TTF_TEXT); Request *r = SK.GetRequest(hr); r->str.strcpy("Abc"); r->font.strcpy("arial.ttf"); SK.GetEntity(hr.entity(1))->PointForceTo(v); SK.GetEntity(hr.entity(2))->PointForceTo(v); pending.operation = DRAGGING_NEW_POINT; pending.point = hr.entity(2); pending.description = "click to place bottom left of text"; break; } case MNU_COMMENT: { ClearSuper(); Constraint c; ZERO(&c); c.group = SS.GW.activeGroup; c.workplane = SS.GW.ActiveWorkplane(); c.type = Constraint::COMMENT; c.disp.offset = v; c.comment.strcpy("NEW COMMENT -- DOUBLE-CLICK TO EDIT"); Constraint::AddConstraint(&c); break; } case DRAGGING_RADIUS: case DRAGGING_NEW_POINT: // The MouseMoved event has already dragged it as desired. ClearPending(); break; case DRAGGING_NEW_ARC_POINT: ConstrainPointByHovered(pending.point); ClearPending(); break; case DRAGGING_NEW_CUBIC_POINT: { hRequest hr = pending.point.request(); Request *r = SK.GetRequest(hr); if(hover.entity.v == hr.entity(1).v && r->extraPoints >= 2) { // They want the endpoints coincident, which means a periodic // spline instead. r->type = Request::CUBIC_PERIODIC; // Remove the off-curve control points, which are no longer // needed here; so move [2,ep+1] down, skipping first pt. int i; for(i = 2; i <= r->extraPoints+1; i++) { SK.GetEntity(hr.entity((i-1)+1))->PointForceTo( SK.GetEntity(hr.entity(i+1))->PointGetNum()); } // and move ep+3 down by two, skipping both SK.GetEntity(hr.entity((r->extraPoints+1)+1))->PointForceTo( SK.GetEntity(hr.entity((r->extraPoints+3)+1))->PointGetNum()); r->extraPoints -= 2; // And we're done. SS.MarkGroupDirty(r->group); SS.ScheduleGenerateAll(); ClearPending(); break; } if(ConstrainPointByHovered(pending.point)) { ClearPending(); break; } Entity e; if(r->extraPoints >= (int)arraylen(e.point) - 4) { ClearPending(); break; } (SK.GetRequest(hr)->extraPoints)++; SS.GenerateAll(-1, -1); int ep = r->extraPoints; Vector last = SK.GetEntity(hr.entity(3+ep))->PointGetNum(); SK.GetEntity(hr.entity(2+ep))->PointForceTo(last); SK.GetEntity(hr.entity(3+ep))->PointForceTo(v); SK.GetEntity(hr.entity(4+ep))->PointForceTo(v); pending.point = hr.entity(4+ep); break; } 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); // Displace the second point of the new line segment slightly, // to avoid creating zero-length edge warnings. SK.GetEntity(hr.entity(2))->PointForceTo( v.Plus(projRight.ScaledBy(0.5/scale))); // Constrain the line segments to share an endpoint Constraint::ConstrainCoincident(pending.point, hr.entity(1)); // And drag an endpoint of the new line segment pending.operation = DRAGGING_NEW_LINE_POINT; pending.point = hr.entity(2); pending.description = "click next point of line, or press Esc"; break; } case 0: default: ClearPending(); if(!hover.IsEmpty()) { hoverWasSelectedOnMousedown = IsSelected(&hover); MakeSelected(&hover); } break; } SS.ScheduleShowTW(); InvalidateGraphics(); }
//----------------------------------------------------------------------------- // The edit control is visible, and the user just pressed enter. //----------------------------------------------------------------------------- void TextWindow::EditControlDone(const char *s) { edit.showAgain = false; switch(edit.meaning) { case EDIT_TIMES_REPEATED: { Expr *e = Expr::From(s, true); if(e) { SS.UndoRemember(); double ev = e->Eval(); if((int)ev < 1) { Error("Can't repeat fewer than 1 time."); break; } if((int)ev > 999) { Error("Can't repeat more than 999 times."); break; } Group *g = SK.GetGroup(edit.group); g->valA = ev; if(g->type == Group::ROTATE) { int i, c = 0; for(i = 0; i < SK.constraint.n; i++) { if(SK.constraint.elem[i].group.v == g->h.v) c++; } // If the group does not contain any constraints, then // set the numerical guess to space the copies uniformly // over one rotation. Don't touch the guess if we're // already constrained, because that would break // convergence. if(c == 0) { double copies = (g->skipFirst) ? (ev + 1) : ev; SK.GetParam(g->h.param(3))->val = PI/(2*copies); } } SS.MarkGroupDirty(g->h); SS.later.generateAll = true; } break; } case EDIT_GROUP_NAME: { if(!StringAllPrintable(s) || !*s) { Error("Invalid characters. Allowed are: A-Z a-z 0-9 _ -"); } else { SS.UndoRemember(); Group *g = SK.GetGroup(edit.group); g->name.strcpy(s); } break; } case EDIT_GROUP_SCALE: { Expr *e = Expr::From(s, true); 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; } } break; } case EDIT_GROUP_COLOR: { Vector rgb; if(sscanf(s, "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) { rgb = rgb.ClampWithin(0, 1); Group *g = SK.group.FindByIdNoOops(SS.TW.shown.group); if(!g) break; g->color = RGBf(rgb.x, rgb.y, rgb.z); SS.MarkGroupDirty(g->h); SS.later.generateAll = true; SS.GW.ClearSuper(); } else { Error("Bad format: specify color as r, g, b"); } break; } case EDIT_TTF_TEXT: { SS.UndoRemember(); Request *r = SK.request.FindByIdNoOops(edit.request); if(r) { r->str.strcpy(s); SS.MarkGroupDirty(r->group); SS.later.generateAll = true; } break; } case EDIT_STEP_DIM_FINISH: { Expr *e = Expr::From(s, true); if(!e) { break; } if(shown.dimIsDistance) { shown.dimFinish = SS.ExprToMm(e); } else { shown.dimFinish = e->Eval(); } break; } case EDIT_STEP_DIM_STEPS: shown.dimSteps = min(300, max(1, atoi(s))); break; case EDIT_TANGENT_ARC_RADIUS: { Expr *e = Expr::From(s, true); if(!e) break; if(e->Eval() < LENGTH_EPS) { Error("Radius cannot be zero or negative."); break; } SS.tangentArcRadius = SS.ExprToMm(e); break; } default: { int cnt = 0; if(EditControlDoneForStyles(s)) cnt++; if(EditControlDoneForConfiguration(s)) cnt++; if(EditControlDoneForPaste(s)) cnt++; if(EditControlDoneForView(s)) cnt++; if(cnt > 1) { // The identifiers were somehow assigned not uniquely? oops(); } break; } } InvalidateGraphics(); SS.later.showTW = true; if(!edit.showAgain) { HideEditControl(); edit.meaning = EDIT_NOTHING; } }
void SolveSpaceUI::MenuAnalyze(int id) { SS.GW.GroupSelection(); #define gs (SS.GW.gs) switch(id) { case GraphicsWindow::MNU_STEP_DIM: if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->HasLabel() && !c->reference) { SS.TW.shown.dimFinish = c->valA; SS.TW.shown.dimSteps = 10; SS.TW.shown.dimIsDistance = (c->type != Constraint::ANGLE) && (c->type != Constraint::LENGTH_RATIO) && (c->type != Constraint::LENGTH_DIFFERENCE); SS.TW.shown.constraint = c->h; SS.TW.shown.screen = TextWindow::SCREEN_STEP_DIMENSION; // The step params are specified in the text window, // so force that to be shown. SS.GW.ForceTextWindowShown(); SS.ScheduleShowTW(); SS.GW.ClearSelection(); } else { Error("Constraint must have a label, and must not be " "a reference dimension."); } } else { Error("Bad selection for step dimension; select a constraint."); } break; case GraphicsWindow::MNU_NAKED_EDGES: { SS.nakedEdges.Clear(); Group *g = SK.GetGroup(SS.GW.activeGroup); SMesh *m = &(g->displayMesh); SKdNode *root = SKdNode::From(m); bool inters, leaks; root->MakeCertainEdgesInto(&(SS.nakedEdges), SKdNode::NAKED_OR_SELF_INTER_EDGES, true, &inters, &leaks); InvalidateGraphics(); const char *intersMsg = inters ? "The mesh is self-intersecting (NOT okay, invalid)." : "The mesh is not self-intersecting (okay, valid)."; const char *leaksMsg = leaks ? "The mesh has naked edges (NOT okay, invalid)." : "The mesh is watertight (okay, valid)."; std::string cntMsg = ssprintf("\n\nThe model contains %d triangles, from " "%d surfaces.", g->displayMesh.l.n, g->runningShell.surface.n); if(SS.nakedEdges.l.n == 0) { Message("%s\n\n%s\n\nZero problematic edges, good.%s", intersMsg, leaksMsg, cntMsg.c_str()); } else { Error("%s\n\n%s\n\n%d problematic edges, bad.%s", intersMsg, leaksMsg, SS.nakedEdges.l.n, cntMsg.c_str()); } break; } case GraphicsWindow::MNU_INTERFERENCE: { SS.nakedEdges.Clear(); SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); SKdNode *root = SKdNode::From(m); bool inters, leaks; root->MakeCertainEdgesInto(&(SS.nakedEdges), SKdNode::SELF_INTER_EDGES, false, &inters, &leaks); InvalidateGraphics(); if(inters) { Error("%d edges interfere with other triangles, bad.", SS.nakedEdges.l.n); } else { Message("The assembly does not interfere, good."); } break; } case GraphicsWindow::MNU_VOLUME: { SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); double vol = 0; int i; for(i = 0; i < m->l.n; i++) { STriangle tr = m->l.elem[i]; // Translate to place vertex A at (x, y, 0) Vector trans = Vector::From(tr.a.x, tr.a.y, 0); tr.a = (tr.a).Minus(trans); tr.b = (tr.b).Minus(trans); tr.c = (tr.c).Minus(trans); // Rotate to place vertex B on the y-axis. Depending on // whether the triangle is CW or CCW, C is either to the // right or to the left of the y-axis. This handles the // sign of our normal. Vector u = Vector::From(-tr.b.y, tr.b.x, 0); u = u.WithMagnitude(1); Vector v = Vector::From(tr.b.x, tr.b.y, 0); v = v.WithMagnitude(1); Vector n = Vector::From(0, 0, 1); tr.a = (tr.a).DotInToCsys(u, v, n); tr.b = (tr.b).DotInToCsys(u, v, n); tr.c = (tr.c).DotInToCsys(u, v, n); n = tr.Normal().WithMagnitude(1); // Triangles on edge don't contribute if(fabs(n.z) < LENGTH_EPS) continue; // The plane has equation p dot n = a dot n double d = (tr.a).Dot(n); // nx*x + ny*y + nz*z = d // nz*z = d - nx*x - ny*y double A = -n.x/n.z, B = -n.y/n.z, C = d/n.z; double mac = tr.c.y/tr.c.x, mbc = (tr.c.y - tr.b.y)/tr.c.x; double xc = tr.c.x, yb = tr.b.y; // I asked Maple for // int(int(A*x + B*y +C, y=mac*x..(mbc*x + yb)), x=0..xc); double integral = (1.0/3)*( A*(mbc-mac)+ (1.0/2)*B*(mbc*mbc-mac*mac) )*(xc*xc*xc)+ (1.0/2)*(A*yb+B*yb*mbc+C*(mbc-mac))*xc*xc+ C*yb*xc+ (1.0/2)*B*yb*yb*xc; vol += integral; } std::string msg = ssprintf("The volume of the solid model is:\n\n"" %.3f %s^3", vol / pow(SS.MmPerUnit(), 3), SS.UnitName()); if(SS.viewUnits == SolveSpaceUI::UNIT_MM) { msg += ssprintf("\n %.2f mL", vol/(10*10*10)); } msg += "\n\nCurved surfaces have been approximated as triangles.\n" "This introduces error, typically of around 1%."; Message("%s", msg.c_str()); break; } case GraphicsWindow::MNU_AREA: { Group *g = SK.GetGroup(SS.GW.activeGroup); if(g->polyError.how != Group::POLY_GOOD) { Error("This group does not contain a correctly-formed " "2d closed area. It is open, not coplanar, or self-" "intersecting."); break; } SEdgeList sel = {}; g->polyLoops.MakeEdgesInto(&sel); SPolygon sp = {}; sel.AssemblePolygon(&sp, NULL, true); sp.normal = sp.ComputeNormal(); sp.FixContourDirections(); double area = sp.SignedArea(); double scale = SS.MmPerUnit(); Message("The area of the region sketched in this group is:\n\n" " %.3f %s^2\n\n" "Curves have been approximated as piecewise linear.\n" "This introduces error, typically of around 1%%.", area / (scale*scale), SS.UnitName()); sel.Clear(); sp.Clear(); break; } case GraphicsWindow::MNU_SHOW_DOF: // This works like a normal solve, except that it calculates // which variables are free/bound at the same time. SS.GenerateAll(SolveSpaceUI::GENERATE_ALL, true); break; case GraphicsWindow::MNU_TRACE_PT: if(gs.points == 1 && gs.n == 1) { SS.traced.point = gs.point[0]; SS.GW.ClearSelection(); } else { Error("Bad selection for trace; select a single point."); } break; case GraphicsWindow::MNU_STOP_TRACING: { std::string exportFile; if(GetSaveFile(&exportFile, "", CsvFileFilter)) { FILE *f = ssfopen(exportFile, "wb"); if(f) { int i; SContour *sc = &(SS.traced.path); for(i = 0; i < sc->l.n; i++) { Vector p = sc->l.elem[i].p; double s = SS.exportScale; fprintf(f, "%.10f, %.10f, %.10f\r\n", p.x/s, p.y/s, p.z/s); } fclose(f); } else { Error("Couldn't write to '%s'", exportFile.c_str()); } } // Clear the trace, and stop tracing SS.traced.point = Entity::NO_ENTITY; SS.traced.path.l.Clear(); InvalidateGraphics(); break; } default: oops(); } }
void TextWindow::ScreenChangeCheckClosedContour(int link, uint32_t v) { SS.checkClosedContour = !SS.checkClosedContour; InvalidateGraphics(); }