Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57f85d0153 | |||
| d9b39ac17b | |||
| 3bcdc95978 | |||
| 4b7035e6be | |||
| 02758a6ee8 | |||
| 111df89033 | |||
| 4476afc1a2 | |||
| d61260fdd3 | |||
| 049898c939 | |||
| 3a188cc47d | |||
| 5db8f5439d | |||
| e1e1441892 |
Generated
+4
@@ -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>
|
||||||
@@ -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,11 +149,7 @@ 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:
|
|
||||||
if normal.z < 0: # asegurarse de que siempre se dibuje por encima del suelo
|
|
||||||
normal.z *= -1
|
|
||||||
else:
|
|
||||||
normal = FreeCAD.Vector(0, 0, 1)
|
normal = FreeCAD.Vector(0, 0, 1)
|
||||||
path = Part.__sortEdges__(pathwire.Edges)
|
path = Part.__sortEdges__(pathwire.Edges)
|
||||||
ends = []
|
ends = []
|
||||||
@@ -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()
|
site = PVPlantSite.get()
|
||||||
if True: # prueba
|
segments = mp.projectShapeOnMesh(pathwire, site.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))
|
||||||
import MeshPart as mp
|
|
||||||
land = FreeCAD.ActiveDocument.Terrain.Mesh
|
|
||||||
segments = mp.projectShapeOnMesh(pathwire, land, FreeCAD.Vector(0, 0, 1))
|
|
||||||
points=[]
|
points=[]
|
||||||
for segment in segments:
|
for segment in segments:
|
||||||
points.extend(segment)
|
points.extend(segment)
|
||||||
pathwire = Part.makePolygon(points)
|
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:
|
|
||||||
land = site.Terrain.Shape
|
|
||||||
pathwire = land.makeParallelProjection(pathwire, FreeCAD.Vector(0, 0, 1))
|
|
||||||
|
|
||||||
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())
|
||||||
@@ -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")
|
||||||
@@ -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())
|
|
||||||
@@ -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())
|
||||||
|
|
||||||
@@ -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)
|
||||||
+137
-30
@@ -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,11 @@ 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")
|
||||||
|
if hasattr(road, 'Width'):
|
||||||
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 +1079,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()
|
||||||
|
|||||||
+322
-26
@@ -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):
|
||||||
@@ -252,6 +266,305 @@ def export(exportList, filename, tessellation=1, colors=None):
|
|||||||
def exportToPVC(path, exportTerrain=False):
|
def exportToPVC(path, exportTerrain=False):
|
||||||
filename = f"{path}.pvc"
|
filename = f"{path}.pvc"
|
||||||
|
|
||||||
|
# 1. Validación inicial de objetos esenciales
|
||||||
|
site = None
|
||||||
|
for obj in FreeCAD.ActiveDocument.Objects:
|
||||||
|
if obj.Name.startswith("Site") and hasattr(obj, 'Terrain'):
|
||||||
|
site = obj
|
||||||
|
break
|
||||||
|
|
||||||
|
if not site:
|
||||||
|
FreeCAD.Console.PrintError("No se encontró objeto 'Site' válido\n")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 2. Configuración de metadatos y autor
|
||||||
|
generated_on = str(datetime.datetime.now())
|
||||||
|
|
||||||
|
try:
|
||||||
|
author = FreeCAD.ActiveDocument.CreatedBy
|
||||||
|
except (AttributeError, UnicodeEncodeError):
|
||||||
|
author = "Unknown"
|
||||||
|
|
||||||
|
author = author.replace("<", "").replace(">", "")
|
||||||
|
ver = FreeCAD.Version()
|
||||||
|
appli = f"PVPlant for FreeCAD {ver[0]}.{ver[1]} build{ver[2]}"
|
||||||
|
|
||||||
|
# 3. Creación estructura XML base
|
||||||
|
root = Element('COLLADA')
|
||||||
|
root.set('xmlns', 'http://www.collada.org/2005/11/COLLADASchema')
|
||||||
|
root.set('version', '1.4.1')
|
||||||
|
|
||||||
|
# 4. Sección <asset>
|
||||||
|
asset = SubElement(root, 'asset')
|
||||||
|
contrib = SubElement(asset, 'contributor')
|
||||||
|
SubElement(contrib, 'author').text = safe_text(author)
|
||||||
|
SubElement(contrib, 'authoring_tool').text = safe_text(appli)
|
||||||
|
SubElement(asset, 'created').text = generated_on
|
||||||
|
SubElement(asset, 'modified').text = generated_on
|
||||||
|
SubElement(asset, 'title').text = safe_text(FreeCAD.ActiveDocument.Name)
|
||||||
|
unit = SubElement(asset, 'unit')
|
||||||
|
unit.set('name', 'meter')
|
||||||
|
unit.set('meter', '1')
|
||||||
|
|
||||||
|
# 5. Materiales y efectos
|
||||||
|
materials = ["Frames", "Tree_trunk", "Tree_crown", "Topography_mesh"]
|
||||||
|
|
||||||
|
# Library materials
|
||||||
|
lib_materials = SubElement(root, 'library_materials')
|
||||||
|
for i, name in enumerate(materials):
|
||||||
|
mat = SubElement(lib_materials, 'material')
|
||||||
|
mat.set('id', f'Material{i}')
|
||||||
|
mat.set('name', name)
|
||||||
|
SubElement(mat, 'instance_effect').set('url', f'#Material{i}-fx')
|
||||||
|
|
||||||
|
# Library effects
|
||||||
|
lib_effects = SubElement(root, 'library_effects')
|
||||||
|
for i, _ in enumerate(materials):
|
||||||
|
effect = SubElement(lib_effects, 'effect')
|
||||||
|
effect.set('id', f'Material{i}-fx')
|
||||||
|
effect.set('name', f'Material{i}')
|
||||||
|
profile = SubElement(effect, 'profile_COMMON')
|
||||||
|
technique = SubElement(profile, 'technique')
|
||||||
|
technique.set('sid', 'standard')
|
||||||
|
lambert = SubElement(technique, 'lambert')
|
||||||
|
|
||||||
|
# Componentes del material
|
||||||
|
color = SubElement(SubElement(lambert, 'emission'), 'color')
|
||||||
|
color.set('sid', 'emission')
|
||||||
|
color.text = '0.000000 0.000000 0.000000 1.000000'
|
||||||
|
color = SubElement(SubElement(lambert, 'ambient'), 'color')
|
||||||
|
color.set('sid', 'ambient')
|
||||||
|
color.text = '0.200000 0.200000 0.200000 1.000000'
|
||||||
|
color = SubElement(SubElement(lambert, 'diffuse'), 'color')
|
||||||
|
color.set('sid', 'diffuse')
|
||||||
|
color.text = '0.250000 0.500000 0.000000 1.000000'
|
||||||
|
transparent = SubElement(lambert, 'transparent')
|
||||||
|
transparent.set('opaque', 'RGB_ZERO')
|
||||||
|
color = SubElement(transparent, 'color')
|
||||||
|
color.set('sid', 'transparent')
|
||||||
|
color.text = '0.000000 0.000000 0.000000 1.000000'
|
||||||
|
value = SubElement(SubElement(lambert, 'transparency'), 'float')
|
||||||
|
value.set('sid', 'transparency')
|
||||||
|
value.text = '0.000000'
|
||||||
|
|
||||||
|
# 6. Geometrías
|
||||||
|
lib_geometries = SubElement(root, 'library_geometries')
|
||||||
|
|
||||||
|
# 7. Escena visual
|
||||||
|
lib_visual = SubElement(root, 'library_visual_scenes')
|
||||||
|
visual_scene = SubElement(lib_visual, 'visual_scene')
|
||||||
|
visual_scene.set('id', 'Scene') # cambiar a visual_scene_0
|
||||||
|
visual_scene.set('name', 'Scene') # cambiar a Default visual scene
|
||||||
|
|
||||||
|
scene_node = SubElement(visual_scene, 'node')
|
||||||
|
scene_node.set('id', 'node_0_id')
|
||||||
|
scene_node.set('name', 'node_0_name')
|
||||||
|
scene_node.set('sid', 'node_0_sid')
|
||||||
|
|
||||||
|
scene_matrix = SubElement(scene_node, 'matrix')
|
||||||
|
scene_matrix.set('sid', 'matrix_0')
|
||||||
|
scene_matrix.text = '1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000'
|
||||||
|
|
||||||
|
root_node = SubElement(scene_node, 'node')
|
||||||
|
root_node.set('id', 'node_1_id')
|
||||||
|
root_node.set('name', 'node_1_name')
|
||||||
|
root_node.set('sid', 'node_1_sid')
|
||||||
|
|
||||||
|
# 8. Función para procesar geometrías
|
||||||
|
def create_geometry(name, vindex, findex, material_id, objind=0, frame_data=None, isTracker = False, axis_line=None):
|
||||||
|
"""Crea elementos COLLADA para una geometría"""
|
||||||
|
# Source (vertices)
|
||||||
|
source_mesh = SubElement(geom, 'mesh')
|
||||||
|
source = SubElement(source_mesh, 'source')
|
||||||
|
source.set('id', f'{name}-mesh_source')
|
||||||
|
float_array = SubElement(source, 'float_array')
|
||||||
|
float_array.set('id', f'{name}-float_array')
|
||||||
|
float_array.set('count', str(len(vindex)))
|
||||||
|
float_array.text = ' '.join(f'{v:.6f}' for v in vindex)
|
||||||
|
|
||||||
|
technique = SubElement(source, 'technique_common')
|
||||||
|
accessor = SubElement(technique, 'accessor')
|
||||||
|
accessor.set('count', str(len(vindex)))
|
||||||
|
accessor.set('source', f'#{name}-float_array')
|
||||||
|
accessor.set('stride', '3')
|
||||||
|
for ax in ['X', 'Y', 'Z']:
|
||||||
|
param = SubElement(accessor, 'param')
|
||||||
|
param.set('name', ax)
|
||||||
|
param.set('type', 'float')
|
||||||
|
|
||||||
|
# Vertices
|
||||||
|
vertices = SubElement(source_mesh, 'vertices')
|
||||||
|
vertices.set('id', f'{name}-vertices_source')
|
||||||
|
vertices = SubElement(vertices, 'input')
|
||||||
|
vertices.set('semantic', 'POSITION')
|
||||||
|
vertices.set('source', f'#{name}-mesh_source')
|
||||||
|
|
||||||
|
# Triangles
|
||||||
|
triangles = SubElement(source_mesh, 'triangles')
|
||||||
|
triangles.set('count', '0')
|
||||||
|
triangles.set('material', f'Material{material_id}')
|
||||||
|
triangles_input = SubElement(triangles, 'input')
|
||||||
|
triangles_input.set('offset', '0')
|
||||||
|
triangles_input.set('semantic', 'VERTEX')
|
||||||
|
triangles_input.set('source', f'#{name}-vertices_source')
|
||||||
|
|
||||||
|
p = SubElement(triangles, 'p')
|
||||||
|
p.text = ' '.join(map(str, findex))
|
||||||
|
|
||||||
|
# Parámetros especiales para estructuras
|
||||||
|
|
||||||
|
frame_params = SubElement(source_mesh, 'tracker_parameters')
|
||||||
|
if frame_data:
|
||||||
|
for key, val in frame_data.items():
|
||||||
|
elem = SubElement(frame_params, key)
|
||||||
|
elem.text = str(val)
|
||||||
|
|
||||||
|
if isTracker:
|
||||||
|
axis_parameter = SubElement(frame_params, 'axis_vertices')
|
||||||
|
if axis_line:
|
||||||
|
for idx, vert in enumerate(axis_line):
|
||||||
|
array = SubElement(axis_parameter, 'float_array')
|
||||||
|
array.set('id', f'{name}-axis_float_array{idx}')
|
||||||
|
array.set('count', '3')
|
||||||
|
array.text = ' '.join(f'{v:.6f}' for v in vert)
|
||||||
|
|
||||||
|
# 9. Procesar estructuras (frames/trackers)
|
||||||
|
center = FreeCAD.Vector()
|
||||||
|
if site.Terrain:
|
||||||
|
center = site.Terrain.Mesh.BoundBox.Center
|
||||||
|
|
||||||
|
objind = 0
|
||||||
|
for frame_type in site.Frames:
|
||||||
|
is_tracker = "tracker" in frame_type.Proxy.Type.lower()
|
||||||
|
|
||||||
|
modules = frame_type.Shape.SubShapes[0].SubShapes[0]
|
||||||
|
pts = []
|
||||||
|
for i in range(4):
|
||||||
|
pts.append(modules.BoundBox.getPoint(i))
|
||||||
|
|
||||||
|
new_shape = Part.Face(Part.makePolygon(pts))
|
||||||
|
mesh = Mesh.Mesh(triangulate(new_shape))
|
||||||
|
axis = Part.makeLine(FreeCAD.Vector(modules.BoundBox.XMin, 0, modules.BoundBox.ZMax),
|
||||||
|
FreeCAD.Vector(modules.BoundBox.XMax, 0, modules.BoundBox.ZMax))
|
||||||
|
|
||||||
|
for obj in FreeCAD.ActiveDocument.Objects:
|
||||||
|
if hasattr(obj, "Setup") and obj.Setup == frame_type:
|
||||||
|
# Procesar geometría
|
||||||
|
mesh.Placement = obj.getGlobalPlacement()
|
||||||
|
axis.Placement = obj.getGlobalPlacement()
|
||||||
|
|
||||||
|
# Transformar vértices
|
||||||
|
vindex = []
|
||||||
|
for point in mesh.Points:
|
||||||
|
adjusted = (point.Vector - center) * scale
|
||||||
|
vindex.extend([
|
||||||
|
-adjusted.x,
|
||||||
|
adjusted.z,
|
||||||
|
adjusted.y
|
||||||
|
])
|
||||||
|
|
||||||
|
# Índices de caras
|
||||||
|
findex = []
|
||||||
|
for facet in mesh.Facets:
|
||||||
|
findex.extend(facet.PointIndices)
|
||||||
|
|
||||||
|
# AXIS
|
||||||
|
# TODO: revisar si es así:
|
||||||
|
vaxis = []
|
||||||
|
for vert in axis.Vertexes:
|
||||||
|
adjusted = (vert.Point - center) * scale
|
||||||
|
vaxis.append([
|
||||||
|
-adjusted.x,
|
||||||
|
adjusted.z,
|
||||||
|
adjusted.y
|
||||||
|
])
|
||||||
|
|
||||||
|
# Crear geometría COLLADA
|
||||||
|
geom = SubElement(lib_geometries, 'geometry')
|
||||||
|
geom.set('id', f'Frame_{objind}')
|
||||||
|
|
||||||
|
# Parámetros específicos de estructura
|
||||||
|
frame_data = {
|
||||||
|
'module_width': obj.Setup.ModuleWidth.Value,
|
||||||
|
'module_height': obj.Setup.ModuleHeight.Value,
|
||||||
|
'module_x_spacing': obj.Setup.ModuleColGap.Value,
|
||||||
|
'module_y_spacing': obj.Setup.ModuleRowGap.Value,
|
||||||
|
'module_name': 'Generic'
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_tracker:
|
||||||
|
frame_data.update({
|
||||||
|
'tracker_type': 'single_axis_trackers',
|
||||||
|
'min_phi': obj.Setup.MinPhi.Value,
|
||||||
|
'max_phi': obj.Setup.MaxPhi.Value,
|
||||||
|
'min_theta': 0,
|
||||||
|
'max_theta': 0
|
||||||
|
})
|
||||||
|
|
||||||
|
create_geometry(
|
||||||
|
name=f'Frame_{objind}',
|
||||||
|
vindex=vindex,
|
||||||
|
findex=findex,
|
||||||
|
material_id=0,
|
||||||
|
objind=objind,
|
||||||
|
frame_data=frame_data,
|
||||||
|
isTracker = is_tracker,
|
||||||
|
axis_line=vaxis
|
||||||
|
)
|
||||||
|
|
||||||
|
# Instancia en escena
|
||||||
|
instance = SubElement(root_node, 'instance_geometry')
|
||||||
|
instance.set('url', f'#Frame_{objind}')
|
||||||
|
|
||||||
|
bind_material = SubElement(instance, 'bind_material')
|
||||||
|
technique_common = SubElement(bind_material, 'technique_common')
|
||||||
|
instance_material = SubElement(technique_common, 'instance_material')
|
||||||
|
instance_material.set('symbol', 'Material0')
|
||||||
|
instance_material.set('target', '#Material0')
|
||||||
|
|
||||||
|
objind += 1
|
||||||
|
|
||||||
|
# 10. Procesar terreno si está habilitado
|
||||||
|
if exportTerrain and site.Terrain:
|
||||||
|
mesh = site.Terrain.Mesh
|
||||||
|
vindex = []
|
||||||
|
for point in mesh.Points:
|
||||||
|
point = point.Vector
|
||||||
|
vindex.extend([
|
||||||
|
-point.x * SCALE,
|
||||||
|
point.z * SCALE,
|
||||||
|
point.y * SCALE
|
||||||
|
])
|
||||||
|
|
||||||
|
findex = []
|
||||||
|
for facet in mesh.Facets:
|
||||||
|
findex.extend(facet.PointIndices)
|
||||||
|
|
||||||
|
geom = SubElement(lib_geometries, 'geometry')
|
||||||
|
geom.set('id', 'Terrain')
|
||||||
|
create_geometry('Terrain', vindex, findex, material_id=3)
|
||||||
|
|
||||||
|
instance = SubElement(root_node, 'instance_geometry')
|
||||||
|
instance.set('url', '#Terrain')
|
||||||
|
|
||||||
|
# 11. Escena principal
|
||||||
|
scene = SubElement(root, 'scene')
|
||||||
|
SubElement(scene, 'instance_visual_scene').set('url', '#Scene')
|
||||||
|
|
||||||
|
# 12. Exportar a archivo
|
||||||
|
xml_str = minidom.parseString(
|
||||||
|
ElementTree.tostring(root, encoding='utf-8')
|
||||||
|
).toprettyxml(indent=" ")
|
||||||
|
|
||||||
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(xml_str)
|
||||||
|
|
||||||
|
FreeCAD.Console.PrintMessage(f"Archivo PVC generado: {filename}\n")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def exportToPVC_old(path, exportTerrain = False):
|
||||||
|
filename = f"{path}.pvc"
|
||||||
|
|
||||||
from xml.etree.ElementTree import Element, SubElement
|
from xml.etree.ElementTree import Element, SubElement
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
@@ -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])):
|
||||||
|
|||||||
+139
-33
@@ -7,7 +7,6 @@ import ssl
|
|||||||
import certifi
|
import certifi
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import math
|
import math
|
||||||
import utm
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import PVPlantImportGrid as ImportElevation
|
import PVPlantImportGrid as ImportElevation
|
||||||
|
|
||||||
@@ -42,15 +41,19 @@ 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], ])
|
"""Transforma coordenadas lat/lon a coordenadas FreeCAD"""
|
||||||
return FreeCAD.Vector(point[0].x, point[0].y, point[0].z) * scale - self.Origin
|
if not coordinates:
|
||||||
'''x, y, _, _ = utm.from_latlon(lat, lon)
|
return []
|
||||||
return FreeCAD.Vector(x, y, .0) * scale - self.Origin'''
|
|
||||||
|
points = ImportElevation.getElevationFromOE(coordinates)
|
||||||
|
pts = [FreeCAD.Vector(p.x, p.y, p.z).sub(self.Origin) for p in points]
|
||||||
|
return pts
|
||||||
|
|
||||||
def get_osm_data(self, bbox):
|
def get_osm_data(self, bbox):
|
||||||
query = f"""
|
""" Obtiene datos de OpenStreetMap """
|
||||||
[out:xml][bbox:{bbox}];
|
# Modificar la consulta en get_osm_data para incluir más tipos de agua:
|
||||||
|
query = f"""[out:xml][bbox:{bbox}];
|
||||||
(
|
(
|
||||||
way["building"];
|
way["building"];
|
||||||
way["highway"];
|
way["highway"];
|
||||||
@@ -58,34 +61,55 @@ class OSMImporter:
|
|||||||
way["power"="line"];
|
way["power"="line"];
|
||||||
way["power"="substation"];
|
way["power"="substation"];
|
||||||
way["natural"="water"];
|
way["natural"="water"];
|
||||||
way["landuse"="forest"];
|
way["waterway"];
|
||||||
|
way["waterway"="river"];
|
||||||
|
way["waterway"="stream"];
|
||||||
|
way["waterway"="canal"];
|
||||||
|
way["landuse"="basin"];
|
||||||
|
way["landuse"="reservoir"];
|
||||||
node["natural"="tree"];
|
node["natural"="tree"];
|
||||||
|
way["landuse"="forest"];
|
||||||
|
way["landuse"="farmland"];
|
||||||
);
|
);
|
||||||
(._;>;);
|
(._;>;);
|
||||||
out body;
|
out body;
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
req = urllib.request.Request(
|
req = urllib.request.Request(
|
||||||
self.overpass_url,
|
self.overpass_url,
|
||||||
data=query.encode('utf-8'),
|
data=query.encode('utf-8'),
|
||||||
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()
|
|
||||||
|
response = urllib.request.urlopen(req, context=self.ssl_context, timeout=160)
|
||||||
|
return response.read()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error obteniendo datos OSM: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
def create_layer(self, name):
|
def create_layer(self, name):
|
||||||
|
"""Crea o obtiene una capa en el documento"""
|
||||||
if not FreeCAD.ActiveDocument.getObject(name):
|
if not FreeCAD.ActiveDocument.getObject(name):
|
||||||
return FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", name)
|
return FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", name)
|
||||||
return FreeCAD.ActiveDocument.getObject(name)
|
return FreeCAD.ActiveDocument.getObject(name)
|
||||||
|
|
||||||
def process_osm_data(self, osm_data):
|
def process_osm_data(self, osm_data):
|
||||||
|
"""Procesa los datos XML de OSM"""
|
||||||
|
if not osm_data:
|
||||||
|
print("No hay datos OSM para procesar")
|
||||||
|
return
|
||||||
|
|
||||||
root = ET.fromstring(osm_data)
|
root = ET.fromstring(osm_data)
|
||||||
|
|
||||||
|
# Primero, recolectar todos los nodos
|
||||||
|
print(f"Procesando {len(root.findall('node'))} nodos...")
|
||||||
|
|
||||||
# Almacenar nodos transformados
|
# Almacenar nodos transformados
|
||||||
for node in root.findall('node'):
|
coordinates = [[float(node.attrib['lat']), float(node.attrib['lon'])] for node in root.findall('node')]
|
||||||
self.nodes[node.attrib['id']] = self.transform_from_latlon(
|
coordinates = self.transform_from_latlon(coordinates)
|
||||||
float(node.attrib['lat']),
|
for i, node in enumerate(root.findall('node')):
|
||||||
float(node.attrib['lon'])
|
self.nodes[node.attrib['id']] = coordinates[i]
|
||||||
)
|
|
||||||
|
|
||||||
# Procesar ways
|
# Procesar ways
|
||||||
for way in root.findall('way'):
|
for way in root.findall('way'):
|
||||||
@@ -162,11 +186,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]
|
||||||
|
|
||||||
@@ -223,11 +246,11 @@ class OSMImporter:
|
|||||||
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]
|
||||||
|
|
||||||
if 'power' in tags:
|
if 'power' in tags:
|
||||||
print("\n\n")
|
#print("\n\n")
|
||||||
print(tags)
|
#print(tags)
|
||||||
feature_type = tags['power']
|
feature_type = tags['power']
|
||||||
if feature_type == 'line':
|
if feature_type == 'line':
|
||||||
print("3.1. Create Power Lines")
|
#print("3.1. Create Power Lines")
|
||||||
FreeCADGui.updateGui()
|
FreeCADGui.updateGui()
|
||||||
self.create_power_line(
|
self.create_power_line(
|
||||||
nodes=nodes,
|
nodes=nodes,
|
||||||
@@ -236,7 +259,7 @@ class OSMImporter:
|
|||||||
)
|
)
|
||||||
|
|
||||||
elif feature_type == 'substation':
|
elif feature_type == 'substation':
|
||||||
print("3.1. Create substations")
|
#print("3.1. Create substations")
|
||||||
FreeCADGui.updateGui()
|
FreeCADGui.updateGui()
|
||||||
self.create_substation(
|
self.create_substation(
|
||||||
way_id=way_id,
|
way_id=way_id,
|
||||||
@@ -246,7 +269,7 @@ class OSMImporter:
|
|||||||
)
|
)
|
||||||
|
|
||||||
elif feature_type == 'tower':
|
elif feature_type == 'tower':
|
||||||
print("3.1. Create power towers")
|
#print("3.1. Create power towers")
|
||||||
FreeCADGui.updateGui()
|
FreeCADGui.updateGui()
|
||||||
self.create_power_tower(
|
self.create_power_tower(
|
||||||
position=nodes[0] if nodes else None,
|
position=nodes[0] if nodes else None,
|
||||||
@@ -559,13 +582,15 @@ class OSMImporter:
|
|||||||
if polygon_points[0] != polygon_points[-1]:
|
if polygon_points[0] != polygon_points[-1]:
|
||||||
polygon_points.append(polygon_points[0])
|
polygon_points.append(polygon_points[0])
|
||||||
|
|
||||||
|
|
||||||
# 3. Base del terreno
|
# 3. Base del terreno
|
||||||
base_height = 0.3
|
base_height = 0.3
|
||||||
try:
|
try:
|
||||||
base_shape = Part.makePolygon(polygon_points)
|
base_shape = Part.makePolygon(polygon_points)
|
||||||
base_face = Part.Face(base_shape)
|
base_face = Part.Face(base_shape)
|
||||||
base_extrude = base_face.extrude(FreeCAD.Vector(0, 0, base_height))
|
base_extrude = base_face.extrude(FreeCAD.Vector(0, 0, base_height))
|
||||||
base_obj = layer.addObject("Part::Feature", f"{name}_Base")
|
base_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", f"{name}_Base")
|
||||||
|
layer.addObject(base_obj)
|
||||||
base_obj.Shape = base_extrude
|
base_obj.Shape = base_extrude
|
||||||
base_obj.ViewObject.ShapeColor = (0.2, 0.2, 0.2)
|
base_obj.ViewObject.ShapeColor = (0.2, 0.2, 0.2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -580,7 +605,8 @@ class OSMImporter:
|
|||||||
fence_shape = Part.makePolygon(fence_points)
|
fence_shape = Part.makePolygon(fence_points)
|
||||||
fence_face = Part.Face(fence_shape)
|
fence_face = Part.Face(fence_shape)
|
||||||
fence_extrude = fence_face.extrude(FreeCAD.Vector(0, 0, 2.8))
|
fence_extrude = fence_face.extrude(FreeCAD.Vector(0, 0, 2.8))
|
||||||
fence_obj = layer.addObject("Part::Feature", f"{name}_Fence")
|
fence_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", f"{name}_Fence")
|
||||||
|
layer.addObject(fence_obj)
|
||||||
fence_obj.Shape = fence_extrude
|
fence_obj.Shape = fence_extrude
|
||||||
fence_obj.ViewObject.ShapeColor = (0.4, 0.4, 0.4)
|
fence_obj.ViewObject.ShapeColor = (0.4, 0.4, 0.4)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -596,14 +622,15 @@ class OSMImporter:
|
|||||||
building_shape = Part.makePolygon(building_points)
|
building_shape = Part.makePolygon(building_points)
|
||||||
building_face = Part.Face(building_shape)
|
building_face = Part.Face(building_shape)
|
||||||
building_extrude = building_face.extrude(FreeCAD.Vector(0, 0, building_height))
|
building_extrude = building_face.extrude(FreeCAD.Vector(0, 0, building_height))
|
||||||
building_obj = layer.addObject("Part::Feature", f"{name}_Building")
|
building_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", f"{name}_Building")
|
||||||
|
layer.addObject(building_obj)
|
||||||
building_obj.Shape = building_extrude
|
building_obj.Shape = building_extrude
|
||||||
building_obj.ViewObject.ShapeColor = (0.7, 0.7, 0.7)
|
building_obj.ViewObject.ShapeColor = (0.7, 0.7, 0.7)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(f"Error edificio {way_id}: {str(e)}\n")
|
FreeCAD.Console.PrintWarning(f"Error edificio {way_id}: {str(e)}\n")
|
||||||
|
|
||||||
# 6. Transformadores
|
# 6. Transformadores
|
||||||
try:
|
'''try:
|
||||||
num_transformers = int(tags.get('transformers', 1))
|
num_transformers = int(tags.get('transformers', 1))
|
||||||
for i in range(num_transformers):
|
for i in range(num_transformers):
|
||||||
transformer_pos = self.calculate_equipment_position(
|
transformer_pos = self.calculate_equipment_position(
|
||||||
@@ -615,11 +642,11 @@ class OSMImporter:
|
|||||||
transformer = self.create_transformer(
|
transformer = self.create_transformer(
|
||||||
position=transformer_pos,
|
position=transformer_pos,
|
||||||
voltage=voltage,
|
voltage=voltage,
|
||||||
tech_type=tags.get('substation:type', 'outdoor')
|
technology=tags.get('substation:type', 'outdoor')
|
||||||
)
|
)
|
||||||
layer.addObject(transformer)
|
layer.addObject(transformer)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintWarning(f"Error transformadores {way_id}: {str(e)}\n")
|
FreeCAD.Console.PrintWarning(f"Error transformadores {way_id}: {str(e)}\n")'''
|
||||||
|
|
||||||
# 7. Torre de seccionamiento para alta tensión
|
# 7. Torre de seccionamiento para alta tensión
|
||||||
if substation_type == 'transmission' and voltage >= 110000:
|
if substation_type == 'transmission' and voltage >= 110000:
|
||||||
@@ -634,7 +661,8 @@ class OSMImporter:
|
|||||||
FreeCAD.Console.PrintWarning(f"Error torre {way_id}: {str(e)}\n")
|
FreeCAD.Console.PrintWarning(f"Error torre {way_id}: {str(e)}\n")
|
||||||
|
|
||||||
# 8. Propiedades técnicas
|
# 8. Propiedades técnicas
|
||||||
substation_data = layer.addObject("App::FeaturePython", f"{name}_Data")
|
substation_data = FreeCAD.ActiveDocument.addObject("App::FeaturePython", f"{name}_Data")
|
||||||
|
layer.addObject(substation_data)
|
||||||
props = {
|
props = {
|
||||||
"Voltage": voltage,
|
"Voltage": voltage,
|
||||||
"Type": substation_type,
|
"Type": substation_type,
|
||||||
@@ -648,7 +676,8 @@ class OSMImporter:
|
|||||||
else:
|
else:
|
||||||
substation_data.addProperty(
|
substation_data.addProperty(
|
||||||
"App::PropertyFloat" if isinstance(value, float) else "App::PropertyString",
|
"App::PropertyFloat" if isinstance(value, float) else "App::PropertyString",
|
||||||
prop, "Technical").setValue(value)
|
prop, "Technical")
|
||||||
|
setattr(substation_data, prop, value)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
FreeCAD.Console.PrintError(f"Error crítico en subestación {way_id}: {str(e)}\n")
|
FreeCAD.Console.PrintError(f"Error crítico en subestación {way_id}: {str(e)}\n")
|
||||||
@@ -897,9 +926,9 @@ class OSMImporter:
|
|||||||
if face.isInside(rand_point, 0.1, True):
|
if face.isInside(rand_point, 0.1, True):
|
||||||
return rand_point
|
return rand_point
|
||||||
|
|
||||||
def create_water_bodies(self):
|
def create_water_bodies_old(self):
|
||||||
water_layer = self.create_layer("Water")
|
water_layer = self.create_layer("Water")
|
||||||
|
print(self.ways_data)
|
||||||
for way_id, data in self.ways_data.items():
|
for way_id, data in self.ways_data.items():
|
||||||
if 'natural' in data['tags'] and data['tags']['natural'] == 'water':
|
if 'natural' in data['tags'] and data['tags']['natural'] == 'water':
|
||||||
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]
|
||||||
@@ -912,3 +941,80 @@ class OSMImporter:
|
|||||||
water.Shape = face.extrude(FreeCAD.Vector(0, 0, 0.1))# * scale - self.Origin )
|
water.Shape = face.extrude(FreeCAD.Vector(0, 0, 0.1))# * scale - self.Origin )
|
||||||
water.ViewObject.ShapeColor = self.feature_colors['water']
|
water.ViewObject.ShapeColor = self.feature_colors['water']
|
||||||
|
|
||||||
|
def create_water_bodies(self):
|
||||||
|
|
||||||
|
water_layer = self.create_layer("Water")
|
||||||
|
|
||||||
|
for way_id, data in self.ways_data.items():
|
||||||
|
tags = data['tags']
|
||||||
|
nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes]
|
||||||
|
|
||||||
|
if len(nodes) < 2:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ===== 1) RÍOS / CANALES (líneas) =====
|
||||||
|
name = self.get_osm_name(tags, tags["waterway"])
|
||||||
|
if 'waterway' in tags:
|
||||||
|
if len(nodes) < 2:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
width = self.parse_width(tags, default=2.0)
|
||||||
|
points = [FreeCAD.Vector(n.x, n.y, n.z) for n in nodes]
|
||||||
|
wire = Draft.make_wire(points, closed=False, face=False)
|
||||||
|
wire.Label = f"{name} ({tags['waterway']})"
|
||||||
|
|
||||||
|
wire.ViewObject.LineWidth = max(1, int(width * 0.5))
|
||||||
|
wire.ViewObject.ShapeColor = self.feature_colors['water']
|
||||||
|
|
||||||
|
water_layer.addObject(wire)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creando waterway {way_id}: {e}")
|
||||||
|
|
||||||
|
continue # importante
|
||||||
|
|
||||||
|
# ===== 2) LAGOS / EMBALSES (polígonos) =====
|
||||||
|
is_area_water = (
|
||||||
|
tags.get('natural') == 'water' or
|
||||||
|
tags.get('landuse') in ['reservoir', 'basin'] or
|
||||||
|
tags.get('water') is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
if not is_area_water or len(nodes) < 3:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
polygon_points = [FreeCAD.Vector(n.x, n.y, n.z) for n in nodes]
|
||||||
|
|
||||||
|
if polygon_points[0] != polygon_points[-1]:
|
||||||
|
polygon_points.append(polygon_points[0])
|
||||||
|
|
||||||
|
polygon = Part.makePolygon(polygon_points)
|
||||||
|
face = Part.Face(polygon)
|
||||||
|
|
||||||
|
water = FreeCAD.ActiveDocument.addObject("Part::Feature", f"Water_{way_id}")
|
||||||
|
water.Shape = face
|
||||||
|
water.ViewObject.ShapeColor = self.feature_colors['water']
|
||||||
|
water.Label = f"{name} ({tags['waterway']})"
|
||||||
|
|
||||||
|
water_layer.addObject(water)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creando área de agua {way_id}: {e}")
|
||||||
|
|
||||||
|
def get_osm_name(self, tags, fallback=""):
|
||||||
|
for key in ["name", "name:es", "name:en", "alt_name", "ref"]:
|
||||||
|
if key in tags and tags[key].strip():
|
||||||
|
return tags[key]
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
def parse_width(self, tags, default=2.0):
|
||||||
|
for key in ["width", "est_width"]:
|
||||||
|
if key in tags:
|
||||||
|
try:
|
||||||
|
w = tags[key].replace("m", "").strip()
|
||||||
|
return float(w)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return default
|
||||||
|
|||||||
+14
-4
@@ -39,9 +39,11 @@ class PVPlantWorkbench(Workbench):
|
|||||||
ToolTip = "Workbench for PV design"
|
ToolTip = "Workbench for PV design"
|
||||||
Icon = str(os.path.join(DirIcons, "icon.svg"))
|
Icon = str(os.path.join(DirIcons, "icon.svg"))
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
''' init '''
|
||||||
|
|
||||||
def Initialize(self):
|
def Initialize(self):
|
||||||
|
|
||||||
#sys.path.append(r"C:\Users\javie\AppData\Roaming\FreeCAD\Mod")
|
|
||||||
sys.path.append(os.path.join(FreeCAD.getUserAppDataDir(), 'Mod'))
|
sys.path.append(os.path.join(FreeCAD.getUserAppDataDir(), 'Mod'))
|
||||||
import PVPlantTools, reload
|
import PVPlantTools, reload
|
||||||
|
|
||||||
@@ -59,6 +61,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 +69,8 @@ class PVPlantWorkbench(Workbench):
|
|||||||
"Stringing",
|
"Stringing",
|
||||||
"Separator",
|
"Separator",
|
||||||
"StringInverter",
|
"StringInverter",
|
||||||
|
"Separator",
|
||||||
|
"PowerConverter"
|
||||||
]
|
]
|
||||||
|
|
||||||
self.roads = ["PVPlantRoad",
|
self.roads = ["PVPlantRoad",
|
||||||
@@ -141,7 +146,10 @@ class PVPlantWorkbench(Workbench):
|
|||||||
from widgets import CountSelection
|
from widgets import CountSelection
|
||||||
|
|
||||||
def Activated(self):
|
def Activated(self):
|
||||||
"This function is executed when the workbench is activated"
|
"""This function is executed when the workbench is activated"""
|
||||||
|
|
||||||
|
FreeCAD.Console.PrintLog("Road workbench activated.\n")
|
||||||
|
|
||||||
import SelectionObserver
|
import SelectionObserver
|
||||||
import FreeCADGui
|
import FreeCADGui
|
||||||
|
|
||||||
@@ -150,7 +158,9 @@ class PVPlantWorkbench(Workbench):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def Deactivated(self):
|
def Deactivated(self):
|
||||||
"This function is executed when the workbench is deactivated"
|
"""This function is executed when the workbench is deactivated"""
|
||||||
|
|
||||||
|
FreeCAD.Console.PrintLog("Road workbench deactivated.\n")
|
||||||
|
|
||||||
#FreeCADGui.Selection.removeObserver(self.observer)
|
#FreeCADGui.Selection.removeObserver(self.observer)
|
||||||
return
|
return
|
||||||
@@ -198,4 +208,4 @@ class PVPlantWorkbench(Workbench):
|
|||||||
return "Gui::PythonWorkbench"
|
return "Gui::PythonWorkbench"
|
||||||
|
|
||||||
|
|
||||||
Gui.addWorkbench(PVPlantWorkbench())
|
FreeCADGui.addWorkbench(PVPlantWorkbench())
|
||||||
|
|||||||
@@ -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,15 +1092,19 @@ 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
|
||||||
|
try:
|
||||||
pl = obj.Placement
|
pl = obj.Placement
|
||||||
shape = obj.Setup.Shape.copy()
|
shape = obj.Setup.Shape.copy()
|
||||||
|
|
||||||
|
# Rotar módulos
|
||||||
p1 = shape.SubShapes[0].SubShapes[1].SubShapes[0].CenterOfMass
|
p1 = shape.SubShapes[0].SubShapes[1].SubShapes[0].CenterOfMass
|
||||||
p2 = min(shape.SubShapes[0].SubShapes[1].SubShapes[0].Faces, key=lambda face: face.Area).CenterOfMass
|
p2 = min(shape.SubShapes[0].SubShapes[1].SubShapes[0].Faces, key=lambda face: face.Area).CenterOfMass
|
||||||
axis = p1 - p2
|
axis = p1 - p2
|
||||||
modules = shape.SubShapes[0].rotate(p1, axis, obj.Tilt.Value)
|
modules = shape.SubShapes[0].rotate(p1, axis, obj.Tilt.Value)
|
||||||
|
|
||||||
|
# Rotar postes
|
||||||
angle = obj.Placement.Rotation.toEuler()[1]
|
angle = obj.Placement.Rotation.toEuler()[1]
|
||||||
newpoles = Part.makeCompound([])
|
newpoles = Part.makeCompound([])
|
||||||
for i in range(len(shape.SubShapes[1].SubShapes[0].SubShapes)):
|
for i in range(len(shape.SubShapes[1].SubShapes[0].SubShapes)):
|
||||||
@@ -1102,10 +1115,16 @@ class Tracker(ArchComponent.Component):
|
|||||||
newpoles.add(pole.rotate(base, axis, -angle))
|
newpoles.add(pole.rotate(base, axis, -angle))
|
||||||
poles = Part.makeCompound([newpoles, shape.SubShapes[1].SubShapes[1].copy()])
|
poles = Part.makeCompound([newpoles, shape.SubShapes[1].SubShapes[1].copy()])
|
||||||
|
|
||||||
|
# Crear forma final
|
||||||
obj.Shape = Part.makeCompound([modules, poles])
|
obj.Shape = Part.makeCompound([modules, poles])
|
||||||
obj.Placement = pl
|
obj.Placement = pl
|
||||||
|
|
||||||
|
# Sincronizar propiedades de ángulo
|
||||||
obj.AngleX, obj.AngleY, obj.AngleZ = obj.Placement.Rotation.toEulerAngles("XYZ")
|
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):
|
||||||
"A View Provider for the Pipe object"
|
"A View Provider for the Pipe object"
|
||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# PVPlant - Paquete reestructurado
|
||||||
|
#
|
||||||
|
# Los imports legacy (from PVPlantSite import X, etc.) siguen funcionando.
|
||||||
|
# Para nuevo código, usar: from PVPlant.core.site import _PVPlantSite
|
||||||
|
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
# /**********************************************************************
|
||||||
|
# * *
|
||||||
|
# * 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
|
||||||
|
|
||||||
|
if FreeCAD.GuiUp:
|
||||||
|
import FreeCADGui
|
||||||
|
from PySide import QtCore, QtGui
|
||||||
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||||
|
|
||||||
|
import os
|
||||||
|
else:
|
||||||
|
# \cond
|
||||||
|
def translate(ctxt,txt):
|
||||||
|
return txt
|
||||||
|
def QT_TRANSLATE_NOOP(ctxt,txt):
|
||||||
|
return txt
|
||||||
|
# \endcond
|
||||||
|
|
||||||
|
import PVPlantResources
|
||||||
|
from PVPlantResources import DirIcons as DirIcons
|
||||||
|
from PVPlantResources import DirResources as DirResources
|
||||||
|
|
||||||
|
|
||||||
|
class MapWindow(QtGui.QWidget):
|
||||||
|
def __init__(self, WinTitle="MapWindow"):
|
||||||
|
super(MapWindow, self).__init__()
|
||||||
|
self.raise_()
|
||||||
|
self.lat = None
|
||||||
|
self.lon = None
|
||||||
|
self.minLat = None
|
||||||
|
self.maxLat = None
|
||||||
|
self.minLon = None
|
||||||
|
self.maxLon = None
|
||||||
|
self.zoom = None
|
||||||
|
self.WinTitle = WinTitle
|
||||||
|
self.georeference_coordinates = {'lat': None, 'lon': None}
|
||||||
|
self.setupUi()
|
||||||
|
|
||||||
|
def setupUi(self):
|
||||||
|
from PySide2.QtWebEngineWidgets import QWebEngineView
|
||||||
|
from PySide2.QtWebChannel import QWebChannel
|
||||||
|
|
||||||
|
self.ui = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantGeoreferencing.ui", self)
|
||||||
|
|
||||||
|
self.resize(1200, 800)
|
||||||
|
self.setWindowTitle(self.WinTitle)
|
||||||
|
self.setWindowIcon(QtGui.QIcon(os.path.join(DirIcons, "Location.svg")))
|
||||||
|
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
|
||||||
|
|
||||||
|
self.layout = QtGui.QHBoxLayout(self)
|
||||||
|
self.layout.setContentsMargins(4, 4, 4, 4)
|
||||||
|
|
||||||
|
LeftWidget = QtGui.QWidget(self)
|
||||||
|
LeftLayout = QtGui.QVBoxLayout(LeftWidget)
|
||||||
|
LeftWidget.setLayout(LeftLayout)
|
||||||
|
LeftLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
RightWidget = QtGui.QWidget(self)
|
||||||
|
RightWidget.setFixedWidth(350)
|
||||||
|
RightLayout = QtGui.QVBoxLayout(RightWidget)
|
||||||
|
RightWidget.setLayout(RightLayout)
|
||||||
|
RightLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
self.layout.addWidget(LeftWidget)
|
||||||
|
self.layout.addWidget(RightWidget)
|
||||||
|
|
||||||
|
# Left Widgets:
|
||||||
|
# -- Search Bar:
|
||||||
|
self.valueSearch = QtGui.QLineEdit(self)
|
||||||
|
self.valueSearch.setPlaceholderText("Search")
|
||||||
|
self.valueSearch.returnPressed.connect(self.onSearch)
|
||||||
|
|
||||||
|
searchbutton = QtGui.QPushButton('Search')
|
||||||
|
searchbutton.setFixedWidth(80)
|
||||||
|
searchbutton.clicked.connect(self.onSearch)
|
||||||
|
|
||||||
|
SearchBarLayout = QtGui.QHBoxLayout(self)
|
||||||
|
SearchBarLayout.addWidget(self.valueSearch)
|
||||||
|
SearchBarLayout.addWidget(searchbutton)
|
||||||
|
LeftLayout.addLayout(SearchBarLayout)
|
||||||
|
|
||||||
|
# -- Webbroser:
|
||||||
|
self.view = QWebEngineView()
|
||||||
|
self.channel = QWebChannel(self.view.page())
|
||||||
|
self.view.page().setWebChannel(self.channel)
|
||||||
|
self.channel.registerObject("MyApp", self)
|
||||||
|
file = os.path.join(DirResources, "webs", "main.html")
|
||||||
|
self.view.page().loadFinished.connect(self.onLoadFinished)
|
||||||
|
self.view.page().load(QtCore.QUrl.fromLocalFile(file))
|
||||||
|
LeftLayout.addWidget(self.view)
|
||||||
|
|
||||||
|
# -- Latitud y longitud:
|
||||||
|
self.labelCoordinates = QtGui.QLabel()
|
||||||
|
self.labelCoordinates.setFixedHeight(21)
|
||||||
|
LeftLayout.addWidget(self.labelCoordinates)
|
||||||
|
|
||||||
|
# Right Widgets:
|
||||||
|
labelKMZ = QtGui.QLabel()
|
||||||
|
labelKMZ.setText("Cargar un archivo KMZ/KML:")
|
||||||
|
self.kmlButton = QtGui.QPushButton()
|
||||||
|
self.kmlButton.setFixedSize(32, 32)
|
||||||
|
self.kmlButton.setIcon(QtGui.QIcon(os.path.join(DirIcons, "googleearth.svg")))
|
||||||
|
widget = QtGui.QWidget(self)
|
||||||
|
layout = QtGui.QHBoxLayout(widget)
|
||||||
|
widget.setLayout(layout)
|
||||||
|
layout.addWidget(labelKMZ)
|
||||||
|
layout.addWidget(self.kmlButton)
|
||||||
|
RightLayout.addWidget(widget)
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
self.groupbox = QtGui.QGroupBox("Importar datos desde:")
|
||||||
|
self.groupbox.setCheckable(True)
|
||||||
|
self.groupbox.setChecked(True)
|
||||||
|
radio1 = QtGui.QRadioButton("Google Elevation")
|
||||||
|
radio2 = QtGui.QRadioButton("Nube de Puntos")
|
||||||
|
radio3 = QtGui.QRadioButton("Datos GPS")
|
||||||
|
radio1.setChecked(True)
|
||||||
|
|
||||||
|
vbox = QtGui.QVBoxLayout(self)
|
||||||
|
vbox.addWidget(radio1)
|
||||||
|
vbox.addWidget(radio2)
|
||||||
|
vbox.addWidget(radio3)
|
||||||
|
|
||||||
|
self.groupbox.setLayout(vbox)
|
||||||
|
RightLayout.addWidget(self.groupbox)
|
||||||
|
# ------------------------
|
||||||
|
|
||||||
|
self.checkboxImportGis = QtGui.QCheckBox("Importar datos GIS")
|
||||||
|
RightLayout.addWidget(self.checkboxImportGis)
|
||||||
|
|
||||||
|
self.checkboxImportSatelitalImagen = QtGui.QCheckBox("Importar Imagen Satelital")
|
||||||
|
RightLayout.addWidget(self.checkboxImportSatelitalImagen)
|
||||||
|
|
||||||
|
verticalSpacer = QtGui.QSpacerItem(20, 48, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||||
|
RightLayout.addItem(verticalSpacer)
|
||||||
|
|
||||||
|
self.bAccept = QtGui.QPushButton('Accept')
|
||||||
|
self.bAccept.clicked.connect(self.onAcceptClick)
|
||||||
|
RightLayout.addWidget(self.bAccept)
|
||||||
|
|
||||||
|
# signals/slots
|
||||||
|
QtCore.QObject.connect(self.kmlButton, QtCore.SIGNAL("clicked()"), self.importKML)
|
||||||
|
|
||||||
|
|
||||||
|
def onLoadFinished(self):
|
||||||
|
file = os.path.join(DirResources, "webs", "map.js")
|
||||||
|
frame = self.view.page()
|
||||||
|
with open(file, 'r') as f:
|
||||||
|
frame.runJavaScript(f.read())
|
||||||
|
|
||||||
|
def onSearch(self):
|
||||||
|
if self.valueSearch.text() == "":
|
||||||
|
return
|
||||||
|
|
||||||
|
from geopy.geocoders import Nominatim
|
||||||
|
|
||||||
|
geolocator = Nominatim(user_agent="http")
|
||||||
|
location = geolocator.geocode(self.valueSearch.text())
|
||||||
|
self.valueSearch.setText(location.address)
|
||||||
|
self.panMap(location.longitude, location.latitude, location.raw['boundingbox'])
|
||||||
|
|
||||||
|
def onAcceptClick(self):
|
||||||
|
frame = self.view.page()
|
||||||
|
# 1. georeferenciar
|
||||||
|
frame.runJavaScript(
|
||||||
|
"MyApp.georeference(drawnItems.getBounds().getCenter().lat, drawnItems.getBounds().getCenter().lng);"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. importar todos los elementos dibujados:
|
||||||
|
frame.runJavaScript(
|
||||||
|
"var data = drawnItems.toGeoJSON();"
|
||||||
|
"MyApp.shapes(JSON.stringify(data));"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
@QtCore.Slot(float, float)
|
||||||
|
def onMapMove(self, lat, lng):
|
||||||
|
from lib.projection import latlon_to_utm
|
||||||
|
|
||||||
|
self.lat = lat
|
||||||
|
self.lon = lng
|
||||||
|
easting, northing, zone_number, zone_letter = latlon_to_utm(lat, lng)
|
||||||
|
self.labelCoordinates.setText('Longitud: {:.5f}, Latitud: {:.5f}'.format(lng, lat) +
|
||||||
|
' | UTM: ' + str(zone_number) + zone_letter +
|
||||||
|
', {:.5f}m E, {:.5f}m N'.format(easting, northing))
|
||||||
|
|
||||||
|
@QtCore.Slot(float, float, float, float, int)
|
||||||
|
def onMapZoom(self, minLat, minLon, maxLat, maxLon, zoom):
|
||||||
|
self.minLat = min([minLat, maxLat])
|
||||||
|
self.maxLat = max([minLat, maxLat])
|
||||||
|
self.minLon = min([minLon, maxLon])
|
||||||
|
self.maxLon = max([minLon, maxLon])
|
||||||
|
self.zoom = zoom
|
||||||
|
|
||||||
|
@QtCore.Slot(float, float)
|
||||||
|
def georeference(self, lat, lng):
|
||||||
|
import PVPlantSite
|
||||||
|
from geopy.geocoders import Nominatim
|
||||||
|
|
||||||
|
self.georeference_coordinates['lat'] = lat
|
||||||
|
self.georeference_coordinates['lon'] = lng
|
||||||
|
|
||||||
|
Site = PVPlantSite.get(create=True)
|
||||||
|
Site.Proxy.setLatLon(lat, lng)
|
||||||
|
|
||||||
|
geolocator = Nominatim(user_agent="http")
|
||||||
|
location = geolocator.reverse('{:.5f}, {:.5f}'.format(lat, lng))
|
||||||
|
if location:
|
||||||
|
if location.raw["address"].get("road"):
|
||||||
|
str = location.raw["address"]["road"]
|
||||||
|
if location.raw["address"].get("house_number"):
|
||||||
|
str += ' ({0})'.format(location.raw["address"]["house_number"])
|
||||||
|
Site.Address = str
|
||||||
|
if location.raw["address"].get("city"):
|
||||||
|
Site.City = location.raw["address"]["city"]
|
||||||
|
if location.raw["address"].get("postcode"):
|
||||||
|
Site.PostalCode = location.raw["address"]["postcode"]
|
||||||
|
if location.raw["address"].get("address"):
|
||||||
|
Site.Region = '{0}'.format(location.raw["address"]["province"])
|
||||||
|
if location.raw["address"].get("state"):
|
||||||
|
if Site.Region != "":
|
||||||
|
Site.Region += " - "
|
||||||
|
Site.Region += '{0}'.format(location.raw["address"]["state"])
|
||||||
|
Site.Country = location.raw["address"]["country"]
|
||||||
|
|
||||||
|
@QtCore.Slot(str)
|
||||||
|
def shapes(self, drawnItems):
|
||||||
|
import geojson
|
||||||
|
import PVPlantImportGrid as ImportElevation
|
||||||
|
import Draft
|
||||||
|
import PVPlantSite
|
||||||
|
Site = PVPlantSite.get()
|
||||||
|
|
||||||
|
offset = FreeCAD.Vector(0, 0, 0)
|
||||||
|
if not (self.lat is None or self.lon is None):
|
||||||
|
offset = FreeCAD.Vector(Site.Origin)
|
||||||
|
offset.z = 0
|
||||||
|
|
||||||
|
items = geojson.loads(drawnItems)
|
||||||
|
for item in items['features']:
|
||||||
|
if item['geometry']['type'] == "Point": # 1. if the feature is a Point or Circle:
|
||||||
|
coord = item['geometry']['coordinates']
|
||||||
|
point = ImportElevation.getElevationFromOE([[coord[1], coord[0]],])
|
||||||
|
c = FreeCAD.Vector(point[0][0], point[0][1], point[0][2]).sub(offset)
|
||||||
|
if item['properties'].get('radius'):
|
||||||
|
r = round(item['properties']['radius'] * 1000, 0)
|
||||||
|
p = FreeCAD.Placement()
|
||||||
|
p.Base = c
|
||||||
|
obj = Draft.makeCircle(r, placement=p, face=False)
|
||||||
|
else:
|
||||||
|
obj = Draft.make_point(c * 1000, color=(0.5, 0.3, 0.6), point_size=10)
|
||||||
|
else: # 2. if the feature is a Polygon or Line:
|
||||||
|
cw = False
|
||||||
|
name = "Línea"
|
||||||
|
lp = item['geometry']['coordinates']
|
||||||
|
if item['geometry']['type'] == "Polygon":
|
||||||
|
cw = True
|
||||||
|
name = "Area"
|
||||||
|
lp = item['geometry']['coordinates'][0]
|
||||||
|
|
||||||
|
pts = [[cords[1], cords[0]] for cords in lp]
|
||||||
|
tmp = ImportElevation.getElevationFromOE(pts)
|
||||||
|
pts = [p.sub(offset) for p in tmp]
|
||||||
|
|
||||||
|
obj = Draft.makeWire(pts, closed=cw, face=False)
|
||||||
|
obj.Label = name
|
||||||
|
Draft.autogroup(obj)
|
||||||
|
|
||||||
|
if item['properties'].get('name'):
|
||||||
|
obj.Label = item['properties']['name']
|
||||||
|
|
||||||
|
if self.checkboxImportGis.isChecked():
|
||||||
|
self.getDataFromOSM(self.minLat, self.minLon, self.maxLat, self.maxLon)
|
||||||
|
|
||||||
|
if self.checkboxImportSatelitalImagen.isChecked():
|
||||||
|
from lib.projection import latlon_to_utm
|
||||||
|
|
||||||
|
s_lat = self.minLat
|
||||||
|
s_lon = self.minLon
|
||||||
|
n_lat = self.maxLat
|
||||||
|
n_lon = self.maxLon
|
||||||
|
|
||||||
|
# Obtener puntos UTM para las esquinas y el punto de referencia
|
||||||
|
points = [
|
||||||
|
[s_lat, s_lon], # Suroeste
|
||||||
|
[n_lat, n_lon], # Noreste
|
||||||
|
[self.georeference_coordinates['lat'], self.georeference_coordinates['lon']] # Punto de referencia
|
||||||
|
]
|
||||||
|
utm_points = ImportElevation.getElevationFromOE(points)
|
||||||
|
|
||||||
|
if not utm_points or len(utm_points) < 3:
|
||||||
|
FreeCAD.Console.PrintError("Error obteniendo elevaciones para las esquinas y referencia\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
sw_utm, ne_utm, ref_utm = utm_points
|
||||||
|
|
||||||
|
# Descargar imagen satelital
|
||||||
|
from lib.GoogleSatelitalImageDownload import GoogleMapDownloader
|
||||||
|
downloader = GoogleMapDownloader(
|
||||||
|
zoom=self.zoom,
|
||||||
|
layer='raw_satellite'
|
||||||
|
)
|
||||||
|
img = downloader.generateImage(
|
||||||
|
sw_lat=s_lat,
|
||||||
|
sw_lng=s_lon,
|
||||||
|
ne_lat=n_lat,
|
||||||
|
ne_lng=n_lon
|
||||||
|
)
|
||||||
|
|
||||||
|
# Guardar imagen
|
||||||
|
doc_path = os.path.dirname(FreeCAD.ActiveDocument.FileName) if FreeCAD.ActiveDocument.FileName else ""
|
||||||
|
if not doc_path:
|
||||||
|
doc_path = FreeCAD.ConfigGet("UserAppData")
|
||||||
|
|
||||||
|
filename = os.path.join(doc_path, "background.jpeg")
|
||||||
|
img.save(filename)
|
||||||
|
|
||||||
|
# Calcular dimensiones reales en metros
|
||||||
|
width_m = ne_utm.x - sw_utm.x
|
||||||
|
height_m = ne_utm.y - sw_utm.y
|
||||||
|
|
||||||
|
# Calcular posición relativa del punto de referencia dentro de la imagen
|
||||||
|
rel_x = (ref_utm.x - sw_utm.x) / width_m if width_m != 0 else 0.5
|
||||||
|
rel_y = (ref_utm.y - sw_utm.y) / height_m if height_m != 0 else 0.5
|
||||||
|
|
||||||
|
# Crear objeto de imagen en FreeCAD
|
||||||
|
doc = FreeCAD.ActiveDocument
|
||||||
|
img_obj = doc.addObject('Image::ImagePlane', 'Background')
|
||||||
|
img_obj.ImageFile = filename
|
||||||
|
img_obj.Label = 'Background'
|
||||||
|
|
||||||
|
# FreeCAD trabaja en mm
|
||||||
|
img_obj.XSize = width_m * 1000
|
||||||
|
img_obj.YSize = height_m * 1000
|
||||||
|
|
||||||
|
# Posicionar para que el punto de referencia esté en (0,0,0)
|
||||||
|
img_obj.Placement.Base = FreeCAD.Vector(
|
||||||
|
-rel_x * width_m * 1000,
|
||||||
|
-rel_y * height_m * 1000,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
doc.recompute()
|
||||||
|
|
||||||
|
def getDataFromOSM(self, min_lat, min_lon, max_lat, max_lon):
|
||||||
|
import Importer.importOSM as importOSM
|
||||||
|
import PVPlantSite
|
||||||
|
site = PVPlantSite.get()
|
||||||
|
|
||||||
|
offset = FreeCAD.Vector(0, 0, 0)
|
||||||
|
if not (self.lat is None or self.lon is None):
|
||||||
|
offset = FreeCAD.Vector(site.Origin)
|
||||||
|
offset.z = 0
|
||||||
|
importer = importOSM.OSMImporter(offset)
|
||||||
|
osm_data = importer.get_osm_data(f"{min_lat},{min_lon},{max_lat},{max_lon}")
|
||||||
|
importer.process_osm_data(osm_data)
|
||||||
|
|
||||||
|
def panMap(self, lng, lat, geometry=None):
|
||||||
|
frame = self.view.page()
|
||||||
|
|
||||||
|
if not geometry or len(geometry) < 4:
|
||||||
|
command = f'map.panTo(L.latLng({lat}, {lng}));'
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
southwest = f"{float(geometry[1])}, {float(geometry[0])}"
|
||||||
|
northeast = f"{float(geometry[3])}, {float(geometry[2])}"
|
||||||
|
command = f'map.panTo(L.latLng({lat}, {lng}));'
|
||||||
|
command += f'map.fitBounds(L.latLngBounds([{southwest}], [{northeast}]));'
|
||||||
|
except (IndexError, ValueError, TypeError) as e:
|
||||||
|
print(f"Error en geometry: {str(e)}")
|
||||||
|
command = f'map.panTo(L.latLng({lat}, {lng}));'
|
||||||
|
frame.runJavaScript(command)
|
||||||
|
|
||||||
|
def importKML(self):
|
||||||
|
file = QtGui.QFileDialog.getOpenFileName(None, "FileDialog", "", "Google Earth (*.kml *.kmz)")[0]
|
||||||
|
|
||||||
|
from lib.kml2geojson import kmz_convert
|
||||||
|
layers = kmz_convert(file, "", )
|
||||||
|
frame = self.view.page()
|
||||||
|
for layer in layers:
|
||||||
|
command = "var geoJsonLayer = L.geoJSON({0}); drawnItems.addLayer(geoJsonLayer); map.fitBounds(geoJsonLayer.getBounds());".format( layer)
|
||||||
|
frame.runJavaScript(command)
|
||||||
|
|
||||||
|
|
||||||
|
class CommandPVPlantGeoreferencing:
|
||||||
|
|
||||||
|
def GetResources(self):
|
||||||
|
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")}
|
||||||
|
|
||||||
|
def Activated(self):
|
||||||
|
self.form = MapWindow()
|
||||||
|
self.form.show()
|
||||||
|
|
||||||
|
def IsActive(self):
|
||||||
|
if FreeCAD.ActiveDocument:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
# /**********************************************************************
|
||||||
|
# * *
|
||||||
|
# * 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, Draft, math, datetime
|
||||||
|
import ArchSite
|
||||||
|
|
||||||
|
if FreeCAD.GuiUp:
|
||||||
|
import FreeCADGui
|
||||||
|
from DraftTools import translate
|
||||||
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||||
|
from pivy import coin
|
||||||
|
else:
|
||||||
|
def translate(ctxt, txt):
|
||||||
|
return txt
|
||||||
|
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||||
|
return txt
|
||||||
|
|
||||||
|
import os
|
||||||
|
from PVPlantResources import DirIcons as DirIcons
|
||||||
|
|
||||||
|
zone_list = ["Z1", "Z2", "Z3", "Z4", "Z5", "Z6", "Z7", "Z8", "Z9", "Z10", "Z11", "Z12",
|
||||||
|
"Z13", "Z14", "Z15", "Z16", "Z17", "Z18", "Z19", "Z20", "Z21", "Z22", "Z23", "Z24",
|
||||||
|
"Z25", "Z26", "Z27", "Z28", "Z29", "Z30", "Z31", "Z32", "Z33", "Z34", "Z35", "Z36",
|
||||||
|
"Z37", "Z38", "Z39", "Z40", "Z41", "Z42", "Z43", "Z44", "Z45", "Z46", "Z47", "Z48",
|
||||||
|
"Z49", "Z50", "Z51", "Z52", "Z53", "Z54", "Z55", "Z56", "Z57", "Z58", "Z59", "Z60"]
|
||||||
|
|
||||||
|
|
||||||
|
def get(origin=FreeCAD.Vector(0, 0, 0), create=False):
|
||||||
|
obj = FreeCAD.ActiveDocument.getObject('Site')
|
||||||
|
if obj:
|
||||||
|
if obj.Origin == FreeCAD.Vector(0, 0, 0):
|
||||||
|
obj.Origin = origin
|
||||||
|
return obj
|
||||||
|
if not obj and create:
|
||||||
|
obj = makePVPlantSite()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def PartToWire(part):
|
||||||
|
import Part, Draft
|
||||||
|
PointList = []
|
||||||
|
edges = Part.__sortEdges__(part.Shape.Edges)
|
||||||
|
for edge in edges:
|
||||||
|
PointList.append(edge.Vertexes[0].Point)
|
||||||
|
PointList.append(edges[-1].Vertexes[-1].Point)
|
||||||
|
Draft.makeWire(PointList, closed=True, face=None, support=None)
|
||||||
|
|
||||||
|
|
||||||
|
def projectWireOnMesh(Boundary, Mesh):
|
||||||
|
import Draft
|
||||||
|
import MeshPart as mp
|
||||||
|
plist = mp.projectShapeOnMesh(Boundary.Shape, Mesh, FreeCAD.Vector(0, 0, 1))
|
||||||
|
PointList = []
|
||||||
|
for pl in plist:
|
||||||
|
PointList += pl
|
||||||
|
Draft.makeWire(PointList, closed=True, face=None, support=None)
|
||||||
|
FreeCAD.activeDocument().recompute()
|
||||||
|
|
||||||
|
|
||||||
|
def makePVPlantSite():
|
||||||
|
def createGroup(father, groupname, type=None):
|
||||||
|
group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", groupname)
|
||||||
|
group.Label = groupname
|
||||||
|
father.addObject(group)
|
||||||
|
return group
|
||||||
|
|
||||||
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Site")
|
||||||
|
_PVPlantSite(obj)
|
||||||
|
if FreeCAD.GuiUp:
|
||||||
|
_ViewProviderSite(obj.ViewObject)
|
||||||
|
|
||||||
|
group = createGroup(obj, "CivilGroup")
|
||||||
|
group1 = createGroup(group, "Areas")
|
||||||
|
createGroup(group1, "Boundaries")
|
||||||
|
createGroup(group1, "CadastralPlots")
|
||||||
|
createGroup(group1, "Exclusions")
|
||||||
|
createGroup(group1, "FrameZones")
|
||||||
|
createGroup(group1, "Offsets")
|
||||||
|
createGroup(group1, "Plots")
|
||||||
|
createGroup(group, "Drains")
|
||||||
|
createGroup(group, "Earthworks")
|
||||||
|
createGroup(group, "Fences")
|
||||||
|
createGroup(group, "Foundations")
|
||||||
|
createGroup(group, "Pads")
|
||||||
|
createGroup(group, "Points")
|
||||||
|
createGroup(group, "Roads")
|
||||||
|
createGroup(group, "Trenches")
|
||||||
|
|
||||||
|
group = createGroup(obj, "ElectricalGroup")
|
||||||
|
createGroup(group, "StringInverters")
|
||||||
|
createGroup(group, "CentralInverter")
|
||||||
|
group1 = createGroup(group, "AC")
|
||||||
|
createGroup(group1, "CableAC")
|
||||||
|
group1 = createGroup(group, "DC")
|
||||||
|
createGroup(group1, "CableDC")
|
||||||
|
createGroup(group1, "StringsSetup")
|
||||||
|
createGroup(group1, "Strings")
|
||||||
|
createGroup(group1, "StringsBoxes")
|
||||||
|
|
||||||
|
group = createGroup(obj, "MechanicalGroup")
|
||||||
|
createGroup(group, "FramesSetups")
|
||||||
|
createGroup(group, "Frames")
|
||||||
|
|
||||||
|
group = createGroup(obj, "Environment")
|
||||||
|
createGroup(group, "Vegetation")
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class _PVPlantSite(ArchSite._Site):
|
||||||
|
"The Site object"
|
||||||
|
|
||||||
|
def __init__(self, obj):
|
||||||
|
ArchSite._Site.__init__(self, obj)
|
||||||
|
self.obj = obj
|
||||||
|
self.Type = "Site"
|
||||||
|
obj.Proxy = self
|
||||||
|
obj.IfcType = "Site"
|
||||||
|
obj.setEditorMode("IfcType", 1)
|
||||||
|
|
||||||
|
def setProperties(self, obj):
|
||||||
|
ArchSite._Site.setProperties(self, obj)
|
||||||
|
obj.addProperty("App::PropertyLink", "Boundary", "PVPlant", "Boundary of land")
|
||||||
|
obj.addProperty("App::PropertyLinkList", "Frames", "PVPlant", "Frames templates")
|
||||||
|
obj.addProperty("App::PropertyEnumeration", "UtmZone", "PVPlant", "UTM zone").UtmZone = zone_list
|
||||||
|
obj.addProperty("App::PropertyVector", "Origin", "PVPlant", "Origin point.").Origin = (0, 0, 0)
|
||||||
|
|
||||||
|
def onDocumentRestored(self, obj):
|
||||||
|
self.obj = obj
|
||||||
|
self.Type = "Site"
|
||||||
|
obj.Proxy = self
|
||||||
|
|
||||||
|
def onChanged(self, obj, prop):
|
||||||
|
ArchSite._Site.onChanged(self, obj, prop)
|
||||||
|
if (prop == "Terrain") or (prop == "Boundary"):
|
||||||
|
if obj.Terrain and obj.Boundary:
|
||||||
|
print("Calcular 3D boundary")
|
||||||
|
if prop == "UtmZone":
|
||||||
|
node = self.get_geoorigin()
|
||||||
|
zone = obj.getPropertyByName("UtmZone")
|
||||||
|
geo_system = ["UTM", zone, "FLAT"]
|
||||||
|
node.geoSystem.setValues(geo_system)
|
||||||
|
if prop == "Origin":
|
||||||
|
node = self.get_geoorigin()
|
||||||
|
origin = obj.getPropertyByName("Origin")
|
||||||
|
node.geoCoords.setValue(origin.x, origin.y, 0)
|
||||||
|
obj.Placement.Base = obj.getPropertyByName(prop)
|
||||||
|
|
||||||
|
def execute(self, obj):
|
||||||
|
ArchSite._Site.execute(self, obj)
|
||||||
|
|
||||||
|
def computeAreas(self, obj):
|
||||||
|
ArchSite._Site.computeAreas(self, obj)
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
node = self.get_geoorigin()
|
||||||
|
system = node.geoSystem.getValues()
|
||||||
|
x, y, z = node.geoCoords.getValue().getValue()
|
||||||
|
return system, [x, y, z]
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
if state:
|
||||||
|
system = state[0]
|
||||||
|
origin = state[1]
|
||||||
|
node = self.get_geoorigin()
|
||||||
|
node.geoSystem.setValues(system)
|
||||||
|
node.geoCoords.setValue(origin[0], origin[1], 0)
|
||||||
|
|
||||||
|
def get_geoorigin(self):
|
||||||
|
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
|
||||||
|
node = sg.getChild(0)
|
||||||
|
if not isinstance(node, coin.SoGeoOrigin):
|
||||||
|
node = coin.SoGeoOrigin()
|
||||||
|
sg.insertChild(node, 0)
|
||||||
|
return node
|
||||||
|
|
||||||
|
def setLatLon(self, lat, lon):
|
||||||
|
from lib.projection import latlon_to_utm
|
||||||
|
import PVPlantImportGrid
|
||||||
|
easting, northing, zone_number, zone_letter = latlon_to_utm(lat, lon)
|
||||||
|
self.obj.UtmZone = zone_list[zone_number - 1]
|
||||||
|
point = PVPlantImportGrid.getElevationFromOE([[lat, lon]])
|
||||||
|
self.obj.Origin = FreeCAD.Vector(point[0].x, point[0].y, point[0].z)
|
||||||
|
self.obj.Latitude = lat
|
||||||
|
self.obj.Longitude = lon
|
||||||
|
self.obj.Elevation = point[0].z
|
||||||
|
|
||||||
|
|
||||||
|
from PVPlant.core.view_provider import ViewProviderSite as _ViewProviderSite
|
||||||
@@ -0,0 +1,353 @@
|
|||||||
|
# /**********************************************************************
|
||||||
|
# * *
|
||||||
|
# * 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, math, datetime
|
||||||
|
from pivy import coin
|
||||||
|
|
||||||
|
|
||||||
|
def makeSolarDiagram(longitude, latitude, scale=1, complete=False, tz=None):
|
||||||
|
"""makeSolarDiagram(longitude,latitude,[scale,complete,tz]):
|
||||||
|
returns a solar diagram as a pivy node. If complete is
|
||||||
|
True, the 12 months are drawn. Tz is the timezone related to
|
||||||
|
UTC (ex: -3 = UTC-3)"""
|
||||||
|
|
||||||
|
oldversion = False
|
||||||
|
ladybug = False
|
||||||
|
try:
|
||||||
|
import ladybug
|
||||||
|
from ladybug import location
|
||||||
|
from ladybug import sunpath
|
||||||
|
except:
|
||||||
|
ladybug = False
|
||||||
|
try:
|
||||||
|
import pysolar
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
import Pysolar as pysolar
|
||||||
|
except:
|
||||||
|
FreeCAD.Console.PrintError("The pysolar module was not found. Unable to generate solar diagrams\n")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
oldversion = True
|
||||||
|
if tz:
|
||||||
|
tz = datetime.timezone(datetime.timedelta(hours=-3))
|
||||||
|
else:
|
||||||
|
tz = datetime.timezone.utc
|
||||||
|
else:
|
||||||
|
loc = ladybug.location.Location(latitude=latitude, longitude=longitude, time_zone=tz)
|
||||||
|
sunpath = ladybug.sunpath.Sunpath.from_location(loc)
|
||||||
|
|
||||||
|
if not scale:
|
||||||
|
return None
|
||||||
|
|
||||||
|
circles = []
|
||||||
|
sunpaths = []
|
||||||
|
hourpaths = []
|
||||||
|
circlepos = []
|
||||||
|
hourpos = []
|
||||||
|
|
||||||
|
import Part
|
||||||
|
for i in range(1, 9):
|
||||||
|
circles.append(Part.makeCircle(scale * (i / 8.0)))
|
||||||
|
for ad in range(0, 360, 15):
|
||||||
|
a = math.radians(ad)
|
||||||
|
p1 = FreeCAD.Vector(math.cos(a) * scale, math.sin(a) * scale, 0)
|
||||||
|
p2 = FreeCAD.Vector(math.cos(a) * scale * 0.125, math.sin(a) * scale * 0.125, 0)
|
||||||
|
p3 = FreeCAD.Vector(math.cos(a) * scale * 1.08, math.sin(a) * scale * 1.08, 0)
|
||||||
|
circles.append(Part.LineSegment(p1, p2).toShape())
|
||||||
|
circlepos.append((ad, p3))
|
||||||
|
|
||||||
|
year = datetime.datetime.now().year
|
||||||
|
hpts = [[] for i in range(24)]
|
||||||
|
m = [(6, 21), (7, 21), (8, 21), (9, 21), (10, 21), (11, 21), (12, 21)]
|
||||||
|
if complete:
|
||||||
|
m.extend([(1, 21), (2, 21), (3, 21), (4, 21), (5, 21)])
|
||||||
|
for i, d in enumerate(m):
|
||||||
|
pts = []
|
||||||
|
for h in range(24):
|
||||||
|
if ladybug:
|
||||||
|
sun = sunpath.calculate_sun(month=d[0], day=d[1], hour=h)
|
||||||
|
alt = math.radians(sun.altitude)
|
||||||
|
az = 90 + sun.azimuth
|
||||||
|
elif oldversion:
|
||||||
|
dt = datetime.datetime(year, d[0], d[1], h)
|
||||||
|
alt = math.radians(pysolar.solar.GetAltitudeFast(latitude, longitude, dt))
|
||||||
|
az = pysolar.solar.GetAzimuth(latitude, longitude, dt)
|
||||||
|
az = -90 + az
|
||||||
|
else:
|
||||||
|
dt = datetime.datetime(year, d[0], d[1], h, tzinfo=tz)
|
||||||
|
alt = math.radians(pysolar.solar.get_altitude_fast(latitude, longitude, dt))
|
||||||
|
az = pysolar.solar.get_azimuth(latitude, longitude, dt)
|
||||||
|
az = 90 + az
|
||||||
|
if az < 0:
|
||||||
|
az = 360 + az
|
||||||
|
az = math.radians(az)
|
||||||
|
zc = math.sin(alt) * scale
|
||||||
|
ic = math.cos(alt) * scale
|
||||||
|
xc = math.cos(az) * ic
|
||||||
|
yc = math.sin(az) * ic
|
||||||
|
p = FreeCAD.Vector(xc, yc, zc)
|
||||||
|
pts.append(p)
|
||||||
|
hpts[h].append(p)
|
||||||
|
if i in [0, 6]:
|
||||||
|
ep = FreeCAD.Vector(p)
|
||||||
|
ep.multiply(1.08)
|
||||||
|
if ep.z >= 0:
|
||||||
|
if not oldversion:
|
||||||
|
h = 24 - h
|
||||||
|
if h == 12:
|
||||||
|
if i == 0:
|
||||||
|
h = "SUMMER"
|
||||||
|
else:
|
||||||
|
h = "WINTER"
|
||||||
|
if latitude < 0:
|
||||||
|
if h == "SUMMER":
|
||||||
|
h = "WINTER"
|
||||||
|
else:
|
||||||
|
h = "SUMMER"
|
||||||
|
hourpos.append((h, ep))
|
||||||
|
if i < 7:
|
||||||
|
sunpaths.append(Part.makePolygon(pts))
|
||||||
|
|
||||||
|
for h in hpts:
|
||||||
|
if complete:
|
||||||
|
h.append(h[0])
|
||||||
|
hourpaths.append(Part.makePolygon(h))
|
||||||
|
|
||||||
|
sz = 2.1 * scale
|
||||||
|
cube = Part.makeBox(sz, sz, sz)
|
||||||
|
cube.translate(FreeCAD.Vector(-sz / 2, -sz / 2, -sz))
|
||||||
|
sunpaths = [sp.cut(cube) for sp in sunpaths]
|
||||||
|
hourpaths = [hp.cut(cube) for hp in hourpaths]
|
||||||
|
|
||||||
|
ts = 0.005 * scale
|
||||||
|
mastersep = coin.SoSeparator()
|
||||||
|
circlesep = coin.SoSeparator()
|
||||||
|
numsep = coin.SoSeparator()
|
||||||
|
pathsep = coin.SoSeparator()
|
||||||
|
hoursep = coin.SoSeparator()
|
||||||
|
hournumsep = coin.SoSeparator()
|
||||||
|
mastersep.addChild(circlesep)
|
||||||
|
mastersep.addChild(numsep)
|
||||||
|
mastersep.addChild(pathsep)
|
||||||
|
mastersep.addChild(hoursep)
|
||||||
|
for item in circles:
|
||||||
|
circlesep.addChild(toNode(item))
|
||||||
|
for item in sunpaths:
|
||||||
|
for w in item.Edges:
|
||||||
|
pathsep.addChild(toNode(w))
|
||||||
|
for item in hourpaths:
|
||||||
|
for w in item.Edges:
|
||||||
|
hoursep.addChild(toNode(w))
|
||||||
|
for p in circlepos:
|
||||||
|
text = coin.SoText2()
|
||||||
|
s = p[0] - 90
|
||||||
|
s = -s
|
||||||
|
if s > 360:
|
||||||
|
s = s - 360
|
||||||
|
if s < 0:
|
||||||
|
s = 360 + s
|
||||||
|
if s == 0:
|
||||||
|
s = "N"
|
||||||
|
elif s == 90:
|
||||||
|
s = "E"
|
||||||
|
elif s == 180:
|
||||||
|
s = "S"
|
||||||
|
elif s == 270:
|
||||||
|
s = "W"
|
||||||
|
else:
|
||||||
|
s = str(s)
|
||||||
|
text.string = s
|
||||||
|
text.justification = coin.SoText2.CENTER
|
||||||
|
coords = coin.SoTransform()
|
||||||
|
coords.translation.setValue([p[1].x, p[1].y, p[1].z])
|
||||||
|
coords.scaleFactor.setValue([ts, ts, ts])
|
||||||
|
item = coin.SoSeparator()
|
||||||
|
item.addChild(coords)
|
||||||
|
item.addChild(text)
|
||||||
|
numsep.addChild(item)
|
||||||
|
for p in hourpos:
|
||||||
|
text = coin.SoText2()
|
||||||
|
s = str(p[0])
|
||||||
|
text.string = s
|
||||||
|
text.justification = coin.SoText2.CENTER
|
||||||
|
coords = coin.SoTransform()
|
||||||
|
coords.translation.setValue([p[1].x, p[1].y, p[1].z])
|
||||||
|
coords.scaleFactor.setValue([ts, ts, ts])
|
||||||
|
item = coin.SoSeparator()
|
||||||
|
item.addChild(coords)
|
||||||
|
item.addChild(text)
|
||||||
|
numsep.addChild(item)
|
||||||
|
return mastersep
|
||||||
|
|
||||||
|
|
||||||
|
def makeWindRose(epwfile, scale=1, sectors=24):
|
||||||
|
try:
|
||||||
|
import ladybug
|
||||||
|
from ladybug import epw
|
||||||
|
except:
|
||||||
|
FreeCAD.Console.PrintError("The ladybug module was not found. Unable to generate solar diagrams\n")
|
||||||
|
return None
|
||||||
|
if not epwfile:
|
||||||
|
FreeCAD.Console.PrintWarning("No EPW file, unable to generate wind rose.\n")
|
||||||
|
return None
|
||||||
|
epw_data = ladybug.epw.EPW(epwfile)
|
||||||
|
baseangle = 360 / sectors
|
||||||
|
sectorangles = [i * baseangle for i in range(sectors)]
|
||||||
|
basebissect = baseangle / 2
|
||||||
|
angles = [basebissect]
|
||||||
|
for i in range(1, sectors):
|
||||||
|
angles.append(angles[-1] + baseangle)
|
||||||
|
windsbysector = [0 for i in range(sectors)]
|
||||||
|
for hour in epw_data.wind_direction:
|
||||||
|
sector = min(angles, key=lambda x: abs(x - hour))
|
||||||
|
sectorindex = angles.index(sector)
|
||||||
|
windsbysector[sectorindex] = windsbysector[sectorindex] + 1
|
||||||
|
maxwind = max(windsbysector)
|
||||||
|
windsbysector = [wind / maxwind for wind in windsbysector]
|
||||||
|
vectors = []
|
||||||
|
dividers = []
|
||||||
|
for i in range(sectors):
|
||||||
|
angle = math.radians(90 + angles[i])
|
||||||
|
x = math.cos(angle) * windsbysector[i] * scale
|
||||||
|
y = math.sin(angle) * windsbysector[i] * scale
|
||||||
|
vectors.append(FreeCAD.Vector(x, y, 0))
|
||||||
|
secangle = math.radians(90 + sectorangles[i])
|
||||||
|
x = math.cos(secangle) * scale
|
||||||
|
y = math.sin(secangle) * scale
|
||||||
|
dividers.append(FreeCAD.Vector(x, y, 0))
|
||||||
|
vectors.append(vectors[0])
|
||||||
|
|
||||||
|
import Part
|
||||||
|
masternode = coin.SoSeparator()
|
||||||
|
for r in (0.25, 0.5, 0.75, 1.0):
|
||||||
|
c = Part.makeCircle(r * scale)
|
||||||
|
masternode.addChild(toNode(c))
|
||||||
|
for divider in dividers:
|
||||||
|
l = Part.makeLine(FreeCAD.Vector(), divider)
|
||||||
|
masternode.addChild(toNode(l))
|
||||||
|
ds = coin.SoDrawStyle()
|
||||||
|
ds.lineWidth = 2.0
|
||||||
|
masternode.addChild(ds)
|
||||||
|
d = Part.makePolygon(vectors)
|
||||||
|
masternode.addChild(toNode(d))
|
||||||
|
return masternode
|
||||||
|
|
||||||
|
|
||||||
|
# Values in mm
|
||||||
|
COMPASS_POINTER_LENGTH = 1000
|
||||||
|
COMPASS_POINTER_WIDTH = 100
|
||||||
|
|
||||||
|
|
||||||
|
class Compass(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.rootNode = self.setupCoin()
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
self.compassswitch.whichChild = coin.SO_SWITCH_ALL
|
||||||
|
|
||||||
|
def hide(self):
|
||||||
|
self.compassswitch.whichChild = coin.SO_SWITCH_NONE
|
||||||
|
|
||||||
|
def rotate(self, angleInDegrees):
|
||||||
|
self.transform.rotation.setValue(
|
||||||
|
coin.SbVec3f(0, 0, 1), math.radians(angleInDegrees))
|
||||||
|
|
||||||
|
def locate(self, x, y, z):
|
||||||
|
self.transform.translation.setValue(x, y, z)
|
||||||
|
|
||||||
|
def scale(self, area):
|
||||||
|
s = round(max(math.sqrt(area.getValueAs("m^2").Value) / 10, 1))
|
||||||
|
self.transform.scaleFactor.setValue(coin.SbVec3f(s, s, 1))
|
||||||
|
|
||||||
|
def setupCoin(self):
|
||||||
|
compasssep = coin.SoSeparator()
|
||||||
|
self.transform = coin.SoTransform()
|
||||||
|
|
||||||
|
darkNorthMaterial = coin.SoMaterial()
|
||||||
|
darkNorthMaterial.diffuseColor.set1Value(0, 0.5, 0, 0)
|
||||||
|
lightNorthMaterial = coin.SoMaterial()
|
||||||
|
lightNorthMaterial.diffuseColor.set1Value(0, 0.9, 0, 0)
|
||||||
|
darkGreyMaterial = coin.SoMaterial()
|
||||||
|
darkGreyMaterial.diffuseColor.set1Value(0, 0.9, 0.9, 0.9)
|
||||||
|
lightGreyMaterial = coin.SoMaterial()
|
||||||
|
lightGreyMaterial.diffuseColor.set1Value(0, 0.5, 0.5, 0.5)
|
||||||
|
|
||||||
|
coords = self.buildCoordinates()
|
||||||
|
lightColorFaceset = coin.SoIndexedFaceSet()
|
||||||
|
lightColorCoordinateIndex = [4, 5, 6, -1, 8, 9, 10, -1, 12, 13, 14, -1]
|
||||||
|
lightColorFaceset.coordIndex.setValues(0, len(lightColorCoordinateIndex), lightColorCoordinateIndex)
|
||||||
|
darkColorFaceset = coin.SoIndexedFaceSet()
|
||||||
|
darkColorCoordinateIndex = [6, 7, 4, -1, 10, 11, 8, -1, 14, 15, 12, -1]
|
||||||
|
darkColorFaceset.coordIndex.setValues(0, len(darkColorCoordinateIndex), darkColorCoordinateIndex)
|
||||||
|
lightNorthFaceset = coin.SoIndexedFaceSet()
|
||||||
|
lightNorthCoordinateIndex = [2, 3, 0, -1]
|
||||||
|
lightNorthFaceset.coordIndex.setValues(0, len(lightNorthCoordinateIndex), lightNorthCoordinateIndex)
|
||||||
|
darkNorthFaceset = coin.SoIndexedFaceSet()
|
||||||
|
darkNorthCoordinateIndex = [0, 1, 2, -1]
|
||||||
|
darkNorthFaceset.coordIndex.setValues(0, len(darkNorthCoordinateIndex), darkNorthCoordinateIndex)
|
||||||
|
|
||||||
|
self.compassswitch = coin.SoSwitch()
|
||||||
|
self.compassswitch.whichChild = coin.SO_SWITCH_NONE
|
||||||
|
self.compassswitch.addChild(compasssep)
|
||||||
|
|
||||||
|
lightGreySeparator = coin.SoSeparator()
|
||||||
|
lightGreySeparator.addChild(lightGreyMaterial)
|
||||||
|
lightGreySeparator.addChild(lightColorFaceset)
|
||||||
|
darkGreySeparator = coin.SoSeparator()
|
||||||
|
darkGreySeparator.addChild(darkGreyMaterial)
|
||||||
|
darkGreySeparator.addChild(darkColorFaceset)
|
||||||
|
lightNorthSeparator = coin.SoSeparator()
|
||||||
|
lightNorthSeparator.addChild(lightNorthMaterial)
|
||||||
|
lightNorthSeparator.addChild(lightNorthFaceset)
|
||||||
|
darkNorthSeparator = coin.SoSeparator()
|
||||||
|
darkNorthSeparator.addChild(darkNorthMaterial)
|
||||||
|
darkNorthSeparator.addChild(darkNorthFaceset)
|
||||||
|
|
||||||
|
compasssep.addChild(coords)
|
||||||
|
compasssep.addChild(self.transform)
|
||||||
|
compasssep.addChild(lightGreySeparator)
|
||||||
|
compasssep.addChild(darkGreySeparator)
|
||||||
|
compasssep.addChild(lightNorthSeparator)
|
||||||
|
compasssep.addChild(darkNorthSeparator)
|
||||||
|
|
||||||
|
return self.compassswitch
|
||||||
|
|
||||||
|
def buildCoordinates(self):
|
||||||
|
coords = coin.SoCoordinate3()
|
||||||
|
coords.point.set1Value(0, 0, 0, 0)
|
||||||
|
coords.point.set1Value(1, COMPASS_POINTER_WIDTH, COMPASS_POINTER_WIDTH, 0)
|
||||||
|
coords.point.set1Value(2, 0, COMPASS_POINTER_LENGTH, 0)
|
||||||
|
coords.point.set1Value(3, -COMPASS_POINTER_WIDTH, COMPASS_POINTER_WIDTH, 0)
|
||||||
|
coords.point.set1Value(4, 0, 0, 0)
|
||||||
|
coords.point.set1Value(5, COMPASS_POINTER_WIDTH, -COMPASS_POINTER_WIDTH, 0)
|
||||||
|
coords.point.set1Value(6, COMPASS_POINTER_LENGTH, 0, 0)
|
||||||
|
coords.point.set1Value(7, COMPASS_POINTER_WIDTH, COMPASS_POINTER_WIDTH, 0)
|
||||||
|
coords.point.set1Value(8, 0, 0, 0)
|
||||||
|
coords.point.set1Value(9, -COMPASS_POINTER_WIDTH, -COMPASS_POINTER_WIDTH, 0)
|
||||||
|
coords.point.set1Value(10, 0, -COMPASS_POINTER_LENGTH, 0)
|
||||||
|
coords.point.set1Value(11, COMPASS_POINTER_WIDTH, -COMPASS_POINTER_WIDTH, 0)
|
||||||
|
coords.point.set1Value(12, 0, 0, 0)
|
||||||
|
coords.point.set1Value(13, -COMPASS_POINTER_WIDTH, COMPASS_POINTER_WIDTH, 0)
|
||||||
|
coords.point.set1Value(14, -COMPASS_POINTER_LENGTH, 0, 0)
|
||||||
|
coords.point.set1Value(15, -COMPASS_POINTER_WIDTH, -COMPASS_POINTER_WIDTH, 0)
|
||||||
|
return coords
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
# /**********************************************************************
|
||||||
|
# * *
|
||||||
|
# * 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, math
|
||||||
|
from pivy import coin
|
||||||
|
|
||||||
|
if FreeCAD.GuiUp:
|
||||||
|
import FreeCADGui
|
||||||
|
from DraftTools import translate
|
||||||
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||||
|
|
||||||
|
from PVPlant.core.solar_compass import makeSolarDiagram, makeWindRose, Compass
|
||||||
|
|
||||||
|
|
||||||
|
class ViewProviderSite(object):
|
||||||
|
"""View Provider for the Site object. Handles solar diagram, wind rose, compass and true north."""
|
||||||
|
|
||||||
|
def __init__(self, vobj):
|
||||||
|
vobj.Proxy = self
|
||||||
|
vobj.addExtension("Gui::ViewProviderGroupExtensionPython", self)
|
||||||
|
self.setProperties(vobj)
|
||||||
|
|
||||||
|
def setProperties(self, vobj):
|
||||||
|
from PVPlantResources import DirIcons as DirIcons
|
||||||
|
pl = vobj.PropertiesList
|
||||||
|
if not "WindRose" in pl:
|
||||||
|
vobj.addProperty("App::PropertyBool", "WindRose", "Site",
|
||||||
|
QT_TRANSLATE_NOOP("App::Property", "Show wind rose diagram or not. Uses solar diagram scale. Needs Ladybug module"))
|
||||||
|
if not "SolarDiagram" in pl:
|
||||||
|
vobj.addProperty("App::PropertyBool", "SolarDiagram", "Site",
|
||||||
|
QT_TRANSLATE_NOOP("App::Property", "Show solar diagram or not"))
|
||||||
|
if not "SolarDiagramScale" in pl:
|
||||||
|
vobj.addProperty("App::PropertyFloat", "SolarDiagramScale", "Site",
|
||||||
|
QT_TRANSLATE_NOOP("App::Property", "The scale of the solar diagram"))
|
||||||
|
vobj.SolarDiagramScale = 1
|
||||||
|
if not "SolarDiagramPosition" in pl:
|
||||||
|
vobj.addProperty("App::PropertyVector", "SolarDiagramPosition", "Site",
|
||||||
|
QT_TRANSLATE_NOOP("App::Property", "The position of the solar diagram"))
|
||||||
|
if not "SolarDiagramColor" in pl:
|
||||||
|
vobj.addProperty("App::PropertyColor", "SolarDiagramColor", "Site",
|
||||||
|
QT_TRANSLATE_NOOP("App::Property", "The color of the solar diagram"))
|
||||||
|
vobj.SolarDiagramColor = (0.16, 0.16, 0.25)
|
||||||
|
if not "Orientation" in pl:
|
||||||
|
vobj.addProperty("App::PropertyEnumeration", "Orientation", "Site",
|
||||||
|
QT_TRANSLATE_NOOP("App::Property", "When set to 'True North' the whole geometry will be rotated to match the true north of this site"))
|
||||||
|
vobj.Orientation = ["Project North", "True North"]
|
||||||
|
vobj.Orientation = "Project North"
|
||||||
|
if not "Compass" in pl:
|
||||||
|
vobj.addProperty("App::PropertyBool", "Compass", "Compass",
|
||||||
|
QT_TRANSLATE_NOOP("App::Property", "Show compass or not"))
|
||||||
|
if not "CompassRotation" in pl:
|
||||||
|
vobj.addProperty("App::PropertyAngle", "CompassRotation", "Compass",
|
||||||
|
QT_TRANSLATE_NOOP("App::Property", "The rotation of the Compass relative to the Site"))
|
||||||
|
if not "CompassPosition" in pl:
|
||||||
|
vobj.addProperty("App::PropertyVector", "CompassPosition", "Compass",
|
||||||
|
QT_TRANSLATE_NOOP("App::Property", "The position of the Compass relative to the Site placement"))
|
||||||
|
if not "UpdateDeclination" in pl:
|
||||||
|
vobj.addProperty("App::PropertyBool", "UpdateDeclination", "Compass",
|
||||||
|
QT_TRANSLATE_NOOP("App::Property", "Update the Declination value based on the compass rotation"))
|
||||||
|
|
||||||
|
def onDocumentRestored(self, vobj):
|
||||||
|
self.setProperties(vobj)
|
||||||
|
|
||||||
|
def getIcon(self):
|
||||||
|
from PVPlantResources import DirIcons as DirIcons
|
||||||
|
return str(os.path.join(DirIcons, "solar-panel.svg"))
|
||||||
|
|
||||||
|
def claimChildren(self):
|
||||||
|
objs = []
|
||||||
|
if hasattr(self, "Object"):
|
||||||
|
objs = self.Object.Group + [self.Object.Terrain]
|
||||||
|
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
|
||||||
|
if hasattr(self.Object, "Additions") and prefs.GetBool("swallowAdditions", True):
|
||||||
|
objs.extend(self.Object.Additions)
|
||||||
|
if hasattr(self.Object, "Subtractions") and prefs.GetBool("swallowSubtractions", True):
|
||||||
|
objs.extend(self.Object.Subtractions)
|
||||||
|
return objs
|
||||||
|
|
||||||
|
def setEdit(self, vobj, mode):
|
||||||
|
if (mode == 0) and hasattr(self, "Object"):
|
||||||
|
import ArchComponent
|
||||||
|
taskd = ArchComponent.ComponentTaskPanel()
|
||||||
|
taskd.obj = self.Object
|
||||||
|
taskd.update()
|
||||||
|
FreeCADGui.Control.showDialog(taskd)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def unsetEdit(self, vobj, mode):
|
||||||
|
FreeCADGui.Control.closeDialog()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def attach(self, vobj):
|
||||||
|
self.Object = vobj.Object
|
||||||
|
basesep = coin.SoSeparator()
|
||||||
|
vobj.Annotation.addChild(basesep)
|
||||||
|
self.color = coin.SoBaseColor()
|
||||||
|
self.coords = coin.SoTransform()
|
||||||
|
basesep.addChild(self.coords)
|
||||||
|
basesep.addChild(self.color)
|
||||||
|
self.diagramsep = coin.SoSeparator()
|
||||||
|
self.diagramswitch = coin.SoSwitch()
|
||||||
|
self.diagramswitch.whichChild = -1
|
||||||
|
self.diagramswitch.addChild(self.diagramsep)
|
||||||
|
basesep.addChild(self.diagramswitch)
|
||||||
|
self.windrosesep = coin.SoSeparator()
|
||||||
|
self.windroseswitch = coin.SoSwitch()
|
||||||
|
self.windroseswitch.whichChild = -1
|
||||||
|
self.windroseswitch.addChild(self.windrosesep)
|
||||||
|
basesep.addChild(self.windroseswitch)
|
||||||
|
self.compass = Compass()
|
||||||
|
self.updateCompassVisibility(vobj)
|
||||||
|
self.updateCompassScale(vobj)
|
||||||
|
self.rotateCompass(vobj)
|
||||||
|
vobj.Annotation.addChild(self.compass.rootNode)
|
||||||
|
|
||||||
|
def updateData(self, obj, prop):
|
||||||
|
if prop in ["Longitude", "Latitude"]:
|
||||||
|
self.onChanged(obj.ViewObject, "SolarDiagram")
|
||||||
|
elif prop == "Declination":
|
||||||
|
self.onChanged(obj.ViewObject, "SolarDiagramPosition")
|
||||||
|
self.updateTrueNorthRotation()
|
||||||
|
elif prop == "Terrain":
|
||||||
|
self.updateCompassLocation(obj.ViewObject)
|
||||||
|
elif prop == "Placement":
|
||||||
|
self.updateCompassLocation(obj.ViewObject)
|
||||||
|
self.updateDeclination(obj.ViewObject)
|
||||||
|
elif prop == "ProjectedArea":
|
||||||
|
self.updateCompassScale(obj.ViewObject)
|
||||||
|
|
||||||
|
def onChanged(self, vobj, prop):
|
||||||
|
if prop == "SolarDiagramPosition":
|
||||||
|
if hasattr(vobj, "SolarDiagramPosition"):
|
||||||
|
p = vobj.SolarDiagramPosition
|
||||||
|
self.coords.translation.setValue([p.x, p.y, p.z])
|
||||||
|
if hasattr(vobj.Object, "Declination"):
|
||||||
|
self.coords.rotation.setValue(coin.SbVec3f((0, 0, 1)), math.radians(vobj.Object.Declination.Value))
|
||||||
|
elif prop == "SolarDiagramColor":
|
||||||
|
if hasattr(vobj, "SolarDiagramColor"):
|
||||||
|
l = vobj.SolarDiagramColor
|
||||||
|
self.color.rgb.setValue([l[0], l[1], l[2]])
|
||||||
|
elif "SolarDiagram" in prop:
|
||||||
|
if hasattr(self, "diagramnode"):
|
||||||
|
self.diagramsep.removeChild(self.diagramnode)
|
||||||
|
del self.diagramnode
|
||||||
|
if hasattr(vobj, "SolarDiagram") and hasattr(vobj, "SolarDiagramScale"):
|
||||||
|
if vobj.SolarDiagram:
|
||||||
|
tz = 0
|
||||||
|
if hasattr(vobj.Object, "TimeZone"):
|
||||||
|
tz = vobj.Object.TimeZone
|
||||||
|
self.diagramnode = makeSolarDiagram(vobj.Object.Longitude, vobj.Object.Latitude,
|
||||||
|
vobj.SolarDiagramScale, tz=tz)
|
||||||
|
if self.diagramnode:
|
||||||
|
self.diagramsep.addChild(self.diagramnode)
|
||||||
|
self.diagramswitch.whichChild = 0
|
||||||
|
else:
|
||||||
|
del self.diagramnode
|
||||||
|
else:
|
||||||
|
self.diagramswitch.whichChild = -1
|
||||||
|
elif prop == "WindRose":
|
||||||
|
if hasattr(self, "windrosenode"):
|
||||||
|
del self.windrosenode
|
||||||
|
if hasattr(vobj, "WindRose"):
|
||||||
|
if vobj.WindRose:
|
||||||
|
if hasattr(vobj.Object, "EPWFile") and vobj.Object.EPWFile:
|
||||||
|
try:
|
||||||
|
import ladybug
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.windrosenode = makeWindRose(vobj.Object.EPWFile, vobj.SolarDiagramScale)
|
||||||
|
if self.windrosenode:
|
||||||
|
self.windrosesep.addChild(self.windrosenode)
|
||||||
|
self.windroseswitch.whichChild = 0
|
||||||
|
else:
|
||||||
|
del self.windrosenode
|
||||||
|
else:
|
||||||
|
self.windroseswitch.whichChild = -1
|
||||||
|
elif prop == 'Visibility':
|
||||||
|
if vobj.Visibility:
|
||||||
|
self.updateCompassVisibility(self.Object)
|
||||||
|
else:
|
||||||
|
self.compass.hide()
|
||||||
|
elif prop == 'Orientation':
|
||||||
|
if vobj.Orientation == 'True North':
|
||||||
|
self.addTrueNorthRotation()
|
||||||
|
else:
|
||||||
|
self.removeTrueNorthRotation()
|
||||||
|
elif prop == "UpdateDeclination":
|
||||||
|
self.updateDeclination(vobj)
|
||||||
|
elif prop == "Compass":
|
||||||
|
self.updateCompassVisibility(vobj)
|
||||||
|
elif prop == "CompassRotation":
|
||||||
|
self.updateDeclination(vobj)
|
||||||
|
self.rotateCompass(vobj)
|
||||||
|
elif prop == "CompassPosition":
|
||||||
|
self.updateCompassLocation(vobj)
|
||||||
|
|
||||||
|
def updateDeclination(self, vobj):
|
||||||
|
if not hasattr(vobj, 'UpdateDeclination') or not vobj.UpdateDeclination:
|
||||||
|
return
|
||||||
|
compassRotation = vobj.CompassRotation.Value
|
||||||
|
siteRotation = math.degrees(vobj.Object.Placement.Rotation.Angle)
|
||||||
|
vobj.Object.Declination = compassRotation + siteRotation
|
||||||
|
|
||||||
|
def addTrueNorthRotation(self):
|
||||||
|
if hasattr(self, 'trueNorthRotation') and self.trueNorthRotation is not None:
|
||||||
|
return
|
||||||
|
self.trueNorthRotation = coin.SoTransform()
|
||||||
|
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
|
||||||
|
sg.insertChild(self.trueNorthRotation, 0)
|
||||||
|
self.updateTrueNorthRotation()
|
||||||
|
|
||||||
|
def removeTrueNorthRotation(self):
|
||||||
|
if hasattr(self, 'trueNorthRotation') and self.trueNorthRotation is not None:
|
||||||
|
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
|
||||||
|
sg.removeChild(self.trueNorthRotation)
|
||||||
|
self.trueNorthRotation = None
|
||||||
|
|
||||||
|
def updateTrueNorthRotation(self):
|
||||||
|
if hasattr(self, 'trueNorthRotation') and self.trueNorthRotation is not None:
|
||||||
|
angle = self.Object.Declination.Value
|
||||||
|
self.trueNorthRotation.rotation.setValue(coin.SbVec3f(0, 0, 1), math.radians(-angle))
|
||||||
|
|
||||||
|
def updateCompassVisibility(self, vobj):
|
||||||
|
if not hasattr(self, 'compass'):
|
||||||
|
return
|
||||||
|
show = hasattr(vobj, 'Compass') and vobj.Compass
|
||||||
|
if show:
|
||||||
|
self.compass.show()
|
||||||
|
else:
|
||||||
|
self.compass.hide()
|
||||||
|
|
||||||
|
def rotateCompass(self, vobj):
|
||||||
|
if not hasattr(self, 'compass'):
|
||||||
|
return
|
||||||
|
if hasattr(vobj, 'CompassRotation'):
|
||||||
|
self.compass.rotate(vobj.CompassRotation.Value)
|
||||||
|
|
||||||
|
def updateCompassLocation(self, vobj):
|
||||||
|
if not hasattr(self, 'compass'):
|
||||||
|
return
|
||||||
|
if not vobj.Object.Shape:
|
||||||
|
return
|
||||||
|
boundBox = vobj.Object.Shape.BoundBox
|
||||||
|
pos = vobj.Object.Placement.Base
|
||||||
|
x = 0
|
||||||
|
y = 0
|
||||||
|
if hasattr(vobj, "CompassPosition"):
|
||||||
|
x = vobj.CompassPosition.x
|
||||||
|
y = vobj.CompassPosition.y
|
||||||
|
z = boundBox.ZMax = pos.z
|
||||||
|
self.compass.locate(x, y, z + 1000)
|
||||||
|
|
||||||
|
def updateCompassScale(self, vobj):
|
||||||
|
if not hasattr(self, 'compass'):
|
||||||
|
return
|
||||||
|
self.compass.scale(vobj.Object.ProjectedArea)
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
return None
|
||||||
@@ -0,0 +1,671 @@
|
|||||||
|
# /**********************************************************************
|
||||||
|
# * *
|
||||||
|
# * 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 json
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
import Draft
|
||||||
|
import FreeCAD
|
||||||
|
import FreeCADGui
|
||||||
|
from PySide import QtCore, QtGui
|
||||||
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||||
|
|
||||||
|
try:
|
||||||
|
_fromUtf8 = QtCore.QString.fromUtf8
|
||||||
|
except AttributeError:
|
||||||
|
def _fromUtf8(s):
|
||||||
|
return s
|
||||||
|
|
||||||
|
import os
|
||||||
|
from PVPlantResources import DirIcons as DirIcons
|
||||||
|
import PVPlantSite
|
||||||
|
|
||||||
|
|
||||||
|
def get_elevation_from_oe(coordinates):
|
||||||
|
"""Obtiene elevaciones de Open-Elevation API y devuelve vectores FreeCAD en coordenadas UTM.
|
||||||
|
Args:
|
||||||
|
coordinates (list): Lista de tuplas con coordenadas (latitud, longitud)
|
||||||
|
Returns:
|
||||||
|
list: Lista de vectores FreeCAD con coordenadas UTM y elevación (en milímetros)
|
||||||
|
o lista vacía en caso de error.
|
||||||
|
"""
|
||||||
|
if not coordinates:
|
||||||
|
return []
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from lib.projection import latlon_to_utm
|
||||||
|
from requests.exceptions import RequestException
|
||||||
|
|
||||||
|
locations = "|".join([f"{lat:.6f},{lon:.6f}" for lat, lon in coordinates])
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(
|
||||||
|
url="https://api.open-elevation.com/api/v1/lookup",
|
||||||
|
params={'locations': locations},
|
||||||
|
timeout=20,
|
||||||
|
verify=True
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
except RequestException as e:
|
||||||
|
print(f"Error en la solicitud: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = response.json()
|
||||||
|
except ValueError:
|
||||||
|
print("Respuesta JSON inválida")
|
||||||
|
return []
|
||||||
|
|
||||||
|
if "results" not in data or len(data["results"]) != len(coordinates):
|
||||||
|
print("Formato de respuesta inesperado")
|
||||||
|
return []
|
||||||
|
|
||||||
|
points = []
|
||||||
|
for result in data["results"]:
|
||||||
|
try:
|
||||||
|
easting, northing, _, _ = latlon_to_utm(
|
||||||
|
result["latitude"],
|
||||||
|
result["longitude"]
|
||||||
|
)
|
||||||
|
|
||||||
|
points.append(FreeCAD.Vector(round(easting),
|
||||||
|
round(northing),
|
||||||
|
round(result["elevation"])) * 1000)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error procesando coordenadas: {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
return points
|
||||||
|
|
||||||
|
|
||||||
|
def getElevationFromOE(coordinates):
|
||||||
|
"""Obtiene elevaciones de Open-Elevation API y devuelve vectores FreeCAD en coordenadas UTM."""
|
||||||
|
|
||||||
|
import certifi
|
||||||
|
from requests.exceptions import RequestException
|
||||||
|
if len(coordinates) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
from requests import get
|
||||||
|
from lib.projection import latlon_to_utm
|
||||||
|
|
||||||
|
locations_str=""
|
||||||
|
total = len(coordinates) - 1
|
||||||
|
for i, point in enumerate(coordinates):
|
||||||
|
locations_str += '{:.6f},{:.6f}'.format(point[0], point[1])
|
||||||
|
if i != total:
|
||||||
|
locations_str += '|'
|
||||||
|
query = 'https://api.open-elevation.com/api/v1/lookup?locations=' + locations_str
|
||||||
|
points = []
|
||||||
|
try:
|
||||||
|
r = get(query, timeout=20, verify=certifi.where())
|
||||||
|
results = r.json()
|
||||||
|
for point in results["results"]:
|
||||||
|
easting, northing, _, _ = latlon_to_utm(point["latitude"], point["longitude"])
|
||||||
|
v = FreeCAD.Vector(round(easting, 0),
|
||||||
|
round(northing, 0),
|
||||||
|
round(point["elevation"], 0)) * 1000
|
||||||
|
points.append(v)
|
||||||
|
except RequestException as e:
|
||||||
|
for point in coordinates:
|
||||||
|
easting, northing, _, _ = latlon_to_utm(point[0], point[1])
|
||||||
|
points.append(FreeCAD.Vector(round(easting, 0),
|
||||||
|
round(northing, 0),
|
||||||
|
0) * 1000)
|
||||||
|
|
||||||
|
return points
|
||||||
|
|
||||||
|
def getSinglePointElevationFromBing(lat, lng):
|
||||||
|
import requests
|
||||||
|
from lib.projection import latlon_to_utm
|
||||||
|
|
||||||
|
source = "http://dev.virtualearth.net/REST/v1/Elevation/List?points="
|
||||||
|
source += str(lat) + "," + str(lng)
|
||||||
|
source += "&heights=sealevel"
|
||||||
|
source += "&key=AmsPZA-zRt2iuIdQgvXZIxme2gWcgLaz7igOUy7VPB8OKjjEd373eCnj1KFv2CqX"
|
||||||
|
|
||||||
|
response = requests.get(source)
|
||||||
|
ans = response.text
|
||||||
|
|
||||||
|
s = json.loads(ans)
|
||||||
|
print(s)
|
||||||
|
res = s['resourceSets'][0]['resources'][0]['elevations']
|
||||||
|
for elevation in res:
|
||||||
|
easting, northing, _, _ = latlon_to_utm(lat, lng)
|
||||||
|
v = FreeCAD.Vector(
|
||||||
|
round(easting * 1000, 0),
|
||||||
|
round(northing * 1000, 0),
|
||||||
|
round(elevation * 1000, 0))
|
||||||
|
return v
|
||||||
|
|
||||||
|
def getGridElevationFromBing(polygon, lat, lng, resolution = 1000):
|
||||||
|
import math
|
||||||
|
import requests
|
||||||
|
from lib.projection import latlon_to_utm, utm_to_latlon
|
||||||
|
|
||||||
|
_, _, zone_number, zone_letter = latlon_to_utm(lat, lng)
|
||||||
|
|
||||||
|
points = []
|
||||||
|
yy = polygon.Shape.BoundBox.YMax
|
||||||
|
while yy > polygon.Shape.BoundBox.YMin:
|
||||||
|
xx = polygon.Shape.BoundBox.XMin
|
||||||
|
while xx < polygon.Shape.BoundBox.XMax:
|
||||||
|
StepsXX = int(math.ceil((polygon.Shape.BoundBox.XMax - xx) / resolution))
|
||||||
|
|
||||||
|
if StepsXX > 1000:
|
||||||
|
StepsXX = 1000
|
||||||
|
xx1 = xx + 1000 * resolution
|
||||||
|
else:
|
||||||
|
xx1 = xx + StepsXX * resolution
|
||||||
|
|
||||||
|
point1 = utm_to_latlon(xx / 1000, yy / 1000, zone_number, zone_letter)
|
||||||
|
point2 = utm_to_latlon(xx1 / 1000, yy / 1000, zone_number, zone_letter)
|
||||||
|
|
||||||
|
source = "http://dev.virtualearth.net/REST/v1/Elevation/Polyline?points="
|
||||||
|
source += "{lat1},{lng1}".format(lat1=point1[0], lng1=point1[1])
|
||||||
|
source += ","
|
||||||
|
source += "{lat2},{lng2}".format(lat2=point2[0], lng2=point2[1])
|
||||||
|
source += "&heights=sealevel"
|
||||||
|
source += "&samples={steps}".format(steps=StepsXX)
|
||||||
|
source += "&key=AmsPZA-zRt2iuIdQgvXZIxme2gWcgLaz7igOUy7VPB8OKjjEd373eCnj1KFv2CqX"
|
||||||
|
|
||||||
|
response = requests.get(source)
|
||||||
|
ans = response.text
|
||||||
|
|
||||||
|
s = json.loads(ans)
|
||||||
|
res = s['resourceSets'][0]['resources'][0]['elevations']
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
for elevation in res:
|
||||||
|
v = FreeCAD.Vector(xx + resolution * i, yy, round(elevation * 1000, 4))
|
||||||
|
points.append(v)
|
||||||
|
i += 1
|
||||||
|
xx = xx1 + resolution
|
||||||
|
yy -= resolution
|
||||||
|
|
||||||
|
return points
|
||||||
|
|
||||||
|
def getSinglePointElevation(lat, lon):
|
||||||
|
source = "https://maps.googleapis.com/maps/api/elevation/json?locations="
|
||||||
|
source += str(lat) + "," + str(lon)
|
||||||
|
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
||||||
|
#print (source)
|
||||||
|
|
||||||
|
#response = request.urlopen(source)
|
||||||
|
#ans = response.read()
|
||||||
|
import requests
|
||||||
|
response = requests.get(source)
|
||||||
|
ans = response.text
|
||||||
|
|
||||||
|
# +# to do: error handling - wait and try again
|
||||||
|
s = json.loads(ans)
|
||||||
|
res = s['results']
|
||||||
|
|
||||||
|
from geopy.distance import geodesic
|
||||||
|
for r in res:
|
||||||
|
|
||||||
|
reference = (0.0, 0.0)
|
||||||
|
v = FreeCAD.Vector(
|
||||||
|
round(geodesic(reference, (0.0, r['location']['lng'])).m, 2),
|
||||||
|
round(geodesic(reference, (r['location']['lat'], 0.0)).m, 2),
|
||||||
|
round(r['elevation'] * 1000, 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
return v
|
||||||
|
|
||||||
|
def _getSinglePointElevation(lat, lon):
|
||||||
|
source = "https://maps.googleapis.com/maps/api/elevation/json?locations="
|
||||||
|
source += str(lat) + "," + str(lon)
|
||||||
|
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
||||||
|
#print (source)
|
||||||
|
|
||||||
|
#response = request.urlopen(source)
|
||||||
|
#ans = response.read()
|
||||||
|
import requests
|
||||||
|
response = requests.get(source)
|
||||||
|
ans = response.text
|
||||||
|
|
||||||
|
# +# to do: error handling - wait and try again
|
||||||
|
s = json.loads(ans)
|
||||||
|
res = s['results']
|
||||||
|
|
||||||
|
import pymap3d as pm
|
||||||
|
for r in res:
|
||||||
|
x, y, z = pm.geodetic2ecef(round(r['location']['lng'], 2),
|
||||||
|
round(r['location']['lat'], 2),
|
||||||
|
0)
|
||||||
|
v = FreeCAD.Vector(x,y,z)
|
||||||
|
|
||||||
|
return v
|
||||||
|
|
||||||
|
def getSinglePointElevation1(lat, lon):
|
||||||
|
source = "https://maps.googleapis.com/maps/api/elevation/json?locations="
|
||||||
|
source += str(lat) + "," + str(lon)
|
||||||
|
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
||||||
|
|
||||||
|
#response = urllib.request.urlopen(source)
|
||||||
|
#ans = response.read()
|
||||||
|
import requests
|
||||||
|
response = requests.get(source)
|
||||||
|
ans = response.text
|
||||||
|
|
||||||
|
# +# to do: error handling - wait and try again
|
||||||
|
s = json.loads(ans)
|
||||||
|
res = s['results']
|
||||||
|
|
||||||
|
for r in res:
|
||||||
|
c = tm.fromGeographic(r['location']['lat'], r['location']['lng'])
|
||||||
|
v = FreeCAD.Vector(
|
||||||
|
round(c[0], 4),
|
||||||
|
round(c[1], 4),
|
||||||
|
round(r['elevation'] * 1000, 2)
|
||||||
|
)
|
||||||
|
return v
|
||||||
|
|
||||||
|
def getSinglePointElevationUtm(lat, lon):
|
||||||
|
import requests
|
||||||
|
from lib.projection import latlon_to_utm
|
||||||
|
|
||||||
|
source = "https://maps.googleapis.com/maps/api/elevation/json?locations="
|
||||||
|
source += str(lat) + "," + str(lon)
|
||||||
|
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
||||||
|
print(source)
|
||||||
|
|
||||||
|
response = requests.get(source)
|
||||||
|
ans = response.text
|
||||||
|
|
||||||
|
s = json.loads(ans)
|
||||||
|
res = s['results']
|
||||||
|
print(res)
|
||||||
|
|
||||||
|
for r in res:
|
||||||
|
easting, northing, _, _ = latlon_to_utm(r['location']['lat'], r['location']['lng'])
|
||||||
|
v = FreeCAD.Vector(
|
||||||
|
round(easting * 1000, 4),
|
||||||
|
round(northing * 1000, 4),
|
||||||
|
round(r['elevation'] * 1000, 2))
|
||||||
|
print(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
def getElevationUTM(polygon, lat, lng, resolution = 10000):
|
||||||
|
from lib.projection import latlon_to_utm, utm_to_latlon
|
||||||
|
|
||||||
|
_, _, zone_number, zone_letter = latlon_to_utm(lat, lng)
|
||||||
|
|
||||||
|
StepsXX = int((polygon.Shape.BoundBox.XMax - polygon.Shape.BoundBox.XMin) / (resolution*1000))
|
||||||
|
points = []
|
||||||
|
yy = polygon.Shape.BoundBox.YMax
|
||||||
|
while yy > polygon.Shape.BoundBox.YMin:
|
||||||
|
point1 = utm_to_latlon(polygon.Shape.BoundBox.XMin / 1000, yy / 1000, zone_number, zone_letter)
|
||||||
|
point2 = utm_to_latlon(polygon.Shape.BoundBox.XMax / 1000, yy / 1000, zone_number, zone_letter)
|
||||||
|
|
||||||
|
source = "https://maps.googleapis.com/maps/api/elevation/json?path="
|
||||||
|
source += "{a},{b}".format(a = point1[0], b = point1[1])
|
||||||
|
source += "|"
|
||||||
|
source += "{a},{b}".format(a = point2[0], b = point2[1])
|
||||||
|
source += "&samples={a}".format(a = StepsXX)
|
||||||
|
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
||||||
|
|
||||||
|
import requests
|
||||||
|
response = requests.get(source)
|
||||||
|
ans = response.text
|
||||||
|
|
||||||
|
s = json.loads(ans)
|
||||||
|
res = s['results']
|
||||||
|
|
||||||
|
for r in res:
|
||||||
|
easting, northing, _, _ = latlon_to_utm(r['location']['lat'], r['location']['lng'])
|
||||||
|
v = FreeCAD.Vector(
|
||||||
|
round(easting * 1000, 2),
|
||||||
|
round(northing * 1000, 2),
|
||||||
|
round(r['elevation'] * 1000, 2)
|
||||||
|
)
|
||||||
|
points.append(v)
|
||||||
|
yy -= (resolution*1000)
|
||||||
|
|
||||||
|
FreeCAD.activeDocument().recompute()
|
||||||
|
return points
|
||||||
|
|
||||||
|
def getElevation1(polygon,resolution=10):
|
||||||
|
|
||||||
|
StepsXX = int((polygon.Shape.BoundBox.XMax - polygon.Shape.BoundBox.XMin) / (resolution * 1000))
|
||||||
|
points = []
|
||||||
|
yy = polygon.Shape.BoundBox.YMax
|
||||||
|
while yy > polygon.Shape.BoundBox.YMin:
|
||||||
|
point1 = tm.toGeographic(polygon.Shape.BoundBox.XMin, yy)
|
||||||
|
point2 = tm.toGeographic(polygon.Shape.BoundBox.XMax, yy)
|
||||||
|
|
||||||
|
source = "https://maps.googleapis.com/maps/api/elevation/json?path="
|
||||||
|
source += "{a},{b}".format(a = point1[0], b = point1[1])
|
||||||
|
source += "|"
|
||||||
|
source += "{a},{b}".format(a = point2[0], b = point2[1])
|
||||||
|
source += "&samples={a}".format(a = StepsXX)
|
||||||
|
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
||||||
|
|
||||||
|
try:
|
||||||
|
#response = urllib.request.urlopen(source)
|
||||||
|
#ans = response.read()
|
||||||
|
import requests
|
||||||
|
response = requests.get(source)
|
||||||
|
ans = response.text
|
||||||
|
|
||||||
|
# +# to do: error handling - wait and try again
|
||||||
|
|
||||||
|
s = json.loads(ans)
|
||||||
|
res = s['results']
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
#points = []
|
||||||
|
for r in res:
|
||||||
|
c = tm.fromGeographic(r['location']['lat'], r['location']['lng'])
|
||||||
|
v = FreeCAD.Vector(
|
||||||
|
round(c[0], 2),
|
||||||
|
round(c[1], 2),
|
||||||
|
round(r['elevation'] * 1000, 2)
|
||||||
|
)
|
||||||
|
points.append(v)
|
||||||
|
|
||||||
|
FreeCAD.activeDocument().recompute()
|
||||||
|
yy -= (resolution*1000)
|
||||||
|
|
||||||
|
return points
|
||||||
|
|
||||||
|
## download the heights from google:
|
||||||
|
def getElevation(lat, lon, b=50.35, le=11.17, size=40):
|
||||||
|
#https://maps.googleapis.com/maps/api/elevation/json?path=36.578581,-118.291994|36.23998,-116.83171&samples=3&key=YOUR_API_KEY
|
||||||
|
#https://maps.googleapis.com/maps/api/elevation/json?locations=39.7391536,-104.9847034&key=YOUR_API_KEY
|
||||||
|
|
||||||
|
source = "https://maps.googleapis.com/maps/api/elevation/json?path="
|
||||||
|
source += str(b-size*0.001) + "," + str(le) + "|" + str(b+size*0.001) + "," + str(le)
|
||||||
|
source += "&samples=" + str(100)
|
||||||
|
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
||||||
|
|
||||||
|
response = urllib.request.urlopen(source)
|
||||||
|
ans = response.read()
|
||||||
|
|
||||||
|
# +# to do: error handling - wait and try again
|
||||||
|
s = json.loads(ans)
|
||||||
|
res = s['results']
|
||||||
|
|
||||||
|
from geopy.distance import geodesic
|
||||||
|
points = []
|
||||||
|
for r in res:
|
||||||
|
reference = (0.0, 0.0)
|
||||||
|
v = FreeCAD.Vector(
|
||||||
|
round(geodesic(reference, (0.0, r['location']['lat'])).m, 2),
|
||||||
|
round(geodesic(reference, (r['location']['lng'], 0.0)).m, 2),
|
||||||
|
round(r['elevation'] * 1000, 2) - baseheight
|
||||||
|
)
|
||||||
|
points.append(v)
|
||||||
|
|
||||||
|
line = Draft.makeWire(points, closed=False, face=False, support=None)
|
||||||
|
line.ViewObject.Visibility = False
|
||||||
|
#FreeCAD.activeDocument().recompute()
|
||||||
|
FreeCADGui.updateGui()
|
||||||
|
return FreeCAD.activeDocument().ActiveObject
|
||||||
|
|
||||||
|
class _ImportPointsTaskPanel:
|
||||||
|
|
||||||
|
def __init__(self, obj = None):
|
||||||
|
self.obj = None
|
||||||
|
self.Boundary = None
|
||||||
|
self.select = 0
|
||||||
|
self.filename = ""
|
||||||
|
|
||||||
|
# form:
|
||||||
|
self.form1 = FreeCADGui.PySideUic.loadUi(os.path.dirname(__file__) + "/PVPlantImportGrid.ui")
|
||||||
|
self.form1.radio1.toggled.connect(lambda: self.mainToggle(self.form1.radio1))
|
||||||
|
self.form1.radio2.toggled.connect(lambda: self.mainToggle(self.form1.radio2))
|
||||||
|
self.form1.radio1.setChecked(True) # << --------------Poner al final para que no dispare antes de crear los componentes a los que va a llamar
|
||||||
|
#self.form.buttonAdd.clicked.connect(self.add)
|
||||||
|
self.form1.buttonDEM.clicked.connect(self.openFileDEM)
|
||||||
|
|
||||||
|
self.form2 = FreeCADGui.PySideUic.loadUi(os.path.dirname(__file__) + "/PVPlantCreateTerrainMesh.ui")
|
||||||
|
#self.form2.buttonAdd.clicked.connect(self.add)
|
||||||
|
self.form2.buttonBoundary.clicked.connect(self.addBoundary)
|
||||||
|
|
||||||
|
|
||||||
|
#self.form = [self.form1, self.form2]
|
||||||
|
self.form = self.form1
|
||||||
|
|
||||||
|
''' future:
|
||||||
|
def retranslateUi(self, dialog):
|
||||||
|
self.form1.setWindowTitle("Configuracion del Rack")
|
||||||
|
self.labelModule.setText(QtGui.QApplication.translate("PVPlant", "Modulo:", None))
|
||||||
|
self.labelModuleLength.setText(QtGui.QApplication.translate("PVPlant", "Longitud:", None))
|
||||||
|
self.labelModuleWidth.setText(QtGui.QApplication.translate("PVPlant", "Ancho:", None))
|
||||||
|
self.labelModuleHeight.setText(QtGui.QApplication.translate("PVPlant", "Alto:", None))
|
||||||
|
self.labelModuleFrame.setText(QtGui.QApplication.translate("PVPlant", "Ancho del marco:", None))
|
||||||
|
self.labelModuleColor.setText(QtGui.QApplication.translate("PVPlant", "Color del modulo:", None))
|
||||||
|
self.labelModules.setText(QtGui.QApplication.translate("Arch", "Colocacion de los Modulos", None))
|
||||||
|
self.labelModuleOrientation.setText(QtGui.QApplication.translate("Arch", "Orientacion del modulo:", None))
|
||||||
|
self.labelModuleGapX.setText(QtGui.QApplication.translate("Arch", "Separacion Horizontal (mm):", None))
|
||||||
|
self.labelModuleGapY.setText(QtGui.QApplication.translate("Arch", "Separacion Vertical (mm):", None))
|
||||||
|
self.labelModuleRows.setText(QtGui.QApplication.translate("Arch", "Filas de modulos:", None))
|
||||||
|
self.labelModuleCols.setText(QtGui.QApplication.translate("Arch", "Columnas de modulos:", None))
|
||||||
|
self.labelRack.setText(QtGui.QApplication.translate("Arch", "Configuracion de la estructura", None))
|
||||||
|
self.labelRackType.setText(QtGui.QApplication.translate("Arch", "Tipo de estructura:", None))
|
||||||
|
self.labelLevel.setText(QtGui.QApplication.translate("Arch", "Nivel:", None))
|
||||||
|
self.labelOffset.setText(QtGui.QApplication.translate("Arch", "Offset", None))
|
||||||
|
'''
|
||||||
|
|
||||||
|
def add(self):
|
||||||
|
sel = FreeCADGui.Selection.getSelection()
|
||||||
|
if len(sel) > 0:
|
||||||
|
self.obj = sel[0]
|
||||||
|
self.lineEdit1.setText(self.obj.Label)
|
||||||
|
|
||||||
|
def addBoundary(self):
|
||||||
|
sel = FreeCADGui.Selection.getSelection()
|
||||||
|
if len(sel) > 0:
|
||||||
|
self.Boundary = sel[0]
|
||||||
|
self.form2.editBoundary.setText(self.Boundary.Label)
|
||||||
|
|
||||||
|
def openFileDEM(self):
|
||||||
|
filters = "Esri ASC (*.asc);;CSV (*.csv);;All files (*.*)"
|
||||||
|
filename = QtGui.QFileDialog.getOpenFileName(None,
|
||||||
|
"Open DEM,",
|
||||||
|
"",
|
||||||
|
filters)
|
||||||
|
self.filename = filename[0]
|
||||||
|
self.form1.editDEM.setText(filename[0])
|
||||||
|
|
||||||
|
def mainToggle(self, radiobox):
|
||||||
|
if radiobox is self.form1.radio1:
|
||||||
|
self.select = 0
|
||||||
|
self.form1.gbLocalFile.setVisible(True)
|
||||||
|
elif radiobox is self.form1.radio2:
|
||||||
|
self.select = 1
|
||||||
|
self.form1.gbLocalFile.setVisible(True)
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
from datetime import datetime
|
||||||
|
starttime = datetime.now()
|
||||||
|
|
||||||
|
site = PVPlantSite.get()
|
||||||
|
|
||||||
|
try:
|
||||||
|
PointGroups = FreeCAD.ActiveDocument.Point_Groups
|
||||||
|
except:
|
||||||
|
PointGroups = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Point_Groups')
|
||||||
|
PointGroups.Label = "Point Groups"
|
||||||
|
|
||||||
|
PointGroup = FreeCAD.ActiveDocument.addObject('Points::Feature', "Point_Group")
|
||||||
|
PointGroup.Label = "Land_Grid_Points"
|
||||||
|
FreeCAD.ActiveDocument.Point_Groups.addObject(PointGroup)
|
||||||
|
PointObject = PointGroup.Points.copy()
|
||||||
|
|
||||||
|
if self.select == 0: # Google or bing or ...
|
||||||
|
#for item in self.obj:
|
||||||
|
#if self.groupbox.isChecked:break
|
||||||
|
resol = FreeCAD.Units.Quantity(self.valueResolution.text()).Value
|
||||||
|
Site = FreeCAD.ActiveDocument.Site
|
||||||
|
pts = getGridElevationFromBing(self.obj, Site.Latitude, Site.Longitude, resol)
|
||||||
|
PointObject.addPoints(pts)
|
||||||
|
PointGroup.Points = PointObject
|
||||||
|
|
||||||
|
else:
|
||||||
|
if self.filename == "":
|
||||||
|
return
|
||||||
|
|
||||||
|
import Utils.importDEM as openDEM
|
||||||
|
if self.select == 1: # DEM.
|
||||||
|
import numpy as np
|
||||||
|
root, extension = os.path.splitext(self.filename)
|
||||||
|
if extension.lower() == ".asc":
|
||||||
|
x, y, datavals, cellsize, nodata_value = openDEM.openEsri(self.filename)
|
||||||
|
|
||||||
|
if self.Boundary:
|
||||||
|
inc_x = self.Boundary.Shape.BoundBox.XLength * 0.05
|
||||||
|
inc_y = self.Boundary.Shape.BoundBox.YLength * 0.05
|
||||||
|
|
||||||
|
min_x = 0
|
||||||
|
max_x = 0
|
||||||
|
|
||||||
|
comp = (self.Boundary.Shape.BoundBox.XMin - inc_x) / 1000
|
||||||
|
for i in range(nx):
|
||||||
|
if x[i] > comp:
|
||||||
|
min_x = i - 1
|
||||||
|
break
|
||||||
|
comp = (self.Boundary.Shape.BoundBox.XMax + inc_x) / 1000
|
||||||
|
for i in range(min_x, nx):
|
||||||
|
if x[i] > comp:
|
||||||
|
max_x = i
|
||||||
|
break
|
||||||
|
|
||||||
|
min_y = 0
|
||||||
|
max_y = 0
|
||||||
|
|
||||||
|
comp = (self.Boundary.Shape.BoundBox.YMax + inc_y) / 1000
|
||||||
|
for i in range(ny):
|
||||||
|
if y[i] < comp:
|
||||||
|
max_y = i
|
||||||
|
break
|
||||||
|
comp = (self.Boundary.Shape.BoundBox.YMin - inc_y) / 1000
|
||||||
|
for i in range(max_y, ny):
|
||||||
|
if y[i] < comp:
|
||||||
|
min_y = i
|
||||||
|
break
|
||||||
|
|
||||||
|
x = x[min_x:max_x]
|
||||||
|
y = y[max_y:min_y]
|
||||||
|
datavals = datavals[max_y:min_y, min_x:max_x]
|
||||||
|
|
||||||
|
pts = []
|
||||||
|
if True: # faster but more memory 46s - 4,25 gb
|
||||||
|
x, y = np.meshgrid(x, y)
|
||||||
|
xx = x.flatten()
|
||||||
|
yy = y.flatten()
|
||||||
|
zz = datavals.flatten()
|
||||||
|
x[:] = 0
|
||||||
|
y[:] = 0
|
||||||
|
datavals[:] = 0
|
||||||
|
|
||||||
|
pts = []
|
||||||
|
for i in range(0, len(xx)):
|
||||||
|
pts.append(FreeCAD.Vector(xx[i], yy[i], zz[i]) * 1000)
|
||||||
|
|
||||||
|
xx[:] = 0
|
||||||
|
yy[:] = 0
|
||||||
|
zz[:] = 0
|
||||||
|
|
||||||
|
else: # 51s 3,2 gb
|
||||||
|
createmesh = True
|
||||||
|
if createmesh:
|
||||||
|
import Part, Draft
|
||||||
|
|
||||||
|
lines=[]
|
||||||
|
for j in range(len(y)):
|
||||||
|
edges = []
|
||||||
|
for i in range(0, len(x) - 1):
|
||||||
|
ed = Part.makeLine(FreeCAD.Vector(x[i], y[j], datavals[j][i]) * 1000,
|
||||||
|
FreeCAD.Vector(x[i + 1], y[j], datavals[j][i + 1]) * 1000)
|
||||||
|
edges.append(ed)
|
||||||
|
|
||||||
|
#bspline = Draft.makeBSpline(pts)
|
||||||
|
#bspline.ViewObject.hide()
|
||||||
|
line = Part.Wire(edges)
|
||||||
|
lines.append(line)
|
||||||
|
|
||||||
|
'''
|
||||||
|
for i in range(0, len(bsplines), 100):
|
||||||
|
p = Part.makeLoft(bsplines[i:i + 100], False, False, False)
|
||||||
|
Part.show(p)
|
||||||
|
'''
|
||||||
|
p = Part.makeLoft(lines, False, True, False)
|
||||||
|
p = Part.Solid(p)
|
||||||
|
Part.show(p)
|
||||||
|
|
||||||
|
else:
|
||||||
|
pts = []
|
||||||
|
for j in range(ny):
|
||||||
|
for i in range(nx):
|
||||||
|
pts.append(FreeCAD.Vector(x[i], y[j], datavals[j][i]) * 1000)
|
||||||
|
|
||||||
|
elif extension.lower() == ".csv" or extension.lower() == ".txt": # x, y, z from gps
|
||||||
|
pts = openDEM.interpolatePoints(openDEM.openCSV(self.filename))
|
||||||
|
|
||||||
|
PointObject.addPoints(pts)
|
||||||
|
PointGroup.Points = PointObject
|
||||||
|
|
||||||
|
FreeCAD.ActiveDocument.recompute()
|
||||||
|
FreeCADGui.Control.closeDialog()
|
||||||
|
print("tiempo: ", datetime.now() - starttime)
|
||||||
|
|
||||||
|
def reject(self):
|
||||||
|
FreeCADGui.Control.closeDialog()
|
||||||
|
|
||||||
|
## Comandos -----------------------------------------------------------------------------------------------------------
|
||||||
|
class CommandImportPoints:
|
||||||
|
|
||||||
|
def GetResources(self):
|
||||||
|
return {'Pixmap': str(os.path.join(DirIcons, "cloud.svg")),
|
||||||
|
'MenuText': QT_TRANSLATE_NOOP("PVPlant", "Importer Grid"),
|
||||||
|
'Accel': "B, U",
|
||||||
|
'ToolTip': QT_TRANSLATE_NOOP("PVPlant", "Creates a cloud of points.")}
|
||||||
|
|
||||||
|
def IsActive(self):
|
||||||
|
return not FreeCAD.ActiveDocument is None
|
||||||
|
|
||||||
|
def Activated(self):
|
||||||
|
self.TaskPanel = _ImportPointsTaskPanel()
|
||||||
|
FreeCADGui.Control.showDialog(self.TaskPanel)
|
||||||
|
|
||||||
|
if FreeCAD.GuiUp:
|
||||||
|
class CommandPointsGroup:
|
||||||
|
|
||||||
|
def GetCommands(self):
|
||||||
|
return tuple(['ImportPoints'
|
||||||
|
])
|
||||||
|
def GetResources(self):
|
||||||
|
return { 'MenuText': QT_TRANSLATE_NOOP("",'Cloud of Points'),
|
||||||
|
'ToolTip': QT_TRANSLATE_NOOP("",'Cloud of Points')
|
||||||
|
}
|
||||||
|
def IsActive(self):
|
||||||
|
return not FreeCAD.ActiveDocument is None
|
||||||
|
|
||||||
|
FreeCADGui.addCommand('ImportPoints', CommandImportPoints())
|
||||||
|
FreeCADGui.addCommand('PointsGroup', CommandPointsGroup())
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
+5
-519
@@ -20,524 +20,10 @@
|
|||||||
# * *
|
# * *
|
||||||
# ***********************************************************************
|
# ***********************************************************************
|
||||||
|
|
||||||
import FreeCAD
|
"""
|
||||||
import utm
|
PVPlantGeoreferencing - Wrapper de compatibilidad.
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
Código movido a PVPlant/core/georef.py.
|
||||||
import FreeCADGui
|
"""
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
||||||
|
|
||||||
import os
|
from PVPlant.core.georef import MapWindow, CommandPVPlantGeoreferencing
|
||||||
else:
|
|
||||||
# \cond
|
|
||||||
def translate(ctxt,txt):
|
|
||||||
return txt
|
|
||||||
def QT_TRANSLATE_NOOP(ctxt,txt):
|
|
||||||
return txt
|
|
||||||
# \endcond
|
|
||||||
|
|
||||||
import PVPlantResources
|
|
||||||
from PVPlantResources import DirIcons as DirIcons
|
|
||||||
from PVPlantResources import DirResources as DirResources
|
|
||||||
|
|
||||||
|
|
||||||
class MapWindow(QtGui.QWidget):
|
|
||||||
def __init__(self, WinTitle="MapWindow"):
|
|
||||||
super(MapWindow, self).__init__()
|
|
||||||
self.raise_()
|
|
||||||
self.lat = None
|
|
||||||
self.lon = None
|
|
||||||
self.minLat = None
|
|
||||||
self.maxLat = None
|
|
||||||
self.minLon = None
|
|
||||||
self.maxLon = None
|
|
||||||
self.zoom = None
|
|
||||||
self.WinTitle = WinTitle
|
|
||||||
self.georeference_coordinates = {'lat': None, 'lon': None}
|
|
||||||
self.setupUi()
|
|
||||||
|
|
||||||
def setupUi(self):
|
|
||||||
from PySide2.QtWebEngineWidgets import QWebEngineView
|
|
||||||
from PySide2.QtWebChannel import QWebChannel
|
|
||||||
|
|
||||||
self.ui = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantGeoreferencing.ui", self)
|
|
||||||
|
|
||||||
self.resize(1200, 800)
|
|
||||||
self.setWindowTitle(self.WinTitle)
|
|
||||||
self.setWindowIcon(QtGui.QIcon(os.path.join(DirIcons, "Location.svg")))
|
|
||||||
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
|
|
||||||
|
|
||||||
self.layout = QtGui.QHBoxLayout(self)
|
|
||||||
self.layout.setContentsMargins(4, 4, 4, 4)
|
|
||||||
|
|
||||||
LeftWidget = QtGui.QWidget(self)
|
|
||||||
LeftLayout = QtGui.QVBoxLayout(LeftWidget)
|
|
||||||
LeftWidget.setLayout(LeftLayout)
|
|
||||||
LeftLayout.setContentsMargins(0, 0, 0, 0)
|
|
||||||
|
|
||||||
RightWidget = QtGui.QWidget(self)
|
|
||||||
RightWidget.setFixedWidth(350)
|
|
||||||
RightLayout = QtGui.QVBoxLayout(RightWidget)
|
|
||||||
RightWidget.setLayout(RightLayout)
|
|
||||||
RightLayout.setContentsMargins(0, 0, 0, 0)
|
|
||||||
|
|
||||||
self.layout.addWidget(LeftWidget)
|
|
||||||
self.layout.addWidget(RightWidget)
|
|
||||||
|
|
||||||
# Left Widgets:
|
|
||||||
# -- Search Bar:
|
|
||||||
self.valueSearch = QtGui.QLineEdit(self)
|
|
||||||
self.valueSearch.setPlaceholderText("Search")
|
|
||||||
self.valueSearch.returnPressed.connect(self.onSearch)
|
|
||||||
|
|
||||||
searchbutton = QtGui.QPushButton('Search')
|
|
||||||
searchbutton.setFixedWidth(80)
|
|
||||||
searchbutton.clicked.connect(self.onSearch)
|
|
||||||
|
|
||||||
SearchBarLayout = QtGui.QHBoxLayout(self)
|
|
||||||
SearchBarLayout.addWidget(self.valueSearch)
|
|
||||||
SearchBarLayout.addWidget(searchbutton)
|
|
||||||
LeftLayout.addLayout(SearchBarLayout)
|
|
||||||
|
|
||||||
# -- Webbroser:
|
|
||||||
self.view = QWebEngineView()
|
|
||||||
self.channel = QWebChannel(self.view.page())
|
|
||||||
self.view.page().setWebChannel(self.channel)
|
|
||||||
self.channel.registerObject("MyApp", self)
|
|
||||||
file = os.path.join(DirResources, "webs", "main.html")
|
|
||||||
self.view.page().loadFinished.connect(self.onLoadFinished)
|
|
||||||
self.view.page().load(QtCore.QUrl.fromLocalFile(file))
|
|
||||||
LeftLayout.addWidget(self.view)
|
|
||||||
# self.layout.addWidget(self.view, 1, 0, 1, 3)
|
|
||||||
|
|
||||||
# -- Latitud y longitud:
|
|
||||||
self.labelCoordinates = QtGui.QLabel()
|
|
||||||
self.labelCoordinates.setFixedHeight(21)
|
|
||||||
LeftLayout.addWidget(self.labelCoordinates)
|
|
||||||
# self.layout.addWidget(self.labelCoordinates, 2, 0, 1, 3)
|
|
||||||
|
|
||||||
# Right Widgets:
|
|
||||||
labelKMZ = QtGui.QLabel()
|
|
||||||
labelKMZ.setText("Cargar un archivo KMZ/KML:")
|
|
||||||
self.kmlButton = QtGui.QPushButton()
|
|
||||||
self.kmlButton.setFixedSize(32, 32)
|
|
||||||
self.kmlButton.setIcon(QtGui.QIcon(os.path.join(DirIcons, "googleearth.svg")))
|
|
||||||
widget = QtGui.QWidget(self)
|
|
||||||
layout = QtGui.QHBoxLayout(widget)
|
|
||||||
widget.setLayout(layout)
|
|
||||||
layout.addWidget(labelKMZ)
|
|
||||||
layout.addWidget(self.kmlButton)
|
|
||||||
RightLayout.addWidget(widget)
|
|
||||||
|
|
||||||
# -----------------------
|
|
||||||
self.groupbox = QtGui.QGroupBox("Importar datos desde:")
|
|
||||||
self.groupbox.setCheckable(True)
|
|
||||||
self.groupbox.setChecked(True)
|
|
||||||
radio1 = QtGui.QRadioButton("Google Elevation")
|
|
||||||
radio2 = QtGui.QRadioButton("Nube de Puntos")
|
|
||||||
radio3 = QtGui.QRadioButton("Datos GPS")
|
|
||||||
radio1.setChecked(True)
|
|
||||||
|
|
||||||
# buttonDialog = QtGui.QPushButton('...')
|
|
||||||
# buttonDialog.setEnabled(False)
|
|
||||||
|
|
||||||
vbox = QtGui.QVBoxLayout(self)
|
|
||||||
vbox.addWidget(radio1)
|
|
||||||
vbox.addWidget(radio2)
|
|
||||||
vbox.addWidget(radio3)
|
|
||||||
|
|
||||||
self.groupbox.setLayout(vbox)
|
|
||||||
RightLayout.addWidget(self.groupbox)
|
|
||||||
# ------------------------
|
|
||||||
|
|
||||||
self.checkboxImportGis = QtGui.QCheckBox("Importar datos GIS")
|
|
||||||
RightLayout.addWidget(self.checkboxImportGis)
|
|
||||||
|
|
||||||
self.checkboxImportSatelitalImagen = QtGui.QCheckBox("Importar Imagen Satelital")
|
|
||||||
RightLayout.addWidget(self.checkboxImportSatelitalImagen)
|
|
||||||
|
|
||||||
verticalSpacer = QtGui.QSpacerItem(20, 48, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
|
||||||
RightLayout.addItem(verticalSpacer)
|
|
||||||
|
|
||||||
self.bAccept = QtGui.QPushButton('Accept')
|
|
||||||
self.bAccept.clicked.connect(self.onAcceptClick)
|
|
||||||
RightLayout.addWidget(self.bAccept)
|
|
||||||
|
|
||||||
# signals/slots
|
|
||||||
QtCore.QObject.connect(self.kmlButton, QtCore.SIGNAL("clicked()"), self.importKML)
|
|
||||||
|
|
||||||
|
|
||||||
def onLoadFinished(self):
|
|
||||||
file = os.path.join(DirResources, "webs", "map.js")
|
|
||||||
frame = self.view.page()
|
|
||||||
with open(file, 'r') as f:
|
|
||||||
frame.runJavaScript(f.read())
|
|
||||||
|
|
||||||
def onSearch(self):
|
|
||||||
if self.valueSearch.text() == "":
|
|
||||||
return
|
|
||||||
|
|
||||||
from geopy.geocoders import Nominatim
|
|
||||||
|
|
||||||
geolocator = Nominatim(user_agent="http")
|
|
||||||
location = geolocator.geocode(self.valueSearch.text())
|
|
||||||
self.valueSearch.setText(location.address)
|
|
||||||
self.panMap(location.longitude, location.latitude, location.raw['boundingbox'])
|
|
||||||
|
|
||||||
def onAcceptClick(self):
|
|
||||||
frame = self.view.page()
|
|
||||||
# 1. georeferenciar
|
|
||||||
frame.runJavaScript(
|
|
||||||
"MyApp.georeference(drawnItems.getBounds().getCenter().lat, drawnItems.getBounds().getCenter().lng);"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 2. importar todos los elementos dibujados:
|
|
||||||
frame.runJavaScript(
|
|
||||||
"var data = drawnItems.toGeoJSON();"
|
|
||||||
"MyApp.shapes(JSON.stringify(data));"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
@QtCore.Slot(float, float)
|
|
||||||
def onMapMove(self, lat, lng):
|
|
||||||
self.lat = lat
|
|
||||||
self.lon = lng
|
|
||||||
x, y, zone_number, zone_letter = utm.from_latlon(lat, lng)
|
|
||||||
self.labelCoordinates.setText('Longitud: {:.5f}, Latitud: {:.5f}'.format(lng, lat) +
|
|
||||||
' | UTM: ' + str(zone_number) + zone_letter +
|
|
||||||
', {:.5f}m E, {:.5f}m N'.format(x, y))
|
|
||||||
|
|
||||||
@QtCore.Slot(float, float, float, float, int)
|
|
||||||
def onMapZoom(self, minLat, minLon, maxLat, maxLon, zoom):
|
|
||||||
self.minLat = min([minLat, maxLat])
|
|
||||||
self.maxLat = max([minLat, maxLat])
|
|
||||||
self.minLon = min([minLon, maxLon])
|
|
||||||
self.maxLon = max([minLon, maxLon])
|
|
||||||
self.zoom = zoom
|
|
||||||
|
|
||||||
@QtCore.Slot(float, float)
|
|
||||||
def georeference(self, lat, lng):
|
|
||||||
import PVPlantSite
|
|
||||||
from geopy.geocoders import Nominatim
|
|
||||||
|
|
||||||
self.georeference_coordinates['lat'] = lat
|
|
||||||
self.georeference_coordinates['lon'] = lng
|
|
||||||
|
|
||||||
Site = PVPlantSite.get(create=True)
|
|
||||||
Site.Proxy.setLatLon(lat, lng)
|
|
||||||
|
|
||||||
geolocator = Nominatim(user_agent="http")
|
|
||||||
location = geolocator.reverse('{:.5f}, {:.5f}'.format(lat, lng))
|
|
||||||
if location:
|
|
||||||
if location.raw["address"].get("road"):
|
|
||||||
str = location.raw["address"]["road"]
|
|
||||||
if location.raw["address"].get("house_number"):
|
|
||||||
str += ' ({0})'.format(location.raw["address"]["house_number"])
|
|
||||||
Site.Address = str
|
|
||||||
if location.raw["address"].get("city"):
|
|
||||||
Site.City = location.raw["address"]["city"]
|
|
||||||
if location.raw["address"].get("postcode"):
|
|
||||||
Site.PostalCode = location.raw["address"]["postcode"]
|
|
||||||
if location.raw["address"].get("address"):
|
|
||||||
Site.Region = '{0}'.format(location.raw["address"]["province"])
|
|
||||||
if location.raw["address"].get("state"):
|
|
||||||
if Site.Region != "":
|
|
||||||
Site.Region += " - "
|
|
||||||
Site.Region += '{0}'.format(location.raw["address"]["state"]) # province - state
|
|
||||||
Site.Country = location.raw["address"]["country"]
|
|
||||||
|
|
||||||
@QtCore.Slot(str)
|
|
||||||
def shapes(self, drawnItems):
|
|
||||||
import geojson
|
|
||||||
import PVPlantImportGrid as ImportElevation
|
|
||||||
import Draft
|
|
||||||
import PVPlantSite
|
|
||||||
Site = PVPlantSite.get()
|
|
||||||
|
|
||||||
offset = FreeCAD.Vector(0, 0, 0)
|
|
||||||
if not (self.lat is None or self.lon is None):
|
|
||||||
offset = FreeCAD.Vector(Site.Origin)
|
|
||||||
offset.z = 0
|
|
||||||
|
|
||||||
items = geojson.loads(drawnItems)
|
|
||||||
for item in items['features']:
|
|
||||||
if item['geometry']['type'] == "Point": # 1. if the feature is a Point or Circle:
|
|
||||||
coord = item['geometry']['coordinates']
|
|
||||||
point = ImportElevation.getElevationFromOE([[coord[0], coord[1]],])
|
|
||||||
c = FreeCAD.Vector(point[0][0], point[0][1], point[0][2]).sub(offset)
|
|
||||||
if item['properties'].get('radius'):
|
|
||||||
r = round(item['properties']['radius'] * 1000, 0)
|
|
||||||
p = FreeCAD.Placement()
|
|
||||||
p.Base = c
|
|
||||||
obj = Draft.makeCircle(r, placement=p, face=False)
|
|
||||||
else:
|
|
||||||
''' do something '''
|
|
||||||
obj = Draft.make_point(c * 1000, color=(0.5, 0.3, 0.6), point_size=10)
|
|
||||||
else: # 2. if the feature is a Polygon or Line:
|
|
||||||
cw = False
|
|
||||||
name = "Línea"
|
|
||||||
lp = item['geometry']['coordinates']
|
|
||||||
if item['geometry']['type'] == "Polygon":
|
|
||||||
cw = True
|
|
||||||
name = "Area"
|
|
||||||
lp = item['geometry']['coordinates'][0]
|
|
||||||
|
|
||||||
pts = [[cords[1], cords[0]] for cords in lp]
|
|
||||||
tmp = ImportElevation.getElevationFromOE(pts)
|
|
||||||
pts = [p.sub(offset) for p in tmp]
|
|
||||||
|
|
||||||
obj = Draft.makeWire(pts, closed=cw, face=False)
|
|
||||||
#obj.Placement.Base = Site.Origin
|
|
||||||
obj.Label = name
|
|
||||||
Draft.autogroup(obj)
|
|
||||||
|
|
||||||
if item['properties'].get('name'):
|
|
||||||
obj.Label = item['properties']['name']
|
|
||||||
|
|
||||||
if self.checkboxImportGis.isChecked():
|
|
||||||
self.getDataFromOSM(self.minLat, self.minLon, self.maxLat, self.maxLon)
|
|
||||||
|
|
||||||
if self.checkboxImportSatelitalImagen.isChecked():
|
|
||||||
# Usar los límites reales del terreno (rectangular)
|
|
||||||
'''s_lat = self.minLat
|
|
||||||
s_lon = self.minLon
|
|
||||||
n_lat = self.maxLat
|
|
||||||
n_lon = self.maxLon
|
|
||||||
|
|
||||||
# Obtener puntos UTM para las esquinas
|
|
||||||
corners = ImportElevation.getElevationFromOE([
|
|
||||||
[s_lat, s_lon], # Esquina suroeste
|
|
||||||
[n_lat, s_lon], # Esquina sureste
|
|
||||||
[n_lat, n_lon], # Esquina noreste
|
|
||||||
[s_lat, n_lon] # Esquina noroeste
|
|
||||||
])
|
|
||||||
|
|
||||||
if not corners or len(corners) < 4:
|
|
||||||
FreeCAD.Console.PrintError("Error obteniendo elevaciones para las esquinas\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Descargar imagen satelital
|
|
||||||
from lib.GoogleSatelitalImageDownload import GoogleMapDownloader
|
|
||||||
downloader = GoogleMapDownloader(
|
|
||||||
zoom= 18, #self.zoom,
|
|
||||||
layer='raw_satellite'
|
|
||||||
)
|
|
||||||
img = downloader.generateImage(
|
|
||||||
sw_lat=s_lat,
|
|
||||||
sw_lng=s_lon,
|
|
||||||
ne_lat=n_lat,
|
|
||||||
ne_lng=n_lon
|
|
||||||
)
|
|
||||||
|
|
||||||
# Guardar imagen en el directorio del documento
|
|
||||||
doc_path = os.path.dirname(FreeCAD.ActiveDocument.FileName) if FreeCAD.ActiveDocument.FileName else ""
|
|
||||||
if not doc_path:
|
|
||||||
doc_path = FreeCAD.ConfigGet("UserAppData")
|
|
||||||
|
|
||||||
filename = os.path.join(doc_path, "background.jpeg")
|
|
||||||
img.save(filename)
|
|
||||||
|
|
||||||
ancho, alto = img.size
|
|
||||||
|
|
||||||
# Crear objeto de imagen en FreeCAD
|
|
||||||
doc = FreeCAD.ActiveDocument
|
|
||||||
img_obj = doc.addObject('Image::ImagePlane', 'Background')
|
|
||||||
img_obj.ImageFile = filename
|
|
||||||
img_obj.Label = 'Background'
|
|
||||||
|
|
||||||
# Calcular dimensiones en metros usando las coordenadas UTM
|
|
||||||
# Extraer las coordenadas de las esquinas
|
|
||||||
sw = corners[0] # Suroeste
|
|
||||||
se = corners[1] # Sureste
|
|
||||||
ne = corners[2] # Noreste
|
|
||||||
nw = corners[3] # Noroeste
|
|
||||||
|
|
||||||
# Calcular ancho (promedio de los lados superior e inferior)
|
|
||||||
width_bottom = se.x - sw.x
|
|
||||||
width_top = ne.x - nw.x
|
|
||||||
width_m = (width_bottom + width_top) / 2
|
|
||||||
|
|
||||||
# Calcular alto (promedio de los lados izquierdo y derecho)
|
|
||||||
height_left = nw.y - sw.y
|
|
||||||
height_right = ne.y - se.y
|
|
||||||
height_m = (height_left + height_right) / 2
|
|
||||||
|
|
||||||
img_obj.XSize = width_m
|
|
||||||
img_obj.YSize = height_m
|
|
||||||
|
|
||||||
# Posicionar el centro de la imagen en (0,0,0)
|
|
||||||
img_obj.Placement.Base = FreeCAD.Vector(-width_m / 2, -height_m / 2, 0)'''
|
|
||||||
|
|
||||||
# Definir área rectangular
|
|
||||||
s_lat = self.minLat
|
|
||||||
s_lon = self.minLon
|
|
||||||
n_lat = self.maxLat
|
|
||||||
n_lon = self.maxLon
|
|
||||||
|
|
||||||
# Obtener puntos UTM para las esquinas y el punto de referencia
|
|
||||||
points = [
|
|
||||||
[s_lat, s_lon], # Suroeste
|
|
||||||
[n_lat, n_lon], # Noreste
|
|
||||||
[self.georeference_coordinates['lat'], self.georeference_coordinates['lon']] # Punto de referencia
|
|
||||||
]
|
|
||||||
utm_points = ImportElevation.getElevationFromOE(points)
|
|
||||||
|
|
||||||
if not utm_points or len(utm_points) < 3:
|
|
||||||
FreeCAD.Console.PrintError("Error obteniendo elevaciones para las esquinas y referencia\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
sw_utm, ne_utm, ref_utm = utm_points
|
|
||||||
|
|
||||||
# Descargar imagen satelital
|
|
||||||
from lib.GoogleSatelitalImageDownload import GoogleMapDownloader
|
|
||||||
downloader = GoogleMapDownloader(
|
|
||||||
zoom=self.zoom,
|
|
||||||
layer='raw_satellite'
|
|
||||||
)
|
|
||||||
img = downloader.generateImage(
|
|
||||||
sw_lat=s_lat,
|
|
||||||
sw_lng=s_lon,
|
|
||||||
ne_lat=n_lat,
|
|
||||||
ne_lng=n_lon
|
|
||||||
)
|
|
||||||
|
|
||||||
# Guardar imagen
|
|
||||||
doc_path = os.path.dirname(FreeCAD.ActiveDocument.FileName) if FreeCAD.ActiveDocument.FileName else ""
|
|
||||||
if not doc_path:
|
|
||||||
doc_path = FreeCAD.ConfigGet("UserAppData")
|
|
||||||
|
|
||||||
filename = os.path.join(doc_path, "background.jpeg")
|
|
||||||
img.save(filename)
|
|
||||||
|
|
||||||
# Calcular dimensiones reales en metros
|
|
||||||
width_m = ne_utm.x - sw_utm.x # Ancho en metros (este-oeste)
|
|
||||||
height_m = ne_utm.y - sw_utm.y # Alto en metros (norte-sur)
|
|
||||||
|
|
||||||
# Calcular posición relativa del punto de referencia dentro de la imagen
|
|
||||||
rel_x = (ref_utm.x - sw_utm.x) / width_m if width_m != 0 else 0.5
|
|
||||||
rel_y = (ref_utm.y - sw_utm.y) / height_m if height_m != 0 else 0.5
|
|
||||||
|
|
||||||
# Crear objeto de imagen en FreeCAD
|
|
||||||
doc = FreeCAD.ActiveDocument
|
|
||||||
img_obj = doc.addObject('Image::ImagePlane', 'Background')
|
|
||||||
img_obj.ImageFile = filename
|
|
||||||
img_obj.Label = 'Background'
|
|
||||||
|
|
||||||
# Convertir dimensiones a milímetros (FreeCAD trabaja en mm)
|
|
||||||
img_obj.XSize = width_m * 1000
|
|
||||||
img_obj.YSize = height_m * 1000
|
|
||||||
|
|
||||||
# Posicionar para que el punto de referencia esté en (0,0,0)
|
|
||||||
# La esquina inferior izquierda debe estar en:
|
|
||||||
# x = -rel_x * ancho_total
|
|
||||||
# y = -rel_y * alto_total
|
|
||||||
img_obj.Placement.Base = FreeCAD.Vector(
|
|
||||||
-rel_x * width_m * 1000,
|
|
||||||
-rel_y * height_m * 1000,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
|
|
||||||
# Refrescar el documento
|
|
||||||
doc.recompute()
|
|
||||||
|
|
||||||
def calculate_texture_transform(self, mesh_obj, width_m, height_m):
|
|
||||||
"""Calcula la transformación precisa para la textura"""
|
|
||||||
try:
|
|
||||||
# Obtener coordenadas reales de las esquinas
|
|
||||||
import utm
|
|
||||||
sw = utm.from_latlon(self.minLat, self.minLon)
|
|
||||||
ne = utm.from_latlon(self.maxLat, self.maxLon)
|
|
||||||
|
|
||||||
# Crear matriz de transformación
|
|
||||||
scale_x = (ne[0] - sw[0]) / width_m
|
|
||||||
scale_y = (ne[1] - sw[1]) / height_m
|
|
||||||
|
|
||||||
# Aplicar transformación (solo si se usa textura avanzada)
|
|
||||||
if hasattr(mesh_obj.ViewObject, "TextureMapping"):
|
|
||||||
mesh_obj.ViewObject.TextureMapping = "PLANE"
|
|
||||||
mesh_obj.ViewObject.TextureScale = (scale_x, scale_y)
|
|
||||||
mesh_obj.ViewObject.TextureOffset = (sw[0], sw[1])
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
FreeCAD.Console.PrintWarning(f"No se pudo calcular transformación: {str(e)}\n")
|
|
||||||
|
|
||||||
def getDataFromOSM(self, min_lat, min_lon, max_lat, max_lon):
|
|
||||||
import Importer.importOSM as importOSM
|
|
||||||
import PVPlantSite
|
|
||||||
site = PVPlantSite.get()
|
|
||||||
|
|
||||||
offset = FreeCAD.Vector(0, 0, 0)
|
|
||||||
if not (self.lat is None or self.lon is None):
|
|
||||||
offset = FreeCAD.Vector(site.Origin)
|
|
||||||
offset.z = 0
|
|
||||||
importer = importOSM.OSMImporter(offset)
|
|
||||||
osm_data = importer.get_osm_data(f"{min_lat},{min_lon},{max_lat},{max_lon}")
|
|
||||||
importer.process_osm_data(osm_data)
|
|
||||||
|
|
||||||
'''FreeCAD.activeDocument().recompute()
|
|
||||||
FreeCADGui.updateGui()
|
|
||||||
FreeCADGui.SendMsgToActiveView("ViewFit")'''
|
|
||||||
|
|
||||||
def panMap_old(self, lng, lat, geometry=""):
|
|
||||||
frame = self.view.page()
|
|
||||||
bbox = "[{0}, {1}], [{2}, {3}]".format(float(geometry[0]), float(geometry[2]),
|
|
||||||
float(geometry[1]), float(geometry[3]))
|
|
||||||
command = 'map.panTo(L.latLng({lt}, {lg}));'.format(lt=lat, lg=lng)
|
|
||||||
command += 'map.fitBounds([{box}]);'.format(box=bbox)
|
|
||||||
frame.runJavaScript(command)
|
|
||||||
|
|
||||||
# deepseek
|
|
||||||
def panMap(self, lng, lat, geometry=None):
|
|
||||||
frame = self.view.page()
|
|
||||||
|
|
||||||
# 1. Validación del parámetro geometry
|
|
||||||
if not geometry or len(geometry) < 4:
|
|
||||||
# Pan básico sin ajuste de bounds
|
|
||||||
command = f'map.panTo(L.latLng({lat}, {lng}));'
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
# 2. Mejor manejo de coordenadas (Leaflet usa [lat, lng])
|
|
||||||
# Asumiendo que geometry es [min_lng, min_lat, max_lng, max_lat]
|
|
||||||
southwest = f"{float(geometry[1])}, {float(geometry[0])}" # min_lat, min_lng
|
|
||||||
northeast = f"{float(geometry[3])}, {float(geometry[2])}" # max_lat, max_lng
|
|
||||||
command = f'map.panTo(L.latLng({lat}, {lng}));'
|
|
||||||
command += f'map.fitBounds(L.latLngBounds([{southwest}], [{northeast}]));'
|
|
||||||
except (IndexError, ValueError, TypeError) as e:
|
|
||||||
print(f"Error en geometry: {str(e)}")
|
|
||||||
command = f'map.panTo(L.latLng({lat}, {lng}));'
|
|
||||||
frame.runJavaScript(command)
|
|
||||||
|
|
||||||
def importKML(self):
|
|
||||||
file = QtGui.QFileDialog.getOpenFileName(None, "FileDialog", "", "Google Earth (*.kml *.kmz)")[0]
|
|
||||||
|
|
||||||
from lib.kml2geojson import kmz_convert
|
|
||||||
layers = kmz_convert(file, "", )
|
|
||||||
frame = self.view.page()
|
|
||||||
for layer in layers:
|
|
||||||
command = "var geoJsonLayer = L.geoJSON({0}); drawnItems.addLayer(geoJsonLayer); map.fitBounds(geoJsonLayer.getBounds());".format( layer)
|
|
||||||
frame.runJavaScript(command)
|
|
||||||
|
|
||||||
|
|
||||||
class CommandPVPlantGeoreferencing:
|
|
||||||
|
|
||||||
def GetResources(self):
|
|
||||||
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")}
|
|
||||||
|
|
||||||
def Activated(self):
|
|
||||||
self.form = MapWindow()
|
|
||||||
self.form.show()
|
|
||||||
|
|
||||||
def IsActive(self):
|
|
||||||
if FreeCAD.ActiveDocument:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
'''if FreeCAD.GuiUp:
|
|
||||||
FreeCADGui.addCommand('PVPlantGeoreferencing',_CommandPVPlantGeoreferencing())
|
|
||||||
'''
|
|
||||||
+17
-666
@@ -20,673 +20,24 @@
|
|||||||
# * *
|
# * *
|
||||||
# ***********************************************************************
|
# ***********************************************************************
|
||||||
|
|
||||||
import json
|
|
||||||
import urllib.request
|
|
||||||
|
|
||||||
import Draft
|
|
||||||
import FreeCAD
|
|
||||||
import FreeCADGui
|
|
||||||
from PySide import QtCore, QtGui
|
|
||||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
||||||
|
|
||||||
try:
|
|
||||||
_fromUtf8 = QtCore.QString.fromUtf8
|
|
||||||
except AttributeError:
|
|
||||||
def _fromUtf8(s):
|
|
||||||
return s
|
|
||||||
|
|
||||||
import os
|
|
||||||
from PVPlantResources import DirIcons as DirIcons
|
|
||||||
import PVPlantSite
|
|
||||||
|
|
||||||
|
|
||||||
def get_elevation_from_oe(coordinates): # v1 deepseek
|
|
||||||
"""Obtiene elevaciones de Open-Elevation API y devuelve vectores FreeCAD en coordenadas UTM.
|
|
||||||
Args:
|
|
||||||
coordinates (list): Lista de tuplas con coordenadas (latitud, longitud)
|
|
||||||
Returns:
|
|
||||||
list: Lista de vectores FreeCAD con coordenadas UTM y elevación (en milímetros)
|
|
||||||
o lista vacía en caso de error.
|
|
||||||
"""
|
"""
|
||||||
if not coordinates:
|
PVPlantImportGrid - Wrapper de compatibilidad.
|
||||||
return []
|
|
||||||
|
|
||||||
import requests
|
Código movido a PVPlant/import_grid/grid.py.
|
||||||
import utm
|
"""
|
||||||
from requests.exceptions import RequestException
|
|
||||||
|
|
||||||
# Construcción más eficiente de parámetros
|
from PVPlant.import_grid.grid import (
|
||||||
locations = "|".join([f"{lat:.6f},{lon:.6f}" for lat, lon in coordinates])
|
get_elevation_from_oe,
|
||||||
|
getElevationFromOE,
|
||||||
try:
|
getSinglePointElevationFromBing,
|
||||||
response = requests.get(
|
getGridElevationFromBing,
|
||||||
url="https://api.open-elevation.com/api/v1/lookup",
|
getSinglePointElevation,
|
||||||
params={'locations': locations},
|
_getSinglePointElevation,
|
||||||
timeout=20,
|
getSinglePointElevation1,
|
||||||
verify=True
|
getSinglePointElevationUtm,
|
||||||
|
getElevationUTM,
|
||||||
|
getElevation1,
|
||||||
|
getElevation,
|
||||||
|
_ImportPointsTaskPanel,
|
||||||
|
CommandImportPoints,
|
||||||
)
|
)
|
||||||
response.raise_for_status() # Lanza excepción para códigos 4xx/5xx
|
|
||||||
|
|
||||||
except RequestException as e:
|
|
||||||
print(f"Error en la solicitud: {str(e)}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = response.json()
|
|
||||||
except ValueError:
|
|
||||||
print("Respuesta JSON inválida")
|
|
||||||
return []
|
|
||||||
|
|
||||||
if "results" not in data or len(data["results"]) != len(coordinates):
|
|
||||||
print("Formato de respuesta inesperado")
|
|
||||||
return []
|
|
||||||
|
|
||||||
points = []
|
|
||||||
for result in data["results"]:
|
|
||||||
try:
|
|
||||||
# Conversión UTM con manejo de errores
|
|
||||||
easting, northing, _, _ = utm.from_latlon(
|
|
||||||
result["latitude"],
|
|
||||||
result["longitude"]
|
|
||||||
)
|
|
||||||
|
|
||||||
points.append(FreeCAD.Vector(round(easting), # Convertir metros a milímetros
|
|
||||||
round(northing),
|
|
||||||
round(result["elevation"])) * 1000)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error procesando coordenadas: {str(e)}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
return points
|
|
||||||
|
|
||||||
|
|
||||||
def getElevationFromOE(coordinates):
|
|
||||||
"""Obtiene elevaciones de Open-Elevation API y devuelve vectores FreeCAD en coordenadas UTM."""
|
|
||||||
|
|
||||||
import certifi
|
|
||||||
from requests.exceptions import RequestException
|
|
||||||
if len(coordinates) == 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
from requests import get
|
|
||||||
import utm
|
|
||||||
|
|
||||||
locations_str=""
|
|
||||||
total = len(coordinates) - 1
|
|
||||||
for i, point in enumerate(coordinates):
|
|
||||||
locations_str += '{:.6f},{:.6f}'.format(point[0], point[1])
|
|
||||||
if i != total:
|
|
||||||
locations_str += '|'
|
|
||||||
query = 'https://api.open-elevation.com/api/v1/lookup?locations=' + locations_str
|
|
||||||
try:
|
|
||||||
r = get(query, timeout=20, verify=certifi.where()) # <-- Corrección aquí
|
|
||||||
except RequestException as e:
|
|
||||||
points = []
|
|
||||||
for i, point in enumerate(coordinates):
|
|
||||||
c = utm.from_latlon(point[0], point[1])
|
|
||||||
points.append(FreeCAD.Vector(round(c[0], 0),
|
|
||||||
round(c[1], 0),
|
|
||||||
0) * 1000)
|
|
||||||
return points
|
|
||||||
|
|
||||||
# Only get the json response in case of 200 or 201
|
|
||||||
points = []
|
|
||||||
if r.status_code == 200 or r.status_code == 201:
|
|
||||||
results = r.json()
|
|
||||||
for point in results["results"]:
|
|
||||||
c = utm.from_latlon(point["latitude"], point["longitude"])
|
|
||||||
v = FreeCAD.Vector(round(c[0], 0),
|
|
||||||
round(c[1], 0),
|
|
||||||
round(point["elevation"], 0)) * 1000
|
|
||||||
points.append(v)
|
|
||||||
return points
|
|
||||||
|
|
||||||
def getSinglePointElevationFromBing(lat, lng):
|
|
||||||
#http://dev.virtualearth.net/REST/v1/Elevation/List?points={lat1,long1,lat2,long2,latN,longnN}&heights={heights}&key={BingMapsAPIKey}
|
|
||||||
import utm
|
|
||||||
|
|
||||||
source = "http://dev.virtualearth.net/REST/v1/Elevation/List?points="
|
|
||||||
source += str(lat) + "," + str(lng)
|
|
||||||
source += "&heights=sealevel"
|
|
||||||
source += "&key=AmsPZA-zRt2iuIdQgvXZIxme2gWcgLaz7igOUy7VPB8OKjjEd373eCnj1KFv2CqX"
|
|
||||||
|
|
||||||
import requests
|
|
||||||
response = requests.get(source)
|
|
||||||
ans = response.text
|
|
||||||
|
|
||||||
s = json.loads(ans)
|
|
||||||
print(s)
|
|
||||||
res = s['resourceSets'][0]['resources'][0]['elevations']
|
|
||||||
for elevation in res:
|
|
||||||
c = utm.from_latlon(lat, lng)
|
|
||||||
v = FreeCAD.Vector(
|
|
||||||
round(c[0] * 1000, 0),
|
|
||||||
round(c[1] * 1000, 0),
|
|
||||||
round(elevation * 1000, 0))
|
|
||||||
return v
|
|
||||||
|
|
||||||
def getGridElevationFromBing(polygon, lat, lng, resolution = 1000):
|
|
||||||
#http://dev.virtualearth.net/REST/v1/Elevation/Polyline?points=35.89431,-110.72522,35.89393,-110.72578,35.89374,-110.72606,35.89337,-110.72662
|
|
||||||
# &heights=ellipsoid&samples=10&key={BingMapsAPIKey}
|
|
||||||
import utm
|
|
||||||
import math
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
geo = utm.from_latlon(lat, lng)
|
|
||||||
# result = (679434.3578335291, 4294023.585627955, 30, 'S')
|
|
||||||
# EASTING, NORTHING, ZONE NUMBER, ZONE LETTER
|
|
||||||
|
|
||||||
#StepsXX = int((polygon.Shape.BoundBox.XMax - polygon.Shape.BoundBox.XMin) / (resolution*1000))
|
|
||||||
points = []
|
|
||||||
yy = polygon.Shape.BoundBox.YMax
|
|
||||||
while yy > polygon.Shape.BoundBox.YMin:
|
|
||||||
xx = polygon.Shape.BoundBox.XMin
|
|
||||||
while xx < polygon.Shape.BoundBox.XMax:
|
|
||||||
StepsXX = int(math.ceil((polygon.Shape.BoundBox.XMax - xx) / resolution))
|
|
||||||
|
|
||||||
if StepsXX > 1000:
|
|
||||||
StepsXX = 1000
|
|
||||||
xx1 = xx + 1000 * resolution
|
|
||||||
else:
|
|
||||||
xx1 = xx + StepsXX * resolution
|
|
||||||
|
|
||||||
point1 = utm.to_latlon(xx / 1000, yy / 1000, geo[2], geo[3])
|
|
||||||
point2 = utm.to_latlon(xx1 / 1000, yy / 1000, geo[2], geo[3])
|
|
||||||
|
|
||||||
source = "http://dev.virtualearth.net/REST/v1/Elevation/Polyline?points="
|
|
||||||
source += "{lat1},{lng1}".format(lat1=point1[0], lng1=point1[1])
|
|
||||||
source += ","
|
|
||||||
source += "{lat2},{lng2}".format(lat2=point2[0], lng2=point2[1])
|
|
||||||
source += "&heights=sealevel"
|
|
||||||
source += "&samples={steps}".format(steps=StepsXX)
|
|
||||||
source += "&key=AmsPZA-zRt2iuIdQgvXZIxme2gWcgLaz7igOUy7VPB8OKjjEd373eCnj1KFv2CqX"
|
|
||||||
|
|
||||||
response = requests.get(source)
|
|
||||||
ans = response.text
|
|
||||||
|
|
||||||
# +# to do: error handling - wait and try again
|
|
||||||
s = json.loads(ans)
|
|
||||||
res = s['resourceSets'][0]['resources'][0]['elevations']
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
for elevation in res:
|
|
||||||
v = FreeCAD.Vector(xx + resolution * i, yy, round(elevation * 1000, 4))
|
|
||||||
points.append(v)
|
|
||||||
i += 1
|
|
||||||
xx = xx1 + resolution # para no repetir un mismo punto
|
|
||||||
yy -= resolution
|
|
||||||
|
|
||||||
return points
|
|
||||||
|
|
||||||
def getSinglePointElevation(lat, lon):
|
|
||||||
source = "https://maps.googleapis.com/maps/api/elevation/json?locations="
|
|
||||||
source += str(lat) + "," + str(lon)
|
|
||||||
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
|
||||||
#print (source)
|
|
||||||
|
|
||||||
#response = request.urlopen(source)
|
|
||||||
#ans = response.read()
|
|
||||||
import requests
|
|
||||||
response = requests.get(source)
|
|
||||||
ans = response.text
|
|
||||||
|
|
||||||
# +# to do: error handling - wait and try again
|
|
||||||
s = json.loads(ans)
|
|
||||||
res = s['results']
|
|
||||||
|
|
||||||
from geopy.distance import geodesic
|
|
||||||
for r in res:
|
|
||||||
|
|
||||||
reference = (0.0, 0.0)
|
|
||||||
v = FreeCAD.Vector(
|
|
||||||
round(geodesic(reference, (0.0, r['location']['lng'])).m, 2),
|
|
||||||
round(geodesic(reference, (r['location']['lat'], 0.0)).m, 2),
|
|
||||||
round(r['elevation'] * 1000, 2)
|
|
||||||
)
|
|
||||||
|
|
||||||
return v
|
|
||||||
|
|
||||||
def _getSinglePointElevation(lat, lon):
|
|
||||||
source = "https://maps.googleapis.com/maps/api/elevation/json?locations="
|
|
||||||
source += str(lat) + "," + str(lon)
|
|
||||||
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
|
||||||
#print (source)
|
|
||||||
|
|
||||||
#response = request.urlopen(source)
|
|
||||||
#ans = response.read()
|
|
||||||
import requests
|
|
||||||
response = requests.get(source)
|
|
||||||
ans = response.text
|
|
||||||
|
|
||||||
# +# to do: error handling - wait and try again
|
|
||||||
s = json.loads(ans)
|
|
||||||
res = s['results']
|
|
||||||
|
|
||||||
import pymap3d as pm
|
|
||||||
for r in res:
|
|
||||||
x, y, z = pm.geodetic2ecef(round(r['location']['lng'], 2),
|
|
||||||
round(r['location']['lat'], 2),
|
|
||||||
0)
|
|
||||||
v = FreeCAD.Vector(x,y,z)
|
|
||||||
|
|
||||||
return v
|
|
||||||
|
|
||||||
def getSinglePointElevation1(lat, lon):
|
|
||||||
source = "https://maps.googleapis.com/maps/api/elevation/json?locations="
|
|
||||||
source += str(lat) + "," + str(lon)
|
|
||||||
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
|
||||||
|
|
||||||
#response = urllib.request.urlopen(source)
|
|
||||||
#ans = response.read()
|
|
||||||
import requests
|
|
||||||
response = requests.get(source)
|
|
||||||
ans = response.text
|
|
||||||
|
|
||||||
# +# to do: error handling - wait and try again
|
|
||||||
s = json.loads(ans)
|
|
||||||
res = s['results']
|
|
||||||
|
|
||||||
for r in res:
|
|
||||||
c = tm.fromGeographic(r['location']['lat'], r['location']['lng'])
|
|
||||||
v = FreeCAD.Vector(
|
|
||||||
round(c[0], 4),
|
|
||||||
round(c[1], 4),
|
|
||||||
round(r['elevation'] * 1000, 2)
|
|
||||||
)
|
|
||||||
return v
|
|
||||||
|
|
||||||
def getSinglePointElevationUtm(lat, lon):
|
|
||||||
source = "https://maps.googleapis.com/maps/api/elevation/json?locations="
|
|
||||||
source += str(lat) + "," + str(lon)
|
|
||||||
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
|
||||||
print(source)
|
|
||||||
|
|
||||||
#response = urllib.request.urlopen(source)
|
|
||||||
#ans = response.read()
|
|
||||||
import requests
|
|
||||||
response = requests.get(source)
|
|
||||||
ans = response.text
|
|
||||||
|
|
||||||
# +# to do: error handling - wait and try again
|
|
||||||
s = json.loads(ans)
|
|
||||||
res = s['results']
|
|
||||||
print (res)
|
|
||||||
|
|
||||||
import utm
|
|
||||||
for r in res:
|
|
||||||
c = utm.from_latlon(r['location']['lat'], r['location']['lng'])
|
|
||||||
v = FreeCAD.Vector(
|
|
||||||
round(c[0] * 1000, 4),
|
|
||||||
round(c[1] * 1000, 4),
|
|
||||||
round(r['elevation'] * 1000, 2))
|
|
||||||
print (v)
|
|
||||||
return v
|
|
||||||
|
|
||||||
def getElevationUTM(polygon, lat, lng, resolution = 10000):
|
|
||||||
|
|
||||||
import utm
|
|
||||||
geo = utm.from_latlon(lat, lng)
|
|
||||||
# result = (679434.3578335291, 4294023.585627955, 30, 'S')
|
|
||||||
# EASTING, NORTHING, ZONE NUMBER, ZONE LETTER
|
|
||||||
|
|
||||||
StepsXX = int((polygon.Shape.BoundBox.XMax - polygon.Shape.BoundBox.XMin) / (resolution*1000))
|
|
||||||
points = []
|
|
||||||
yy = polygon.Shape.BoundBox.YMax
|
|
||||||
while yy > polygon.Shape.BoundBox.YMin:
|
|
||||||
# utm.to_latlon(EASTING, NORTHING, ZONE NUMBER, ZONE LETTER).
|
|
||||||
# result = (LATITUDE, LONGITUDE)
|
|
||||||
point1 = utm.to_latlon(polygon.Shape.BoundBox.XMin / 1000, yy / 1000, geo[2], geo[3])
|
|
||||||
point2 = utm.to_latlon(polygon.Shape.BoundBox.XMax / 1000, yy / 1000, geo[2], geo[3])
|
|
||||||
|
|
||||||
source = "https://maps.googleapis.com/maps/api/elevation/json?path="
|
|
||||||
source += "{a},{b}".format(a = point1[0], b = point1[1])
|
|
||||||
source += "|"
|
|
||||||
source += "{a},{b}".format(a = point2[0], b = point2[1])
|
|
||||||
source += "&samples={a}".format(a = StepsXX)
|
|
||||||
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
|
||||||
|
|
||||||
import requests
|
|
||||||
response = requests.get(source)
|
|
||||||
ans = response.text
|
|
||||||
|
|
||||||
# +# to do: error handling - wait and try again
|
|
||||||
s = json.loads(ans)
|
|
||||||
res = s['results']
|
|
||||||
|
|
||||||
for r in res:
|
|
||||||
c = utm.from_latlon(r['location']['lat'], r['location']['lng'])
|
|
||||||
v = FreeCAD.Vector(
|
|
||||||
round(c[0] * 1000, 2),
|
|
||||||
round(c[1] * 1000, 2),
|
|
||||||
round(r['elevation'] * 1000, 2)
|
|
||||||
)
|
|
||||||
points.append(v)
|
|
||||||
yy -= (resolution*1000)
|
|
||||||
|
|
||||||
FreeCAD.activeDocument().recompute()
|
|
||||||
return points
|
|
||||||
|
|
||||||
def getElevation1(polygon,resolution=10):
|
|
||||||
|
|
||||||
StepsXX = int((polygon.Shape.BoundBox.XMax - polygon.Shape.BoundBox.XMin) / (resolution * 1000))
|
|
||||||
points = []
|
|
||||||
yy = polygon.Shape.BoundBox.YMax
|
|
||||||
while yy > polygon.Shape.BoundBox.YMin:
|
|
||||||
point1 = tm.toGeographic(polygon.Shape.BoundBox.XMin, yy)
|
|
||||||
point2 = tm.toGeographic(polygon.Shape.BoundBox.XMax, yy)
|
|
||||||
|
|
||||||
source = "https://maps.googleapis.com/maps/api/elevation/json?path="
|
|
||||||
source += "{a},{b}".format(a = point1[0], b = point1[1])
|
|
||||||
source += "|"
|
|
||||||
source += "{a},{b}".format(a = point2[0], b = point2[1])
|
|
||||||
source += "&samples={a}".format(a = StepsXX)
|
|
||||||
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
|
||||||
|
|
||||||
try:
|
|
||||||
#response = urllib.request.urlopen(source)
|
|
||||||
#ans = response.read()
|
|
||||||
import requests
|
|
||||||
response = requests.get(source)
|
|
||||||
ans = response.text
|
|
||||||
|
|
||||||
# +# to do: error handling - wait and try again
|
|
||||||
|
|
||||||
s = json.loads(ans)
|
|
||||||
res = s['results']
|
|
||||||
except:
|
|
||||||
continue
|
|
||||||
|
|
||||||
#points = []
|
|
||||||
for r in res:
|
|
||||||
c = tm.fromGeographic(r['location']['lat'], r['location']['lng'])
|
|
||||||
v = FreeCAD.Vector(
|
|
||||||
round(c[0], 2),
|
|
||||||
round(c[1], 2),
|
|
||||||
round(r['elevation'] * 1000, 2)
|
|
||||||
)
|
|
||||||
points.append(v)
|
|
||||||
|
|
||||||
FreeCAD.activeDocument().recompute()
|
|
||||||
yy -= (resolution*1000)
|
|
||||||
|
|
||||||
return points
|
|
||||||
|
|
||||||
## download the heights from google:
|
|
||||||
def getElevation(lat, lon, b=50.35, le=11.17, size=40):
|
|
||||||
#https://maps.googleapis.com/maps/api/elevation/json?path=36.578581,-118.291994|36.23998,-116.83171&samples=3&key=YOUR_API_KEY
|
|
||||||
#https://maps.googleapis.com/maps/api/elevation/json?locations=39.7391536,-104.9847034&key=YOUR_API_KEY
|
|
||||||
|
|
||||||
source = "https://maps.googleapis.com/maps/api/elevation/json?path="
|
|
||||||
source += str(b-size*0.001) + "," + str(le) + "|" + str(b+size*0.001) + "," + str(le)
|
|
||||||
source += "&samples=" + str(100)
|
|
||||||
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
|
||||||
|
|
||||||
response = urllib.request.urlopen(source)
|
|
||||||
ans = response.read()
|
|
||||||
|
|
||||||
# +# to do: error handling - wait and try again
|
|
||||||
s = json.loads(ans)
|
|
||||||
res = s['results']
|
|
||||||
|
|
||||||
from geopy.distance import geodesic
|
|
||||||
points = []
|
|
||||||
for r in res:
|
|
||||||
reference = (0.0, 0.0)
|
|
||||||
v = FreeCAD.Vector(
|
|
||||||
round(geodesic(reference, (0.0, r['location']['lat'])).m, 2),
|
|
||||||
round(geodesic(reference, (r['location']['lng'], 0.0)).m, 2),
|
|
||||||
round(r['elevation'] * 1000, 2) - baseheight
|
|
||||||
)
|
|
||||||
points.append(v)
|
|
||||||
|
|
||||||
line = Draft.makeWire(points, closed=False, face=False, support=None)
|
|
||||||
line.ViewObject.Visibility = False
|
|
||||||
#FreeCAD.activeDocument().recompute()
|
|
||||||
FreeCADGui.updateGui()
|
|
||||||
return FreeCAD.activeDocument().ActiveObject
|
|
||||||
|
|
||||||
class _ImportPointsTaskPanel:
|
|
||||||
|
|
||||||
def __init__(self, obj = None):
|
|
||||||
self.obj = None
|
|
||||||
self.Boundary = None
|
|
||||||
self.select = 0
|
|
||||||
self.filename = ""
|
|
||||||
|
|
||||||
# form:
|
|
||||||
self.form1 = FreeCADGui.PySideUic.loadUi(os.path.dirname(__file__) + "/PVPlantImportGrid.ui")
|
|
||||||
self.form1.radio1.toggled.connect(lambda: self.mainToggle(self.form1.radio1))
|
|
||||||
self.form1.radio2.toggled.connect(lambda: self.mainToggle(self.form1.radio2))
|
|
||||||
self.form1.radio1.setChecked(True) # << --------------Poner al final para que no dispare antes de crear los componentes a los que va a llamar
|
|
||||||
#self.form.buttonAdd.clicked.connect(self.add)
|
|
||||||
self.form1.buttonDEM.clicked.connect(self.openFileDEM)
|
|
||||||
|
|
||||||
self.form2 = FreeCADGui.PySideUic.loadUi(os.path.dirname(__file__) + "/PVPlantCreateTerrainMesh.ui")
|
|
||||||
#self.form2.buttonAdd.clicked.connect(self.add)
|
|
||||||
self.form2.buttonBoundary.clicked.connect(self.addBoundary)
|
|
||||||
|
|
||||||
|
|
||||||
#self.form = [self.form1, self.form2]
|
|
||||||
self.form = self.form1
|
|
||||||
|
|
||||||
''' future:
|
|
||||||
def retranslateUi(self, dialog):
|
|
||||||
self.form1.setWindowTitle("Configuracion del Rack")
|
|
||||||
self.labelModule.setText(QtGui.QApplication.translate("PVPlant", "Modulo:", None))
|
|
||||||
self.labelModuleLength.setText(QtGui.QApplication.translate("PVPlant", "Longitud:", None))
|
|
||||||
self.labelModuleWidth.setText(QtGui.QApplication.translate("PVPlant", "Ancho:", None))
|
|
||||||
self.labelModuleHeight.setText(QtGui.QApplication.translate("PVPlant", "Alto:", None))
|
|
||||||
self.labelModuleFrame.setText(QtGui.QApplication.translate("PVPlant", "Ancho del marco:", None))
|
|
||||||
self.labelModuleColor.setText(QtGui.QApplication.translate("PVPlant", "Color del modulo:", None))
|
|
||||||
self.labelModules.setText(QtGui.QApplication.translate("Arch", "Colocacion de los Modulos", None))
|
|
||||||
self.labelModuleOrientation.setText(QtGui.QApplication.translate("Arch", "Orientacion del modulo:", None))
|
|
||||||
self.labelModuleGapX.setText(QtGui.QApplication.translate("Arch", "Separacion Horizontal (mm):", None))
|
|
||||||
self.labelModuleGapY.setText(QtGui.QApplication.translate("Arch", "Separacion Vertical (mm):", None))
|
|
||||||
self.labelModuleRows.setText(QtGui.QApplication.translate("Arch", "Filas de modulos:", None))
|
|
||||||
self.labelModuleCols.setText(QtGui.QApplication.translate("Arch", "Columnas de modulos:", None))
|
|
||||||
self.labelRack.setText(QtGui.QApplication.translate("Arch", "Configuracion de la estructura", None))
|
|
||||||
self.labelRackType.setText(QtGui.QApplication.translate("Arch", "Tipo de estructura:", None))
|
|
||||||
self.labelLevel.setText(QtGui.QApplication.translate("Arch", "Nivel:", None))
|
|
||||||
self.labelOffset.setText(QtGui.QApplication.translate("Arch", "Offset", None))
|
|
||||||
'''
|
|
||||||
|
|
||||||
def add(self):
|
|
||||||
sel = FreeCADGui.Selection.getSelection()
|
|
||||||
if len(sel) > 0:
|
|
||||||
self.obj = sel[0]
|
|
||||||
self.lineEdit1.setText(self.obj.Label)
|
|
||||||
|
|
||||||
def addBoundary(self):
|
|
||||||
sel = FreeCADGui.Selection.getSelection()
|
|
||||||
if len(sel) > 0:
|
|
||||||
self.Boundary = sel[0]
|
|
||||||
self.form2.editBoundary.setText(self.Boundary.Label)
|
|
||||||
|
|
||||||
def openFileDEM(self):
|
|
||||||
filters = "Esri ASC (*.asc);;CSV (*.csv);;All files (*.*)"
|
|
||||||
filename = QtGui.QFileDialog.getOpenFileName(None,
|
|
||||||
"Open DEM,",
|
|
||||||
"",
|
|
||||||
filters)
|
|
||||||
self.filename = filename[0]
|
|
||||||
self.form1.editDEM.setText(filename[0])
|
|
||||||
|
|
||||||
def mainToggle(self, radiobox):
|
|
||||||
if radiobox is self.form1.radio1:
|
|
||||||
self.select = 0
|
|
||||||
self.form1.gbLocalFile.setVisible(True)
|
|
||||||
elif radiobox is self.form1.radio2:
|
|
||||||
self.select = 1
|
|
||||||
self.form1.gbLocalFile.setVisible(True)
|
|
||||||
|
|
||||||
def accept(self):
|
|
||||||
from datetime import datetime
|
|
||||||
starttime = datetime.now()
|
|
||||||
|
|
||||||
site = PVPlantSite.get()
|
|
||||||
|
|
||||||
try:
|
|
||||||
PointGroups = FreeCAD.ActiveDocument.Point_Groups
|
|
||||||
except:
|
|
||||||
PointGroups = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Point_Groups')
|
|
||||||
PointGroups.Label = "Point Groups"
|
|
||||||
|
|
||||||
PointGroup = FreeCAD.ActiveDocument.addObject('Points::Feature', "Point_Group")
|
|
||||||
PointGroup.Label = "Land_Grid_Points"
|
|
||||||
FreeCAD.ActiveDocument.Point_Groups.addObject(PointGroup)
|
|
||||||
PointObject = PointGroup.Points.copy()
|
|
||||||
|
|
||||||
if self.select == 0: # Google or bing or ...
|
|
||||||
#for item in self.obj:
|
|
||||||
#if self.groupbox.isChecked:break
|
|
||||||
resol = FreeCAD.Units.Quantity(self.valueResolution.text()).Value
|
|
||||||
Site = FreeCAD.ActiveDocument.Site
|
|
||||||
pts = getGridElevationFromBing(self.obj, Site.Latitude, Site.Longitude, resol)
|
|
||||||
PointObject.addPoints(pts)
|
|
||||||
PointGroup.Points = PointObject
|
|
||||||
|
|
||||||
else:
|
|
||||||
if self.filename == "":
|
|
||||||
return
|
|
||||||
|
|
||||||
import Utils.importDEM as openDEM
|
|
||||||
if self.select == 1: # DEM.
|
|
||||||
import numpy as np
|
|
||||||
root, extension = os.path.splitext(self.filename)
|
|
||||||
if extension.lower() == ".asc":
|
|
||||||
x, y, datavals, cellsize, nodata_value = openDEM.openEsri(self.filename)
|
|
||||||
|
|
||||||
if self.Boundary:
|
|
||||||
inc_x = self.Boundary.Shape.BoundBox.XLength * 0.05
|
|
||||||
inc_y = self.Boundary.Shape.BoundBox.YLength * 0.05
|
|
||||||
|
|
||||||
min_x = 0
|
|
||||||
max_x = 0
|
|
||||||
|
|
||||||
comp = (self.Boundary.Shape.BoundBox.XMin - inc_x) / 1000
|
|
||||||
for i in range(nx):
|
|
||||||
if x[i] > comp:
|
|
||||||
min_x = i - 1
|
|
||||||
break
|
|
||||||
comp = (self.Boundary.Shape.BoundBox.XMax + inc_x) / 1000
|
|
||||||
for i in range(min_x, nx):
|
|
||||||
if x[i] > comp:
|
|
||||||
max_x = i
|
|
||||||
break
|
|
||||||
|
|
||||||
min_y = 0
|
|
||||||
max_y = 0
|
|
||||||
|
|
||||||
comp = (self.Boundary.Shape.BoundBox.YMax + inc_y) / 1000
|
|
||||||
for i in range(ny):
|
|
||||||
if y[i] < comp:
|
|
||||||
max_y = i
|
|
||||||
break
|
|
||||||
comp = (self.Boundary.Shape.BoundBox.YMin - inc_y) / 1000
|
|
||||||
for i in range(max_y, ny):
|
|
||||||
if y[i] < comp:
|
|
||||||
min_y = i
|
|
||||||
break
|
|
||||||
|
|
||||||
x = x[min_x:max_x]
|
|
||||||
y = y[max_y:min_y]
|
|
||||||
datavals = datavals[max_y:min_y, min_x:max_x]
|
|
||||||
|
|
||||||
pts = []
|
|
||||||
if True: # faster but more memory 46s - 4,25 gb
|
|
||||||
x, y = np.meshgrid(x, y)
|
|
||||||
xx = x.flatten()
|
|
||||||
yy = y.flatten()
|
|
||||||
zz = datavals.flatten()
|
|
||||||
x[:] = 0
|
|
||||||
y[:] = 0
|
|
||||||
datavals[:] = 0
|
|
||||||
|
|
||||||
pts = []
|
|
||||||
for i in range(0, len(xx)):
|
|
||||||
pts.append(FreeCAD.Vector(xx[i], yy[i], zz[i]) * 1000)
|
|
||||||
|
|
||||||
xx[:] = 0
|
|
||||||
yy[:] = 0
|
|
||||||
zz[:] = 0
|
|
||||||
|
|
||||||
else: # 51s 3,2 gb
|
|
||||||
createmesh = True
|
|
||||||
if createmesh:
|
|
||||||
import Part, Draft
|
|
||||||
|
|
||||||
lines=[]
|
|
||||||
for j in range(len(y)):
|
|
||||||
edges = []
|
|
||||||
for i in range(0, len(x) - 1):
|
|
||||||
ed = Part.makeLine(FreeCAD.Vector(x[i], y[j], datavals[j][i]) * 1000,
|
|
||||||
FreeCAD.Vector(x[i + 1], y[j], datavals[j][i + 1]) * 1000)
|
|
||||||
edges.append(ed)
|
|
||||||
|
|
||||||
#bspline = Draft.makeBSpline(pts)
|
|
||||||
#bspline.ViewObject.hide()
|
|
||||||
line = Part.Wire(edges)
|
|
||||||
lines.append(line)
|
|
||||||
|
|
||||||
'''
|
|
||||||
for i in range(0, len(bsplines), 100):
|
|
||||||
p = Part.makeLoft(bsplines[i:i + 100], False, False, False)
|
|
||||||
Part.show(p)
|
|
||||||
'''
|
|
||||||
p = Part.makeLoft(lines, False, True, False)
|
|
||||||
p = Part.Solid(p)
|
|
||||||
Part.show(p)
|
|
||||||
|
|
||||||
else:
|
|
||||||
pts = []
|
|
||||||
for j in range(ny):
|
|
||||||
for i in range(nx):
|
|
||||||
pts.append(FreeCAD.Vector(x[i], y[j], datavals[j][i]) * 1000)
|
|
||||||
|
|
||||||
elif extension.lower() == ".csv" or extension.lower() == ".txt": # x, y, z from gps
|
|
||||||
pts = openDEM.interpolatePoints(openDEM.openCSV(self.filename))
|
|
||||||
|
|
||||||
PointObject.addPoints(pts)
|
|
||||||
PointGroup.Points = PointObject
|
|
||||||
|
|
||||||
FreeCAD.ActiveDocument.recompute()
|
|
||||||
FreeCADGui.Control.closeDialog()
|
|
||||||
print("tiempo: ", datetime.now() - starttime)
|
|
||||||
|
|
||||||
def reject(self):
|
|
||||||
FreeCADGui.Control.closeDialog()
|
|
||||||
|
|
||||||
## Comandos -----------------------------------------------------------------------------------------------------------
|
|
||||||
class CommandImportPoints:
|
|
||||||
|
|
||||||
def GetResources(self):
|
|
||||||
return {'Pixmap': str(os.path.join(DirIcons, "cloud.svg")),
|
|
||||||
'MenuText': QT_TRANSLATE_NOOP("PVPlant", "Importer Grid"),
|
|
||||||
'Accel': "B, U",
|
|
||||||
'ToolTip': QT_TRANSLATE_NOOP("PVPlant", "Creates a cloud of points.")}
|
|
||||||
|
|
||||||
def IsActive(self):
|
|
||||||
return not FreeCAD.ActiveDocument is None
|
|
||||||
|
|
||||||
def Activated(self):
|
|
||||||
self.TaskPanel = _ImportPointsTaskPanel()
|
|
||||||
FreeCADGui.Control.showDialog(self.TaskPanel)
|
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
|
||||||
class CommandPointsGroup:
|
|
||||||
|
|
||||||
def GetCommands(self):
|
|
||||||
return tuple(['ImportPoints'
|
|
||||||
])
|
|
||||||
def GetResources(self):
|
|
||||||
return { 'MenuText': QT_TRANSLATE_NOOP("",'Cloud of Points'),
|
|
||||||
'ToolTip': QT_TRANSLATE_NOOP("",'Cloud of Points')
|
|
||||||
}
|
|
||||||
def IsActive(self):
|
|
||||||
return not FreeCAD.ActiveDocument is None
|
|
||||||
|
|
||||||
FreeCADGui.addCommand('ImportPoints', CommandImportPoints())
|
|
||||||
FreeCADGui.addCommand('PointsGroup', CommandPointsGroup())
|
|
||||||
|
|
||||||
|
|||||||
+9
-1116
File diff suppressed because it is too large
Load Diff
+330
-322
@@ -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>
|
||||||
|
|||||||
+23
-1168
File diff suppressed because it is too large
Load Diff
+45
-1
@@ -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,7 +196,12 @@ 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
|
||||||
|
suffix = Path(obj.DEM).suffix
|
||||||
|
if suffix == '.asc':
|
||||||
'''
|
'''
|
||||||
|
ASC format:
|
||||||
|
|
||||||
Parámetro Descripción Requisitos
|
Parámetro Descripción Requisitos
|
||||||
NCOLS: Cantidad de columnas de celdas Entero mayor que 0.
|
NCOLS: Cantidad de columnas de celdas Entero mayor que 0.
|
||||||
NROWS: Cantidad de filas de celdas Entero mayor que 0.
|
NROWS: Cantidad de filas de celdas Entero mayor que 0.
|
||||||
@@ -256,6 +296,10 @@ class Terrain(ArchComponent.Component):
|
|||||||
mesh.removeFoldsOnSurface()
|
mesh.removeFoldsOnSurface()
|
||||||
obj.InitialMesh = mesh.copy()
|
obj.InitialMesh = mesh.copy()
|
||||||
Mesh.show(mesh)
|
Mesh.show(mesh)
|
||||||
|
elif suffix in ['.xyz']:
|
||||||
|
data = open_xyz_mmap(obj.DEM)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if prop == "PointsGroup" or prop == "CuttingBoundary":
|
if prop == "PointsGroup" or prop == "CuttingBoundary":
|
||||||
if obj.PointsGroup and obj.CuttingBoundary:
|
if obj.PointsGroup and obj.CuttingBoundary:
|
||||||
|
|||||||
+7
-27
@@ -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,10 +649,12 @@ 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())
|
||||||
|
|
||||||
|
import docgenerator
|
||||||
|
FreeCADGui.addCommand('GenerateDocuments', docgenerator.generateDocuments())
|
||||||
|
|
||||||
projectlist = [ # "Reload",
|
projectlist = [ # "Reload",
|
||||||
"PVPlantSite",
|
"PVPlantSite",
|
||||||
"ProjectSetup",
|
"ProjectSetup",
|
||||||
@@ -712,4 +690,6 @@ pv_mechanical = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
objectlist = ['PVPlantTree',
|
objectlist = ['PVPlantTree',
|
||||||
'PVPlantFence',]
|
'PVPlantFenceGroup',
|
||||||
|
'GenerateDocuments',
|
||||||
|
]
|
||||||
+457
-44
@@ -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)
|
||||||
|
# Crear forma solo si hay resultados
|
||||||
|
if len(pts)>0:
|
||||||
obj.Shape = Part.makePolygon(pts)
|
obj.Shape = Part.makePolygon(pts)
|
||||||
|
else:
|
||||||
def __getstate__(self):
|
obj.Shape = Part.Shape() # Forma vacía si falla
|
||||||
return None
|
|
||||||
|
|
||||||
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 __getstate__(self):
|
def execute(self, obj):
|
||||||
return None
|
# 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
|
||||||
|
|
||||||
def __setstate__(self, state):
|
base = obj.Base.Shape
|
||||||
pass
|
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(_ViewProviderArea):
|
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):
|
def getIcon(self):
|
||||||
'''Return object treeview icon'''
|
'''Return object treeview icon'''
|
||||||
return str(os.path.join(DirIcons, "area_forbidden.svg"))
|
return str(os.path.join(DirIcons, "area_forbidden.svg"))
|
||||||
|
|
||||||
|
def onDocumentRestored(self, vobj):
|
||||||
|
"""Método ejecutado cuando el documento es restaurado"""
|
||||||
|
self.ViewObject = vobj
|
||||||
|
self.Object = vobj.Object
|
||||||
|
self.setProperties(vobj)
|
||||||
|
self.attach(vobj)
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class ViewProviderForbiddenArea:
|
||||||
|
def __init__(self, vobj):
|
||||||
|
vobj.Proxy = self
|
||||||
|
self.ViewObject = vobj
|
||||||
|
|
||||||
|
# Inicializar propiedades PRIMERO
|
||||||
|
self.setProperties(vobj)
|
||||||
|
|
||||||
|
# Configurar colores iniciales
|
||||||
|
self.updateColors(vobj)
|
||||||
|
|
||||||
|
def setProperties(self, vobj):
|
||||||
|
if not hasattr(vobj, "OriginalColor"):
|
||||||
|
vobj.addProperty("App::PropertyColor",
|
||||||
|
"OriginalColor",
|
||||||
|
"Display",
|
||||||
|
"Color for original wire")
|
||||||
|
vobj.OriginalColor = (1.0, 0.0, 0.0) # Rojo
|
||||||
|
|
||||||
|
if not hasattr(vobj, "OffsetColor"):
|
||||||
|
vobj.addProperty("App::PropertyColor",
|
||||||
|
"OffsetColor",
|
||||||
|
"Display",
|
||||||
|
"Color for offset wire")
|
||||||
|
vobj.OffsetColor = (1.0, 0.5, 0.0) # Naranja
|
||||||
|
|
||||||
|
def updateColors(self, vobj):
|
||||||
|
"""Actualiza los colores desde las propiedades"""
|
||||||
|
try:
|
||||||
|
if hasattr(vobj, "OriginalColor"):
|
||||||
|
self.original_color.rgb.setValue(*vobj.OriginalColor)
|
||||||
|
else:
|
||||||
|
self.original_color.rgb.setValue(1.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
if hasattr(vobj, "OffsetColor"):
|
||||||
|
self.offset_color.rgb.setValue(*vobj.OffsetColor)
|
||||||
|
else:
|
||||||
|
self.offset_color.rgb.setValue(1.0, 0.5, 0.0)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error en updateColors: {e}")
|
||||||
|
|
||||||
|
def onDocumentRestored(self, vobj):
|
||||||
|
self.setProperties(vobj)
|
||||||
|
# No llamar a __init__ de nuevo, solo actualizar propiedades
|
||||||
|
self.updateColors(vobj)
|
||||||
|
|
||||||
|
def getIcon(self):
|
||||||
|
return str(os.path.join(DirIcons, "area_forbidden.svg"))
|
||||||
|
|
||||||
|
def attach(self, vobj):
|
||||||
|
self.ViewObject = vobj
|
||||||
|
|
||||||
|
# Inicializar nodos Coin3D
|
||||||
|
self.root = coin.SoGroup()
|
||||||
|
self.original_coords = coin.SoCoordinate3()
|
||||||
|
self.offset_coords = coin.SoCoordinate3()
|
||||||
|
self.original_color = coin.SoBaseColor()
|
||||||
|
self.offset_color = coin.SoBaseColor()
|
||||||
|
self.original_lineset = coin.SoLineSet()
|
||||||
|
self.offset_lineset = coin.SoLineSet()
|
||||||
|
|
||||||
|
# Añadir un nodo de dibujo para establecer el estilo de línea
|
||||||
|
self.draw_style = coin.SoDrawStyle()
|
||||||
|
self.draw_style.style = coin.SoDrawStyle.LINES
|
||||||
|
self.draw_style.lineWidth = 3.0
|
||||||
|
|
||||||
|
# Construir la escena
|
||||||
|
self.root.addChild(self.draw_style)
|
||||||
|
|
||||||
|
# Grupo para el polígono original
|
||||||
|
original_group = coin.SoGroup()
|
||||||
|
original_group.addChild(self.original_color)
|
||||||
|
original_group.addChild(self.original_coords)
|
||||||
|
original_group.addChild(self.original_lineset)
|
||||||
|
|
||||||
|
# Grupo para el polígono offset
|
||||||
|
offset_group = coin.SoGroup()
|
||||||
|
offset_group.addChild(self.offset_color)
|
||||||
|
offset_group.addChild(self.offset_coords)
|
||||||
|
offset_group.addChild(self.offset_lineset)
|
||||||
|
|
||||||
|
self.root.addChild(original_group)
|
||||||
|
self.root.addChild(offset_group)
|
||||||
|
|
||||||
|
vobj.addDisplayMode(self.root, "Standard")
|
||||||
|
# Asegurar que la visibilidad esté activada
|
||||||
|
vobj.Visibility = True
|
||||||
|
|
||||||
|
def updateData(self, obj, prop):
|
||||||
|
if prop == "Shape":
|
||||||
|
self.updateVisual(obj)
|
||||||
|
|
||||||
|
def updateVisual(self, obj):
|
||||||
|
"""Actualiza la representación visual basada en la forma del objeto"""
|
||||||
|
if not hasattr(obj, 'Shape') or not obj.Shape or obj.Shape.isNull():
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Obtener todos los bordes de la forma compuesta
|
||||||
|
all_edges = obj.Shape.Edges
|
||||||
|
|
||||||
|
# Separar bordes por polígono (asumimos que el primer polígono es el original)
|
||||||
|
# Esto es una simplificación - podrías necesitar una lógica más sofisticada
|
||||||
|
if len(all_edges) >= 2:
|
||||||
|
# Polígono original - primer conjunto de bordes
|
||||||
|
original_edges = [all_edges[0]]
|
||||||
|
original_points = []
|
||||||
|
for edge in original_edges:
|
||||||
|
for vertex in edge.Vertexes:
|
||||||
|
original_points.append((vertex.Point.x, vertex.Point.y, vertex.Point.z))
|
||||||
|
|
||||||
|
# Polígono offset - segundo conjunto de bordes
|
||||||
|
offset_edges = [all_edges[1]]
|
||||||
|
offset_points = []
|
||||||
|
for edge in offset_edges:
|
||||||
|
for vertex in edge.Vertexes:
|
||||||
|
offset_points.append((vertex.Point.x, vertex.Point.y, vertex.Point.z))
|
||||||
|
|
||||||
|
# Asignar puntos a los nodos Coordinate3
|
||||||
|
if original_points:
|
||||||
|
self.original_coords.point.setValues(0, len(original_points), original_points)
|
||||||
|
self.original_lineset.numVertices.setValue(len(original_points))
|
||||||
|
|
||||||
|
if offset_points:
|
||||||
|
self.offset_coords.point.setValues(0, len(offset_points), offset_points)
|
||||||
|
self.offset_lineset.numVertices.setValue(len(offset_points))
|
||||||
|
|
||||||
|
# Actualizar colores
|
||||||
|
if hasattr(obj, 'ViewObject') and obj.ViewObject:
|
||||||
|
self.updateColors(obj.ViewObject)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error en updateVisual: {e}")
|
||||||
|
|
||||||
|
def onChanged(self, vobj, prop):
|
||||||
|
if prop in ["OriginalColor", "OffsetColor"]:
|
||||||
|
self.updateColors(vobj)
|
||||||
|
elif prop == "Visibility" and vobj.Visibility:
|
||||||
|
# Cuando la visibilidad cambia a True, actualizar visual
|
||||||
|
self.updateVisual(vobj.Object)
|
||||||
|
|
||||||
|
def getDisplayModes(self, obj):
|
||||||
|
return ["Standard"]
|
||||||
|
|
||||||
|
def getDefaultDisplayMode(self):
|
||||||
|
return "Standard"
|
||||||
|
|
||||||
|
def setDisplayMode(self, mode):
|
||||||
|
return mode
|
||||||
|
|
||||||
def claimChildren(self):
|
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)
|
||||||
|
|||||||
+61
-61
@@ -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>
|
||||||
|
|||||||
@@ -4,12 +4,15 @@
|
|||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
|
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css">
|
<!-- 1. Core Leaflet library -->
|
||||||
<script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"></script>
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://rawgit.com/Leaflet/Leaflet.draw/v1.0.4/dist/leaflet.draw.css">
|
<!-- 2. Leaflet.draw Plugin (MUST be loaded AFTER Leaflet) -->
|
||||||
<script src="https://rawgit.com/Leaflet/Leaflet.draw/v1.0.4/dist/leaflet.draw-src.js"></script>
|
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css" />
|
||||||
|
<script src="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js"></script>
|
||||||
|
|
||||||
|
<!-- 3. Other plugins -->
|
||||||
<script src="https://unpkg.com/togeojson@0.16.0"></script>
|
<script src="https://unpkg.com/togeojson@0.16.0"></script>
|
||||||
<script src="https://unpkg.com/leaflet-filelayer@1.2.0"></script>
|
<script src="https://unpkg.com/leaflet-filelayer@1.2.0"></script>
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
'''
|
'''
|
||||||
|
|||||||
+357
@@ -0,0 +1,357 @@
|
|||||||
|
# Script para FreeCAD - Procesador de Documentos Word con Carátula
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
from PySide2 import QtWidgets, QtCore
|
||||||
|
from PySide2.QtWidgets import (QFileDialog, QMessageBox, QProgressDialog,
|
||||||
|
QApplication, QVBoxLayout, QWidget, QPushButton,
|
||||||
|
QLabel, QTextEdit)
|
||||||
|
import FreeCAD
|
||||||
|
import FreeCADGui
|
||||||
|
|
||||||
|
import PVPlantResources
|
||||||
|
from PVPlantResources import DirIcons as DirIcons
|
||||||
|
|
||||||
|
try:
|
||||||
|
from docx import Document
|
||||||
|
from docx.shared import Pt, RGBColor, Inches
|
||||||
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||||
|
from docx.oxml.ns import qn
|
||||||
|
|
||||||
|
DOCX_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
DOCX_AVAILABLE = False
|
||||||
|
FreeCAD.Console.PrintError("Error: python-docx no está instalado. Instala con: pip install python-docx\n")
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentProcessor(QtWidgets.QDialog):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(DocumentProcessor, self).__init__(parent)
|
||||||
|
self.caratula_path = ""
|
||||||
|
self.carpeta_path = ""
|
||||||
|
self.setup_ui()
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
self.setWindowTitle("Procesador de Documentos Word")
|
||||||
|
self.setMinimumWidth(600)
|
||||||
|
self.setMinimumHeight(500)
|
||||||
|
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
|
||||||
|
# Título
|
||||||
|
title = QLabel("<h2>Procesador de Documentos Word</h2>")
|
||||||
|
title.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
layout.addWidget(title)
|
||||||
|
|
||||||
|
# Información
|
||||||
|
info_text = QLabel(
|
||||||
|
"Este script buscará recursivamente todos los archivos .docx en una carpeta,\n"
|
||||||
|
"insertará una carátula y aplicará formato estándar a todos los documentos."
|
||||||
|
)
|
||||||
|
info_text.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
layout.addWidget(info_text)
|
||||||
|
|
||||||
|
# Botón seleccionar carátula
|
||||||
|
self.btn_caratula = QPushButton("1. Seleccionar Carátula")
|
||||||
|
self.btn_caratula.clicked.connect(self.seleccionar_caratula)
|
||||||
|
layout.addWidget(self.btn_caratula)
|
||||||
|
|
||||||
|
self.label_caratula = QLabel("No se ha seleccionado carátula")
|
||||||
|
self.label_caratula.setWordWrap(True)
|
||||||
|
layout.addWidget(self.label_caratula)
|
||||||
|
|
||||||
|
# Botón seleccionar carpeta
|
||||||
|
self.btn_carpeta = QPushButton("2. Seleccionar Carpeta de Documentos")
|
||||||
|
self.btn_carpeta.clicked.connect(self.seleccionar_carpeta)
|
||||||
|
layout.addWidget(self.btn_carpeta)
|
||||||
|
|
||||||
|
self.label_carpeta = QLabel("No se ha seleccionado carpeta")
|
||||||
|
self.label_carpeta.setWordWrap(True)
|
||||||
|
layout.addWidget(self.label_carpeta)
|
||||||
|
|
||||||
|
# Botón procesar
|
||||||
|
self.btn_procesar = QPushButton("3. Procesar Documentos")
|
||||||
|
self.btn_procesar.clicked.connect(self.procesar_documentos)
|
||||||
|
self.btn_procesar.setEnabled(False)
|
||||||
|
layout.addWidget(self.btn_procesar)
|
||||||
|
|
||||||
|
# Área de log
|
||||||
|
self.log_area = QTextEdit()
|
||||||
|
self.log_area.setReadOnly(True)
|
||||||
|
layout.addWidget(self.log_area)
|
||||||
|
|
||||||
|
# Botón cerrar
|
||||||
|
self.btn_cerrar = QPushButton("Cerrar")
|
||||||
|
self.btn_cerrar.clicked.connect(self.close)
|
||||||
|
layout.addWidget(self.btn_cerrar)
|
||||||
|
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def log(self, mensaje):
|
||||||
|
"""Agrega un mensaje al área de log"""
|
||||||
|
self.log_area.append(mensaje)
|
||||||
|
QApplication.processEvents() # Para actualizar la UI
|
||||||
|
|
||||||
|
def seleccionar_caratula(self):
|
||||||
|
"""Abre un diálogo para seleccionar el archivo de carátula"""
|
||||||
|
archivo, _ = QFileDialog.getOpenFileName(
|
||||||
|
self,
|
||||||
|
"Seleccionar archivo de carátula",
|
||||||
|
"",
|
||||||
|
"Word documents (*.docx);;All files (*.*)"
|
||||||
|
)
|
||||||
|
|
||||||
|
if archivo and os.path.exists(archivo):
|
||||||
|
self.caratula_path = archivo
|
||||||
|
self.label_caratula.setText(f"Carátula: {os.path.basename(archivo)}")
|
||||||
|
self.verificar_estado()
|
||||||
|
self.log(f"✓ Carátula seleccionada: {archivo}")
|
||||||
|
|
||||||
|
def seleccionar_carpeta(self):
|
||||||
|
"""Abre un diálogo para seleccionar la carpeta de documentos"""
|
||||||
|
carpeta = QFileDialog.getExistingDirectory(
|
||||||
|
self,
|
||||||
|
"Seleccionar carpeta con documentos"
|
||||||
|
)
|
||||||
|
|
||||||
|
if carpeta:
|
||||||
|
self.carpeta_path = carpeta
|
||||||
|
self.label_carpeta.setText(f"Carpeta: {carpeta}")
|
||||||
|
self.verificar_estado()
|
||||||
|
self.log(f"✓ Carpeta seleccionada: {carpeta}")
|
||||||
|
|
||||||
|
def verificar_estado(self):
|
||||||
|
"""Habilita el botón procesar si ambos paths están seleccionados"""
|
||||||
|
if self.caratula_path and self.carpeta_path:
|
||||||
|
self.btn_procesar.setEnabled(True)
|
||||||
|
|
||||||
|
def buscar_docx_recursivamente(self, carpeta):
|
||||||
|
"""Busca recursivamente todos los archivos .docx en una carpeta"""
|
||||||
|
archivos_docx = []
|
||||||
|
patron = os.path.join(carpeta, "**", "*.docx")
|
||||||
|
|
||||||
|
for archivo in glob.glob(patron, recursive=True):
|
||||||
|
archivos_docx.append(archivo)
|
||||||
|
|
||||||
|
return archivos_docx
|
||||||
|
|
||||||
|
def aplicar_formato_estandar(self, doc):
|
||||||
|
"""Aplica formato estándar al documento"""
|
||||||
|
try:
|
||||||
|
# Configurar estilos por defecto
|
||||||
|
style = doc.styles['Normal']
|
||||||
|
font = style.font
|
||||||
|
font.name = 'Arial'
|
||||||
|
font.size = Pt(11)
|
||||||
|
font.color.rgb = RGBColor(0, 0, 0) # Negro
|
||||||
|
|
||||||
|
# Configurar encabezados
|
||||||
|
try:
|
||||||
|
heading_style = doc.styles['Heading 1']
|
||||||
|
heading_font = heading_style.font
|
||||||
|
heading_font.name = 'Arial'
|
||||||
|
heading_font.size = Pt(14)
|
||||||
|
heading_font.bold = True
|
||||||
|
heading_font.color.rgb = RGBColor(0, 51, 102) # Azul oscuro
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f" ⚠ Advertencia en formato: {str(e)}")
|
||||||
|
|
||||||
|
def aplicar_formato_avanzado(self, doc):
|
||||||
|
"""Aplica formato más avanzado y personalizado"""
|
||||||
|
try:
|
||||||
|
# Configurar márgenes
|
||||||
|
sections = doc.sections
|
||||||
|
for section in sections:
|
||||||
|
section.top_margin = Inches(1)
|
||||||
|
section.bottom_margin = Inches(1)
|
||||||
|
section.left_margin = Inches(1)
|
||||||
|
section.right_margin = Inches(1)
|
||||||
|
|
||||||
|
# Configurar estilos de párrafo
|
||||||
|
for paragraph in doc.paragraphs:
|
||||||
|
paragraph.paragraph_format.space_after = Pt(6)
|
||||||
|
paragraph.paragraph_format.space_before = Pt(0)
|
||||||
|
paragraph.paragraph_format.line_spacing = 1.15
|
||||||
|
|
||||||
|
# Alinear párrafos justificados
|
||||||
|
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
|
||||||
|
|
||||||
|
# Aplicar fuente específica a cada run
|
||||||
|
for run in paragraph.runs:
|
||||||
|
run.font.name = 'Arial'
|
||||||
|
run._element.rPr.rFonts.set(qn('w:eastAsia'), 'Arial')
|
||||||
|
run.font.size = Pt(11)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f" ⚠ Advertencia en formato avanzado: {str(e)}")
|
||||||
|
|
||||||
|
def insertar_caratula_y_formatear(self, archivo_docx, archivo_caratula):
|
||||||
|
"""Inserta la carátula y aplica formato al documento"""
|
||||||
|
try:
|
||||||
|
# Abrir el documento de carátula
|
||||||
|
doc_caratula = Document(archivo_caratula)
|
||||||
|
|
||||||
|
# Abrir el documento destino
|
||||||
|
doc_destino = Document(archivo_docx)
|
||||||
|
|
||||||
|
# Crear un nuevo documento que contendrá la carátula + contenido original
|
||||||
|
nuevo_doc = Document()
|
||||||
|
|
||||||
|
# Copiar todo el contenido de la carátula
|
||||||
|
for elemento in doc_caratula.element.body:
|
||||||
|
print(elemento)
|
||||||
|
nuevo_doc.element.body.append(elemento)
|
||||||
|
|
||||||
|
# Agregar un salto de página después de la carátula
|
||||||
|
nuevo_doc.add_page_break()
|
||||||
|
|
||||||
|
# Copiar todo el contenido del documento original
|
||||||
|
for elemento in doc_destino.element.body:
|
||||||
|
nuevo_doc.element.body.append(elemento)
|
||||||
|
|
||||||
|
# Aplicar formatos
|
||||||
|
self.aplicar_formato_estandar(nuevo_doc)
|
||||||
|
self.aplicar_formato_avanzado(nuevo_doc)
|
||||||
|
|
||||||
|
# Guardar el documento (sobrescribir el original)
|
||||||
|
nombre_base = os.path.splitext(os.path.basename(archivo_docx))[0]
|
||||||
|
extension = os.path.splitext(archivo_docx)[1]
|
||||||
|
name = f"{nombre_base}{extension}"
|
||||||
|
nuevo_docx = os.path.join(self.output_carpeta, name)
|
||||||
|
nuevo_doc.save(nuevo_docx)
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
def procesar_documentos(self):
|
||||||
|
"""Función principal que orquesta todo el proceso"""
|
||||||
|
if not DOCX_AVAILABLE:
|
||||||
|
QMessageBox.critical(self, "Error",
|
||||||
|
"La biblioteca python-docx no está disponible.\n\n"
|
||||||
|
"Instala con: pip install python-docx")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Verificar paths
|
||||||
|
if not os.path.exists(self.caratula_path):
|
||||||
|
QMessageBox.warning(self, "Error", "El archivo de carátula no existe.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not os.path.exists(self.carpeta_path):
|
||||||
|
QMessageBox.warning(self, "Error", "La carpeta de documentos no existe.")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log("\n=== INICIANDO PROCESAMIENTO ===")
|
||||||
|
self.log(f"Carátula: {self.caratula_path}")
|
||||||
|
self.log(f"Carpeta: {self.carpeta_path}")
|
||||||
|
|
||||||
|
directorio_padre = os.path.dirname(self.carpeta_path)
|
||||||
|
self.output_carpeta = os.path.join(directorio_padre, "03.Outputs")
|
||||||
|
os.makedirs(self.output_carpeta, exist_ok=True)
|
||||||
|
|
||||||
|
# Buscar archivos .docx
|
||||||
|
self.log("Buscando archivos .docx...")
|
||||||
|
archivos_docx = self.buscar_docx_recursivamente(self.carpeta_path)
|
||||||
|
|
||||||
|
if not archivos_docx:
|
||||||
|
self.log("No se encontraron archivos .docx en la carpeta seleccionada.")
|
||||||
|
QMessageBox.information(self, "Información",
|
||||||
|
"No se encontraron archivos .docx en la carpeta seleccionada.")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log(f"Se encontraron {len(archivos_docx)} archivos .docx")
|
||||||
|
|
||||||
|
# Crear diálogo de progreso
|
||||||
|
progress = QProgressDialog("Procesando documentos...", "Cancelar", 0, len(archivos_docx), self)
|
||||||
|
progress.setWindowTitle("Procesando")
|
||||||
|
progress.setWindowModality(QtCore.Qt.WindowModal)
|
||||||
|
progress.show()
|
||||||
|
|
||||||
|
# Procesar cada archivo
|
||||||
|
exitosos = 0
|
||||||
|
fallidos = 0
|
||||||
|
errores_detallados = []
|
||||||
|
|
||||||
|
for i, archivo_docx in enumerate(archivos_docx):
|
||||||
|
if progress.wasCanceled():
|
||||||
|
self.log("Proceso cancelado por el usuario.")
|
||||||
|
break
|
||||||
|
|
||||||
|
progress.setValue(i)
|
||||||
|
progress.setLabelText(f"Procesando {i + 1}/{len(archivos_docx)}: {os.path.basename(archivo_docx)}")
|
||||||
|
QApplication.processEvents()
|
||||||
|
|
||||||
|
self.log(f"Procesando: {os.path.basename(archivo_docx)}")
|
||||||
|
|
||||||
|
success, error_msg = self.insertar_caratula_y_formatear(archivo_docx, self.caratula_path)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.log(f" ✓ Completado")
|
||||||
|
exitosos += 1
|
||||||
|
else:
|
||||||
|
self.log(f" ✗ Error: {error_msg}")
|
||||||
|
fallidos += 1
|
||||||
|
errores_detallados.append(f"{os.path.basename(archivo_docx)}: {error_msg}")
|
||||||
|
|
||||||
|
progress.setValue(len(archivos_docx))
|
||||||
|
|
||||||
|
# Mostrar resumen
|
||||||
|
self.log("\n=== RESUMEN ===")
|
||||||
|
self.log(f"Documentos procesados exitosamente: {exitosos}")
|
||||||
|
self.log(f"Documentos con errores: {fallidos}")
|
||||||
|
self.log(f"Total procesados: {exitosos + fallidos}")
|
||||||
|
|
||||||
|
# Mostrar mensaje final
|
||||||
|
mensaje = (f"Procesamiento completado:\n"
|
||||||
|
f"✓ Exitosos: {exitosos}\n"
|
||||||
|
f"✗ Fallidos: {fallidos}\n"
|
||||||
|
f"Total: {len(archivos_docx)}")
|
||||||
|
|
||||||
|
if fallidos > 0:
|
||||||
|
mensaje += f"\n\nErrores encontrados:\n" + "\n".join(
|
||||||
|
errores_detallados[:5]) # Mostrar solo primeros 5 errores
|
||||||
|
if len(errores_detallados) > 5:
|
||||||
|
mensaje += f"\n... y {len(errores_detallados) - 5} más"
|
||||||
|
|
||||||
|
QMessageBox.information(self, "Proceso Completado", mensaje)
|
||||||
|
|
||||||
|
|
||||||
|
# Función para ejecutar desde FreeCAD
|
||||||
|
def run_document_processor():
|
||||||
|
"""Función principal para ejecutar el procesador desde FreeCAD"""
|
||||||
|
# Verificar si python-docx está disponible
|
||||||
|
if not DOCX_AVAILABLE:
|
||||||
|
msg = QMessageBox()
|
||||||
|
msg.setIcon(QMessageBox.Critical)
|
||||||
|
msg.setText("Biblioteca python-docx no encontrada")
|
||||||
|
msg.setInformativeText(
|
||||||
|
"Para usar este script necesitas instalar python-docx:\n\n"
|
||||||
|
"1. Abre la consola de FreeCAD\n"
|
||||||
|
"2. Ejecuta: import subprocess, sys\n"
|
||||||
|
"3. Ejecuta: subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'python-docx'])\n\n"
|
||||||
|
"O instala desde una terminal externa con: pip install python-docx"
|
||||||
|
)
|
||||||
|
msg.setWindowTitle("Dependencia faltante")
|
||||||
|
msg.exec_()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Crear y mostrar la interfaz
|
||||||
|
dialog = DocumentProcessor(FreeCADGui.getMainWindow())
|
||||||
|
dialog.exec_()
|
||||||
|
|
||||||
|
class generateDocuments:
|
||||||
|
def GetResources(self):
|
||||||
|
return {'Pixmap': str(os.path.join(DirIcons, "house.svg")),
|
||||||
|
'MenuText': "DocumentGenerator",
|
||||||
|
'Accel': "D, G",
|
||||||
|
'ToolTip': "Creates a Building object from setup dialog."}
|
||||||
|
|
||||||
|
def IsActive(self):
|
||||||
|
return not FreeCAD.ActiveDocument is None
|
||||||
|
|
||||||
|
|
||||||
|
def Activated(self):
|
||||||
|
run_document_processor()
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
# /**********************************************************************
|
||||||
|
# * *
|
||||||
|
# * Copyright (c) 2026 Javier Brana <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 *
|
||||||
|
# * *
|
||||||
|
# ***********************************************************************
|
||||||
|
|
||||||
|
"""
|
||||||
|
Proyecciones y transformaciones geodésicas unificadas para PVPlant.
|
||||||
|
|
||||||
|
Reemplaza el uso disperso de la librería 'utm' con pyproj (PROJ),
|
||||||
|
soporte multi-zona UTM y transformaciones entre datums.
|
||||||
|
|
||||||
|
Uso básico:
|
||||||
|
from lib.projection import latlon_to_utm, utm_to_latlon, get_utm_zone
|
||||||
|
"""
|
||||||
|
|
||||||
|
import FreeCAD
|
||||||
|
from pyproj import CRS, Transformer
|
||||||
|
from pyproj.aoi import AreaOfInterest
|
||||||
|
from pyproj.database import query_utm_crs_info
|
||||||
|
|
||||||
|
# WGS84 – sistema de coordenadas geográfico de referencia
|
||||||
|
_WGS84 = CRS.from_epsg(4326)
|
||||||
|
|
||||||
|
# Cache de transformadores UTM por zona (lazy)
|
||||||
|
_utm_transformers = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_utm_transformer(lat, lon):
|
||||||
|
"""Obtiene (o crea en caché) un transformador UTM para la zona de las coordenadas dadas.
|
||||||
|
Returns:
|
||||||
|
tuple: (transformer, zone_number, zone_letter)
|
||||||
|
"""
|
||||||
|
# Determinar la zona UTM a partir de lat/lon
|
||||||
|
zone_number = int((lon + 180) / 6) + 1
|
||||||
|
|
||||||
|
if lat >= 0:
|
||||||
|
zone_letter = 'N'
|
||||||
|
epsg = 32600 + zone_number
|
||||||
|
else:
|
||||||
|
zone_letter = 'S'
|
||||||
|
epsg = 32700 + zone_number
|
||||||
|
|
||||||
|
cache_key = (zone_number, zone_letter)
|
||||||
|
if cache_key not in _utm_transformers:
|
||||||
|
utm_crs = CRS.from_epsg(epsg)
|
||||||
|
_utm_transformers[cache_key] = Transformer.from_crs(
|
||||||
|
_WGS84, utm_crs, always_xy=True
|
||||||
|
)
|
||||||
|
|
||||||
|
return _utm_transformers[cache_key], zone_number, zone_letter
|
||||||
|
|
||||||
|
|
||||||
|
def latlon_to_utm(lat, lon):
|
||||||
|
"""Convierte coordenadas geográficas (WGS84) a UTM (este, norte, zona, letra).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lat (float): Latitud en grados.
|
||||||
|
lon (float): Longitud en grados.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (easting, northing, zone_number, zone_letter)
|
||||||
|
easting/northing en metros.
|
||||||
|
"""
|
||||||
|
transformer, zone_number, zone_letter = _get_utm_transformer(lat, lon)
|
||||||
|
easting, northing = transformer.transform(lon, lat)
|
||||||
|
return easting, northing, zone_number, zone_letter
|
||||||
|
|
||||||
|
|
||||||
|
def utm_to_latlon(easting, northing, zone_number, zone_letter='N'):
|
||||||
|
"""Convierte coordenadas UTM a geográficas (WGS84).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
easting (float): Coordenada E en metros.
|
||||||
|
northing (float): Coordenada N en metros.
|
||||||
|
zone_number (int): Número de zona UTM (1-60).
|
||||||
|
zone_letter (str): Letra de zona ('N' o 'S').
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (latitude, longitude) en grados.
|
||||||
|
"""
|
||||||
|
if zone_letter.upper() not in ('N', 'S'):
|
||||||
|
zone_letter = 'N'
|
||||||
|
|
||||||
|
epsg = 32600 + zone_number if zone_letter.upper() == 'N' else 32700 + zone_number
|
||||||
|
utm_crs = CRS.from_epsg(epsg)
|
||||||
|
transformer = Transformer.from_crs(utm_crs, _WGS84, always_xy=True)
|
||||||
|
lon, lat = transformer.transform(easting, northing)
|
||||||
|
return lat, lon
|
||||||
|
|
||||||
|
|
||||||
|
def get_utm_zone(lat, lon):
|
||||||
|
"""Obtiene la zona UTM para unas coordenadas dadas.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lat (float): Latitud en grados.
|
||||||
|
lon (float): Longitud en grados.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (zone_number, zone_letter)
|
||||||
|
"""
|
||||||
|
_, _, zone_number, zone_letter = latlon_to_utm(lat, lon)
|
||||||
|
return zone_number, zone_letter
|
||||||
|
|
||||||
|
|
||||||
|
def latlon_to_utm_vector(lat, lon, elevation=0.0):
|
||||||
|
"""Convierte lat/lon/elevación a un FreeCAD.Vector en UTM (mm).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lat (float): Latitud en grados.
|
||||||
|
lon (float): Longitud en grados.
|
||||||
|
elevation (float): Elevación en metros (default 0).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
FreeCAD.Vector: Coordenadas UTM en milímetros.
|
||||||
|
"""
|
||||||
|
transformer, _, _ = _get_utm_transformer(lat, lon)
|
||||||
|
easting, northing = transformer.transform(lon, lat)
|
||||||
|
return FreeCAD.Vector(
|
||||||
|
round(easting, 0),
|
||||||
|
round(northing, 0),
|
||||||
|
round(elevation, 0)
|
||||||
|
) * 1000
|
||||||
+21
-6
@@ -2,17 +2,32 @@
|
|||||||
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
|
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
|
||||||
<name>PVPlant</name>
|
<name>PVPlant</name>
|
||||||
<description>FreeCAD Fotovoltaic Power Plant Toolkit</description>
|
<description>FreeCAD Fotovoltaic Power Plant Toolkit</description>
|
||||||
<version>2025.07.06</version>
|
<version>2026.02.12</version>
|
||||||
<date>2025.07.06</date>
|
<date>2026.02.15</date>
|
||||||
<maintainer email="javier.branagutierrez@gmail.com">Javier Braña</maintainer>
|
|
||||||
|
<maintainer email="javier.branagutierrez@gmail.com">
|
||||||
|
Javier Braña
|
||||||
|
</maintainer>
|
||||||
|
|
||||||
<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="bugtracker">https://homehud.duckdns.org/javier/PVPlant/issues</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="readme">
|
||||||
|
https://homehud.duckdns.org/javier/PVPlant/raw/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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -51,6 +55,10 @@ class _CommandReload:
|
|||||||
import hydro.hydrological as hydro
|
import hydro.hydrological as hydro
|
||||||
import Importer.importOSM as iOSM
|
import Importer.importOSM as iOSM
|
||||||
|
|
||||||
|
import docgenerator
|
||||||
|
|
||||||
|
importlib.reload(docgenerator)
|
||||||
|
|
||||||
importlib.reload(ProjectSetup)
|
importlib.reload(ProjectSetup)
|
||||||
importlib.reload(PVPlantPlacement)
|
importlib.reload(PVPlantPlacement)
|
||||||
importlib.reload(PVPlantImportGrid)
|
importlib.reload(PVPlantImportGrid)
|
||||||
@@ -59,9 +67,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)
|
||||||
|
|||||||
+3
-8
@@ -2,23 +2,18 @@ numpy~=1.26.2
|
|||||||
opencv-python~=4.8.1
|
opencv-python~=4.8.1
|
||||||
matplotlib~=3.8.2
|
matplotlib~=3.8.2
|
||||||
openpyxl~=3.1.2
|
openpyxl~=3.1.2
|
||||||
utm~=0.7.0
|
|
||||||
PySide2~=5.15.8
|
PySide2~=5.15.8
|
||||||
requests~=2.31.0
|
requests~=2.31.0
|
||||||
setuptools~=68.2.2
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user