Compare commits

...

5 Commits

Author SHA1 Message Date
d61260fdd3 updates 2025-11-20 00:57:15 +01:00
049898c939 updates 2025-08-17 13:34:09 +04:00
3a188cc47d new code 2025-08-17 13:33:17 +04:00
5db8f5439d Punto de restauración. 2025-07-31 09:58:38 +02:00
e1e1441892 Punto de restauración. 2025-07-16 08:58:35 +02:00
26 changed files with 3699 additions and 1185 deletions

4
.idea/PVPlant.iml generated
View File

@@ -5,4 +5,8 @@
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="PackageRequirementsSettings">
<option name="removeUnused" value="true" />
<option name="modifyBaseFiles" value="true" />
</component>
</module> </module>

View File

@@ -20,16 +20,18 @@
# * * # * *
# *********************************************************************** # ***********************************************************************
import FreeCAD
import Part
import Draft
import MeshPart as mp
import ArchComponent
import Civil.Fence.PVPlantFencePost as PVPlantFencePost
import PVPlantSite
import Utils.PVPlantUtils as utils
import copy import copy
import math import math
import ArchComponent
import Draft
import FreeCAD
import Part
import PVPlantFencePost
import PVPlantSite
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
import FreeCADGui import FreeCADGui
@@ -56,26 +58,28 @@ from PVPlantResources import DirIcons as DirIcons
EAST = FreeCAD.Vector(1, 0, 0) EAST = FreeCAD.Vector(1, 0, 0)
def makeprojection(pathwire):
site = FreeCAD.ActiveDocument.Site
land = site.Terrain.Shape
proj = land.makeParallelProjection(pathwire, FreeCAD.Vector(0, 0, 1))
return proj
def makePVPlantFence(section, post, path): def makePVPlantFence(section, post, path):
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Fence') obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Fence')
_Fence(obj) Fence(obj)
obj.Post = post obj.Post = post
obj.Base = path obj.Base = path
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
_ViewProviderFence(obj.ViewObject) ViewProviderFence(obj.ViewObject)
hide(section) hide(section)
hide(post) hide(post)
hide(path) hide(path)
try:
fende_group = FreeCAD.ActiveDocument.Fences
except:
fende_group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Fences')
fende_group.Label = "Fences"
FreeCAD.ActiveDocument.CivilGroup.addObject(fende_group)
fende_group.addObject(obj)
FreeCAD.ActiveDocument.recompute() FreeCAD.ActiveDocument.recompute()
return obj return obj
@@ -83,16 +87,8 @@ def hide(obj):
if hasattr(obj, 'ViewObject') and obj.ViewObject: if hasattr(obj, 'ViewObject') and obj.ViewObject:
obj.ViewObject.Visibility = False obj.ViewObject.Visibility = False
def getAngle(Line1, Line2): def get_parameter_from_v0_old(edge, offset):
v1 = Line1.Vertexes[1].Point - Line1.Vertexes[0].Point """ Return parameter at distance offset from edge.Vertexes[0].sb method in Part.TopoShapeEdge??? """
v2 = Line2.Vertexes[1].Point - Line2.Vertexes[0].Point
return v1.getAngle(v2)
def get_parameter_from_v0(edge, offset):
"""Return parameter at distance offset from edge.Vertexes[0].
sb method in Part.TopoShapeEdge???
"""
import DraftVecUtils import DraftVecUtils
lpt = edge.valueAt(edge.getParameterByLength(0)) lpt = edge.valueAt(edge.getParameterByLength(0))
@@ -106,14 +102,16 @@ def get_parameter_from_v0(edge, offset):
length = offset length = offset
return edge.getParameterByLength(length) return edge.getParameterByLength(length)
def get_parameter_from_v0(edge, offset):
"""Parámetro a distancia offset desde el primer vértice"""
lpt = edge.valueAt(edge.getParameterByLength(0))
vpt = edge.Vertexes[0].Point
if not vpt.isEqual(lpt, 1e-6):
return edge.getParameterByLength(edge.Length - offset)
return edge.getParameterByLength(offset)
def calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal=None): def calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal=None):
"""Orient shape to tangent at parm offset along edge."""
import functools
import DraftVecUtils
# http://en.wikipedia.org/wiki/Euler_angles
# start with null Placement point so translate goes to right place.
placement = FreeCAD.Placement() placement = FreeCAD.Placement()
placement.Rotation = globalRotation placement.Rotation = globalRotation
placement.move(RefPt + xlate) placement.move(RefPt + xlate)
@@ -121,55 +119,23 @@ def calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal
if not align: if not align:
return placement return placement
# unit +Z Probably defined elsewhere? t = edge.tangentAt(get_parameter_from_v0(edge, offset)).normalize()
z = FreeCAD.Vector(0, 0, 1) n = normal or FreeCAD.Vector(0, 0, 1)
# y = FreeCAD.Vector(0, 1, 0) # unit +Y b = t.cross(n).normalize()
x = FreeCAD.Vector(1, 0, 0) # unit +X
nullv = FreeCAD.Vector(0, 0, 0)
# get local coord system - tangent, normal, binormal, if possible # Asegurar sistema de coordenadas derecho
t = edge.tangentAt(get_parameter_from_v0(edge, offset)) if n.dot(t.cross(b)) < 0:
t.normalize() b = -b
n = normal
b = t.cross(n)
b.normalize()
lnodes = z.cross(b) # Construir matriz
try: rotation_matrix = FreeCAD.Matrix(
# Can't normalize null vector. t.x, b.x, n.x, 0,
lnodes.normalize() t.y, b.y, n.y, 0,
except: t.z, b.z, n.z, 0,
# pathological cases: 0, 0, 0, 1
pass )
print(b, " - ", b.dot(z))
if abs(b.dot(z)) == 1.0: # 2) binormal is || z
# align shape to tangent only
psi = math.degrees(DraftVecUtils.angle(x, t, z))
theta = 0.0
phi = 0.0
FreeCAD.Console.PrintWarning("Draft PathArray.orientShape - Gimbal lock. Infinite lnodes. Change Path or Base.\n")
else: # regular case
psi = math.degrees(DraftVecUtils.angle(x, lnodes, z))
theta = math.degrees(DraftVecUtils.angle(z, b, lnodes))
phi = math.degrees(DraftVecUtils.angle(lnodes, t, b))
rotations = [placement.Rotation]
if psi != 0.0:
rotations.insert(0, FreeCAD.Rotation(z, psi))
if theta != 0.0:
rotations.insert(0, FreeCAD.Rotation(lnodes, theta))
if phi != 0.0:
rotations.insert(0, FreeCAD.Rotation(b, phi))
if len(rotations) == 1:
finalRotation = rotations[0]
else:
finalRotation = functools.reduce(lambda rot1, rot2: rot1.multiply(rot2), rotations)
placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), finalRotation.toEuler()[2])
placement.Rotation = FreeCAD.Rotation(rotation_matrix)
return placement return placement
def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align): def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
@@ -183,12 +149,8 @@ def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
import DraftGeomUtils import DraftGeomUtils
closedpath = DraftGeomUtils.isReallyClosed(pathwire) closedpath = DraftGeomUtils.isReallyClosed(pathwire)
normal = DraftGeomUtils.getNormal(pathwire)
if normal: normal = FreeCAD.Vector(0, 0, 1)
if normal.z < 0: # asegurarse de que siempre se dibuje por encima del suelo
normal.z *= -1
else:
normal = FreeCAD.Vector(0, 0, 1)
path = Part.__sortEdges__(pathwire.Edges) path = Part.__sortEdges__(pathwire.Edges)
ends = [] ends = []
cdist = 0 cdist = 0
@@ -241,7 +203,7 @@ def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
return placements return placements
class _Fence(ArchComponent.Component): class Fence(ArchComponent.Component):
def __init__(self, obj): def __init__(self, obj):
ArchComponent.Component.__init__(self, obj) ArchComponent.Component.__init__(self, obj)
self.setProperties(obj) self.setProperties(obj)
@@ -347,7 +309,6 @@ class _Fence(ArchComponent.Component):
QT_TRANSLATE_NOOP("App::Property", "The number of posts used to build the fence")) QT_TRANSLATE_NOOP("App::Property", "The number of posts used to build the fence"))
obj.setEditorMode("Length", 1) obj.setEditorMode("Length", 1)
self.Type = "PVPlatFence" self.Type = "PVPlatFence"
def __getstate__(self): def __getstate__(self):
@@ -361,75 +322,30 @@ class _Fence(ArchComponent.Component):
return None return None
def execute(self, obj): def execute(self, obj):
if not obj.Base or not obj.Post:
return
# 1. Preparar trazado base
pathwire = self.calculatePathWire(obj) pathwire = self.calculatePathWire(obj)
if pathwire is None: pathwire = utils.getProjected(pathwire, FreeCAD.Vector(0, 0, 1))
# FreeCAD.Console.PrintLog("ArchFence.execute: path " + obj.Base.Name + " has no edges\n") pathwire = utils.simplifyWire(pathwire)
return if not pathwire or not pathwire.Edges:
if not obj.Post:
FreeCAD.Console.PrintLog("ArchFence.execute: Post not set\n")
return return
# 2. Proyectar sobre terreno (con caché)
self.Posts = [] self.Posts = []
self.Foundations = [] self.Foundations = []
site = PVPlantSite.get()
if True: # prueba
import MeshPart as mp
land = FreeCAD.ActiveDocument.Terrain.Mesh
segments = mp.projectShapeOnMesh(pathwire, land, FreeCAD.Vector(0, 0, 1))
points=[]
for segment in segments:
points.extend(segment)
pathwire = Part.makePolygon(points)
else:
if PVPlantSite.get().Terrain.TypeId == 'Mesh::Feature':
import MeshPart as mp
land = PVPlantSite.get().Terrain.Mesh
pathwire = mp.projectShapeOnMesh(pathwire, land, FreeCAD.Vector(0, 0, 1))
else: site = PVPlantSite.get()
land = site.Terrain.Shape segments = mp.projectShapeOnMesh(pathwire, site.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))
pathwire = land.makeParallelProjection(pathwire, FreeCAD.Vector(0, 0, 1)) points=[]
for segment in segments:
points.extend(segment)
pathwire = Part.makePolygon(points)
if pathwire is None: if pathwire is None:
return return
''' no sirve:
if len(pathwire.Wires) > 1:
import draftgeoutils
pathwire = draftgeoutils.wires.superWire(pathwire.Edges, True)
Part.show(pathwire)
'''
''' unir todas en una '''
'''
if len(pathwire.Wires) > 1:
import Utils.PVPlantUtils as utils
wires = pathwire.Wires
new_wire = []
to_compare = utils.getPoints(wires.pop(0))
new_wire.extend(to_compare)
while len(wires)>0:
wire = wires[0]
points = utils.getPoints(wire)
to_remove = None
if points[0] in to_compare:
to_remove = points[0]
if points[-1] in to_compare:
to_remove = points[-1]
if to_remove:
to_compare = points.copy()
points.remove(to_remove)
new_wire.extend(points)
wires.pop()
continue
wires.append(wires.pop())
pathwire = Part.makePolygon(new_wire)
#Part.show(pathwire)
#return
'''
sectionLength = obj.Gap.Value sectionLength = obj.Gap.Value
postLength = 0 #obj.Post.Diameter.Value #considerarlo 0 porque no influye postLength = 0 #obj.Post.Diameter.Value #considerarlo 0 porque no influye
postPlacements = [] postPlacements = []
@@ -457,18 +373,19 @@ class _Fence(ArchComponent.Component):
postPlacements.extend(placements) postPlacements.extend(placements)
# 5. Generar geometría
postShapes, postFoundation = self.calculatePosts(obj, postPlacements) postShapes, postFoundation = self.calculatePosts(obj, postPlacements)
sections, num = self.calculateSections(obj, postPlacements) mesh = self.calculate_sections(obj, postPlacements)
postShapes = Part.makeCompound(postShapes) postShapes = Part.makeCompound(postShapes)
postFoundation = Part.makeCompound(postFoundation) postFoundation = Part.makeCompound(postFoundation)
sections = Part.makeCompound(sections)
compound = Part.makeCompound([postShapes, postFoundation, sections])
obj.Shape = compound
# Give information # 6. Crear forma final
obj.Shape = Part.makeCompound([postShapes, postFoundation, mesh])
# 7. Actualizar propiedades
obj.NumberOfSections = count obj.NumberOfSections = count
obj.NumberOfPosts = obj.NumberOfSections + 1 obj.NumberOfPosts = count + 1
obj.Length = pathLength obj.Length = pathLength
obj.Concrete = count * postFoundation.SubShapes[0].Volume obj.Concrete = count * postFoundation.SubShapes[0].Volume
@@ -497,7 +414,7 @@ class _Fence(ArchComponent.Component):
def calculatePostPlacements(self, obj, pathwire, rotation): def calculatePostPlacements(self, obj, pathwire, rotation):
postWidth = obj.Post.Diameter.Value postWidth = obj.Post.Diameter.Value
transformationVector = FreeCAD.Vector(0, postWidth / 2, 0) transformationVector = FreeCAD.Vector(0, - postWidth / 2, 0)
placements = calculatePlacementsOnPath(rotation, pathwire, int(obj.NumberOfSections) + 1, transformationVector, True) placements = calculatePlacementsOnPath(rotation, pathwire, int(obj.NumberOfSections) + 1, transformationVector, True)
# The placement of the last object is always the second entry in the list. # The placement of the last object is always the second entry in the list.
# So we move it to the end: # So we move it to the end:
@@ -509,47 +426,36 @@ class _Fence(ArchComponent.Component):
posts = [] posts = []
foundations = [] foundations = []
for placement in postPlacements: for placement in postPlacements:
postCopy = obj.Post.Shape.copy() new_post = obj.Post.Shape.copy()
postCopy = Part.Solid(postCopy) new_post = Part.Solid(new_post)
postCopy.Placement = placement new_post.Placement = placement
postCopy.Placement.Base.z += 100 new_post.Placement.Base.z += 100
posts.append(postCopy) posts.append(new_post)
foundation = Part.makeCylinder(150, 700) foundation = Part.makeCylinder(150, 700)
foundation.Placement = placement foundation.Placement = placement
foundation.Placement.Base.z -= obj.Depth.Value foundation.Placement.Base.z -= obj.Depth.Value
foundation = foundation.cut(postCopy) #foundation = foundation.cut(new_post)
foundations.append(foundation) foundations.append(foundation)
return posts, foundations return posts, foundations
def calculateSections(self, obj, postPlacements): def calculate_sections(self, obj, postPlacements):
shapes = [] offsetz = FreeCAD.Vector(0, 0, obj.MeshOffsetZ.Value)
faceNumbers = [] meshHeight = FreeCAD.Vector(0, 0, obj.MeshHeight.Value)
offsetz = obj.MeshOffsetZ.Value points_down = []
meshHeight = obj.MeshHeight.Value points_up = []
for i in range(len(postPlacements) - 1): for i in range(len(postPlacements) - 1):
startPlacement = postPlacements[i] p1 = postPlacements[i].Base + offsetz
endPlacement = postPlacements[i + 1] p2 = postPlacements[i + 1].Base + offsetz
p3 = p1 + meshHeight
p4 = p2 + meshHeight
points_down.extend([p1, p2])
points_up.extend([p3, p4])
p1 = startPlacement.Base + FreeCAD.Vector(0, 0, offsetz) shape = Part.makeRuledSurface(Part.makePolygon(points_down), Part.makePolygon(points_up))
p2 = endPlacement.Base + FreeCAD.Vector(0, 0, offsetz) return shape
p3 = p2 + FreeCAD.Vector(0, 0, meshHeight)
p4 = p1 + FreeCAD.Vector(0, 0, meshHeight)
pointlist = [p1, p2, p3, p4, p1]
try:
pol = Part.makePolygon(pointlist)
face = Part.Face(pol)
shapes.append(face)
faceNumbers.append(1)
except:
print("No es posible crear la cara: ---------------------------------------------------")
print(" +++++ Start: ", startPlacement.Base, " - end: ", endPlacement.Base)
print(" +++++ algo: ", pointlist, "\n")
print("---------------------------------------------------\n")
return (shapes, faceNumbers)
def calculatePathWire(self, obj): def calculatePathWire(self, obj):
if obj.Base: if obj.Base:
@@ -562,7 +468,7 @@ class _Fence(ArchComponent.Component):
return None return None
class _ViewProviderFence(ArchComponent.ViewProviderComponent): class ViewProviderFence(ArchComponent.ViewProviderComponent):
"A View Provider for the Fence object" "A View Provider for the Fence object"
def __init__(self, vobj): def __init__(self, vobj):
@@ -642,7 +548,7 @@ class _ViewProviderFence(ArchComponent.ViewProviderComponent):
children.append(self.Object.Gate) children.append(self.Object.Gate)
return children return children
class _FenceTaskPanel: class FenceTaskPanel:
'''The TaskPanel to setup the fence''' '''The TaskPanel to setup the fence'''
def __init__(self): def __init__(self):
@@ -775,15 +681,8 @@ class _FenceTaskPanel:
self.form = [self.formFence, self.formPost, self.formFoundation] self.form = [self.formFence, self.formPost, self.formFoundation]
# valores iniciales y creación del la valla: # valores iniciales y creación del la valla:
import Draft self.post = PVPlantFencePost.makeFencePost()
self.post = PVPlantFencePost.makeFencePost() # Arch.makePipe()
self.post.Label = "Post" self.post.Label = "Post"
Draft.autogroup(self.post)
'''
self.section = self.makeGrid()
self.path = self.section.Base
'''
FreeCAD.ActiveDocument.recompute() FreeCAD.ActiveDocument.recompute()
self.fence = makePVPlantFence(self.section, self.post, self.path) self.fence = makePVPlantFence(self.section, self.post, self.path)
@@ -859,7 +758,7 @@ class CommandPVPlantFence:
return not FreeCAD.ActiveDocument is None return not FreeCAD.ActiveDocument is None
def Activated(self): def Activated(self):
self.TaskPanel = _FenceTaskPanel() self.TaskPanel = FenceTaskPanel()
FreeCADGui.Control.showDialog(self.TaskPanel) FreeCADGui.Control.showDialog(self.TaskPanel)
@@ -877,12 +776,13 @@ if FreeCAD.GuiUp:
} }
def IsActive(self): def IsActive(self):
site = FreeCAD.ActiveDocument.getObject("Site")
return (not (FreeCAD.ActiveDocument is None) and return (not (FreeCAD.ActiveDocument is None) and
not (FreeCAD.ActiveDocument.getObject("Site") is None) and not (site is None) and
not (FreeCAD.ActiveDocument.getObject("Terrain") is None)) not (site.Terrain is None))
import PVPlantFenceGate import Civil.Fence.PVPlantFenceGate as PVPlantFenceGate
FreeCADGui.addCommand('PVPlantFence', CommandPVPlantFence()) FreeCADGui.addCommand('PVPlantFence', CommandPVPlantFence())
FreeCADGui.addCommand('PVPlantGate', PVPlantFenceGate._CommandPVPlantGate()) FreeCADGui.addCommand('PVPlantGate', PVPlantFenceGate.CommandPVPlantGate())
FreeCADGui.addCommand('PVPlantFencePost', PVPlantFencePost._CommandFencePost()) FreeCADGui.addCommand('PVPlantFencePost', PVPlantFencePost.CommandFencePost())
#FreeCADGui.addCommand('PVPlantFenceGroup', CommandFenceGroup()) #FreeCADGui.addCommand('PVPlantFenceGroup', CommandFenceGroup())

View File

@@ -202,7 +202,7 @@ class ViewProviderGate:
children.append(self.Object.Base) children.append(self.Object.Base)
return children return children
class _CommandPVPlantGate: class CommandPVPlantGate:
"the PVPlant Fence command definition" "the PVPlant Fence command definition"
def __init__(self): def __init__(self):
@@ -256,7 +256,9 @@ class _CommandPVPlantGate:
gate = makePVPlantFence() gate = makePVPlantFence()
try: try:
import MeshPart as mp import MeshPart as mp
point1 = mp.projectPointsOnMesh([point1,], FreeCAD.ActiveDocument.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))[0] import PVPlantSite
site = PVPlantSite.get()
point1 = mp.projectPointsOnMesh([point1,], site.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))[0]
except: except:
FreeCAD.Console.PrintError("No se puede encontrar punto 3D.." + "\n") FreeCAD.Console.PrintError("No se puede encontrar punto 3D.." + "\n")

View File

@@ -1,5 +1,6 @@
import ArchComponent
import FreeCAD import FreeCAD
import Part
import ArchComponent
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
import FreeCADGui import FreeCADGui
@@ -9,8 +10,6 @@ else:
# \cond # \cond
def translate(ctxt, txt): def translate(ctxt, txt):
return txt return txt
def QT_TRANSLATE_NOOP(ctxt, txt): def QT_TRANSLATE_NOOP(ctxt, txt):
return txt return txt
# \endcond # \endcond
@@ -21,20 +20,14 @@ except AttributeError:
def _fromUtf8(s): def _fromUtf8(s):
return s return s
def makeFencePost(diameter=48, length=3000, placement=None, name="FencePost"):
def makeFencePost(diameter=48, length=3000, placement=None, name="Post"):
"makePipe([baseobj,diamerter,length,placement,name]): creates an pipe object from the given base object"
if not FreeCAD.ActiveDocument:
FreeCAD.Console.PrintError("No active document. Aborting\n")
return
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name) obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
obj.Label = name obj.Label = name
_FencePost(obj) FencePost(obj)
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
_ViewProviderFencePost(obj.ViewObject) ViewProviderFencePost(obj.ViewObject)
obj.Length = length obj.Length = length
obj.Diameter = diameter obj.Diameter = diameter
@@ -45,18 +38,13 @@ def makeFencePost(diameter=48, length=3000, placement=None, name="Post"):
def makeFenceReinforcePost(diameter=48, length=3000, placement=None, name="Post"): def makeFenceReinforcePost(diameter=48, length=3000, placement=None, name="Post"):
"makePipe([baseobj,diamerter,length,placement,name]): creates an pipe object from the given base object"
if not FreeCAD.ActiveDocument:
FreeCAD.Console.PrintError("No active document. Aborting\n")
return
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name) obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
obj.Label = name obj.Label = name
_FenceReinforcePostPost(obj) FenceReinforcePost(obj)
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
_ViewProviderFencePost(obj.ViewObject) ViewProviderFencePost(obj.ViewObject)
obj.Length = length obj.Length = length
obj.Diameter = diameter obj.Diameter = diameter
@@ -66,7 +54,7 @@ def makeFenceReinforcePost(diameter=48, length=3000, placement=None, name="Post"
return obj return obj
class _FencePost(ArchComponent.Component): class FencePost(ArchComponent.Component):
def __init__(self, obj): def __init__(self, obj):
ArchComponent.Component.__init__(self, obj) ArchComponent.Component.__init__(self, obj)
self.setProperties(obj) self.setProperties(obj)
@@ -80,10 +68,10 @@ class _FencePost(ArchComponent.Component):
obj.addProperty("App::PropertyLength", "Diameter", "Pipe", obj.addProperty("App::PropertyLength", "Diameter", "Pipe",
QT_TRANSLATE_NOOP("App::Property", "The diameter of this pipe, if not based on a profile") QT_TRANSLATE_NOOP("App::Property", "The diameter of this pipe, if not based on a profile")
).Diameter = 48 ).Diameter = 48
if not "Thickness" in pl: '''if not "Thickness" in pl:
obj.addProperty("App::PropertyLength", "Thickness", "Pipe", obj.addProperty("App::PropertyLength", "Thickness", "Pipe",
QT_TRANSLATE_NOOP("App::Property", "The Thickness of this pipe, if not based on a profile") QT_TRANSLATE_NOOP("App::Property", "The Thickness of this pipe, if not based on a profile")
).Thickness = 4 ).Thickness = 4'''
if not "Length" in pl: if not "Length" in pl:
obj.addProperty("App::PropertyLength", "Length", "Pipe", obj.addProperty("App::PropertyLength", "Length", "Pipe",
QT_TRANSLATE_NOOP("App::Property", "The length of this pipe, if not based on an edge") QT_TRANSLATE_NOOP("App::Property", "The length of this pipe, if not based on an edge")
@@ -94,86 +82,49 @@ class _FencePost(ArchComponent.Component):
ArchComponent.Component.onDocumentRestored(self, obj) ArchComponent.Component.onDocumentRestored(self, obj)
self.setProperties(obj) self.setProperties(obj)
def get_axis(self, obj, lip_heigth):
wire = Part.makeLine(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, obj.Length.Value - lip_heigth))
#wire = Part.makePolygon(FreeCAD.Vector(0, 0, obj.Length.Value - lip_heigth),)
return Part.Wire(wire)
def execute(self, obj): def execute(self, obj):
import Part
pl = obj.Placement pl = obj.Placement
if obj.CloneOf: lip_heigth = 20
obj.Shape = obj.CloneOf.Shape radius = obj.Diameter.Value / 2
else:
w = self.getProfile(obj)
try:
# sh = w.makePipeShell([p], True, False, 2)
sh = w.revolve(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(0.0, 0.0, 1.0), 360)
sh = Part.Solid(sh)
except:
FreeCAD.Console.PrintError("Unable to build the pipe \n")
else:
obj.Shape = sh
obj.Placement = pl
# para que sea una función que sirva para los postes rectos y con curva:
axis = self.get_axis(obj, lip_heigth)
profile = Part.Wire([Part.Circle(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), radius).toShape()])
post = axis.makePipeShell([profile, ],True,True,2)
lip = Part.makeCylinder(radius + 2, lip_heigth)
lip = lip.makeFillet(5, [lip.Edges[0]])
# Obtener caras
face_post = post.Faces[2] # Cara superior del cilindro largo
face_lip = lip.Faces[2] # Cara inferior del cilindro corto
# Calcular centro y normal de las caras
face_post_center = face_post.CenterOfMass
face_post_normal = face_post.normalAt(0, 0)
face_lip_center = face_lip.CenterOfMass
face_lip_normal = face_lip.normalAt(0, 0)
# Calcular rotación para alinear normales (ajustar dirección)
rotacion = FreeCAD.Rotation(face_lip_normal, -face_post_normal) # Invertir normal del cilindro corto
lip.Placement.Rotation = rotacion.multiply(lip.Placement.Rotation)
# Calcular traslación: mover centro del cilindro corto al centro del cilindro largo
traslacion = face_post_center - rotacion.multVec(face_lip_center)
lip.Placement.Base = traslacion #face_post_center
obj.Shape = post.fuse(lip)
obj.Placement = pl
return return
# ------------------------- Prueba para apoyos de refuerzo:
import math
L = math.pi / 2 * (obj.Diameter.Value - 2 * obj.Thickness.Value)
v1 = FreeCAD.Vector(L / 2, 0, obj.Thickness.Value) class FenceReinforcePost(ArchComponent.Component):
vc1 = FreeCAD.Vector(L / 2 + obj.Thickness.Value, 0, 0)
v2 = FreeCAD.Vector(L / 2, 0, -obj.Thickness.Value)
v11 = FreeCAD.Vector(-L / 2, 0, obj.Thickness.Value)
vc11 = FreeCAD.Vector(-(L / 2 + obj.Thickness.Value), 0, 0)
v21 = FreeCAD.Vector(-L / 2, 0, -obj.Thickness.Value)
arc1 = Part.Arc(v1, vc1, v2).toShape()
arc11 = Part.Arc(v11, vc11, v21).toShape()
line1 = Part.LineSegment(v11, v1).toShape()
line2 = Part.LineSegment(v21, v2).toShape()
w = Part.Wire([arc1, line2, arc11, line1])
face = Part.Face(w)
pro = face.extrude(FreeCAD.Vector(0, 40, 0))
#Part.Circle(Center, Normal, Radius)
cir1 = Part.Face(Part.Wire(Part.Circle(FreeCAD.Vector(0, -200, 0), FreeCAD.Vector(0, 1, 0), obj.Diameter.Value / 2).toShape()))
ext = cir1.extrude(FreeCAD.Vector(0, 170, 0))
cir2 = Part.Circle(FreeCAD.Vector(0, -30, 0), FreeCAD.Vector(0, 1, 0), obj.Diameter.Value/2).toShape()
loft = Part.makeLoft([cir2, w], True)
ext = ext.fuse([loft, pro])
Part.show(ext)
def getProfile(self, obj):
import Part
sin45 = 0.707106781
radio = obj.Diameter.Value / 2
taph = 20
tapw = radio + 2
chamfer = 5
chamfer2 = chamfer * sin45
edge1 = Part.makeLine(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(radio, 0, 0))
edge2 = Part.makeLine(FreeCAD.Vector(radio, 0, 0), FreeCAD.Vector(radio, 0, obj.Length.Value - taph))
edge3 = Part.makeLine(FreeCAD.Vector(radio, 0, obj.Length.Value - taph),
FreeCAD.Vector(tapw, 0, obj.Length.Value - taph))
edge4 = Part.makeLine(FreeCAD.Vector(tapw, 0, obj.Length.Value - taph),
FreeCAD.Vector(tapw, 0, obj.Length.Value - chamfer))
if True:
edge5 = Part.makeLine(FreeCAD.Vector(tapw, 0, obj.Length.Value - chamfer),
FreeCAD.Vector(tapw - chamfer, 0, obj.Length.Value))
else:
edge5 = Part.Arc(FreeCAD.Vector(tapw, 0, obj.Length.Value - chamfer),
FreeCAD.Vector(tapw - chamfer2, 0, obj.Length.Value - chamfer2),
FreeCAD.Vector(tapw - chamfer, 0, obj.Length.Value)
).toShape()
edge6 = Part.makeLine(FreeCAD.Vector(tapw - chamfer, 0, obj.Length.Value),
FreeCAD.Vector(0, 0, obj.Length.Value))
w = Part.Wire([edge1, edge2, edge3, edge4, edge5, edge6])
return w
class _FenceReinforcePost(ArchComponent.Component):
def __init__(self, obj): def __init__(self, obj):
ArchComponent.Component.__init__(self, obj) ArchComponent.Component.__init__(self, obj)
self.setProperties(obj) self.setProperties(obj)
@@ -199,10 +150,18 @@ class _FenceReinforcePost(ArchComponent.Component):
self.setProperties(obj) self.setProperties(obj)
def execute(self, obj): def execute(self, obj):
pl = obj.Placement pl = obj.Placement
w = self.getWire(obj)
lip_heigth = 20
post = Part.makeCylinder(obj.Diameter.Value / 2, obj.Length.Value - lip_heigth)
lip = Part.makeCylinder(obj.Diameter.Value / 2 + 2, lip_heigth)
lip = lip.makeFillet(5, [lip.Edges[0]])
lip.translate(FreeCAD.Vector(0, 0, obj.Length.Value - lip_heigth))
obj.Shape = post.fuse(lip)
obj.Placement = pl
return
w = self.getWire(obj)
try: try:
# sh = w.makePipeShell([p], True, False, 2) # sh = w.makePipeShell([p], True, False, 2)
sh = w.revolve(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(0.0, 0.0, 1.0), 360) sh = w.revolve(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(0.0, 0.0, 1.0), 360)
@@ -244,7 +203,7 @@ class _FenceReinforcePost(ArchComponent.Component):
return w return w
class _ViewProviderFencePost(ArchComponent.ViewProviderComponent): class ViewProviderFencePost(ArchComponent.ViewProviderComponent):
"A View Provider for the Pipe object" "A View Provider for the Pipe object"
def __init__(self, vobj): def __init__(self, vobj):
@@ -254,7 +213,7 @@ class _ViewProviderFencePost(ArchComponent.ViewProviderComponent):
return ":/icons/Arch_Pipe_Tree.svg" return ":/icons/Arch_Pipe_Tree.svg"
class _CommandFencePost: class CommandFencePost:
"the Arch Pipe command definition" "the Arch Pipe command definition"
def GetResources(self): def GetResources(self):
@@ -269,17 +228,5 @@ class _CommandFencePost:
return not FreeCAD.ActiveDocument is None return not FreeCAD.ActiveDocument is None
def Activated(self): def Activated(self):
if True: makeFencePost()
makeFencePost()
else:
FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Create Pipe"))
FreeCADGui.addModule("Arch")
FreeCADGui.doCommand("obj = Arch.makePipe()")
FreeCADGui.addModule("Draft")
FreeCADGui.doCommand("Draft.autogroup(obj)")
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute() FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
FreeCADGui.addCommand('FencePost', _CommandFencePost())

View File

@@ -0,0 +1,200 @@
# /**********************************************************************
# * *
# * Copyright (c) 2021 Javier Braña <javier.branagutierrez@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify*
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307*
# * USA *
# * *
# ***********************************************************************
import FreeCAD
import ArchComponent
import Part
import os
import zipfile
import re
if FreeCAD.GuiUp:
import FreeCADGui
from DraftTools import translate
else:
# \cond
def translate(ctxt,txt):
return txt
def QT_TRANSLATE_NOOP(ctxt,txt):
return txt
# \endcond
import os
from PVPlantResources import DirIcons as DirIcons
__title__ = "PVPlant Areas"
__author__ = "Javier Braña"
__url__ = "http://www.sogos-solar.com"
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", "PowerConversionSystem")
PowerConverter(obj)
ViewProviderPowerConverter(obj.ViewObject)
try:
folder = FreeCAD.ActiveDocument.PowerConversionSystemGroup
except:
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'PowerConversionSystemGroup')
folder.Label = "PowerConversionSystemGroup"
folder.addObject(obj)
return obj
class PowerConverter(ArchComponent.Component):
def __init__(self, obj):
''' Initialize the Area object '''
ArchComponent.Component.__init__(self, obj)
self.Type = None
self.obj = None
self.setProperties(obj)
def setProperties(self, obj):
pl = obj.PropertiesList
# Transformer properties
if not "Technology" in pl:
obj.addProperty("App::PropertyEnumeration",
"Technology",
"Transformer",
"Number of phases and type of transformer"
).Technology = ["Single Phase Transformer", "Three Phase Transformer"]
if not "PowerPrimary" in pl:
obj.addProperty("App::PropertyPower",
"PowerPrimary",
"Transformer",
"The base file this component is built upon").PowerPrimary = 6000000000
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):
""" Method run when the document is restored """
self.setProperties(obj)
def onBeforeChange(self, obj, prop):
''' '''
# 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):
''' '''
def execute(self, obj):
''' '''
# obj.Shape: compound
# |- body: compound
# |- transformer: solid
# |- primary switchgear: compound
# |- secundary 1 switchgear: compound
# |- secundary 2 switchgear: compound
pl = obj.Placement
obj.Shape = Part.makeBox(6058, 2438, 2591) # Placeholder for the shape
obj.Placement = pl
class ViewProviderPowerConverter(ArchComponent.ViewProviderComponent):
def __init__(self, vobj):
ArchComponent.ViewProviderComponent.__init__(self, vobj)
def getIcon(self):
return str(os.path.join(PVPlantResources.DirIcons, "Inverter.svg"))
class CommandPowerConverter:
def GetResources(self):
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "Inverter.svg")),
'Accel': "E, P",
'MenuText': "Power Converter",
'ToolTip': "Power Converter",}
def Activated(self):
sinverter = makePCS()
def IsActive(self):
active = not (FreeCAD.ActiveDocument is None)
return active
if FreeCAD.GuiUp:
FreeCADGui.addCommand('PowerConverter', CommandPowerConverter())

