BIM: add area calculation unit tests for Arch Components
This commit is contained in:
@@ -199,6 +199,7 @@ SET(nativeifc_SRCS
|
||||
|
||||
SET(bimtests_SRCS
|
||||
bimtests/TestArchBase.py
|
||||
bimtests/TestArchComponent.py
|
||||
bimtests/TestArchRoof.py
|
||||
bimtests/TestArchSpace.py
|
||||
bimtests/TestArchWall.py
|
||||
|
||||
@@ -38,6 +38,7 @@ import WorkingPlane
|
||||
from bimtests.TestArchRoof import TestArchRoof
|
||||
from bimtests.TestArchSpace import TestArchSpace
|
||||
from bimtests.TestArchWall import TestArchWall
|
||||
from bimtests.TestArchComponent import TestArchComponent
|
||||
|
||||
from draftutils.messages import _msg
|
||||
|
||||
@@ -215,4 +216,5 @@ class ArchTest(unittest.TestCase):
|
||||
# Use the modules so that code checkers don't complain (flake8)
|
||||
True if TestArchSpace else False
|
||||
True if TestArchRoof else False
|
||||
True if TestArchWall else False
|
||||
True if TestArchWall else False
|
||||
True if TestArchComponent else False
|
||||
|
||||
184
src/Mod/BIM/bimtests/TestArchComponent.py
Normal file
184
src/Mod/BIM/bimtests/TestArchComponent.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * 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 ArchComponent module
|
||||
|
||||
import Arch
|
||||
import Draft
|
||||
import FreeCAD as App
|
||||
from bimtests import TestArchBase
|
||||
from draftutils.messages import _msg
|
||||
|
||||
from math import pi, cos, sin, radians
|
||||
|
||||
class TestArchComponent(TestArchBase.TestArchBase):
|
||||
|
||||
def testBsplineSlabAreas(self):
|
||||
"""Test the HorizontalArea and VerticalArea properties of a Bspline-based slab.
|
||||
|
||||
See https://github.com/FreeCAD/FreeCAD/issues/20989.
|
||||
"""
|
||||
|
||||
operation = "Checking Bspline slab area calculation..."
|
||||
self.printTestMessage(operation)
|
||||
|
||||
doc = App.ActiveDocument
|
||||
|
||||
# Parameters
|
||||
radius = 10000 # 10 meters in mm
|
||||
extrusionLength = 100 # 10 cm in mm
|
||||
numPoints = 50 # Number of points for B-spline
|
||||
startAngle = 0 # Start at 0 degrees (right)
|
||||
endAngle = 180 # End at 180 degrees (left)
|
||||
|
||||
# Create points for semicircle
|
||||
points = []
|
||||
angleStep = (endAngle - startAngle) / (numPoints - 1)
|
||||
for i in range(numPoints):
|
||||
angleDeg = startAngle + i * angleStep
|
||||
angleRad = radians(angleDeg)
|
||||
x = radius * cos(angleRad)
|
||||
y = radius * sin(angleRad)
|
||||
points.append(App.Vector(x, y, 0))
|
||||
|
||||
# Create Draft objects
|
||||
bspline = Draft.makeBSpline(points, closed=False)
|
||||
closingLine = Draft.makeLine(points[-1], points[0])
|
||||
doc.recompute()
|
||||
|
||||
# Create sketch
|
||||
# We do this because Draft.make_wires does not support B-splines
|
||||
# and we need a closed wire for the slab
|
||||
sketch = Draft.makeSketch([bspline, closingLine],
|
||||
autoconstraints=True,
|
||||
delete=True)
|
||||
if sketch is None:
|
||||
self.fail("Sketch creation failed")
|
||||
sketch.recompute()
|
||||
|
||||
# Create slab
|
||||
slab = Arch.makeStructure(sketch, length=extrusionLength, name="Slab")
|
||||
slab.recompute()
|
||||
|
||||
# Calculate theoretical areas
|
||||
radiusMeters = radius / 1000
|
||||
heightMeters = extrusionLength / 1000
|
||||
theoreticalHorizontalArea = (pi * radiusMeters**2) / 2
|
||||
theoreticalVerticalArea = (pi * radiusMeters + 2 * radiusMeters) * heightMeters
|
||||
|
||||
# Get actual areas
|
||||
actualHorizontalArea = slab.HorizontalArea.getValueAs("m^2").Value
|
||||
actualVerticalArea = slab.VerticalArea.getValueAs("m^2").Value
|
||||
|
||||
# Optimally wrapped assertions
|
||||
self.assertAlmostEqual(
|
||||
actualHorizontalArea, theoreticalHorizontalArea, places=3,
|
||||
msg=(
|
||||
"Horizontal area > 0.1% tolerance | "
|
||||
f"Exp: {theoreticalHorizontalArea:.3f} m² | "
|
||||
f"Got: {actualHorizontalArea:.3f} m²"
|
||||
)
|
||||
)
|
||||
|
||||
self.assertAlmostEqual(
|
||||
actualVerticalArea, theoreticalVerticalArea, places=3,
|
||||
msg=(
|
||||
"Vertical area > 0.1% tolerance | "
|
||||
f"Exp: {theoreticalVerticalArea:.3f} m² | "
|
||||
f"Got: {actualVerticalArea:.3f} m²"
|
||||
)
|
||||
)
|
||||
|
||||
def testHouseSpaceAreas(self):
|
||||
"""Test the HorizontalArea and VerticalArea properties of a house-like space.
|
||||
|
||||
See https://github.com/FreeCAD/FreeCAD/issues/14687.
|
||||
"""
|
||||
|
||||
operation = "Checking house space area calculation..."
|
||||
self.printTestMessage(operation)
|
||||
|
||||
doc = App.ActiveDocument
|
||||
|
||||
# Dimensional parameters (all in mm)
|
||||
baseLength = 5000 # 5m along X-axis
|
||||
baseWidth = 5000 # 5m along Y-axis (extrusion depth)
|
||||
rectangleHeight = 2500 # 2.5m lower rectangular portion
|
||||
triangleHeight = 2500 # 2.5m upper triangular portion
|
||||
totalHeight = rectangleHeight + triangleHeight # 5m total height
|
||||
|
||||
# Create envelope profile points (XZ plane)
|
||||
points = [
|
||||
App.Vector(0, 0, 0),
|
||||
App.Vector(baseLength, 0, 0),
|
||||
App.Vector(baseLength, 0, rectangleHeight),
|
||||
App.Vector(baseLength/2, 0, totalHeight),
|
||||
App.Vector(0, 0, rectangleHeight)
|
||||
]
|
||||
|
||||
# Create wire with automatic face creation
|
||||
wire = Draft.makeWire(points, closed=True, face=True)
|
||||
doc.recompute()
|
||||
|
||||
# Extrude the wire
|
||||
extrudedObj = Draft.extrude(wire, App.Vector(0, baseWidth, 0))
|
||||
extrudedObj.Label = "Extruded house"
|
||||
doc.recompute()
|
||||
|
||||
# Create Arch Space from the extrusion
|
||||
space = Arch.makeSpace(extrudedObj)
|
||||
space.Label = "House space"
|
||||
doc.recompute()
|
||||
|
||||
# Calculate theoretical areas
|
||||
# Horizontal area (only bottom face on XY plane)
|
||||
theoreticalHorizontalArea = (baseLength * baseWidth) / 1e6 # 25 m²
|
||||
|
||||
# Vertical areas
|
||||
# Side faces (YZ plane) - two rectangles
|
||||
sideFaceArea = (rectangleHeight * baseWidth) / 1e6 # 12.5 m² each
|
||||
totalSides = sideFaceArea * 2 # 25 m²
|
||||
|
||||
# Front/back faces (XZ plane)
|
||||
rectangularPart = (baseLength * rectangleHeight) / 1e6 # 12.5 m²
|
||||
triangularPart = (baseLength * triangleHeight / 2) / 1e6 # 6.25 m²
|
||||
totalFrontBack = (rectangularPart + triangularPart) * 2 # 37.5 m²
|
||||
|
||||
theoreticalVerticalArea = totalSides + totalFrontBack # 62.5 m²
|
||||
|
||||
# Get actual areas from space
|
||||
actualHorizontalArea = space.HorizontalArea.getValueAs("m^2").Value
|
||||
actualVerticalArea = space.VerticalArea.getValueAs("m^2").Value
|
||||
|
||||
self.assertAlmostEqual(
|
||||
actualHorizontalArea, theoreticalHorizontalArea, places=3,
|
||||
msg=f"Horizontal area > 0.1% | Exp: {theoreticalHorizontalArea:.3f} | "
|
||||
f"Got: {actualHorizontalArea:.3f}"
|
||||
)
|
||||
|
||||
self.assertAlmostEqual(
|
||||
actualVerticalArea, theoreticalVerticalArea, places=3,
|
||||
msg=f"Vertical area > 0.1% | Exp: {theoreticalVerticalArea:.3f} | "
|
||||
f"Got: {actualVerticalArea:.3f}"
|
||||
)
|
||||
Reference in New Issue
Block a user