FreeCAD

Форк
0
/
Document.py 
2605 строк · 101.4 Кб
1
# ***************************************************************************
2
# *   Copyright (c) 2003 Juergen Riegel <juergen.riegel@web.de>             *
3
# *                                                                         *
4
# *   This file is part of the FreeCAD CAx development system.              *
5
# *                                                                         *
6
# *   This program is free software; you can redistribute it and/or modify  *
7
# *   it under the terms of the GNU Lesser General Public License (LGPL)    *
8
# *   as published by the Free Software Foundation; either version 2 of     *
9
# *   the License, or (at your option) any later version.                   *
10
# *   for detail see the LICENCE text file.                                 *
11
# *                                                                         *
12
# *   FreeCAD is distributed in the hope that it will be useful,            *
13
# *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
14
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
15
# *   GNU Library General Public License for more details.                  *
16
# *                                                                         *
17
# *   You should have received a copy of the GNU Library General Public     *
18
# *   License along with FreeCAD; if not, write to the Free Software        *
19
# *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
20
# *   USA                                                                   *
21
# *                                                                         *
22
# ***************************************************************************/
23

24
import FreeCAD, os, unittest, tempfile
25
from FreeCAD import Base
26
import math
27
import xml.etree.ElementTree as ET
28

29
# ---------------------------------------------------------------------------
30
# define the functions to test the FreeCAD Document code
31
# ---------------------------------------------------------------------------
32

33

34
class Proxy:
35
    def __init__(self, obj):
36
        self.Dictionary = {}
37
        self.obj = obj
38
        obj.Proxy = self
39

40
    def dumps(self):
41
        return self.Dictionary
42

43
    def loads(self, data):
44
        self.Dictionary = data
45

46

47
class DocumentBasicCases(unittest.TestCase):
48
    def setUp(self):
49
        self.Doc = FreeCAD.newDocument("CreateTest")
50

51
    def saveAndRestore(self):
52
        # saving and restoring
53
        SaveName = tempfile.gettempdir() + os.sep + "CreateTest.FCStd"
54
        self.Doc.saveAs(SaveName)
55
        FreeCAD.closeDocument("CreateTest")
56
        self.Doc = FreeCAD.open(SaveName)
57
        return self.Doc
58

59
    def testAccessByNameOrID(self):
60
        obj = self.Doc.addObject("App::DocumentObject", "MyName")
61

62
        with self.assertRaises(TypeError):
63
            self.Doc.getObject([1])
64

65
        self.assertEqual(self.Doc.getObject(obj.Name), obj)
66
        self.assertEqual(self.Doc.getObject("Unknown"), None)
67
        self.assertEqual(self.Doc.getObject(obj.ID), obj)
68
        self.assertEqual(self.Doc.getObject(obj.ID + 1), None)
69

70
    def testCreateDestroy(self):
71
        # FIXME: Causes somehow a ref count error but it's _not_ FreeCAD.getDocument()!!!
72
        # If we remove the whole method no error appears.
73
        self.assertTrue(FreeCAD.getDocument("CreateTest") is not None, "Creating Document failed")
74

75
    def testAddition(self):
76
        # Cannot write a real test case for that but when debugging the
77
        # C-code there shouldn't be a memory leak (see rev. 1814)
78
        self.Doc.openTransaction("Add")
79
        L1 = self.Doc.addObject("App::FeatureTest", "Label")
80
        self.Doc.commitTransaction()
81
        self.Doc.undo()
82

83
    def testAddRemoveUndo(self):
84
        # Bug #0000525
85
        self.Doc.openTransaction("Add")
86
        obj = self.Doc.addObject("App::FeatureTest", "Label")
87
        self.Doc.commitTransaction()
88
        self.Doc.removeObject(obj.Name)
89
        self.Doc.undo()
90
        self.Doc.undo()
91

92
    def testNoRecompute(self):
93
        L1 = self.Doc.addObject("App::FeatureTest", "Label")
94
        self.Doc.recompute()
95
        L1.TypeNoRecompute = 2
96
        execcount = L1.ExecCount
97
        objectcount = self.Doc.recompute()
98
        self.assertEqual(objectcount, 0)
99
        self.assertEqual(L1.ExecCount, execcount)
100

101
    def testNoRecomputeParent(self):
102
        L1 = self.Doc.addObject("App::FeatureTest", "Child")
103
        L2 = self.Doc.addObject("App::FeatureTest", "Parent")
104
        L2.Source1 = L1
105
        self.Doc.recompute()
106
        L1.TypeNoRecompute = 2
107
        countChild = L1.ExecCount
108
        countParent = L2.ExecCount
109
        objectcount = self.Doc.recompute()
110
        self.assertEqual(objectcount, 1)
111
        self.assertEqual(L1.ExecCount, countChild)
112
        self.assertEqual(L2.ExecCount, countParent + 1)
113

114
        L1.touch("")
115
        countChild = L1.ExecCount
116
        countParent = L2.ExecCount
117
        objectcount = self.Doc.recompute()
118
        self.assertEqual(objectcount, 1)
119
        self.assertEqual(L1.ExecCount, countChild)
120
        self.assertEqual(L2.ExecCount, countParent + 1)
121

122
        L1.enforceRecompute()
123
        countChild = L1.ExecCount
124
        countParent = L2.ExecCount
125
        objectcount = self.Doc.recompute()
126
        self.assertEqual(objectcount, 2)
127
        self.assertEqual(L1.ExecCount, countChild + 1)
128
        self.assertEqual(L2.ExecCount, countParent + 1)
129

130
    def testAbortTransaction(self):
131
        self.Doc.openTransaction("Add")
132
        obj = self.Doc.addObject("App::FeatureTest", "Label")
133
        self.Doc.abortTransaction()
134
        TempPath = tempfile.gettempdir()
135
        SaveName = TempPath + os.sep + "SaveRestoreTests.FCStd"
136
        self.Doc.saveAs(SaveName)
137

138
    def testRemoval(self):
139
        # Cannot write a real test case for that but when debugging the
140
        # C-code there shouldn't be a memory leak (see rev. 1814)
141
        self.Doc.openTransaction("Add")
142
        L1 = self.Doc.addObject("App::FeatureTest", "Label")
143
        self.Doc.commitTransaction()
144
        self.Doc.openTransaction("Rem")
145
        L1 = self.Doc.removeObject("Label")
146
        self.Doc.commitTransaction()
147

148
    def testObjects(self):
149
        L1 = self.Doc.addObject("App::FeatureTest", "Label_1")
150
        # call members to check for errors in ref counting
151
        self.Doc.ActiveObject
152
        self.Doc.Objects
153
        self.Doc.UndoMode
154
        self.Doc.UndoRedoMemSize
155
        self.Doc.UndoCount
156
        # test read only mechanismus
157
        try:
158
            self.Doc.UndoCount = 3
159
        except Exception:
160
            FreeCAD.Console.PrintLog("   exception thrown, OK\n")
161
        else:
162
            self.fail("no exception thrown")
163
        self.Doc.RedoCount
164
        self.Doc.UndoNames
165
        self.Doc.RedoNames
166
        self.Doc.recompute()
167
        self.assertTrue(L1.Integer == 4711)
168
        self.assertTrue(L1.Float - 47.11 < 0.001)
169
        self.assertTrue(L1.Bool == True)
170
        self.assertTrue(L1.String == "4711")
171
        # temporarily not checked because of strange behavior of boost::filesystem JR
172
        # self.assertTrue(L1.Path  == "c:/temp")
173
        self.assertTrue(float(L1.Angle) - 3.0 < 0.001)
174
        self.assertTrue(float(L1.Distance) - 47.11 < 0.001)
175

176
        # test basic property stuff
177
        self.assertTrue(not L1.getDocumentationOfProperty("Source1") == "")
178
        self.assertTrue(L1.getGroupOfProperty("Source1") == "Feature Test")
179
        self.assertTrue(L1.getTypeOfProperty("Source1") == [])
180
        self.assertTrue(L1.getEnumerationsOfProperty("Source1") is None)
181

182
        # test the constraint types ( both are constraint to percent range)
183
        self.assertTrue(L1.ConstraintInt == 5)
184
        self.assertTrue(L1.ConstraintFloat - 5.0 < 0.001)
185
        L1.ConstraintInt = 500
186
        L1.ConstraintFloat = 500.0
187
        self.assertTrue(L1.ConstraintInt == 100)
188
        self.assertTrue(L1.ConstraintFloat - 100.0 < 0.001)
189
        L1.ConstraintInt = -500
190
        L1.ConstraintFloat = -500.0
191
        self.assertTrue(L1.ConstraintInt == 0)
192
        self.assertTrue(L1.ConstraintFloat - 0.0 < 0.001)
193

194
        # test enum property
195
        # in App::FeatureTest the current value is set to 4
196
        self.assertTrue(L1.Enum == "Four")
197
        L1.Enum = "Three"
198
        self.assertTrue(L1.Enum == "Three", "Different value to 'Three'")
199
        L1.Enum = 2
200
        self.assertTrue(L1.Enum == "Two", "Different value to 'Two'")
201
        try:
202
            L1.Enum = "SurelyNotInThere!"
203
        except Exception:
204
            FreeCAD.Console.PrintLog("   exception thrown, OK\n")
205
        else:
206
            self.fail("no exception thrown")
207
        self.assertTrue(
208
            sorted(L1.getEnumerationsOfProperty("Enum"))
209
            == sorted(["Zero", "One", "Two", "Three", "Four"])
210
        )
211

212
        # self.assertTrue(L1.IntegerList  == [4711]   )
213
        # f = L1.FloatList
214
        # self.assertTrue(f -47.11<0.001    )
215
        # self.assertTrue(L1.Matrix  == [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0] )
216
        # self.assertTrue(L1.Vector  == [1.0,2.0,3.0])
217

218
        self.assertTrue(L1.Label == "Label_1", "Invalid object name")
219
        L1.Label = "Label_2"
220
        self.Doc.recompute()
221
        self.assertTrue(L1.Label == "Label_2", "Invalid object name")
222
        self.Doc.removeObject("Label_1")
223

224
    def testEnum(self):
225
        enumeration_choices = ["one", "two"]
226
        obj = self.Doc.addObject("App::FeaturePython", "Label_2")
227
        obj.addProperty("App::PropertyEnumeration", "myEnumeration", "Enum", "mytest")
228
        with self.assertRaises(ValueError):
229
            obj.myEnumeration = enumeration_choices[0]
230

231
        obj.myEnumeration = enumeration_choices
232
        obj.myEnumeration = 0
233
        self.Doc.openTransaction("Modify enum")
234
        obj.myEnumeration = 1
235
        self.assertTrue(obj.myEnumeration, enumeration_choices[1])
236
        self.Doc.commitTransaction()
237
        self.Doc.undo()
238
        self.assertTrue(obj.myEnumeration, enumeration_choices[0])
239

240
    def testWrongTypes(self):
241
        with self.assertRaises(TypeError):
242
            self.Doc.addObject("App::DocumentObjectExtension")
243

244
        class Feature:
245
            pass
246

247
        with self.assertRaises(TypeError):
248
            self.Doc.addObject(type="App::DocumentObjectExtension", objProxy=Feature(), attach=True)
249

250
        ext = FreeCAD.Base.TypeId.fromName("App::DocumentObjectExtension")
251
        self.assertEqual(ext.createInstance(), None)
252

253
        obj = self.Doc.addObject("App::FeaturePython", "Object")
254
        with self.assertRaises(TypeError):
255
            obj.addProperty("App::DocumentObjectExtension", "Property")
256

257
        with self.assertRaises(TypeError):
258
            self.Doc.findObjects(Type="App::DocumentObjectExtension")
259

260
        e = FreeCAD.Base.TypeId.fromName("App::LinkExtensionPython")
261
        self.assertIsNone(e.createInstance())
262

263
        if FreeCAD.GuiUp:
264
            obj = self.Doc.addObject("App::DocumentObject", viewType="App::Extension")
265
            self.assertIsNone(obj.ViewObject)
266

267
    def testMem(self):
268
        self.Doc.MemSize
269

270
    def testDuplicateLinks(self):
271
        obj = self.Doc.addObject("App::FeatureTest", "obj")
272
        grp = self.Doc.addObject("App::DocumentObjectGroup", "group")
273
        grp.Group = [obj, obj]
274
        self.Doc.removeObject(obj.Name)
275
        self.assertListEqual(grp.Group, [])
276

277
    def testPlacementList(self):
278
        obj = self.Doc.addObject("App::FeaturePython", "Label")
279
        obj.addProperty("App::PropertyPlacementList", "PlmList")
280
        plm = FreeCAD.Placement()
281
        plm.Base = (1, 2, 3)
282
        plm.Rotation = (0, 0, 1, 0)
283
        obj.PlmList = [plm]
284
        cpy = self.Doc.copyObject(obj)
285
        self.assertListEqual(obj.PlmList, cpy.PlmList)
286

287
    def testRawAxis(self):
288
        obj = self.Doc.addObject("App::FeaturePython", "Label")
289
        obj.addProperty("App::PropertyPlacement", "Plm")
290
        obj.addProperty("App::PropertyRotation", "Rot")
291
        obj.Plm.Rotation.Axis = (1, 2, 3)
292
        obj.Rot.Axis = (3, 2, 1)
293

294
        # saving and restoring
295
        SaveName = tempfile.gettempdir() + os.sep + "CreateTest.FCStd"
296
        self.Doc.saveAs(SaveName)
297
        FreeCAD.closeDocument("CreateTest")
298
        self.Doc = FreeCAD.open(SaveName)
299
        obj = self.Doc.ActiveObject
300

301
        self.assertEqual(obj.Plm.Rotation.RawAxis.x, 1)
302
        self.assertEqual(obj.Plm.Rotation.RawAxis.y, 2)
303
        self.assertEqual(obj.Plm.Rotation.RawAxis.z, 3)
304

305
        self.assertEqual(obj.Rot.RawAxis.x, 3)
306
        self.assertEqual(obj.Rot.RawAxis.y, 2)
307
        self.assertEqual(obj.Rot.RawAxis.z, 1)
308

309
    def testAddRemove(self):
310
        L1 = self.Doc.addObject("App::FeatureTest", "Label_1")
311
        # must delete object
312
        self.Doc.removeObject(L1.Name)
313
        try:
314
            L1.Name
315
        except Exception:
316
            self.assertTrue(True)
317
        else:
318
            self.assertTrue(False)
319
        del L1
320

321
        # What do we expect here?
322
        self.Doc.openTransaction("AddRemove")
323
        L2 = self.Doc.addObject("App::FeatureTest", "Label_2")
324
        self.Doc.removeObject(L2.Name)
325
        self.Doc.commitTransaction()
326
        self.Doc.undo()
327
        try:
328
            L2.Name
329
        except Exception:
330
            self.assertTrue(True)
331
        else:
332
            self.assertTrue(False)
333
        del L2
334

335
    def testSubObject(self):
336
        obj = self.Doc.addObject("App::Origin", "Origin")
337
        self.Doc.recompute()
338

339
        res = obj.getSubObject("X_Axis", retType=2)
340
        self.assertEqual(
341
            res[1].multVec(FreeCAD.Vector(1, 0, 0)).getAngle(FreeCAD.Vector(1, 0, 0)), 0.0
342
        )