392
Electrical/group.py Normal file
View File

@@ -0,0 +1,392 @@
def groupTrackersToTransformers(transformer_power, max_distance):
import numpy as np
from scipy.spatial import KDTree
import FreeCAD
from collections import deque
# 1. Obtener todos los trackers válidos
valid_trackers = []
valid_points = []
valid_power = []
for tracker in FreeCAD.ActiveDocument.Objects:
if hasattr(tracker, 'Proxy') and (tracker.Proxy.Type == "Tracker"):
base = tracker.Placement.Base
if all(np.isfinite([base.x, base.y, base.z])):
valid_trackers.append(tracker)
valid_points.append([base.x, base.y])
valid_power.append(tracker.Setup.TotalPower)
if not valid_trackers:
FreeCAD.Console.PrintWarning("No se encontraron trackers válidos para agrupar\n")
return
# 2. Obtener parámetros de los CTs
target_power = transformer_power * 1.2
points = np.array(valid_points)
power_values = np.array(valid_power)
# 3. Determinar dirección de barrido (oeste a este por defecto)
min_x, min_y = np.min(points, axis=0)
max_x, max_y = np.max(points, axis=0)
# 4. Ordenar trackers de oeste a este (menor X a mayor X)
'''
Norte a Sur: sorted_indices = np.argsort(-points[:, 1]) (Y descendente)
Sur a Norte: sorted_indices = np.argsort(points[:, 1]) (Y ascendente)
Este a Oeste: sorted_indices = np.argsort(-points[:, 0]) (X descendente)
'''
sorted_indices = np.argsort(points[:, 0])
sorted_points = points[sorted_indices]
sorted_power = power_values[sorted_indices]
sorted_trackers = [valid_trackers[i] for i in sorted_indices]
# 5. Crear KDTree para búsquedas rápidas
kdtree = KDTree(sorted_points)
# 6. Algoritmo de barrido espacial
transformer_groups = []
used_indices = set()
# Función para expandir un grupo desde un punto inicial
def expand_group(start_idx):
group = []
total_power = 0
queue = deque([start_idx])
while queue and total_power < target_power:
idx = queue.popleft()
if idx in used_indices:
continue
# Añadir tracker al grupo si no excede la potencia
tracker_power = sorted_power[idx]
if total_power + tracker_power > target_power * 1.05:
continue
group.append(sorted_trackers[idx])
total_power += tracker_power
used_indices.add(idx)
# Buscar vecinos cercanos
neighbors = kdtree.query_ball_point(
sorted_points[idx],
max_distance
)
# Filtrar vecinos no usados y ordenar por proximidad al punto inicial
neighbors = [n for n in neighbors if n not in used_indices]
neighbors.sort(key=lambda n: abs(n - start_idx))
queue.extend(neighbors)
return group, total_power
# 7. Barrido principal de oeste a este
for i in range(len(sorted_points)):
if i in used_indices:
continue
group, total_power = expand_group(i)
if group:
# Calcular centro del grupo
group_points = np.array([t.Placement.Base[:2] for t in group])
center = np.mean(group_points, axis=0)
transformer_groups.append({
'trackers': group,
'total_power': total_power,
'center': center
})
# 8. Manejar grupos residuales (si los hay)
unused_indices = set(range(len(sorted_points))) - used_indices
if unused_indices:
# Intentar añadir trackers residuales a grupos existentes
for idx in unused_indices:
point = sorted_points[idx]
tracker_power = sorted_power[idx]
# Buscar el grupo más cercano que pueda aceptar este tracker
best_group = None
min_distance = float('inf')
for group in transformer_groups:
if group['total_power'] + tracker_power <= target_power * 1.05:
dist = np.linalg.norm(point - group['center'])
if dist < min_distance and dist < max_distance * 1.5:
min_distance = dist
best_group = group
# Añadir al grupo si se encontró uno adecuado
if best_group:
best_group['trackers'].append(sorted_trackers[idx])
best_group['total_power'] += tracker_power
# Actualizar centro del grupo
group_points = np.array([t.Placement.Base[:2] for t in best_group['trackers']])
best_group['center'] = np.mean(group_points, axis=0)
else:
# Crear un nuevo grupo con este tracker residual
group = [sorted_trackers[idx]]
center = point
transformer_groups.append({
'trackers': group,
'total_power': tracker_power,
'center': center
})
# 9. Crear los grupos en FreeCAD
transformer_group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "Transformers")
transformer_group.Label = "Centros de Transformación"
for i, group in enumerate(transformer_groups):
# Crear la esfera que representará el CT
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_shape.addProperty("App::PropertyLinkList", "Trackers", "CT",
"Lista de trackers asociados a este CT")
ct_shape.addProperty("App::PropertyFloat", "TotalPower", "CT",
"Potencia total del grupo (W)")
ct_shape.addProperty("App::PropertyFloat", "NominalPower", "CT",
"Potencia nominal del transformador (W)")
ct_shape.addProperty("App::PropertyFloat", "Utilization", "CT",
"Porcentaje de utilización (Total/Nominal)")
# Establecer valores de las propiedades
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_shape.Utilization
if utilization <= 100:
color = (0.0, 1.0, 0.0) # Verde
elif utilization <= 110:
color = (1.0, 1.0, 0.0) # Amarillo
else:
color = (1.0, 0.0, 0.0) # Rojo
ct_shape.ViewObject.ShapeColor = color
ct_shape.ViewObject.Transparency = 40 # 40% de transparencia
# Añadir etiqueta con información
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_shape)
FreeCAD.Console.PrintMessage(f"Se crearon {len(transformer_groups)} centros de transformación\n")
#onSelectGatePoint()
import FreeCAD, FreeCADGui, Part
import numpy as np
from scipy.stats import linregress
from PySide import QtGui
class InternalPathCreator:
def __init__(self, gate_point, strategy=1, path_width=4000):
self.gate_point = gate_point
self.strategy = strategy
self.path_width = path_width
self.ct_shapes = []
self.ct_positions = []
def get_transformers(self):
transformers_group = FreeCAD.ActiveDocument.getObject("Transformers")
if not transformers_group:
FreeCAD.Console.PrintError("No se encontró el grupo 'Transformers'\n")
return False
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_shapes:
base = sphere.Placement.Base
self.ct_positions.append(FreeCAD.Vector(base.x, base.y, 0))
return True
def create_paths(self):
if not self.get_transformers():
return []
if self.strategy == 1:
return self.create_direct_paths()
elif self.strategy == 2:
return self.create_unified_path()
else:
FreeCAD.Console.PrintError("Estrategia no válida. Use 1 o 2.\n")
return []
def create_direct_paths(self):
"""Estrategia 1: Caminos independientes desde cada CT hasta la puerta"""
paths = []
for ct in self.ct_positions:
paths.append([ct, self.gate_point])
return paths
def create_unified_path(self):
"""Estrategia 2: Único camino que une todos los CTs y la puerta usando regresión lineal"""
if not self.ct_positions:
return []
all_points = self.ct_positions + [self.gate_point]
x = [p.x for p in all_points]
y = [p.y for p in all_points]
# Manejar caso de puntos alineados verticalmente
if np.std(x) < 1e-6:
sorted_points = sorted(all_points, key=lambda p: p.y)
paths = []
for i in range(len(sorted_points) - 1):
paths.append([sorted_points[i], sorted_points[i + 1]])
return paths
# Calcular regresión lineal
slope, intercept, _, _, _ = linregress(x, y)
# Función para proyectar puntos
def project_point(point):
x0, y0 = point.x, point.y
if abs(slope) > 1e6:
return FreeCAD.Vector(x0, intercept, 0)
x_proj = (x0 + slope * (y0 - intercept)) / (1 + slope ** 2)
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
ref_point = projected_points[0]
direction_vector = FreeCAD.Vector(1, slope).normalize()
distances = []
for p in projected_points:
vec_to_point = p - ref_point
distance = vec_to_point.dot(direction_vector)
distances.append(distance)
# Ordenar por distancia
sorted_indices = np.argsort(distances)
sorted_points = [all_points[i] for i in sorted_indices]
# Crear caminos
paths = []
for i in range(len(sorted_points) - 1):
paths.append([sorted_points[i], sorted_points[i + 1]])
return paths
def create_3d_path(self, path_poly):
"""Crea geometría 3D para el camino adaptada a orientación norte-sur"""
segments = []
for i in range(len(path_poly.Vertexes) - 1):
start = path_poly.Vertexes[i].Point
end = path_poly.Vertexes[i + 1].Point
direction = end - start
# Determinar orientación predominante
if abs(direction.x) > abs(direction.y):
normal = FreeCAD.Vector(0, 1, 0) # Norte-sur
else:
normal = FreeCAD.Vector(1, 0, 0) # Este-oeste
offset = normal * self.path_width / 2
# Crear puntos para la sección transversal
p1 = start + offset
p2 = start - offset
p3 = end - offset
p4 = end + offset
# Crear cara
wire = Part.makePolygon([p1, p2, p3, p4, p1])
face = Part.Face(wire)
segments.append(face)
if segments:
'''road_shape = segments[0].fuse(segments[1:])
return road_shape.removeSplitter()'''
return Part.makeCompound(segments)
return Part.Shape()
def build(self):
paths = self.create_paths()
if not paths:
return
path_group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "InternalPaths")
path_group.Label = f"Caminos Internos (Estrategia {self.strategy})"
for i, path in enumerate(paths):
poly = Part.makePolygon(path)
# Objeto para la línea central
path_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", f"Path_{i + 1}")
path_obj.Shape = poly
path_obj.ViewObject.LineWidth = 3.0
path_obj.ViewObject.LineColor = (0.0, 0.0, 1.0)
# Objeto para la superficie 3D
road_shape = self.create_3d_path(poly)
road_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", f"Road_{i + 1}")
road_obj.Shape = road_shape
road_obj.ViewObject.ShapeColor = (0.7, 0.7, 0.7)
path_group.addObject(path_obj)
path_group.addObject(road_obj)
FreeCAD.Console.PrintMessage(f"Se crearon {len(paths)} segmentos de caminos internos\n")
# Función para mostrar el diálogo de estrategia
def show_path_strategy_dialog(gate_point):
dialog = QtGui.QDialog()
dialog.setWindowTitle("Seleccionar Estrategia de Caminos")
layout = QtGui.QVBoxLayout(dialog)
label = QtGui.QLabel("Seleccione la estrategia para crear los caminos internos:")
layout.addWidget(label)
rb1 = QtGui.QRadioButton("Estrategia 1: Caminos independientes desde cada CT hasta la puerta")
rb1.setChecked(True)
layout.addWidget(rb1)
rb2 = QtGui.QRadioButton("Estrategia 2: Único camino que une todos los CTs y la puerta")
layout.addWidget(rb2)
btn_box = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
layout.addWidget(btn_box)
def on_accept():
strategy = 1 if rb1.isChecked() else 2
dialog.accept()
creator = InternalPathCreator(gate_point, strategy)
creator.build()
btn_box.accepted.connect(on_accept)
btn_box.rejected.connect(dialog.reject)
dialog.exec_()
# Uso: seleccionar un punto para la puerta de entrada
def onSelectGatePoint():
'''gate = FreeCAD.ActiveDocument.findObjects(Name="FenceGate")[0]
gate_point = gate.Placement.Base
show_path_strategy_dialog(gate_point)'''
sel = FreeCADGui.Selection.getSelectionEx()[0]
show_path_strategy_dialog(sel.SubObjects[0].CenterOfMass)

