Solvespace

Форк
0
/
file.cpp 
1017 строк · 40.7 Кб
1
//-----------------------------------------------------------------------------
2
// Routines to write and read our .slvs file format.
3
//
4
// Copyright 2008-2013 Jonathan Westhues.
5
//-----------------------------------------------------------------------------
6
#include "solvespace.h"
7

8
#define VERSION_STRING "\261\262\263" "SolveSpaceREVa"
9

10
static int StrStartsWith(const char *str, const char *start) {
11
    return memcmp(str, start, strlen(start)) == 0;
12
}
13

14
//-----------------------------------------------------------------------------
15
// Clear and free all the dynamic memory associated with our currently-loaded
16
// sketch. This does not leave the program in an acceptable state (with the
17
// references created, and so on), so anyone calling this must fix that later.
18
//-----------------------------------------------------------------------------
19
void SolveSpaceUI::ClearExisting() {
20
    UndoClearStack(&redo);
21
    UndoClearStack(&undo);
22

23
    for(hGroup hg : SK.groupOrder) {
24
        Group *g = SK.GetGroup(hg);
25
        g->Clear();
26
    }
27

28
    SK.constraint.Clear();
29
    SK.request.Clear();
30
    SK.group.Clear();
31
    SK.groupOrder.Clear();
32
    SK.style.Clear();
33

34
    SK.entity.Clear();
35
    SK.param.Clear();
36
    images.clear();
37
}
38

39
hGroup SolveSpaceUI::CreateDefaultDrawingGroup() {
40
    Group g = {};
41

42
    // And an empty group, for the first stuff the user draws.
43
    g.visible = true;
44
    g.name = C_("group-name", "sketch-in-plane");
45
    g.type = Group::Type::DRAWING_WORKPLANE;
46
    g.subtype = Group::Subtype::WORKPLANE_BY_POINT_ORTHO;
47
    g.order = 1;
48
    g.predef.q = Quaternion::From(1, 0, 0, 0);
49
    hRequest hr = Request::HREQUEST_REFERENCE_XY;
50
    g.predef.origin = hr.entity(1);
51
    SK.group.AddAndAssignId(&g);
52
    SK.GetGroup(g.h)->activeWorkplane = g.h.entity(0);
53
    return g.h;
54
}
55

56
void SolveSpaceUI::NewFile() {
57
    ClearExisting();
58

59
    // Our initial group, that contains the references.
60
    Group g = {};
61
    g.visible = true;
62
    g.name = C_("group-name", "#references");
63
    g.type = Group::Type::DRAWING_3D;
64
    g.order = 0;
65
    g.h = Group::HGROUP_REFERENCES;
66
    SK.group.Add(&g);
67

68
    // Let's create three two-d coordinate systems, for the coordinate
69
    // planes; these are our references, present in every sketch.
70
    Request r = {};
71
    r.type = Request::Type::WORKPLANE;
72
    r.group = Group::HGROUP_REFERENCES;
73
    r.workplane = Entity::FREE_IN_3D;
74

75
    r.h = Request::HREQUEST_REFERENCE_XY;
76
    SK.request.Add(&r);
77

78
    r.h = Request::HREQUEST_REFERENCE_YZ;
79
    SK.request.Add(&r);
80

81
    r.h = Request::HREQUEST_REFERENCE_ZX;
82
    SK.request.Add(&r);
83

84
    CreateDefaultDrawingGroup();
85
}
86