343

344
        res = obj.getSubObject("Y_Axis", retType=2)
345
        self.assertEqual(
346
            res[1].multVec(FreeCAD.Vector(1, 0, 0)).getAngle(FreeCAD.Vector(0, 1, 0)), 0.0
347
        )
348

349
        res = obj.getSubObject("Z_Axis", retType=2)
350
        self.assertEqual(
351
            res[1].multVec(FreeCAD.Vector(1, 0, 0)).getAngle(FreeCAD.Vector(0, 0, 1)), 0.0
352
        )
353

354
        res = obj.getSubObject("XY_Plane", retType=2)
355
        self.assertEqual(
356
            res[1].multVec(FreeCAD.Vector(0, 0, 1)).getAngle(FreeCAD.Vector(0, 0, 1)), 0.0
357
        )
358

359
        res = obj.getSubObject("XZ_Plane", retType=2)
360
        self.assertEqual(
361
            res[1].multVec(FreeCAD.Vector(0, 0, 1)).getAngle(FreeCAD.Vector(0, -1, 0)), 0.0
362
        )
363

364
        res = obj.getSubObject("YZ_Plane", retType=2)
365
        self.assertEqual(
366
            res[1].multVec(FreeCAD.Vector(0, 0, 1)).getAngle(FreeCAD.Vector(1, 0, 0)), 0.0
367
        )
368

369
        res = obj.getSubObject("YZ_Plane", retType=3)
370
        self.assertEqual(
371
            res.multVec(FreeCAD.Vector(0, 0, 1)).getAngle(FreeCAD.Vector(1, 0, 0)), 0.0
372
        )
373

374
        res = obj.getSubObject("YZ_Plane", retType=4)
375
        self.assertEqual(
376
            res.multVec(FreeCAD.Vector(0, 0, 1)).getAngle(FreeCAD.Vector(1, 0, 0)), 0.0
377
        )
378

379
        self.assertEqual(
380
            obj.getSubObject(("XY_Plane", "YZ_Plane"), retType=4)[0],
381
            obj.getSubObject("XY_Plane", retType=4),
382
        )
383
        self.assertEqual(
384
            obj.getSubObject(("XY_Plane", "YZ_Plane"), retType=4)[1],
385
            obj.getSubObject("YZ_Plane", retType=4),
386
        )
387

388
        # Create a second origin object
389
        obj2 = self.Doc.addObject("App::Origin", "Origin2")
390
        self.Doc.recompute()
391

392
        # Use the names of the origin's out-list
393
        for i in obj2.OutList:
394
            self.assertEqual(obj2.getSubObject(i.Name, retType=1).Name, i.Name)
395
        # Add a '.' to the names
396
        for i in obj2.OutList:
397
            self.assertEqual(obj2.getSubObject(i.Name + ".", retType=1).Name, i.Name)
398

399
    def testExtensions(self):
400
        # we try to create a normal python object and add an extension to it
401
        obj = self.Doc.addObject("App::DocumentObject", "Extension_1")
402
        grp = self.Doc.addObject("App::DocumentObject", "Extension_2")
403
        # we should have all methods we need to handle extensions
404
        try:
405
            self.assertTrue(not grp.hasExtension("App::GroupExtensionPython"))
406
            grp.addExtension("App::GroupExtensionPython")
407
            self.assertTrue(grp.hasExtension("App::GroupExtension"))
408
            self.assertTrue(grp.hasExtension("App::GroupExtensionPython"))
409
            grp.addObject(obj)
410
            self.assertTrue(len(grp.Group) == 1)
411
            self.assertTrue(grp.Group[0] == obj)
412
        except Exception:
413
            self.assertTrue(False)
414

415
        # test if the method override works
416
        class SpecialGroup:
417
            def allowObject(self, obj):
418
                return False
419

420
        callback = SpecialGroup()
421
        grp2 = self.Doc.addObject("App::FeaturePython", "Extension_3")
422
        grp2.addExtension("App::GroupExtensionPython")
423
        grp2.Proxy = callback
424

425
        try:
426
            self.assertTrue(grp2.hasExtension("App::GroupExtension"))
427
            grp2.addObject(obj)
428
            self.assertTrue(len(grp2.Group) == 0)
429
        except Exception:
430
            self.assertTrue(True)
431

432
        self.Doc.removeObject(grp.Name)
433
        self.Doc.removeObject(grp2.Name)
434
        self.Doc.removeObject(obj.Name)
435
        del obj
436
        del grp
437
        del grp2
438

439
    def testExtensionBug0002785(self):
440
        class MyExtension:
441
            def __init__(self, obj):
442
                obj.addExtension("App::GroupExtensionPython")
443

444
        obj = self.Doc.addObject("App::DocumentObject", "myObj")
445
        MyExtension(obj)
446
        self.assertTrue(obj.hasExtension("App::GroupExtension"))
447
        self.assertTrue(obj.hasExtension("App::GroupExtensionPython"))
448
        self.Doc.removeObject(obj.Name)
449
        del obj
450

451
    def testExtensionGroup(self):
452
        obj = self.Doc.addObject("App::DocumentObject", "Obj")
453
        grp = self.Doc.addObject("App::FeaturePython", "Extension_2")
454
        grp.addExtension("App::GroupExtensionPython")
455
        grp.Group = [obj]
456
        self.assertTrue(obj in grp.Group)
457

458
    def testExtensionBugViewProvider(self):
459
        class Layer:
460
            def __init__(self, obj):
461
                obj.addExtension("App::GroupExtensionPython")
462

463
        class LayerViewProvider:
464
            def __init__(self, obj):
465
                obj.addExtension("Gui::ViewProviderGroupExtensionPython")
466
                obj.Proxy = self
467

468
        obj = self.Doc.addObject("App::FeaturePython", "Layer")
469
        Layer(obj)
470
        self.assertTrue(obj.hasExtension("App::GroupExtension"))
471

472
        if FreeCAD.GuiUp:
473
            LayerViewProvider(obj.ViewObject)
474
            self.assertTrue(obj.ViewObject.hasExtension("Gui::ViewProviderGroupExtension"))
475
            self.assertTrue(obj.ViewObject.hasExtension("Gui::ViewProviderGroupExtensionPython"))
476

477
        self.Doc.removeObject(obj.Name)
478
        del obj
479

480
    def testHasSelection(self):
481
        if FreeCAD.GuiUp:
482
            import FreeCADGui
483

484
            self.assertFalse(FreeCADGui.Selection.hasSelection("", 1))
485

486
    def testPropertyLink_Issue2902Part1(self):
487
        o1 = self.Doc.addObject("App::FeatureTest", "test1")
488
        o2 = self.Doc.addObject("App::FeatureTest", "test2")
489
        o3 = self.Doc.addObject("App::FeatureTest", "test3")
490

491
        o1.Link = o2
492
        self.assertEqual(o1.Link, o2)
493
        o1.Link = o3
494
        self.assertEqual(o1.Link, o3)
495
        o2.Placement = FreeCAD.Placement()
496
        self.assertEqual(o1.Link, o3)
497

498
    def testProp_NonePropertyLink(self):
499
        obj1 = self.Doc.addObject("App::FeaturePython", "Obj1")
500
        obj2 = self.Doc.addObject("App::FeaturePython", "Obj2")
501
        obj1.addProperty(
502
            "App::PropertyLink",
503
            "Link",
504
            "Base",
505
            "Link to another feature",
506
            FreeCAD.PropertyType.Prop_None,
507
            False,
508
            False,
509
        )
510
        obj1.Link = obj2
511
        self.assertEqual(obj1.MustExecute, True)
512

513
    def testProp_OutputPropertyLink(self):
514
        obj1 = self.Doc.addObject("App::FeaturePython", "Obj1")
515
        obj2 = self.Doc.addObject("App::FeaturePython", "Obj2")
516
        obj1.addProperty(
517
            "App::PropertyLink",
518
            "Link",
519
            "Base",
520
            "Link to another feature",
521
            FreeCAD.PropertyType.Prop_Output,
522
            False,
523
            False,
524
        )
525
        obj1.Link = obj2
526
        self.assertEqual(obj1.MustExecute, False)
527

528
    def testAttributeOfDynamicProperty(self):
529
        obj = self.Doc.addObject("App::FeaturePython", "Obj")
530
        # Prop_NoPersist is the enum with the highest value
531
        max_value = FreeCAD.PropertyType.Prop_NoPersist
532
        list_of_types = []
533
        for i in range(0, max_value + 1):
534
            obj.addProperty("App::PropertyString", "String" + str(i), "", "", i)
535
            list_of_types.append(obj.getTypeOfProperty("String" + str(i)))
536

537
        # saving and restoring
538
        SaveName = tempfile.gettempdir() + os.sep + "CreateTest.FCStd"
539
        self.Doc.saveAs(SaveName)
540
        FreeCAD.closeDocument("CreateTest")
541
        self.Doc = FreeCAD.open(SaveName)
542

543
        obj = self.Doc.ActiveObject
544
        for i in range(0, max_value):
545
            types = obj.getTypeOfProperty("String" + str(i))
546
            self.assertEqual(list_of_types[i], types)
547

548
        # A property with flag Prop_NoPersist won't be saved to the file
549
        with self.assertRaises(AttributeError):
550
            obj.getTypeOfProperty("String" + str(max_value))
551

552
    def testNotification_Issue2902Part2(self):
553
        o = self.Doc.addObject("App::FeatureTest", "test")
554

555
        plm = o.Placement
556
        o.Placement = FreeCAD.Placement()
557
        plm.Base.x = 5
558
        self.assertEqual(o.Placement.Base.x, 0)
559
        o.Placement.Base.x = 5
560
        self.assertEqual(o.Placement.Base.x, 5)
561

562
    def testNotification_Issue2996(self):
563
        if not FreeCAD.GuiUp:
564
            return
565

566
        # works only if Gui is shown
567
        class ViewProvider:
568
            def __init__(self, vobj):
569
                vobj.Proxy = self
570

571
            def attach(self, vobj):
572
                self.ViewObject = vobj
573
                self.Object = vobj.Object
574

575
            def claimChildren(self):
576
                children = [self.Object.Link]
577
                return children
578

579
        obj = self.Doc.addObject("App::FeaturePython", "Sketch")
580
        obj.addProperty("App::PropertyLink", "Link")
581
        ViewProvider(obj.ViewObject)
582

583
        ext = self.Doc.addObject("App::FeatureTest", "Extrude")
584
        ext.Link = obj
585

586
        sli = self.Doc.addObject("App::FeaturePython", "Slice")
587
        sli.addProperty("App::PropertyLink", "Link").Link = ext
588
        ViewProvider(sli.ViewObject)
589

590
        com = self.Doc.addObject("App::FeaturePython", "CompoundFilter")
591
        com.addProperty("App::PropertyLink", "Link").Link = sli
592
        ViewProvider(com.ViewObject)
593

594
        ext.Label = "test"
595

596
        self.assertEqual(ext.Link, obj)
597
        self.assertNotEqual(ext.Link, sli)
598

599
    def testIssue4823(self):
600
        # https://forum.freecad.org/viewtopic.php?f=3&t=52775
601
        # The issue was only visible in GUI mode and it crashed in the tree view
602
        obj = self.Doc.addObject("App::Origin")
603
        self.Doc.removeObject(obj.Name)
604

605
    def testSamePropertyOfLinkAndLinkedObject(self):
606
        # See also https://github.com/FreeCAD/FreeCAD/pull/6787
607
        test = self.Doc.addObject("App::FeaturePython", "Python")
608
        link = self.Doc.addObject("App::Link", "Link")
609
        test.addProperty("App::PropertyFloat", "Test")
610
        link.addProperty("App::PropertyFloat", "Test")
611
        link.LinkedObject = test
612
        # saving and restoring
613
        SaveName = tempfile.gettempdir() + os.sep + "CreateTest.FCStd"
614
        self.Doc.saveAs(SaveName)
615
        FreeCAD.closeDocument("CreateTest")
616
        self.Doc = FreeCAD.open(SaveName)
617
        self.assertIn("Test", self.Doc.Python.PropertiesList)
618
        self.assertIn("Test", self.Doc.Link.PropertiesList)
619

620
    def testNoProxy(self):
621
        test = self.Doc.addObject("App::DocumentObject", "Object")
622
        test.addProperty("App::PropertyPythonObject", "Dictionary")
623
        test.Dictionary = {"Stored data": [3, 5, 7]}
624

625
        doc = self.saveAndRestore()
626
        obj = doc.Object
627

628
        self.assertEqual(obj.Dictionary, {"Stored data": [3, 5, 7]})
629

630
    def testWithProxy(self):
631
        test = self.Doc.addObject("App::FeaturePython", "Python")
632
        proxy = Proxy(test)
633
        proxy.Dictionary["Stored data"] = [3, 5, 7]
634

635
        doc = self.saveAndRestore()
636
        obj = doc.Python.Proxy
637

638
        self.assertEqual(obj.Dictionary, {"Stored data": [3, 5, 7]})
639

640
    def testContent(self):
641
        test = self.Doc.addObject("App::FeaturePython", "Python")
642
        types = Base.TypeId.getAllDerivedFrom("App::Property")
643
        for type in types:
644
            try:
645
                test.addProperty(type.Name, type.Name.replace(":", "_"))
646
                print("Add property type: {}".format(type.Name))
647
            except Exception as e:
648
                pass
649
        root = ET.fromstring(test.Content)
650
        self.assertEqual(root.tag, "Properties")
651

652
    def tearDown(self):
653
        # closing doc
654
        FreeCAD.closeDocument("CreateTest")
655

656

657
# class must be defined in global scope to allow it to be reloaded on document open
658
class SaveRestoreSpecialGroup:
659
    def __init__(self, obj):
660
        obj.addExtension("App::GroupExtensionPython")
661
        obj.Proxy = self
662

663
    def allowObject(self, obj):
664
        return False
665

666

667
# class must be defined in global scope to allow it to be reloaded on document open
668
class SaveRestoreSpecialGroupViewProvider:
669
    def __init__(self, obj):
670
        obj.addExtension("Gui::ViewProviderGroupExtensionPython")
671
        obj.Proxy = self
672

673
    def testFunction(self):
674
        pass
675

676

677
class DocumentSaveRestoreCases(unittest.TestCase):
678
    def setUp(self):
679
        self.Doc = FreeCAD.newDocument("SaveRestoreTests")
680
        L1 = self.Doc.addObject("App::FeatureTest", "Label_1")
681
        L2 = self.Doc.addObject("App::FeatureTest", "Label_2")
682
        L3 = self.Doc.addObject("App::FeatureTest", "Label_3")
683
        self.TempPath = tempfile.gettempdir()
684
        FreeCAD.Console.PrintLog("  Using temp path: " + self.TempPath + "\n")
685

686
    def testSaveAndRestore(self):
687
        # saving and restoring
688
        SaveName = self.TempPath + os.sep + "SaveRestoreTests.FCStd"
689
        self.assertTrue(self.Doc.Label_1.TypeTransient == 4711)
690
        self.Doc.Label_1.TypeTransient = 4712
691
        # setup Linking