View File

@@ -1,5 +1,6 @@
import math import math
import FreeCAD import FreeCAD
import Part
from Utils.PVPlantUtils import findObjects from Utils.PVPlantUtils import findObjects
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
@@ -75,8 +76,10 @@ def getWire(wire, nospline=False, width=.0):
import DraftGeomUtils import DraftGeomUtils
import math import math
offset = FreeCAD.ActiveDocument.Site.Origin
def fmt(vec, b=0.0): 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 = [] points = []
edges = Part.__sortEdges__(wire.Edges) edges = Part.__sortEdges__(wire.Edges)
@@ -259,15 +262,60 @@ class exportDXF:
'rotation': rotation 'rotation': rotation
}) })
def createPolyline(self, wire): def createPolyline(self, wire, layer=""):
try: try:
data = getWire(wire.Shape) data = getWire(wire.Shape)
lwp = self.msp.add_lwpolyline(data) lwp = self.msp.add_lwpolyline(data)
if layer:
lwp.dxf.layer = layer
return lwp return lwp
except Exception as e: except Exception as e:
print("Error creating polyline:", e) print("Error creating polyline:", e)
return None return None
def createHatch(self, wire, pattern="SOLID", scale=1.0, angle=0, layer=None):
"""Crea un sombreado (hatch) para un área"""
try:
# Obtener los puntos en metros
points = [(x, y) for (x, y, *_) in wire]
# Crear el hatch
hatch = self.msp.add_hatch(color=7, dxfattribs={'layer': layer})
if pattern == "SOLID":
# Sombreado sólido
hatch.set_solid_fill()
else:
# Patrón de sombreado
hatch.set_pattern_fill(name=pattern, scale=scale, angle=angle)
# Añadir el contorno
hatch.paths.add_polyline_path(points, is_closed=True)
return hatch
except Exception as e:
print("Error creating hatch:", e)
return None
def export_feature_image(self, feature, layer_name):
"""Exporta una imagen del GeoFeature y la añade al DXF"""
try:
# Añadir la imagen al DXF
image_def = self.doc.add_image_def(feature.ImageFile, size_in_pixel=(feature.XSize, feature.YSize))
self.msp.add_image(image_def,
insert=(0, 0, 0),
size_in_units=(feature.XSize,
feature.YSize),
rotation=0,
dxfattribs={'layer': layer_name})
print(f"Imagen exportada para {feature.Label}")
# Eliminar el archivo temporal
import os
os.unlink(temp_img.name)
except Exception as e:
print(f"Error en exportación de imagen: {e}")
# ================================================================================= # =================================================================================
# INTERFAZ DE USUARIO # INTERFAZ DE USUARIO
# ================================================================================= # =================================================================================
@@ -575,6 +623,59 @@ class LineTypeComboBox(QtWidgets.QComboBox):
finally: finally:
painter.end() # Asegurar que el painter se cierre correctamente painter.end() # Asegurar que el painter se cierre correctamente
layers = [
("Available area", QtGui.QColor(0, 204, 153), "Continuous", "1", True),
("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),
("Areas Offset", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
("Cable codes LV AC inverter", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
("Cable codes LV string", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
("Cable codes MV System", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
("CABLES LV AC inverter 120 mm2", QtGui.QColor(255, 204, 0), "Continuous", "1", True),
("CABLES LV AC inverter 185 mm2", QtGui.QColor(204, 153, 0), "Continuous", "1", True),
("CABLES LV string 4 mm2", QtGui.QColor(255, 255, 0), "Continuous", "1", True),
("CABLES LV string 10 mm2", QtGui.QColor(255, 255, 102), "Continuous", "1", True),
("CABLES MV system 300 mm2", QtGui.QColor(102, 51, 0), "Continuous", "1", True),
("CIVIL Fence", QtGui.QColor(102, 102, 102), "FENCELINE1", "1", True),
("CIVIL External Roads", QtGui.QColor(91, 91, 91), "Continuous", "1", True),
("CIVIL External Roads Axis", QtGui.QColor(255, 255, 192), "Dashed", "1", True),
("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 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),
("Major contour value", QtGui.QColor(0, 0, 0), "Continuous", "1", True),
("Minor contour line", QtGui.QColor(128, 128, 128), "Continuous", "1", True),
("Minor contour value", QtGui.QColor(128, 128, 128), "Continuous", "1", True),
("Power Stations", QtGui.QColor(255, 0, 0), "Continuous", "1", True),
("Power Stations Names", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
("ST", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
("ST Names", QtGui.QColor(255, 255, 0), "Continuous", "1", True),
("String Inv", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
("STRUC Structure 1", QtGui.QColor(0, 0, 255), "Continuous", "1", True),
("STRUC Structure 2", QtGui.QColor(0, 0, 204), "Continuous", "1", True),
("STRUC Structure 3", QtGui.QColor(0, 0, 153), "Continuous", "1", True),
("STRUC Structure 4", QtGui.QColor(0, 0, 128), "Continuous", "1", True),
("STRUC Structure 5", QtGui.QColor(0, 0, 102), "Continuous", "1", True),
("STRUC Structure 6", QtGui.QColor(0, 0, 76), "Continuous", "1", True),
("STRUC Structure 7", QtGui.QColor(0, 0, 51), "Continuous", "1", True),
("STRUC Structure 8", QtGui.QColor(0, 0, 25), "Continuous", "1", True),
("Structure Codes", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
("TRENCHES Low voltage 400.0 x 1000.0 m", QtGui.QColor(128, 128, 128), "Continuous", "1", True),
("TRENCHES Medium voltage 400.0 x 1000.", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
("TRENCHES Medium voltage 800.0 x 1000.", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
("TRENCHES Medium voltage 800.0 x 1500.", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
]
class _PVPlantExportDXF(QtGui.QWidget): class _PVPlantExportDXF(QtGui.QWidget):
'''The editmode TaskPanel to select what you want to export''' '''The editmode TaskPanel to select what you want to export'''
@@ -610,10 +711,8 @@ class _PVPlantExportDXF(QtGui.QWidget):
self.form.tableLayers.removeRow(row) self.form.tableLayers.removeRow(row)
# Configuración de las capas por defecto # Configuración de las capas por defecto
self.add_row("Areas_Boundary", QtGui.QColor(0, 125, 125), "FENCELINE1", "1", True) for row in layers:
self.add_row("Areas_Exclusion", QtGui.QColor(255, 0, 0), "CONTINUOUS", "1", True) self.add_row(*row)
self.add_row("Internal_Roads", QtGui.QColor(128, 128, 128), "CONTINUOUS", "1", True)
self.add_row("Frames", QtGui.QColor(0, 255, 0), "CONTINUOUS", "1", True)
def save_project_settings(self): def save_project_settings(self):
"""Guarda la configuración actual en el proyecto de FreeCAD""" """Guarda la configuración actual en el proyecto de FreeCAD"""
@@ -778,25 +877,28 @@ class _PVPlantExportDXF(QtGui.QWidget):
) )
def writeArea(self, exporter): def writeArea(self, exporter):
exporter.createPolyline(FreeCAD.ActiveDocument.Site.Boundary, "boundary") exporter.createPolyline(FreeCAD.ActiveDocument.Site.Boundary, "Available area")
areas_types = [("Boundaries", "Available area"),
areas_types = ["Boundaries", "Exclusions", "Offsets"] ("CadastralPlots", "Areas Cadastral Plot"),
("Exclusions", "Areas Exclusion"),
("Offsets", "Areas Offset")]
for area_type in areas_types: for area_type in areas_types:
if hasattr(FreeCAD.ActiveDocument, area_type): if hasattr(FreeCAD.ActiveDocument, area_type[0]):
for area in FreeCAD.ActiveDocument.Boundaries.Group: area_group = FreeCAD.ActiveDocument.getObjectsByLabel(area_type[0])
exporter.createPolyline(area, "Areas_Boundary") if len(area_group):
for area in area_group[0].Group:
tmp = exporter.createPolyline(area, area_type[1])
if area_type[0] == "Exclusions":
exporter.createHatch(
tmp,
pattern="ANSI37",
scale=0.3,
angle=0,
layer=area_type[1]
)
for obj in FreeCADGui.Selection.getSelection():
tmp = exporter.createPolyline(obj, areas_types[0][1])
'''for area in FreeCAD.ActiveDocument.Boundaries.Group:
pol = exporter.createPolyline(area)
pol.dxf.layer = "Areas_Boundary"
for area in FreeCAD.ActiveDocument.Exclusions.Group:
pol = exporter.createPolyline(area)
pol.dxf.layer = "Areas_Exclusion"
for area in FreeCAD.ActiveDocument.Offsets.Group:
pol = exporter.createPolyline(area)
pol.dxf.layer = "Areas_Offsets"'''
def writeFrameSetups(self, exporter): def writeFrameSetups(self, exporter):
if not hasattr(FreeCAD.ActiveDocument, "Site"): if not hasattr(FreeCAD.ActiveDocument, "Site"):
@@ -811,11 +913,11 @@ class _PVPlantExportDXF(QtGui.QWidget):
w.Placement.Base = w.Placement.Base.sub(center) w.Placement.Base = w.Placement.Base.sub(center)
block.add_lwpolyline(getWire(w)) block.add_lwpolyline(getWire(w))
block.add_circle((0, 0), 0.2, dxfattribs={'color': 2}) block.add_circle((0, 0), 0.2, dxfattribs={'layer': 'Structure Posts'}) #'color': 2,
p = math.sin(math.radians(45)) * 0.2 p = math.sin(math.radians(45)) * 0.2
block.add_line((-p, -p), (p, p), dxfattribs={"layer": "MyLines"}) block.add_line((-p, -p), (p, p), dxfattribs={'layer': 'Structure Posts'})
block.add_line((-p, p), (p, -p), dxfattribs={"layer": "MyLines"}) block.add_line((-p, p), (p, -p), dxfattribs={'layer': 'Structure Posts'})
# 2. Frames # 2. Frames
for ts in FreeCAD.ActiveDocument.Site.Frames: for ts in FreeCAD.ActiveDocument.Site.Frames:
@@ -868,10 +970,10 @@ class _PVPlantExportDXF(QtGui.QWidget):
if FreeCAD.ActiveDocument.Transport: if FreeCAD.ActiveDocument.Transport:
for road in FreeCAD.ActiveDocument.Transport.Group: for road in FreeCAD.ActiveDocument.Transport.Group:
base = exporter.createPolyline(road, "External_Roads") base = exporter.createPolyline(road, "CIVIL External Roads")
base.dxf.const_width = road.Width base.dxf.const_width = road.Width
axis = exporter.createPolyline(road.Base, "External_Roads_Axis") axis = exporter.createPolyline(road, "CIVIL External Roads Axis")
axis.dxf.const_width = .2 axis.dxf.const_width = .2
def writeTrenches(self, exporter): def writeTrenches(self, exporter):
@@ -976,8 +1078,12 @@ class _PVPlantExportDXF(QtGui.QWidget):
self.writeTrenches(exporter) self.writeTrenches(exporter)
# Crear espacios de papel # Crear espacios de papel
self.setup_layout4(exporter.doc) #self.setup_layout4(exporter.doc)
self.createPaperSpaces(exporter) #self.createPaperSpaces(exporter)
if hasattr(FreeCAD.ActiveDocument, "Background"):
# Exportar como imagen en lugar de polilínea
exporter.export_feature_image(FreeCAD.ActiveDocument.Background, "Site_Image")
# Guardar archivo # Guardar archivo
exporter.save() exporter.save()

View File

@@ -29,6 +29,15 @@ import Part
import numpy import numpy
import os 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: if FreeCAD.GuiUp:
import FreeCADGui import FreeCADGui
from PySide import QtCore from PySide import QtCore
@@ -63,6 +72,11 @@ def check_collada():
FreeCAD.Console.PrintError(translate("PVPlant", "pycollada no encontrado, soporte Collada desactivado.") + "\n") FreeCAD.Console.PrintError(translate("PVPlant", "pycollada no encontrado, soporte Collada desactivado.") + "\n")
return COLLADA_AVAILABLE 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: # from ARCH:
def triangulate(shape): 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) 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" filename = f"{path}.pvc"
from xml.etree.ElementTree import Element, SubElement from xml.etree.ElementTree import Element, SubElement
@@ -291,17 +604,18 @@ def exportToPVC(path, exportTerrain = False):
# xml: 1. Asset: # xml: 1. Asset:
asset = SubElement(root, 'asset') asset = SubElement(root, 'asset')
asset_contributor = SubElement(asset, 'contributor') asset_contributor = SubElement(asset, 'contributor')
asset_contributor_autor = SubElement(asset_contributor, 'autor') asset_contributor_autor = SubElement(asset_contributor, 'author')
#asset_contributor_autor.text = author asset_contributor_autor.text = author
asset_contributor_authoring_tool = SubElement(asset_contributor, 'authoring_tool') 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_contributor_comments = SubElement(asset_contributor, 'comments')
asset_keywords = SubElement(asset, 'keywords') asset_keywords = SubElement(asset, 'keywords')
asset_revision = SubElement(asset, 'revision') asset_revision = SubElement(asset, 'revision')
asset_subject = SubElement(asset, 'subject') asset_subject = SubElement(asset, 'subject')
asset_tittle = SubElement(asset, 'title') asset_tittle = SubElement(asset, 'title')
#asset_tittle.text = FreeCAD.ActiveDocument.Name asset_tittle.text = FreeCAD.ActiveDocument.Name
asset_unit = SubElement(asset, 'unit') asset_unit = SubElement(asset, 'unit')
asset_unit.set('meter', '0.001') asset_unit.set('meter', '0.001')
asset_unit.set('name', 'millimeter') asset_unit.set('name', 'millimeter')
@@ -359,7 +673,6 @@ def exportToPVC(path, exportTerrain = False):
# xml: 4. library_geometries: # xml: 4. library_geometries:
library_geometries = SubElement(root, 'library_geometries') library_geometries = SubElement(root, 'library_geometries')
def add_geometry(objtype, vindex, findex, objind = 0, centers = None): def add_geometry(objtype, vindex, findex, objind = 0, centers = None):
isFrame = False isFrame = False
if objtype == 0: if objtype == 0:
geometryName = 'Frame' geometryName = 'Frame'
@@ -505,36 +818,20 @@ def exportToPVC(path, exportTerrain = False):
end_time.text = '1.000000' end_time.text = '1.000000'
# xml: 6. scene: # xml: 6. scene:
scene = SubElement(root, 'scene') '''scene = SubElement(root, 'scene')
instance = SubElement(scene, 'instance_visual_scene') instance = SubElement(scene, 'instance_visual_scene')
instance.set('url', '#') instance.set('url', '#')'''
full_list_of_objects = FreeCAD.ActiveDocument.Objects
# CASO 1 - FRAMES: # CASO 1 - FRAMES:
frameType = site.Frames frameType = site.Frames
frame_setup = {"type": [],
"footprint": []}
for obj in frameType:
frame_setup["type"] = obj
frame_setup["footprint"] = ""
objind = 0 objind = 0
# TODO: revisar # TODO: revisar
for typ in frameType: for typ in frameType:
isTracker = "tracker" in typ.Proxy.Type.lower() isTracker = "tracker" in typ.Proxy.Type.lower()
isTracker = False #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()
objectlist = utils.findObjects("Tracker")
for obj in objectlist: for obj in objectlist:
if obj.Setup == typ: if obj.Setup == typ:
findex = numpy.array([]) findex = numpy.array([])
@@ -580,7 +877,6 @@ def exportToPVC(path, exportTerrain = False):
v = Topology[0][i] v = Topology[0][i]
vindex[list(range(i * 3, i * 3 + 3))] = (-(v.x - center.x) * scale, (v.z - center.z) * scale, vindex[list(range(i * 3, i * 3 + 3))] = (-(v.x - center.x) * scale, (v.z - center.z) * scale,
(v.y - center.y) * scale) (v.y - center.y) * scale)
# 2. face indices # 2. face indices
findex = numpy.empty(len(Topology[1]) * 3, numpy.int64) findex = numpy.empty(len(Topology[1]) * 3, numpy.int64)
for i in range(len(Topology[1])): for i in range(len(Topology[1])):

View File

@@ -42,11 +42,10 @@ class OSMImporter:
} }
self.ssl_context = ssl.create_default_context(cafile=certifi.where()) self.ssl_context = ssl.create_default_context(cafile=certifi.where())
def transform_from_latlon(self, lat, lon): def transform_from_latlon(self, coordinates):
point = ImportElevation.getElevationFromOE([[lat, lon], ]) points = ImportElevation.getElevationFromOE(coordinates)
return FreeCAD.Vector(point[0].x, point[0].y, point[0].z) * scale - self.Origin pts = [FreeCAD.Vector(p.x, p.y, p.z).sub(self.Origin) for p in points]
'''x, y, _, _ = utm.from_latlon(lat, lon) return pts
return FreeCAD.Vector(x, y, .0) * scale - self.Origin'''
def get_osm_data(self, bbox): def get_osm_data(self, bbox):
query = f""" query = f"""
@@ -70,7 +69,7 @@ class OSMImporter:
headers={'User-Agent': 'FreeCAD-OSM-Importer/1.0'}, headers={'User-Agent': 'FreeCAD-OSM-Importer/1.0'},
method='POST' 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): def create_layer(self, name):
if not FreeCAD.ActiveDocument.getObject(name): if not FreeCAD.ActiveDocument.getObject(name):
@@ -81,11 +80,16 @@ class OSMImporter:
root = ET.fromstring(osm_data) root = ET.fromstring(osm_data)
# Almacenar nodos transformados # Almacenar nodos transformados
coordinates = [[float(node.attrib['lat']), float(node.attrib['lon'])] for node in root.findall('node')]
coordinates = self.transform_from_latlon(coordinates)
for i, node in enumerate(root.findall('node')):
self. nodes[node.attrib['id']] = coordinates[i]
'''return
for node in root.findall('node'): for node in root.findall('node'):
self.nodes[node.attrib['id']] = self.transform_from_latlon( self.nodes[node.attrib['id']] = self.transform_from_latlon(
float(node.attrib['lat']), float(node.attrib['lat']),
float(node.attrib['lon']) float(node.attrib['lon'])
) )'''
# Procesar ways # Procesar ways
for way in root.findall('way'): for way in root.findall('way'):
@@ -162,11 +166,10 @@ class OSMImporter:
def create_buildings(self): def create_buildings(self):
building_layer = self.create_layer("Buildings") building_layer = self.create_layer("Buildings")
for way_id, data in self.ways_data.items(): for way_id, data in self.ways_data.items():
print(data)
if 'building' not in data['tags']: if 'building' not in data['tags']:
continue continue
print(data)
tags = data['tags'] tags = data['tags']
nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes] nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes]

View File

@@ -59,6 +59,7 @@ class PVPlantWorkbench(Workbench):
"PVPlantBuilding", "PVPlantBuilding",
"PVPlantFenceGroup", "PVPlantFenceGroup",
]''' ]'''
from Electrical.PowerConverter import PowerConverter
self.electricalList = ["PVPlantStringBox", self.electricalList = ["PVPlantStringBox",
"PVPlantCable", "PVPlantCable",
"PVPlanElectricalLine", "PVPlanElectricalLine",
@@ -66,6 +67,8 @@ class PVPlantWorkbench(Workbench):
"Stringing", "Stringing",
"Separator", "Separator",
"StringInverter", "StringInverter",
"Separator",
"PowerConverter"
] ]
self.roads = ["PVPlantRoad", self.roads = ["PVPlantRoad",

View File

@@ -540,6 +540,7 @@ def makeTrackerSetup(name="TrackerSetup"):
pass pass
return obj return obj
def getarray(array, numberofpoles): def getarray(array, numberofpoles):
if len(array) == 0: if len(array) == 0:
newarray = [0] * numberofpoles newarray = [0] * numberofpoles
@@ -568,6 +569,7 @@ def getarray(array, numberofpoles):
newarray = [array[0]] * numberofpoles newarray = [array[0]] * numberofpoles
return newarray return newarray
class TrackerSetup(FrameSetup): class TrackerSetup(FrameSetup):
"A 1 Axis Tracker Obcject" "A 1 Axis Tracker Obcject"
@@ -589,7 +591,7 @@ class TrackerSetup(FrameSetup):
obj.addProperty("App::PropertyDistance", obj.addProperty("App::PropertyDistance",
"MotorGap", "MotorGap",
"ModuleArray", "ModuleArray",
QT_TRANSLATE_NOOP("App::Property", "Thse height of this object") QT_TRANSLATE_NOOP("App::Property", "The height of this object")
).MotorGap = 550 ).MotorGap = 550
if not "UseGroupsOfModules" in pl: if not "UseGroupsOfModules" in pl:
@@ -880,6 +882,9 @@ class TrackerSetup(FrameSetup):
def CalculatePosts(self, obj, totalh, totalw): def CalculatePosts(self, obj, totalh, totalw):
# Temp: utilizar el uso de versiones: # Temp: utilizar el uso de versiones:
if len(obj.PoleType) == 0:
return None, None
ver = 1 ver = 1
if ver == 0: if ver == 0:
# versión 0: # versión 0:
@@ -906,8 +911,8 @@ class TrackerSetup(FrameSetup):
elif ver == 1: elif ver == 1:
# versión 1: # versión 1:
linetmp = Part.LineSegment(FreeCAD.Vector(0), FreeCAD.Vector(0, 10, 0)).toShape() linetmp = Part.LineSegment(FreeCAD.Vector(0), FreeCAD.Vector(0, 10, 0)).toShape()
compoundPoles = Part.makeCompound([]) compound_poles = Part.makeCompound([])
compoundAxis = Part.makeCompound([]) compound_axis = Part.makeCompound([])
offsetX = - totalw / 2 offsetX = - totalw / 2
arrayDistance = obj.DistancePole arrayDistance = obj.DistancePole
@@ -915,15 +920,16 @@ class TrackerSetup(FrameSetup):
arrayPost = obj.PoleSequence arrayPost = obj.PoleSequence
for x in range(int(obj.NumberPole.Value)): 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] offsetX += arrayDistance[x]
postCopy.Placement.Base = FreeCAD.Vector(offsetX, 0, -(postCopy.BoundBox.ZLength - arrayAerial[x])) post_copy.Placement.Base = FreeCAD.Vector(offsetX, 0, -(post_copy.BoundBox.ZLength - arrayAerial[x]))
compoundPoles.add(postCopy) compound_poles.add(post_copy)
axis = linetmp.copy() axis = linetmp.copy()
axis.Placement.Base = FreeCAD.Vector(offsetX, 0, arrayAerial[x]) axis.Placement.Base = FreeCAD.Vector(offsetX, 0, arrayAerial[x])
compoundAxis.add(axis) compound_axis.add(axis)
return compoundPoles, compoundAxis
return compound_poles, compound_axis
def execute(self, obj): def execute(self, obj):
# obj.Shape: compound # obj.Shape: compound
@@ -1029,14 +1035,14 @@ class Tracker(ArchComponent.Component):
"AngleY", "AngleY",
"Outputs", "Outputs",
QT_TRANSLATE_NOOP("App::Property", "The height of this object") QT_TRANSLATE_NOOP("App::Property", "The height of this object")
).AngleX = 0 ).AngleY = 0
if not ("AngleZ" in pl): if not ("AngleZ" in pl):
obj.addProperty("App::PropertyAngle", obj.addProperty("App::PropertyAngle",
"AngleZ", "AngleZ",
"Outputs", "Outputs",
QT_TRANSLATE_NOOP("App::Property", "The height of this object") QT_TRANSLATE_NOOP("App::Property", "The height of this object")
).AngleX = 0 ).AngleZ = 0
self.Type = "Tracker" self.Type = "Tracker"
#obj.Type = self.Type #obj.Type = self.Type
@@ -1056,12 +1062,15 @@ class Tracker(ArchComponent.Component):
if prop.startswith("Angle"): if prop.startswith("Angle"):
base = obj.Placement.Base base = obj.Placement.Base
angles = obj.Placement.Rotation.toEulerAngles("XYZ") angles = obj.Placement.Rotation.toEulerAngles("XYZ")
# Actualizar rotación según el ángulo modificado
if prop == "AngleX": if prop == "AngleX":
rot = FreeCAD.Rotation(angles[2], angles[1], obj.AngleX.Value) rot = FreeCAD.Rotation(angles[2], angles[1], obj.AngleX.Value)
elif prop == "AngleY": elif prop == "AngleY":
rot = FreeCAD.Rotation(angles[2], obj.AngleY.Value, angles[0]) rot = FreeCAD.Rotation(angles[2], obj.AngleY.Value, angles[0])
elif prop == "AngleZ": elif prop == "AngleZ":
rot = FreeCAD.Rotation(obj.AngleZ.Value, angles[1], angles[0]) rot = FreeCAD.Rotation(obj.AngleZ.Value, angles[1], angles[0])
obj.Placement = FreeCAD.Placement(base, rot, FreeCAD.Vector(0,0,0)) obj.Placement = FreeCAD.Placement(base, rot, FreeCAD.Vector(0,0,0))
if hasattr(FreeCAD.ActiveDocument, "FramesChecking"): if hasattr(FreeCAD.ActiveDocument, "FramesChecking"):
@@ -1083,28 +1092,38 @@ class Tracker(ArchComponent.Component):
# |-- PoleAxes: Edge # |-- PoleAxes: Edge
if obj.Setup is None: if obj.Setup is None:
print("Warning: No Setup defined for tracker")
return return
pl = obj.Placement try:
shape = obj.Setup.Shape.copy() pl = obj.Placement
shape = obj.Setup.Shape.copy()
p1 = shape.SubShapes[0].SubShapes[1].SubShapes[0].CenterOfMass # Rotar módulos
p2 = min(shape.SubShapes[0].SubShapes[1].SubShapes[0].Faces, key=lambda face: face.Area).CenterOfMass p1 = shape.SubShapes[0].SubShapes[1].SubShapes[0].CenterOfMass
axis = p1 - p2 p2 = min(shape.SubShapes[0].SubShapes[1].SubShapes[0].Faces, key=lambda face: face.Area).CenterOfMass
modules = shape.SubShapes[0].rotate(p1, axis, obj.Tilt.Value) axis = p1 - p2
modules = shape.SubShapes[0].rotate(p1, axis, obj.Tilt.Value)
angle = obj.Placement.Rotation.toEuler()[1] # Rotar postes
newpoles = Part.makeCompound([]) angle = obj.Placement.Rotation.toEuler()[1]
for i in range(len(shape.SubShapes[1].SubShapes[0].SubShapes)): newpoles = Part.makeCompound([])
pole = shape.SubShapes[1].SubShapes[0].SubShapes[i] for i in range(len(shape.SubShapes[1].SubShapes[0].SubShapes)):
axis = shape.SubShapes[1].SubShapes[1].SubShapes[i] pole = shape.SubShapes[1].SubShapes[0].SubShapes[i]
base = axis.Vertexes[0].Point axis = shape.SubShapes[1].SubShapes[1].SubShapes[i]
axis = axis.Vertexes[1].Point - axis.Vertexes[0].Point base = axis.Vertexes[0].Point
newpoles.add(pole.rotate(base, axis, -angle)) axis = axis.Vertexes[1].Point - axis.Vertexes[0].Point
poles = Part.makeCompound([newpoles, shape.SubShapes[1].SubShapes[1].copy()]) newpoles.add(pole.rotate(base, axis, -angle))
poles = Part.makeCompound([newpoles, shape.SubShapes[1].SubShapes[1].copy()])
obj.Shape = Part.makeCompound([modules, poles]) # Crear forma final
obj.Placement = pl obj.Shape = Part.makeCompound([modules, poles])
obj.AngleX, obj.AngleY, obj.AngleZ = obj.Placement.Rotation.toEulerAngles("XYZ") 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): class ViewProviderTracker(ArchComponent.ViewProviderComponent):
@@ -1271,6 +1290,7 @@ class CommandFixedRack:
#FreeCADGui.Control.showDialog(self.TaskPanel) #FreeCADGui.Control.showDialog(self.TaskPanel)
return return
class CommandTrackerSetup: class CommandTrackerSetup:
"the Arch Building command definition" "the Arch Building command definition"
@@ -1292,6 +1312,7 @@ class CommandTrackerSetup:
FreeCADGui.Control.showDialog(self.TaskPanel) FreeCADGui.Control.showDialog(self.TaskPanel)
return return
class CommandTracker: class CommandTracker:
"the Arch Building command definition" "the Arch Building command definition"

View File

@@ -122,6 +122,7 @@ def getElevationFromOE(coordinates):
try: try:
r = get(query, timeout=20, verify=certifi.where()) # <-- Corrección aquí r = get(query, timeout=20, verify=certifi.where()) # <-- Corrección aquí
except RequestException as e: except RequestException as e:
print(f"Error en la solicitud: {str(e)}")
points = [] points = []
for i, point in enumerate(coordinates): for i, point in enumerate(coordinates):
c = utm.from_latlon(point[0], point[1]) c = utm.from_latlon(point[0], point[1])

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,318 @@
<string>Park Settings</string> <string>Park Settings</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Estructura:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="2">
<widget class="QWidget" name="widget_2" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="buttonPVArea">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="3">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Configuración</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="horizontalSpacing">
<number>10</number>
</property>
<property name="verticalSpacing">
<number>6</number>
</property>
<item row="8" column="1">
<widget class="QComboBox" name="comboDirV">
<item>
<property name="text">
<string>De arriba a abajo</string>
</property>
</item>
<item>
<property name="text">
<string>De abajo a arriba</string>
</property>
</item>
<item>
<property name="text">
<string>Del centro a los lados</string>
</property>
</item>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_2">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Dirección Horizontal</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QComboBox" name="comboDirH">
<item>
<property name="text">
<string>De izquierda a derecha</string>
</property>
</item>
<item>
<property name="text">
<string>De derecha a izquiera</string>
</property>
</item>
<item>
<property name="text">
<string>De centro a los lados</string>
</property>
</item>
</widget>
</item>
<item row="11" column="1">
<widget class="QCheckBox" name="cbAlignFrames">
<property name="text">
<string>Alinear estructuras</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="editGapRows">
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
</property>
<property name="suffix">
<string> mm</string>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>500</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Pitch</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QDoubleSpinBox" name="editOffsetHorizontal">
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
</property>
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Orientación</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_7">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Offset Horizontal</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_9">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Offset Vertical</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QDoubleSpinBox" name="editOffsetVertical">
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
</property>
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="editGapCols">
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
</property>
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>100.000000000000000</double>
</property>
<property name="value">
<double>5.000000000000000</double>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboOrientation">
<item>
<property name="text">
<string>Norte - Sur</string>
</property>
</item>
<item>
<property name="text">
<string>Este - Oeste</string>
</property>
</item>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_11">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Dirección Vertical</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Espacio entre filas</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="editInnerSpacing">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string> - Inner Spacing</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="6" column="0" colspan="3"> <item row="6" column="0" colspan="3">
<widget class="QGroupBox" name="groupCorridor"> <widget class="QGroupBox" name="groupCorridor">
<property name="title"> <property name="title">
@@ -59,10 +371,10 @@
<item row="3" column="1"> <item row="3" column="1">
<widget class="QDoubleSpinBox" name="editRowGap"> <widget class="QDoubleSpinBox" name="editRowGap">
<property name="alignment"> <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property> </property>
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
</property> </property>
<property name="prefix"> <property name="prefix">
<string/> <string/>
@@ -110,10 +422,10 @@
<item row="1" column="1"> <item row="1" column="1">
<widget class="QDoubleSpinBox" name="editColGap"> <widget class="QDoubleSpinBox" name="editColGap">
<property name="alignment"> <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property> </property>
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
</property> </property>
<property name="prefix"> <property name="prefix">
<string/> <string/>
@@ -135,10 +447,10 @@
<item row="2" column="1"> <item row="2" column="1">
<widget class="QSpinBox" name="editRowCount"> <widget class="QSpinBox" name="editRowCount">
<property name="alignment"> <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property> </property>
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
</property> </property>
<property name="value"> <property name="value">
<number>4</number> <number>4</number>
@@ -148,10 +460,10 @@
<item row="0" column="1"> <item row="0" column="1">
<widget class="QSpinBox" name="editColCount"> <widget class="QSpinBox" name="editColCount">
<property name="alignment"> <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property> </property>
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
</property> </property>
<property name="value"> <property name="value">
<number>8</number> <number>8</number>
@@ -161,45 +473,6 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="1" column="2">
<widget class="QPushButton" name="buttonPVArea">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QWidget" name="widget_2" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="buttonAddFrame">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonRemoveFrame">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
@@ -207,282 +480,9 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="editPVArea"/> <widget class="QLineEdit" name="editPVArea"/>
</item> </item>
<item row="5" column="0" colspan="3">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Configuración</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="horizontalSpacing">
<number>10</number>
</property>
<property name="verticalSpacing">
<number>6</number>
</property>
<item row="8" column="0">
<widget class="QLabel" name="label_9">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Offset Vertical</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Orientación</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QCheckBox" name="cbAlignFrames">
<property name="text">
<string>Alinear estructuras</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Espacio entre filas</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_2">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Dirección Horizontal</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_7">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Offset Horizontal</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Pitch</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_11">
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Dirección Vertical</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboOrientation">
<item>
<property name="text">
<string>Norte - Sur</string>
</property>
</item>
<item>
<property name="text">
<string>Este - Oeste</string>
</property>
</item>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="editGapCols">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>100.000000000000000</double>
</property>
<property name="value">
<double>5.000000000000000</double>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="comboDirH">
<item>
<property name="text">
<string>De izquierda a derecha</string>
</property>
</item>
<item>
<property name="text">
<string>De derecha a izquiera</string>
</property>
</item>
<item>
<property name="text">
<string>De centro a los lados</string>
</property>
</item>
</widget>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="comboDirV">
<item>
<property name="text">
<string>De arriba a abajo</string>
</property>
</item>
<item>
<property name="text">
<string>De abajo a arriba</string>
</property>
</item>
<item>
<property name="text">
<string>Del centro a los lados</string>
</property>
</item>
</widget>
</item>
<item row="7" column="1">
<widget class="QDoubleSpinBox" name="editOffsetHorizontal">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QDoubleSpinBox" name="editOffsetVertical">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="suffix">
<string> m</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="editGapRows">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="suffix">
<string> mm</string>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>500</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" alignment="Qt::AlignTop">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Estructura:</string>
</property>
</widget>
</item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QListWidget" name="listFrameSetups"> <widget class="QListWidget" name="listFrameSetups">
<property name="maximumSize"> <property name="maximumSize">
@@ -493,11 +493,19 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1">
<widget class="QCheckBox" name="cbSubfolders">
<property name="text">
<string>Organizar en subcarpetas</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<tabstops> <tabstops>
<tabstop>buttonAddFrame</tabstop>
<tabstop>buttonRemoveFrame</tabstop>
<tabstop>editPVArea</tabstop> <tabstop>editPVArea</tabstop>
<tabstop>buttonPVArea</tabstop> <tabstop>buttonPVArea</tabstop>
<tabstop>comboOrientation</tabstop> <tabstop>comboOrientation</tabstop>

View File

@@ -771,12 +771,12 @@ class _PVPlantSite(ArchSite._Site):
import PVPlantImportGrid import PVPlantImportGrid
x, y, zone_number, zone_letter = utm.from_latlon(lat, lon) x, y, zone_number, zone_letter = utm.from_latlon(lat, lon)
self.obj.UtmZone = zone_list[zone_number - 1] self.obj.UtmZone = zone_list[zone_number - 1]
zz = PVPlantImportGrid.getElevationFromOE([[lat, lon]])
self.obj.Origin = FreeCAD.Vector(x * 1000, y * 1000, zz[0].z) point = PVPlantImportGrid.getElevationFromOE([[lat, lon]])
#self.obj.OriginOffset = FreeCAD.Vector(x * 1000, y * 1000, 0) #?? self.obj.Origin = FreeCAD.Vector(point[0].x, point[0].y, point[0].z)
self.obj.Latitude = lat self.obj.Latitude = lat
self.obj.Longitude = lon self.obj.Longitude = lon
self.obj.Elevation = zz[0].z self.obj.Elevation = point[0].z
class _ViewProviderSite(ArchSite._ViewProviderSite): class _ViewProviderSite(ArchSite._ViewProviderSite):

View File

@@ -73,6 +73,42 @@ line_patterns = {
"Dot (.5x) ...............................": 0x5555, "Dot (.5x) ...............................": 0x5555,
"Dot (2x) . . . . . . . . . . .": 0x8888} "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"): def makeTerrain(name="Terrain"):
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Terrain") obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Terrain")
obj.Label = name obj.Label = name
@@ -81,7 +117,6 @@ def makeTerrain(name="Terrain"):
FreeCAD.ActiveDocument.recompute() FreeCAD.ActiveDocument.recompute()
return obj return obj
class Terrain(ArchComponent.Component): class Terrain(ArchComponent.Component):
"A Shadow Terrain Obcject" "A Shadow Terrain Obcject"
@@ -161,101 +196,110 @@ class Terrain(ArchComponent.Component):
if prop == "DEM" or prop == "CuttingBoundary": if prop == "DEM" or prop == "CuttingBoundary":
from datetime import datetime from datetime import datetime
if obj.DEM and obj.CuttingBoundary: if obj.DEM and obj.CuttingBoundary:
''' from pathlib import Path
Parámetro Descripción Requisitos suffix = Path(obj.DEM).suffix
NCOLS: Cantidad de columnas de celdas Entero mayor que 0. if suffix == '.asc':
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. ASC format:
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. Parámetro Descripción Requisitos
NODATA_VALUE: Los valores de entrada que serán NoData en el ráster de salida Opcional. El valor predeterminado es -9999 NCOLS: Cantidad de columnas de celdas Entero mayor que 0.
''' NROWS: Cantidad de filas de celdas Entero mayor que 0.
grid_space = 1 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.
file = open(obj.DEM, "r") 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.
templist = [line.split() for line in file.readlines()] CELLSIZE: Tamaño de celda Mayor que 0.
file.close() NODATA_VALUE: Los valores de entrada que serán NoData en el ráster de salida Opcional. El valor predeterminado es -9999
del file '''
grid_space = 1
file = open(obj.DEM, "r")
templist = [line.split() for line in file.readlines()]
file.close()
del file
# Read meta data: # Read meta data:
meta = templist[0:6] meta = templist[0:6]
nx = int(meta[0][1]) # NCOLS nx = int(meta[0][1]) # NCOLS
ny = int(meta[1][1]) # NROWS ny = int(meta[1][1]) # NROWS
xllref = meta[2][0] # XLLCENTER / XLLCORNER xllref = meta[2][0] # XLLCENTER / XLLCORNER
xllvalue = round(float(meta[2][1]), 3) xllvalue = round(float(meta[2][1]), 3)
yllref = meta[3][0] # YLLCENTER / XLLCORNER yllref = meta[3][0] # YLLCENTER / XLLCORNER
yllvalue = round(float(meta[3][1]), 3) yllvalue = round(float(meta[3][1]), 3)
cellsize = round(float(meta[4][1]), 3) # CELLSIZE cellsize = round(float(meta[4][1]), 3) # CELLSIZE
nodata_value = float(meta[5][1]) # NODATA_VALUE nodata_value = float(meta[5][1]) # NODATA_VALUE
# set coarse_factor # set coarse_factor
coarse_factor = max(round(grid_space / cellsize), 1) coarse_factor = max(round(grid_space / cellsize), 1)
# Get z values # Get z values
templist = templist[6:(6 + ny)] templist = templist[6:(6 + ny)]
templist = [templist[i][0::coarse_factor] for i in np.arange(0, len(templist), coarse_factor)] templist = [templist[i][0::coarse_factor] for i in np.arange(0, len(templist), coarse_factor)]
datavals = np.array(templist).astype(float) datavals = np.array(templist).astype(float)
del templist del templist
# create xy coordinates # create xy coordinates
offset = self.site.Origin offset = self.site.Origin
x = (cellsize * np.arange(nx)[0::coarse_factor] + xllvalue) * 1000 - offset.x 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 y = (cellsize * np.arange(ny)[-1::-1][0::coarse_factor] + yllvalue) * 1000 - offset.y
datavals = datavals * 1000 # Ajuste de altura datavals = datavals * 1000 # Ajuste de altura
# remove points out of area # remove points out of area
# 1. coarse: # 1. coarse:
if obj.CuttingBoundary: if obj.CuttingBoundary:
inc_x = obj.CuttingBoundary.Shape.BoundBox.XLength * 0.0 inc_x = obj.CuttingBoundary.Shape.BoundBox.XLength * 0.0
inc_y = obj.CuttingBoundary.Shape.BoundBox.YLength * 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), tmp = np.where(np.logical_and(x >= (obj.CuttingBoundary.Shape.BoundBox.XMin - inc_x),
x <= (obj.CuttingBoundary.Shape.BoundBox.XMax + inc_x)))[0] x <= (obj.CuttingBoundary.Shape.BoundBox.XMax + inc_x)))[0]
x_max = np.ndarray.max(tmp) x_max = np.ndarray.max(tmp)
x_min = np.ndarray.min(tmp) x_min = np.ndarray.min(tmp)
tmp = np.where(np.logical_and(y >= (obj.CuttingBoundary.Shape.BoundBox.YMin - inc_y), tmp = np.where(np.logical_and(y >= (obj.CuttingBoundary.Shape.BoundBox.YMin - inc_y),
y <= (obj.CuttingBoundary.Shape.BoundBox.YMax + inc_y)))[0] y <= (obj.CuttingBoundary.Shape.BoundBox.YMax + inc_y)))[0]
y_max = np.ndarray.max(tmp) y_max = np.ndarray.max(tmp)
y_min = np.ndarray.min(tmp) y_min = np.ndarray.min(tmp)
del tmp del tmp
x = x[x_min:x_max+1] x = x[x_min:x_max+1]
y = y[y_min:y_max+1] y = y[y_min:y_max+1]
datavals = datavals[y_min:y_max+1, x_min:x_max+1] datavals = datavals[y_min:y_max+1, x_min:x_max+1]
# Create mesh - surface: # Create mesh - surface:
import MeshTools.Triangulation as Triangulation import MeshTools.Triangulation as Triangulation
import Mesh import Mesh
stepsize = 75 stepsize = 75
stepx = math.ceil(nx / stepsize) stepx = math.ceil(nx / stepsize)
stepy = math.ceil(ny / stepsize) stepy = math.ceil(ny / stepsize)
mesh = Mesh.Mesh() mesh = Mesh.Mesh()
for indx in range(stepx): for indx in range(stepx):
inix = indx * stepsize - 1 inix = indx * stepsize - 1
finx = min([stepsize * (indx + 1), len(x)-1]) finx = min([stepsize * (indx + 1), len(x)-1])
for indy in range(stepy): for indy in range(stepy):
iniy = indy * stepsize - 1 iniy = indy * stepsize - 1
finy = min([stepsize * (indy + 1), len(y) - 1]) finy = min([stepsize * (indy + 1), len(y) - 1])
pts = [] pts = []
for i in range(inix, finx): for i in range(inix, finx):
for j in range(iniy, finy): for j in range(iniy, finy):
if datavals[j][i] != nodata_value: if datavals[j][i] != nodata_value:
if obj.CuttingBoundary: if obj.CuttingBoundary:
if obj.CuttingBoundary.Shape.isInside(FreeCAD.Vector(x[i], y[j], 0), 0, True): 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]]) pts.append([x[i], y[j], datavals[j][i]])
else: if len(pts) > 3:
pts.append([x[i], y[j], datavals[j][i]]) try:
if len(pts) > 3: triangulated = Triangulation.Triangulate(pts)
try: mesh.addMesh(triangulated)
triangulated = Triangulation.Triangulate(pts) except TypeError:
mesh.addMesh(triangulated) print(f"Error al procesar {len(pts)} puntos: {str(e)}")
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 prop == "PointsGroup" or prop == "CuttingBoundary":
if obj.PointsGroup and obj.CuttingBoundary: if obj.PointsGroup and obj.CuttingBoundary:

View File

@@ -54,30 +54,6 @@ class CommandPVPlantSite:
return 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: class CommandProjectSetup:
@staticmethod @staticmethod
def GetResources(): def GetResources():
@@ -673,8 +649,7 @@ if FreeCAD.GuiUp:
FreeCADGui.addCommand('PVPlantTracker', PVPlantFrame.CommandTracker()) FreeCADGui.addCommand('PVPlantTracker', PVPlantFrame.CommandTracker())
FreeCADGui.addCommand('RackType', CommandRackGroup()) FreeCADGui.addCommand('RackType', CommandRackGroup())
from Civil.Fence import PVPlantFence
import PVPlantFence
FreeCADGui.addCommand('PVPlantFenceGroup', PVPlantFence.CommandFenceGroup()) FreeCADGui.addCommand('PVPlantFenceGroup', PVPlantFence.CommandFenceGroup())
projectlist = [ # "Reload", projectlist = [ # "Reload",
@@ -712,4 +687,4 @@ pv_mechanical = [
] ]
objectlist = ['PVPlantTree', objectlist = ['PVPlantTree',
'PVPlantFence',] 'PVPlantFenceGroup',]

View File

@@ -26,6 +26,9 @@ import PVPlantSite
import Utils.PVPlantUtils as utils import Utils.PVPlantUtils as utils
import MeshPart as mp import MeshPart as mp
import pivy
from pivy import coin
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
import FreeCADGui import FreeCADGui
from DraftTools import translate from DraftTools import translate
@@ -69,6 +72,7 @@ class _Area:
''' Initialize the Area object ''' ''' Initialize the Area object '''
self.Type = None self.Type = None
self.obj = None self.obj = None
self.setProperties(obj)
def setProperties(self, obj): def setProperties(self, obj):
pl = obj.PropertiesList pl = obj.PropertiesList
@@ -101,18 +105,18 @@ class _Area:
def __setstate__(self, state): def __setstate__(self, state):
pass pass
def execute(self, obj):
''' Execute the area object '''
pass
class _ViewProviderArea: class _ViewProviderArea:
def __init__(self, vobj): def __init__(self, vobj):
self.Object = vobj.Object
vobj.Proxy = self vobj.Proxy = self
def attach(self, vobj): def attach(self, vobj):
''' ''' Create Object visuals in 3D view. '''
Create Object visuals in 3D view. self.ViewObject = vobj
'''
self.Object = vobj.Object
return
def getIcon(self): def getIcon(self):
''' '''
@@ -120,6 +124,7 @@ class _ViewProviderArea:
''' '''
return str(os.path.join(DirIcons, "area.svg")) return str(os.path.join(DirIcons, "area.svg"))
''' '''
def claimChildren(self): def claimChildren(self):
""" """
@@ -159,17 +164,10 @@ class _ViewProviderArea:
pass pass
def __getstate__(self): def __getstate__(self):
"""
Save variables to file.
"""
return None return None
def __setstate__(self, state): def __setstate__(self, state):
""" pass
Get variables from file.
"""
return None
''' Frame Area ''' ''' Frame Area '''
@@ -311,17 +309,14 @@ class ViewProviderFrameArea(_ViewProviderArea):
''' offsets ''' ''' offsets '''
def makeOffsetArea(base = None, val=None): def makeOffsetArea(base = None, val=None):
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "OffsetArea") obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "OffsetArea")
OffsetArea(obj) OffsetArea(obj)
obj.Base = base obj.Base = base
ViewProviderOffsetArea(obj.ViewObject) ViewProviderOffsetArea(obj.ViewObject)
if val: if val:
obj.Distance = val obj.OffsetDistance = val
offsets = None
try: try:
offsetsgroup = FreeCAD.ActiveDocument.Offsets offsetsgroup = FreeCAD.ActiveDocument.Offsets
except: except:
@@ -334,11 +329,13 @@ def makeOffsetArea(base = None, val=None):
class OffsetArea(_Area): class OffsetArea(_Area):
def __init__(self, obj): def __init__(self, obj):
_Area.__init__(self, obj) '''_Area.__init__(self, obj)
self.setProperties(obj) self.setProperties(obj)'''
super().__init__(obj) # Llama al constructor de _Area
def setProperties(self, obj): def setProperties(self, obj):
_Area.setProperties(self, obj) super().setProperties(obj) # Propiedades de la clase base
pl = obj.PropertiesList pl = obj.PropertiesList
if not ("OffsetDistance" in pl): if not ("OffsetDistance" in pl):
obj.addProperty("App::PropertyDistance", obj.addProperty("App::PropertyDistance",
@@ -354,24 +351,28 @@ class OffsetArea(_Area):
self.setProperties(obj) self.setProperties(obj)
def execute(self, obj): def execute(self, obj):
import Utils.PVPlantUtils as utils # Comprobar dependencias críticas
if not hasattr(obj, "Base") or not obj.Base or not obj.Base.Shape:
return
if not hasattr(PVPlantSite, "get") or not PVPlantSite.get().Terrain:
return
base = obj.Base.Shape base = obj.Base.Shape
land = PVPlantSite.get().Terrain.Mesh land = PVPlantSite.get().Terrain.Mesh
vec = FreeCAD.Vector(0, 0, 1) vec = FreeCAD.Vector(0, 0, 1)
wire = utils.getProjected(base, vec) wire = utils.getProjected(base, vec)
wire = wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True) wire = wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True)
tmp = mp.projectShapeOnMesh(wire, land, vec) sections = mp.projectShapeOnMesh(wire, land, vec)
print(" javi ", sections)
pts = [] pts = []
for section in tmp: for section in sections:
pts.extend(section) pts.extend(section)
obj.Shape = Part.makePolygon(pts) # Crear forma solo si hay resultados
if len(pts)>0:
def __getstate__(self): obj.Shape = Part.makePolygon(pts)
return None else:
obj.Shape = Part.Shape() # Forma vacía si falla
def __setstate__(self, state):
pass
class ViewProviderOffsetArea(_ViewProviderArea): class ViewProviderOffsetArea(_ViewProviderArea):
@@ -382,14 +383,12 @@ class ViewProviderOffsetArea(_ViewProviderArea):
def claimChildren(self): def claimChildren(self):
""" Provides object grouping """ """ Provides object grouping """
children = [] children = []
if self.Object.Base: if self.ViewObject and self.ViewObject.Object.Base:
children.append(self.Object.Base) children.append(self.ViewObject.Object.Base)
return children return children
''' Forbidden Area: ''' ''' Forbidden Area: '''
def makeProhibitedArea(base = None): def makeProhibitedArea(base = None):
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "ExclusionArea") obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "ExclusionArea")
ProhibitedArea(obj) ProhibitedArea(obj)
@@ -420,29 +419,443 @@ class ProhibitedArea(OffsetArea):
"""Method run when the document is restored.""" """Method run when the document is restored."""
self.setProperties(obj) self.setProperties(obj)
def execute(self, obj):
# Comprobar dependencias
if not hasattr(obj, "Base") or not obj.Base or not obj.Base.Shape:
return
if not hasattr(PVPlantSite, "get") or not PVPlantSite.get().Terrain:
return
base = obj.Base.Shape
land = PVPlantSite.get().Terrain.Mesh
vec = FreeCAD.Vector(0, 0, 1)
# 1. Crear wire original
original_wire = utils.getProjected(base, vec)
sections_original = mp.projectShapeOnMesh(original_wire, land, vec)
# 2. Crear wire offset
offset_wire = original_wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True)
sections_offset = mp.projectShapeOnMesh(offset_wire, land, vec)
# Crear formas compuestas
def make_polygon(sections):
if not sections:
return Part.Shape()
pts = []
for section in sections:
pts.extend(section)
return Part.makePolygon(pts)
compounds = []
if sections_original:
compounds.append(make_polygon(sections_original))
if sections_offset:
compounds.append(make_polygon(sections_offset))
if compounds:
obj.Shape = Part.makeCompound(compounds)
else:
obj.Shape = Part.Shape()
# Actualizar colores en la vista
"""if FreeCAD.GuiUp and obj.ViewObject:
obj.ViewObject.Proxy.updateVisual()"""
class ViewProviderForbiddenArea_old:
def __init__(self, vobj):
vobj.Proxy = self
self.setProperties(vobj)
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
if not hasattr(vobj, "OffsetColor"):
vobj.addProperty("App::PropertyColor",
"OffsetColor",
"ObjectStyle",
"Color for offset wire")
vobj.OffsetColor = (1.0, 0.0, 0.0) # Rojo
# 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):
self.ViewObject = vobj
self.Object = vobj.Object
# Crear la estructura de escena Coin3D
self.root = coin.SoGroup()
# Switch para habilitar/deshabilitar la selección
self.switch = coin.SoSwitch()
self.switch.whichChild = coin.SO_SWITCH_ALL
# 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()
# 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()
# 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)
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)
self.switch.addChild(self.original_sep)
self.switch.addChild(self.offset_sep)
self.root.addChild(self.switch)
vobj.addDisplayMode(self.root, "Wireframe")
# Inicializar estilos de dibujo
self.original_draw_style.style = coin.SoDrawStyle.LINES
self.offset_draw_style.style = coin.SoDrawStyle.LINES
# Actualizar visualización inicial
if hasattr(self.Object, 'Shape'):
self.updateData(self.Object, "Shape")
self.updateVisual()
def updateData(self, obj, prop):
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 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): def __getstate__(self):
return None return None
def __setstate__(self, state): def __setstate__(self, state):
pass return None
class ViewProviderForbiddenArea(_ViewProviderArea): 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): def getIcon(self):
''' Return object treeview icon '''
return str(os.path.join(DirIcons, "area_forbidden.svg")) 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): def claimChildren(self):
""" Provides object grouping """
children = [] children = []
if self.Object.Base: if hasattr(self, 'ViewObject') and self.ViewObject and hasattr(self.ViewObject.Object, 'Base'):
children.append(self.Object.Base) children.append(self.ViewObject.Object.Base)
return children return children
def dumps(self):
return None
def loads(self, state):
return None
''' PV Area: ''' ''' PV Area: '''
def makePVSubplant(): def makePVSubplant():
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PVSubplant") obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PVSubplant")
PVSubplant(obj) PVSubplant(obj)

View File

@@ -39,6 +39,16 @@
<property name="spacing"> <property name="spacing">
<number>9</number> <number>9</number>
</property> </property>
<item row="3" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Maximum west-east slope:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QWidget" name="widget_2" native="true"/>
</item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
@@ -46,17 +56,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="0" column="2">
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>Frame coloring:</string> <string>South facing</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="5" column="0">
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
@@ -66,71 +79,20 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="3" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_4">
<property name="text"> <property name="text">
<string>Maximum west-east slope:</string> <string>Frame coloring:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="editWETL">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="suffix">
<string> º</string>
</property>
<property name="maximum">
<double>90.000000000000000</double>
</property>
<property name="value">
<double>8.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QDoubleSpinBox" name="editSFTL">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="suffix">
<string> º</string>
</property>
<property name="maximum">
<double>90.000000000000000</double>
</property>
<property name="value">
<double>2.800000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QWidget" name="widget_2" native="true"/>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_2">
<property name="text">
<string>South facing</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QDoubleSpinBox" name="editNFTL"> <widget class="QDoubleSpinBox" name="editNFTL">
<property name="alignment"> <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property> </property>
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum> <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
</property> </property>
<property name="suffix"> <property name="suffix">
<string> º</string> <string> º</string>
@@ -149,7 +111,45 @@
<string>North Facing</string> <string>North Facing</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QDoubleSpinBox" name="editSFTL">
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
</property>
<property name="suffix">
<string> º</string>
</property>
<property name="maximum">
<double>90.000000000000000</double>
</property>
<property name="value">
<double>2.800000000000000</double>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="editWETL">
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
</property>
<property name="suffix">
<string> º</string>
</property>
<property name="maximum">
<double>90.000000000000000</double>
</property>
<property name="value">
<double>8.000000000000000</double>
</property> </property>
</widget> </widget>
</item> </item>

View File

@@ -224,14 +224,20 @@ def getProjected(shape, direction=FreeCAD.Vector(0, 0, 1)): # Based on Draft / s
return ow return ow
def findObjects(classtype): '''def findObjects(classtype):
objects = FreeCAD.ActiveDocument.Objects objects = FreeCAD.ActiveDocument.Objects
objlist = list() objlist = list()
for object in objects: for object in objects:
if hasattr(object, "Proxy"): if hasattr(object, "Proxy"):
if object.Proxy.Type == classtype: if object.Proxy.Type == classtype:
objlist.append(object) 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): def getClosePoints(sh1, angle):
''' '''

