Add support for a clipboard, with cut, copy, and paste. This works
only in a workplane; but this means that plane sketches are conveniently transformed from one plane to another. Also tweak snap to grid to ignore unsnappable entities instead of triggering an error, and to remove arbitrary limit on the number of entities / comments that will be snapped. [git-p4: depot-paths = "//depot/solvespace/": change = 2084]solver
parent
c74ab47833
commit
9723f4e44f
1
Makefile
1
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 \
|
||||
|
|
|
@ -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<Selection> *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<Selection> *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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Selection> *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<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);
|
||||
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;
|
||||
|
|
|
@ -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:
|
||||
|
|
12
request.cpp
12
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) {
|
||||
|
|
15
sketch.h
15
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
|
||||
|
|
11
solvespace.h
11
solvespace.h
|
@ -684,6 +684,17 @@ public:
|
|||
Vector origin;
|
||||
} bgImage;
|
||||
|
||||
class Clipboard {
|
||||
public:
|
||||
List<ClipboardRequest> r;
|
||||
List<Constraint> c;
|
||||
|
||||
void Clear(void);
|
||||
bool ContainsEntity(hEntity old);
|
||||
hEntity NewEntityFor(hEntity old);
|
||||
};
|
||||
Clipboard clipboard;
|
||||
|
||||
void MarkGroupDirty(hGroup hg);
|
||||
void MarkGroupDirtyByEntity(hEntity he);
|
||||
|
||||
|
|
4
ui.h
4
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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue