FreeCAD/src/Mod/Arch/ArchSpace.py

530 lines
22 KiB
Python

# -*- coding: utf8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2013 *
#* 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 *
#* *
#***************************************************************************
__title__="FreeCAD Arch Space"
__author__ = "Yorik van Havre"
__url__ = "http://www.freecadweb.org"
Roles = ["Space"]
SpaceTypes = [
"Undefined",
"Exterior",
"Exterior - Terrace",
"Office",
"Office - Enclosed",
"Office - Open Plan",
"Conference / Meeting / Multipurpose",
"Classroom / Lecture / Training For Penitentiary",
"Lobby",
"Lobby - For Hotel",
"Lobby - For Performing Arts Theater",
"Lobby - For Motion Picture Theater",
"Audience/Seating Area",
"Audience/Seating Area - For Gymnasium",
"Audience/Seating Area - For Exercise Center",
"Audience/Seating Area - For Convention Center",
"Audience/Seating Area - For Penitentiary",
"Audience/Seating Area - For Religious Buildings",
"Audience/Seating Area - For Sports Arena",
"Audience/Seating Area - For Performing Arts Theater",
"Audience/Seating Area - For Motion Picture Theater",
"Audience/Seating Area - For Transportation",
"Atrium",
"Atrium - First Three Floors",
"Atrium - Each Additional Floor",
"Lounge / Recreation",
"Lounge / Recreation - For Hospital",
"Dining Area",
"Dining Area - For Penitentiary",
"Dining Area - For Hotel",
"Dining Area - For Motel",
"Dining Area - For Bar Lounge/Leisure Dining",
"Dining Area - For Family Dining",
"Food Preparation",
"Laboratory",
"Restrooms",
"Dressing / Locker / Fitting",
"Room",
"Corridor / Transition",
"Corridor / Transition - For Hospital",
"Corridor / Transition - For Manufacturing Facility",
"Stairs",
"Active Storage",
"Active Storage - For Hospital",
"Inactive Storage",
"Inactive Storage - For Museum",
"Electrical / Mechanical",
"Gymnasium / Exercise Center",
"Gymnasium / Exercise Center - Playing Area",
"Gymnasium / Exercise Center - Exercise Area",
"Courthouse / Police Station / Penitentiary",
"Courthouse / Police Station / Penitentiary - Courtroom",
"Courthouse / Police Station / Penitentiary - Confinement Cells",
"Courthouse / Police Station / Penitentiary - Judges' Chambers",
"Fire Stations",
"Fire Stations - Engine Room",
"Fire Stations - Sleeping Quarters",
"Post Office - Sorting Area",
"Convention Center - Exhibit Space",
"Library",
"Library - Card File and Cataloging",
"Library - Stacks",
"Library - Reading Area",
"Hospital",
"Hospital - Emergency",
"Hospital - Recovery",
"Hospital - Nurses' Station",
"Hospital - Exam / Treatment",
"Hospital - Pharmacy",
"Hospital - Patient Room",
"Hospital - Operating Room",
"Hospital - Nursery",
"Hospital - Medical Supply",
"Hospital - Physical Therapy",
"Hospital - Radiology",
"Hospital - Laundry-Washing",
"Automotive - Service / Repair",
"Manufacturing",
"Manufacturing - Low Bay (< 7.5m Floor to Ceiling Height)",
"Manufacturing - High Bay (> 7.5m Floor to Ceiling Height)",
"Manufacturing - Detailed Manufacturing",
"Manufacturing - Equipment Room",
"Manufacturing - Control Room",
"Hotel / Motel Guest Rooms",
"Dormitory - Living Quarters",
"Museum",
"Museum - General Exhibition",
"Museum - Restoration",
"Bank / Office - Banking Activity Area",
"Workshop",
"Sales Area",
"Religious Buildings",
"Religious Buildings - Worship Pulpit, Choir",
"Religious Buildings - Fellowship Hall",
"Retail",
"Retail - Sales Area",
"Retail - Mall Concourse",
"Sports Arena",
"Sports Arena - Ring Sports Area",
"Sports Arena - Court Sports Area",
"Sports Arena - Indoor Playing Field Area",
"Warehouse",
"Warehouse - Fine Material Storage",
"Warehouse - Medium / Bulky Material Storage",
"Parking Garage - Garage Area",
"Transportation",
"Transportation - Airport / Concourse",
"Transportation - Air / Train / Bus - Baggage Area",
"Transportation - Terminal - Ticket Counter"
]
import FreeCAD,ArchComponent,ArchCommands,math,Draft
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtCore, QtGui
from DraftTools import translate
else:
def translate(ctxt,txt):
return txt
def makeSpace(objects=None,baseobj=None,name="Space"):
"""makeSpace([objects]): Creates a space object from the given objects. Objects can be one
document object, in which case it becomes the base shape of the space object, or a list of
selection objects as got from getSelectionEx(), or a list of tuples (object, subobjectname)"""
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name)
obj.Label = translate("Arch",name)
_Space(obj)
if FreeCAD.GuiUp:
_ViewProviderSpace(obj.ViewObject)
if baseobj:
objects = baseobj
if objects:
if not isinstance(objects,list):
objects = [objects]
if len(objects) == 1:
obj.Base = objects[0]
objects[0].ViewObject.hide()
else:
obj.Proxy.addSubobjects(obj,objects)
return obj
def addSpaceBoundaries(space,subobjects):
"""addSpaceBoundaries(space,subobjects): adds the given subobjects to the given space"""
import Draft
if Draft.getType(space) == "Space":
space.Proxy.addSubobjects(space,subobjects)
def removeSpaceBoundaries(space,objects):
"""removeSpaceBoundaries(space,objects): removes the given objects from the given spaces boundaries"""
import Draft
if Draft.getType(space) == "Space":
bounds = space.Boundaries
for o in objects:
for b in bounds:
if o.Name == b[0].Name:
bounds.remove(b)
break
space.Boundaries = bounds
class _CommandSpace:
"the Arch Space command definition"
def GetResources(self):
return {'Pixmap' : 'Arch_Space',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Arch_Space","Space"),
'Accel': "S, P",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Arch_Space","Creates a space object from selected boundary objects")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Space"))
FreeCADGui.addModule("Arch")
sel = FreeCADGui.Selection.getSelection()
if sel:
FreeCADGui.Control.closeDialog()
if len(sel) == 1:
FreeCADGui.doCommand("Arch.makeSpace(FreeCADGui.Selection.getSelection())")
else:
FreeCADGui.doCommand("Arch.makeSpace(FreeCADGui.Selection.getSelectionEx())")
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
else:
FreeCAD.Console.PrintMessage(translate("Arch","Please select a base object\n"))
FreeCADGui.Control.showDialog(ArchComponent.SelectionTaskPanel())
FreeCAD.ArchObserver = ArchComponent.ArchSelectionObserver(nextCommand="Arch_Space")
FreeCADGui.Selection.addObserver(FreeCAD.ArchObserver)
class _Space(ArchComponent.Component):
"A space object"
def __init__(self,obj):
ArchComponent.Component.__init__(self,obj)
obj.addProperty("App::PropertyLinkSubList","Boundaries", "Arch",translate("Arch","The objects that make the boundaries of this space object"))
obj.addProperty("App::PropertyFloat", "Area", "Arch",translate("Arch","The computed floor area of this space"))
obj.addProperty("App::PropertyString", "FinishFloor", "Arch",translate("Arch","The finishing of the floor of this space"))
obj.addProperty("App::PropertyString", "FinishWalls", "Arch",translate("Arch","The finishing of the walls of this space"))
obj.addProperty("App::PropertyString", "FinishCeiling", "Arch",translate("Arch","The finishing of the ceiling of this space"))
obj.addProperty("App::PropertyLinkList", "Group", "Arch",translate("Arch","Objects that are included inside this space, such as furniture"))
obj.addProperty("App::PropertyEnumeration","SpaceType", "Arch",translate("Arch","The type of this space"))
obj.addProperty("App::PropertyLength", "FloorThickness","Arch",translate("Arch","The thickness of the floor finish"))
self.Type = "Space"
self.SpaceType = "Undefined"
obj.Role = Roles
def execute(self,obj):
if self.clone(obj):
return
self.getShape(obj)
def onChanged(self,obj,prop):
if prop in ["Boundaries","Base"]:
self.getShape(obj)
obj.Area = self.getArea(obj)
if hasattr(obj,"Area"):
obj.setEditorMode('Area',1)
def addSubobjects(self,obj,subobjects):
"adds subobjects to this space"
objs = obj.Boundaries
for o in subobjects:
if isinstance(o,tuple) or isinstance(o,list):
if o[0].Name != obj.Name:
objs.append(tuple(o))
else:
for el in o.SubElementNames:
if "Face" in el:
if o.Object.Name != obj.Name:
objs.append((o.Object,el))
obj.Boundaries = objs
def getShape(self,obj):
"computes a shape from a base shape and/or bounday faces"
import Part
shape = None
faces = []
#print "starting compute"
# 1: if we have a base shape, we use it
if obj.Base:
if obj.Base.isDerivedFrom("Part::Feature"):
if obj.Base.Shape.Solids:
shape = obj.Base.Shape.Solids[0].copy()
shape = shape.removeSplitter()
# 2: if not, add all bounding boxes of considered objects and build a first shape
if shape:
#print "got shape from base object"
bb = shape.BoundBox
else:
bb = None
for b in obj.Boundaries:
if b[0].isDerivedFrom("Part::Feature"):
if not bb:
bb = b[0].Shape.BoundBox
else:
bb.add(b[0].Shape.BoundBox)
if not bb:
return
shape = Part.makeBox(bb.XLength,bb.YLength,bb.ZLength,FreeCAD.Vector(bb.XMin,bb.YMin,bb.ZMin))
#print "created shape from boundbox"
# 3: identifing boundary faces
goodfaces = []
for b in obj.Boundaries:
if b[0].isDerivedFrom("Part::Feature"):
if "Face" in b[1]:
fn = int(b[1][4:])-1
faces.append(b[0].Shape.Faces[fn])
#print "adding face ",fn," of object ",b[0].Name
#print "total: ", len(faces), " faces"
# 4: get cutvolumes from faces
cutvolumes = []
for f in faces:
f = f.copy()
f.reverse()
cutface,cutvolume,invcutvolume = ArchCommands.getCutVolume(f,shape)
if cutvolume:
#print "generated 1 cutvolume"
cutvolumes.append(cutvolume.copy())
#Part.show(cutvolume)
for v in cutvolumes:
#print "cutting"
shape = shape.cut(v)
# 5: get the final shape
if shape:
if shape.Solids:
#print "setting objects shape"
shape = shape.Solids[0]
obj.Shape = shape
return
print "Arch: error computing space boundary"
def getArea(self,obj):
"returns the horizontal area at the center of the space"
import Part,DraftGeomUtils
if not hasattr(obj.Shape,"CenterOfMass"):
return 0
try:
pl = Part.makePlane(1,1)
pl.translate(obj.Shape.CenterOfMass)
sh = obj.Shape.copy()
cutplane,v1,v2 = ArchCommands.getCutVolume(pl,sh)
e = sh.section(cutplane)
e = DraftGeomUtils.sortEdges(e.Edges)
w = Part.Wire(e)
f = Part.Face(w)
return f.Area
except Part.OCCError:
return 0
class _ViewProviderSpace(ArchComponent.ViewProviderComponent):
"A View Provider for Section Planes"
def __init__(self,vobj):
ArchComponent.ViewProviderComponent.__init__(self,vobj)
vobj.Transparency = 85
vobj.LineWidth = 1
vobj.LineColor = (1.0,0.0,0.0,1.0)
vobj.DrawStyle = "Dotted"
vobj.addProperty("App::PropertyStringList", "Text", "Arch",translate("Arch","The text to show. Use $area, $label, $tag, $floor, $walls, $ceiling to insert the respective data"))
vobj.addProperty("App::PropertyString", "FontName", "Arch",translate("Arch","The name of the font"))
vobj.addProperty("App::PropertyColor", "TextColor", "Arch",translate("Arch","The color of the area text"))
vobj.addProperty("App::PropertyLength", "FontSize", "Arch",translate("Arch","The size of the text font"))
vobj.addProperty("App::PropertyLength", "FirstLine", "Arch",translate("Arch","The size of the first line of text"))
vobj.addProperty("App::PropertyFloat", "LineSpacing", "Arch",translate("Arch","The space between the lines of text"))
vobj.addProperty("App::PropertyVectorDistance","TextPosition","Arch",translate("Arch","The position of the text. Leave (0,0,0) for automatic position"))
vobj.addProperty("App::PropertyEnumeration", "TextAlign", "Arch",translate("Arch","The justification of the text"))
vobj.addProperty("App::PropertyInteger", "Decimals", "Arch",translate("Arch","The number of decimals to use for calculated texts"))
vobj.addProperty("App::PropertyBool", "ShowUnit", "Arch",translate("Arch","Show the unit suffix"))
vobj.TextColor = (0.0,0.0,0.0,1.0)
vobj.Text = ["$label","$area"]
vobj.TextAlign = ["Left","Center","Right"]
vobj.FontSize = Draft.getParam("textheight",10)
vobj.FirstLine = Draft.getParam("textheight",10)
vobj.FontName = Draft.getParam("textfont","")
vobj.Decimals = Draft.getParam("dimPrecision",2)
vobj.ShowUnit = Draft.getParam("showUnit",True)
vobj.LineSpacing = 1.0
def getDefaultDisplayMode(self):
return "Wireframe"
def getIcon(self):
import Arch_rc
return ":/icons/Arch_Space_Tree.svg"
def attach(self,vobj):
ArchComponent.ViewProviderComponent.attach(self,vobj)
from pivy import coin
self.color = coin.SoBaseColor()
self.font = coin.SoFont()
self.text1 = coin.SoAsciiText()
self.text1.string = " "
self.text1.justification = coin.SoAsciiText.LEFT
self.text2 = coin.SoAsciiText()
self.text2.string = " "
self.text2.justification = coin.SoAsciiText.LEFT
self.coords = coin.SoTransform()
self.header = coin.SoTransform()
label = coin.SoSeparator()
label.addChild(self.coords)
label.addChild(self.color)
label.addChild(self.font)
label.addChild(self.text2)
label.addChild(self.header)
label.addChild(self.text1)
vobj.Annotation.addChild(label)
self.onChanged(vobj,"TextColor")
self.onChanged(vobj,"FontSize")
self.onChanged(vobj,"FirstLine")
self.onChanged(vobj,"LineSpacing")
self.onChanged(vobj,"FontName")
def updateData(self,obj,prop):
if prop in ["Shape","Label","Tag"]:
self.onChanged(obj.ViewObject,"Text")
self.onChanged(obj.ViewObject,"TextPosition")
def getTextPosition(self,vobj):
pos = FreeCAD.Vector()
if hasattr(vobj,"TextPosition"):
import DraftVecUtils
if DraftVecUtils.isNull(vobj.TextPosition):
try:
pos = vobj.Object.Shape.CenterOfMass
z = vobj.Object.Shape.BoundBox.ZMin
pos = FreeCAD.Vector(pos.x,pos.y,z)
except (AttributeError, RuntimeError):
pos = FreeCAD.Vector()
else:
pos = vobj.TextPosition
return pos
def onChanged(self,vobj,prop):
if prop in ["Text","Decimals","ShowUnit"]:
if hasattr(self,"text1") and hasattr(self,"text2") and hasattr(vobj,"Text"):
self.text1.string.deleteValues(0)
self.text2.string.deleteValues(0)
text1 = []
text2 = []
first = True
for t in vobj.Text:
if t:
if hasattr(vobj.Object,"Area"):
from FreeCAD import Units
q = Units.Quantity(vobj.Object.Area,Units.Area).getUserPreferred()
qt = vobj.Object.Area/q[1]
if hasattr(vobj,"Decimals"):
if vobj.Decimals == 0:
qt = str(int(qt))
else:
f = "%."+str(abs(vobj.Decimals))+"f"
qt = f % qt
else:
qt = str(qt)
if hasattr(vobj,"ShowUnit"):
if vobj.ShowUnit:
qt = qt + q[2].replace("^2",u"\xb2") # square symbol
t = t.replace("$area",qt)
t = t.replace("$label",vobj.Object.Label)
if hasattr(vobj.Object,"Tag"):
t = t.replace("$tag",vobj.Object.Tag)
if hasattr(vobj.Object,"FinishFloor"):
t = t.replace("$floor",vobj.Object.FinishFloor)
if hasattr(vobj.Object,"FinishWalls"):
t = t.replace("$walls",vobj.Object.FinishWalls)
if hasattr(vobj.Object,"FinishCeiling"):
t = t.replace("$ceiling",vobj.Object.FinishCeiling)
if first:
text1.append(t.encode("utf8"))
else:
text2.append(t.encode("utf8"))
first = False
if text1:
self.text1.string.setValues(text1)
if text2:
self.text2.string.setValues(text2)
elif prop == "FontName":
if hasattr(self,"font") and hasattr(vobj,"FontName"):
self.font.name = str(vobj.FontName)
elif (prop == "FontSize"):
if hasattr(self,"font") and hasattr(vobj,"FontSize"):
self.font.size = vobj.FontSize.Value
elif (prop == "FirstLine"):
if hasattr(self,"header") and hasattr(vobj,"FontSize") and hasattr(vobj,"FirstLine"):
scale = vobj.FirstLine.Value/vobj.FontSize.Value
self.header.scaleFactor.setValue([scale,scale,scale])
elif prop == "TextColor":
if hasattr(self,"color") and hasattr(vobj,"TextColor"):
c = vobj.TextColor
self.color.rgb.setValue(c[0],c[1],c[2])
elif prop == "TextPosition":
if hasattr(self,"coords") and hasattr(self,"header") and hasattr(vobj,"TextPosition") and hasattr(vobj,"FirstLine"):
pos = self.getTextPosition(vobj)
self.coords.translation.setValue([pos.x,pos.y,pos.z])
up = vobj.FirstLine.Value * vobj.LineSpacing
self.header.translation.setValue([0,up,0])
elif prop == "LineSpacing":
if hasattr(self,"text1") and hasattr(self,"text2") and hasattr(vobj,"LineSpacing"):
self.text1.spacing = vobj.LineSpacing
self.text2.spacing = vobj.LineSpacing
self.onChanged(vobj,"TextPosition")
elif prop == "TextAlign":
if hasattr(self,"text1") and hasattr(self,"text2") and hasattr(vobj,"TextAlign"):
from pivy import coin
if vobj.TextAlign == "Center":
self.text1.justification = coin.SoAsciiText.CENTER
self.text2.justification = coin.SoAsciiText.CENTER
elif vobj.TextAlign == "Right":
self.text1.justification = coin.SoAsciiText.RIGHT
self.text2.justification = coin.SoAsciiText.RIGHT
else:
self.text1.justification = coin.SoAsciiText.LEFT
self.text2.justification = coin.SoAsciiText.LEFT
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Arch_Space',_CommandSpace())