692
        self.Doc.Label_1.Link = self.Doc.Label_2
693
        self.Doc.Label_2.Link = self.Doc.Label_3
694
        self.Doc.Label_1.LinkSub = (self.Doc.Label_2, ["Sub1", "Sub2"])
695
        self.Doc.Label_2.LinkSub = (self.Doc.Label_3, ["Sub3", "Sub4"])
696
        # save the document
697
        self.Doc.saveAs(SaveName)
698
        FreeCAD.closeDocument("SaveRestoreTests")
699
        self.Doc = FreeCAD.open(SaveName)
700
        self.assertTrue(self.Doc.Label_1.Integer == 4711)
701
        self.assertTrue(self.Doc.Label_2.Integer == 4711)
702
        # test Linkage
703
        self.assertTrue(self.Doc.Label_1.Link == self.Doc.Label_2)
704
        self.assertTrue(self.Doc.Label_2.Link == self.Doc.Label_3)
705
        self.assertTrue(self.Doc.Label_1.LinkSub == (self.Doc.Label_2, ["Sub1", "Sub2"]))
706
        self.assertTrue(self.Doc.Label_2.LinkSub == (self.Doc.Label_3, ["Sub3", "Sub4"]))
707
        # do NOT save transient properties
708
        self.assertTrue(self.Doc.Label_1.TypeTransient == 4711)
709
        self.assertTrue(self.Doc == FreeCAD.getDocument(self.Doc.Name))
710

711
    def testRestore(self):
712
        Doc = FreeCAD.newDocument("RestoreTests")
713
        Doc.addObject("App::FeatureTest", "Label_1")
714
        # saving and restoring
715
        FileName = self.TempPath + os.sep + "Test2.FCStd"
716
        Doc.saveAs(FileName)
717
        # restore must first clear the current content
718
        Doc.restore()
719
        self.assertTrue(len(Doc.Objects) == 1)
720
        FreeCAD.closeDocument("RestoreTests")
721

722
    def testActiveDocument(self):
723
        # open 2nd doc
724
        Second = FreeCAD.newDocument("Active")
725
        FreeCAD.closeDocument("Active")
726
        try:
727
            # There might be no active document anymore
728
            # This also checks for dangling pointers
729
            Active = FreeCAD.activeDocument()
730
            # Second is still a valid object
731
            self.assertTrue(Second != Active)
732
        except Exception:
733
            # Okay, no document open
734
            self.assertTrue(True)
735

736
    def testExtensionSaveRestore(self):
737
        # saving and restoring
738
        SaveName = self.TempPath + os.sep + "SaveRestoreExtensions.FCStd"
739
        Doc = FreeCAD.newDocument("SaveRestoreExtensions")
740
        # we try to create a normal python object and add an extension to it
741
        obj = Doc.addObject("App::DocumentObject", "Obj")
742
        grp1 = Doc.addObject("App::DocumentObject", "Extension_1")
743
        grp2 = Doc.addObject("App::FeaturePython", "Extension_2")
744

745
        grp1.addExtension("App::GroupExtensionPython")
746
        SaveRestoreSpecialGroup(grp2)
747
        if FreeCAD.GuiUp:
748
            SaveRestoreSpecialGroupViewProvider(grp2.ViewObject)
749
        grp2.Group = [obj]
750

751
        Doc.saveAs(SaveName)
752
        FreeCAD.closeDocument("SaveRestoreExtensions")
753
        Doc = FreeCAD.open(SaveName)
754

755
        self.assertTrue(Doc.Extension_1.hasExtension("App::GroupExtension"))
756
        self.assertTrue(Doc.Extension_2.hasExtension("App::GroupExtension"))
757
        self.assertTrue(Doc.Extension_2.Group[0] is Doc.Obj)
758
        self.assertTrue(hasattr(Doc.Extension_2.Proxy, "allowObject"))
759

760
        if FreeCAD.GuiUp:
761
            self.assertTrue(
762
                Doc.Extension_2.ViewObject.hasExtension("Gui::ViewProviderGroupExtensionPython")
763
            )
764
            self.assertTrue(hasattr(Doc.Extension_2.ViewObject.Proxy, "testFunction"))
765

766
        FreeCAD.closeDocument("SaveRestoreExtensions")
767

768
    def testPersistenceContentDump(self):
769
        # test smallest level... property
770
        self.Doc.Label_1.Vector = (1, 2, 3)
771
        dump = self.Doc.Label_1.dumpPropertyContent("Vector", Compression=9)
772
        self.Doc.Label_2.restorePropertyContent("Vector", dump)
773
        self.assertEqual(self.Doc.Label_1.Vector, self.Doc.Label_2.Vector)
774

775
        # next higher: object
776
        self.Doc.Label_1.Distance = 12
777
        self.Doc.Label_1.String = "test"
778
        dump = self.Doc.Label_1.dumpContent()
779
        self.Doc.Label_3.restoreContent(dump)
780
        self.assertEqual(self.Doc.Label_1.Distance, self.Doc.Label_3.Distance)
781
        self.assertEqual(self.Doc.Label_1.String, self.Doc.Label_3.String)
782

783
        # highest level: document
784
        dump = self.Doc.dumpContent(9)
785
        Doc = FreeCAD.newDocument("DumpTest")
786
        Doc.restoreContent(dump)
787
        self.assertEqual(len(self.Doc.Objects), len(Doc.Objects))
788
        self.assertEqual(self.Doc.Label_1.Distance, Doc.Label_1.Distance)
789
        self.assertEqual(self.Doc.Label_1.String, Doc.Label_1.String)
790
        self.assertEqual(self.Doc.Label_1.Vector, Doc.Label_1.Vector)
791
        FreeCAD.closeDocument("DumpTest")
792

793
    def tearDown(self):
794
        # closing doc
795
        FreeCAD.closeDocument("SaveRestoreTests")
796

797

798
class DocumentRecomputeCases(unittest.TestCase):
799
    def setUp(self):
800
        self.Doc = FreeCAD.newDocument("RecomputeTests")
801
        self.L1 = self.Doc.addObject("App::FeatureTest", "Label_1")
802
        self.L2 = self.Doc.addObject("App::FeatureTest", "Label_2")
803
        self.L3 = self.Doc.addObject("App::FeatureTest", "Label_3")
804

805
    def testDescent(self):
806
        # testing the up and downstream stuff
807
        FreeCAD.Console.PrintLog("def testDescent(self):Testcase not implemented\n")
808
        self.L1.Link = self.L2
809
        self.L2.Link = self.L3
810

811
    def testRecompute(self):
812

813
        # sequence to test recompute behaviour
814
        #       L1---\    L7
815
        #      /  \   \    |
816
        #    L2   L3   \  L8
817
        #   /  \ /  \  /
818
        #  L4   L5   L6
819

820
        L1 = self.Doc.addObject("App::FeatureTest", "Label_1")
821
        L2 = self.Doc.addObject("App::FeatureTest", "Label_2")
822
        L3 = self.Doc.addObject("App::FeatureTest", "Label_3")
823
        L4 = self.Doc.addObject("App::FeatureTest", "Label_4")
824
        L5 = self.Doc.addObject("App::FeatureTest", "Label_5")
825
        L6 = self.Doc.addObject("App::FeatureTest", "Label_6")
826
        L7 = self.Doc.addObject("App::FeatureTest", "Label_7")
827
        L8 = self.Doc.addObject("App::FeatureTest", "Label_8")
828
        L1.LinkList = [L2, L3, L6]
829
        L2.Link = L4
830
        L2.LinkList = [L5]
831
        L3.LinkList = [L5, L6]
832
        L7.Link = L8  # make second root
833

834
        self.assertTrue(L7 in self.Doc.RootObjects)
835
        self.assertTrue(L1 in self.Doc.RootObjects)
836

837
        self.assertTrue(len(self.Doc.Objects) == len(self.Doc.TopologicalSortedObjects))
838

839
        seqDic = {}
840
        i = 0
841
        for obj in self.Doc.TopologicalSortedObjects:
842
            seqDic[obj] = i
843
            print(obj)
844
            i += 1
845

846
        self.assertTrue(seqDic[L2] > seqDic[L1])
847
        self.assertTrue(seqDic[L3] > seqDic[L1])
848
        self.assertTrue(seqDic[L5] > seqDic[L2])
849
        self.assertTrue(seqDic[L5] > seqDic[L3])
850
        self.assertTrue(seqDic[L5] > seqDic[L1])
851

852
        self.assertTrue(
853
            (0, 0, 0, 0, 0, 0)
854
            == (L1.ExecCount, L2.ExecCount, L3.ExecCount, L4.ExecCount, L5.ExecCount, L6.ExecCount)
855
        )
856
        self.assertTrue(self.Doc.recompute() == 4)
857
        self.assertTrue(
858
            (1, 1, 1, 0, 0, 0)
859
            == (L1.ExecCount, L2.ExecCount, L3.ExecCount, L4.ExecCount, L5.ExecCount, L6.ExecCount)
860
        )
861
        L5.enforceRecompute()
862
        self.assertTrue(
863
            (1, 1, 1, 0, 0, 0)
864
            == (L1.ExecCount, L2.ExecCount, L3.ExecCount, L4.ExecCount, L5.ExecCount, L6.ExecCount)
865
        )
866
        self.assertTrue(self.Doc.recompute() == 4)
867
        self.assertTrue(
868
            (2, 2, 2, 0, 1, 0)
869
            == (L1.ExecCount, L2.ExecCount, L3.ExecCount, L4.ExecCount, L5.ExecCount, L6.ExecCount)
870
        )
871
        L4.enforceRecompute()
872
        self.assertTrue(self.Doc.recompute() == 3)
873
        self.assertTrue(
874
            (3, 3, 2, 1, 1, 0)
875
            == (L1.ExecCount, L2.ExecCount, L3.ExecCount, L4.ExecCount, L5.ExecCount, L6.ExecCount)
876
        )
877
        L5.enforceRecompute()
878
        self.assertTrue(self.Doc.recompute() == 4)
879
        self.assertTrue(
880
            (4, 4, 3, 1, 2, 0)
881
            == (L1.ExecCount, L2.ExecCount, L3.ExecCount, L4.ExecCount, L5.ExecCount, L6.ExecCount)
882
        )
883
        L6.enforceRecompute()
884
        self.assertTrue(self.Doc.recompute() == 3)
885
        self.assertTrue(
886
            (5, 4, 4, 1, 2, 1)
887
            == (L1.ExecCount, L2.ExecCount, L3.ExecCount, L4.ExecCount, L5.ExecCount, L6.ExecCount)
888
        )
889
        L2.enforceRecompute()
890
        self.assertTrue(self.Doc.recompute() == 2)
891
        self.assertTrue(
892
            (6, 5, 4, 1, 2, 1)
893
            == (L1.ExecCount, L2.ExecCount, L3.ExecCount, L4.ExecCount, L5.ExecCount, L6.ExecCount)
894
        )
895
        L1.enforceRecompute()
896
        self.assertTrue(self.Doc.recompute() == 1)
897
        self.assertTrue(
898
            (7, 5, 4, 1, 2, 1)
899
            == (L1.ExecCount, L2.ExecCount, L3.ExecCount, L4.ExecCount, L5.ExecCount, L6.ExecCount)
900
        )
901

902
        self.Doc.removeObject(L1.Name)
903
        self.Doc.removeObject(L2.Name)
904
        self.Doc.removeObject(L3.Name)
905
        self.Doc.removeObject(L4.Name)
906
        self.Doc.removeObject(L5.Name)
907
        self.Doc.removeObject(L6.Name)
908
        self.Doc.removeObject(L7.Name)
909
        self.Doc.removeObject(L8.Name)
910

911
    def tearDown(self):
912
        # closing doc
913
        FreeCAD.closeDocument("RecomputeTests")
914

915

916
class UndoRedoCases(unittest.TestCase):
917
    def setUp(self):
918
        self.Doc = FreeCAD.newDocument("UndoTest")
919
        self.Doc.UndoMode = 0
920
        self.Doc.addObject("App::FeatureTest", "Base")
921
        self.Doc.addObject("App::FeatureTest", "Del")
922
        self.Doc.getObject("Del").Integer = 2
923

924
    def testUndoProperties(self):
925
        # switch on the Undo
926
        self.Doc.UndoMode = 1
927

928
        # first transaction
929
        self.Doc.openTransaction("Transaction1")
930
        self.Doc.addObject("App::FeatureTest", "test1")
931
        self.Doc.getObject("test1").Integer = 1
932
        self.Doc.getObject("test1").String = "test1"
933
        self.Doc.getObject("test1").Float = 1.0
934
        self.Doc.getObject("test1").Bool = 1
935

936
        # self.Doc.getObject("test1").IntegerList  = 1
937
        # self.Doc.getObject("test1").FloatList  = 1.0
938

939
        # self.Doc.getObject("test1").Matrix  = (1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0)
940
        # self.Doc.getObject("test1").Vector  = (1.0,1.0,1.0)
941

942
        # second transaction
943
        self.Doc.openTransaction("Transaction2")
944
        self.Doc.getObject("test1").Integer = 2
945
        self.Doc.getObject("test1").String = "test2"
946
        self.Doc.getObject("test1").Float = 2.0
947
        self.Doc.getObject("test1").Bool = 0
948

949
        # switch on the Undo OFF
950
        self.Doc.UndoMode = 0
951

952
    def testUndoClear(self):
953
        # switch on the Undo
954
        self.Doc.UndoMode = 1
955
        self.assertEqual(self.Doc.UndoNames, [])
956
        self.assertEqual(self.Doc.UndoCount, 0)
957
        self.assertEqual(self.Doc.RedoNames, [])
958
        self.assertEqual(self.Doc.RedoCount, 0)
959

960
        self.Doc.openTransaction("Transaction1")
961
        # becomes the active object
962
        self.Doc.addObject("App::FeatureTest", "test1")
963
        self.Doc.commitTransaction()
964
        # removes the active object
965
        self.Doc.undo()
966
        self.assertEqual(self.Doc.ActiveObject, None)
967
        # deletes the active object
968
        self.Doc.clearUndos()
969
        self.assertEqual(self.Doc.ActiveObject, None)
970

971
    def testUndo(self):
972
        # switch on the Undo
973
        self.Doc.UndoMode = 1
974
        self.assertEqual(self.Doc.UndoNames, [])
975
        self.assertEqual(self.Doc.UndoCount, 0)
976
        self.assertEqual(self.Doc.RedoNames, [])
977
        self.assertEqual(self.Doc.RedoCount, 0)
978

979
        # first transaction
980
        self.Doc.openTransaction("Transaction1")
981
        self.Doc.addObject("App::FeatureTest", "test1")
982
        self.Doc.getObject("test1").Integer = 1
983
        self.Doc.getObject("Del").Integer = 1
984
        self.Doc.removeObject("Del")
985
        self.assertEqual(self.Doc.UndoNames, ["Transaction1"])
986
        self.assertEqual(self.Doc.UndoCount, 1)
987
        self.assertEqual(self.Doc.RedoNames, [])
988
        self.assertEqual(self.Doc.RedoCount, 0)
989

990
        # second transaction
991
        self.Doc.openTransaction("Transaction2")
992
        # new behavior: no change, no transaction
