Files
create/src/Mod/BIM/ArchSpace.py
Paul Lee 238bd30cf1 [ArchSpace] Improve Space Creation and ensureBase
Refer to discussion at -
https://github.com/FreeCAD/FreeCAD/issues/18917

Like Wall and Stairs, Space should do without Base. Base validity tested in execute() prevented the desired and documented behaviour.

EnsureBase() is remarked out in execute() and to be run in getShape(). With this improvement, if there is no Base, or Base is not valid, Space would be created if it could be defined by obj.Boundaries.
2025-01-10 10:44:48 +01:00

783 lines
32 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__ = "https://www.freecad.org"
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"
]
ConditioningTypes = [
"Unconditioned",
"Heated",
"Cooled",
"HeatedAndCooled",
"Vented",
"NaturallyVentedOnly"
]
AreaCalculationType = [
"XY-plane projection",
"At Center of Mass"
]
import FreeCAD
import ArchComponent
import ArchCommands
import Draft
from draftutils import params
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtCore, QtGui
from draftutils.translate import translate
from PySide.QtCore import QT_TRANSLATE_NOOP
else:
# \cond
def translate(ctxt,txt):
return txt
def QT_TRANSLATE_NOOP(ctxt,txt):
return txt
# \endcond
## @package ArchSpace
# \ingroup ARCH
# \brief The Space object and tools
#
# This module provides tools to build Space objects.
# Spaces define an open volume inside or outside a
# building, ie. a room.
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 _Space(ArchComponent.Component):
"A space object"
def __init__(self,obj):
ArchComponent.Component.__init__(self,obj)
self.setProperties(obj)
obj.IfcType = "Space"
obj.CompositionType = "ELEMENT"
def setProperties(self,obj):
pl = obj.PropertiesList
if not "Boundaries" in pl:
obj.addProperty("App::PropertyLinkSubList","Boundaries", "Space",QT_TRANSLATE_NOOP("App::Property","The objects that make the boundaries of this space object"))
if not "Area" in pl:
obj.addProperty("App::PropertyArea", "Area", "Space",QT_TRANSLATE_NOOP("App::Property","Identical to Horizontal Area"))
if not "FinishFloor" in pl:
obj.addProperty("App::PropertyString", "FinishFloor", "Space",QT_TRANSLATE_NOOP("App::Property","The finishing of the floor of this space"))
if not "FinishWalls" in pl:
obj.addProperty("App::PropertyString", "FinishWalls", "Space",QT_TRANSLATE_NOOP("App::Property","The finishing of the walls of this space"))
if not "FinishCeiling" in pl:
obj.addProperty("App::PropertyString", "FinishCeiling", "Space",QT_TRANSLATE_NOOP("App::Property","The finishing of the ceiling of this space"))
if not "Group" in pl:
obj.addProperty("App::PropertyLinkList", "Group", "Space",QT_TRANSLATE_NOOP("App::Property","Objects that are included inside this space, such as furniture"))
if not "SpaceType" in pl:
obj.addProperty("App::PropertyEnumeration","SpaceType", "Space",QT_TRANSLATE_NOOP("App::Property","The type of this space"))
obj.SpaceType = SpaceTypes
if not "FloorThickness" in pl:
obj.addProperty("App::PropertyLength", "FloorThickness","Space",QT_TRANSLATE_NOOP("App::Property","The thickness of the floor finish"))
if not "NumberOfPeople" in pl:
obj.addProperty("App::PropertyInteger", "NumberOfPeople","Space",QT_TRANSLATE_NOOP("App::Property","The number of people who typically occupy this space"))
if not "LightingPower" in pl:
obj.addProperty("App::PropertyFloat", "LightingPower", "Space",QT_TRANSLATE_NOOP("App::Property","The electric power needed to light this space in Watts"))
if not "EquipmentPower" in pl:
obj.addProperty("App::PropertyFloat", "EquipmentPower","Space",QT_TRANSLATE_NOOP("App::Property","The electric power needed by the equipment of this space in Watts"))
if not "AutoPower" in pl:
obj.addProperty("App::PropertyBool", "AutoPower", "Space",QT_TRANSLATE_NOOP("App::Property","If True, Equipment Power will be automatically filled by the equipment included in this space"))
if not "Conditioning" in pl:
obj.addProperty("App::PropertyEnumeration","Conditioning", "Space",QT_TRANSLATE_NOOP("App::Property","The type of air conditioning of this space"))
obj.Conditioning = ConditioningTypes
if not "Internal" in pl:
obj.addProperty("App::PropertyBool", "Internal", "Space",QT_TRANSLATE_NOOP("App::Property","Specifies if this space is internal or external"))
obj.Internal = True
if not "AreaCalculationType" in pl:
obj.addProperty("App::PropertyEnumeration", "AreaCalculationType", "Space",QT_TRANSLATE_NOOP("App::Property","Defines the calculation type for the horizontal area and its perimeter length"))
obj.AreaCalculationType = AreaCalculationType
self.Type = "Space"
def onDocumentRestored(self,obj):
ArchComponent.Component.onDocumentRestored(self,obj)
self.setProperties(obj)
def execute(self,obj):
if self.clone(obj):
return
# Space can do without Base. Base validity is tested in getShape() code below.
# Remarked out ensureBase() below
#if not self.ensureBase(obj):
# return
self.getShape(obj)
def onChanged(self,obj,prop):
if prop == "Group":
if hasattr(obj,"EquipmentPower"):
if obj.AutoPower:
p = 0
for o in Draft.getObjectsOfType(Draft.get_group_contents(obj.Group, addgroups=True),
"Equipment"):
if hasattr(o,"EquipmentPower"):
p += o.EquipmentPower
if p != obj.EquipmentPower:
obj.EquipmentPower = p
elif prop == "Zone":
if obj.Zone:
if obj.Zone.ViewObject:
if hasattr(obj.Zone.ViewObject,"Proxy"):
if hasattr(obj.Zone.ViewObject.Proxy,"claimChildren"):
obj.Zone.ViewObject.Proxy.claimChildren()
if hasattr(obj,"Area"):
obj.setEditorMode('Area',1)
ArchComponent.Component.onChanged(self,obj,prop)
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 addObject(self,obj,child):
"Adds an object to this Space"
if not child in obj.Group:
g = obj.Group
g.append(child)
obj.Group = g
def getShape(self,obj):
"computes a shape from a base shape and/or boundary faces"
import Part
shape = None
faces = []
pl = obj.Placement
#print("starting compute")
# 1: if we have a base shape, we use it
# Check if there is obj.Base and its validity to proceed
if self.ensureBase(obj):
if obj.Base.Shape.Solids:
shape = obj.Base.Shape.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 hasattr(b[0],'Shape'):
if not bb:
bb = b[0].Shape.BoundBox
else:
bb.add(b[0].Shape.BoundBox)
if not bb:
# compute area even if we are not calculating the shape
if obj.Shape and obj.Shape.Solids:
if hasattr(obj.Area,"Value"):
a = self.getArea(obj)
if obj.Area.Value != a:
obj.Area = a
return
shape = Part.makeBox(bb.XLength,bb.YLength,bb.ZLength,FreeCAD.Vector(bb.XMin,bb.YMin,bb.ZMin))
#print("created shape from boundbox")
# 3: identifying boundary faces
goodfaces = []
for b in obj.Boundaries:
if hasattr(b[0],'Shape'):
for sub in b[1]:
if "Face" in sub:
fn = int(sub[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]
self.applyShape(obj,shape,pl)
if hasattr(obj.HorizontalArea,"Value"):
if hasattr(obj,"AreaCalculationType"):
if obj.AreaCalculationType == "At Center of Mass":
a = self.getArea(obj)
obj.HorizontalArea = a
if hasattr(obj,"Area"):
obj.Area = obj.HorizontalArea
return
print("Arch: error computing space boundary for",obj.Label)
def getArea(self,obj,notouch=False):
"returns the horizontal area at the center of the space"
self.face = self.getFootprint(obj)
if self.face:
if not notouch:
if hasattr(obj,"PerimeterLength"):
if self.face.OuterWire.Length != obj.PerimeterLength.Value:
obj.PerimeterLength = self.face.OuterWire.Length
return self.face.Area
else:
return 0
def getFootprint(self,obj):
"returns a face that represents the footprint of this space at the center of mass"
import Part
import DraftGeomUtils
if not hasattr(obj.Shape,"CenterOfMass"):
return None
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 = Part.__sortEdges__(e.Edges)
w = Part.Wire(e)
dv = FreeCAD.Vector(obj.Shape.CenterOfMass.x,obj.Shape.CenterOfMass.y,obj.Shape.BoundBox.ZMin)
dv = dv.sub(obj.Shape.CenterOfMass)
w.translate(dv)
return Part.Face(w)
except Part.OCCError:
return None
class _ViewProviderSpace(ArchComponent.ViewProviderComponent):
"A View Provider for Section Planes"
def __init__(self,vobj):
ArchComponent.ViewProviderComponent.__init__(self,vobj)
self.setProperties(vobj)
vobj.Transparency = params.get_param_arch("defaultSpaceTransparency")
vobj.LineWidth = params.get_param_view("DefaultShapeLineWidth")
vobj.LineColor = ArchCommands.getDefaultColor("Space")
vobj.DrawStyle = ["Solid","Dashed","Dotted","Dashdot"][params.get_param_arch("defaultSpaceStyle")]
def setProperties(self,vobj):
pl = vobj.PropertiesList
if not "Text" in pl:
vobj.addProperty("App::PropertyStringList", "Text", "Space",QT_TRANSLATE_NOOP("App::Property","The text to show. Use $area, $label, $tag, $longname, $description and for finishes $floor, $walls, $ceiling to insert the respective data"))
vobj.Text = ["$label","$area"]
if not "FontName" in pl:
vobj.addProperty("App::PropertyFont", "FontName", "Space",QT_TRANSLATE_NOOP("App::Property","The name of the font"))
vobj.FontName = params.get_param("textfont")
if not "TextColor" in pl:
vobj.addProperty("App::PropertyColor", "TextColor", "Space",QT_TRANSLATE_NOOP("App::Property","The color of the area text"))
vobj.TextColor = (0.0,0.0,0.0,1.0)
if not "FontSize" in pl:
vobj.addProperty("App::PropertyLength", "FontSize", "Space",QT_TRANSLATE_NOOP("App::Property","The size of the text font"))
vobj.FontSize = params.get_param("textheight") * params.get_param("DefaultAnnoScaleMultiplier")
if not "FirstLine" in pl:
vobj.addProperty("App::PropertyLength", "FirstLine", "Space",QT_TRANSLATE_NOOP("App::Property","The size of the first line of text"))
vobj.FirstLine = params.get_param("textheight") * params.get_param("DefaultAnnoScaleMultiplier")
if not "LineSpacing" in pl:
vobj.addProperty("App::PropertyFloat", "LineSpacing", "Space",QT_TRANSLATE_NOOP("App::Property","The space between the lines of text"))
vobj.LineSpacing = 1.0
if not "TextPosition" in pl:
vobj.addProperty("App::PropertyVectorDistance","TextPosition","Space",QT_TRANSLATE_NOOP("App::Property","The position of the text. Leave (0,0,0) for automatic position"))
if not "TextAlign" in pl:
vobj.addProperty("App::PropertyEnumeration", "TextAlign", "Space",QT_TRANSLATE_NOOP("App::Property","The justification of the text"))
vobj.TextAlign = ["Left","Center","Right"]
vobj.TextAlign = "Center"
if not "Decimals" in pl:
vobj.addProperty("App::PropertyInteger", "Decimals", "Space",QT_TRANSLATE_NOOP("App::Property","The number of decimals to use for calculated texts"))
vobj.Decimals = params.get_param("dimPrecision")
if not "ShowUnit" in pl:
vobj.addProperty("App::PropertyBool", "ShowUnit", "Space",QT_TRANSLATE_NOOP("App::Property","Show the unit suffix"))
vobj.ShowUnit = params.get_param("showUnit")
def onDocumentRestored(self,vobj):
self.setProperties(vobj)
def getIcon(self):
import Arch_rc
if hasattr(self,"Object"):
if hasattr(self.Object,"CloneOf"):
if self.Object.CloneOf:
return ":/icons/Arch_Space_Clone.svg"
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()
self.label = coin.SoSwitch()
sep = coin.SoSeparator()
self.label.whichChild = 0
sep.addChild(self.coords)
sep.addChild(self.color)
sep.addChild(self.font)
sep.addChild(self.text2)
sep.addChild(self.header)
sep.addChild(self.text1)
self.label.addChild(sep)
vobj.Annotation.addChild(self.label)
self.onChanged(vobj,"TextColor")
self.onChanged(vobj,"FontSize")
self.onChanged(vobj,"FirstLine")
self.onChanged(vobj,"LineSpacing")
self.onChanged(vobj,"FontName")
self.Object = vobj.Object
# footprint mode
self.fmat = coin.SoMaterial()
self.fcoords = coin.SoCoordinate3()
self.fset = coin.SoIndexedFaceSet()
fhints = coin.SoShapeHints()
fhints.vertexOrdering = fhints.COUNTERCLOCKWISE
sep = coin.SoSeparator()
sep.addChild(self.fmat)
sep.addChild(self.fcoords)
sep.addChild(fhints)
sep.addChild(self.fset)
vobj.RootNode.addChild(sep)
def updateData(self,obj,prop):
if prop in ["Shape","Label","Tag","Area"]:
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.Object.Placement.multVec(vobj.TextPosition)
# placement's displacement will be already added by the coin node
pos = vobj.Object.Placement.inverse().multVec(pos)
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.Value,Units.Area).getUserPreferred()
qt = vobj.Object.Area.Value/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 hasattr(vobj.Object,"LongName"):
t = t.replace("$longname",vobj.Object.LongName)
if hasattr(vobj.Object,"Description"):
t = t.replace("$description",vobj.Object.Description)
if first:
text1.append(t)
else:
text2.append(t)
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
if hasattr(vobj,"FirstLine"):
scale = vobj.FirstLine.Value/vobj.FontSize.Value
self.header.scaleFactor.setValue([scale,scale,scale])
self.onChanged(vobj, "TextPosition")
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])
self.onChanged(vobj, "TextPosition")
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+0.01]) # adding small z offset to separate from bottom face
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
elif prop == "Visibility":
if vobj.Visibility:
self.label.whichChild = 0
else:
self.label.whichChild = -1
elif prop == "Transparency":
if hasattr(vobj,"DisplayMode"):
vobj.DisplayMode = "Wireframe" if vobj.Transparency == 100 else "Flat Lines"
def setEdit(self, vobj, mode):
if mode != 0:
return None
taskd = SpaceTaskPanel()
taskd.obj = self.Object
taskd.update()
taskd.updateBoundaries()
FreeCADGui.Control.showDialog(taskd)
return True
def getDisplayModes(self,vobj):
modes = ArchComponent.ViewProviderComponent.getDisplayModes(self,vobj)+["Footprint"]
return modes
def setDisplayMode(self,mode):
self.fset.coordIndex.deleteValues(0)
self.fcoords.point.deleteValues(0)
if mode == "Footprint":
if hasattr(self,"Object"):
face = self.Object.Proxy.getFootprint(self.Object)
if face:
verts = []
fdata = []
idx = 0
tri = face.tessellate(1)
for v in tri[0]:
verts.append([v.x,v.y,v.z])
for f in tri[1]:
fdata.extend([f[0]+idx,f[1]+idx,f[2]+idx,-1])
idx += len(tri[0])
self.fcoords.point.setValues(verts)
self.fset.coordIndex.setValues(0,len(fdata),fdata)
return "Points"
return ArchComponent.ViewProviderComponent.setDisplayMode(self,mode)
class SpaceTaskPanel(ArchComponent.ComponentTaskPanel):
"A modified version of the Arch component task panel"
def __init__(self):
ArchComponent.ComponentTaskPanel.__init__(self)
self.editButton = QtGui.QPushButton(self.form)
self.editButton.setObjectName("editButton")
self.editButton.setIcon(QtGui.QIcon(":/icons/Draft_Edit.svg"))
self.grid.addWidget(self.editButton, 4, 0, 1, 2)
self.editButton.setText(QtGui.QApplication.translate("Arch", "Set text position", None))
QtCore.QObject.connect(self.editButton, QtCore.SIGNAL("clicked()"), self.setTextPos)
boundLabel = QtGui.QLabel(self.form)
self.grid.addWidget(boundLabel, 5, 0, 1, 2)
boundLabel.setText(QtGui.QApplication.translate("Arch", "Space boundaries", None))
self.boundList = QtGui.QListWidget(self.form)
self.grid.addWidget(self.boundList, 6, 0, 1, 2)
self.addCompButton = QtGui.QPushButton(self.form)
self.addCompButton.setObjectName("addCompButton")
self.addCompButton.setIcon(QtGui.QIcon(":/icons/Arch_Add.svg"))
self.grid.addWidget(self.addCompButton, 7, 0, 1, 1)
self.addCompButton.setText(QtGui.QApplication.translate("Arch", "Add", None))
QtCore.QObject.connect(self.addCompButton, QtCore.SIGNAL("clicked()"), self.addBoundary)
self.delCompButton = QtGui.QPushButton(self.form)
self.delCompButton.setObjectName("delCompButton")
self.delCompButton.setIcon(QtGui.QIcon(":/icons/Arch_Remove.svg"))
self.grid.addWidget(self.delCompButton, 7, 1, 1, 1)
self.delCompButton.setText(QtGui.QApplication.translate("Arch", "Remove", None))
QtCore.QObject.connect(self.delCompButton, QtCore.SIGNAL("clicked()"), self.delBoundary)
def updateBoundaries(self):
self.boundList.clear()
if self.obj:
for b in self.obj.Boundaries:
s = b[0].Label
for n in b[1]:
s += ", " + n
it = QtGui.QListWidgetItem(s)
it.setToolTip(b[0].Name)
self.boundList.addItem(it)
def setTextPos(self):
FreeCADGui.runCommand("Draft_Edit")
def addBoundary(self):
if self.obj:
if FreeCADGui.Selection.getSelectionEx():
self.obj.Proxy.addSubobjects(self.obj,FreeCADGui.Selection.getSelectionEx())
self.updateBoundaries()
def delBoundary(self):
if self.boundList.currentRow() >= 0:
it = self.boundList.item(self.boundList.currentRow())
if it and self.obj:
on = it.toolTip()
bounds = self.obj.Boundaries
for b in bounds:
if b[0].Name == on:
bounds.remove(b)
break
self.obj.Boundaries = bounds
self.updateBoundaries()