87
const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = {
88
    { 'g',  "Group.h.v",                'x',    &(SS.sv.g.h.v)                },
89
    { 'g',  "Group.type",               'd',    &(SS.sv.g.type)               },
90
    { 'g',  "Group.order",              'd',    &(SS.sv.g.order)              },
91
    { 'g',  "Group.name",               'S',    &(SS.sv.g.name)               },
92
    { 'g',  "Group.activeWorkplane.v",  'x',    &(SS.sv.g.activeWorkplane.v)  },
93
    { 'g',  "Group.opA.v",              'x',    &(SS.sv.g.opA.v)              },
94
    { 'g',  "Group.opB.v",              'x',    &(SS.sv.g.opB.v)              },
95
    { 'g',  "Group.valA",               'f',    &(SS.sv.g.valA)               },
96
    { 'g',  "Group.valB",               'f',    &(SS.sv.g.valB)               },
97
    { 'g',  "Group.valC",               'f',    &(SS.sv.g.valB)               },
98
    { 'g',  "Group.color",              'c',    &(SS.sv.g.color)              },
99
    { 'g',  "Group.subtype",            'd',    &(SS.sv.g.subtype)            },
100
    { 'g',  "Group.skipFirst",          'b',    &(SS.sv.g.skipFirst)          },
101
    { 'g',  "Group.meshCombine",        'd',    &(SS.sv.g.meshCombine)        },
102
    { 'g',  "Group.forceToMesh",        'd',    &(SS.sv.g.forceToMesh)        },
103
    { 'g',  "Group.predef.q.w",         'f',    &(SS.sv.g.predef.q.w)         },
104
    { 'g',  "Group.predef.q.vx",        'f',    &(SS.sv.g.predef.q.vx)        },
105
    { 'g',  "Group.predef.q.vy",        'f',    &(SS.sv.g.predef.q.vy)        },
106
    { 'g',  "Group.predef.q.vz",        'f',    &(SS.sv.g.predef.q.vz)        },
107
    { 'g',  "Group.predef.origin.v",    'x',    &(SS.sv.g.predef.origin.v)    },
108
    { 'g',  "Group.predef.entityB.v",   'x',    &(SS.sv.g.predef.entityB.v)   },
109
    { 'g',  "Group.predef.entityC.v",   'x',    &(SS.sv.g.predef.entityC.v)   },
110
    { 'g',  "Group.predef.swapUV",      'b',    &(SS.sv.g.predef.swapUV)      },
111
    { 'g',  "Group.predef.negateU",     'b',    &(SS.sv.g.predef.negateU)     },
112
    { 'g',  "Group.predef.negateV",     'b',    &(SS.sv.g.predef.negateV)     },
113
    { 'g',  "Group.visible",            'b',    &(SS.sv.g.visible)            },
114
    { 'g',  "Group.suppress",           'b',    &(SS.sv.g.suppress)           },
115
    { 'g',  "Group.relaxConstraints",   'b',    &(SS.sv.g.relaxConstraints)   },
116
    { 'g',  "Group.allowRedundant",     'b',    &(SS.sv.g.allowRedundant)     },
117
    { 'g',  "Group.allDimsReference",   'b',    &(SS.sv.g.allDimsReference)   },
118
    { 'g',  "Group.scale",              'f',    &(SS.sv.g.scale)              },
119
    { 'g',  "Group.remap",              'M',    &(SS.sv.g.remap)              },
120
    { 'g',  "Group.impFile",            'i',    NULL                          },
121
    { 'g',  "Group.impFileRel",         'P',    &(SS.sv.g.linkFile)           },
122

123
    { 'p',  "Param.h.v.",               'x',    &(SS.sv.p.h.v)                },
124
    { 'p',  "Param.val",                'f',    &(SS.sv.p.val)                },
125

126
    { 'r',  "Request.h.v",              'x',    &(SS.sv.r.h.v)                },
127
    { 'r',  "Request.type",             'd',    &(SS.sv.r.type)               },
128
    { 'r',  "Request.extraPoints",      'd',    &(SS.sv.r.extraPoints)        },
129
    { 'r',  "Request.workplane.v",      'x',    &(SS.sv.r.workplane.v)        },
130
    { 'r',  "Request.group.v",          'x',    &(SS.sv.r.group.v)            },
131
    { 'r',  "Request.construction",     'b',    &(SS.sv.r.construction)       },
132
    { 'r',  "Request.style",            'x',    &(SS.sv.r.style)              },
133
    { 'r',  "Request.str",              'S',    &(SS.sv.r.str)                },
134
    { 'r',  "Request.font",             'S',    &(SS.sv.r.font)               },
135
    { 'r',  "Request.file",             'P',    &(SS.sv.r.file)               },
136
    { 'r',  "Request.aspectRatio",      'f',    &(SS.sv.r.aspectRatio)        },
137

138
    { 'e',  "Entity.h.v",               'x',    &(SS.sv.e.h.v)                },
139
    { 'e',  "Entity.type",              'd',    &(SS.sv.e.type)               },
140
    { 'e',  "Entity.construction",      'b',    &(SS.sv.e.construction)       },
141
    { 'e',  "Entity.style",             'x',    &(SS.sv.e.style)              },
142
    { 'e',  "Entity.str",               'S',    &(SS.sv.e.str)                },
143
    { 'e',  "Entity.font",              'S',    &(SS.sv.e.font)               },
144
    { 'e',  "Entity.file",              'P',    &(SS.sv.e.file)               },
145
    { 'e',  "Entity.point[0].v",        'x',    &(SS.sv.e.point[0].v)         },
146
    { 'e',  "Entity.point[1].v",        'x',    &(SS.sv.e.point[1].v)         },
147
    { 'e',  "Entity.point[2].v",        'x',    &(SS.sv.e.point[2].v)         },
148
    { 'e',  "Entity.point[3].v",        'x',    &(SS.sv.e.point[3].v)         },
149
    { 'e',  "Entity.point[4].v",        'x',    &(SS.sv.e.point[4].v)         },
150
    { 'e',  "Entity.point[5].v",        'x',    &(SS.sv.e.point[5].v)         },
151
    { 'e',  "Entity.point[6].v",        'x',    &(SS.sv.e.point[6].v)         },
152
    { 'e',  "Entity.point[7].v",        'x',    &(SS.sv.e.point[7].v)         },
153
    { 'e',  "Entity.point[8].v",        'x',    &(SS.sv.e.point[8].v)         },
154
    { 'e',  "Entity.point[9].v",        'x',    &(SS.sv.e.point[9].v)         },
155
    { 'e',  "Entity.point[10].v",       'x',    &(SS.sv.e.point[10].v)        },
156
    { 'e',  "Entity.point[11].v",       'x',    &(SS.sv.e.point[11].v)        },
157
    { 'e',  "Entity.extraPoints",       'd',    &(SS.sv.e.extraPoints)        },
158
    { 'e',  "Entity.normal.v",          'x',    &(SS.sv.e.normal.v)           },
159
    { 'e',  "Entity.distance.v",        'x',    &(SS.sv.e.distance.v)         },
160
    { 'e',  "Entity.workplane.v",       'x',    &(SS.sv.e.workplane.v)        },
161
    { 'e',  "Entity.actPoint.x",        'f',    &(SS.sv.e.actPoint.x)         },
162
    { 'e',  "Entity.actPoint.y",        'f',    &(SS.sv.e.actPoint.y)         },
163
    { 'e',  "Entity.actPoint.z",        'f',    &(SS.sv.e.actPoint.z)         },
164
    { 'e',  "Entity.actNormal.w",       'f',    &(SS.sv.e.actNormal.w)        },
165
    { 'e',  "Entity.actNormal.vx",      'f',    &(SS.sv.e.actNormal.vx)       },
166
    { 'e',  "Entity.actNormal.vy",      'f',    &(SS.sv.e.actNormal.vy)       },
167
    { 'e',  "Entity.actNormal.vz",      'f',    &(SS.sv.e.actNormal.vz)       },
168
    { 'e',  "Entity.actDistance",       'f',    &(SS.sv.e.actDistance)        },
169
    { 'e',  "Entity.actVisible",        'b',    &(SS.sv.e.actVisible),        },
170

171

172
    { 'c',  "Constraint.h.v",           'x',    &(SS.sv.c.h.v)                },
173
    { 'c',  "Constraint.type",          'd',    &(SS.sv.c.type)               },
174
    { 'c',  "Constraint.group.v",       'x',    &(SS.sv.c.group.v)            },
175
    { 'c',  "Constraint.workplane.v",   'x',    &(SS.sv.c.workplane.v)        },
176
    { 'c',  "Constraint.valA",          'f',    &(SS.sv.c.valA)               },
177
    { 'c',  "Constraint.valP.v",        'x',    &(SS.sv.c.valP.v)             },
178
    { 'c',  "Constraint.ptA.v",         'x',    &(SS.sv.c.ptA.v)              },
179
    { 'c',  "Constraint.ptB.v",         'x',    &(SS.sv.c.ptB.v)              },
180
    { 'c',  "Constraint.entityA.v",     'x',    &(SS.sv.c.entityA.v)          },
181
    { 'c',  "Constraint.entityB.v",     'x',    &(SS.sv.c.entityB.v)          },
182
    { 'c',  "Constraint.entityC.v",     'x',    &(SS.sv.c.entityC.v)          },
183
    { 'c',  "Constraint.entityD.v",     'x',    &(SS.sv.c.entityD.v)          },
184
    { 'c',  "Constraint.other",         'b',    &(SS.sv.c.other)              },
185
    { 'c',  "Constraint.other2",        'b',    &(SS.sv.c.other2)             },
186
    { 'c',  "Constraint.reference",     'b',    &(SS.sv.c.reference)          },
187
    { 'c',  "Constraint.comment",       'S',    &(SS.sv.c.comment)            },
188
    { 'c',  "Constraint.disp.offset.x", 'f',    &(SS.sv.c.disp.offset.x)      },
189
    { 'c',  "Constraint.disp.offset.y", 'f',    &(SS.sv.c.disp.offset.y)      },
190
    { 'c',  "Constraint.disp.offset.z", 'f',    &(SS.sv.c.disp.offset.z)      },
191
    { 'c',  "Constraint.disp.style",    'x',    &(SS.sv.c.disp.style)         },
192

193
    { 's',  "Style.h.v",                'x',    &(SS.sv.s.h.v)                },
194
    { 's',  "Style.name",               'S',    &(SS.sv.s.name)               },
195
    { 's',  "Style.width",              'f',    &(SS.sv.s.width)              },
196
    { 's',  "Style.widthAs",            'd',    &(SS.sv.s.widthAs)            },
197
    { 's',  "Style.textHeight",         'f',    &(SS.sv.s.textHeight)         },
198
    { 's',  "Style.textHeightAs",       'd',    &(SS.sv.s.textHeightAs)       },
199
    { 's',  "Style.textAngle",          'f',    &(SS.sv.s.textAngle)          },
200
    { 's',  "Style.textOrigin",         'x',    &(SS.sv.s.textOrigin)         },
201
    { 's',  "Style.color",              'c',    &(SS.sv.s.color)              },
202
    { 's',  "Style.fillColor",          'c',    &(SS.sv.s.fillColor)          },
203
    { 's',  "Style.filled",             'b',    &(SS.sv.s.filled)             },
204
    { 's',  "Style.visible",            'b',    &(SS.sv.s.visible)            },
205
    { 's',  "Style.exportable",         'b',    &(SS.sv.s.exportable)         },
206
    { 's',  "Style.stippleType",        'd',    &(SS.sv.s.stippleType)        },
207
    { 's',  "Style.stippleScale",       'f',    &(SS.sv.s.stippleScale)       },
208

209
    { 0, NULL, 0, NULL }
210
};
211