993
        self.assertEqual(self.Doc.UndoNames, ["Transaction1"])
994
        self.assertEqual(self.Doc.UndoCount, 1)
995
        self.assertEqual(self.Doc.RedoNames, [])
996
        self.assertEqual(self.Doc.RedoCount, 0)
997

998
        self.Doc.getObject("test1").Integer = 2
999
        self.assertEqual(self.Doc.UndoNames, ["Transaction2", "Transaction1"])
1000
        self.assertEqual(self.Doc.UndoCount, 2)
1001
        self.assertEqual(self.Doc.RedoNames, [])
1002
        self.assertEqual(self.Doc.RedoCount, 0)
1003

1004
        # abort second transaction
1005
        self.Doc.abortTransaction()
1006
        self.assertEqual(self.Doc.UndoNames, ["Transaction1"])
1007
        self.assertEqual(self.Doc.UndoCount, 1)
1008
        self.assertEqual(self.Doc.RedoNames, [])
1009
        self.assertEqual(self.Doc.RedoCount, 0)
1010
        self.assertEqual(self.Doc.getObject("test1").Integer, 1)
1011

1012
        # again second transaction
1013
        self.Doc.openTransaction("Transaction2")
1014
        self.Doc.getObject("test1").Integer = 2
1015
        self.assertEqual(self.Doc.UndoNames, ["Transaction2", "Transaction1"])
1016
        self.assertEqual(self.Doc.UndoCount, 2)
1017
        self.assertEqual(self.Doc.RedoNames, [])
1018
        self.assertEqual(self.Doc.RedoCount, 0)
1019

1020
        # third transaction
1021
        self.Doc.openTransaction("Transaction3")
1022
        self.Doc.getObject("test1").Integer = 3
1023
        self.assertEqual(self.Doc.UndoNames, ["Transaction3", "Transaction2", "Transaction1"])
1024
        self.assertEqual(self.Doc.UndoCount, 3)
1025
        self.assertEqual(self.Doc.RedoNames, [])
1026
        self.assertEqual(self.Doc.RedoCount, 0)
1027

1028
        # fourth transaction
1029
        self.Doc.openTransaction("Transaction4")
1030
        self.Doc.getObject("test1").Integer = 4
1031
        self.assertEqual(
1032
            self.Doc.UndoNames, ["Transaction4", "Transaction3", "Transaction2", "Transaction1"]
1033
        )
1034
        self.assertEqual(self.Doc.UndoCount, 4)
1035
        self.assertEqual(self.Doc.RedoNames, [])
1036
        self.assertEqual(self.Doc.RedoCount, 0)
1037

1038
        # undo the fourth transaction
1039
        self.Doc.undo()
1040
        self.assertEqual(self.Doc.getObject("test1").Integer, 3)
1041
        self.assertEqual(self.Doc.UndoNames, ["Transaction3", "Transaction2", "Transaction1"])
1042
        self.assertEqual(self.Doc.UndoCount, 3)
1043
        self.assertEqual(self.Doc.RedoNames, ["Transaction4"])
1044
        self.assertEqual(self.Doc.RedoCount, 1)
1045

1046
        # undo the third transaction
1047
        self.Doc.undo()
1048
        self.assertEqual(self.Doc.getObject("test1").Integer, 2)
1049
        self.assertEqual(self.Doc.UndoNames, ["Transaction2", "Transaction1"])
1050
        self.assertEqual(self.Doc.UndoCount, 2)
1051
        self.assertEqual(self.Doc.RedoNames, ["Transaction3", "Transaction4"])
1052
        self.assertEqual(self.Doc.RedoCount, 2)
1053

1054
        # undo the second transaction
1055
        self.Doc.undo()
1056
        self.assertEqual(self.Doc.getObject("test1").Integer, 1)
1057
        self.assertEqual(self.Doc.UndoNames, ["Transaction1"])
1058
        self.assertEqual(self.Doc.UndoCount, 1)
1059
        self.assertEqual(self.Doc.RedoNames, ["Transaction2", "Transaction3", "Transaction4"])
1060
        self.assertEqual(self.Doc.RedoCount, 3)
1061

1062
        # undo the first transaction
1063
        self.Doc.undo()
1064
        self.assertTrue(self.Doc.getObject("test1") is None)
1065
        self.assertTrue(self.Doc.getObject("Del").Integer == 2)
1066
        self.assertEqual(self.Doc.UndoNames, [])
1067
        self.assertEqual(self.Doc.UndoCount, 0)
1068
        self.assertEqual(
1069
            self.Doc.RedoNames, ["Transaction1", "Transaction2", "Transaction3", "Transaction4"]
1070
        )
1071
        self.assertEqual(self.Doc.RedoCount, 4)
1072

1073
        # redo the first transaction
1074
        self.Doc.redo()
1075
        self.assertEqual(self.Doc.getObject("test1").Integer, 1)
1076
        self.assertEqual(self.Doc.UndoNames, ["Transaction1"])
1077
        self.assertEqual(self.Doc.UndoCount, 1)
1078
        self.assertEqual(self.Doc.RedoNames, ["Transaction2", "Transaction3", "Transaction4"])
1079
        self.assertEqual(self.Doc.RedoCount, 3)
1080

1081
        # redo the second transaction
1082
        self.Doc.redo()
1083
        self.assertEqual(self.Doc.getObject("test1").Integer, 2)
1084
        self.assertEqual(self.Doc.UndoNames, ["Transaction2", "Transaction1"])
1085
        self.assertEqual(self.Doc.UndoCount, 2)
1086
        self.assertEqual(self.Doc.RedoNames, ["Transaction3", "Transaction4"])
1087
        self.assertEqual(self.Doc.RedoCount, 2)
1088

1089
        # undo the second transaction
1090
        self.Doc.undo()
1091
        self.assertEqual(self.Doc.getObject("test1").Integer, 1)
1092
        self.assertEqual(self.Doc.UndoNames, ["Transaction1"])
1093
        self.assertEqual(self.Doc.UndoCount, 1)
1094
        self.assertEqual(self.Doc.RedoNames, ["Transaction2", "Transaction3", "Transaction4"])
1095
        self.assertEqual(self.Doc.RedoCount, 3)
1096

1097
        # new transaction eight
1098
        self.Doc.openTransaction("Transaction8")
1099
        self.Doc.getObject("test1").Integer = 8
1100
        self.assertEqual(self.Doc.UndoNames, ["Transaction8", "Transaction1"])
1101
        self.assertEqual(self.Doc.UndoCount, 2)
1102
        self.assertEqual(self.Doc.RedoNames, [])
1103
        self.assertEqual(self.Doc.RedoCount, 0)
1104
        self.Doc.abortTransaction()
1105
        self.assertEqual(self.Doc.UndoNames, ["Transaction1"])
1106
        self.assertEqual(self.Doc.UndoCount, 1)
1107
        self.assertEqual(self.Doc.RedoNames, [])
1108
        self.assertEqual(self.Doc.RedoCount, 0)
1109

1110
        # again new transaction eight
1111
        self.Doc.openTransaction("Transaction8")
1112
        self.Doc.getObject("test1").Integer = 8
1113
        self.assertEqual(self.Doc.UndoNames, ["Transaction8", "Transaction1"])
1114
        self.assertEqual(self.Doc.UndoCount, 2)
1115
        self.assertEqual(self.Doc.RedoNames, [])
1116
        self.assertEqual(self.Doc.RedoCount, 0)
1117

1118
        # again new transaction nine
1119
        self.Doc.openTransaction("Transaction9")
1120
        self.Doc.getObject("test1").Integer = 9
1121
        self.assertEqual(self.Doc.UndoNames, ["Transaction9", "Transaction8", "Transaction1"])
1122
        self.assertEqual(self.Doc.UndoCount, 3)
1123
        self.assertEqual(self.Doc.RedoNames, [])
1124
        self.assertEqual(self.Doc.RedoCount, 0)
1125
        self.Doc.commitTransaction()
1126
        self.assertEqual(self.Doc.UndoNames, ["Transaction9", "Transaction8", "Transaction1"])
1127
        self.assertEqual(self.Doc.UndoCount, 3)
1128
        self.assertEqual(self.Doc.RedoNames, [])
1129
        self.assertEqual(self.Doc.RedoCount, 0)
1130
        self.assertEqual(self.Doc.getObject("test1").Integer, 9)
1131

1132
        # undo the ninth transaction
1133
        self.Doc.undo()
1134
        self.assertEqual(self.Doc.getObject("test1").Integer, 8)
1135
        self.assertEqual(self.Doc.UndoNames, ["Transaction8", "Transaction1"])
1136
        self.assertEqual(self.Doc.UndoCount, 2)
1137
        self.assertEqual(self.Doc.RedoNames, ["Transaction9"])
1138
        self.assertEqual(self.Doc.RedoCount, 1)
1139

1140
        # switch on the Undo OFF
1141
        self.Doc.UndoMode = 0
1142
        self.assertEqual(self.Doc.UndoNames, [])
1143
        self.assertEqual(self.Doc.UndoCount, 0)
1144
        self.assertEqual(self.Doc.RedoNames, [])
1145
        self.assertEqual(self.Doc.RedoCount, 0)
1146

1147
    def testUndoInList(self):
1148

1149
        self.Doc.UndoMode = 1
1150

1151
        self.Doc.openTransaction("Box")
1152
        self.Box = self.Doc.addObject("App::FeatureTest")
1153
        self.Doc.commitTransaction()
1154

1155
        self.Doc.openTransaction("Cylinder")
1156
        self.Cylinder = self.Doc.addObject("App::FeatureTest")
1157
        self.Doc.commitTransaction()
1158

1159
        self.Doc.openTransaction("Fuse")
1160
        self.Fuse1 = self.Doc.addObject("App::FeatureTest", "Fuse")
1161
        self.Fuse1.LinkList = [self.Box, self.Cylinder]
1162
        self.Doc.commitTransaction()
1163

1164
        self.Doc.undo()
1165
        self.assertTrue(len(self.Box.InList) == 0)
1166
        self.assertTrue(len(self.Cylinder.InList) == 0)
1167

1168
        self.Doc.redo()
1169
        self.assertTrue(len(self.Box.InList) == 1)
1170
        self.assertTrue(self.Box.InList[0] == self.Doc.Fuse)
1171
        self.assertTrue(len(self.Cylinder.InList) == 1)
1172
        self.assertTrue(self.Cylinder.InList[0] == self.Doc.Fuse)
1173

1174
    def testUndoIssue0003150Part1(self):
1175

1176
        self.Doc.UndoMode = 1
1177

1178
        self.Doc.openTransaction("Box")
1179
        self.Box = self.Doc.addObject("App::FeatureTest")
1180
        self.Doc.commitTransaction()
1181

1182
        self.Doc.openTransaction("Cylinder")
1183
        self.Cylinder = self.Doc.addObject("App::FeatureTest")
1184
        self.Doc.commitTransaction()
1185

1186
        self.Doc.openTransaction("Fuse")
1187
        self.Fuse1 = self.Doc.addObject("App::FeatureTest")
1188
        self.Fuse1.LinkList = [self.Box, self.Cylinder]
1189
        self.Doc.commitTransaction()
1190
        self.Doc.recompute()
1191

1192
        self.Doc.openTransaction("Sphere")
1193
        self.Sphere = self.Doc.addObject("App::FeatureTest")
1194
        self.Doc.commitTransaction()
1195

1196
        self.Doc.openTransaction("Fuse")
1197
        self.Fuse2 = self.Doc.addObject("App::FeatureTest")
1198
        self.Fuse2.LinkList = [self.Fuse1, self.Sphere]
1199
        self.Doc.commitTransaction()
1200
        self.Doc.recompute()
1201

1202
        self.Doc.openTransaction("Part")
1203
        self.Part = self.Doc.addObject("App::Part")
1204
        self.Doc.commitTransaction()
1205

1206
        self.Doc.openTransaction("Drag")
1207
        self.Part.addObject(self.Fuse2)
1208
        self.Doc.commitTransaction()
1209

1210
        # 3 undos show the problem of failing recompute
1211
        self.Doc.undo()
1212
        self.Doc.undo()
1213
        self.Doc.undo()
1214
        self.assertTrue(self.Doc.recompute() >= 0)
1215

1216
    def tearDown(self):
1217
        # closing doc
1218
        FreeCAD.closeDocument("UndoTest")
1219

1220

1221
class DocumentGroupCases(unittest.TestCase):
1222
    def setUp(self):
1223
        self.Doc = FreeCAD.newDocument("GroupTests")
1224

1225
    def testGroup(self):
1226
        # Add an object to the group
1227
        L2 = self.Doc.addObject("App::FeatureTest", "Label_2")
1228
        G1 = self.Doc.addObject("App::DocumentObjectGroup", "Group")
1229
        G1.addObject(L2)
1230
        self.assertTrue(G1.hasObject(L2))
1231

1232
        # Adding the group to itself must fail
1233
        try:
1234
            G1.addObject(G1)
1235
        except Exception:
1236
            FreeCAD.Console.PrintLog("Cannot add group to itself, OK\n")
1237
        else:
1238
            self.fail("Adding the group to itself must not be possible")
1239

1240
        self.Doc.UndoMode = 1
1241

1242
        # Remove object from group
1243
        self.Doc.openTransaction("Remove")
1244
        self.Doc.removeObject("Label_2")
1245
        self.Doc.commitTransaction()
1246
        self.assertTrue(G1.getObject("Label_2") is None)
1247
        self.Doc.undo()
1248
        self.assertTrue(G1.getObject("Label_2") is not None)
1249

1250
        # Remove first group and then the object
1251
        self.Doc.openTransaction("Remove")
1252
        self.Doc.removeObject("Group")
1253
        self.Doc.removeObject("Label_2")
1254
        self.Doc.commitTransaction()
1255
        self.Doc.undo()
1256
        self.assertTrue(G1.getObject("Label_2") is not None)
1257

1258
        # Remove first object and then the group in two transactions
1259
        self.Doc.openTransaction("Remove")
1260
        self.Doc.removeObject("Label_2")
1261
        self.Doc.commitTransaction()
1262
        self.assertTrue(G1.getObject("Label_2") is None)
1263
        self.Doc.openTransaction("Remove")
1264
        self.Doc.removeObject("Group")
1265
        self.Doc.commitTransaction()
1266
        self.Doc.undo()
1267
        self.Doc.undo()
1268
        self.assertTrue(G1.getObject("Label_2") is not None)
1269

1270
        # Remove first object and then the group in one transaction
1271
        self.Doc.openTransaction("Remove")
1272
        self.Doc.removeObject("Label_2")
1273
        self.assertTrue(G1.getObject("Label_2") is None)
1274
        self.Doc.removeObject("Group")
1275
        self.Doc.commitTransaction()
1276
        self.Doc.undo()
1277
        # FIXME: See bug #1820554
1278
        self.assertTrue(G1.getObject("Label_2") is not None)
1279

1280
        # Add a second object to the group
1281
        L3 = self.Doc.addObject("App::FeatureTest", "Label_3")
1282
        G1.addObject(L3)
1283
        self.Doc.openTransaction("Remove")
1284
        self.Doc.removeObject("Label_2")
1285
        self.assertTrue(G1.getObject("Label_2") is None)
