Files
PVPlant/PVPlantPad.py

440 lines
16 KiB
Python
Raw Normal View History

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())