212
struct SAVEDptr {
213
    EntityMap      &M() { return *((EntityMap *)this); }
214
    std::string    &S() { return *((std::string *)this); }
215
    Platform::Path &P() { return *((Platform::Path *)this); }
216
    bool      &b() { return *((bool *)this); }
217
    RgbaColor &c() { return *((RgbaColor *)this); }
218
    int       &d() { return *((int *)this); }
219
    double    &f() { return *((double *)this); }
220
    uint32_t  &x() { return *((uint32_t *)this); }
221
};
222

223
void SolveSpaceUI::SaveUsingTable(const Platform::Path &filename, int type) {
224
    int i;
225
    for(i = 0; SAVED[i].type != 0; i++) {
226
        if(SAVED[i].type != type) continue;
227

228
        int fmt = SAVED[i].fmt;
229
        SAVEDptr *p = (SAVEDptr *)SAVED[i].ptr;
230
        // Any items that aren't specified are assumed to be zero
231
        if(fmt == 'S' && p->S().empty())          continue;
232
        if(fmt == 'P' && p->P().IsEmpty())        continue;
233
        if(fmt == 'd' && p->d() == 0)             continue;
234
        if(fmt == 'f' && EXACT(p->f() == 0.0))    continue;
235
        if(fmt == 'x' && p->x() == 0)             continue;
236
        if(fmt == 'i')                            continue;
237

238
        fprintf(fh, "%s=", SAVED[i].desc);
239
        switch(fmt) {
240
            case 'S': fprintf(fh, "%s",    p->S().c_str());       break;
241
            case 'b': fprintf(fh, "%d",    p->b() ? 1 : 0);       break;
242
            case 'c': fprintf(fh, "%08x",  p->c().ToPackedInt()); break;
243
            case 'd': fprintf(fh, "%d",    p->d());               break;
244
            case 'f': fprintf(fh, "%.20f", p->f());               break;
245
            case 'x': fprintf(fh, "%08x",  p->x());               break;
246

247
            case 'P': {
248
                if(!p->P().IsEmpty()) {
249
                    Platform::Path relativePath = p->P().RelativeTo(filename.Parent());
250
                    ssassert(!relativePath.IsEmpty(), "Cannot relativize path");
251
                    fprintf(fh, "%s", relativePath.ToPortable().c_str());
252
                }
253
                break;
254
            }
255

256
            case 'M': {
257
                fprintf(fh, "{\n");
258
                // Sort the mapping, since EntityMap is not deterministic.
259
                std::vector<std::pair<EntityKey, EntityId>> sorted(p->M().begin(), p->M().end());
260
                std::sort(sorted.begin(), sorted.end(),
261
                    [](std::pair<EntityKey, EntityId> &a, std::pair<EntityKey, EntityId> &b) {
262
                        return a.second.v < b.second.v;
263
                    });
264
                for(auto it : sorted) {
265
                    fprintf(fh, "    %d %08x %d\n",
266
                            it.second.v, it.first.input.v, it.first.copyNumber);
267
                }
268
                fprintf(fh, "}");
269
                break;
270
            }
271

272
            case 'i': break;
273

274
            default: ssassert(false, "Unexpected value format");
275
        }
276
        fprintf(fh, "\n");
277
    }
278
}
279