1286
        self.Doc.removeObject("Label_3")
1287
        self.assertTrue(G1.getObject("Label_3") is None)
1288
        self.Doc.removeObject("Group")
1289
        self.Doc.commitTransaction()
1290
        self.Doc.undo()
1291
        self.assertTrue(G1.getObject("Label_3") is not None)
1292
        self.assertTrue(G1.getObject("Label_2") is not None)
1293

1294
        self.Doc.UndoMode = 0
1295

1296
        # Cleanup
1297
        self.Doc.removeObject("Group")
1298
        self.Doc.removeObject("Label_2")
1299
        self.Doc.removeObject("Label_3")
1300

1301
    def testGroupAndGeoFeatureGroup(self):
1302

1303
        # an object can only be in one group at once, that must be enforced
1304
        obj1 = self.Doc.addObject("App::FeatureTest", "obj1")
1305
        grp1 = self.Doc.addObject("App::DocumentObjectGroup", "Group1")
1306
        grp2 = self.Doc.addObject("App::DocumentObjectGroup", "Group2")
1307
        grp1.addObject(obj1)
1308
        self.assertTrue(obj1.getParentGroup() == grp1)
1309
        self.assertTrue(obj1.getParentGeoFeatureGroup() is None)
1310
        self.assertTrue(grp1.hasObject(obj1))
1311
        grp2.addObject(obj1)
1312
        self.assertTrue(grp1.hasObject(obj1) == False)
1313
        self.assertTrue(grp2.hasObject(obj1))
1314

1315
        # an object is allowed to be in a group and a geofeaturegroup
1316
        prt1 = self.Doc.addObject("App::Part", "Part1")
1317
        prt2 = self.Doc.addObject("App::Part", "Part2")
1318

1319
        prt1.addObject(grp2)
1320
        self.assertTrue(grp2.getParentGeoFeatureGroup() == prt1)
1321
        self.assertTrue(grp2.getParentGroup() is None)
1322
        self.assertTrue(grp2.hasObject(obj1))
1323
        self.assertTrue(prt1.hasObject(grp2))
1324
        self.assertTrue(prt1.hasObject(obj1))
1325

1326
        # it is not allowed to be in 2 geofeaturegroups
1327
        prt2.addObject(grp2)
1328
        self.assertTrue(grp2.hasObject(obj1))
1329
        self.assertTrue(prt1.hasObject(grp2) == False)
1330
        self.assertTrue(prt1.hasObject(obj1) == False)
1331
        self.assertTrue(prt2.hasObject(grp2))
1332
        self.assertTrue(prt2.hasObject(obj1))
1333
        try:
1334
            grp = prt1.Group
1335
            grp.append(obj1)
1336
            prt1.Group = grp
1337
        except Exception:
1338
            grp.remove(obj1)
1339
            self.assertTrue(prt1.Group == grp)
1340
        else:
1341
            self.fail("No exception thrown when object is in multiple Groups")
1342

1343
        # it is not allowed to be in 2 Groups
1344
        prt2.addObject(grp1)
1345
        grp = grp1.Group
1346
        grp.append(obj1)
1347
        try:
1348
            grp1.Group = grp
1349
        except Exception:
1350
            pass
1351
        else:
1352
            self.fail("No exception thrown when object is in multiple Groups")
1353

1354
        # cross linking between GeoFeatureGroups is not allowed
1355
        self.Doc.recompute()
1356
        box = self.Doc.addObject("App::FeatureTest", "Box")
1357
        cyl = self.Doc.addObject("App::FeatureTest", "Cylinder")
1358
        fus = self.Doc.addObject("App::FeatureTest", "Fusion")
1359
        fus.LinkList = [cyl, box]
1360
        self.Doc.recompute()
1361
        self.assertTrue(fus.State[0] == "Up-to-date")
1362
        fus.LinkList = (
1363
            []
1364
        )  # remove all links as addObject would otherwise transfer all linked objects
1365
        prt1.addObject(cyl)
1366
        fus.LinkList = [cyl, box]
1367
        self.Doc.recompute()
1368
        # self.assertTrue(fus.State[0] == 'Invalid')
1369
        fus.LinkList = []
1370
        prt1.addObject(box)
1371
        fus.LinkList = [cyl, box]
1372
        self.Doc.recompute()
1373
        # self.assertTrue(fus.State[0] == 'Invalid')
1374
        fus.LinkList = []
1375
        prt1.addObject(fus)
1376
        fus.LinkList = [cyl, box]
1377
        self.Doc.recompute()
1378
        self.assertTrue(fus.State[0] == "Up-to-date")
1379
        prt2.addObject(box)  # this time addObject should move all dependencies to the new part
1380
        self.Doc.recompute()
1381
        self.assertTrue(fus.State[0] == "Up-to-date")
1382

1383
        # grouping must be resilient against cyclic links and not crash: #issue 0002567
1384
        prt1.addObject(prt2)
1385
        grp = prt2.Group
1386
        grp.append(prt1)
1387
        prt2.Group = grp
1388
        self.Doc.recompute()
1389
        prt2.Group = []
1390
        try:
1391
            prt2.Group = [prt2]
1392
        except Exception:
1393
            pass
1394
        else:
1395
            self.fail("Exception is expected")
1396

1397
        self.Doc.recompute()
1398

1399
    def testIssue0003150Part2(self):
1400
        self.box = self.Doc.addObject("App::FeatureTest")
1401
        self.cyl = self.Doc.addObject("App::FeatureTest")
1402
        self.sph = self.Doc.addObject("App::FeatureTest")
1403

1404
        self.fus1 = self.Doc.addObject("App::FeatureTest")
1405
        self.fus2 = self.Doc.addObject("App::FeatureTest")
1406

1407
        self.fus1.LinkList = [self.box, self.cyl]
1408
        self.fus2.LinkList = [self.sph, self.cyl]
1409

1410
        self.prt = self.Doc.addObject("App::Part")
1411
        self.prt.addObject(self.fus1)
1412
        self.assertTrue(len(self.prt.Group) == 5)
1413
        self.assertTrue(self.fus2.getParentGeoFeatureGroup() == self.prt)
1414
        self.assertTrue(self.prt.hasObject(self.sph))
1415

1416
        self.prt.removeObject(self.fus1)
1417
        self.assertTrue(len(self.prt.Group) == 0)
1418

1419
    def tearDown(self):
1420
        # closing doc
1421
        FreeCAD.closeDocument("GroupTests")
1422

1423

1424
class DocumentPlatformCases(unittest.TestCase):
1425
    def setUp(self):
1426
        self.Doc = FreeCAD.newDocument("PlatformTests")
1427
        self.Doc.addObject("App::FeatureTest", "Test")
1428
        self.TempPath = tempfile.gettempdir()
1429
        self.DocName = self.TempPath + os.sep + "PlatformTests.FCStd"
1430

1431
    def testFloatList(self):
1432
        self.Doc.Test.FloatList = [-0.05, 2.5, 5.2]
1433

1434
        # saving and restoring
1435
        self.Doc.saveAs(self.DocName)
1436
        FreeCAD.closeDocument("PlatformTests")
1437
        self.Doc = FreeCAD.open(self.DocName)
1438

1439
        self.assertTrue(abs(self.Doc.Test.FloatList[0] + 0.05) < 0.01)
1440
        self.assertTrue(abs(self.Doc.Test.FloatList[1] - 2.5) < 0.01)
1441
        self.assertTrue(abs(self.Doc.Test.FloatList[2] - 5.2) < 0.01)
1442

1443
    def testColorList(self):
1444
        self.Doc.Test.ColourList = [(1.0, 0.5, 0.0), (0.0, 0.5, 1.0)]
1445

1446
        # saving and restoring
1447
        self.Doc.saveAs(self.DocName)
1448
        FreeCAD.closeDocument("PlatformTests")
1449
        self.Doc = FreeCAD.open(self.DocName)
1450

1451
        self.assertTrue(abs(self.Doc.Test.ColourList[0][0] - 1.0) < 0.01)
1452
        self.assertTrue(abs(self.Doc.Test.ColourList[0][1] - 0.5) < 0.01)
1453
        self.assertTrue(abs(self.Doc.Test.ColourList[0][2] - 0.0) < 0.01)
1454
        self.assertTrue(abs(self.Doc.Test.ColourList[0][3] - 0.0) < 0.01)
1455
        self.assertTrue(abs(self.Doc.Test.ColourList[1][0] - 0.0) < 0.01)
1456
        self.assertTrue(abs(self.Doc.Test.ColourList[1][1] - 0.5) < 0.01)
1457
        self.assertTrue(abs(self.Doc.Test.ColourList[1][2] - 1.0) < 0.01)
1458
        self.assertTrue(abs(self.Doc.Test.ColourList[1][3] - 0.0) < 0.01)
1459

1460
    def testVectorList(self):
1461
        self.Doc.Test.VectorList = [(-0.05, 2.5, 5.2), (-0.05, 2.5, 5.2)]
1462

1463
        # saving and restoring
1464
        self.Doc.saveAs(self.DocName)
1465
        FreeCAD.closeDocument("PlatformTests")
1466
        self.Doc = FreeCAD.open(self.DocName)
1467

1468
        self.assertTrue(len(self.Doc.Test.VectorList) == 2)
1469

1470
    def testPoints(self):
1471
        try:
1472
            self.Doc.addObject("Points::Feature", "Points")
1473

1474
            # saving and restoring
1475
            self.Doc.saveAs(self.DocName)
1476
            FreeCAD.closeDocument("PlatformTests")
1477
            self.Doc = FreeCAD.open(self.DocName)
1478

1479
            self.assertTrue(self.Doc.Points.Points.count() == 0)
1480
        except Exception:
1481
            pass
1482

1483
    def tearDown(self):
1484
        # closing doc
1485
        FreeCAD.closeDocument("PlatformTests")
1486

1487

1488
class DocumentBacklinks(unittest.TestCase):
1489
    def setUp(self):
1490
        self.Doc = FreeCAD.newDocument("BackLinks")
1491

1492
    def testIssue0003323(self):
1493
        self.Doc.UndoMode = 1
1494
        self.Doc.openTransaction("Create object")
1495
        obj1 = self.Doc.addObject("App::FeatureTest", "Test1")
1496
        obj2 = self.Doc.addObject("App::FeatureTest", "Test2")
1497
        obj2.Link = obj1
1498
        self.Doc.commitTransaction()
1499
        self.Doc.undo()
1500
        self.Doc.openTransaction("Create object")
1501

1502
    def tearDown(self):
1503
        # closing doc
1504
        FreeCAD.closeDocument("BackLinks")
1505

1506

1507
class DocumentFileIncludeCases(unittest.TestCase):
1508
    def setUp(self):
1509
        self.Doc = FreeCAD.newDocument("FileIncludeTests")
1510
        # testing with undo
1511
        self.Doc.UndoMode = 1
1512

1513
    def testApplyFiles(self):
1514
        self.Doc.openTransaction("Transaction0")
1515
        self.L1 = self.Doc.addObject("App::DocumentObjectFileIncluded", "FileObject1")
1516
        self.assertTrue(self.L1.File == "")
1517
        self.Filename = self.L1.File
1518

1519
        self.Doc.openTransaction("Transaction1")
1520
        self.TempPath = tempfile.gettempdir()
1521
        # creating a file in the Transient directory of the document
1522
        file = open(self.Doc.getTempFileName("test"), "w")
1523
        file.write("test No1")
1524
        file.close()
1525
        # applying the file
1526
        self.L1.File = (file.name, "Test.txt")
1527
        self.assertTrue(self.L1.File.split("/")[-1] == "Test.txt")
1528
        # read again
1529
        file = open(self.L1.File, "r")
1530
        self.assertTrue(file.read() == "test No1")
1531
        file.close()
1532
        file = open(self.TempPath + "/testNest.txt", "w")
1533
        file.write("test No2")
1534
        file.close()
1535
        # applying the file
1536
        self.Doc.openTransaction("Transaction2")
1537
        self.L1.File = file.name
1538
        self.assertTrue(self.L1.File.split("/")[-1] == "Test.txt")
1539
        # read again
1540
        file = open(self.L1.File, "r")
1541
        self.assertTrue(file.read() == "test No2")
1542
        file.close()
1543
        self.Doc.undo()
1544
        self.assertTrue(self.L1.File.split("/")[-1] == "Test.txt")
1545
        # read again
1546
        file = open(self.L1.File, "r")
1547
        self.assertTrue(file.read() == "test No1")
1548
        file.close()
1549
        self.Doc.undo()
1550
        # read again
1551
        self.assertTrue(self.L1.File == "")
1552
        self.Doc.redo()
1553
        self.assertTrue(self.L1.File.split("/")[-1] == "Test.txt")
1554
        # read again
1555
        file = open(self.L1.File, "r")
1556
        self.assertTrue(file.read() == "test No1")
1557
        file.close()
1558
        self.Doc.redo()
1559
        self.assertTrue(self.L1.File.split("/")[-1] == "Test.txt")
1560
        # read again
1561
        file = open(self.L1.File, "r")
1562
        self.assertTrue(file.read() == "test No2")
1563
        file.close()
1564
        # Save restore test
1565
        FileName = self.TempPath + "/FileIncludeTests.fcstd"
1566
        self.Doc.saveAs(FileName)
1567
        FreeCAD.closeDocument("FileIncludeTests")
1568
        self.Doc = FreeCAD.open(self.TempPath + "/FileIncludeTests.fcstd")
1569
        # check if the file is still there
1570
        self.L1 = self.Doc.getObject("FileObject1")
1571
        file = open(self.L1.File, "r")
1572
        res = file.read()
1573
        FreeCAD.Console.PrintLog(res + "\n")
1574
        self.assertTrue(res == "test No2")
1575
        self.assertTrue(self.L1.File.split("/")[-1] == "Test.txt")
1576
        file.close()
1577

1578
        # test for bug #94 (File overlap in PropertyFileIncluded)
1579
        L2 = self.Doc.addObject("App::DocumentObjectFileIncluded", "FileObject2")
1580
        L3 = self.Doc.addObject("App::DocumentObjectFileIncluded", "FileObject3")
1581

1582
        # creating two files in the Transient directory of the document
1583
        file1 = open(self.Doc.getTempFileName("test"), "w")
1584
        file1.write("test No1")
1585
        file1.close()
1586
        file2 = open(self.Doc.getTempFileName("test"), "w")
1587
        file2.write("test No2")
1588
        file2.close()
1589

1590
        # applying the file with the same base name
1591
        L2.File = (file1.name, "Test.txt")
1592
        L3.File = (file2.name, "Test.txt")
1593

1594
        file = open(L2.File, "r")
1595
        self.assertTrue(file.read() == "test No1")
1596
        file.close()
1597
        file = open(L3.File, "r")
1598
        self.assertTrue(file.read() == "test No2")
1599
        file.close()
1600

1601
        # create a second document, copy a file and close the document
1602
        # the test is about to put the file to the correct transient dir
1603
        doc2 = FreeCAD.newDocument("Doc2")
1604
        L4 = doc2.addObject("App::DocumentObjectFileIncluded", "FileObject")
1605
        L5 = doc2.addObject("App::DocumentObjectFileIncluded", "FileObject")
