# /********************************************************************** # * * # * 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 copy import ArchComponent import FreeCAD import Part import numpy as np import math import PVPlantSite if FreeCAD.GuiUp: import FreeCADGui from pivy import coin import os else: # \cond def translate(ctxt, txt): return txt def QT_TRANSLATE_NOOP(ctxt, txt): return txt # \endcond __title__ = "FreeCAD Fixed Rack" __author__ = "Javier Braña" __url__ = "http://www.sogos-solar.com" __dir__ = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", "PVPlant") DirResources = os.path.join(__dir__, "Resources") DirIcons = os.path.join(DirResources, "Icons") DirImages = os.path.join(DirResources, "Images") line_patterns = { "Continues _______________________________": 0xFFFF, "Border __ . __ __ . __ __ . __ __ . __": 0x3CF2, "Border (.5x) __.__.__.__.__.__.__.__.__.__._": 0x3939, "Border (2x) ____ ____ . ____ ____ . _": 0xFDFA, "Center ____ _ ____ _ ____ _ ____ _ ___": 0xFF3C, "Center (.5x) ___ _ ___ _ ___ _ ___ _ ___ _ _": 0xFC78, "Center (2x) ________ __ ________ __ ___": 0xFFDE, "Dash dot __ . __ . __ . __ . __ . __ . _": 0xE4E4, "Dash dot (.5x) _._._._._._._._._._._._._._._._": 0xEBAE, "Dash dot (2x) ____ . ____ . ____ . ____": 0xFF08, "Dashed __ __ __ __ __ __ __ __ __ __ _": 0x739C, "Dashed (.5x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _": 0xDB6E, "Dashed (2x) ____ ____ ____ ____ ____ _": 0xFFE0, "Divide ____ . . ____ . . ____ . . ____": 0xFF24, "Divide (.5x) __..__..__..__..__..__..__..__.": 0xEAEA, "Divide (2x) ________ . . ________ . . ": 0xFFEA, "Dot . . . . . . . . . . . . . . . .": 0x4924, "Dot (.5x) ...............................": 0x5555, "Dot (2x) . . . . . . . . . . .": 0x8888} def makeTerrain(name="Terrain"): obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Terrain") obj.Label = name Terrain(obj) ViewProviderTerrain(obj.ViewObject) FreeCAD.ActiveDocument.recompute() return obj class Terrain(ArchComponent.Component): "A Shadow Terrain Obcject" def __init__(self, obj): # Definición de variables: ArchComponent.Component.__init__(self, obj) self.setProperties(obj) self.obj = obj # Does a IfcType exist? # obj.IfcType = "Fence" # obj.MoveWithHost = False self.site = PVPlantSite.get() self.site.Terrain = obj obj.ViewObject.ShapeColor = (0.0000, 0.6667, 0.4980) obj.ViewObject.LineColor = (0.0000, 0.6000, 0.4392) def setProperties(self, obj): # Definicion de Propiedades: pl = obj.PropertiesList if not ("CuttingBoundary" in pl): obj.addProperty("App::PropertyLink", "CuttingBoundary", "Surface", "A boundary line to delimit the surface") if not ("DEM" in pl): obj.addProperty("App::PropertyFile", "DEM", "Surface", "Load a ASC file to generate the surface") if not ("PointsGroup" in pl): obj.addProperty("App::PropertyLink", "PointsGroup", "Surface", "Use a Point Group to generate the surface") if not ("mesh" in pl): obj.addProperty("Mesh::PropertyMeshKernel", "mesh", "Surface", "Mesh") obj.setEditorMode("mesh", 1) if not ("InitialMesh" in pl): obj.addProperty("Mesh::PropertyMeshKernel", "InitialMesh", "Surface", "Mesh") obj.setEditorMode("InitialMesh", 1) ''' #obj.setEditorMode("Volume", 1) if not "AllowedAreas" in pl: obj.addProperty("App::PropertyLinkList", "AllowedAreas", "Areas", "A boundary to delimitated the terrain").AllowedAreas = [] if not "ProhibitedAreas" in pl: obj.addProperty("App::PropertyLinkList", "ProhibitedAreas", "Areas", "A boundary to delimitated the terrain").ProhibitedAreas = [] ''' def onDocumentRestored(self, obj): ArchComponent.Component.onDocumentRestored(self, obj) self.setProperties(obj) def onChanged(self, obj, prop): '''Do something when a property has changed''' if prop == "InitialMesh": obj.mesh = obj.InitialMesh.copy() if prop == "DEM" or prop == "CuttingBoundary": from datetime import datetime if obj.DEM and obj.CuttingBoundary: ''' Parámetro Descripción Requisitos NCOLS: Cantidad de columnas de celdas Entero mayor que 0. NROWS: Cantidad de filas de celdas Entero mayor que 0. XLLCENTER o XLLCORNER: Coordenada X del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada y. YLLCENTER o YLLCORNER: Coordenada Y del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada x. CELLSIZE: Tamaño de celda Mayor que 0. NODATA_VALUE: Los valores de entrada que serán NoData en el ráster de salida Opcional. El valor predeterminado es -9999 ''' grid_space = 1 file = open(obj.DEM, "r") templist = [line.split() for line in file.readlines()] file.close() del file # Read meta data: meta = templist[0:6] nx = int(meta[0][1]) # NCOLS ny = int(meta[1][1]) # NROWS xllref = meta[2][0] # XLLCENTER / XLLCORNER xllvalue = round(float(meta[2][1]), 3) yllref = meta[3][0] # YLLCENTER / XLLCORNER yllvalue = round(float(meta[3][1]), 3) cellsize = round(float(meta[4][1]), 3) # CELLSIZE nodata_value = float(meta[5][1]) # NODATA_VALUE # set coarse_factor coarse_factor = max(round(grid_space / cellsize), 1) # Get z values templist = templist[6:(6 + ny)] templist = [templist[i][0::coarse_factor] for i in np.arange(0, len(templist), coarse_factor)] datavals = np.array(templist).astype(float) del templist # create xy coordinates offset = self.site.Origin x = (cellsize * np.arange(nx)[0::coarse_factor] + xllvalue) * 1000 - offset.x y = (cellsize * np.arange(ny)[-1::-1][0::coarse_factor] + yllvalue) * 1000 - offset.y datavals = datavals * 1000 # Ajuste de altura # remove points out of area # 1. coarse: if obj.CuttingBoundary: inc_x = obj.CuttingBoundary.Shape.BoundBox.XLength * 0.0 inc_y = obj.CuttingBoundary.Shape.BoundBox.YLength * 0.0 tmp = np.where(np.logical_and(x >= (obj.CuttingBoundary.Shape.BoundBox.XMin - inc_x), x <= (obj.CuttingBoundary.Shape.BoundBox.XMax + inc_x)))[0] x_max = np.ndarray.max(tmp) x_min = np.ndarray.min(tmp) tmp = np.where(np.logical_and(y >= (obj.CuttingBoundary.Shape.BoundBox.YMin - inc_y), y <= (obj.CuttingBoundary.Shape.BoundBox.YMax + inc_y)))[0] y_max = np.ndarray.max(tmp) y_min = np.ndarray.min(tmp) del tmp x = x[x_min:x_max+1] y = y[y_min:y_max+1] datavals = datavals[y_min:y_max+1, x_min:x_max+1] # Create mesh - surface: import MeshTools.Triangulation as Triangulation import Mesh stepsize = 75 stepx = math.ceil(nx / stepsize) stepy = math.ceil(ny / stepsize) mesh = Mesh.Mesh() for indx in range(stepx): inix = indx * stepsize - 1 finx = min([stepsize * (indx + 1), len(x)-1]) for indy in range(stepy): iniy = indy * stepsize - 1 finy = min([stepsize * (indy + 1), len(y) - 1]) pts = [] for i in range(inix, finx): for j in range(iniy, finy): if datavals[j][i] != nodata_value: if obj.CuttingBoundary: if obj.CuttingBoundary.Shape.isInside(FreeCAD.Vector(x[i], y[j], 0), 0, True): pts.append([x[i], y[j], datavals[j][i]]) else: pts.append([x[i], y[j], datavals[j][i]]) if len(pts) > 3: try: triangulated = Triangulation.Triangulate(pts) mesh.addMesh(triangulated) except TypeError: print(f"Error al procesar {len(pts)} puntos: {str(e)}") mesh.removeDuplicatedPoints() mesh.removeFoldsOnSurface() obj.InitialMesh = mesh.copy() Mesh.show(mesh) if prop == "PointsGroup" or prop == "CuttingBoundary": if obj.PointsGroup and obj.CuttingBoundary: bnd = obj.CuttingBoundary.Shape if len(bnd.Faces) == 0: pts = [ver.Point for ver in bnd.Vertexes] pts.append(pts[0]) bnd = Part.makePolygon(pts) # TODO: not use the first point, else the Origin in "Site". # It is standard for everything. firstPoint = self.obj.PointsGroup.Points.Points[0] nbase = FreeCAD.Vector(firstPoint.x, firstPoint.y, firstPoint.z) data = [] for point in self.obj.PointsGroup.Points.Points: tmp = FreeCAD.Vector(0, 0, 0).add(point) tmp.z = 0 if bnd.isInside(tmp, 0, True): p = point - nbase data.append([float(p.x), float(p.y), float(p.z)]) Data = np.array(data) data.clear() import MeshTools.Triangulation as Triangulation mesh = Triangulation.Triangulate(Data) if obj.DEM: obj.DEM = None obj.mesh = mesh def execute(self, obj): '''''' #print(" ----- Terrain - EXECUTE ----------") def __getstate__(self): return self.Type def __setstate__(self, state): if state: self.Type = state class ViewProviderTerrain: "A View Provider for the Pipe object" def __init__(self, vobj): self.boundary_color = None self.edge_style = None self.edge_color = None self.edge_material = None self.face_material = None self.triangles = None self.geo_coords = None self.setProperties(vobj) def setProperties(self, vobj): # Triangulation properties. pl = vobj.PropertiesList if not ("Transparency" in pl): '''vobj.addProperty("App::PropertyIntegerConstraint", "Transparency", "Surface Style", "Set triangle face transparency").Transparency = (50, 0, 100, 1)''' if not ("ShapeColor" in pl): vobj.addProperty("App::PropertyColor", "ShapeColor", "Surface Style", "Set triangle face color").ShapeColor = (0.0, 0.667, 0.49, vobj.Transparency / 100) if not ("ShapeMaterial" in pl): vobj.addProperty("App::PropertyMaterial", "ShapeMaterial", "Surface Style", "Triangle face material").ShapeMaterial = FreeCAD.Material() if not ("LineTransparency" in pl): vobj.addProperty("App::PropertyIntegerConstraint", "LineTransparency", "Surface Style", "Set triangle edge transparency").LineTransparency = (50, 0, 100, 1) if not ("LineColor" in pl): vobj.addProperty("App::PropertyColor", "LineColor", "Surface Style", "Set triangle face color").LineColor = (0.5, 0.5, 0.5, vobj.LineTransparency / 100) if not ("LineMaterial" in pl): vobj.addProperty("App::PropertyMaterial", "LineMaterial", "Surface Style", "Triangle face material").LineMaterial = FreeCAD.Material() if not ("LineWidth" in pl): vobj.addProperty("App::PropertyFloatConstraint", "LineWidth", "Surface Style", "Set triangle edge line width").LineWidth = (0.0, 1.0, 20.0, 1.0) # Boundary properties. if not ("BoundaryColor" in pl): vobj.addProperty("App::PropertyColor", "BoundaryColor", "Boundary Style", "Set boundary contour color").BoundaryColor = (0.0, 0.75, 1.0, 0.0) if not ("BoundaryWidth" in pl): vobj.addProperty("App::PropertyFloatConstraint", "BoundaryWidth", "Boundary Style", "Set boundary contour line width").BoundaryWidth = (3.0, 1.0, 20.0, 1.0) if not ("BoundaryPattern" in pl): vobj.addProperty("App::PropertyEnumeration", "BoundaryPattern", "Boundary Style", "Set a line pattern for boundary").BoundaryPattern = [*line_patterns] if not ("PatternScale" in pl): vobj.addProperty("App::PropertyIntegerConstraint", "PatternScale", "Boundary Style", "Scale the line pattern").PatternScale = (3, 1, 20, 1) # Contour properties. if not ("MajorColor" in pl): vobj.addProperty("App::PropertyColor", "MajorColor", "Contour Style", "Set major contour color").MajorColor = (1.0, 0.0, 0.0, 0.0) if not ("MajorWidth" in pl): vobj.addProperty("App::PropertyFloatConstraint", "MajorWidth", "Contour Style", "Set major contour line width").MajorWidth = (4.0, 1.0, 20.0, 1.0) if not ("MinorColor" in pl): vobj.addProperty("App::PropertyColor", "MinorColor", "Contour Style", "Set minor contour color").MinorColor = (1.0, 1.0, 0.0, 0.0) if not ("MinorWidth" in pl): vobj.addProperty("App::PropertyFloatConstraint", "MinorWidth", "Contour Style", "Set major contour line width").MinorWidth = (2.0, 1.0, 20.0, 1.0) vobj.Proxy = self self.Object = vobj.Object # Inicializar colores correctamente vobj.ShapeMaterial.DiffuseColor = vobj.ShapeColor def onDocumentRestored(self, vobj): self.setProperties(vobj) def onChanged(self, vobj, prop): """ Update Object visuals when a view property changed. """ if prop == "ShapeColor" or prop == "Transparency": if hasattr(vobj, "ShapeColor") and hasattr(vobj, "Transparency"): color = vobj.getPropertyByName("ShapeColor") transparency = vobj.getPropertyByName("Transparency") color = (color[0], color[1], color[2], 50 / 100) vobj.ShapeMaterial.DiffuseColor = color if prop == "ShapeMaterial": if hasattr(vobj, "ShapeMaterial"): material = vobj.getPropertyByName("ShapeMaterial") self.face_material.diffuseColor.setValue(material.DiffuseColor[:3]) self.face_material.transparency = material.DiffuseColor[3] if prop == "LineColor" or prop == "LineTransparency": if hasattr(vobj, "LineColor") and hasattr(vobj, "LineTransparency"): color = vobj.getPropertyByName("LineColor") transparency = vobj.getPropertyByName("LineTransparency") color = (color[0], color[1], color[2], transparency / 100) vobj.LineMaterial.DiffuseColor = color if prop == "LineMaterial": material = vobj.getPropertyByName(prop) self.edge_material.diffuseColor.setValue(material.DiffuseColor[:3]) self.edge_material.transparency = material.DiffuseColor[3] if prop == "LineWidth": width = vobj.getPropertyByName(prop) self.edge_style.lineWidth = width if prop == "BoundaryColor": color = vobj.getPropertyByName(prop) self.boundary_color.rgb = color[:3] if prop == "BoundaryWidth": width = vobj.getPropertyByName(prop) self.boundary_style.lineWidth = width if prop == "BoundaryPattern": if hasattr(vobj, "BoundaryPattern"): pattern = vobj.getPropertyByName(prop) self.boundary_style.linePattern = line_patterns[pattern] if prop == "PatternScale": if hasattr(vobj, "PatternScale"): scale = vobj.getPropertyByName(prop) self.boundary_style.linePatternScaleFactor = scale if prop == "MajorColor": color = vobj.getPropertyByName(prop) self.major_color.rgb = color[:3] if prop == "MajorWidth": width = vobj.getPropertyByName(prop) self.major_style.lineWidth = width if prop == "MinorColor": color = vobj.getPropertyByName(prop) self.minor_color.rgb = color[:3] if prop == "MinorWidth": width = vobj.getPropertyByName(prop) self.minor_style.lineWidth = width def attach(self, vobj): ''' Create Object visuals in 3D view.''' # GeoCoords Node. self.geo_coords = coin.SoGeoCoordinate() # Surface features. self.triangles = coin.SoIndexedFaceSet() self.face_material = coin.SoMaterial() self.edge_material = coin.SoMaterial() self.edge_color = coin.SoBaseColor() self.edge_style = coin.SoDrawStyle() self.edge_style.style = coin.SoDrawStyle.LINES shape_hints = coin.SoShapeHints() shape_hints.vertex_ordering = coin.SoShapeHints.COUNTERCLOCKWISE mat_binding = coin.SoMaterialBinding() mat_binding.value = coin.SoMaterialBinding.PER_FACE offset = coin.SoPolygonOffset() offset.styles = coin.SoPolygonOffset.LINES offset.factor = -2.0 # Boundary features. '''self.boundary_color = coin.SoBaseColor() self.boundary_coords = coin.SoGeoCoordinate() self.boundary_lines = coin.SoLineSet() self.boundary_style = coin.SoDrawStyle() self.boundary_style.style = coin.SoDrawStyle.LINES''' # Boundary root. '''boundaries = coin.SoType.fromName('SoFCSelection').createInstance() boundaries.style = 'EMISSIVE_DIFFUSE' boundaries.addChild(self.boundary_color) boundaries.addChild(self.boundary_style) boundaries.addChild(self.boundary_coords) boundaries.addChild(self.boundary_lines)''' # Major Contour features. '''self.major_color = coin.SoBaseColor() self.major_coords = coin.SoGeoCoordinate() self.major_lines = coin.SoLineSet() self.major_style = coin.SoDrawStyle() self.major_style.style = coin.SoDrawStyle.LINES''' # Major Contour root. '''major_contours = coin.SoSeparator() major_contours.addChild(self.major_color) major_contours.addChild(self.major_style) major_contours.addChild(self.major_coords) major_contours.addChild(self.major_lines)''' # Minor Contour features. '''self.minor_color = coin.SoBaseColor() self.minor_coords = coin.SoGeoCoordinate() self.minor_lines = coin.SoLineSet() self.minor_style = coin.SoDrawStyle() self.minor_style.style = coin.SoDrawStyle.LINES''' # Minor Contour root. '''minor_contours = coin.SoSeparator() minor_contours.addChild(self.minor_color) minor_contours.addChild(self.minor_style) minor_contours.addChild(self.minor_coords) minor_contours.addChild(self.minor_lines)''' # Highlight for selection. highlight = coin.SoType.fromName('SoFCSelection').createInstance() highlight.style = 'EMISSIVE_DIFFUSE' highlight.addChild(shape_hints) highlight.addChild(mat_binding) highlight.addChild(self.geo_coords) highlight.addChild(self.triangles) #highlight.addChild(boundaries) # Face root. face = coin.SoSeparator() face.addChild(self.face_material) face.addChild(highlight) # Edge root. edge = coin.SoSeparator() edge.addChild(self.edge_material) edge.addChild(self.edge_style) edge.addChild(highlight) # Surface root. surface_root = coin.SoSeparator() surface_root.addChild(face) surface_root.addChild(offset) surface_root.addChild(edge) #surface_root.addChild(major_contours) #surface_root.addChild(minor_contours) vobj.addDisplayMode(surface_root, "Surface") # Boundary root. #boundary_root = coin.SoSeparator() #boundary_root.addChild(boundaries) #vobj.addDisplayMode(boundary_root, "Boundary") # Elevation/Shaded root. '''shaded_root = coin.SoSeparator() shaded_root.addChild(face) vobj.addDisplayMode(shaded_root, "Elevation") vobj.addDisplayMode(shaded_root, "Slope") vobj.addDisplayMode(shaded_root, "Shaded")''' # Flat Lines root. flatlines_root = coin.SoSeparator() flatlines_root.addChild(face) flatlines_root.addChild(offset) flatlines_root.addChild(edge) vobj.addDisplayMode(flatlines_root, "Flat Lines") # Wireframe root. wireframe_root = coin.SoSeparator() wireframe_root.addChild(edge) #wireframe_root.addChild(major_contours) #wireframe_root.addChild(minor_contours) vobj.addDisplayMode(wireframe_root, "Wireframe") # Take features from properties. self.onChanged(vobj, "ShapeColor") self.onChanged(vobj, "LineColor") self.onChanged(vobj, "LineWidth") #self.onChanged(vobj, "BoundaryColor") #self.onChanged(vobj, "BoundaryWidth") #self.onChanged(vobj, "BoundaryPattern") #self.onChanged(vobj, "PatternScale") #self.onChanged(vobj, "MajorColor") #self.onChanged(vobj, "MajorWidth") #self.onChanged(vobj, "MinorColor") #self.onChanged(vobj, "MinorWidth") def updateData(self, obj, prop): ''' Update Object visuals when a data property changed. ''' # Set geosystem. geo_system = ["UTM", FreeCAD.ActiveDocument.Site.UtmZone, "FLAT"] self.geo_coords.geoSystem.setValues(geo_system) ''' self.boundary_coords.geoSystem.setValues(geo_system) self.major_coords.geoSystem.setValues(geo_system) self.minor_coords.geoSystem.setValues(geo_system) ''' if prop == "Mesh": if obj.mesh: print("Mostrar mesh") mesh = obj.mesh vertices = [tuple(v) for v in mesh.Topology[0]] faces = [] for face in mesh.Topology[1]: faces.extend(face) faces.append(-1) # Asignar a los nodos de visualización self.geo_coords.point.values = vertices # <-- ¡Clave! self.triangles.coordIndex.values = faces # <-- ¡Clave! def getDisplayModes(self, vobj): ''' Return a list of display modes. ''' modes = ["Surface", "Boundary"] return modes def getDefaultDisplayMode(self): ''' Return the name of the default display mode. ''' return "Surface" def claimChildren(self): if hasattr(self, "Object") and self.Object: return [self.Object.CuttingBoundary, ] return [] def getIcon(self): return str(os.path.join(DirIcons, "terrain.svg")) def __getstate__(self): """ Save variables to file. """ return None def __setstate__(self, state): """ Get variables from file. """ return None '''class _CommandTerrain: "the PVPlant Terrain command definition" def GetResources(self): return {'Pixmap': str(os.path.join(DirIcons, "terrain.svg")), 'MenuText': "Terrain", 'Accel': "S, T", 'ToolTip': "Creates a Terrain object from setup dialog."} def IsActive(self): return (not (FreeCAD.ActiveDocument is None) and not (FreeCAD.ActiveDocument.getObject("Site") is None) and (FreeCAD.ActiveDocument.getObject("Terrain") is None)) def Activated(self): makeTerrain() # task = _TerrainTaskPanel() # FreeCADGui.Control.showDialog(task) return if FreeCAD.GuiUp: FreeCADGui.addCommand('Terrain', _CommandTerrain())'''