* Add BIM workbench to .pre-commit-config.yaml * pre-commit: ignore translations * pre-commit: add additional ignore pattern * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
519 lines
12 KiB
Python
519 lines
12 KiB
Python
# SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
# ***************************************************************************
|
|
# * *
|
|
# * Copyright (c) 2013 Yorik van Havre <yorik@uncreated.net> *
|
|
# * Copyright (c) 2025 Furgo *
|
|
# * *
|
|
# * This file is part of FreeCAD. *
|
|
# * *
|
|
# * FreeCAD is free software: you can redistribute it and/or modify it *
|
|
# * under the terms of the GNU Lesser General Public License as *
|
|
# * published by the Free Software Foundation, either version 2.1 of the *
|
|
# * License, or (at your option) any later version. *
|
|
# * *
|
|
# * FreeCAD is distributed in the hope that it will be useful, but *
|
|
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
# * Lesser General Public License for more details. *
|
|
# * *
|
|
# * You should have received a copy of the GNU Lesser General Public *
|
|
# * License along with FreeCAD. If not, see *
|
|
# * <https://www.gnu.org/licenses/>. *
|
|
# * *
|
|
# ***************************************************************************
|
|
|
|
# Unit tests for the Arch space module
|
|
|
|
import os
|
|
import Arch
|
|
import Draft
|
|
import Part
|
|
import FreeCAD as App
|
|
from FreeCAD import Units
|
|
from bimtests import TestArchBase
|
|
import WorkingPlane
|
|
|
|
|
|
def like(a, b):
|
|
return abs(a - b) < 0.001
|
|
|
|
|
|
def checkBB(a, b):
|
|
return (
|
|
like(a.XMin, b.XMin)
|
|
and like(a.YMin, b.YMin)
|
|
and like(a.ZMin, b.ZMin)
|
|
and like(a.XMax, b.XMax)
|
|
and like(a.YMax, b.YMax)
|
|
and like(a.ZMax, b.ZMax)
|
|
)
|
|
|
|
|
|
class TestArchSpace(TestArchBase.TestArchBase):
|
|
|
|
def testSpace(self):
|
|
operation = "Checking Arch Space..."
|
|
self.printTestMessage(operation)
|
|
|
|
sb = Part.makeBox(1, 1, 1)
|
|
b = App.ActiveDocument.addObject("Part::Feature", "Box")
|
|
b.Shape = sb
|
|
s = Arch.makeSpace([b])
|
|
self.assertTrue(s, "Arch Space failed")
|
|
|
|
def testSpaceBBox(self):
|
|
operation = "Checking Arch Space bound box..."
|
|
self.printTestMessage(operation)
|
|
|
|
shape = Part.Shape()
|
|
shape.importBrepFromString(brepArchiCAD)
|
|
bborig = shape.BoundBox
|
|
App.Console.PrintLog("Original BB: " + str(bborig))
|
|
baseobj = App.ActiveDocument.addObject("Part::Feature", "brepArchiCAD_body")
|
|
baseobj.Shape = shape
|
|
space = Arch.makeSpace(baseobj)
|
|
space.recompute()
|
|
bbnew = space.Shape.BoundBox
|
|
App.Console.PrintLog("New BB: " + str(bbnew))
|
|
self.assertTrue(checkBB(bborig, bbnew), "Arch Space has wrong Placement")
|
|
|
|
def test_addSpaceBoundaries(self):
|
|
"""Test the Arch.addSpaceBoundaries method.
|
|
Create a space and a wall that intersects it. Add the wall as a boundary to the space,
|
|
and check if the resulting space area is as expected.
|
|
"""
|
|
operation = "Add a wall face as a boundary to a space"
|
|
self.printTestMessage(operation)
|
|
|
|
# Create the space
|
|
pl = App.Placement()
|
|
pl.Rotation.Q = (0.0, 0.0, 0.0, 1.0)
|
|
pl.Base = App.Vector(-2000.0, -2000.0, 0.0)
|
|
rectangleBase = Draft.make_rectangle(
|
|
length=4000.0, height=4000.0, placement=pl, face=True, support=None
|
|
)
|
|
App.ActiveDocument.recompute()
|
|
extr = rectangleBase.Shape.extrude(App.Vector(0, 0, 2000))
|
|
Part.show(extr, "Extrusion")
|
|
space = Arch.makeSpace(App.activeDocument().getObject("Extrusion"))
|
|
App.ActiveDocument.recompute() # To calculate area
|
|
|
|
# Create the wall
|
|
trace = Part.LineSegment(App.Vector(3000.0, 1000.0, 0.0), App.Vector(-3000.0, 1000.0, 0.0))
|
|
wp = WorkingPlane.get_working_plane()
|
|
base = App.ActiveDocument.addObject("Sketcher::SketchObject", "WallTrace")
|
|
base.Placement = wp.get_placement()
|
|
base.addGeometry(trace)
|
|
wall = Arch.makeWall(base, width=200.0, height=3000.0, align="Left")
|
|
wall.Normal = wp.axis
|
|
|
|
# Add the boundary
|
|
wallBoundary = [(wall, ["Face1"])]
|
|
Arch.addSpaceBoundaries(App.ActiveDocument.Space, wallBoundary)
|
|
App.ActiveDocument.recompute() # To recalculate area
|
|
|
|
# Assert if area is as expected
|
|
expectedArea = Units.parseQuantity("12 m^2")
|
|
actualArea = Units.parseQuantity(str(space.Area))
|
|
|
|
self.assertAlmostEqual(
|
|
expectedArea.Value,
|
|
actualArea.Value,
|
|
msg=(
|
|
f"Invalid area value. "
|
|
+ f"Expected: {expectedArea.UserString}, actual: {actualArea.UserString}"
|
|
),
|
|
)
|
|
|
|
def test_SpaceFromSingleWall(self):
|
|
"""Create a space from boundaries of a single wall."""
|
|
operation = "Arch Space from single wall"
|
|
self.printTestMessage(operation)
|
|
|
|
# Create a wall
|
|
wallInnerLength = 4000.0
|
|
wallHeight = 3000.0
|
|
wallInnerFaceArea = wallInnerLength * wallHeight
|
|
pl = App.Placement()
|
|
pl.Rotation.Q = (0.0, 0.0, 0.0, 1.0)
|
|
pl.Base = App.Vector(0.0, 0.0, 0.0)
|
|
rectangleBase = Draft.make_rectangle(
|
|
length=wallInnerLength, height=wallInnerLength, placement=pl, face=True, support=None
|
|
)
|
|
App.ActiveDocument.recompute() # To calculate rectangle area
|
|
rectangleArea = rectangleBase.Area
|
|
App.ActiveDocument.getObject(rectangleBase.Name).MakeFace = False
|
|
wall = Arch.makeWall(baseobj=rectangleBase, height=wallHeight, align="Left")
|
|
App.ActiveDocument.recompute() # To calculate face areas
|
|
|
|
# Create a space from the wall's inner faces
|
|
boundaries = [
|
|
f"Face{ind+1}"
|
|
for ind, face in enumerate(wall.Shape.Faces)
|
|
if round(face.Area) == round(wallInnerFaceArea)
|
|
]
|
|
|
|
if App.GuiUp:
|
|
import FreeCADGui
|
|
|
|
FreeCADGui.Selection.clearSelection()
|
|
FreeCADGui.Selection.addSelection(wall, boundaries)
|
|
|
|
space = Arch.makeSpace(FreeCADGui.Selection.getSelectionEx())
|
|
# Alternative, but test takes longer to run (~10x)
|
|
# FreeCADGui.activateWorkbench("BIMWorkbench")
|
|
# FreeCADGui.runCommand('Arch_Space', 0)
|
|
# space = App.ActiveDocument.Space
|
|
else:
|
|
# Also tests the alternative way of specifying the boundaries
|
|
# [ (<Part::Feature>, ["Face1", ...]), ... ]
|
|
space = Arch.makeSpace([(wall, boundaries)])
|
|
|
|
App.ActiveDocument.recompute() # To calculate space area
|
|
|
|
# Assert if area is as expected
|
|
expectedArea = Units.parseQuantity(str(rectangleArea))
|
|
actualArea = Units.parseQuantity(str(space.Area))
|
|
|
|
self.assertAlmostEqual(
|
|
expectedArea.Value,
|
|
actualArea.Value,
|
|
msg=f"Invalid area value. Expected: {expectedArea.UserString}, actual: {actualArea.UserString}",
|
|
)
|
|
|
|
|
|
brepArchiCAD = """
|
|
DBRep_DrawableShape
|
|
|
|
CASCADE Topology V1, (c) Matra-Datavision
|
|
Locations 3
|
|
1
|
|
1 0 0 0
|
|
0 1 0 0
|
|
0 0 1 0
|
|
1
|
|
0 1 0 0
|
|
-1 0 0 0
|
|
0 0 1 0
|
|
2 2 -1 0
|
|
Curve2ds 0
|
|
Curves 12
|
|
1 0 0 0 1 0 0
|
|
1 3000 0 0 0 0 1
|
|
1 3000 0 3000 -1 0 0
|
|
1 0 0 3000 0 0 -1
|
|
1 0 0 0 0 1 0
|
|
1 0 5000 0 1 0 0
|
|
1 3000 5000 0 0 -1 0
|
|
1 3000 5000 0 0 0 1
|
|
1 3000 5000 3000 0 -1 0
|
|
1 3000 5000 3000 -1 0 0
|
|
1 0 5000 3000 0 -1 0
|
|
1 0 5000 3000 0 0 -1
|
|
Polygon3D 0
|
|
PolygonOnTriangulations 24
|
|
2 1 2
|
|
p 18.3333333333333 1 0 3000
|
|
2 1 4
|
|
p 18.3333333333333 1 0 3000
|
|
2 2 3
|
|
p 18.3333333333333 1 0 3000
|
|
2 2 4
|
|
p 18.3333333333333 1 0 3000
|
|
2 3 4
|
|
p 18.3333333333333 1 0 3000
|
|
2 1 2
|
|
p 18.3333333333333 1 0 3000
|
|
2 4 1
|
|
p 18.3333333333333 1 0 3000
|
|
2 3 1
|
|
p 18.3333333333333 1 0 3000
|
|
2 1 2
|
|
p 18.3333333333333 1 0 5000
|
|
2 1 2
|
|
p 18.3333333333333 1 0 5000
|
|
2 2 3
|
|
p 18.3333333333333 1 0 3000
|
|
2 1 2
|
|
p 18.3333333333333 1 0 3000
|
|
2 3 4
|
|
p 18.3333333333333 1 0 5000
|
|
2 1 2
|
|
p 18.3333333333333 1 0 5000
|
|
2 1 3
|
|
p 18.3333333333333 1 0 3000
|
|
2 2 4
|
|
p 18.3333333333333 1 0 3000
|
|
2 3 4
|
|
p 18.3333333333333 1 0 5000
|
|
2 3 1
|
|
p 18.3333333333333 1 0 5000
|
|
2 3 4
|
|
p 18.3333333333333 1 0 3000
|
|
2 4 3
|
|
p 18.3333333333333 1 0 3000
|
|
2 4 2
|
|
p 18.3333333333333 1 0 5000
|
|
2 4 3
|
|
p 18.3333333333333 1 0 5000
|
|
2 4 2
|
|
p 18.3333333333333 1 0 3000
|
|
2 3 1
|
|
p 18.3333333333333 1 0 3000
|
|
Surfaces 6
|
|
1 1500 0 1500 -0 -1 -0 0 0 -1 1 0 0
|
|
1 1500 2500 0 -0 -0 -1 -1 0 0 0 1 0
|
|
1 3000 2500 1500 1 0 0 0 0 1 0 -1 0
|
|
1 1500 2500 3000 0 0 1 1 0 0 0 1 0
|
|
1 0 2500 1500 -1 -0 -0 0 0 -1 0 -1 0
|
|
1 1500 5000 1500 0 1 0 0 0 1 1 0 0
|
|
Triangulations 6
|
|
4 2 1 18.3333333333333
|
|
0 0 0 3000 0 0 3000 0 3000 0 0 3000 1500 -1500 1500 1500 -1500 1500 -1500 -1500 3 4 1 2 3 1
|
|
4 2 1 18.3333333333333
|
|
0 0 0 0 5000 0 3000 5000 0 3000 0 0 1500 -2500 1500 2500 -1500 2500 -1500 -2500 2 3 4 2 4 1
|
|
4 2 1 18.3333333333333
|
|
3000 5000 0 3000 0 0 3000 5000 3000 3000 0 3000 -1500 -2500 -1500 2500 1500 -2500 1500 2500 4 2 1 4 1 3
|
|
4 2 1 18.3333333333333
|
|
3000 0 3000 0 0 3000 3000 5000 3000 0 5000 3000 1500 -2500 -1500 -2500 1500 2500 -1500 2500 3 2 1 3 4 2
|
|
4 2 1 18.3333333333333
|
|
0 0 0 0 5000 0 0 0 3000 0 5000 3000 1500 2500 1500 -2500 -1500 2500 -1500 -2500 1 3 4 1 4 2
|
|
4 2 1 18.3333333333333
|
|
0 5000 0 3000 5000 0 0 5000 3000 3000 5000 3000 -1500 -1500 -1500 1500 1500 -1500 1500 1500 3 2 1 4 2 3
|
|
|
|
TShapes 35
|
|
Ve
|
|
0.1
|
|
0 0 0
|
|
0 0
|
|
|
|
0101101
|
|
*
|
|
Ve
|
|
0.1
|
|
0 -3000 0
|
|
0 0
|
|
|
|
0101101
|
|
*
|
|
Ed
|
|
0.0001 1 1 0
|
|
1 1 0 0 3000
|
|
6 1 1 0
|
|
6 2 2 0
|
|
0
|
|
|
|
0101000
|
|
+35 3 -34 3 *
|
|
Ve
|
|
0.1
|
|
0 -3000 3000
|
|
0 0
|
|
|
|
0101101
|
|
*
|
|
Ed
|
|
0.0001 1 1 0
|
|
1 2 0 0 3000
|
|
6 3 1 0
|
|
6 4 3 0
|
|
0
|
|
|
|
0101000
|
|
+34 3 -32 3 *
|
|
Ve
|
|
0.1
|
|
0 0 3000
|
|
0 0
|
|
|
|
0101101
|
|
*
|
|
Ed
|
|
0.0001 1 1 0
|
|
1 3 0 0 3000
|
|
6 5 1 0
|
|
6 6 4 0
|
|
0
|
|
|
|
0101000
|
|
+32 3 -30 3 *
|
|
Ed
|
|
0.0001 1 1 0
|
|
1 4 0 0 3000
|
|
6 7 1 0
|
|
6 8 5 0
|
|
0
|
|
|
|
0101000
|
|
+30 3 -35 3 *
|
|
Wi
|
|
|
|
0101100
|
|
+33 0 +31 0 +29 0 +28 0 *
|
|
Fa
|
|
0 0.1 1 0
|
|
2 1
|
|
0111000
|
|
+27 0 *
|
|
Ve
|
|
0.1
|
|
5000 0 0
|
|
0 0
|
|
|
|
0101101
|
|
*
|
|
Ed
|
|
0.0001 1 1 0
|
|
1 5 0 0 5000
|
|
6 9 2 0
|
|
6 10 5 0
|
|
0
|
|
|
|
0101000
|
|
+35 3 -25 3 *
|
|
Ve
|
|
0.1
|
|
5000 -3000 0
|
|
0 0
|
|
|
|
0101101
|
|
*
|
|
Ed
|
|
0.0001 1 1 0
|
|
1 6 0 0 3000
|
|
6 11 2 0
|
|
6 12 6 0
|
|
0
|
|
|
|
0101000
|
|
+25 3 -23 3 *
|
|
Ed
|
|
0.0001 1 1 0
|
|
1 7 0 0 5000
|
|
6 13 2 0
|
|
6 14 3 0
|
|
0
|
|
|
|
0101000
|
|
+23 3 -34 3 *
|
|
Wi
|
|
|
|
0101100
|
|
+24 0 +22 0 +21 0 -33 0 *
|
|
Fa
|
|
0 0.1 2 0
|
|
2 2
|
|
0111000
|
|
+20 0 *
|
|
Ve
|
|
0.1
|
|
5000 -3000 3000
|
|
0 0
|
|
|
|
0101101
|
|
*
|
|
Ed
|
|
0.0001 1 1 0
|
|
1 8 0 0 3000
|
|
6 15 3 0
|
|
6 16 6 0
|
|
0
|
|
|
|
0101000
|
|
+23 3 -18 3 *
|
|
Ed
|
|
0.0001 1 1 0
|
|
1 9 0 0 5000
|
|
6 17 3 0
|
|
6 18 4 0
|
|
0
|
|
|
|
0101000
|
|
+18 3 -32 3 *
|
|
Wi
|
|
|
|
0101100
|
|
-21 0 +17 0 +16 0 -31 0 *
|
|
Fa
|
|
0 0.1 3 0
|
|
2 3
|
|
0111000
|
|
+15 0 *
|
|
Ve
|
|
0.1
|
|
5000 0 3000
|
|
0 0
|
|
|
|
0101101
|
|
*
|
|
Ed
|
|
0.0001 1 1 0
|
|
1 10 0 0 3000
|
|
6 19 4 0
|
|
6 20 6 0
|
|
0
|
|
|
|
0101000
|
|
+18 3 -13 3 *
|
|
Ed
|
|
0.0001 1 1 0
|
|
1 11 0 0 5000
|
|
6 21 4 0
|
|
6 22 5 0
|
|
0
|
|
|
|
0101000
|
|
+13 3 -30 3 *
|
|
Wi
|
|
|
|
0101100
|
|
-29 0 -16 0 +12 0 +11 0 *
|
|
Fa
|
|
0 0.1 4 0
|
|
2 4
|
|
0111000
|
|
+10 0 *
|
|
Ed
|
|
0.0001 1 1 0
|
|
1 12 0 0 3000
|
|
6 23 5 0
|
|
6 24 6 0
|
|
0
|
|
|
|
0101000
|
|
+13 3 -25 3 *
|
|
Wi
|
|
|
|
0101100
|
|
-24 0 -28 0 -11 0 +8 0 *
|
|
Fa
|
|
0 0.1 5 0
|
|
2 5
|
|
0111000
|
|
+7 0 *
|
|
Wi
|
|
|
|
0101100
|
|
-22 0 -8 0 -12 0 -17 0 *
|
|
Fa
|
|
0 0.1 6 0
|
|
2 6
|
|
0111000
|
|
+5 0 *
|
|
Sh
|
|
|
|
0101100
|
|
+26 0 +19 0 +14 0 +9 0 +6 0 +4 0 *
|
|
So
|
|
|
|
0100000
|
|
+3 0 *
|
|
Co
|
|
|
|
1100000
|
|
+2 2 *
|
|
|
|
+1 1
|
|
"""
|