1606
        L6 = doc2.addObject("App::DocumentObjectFileIncluded", "FileObject")
1607
        L4.File = (L3.File, "Test.txt")
1608
        L5.File = L3.File
1609
        L6.File = L3.File
1610
        FreeCAD.closeDocument("FileIncludeTests")
1611
        self.Doc = FreeCAD.open(self.TempPath + "/FileIncludeTests.fcstd")
1612
        self.assertTrue(os.path.exists(L4.File))
1613
        self.assertTrue(os.path.exists(L5.File))
1614
        self.assertTrue(os.path.exists(L6.File))
1615
        self.assertTrue(L5.File != L6.File)
1616
        # copy file from L5 which is in the same directory
1617
        L7 = doc2.addObject("App::DocumentObjectFileIncluded", "FileObject3")
1618
        L7.File = (L5.File, "Copy.txt")
1619
        self.assertTrue(os.path.exists(L7.File))
1620
        FreeCAD.closeDocument("Doc2")
1621

1622
    def tearDown(self):
1623
        # closing doc
1624
        FreeCAD.closeDocument("FileIncludeTests")
1625

1626

1627
class DocumentPropertyCases(unittest.TestCase):
1628
    def setUp(self):
1629
        self.Doc = FreeCAD.newDocument("PropertyTests")
1630
        self.Obj = self.Doc.addObject("App::FeaturePython", "Test")
1631

1632
    def testDescent(self):
1633
        # testing the up and downstream stuff
1634
        props = self.Obj.supportedProperties()
1635
        for i in props:
1636
            self.Obj.addProperty(i, i.replace(":", "_"))
1637
        tempPath = tempfile.gettempdir()
1638
        tempFile = tempPath + os.sep + "PropertyTests.FCStd"
1639
        self.Doc.saveAs(tempFile)
1640
        FreeCAD.closeDocument("PropertyTests")
1641
        self.Doc = FreeCAD.open(tempFile)
1642

1643
    def testRemoveProperty(self):
1644
        prop = "Something"
1645
        self.Obj.addProperty("App::PropertyFloat", prop)
1646
        self.Obj.Something = 0.01
1647
        self.Doc.recompute()
1648
        self.Doc.openTransaction("modify and remove property")
1649
        self.Obj.Something = 0.00
1650
        self.Obj.removeProperty(prop)
1651
        self.Obj.recompute()
1652
        self.Doc.abortTransaction()
1653

1654
    def testRemovePropertyExpression(self):
1655
        p1 = self.Doc.addObject("App::FeaturePython", "params1")
1656
        p2 = self.Doc.addObject("App::FeaturePython", "params2")
1657
        p1.addProperty("App::PropertyFloat", "a")
1658
        p1.a = 42
1659
        p2.addProperty("App::PropertyFloat", "b")
1660
        p2.setExpression("b", "params1.a")
1661
        self.Doc.recompute()
1662
        p2.removeProperty("b")
1663
        p1.touch()
1664
        self.Doc.recompute()
1665
        self.assertTrue(not p2 in p1.InList)
1666

1667
    def testRemovePropertyOnChange(self):
1668
        class Feature:
1669
            def __init__(self, fp):
1670
                fp.Proxy = self
1671
                fp.addProperty("App::PropertyString", "Test")
1672

1673
            def onBeforeChange(self, fp, prop):
1674
                if prop == "Test":
1675
                    fp.removeProperty("Test")
1676

1677
            def onChanged(self, fp, prop):
1678
                getattr(fp, prop)
1679

1680
        obj = self.Doc.addObject("App::FeaturePython")
1681
        fea = Feature(obj)
1682
        obj.Test = "test"
1683

1684
    def tearDown(self):
1685
        # closing doc
1686
        FreeCAD.closeDocument("PropertyTests")
1687

1688

1689
class DocumentExpressionCases(unittest.TestCase):
1690
    def setUp(self):
1691
        self.Doc = FreeCAD.newDocument()
1692

1693
    def assertAlmostEqual(self, v1, v2):
1694
        if math.fabs(v2 - v1) > 1e-12:
1695
            self.assertEqual(v1, v2)
1696

1697
    def testExpression(self):
1698
        self.Obj1 = self.Doc.addObject("App::FeatureTest", "Test")
1699
        self.Obj2 = self.Doc.addObject("App::FeatureTest", "Test")
1700
        # set the object twice to test that the backlinks are removed when overwriting the expression
1701
        self.Obj2.setExpression(
1702
            "Placement.Rotation.Angle", "%s.Placement.Rotation.Angle" % self.Obj1.Name
1703
        )
1704
        self.Obj2.setExpression(
1705
            "Placement.Rotation.Angle", "%s.Placement.Rotation.Angle" % self.Obj1.Name
1706
        )
1707
        self.Obj1.Placement = FreeCAD.Placement(
1708
            FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 10)
1709
        )
1710
        self.Doc.recompute()
1711
        self.assertAlmostEqual(
1712
            self.Obj1.Placement.Rotation.Angle, self.Obj2.Placement.Rotation.Angle
1713
        )
1714

1715
        # clear the expression
1716
        self.Obj2.setExpression("Placement.Rotation.Angle", None)
1717
        self.assertAlmostEqual(
1718
            self.Obj1.Placement.Rotation.Angle, self.Obj2.Placement.Rotation.Angle
1719
        )
1720
        self.Doc.recompute()
1721
        self.assertAlmostEqual(
1722
            self.Obj1.Placement.Rotation.Angle, self.Obj2.Placement.Rotation.Angle
1723
        )
1724
        # touch the objects to perform a recompute
1725
        self.Obj1.Placement = self.Obj1.Placement
1726
        self.Obj2.Placement = self.Obj2.Placement
1727
        # must not raise a topological error
1728
        self.assertEqual(self.Doc.recompute(), 2)
1729

1730
        # add test for issue #6948
1731
        self.Obj3 = self.Doc.addObject("App::FeatureTest", "Test")
1732
        self.Obj3.setExpression("Float", "2*(5%3)")
1733
        self.Doc.recompute()
1734
        self.assertEqual(self.Obj3.Float, 4)
1735
        self.assertEqual(self.Obj3.evalExpression(self.Obj3.ExpressionEngine[0][1]), 4)
1736

1737
    def testIssue4649(self):
1738
        class Cls:
1739
            def __init__(self, obj):
1740
                self.MonitorChanges = False
1741
                obj.Proxy = self
1742
                obj.addProperty("App::PropertyFloat", "propA", "group")
1743
                obj.addProperty("App::PropertyFloat", "propB", "group")
1744
                self.MonitorChanges = True
1745
                obj.setExpression("propB", "6*9")
1746

1747
            def onChanged(self, obj, prop):
1748
                print("onChanged", self, obj, prop)
1749
                if self.MonitorChanges and prop == "propA":
1750
                    print("Removing expression...")
1751
                    obj.setExpression("propB", None)
1752

1753
        obj = self.Doc.addObject("App::DocumentObjectGroupPython", "Obj")
1754
        Cls(obj)
1755
        self.Doc.UndoMode = 1
1756
        self.Doc.openTransaction("Expression")
1757
        obj.setExpression("propA", "42")
1758
        self.Doc.recompute()
1759
        self.Doc.commitTransaction()
1760
        self.assertTrue(("propB", None) in obj.ExpressionEngine)
1761
        self.assertTrue(("propA", "42") in obj.ExpressionEngine)
1762

1763
        self.Doc.undo()
1764
        self.assertFalse(("propB", None) in obj.ExpressionEngine)
1765
        self.assertFalse(("propA", "42") in obj.ExpressionEngine)
1766

1767
        self.Doc.redo()
1768
        self.assertTrue(("propB", None) in obj.ExpressionEngine)
1769
        self.assertTrue(("propA", "42") in obj.ExpressionEngine)
1770

1771
        self.Doc.recompute()
1772
        obj.ExpressionEngine
1773

1774
        TempPath = tempfile.gettempdir()
1775
        SaveName = TempPath + os.sep + "ExpressionTests.FCStd"
1776
        self.Doc.saveAs(SaveName)
1777
        FreeCAD.closeDocument(self.Doc.Name)
1778
        self.Doc = FreeCAD.openDocument(SaveName)
1779

1780
    def testCyclicDependencyOnPlacement(self):
1781
        obj = self.Doc.addObject("App::FeaturePython", "Python")
1782
        obj.addProperty("App::PropertyPlacement", "Placement")
1783
        obj.setExpression(".Placement.Base.x", ".Placement.Base.y + 10mm")
1784
        with self.assertRaises(RuntimeError):
1785
            obj.setExpression(".Placement.Base.y", ".Placement.Base.x + 10mm")
1786

1787
    def tearDown(self):
1788
        # closing doc
1789
        FreeCAD.closeDocument(self.Doc.Name)
1790

1791

1792
class DocumentObserverCases(unittest.TestCase):
1793
    class Observer:
1794
        def __init__(self):
1795
            self.clear()
1796

1797
        def clear(self):
1798
            self.signal = []
1799
            self.parameter = []
1800
            self.parameter2 = []
1801

1802
        def slotCreatedDocument(self, doc):
1803
            self.signal.append("DocCreated")
1804
            self.parameter.append(doc)
1805

1806
        def slotDeletedDocument(self, doc):
1807
            self.signal.append("DocDeleted")
1808
            self.parameter.append(doc)
1809

1810
        def slotRelabelDocument(self, doc):
1811
            self.signal.append("DocRelabled")
1812
            self.parameter.append(doc)
1813

1814
        def slotActivateDocument(self, doc):
1815
            self.signal.append("DocActivated")
1816
            self.parameter.append(doc)
1817

1818
        def slotRecomputedDocument(self, doc):
1819
            self.signal.append("DocRecomputed")
1820
            self.parameter.append(doc)
1821

1822
        def slotUndoDocument(self, doc):
1823
            self.signal.append("DocUndo")
1824
            self.parameter.append(doc)
1825

1826
        def slotRedoDocument(self, doc):
1827
            self.signal.append("DocRedo")
1828
            self.parameter.append(doc)
1829

1830
        def slotOpenTransaction(self, doc, name):
1831
            self.signal.append("DocOpenTransaction")
1832
            self.parameter.append(doc)
1833
            self.parameter2.append(name)
1834

1835
        def slotCommitTransaction(self, doc):
1836
            self.signal.append("DocCommitTransaction")
1837
            self.parameter.append(doc)
1838

1839
        def slotAbortTransaction(self, doc):
1840
            self.signal.append("DocAbortTransaction")
1841
            self.parameter.append(doc)
1842

1843
        def slotBeforeChangeDocument(self, doc, prop):
1844
            self.signal.append("DocBeforeChange")
1845
            self.parameter.append(doc)
1846
            self.parameter2.append(prop)
1847

1848
        def slotChangedDocument(self, doc, prop):
1849
            self.signal.append("DocChanged")
1850
            self.parameter.append(doc)
1851
            self.parameter2.append(prop)
1852

1853
        def slotCreatedObject(self, obj):
1854
            self.signal.append("ObjCreated")
1855
            self.parameter.append(obj)
1856

1857
        def slotDeletedObject(self, obj):
1858
            self.signal.append("ObjDeleted")
1859
            self.parameter.append(obj)
1860

1861
        def slotChangedObject(self, obj, prop):
1862
            self.signal.append("ObjChanged")
1863
            self.parameter.append(obj)
1864
            self.parameter2.append(prop)
1865

1866
        def slotBeforeChangeObject(self, obj, prop):
1867
            self.signal.append("ObjBeforeChange")
1868
            self.parameter.append(obj)
1869
            self.parameter2.append(prop)
1870

1871
        def slotRecomputedObject(self, obj):
1872
            self.signal.append("ObjRecomputed")
1873
            self.parameter.append(obj)
1874

1875
        def slotAppendDynamicProperty(self, obj, prop):
1876
            self.signal.append("ObjAddDynProp")
1877
            self.parameter.append(obj)
1878
            self.parameter2.append(prop)
1879

1880
        def slotRemoveDynamicProperty(self, obj, prop):
1881
            self.signal.append("ObjRemoveDynProp")
1882
            self.parameter.append(obj)
1883
            self.parameter2.append(prop)
1884

1885
        def slotChangePropertyEditor(self, obj, prop):
1886
            self.signal.append("ObjChangePropEdit")
1887
            self.parameter.append(obj)
1888
            self.parameter2.append(prop)
1889

1890
        def slotStartSaveDocument(self, obj, name):
1891
            self.signal.append("DocStartSave")
1892
            self.parameter.append(obj)
1893
            self.parameter2.append(name)
1894

1895
        def slotFinishSaveDocument(self, obj, name):
1896
            self.signal.append("DocFinishSave")
1897
            self.parameter.append(obj)
1898
            self.parameter2.append(name)
1899

1900
        def slotBeforeAddingDynamicExtension(self, obj, extension):
1901
            self.signal.append("ObjBeforeDynExt")
1902
            self.parameter.append(obj)
1903
            self.parameter2.append(extension)
1904

1905
        def slotAddedDynamicExtension(self, obj, extension):
1906
            self.signal.append("ObjDynExt")
1907
            self.parameter.append(obj)
1908
            self.parameter2.append(extension)
1909

1910
    class GuiObserver:
1911
        def __init__(self):
1912
            self.clear()
1913

1914
        def clear(self):
1915
            self.signal = []
1916
            self.parameter = []
1917
            self.parameter2 = []
1918

1919
        def slotCreatedDocument(self, doc):
1920
            self.signal.append("DocCreated")
1921
            self.parameter.append(doc)
1922

1923
        def slotDeletedDocument(self, doc):
1924
            self.signal.append("DocDeleted")
1925
            self.parameter.append(doc)
1926

1927
        def slotRelabelDocument(self, doc):
1928
            self.signal.append("DocRelabled")
1929
            self.parameter.append(doc)
1930

1931
        def slotRenameDocument(self, doc):
1932
            self.signal.append("DocRenamed")
1933
            self.parameter.append(doc)
1934

1935
        def slotActivateDocument(self, doc):
1936
            self.signal.append("DocActivated")
1937
            self.parameter.append(doc)
1938

1939
        def slotCreatedObject(self, obj):
1940
            self.signal.append("ObjCreated")
1941
            self.parameter.append(obj)
1942

1943
        def slotDeletedObject(self, obj):
1944
            self.signal.append("ObjDeleted")
1945
            self.parameter.append(obj)
1946

1947
        def slotChangedObject(self, obj, prop):
1948
            self.signal.append("ObjChanged")
1949
            self.parameter.append(obj)
1950
            self.parameter2.append(prop)
1951

1952
        def slotInEdit(self, obj):
1953
            self.signal.append("ObjInEdit")
1954
            self.parameter.append(obj)
1955

1956
        def slotResetEdit(self, obj):
1957
            self.signal.append("ObjResetEdit")
1958
            self.parameter.append(obj)
1959

1960
    def setUp(self):
1961
        self.Obs = self.Observer()
1962
        FreeCAD.addDocumentObserver(self.Obs)
1963

1964
    def testRemoveObserver(self):
1965
        FreeCAD.removeDocumentObserver(self.Obs)
1966
        self.Obs.clear()
1967
        self.Doc1 = FreeCAD.newDocument("Observer")
