* 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>
350 lines
12 KiB
Python
350 lines
12 KiB
Python
# SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
# ***************************************************************************
|
|
# * *
|
|
# * Copyright (c) 2014 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 Equipment"
|
|
__author__ = "Yorik van Havre"
|
|
__url__ = "https://www.freecad.org"
|
|
|
|
## @package ArchEquipment
|
|
# \ingroup ARCH
|
|
# \brief The Equipment object and tools
|
|
#
|
|
# This module provides tools to build equipment objects.
|
|
# Equipment is used to represent furniture and all kinds of electrical
|
|
# or hydraulic appliances in a building
|
|
|
|
import FreeCAD
|
|
import ArchComponent
|
|
import DraftVecUtils
|
|
|
|
if FreeCAD.GuiUp:
|
|
from PySide import QtGui
|
|
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
|
|
|
|
|
|
def createMeshView(obj, direction=FreeCAD.Vector(0, 0, -1), outeronly=False, largestonly=False):
|
|
"""createMeshView(obj,[direction,outeronly,largestonly]): creates a flat shape that is the
|
|
projection of the given mesh object in the given direction (default = on the XY plane). If
|
|
outeronly is True, only the outer contour is taken into consideration, discarding the inner
|
|
holes. If largestonly is True, only the largest segment of the given mesh will be used."""
|
|
|
|
import math
|
|
import DraftGeomUtils
|
|
import Mesh
|
|
import Part
|
|
|
|
if not obj.isDerivedFrom("Mesh::Feature"):
|
|
return
|
|
mesh = obj.Mesh
|
|
|
|
# 1. Flattening the mesh
|
|
proj = []
|
|
for f in mesh.Facets:
|
|
nf = []
|
|
for v in f.Points:
|
|
v = FreeCAD.Vector(v)
|
|
a = v.negative().getAngle(direction)
|
|
l = math.cos(a) * v.Length
|
|
p = v.add(FreeCAD.Vector(direction).multiply(l))
|
|
p = DraftVecUtils.rounded(p)
|
|
nf.append(p)
|
|
proj.append(nf)
|
|
flatmesh = Mesh.Mesh(proj)
|
|
|
|
# 2. Removing wrong faces
|
|
facets = []
|
|
for f in flatmesh.Facets:
|
|
if f.Normal.getAngle(direction) < math.pi:
|
|
facets.append(f)
|
|
cleanmesh = Mesh.Mesh(facets)
|
|
|
|
# Mesh.show(cleanmesh)
|
|
|
|
# 3. Getting the bigger mesh from the planar segments
|
|
if largestonly:
|
|
c = cleanmesh.getSeparateComponents()
|
|
# print(c)
|
|
cleanmesh = c[0]
|
|
segs = cleanmesh.getPlanarSegments(1)
|
|
meshes = []
|
|
for s in segs:
|
|
f = [cleanmesh.Facets[i] for i in s]
|
|
meshes.append(Mesh.Mesh(f))
|
|
a = 0
|
|
for m in meshes:
|
|
if m.Area > a:
|
|
boundarymesh = m
|
|
a = m.Area
|
|
# Mesh.show(boundarymesh)
|
|
cleanmesh = boundarymesh
|
|
|
|
# 4. Creating a Part and getting the contour
|
|
|
|
shape = None
|
|
for f in cleanmesh.Facets:
|
|
p = Part.makePolygon(f.Points + [f.Points[0]])
|
|
# print(p,len(p.Vertexes),p.isClosed())
|
|
try:
|
|
p = Part.Face(p)
|
|
if shape:
|
|
shape = shape.fuse(p)
|
|
else:
|
|
shape = p
|
|
except Part.OCCError:
|
|
pass
|
|
shape = shape.removeSplitter()
|
|
|
|
# 5. Extracting the largest wire
|
|
|
|
if outeronly:
|
|
count = 0
|
|
largest = None
|
|
for w in shape.Wires:
|
|
if len(w.Vertexes) > count:
|
|
count = len(w.Vertexes)
|
|
largest = w
|
|
if largest:
|
|
try:
|
|
f = Part.Face(w)
|
|
except Part.OCCError:
|
|
print("Unable to produce a face from the outer wire.")
|
|
else:
|
|
shape = f
|
|
|
|
return shape
|
|
|
|
|
|
class _Equipment(ArchComponent.Component):
|
|
"The Equipment object"
|
|
|
|
def __init__(self, obj):
|
|
|
|
ArchComponent.Component.__init__(self, obj)
|
|
self.Type = "Equipment"
|
|
self.setProperties(obj)
|
|
from ArchIFC import IfcTypes
|
|
|
|
if "Furniture" in IfcTypes:
|
|
# IfcFurniture is new in IFC4
|
|
obj.IfcType = "Furniture"
|
|
elif "Furnishing Element" in IfcTypes:
|
|
# IFC2x3 does know a IfcFurnishingElement
|
|
obj.IfcType = "Furnishing Element"
|
|
else:
|
|
obj.IfcType = "Building Element Proxy"
|
|
# Add features in the SketchArch External Add-on, if present
|
|
self.addSketchArchFeatures(obj)
|
|
|
|
def addSketchArchFeatures(self, obj, linkObj=None, mode=None):
|
|
"""
|
|
To add features in the SketchArch External Add-on, if present (https://github.com/paullee0/FreeCAD_SketchArch)
|
|
- import ArchSketchObject module, and
|
|
- set properties that are common to ArchObjects (including Links) and ArchSketch
|
|
to support the additional features
|
|
|
|
To install SketchArch External Add-on, see https://github.com/paullee0/FreeCAD_SketchArch#iv-install
|
|
"""
|
|
|
|
try:
|
|
import ArchSketchObject
|
|
|
|
ArchSketchObject.ArchSketch.setPropertiesLinkCommon(self, obj, linkObj, mode)
|
|
except:
|
|
pass
|
|
|
|
def setProperties(self, obj):
|
|
|
|
pl = obj.PropertiesList
|
|
if not "Model" in pl:
|
|
obj.addProperty(
|
|
"App::PropertyString",
|
|
"Model",
|
|
"Equipment",
|
|
QT_TRANSLATE_NOOP("App::Property", "The model description of this equipment"),
|
|
locked=True,
|
|
)
|
|
if not "ProductURL" in pl:
|
|
obj.addProperty(
|
|
"App::PropertyString",
|
|
"ProductURL",
|
|
"Equipment",
|
|
QT_TRANSLATE_NOOP("App::Property", "The URL of the product page of this equipment"),
|
|
locked=True,
|
|
)
|
|
if not "StandardCode" in pl:
|
|
obj.addProperty(
|
|
"App::PropertyString",
|
|
"StandardCode",
|
|
"Equipment",
|
|
QT_TRANSLATE_NOOP("App::Property", "A standard code (MasterFormat, OmniClass,…)"),
|
|
locked=True,
|
|
)
|
|
if not "SnapPoints" in pl:
|
|
obj.addProperty(
|
|
"App::PropertyVectorList",
|
|
"SnapPoints",
|
|
"Equipment",
|
|
QT_TRANSLATE_NOOP("App::Property", "Additional snap points for this equipment"),
|
|
locked=True,
|
|
)
|
|
if not "EquipmentPower" in pl:
|
|
obj.addProperty(
|
|
"App::PropertyFloat",
|
|
"EquipmentPower",
|
|
"Equipment",
|
|
QT_TRANSLATE_NOOP(
|
|
"App::Property", "The electric power needed by this equipment in Watts"
|
|
),
|
|
locked=True,
|
|
)
|
|
obj.setEditorMode("VerticalArea", 2)
|
|
obj.setEditorMode("HorizontalArea", 2)
|
|
obj.setEditorMode("PerimeterLength", 2)
|
|
|
|
def onDocumentRestored(self, obj):
|
|
|
|
ArchComponent.Component.onDocumentRestored(self, obj)
|
|
self.setProperties(obj)
|
|
|
|
# Add features in the SketchArch External Add-on, if present
|
|
self.addSketchArchFeatures(obj)
|
|
|
|
def loads(self, state):
|
|
|
|
self.Type = "Equipment"
|
|
|
|
def onChanged(self, obj, prop):
|
|
|
|
self.hideSubobjects(obj, prop)
|
|
ArchComponent.Component.onChanged(self, obj, prop)
|
|
|
|
def execute(self, obj):
|
|
|
|
if self.clone(obj):
|
|
return
|
|
if not self.ensureBase(obj):
|
|
return
|
|
|
|
pl = obj.Placement
|
|
if obj.Base:
|
|
base = None
|
|
if hasattr(obj.Base, "Shape"):
|
|
base = obj.Base.Shape.copy()
|
|
base = self.processSubShapes(obj, base, pl)
|
|
self.applyShape(obj, base, pl, allowinvalid=False, allownosolid=True)
|
|
|
|
# Execute features in the SketchArch External Add-on, if present
|
|
self.executeSketchArchFeatures(obj)
|
|
|
|
def executeSketchArchFeatures(self, obj, linkObj=None, index=None, linkElement=None):
|
|
"""
|
|
To execute features in the SketchArch External Add-on (https://github.com/paullee0/FreeCAD_SketchArch)
|
|
- import ArchSketchObject module, and
|
|
- execute features that are common to ArchObjects (including Links) and ArchSketch
|
|
|
|
To install SketchArch External Add-on, see https://github.com/paullee0/FreeCAD_SketchArch#iv-install
|
|
"""
|
|
|
|
# To execute features in SketchArch External Add-on, if present
|
|
try:
|
|
import ArchSketchObject
|
|
|
|
# Execute SketchArch Feature - Intuitive Automatic Placement for Arch Windows/Doors, Equipment etc.
|
|
# see https://forum.freecad.org/viewtopic.php?f=23&t=50802
|
|
ArchSketchObject.updateAttachmentOffset(obj, linkObj)
|
|
except:
|
|
pass
|
|
|
|
def appLinkExecute(self, obj, linkObj, index, linkElement):
|
|
"""
|
|
Default Link Execute method() -
|
|
See https://forum.freecad.org/viewtopic.php?f=22&t=42184&start=10#p361124
|
|
@realthunder added support to Links to run Linked Scripted Object's methods()
|
|
"""
|
|
|
|
# Add features in the SketchArch External Add-on, if present
|
|
self.addSketchArchFeatures(obj, linkObj)
|
|
|
|
# Execute features in the SketchArch External Add-on, if present
|
|
self.executeSketchArchFeatures(obj, linkObj)
|
|
|
|
def computeAreas(self, obj):
|
|
return
|
|
|
|
|
|
class _ViewProviderEquipment(ArchComponent.ViewProviderComponent):
|
|
"A View Provider for the Equipment object"
|
|
|
|
def __init__(self, vobj):
|
|
|
|
ArchComponent.ViewProviderComponent.__init__(self, vobj)
|
|
|
|
def getIcon(self):
|
|
|
|
import Arch_rc
|
|
|
|
if hasattr(self, "Object"):
|
|
if hasattr(self.Object, "CloneOf"):
|
|
if self.Object.CloneOf:
|
|
return ":/icons/Arch_Equipment_Clone.svg"
|
|
return ":/icons/Arch_Equipment_Tree.svg"
|
|
|
|
def attach(self, vobj):
|
|
|
|
self.Object = vobj.Object
|
|
from pivy import coin
|
|
|
|
sep = coin.SoSeparator()
|
|
self.coords = coin.SoCoordinate3()
|
|
sep.addChild(self.coords)
|
|
self.coords.point.deleteValues(0)
|
|
symbol = coin.SoMarkerSet()
|
|
symbol.markerIndex = FreeCADGui.getMarkerIndex("", 5)
|
|
sep.addChild(symbol)
|
|
rn = vobj.RootNode
|
|
rn.addChild(sep)
|
|
ArchComponent.ViewProviderComponent.attach(self, vobj)
|
|
|
|
def updateData(self, obj, prop):
|
|
|
|
if prop == "SnapPoints":
|
|
if obj.SnapPoints:
|
|
self.coords.point.setNum(len(obj.SnapPoints))
|
|
self.coords.point.setValues([[p.x, p.y, p.z] for p in obj.SnapPoints])
|
|
else:
|
|
self.coords.point.deleteValues(0)
|
|
else:
|
|
ArchComponent.ViewProviderComponent.updateData(self, obj, prop)
|