void GraphicsWindow::AnimateOntoWorkplane(void) { if(!LockedInWorkplane()) return; Entity *w = SK.GetEntity(ActiveWorkplane()); Quaternion quatf = w->Normal()->NormalGetNum(); Vector offsetf = (SK.GetEntity(w->point[0])->PointGetNum()).ScaledBy(-1); AnimateOnto(quatf, offsetf); }
Vector GraphicsWindow::SnapToGrid(Vector p) { if(!LockedInWorkplane()) return p; EntityBase *wrkpl = SK.GetEntity(ActiveWorkplane()), *norm = wrkpl->Normal(); Vector wo = SK.GetEntity(wrkpl->point[0])->PointGetNum(), wu = norm->NormalU(), wv = norm->NormalV(), wn = norm->NormalN(); Vector pp = (p.Minus(wo)).DotInToCsys(wu, wv, wn); pp.x = floor((pp.x / SS.gridSpacing) + 0.5)*SS.gridSpacing; pp.y = floor((pp.y / SS.gridSpacing) + 0.5)*SS.gridSpacing; pp.z = 0; return pp.ScaleOutOfCsys(wu, wv, wn).Plus(wo); }
GraphicsWindow::SuggestedConstraint GraphicsWindow::SuggestLineConstraint(hRequest request) { if(LockedInWorkplane()) { Entity *ptA = SK.GetEntity(request.entity(1)), *ptB = SK.GetEntity(request.entity(2)); Expr *au, *av, *bu, *bv; ptA->PointGetExprsInWorkplane(ActiveWorkplane(), &au, &av); ptB->PointGetExprsInWorkplane(ActiveWorkplane(), &bu, &bv); double du = au->Minus(bu)->Eval(); double dv = av->Minus(bv)->Eval(); const double TOLERANCE_RATIO = 0.02; if(fabs(dv) > LENGTH_EPS && fabs(du / dv) < TOLERANCE_RATIO) return SUGGESTED_VERTICAL; else if(fabs(du) > LENGTH_EPS && fabs(dv / du) < TOLERANCE_RATIO) return SUGGESTED_HORIZONTAL; else return SUGGESTED_NONE; } else { return SUGGESTED_NONE; } }
void GraphicsWindow::SplitLinesOrCurves(void) { if(!LockedInWorkplane()) { Error("Must be sketching in workplane to split."); return; } GroupSelection(); if(!(gs.n == 2 &&(gs.lineSegments + gs.circlesOrArcs + gs.cubics + gs.periodicCubics) == 2)) { Error("Select two entities that intersect each other (e.g. two lines " "or two circles or a circle and a line)."); return; } hEntity ha = gs.entity[0], hb = gs.entity[1]; Entity *ea = SK.GetEntity(ha), *eb = SK.GetEntity(hb); // Compute the possibly-rational Bezier curves for each of these entities SBezierList sbla, sblb; ZERO(&sbla); ZERO(&sblb); ea->GenerateBezierCurves(&sbla); eb->GenerateBezierCurves(&sblb); // and then compute the points where they intersect, based on those curves. SPointList inters; ZERO(&inters); sbla.AllIntersectionsWith(&sblb, &inters); if(inters.l.n > 0) { Vector pi; // If there's multiple points, then take the one closest to the // mouse pointer. double dmin = VERY_POSITIVE; SPoint *sp; for(sp = inters.l.First(); sp; sp = inters.l.NextAfter(sp)) { double d = ProjectPoint(sp->p).DistanceTo(currentMousePosition); if(d < dmin) { dmin = d; pi = sp->p; } } SS.UndoRemember(); hEntity hia = SplitEntity(ha, pi), hib = SplitEntity(hb, pi); // SplitEntity adds the coincident constraints to join the split halves // of each original entity; and then we add the constraint to join // the two entities together at the split point. if(hia.v && hib.v) { Constraint::ConstrainCoincident(hia, hib); } } else { Error("Can't split; no intersection found."); } // All done, clean up and regenerate. inters.Clear(); sbla.Clear(); sblb.Clear(); ClearSelection(); SS.later.generateAll = true; }
//----------------------------------------------------------------------------- // A single point must be selected when this function is called. We find two // non-construction line segments that join at this point, and create a // tangent arc joining them. //----------------------------------------------------------------------------- void GraphicsWindow::MakeTangentArc(void) { if(!LockedInWorkplane()) { Error("Must be sketching in workplane to create tangent " "arc."); return; } // The point corresponding to the vertex to be rounded. Vector pshared = SK.GetEntity(gs.point[0])->PointGetNum(); ClearSelection(); // First, find two requests (that are not construction, and that are // in our group and workplane) that generate entities that have an // endpoint at our vertex to be rounded. int i, c = 0; Entity *ent[2]; Request *req[2]; hRequest hreq[2]; hEntity hent[2]; bool pointf[2]; for(i = 0; i < SK.request.n; i++) { Request *r = &(SK.request.elem[i]); if(r->group.v != activeGroup.v) continue; if(r->workplane.v != ActiveWorkplane().v) continue; if(r->construction) continue; if(r->type != Request::LINE_SEGMENT && r->type != Request::ARC_OF_CIRCLE) { continue; } Entity *e = SK.GetEntity(r->h.entity(0)); Vector ps = e->EndpointStart(), pf = e->EndpointFinish(); if(ps.Equals(pshared) || pf.Equals(pshared)) { if(c < 2) { // We record the entity and request and their handles, // and whether the vertex to be rounded is the start or // finish of this entity. ent[c] = e; hent[c] = e->h; req[c] = r; hreq[c] = r->h; pointf[c] = (pf.Equals(pshared)); } c++; } } if(c != 2) { Error("To create a tangent arc, select a point where two " "non-construction lines or cicles in this group and " "workplane join."); return; } Entity *wrkpl = SK.GetEntity(ActiveWorkplane()); Vector wn = wrkpl->Normal()->NormalN(); // Based on these two entities, we make the objects that we'll use to // numerically find the tangent arc. ParametricCurve pc[2]; pc[0].MakeFromEntity(ent[0]->h, pointf[0]); pc[1].MakeFromEntity(ent[1]->h, pointf[1]); // And thereafter we mustn't touch the entity or req ptrs, // because the new requests/entities we add might force a // realloc. memset(ent, 0, sizeof(ent)); memset(req, 0, sizeof(req)); Vector pinter; double r, vv; // We now do Newton iterations to find the tangent arc, and its positions // t back along the two curves, starting from shared point of the curves // at t = 0. Lots of iterations helps convergence, and this is still // ~10 ms for everything. int iters = 1000; double t[2] = { 0, 0 }, tp[2]; for(i = 0; i < iters + 20; i++) { Vector p0 = pc[0].PointAt(t[0]), p1 = pc[1].PointAt(t[1]), t0 = pc[0].TangentAt(t[0]), t1 = pc[1].TangentAt(t[1]); pinter = Vector::AtIntersectionOfLines(p0, p0.Plus(t0), p1, p1.Plus(t1), NULL, NULL, NULL); // The sign of vv determines whether shortest distance is // clockwise or anti-clockwise. Vector v = (wn.Cross(t0)).WithMagnitude(1); vv = t1.Dot(v); double dot = (t0.WithMagnitude(1)).Dot(t1.WithMagnitude(1)); double theta = acos(dot); if(SS.tangentArcManual) { r = SS.tangentArcRadius; } else { r = 200/scale; // Set the radius so that no more than one third of the // line segment disappears. r = min(r, pc[0].LengthForAuto()*tan(theta/2)); r = min(r, pc[1].LengthForAuto()*tan(theta/2));; } // We are source-stepping the radius, to improve convergence. So // ramp that for most of the iterations, and then do a few at // the end with that constant for polishing. if(i < iters) { r *= 0.1 + 0.9*i/((double)iters); } // The distance from the intersection of the lines to the endpoint // of the arc, along each line. double el = r/tan(theta/2); // Compute the endpoints of the arc, for each curve Vector pa0 = pinter.Plus(t0.WithMagnitude(el)), pa1 = pinter.Plus(t1.WithMagnitude(el)); tp[0] = t[0]; tp[1] = t[1]; // And convert those points to parameter values along the curve. t[0] += (pa0.Minus(p0)).DivPivoting(t0); t[1] += (pa1.Minus(p1)).DivPivoting(t1); } // Stupid check for convergence, and for an out of range result (as // we would get, for example, if the line is too short to fit the // rounding arc). if(fabs(tp[0] - t[0]) > 1e-3 || fabs(tp[1] - t[1]) > 1e-3 || t[0] < 0.01 || t[1] < 0.01 || t[0] > 0.99 || t[1] > 0.99 || isnan(t[0]) || isnan(t[1])) { Error("Couldn't round this corner. Try a smaller radius, or try " "creating the desired geometry by hand with tangency " "constraints."); return; } // Compute the location of the center of the arc Vector center = pc[0].PointAt(t[0]), v0inter = pinter.Minus(center); int a, b; if(vv < 0) { a = 1; b = 2; center = center.Minus(v0inter.Cross(wn).WithMagnitude(r)); } else { a = 2; b = 1; center = center.Plus(v0inter.Cross(wn).WithMagnitude(r)); } SS.UndoRemember(); hRequest harc = AddRequest(Request::ARC_OF_CIRCLE, false); Entity *earc = SK.GetEntity(harc.entity(0)); hEntity hearc = earc->h; SK.GetEntity(earc->point[0])->PointForceTo(center); SK.GetEntity(earc->point[a])->PointForceTo(pc[0].PointAt(t[0])); SK.GetEntity(earc->point[b])->PointForceTo(pc[1].PointAt(t[1])); earc = NULL; pc[0].CreateRequestTrimmedTo(t[0], !SS.tangentArcDeleteOld, hent[0], hearc, (b == 1)); pc[1].CreateRequestTrimmedTo(t[1], !SS.tangentArcDeleteOld, hent[1], hearc, (a == 1)); // Now either make the original entities construction, or delete them // entirely, according to user preference. Request *re; SK.request.ClearTags(); for(re = SK.request.First(); re; re = SK.request.NextAfter(re)) { if(re->h.v == hreq[0].v || re->h.v == hreq[1].v) { if(SS.tangentArcDeleteOld) { re->tag = 1; } else { re->construction = true; } } } if(SS.tangentArcDeleteOld) { DeleteTaggedRequests(); } SS.later.generateAll = true; }
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(); }
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::EnsureValidActives(void) { bool change = false; // The active group must exist, and not be the references. Group *g = SK.group.FindByIdNoOops(activeGroup); if((!g) || (g->h.v == Group::HGROUP_REFERENCES.v)) { int i; for(i = 0; i < SK.groupOrder.n; i++) { if(SK.groupOrder.elem[i].v != Group::HGROUP_REFERENCES.v) { break; } } if(i >= SK.groupOrder.n) { // This can happen if the user deletes all the groups in the // sketch. It's difficult to prevent that, because the last // group might have been deleted automatically, because it failed // a dependency. There needs to be something, so create a plane // drawing group and activate that. They should never be able // to delete the references, though. activeGroup = SS.CreateDefaultDrawingGroup(); // We've created the default group, but not the workplane entity; // do it now so that drawing mode isn't switched to "Free in 3d". SS.GenerateAll(SolveSpaceUI::GENERATE_ALL); } else { activeGroup = SK.groupOrder.elem[i]; } SK.GetGroup(activeGroup)->Activate(); change = true; } // The active coordinate system must also exist. if(LockedInWorkplane()) { Entity *e = SK.entity.FindByIdNoOops(ActiveWorkplane()); if(e) { hGroup hgw = e->group; if(hgw.v != activeGroup.v && SS.GroupsInOrder(activeGroup, hgw)) { // The active workplane is in a group that comes after the // active group; so any request or constraint will fail. SetWorkplaneFreeIn3d(); change = true; } } else { SetWorkplaneFreeIn3d(); change = true; } } // And update the checked state for various menus bool locked = LockedInWorkplane(); RadioMenuById(MNU_FREE_IN_3D, !locked); RadioMenuById(MNU_SEL_WORKPLANE, locked); SS.UndoEnableMenus(); switch(SS.viewUnits) { case SolveSpaceUI::UNIT_MM: case SolveSpaceUI::UNIT_INCHES: break; default: SS.viewUnits = SolveSpaceUI::UNIT_MM; break; } RadioMenuById(MNU_UNITS_MM, SS.viewUnits == SolveSpaceUI::UNIT_MM); RadioMenuById(MNU_UNITS_INCHES, SS.viewUnits == SolveSpaceUI::UNIT_INCHES); ShowTextWindow(SS.GW.showTextWindow); CheckMenuById(MNU_SHOW_TEXT_WND, SS.GW.showTextWindow); #if defined(__APPLE__) CheckMenuById(MNU_SHOW_MENU_BAR, MenuBarIsVisible()); #endif CheckMenuById(MNU_SHOW_TOOLBAR, SS.showToolbar); CheckMenuById(MNU_PERSPECTIVE_PROJ, SS.usePerspectiveProj); CheckMenuById(MNU_SHOW_GRID, SS.GW.showSnapGrid); #if defined(HAVE_GTK) || defined(__APPLE__) CheckMenuById(MNU_FULL_SCREEN, FullScreenIsActive()); #endif if(change) SS.ScheduleShowTW(); }
void GraphicsWindow::Paint(void) { int i; havePainted = true; int w, h; GetGraphicsWindowSize(&w, &h); width = w; height = h; glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glScaled(scale*2.0/w, scale*2.0/h, scale*1.0/30000); double mat[16]; // Last thing before display is to apply the perspective double clp = SS.CameraTangent()*scale; MakeMatrix(mat, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, clp, 1); glMultMatrixd(mat); // Before that, we apply the rotation Vector n = projUp.Cross(projRight); MakeMatrix(mat, projRight.x, projRight.y, projRight.z, 0, projUp.x, projUp.y, projUp.z, 0, n.x, n.y, n.z, 0, 0, 0, 0, 1); glMultMatrixd(mat); // And before that, the translation MakeMatrix(mat, 1, 0, 0, offset.x, 0, 1, 0, offset.y, 0, 0, 1, offset.z, 0, 0, 0, 1); glMultMatrixd(mat); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glShadeModel(GL_SMOOTH); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glEnable(GL_LINE_SMOOTH); // don't enable GL_POLYGON_SMOOTH; that looks ugly on some graphics cards, // drawn with leaks in the mesh glEnable(GL_POLYGON_OFFSET_LINE); glEnable(GL_POLYGON_OFFSET_FILL); glEnable(GL_DEPTH_TEST); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_NORMALIZE); // At the same depth, we want later lines drawn over earlier. glDepthFunc(GL_LEQUAL); if(SS.AllGroupsOkay()) { glClearColor(SS.backgroundColor.redF(), SS.backgroundColor.greenF(), SS.backgroundColor.blueF(), 1.0f); } else { // Draw a different background whenever we're having solve problems. RgbColor rgb = Style::Color(Style::DRAW_ERROR); glClearColor(0.4f*rgb.redF(), 0.4f*rgb.greenF(), 0.4f*rgb.blueF(), 1.0f); // And show the text window, which has info to debug it ForceTextWindowShown(); } glClear(GL_COLOR_BUFFER_BIT); glClearDepth(1.0); glClear(GL_DEPTH_BUFFER_BIT); if(SS.bgImage.fromFile) { // If a background image is loaded, then we draw it now as a texture. // This handles the resizing for us nicely. glBindTexture(GL_TEXTURE_2D, TEXTURE_BACKGROUND_IMG); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SS.bgImage.rw, SS.bgImage.rh, 0, GL_RGB, GL_UNSIGNED_BYTE, SS.bgImage.fromFile); double tw = ((double)SS.bgImage.w) / SS.bgImage.rw, th = ((double)SS.bgImage.h) / SS.bgImage.rh; double mmw = SS.bgImage.w / SS.bgImage.scale, mmh = SS.bgImage.h / SS.bgImage.scale; Vector origin = SS.bgImage.origin; origin = origin.DotInToCsys(projRight, projUp, n); // Place the depth of our origin at the point that corresponds to // w = 1, so that it's unaffected by perspective. origin.z = (offset.ScaledBy(-1)).Dot(n); origin = origin.ScaleOutOfCsys(projRight, projUp, n); // Place the background at the very back of the Z order, though, by // mucking with the depth range. glDepthRange(1, 1); glEnable(GL_TEXTURE_2D); glBegin(GL_QUADS); glTexCoord2d(0, 0); ssglVertex3v(origin); glTexCoord2d(0, th); ssglVertex3v(origin.Plus(projUp.ScaledBy(mmh))); glTexCoord2d(tw, th); ssglVertex3v(origin.Plus(projRight.ScaledBy(mmw).Plus( projUp. ScaledBy(mmh)))); glTexCoord2d(tw, 0); ssglVertex3v(origin.Plus(projRight.ScaledBy(mmw))); glEnd(); glDisable(GL_TEXTURE_2D); } ssglDepthRangeOffset(0); // Nasty case when we're reloading the imported files; could be that // we get an error, so a dialog pops up, and a message loop starts, and // we have to get called to paint ourselves. If the sketch is screwed // up, then we could trigger an oops trying to draw. if(!SS.allConsistent) return; // Let's use two lights, at the user-specified locations GLfloat f; glEnable(GL_LIGHT0); f = (GLfloat)SS.lightIntensity[0]; GLfloat li0[] = { f, f, f, 1.0f }; glLightfv(GL_LIGHT0, GL_DIFFUSE, li0); glLightfv(GL_LIGHT0, GL_SPECULAR, li0); glEnable(GL_LIGHT1); f = (GLfloat)SS.lightIntensity[1]; GLfloat li1[] = { f, f, f, 1.0f }; glLightfv(GL_LIGHT1, GL_DIFFUSE, li1); glLightfv(GL_LIGHT1, GL_SPECULAR, li1); Vector ld; ld = VectorFromProjs(SS.lightDir[0]); GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 }; glLightfv(GL_LIGHT0, GL_POSITION, ld0); ld = VectorFromProjs(SS.lightDir[1]); GLfloat ld1[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 }; glLightfv(GL_LIGHT1, GL_POSITION, ld1); if(SS.drawBackFaces) { // For debugging, draw the backs of the triangles in red, so that we // notice when a shell is open glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 1); } else { glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 0); } GLfloat ambient[4] = { (float)SS.ambientIntensity, (float)SS.ambientIntensity, (float)SS.ambientIntensity, 1 }; glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); ssglUnlockColor(); if(showSnapGrid && LockedInWorkplane()) { hEntity he = ActiveWorkplane(); EntityBase *wrkpl = SK.GetEntity(he), *norm = wrkpl->Normal(); Vector wu, wv, wn, wp; wp = SK.GetEntity(wrkpl->point[0])->PointGetNum(); wu = norm->NormalU(); wv = norm->NormalV(); wn = norm->NormalN(); double g = SS.gridSpacing; double umin = VERY_POSITIVE, umax = VERY_NEGATIVE, vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE; int a; for(a = 0; a < 4; a++) { // Ideally, we would just do +/- half the width and height; but // allow some extra slop for rounding. Vector horiz = projRight.ScaledBy((0.6*width)/scale + 2*g), vert = projUp. ScaledBy((0.6*height)/scale + 2*g); if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1); if(a == 1 || a == 3) vert = vert. ScaledBy(-1); Vector tp = horiz.Plus(vert).Minus(offset); // Project the point into our grid plane, normal to the screen // (not to the grid plane). If the plane is on edge then this is // impossible so don't try to draw the grid. bool parallel; Vector tpp = Vector::AtIntersectionOfPlaneAndLine( wn, wn.Dot(wp), tp, tp.Plus(n), ¶llel); if(parallel) goto nogrid; tpp = tpp.Minus(wp); double uu = tpp.Dot(wu), vv = tpp.Dot(wv); umin = min(uu, umin); umax = max(uu, umax); vmin = min(vv, vmin); vmax = max(vv, vmax); } int i, j, i0, i1, j0, j1; i0 = (int)(umin / g); i1 = (int)(umax / g); j0 = (int)(vmin / g); j1 = (int)(vmax / g); if(i0 > i1 || i1 - i0 > 400) goto nogrid; if(j0 > j1 || j1 - j0 > 400) goto nogrid; glLineWidth(1); ssglColorRGBa(Style::Color(Style::DATUM), 0.3); glBegin(GL_LINES); for(i = i0 + 1; i < i1; i++) { ssglVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g))); ssglVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g))); } for(j = j0 + 1; j < j1; j++) { ssglVertex3v(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g))); ssglVertex3v(wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g))); } glEnd(); // Clear the depth buffer, so that the grid is at the very back of // the Z order. glClear(GL_DEPTH_BUFFER_BIT); nogrid:; } // Draw the active group; this does stuff like the mesh and edges. (SK.GetGroup(activeGroup))->Draw(); // Now draw the entities if(showHdnLines) glDisable(GL_DEPTH_TEST); Entity::DrawAll(); // Draw filled paths in all groups, when those filled paths were requested // specially by assigning a style with a fill color, or when the filled // paths are just being filled by default. This should go last, to make // the transparency work. Group *g; for(g = SK.group.First(); g; g = SK.group.NextAfter(g)) { if(!(g->IsVisible())) continue; g->DrawFilledPaths(); } glDisable(GL_DEPTH_TEST); // Draw the constraints for(i = 0; i < SK.constraint.n; i++) { SK.constraint.elem[i].Draw(); } // Draw the traced path, if one exists glLineWidth(Style::Width(Style::ANALYZE)); ssglColorRGB(Style::Color(Style::ANALYZE)); SContour *sc = &(SS.traced.path); glBegin(GL_LINE_STRIP); for(i = 0; i < sc->l.n; i++) { ssglVertex3v(sc->l.elem[i].p); } glEnd(); // And the naked edges, if the user did Analyze -> Show Naked Edges. glLineWidth(Style::Width(Style::DRAW_ERROR)); ssglColorRGB(Style::Color(Style::DRAW_ERROR)); ssglDrawEdges(&(SS.nakedEdges), true); // Then redraw whatever the mouse is hovering over, highlighted. glDisable(GL_DEPTH_TEST); ssglLockColorTo(Style::Color(Style::HOVERED)); hover.Draw(); // And finally draw the selection, same mechanism. ssglLockColorTo(Style::Color(Style::SELECTED)); for(Selection *s = selection.First(); s; s = selection.NextAfter(s)) { s->Draw(); } ssglUnlockColor(); // If a marquee selection is in progress, then draw the selection // rectangle, as an outline and a transparent fill. if(pending.operation == DRAGGING_MARQUEE) { Point2d begin = ProjectPoint(orig.marqueePoint); double xmin = min(orig.mouse.x, begin.x), xmax = max(orig.mouse.x, begin.x), ymin = min(orig.mouse.y, begin.y), ymax = max(orig.mouse.y, begin.y); Vector tl = UnProjectPoint(Point2d::From(xmin, ymin)), tr = UnProjectPoint(Point2d::From(xmax, ymin)), br = UnProjectPoint(Point2d::From(xmax, ymax)), bl = UnProjectPoint(Point2d::From(xmin, ymax)); glLineWidth((GLfloat)1.3); ssglColorRGB(Style::Color(Style::HOVERED)); glBegin(GL_LINE_LOOP); ssglVertex3v(tl); ssglVertex3v(tr); ssglVertex3v(br); ssglVertex3v(bl); glEnd(); ssglColorRGBa(Style::Color(Style::HOVERED), 0.10); glBegin(GL_QUADS); ssglVertex3v(tl); ssglVertex3v(tr); ssglVertex3v(br); ssglVertex3v(bl); glEnd(); } // An extra line, used to indicate the origin when rotating within the // plane of the monitor. if(SS.extraLine.draw) { glLineWidth(1); ssglLockColorTo(Style::Color(Style::DATUM)); glBegin(GL_LINES); ssglVertex3v(SS.extraLine.ptA); ssglVertex3v(SS.extraLine.ptB); glEnd(); } // A note to indicate the origin in the just-exported file. if(SS.justExportedInfo.draw) { ssglColorRGB(Style::Color(Style::DATUM)); Vector p = SS.justExportedInfo.pt, u = SS.justExportedInfo.u, v = SS.justExportedInfo.v; glLineWidth(1.5); glBegin(GL_LINES); ssglVertex3v(p.Plus(u.WithMagnitude(-15/scale))); ssglVertex3v(p.Plus(u.WithMagnitude(30/scale))); ssglVertex3v(p.Plus(v.WithMagnitude(-15/scale))); ssglVertex3v(p.Plus(v.WithMagnitude(30/scale))); glEnd(); ssglWriteText("(x, y) = (0, 0) for file just exported", DEFAULT_TEXT_HEIGHT, p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)), u, v, NULL, NULL); ssglWriteText("press Esc to clear this message", DEFAULT_TEXT_HEIGHT, p.Plus(u.ScaledBy(40/scale)).Plus( v.ScaledBy(-(DEFAULT_TEXT_HEIGHT)/scale)), u, v, NULL, NULL); } // And finally the toolbar. if(SS.showToolbar) { ToolbarDraw(); } }