Compare commits

...

28 Commits

Author SHA1 Message Date
4476afc1a2 updates 2025-11-20 11:20:18 +01:00
d61260fdd3 updates 2025-11-20 00:57:15 +01:00
049898c939 updates 2025-08-17 13:34:09 +04:00
3a188cc47d new code 2025-08-17 13:33:17 +04:00
5db8f5439d Punto de restauración. 2025-07-31 09:58:38 +02:00
e1e1441892 Punto de restauración. 2025-07-16 08:58:35 +02:00
d009cb7695 update 2025-07-06 01:14:57 +02:00
5a642a4119 update 2025-07-06 01:12:08 +02:00
74bf60101c update 2025-06-15 23:10:17 +02:00
5dd8869caf update 2025-05-08 08:29:11 +02:00
03464ffafd Adaptado a PVPlant 2025-05-08 00:11:52 +02:00
e111a985c3 Adaptado a PVPlant 2025-05-08 00:10:56 +02:00
65d57e98b7 cambios 2025-05-07 23:56:42 +02:00
86bae4f643 cambios 2025-05-07 23:42:54 +02:00
e49d0694b4 new 2025-05-07 23:42:40 +02:00
1241ee97ba algo 2025-04-14 10:05:32 +06:00
0e4b6e7fa4 algo 2025-04-04 04:32:02 +06:00
092ccb75e0 algo 2025-04-04 04:31:18 +06:00
9524e73955 reposicionado 2025-04-04 04:30:44 +06:00
a7ac8826a0 solucionado error al guardar 2025-04-04 04:30:06 +06:00
1d062a087f mejoras 2025-03-28 19:40:11 +06:00
af559092bf mejoras 2025-03-28 19:39:33 +06:00
c0291198b1 Importa carreteras y líneas 2025-03-28 19:39:06 +06:00
4981b00918 Optimize and order 2025-03-21 01:59:33 +01:00
3b38651609 primera versión 2025-03-11 21:18:59 +01:00
322830c79e primera versión 2025-03-11 21:18:47 +01:00
58bf3b890b primera versión 2025-03-11 21:13:09 +01:00
c76f541ba2 primera versión 2025-03-11 16:35:15 +01:00
72 changed files with 32929 additions and 5123 deletions

4
.idea/PVPlant.iml generated
View File

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

View File

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

View File

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

View File

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

View File

@@ -340,7 +340,44 @@ class Trench(ArchComponent.Component):
p2.z = 0 p2.z = 0
return p2.sub(p1) return p2.sub(p1)
def getsegments(wire): def getsegments(wire): #deepseek
"""Divide un wire en segmentos rectos basados en cambios de dirección (sin splitWiresByCurvature)"""
import Part
from math import degrees
segments = []
current_segment = []
angle_threshold = 1.0 # Grados para considerar cambio de dirección
def get_angle(v1, v2):
return degrees(v1.getAngle(v2))
edges = wire.Edges
for i in range(len(edges)):
if i == 0:
current_segment.append(edges[i])
continue
prev_edge = edges[i - 1]
curr_edge = edges[i]
# Vectores de dirección
v1 = prev_edge.tangentAt(prev_edge.FirstParameter)
v2 = curr_edge.tangentAt(curr_edge.FirstParameter)
angle = get_angle(v1, v2)
if angle > angle_threshold:
segments.append(Part.Wire(current_segment))
current_segment = [curr_edge]
else:
current_segment.append(curr_edge)
if current_segment:
segments.append(Part.Wire(current_segment))
return segments
def getsegments_old(wire):
import math import math
segments = [] segments = []
@@ -381,13 +418,6 @@ class Trench(ArchComponent.Component):
pts_plane.append(tmp) pts_plane.append(tmp)
path_plane = Part.makePolygon(pts_plane) path_plane = Part.makePolygon(pts_plane)
'''o1 = path_plane.makeOffset2D(d, 2, False, True, True)
o2 = path_plane.makeOffset2D(-d, 2, False, True, True)
points = calculateOffset(o1)
points.insert(0, points.pop(1))
points.reverse()
points2 = calculateOffset(o2)'''
points = self.calculateOffset(path_plane, d) points = self.calculateOffset(path_plane, d)
points2 = self.calculateOffset(path_plane, -d) points2 = self.calculateOffset(path_plane, -d)
@@ -1018,7 +1048,7 @@ class semiAutomaticTrench:
self.unregister_editing_callbacks() self.unregister_editing_callbacks()
class CommandTrench: # V1: '''class CommandTrench: # V1:
"""Gui command for the Line tool.""" """Gui command for the Line tool."""
def GetResources(self): def GetResources(self):
@@ -1107,5 +1137,5 @@ if FreeCAD.GuiUp:
FreeCADGui.addCommand('PVPlantTrench', CommandTrench()) FreeCADGui.addCommand('PVPlantTrench', CommandTrench())
FreeCADGui.addCommand('PVPlantSemiAutomaticTrench', CommandSemiAutomaticTrench()) FreeCADGui.addCommand('PVPlantSemiAutomaticTrench', CommandSemiAutomaticTrench())
FreeCADGui.addCommand('Trenches', CommandTrenchGroup()) FreeCADGui.addCommand('Trenches', CommandTrenchGroup())'''

View File

@@ -47,16 +47,16 @@ except AttributeError:
import PVPlantResources import PVPlantResources
def makeCable(base = None): def makeCable(name="Cable"):
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Cable") obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
obj.Label = name
Cable(obj) Cable(obj)
ViewProviderCable(obj.ViewObject) ViewProviderCable(obj.ViewObject)
if base:
obj.Base = base
return obj return obj
class Cable(ArchComponent.Component): class Cable(ArchComponent.Component):
"A Base Frame Obcject - Class" "A Cable Obcject - Class"
def __init__(self, obj): def __init__(self, obj):
ArchComponent.Component.__init__(self, obj) ArchComponent.Component.__init__(self, obj)
@@ -254,48 +254,10 @@ class Cable(ArchComponent.Component):
return val.Placement.Base return val.Placement.Base
def execute(self, obj): def execute(self, obj):
import Part, DraftGeomUtils ''' No hacer nada. Es un componente sin shape'''
import Draft
if obj.Base:
w = obj.Base.Shape.SubShapes[1].SubShapes[0]
w = DraftGeomUtils.filletWire(w, 150)
else:
return
"""if obj.Base:
# Si tiene ruta, dibujar ruteado
import PVPlantTrench as trench
if isinstance(obj.Base, trench.Trench):
w = obj.Base.Shape.SubShapes[0]
else:
w = obj.Base.Shape
elif obj.From and obj.Name:
'''line = Part.LineSegment()
line.StartPoint = getPoint(obj.From)
line.EndPoint = getPoint(obj.To)
w = Part.Wire(line.toShape())'''
w = Part.makePolygon([self.getPoint(obj.From), self.getPoint(obj.To)])
else:
return"""
r = obj.ExternalDiameter.Value / 2
p = Part.Wire([Part.Circle(FreeCAD.Vector(0, 0, 0),
FreeCAD.Vector(0, 1, 0),
r).toShape()])
c = obj.Offset
c.x -= r
c.z += r
v1 = w.Vertexes[1].Point - w.Vertexes[0].Point
v2 = DraftGeomUtils.getNormal(p)
p.Placement.Base = w.Vertexes[0].Point + c
p.Placement.Rotation = FreeCAD.Rotation(v2, v1)
obj.Shape = w.makePipeShell([p], True, False, 0)
obj.Distance = w.Length
class ViewProviderCable(ArchComponent.ViewProviderComponent): class ViewProviderCable(ArchComponent.ViewProviderComponent): #hace falta??
def __init__(self, vobj): def __init__(self, vobj):
ArchComponent.ViewProviderComponent.__init__(self, vobj) ArchComponent.ViewProviderComponent.__init__(self, vobj)
@@ -311,15 +273,7 @@ class CommandCable:
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Calcular el BOQ de la")} 'ToolTip': QT_TRANSLATE_NOOP("Placement", "Calcular el BOQ de la")}
def Activated(self): def Activated(self):
import Draft makeCable()
sel = FreeCADGui.Selection.getSelection()
wire = None
for obj in sel:
if Draft.getType(obj) == "Wire":
wire = obj
break
makeCable(wire)
FreeCAD.ActiveDocument.recompute() FreeCAD.ActiveDocument.recompute()
def IsActive(self): def IsActive(self):

View File

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

392
Electrical/group.py Normal file
View File

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

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2017 - Amritpal Singh <amrit3701@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 *
# * *
# ***************************************************************************
__title__ = "RebarCommands"
__author__ = "Amritpal Singh"
__url__ = "https://www.freecadweb.org"
from pathlib import Path
import FreeCADGui, FreeCAD
from PySide import QtGui, QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
from PVPlantResources import DirIcons as DirIcons
import os
class CommandExportToPVSyst:
"Export to PVSyst"
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "PVsyst.png")),
'Accel': "E, P",
'MenuText': QT_TRANSLATE_NOOP("Outputs", "Export to PVSyst"),
'ToolTip': QT_TRANSLATE_NOOP("Outputs", "Exportar a PVSyst")}
@staticmethod
def Activated():
from Export import exportPVSyst
taskd = exportPVSyst.PVSystTaskPanel()
# taskd.show()
FreeCADGui.Control.showDialog(taskd)
@staticmethod
def IsActive():
if FreeCAD.ActiveDocument:
return True
else:
return False
if FreeCAD.GuiUp:
from Export import PVPlantBOQMechanical, PVPlantBOQElectrical, PVPlantBOQCivil
from Export import exportDXF, exportKMZ
FreeCADGui.addCommand('BOQMechanical', PVPlantBOQMechanical.CommandBOQMechanical())
FreeCADGui.addCommand('BOQElectrical', PVPlantBOQElectrical.CommandBOQElectrical())
FreeCADGui.addCommand('BOQCivil', PVPlantBOQCivil.CommandBOQCivil())
FreeCADGui.addCommand('exportDXF', exportDXF.CommandExportDXF())
FreeCADGui.addCommand('exportToPVSyst', CommandExportToPVSyst())
FreeCADGui.addCommand('exportKMZ', exportKMZ.CommandExportKMZ())
Exportlist = ["BOQCivil",
"BOQMechanical",
"BOQElectrical",
"Separator",
"exportDXF",
# "importDXF",
"exportToPVSyst",
"exportKMZ",
]

View File

@@ -59,7 +59,7 @@ def makeBOQCivil():
class _CommandBOQCivil: class CommandBOQCivil:
def GetResources(self): def GetResources(self):
return {'Pixmap': str(os.path.join(DirIcons, "boqc.svg")), return {'Pixmap': str(os.path.join(DirIcons, "boqc.svg")),
@@ -76,5 +76,5 @@ class _CommandBOQCivil:
else: else:
return False return False
if FreeCAD.GuiUp: '''if FreeCAD.GuiUp:
FreeCADGui.addCommand('BOQCivil', _CommandBOQCivil()) FreeCADGui.addCommand('BOQCivil', _CommandBOQCivil())'''

View File

@@ -83,7 +83,8 @@ def makeBOQElectrical():
b = mgb.get_boundary(m) b = mgb.get_boundary(m)
Part.show(b) Part.show(b)
class _CommandBOQElectrical:
class CommandBOQElectrical:
def GetResources(self): def GetResources(self):
return {'Pixmap': str(os.path.join(DirIcons, "boqe.svg")), return {'Pixmap': str(os.path.join(DirIcons, "boqe.svg")),
'Accel': "R, E", 'Accel': "R, E",
@@ -100,5 +101,5 @@ class _CommandBOQElectrical:
return False return False
if FreeCAD.GuiUp: '''if FreeCAD.GuiUp:
FreeCADGui.addCommand('BOQElectrical', _CommandBOQElectrical()) FreeCADGui.addCommand('BOQElectrical', _CommandBOQElectrical())'''

View File

@@ -20,32 +20,43 @@
# * * # * *
# *********************************************************************** # ***********************************************************************
import os
import platform
import subprocess
from typing import Dict, List
import FreeCAD import FreeCAD
if FreeCAD.GuiUp:
import FreeCADGui, os
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
else:
# \cond
def translate(ctxt, txt):
return txt
def QT_TRANSLATE_NOOP(ctxt, txt):
return txt
# \endcond
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
import openpyxl import openpyxl
from openpyxl.styles import Alignment, Border, Side, PatternFill, Font from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.workbook import Workbook
from openpyxl.worksheet.worksheet import Worksheet
import PVPlantResources import PVPlantResources
import PVPlantSite import PVPlantSite
if FreeCAD.GuiUp:
from PySide.QtCore import QT_TRANSLATE_NOOP
__title__ = "PVPlant Mechanical BOQ"
__author__ = "Javier Braña"
__url__ = "http://www.sogos-solar.com"
# Constants
SCALE = 0.001 # Millimeters to meters
THIN_BORDER = Border(
top=Side(border_style="thin", color="7DA4B8"),
left=Side(border_style="thin", color="7DA4B8"),
right=Side(border_style="thin", color="7DA4B8"),
bottom=Side(border_style="thin", color="7DA4B8")
)
HEADER_FILL = PatternFill("solid", fgColor="7DA4B8")
HEADER_FONT = Font(name='Arial', size=10, bold=True, color="FFFFFF")
DATA_FONT = Font(name='Arial', size=10)
CENTER_ALIGN = Alignment(horizontal="center", vertical="center")
# Estilos: # Estilos:
thin = Side(border_style="thin", color="7DA4B8") thin = Side(border_style="thin", color="7DA4B8")
double = Side(border_style="double", color="ff0000") double = Side(border_style="double", color="ff0000")
@@ -56,6 +67,16 @@ border_fat = Border(top=thin, left=thin, right=thin, bottom=thin)
scale = 0.001 # milimeters to meter scale = 0.001 # milimeters to meter
def open_file(path: str) -> None:
"""Open a file or directory using the system's default handler"""
if platform.system() == "Windows":
os.startfile(path)
elif platform.system() == "Darwin":
subprocess.Popen(["open", path])
else:
subprocess.Popen(["xdg-open", path])
def style_range(ws, cell_range, border=Border(), fill=None, font=None, alignment=None): def style_range(ws, cell_range, border=Border(), fill=None, font=None, alignment=None):
""" """
Apply styles to a range of cells as if they were a single cell. Apply styles to a range of cells as if they were a single cell.
@@ -94,124 +115,118 @@ def style_range(ws, cell_range, border=Border(), fill=None, font=None, alignment
for c in row: for c in row:
c.fill = fill c.fill = fill
def spreadsheetBOQFrames(sheet, sel):
sheet['A1'] = 'Index'
sheet['B1'] = 'Frame'
sheet['C1'] = 'Frame Type'
sheet['D1'] = 'X'
sheet['E1'] = 'Y'
sheet['F1'] = 'Z'
sheet['G1'] = 'Angle N-S'
sheet['H1'] = 'Angle L-W'
sheet['I1'] = 'Nº Poles'
sheet.column_dimensions['A'].width = 8 def create_sheet_headers(sheet: Worksheet, headers: List[str], widths: Dict[str, float]) -> None:
sheet.column_dimensions['B'].width = 30 """Create standardized sheet headers."""
sheet.column_dimensions['C'].width = 20 for col, header in enumerate(headers, start=1):
sheet.column_dimensions['D'].width = 20 cell = sheet.cell(row=1, column=col, value=header)
sheet.column_dimensions['E'].width = 20 cell.fill = HEADER_FILL
sheet.column_dimensions['F'].width = 20 cell.font = HEADER_FONT
sheet.column_dimensions['G'].width = 15 cell.alignment = CENTER_ALIGN
sheet.column_dimensions['H'].width = 15
sheet.column_dimensions['I'].width = 15
sheet.row_dimensions[1].height = 40
style_range(sheet, 'A1:I1', for col, width in widths.items():
border=Border(top=thin, left=thin, right=thin, bottom=thin), sheet.column_dimensions[col].width = width
fill=PatternFill("solid", fgColor="7DA4B8"),
font=Font(name='Quicksand', size=10, b=True, color="FFFFFF"),
alignment=Alignment(horizontal="center", vertical="center"))
for ind in range(0, len(sel)): sheet.row_dimensions[1].height = 30
row = ind + 2 style_range(sheet, f'A1:{chr(64 + len(headers))}1',
sheet['A{0}'.format(row)] = ind + 1 border=THIN_BORDER,
sheet['B{0}'.format(row)] = sel[ind].Label fill=HEADER_FILL,
sheet['C{0}'.format(row)] = sel[ind].Setup.Label font=HEADER_FONT,
sheet['D{0}'.format(row)] = sel[ind].Placement.Base.x * scale alignment=CENTER_ALIGN)
sheet['E{0}'.format(row)] = sel[ind].Placement.Base.y * scale
sheet['R{0}'.format(row)] = sel[ind].Placement.Base.z * scale
sheet['G{0}'.format(row)] = sel[ind].Placement.Rotation.toEuler()[0]
sheet['H{0}'.format(row)] = sel[ind].Placement.Rotation.toEuler()[1]
sheet['I{0}'.format(row)] = sel[ind].Setup.NumberPole.Value
style_range(sheet, 'A' + str(row) + ':I' + str(row),
border=Border(top=thin, left=thin, right=thin, bottom=thin),
font=Font(name='Quicksand', size=10),
alignment=Alignment(horizontal="center", vertical="center"))
def spreadsheetBOQPoles(sheet, sel):
import MeshPart as mp
from Mechanical.Frame import PVPlantFrame
# Data: def spreadsheetBOQFrames(sheet: Worksheet, selection: List[FreeCAD.DocumentObject]) -> None:
terrain = PVPlantSite.get().Terrain.Mesh # Shape """Generate frames information sheet."""
headers = [
'Index', 'Frame', 'Frame Type', 'X (m)', 'Y (m)', 'Z (m)',
'Angle N-S', 'Angle L-W', 'Nº Poles'
]
widths = {'A': 8, 'B': 30, 'C': 20, 'D': 15, 'E': 15,
'F': 15, 'G': 15, 'H': 15, 'I': 10}
# Headers: create_sheet_headers(sheet, headers, widths)
sheet['A1'] = 'Frame'
sheet['B1'] = 'Pole'
sheet['C1'] = 'Pole Type'
sheet['D1'] = 'X'
sheet['E1'] = 'Y'
sheet['F1'] = 'Z frame attach'
sheet['G1'] = 'Z aerial head'
sheet['H1'] = 'Pole length'
sheet['I1'] = 'Pole aerial length'
sheet['J1'] = 'Pole terrain enter length'
sheet.column_dimensions['A'].width = 30 for idx, obj in enumerate(selection, start=2):
sheet.column_dimensions['B'].width = 8 placement = obj.Placement
sheet.column_dimensions['C'].width = 20 sheet[f'A{idx}'] = idx - 1
sheet.column_dimensions['D'].width = 20 sheet[f'B{idx}'] = obj.Label
sheet.column_dimensions['E'].width = 20 sheet[f'C{idx}'] = obj.Setup.Label
sheet.column_dimensions['F'].width = 20 sheet[f'D{idx}'] = placement.Base.x * SCALE
sheet.column_dimensions['G'].width = 20 sheet[f'E{idx}'] = placement.Base.y * SCALE
sheet.column_dimensions['H'].width = 20 sheet[f'F{idx}'] = placement.Base.z * SCALE
sheet.column_dimensions['I'].width = 20 sheet[f'G{idx}'] = placement.Rotation.toEuler()[0]
sheet.column_dimensions['J'].width = 20 sheet[f'H{idx}'] = placement.Rotation.toEuler()[1]
sheet.row_dimensions[1].height = 40 sheet[f'I{idx}'] = obj.Setup.NumberPole.Value
style_range(sheet, 'A1:J1',
border=Border(top=thin, left=thin, right=thin, bottom=thin), style_range(sheet, f'A{idx}:I{idx}',
fill=PatternFill("solid", fgColor="7DA4B8"), border=THIN_BORDER,
font=Font(name='Quicksand', size=11, b=True, color="FFFFFF"), font=DATA_FONT,
alignment=Alignment(horizontal="center", vertical="center")) alignment=CENTER_ALIGN)
sheet['A2'] = ""
def spreadsheetBOQPoles(sheet: Worksheet, selection: List[FreeCAD.DocumentObject]) -> None:
"""Generate poles information sheet."""
headers = [
'Frame', 'Pole', 'Pole Type', 'X (m)', 'Y (m)', 'Z Frame Attach (m)',
'Z Aerial Head (m)', 'Pole Length (m)', 'Aerial Length (m)',
'Terrain Enter Length (m)'
]
widths = {chr(65 + i): 20 for i in range(10)}
widths['A'] = 30
widths['B'] = 10
create_sheet_headers(sheet, headers, widths)
sheet.row_dimensions[2].height = 5 sheet.row_dimensions[2].height = 5
data = {"Frame": [],
#"FrameType": [],
"Pole": [],
"PoleType": [],
"PoleLength": [],
"Center": [],
"Head": []}
cnt = 0 import MeshPart as mp
for frame_ind, frame in enumerate(sel): from Mechanical.Frame import PVPlantFrame
poles = frame.Shape.SubShapes[1].SubShapes[0].SubShapes # Data:
numpoles = int(frame.Setup.NumberPole.Value) terrain = PVPlantSite.get().Terrain.Mesh # Shape
seq = frame.Setup.PoleSequence poles_data = []
if len(seq) < numpoles:
seq = PVPlantFrame.getarray(frame.Setup.PoleSequence, numpoles)
for pole_ind in range(numpoles):
pole = poles[pole_ind]
poletype = frame.Setup.PoleType[seq[pole_ind]]
data["Frame"].append(frame.Label)
#data["FrameType"].append(frame.Setup.Label)
data["Pole"].append(pole_ind + 1)
data["PoleType"].append(poletype.Label)
data["PoleLength"].append(int(poletype.Height))
data["Center"].append(pole.BoundBox.Center)
data["Head"].append(pole.BoundBox.ZMax)
cnt += 1
pts = mp.projectPointsOnMesh(data["Center"], terrain, FreeCAD.Vector(0, 0, 1)) for frame in selection:
#if cnt == len(pts): try:
data["Soil"] = pts poles = frame.Shape.SubShapes[1].SubShapes[0].SubShapes
num_poles = int(frame.Setup.NumberPole.Value)
sequence = frame.Setup.PoleSequence
if len(sequence) < num_poles:
sequence = PVPlantFrame.getarray(frame.Setup.PoleSequence, num_poles)
for pole_idx in range(num_poles):
pole = poles[pole_idx]
pole_type = frame.Setup.PoleType[sequence[pole_idx]]
center = pole.BoundBox.Center
poles_data.append({
'frame': frame.Label,
'number': pole_idx + 1,
'type': pole_type.Label,
'center': center,
'head_z': pole.BoundBox.ZMax,
'length': pole_type.Height.Value
})
except Exception as e:
FreeCAD.Console.PrintError(f"Error processing frame {frame.Label}: {str(e)}\n")
# Project points on terrain
try:
import MeshPart
points = [data['center'] for data in poles_data]
terrain_z = MeshPart.projectPointsOnMesh(points, terrain, FreeCAD.Vector(0, 0, 1))
for data, z in zip(poles_data, terrain_z):
data['terrain_z'] = z.z
except Exception as e:
FreeCAD.Console.PrintError(f"Terrain projection failed: {str(e)}\n")
for data in poles_data:
data['terrain_z'] = 0
# Write data to sheet
row = 3 row = 3
group_from = row group_from = row
f = data["Frame"][0] f = poles_data[0]['frame']
for i in range(0, len(data["Frame"])): for i, data in enumerate(poles_data):
if f != data["Frame"][i]: if f != data["frame"]:
style_range(sheet, 'A' + str(group_from) + ':F' + str(row - 1), style_range(sheet, 'A' + str(group_from) + ':F' + str(row - 1),
border=Border(top=thin, left=thin, right=thin, bottom=thin), border=Border(top=thin, left=thin, right=thin, bottom=thin),
font=Font(name='Quicksand', size=11, ), font=Font(name='Quicksand', size=11, ),
@@ -221,27 +236,28 @@ def spreadsheetBOQPoles(sheet, sel):
border=Border(top=thin, left=thin, right=thin, bottom=thin), border=Border(top=thin, left=thin, right=thin, bottom=thin),
font=Font(name='Quicksand', size=11, ), font=Font(name='Quicksand', size=11, ),
alignment=Alignment(horizontal="center", vertical="center")) alignment=Alignment(horizontal="center", vertical="center"))
#sheet['A{0}'.format(row)] = "" # sheet['A{0}'.format(row)] = ""
sheet.row_dimensions[row].height = 5 sheet.row_dimensions[row].height = 5
row += 1 row += 1
f = data["Frame"][i] f = data["frame"]
group_from = row group_from = row
sheet['A{0}'.format(row)] = data['Frame'][i] sheet['A{0}'.format(row)] = data['frame']
sheet['B{0}'.format(row)] = data['Pole'][i] sheet['B{0}'.format(row)] = data['number']
sheet['C{0}'.format(row)] = data['PoleType'][i] sheet['C{0}'.format(row)] = data['type']
sheet['D{0}'.format(row)] = round(data['Center'][i].x, 0) * scale sheet['D{0}'.format(row)] = round(data['center'].x, 0) * scale
sheet['D{0}'.format(row)].number_format = "0.000" sheet['D{0}'.format(row)].number_format = "0.000"
sheet['E{0}'.format(row)] = round(data['Center'][i].y, 0) * scale sheet['E{0}'.format(row)] = round(data['center'].y, 0) * scale
sheet['E{0}'.format(row)].number_format = "0.000" sheet['E{0}'.format(row)].number_format = "0.000"
try: try:
sheet['F{0}'.format(row)] = round(data['Soil'][i].z, 0) * scale sheet['F{0}'.format(row)] = round(data['terrain_z'].z, 0) * scale
sheet['F{0}'.format(row)].number_format = "0.000" sheet['F{0}'.format(row)].number_format = "0.000"
except: except:
pass pass
sheet['G{0}'.format(row)] = round(data['Head'][i]) * scale
sheet['G{0}'.format(row)] = round(data['head_z']) * scale
sheet['G{0}'.format(row)].number_format = "0.000" sheet['G{0}'.format(row)].number_format = "0.000"
sheet['H{0}'.format(row)] = data["PoleLength"][i] * scale sheet['H{0}'.format(row)] = data["length"] * scale
sheet['H{0}'.format(row)].number_format = "0.000" sheet['H{0}'.format(row)].number_format = "0.000"
sheet['I{0}'.format(row)] = '=G{0}-F{0}'.format(row) sheet['I{0}'.format(row)] = '=G{0}-F{0}'.format(row)
sheet['I{0}'.format(row)].number_format = "0.000" sheet['I{0}'.format(row)].number_format = "0.000"
@@ -250,7 +266,7 @@ def spreadsheetBOQPoles(sheet, sel):
style_range(sheet, 'A' + str(row) + ':J' + str(row), style_range(sheet, 'A' + str(row) + ':J' + str(row),
border=Border(top=thin, left=thin, right=thin, bottom=thin), border=Border(top=thin, left=thin, right=thin, bottom=thin),
font=Font(name='Quicksand', size=11,), font=Font(name='Quicksand', size=11, ),
alignment=Alignment(horizontal="center", vertical="center")) alignment=Alignment(horizontal="center", vertical="center"))
row += 1 row += 1
@@ -294,7 +310,7 @@ def spreadsheetBOQPanelCollision(sheet, sel):
sheet['G{0}'.format(ind + 2)] = sel[ind].Placement.Rotation.toEuler()[1] sheet['G{0}'.format(ind + 2)] = sel[ind].Placement.Rotation.toEuler()[1]
sheet['H{0}'.format(ind + 2)] = sel[ind].NumberPole.Value sheet['H{0}'.format(ind + 2)] = sel[ind].NumberPole.Value
class _CommandBOQMechanical: class CommandBOQMechanical:
def GetResources(self): def GetResources(self):
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "boqm.svg")), return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "boqm.svg")),
'Accel': "R, M", 'Accel': "R, M",
@@ -302,37 +318,39 @@ class _CommandBOQMechanical:
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Calcular el BOQ de la")} 'ToolTip': QT_TRANSLATE_NOOP("Placement", "Calcular el BOQ de la")}
def Activated(self): def Activated(self):
# make file global: doc = FreeCAD.ActiveDocument
#sel = FreeCAD.ActiveDocument.findObjects(Name="Tracker")
sel = []
for obj in FreeCAD.ActiveDocument.Objects:
'''if not hasattr(obj, "Proxy"):
continue
if issubclass(obj.Proxy.__class__, PVPlantRack.Frame):
objects.append(obj)'''
if obj.Name.startswith("Tracker") and not obj.Name.startswith("TrackerSetup"):
sel.append(obj)
sel = sorted(sel, key=lambda x: x.Label)
if len(sel) > 0:
path = os.path.dirname(FreeCAD.ActiveDocument.FileName)
filename = os.path.join(path, "BOQMechanical.xlsx")
mywb = openpyxl.Workbook()
sheet = mywb.active
sheet.title = 'Frames information'
spreadsheetBOQFrames(sheet, sel)
sheet = mywb.create_sheet("Poles information") if not doc or not doc.FileName:
spreadsheetBOQPoles(sheet, sel) FreeCAD.Console.PrintError("Document must be saved first\n")
mywb.save(filename) return
print("Se ha generado el BOQMechanical: ") try:
print(filename) sel = [obj for obj in FreeCAD.ActiveDocument.Objects if (hasattr(obj, "Proxy") and (obj.Proxy.Type == "Tracker"))]
'''import sys
path = r'C:\Program Files (x86)\IronPython 2.7\Lib'
sys.path.append(path)'''
import subprocess if not sel:
subprocess.Popen('explorer ' + path) FreeCAD.Console.PrintWarning("No valid trackers found\n")
return
path = os.path.dirname(doc.FileName)
filename = os.path.join(path, "BOQ_Mechanical.xlsx")
sel = sorted(sel, key=lambda x: x.Label)
wb = openpyxl.Workbook()
wb.remove(wb.active) # Remove default sheet
frames_sheet = wb.create_sheet("Frames Information")
spreadsheetBOQFrames(frames_sheet, sel)
poles_sheet = wb.create_sheet("Poles information")
spreadsheetBOQPoles(poles_sheet , sel)
wb.save(filename)
FreeCAD.Console.PrintMessage(f"Report generated: {filename}\n")
open_file(path)
except Exception as e:
FreeCAD.Console.PrintError(f"Error generating BOQ: {str(e)}\n")
def IsActive(self): def IsActive(self):
if FreeCAD.ActiveDocument: if FreeCAD.ActiveDocument:
@@ -340,5 +358,3 @@ class _CommandBOQMechanical:
else: else:
return False return False
if FreeCAD.GuiUp:
FreeCADGui.addCommand('BOQMechanical', _CommandBOQMechanical())

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>715</width> <width>462</width>
<height>520</height> <height>282</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -33,19 +33,197 @@
</property> </property>
<widget class="QWidget" name="tab"> <widget class="QWidget" name="tab">
<attribute name="title"> <attribute name="title">
<string>Tab 1</string> <string>Layers</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTableWidget" name="tableLayers">
<property name="showGrid">
<bool>false</bool>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<row/>
<row/>
<row/>
<column>
<property name="text">
<string>Sel</string>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Color</string>
</property>
</column>
<column>
<property name="text">
<string>LineType</string>
</property>
</column>
<column>
<property name="text">
<string>Lineweight</string>
</property>
</column>
<item row="2" column="1">
<property name="text">
<string/>
</property>
</item>
</widget>
</item>
</layout>
</widget> </widget>
<widget class="QWidget" name="tab_2"> <widget class="QWidget" name="tab_2">
<attribute name="title"> <attribute name="title">
<string>Tab 2</string> <string>Objetos a exportar</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QTableWidget" name="tableObjects">
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<row/>
<row/>
<row/>
<row>
<property name="text">
<string>New Row</string>
</property>
</row>
<row>
<property name="text">
<string>New Row</string>
</property>
</row>
<column>
<property name="text">
<string>Sel</string>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Color</string>
</property>
</column>
<column>
<property name="text">
<string>LineType</string>
</property>
</column>
<column>
<property name="text">
<string>Lineweight</string>
</property>
</column>
<item row="0" column="0">
<property name="text">
<string/>
</property>
</item>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>Planos en papel a crear</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTableWidget" name="tablePapers">
<property name="showGrid">
<bool>false</bool>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<row/>
<row/>
<row/>
<row>
<property name="text">
<string>New Row</string>
</property>
</row>
<row>
<property name="text">
<string>New Row</string>
</property>
</row>
<column>
<property name="text">
<string>Sel</string>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Color</string>
</property>
</column>
<column>
<property name="text">
<string>LineType</string>
</property>
</column>
<column>
<property name="text">
<string>Lineweight</string>
</property>
</column>
<item row="0" column="0">
<property name="text">
<string/>
</property>
</item>
</widget>
</item>
</layout>
</widget> </widget>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QWidget" name="widget" native="true"> <widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<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> <item>
<widget class="QPushButton" name="buttonAcept"> <widget class="QPushButton" name="buttonAcept">
<property name="text"> <property name="text">

278
Export/exportKMZ.py Normal file
View File

@@ -0,0 +1,278 @@
# -*- coding: utf-8 -*-
import os
import sys
import zipfile
import tempfile
import shutil
import xml.etree.ElementTree as ET
from PySide2 import QtWidgets, QtCore, QtGui
import FreeCAD
import Mesh
import Part
import Import
import pyproj
import simplekml
import os
from datetime import datetime
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
from DraftTools import translate
else:
def translate(ctxt, txt):
return txt
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
from PVPlantResources import DirIcons as DirIcons
# Verificación de dependencias
try:
from pyproj import Transformer
except ImportError:
QtWidgets.QMessageBox.critical(None, "Error", "pyproj no está instalado.")
sys.exit(1)
try:
import simplekml
except ImportError:
QtWidgets.QMessageBox.critical(None, "Error", "simplekml no está instalado.")
sys.exit(1)
class ExportKMZDialog(QtWidgets.QDialog):
def __init__(self):
super(ExportKMZDialog, self).__init__()
self.setWindowTitle("Exportar a KMZ")
self.layout = QtWidgets.QVBoxLayout(self)
# Selección de archivo FCStd
self.file_layout = QtWidgets.QHBoxLayout()
self.fcstd_line = QtWidgets.QLineEdit()
self.browse_fcstd_btn = QtWidgets.QPushButton("Examinar...")
self.browse_fcstd_btn.clicked.connect(self.browse_fcstd)
self.file_layout.addWidget(self.fcstd_line)
self.file_layout.addWidget(self.browse_fcstd_btn)
self.layout.addLayout(self.file_layout)
# Sistema de coordenadas
self.crs_label = QtWidgets.QLabel("Sistema de coordenadas origen:")
self.crs_display = QtWidgets.QLabel("No encontrado")
self.layout.addWidget(self.crs_label)
self.layout.addWidget(self.crs_display)
# Archivo de salida KMZ
self.output_layout = QtWidgets.QHBoxLayout()
self.kmz_line = QtWidgets.QLineEdit()
self.browse_kmz_btn = QtWidgets.QPushButton("Examinar...")
self.browse_kmz_btn.clicked.connect(self.browse_kmz)
self.output_layout.addWidget(self.kmz_line)
self.output_layout.addWidget(self.browse_kmz_btn)
self.layout.addWidget(QtWidgets.QLabel("Archivo KMZ de salida:"))
self.layout.addLayout(self.output_layout)
# Progreso y logs
self.progress = QtWidgets.QProgressBar()
self.log = QtWidgets.QTextEdit()
self.log.setReadOnly(True)
self.layout.addWidget(self.progress)
self.layout.addWidget(self.log)
# Botones
self.buttons = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject)
self.layout.addWidget(self.buttons)
path = os.path.join(os.path.dirname(FreeCAD.ActiveDocument.FileName), "outputs", "kmz")
if not os.path.exists(path):
os.makedirs(path)
name = datetime.now().strftime("%Y%m%d%H%M%S") + "-" + FreeCAD.ActiveDocument.Name
self.filename = os.path.join(path, name) + ".kmz"
def browse_fcstd(self):
path, _ = QtWidgets.QFileDialog.getOpenFileName(
self, "Seleccionar archivo FreeCAD", "", "*.FCStd")
if path:
self.fcstd_line.setText(path)
crs = self.get_crs_from_fcstd(path)
self.crs_display.setText(crs if crs else "No encontrado")
def get_crs_from_fcstd(self, path):
try:
with zipfile.ZipFile(path, 'r') as z:
doc_xml = z.read('Document.xml')
root = ET.fromstring(doc_xml)
for prop in root.findall('.//Property[@name="CoordinateSystem"]'):
if prop.get('type') == 'App::PropertyString':
return prop.find('String').get('value')
except Exception as e:
self.log.append(f"Error leyendo CRS: {str(e)}")
return None
def browse_kmz(self):
path, _ = QtWidgets.QFileDialog.getSaveFileName(
self, "Guardar KMZ", "", "*.kmz")
if path:
self.kmz_line.setText(path)
class ExportKMZ(QtCore.QObject):
progress_updated = QtCore.Signal(int)
log_message = QtCore.Signal(str)
def __init__(self, input_path, output_path, crs):
super().__init__()
self.input_path = input_path
self.output_path = output_path
self.crs = crs
self.doc = None
self.transformer = pyproj.Transformer.from_crs(crs, 'EPSG:4326', always_xy=True)
self.temp_dir = tempfile.mkdtemp()
self.kml = simplekml.Kml()
self.model_folder = self.kml.newfolder(name='Modelos 3D')
self.drawing_folder = self.kml.newfolder(name='Dibujos 2D')
def process(self):
try:
self.doc = FreeCAD.openDocument(self.input_path)
FreeCAD.setActiveDocument(self.doc.Name)
self.process_objects()
self.save_kmz()
self.log_message.emit("Exportación completada exitosamente.")
return True
except Exception as e:
self.log_message.emit(f"Error: {str(e)}")
return False
finally:
if self.doc:
FreeCAD.closeDocument(self.doc.Name)
shutil.rmtree(self.temp_dir, ignore_errors=True)
def process_objects(self):
total = len(self.doc.Objects)
for i, obj in enumerate(self.doc.Objects):
try:
if hasattr(obj, 'Shape') and obj.Shape.Volume > 0:
self.process_3d(obj)
elif obj.TypeId == 'Sketcher::SketchObject':
self.process_2d(obj)
except Exception as e:
self.log_message.emit(f"Error procesando {obj.Name}: {str(e)}")
self.progress_updated.emit(int((i + 1) / total * 100))
def process_3d(self, obj):
placement = obj.getGlobalPlacement()
x, y, z = placement.Base.x, placement.Base.y, placement.Base.z
lon, lat = self.transformer.transform(x, y)
temp_doc = FreeCAD.newDocument("TempExport", hidden=True)
temp_obj = temp_doc.addObject('Part::Feature', 'TempObj')
temp_obj.Shape = obj.Shape.copy()
temp_obj.Placement = FreeCAD.Placement()
model_path = os.path.join(self.temp_dir, f"{obj.Name}.dae")
Import.export(temp_obj, model_path)
FreeCAD.closeDocument(temp_doc.Name)
color = obj.ViewObject.ShapeColor
kml_color = simplekml.Color.rgb(
int(color[0] * 255), int(color[1] * 255), int(color[2] * 255))
model = self.model_folder.newmodel(name=obj.Name)
model.altitudemode = simplekml.AltitudeMode.relativetoground
model.longitude = lon
model.latitude = lat
model.altitude = z
model.model = simplekml.Model()
model.model.link.href = f"models/{obj.Name}.dae"
model.style = simplekml.Style()
model.style.polystyle.color = kml_color
def process_2d(self, obj):
coords = []
placement = obj.getGlobalPlacement()
for geom in obj.Geometry:
for point in geom.StartPoint, geom.EndPoint:
global_point = placement.multVec(point)
lon, lat = self.transformer.transform(global_point.x, global_point.y)
coords.append((lon, lat, global_point.z))
if len(coords) < 3:
return
poly = self.drawing_folder.newpolygon(name=obj.Name)
poly.outerboundaryis = coords
poly.altitudemode = simplekml.AltitudeMode.relativetoground
poly.style.polystyle.color = simplekml.Color.rgb(255, 0, 0, 128)
def save_kmz(self):
kml_path = os.path.join(self.temp_dir, "doc.kml")
self.kml.save(kml_path)
with zipfile.ZipFile(self.output_path, 'w') as zipf:
zipf.write(kml_path, arcname='doc.kml')
for root, _, files in os.walk(self.temp_dir):
for file in files:
if file.endswith('.dae'):
zipf.write(
os.path.join(root, file),
arcname=os.path.join('models', file))
'''
def main():
app = QtWidgets.QApplication([]) if not FreeCAD.GuiUp else QtWidgets.QApplication.instance()
dialog = ExportKMZDialog()
if dialog.exec_() == QtWidgets.QDialog.Accepted:
input_path = dialog.fcstd_line.text()
output_path = dialog.kmz_line.text()
crs = dialog.crs_display.text()
if not crs:
QtWidgets.QMessageBox.critical(None, "Error", "Sistema de coordenadas no definido.")
return
exporter = ExportKMZ(input_path, output_path, crs)
progress_dialog = QtWidgets.QProgressDialog("Exportando...", "Cancelar", 0, 100)
exporter.progress_updated.connect(progress_dialog.setValue)
exporter.log_message.connect(lambda msg: dialog.log.append(msg))
progress_dialog.show()
QtCore.QTimer.singleShot(100, exporter.process)
app.exec_()
if __name__ == "__main__":
main()
'''
class CommandExportKMZ:
def GetResources(self):
return {'Pixmap': str(os.path.join(DirIcons, "googleearth.svg")),
'Accel': "E, G",
'MenuText': "Export to KMZ",
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Export choosed layers to kmz file")}
def Activated(self):
taskd = ExportKMZDialog()
taskd.setParent(FreeCADGui.getMainWindow())
taskd.setWindowFlags(QtCore.Qt.Dialog or QtCore.Qt.Dialog)
taskd.setWindowModality(QtCore.Qt.WindowModal)
taskd.show()
def IsActive(self):
if FreeCAD.ActiveDocument:
return True
else:
return False

View File

@@ -186,8 +186,8 @@ def createPVPanel(name):
typpvpanel.noct = 45.0 # SINTC (double) (Cº) typpvpanel.noct = 45.0 # SINTC (double) (Cº)
typpvpanel.manuf = "" # Fabricante typpvpanel.manuf = "" # Fabricante
typpypanel.chr_name = "" # Nombre característico typpvpanel.chr_name = "" # Nombre característico
typpypanel.appr_status = 1 # Estado: 0: No aprobado, 1: aprobado typpvpanel.appr_status = 1 # Estado: 0: No aprobado, 1: aprobado
return typpvpanel return typpvpanel

View File

@@ -29,6 +29,15 @@ import Part
import numpy import numpy
import os import os
from xml.etree.ElementTree import Element, SubElement
import xml.etree.ElementTree as ElementTree
import datetime
from xml.dom import minidom
from numpy.matrixlib.defmatrix import matrix
from Utils import PVPlantUtils as utils
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
import FreeCADGui import FreeCADGui
from PySide import QtCore from PySide import QtCore
@@ -44,38 +53,30 @@ except AttributeError:
def _fromUtf8(s): def _fromUtf8(s):
return s return s
from PVPlantResources import DirIcons as DirIcons
## @package importDAE
# \ingroup ARCH
# \brief DAE (Collada) file format importer and exporter
#
# This module provides tools to import and export Collada (.dae) files.
__title__ = "FreeCAD Collada importer"
__author__ = "Yorik van Havre"
__url__ = "http://www.freecadweb.org"
try: try:
# Python 2 forward compatibility import collada
range = xrange COLLADA_AVAILABLE = True
except NameError: except ImportError:
pass COLLADA_AVAILABLE = False
__title__ = "FreeCAD PVSyst importer"
__author__ = "Javier Braña"
#__url__ = "http://www.freecadweb.org"
scale = 0.001 # from millimeters (FreeCAD) to meters (Collada)
def checkCollada(): def check_collada():
"checks if collada if available" """Verifica la disponibilidad de pycollada"""
if not COLLADA_AVAILABLE:
global collada FreeCAD.Console.PrintError(translate("PVPlant", "pycollada no encontrado, soporte Collada desactivado.") + "\n")
COLLADA = None return COLLADA_AVAILABLE
try:
import collada
except ImportError:
FreeCAD.Console.PrintError(translate("Arch", "pycollada not found, collada support is disabled.") + "\n")
return False
else:
return True
# 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):
@@ -84,6 +85,7 @@ def triangulate(shape):
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
mesher = p.GetInt("ColladaMesher", 0) mesher = p.GetInt("ColladaMesher", 0)
tessellation = p.GetFloat("ColladaTessellation", 1.0) tessellation = p.GetFloat("ColladaTessellation", 1.0)
grading = p.GetFloat("ColladaGrading", 0.3) grading = p.GetFloat("ColladaGrading", 0.3)
segsperedge = p.GetInt("ColladaSegsPerEdge", 1) segsperedge = p.GetInt("ColladaSegsPerEdge", 1)
segsperradius = p.GetInt("ColladaSegsPerRadius", 2) segsperradius = p.GetInt("ColladaSegsPerRadius", 2)
@@ -96,9 +98,15 @@ def triangulate(shape):
elif mesher == 1: elif mesher == 1:
return MeshPart.meshFromShape(Shape=shape, MaxLength=tessellation).Topology return MeshPart.meshFromShape(Shape=shape, MaxLength=tessellation).Topology
else: else:
return MeshPart.meshFromShape(Shape=shape, GrowthRate=grading, SegPerEdge=segsperedge, return MeshPart.meshFromShape(Shape=shape,
SegPerRadius=segsperradius, SecondOrder=secondorder, Optimize=optimize, GrowthRate=grading,
AllowQuad=allowquads).Topology SegPerEdge=segsperedge,
SegPerRadius=segsperradius,
SecondOrder=secondorder,
Optimize=optimize,
AllowQuad=allowquads
).Topology
def export(exportList, filename, tessellation=1, colors=None): def export(exportList, filename, tessellation=1, colors=None):
"""export(exportList,filename,tessellation=1,colors=None) -- exports FreeCAD contents to a DAE file. """export(exportList,filename,tessellation=1,colors=None) -- exports FreeCAD contents to a DAE file.
@@ -254,12 +262,308 @@ def export(exportList, filename, tessellation=1, colors=None):
FreeCAD.Console.PrintMessage(translate("Arch", "file %s successfully created.") % filename) FreeCAD.Console.PrintMessage(translate("Arch", "file %s successfully created.") % filename)
def exportToDAE(path):
filename = path + ".dae"
def exportToPVC(path, exportTerrain = False): def exportToPVC(path, exportTerrain=False):
filename = path + ".pvc" filename = f"{path}.pvc"
scale = 0.001 # from millimeters (FreeCAD) to meters (Collada)
# 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
@@ -300,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')
@@ -368,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'
@@ -453,7 +757,7 @@ def exportToPVC(path, exportTerrain = False):
array = SubElement(axis, 'float_array') array = SubElement(axis, 'float_array')
array.set('id', 'tracker{0}AxisFloatArray1'.format(ind)) array.set('id', 'tracker{0}AxisFloatArray1'.format(ind))
array.set('count', '3') array.set('count', '3')
array.text = '{0:.6f} {1:.6f} {2:.6f}'.format(centers[i].x, centers[i].y, centers[i].z) array.text = '{0:.6f} {1:.6f} {2:.6f}'.format(centers[ind].x, centers[ind].y, centers[ind].z)
min_phi = SubElement(frame, 'min_phi') min_phi = SubElement(frame, 'min_phi')
min_phi.text = '{0}'.format(int(obj.Setup.MinPhi.Value)) min_phi.text = '{0}'.format(int(obj.Setup.MinPhi.Value))
@@ -514,38 +818,22 @@ 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 type in frameType: for typ in frameType:
isTracker = "tracker" in type.Proxy.Type.lower() isTracker = "tracker" in typ.Proxy.Type.lower()
#TODO: Sólo para los proyectos de NAcho. Borrar #isTracker = False
isTracker = False
objectlist = FreeCAD.ActiveDocument.findObjects(Name="Tracker") objectlist = utils.findObjects("Tracker")
tmp = []
for obj in objectlist: for obj in objectlist:
if obj.Name.startswith("TrackerSetup"): if obj.Setup == typ:
continue
else:
tmp.append(obj)
objectlist = tmp.copy()
for obj in objectlist:
if obj.Setup == type:
findex = numpy.array([]) findex = numpy.array([])
modules = obj.Setup.Shape.SubShapes[0].SubShapes[0] modules = obj.Setup.Shape.SubShapes[0].SubShapes[0]
@@ -589,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])):
@@ -726,26 +1013,26 @@ def exportToH2P(path): # sólo válido para mesas
#for obj in objects: #for obj in objects:
grouptype.append(objects[0]) grouptype.append(objects[0])
for type in grouptype: for typ in grouptype:
st += 'TABLE\n' \ st += 'TABLE\n' \
'10\n' '10\n'
st += f3.format(type.Width.Value) + ',' + f3.format(type.Length.Value) + ',' + \ st += f3.format(typ.Width.Value) + ',' + f3.format(typ.Length.Value) + ',' + \
f3.format(0) + ',' + f3.format(0) + ',' + f3.format(0) + ',' + f3.format(0) + "\n" f3.format(0) + ',' + f3.format(0) + ',' + f3.format(0) + ',' + f3.format(0) + "\n"
#'#{ f3 %pvsyst.ilb.to_mm },#{f3 %pvsyst.irb.to_mm},#{f3 %pvsyst.itb.to_mm},' \ #'#{ f3 %pvsyst.ilb.to_mm },#{f3 %pvsyst.irb.to_mm},#{f3 %pvsyst.itb.to_mm},' \
#'#{f3 %pvsyst.ibb.to_mm}\n' #'#{f3 %pvsyst.ibb.to_mm}\n'
st += '20\n' st += '20\n'
st += str(int(type.ModulesCols.Value)) + ',' + str(int(type.ModulesRows.Value)) + ',' + \ st += str(int(typ.ModulesCols.Value)) + ',' + str(int(typ.ModulesRows.Value)) + ',' + \
str(type.ModuleColGap.Value) + ',' + str(type.ModuleRowGap.Value) + ',' + '30\n' str(typ.ModuleColGap.Value) + ',' + str(typ.ModuleRowGap.Value) + ',' + '30\n'
st += '30\n' st += '30\n'
st += '1,' + f3.format(type.ModuleWidth.Value) + ',' + f3.format(type.ModuleHeight.Value) + ',' + \ st += '1,' + f3.format(typ.ModuleWidth.Value) + ',' + f3.format(typ.ModuleHeight.Value) + ',' + \
f3.format(type.ModuleThick.Value) + ',' + f2.format(450) + '\n' #f2.format(type.ModulePower.Value) + '\n' f3.format(typ.ModuleThick.Value) + ',' + f2.format(450) + '\n' #f2.format(typ.ModulePower.Value) + '\n'
# cornerdown = find_component_sizes(group.cdef)[1] # cornerdown = find_component_sizes(group.cdef)[1]
# pvorigin = Geom::Point3d.new(cornerdown.x, cornerdown.y, 0) # pvorigin = Geom::Point3d.new(cornerdown.x, cornerdown.y, 0)
# group.instances.each{ | ins | str += pvsyst_insert(ins, pvorigin)} # group.instances.each{ | ins | str += pvsyst_insert(ins, pvorigin)}
for obj in objects: for obj in objects:
if obj.CloneOf == type: if obj.CloneOf == typ:
st += H2PInsert(obj) st += H2PInsert(obj)
## TODO: Bucle para buscar objetos que den sombra y el terreno. Todos llaman a H2PMesh ## TODO: Bucle para buscar objetos que den sombra y el terreno. Todos llaman a H2PMesh
@@ -778,12 +1065,12 @@ def H2PInsert(obj):
return st return st
def H2PMesh(mesh, type): def H2PMesh(mesh, typ):
scale = 0.001 ## ver como se puede hacer para que sea general. Pasar de mm a m scale = 0.001 ## ver como se puede hacer para que sea general. Pasar de mm a m
f3 = '{:.3f}' f3 = '{:.3f}'
st = '' st = ''
if type: if typ:
st = 'ShadowObject\nFence\n' st = 'ShadowObject\nFence\n'
else: else:
st = 'DGM\n' st = 'DGM\n'
@@ -799,7 +1086,7 @@ def H2PMesh(mesh, type):
return st return st
class _PVSystTaskPanel: class PVSystTaskPanel:
def __init__(self): def __init__(self):
self.form = FreeCADGui.PySideUic.loadUi(os.path.dirname(__file__) + "/exportPVSyst.ui") self.form = FreeCADGui.PySideUic.loadUi(os.path.dirname(__file__) + "/exportPVSyst.ui")
@@ -812,7 +1099,7 @@ class _PVSystTaskPanel:
def accept(self): def accept(self):
import datetime import datetime
x = datetime.datetime.now() x = datetime.datetime.now()
date = x.strftime("%y%m%d%H%M%S") date = x.strftime("%Y%m%d%H%M%S")
overwrite = True overwrite = True
path = os.path.join(os.path.dirname(FreeCAD.ActiveDocument.FileName), "outputs", "PVSyst") path = os.path.join(os.path.dirname(FreeCAD.ActiveDocument.FileName), "outputs", "PVSyst")
@@ -820,12 +1107,10 @@ class _PVSystTaskPanel:
os.makedirs(path) os.makedirs(path)
name = FreeCAD.ActiveDocument.Label name = FreeCAD.ActiveDocument.Label
if not overwrite: #if not overwrite:
name = date + "-" + name name = date + "-" + name
filename = os.path.join(path, name) filename = os.path.join(path, name)
#if self.form.cbDAE.isChecked():
# exportToDAE(filename)
if self.form.cbPVC.isChecked(): if self.form.cbPVC.isChecked():
exportToPVC(filename, self.form.cbTerrain.isChecked()) exportToPVC(filename, self.form.cbTerrain.isChecked())
@@ -840,7 +1125,7 @@ class _PVSystTaskPanel:
FreeCADGui.Control.closeDialog() FreeCADGui.Control.closeDialog()
return True return True
class _CommandExportToPVSyst: '''class _CommandExportToPVSyst:
"Export to PVSyst" "Export to PVSyst"
def GetResources(self): def GetResources(self):
@@ -861,4 +1146,4 @@ class _CommandExportToPVSyst:
return False return False
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
FreeCADGui.addCommand('ExportToPVSyst', _CommandExportToPVSyst()) FreeCADGui.addCommand('ExportToPVSyst', _CommandExportToPVSyst())'''

View File

@@ -20,47 +20,32 @@
# * * # * *
# *********************************************************************** # ***********************************************************************
import FreeCAD, Draft import FreeCAD
import PVPlantSite import Draft
import copy import os
import platform
import subprocess
import openpyxl
from openpyxl.styles import Alignment, Border, Side, Font
from PVPlantPlacement import getCols
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
from DraftTools import translate from DraftTools import translate
from PySide.QtCore import QT_TRANSLATE_NOOP from PySide.QtCore import QT_TRANSLATE_NOOP
import Part
import pivy
from pivy import coin
import os
else:
# \cond
def translate(ctxt, txt):
return txt
def QT_TRANSLATE_NOOP(ctxt, txt):
return txt
# \endcond
__title__ = "PVPlant Trench" __title__ = "PVPlant Trench"
__author__ = "Javier Braña" __author__ = "Javier Braña"
__url__ = "http://www.sogos-solar.com" __url__ = "http://www.sogos-solar.com"
from PVPlantResources import DirIcons as DirIcons
from PVPlantResources import DirDocuments as DirDocuments
'''import os
import platform
import subprocess
def open_file(path): def open_file(path):
"""Open a file or directory using the default system handler"""
if platform.system() == "Windows": if platform.system() == "Windows":
os.startfile(path) os.startfile(path)
elif platform.system() == "Darwin": elif platform.system() == "Darwin":
subprocess.Popen(["open", path]) subprocess.Popen(["open", path])
else: else:
subprocess.Popen(["xdg-open", path])''' subprocess.Popen(["xdg-open", path])
from PVPlantPlacement import getCols from PVPlantPlacement import getCols

917
Importer/importOSM.py Normal file
View File

@@ -0,0 +1,917 @@
import FreeCAD
import FreeCADGui
import Part
import Draft
from xml.etree import ElementTree as ET
import ssl
import certifi
import urllib.request
import math
import utm
from collections import defaultdict
import PVPlantImportGrid as ImportElevation
scale = 1000.0
class OSMImporter:
def __init__(self, origin):
self.Origin = origin if origin else FreeCAD.Vector(0, 0, 0)
self.overpass_url = "https://overpass-api.de/api/interpreter"
self.nodes = {}
self.ways_data = defaultdict(dict)
self.feature_colors = {
'building': (0.8, 0.8, 0.6),
'highway': {
'motorway': (1.0, 0.4, 0.4),
'trunk': (1.0, 0.6, 0.4),
'primary': (1.0, 0.8, 0.4),
'secondary': (1.0, 1.0, 0.4),
'tertiary': (0.8, 1.0, 0.4),
'residential': (0.6, 0.6, 0.6)
},
'railway': (0.4, 0.4, 0.4),
'power': {
'line': (0.0, 0.0, 0.0),
'tower': (0.3, 0.3, 0.3),
'substation': (0.8, 0.0, 0.0)
},
'vegetation': (0.4, 0.8, 0.4),
'water': (0.4, 0.6, 1.0)
}
self.ssl_context = ssl.create_default_context(cafile=certifi.where())
def transform_from_latlon(self, coordinates):
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):
query = f"""
[out:xml][bbox:{bbox}];
(
way["building"];
way["highway"];
way["railway"];
way["power"="line"];
way["power"="substation"];
way["natural"="water"];
way["landuse"="forest"];
node["natural"="tree"];
);
(._;>;);
out body;
"""
req = urllib.request.Request(
self.overpass_url,
data=query.encode('utf-8'),
headers={'User-Agent': 'FreeCAD-OSM-Importer/1.0'},
method='POST'
)
return urllib.request.urlopen(req, context=self.ssl_context, timeout=160).read()
def create_layer(self, name):
if not FreeCAD.ActiveDocument.getObject(name):
return FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", name)
return FreeCAD.ActiveDocument.getObject(name)
def process_osm_data(self, osm_data):
root = ET.fromstring(osm_data)
# Almacenar nodos transformados
coordinates = [[float(node.attrib['lat']), float(node.attrib['lon'])] for node in root.findall('node')]
coordinates = self.transform_from_latlon(coordinates)
for i, node in enumerate(root.findall('node')):
self. nodes[node.attrib['id']] = coordinates[i]
'''return
for node in root.findall('node'):
self.nodes[node.attrib['id']] = self.transform_from_latlon(
float(node.attrib['lat']),
float(node.attrib['lon'])
)'''
# Procesar ways
for way in root.findall('way'):
way_id = way.attrib['id']
self.ways_data[way_id] = {
'tags': {tag.attrib['k']: tag.attrib['v'] for tag in way.findall('tag')},
'nodes': [nd.attrib['ref'] for nd in way.findall('nd')]
}
print("1. Create Transportations")
FreeCADGui.updateGui()
self.create_transportation()
print("2. Create Buildings")
FreeCADGui.updateGui()
self.create_buildings()
print("3. Create Power Infrastructure")
FreeCADGui.updateGui()
self.create_power_infrastructure()
print("4. Create Vegetation")
FreeCADGui.updateGui()
self.create_vegetation()
print("5. Create Water Bodies")
FreeCADGui.updateGui()
self.create_water_bodies()
def create_transportation(self):
transport_layer = self.create_layer("Transport")
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
# Carreteras
if 'highway' in tags:
highway_type = tags['highway']
width = {
'motorway': 10.0,
'trunk': 8.0,
'primary': 6.0,
'secondary': 5.0,
'tertiary': 4.0
}.get(highway_type, 3.0)
self.create_road(nodes, width, highway_type, transport_layer, tags['name'] if 'name' in tags else "")
# Vías férreas
if 'railway' in tags:
self.create_railway(nodes, transport_layer, tags['name'] if 'name' in tags else "")
def create_road(self, nodes, width, road_type, layer, name=""):
points = [n for n in nodes]
polyline = Draft.make_wire(points, closed=False, face=False)
polyline.Label = f"Road_{name if (name != '') else road_type}"
polyline.ViewObject.LineWidth = 2.0
polyline.ViewObject.ShapeColor = self.feature_colors['highway'].get(road_type, (0.5, 0.5, 0.5))
polyline.addProperty("App::PropertyString", "OSMType", "Metadata", "Tipo de vía").OSMType = road_type
polyline.addProperty("App::PropertyFloat", "Width", "Metadata", "Ancho de la vía").Width = width
layer.addObject(polyline)
def create_railway(self, nodes, layer, name=""):
points = [n for n in nodes]
rail_line = Draft.make_wire(points, closed=False, face=False)
rail_line.Label = f"{name if (name != '') else 'Railway'}"
rail_line.ViewObject.LineWidth = 1.5
rail_line.ViewObject.ShapeColor = self.feature_colors['railway']
layer.addObject(rail_line)
def create_buildings(self):
building_layer = self.create_layer("Buildings")
for way_id, data in self.ways_data.items():
print(data)
if 'building' not in data['tags']:
continue
tags = data['tags']
nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes]
if len(nodes) < 3:
continue
# Calcular altura
height = self.get_building_height(tags)
# Crear polígono base
polygon_points = [n for n in nodes]
if polygon_points[0] != polygon_points[-1]:
polygon_points.append(polygon_points[0])
try:
polygon = Part.makePolygon(polygon_points)
face = Part.Face(polygon)
extruded = face.extrude(FreeCAD.Vector(0, 0, height))# * scale - self.Origin )
building = building_layer.addObject("Part::Feature", f"Building_{way_id}")
building.Shape = extruded
building.Label = f"Building ({height}m)"
building.ViewObject.ShapeColor = self.feature_colors['building']
# Metadatos
building.addProperty("App::PropertyFloat", "Height", "Metadata", "Altura del edificio").Height = height
if 'building:levels' in tags:
building.addProperty("App::PropertyInteger", "Levels", "Metadata",
"Niveles del edificio").Levels = int(tags['building:levels'])
except Exception as e:
print(f"Error en edificio {way_id}: {str(e)}")
@staticmethod
def get_building_height(tags):
# Lógica de cálculo de altura
if 'height' in tags:
try:
return float(tags['height'].split()[0])
except:
pass
if 'building:levels' in tags:
try:
return float(tags['building:levels']) * 3.0
except:
pass
return 5.0 # Altura por defecto
def create_power_infrastructure(self):
power_layer = self.create_layer("Power_Infrastructure")
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 'power' in tags:
print("\n\n")
print(tags)
feature_type = tags['power']
if feature_type == 'line':
print("3.1. Create Power Lines")
FreeCADGui.updateGui()
self.create_power_line(
nodes=nodes,
tags=tags,
layer=power_layer
)
elif feature_type == 'substation':
print("3.1. Create substations")
FreeCADGui.updateGui()
self.create_substation(
way_id=way_id,
tags=tags,
nodes=nodes,
layer=power_layer
)
elif feature_type == 'tower':
print("3.1. Create power towers")
FreeCADGui.updateGui()
self.create_power_tower(
position=nodes[0] if nodes else None,
tags=tags,
layer=power_layer
)
'''self.create_power_tower_1(
way=way_id,
layer=power_layer
)'''
def create_power_line(self, nodes, tags, layer):
"""Crea líneas de transmisión eléctrica con propiedades técnicas"""
try:
# Configuración basada en tags
line_type = tags.get('line', 'overhead')
voltage = self.parse_voltage(tags.get('voltage', '0'))
cables = int(tags.get('cables', '3'))
material = tags.get('material', 'aluminum')
# Crear geometría
points = [FreeCAD.Vector(*n) for n in nodes]
if len(points) < 2:
return
wire = Draft.make_wire(points, closed=False, face=False)
wire.Label = f"Power_Line_{int(voltage/1000000)}kV"
# Propiedades visuales
wire.ViewObject.LineWidth = 6 #'''1 + (voltage / 100000)'''
color = self.feature_colors['power']['line']
wire.ViewObject.ShapeColor = color
# Propiedades técnicas
wire.addProperty("App::PropertyFloat", "Voltage", "PowerLine", "Voltage in volts").Voltage = voltage
wire.addProperty("App::PropertyInteger", "Cables", "PowerLine", "Number of conductors").Cables = cables
wire.addProperty("App::PropertyString", "Material", "PowerLine", "Conductor material").Material = material
wire.addProperty("App::PropertyString", "Type", "PowerLine", "Line type").Type = line_type
layer.addObject(wire)
# Añadir torres si es overhead
'''if line_type == 'overhead':
distance_between_towers = 150 # metros por defecto
if 'distance_between_towers' in tags:
try:
distance_between_towers = float(tags['distance_between_towers'])
except:
pass
self.create_power_towers_along_line(
points=points,
voltage=voltage,
distance=distance_between_towers,
layer=layer
)'''
except Exception as e:
FreeCAD.Console.PrintError(f"Error creating power line: {str(e)}\n")
def create_power_towers_along_line(self, points, voltage, distance, layer):
"""Crea torres de transmisión a lo largo de la línea"""
total_length = 0
previous_point = None
for point in points:
if previous_point is not None:
segment_length = (point - previous_point).Length
num_towers = int(segment_length / distance)
if num_towers > 0:
step = segment_length / num_towers
direction = (point - previous_point).normalize()
for i in range(num_towers):
tower_pos = previous_point + (direction * (i * step))
self.create_power_tower(
position=tower_pos,
tags={'voltage': str(voltage)},
layer=layer
)
previous_point = point
def create_power_tower(self, position, tags, layer):
"""Crea una torre de transmisión individual"""
try:
voltage = self.parse_voltage(tags.get('voltage', '0'))
# Dimensiones basadas en voltaje
base_size = 2.0 + (voltage / 100000)
height = 25.0 + (voltage / 10000)
# Geometría de la torre
base = Part.makeBox(base_size, base_size, 3.0,
FreeCAD.Vector(position.x - base_size / 2, position.y - base_size / 2, 0))
mast = Part.makeCylinder(0.5, height, FreeCAD.Vector(position.x, position.y, 3.0))
# Unir componentes
tower = base.fuse(mast)
tower_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Power_Tower")
layer.addObject(tower_obj)
tower_obj.Shape = tower
tower_obj.ViewObject.ShapeColor = self.feature_colors['power']['tower']
# Añadir propiedades
tower_obj.addProperty("App::PropertyFloat", "Voltage", "Technical", "Design voltage").Voltage = voltage
tower_obj.addProperty("App::PropertyFloat", "Height", "Technical", "Tower height").Height = height
# Añadir crossarms (crucetas)
for i in range(3):
crossarm_height = 3.0 + (i * 5.0)
crossarm = Part.makeBox(
4.0, 0.2, 0.2,
FreeCAD.Vector(position.x - 2.0, position.y - 0.1, crossarm_height)
)
tower_obj.Shape = tower_obj.Shape.fuse(crossarm)
except Exception as e:
FreeCAD.Console.PrintError(f"Error creating power tower: {str(e)}\n")
def create_power_line_simple(self, nodes, layer):
# Torres de alta tensión
for node in nodes:
cylinder = Part.makeCylinder(1.0, 20.0, FreeCAD.Vector(node[0], node[1], 0))# * scale - self.Origin )
pole = FreeCAD.ActiveDocument.addObject("Part::Feature", "PowerPole")
layer.addObject(pole)
pole.Shape = cylinder
pole.ViewObject.ShapeColor = self.feature_colors['power']['tower']
# Líneas eléctricas
points = [n for n in nodes]
cable = Draft.make_wire(points, closed=False, face=False)
cable.ViewObject.LineWidth = 3.0
cable.ViewObject.ShapeColor = self.feature_colors['power']['line']
layer.addObject(cable)
def create_power_tower_1(self, way, layer):
"""Crea una torre eléctrica según especificaciones OSM"""
tags = way['tags']
nodes = [self.nodes[ref] for ref in way['nodes'] if ref in self.nodes]
if not nodes:
return
try:
# Obtener parámetros principales
structure_type = tags.get('structure', 'lattice')
material = tags.get('material', 'steel')
height = float(tags.get('height', 30.0)) # Altura en metros
voltage = self.parse_voltage(tags.get('voltage', '132000')) # 132kV por defecto
lines = int(tags.get('lines', '3'))
operator = tags.get('operator', '')
tower_name = tags.get('name', f"Tower_{way['id']}")
# Calcular posición (usar primer nodo)
position = FreeCAD.Vector(*nodes[0])
# Configurar dimensiones basadas en parámetros
base_size = self.calculate_base_size(structure_type, height)
crossarm_length = self.calculate_crossarm_length(voltage, lines)
color = self.get_material_color(material)
# Crear geometría según tipo de estructura
if structure_type == 'lattice':
tower = self.create_lattice_tower(height, base_size, crossarm_length)
elif structure_type == 'tubular':
tower = self.create_tubular_tower(height, base_size, crossarm_length)
elif structure_type == 'portal':
tower = self.create_portal_tower(height, base_size, crossarm_length)
else:
tower = self.create_default_tower(height, base_size, crossarm_length)
# Crear objeto en FreeCAD
tower_obj = layer.addObject("Part::Feature", "Power_Tower")
tower_obj.Shape = tower
tower_obj.ViewObject.ShapeColor = color
tower_obj.Placement.Base = position
# Añadir propiedades técnicas
tower_obj.addProperty("App::PropertyFloat", "Voltage", "Technical", "Voltaje nominal (V)").Voltage = voltage
tower_obj.addProperty("App::PropertyFloat", "Height", "Technical", "Altura total (m)").Height = height
tower_obj.addProperty("App::PropertyString", "StructureType", "Technical",
"Tipo de estructura").StructureType = structure_type
tower_obj.addProperty("App::PropertyString", "Material", "Technical",
"Material de construcción").Material = material
tower_obj.addProperty("App::PropertyInteger", "Lines", "Technical", "Número de circuitos").Lines = lines
if operator:
tower_obj.addProperty("App::PropertyString", "Operator", "General", "Operador").Operator = operator
# Añadir cables si existen nodos de conexión
if len(nodes) >= 2:
connection_points = [FreeCAD.Vector(*n) for n in nodes]
self.create_power_lines_between_towers(connection_points, voltage, layer)
except Exception as e:
FreeCAD.Console.PrintError(f"Error creando torre {way['id']}: {str(e)}\n")
def create_lattice_tower(self, height, base_size, crossarm_length):
"""Crea torre de celosía tipo armazón"""
# Base
base = Part.makeBox(base_size, base_size, 3.0)
# Patas principales
leg_profile = Part.makeBox(0.5, 0.5, height)
legs = []
for x in [-base_size / 2, base_size / 2]:
for y in [-base_size / 2, base_size / 2]:
leg = leg_profile.copy()
leg.translate(FreeCAD.Vector(x, y, 3.0))
legs.append(leg)
# Travesaños horizontales
horizontal_bars = []
for z in [10.0, height / 2, height - 5.0]:
bar = Part.makeBox(base_size + 1.0, 0.3, 0.3, FreeCAD.Vector(-(base_size + 1) / 2, -0.15, z))
horizontal_bars.append(bar)
# Crucetas
crossarms = self.create_crossarms(height, crossarm_length)
# Unir todas las partes
tower = base.multiFuse(legs + horizontal_bars + crossarms)
return tower.removeSplitter()
def create_crossarms(self, height, length):
"""Crea crucetas para líneas eléctricas"""
crossarms = []
positions = [
(height - 5.0, 0), # Superior
(height - 15.0, 22.5), # Media con ángulo
(height - 25.0, -22.5) # Inferior con ángulo
]
for z, angle in positions:
crossarm = Part.makeBox(length, 0.3, 0.3)
crossarm.rotate(FreeCAD.Vector(0, 0, z), FreeCAD.Vector(0, 0, 1), angle)
crossarm.translate(FreeCAD.Vector(-length / 2, -0.15, z))
crossarms.append(crossarm)
return crossarms
def calculate_base_size(self, structure_type, height):
"""Calcula tamaño de base según tipo de estructura y altura"""
base_sizes = {
'lattice': 4.0 + (height * 0.1),
'tubular': 3.0 + (height * 0.05),
'portal': 6.0,
'default': 3.0
}
return base_sizes.get(structure_type, base_sizes['default'])
def calculate_crossarm_length(self, voltage, lines):
"""Calcula longitud de crucetas según voltaje y número de circuitos"""
return (voltage / 100000) * 8.0 + (lines * 2.0)
def get_material_color(self, material):
"""Devuelve color según material de construcción"""
colors = {
'steel': (0.65, 0.65, 0.7),
'concrete': (0.8, 0.8, 0.8),
'wood': (0.5, 0.3, 0.2),
'aluminum': (0.9, 0.9, 0.9),
'default': (0.5, 0.5, 0.5)
}
return colors.get(material.lower(), colors['default'])
def create_power_lines_between_towers(self, points, voltage, layer):
"""Crea cables entre torres"""
cable_thickness = 0.1 + (voltage / 500000)
for i in range(len(points) - 1):
start = points[i]
end = points[i + 1]
# Crear cable curvo (catenaria)
cable = self.create_catenary(start, end, sag=5.0)
cable_obj = layer.addObject("Part::Feature", f"Power_Line_{i}")
cable_obj.Shape = cable
cable_obj.ViewObject.LineWidth = cable_thickness * 1000 # Convertir a mm
cable_obj.ViewObject.ShapeColor = (0.1, 0.1, 0.1)
def create_catenary(self, start, end, sag=5.0):
"""Crea curva de catenaria para cables eléctricos"""
mid_point = (start + end) / 2
mid_point.z -= sag
arc = Part.Arc(
start,
mid_point,
end
)
return Part.Edge(arc.toShape())
def create_substation(self, way_id, tags, nodes, layer):
"""Crea subestaciones con todos los componentes detallados"""
try:
# 1. Parámetros base
voltage = self.parse_voltage(tags.get('voltage', '0'))
substation_type = tags.get('substation', 'distribution')
name = tags.get('name', f"Substation_{way_id}")
if len(nodes) < 3:
FreeCAD.Console.PrintWarning(f"Subestación {way_id} ignorada: polígono inválido\n")
return
# 2. Geometría base
polygon_points = [n for n in nodes if isinstance(n, FreeCAD.Vector)]
if polygon_points[0] != polygon_points[-1]:
polygon_points.append(polygon_points[0])
# 3. Base del terreno
base_height = 0.3
try:
base_shape = Part.makePolygon(polygon_points)
base_face = Part.Face(base_shape)
base_extrude = base_face.extrude(FreeCAD.Vector(0, 0, base_height))
base_obj = layer.addObject("Part::Feature", f"{name}_Base")
base_obj.Shape = base_extrude
base_obj.ViewObject.ShapeColor = (0.2, 0.2, 0.2)
except Exception as e:
FreeCAD.Console.PrintError(f"Error base {way_id}: {str(e)}\n")
# 4. Cercado perimetral
if tags.get('fence', 'no') == 'yes':
try:
fence_offset = -0.8 # metros hacia adentro
fence_points = self.offset_polygon(polygon_points, fence_offset)
if len(fence_points) > 2:
fence_shape = Part.makePolygon(fence_points)
fence_face = Part.Face(fence_shape)
fence_extrude = fence_face.extrude(FreeCAD.Vector(0, 0, 2.8))
fence_obj = layer.addObject("Part::Feature", f"{name}_Fence")
fence_obj.Shape = fence_extrude
fence_obj.ViewObject.ShapeColor = (0.4, 0.4, 0.4)
except Exception as e:
FreeCAD.Console.PrintWarning(f"Error cerca {way_id}: {str(e)}\n")
# 5. Edificio principal
if tags.get('building', 'no') == 'yes':
try:
building_offset = -2.0 # metros hacia adentro
building_height = 4.5 + (voltage / 100000)
building_points = self.offset_polygon(polygon_points, building_offset)
if len(building_points) > 2:
building_shape = Part.makePolygon(building_points)
building_face = Part.Face(building_shape)
building_extrude = building_face.extrude(FreeCAD.Vector(0, 0, building_height))
building_obj = layer.addObject("Part::Feature", f"{name}_Building")
building_obj.Shape = building_extrude
building_obj.ViewObject.ShapeColor = (0.7, 0.7, 0.7)
except Exception as e:
FreeCAD.Console.PrintWarning(f"Error edificio {way_id}: {str(e)}\n")
# 6. Transformadores
try:
num_transformers = int(tags.get('transformers', 1))
for i in range(num_transformers):
transformer_pos = self.calculate_equipment_position(
polygon_points,
index=i,
total=num_transformers,
offset=3.0
)
transformer = self.create_transformer(
position=transformer_pos,
voltage=voltage,
tech_type=tags.get('substation:type', 'outdoor')
)
layer.addObject(transformer)
except Exception as e:
FreeCAD.Console.PrintWarning(f"Error transformadores {way_id}: {str(e)}\n")
# 7. Torre de seccionamiento para alta tensión
if substation_type == 'transmission' and voltage >= 110000:
try:
tower_pos = self.calculate_tower_position(polygon_points)
tower = self.create_circuit_breaker_tower(
position=tower_pos,
voltage=voltage
)
layer.addObject(tower)
except Exception as e:
FreeCAD.Console.PrintWarning(f"Error torre {way_id}: {str(e)}\n")
# 8. Propiedades técnicas
substation_data = layer.addObject("App::FeaturePython", f"{name}_Data")
props = {
"Voltage": voltage,
"Type": substation_type,
"Components": ['Base'] + (['Fence'] if 'fence' in tags else []) +
(['Building'] if 'building' in tags else []) +
[f'Transformer_{n + 1}' for n in range(num_transformers)]
}
for prop, value in props.items():
if isinstance(value, list):
substation_data.addProperty("App::PropertyStringList", prop, "Technical").Components = value
else:
substation_data.addProperty(
"App::PropertyFloat" if isinstance(value, float) else "App::PropertyString",
prop, "Technical").setValue(value)
except Exception as e:
FreeCAD.Console.PrintError(f"Error crítico en subestación {way_id}: {str(e)}\n")
def add_substation_fence(self, parent_obj, polygon_points):
"""Añade cerca perimetral"""
try:
offset_points = self.offset_polygon(polygon_points, -0.5)
fence = Part.makePolygon(offset_points).extrude(FreeCAD.Vector(0, 0, 2.5))
fence_obj = parent_obj.Document.addObject("Part::Feature", "Fence")
fence_obj.Shape = fence
fence_obj.ViewObject.ShapeColor = (0.4, 0.4, 0.4)
fence_obj.Placement.Base = parent_obj.Placement.Base
except Exception as e:
FreeCAD.Console.PrintWarning(f"Error en cerca: {str(e)}\n")
def parse_voltage(self, voltage_str):
# Convertir valores comunes de voltaje a numéricos
voltage_map = {
'low': 400,
'medium': 20000,
'high': 110000,
'extra high': 380000,
'ultra high': 765000
}
try:
return float(''.join(filter(str.isdigit, voltage_str.split()[0]))) * 1000
except:
return 20000 # Valor por defecto
def offset_polygon(self, points, offset):
"""Versión corregida sin error de sintaxis"""
if not points:
return []
sum_x = sum(p.x for p in points)
sum_y = sum(p.y for p in points)
centroid = FreeCAD.Vector(sum_x / len(points), sum_y / len(points), 0)
return [p + (centroid - p).normalize() * offset for p in points]
def create_transformer(self, position, voltage, technology):
# Crear transformador básico según características
height = 2.0 + (voltage / 100000)
radius = 0.5 + (voltage / 500000)
transformer = Part.makeCylinder(radius, height, FreeCAD.Vector(position))
transformer_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Transformer")
transformer_obj.Shape = transformer
transformer_obj.ViewObject.ShapeColor = (0.1, 0.1, 0.5) if 'oil' in technology.lower() else (0.5, 0.5, 0.5)
return transformer_obj
def calculate_equipment_position(self, polygon_points, index, total, offset=0.0):
"""Calcula posición equidistante alrededor del perímetro con offset"""
perimeter = sum((p2 - p1).Length for p1, p2 in zip(polygon_points, polygon_points[1:]))
target_dist = (perimeter / total) * index
accumulated = 0.0
for i in range(len(polygon_points) - 1):
p1 = polygon_points[i]
p2 = polygon_points[i + 1]
segment_length = (p2 - p1).Length
if accumulated + segment_length >= target_dist:
direction = (p2 - p1).normalize()
return p1 + direction * (target_dist - accumulated) + direction.cross(FreeCAD.Vector(0, 0, 1)) * offset
accumulated += segment_length
return polygon_points[0]
def create_circuit_breaker_tower(self, position, voltage):
"""Crea torre de seccionamiento especializada"""
tower = Part.makeCompound([
Part.makeCylinder(0.8, 12, position), # Poste principal
Part.makeBox(3, 0.3, 0.3, position + FreeCAD.Vector(-1.5, -0.15, 12)), # Cruceta superior
Part.makeBox(2.5, 0.3, 0.3, position + FreeCAD.Vector(-1.25, -0.15, 9)) # Cruceta media
])
tower_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "CircuitBreakerTower")
tower_obj.Shape = tower
tower_obj.ViewObject.ShapeColor = (0.1, 0.1, 0.1)
tower_obj.addProperty("App::PropertyFloat", "Voltage", "Technical").Voltage = voltage
return tower_obj
def calculate_tower_position(self, polygon_points):
# Colocar torre en el punto medio del lado más largo
# (Implementación compleja que requeriría cálculo geométrico)
return FreeCAD.Vector(polygon_points[0].x, polygon_points[0].y, 0)
def create_vegetation(self):
vegetation_layer = self.create_layer("Vegetation")
# Procesar nodos de vegetación individual
for way_id, tags in self.ways_data.items():
coords = self.nodes.get(way_id)
if not coords:
continue
pos = FreeCAD.Vector(*coords)
if tags.get('natural') == 'tree':
self.create_tree(pos, tags, vegetation_layer)
elif tags.get('natural') == 'shrub':
self.create_shrub(pos, tags, vegetation_layer)
"""elif tags.get('natural') == 'tree_stump':
self.create_tree_stump(pos, vegetation_layer)"""
# Procesar áreas vegetales
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 not nodes or len(nodes) < 3:
continue
if tags.get('natural') == 'wood' or tags.get('landuse') == 'forest':
self.create_forest(nodes, tags, vegetation_layer)
elif tags.get('natural') == 'grassland':
self.create_grassland(nodes, vegetation_layer)
elif tags.get('natural') == 'heath':
self.create_heathland(nodes, vegetation_layer)
elif tags.get('natural') == 'scrub':
self.create_scrub_area(nodes, vegetation_layer)
def create_tree(self, position, tags, layer):
"""Crea un árbol individual con propiedades basadas en etiquetas OSM"""
height = float(tags.get('height', 10.0))
trunk_radius = float(tags.get('circumference', 1.0)) / (2 * math.pi)
canopy_radius = float(tags.get('diameter_crown', 4.0)) / 2
# Crear tronco
trunk = Part.makeCylinder(trunk_radius, height, position)
# Crear copa (forma cónica)
canopy_center = position + FreeCAD.Vector(0, 0, height)
canopy = Part.makeCone(canopy_radius, canopy_radius * 0.7, canopy_radius * 1.5, canopy_center)
tree = trunk.fuse(canopy)
tree_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Tree")
layer.addObject(tree_obj)
tree_obj.Shape = tree
tree_obj.ViewObject.ShapeColor = (0.3, 0.6, 0.2) # Verde bosque
# Añadir metadatos
for prop in ['genus', 'species', 'leaf_type', 'height']:
if prop in tags:
tree_obj.addProperty("App::PropertyString", prop.capitalize(), "Botany",
"Botanical property").__setattr__(prop.capitalize(), tags[prop])
def create_forest(self, nodes, tags, layer):
"""Crea un área boscosa con densidad variable"""
polygon_points = [FreeCAD.Vector(*n) for n in nodes]
if polygon_points[0] != polygon_points[-1]:
polygon_points.append(polygon_points[0])
# Crear base del bosque
polygon = Part.makePolygon(polygon_points)
face = Part.Face(polygon)
forest_base = FreeCAD.ActiveDocument.addObject("Part::Feature", "Forest_Base")
layer.addObject(forest_base)
forest_base.Shape = face
forest_base.ViewObject.ShapeColor = (0.15, 0.4, 0.1) # Verde oscuro
# Generar árboles aleatorios dentro del polígono
density = float(tags.get('density', 0.5)) # Árboles por m²
area = face.Area
num_trees = int(area * density)
for _ in range(num_trees):
rand_point = self.random_point_in_polygon(polygon_points)
self.create_tree(rand_point, {}, layer)
def create_grassland(self, nodes, layer):
"""Crea un área de pastizales"""
polygon_points = [FreeCAD.Vector(*n) 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)
grassland = FreeCAD.ActiveDocument.addObject("Part::Feature", "Grassland")
layer.addObject(grassland)
grassland.Shape = face.extrude(FreeCAD.Vector(0, 0, 0.1))
grassland.ViewObject.ShapeColor = (0.5, 0.7, 0.3) # Verde pasto
def create_heathland(self, nodes, layer):
"""Crea un área de brezales con vegetación baja"""
polygon_points = [FreeCAD.Vector(*n) 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)
heath = FreeCAD.ActiveDocument.addObject("Part::Feature", "Heathland")
layer.addObject(heath)
heath.Shape = face
heath.ViewObject.ShapeColor = (0.6, 0.5, 0.4) # Color terroso
# Añadir arbustos dispersos
for _ in range(int(face.Area * 0.1)): # 1 arbusto cada 10m²
rand_point = self.random_point_in_polygon(polygon_points)
self.create_shrub(rand_point, {}, layer)
def create_shrub(self, position, tags, layer):
"""Crea un arbusto individual"""
height = float(tags.get('height', 1.5))
radius = float(tags.get('diameter_crown', 1.0)) / 2
# Crear forma de arbusto (cono invertido)
base_center = position + FreeCAD.Vector(0, 0, height / 2)
shrub = Part.makeCone(radius, radius * 1.5, height, base_center)
shrub_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Shrub")
layer.addObject(shrub_obj)
shrub_obj.Shape = shrub
shrub_obj.ViewObject.ShapeColor = (0.4, 0.5, 0.3) # Verde arbusto
# Añadir metadatos si existen
if 'genus' in tags:
shrub_obj.addProperty("App::PropertyString", "Genus", "Botany", "Plant genus").Genus = tags['genus']
def create_tree_stump(self, position, layer):
"""Crea un tocón de árbol"""
height = 0.4
radius = 0.5
stump = Part.makeCylinder(radius, height, position)
stump_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Tree_Stump")
layer.addObject(stump_obj)
stump_obj.Shape = stump
stump_obj.ViewObject.ShapeColor = (0.3, 0.2, 0.1) # Marrón madera
def random_point_in_polygon(self, polygon_points):
"""Genera un punto aleatorio dentro de un polígono"""
min_x = min(p.x for p in polygon_points)
max_x = max(p.x for p in polygon_points)
min_y = min(p.y for p in polygon_points)
max_y = max(p.y for p in polygon_points)
while True:
rand_x = random.uniform(min_x, max_x)
rand_y = random.uniform(min_y, max_y)
rand_point = FreeCAD.Vector(rand_x, rand_y, 0)
# Verificar si el punto está dentro del polígono
polygon = Part.makePolygon(polygon_points)
face = Part.Face(polygon)
if face.isInside(rand_point, 0.1, True):
return rand_point
def create_water_bodies(self):
water_layer = self.create_layer("Water")
for way_id, data in self.ways_data.items():
if 'natural' in data['tags'] and data['tags']['natural'] == 'water':
nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes]
if len(nodes) > 2:
polygon_points = [n for n in nodes]
polygon = Part.makePolygon(polygon_points)
face = Part.Face(polygon)
water = FreeCAD.ActiveDocument.addObject("Part::Feature", "WaterBody")
water_layer.addObject(water)
water.Shape = face.extrude(FreeCAD.Vector(0, 0, 0.1))# * scale - self.Origin )
water.ViewObject.ShapeColor = self.feature_colors['water']

111
Importer/module_inserter.py Normal file
View File

@@ -0,0 +1,111 @@
import os
import csv
from PySide import QtGui, QtCore
class SelectorDialog(QtGui.QDialog):
def __init__(self, csv_path, title, parent=None):
super(SelectorDialog, self).__init__(parent)
self.setWindowTitle(title)
self.csv_path = csv_path
self.data = []
self.brand_filter = ""
self.model_filter = ""
# Cargar datos del CSV
self.load_csv_data()
# Crear widgets
self.create_widgets()
self.create_layout()
self.create_connections()
def load_csv_data(self):
"""Carga los datos desde el archivo CSV"""
if os.path.exists(self.csv_path):
with open(self.csv_path, 'r') as f:
reader = csv.DictReader(f, delimiter=';')
self.data = [row for row in reader]
def get_unique_brands(self):
"""Obtiene marcas únicas"""
return list(set(row['Marca'] for row in self.data))
def get_models_by_brand(self, brand):
"""Filtra modelos por marca"""
return [row['Modelo'] for row in self.data if row['Marca'] == brand]
def create_widgets(self):
self.lbl_brand = QtGui.QLabel("Marca:")
self.cb_brand = QtGui.QComboBox()
self.cb_brand.addItems(self.get_unique_brands())
self.lbl_model = QtGui.QLabel("Modelo:")
self.cb_model = QtGui.QComboBox()
self.update_model_combo()
self.btn_accept = QtGui.QPushButton("Aceptar")
self.btn_cancel = QtGui.QPushButton("Cancelar")
def create_layout(self):
layout = QtGui.QVBoxLayout()
form_layout = QtGui.QFormLayout()
form_layout.addRow(self.lbl_brand, self.cb_brand)
form_layout.addRow(self.lbl_model, self.cb_model)
button_layout = QtGui.QHBoxLayout()
button_layout.addWidget(self.btn_accept)
button_layout.addWidget(self.btn_cancel)
layout.addLayout(form_layout)
layout.addLayout(button_layout)
self.setLayout(layout)
def create_connections(self):
self.cb_brand.currentIndexChanged.connect(self.update_model_combo)
self.btn_accept.clicked.connect(self.accept)
self.btn_cancel.clicked.connect(self.reject)
def update_model_combo(self):
brand = self.cb_brand.currentText()
models = self.get_models_by_brand(brand)
self.cb_model.clear()
self.cb_model.addItems(models)
def get_selected_item(self):
brand = self.cb_brand.currentText()
model = self.cb_model.currentText()
for row in self.data:
if row['Marca'] == brand and row['Modelo'] == model:
return row
return None
def select_modulo():
csv_path = "/ruta/a/tu/databases/modulos.csv" # Ajusta esta ruta
dialog = SelectorDialog(csv_path, "Seleccionar Módulo")
if dialog.exec_():
selected = dialog.get_selected_item()
print("Módulo seleccionado:", selected) # Aquí puedes agregar la lógica de importación
def select_inversor():
csv_path = "/ruta/a/tu/databases/inversores.csv" # Ajusta esta ruta
dialog = SelectorDialog(csv_path, "Seleccionar Inversor")
if dialog.exec_():
selected = dialog.get_selected_item()
print("Inversor seleccionado:", selected) # Aquí puedes agregar la lógica de importación
# Crear una barra de herramientas para acceder fácilmente
toolbar = QtGui.QToolBar()
select_modulo_action = QtGui.QAction("Seleccionar Módulo", toolbar)
select_modulo_action.triggered.connect(select_modulo)
toolbar.addAction(select_modulo_action)
select_inversor_action = QtGui.QAction("Seleccionar Inversor", toolbar)
select_inversor_action.triggered.connect(select_inversor)
toolbar.addAction(select_inversor_action)
# Agregar la barra de herramientas a FreeCAD
Gui.addToolBar(toolbar)

View File

@@ -20,93 +20,46 @@
# * * # * *
# *********************************************************************** # ***********************************************************************
__title__="FreeCAD Fotovoltaic Power Plant Toolkit" __title__ = "FreeCAD Fotovoltaic Power Plant Toolkit"
__author__ = "Javier Braña" __author__ = "Javier Braña"
__url__ = "sn" __url__ = "sn"
from pathlib import Path
import sys
import os
import FreeCADGui
import FreeCAD
FreeCADGui.updateLocale()
class PVPlantWorkbench (Workbench): class PVPlantWorkbench(Workbench):
import os
from PVPlantResources import DirIcons as DirIcons from PVPlantResources import DirIcons as DirIcons
MenuText = "PVPlant" MenuText = "PVPlant"
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 Initialize(self): def Initialize(self):
import sys
sys.path.append(r"C:\Users\javie\AppData\Roaming\FreeCAD\Mod")
# Mias #sys.path.append(r"C:\Users\javie\AppData\Roaming\FreeCAD\Mod")
import PVPlantGeoreferencing, PVPlantPlacement, \ sys.path.append(os.path.join(FreeCAD.getUserAppDataDir(), 'Mod'))
PVPlantTerrainAnalisys, PVPlantSite, PVPlantImportGrid, PVPlantFence,\ import PVPlantTools, reload
PVPlantFoundation, PVPlantCreateTerrainMesh, \
PVPlantTreeGenerator, PVPlantBuilding, PVPlantTrench, PVPlantEarthWorks, \
PVPlantStringing, \
PVPlantPad, PVPlantRoad, PVPlantTerrain, PVPlantManhole, \
GraphProfile, Utils.PVPlantTrace,\
reload
import PVPlantRackChecking
from Project.Area import PVPlantArea, PVPlantAreaUtils self.projectlist = PVPlantTools.projectlist
from Project import ProjectSetup self.projectlist.insert(0, 'Reload')
from Export import exportPVSyst, PVPlantBOQMechanical, PVPlantBOQElectrical, PVPlantBOQCivil,\ self.projectlist.insert(1, 'Separator')
exportDXF self.framelist = PVPlantTools.pv_mechanical
from Importer import importDXF
from Mechanical.Frame import PVPlantFrame from Export import ExporterCommands
self.inportExportlist = ExporterCommands.Exportlist
from Electrical.Cable import PVPlantCable, PVPlantElectricalLine
from Electrical.CombinerBox import PVPlantStringBox
from Electrical.Inverter import PVPlantInverter
# A list of command names created in the line above
self.projectlist = ["Reload",
"PVPlantSite",
"PVPlantGeoreferencing",
"ProjectSetup",
#"ImportGrid",
"Terrain",
"PointsGroup",
"PVPlantCreateTerrainMesh",
"PVPlantAreas",
"SplitArea",
"TerrainAnalisys",
"Trenches",
"PVPlantEarthworks",
"PVPlantPad",
"PVPlantRoad",
"PVPlantManhole",
#"PVPlantFoundation"
"GraphTerrainProfile",
"Trace",
]
self.framelist = [
"RackType",
"PVPlantRackCheck",
"Separator",
"PVPlantPlacement",
"PVPlantAdjustToTerrain",
"PVPlantConvertTo",
"PVArea"
]
self.objectlist = [
"PVPlantTree",
"PVPlantBuilding",
"PVPlantFenceGroup",
]
self.inportExportlist = ["BOQCivil",
"BOQMechanical",
"BOQElectrical",
"Separator",
"exportDXF",
#"importDXF",
"ExportToPVSyst",
]
self.objectlist = PVPlantTools.objectlist
''' [
"PVPlantTree",
"PVPlantBuilding",
"PVPlantFenceGroup",
]'''
from Electrical.PowerConverter import PowerConverter
self.electricalList = ["PVPlantStringBox", self.electricalList = ["PVPlantStringBox",
"PVPlantCable", "PVPlantCable",
"PVPlanElectricalLine", "PVPlanElectricalLine",
@@ -114,60 +67,63 @@ class PVPlantWorkbench (Workbench):
"Stringing", "Stringing",
"Separator", "Separator",
"StringInverter", "StringInverter",
] "Separator",
"PowerConverter"
]
self.roads = ["PVPlantRoad", self.roads = ["PVPlantRoad",
]
] self.pads = ["PVPlantPad",
"Separator"
self.pads = ["PVPlantPad",
"Separator"
] ]
# Toolbar # Toolbar
self.appendToolbar("Civil", self.projectlist) # creates a new toolbar with your commands self.appendToolbar("Civil", self.projectlist) # creates a new toolbar with your commands
self.appendToolbar("PVPlant", self.framelist) # creates a new toolbar with your commands self.appendToolbar("Mechanical", self.framelist) # creates a new toolbar with your commands
self.appendToolbar("Shadow", self.objectlist) # creates a new toolbar with your commands self.appendToolbar("Shadow", self.objectlist) # creates a new toolbar with your commands
self.appendToolbar("Outputs", self.inportExportlist) # creates a new toolbar with your commands self.appendToolbar("Outputs", self.inportExportlist) # creates a new toolbar with your commands
self.appendToolbar("Electrical", self.electricalList) # creates a new toolbar with your commands self.appendToolbar("Electrical", self.electricalList) # creates a new toolbar with your commands
# Menu # Menu
self.appendMenu("&Civil", self.projectlist) # creates a new menu self.appendMenu("&Civil", self.projectlist) # creates a new menu
self.appendMenu("&PVPlant", self.framelist) # creates a new menu self.appendMenu("&Mechanical", self.framelist) # creates a new menu
self.appendMenu("&Shadow", self.objectlist) # creates a new menu self.appendMenu("&Shadow", self.objectlist) # creates a new menu
self.appendMenu("&Outputs", self.inportExportlist) # creates a new menu self.appendMenu("&Outputs", self.inportExportlist) # creates a new menu
self.appendMenu("&Electrical", self.electricalList) # creates a new menu self.appendMenu("&Electrical", self.electricalList) # creates a new menu
# Draft tools # Draft tools
from DraftTools import translate from DraftTools import translate
self.drafttools = ["Draft_Line","Draft_Wire","Draft_Circle","Draft_Arc","Draft_Ellipse", self.drafttools = ["Draft_Line", "Draft_Wire", "Draft_Circle", "Draft_Arc", "Draft_Ellipse",
"Draft_Polygon","Draft_Rectangle", "Draft_Text", "Draft_Polygon", "Draft_Rectangle", "Draft_Text",
"Draft_Dimension", "Draft_BSpline","Draft_Point", "Draft_Dimension", "Draft_BSpline", "Draft_Point",
"Draft_Facebinder","Draft_BezCurve","Draft_Label"] "Draft_Facebinder", "Draft_BezCurve", "Draft_Label"]
self.draftmodtools = ["Draft_Move","Draft_Rotate","Draft_Offset", self.draftmodtools = ["Draft_Move", "Draft_Rotate", "Draft_Offset",
"Draft_Trimex", "Draft_Upgrade", "Draft_Downgrade", "Draft_Scale", "Draft_Trimex", "Draft_Upgrade", "Draft_Downgrade", "Draft_Scale",
"Draft_Shape2DView","Draft_Draft2Sketch","Draft_Array", "Draft_Shape2DView", "Draft_Draft2Sketch", "Draft_Array",
"Draft_Clone"] "Draft_Clone"]
self.draftextratools = ["Draft_WireToBSpline","Draft_ShapeString", self.draftextratools = ["Draft_WireToBSpline", "Draft_ShapeString",
"Draft_PathArray","Draft_Mirror","Draft_Stretch"] "Draft_PathArray", "Draft_Mirror", "Draft_Stretch"]
self.draftcontexttools = ["Draft_ApplyStyle","Draft_ToggleDisplayMode","Draft_AddToGroup","Draft_AutoGroup", self.draftcontexttools = ["Draft_ApplyStyle", "Draft_ToggleDisplayMode", "Draft_AddToGroup", "Draft_AutoGroup",
"Draft_SelectGroup","Draft_SelectPlane", "Draft_SelectGroup", "Draft_SelectPlane",
"Draft_ShowSnapBar","Draft_ToggleGrid",] "Draft_ShowSnapBar", "Draft_ToggleGrid", ]
self.draftutils = ["Draft_Heal","Draft_FlipDimension", self.draftutils = ["Draft_Heal", "Draft_FlipDimension",
"Draft_ToggleConstructionMode","Draft_ToggleContinueMode","Draft_Edit", "Draft_ToggleConstructionMode", "Draft_ToggleContinueMode", "Draft_Edit",
"Draft_Slope","Draft_AddConstruction"] "Draft_Slope", "Draft_AddConstruction"]
self.snapList = ['Draft_Snap_Lock','Draft_Snap_Midpoint','Draft_Snap_Perpendicular', self.snapList = ['Draft_Snap_Lock', 'Draft_Snap_Midpoint', 'Draft_Snap_Perpendicular',
'Draft_Snap_Grid','Draft_Snap_Intersection','Draft_Snap_Parallel', 'Draft_Snap_Grid', 'Draft_Snap_Intersection', 'Draft_Snap_Parallel',
'Draft_Snap_Endpoint','Draft_Snap_Angle','Draft_Snap_Center', 'Draft_Snap_Endpoint', 'Draft_Snap_Angle', 'Draft_Snap_Center',
'Draft_Snap_Extension','Draft_Snap_Near','Draft_Snap_Ortho','Draft_Snap_Special', 'Draft_Snap_Extension', 'Draft_Snap_Near', 'Draft_Snap_Ortho', 'Draft_Snap_Special',
'Draft_Snap_Dimensions','Draft_Snap_WorkingPlane'] 'Draft_Snap_Dimensions', 'Draft_Snap_WorkingPlane']
def QT_TRANSLATE_NOOP(scope, text): return text def QT_TRANSLATE_NOOP(scope, text): return text
self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Draft tools"), self.drafttools) self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Draft tools"), self.drafttools)
self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Draft mod tools"), self.draftmodtools) self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Draft mod tools"), self.draftmodtools)
self.appendMenu(QT_TRANSLATE_NOOP("arch", "&Draft"), self.drafttools + self.draftmodtools + self.draftextratools) self.appendMenu(QT_TRANSLATE_NOOP("arch", "&Draft"),
self.appendMenu([QT_TRANSLATE_NOOP("arch", "&Draft"), QT_TRANSLATE_NOOP("arch", "Utilities")], self.draftutils + self.draftcontexttools) self.drafttools + self.draftmodtools + self.draftextratools)
self.appendMenu([QT_TRANSLATE_NOOP("arch", "&Draft"), QT_TRANSLATE_NOOP("arch", "Utilities")],
self.draftutils + self.draftcontexttools)
self.appendMenu([QT_TRANSLATE_NOOP("arch", "&Draft"), QT_TRANSLATE_NOOP("arch", "Snapping")], self.snapList) self.appendMenu([QT_TRANSLATE_NOOP("arch", "&Draft"), QT_TRANSLATE_NOOP("arch", "Snapping")], self.snapList)
import Part import Part
@@ -192,23 +148,24 @@ class PVPlantWorkbench (Workbench):
import SelectionObserver import SelectionObserver
import FreeCADGui import FreeCADGui
self.observer = SelectionObserver.SelObserver() #self.observer = SelectionObserver.SelObserver()
FreeCADGui.Selection.addObserver(self.observer) # installe la fonction en mode resident #FreeCADGui.Selection.addObserver(self.observer) # installe la fonction en mode resident
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"
FreeCADGui.Selection.removeObserver(self.observer)
#FreeCADGui.Selection.removeObserver(self.observer)
return return
def ContextMenu(self, recipient): def ContextMenu(self, recipient):
"This is executed whenever the user right-clicks on screen" "This is executed whenever the user right-clicks on screen"
# "recipient" will be either "view" or "tree" # "recipient" will be either "view" or "tree"
#if FreeCAD.activeDraftCommand is None: # if FreeCAD.activeDraftCommand is None:
if recipient.lower() == "view": if recipient.lower() == "view":
print("Menus en la 'View'") print("Menus en la 'View'")
#if FreeCAD.activeDraftCommand is None: # if FreeCAD.activeDraftCommand is None:
presel = FreeCADGui.Selection.getPreselection() presel = FreeCADGui.Selection.getPreselection()
print(presel.SubElementNames, " - ", presel.PickedPoints) print(presel.SubElementNames, " - ", presel.PickedPoints)
if not presel is None: if not presel is None:
@@ -239,7 +196,7 @@ class PVPlantWorkbench (Workbench):
self.contextMenu.show() self.contextMenu.show()
''' '''
def GetClassName(self): def GetClassName(self):
# this function is mandatory if this is a full python workbench # this function is mandatory if this is a full python workbench
return "Gui::PythonWorkbench" return "Gui::PythonWorkbench"

View File

@@ -0,0 +1,877 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>formRack</class>
<widget class="QDialog" name="formRack">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>476</width>
<height>1032</height>
</rect>
</property>
<property name="windowTitle">
<string>Fixed Frame:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Módulos:</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<property name="horizontalSpacing">
<number>5</number>
</property>
<property name="verticalSpacing">
<number>2</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Altura (m)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="editModuleHeight">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>5.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>1.990000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Largura (m)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="editModuleLenght">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>5.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.960000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Anchura (m)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="editModuleWidth">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>0.100000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.030000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Potencia (wp)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="editModulePower">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="minimum">
<number>150</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
<property name="value">
<number>350</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Estructura</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Columnas (un)</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QDoubleSpinBox" name="editFrontHeight">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="maximum">
<double>5.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>0.800000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Orientación del módulo</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QDoubleSpinBox" name="editVerticalGap">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="maximum">
<double>0.900000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.020000000000000</double>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="editRows">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboFrameType">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<item>
<property name="text">
<string>Fija</string>
</property>
</item>
<item>
<property name="text">
<string>Tracker 1 Eje</string>
</property>
</item>
</widget>
</item>
<item row="7" column="1">
<widget class="QDoubleSpinBox" name="editLeftOffset">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="maximum">
<double>0.900000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.050000000000000</double>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Offset borde derecha (m)</string>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Ángulo de inclinación (º)</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QDoubleSpinBox" name="editRightOffset">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="maximum">
<double>0.900000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.050000000000000</double>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QSpinBox" name="editTilt">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>60</number>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QSpinBox" name="editInclination">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>60</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Filas (un)</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Distancia al suelo en el frente (m)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="editCols">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="minimum">
<number>20</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="labelVerticalGap">
<property name="text">
<string>Separación vertical entre módulos (m)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboModuleOrientation">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<item>
<property name="text">
<string>Landscape</string>
</property>
</item>
<item>
<property name="text">
<string>Portrait</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Separación horizontal entre módulos (m)</string>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Ängulo máximo de inclinación longitudinal (ª)</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QDoubleSpinBox" name="editHorizontalGap">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="maximum">
<double>0.900000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.020000000000000</double>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Tipo de estructura</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Offset borde izquierda (m)</string>
</property>
</widget>
</item>
<item row="16" column="0" colspan="2">
<widget class="QWidget" name="widgetTracker" native="true">
<layout class="QGridLayout" name="gridLayout_4">
<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>
<property name="spacing">
<number>5</number>
</property>
<item row="2" column="0">
<widget class="QLabel" name="labelVerticalGap_2">
<property name="text">
<string>Separación entre uniones (m)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="checkBox">
<property name="text">
<string>Separación Motor (m)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="editInternalGapNumber">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>6</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Número de uniones</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="editInternalGap">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="maximum">
<double>0.900000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.020000000000000</double>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="editMotorGap">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="maximum">
<double>0.900000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>0.020000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Resultado</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Total de módulos</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="editTotalModules">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Potencia total (wp)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="editTotalPower">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Longitud (m)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="editTotalLength">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Anchura (m)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="editTotalWidth">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>140</width>
<height>0</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@ except AttributeError:
import os, math import os, math
from PVPlantResources import DirIcons as DirIcons from PVPlantResources import DirIcons as DirIcons
class _TaskPanel: class TaskPanel:
def __init__(self, obj = None): def __init__(self, obj = None):
self.obj = None self.obj = None
self.select = 0 self.select = 0
@@ -251,7 +251,7 @@ def Open3DTriangle(point_cloud):
#p_mesh_crop = mesh.crop(bbox) #p_mesh_crop = mesh.crop(bbox)
return mesh return mesh
class _PVPlantCreateTerrainMesh: '''class _PVPlantCreateTerrainMesh:
def GetResources(self): def GetResources(self):
return {'Pixmap': str(os.path.join(DirIcons, "surface.svg")), return {'Pixmap': str(os.path.join(DirIcons, "surface.svg")),
@@ -268,4 +268,4 @@ class _PVPlantCreateTerrainMesh:
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
FreeCADGui.addCommand('PVPlantCreateTerrainMesh', _PVPlantCreateTerrainMesh()) FreeCADGui.addCommand('PVPlantCreateTerrainMesh', _PVPlantCreateTerrainMesh())'''

View File

@@ -5,6 +5,7 @@ import Part
import ArchComponent import ArchComponent
from pivy import coin from pivy import coin
import numpy as np import numpy as np
import DraftGeomUtils
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
import FreeCADGui, os import FreeCADGui, os
@@ -488,7 +489,7 @@ class ViewProviderEarthWorksVolume:
return None return None
class _EarthWorksTaskPanel: class EarthWorksTaskPanel:
def __init__(self): def __init__(self):
self.To = None self.To = None
@@ -516,6 +517,7 @@ class _EarthWorksTaskPanel:
return False return False
FreeCAD.ActiveDocument.openTransaction("Calcular movimiento de tierras") FreeCAD.ActiveDocument.openTransaction("Calcular movimiento de tierras")
def calculateEarthWorks(line, extreme=False): def calculateEarthWorks(line, extreme=False):
pts = [] pts = []
pts1 = [] pts1 = []
@@ -576,33 +578,6 @@ class _EarthWorksTaskPanel:
elif ver == 1: elif ver == 1:
from PVPlantPlacement import getCols from PVPlantPlacement import getCols
columns = getCols(frames) columns = getCols(frames)
'''colelements = set()
rowelements = set()
for groups in columns:
for group in groups:
for frame in group:
colelements.add(frame.Placement.Base.x)
rowelements.add(frame.Placement.Base.y)
colelements = sorted(colelements)
rowelements = sorted(rowelements, reverse=True)
print("Cols: ", len(colelements), " - ", colelements)
print("Rows: ", len(rowelements), " - ", rowelements)
a = []
colnum = len(colelements)
for r in range(len(rowelements)):
a.append([None] * colnum)
mat = np.array(a, dtype=object)
for groups in columns:
for group in groups:
for frame in group:
colidx = colelements.index(frame.Placement.Base.x)
rowidx = rowelements.index(frame.Placement.Base.y)
mat[rowidx][colidx] = frame
print(mat)
return'''
for groups in columns: for groups in columns:
for group in groups: for group in groups:
first = group[0] first = group[0]
@@ -709,44 +684,46 @@ class _EarthWorksTaskPanel:
import Mesh import Mesh
pro = utils.getProjected(sh) pro = utils.getProjected(sh)
pro = utils.simplifyWire(pro) pro = utils.simplifyWire(pro)
#pro = pro.makeOffset2D(20000, 2, False, False, True)
Part.show(sh, "loft")
Part.show(pro, "pro")
pts = [ver.Point for ver in pro.Vertexes] pts = [ver.Point for ver in pro.Vertexes]
'''if pts[0] != pts[-1]:
pts.append(pts[0])'''
land.trim(pts, 1) land.trim(pts, 1)
tmp = [] tmp = []
shp = Part.Shape()
for face in sh.Faces: for face in sh.Faces:
wire = face.Wires[0].copy() wire = face.Wires[0].copy()
pl = wire.Placement.Base pl = wire.Placement.Base
wire.Placement.Base = wire.Placement.Base - pl wire.Placement.Base = wire.Placement.Base - pl
wire = wire.scale(2)
wire.Placement.Base = wire.Placement.Base + pl
#wire = wire.makeOffset2D(10000, 0, False, False, True)
wire.Placement.Base.z = wire.Placement.Base.z - 10000
face1 = Part.makeLoft([face.Wires[0], wire], True, True, False)
if DraftGeomUtils.isPlanar(wire):
# Caso simple
wire = wire.makeOffset2D(10000, 0, False, False, True)
wire.Placement.Base.z = wire.Placement.Base.z - 10000
top = wire.makeOffset2D(1, 0, False, False, True)
loft = Part.makeLoft([top, wire], True, True, False)
tmp.append(loft)
shp = shp.fuse(loft)
else:
# Caso complejo:
vertices = face.Vertexes
# Dividir rectángulo en 2 triángulos
triangles = [
[vertices[0], vertices[1], vertices[2]],
[vertices[2], vertices[3], vertices[0]]
]
Part.show(face1, "tool") for tri in triangles:
#tmp.append(face.extrude(FreeCAD.Vector(0, 0, -10000))) # Crear wire triangular
#Part.show(tmp[-1], "face-extrude") wire = Part.makePolygon([v.Point for v in tri] + [tri[0].Point])
sh = sh.extrude(FreeCAD.Vector(0, 0, -10000)) wire = wire.makeOffset2D(10000, 0, False, False, True)
sh = Part.Solid(sh) wire.Placement.Base.z = wire.Placement.Base.z - 10000
Part.show(sh) top = wire.makeOffset2D(1, 0, False, False, True)
import MeshPart as mp loft = Part.makeLoft([top, wire], True, True, False)
msh = mp.meshFromShape(Shape=sh) # , MaxLength=1) tmp.append(loft)
# msh = msh.smooth("Laplace", 3) shp = shp.fuse(loft)
Mesh.show(msh, "tool")
Mesh.show(land, "trim") final_tool = Part.makeCompound(tmp)
'''inner = msh.inner(land) Part.show(final_tool, "tool")
Mesh.show(inner) Part.show(shp)
outer = msh.inner(land)
Mesh.show(outer)'''
'''intersec = land.section(msh, MinDist=0.01)
import Draft
for sec in intersec:
Draft.makeWire(sec)'''
FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.commitTransaction()
self.closeForm() self.closeForm()
@@ -827,7 +804,7 @@ def searchTool(obj, tools):
return tool return tool
class _CommandCalculateEarthworks: '''class _CommandCalculateEarthworks:
def GetResources(self): def GetResources(self):
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "pico.svg")), return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "pico.svg")),
@@ -846,7 +823,7 @@ class _CommandCalculateEarthworks:
return active return active
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
FreeCADGui.addCommand('PVPlantEarthworks', _CommandCalculateEarthworks()) FreeCADGui.addCommand('PVPlantEarthworks', _CommandCalculateEarthworks())'''
def accept(): def accept():
@@ -964,4 +941,5 @@ def accept():
FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.commitTransaction()
self.closeForm() self.closeForm()
return True return True

View File

@@ -27,8 +27,7 @@ if FreeCAD.GuiUp:
import FreeCADGui import FreeCADGui
from PySide import QtCore, QtGui from PySide import QtCore, QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP from PySide.QtCore import QT_TRANSLATE_NOOP
from PySide2.QtWebEngineWidgets import QWebEngineView
from PySide2.QtWebChannel import QWebChannel
import os import os
else: else:
# \cond # \cond
@@ -47,13 +46,21 @@ class MapWindow(QtGui.QWidget):
def __init__(self, WinTitle="MapWindow"): def __init__(self, WinTitle="MapWindow"):
super(MapWindow, self).__init__() super(MapWindow, self).__init__()
self.raise_() self.raise_()
self.lat = 0 self.lat = None
self.lon = 0 self.lon = None
self.minLat = None
self.maxLat = None
self.minLon = None
self.maxLon = None
self.zoom = None
self.WinTitle = WinTitle self.WinTitle = WinTitle
self.georeference_coordinates = {'lat': None, 'lon': None}
self.setupUi() self.setupUi()
def setupUi(self): def setupUi(self):
from PySide2.QtWebEngineWidgets import QWebEngineView
from PySide2.QtWebChannel import QWebChannel
self.ui = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantGeoreferencing.ui", self) self.ui = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantGeoreferencing.ui", self)
self.resize(1200, 800) self.resize(1200, 800)
@@ -144,6 +151,12 @@ class MapWindow(QtGui.QWidget):
RightLayout.addWidget(self.groupbox) 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) verticalSpacer = QtGui.QSpacerItem(20, 48, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
RightLayout.addItem(verticalSpacer) RightLayout.addItem(verticalSpacer)
@@ -169,7 +182,6 @@ class MapWindow(QtGui.QWidget):
geolocator = Nominatim(user_agent="http") geolocator = Nominatim(user_agent="http")
location = geolocator.geocode(self.valueSearch.text()) location = geolocator.geocode(self.valueSearch.text())
print(location.raw)
self.valueSearch.setText(location.address) self.valueSearch.setText(location.address)
self.panMap(location.longitude, location.latitude, location.raw['boundingbox']) self.panMap(location.longitude, location.latitude, location.raw['boundingbox'])
@@ -185,6 +197,7 @@ class MapWindow(QtGui.QWidget):
"var data = drawnItems.toGeoJSON();" "var data = drawnItems.toGeoJSON();"
"MyApp.shapes(JSON.stringify(data));" "MyApp.shapes(JSON.stringify(data));"
) )
self.close() self.close()
@QtCore.Slot(float, float) @QtCore.Slot(float, float)
@@ -196,10 +209,22 @@ class MapWindow(QtGui.QWidget):
' | UTM: ' + str(zone_number) + zone_letter + ' | UTM: ' + str(zone_number) + zone_letter +
', {:.5f}m E, {:.5f}m N'.format(x, y)) ', {:.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) @QtCore.Slot(float, float)
def georeference(self, lat, lng): def georeference(self, lat, lng):
import PVPlantSite import PVPlantSite
from geopy.geocoders import Nominatim from geopy.geocoders import Nominatim
self.georeference_coordinates['lat'] = lat
self.georeference_coordinates['lon'] = lng
Site = PVPlantSite.get(create=True) Site = PVPlantSite.get(create=True)
Site.Proxy.setLatLon(lat, lng) Site.Proxy.setLatLon(lat, lng)
@@ -228,13 +253,20 @@ class MapWindow(QtGui.QWidget):
import geojson import geojson
import PVPlantImportGrid as ImportElevation import PVPlantImportGrid as ImportElevation
import Draft 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) items = geojson.loads(drawnItems)
for item in items['features']: for item in items['features']:
if item['geometry']['type'] == "Point": # 1. if the feature is a Point or Circle: if item['geometry']['type'] == "Point": # 1. if the feature is a Point or Circle:
coord = item['geometry']['coordinates'] coord = item['geometry']['coordinates']
point = ImportElevation.getElevationFromOE([[coord[0], coord[1]],]) point = ImportElevation.getElevationFromOE([[coord[0], coord[1]],])
c = FreeCAD.Vector(point[0][0], point[0][1], point[0][2]) c = FreeCAD.Vector(point[0][0], point[0][1], point[0][2]).sub(offset)
if item['properties'].get('radius'): if item['properties'].get('radius'):
r = round(item['properties']['radius'] * 1000, 0) r = round(item['properties']['radius'] * 1000, 0)
p = FreeCAD.Placement() p = FreeCAD.Placement()
@@ -252,34 +284,231 @@ class MapWindow(QtGui.QWidget):
name = "Area" name = "Area"
lp = item['geometry']['coordinates'][0] lp = item['geometry']['coordinates'][0]
pts = [] pts = [[cords[1], cords[0]] for cords in lp]
for cords in lp:
pts.append([cords[1], cords[0]])
tmp = ImportElevation.getElevationFromOE(pts) tmp = ImportElevation.getElevationFromOE(pts)
pts = [] pts = [p.sub(offset) for p in tmp]
for p in tmp:
pts.append(p.sub(FreeCAD.ActiveDocument.Site.Origin))
obj = Draft.makeWire(pts, closed=cw, face=False) obj = Draft.makeWire(pts, closed=cw, face=False)
obj.Placement.Base = FreeCAD.ActiveDocument.Site.Origin #obj.Placement.Base = Site.Origin
obj.Label = name obj.Label = name
Draft.autogroup(obj) Draft.autogroup(obj)
if item['properties'].get('name'): if item['properties'].get('name'):
obj.Label = item['properties']['name'] obj.Label = item['properties']['name']
FreeCAD.activeDocument().recompute() if self.checkboxImportGis.isChecked():
FreeCADGui.updateGui() self.getDataFromOSM(self.minLat, self.minLon, self.maxLat, self.maxLon)
FreeCADGui.SendMsgToActiveView("ViewFit")
def panMap(self, lng, lat, geometry=""): 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() frame = self.view.page()
bbox = "[{0}, {1}], [{2}, {3}]".format(float(geometry[0]), float(geometry[2]), bbox = "[{0}, {1}], [{2}, {3}]".format(float(geometry[0]), float(geometry[2]),
float(geometry[1]), float(geometry[3])) float(geometry[1]), float(geometry[3]))
command = 'map.panTo(L.latLng({lt}, {lg}));'.format(lt=lat, lg=lng) command = 'map.panTo(L.latLng({lt}, {lg}));'.format(lt=lat, lg=lng)
command += 'map.fitBounds([{box}]);'.format(box=bbox) command += 'map.fitBounds([{box}]);'.format(box=bbox)
frame.runJavaScript(command) 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): def importKML(self):
file = QtGui.QFileDialog.getOpenFileName(None, "FileDialog", "", "Google Earth (*.kml *.kmz)")[0] file = QtGui.QFileDialog.getOpenFileName(None, "FileDialog", "", "Google Earth (*.kml *.kmz)")[0]
@@ -287,11 +516,11 @@ class MapWindow(QtGui.QWidget):
layers = kmz_convert(file, "", ) layers = kmz_convert(file, "", )
frame = self.view.page() frame = self.view.page()
for layer in layers: for layer in layers:
command = "drawnItems.addLayer(L.geoJSON({0}));".format(layer) command = "var geoJsonLayer = L.geoJSON({0}); drawnItems.addLayer(geoJsonLayer); map.fitBounds(geoJsonLayer.getBounds());".format( layer)
frame.runJavaScript(command) frame.runJavaScript(command)
class _CommandPVPlantGeoreferencing: class CommandPVPlantGeoreferencing:
def GetResources(self): def GetResources(self):
return {'Pixmap': str(os.path.join(DirIcons, "Location.svg")), return {'Pixmap': str(os.path.join(DirIcons, "Location.svg")),
@@ -309,6 +538,6 @@ class _CommandPVPlantGeoreferencing:
else: else:
return False return False
if FreeCAD.GuiUp: '''if FreeCAD.GuiUp:
FreeCADGui.addCommand('PVPlantGeoreferencing',_CommandPVPlantGeoreferencing()) FreeCADGui.addCommand('PVPlantGeoreferencing',_CommandPVPlantGeoreferencing())
'''

View File

@@ -57,14 +57,14 @@
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QLineEdit" name="lineEdit"> <widget class="QLineEdit" name="search_bar">
<property name="placeholderText"> <property name="placeholderText">
<string>Search...</string> <string>Search...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="pushButton"> <widget class="QPushButton" name="search_button">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@@ -80,10 +80,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QWebEngineView" name="widget_4" native="true"/> <widget class="QLabel" name="coordinates_label">
</item>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@@ -91,7 +88,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>TextLabel</string> <string>coordenadas:</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -111,7 +108,7 @@
<widget class="QWidget" name="widget_5" native="true"> <widget class="QWidget" name="widget_5" native="true">
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="1" column="1"> <item row="1" column="1">
<widget class="QPushButton" name="pushButton_4"> <widget class="QPushButton" name="kmz_button">
<property name="text"> <property name="text">
<string>PushButton</string> <string>PushButton</string>
</property> </property>
@@ -124,20 +121,27 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0">
<widget class="QCheckBox" name="checkBox">
<property name="text">
<string>Georeferenciar</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="checkBoxGeoreference">
<property name="text">
<string>Georeferenciar</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBoxImportGis">
<property name="text">
<string>CheckBox</string>
</property>
</widget>
</item>
<item> <item>
<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>
@@ -164,7 +168,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="pushButton_3"> <widget class="QPushButton" name="accept_button">
<property name="text"> <property name="text">
<string>Aceptar</string> <string>Aceptar</string>
</property> </property>
@@ -178,14 +182,6 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>QWebEngineView</class>
<extends>QWidget</extends>
<header>qwebengineview.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View File

@@ -1,213 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Georeferencing</class>
<widget class="QDialog" name="Georeferencing">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>574</width>
<height>350</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Create Surface</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QWidget" name="widgetLeft" native="true">
<layout class="QVBoxLayout" name="verticalLayout">
<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="QWidget" name="widgetSearch" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<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="QLineEdit" name="lineEdit">
<property name="placeholderText">
<string>Search...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWebEngineView" name="widget_4" native="true"/>
</item>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widgetRight" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Configuraciones:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox">
<property name="text">
<string>Georeferenciar</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Cargar un archivo KMZ/KML:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_4">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QListWidget" name="listWidget">
<item>
<property name="text">
<string>New Item</string>
</property>
<property name="checkState">
<enum>Unchecked</enum>
</property>
</item>
</widget>
</item>
<item>
<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>
<widget class="QWidget" name="widget_6" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Cancelar</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_3">
<property name="text">
<string>Aceptar</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QWebEngineView</class>
<extends>QWidget</extends>
<header>qwebengineview.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -40,21 +40,96 @@ from PVPlantResources import DirIcons as DirIcons
import PVPlantSite 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:
return []
import requests
import utm
from requests.exceptions import RequestException
# Construcción más eficiente de parámetros
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() # 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): 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: if len(coordinates) == 0:
return None return None
from requests import get from requests import get
import utm import utm
str="" locations_str=""
total = len(coordinates) - 1 total = len(coordinates) - 1
for i, point in enumerate(coordinates): for i, point in enumerate(coordinates):
str += '{:.6f},{:.6f}'.format(point[0], point[1]) locations_str += '{:.6f},{:.6f}'.format(point[0], point[1])
if i != total: if i != total:
str += '|' locations_str += '|'
query = 'https://api.open-elevation.com/api/v1/lookup?locations=' + str query = 'https://api.open-elevation.com/api/v1/lookup?locations=' + locations_str
r = get(query, timeout=20) try:
r = get(query, timeout=20, verify=certifi.where()) # <-- Corrección aquí
except RequestException as e:
print(f"Error en la solicitud: {str(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 # Only get the json response in case of 200 or 201
points = [] points = []
@@ -62,14 +137,16 @@ def getElevationFromOE(coordinates):
results = r.json() results = r.json()
for point in results["results"]: for point in results["results"]:
c = utm.from_latlon(point["latitude"], point["longitude"]) c = utm.from_latlon(point["latitude"], point["longitude"])
v = FreeCAD.Vector(round(c[0] * 1000, 0), v = FreeCAD.Vector(round(c[0], 0),
round(c[1] * 1000, 0), round(c[1], 0),
round(point["elevation"] * 1000, 0)) round(point["elevation"], 0)) * 1000
points.append(v) points.append(v)
return points return points
def getSinglePointElevationFromBing(lat, lng): def getSinglePointElevationFromBing(lat, lng):
#http://dev.virtualearth.net/REST/v1/Elevation/List?points={lat1,long1,lat2,long2,latN,longnN}&heights={heights}&key={BingMapsAPIKey} #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 = "http://dev.virtualearth.net/REST/v1/Elevation/List?points="
source += str(lat) + "," + str(lng) source += str(lat) + "," + str(lng)
source += "&heights=sealevel" source += "&heights=sealevel"
@@ -79,11 +156,9 @@ def getSinglePointElevationFromBing(lat, lng):
response = requests.get(source) response = requests.get(source)
ans = response.text ans = response.text
# +# to do: error handling - wait and try again
s = json.loads(ans) s = json.loads(ans)
print(s)
res = s['resourceSets'][0]['resources'][0]['elevations'] res = s['resourceSets'][0]['resources'][0]['elevations']
import utm
for elevation in res: for elevation in res:
c = utm.from_latlon(lat, lng) c = utm.from_latlon(lat, lng)
v = FreeCAD.Vector( v = FreeCAD.Vector(
@@ -250,7 +325,6 @@ def getSinglePointElevationUtm(lat, lon):
print (v) print (v)
return v return v
def getElevationUTM(polygon, lat, lng, resolution = 10000): def getElevationUTM(polygon, lat, lng, resolution = 10000):
import utm import utm
@@ -374,47 +448,6 @@ def getElevation(lat, lon, b=50.35, le=11.17, size=40):
FreeCADGui.updateGui() FreeCADGui.updateGui()
return FreeCAD.activeDocument().ActiveObject return FreeCAD.activeDocument().ActiveObject
'''
# original::
def getElevation(lat, lon, b=50.35, le=11.17, size=40):
tm.lat = lat
tm.lon = lon
baseheight = 0 #getheight(tm.lat, tm.lon)
center = tm.fromGeographic(tm.lat, tm.lon)
#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']
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) - 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: class _ImportPointsTaskPanel:
def __init__(self, obj = None): def __init__(self, obj = None):

View File

@@ -138,8 +138,6 @@ class _Manhole(ArchComponent.Component):
obj.Shape = ext_sol.cut([ins_sol, ], 0.0) obj.Shape = ext_sol.cut([ins_sol, ], 0.0)
class _ViewProviderManhole(ArchComponent.ViewProviderComponent): class _ViewProviderManhole(ArchComponent.ViewProviderComponent):
"A View Provider for the Pipe object" "A View Provider for the Pipe object"
@@ -180,7 +178,7 @@ class _ViewProviderManhole(ArchComponent.ViewProviderComponent):
import draftguitools.gui_tool_utils as gui_tool_utils import draftguitools.gui_tool_utils as gui_tool_utils
class _ManholeTaskPanel: class ManholeTaskPanel:
def __init__(self, obj=None): def __init__(self, obj=None):
self.new = False self.new = False
if obj is None: if obj is None:
@@ -249,7 +247,7 @@ class _ManholeTaskPanel:
self.view.removeEventCallback("SoEvent", self.call) self.view.removeEventCallback("SoEvent", self.call)
class _CommandManhole: '''class _CommandManhole:
"the Arch Building command definition" "the Arch Building command definition"
def GetResources(self): def GetResources(self):
@@ -274,5 +272,5 @@ class _CommandManhole:
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
FreeCADGui.addCommand('PVPlantManhole', _CommandManhole()) FreeCADGui.addCommand('PVPlantManhole', _CommandManhole())'''

View File

@@ -323,7 +323,7 @@ class _PadTaskPanel:
self.new = False self.new = False
self.obj = obj self.obj = obj
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "PVPlantTrench.ui")) self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "Civil/PVPlantTrench.ui"))
def accept(self): def accept(self):
FreeCAD.ActiveDocument.openTransaction("Create Pad") FreeCAD.ActiveDocument.openTransaction("Create Pad")

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -578,22 +578,22 @@ class _PVPlantSite(ArchSite._Site):
obj.addProperty("App::PropertyLink", obj.addProperty("App::PropertyLink",
"Boundary", "Boundary",
"Site", "PVPlant",
"Boundary of land") "Boundary of land")
obj.addProperty("App::PropertyLinkList", obj.addProperty("App::PropertyLinkList",
"Frames", "Frames",
"Site", "PVPlant",
"Frames templates") "Frames templates")
obj.addProperty("App::PropertyEnumeration", obj.addProperty("App::PropertyEnumeration",
"UtmZone", "UtmZone",
"Base", "PVPlant",
"UTM zone").UtmZone = zone_list "UTM zone").UtmZone = zone_list
obj.addProperty("App::PropertyVector", obj.addProperty("App::PropertyVector",
"Origin", "Origin",
"Base", "PVPlant",
"Origin point.").Origin = (0, 0, 0) "Origin point.").Origin = (0, 0, 0)
def onDocumentRestored(self, obj): def onDocumentRestored(self, obj):
@@ -771,10 +771,12 @@ class _PVPlantSite(ArchSite._Site):
import PVPlantImportGrid import PVPlantImportGrid
x, y, zone_number, zone_letter = utm.from_latlon(lat, lon) x, y, zone_number, zone_letter = utm.from_latlon(lat, lon)
self.obj.UtmZone = zone_list[zone_number - 1] self.obj.UtmZone = zone_list[zone_number - 1]
# self.obj.UtmZone = "Z"+str(zone_number)
#z = PVPlantImportGrid.get_elevation(lat, lon) point = PVPlantImportGrid.getElevationFromOE([[lat, lon]])
zz = PVPlantImportGrid.getSinglePointElevationFromBing(lat, lon) self.obj.Origin = FreeCAD.Vector(point[0].x, point[0].y, point[0].z)
self.obj.Origin = FreeCAD.Vector(x * 1000, y * 1000, zz.z) self.obj.Latitude = lat
self.obj.Longitude = lon
self.obj.Elevation = point[0].z
class _ViewProviderSite(ArchSite._ViewProviderSite): class _ViewProviderSite(ArchSite._ViewProviderSite):
@@ -1171,7 +1173,7 @@ class _ViewProviderSite:
''' '''
class _CommandPVPlantSite: '''class _CommandPVPlantSite:
"the Arch Site command definition" "the Arch Site command definition"
def GetResources(self): def GetResources(self):
@@ -1189,4 +1191,4 @@ class _CommandPVPlantSite:
return return
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
FreeCADGui.addCommand('PVPlantSite', _CommandPVPlantSite()) FreeCADGui.addCommand('PVPlantSite', _CommandPVPlantSite())'''

View File

@@ -73,6 +73,42 @@ line_patterns = {
"Dot (.5x) ...............................": 0x5555, "Dot (.5x) ...............................": 0x5555,
"Dot (2x) . . . . . . . . . . .": 0x8888} "Dot (2x) . . . . . . . . . . .": 0x8888}
def open_xyz_mmap(archivo_path):
"""
Usa memory-mapping para archivos muy grandes (máxima velocidad)
"""
# Primera pasada: contar líneas válidas
total_puntos = 0
with open(archivo_path, 'r') as f:
for linea in f:
partes = linea.strip().split()
if len(partes) >= 3:
try:
float(partes[0]);
float(partes[1]);
float(partes[2])
total_puntos += 1
except:
continue
# Segunda pasada: cargar datos
puntos = np.empty((total_puntos, 3))
idx = 0
with open(archivo_path, 'r') as f:
for linea in f:
partes = linea.strip().split()
if len(partes) >= 3:
try:
x, y, z = float(partes[0]), float(partes[1]), float(partes[2])
puntos[idx] = [x, y, z]
idx += 1
except:
continue
return puntos
def makeTerrain(name="Terrain"): def makeTerrain(name="Terrain"):
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Terrain") obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Terrain")
obj.Label = name obj.Label = name
@@ -81,7 +117,6 @@ def makeTerrain(name="Terrain"):
FreeCAD.ActiveDocument.recompute() FreeCAD.ActiveDocument.recompute()
return obj return obj
class Terrain(ArchComponent.Component): class Terrain(ArchComponent.Component):
"A Shadow Terrain Obcject" "A Shadow Terrain Obcject"
@@ -120,12 +155,12 @@ class Terrain(ArchComponent.Component):
"Surface", "Surface",
"Use a Point Group to generate the surface") "Use a Point Group to generate the surface")
if not ("Mesh" in pl): if not ("mesh" in pl):
obj.addProperty("Mesh::PropertyMeshKernel", obj.addProperty("Mesh::PropertyMeshKernel",
"Mesh", "mesh",
"Surface", "Surface",
"Mesh") "Mesh")
obj.setEditorMode("Mesh", 1) obj.setEditorMode("mesh", 1)
if not ("InitialMesh" in pl): if not ("InitialMesh" in pl):
obj.addProperty("Mesh::PropertyMeshKernel", obj.addProperty("Mesh::PropertyMeshKernel",
@@ -156,108 +191,115 @@ class Terrain(ArchComponent.Component):
'''Do something when a property has changed''' '''Do something when a property has changed'''
if prop == "InitialMesh": if prop == "InitialMesh":
obj.Mesh = obj.InitialMesh.copy() obj.mesh = obj.InitialMesh.copy()
if prop == "DEM" or prop == "CuttingBoundary": if prop == "DEM" or prop == "CuttingBoundary":
from datetime import datetime from datetime import datetime
if obj.DEM and obj.CuttingBoundary: if obj.DEM and obj.CuttingBoundary:
''' from pathlib import Path
Parámetro Descripción Requisitos suffix = Path(obj.DEM).suffix
NCOLS: Cantidad de columnas de celdas Entero mayor que 0. if suffix == '.asc':
NROWS: Cantidad de filas de celdas Entero mayor que 0. '''
XLLCENTER o XLLCORNER: Coordenada X del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada y. ASC format:
YLLCENTER o YLLCORNER: Coordenada Y del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada x.
CELLSIZE: Tamaño de celda Mayor que 0. Parámetro Descripción Requisitos
NODATA_VALUE: Los valores de entrada que serán NoData en el ráster de salida Opcional. El valor predeterminado es -9999 NCOLS: Cantidad de columnas de celdas Entero mayor que 0.
''' NROWS: Cantidad de filas de celdas Entero mayor que 0.
grid_space = 1 XLLCENTER o XLLCORNER: Coordenada X del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada y.
file = open(obj.DEM, "r") YLLCENTER o YLLCORNER: Coordenada Y del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada x.
templist = [line.split() for line in file.readlines()] CELLSIZE: Tamaño de celda Mayor que 0.
file.close() NODATA_VALUE: Los valores de entrada que serán NoData en el ráster de salida Opcional. El valor predeterminado es -9999
del file '''
grid_space = 1
file = open(obj.DEM, "r")
templist = [line.split() for line in file.readlines()]
file.close()
del file
# Read meta data: # Read meta data:
meta = templist[0:6] meta = templist[0:6]
nx = int(meta[0][1]) # NCOLS nx = int(meta[0][1]) # NCOLS
ny = int(meta[1][1]) # NROWS ny = int(meta[1][1]) # NROWS
xllref = meta[2][0] # XLLCENTER / XLLCORNER xllref = meta[2][0] # XLLCENTER / XLLCORNER
xllvalue = round(float(meta[2][1]), 3) xllvalue = round(float(meta[2][1]), 3)
yllref = meta[3][0] # YLLCENTER / XLLCORNER yllref = meta[3][0] # YLLCENTER / XLLCORNER
yllvalue = round(float(meta[3][1]), 3) yllvalue = round(float(meta[3][1]), 3)
cellsize = round(float(meta[4][1]), 3) # CELLSIZE cellsize = round(float(meta[4][1]), 3) # CELLSIZE
nodata_value = float(meta[5][1]) # NODATA_VALUE nodata_value = float(meta[5][1]) # NODATA_VALUE
# set coarse_factor # set coarse_factor
coarse_factor = max(round(grid_space / cellsize), 1) coarse_factor = max(round(grid_space / cellsize), 1)
# Get z values # Get z values
templist = templist[6:(6 + ny)] templist = templist[6:(6 + ny)]
templist = [templist[i][0::coarse_factor] for i in np.arange(0, len(templist), coarse_factor)] templist = [templist[i][0::coarse_factor] for i in np.arange(0, len(templist), coarse_factor)]
datavals = np.array(templist).astype(float) datavals = np.array(templist).astype(float)
del templist del templist
# create xy coordinates # create xy coordinates
import PVPlantSite offset = self.site.Origin
offset = PVPlantSite.get().Origin x = (cellsize * np.arange(nx)[0::coarse_factor] + xllvalue) * 1000 - offset.x
x = 1000 * (cellsize * np.arange(nx)[0::coarse_factor] + xllvalue) - offset.x y = (cellsize * np.arange(ny)[-1::-1][0::coarse_factor] + yllvalue) * 1000 - offset.y
y = 1000 * (cellsize * np.arange(ny)[-1::-1][0::coarse_factor] + yllvalue) - offset.y datavals = datavals * 1000 # Ajuste de altura
datavals = 1000 * datavals # - offset.z
# remove points out of area # remove points out of area
# 1. coarse: # 1. coarse:
if obj.CuttingBoundary: if obj.CuttingBoundary:
inc_x = obj.CuttingBoundary.Shape.BoundBox.XLength * 0.0 inc_x = obj.CuttingBoundary.Shape.BoundBox.XLength * 0.0
inc_y = obj.CuttingBoundary.Shape.BoundBox.YLength * 0.0 inc_y = obj.CuttingBoundary.Shape.BoundBox.YLength * 0.0
tmp = np.where(np.logical_and(x >= (obj.CuttingBoundary.Shape.BoundBox.XMin - inc_x), tmp = np.where(np.logical_and(x >= (obj.CuttingBoundary.Shape.BoundBox.XMin - inc_x),
x <= (obj.CuttingBoundary.Shape.BoundBox.XMax + inc_x)))[0] x <= (obj.CuttingBoundary.Shape.BoundBox.XMax + inc_x)))[0]
print(tmp) x_max = np.ndarray.max(tmp)
x_max = np.ndarray.max(tmp) x_min = np.ndarray.min(tmp)
x_min = np.ndarray.min(tmp)
tmp = np.where(np.logical_and(y >= (obj.CuttingBoundary.Shape.BoundBox.YMin - inc_y), tmp = np.where(np.logical_and(y >= (obj.CuttingBoundary.Shape.BoundBox.YMin - inc_y),
y <= (obj.CuttingBoundary.Shape.BoundBox.YMax + inc_y)))[0] y <= (obj.CuttingBoundary.Shape.BoundBox.YMax + inc_y)))[0]
y_max = np.ndarray.max(tmp) y_max = np.ndarray.max(tmp)
y_min = np.ndarray.min(tmp) y_min = np.ndarray.min(tmp)
del tmp del tmp
x = x[x_min:x_max+1] x = x[x_min:x_max+1]
y = y[y_min:y_max+1] y = y[y_min:y_max+1]
datavals = datavals[y_min:y_max+1, x_min:x_max+1] datavals = datavals[y_min:y_max+1, x_min:x_max+1]
# Create mesh - surface: # Create mesh - surface:
import MeshTools.Triangulation as Triangulation import MeshTools.Triangulation as Triangulation
import Mesh import Mesh
stepsize = 75 stepsize = 75
stepx = math.ceil(nx / stepsize) stepx = math.ceil(nx / stepsize)
stepy = math.ceil(ny / stepsize) stepy = math.ceil(ny / stepsize)
mesh = Mesh.Mesh() mesh = Mesh.Mesh()
for indx in range(stepx): for indx in range(stepx):
inix = indx * stepsize - 1 inix = indx * stepsize - 1
finx = min([stepsize * (indx + 1), len(x)-1]) finx = min([stepsize * (indx + 1), len(x)-1])
for indy in range(stepy): for indy in range(stepy):
iniy = indy * stepsize - 1 iniy = indy * stepsize - 1
finy = min([stepsize * (indy + 1), len(y) - 1]) finy = min([stepsize * (indy + 1), len(y) - 1])
pts = [] pts = []
for i in range(inix, finx): for i in range(inix, finx):
for j in range(iniy, finy): for j in range(iniy, finy):
if datavals[j][i] != nodata_value: if datavals[j][i] != nodata_value:
if obj.CuttingBoundary: if obj.CuttingBoundary:
if obj.CuttingBoundary.Shape.isInside(FreeCAD.Vector(x[i], y[j], 0), 0, True): if obj.CuttingBoundary.Shape.isInside(FreeCAD.Vector(x[i], y[j], 0), 0, True):
pts.append([x[i], y[j], datavals[j][i]])
else:
pts.append([x[i], y[j], datavals[j][i]]) pts.append([x[i], y[j], datavals[j][i]])
else: if len(pts) > 3:
pts.append([x[i], y[j], datavals[j][i]]) try:
if len(pts) > 3: triangulated = Triangulation.Triangulate(pts)
try: mesh.addMesh(triangulated)
mesh.addMesh(Triangulation.Triangulate(pts)) except TypeError:
#Mesh.show(mesh) print(f"Error al procesar {len(pts)} puntos: {str(e)}")
except TypeError:
print("error al procesar: {0} puntos".format(len(pts))) mesh.removeDuplicatedPoints()
mesh.removeFoldsOnSurface()
obj.InitialMesh = mesh.copy()
Mesh.show(mesh)
elif suffix in ['.xyz']:
data = open_xyz_mmap(obj.DEM)
mesh.removeDuplicatedPoints()
mesh.removeFoldsOnSurface()
obj.InitialMesh = mesh.copy()
Mesh.show(mesh)
if prop == "PointsGroup" or prop == "CuttingBoundary": if prop == "PointsGroup" or prop == "CuttingBoundary":
if obj.PointsGroup and obj.CuttingBoundary: if obj.PointsGroup and obj.CuttingBoundary:
@@ -284,12 +326,9 @@ class Terrain(ArchComponent.Component):
import MeshTools.Triangulation as Triangulation import MeshTools.Triangulation as Triangulation
mesh = Triangulation.Triangulate(Data) mesh = Triangulation.Triangulate(Data)
'''shape = PVPlantCreateTerrainMesh.MeshToShape(mesh)
shape.Placement.move(nbase)'''
obj.Shape = shape
if obj.DEM: if obj.DEM:
obj.DEM = None obj.DEM = None
obj.mesh = mesh
def execute(self, obj): def execute(self, obj):
'''''' ''''''
@@ -307,7 +346,6 @@ class ViewProviderTerrain:
"A View Provider for the Pipe object" "A View Provider for the Pipe object"
def __init__(self, vobj): def __init__(self, vobj):
self.Object = vobj.Object
self.boundary_color = None self.boundary_color = None
self.edge_style = None self.edge_style = None
self.edge_color = None self.edge_color = None
@@ -321,16 +359,16 @@ class ViewProviderTerrain:
# Triangulation properties. # Triangulation properties.
pl = vobj.PropertiesList pl = vobj.PropertiesList
if not ("Transparency" in pl): if not ("Transparency" in pl):
vobj.addProperty("App::PropertyIntegerConstraint", '''vobj.addProperty("App::PropertyIntegerConstraint",
"Transparency", "Transparency",
"Surface Style", "Surface Style",
"Set triangle face transparency").Transparency = (50, 0, 100, 1) "Set triangle face transparency").Transparency = (50, 0, 100, 1)'''
if not ("ShapeColor" in pl): if not ("ShapeColor" in pl):
vobj.addProperty("App::PropertyColor", vobj.addProperty("App::PropertyColor",
"ShapeColor", "ShapeColor",
"Surface Style", "Surface Style",
"Set triangle face color").ShapeColor = (r, g, b, vobj.Transparency / 100) "Set triangle face color").ShapeColor = (0.0, 0.667, 0.49, vobj.Transparency / 100)
if not ("ShapeMaterial" in pl): if not ("ShapeMaterial" in pl):
vobj.addProperty("App::PropertyMaterial", vobj.addProperty("App::PropertyMaterial",
@@ -413,18 +451,21 @@ class ViewProviderTerrain:
"Set major contour line width").MinorWidth = (2.0, 1.0, 20.0, 1.0) "Set major contour line width").MinorWidth = (2.0, 1.0, 20.0, 1.0)
vobj.Proxy = self vobj.Proxy = self
self.Object = vobj.Object
# Inicializar colores correctamente
vobj.ShapeMaterial.DiffuseColor = vobj.ShapeColor vobj.ShapeMaterial.DiffuseColor = vobj.ShapeColor
def onDocumentRestored(self, vobj): def onDocumentRestored(self, vobj):
self.setProperties(vobj) self.setProperties(vobj)
def onChanged(self, vobj, prop): def onChanged(self, vobj, prop):
''' Update Object visuals when a view property changed. ''' """ Update Object visuals when a view property changed. """
if prop == "ShapeColor" or prop == "Transparency": if prop == "ShapeColor" or prop == "Transparency":
if hasattr(vobj, "ShapeColor") and hasattr(vobj, "Transparency"): if hasattr(vobj, "ShapeColor") and hasattr(vobj, "Transparency"):
color = vobj.getPropertyByName("ShapeColor") color = vobj.getPropertyByName("ShapeColor")
transparency = vobj.getPropertyByName("Transparency") transparency = vobj.getPropertyByName("Transparency")
color = (color[0], color[1], color[2], transparency / 100) color = (color[0], color[1], color[2], 50 / 100)
vobj.ShapeMaterial.DiffuseColor = color vobj.ShapeMaterial.DiffuseColor = color
if prop == "ShapeMaterial": if prop == "ShapeMaterial":
@@ -555,7 +596,7 @@ class ViewProviderTerrain:
highlight.addChild(mat_binding) highlight.addChild(mat_binding)
highlight.addChild(self.geo_coords) highlight.addChild(self.geo_coords)
highlight.addChild(self.triangles) highlight.addChild(self.triangles)
highlight.addChild(boundaries) #highlight.addChild(boundaries)
# Face root. # Face root.
face = coin.SoSeparator() face = coin.SoSeparator()
@@ -573,14 +614,14 @@ class ViewProviderTerrain:
surface_root.addChild(face) surface_root.addChild(face)
surface_root.addChild(offset) surface_root.addChild(offset)
surface_root.addChild(edge) surface_root.addChild(edge)
surface_root.addChild(major_contours) #surface_root.addChild(major_contours)
surface_root.addChild(minor_contours) #surface_root.addChild(minor_contours)
vobj.addDisplayMode(surface_root, "Surface") vobj.addDisplayMode(surface_root, "Surface")
# Boundary root. # Boundary root.
boundary_root = coin.SoSeparator() #boundary_root = coin.SoSeparator()
boundary_root.addChild(boundaries) #boundary_root.addChild(boundaries)
vobj.addDisplayMode(boundary_root, "Boundary") #vobj.addDisplayMode(boundary_root, "Boundary")
# Elevation/Shaded root. # Elevation/Shaded root.
'''shaded_root = coin.SoSeparator() '''shaded_root = coin.SoSeparator()
@@ -599,8 +640,8 @@ class ViewProviderTerrain:
# Wireframe root. # Wireframe root.
wireframe_root = coin.SoSeparator() wireframe_root = coin.SoSeparator()
wireframe_root.addChild(edge) wireframe_root.addChild(edge)
wireframe_root.addChild(major_contours) #wireframe_root.addChild(major_contours)
wireframe_root.addChild(minor_contours) #wireframe_root.addChild(minor_contours)
vobj.addDisplayMode(wireframe_root, "Wireframe") vobj.addDisplayMode(wireframe_root, "Wireframe")
# Take features from properties. # Take features from properties.
@@ -629,19 +670,19 @@ class ViewProviderTerrain:
''' '''
if prop == "Mesh": if prop == "Mesh":
print("update terrain mesh") if obj.mesh:
mesh = obj.Mesh print("Mostrar mesh")
copy_mesh = mesh.copy()
# copy_mesh.Placement.move(origin.Origin)
triangles = [] mesh = obj.mesh
for i in copy_mesh.Topology[1]: vertices = [tuple(v) for v in mesh.Topology[0]]
triangles.extend(list(i)) faces = []
triangles.append(-1) for face in mesh.Topology[1]:
faces.extend(face)
faces.append(-1)
self.geo_coords.point.values = copy_mesh.Topology[0] # Asignar a los nodos de visualización
self.triangles.coordIndex.values = triangles self.geo_coords.point.values = vertices # <-- ¡Clave!
del copy_mesh self.triangles.coordIndex.values = faces # <-- ¡Clave!
def getDisplayModes(self, vobj): def getDisplayModes(self, vobj):
''' Return a list of display modes. ''' ''' Return a list of display modes. '''
@@ -656,7 +697,9 @@ class ViewProviderTerrain:
return "Surface" return "Surface"
def claimChildren(self): def claimChildren(self):
return [self.Object.CuttingBoundary, ] if hasattr(self, "Object") and self.Object:
return [self.Object.CuttingBoundary, ]
return []
def getIcon(self): def getIcon(self):
return str(os.path.join(DirIcons, "terrain.svg")) return str(os.path.join(DirIcons, "terrain.svg"))
@@ -670,7 +713,7 @@ class ViewProviderTerrain:
return None return None
class _CommandTerrain: '''class _CommandTerrain:
"the PVPlant Terrain command definition" "the PVPlant Terrain command definition"
def GetResources(self): def GetResources(self):
@@ -692,4 +735,4 @@ class _CommandTerrain:
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
FreeCADGui.addCommand('Terrain', _CommandTerrain()) FreeCADGui.addCommand('Terrain', _CommandTerrain())'''

View File

@@ -23,6 +23,7 @@ except AttributeError:
import os import os
from PVPlantResources import DirIcons as DirIcons from PVPlantResources import DirIcons as DirIcons
def Mest2FemMesh(obj): def Mest2FemMesh(obj):
import Fem import Fem
@@ -44,6 +45,7 @@ def Mest2FemMesh(obj):
FreeCAD.activeDocument().recompute() FreeCAD.activeDocument().recompute()
return obj2 return obj2
def makeContours(land, minor = 1000, mayor = 5000, def makeContours(land, minor = 1000, mayor = 5000,
minorColor=(0.0, 0.00, 0.80), mayorColor=(0.00, 0.00, 1.00), minorColor=(0.0, 0.00, 0.80), mayorColor=(0.00, 0.00, 1.00),
minorThickness = 2, mayorThickness = 5, minorThickness = 2, mayorThickness = 5,
@@ -58,6 +60,7 @@ def makeContours(land, minor = 1000, mayor = 5000,
FreeCAD.ActiveDocument.recompute() FreeCAD.ActiveDocument.recompute()
def Contours_Mesh(Mesh, minor, mayor, def Contours_Mesh(Mesh, minor, mayor,
minorColor, mayorColor, minorColor, mayorColor,
minorLineWidth, mayorLineWidth, minorLineWidth, mayorLineWidth,
@@ -144,6 +147,7 @@ def Contours_Mesh(Mesh, minor, mayor,
calculateSection(minor_array) calculateSection(minor_array)
calculateSection(mayor_array) calculateSection(mayor_array)
def Contours_Part(Terrain, minor, mayor, def Contours_Part(Terrain, minor, mayor,
minorColor, mayorColor, minorColor, mayorColor,
minorLineWidth, mayorLineWidth, minorLineWidth, mayorLineWidth,
@@ -235,6 +239,8 @@ def Contours_Part(Terrain, minor, mayor,
calculateSection(mayor_array) calculateSection(mayor_array)
# Base widget for task panel terrain analisys # Base widget for task panel terrain analisys
class _generalTaskPanel: class _generalTaskPanel:
'''The TaskPanel for Slope setup''' '''The TaskPanel for Slope setup'''
@@ -324,7 +330,7 @@ class _generalTaskPanel:
self.ranges[curentIndex.row()][2] = (color.red()/255, color.green()/255, color.blue()/255) self.ranges[curentIndex.row()][2] = (color.red()/255, color.green()/255, color.blue()/255)
# Contours Analisys: --------------------------------------------------------------------------------- # Contours Analisys: ---------------------------------------------------------------------------------
class _ContourTaskPanel(): class ContourTaskPanel():
'''The editmode TaskPanel for contours generator''' '''The editmode TaskPanel for contours generator'''
def __init__(self): def __init__(self):
@@ -480,7 +486,7 @@ class _ContourTaskPanel():
return True return True
# Height Analisys: --------------------------------------------------------------------------------- # Height Analisys: ---------------------------------------------------------------------------------
class _HeightTaskPanel(_generalTaskPanel): class HeightTaskPanel(_generalTaskPanel):
'''The TaskPanel for Slope setup''' '''The TaskPanel for Slope setup'''
def __init__(self): def __init__(self):
@@ -513,7 +519,7 @@ class _HeightTaskPanel(_generalTaskPanel):
return True return True
# Slope Analisys: --------------------------------------------------------------------------------- # Slope Analisys: ---------------------------------------------------------------------------------
class _SlopeTaskPanel(_generalTaskPanel): class SlopeTaskPanel(_generalTaskPanel):
'''The TaskPanel for Slope setup''' '''The TaskPanel for Slope setup'''
def __init__(self): def __init__(self):
@@ -603,7 +609,7 @@ class _SlopeTaskPanel(_generalTaskPanel):
return True return True
# Orientation Analisys: --------------------------------------------------------------------------------- # Orientation Analisys: ---------------------------------------------------------------------------------
class _OrientationTaskPanel(_generalTaskPanel): class OrientationTaskPanel(_generalTaskPanel):
'''The TaskPanel for Orientation setup''' '''The TaskPanel for Orientation setup'''
def __init__(self): def __init__(self):
@@ -707,7 +713,7 @@ class _OrientationTaskPanel(_generalTaskPanel):
## Commands ---------------------------------------------------------------------------------------------------------- ## Commands ----------------------------------------------------------------------------------------------------------
## 1. Contours: ## 1. Contours:
class _CommandContours: '''class _CommandContours:
def GetResources(self): def GetResources(self):
return {'Pixmap': str(os.path.join(DirIcons, "TerrainContours.svg")), return {'Pixmap': str(os.path.join(DirIcons, "TerrainContours.svg")),
'Accel': "T, C", 'Accel': "T, C",
@@ -803,4 +809,4 @@ if FreeCAD.GuiUp:
FreeCADGui.addCommand('SlopeAnalisys', _CommandSlopeAnalisys()) FreeCADGui.addCommand('SlopeAnalisys', _CommandSlopeAnalisys())
FreeCADGui.addCommand('HeightAnalisys', _CommandHeightAnalisys()) FreeCADGui.addCommand('HeightAnalisys', _CommandHeightAnalisys())
FreeCADGui.addCommand('OrientationAnalisys', _CommandOrientationAnalisys()) FreeCADGui.addCommand('OrientationAnalisys', _CommandOrientationAnalisys())
FreeCADGui.addCommand('TerrainAnalisys', CommandTerrainAnalisysGroup()) FreeCADGui.addCommand('TerrainAnalisys', CommandTerrainAnalisysGroup())'''

690
PVPlantTools.py Normal file
View File

@@ -0,0 +1,690 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2017 - Amritpal Singh <amrit3701@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 *
# * *
# ***************************************************************************
__title__ = "RebarCommands"
__author__ = "Amritpal Singh"
__url__ = "https://www.freecadweb.org"
import FreeCADGui, FreeCAD
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
from PVPlantResources import DirIcons as DirIcons
import os
class CommandPVPlantSite:
"the PVPlant Site command definition"
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "icon.svg")),
'MenuText': QT_TRANSLATE_NOOP("Arch_Site", "Site"),
'Accel': "S, I",
'ToolTip': QT_TRANSLATE_NOOP("Arch_Site", "Creates a site object including selected objects.")}
@staticmethod
def IsActive():
return ((not (FreeCAD.ActiveDocument is None)) and
(FreeCAD.ActiveDocument.getObject("Site") is None))
@staticmethod
def Activated():
import PVPlantSite
PVPlantSite.makePVPlantSite()
return
class CommandProjectSetup:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "flash.svg")),
'Accel': "P, S",
'MenuText': "Project Setup",
'ToolTip': "Setup all the variable for this project"}
@staticmethod
def IsActive():
if FreeCAD.ActiveDocument:
return True
else:
return False
@staticmethod
def Activated():
from Project import ProjectSetup
taskd = ProjectSetup.ProjectSetupDialog()
taskd.setParent(FreeCADGui.getMainWindow())
taskd.setWindowFlags(QtCore.Qt.Window)
taskd.show()
class CommandTerrain:
"the PVPlant Terrain command definition"
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "terrain.svg")),
'MenuText': "Terrain",
'Accel': "S, T",
'ToolTip': "Creates a Terrain object from setup dialog."}
@staticmethod
def IsActive():
return (not (FreeCAD.ActiveDocument is None) and
not (FreeCAD.ActiveDocument.getObject("Site") is None) and
(FreeCAD.ActiveDocument.getObject("Terrain") is None))
@staticmethod
def Activated():
import PVPlantTerrain
PVPlantTerrain.makeTerrain()
# task = _TerrainTaskPanel()
# FreeCADGui.Control.showDialog(task)
return
class CommandCreateTerrainMesh:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "surface.svg")),
'MenuText': QT_TRANSLATE_NOOP("PVPlant", "Create Surface"),
'Accel': "C, S",
'ToolTip': QT_TRANSLATE_NOOP("PVPlant", "Creates a surface form a cloud of points.")}
@staticmethod
def IsActive():
return not FreeCAD.ActiveDocument is None
@staticmethod
def Activated():
import PVPlantCreateTerrainMesh
TaskPanel = PVPlantCreateTerrainMesh.TaskPanel()
FreeCADGui.Control.showDialog(TaskPanel)
class CommandDivideArea:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "area.svg")),
'Accel': "A, D",
'MenuText': "Divide Area",
'ToolTip': "Allowed Area"}
@staticmethod
def IsActive():
if FreeCAD.ActiveDocument:
return True
else:
return False
@staticmethod
def Activated():
sel = FreeCADGui.Selection.getSelection()[0]
class CommandBoundary:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "area.svg")),
'Accel': "A, B",
'MenuText': "Area",
'ToolTip': "Allowed Area"}
@staticmethod
def IsActive():
if FreeCAD.ActiveDocument:
return True
else:
return False
@staticmethod
def Activated():
from Project.Area import PVPlantArea
sel = FreeCADGui.Selection.getSelection()[0]
obj = PVPlantArea.makeArea([ver.Point for ver in sel.Shape.Vertexes])
# taskd = _PVPlantPlacementTaskPanel()
# FreeCADGui.Control.showDialog(taskd)
class CommandFrameArea:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "FrameArea.svg")),
'Accel': "A, F",
'MenuText': "Frame Area",
'ToolTip': "Frame Area"}
@staticmethod
def IsActive():
if FreeCAD.ActiveDocument:
return True
else:
return False
@staticmethod
def Activated():
sel = FreeCADGui.Selection.getSelection()
makeFramedArea(None, sel)
class CommandProhibitedArea:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "area_forbidden.svg")),
'Accel': "A, F",
'MenuText': "Prohibited Area",
'ToolTip': "Prohibited Area"}
@staticmethod
def IsActive():
if FreeCAD.ActiveDocument:
return True
else:
return False
@staticmethod
def Activated():
from Project.Area import PVPlantArea
sel = FreeCADGui.Selection.getSelection()
PVPlantArea.makeProhibitedArea(sel[0])
class CommandPVSubplant:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "subplant.svg")),
'Accel': "A, P",
'MenuText': "PV Subplant",
'ToolTip': "PV Subplant"}
@staticmethod
def IsActive():
if FreeCAD.ActiveDocument:
return True
else:
return False
@staticmethod
def Activated():
from Project.Area import PVPlantArea
area = PVPlantArea.makePVSubplant()
sel = FreeCADGui.Selection.getSelection()
for obj in sel:
if obj.Name[:7] == "Tracker":
frame_list = area.Frames
frame_list.append(obj)
area.Frames = frame_list
class CommandOffsetArea:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "offset.svg")),
'Accel': "A, O",
'MenuText': "OffsetArea",
'ToolTip': "OffsetArea"}
@staticmethod
def IsActive():
if FreeCAD.ActiveDocument:
return True
else:
return False
@staticmethod
def Activated():
from Project.Area import PVPlantArea
sel = FreeCADGui.Selection.getSelection()
base = None
if sel:
base = sel[0]
obj = PVPlantArea.makeOffsetArea(base)
class CommandSplitArea:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "split_area.svg")),
'Accel': "A, S",
'MenuText': "Split Area",
'ToolTip': "Split Area"}
@staticmethod
def IsActive():
return (not FreeCAD.ActiveDocument is None and
not FreeCAD.ActiveDocument.findObjects(Name="ProhibitedArea") is None and
not FreeCAD.ActiveDocument.findObjects(Name="OffsetArea") is None)
@staticmethod
def Activated():
from Project.Area import PVPlantAreaUtils
TaskPanel = PVPlantAreaUtils.splitAreaTaskPanel()
FreeCADGui.Control.showDialog(TaskPanel)
return
class CommandJoinAreas:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "split_area.svg")),
'Accel': "A, J",
'MenuText': "Join Areas",
'ToolTip': "Join Areas"}
@staticmethod
def IsActive():
return (not FreeCAD.ActiveDocument is None and
not FreeCAD.ActiveDocument.findObjects(Name="ProhibitedArea") is None and
not FreeCAD.ActiveDocument.findObjects(Name="OffsetArea") is None)
@staticmethod
def Activated():
from Project.Area import PVPlantAreaUtils
TaskPanel = PVPlantAreaUtils.splitAreaTaskPanel()
FreeCADGui.Control.showDialog(TaskPanel)
return
class CommandContours:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "TerrainContours.svg")),
'Accel': "T, C",
'MenuText': 'Curvas de nivel',
'ToolTip': 'Curvas de nivel'
}
@staticmethod
def IsActive():
# return not FreeCAD.ActiveDocument is None
if FreeCAD.ActiveDocument is None:
return False
return True
if FreeCADGui.Selection.getSelection() is not None:
selection = FreeCADGui.Selection.getSelection()[-1]
if selection.TypeId == 'Mesh::Feature':
return True
return False
@staticmethod
def Activated():
import PVPlantTerrainAnalisys
TaskPanel = PVPlantTerrainAnalisys.ContourTaskPanel()
FreeCADGui.Control.showDialog(TaskPanel)
class CommandSlopeAnalisys:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "TerrainSlope.svg")),
'Accel': "T, S",
'MenuText': 'Analisis de Pendiente',
'ToolTip': 'Analisis de Pendiente'
}
@staticmethod
def IsActive():
return not FreeCAD.ActiveDocument is None
@staticmethod
def Activated():
import PVPlantTerrainAnalisys
TaskPanel = PVPlantTerrainAnalisys.SlopeTaskPanel()
FreeCADGui.Control.showDialog(TaskPanel)
class CommandHeightAnalisys:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "TerrainHeight.svg")),
'Accel': "T, H",
'MenuText': 'Analisis de Altura',
'ToolTip': 'Analisis de Altura'
}
@staticmethod
def IsActive():
return not FreeCAD.ActiveDocument is None
@staticmethod
def Activated():
import PVPlantTerrainAnalisys
TaskPanel = PVPlantTerrainAnalisys.HeightTaskPanel()
FreeCADGui.Control.showDialog(TaskPanel)
class CommandOrientationAnalisys:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "TerrainOrientation.svg")),
'Accel': "T, H",
'MenuText': 'Analisis de Orientación',
'ToolTip': 'Analisis de Orientación'}
@staticmethod
def IsActive():
return not FreeCAD.ActiveDocument is None
@staticmethod
def Activated():
import PVPlantTerrainAnalisys
TaskPanel = PVPlantTerrainAnalisys.OrientationTaskPanel()
FreeCADGui.Control.showDialog(TaskPanel)
class CommandTrench: # V1:
"""Gui command for the Line tool."""
@staticmethod
def GetResources():
"""Set icon, menu and tooltip."""
return {'Pixmap': str(os.path.join(DirIcons, "trench.svg")),
'MenuText': "Trench",
'Accel': "C, T",
'ToolTip': "Creates a Trench object from setup dialog."}
@staticmethod
def IsActive():
active = not (FreeCAD.ActiveDocument is None)
terrain = not (FreeCAD.ActiveDocument.getObject("Terrain") is None)
active = active and terrain
if terrain:
active = active and not (FreeCAD.ActiveDocument.getObject("Terrain").Mesh is None)
return active
@staticmethod
def Activated():
"""Execute when the command is called."""
from Civil import PVPlantTrench
sel = FreeCADGui.Selection.getSelection()
done = False
if len(sel) > 0:
import Draft
for obj in sel:
if Draft.getType(obj) == "Wire":
FreeCAD.ActiveDocument.openTransaction("Create Trench")
PVPlantTrench.makeTrench(obj)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
done = True
break
if not done:
taskd = PVPlantTrench.TrenchTaskPanel()
if taskd:
FreeCADGui.Control.showDialog(taskd)
else:
print(" No ha sido posible crear el formulario")
class CommandSemiAutomaticTrench: # V1:
"""Gui command for the Line tool."""
@staticmethod
def GetResources():
"""Set icon, menu and tooltip."""
return {'Pixmap': str(os.path.join(DirIcons, "trench.svg")),
'MenuText': "Semi-Automatic Trench Generator",
'Accel': "T, S",
'ToolTip': "Creates a Trench object from setup dialog."}
@staticmethod
def IsActive():
active = not (FreeCAD.ActiveDocument is None)
terrain = not (FreeCAD.ActiveDocument.getObject("Terrain") is None)
active = active and terrain
if terrain:
active = active and not (FreeCAD.ActiveDocument.getObject("Terrain").Mesh is None)
return active
@staticmethod
def Activated():
"""Execute when the command is called."""
from Civil import PVPlantTrench
semi = PVPlantTrench.semiAutomaticTrench()
class CommandCalculateEarthworks:
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "pico.svg")),
'Accel': "C, E",
'MenuText': QT_TRANSLATE_NOOP("Placement", "Movimiento de tierras"),
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Calcular el movimiento de tierras")}
@staticmethod
def IsActive():
active = not (FreeCAD.ActiveDocument is None)
if not (FreeCAD.ActiveDocument.getObject("Terrain") is None):
active = active and not (FreeCAD.ActiveDocument.getObject("Terrain").Mesh is None)
return active
@staticmethod
def Activated():
import PVPlantEarthWorks
TaskPanel = PVPlantEarthWorks.EarthWorksTaskPanel()
FreeCADGui.Control.showDialog(TaskPanel)
class CommandManhole:
"the PVPlant Manhole command definition"
@staticmethod
def GetResources():
return {'Pixmap': str(os.path.join(DirIcons, "manhole.svg")),
'MenuText': "Manhole",
'Accel': "C, M",
'ToolTip': "Creates a Manhole object from setup dialog."}
@staticmethod
def IsActive():
return not (FreeCAD.ActiveDocument is None)
if FreeCAD.ActiveDocument is not None:
if FreeCADGui.Selection.getCompleteSelection():
for ob in FreeCAD.ActiveDocument.Objects:
if ob.Name[:4] == "Site":
return True
@staticmethod
def Activated():
import PVPlantManhole
task_panel = PVPlantManhole._ManholeTaskPanel()
FreeCADGui.Control.showDialog(task_panel)
return
if FreeCAD.GuiUp:
FreeCADGui.addCommand('PVPlantSite', CommandPVPlantSite())
import PVPlantGeoreferencing
FreeCADGui.addCommand('PVPlantGeoreferencing', PVPlantGeoreferencing.CommandPVPlantGeoreferencing())
FreeCADGui.addCommand('ProjectSetup', CommandProjectSetup())
FreeCADGui.addCommand('Terrain', CommandTerrain())
FreeCADGui.addCommand('PVPlantCreateTerrainMesh', CommandCreateTerrainMesh())
class CommandAreaGroup:
@staticmethod
def GetCommands():
return tuple([ # 'Area',
'FrameArea',
'ForbiddenArea',
'PVSubplant',
'OffsetArea'
])
@staticmethod
def GetResources():
return {'MenuText': 'Areas',
'ToolTip': 'Areas'
}
@staticmethod
def IsActive():
return not FreeCAD.ActiveDocument is None
# FreeCADGui.addCommand('Area', CommandBoundary())
FreeCADGui.addCommand('FrameArea', CommandFrameArea())
FreeCADGui.addCommand('ForbiddenArea', CommandProhibitedArea())
FreeCADGui.addCommand('PVSubplant', CommandPVSubplant())
FreeCADGui.addCommand('OffsetArea', CommandOffsetArea())
FreeCADGui.addCommand('PVPlantAreas', CommandAreaGroup())
FreeCADGui.addCommand('SplitArea', CommandSplitArea())
FreeCADGui.addCommand('JoinAreas', CommandJoinAreas())
class CommandTerrainAnalisysGroup:
@staticmethod
def GetCommands():
return tuple(['Contours',
'HeightAnalisys',
'SlopeAnalisys',
'OrientationAnalisys'
])
@staticmethod
def GetResources():
return {'MenuText': QT_TRANSLATE_NOOP("", 'Terrain Analisys'),
'ToolTip': QT_TRANSLATE_NOOP("", 'Terrain Analisys')
}
@staticmethod
def IsActive():
return not FreeCAD.ActiveDocument is None
FreeCADGui.addCommand('Contours', CommandContours())
FreeCADGui.addCommand('SlopeAnalisys', CommandSlopeAnalisys())
FreeCADGui.addCommand('HeightAnalisys', CommandHeightAnalisys())
FreeCADGui.addCommand('OrientationAnalisys', CommandOrientationAnalisys())
FreeCADGui.addCommand('TerrainAnalisys', CommandTerrainAnalisysGroup())
class CommandTrenchGroup:
@staticmethod
def GetCommands():
return tuple(['PVPlantTrench',
'PVPlantSemiAutomaticTrench',
])
@staticmethod
def GetResources():
return {'MenuText': 'Rack Types',
'ToolTip': 'Rack Types'
}
@staticmethod
def IsActive():
active = not (FreeCAD.ActiveDocument is None)
terrain = not (FreeCAD.ActiveDocument.getObject("Terrain") is None)
active = active and terrain
if terrain:
active = active and not (FreeCAD.ActiveDocument.getObject("Terrain").Mesh is None)
return active
FreeCADGui.addCommand('PVPlantTrench', CommandTrench())
FreeCADGui.addCommand('PVPlantSemiAutomaticTrench', CommandSemiAutomaticTrench())
FreeCADGui.addCommand('Trenches', CommandTrenchGroup())
FreeCADGui.addCommand('PVPlantEarthworks', CommandCalculateEarthworks())
FreeCADGui.addCommand('PVPlantManhole', CommandManhole())
import PVPlantPlacement
FreeCADGui.addCommand('PVPlantPlacement', PVPlantPlacement.CommandPVPlantPlacement())
FreeCADGui.addCommand('PVPlantAdjustToTerrain', PVPlantPlacement.CommandAdjustToTerrain())
FreeCADGui.addCommand('PVPlantConvertTo', PVPlantPlacement.CommandConvert())
import hydro.hydrological as hydro
FreeCADGui.addCommand('HydrologicalAnalysis', hydro.CommandHydrologicalAnalysis())
import Vegetation.PVPlantTreeGenerator as TreeGenerator
FreeCADGui.addCommand('PVPlantTree', TreeGenerator.CommandTree())
import Project.GenerateExternalDocument as GED
FreeCADGui.addCommand('newExternalDocument', GED.CommandGenerateExternalDocument())
from Mechanical.Frame import PVPlantFrame
class CommandRackGroup:
def GetCommands(self):
return tuple(['PVPlantFixedRack',
'PVPlantTrackerSetup',
'PVPlantTracker'
])
def GetResources(self):
return {'MenuText': QT_TRANSLATE_NOOP("", 'Rack Types'),
'ToolTip': QT_TRANSLATE_NOOP("", 'Rack Types')
}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
FreeCADGui.addCommand('PVPlantFixedRack', PVPlantFrame.CommandFixedRack())
FreeCADGui.addCommand('PVPlantTrackerSetup', PVPlantFrame.CommandTrackerSetup())
FreeCADGui.addCommand('PVPlantTracker', PVPlantFrame.CommandTracker())
FreeCADGui.addCommand('RackType', CommandRackGroup())
from Civil.Fence import PVPlantFence
FreeCADGui.addCommand('PVPlantFenceGroup', PVPlantFence.CommandFenceGroup())
projectlist = [ # "Reload",
"PVPlantSite",
"ProjectSetup",
"PVPlantGeoreferencing",
"Separator",
# "ImportGrid",
"Terrain",
"TerrainAnalisys",
"PVPlantCreateTerrainMesh",
"Separator",
# "PointsGroup",
"PVPlantAreas",
"SplitArea",
"Separator",
"Trenches",
"PVPlantEarthworks",
# "PVPlantPad",
# "PVPlantRoad",
"PVPlantManhole",
# "PVPlantFoundation"
# "GraphTerrainProfile",
# "Trace",
"Separator",
'HydrologicalAnalysis',
'newExternalDocument',
]
pv_mechanical = [
"RackType",
"PVPlantPlacement",
"PVPlantAdjustToTerrain",
"PVPlantConvertTo",
]
objectlist = ['PVPlantTree',
'PVPlantFenceGroup',]

View File

@@ -1,368 +0,0 @@
import math
import ArchComponent
import FreeCAD
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtCore, QtGui
from DraftTools import translate
from PySide.QtCore import QT_TRANSLATE_NOOP
import Part
import os
else:
# \cond
def translate(ctxt, txt):
return txt
def QT_TRANSLATE_NOOP(ctxt, txt):
return txt
# \endcond
__title__ = "FreeCAD Fixed Rack"
__author__ = "Javier Braña"
__url__ = "http://www.sogos-solar.com"
__dir__ = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", "PVPlant")
DirResources = os.path.join(__dir__, "Resources")
DirIcons = os.path.join(DirResources, "Icons")
DirImages = os.path.join(DirResources, "Images")
def makeTree():
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Tree")
Tree(obj)
ViewProviderTree(obj.ViewObject)
FreeCAD.ActiveDocument.recompute()
try:
folder = FreeCAD.ActiveDocument.Vegetation
except:
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Vegetation')
folder.Label = "Vegetation"
folder.addObject(obj)
return obj
class Tree(ArchComponent.Component):
""" A Shadow Tree Obcject """
def __init__(self, obj):
# Definición de variables:
ArchComponent.Component.__init__(self, obj)
self.obj = obj
self.setProperties(obj)
def setProperties(self, obj):
# Definicion de Propiedades:
pl = obj.PropertiesList
# CANOPY: ---------------------------------------------------------
if not ("CanopyHeight" in pl):
obj.addProperty("App::PropertyLength",
"CanopyHeight",
"Canopy",
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
).CanopyHeight = 4000
if not ("CanopyRadius" in pl):
obj.addProperty("App::PropertyLength",
"CanopyRadius",
"Canopy",
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
).CanopyRadius = 1500
if not ("Spikiness" in pl):
obj.addProperty("App::PropertyFloatConstraint",
"Spikiness",
"Canopy",
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
).Spikiness = (0.5, 0.0, 1.0, 0.05) # (Default, Start, Finish, Step)
'''
if not ("Lumpiness" in pl):
obj.addProperty("App::PropertyFloatConstraint",
"Lumpiness",
"Canopy",
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
).Lumpiness = (0.0, 0.0, 1.0, 0.05) #(Default, Start, Finish, Step)'''
if not ("CrownExpansion" in pl):
obj.addProperty("App::PropertyFloatConstraint",
"CrownExpansion",
"Canopy",
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
).CrownExpansion = (1.0, 0.0, 2.0, 0.05) # (Default, Start, Finish, Step)
if not ("UmbrellaEffect" in pl):
obj.addProperty("App::PropertyFloatConstraint",
"UmbrellaEffect",
"Canopy",
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
).UmbrellaEffect = (0.0, 0.0, 1.0, 0.05) # (Default, Start, Finish, Step)
if not ("LeafCount" in pl):
obj.addProperty("App::PropertyQuantity",
"LeafCount",
"Canopy",
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
).LeafCount = 20
# TRUNK: ------------------------------------------------------------------------------------------------------
if not ("TrunkHeight" in pl):
obj.addProperty("App::PropertyLength",
"TrunkHeight",
"Trunk",
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
).TrunkHeight = 2000
if not ("TrunkRadius" in pl):
obj.addProperty("App::PropertyLength",
"TrunkRadius",
"Trunk",
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
).TrunkRadius = 150
if not ("TrunkFaces" in pl):
obj.addProperty("App::PropertyQuantity",
"TrunkFaces",
"Trunk",
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
).TrunkFaces = 6
if not ("Type" in pl):
obj.addProperty("App::PropertyString",
"Type",
"Base",
"Type").Type = "Vegetable-Tree"
obj.setEditorMode("Type", 1)
self.Type = obj.Type
obj.Proxy = self
obj.IfcType = "Shading Device"
obj.setEditorMode("IfcType", 1)
def onDocumentRestored(self, obj):
ArchComponent.Component.onDocumentRestored(self, obj)
self.setProperties(obj)
def onChanged(self, obj, prop):
'''if prop in ["CanopyHeight", "CanopyHeight", "Spikiness", "CrownExpansion", "UmbrellaEffect",
"LeafCount"]:
self.canopy = self.createCanopy(obj)
if prop in ["TrunkHeight", "TrunkRadius", "TrunkFaces"]:
self.trunk = self.createTrunk(obj)'''
def createTrunk(self, obj):
import Part
angle = (math.pi * 2) / obj.TrunkFaces.Value
delta = obj.TrunkRadius.Value
pts = [FreeCAD.Vector(delta, 0, 0)]
for i in range(int(obj.TrunkFaces.Value) - 1):
ang = (i + 1) * angle
pts.append(FreeCAD.Vector(delta * math.cos(ang),
delta * math.sin(ang),
0))
pts.append(pts[0])
p1 = Part.makePolygon(pts)
p0 = p1.makeOffset2D(90, 2, False, False, True)
p2 = p1.makeOffset2D(-50, 2, False, False, True)
p0.Placement.Base.z = -150
p1.Placement.Base.z = 150
p2.Placement.Base.z = obj.TrunkHeight.Value - 250
return Part.makeLoft([p0, p1, p2], True, True, False)
def createCanopy(self, obj):
import Part
import random
import Mesh
import numpy as np
ncircles = int(obj.LeafCount.Value)
if ncircles % 2 == 0:
ncircles += 1
half_ncircles = int(ncircles / 2)
ncirclesumbrella = int(half_ncircles/2)
ncirclestop = ncircles - ncirclesumbrella
# 1. Create circles to define the sphere
circles = []
dist = 2 * obj.CanopyRadius.Value / (ncircles - 1)
margin = dist * 0.01
'''for i in range(half_ncircles + 1):
if i > 0:
d = (obj.CanopyRadius.Value - dist * i)
else:
d = obj.CanopyRadius.Value - margin
r = (obj.CanopyRadius.Value ** 2 - d ** 2) ** 0.5
c = Part.makeCircle(r)
circles.append(c)
ctmp = [c.copy() for c in circles]
ctmp.pop()
ctmp.reverse()
circles.extend(ctmp)'''
d = - obj.CanopyRadius.Value - dist
b = (obj.CanopyRadius.Value ** 2 - (dist * (ncirclesumbrella - 1)) ** 2) ** 0.5
for i in range(ncircles):
d += dist
r = (obj.CanopyRadius.Value ** 2 - d ** 2) ** 0.5
if i > ncirclesumbrella:
if obj.CrownExpansion < 1:
r = r * obj.CrownExpansion
if r == 0:
r = obj.CanopyRadius.Value * 0.01
c = Part.makeCircle(r)
circles.append(c)
# 2. Place circles
dist = obj.CanopyHeight.Value / ncircles
z = 0
#zmax = dist * half_ncircles
for idx in range(1, half_ncircles):
z += dist
circles[idx].Placement.Base.z = (1 - obj.UmbrellaEffect) * z
#c.Placement.Base.z = obj.UmbrellaEffect * (zmax - z) + z
dist1 = (obj.CanopyHeight.Value - z) / (half_ncircles + 1)
for idx in range(half_ncircles, ncircles):
c = circles[idx]
z += dist1
c.Placement.Base.z = z
# 3. noise generator
pts = []
val = (dist / 2 - margin) * obj.Spikiness
for c in circles:
tmppts = c.discretize(ncircles)
for j in range(len(tmppts)):
point = tmppts[j]
point.x += random.uniform(-val, val)
point.y += random.uniform(-val, val)
point.z += random.uniform(-val, val)
pts.append(point)
# 4. generate the mesh / solid
from scipy import spatial as sp_spatial
pts = np.array(pts)
hull = sp_spatial.ConvexHull(pts)
indices = hull.simplices
faces = pts[indices]
mesh = Mesh.Mesh(faces.tolist())
if len(mesh.Facets) == 0:
return None
mesh.harmonizeNormals()
'''if mesh.Facets[0].Normal.z < 0:
mesh.flipNormals()'''
shape = Part.Shape()
shape.makeShapeFromMesh(mesh.Topology, 0.1)
return Part.makeSolid(shape)
def execute(self, obj):
pl = obj.Placement
trunk = self.createTrunk(obj)
canopy = self.createCanopy(obj)
canopy.Placement.Base.z = obj.TrunkHeight.Value - 250 # - obj.CanopyRadius.Value / 2
obj.Shape = Part.makeCompound([trunk, canopy])
obj.Placement = pl
color = [(0.2510, 0.1255, 0.0)] * len(trunk.Faces)
color.extend([(0.0, 0.3922, 0.0)] * len(canopy.Faces))
obj.ViewObject.DiffuseColor = color
class ViewProviderTree(ArchComponent.ViewProviderComponent):
"A View Provider for the Pipe object"
def __init__(self, vobj):
ArchComponent.ViewProviderComponent.__init__(self, vobj)
def getIcon(self):
return str(os.path.join(DirIcons, "tree(1).svg"))
class TreeTaskPanel(QtGui.QWidget):
def __init__(self, obj=None):
QtGui.QWidget.__init__(self)
self.obj = obj
if self.obj is None:
self.obj = makeTree()
self.form = FreeCADGui.PySideUic.loadUi(__dir__ + "/PVPlantTree.ui")
self.layout = QtGui.QHBoxLayout(self)
self.layout.setContentsMargins(4, 4, 4, 4)
self.layout.addWidget(self.form)
self.form.editCanopyHeight.valueChanged.connect(self.Canopy)
self.form.editCanopyRadius.valueChanged.connect(self.Canopy)
self.form.editSpikiness.valueChanged.connect(self.Canopy)
self.form.editCrownExpansion.valueChanged.connect(self.Canopy)
self.form.editLeftUmbrellaEffect.valueChanged.connect(self.Canopy)
self.form.editLeafCount.valueChanged.connect(self.Canopy)
def Canopy(self):
self.obj.CanopyHeight = FreeCAD.Units.Quantity(self.form.editCanopyHeight.text()).Value
self.obj.CanopyRadius = FreeCAD.Units.Quantity(self.form.editCanopyRadius.text()).Value
self.obj.Spikiness = self.form.editSpikiness.value()
self.obj.CrownExpansion = self.form.editCrownExpansion.value()
self.obj.UmbrellaEffect = self.form.editLeftUmbrellaEffect.value()
self.obj.LeafCount = self.form.editLeafCount.value()
FreeCAD.ActiveDocument.recompute()
def accept(self):
FreeCADGui.Control.closeDialog()
return True
def reject(self):
FreeCAD.ActiveDocument.removeObject(self.obj.Name)
FreeCADGui.Control.closeDialog()
return True
class _CommandTree:
"the PVPlant Tree command definition"
def GetResources(self):
return {'Pixmap': str(os.path.join(DirIcons, "tree(1).svg")),
'MenuText': QtCore.QT_TRANSLATE_NOOP("PVPlantTree", "Tree"),
'Accel': "S, T",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PVPlanTree",
"Creates a Tree object from setup dialog.")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
import draftguitools.gui_trackers as DraftTrackers
self.tree = makeTree()
FreeCADGui.Snapper.getPoint(callback=self.getPoint,
movecallback=self.mousemove,
extradlg=self.taskbox(),
title="Position of the tree:")
def getPoint(self, point=None, obj=None):
self.tree.Placement.Base = point
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
self.tracker.finalize()
def mousemove(self, pt, snapInfo):
self.tree.Placement.Base = pt
def taskbox(self):
self.form = TreeTaskPanel(self.tree)
return self.form
if FreeCAD.GuiUp:
FreeCADGui.addCommand('PVPlantTree', _CommandTree())

301
Photovoltaic/Module.py Normal file
View File

@@ -0,0 +1,301 @@
# /**********************************************************************
# * *
# * 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 ArchComponent
import FreeCAD
import Part
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtCore
import draftguitools.gui_trackers as DraftTrackers
else:
# \cond
def translate(ctxt, txt):
return txt
def QT_TRANSLATE_NOOP(ctxt, txt):
return txt
# \endcond
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
import os
import PVPlantResources
from PVPlantResources import DirIcons as DirIcons
def makeModule(name="Module"):
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
obj.Label = name
Module(obj)
ViewProviderModule(obj.ViewObject)
FreeCAD.ActiveDocument.recompute()
return obj
class Module(ArchComponent.Component):
"A Manhole Obcject"
def __init__(self, obj):
# Definición de Variables:
ArchComponent.Component.__init__(self, obj)
self.setProperties(obj)
def setProperties(self, obj):
pl = obj.PropertiesList
# Base de datos: -----------------------------------------------------------------------------------------------
if not ("Manufacturer" in pl):
obj.addProperty("App::PropertyStringList",
"Manufacturer",
"Module",
"The manufacturer of this object"
)
if not ("Model" in pl):
obj.addProperty("App::PropertyStringList",
"Model",
"Module",
"The model of this object"
)
# Dimensions: --------------------------------------------------------------------------------------------------
if not ("Height" in pl):
obj.addProperty("App::PropertyLength",
"Height",
"Manhole",
"The height of this object"
).Height = 2804
if not ("Width" in pl):
obj.addProperty("App::PropertyLength",
"Width",
"Manhole",
"The width of this object"
).Width = 1303
if not ("Thickness" in pl):
obj.addProperty("App::PropertyLength",
"Thickness",
"Manhole",
"The height of this object"
).Thickness = 35
# Electrical: --------------------------------------------------------------------------------------------------
if not ("Power" in pl):
obj.addProperty("App::PropertyPower",
"Power",
"Outputs",
"The height of this object"
).Power = 650
if not ("Umpp" in pl):
obj.addProperty("App::PropertyElectricCurrent",
"Umpp",
"Outputs",
"The height of this object"
).Umpp = 650
if not ("Impp" in pl):
obj.addProperty("App::PropertyElectricCurrent",
"Impp",
"Outputs",
"The height of this object"
).Impp = 650
if not ("Uoc" in pl):
obj.addProperty("App::PropertyElectricCurrent",
"Uoc",
"Outputs",
"The height of this object"
).Uoc = 650
if not ("Isc" in pl):
obj.addProperty("App::PropertyElectricCurrent",
"Isc",
"Outputs",
"The height of this object"
).Isc = 650
if not ("Isc" in pl):
obj.addProperty("App::PropertyElectricCurrent",
"Impp",
"Outputs",
"The height of this object"
).Impp = 650
self.Type = "Module"
obj.Proxy = self
def onDocumentRestored(self, obj):
"""Method run when the document is restored.
Re-adds the Arch component, and Arch wall properties."""
ArchComponent.Component.onDocumentRestored(self, obj)
self.setProperties(obj)
def onChanged(self, obj, prop):
'''Do something when a property has changed'''
def execute(self, obj):
box = Part.makeBox(obj.Width, obj.Height, obj.Thickness)
box.translate(FreeCAD.Vector(-obj.Width, -obj.Height, 0) / 2)
obj.Shape = box
class ViewProviderModule(ArchComponent.ViewProviderComponent):
"A View Provider for the Module object"
def __init__(self, vobj):
ArchComponent.ViewProviderComponent.__init__(self, vobj)
def getIcon(self):
return str(os.path.join(DirIcons, "manhole.svg"))
def setEdit(self, vobj, mode):
"""Method called when the document requests the object to enter edit mode.
Edit mode is entered when a user double clicks on an object in the tree
view, or when they use the menu option [Edit -> Toggle Edit Mode].
Just display the standard Arch component task panel.
Parameters
----------
mode: int or str
The edit mode the document has requested. Set to 0 when requested via
a double click or [Edit -> Toggle Edit Mode].
Returns
-------
bool
If edit mode was entered.
"""
if (mode == 0) and hasattr(self, "Object"):
taskd = _ManholeTaskPanel(self.Object)
taskd.obj = self.Object
# taskd.update()
FreeCADGui.Control.showDialog(taskd)
return True
return False
class ManholeTaskPanel:
def __init__(self, obj=None):
self.new = False
if obj is None:
self.new = True
obj = makeManhole()
self.obj = obj
self.form = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantManhole.ui")
self.node = None
self.view = FreeCADGui.ActiveDocument.ActiveView
self.tracker = DraftTrackers.ghostTracker(obj)
self.tracker.on()
self.call = self.view.addEventCallback("SoEvent", self.action)
def action(self, arg):
"""Handle the 3D scene events.
This is installed as an EventCallback in the Inventor view.
Parameters
----------
arg: dict
Dictionary with strings that indicates the type of event received
from the 3D view.
"""
if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE":
self.finish()
elif arg["Type"] == "SoLocation2Event":
point, ctrlPoint, info = gui_tool_utils.getPoint(self, arg)
if info:
self.tracker.move(FreeCAD.Vector(info["x"], info["y"], info["z"]))
else:
self.tracker.move(point)
elif (arg["Type"] == "SoMouseButtonEvent" and
arg["State"] == "DOWN" and
arg["Button"] == "BUTTON1"):
point, ctrlPoint, info = gui_tool_utils.getPoint(self, arg)
if info:
self.obj.Placement.Base = FreeCAD.Vector(info["x"], info["y"], info["z"])
else:
self.obj.Placement.Base = point
self.finish()
def finish(self):
self.accept()
def accept(self):
self.closeForm()
return True
def reject(self):
if self.new:
FreeCAD.ActiveDocument.removeObject(self.obj.Name)
self.closeForm()
return True
def closeForm(self):
self.tracker.finalize()
FreeCADGui.Control.closeDialog()
self.view.removeEventCallback("SoEvent", self.call)
'''class _CommandManhole:
"the Arch Building command definition"
def GetResources(self):
return {'Pixmap': str(os.path.join(DirIcons, "manhole.svg")),
'MenuText': "Manhole",
'Accel': "C, M",
'ToolTip': "Creates a Manhole object from setup dialog."}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
if FreeCAD.ActiveDocument is not None:
if FreeCADGui.Selection.getCompleteSelection():
for ob in FreeCAD.ActiveDocument.Objects:
if ob.Name[:4] == "Site":
return True
def Activated(self):
TaskPanel = _ManholeTaskPanel()
FreeCADGui.Control.showDialog(TaskPanel)
return
if FreeCAD.GuiUp:
FreeCADGui.addCommand('PVPlantManhole', _CommandManhole())'''

View File

@@ -26,6 +26,9 @@ import PVPlantSite
import Utils.PVPlantUtils as utils import Utils.PVPlantUtils as utils
import MeshPart as mp import MeshPart as mp
import pivy
from pivy import coin
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
import FreeCADGui import FreeCADGui
from DraftTools import translate from DraftTools import translate
@@ -69,6 +72,7 @@ class _Area:
''' Initialize the Area object ''' ''' Initialize the Area object '''
self.Type = None self.Type = None
self.obj = None self.obj = None
self.setProperties(obj)
def setProperties(self, obj): def setProperties(self, obj):
pl = obj.PropertiesList pl = obj.PropertiesList
@@ -95,18 +99,24 @@ class _Area:
""" Method run when the document is restored """ """ Method run when the document is restored """
self.setProperties(obj) self.setProperties(obj)
def __getstate__(self):
return None # No necesitamos guardar estado adicional
def __setstate__(self, state):
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):
''' '''
@@ -114,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):
""" """
@@ -153,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 '''
@@ -263,7 +267,7 @@ class FrameArea(_Area):
if not hasattr(o, "Proxy"): if not hasattr(o, "Proxy"):
continue continue
if o.Proxy.Type == "Tracker": if o.Proxy.Type == "Tracker":
lf.append(obj) lf.append(o)
obj.Frames = lf obj.Frames = lf
obj.FramesNumber = len(obj.Frames) obj.FramesNumber = len(obj.Frames)
@@ -274,10 +278,17 @@ class FrameArea(_Area):
def execute(self, obj): def execute(self, obj):
''' execute ''' ''' execute '''
#_Area.execute(self, obj)
if not hasattr(obj, "Frames"):
return
obj.FrameNumber = len(obj.Frames) obj.FrameNumber = len(obj.Frames)
def __getstate__(self):
return None
def __setstate__(self, state):
pass
class ViewProviderFrameArea(_ViewProviderArea): class ViewProviderFrameArea(_ViewProviderArea):
def __init__(self, vobj): def __init__(self, vobj):
@@ -298,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:
@@ -321,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",
@@ -341,18 +351,28 @@ class OffsetArea(_Area):
self.setProperties(obj) self.setProperties(obj)
def execute(self, obj): def execute(self, obj):
import Utils.PVPlantUtils as utils # Comprobar dependencias críticas
if not hasattr(obj, "Base") or not obj.Base or not obj.Base.Shape:
return
if not hasattr(PVPlantSite, "get") or not PVPlantSite.get().Terrain:
return
base = obj.Base.Shape base = obj.Base.Shape
land = PVPlantSite.get().Terrain.Mesh land = PVPlantSite.get().Terrain.Mesh
vec = FreeCAD.Vector(0, 0, 1) vec = FreeCAD.Vector(0, 0, 1)
wire = utils.getProjected(base, vec) wire = utils.getProjected(base, vec)
wire = wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True) wire = wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True)
tmp = mp.projectShapeOnMesh(wire, land, vec) sections = mp.projectShapeOnMesh(wire, land, vec)
print(" javi ", sections)
pts = [] pts = []
for section in tmp: for section in sections:
pts.extend(section) pts.extend(section)
obj.Shape = Part.makePolygon(pts) # Crear forma solo si hay resultados
if len(pts)>0:
obj.Shape = Part.makePolygon(pts)
else:
obj.Shape = Part.Shape() # Forma vacía si falla
class ViewProviderOffsetArea(_ViewProviderArea): class ViewProviderOffsetArea(_ViewProviderArea):
@@ -363,24 +383,22 @@ 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", "ProhibitedArea") obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "ExclusionArea")
ProhibitedArea(obj) ProhibitedArea(obj)
ViewProviderForbiddenArea(obj.ViewObject) ViewProviderForbiddenArea(obj.ViewObject)
if base: if base:
obj.Base = base obj.Base = base
try: try:
group = FreeCAD.ActiveDocument.Exclusion group = FreeCAD.ActiveDocument.getObject("Exclusions")
except: except:
group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Exclusion') group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Exclusions')
group.Label = "Exclusions" group.Label = "Exclusions"
group.addObject(obj) group.addObject(obj)
@@ -401,23 +419,443 @@ class ProhibitedArea(OffsetArea):
"""Method run when the document is restored.""" """Method run when the document is restored."""
self.setProperties(obj) self.setProperties(obj)
def execute(self, obj):
# Comprobar dependencias
if not hasattr(obj, "Base") or not obj.Base or not obj.Base.Shape:
return
if not hasattr(PVPlantSite, "get") or not PVPlantSite.get().Terrain:
return
class ViewProviderForbiddenArea(_ViewProviderArea): base = obj.Base.Shape
def getIcon(self): land = PVPlantSite.get().Terrain.Mesh
''' Return object treeview icon ''' vec = FreeCAD.Vector(0, 0, 1)
return str(os.path.join(DirIcons, "area_forbidden.svg"))
# 1. Crear wire original
original_wire = utils.getProjected(base, vec)
sections_original = mp.projectShapeOnMesh(original_wire, land, vec)
# 2. Crear wire offset
offset_wire = original_wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True)
sections_offset = mp.projectShapeOnMesh(offset_wire, land, vec)
# Crear formas compuestas
def make_polygon(sections):
if not sections:
return Part.Shape()
pts = []
for section in sections:
pts.extend(section)
return Part.makePolygon(pts)
compounds = []
if sections_original:
compounds.append(make_polygon(sections_original))
if sections_offset:
compounds.append(make_polygon(sections_offset))
if compounds:
obj.Shape = Part.makeCompound(compounds)
else:
obj.Shape = Part.Shape()
# Actualizar colores en la vista
"""if FreeCAD.GuiUp and obj.ViewObject:
obj.ViewObject.Proxy.updateVisual()"""
class ViewProviderForbiddenArea_old:
def __init__(self, vobj):
vobj.Proxy = self
self.setProperties(vobj)
def setProperties(self, vobj):
# Propiedades de color
if not hasattr(vobj, "OriginalColor"):
vobj.addProperty("App::PropertyColor",
"OriginalColor",
"ObjectStyle",
"Color for original wire")
vobj.OriginalColor = (1.0, 0.0, 0.0) # Rojo
if not hasattr(vobj, "OffsetColor"):
vobj.addProperty("App::PropertyColor",
"OffsetColor",
"ObjectStyle",
"Color for offset wire")
vobj.OffsetColor = (1.0, 0.0, 0.0) # Rojo
# Propiedades de grosor
if not hasattr(vobj, "OriginalWidth"):
vobj.addProperty("App::PropertyFloat",
"OriginalWidth",
"ObjectStyle",
"Line width for original wire")
vobj.OriginalWidth = 4.0
if not hasattr(vobj, "OffsetWidth"):
vobj.addProperty("App::PropertyFloat",
"OffsetWidth",
"ObjectStyle",
"Line width for offset wire")
vobj.OffsetWidth = 4.0
# Deshabilitar el color por defecto
vobj.setPropertyStatus("LineColor", "Hidden")
vobj.setPropertyStatus("PointColor", "Hidden")
vobj.setPropertyStatus("ShapeAppearance", "Hidden")
def attach(self, vobj):
self.ViewObject = vobj
self.Object = vobj.Object
# Crear la estructura de escena Coin3D
self.root = coin.SoGroup()
# Switch para habilitar/deshabilitar la selección
self.switch = coin.SoSwitch()
self.switch.whichChild = coin.SO_SWITCH_ALL
# Separador para el wire original
self.original_sep = coin.SoSeparator()
self.original_color = coin.SoBaseColor()
self.original_coords = coin.SoCoordinate3()
self.original_line_set = coin.SoLineSet()
self.original_draw_style = coin.SoDrawStyle()
# Separador para el wire offset
self.offset_sep = coin.SoSeparator()
self.offset_color = coin.SoBaseColor()
self.offset_coords = coin.SoCoordinate3()
self.offset_line_set = coin.SoLineSet()
self.offset_draw_style = coin.SoDrawStyle()
# Construir la jerarquía de escena
self.original_sep.addChild(self.original_color)
self.original_sep.addChild(self.original_draw_style)
self.original_sep.addChild(self.original_coords)
self.original_sep.addChild(self.original_line_set)
self.offset_sep.addChild(self.offset_color)
self.offset_sep.addChild(self.offset_draw_style)
self.offset_sep.addChild(self.offset_coords)
self.offset_sep.addChild(self.offset_line_set)
self.switch.addChild(self.original_sep)
self.switch.addChild(self.offset_sep)
self.root.addChild(self.switch)
vobj.addDisplayMode(self.root, "Wireframe")
# Inicializar estilos de dibujo
self.original_draw_style.style = coin.SoDrawStyle.LINES
self.offset_draw_style.style = coin.SoDrawStyle.LINES
# Actualizar visualización inicial
if hasattr(self.Object, 'Shape'):
self.updateData(self.Object, "Shape")
self.updateVisual()
def updateData(self, obj, prop):
if prop == "Shape" and obj.Shape and not obj.Shape.isNull():
self.updateGeometry()
def updateGeometry(self):
"""Actualiza la geometría en la escena 3D"""
if not hasattr(self, 'Object') or not self.Object.Shape or self.Object.Shape.isNull():
return
# Limpiar coordenadas existentes
self.original_coords.point.deleteValues(0)
self.offset_coords.point.deleteValues(0)
# Obtener los sub-shapes
subshapes = []
if hasattr(self.Object.Shape, 'SubShapes') and self.Object.Shape.SubShapes:
subshapes = self.Object.Shape.SubShapes
elif hasattr(self.Object.Shape, 'ChildShapes') and self.Object.Shape.ChildShapes:
subshapes = self.Object.Shape.ChildShapes
# Procesar wire original (primer sub-shape)
if len(subshapes) > 0:
self.processShape(subshapes[0], self.original_coords, self.original_line_set)
# Procesar wire offset (segundo sub-shape)
if len(subshapes) > 1:
self.processShape(subshapes[1], self.offset_coords, self.offset_line_set)
# Actualizar colores y grosores
self.updateVisual()
def processShape(self, shape, coords_node, lineset_node):
"""Procesa una forma y la añade al nodo de coordenadas"""
if not shape or shape.isNull():
return
points = []
line_indices = []
current_index = 0
# Obtener todos los edges de la forma
edges = []
if hasattr(shape, 'Edges'):
edges = shape.Edges
elif hasattr(shape, 'ChildShapes'):
for child in shape.ChildShapes:
if hasattr(child, 'Edges'):
edges.extend(child.Edges)
for edge in edges:
try:
# Discretizar la curva para obtener puntos
vertices = edge.discretize(Number=50)
for i, vertex in enumerate(vertices):
points.append([vertex.x, vertex.y, vertex.z])
line_indices.append(current_index)
current_index += 1
# Añadir -1 para indicar fin de línea
line_indices.append(-1)
except Exception as e:
print(f"Error processing edge: {e}")
continue
# Configurar coordenadas y líneas
if points:
coords_node.point.setValues(0, len(points), points)
lineset_node.numVertices.deleteValues(0)
lineset_node.numVertices.setValues(0, len(line_indices), line_indices)
def updateVisual(self):
"""Actualiza colores y grosores según las propiedades"""
if not hasattr(self, 'ViewObject') or not self.ViewObject:
return
vobj = self.ViewObject
try:
# Configurar wire original
if hasattr(vobj, "OriginalColor"):
original_color = vobj.OriginalColor
self.original_color.rgb.setValue(original_color[0], original_color[1], original_color[2])
if hasattr(vobj, "OriginalWidth"):
self.original_draw_style.lineWidth = vobj.OriginalWidth
# Configurar wire offset
if hasattr(vobj, "OffsetColor"):
offset_color = vobj.OffsetColor
self.offset_color.rgb.setValue(offset_color[0], offset_color[1], offset_color[2])
if hasattr(vobj, "OffsetWidth"):
self.offset_draw_style.lineWidth = vobj.OffsetWidth
except Exception as e:
print(f"Error updating visual: {e}")
def onChanged(self, vobj, prop):
"""Maneja cambios en propiedades"""
if prop in ["OriginalColor", "OffsetColor", "OriginalWidth", "OffsetWidth"]:
self.updateVisual()
def getDisplayModes(self, obj):
return ["Wireframe"]
def getDefaultDisplayMode(self):
return "Wireframe"
def setDisplayMode(self, mode):
return mode
def claimChildren(self): def claimChildren(self):
""" Provides object grouping """ """Proporciona agrupamiento de objetos"""
children = [] children = []
if self.Object.Base: if hasattr(self, 'Object') and self.Object and hasattr(self.Object, "Base"):
children.append(self.Object.Base) children.append(self.Object.Base)
return children return children
def getIcon(self):
'''Return object treeview icon'''
return str(os.path.join(DirIcons, "area_forbidden.svg"))
def onDocumentRestored(self, vobj):
"""Método ejecutado cuando el documento es restaurado"""
self.ViewObject = vobj
self.Object = vobj.Object
self.setProperties(vobj)
self.attach(vobj)
def __getstate__(self):
return None
def __setstate__(self, state):
return None
class ViewProviderForbiddenArea:
def __init__(self, vobj):
vobj.Proxy = self
self.ViewObject = vobj
# Inicializar propiedades PRIMERO
self.setProperties(vobj)
# Configurar colores iniciales
self.updateColors(vobj)
def setProperties(self, vobj):
if not hasattr(vobj, "OriginalColor"):
vobj.addProperty("App::PropertyColor",
"OriginalColor",
"Display",
"Color for original wire")
vobj.OriginalColor = (1.0, 0.0, 0.0) # Rojo
if not hasattr(vobj, "OffsetColor"):
vobj.addProperty("App::PropertyColor",
"OffsetColor",
"Display",
"Color for offset wire")
vobj.OffsetColor = (1.0, 0.5, 0.0) # Naranja
def updateColors(self, vobj):
"""Actualiza los colores desde las propiedades"""
try:
if hasattr(vobj, "OriginalColor"):
self.original_color.rgb.setValue(*vobj.OriginalColor)
else:
self.original_color.rgb.setValue(1.0, 0.0, 0.0)
if hasattr(vobj, "OffsetColor"):
self.offset_color.rgb.setValue(*vobj.OffsetColor)
else:
self.offset_color.rgb.setValue(1.0, 0.5, 0.0)
except Exception as e:
print(f"Error en updateColors: {e}")
def onDocumentRestored(self, vobj):
self.setProperties(vobj)
# No llamar a __init__ de nuevo, solo actualizar propiedades
self.updateColors(vobj)
def getIcon(self):
return str(os.path.join(DirIcons, "area_forbidden.svg"))
def attach(self, vobj):
self.ViewObject = vobj
# Inicializar nodos Coin3D
self.root = coin.SoGroup()
self.original_coords = coin.SoCoordinate3()
self.offset_coords = coin.SoCoordinate3()
self.original_color = coin.SoBaseColor()
self.offset_color = coin.SoBaseColor()
self.original_lineset = coin.SoLineSet()
self.offset_lineset = coin.SoLineSet()
# Añadir un nodo de dibujo para establecer el estilo de línea
self.draw_style = coin.SoDrawStyle()
self.draw_style.style = coin.SoDrawStyle.LINES
self.draw_style.lineWidth = 3.0
# Construir la escena
self.root.addChild(self.draw_style)
# Grupo para el polígono original
original_group = coin.SoGroup()
original_group.addChild(self.original_color)
original_group.addChild(self.original_coords)
original_group.addChild(self.original_lineset)
# Grupo para el polígono offset
offset_group = coin.SoGroup()
offset_group.addChild(self.offset_color)
offset_group.addChild(self.offset_coords)
offset_group.addChild(self.offset_lineset)
self.root.addChild(original_group)
self.root.addChild(offset_group)
vobj.addDisplayMode(self.root, "Standard")
# Asegurar que la visibilidad esté activada
vobj.Visibility = True
def updateData(self, obj, prop):
if prop == "Shape":
self.updateVisual(obj)
def updateVisual(self, obj):
"""Actualiza la representación visual basada en la forma del objeto"""
if not hasattr(obj, 'Shape') or not obj.Shape or obj.Shape.isNull():
return
try:
# Obtener todos los bordes de la forma compuesta
all_edges = obj.Shape.Edges
# Separar bordes por polígono (asumimos que el primer polígono es el original)
# Esto es una simplificación - podrías necesitar una lógica más sofisticada
if len(all_edges) >= 2:
# Polígono original - primer conjunto de bordes
original_edges = [all_edges[0]]
original_points = []
for edge in original_edges:
for vertex in edge.Vertexes:
original_points.append((vertex.Point.x, vertex.Point.y, vertex.Point.z))
# Polígono offset - segundo conjunto de bordes
offset_edges = [all_edges[1]]
offset_points = []
for edge in offset_edges:
for vertex in edge.Vertexes:
offset_points.append((vertex.Point.x, vertex.Point.y, vertex.Point.z))
# Asignar puntos a los nodos Coordinate3
if original_points:
self.original_coords.point.setValues(0, len(original_points), original_points)
self.original_lineset.numVertices.setValue(len(original_points))
if offset_points:
self.offset_coords.point.setValues(0, len(offset_points), offset_points)
self.offset_lineset.numVertices.setValue(len(offset_points))
# Actualizar colores
if hasattr(obj, 'ViewObject') and obj.ViewObject:
self.updateColors(obj.ViewObject)
except Exception as e:
print(f"Error en updateVisual: {e}")
def onChanged(self, vobj, prop):
if prop in ["OriginalColor", "OffsetColor"]:
self.updateColors(vobj)
elif prop == "Visibility" and vobj.Visibility:
# Cuando la visibilidad cambia a True, actualizar visual
self.updateVisual(vobj.Object)
def getDisplayModes(self, obj):
return ["Standard"]
def getDefaultDisplayMode(self):
return "Standard"
def setDisplayMode(self, mode):
return mode
def claimChildren(self):
children = []
if hasattr(self, 'ViewObject') and self.ViewObject and hasattr(self.ViewObject.Object, 'Base'):
children.append(self.ViewObject.Object.Base)
return children
def dumps(self):
return None
def loads(self, state):
return None
''' PV Area: ''' ''' PV Area: '''
def makePVSubplant(): def makePVSubplant():
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PVSubplant") obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PVSubplant")
PVSubplant(obj) PVSubplant(obj)
@@ -556,6 +994,12 @@ class PVSubplant:
''' ''' ''' '''
pass pass
def __getstate__(self):
return None
def __setstate__(self, state):
pass
class ViewProviderPVSubplant: class ViewProviderPVSubplant:
def __init__(self, vobj): def __init__(self, vobj):
@@ -702,8 +1146,8 @@ class CommandFrameArea:
'ToolTip': "Frame Area"} 'ToolTip': "Frame Area"}
def Activated(self): def Activated(self):
sel = FreeCADGui.Selection.getSelection() for base in FreeCADGui.Selection.getSelection():
makeFramedArea(None, sel) makeFramedArea(None, base)
def IsActive(self): def IsActive(self):
if FreeCAD.ActiveDocument: if FreeCAD.ActiveDocument:
@@ -720,8 +1164,8 @@ class CommandProhibitedArea:
'ToolTip': "Prohibited Area"} 'ToolTip': "Prohibited Area"}
def Activated(self): def Activated(self):
sel = FreeCADGui.Selection.getSelection() for base in FreeCADGui.Selection.getSelection():
makeProhibitedArea(sel[0]) makeProhibitedArea(base)
def IsActive(self): def IsActive(self):
if FreeCAD.ActiveDocument: if FreeCAD.ActiveDocument:
@@ -742,12 +1186,11 @@ class CommandPVSubplant:
area = makePVSubplant() area = makePVSubplant()
sel = FreeCADGui.Selection.getSelection() sel = FreeCADGui.Selection.getSelection()
for obj in sel: for obj in sel:
if obj.Name[:7] == "Tracker": if hasattr(obj, 'Proxy') and obj.Proxy.Type == "Tracker":
frame_list = area.Frames frame_list = area.Frames
frame_list.append(obj) frame_list.append(obj)
area.Frames = frame_list area.Frames = frame_list
def IsActive(self): def IsActive(self):
if FreeCAD.ActiveDocument: if FreeCAD.ActiveDocument:
return True return True
@@ -763,11 +1206,8 @@ class CommandOffsetArea:
'ToolTip': "OffsetArea"} 'ToolTip': "OffsetArea"}
def Activated(self): def Activated(self):
sel = FreeCADGui.Selection.getSelection() for base in FreeCADGui.Selection.getSelection():
base = None makeOffsetArea(base)
if sel:
base = sel[0]
obj = makeOffsetArea(base)
def IsActive(self): def IsActive(self):
if FreeCAD.ActiveDocument: if FreeCAD.ActiveDocument:
@@ -776,7 +1216,7 @@ class CommandOffsetArea:
return False return False
if FreeCAD.GuiUp: '''if FreeCAD.GuiUp:
class CommandAreaGroup: class CommandAreaGroup:
def GetCommands(self): def GetCommands(self):
@@ -800,4 +1240,4 @@ if FreeCAD.GuiUp:
FreeCADGui.addCommand('ForbiddenArea', CommandProhibitedArea()) FreeCADGui.addCommand('ForbiddenArea', CommandProhibitedArea())
FreeCADGui.addCommand('PVSubplant', CommandPVSubplant()) FreeCADGui.addCommand('PVSubplant', CommandPVSubplant())
FreeCADGui.addCommand('OffsetArea', CommandOffsetArea()) FreeCADGui.addCommand('OffsetArea', CommandOffsetArea())
FreeCADGui.addCommand('PVPlantAreas', CommandAreaGroup()) FreeCADGui.addCommand('PVPlantAreas', CommandAreaGroup())'''

View File

@@ -128,7 +128,7 @@ def joinAreas(areas):
shape.fuse(shapes) shape.fuse(shapes)
return shape return shape
class CommandSplitArea: '''class CommandSplitArea:
def GetResources(self): def GetResources(self):
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "split_area.svg")), return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "split_area.svg")),
'Accel': "A, S", 'Accel': "A, S",
@@ -162,6 +162,6 @@ class CommandJoinAreas:
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
FreeCADGui.addCommand('SplitArea', CommandSplitArea()) FreeCADGui.addCommand('SplitArea', CommandSplitArea())
FreeCADGui.addCommand('JoinAreas', CommandJoinAreas()) FreeCADGui.addCommand('JoinAreas', CommandJoinAreas())'''

View File

@@ -0,0 +1,92 @@
import FreeCAD
import FreeCADGui
from PySide2 import QtWidgets
import os
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtCore, QtGui, QtWidgets
from PySide.QtCore import QT_TRANSLATE_NOOP
import os
else:
# \cond
def translate(ctxt, txt):
return txt
def QT_TRANSLATE_NOOP(ctxt, txt):
return txt
# \endcond
__title__ = "PVPlant Export to DXF"
__author__ = "Javier Braña"
__url__ = "http://www.sogos-solar.com"
from PVPlantResources import DirIcons as DirIcons
def copy_object_with_reference():
try:
# Verificar selección
selected = FreeCADGui.Selection.getSelection()
if len(selected) != 1:
QtWidgets.QMessageBox.critical(None, "Error", "Selecciona exactamente un objeto")
return
original_doc = FreeCAD.ActiveDocument
original_obj = selected[0]
original_center = original_obj.Shape.BoundBox.Center
# Crear nuevo documento
new_doc = FreeCAD.newDocument(f"{original_doc.Name} - {original_obj.Label}")
# Copiar objeto al nuevo documento
new_obj = new_doc.copyObject(original_obj, True)
new_obj.Label = f"Linked_{original_obj.Label}"
new_obj.Placement.Base = original_obj.Placement.Base - original_center
# Guardar el documenton nuevp
path = os.path.dirname(FreeCAD.ActiveDocument.FileName)
new_doc.saveAs(os.path.join(path, new_doc.Name))
# Mantener posición original en el nuevo documento
# new_obj.Placement = original_obj.Placement
# Crear referencia (App::Link) en el documento original
link = original_doc.addObject("App::Link", f"Link_{new_obj.Label}")
link.LinkedObject = new_obj
# Mantener posición original del objeto
link.Placement = original_obj.Placement
# Actualizar vistas
original_doc.recompute()
new_doc.recompute()
# Regresar al documento original
FreeCAD.setActiveDocument(original_doc.Name)
#QtWidgets.QMessageBox.information(None, "Éxito", "Operación completada correctamente")
except Exception as e:
QtWidgets.QMessageBox.critical(None, "Error", f"Error: {str(e)}")
# Ejecutar la función
class CommandGenerateExternalDocument:
def GetResources(self):
return {'Pixmap': str(os.path.join(DirIcons, "dxf.svg")),
'Accel': "P, E",
'MenuText': "Export to DXF",
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Export choosed layers to dxf")}
def Activated(self):
''' '''
copy_object_with_reference()
def IsActive(self):
if FreeCAD.ActiveDocument:
return True
else:
return False

View File

@@ -138,7 +138,7 @@ class ProjectSetupDialog(QtGui.QWidget):
def closeForm(self): def closeForm(self):
self.close() self.close()
class CommandProjectSetup: '''class CommandProjectSetup:
def GetResources(self): def GetResources(self):
return {'Pixmap': str(os.path.join(DirIcons, "flash.svg")), return {'Pixmap': str(os.path.join(DirIcons, "flash.svg")),
'Accel': "P, S", 'Accel': "P, S",
@@ -159,5 +159,5 @@ class CommandProjectSetup:
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
FreeCADGui.addCommand('ProjectSetup', CommandProjectSetup()) FreeCADGui.addCommand('ProjectSetup', CommandProjectSetup())'''

View File

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

View File

@@ -21,6 +21,10 @@
# *********************************************************************** # ***********************************************************************
import FreeCAD import FreeCAD
import FreeCADGui
from PySide import QtGui, QtCore
import datetime
import getpass
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
import FreeCADGui, os import FreeCADGui, os
@@ -43,51 +47,99 @@ except AttributeError:
import PVPlantResources import PVPlantResources
from PVPlantResources import DirIcons as DirIcons from PVPlantResources import DirIcons as DirIcons
class SafeDict(dict):
"""Diccionario seguro para manejar placeholders no definidos"""
def rename(objects, mask, mode=0): def __missing__(self, key):
''' return f'{{{key}}}'
mode = 0/1/2/3
0: izquierda a derecha - arriba a abajo
1: arriba a abajo - izquierda a derecha
'''
# sort:
tmp = sorted(objects, key=lambda x: (x.Placement.Base.x,
x.Placement.Base.y))
for idx, obj in tmp:
obj.Name = name
class renamerTaskPanel:
def __init__(self, obj=None):
self.obj = obj
# -------------------------------------------------------------------------------------------------------------
# Module widget form
# -------------------------------------------------------------------------------------------------------------
self.formRack = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantFrame.ui")
self.formRack.widgetTracker.setVisible(False)
self.formRack.comboFrameType.currentIndexChanged.connect(self.selectionchange)
self.formPiling = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantRackFixedPiling.ui")
self.formPiling.editBreadthwaysNumOfPost.valueChanged.connect(self.editBreadthwaysNumOfPostChange)
self.formPiling.editAlongNumOfPost.valueChanged.connect(self.editAlongNumOfPostChange)
self.form = [self.formRack, self.formPiling]
def accept(self):
self.closeForm()
return True
def reject(self):
self.closeForm()
return False
def closeForm(self):
FreeCADGui.Control.closeDialog()
class _CommandRenamer: class RenameDialog(QtGui.QDialog):
def __init__(self):
super(RenameDialog, self).__init__()
self.setupUI()
def setupUI(self):
self.setWindowTitle("Renombrar objetos con plantilla")
self.setMinimumWidth(400)
layout = QtGui.QVBoxLayout(self)
# Campo para la plantilla
layout.addWidget(QtGui.QLabel("Plantilla de nombre:"))
self.template_input = QtGui.QLineEdit()
self.template_input.setPlaceholderText("Ej: {label}_mod_{index:03d}_{date:%Y%m%d}")
layout.addWidget(self.template_input)
# Info de placeholders
info = QtGui.QLabel(
"Placeholders disponibles:\n"
"{index} - Número en orden\n"
"{label} - Nombre actual del objeto\n"
"{name} - Nombre interno\n"
"{date} - Fecha actual\n"
"{time} - Hora actual\n"
"{user} - Usuario del sistema\n"
"{datetime} - Fecha y hora completa\n"
"Formatos: {date:%Y/%m/%d}, {index:03d}, etc."
)
layout.addWidget(info)
# Botones
btn_box = QtGui.QDialogButtonBox()
btn_box.addButton(QtGui.QDialogButtonBox.Apply)
btn_box.addButton(QtGui.QDialogButtonBox.Close)
btn_box.clicked.connect(self.on_button_click)
layout.addWidget(btn_box)
def on_button_click(self, button):
if button == btn_box.button(QtGui.QDialogButtonBox.Apply):
self.rename_objects()
else:
self.close()
def rename_objects(self):
template = self.template_input.text()
if not template:
QtGui.QMessageBox.warning(self, "Error", "¡La plantilla no puede estar vacía!")
return
selected_objects = FreeCADGui.Selection.getSelection()
if not selected_objects:
QtGui.QMessageBox.warning(self, "Error", "¡No hay objetos seleccionados!")
return
now = datetime.datetime.now()
user_name = getpass.getuser()
errors = []
for idx, obj in enumerate(selected_objects, 1):
try:
placeholders = SafeDict({
'index': idx,
'label': obj.Label,
'name': obj.Name,
'date': now.date(),
'time': now.time(),
'datetime': now,
'user': user_name
})
new_name = template.format_map(placeholders)
obj.Label = new_name
except Exception as e:
errors.append(f"{obj.Name}: {str(e)}")
FreeCAD.ActiveDocument.recompute()
if errors:
error_msg = "\n".join(errors)
QtGui.QMessageBox.critical(self, "Errores", f"Error(es) encontrado(s):\n{error_msg}")
else:
QtGui.QMessageBox.information(self, "Éxito", "¡Objetos renombrados correctamente!")
class CommandRenamer:
"the Arch Building command definition" "the Arch Building command definition"
def GetResources(self): def GetResources(self):

View File

@@ -1,53 +1,53 @@
# 🚧 FreeCAD Road Workbench # 🚧 FreeCAD PVPlant Workbench
Road is the Transportation and Geomatics Engineering workbench for FreeCAD. PVPlant es el workbench de Ingeniería Fotovoltaica para FreeCAD.
## ✨ Features ## ✨ Características
* Geopoints * Puntos Geográficos
* Terrain * Terreno
* Alignment * Diseño de Estructuras
* Profile * Generación de Perfiles
* Regions * Regiones
* Sections * Secciones
* Volume * Cálculo de Volumen
* GeoLine * Líneas Geográficas
* LandXML * Importación/Exportación LandXML
## 📥 Installation ## 📥 Instalación
### 🔹 Option 1: Install via Addon Manager (Recommended) ### 🔹 Opción 1: Instalar mediante el Administrador de Complementos (Recomendado)
1. Open FreeCAD. 1. Abre FreeCAD.
2. Go to **Tools > Addon Manager**. 2. Ve a **Herramientas > Administrador de Complementos**.
3. In the Addon Manager window, search for **Road**. 3. En la ventana del Administrador de Complementos, busca **PVPlant**.
4. Select the workbench and click the **Install** button. 4. Selecciona el workbench y haz clic en el botón **Instalar**.
5. Restart FreeCAD to complete the installation. 5. Reinicia FreeCAD para completar la instalación.
### 🔹 Option 2: Manual Installation ### 🔹 Opción 2: Instalación Manual
1. Download the latest release the repository. 1. Descarga la última versión del repositorio.
2. Copy the downloaded folder to your FreeCAD Mod directory: 2. Copia la carpeta descargada en el directorio Mod de FreeCAD:
``` ```
Windows: C:\Users\<YourUsername>\AppData\Roaming\FreeCAD\Mod Windows: C:\Users\<TuUsuario>\AppData\Roaming\FreeCAD\Mod
Linux(Flatpak): /home/<YourUsername>/.var/app/org.freecad.FreeCAD/data/FreeCAD/Mod Linux(Flatpak): /home/<TuUsuario>/.var/app/org.freecad.FreeCAD/data/FreeCAD/Mod
MacOS: ~/Library/Preferences/FreeCAD/Mod MacOS: ~/Library/Preferences/FreeCAD/Mod
``` ```
3. Restart FreeCAD to complete the installation. 3. Reinicia FreeCAD para completar la instalación.
## 💬 Feedback and Support ## 💬 Feedback y Soporte
💡 Need help? Join the discussion on the FreeCAD Forum: [FreeCAD Road Workbench](https://forum.freecadweb.org/viewtopic.php?f=8&t=34371). 💡 ¿Necesitas ayuda? Únete a la discusión en el foro de FreeCAD: [FreeCAD PVPlant Workbench](https://forum.freecadweb.org/).
🐞 Found a bug? Report issues on [GitHub](https://github.com/HakanSeven12/Road/issues). 🐞 ¿Encontraste un error? Reporta problemas en [GitHub](https://github.com/HakanSeven12/PVPlant/issues).
## 👨‍💻 Developer ## 👨‍💻 Desarrollador
Developed with passion by Hakan Seven ([@HakanSeven12](https://github.com/HakanSeven12)) with inspiration and contributions from the FreeCAD community. Desarrollado con pasión por Javier Braña con inspiración y contribuciones de la comunidad de FreeCAD.
## 📸 Screenshots ## 📸 Capturas de Pantalla
![GeoPoint](https://github.com/user-attachments/assets/7803dbdc-f646-4d34-80e5-c040a949567b) ![Puntos Geográficos](https://github.com/user-attachments/assets/7803dbdc-f646-4d34-80e5-c040a949567b)
![Terrain](https://github.com/user-attachments/assets/7c6e91fb-4e6a-466c-a59d-e3a20a543dca) ![Terreno](https://github.com/user-attachments/assets/7c6e91fb-4e6a-466c-a59d-e3a20a543dca)
![Alignment](https://github.com/user-attachments/assets/423c2bdb-91f7-4ae9-8187-28902157d0a2) ![Diseño de Estructuras](https://github.com/user-attachments/assets/423c2bdb-91f7-4ae9-8187-28902157d0a2)
![Profile](https://github.com/user-attachments/assets/8516b5bf-33c6-4966-a4a6-29d81c272710) ![Perfiles](https://github.com/user-attachments/assets/8516b5bf-33c6-4966-a4a6-29d81c272710)

BIN
Resources/Icons/drop.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,462 +0,0 @@
/*!
Copyright (c) 2011-2015, Pavel Shramov, Bruno Bergot - MIT licence
*/
L.KML = L.FeatureGroup.extend({
initialize: function (kml) {
this._kml = kml;
this._layers = {};
if (kml) {
this.addKML(kml);
}
},
addKML: function (xml) {
var layers = L.KML.parseKML(xml);
if (!layers || !layers.length) return;
for (var i = 0; i < layers.length; i++) {
this.fire('addlayer', {
layer: layers[i]
});
this.addLayer(layers[i]);
}
this.latLngs = L.KML.getLatLngs(xml);
this.fire('loaded');
},
latLngs: []
});
L.Util.extend(L.KML, {
parseKML: function (xml) {
var style = this.parseStyles(xml);
this.parseStyleMap(xml, style);
var el = xml.getElementsByTagName('Folder');
var layers = [], l;
for (var i = 0; i < el.length; i++) {
if (!this._check_folder(el[i])) { continue; }
l = this.parseFolder(el[i], style);
if (l) { layers.push(l); }
}
el = xml.getElementsByTagName('Placemark');
for (var j = 0; j < el.length; j++) {
if (!this._check_folder(el[j])) { continue; }
l = this.parsePlacemark(el[j], xml, style);
if (l) { layers.push(l); }
}
el = xml.getElementsByTagName('GroundOverlay');
for (var k = 0; k < el.length; k++) {
l = this.parseGroundOverlay(el[k]);
if (l) { layers.push(l); }
}
return layers;
},
// Return false if e's first parent Folder is not [folder]
// - returns true if no parent Folders
_check_folder: function (e, folder) {
e = e.parentNode;
while (e && e.tagName !== 'Folder')
{
e = e.parentNode;
}
return !e || e === folder;
},
parseStyles: function (xml) {
var styles = {};
var sl = xml.getElementsByTagName('Style');
for (var i=0, len=sl.length; i<len; i++) {
var style = this.parseStyle(sl[i]);
if (style) {
var styleName = '#' + style.id;
styles[styleName] = style;
}
}
return styles;
},
parseStyle: function (xml) {
var style = {}, poptions = {}, ioptions = {}, el, id;
var attributes = {color: true, width: true, Icon: true, href: true, hotSpot: true};
function _parse (xml) {
var options = {};
for (var i = 0; i < xml.childNodes.length; i++) {
var e = xml.childNodes[i];
var key = e.tagName;
if (!attributes[key]) { continue; }
if (key === 'hotSpot')
{
for (var j = 0; j < e.attributes.length; j++) {
options[e.attributes[j].name] = e.attributes[j].nodeValue;
}
} else {
var value = e.childNodes[0].nodeValue;
if (key === 'color') {
options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
options.color = '#' + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
} else if (key === 'width') {
options.weight = parseInt(value);
} else if (key === 'Icon') {
ioptions = _parse(e);
if (ioptions.href) { options.href = ioptions.href; }
} else if (key === 'href') {
options.href = value;
}
}
}
return options;
}
el = xml.getElementsByTagName('LineStyle');
if (el && el[0]) { style = _parse(el[0]); }
el = xml.getElementsByTagName('PolyStyle');
if (el && el[0]) { poptions = _parse(el[0]); }
if (poptions.color) { style.fillColor = poptions.color; }
if (poptions.opacity) { style.fillOpacity = poptions.opacity; }
el = xml.getElementsByTagName('IconStyle');
if (el && el[0]) { ioptions = _parse(el[0]); }
if (ioptions.href) {
style.icon = new L.KMLIcon({
iconUrl: ioptions.href,
shadowUrl: null,
anchorRef: {x: ioptions.x, y: ioptions.y},
anchorType: {x: ioptions.xunits, y: ioptions.yunits}
});
}
id = xml.getAttribute('id');
if (id && style) {
style.id = id;
}
return style;
},
parseStyleMap: function (xml, existingStyles) {
var sl = xml.getElementsByTagName('StyleMap');
for (var i = 0; i < sl.length; i++) {
var e = sl[i], el;
var smKey, smStyleUrl;
el = e.getElementsByTagName('key');
if (el && el[0]) { smKey = el[0].textContent; }
el = e.getElementsByTagName('styleUrl');
if (el && el[0]) { smStyleUrl = el[0].textContent; }
if (smKey === 'normal')
{
existingStyles['#' + e.getAttribute('id')] = existingStyles[smStyleUrl];
}
}
return;
},
parseFolder: function (xml, style) {
var el, layers = [], l;
el = xml.getElementsByTagName('Folder');
for (var i = 0; i < el.length; i++) {
if (!this._check_folder(el[i], xml)) { continue; }
l = this.parseFolder(el[i], style);
if (l) { layers.push(l); }
}
el = xml.getElementsByTagName('Placemark');
for (var j = 0; j < el.length; j++) {
if (!this._check_folder(el[j], xml)) { continue; }
l = this.parsePlacemark(el[j], xml, style);
if (l) { layers.push(l); }
}
el = xml.getElementsByTagName('GroundOverlay');
for (var k = 0; k < el.length; k++) {
if (!this._check_folder(el[k], xml)) { continue; }
l = this.parseGroundOverlay(el[k]);
if (l) { layers.push(l); }
}
if (!layers.length) { return; }
if (layers.length === 1) { return layers[0]; }
return new L.FeatureGroup(layers);
},
parsePlacemark: function (place, xml, style, options) {
var h, i, j, k, el, il, opts = options || {};
el = place.getElementsByTagName('styleUrl');
for (i = 0; i < el.length; i++) {
var url = el[i].childNodes[0].nodeValue;
for (var a in style[url]) {
opts[a] = style[url][a];
}
}
il = place.getElementsByTagName('Style')[0];
if (il) {
var inlineStyle = this.parseStyle(place);
if (inlineStyle) {
for (k in inlineStyle) {
opts[k] = inlineStyle[k];
}
}
}
var multi = ['MultiGeometry', 'MultiTrack', 'gx:MultiTrack'];
for (h in multi) {
el = place.getElementsByTagName(multi[h]);
for (i = 0; i < el.length; i++) {
var layer = this.parsePlacemark(el[i], xml, style, opts);
this.addPlacePopup(place, layer);
return layer;
}
}
var layers = [];
var parse = ['LineString', 'Polygon', 'Point', 'Track', 'gx:Track'];
for (j in parse) {
var tag = parse[j];
el = place.getElementsByTagName(tag);
for (i = 0; i < el.length; i++) {
var l = this['parse' + tag.replace(/gx:/, '')](el[i], xml, opts);
if (l) { layers.push(l); }
}
}
if (!layers.length) {
return;
}
var layer = layers[0];
if (layers.length > 1) {
layer = new L.FeatureGroup(layers);
}
this.addPlacePopup(place, layer);
return layer;
},
addPlacePopup: function(place, layer) {
var i, j, name, descr = '';
el = place.getElementsByTagName('name');
if (el.length && el[0].childNodes.length) {
name = el[0].childNodes[0].nodeValue;
}
el = place.getElementsByTagName('description');
for (i = 0; i < el.length; i++) {
for (j = 0; j < el[i].childNodes.length; j++) {
descr = descr + el[i].childNodes[j].nodeValue;
}
}
if (name) {
layer.bindPopup('<h2>' + name + '</h2>' + descr, { className: 'kml-popup'});
}
},
parseCoords: function (xml) {
var el = xml.getElementsByTagName('coordinates');
return this._read_coords(el[0]);
},
parseLineString: function (line, xml, options) {
var coords = this.parseCoords(line);
if (!coords.length) { return; }
return new L.Polyline(coords, options);
},
parseTrack: function (line, xml, options) {
var el = xml.getElementsByTagName('gx:coord');
if (el.length === 0) { el = xml.getElementsByTagName('coord'); }
var coords = [];
for (var j = 0; j < el.length; j++) {
coords = coords.concat(this._read_gxcoords(el[j]));
}
if (!coords.length) { return; }
return new L.Polyline(coords, options);
},
parsePoint: function (line, xml, options) {
var el = line.getElementsByTagName('coordinates');
if (!el.length) {
return;
}
var ll = el[0].childNodes[0].nodeValue.split(',');
return new L.KMLMarker(new L.LatLng(ll[1], ll[0]), options);
},
parsePolygon: function (line, xml, options) {
var el, polys = [], inner = [], i, coords;
el = line.getElementsByTagName('outerBoundaryIs');
for (i = 0; i < el.length; i++) {
coords = this.parseCoords(el[i]);
if (coords) {
polys.push(coords);
}
}
el = line.getElementsByTagName('innerBoundaryIs');
for (i = 0; i < el.length; i++) {
coords = this.parseCoords(el[i]);
if (coords) {
inner.push(coords);
}
}
if (!polys.length) {
return;
}
if (options.fillColor) {
options.fill = true;
}
if (polys.length === 1) {
return new L.Polygon(polys.concat(inner), options);
}
return new L.MultiPolygon(polys, options);
},
getLatLngs: function (xml) {
var el = xml.getElementsByTagName('coordinates');
var coords = [];
for (var j = 0; j < el.length; j++) {
// text might span many childNodes
coords = coords.concat(this._read_coords(el[j]));
}
return coords;
},
_read_coords: function (el) {
var text = '', coords = [], i;
for (i = 0; i < el.childNodes.length; i++) {
text = text + el.childNodes[i].nodeValue;
}
text = text.split(/[\s\n]+/);
for (i = 0; i < text.length; i++) {
var ll = text[i].split(',');
if (ll.length < 2) {
continue;
}
coords.push(new L.LatLng(ll[1], ll[0]));
}
return coords;
},
_read_gxcoords: function (el) {
var text = '', coords = [];
text = el.firstChild.nodeValue.split(' ');
coords.push(new L.LatLng(text[1], text[0]));
return coords;
},
parseGroundOverlay: function (xml) {
var latlonbox = xml.getElementsByTagName('LatLonBox')[0];
var bounds = new L.LatLngBounds(
[
latlonbox.getElementsByTagName('south')[0].childNodes[0].nodeValue,
latlonbox.getElementsByTagName('west')[0].childNodes[0].nodeValue
],
[
latlonbox.getElementsByTagName('north')[0].childNodes[0].nodeValue,
latlonbox.getElementsByTagName('east')[0].childNodes[0].nodeValue
]
);
var attributes = {Icon: true, href: true, color: true};
function _parse (xml) {
var options = {}, ioptions = {};
for (var i = 0; i < xml.childNodes.length; i++) {
var e = xml.childNodes[i];
var key = e.tagName;
if (!attributes[key]) { continue; }
var value = e.childNodes[0].nodeValue;
if (key === 'Icon') {
ioptions = _parse(e);
if (ioptions.href) { options.href = ioptions.href; }
} else if (key === 'href') {
options.href = value;
} else if (key === 'color') {
options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
options.color = '#' + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
}
}
return options;
}
var options = {};
options = _parse(xml);
if (latlonbox.getElementsByTagName('rotation')[0] !== undefined) {
var rotation = latlonbox.getElementsByTagName('rotation')[0].childNodes[0].nodeValue;
options.rotation = parseFloat(rotation);
}
return new L.RotatedImageOverlay(options.href, bounds, {opacity: options.opacity, angle: options.rotation});
}
});
L.KMLIcon = L.Icon.extend({
options: {
iconSize: [32, 32],
iconAnchor: [16, 16],
},
_setIconStyles: function (img, name) {
L.Icon.prototype._setIconStyles.apply(this, [img, name]);
if( img.complete ) {
this.applyCustomStyles( img )
} else {
img.onload = this.applyCustomStyles.bind(this,img)
}
},
applyCustomStyles: function(img) {
var options = this.options;
var width = options.iconSize[0];
var height = options.iconSize[1];
this.options.popupAnchor = [0,(-0.83*height)];
if (options.anchorType.x === 'fraction')
img.style.marginLeft = (-options.anchorRef.x * width) + 'px';
if (options.anchorType.y === 'fraction')
img.style.marginTop = ((-(1 - options.anchorRef.y) * height) + 1) + 'px';
if (options.anchorType.x === 'pixels')
img.style.marginLeft = (-options.anchorRef.x) + 'px';
if (options.anchorType.y === 'pixels')
img.style.marginTop = (options.anchorRef.y - height + 1) + 'px';
}
});
L.KMLMarker = L.Marker.extend({
options: {
icon: new L.KMLIcon.Default()
}
});
// Inspired by https://github.com/bbecquet/Leaflet.PolylineDecorator/tree/master/src
L.RotatedImageOverlay = L.ImageOverlay.extend({
options: {
angle: 0
},
_reset: function () {
L.ImageOverlay.prototype._reset.call(this);
this._rotate();
},
_animateZoom: function (e) {
L.ImageOverlay.prototype._animateZoom.call(this, e);
this._rotate();
},
_rotate: function () {
if (L.DomUtil.TRANSFORM) {
// use the CSS transform rule if available
this._image.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)';
} else if (L.Browser.ie) {
// fallback for IE6, IE7, IE8
var rad = this.options.angle * (Math.PI / 180),
costheta = Math.cos(rad),
sintheta = Math.sin(rad);
this._image.style.filter += ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' +
costheta + ', M12=' + (-sintheta) + ', M21=' + sintheta + ', M22=' + costheta + ')';
}
},
getBounds: function () {
return this._bounds;
}
});

View File

@@ -1,58 +0,0 @@
# Leaflet KML layer plugin
![Example](assets/screenshot.jpg)
Demo: https://www.windy.com/uploader
This plugin was extracted from Pavel Shramov's Leaflet Plugins [repository](https://github.com/shramov/leaflet-plugins) in order to maintain this code more frequently and separate KML layer from other plugins.
So far we have fixed few issues.
Probably will work on Leaflet 1+, tested on Leaflet 1.4.
## How to use
```html
<html>
<head>
<link rel="stylesheet" href="http://unpkg.com/leaflet@1.4.0/dist/leaflet.css" />
<script src="http://unpkg.com/leaflet@1.4.0/dist/leaflet.js"></script>
<script src="./L.KML.js"></script>
</head>
<body>
<div style="width: 100vw; height: 100vh" id="map"></div>
<script type="text/javascript">
// Make basemap
const map = new L.Map('map', { center: new L.LatLng(58.4, 43.0), zoom: 11 });
const osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
map.addLayer(osm);
// Load kml file
fetch('assets/example1.kml')
.then(res => res.text())
.then(kmltext => {
// Create new kml overlay
const parser = new DOMParser();
const kml = parser.parseFromString(kmltext, 'text/xml');
const track = new L.KML(kml);
map.addLayer(track);
// Adjust map to show the kml
const bounds = track.getBounds();
map.fitBounds(bounds);
});
</script>
</body>
</html>
```
## Changelog
- 1.0.1 - Updated README
- 1.0.0 - Initial commit, original version with few fixes
## Licence
MIT

View File

@@ -1,915 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>KML Samples</name>
<open>1</open>
<description>Unleash your creativity with the help of these examples!</description>
<Style id="downArrowIcon">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pal4/icon28.png</href>
</Icon>
</IconStyle>
</Style>
<Style id="globeIcon">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pal3/icon19.png</href>
</Icon>
</IconStyle>
<LineStyle>
<width>2</width>
</LineStyle>
</Style>
<Style id="transPurpleLineGreenPoly">
<LineStyle>
<color>7fff00ff</color>
<width>4</width>
</LineStyle>
<PolyStyle>
<color>7f00ff00</color>
</PolyStyle>
</Style>
<Style id="yellowLineGreenPoly">
<LineStyle>
<color>7f00ffff</color>
<width>4</width>
</LineStyle>
<PolyStyle>
<color>7f00ff00</color>
</PolyStyle>
</Style>
<Style id="thickBlackLine">
<LineStyle>
<color>87000000</color>
<width>10</width>
</LineStyle>
</Style>
<Style id="redLineBluePoly">
<LineStyle>
<color>ff0000ff</color>
</LineStyle>
<PolyStyle>
<color>ffff0000</color>
</PolyStyle>
</Style>
<Style id="blueLineRedPoly">
<LineStyle>
<color>ffff0000</color>
</LineStyle>
<PolyStyle>
<color>ff0000ff</color>
</PolyStyle>
</Style>
<Style id="transRedPoly">
<LineStyle>
<width>1.5</width>
</LineStyle>
<PolyStyle>
<color>7d0000ff</color>
</PolyStyle>
</Style>
<Style id="transBluePoly">
<LineStyle>
<width>1.5</width>
</LineStyle>
<PolyStyle>
<color>7dff0000</color>
</PolyStyle>
</Style>
<Style id="transGreenPoly">
<LineStyle>
<width>1.5</width>
</LineStyle>
<PolyStyle>
<color>7d00ff00</color>
</PolyStyle>
</Style>
<Style id="transYellowPoly">
<LineStyle>
<width>1.5</width>
</LineStyle>
<PolyStyle>
<color>7d00ffff</color>
</PolyStyle>
</Style>
<Style id="noDrivingDirections">
<BalloonStyle>
<text><![CDATA[
<b>$[name]</b>
<br /><br />
$[description]
]]></text>
</BalloonStyle>
</Style>
<Folder>
<name>Placemarks</name>
<description>These are just some of the different kinds of placemarks with
which you can mark your favorite places</description>
<LookAt>
<longitude>-122.0839597145766</longitude>
<latitude>37.42222904525232</latitude>
<altitude>0</altitude>
<heading>-148.4122922628044</heading>
<tilt>40.5575073395506</tilt>
<range>500.6566641072245</range>
</LookAt>
<Placemark>
<name>Simple placemark</name>
<description>Attached to the ground. Intelligently places itself at the
height of the underlying terrain.</description>
<Point>
<coordinates>-122.0822035425683,37.42228990140251,0</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Floating placemark</name>
<visibility>0</visibility>
<description>Floats a defined distance above the ground.</description>
<LookAt>
<longitude>-122.0839597145766</longitude>
<latitude>37.42222904525232</latitude>
<altitude>0</altitude>
<heading>-148.4122922628044</heading>
<tilt>40.5575073395506</tilt>
<range>500.6566641072245</range>
</LookAt>
<styleUrl>#downArrowIcon</styleUrl>
<Point>
<altitudeMode>relativeToGround</altitudeMode>
<coordinates>-122.084075,37.4220033612141,50</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Extruded placemark</name>
<visibility>0</visibility>
<description>Tethered to the ground by a customizable
&quot;tail&quot;</description>
<LookAt>
<longitude>-122.0845787421525</longitude>
<latitude>37.42215078737763</latitude>
<altitude>0</altitude>
<heading>-148.4126684946234</heading>
<tilt>40.55750733918048</tilt>
<range>365.2646606980322</range>
</LookAt>
<styleUrl>#globeIcon</styleUrl>
<Point>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<coordinates>-122.0857667006183,37.42156927867553,50</coordinates>
</Point>
</Placemark>
</Folder>
<Folder>
<name>Styles and Markup</name>
<visibility>0</visibility>
<description>With KML it is easy to create rich, descriptive markup to
annotate and enrich your placemarks</description>
<LookAt>
<longitude>-122.0845787422371</longitude>
<latitude>37.42215078726837</latitude>
<altitude>0</altitude>
<heading>-148.4126777488172</heading>
<tilt>40.55750733930874</tilt>
<range>365.2646826292919</range>
</LookAt>
<styleUrl>#noDrivingDirections</styleUrl>
<Document>
<name>Highlighted Icon</name>
<visibility>0</visibility>
<description>Place your mouse over the icon to see it display the new
icon</description>
<LookAt>
<longitude>-122.0856552124024</longitude>
<latitude>37.4224281311035</latitude>
<altitude>0</altitude>
<heading>0</heading>
<tilt>0</tilt>
<range>265.8520424250024</range>
</LookAt>
<Style id="highlightPlacemark">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/paddle/red-stars.png</href>
</Icon>
</IconStyle>
</Style>
<Style id="normalPlacemark">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/paddle/wht-blank.png</href>
</Icon>
</IconStyle>
</Style>
<StyleMap id="exampleStyleMap">
<Pair>
<key>normal</key>
<styleUrl>#normalPlacemark</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#highlightPlacemark</styleUrl>
</Pair>
</StyleMap>
<Placemark>
<name>Roll over this icon</name>
<visibility>0</visibility>
<styleUrl>#exampleStyleMap</styleUrl>
<Point>
<coordinates>-122.0856545755255,37.42243077405461,0</coordinates>
</Point>
</Placemark>
</Document>
<Placemark>
<name>Descriptive HTML</name>
<visibility>0</visibility>
<description><![CDATA[Click on the blue link!<br><br>
Placemark descriptions can be enriched by using many standard HTML tags.<br>
For example:
<hr>
Styles:<br>
<i>Italics</i>,
<b>Bold</b>,
<u>Underlined</u>,
<s>Strike Out</s>,
subscript<sub>subscript</sub>,
superscript<sup>superscript</sup>,
<big>Big</big>,
<small>Small</small>,
<tt>Typewriter</tt>,
<em>Emphasized</em>,
<strong>Strong</strong>,
<code>Code</code>
<hr>
Fonts:<br>
<font color="red">red by name</font>,
<font color="#408010">leaf green by hexadecimal RGB</font>
<br>
<font size=1>size 1</font>,
<font size=2>size 2</font>,
<font size=3>size 3</font>,
<font size=4>size 4</font>,
<font size=5>size 5</font>,
<font size=6>size 6</font>,
<font size=7>size 7</font>
<br>
<font face=times>Times</font>,
<font face=verdana>Verdana</font>,
<font face=arial>Arial</font><br>
<hr>
Links:
<br>
<a href="http://earth.google.com/">Google Earth!</a>
<br>
or: Check out our website at www.google.com
<hr>
Alignment:<br>
<p align=left>left</p>
<p align=center>center</p>
<p align=right>right</p>
<hr>
Ordered Lists:<br>
<ol><li>First</li><li>Second</li><li>Third</li></ol>
<ol type="a"><li>First</li><li>Second</li><li>Third</li></ol>
<ol type="A"><li>First</li><li>Second</li><li>Third</li></ol>
<hr>
Unordered Lists:<br>
<ul><li>A</li><li>B</li><li>C</li></ul>
<ul type="circle"><li>A</li><li>B</li><li>C</li></ul>
<ul type="square"><li>A</li><li>B</li><li>C</li></ul>
<hr>
Definitions:<br>
<dl>
<dt>Google:</dt><dd>The best thing since sliced bread</dd>
</dl>
<hr>
Centered:<br><center>
Time present and time past<br>
Are both perhaps present in time future,<br>
And time future contained in time past.<br>
If all time is eternally present<br>
All time is unredeemable.<br>
</center>
<hr>
Block Quote:
<br>
<blockquote>
We shall not cease from exploration<br>
And the end of all our exploring<br>
Will be to arrive where we started<br>
And know the place for the first time.<br>
<i>-- T.S. Eliot</i>
</blockquote>
<br>
<hr>
Headings:<br>
<h1>Header 1</h1>
<h2>Header 2</h2>
<h3>Header 3</h3>
<h3>Header 4</h4>
<h3>Header 5</h5>
<hr>
Images:<br>
<i>Remote image</i><br>
<img src="//developers.google.com/kml/documentation/images/googleSample.png"><br>
<i>Scaled image</i><br>
<img src="//developers.google.com/kml/documentation/images/googleSample.png" width=100><br>
<hr>
Simple Tables:<br>
<table border="1" padding="1">
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td></tr>
<tr><td>a</td><td>b</td><td>c</td><td>d</td><td>e</td></tr>
</table>
<br>
[Did you notice that double-clicking on the placemark doesn't cause the viewer to take you anywhere? This is because it is possible to directly author a "placeless placemark". If you look at the code for this example, you will see that it has neither a point coordinate nor a LookAt element.]]]></description>
</Placemark>
</Folder>
<Folder>
<name>Ground Overlays</name>
<visibility>0</visibility>
<description>Examples of ground overlays</description>
<GroundOverlay>
<name>Large-scale overlay on terrain</name>
<visibility>0</visibility>
<description>Overlay shows Mount Etna erupting on July 13th, 2001.</description>
<LookAt>
<longitude>15.02468937557116</longitude>
<latitude>37.67395167941667</latitude>
<altitude>0</altitude>
<heading>-16.5581842842829</heading>
<tilt>58.31228652890705</tilt>
<range>30350.36838438907</range>
</LookAt>
<Icon>
<href>http://developers.google.com/kml/documentation/images/etna.jpg</href>
</Icon>
<LatLonBox>
<north>37.91904192681665</north>
<south>37.46543388598137</south>
<east>15.35832653742206</east>
<west>14.60128369746704</west>
<rotation>-0.1556640799496235</rotation>
</LatLonBox>
</GroundOverlay>
</Folder>
<Folder>
<name>Screen Overlays</name>
<visibility>0</visibility>
<description>Screen overlays have to be authored directly in KML. These
examples illustrate absolute and dynamic positioning in screen space.</description>
<ScreenOverlay>
<name>Simple crosshairs</name>
<visibility>0</visibility>
<description>This screen overlay uses fractional positioning to put the
image in the exact center of the screen</description>
<Icon>
<href>http://developers.google.com/kml/documentation/images/crosshairs.png</href>
</Icon>
<overlayXY x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
<screenXY x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
<rotationXY x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
<size x="0" y="0" xunits="pixels" yunits="pixels"/>
</ScreenOverlay>
<ScreenOverlay>
<name>Absolute Positioning: Top left</name>
<visibility>0</visibility>
<Icon>
<href>http://developers.google.com/kml/documentation/images/top_left.jpg</href>
</Icon>
<overlayXY x="0" y="1" xunits="fraction" yunits="fraction"/>
<screenXY x="0" y="1" xunits="fraction" yunits="fraction"/>
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<size x="0" y="0" xunits="fraction" yunits="fraction"/>
</ScreenOverlay>
<ScreenOverlay>
<name>Absolute Positioning: Top right</name>
<visibility>0</visibility>
<Icon>
<href>http://developers.google.com/kml/documentation/images/top_right.jpg</href>
</Icon>
<overlayXY x="1" y="1" xunits="fraction" yunits="fraction"/>
<screenXY x="1" y="1" xunits="fraction" yunits="fraction"/>
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<size x="0" y="0" xunits="fraction" yunits="fraction"/>
</ScreenOverlay>
<ScreenOverlay>
<name>Absolute Positioning: Bottom left</name>
<visibility>0</visibility>
<Icon>
<href>http://developers.google.com/kml/documentation/images/bottom_left.jpg</href>
</Icon>
<overlayXY x="0" y="-1" xunits="fraction" yunits="fraction"/>
<screenXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<size x="0" y="0" xunits="fraction" yunits="fraction"/>
</ScreenOverlay>
<ScreenOverlay>
<name>Absolute Positioning: Bottom right</name>
<visibility>0</visibility>
<Icon>
<href>http://developers.google.com/kml/documentation/images/bottom_right.jpg</href>
</Icon>
<overlayXY x="1" y="-1" xunits="fraction" yunits="fraction"/>
<screenXY x="1" y="0" xunits="fraction" yunits="fraction"/>
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<size x="0" y="0" xunits="fraction" yunits="fraction"/>
</ScreenOverlay>
<ScreenOverlay>
<name>Dynamic Positioning: Top of screen</name>
<visibility>0</visibility>
<Icon>
<href>http://developers.google.com/kml/documentation/images/dynamic_screenoverlay.jpg</href>
</Icon>
<overlayXY x="0" y="1" xunits="fraction" yunits="fraction"/>
<screenXY x="0" y="1" xunits="fraction" yunits="fraction"/>
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<size x="1" y="0.2" xunits="fraction" yunits="fraction"/>
</ScreenOverlay>
<ScreenOverlay>
<name>Dynamic Positioning: Right of screen</name>
<visibility>0</visibility>
<Icon>
<href>http://developers.google.com/kml/documentation/images/dynamic_right.jpg</href>
</Icon>
<overlayXY x="1" y="1" xunits="fraction" yunits="fraction"/>
<screenXY x="1" y="1" xunits="fraction" yunits="fraction"/>
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
<size x="0" y="1" xunits="fraction" yunits="fraction"/>
</ScreenOverlay>
</Folder>
<Folder>
<name>Paths</name>
<visibility>0</visibility>
<description>Examples of paths. Note that the tessellate tag is by default
set to 0. If you want to create tessellated lines, they must be authored
(or edited) directly in KML.</description>
<Placemark>
<name>Tessellated</name>
<visibility>0</visibility>
<description><![CDATA[If the <tessellate> tag has a value of 1, the line will contour to the underlying terrain]]></description>
<LookAt>
<longitude>-112.0822680013139</longitude>
<latitude>36.09825589333556</latitude>
<altitude>0</altitude>
<heading>103.8120432044965</heading>
<tilt>62.04855796276328</tilt>
<range>2889.145007690472</range>
</LookAt>
<LineString>
<tessellate>1</tessellate>
<coordinates> -112.0814237830345,36.10677870477137,0
-112.0870267752693,36.0905099328766,0 </coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Untessellated</name>
<visibility>0</visibility>
<description><![CDATA[If the <tessellate> tag has a value of 0, the line follow a simple straight-line path from point to point]]></description>
<LookAt>
<longitude>-112.0822680013139</longitude>
<latitude>36.09825589333556</latitude>
<altitude>0</altitude>
<heading>103.8120432044965</heading>
<tilt>62.04855796276328</tilt>
<range>2889.145007690472</range>
</LookAt>
<LineString>
<tessellate>0</tessellate>
<coordinates> -112.080622229595,36.10673460007995,0
-112.085242575315,36.09049598612422,0 </coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Absolute</name>
<visibility>0</visibility>
<description>Transparent purple line</description>
<LookAt>
<longitude>-112.2719329043177</longitude>
<latitude>36.08890633450894</latitude>
<altitude>0</altitude>
<heading>-106.8161545998597</heading>
<tilt>44.60763714063257</tilt>
<range>2569.386744398339</range>
</LookAt>
<styleUrl>#transPurpleLineGreenPoly</styleUrl>
<LineString>
<tessellate>1</tessellate>
<altitudeMode>absolute</altitudeMode>
<coordinates> -112.265654928602,36.09447672602546,2357
-112.2660384528238,36.09342608838671,2357
-112.2668139013453,36.09251058776881,2357
-112.2677826834445,36.09189827357996,2357
-112.2688557510952,36.0913137941187,2357
-112.2694810717219,36.0903677207521,2357
-112.2695268555611,36.08932171487285,2357
-112.2690144567276,36.08850916060472,2357
-112.2681528815339,36.08753813597956,2357
-112.2670588176031,36.08682685262568,2357
-112.2657374587321,36.08646312301303,2357 </coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Absolute Extruded</name>
<visibility>0</visibility>
<description>Transparent green wall with yellow outlines</description>
<LookAt>
<longitude>-112.2643334742529</longitude>
<latitude>36.08563154742419</latitude>
<altitude>0</altitude>
<heading>-125.7518698668815</heading>
<tilt>44.61038665812578</tilt>
<range>4451.842204068102</range>
</LookAt>
<styleUrl>#yellowLineGreenPoly</styleUrl>
<LineString>
<extrude>1</extrude>
<tessellate>1</tessellate>
<altitudeMode>absolute</altitudeMode>
<coordinates> -112.2550785337791,36.07954952145647,2357
-112.2549277039738,36.08117083492122,2357
-112.2552505069063,36.08260761307279,2357
-112.2564540158376,36.08395660588506,2357
-112.2580238976449,36.08511401044813,2357
-112.2595218489022,36.08584355239394,2357
-112.2608216347552,36.08612634548589,2357
-112.262073428656,36.08626019085147,2357
-112.2633204928495,36.08621519860091,2357
-112.2644963846444,36.08627897945274,2357
-112.2656969554589,36.08649599090644,2357 </coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Relative</name>
<visibility>0</visibility>
<description>Black line (10 pixels wide), height tracks terrain</description>
<LookAt>
<longitude>-112.2580438551384</longitude>
<latitude>36.1072674824385</latitude>
<altitude>0</altitude>
<heading>4.947421249553717</heading>
<tilt>44.61324882043339</tilt>
<range>2927.61105910266</range>
</LookAt>
<styleUrl>#thickBlackLine</styleUrl>
<LineString>
<tessellate>1</tessellate>
<altitudeMode>relativeToGround</altitudeMode>
<coordinates> -112.2532845153347,36.09886943729116,645
-112.2540466121145,36.09919570465255,645
-112.254734666947,36.09984998366178,645
-112.255493345654,36.10051310621746,645
-112.2563157098468,36.10108441943419,645
-112.2568033076439,36.10159722088088,645
-112.257494011321,36.10204323542867,645
-112.2584106072308,36.10229131995655,645
-112.2596588987972,36.10240001286358,645
-112.2610581199487,36.10213176873407,645
-112.2626285262793,36.10157011437219,645 </coordinates>
</LineString>
</Placemark>
<Placemark>
<name>Relative Extruded</name>
<visibility>0</visibility>
<description>Opaque blue walls with red outline, height tracks terrain</description>
<LookAt>
<longitude>-112.2683594333433</longitude>
<latitude>36.09884362144909</latitude>
<altitude>0</altitude>
<heading>-72.24271551768405</heading>
<tilt>44.60855445139561</tilt>
<range>2184.193522571467</range>
</LookAt>
<styleUrl>#redLineBluePoly</styleUrl>
<LineString>
<extrude>1</extrude>
<tessellate>1</tessellate>
<altitudeMode>relativeToGround</altitudeMode>
<coordinates> -112.2656634181359,36.09445214722695,630
-112.2652238941097,36.09520916122063,630
-112.2645079986395,36.09580763864907,630
-112.2638827428817,36.09628572284063,630
-112.2635746835406,36.09679275951239,630
-112.2635711822407,36.09740038871899,630
-112.2640296531825,36.09804913435539,630
-112.264327720538,36.09880337400301,630
-112.2642436562271,36.09963644790288,630
-112.2639148687042,36.10055381117246,630
-112.2626894973474,36.10149062823369,630 </coordinates>
</LineString>
</Placemark>
</Folder>
<Folder>
<name>Polygons</name>
<visibility>0</visibility>
<description>Examples of polygon shapes</description>
<Folder>
<name>Google Campus</name>
<visibility>0</visibility>
<description>A collection showing how easy it is to create 3-dimensional
buildings</description>
<LookAt>
<longitude>-122.084120030116</longitude>
<latitude>37.42174011925477</latitude>
<altitude>0</altitude>
<heading>-34.82469740081282</heading>
<tilt>53.454348562403</tilt>
<range>276.7870053764046</range>
</LookAt>
<Placemark>
<name>Building 40</name>
<visibility>0</visibility>
<styleUrl>#transRedPoly</styleUrl>
<Polygon>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -122.0848938459612,37.42257124044786,17
-122.0849580979198,37.42211922626856,17
-122.0847469573047,37.42207183952619,17
-122.0845725380962,37.42209006729676,17
-122.0845954886723,37.42215932700895,17
-122.0838521118269,37.42227278564371,17
-122.083792243335,37.42203539112084,17
-122.0835076656616,37.42209006957106,17
-122.0834709464152,37.42200987395161,17
-122.0831221085748,37.4221046494946,17
-122.0829247374572,37.42226503990386,17
-122.0829339169385,37.42231242843094,17
-122.0833837359737,37.42225046087618,17
-122.0833607854248,37.42234159228745,17
-122.0834204551642,37.42237075460644,17
-122.083659133885,37.42251292011001,17
-122.0839758438952,37.42265873093781,17
-122.0842374743331,37.42265143972521,17
-122.0845036949503,37.4226514386435,17
-122.0848020460801,37.42261133916315,17
-122.0847882750515,37.42256395055121,17
-122.0848938459612,37.42257124044786,17 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
<Placemark>
<name>Building 41</name>
<visibility>0</visibility>
<styleUrl>#transBluePoly</styleUrl>
<Polygon>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -122.0857412771483,37.42227033155257,17
-122.0858169768481,37.42231408832346,17
-122.085852582875,37.42230337469744,17
-122.0858799945639,37.42225686138789,17
-122.0858860101409,37.4222311076138,17
-122.0858069157288,37.42220250173855,17
-122.0858379542653,37.42214027058678,17
-122.0856732640519,37.42208690214408,17
-122.0856022926407,37.42214885429042,17
-122.0855902778436,37.422128290487,17
-122.0855841672237,37.42208171967246,17
-122.0854852065741,37.42210455874995,17
-122.0855067264352,37.42214267949824,17
-122.0854430712915,37.42212783846172,17
-122.0850990714904,37.42251282407603,17
-122.0856769818632,37.42281815323651,17
-122.0860162273783,37.42244918858722,17
-122.0857260327004,37.42229239604253,17
-122.0857412771483,37.42227033155257,17 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
<Placemark>
<name>Building 42</name>
<visibility>0</visibility>
<styleUrl>#transGreenPoly</styleUrl>
<Polygon>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -122.0857862287242,37.42136208886969,25
-122.0857312990603,37.42136935989481,25
-122.0857312992918,37.42140934910903,25
-122.0856077073679,37.42138390166565,25
-122.0855802426516,37.42137299550869,25
-122.0852186221971,37.42137299504316,25
-122.0852277765639,37.42161656508265,25
-122.0852598189347,37.42160565894403,25
-122.0852598185499,37.42168200156,25
-122.0852369311478,37.42170017860346,25
-122.0852643957828,37.42176197982575,25
-122.0853239032746,37.42176198013907,25
-122.0853559454324,37.421852864452,25
-122.0854108752463,37.42188921823734,25
-122.0854795379357,37.42189285337048,25
-122.0855436229819,37.42188921797546,25
-122.0856260178042,37.42186013499926,25
-122.085937287963,37.42186013453605,25
-122.0859428718666,37.42160898590042,25
-122.0859655469861,37.42157992759144,25
-122.0858640462341,37.42147115002957,25
-122.0858548911215,37.42140571326184,25
-122.0858091162768,37.4214057134039,25
-122.0857862287242,37.42136208886969,25 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
<Placemark>
<name>Building 43</name>
<visibility>0</visibility>
<styleUrl>#transYellowPoly</styleUrl>
<Polygon>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -122.0844371128284,37.42177253003091,19
-122.0845118855746,37.42191111542896,19
-122.0850470999805,37.42178755121535,19
-122.0850719913391,37.42143663023161,19
-122.084916406232,37.42137237822116,19
-122.0842193868167,37.42137237801626,19
-122.08421938659,37.42147617161496,19
-122.0838086419991,37.4214613409357,19
-122.0837899728564,37.42131306410796,19
-122.0832796534698,37.42129328840593,19
-122.0832609819207,37.42139213944298,19
-122.0829373621737,37.42137236399876,19
-122.0829062425667,37.42151569778871,19
-122.0828502269665,37.42176282576465,19
-122.0829435788635,37.42176776969635,19
-122.083217411188,37.42179248552686,19
-122.0835970430103,37.4217480074456,19
-122.0839455556771,37.42169364237603,19
-122.0840077894637,37.42176283815853,19
-122.084113587521,37.42174801104392,19
-122.0840762473784,37.42171341292375,19
-122.0841447047739,37.42167881534569,19
-122.084144704223,37.42181720660197,19
-122.0842503333074,37.4218170700446,19
-122.0844371128284,37.42177253003091,19 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
</Folder>
<Folder>
<name>Extruded Polygon</name>
<description>A simple way to model a building</description>
<Placemark>
<name>The Pentagon</name>
<LookAt>
<longitude>-77.05580139178142</longitude>
<latitude>38.870832443487</latitude>
<heading>59.88865561738225</heading>
<tilt>48.09646074797388</tilt>
<range>742.0552506670548</range>
</LookAt>
<Polygon>
<extrude>1</extrude>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -77.05788457660967,38.87253259892824,100
-77.05465973756702,38.87291016281703,100
-77.05315536854791,38.87053267794386,100
-77.05552622493516,38.868757801256,100
-77.05844056290393,38.86996206506943,100
-77.05788457660967,38.87253259892824,100 </coordinates>
</LinearRing>
</outerBoundaryIs>
<innerBoundaryIs>
<LinearRing>
<coordinates> -77.05668055019126,38.87154239798456,100
-77.05542625960818,38.87167890344077,100
-77.05485125901024,38.87076535397792,100
-77.05577677433152,38.87008686581446,100
-77.05691162017543,38.87054446963351,100
-77.05668055019126,38.87154239798456,100 </coordinates>
</LinearRing>
</innerBoundaryIs>
</Polygon>
</Placemark>
</Folder>
<Folder>
<name>Absolute and Relative</name>
<visibility>0</visibility>
<description>Four structures whose roofs meet exactly. Turn on/off
terrain to see the difference between relative and absolute
positioning.</description>
<LookAt>
<longitude>-112.3348969157552</longitude>
<latitude>36.14845533214919</latitude>
<altitude>0</altitude>
<heading>-86.91235037566909</heading>
<tilt>49.30695423894192</tilt>
<range>990.6761201087104</range>
</LookAt>
<Placemark>
<name>Absolute</name>
<visibility>0</visibility>
<styleUrl>#transBluePoly</styleUrl>
<Polygon>
<tessellate>1</tessellate>
<altitudeMode>absolute</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -112.3372510731295,36.14888505105317,1784
-112.3356128688403,36.14781540589019,1784
-112.3368169371048,36.14658677734382,1784
-112.3384408457543,36.14762778914076,1784
-112.3372510731295,36.14888505105317,1784 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
<Placemark>
<name>Absolute Extruded</name>
<visibility>0</visibility>
<styleUrl>#transRedPoly</styleUrl>
<Polygon>
<extrude>1</extrude>
<tessellate>1</tessellate>
<altitudeMode>absolute</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -112.3396586818843,36.14637618647505,1784
-112.3380597654315,36.14531751871353,1784
-112.3368254237788,36.14659596244607,1784
-112.3384555043203,36.14762621763982,1784
-112.3396586818843,36.14637618647505,1784 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
<Placemark>
<name>Relative</name>
<visibility>0</visibility>
<LookAt>
<longitude>-112.3350152490417</longitude>
<latitude>36.14943123077423</latitude>
<altitude>0</altitude>
<heading>-118.9214100848499</heading>
<tilt>37.92486261093203</tilt>
<range>345.5169113679813</range>
</LookAt>
<styleUrl>#transGreenPoly</styleUrl>
<Polygon>
<tessellate>1</tessellate>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -112.3349463145932,36.14988705767721,100
-112.3354019540677,36.14941108398372,100
-112.3344428289146,36.14878490381308,100
-112.3331289492913,36.14780840132443,100
-112.3317019516947,36.14680755678357,100
-112.331131440106,36.1474173426228,100
-112.332616324338,36.14845453364654,100
-112.3339876620524,36.14926570522069,100
-112.3349463145932,36.14988705767721,100 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
<Placemark>
<name>Relative Extruded</name>
<visibility>0</visibility>
<LookAt>
<longitude>-112.3351587892382</longitude>
<latitude>36.14979247129029</latitude>
<altitude>0</altitude>
<heading>-55.42811560891606</heading>
<tilt>56.10280503739589</tilt>
<range>401.0997279712519</range>
</LookAt>
<styleUrl>#transYellowPoly</styleUrl>
<Polygon>
<extrude>1</extrude>
<tessellate>1</tessellate>
<altitudeMode>relativeToGround</altitudeMode>
<outerBoundaryIs>
<LinearRing>
<coordinates> -112.3348783983763,36.1514008468736,100
-112.3372535345629,36.14888517553886,100
-112.3356068927954,36.14781612679284,100
-112.3350034807972,36.14846469024177,100
-112.3358353861232,36.1489624162954,100
-112.3345888301373,36.15026229372507,100
-112.3337937856278,36.14978096026463,100
-112.3331798208424,36.1504472788618,100
-112.3348783983763,36.1514008468736,100 </coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
</Folder>
</Folder>
</Document>
</kml>

View File

@@ -1,446 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>Red Bull X-Alps 2019 Route</name>
<snippet>https://www.redbullxalps.com/ Created by twpayne@gmail.com</snippet>
<open>1</open>
<Folder>
<name>Route</name>
<Placemark>
<LineString>
<coordinates>13.0484,47.79885 13.110917,47.804133 13.305787,47.332295 12.33277,47.784362 11.9549,46.737598 10.98526,47.4211 10.879767,47.401283 9.851879,46.815225 8.424457,46.770918 8.005393,46.577621 5.887857,45.306816 7.090381,44.667312 6.422229,44.120985 7.410751,43.755956 7.454787,43.75875</coordinates>
<tessellate>1</tessellate>
</LineString>
<Style>
<LineStyle>
<color>c0009090</color>
<width>4</width>
</LineStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Turnpoints</name>
<Folder>
<name>Salzburg</name>
<Placemark>
<Point>
<coordinates>13.0484,47.79885</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/go.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Gaisberg</name>
<snippet>signboard</snippet>
<Placemark>
<Point>
<coordinates>13.110917,47.804133</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/1.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Wagrain-Kleinarl</name>
<snippet>signboard</snippet>
<Placemark>
<Point>
<coordinates>13.305787,47.332295</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/2.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Aschau-Chiemsee</name>
<snippet>signboard</snippet>
<Placemark>
<Point>
<coordinates>12.33277,47.784362</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/3.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Kronplatz</name>
<snippet>signboard</snippet>
<Placemark>
<Point>
<coordinates>11.9549,46.737598</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/4.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Zugspitz</name>
<snippet>pass N</snippet>
<Placemark>
<Point>
<coordinates>10.98526,47.4211</coordinates>
</Point>
<Style>
<IconStyle>
<Icon>
<href>https://maps.google.com/mapfiles/kml/pal2/icon15.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Folder>
<Placemark>
<LineString>
<coordinates>10.98526,47.4211 10.98526,47.196269598520324</coordinates>
</LineString>
<Style>
<LineStyle>
<color>c00000c0</color>
<tessellate>1</tessellate>
<width>3</width>
</LineStyle>
</Style>
</Placemark>
</Folder>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Lermoos-Tiroler Zugspitz Arena</name>
<snippet>signboard</snippet>
<Placemark>
<Point>
<coordinates>10.879767,47.401283</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/5.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Davos</name>
<snippet>signboard</snippet>
<Placemark>
<Point>
<coordinates>9.851879,46.815225</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/6.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Titlis</name>
<snippet>signboard</snippet>
<Placemark>
<Point>
<coordinates>8.424457,46.770918</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/7.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Eiger</name>
<snippet>1500m radius</snippet>
<Placemark>
<Point>
<coordinates>8.005393,46.577621</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/8.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Placemark>
<LineString>
<coordinates>8.005393,46.59111082408879 8.007411386004984,46.59103930859754 8.009408360780075,46.590825520768504 8.011362740730094,46.59047172847008 8.013253795087904,46.58998168468399 8.01506146625202,46.58936058760861 8.016766582908044,46.58861502540384 8.018351063653368,46.587752906169705 8.019798108949551,46.586783373908844 8.021092379355597,46.58571671137243 8.022220158146354,46.584564230829145 8.02316949659154,46.58333815392459 8.023930340360643,46.582051481914704 8.024494635724556,46.58071785765729 8.024856414444256,46.57935142083319 8.025011856467065,46.57796665793827 8.024959329790072,46.576578248641624 8.024699407094783,46.57520091014225 8.02423485900483,46.5738492411753 8.023570624066586,46.572537567321 8.022713755798401,46.57127978925356 8.02167334739507,46.57008923553433 8.020460434907957,46.56897852150325 8.019087879945149,46.56795941575718 8.01757023314834,46.5670427156218 8.015923579901438,46.56623813292746 8.014165369908543,46.565554191290495 8.012314232444112,46.56499813597875 8.01038977922428,46.564575857307794 8.00841239697435,46.56429182837157 8.006403031871999,46.5641490577601 8.004382968128002,46.5641490577601 8.002373603025651,46.56429182837157 8.00039622077572,46.564575857307794 7.998471767555888,46.56499813597875 7.996620630091457,46.565554191290495 7.9948624200985625,46.56623813292746 7.99321576685166,46.5670427156218 7.991698120054851,46.56795941575718 7.990325565092044,46.56897852150325 7.989112652604931,46.57008923553433 7.9880722442016,46.57127978925356 7.987215375933413,46.572537567321 7.986551140995171,46.5738492411753 7.986086592905218,46.57520091014225 7.98582667020993,46.576578248641624 7.985774143532935,46.57796665793827 7.985929585555744,46.57935142083319 7.986291364275444,46.58071785765729 7.9868556596393585,46.582051481914704 7.987616503408459,46.58333815392459 7.988565841853647,46.584564230829145 7.989693620644403,46.58571671137243 7.990987891050449,46.586783373908844 7.992434936346632,46.587752906169705 7.994019417091955,46.58861502540384 7.995724533747981,46.58936058760861 7.997532204912097,46.58998168468399 7.999423259269906,46.59047172847008 8.001377639219925,46.590825520768504 8.003374613995016,46.59103930859754 8.005393,46.59111082408879</coordinates>
</LineString>
<Style>
<LineStyle>
<color>c000c000</color>
<tessellate>1</tessellate>
<width>3</width>
</LineStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Mont Blanc</name>
<snippet>pass N</snippet>
<Placemark>
<Point>
<coordinates>6.867674,45.830359</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/9.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Folder>
<Placemark>
<LineString>
<coordinates>6.867674,45.830359 6.867674,45.605528598520316</coordinates>
</LineString>
<Style>
<LineStyle>
<color>c00000c0</color>
<tessellate>1</tessellate>
<width>3</width>
</LineStyle>
</Style>
</Placemark>
</Folder>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>St. Hilare</name>
<snippet>signboard</snippet>
<Placemark>
<Point>
<coordinates>5.887857,45.306816</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/10.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Monte Viso</name>
<snippet>2250m radius</snippet>
<Placemark>
<Point>
<coordinates>7.090381,44.667312</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/A.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Placemark>
<LineString>
<coordinates>7.090381,44.68754673613318 7.092762589980537,44.6874757453242 7.095127457411984,44.687263271366724 7.09745899753014,44.686910806157954 7.099740840300694,44.68642082451503 7.101956965690705,44.68579676674505 7.104091816445464,44.685043014417296 7.106130407568226,44.68416485951042 7.108058431724881,44.68316846715423 7.109862359825947,44.6820608322314 7.111529536074018,44.680849730147806 7.113048266805791,44.67954366212132 7.114407902503551,44.678151795378 7.115598912401194,44.67668389867965 7.1166129511641305,44.675150273640085 7.117442917180201,44.673561682316134 7.118083002059652,44.67192927158581 7.118528731005785,44.67026449484756 7.118776993783513,44.668579031593154 7.11882606608039,44.666884705420784 7.118675621123042,44.66519340106522 7.118326731480924,44.663516981028 7.1177818610584405,44.661867202392706 7.117044847345161,44.66025563440796 7.116120874061689,44.65869357741505 7.115016434405281,44.65719198368645 7.113739285164013,44.65576138072787 7.1122983920308585,44.65441179757801 7.110703866508997,44.653152694619315 7.108966894856653,44.65199289738741 7.1070996595734846,44.650940534838895 7.105115253980594,44.65000298250571 7.103027590492422,44.64918681092998 7.1008513032207565,44.64849773973612 7.098601645588721,44.64794059765821 7.096294383665553,44.64751928879869 7.093945685961249,44.64723676535147 7.091572010443376,44.64709500697793 7.0891899895566235,44.64709500697793 7.08681631403875,44.64723676535147 7.084467616334447,44.64751928879869 7.08216035441128,44.64794059765821 7.079910696779244,44.64849773973612 7.0777344095075785,44.64918681092998 7.075646746019405,44.65000298250571 7.073662340426515,44.650940534838895 7.071795105143346,44.65199289738741 7.070058133491003,44.653152694619315 7.068463607969141,44.65441179757801 7.0670227148359865,44.65576138072787 7.065745565594718,44.65719198368645 7.06464112593831,44.65869357741505 7.063717152654839,44.66025563440796 7.062980138941559,44.661867202392706 7.062435268519076,44.663516981028 7.062086378876957,44.66519340106522 7.061935933919609,44.666884705420784 7.061985006216487,44.668579031593154 7.062233268994214,44.67026449484756 7.062678997940347,44.67192927158581 7.063319082819799,44.673561682316134 7.064149048835869,44.675150273640085 7.065163087598806,44.67668389867965 7.06635409749645,44.678151795378 7.067713733194209,44.67954366212132 7.0692324639259825,44.680849730147806 7.070899640174052,44.6820608322314 7.072703568275118,44.68316846715423 7.074631592431774,44.68416485951042 7.076670183554536,44.685043014417296 7.078805034309295,44.68579676674505 7.081021159699306,44.68642082451503 7.08330300246986,44.686910806157954 7.085634542588016,44.687263271366724 7.087999410019463,44.6874757453242 7.090381,44.68754673613318</coordinates>
</LineString>
<Style>
<LineStyle>
<color>c000c000</color>
<tessellate>1</tessellate>
<width>3</width>
</LineStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Cheval Blanc</name>
<snippet>pass W</snippet>
<Placemark>
<Point>
<coordinates>6.422229,44.120985</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/B.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Folder>
<Placemark>
<LineString>
<coordinates>6.422229,44.120985 6.7354178618529215,44.12055721299625</coordinates>
</LineString>
<Style>
<LineStyle>
<color>c00000c0</color>
<tessellate>1</tessellate>
<width>3</width>
</LineStyle>
</Style>
</Placemark>
</Folder>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Peille</name>
<snippet>signboard</snippet>
<Placemark>
<Point>
<coordinates>7.410751,43.755956</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/stop.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
<Folder>
<name>Monaco</name>
<Placemark>
<Point>
<coordinates>7.454787,43.75875</coordinates>
</Point>
<Style>
<IconStyle>
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
<Icon>
<href>https://maps.google.com/mapfiles/kml/paddle/ylw-stars.png</href>
</Icon>
</IconStyle>
</Style>
</Placemark>
<Style>
<ListStyle>
<listItemType>checkHideChildren</listItemType>
</ListStyle>
</Style>
</Folder>
</Folder>
</Document>
</kml>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

View File

@@ -1,19 +0,0 @@
{
"name": "leaflet-kml",
"version": "1.0.1",
"description": "Leaflet KML layer plugin",
"main": "L.KML.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/windycom/leaflet-kml.git"
},
"author": "Pavel Shramov, Bruno Bergot",
"license": "MIT",
"bugs": {
"url": "https://github.com/windycom/leaflet-kml/issues"
},
"homepage": "https://github.com/windycom/leaflet-kml#readme"
}

View File

@@ -38,7 +38,10 @@ new QWebChannel(qt.webChannelTransport, function (channel)
map.on('mousemove', function(e) map.on('mousemove', function(e)
{ {
MyApp.onMapMove(e.latlng.lat, e.latlng.lng) MyApp.onMapMove(e.latlng.lat, e.latlng.lng);
const bounds = map.getBounds();
MyApp.onMapZoom(bounds.getSouth(), bounds.getWest(), bounds.getNorth(), bounds.getEast(), map.getZoom());
}); });
var DrawShapes; var DrawShapes;

View File

@@ -0,0 +1,97 @@
import FreeCAD
import Part
import math
import requests
from io import BytesIO
from PIL import Image
import numpy as np
# ==================================================================
# CONFIGURACIÓN INICIAL (AJUSTAR ESTOS VALORES)
# ==================================================================
lat_min = 40.4165 # Latitud mínima (Ej: Madrid)
lat_max = 40.4300
lon_min = -3.7150 # Longitud mínima
lon_max = -3.6900
zoom = 16 # Nivel de zoom (13-19 para mejor resolución)
terrain_resolution = 50 # Puntos por eje en la malla del terreno
# ==================================================================
def download_tile(lon_deg, lat_deg, zoom):
"""Descarga un tile de OpenStreetMap"""
xtile = int((lon_deg + 180) / 360 * (2 ** zoom))
ytile = int((1 - math.log(math.tan(math.radians(lat_deg)) + 1 / math.cos(math.radians(lat_deg))) / math.pi) / 2 * (
2 ** zoom))
url = f"https://tile.openstreetmap.org/{zoom}/{xtile}/{ytile}.png"
response = requests.get(url)
return Image.open(BytesIO(response.content))
def get_tile_grid(lon_min, lon_max, lat_min, lat_max, zoom):
"""Calcula la cuadrícula de tiles necesarios"""
xtile_min = int((lon_min + 180) / 360 * (2 ** zoom))
xtile_max = int((lon_max + 180) / 360 * (2 ** zoom))
ytile_min = int((1 - math.log(math.tan(math.radians(lat_min)) + 1 / math.cos(math.radians(lat_min))) / math.pi) / 2 * (2 ** zoom))
ytile_max = int((1 - math.log(math.tan(math.radians(lat_max)) + 1 / math.cos(math.radians(lat_max))) / math.pi) / 2 * (2 ** zoom))
return range(xtile_min, xtile_max + 1), range(ytile_min, ytile_max + 1)
def create_stitched_image(xtiles, ytiles, zoom):
"""Une los tiles en una sola imagen"""
tile_size = 256
full_image = Image.new('RGB', (len(xtiles) * tile_size, len(ytiles) * tile_size))
for i, x in enumerate(xtiles):
for j, y in enumerate(ytiles):
tile = download_tile(x, y, zoom)
full_image.paste(tile, (i * tile_size, j * tile_size))
return full_image
def create_parametric_terrain(lon_min, lon_max, lat_min, lat_max, resolution):
"""Crea una superficie paramétrica basada en coordenadas geográficas"""
points = []
for i in np.linspace(lat_min, lat_max, resolution):
row = []
for j in np.linspace(lon_min, lon_max, resolution):
row.append(FreeCAD.Vector(j, i, 0)) # Añadir altura aquí si hay datos de elevación
points.append(row)
terrain = Part.makeFilledFace([points])
obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Terrain")
obj.Shape = terrain
return obj
def apply_texture(obj, image_path):
"""Aplica la textura al objeto en FreeCAD"""
obj.ViewObject.TextureMode = "EMBEDDED"
obj.ViewObject.TextureImage = image_path
obj.ViewObject.DisplayMode = "Shaded"
# Ejecución principal
'''if __name__ == "__main__":
# Paso 1: Descargar y unir tiles
xtiles, ytiles = get_tile_grid(lon_min, lon_max, lat_min, lat_max, zoom)
stitched_image = create_stitched_image(xtiles, ytiles, zoom)
texture_path = "/tmp/combined_texture.png"
stitched_image.save(texture_path)
# Paso 2: Crear terreno
doc = FreeCAD.newDocument()
terrain = create_parametric_terrain(lon_min, lon_max, lat_min, lat_max, terrain_resolution)
# Paso 3: Aplicar textura
apply_texture(terrain, texture_path)
# Ajustar vista
FreeCAD.Gui.SendMsgToActiveView("ViewFit")
FreeCAD.Gui.updateGui()
print("¡Proceso completado!")'''

View File

@@ -201,8 +201,8 @@ def simplifyWire(wire):
else: else:
i += 1 i += 1
if closed: #if closed:
points.append(FreeCAD.Vector(points[0])) points.append(FreeCAD.Vector(points[0]))
pol = Part.makePolygon(points) pol = Part.makePolygon(points)
return pol return pol
@@ -224,14 +224,20 @@ def getProjected(shape, direction=FreeCAD.Vector(0, 0, 1)): # Based on Draft / s
return ow return ow
def findObjects(classtype): '''def findObjects(classtype):
objects = FreeCAD.ActiveDocument.Objects objects = FreeCAD.ActiveDocument.Objects
objlist = list() objlist = list()
for object in objects: for object in objects:
if hasattr(object, "Proxy"): if hasattr(object, "Proxy"):
if object.Proxy.Type == classtype: if object.Proxy.Type == classtype:
objlist.append(object) objlist.append(object)
return objlist return objlist'''
def findObjects(classtype):
return [obj for obj in FreeCAD.ActiveDocument.Objects
if hasattr(obj, "Proxy")
and hasattr(obj.Proxy, "Type")
and obj.Proxy.Type == classtype]
def getClosePoints(sh1, angle): def getClosePoints(sh1, angle):
''' '''

View File

@@ -0,0 +1,305 @@
import math
import ArchComponent
import FreeCAD
import Part
import random
from FreeCAD import Qt
from PySide.QtCore import QT_TRANSLATE_NOOP
try:
from scipy import spatial
has_scipy = True
except ImportError:
has_scipy = False
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtCore, QtGui
from DraftTools import translate
from PySide.QtCore import QT_TRANSLATE_NOOP
import Part
import os
else:
# \cond
def translate(ctxt, txt):
return txt
def QT_TRANSLATE_NOOP(ctxt, txt):
return txt
# \endcond
__title__ = "FreeCAD Fixed Rack"
__author__ = "Javier Braña"
__url__ = "http://www.sogos-solar.com"
__dir__ = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", "PVPlant")
DirResources = os.path.join(__dir__, "../Resources")
DirIcons = os.path.join(DirResources, "Icons")
DirImages = os.path.join(DirResources, "Images")
def makeTree():
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Tree")
Tree(obj)
ViewProviderTree(obj.ViewObject)
FreeCAD.ActiveDocument.recompute()
try:
folder = FreeCAD.ActiveDocument.Vegetation
except:
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Vegetation')
folder.Label = "Vegetation"
folder.addObject(obj)
return obj
class Tree(ArchComponent.Component):
"""A parametric tree object for architectural design"""
def __init__(self, obj):
ArchComponent.Component.__init__(self, obj)
self.obj = obj
self.setProperties(obj)
random.seed(42) # Semilla para resultados consistentes
def setProperties(self, obj):
"""Define y configura las propiedades del objeto"""
pl = obj.PropertiesList
# Propiedades de la copa
canopy_props = [
("CanopyHeight", "App::PropertyLength", "Altura total de la copa"),
("CanopyRadius", "App::PropertyLength", "Radio máximo de la copa"),
("Spikiness", "App::PropertyFloatConstraint", "Irregularidad de la superficie", (0.5, 0.0, 1.0, 0.05)),
(
"CrownExpansion", "App::PropertyFloatConstraint", "Expansión de la corona superior", (1.0, 0.0, 2.0, 0.05)),
("UmbrellaEffect", "App::PropertyFloatConstraint", "Efecto de dosel/paraguas", (0.0, 0.0, 1.0, 0.05)),
("LeafCount", "App::PropertyInteger", "Densidad de follaje (número de segmentos)")
]
for prop in canopy_props:
name, ptype, doc, *args = prop
if name not in pl:
if ptype == "App::PropertyFloatConstraint":
obj.addProperty(ptype, name, "Canopy", doc).__setattr__(name, args[0])
else:
obj.addProperty(ptype, name, "Canopy", doc)
# Valores por defecto
if name == "LeafCount":
setattr(obj, name, 20)
elif name in ["CanopyHeight", "CanopyRadius"]:
setattr(obj, name, 4000 if "Height" in name else 1500)
# Propiedades del tronco
trunk_props = [
("TrunkHeight", "App::PropertyLength", "Altura del tronco", 2000),
("TrunkRadius", "App::PropertyLength", "Radio base del tronco", 150),
("TrunkFaces", "App::PropertyInteger", "Caras del tronco", 6)
]
for prop in trunk_props:
name, ptype, doc, default = prop
if name not in pl:
obj.addProperty(ptype, name, "Trunk", doc)
setattr(obj, name, default)
# Propiedades base
if "Type" not in pl:
obj.addProperty("App::PropertyString", "Type", "Base", "Tipo de objeto").Type = "Vegetable-Tree"
obj.setEditorMode("Type", 1) # Hacerla de solo lectura
obj.Proxy = self
obj.IfcType = "Shading Device"
obj.setEditorMode("IfcType", 1)
def onDocumentRestored(self, obj):
ArchComponent.Component.onDocumentRestored(self, obj)
self.setProperties(obj)
def onChanged(self, obj, prop):
"""Actualiza la forma cuando cambian propiedades"""
if prop in ["CanopyHeight", "CanopyRadius", "Spikiness", "CrownExpansion",
"UmbrellaEffect", "LeafCount", "TrunkHeight", "TrunkRadius", "TrunkFaces"]:
self.execute(obj)
def createTrunk(self, obj):
"""Crea la geometría del tronco usando un loft"""
try:
# Calcula dimensiones proporcionales
base_radius = obj.TrunkRadius.Value
top_radius = base_radius * 0.8
height = obj.TrunkHeight.Value
# Crea tres perfiles circulares
profiles = []
for z, radius in [(0, base_radius),
(height / 3, base_radius),
(height, top_radius)]:
circle = Part.makeCircle(radius, FreeCAD.Vector(0, 0, z))
profiles.append(Part.Wire([circle]))
return Part.makeLoft(profiles, True, True)
except Exception as e:
FreeCAD.Console.PrintError(f"Error creando tronco: {str(e)}\n")
return None
def createCanopy(self, obj):
"""Genera la forma de la copa usando una envoltura convexa"""
if not has_scipy:
FreeCAD.Console.PrintError("Scipy no está instalado. No se puede generar la copa.\n")
return None
try:
# Configuración inicial
n_segments = max(3, obj.LeafCount) # Mínimo 3 segmentos
radius = obj.CanopyRadius.Value
height = obj.CanopyHeight.Value
# Genera puntos distribuidos esféricamente con ruido
points = []
for _ in range(n_segments * 10): # 10 puntos por segmento
theta = random.uniform(0, 2 * math.pi)
phi = math.acos(random.uniform(-1, 1))
# Aplica parámetros de forma
r = radius * (1 - obj.Spikiness * random.random())
x = r * math.sin(phi) * math.cos(theta)
y = r * math.sin(phi) * math.sin(theta)
z = height * (0.5 + 0.5 * math.cos(phi)) # Distribución vertical
# Aplica efectos de forma
z *= (1 - obj.UmbrellaEffect)
if z > height / 2:
x *= obj.CrownExpansion
y *= obj.CrownExpansion
points.append(FreeCAD.Vector(x, y, z))
# Crea la envoltura convexa
hull = spatial.ConvexHull([(p.x, p.y, p.z) for p in points])
faces = []
for simplex in hull.simplices:
triangle = [points[i] for i in simplex]
faces.append(Part.Face(Part.makePolygon(triangle + [triangle[0]])))
return Part.Compound(faces)
except Exception as e:
FreeCAD.Console.PrintError(f"Error creando copa: {str(e)}\n")
return None
def execute(self, obj):
"""Ensambla el objeto final"""
try:
# Crea componentes
trunk = self.createTrunk(obj)
canopy = self.createCanopy(obj)
# Verifica componentes válidos
if not trunk or not canopy:
raise ValueError("Error en la generación de componentes")
# Posiciona la copa sobre el tronco
canopy_placement = FreeCAD.Placement()
canopy_placement.Base.z = obj.TrunkHeight.Value
canopy.Placement = canopy_placement
# Combina las formas
compound = Part.Compound([trunk, canopy])
obj.Shape = compound
# Configura apariencia
if obj.ViewObject:
obj.ViewObject.DiffuseColor = ([(0.35, 0.2, 0.05)] * len(trunk.Faces) +
[(0.1, 0.6, 0.2)] * len(canopy.Faces)) # Color copa
except Exception as e:
FreeCAD.Console.PrintError(f"Error al ejecutar: {str(e)}\n")
class ViewProviderTree(ArchComponent.ViewProviderComponent):
"A View Provider for the Pipe object"
def __init__(self, vobj):
ArchComponent.ViewProviderComponent.__init__(self, vobj)
def getIcon(self):
return str(os.path.join(DirIcons, "tree(1).svg"))
class TreeTaskPanel(QtGui.QWidget):
def __init__(self, obj=None):
QtGui.QWidget.__init__(self)
self.obj = obj
if self.obj is None:
self.obj = makeTree()
self.form = FreeCADGui.PySideUic.loadUi(__dir__ + "/PVPlantTree.ui")
self.layout = QtGui.QHBoxLayout(self)
self.layout.setContentsMargins(4, 4, 4, 4)
self.layout.addWidget(self.form)
self.form.editCanopyHeight.valueChanged.connect(self.Canopy)
self.form.editCanopyRadius.valueChanged.connect(self.Canopy)
self.form.editSpikiness.valueChanged.connect(self.Canopy)
self.form.editCrownExpansion.valueChanged.connect(self.Canopy)
self.form.editLeftUmbrellaEffect.valueChanged.connect(self.Canopy)
self.form.editLeafCount.valueChanged.connect(self.Canopy)
def Canopy(self):
self.obj.CanopyHeight = FreeCAD.Units.Quantity(self.form.editCanopyHeight.text()).Value
self.obj.CanopyRadius = FreeCAD.Units.Quantity(self.form.editCanopyRadius.text()).Value
self.obj.Spikiness = self.form.editSpikiness.value()
self.obj.CrownExpansion = self.form.editCrownExpansion.value()
self.obj.UmbrellaEffect = self.form.editLeftUmbrellaEffect.value()
self.obj.LeafCount = self.form.editLeafCount.value()
FreeCAD.ActiveDocument.recompute()
def accept(self):
FreeCADGui.Control.closeDialog()
return True
def reject(self):
FreeCAD.ActiveDocument.removeObject(self.obj.Name)
FreeCADGui.Control.closeDialog()
return True
class CommandTree:
"the PVPlant Tree command definition"
def GetResources(self):
return {'Pixmap': str(os.path.join(DirIcons, "tree(1).svg")),
'MenuText': QtCore.QT_TRANSLATE_NOOP("PVPlantTree", "Tree"),
'Accel': "S, T",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PVPlanTree",
"Creates a Tree object from setup dialog.")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
import draftguitools.gui_trackers as DraftTrackers
self.tree = makeTree()
FreeCADGui.Snapper.getPoint(callback=self.getPoint,
movecallback=self.mousemove,
extradlg=self.taskbox(),
title="Position of the tree:")
def getPoint(self, point=None, obj=None):
self.tree.Placement.Base = point
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
self.tracker.finalize()
def mousemove(self, pt, snapInfo):
self.tree.Placement.Base = pt
def taskbox(self):
self.form = TreeTaskPanel(self.tree)
return self.form

389
hydro/hydrological.py Normal file
View File

@@ -0,0 +1,389 @@
import FreeCAD
import FreeCADGui
import Mesh
import Part
import numpy as np
import random
from concurrent.futures import ThreadPoolExecutor
from multiprocessing import Pool, cpu_count
from collections import deque
import os
from PVPlantResources import DirIcons as DirIcons
def mesh_to_numpy(mesh_obj):
"""Convierte la malla a arrays de NumPy con validación robusta"""
mesh = mesh_obj.Mesh
# Convertir vértices a array NumPy (shape: Nx3)
vertices = np.array([(v.x, v.y, v.z) for v in mesh.Points], dtype=np.float32)
# Convertir facetas a array NumPy (shape: Mx3)
facets = np.array( [f.PointIndices for f in mesh.Facets], dtype=np.uint32)
# Verificar integridad de índices
max_index = len(mesh.Points) - 1
if facets.size > 0 and (facets > max_index).any():
raise ValueError("Índices de vértices fuera de rango")
return vertices, facets
def build_adjacency_matrix(facets):
"""Construye matriz de adyacencia con conversión segura de tipos"""
edges = {}
adjacency = [[] for _ in range(len(facets))]
for idx, facet in enumerate(facets):
if len(facet) != 3:
continue
v0, v1, v2 = facet
for edge in [(v0, v1), (v1, v2), (v2, v0)]:
sorted_edge = tuple(sorted(edge))
if sorted_edge not in edges:
edges[sorted_edge] = []
edges[sorted_edge].append(idx)
# Procesar solo aristas con 2 facetas
for edge, facet_indices in edges.items():
if len(facet_indices) == 2:
f1, f2 = facet_indices
adjacency[f1].append(f2)
adjacency[f2].append(f1)
return adjacency
def calculate_incenters_parallel(vertices, facets):
"""Cálculo paralelizado de incentros usando NumPy"""
v0 = vertices[facets[:, 0]]
v1 = vertices[facets[:, 1]]
v2 = vertices[facets[:, 2]]
a = np.linalg.norm(v1 - v2, axis=1)
b = np.linalg.norm(v0 - v2, axis=1)
c = np.linalg.norm(v0 - v1, axis=1)
perimeters = a + b + c
return (a[:, None] * v0 + b[:, None] * v1 + c[:, None] * v2) / perimeters[:, None]
def find_basins_parallel(args):
"""Función paralelizable para procesamiento de cuencas"""
chunk, adjacency, elevations = args
basins = []
visited = np.zeros(len(elevations), dtype=bool)
for seed in chunk:
if visited[seed]:
continue
queue = deque([seed])
basin = []
min_elev = elevations[seed]
while queue:
current = queue.popleft()
if visited[current]:
continue
visited[current] = True
basin.append(current)
neighbors = [n for n in adjacency[current] if elevations[n] >= min_elev]
queue.extend(neighbors)
if len(basin) > 0:
basins.append(basin)
return basins
def find_hydrological_basins(mesh_obj, min_area=100):
"""Identificación de cuencas optimizada"""
FreeCAD.Console.PrintMessage(f" -- vertices y facets: ")
FreeCADGui.updateGui()
vertices, facets = mesh_to_numpy(mesh_obj)
FreeCAD.Console.PrintMessage(f" -- Adjacency: ")
FreeCADGui.updateGui()
adjacency = build_adjacency_matrix(facets)
FreeCAD.Console.PrintMessage(f" -- Elevations: ")
FreeCADGui.updateGui()
elevations = calculate_incenters_parallel(vertices, facets)[:, 2]
# Dividir trabajo en chunks
chunk_size = len(facets) // (cpu_count() * 2)
chunks = [
(chunk_range, adjacency, elevations) # Empaqueta los 3 argumentos
for chunk_range in [
range(i, min(i + chunk_size, len(facets)))
for i in range(0, len(facets), chunk_size)
]
]
# Procesamiento paralelo
with ThreadPoolExecutor(max_workers=cpu_count()) as executor:
results = list(executor.map(find_basins_parallel, chunks))
# Combinar resultados
all_basins = [b for sublist in results for b in sublist]
# Filtrar por área mínima
valid_basins = []
for basin in all_basins:
area = sum(triangle_area(vertices[facets[i]]) for i in basin)
if area >= min_area:
valid_basins.append({'facets': basin, 'area': area})
return valid_basins
def triangle_area(vertices):
"""Cálculo rápido de área con producto cruz"""
return 0.5 * np.linalg.norm(
np.cross(vertices[1] - vertices[0], vertices[2] - vertices[0])
)
def validate_facet(facet):
"""Valida que la faceta sea un triángulo válido"""
return hasattr(facet, 'Points') and len(facet.Points) == 3
def calculate_incenter(facet):
"""Calcula el incentro usando la función nativa de FreeCAD"""
try:
return facet.InCircle[0] # (x, y, z)
except:
return None
def build_adjacency(mesh):
"""Construye matriz de adyacencia eficiente en memoria"""
edges = {}
adjacency = [[] for _ in mesh.Facets]
for idx, facet in enumerate(mesh.Facets):
if not validate_facet(facet):
continue
pts = facet.Points
for edge in [(min(pts[0], pts[1]), max(pts[0], pts[1])),
(min(pts[1], pts[2]), max(pts[1], pts[2])),
(min(pts[2], pts[0]), max(pts[2], pts[0]))]:
if edge in edges:
neighbor = edges[edge]
adjacency[idx].append(neighbor)
adjacency[neighbor].append(idx)
del edges[edge] # Liberar memoria
else:
edges[edge] = idx
return adjacency
def find_hydrological_basins_old(mesh_obj, min_area=100):
"""Identificación de cuencas con validación de datos"""
mesh = mesh_obj.Mesh
adjacency = build_adjacency(mesh)
basin_map = {}
current_basin = 0
for seed in range(len(mesh.Facets)):
if seed in basin_map or not validate_facet(mesh.Facets[seed]):
continue
queue = deque([seed])
basin_area = 0.0
basin_facets = []
while queue:
facet_idx = queue.popleft()
if facet_idx in basin_map:
continue
facet = mesh.Facets[facet_idx]
in_center = calculate_incenter(facet)
if not in_center:
continue
# Verificar mínimo local
is_sink = True
for neighbor in adjacency[facet_idx]:
if neighbor >= len(mesh.Facets) or not validate_facet(mesh.Facets[neighbor]):
continue
n_center = calculate_incenter(mesh.Facets[neighbor])
if n_center and n_center[2] < in_center[2]:
is_sink = False
break
if is_sink:
basin_map[facet_idx] = current_basin
basin_facets.append(facet_idx)
basin_area += facet.Area
# Expansión controlada
for neighbor in adjacency[facet_idx]:
if neighbor not in basin_map:
queue.append(neighbor)
if basin_area >= min_area:
yield {
'facets': basin_facets,
'area': basin_area,
'depth': calculate_basin_depth(mesh, basin_facets)
}
current_basin += 1
def calculate_basin_depth(mesh, basin_facets):
"""Calcula la profundidad máxima de la cuenca"""
min_z = float('inf')
max_z = -float('inf')
for idx in basin_facets:
center = calculate_incenter(mesh.Facets[idx])
if center:
min_z = min(min_z, center[2])
max_z = max(max_z, center[2])
return max_z - min_z if max_z != min_z else 0.0
def simulate_water_flow(mesh_obj, basins, rainfall=1.0):
""" Simulación de flujo con prevención de bucles infinitos """
mesh = mesh_obj.Mesh
adjacency = build_adjacency(mesh)
flow_paths = []
for basin in basins:
start_facets = basin['facets'][:2] # Muestra primeros 10 caminos
for start in start_facets:
path = []
visited = set()
current = start
while current is not None and current not in visited:
visited.add(current)
facet = mesh.Facets[current]
center = calculate_incenter(facet)
if not center:
break
path.append(FreeCAD.Vector(*center))
# Buscar vecino más bajo
next_facet = None
min_elev = float('inf')
for neighbor in adjacency[current]:
if neighbor >= len(mesh.Facets):
continue
n_center = calculate_incenter(mesh.Facets[neighbor])
if n_center and n_center[2] < min_elev:
min_elev = n_center[2]
next_facet = neighbor
current = next_facet if min_elev < center[2] else None
if len(path) > 1:
flow_paths.append(path)
return flow_paths
def colorize_mesh(mesh_obj, facet_indices, color):
"""Coloriza facetas específicas de forma compatible"""
mesh = mesh_obj.Mesh
# Crear nuevo objeto Mesh
colored_mesh = Mesh.Mesh()
colored_mesh.addMesh(mesh)
# Crear nuevo objeto en el documento
new_obj = FreeCAD.ActiveDocument.addObject("Mesh::Feature", "ColoredBasin")
new_obj.Mesh = colored_mesh
# Asignar colores a los vértices
vcolors = []
for idx in range(len(mesh.Points)):
vcolors.append((0.8, 0.8, 0.8)) # Color base
for facet_id in facet_indices:
facet = mesh.Facets[facet_id]
for vtx in facet.PointIndices:
vcolors[vtx] = color # Color de la cuenca
new_obj.ViewObject.PointColor = vcolors
new_obj.ViewObject.Lighting = "One side"
new_obj.ViewObject.Shading = "Flat Lines"
def create_polyline(points):
"""Crea un objeto Polyline en FreeCAD"""
if len(points) < 2:
return
poly = Part.makePolygon(points)
obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "FlowPath")
obj.Shape = poly
obj.ViewObject.LineWidth = 2.0
obj.ViewObject.LineColor = (0.0, 0.0, 1.0)
class CommandHydrologicalAnalysis:
def GetResources(self):
return {'Pixmap': str(os.path.join(DirIcons, "drop.jpg")),
'MenuText': "Hidrological analysis",
'Accel': "H, A",
'ToolTip': "Hidrological analysis"}
def IsActive(self):
return True
def Activated(self):
# User input parameters (example values)
os.environ['OMP_NUM_THREADS'] = str(cpu_count())
os.environ['MKL_NUM_THREADS'] = str(cpu_count())
os.environ["FREECAD_NO_FORK"] = "1" # Desactiva el fork en sistemas Unix
#try:
# Parámetros de usuario
min_basin_area = 100 # m²
rainfall_intensity = 1.0
# Validar selección
mesh_obj = FreeCADGui.Selection.getSelection()[0]
if not mesh_obj.isDerivedFrom("Mesh::Feature"):
raise ValueError("Selecciona un objeto de malla")
# Procesamiento principal
FreeCAD.Console.PrintMessage(f"buscar basins: ")
FreeCADGui.updateGui()
basins = list(find_hydrological_basins(mesh_obj, min_basin_area))
FreeCAD.Console.PrintMessage(f" - Cuencas identificadas: {len(basins)}\n")
'''FreeCAD.Console.PrintMessage(f"simulate_water_flow: ")
FreeCADGui.updateGui()
flow_paths = simulate_water_flow(mesh_obj, basins, rainfall_intensity)
FreeCAD.Console.PrintMessage(f" - Trayectorias de flujo generadas: {len(flow_paths)}\n")
FreeCADGui.updateGui()'''
# Visualización
for basin in basins:
color = (random.random(), random.random(), random.random())
colorize_mesh(mesh_obj, basin['facets'], color)
'''for path in flow_paths:
create_polyline(path)'''
FreeCAD.ActiveDocument.recompute()
'''except Exception as e:
FreeCAD.Console.PrintError(f"Error: {str(e)}\n")
finally:
# Limpieza de memoria
import gc
gc.collect()'''

View File

@@ -7,7 +7,7 @@
# Find the associated blog post at: http://blog.eskriett.com/2013/07/19/downloading-google-maps/ # Find the associated blog post at: http://blog.eskriett.com/2013/07/19/downloading-google-maps/
import math import math
# from PIL import Image from PIL import Image
import os import os
import urllib import urllib
@@ -15,7 +15,7 @@ import urllib
# alternativa a PIL: Image # alternativa a PIL: Image
# CV2 # CV2
class GoogleMapDownloader: class GoogleMapDownloader1:
""" """
A class which generates high resolution google maps images given A class which generates high resolution google maps images given
a longitude, latitude and zoom level a longitude, latitude and zoom level

View File

@@ -0,0 +1,207 @@
import math
from PIL import Image
import urllib.request
from io import BytesIO
import time
class GoogleMapDownloader:
def __init__(self, zoom=12, layer='raw_satellite'):
self._zoom = zoom
self.layer_map = {
'roadmap': 'm',
'terrain': 'p',
'satellite': 's',
'hybrid': 'y',
'raw_satellite': 's'
}
self._layer = self.layer_map.get(layer, 's')
self._style = 'style=feature:all|element:labels|visibility:off' if layer == 'raw_satellite' else ''
def latlng_to_tile(self, lat, lng):
"""Convierte coordenadas a tiles X/Y con precisión decimal"""
tile_size = 256
numTiles = 1 << self._zoom
point_x = (tile_size / 2 + lng * tile_size / 360.0) * numTiles / tile_size
sin_y = math.sin(lat * (math.pi / 180.0))
point_y = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) *
-(tile_size / (2 * math.pi))) * numTiles / tile_size
return point_x, point_y
def generateImage(self, sw_lat, sw_lng, ne_lat, ne_lng):
"""Genera la imagen para un área rectangular definida por coordenadas"""
# Convertir coordenadas a tiles con precisión decimal
sw_x, sw_y = self.latlng_to_tile(sw_lat, sw_lng)
ne_x, ne_y = self.latlng_to_tile(ne_lat, ne_lng)
# Asegurar que las coordenadas estén en el orden correcto
min_x = min(sw_x, ne_x)
max_x = max(sw_x, ne_x)
min_y = min(sw_y, ne_y)
max_y = max(sw_y, ne_y)
# Calcular los tiles mínimos y máximos necesarios
min_tile_x = math.floor(min_x)
max_tile_x = math.ceil(max_x)
min_tile_y = math.floor(min_y)
max_tile_y = math.ceil(max_y)
# Calcular dimensiones en tiles
tile_width = int(max_tile_x - min_tile_x) + 1
tile_height = int(max_tile_y - min_tile_y) + 1
# Crear imagen temporal para todos los tiles necesarios
full_img = Image.new('RGB', (tile_width * 256, tile_height * 256))
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
servers = ['mt0', 'mt1', 'mt2', 'mt3']
for x in range(min_tile_x, max_tile_x + 1):
for y in range(min_tile_y, max_tile_y + 1):
server = servers[(x + y) % len(servers)]
base_url = f"https://{server}.google.com/vt?lyrs={self._layer}&x={x}&y={y}&z={self._zoom}"
url = f"{base_url}&{self._style}" if self._style else base_url
try:
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req) as response:
tile_data = response.read()
img = Image.open(BytesIO(tile_data))
pos_x = (x - min_tile_x) * 256
pos_y = (y - min_tile_y) * 256
full_img.paste(img, (pos_x, pos_y))
#print(f"✅ Tile ({x}, {y}) descargado")
except Exception as e:
#print(f"❌ Error en tile ({x},{y}): {str(e)}")
error_tile = Image.new('RGB', (256, 256), (255, 0, 0))
full_img.paste(error_tile, (pos_x, pos_y))
time.sleep(0.05)
# Calcular desplazamientos para recorte final
left_offset = int((min_x - min_tile_x) * 256)
right_offset = int((max_tile_x - max_x) * 256)
top_offset = int((min_y - min_tile_y) * 256)
bottom_offset = int((max_tile_y - max_y) * 256)
# Calcular coordenadas de recorte
left = left_offset
top = top_offset
right = full_img.width - right_offset
bottom = full_img.height - bottom_offset
# Asegurar que las coordenadas sean válidas
if right < left:
right = left + 1
if bottom < top:
bottom = top + 1
# Recortar la imagen al área exacta solicitada
result = full_img.crop((
left,
top,
right,
bottom
))
return full_img
class GoogleMapDownloader_1:
def __init__(self, zoom=12, layer='hybrid'):
"""
Args:
zoom: Zoom level (0-23)
layer: Map type (roadmap, terrain, satellite, hybrid)
"""
self._zoom = zoom
self.layer_map = {
'roadmap': 'm',
'terrain': 'p',
'satellite': 's',
'hybrid': 'y',
'raw_satellite': 's' # Capa especial sin etiquetas
}
self._layer = self.layer_map.get(layer, 's')
self._style = 'style=feature:all|element:labels|visibility:off' if layer == 'raw_satellite' else ''
def latlng_to_tile(self, lat, lng):
"""Convierte coordenadas a tiles X/Y"""
tile_size = 256
numTiles = 1 << self._zoom
# Cálculo para coordenada X
point_x = (tile_size / 2 + lng * tile_size / 360.0) * numTiles / tile_size
# Cálculo para coordenada Y
sin_y = math.sin(lat * (math.pi / 180.0))
point_y = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) *
-(tile_size / (2 * math.pi))) * numTiles / tile_size
return int(point_x), int(point_y)
def generateImage(self, sw_lat, sw_lng, ne_lat, ne_lng):
"""
Genera la imagen para un área rectangular definida por:
- sw_lat, sw_lng: Esquina suroeste (latitud, longitud)
- ne_lat, ne_lng: Esquina noreste (latitud, longitud)
"""
# Convertir coordenadas a tiles
sw_x, sw_y = self.latlng_to_tile(sw_lat, sw_lng)
ne_x, ne_y = self.latlng_to_tile(ne_lat, ne_lng)
# Determinar rango de tiles
min_x = min(sw_x, ne_x)
max_x = max(sw_x, ne_x)
min_y = min(sw_y, ne_y)
max_y = max(sw_y, ne_y)
# Calcular dimensiones en tiles
tile_width = max_x - min_x + 1
tile_height = max_y - min_y + 1
# Crear imagen final
result = Image.new('RGB', (256 * tile_width, 256 * tile_height))
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
servers = ['mt0', 'mt1', 'mt2', 'mt3']
print(f"Descargando {tile_width}x{tile_height} tiles ({tile_width * tile_height} total)")
for x in range(min_x, max_x + 1):
for y in range(min_y, max_y + 1):
# Seleccionar servidor rotatorio
server = servers[(x + y) % len(servers)]
# Construir URL con parámetro para quitar etiquetas si es necesario
url = f"https://{server}.google.com/vt?lyrs={self._layer}&x={x}&y={y}&z={self._zoom}"
if self._style:
url = f"{url}&{self._style}"
print("Descargando tile:", url)
try:
# Descargar tile
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req) as response:
tile_data = response.read()
# Procesar en memoria
img = Image.open(BytesIO(tile_data))
pos_x = (x - min_x) * 256
pos_y = (y - min_y) * 256
result.paste(img, (pos_x, pos_y))
print(f"✅ Tile ({x}, {y}) descargado")
except Exception as e:
print(f"❌ Error en tile ({x},{y}): {str(e)}")
# Crear tile de error (rojo)
error_tile = Image.new('RGB', (256, 256), (255, 0, 0))
pos_x = (x - min_x) * 256
pos_y = (y - min_y) * 256
result.paste(error_tile, (pos_x, pos_y))
# Pausa para evitar bloqueos
time.sleep(0.05)
return result

21
package.xml Normal file
View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
<name>PVPlant</name>
<description>FreeCAD Fotovoltaic Power Plant Toolkit</description>
<version>2025.11.20</version>
<date>2025.11.20</date>
<maintainer email="javier.branagutierrez@gmail.com">Javier Braña</maintainer>
<license file="LICENSE">LGPL-2.1-or-later</license>
<url type="repository" branch="developed">https://homehud.duckdns.org/javier/PVPlant</url>
<url type="bugtracker">https://homehud.duckdns.org/javier/PVPlant/issues</url>
<url type="readme">https://homehud.duckdns.org/javier/PVPlant/src/branch/developed/README.md</url>
<icon>PVPlant/Resources/Icons/PVPlantWorkbench.svg</icon>
<content>
<workbench>
<classname>PVPlantWorkbench</classname>
<subdirectory>./</subdirectory>
</workbench>
</content>
</package>

View File

@@ -24,18 +24,23 @@ 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, PVPlantTreeGenerator, PVPlantBuilding, PVPlantTrench, 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 Vegetation import PVPlantTreeGenerator
from Mechanical.Frame import PVPlantFrame from Mechanical.Frame import PVPlantFrame
from Project.Area import PVPlantArea, PVPlantAreaUtils from Project.Area import PVPlantArea, PVPlantAreaUtils
#from Importer import importDXF #from Importer import importDXF
from Export import PVPlantBOQCivil, PVPlantBOQElectrical, PVPlantBOQMechanical, exportPVSyst, exportDXF from Export import PVPlantBOQCivil, PVPlantBOQElectrical, PVPlantBOQMechanical, exportPVSyst, exportDXF
from Utils import PVPlantUtils, PVPlantTrace, m_gui_edit, profile_editor, graphics from Utils import PVPlantUtils, PVPlantTrace, m_gui_edit, profile_editor, graphics
#from Lib import GoogleMapDownloader
from Electrical.Cable import PVPlantCable, PVPlantElectricalLine from Electrical.Cable import PVPlantCable, PVPlantElectricalLine
from Electrical import Conduit from Electrical import Conduit
@@ -47,6 +52,8 @@ class _CommandReload:
import MeshTools.Triangulation as Triangulation import MeshTools.Triangulation as Triangulation
from Project import ProjectSetup from Project import ProjectSetup
import importlib import importlib
import hydro.hydrological as hydro
import Importer.importOSM as iOSM
importlib.reload(ProjectSetup) importlib.reload(ProjectSetup)
importlib.reload(PVPlantPlacement) importlib.reload(PVPlantPlacement)
@@ -56,9 +63,11 @@ class _CommandReload:
importlib.reload(PVPlantSite) importlib.reload(PVPlantSite)
importlib.reload(PVPlantFrame) importlib.reload(PVPlantFrame)
importlib.reload(PVPlantRackChecking) importlib.reload(PVPlantRackChecking)
importlib.reload(PVPlantFence) importlib.reload(PVPlantFence)
importlib.reload(PVPlantFenceGate) importlib.reload(PVPlantFenceGate)
importlib.reload(PVPlantFencePost) importlib.reload(PVPlantFencePost)
importlib.reload(PVPlantFoundation) importlib.reload(PVPlantFoundation)
importlib.reload(PVPlantCreateTerrainMesh) importlib.reload(PVPlantCreateTerrainMesh)
importlib.reload(PVPlantTreeGenerator) importlib.reload(PVPlantTreeGenerator)
@@ -98,6 +107,11 @@ class _CommandReload:
importlib.reload(layoutToExcel) importlib.reload(layoutToExcel)
importlib.reload(Conduit) importlib.reload(Conduit)
importlib.reload(hydro)
importlib.reload(iOSM)
import Project.GenerateExternalDocument as GED
importlib.reload(GED)
#importlib.reload(GoogleMapDownloader) #importlib.reload(GoogleMapDownloader)
print("Reload modules...") print("Reload modules...")

View File

@@ -5,4 +5,16 @@ openpyxl~=3.1.2
utm~=0.7.0 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
geopy~=2.4.1
lxml~=4.9.3
Pillow~=10.1.0
pyproj~=3.7.1
simplekml~=1.3.6
geojson~=3.1.0
certifi~=2023.11.17
SciPy~=1.11.4
pycollada~=0.7.2
shapely
rtree