diff --git a/Makefile b/Makefile index 9502b34..3412c30 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ SSOBJS = $(OBJDIR)\solvespace.obj \ $(OBJDIR)\confscreen.obj \ $(OBJDIR)\graphicswin.obj \ $(OBJDIR)\modify.obj \ + $(OBJDIR)\clipboard.obj \ $(OBJDIR)\util.obj \ $(OBJDIR)\style.obj \ $(OBJDIR)\entity.obj \ diff --git a/clipboard.cpp b/clipboard.cpp new file mode 100644 index 0000000..d1fc132 --- /dev/null +++ b/clipboard.cpp @@ -0,0 +1,212 @@ +#include "solvespace.h" + +void SolveSpace::Clipboard::Clear(void) { + c.Clear(); + r.Clear(); +} + +hEntity SolveSpace::Clipboard::NewEntityFor(hEntity he) { + ClipboardRequest *cr; + for(cr = r.First(); cr; cr = r.NextAfter(cr)) { + if(cr->oldEnt.v == he.v) { + return cr->newReq.entity(0); + } + for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) { + if(cr->oldPointEnt[i].v == he.v) { + return cr->newReq.entity(1+i); + } + } + } + return Entity::NO_ENTITY; +} + +bool SolveSpace::Clipboard::ContainsEntity(hEntity he) { + hEntity hen = NewEntityFor(he); + if(hen.v) { + return true; + } else { + return false; + } +} + +void GraphicsWindow::DeleteSelection(void) { + SK.request.ClearTags(); + SK.constraint.ClearTags(); + List *ls = &(selection); + for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { + hRequest r = { 0 }; + if(s->entity.v && s->entity.isFromRequest()) { + r = s->entity.request(); + } + if(r.v && !r.IsFromReferences()) { + SK.request.Tag(r, 1); + } + if(s->constraint.v) { + SK.constraint.Tag(s->constraint, 1); + } + } + + SK.constraint.RemoveTagged(); + // Note that this regenerates and clears the selection, to avoid + // lingering references to the just-deleted items. + DeleteTaggedRequests(); +} + +void GraphicsWindow::CopySelection(void) { + SS.clipboard.Clear(); + + Entity *wrkpl = SK.GetEntity(ActiveWorkplane()); + Entity *wrkpln = SK.GetEntity(wrkpl->normal); + Vector u = wrkpln->NormalU(), + v = wrkpln->NormalV(), + n = wrkpln->NormalN(), + p = SK.GetEntity(wrkpl->point[0])->PointGetNum(); + + List *ls = &(selection); + for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { + if(!s->entity.v) continue; + // Work only on entities that have requests that will generate them. + Entity *e = SK.GetEntity(s->entity); + bool hasDistance; + int req, pts; + if(!EntReqTable::GetEntityInfo(e->type, e->extraPoints, + &req, &pts, NULL, &hasDistance)) + { + continue; + } + if(req == Request::WORKPLANE) continue; + + ClipboardRequest cr; + ZERO(&cr); + cr.type = req; + cr.extraPoints = e->extraPoints; + cr.style = e->style; + cr.str.strcpy( e->str.str); + cr.font.strcpy( e->font.str); + cr.construction = e->construction; + for(int i = 0; i < pts; i++) { + Vector pt = SK.GetEntity(e->point[i])->PointGetNum(); + pt = pt.Minus(p); + pt = pt.DotInToCsys(u, v, n); + cr.point[i] = pt; + } + if(hasDistance) { + cr.distance = SK.GetEntity(e->distance)->DistanceGetNum(); + } + + cr.oldEnt = e->h; + for(int i = 0; i < pts; i++) { + cr.oldPointEnt[i] = e->point[i]; + } + + SS.clipboard.r.Add(&cr); + } + + Constraint *c; + for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { + if(c->type == Constraint::POINTS_COINCIDENT) { + if(!SS.clipboard.ContainsEntity(c->ptA)) continue; + if(!SS.clipboard.ContainsEntity(c->ptB)) continue; + } else { + continue; + } + SS.clipboard.c.Add(c); + } +} + +void GraphicsWindow::PasteClipboard(Vector trans, double theta, bool mirror) { + SS.UndoRemember(); + ClearSelection(); + + Entity *wrkpl = SK.GetEntity(ActiveWorkplane()); + Entity *wrkpln = SK.GetEntity(wrkpl->normal); + Vector u = wrkpln->NormalU(), + v = wrkpln->NormalV(), + p = SK.GetEntity(wrkpl->point[0])->PointGetNum(); + + + ClipboardRequest *cr; + for(cr = SS.clipboard.r.First(); cr; cr = SS.clipboard.r.NextAfter(cr)) { + hRequest hr = AddRequest(cr->type, false); + Request *r = SK.GetRequest(hr); + r->extraPoints = cr->extraPoints; + r->style = cr->style; + r->str.strcpy( cr->str.str); + r->font.strcpy( cr->font.str); + r->construction = cr->construction; + // Need to regen to get the right number of points, if extraPoints + // changed. + SS.GenerateAll(-1, -1); + SS.MarkGroupDirty(r->group); + bool hasDistance; + int pts; + EntReqTable::GetRequestInfo(r->type, r->extraPoints, + NULL, &pts, NULL, &hasDistance); + for(int i = 0; i < pts; i++) { + Vector pt = cr->point[i]; + if(mirror) pt.x *= -1; + pt = Vector::From( cos(theta)*pt.x + sin(theta)*pt.y, + -sin(theta)*pt.x + cos(theta)*pt.y, + 0); + pt = pt.ScaleOutOfCsys(u, v, Vector::From(0, 0, 0)); + pt = pt.Plus(p); + pt = pt.Plus(trans); + SK.GetEntity(hr.entity(i+1))->PointForceTo(pt); + } + if(hasDistance) { + SK.GetEntity(hr.entity(64))->DistanceForceTo(cr->distance); + } + + cr->newReq = hr; + ToggleSelectionStateOf(hr.entity(0)); + } + + Constraint *c; + for(c = SS.clipboard.c.First(); c; c = SS.clipboard.c.NextAfter(c)) { + if(c->type == Constraint::POINTS_COINCIDENT) { + Constraint::ConstrainCoincident(SS.clipboard.NewEntityFor(c->ptA), + SS.clipboard.NewEntityFor(c->ptB)); + } + } + + SS.later.generateAll = true; +} + +void GraphicsWindow::MenuClipboard(int id) { + if(id != MNU_DELETE && !SS.GW.LockedInWorkplane()) { + Error("Cut, paste, and copy work only in a workplane.\r\n\r\n" + "Select one with Sketch -> In Workplane."); + return; + } + + switch(id) { + case MNU_PASTE: { + Vector trans = SS.GW.projRight.ScaledBy(80/SS.GW.scale).Plus( + SS.GW.projUp .ScaledBy(40/SS.GW.scale)); + SS.GW.PasteClipboard(trans, 0, false); + break; + } + + case MNU_PASTE_TRANSFORM: + break; + + case MNU_COPY: + SS.GW.CopySelection(); + SS.GW.ClearSelection(); + break; + + case MNU_CUT: + SS.UndoRemember(); + SS.GW.CopySelection(); + SS.GW.DeleteSelection(); + break; + + case MNU_DELETE: + SS.UndoRemember(); + SS.GW.DeleteSelection(); + break; + + default: oops(); + } +} + diff --git a/graphicswin.cpp b/graphicswin.cpp index 49726a0..0eeeec2 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -2,6 +2,7 @@ #define mView (&GraphicsWindow::MenuView) #define mEdit (&GraphicsWindow::MenuEdit) +#define mClip (&GraphicsWindow::MenuClipboard) #define mReq (&GraphicsWindow::MenuRequest) #define mCon (&Constraint::MenuConstrain) #define mFile (&SolveSpace::MenuFile) @@ -36,11 +37,11 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { { 1, "Snap Selection to &Grid\t.", MNU_SNAP_TO_GRID, '.', mEdit }, { 1, "Rotate Imported &90°\t9", MNU_ROTATE_90, '9', mEdit }, { 1, NULL, 0, NULL }, -{ 1, "Cu&t\tCtrl+X", MNU_CUT, 'X'|C, mEdit }, -{ 1, "&Copy\tCtrl+C", MNU_COPY, 'C'|C, mEdit }, -{ 1, "&Paste\tCtrl+V", MNU_PASTE, 'V'|C, mEdit }, -{ 1, "Paste &Transformed...\tCtrl+T", MNU_PASTE_TRANSFORM,'T'|C, mEdit }, -{ 1, "&Delete\tDel", MNU_DELETE, 127, mEdit }, +{ 1, "Cu&t\tCtrl+X", MNU_CUT, 'X'|C, mClip }, +{ 1, "&Copy\tCtrl+C", MNU_COPY, 'C'|C, mClip }, +{ 1, "&Paste\tCtrl+V", MNU_PASTE, 'V'|C, mClip }, +{ 1, "Paste &Transformed...\tCtrl+T", MNU_PASTE_TRANSFORM,'T'|C, mClip }, +{ 1, "&Delete\tDel", MNU_DELETE, 127, mClip }, { 1, NULL, 0, NULL }, { 1, "Select Edge Cha&in\tCtrl+I", MNU_SELECT_CHAIN, 'I'|C, mEdit }, { 1, "Invert &Selection\tCtrl+A", MNU_INVERT_SEL, 'A'|C, mEdit }, @@ -358,9 +359,9 @@ void GraphicsWindow::MenuView(int id) { if(SS.cameraTangent < 1e-6) { Error("The perspective factor is set to zero, so the view will " "always be a parallel projection.\r\n\r\n" - "For a perspective projection, modify the camera tangent " - "in the configuration screen. A value around 0.3 is " - "typical."); + "For a perspective projection, modify the perspective " + "factor in the configuration screen. A value around 0.3 " + "is typical."); } SS.GW.EnsureValidActives(); InvalidateGraphics(); @@ -689,30 +690,6 @@ void GraphicsWindow::MenuEdit(int id) { break; } - case MNU_DELETE: { - SS.UndoRemember(); - - SK.request.ClearTags(); - SK.constraint.ClearTags(); - List *ls = &(SS.GW.selection); - for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { - hRequest r; r.v = 0; - if(s->entity.v && s->entity.isFromRequest()) { - r = s->entity.request(); - } - if(r.v && !r.IsFromReferences()) { - SK.request.Tag(r, 1); - } - if(s->constraint.v) { - SK.constraint.Tag(s->constraint, 1); - } - } - - SK.constraint.RemoveTagged(); - SS.GW.DeleteTaggedRequests(); - break; - } - case MNU_ROTATE_90: { SS.GW.GroupSelection(); Entity *e = NULL; @@ -756,34 +733,36 @@ void GraphicsWindow::MenuEdit(int id) { break; } SS.GW.GroupSelection(); - if(SS.GW.gs.n != SS.GW.gs.points || - SS.GW.gs.constraints != SS.GW.gs.comments || - (SS.GW.gs.n == 0 && SS.GW.gs.constraints == 0)) - { - Error("Can't snap these items to grid; select only points or " + if(SS.GW.gs.points == 0 && SS.GW.gs.comments == 0) { + Error("Can't snap these items to grid; select points or " "text comments. To snap a line, select its endpoints."); break; } SS.UndoRemember(); - int i; - for(i = 0; i < SS.GW.gs.points; i++) { - hEntity hp = SS.GW.gs.point[i]; - Entity *ep = SK.GetEntity(hp); - Vector p = ep->PointGetNum(); - ep->PointForceTo(SS.GW.SnapToGrid(p)); - SS.GW.pending.points.Add(&hp); - SS.MarkGroupDirty(ep->group); + List *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); + if(c->type != Constraint::COMMENT) continue; + + 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(); - for(i = 0; i < SS.GW.gs.constraints; i++) { - Constraint *c = SK.GetConstraint(SS.GW.gs.constraint[i]); - c->disp.offset = SS.GW.SnapToGrid(c->disp.offset); - } SS.GW.ClearSelection(); InvalidateGraphics(); break; diff --git a/mouse.cpp b/mouse.cpp index e5f71b3..4ebc938 100644 --- a/mouse.cpp +++ b/mouse.cpp @@ -592,7 +592,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { break; case CMNU_DELETE_SEL: - MenuEdit(MNU_DELETE); + MenuClipboard(MNU_DELETE); break; case CMNU_REFERENCE_DIM: diff --git a/request.cpp b/request.cpp index 09b3a09..f18a4f9 100644 --- a/request.cpp +++ b/request.cpp @@ -39,7 +39,7 @@ void EntReqTable::CopyEntityInfo(const TableEntry *te, int extraPoints, if(hasDistance) *hasDistance = te->hasDistance; } -void EntReqTable::GetRequestInfo(int req, int extraPoints, +bool EntReqTable::GetRequestInfo(int req, int extraPoints, int *ent, int *pts, bool *hasNormal, bool *hasDistance) { for(int i = 0; Table[i].reqType; i++) { @@ -47,13 +47,13 @@ void EntReqTable::GetRequestInfo(int req, int extraPoints, if(req == te->reqType) { CopyEntityInfo(te, extraPoints, ent, NULL, pts, hasNormal, hasDistance); - return; + return true; } } - oops(); + return false; } -void EntReqTable::GetEntityInfo(int ent, int extraPoints, +bool EntReqTable::GetEntityInfo(int ent, int extraPoints, int *req, int *pts, bool *hasNormal, bool *hasDistance) { for(int i = 0; Table[i].reqType; i++) { @@ -61,10 +61,10 @@ void EntReqTable::GetEntityInfo(int ent, int extraPoints, if(ent == te->entType) { CopyEntityInfo(te, extraPoints, NULL, req, pts, hasNormal, hasDistance); - return; + return true; } } - oops(); + return false; } int EntReqTable::GetRequestForEntity(int ent) { diff --git a/sketch.h b/sketch.h index f12b241..a48e4e0 100644 --- a/sketch.h +++ b/sketch.h @@ -476,9 +476,9 @@ public: static char *DescriptionForRequest(int req); static void CopyEntityInfo(const TableEntry *te, int extraPoints, int *ent, int *req, int *pts, bool *hasNormal, bool *hasDistance); - static void GetRequestInfo(int req, int extraPoints, + static bool GetRequestInfo(int req, int extraPoints, int *ent, int *pts, bool *hasNormal, bool *hasDistance); - static void GetEntityInfo(int ent, int extraPoints, + static bool GetEntityInfo(int ent, int extraPoints, int *req, int *pts, bool *hasNormal, bool *hasDistance); static int GetRequestForEntity(int ent); }; @@ -767,16 +767,21 @@ inline hConstraint hEquation::constraint(void) { hConstraint r; r.v = (v >> 16); return r; } // The format for entities stored on the clipboard. -class ClipboardEntity { +class ClipboardRequest { public: int type; - Vector point[MAX_POINTS_IN_ENTITY]; int extraPoints; - hStyle style; NameStr str; NameStr font; bool construction; + + Vector point[MAX_POINTS_IN_ENTITY]; + double distance; + + hEntity oldEnt; + hEntity oldPointEnt[MAX_POINTS_IN_ENTITY]; + hRequest newReq; }; #endif diff --git a/solvespace.h b/solvespace.h index 6046c67..5f79d8d 100644 --- a/solvespace.h +++ b/solvespace.h @@ -684,6 +684,17 @@ public: Vector origin; } bgImage; + class Clipboard { +public: + List r; + List c; + + void Clear(void); + bool ContainsEntity(hEntity old); + hEntity NewEntityFor(hEntity old); + }; + Clipboard clipboard; + void MarkGroupDirty(hGroup hg); void MarkGroupDirtyByEntity(hEntity he); diff --git a/ui.h b/ui.h index 8b6e862..baee4e9 100644 --- a/ui.h +++ b/ui.h @@ -315,6 +315,10 @@ public: static void MenuView(int id); static void MenuEdit(int id); static void MenuRequest(int id); + void DeleteSelection(void); + void CopySelection(void); + void PasteClipboard(Vector trans, double theta, bool mirror); + static void MenuClipboard(int id); // The width and height (in pixels) of the window. double width, height; diff --git a/wishlist.txt b/wishlist.txt index a815d77..91ce476 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,6 +1,8 @@ -copy and paste +paste transformed de-select after left-clicking nothing, keep sel on drag? +bbox selection is select-only, not toggle? +show and modify view parameters (translate, rotate, scale) ----- associative entities from solid model, as a special group