979 lines
42 KiB
Python
979 lines
42 KiB
Python
# -*- coding: utf8 -*-
|
|
#***************************************************************************
|
|
#* Copyright (c) 2018 Yorik van Havre <yorik@uncreated.net> *
|
|
#* *
|
|
#* This program is free software; you can redistribute it and/or modify *
|
|
#* it under the terms of the GNU Lesser General Public License (LGPL) *
|
|
#* as published by the Free Software Foundation; either version 2 of *
|
|
#* the License, or (at your option) any later version. *
|
|
#* for detail see the LICENCE text file. *
|
|
#* *
|
|
#* This program 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 Library General Public License for more details. *
|
|
#* *
|
|
#* You should have received a copy of the GNU Library General Public *
|
|
#* License along with this program; if not, write to the Free Software *
|
|
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
|
#* USA *
|
|
#* *
|
|
#***************************************************************************
|
|
|
|
import FreeCAD
|
|
import Draft
|
|
import ArchCommands
|
|
import DraftVecUtils
|
|
import ArchIFC
|
|
import tempfile
|
|
import os
|
|
from draftutils import params
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
from draftutils.translate import translate
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
import draftutils.units as units
|
|
else:
|
|
# \cond
|
|
def translate(ctxt,txt):
|
|
return txt
|
|
def QT_TRANSLATE_NOOP(ctxt,txt):
|
|
return txt
|
|
# \endcond
|
|
unicode = str
|
|
|
|
## @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
|
|
|
|
__title__ = "FreeCAD Arch BuildingPart"
|
|
__author__ = "Yorik van Havre"
|
|
__url__ = "https://www.freecad.org"
|
|
|
|
|
|
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'
|
|
]
|
|
|
|
|
|
class BuildingPart(ArchIFC.IfcProduct):
|
|
|
|
|
|
"The BuildingPart object"
|
|
|
|
def __init__(self,obj):
|
|
|
|
obj.Proxy = self
|
|
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"))
|
|
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"))
|
|
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"))
|
|
if not "Area" in pl:
|
|
obj.addProperty("App::PropertyArea","Area", "BuildingPart",QT_TRANSLATE_NOOP("App::Property","The computed floor area of this floor"))
|
|
if not "Description" in pl:
|
|
obj.addProperty("App::PropertyString","Description","Component",QT_TRANSLATE_NOOP("App::Property","An optional description for this component"))
|
|
if not "Tag" in pl:
|
|
obj.addProperty("App::PropertyString","Tag","Component",QT_TRANSLATE_NOOP("App::Property","An optional tag for this component"))
|
|
if not "Shape" in pl:
|
|
obj.addProperty("Part::PropertyPartShape","Shape","BuildingPart",QT_TRANSLATE_NOOP("App::Property","The shape of this object"))
|
|
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"))
|
|
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"))
|
|
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"))
|
|
|
|
self.Type = "BuildingPart"
|
|
|
|
def onDocumentRestored(self,obj):
|
|
|
|
self.setProperties(obj)
|
|
|
|
def dumps(self):
|
|
|
|
return None
|
|
|
|
def loads(self,state):
|
|
|
|
return None
|
|
|
|
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):
|
|
|
|
vobj.addExtension("Gui::ViewProviderGroupExtensionPython")
|
|
#vobj.addExtension("Gui::ViewProviderGeoFeatureGroupExtensionPython")
|
|
vobj.Proxy = self
|
|
self.setProperties(vobj)
|
|
vobj.ShapeColor = ArchCommands.getDefaultColor("Helpers")
|
|
|
|
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"))
|
|
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"))
|
|
if not "DisplayOffset" in pl:
|
|
vobj.addProperty("App::PropertyPlacement","DisplayOffset","BuildingPart",QT_TRANSLATE_NOOP("App::Property","A transformation to apply to the level mark"))
|
|
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"))
|
|
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"))
|
|
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"))
|
|
if not "ShowLabel" in pl:
|
|
vobj.addProperty("App::PropertyBool","ShowLabel","BuildingPart",QT_TRANSLATE_NOOP("App::Property","If true, the object's label is displayed"))
|
|
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"))
|
|
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"))
|
|
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"))
|
|
|
|
# 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"))
|
|
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"))
|
|
if not "ViewData" in pl:
|
|
vobj.addProperty("App::PropertyFloatList","ViewData","Interaction",QT_TRANSLATE_NOOP("App::Property","Camera position data associated with this object"))
|
|
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"))
|
|
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"))
|
|
|
|
# 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."))
|
|
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"))
|
|
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"))
|
|
if not "ChildrenLineWidth" in pl:
|
|
vobj.addProperty("App::PropertyFloat","ChildrenLineWidth","Children",QT_TRANSLATE_NOOP("App::Property","The line width of child objects"))
|
|
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"))
|
|
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"))
|
|
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"))
|
|
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"))
|
|
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"))
|
|
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"))
|
|
|
|
# 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]"))
|
|
if not "AutogroupBox" in pl:
|
|
vobj.addProperty("App::PropertyBool","AutogroupBox","AutoGroup",QT_TRANSLATE_NOOP("App::Property","Turns auto group box on/off"))
|
|
if not "AutogroupAutosize" in pl:
|
|
vobj.addProperty("App::PropertyBool","AutogroupAutosize","AutoGroup",QT_TRANSLATE_NOOP("App::Property","Automatically set size from contents"))
|
|
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"))
|
|
|
|
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"
|
|
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]+(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"):
|
|
z = vobj.Object.Placement.Base.z + vobj.Object.LevelOffset.Value
|
|
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 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 = "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.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 (not hasattr(vobj,"DoubleClickActivates")) or vobj.DoubleClickActivates:
|
|
if FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch") == self.Object:
|
|
menuTxt = translate("Arch", "Deactivate")
|
|
else:
|
|
menuTxt = translate("Arch", "Activate")
|
|
actionActivate = QtGui.QAction(menuTxt,
|
|
menu)
|
|
QtCore.QObject.connect(actionActivate,
|
|
QtCore.SIGNAL("triggered()"),
|
|
self.activate)
|
|
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", "Create 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):
|
|
vobj = self.Object.ViewObject
|
|
|
|
if FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch") == self.Object:
|
|
FreeCADGui.ActiveDocument.ActiveView.setActiveObject("Arch", None)
|
|
if vobj.SetWorkingPlane:
|
|
self.setWorkingPlane(restore=True)
|
|
elif (not hasattr(vobj,"DoubleClickActivates")) or vobj.DoubleClickActivates:
|
|
FreeCADGui.ActiveDocument.ActiveView.setActiveObject("Arch", self.Object)
|
|
if vobj.SetWorkingPlane:
|
|
self.setWorkingPlane()
|
|
|
|
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:
|
|
wp._previous()
|
|
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. Please 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 = 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)
|