FreeCAD / src /Mod /BIM /ArchBuildingPart.py
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2018 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/>. *
# * *
# ***************************************************************************
__title__ = "FreeCAD Arch BuildingPart"
__author__ = "Yorik van Havre"
__url__ = "https://www.freecad.org"
## @package ArchBuildingPart
# \ingroup ARCH
# \brief The BuildingPart object and tools
#
# This module provides tools to build BuildingPart objects.
# BuildingParts are used to group different Arch objects
import os
import tempfile
import FreeCAD
import Arch
import ArchCommands
import ArchIFC
import Draft
import DraftVecUtils
from draftutils import params
if FreeCAD.GuiUp:
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCADGui
from draftutils.translate import translate
import draftutils.units as units
else:
# \cond
def translate(ctxt, txt):
return txt
def QT_TRANSLATE_NOOP(ctxt, txt):
return txt
# \endcond
unicode = str
# fmt: off
BuildingTypes = ['Undefined',
'Agricultural - Barn',
'Agricultural - Chicken coop or chickenhouse',
'Agricultural - Cow-shed',
'Agricultural - Farmhouse',
'Agricultural - Granary',
'Agricultural - Greenhouse',
'Agricultural - Hayloft',
'Agricultural - Pigpen or sty',
'Agricultural - Root cellar',
'Agricultural - Shed',
'Agricultural - Silo',
'Agricultural - Stable',
'Agricultural - Storm cellar',
'Agricultural - Well house',
'Agricultural - Underground pit',
'Commercial - Automobile repair shop',
'Commercial - Bank',
'Commercial - Car wash',
'Commercial - Convention center',
'Commercial - Forum',
'Commercial - Gas station',
'Commercial - Hotel',
'Commercial - Market',
'Commercial - Market house',
'Commercial - Skyscraper',
'Commercial - Shop',
'Commercial - Shopping mall',
'Commercial - Supermarket',
'Commercial - Warehouse',
'Commercial - Restaurant',
'Residential - Apartment block',
'Residential - Asylum',
'Residential - Condominium',
'Residential - Dormitory',
'Residential - Duplex',
'Residential - House',
'Residential - Nursing home',
'Residential - Townhouse',
'Residential - Villa',
'Residential - Bungalow',
'Educational - Archive',
'Educational - College classroom building',
'Educational - College gymnasium',
'Educational - College students union',
'Educational - School',
'Educational - Library',
'Educational - Museum',
'Educational - Art gallery',
'Educational - Theater',
'Educational - Amphitheater',
'Educational - Concert hall',
'Educational - Cinema',
'Educational - Opera house',
'Educational - Boarding school',
'Government - Capitol',
'Government - City hall',
'Government - Consulate',
'Government - Courthouse',
'Government - Embassy',
'Government - Fire station',
'Government - Meeting house',
'Government - Moot hall',
'Government - Palace',
'Government - Parliament',
'Government - Police station',
'Government - Post office',
'Government - Prison',
'Industrial - Brewery',
'Industrial - Factory',
'Industrial - Foundry',
'Industrial - Power plant',
'Industrial - Mill',
'Military - Arsenal',
'Military -Barracks',
'Parking - Boathouse',
'Parking - Garage',
'Parking - Hangar',
'Storage - Silo',
'Storage - Hangar',
'Religious - Church',
'Religious - Basilica',
'Religious - Cathedral',
'Religious - Chapel',
'Religious - Oratory',
'Religious - Martyrium',
'Religious - Mosque',
'Religious - Mihrab',
'Religious - Surau',
'Religious - Imambargah',
'Religious - Monastery',
'Religious - Mithraeum',
'Religious - Fire temple',
'Religious - Shrine',
'Religious - Synagogue',
'Religious - Temple',
'Religious - Pagoda',
'Religious - Gurdwara',
'Religious - Hindu temple',
'Transport - Airport terminal',
'Transport - Bus station',
'Transport - Metro station',
'Transport - Taxi station',
'Transport - Railway station',
'Transport - Signal box',
'Transport - Lighthouse',
'Infrastructure - Data centre',
'Power station - Fossil-fuel power station',
'Power station - Nuclear power plant',
'Power station - Geothermal power',
'Power station - Biomass-fuelled power plant',
'Power station - Waste heat power plant',
'Power station - Renewable energy power station',
'Power station - Atomic energy plant',
'Other - Apartment',
'Other - Clinic',
'Other - Community hall',
'Other - Eatery',
'Other - Folly',
'Other - Food court',
'Other - Hospice',
'Other - Hospital',
'Other - Hut',
'Other - Bathhouse',
'Other - Workshop',
'Other - World trade centre'
]
# fmt: on
class BuildingPart(ArchIFC.IfcProduct):
"The BuildingPart object"
def __init__(self, obj):
obj.Proxy = self
self.Type = "BuildingPart"
obj.addExtension("App::GroupExtensionPython")
# obj.addExtension('App::OriginGroupExtensionPython')
self.setProperties(obj)
def setProperties(self, obj):
ArchIFC.IfcProduct.setProperties(self, obj)
pl = obj.PropertiesList
if not "Height" in pl:
obj.addProperty(
"App::PropertyLength",
"Height",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "The height of this object"),
locked=True,
)
if not "HeightPropagate" in pl:
obj.addProperty(
"App::PropertyBool",
"HeightPropagate",
"Children",
QT_TRANSLATE_NOOP(
"App::Property",
"If true, the height value propagates to contained objects if the height of those objects is set to 0",
),
locked=True,
)
obj.HeightPropagate = True
if not "LevelOffset" in pl:
obj.addProperty(
"App::PropertyDistance",
"LevelOffset",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "The level of the (0,0,0) point of this level"),
locked=True,
)
if not "Area" in pl:
obj.addProperty(
"App::PropertyArea",
"Area",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "The computed floor area of this floor"),
locked=True,
)
if not "Description" in pl:
obj.addProperty(
"App::PropertyString",
"Description",
"Component",
QT_TRANSLATE_NOOP("App::Property", "An optional description for this component"),
locked=True,
)
if not "Tag" in pl:
obj.addProperty(
"App::PropertyString",
"Tag",
"Component",
QT_TRANSLATE_NOOP("App::Property", "An optional tag for this component"),
locked=True,
)
if not "Shape" in pl:
obj.addProperty(
"Part::PropertyPartShape",
"Shape",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "The shape of this object"),
locked=True,
)
if not "SavedInventor" in pl:
obj.addProperty(
"App::PropertyFileIncluded",
"SavedInventor",
"BuildingPart",
QT_TRANSLATE_NOOP(
"App::Property",
"This property stores an OpenInventor representation for this object",
),
locked=True,
)
obj.setEditorMode("SavedInventor", 2)
if not "OnlySolids" in pl:
obj.addProperty(
"App::PropertyBool",
"OnlySolids",
"BuildingPart",
QT_TRANSLATE_NOOP(
"App::Property",
"If true, only solids will be collected by this object when referenced from other files",
),
locked=True,
)
obj.OnlySolids = True
if not "MaterialsTable" in pl:
obj.addProperty(
"App::PropertyMap",
"MaterialsTable",
"BuildingPart",
QT_TRANSLATE_NOOP(
"App::Property",
"A MaterialName:SolidIndexesList map that relates material names with solid indexes to be used when referencing this object from other files",
),
locked=True,
)
def onDocumentRestored(self, obj):
self.setProperties(obj)
def dumps(self):
return None
def loads(self, state):
self.Type = "BuildingPart"
def onBeforeChange(self, obj, prop):
if prop == "Placement":
self.oldPlacement = FreeCAD.Placement(obj.Placement)
def onChanged(self, obj, prop):
import math
ArchIFC.IfcProduct.onChanged(self, obj, prop)
# clean svg cache if needed
if prop in ["Placement", "Group"]:
self.svgcache = None
self.shapecache = None
if (prop == "Height" or prop == "HeightPropagate") and obj.Height.Value:
self.touchChildren(obj)
elif prop == "Placement":
if hasattr(self, "oldPlacement") and self.oldPlacement != obj.Placement:
deltap = obj.Placement.Base.sub(self.oldPlacement.Base)
if deltap.Length == 0:
deltap = None
deltar = obj.Placement.Rotation * self.oldPlacement.Rotation.inverted()
if deltar.Angle < 0.0001:
deltar = None
for child in self.getMovableChildren(obj):
if deltar:
child.Placement.rotate(
self.oldPlacement.Base,
deltar.Axis,
math.degrees(deltar.Angle),
comp=True,
)
if deltap:
child.Placement.move(deltap)
def execute(self, obj):
"gather all the child shapes into a compound"
pl = obj.Placement
shapes, materialstable = self.getShapes(obj)
if shapes:
import Part
if obj.OnlySolids:
f = []
for s in shapes:
f.extend(s.Solids)
# print("faces before compound:",len(f))
obj.Shape = Part.makeCompound(f)
# print("faces after compound:",len(obj.Shape.Faces))
# print("recomputing ",obj.Label)
else:
obj.Shape = Part.makeCompound(shapes)
obj.Placement = pl
obj.Area = self.getArea(obj)
obj.MaterialsTable = materialstable
if obj.ViewObject:
# update the autogroup box if needed
obj.ViewObject.Proxy.onChanged(obj.ViewObject, "AutoGroupBox")
def getMovableChildren(self, obj):
"recursively get movable children"
result = []
for child in obj.Group:
if child.isDerivedFrom("App::DocumentObjectGroup"):
result.extend(self.getMovableChildren(child))
if not hasattr(child, "MoveWithHost") or child.MoveWithHost:
if hasattr(child, "Placement"):
result.append(child)
return result
def getArea(self, obj):
"computes the area of this floor by adding its inner spaces"
area = 0
if hasattr(obj, "Group"):
for child in obj.Group:
if (Draft.get_type(child) in ["Space", "BuildingPart"]) and hasattr(
child, "IfcType"
):
area += child.Area.Value
return area
def getShapes(self, obj):
"recursively get the shapes of objects inside this BuildingPart"
shapes = []
solidindex = 0
materialstable = {}
for child in Draft.get_group_contents(obj, walls=True):
if not Draft.get_type(child) in ["Space"]:
if hasattr(child, "Shape") and child.Shape:
shapes.append(child.Shape)
for solid in child.Shape.Solids:
matname = "Undefined"
if hasattr(child, "Material") and child.Material:
matname = child.Material.Name
if matname in materialstable:
materialstable[matname] = (
materialstable[matname] + "," + str(solidindex)
)
else:
materialstable[matname] = str(solidindex)
solidindex += 1
return shapes, materialstable
def getSpaces(self, obj):
"gets the list of Spaces that have this object as their Zone property"
g = []
for o in obj.OutList:
if hasattr(o, "Zone"):
if o.Zone == obj:
g.append(o)
return g
def touchChildren(self, obj):
"Touches all descendents where applicable"
g = []
if hasattr(obj, "Group"):
g = obj.Group
elif Draft.getType(obj) in ["Wall", "Structure"]:
g = obj.Additions
for child in g:
if Draft.getType(child) in ["Wall", "Structure"]:
if not child.Height.Value:
FreeCAD.Console.PrintLog("Auto-updating Height of " + child.Name + "\n")
self.touchChildren(child)
child.Proxy.execute(child)
elif Draft.getType(child) in ["App::DocumentObjectGroup", "Group", "BuildingPart"]:
self.touchChildren(child)
def addObject(self, obj, child):
"Adds an object to the group of this BuildingPart"
if not child in obj.Group:
g = obj.Group
g.append(child)
obj.Group = g
def autogroup(self, obj, child):
"Adds an object to the group of this BuildingPart automatically"
if obj.ViewObject:
if hasattr(obj.ViewObject.Proxy, "autobbox") and obj.ViewObject.Proxy.autobbox:
if hasattr(child, "Shape") and child.Shape:
abb = obj.ViewObject.Proxy.autobbox
cbb = child.Shape.BoundBox
if abb.isValid():
if not cbb.isValid():
FreeCAD.ActiveDocument.recompute()
if not cbb.isValid():
cbb = FreeCAD.BoundBox()
for v in child.Shape.Vertexes:
print(v.Point)
cbb.add(v.Point)
if cbb.isValid() and abb.isInside(cbb):
self.addObject(obj, child)
return True
return False
class ViewProviderBuildingPart:
"A View Provider for the BuildingPart object"
def __init__(self, vobj):
if vobj:
vobj.addExtension("Gui::ViewProviderGroupExtensionPython")
vobj.Proxy = self
self.setProperties(vobj)
vobj.ShapeColor = ArchCommands.getDefaultColor("Helpers")
self.Object = vobj.Object
def setProperties(self, vobj):
pl = vobj.PropertiesList
if not "LineWidth" in pl:
vobj.addProperty(
"App::PropertyFloat",
"LineWidth",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "The line width of this object"),
locked=True,
)
vobj.LineWidth = 1
if not "OverrideUnit" in pl:
vobj.addProperty(
"App::PropertyString",
"OverrideUnit",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "An optional unit to express levels"),
locked=True,
)
if not "DisplayOffset" in pl:
vobj.addProperty(
"App::PropertyPlacement",
"DisplayOffset",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "A transformation to apply to the level mark"),
locked=True,
)
vobj.DisplayOffset = FreeCAD.Placement(
FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), 90)
)
if not "ShowLevel" in pl:
vobj.addProperty(
"App::PropertyBool",
"ShowLevel",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "If true, show the level"),
locked=True,
)
vobj.ShowLevel = True
if not "ShowUnit" in pl:
vobj.addProperty(
"App::PropertyBool",
"ShowUnit",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "If true, show the unit on the level tag"),
locked=True,
)
if not "OriginOffset" in pl:
vobj.addProperty(
"App::PropertyBool",
"OriginOffset",
"BuildingPart",
QT_TRANSLATE_NOOP(
"App::Property", "If true, display offset will affect the origin mark too"
),
locked=True,
)
if not "ShowLabel" in pl:
vobj.addProperty(
"App::PropertyBool",
"ShowLabel",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "If true, the object's label is displayed"),
locked=True,
)
vobj.ShowLabel = True
if not "FontName" in pl:
vobj.addProperty(
"App::PropertyFont",
"FontName",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "The font to be used for texts"),
locked=True,
)
vobj.FontName = params.get_param("textfont")
if not "FontSize" in pl:
vobj.addProperty(
"App::PropertyLength",
"FontSize",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "The font size of texts"),
locked=True,
)
vobj.FontSize = params.get_param("textheight") * params.get_param(
"DefaultAnnoScaleMultiplier"
)
if not "DiffuseColor" in pl:
vobj.addProperty(
"App::PropertyColorList",
"DiffuseColor",
"BuildingPart",
QT_TRANSLATE_NOOP("App::Property", "The individual face colors"),
locked=True,
)
# Interaction properties
if not "SetWorkingPlane" in pl:
vobj.addProperty(
"App::PropertyBool",
"SetWorkingPlane",
"Interaction",
QT_TRANSLATE_NOOP(
"App::Property",
"If true, when activated, the working plane will automatically adapt to this level",
),
locked=True,
)
vobj.SetWorkingPlane = True
if not "AutoWorkingPlane" in pl:
vobj.addProperty(
"App::PropertyBool",
"AutoWorkingPlane",
"Interaction",
QT_TRANSLATE_NOOP(
"App::Property", "If set to True, the working plane will be kept on Auto mode"
),
locked=True,
)
if not "ViewData" in pl:
vobj.addProperty(
"App::PropertyFloatList",
"ViewData",
"Interaction",
QT_TRANSLATE_NOOP(
"App::Property", "Camera position data associated with this object"
),
locked=True,
)
vobj.setEditorMode("ViewData", 2)
if not "RestoreView" in pl:
vobj.addProperty(
"App::PropertyBool",
"RestoreView",
"Interaction",
QT_TRANSLATE_NOOP(
"App::Property",
"If set, the view stored in this object will be restored on double-click",
),
locked=True,
)
if not "DoubleClickActivates" in pl:
vobj.addProperty(
"App::PropertyBool",
"DoubleClickActivates",
"Interaction",
QT_TRANSLATE_NOOP(
"App::Property", "If True, double-clicking this object in the tree activates it"
),
locked=True,
)
vobj.DoubleClickActivates = True
# inventor saving
if not "SaveInventor" in pl:
vobj.addProperty(
"App::PropertyBool",
"SaveInventor",
"Interaction",
QT_TRANSLATE_NOOP(
"App::Property",
"If this is enabled, the OpenInventor representation of this object will be saved in the FreeCAD file, allowing to reference it in other files in lightweight mode.",
),
locked=True,
)
if not "SavedInventor" in pl:
vobj.addProperty(
"App::PropertyFileIncluded",
"SavedInventor",
"Interaction",
QT_TRANSLATE_NOOP(
"App::Property",
"A slot to save the OpenInventor representation of this object, if enabled",
),
locked=True,
)
vobj.setEditorMode("SavedInventor", 2)
# children properties
if not "ChildrenOverride" in pl:
vobj.addProperty(
"App::PropertyBool",
"ChildrenOverride",
"Children",
QT_TRANSLATE_NOOP(
"App::Property",
"If true, show the objects contained in this Building Part will adopt these line, color and transparency settings",
),
locked=True,
)
if not "ChildrenLineWidth" in pl:
vobj.addProperty(
"App::PropertyFloat",
"ChildrenLineWidth",
"Children",
QT_TRANSLATE_NOOP("App::Property", "The line width of child objects"),
locked=True,
)
vobj.ChildrenLineWidth = params.get_param_view("DefaultShapeLineWidth")
if not "ChildrenLineColor" in pl:
vobj.addProperty(
"App::PropertyColor",
"ChildrenLineColor",
"Children",
QT_TRANSLATE_NOOP("App::Property", "The line color of child objects"),
locked=True,
)
vobj.ChildrenLineColor = params.get_param_view("DefaultShapeLineColor") & 0xFFFFFF00
if not "ChildrenShapeColor" in pl:
vobj.addProperty(
"App::PropertyMaterial",
"ChildrenShapeColor",
"Children",
QT_TRANSLATE_NOOP("App::Property", "The shape appearance of child objects"),
locked=True,
)
vobj.ChildrenShapeColor = params.get_param_view("DefaultShapeColor") & 0xFFFFFF00
if not "ChildrenTransparency" in pl:
vobj.addProperty(
"App::PropertyPercent",
"ChildrenTransparency",
"Children",
QT_TRANSLATE_NOOP("App::Property", "The transparency of child objects"),
locked=True,
)
vobj.ChildrenTransparency = params.get_param_view("DefaultShapeTransparency")
# clip properties
if not "CutView" in pl:
vobj.addProperty(
"App::PropertyBool",
"CutView",
"Clip",
QT_TRANSLATE_NOOP("App::Property", "Cut the view above this level"),
locked=True,
)
if not "CutMargin" in pl:
vobj.addProperty(
"App::PropertyLength",
"CutMargin",
"Clip",
QT_TRANSLATE_NOOP(
"App::Property", "The distance between the level plane and the cut line"
),
locked=True,
)
vobj.CutMargin = 1600
if not "AutoCutView" in pl:
vobj.addProperty(
"App::PropertyBool",
"AutoCutView",
"Clip",
QT_TRANSLATE_NOOP("App::Property", "Turn cutting on when activating this level"),
locked=True,
)
# autogroup properties
if not "AutogroupSize" in pl:
vobj.addProperty(
"App::PropertyIntegerList",
"AutogroupSize",
"AutoGroup",
QT_TRANSLATE_NOOP(
"App::Property",
"The capture box for newly created objects expressed as [XMin,YMin,ZMin,XMax,YMax,ZMax]",
),
locked=True,
)
if not "AutogroupBox" in pl:
vobj.addProperty(
"App::PropertyBool",
"AutogroupBox",
"AutoGroup",
QT_TRANSLATE_NOOP("App::Property", "Turns auto group box on/off"),
locked=True,
)
if not "AutogroupAutosize" in pl:
vobj.addProperty(
"App::PropertyBool",
"AutogroupAutosize",
"AutoGroup",
QT_TRANSLATE_NOOP("App::Property", "Automatically set size from contents"),
locked=True,
)
if not "AutogroupMargin" in pl:
vobj.addProperty(
"App::PropertyLength",
"AutogroupMargin",
"AutoGroup",
QT_TRANSLATE_NOOP("App::Property", "A margin to use when autosize is turned on"),
locked=True,
)
def onDocumentRestored(self, vobj):
self.setProperties(vobj)
def getIcon(self):
import Arch_rc
if hasattr(self, "Object"):
if self.Object.IfcType == "Building Storey":
return ":/icons/Arch_Floor_Tree.svg"
elif self.Object.IfcType == "Building":
return ":/icons/Arch_Building_Tree.svg"
elif self.Object.IfcType == "Annotation":
return ":/icons/BIM_ArchView.svg"
elif hasattr(self.Object, "IfcClass"):
from nativeifc import ifc_viewproviders
return ifc_viewproviders.get_icon(self)
return ":/icons/Arch_BuildingPart_Tree.svg"
def attach(self, vobj):
self.Object = vobj.Object
self.clip = None
from pivy import coin
self.sep = coin.SoGroup()
self.mat = coin.SoMaterial()
self.sep.addChild(self.mat)
self.dst = coin.SoDrawStyle()
self.sep.addChild(self.dst)
self.lco = coin.SoCoordinate3()
self.sep.addChild(self.lco)
import PartGui # Required for "SoBrepEdgeSet" (because a BuildingPart is not a Part::FeaturePython object).
lin = coin.SoType.fromName("SoBrepEdgeSet").createInstance()
if lin:
lin.coordIndex.setValues([0, 1, -1, 2, 3, -1, 4, 5, -1])
self.sep.addChild(lin)
self.bbox = coin.SoSwitch()
self.bbox.whichChild = -1
bboxsep = coin.SoSeparator()
self.bbox.addChild(bboxsep)
drawstyle = coin.SoDrawStyle()
drawstyle.style = coin.SoDrawStyle.LINES
drawstyle.lineWidth = 3
drawstyle.linePattern = 0x0F0F # 0xaa
bboxsep.addChild(drawstyle)
self.bbco = coin.SoCoordinate3()
bboxsep.addChild(self.bbco)
lin = coin.SoIndexedLineSet()
lin.coordIndex.setValues(
[0, 1, 2, 3, 0, -1, 4, 5, 6, 7, 4, -1, 0, 4, -1, 1, 5, -1, 2, 6, -1, 3, 7, -1]
)
bboxsep.addChild(lin)
self.sep.addChild(self.bbox)
self.tra = coin.SoTransform()
self.tra.rotation.setValue(FreeCAD.Rotation(0, 0, 90).Q)
self.sep.addChild(self.tra)
self.fon = coin.SoFont()
self.sep.addChild(self.fon)
self.txt = coin.SoAsciiText()
self.txt.justification = coin.SoText2.LEFT
self.txt.string.setValue("level")
self.sep.addChild(self.txt)
vobj.addDisplayMode(self.sep, "Default")
self.onChanged(vobj, "ShapeColor")
self.onChanged(vobj, "FontName")
self.onChanged(vobj, "ShowLevel")
self.onChanged(vobj, "FontSize")
self.onChanged(vobj, "AutogroupBox")
self.setProperties(vobj)
return
def getDisplayModes(self, vobj):
return ["Default"]
def getDefaultDisplayMode(self):
return "Default"
def setDisplayMode(self, mode):
return mode
def updateData(self, obj, prop):
if prop in ["Placement", "LevelOffset"]:
self.onChanged(obj.ViewObject, "OverrideUnit")
elif prop == "Shape":
# gather all the child shapes
colors = self.getColors(obj)
if colors and hasattr(obj.ViewObject, "DiffuseColor"):
if len(colors) == len(obj.Shape.Faces):
if colors != obj.ViewObject.DiffuseColor:
obj.ViewObject.DiffuseColor = colors
self.writeInventor(obj)
# else:
# print("color mismatch:",len(colors),"colors,",len(obj.Shape.Faces),"faces")
elif prop == "Group":
self.onChanged(obj.ViewObject, "ChildrenOverride")
elif prop == "Label":
self.onChanged(obj.ViewObject, "ShowLabel")
def getColors(self, obj):
"recursively get the colors of objects inside this BuildingPart"
colors = []
for child in Draft.get_group_contents(obj, walls=True):
if not Draft.get_type(child) in ["Space"]:
if hasattr(child, "Shape") and (
hasattr(child.ViewObject, "DiffuseColor")
or hasattr(child.ViewObject, "ShapeColor")
):
if hasattr(child.ViewObject, "DiffuseColor") and len(
child.ViewObject.DiffuseColor
) == len(child.Shape.Faces):
colors.extend(child.ViewObject.DiffuseColor)
else:
c = child.ViewObject.ShapeColor[:3] + (
1.0 - child.ViewObject.Transparency / 100.0,
)
for i in range(len(child.Shape.Faces)):
colors.append(c)
return colors
def onChanged(self, vobj, prop):
# print(vobj.Object.Label," - ",prop)
if prop == "ShapeColor":
if hasattr(vobj, "ShapeColor"):
l = vobj.ShapeColor
self.mat.diffuseColor.setValue([l[0], l[1], l[2]])
elif prop == "LineWidth":
if hasattr(vobj, "LineWidth"):
self.dst.lineWidth = vobj.LineWidth
elif prop == "FontName":
if hasattr(vobj, "FontName") and hasattr(self, "fon"):
if vobj.FontName:
self.fon.name = vobj.FontName
elif prop in ["FontSize", "DisplayOffset", "OriginOffset"]:
if (
hasattr(vobj, "FontSize")
and hasattr(vobj, "DisplayOffset")
and hasattr(vobj, "OriginOffset")
and hasattr(self, "fon")
):
fs = vobj.FontSize.Value
if fs:
self.fon.size = fs
b = vobj.DisplayOffset.Base
self.tra.translation.setValue([b.x + fs / 8, b.y, b.z + fs / 8])
r = vobj.DisplayOffset.Rotation
self.tra.rotation.setValue(r.Q)
if vobj.OriginOffset:
self.lco.point.setValues(
[
[b.x - fs, b.y, b.z],
[b.x + fs, b.y, b.z],
[b.x, b.y - fs, b.z],
[b.x, b.y + fs, b.z],
[b.x, b.y, b.z - fs],
[b.x, b.y, b.z + fs],
]
)
else:
self.lco.point.setValues(
[
[-fs, 0, 0],
[fs, 0, 0],
[0, -fs, 0],
[0, fs, 0],
[0, 0, -fs],
[0, 0, fs],
]
)
elif prop in ["OverrideUnit", "ShowUnit", "ShowLevel", "ShowLabel"]:
if (
hasattr(vobj, "OverrideUnit")
and hasattr(vobj, "ShowUnit")
and hasattr(vobj, "ShowLevel")
and hasattr(vobj, "ShowLabel")
and hasattr(self, "txt")
):
offset = getattr(vobj.Object, "LevelOffset", 0)
if hasattr(offset, "Value"):
offset = offset.Value
z = vobj.Object.Placement.Base.z + offset
q = FreeCAD.Units.Quantity(z, FreeCAD.Units.Length)
txt = ""
if vobj.ShowLabel:
txt += vobj.Object.Label
if vobj.ShowLevel:
if txt:
txt += " "
if z >= 0:
txt += "+"
if vobj.OverrideUnit:
u = vobj.OverrideUnit
else:
u = q.getUserPreferred()[2]
try:
txt += units.display_external(float(q), None, "Length", vobj.ShowUnit, u)
except Exception:
q = q.getValueAs(q.getUserPreferred()[2])
d = params.get_param("Decimals", path="Units")
fmt = "{0:." + str(d) + "f}"
if not vobj.ShowUnit:
u = ""
txt += fmt.format(float(q)) + str(u)
if not txt:
txt = " " # empty texts make coin crash...
if isinstance(txt, unicode):
txt = txt.encode("utf8")
self.txt.string.setValue(txt)
elif prop in [
"ChildrenOverride",
"ChildenLineWidth",
"ChildrenLineColor",
"ChildrenShapeColor",
"ChildrenTransparency",
]:
if hasattr(vobj, "ChildrenOverride") and vobj.ChildrenOverride:
props = [
"ChildenLineWidth",
"ChildrenLineColor",
"ChildrenShapeColor",
"ChildrenTransparency",
]
for child in vobj.Object.Group:
for prop in props:
if (
hasattr(vobj, prop)
and hasattr(child.ViewObject, prop[8:])
and not hasattr(child, "ChildrenOverride")
):
setattr(child.ViewObject, prop[8:], getattr(vobj, prop))
elif prop in ["CutView", "CutMargin"]:
if (
hasattr(vobj, "CutView")
and FreeCADGui.ActiveDocument.ActiveView
and hasattr(FreeCADGui.ActiveDocument.ActiveView, "getSceneGraph")
):
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
if vobj.CutView:
from pivy import coin
if self.clip:
sg.removeChild(self.clip)
self.clip = None
for o in Draft.get_group_contents(vobj.Object.Group, walls=True):
if hasattr(o.ViewObject, "Lighting"):
o.ViewObject.Lighting = "One side"
self.clip = coin.SoClipPlane()
self.clip.on.setValue(True)
norm = vobj.Object.Placement.multVec(FreeCAD.Vector(0, 0, 1))
mp = vobj.Object.Placement.Base
mp = DraftVecUtils.project(mp, norm)
dist = mp.Length # - 0.1 # to not clip exactly on the section object
norm = norm.negative()
marg = 1
if hasattr(vobj, "CutMargin"):
marg = vobj.CutMargin.Value
if mp.getAngle(norm) > 1:
dist += marg
dist = -dist
else:
dist -= marg
plane = coin.SbPlane(coin.SbVec3f(norm.x, norm.y, norm.z), dist)
self.clip.plane.setValue(plane)
sg.insertChild(self.clip, 0)
else:
if getattr(self, "clip", None):
sg.removeChild(self.clip)
self.clip = None
for o in Draft.get_group_contents(vobj.Object.Group, walls=True):
if hasattr(o.ViewObject, "Lighting"):
o.ViewObject.Lighting = "Two side"
elif prop == "Visibility":
# turn clipping off when turning the object off
if hasattr(vobj, "Visibility") and not (vobj.Visibility) and hasattr(vobj, "CutView"):
vobj.CutView = False
elif prop == "SaveInventor":
self.writeInventor(vobj.Object)
elif prop in ["AutogroupBox", "AutogroupSize"]:
if hasattr(vobj, "AutogroupBox") and hasattr(vobj, "AutogroupSize"):
if vobj.AutogroupBox:
if len(vobj.AutogroupSize) >= 6:
self.autobbox = FreeCAD.BoundBox(*vobj.AutogroupSize[0:6])
self.autobbox.move(vobj.Object.Placement.Base)
pts = [list(self.autobbox.getPoint(i)) for i in range(8)]
self.bbco.point.setValues(pts)
self.bbox.whichChild = 0
else:
self.autobbox = None
self.bbox.whichChild = -1
elif prop in ["AutogroupAutosize", "AutogroupMargin"]:
if hasattr(vobj, "AutogroupAutosize") and vobj.AutogroupAutosize:
bbox = vobj.Object.Shape.BoundBox
bbox.enlarge(vobj.AutogroupMargin.Value)
vobj.AutogroupSize = [
int(i)
for i in [bbox.XMin, bbox.YMin, bbox.ZMin, bbox.XMax, bbox.YMax, bbox.ZMax]
]
def onDelete(self, vobj, subelements):
if self.clip:
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
sg.removeChild(self.clip)
self.clip = None
for o in Draft.get_group_contents(vobj.Object.Group, walls=True):
if hasattr(o.ViewObject, "Lighting"):
o.ViewObject.Lighting = "Two side"
return True
def setEdit(self, vobj, mode):
# mode == 1 if Transform is selected in the Tree view context menu.
# mode == 2 has been added for consistency.
if mode == 1 or mode == 2:
return None
# For some reason mode is always 0 if the object is double-clicked in
# the Tree view. Using FreeCADGui.getUserEditMode() as a workaround.
if FreeCADGui.getUserEditMode() in ("Transform", "Cutting"):
return None
self.activate()
return False # Return `False` as we don't want to enter edit mode.
def unsetEdit(self, vobj, mode):
if mode == 1 or mode == 2:
return None
if FreeCADGui.getUserEditMode() in ("Transform", "Cutting"):
return None
return True
def setupContextMenu(self, vobj, menu):
from PySide import QtCore, QtGui
import Draft_rc
if FreeCADGui.activeWorkbench().name() != "BIMWorkbench":
return
if (not hasattr(vobj, "DoubleClickActivates")) or vobj.DoubleClickActivates:
menuTxt = translate("Arch", "Active")
actionActivate = QtGui.QAction(menuTxt, menu)
actionActivate.setCheckable(True)
if FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch") == self.Object:
actionActivate.setChecked(True)
else:
actionActivate.setChecked(False)
actionActivate.triggered.connect(lambda _: self.activate(actionActivate))
menu.addAction(actionActivate)
actionSetWorkingPlane = QtGui.QAction(
QtGui.QIcon(":/icons/Draft_SelectPlane.svg"),
translate("Arch", "Set Working Plane"),
menu,
)
QtCore.QObject.connect(
actionSetWorkingPlane, QtCore.SIGNAL("triggered()"), self.setWorkingPlane
)
menu.addAction(actionSetWorkingPlane)
actionWriteCamera = QtGui.QAction(
QtGui.QIcon(":/icons/Draft_SelectPlane.svg"),
translate("Arch", "Write Camera Position"),
menu,
)
QtCore.QObject.connect(actionWriteCamera, QtCore.SIGNAL("triggered()"), self.writeCamera)
menu.addAction(actionWriteCamera)
actionCreateGroup = QtGui.QAction(translate("Arch", "New Group"), menu)
QtCore.QObject.connect(actionCreateGroup, QtCore.SIGNAL("triggered()"), self.createGroup)
menu.addAction(actionCreateGroup)
actionReorder = QtGui.QAction(translate("Arch", "Reorder Children Alphabetically"), menu)
QtCore.QObject.connect(actionReorder, QtCore.SIGNAL("triggered()"), self.reorder)
menu.addAction(actionReorder)
actionCloneUp = QtGui.QAction(translate("Arch", "Clone Level Up"), menu)
QtCore.QObject.connect(actionCloneUp, QtCore.SIGNAL("triggered()"), self.cloneUp)
menu.addAction(actionCloneUp)
def activate(self, action=None):
from draftutils.gui_utils import toggle_working_plane
vobj = self.Object.ViewObject
if (not hasattr(vobj, "DoubleClickActivates")) or vobj.DoubleClickActivates:
if toggle_working_plane(self.Object, action, restore=True):
print("Setting active working plane to: ", self.Object.Label)
else:
print("Deactivating working plane from: ", self.Object.Label)
FreeCADGui.Selection.clearSelection()
def setWorkingPlane(self, restore=False):
vobj = self.Object.ViewObject
import WorkingPlane
wp = WorkingPlane.get_working_plane(update=False)
autoclip = False
if hasattr(vobj, "AutoCutView"):
autoclip = vobj.AutoCutView
if restore:
if wp.label.rstrip("*") == self.Object.Label:
prev_data = wp._previous()
if prev_data:
prev_label = prev_data.get("label", "").rstrip("*")
prev_obj = None
for obj in FreeCAD.ActiveDocument.Objects:
if hasattr(obj, "Label") and obj.Label == prev_label:
prev_obj = obj
break
if prev_obj:
# check in which context we need to set the active object
context = "Arch"
obj_type = Draft.getType(prev_obj)
if obj_type == "IfcBuildingStorey":
context = "NativeIFC"
FreeCADGui.ActiveDocument.ActiveView.setActiveObject(context, prev_obj)
print(f"Set active object to: {prev_obj.Label} (context: {context})")
if autoclip:
vobj.CutView = False
else:
wp.align_to_selection()
if autoclip:
vobj.CutView = True
def writeCamera(self):
if hasattr(self, "Object"):
from pivy import coin
n = FreeCADGui.ActiveDocument.ActiveView.getCameraNode()
FreeCAD.Console.PrintMessage(
QT_TRANSLATE_NOOP("Draft", "Writing camera position") + "\n"
)
cdata = list(n.position.getValue().getValue())
cdata.extend(list(n.orientation.getValue().getValue()))
cdata.append(n.nearDistance.getValue())
cdata.append(n.farDistance.getValue())
cdata.append(n.aspectRatio.getValue())
cdata.append(n.focalDistance.getValue())
if isinstance(n, coin.SoOrthographicCamera):
cdata.append(n.height.getValue())
cdata.append(0.0) # orthograhic camera
elif isinstance(n, coin.SoPerspectiveCamera):
cdata.append(n.heightAngle.getValue())
cdata.append(1.0) # perspective camera
self.Object.ViewObject.ViewData = cdata
def createGroup(self):
if hasattr(self, "Object"):
s = (
'FreeCAD.ActiveDocument.getObject("%s").newObject("App::DocumentObjectGroup","Group")'
% self.Object.Name
)
FreeCADGui.doCommand(s)
def reorder(self):
if hasattr(self, "Object"):
if hasattr(self.Object, "Group") and self.Object.Group:
g = self.Object.Group
g.sort(key=lambda obj: obj.Label)
self.Object.Group = g
FreeCAD.ActiveDocument.recompute()
def cloneUp(self):
if hasattr(self, "Object"):
if not self.Object.Height.Value:
FreeCAD.Console.PrintError(
"This level has no height value. Define a height before using this function.\n"
)
return
height = self.Object.Height.Value
ng = []
if hasattr(self.Object, "Group") and self.Object.Group:
for o in self.Object.Group:
no = Draft.clone(o)
Draft.move(no, FreeCAD.Vector(0, 0, height))
ng.append(no)
nobj = Arch.makeBuildingPart()
Draft.formatObject(nobj, self.Object)
nobj.Placement = self.Object.Placement
nobj.Placement.move(FreeCAD.Vector(0, 0, height))
nobj.IfcType = self.Object.IfcType
nobj.Height = height
nobj.Label = self.Object.Label
nobj.Group = ng
for parent in self.Object.InList:
if (
hasattr(parent, "Group")
and hasattr(parent, "addObject")
and (self.Object in parent.Group)
):
parent.addObject(nobj)
FreeCAD.ActiveDocument.recompute()
# fix for missing IFC attributes
for no in ng:
if (
hasattr(no, "LongName")
and hasattr(no, "CloneOf")
and no.CloneOf
and hasattr(no.CloneOf, "LongName")
):
no.LongName = no.CloneOf.LongName
FreeCAD.ActiveDocument.recompute()
def dumps(self):
return None
def loads(self, state):
return None
def writeInventor(self, obj):
def callback(match):
return next(callback.v)
if hasattr(obj.ViewObject, "SaveInventor") and obj.ViewObject.SaveInventor:
if obj.Shape and obj.Shape.Faces and hasattr(obj, "SavedInventor"):
colors = obj.ViewObject.DiffuseColor
if len(colors) != len(obj.Shape.Faces):
print("Debug: Colors mismatch in", obj.Label)
colors = None
iv = self.Object.Shape.writeInventor()
import re
if colors:
if len(re.findall(r"IndexedFaceSet", iv)) == len(obj.Shape.Faces):
# convert colors to iv representations
colors = [
"Material { diffuseColor "
+ str(color[0])
+ " "
+ str(color[1])
+ " "
+ str(color[2])
+ "}\n IndexedFaceSet"
for color in colors
]
# replace
callback.v = iter(colors)
iv = re.sub(r"IndexedFaceSet", callback, iv)
else:
print("Debug: IndexedFaceSet mismatch in", obj.Label)
# save embedded file
tf = tempfile.mkstemp(prefix=obj.Name, suffix=".iv")[1]
f = open(tf, "w")
f.write(iv)
f.close()
obj.SavedInventor = tf
os.remove(tf)