1968
        FreeCAD.closeDocument(self.Doc1.Name)
1969
        self.assertEqual(len(self.Obs.signal), 0)
1970
        self.assertEqual(len(self.Obs.parameter2), 0)
1971
        self.assertEqual(len(self.Obs.signal), 0)
1972
        FreeCAD.addDocumentObserver(self.Obs)
1973

1974
    def testSave(self):
1975
        TempPath = tempfile.gettempdir()
1976
        SaveName = TempPath + os.sep + "SaveRestoreTests.FCStd"
1977
        self.Doc1 = FreeCAD.newDocument("Observer1")
1978
        self.Doc1.saveAs(SaveName)
1979
        self.assertEqual(self.Obs.signal.pop(), "DocFinishSave")
1980
        self.assertEqual(self.Obs.parameter2.pop(), self.Doc1.FileName)
1981
        self.assertEqual(self.Obs.signal.pop(), "DocStartSave")
1982
        self.assertEqual(self.Obs.parameter2.pop(), self.Doc1.FileName)
1983
        FreeCAD.closeDocument(self.Doc1.Name)
1984

1985
    def testDocument(self):
1986
        # in case another document already exists then the tests cannot
1987
        # be done reliably
1988
        if FreeCAD.GuiUp and FreeCAD.activeDocument():
1989
            return
1990

1991
        # testing document level signals
1992
        self.Doc1 = FreeCAD.newDocument("Observer1")
1993
        if FreeCAD.GuiUp:
1994
            self.assertEqual(self.Obs.signal.pop(0), "DocActivated")
1995
            self.assertTrue(self.Obs.parameter.pop(0) is self.Doc1)
1996
        self.assertEqual(self.Obs.signal.pop(0), "DocCreated")
1997
        self.assertTrue(self.Obs.parameter.pop(0) is self.Doc1)
1998
        self.assertEqual(self.Obs.signal.pop(0), "DocBeforeChange")
1999
        self.assertTrue(self.Obs.parameter.pop(0) is self.Doc1)
2000
        self.assertEqual(self.Obs.parameter2.pop(0), "Label")
2001
        self.assertEqual(self.Obs.signal.pop(0), "DocChanged")
2002
        self.assertTrue(self.Obs.parameter.pop(0) is self.Doc1)
2003
        self.assertEqual(self.Obs.parameter2.pop(0), "Label")
2004
        self.assertEqual(self.Obs.signal.pop(0), "DocRelabled")
2005
        self.assertTrue(self.Obs.parameter.pop(0) is self.Doc1)
2006
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2007

2008
        self.Doc2 = FreeCAD.newDocument("Observer2")
2009
        if FreeCAD.GuiUp:
2010
            self.assertEqual(self.Obs.signal.pop(0), "DocActivated")
2011
            self.assertTrue(self.Obs.parameter.pop(0) is self.Doc2)
2012
        self.assertEqual(self.Obs.signal.pop(0), "DocCreated")
2013
        self.assertTrue(self.Obs.parameter.pop(0) is self.Doc2)
2014
        self.assertEqual(self.Obs.signal.pop(0), "DocBeforeChange")
2015
        self.assertTrue(self.Obs.parameter.pop(0) is self.Doc2)
2016
        self.assertEqual(self.Obs.parameter2.pop(0), "Label")
2017
        self.assertEqual(self.Obs.signal.pop(0), "DocChanged")
2018
        self.assertTrue(self.Obs.parameter.pop(0) is self.Doc2)
2019
        self.assertEqual(self.Obs.parameter2.pop(0), "Label")
2020
        self.assertEqual(self.Obs.signal.pop(0), "DocRelabled")
2021
        self.assertTrue(self.Obs.parameter.pop(0) is self.Doc2)
2022
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2023

2024
        FreeCAD.setActiveDocument("Observer1")
2025
        self.assertEqual(self.Obs.signal.pop(), "DocActivated")
2026
        self.assertTrue(self.Obs.parameter.pop() is self.Doc1)
2027
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2028

2029
        # undo/redo is not enabled in cmd line mode by default
2030
        self.Doc2.UndoMode = 1
2031

2032
        # Must set Doc2 as active document before start transaction test. If not,
2033
        # then a transaction will be auto created inside the active document if a
2034
        # new transaction is triggered from a non active document
2035
        FreeCAD.setActiveDocument("Observer2")
2036
        self.assertEqual(self.Obs.signal.pop(), "DocActivated")
2037
        self.assertTrue(self.Obs.parameter.pop() is self.Doc2)
2038
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2039

2040
        self.Doc2.openTransaction("test")
2041
        # openTransaction() now only setup pending transaction, which will only be
2042
        # created when there is actual change
2043
        self.Doc2.addObject("App::FeatureTest", "test")
2044
        self.assertEqual(self.Obs.signal[0], "DocOpenTransaction")
2045
        self.assertEqual(self.Obs.signal.count("DocOpenTransaction"), 1)
2046
        self.assertTrue(self.Obs.parameter[0] is self.Doc2)
2047
        self.assertEqual(self.Obs.parameter2[0], "test")
2048
        self.Obs.clear()
2049

2050
        self.Doc2.commitTransaction()
2051
        self.assertEqual(self.Obs.signal.pop(), "DocCommitTransaction")
2052
        self.assertTrue(self.Obs.parameter.pop() is self.Doc2)
2053
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2054

2055
        self.Doc2.openTransaction("test2")
2056
        # openTransaction() now only setup pending transaction, which will only be
2057
        # created when there is actual change
2058
        self.Doc2.addObject("App::FeatureTest", "test")
2059
        self.assertEqual(self.Obs.signal[0], "DocOpenTransaction")
2060
        self.assertEqual(self.Obs.signal.count("DocOpenTransaction"), 1)
2061
        self.assertTrue(self.Obs.parameter[0] is self.Doc2)
2062
        self.assertEqual(self.Obs.parameter2[0], "test2")
2063
        # there will be other signals because of the addObject()
2064
        self.Obs.clear()
2065

2066
        self.Doc2.abortTransaction()
2067
        self.assertEqual(self.Obs.signal.pop(), "DocAbortTransaction")
2068
        self.assertTrue(self.Obs.parameter.pop() is self.Doc2)
2069
        # there will be other signals because of aborting the above addObject()
2070
        self.Obs.clear()
2071

2072
        self.Doc2.undo()
2073
        self.assertEqual(self.Obs.signal.pop(), "DocUndo")
2074
        self.assertTrue(self.Obs.parameter.pop() is self.Doc2)
2075
        # there will be other signals because undoing the above addObject()
2076
        self.Obs.clear()
2077

2078
        self.Doc2.redo()
2079
        self.assertEqual(self.Obs.signal.pop(), "DocRedo")
2080
        self.assertTrue(self.Obs.parameter.pop() is self.Doc2)
2081
        # there will be other signals because redoing the above addObject()
2082
        self.Obs.clear()
2083

2084
        self.Doc1.Comment = "test comment"
2085
        self.assertEqual(self.Obs.signal.pop(0), "DocBeforeChange")
2086
        self.assertTrue(self.Obs.parameter.pop(0) is self.Doc1)
2087
        self.assertEqual(self.Obs.parameter2.pop(0), "Comment")
2088
        self.assertEqual(self.Obs.signal.pop(0), "DocChanged")
2089
        self.assertTrue(self.Obs.parameter.pop(0) is self.Doc1)
2090
        self.assertEqual(self.Obs.parameter2.pop(0), "Comment")
2091

2092
        FreeCAD.closeDocument(self.Doc2.Name)
2093
        self.assertEqual(self.Obs.signal.pop(), "DocDeleted")
2094
        self.assertTrue(self.Obs.parameter.pop() is self.Doc2)
2095
        if FreeCAD.GuiUp:
2096
            # only has document activated signal when running in GUI mode
2097
            self.assertEqual(self.Obs.signal.pop(), "DocActivated")
2098
            self.assertTrue(self.Obs.parameter.pop() is self.Doc1)
2099
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2100

2101
        FreeCAD.closeDocument(self.Doc1.Name)
2102
        self.assertEqual(self.Obs.signal.pop(), "DocDeleted")
2103
        self.assertEqual(self.Obs.parameter.pop(), self.Doc1)
2104
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2105

2106
    def testObject(self):
2107
        # testing signal on object changes
2108

2109
        self.Doc1 = FreeCAD.newDocument("Observer1")
2110
        self.Obs.clear()
2111

2112
        obj = self.Doc1.addObject("App::DocumentObject", "obj")
2113
        self.assertTrue(self.Obs.signal.pop() == "ObjCreated")
2114
        self.assertTrue(self.Obs.parameter.pop() is obj)
2115
        # there are multiple object change signals
2116
        self.Obs.clear()
2117

2118
        obj.Label = "myobj"
2119
        self.assertTrue(self.Obs.signal.pop(0) == "ObjBeforeChange")
2120
        self.assertTrue(self.Obs.parameter.pop(0) is obj)
2121
        self.assertTrue(self.Obs.parameter2.pop(0) == "Label")
2122
        self.assertTrue(self.Obs.signal.pop(0) == "ObjChanged")
2123
        self.assertTrue(self.Obs.parameter.pop(0) is obj)
2124
        self.assertTrue(self.Obs.parameter2.pop(0) == "Label")
2125
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2126

2127
        obj.enforceRecompute()
2128
        obj.recompute()
2129
        self.assertTrue(self.Obs.signal.pop(0) == "ObjRecomputed")
2130
        self.assertTrue(self.Obs.parameter.pop(0) is obj)
2131
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2132

2133
        obj.enforceRecompute()
2134
        self.Doc1.recompute()
2135
        self.assertTrue(self.Obs.signal.pop(0) == "ObjRecomputed")
2136
        self.assertTrue(self.Obs.parameter.pop(0) is obj)
2137
        self.assertTrue(self.Obs.signal.pop(0) == "DocRecomputed")
2138
        self.assertTrue(self.Obs.parameter.pop(0) is self.Doc1)
2139
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2140

2141
        FreeCAD.ActiveDocument.removeObject(obj.Name)
2142
        self.assertTrue(self.Obs.signal.pop(0) == "ObjDeleted")
2143
        self.assertTrue(self.Obs.parameter.pop(0) is obj)
2144
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2145

2146
        pyobj = self.Doc1.addObject("App::FeaturePython", "pyobj")
2147
        self.Obs.clear()
2148
        pyobj.addProperty("App::PropertyLength", "Prop", "Group", "test property")
2149
        self.assertTrue(self.Obs.signal.pop() == "ObjAddDynProp")
2150
        self.assertTrue(self.Obs.parameter.pop() is pyobj)
2151
        self.assertTrue(self.Obs.parameter2.pop() == "Prop")
2152
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2153

2154
        pyobj.setEditorMode("Prop", ["ReadOnly"])
2155
        self.assertTrue(self.Obs.signal.pop() == "ObjChangePropEdit")
2156
        self.assertTrue(self.Obs.parameter.pop() is pyobj)
2157
        self.assertTrue(self.Obs.parameter2.pop() == "Prop")
2158
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2159

2160
        pyobj.removeProperty("Prop")
2161
        self.assertTrue(self.Obs.signal.pop() == "ObjRemoveDynProp")
2162
        self.assertTrue(self.Obs.parameter.pop() is pyobj)
2163
        self.assertTrue(self.Obs.parameter2.pop() == "Prop")
2164
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2165

2166
        pyobj.addExtension("App::GroupExtensionPython")
2167
        self.assertTrue(self.Obs.signal.pop() == "ObjDynExt")
2168
        self.assertTrue(self.Obs.parameter.pop() is pyobj)
2169
        self.assertTrue(self.Obs.parameter2.pop() == "App::GroupExtensionPython")
2170
        self.assertTrue(self.Obs.signal.pop(0) == "ObjBeforeDynExt")
2171
        self.assertTrue(self.Obs.parameter.pop(0) is pyobj)
2172
        self.assertTrue(self.Obs.parameter2.pop(0) == "App::GroupExtensionPython")
2173
        # a proxy property was changed, hence those events are also in the signal list
2174
        self.Obs.clear()
2175

2176
        FreeCAD.closeDocument(self.Doc1.Name)
2177
        self.Obs.clear()
2178

2179
    def testUndoDisabledDocument(self):
2180

2181
        # testing document level signals
2182
        self.Doc1 = FreeCAD.newDocument("Observer1")
2183
        self.Doc1.UndoMode = 0
2184
        self.Obs.clear()
2185

2186
        self.Doc1.openTransaction("test")
2187
        self.Doc1.commitTransaction()
2188
        self.Doc1.undo()
2189
        self.Doc1.redo()
2190
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2191

2192
        FreeCAD.closeDocument(self.Doc1.Name)
2193
        self.Obs.clear()
2194

2195
    def testGuiObserver(self):
2196

2197
        if not FreeCAD.GuiUp:
2198
            return
2199

2200
        # in case another document already exists then the tests cannot
2201
        # be done reliably
2202
        if FreeCAD.activeDocument():
2203
            return
2204

2205
        self.GuiObs = self.GuiObserver()
2206
        FreeCAD.Gui.addDocumentObserver(self.GuiObs)
2207
        self.Doc1 = FreeCAD.newDocument("Observer1")
2208
        self.GuiDoc1 = FreeCAD.Gui.getDocument(self.Doc1.Name)
2209
        self.Obs.clear()
2210
        self.assertTrue(self.GuiObs.signal.pop(0) == "DocCreated")
2211
        self.assertTrue(self.GuiObs.parameter.pop(0) is self.GuiDoc1)
2212
        self.assertTrue(self.GuiObs.signal.pop(0) == "DocActivated")
2213
        self.assertTrue(self.GuiObs.parameter.pop(0) is self.GuiDoc1)
2214
        self.assertTrue(self.GuiObs.signal.pop(0) == "DocRelabled")
2215
        self.assertTrue(self.GuiObs.parameter.pop(0) is self.GuiDoc1)
2216
        self.assertTrue(
2217
            not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2
2218
        )
2219

2220
        self.Doc1.Label = "test"
2221
        self.assertTrue(self.Obs.signal.pop() == "DocRelabled")
2222
        self.assertTrue(self.Obs.parameter.pop() is self.Doc1)
2223
        # not interested in the change signals
2224
        self.Obs.clear()
2225
        self.assertTrue(self.GuiObs.signal.pop(0) == "DocRelabled")
2226
        self.assertTrue(self.GuiObs.parameter.pop(0) is self.GuiDoc1)
2227
        self.assertTrue(
2228
            not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2
2229
        )
2230

2231
        FreeCAD.setActiveDocument(self.Doc1.Name)
2232
        self.assertTrue(self.Obs.signal.pop() == "DocActivated")
2233
        self.assertTrue(self.Obs.parameter.pop() is self.Doc1)
2234
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2235
        self.assertTrue(self.GuiObs.signal.pop() == "DocActivated")
2236
        self.assertTrue(self.GuiObs.parameter.pop() is self.GuiDoc1)
2237
        self.assertTrue(
2238
            not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2
2239
        )
2240

2241
        obj = self.Doc1.addObject("App::FeaturePython", "obj")
2242
        self.assertTrue(self.Obs.signal.pop() == "ObjCreated")
