FreeCAD / src /Mod /BIM /ArchFrame.py
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2013 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 Frame"
__author__ = "Yorik van Havre"
__url__ = "https://www.freecad.org"
## @package ArchFrame
# \ingroup ARCH
# \brief The Frame object and tools
#
# This module provides tools to build Frame objects.
# Frames are objects made of a profile and an object with
# edges along which the profile gets extruded
import FreeCAD
import ArchComponent
import Draft
import DraftVecUtils
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
class _Frame(ArchComponent.Component):
"A parametric frame object"
def __init__(self, obj):
ArchComponent.Component.__init__(self, obj)
self.Type = "Frame"
self.setProperties(obj)
obj.IfcType = "Railing"
def setProperties(self, obj):
pl = obj.PropertiesList
if not "Profile" in pl:
obj.addProperty(
"App::PropertyLink",
"Profile",
"Frame",
QT_TRANSLATE_NOOP("App::Property", "The profile used to build this frame"),
locked=True,
)
if not "Align" in pl:
obj.addProperty(
"App::PropertyBool",
"Align",
"Frame",
QT_TRANSLATE_NOOP(
"App::Property",
"Specifies if the profile must be aligned with the extrusion wires",
),
locked=True,
)
obj.Align = True
if not "Offset" in pl:
obj.addProperty(
"App::PropertyVectorDistance",
"Offset",
"Frame",
QT_TRANSLATE_NOOP(
"App::Property", "An offset vector between the base sketch and the frame"
),
locked=True,
)
if not "BasePoint" in pl:
obj.addProperty(
"App::PropertyInteger",
"BasePoint",
"Frame",
QT_TRANSLATE_NOOP("App::Property", "Crossing point of the path on the profile."),
locked=True,
)
if not "ProfilePlacement" in pl:
obj.addProperty(
"App::PropertyPlacement",
"ProfilePlacement",
"Frame",
QT_TRANSLATE_NOOP(
"App::Property",
"An optional additional placement to add to the profile before extruding it",
),
locked=True,
)
if not "Rotation" in pl:
obj.addProperty(
"App::PropertyAngle",
"Rotation",
"Frame",
QT_TRANSLATE_NOOP(
"App::Property", "The rotation of the profile around its extrusion axis"
),
locked=True,
)
if not "Edges" in pl:
obj.addProperty(
"App::PropertyEnumeration",
"Edges",
"Frame",
QT_TRANSLATE_NOOP("App::Property", "The type of edges to consider"),
locked=True,
)
obj.Edges = [
"All edges",
"Vertical edges",
"Horizontal edges",
"Bottom horizontal edges",
"Top horizontal edges",
]
if not "Fuse" in pl:
obj.addProperty(
"App::PropertyBool",
"Fuse",
"Frame",
QT_TRANSLATE_NOOP(
"App::Property", "If true, geometry is fused, otherwise a compound"
),
locked=True,
)
def onDocumentRestored(self, obj):
ArchComponent.Component.onDocumentRestored(self, obj)
self.setProperties(obj)
def loads(self, state):
self.Type = "Frame"
def execute(self, obj):
if self.clone(obj):
return
if not self.ensureBase(obj):
return
if not obj.Base:
return
if not obj.Base.Shape:
return
if not obj.Base.Shape.Wires:
return
pl = obj.Placement
if obj.Base.Shape.Solids:
obj.Shape = obj.Base.Shape.copy()
if not pl.isNull():
obj.Placement = obj.Shape.Placement.multiply(pl)
else:
if not obj.Profile:
return
if not obj.Profile.Shape:
return
if obj.Profile.Shape.findPlane() is None:
return
if not obj.Profile.Shape.Wires:
return
if not obj.Profile.Shape.Faces:
for w in obj.Profile.Shape.Wires:
if not w.isClosed():
return
import math
import DraftGeomUtils
import Part
baseprofile = obj.Profile.Shape.copy()
if hasattr(obj, "ProfilePlacement"):
if not obj.ProfilePlacement.isNull():
baseprofile.Placement = obj.ProfilePlacement.multiply(baseprofile.Placement)
if not baseprofile.Faces:
f = []
for w in baseprofile.Wires:
f.append(Part.Face(w))
if len(f) == 1:
baseprofile = f[0]
else:
baseprofile = Part.makeCompound(f)
shapes = []
normal = DraftGeomUtils.getNormal(obj.Base.Shape)
edges = obj.Base.Shape.Edges
if hasattr(obj, "Edges"):
if obj.Edges == "Vertical edges":
rv = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(0, 1, 0))
edges = [
e
for e in edges
if round(rv.getAngle(e.tangentAt(e.FirstParameter)), 4) in [0, 3.1416]
]
elif obj.Edges == "Horizontal edges":
rv = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(1, 0, 0))
edges = [
e
for e in edges
if round(rv.getAngle(e.tangentAt(e.FirstParameter)), 4) in [0, 3.1416]
]
elif obj.Edges == "Top horizontal edges":
rv = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(1, 0, 0))
edges = [
e
for e in edges
if round(rv.getAngle(e.tangentAt(e.FirstParameter)), 4) in [0, 3.1416]
]
edges = sorted(edges, key=lambda x: x.CenterOfMass.z, reverse=True)
z = edges[0].CenterOfMass.z
edges = [e for e in edges if abs(e.CenterOfMass.z - z) < 0.00001]
elif obj.Edges == "Bottom horizontal edges":
rv = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(1, 0, 0))
edges = [
e
for e in edges
if round(rv.getAngle(e.tangentAt(e.FirstParameter)), 4) in [0, 3.1416]
]
edges = sorted(edges, key=lambda x: x.CenterOfMass.z)
z = edges[0].CenterOfMass.z
edges = [e for e in edges if abs(e.CenterOfMass.z - z) < 0.00001]
for e in edges:
bvec = DraftGeomUtils.vec(e)
bpoint = e.Vertexes[0].Point
profile = baseprofile.copy()
rot = None # New rotation.
# Supplying FreeCAD.Rotation() with two parallel vectors and
# a null vector may seem strange, but the function is perfectly
# able to handle this. Its algorithm will use default axes in
# such cases.
if obj.Align:
if normal is None:
rot = FreeCAD.Rotation(FreeCAD.Vector(), bvec, bvec, "ZYX")
else:
rot = FreeCAD.Rotation(FreeCAD.Vector(), normal, bvec, "ZYX")
profile.Placement.Rotation = rot
if hasattr(obj, "BasePoint"):
edges = Part.__sortEdges__(profile.Edges)
basepointliste = [profile.Placement.Base]
for edge in edges:
basepointliste.append(DraftGeomUtils.findMidpoint(edge))
basepointliste.append(edge.Vertexes[-1].Point)
try:
basepoint = basepointliste[obj.BasePoint]
except IndexError:
FreeCAD.Console.PrintMessage(
translate("Arch", "Crossing point not found in profile.") + "\n"
)
basepoint = basepointliste[0]
else:
basepoint = profile.Placement.Base
delta = bpoint.sub(basepoint) # Translation vector.
if obj.Offset and (not DraftVecUtils.isNull(obj.Offset)):
if rot is None:
delta = delta + obj.Offset
else:
delta = delta + rot.multVec(obj.Offset)
profile.translate(delta)
if obj.Rotation:
profile.rotate(bpoint, bvec, obj.Rotation)
# profile = wire.makePipeShell([profile], True, False, 2) TODO buggy
profile = profile.extrude(bvec)
shapes.append(profile)
if shapes:
if hasattr(obj, "Fuse"):
if obj.Fuse:
if len(shapes) > 1:
s = shapes[0].multiFuse(shapes[1:])
s = s.removeSplitter()
obj.Shape = s
obj.Placement = pl
return
obj.Shape = Part.makeCompound(shapes)
obj.Placement = pl
class _ViewProviderFrame(ArchComponent.ViewProviderComponent):
"A View Provider for the Frame object"
def __init__(self, vobj):
ArchComponent.ViewProviderComponent.__init__(self, vobj)
def getIcon(self):
import Arch_rc
return ":/icons/Arch_Frame_Tree.svg"
def claimChildren(self):
p = []
if hasattr(self, "Object"):
if self.Object.Profile:
p = [self.Object.Profile]
return ArchComponent.ViewProviderComponent.claimChildren(self) + p