* Add BIM workbench to .pre-commit-config.yaml * pre-commit: ignore translations * pre-commit: add additional ignore pattern * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
796 lines
32 KiB
Python
796 lines
32 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()
|
|
|
|
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
|
|
else:
|
|
# Curve extruded face (surface) seems return no Plane - need more robust algorithm
|
|
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
|