280
bool SolveSpaceUI::SaveToFile(const Platform::Path &filename) {
281
    // Make sure all the entities are regenerated up to date, since they will be exported.
282
    SS.ScheduleShowTW();
283
    SS.GenerateAll(SolveSpaceUI::Generate::ALL);
284

285
    for(Group &g : SK.group) {
286
        if(g.type != Group::Type::LINKED) continue;
287

288
        if(g.linkFile.RelativeTo(filename).IsEmpty()) {
289
            Error("This sketch links the sketch '%s'; it can only be saved "
290
                  "on the same volume.", g.linkFile.raw.c_str());
291
            return false;
292
        }
293
    }
294

295
    fh = OpenFile(filename, "wb");
296
    if(!fh) {
297
        Error("Couldn't write to file '%s'", filename.raw.c_str());
298
        return false;
299
    }
300

301
    fprintf(fh, "%s\n\n\n", VERSION_STRING);
302

303
    int i, j;
304
    for(auto &g : SK.group) {
305
        sv.g = g;
306
        SaveUsingTable(filename, 'g');
307
        fprintf(fh, "AddGroup\n\n");
308
    }
309

310
    for(auto &p : SK.param) {
311
        sv.p = p;
312
        SaveUsingTable(filename, 'p');
313
        fprintf(fh, "AddParam\n\n");
314
    }
315

316
    for(auto &r : SK.request) {
317
        sv.r = r;
318
        SaveUsingTable(filename, 'r');
319
        fprintf(fh, "AddRequest\n\n");
320
    }
321

322
    for(auto &e : SK.entity) {
323
        e.CalculateNumerical(/*forExport=*/true);
324
        sv.e = e;
325
        SaveUsingTable(filename, 'e');
326
        fprintf(fh, "AddEntity\n\n");
327
    }
328

329
    for(auto &c : SK.constraint) {
330
        sv.c = c;
331
        SaveUsingTable(filename, 'c');
332
        fprintf(fh, "AddConstraint\n\n");
333
    }
334

335
    for(auto &s : SK.style) {
336
        sv.s = s;
337
        if(sv.s.h.v >= Style::FIRST_CUSTOM) {
338
            SaveUsingTable(filename, 's');
339
            fprintf(fh, "AddStyle\n\n");
340
        }
341
    }
342

343
    // A group will have either a mesh or a shell, but not both; but the code
344
    // to print either of those just does nothing if the mesh/shell is empty.
345

346
    Group *g = SK.GetGroup(*SK.groupOrder.Last());
347
    SMesh *m = &g->runningMesh;
348
    for(i = 0; i < m->l.n; i++) {
349
        STriangle *tr = &(m->l[i]);
350
        fprintf(fh, "Triangle %08x %08x "
351
                "%.20f %.20f %.20f  %.20f %.20f %.20f  %.20f %.20f %.20f\n",
352
            tr->meta.face, tr->meta.color.ToPackedInt(),
353
            CO(tr->a), CO(tr->b), CO(tr->c));
354
    }
355

356
    SShell *s = &g->runningShell;
357
    for(SSurface &srf : s->surface) {
358
        fprintf(fh, "Surface %08x %08x %08x %d %d\n",
359
            srf.h.v, srf.color.ToPackedInt(), srf.face, srf.degm, srf.degn);
360
        for(i = 0; i <= srf.degm; i++) {
361
            for(j = 0; j <= srf.degn; j++) {
362
                fprintf(fh, "SCtrl %d %d %.20f %.20f %.20f Weight %20.20f\n",
363
                    i, j, CO(srf.ctrl[i][j]), srf.weight[i][j]);
364
            }
365
        }
366

367
        STrimBy *stb;
368
        for(stb = srf.trim.First(); stb; stb = srf.trim.NextAfter(stb)) {
369
            fprintf(fh, "TrimBy %08x %d %.20f %.20f %.20f  %.20f %.20f %.20f\n",
370
                stb->curve.v, stb->backwards ? 1 : 0,
371
                CO(stb->start), CO(stb->finish));
372
        }
373

374
        fprintf(fh, "AddSurface\n");
375
    }
376
    for(SCurve &sc : s->curve) {
377
        fprintf(fh, "Curve %08x %d %d %08x %08x\n",
378
            sc.h.v,
379
            sc.isExact ? 1 : 0, sc.exact.deg,
380
            sc.surfA.v, sc.surfB.v);
381

382
        if(sc.isExact) {
383
            for(i = 0; i <= sc.exact.deg; i++) {
384
                fprintf(fh, "CCtrl %d %.20f %.20f %.20f Weight %.20f\n",
385
                    i, CO(sc.exact.ctrl[i]), sc.exact.weight[i]);
386
            }
387
        }
388
        SCurvePt *scpt;
389
        for(scpt = sc.pts.First(); scpt; scpt = sc.pts.NextAfter(scpt)) {
390
            fprintf(fh, "CurvePt %d %.20f %.20f %.20f\n",
391
                scpt->vertex ? 1 : 0, CO(scpt->p));
392
        }
393

394
        fprintf(fh, "AddCurve\n");
395
    }
396

397
    fclose(fh);
398

399
    return true;
400
}
401

