BIM: set up TestArchGui, clean up CLI/GUI tests

- Also update TechDraw templates path after reorganization from https://github.com/FreeCAD/FreeCAD/pull/23719
This commit is contained in:
Furgo
2025-07-14 18:47:18 +02:00
parent 2d668b1f4e
commit 237060bc95
15 changed files with 432 additions and 384 deletions

View File

@@ -202,9 +202,9 @@ SET(nativeifc_SRCS
)
SET(bimtests_SRCS
bimtests/TestArch.py
bimtests/TestArchAxis.py
bimtests/TestArchBase.py
bimtests/TestArchBaseGui.py
bimtests/TestArchComponent.py
bimtests/TestArchBuildingPart.py
bimtests/TestArchRoof.py
@@ -214,6 +214,7 @@ SET(bimtests_SRCS
bimtests/TestArchPanel.py
bimtests/TestArchWindow.py
bimtests/TestArchStairs.py
bimtests/TestArchStructure.py
bimtests/TestArchPipe.py
bimtests/TestArchCurtainWall.py
bimtests/TestArchProfile.py
@@ -229,6 +230,8 @@ SET(bimtests_SRCS
bimtests/TestArchTruss.py
bimtests/TestWebGLExport.py
bimtests/TestWebGLExportGui.py
bimtests/TestArchImportersGui.py
bimtests/TestArchBuildingPartGui.py
)
SOURCE_GROUP("" FILES ${Arch_SRCS})

View File

@@ -28,7 +28,7 @@ from bimtests.TestArchSpace import TestArchSpace
from bimtests.TestArchWall import TestArchWall
from bimtests.TestArchBuildingPart import TestArchBuildingPart
from bimtests.TestArchAxis import TestArchAxis
from bimtests.TestArch import TestArch
from bimtests.TestArchStructure import TestArchStructure
from bimtests.TestArchMaterial import TestArchMaterial
from bimtests.TestArchPanel import TestArchPanel
from bimtests.TestArchWindow import TestArchWindow

View File

@@ -22,208 +22,8 @@
# * *
# ***************************************************************************
# Unit test for the Arch module
import os
import unittest
import FreeCAD as App
from FreeCAD import Units
import Arch
import Draft
import Part
import Sketcher
import TechDraw
import WorkingPlane
from draftutils.messages import _msg
if App.GuiUp:
import FreeCADGui
"""Import all Arch module unit tests in GUI mode."""
from bimtests.TestArchImportersGui import TestArchImportersGui
from bimtests.TestArchBuildingPartGui import TestArchBuildingPartGui
from bimtests.TestWebGLExportGui import TestWebGLExportGui
class ArchTest(unittest.TestCase):
def setUp(self):
# setting a new document to hold the tests
if App.ActiveDocument:
if App.ActiveDocument.Name != "ArchTest":
App.newDocument("ArchTest")
else:
App.newDocument("ArchTest")
App.setActiveDocument("ArchTest")
def testRebar(self):
App.Console.PrintLog ('Checking Arch Rebar…\n')
s = Arch.makeStructure(length=2,width=3,height=5)
sk = App.ActiveDocument.addObject('Sketcher::SketchObject','Sketch')
sk.AttachmentSupport = (s,["Face6"])
sk.addGeometry(Part.LineSegment(App.Vector(-0.85,1.25,0),App.Vector(0.75,1.25,0)))
sk.addGeometry(Part.LineSegment(App.Vector(0.75,1.25,0),App.Vector(0.75,-1.20,0)))
sk.addGeometry(Part.LineSegment(App.Vector(0.75,-1.20,0),App.Vector(-0.85,-1.20,0)))
sk.addGeometry(Part.LineSegment(App.Vector(-0.85,-1.20,0),App.Vector(-0.85,1.25,0)))
sk.addConstraint(Sketcher.Constraint('Coincident',0,2,1,1))
sk.addConstraint(Sketcher.Constraint('Coincident',1,2,2,1))
sk.addConstraint(Sketcher.Constraint('Coincident',2,2,3,1))
sk.addConstraint(Sketcher.Constraint('Coincident',3,2,0,1))
r = Arch.makeRebar(s,sk,diameter=.1,amount=2)
self.assertTrue(r,"Arch Rebar failed")
def testBuildingPart(self):
"""Create a BuildingPart from a wall with a window and check its shape.
"""
# Also regression test for:
# https://github.com/FreeCAD/FreeCAD/issues/6178
operation = "Arch BuildingPart"
_msg(" Test '{}'".format(operation))
# Most of the code below taken from testWindow function.
line = Draft.makeLine(App.Vector(0, 0, 0), App.Vector(3000, 0, 0))
wall = Arch.makeWall(line)
sk = App.ActiveDocument.addObject("Sketcher::SketchObject", "Sketch001")
sk.Placement.Rotation = App.Rotation(App.Vector(1, 0, 0), 90)
sk.addGeometry(Part.LineSegment(App.Vector( 500, 800, 0), App.Vector(1500, 800, 0)))
sk.addGeometry(Part.LineSegment(App.Vector(1500, 800, 0), App.Vector(1500, 2000, 0)))
sk.addGeometry(Part.LineSegment(App.Vector(1500, 2000, 0), App.Vector( 500, 2000, 0)))
sk.addGeometry(Part.LineSegment(App.Vector( 500, 2000, 0), App.Vector( 500, 800, 0)))
sk.addConstraint(Sketcher.Constraint('Coincident', 0, 2, 1, 1))
sk.addConstraint(Sketcher.Constraint('Coincident', 1, 2, 2, 1))
sk.addConstraint(Sketcher.Constraint('Coincident', 2, 2, 3, 1))
sk.addConstraint(Sketcher.Constraint('Coincident', 3, 2, 0, 1))
App.ActiveDocument.recompute()
win = Arch.makeWindow(sk)
Arch.removeComponents(win, host=wall)
App.ActiveDocument.recompute()
bp = Arch.makeBuildingPart()
# Wall visibility works when standalone
FreeCADGui.Selection.clearSelection()
FreeCADGui.Selection.addSelection('ArchTest',wall.Name)
assert wall.Visibility
FreeCADGui.runCommand('Std_ToggleVisibility',0)
App.ActiveDocument.recompute()
assert not wall.Visibility
FreeCADGui.runCommand('Std_ToggleVisibility',0)
assert wall.Visibility
bp.Group = [wall]
App.ActiveDocument.recompute()
# Fails with OCC 7.5
# self.assertTrue(len(bp.Shape.Faces) == 16, "'{}' failed".format(operation))
# Wall visibility works when inside a BuildingPart
FreeCADGui.runCommand('Std_ToggleVisibility',0)
App.ActiveDocument.recompute()
assert not wall.Visibility
FreeCADGui.runCommand('Std_ToggleVisibility',0)
assert wall.Visibility
# Wall visibility works when BuildingPart Toggled
FreeCADGui.Selection.clearSelection()
FreeCADGui.Selection.addSelection('ArchTest',bp.Name)
FreeCADGui.runCommand('Std_ToggleVisibility',0)
assert not wall.Visibility
FreeCADGui.runCommand('Std_ToggleVisibility',0)
assert wall.Visibility
# Wall visibiity works inside group inside BuildingPart Toggled
grp = App.ActiveDocument.addObject("App::DocumentObjectGroup","Group")
grp.Label="Group"
grp.Group = [wall]
bp.Group = [grp]
App.ActiveDocument.recompute()
assert wall.Visibility
FreeCADGui.runCommand('Std_ToggleVisibility',0)
App.ActiveDocument.recompute()
assert not wall.Visibility
FreeCADGui.runCommand('Std_ToggleVisibility',0)
App.ActiveDocument.recompute()
assert wall.Visibility
def testImportSH3D(self):
"""Import a SweetHome 3D file
"""
operation = "importers.importSH3D"
_msg(" Test '{}'".format(operation))
import BIM.importers.importSH3DHelper
importer = BIM.importers.importSH3DHelper.SH3DImporter(None)
importer.import_sh3d_from_string(SH3D_HOME)
assert App.ActiveDocument.Site
assert App.ActiveDocument.BuildingPart.Label == "Building"
assert App.ActiveDocument.BuildingPart001.Label == "Level"
assert App.ActiveDocument.Wall
def tearDown(self):
App.closeDocument("ArchTest")
pass
SH3D_HOME = """<?xml version='1.0'?>
<home version='7200' name='0-JoinWall.sh3d' camera='topCamera' wallHeight='250.0'>
<property name='com.eteks.sweethome3d.SweetHome3D.CatalogPaneDividerLocation' value='327'/>
<property name='com.eteks.sweethome3d.SweetHome3D.ColumnWidths' value='100,84,82,85,84'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameHeight' value='576'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameMaximized' value='true'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameWidth' value='1092'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameX' value='50'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameY' value='87'/>
<property name='com.eteks.sweethome3d.SweetHome3D.MainPaneDividerLocation' value='441'/>
<property name='com.eteks.sweethome3d.SweetHome3D.PlanPaneDividerLocation' value='263'/>
<property name='com.eteks.sweethome3d.SweetHome3D.PlanScale' value='0.21343713'/>
<property name='com.eteks.sweethome3d.SweetHome3D.PlanViewportX' value='0'/>
<property name='com.eteks.sweethome3d.SweetHome3D.PlanViewportY' value='0'/>
<property name='com.eteks.sweethome3d.SweetHome3D.ScreenHeight' value='720'/>
<property name='com.eteks.sweethome3d.SweetHome3D.ScreenWidth' value='1366'/>
<furnitureVisibleProperty name='NAME'/>
<furnitureVisibleProperty name='WIDTH'/>
<furnitureVisibleProperty name='DEPTH'/>
<furnitureVisibleProperty name='HEIGHT'/>
<furnitureVisibleProperty name='VISIBLE'/>
<environment groundColor='FFB78744' skyColor='00CCE4FC' lightColor='00D0D0D0' ceillingLightColor='00D0D0D0' photoWidth='400' photoHeight='300' photoAspectRatio='VIEW_3D_RATIO' photoQuality='0' videoWidth='320' videoAspectRatio='RATIO_4_3' videoQuality='0' videoFrameRate='25'>
<texture attribute='skyTexture' name='Cloudy' creator='eTeks' catalogId='eTeks#cloudy' width='100.0' height='27.6' image='0'/>
</environment>
<compass x='-100.0' y='50.0' diameter='100.0' northDirection='0.0' longitude='-0.06428629' latitude='0.70511305' timeZone='Europe/Madrid'/>
<observerCamera attribute='observerCamera' lens='PINHOLE' x='50.0' y='50.0' z='170.0' yaw='5.4977875' pitch='0.19634955' fieldOfView='1.0995575' time='1729080000000'/>
<camera attribute='topCamera' lens='PINHOLE' x='1304.082' y='1936.5889' z='1526.6199' yaw='8.98363' pitch='0.7049999' fieldOfView='1.0995575' time='1729080000000'/>
<wall id='wall0' wallAtEnd='wall1' xStart='0.0' yStart='0.0' xEnd='100.0' yEnd='0.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall1' wallAtStart='wall0' wallAtEnd='wall2' xStart='100.0' yStart='0.0' xEnd='200.0' yEnd='0.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall2' wallAtStart='wall1' xStart='200.0' yStart='0.0' xEnd='300.0' yEnd='0.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall3' wallAtEnd='wall4' xStart='0.0' yStart='50.0' xEnd='100.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall4' wallAtStart='wall3' wallAtEnd='wall5' xStart='100.0' yStart='100.0' xEnd='200.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall5' wallAtStart='wall4' xStart='200.0' yStart='100.0' xEnd='300.0' yEnd='50.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall6' wallAtEnd='wall7' xStart='0.0' yStart='200.0' xEnd='100.0' yEnd='300.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall7' wallAtStart='wall6' wallAtEnd='wall8' xStart='100.0' yStart='300.0' xEnd='200.0' yEnd='300.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall8' wallAtStart='wall7' xStart='200.0' yStart='300.0' xEnd='300.0' yEnd='200.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall9' wallAtEnd='wall10' xStart='100.0' yStart='400.0' xEnd='100.0' yEnd='500.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall10' wallAtStart='wall9' wallAtEnd='wall11' xStart='100.0' yStart='500.0' xEnd='200.0' yEnd='500.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall11' wallAtStart='wall10' xStart='200.0' yStart='500.0' xEnd='200.0' yEnd='400.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall12' wallAtStart='wall14' wallAtEnd='wall13' xStart='150.0' yStart='600.0' xEnd='100.0' yEnd='700.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall13' wallAtStart='wall12' wallAtEnd='wall14' xStart='100.0' yStart='700.0' xEnd='200.0' yEnd='700.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall14' wallAtStart='wall13' wallAtEnd='wall12' xStart='200.0' yStart='700.0' xEnd='150.0' yEnd='600.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall15' wallAtEnd='wall16' xStart='400.0' yStart='150.0' xEnd='500.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall16' wallAtStart='wall15' wallAtEnd='wall17' xStart='500.0' yStart='100.0' xEnd='600.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall17' wallAtStart='wall16' xStart='600.0' yStart='100.0' xEnd='700.0' yEnd='50.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall18' wallAtEnd='wall19' xStart='400.0' yStart='400.0' xEnd='500.0' yEnd='300.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall19' wallAtStart='wall18' wallAtEnd='wall20' xStart='500.0' yStart='300.0' xEnd='600.0' yEnd='300.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall20' wallAtStart='wall19' xStart='600.0' yStart='300.0' xEnd='700.0' yEnd='200.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall21' wallAtEnd='wall22' xStart='400.0' yStart='600.0' xEnd='400.0' yEnd='500.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall22' wallAtStart='wall21' wallAtEnd='wall23' xStart='400.0' yStart='500.0' xEnd='600.0' yEnd='500.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall23' wallAtStart='wall22' xStart='600.0' yStart='500.0' xEnd='600.0' yEnd='400.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall24' wallAtEnd='wall25' xStart='600.0' yStart='800.0' xEnd='500.0' yEnd='700.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall25' wallAtStart='wall24' wallAtEnd='wall26' xStart='500.0' yStart='700.0' xEnd='600.0' yEnd='700.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall26' wallAtStart='wall25' xStart='600.0' yStart='700.0' xEnd='500.0' yEnd='600.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall27' wallAtStart='wall30' wallAtEnd='wall28' xStart='800.0' yStart='0.0' xEnd='1000.0' yEnd='0.0' height='250.0' thickness='10.0' arcExtent='1.0471976' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall28' wallAtStart='wall27' wallAtEnd='wall29' xStart='1000.0' yStart='0.0' xEnd='1000.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall29' wallAtStart='wall28' wallAtEnd='wall30' xStart='1000.0' yStart='100.0' xEnd='800.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall30' wallAtStart='wall29' wallAtEnd='wall27' xStart='800.0' yStart='100.0' xEnd='800.0' yEnd='0.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall31' wallAtEnd='wall32' xStart='800.0' yStart='400.0' xEnd='1000.0' yEnd='200.0' height='250.0' thickness='10.0' arcExtent='1.5707964' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall32' wallAtStart='wall31' wallAtEnd='wall33' xStart='1000.0' yStart='200.0' xEnd='1200.0' yEnd='400.0' height='250.0' thickness='10.0' arcExtent='1.5707964' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall33' wallAtStart='wall32' wallAtEnd='wall34' xStart='1200.0' yStart='400.0' xEnd='1000.0' yEnd='600.0' height='250.0' thickness='10.0' arcExtent='1.5707964' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall34' wallAtStart='wall33' xStart='1000.0' yStart='600.0' xEnd='800.0' yEnd='400.0' height='250.0' thickness='10.0' arcExtent='1.5707964' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall35' wallAtEnd='wall36' xStart='800.0' yStart='800.0' xEnd='900.0' yEnd='900.0' height='250.0' thickness='10.0' arcExtent='-3.1415927' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall36' wallAtStart='wall35' wallAtEnd='wall37' xStart='900.0' yStart='900.0' xEnd='1000.0' yEnd='800.0' height='250.0' thickness='10.0' arcExtent='-3.1415927' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall37' wallAtStart='wall36' wallAtEnd='wall38' xStart='1000.0' yStart='800.0' xEnd='900.0' yEnd='700.0' height='250.0' thickness='10.0' arcExtent='-3.1415927' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall38' wallAtStart='wall37' xStart='900.0' yStart='700.0' xEnd='800.0' yEnd='800.0' height='250.0' thickness='10.0' arcExtent='-3.1415927' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
</home>
"""

View File

@@ -1,150 +0,0 @@
# 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/>. *
# * *
# ***************************************************************************
import os
import FreeCAD as App
import Arch
import Draft
import Part
import Sketcher
import Arch
from bimtests import TestArchBase
from draftutils.messages import _msg
# TODO: move these tests to their relevant modules, remove this file
class TestArch(TestArchBase.TestArchBase):
def testStructure(self):
App.Console.PrintLog ('Checking BIM Structure...\n')
s = Arch.makeStructure(length=2,width=3,height=5)
self.assertTrue(s,"BIM Structure failed")
def testAxis(self):
App.Console.PrintLog ('Checking Arch Axis...\n')
a = Arch.makeAxis()
self.assertTrue(a,"Arch Axis failed")
def testSection(self):
App.Console.PrintLog ('Checking Arch Section...\n')
s = Arch.makeSectionPlane([])
self.assertTrue(s,"Arch Section failed")
def testStairs(self):
App.Console.PrintLog ('Checking Arch Stairs...\n')
s = Arch.makeStairs()
self.assertTrue(s,"Arch Stairs failed")
def testFrame(self):
App.Console.PrintLog ('Checking Arch Frame...\n')
l=Draft.makeLine(App.Vector(0,0,0),App.Vector(-2,0,0))
p = Draft.makeRectangle(length=.5,height=.5)
f = Arch.makeFrame(l,p)
self.assertTrue(f,"Arch Frame failed")
def testEquipment(self):
App.Console.PrintLog ('Checking Arch Equipment...\n')
box = App.ActiveDocument.addObject("Part::Box", "Box")
box.Length = 500
box.Width = 2000
box.Height = 600
equip = Arch.makeEquipment(box)
self.assertTrue(equip,"Arch Equipment failed")
def testPipe(self):
App.Console.PrintLog ('Checking Arch Pipe...\n')
pipe = Arch.makePipe(diameter=120, length=3000)
self.assertTrue(pipe,"Arch Pipe failed")
def testAdd(self):
App.Console.PrintLog ('Checking Arch Add...\n')
l=Draft.makeLine(App.Vector(0,0,0),App.Vector(2,0,0))
w = Arch.makeWall(l,width=0.2,height=2)
sb = Part.makeBox(1,1,1)
b = App.ActiveDocument.addObject('Part::Feature','Box')
b.Shape = sb
App.ActiveDocument.recompute()
Arch.addComponents(b,w)
App.ActiveDocument.recompute()
r = (w.Shape.Volume > 1.5)
self.assertTrue(r,"Arch Add failed")
def testRemove(self):
App.Console.PrintLog ('Checking Arch Remove...\n')
l=Draft.makeLine(App.Vector(0,0,0),App.Vector(2,0,0))
w = Arch.makeWall(l,width=0.2,height=2,align="Right")
sb = Part.makeBox(1,1,1)
b = App.ActiveDocument.addObject('Part::Feature','Box')
b.Shape = sb
App.ActiveDocument.recompute()
Arch.removeComponents(b,w)
App.ActiveDocument.recompute()
r = (w.Shape.Volume < 0.75)
self.assertTrue(r,"Arch Remove failed")
def testViewGeneration(self):
"""Tests the whole TD view generation workflow"""
operation = "View generation"
_msg(" Test '{}'".format(operation))
# Create a few objects
points = [App.Vector(0.0, 0.0, 0.0), App.Vector(2000.0, 0.0, 0.0)]
line = Draft.make_wire(points)
wall = Arch.makeWall(line, height=2000)
wpl = App.Placement(App.Vector(500,0,1500), App.Vector(1,0,0),-90)
win = Arch.makeWindowPreset('Fixed', width=1000.0, height=1000.0, h1=50.0, h2=50.0, h3=50.0, w1=100.0, w2=50.0, o1=0.0, o2=50.0, placement=wpl)
win.Hosts = [wall]
profile = Arch.makeProfile([169, 'HEA', 'HEA100', 'H', 100.0, 96.0, 5.0, 8.0])
column = Arch.makeStructure(profile, height=2000.0)
column.Profile = "HEA100"
column.Placement.Base = App.Vector(500.0, 600.0, 0.0)
level = Arch.makeFloor()
level.addObjects([wall, column])
App.ActiveDocument.recompute()
# Create a drawing view
section = Arch.makeSectionPlane(level)
drawing = Arch.make2DDrawing()
view = Draft.make_shape2dview(section)
cut = Draft.make_shape2dview(section)
cut.InPlace = False
cut.ProjectionMode = "Cutfaces"
drawing.addObjects([view, cut])
App.ActiveDocument.recompute()
# Create a TD page
tpath = os.path.join(App.getResourceDir(),"Mod","TechDraw","Templates","ISO","A3_Landscape_blank.svg")
page = App.ActiveDocument.addObject("TechDraw::DrawPage", "Page")
template = App.ActiveDocument.addObject("TechDraw::DrawSVGTemplate", "Template")
template.Template = tpath
page.Template = template
view = App.ActiveDocument.addObject("TechDraw::DrawViewDraft", "DraftView")
view.Source = drawing
page.addView(view)
view.Scale = 1.0
view.X = "20cm"
view.Y = "15cm"
App.ActiveDocument.recompute()
assert True

View File

@@ -30,11 +30,35 @@ import FreeCAD
class TestArchBase(unittest.TestCase):
def setUp(self):
print(f"Initializing: {self.__class__.__name__}")
self.document = FreeCAD.newDocument(self.__class__.__name__)
"""
Set up a new, clean document for the test. Ensure test isolation by creating a
uniquely-named document and cleaning up any potential leftovers from a previously failed
run.
"""
self.doc_name = self.__class__.__name__
# Close any document of the same name that might have been left over from a crashed or
# aborted test run. FreeCAD.getDocument() raises a NameError if the document is not found,
# so we wrap this check in a try...except block.
try:
FreeCAD.getDocument(self.doc_name)
# If getDocument() succeeds, the document exists and must be closed.
FreeCAD.closeDocument(self.doc_name)
except NameError:
# This is the expected path on a clean run; do nothing.
pass
# Create a fresh document for the current test.
self.document = FreeCAD.newDocument(self.doc_name)
self.assertEqual(self.document.Name, self.doc_name)
def tearDown(self):
FreeCAD.closeDocument(self.document.Name)
"""Close the test document after all tests in the class are complete."""
if hasattr(self, 'document') and self.document:
try:
FreeCAD.closeDocument(self.document.Name)
except Exception as e:
FreeCAD.Console.PrintError(f"Error during tearDown in {self.__class__.__name__}: {e}\n")
def printTestMessage(self, text, prepend_text="Test ", end="\n"):
"""Write messages to the console including the line ending.
@@ -43,3 +67,4 @@ class TestArchBase(unittest.TestCase):
passed as the prepend_text argument
"""
FreeCAD.Console.PrintMessage(prepend_text + text + end)

View File

@@ -0,0 +1,55 @@
# 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/>. *
# * *
# ***************************************************************************
import unittest
import FreeCAD
import FreeCADGui
from bimtests.TestArchBase import TestArchBase
class TestArchBaseGui(TestArchBase):
"""
The base class for all Arch/BIM GUI unit tests.
It inherits from TestArchBase to handle document setup and adds
GUI-specific initialization by activating the BIM workbench.
"""
@classmethod
def setUpClass(cls):
"""
Ensure the GUI is available and activate the BIM workbench once
before any tests in the inheriting class are run.
"""
if not FreeCAD.GuiUp:
raise unittest.SkipTest("Cannot run GUI tests in a CLI environment.")
# Activating the workbench ensures all GUI commands are loaded and ready.
FreeCADGui.activateWorkbench("BIMWorkbench")
def setUp(self):
"""
Run the parent's setup to create the uniquely named document.
The workbench is already activated by setUpClass.
"""
super().setUp()

View File

@@ -0,0 +1,79 @@
import FreeCAD as App
import FreeCADGui
import Arch
import Draft
import Part
import Sketcher
from bimtests.TestArchBaseGui import TestArchBaseGui
class TestArchBuildingPartGui(TestArchBaseGui):
def testBuildingPart(self):
"""Create a BuildingPart from a wall with a window and check its shape.
"""
# Also regression test for:
# https://github.com/FreeCAD/FreeCAD/issues/6178
#operation = "Arch BuildingPart"
#_msg(" Test '{}'".format(operation))
# Most of the code below taken from testWindow function.
line = Draft.makeLine(App.Vector(0, 0, 0), App.Vector(3000, 0, 0))
wall = Arch.makeWall(line)
sk = App.ActiveDocument.addObject("Sketcher::SketchObject", "Sketch001")
sk.Placement.Rotation = App.Rotation(App.Vector(1, 0, 0), 90)
sk.addGeometry(Part.LineSegment(App.Vector( 500, 800, 0), App.Vector(1500, 800, 0)))
sk.addGeometry(Part.LineSegment(App.Vector(1500, 800, 0), App.Vector(1500, 2000, 0)))
sk.addGeometry(Part.LineSegment(App.Vector(1500, 2000, 0), App.Vector( 500, 2000, 0)))
sk.addGeometry(Part.LineSegment(App.Vector( 500, 2000, 0), App.Vector( 500, 800, 0)))
sk.addConstraint(Sketcher.Constraint('Coincident', 0, 2, 1, 1))
sk.addConstraint(Sketcher.Constraint('Coincident', 1, 2, 2, 1))
sk.addConstraint(Sketcher.Constraint('Coincident', 2, 2, 3, 1))
sk.addConstraint(Sketcher.Constraint('Coincident', 3, 2, 0, 1))
App.ActiveDocument.recompute()
win = Arch.makeWindow(sk)
Arch.removeComponents(win, host=wall)
App.ActiveDocument.recompute()
bp = Arch.makeBuildingPart()
# Wall visibility works when standalone
FreeCADGui.Selection.clearSelection()
FreeCADGui.Selection.addSelection(self.doc_name,wall.Name)
assert wall.Visibility
FreeCADGui.runCommand('Std_ToggleVisibility',0)
App.ActiveDocument.recompute()
assert not wall.Visibility
FreeCADGui.runCommand('Std_ToggleVisibility',0)
assert wall.Visibility
bp.Group = [wall]
App.ActiveDocument.recompute()
# Fails with OCC 7.5
# self.assertTrue(len(bp.Shape.Faces) == 16, "'{}' failed".format(operation))
# Wall visibility works when inside a BuildingPart
FreeCADGui.runCommand('Std_ToggleVisibility',0)
App.ActiveDocument.recompute()
assert not wall.Visibility
FreeCADGui.runCommand('Std_ToggleVisibility',0)
assert wall.Visibility
# Wall visibility works when BuildingPart Toggled
FreeCADGui.Selection.clearSelection()
FreeCADGui.Selection.addSelection(self.doc_name,bp.Name)
FreeCADGui.runCommand('Std_ToggleVisibility',0)
assert not wall.Visibility
FreeCADGui.runCommand('Std_ToggleVisibility',0)
assert wall.Visibility
# Wall visibiity works inside group inside BuildingPart Toggled
grp = App.ActiveDocument.addObject("App::DocumentObjectGroup","Group")
grp.Label="Group"
grp.Group = [wall]
bp.Group = [grp]
App.ActiveDocument.recompute()
assert wall.Visibility
FreeCADGui.runCommand('Std_ToggleVisibility',0)
App.ActiveDocument.recompute()
assert not wall.Visibility
FreeCADGui.runCommand('Std_ToggleVisibility',0)
App.ActiveDocument.recompute()
assert wall.Visibility

View File

@@ -26,6 +26,7 @@
import Arch
import Draft
import Part
import FreeCAD as App
from bimtests import TestArchBase
from draftutils.messages import _msg
@@ -34,6 +35,32 @@ from math import pi, cos, sin, radians
class TestArchComponent(TestArchBase.TestArchBase):
def testAdd(self):
App.Console.PrintLog ('Checking Arch Add...\n')
l=Draft.makeLine(App.Vector(0,0,0),App.Vector(2,0,0))
w = Arch.makeWall(l,width=0.2,height=2)
sb = Part.makeBox(1,1,1)
b = App.ActiveDocument.addObject('Part::Feature','Box')
b.Shape = sb
App.ActiveDocument.recompute()
Arch.addComponents(b,w)
App.ActiveDocument.recompute()
r = (w.Shape.Volume > 1.5)
self.assertTrue(r,"Arch Add failed")
def testRemove(self):
App.Console.PrintLog ('Checking Arch Remove...\n')
l=Draft.makeLine(App.Vector(0,0,0),App.Vector(2,0,0))
w = Arch.makeWall(l,width=0.2,height=2,align="Right")
sb = Part.makeBox(1,1,1)
b = App.ActiveDocument.addObject('Part::Feature','Box')
b.Shape = sb
App.ActiveDocument.recompute()
Arch.removeComponents(b,w)
App.ActiveDocument.recompute()
r = (w.Shape.Volume < 0.75)
self.assertTrue(r,"Arch Remove failed")
def testBsplineSlabAreas(self):
"""Test the HorizontalArea and VerticalArea properties of a Bspline-based slab.

View File

@@ -23,15 +23,22 @@
# ***************************************************************************
import Arch
import FreeCAD as App
from bimtests import TestArchBase
class TestArchEquipment(TestArchBase.TestArchBase):
def test_makeEquipment(self):
"""Test the makeEquipment function."""
operation = "Testing makeEquipment..."
self.printTestMessage(operation)
obj = Arch.makeEquipment()
self.assertIsNotNone(obj, "makeEquipment failed to create an object")
self.assertEqual(obj.Label, "Equipment", "Incorrect default label for Equipment")
self.assertEqual(obj.Label, "Equipment", "Incorrect default label for Equipment")
def testEquipment(self):
box = App.ActiveDocument.addObject("Part::Box", "Box")
box.Length = 500
box.Width = 2000
box.Height = 600
equip = Arch.makeEquipment(box)
self.assertTrue(equip,"Arch Equipment failed")

View File

@@ -23,15 +23,21 @@
# ***************************************************************************
import Arch
import Draft
import FreeCAD as App
from bimtests import TestArchBase
class TestArchFrame(TestArchBase.TestArchBase):
def test_makeFrame(self):
"""Test the makeFrame function."""
operation = "Testing makeFrame..."
self.printTestMessage(operation)
obj = Arch.makeFrame(None, None)
self.assertIsNotNone(obj, "makeFrame failed to create an object")
self.assertEqual(obj.Label, "Frame", "Incorrect default label for Frame")
self.assertEqual(obj.Label, "Frame", "Incorrect default label for Frame")
def testFrame(self):
l=Draft.makeLine(App.Vector(0,0,0),App.Vector(-2,0,0))
p = Draft.makeRectangle(length=.5,height=.5)
f = Arch.makeFrame(l,p)
self.assertTrue(f,"Arch Frame failed")

View File

@@ -0,0 +1,88 @@
import FreeCAD as App
from bimtests.TestArchBaseGui import TestArchBaseGui
class TestArchImportersGui(TestArchBaseGui):
def testImportSH3D(self):
"""Import a SweetHome 3D file
"""
import BIM.importers.importSH3DHelper
importer = BIM.importers.importSH3DHelper.SH3DImporter(None)
importer.import_sh3d_from_string(SH3D_HOME)
assert App.ActiveDocument.Site
assert App.ActiveDocument.BuildingPart.Label == "Building"
assert App.ActiveDocument.BuildingPart001.Label == "Level"
assert App.ActiveDocument.Wall
SH3D_HOME = """<?xml version='1.0'?>
<home version='7200' name='0-JoinWall.sh3d' camera='topCamera' wallHeight='250.0'>
<property name='com.eteks.sweethome3d.SweetHome3D.CatalogPaneDividerLocation' value='327'/>
<property name='com.eteks.sweethome3d.SweetHome3D.ColumnWidths' value='100,84,82,85,84'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameHeight' value='576'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameMaximized' value='true'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameWidth' value='1092'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameX' value='50'/>
<property name='com.eteks.sweethome3d.SweetHome3D.FrameY' value='87'/>
<property name='com.eteks.sweethome3d.SweetHome3D.MainPaneDividerLocation' value='441'/>
<property name='com.eteks.sweethome3d.SweetHome3D.PlanPaneDividerLocation' value='263'/>
<property name='com.eteks.sweethome3d.SweetHome3D.PlanScale' value='0.21343713'/>
<property name='com.eteks.sweethome3d.SweetHome3D.PlanViewportX' value='0'/>
<property name='com.eteks.sweethome3d.SweetHome3D.PlanViewportY' value='0'/>
<property name='com.eteks.sweethome3d.SweetHome3D.ScreenHeight' value='720'/>
<property name='com.eteks.sweethome3d.SweetHome3D.ScreenWidth' value='1366'/>
<furnitureVisibleProperty name='NAME'/>
<furnitureVisibleProperty name='WIDTH'/>
<furnitureVisibleProperty name='DEPTH'/>
<furnitureVisibleProperty name='HEIGHT'/>
<furnitureVisibleProperty name='VISIBLE'/>
<environment groundColor='FFB78744' skyColor='00CCE4FC' lightColor='00D0D0D0' ceillingLightColor='00D0D0D0' photoWidth='400' photoHeight='300' photoAspectRatio='VIEW_3D_RATIO' photoQuality='0' videoWidth='320' videoAspectRatio='RATIO_4_3' videoQuality='0' videoFrameRate='25'>
<texture attribute='skyTexture' name='Cloudy' creator='eTeks' catalogId='eTeks#cloudy' width='100.0' height='27.6' image='0'/>
</environment>
<compass x='-100.0' y='50.0' diameter='100.0' northDirection='0.0' longitude='-0.06428629' latitude='0.70511305' timeZone='Europe/Madrid'/>
<observerCamera attribute='observerCamera' lens='PINHOLE' x='50.0' y='50.0' z='170.0' yaw='5.4977875' pitch='0.19634955' fieldOfView='1.0995575' time='1729080000000'/>
<camera attribute='topCamera' lens='PINHOLE' x='1304.082' y='1936.5889' z='1526.6199' yaw='8.98363' pitch='0.7049999' fieldOfView='1.0995575' time='1729080000000'/>
<wall id='wall0' wallAtEnd='wall1' xStart='0.0' yStart='0.0' xEnd='100.0' yEnd='0.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall1' wallAtStart='wall0' wallAtEnd='wall2' xStart='100.0' yStart='0.0' xEnd='200.0' yEnd='0.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall2' wallAtStart='wall1' xStart='200.0' yStart='0.0' xEnd='300.0' yEnd='0.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall3' wallAtEnd='wall4' xStart='0.0' yStart='50.0' xEnd='100.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall4' wallAtStart='wall3' wallAtEnd='wall5' xStart='100.0' yStart='100.0' xEnd='200.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall5' wallAtStart='wall4' xStart='200.0' yStart='100.0' xEnd='300.0' yEnd='50.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall6' wallAtEnd='wall7' xStart='0.0' yStart='200.0' xEnd='100.0' yEnd='300.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall7' wallAtStart='wall6' wallAtEnd='wall8' xStart='100.0' yStart='300.0' xEnd='200.0' yEnd='300.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall8' wallAtStart='wall7' xStart='200.0' yStart='300.0' xEnd='300.0' yEnd='200.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall9' wallAtEnd='wall10' xStart='100.0' yStart='400.0' xEnd='100.0' yEnd='500.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall10' wallAtStart='wall9' wallAtEnd='wall11' xStart='100.0' yStart='500.0' xEnd='200.0' yEnd='500.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall11' wallAtStart='wall10' xStart='200.0' yStart='500.0' xEnd='200.0' yEnd='400.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall12' wallAtStart='wall14' wallAtEnd='wall13' xStart='150.0' yStart='600.0' xEnd='100.0' yEnd='700.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall13' wallAtStart='wall12' wallAtEnd='wall14' xStart='100.0' yStart='700.0' xEnd='200.0' yEnd='700.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall14' wallAtStart='wall13' wallAtEnd='wall12' xStart='200.0' yStart='700.0' xEnd='150.0' yEnd='600.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall15' wallAtEnd='wall16' xStart='400.0' yStart='150.0' xEnd='500.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall16' wallAtStart='wall15' wallAtEnd='wall17' xStart='500.0' yStart='100.0' xEnd='600.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall17' wallAtStart='wall16' xStart='600.0' yStart='100.0' xEnd='700.0' yEnd='50.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall18' wallAtEnd='wall19' xStart='400.0' yStart='400.0' xEnd='500.0' yEnd='300.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall19' wallAtStart='wall18' wallAtEnd='wall20' xStart='500.0' yStart='300.0' xEnd='600.0' yEnd='300.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall20' wallAtStart='wall19' xStart='600.0' yStart='300.0' xEnd='700.0' yEnd='200.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall21' wallAtEnd='wall22' xStart='400.0' yStart='600.0' xEnd='400.0' yEnd='500.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall22' wallAtStart='wall21' wallAtEnd='wall23' xStart='400.0' yStart='500.0' xEnd='600.0' yEnd='500.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall23' wallAtStart='wall22' xStart='600.0' yStart='500.0' xEnd='600.0' yEnd='400.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall24' wallAtEnd='wall25' xStart='600.0' yStart='800.0' xEnd='500.0' yEnd='700.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall25' wallAtStart='wall24' wallAtEnd='wall26' xStart='500.0' yStart='700.0' xEnd='600.0' yEnd='700.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall26' wallAtStart='wall25' xStart='600.0' yStart='700.0' xEnd='500.0' yEnd='600.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall27' wallAtStart='wall30' wallAtEnd='wall28' xStart='800.0' yStart='0.0' xEnd='1000.0' yEnd='0.0' height='250.0' thickness='10.0' arcExtent='1.0471976' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall28' wallAtStart='wall27' wallAtEnd='wall29' xStart='1000.0' yStart='0.0' xEnd='1000.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall29' wallAtStart='wall28' wallAtEnd='wall30' xStart='1000.0' yStart='100.0' xEnd='800.0' yEnd='100.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall30' wallAtStart='wall29' wallAtEnd='wall27' xStart='800.0' yStart='100.0' xEnd='800.0' yEnd='0.0' height='250.0' thickness='10.0' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall31' wallAtEnd='wall32' xStart='800.0' yStart='400.0' xEnd='1000.0' yEnd='200.0' height='250.0' thickness='10.0' arcExtent='1.5707964' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall32' wallAtStart='wall31' wallAtEnd='wall33' xStart='1000.0' yStart='200.0' xEnd='1200.0' yEnd='400.0' height='250.0' thickness='10.0' arcExtent='1.5707964' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall33' wallAtStart='wall32' wallAtEnd='wall34' xStart='1200.0' yStart='400.0' xEnd='1000.0' yEnd='600.0' height='250.0' thickness='10.0' arcExtent='1.5707964' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall34' wallAtStart='wall33' xStart='1000.0' yStart='600.0' xEnd='800.0' yEnd='400.0' height='250.0' thickness='10.0' arcExtent='1.5707964' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall35' wallAtEnd='wall36' xStart='800.0' yStart='800.0' xEnd='900.0' yEnd='900.0' height='250.0' thickness='10.0' arcExtent='-3.1415927' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall36' wallAtStart='wall35' wallAtEnd='wall37' xStart='900.0' yStart='900.0' xEnd='1000.0' yEnd='800.0' height='250.0' thickness='10.0' arcExtent='-3.1415927' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall37' wallAtStart='wall36' wallAtEnd='wall38' xStart='1000.0' yStart='800.0' xEnd='900.0' yEnd='700.0' height='250.0' thickness='10.0' arcExtent='-3.1415927' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
<wall id='wall38' wallAtStart='wall37' xStart='900.0' yStart='700.0' xEnd='800.0' yEnd='800.0' height='250.0' thickness='10.0' arcExtent='-3.1415927' pattern='hatchUp' topColor='FF0000FF' leftSideColor='FF00FF00' rightSideColor='FFFF0000'/>
</home>
"""

View File

@@ -170,4 +170,27 @@ class TestArchRebar(TestArchBase.TestArchBase):
# The Shape should be the default null/empty shape.
self.assertIsNotNone(rebar.Shape, "Rebar object should always have a Shape attribute.")
self.assertTrue(rebar.Shape.isNull(),
"Rebar.Shape should be a null shape if no Base is provided.")
"Rebar.Shape should be a null shape if no Base is provided.")
def test_RebarMe(self):
"""
Tests the creation of an Arch Rebar
"""
import Sketcher
self.printTestMessage('Checking Arch Rebar...\n')
s = Arch.makeStructure(length=2, width=3, height=5)
sk = self.document.addObject('Sketcher::SketchObject', 'Sketch')
sk.AttachmentSupport = (s, ["Face6"])
sk.addGeometry(Part.LineSegment(FreeCAD.Vector(-0.85, 1.25, 0), FreeCAD.Vector(0.75, 1.25, 0)))
sk.addGeometry(Part.LineSegment(FreeCAD.Vector(0.75, 1.25, 0), FreeCAD.Vector(0.75, -1.20, 0)))
sk.addGeometry(Part.LineSegment(FreeCAD.Vector(0.75, -1.20, 0), FreeCAD.Vector(-0.85, -1.20, 0)))
sk.addGeometry(Part.LineSegment(FreeCAD.Vector(-0.85, -1.20, 0), FreeCAD.Vector(-0.85, 1.25, 0)))
sk.addConstraint(Sketcher.Constraint('Coincident', 0, 2, 1, 1))
sk.addConstraint(Sketcher.Constraint('Coincident', 1, 2, 2, 1))
sk.addConstraint(Sketcher.Constraint('Coincident', 2, 2, 3, 1))
sk.addConstraint(Sketcher.Constraint('Coincident', 3, 2, 0, 1))
self.document.recompute()
r = Arch.makeRebar(s, sk, diameter=.1, amount=2)
self.document.recompute()
self.assertTrue(r, "Arch Rebar creation failed")
self.assertFalse(r.Shape.isNull(), "Rebar shape is null")

View File

@@ -23,6 +23,9 @@
# ***************************************************************************
import Arch
import Draft
import os
import FreeCAD as App
from bimtests import TestArchBase
class TestArchSectionPlane(TestArchBase.TestArchBase):
@@ -34,4 +37,47 @@ class TestArchSectionPlane(TestArchBase.TestArchBase):
section_plane = Arch.makeSectionPlane(name="TestSectionPlane")
self.assertIsNotNone(section_plane, "makeSectionPlane failed to create a section plane object.")
self.assertEqual(section_plane.Label, "TestSectionPlane", "Section plane label is incorrect.")
self.assertEqual(section_plane.Label, "TestSectionPlane", "Section plane label is incorrect.")
def testViewGeneration(self):
"""Tests the whole TD view generation workflow"""
# Create a few objects
points = [App.Vector(0.0, 0.0, 0.0), App.Vector(2000.0, 0.0, 0.0)]
line = Draft.make_wire(points)
wall = Arch.makeWall(line, height=2000)
wpl = App.Placement(App.Vector(500,0,1500), App.Vector(1,0,0),-90)
win = Arch.makeWindowPreset('Fixed', width=1000.0, height=1000.0, h1=50.0, h2=50.0, h3=50.0, w1=100.0, w2=50.0, o1=0.0, o2=50.0, placement=wpl)
win.Hosts = [wall]
profile = Arch.makeProfile([169, 'HEA', 'HEA100', 'H', 100.0, 96.0, 5.0, 8.0])
column = Arch.makeStructure(profile, height=2000.0)
column.Profile = "HEA100"
column.Placement.Base = App.Vector(500.0, 600.0, 0.0)
level = Arch.makeFloor()
level.addObjects([wall, column])
App.ActiveDocument.recompute()
# Create a drawing view
section = Arch.makeSectionPlane(level)
drawing = Arch.make2DDrawing()
view = Draft.make_shape2dview(section)
cut = Draft.make_shape2dview(section)
cut.InPlace = False
cut.ProjectionMode = "Cutfaces"
drawing.addObjects([view, cut])
App.ActiveDocument.recompute()
# Create a TD page
tpath = os.path.join(App.getResourceDir(),"Mod","TechDraw","Templates","ISO","A3_Landscape_blank.svg")
page = App.ActiveDocument.addObject("TechDraw::DrawPage", "Page")
template = App.ActiveDocument.addObject("TechDraw::DrawSVGTemplate", "Template")
template.Template = tpath
page.Template = template
view = App.ActiveDocument.addObject("TechDraw::DrawViewDraft", "DraftView")
view.Source = drawing
page.addView(view)
view.Scale = 1.0
view.X = "20cm"
view.Y = "15cm"
App.ActiveDocument.recompute()
assert True

View File

@@ -0,0 +1,34 @@
# 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/>. *
# * *
# ***************************************************************************
import FreeCAD as App
import Arch
from bimtests import TestArchBase
class TestArchStructure(TestArchBase.TestArchBase):
def testStructure(self):
App.Console.PrintLog ('Checking BIM Structure...\n')
s = Arch.makeStructure(length=2,width=3,height=5)
self.assertTrue(s,"BIM Structure failed")

View File

@@ -309,34 +309,39 @@ class TestArchWindow(TestArchBase.TestArchBase):
def test_create_window_on_xz_plane(self):
"""Test creating a window oriented on the XZ (vertical) plane."""
sketch = self._create_sketch_with_wires("Sketch_XZ_Plane",
[(0,0,1000,1200), (100,100,800,1000)])
sketch.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(1,0,0), 90)
# Create a a frame-like profile.
sketch = self._create_sketch_with_wires("Sketch_XZ_Plane",
[(0, 0, 1000, 1200), (100, 100, 800, 1000)])
# Orient the sketch to the XZ plane.
sketch.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), 90)
self.document.recompute()
# Create the window using explicit parts.
window = Arch.makeWindow(baseobj=sketch, name="Window_XZ_Plane")
window.WindowParts = [
"Frame", "Frame", "Wire0,Wire1", "60", "0",
"Glass", "Glass panel", "Wire1", "10", "25"
"Frame", "Frame", "Wire0,Wire1", "60", "0", # A 60mm thick frame
"Glass", "Glass panel", "Wire1", "10", "25" # A 10mm thick glass pane, offset by 25mm
]
self.document.recompute()
self.assertFalse(window.Shape.isNull())
self.assertGreater(len(window.Shape.Solids), 0)
expected_normal_y = 1.0
self.assertAlmostEqual(window.Normal.x, 0.0, places=5)
self.assertAlmostEqual(window.Normal.y, expected_normal_y, places=5,
msg=f"Window normal Y component incorrect. Expected approx {expected_normal_y}, got {window.Normal.y}")
self.assertAlmostEqual(window.Normal.z, 0.0, places=5)
# Check the resulting geometry's orientation and dimensions.
self.assertFalse(window.Shape.isNull(), "Window shape should not be null.")
self.assertEqual(len(window.Shape.Solids), 2, "Window should contain two solids (frame and glass).")
bb = window.Shape.BoundBox
# The window's overall "thickness" is determined by the frame (60mm).
# Its "width" (X) and "height" (Z) should be larger.
self.assertGreater(bb.XLength, bb.YLength,
"Window XLength (width) should be greater than YLength (thickness).")
"Window XLength (width) should be greater than YLength (thickness).")
self.assertGreater(bb.ZLength, bb.YLength,
"Window ZLength (height) should be greater than YLength (thickness).")
"Window ZLength (height) should be greater than YLength (thickness).")
# Verify the overall thickness is correct (60mm, from the Frame component).
self.assertAlmostEqual(bb.YLength, 60.0, places=5,
msg="Window thickness (YLength) is incorrect.")
def test_opening_property_rotates_component(self):
"""Test that setting the Opening property rotates a hinged component."""
@@ -559,7 +564,7 @@ class TestArchWindow(TestArchBase.TestArchBase):
Helper to create a sketch with one or more specified rectangular wires.
Each rectangle is defined in the sketch's local XY plane. The sketch
is created in the current document (`self.document`). After adding all
is created in the current document (`self.doc`). After adding all
geometry and basic coincident constraints for each rectangle, the
document is recomputed.
@@ -608,7 +613,7 @@ class TestArchWindow(TestArchBase.TestArchBase):
"""
Helper to create a rectangular sketch with "Width" and "Height" named constraints.
The sketch is created in the current document (`self.document`) on the
The sketch is created in the current document (`self.doc`) on the
default XY plane. It consists of a single rectangle defined by four
line segments, with its bottom-left corner at (0,0,0) in the
sketch's local coordinates.