BIM: allow boundaries to be defined from a single object (e.g. wall) (#20158)
* BIM: Add test for space from single wall boundaries * BIM: Arch_Space, enable creation of spaces from single objects with boundaries * BIM: update and expand docstring
This commit is contained in:
@@ -768,33 +768,93 @@ def makeSite(objectslist=None,baseobj=None,name=None):
|
||||
|
||||
|
||||
def makeSpace(objects=None,baseobj=None,name=None):
|
||||
"""Creates a space object from the given objects.
|
||||
|
||||
"""makeSpace([objects],[baseobj],[name]): Creates a space object from the given objects.
|
||||
Objects can be one document object, in which case it becomes the base shape of the space
|
||||
object, or a list of selection objects as got from getSelectionEx(), or a list of tuples
|
||||
(object, subobjectname)"""
|
||||
Parameters
|
||||
----------
|
||||
objects : object or List(<SelectionObject>) or App::PropertyLinkSubList, optional
|
||||
The object or selection set that defines the space. If a single object is given,
|
||||
it becomes the base shape for the object. If the object or selection set contains
|
||||
subelements, these will be used as the boundaries to create the space. By default None.
|
||||
baseobj : object or List(<SelectionObject>) or App::PropertyLinkSubList, optional
|
||||
Currently unimplemented, it replaces and behaves in the same way as the objects parameter
|
||||
if defined. By default None.
|
||||
name : str, optional
|
||||
The user-facing name to assign to the space object's label. By default None, in
|
||||
which case the label is set to "Space".
|
||||
|
||||
Notes
|
||||
-----
|
||||
The objects parameter can be passed using either of these different formats:
|
||||
1. Single object (e.g. a Part::Feature document object). Will be used as the space's base
|
||||
shape.
|
||||
::
|
||||
objects = <Part::Feature>
|
||||
2. List of selection objects, as provided by ``Gui.Selection.getSelectionEx()``. This
|
||||
requires the GUI to be active. The `SubObjects` property of each selection object in the
|
||||
list defines the space's boundaries. If the list contains a single selection object without
|
||||
subobjects, or with only one subobject, the object in its ``Object`` property is used as
|
||||
the base shape.
|
||||
::
|
||||
objects = [<SelectionObject>, ...]
|
||||
3. A list of tuples that can be assigned to an ``App::PropertyLinkSubList`` property. Each
|
||||
tuple contains a document object and a nested tuple of subobjects that define boundaries. If
|
||||
the list contains a single tuple without a nested subobjects tuple, or a subobjects tuple
|
||||
with only one subobject, the object in the tuple is used as the base shape.
|
||||
::
|
||||
objects = [(obj1, ("Face1")), (obj2, ("Face1")), ...]
|
||||
objects = [(obj, ("Face1", "Face2", "Face3", "Face4"))]
|
||||
"""
|
||||
import ArchSpace
|
||||
if not FreeCAD.ActiveDocument:
|
||||
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
||||
return
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Space")
|
||||
obj.Label = name if name else translate("Arch","Space")
|
||||
ArchSpace._Space(obj)
|
||||
space = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Space")
|
||||
space.Label = name if name else translate("Arch","Space")
|
||||
ArchSpace._Space(space)
|
||||
if FreeCAD.GuiUp:
|
||||
ArchSpace._ViewProviderSpace(obj.ViewObject)
|
||||
ArchSpace._ViewProviderSpace(space.ViewObject)
|
||||
if baseobj:
|
||||
objects = baseobj
|
||||
if objects:
|
||||
if not isinstance(objects,list):
|
||||
objects = [objects]
|
||||
if len(objects) == 1:
|
||||
obj.Base = objects[0]
|
||||
if FreeCAD.GuiUp:
|
||||
objects[0].ViewObject.hide()
|
||||
|
||||
isSingleObject = lambda objs: len(objs) == 1
|
||||
|
||||
# We assume that the objects list is not a mixed set. The type of the first
|
||||
# object will determine the type of the set.
|
||||
# Input to this function can come into three different formats. First convert it
|
||||
# to a common format: [ (<Part::PartFeature>, ["Face1", ...]), ... ]
|
||||
if (hasattr(objects[0], "isDerivedFrom") and
|
||||
objects[0].isDerivedFrom("Gui::SelectionObject")):
|
||||
# Selection set: convert to common format
|
||||
# [<SelectionObject>, ...]
|
||||
objects = [(obj.Object, obj.SubElementNames) for obj in objects]
|
||||
elif (isinstance(objects[0], tuple) or isinstance(objects[0], list)):
|
||||
# Tuple or list of object with subobjects: pass unmodified
|
||||
# [ (<Part::PartFeature>, ["Face1", ...]), ... ]
|
||||
pass
|
||||
else:
|
||||
obj.Proxy.addSubobjects(obj,objects)
|
||||
return obj
|
||||
# Single object: assume anything else passed is a single object with no
|
||||
# boundaries.
|
||||
# [ <Part::PartFeature> ]
|
||||
objects = [(objects[0], [])]
|
||||
|
||||
if isSingleObject(objects):
|
||||
# For a single object, having boundaries is determined by them being defined
|
||||
# as more than one subelement (e.g. two faces)
|
||||
boundaries = [obj for obj in objects if len(obj[1]) > 1]
|
||||
else:
|
||||
boundaries = [obj for obj in objects if obj[1]]
|
||||
|
||||
if isSingleObject(objects) and not boundaries:
|
||||
space.Base = objects[0][0]
|
||||
if FreeCAD.GuiUp:
|
||||
objects[0][0].ViewObject.hide()
|
||||
else:
|
||||
space.Proxy.addSubobjects(space, boundaries)
|
||||
return space
|
||||
|
||||
|
||||
def makeStairs(baseobj=None,length=None,width=None,height=None,steps=None,name=None):
|
||||
|
||||
@@ -818,6 +818,58 @@ class ArchTest(unittest.TestCase):
|
||||
App.ActiveDocument.recompute()
|
||||
assert True
|
||||
|
||||
def test_SpaceFromSingleWall(self):
|
||||
"""Create a space from boundaries of a single wall.
|
||||
"""
|
||||
from FreeCAD import Units
|
||||
|
||||
operation = "Arch Space from single wall"
|
||||
_msg(f"\n Test '{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:
|
||||
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::PartFeature>, ["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}")
|
||||
|
||||
def tearDown(self):
|
||||
App.closeDocument("ArchTest")
|
||||
pass
|
||||
|
||||
@@ -56,10 +56,7 @@ class Arch_Space:
|
||||
sel = FreeCADGui.Selection.getSelection()
|
||||
if sel:
|
||||
FreeCADGui.Control.closeDialog()
|
||||
if len(sel) == 1:
|
||||
FreeCADGui.doCommand("obj = Arch.makeSpace(FreeCADGui.Selection.getSelection())")
|
||||
else:
|
||||
FreeCADGui.doCommand("obj = Arch.makeSpace(FreeCADGui.Selection.getSelectionEx())")
|
||||
FreeCADGui.doCommand("obj = Arch.makeSpace(FreeCADGui.Selection.getSelectionEx())")
|
||||
FreeCADGui.addModule("Draft")
|
||||
FreeCADGui.doCommand("Draft.autogroup(obj)")
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
|
||||
Reference in New Issue
Block a user