402
void SolveSpaceUI::LoadUsingTable(const Platform::Path &filename, char *key, char *val) {
403
    int i;
404
    for(i = 0; SAVED[i].type != 0; i++) {
405
        if(strcmp(SAVED[i].desc, key)==0) {
406
            SAVEDptr *p = (SAVEDptr *)SAVED[i].ptr;
407
            unsigned int u = 0;
408
            switch(SAVED[i].fmt) {
409
                case 'S': p->S() = val;                     break;
410
                case 'b': p->b() = (atoi(val) != 0);        break;
411
                case 'd': p->d() = atoi(val);               break;
412
                case 'f': p->f() = atof(val);               break;
413
                case 'x': sscanf(val, "%x", &u); p->x()= u; break;
414

415
                case 'P': {
416
                    Platform::Path path = Platform::Path::FromPortable(val);
417
                    if(!path.IsEmpty()) {
418
                        p->P() = filename.Parent().Join(path).Expand();
419
                    }
420
                    break;
421
                }
422

423
                case 'c':
424
                    sscanf(val, "%x", &u);
425
                    p->c() = RgbaColor::FromPackedInt(u);
426
                    break;
427

428
                case 'M': {
429
                    p->M().clear();
430
                    for(;;) {
431
                        EntityKey ek;
432
                        EntityId ei;
433
                        char line2[1024];
434
                        if (fgets(line2, (int)sizeof(line2), fh) == NULL)
435
                            break;
436
                        if(sscanf(line2, "%d %x %d", &(ei.v), &(ek.input.v),
437
                                                     &(ek.copyNumber)) == 3) {
438
                            if(ei.v == Entity::NO_ENTITY.v) {
439
                                // Commit bd84bc1a mistakenly introduced code that would remap
440
                                // some entities to NO_ENTITY. This was fixed in commit bd84bc1a,
441
                                // but files created meanwhile are corrupt, and can cause crashes.
442
                                //
443
                                // To fix this, we skip any such remaps when loading; they will be
444
                                // recreated on the next regeneration. Any resulting orphans will
445
                                // be pruned in the usual way, recovering to a well-defined state.
446
                                continue;
447
                            }
448
                            p->M().insert({ ek, ei });
449
                        } else {
450
                            break;
451
                        }
452
                    }
453
                    break;
454
                }
455

456
                case 'i': break;
457

458
                default: ssassert(false, "Unexpected value format");
459
            }
460
            break;
461
        }
462
    }
463
    if(SAVED[i].type == 0) {
464
        fileLoadError = true;
465
    }
466
}
467

468
bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel) {
469
    bool fileIsEmpty = true;
470
    allConsistent = false;
471
    fileLoadError = false;
472

473
    fh = OpenFile(filename, "rb");
474
    if(!fh) {
475
        Error("Couldn't read from file '%s'", filename.raw.c_str());
476
        return false;
477
    }
478

479
    ClearExisting();
480

481
    sv = {};
482
    sv.g.scale = 1; // default is 1, not 0; so legacy files need this
483
    Style::FillDefaultStyle(&sv.s);
484

485
    char line[1024];
486
    while(fgets(line, (int)sizeof(line), fh)) {
487
        fileIsEmpty = false;
488

489
        char *s = strchr(line, '\n');
490
        if(s) *s = '\0';
491
        // We should never get files with \r characters in them, but mailers
492
        // will sometimes mangle attachments.
493
        s = strchr(line, '\r');
494
        if(s) *s = '\0';
495

496
        if(*line == '\0') continue;
497

498
        char *e = strchr(line, '=');
499
        if(e) {
500
            *e = '\0';
501
            char *key = line, *val = e+1;
502
            LoadUsingTable(filename, key, val);
503
        } else if(strcmp(line, "AddGroup")==0) {
504
            // legacy files have a spurious dependency between linked groups
505
            // and their parent groups, remove
506
            if(sv.g.type == Group::Type::LINKED)
507
                sv.g.opA.v = 0;
508

509
            SK.group.Add(&(sv.g));
510
            sv.g = {};
511
            sv.g.scale = 1; // default is 1, not 0; so legacy files need this
512
        } else if(strcmp(line, "AddParam")==0) {
513
            // params are regenerated, but we want to preload the values
514
            // for initial guesses
515
            SK.param.Add(&(sv.p));
516
            sv.p = {};
517
        } else if(strcmp(line, "AddEntity")==0) {
518
            // entities are regenerated
519
        } else if(strcmp(line, "AddRequest")==0) {
520
            SK.request.Add(&(sv.r));
521
            sv.r = {};
522
        } else if(strcmp(line, "AddConstraint")==0) {
523
            SK.constraint.Add(&(sv.c));
524
            sv.c = {};
525
        } else if(strcmp(line, "AddStyle")==0) {
526
            SK.style.Add(&(sv.s));
527
            sv.s = {};
528
            Style::FillDefaultStyle(&sv.s);
529
        } else if(strcmp(line, VERSION_STRING)==0) {
530
            // do nothing, version string
531
        } else if(StrStartsWith(line, "Triangle ")      ||
532
                  StrStartsWith(line, "Surface ")       ||
533
                  StrStartsWith(line, "SCtrl ")         ||
534
                  StrStartsWith(line, "TrimBy ")        ||
535
                  StrStartsWith(line, "Curve ")         ||
536
                  StrStartsWith(line, "CCtrl ")         ||
537
                  StrStartsWith(line, "CurvePt ")       ||
538
                  strcmp(line, "AddSurface")==0         ||
539
                  strcmp(line, "AddCurve")==0)
540
        {
541
            // ignore the mesh or shell, since we regenerate that
542
        } else {
543
            fileLoadError = true;
544
        }
545
    }
546

547
    fclose(fh);
548

549
    if(fileIsEmpty) {
550
        Error(_("The file is empty. It may be corrupt."));
551
        NewFile();
552
    }
553

554
    if(fileLoadError) {
555
        Error(_("Unrecognized data in file. This file may be corrupt, or "
556
                "from a newer version of the program."));
557
        // At least leave the program in a non-crashing state.
558
        if(SK.group.IsEmpty()) {
559
            NewFile();
560
        }
561
    }
562
    if(!ReloadAllLinked(filename, canCancel)) {
563
        return false;
564
    }
565
    UpgradeLegacyData();
566

567
    return true;
568
}
569

