2192 lines
80 KiB
Python
2192 lines
80 KiB
Python
# SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
# ***************************************************************************
|
|
# * *
|
|
# * Copyright (c) 2011 Yorik van Havre <yorik@uncreated.net> *
|
|
# * *
|
|
# * 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/>. *
|
|
# * *
|
|
# ***************************************************************************
|
|
|
|
"""Core API for architectural and Building Information Modeling (BIM) in FreeCAD.
|
|
|
|
Provides tools for creating parametric architectural elements (walls, windows,
|
|
structures) and managing BIM data. Serves as the foundation for both the BIM
|
|
Workbench and third-party extensions.
|
|
|
|
## Features
|
|
- Parametric architectural components (walls, floors, roofs, windows)
|
|
- BIM data support (materials, IFC properties, classification systems)
|
|
- Integration with FreeCAD's core (Part, Draft) and other workbenches
|
|
- Object creation utilities for architectural workflows
|
|
|
|
## Usage
|
|
Designed for:
|
|
1. Internal API for FreeCAD's built-in BIM commands
|
|
2. Public API for add-on developers creating extension macros, workbenches, or
|
|
other specialized BIM tools
|
|
|
|
## Examples
|
|
```python
|
|
import Arch
|
|
wall = Arch.makeWall(length=5000, width=200, height=3000) # mm units
|
|
wall.recompute()
|
|
```
|
|
"""
|
|
__title__ = "FreeCAD Arch API"
|
|
__author__ = "Yorik van Havre"
|
|
__url__ = "https://www.freecad.org"
|
|
|
|
import FreeCAD
|
|
from typing import Optional
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
FreeCADGui.updateLocale()
|
|
|
|
QT_TRANSLATE_NOOP = FreeCAD.Qt.QT_TRANSLATE_NOOP
|
|
translate = FreeCAD.Qt.translate
|
|
|
|
|
|
# Importing all members from these modules enables us to use them directly by
|
|
# simply importing the Arch module, as if they were part of this module.
|
|
from ArchCommands import *
|
|
from ArchWindowPresets import *
|
|
|
|
# TODO: migrate this one
|
|
# Currently makeStructure, makeStructuralSystem need migration
|
|
from ArchStructure import *
|
|
|
|
|
|
# make functions
|
|
|
|
def makeAxis(num=1, size=1000, name=None):
|
|
"""
|
|
Creates an axis set in the active document.
|
|
|
|
Parameters
|
|
----------
|
|
num : int, optional
|
|
The number of axes to create. Defaults to 1.
|
|
size : float, optional
|
|
The interval distance between axes. Defaults to 1000.
|
|
name : str, optional
|
|
The name to assign to the created axis object. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created axis object.
|
|
"""
|
|
import ArchAxis
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Axis")
|
|
obj.Label = name if name else translate("Arch", "Axes")
|
|
ArchAxis._Axis(obj)
|
|
if FreeCAD.GuiUp:
|
|
ArchAxis._ViewProviderAxis(obj.ViewObject)
|
|
if num:
|
|
dist = []
|
|
angles = []
|
|
for i in range(num):
|
|
if i == 0:
|
|
dist.append(0)
|
|
else:
|
|
dist.append(float(size))
|
|
angles.append(float(0))
|
|
obj.Distances = dist
|
|
obj.Angles = angles
|
|
FreeCAD.ActiveDocument.recompute()
|
|
return obj
|
|
|
|
|
|
def makeAxisSystem(axes, name=None):
|
|
"""
|
|
Creates an axis system from the given list of axes.
|
|
|
|
Parameters
|
|
----------
|
|
axes : list of Part::FeaturePython
|
|
A list of axis objects to include in the axis system.
|
|
name : str, optional
|
|
The name to assign to the created axis system. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
App::FeaturePython
|
|
The created axis system object.
|
|
"""
|
|
import ArchAxisSystem
|
|
if not isinstance(axes, list):
|
|
axes = [axes]
|
|
obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", "AxisSystem")
|
|
obj.Label = name if name else translate("Arch", "Axis System")
|
|
ArchAxisSystem._AxisSystem(obj)
|
|
obj.Axes = axes
|
|
if FreeCAD.GuiUp:
|
|
ArchAxisSystem._ViewProviderAxisSystem(obj.ViewObject)
|
|
FreeCAD.ActiveDocument.recompute()
|
|
return obj
|
|
|
|
|
|
def makeBuildingPart(objectslist=None, baseobj=None, name=None):
|
|
"""
|
|
Creates a building part including the given objects in the list.
|
|
|
|
Parameters
|
|
----------
|
|
objectslist : list of Part::FeaturePython, optional
|
|
A list of objects to include in the building part. Defaults to None.
|
|
baseobj : Part::FeaturePython, optional
|
|
The base object for the building part. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created building part. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
App::GeometryPython
|
|
The created building part object.
|
|
"""
|
|
import ArchBuildingPart
|
|
obj = FreeCAD.ActiveDocument.addObject("App::GeometryPython", "BuildingPart")
|
|
#obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython","BuildingPart")
|
|
obj.Label = name if name else translate("Arch", "BuildingPart")
|
|
ArchBuildingPart.BuildingPart(obj)
|
|
obj.IfcType = "Building Element Part"
|
|
if FreeCAD.GuiUp:
|
|
ArchBuildingPart.ViewProviderBuildingPart(obj.ViewObject)
|
|
if objectslist:
|
|
if isinstance(objectslist, (list, tuple)):
|
|
obj.addObjects(objectslist)
|
|
else:
|
|
obj.addObject(objectslist)
|
|
return obj
|
|
|
|
|
|
def makeFloor(objectslist=None, baseobj=None, name=None):
|
|
"""
|
|
Creates a floor/level in the active document.
|
|
|
|
Parameters
|
|
----------
|
|
objectslist : list of Part::FeaturePython, optional
|
|
A list of objects to include in the floor. Defaults to None.
|
|
baseobj : Part::FeaturePython, optional
|
|
The base object for the floor. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created floor. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
App::GeometryPython
|
|
The created floor object.
|
|
"""
|
|
obj = makeBuildingPart(objectslist)
|
|
obj.Label = name if name else translate("Arch", "Level")
|
|
obj.IfcType = "Building Storey"
|
|
obj.CompositionType = "ELEMENT"
|
|
return obj
|
|
|
|
|
|
def makeBuilding(objectslist=None, baseobj=None, name=None):
|
|
"""
|
|
Creates a building in the active document.
|
|
|
|
Parameters
|
|
----------
|
|
objectslist : list of Part::FeaturePython, optional
|
|
A list of objects to include in the building. Defaults to None.
|
|
baseobj : Part::FeaturePython, optional
|
|
The base object for the building. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created building. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
App::GeometryPython
|
|
The created building object.
|
|
"""
|
|
import ArchBuildingPart
|
|
obj = makeBuildingPart(objectslist)
|
|
obj.Label = name if name else translate("Arch", "Building")
|
|
obj.IfcType = "Building"
|
|
obj.CompositionType = "ELEMENT"
|
|
t = QT_TRANSLATE_NOOP("App::Property", "The type of this building")
|
|
obj.addProperty("App::PropertyEnumeration", "BuildingType", "Building", t, locked=True)
|
|
obj.BuildingType = ArchBuildingPart.BuildingTypes
|
|
if FreeCAD.GuiUp:
|
|
obj.ViewObject.ShowLevel = False
|
|
obj.ViewObject.ShowLabel = False
|
|
return obj
|
|
|
|
|
|
def make2DDrawing(objectslist=None, baseobj=None, name=None):
|
|
"""
|
|
Creates a 2D drawing view in the active document.
|
|
|
|
Parameters
|
|
----------
|
|
objectslist : list of Part::FeaturePython, optional
|
|
A list of objects to include in the drawing. Defaults to None.
|
|
baseobj : Part::FeaturePython, optional
|
|
The base object for the drawing. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created drawing. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
App::GeometryPython
|
|
The created 2D drawing object.
|
|
"""
|
|
obj = makeBuildingPart(objectslist)
|
|
obj.Label = name if name else translate("Arch", "Drawing")
|
|
obj.IfcType = "Annotation"
|
|
obj.ObjectType = "DRAWING"
|
|
obj.setEditorMode("Area", 2)
|
|
obj.setEditorMode("Height", 2)
|
|
obj.setEditorMode("LevelOffset", 2)
|
|
obj.setEditorMode("OnlySolids", 2)
|
|
obj.setEditorMode("HeightPropagate", 2)
|
|
if FreeCAD.GuiUp:
|
|
obj.ViewObject.DisplayOffset = FreeCAD.Placement()
|
|
obj.ViewObject.ShowLevel = False
|
|
return obj
|
|
|
|
|
|
def convertFloors(floor=None):
|
|
"""
|
|
Converts the given floor or building into building parts.
|
|
|
|
Parameters
|
|
----------
|
|
floor : Part::FeaturePython, optional
|
|
The floor or building to convert. If None, all Arch floors in the active document
|
|
are converted. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
None
|
|
"""
|
|
import Draft
|
|
import ArchBuildingPart
|
|
todel = []
|
|
if floor:
|
|
objset = [floor]
|
|
else:
|
|
objset = FreeCAD.ActiveDocument.Objects
|
|
for obj in objset:
|
|
if Draft.getType(obj) in ["Floor", "Building"]:
|
|
nobj = makeBuildingPart(obj.Group)
|
|
if Draft.getType(obj) == "Floor":
|
|
nobj.IfcType = "Building Storey"
|
|
nobj.CompositionType = "ELEMENT"
|
|
else:
|
|
nobj.IfcType = "Building"
|
|
nobj.CompositionType = "ELEMENT"
|
|
t = QT_TRANSLATE_NOOP("App::Property", "The type of this building")
|
|
nobj.addProperty(
|
|
"App::PropertyEnumeration", "BuildingType", "Building", t, locked=True
|
|
)
|
|
nobj.BuildingType = ArchBuildingPart.BuildingTypes
|
|
label = obj.Label
|
|
for parent in obj.InList:
|
|
if hasattr(parent, "Group"):
|
|
if obj in parent.Group:
|
|
parent.addObject(nobj)
|
|
#g = parent.Group
|
|
#g.append(nobj)
|
|
#parent.Group = g
|
|
todel.append(obj.Name)
|
|
if obj.ViewObject:
|
|
# some bug makes this trigger even efter the object has been deleted...
|
|
obj.ViewObject.Proxy.Object = None
|
|
# in case FreeCAD doesn't allow 2 objs with same label
|
|
obj.Label = obj.Label + " to delete"
|
|
nobj.Label = label
|
|
for n in todel:
|
|
from draftutils import todo
|
|
todo.ToDo.delay(FreeCAD.ActiveDocument.removeObject, n)
|
|
|
|
|
|
def makeCurtainWall(baseobj=None, name=None):
|
|
"""
|
|
Creates a curtain wall object in the active document.
|
|
|
|
Parameters
|
|
----------
|
|
baseobj : Part::FeaturePython, optional
|
|
The base object for the curtain wall. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created curtain wall. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created curtain wall object.
|
|
"""
|
|
curtainWall = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="CurtainWall",
|
|
internalName="CurtainWall",
|
|
defaultLabel=name if name else translate("Arch", "Curtain Wall"),
|
|
viewProviderName="ViewProviderCurtainWall",
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if baseobj:
|
|
curtainWall.Base = baseobj
|
|
if FreeCAD.GuiUp:
|
|
baseobj.ViewObject.hide()
|
|
|
|
return curtainWall
|
|
|
|
|
|
def makeEquipment(baseobj=None, placement=None, name=None):
|
|
"""
|
|
Creates an equipment object from the given base object in the active document.
|
|
|
|
Parameters
|
|
----------
|
|
baseobj : Part::FeaturePython or Mesh::Feature, optional
|
|
The base object for the equipment. Defaults to None.
|
|
placement : FreeCAD.Placement, optional
|
|
The placement of the equipment. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created equipment. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created equipment object.
|
|
"""
|
|
equipment = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_Equipment",
|
|
internalName="Equipment",
|
|
defaultLabel=name if name else translate("Arch", "Equipment"),
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if baseobj:
|
|
if baseobj.isDerivedFrom("Mesh::Feature"):
|
|
equipment.Mesh = baseobj
|
|
else:
|
|
equipment.Base = baseobj
|
|
if placement:
|
|
equipment.Placement = placement
|
|
|
|
if FreeCAD.GuiUp and baseobj:
|
|
baseobj.ViewObject.hide()
|
|
return equipment
|
|
|
|
|
|
def makeFence(section, post, path):
|
|
"""
|
|
Creates a fence object in the active document.
|
|
|
|
Parameters
|
|
----------
|
|
section : Part::FeaturePython
|
|
The section profile of the fence.
|
|
post : Part::FeaturePython
|
|
The post profile of the fence.
|
|
path : Part::FeaturePython
|
|
The path along which the fence is created.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created fence object.
|
|
"""
|
|
fence = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_Fence",
|
|
internalName="Fence",
|
|
defaultLabel=translate("Arch", "Fence"),
|
|
)
|
|
fence.Section = section
|
|
fence.Post = post
|
|
fence.Path = path
|
|
if FreeCAD.GuiUp:
|
|
import ArchFence
|
|
ArchFence.hide(section)
|
|
ArchFence.hide(post)
|
|
ArchFence.hide(path)
|
|
return fence
|
|
|
|
|
|
def makeFrame(baseobj, profile, name=None):
|
|
"""Creates a frame object from a base sketch (or any other object containing wires) and a
|
|
profile object (an extrudable 2D object containing faces or closed wires).
|
|
|
|
Parameters
|
|
----------
|
|
baseobj : Part::FeaturePython
|
|
The base object containing wires to define the frame.
|
|
profile : Part::FeaturePython
|
|
The profile object, an extrudable 2D object containing faces or closed wires.
|
|
name : str, optional
|
|
The name to assign to the created frame. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created frame object.
|
|
"""
|
|
frame = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_Frame",
|
|
internalName="Frame",
|
|
defaultLabel=name if name else translate("Arch", "Frame"),
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if baseobj:
|
|
frame.Base = baseobj
|
|
if profile:
|
|
frame.Profile = profile
|
|
if FreeCAD.GuiUp:
|
|
profile.ViewObject.hide()
|
|
|
|
return frame
|
|
|
|
|
|
def makeGrid(name=None):
|
|
"""
|
|
Creates a grid object in the active document.
|
|
|
|
Parameters
|
|
----------
|
|
name : str, optional
|
|
The name to assign to the created grid. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created grid object.
|
|
"""
|
|
grid = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="ArchGrid",
|
|
internalName="Grid",
|
|
defaultLabel=name if name else translate("Arch", "Grid"),
|
|
moduleName="ArchGrid",
|
|
viewProviderName="ViewProviderArchGrid",
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if FreeCAD.GuiUp:
|
|
grid.ViewObject.Transparency = 85
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
return grid
|
|
|
|
|
|
def makeMaterial(name=None, color=None, transparency=None):
|
|
"""
|
|
Creates a material object in the active document.
|
|
|
|
Parameters
|
|
----------
|
|
name : str, optional
|
|
The name to assign to the created material. Defaults to None.
|
|
color : tuple of float, optional
|
|
The RGB color of the material. Defaults to None.
|
|
transparency : float, optional
|
|
The transparency level of the material. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
App::MaterialObjectPython
|
|
The created material object.
|
|
"""
|
|
material = _initializeArchObject(
|
|
"App::MaterialObjectPython",
|
|
baseClassName="_ArchMaterial",
|
|
internalName="Material",
|
|
defaultLabel=name if name else translate("Arch", "Material"),
|
|
)
|
|
getMaterialContainer().addObject(material)
|
|
|
|
# Initialize all relevant properties
|
|
if color:
|
|
r, g, b = color[:3]
|
|
material.Color = (r, g, b)
|
|
if len(color) > 3:
|
|
alpha = color[3]
|
|
material.Transparency = alpha * 100
|
|
if transparency:
|
|
material.Transparency = transparency
|
|
|
|
return material
|
|
|
|
|
|
def makeMultiMaterial(name=None):
|
|
"""
|
|
Creates a multi-material object in the active document.
|
|
|
|
Parameters
|
|
----------
|
|
name : str, optional
|
|
The name to assign to the created multi-material. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
App::FeaturePython
|
|
The created multi-material object.
|
|
"""
|
|
multimaterial = _initializeArchObject(
|
|
"App::FeaturePython",
|
|
baseClassName="_ArchMultiMaterial",
|
|
internalName="MultiMaterial",
|
|
defaultLabel=name if name else translate("Arch", "MultiMaterial"),
|
|
moduleName="ArchMaterial",
|
|
)
|
|
getMaterialContainer().addObject(multimaterial)
|
|
|
|
return multimaterial
|
|
|
|
|
|
def getMaterialContainer():
|
|
"""
|
|
Returns a group object to store materials in the active document.
|
|
|
|
Returns
|
|
-------
|
|
App::DocumentObjectGroupPython
|
|
The material container object.
|
|
"""
|
|
# Check if a container already exists
|
|
for obj in FreeCAD.ActiveDocument.Objects:
|
|
if obj.Name == "MaterialContainer":
|
|
return obj
|
|
|
|
# If no container exists, create one
|
|
materialContainer = _initializeArchObject(
|
|
"App::DocumentObjectGroupPython",
|
|
baseClassName="_ArchMaterialContainer",
|
|
internalName="MaterialContainer",
|
|
defaultLabel=translate("Arch", "Materials"),
|
|
moduleName="ArchMaterial",
|
|
)
|
|
|
|
return materialContainer
|
|
|
|
|
|
def getDocumentMaterials():
|
|
"""
|
|
Retrieves all material objects in the active document.
|
|
|
|
Returns
|
|
-------
|
|
list of App::MaterialObjectPython
|
|
A list of all material objects in the document.
|
|
"""
|
|
for obj in FreeCAD.ActiveDocument.Objects:
|
|
if obj.Name == "MaterialContainer":
|
|
materials = []
|
|
for o in obj.Group:
|
|
if o.isDerivedFrom("App::MaterialObjectPython"):
|
|
materials.append(o)
|
|
return materials
|
|
return []
|
|
|
|
|
|
def makePanel(baseobj=None, length=0, width=0, thickness=0, placement=None, name=None):
|
|
"""
|
|
Creates a panel element based on the given profile object and the given
|
|
extrusion thickness. If no base object is given, you can also specify
|
|
length and width for a simple cubic object.
|
|
|
|
Parameters
|
|
----------
|
|
baseobj : Part::FeaturePython, optional
|
|
The base profile object for the panel. Defaults to None.
|
|
length : float, optional
|
|
The length of the panel. Defaults to 0.
|
|
width : float, optional
|
|
The width of the panel. Defaults to 0.
|
|
thickness : float, optional
|
|
The thickness of the panel. Defaults to 0.
|
|
placement : FreeCAD.Placement, optional
|
|
The placement of the panel. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created panel. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created panel object.
|
|
"""
|
|
panel = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_Panel",
|
|
internalName="Panel",
|
|
defaultLabel=name if name else translate("Arch", "Panel"),
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if baseobj:
|
|
panel.Base = baseobj
|
|
if FreeCAD.GuiUp:
|
|
panel.Base.ViewObject.hide()
|
|
if width:
|
|
panel.Width = width
|
|
if thickness:
|
|
panel.Thickness = thickness
|
|
if length:
|
|
panel.Length = length
|
|
|
|
return panel
|
|
|
|
|
|
def makePanelCut(panel, name=None):
|
|
"""
|
|
Creates a 2D view of the given panel in the 3D space, positioned at the origin.
|
|
|
|
Parameters
|
|
----------
|
|
panel : Part::FeaturePython
|
|
The panel object to create a 2D view for.
|
|
name : str, optional
|
|
The name to assign to the created panel cut. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created panel cut object.
|
|
"""
|
|
view = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="PanelCut",
|
|
internalName="PanelCut",
|
|
defaultLabel=name if name else translate("Arch", f"View of {panel.Label}"),
|
|
moduleName="ArchPanel",
|
|
viewProviderName="ViewProviderPanelCut",
|
|
)
|
|
view.Source = panel
|
|
return view
|
|
|
|
|
|
def makePanelSheet(panels=[], name=None):
|
|
"""
|
|
Creates a sheet with the given panel cuts in the 3D space, positioned at the origin.
|
|
|
|
Parameters
|
|
----------
|
|
panels : list of Part::FeaturePython, optional
|
|
A list of panel cuts to include in the sheet. Defaults to an empty list.
|
|
name : str, optional
|
|
The name to assign to the created panel sheet. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created panel sheet object.
|
|
"""
|
|
sheet = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="PanelSheet",
|
|
internalName="PanelSheet",
|
|
defaultLabel=name if name else translate("Arch", "PanelSheet"),
|
|
moduleName="ArchPanel",
|
|
viewProviderName="ViewProviderPanelSheet",
|
|
)
|
|
if panels:
|
|
sheet.Group = panels
|
|
return sheet
|
|
|
|
|
|
def makePipe(baseobj=None, diameter=0, length=0, placement=None, name=None):
|
|
"""
|
|
Creates a pipe object from the given base object or specified dimensions.
|
|
|
|
Parameters
|
|
----------
|
|
baseobj : Part::FeaturePython, optional
|
|
The base object for the pipe. Defaults to None.
|
|
diameter : float, optional
|
|
The diameter of the pipe. Defaults to 0.
|
|
length : float, optional
|
|
The length of the pipe. Defaults to 0.
|
|
placement : FreeCAD.Placement, optional
|
|
The placement of the pipe. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created pipe. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created pipe object.
|
|
"""
|
|
pipe = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_ArchPipe",
|
|
internalName="Pipe",
|
|
defaultLabel=name if name else translate("Arch", "Pipe"),
|
|
viewProviderName="_ViewProviderPipe",
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
pipe.Diameter = diameter if diameter else params.get_param_arch("PipeDiameter")
|
|
pipe.Width = pipe.Diameter
|
|
pipe.Height = pipe.Diameter
|
|
|
|
if baseobj:
|
|
pipe.Base = baseobj
|
|
else:
|
|
pipe.Length = length if length else 1000
|
|
|
|
if placement:
|
|
pipe.Placement = placement
|
|
|
|
if FreeCAD.GuiUp:
|
|
if baseobj:
|
|
baseobj.ViewObject.hide()
|
|
|
|
return pipe
|
|
|
|
|
|
def makePipeConnector(pipes, radius=0, name=None):
|
|
"""
|
|
Creates a connector between the given pipes.
|
|
|
|
Parameters
|
|
----------
|
|
pipes : list of Part::FeaturePython
|
|
A list of pipe objects to connect.
|
|
radius : float, optional
|
|
The curvature radius of the connector. Defaults to 0, which uses the diameter of the first
|
|
pipe.
|
|
name : str, optional
|
|
The name to assign to the created connector. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created pipe connector object.
|
|
"""
|
|
pipeConnector = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_ArchPipeConnector",
|
|
internalName="Connector",
|
|
defaultLabel=name if name else translate("Arch", "Connector"),
|
|
moduleName="ArchPipe",
|
|
viewProviderName="_ViewProviderPipe",
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
pipeConnector.Pipes = pipes
|
|
if radius:
|
|
pipeConnector.Radius = radius
|
|
elif pipes[0].ProfileType == "Circle":
|
|
pipeConnector.Radius = pipes[0].Diameter
|
|
else:
|
|
pipeConnector.Radius = max(pipes[0].Height, pipes[0].Width)
|
|
|
|
return pipeConnector
|
|
|
|
|
|
def makeProfile(profile=[0, 'REC', 'REC100x100', 'R', 100, 100]):
|
|
"""
|
|
Creates a profile object based on the given profile data.
|
|
|
|
Parameters
|
|
----------
|
|
profile : list, optional
|
|
A list defining the profile data. Defaults to [0, 'REC', 'REC100x100', 'R', 100, 100].
|
|
The list should contain the following elements:
|
|
|
|
0. listOrder: str
|
|
The order of the profile data. Currently not used.
|
|
1. profileSubClass: str
|
|
The subclass of a given profile class (e.g. 'REC' for the 'C' class).
|
|
2. profileName: str
|
|
The name of the profile (e.g., 'REC100x100').
|
|
3. profileClass: str
|
|
The class of the profile (e.g., 'REC', 'C', 'H', etc.).
|
|
4. dimensionsList: int
|
|
A variable set of arguments that define the dimensions of the profile. Their
|
|
interpretation and count depends on the type of profile. Not implemented
|
|
as a list, it's a variable number of arguments within the main profile
|
|
argument. For instance, a C profile will define outside diameter and thickness,
|
|
whereas a H profile will define width, height, web thickness, and flange thickness.
|
|
See https://wiki.freecad.org/Arch_Profile for more details on profile presets.
|
|
|
|
Returns
|
|
-------
|
|
Part::Part2DObjectPython
|
|
The created profile object.
|
|
"""
|
|
import ArchProfile
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython", "Profile")
|
|
|
|
profileName, profileClass = profile[2:4]
|
|
|
|
match profileClass:
|
|
case "C":
|
|
ArchProfile._ProfileC(obj, profile)
|
|
case "H":
|
|
ArchProfile._ProfileH(obj, profile)
|
|
case "R":
|
|
ArchProfile._ProfileR(obj, profile)
|
|
case "RH":
|
|
ArchProfile._ProfileRH(obj, profile)
|
|
case "U":
|
|
ArchProfile._ProfileU(obj, profile)
|
|
case "L":
|
|
ArchProfile._ProfileL(obj, profile)
|
|
case "T":
|
|
ArchProfile._ProfileT(obj, profile)
|
|
case "TSLOT":
|
|
ArchProfile._ProfileTSLOT(obj, profile)
|
|
case _:
|
|
print("Profile not supported")
|
|
|
|
if FreeCAD.GuiUp:
|
|
ArchProfile.ViewProviderProfile(obj.ViewObject)
|
|
|
|
# Initialize all relevant properties
|
|
obj.Label = profileName + "_"
|
|
|
|
return obj
|
|
|
|
|
|
def makeProject(sites=None, name=None):
|
|
"""Create an Arch project.
|
|
|
|
If sites are provided, add them as children of the new project.
|
|
|
|
.. deprecated:: 1.0.0
|
|
|
|
Parameters
|
|
----------
|
|
sites: list of <Part::FeaturePython>, optional
|
|
Sites to add as children of the project. Ultimately this could be
|
|
anything, however.
|
|
name: str, optional
|
|
The label for the project.
|
|
|
|
Returns
|
|
-------
|
|
<Part::FeaturePython>
|
|
The created project.
|
|
|
|
Notes
|
|
-----
|
|
This function is deprecated and will be removed in a future version.
|
|
The NativeIFC project is the new way to create IFC projects.
|
|
"""
|
|
project = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_Project",
|
|
internalName="Project",
|
|
defaultLabel=name if name else translate("Arch", "Project"),
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if sites:
|
|
project.Group = sites
|
|
|
|
return project
|
|
|
|
def makeRebar(
|
|
baseobj: Optional[FreeCAD.DocumentObject] = None,
|
|
sketch: Optional[FreeCAD.DocumentObject] = None,
|
|
diameter: Optional[float] = None,
|
|
amount: int = 1,
|
|
offset: Optional[float] = None,
|
|
name: Optional[str] = None
|
|
) -> Optional[FreeCAD.DocumentObject]:
|
|
"""
|
|
Creates a reinforcement bar (rebar) object.
|
|
|
|
The rebar's geometry is typically defined by a `sketch` object (e.g., a Sketcher::SketchObject
|
|
or a Draft.Wire). This sketch represents the path of a single bar. The `amount` and `spacing`
|
|
(calculated by the object) properties then determine how many such bars are created and
|
|
distributed.
|
|
|
|
The `baseobj` usually acts as the structural host for the rebar. The rebar's distribution (e.g.,
|
|
spacing, direction) can be calculated relative to this host object's dimensions if a `Host` is
|
|
assigned and the rebar logic uses it.
|
|
|
|
Parameters
|
|
----------
|
|
baseobj : FreeCAD.DocumentObject, optional
|
|
The structural object to host the rebar (e.g., an ArchStructure._Structure created with
|
|
`Arch.makeStructure()`). If provided with `sketch`, it's set as `rebar.Host`. If provided
|
|
*without* a `sketch`, `rebar.Shape` is set from `baseobj.Shape`, and `rebar.Host` remains
|
|
None. Defaults to None.
|
|
sketch : FreeCAD.DocumentObject, optional
|
|
An object (e.g., "Sketcher::SketchObject") whose shape defines the rebar's path. Assigned to
|
|
`rebar.Base`. If the sketch is attached to `baseobj` before calling this function (e.g. for
|
|
positioning purposes), this function may clear that specific attachment to avoid conflicts,
|
|
as the rebar itself will be hosted. Defaults to None.
|
|
diameter : float, optional
|
|
The diameter of the rebar. If None, uses Arch preferences ("RebarDiameter"). Defaults to
|
|
None.
|
|
amount : int, optional
|
|
The number of rebar instances. Defaults to 1.
|
|
offset : float, optional
|
|
Concrete cover distance, sets `rebar.OffsetStart` and `rebar.OffsetEnd`. If None, uses Arch
|
|
preferences ("RebarOffset"). Defaults to None.
|
|
name : str, optional
|
|
The user-visible name (Label) for the rebar. If None, defaults to "Rebar". Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
FreeCAD.DocumentObject or None
|
|
The created rebar object, or None if creation fails.
|
|
|
|
Examples
|
|
--------
|
|
>>> import FreeCAD, Arch, Part, Sketcher
|
|
>>> doc = FreeCAD.newDocument()
|
|
>>> # Create a host structure (e.g., a concrete beam)
|
|
>>> beam = Arch.makeStructure(length=2000, width=200, height=300)
|
|
>>> doc.recompute() # Ensure beam's shape is ready
|
|
>>>
|
|
>>> # Create a sketch for the rebar path
|
|
>>> rebar_sketch = doc.addObject('Sketcher::SketchObject')
|
|
>>> # For positioning, attach the sketch to a face of the beam *before* makeRebar
|
|
>>> # Programmatically select a face (e.g., the first one)
|
|
>>> # For stable scripts, select faces by more reliable means
|
|
>>> rebar_sketch.AttachmentSupport = (beam, ['Face1']) # Faces are 1-indexed
|
|
>>> rebar_sketch.MapMode = "FlatFace"
|
|
>>> # Define sketch geometry relative to the attached face's plane
|
|
>>> rebar_sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(25, 25, 0),
|
|
... FreeCAD.Vector(1975, 25, 0)), False)
|
|
>>> doc.recompute() # Recompute sketch after geometry and attachment
|
|
>>>
|
|
>>> # Create the rebar object, linking it to the beam and using the sketch
|
|
>>> rebar_obj = Arch.makeRebar(baseobj=beam, sketch=rebar_sketch, diameter=12,
|
|
... amount=4, offset=25)
|
|
>>> doc.recompute() # Trigger rebar's geometry calculation
|
|
"""
|
|
rebar = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_Rebar",
|
|
internalName="Rebar",
|
|
defaultLabel=name if name else translate("Arch", "Rebar"),
|
|
moduleName="ArchRebar",
|
|
viewProviderName="_ViewProviderRebar",
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if baseobj and sketch:
|
|
# Case 1: both the structural element (base object) and a sketch defining the shape and path
|
|
# of a single rebar strand are provided. This is the most common scenario.
|
|
if hasattr(sketch, "AttachmentSupport"):
|
|
if sketch.AttachmentSupport:
|
|
# If the sketch is already attached to the base object, remove that attachment.
|
|
# Support two AttachmentSupport (PropertyLinkList) formats:
|
|
# 1. Tuple: (baseobj, subelement)
|
|
# 2. Direct object: baseobj
|
|
# TODO: why is the list format not checked for here?
|
|
# ~ 3. List: [baseobj, subelement] ~
|
|
if isinstance(sketch.AttachmentSupport, tuple):
|
|
if sketch.AttachmentSupport[0] == baseobj:
|
|
sketch.AttachmentSupport = None
|
|
elif sketch.AttachmentSupport == baseobj:
|
|
sketch.AttachmentSupport = None
|
|
rebar.Base = sketch
|
|
if FreeCAD.GuiUp:
|
|
sketch.ViewObject.hide()
|
|
rebar.Host = baseobj
|
|
elif not baseobj and sketch:
|
|
# Case 2: standalone rebar strand defined by a sketch, not attached to any structural
|
|
# element.
|
|
rebar.Base = sketch
|
|
if FreeCAD.GuiUp:
|
|
sketch.ViewObject.hide()
|
|
rebar.Host = None
|
|
elif baseobj and not sketch:
|
|
# Case 3: rebar strand defined by the shape of a structural element (base object). The
|
|
# base object becomes the rebar.
|
|
rebar.Shape = baseobj.Shape
|
|
rebar.Diameter = diameter if diameter else params.get_param_arch("RebarDiameter")
|
|
rebar.Amount = amount
|
|
rebar.Document.recompute()
|
|
if offset is not None:
|
|
rebar.OffsetStart = offset
|
|
rebar.OffsetEnd = offset
|
|
else:
|
|
rebar.OffsetStart = params.get_param_arch("RebarOffset")
|
|
rebar.OffsetEnd = params.get_param_arch("RebarOffset")
|
|
rebar.Mark = rebar.Label
|
|
|
|
return rebar
|
|
|
|
|
|
def makeReference(filepath=None, partname=None, name=None):
|
|
"""
|
|
Creates an Arch reference object.
|
|
|
|
Parameters
|
|
----------
|
|
filepath : str, optional
|
|
The file path of the external reference. Defaults to None.
|
|
partname : str, optional
|
|
The name of the part in the external file. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created reference. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created reference object.
|
|
"""
|
|
reference = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="ArchReference",
|
|
internalName="ArchReference",
|
|
defaultLabel=name if name else translate("Arch", "External Reference"),
|
|
moduleName="ArchReference",
|
|
viewProviderName="ViewProviderArchReference",
|
|
)
|
|
|
|
if filepath:
|
|
reference.File = filepath
|
|
if partname:
|
|
reference.Part = partname
|
|
|
|
import Draft
|
|
Draft.select(reference)
|
|
|
|
return reference
|
|
|
|
|
|
def makeRoof(baseobj=None,
|
|
facenr=0,
|
|
angles=[45.0],
|
|
run=[250.0],
|
|
idrel=[-1],
|
|
thickness=[50.0],
|
|
overhang=[100.0],
|
|
name=None):
|
|
"""
|
|
Creates a roof object based on a closed wire or an object.
|
|
|
|
Parameters
|
|
----------
|
|
baseobj : Part::FeaturePython, optional
|
|
The base object for the roof. Defaults to None.
|
|
facenr : int, optional
|
|
The face number to use as the base. Defaults to 0.
|
|
angles : list of float, optional
|
|
The angles for each edge of the roof. Defaults to [45.0].
|
|
run : list of float, optional
|
|
The run distances for each edge. Defaults to [250.0].
|
|
idrel : list of int, optional
|
|
The relative IDs for each edge. Defaults to [-1].
|
|
thickness : list of float, optional
|
|
The thickness of the roof for each edge. Defaults to [50.0].
|
|
overhang : list of float, optional
|
|
The overhang distances for each edge. Defaults to [100.0].
|
|
name : str, optional
|
|
The name to assign to the created roof. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created roof object.
|
|
|
|
Notes
|
|
-----
|
|
1. If the base object is a solid the roof uses its shape.
|
|
2. The angles, run, idrel, thickness, and overhang lists are automatically
|
|
completed to match the number of edges in the wire.
|
|
"""
|
|
import Part
|
|
import ArchRoof
|
|
|
|
baseWire = None
|
|
|
|
roof = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_Roof",
|
|
internalName="Roof",
|
|
defaultLabel=name if name else translate("Arch", "Roof"),
|
|
moduleName="ArchRoof",
|
|
viewProviderName="_ViewProviderRoof",
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if baseobj:
|
|
roof.Base = baseobj
|
|
if hasattr(roof.Base, "Shape"):
|
|
if roof.Base.Shape.Solids:
|
|
if FreeCAD.GuiUp:
|
|
roof.Base.ViewObject.hide()
|
|
else:
|
|
if (roof.Base.Shape.Faces and roof.Face):
|
|
baseWire = roof.Base.Shape.Faces[roof.Face - 1].Wires[0]
|
|
if FreeCAD.GuiUp:
|
|
roof.Base.ViewObject.hide()
|
|
elif roof.Base.Shape.Wires:
|
|
baseWire = roof.Base.Shape.Wires[0]
|
|
if FreeCAD.GuiUp:
|
|
roof.Base.ViewObject.hide()
|
|
if baseWire:
|
|
if baseWire.isClosed():
|
|
if FreeCAD.GuiUp:
|
|
roof.Base.ViewObject.hide()
|
|
edges = Part.__sortEdges__(baseWire.Edges)
|
|
ln = len(edges)
|
|
roof.Angles = ArchRoof.adjust_list_len(angles, ln, angles[0])
|
|
roof.Runs = ArchRoof.adjust_list_len(run, ln, run[0])
|
|
roof.IdRel = ArchRoof.adjust_list_len(idrel, ln, idrel[0])
|
|
roof.Thickness = ArchRoof.adjust_list_len(thickness, ln, thickness[0])
|
|
roof.Overhang = ArchRoof.adjust_list_len(overhang, ln, overhang[0])
|
|
|
|
roof.Face = facenr
|
|
|
|
return roof
|
|
|
|
|
|
def makeSchedule():
|
|
"""
|
|
Creates a schedule object in the active document.
|
|
|
|
Returns
|
|
-------
|
|
App::FeaturePython
|
|
The created schedule object.
|
|
"""
|
|
schedule = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
internalName="Schedule",
|
|
baseClassName="_ArchSchedule",
|
|
defaultLabel=translate("Arch", "Schedule"),
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if hasattr(schedule, "CreateSpreadsheet") and schedule.CreateSpreadsheet:
|
|
schedule.Proxy.getSpreadSheet(schedule, force=True)
|
|
|
|
return schedule
|
|
|
|
|
|
def makeSectionPlane(objectslist=None, name=None):
|
|
"""
|
|
Creates a section plane object including the given objects.
|
|
|
|
Parameters
|
|
----------
|
|
objectslist : list of Part::FeaturePython, optional
|
|
A list of objects to include in the section plane. If no object is given, the whole
|
|
document will be considered. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created section plane. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
App::FeaturePython
|
|
The created section plane object.
|
|
"""
|
|
import Draft
|
|
from WorkingPlane import get_working_plane
|
|
|
|
sectionPlane = _initializeArchObject(
|
|
"App::FeaturePython",
|
|
baseClassName="_SectionPlane",
|
|
internalName="Section",
|
|
defaultLabel=name if name else translate("Arch", "Section"),
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if objectslist:
|
|
sectionPlane.Objects = objectslist
|
|
boundBox = FreeCAD.BoundBox()
|
|
for obj in Draft.get_group_contents(objectslist):
|
|
if hasattr(obj, "Shape") and hasattr(obj.Shape, "BoundBox"):
|
|
boundBox.add(obj.Shape.BoundBox)
|
|
sectionPlane.Placement = get_working_plane().get_placement()
|
|
sectionPlane.Placement.Base = boundBox.Center
|
|
if FreeCAD.GuiUp:
|
|
margin = boundBox.XLength * 0.1
|
|
sectionPlane.ViewObject.DisplayLength = boundBox.XLength + margin
|
|
sectionPlane.ViewObject.DisplayHeight = boundBox.YLength + margin
|
|
return sectionPlane
|
|
|
|
|
|
def makeSite(objectslist=None, baseobj=None, name=None):
|
|
"""
|
|
Creates a site object including the given objects.
|
|
|
|
Parameters
|
|
----------
|
|
objectslist : list of Part::FeaturePython, optional
|
|
A list of objects to include in the site. Defaults to None.
|
|
baseobj : Part::FeaturePython, optional
|
|
The base object for the site. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created site. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created site object.
|
|
"""
|
|
site = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_Site",
|
|
internalName="Site",
|
|
defaultLabel=name if name else translate("Arch", "Site"),
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if objectslist:
|
|
site.Group = objectslist
|
|
if baseobj:
|
|
import Part
|
|
if isinstance(baseobj, Part.Shape):
|
|
site.Shape = baseobj
|
|
else:
|
|
site.Terrain = baseobj
|
|
|
|
return site
|
|
|
|
|
|
def makeSpace(objects=None, baseobj=None, name=None):
|
|
"""Creates a space object from the given objects.
|
|
|
|
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".
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created space object.
|
|
|
|
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 the 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"))]
|
|
"""
|
|
space = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_Space",
|
|
internalName="Space",
|
|
defaultLabel=name if name else translate("Arch", "Space"),
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if baseobj:
|
|
objects = baseobj
|
|
if objects:
|
|
if not isinstance(objects, list):
|
|
objects = [objects]
|
|
|
|
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::Feature>, ["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::Feature>, ["Face1", ...]), ... ]
|
|
pass
|
|
else:
|
|
# Single object: assume anything else passed is a single object with no
|
|
# boundaries.
|
|
# [ <Part::Feature> ]
|
|
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 addSpaceBoundaries(space, subobjects):
|
|
"""Adds the given subobjects as defining boundaries of the given space.
|
|
|
|
Parameters
|
|
----------
|
|
space : ArchSpace._Space
|
|
Arch space object to add the boundaries to.
|
|
subobjects : List(<SelectionObject>) or App::PropertyLinkSubList
|
|
List of boundaries to add to the space.
|
|
|
|
Notes
|
|
-----
|
|
The subobjects parameter can be passed using either of these different formats:
|
|
|
|
1. 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 boundaries to add to the space.::
|
|
subobjects = [<SelectionObject>, ...]
|
|
2. 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 the boundaries
|
|
to add.::
|
|
subobjects = [(obj1, ("Face1")), (obj2, ("Face1")), ...]
|
|
subobjects = [(obj, ("Face1", "Face2", "Face3", "Face4"))]
|
|
"""
|
|
import Draft
|
|
if Draft.getType(space) == "Space":
|
|
space.Proxy.addSubobjects(space, subobjects)
|
|
|
|
def removeSpaceBoundaries(space, subobjects):
|
|
"""Remove the given subobjects as defining boundaries of the given space.
|
|
|
|
Parameters
|
|
----------
|
|
space : ArchSpace._Space
|
|
Arch space object to remove the boundaries from.
|
|
subobjects : List(<SelectionObject>) or App::PropertyLinkSubList
|
|
List of boundaries to remove from the space.
|
|
|
|
Notes
|
|
-----
|
|
The subobjects parameter can be passed using either of these different formats:
|
|
|
|
1. 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 boundaries to remove from the space.::
|
|
subobjects = [<SelectionObject>, ...]
|
|
2. 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 the boundaries
|
|
to remove.::
|
|
subobjects = [(obj1, ("Face1")), (obj2, ("Face1")), ...]
|
|
subobjects = [(obj, ("Face1", "Face2", "Face3", "Face4"))]
|
|
"""
|
|
import Draft
|
|
if Draft.getType(space) == "Space":
|
|
space.Proxy.removeSubobjects(space, subobjects)
|
|
|
|
def makeStairs(baseobj=None, length=None, width=None, height=None, steps=None, name=None):
|
|
"""
|
|
Creates a stairs object with the given attributes.
|
|
|
|
Parameters
|
|
----------
|
|
baseobj : Part::FeaturePython, optional
|
|
The base object for the stairs. Defaults to None.
|
|
length : float, optional
|
|
The length of the stairs. Defaults to None.
|
|
width : float, optional
|
|
The width of the stairs. Defaults to None.
|
|
height : float, optional
|
|
The height of the stairs. Defaults to None.
|
|
steps : int, optional
|
|
The number of steps. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created stairs. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created stairs object.
|
|
"""
|
|
import ArchStairs
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
|
|
stairs = []
|
|
additions = []
|
|
label = name if name else translate("Arch", "Stairs")
|
|
|
|
def setProperty(obj, length, width, height, steps):
|
|
"""setProperty(obj,length,width,height,steps): sets up the basic properties for this stair
|
|
"""
|
|
obj.Length = length if length else params.get_param_arch("StairsLength")
|
|
obj.Width = width if width else params.get_param_arch("StairsWidth")
|
|
obj.Height = height if height else params.get_param_arch("StairsHeight")
|
|
obj.Structure = "Massive"
|
|
obj.StructureThickness = 150
|
|
obj.DownSlabThickness = 150
|
|
obj.UpSlabThickness = 150
|
|
if steps:
|
|
obj.NumberOfSteps = steps
|
|
|
|
obj.RailingOffsetLeft = 60
|
|
obj.RailingOffsetRight = 60
|
|
obj.RailingHeightLeft = 900
|
|
obj.RailingHeightRight = 900
|
|
|
|
if baseobj:
|
|
if not isinstance(baseobj, list):
|
|
baseobj = [baseobj]
|
|
lenSelection = len(baseobj)
|
|
if lenSelection > 1:
|
|
stair = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Stairs")
|
|
stair.Label = label
|
|
ArchStairs._Stairs(stair)
|
|
stairs.append(stair)
|
|
i = 1
|
|
else:
|
|
i = 0
|
|
|
|
for baseobjI in baseobj:
|
|
stair = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Stairs")
|
|
stair.Label = label
|
|
ArchStairs._Stairs(stair)
|
|
stairs.append(stair)
|
|
stairs[i].Base = baseobjI
|
|
if steps:
|
|
stepsI = steps
|
|
else:
|
|
stepsI = 20
|
|
setProperty(stairs[i], None, width, height, stepsI)
|
|
|
|
if lenSelection > 1: # More than 1 segment
|
|
# All semgments in a complex stairs moved together by default
|
|
# regardless MoveWithHost setting in system setting
|
|
stair.MoveWithHost = True
|
|
|
|
# All segment goes to Additions (rather than previously 1st segment
|
|
# went to Base) - for consistent in MoveWithHost behaviour
|
|
if i > 0:
|
|
additions.append(stairs[i])
|
|
if i > 1:
|
|
stairs[i].LastSegment = stairs[i - 1]
|
|
#else:
|
|
# Below made '1st segment' of a complex stairs went to Base
|
|
# Remarked below out, 2025.8.31.
|
|
# Seems no other Arch object create an Arch object as its Base
|
|
# and use a 'master' Arch(Stairs) object like Stairs. Base is
|
|
# not moved together with host upon onChanged(), unlike
|
|
# behaviour in objects of Additions.
|
|
#
|
|
#if len(stairs) > 1: # i.e. length >1, have a 'master' staircase created
|
|
# stairs[0].Base = stairs[1]
|
|
|
|
i += 1
|
|
if lenSelection > 1:
|
|
stairs[0].Additions = additions
|
|
else:
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Stairs")
|
|
obj.Label = label
|
|
ArchStairs._Stairs(obj)
|
|
setProperty(obj, length, width, height, steps)
|
|
stairs.append(obj)
|
|
if FreeCAD.GuiUp:
|
|
if baseobj:
|
|
for stair in stairs:
|
|
ArchStairs._ViewProviderStairs(stair.ViewObject)
|
|
for bo in baseobj:
|
|
bo.ViewObject.hide()
|
|
else:
|
|
ArchStairs._ViewProviderStairs(obj.ViewObject)
|
|
if stairs:
|
|
for stair in stairs:
|
|
stair.recompute()
|
|
makeRailing(stairs)
|
|
# return stairs - all other functions expect one object as return value
|
|
return stairs[0]
|
|
else:
|
|
obj.recompute()
|
|
return obj
|
|
|
|
|
|
def makeRailing(stairs):
|
|
"""
|
|
Creates railings for the given stairs.
|
|
|
|
Parameters
|
|
----------
|
|
stairs : list of Part::FeaturePython
|
|
The stairs objects to add railings to.
|
|
|
|
Returns
|
|
-------
|
|
None
|
|
"""
|
|
def makeRailingLorR(stairs, side="L"):
|
|
"""makeRailingLorR(stairs,side="L"): Creates a railing on the given side of the stairs, L or
|
|
R"""
|
|
for stair in reversed(stairs):
|
|
if side == "L":
|
|
outlineLR = stair.OutlineLeft
|
|
outlineLRAll = stair.OutlineLeftAll
|
|
stairRailingLR = "RailingLeft"
|
|
elif side == "R":
|
|
outlineLR = stair.OutlineRight
|
|
outlineLRAll = stair.OutlineRightAll
|
|
stairRailingLR = "RailingRight"
|
|
if outlineLR or outlineLRAll:
|
|
lrRail = makePipe(
|
|
baseobj=None, diameter=0, length=0, placement=None,
|
|
name=translate("Arch", "Railing")
|
|
)
|
|
# All semgments in a complex stairs moved together by default
|
|
# regardless Move With Host setting in system setting
|
|
lrRail.MoveWithHost = True
|
|
if outlineLRAll:
|
|
setattr(stair, stairRailingLR, lrRail)
|
|
break
|
|
elif outlineLR:
|
|
setattr(stair, stairRailingLR, lrRail)
|
|
|
|
if stairs is None:
|
|
sel = FreeCADGui.Selection.getSelection()
|
|
sel0 = sel[0]
|
|
stairs = []
|
|
# TODO currently consider 1st selected object, then would tackle multiple objects?
|
|
if Draft.getType(sel[0]) == "Stairs":
|
|
stairs.append(sel0)
|
|
if Draft.getType(sel0.Base) == "Stairs":
|
|
stairs.append(sel0.Base)
|
|
additions = sel0.Additions
|
|
for additionsI in additions:
|
|
if Draft.getType(additionsI) == "Stairs":
|
|
stairs.append(additionsI)
|
|
else:
|
|
stairs.append(sel[0])
|
|
else:
|
|
print("No Stairs object selected")
|
|
return
|
|
|
|
makeRailingLorR(stairs, "L")
|
|
makeRailingLorR(stairs, "R")
|
|
|
|
|
|
def makeTruss(baseobj=None, name=None):
|
|
"""
|
|
Creates a truss object from the given base object.
|
|
|
|
Parameters
|
|
----------
|
|
baseobj : Part::FeaturePython, optional
|
|
The base object for the truss. Defaults to None.
|
|
name : str, optional
|
|
The name to assign to the created truss. Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The created truss object.
|
|
"""
|
|
truss = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="Truss",
|
|
internalName="Truss",
|
|
defaultLabel=name if name else translate("Arch", "Truss"),
|
|
moduleName="ArchTruss",
|
|
viewProviderName="ViewProviderTruss",
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if baseobj:
|
|
truss.Base = baseobj
|
|
if FreeCAD.GuiUp:
|
|
baseobj.ViewObject.hide()
|
|
|
|
return truss
|
|
|
|
|
|
def makeWall(
|
|
baseobj=None, height=None, length=None, width=None, align=None, offset=None,
|
|
face=None, name=None
|
|
):
|
|
"""Create a wall based on a given object, and returns the generated wall.
|
|
|
|
TODO: It is unclear what defines which units this function uses.
|
|
|
|
Parameters
|
|
----------
|
|
baseobj: <Part::Feature>, optional
|
|
The base object with which to build the wall. This can be a sketch, a
|
|
draft object, a face, or a solid. It can also be left as None.
|
|
height: float, optional
|
|
The height of the wall.
|
|
length: float, optional
|
|
The length of the wall. Not used if the wall is based off an object.
|
|
Will use Arch default if left empty.
|
|
width: float, optional
|
|
The width of the wall. Not used if the base object is a face. Will use
|
|
Arch default if left empty.
|
|
align: str, optional
|
|
Either "Center", "Left", or "Right". Effects the alignment of the wall
|
|
on its baseline.
|
|
face: int, optional
|
|
The index number of a face on the given baseobj, to base the wall on.
|
|
name: str, optional
|
|
The name to give to the created wall.
|
|
|
|
Returns
|
|
-------
|
|
<Part::FeaturePython>
|
|
Returns the generated wall.
|
|
|
|
Notes
|
|
-----
|
|
1. Creates a new <Part::FeaturePython> object, and turns it into a parametric wall
|
|
object. This <Part::FeaturePython> object does not yet have any shape.
|
|
2. The wall then uses the baseobj.Shape as the basis to extrude out a wall shape,
|
|
giving the new <Part::FeaturePython> object a shape.
|
|
3. It then hides the original baseobj.
|
|
"""
|
|
import Draft
|
|
|
|
wall = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_Wall",
|
|
internalName="Wall",
|
|
defaultLabel=name if name else translate("Arch", "Wall"),
|
|
moduleName="ArchWall",
|
|
viewProviderName="_ViewProviderWall",
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if baseobj:
|
|
if hasattr(baseobj, 'Shape') or baseobj.isDerivedFrom("Mesh::Feature"):
|
|
wall.Base = baseobj
|
|
else:
|
|
FreeCAD.Console.PrintWarning(
|
|
str(translate("Arch", "Walls can only be based on Part or Mesh objects"))
|
|
)
|
|
if face:
|
|
wall.Face = face
|
|
if length:
|
|
wall.Length = length
|
|
wall.Width = width if width else params.get_param_arch("WallWidth")
|
|
wall.Height = height if height else params.get_param_arch("WallHeight")
|
|
wall.Align = (
|
|
align if align else ["Center", "Left", "Right"][params.get_param_arch("WallAlignment")]
|
|
)
|
|
|
|
if wall.Base and FreeCAD.GuiUp:
|
|
if Draft.getType(wall.Base) != "Space":
|
|
wall.Base.ViewObject.hide()
|
|
|
|
return wall
|
|
|
|
|
|
def joinWalls(walls, delete=False, deletebase=False):
|
|
"""Join the given list of walls into one sketch-based wall.
|
|
|
|
Take the first wall in the list, and adds on the other walls in the list.
|
|
Return the modified first wall.
|
|
|
|
Setting delete to True, will delete the other walls. Only join walls
|
|
if the walls have the same width, height and alignment.
|
|
|
|
Parameters
|
|
----------
|
|
walls : list of <Part::FeaturePython>
|
|
List containing the walls to add to the first wall in the list. Walls must
|
|
be based off a base object.
|
|
delete : bool, optional
|
|
If True, deletes the other walls in the list. Defaults to False.
|
|
deletebase : bool, optional
|
|
If True, and delete is True, the base of the other walls is also deleted
|
|
Defaults to False.
|
|
|
|
Returns
|
|
-------
|
|
Part::FeaturePython
|
|
The joined wall object.
|
|
"""
|
|
import Part
|
|
import Draft
|
|
import ArchWall
|
|
if not walls:
|
|
return None
|
|
if not isinstance(walls, list):
|
|
walls = [walls]
|
|
if not ArchWall.areSameWallTypes(walls):
|
|
return None
|
|
deleteList = []
|
|
base = walls.pop()
|
|
if base.Base:
|
|
if base.Base.Shape.Faces:
|
|
return None
|
|
# Use ArchSketch if SketchArch add-on is present
|
|
if Draft.getType(base.Base) == "ArchSketch":
|
|
sk = base.Base
|
|
else:
|
|
try:
|
|
import ArchSketchObject
|
|
newSk = ArchSketchObject.makeArchSketch()
|
|
except:
|
|
if Draft.getType(base.Base) != "Sketcher::SketchObject":
|
|
newSk = FreeCAD.ActiveDocument.addObject("Sketcher::SketchObject", "WallTrace")
|
|
else:
|
|
newSk = None
|
|
if newSk:
|
|
sk = Draft.makeSketch(base.Base, autoconstraints=True, addTo=newSk)
|
|
base.Base = sk
|
|
else:
|
|
sk = base.Base
|
|
for w in walls:
|
|
if w.Base and not w.Base.Shape.Faces:
|
|
for hostedObj in w.Proxy.getHosts(w):
|
|
if hasattr(hostedObj, "Host"):
|
|
hostedObj.Host = base
|
|
else:
|
|
tmp = hostedObj.Hosts
|
|
if delete:
|
|
tmp.remove(w)
|
|
if not base in tmp:
|
|
tmp.append(base)
|
|
hostedObj.Hosts = tmp
|
|
tmp = []
|
|
for add in w.Additions:
|
|
if not add in base.Additions:
|
|
tmp.append(add)
|
|
if delete:
|
|
w.Additions = None
|
|
base.Additions += tmp
|
|
tmp = []
|
|
for sub in w.Subtractions:
|
|
if not sub in base.Subtractions:
|
|
tmp.append(sub)
|
|
if delete:
|
|
w.Subtractions = None
|
|
base.Subtractions += tmp
|
|
for e in w.Base.Shape.Edges:
|
|
l = e.Curve
|
|
if isinstance(l, Part.Line):
|
|
l = Part.LineSegment(e.Vertexes[0].Point, e.Vertexes[-1].Point)
|
|
sk.addGeometry(l)
|
|
deleteList.append(w.Name)
|
|
if deletebase:
|
|
deleteList.append(w.Base.Name)
|
|
if delete:
|
|
for n in deleteList:
|
|
FreeCAD.ActiveDocument.removeObject(n)
|
|
FreeCAD.ActiveDocument.recompute()
|
|
if base.Base and FreeCAD.GuiUp:
|
|
base.ViewObject.show()
|
|
return base
|
|
|
|
|
|
def makeWindow(
|
|
baseobj: Optional[FreeCAD.DocumentObject] = None,
|
|
width: Optional[float] = None,
|
|
height: Optional[float] = None,
|
|
parts: Optional[list[str]] = None,
|
|
name: Optional[str] = None,
|
|
) -> FreeCAD.DocumentObject:
|
|
"""
|
|
Creates an Arch Window object, which can represent either a window or a door.
|
|
|
|
The created object can be based on a 2D profile (e.g., a Sketch), have its
|
|
dimensions set directly, or be defined by custom components. It can be
|
|
inserted into host objects like Walls, creating openings. The IfcType of
|
|
the object can be set to "Window" or "Door" accordingly (presets often
|
|
handle this automatically).
|
|
|
|
Parameters
|
|
----------
|
|
baseobj : FreeCAD.DocumentObject, optional
|
|
The base object for the window/door.
|
|
If `baseobj` is an existing `Arch.Window` (or Door), it will be cloned.
|
|
If `baseobj` is a 2D object with wires (e.g., `Sketcher::SketchObject`,
|
|
`Draft.Wire`), these wires are used to define the geometry.
|
|
If `parts` is None, default components are generated from `baseobj.Shape.Wires`:
|
|
- If one closed wire: `["Default", "Frame", "Wire0", "1", "0"]` (or "Solid panel").
|
|
- If multiple closed wires (e.g., Wire0 outer, Wire1 inner):
|
|
`["Default", "Frame", "Wire0,Wire1", "1", "0"]` (Wire1 cuts Wire0).
|
|
The `Normal` direction is derived from `baseobj.Placement`.
|
|
Defaults to None.
|
|
width : float, optional
|
|
The total width of the window/door.
|
|
If `baseobj` is None, this value is used by `ensureBase()` on first
|
|
recompute to create a default sketch with a "Width" constraint.
|
|
If `baseobj` is a sketch with a "Width" named constraint, setting
|
|
`window_or_door.Width` will drive this sketch constraint. `makeWindow` itself
|
|
does not initially set the object's `Width` *from* a sketch's constraint.
|
|
Defaults to None (or an Arch preference value if `baseobj` is None).
|
|
height : float, optional
|
|
The total height of the window/door.
|
|
If `baseobj` is None, this value is used by `ensureBase()` on first
|
|
recompute to create a default sketch with a "Height" constraint.
|
|
If `baseobj` is a sketch with a "Height" named constraint, setting
|
|
`window_or_door.Height` will drive this sketch constraint. `makeWindow` itself
|
|
does not initially set the object's `Height` *from* a sketch's constraint.
|
|
Defaults to None (or an Arch preference value if `baseobj` is None).
|
|
parts : list[str], optional
|
|
A list defining custom components for the window/door. The list is flat, with
|
|
every 5 elements describing one component:
|
|
`["Name1", "Type1", "WiresStr1", "ThickStr1", "OffsetStr1", ...]`
|
|
- `Name`: User-defined name (e.g., "OuterFrame").
|
|
- `Type`: Component type (e.g., "Frame", "Glass panel", "Solid panel").
|
|
See `ArchWindow.WindowPartTypes`.
|
|
- `WiresStr`: Comma-separated string defining wire usage from `baseobj.Shape.Wires`
|
|
(0-indexed) and optionally hinge/opening from `baseobj.Shape.Edges` (1-indexed).
|
|
Example: `"Wire0,Wire1,Edge8,Mode1"`.
|
|
- `"WireN"`: Uses Nth wire for the base face.
|
|
- `"WireN,WireM"`: WireN is base, WireM is cutout.
|
|
- `"EdgeK"`: Kth edge is hinge.
|
|
- `"ModeL"`: Lth opening mode from `ArchWindow.WindowOpeningModes`.
|
|
- `ThickStr`: Thickness as string (e.g., `"50.0"`). Appending `"+V"`
|
|
adds the object's `Frame` property value.
|
|
- `OffsetStr`: Offset along normal as string (e.g., `"25.0"`). Appending `"+V"`
|
|
adds the object's `Offset` property value.
|
|
Defaults to None. If None and `baseobj` is a sketch, default parts
|
|
are generated as described under `baseobj`.
|
|
name : str, optional
|
|
The name (label) for the created window/door. If None, a default localized
|
|
name ("Window" or "Door", depending on context or subsequent changes) is used.
|
|
Defaults to None.
|
|
|
|
Returns
|
|
-------
|
|
FreeCAD.DocumentObject
|
|
The created Arch Window object (which is a `Part::FeaturePython` instance,
|
|
configurable to represent a window or a door).
|
|
|
|
See Also
|
|
--------
|
|
ArchWindowPresets.makeWindowPreset : Create window/door from predefined types.
|
|
ArchWall.addComponents : Add a window/door to a wall (creates opening).
|
|
|
|
Notes
|
|
-----
|
|
- **Dual purpose (window/door)**: despite its name, this function is the primary
|
|
way to programmatically create both windows and doors in the BIM workbench.
|
|
The distinction is often made by setting the `IfcType` property of the
|
|
created object to "Window" or "Door", and by the chosen components or preset.
|
|
- **Sketch-based dimensions**: If `baseobj` is a `Sketcher::SketchObject`
|
|
with named constraints "Width" and "Height", these sketch constraints will be
|
|
parametrically driven by the created object's `Width` and `Height` properties
|
|
respectively *after* the object is created and its properties are changed.
|
|
`makeWindow` itself does not initially populate the object's `Width`/`Height` from
|
|
these sketch constraints if `width`/`height` arguments are not passed to it.
|
|
The object's internal `Width` and `Height` properties are the drivers.
|
|
- **Object from dimensions (No `baseobj` initially)**: if `baseobj` is `None` but
|
|
`width` and `height` are provided, `makeWindow` creates an Arch Window object.
|
|
Upon the first `doc.recompute()`, the `ensureBase()` mechanism generates
|
|
an internal sketch (`obj.Base`) with "Width" and "Height" constraints
|
|
driven by `obj.Width` and `obj.Height`. However, `obj.WindowParts`
|
|
will remain undefined, resulting in a shapeless object until `WindowParts`
|
|
are manually set.
|
|
- **`obj.Frame` and `obj.Offset` properties**: these main properties of the
|
|
created object (e.g., `my_window.Frame = 50.0`) provide the values used when
|
|
`"+V"` is specified in the `ThicknessString` or `OffsetString` of a component
|
|
within the `parts` list.
|
|
- **Hosting and openings**: to create an opening in a host object (e.g., `Arch.Wall`),
|
|
set `obj.Hosts = [my_wall]`. The opening's shape is typically derived
|
|
from `obj.HoleWire` (defaulting to the largest wire of `obj.Base`) and
|
|
extruded by `obj.HoleDepth` (if 0, tries to match host thickness).
|
|
A custom `obj.Subvolume` can also define the opening shape.
|
|
- **Component management**: components and their geometry are primarily
|
|
managed by the `_Window` class and its methods in `ArchWindow.py`.
|
|
- **Initialization from sketch `baseobj`**: when `baseobj` is a sketch
|
|
(e.g., `Sketcher::SketchObject`) and `parts` is `None` or provided:
|
|
- The `window.Shape` (geometric representation) is correctly generated
|
|
at the global position and orientation defined by `baseobj.Placement`.
|
|
- However, the created window object's own `window.Placement` property is
|
|
**not** automatically initialized from `baseobj.Placement` and typically
|
|
remains at the identity placement (origin, no rotation).
|
|
- Similarly, the `window.Width` and `window.Height` properties are **not**
|
|
automatically populated from the dimensions of the `baseobj` sketch.
|
|
These properties will default to 0.0 or values from Arch preferences
|
|
(if `width`/`height` arguments to `makeWindow` are also `None`).
|
|
- If you need the `window` object's `Placement`, `Width`, or `Height`
|
|
properties to reflect the `baseobj` sketch for subsequent operations
|
|
(e.g., if other systems query these specific window properties, or if
|
|
you intend to parametrically drive the sketch via these window properties),
|
|
you may need to set them manually after `makeWindow` is called:
|
|
- The `ArchWindow._Window.execute()` method, when recomputing the window,
|
|
*does* use `window.Base.Shape` (the sketch's shape in its global position)
|
|
to generate the window's geometry. The `ArchWindow._Window.getSubVolume()`
|
|
method also correctly uses `window.Base.Shape` and the window object's
|
|
(identity) `Placement` for creating the cutting volume.
|
|
|
|
Examples
|
|
--------
|
|
>>> import FreeCAD as App
|
|
>>> import Draft, Arch, Sketcher, Part
|
|
>>> doc = App.newDocument("ArchWindowDoorExamples")
|
|
|
|
>>> # Ex1: Basic window from sketch and parts definition, oriented to XZ (vertical) plane
|
|
>>> sketch_ex1 = doc.addObject('Sketcher::SketchObject', 'WindowSketchEx1_Vertical')
|
|
>>> # Define geometry in sketch's local XY plane (width along local X, height along local Y)
|
|
>>> sketch_ex1.addGeometry(Part.LineSegment(App.Vector(0,0,0), App.Vector(1000,0,0))) # Wire0 - Outer
|
|
>>> sketch_ex1.addGeometry(Part.LineSegment(App.Vector(1000,0,0), App.Vector(1000,1200,0)))
|
|
>>> sketch_ex1.addGeometry(Part.LineSegment(App.Vector(1000,1200,0), App.Vector(0,1200,0)))
|
|
>>> sketch_ex1.addGeometry(Part.LineSegment(App.Vector(0,1200,0), App.Vector(0,0,0)))
|
|
>>> sketch_ex1.addGeometry(Part.LineSegment(App.Vector(100,100,0), App.Vector(900,100,0))) # Wire1 - Inner
|
|
>>> sketch_ex1.addGeometry(Part.LineSegment(App.Vector(900,100,0), App.Vector(900,1100,0)))
|
|
>>> sketch_ex1.addGeometry(Part.LineSegment(App.Vector(900,1100,0), App.Vector(100,1100,0)))
|
|
>>> sketch_ex1.addGeometry(Part.LineSegment(App.Vector(100,1100,0), App.Vector(100,100,0)))
|
|
>>> doc.recompute() # Update sketch Wires
|
|
>>> # Orient sketch: Rotate +90 deg around X-axis to place sketch's XY onto global XZ.
|
|
>>> # Sketch's local Y (height) now aligns with global Z. Sketch normal is global -Y.
|
|
>>> sketch_ex1.Placement.Rotation = App.Rotation(App.Vector(1,0,0), 90)
|
|
>>> doc.recompute() # Apply sketch placement
|
|
>>> window_ex1 = Arch.makeWindow(baseobj=sketch_ex1, name="MyWindowEx1_Vertical")
|
|
>>> # Window Normal will be derived as global +Y, extrusion along +Y.
|
|
>>> window_ex1.WindowParts = [
|
|
... "Frame", "Frame", "Wire0,Wire1", "60", "0", # Frame from Wire0-Wire1
|
|
... "Glass", "Glass panel", "Wire1", "10", "25" # Glass from Wire1, offset in Normal dir
|
|
... ]
|
|
>>> doc.recompute()
|
|
|
|
>>> # Ex2: Window from sketch with named "Width"/"Height" constraints (on default XY plane)
|
|
>>> sketch_ex2 = doc.addObject('Sketcher::SketchObject', 'WindowSketchEx2_Named')
|
|
>>> sketch_ex2.addGeometry(Part.LineSegment(App.Vector(0,0,0), App.Vector(800,0,0))) # Edge 0
|
|
>>> sketch_ex2.addGeometry(Part.LineSegment(App.Vector(800,0,0), App.Vector(800,600,0))) # Edge 1
|
|
>>> sketch_ex2.addGeometry(Part.LineSegment(App.Vector(800,600,0), App.Vector(0,600,0))) # Complete Wire0
|
|
>>> sketch_ex2.addGeometry(Part.LineSegment(App.Vector(0,600,0), App.Vector(0,0,0)))
|
|
>>> sketch_ex2.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2, 800))
|
|
>>> sketch_ex2.renameConstraint(sketch_ex2.ConstraintCount-1, "Width")
|
|
>>> sketch_ex2.addConstraint(Sketcher.Constraint('DistanceY',1,1,1,2, 600))
|
|
>>> sketch_ex2.renameConstraint(sketch_ex2.ConstraintCount-1, "Height")
|
|
>>> doc.recompute()
|
|
>>> window_ex2 = Arch.makeWindow(baseobj=sketch_ex2, name="MyWindowEx2_Parametric")
|
|
>>> window_ex2.WindowParts = ["Frame", "Frame", "Wire0", "50", "0"]
|
|
>>> doc.recompute()
|
|
>>> print(f"Ex2 Initial - Sketch Width: {sketch_ex2.getDatum('Width')}, Window Width: {window_ex2.Width.Value}")
|
|
>>> window_ex2.Width = 950 # This drives the sketch constraint
|
|
>>> doc.recompute()
|
|
>>> print(f"Ex2 Updated - Sketch Width: {sketch_ex2.getDatum('Width')}, Window Width: {window_ex2.Width.Value}")
|
|
|
|
>>> # Ex3: Window from dimensions only (initially shapeless, sketch on XY plane)
|
|
>>> window_ex3 = Arch.makeWindow(width=700, height=900, name="MyWindowEx3_Dims")
|
|
>>> print(f"Ex3 Initial - Base: {window_ex3.Base}, Shape isNull: {window_ex3.Shape.isNull()}")
|
|
>>> doc.recompute() # ensureBase creates the sketch on XY plane
|
|
>>> print(f"Ex3 After Recompute - Base: {window_ex3.Base.Name if window_ex3.Base else 'None'}, Shape isNull: {window_ex3.Shape.isNull()}")
|
|
>>> window_ex3.WindowParts = ["SimpleFrame", "Frame", "Wire0", "40", "0"] # Wire0 from auto-generated sketch
|
|
>>> doc.recompute()
|
|
>>> print(f"Ex3 After Parts - Shape isNull: {window_ex3.Shape.isNull()}")
|
|
|
|
>>> # Ex4: Door created using an ArchWindowPresets function
|
|
>>> # Note: Arch.makeWindowPreset calls Arch.makeWindow internally
|
|
>>> door_ex4_preset = makeWindowPreset(
|
|
... "Simple door", width=900, height=2100,
|
|
... h1=50, h2=0, h3=0, w1=70, w2=40, o1=0, o2=0 # Preset-specific params
|
|
... )
|
|
>>> if door_ex4_preset:
|
|
... door_ex4_preset.Label = "MyDoorEx4_Preset"
|
|
... doc.recompute()
|
|
|
|
>>> # Ex5: Door created from a sketch, with IfcType manually set (sketch on XY plane)
|
|
>>> sketch_ex5_door = doc.addObject('Sketcher::SketchObject', 'DoorSketchEx5')
|
|
>>> sketch_ex5_door.addGeometry(Part.LineSegment(App.Vector(0,0,0), App.Vector(850,0,0))) # Wire0
|
|
>>> sketch_ex5_door.addGeometry(Part.LineSegment(App.Vector(850,0,0), App.Vector(850,2050,0)))
|
|
>>> sketch_ex5_door.addGeometry(Part.LineSegment(App.Vector(850,2050,0), App.Vector(0,2050,0)))
|
|
>>> sketch_ex5_door.addGeometry(Part.LineSegment(App.Vector(0,2050,0), App.Vector(0,0,0)))
|
|
>>> doc.recompute()
|
|
>>> door_ex5_manual = Arch.makeWindow(baseobj=sketch_ex5_door, name="MyDoorEx5_Manual")
|
|
>>> door_ex5_manual.WindowParts = ["DoorPanel", "Solid panel", "Wire0", "40", "0"]
|
|
>>> door_ex5_manual.IfcType = "Door" # Explicitly define as a Door
|
|
>>> doc.recompute()
|
|
|
|
>>> # Ex6: Hosting the vertical window from Ex1 in an Arch.Wall
|
|
>>> wall_ex6 = Arch.makeWall(None, length=4000, width=200, height=2400)
|
|
>>> wall_ex6.Label = "WallForOpening_Ex6"
|
|
>>> # Window_ex1 is already oriented (its sketch placement was set in Ex1).
|
|
>>> # Now, just position the window object itself.
|
|
>>> window_ex1.Placement.Base = App.Vector(1500, wall_ex6.Width.Value / 2, 900) # X, Y (center of wall), Z (sill)
|
|
>>> window_ex1.HoleDepth = 0 # Use wall's thickness for the opening depth
|
|
>>> doc.recompute() # Apply window placement and HoleDepth
|
|
>>> window_ex1.Hosts = [wall_ex6]
|
|
>>> doc.recompute() # Wall recomputes to create the opening
|
|
"""
|
|
import Draft
|
|
import DraftGeomUtils
|
|
from draftutils import todo
|
|
|
|
if baseobj and Draft.getType(baseobj) == "Window" and FreeCAD.ActiveDocument:
|
|
window = Draft.clone(baseobj)
|
|
return window
|
|
|
|
window = _initializeArchObject(
|
|
"Part::FeaturePython",
|
|
baseClassName="_Window",
|
|
internalName="Window",
|
|
defaultLabel=name if name else translate("Arch", "Window"),
|
|
moduleName="ArchWindow",
|
|
viewProviderName="_ViewProviderWindow",
|
|
)
|
|
|
|
# Initialize all relevant properties
|
|
if width:
|
|
window.Width = width
|
|
if height:
|
|
window.Height = height
|
|
if baseobj:
|
|
# 2025.5.25
|
|
# Historically, this normal was deduced by the orientation of the Base Sketch and hardcoded
|
|
# in the Normal property. Now with the new AutoNormalReversed property/flag, set True as
|
|
# default, the auto Normal previously in opposite direction to is now consistent with that
|
|
# previously hardcoded. With the normal set to 'auto', window object would not suffer weird
|
|
# shape if the Base Sketch is rotated by some reason. Keep the property be 'auto' (0,0,0)
|
|
# here.
|
|
#obj.Normal = baseobj.Placement.Rotation.multVec(FreeCAD.Vector(0, 0, -1))
|
|
window.Base = baseobj
|
|
if parts is not None:
|
|
window.WindowParts = parts
|
|
else:
|
|
if baseobj:
|
|
linked_obj = baseobj.getLinkedObject(True)
|
|
if (linked_obj.isDerivedFrom("Part::Part2DObject")
|
|
or Draft.getType(linked_obj) in ["BezCurve", "BSpline", "Wire"]) \
|
|
and DraftGeomUtils.isPlanar(baseobj.Shape):
|
|
# "BezCurve", "BSpline" and "Wire" objects created with < v1.1 are
|
|
# "Part::Part2DObject" objects. In all versions these objects need not be planar.
|
|
if baseobj.Shape.Wires:
|
|
part_type = "Frame"
|
|
if len(baseobj.Shape.Wires) == 1:
|
|
part_type = "Solid panel"
|
|
wires = []
|
|
for i, wire in enumerate(baseobj.Shape.Wires):
|
|
if wire.isClosed():
|
|
wires.append(f"Wire{i}")
|
|
wires_str = ",".join(wires)
|
|
part_name = "Default"
|
|
part_frame_thickness = "1" # mm
|
|
part_offset = "0" # mm
|
|
window.WindowParts = [
|
|
part_name, part_type, wires_str, part_frame_thickness, part_offset
|
|
]
|
|
else:
|
|
# Bind properties from base obj if they exist
|
|
for prop in ["Height", "Width", "Subvolume", "Tag", "Description", "Material"]:
|
|
for baseobj_prop in baseobj.PropertiesList:
|
|
if (baseobj_prop == prop) or baseobj_prop.endswith(f"_{prop}"):
|
|
window.setExpression(prop, f"{baseobj.Name}.{baseobj_prop}")
|
|
|
|
if window.Base and FreeCAD.GuiUp:
|
|
from ArchWindow import recolorize
|
|
window.Base.ViewObject.DisplayMode = "Wireframe"
|
|
window.Base.ViewObject.hide()
|
|
todo.ToDo.delay(recolorize, [window.Document.Name, window.Name])
|
|
|
|
return window
|
|
|
|
|
|
def _initializeArchObject(
|
|
objectType,
|
|
baseClassName=None,
|
|
internalName=None,
|
|
defaultLabel=None,
|
|
moduleName=None,
|
|
viewProviderName=None,
|
|
):
|
|
"""
|
|
Initializes a new Arch object in the active document.
|
|
|
|
Parameters
|
|
----------
|
|
objectType : str
|
|
The type of object to create (e.g., "Part::FeaturePython").
|
|
baseClassName : str
|
|
The name of the base class to initialize the object (e.g., "_ArchSchedule").
|
|
internalName : str, optional
|
|
The internal name to assign to the object.
|
|
defaultLabel : str, optional
|
|
The default label to assign to the object if no name is provided.
|
|
moduleName : str, optional
|
|
The name of the module containing the base class and view provider. If not provided,
|
|
it is inferred from baseClassName.
|
|
viewProviderName : str, optional
|
|
The name of the view provider class to initialize the object's view. If not provided,
|
|
it is inferred from baseClassName.
|
|
|
|
Returns
|
|
-------
|
|
App.DocumentObject
|
|
The created object, or None if no active document exists.
|
|
"""
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return None
|
|
|
|
import importlib
|
|
|
|
# Infer moduleName and viewProviderName if not provided
|
|
if not moduleName:
|
|
moduleName = "Arch" + baseClassName.lstrip("_").strip("Arch")
|
|
if not viewProviderName:
|
|
viewProviderName = "_ViewProvider" + baseClassName.lstrip("_")
|
|
|
|
obj = FreeCAD.ActiveDocument.addObject(objectType, internalName)
|
|
if not obj:
|
|
return None
|
|
|
|
obj.Label = defaultLabel
|
|
|
|
try:
|
|
# Import module and initialize base class
|
|
module = importlib.import_module(moduleName)
|
|
baseClass = getattr(module, baseClassName, None)
|
|
if not baseClass:
|
|
FreeCAD.Console.PrintError(
|
|
f"Base class '{baseClassName}' not found in module '{moduleName}'.\n"
|
|
)
|
|
return None
|
|
baseClass(obj)
|
|
|
|
# Initialize view provider
|
|
if FreeCAD.GuiUp:
|
|
viewProvider = getattr(module, viewProviderName, None)
|
|
if not viewProvider:
|
|
FreeCAD.Console.PrintWarning(
|
|
f"View provider '{viewProviderName}' not found in module '{moduleName}'.\n"
|
|
)
|
|
else:
|
|
viewProvider(obj.ViewObject)
|
|
|
|
except ImportError as e:
|
|
FreeCAD.Console.PrintError(f"Failed to import module '{moduleName}': {e}\n")
|
|
return None
|
|
|
|
return obj
|