Solvespace
519 строк · 18.2 Кб
1//-----------------------------------------------------------------------------
2// The clipboard that gets manipulated when the user selects Edit -> Cut,
3// Copy, Paste, etc.; may contain entities only, not constraints.
4//
5// Copyright 2008-2013 Jonathan Westhues.
6//-----------------------------------------------------------------------------
7#include "solvespace.h"
8
9void SolveSpaceUI::Clipboard::Clear() {
10c.Clear();
11r.Clear();
12}
13
14bool SolveSpaceUI::Clipboard::ContainsEntity(hEntity he) {
15if(he == Entity::NO_ENTITY)
16return true;
17
18ClipboardRequest *cr;
19for(cr = r.First(); cr; cr = r.NextAfter(cr)) {
20if(cr->oldEnt == he)
21return true;
22
23for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) {
24if(cr->oldPointEnt[i] == he)
25return true;
26}
27}
28return false;
29}
30
31hEntity SolveSpaceUI::Clipboard::NewEntityFor(hEntity he) {
32if(he == Entity::NO_ENTITY)
33return Entity::NO_ENTITY;
34
35ClipboardRequest *cr;
36for(cr = r.First(); cr; cr = r.NextAfter(cr)) {
37if(cr->oldEnt == he)
38return cr->newReq.entity(0);
39
40for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) {
41if(cr->oldPointEnt[i] == he)
42return cr->newReq.entity(1+i);
43}
44}
45
46ssassert(false, "Expected to find entity in some clipboard request");
47}
48
49void GraphicsWindow::DeleteSelection() {
50SK.request.ClearTags();
51SK.constraint.ClearTags();
52List<Selection> *ls = &(selection);
53for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {
54hRequest r = { 0 };
55if(s->entity.v && s->entity.isFromRequest()) {
56r = s->entity.request();
57}
58if(r.v && !r.IsFromReferences()) {
59SK.request.Tag(r, 1);
60}
61if(s->constraint.v) {
62SK.constraint.Tag(s->constraint, 1);
63}
64}
65
66SK.constraint.RemoveTagged();
67// Note that this regenerates and clears the selection, to avoid
68// lingering references to the just-deleted items.
69DeleteTaggedRequests();
70}
71
72void GraphicsWindow::CopySelection() {
73SS.clipboard.Clear();
74
75Entity *wrkpl = SK.GetEntity(ActiveWorkplane());
76Entity *wrkpln = SK.GetEntity(wrkpl->normal);
77Vector u = wrkpln->NormalU(),
78v = wrkpln->NormalV(),
79n = wrkpln->NormalN(),
80p = SK.GetEntity(wrkpl->point[0])->PointGetNum();
81
82List<Selection> *ls = &(selection);
83for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {
84if(!s->entity.v) continue;
85// Work only on entities that have requests that will generate them.
86Entity *e = SK.GetEntity(s->entity);
87bool hasDistance;
88Request::Type req;
89int pts;
90if(!EntReqTable::GetEntityInfo(e->type, e->extraPoints,
91&req, &pts, NULL, &hasDistance))
92{
93if(!e->h.isFromRequest()) continue;
94Request *r = SK.GetRequest(e->h.request());
95if(r->type != Request::Type::DATUM_POINT) continue;
96EntReqTable::GetEntityInfo((Entity::Type)0, e->extraPoints,
97&req, &pts, NULL, &hasDistance);
98}
99if(req == Request::Type::WORKPLANE) continue;
100
101ClipboardRequest cr = {};
102cr.type = req;
103cr.extraPoints = e->extraPoints;
104cr.style = e->style;
105cr.str = e->str;
106cr.font = e->font;
107cr.file = e->file;
108cr.construction = e->construction;
109{for(int i = 0; i < pts; i++) {
110Vector pt;
111if(req == Request::Type::DATUM_POINT) {
112pt = e->PointGetNum();
113} else {
114pt = SK.GetEntity(e->point[i])->PointGetNum();
115}
116pt = pt.Minus(p);
117pt = pt.DotInToCsys(u, v, n);
118cr.point[i] = pt;
119}}
120if(hasDistance) {
121cr.distance = SK.GetEntity(e->distance)->DistanceGetNum();
122}
123
124cr.oldEnt = e->h;
125for(int i = 0; i < pts; i++) {
126cr.oldPointEnt[i] = e->point[i];
127}
128
129SS.clipboard.r.Add(&cr);
130}
131
132for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) {
133if(!s->constraint.v) continue;
134
135Constraint *c = SK.GetConstraint(s->constraint);
136if(c->type == Constraint::Type::COMMENT) {
137SS.clipboard.c.Add(c);
138}
139}
140
141for(Constraint &c : SK.constraint) {
142if(!SS.clipboard.ContainsEntity(c.ptA) ||
143!SS.clipboard.ContainsEntity(c.ptB) ||
144!SS.clipboard.ContainsEntity(c.entityA) ||
145!SS.clipboard.ContainsEntity(c.entityB) ||
146!SS.clipboard.ContainsEntity(c.entityC) ||
147!SS.clipboard.ContainsEntity(c.entityD) ||
148c.type == Constraint::Type::COMMENT) {
149continue;
150}
151SS.clipboard.c.Add(&c);
152}
153}
154
155void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
156Entity *wrkpl = SK.GetEntity(ActiveWorkplane());
157Entity *wrkpln = SK.GetEntity(wrkpl->normal);
158Vector u = wrkpln->NormalU(),
159v = wrkpln->NormalV(),
160n = wrkpln->NormalN(),
161p = SK.GetEntity(wrkpl->point[0])->PointGetNum();
162
163// For arcs, reflection involves swapping the endpoints, or otherwise
164// the arc gets inverted.
165auto mapPoint = [scale](hEntity he) {
166if(he.v == 0) return he;
167
168if(scale < 0) {
169hRequest hr = he.request();
170Request *r = SK.GetRequest(hr);
171if(r->type == Request::Type::ARC_OF_CIRCLE) {
172if(he == hr.entity(2)) {
173return hr.entity(3);
174} else if(he == hr.entity(3)) {
175return hr.entity(2);
176}
177}
178}
179return he;
180};
181
182ClipboardRequest *cr;
183for(cr = SS.clipboard.r.First(); cr; cr = SS.clipboard.r.NextAfter(cr)) {
184hRequest hr = AddRequest(cr->type, /*rememberForUndo=*/false);
185Request *r = SK.GetRequest(hr);
186r->extraPoints = cr->extraPoints;
187r->style = cr->style;
188r->str = cr->str;
189r->font = cr->font;
190r->file = cr->file;
191r->construction = cr->construction;
192// Need to regen to get the right number of points, if extraPoints
193// changed.
194SS.GenerateAll(SolveSpaceUI::Generate::REGEN);
195SS.MarkGroupDirty(r->group);
196bool hasDistance;
197int i, pts;
198EntReqTable::GetRequestInfo(r->type, r->extraPoints,
199NULL, &pts, NULL, &hasDistance);
200for(i = 0; i < pts; i++) {
201Vector pt = cr->point[i];
202// We need the reflection to occur within the workplane; it may
203// otherwise correspond to just a rotation as projected.
204if(scale < 0) {
205pt.x *= -1;
206}
207// Likewise the scale, which could otherwise take us out of the
208// workplane.
209pt = pt.ScaledBy(fabs(scale));
210pt = pt.ScaleOutOfCsys(u, v, Vector::From(0, 0, 0));
211pt = pt.Plus(p);
212pt = pt.RotatedAbout(n, theta);
213pt = pt.Plus(trans);
214int j = (r->type == Request::Type::DATUM_POINT) ? i : i + 1;
215SK.GetEntity(mapPoint(hr.entity(j)))->PointForceTo(pt);
216}
217if(hasDistance) {
218SK.GetEntity(hr.entity(64))->DistanceForceTo(
219cr->distance*fabs(scale));
220}
221
222cr->newReq = hr;
223MakeSelected(hr.entity(0));
224for(i = 0; i < pts; i++) {
225int j = (r->type == Request::Type::DATUM_POINT) ? i : i + 1;
226MakeSelected(hr.entity(j));
227}
228}
229Constraint *cc;
230for(cc = SS.clipboard.c.First(); cc; cc = SS.clipboard.c.NextAfter(cc)) {
231Constraint c = {};
232c.group = SS.GW.activeGroup;
233c.workplane = SS.GW.ActiveWorkplane();
234c.type = cc->type;
235c.valA = cc->valA;
236c.ptA = SS.clipboard.NewEntityFor(mapPoint(cc->ptA));
237c.ptB = SS.clipboard.NewEntityFor(mapPoint(cc->ptB));
238c.entityA = SS.clipboard.NewEntityFor(cc->entityA);
239c.entityB = SS.clipboard.NewEntityFor(cc->entityB);
240c.entityC = SS.clipboard.NewEntityFor(cc->entityC);
241c.entityD = SS.clipboard.NewEntityFor(cc->entityD);
242c.other = cc->other;
243c.other2 = cc->other2;
244c.reference = cc->reference;
245c.disp = cc->disp;
246c.comment = cc->comment;
247bool dontAddConstraint = false;
248switch(c.type) {
249case Constraint::Type::COMMENT:
250c.disp.offset = c.disp.offset.Plus(trans);
251break;
252case Constraint::Type::PT_LINE_DISTANCE:
253c.valA *= scale;
254break;
255case Constraint::Type::PT_PT_DISTANCE:
256case Constraint::Type::PROJ_PT_DISTANCE:
257case Constraint::Type::DIAMETER:
258c.valA *= fabs(scale);
259break;
260case Constraint::Type::ARC_LINE_TANGENT: {
261Entity *line = SK.GetEntity(c.entityB),
262*arc = SK.GetEntity(c.entityA);
263if(line->type == Entity::Type::ARC_OF_CIRCLE) {
264swap(line, arc);
265}
266Constraint::ConstrainArcLineTangent(&c, line, arc);
267break;
268}
269case Constraint::Type::CUBIC_LINE_TANGENT: {
270Entity *line = SK.GetEntity(c.entityB),
271*cubic = SK.GetEntity(c.entityA);
272if(line->type == Entity::Type::CUBIC) {
273swap(line, cubic);
274}
275Constraint::ConstrainCubicLineTangent(&c, line, cubic);
276break;
277}
278case Constraint::Type::CURVE_CURVE_TANGENT: {
279Entity *eA = SK.GetEntity(c.entityA),
280*eB = SK.GetEntity(c.entityB);
281Constraint::ConstrainCurveCurveTangent(&c, eA, eB);
282break;
283}
284case Constraint::Type::HORIZONTAL:
285case Constraint::Type::VERTICAL:
286// When rotating 90 or 270 degrees, swap the vertical / horizontal constraints
287if (EXACT(fmod(theta + (PI/2), PI) == 0)) {
288if(c.type == Constraint::Type::HORIZONTAL) {
289c.type = Constraint::Type::VERTICAL;
290} else {
291c.type = Constraint::Type::HORIZONTAL;
292}
293} else if (fmod(theta, PI/2) != 0) {
294dontAddConstraint = true;
295}
296break;
297default:
298break;
299}
300if (!dontAddConstraint) {
301hConstraint hc = Constraint::AddConstraint(&c, /*rememberForUndo=*/false);
302if(c.type == Constraint::Type::COMMENT) {
303MakeSelected(hc);
304}
305}
306}
307}
308
309void GraphicsWindow::MenuClipboard(Command id) {
310if(id != Command::DELETE && !SS.GW.LockedInWorkplane()) {
311Error(_("Cut, paste, and copy work only in a workplane.\n\n"
312"Activate one with Sketch -> In Workplane."));
313return;
314}
315
316switch(id) {
317case Command::PASTE: {
318SS.UndoRemember();
319Vector trans = SS.GW.projRight.ScaledBy(80/SS.GW.scale).Plus(
320SS.GW.projUp .ScaledBy(40/SS.GW.scale));
321SS.GW.ClearSelection();
322SS.GW.PasteClipboard(trans, 0, 1);
323break;
324}
325
326case Command::PASTE_TRANSFORM: {
327if(SS.clipboard.r.IsEmpty()) {
328Error(_("Clipboard is empty; nothing to paste."));
329break;
330}
331
332Entity *wrkpl = SK.GetEntity(SS.GW.ActiveWorkplane());
333Vector p = SK.GetEntity(wrkpl->point[0])->PointGetNum();
334SS.TW.shown.paste.times = 1;
335SS.TW.shown.paste.trans = Vector::From(0, 0, 0);
336SS.TW.shown.paste.theta = 0;
337SS.TW.shown.paste.origin = p;
338SS.TW.shown.paste.scale = 1;
339SS.TW.GoToScreen(TextWindow::Screen::PASTE_TRANSFORMED);
340SS.GW.ForceTextWindowShown();
341SS.ScheduleShowTW();
342break;
343}
344
345case Command::COPY:
346SS.GW.CopySelection();
347SS.GW.ClearSelection();
348break;
349
350case Command::CUT:
351SS.UndoRemember();
352SS.GW.CopySelection();
353SS.GW.DeleteSelection();
354break;
355
356case Command::DELETE:
357SS.UndoRemember();
358SS.GW.DeleteSelection();
359break;
360
361default: ssassert(false, "Unexpected menu ID");
362}
363}
364
365bool TextWindow::EditControlDoneForPaste(const std::string &s) {
366Expr *e;
367switch(edit.meaning) {
368case Edit::PASTE_TIMES_REPEATED: {
369e = Expr::From(s, /*popUpError=*/true);
370if(!e) break;
371int v = (int)e->Eval();
372if(v > 0) {
373shown.paste.times = v;
374} else {
375Error(_("Number of copies to paste must be at least one."));
376}
377break;
378}
379case Edit::PASTE_ANGLE:
380e = Expr::From(s, /*popUpError=*/true);
381if(!e) break;
382shown.paste.theta = WRAP_SYMMETRIC((e->Eval())*PI/180, 2*PI);
383break;
384
385case Edit::PASTE_SCALE: {
386e = Expr::From(s, /*popUpError=*/true);
387double v = e->Eval();
388if(fabs(v) > 1e-6) {
389shown.paste.scale = shown.paste.scale < 0 ? -v : v;
390} else {
391Error(_("Scale cannot be zero."));
392}
393break;
394}
395
396default:
397return false;
398}
399return true;
400}
401
402void TextWindow::ScreenChangePasteTransformed(int link, uint32_t v) {
403switch(link) {
404case 't':
405SS.TW.ShowEditControl(13, ssprintf("%d", SS.TW.shown.paste.times));
406SS.TW.edit.meaning = Edit::PASTE_TIMES_REPEATED;
407break;
408
409case 'r':
410SS.TW.ShowEditControl(13, ssprintf("%.3f", SS.TW.shown.paste.theta*180/PI));
411SS.TW.edit.meaning = Edit::PASTE_ANGLE;
412break;
413
414case 's':
415SS.TW.ShowEditControl(13, ssprintf("%.3f", fabs(SS.TW.shown.paste.scale)));
416SS.TW.edit.meaning = Edit::PASTE_SCALE;
417break;
418
419case 'f':
420SS.TW.shown.paste.scale *= -1;
421break;
422}
423}
424
425void TextWindow::ScreenPasteTransformed(int link, uint32_t v) {
426SS.GW.GroupSelection();
427switch(link) {
428case 'o':
429if(SS.GW.gs.points == 1 && SS.GW.gs.n == 1) {
430Entity *e = SK.GetEntity(SS.GW.gs.point[0]);
431SS.TW.shown.paste.origin = e->PointGetNum();
432} else {
433Error(_("Select one point to define origin of rotation."));
434}
435SS.GW.ClearSelection();
436break;
437
438case 't':
439if(SS.GW.gs.points == 2 && SS.GW.gs.n == 2) {
440Entity *pa = SK.GetEntity(SS.GW.gs.point[0]),
441*pb = SK.GetEntity(SS.GW.gs.point[1]);
442SS.TW.shown.paste.trans =
443(pb->PointGetNum()).Minus(pa->PointGetNum());
444} else {
445Error(_("Select two points to define translation vector."));
446}
447SS.GW.ClearSelection();
448break;
449
450case 'g': {
451if(fabs(SS.TW.shown.paste.theta) < LENGTH_EPS &&
452SS.TW.shown.paste.trans.Magnitude() < LENGTH_EPS &&
453SS.TW.shown.paste.times != 1)
454{
455Message(_("Transformation is identity. So all copies will be "
456"exactly on top of each other."));
457}
458if(SS.TW.shown.paste.times*SS.clipboard.r.n > 100) {
459Error(_("Too many items to paste; split this into smaller "
460"pastes."));
461break;
462}
463if(!SS.GW.LockedInWorkplane()) {
464Error(_("No workplane active."));
465break;
466}
467Entity *wrkpl = SK.GetEntity(SS.GW.ActiveWorkplane());
468Entity *wrkpln = SK.GetEntity(wrkpl->normal);
469Vector wn = wrkpln->NormalN();
470SS.UndoRemember();
471SS.GW.ClearSelection();
472for(int i = 0; i < SS.TW.shown.paste.times; i++) {
473Vector trans = SS.TW.shown.paste.trans.ScaledBy(i+1),
474origin = SS.TW.shown.paste.origin;
475double theta = SS.TW.shown.paste.theta*(i+1);
476// desired transformation is Q*(p - o) + o + t =
477// Q*p - Q*o + o + t = Q*p + (t + o - Q*o)
478Vector t = trans.Plus(
479origin).Minus(
480origin.RotatedAbout(wn, theta));
481
482SS.GW.PasteClipboard(t, theta, SS.TW.shown.paste.scale);
483}
484SS.TW.GoToScreen(Screen::LIST_OF_GROUPS);
485SS.ScheduleShowTW();
486break;
487}
488}
489}
490
491void TextWindow::ShowPasteTransformed() {
492Printf(true, "%FtPASTE TRANSFORMED%E");
493Printf(true, "%Ba %Ftrepeat%E %d time%s %Fl%Lt%f[change]%E",
494shown.paste.times, (shown.paste.times == 1) ? "" : "s",
495&ScreenChangePasteTransformed);
496Printf(false, "%Bd %Ftrotate%E %@ degrees %Fl%Lr%f[change]%E",
497shown.paste.theta*180/PI,
498&ScreenChangePasteTransformed);
499Printf(false, "%Ba %Ftabout pt%E (%s, %s, %s) %Fl%Lo%f[use selected]%E",
500SS.MmToString(shown.paste.origin.x).c_str(),
501SS.MmToString(shown.paste.origin.y).c_str(),
502SS.MmToString(shown.paste.origin.z).c_str(),
503&ScreenPasteTransformed);
504Printf(false, "%Bd %Fttranslate%E (%s, %s, %s) %Fl%Lt%f[use selected]%E",
505SS.MmToString(shown.paste.trans.x).c_str(),
506SS.MmToString(shown.paste.trans.y).c_str(),
507SS.MmToString(shown.paste.trans.z).c_str(),
508&ScreenPasteTransformed);
509Printf(false, "%Ba %Ftscale%E %@ %Fl%Ls%f[change]%E",
510fabs(shown.paste.scale),
511&ScreenChangePasteTransformed);
512Printf(false, "%Ba %Ftmirror%E %Fd%Lf%f%s flip%E",
513&ScreenChangePasteTransformed,
514shown.paste.scale < 0 ? CHECK_TRUE : CHECK_FALSE);
515
516Printf(true, " %Fl%Lg%fpaste transformed now%E", &ScreenPasteTransformed);
517
518Printf(true, "(or %Fl%Ll%fcancel operation%E)", &ScreenHome);
519}
520
521