570
void SolveSpaceUI::UpgradeLegacyData() {
571
    for(Request &r : SK.request) {
572
        switch(r.type) {
573
            // TTF text requests saved in versions prior to 3.0 only have two
574
            // reference points (origin and origin plus v); version 3.0 adds two
575
            // more points, and if we don't do anything, then they will appear
576
            // at workplane origin, and the solver will mess up the sketch if
577
            // it is not fully constrained.
578
            case Request::Type::TTF_TEXT: {
579
                IdList<Entity,hEntity> entity = {};
580
                IdList<Param,hParam>   param = {};
581
                r.Generate(&entity, &param);
582

583
                // If we didn't load all of the entities and params that this
584
                // request would generate, then add them now, so that we can
585
                // force them to their appropriate positions.
586
                for(Param &p : param) {
587
                    if(SK.param.FindByIdNoOops(p.h) != NULL) continue;
588
                    SK.param.Add(&p);
589
                }
590
                bool allPointsExist = true;
591
                for(Entity &e : entity) {
592
                    if(SK.entity.FindByIdNoOops(e.h) != NULL) continue;
593
                    SK.entity.Add(&e);
594
                    allPointsExist = false;
595
                }
596

597
                if(!allPointsExist) {
598
                    Entity *text = entity.FindById(r.h.entity(0));
599
                    Entity *b = entity.FindById(text->point[2]);
600
                    Entity *c = entity.FindById(text->point[3]);
601
                    ExprVector bex, cex;
602
                    text->RectGetPointsExprs(&bex, &cex);
603
                    b->PointForceParamTo(bex.Eval());
604
                    c->PointForceParamTo(cex.Eval());
605
                }
606
                entity.Clear();
607
                param.Clear();
608
                break;
609
            }
610

611
            default:
612
                break;
613
        }
614
    }
615

616
    // Constraints saved in versions prior to 3.0 never had any params;
617
    // version 3.0 introduced params to constraints to avoid the hairy ball problem,
618
    // so force them where they belong.
619
    IdList<Param,hParam> oldParam = {};
620
    SK.param.DeepCopyInto(&oldParam);
621
    SS.GenerateAll(SolveSpaceUI::Generate::REGEN);
622

623
    auto AllParamsExistFor = [&](Constraint &c) {
624
        IdList<Param,hParam> param = {};
625
        c.Generate(&param);
626
        bool allParamsExist = true;
627
        for(Param &p : param) {
628
            if(oldParam.FindByIdNoOops(p.h) != NULL) continue;
629
            allParamsExist = false;
630
            break;
631
        }
632
        param.Clear();
633
        return allParamsExist;
634
    };
635

636
    for(Constraint &c : SK.constraint) {
637
        switch(c.type) {
638
            case Constraint::Type::PT_ON_LINE: {
639
                if(AllParamsExistFor(c)) continue;
640

641
                EntityBase *eln = SK.GetEntity(c.entityA);
642
                EntityBase *ea = SK.GetEntity(eln->point[0]);
643
                EntityBase *eb = SK.GetEntity(eln->point[1]);
644
                EntityBase *ep = SK.GetEntity(c.ptA);
645

646
                ExprVector exp = ep->PointGetExprsInWorkplane(c.workplane);
647
                ExprVector exa = ea->PointGetExprsInWorkplane(c.workplane);
648
                ExprVector exb = eb->PointGetExprsInWorkplane(c.workplane);
649
                ExprVector exba = exb.Minus(exa);
650
                Param *p = SK.GetParam(c.h.param(0));
651
                p->val = exba.Dot(exp.Minus(exa))->Eval() / exba.Dot(exba)->Eval();
652
                break;
653
            }
654

655
            case Constraint::Type::CUBIC_LINE_TANGENT: {
656
                if(AllParamsExistFor(c)) continue;
657

658
                EntityBase *cubic = SK.GetEntity(c.entityA);
659
                EntityBase *line  = SK.GetEntity(c.entityB);
660

661
                ExprVector a;
662
                if(c.other) {
663
                    a = cubic->CubicGetFinishTangentExprs();
664
                } else {
665
                    a = cubic->CubicGetStartTangentExprs();
666
                }
667

668
                ExprVector b = line->VectorGetExprs();
669

670
                Param *param = SK.GetParam(c.h.param(0));
671
                param->val = a.Dot(b)->Eval() / b.Dot(b)->Eval();
672
                break;
673
            }
674

675
            case Constraint::Type::SAME_ORIENTATION: {
676
                if(AllParamsExistFor(c)) continue;
677

678
                EntityBase *an = SK.GetEntity(c.entityA);
679
                EntityBase *bn = SK.GetEntity(c.entityB);
680

681
                ExprVector a = an->NormalExprsN();
682
                ExprVector b = bn->NormalExprsN();
683

684
                Param *param = SK.GetParam(c.h.param(0));
685
                param->val = a.Dot(b)->Eval() / b.Dot(b)->Eval();
686
                break;
687
            }
688

689
            case Constraint::Type::PARALLEL: {
690
                if(AllParamsExistFor(c)) continue;
691

692
                EntityBase *ea = SK.GetEntity(c.entityA),
693
                           *eb = SK.GetEntity(c.entityB);
694
                ExprVector a = ea->VectorGetExprsInWorkplane(c.workplane);
695
                ExprVector b = eb->VectorGetExprsInWorkplane(c.workplane);
696

697
                Param *param = SK.GetParam(c.h.param(0));
698
                param->val = a.Dot(b)->Eval() / b.Dot(b)->Eval();
699
                break;
700
            }
701

702
            default:
703
                break;
704
        }
705
    }
706
    oldParam.Clear();
707
}
708

709
bool SolveSpaceUI::LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le,
710
                                        SMesh *m, SShell *sh)
711
{
712
    if(strcmp(filename.Extension().c_str(), "emn")==0) {
713
        return LinkIDF(filename, le, m, sh);
714
    } else if(strcmp(filename.Extension().c_str(), "stl")==0) {
715
        return LinkStl(filename, le, m, sh);    
716
    } else {
717
        return LoadEntitiesFromSlvs(filename, le, m, sh);
718
    }
719
}
720

721
bool SolveSpaceUI::LoadEntitiesFromSlvs(const Platform::Path &filename, EntityList *le,
722
                                        SMesh *m, SShell *sh)
