2025-01-28 00:04:13 +01:00
|
|
|
# /**********************************************************************
|
|
|
|
|
# * *
|
|
|
|
|
# * Copyright (c) 2021 Javier Braña <javier.branagutierrez@gmail.com> *
|
|
|
|
|
# * *
|
|
|
|
|
# * 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 *
|
|
|
|
|
# * *
|
|
|
|
|
# ***********************************************************************
|
|
|
|
|
|
|
|
|
|
import FreeCAD, Part
|
|
|
|
|
import BOPTools.SplitAPI as splitter
|
|
|
|
|
import ArchComponent
|
|
|
|
|
import PVPlantSite
|
|
|
|
|
import math
|
|
|
|
|
|
|
|
|
|
if FreeCAD.GuiUp:
|
|
|
|
|
import FreeCADGui
|
|
|
|
|
from PySide import QtCore
|
|
|
|
|
from DraftTools import translate
|
|
|
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
|
|
|
import os
|
|
|
|
|
else:
|
|
|
|
|
# \cond
|
|
|
|
|
def translate(ctxt, txt):
|
|
|
|
|
return txt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def QT_TRANSLATE_NOOP(ctxt, txt):
|
|
|
|
|
return txt
|
|
|
|
|
# \endcond
|
|
|
|
|
|
|
|
|
|
__title__ = "PVPlant Trench"
|
|
|
|
|
__author__ = "Javier Braña"
|
|
|
|
|
__url__ = "http://www.sogos-solar.com"
|
|
|
|
|
|
|
|
|
|
import PVPlantResources
|
|
|
|
|
from PVPlantResources import DirIcons as DirIcons
|
|
|
|
|
|
|
|
|
|
def makePad(base=None):
|
|
|
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Pad")
|
|
|
|
|
Pad(obj)
|
|
|
|
|
ViewProviderPad(obj.ViewObject)
|
|
|
|
|
obj.Base = base
|
|
|
|
|
return obj
|
|
|
|
|
|
|
|
|
|
class Pad(ArchComponent.Component):
|
|
|
|
|
def __init__(self, obj):
|
|
|
|
|
# Definición de Variables:
|
|
|
|
|
ArchComponent.Component.__init__(self, obj)
|
|
|
|
|
self.obj = obj
|
|
|
|
|
self.base = None
|
|
|
|
|
|
|
|
|
|
self.setProperties(obj)
|
|
|
|
|
|
|
|
|
|
obj.Proxy = self
|
|
|
|
|
obj.IfcType = "Civil Element"
|
|
|
|
|
obj.setEditorMode("IfcType", 1)
|
|
|
|
|
|
|
|
|
|
obj.ViewObject.ShapeColor = (0.305, 0.230, 0.191)
|
|
|
|
|
|
|
|
|
|
def setProperties(self, obj):
|
|
|
|
|
# Definicion de Propiedades:
|
|
|
|
|
|
|
|
|
|
#TODO: Los parametros width y length desaparecerán. Se tiene que selecionar objeto base
|
|
|
|
|
obj.addProperty("App::PropertyLength",
|
|
|
|
|
"Width",
|
|
|
|
|
"Pad",
|
|
|
|
|
QT_TRANSLATE_NOOP("App::Property", "Connection")).Width = 5000
|
|
|
|
|
|
|
|
|
|
obj.addProperty("App::PropertyLength",
|
|
|
|
|
"Length",
|
|
|
|
|
"Pad",
|
|
|
|
|
QT_TRANSLATE_NOOP("App::Property", "Connection")).Length = 10000
|
|
|
|
|
|
|
|
|
|
obj.addProperty("App::PropertyAngle",
|
|
|
|
|
"FillSlope",
|
|
|
|
|
"Pad",
|
|
|
|
|
QT_TRANSLATE_NOOP("App::Property", "Connection")).FillSlope = 45.00
|
|
|
|
|
|
|
|
|
|
obj.addProperty("App::PropertyAngle",
|
|
|
|
|
"CutSlope",
|
|
|
|
|
"Pad",
|
|
|
|
|
QT_TRANSLATE_NOOP("App::Property", "Connection")).CutSlope = 60.00
|
|
|
|
|
|
|
|
|
|
obj.addProperty("App::PropertyBool",
|
|
|
|
|
"TopsoilCalculation",
|
|
|
|
|
"Pad",
|
|
|
|
|
QT_TRANSLATE_NOOP("App::Property", "Connection")).TopsoilCalculation = False
|
|
|
|
|
|
|
|
|
|
obj.addProperty("App::PropertyLength",
|
|
|
|
|
"TopsoilHeight",
|
|
|
|
|
"Pad",
|
|
|
|
|
QT_TRANSLATE_NOOP("App::Property", "Connection")).TopsoilHeight = 300
|
|
|
|
|
|
|
|
|
|
# Output values:
|
|
|
|
|
obj.addProperty("App::PropertyVolume",
|
|
|
|
|
"CutVolume",
|
|
|
|
|
"Output",
|
|
|
|
|
QT_TRANSLATE_NOOP("App::Property", "Connection"))
|
|
|
|
|
obj.setEditorMode("CutVolume", 1)
|
|
|
|
|
|
|
|
|
|
obj.addProperty("App::PropertyVolume",
|
|
|
|
|
"FillVolume",
|
|
|
|
|
"Output",
|
|
|
|
|
QT_TRANSLATE_NOOP("App::Property", "Connection"))
|
|
|
|
|
obj.setEditorMode("FillVolume", 1)
|
|
|
|
|
|
|
|
|
|
obj.addProperty("App::PropertyArea",
|
|
|
|
|
"PadArea",
|
|
|
|
|
"Output",
|
|
|
|
|
QT_TRANSLATE_NOOP("App::Property", "Connection"))
|
|
|
|
|
obj.setEditorMode("PadArea", 1)
|
|
|
|
|
|
|
|
|
|
obj.addProperty("App::PropertyArea",
|
|
|
|
|
"TopSoilArea",
|
|
|
|
|
"Output",
|
|
|
|
|
QT_TRANSLATE_NOOP("App::Property", "Connection"))
|
|
|
|
|
obj.setEditorMode("TopSoilArea", 1)
|
|
|
|
|
|
|
|
|
|
obj.addProperty("App::PropertyVolume",
|
|
|
|
|
"TopSoilVolume",
|
|
|
|
|
"Output",
|
|
|
|
|
QT_TRANSLATE_NOOP("App::Property", "Connection"))
|
|
|
|
|
obj.setEditorMode("TopSoilVolume", 1)
|
|
|
|
|
obj.setEditorMode("Placement", 1)
|
|
|
|
|
|
|
|
|
|
self.Type = "Pad"
|
|
|
|
|
obj.Proxy = self
|
|
|
|
|
|
|
|
|
|
def onDocumentRestored(self, obj):
|
|
|
|
|
"""Method run when the document is restored.
|
|
|
|
|
Re-adds the Arch component, and object properties."""
|
|
|
|
|
|
|
|
|
|
ArchComponent.Component.onDocumentRestored(self, obj)
|
|
|
|
|
self.obj = obj
|
|
|
|
|
self.Type = "Pad"
|
|
|
|
|
obj.Proxy = self
|
|
|
|
|
|
|
|
|
|
def onChanged(self, fp, prop):
|
|
|
|
|
'''Do something when a property has changed'''
|
|
|
|
|
|
|
|
|
|
def execute(self, obj):
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
starttime = datetime.now()
|
|
|
|
|
|
|
|
|
|
pb = obj.Placement.Base
|
|
|
|
|
land = PVPlantSite.get().Terrain
|
|
|
|
|
shapes = []
|
|
|
|
|
pad = None
|
|
|
|
|
|
|
|
|
|
if obj.Base:
|
|
|
|
|
if hasattr(obj.Base.Shape, 'Wires') and obj.Base.Shape.Wires:
|
|
|
|
|
pad = obj.Base.Shape.Wires[0]
|
|
|
|
|
elif obj.Base.Shape.Edges:
|
|
|
|
|
pad = Part.Wire(obj.Base.Shape.Edges)
|
|
|
|
|
pb = obj.Base.Placement.Base
|
|
|
|
|
else:
|
|
|
|
|
# Si no hay una base seleccionada se crea una rectangular:
|
|
|
|
|
halfWidth = obj.Width.Value / 2
|
|
|
|
|
halfLength = obj.Length.Value / 2
|
|
|
|
|
|
|
|
|
|
p1 = FreeCAD.Vector(-halfLength, -halfWidth, 0)
|
|
|
|
|
p2 = FreeCAD.Vector( halfLength, -halfWidth, 0)
|
|
|
|
|
p3 = FreeCAD.Vector( halfLength, halfWidth, 0)
|
|
|
|
|
p4 = FreeCAD.Vector(-halfLength, halfWidth, 0)
|
|
|
|
|
pad = Part.makePolygon([p1, p2, p3, p4, p1])
|
|
|
|
|
'''pad = Draft.makeWire([p1, p2, p3, p4, p1])
|
|
|
|
|
obj.Base = pad
|
|
|
|
|
pad = obj.Base.Shape.Wires[0]'''
|
|
|
|
|
|
|
|
|
|
# 1. Terraplén (fill):
|
|
|
|
|
fill = None
|
|
|
|
|
fillcommon = None
|
|
|
|
|
if land.Shape.BoundBox.ZMin < pb.z:
|
|
|
|
|
tool = self.createSolid(obj, pad, land, -1)
|
|
|
|
|
fillcommon, fill = self.calculateFill(obj, tool)
|
|
|
|
|
else:
|
|
|
|
|
print("- PAD: NOOOO Calculete fill solid:")
|
|
|
|
|
|
|
|
|
|
# 2. Desmonte (cut):
|
|
|
|
|
cut = None
|
|
|
|
|
'''cutcommon = None
|
|
|
|
|
if land.Shape.BoundBox.ZMax > pb.z:
|
|
|
|
|
cut = self.createSolid(obj, pad, land, 1)
|
|
|
|
|
cut.Placement.Base += pb
|
|
|
|
|
cutcommon, cut = self.calculateCut(obj, cut)
|
|
|
|
|
else:
|
|
|
|
|
print("- PAD: NOOOO Calcalete cut solid:")'''
|
|
|
|
|
|
|
|
|
|
topsoilArea = 0
|
|
|
|
|
topsoilVolume = 0
|
|
|
|
|
if fill:
|
|
|
|
|
if obj.TopsoilCalculation:
|
|
|
|
|
filltopsoil = fillcommon.extrude(FreeCAD.Vector(0, 0, -obj.TopsoilHeight))
|
|
|
|
|
topsoilVolume += filltopsoil.Volume
|
|
|
|
|
filltopsoil.Placement.Base -= pb
|
|
|
|
|
shapes.append(filltopsoil)
|
|
|
|
|
fill.Placement.Base -= pb
|
|
|
|
|
shapes.append(fill)
|
|
|
|
|
topsoilArea += fill.Area
|
|
|
|
|
if cut:
|
|
|
|
|
cut.Placement.Base -= pb
|
|
|
|
|
shapes.append(cut)
|
|
|
|
|
topsoilArea += cut.Area
|
|
|
|
|
if obj.TopsoilCalculation:
|
|
|
|
|
cuttopsoil = cutcommon.extrude(FreeCAD.Vector(0, 0, -obj.TopsoilHeight))
|
|
|
|
|
topsoilVolume += cuttopsoil.Volume
|
|
|
|
|
obj.CutVolume = obj.CutVolume.Value - cuttopsoil.Volume
|
|
|
|
|
|
|
|
|
|
pad = Part.Face(pad)
|
|
|
|
|
if len(shapes) == 0:
|
|
|
|
|
shapes.append(pad)
|
|
|
|
|
|
|
|
|
|
shape = Part.makeCompound(shapes)
|
|
|
|
|
#shape.Placement.Base = FreeCAD.Vector(0)
|
|
|
|
|
obj.Shape = shape
|
|
|
|
|
obj.Placement.Base = pb
|
|
|
|
|
obj.PadArea = pad.Area
|
|
|
|
|
obj.TopSoilArea = topsoilArea
|
|
|
|
|
obj.TopSoilVolume = topsoilVolume
|
|
|
|
|
|
|
|
|
|
total_time = datetime.now() - starttime
|
|
|
|
|
print(" -- Tiempo tardado:", total_time)
|
|
|
|
|
|
|
|
|
|
def createSolid(self, obj, base, land, dir = -1):
|
|
|
|
|
base_copy = base.copy()
|
|
|
|
|
base_copy.Placement.Base = FreeCAD.Vector(0,0,0)
|
|
|
|
|
if dir == -1:
|
|
|
|
|
zz = land.Mesh.BoundBox.ZMin
|
|
|
|
|
angle = obj.FillSlope.Value
|
|
|
|
|
else:
|
|
|
|
|
zz = land.Mesh.BoundBox.ZMax
|
|
|
|
|
angle = obj.CutSlope.Value
|
|
|
|
|
height = abs(zz - base.Placement.Base.z)
|
|
|
|
|
offset = base_copy.makeOffset2D(height / math.tan(math.radians(angle)), 0, False, False, True)
|
|
|
|
|
offset.Placement.Base.z = dir * height
|
|
|
|
|
|
|
|
|
|
import DraftGeomUtils
|
|
|
|
|
base_fillet = DraftGeomUtils.filletWire(base_copy, 1) #trick to get a nice shape: (fillet of 1 mm)
|
|
|
|
|
pad = Part.makeLoft([base_fillet, offset], True)
|
|
|
|
|
pad.Placement.Base = base.Placement.Base
|
|
|
|
|
return pad
|
|
|
|
|
|
|
|
|
|
def calculateFill(self, obj, solid):
|
|
|
|
|
common = solid.common(PVPlantSite.get().Terrain.Shape)
|
|
|
|
|
if common.Area > 0:
|
|
|
|
|
sp = splitter.slice(solid, [common, ], "Split")
|
|
|
|
|
commoncopy = common.copy()
|
|
|
|
|
commoncopy.Placement.Base.z += 10
|
|
|
|
|
volume = 0
|
|
|
|
|
fills = []
|
|
|
|
|
for sol in sp.Solids:
|
|
|
|
|
common1 = sol.common(commoncopy)
|
|
|
|
|
if common1.Area > 0:
|
|
|
|
|
volume += sol.Volume
|
|
|
|
|
fills.append(sol)
|
|
|
|
|
obj.FillVolume = volume
|
|
|
|
|
if len(fills) > 0:
|
|
|
|
|
base = fills.pop(0)
|
|
|
|
|
if len(fills) > 0:
|
|
|
|
|
base = base.fuse(fills)
|
|
|
|
|
return common, base
|
|
|
|
|
else:
|
|
|
|
|
obj.FillVolume = 0
|
|
|
|
|
print("--- Fill: no common Area --------------------------")
|
|
|
|
|
return None, None
|
|
|
|
|
|
|
|
|
|
def calculateCut(self, obj, solid):
|
|
|
|
|
common = solid.common(PVPlantSite.get().Terrain.Shape)
|
|
|
|
|
if common.Area > 0:
|
|
|
|
|
sp = splitter.slice(solid, [common, ], "Split")
|
|
|
|
|
shells = []
|
|
|
|
|
volume = 0
|
|
|
|
|
commoncopy = common.copy()
|
|
|
|
|
commoncopy.Placement.Base.z -= 1
|
|
|
|
|
for sol in sp.Solids:
|
|
|
|
|
common1 = sol.common(commoncopy)
|
|
|
|
|
if common1.Area > 0:
|
|
|
|
|
volume += sol.Volume
|
|
|
|
|
shell = sol.Shells[0]
|
|
|
|
|
shell = shell.cut(common)
|
|
|
|
|
shells.append(shell)
|
|
|
|
|
obj.CutVolume = volume
|
|
|
|
|
if len(shells) > 0:
|
|
|
|
|
base = shells.pop(0)
|
|
|
|
|
if len(shells) > 0:
|
|
|
|
|
base = base.fuse(shells)
|
|
|
|
|
return common, base
|
|
|
|
|
else:
|
|
|
|
|
obj.CutVolume = 0
|
|
|
|
|
print("--- Cut: no common Area --------------------------")
|
|
|
|
|
|
|
|
|
|
return None, None
|
|
|
|
|
|
|
|
|
|
class ViewProviderPad(ArchComponent.ViewProviderComponent):
|
|
|
|
|
def __init__(self, vobj):
|
|
|
|
|
ArchComponent.ViewProviderComponent.__init__(self, vobj)
|
|
|
|
|
|
|
|
|
|
def getIcon(self):
|
|
|
|
|
return str(os.path.join(PVPlantResources.DirIcons, "pad.svg"))
|
|
|
|
|
|
|
|
|
|
class _PadTaskPanel:
|
|
|
|
|
|
|
|
|
|
def __init__(self, obj=None):
|
|
|
|
|
|
|
|
|
|
if obj is None:
|
|
|
|
|
self.new = True
|
|
|
|
|
self.obj = makeTrench()
|
|
|
|
|
else:
|
|
|
|
|
self.new = False
|
|
|
|
|
self.obj = obj
|
|
|
|
|
|
2025-04-14 10:05:32 +06:00
|
|
|
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "Civil/PVPlantTrench.ui"))
|
2025-01-28 00:04:13 +01:00
|
|
|
|
|
|
|
|
def accept(self):
|
|
|
|
|
FreeCAD.ActiveDocument.openTransaction("Create Pad")
|
|
|
|
|
FreeCADGui.Control.closeDialog()
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def reject(self):
|
|
|
|
|
FreeCAD.ActiveDocument.removeObject(self.obj.Name)
|
|
|
|
|
if self.new:
|
|
|
|
|
FreeCADGui.Control.closeDialog()
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
|
|
|
|
|
|
|
|
import draftutils.utils as utils
|
|
|
|
|
import draftutils.gui_utils as gui_utils
|
|
|
|
|
import draftutils.todo as todo
|
|
|
|
|
import draftguitools.gui_base_original as gui_base_original
|
|
|
|
|
import draftguitools.gui_tool_utils as gui_tool_utils
|
|
|
|
|
|
|
|
|
|
from draftutils.translate import translate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _CommandPad(gui_base_original.Creator):
|
|
|
|
|
"""Gui command for the Line tool."""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
# super(_CommandTrench, self).__init__()
|
|
|
|
|
gui_base_original.Creator.__init__(self)
|
|
|
|
|
self.path = None
|
|
|
|
|
self.obj = None
|
|
|
|
|
|
|
|
|
|
def GetResources(self):
|
|
|
|
|
"""Set icon, menu and tooltip."""
|
|
|
|
|
return {'Pixmap': str(os.path.join(DirIcons, "pad.svg")),
|
|
|
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP("PVPlantPad", "Pad"),
|
|
|
|
|
'Accel': "C, P",
|
|
|
|
|
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PVPlantPad",
|
|
|
|
|
"Creates a Pad object from setup dialog.")}
|
|
|
|
|
|
|
|
|
|
def Activated(self, name=translate("draft", "Line")):
|
|
|
|
|
"""Execute when the command is called."""
|
|
|
|
|
|
|
|
|
|
sel = FreeCADGui.Selection.getSelection()
|
|
|
|
|
base = None
|
|
|
|
|
needbase = True
|
|
|
|
|
if len(sel) > 0:
|
|
|
|
|
needbase = False
|
|
|
|
|
base = sel[0]
|
|
|
|
|
self.obj = makePad(base)
|
|
|
|
|
|
|
|
|
|
if needbase:
|
|
|
|
|
gui_base_original.Creator.Activated(self, name=translate("draft", "Line"))
|
|
|
|
|
self.ui.wireUi(name)
|
|
|
|
|
self.ui.setTitle("Pad")
|
|
|
|
|
#self.obj = self.doc.addObject("Part::Feature", self.featureName)
|
|
|
|
|
#gui_utils.format_object(self.obj)
|
|
|
|
|
self.call = self.view.addEventCallback("SoEvent", self.action)
|
|
|
|
|
|
|
|
|
|
def action(self, arg):
|
|
|
|
|
"""Handle the 3D scene events.
|
|
|
|
|
|
|
|
|
|
This is installed as an EventCallback in the Inventor view.
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
arg: dict
|
|
|
|
|
Dictionary with strings that indicates the type of event received
|
|
|
|
|
from the 3D view.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
print(self.obj)
|
|
|
|
|
if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE":
|
|
|
|
|
self.finish()
|
|
|
|
|
FreeCAD.ActiveDocument.removeObject(self.obj.Name)
|
|
|
|
|
|
|
|
|
|
elif arg["Type"] == "SoLocation2Event":
|
|
|
|
|
self.point, ctrlPoint, self.info = gui_tool_utils.getPoint(self, arg)
|
|
|
|
|
gui_tool_utils.redraw3DView()
|
|
|
|
|
self.obj.Placement.Base = FreeCAD.Vector(self.info["x"], self.info["y"], self.info["z"])
|
|
|
|
|
|
|
|
|
|
elif (arg["Type"] == "SoMouseButtonEvent"
|
|
|
|
|
and arg["State"] == "DOWN"
|
|
|
|
|
and arg["Button"] == "BUTTON1"):
|
|
|
|
|
|
|
|
|
|
gui_tool_utils.getSupport(arg)
|
|
|
|
|
self.point, ctrlPoint, self.info = gui_tool_utils.getPoint(self, arg)
|
|
|
|
|
|
|
|
|
|
if self.point:
|
|
|
|
|
self.point = FreeCAD.Vector(self.info["x"], self.info["y"], self.info["z"])
|
|
|
|
|
self.ui.redraw()
|
|
|
|
|
self.obj.Placement.Base = FreeCAD.Vector(self.info["x"], self.info["y"], self.info["z"])
|
|
|
|
|
self.finish()
|
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
|
|
|
|
|
|
def finish(self, closed=False, cont=False):
|
|
|
|
|
"""Terminate the operation and close the polyline if asked.
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
closed: bool, optional
|
|
|
|
|
Close the line if `True`.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# super(_CommandTrench, self).finish()
|
|
|
|
|
gui_base_original.Creator.finish(self)
|
|
|
|
|
if self.ui and self.ui.continueMode:
|
|
|
|
|
self.Activated()
|
|
|
|
|
|
|
|
|
|
if FreeCAD.GuiUp:
|
|
|
|
|
FreeCADGui.addCommand('PVPlantPad', _CommandPad())
|