2243
        self.assertTrue(self.Obs.parameter.pop() is obj)
2244
        # there are multiple object change signals
2245
        self.Obs.clear()
2246
        self.assertTrue(self.GuiObs.signal.pop() == "ObjCreated")
2247
        self.assertTrue(self.GuiObs.parameter.pop() is obj.ViewObject)
2248

2249
        # There are object change signals, caused by sync of obj.Visibility. Same below.
2250
        self.GuiObs.clear()
2251

2252
        obj.ViewObject.Visibility = False
2253
        self.assertTrue(self.Obs.signal.pop() == "ObjChanged")
2254
        self.assertTrue(self.Obs.parameter.pop() is obj)
2255
        self.assertTrue(self.Obs.parameter2.pop() == "Visibility")
2256
        self.assertTrue(self.Obs.signal.pop() == "ObjBeforeChange")
2257
        self.assertTrue(self.Obs.parameter.pop() is obj)
2258
        self.assertTrue(self.Obs.parameter2.pop() == "Visibility")
2259
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2260
        self.assertTrue(self.GuiObs.signal.pop(0) == "ObjChanged")
2261
        self.assertTrue(self.GuiObs.parameter.pop(0) is obj.ViewObject)
2262
        self.assertTrue(self.GuiObs.parameter2.pop(0) == "Visibility")
2263
        self.assertTrue(
2264
            not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2
2265
        )
2266

2267
        obj.ViewObject.addProperty("App::PropertyLength", "Prop", "Group", "test property")
2268
        self.assertTrue(self.Obs.signal.pop() == "ObjAddDynProp")
2269
        self.assertTrue(self.Obs.parameter.pop() is obj.ViewObject)
2270
        self.assertTrue(self.Obs.parameter2.pop() == "Prop")
2271
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2272
        self.assertTrue(
2273
            not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2
2274
        )
2275

2276
        obj.ViewObject.setEditorMode("Prop", ["ReadOnly"])
2277
        self.assertTrue(self.Obs.signal.pop() == "ObjChangePropEdit")
2278
        self.assertTrue(self.Obs.parameter.pop() is obj.ViewObject)
2279
        self.assertTrue(self.Obs.parameter2.pop() == "Prop")
2280
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2281
        self.assertTrue(
2282
            not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2
2283
        )
2284

2285
        obj.ViewObject.removeProperty("Prop")
2286
        self.assertTrue(self.Obs.signal.pop() == "ObjRemoveDynProp")
2287
        self.assertTrue(self.Obs.parameter.pop() is obj.ViewObject)
2288
        self.assertTrue(self.Obs.parameter2.pop() == "Prop")
2289
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2290
        self.assertTrue(
2291
            not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2
2292
        )
2293

2294
        self.GuiDoc1.setEdit("obj", 0)
2295
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2296
        self.assertTrue(self.GuiObs.signal.pop(0) == "ObjInEdit")
2297
        self.assertTrue(self.GuiObs.parameter.pop(0) is obj.ViewObject)
2298
        self.assertTrue(
2299
            not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2
2300
        )
2301

2302
        self.GuiDoc1.resetEdit()
2303
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2304
        self.assertTrue(self.GuiObs.signal.pop(0) == "ObjResetEdit")
2305
        self.assertTrue(self.GuiObs.parameter.pop(0) is obj.ViewObject)
2306
        self.assertTrue(
2307
            not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2
2308
        )
2309

2310
        obj.ViewObject.addExtension("Gui::ViewProviderGroupExtensionPython")
2311
        self.assertTrue(self.Obs.signal.pop() == "ObjDynExt")
2312
        self.assertTrue(self.Obs.parameter.pop() is obj.ViewObject)
2313
        self.assertTrue(self.Obs.parameter2.pop() == "Gui::ViewProviderGroupExtensionPython")
2314
        self.assertTrue(self.Obs.signal.pop() == "ObjBeforeDynExt")
2315
        self.assertTrue(self.Obs.parameter.pop() is obj.ViewObject)
2316
        self.assertTrue(self.Obs.parameter2.pop() == "Gui::ViewProviderGroupExtensionPython")
2317
        # a proxy property was changed, hence those events are also in the signal list (but of GUI observer)
2318
        self.GuiObs.clear()
2319

2320
        vo = obj.ViewObject
2321
        FreeCAD.ActiveDocument.removeObject(obj.Name)
2322
        self.assertTrue(self.Obs.signal.pop(0) == "ObjDeleted")
2323
        self.assertTrue(self.Obs.parameter.pop(0) is obj)
2324
        self.assertTrue(not self.Obs.signal and not self.Obs.parameter and not self.Obs.parameter2)
2325
        self.assertTrue(self.GuiObs.signal.pop() == "ObjDeleted")
2326
        self.assertTrue(self.GuiObs.parameter.pop() is vo)
2327
        self.assertTrue(
2328
            not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2
2329
        )
2330

2331
        FreeCAD.closeDocument(self.Doc1.Name)
2332
        self.Obs.clear()
2333
        self.assertTrue(self.GuiObs.signal.pop() == "DocDeleted")
2334
        self.assertTrue(self.GuiObs.parameter.pop() is self.GuiDoc1)
2335
        self.assertTrue(
2336
            not self.GuiObs.signal and not self.GuiObs.parameter and not self.GuiObs.parameter2
2337
        )
2338

2339
        FreeCAD.Gui.removeDocumentObserver(self.GuiObs)
2340
        self.GuiObs.clear()
2341

2342
    def tearDown(self):
2343
        # closing doc
2344
        FreeCAD.removeDocumentObserver(self.Obs)
2345
        self.Obs.clear()
2346
        self.Obs = None
2347

2348

2349
class FeatureTestColumn(unittest.TestCase):
2350
    def setUp(self):
2351
        doc = FreeCAD.newDocument("TestColumn")
2352
        self.obj = doc.addObject("App::FeatureTestColumn", "Column")
2353

2354
    def testEmpty(self):
2355
        value = self.obj.Value
2356
        self.obj.Column = ""
2357
        self.assertFalse(self.obj.recompute())
2358
        self.assertEqual(self.obj.Value, value)
2359

2360
    def testA(self):
2361
        self.obj.Column = "A"
2362
        self.obj.recompute()
2363
        self.assertEqual(self.obj.Value, 0)
2364

2365
    def testZ(self):
2366
        self.obj.Column = "Z"
2367
        self.obj.recompute()
2368
        self.assertEqual(self.obj.Value, 25)
2369

2370
    def testAA(self):
2371
        self.obj.Column = "AA"
2372
        self.obj.recompute()
2373
        self.assertEqual(self.obj.Value, 26)
2374

2375
    def testAB(self):
2376
        self.obj.Column = "AB"
2377
        self.obj.recompute()
2378
        self.assertEqual(self.obj.Value, 27)
2379

2380
    def testAZ(self):
2381
        self.obj.Column = "AZ"
2382
        self.obj.recompute()
2383
        self.assertEqual(self.obj.Value, 51)
2384

2385
    def testBA(self):
2386
        self.obj.Column = "BA"
2387
        self.obj.recompute()
2388
        self.assertEqual(self.obj.Value, 52)
2389

2390
    def testCB(self):
2391
        self.obj.Column = "CB"
2392
        self.obj.recompute()
2393
        self.assertEqual(self.obj.Value, 79)
2394

2395
    def testZA(self):
2396
        self.obj.Column = "ZA"
2397
        self.obj.recompute()
2398
        self.assertEqual(self.obj.Value, 676)
2399

2400
    def testZZ(self):
2401
        self.obj.Column = "ZZ"
2402
        self.obj.recompute()
2403
        self.assertEqual(self.obj.Value, 701)
2404

2405
    def testAAA(self):
2406
        self.obj.Column = "AAA"
2407
        self.obj.recompute()
2408
        self.assertEqual(self.obj.Value, 702)
2409

2410
    def testAAZ(self):
2411
        self.obj.Column = "AAZ"
2412
        self.obj.recompute()
2413
        self.assertEqual(self.obj.Value, 727)
2414

2415
    def testCBA(self):
2416
        self.obj.Column = "CBA"
2417
        self.obj.recompute()
2418
        self.assertEqual(self.obj.Value, 2080)
2419

2420
    def testAZA(self):
2421
        self.obj.Column = "AZA"
2422
        self.obj.recompute()
2423
        self.assertEqual(self.obj.Value, 1352)
2424

2425
    def testZZA(self):
2426
        self.obj.Column = "ZZA"
2427
        self.obj.recompute()
2428
        self.assertEqual(self.obj.Value, 18252)
2429

2430
    def testZZZ(self):
2431
        self.obj.Column = "ZZZ"
2432
        self.obj.recompute()
2433
        self.assertEqual(self.obj.Value, 18277)
2434

2435
    def testALL(self):
2436
        self.obj.Column = "ALL"
2437
        self.obj.recompute()
2438
        self.assertEqual(self.obj.Value, 999)
2439

2440
    def testAb(self):
2441
        value = self.obj.Value
2442
        self.obj.Column = "Ab"
2443
        self.assertFalse(self.obj.recompute())
2444
        self.assertEqual(self.obj.Value, value)
2445

2446
    def testABCD(self):
2447
        value = self.obj.Value
2448
        self.obj.Column = "ABCD"
2449
        self.assertFalse(self.obj.recompute())
2450
        self.assertEqual(self.obj.Value, value)
2451

2452
    def testEmptySilent(self):
2453
        self.obj.Column = ""
2454
        self.obj.Silent = True
2455
        self.assertTrue(self.obj.recompute())
2456
        self.assertEqual(self.obj.Value, -1)
2457

2458
    def testAbSilent(self):
2459
        self.obj.Column = "Ab"
2460
        self.obj.Silent = True
2461
        self.assertTrue(self.obj.recompute())
2462
        self.assertEqual(self.obj.Value, -1)
2463

2464
    def testABCDSilent(self):
2465
        self.obj.Column = "ABCD"
2466
        self.obj.Silent = True
2467
        self.assertTrue(self.obj.recompute())
2468
        self.assertEqual(self.obj.Value, -1)
2469

2470
    def tearDown(self):
2471
        FreeCAD.closeDocument("TestColumn")
2472

2473

2474
class FeatureTestRow(unittest.TestCase):
2475
    def setUp(self):
2476
        doc = FreeCAD.newDocument("TestRow")
2477
        self.obj = doc.addObject("App::FeatureTestRow", "Row")
2478

2479
    def testEmpty(self):
2480
        self.obj.Silent = True
2481
        self.obj.Row = ""
2482
        self.obj.recompute()
2483
        self.assertEqual(self.obj.Value, -1)
2484

2485
    def testA(self):
2486
        self.obj.Silent = True
2487
        self.obj.Row = "A"
2488
        self.obj.recompute()
2489
        self.assertEqual(self.obj.Value, -1)
2490

2491
    def testException(self):
2492
        value = self.obj.Value
2493
        self.obj.Row = "A"
2494
        self.assertFalse(self.obj.recompute())
2495
        self.assertEqual(self.obj.Value, value)
2496

2497
    def test0(self):
2498
        self.obj.Silent = True
2499
        self.obj.Row = "0"
2500
        self.obj.recompute()
2501
        self.assertEqual(self.obj.Value, -1)
2502

2503
    def test1(self):
2504
        self.obj.Silent = True
2505
        self.obj.Row = "1"
2506
        self.obj.recompute()
2507
        self.assertEqual(self.obj.Value, 0)
2508

2509
    def test16384(self):
2510
        self.obj.Silent = True
2511
        self.obj.Row = "16384"
2512
        self.obj.recompute()
2513
        self.assertEqual(self.obj.Value, 16383)
2514

2515
    def test16385(self):
2516
        self.obj.Silent = True
2517
        self.obj.Row = "16385"
2518
        self.obj.recompute()
2519
        self.assertEqual(self.obj.Value, -1)
2520

2521
    def tearDown(self):
2522
        FreeCAD.closeDocument("TestRow")
2523

2524

2525
class FeatureTestAbsAddress(unittest.TestCase):
2526
    def setUp(self):
2527
        doc = FreeCAD.newDocument("TestAbsAddress")
2528
        self.obj = doc.addObject("App::FeatureTestAbsAddress", "Cell")
2529

2530
    def testAbsoluteA12(self):
2531
        self.obj.Address = "$A$12"
2532
        self.obj.recompute()
2533
        self.assertEqual(self.obj.Valid, True)
2534

2535
    def testAbsoluteA13(self):
2536
        self.obj.Address = "A$13"
2537
        self.obj.recompute()
2538
        self.assertEqual(self.obj.Valid, True)
2539

2540
    def testAbsoluteAA13(self):
2541
        self.obj.Address = "AA$13"
2542
        self.obj.recompute()
2543
        self.assertEqual(self.obj.Valid, True)
2544

2545
    def testAbsoluteZZ12(self):
2546
        self.obj.Address = "$ZZ$12"
2547
        self.obj.recompute()
2548
        self.assertEqual(self.obj.Valid, True)
2549

2550
    def testAbsoluteABC1(self):
2551
        self.obj.Address = "$ABC1"
2552
        self.obj.recompute()
2553
        self.assertEqual(self.obj.Valid, False)
2554

2555
    def testAbsoluteABC2(self):
2556
        self.obj.Address = "ABC$2"
2557
        self.obj.recompute()
2558
        self.assertEqual(self.obj.Valid, False)
2559

2560
    def testRelative(self):
2561
        self.obj.Address = "A1"
2562
        self.obj.recompute()
2563
        self.assertEqual(self.obj.Valid, False)
2564

2565
    def testInvalid(self):
2566
        self.obj.Address = "A"
2567
        self.obj.recompute()
2568
        self.assertEqual(self.obj.Valid, False)
2569

2570
    def testEmpty(self):
2571
        self.obj.Address = ""
2572
        self.obj.recompute()
2573
        self.assertEqual(self.obj.Valid, False)
2574

2575
    def tearDown(self):
2576
        FreeCAD.closeDocument("TestAbsAddress")
2577

2578

2579
class FeatureTestAttribute(unittest.TestCase):
2580
    def setUp(self):
2581
        self.doc = FreeCAD.newDocument("TestAttribute")
2582
        self.doc.UndoMode = 0
2583

2584
    def testValidAttribute(self):
2585
        obj = self.doc.addObject("App::FeatureTestAttribute", "Attribute")
2586
        obj.Object = obj
2587
        obj.Attribute = "Name"
2588
        self.doc.recompute()
2589
        self.assertIn("Up-to-date", obj.State)
2590

2591
    def testInvalidAttribute(self):
2592
        obj = self.doc.addObject("App::FeatureTestAttribute", "Attribute")
2593
        obj.Object = obj
2594
        obj.Attribute = "Name123"
2595
        self.doc.recompute()
2596
        self.assertIn("Invalid", obj.State)
2597
        self.assertIn("Touched", obj.State)
2598

2599
    def testRemoval(self):
2600
        obj = self.doc.addObject("App::FeatureTestAttribute", "Attribute")
2601
        obj.Object = obj
2602
        self.assertEqual(self.doc.removeObject("Attribute"), None)
2603

2604
    def tearDown(self):
2605
        FreeCAD.closeDocument("TestAttribute")
2606

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

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

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

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