723
{
724
    SSurface srf = {};
725
    SCurve crv = {};
726

727
    fh = OpenFile(filename, "rb");
728
    if(!fh) return false;
729

730
    le->Clear();
731
    sv = {};
732

733
    char line[1024];
734
    while(fgets(line, (int)sizeof(line), fh)) {
735
        char *s = strchr(line, '\n');
736
        if(s) *s = '\0';
737
        // We should never get files with \r characters in them, but mailers
738
        // will sometimes mangle attachments.
739
        s = strchr(line, '\r');
740
        if(s) *s = '\0';
741

742
        if(*line == '\0') continue;
743

744
        char *e = strchr(line, '=');
745
        if(e) {
746
            *e = '\0';
747
            char *key = line, *val = e+1;
748
            LoadUsingTable(filename, key, val);
749
        } else if(strcmp(line, "AddGroup")==0) {
750
            // These get allocated whether we want them or not.
751
            sv.g.remap.clear();
752
        } else if(strcmp(line, "AddParam")==0) {
753

754
        } else if(strcmp(line, "AddEntity")==0) {
755
            le->Add(&(sv.e));
756
            sv.e = {};
757
        } else if(strcmp(line, "AddRequest")==0) {
758

759
        } else if(strcmp(line, "AddConstraint")==0) {
760

761
        } else if(strcmp(line, "AddStyle")==0) {
762
            // Linked file contains a style that we don't have yet,
763
            // so import it.
764
            if (SK.style.FindByIdNoOops(sv.s.h) == nullptr) {
765
                SK.style.Add(&(sv.s));
766
            }
767
            sv.s = {};
768
            Style::FillDefaultStyle(&sv.s);
769
        } else if(strcmp(line, VERSION_STRING)==0) {
770

771
        } else if(StrStartsWith(line, "Triangle ")) {
772
            STriangle tr = {};
773
            unsigned int rgba = 0;
774
            if(sscanf(line, "Triangle %x %x  "
775
                             "%lf %lf %lf  %lf %lf %lf  %lf %lf %lf",
776
                &(tr.meta.face), &rgba,
777
                &(tr.a.x), &(tr.a.y), &(tr.a.z),
778
                &(tr.b.x), &(tr.b.y), &(tr.b.z),
779
                &(tr.c.x), &(tr.c.y), &(tr.c.z)) != 11) {
780
                ssassert(false, "Unexpected Triangle format");
781
            }
782
            tr.meta.color = RgbaColor::FromPackedInt((uint32_t)rgba);
783
            m->AddTriangle(&tr);
784
        } else if(StrStartsWith(line, "Surface ")) {
785
            unsigned int rgba = 0;
786
            if(sscanf(line, "Surface %x %x %x %d %d",
787
                &(srf.h.v), &rgba, &(srf.face),
788
                &(srf.degm), &(srf.degn)) != 5) {
789
                ssassert(false, "Unexpected Surface format");
790
            }
791
            srf.color = RgbaColor::FromPackedInt((uint32_t)rgba);
792
        } else if(StrStartsWith(line, "SCtrl ")) {
793
            int i, j;
794
            Vector c;
795
            double w;
796
            if(sscanf(line, "SCtrl %d %d %lf %lf %lf Weight %lf",
797
                                &i, &j, &(c.x), &(c.y), &(c.z), &w) != 6)
798
            {
799
                ssassert(false, "Unexpected SCtrl format");
800
            }
801
            srf.ctrl[i][j] = c;
802
            srf.weight[i][j] = w;
803
        } else if(StrStartsWith(line, "TrimBy ")) {
804
            STrimBy stb = {};
805
            int backwards;
806
            if(sscanf(line, "TrimBy %x %d  %lf %lf %lf  %lf %lf %lf",
807
                &(stb.curve.v), &backwards,
808
                &(stb.start.x), &(stb.start.y), &(stb.start.z),
809
                &(stb.finish.x), &(stb.finish.y), &(stb.finish.z)) != 8)
810
            {
811
                ssassert(false, "Unexpected TrimBy format");
812
            }
813
            stb.backwards = (backwards != 0);
814
            srf.trim.Add(&stb);
815
        } else if(strcmp(line, "AddSurface")==0) {
816
            sh->surface.Add(&srf);
817
            srf = {};
818
        } else if(StrStartsWith(line, "Curve ")) {
819
            int isExact;
820
            if(sscanf(line, "Curve %x %d %d %x %x",
821
                &(crv.h.v),
822
                &(isExact),
823
                &(crv.exact.deg),
824
                &(crv.surfA.v), &(crv.surfB.v)) != 5)
825
            {
826
                ssassert(false, "Unexpected Curve format");
827
            }
828
            crv.isExact = (isExact != 0);
829
        } else if(StrStartsWith(line, "CCtrl ")) {
830
            int i;
831
            Vector c;
832
            double w;
833
            if(sscanf(line, "CCtrl %d %lf %lf %lf Weight %lf",
834
                                &i, &(c.x), &(c.y), &(c.z), &w) != 5)
835
            {
836
                ssassert(false, "Unexpected CCtrl format");
837
            }
838
            crv.exact.ctrl[i] = c;
839
            crv.exact.weight[i] = w;
840
        } else if(StrStartsWith(line, "CurvePt ")) {
841
            SCurvePt scpt;
842
            int vertex;
843
            if(sscanf(line, "CurvePt %d %lf %lf %lf",
844
                &vertex,
845
                &(scpt.p.x), &(scpt.p.y), &(scpt.p.z)) != 4)
846
            {
847
                ssassert(false, "Unexpected CurvePt format");
848
            }
849
            scpt.vertex = (vertex != 0);
850
            crv.pts.Add(&scpt);
851
        } else if(strcmp(line, "AddCurve")==0) {
852
            sh->curve.Add(&crv);
853
            crv = {};
854
        } else ssassert(false, "Unexpected operation");
855
    }
856

857
    fclose(fh);
858
    return true;
859
}
860

