# /********************************************************************** # * * # * Copyright (c) 2021 Javier Braña * # * * # * 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 self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "Civil/PVPlantTrench.ui")) 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())