View File

@@ -8,11 +8,12 @@
<license file="LICENSE">LGPL-2.1-or-later</license> <license file="LICENSE">LGPL-2.1-or-later</license>
<url type="repository" branch="main">https://homehud.duckdns.org/javier/PVPlant</url> <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="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> <icon>PVPlant/Resources/Icons/PVPlantWorkbench.svg</icon>
<content> <content>
<workbench> <workbench>
<classname>RoadWorkbench</classname> <classname>PVPlantWorkbench</classname>
<subdirectory>./</subdirectory> <subdirectory>./</subdirectory>
</workbench> </workbench>
</content> </content>

View File

@@ -24,11 +24,15 @@ class _CommandReload:
def Activated(self): def Activated(self):
import PVPlantPlacement, \ import PVPlantPlacement, \
PVPlantGeoreferencing, PVPlantImportGrid, PVPlantTerrainAnalisys, \ PVPlantGeoreferencing, PVPlantImportGrid, PVPlantTerrainAnalisys, \
PVPlantSite, PVPlantRackChecking, PVPlantFence, PVPlantFencePost, PVPlantFenceGate, \ PVPlantSite, PVPlantRackChecking, PVPlantCreateTerrainMesh, \
PVPlantCreateTerrainMesh, \
PVPlantFoundation, PVPlantBuilding, PVPlantEarthWorks, PVPlantPad, \ PVPlantFoundation, PVPlantBuilding, PVPlantEarthWorks, PVPlantPad, \
PVPlantRoad, PVPlantTerrain, PVPlantStringing, PVPlantManhole, \ PVPlantRoad, PVPlantTerrain, PVPlantStringing, PVPlantManhole, \
GraphProfile GraphProfile
from Civil.Fence import PVPlantFenceGate as PVPlantFenceGate
from Civil.Fence import PVPlantFence as PVPlantFence
from Civil.Fence import PVPlantFencePost as PVPlantFencePost
from Civil import PVPlantTrench from Civil import PVPlantTrench
from Vegetation import PVPlantTreeGenerator from Vegetation import PVPlantTreeGenerator
@@ -59,9 +63,11 @@ class _CommandReload:
importlib.reload(PVPlantSite) importlib.reload(PVPlantSite)
importlib.reload(PVPlantFrame) importlib.reload(PVPlantFrame)
importlib.reload(PVPlantRackChecking) importlib.reload(PVPlantRackChecking)
importlib.reload(PVPlantFence) importlib.reload(PVPlantFence)
importlib.reload(PVPlantFenceGate) importlib.reload(PVPlantFenceGate)
importlib.reload(PVPlantFencePost) importlib.reload(PVPlantFencePost)
importlib.reload(PVPlantFoundation) importlib.reload(PVPlantFoundation)
importlib.reload(PVPlantCreateTerrainMesh) importlib.reload(PVPlantCreateTerrainMesh)
importlib.reload(PVPlantTreeGenerator) importlib.reload(PVPlantTreeGenerator)

View File

@@ -9,16 +9,12 @@ setuptools~=68.2.2
laspy~=2.5.3 laspy~=2.5.3
geopy~=2.4.1 geopy~=2.4.1
lxml~=4.9.3 lxml~=4.9.3
pip~=23.3.2
wheel~=0.42.0
Brotli~=1.1.0
PySocks~=1.7.1
typing_extensions~=4.9.0
docutils~=0.20.1
Pillow~=10.1.0 Pillow~=10.1.0
pyproj~=3.7.1 pyproj~=3.7.1
simplekml~=1.3.6 simplekml~=1.3.6
geojson~=3.1.0 geojson~=3.1.0
certifi~=2023.11.17 certifi~=2023.11.17
SciPy~=1.11.4 SciPy~=1.11.4
ezdxf~=1.4.1 pycollada~=0.7.2
shapely
rtree