861
static Platform::MessageDialog::Response LocateImportedFile(const Platform::Path &filename,
862
                                                            bool canCancel) {
863
    Platform::MessageDialogRef dialog = CreateMessageDialog(SS.GW.window);
864

865
    using Platform::MessageDialog;
866
    dialog->SetType(MessageDialog::Type::QUESTION);
867
    dialog->SetTitle(C_("title", "Missing File"));
868
    dialog->SetMessage(ssprintf(C_("dialog", "The linked file “%s” is not present."),
869
                                filename.raw.c_str()));
870
    dialog->SetDescription(C_("dialog", "Do you want to locate it manually?\n\n"
871
                                        "If you decline, any geometry that depends on "
872
                                        "the missing file will be permanently removed."));
873
    dialog->AddButton(C_("button", "&Yes"), MessageDialog::Response::YES,
874
                      /*isDefault=*/true);
875
    dialog->AddButton(C_("button", "&No"), MessageDialog::Response::NO);
876
    if(canCancel) {
877
        dialog->AddButton(C_("button", "&Cancel"), MessageDialog::Response::CANCEL);
878
    }
879

880
    // FIXME(async): asyncify this call
881
    return dialog->RunModal();
882
}
883

884
bool SolveSpaceUI::ReloadAllLinked(const Platform::Path &saveFile, bool canCancel) {
885
    Platform::SettingsRef settings = Platform::GetSettings();
886

887
    std::map<Platform::Path, Platform::Path, Platform::PathLess> linkMap;
888

889
    allConsistent = false;
890

891
    for(Group &g : SK.group) {
892
        if(g.type != Group::Type::LINKED) continue;
893

894
        g.impEntity.Clear();
895
        g.impMesh.Clear();
896
        g.impShell.Clear();
897

898
        // If we prompted for this specific file before, don't ask again.
899
        if(linkMap.count(g.linkFile)) {
900
            g.linkFile = linkMap[g.linkFile];
901
        }
902

903
try_again:
904
        if(LoadEntitiesFromFile(g.linkFile, &g.impEntity, &g.impMesh, &g.impShell)) {
905
            // We loaded the data, good. Now import its dependencies as well.
906
            for(Entity &e : g.impEntity) {
907
                if(e.type != Entity::Type::IMAGE) continue;
908
                if(!ReloadLinkedImage(g.linkFile, &e.file, canCancel)) {
909
                    return false;
910
                }
911
            }
912
            if(g.IsTriangleMeshAssembly())
913
                g.forceToMesh = true;
914
        } else if(linkMap.count(g.linkFile) == 0) {
915
            dbp("Missing file for group: %s", g.name.c_str());
916
            // The file was moved; prompt the user for its new location.
917
            const auto linkFileRelative = g.linkFile.RelativeTo(saveFile);
918
            switch(LocateImportedFile(linkFileRelative, canCancel)) {
919
                case Platform::MessageDialog::Response::YES: {
920
                    Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
921
                    dialog->AddFilters(Platform::SolveSpaceLinkFileFilters);
922
                    dialog->ThawChoices(settings, "LinkSketch");
923
                    dialog->SuggestFilename(linkFileRelative);
924
                    if(dialog->RunModal()) {
925
                        dialog->FreezeChoices(settings, "LinkSketch");
926
                        linkMap[g.linkFile] = dialog->GetFilename();
927
                        g.linkFile = dialog->GetFilename();
928
                        goto try_again;
929
                    } else {
930
                        if(canCancel) return false;
931
                        break;
932
                    }
933
                }
934

935
                case Platform::MessageDialog::Response::NO:
936
                    linkMap[g.linkFile].Clear();
937
                    // Geometry will be pruned by GenerateAll().
938
                    break;
939

940
                case Platform::MessageDialog::Response::CANCEL:
941
                    return false;
942

943
                default:
944
                    ssassert(false, "Unexpected dialog response");
945
            }
946
        } else {
947
            // User was already asked to and refused to locate a missing linked file.
948
        }
949
    }
950

951
    for(Request &r : SK.request) {
952
        if(r.type != Request::Type::IMAGE) continue;
953

954
        if(!ReloadLinkedImage(saveFile, &r.file, canCancel)) {
955
            return false;
956
        }
957
    }
958

959
    return true;
960
}
961

962
bool SolveSpaceUI::ReloadLinkedImage(const Platform::Path &saveFile,
963
                                     Platform::Path *filename, bool canCancel) {
964
    Platform::SettingsRef settings = Platform::GetSettings();
965

966
    std::shared_ptr<Pixmap> pixmap;
967
    bool promptOpenFile = false;
968
    if(filename->IsEmpty()) {
969
        // We're prompting the user for a new image.
970
        promptOpenFile = true;
971
    } else {
972
        auto image = SS.images.find(*filename);
973
        if(image != SS.images.end()) return true;
974

975
        pixmap = Pixmap::ReadPng(*filename);
976
        if(pixmap == NULL) {
977
            // The file was moved; prompt the user for its new location.
978
            switch(LocateImportedFile(filename->RelativeTo(saveFile), canCancel)) {
979
                case Platform::MessageDialog::Response::YES:
980
                    promptOpenFile = true;
981
                    break;
982

983
                case Platform::MessageDialog::Response::NO:
984
                    // We don't know where the file is, record it as absent.
985
                    break;
986

987
                case Platform::MessageDialog::Response::CANCEL:
988
                    return false;
989

990
                default:
991
                    ssassert(false, "Unexpected dialog response");
992
            }
993
        }
994
    }
995

996
    if(promptOpenFile) {
997
        Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
998
        dialog->AddFilters(Platform::RasterFileFilters);
999
        dialog->ThawChoices(settings, "LinkImage");
1000
        dialog->SuggestFilename(filename->RelativeTo(saveFile));
1001
        if(dialog->RunModal()) {
1002
            dialog->FreezeChoices(settings, "LinkImage");
1003
            *filename = dialog->GetFilename();
1004
            pixmap = Pixmap::ReadPng(*filename);
1005
            if(pixmap == NULL) {
1006
                Error("The image '%s' is corrupted.", filename->raw.c_str());
1007
            }
1008
            // We know where the file is now, good.
1009
        } else if(canCancel) {
1010
            return false;
1011
        }
1012
    }
1013

1014
    // We loaded the data, good.
1015
    SS.images[*filename] = pixmap;
1016
    return true;
1017
}
1018

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.