Fixes #21364. `self.Type` should be set in `__init__` and `loads`, and not in `onDocumentRestored`. Additionally: fixed mistake in `loads` in ifc_objects.py.
610 lines
29 KiB
Python
610 lines
29 KiB
Python
# SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
# ***************************************************************************
|
|
# * *
|
|
# * Copyright (c) 2020 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 Curtain Wall"
|
|
__author__ = "Yorik van Havre"
|
|
__url__ = "https://www.freecad.org"
|
|
|
|
## @package ArchCurtainWall
|
|
# \ingroup ARCH
|
|
# \brief The Curtain Wall object and tools
|
|
#
|
|
# This module provides tools to build Curtain wall objects.
|
|
|
|
"""
|
|
Curtain wall tool
|
|
|
|
Abstract: Curtain walls need a surface to work on (base).
|
|
They then divide each face of that surface into quads,
|
|
using the face parameters grid.
|
|
|
|
The vertical lines can then receive one type of profile
|
|
(vertical mullions), the horizontal ones another
|
|
(horizontal mullions), and the quads a third (panels).
|
|
|
|
We then have two cases, depending on each quad: Either the
|
|
four corners of each quad are coplanar, in which case the
|
|
panel filling is rectangular, or they don't, in which case
|
|
the facet is triangulated and receives a third mullion
|
|
(diagonal mullion).
|
|
"""
|
|
|
|
import math
|
|
|
|
import FreeCAD
|
|
import ArchCommands
|
|
import ArchComponent
|
|
import DraftVecUtils
|
|
|
|
from draftutils import params
|
|
|
|
if FreeCAD.GuiUp:
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
import FreeCADGui
|
|
from draftutils.translate import translate
|
|
else:
|
|
# \cond
|
|
def translate(ctxt,txt):
|
|
return txt
|
|
def QT_TRANSLATE_NOOP(ctxt,txt):
|
|
return txt
|
|
# \endcond
|
|
|
|
ANGLETOLERANCE = 0.67 # vectors with angles below this are considered going in same dir
|
|
|
|
|
|
class CurtainWall(ArchComponent.Component):
|
|
|
|
|
|
"The curtain wall object"
|
|
|
|
def __init__(self,obj):
|
|
|
|
ArchComponent.Component.__init__(self,obj)
|
|
self.Type = "CurtainWall"
|
|
self.setProperties(obj)
|
|
obj.IfcType = "Curtain Wall"
|
|
|
|
def setProperties(self,obj):
|
|
|
|
pl = obj.PropertiesList
|
|
vsize = 50
|
|
hsize = 50
|
|
if not "Host" in pl:
|
|
obj.addProperty("App::PropertyLink","Host","CurtainWall",QT_TRANSLATE_NOOP("App::Property","An optional host object for this curtain wall"), locked=True)
|
|
if not "Height" in pl:
|
|
obj.addProperty("App::PropertyLength","Height","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The height of the curtain wall, if based on an edge"), locked=True)
|
|
obj.Height = params.get_param_arch("WallHeight")
|
|
if not "VerticalMullionNumber" in pl:
|
|
obj.addProperty("App::PropertyInteger","VerticalMullionNumber","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The number of vertical mullions"), locked=True)
|
|
obj.setEditorMode("VerticalMullionNumber",1)
|
|
if not "VerticalMullionAlignment" in pl:
|
|
obj.addProperty("App::PropertyBool","VerticalMullionAlignment","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","If the profile of the vertical mullions get aligned with the surface or not"), locked=True)
|
|
if not "VerticalSections" in pl:
|
|
obj.addProperty("App::PropertyInteger","VerticalSections","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The number of vertical sections of this curtain wall"), locked=True)
|
|
obj.VerticalSections = 1
|
|
if "VerticalMullionSize" in pl:
|
|
# obsolete
|
|
vsize = obj.VerticalMullionSize.Value
|
|
obj.removeProperty("VerticalMullionSize")
|
|
if not "VerticalMullionHeight" in pl:
|
|
obj.addProperty("App::PropertyLength","VerticalMullionHeight","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The height of the vertical mullions profile, if no profile is used"), locked=True)
|
|
obj.VerticalMullionHeight = vsize
|
|
if not "VerticalMullionWidth" in pl:
|
|
obj.addProperty("App::PropertyLength","VerticalMullionWidth","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The width of the vertical mullions profile, if no profile is used"), locked=True)
|
|
obj.VerticalMullionWidth = vsize
|
|
if not "VerticalMullionProfile" in pl:
|
|
obj.addProperty("App::PropertyLink","VerticalMullionProfile","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","A profile for vertical mullions (disables vertical mullion size)"), locked=True)
|
|
if not "HorizontalMullionNumber" in pl:
|
|
obj.addProperty("App::PropertyInteger","HorizontalMullionNumber","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The number of horizontal mullions"), locked=True)
|
|
obj.setEditorMode("HorizontalMullionNumber",1)
|
|
if not "HorizontalMullionAlignment" in pl:
|
|
obj.addProperty("App::PropertyBool","HorizontalMullionAlignment","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","If the profile of the horizontal mullions gets aligned with the surface or not"), locked=True)
|
|
if not "HorizontalSections" in pl:
|
|
obj.addProperty("App::PropertyInteger","HorizontalSections","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The number of horizontal sections of this curtain wall"), locked=True)
|
|
obj.HorizontalSections = 1
|
|
if "HorizontalMullionSize" in pl:
|
|
# obsolete
|
|
hsize = obj.HorizontalMullionSize.Value
|
|
obj.removeProperty("HorizontalMullionSize")
|
|
if not "HorizontalMullionHeight" in pl:
|
|
obj.addProperty("App::PropertyLength","HorizontalMullionHeight","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The height of the horizontal mullions profile, if no profile is used"), locked=True)
|
|
obj.HorizontalMullionHeight = hsize
|
|
if not "HorizontalMullionWidth" in pl:
|
|
obj.addProperty("App::PropertyLength","HorizontalMullionWidth","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The width of the horizontal mullions profile, if no profile is used"), locked=True)
|
|
obj.HorizontalMullionWidth = hsize
|
|
if not "HorizontalMullionProfile" in pl:
|
|
obj.addProperty("App::PropertyLink","HorizontalMullionProfile","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","A profile for horizontal mullions (disables horizontal mullion size)"), locked=True)
|
|
if not "DiagonalMullionNumber" in pl:
|
|
obj.addProperty("App::PropertyInteger","DiagonalMullionNumber","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The number of diagonal mullions"), locked=True)
|
|
obj.setEditorMode("DiagonalMullionNumber",1)
|
|
if not "DiagonalMullionSize" in pl:
|
|
obj.addProperty("App::PropertyLength","DiagonalMullionSize","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The size of the diagonal mullions, if any, if no profile is used"), locked=True)
|
|
obj.DiagonalMullionSize = 50
|
|
if not "DiagonalMullionProfile" in pl:
|
|
obj.addProperty("App::PropertyLink","DiagonalMullionProfile","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","A profile for diagonal mullions, if any (disables horizontal mullion size)"), locked=True)
|
|
if not "PanelNumber" in pl:
|
|
obj.addProperty("App::PropertyInteger","PanelNumber","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The number of panels"), locked=True)
|
|
obj.setEditorMode("PanelNumber",1)
|
|
if not "PanelThickness" in pl:
|
|
obj.addProperty("App::PropertyLength","PanelThickness","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The thickness of the panels"), locked=True)
|
|
obj.PanelThickness = 20
|
|
if not "SwapHorizontalVertical" in pl:
|
|
obj.addProperty("App::PropertyBool","SwapHorizontalVertical","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","Swaps horizontal and vertical lines"), locked=True)
|
|
if not "Refine" in pl:
|
|
obj.addProperty("App::PropertyBool","Refine","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","Perform subtractions between components so none overlap"), locked=True)
|
|
if not "CenterProfiles" in pl:
|
|
obj.addProperty("App::PropertyBool","CenterProfiles","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","Centers the profile over the edges or not"), locked=True)
|
|
obj.CenterProfiles = True
|
|
if not "VerticalDirection" in pl:
|
|
obj.addProperty("App::PropertyVector","VerticalDirection","CurtainWall",
|
|
QT_TRANSLATE_NOOP("App::Property","The vertical direction reference to be used by this object to deduce vertical/horizontal directions. Keep it close to the actual vertical direction of your curtain wall"), locked=True)
|
|
obj.VerticalDirection = FreeCAD.Vector(0,0,1)
|
|
if not "OverrideEdges" in pl: # PropertyStringList
|
|
obj.addProperty("App::PropertyStringList","OverrideEdges","CurtainWall",QT_TRANSLATE_NOOP("App::Property","Input are index numbers of edges of Base ArchSketch/Sketch geometries (in Edit mode). Selected edges are used to create the shape of this Arch Curtain Wall (instead of using all edges by default). [ENHANCED by ArchSketch] GUI 'Edit Curtain Wall' Tool is provided in external Add-on ('SketchArch') to let users to select the edges interactively. 'Toponaming-Tolerant' if ArchSketch is used in Base (and SketchArch Add-on is installed). Warning : Not 'Toponaming-Tolerant' if just Sketch is used. Property is ignored if Base ArchSketch provided the selected edges."), locked=True)
|
|
|
|
def onDocumentRestored(self,obj):
|
|
|
|
ArchComponent.Component.onDocumentRestored(self,obj)
|
|
self.setProperties(obj)
|
|
|
|
def loads(self,state):
|
|
|
|
self.Type = "CurtainWall"
|
|
|
|
def onChanged(self,obj,prop):
|
|
|
|
ArchComponent.Component.onChanged(self,obj,prop)
|
|
|
|
def execute(self,obj):
|
|
|
|
if self.clone(obj):
|
|
return
|
|
if not self.ensureBase(obj):
|
|
return
|
|
|
|
import Part
|
|
import DraftGeomUtils
|
|
|
|
pl = obj.Placement
|
|
|
|
# test properties
|
|
if not obj.Base:
|
|
FreeCAD.Console.PrintLog(obj.Label+": no base\n")
|
|
return
|
|
if not hasattr(obj.Base,"Shape"):
|
|
FreeCAD.Console.PrintLog(obj.Label+": invalid base\n")
|
|
return
|
|
if obj.VerticalMullionProfile:
|
|
if not hasattr(obj.VerticalMullionProfile,"Shape"):
|
|
FreeCAD.Console.PrintLog(obj.Label+": invalid vertical mullion profile\n")
|
|
return
|
|
if obj.HorizontalMullionProfile:
|
|
if not hasattr(obj.HorizontalMullionProfile,"Shape"):
|
|
FreeCAD.Console.PrintLog(obj.Label+": invalid horizontal mullion profile\n")
|
|
return
|
|
if obj.DiagonalMullionProfile:
|
|
if not hasattr(obj.DiagonalMullionProfile,"Shape"):
|
|
FreeCAD.Console.PrintLog(obj.Label+": invalid diagonal mullion profile\n")
|
|
return
|
|
|
|
facets = []
|
|
faces = []
|
|
|
|
curtainWallBaseShapeEdges = None
|
|
curtainWallEdges = None
|
|
if obj.Base.Shape.Faces:
|
|
faces = obj.Base.Shape.Faces
|
|
elif obj.Height.Value and obj.VerticalDirection.Length:
|
|
ext = FreeCAD.Vector(obj.VerticalDirection)
|
|
ext.normalize()
|
|
ext = ext.multiply(obj.Height.Value)
|
|
if hasattr(obj.Base, 'Proxy'):
|
|
if hasattr(obj.Base.Proxy, 'getCurtainWallBaseShapeEdgesInfo'):
|
|
curtainWallBaseShapeEdges = obj.Base.Proxy.getCurtainWallBaseShapeEdgesInfo(obj.Base)
|
|
# returned a {dict}
|
|
# get curtain wall edges (not wires); use original edges if getCurtainWallBaseShapeEdges() provided none
|
|
if curtainWallBaseShapeEdges: # would be false (none) if SketchArch Add-on is not installed, or base ArchSketch does not have the edges stored / input by user
|
|
curtainWallEdges = curtainWallBaseShapeEdges.get('curtainWallEdges')
|
|
elif obj.Base.isDerivedFrom("Sketcher::SketchObject"):
|
|
skGeom = obj.Base.GeometryFacadeList
|
|
skGeomEdges = []
|
|
skPlacement = obj.Base.Placement # Get Sketch's placement to restore later
|
|
for ig, geom in enumerate(skGeom):
|
|
if (not obj.OverrideEdges and not geom.Construction) or str(ig) in obj.OverrideEdges:
|
|
# support Line, Arc, Circle, Ellipse for Sketch
|
|
# as Base at the moment
|
|
if isinstance(geom.Geometry, (Part.LineSegment,
|
|
Part.Circle, Part.ArcOfCircle)):
|
|
skGeomEdgesI = geom.Geometry.toShape()
|
|
skGeomEdges.append(skGeomEdgesI)
|
|
curtainWallEdges = []
|
|
for edge in skGeomEdges:
|
|
edge.Placement = edge.Placement.multiply(skPlacement)
|
|
curtainWallEdges.append(edge)
|
|
if not curtainWallEdges:
|
|
curtainWallEdges = obj.Base.Shape.Edges
|
|
if curtainWallEdges:
|
|
faces = [edge.extrude(ext) for edge in curtainWallEdges]
|
|
if not faces:
|
|
FreeCAD.Console.PrintLog(obj.Label+": unable to build base faces\n")
|
|
return
|
|
|
|
# subdivide the faces into quads
|
|
for face in faces:
|
|
|
|
fp = face.ParameterRange
|
|
|
|
# guessing horizontal/vertical directions
|
|
vdir = obj.VerticalDirection
|
|
if not vdir.Length:
|
|
vdir = FreeCAD.Vector(0,0,1)
|
|
vdir.normalize()
|
|
|
|
# Check if face if vertical in the first place
|
|
# Fix issue in 'Curtain wall vertical/horizontal mullion mix-up'
|
|
# https://github.com/FreeCAD/FreeCAD/issues/21845
|
|
#
|
|
face_plane = face.findPlane() # Curve face (surface) seems return no Plane
|
|
if face_plane:
|
|
if -0.001 < face_plane.Axis[2] < 0.001: # i.e. face is vertical (normal pointing horizon)
|
|
faceVert = True
|
|
# Support 'Swap Horizontal Vertical'
|
|
# See issue 'Swap Horizontal Vertical does not work'
|
|
# https://github.com/FreeCAD/FreeCAD/issues/21866
|
|
if obj.SwapHorizontalVertical:
|
|
vertsec = obj.HorizontalSections
|
|
horizsec = obj.VerticalSections
|
|
else:
|
|
vertsec = obj.VerticalSections
|
|
horizsec = obj.HorizontalSections
|
|
else:
|
|
faceVert = False
|
|
|
|
# Guess algorithm if face is not vertical
|
|
if not faceVert:
|
|
# TODO 2025.6.15 : Need a more robust algorithm below
|
|
# See issue 'Curtain wall vertical/horizontal mullion mix-up'
|
|
# https://github.com/FreeCAD/FreeCAD/issues/21845
|
|
# Partially improved by checking 'if face is vertical' above
|
|
#
|
|
basevector = face.valueAt(fp[1],fp[3]).sub(face.valueAt(fp[0],fp[2]))
|
|
bv_angle = basevector.getAngle(vdir)
|
|
if (bv_angle <= math.pi/2+ANGLETOLERANCE) and (bv_angle >= math.pi/2-ANGLETOLERANCE):
|
|
facedir = True
|
|
if obj.SwapHorizontalVertical:
|
|
vertsec = obj.HorizontalSections
|
|
horizsec = obj.VerticalSections
|
|
else:
|
|
vertsec = obj.VerticalSections
|
|
horizsec = obj.HorizontalSections
|
|
else:
|
|
facedir = False
|
|
if obj.SwapHorizontalVertical:
|
|
vertsec = obj.VerticalSections
|
|
horizsec = obj.HorizontalSections
|
|
else:
|
|
vertsec = obj.HorizontalSections
|
|
horizsec = obj.VerticalSections
|
|
|
|
hstep = (fp[1]-fp[0])
|
|
if vertsec:
|
|
hstep = hstep/vertsec
|
|
vstep = (fp[3]-fp[2])
|
|
if horizsec:
|
|
vstep = vstep/horizsec
|
|
|
|
# construct facets
|
|
for i in range(vertsec or 1):
|
|
for j in range(horizsec or 1):
|
|
p0 = face.valueAt(fp[0]+i*hstep,fp[2]+j*vstep)
|
|
p1 = face.valueAt(fp[0]+(i+1)*hstep,fp[2]+j*vstep)
|
|
p2 = face.valueAt(fp[0]+(i+1)*hstep,fp[2]+(j+1)*vstep)
|
|
p3 = face.valueAt(fp[0]+i*hstep,fp[2]+(j+1)*vstep)
|
|
facet = Part.Face(Part.makePolygon([p0,p1,p2,p3,p0]))
|
|
facets.append(facet)
|
|
|
|
if not facets:
|
|
FreeCAD.Console.PrintLog(obj.Label+": failed to subdivide shape\n")
|
|
return
|
|
|
|
baseshape = Part.makeShell(facets)
|
|
|
|
# make edge/normal relation table
|
|
edgetable = {}
|
|
for face in baseshape.Faces:
|
|
for edge in face.Edges:
|
|
ec = edge.hashCode()
|
|
if ec in edgetable:
|
|
edgetable[ec].append(face)
|
|
else:
|
|
edgetable[ec] = [face]
|
|
self.edgenormals = {}
|
|
for ec,faces in edgetable.items():
|
|
if len(faces) == 1:
|
|
self.edgenormals[ec] = faces[0].normalAt(0,0)
|
|
else:
|
|
n = faces[0].normalAt(0,0).add(faces[1].normalAt(0,0))
|
|
if n.Length > 0.001:
|
|
n.normalize()
|
|
else:
|
|
# adjacent faces have same normals
|
|
n = faces[0].normalAt(0,0)
|
|
self.edgenormals[ec] = n
|
|
|
|
# sort edges between vertical/horizontal
|
|
hedges = []
|
|
vedges = []
|
|
for edge in baseshape.Edges:
|
|
v = edge.Vertexes[-1].Point.sub(edge.Vertexes[0].Point)
|
|
a = v.getAngle(vdir)
|
|
if (a <= math.pi/2+ANGLETOLERANCE) and (a >= math.pi/2-ANGLETOLERANCE):
|
|
hedges.append(edge)
|
|
else:
|
|
vedges.append(edge)
|
|
|
|
# construct vertical mullions
|
|
vmullions = []
|
|
vprofile = self.getMullionProfile(obj,"Vertical")
|
|
if vprofile and vertsec:
|
|
for vedge in vedges:
|
|
vn = self.edgenormals[vedge.hashCode()]
|
|
if (vn.x != 0) or (vn.y != 0):
|
|
avn = FreeCAD.Vector(vn.x,vn.y,0)
|
|
rot = FreeCAD.Rotation(FreeCAD.Vector(0,-1,0),avn)
|
|
else:
|
|
rot = FreeCAD.Rotation()
|
|
if obj.VerticalMullionAlignment:
|
|
ev = vedge.Vertexes[-1].Point.sub(vedge.Vertexes[0].Point)
|
|
rot = FreeCAD.Rotation(FreeCAD.Vector(1,0,0),ev).multiply(rot)
|
|
vmullions.append(self.makeMullion(vedge,vprofile,rot,obj.CenterProfiles))
|
|
|
|
# construct horizontal mullions
|
|
hmullions = []
|
|
hprofile = self.getMullionProfile(obj,"Horizontal")
|
|
if hprofile and horizsec:
|
|
for hedge in hedges:
|
|
rot = FreeCAD.Rotation(FreeCAD.Vector(0,1,0),-90)
|
|
vn = self.edgenormals[hedge.hashCode()]
|
|
if (vn.x != 0) or (vn.y != 0):
|
|
avn = FreeCAD.Vector(vn.x,vn.y,0)
|
|
rot = FreeCAD.Rotation(FreeCAD.Vector(0,-1,0),avn).multiply(rot)
|
|
if obj.HorizontalMullionAlignment:
|
|
rot = FreeCAD.Rotation(avn,vn).multiply(rot)
|
|
hmullions.append(self.makeMullion(hedge,hprofile,rot,obj.CenterProfiles))
|
|
|
|
# construct panels
|
|
panels = []
|
|
dedges = []
|
|
if obj.PanelThickness.Value:
|
|
for face in baseshape.Faces:
|
|
verts = [v.Point for v in face.OuterWire.OrderedVertexes]
|
|
if DraftGeomUtils.isPlanar(verts):
|
|
panel = self.makePanel(verts,obj.PanelThickness.Value)
|
|
panels.append(panel)
|
|
elif len(verts) == 4:
|
|
verts1 = [verts[0],verts[1],verts[2]]
|
|
panel = self.makePanel(verts1,obj.PanelThickness.Value)
|
|
panels.append(panel)
|
|
verts2 = [verts[0],verts[2],verts[3]]
|
|
panel = self.makePanel(verts2,obj.PanelThickness.Value)
|
|
panels.append(panel)
|
|
dedges.append(Part.makeLine(verts[0],verts[2]))
|
|
|
|
# construct diagonal mullions
|
|
dmullions = []
|
|
if dedges:
|
|
n = (dedges[0].Vertexes[-1].Point.sub(dedges[0].Point))
|
|
dprofile = self.getMullionProfile(obj,"Diagonal")
|
|
if dprofile:
|
|
for dedge in dedges:
|
|
rot = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),dedge.Vertexes[-1].Point.sub(dedge.Vertexes[0].Point))
|
|
dmullions.append(self.makeMullion(dedge,dprofile,rot,obj.CenterProfiles))
|
|
|
|
# perform subtractions
|
|
if obj.Refine:
|
|
subvmullion = None
|
|
subhmullion = None
|
|
subdmullion = None
|
|
if vmullions:
|
|
subvmullion = vmullions[0].copy()
|
|
for m in vmullions[1:]:
|
|
subvmullion = subvmullion.fuse(m)
|
|
if hmullions:
|
|
subhmullion = hmullions[0].copy()
|
|
for m in hmullions[1:]:
|
|
subhmullion = subhmullion.fuse(m)
|
|
if dmullions:
|
|
subdmullion = dmullions[0].copy()
|
|
for m in dmullions[1:]:
|
|
subdmullion = subdmullion.fuse(m)
|
|
if subvmullion:
|
|
hmullions = [m.cut(subvmullion) for m in hmullions]
|
|
if subhmullion:
|
|
dmullions = [m.cut(subvmullion) for m in dmullions]
|
|
dmullions = [m.cut(subhmullion) for m in dmullions]
|
|
panels = [m.cut(subvmullion) for m in panels]
|
|
panels = [m.cut(subhmullion) for m in panels]
|
|
if subdmullion:
|
|
panels = [m.cut(subdmullion) for m in panels]
|
|
|
|
# mount shape
|
|
obj.VerticalMullionNumber = len(vmullions)
|
|
obj.HorizontalMullionNumber = len(hmullions)
|
|
obj.DiagonalMullionNumber = len(dmullions)
|
|
obj.PanelNumber = len(panels)
|
|
shape = Part.makeCompound(vmullions+hmullions+dmullions+panels)
|
|
shape = self.processSubShapes(obj,shape,pl)
|
|
self.applyShape(obj,shape,pl)
|
|
|
|
def makePanel(self,verts,thickness):
|
|
|
|
"""creates a panel from face points and thickness"""
|
|
|
|
import Part
|
|
|
|
panel = Part.Face(Part.makePolygon(verts+[verts[0]]))
|
|
n = panel.normalAt(0,0)
|
|
n.multiply(thickness)
|
|
panel = panel.extrude(n)
|
|
return panel
|
|
|
|
def makeMullion(self,edge,profile,rotation,recenter=False):
|
|
|
|
"""creates a mullions from an edge and a profile"""
|
|
|
|
center = FreeCAD.Vector(0,0,0)
|
|
if recenter:
|
|
if hasattr(profile,"CenterOfMass"):
|
|
center = profile.CenterOfMass
|
|
elif hasattr(profile,"BoundBox"):
|
|
center = profile.BoundBox.Center
|
|
p0 = edge.Vertexes[0].Point
|
|
p1 = edge.Vertexes[-1].Point
|
|
mullion = profile.copy()
|
|
if rotation:
|
|
mullion = mullion.rotate(center,rotation.Axis,math.degrees(rotation.Angle))
|
|
mullion = mullion.translate(p0.sub(center))
|
|
mullion = mullion.extrude(p1.sub(p0))
|
|
return mullion
|
|
|
|
def getMullionProfile(self,obj,direction):
|
|
|
|
"""returns a profile shape already properly oriented, ready for extrude"""
|
|
|
|
import Part
|
|
import DraftGeomUtils
|
|
|
|
prof = getattr(obj,direction+"MullionProfile")
|
|
proh = getattr(obj,direction+"MullionHeight").Value
|
|
prow = getattr(obj,direction+"MullionWidth").Value
|
|
if prof:
|
|
profile = prof.Shape.copy()
|
|
else:
|
|
if (not proh) or (not prow):
|
|
return None
|
|
profile = Part.Face(Part.makePlane(prow,proh,FreeCAD.Vector(-prow/2,-proh/2,0)))
|
|
return profile
|
|
|
|
def getProjectedLength(self,v,ref):
|
|
|
|
"""gets a signed length from projecting a vector on another"""
|
|
|
|
proj = DraftVecUtils.project(v,ref)
|
|
if proj.getAngle(ref) < 1:
|
|
return proj.Length
|
|
else:
|
|
return -proj.Length
|
|
|
|
|
|
class ViewProviderCurtainWall(ArchComponent.ViewProviderComponent):
|
|
|
|
|
|
"A View Provider for the CurtainWall object"
|
|
|
|
def __init__(self,vobj):
|
|
|
|
ArchComponent.ViewProviderComponent.__init__(self,vobj)
|
|
|
|
def getIcon(self):
|
|
|
|
import Arch_rc
|
|
return ":/icons/Arch_CurtainWall_Tree.svg"
|
|
|
|
def updateData(self,obj,prop):
|
|
|
|
if prop == "Shape":
|
|
self.colorize(obj,force=True)
|
|
|
|
def onChanged(self,vobj,prop):
|
|
|
|
if (prop in ["DiffuseColor","Transparency"]) and vobj.Object:
|
|
self.colorize(vobj.Object)
|
|
elif prop == "ShapeColor":
|
|
self.colorize(vobj.Object,force=True)
|
|
ArchComponent.ViewProviderComponent.onChanged(self,vobj,prop)
|
|
|
|
def colorize(self,obj,force=False):
|
|
|
|
"setting different part colors"
|
|
|
|
if not obj.Shape or not obj.Shape.Solids:
|
|
return
|
|
if not obj.ViewObject:
|
|
return
|
|
basecolor = obj.ViewObject.ShapeColor
|
|
basetransparency = obj.ViewObject.Transparency/100.0
|
|
panelcolor = ArchCommands.getDefaultColor("WindowGlass")
|
|
paneltransparency = 0.7
|
|
if hasattr(obj,"Material") and obj.Material and hasattr(obj.Material,"Materials"):
|
|
if obj.Material.Names:
|
|
if "Frame" in obj.Material.Names:
|
|
mat = obj.Material.Materials[obj.Material.Names.index("Frame")]
|
|
if ('DiffuseColor' in mat.Material) and ("(" in mat.Material['DiffuseColor']):
|
|
basecolor = tuple([float(f) for f in mat.Material['DiffuseColor'].strip("()").split(",")])
|
|
if "Glass panel" in obj.Material.Names:
|
|
mat = obj.Material.Materials[obj.Material.Names.index("Glass panel")]
|
|
if ('DiffuseColor' in mat.Material) and ("(" in mat.Material['DiffuseColor']):
|
|
panelcolor = tuple([float(f) for f in mat.Material['DiffuseColor'].strip("()").split(",")])
|
|
if ('Transparency' in mat.Material):
|
|
paneltransparency = float(mat.Material['Transparency'])/100.0
|
|
elif "Solid panel" in obj.Material.Names:
|
|
mat = obj.Material.Materials[obj.Material.Names.index("Solid panel")]
|
|
if ('DiffuseColor' in mat.Material) and ("(" in mat.Material['DiffuseColor']):
|
|
panelcolor = tuple([float(f) for f in mat.Material['DiffuseColor'].strip("()").split(",")])
|
|
paneltransparency = 0
|
|
basecolor = basecolor[:3]+(1.0 - basetransparency,)
|
|
panelcolor = panelcolor[:3]+(1.0 - paneltransparency,)
|
|
colors = []
|
|
nmullions = obj.VerticalMullionNumber + obj.HorizontalMullionNumber + obj.DiagonalMullionNumber
|
|
for i,solid in enumerate(obj.Shape.Solids):
|
|
for _ in solid.Faces:
|
|
if i < nmullions:
|
|
colors.append(basecolor)
|
|
else:
|
|
colors.append(panelcolor)
|
|
if self.areDifferentColors(colors,obj.ViewObject.DiffuseColor) or force:
|
|
obj.ViewObject.DiffuseColor = colors
|