FreeCAD
1577 строк · 64.8 Кб
1# ***************************************************************************
2# * Copyright (c) 2016 Werner Mayer <wmayer[at]users.sourceforge.net> *
3# * Copyright (c) 2016 Eivind Kvedalen <eivind@kvedalen.name> *
4# * *
5# * This program is free software; you can redistribute it and/or modify *
6# * it under the terms of the GNU General Public License (GPL) *
7# * as published by the Free Software Foundation; either version 2 of *
8# * the License, or (at your option) any later version. *
9# * for detail see the LICENCE text file. *
10# * *
11# * FreeCAD is distributed in the hope that it will be useful, *
12# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14# * GNU Library General Public License for more details. *
15# * *
16# * You should have received a copy of the GNU Library General Public *
17# * License along with FreeCAD; if not, write to the Free Software *
18# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
19# * USA *
20# ***************************************************************************/
21
22import os
23import sys
24import math
25from math import sqrt
26import unittest
27import FreeCAD
28import Part
29import Sketcher
30import tempfile
31from FreeCAD import Base
32from FreeCAD import Units
33
34v = Base.Vector
35
36# ----------------------------------------------------------------------------------
37# define the functions to test the FreeCAD Spreadsheet module and expression engine
38# ----------------------------------------------------------------------------------
39
40
41class SpreadsheetCases(unittest.TestCase):
42def setUp(self):
43self.doc = FreeCAD.newDocument()
44self.TempPath = tempfile.gettempdir()
45FreeCAD.Console.PrintLog(" Using temp path: " + self.TempPath + "\n")
46
47def testAggregates(self):
48"""Test all aggregate functions"""
49sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
50sheet.set("B13", "4")
51sheet.set("B14", "5")
52sheet.set("B15", "6")
53sheet.set("C13", "4mm")
54sheet.set("C14", "5mm")
55sheet.set("C15", "6mm")
56sheet.set("C16", "6")
57
58sheet.set("A1", "=sum(1)")
59sheet.set("A2", "=sum(1;2)")
60sheet.set("A3", "=sum(1;2;3)")
61sheet.set("A4", "=sum(1;2;3;B13)")
62sheet.set("A5", "=sum(1;2;3;B13:B15)")
63
64sheet.set("B1", "=min(1)")
65sheet.set("B2", "=min(1;2)")
66sheet.set("B3", "=min(1;2;3)")
67sheet.set("B4", "=min(1;2;3;B13)")
68sheet.set("B5", "=min(1;2;3;B13:B15)")
69
70sheet.set("C1", "=max(1)")
71sheet.set("C2", "=max(1;2)")
72sheet.set("C3", "=max(1;2;3)")
73sheet.set("C4", "=max(1;2;3;B13)")
74sheet.set("C5", "=max(1;2;3;B13:B15)")
75
76sheet.set("D1", "=stddev(1)")
77sheet.set("D2", "=stddev(1;2)")
78sheet.set("D3", "=stddev(1;2;3)")
79sheet.set("D4", "=stddev(1;2;3;B13)")
80sheet.set("D5", "=stddev(1;2;3;B13:B15)")
81
82sheet.set("E1", "=count(1)")
83sheet.set("E2", "=count(1;2)")
84sheet.set("E3", "=count(1;2;3)")
85sheet.set("E4", "=count(1;2;3;B13)")
86sheet.set("E5", "=count(1;2;3;B13:B15)")
87
88sheet.set("F1", "=average(1)")
89sheet.set("F2", "=average(1;2)")
90sheet.set("F3", "=average(1;2;3)")
91sheet.set("F4", "=average(1;2;3;B13)")
92sheet.set("F5", "=average(1;2;3;B13:B15)")
93
94sheet.set("G1", "=average(C13:C15)")
95sheet.set("G2", "=min(C13:C15)")
96sheet.set("G3", "=max(C13:C15)")
97sheet.set("G4", "=count(C13:C15)")
98sheet.set("G5", "=stddev(C13:C15)")
99sheet.set("G6", "=sum(C13:C15)")
100
101sheet.set("H1", "=average(C13:C16)")
102sheet.set("H2", "=min(C13:C16)")
103sheet.set("H3", "=max(C13:C16)")
104sheet.set("H4", "=count(C13:C16)")
105sheet.set("H5", "=stddev(C13:C16)")
106sheet.set("H6", "=sum(C13:C16)")
107
108self.doc.recompute()
109self.assertEqual(sheet.A1, 1)
110self.assertEqual(sheet.A2, 3)
111self.assertEqual(sheet.A3, 6)
112self.assertEqual(sheet.A4, 10)
113self.assertEqual(sheet.A5, 21)
114
115self.assertEqual(sheet.B1, 1)
116self.assertEqual(sheet.B2, 1)
117self.assertEqual(sheet.B3, 1)
118self.assertEqual(sheet.B4, 1)
119self.assertEqual(sheet.B5, 1)
120
121self.assertEqual(sheet.C1, 1)
122self.assertEqual(sheet.C2, 2)
123self.assertEqual(sheet.C3, 3)
124self.assertEqual(sheet.C4, 4)
125self.assertEqual(sheet.C5, 6)
126
127self.assertTrue(
128sheet.D1.startswith("ERR: Invalid number of entries: at least two required.")
129)
130self.assertEqual(sheet.D2, 0.7071067811865476)
131self.assertEqual(sheet.D3, 1.0)
132self.assertEqual(sheet.D4, 1.2909944487358056)
133self.assertEqual(sheet.D5, 1.8708286933869707)
134
135self.assertEqual(sheet.E1, 1)
136self.assertEqual(sheet.E2, 2)
137self.assertEqual(sheet.E3, 3)
138self.assertEqual(sheet.E4, 4)
139self.assertEqual(sheet.E5, 6)
140
141self.assertEqual(sheet.F1, 1)
142self.assertEqual(sheet.F2, (1.0 + 2.0) / 2.0)
143self.assertEqual(sheet.F3, (1.0 + 2 + 3) / 3)
144self.assertEqual(sheet.F4, (1.0 + 2 + 3 + 4) / 4)
145self.assertEqual(sheet.F5, (1.0 + 2 + 3 + 4 + 5 + 6) / 6)
146
147self.assertEqual(sheet.G1, Units.Quantity("5 mm"))
148self.assertEqual(sheet.G2, Units.Quantity("4 mm"))
149self.assertEqual(sheet.G3, Units.Quantity("6 mm"))
150self.assertEqual(sheet.G4, 3)
151self.assertEqual(sheet.G5, Units.Quantity("1 mm"))
152self.assertEqual(sheet.G6, Units.Quantity("15 mm"))
153
154self.assertTrue(
155sheet.H1.startswith("ERR: Quantity::operator +=(): Unit mismatch in plus operation")
156)
157self.assertTrue(
158sheet.H2.startswith(
159"ERR: Quantity::operator <(): quantities need to have same unit to compare"
160)
161)
162self.assertTrue(
163sheet.H3.startswith(
164"ERR: Quantity::operator >(): quantities need to have same unit to compare"
165)
166)
167self.assertEqual(sheet.H4, 4)
168self.assertTrue(
169sheet.H5.startswith("ERR: Quantity::operator -(): Unit mismatch in minus operation")
170)
171self.assertTrue(
172sheet.H6.startswith("ERR: Quantity::operator +=(): Unit mismatch in plus operation")
173)
174
175def assertMostlyEqual(self, a, b):
176if type(a) is Units.Quantity:
177self.assertTrue(math.fabs(a.Value - b.Value) < 1e-14)
178self.assertTrue(a.Unit == b.Unit)
179else:
180self.assertTrue(math.fabs(a - b) < 1e-14)
181
182def testFunctions(self):
183"""Test all built-in simple functions"""
184sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
185sheet.set("A1", "=cos(60)") # Cos
186sheet.set("B1", "=cos(60deg)")
187sheet.set("C1", "=cos(pi / 2 * 1rad)")
188sheet.set("A2", "=sin(30)") # Sin
189sheet.set("B2", "=sin(30deg)")
190sheet.set("C2", "=sin(pi / 6 * 1rad)")
191sheet.set("A3", "=tan(45)") # Tan
192sheet.set("B3", "=tan(45deg)")
193sheet.set("C3", "=tan(pi / 4 * 1rad)")
194sheet.set("A4", "=abs(3)") # Abs
195sheet.set("B4", "=abs(-3)")
196sheet.set("C4", "=abs(-3mm)")
197sheet.set("A5", "=exp(3)") # Exp
198sheet.set("B5", "=exp(-3)")
199sheet.set("C5", "=exp(-3mm)")
200sheet.set("A6", "=log(3)") # Log
201sheet.set("B6", "=log(-3)")
202sheet.set("C6", "=log(-3mm)")
203sheet.set("A7", "=log10(10)") # Log10
204sheet.set("B7", "=log10(-3)")
205sheet.set("C7", "=log10(-3mm)")
206sheet.set("A8", "=round(3.4)") # Round
207sheet.set("B8", "=round(3.6)")
208sheet.set("C8", "=round(-3.4)")
209sheet.set("D8", "=round(-3.6)")
210sheet.set("E8", "=round(3.4mm)")
211sheet.set("F8", "=round(3.6mm)")
212sheet.set("G8", "=round(-3.4mm)")
213sheet.set("H8", "=round(-3.6mm)")
214sheet.set("A9", "=trunc(3.4)") # Trunc
215sheet.set("B9", "=trunc(3.6)")
216sheet.set("C9", "=trunc(-3.4)")
217sheet.set("D9", "=trunc(-3.6)")
218sheet.set("E9", "=trunc(3.4mm)")
219sheet.set("F9", "=trunc(3.6mm)")
220sheet.set("G9", "=trunc(-3.4mm)")
221sheet.set("H9", "=trunc(-3.6mm)")
222sheet.set("A10", "=ceil(3.4)") # Ceil
223sheet.set("B10", "=ceil(3.6)")
224sheet.set("C10", "=ceil(-3.4)")
225sheet.set("D10", "=ceil(-3.6)")
226sheet.set("E10", "=ceil(3.4mm)")
227sheet.set("F10", "=ceil(3.6mm)")
228sheet.set("G10", "=ceil(-3.4mm)")
229sheet.set("H10", "=ceil(-3.6mm)")
230sheet.set("A11", "=floor(3.4)") # Floor
231sheet.set("B11", "=floor(3.6)")
232sheet.set("C11", "=floor(-3.4)")
233sheet.set("D11", "=floor(-3.6)")
234sheet.set("E11", "=floor(3.4mm)")
235sheet.set("F11", "=floor(3.6mm)")
236sheet.set("G11", "=floor(-3.4mm)")
237sheet.set("H11", "=floor(-3.6mm)")
238sheet.set("A12", "=asin(0.5)") # Asin
239sheet.set("B12", "=asin(0.5mm)")
240sheet.set("A13", "=acos(0.5)") # Acos
241sheet.set("B13", "=acos(0.5mm)")
242sheet.set("A14", "=atan(sqrt(3))") # Atan
243sheet.set("B14", "=atan(0.5mm)")
244sheet.set("A15", "=sinh(0.5)") # Sinh
245sheet.set("B15", "=sinh(0.5mm)")
246sheet.set("A16", "=cosh(0.5)") # Cosh
247sheet.set("B16", "=cosh(0.5mm)")
248sheet.set("A17", "=tanh(0.5)") # Tanh
249sheet.set("B17", "=tanh(0.5mm)")
250sheet.set("A18", "=sqrt(4)") # Sqrt
251sheet.set("B18", "=sqrt(4mm^2)")
252sheet.set("A19", "=mod(7; 4)") # Mod
253sheet.set("B19", "=mod(-7; 4)")
254sheet.set("C19", "=mod(7mm; 4)")
255sheet.set("D19", "=mod(7mm; 4mm)")
256sheet.set("A20", "=atan2(3; 3)") # Atan2
257sheet.set("B20", "=atan2(-3; 3)")
258sheet.set("C20", "=atan2(3mm; 3)")
259sheet.set("D20", "=atan2(3mm; 3mm)")
260sheet.set("A21", "=pow(7; 4)") # Pow
261sheet.set("B21", "=pow(-7; 4)")
262sheet.set("C21", "=pow(7mm; 4)")
263sheet.set("D21", "=pow(7mm; 4mm)")
264sheet.set("A23", "=hypot(3; 4)") # Hypot
265sheet.set("B23", "=hypot(-3; 4)")
266sheet.set("C23", "=hypot(3mm; 4)")
267sheet.set("D23", "=hypot(3mm; 4mm)")
268sheet.set("A24", "=hypot(3; 4; 5)") # Hypot
269sheet.set("B24", "=hypot(-3; 4; 5)")
270sheet.set("C24", "=hypot(3mm; 4; 5)")
271sheet.set("D24", "=hypot(3mm; 4mm; 5mm)")
272sheet.set("A26", "=cath(5; 3)") # Cath
273sheet.set("B26", "=cath(-5; 3)")
274sheet.set("C26", "=cath(5mm; 3)")
275sheet.set("D26", "=cath(5mm; 3mm)")
276
277l = math.sqrt(5 * 5 + 4 * 4 + 3 * 3)
278sheet.set("A27", "=cath(%0.15f; 5; 4)" % l) # Cath
279sheet.set("B27", "=cath(%0.15f; -5; 4)" % l)
280sheet.set("C27", "=cath(%0.15f mm; 5mm; 4)" % l)
281sheet.set("D27", "=cath(%0.15f mm; 5mm; 4mm)" % l)
282
283self.doc.recompute()
284self.assertMostlyEqual(sheet.A1, 0.5) # Cos
285self.assertMostlyEqual(sheet.B1, 0.5)
286self.assertMostlyEqual(sheet.C1, 0)
287self.assertMostlyEqual(sheet.A2, 0.5) # Sin
288self.assertMostlyEqual(sheet.B2, 0.5)
289self.assertMostlyEqual(sheet.C2, 0.5)
290self.assertMostlyEqual(sheet.A3, 1) # Tan
291self.assertMostlyEqual(sheet.B3, 1)
292self.assertMostlyEqual(sheet.C3, 1)
293self.assertMostlyEqual(sheet.A4, 3) # Abs
294self.assertMostlyEqual(sheet.B4, 3)
295self.assertMostlyEqual(sheet.C4, Units.Quantity("3 mm"))
296self.assertMostlyEqual(sheet.A5, math.exp(3)) # Exp
297self.assertMostlyEqual(sheet.B5, math.exp(-3))
298self.assertTrue(sheet.C5.startswith("ERR: Unit must be empty."))
299self.assertMostlyEqual(sheet.A6, math.log(3)) # Log
300self.assertTrue(math.isnan(sheet.B6))
301self.assertTrue(sheet.C6.startswith("ERR: Unit must be empty."))
302self.assertMostlyEqual(sheet.A7, math.log10(10)) # Log10
303self.assertTrue(math.isnan(sheet.B7))
304self.assertTrue(sheet.C7.startswith("ERR: Unit must be empty."))
305self.assertMostlyEqual(sheet.A8, 3) # Round
306self.assertMostlyEqual(sheet.B8, 4)
307self.assertMostlyEqual(sheet.C8, -3)
308self.assertMostlyEqual(sheet.D8, -4)
309self.assertEqual(sheet.E8, Units.Quantity("3 mm"))
310self.assertEqual(sheet.F8, Units.Quantity("4 mm"))
311self.assertEqual(sheet.G8, Units.Quantity("-3 mm"))
312self.assertEqual(sheet.H8, Units.Quantity("-4 mm"))
313self.assertMostlyEqual(sheet.A9, 3) # Trunc
314self.assertMostlyEqual(sheet.B9, 3)
315self.assertMostlyEqual(sheet.C9, -3)
316self.assertMostlyEqual(sheet.D9, -3)
317self.assertEqual(sheet.E9, Units.Quantity("3 mm"))
318self.assertEqual(sheet.F9, Units.Quantity("3 mm"))
319self.assertEqual(sheet.G9, Units.Quantity("-3 mm"))
320self.assertEqual(sheet.H9, Units.Quantity("-3 mm"))
321self.assertMostlyEqual(sheet.A10, 4) # Ceil
322self.assertMostlyEqual(sheet.B10, 4)
323self.assertMostlyEqual(sheet.C10, -3)
324self.assertMostlyEqual(sheet.D10, -3)
325self.assertMostlyEqual(sheet.E10, Units.Quantity("4 mm"))
326self.assertMostlyEqual(sheet.F10, Units.Quantity("4 mm"))
327self.assertMostlyEqual(sheet.G10, Units.Quantity("-3 mm"))
328self.assertMostlyEqual(sheet.H10, Units.Quantity("-3 mm"))
329self.assertMostlyEqual(sheet.A11, 3) # Floor
330self.assertMostlyEqual(sheet.B11, 3)
331self.assertMostlyEqual(sheet.C11, -4)
332self.assertMostlyEqual(sheet.D11, -4)
333self.assertMostlyEqual(sheet.E11, Units.Quantity("3 mm"))
334self.assertMostlyEqual(sheet.F11, Units.Quantity("3 mm"))
335self.assertMostlyEqual(sheet.G11, Units.Quantity("-4 mm"))
336self.assertMostlyEqual(sheet.H11, Units.Quantity("-4 mm"))
337self.assertMostlyEqual(sheet.A12, Units.Quantity("30 deg")) # Asin
338self.assertTrue(sheet.B12.startswith("ERR: Unit must be empty."))
339self.assertMostlyEqual(sheet.A13, Units.Quantity("60 deg")) # Acos
340self.assertTrue(sheet.B13.startswith("ERR: Unit must be empty."))
341self.assertMostlyEqual(sheet.A14, Units.Quantity("60 deg")) # Atan
342self.assertTrue(sheet.B14.startswith("ERR: Unit must be empty."))
343self.assertMostlyEqual(sheet.A15, math.sinh(0.5)) # Sinh
344self.assertTrue(sheet.B15.startswith("ERR: Unit must be empty."))
345self.assertMostlyEqual(sheet.A16, math.cosh(0.5)) # Cosh
346self.assertTrue(sheet.B16.startswith("ERR: Unit must be empty."))
347self.assertMostlyEqual(sheet.A17, math.tanh(0.5)) # Tanh
348self.assertTrue(sheet.B17.startswith("ERR: Unit must be empty."))
349self.assertMostlyEqual(sheet.A18, 2) # Sqrt
350self.assertMostlyEqual(sheet.B18, Units.Quantity("2 mm"))
351self.assertMostlyEqual(sheet.A19, 3) # Mod
352self.assertMostlyEqual(sheet.B19, -3)
353self.assertMostlyEqual(sheet.C19, Units.Quantity("3 mm"))
354self.assertEqual(sheet.D19, 3)
355self.assertMostlyEqual(sheet.A20, Units.Quantity("45 deg")) # Atan2
356self.assertMostlyEqual(sheet.B20, Units.Quantity("-45 deg"))
357self.assertTrue(sheet.C20.startswith("ERR: Units must be equal"))
358self.assertMostlyEqual(sheet.D20, Units.Quantity("45 deg"))
359self.assertMostlyEqual(sheet.A21, 2401) # Pow
360self.assertMostlyEqual(sheet.B21, 2401)
361self.assertMostlyEqual(sheet.C21, Units.Quantity("2401mm^4"))
362self.assertTrue(sheet.D21.startswith("ERR: Exponent is not allowed to have a unit."))
363self.assertMostlyEqual(sheet.A23, 5) # Hypot
364self.assertMostlyEqual(sheet.B23, 5)
365self.assertTrue(sheet.C23.startswith("ERR: Units must be equal"))
366self.assertMostlyEqual(sheet.D23, Units.Quantity("5mm"))
367
368l = math.sqrt(3 * 3 + 4 * 4 + 5 * 5)
369self.assertMostlyEqual(sheet.A24, l) # Hypot
370self.assertMostlyEqual(sheet.B24, l)
371self.assertTrue(sheet.C24.startswith("ERR: Units must be equal"))
372self.assertMostlyEqual(sheet.D24, Units.Quantity("7.07106781186548 mm"))
373self.assertMostlyEqual(sheet.A26, 4) # Cath
374self.assertMostlyEqual(sheet.B26, 4)
375self.assertTrue(sheet.C26.startswith("ERR: Units must be equal"))
376self.assertMostlyEqual(sheet.D26, Units.Quantity("4mm"))
377
378l = math.sqrt(5 * 5 + 4 * 4 + 3 * 3)
379l = math.sqrt(l * l - 5 * 5 - 4 * 4)
380self.assertMostlyEqual(sheet.A27, l) # Cath
381self.assertMostlyEqual(sheet.B27, l)
382self.assertTrue(sheet.C27.startswith("ERR: Units must be equal"))
383self.assertMostlyEqual(sheet.D27, Units.Quantity("3 mm"))
384
385def testRelationalOperators(self):
386"""Test relational operators"""
387sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
388# All should be 1 as result
389sheet.set("A1", "=1 == 1 ? 1 : 0")
390sheet.set("A2", "=1 != 1 ? 0 : 1")
391sheet.set("A3", "=1e9 == 1e9 ? 1 : 0")
392sheet.set("A4", "=1e9 != 1e9 ? 0 : 1")
393sheet.set("A5", "=1 > 1 ? 0 : 1")
394sheet.set("A6", "=2 > 1 ? 1 : 0")
395sheet.set("A7", "=1 > 2 ? 0 : 1")
396sheet.set("A8", "=1 < 1 ? 0 : 1")
397sheet.set("A9", "=1 < 2 ? 1 : 0")
398sheet.set("A10", "=2 < 1 ? 0 : 1")
399sheet.set("A11", "=1 >= 1 ? 1 : 0")
400sheet.set("A12", "=2 >= 1 ? 1 : 0")
401sheet.set("A13", "=1 >= 2 ? 0 : 1")
402sheet.set("A14", "=1 <= 1 ? 1 : 1")
403sheet.set("A15", "=1 <= 2 ? 1 : 0")
404sheet.set("A16", "=2 <= 1 ? 0 : 1")
405sheet.set("A17", "=1 >= 1.000000000000001 ? 0 : 1")
406sheet.set("A18", "=1 >= 1.0000000000000001 ? 1 : 0")
407sheet.set("A19", "=1 <= 1.000000000000001 ? 1 : 0")
408sheet.set("A20", "=1 <= 1.0000000000000001 ? 1 : 0")
409sheet.set("A21", "=1 == 1.000000000000001 ? 0 : 1")
410sheet.set("A22", "=1 == 1.0000000000000001 ? 1 : 0")
411sheet.set("A23", "=1 != 1.000000000000001 ? 1 : 0")
412sheet.set("A24", "=1 != 1.0000000000000001 ? 0 : 1")
413
414self.doc.recompute()
415self.assertEqual(sheet.A1, 1)
416self.assertEqual(sheet.A2, 1)
417self.assertEqual(sheet.A3, 1)
418self.assertEqual(sheet.A4, 1)
419self.assertEqual(sheet.A5, 1)
420self.assertEqual(sheet.A6, 1)
421self.assertEqual(sheet.A7, 1)
422self.assertEqual(sheet.A8, 1)
423self.assertEqual(sheet.A9, 1)
424self.assertEqual(sheet.A10, 1)
425self.assertEqual(sheet.A11, 1)
426self.assertEqual(sheet.A12, 1)
427self.assertEqual(sheet.A13, 1)
428self.assertEqual(sheet.A14, 1)
429self.assertEqual(sheet.A15, 1)
430self.assertEqual(sheet.A16, 1)
431self.assertEqual(sheet.A17, 1)
432self.assertEqual(sheet.A18, 1)
433self.assertEqual(sheet.A19, 1)
434self.assertEqual(sheet.A20, 1)
435self.assertEqual(sheet.A21, 1)
436self.assertEqual(sheet.A22, 1)
437self.assertEqual(sheet.A23, 1)
438self.assertEqual(sheet.A24, 1)
439
440def testUnits(self):
441"""Units -- test unit calculations."""
442sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
443sheet.set("A1", "=2mm + 3mm")
444sheet.set("A2", "=2mm - 3mm")
445sheet.set("A3", "=2mm * 3mm")
446sheet.set("A4", "=4mm / 2mm")
447sheet.set("A5", "=(4mm)^2")
448sheet.set("A6", "=5(mm^2)")
449sheet.set("A7", "=5mm^2") # ^2 operates on whole number
450sheet.set("A8", "=5")
451sheet.set("A9", "=5*1/K") # Currently fails
452sheet.set("A10", "=5 K^-1") # Currently fails
453sheet.set("A11", "=9.8 m/s^2") # Currently fails
454sheet.setDisplayUnit("A8", "1/K")
455self.doc.recompute()
456self.assertEqual(sheet.A1, Units.Quantity("5mm"))
457self.assertEqual(sheet.A2, Units.Quantity("-1 mm"))
458self.assertEqual(sheet.A3, Units.Quantity("6 mm^2"))
459self.assertEqual(sheet.A4, Units.Quantity("2"))
460self.assertEqual(sheet.A5, Units.Quantity("16 mm^2"))
461self.assertEqual(sheet.A6, Units.Quantity("5 mm^2"))
462self.assertEqual(sheet.A7, Units.Quantity("5 mm^2"))
463self.assertEqual(sheet.A8, Units.Quantity("5"))
464self.assertEqual(sheet.A9, Units.Quantity("5 K^-1"))
465self.assertEqual(sheet.A10, Units.Quantity("5 K^-1"))
466self.assertEqual(sheet.A11, Units.Quantity("9.8 m/s^2"))
467
468def testPrecedence(self):
469"""Precedence -- test precedence for relational operators and conditional operator."""
470sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
471sheet.set("A1", "=1 < 2 ? 3 : 4")
472sheet.set("A2", "=1 + 2 < 3 + 4 ? 5 + 6 : 7 + 8")
473sheet.set("A3", "=1 + 2 * 1 < 3 + 4 ? 5 * 2 + 6 * 3 + 2 ^ 4 : 7 * 2 + 8 * 3 + 2 ^ 3")
474sheet.set("A4", "=123")
475sheet.set("A5", "=123 + 321")
476sheet.set("A6", "=123 * 2 + 321")
477sheet.set("A7", "=123 * 2 + 333 / 3")
478sheet.set("A8", "=123 * (2 + 321)")
479sheet.set("A9", "=3 ^ 4")
480sheet.set("A10", "=3 ^ 4 * 2")
481sheet.set("A11", "=3 ^ (4 * 2)")
482sheet.set("A12", "=3 ^ 4 + 4")
483sheet.set("A13", "=1 + 4 / 2 + 5")
484sheet.set("A14", "=(3 + 6) / (1 + 2)")
485sheet.set("A15", "=1 * 2 / 3 * 4")
486sheet.set("A16", "=(1 * 2) / (3 * 4)")
487# Test associativity
488sheet.set(
489"A17", "=3 ^ 4 ^ 2"
490) # exponentiation is left-associative; to follow excel, openoffice, matlab, octave
491sheet.set("A18", "=3 ^ (4 ^ 2)") # exponentiation is left-associative
492sheet.set("A19", "=(3 ^ 4) ^ 2") # exponentiation is left-associative
493sheet.set("A20", "=3 + 4 + 2")
494sheet.set("A21", "=3 + (4 + 2)")
495sheet.set("A22", "=(3 + 4) + 2")
496sheet.set("A23", "=3 - 4 - 2")
497sheet.set("A24", "=3 - (4 - 2)")
498sheet.set("A25", "=(3 - 4) - 2")
499sheet.set("A26", "=3 * 4 * 2")
500sheet.set("A27", "=3 * (4 * 2)")
501sheet.set("A28", "=(3 * 4) * 2")
502sheet.set("A29", "=3 / 4 / 2")
503sheet.set("A30", "=3 / (4 / 2)")
504sheet.set("A31", "=(3 / 4) / 2")
505sheet.set("A32", "=pi * 3")
506sheet.set("A33", "=A32 / 3")
507sheet.set("A34", "=1 < 2 ? <<A>> : <<B>>")
508sheet.set("A35", "=min(A32:A33)")
509sheet.set("A36", "=(1 < 2 ? 0 : 1) * 3")
510sheet.set("A37", "=8/(2^2*2)")
511sheet.set("A38", "=(2^2*2)/8")
512sheet.set("A39", "=2^(2*2)/8")
513sheet.set("A40", "=8/2^(2*2)")
514sheet.set("A41", "=-1")
515sheet.set("A42", "=-(1)")
516sheet.set("A43", "=-(1 + 1)")
517sheet.set("A44", "=-(1 - 1)")
518sheet.set("A45", "=-(-1 + 1)")
519sheet.set("A46", "=-(-1 + -1)")
520sheet.set("A47", "=+1")
521sheet.set("A48", "=+(1)")
522sheet.set("A49", "=+(1 + 1)")
523sheet.set("A50", "=+(1 - 1)")
524sheet.set("A51", "=+(-1 + 1)")
525sheet.set("A52", "=+(-1 + -1)")
526
527self.doc.addObject("Part::Cylinder", "Cylinder")
528# We cannot use Thickness, as this feature requires a source shape,
529# otherwise it will cause recomputation failure. The new logic of
530# App::Document will not continue recompute any dependent objects
531
532# self.doc.addObject("Part::Thickness", "Pipe")
533self.doc.addObject("Part::Box", "Box")
534self.doc.Box.Length = 1
535
536sheet.set("B1", "101")
537sheet.set("A53", "=-(-(B1-1)/2)")
538sheet.set("A54", '=-(Cylinder.Radius + Box.Length - 1"/2)')
539
540self.doc.recompute()
541self.assertEqual(sheet.getContents("A1"), "=1 < 2 ? 3 : 4")
542self.assertEqual(sheet.getContents("A2"), "=1 + 2 < 3 + 4 ? 5 + 6 : 7 + 8")
543self.assertEqual(
544sheet.getContents("A3"),
545"=1 + 2 * 1 < 3 + 4 ? 5 * 2 + 6 * 3 + 2 ^ 4 : 7 * 2 + 8 * 3 + 2 ^ 3",
546)
547self.assertEqual(sheet.A1, 3)
548self.assertEqual(sheet.A2, 11)
549self.assertEqual(sheet.A3, 44)
550self.assertEqual(sheet.A4, 123)
551self.assertEqual(sheet.A5, 444)
552self.assertEqual(sheet.A6, 567)
553self.assertEqual(sheet.A7, 357)
554self.assertEqual(sheet.A8, 39729)
555self.assertEqual(sheet.A9, 81)
556self.assertEqual(sheet.A10, 162)
557self.assertEqual(sheet.A11, 6561)
558self.assertEqual(sheet.A12, 85)
559self.assertEqual(sheet.A13, 8)
560self.assertEqual(sheet.A14, 3)
561self.assertEqual(sheet.A15, 8.0 / 3)
562self.assertEqual(sheet.A16, 1.0 / 6)
563self.assertEqual(sheet.A17, 6561)
564self.assertEqual(sheet.A18, 43046721)
565self.assertEqual(sheet.A19, 6561)
566self.assertEqual(sheet.A20, 9)
567self.assertEqual(sheet.A21, 9)
568self.assertEqual(sheet.A22, 9)
569self.assertEqual(sheet.A23, -3)
570self.assertEqual(sheet.A24, 1)
571self.assertEqual(sheet.A25, -3)
572self.assertEqual(sheet.A26, 24)
573self.assertEqual(sheet.A27, 24)
574self.assertEqual(sheet.A28, 24)
575self.assertEqual(sheet.A29, 3.0 / 8)
576self.assertEqual(sheet.A30, 3.0 / 2)
577self.assertEqual(sheet.A31, 3.0 / 8)
578self.assertEqual(sheet.A37, 1)
579self.assertEqual(sheet.A38, 1)
580self.assertEqual(sheet.A39, 2)
581self.assertEqual(sheet.A40, 0.5)
582self.assertEqual(sheet.A41, -1)
583self.assertEqual(sheet.A42, -1)
584self.assertEqual(sheet.A43, -2)
585self.assertEqual(sheet.A44, 0)
586self.assertEqual(sheet.A45, 0)
587self.assertEqual(sheet.A46, 2)
588self.assertEqual(sheet.A47, 1)
589self.assertEqual(sheet.A48, 1)
590self.assertEqual(sheet.A49, 2)
591self.assertEqual(sheet.A50, 0)
592self.assertEqual(sheet.A51, 0)
593self.assertEqual(sheet.A52, -2)
594self.assertEqual(sheet.A53, 50)
595self.assertEqual(sheet.A54, Units.Quantity("9.7mm"))
596self.assertEqual(sheet.getContents("A1"), "=1 < 2 ? 3 : 4")
597self.assertEqual(sheet.getContents("A2"), "=1 + 2 < 3 + 4 ? 5 + 6 : 7 + 8")
598self.assertEqual(
599sheet.getContents("A3"),
600"=1 + 2 * 1 < 3 + 4 ? 5 * 2 + 6 * 3 + 2 ^ 4 : 7 * 2 + 8 * 3 + 2 ^ 3",
601)
602self.assertEqual(sheet.getContents("A4"), "123")
603self.assertEqual(sheet.getContents("A5"), "=123 + 321")
604self.assertEqual(sheet.getContents("A6"), "=123 * 2 + 321")
605self.assertEqual(sheet.getContents("A7"), "=123 * 2 + 333 / 3")
606self.assertEqual(sheet.getContents("A8"), "=123 * (2 + 321)")
607self.assertEqual(sheet.getContents("A9"), "=3 ^ 4")
608self.assertEqual(sheet.getContents("A10"), "=3 ^ 4 * 2")
609self.assertEqual(sheet.getContents("A11"), "=3 ^ (4 * 2)")
610self.assertEqual(sheet.getContents("A12"), "=3 ^ 4 + 4")
611self.assertEqual(sheet.getContents("A13"), "=1 + 4 / 2 + 5")
612self.assertEqual(sheet.getContents("A14"), "=(3 + 6) / (1 + 2)")
613self.assertEqual(sheet.getContents("A15"), "=1 * 2 / 3 * 4")
614self.assertEqual(sheet.getContents("A16"), "=1 * 2 / (3 * 4)")
615self.assertEqual(sheet.getContents("A17"), "=3 ^ 4 ^ 2")
616self.assertEqual(sheet.getContents("A18"), "=3 ^ (4 ^ 2)")
617self.assertEqual(sheet.getContents("A19"), "=3 ^ 4 ^ 2")
618self.assertEqual(sheet.getContents("A20"), "=3 + 4 + 2")
619self.assertEqual(sheet.getContents("A21"), "=3 + 4 + 2")
620self.assertEqual(sheet.getContents("A22"), "=3 + 4 + 2")
621self.assertEqual(sheet.getContents("A23"), "=3 - 4 - 2")
622self.assertEqual(sheet.getContents("A24"), "=3 - (4 - 2)")
623self.assertEqual(sheet.getContents("A25"), "=3 - 4 - 2")
624self.assertEqual(sheet.getContents("A26"), "=3 * 4 * 2")
625self.assertEqual(sheet.getContents("A27"), "=3 * 4 * 2")
626self.assertEqual(sheet.getContents("A28"), "=3 * 4 * 2")
627self.assertEqual(sheet.getContents("A29"), "=3 / 4 / 2")
628self.assertEqual(sheet.getContents("A30"), "=3 / (4 / 2)")
629self.assertEqual(sheet.getContents("A31"), "=3 / 4 / 2")
630self.assertEqual(sheet.getContents("A32"), "=pi * 3")
631self.assertEqual(sheet.getContents("A33"), "=A32 / 3")
632self.assertEqual(sheet.getContents("A34"), "=1 < 2 ? <<A>> : <<B>>")
633self.assertEqual(sheet.getContents("A35"), "=min(A32:A33)")
634self.assertEqual(sheet.getContents("A36"), "=(1 < 2 ? 0 : 1) * 3")
635self.assertEqual(sheet.getContents("A37"), "=8 / (2 ^ 2 * 2)")
636self.assertEqual(sheet.getContents("A38"), "=2 ^ 2 * 2 / 8")
637self.assertEqual(sheet.getContents("A39"), "=2 ^ (2 * 2) / 8")
638self.assertEqual(sheet.getContents("A40"), "=8 / 2 ^ (2 * 2)")
639self.assertEqual(sheet.getContents("A41"), "=-1")
640self.assertEqual(sheet.getContents("A42"), "=-1")
641self.assertEqual(sheet.getContents("A43"), "=-(1 + 1)")
642self.assertEqual(sheet.getContents("A44"), "=-(1 - 1)")
643self.assertEqual(sheet.getContents("A45"), "=-(-1 + 1)")
644self.assertEqual(sheet.getContents("A46"), "=-(-1 + -1)")
645self.assertEqual(sheet.getContents("A47"), "=+1")
646self.assertEqual(sheet.getContents("A48"), "=+1")
647self.assertEqual(sheet.getContents("A49"), "=+(1 + 1)")
648self.assertEqual(sheet.getContents("A50"), "=+(1 - 1)")
649self.assertEqual(sheet.getContents("A51"), "=+(-1 + 1)")
650self.assertEqual(sheet.getContents("A52"), "=+(-1 + -1)")
651
652def testNumbers(self):
653"""Test different numbers"""
654sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
655sheet.set("A1", "1")
656sheet.set("A2", "1.5")
657sheet.set("A3", ".5")
658sheet.set("A4", "1e2")
659sheet.set("A5", "1E2")
660sheet.set("A6", "1e-2")
661sheet.set("A7", "1E-2")
662sheet.set("A8", "1.5e2")
663sheet.set("A9", "1.5E2")
664sheet.set("A10", "1.5e-2")
665sheet.set("A11", "1.5E-2")
666sheet.set("A12", ".5e2")
667sheet.set("A13", ".5E2")
668sheet.set("A14", ".5e-2")
669sheet.set("A15", ".5E-2")
670sheet.set("A16", "1/1")
671sheet.set("A17", "1/2")
672sheet.set("A18", "2/4")
673self.doc.recompute()
674self.assertEqual(sheet.A1, 1)
675self.assertEqual(sheet.A2, 1.5)
676self.assertEqual(sheet.A3, 0.5)
677self.assertEqual(sheet.A4, 1e2)
678self.assertEqual(sheet.A5, 1e2)
679self.assertEqual(sheet.A6, 1e-2)
680self.assertEqual(sheet.A7, 1e-2)
681self.assertEqual(sheet.A8, 1.5e2)
682self.assertEqual(sheet.A9, 1.5e2)
683self.assertEqual(sheet.A10, 1.5e-2)
684self.assertEqual(sheet.A11, 1.5e-2)
685self.assertEqual(sheet.A12, 0.5e2)
686self.assertEqual(sheet.A13, 0.5e2)
687self.assertEqual(sheet.A14, 0.5e-2)
688self.assertEqual(sheet.A15, 0.5e-2)
689self.assertEqual(sheet.A16, 1)
690self.assertEqual(sheet.A17, 0.5)
691self.assertEqual(sheet.A18, 0.5)
692
693def testQuantitiesAndFractionsAsNumbers(self):
694"""Test quantities and simple fractions as numbers"""
695sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
696sheet.set("A1", "1mm")
697sheet.set("A2", "1/2")
698sheet.set("A3", "4mm/2")
699sheet.set("A4", "2/mm")
700sheet.set("A5", "4/2mm")
701sheet.set("A6", "6mm/3s")
702self.doc.recompute()
703self.assertEqual(sheet.A1, Units.Quantity("1 mm"))
704self.assertEqual(sheet.A2, 0.5)
705self.assertEqual(sheet.A3, Units.Quantity("2 mm"))
706self.assertEqual(sheet.A4, Units.Quantity("2 1/mm"))
707self.assertEqual(sheet.A5, Units.Quantity("2 1/mm"))
708self.assertEqual(sheet.A6, Units.Quantity("2 mm/s"))
709
710def testRemoveRows(self):
711"""Removing rows -- check renaming of internal cells"""
712sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
713sheet.set("A3", "123")
714sheet.set("A1", "=A3")
715sheet.removeRows("2", 1)
716self.assertEqual(sheet.getContents("A1"), "=A2")
717
718def testInsertRows(self):
719"""Inserting rows -- check renaming of internal cells"""
720sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
721sheet.set("B1", "=B2")
722sheet.set("B2", "124")
723# Calling getContents() here activates ObjectIdentifier internal cache,
724# which needs to be tested as well.
725self.assertEqual(sheet.getContents("B1"), "=B2")
726sheet.insertRows("2", 1)
727self.assertEqual(sheet.getContents("B1"), "=B3")
728
729def testIssue3225(self):
730"""Inserting rows -- check renaming of internal cells"""
731sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
732sheet.set("B2", "25")
733sheet.set("B3", "=B2")
734sheet.insertRows("2", 1)
735self.assertEqual(sheet.getContents("B4"), "=B3")
736
737def testRenameAlias(self):
738"""Test renaming of alias1 to alias2 in a spreadsheet"""
739sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
740sheet.set("B1", "124")
741sheet.setAlias("B1", "alias1")
742sheet.set("B2", "=alias1")
743self.doc.recompute()
744self.assertEqual(sheet.get("alias1"), 124)
745self.assertEqual(sheet.get("B1"), 124)
746self.assertEqual(sheet.get("B2"), 124)
747sheet.setAlias("B1", "alias2")
748self.doc.recompute()
749self.assertEqual(sheet.get("alias2"), 124)
750self.assertEqual(sheet.getContents("B2"), "=alias2")
751
752def testRenameAlias2(self):
753"""Test renaming of alias1 to alias2 in a spreadsheet, when referenced from another object"""
754sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
755sheet.set("B1", "124")
756sheet.setAlias("B1", "alias1")
757box = self.doc.addObject("Part::Box", "Box")
758box.setExpression("Length", "Spreadsheet.alias1")
759sheet.setAlias("B1", "alias2")
760self.assertEqual(box.ExpressionEngine[0][1], "Spreadsheet.alias2")
761
762def testRenameAlias3(self):
763"""Test renaming of document object referenced from another object"""
764sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
765sheet.set("B1", "124")
766sheet.setAlias("B1", "alias1")
767box = self.doc.addObject("Part::Box", "Box")
768box.setExpression("Length", "Spreadsheet.alias1")
769box2 = self.doc.addObject("Part::Box", "Box")
770box2.setExpression("Length", "<<Spreadsheet>>.alias1")
771sheet.Label = "Params"
772self.assertEqual(box.ExpressionEngine[0][1], "Spreadsheet.alias1")
773self.assertEqual(box2.ExpressionEngine[0][1], "<<Params>>.alias1")
774
775def testAlias(self):
776"""Playing with aliases"""
777sheet = self.doc.addObject("Spreadsheet::Sheet", "Calc")
778sheet.setAlias("A1", "Test")
779self.assertEqual(sheet.getAlias("A1"), "Test")
780
781sheet.set("A1", "4711")
782self.doc.recompute()
783self.assertEqual(sheet.get("Test"), 4711)
784self.assertEqual(sheet.get("Test"), sheet.get("A1"))
785
786def testAmbiguousAlias(self):
787"""Try to set the same alias twice (bug #2402)"""
788sheet = self.doc.addObject("Spreadsheet::Sheet", "Calc")
789sheet.setAlias("A1", "Test")
790try:
791sheet.setAlias("A2", "Test")
792self.fail("An ambiguous alias was set which shouldn't be allowed")
793except Exception:
794self.assertEqual(sheet.getAlias("A2"), None)
795
796def testClearAlias(self):
797"""This was causing a crash"""
798sheet = self.doc.addObject("Spreadsheet::Sheet", "Calc")
799sheet.setAlias("A1", "Test")
800sheet.setAlias("A1", "")
801self.assertEqual(sheet.getAlias("A1"), None)
802
803def testSetInvalidAlias(self):
804"""Try to use a cell address as alias name"""
805sheet = self.doc.addObject("Spreadsheet::Sheet", "Calc")
806try:
807sheet.setAlias("A1", "B1")
808except Exception:
809self.assertEqual(sheet.getAlias("A1"), None)
810else:
811self.fail("A cell address was used as alias which shouldn't be allowed")
812
813def testSetInvalidAlias2(self):
814"""Try to use a unit (reserved word) as alias name"""
815sheet = self.doc.addObject("Spreadsheet::Sheet", "Calc")
816try:
817sheet.setAlias("A1", "mA")
818except Exception:
819self.assertEqual(sheet.getAlias("A1"), None)
820else:
821self.fail("A unit (reserved word) was used as alias which shouldn't be allowed")
822
823def testPlacementName(self):
824"""Object name is equal to property name (bug #2389)"""
825if not FreeCAD.GuiUp:
826return
827
828import FreeCADGui
829
830o = self.doc.addObject("Part::FeaturePython", "Placement")
831FreeCADGui.Selection.addSelection(o)
832
833def testInvoluteGear(self):
834"""Support of boolean or integer values"""
835try:
836import InvoluteGearFeature
837except ImportError:
838return
839InvoluteGearFeature.makeInvoluteGear("InvoluteGear")
840self.doc.recompute()
841sketch = self.doc.addObject("Sketcher::SketchObject", "Sketch")
842sketch.addGeometry(Part.LineSegment(v(0, 0, 0), v(10, 10, 0)), False)
843sketch.addConstraint(Sketcher.Constraint("Distance", 0, 65.285388))
844sketch.setExpression("Constraints[0]", "InvoluteGear.NumberOfTeeth")
845self.doc.recompute()
846self.assertIn("Up-to-date", sketch.State)
847
848def testSketcher(self):
849"""Mixup of Label and Name (bug #2407)"""
850sketch = self.doc.addObject("Sketcher::SketchObject", "Sketch")
851sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
852sheet.setAlias("A1", "Length")
853self.doc.recompute()
854sheet.set("A1", "47,11")
855self.doc.recompute()
856
857index = sketch.addGeometry(Part.LineSegment(v(0, 0, 0), v(10, 10, 0)), False)
858sketch.addConstraint(Sketcher.Constraint("Distance", index, 14.0))
859self.doc.recompute()
860sketch.setExpression("Constraints[0]", "<<Spreadsheet>>.Length")
861self.doc.recompute()
862sheet.Label = "Calc"
863self.doc.recompute()
864self.assertEqual(sketch.ExpressionEngine[0][1], "<<Calc>>.Length")
865self.assertIn("Up-to-date", sketch.State)
866
867def testCrossDocumentLinks(self):
868"""Expressions across files are not saved (bug #2442)"""
869
870# Create a box
871box = self.doc.addObject("Part::Box", "Box")
872
873# Create a second document with a cylinder
874doc2 = FreeCAD.newDocument()
875cylinder = doc2.addObject("Part::Cylinder", "Cylinder")
876cylinder.setExpression("Radius", "cube#Cube.Height")
877
878# Save and close first document
879self.doc.saveAs(self.TempPath + os.sep + "cube.fcstd")
880FreeCAD.closeDocument(self.doc.Name)
881
882# Save and close second document
883doc2.saveAs(self.TempPath + os.sep + "cylinder.fcstd")
884FreeCAD.closeDocument(doc2.Name)
885
886# Open both documents again
887self.doc = FreeCAD.openDocument(self.TempPath + os.sep + "cube.fcstd")
888doc2 = FreeCAD.openDocument(self.TempPath + os.sep + "cylinder.fcstd")
889
890# Check reference between them
891self.assertEqual(doc2.getObject("Cylinder").ExpressionEngine[0][1], "cube#Cube.Height")
892
893# Close second document
894FreeCAD.closeDocument(doc2.Name)
895
896def testMatrix(self):
897"""Test Matrix/Vector/Placement/Rotation operations"""
898
899def plm_equal(plm1, plm2):
900from math import sqrt
901
902qpair = zip(plm1.Rotation.Q, plm2.Rotation.Q)
903qdiff1 = sqrt(sum([(v1 - v2) ** 2 for v1, v2 in qpair]))
904qdiff2 = sqrt(sum([(v1 + v2) ** 2 for v1, v2 in qpair]))
905return (plm1.Base - plm2.Base).Length < 1e-7 and (qdiff1 < 1e-12 or dqiff2 < 1e-12)
906
907sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
908
909mat = FreeCAD.Matrix()
910mat.scale(2, 1, 2)
911imat = mat.inverse()
912
913vec = FreeCAD.Vector(2, 1, 2)
914
915rot = FreeCAD.Rotation(FreeCAD.Vector(0, 1, 0), 45)
916irot = rot.inverted()
917
918pla = FreeCAD.Placement(vec, rot)
919ipla = pla.inverse()
920
921sheet.set("A1", "=vector(2, 1, 2)")
922
923# different ways of calling mscale()
924sheet.set("B1", "=mscale(create(<<matrix>>), A1)")
925sheet.set("C1", "=mscale(create(<<matrix>>), tuple(2, 1, 2))")
926sheet.set("A2", "=mscale(create(<<matrix>>), 2, 1, 2)")
927
928# test matrix power operation
929sheet.set("B2", "=A2^-2")
930sheet.set("C2", "=A2^-1")
931sheet.set("D2", "=A2^0")
932sheet.set("E2", "=A2^1")
933sheet.set("F2", "=A2^2")
934sheet.set("G2", "=matrix(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)")
935sheet.set("H2", "=G2^-1")
936
937sheet.set("A3", "=rotation(vector(0, 1, 0), 45)")
938
939# test rotation power operation
940sheet.set("B3", "=A3^-2")
941sheet.set("C3", "=A3^-1")
942sheet.set("D3", "=A3^0")
943sheet.set("E3", "=A3^1")
944sheet.set("F3", "=A3^2")
945
946sheet.set("A4", "=placement(A1, A3)")
947
948# test placement power operation
949sheet.set("B4", "=A4^-2")
950sheet.set("C4", "=A4^-1")
951sheet.set("D4", "=A4^0")
952sheet.set("E4", "=A4^1")
953sheet.set("F4", "=A4^2")
954
955# vector transformation with mixing matrix and placement and rotation
956sheet.set("A5", "=A2*A3*A4*A1")
957sheet.set("B5", "=B2*B4*B3*A1")
958sheet.set("C5", "=C3*C2*C4*A1")
959sheet.set("D5", "=D3*D4*D2*A1")
960sheet.set("E5", "=E4*E2*E3*A1")
961sheet.set("F5", "=F3*F4*F2*A1")
962
963# inverse of the above transformation with power -1 and minvert()
964sheet.set("A6", "=A4^-1 * minvert(A3) * A2^-1 * A5")
965sheet.set("B6", "=minvert(B3) * B4^-1 * minvert(B2) * B5")
966sheet.set("C6", "=C4^-1 * C2^-1 * C3^-1 * C5")
967sheet.set("D6", "=minvert(D4*D2) * minvert(D3) * D5")
968sheet.set("E6", "=(E2 * E3)^-1 * E4^-1 * E5")
969sheet.set("F6", "=(F3*F4*F2)^-1 * F5")
970
971# Rotate and translate.
972sheet.set("A7", "=placement(vector(1; 2; 3), vector(1; 0; 0); 0)")
973sheet.set("B7", "=mrotate(A7; vector(1; 0; 0); 90)")
974sheet.set("C7", "=mrotatex(A7; 90)")
975sheet.set("D7", "=mrotatey(A7; 90)")
976sheet.set("E7", "=mrotatez(A7; 90)")
977sheet.set("F7", "=mtranslate(A7; vector(1; 2; 3))")
978sheet.set("G7", "=mtranslate(A7; 1; 2; 3)")
979
980# Compatibility with old syntax.
981sheet.set("A8", "=create(<<vector>>, 2, 1, 2)")
982sheet.set("B8", "=create(<<rotation>>, create(<<vector>>, 0, 1, 0), 45)")
983sheet.set("C8", "=create(<<placement>>, A8, B8)")
984
985self.doc.recompute()
986
987self.assertEqual(sheet.A1, vec)
988
989self.assertEqual(sheet.B1, mat)
990self.assertEqual(sheet.C1, mat)
991self.assertEqual(sheet.A2, mat)
992
993self.assertEqual(sheet.B2, imat * imat)
994self.assertEqual(sheet.B2, mat**-2)
995self.assertEqual(sheet.C2, imat)
996self.assertEqual(sheet.C2, mat**-1)
997self.assertEqual(sheet.D2, FreeCAD.Matrix())
998self.assertEqual(sheet.D2, mat**0)
999self.assertEqual(sheet.E2, mat)
1000self.assertEqual(sheet.E2, mat**1)
1001self.assertEqual(sheet.F2, mat * mat)
1002self.assertEqual(sheet.F2, mat**2)
1003
1004self.assertTrue(sheet.H2.startswith("ERR: Cannot invert singular matrix"))
1005
1006self.assertEqual(sheet.A3, rot)
1007
1008rtol = 1e-12
1009self.assertTrue(sheet.B3.isSame(irot * irot, rtol))
1010self.assertTrue(sheet.B3.isSame(rot**-2, rtol))
1011self.assertTrue(sheet.C3.isSame(irot, rtol))
1012self.assertTrue(sheet.C3.isSame(rot**-1, rtol))
1013self.assertTrue(sheet.D3.isSame(FreeCAD.Rotation(), rtol))
1014self.assertTrue(sheet.D3.isSame(rot**0, rtol))
1015self.assertTrue(sheet.E3.isSame(rot, rtol))
1016self.assertTrue(sheet.E3.isSame(rot**1, rtol))
1017self.assertTrue(sheet.F3.isSame(rot * rot, rtol))
1018self.assertTrue(sheet.F3.isSame(rot**2, rtol))
1019
1020self.assertEqual(sheet.A4, pla)
1021
1022self.assertTrue(plm_equal(sheet.B4, ipla * ipla))
1023self.assertTrue(plm_equal(sheet.B4, pla**-2))
1024self.assertTrue(plm_equal(sheet.C4, ipla))
1025self.assertTrue(plm_equal(sheet.C4, pla**-1))
1026self.assertTrue(plm_equal(sheet.D4, FreeCAD.Placement()))
1027self.assertTrue(plm_equal(sheet.D4, pla**0))
1028self.assertTrue(plm_equal(sheet.E4, pla))
1029self.assertTrue(plm_equal(sheet.E4, pla**1))
1030self.assertTrue(plm_equal(sheet.F4, pla * pla))
1031self.assertTrue(plm_equal(sheet.F4, pla**2))
1032
1033tol = 1e-10
1034
1035self.assertLess(
1036sheet.A5.distanceToPoint(
1037sheet.A2.multiply(sheet.A3.Matrix).multiply(sheet.A4.Matrix).multVec(vec)
1038),
1039tol,
1040)
1041self.assertLess(
1042sheet.B5.distanceToPoint(
1043sheet.B2.multiply(sheet.B4.Matrix).multiply(sheet.B3.Matrix).multVec(vec)
1044),
1045tol,
1046)
1047self.assertLess(
1048sheet.C5.distanceToPoint(
1049sheet.C3.Matrix.multiply(sheet.C2).multiply(sheet.C4.Matrix).multVec(vec)
1050),
1051tol,
1052)
1053self.assertLess(
1054sheet.D5.distanceToPoint(
1055sheet.D3.Matrix.multiply(sheet.D4.Matrix).multiply(sheet.D2).multVec(vec)
1056),
1057tol,
1058)
1059self.assertLess(
1060sheet.E5.distanceToPoint(
1061sheet.E4.Matrix.multiply(sheet.E2).multiply(sheet.E3.Matrix).multVec(vec)
1062),
1063tol,
1064)
1065self.assertLess(
1066sheet.F5.distanceToPoint(
1067sheet.F3.Matrix.multiply(sheet.F4.Matrix).multiply(sheet.F2).multVec(vec)
1068),
1069tol,
1070)
1071
1072self.assertLess(sheet.A6.distanceToPoint(vec), tol)
1073self.assertLess(sheet.B6.distanceToPoint(vec), tol)
1074self.assertLess(sheet.C6.distanceToPoint(vec), tol)
1075self.assertLess(sheet.D6.distanceToPoint(vec), tol)
1076self.assertLess(sheet.E6.distanceToPoint(vec), tol)
1077self.assertLess(sheet.F6.distanceToPoint(vec), tol)
1078
1079self.assertTrue(sheet.A7.Base.isEqual(FreeCAD.Vector(1, 2, 3), tol))
1080self.assertTrue(sheet.B7.Base.isEqual(FreeCAD.Vector(1, -3, 2), tol))
1081self.assertTrue(sheet.C7.Base.isEqual(FreeCAD.Vector(1, -3, 2), tol))
1082self.assertTrue(sheet.D7.Base.isEqual(FreeCAD.Vector(3, 2.0, -1), tol))
1083self.assertTrue(sheet.E7.Base.isEqual(FreeCAD.Vector(-2, 1, 3.0), tol))
1084self.assertTrue(sheet.F7.Base.isEqual(FreeCAD.Vector(2, 4, 6), tol))
1085self.assertTrue(sheet.G7.Base.isEqual(FreeCAD.Vector(2, 4, 6), tol))
1086
1087self.assertEqual(sheet.A8, vec)
1088self.assertEqual(sheet.B8, rot)
1089self.assertEqual(sheet.C8, pla)
1090
1091def testIssue3128(self):
1092"""Regression test for issue 3128; mod should work with arbitrary units for both arguments"""
1093sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1094sheet.set("A1", "=mod(7mm;3mm)")
1095sheet.set("A2", "=mod(7kg;3mm)")
1096self.doc.recompute()
1097self.assertEqual(sheet.A1, Units.Quantity("1"))
1098self.assertEqual(sheet.A2, Units.Quantity("1 kg/mm"))
1099
1100def testIssue3363(self):
1101"""Regression test for issue 3363; Nested conditionals statement fails with additional conditional statement in false-branch"""
1102sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1103sheet.set("A1", "1")
1104sheet.set("B1", "=A1==1?11:(A1==2?12:13)")
1105sheet.set("C1", "=A1==1?(A1==2?12:13) : 11")
1106self.doc.recompute()
1107
1108# Save and close first document
1109self.doc.saveAs(self.TempPath + os.sep + "conditionals.fcstd")
1110FreeCAD.closeDocument(self.doc.Name)
1111
1112# Open documents again
1113self.doc = FreeCAD.openDocument(self.TempPath + os.sep + "conditionals.fcstd")
1114
1115sheet = self.doc.getObject("Spreadsheet")
1116self.assertEqual(sheet.getContents("B1"), "=A1 == 1 ? 11 : (A1 == 2 ? 12 : 13)")
1117self.assertEqual(sheet.getContents("C1"), "=A1 == 1 ? (A1 == 2 ? 12 : 13) : 11")
1118
1119def testIssue3432(self):
1120"""Regression test for issue 3432; numbers with units are ignored from aggregates"""
1121sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1122sheet.set("A1", "1mm")
1123sheet.set("B1", "2mm")
1124sheet.set("C1", "=max(A1:B1;3mm)")
1125self.doc.recompute()
1126self.assertEqual(sheet.get("C1"), Units.Quantity("3 mm"))
1127
1128def testIssue4156(self):
1129"""Regression test for issue 4156; necessarily use of leading '=' to enter an expression, creates inconsistent behavior depending on the spreadsheet state"""
1130sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1131sheet.set("A3", "A1")
1132sheet.set("A1", "1000")
1133self.doc.recompute()
1134sheet.set("A3", "")
1135sheet.set("A3", "A1")
1136self.assertEqual(sheet.getContents("A3"), "'A1")
1137
1138def testInsertRowsAlias(self):
1139"""Regression test for issue 4429; insert rows to sheet with aliases"""
1140sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1141sheet.set("A3", "1")
1142sheet.setAlias("A3", "alias1")
1143sheet.set("A4", "=alias1 + 1")
1144sheet.setAlias("A4", "alias2")
1145sheet.set("A5", "=alias2 + 1")
1146self.doc.recompute()
1147sheet.insertRows("1", 1)
1148self.doc.recompute()
1149self.assertEqual(sheet.A6, 3)
1150
1151def testInsertColumnsAlias(self):
1152"""Regression test for issue 4429; insert columns to sheet with aliases"""
1153sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1154sheet.set("C1", "1")
1155sheet.setAlias("C1", "alias1")
1156sheet.set("D1", "=alias1 + 1")
1157sheet.setAlias("D1", "alias2")
1158sheet.set("E1", "=alias2 + 1")
1159self.doc.recompute()
1160sheet.insertColumns("A", 1)
1161self.doc.recompute()
1162self.assertEqual(sheet.F1, 3)
1163
1164def testRemoveRowsAlias(self):
1165"""Regression test for issue 4429; remove rows from sheet with aliases"""
1166sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1167sheet.set("A3", "1")
1168sheet.setAlias("A3", "alias1")
1169sheet.set("A5", "=alias1 + 1")
1170sheet.setAlias("A5", "alias2")
1171sheet.set("A4", "=alias2 + 1")
1172self.doc.recompute()
1173sheet.removeRows("1", 1)
1174self.doc.recompute()
1175self.assertEqual(sheet.A3, 3)
1176
1177def testRemoveRowsAliasReuseName(self):
1178"""Regression test for issue 4492; deleted aliases remains in database"""
1179sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1180sheet.setAlias("B2", "test")
1181self.doc.recompute()
1182sheet.removeRows("2", 1)
1183sheet.setAlias("B3", "test")
1184
1185def testRemoveColumnsAlias(self):
1186"""Regression test for issue 4429; remove columns from sheet with aliases"""
1187sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1188sheet.set("C1", "1")
1189sheet.setAlias("C1", "alias1")
1190sheet.set("E1", "=alias1 + 1")
1191sheet.setAlias("E1", "alias2")
1192sheet.set("D1", "=alias2 + 1")
1193self.doc.recompute()
1194sheet.removeColumns("A", 1)
1195self.doc.recompute()
1196self.assertEqual(sheet.C1, 3)
1197
1198def testRemoveColumnsAliasReuseName(self):
1199"""Regression test for issue 4492; deleted aliases remains in database"""
1200sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1201sheet.setAlias("B2", "test")
1202self.doc.recompute()
1203sheet.removeColumns("B", 1)
1204sheet.setAlias("C3", "test")
1205
1206def testUndoAliasCreationReuseName(self):
1207"""Test deleted aliases by undo remains in database"""
1208sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1209
1210self.doc.UndoMode = 1
1211self.doc.openTransaction("create alias")
1212sheet.setAlias("B2", "test")
1213self.doc.commitTransaction()
1214self.doc.recompute()
1215
1216self.doc.undo()
1217self.doc.recompute()
1218sheet.setAlias("C3", "test")
1219
1220def testCrossLinkEmptyPropertyName(self):
1221# https://forum.freecad.org/viewtopic.php?f=3&t=58603
1222base = FreeCAD.newDocument("base")
1223sheet = base.addObject("Spreadsheet::Sheet", "Spreadsheet")
1224sheet.setAlias("A1", "x")
1225sheet.set("x", "42mm")
1226base.recompute()
1227
1228square = FreeCAD.newDocument("square")
1229body = square.addObject("PartDesign::Body", "Body")
1230box = square.addObject("PartDesign::AdditiveBox", "Box")
1231body.addObject(box)
1232box.Length = 10.00
1233box.Width = 10.00
1234box.Height = 10.00
1235square.recompute()
1236
1237basePath = self.TempPath + os.sep + "base.FCStd"
1238base.saveAs(basePath)
1239squarePath = self.TempPath + os.sep + "square.FCStd"
1240square.saveAs(squarePath)
1241
1242base.save()
1243square.save()
1244
1245FreeCAD.closeDocument(square.Name)
1246FreeCAD.closeDocument(base.Name)
1247
1248##
1249## preparation done
1250base = FreeCAD.openDocument(basePath)
1251square = FreeCAD.openDocument(squarePath)
1252
1253square.Box.setExpression("Length", "base#Spreadsheet.x")
1254square.recompute()
1255
1256square.save()
1257base.save()
1258FreeCAD.closeDocument(square.Name)
1259FreeCAD.closeDocument(base.Name)
1260
1261def testExpressionWithAlias(self):
1262# https://forum.freecad.org/viewtopic.php?p=564502#p564502
1263ss1 = self.doc.addObject("Spreadsheet::Sheet", "Input")
1264ss1.setAlias("A1", "one")
1265ss1.setAlias("A2", "two")
1266ss1.set("A1", "1")
1267ss1.set("A2", "2")
1268self.doc.recompute()
1269
1270ss2 = self.doc.addObject("Spreadsheet::Sheet", "Output")
1271ss2.set("A1", "=Input.A1 + Input.A2")
1272ss2.set("A2", "=Input.one + Input.two")
1273ss2.set("A3", "=<<Input>>.A1 + <<Input>>.A2")
1274ss2.set("A4", "=<<Input>>.one + <<Input>>.two")
1275self.doc.recompute()
1276
1277self.assertEqual(ss2.get("A1"), 3)
1278self.assertEqual(ss2.get("A2"), 3)
1279self.assertEqual(ss2.get("A3"), 3)
1280self.assertEqual(ss2.get("A4"), 3)
1281
1282project_path = self.TempPath + os.sep + "alias.FCStd"
1283self.doc.saveAs(project_path)
1284FreeCAD.closeDocument(self.doc.Name)
1285self.doc = FreeCAD.openDocument(project_path)
1286ss1 = self.doc.Input
1287ss2 = self.doc.Output
1288
1289self.assertEqual(ss2.get("A1"), 3)
1290self.assertEqual(ss2.get("A2"), 3)
1291self.assertEqual(ss2.get("A3"), 3)
1292self.assertEqual(ss2.get("A4"), 3)
1293
1294ss1.set("A1", "2")
1295self.doc.recompute()
1296
1297self.assertEqual(ss1.get("A1"), 2)
1298self.assertEqual(ss1.get("one"), 2)
1299
1300self.assertEqual(ss2.get("A1"), 4)
1301self.assertEqual(ss2.get("A2"), 4)
1302self.assertEqual(ss2.get("A3"), 4)
1303self.assertEqual(ss2.get("A4"), 4)
1304
1305def testIssue6844(self):
1306body = self.doc.addObject("App::FeaturePython", "Body")
1307body.addProperty("App::PropertyEnumeration", "Configuration")
1308body.Configuration = ["Item1", "Item2", "Item3"]
1309
1310sheet = self.doc.addObject("Spreadsheet::Sheet", "Sheet")
1311sheet.addProperty("App::PropertyString", "A2")
1312sheet.A2 = "Item2"
1313sheet.addProperty("App::PropertyEnumeration", "body")
1314sheet.body = ["Item1", "Item2", "Item3"]
1315
1316sheet.setExpression(".body.Enum", "cells[<<A2:|>>]")
1317sheet.setExpression(
1318".cells.Bind.B1.ZZ1",
1319"tuple(.cells; <<B>> + str(hiddenref(Body.Configuration) + 2); <<ZZ>> + str(hiddenref(Body.Configuration) + 2))",
1320)
1321
1322self.doc.recompute()
1323self.doc.UndoMode = 0
1324self.doc.removeObject("Body")
1325sheet.clearAll()
1326
1327def testIssue6840(self):
1328body = self.doc.addObject("App::FeaturePython", "Body")
1329body.addProperty("App::PropertyEnumeration", "Configuration")
1330body.Configuration = ["Item1", "Item2", "Item3"]
1331
1332sheet = self.doc.addObject("Spreadsheet::Sheet", "Sheet")
1333sheet.addProperty("App::PropertyString", "A2")
1334sheet.A2 = "Item2"
1335sheet.addProperty("App::PropertyEnumeration", "body")
1336sheet.body = ["Item1", "Item2", "Item3"]
1337
1338sheet.setExpression(".body.Enum", "cells[<<A2:|>>]")
1339sheet.setExpression(
1340".cells.Bind.B1.ZZ1",
1341"tuple(.cells; <<B>> + str(hiddenref(Body.Configuration) + 2); <<ZZ>> + str(hiddenref(Body.Configuration) + 2))",
1342)
1343
1344self.doc.recompute()
1345self.doc.clearDocument()
1346
1347def testFixPR6843(self):
1348sheet = self.doc.addObject("Spreadsheet::Sheet", "Sheet")
1349sheet.set("A5", "a")
1350sheet.set("A6", "b")
1351self.doc.recompute()
1352sheet.insertRows("6", 1)
1353self.doc.recompute()
1354self.assertEqual(sheet.A5, "a")
1355self.assertEqual(sheet.A7, "b")
1356with self.assertRaises(AttributeError):
1357self.assertEqual(sheet.A6, "")
1358
1359def testBindAcrossSheets(self):
1360ss1 = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet1")
1361ss2 = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet2")
1362ss2.set("B1", "B1")
1363ss2.set("B2", "B2")
1364ss2.set("C1", "C1")
1365ss2.set("C2", "C2")
1366ss2.set("D1", "D1")
1367ss2.set("D2", "D2")
1368
1369ss1.setExpression(".cells.Bind.A3.C4", "tuple(Spreadsheet2.cells, <<B1>>, <<D2>>)")
1370self.doc.recompute()
1371
1372self.assertEqual(ss1.A3, ss2.B1)
1373self.assertEqual(ss1.A4, ss2.B2)
1374self.assertEqual(ss1.B3, ss2.C1)
1375self.assertEqual(ss1.B4, ss2.C2)
1376self.assertEqual(ss1.C3, ss2.D1)
1377self.assertEqual(ss1.C4, ss2.D2)
1378
1379self.assertEqual(len(ss1.ExpressionEngine), 1)
1380ss1.setExpression(".cells.Bind.A3.C4", None)
1381self.doc.recompute()
1382
1383def testBindHiddenRefAcrossSheets(self):
1384ss1 = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet1")
1385ss2 = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet2")
1386ss2.set("B1", "B1")
1387ss2.set("B2", "B2")
1388ss2.set("C1", "C1")
1389ss2.set("C2", "C2")
1390ss2.set("D1", "D1")
1391ss2.set("D2", "D2")
1392
1393self.doc.recompute()
1394ss1.setExpression(".cells.Bind.A3.C4", None)
1395ss1.setExpression(
1396".cells.BindHiddenRef.A3.C4", "hiddenref(tuple(Spreadsheet2.cells, <<B1>>, <<D2>>))"
1397)
1398self.doc.recompute()
1399
1400ss1.recompute() # True
1401self.assertEqual(ss1.A3, ss2.B1)
1402
1403ss1.setExpression(".cells.Bind.A3.C4", None)
1404ss1.setExpression(".cells.BindHiddenRef.A3.C4", None)
1405self.doc.recompute()
1406self.assertEqual(len(ss1.ExpressionEngine), 0)
1407
1408def testMergeCells(self):
1409ss1 = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet1")
1410ss1.mergeCells("A1:B4")
1411ss1.mergeCells("C1:D4")
1412self.doc.recompute()
1413ss1.set("B1", "fail")
1414self.doc.recompute()
1415with self.assertRaises(AttributeError):
1416self.assertEqual(ss1.B1, "fail")
1417
1418def testMergeCellsAndBind(self):
1419ss1 = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet1")
1420ss1.mergeCells("A1:B1")
1421ss1.setExpression(".cells.Bind.A1.A1", "tuple(.cells, <<A2>>, <<A2>>)")
1422ss1.set("A2", "test")
1423self.doc.recompute()
1424self.assertEqual(ss1.A1, ss1.A2)
1425ss1.set("B1", "fail")
1426self.doc.recompute()
1427with self.assertRaises(AttributeError):
1428self.assertEqual(ss1.B1, "fail")
1429
1430def testGetUsedCells(self):
1431sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1432test_cells = ["B13", "C14", "D15"]
1433for i, cell in enumerate(test_cells):
1434sheet.set(cell, str(i))
1435
1436used_cells = sheet.getUsedCells()
1437self.assertEqual(len(used_cells), len(test_cells))
1438for cell in test_cells:
1439self.assertTrue(cell in used_cells)
1440
1441for cell in test_cells:
1442sheet.set(cell, "")
1443sheet.setAlignment(cell, "center")
1444non_empty_cells = sheet.getUsedCells()
1445self.assertEqual(len(non_empty_cells), len(test_cells)) # Alignment counts as "used"
1446
1447def testGetUsedRange(self):
1448sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1449test_cells = ["C5", "Z3", "D10", "E20"]
1450for i, cell in enumerate(test_cells):
1451sheet.set(cell, str(i))
1452used_range = sheet.getUsedRange()
1453self.assertEqual(used_range, ("C3", "Z20"))
1454
1455for i, cell in enumerate(test_cells):
1456sheet.set(cell, "")
1457sheet.setAlignment(cell, "center")
1458used_range = sheet.getUsedRange()
1459self.assertEqual(used_range, ("C3", "Z20"))
1460
1461def testGetNonEmptyCells(self):
1462sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1463test_cells = ["B13", "C14", "D15"]
1464for i, cell in enumerate(test_cells):
1465sheet.set(cell, str(i))
1466
1467non_empty_cells = sheet.getNonEmptyCells()
1468self.assertEqual(len(non_empty_cells), len(test_cells))
1469for cell in test_cells:
1470self.assertTrue(cell in non_empty_cells)
1471
1472for cell in test_cells:
1473sheet.set(cell, "")
1474sheet.setAlignment(cell, "center")
1475non_empty_cells = sheet.getNonEmptyCells()
1476self.assertEqual(len(non_empty_cells), 0) # Alignment does not count as "non-empty"
1477
1478def testGetNonEmptyRange(self):
1479sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1480test_cells = ["C5", "Z3", "D10", "E20"]
1481for i, cell in enumerate(test_cells):
1482sheet.set(cell, str(i))
1483non_empty_range = sheet.getNonEmptyRange()
1484self.assertEqual(non_empty_range, ("C3", "Z20"))
1485
1486for i, cell in enumerate(test_cells):
1487sheet.set(cell, "")
1488sheet.setAlignment(cell, "center")
1489more_cells = ["D10", "X5", "E10", "K15"]
1490for i, cell in enumerate(more_cells):
1491sheet.set(cell, str(i))
1492non_empty_range = sheet.getNonEmptyRange()
1493self.assertEqual(non_empty_range, ("D5", "X15"))
1494
1495def testAliasEmptyCell(self):
1496# https://github.com/FreeCAD/FreeCAD/issues/7841
1497sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1498sheet.setAlias("A1", "aliasOfEmptyCell")
1499self.assertEqual(sheet.getCellFromAlias("aliasOfEmptyCell"), "A1")
1500
1501def testParensAroundCondition(self):
1502"""Parens around a condition should be accepted"""
1503sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1504
1505sheet.set("A1", "=(1 == 1) ? 1 : 0")
1506self.doc.recompute()
1507self.assertEqual(sheet.getContents("A1"), "=1 == 1 ? 1 : 0")
1508self.assertEqual(sheet.A1, 1)
1509
1510def testIssue6395(self):
1511"""Testing strings are correctly saved and restored"""
1512sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1513sheet.set("A1", "'36C") # Use a string that may be parsed as a quantity
1514self.doc.recompute()
1515
1516self.doc.saveAs(self.TempPath + os.sep + "string.fcstd")
1517FreeCAD.closeDocument(self.doc.Name)
1518
1519self.doc = FreeCAD.openDocument(self.TempPath + os.sep + "string.fcstd")
1520
1521sheet = self.doc.getObject("Spreadsheet")
1522self.assertEqual(sheet.getContents("A1"), "'36C")
1523self.assertEqual(sheet.get("A1"), "36C")
1524
1525def testVectorFunctions(self):
1526sheet = self.doc.addObject("Spreadsheet::Sheet", "Spreadsheet")
1527
1528sheet.set("A1", "=vcross(vector(1; 2; 3); vector(1; 5; 7))")
1529
1530sheet.set("B1", "=vdot(vector(1; 2; 3); vector(4; -5; 6))")
1531
1532sheet.set("C1", "=vangle(vector(1; 0; 0); vector(0; 1; 0))")
1533
1534sheet.set("D1", "=vnormalize(vector(1; 0; 0))")
1535sheet.set("D2", "=vnormalize(vector(1; 1; 1))")
1536
1537sheet.set("E1", "=vscale(vector(1; 2; 3); 2; 3; 4)")
1538sheet.set("E2", "=vscalex(vector(1; 2; 3); -2)")
1539sheet.set("E3", "=vscaley(vector(1; 2; 3); -2)")
1540sheet.set("E4", "=vscalez(vector(1; 2; 3); -2)")
1541
1542sheet.set("F1", "=vlinedist(vector(1; 2; 3); vector(2; 3; 4); vector(3; 4; 5))")
1543sheet.set("F2", "=vlinesegdist(vector(1; 2; 3); vector(2; 3; 4); vector(3; 4; 5))")
1544sheet.set("F3", "=vlineproj(vector(1; 2; 3); vector(2; 3; 4); vector(3; 4; 5))")
1545sheet.set("F4", "=vplanedist(vector(1; 2; 3); vector(2; 3; 4); vector(3; 4; 5))")
1546sheet.set("F5", "=vplaneproj(vector(1; 2; 3); vector(2; 3; 4); vector(3; 4; 5))")
1547
1548self.doc.recompute()
1549
1550tolerance = 1e-10
1551
1552self.assertEqual(sheet.A1, FreeCAD.Vector(-1, -4, 3))
1553
1554self.assertEqual(sheet.B1, 12)
1555
1556self.assertEqual(sheet.C1, 90)
1557
1558self.assertEqual(sheet.D1, FreeCAD.Vector(1, 0, 0))
1559self.assertLess(
1560sheet.D2.distanceToPoint(FreeCAD.Vector(1 / sqrt(3), 1 / sqrt(3), 1 / sqrt(3))),
1561tolerance,
1562)
1563
1564self.assertEqual(sheet.E1, FreeCAD.Vector(2, 6, 12))
1565self.assertEqual(sheet.E2, FreeCAD.Vector(-2, 2, 3))
1566self.assertEqual(sheet.E3, FreeCAD.Vector(1, -4, 3))
1567self.assertEqual(sheet.E4, FreeCAD.Vector(1, 2, -6))
1568
1569self.assertLess(abs(sheet.F1.Value - 0.3464), 0.0001)
1570self.assertEqual(sheet.F2, FreeCAD.Vector(1, 1, 1))
1571self.assertLess(sheet.F3.distanceToPoint(FreeCAD.Vector(0.28, 0.04, -0.2)), tolerance)
1572self.assertLess(abs(sheet.F4.Value - -1.6971), 0.0001)
1573self.assertEqual(sheet.F5, FreeCAD.Vector(1.72, 2.96, 4.2))
1574
1575def tearDown(self):
1576# closing doc
1577FreeCAD.closeDocument(self.doc.Name)
1578