2 // fileio.cpp: Architektonas file save/load support
4 // Part of the Architektonas Project
5 // (C) 2013 Underground Software
6 // See the README and GPLv3 files for licensing and warranty information
8 // JLH = James Hammons <jlhamm@acm.org>
11 // --- ---------- ------------------------------------------------------------
12 // JLH 02/20/2013 Created this file
25 How to handle connected objects
26 -------------------------------
28 Every Object has a vector<Object *> which enumerates all Objects connected to
29 the one we're looking at. So it looks like we'll have to take a two pass
30 approach to loading and saving.
32 Basically, in the saving case, first we write out all objects, keeping a
33 pointer-to-index-number record. Second, we loop through all the objects we
34 wrote out, writing connection lists. Format (indices are 1-based):
42 In the reading case, we do pretty much the same: we construct pointer-to-index-
43 number list, then read the connection list. The PTIN connects the index to the
44 Object pointers we've created. Then we simply call the Object's Connect()
45 function to connect the objects.
47 Small problem though: How does a Dimension know which points on a Line it's
48 connected to? This approach tells the Dimension it's connected to the Line and
49 the Line that it's connected to the Dimension, but not which points.
51 How to handle them then? Do we list the point with the Object pointed at? A
52 Line can contain an infinite number of points to connect with besides its
55 So with each connection Object in the vector, there would also have to be a
56 corresponding point to go with it, that would be gotten from the other Object's
57 Connect() function. Or, instead of a point, a parameter value?
59 Doing that, with the Line and a parameter "t", if t == 0 we have endpoint 1.
60 if t == 1, then we have endpoint 2. With a Circle, the parameter is a number
61 between 0 and 1 (scaled to 0 to 2π). With an Arc, the parameter goes from 0 to
62 1, 0 being enpoint 1 and 1 being endpoint 2.
64 How does this work for moving objects that are connected? Again, with the Line
65 and Dimension. The Line's connections looks like this:
70 Dimension looks like this:
72 Object *: line1, t = 0
73 Object *: line1, t = 1
75 For Dimensions, it can query the connected object (if any) using something like
76 GetPointForParameter(). That way it can figure out where its endpoints are. If
77 there is no connected point, then it uses its internal point.
80 Dimensions are special cases of lines: They have exactly *two* points and none
81 in between. Therefore, the Dimension object only needs to have two points. But
82 those points can be connected to multiple objects. The can also be connected to
83 no points/Objects too.
85 How to describe them and their connections (or lack thereof)?
87 Would have to be a 2nd pass, after all objects have been written out in order.
88 Then you could do something like:
91 8 (the Dimension #): 1 (the Object # for point 1) 1 (the Object # for point 2)
96 Connection attributes: E.g., between a Line a Circle, it can be tangent,
97 perpendicular, or an arbitrary angle. How to encode that information? It's not
98 intrinsic to either the Line or the Circle, but is a function of the
99 relationship between them by virtue of their connection.
102 OTHER CONSIDERATIONS:
103 ---------------------
105 - Need to figure out how to store the Layer list (should layer list be optional?)
106 - Need to figure out how to store the Block list and blocks
111 //enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFLine, OTFCircle, OTFArc,
112 // OTFDimension, OTFPolygon, OTFText, OTFImage, OTFBlock, OTFEndOfFile };
113 enum ObjectTypeFile { OTFContainer, OTFContainerEnd, OTFObject, OTFEndOfFile };
116 // Instantiate class variables
117 /*static*/ int FileIO::objectFileType = OTFObject;
120 /*static*/ bool FileIO::SaveAtnsFile(FILE * file, Container * c)
122 /* Approach: loop through the container, doing a depth-first traversal. Any
123 extra containers found are looped through until there aren't any more
124 down, then ordinary objects are described. This can be handled by a
125 virtual Object function that reports the object by itself if it's a non-
126 Container, otherwise it enumerates all objects within itself. */
128 fprintf(file, "ARCHITEKTONAS DRAWING V1.1\n");
130 object->Enumerate(file);
132 WriteObjectToFile(file, (Object *)c);
134 fprintf(file, "END\n");
139 /*static*/ bool FileIO::LoadAtnsFile(FILE * file, Container * drawing)
143 fscanf(file, "ARCHITEKTONAS DRAWING V%f", &version);
145 //printf("Load: version = %f\n", version);
146 // if (version != 1.0)
150 return LoadVersion1_0(file, drawing);
151 else if (version == 1.1)
152 return LoadVersion1_1(file, drawing);
155 /* Approach: read each object in the file, one by one. If the object is a
156 Container, add objects to it until an "endContainer" marker is found.
157 This will require a stack to maintain the current Container. */
160 std::vector<Container *> containerStack;
161 Container * currentTopContainer = drawing;//new Container(Vector(0, 0));
163 // ObjectType objectType;
168 if (FileIO::GetObjectFromFile(file, currentTopContainer, &object, &objectType) == false)
171 // object->type down below can be replaced with objType.
172 // Above could be: bool FileIO::GetObjectFromFile(FILE *, Object *,
173 // ObjectType *); where the return value tells if it's a valid object,
174 // Object * returns the reconstructed object and ObjectType * returns
177 if (objectType == OTFEndOfFile)
179 //printf("Load: container size = %li\n", drawing->objects.size());
182 else if (objectType == OTFContainer)
184 containerStack.push_back(currentTopContainer);
185 currentTopContainer = new Container(Vector(0, 0), currentTopContainer);
187 else if (objectType == OTFContainerEnd)
189 Container * containerToAdd = currentTopContainer;
190 currentTopContainer = containerStack.back();
191 containerStack.pop_back();
192 currentTopContainer->Add(containerToAdd);
196 currentTopContainer->Add(object);
197 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
208 /*static*/ bool FileIO::LoadVersion1_0(FILE * file, Container * drawing)
210 // Approach: read each object in the file, one by one. If the object is a
211 // Container, add objects to it until an "endContainer" marker is found.
212 // This will require a stack to maintain the current Container.
213 std::vector<Container *> containerStack;
214 Container * currentTopContainer = drawing;
218 // Reconstruct the object
219 Object * obj = GetObjectFromFile(file);
221 // objectFileType is set in GetObjectFromFile()...
222 if (objectFileType == OTFObject)
227 currentTopContainer->objects.push_back(obj);
228 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
230 // If the object is a container, push current TLC on the stack and
231 // set it as the new TLC
232 if (obj->type == OTContainer)
234 containerStack.push_back(currentTopContainer);
235 currentTopContainer = (Container *)obj;
238 else if (objectFileType == OTFContainerEnd)
240 // Container is done, so pop the stack to get back the previous TLC
241 currentTopContainer = containerStack.back();
242 containerStack.pop_back();
244 else if (objectFileType == OTFEndOfFile)
246 //printf("Load: container size = %li\n", drawing->objects.size());
255 /*static*/ bool FileIO::LoadVersion1_1(FILE * file, Container * drawing)
257 // Approach: read each object in the file, one by one. If the object is a
258 // Container, add objects to it until an "endContainer" marker is found.
259 // This will require a stack to maintain the current Container.
260 std::vector<Container *> containerStack;
261 Container * currentTopContainer = drawing;
265 // Reconstruct the object (extended format!)
266 Object * obj = GetObjectFromFile(file, true);
268 // objectFileType is set in GetObjectFromFile()...
269 if (objectFileType == OTFObject)
274 currentTopContainer->objects.push_back(obj);
275 //printf("Load: Adding object. Container size = %li (%li)\n", drawing->objects.size(), currentTopContainer->objects.size());
277 // If the object is a container, push current TLC on the stack and
278 // set it as the new TLC
279 if (obj->type == OTContainer)
281 containerStack.push_back(currentTopContainer);
282 currentTopContainer = (Container *)obj;
285 else if (objectFileType == OTFContainerEnd)
287 // Container is done, so pop the stack to get back the previous TLC
288 currentTopContainer = containerStack.back();
289 containerStack.pop_back();
291 else if (objectFileType == OTFEndOfFile)
293 //printf("Load: container size = %li\n", drawing->objects.size());
302 /*static*/ Object * FileIO::GetObjectFromFile(FILE * file, bool extended/*= false*/)
306 /*int num =*/ fscanf(file, "%s", buffer);
308 objectFileType = OTFObject;
309 //printf("FileIO: fscanf returned %i, buffer = \"%s\"\n", num, buffer);
311 // The following fugliness is for troubleshooting. Can remove later.
312 if ((strcmp(buffer, "END") != 0) && (strcmp(buffer, "ENDCONTAINER") != 0))
315 /*num =*/ fscanf(file, " %i ", &foundLayer);
316 //printf("FileIO: fscanf returned %i, foundLayer = %i\n", num, foundLayer);
321 else if (errno == EBADF)
323 else if (errno == EILSEQ)
325 else if (errno == EINTR)
327 else if (errno == EINVAL)
329 else if (errno == ENOMEM)
331 else if (errno == ERANGE)
334 printf("errno = %i\n", errno);
338 // Need to add pen attributes as well... do that for v1.1 :-P
339 if (strcmp(buffer, "LINE") == 0)
342 fscanf(file, "(%lf,%lf) (%lf,%lf)", &p1.x, &p1.y, &p2.x, &p2.y);
343 obj = (Object *)new Line(p1, p2);
345 else if (strcmp(buffer, "CIRCLE") == 0)
349 fscanf(file, "(%lf,%lf) %lf", &p.x, &p.y, &r);
350 obj = (Object *)new Circle(p, r);
352 else if (strcmp(buffer, "ARC") == 0)
356 fscanf(file, "(%lf,%lf) %lf, %lf, %lf", &p.x, &p.y, &r, &a1, &a2);
357 obj = (Object *)new Arc(p, r, a1, a2);
359 else if (strcmp(buffer, "DIMENSION") == 0)
363 fscanf(file, "(%lf,%lf) (%lf,%lf) %i", &p1.x, &p1.y, &p2.x, &p2.y, (int *)&type);
364 obj = (Object *)new Dimension(p1, p2, type);
366 else if (strcmp(buffer, "CONTAINER") == 0)
368 obj = (Object *)new Container();
369 // objectFileType = OTFContainer;
371 else if (strcmp(buffer, "ENDCONTAINER") == 0)
373 objectFileType = OTFContainerEnd;
375 else if (strcmp(buffer, "END") == 0)
377 objectFileType = OTFEndOfFile;
382 obj->layer = foundLayer;
384 if (extended && (obj->type != OTContainer))
386 fscanf(file, " (%i, %f, %i)", &obj->color, &obj->thickness, &obj->style);
394 /*static*/ bool FileIO::WriteObjectToFile(FILE * file, Object * obj)
403 fprintf(file, "LINE %i (%lf,%lf) (%lf,%lf)", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y);
406 fprintf(file, "CIRCLE %i (%lf,%lf) %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0]);
411 fprintf(file, "ARC %i (%lf,%lf) %lf, %lf, %lf", obj->layer, obj->p[0].x, obj->p[0].y, obj->radius[0], obj->angle[0], obj->angle[1]);
416 // fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i\n", layer, position.x, position.y, endpoint.x, endpoint.y, dimensionType);
417 fprintf(file, "DIMENSION %i (%lf,%lf) (%lf,%lf) %i", obj->layer, obj->p[0].x, obj->p[0].y, obj->p[1].x, obj->p[1].y, ((Dimension *)obj)->subtype);
422 fprintf(file, "TEXT %i (%lf,%lf) \"%s\"", obj->layer, obj->p[0].x, obj->p[0].y, ((Text *)obj)->s.c_str());
426 Container * c = (Container *)obj;
428 if (c->topLevel == false)
429 fprintf(file, "CONTAINER %i\n", obj->layer);
431 std::vector<void *>::iterator i;
433 for(i=c->objects.begin(); i!=c->objects.end(); i++)
434 WriteObjectToFile(file, (Object *)*i);
436 if (c->topLevel == false)
437 fprintf(file, "ENDCONTAINER\n");
445 if (obj->type != OTContainer)
446 fprintf(file, " (%i, %f, %i)\n", obj->color, obj->thickness, obj->style);