Compare commits
3 Commits
5db8f5439d
...
d61260fdd3
| Author | SHA1 | Date | |
|---|---|---|---|
| d61260fdd3 | |||
| 049898c939 | |||
| 3a188cc47d |
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import ArchComponent
|
||||
import Part
|
||||
import os
|
||||
import zipfile
|
||||
import re
|
||||
@@ -48,17 +49,18 @@ import PVPlantResources
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
Dir3dObjects = os.path.join(PVPlantResources.DirResources, "3dObjects")
|
||||
|
||||
vector = ["Y", "YN", "Z", "ZN", "D"]
|
||||
|
||||
def makePCS():
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "StringInverter")
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PowerConversionSystem")
|
||||
PowerConverter(obj)
|
||||
ViewProviderStringInverter(obj.ViewObject)
|
||||
ViewProviderPowerConverter(obj.ViewObject)
|
||||
|
||||
try:
|
||||
folder = FreeCAD.ActiveDocument.StringInverters
|
||||
folder = FreeCAD.ActiveDocument.PowerConversionSystemGroup
|
||||
except:
|
||||
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'StringInverters')
|
||||
folder.Label = "StringInverters"
|
||||
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'PowerConversionSystemGroup')
|
||||
folder.Label = "PowerConversionSystemGroup"
|
||||
folder.addObject(obj)
|
||||
return obj
|
||||
|
||||
@@ -67,9 +69,6 @@ class PowerConverter(ArchComponent.Component):
|
||||
def __init__(self, obj):
|
||||
''' Initialize the Area object '''
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
|
||||
self.oldMPPTs = 0
|
||||
|
||||
self.Type = None
|
||||
self.obj = None
|
||||
self.setProperties(obj)
|
||||
@@ -77,36 +76,69 @@ class PowerConverter(ArchComponent.Component):
|
||||
def setProperties(self, obj):
|
||||
pl = obj.PropertiesList
|
||||
|
||||
if not "File" in pl:
|
||||
obj.addProperty("App::PropertyFile",
|
||||
"File",
|
||||
"Inverter",
|
||||
"The base file this component is built upon")
|
||||
|
||||
if not ("MPPTs" in pl):
|
||||
obj.addProperty("App::PropertyQuantity",
|
||||
"MPPTs",
|
||||
"Inverter",
|
||||
"Points that define the area"
|
||||
).MPPTs = 0
|
||||
|
||||
if not ("Generator" in pl):
|
||||
# Transformer properties
|
||||
if not "Technology" in pl:
|
||||
obj.addProperty("App::PropertyEnumeration",
|
||||
"Generator",
|
||||
"Inverter",
|
||||
"Points that define the area"
|
||||
).Generator = ["Generic", "Library"]
|
||||
obj.Generator = "Generic"
|
||||
"Technology",
|
||||
"Transformer",
|
||||
"Number of phases and type of transformer"
|
||||
).Technology = ["Single Phase Transformer", "Three Phase Transformer"]
|
||||
|
||||
if not ("Type" in pl):
|
||||
obj.addProperty("App::PropertyString",
|
||||
"Type",
|
||||
"Base",
|
||||
"Points that define the area"
|
||||
).Type = "PowerConverter"
|
||||
obj.setEditorMode("Type", 1)
|
||||
if not "PowerPrimary" in pl:
|
||||
obj.addProperty("App::PropertyPower",
|
||||
"PowerPrimary",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").PowerPrimary = 6000000000
|
||||
|
||||
self.Type = obj.Type
|
||||
if not "PowerSecundary1" in pl:
|
||||
obj.addProperty("App::PropertyPower",
|
||||
"PowerSecundary1",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").PowerSecundary1 = 3000000000
|
||||
|
||||
if not "PowerSecundary2" in pl:
|
||||
obj.addProperty("App::PropertyPower",
|
||||
"PowerSecundary2",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").PowerSecundary2 = 3000000000
|
||||
|
||||
if not "VoltagePrimary" in pl:
|
||||
obj.addProperty("App::PropertyElectricPotential",
|
||||
"VoltagePrimary",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").VoltagePrimary = 33000000000
|
||||
|
||||
if not "VoltageSecundary1" in pl:
|
||||
obj.addProperty("App::PropertyElectricPotential",
|
||||
"VoltageSecundary1",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").VoltageSecundary1 = 11000000000
|
||||
|
||||
if not "VoltageSecundary2" in pl:
|
||||
obj.addProperty("App::PropertyElectricPotential",
|
||||
"VoltageSecundary2",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").VoltageSecundary2 = 11000000000
|
||||
|
||||
if not "VectorPrimary" in pl:
|
||||
obj.addProperty("App::PropertyEnumeration",
|
||||
"VectorPrimary",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").VectorPrimary = vector
|
||||
|
||||
if not "VectorSecundary1" in pl:
|
||||
obj.addProperty("App::PropertyEnumeration",
|
||||
"VectorSecundary1",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").VectorSecundary1 = vector
|
||||
|
||||
if not "VectorSecundary2" in pl:
|
||||
obj.addProperty("App::PropertyEnumeration",
|
||||
"VectorSecundary2",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").VectorSecundary2 = vector
|
||||
|
||||
self.Type = "PowerConverter"
|
||||
obj.Proxy = self
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
@@ -114,263 +146,34 @@ class PowerConverter(ArchComponent.Component):
|
||||
self.setProperties(obj)
|
||||
|
||||
def onBeforeChange(self, obj, prop):
|
||||
|
||||
if prop == "MPPTs":
|
||||
self.oldMPPTs = int(obj.MPPTs)
|
||||
''' '''
|
||||
# This method is called before a property is changed.
|
||||
# It can be used to validate the property value or to update other properties.
|
||||
# If the property is not valid, you can raise an exception.
|
||||
# If you want to prevent the change, you can return False.
|
||||
# Otherwise, return True to allow the change.
|
||||
return True
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
''' '''
|
||||
|
||||
if prop == "Generator":
|
||||
if obj.Generator == "Generic":
|
||||
obj.setEditorMode("MPPTs", 0)
|
||||
else:
|
||||
obj.setEditorMode("MPPTs", 1)
|
||||
|
||||
if prop == "MPPTs":
|
||||
''' '''
|
||||
if self.oldMPPTs > obj.MPPTs:
|
||||
''' borrar sobrantes '''
|
||||
obj.removeProperty()
|
||||
|
||||
elif self.oldMPPTs < obj.MPPTs:
|
||||
''' crear los faltantes '''
|
||||
for i in range(self.oldMPPTs, int(obj.MPPTs)):
|
||||
''' '''
|
||||
print(i)
|
||||
else:
|
||||
pass
|
||||
|
||||
if (prop == "File") and obj.File:
|
||||
''' '''
|
||||
|
||||
def execute(self, obj):
|
||||
''' '''
|
||||
# obj.Shape: compound
|
||||
# |- body: compound
|
||||
# |-- inverter: solid
|
||||
# |-- door: solid
|
||||
# |-- holder: solid
|
||||
|
||||
# |- connectors: compound
|
||||
# |-- DC: compound
|
||||
# |--- MPPT 1..x: compound
|
||||
# |---- positive: compound
|
||||
# |----- connector 1..y: ??
|
||||
# |---- negative 1..y: compound
|
||||
# |----- connector 1..y: ??
|
||||
# |-- AC: compound
|
||||
# |--- R,S,T,: ??
|
||||
# |-- Communication
|
||||
# |- transformer: solid
|
||||
# |- primary switchgear: compound
|
||||
# |- secundary 1 switchgear: compound
|
||||
# |- secundary 2 switchgear: compound
|
||||
|
||||
pl = obj.Placement
|
||||
filename = self.getFile(obj)
|
||||
if filename:
|
||||
parts = self.getPartsList(obj)
|
||||
if parts:
|
||||
zdoc = zipfile.ZipFile(filename)
|
||||
if zdoc:
|
||||
f = zdoc.open(parts[list(parts.keys())[-1]][1])
|
||||
shapedata = f.read()
|
||||
f.close()
|
||||
shapedata = shapedata.decode("utf8")
|
||||
shape = self.cleanShape(shapedata, obj, parts[list(parts.keys())[-1]][2])
|
||||
obj.Shape = shape
|
||||
if not pl.isIdentity():
|
||||
obj.Placement = pl
|
||||
obj.MPPTs = len(shape.SubShapes[1].SubShapes[0].SubShapes)
|
||||
obj.Shape = Part.makeBox(6058, 2438, 2591) # Placeholder for the shape
|
||||
obj.Placement = pl
|
||||
|
||||
def cleanShape(self, shapedata, obj, materials):
|
||||
"cleans the imported shape"
|
||||
|
||||
import Part
|
||||
shape = Part.Shape()
|
||||
shape.importBrepFromString(shapedata)
|
||||
'''if obj.FuseArch and materials:
|
||||
# separate lone edges
|
||||
shapes = []
|
||||
for edge in shape.Edges:
|
||||
found = False
|
||||
for solid in shape.Solids:
|
||||
for soledge in solid.Edges:
|
||||
if edge.hashCode() == soledge.hashCode():
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
break
|
||||
if found:
|
||||
break
|
||||
else:
|
||||
shapes.append(edge)
|
||||
print("solids:",len(shape.Solids),"mattable:",materials)
|
||||
for key,solindexes in materials.items():
|
||||
if key == "Undefined":
|
||||
# do not join objects with no defined material
|
||||
for solindex in [int(i) for i in solindexes.split(",")]:
|
||||
shapes.append(shape.Solids[solindex])
|
||||
else:
|
||||
fusion = None
|
||||
for solindex in [int(i) for i in solindexes.split(",")]:
|
||||
if not fusion:
|
||||
fusion = shape.Solids[solindex]
|
||||
else:
|
||||
fusion = fusion.fuse(shape.Solids[solindex])
|
||||
if fusion:
|
||||
shapes.append(fusion)
|
||||
shape = Part.makeCompound(shapes)
|
||||
try:
|
||||
shape = shape.removeSplitter()
|
||||
except Exception:
|
||||
print(obj.Label,": error removing splitter")'''
|
||||
return shape
|
||||
|
||||
def getFile(self, obj, filename=None):
|
||||
"gets a valid file, if possible"
|
||||
|
||||
if not filename:
|
||||
filename = obj.File
|
||||
if not filename:
|
||||
return None
|
||||
if not filename.lower().endswith(".fcstd"):
|
||||
return None
|
||||
if not os.path.exists(filename):
|
||||
# search for the file in the current directory if not found
|
||||
basename = os.path.basename(filename)
|
||||
currentdir = os.path.dirname(obj.Document.FileName)
|
||||
altfile = os.path.join(currentdir,basename)
|
||||
if altfile == obj.Document.FileName:
|
||||
return None
|
||||
elif os.path.exists(altfile):
|
||||
return altfile
|
||||
else:
|
||||
# search for subpaths in current folder
|
||||
altfile = None
|
||||
subdirs = self.splitall(os.path.dirname(filename))
|
||||
for i in range(len(subdirs)):
|
||||
subpath = [currentdir]+subdirs[-i:]+[basename]
|
||||
altfile = os.path.join(*subpath)
|
||||
if os.path.exists(altfile):
|
||||
return altfile
|
||||
return None
|
||||
return filename
|
||||
|
||||
def getPartsList(self, obj, filename=None):
|
||||
|
||||
"returns a list of Part-based objects in a FCStd file"
|
||||
|
||||
parts = {}
|
||||
materials = {}
|
||||
filename = self.getFile(obj,filename)
|
||||
if not filename:
|
||||
return parts
|
||||
zdoc = zipfile.ZipFile(filename)
|
||||
with zdoc.open("Document.xml") as docf:
|
||||
name = None
|
||||
label = None
|
||||
part = None
|
||||
materials = {}
|
||||
writemode = False
|
||||
for line in docf:
|
||||
line = line.decode("utf8")
|
||||
if "<Object name=" in line:
|
||||
n = re.findall('name=\"(.*?)\"',line)
|
||||
if n:
|
||||
name = n[0]
|
||||
elif "<Property name=\"Label\"" in line:
|
||||
writemode = True
|
||||
elif writemode and "<String value=" in line:
|
||||
n = re.findall('value=\"(.*?)\"',line)
|
||||
if n:
|
||||
label = n[0]
|
||||
writemode = False
|
||||
elif "<Property name=\"Shape\" type=\"Part::PropertyPartShape\"" in line:
|
||||
writemode = True
|
||||
elif writemode and "<Part file=" in line:
|
||||
n = re.findall('file=\"(.*?)\"',line)
|
||||
if n:
|
||||
part = n[0]
|
||||
writemode = False
|
||||
elif "<Property name=\"MaterialsTable\" type=\"App::PropertyMap\"" in line:
|
||||
writemode = True
|
||||
elif writemode and "<Item key=" in line:
|
||||
n = re.findall('key=\"(.*?)\"',line)
|
||||
v = re.findall('value=\"(.*?)\"',line)
|
||||
if n and v:
|
||||
materials[n[0]] = v[0]
|
||||
elif writemode and "</Map>" in line:
|
||||
writemode = False
|
||||
elif "</Object>" in line:
|
||||
if name and label and part:
|
||||
parts[name] = [label,part,materials]
|
||||
name = None
|
||||
label = None
|
||||
part = None
|
||||
materials = {}
|
||||
writemode = False
|
||||
return parts
|
||||
|
||||
def getColors(self,obj):
|
||||
|
||||
"returns the DiffuseColor of the referenced object"
|
||||
|
||||
filename = self.getFile(obj)
|
||||
if not filename:
|
||||
return None
|
||||
part = obj.Part
|
||||
if not obj.Part:
|
||||
return None
|
||||
zdoc = zipfile.ZipFile(filename)
|
||||
if not "GuiDocument.xml" in zdoc.namelist():
|
||||
return None
|
||||
colorfile = None
|
||||
with zdoc.open("GuiDocument.xml") as docf:
|
||||
writemode1 = False
|
||||
writemode2 = False
|
||||
for line in docf:
|
||||
line = line.decode("utf8")
|
||||
if ("<ViewProvider name=" in line) and (part in line):
|
||||
writemode1 = True
|
||||
elif writemode1 and ("<Property name=\"DiffuseColor\"" in line):
|
||||
writemode1 = False
|
||||
writemode2 = True
|
||||
elif writemode2 and ("<ColorList file=" in line):
|
||||
n = re.findall('file=\"(.*?)\"',line)
|
||||
if n:
|
||||
colorfile = n[0]
|
||||
break
|
||||
if not colorfile:
|
||||
return None
|
||||
if not colorfile in zdoc.namelist():
|
||||
return None
|
||||
colors = []
|
||||
cf = zdoc.open(colorfile)
|
||||
buf = cf.read()
|
||||
cf.close()
|
||||
for i in range(1,int(len(buf)/4)):
|
||||
colors.append((buf[i*4+3]/255.0,buf[i*4+2]/255.0,buf[i*4+1]/255.0,buf[i*4]/255.0))
|
||||
if colors:
|
||||
return colors
|
||||
return None
|
||||
|
||||
def splitall(self,path):
|
||||
|
||||
"splits a path between its components"
|
||||
|
||||
allparts = []
|
||||
while 1:
|
||||
parts = os.path.split(path)
|
||||
if parts[0] == path: # sentinel for absolute paths
|
||||
allparts.insert(0, parts[0])
|
||||
break
|
||||
elif parts[1] == path: # sentinel for relative paths
|
||||
allparts.insert(0, parts[1])
|
||||
break
|
||||
else:
|
||||
path = parts[0]
|
||||
allparts.insert(0, parts[1])
|
||||
return allparts
|
||||
|
||||
class ViewProviderStringInverter(ArchComponent.ViewProviderComponent):
|
||||
class ViewProviderPowerConverter(ArchComponent.ViewProviderComponent):
|
||||
def __init__(self, vobj):
|
||||
ArchComponent.ViewProviderComponent.__init__(self, vobj)
|
||||
|
||||
@@ -381,12 +184,12 @@ class CommandPowerConverter:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "Inverter.svg")),
|
||||
'Accel': "E, I",
|
||||
'MenuText': "String Inverter",
|
||||
'ToolTip': "String Placement",}
|
||||
'Accel': "E, P",
|
||||
'MenuText': "Power Converter",
|
||||
'ToolTip': "Power Converter",}
|
||||
|
||||
def Activated(self):
|
||||
sinverter = makeStringInverter()
|
||||
sinverter = makePCS()
|
||||
|
||||
def IsActive(self):
|
||||
active = not (FreeCAD.ActiveDocument is None)
|
||||
|
||||
@@ -141,29 +141,31 @@ def groupTrackersToTransformers(transformer_power, max_distance):
|
||||
|
||||
for i, group in enumerate(transformer_groups):
|
||||
# Crear la esfera que representará el CT
|
||||
ct_sphere = FreeCAD.ActiveDocument.addObject("Part::Sphere", f"CT_{i + 1}")
|
||||
ct_sphere.Radius = 5000 # 2m de radio
|
||||
ct_sphere.Placement.Base = FreeCAD.Vector(group['center'][0], group['center'][1], 0)
|
||||
ct_shape = FreeCAD.ActiveDocument.addObject("Part::Box", f"CT_{i + 1}")
|
||||
ct_shape.Length = 6058
|
||||
ct_shape.Width = 2438
|
||||
ct_shape.Height = 2591
|
||||
ct_shape.Placement.Base = FreeCAD.Vector(group['center'][0], group['center'][1], 0)
|
||||
|
||||
# Añadir propiedades personalizadas
|
||||
ct_sphere.addProperty("App::PropertyLinkList", "Trackers", "CT",
|
||||
ct_shape.addProperty("App::PropertyLinkList", "Trackers", "CT",
|
||||
"Lista de trackers asociados a este CT")
|
||||
ct_sphere.addProperty("App::PropertyFloat", "TotalPower", "CT",
|
||||
ct_shape.addProperty("App::PropertyFloat", "TotalPower", "CT",
|
||||
"Potencia total del grupo (W)")
|
||||
ct_sphere.addProperty("App::PropertyFloat", "NominalPower", "CT",
|
||||
ct_shape.addProperty("App::PropertyFloat", "NominalPower", "CT",
|
||||
"Potencia nominal del transformador (W)")
|
||||
ct_sphere.addProperty("App::PropertyFloat", "Utilization", "CT",
|
||||
ct_shape.addProperty("App::PropertyFloat", "Utilization", "CT",
|
||||
"Porcentaje de utilización (Total/Nominal)")
|
||||
|
||||
# Establecer valores de las propiedades
|
||||
ct_sphere.Trackers = group['trackers']
|
||||
ct_sphere.TotalPower = group['total_power'].Value
|
||||
ct_sphere.NominalPower = transformer_power
|
||||
ct_sphere.Utilization = (group['total_power'].Value / transformer_power) * 100
|
||||
ct_shape.Trackers = group['trackers']
|
||||
ct_shape.TotalPower = group['total_power'].Value
|
||||
ct_shape.NominalPower = transformer_power
|
||||
ct_shape.Utilization = (group['total_power'].Value / transformer_power) * 100
|
||||
|
||||
# Configurar visualización
|
||||
# Calcular color basado en utilización (verde < 100%, amarillo < 110%, rojo > 110%)
|
||||
utilization = ct_sphere.Utilization
|
||||
utilization = ct_shape.Utilization
|
||||
if utilization <= 100:
|
||||
color = (0.0, 1.0, 0.0) # Verde
|
||||
elif utilization <= 110:
|
||||
@@ -171,18 +173,19 @@ def groupTrackersToTransformers(transformer_power, max_distance):
|
||||
else:
|
||||
color = (1.0, 0.0, 0.0) # Rojo
|
||||
|
||||
ct_sphere.ViewObject.ShapeColor = color
|
||||
ct_sphere.ViewObject.Transparency = 40 # 40% de transparencia
|
||||
ct_shape.ViewObject.ShapeColor = color
|
||||
ct_shape.ViewObject.Transparency = 40 # 40% de transparencia
|
||||
|
||||
# Añadir etiqueta con información
|
||||
ct_sphere.ViewObject.DisplayMode = "Shaded"
|
||||
ct_sphere.Label = f"CT {i + 1} ({ct_sphere.TotalPower / 1000:.1f}kW/{ct_sphere.NominalPower / 1000:.1f}kW)"
|
||||
ct_shape.ViewObject.DisplayMode = "Shaded"
|
||||
ct_shape.Label = f"CT {i + 1} ({ct_shape.TotalPower / 1000:.1f}kW/{ct_shape.NominalPower / 1000:.1f}kW)"
|
||||
|
||||
# Añadir al grupo principal
|
||||
transformer_group.addObject(ct_sphere)
|
||||
transformer_group.addObject(ct_shape)
|
||||
|
||||
FreeCAD.Console.PrintMessage(f"Se crearon {len(transformer_groups)} centros de transformación\n")
|
||||
onSelectGatePoint()
|
||||
|
||||
#onSelectGatePoint()
|
||||
|
||||
|
||||
import FreeCAD, FreeCADGui, Part
|
||||
@@ -195,7 +198,7 @@ class InternalPathCreator:
|
||||
self.gate_point = gate_point
|
||||
self.strategy = strategy
|
||||
self.path_width = path_width
|
||||
self.ct_spheres = []
|
||||
self.ct_shapes = []
|
||||
self.ct_positions = []
|
||||
|
||||
def get_transformers(self):
|
||||
@@ -204,13 +207,13 @@ class InternalPathCreator:
|
||||
FreeCAD.Console.PrintError("No se encontró el grupo 'Transformers'\n")
|
||||
return False
|
||||
|
||||
self.ct_spheres = transformers_group.Group
|
||||
if not self.ct_spheres:
|
||||
self.ct_shapes = transformers_group.Group
|
||||
if not self.ct_shapes:
|
||||
FreeCAD.Console.PrintWarning("No hay Centros de Transformación en el grupo\n")
|
||||
return False
|
||||
|
||||
# Obtener las posiciones de los CTs
|
||||
for sphere in self.ct_spheres:
|
||||
for sphere in self.ct_shapes:
|
||||
base = sphere.Placement.Base
|
||||
self.ct_positions.append(FreeCAD.Vector(base.x, base.y, 0))
|
||||
return True
|
||||
@@ -263,6 +266,8 @@ class InternalPathCreator:
|
||||
y_proj = slope * x_proj + intercept
|
||||
return FreeCAD.Vector(x_proj, y_proj, 0)
|
||||
|
||||
# return slope * x + intercept --> desde placement
|
||||
|
||||
projected_points = [project_point(p) for p in all_points]
|
||||
|
||||
# Calcular distancias a lo largo de la línea
|
||||
|
||||
@@ -76,8 +76,10 @@ def getWire(wire, nospline=False, width=.0):
|
||||
import DraftGeomUtils
|
||||
import math
|
||||
|
||||
offset = FreeCAD.ActiveDocument.Site.Origin
|
||||
|
||||
def fmt(vec, b=0.0):
|
||||
return (vec.x * 0.001, vec.y * 0.001, width, width, b)
|
||||
return ((vec.x + offset.x) * 0.001, (vec.y + offset.y) * 0.001, width, width, b)
|
||||
|
||||
points = []
|
||||
edges = Part.__sortEdges__(wire.Edges)
|
||||
@@ -626,6 +628,7 @@ layers = [
|
||||
("Available area Names", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
|
||||
("Areas Exclusion", QtGui.QColor(255, 85, 0), "Continuous", "1", True),
|
||||
("Areas Exclusion Offset", QtGui.QColor(255, 85, 0), "Continuous", "1", True),
|
||||
("Areas Exclusion Name", QtGui.QColor(255, 85, 0), "Continuous", "1", True),
|
||||
("Areas Cadastral Plot", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("Areas Cadastral Plot Name", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
@@ -646,7 +649,7 @@ layers = [
|
||||
("CIVIL External Roads Text", QtGui.QColor(255, 255, 192), "Continuous", "1", True),
|
||||
("CIVIL Internal Roads", QtGui.QColor(153, 95, 76), "Continuous", "1", True),
|
||||
("CIVIL Internal Roads Axis", QtGui.QColor(192, 192, 192), "Dashed", "1", True),
|
||||
("CIVIL External Roads Text", QtGui.QColor(192, 192, 192), "Continuous", "1", True),
|
||||
("CIVIL Internal Roads Text", QtGui.QColor(192, 192, 192), "Continuous", "1", True),
|
||||
|
||||
("Contour Line Legend text", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("Major contour line", QtGui.QColor(0, 0, 0), "Continuous", "1", True),
|
||||
@@ -893,6 +896,9 @@ class _PVPlantExportDXF(QtGui.QWidget):
|
||||
angle=0,
|
||||
layer=area_type[1]
|
||||
)
|
||||
for obj in FreeCADGui.Selection.getSelection():
|
||||
tmp = exporter.createPolyline(obj, areas_types[0][1])
|
||||
|
||||
|
||||
def writeFrameSetups(self, exporter):
|
||||
if not hasattr(FreeCAD.ActiveDocument, "Site"):
|
||||
|
||||
@@ -29,6 +29,15 @@ import Part
|
||||
import numpy
|
||||
import os
|
||||
|
||||
from xml.etree.ElementTree import Element, SubElement
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
import datetime
|
||||
from xml.dom import minidom
|
||||
|
||||
from numpy.matrixlib.defmatrix import matrix
|
||||
|
||||
from Utils import PVPlantUtils as utils
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore
|
||||
@@ -63,6 +72,11 @@ def check_collada():
|
||||
FreeCAD.Console.PrintError(translate("PVPlant", "pycollada no encontrado, soporte Collada desactivado.") + "\n")
|
||||
return COLLADA_AVAILABLE
|
||||
|
||||
# Asegurar que el texto es Unicode válido
|
||||
def safe_text(text):
|
||||
if isinstance(text, bytes):
|
||||
return text.decode('utf-8', errors='replace')
|
||||
return text
|
||||
|
||||
# from ARCH:
|
||||
def triangulate(shape):
|
||||
@@ -249,7 +263,306 @@ def export(exportList, filename, tessellation=1, colors=None):
|
||||
FreeCAD.Console.PrintMessage(translate("Arch", "file %s successfully created.") % filename)
|
||||
|
||||
|
||||
def exportToPVC(path, exportTerrain = False):
|
||||
def exportToPVC(path, exportTerrain=False):
|
||||
filename = f"{path}.pvc"
|
||||
|
||||
# 1. Validación inicial de objetos esenciales
|
||||
site = None
|
||||
for obj in FreeCAD.ActiveDocument.Objects:
|
||||
if obj.Name.startswith("Site") and hasattr(obj, 'Terrain'):
|
||||
site = obj
|
||||
break
|
||||
|
||||
if not site:
|
||||
FreeCAD.Console.PrintError("No se encontró objeto 'Site' válido\n")
|
||||
return False
|
||||
|
||||
# 2. Configuración de metadatos y autor
|
||||
generated_on = str(datetime.datetime.now())
|
||||
|
||||
try:
|
||||
author = FreeCAD.ActiveDocument.CreatedBy
|
||||
except (AttributeError, UnicodeEncodeError):
|
||||
author = "Unknown"
|
||||
|
||||
author = author.replace("<", "").replace(">", "")
|
||||
ver = FreeCAD.Version()
|
||||
appli = f"PVPlant for FreeCAD {ver[0]}.{ver[1]} build{ver[2]}"
|
||||
|
||||
# 3. Creación estructura XML base
|
||||
root = Element('COLLADA')
|
||||
root.set('xmlns', 'http://www.collada.org/2005/11/COLLADASchema')
|
||||
root.set('version', '1.4.1')
|
||||
|
||||
# 4. Sección <asset>
|
||||
asset = SubElement(root, 'asset')
|
||||
contrib = SubElement(asset, 'contributor')
|
||||
SubElement(contrib, 'author').text = safe_text(author)
|
||||
SubElement(contrib, 'authoring_tool').text = safe_text(appli)
|
||||
SubElement(asset, 'created').text = generated_on
|
||||
SubElement(asset, 'modified').text = generated_on
|
||||
SubElement(asset, 'title').text = safe_text(FreeCAD.ActiveDocument.Name)
|
||||
unit = SubElement(asset, 'unit')
|
||||
unit.set('name', 'meter')
|
||||
unit.set('meter', '1')
|
||||
|
||||
# 5. Materiales y efectos
|
||||
materials = ["Frames", "Tree_trunk", "Tree_crown", "Topography_mesh"]
|
||||
|
||||
# Library materials
|
||||
lib_materials = SubElement(root, 'library_materials')
|
||||
for i, name in enumerate(materials):
|
||||
mat = SubElement(lib_materials, 'material')
|
||||
mat.set('id', f'Material{i}')
|
||||
mat.set('name', name)
|
||||
SubElement(mat, 'instance_effect').set('url', f'#Material{i}-fx')
|
||||
|
||||
# Library effects
|
||||
lib_effects = SubElement(root, 'library_effects')
|
||||
for i, _ in enumerate(materials):
|
||||
effect = SubElement(lib_effects, 'effect')
|
||||
effect.set('id', f'Material{i}-fx')
|
||||
effect.set('name', f'Material{i}')
|
||||
profile = SubElement(effect, 'profile_COMMON')
|
||||
technique = SubElement(profile, 'technique')
|
||||
technique.set('sid', 'standard')
|
||||
lambert = SubElement(technique, 'lambert')
|
||||
|
||||
# Componentes del material
|
||||
color = SubElement(SubElement(lambert, 'emission'), 'color')
|
||||
color.set('sid', 'emission')
|
||||
color.text = '0.000000 0.000000 0.000000 1.000000'
|
||||
color = SubElement(SubElement(lambert, 'ambient'), 'color')
|
||||
color.set('sid', 'ambient')
|
||||
color.text = '0.200000 0.200000 0.200000 1.000000'
|
||||
color = SubElement(SubElement(lambert, 'diffuse'), 'color')
|
||||
color.set('sid', 'diffuse')
|
||||
color.text = '0.250000 0.500000 0.000000 1.000000'
|
||||
transparent = SubElement(lambert, 'transparent')
|
||||
transparent.set('opaque', 'RGB_ZERO')
|
||||
color = SubElement(transparent, 'color')
|
||||
color.set('sid', 'transparent')
|
||||
color.text = '0.000000 0.000000 0.000000 1.000000'
|
||||
value = SubElement(SubElement(lambert, 'transparency'), 'float')
|
||||
value.set('sid', 'transparency')
|
||||
value.text = '0.000000'
|
||||
|
||||
# 6. Geometrías
|
||||
lib_geometries = SubElement(root, 'library_geometries')
|
||||
|
||||
# 7. Escena visual
|
||||
lib_visual = SubElement(root, 'library_visual_scenes')
|
||||
visual_scene = SubElement(lib_visual, 'visual_scene')
|
||||
visual_scene.set('id', 'Scene') # cambiar a visual_scene_0
|
||||
visual_scene.set('name', 'Scene') # cambiar a Default visual scene
|
||||
|
||||
scene_node = SubElement(visual_scene, 'node')
|
||||
scene_node.set('id', 'node_0_id')
|
||||
scene_node.set('name', 'node_0_name')
|
||||
scene_node.set('sid', 'node_0_sid')
|
||||
|
||||
scene_matrix = SubElement(scene_node, 'matrix')
|
||||
scene_matrix.set('sid', 'matrix_0')
|
||||
scene_matrix.text = '1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000'
|
||||
|
||||
root_node = SubElement(scene_node, 'node')
|
||||
root_node.set('id', 'node_1_id')
|
||||
root_node.set('name', 'node_1_name')
|
||||
root_node.set('sid', 'node_1_sid')
|
||||
|
||||
# 8. Función para procesar geometrías
|
||||
def create_geometry(name, vindex, findex, material_id, objind=0, frame_data=None, isTracker = False, axis_line=None):
|
||||
"""Crea elementos COLLADA para una geometría"""
|
||||
# Source (vertices)
|
||||
source_mesh = SubElement(geom, 'mesh')
|
||||
source = SubElement(source_mesh, 'source')
|
||||
source.set('id', f'{name}-mesh_source')
|
||||
float_array = SubElement(source, 'float_array')
|
||||
float_array.set('id', f'{name}-float_array')
|
||||
float_array.set('count', str(len(vindex)))
|
||||
float_array.text = ' '.join(f'{v:.6f}' for v in vindex)
|
||||
|
||||
technique = SubElement(source, 'technique_common')
|
||||
accessor = SubElement(technique, 'accessor')
|
||||
accessor.set('count', str(len(vindex)))
|
||||
accessor.set('source', f'#{name}-float_array')
|
||||
accessor.set('stride', '3')
|
||||
for ax in ['X', 'Y', 'Z']:
|
||||
param = SubElement(accessor, 'param')
|
||||
param.set('name', ax)
|
||||
param.set('type', 'float')
|
||||
|
||||
# Vertices
|
||||
vertices = SubElement(source_mesh, 'vertices')
|
||||
vertices.set('id', f'{name}-vertices_source')
|
||||
vertices = SubElement(vertices, 'input')
|
||||
vertices.set('semantic', 'POSITION')
|
||||
vertices.set('source', f'#{name}-mesh_source')
|
||||
|
||||
# Triangles
|
||||
triangles = SubElement(source_mesh, 'triangles')
|
||||
triangles.set('count', '0')
|
||||
triangles.set('material', f'Material{material_id}')
|
||||
triangles_input = SubElement(triangles, 'input')
|
||||
triangles_input.set('offset', '0')
|
||||
triangles_input.set('semantic', 'VERTEX')
|
||||
triangles_input.set('source', f'#{name}-vertices_source')
|
||||
|
||||
p = SubElement(triangles, 'p')
|
||||
p.text = ' '.join(map(str, findex))
|
||||
|
||||
# Parámetros especiales para estructuras
|
||||
|
||||
frame_params = SubElement(source_mesh, 'tracker_parameters')
|
||||
if frame_data:
|
||||
for key, val in frame_data.items():
|
||||
elem = SubElement(frame_params, key)
|
||||
elem.text = str(val)
|
||||
|
||||
if isTracker:
|
||||
axis_parameter = SubElement(frame_params, 'axis_vertices')
|
||||
if axis_line:
|
||||
for idx, vert in enumerate(axis_line):
|
||||
array = SubElement(axis_parameter, 'float_array')
|
||||
array.set('id', f'{name}-axis_float_array{idx}')
|
||||
array.set('count', '3')
|
||||
array.text = ' '.join(f'{v:.6f}' for v in vert)
|
||||
|
||||
# 9. Procesar estructuras (frames/trackers)
|
||||
center = FreeCAD.Vector()
|
||||
if site.Terrain:
|
||||
center = site.Terrain.Mesh.BoundBox.Center
|
||||
|
||||
objind = 0
|
||||
for frame_type in site.Frames:
|
||||
is_tracker = "tracker" in frame_type.Proxy.Type.lower()
|
||||
|
||||
modules = frame_type.Shape.SubShapes[0].SubShapes[0]
|
||||
pts = []
|
||||
for i in range(4):
|
||||
pts.append(modules.BoundBox.getPoint(i))
|
||||
|
||||
new_shape = Part.Face(Part.makePolygon(pts))
|
||||
mesh = Mesh.Mesh(triangulate(new_shape))
|
||||
axis = Part.makeLine(FreeCAD.Vector(modules.BoundBox.XMin, 0, modules.BoundBox.ZMax),
|
||||
FreeCAD.Vector(modules.BoundBox.XMax, 0, modules.BoundBox.ZMax))
|
||||
|
||||
for obj in FreeCAD.ActiveDocument.Objects:
|
||||
if hasattr(obj, "Setup") and obj.Setup == frame_type:
|
||||
# Procesar geometría
|
||||
mesh.Placement = obj.getGlobalPlacement()
|
||||
axis.Placement = obj.getGlobalPlacement()
|
||||
|
||||
# Transformar vértices
|
||||
vindex = []
|
||||
for point in mesh.Points:
|
||||
adjusted = (point.Vector - center) * scale
|
||||
vindex.extend([
|
||||
-adjusted.x,
|
||||
adjusted.z,
|
||||
adjusted.y
|
||||
])
|
||||
|
||||
# Índices de caras
|
||||
findex = []
|
||||
for facet in mesh.Facets:
|
||||
findex.extend(facet.PointIndices)
|
||||
|
||||
# AXIS
|
||||
# TODO: revisar si es así:
|
||||
vaxis = []
|
||||
for vert in axis.Vertexes:
|
||||
adjusted = (vert.Point - center) * scale
|
||||
vaxis.append([
|
||||
-adjusted.x,
|
||||
adjusted.z,
|
||||
adjusted.y
|
||||
])
|
||||
|
||||
# Crear geometría COLLADA
|
||||
geom = SubElement(lib_geometries, 'geometry')
|
||||
geom.set('id', f'Frame_{objind}')
|
||||
|
||||
# Parámetros específicos de estructura
|
||||
frame_data = {
|
||||
'module_width': obj.Setup.ModuleWidth.Value,
|
||||
'module_height': obj.Setup.ModuleHeight.Value,
|
||||
'module_x_spacing': obj.Setup.ModuleColGap.Value,
|
||||
'module_y_spacing': obj.Setup.ModuleRowGap.Value,
|
||||
'module_name': 'Generic'
|
||||
}
|
||||
|
||||
if is_tracker:
|
||||
frame_data.update({
|
||||
'tracker_type': 'single_axis_trackers',
|
||||
'min_phi': obj.Setup.MinPhi.Value,
|
||||
'max_phi': obj.Setup.MaxPhi.Value,
|
||||
'min_theta': 0,
|
||||
'max_theta': 0
|
||||
})
|
||||
|
||||
create_geometry(
|
||||
name=f'Frame_{objind}',
|
||||
vindex=vindex,
|
||||
findex=findex,
|
||||
material_id=0,
|
||||
objind=objind,
|
||||
frame_data=frame_data,
|
||||
isTracker = is_tracker,
|
||||
axis_line=vaxis
|
||||
)
|
||||
|
||||
# Instancia en escena
|
||||
instance = SubElement(root_node, 'instance_geometry')
|
||||
instance.set('url', f'#Frame_{objind}')
|
||||
|
||||
bind_material = SubElement(instance, 'bind_material')
|
||||
technique_common = SubElement(bind_material, 'technique_common')
|
||||
instance_material = SubElement(technique_common, 'instance_material')
|
||||
instance_material.set('symbol', 'Material0')
|
||||
instance_material.set('target', '#Material0')
|
||||
|
||||
objind += 1
|
||||
|
||||
# 10. Procesar terreno si está habilitado
|
||||
if exportTerrain and site.Terrain:
|
||||
mesh = site.Terrain.Mesh
|
||||
vindex = []
|
||||
for point in mesh.Points:
|
||||
point = point.Vector
|
||||
vindex.extend([
|
||||
-point.x * SCALE,
|
||||
point.z * SCALE,
|
||||
point.y * SCALE
|
||||
])
|
||||
|
||||
findex = []
|
||||
for facet in mesh.Facets:
|
||||
findex.extend(facet.PointIndices)
|
||||
|
||||
geom = SubElement(lib_geometries, 'geometry')
|
||||
geom.set('id', 'Terrain')
|
||||
create_geometry('Terrain', vindex, findex, material_id=3)
|
||||
|
||||
instance = SubElement(root_node, 'instance_geometry')
|
||||
instance.set('url', '#Terrain')
|
||||
|
||||
# 11. Escena principal
|
||||
scene = SubElement(root, 'scene')
|
||||
SubElement(scene, 'instance_visual_scene').set('url', '#Scene')
|
||||
|
||||
# 12. Exportar a archivo
|
||||
xml_str = minidom.parseString(
|
||||
ElementTree.tostring(root, encoding='utf-8')
|
||||
).toprettyxml(indent=" ")
|
||||
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write(xml_str)
|
||||
|
||||
FreeCAD.Console.PrintMessage(f"Archivo PVC generado: {filename}\n")
|
||||
return True
|
||||
|
||||
def exportToPVC_old(path, exportTerrain = False):
|
||||
filename = f"{path}.pvc"
|
||||
|
||||
from xml.etree.ElementTree import Element, SubElement
|
||||
@@ -291,17 +604,18 @@ def exportToPVC(path, exportTerrain = False):
|
||||
|
||||
# xml: 1. Asset:
|
||||
asset = SubElement(root, 'asset')
|
||||
|
||||
asset_contributor = SubElement(asset, 'contributor')
|
||||
asset_contributor_autor = SubElement(asset_contributor, 'autor')
|
||||
#asset_contributor_autor.text = author
|
||||
asset_contributor_autor = SubElement(asset_contributor, 'author')
|
||||
asset_contributor_autor.text = author
|
||||
asset_contributor_authoring_tool = SubElement(asset_contributor, 'authoring_tool')
|
||||
#asset_contributor_authoring_tool.text = appli
|
||||
asset_contributor_authoring_tool.text = appli
|
||||
asset_contributor_comments = SubElement(asset_contributor, 'comments')
|
||||
asset_keywords = SubElement(asset, 'keywords')
|
||||
asset_revision = SubElement(asset, 'revision')
|
||||
asset_subject = SubElement(asset, 'subject')
|
||||
asset_tittle = SubElement(asset, 'title')
|
||||
#asset_tittle.text = FreeCAD.ActiveDocument.Name
|
||||
asset_tittle.text = FreeCAD.ActiveDocument.Name
|
||||
asset_unit = SubElement(asset, 'unit')
|
||||
asset_unit.set('meter', '0.001')
|
||||
asset_unit.set('name', 'millimeter')
|
||||
@@ -359,7 +673,6 @@ def exportToPVC(path, exportTerrain = False):
|
||||
# xml: 4. library_geometries:
|
||||
library_geometries = SubElement(root, 'library_geometries')
|
||||
def add_geometry(objtype, vindex, findex, objind = 0, centers = None):
|
||||
|
||||
isFrame = False
|
||||
if objtype == 0:
|
||||
geometryName = 'Frame'
|
||||
@@ -505,36 +818,20 @@ def exportToPVC(path, exportTerrain = False):
|
||||
end_time.text = '1.000000'
|
||||
|
||||
# xml: 6. scene:
|
||||
scene = SubElement(root, 'scene')
|
||||
'''scene = SubElement(root, 'scene')
|
||||
instance = SubElement(scene, 'instance_visual_scene')
|
||||
instance.set('url', '#')
|
||||
|
||||
full_list_of_objects = FreeCAD.ActiveDocument.Objects
|
||||
instance.set('url', '#')'''
|
||||
|
||||
# CASO 1 - FRAMES:
|
||||
frameType = site.Frames
|
||||
frame_setup = {"type": [],
|
||||
"footprint": []}
|
||||
for obj in frameType:
|
||||
frame_setup["type"] = obj
|
||||
frame_setup["footprint"] = ""
|
||||
|
||||
objind = 0
|
||||
|
||||
# TODO: revisar
|
||||
for typ in frameType:
|
||||
isTracker = "tracker" in typ.Proxy.Type.lower()
|
||||
isTracker = False
|
||||
|
||||
objectlist = FreeCAD.ActiveDocument.findObjects(Name="Tracker")
|
||||
tmp = []
|
||||
for obj in objectlist:
|
||||
if obj.Name.startswith("TrackerSetup"):
|
||||
continue
|
||||
else:
|
||||
tmp.append(obj)
|
||||
objectlist = tmp.copy()
|
||||
#isTracker = False
|
||||
|
||||
objectlist = utils.findObjects("Tracker")
|
||||
for obj in objectlist:
|
||||
if obj.Setup == typ:
|
||||
findex = numpy.array([])
|
||||
@@ -580,7 +877,6 @@ def exportToPVC(path, exportTerrain = False):
|
||||
v = Topology[0][i]
|
||||
vindex[list(range(i * 3, i * 3 + 3))] = (-(v.x - center.x) * scale, (v.z - center.z) * scale,
|
||||
(v.y - center.y) * scale)
|
||||
|
||||
# 2. face indices
|
||||
findex = numpy.empty(len(Topology[1]) * 3, numpy.int64)
|
||||
for i in range(len(Topology[1])):
|
||||
|
||||
@@ -69,7 +69,7 @@ class OSMImporter:
|
||||
headers={'User-Agent': 'FreeCAD-OSM-Importer/1.0'},
|
||||
method='POST'
|
||||
)
|
||||
return urllib.request.urlopen(req, context=self.ssl_context, timeout=30).read()
|
||||
return urllib.request.urlopen(req, context=self.ssl_context, timeout=160).read()
|
||||
|
||||
def create_layer(self, name):
|
||||
if not FreeCAD.ActiveDocument.getObject(name):
|
||||
|
||||
@@ -59,6 +59,7 @@ class PVPlantWorkbench(Workbench):
|
||||
"PVPlantBuilding",
|
||||
"PVPlantFenceGroup",
|
||||
]'''
|
||||
from Electrical.PowerConverter import PowerConverter
|
||||
self.electricalList = ["PVPlantStringBox",
|
||||
"PVPlantCable",
|
||||
"PVPlanElectricalLine",
|
||||
@@ -66,6 +67,8 @@ class PVPlantWorkbench(Workbench):
|
||||
"Stringing",
|
||||
"Separator",
|
||||
"StringInverter",
|
||||
"Separator",
|
||||
"PowerConverter"
|
||||
]
|
||||
|
||||
self.roads = ["PVPlantRoad",
|
||||
|
||||
@@ -540,6 +540,7 @@ def makeTrackerSetup(name="TrackerSetup"):
|
||||
pass
|
||||
return obj
|
||||
|
||||
|
||||
def getarray(array, numberofpoles):
|
||||
if len(array) == 0:
|
||||
newarray = [0] * numberofpoles
|
||||
@@ -568,6 +569,7 @@ def getarray(array, numberofpoles):
|
||||
newarray = [array[0]] * numberofpoles
|
||||
return newarray
|
||||
|
||||
|
||||
class TrackerSetup(FrameSetup):
|
||||
"A 1 Axis Tracker Obcject"
|
||||
|
||||
@@ -589,7 +591,7 @@ class TrackerSetup(FrameSetup):
|
||||
obj.addProperty("App::PropertyDistance",
|
||||
"MotorGap",
|
||||
"ModuleArray",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Thse height of this object")
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of this object")
|
||||
).MotorGap = 550
|
||||
|
||||
if not "UseGroupsOfModules" in pl:
|
||||
@@ -880,6 +882,9 @@ class TrackerSetup(FrameSetup):
|
||||
|
||||
def CalculatePosts(self, obj, totalh, totalw):
|
||||
# Temp: utilizar el uso de versiones:
|
||||
if len(obj.PoleType) == 0:
|
||||
return None, None
|
||||
|
||||
ver = 1
|
||||
if ver == 0:
|
||||
# versión 0:
|
||||
@@ -906,8 +911,8 @@ class TrackerSetup(FrameSetup):
|
||||
elif ver == 1:
|
||||
# versión 1:
|
||||
linetmp = Part.LineSegment(FreeCAD.Vector(0), FreeCAD.Vector(0, 10, 0)).toShape()
|
||||
compoundPoles = Part.makeCompound([])
|
||||
compoundAxis = Part.makeCompound([])
|
||||
compound_poles = Part.makeCompound([])
|
||||
compound_axis = Part.makeCompound([])
|
||||
|
||||
offsetX = - totalw / 2
|
||||
arrayDistance = obj.DistancePole
|
||||
@@ -915,15 +920,16 @@ class TrackerSetup(FrameSetup):
|
||||
arrayPost = obj.PoleSequence
|
||||
|
||||
for x in range(int(obj.NumberPole.Value)):
|
||||
postCopy = obj.PoleType[arrayPost[x]].Shape.copy()
|
||||
post_copy = obj.PoleType[arrayPost[x]].Shape.copy()
|
||||
offsetX += arrayDistance[x]
|
||||
postCopy.Placement.Base = FreeCAD.Vector(offsetX, 0, -(postCopy.BoundBox.ZLength - arrayAerial[x]))
|
||||
compoundPoles.add(postCopy)
|
||||
post_copy.Placement.Base = FreeCAD.Vector(offsetX, 0, -(post_copy.BoundBox.ZLength - arrayAerial[x]))
|
||||
compound_poles.add(post_copy)
|
||||
|
||||
axis = linetmp.copy()
|
||||
axis.Placement.Base = FreeCAD.Vector(offsetX, 0, arrayAerial[x])
|
||||
compoundAxis.add(axis)
|
||||
return compoundPoles, compoundAxis
|
||||
compound_axis.add(axis)
|
||||
|
||||
return compound_poles, compound_axis
|
||||
|
||||
def execute(self, obj):
|
||||
# obj.Shape: compound
|
||||
@@ -1029,14 +1035,14 @@ class Tracker(ArchComponent.Component):
|
||||
"AngleY",
|
||||
"Outputs",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of this object")
|
||||
).AngleX = 0
|
||||
).AngleY = 0
|
||||
|
||||
if not ("AngleZ" in pl):
|
||||
obj.addProperty("App::PropertyAngle",
|
||||
"AngleZ",
|
||||
"Outputs",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of this object")
|
||||
).AngleX = 0
|
||||
).AngleZ = 0
|
||||
|
||||
self.Type = "Tracker"
|
||||
#obj.Type = self.Type
|
||||
@@ -1056,12 +1062,15 @@ class Tracker(ArchComponent.Component):
|
||||
if prop.startswith("Angle"):
|
||||
base = obj.Placement.Base
|
||||
angles = obj.Placement.Rotation.toEulerAngles("XYZ")
|
||||
|
||||
# Actualizar rotación según el ángulo modificado
|
||||
if prop == "AngleX":
|
||||
rot = FreeCAD.Rotation(angles[2], angles[1], obj.AngleX.Value)
|
||||
elif prop == "AngleY":
|
||||
rot = FreeCAD.Rotation(angles[2], obj.AngleY.Value, angles[0])
|
||||
elif prop == "AngleZ":
|
||||
rot = FreeCAD.Rotation(obj.AngleZ.Value, angles[1], angles[0])
|
||||
|
||||
obj.Placement = FreeCAD.Placement(base, rot, FreeCAD.Vector(0,0,0))
|
||||
|
||||
if hasattr(FreeCAD.ActiveDocument, "FramesChecking"):
|
||||
@@ -1083,28 +1092,38 @@ class Tracker(ArchComponent.Component):
|
||||
# |-- PoleAxes: Edge
|
||||
|
||||
if obj.Setup is None:
|
||||
print("Warning: No Setup defined for tracker")
|
||||
return
|
||||
pl = obj.Placement
|
||||
shape = obj.Setup.Shape.copy()
|
||||
try:
|
||||
pl = obj.Placement
|
||||
shape = obj.Setup.Shape.copy()
|
||||
|
||||
p1 = shape.SubShapes[0].SubShapes[1].SubShapes[0].CenterOfMass
|
||||
p2 = min(shape.SubShapes[0].SubShapes[1].SubShapes[0].Faces, key=lambda face: face.Area).CenterOfMass
|
||||
axis = p1 - p2
|
||||
modules = shape.SubShapes[0].rotate(p1, axis, obj.Tilt.Value)
|
||||
# Rotar módulos
|
||||
p1 = shape.SubShapes[0].SubShapes[1].SubShapes[0].CenterOfMass
|
||||
p2 = min(shape.SubShapes[0].SubShapes[1].SubShapes[0].Faces, key=lambda face: face.Area).CenterOfMass
|
||||
axis = p1 - p2
|
||||
modules = shape.SubShapes[0].rotate(p1, axis, obj.Tilt.Value)
|
||||
|
||||
angle = obj.Placement.Rotation.toEuler()[1]
|
||||
newpoles = Part.makeCompound([])
|
||||
for i in range(len(shape.SubShapes[1].SubShapes[0].SubShapes)):
|
||||
pole = shape.SubShapes[1].SubShapes[0].SubShapes[i]
|
||||
axis = shape.SubShapes[1].SubShapes[1].SubShapes[i]
|
||||
base = axis.Vertexes[0].Point
|
||||
axis = axis.Vertexes[1].Point - axis.Vertexes[0].Point
|
||||
newpoles.add(pole.rotate(base, axis, -angle))
|
||||
poles = Part.makeCompound([newpoles, shape.SubShapes[1].SubShapes[1].copy()])
|
||||
# Rotar postes
|
||||
angle = obj.Placement.Rotation.toEuler()[1]
|
||||
newpoles = Part.makeCompound([])
|
||||
for i in range(len(shape.SubShapes[1].SubShapes[0].SubShapes)):
|
||||
pole = shape.SubShapes[1].SubShapes[0].SubShapes[i]
|
||||
axis = shape.SubShapes[1].SubShapes[1].SubShapes[i]
|
||||
base = axis.Vertexes[0].Point
|
||||
axis = axis.Vertexes[1].Point - axis.Vertexes[0].Point
|
||||
newpoles.add(pole.rotate(base, axis, -angle))
|
||||
poles = Part.makeCompound([newpoles, shape.SubShapes[1].SubShapes[1].copy()])
|
||||
|
||||
obj.Shape = Part.makeCompound([modules, poles])
|
||||
obj.Placement = pl
|
||||
obj.AngleX, obj.AngleY, obj.AngleZ = obj.Placement.Rotation.toEulerAngles("XYZ")
|
||||
# Crear forma final
|
||||
obj.Shape = Part.makeCompound([modules, poles])
|
||||
obj.Placement = pl
|
||||
|
||||
# Sincronizar propiedades de ángulo
|
||||
obj.AngleX, obj.AngleY, obj.AngleZ = obj.Placement.Rotation.toEulerAngles("XYZ")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in Tracker execution: {str(e)}")
|
||||
|
||||
|
||||
class ViewProviderTracker(ArchComponent.ViewProviderComponent):
|
||||
@@ -1271,6 +1290,7 @@ class CommandFixedRack:
|
||||
#FreeCADGui.Control.showDialog(self.TaskPanel)
|
||||
return
|
||||
|
||||
|
||||
class CommandTrackerSetup:
|
||||
"the Arch Building command definition"
|
||||
|
||||
@@ -1292,6 +1312,7 @@ class CommandTrackerSetup:
|
||||
FreeCADGui.Control.showDialog(self.TaskPanel)
|
||||
return
|
||||
|
||||
|
||||
class CommandTracker:
|
||||
"the Arch Building command definition"
|
||||
|
||||
|
||||
1306
PVPlantPlacement.py
1306
PVPlantPlacement.py
File diff suppressed because it is too large
Load Diff
@@ -73,6 +73,42 @@ line_patterns = {
|
||||
"Dot (.5x) ...............................": 0x5555,
|
||||
"Dot (2x) . . . . . . . . . . .": 0x8888}
|
||||
|
||||
|
||||
def open_xyz_mmap(archivo_path):
|
||||
"""
|
||||
Usa memory-mapping para archivos muy grandes (máxima velocidad)
|
||||
"""
|
||||
# Primera pasada: contar líneas válidas
|
||||
total_puntos = 0
|
||||
with open(archivo_path, 'r') as f:
|
||||
for linea in f:
|
||||
partes = linea.strip().split()
|
||||
if len(partes) >= 3:
|
||||
try:
|
||||
float(partes[0]);
|
||||
float(partes[1]);
|
||||
float(partes[2])
|
||||
total_puntos += 1
|
||||
except:
|
||||
continue
|
||||
|
||||
# Segunda pasada: cargar datos
|
||||
puntos = np.empty((total_puntos, 3))
|
||||
idx = 0
|
||||
|
||||
with open(archivo_path, 'r') as f:
|
||||
for linea in f:
|
||||
partes = linea.strip().split()
|
||||
if len(partes) >= 3:
|
||||
try:
|
||||
x, y, z = float(partes[0]), float(partes[1]), float(partes[2])
|
||||
puntos[idx] = [x, y, z]
|
||||
idx += 1
|
||||
except:
|
||||
continue
|
||||
|
||||
return puntos
|
||||
|
||||
def makeTerrain(name="Terrain"):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Terrain")
|
||||
obj.Label = name
|
||||
@@ -81,7 +117,6 @@ def makeTerrain(name="Terrain"):
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
return obj
|
||||
|
||||
|
||||
class Terrain(ArchComponent.Component):
|
||||
"A Shadow Terrain Obcject"
|
||||
|
||||
@@ -161,101 +196,110 @@ class Terrain(ArchComponent.Component):
|
||||
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
|
||||
from pathlib import Path
|
||||
suffix = Path(obj.DEM).suffix
|
||||
if suffix == '.asc':
|
||||
'''
|
||||
ASC format:
|
||||
|
||||
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
|
||||
# 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)
|
||||
# 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
|
||||
# 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
|
||||
# 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)
|
||||
# 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
|
||||
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]
|
||||
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)
|
||||
# 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):
|
||||
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]])
|
||||
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)}")
|
||||
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)
|
||||
elif suffix in ['.xyz']:
|
||||
data = open_xyz_mmap(obj.DEM)
|
||||
|
||||
|
||||
mesh.removeDuplicatedPoints()
|
||||
mesh.removeFoldsOnSurface()
|
||||
obj.InitialMesh = mesh.copy()
|
||||
Mesh.show(mesh)
|
||||
|
||||
if prop == "PointsGroup" or prop == "CuttingBoundary":
|
||||
if obj.PointsGroup and obj.CuttingBoundary:
|
||||
|
||||
@@ -54,30 +54,6 @@ class CommandPVPlantSite:
|
||||
return
|
||||
|
||||
|
||||
'''class CommandPVPlantGeoreferencing:
|
||||
@staticmethod
|
||||
def GetResources():
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "Location.svg")),
|
||||
'Accel': "G, R",
|
||||
'MenuText': QT_TRANSLATE_NOOP("Georeferencing","Georeferencing"),
|
||||
'ToolTip': QT_TRANSLATE_NOOP("Georeferencing","Referenciar el lugar")}
|
||||
|
||||
@staticmethod
|
||||
def IsActive():
|
||||
if FreeCAD.ActiveDocument:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def Activated():
|
||||
import PVPlantGeoreferencing
|
||||
taskd = PVPlantGeoreferencing.MapWindow()
|
||||
#taskd.setParent(FreeCADGui.getMainWindow())
|
||||
#taskd.setWindowFlags(QtCore.Qt.Window)
|
||||
taskd.show()#exec_()'''
|
||||
|
||||
|
||||
class CommandProjectSetup:
|
||||
@staticmethod
|
||||
def GetResources():
|
||||
|
||||
@@ -26,6 +26,9 @@ import PVPlantSite
|
||||
import Utils.PVPlantUtils as utils
|
||||
import MeshPart as mp
|
||||
|
||||
import pivy
|
||||
from pivy import coin
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from DraftTools import translate
|
||||
@@ -361,12 +364,12 @@ class OffsetArea(_Area):
|
||||
wire = utils.getProjected(base, vec)
|
||||
wire = wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True)
|
||||
sections = mp.projectShapeOnMesh(wire, land, vec)
|
||||
print(" javi ", sections)
|
||||
pts = []
|
||||
for section in sections:
|
||||
pts.extend(section)
|
||||
|
||||
# Crear forma solo si hay resultados
|
||||
if sections:
|
||||
if len(pts)>0:
|
||||
obj.Shape = Part.makePolygon(pts)
|
||||
else:
|
||||
obj.Shape = Part.Shape() # Forma vacía si falla
|
||||
@@ -412,35 +415,9 @@ class ProhibitedArea(OffsetArea):
|
||||
self.Type = obj.Type = "ProhibitedArea"
|
||||
obj.Proxy = self
|
||||
|
||||
'''# Propiedades de color
|
||||
if not hasattr(obj, "OriginalColor"):
|
||||
obj.addProperty("App::PropertyColor",
|
||||
"OriginalColor",
|
||||
"Display",
|
||||
"Color for original wire")
|
||||
obj.OriginalColor = (1.0, 0.0, 0.0) # Rojo
|
||||
|
||||
if not hasattr(obj, "OffsetColor"):
|
||||
obj.addProperty("App::PropertyColor",
|
||||
"OffsetColor",
|
||||
"Display",
|
||||
"Color for offset wire")
|
||||
obj.OffsetColor = (1.0, 0.5, 0.0) # Naranja
|
||||
|
||||
# Propiedades de grosor
|
||||
if not hasattr(obj, "OriginalWidth"):
|
||||
obj.addProperty("App::PropertyFloat",
|
||||
"OriginalWidth",
|
||||
"Display",
|
||||
"Line width for original wire")
|
||||
obj.OriginalWidth = 4.0
|
||||
|
||||
if not hasattr(obj, "OffsetWidth"):
|
||||
obj.addProperty("App::PropertyFloat",
|
||||
"OffsetWidth",
|
||||
"Display",
|
||||
"Line width for offset wire")
|
||||
obj.OffsetWidth = 4.0'''
|
||||
def onDocumentRestored(self, obj):
|
||||
"""Method run when the document is restored."""
|
||||
self.setProperties(obj)
|
||||
|
||||
def execute(self, obj):
|
||||
# Comprobar dependencias
|
||||
@@ -482,121 +459,402 @@ class ProhibitedArea(OffsetArea):
|
||||
obj.Shape = Part.Shape()
|
||||
|
||||
# Actualizar colores en la vista
|
||||
if FreeCAD.GuiUp and obj.ViewObject:
|
||||
obj.ViewObject.Proxy.updateVisual()
|
||||
"""if FreeCAD.GuiUp and obj.ViewObject:
|
||||
obj.ViewObject.Proxy.updateVisual()"""
|
||||
|
||||
|
||||
class ViewProviderForbiddenArea(_ViewProviderArea):
|
||||
class ViewProviderForbiddenArea_old:
|
||||
def __init__(self, vobj):
|
||||
super().__init__(vobj)
|
||||
# Valores por defecto
|
||||
self.original_color = (1.0, 0.0, 0.0) # Rojo
|
||||
self.offset_color = (1.0, 0.5, 0.0) # Naranja
|
||||
self.original_width = 4.0
|
||||
self.offset_width = 4.0
|
||||
self.line_widths = [] # Almacenará los grosores por arista
|
||||
vobj.Proxy = self
|
||||
self.setProperties(vobj)
|
||||
|
||||
vobj.LineColor = (1.0, 0.0, 0.0)
|
||||
vobj.LineWidth = 4
|
||||
vobj.PointColor = (1.0, 0.0, 0.0)
|
||||
vobj.PointSize = 4
|
||||
def setProperties(self, vobj):
|
||||
# Propiedades de color
|
||||
if not hasattr(vobj, "OriginalColor"):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"OriginalColor",
|
||||
"ObjectStyle",
|
||||
"Color for original wire")
|
||||
vobj.OriginalColor = (1.0, 0.0, 0.0) # Rojo
|
||||
|
||||
def getIcon(self):
|
||||
''' Return object treeview icon. '''
|
||||
return str(os.path.join(DirIcons, "area_forbidden.svg"))
|
||||
if not hasattr(vobj, "OffsetColor"):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"OffsetColor",
|
||||
"ObjectStyle",
|
||||
"Color for offset wire")
|
||||
vobj.OffsetColor = (1.0, 0.0, 0.0) # Rojo
|
||||
|
||||
def claimChildren(self):
|
||||
""" Provides object grouping """
|
||||
children = []
|
||||
if self.ViewObject and self.ViewObject.Object.Base:
|
||||
children.append(self.ViewObject.Object.Base)
|
||||
return children
|
||||
# Propiedades de grosor
|
||||
if not hasattr(vobj, "OriginalWidth"):
|
||||
vobj.addProperty("App::PropertyFloat",
|
||||
"OriginalWidth",
|
||||
"ObjectStyle",
|
||||
"Line width for original wire")
|
||||
vobj.OriginalWidth = 4.0
|
||||
|
||||
if not hasattr(vobj, "OffsetWidth"):
|
||||
vobj.addProperty("App::PropertyFloat",
|
||||
"OffsetWidth",
|
||||
"ObjectStyle",
|
||||
"Line width for offset wire")
|
||||
vobj.OffsetWidth = 4.0
|
||||
|
||||
# Deshabilitar el color por defecto
|
||||
vobj.setPropertyStatus("LineColor", "Hidden")
|
||||
vobj.setPropertyStatus("PointColor", "Hidden")
|
||||
vobj.setPropertyStatus("ShapeAppearance", "Hidden")
|
||||
|
||||
def attach(self, vobj):
|
||||
super().attach(vobj)
|
||||
# Inicializar visualización
|
||||
self.updateVisual()
|
||||
self.ViewObject = vobj
|
||||
self.Object = vobj.Object
|
||||
|
||||
def updateVisual(self):
|
||||
"""Actualiza colores y grosores de línea"""
|
||||
if not hasattr(self, 'ViewObject') or not self.ViewObject or not self.ViewObject.Object:
|
||||
return
|
||||
# Crear la estructura de escena Coin3D
|
||||
self.root = coin.SoGroup()
|
||||
|
||||
obj = self.ViewObject.Object
|
||||
# Switch para habilitar/deshabilitar la selección
|
||||
self.switch = coin.SoSwitch()
|
||||
self.switch.whichChild = coin.SO_SWITCH_ALL
|
||||
|
||||
# Obtener propiedades de color y grosor
|
||||
try:
|
||||
self.original_color = obj.OriginalColor
|
||||
self.offset_color = obj.OffsetColor
|
||||
self.original_width = obj.OriginalWidth
|
||||
self.offset_width = obj.OffsetWidth
|
||||
except:
|
||||
pass
|
||||
# Separador para el wire original
|
||||
self.original_sep = coin.SoSeparator()
|
||||
self.original_color = coin.SoBaseColor()
|
||||
self.original_coords = coin.SoCoordinate3()
|
||||
self.original_line_set = coin.SoLineSet()
|
||||
self.original_draw_style = coin.SoDrawStyle()
|
||||
|
||||
# Actualizar colores si hay forma
|
||||
if hasattr(obj, 'Shape') and obj.Shape and not obj.Shape.isNull():
|
||||
if len(obj.Shape.SubShapes) >= 2:
|
||||
# Asignar colores
|
||||
colors = []
|
||||
colors.append(self.original_color) # Primer wire (original)
|
||||
colors.append(self.offset_color) # Segundo wire (offset)
|
||||
self.ViewObject.DiffuseColor = colors
|
||||
# Separador para el wire offset
|
||||
self.offset_sep = coin.SoSeparator()
|
||||
self.offset_color = coin.SoBaseColor()
|
||||
self.offset_coords = coin.SoCoordinate3()
|
||||
self.offset_line_set = coin.SoLineSet()
|
||||
self.offset_draw_style = coin.SoDrawStyle()
|
||||
|
||||
# Preparar grosores por arista
|
||||
#self.prepareLineWidths()
|
||||
# Construir la jerarquía de escena
|
||||
self.original_sep.addChild(self.original_color)
|
||||
self.original_sep.addChild(self.original_draw_style)
|
||||
self.original_sep.addChild(self.original_coords)
|
||||
self.original_sep.addChild(self.original_line_set)
|
||||
|
||||
# Asignar grosores usando LineWidthArray
|
||||
'''if self.line_widths:
|
||||
self.ViewObject.LineWidthArray = self.line_widths'''
|
||||
self.offset_sep.addChild(self.offset_color)
|
||||
self.offset_sep.addChild(self.offset_draw_style)
|
||||
self.offset_sep.addChild(self.offset_coords)
|
||||
self.offset_sep.addChild(self.offset_line_set)
|
||||
|
||||
# Establecer grosor global como respaldo
|
||||
#self.ViewObject.LineWidth = max(self.original_width, self.offset_width)
|
||||
self.switch.addChild(self.original_sep)
|
||||
self.switch.addChild(self.offset_sep)
|
||||
self.root.addChild(self.switch)
|
||||
|
||||
def prepareLineWidths(self):
|
||||
"""Prepara la lista de grosores para cada arista"""
|
||||
self.line_widths = []
|
||||
obj = self.ViewObject.Object
|
||||
vobj.addDisplayMode(self.root, "Wireframe")
|
||||
|
||||
if hasattr(obj, 'Shape') and obj.Shape and not obj.Shape.isNull():
|
||||
# Contar aristas en cada subforma
|
||||
for i, subshape in enumerate(obj.Shape.SubShapes):
|
||||
edge_count = len(subshape.Edges) if hasattr(subshape, 'Edges') else 1
|
||||
# Inicializar estilos de dibujo
|
||||
self.original_draw_style.style = coin.SoDrawStyle.LINES
|
||||
self.offset_draw_style.style = coin.SoDrawStyle.LINES
|
||||
|
||||
# Determinar grosor según tipo de wire
|
||||
width = self.original_width if i == 0 else self.offset_width
|
||||
|
||||
# Asignar el mismo grosor a todas las aristas de este wire
|
||||
self.line_widths.extend([width] * edge_count)
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
"""Maneja cambios en propiedades de visualización"""
|
||||
if prop in ["LineColor", "PointColor", "ShapeColor", "LineWidth"]:
|
||||
# Actualizar visualización inicial
|
||||
if hasattr(self.Object, 'Shape'):
|
||||
self.updateData(self.Object, "Shape")
|
||||
self.updateVisual()
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
"""Actualiza cuando cambian los datos del objeto"""
|
||||
if prop == "Shape":
|
||||
if prop == "Shape" and obj.Shape and not obj.Shape.isNull():
|
||||
self.updateGeometry()
|
||||
|
||||
def updateGeometry(self):
|
||||
"""Actualiza la geometría en la escena 3D"""
|
||||
if not hasattr(self, 'Object') or not self.Object.Shape or self.Object.Shape.isNull():
|
||||
return
|
||||
|
||||
# Limpiar coordenadas existentes
|
||||
self.original_coords.point.deleteValues(0)
|
||||
self.offset_coords.point.deleteValues(0)
|
||||
|
||||
# Obtener los sub-shapes
|
||||
subshapes = []
|
||||
if hasattr(self.Object.Shape, 'SubShapes') and self.Object.Shape.SubShapes:
|
||||
subshapes = self.Object.Shape.SubShapes
|
||||
elif hasattr(self.Object.Shape, 'ChildShapes') and self.Object.Shape.ChildShapes:
|
||||
subshapes = self.Object.Shape.ChildShapes
|
||||
|
||||
# Procesar wire original (primer sub-shape)
|
||||
if len(subshapes) > 0:
|
||||
self.processShape(subshapes[0], self.original_coords, self.original_line_set)
|
||||
|
||||
# Procesar wire offset (segundo sub-shape)
|
||||
if len(subshapes) > 1:
|
||||
self.processShape(subshapes[1], self.offset_coords, self.offset_line_set)
|
||||
|
||||
# Actualizar colores y grosores
|
||||
self.updateVisual()
|
||||
|
||||
def processShape(self, shape, coords_node, lineset_node):
|
||||
"""Procesa una forma y la añade al nodo de coordenadas"""
|
||||
if not shape or shape.isNull():
|
||||
return
|
||||
|
||||
points = []
|
||||
line_indices = []
|
||||
current_index = 0
|
||||
|
||||
# Obtener todos los edges de la forma
|
||||
edges = []
|
||||
if hasattr(shape, 'Edges'):
|
||||
edges = shape.Edges
|
||||
elif hasattr(shape, 'ChildShapes'):
|
||||
for child in shape.ChildShapes:
|
||||
if hasattr(child, 'Edges'):
|
||||
edges.extend(child.Edges)
|
||||
|
||||
for edge in edges:
|
||||
try:
|
||||
# Discretizar la curva para obtener puntos
|
||||
vertices = edge.discretize(Number=50)
|
||||
|
||||
for i, vertex in enumerate(vertices):
|
||||
points.append([vertex.x, vertex.y, vertex.z])
|
||||
line_indices.append(current_index)
|
||||
current_index += 1
|
||||
|
||||
# Añadir -1 para indicar fin de línea
|
||||
line_indices.append(-1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing edge: {e}")
|
||||
continue
|
||||
|
||||
# Configurar coordenadas y líneas
|
||||
if points:
|
||||
coords_node.point.setValues(0, len(points), points)
|
||||
lineset_node.numVertices.deleteValues(0)
|
||||
lineset_node.numVertices.setValues(0, len(line_indices), line_indices)
|
||||
|
||||
def updateVisual(self):
|
||||
"""Actualiza colores y grosores según las propiedades"""
|
||||
if not hasattr(self, 'ViewObject') or not self.ViewObject:
|
||||
return
|
||||
|
||||
vobj = self.ViewObject
|
||||
|
||||
try:
|
||||
# Configurar wire original
|
||||
if hasattr(vobj, "OriginalColor"):
|
||||
original_color = vobj.OriginalColor
|
||||
self.original_color.rgb.setValue(original_color[0], original_color[1], original_color[2])
|
||||
|
||||
if hasattr(vobj, "OriginalWidth"):
|
||||
self.original_draw_style.lineWidth = vobj.OriginalWidth
|
||||
|
||||
# Configurar wire offset
|
||||
if hasattr(vobj, "OffsetColor"):
|
||||
offset_color = vobj.OffsetColor
|
||||
self.offset_color.rgb.setValue(offset_color[0], offset_color[1], offset_color[2])
|
||||
|
||||
if hasattr(vobj, "OffsetWidth"):
|
||||
self.offset_draw_style.lineWidth = vobj.OffsetWidth
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error updating visual: {e}")
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
"""Maneja cambios en propiedades"""
|
||||
if prop in ["OriginalColor", "OffsetColor", "OriginalWidth", "OffsetWidth"]:
|
||||
self.updateVisual()
|
||||
|
||||
'''def __getstate__(self):
|
||||
return {
|
||||
"original_color": self.original_color,
|
||||
"offset_color": self.offset_color,
|
||||
"original_width": self.original_width,
|
||||
"offset_width": self.offset_width
|
||||
}
|
||||
def getDisplayModes(self, obj):
|
||||
return ["Wireframe"]
|
||||
|
||||
def getDefaultDisplayMode(self):
|
||||
return "Wireframe"
|
||||
|
||||
def setDisplayMode(self, mode):
|
||||
return mode
|
||||
|
||||
def claimChildren(self):
|
||||
"""Proporciona agrupamiento de objetos"""
|
||||
children = []
|
||||
if hasattr(self, 'Object') and self.Object and hasattr(self.Object, "Base"):
|
||||
children.append(self.Object.Base)
|
||||
return children
|
||||
|
||||
def getIcon(self):
|
||||
'''Return object treeview icon'''
|
||||
return str(os.path.join(DirIcons, "area_forbidden.svg"))
|
||||
|
||||
def onDocumentRestored(self, vobj):
|
||||
"""Método ejecutado cuando el documento es restaurado"""
|
||||
self.ViewObject = vobj
|
||||
self.Object = vobj.Object
|
||||
self.setProperties(vobj)
|
||||
self.attach(vobj)
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
if "original_color" in state:
|
||||
self.original_color = state["original_color"]
|
||||
if "offset_color" in state:
|
||||
self.offset_color = state["offset_color"]
|
||||
if "original_width" in state:
|
||||
self.original_width = state.get("original_width", 4.0)
|
||||
if "offset_width" in state:
|
||||
self.offset_width = state.get("offset_width", 4.0)'''
|
||||
return None
|
||||
|
||||
|
||||
class ViewProviderForbiddenArea:
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
self.ViewObject = vobj
|
||||
|
||||
# Inicializar propiedades PRIMERO
|
||||
self.setProperties(vobj)
|
||||
|
||||
# Configurar colores iniciales
|
||||
self.updateColors(vobj)
|
||||
|
||||
def setProperties(self, vobj):
|
||||
if not hasattr(vobj, "OriginalColor"):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"OriginalColor",
|
||||
"Display",
|
||||
"Color for original wire")
|
||||
vobj.OriginalColor = (1.0, 0.0, 0.0) # Rojo
|
||||
|
||||
if not hasattr(vobj, "OffsetColor"):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"OffsetColor",
|
||||
"Display",
|
||||
"Color for offset wire")
|
||||
vobj.OffsetColor = (1.0, 0.5, 0.0) # Naranja
|
||||
|
||||
def updateColors(self, vobj):
|
||||
"""Actualiza los colores desde las propiedades"""
|
||||
try:
|
||||
if hasattr(vobj, "OriginalColor"):
|
||||
self.original_color.rgb.setValue(*vobj.OriginalColor)
|
||||
else:
|
||||
self.original_color.rgb.setValue(1.0, 0.0, 0.0)
|
||||
|
||||
if hasattr(vobj, "OffsetColor"):
|
||||
self.offset_color.rgb.setValue(*vobj.OffsetColor)
|
||||
else:
|
||||
self.offset_color.rgb.setValue(1.0, 0.5, 0.0)
|
||||
except Exception as e:
|
||||
print(f"Error en updateColors: {e}")
|
||||
|
||||
def onDocumentRestored(self, vobj):
|
||||
self.setProperties(vobj)
|
||||
# No llamar a __init__ de nuevo, solo actualizar propiedades
|
||||
self.updateColors(vobj)
|
||||
|
||||
def getIcon(self):
|
||||
return str(os.path.join(DirIcons, "area_forbidden.svg"))
|
||||
|
||||
def attach(self, vobj):
|
||||
self.ViewObject = vobj
|
||||
|
||||
# Inicializar nodos Coin3D
|
||||
self.root = coin.SoGroup()
|
||||
self.original_coords = coin.SoCoordinate3()
|
||||
self.offset_coords = coin.SoCoordinate3()
|
||||
self.original_color = coin.SoBaseColor()
|
||||
self.offset_color = coin.SoBaseColor()
|
||||
self.original_lineset = coin.SoLineSet()
|
||||
self.offset_lineset = coin.SoLineSet()
|
||||
|
||||
# Añadir un nodo de dibujo para establecer el estilo de línea
|
||||
self.draw_style = coin.SoDrawStyle()
|
||||
self.draw_style.style = coin.SoDrawStyle.LINES
|
||||
self.draw_style.lineWidth = 3.0
|
||||
|
||||
# Construir la escena
|
||||
self.root.addChild(self.draw_style)
|
||||
|
||||
# Grupo para el polígono original
|
||||
original_group = coin.SoGroup()
|
||||
original_group.addChild(self.original_color)
|
||||
original_group.addChild(self.original_coords)
|
||||
original_group.addChild(self.original_lineset)
|
||||
|
||||
# Grupo para el polígono offset
|
||||
offset_group = coin.SoGroup()
|
||||
offset_group.addChild(self.offset_color)
|
||||
offset_group.addChild(self.offset_coords)
|
||||
offset_group.addChild(self.offset_lineset)
|
||||
|
||||
self.root.addChild(original_group)
|
||||
self.root.addChild(offset_group)
|
||||
|
||||
vobj.addDisplayMode(self.root, "Standard")
|
||||
# Asegurar que la visibilidad esté activada
|
||||
vobj.Visibility = True
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
if prop == "Shape":
|
||||
self.updateVisual(obj)
|
||||
|
||||
def updateVisual(self, obj):
|
||||
"""Actualiza la representación visual basada en la forma del objeto"""
|
||||
if not hasattr(obj, 'Shape') or not obj.Shape or obj.Shape.isNull():
|
||||
return
|
||||
|
||||
try:
|
||||
# Obtener todos los bordes de la forma compuesta
|
||||
all_edges = obj.Shape.Edges
|
||||
|
||||
# Separar bordes por polígono (asumimos que el primer polígono es el original)
|
||||
# Esto es una simplificación - podrías necesitar una lógica más sofisticada
|
||||
if len(all_edges) >= 2:
|
||||
# Polígono original - primer conjunto de bordes
|
||||
original_edges = [all_edges[0]]
|
||||
original_points = []
|
||||
for edge in original_edges:
|
||||
for vertex in edge.Vertexes:
|
||||
original_points.append((vertex.Point.x, vertex.Point.y, vertex.Point.z))
|
||||
|
||||
# Polígono offset - segundo conjunto de bordes
|
||||
offset_edges = [all_edges[1]]
|
||||
offset_points = []
|
||||
for edge in offset_edges:
|
||||
for vertex in edge.Vertexes:
|
||||
offset_points.append((vertex.Point.x, vertex.Point.y, vertex.Point.z))
|
||||
|
||||
# Asignar puntos a los nodos Coordinate3
|
||||
if original_points:
|
||||
self.original_coords.point.setValues(0, len(original_points), original_points)
|
||||
self.original_lineset.numVertices.setValue(len(original_points))
|
||||
|
||||
if offset_points:
|
||||
self.offset_coords.point.setValues(0, len(offset_points), offset_points)
|
||||
self.offset_lineset.numVertices.setValue(len(offset_points))
|
||||
|
||||
# Actualizar colores
|
||||
if hasattr(obj, 'ViewObject') and obj.ViewObject:
|
||||
self.updateColors(obj.ViewObject)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error en updateVisual: {e}")
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
if prop in ["OriginalColor", "OffsetColor"]:
|
||||
self.updateColors(vobj)
|
||||
elif prop == "Visibility" and vobj.Visibility:
|
||||
# Cuando la visibilidad cambia a True, actualizar visual
|
||||
self.updateVisual(vobj.Object)
|
||||
|
||||
def getDisplayModes(self, obj):
|
||||
return ["Standard"]
|
||||
|
||||
def getDefaultDisplayMode(self):
|
||||
return "Standard"
|
||||
|
||||
def setDisplayMode(self, mode):
|
||||
return mode
|
||||
|
||||
def claimChildren(self):
|
||||
children = []
|
||||
if hasattr(self, 'ViewObject') and self.ViewObject and hasattr(self.ViewObject.Object, 'Base'):
|
||||
children.append(self.ViewObject.Object.Base)
|
||||
return children
|
||||
|
||||
def dumps(self):
|
||||
return None
|
||||
|
||||
def loads(self, state):
|
||||
return None
|
||||
|
||||
''' PV Area: '''
|
||||
def makePVSubplant():
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PVSubplant")
|
||||
|
||||
@@ -224,14 +224,20 @@ def getProjected(shape, direction=FreeCAD.Vector(0, 0, 1)): # Based on Draft / s
|
||||
return ow
|
||||
|
||||
|
||||
def findObjects(classtype):
|
||||
'''def findObjects(classtype):
|
||||
objects = FreeCAD.ActiveDocument.Objects
|
||||
objlist = list()
|
||||
for object in objects:
|
||||
if hasattr(object, "Proxy"):
|
||||
if object.Proxy.Type == classtype:
|
||||
objlist.append(object)
|
||||
return objlist
|
||||
return objlist'''
|
||||
|
||||
def findObjects(classtype):
|
||||
return [obj for obj in FreeCAD.ActiveDocument.Objects
|
||||
if hasattr(obj, "Proxy")
|
||||
and hasattr(obj.Proxy, "Type")
|
||||
and obj.Proxy.Type == classtype]
|
||||
|
||||
def getClosePoints(sh1, angle):
|
||||
'''
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
<license file="LICENSE">LGPL-2.1-or-later</license>
|
||||
<url type="repository" branch="main">https://homehud.duckdns.org/javier/PVPlant</url>
|
||||
<url type="bugtracker">https://homehud.duckdns.org/javier/PVPlant/issues</url>
|
||||
<url type="readme">https://homehud.duckdns.org/javier/PVPlant/src/branch/main/README.md</url>
|
||||
<icon>PVPlant/Resources/Icons/PVPlantWorkbench.svg</icon>
|
||||
|
||||
<content>
|
||||
<workbench>
|
||||
<classname>RoadWorkbench</classname>
|
||||
<classname>PVPlantWorkbench</classname>
|
||||
<subdirectory>./</subdirectory>
|
||||
</workbench>
|
||||
</content>
|
||||
|
||||
Reference in New Issue
Block a user