Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4476afc1a2 | |||
| d61260fdd3 | |||
| 049898c939 | |||
| 3a188cc47d | |||
| 5db8f5439d | |||
| e1e1441892 |
4
.idea/PVPlant.iml
generated
4
.idea/PVPlant.iml
generated
@@ -5,4 +5,8 @@
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PackageRequirementsSettings">
|
||||
<option name="removeUnused" value="true" />
|
||||
<option name="modifyBaseFiles" value="true" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -20,16 +20,18 @@
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import Part
|
||||
import Draft
|
||||
import MeshPart as mp
|
||||
import ArchComponent
|
||||
|
||||
import Civil.Fence.PVPlantFencePost as PVPlantFencePost
|
||||
import PVPlantSite
|
||||
import Utils.PVPlantUtils as utils
|
||||
import copy
|
||||
import math
|
||||
|
||||
import ArchComponent
|
||||
import Draft
|
||||
import FreeCAD
|
||||
import Part
|
||||
|
||||
import PVPlantFencePost
|
||||
import PVPlantSite
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
@@ -56,26 +58,28 @@ from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
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):
|
||||
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Fence')
|
||||
|
||||
_Fence(obj)
|
||||
Fence(obj)
|
||||
obj.Post = post
|
||||
obj.Base = path
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
_ViewProviderFence(obj.ViewObject)
|
||||
ViewProviderFence(obj.ViewObject)
|
||||
|
||||
hide(section)
|
||||
hide(post)
|
||||
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()
|
||||
return obj
|
||||
|
||||
@@ -83,16 +87,8 @@ def hide(obj):
|
||||
if hasattr(obj, 'ViewObject') and obj.ViewObject:
|
||||
obj.ViewObject.Visibility = False
|
||||
|
||||
def getAngle(Line1, Line2):
|
||||
v1 = Line1.Vertexes[1].Point - Line1.Vertexes[0].Point
|
||||
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???
|
||||
"""
|
||||
def get_parameter_from_v0_old(edge, offset):
|
||||
""" Return parameter at distance offset from edge.Vertexes[0].sb method in Part.TopoShapeEdge??? """
|
||||
import DraftVecUtils
|
||||
|
||||
lpt = edge.valueAt(edge.getParameterByLength(0))
|
||||
@@ -106,14 +102,16 @@ def get_parameter_from_v0(edge, offset):
|
||||
length = offset
|
||||
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):
|
||||
"""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.Rotation = globalRotation
|
||||
placement.move(RefPt + xlate)
|
||||
@@ -121,55 +119,23 @@ def calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal
|
||||
if not align:
|
||||
return placement
|
||||
|
||||
# unit +Z Probably defined elsewhere?
|
||||
z = FreeCAD.Vector(0, 0, 1)
|
||||
# y = FreeCAD.Vector(0, 1, 0) # unit +Y
|
||||
x = FreeCAD.Vector(1, 0, 0) # unit +X
|
||||
nullv = FreeCAD.Vector(0, 0, 0)
|
||||
t = edge.tangentAt(get_parameter_from_v0(edge, offset)).normalize()
|
||||
n = normal or FreeCAD.Vector(0, 0, 1)
|
||||
b = t.cross(n).normalize()
|
||||
|
||||
# get local coord system - tangent, normal, binormal, if possible
|
||||
t = edge.tangentAt(get_parameter_from_v0(edge, offset))
|
||||
t.normalize()
|
||||
n = normal
|
||||
b = t.cross(n)
|
||||
b.normalize()
|
||||
# Asegurar sistema de coordenadas derecho
|
||||
if n.dot(t.cross(b)) < 0:
|
||||
b = -b
|
||||
|
||||
lnodes = z.cross(b)
|
||||
try:
|
||||
# Can't normalize null vector.
|
||||
lnodes.normalize()
|
||||
except:
|
||||
# pathological cases:
|
||||
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])
|
||||
# Construir matriz
|
||||
rotation_matrix = FreeCAD.Matrix(
|
||||
t.x, b.x, n.x, 0,
|
||||
t.y, b.y, n.y, 0,
|
||||
t.z, b.z, n.z, 0,
|
||||
0, 0, 0, 1
|
||||
)
|
||||
|
||||
placement.Rotation = FreeCAD.Rotation(rotation_matrix)
|
||||
return placement
|
||||
|
||||
def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
|
||||
@@ -183,12 +149,8 @@ def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
|
||||
import DraftGeomUtils
|
||||
|
||||
closedpath = DraftGeomUtils.isReallyClosed(pathwire)
|
||||
normal = DraftGeomUtils.getNormal(pathwire)
|
||||
if normal:
|
||||
if normal.z < 0: # asegurarse de que siempre se dibuje por encima del suelo
|
||||
normal.z *= -1
|
||||
else:
|
||||
normal = FreeCAD.Vector(0, 0, 1)
|
||||
|
||||
normal = FreeCAD.Vector(0, 0, 1)
|
||||
path = Part.__sortEdges__(pathwire.Edges)
|
||||
ends = []
|
||||
cdist = 0
|
||||
@@ -241,7 +203,7 @@ def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
|
||||
|
||||
return placements
|
||||
|
||||
class _Fence(ArchComponent.Component):
|
||||
class Fence(ArchComponent.Component):
|
||||
def __init__(self, obj):
|
||||
ArchComponent.Component.__init__(self, 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"))
|
||||
obj.setEditorMode("Length", 1)
|
||||
|
||||
|
||||
self.Type = "PVPlatFence"
|
||||
|
||||
def __getstate__(self):
|
||||
@@ -361,75 +322,30 @@ class _Fence(ArchComponent.Component):
|
||||
return None
|
||||
|
||||
def execute(self, obj):
|
||||
if not obj.Base or not obj.Post:
|
||||
return
|
||||
|
||||
# 1. Preparar trazado base
|
||||
pathwire = self.calculatePathWire(obj)
|
||||
if pathwire is None:
|
||||
# FreeCAD.Console.PrintLog("ArchFence.execute: path " + obj.Base.Name + " has no edges\n")
|
||||
return
|
||||
|
||||
if not obj.Post:
|
||||
FreeCAD.Console.PrintLog("ArchFence.execute: Post not set\n")
|
||||
pathwire = utils.getProjected(pathwire, FreeCAD.Vector(0, 0, 1))
|
||||
pathwire = utils.simplifyWire(pathwire)
|
||||
if not pathwire or not pathwire.Edges:
|
||||
return
|
||||
|
||||
# 2. Proyectar sobre terreno (con caché)
|
||||
self.Posts = []
|
||||
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:
|
||||
land = site.Terrain.Shape
|
||||
pathwire = land.makeParallelProjection(pathwire, FreeCAD.Vector(0, 0, 1))
|
||||
site = PVPlantSite.get()
|
||||
segments = mp.projectShapeOnMesh(pathwire, site.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))
|
||||
points=[]
|
||||
for segment in segments:
|
||||
points.extend(segment)
|
||||
pathwire = Part.makePolygon(points)
|
||||
|
||||
if pathwire is None:
|
||||
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
|
||||
postLength = 0 #obj.Post.Diameter.Value #considerarlo 0 porque no influye
|
||||
postPlacements = []
|
||||
@@ -457,18 +373,19 @@ class _Fence(ArchComponent.Component):
|
||||
|
||||
postPlacements.extend(placements)
|
||||
|
||||
# 5. Generar geometría
|
||||
postShapes, postFoundation = self.calculatePosts(obj, postPlacements)
|
||||
sections, num = self.calculateSections(obj, postPlacements)
|
||||
mesh = self.calculate_sections(obj, postPlacements)
|
||||
|
||||
postShapes = Part.makeCompound(postShapes)
|
||||
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.NumberOfPosts = obj.NumberOfSections + 1
|
||||
obj.NumberOfPosts = count + 1
|
||||
obj.Length = pathLength
|
||||
obj.Concrete = count * postFoundation.SubShapes[0].Volume
|
||||
|
||||
@@ -497,7 +414,7 @@ class _Fence(ArchComponent.Component):
|
||||
|
||||
def calculatePostPlacements(self, obj, pathwire, rotation):
|
||||
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)
|
||||
# The placement of the last object is always the second entry in the list.
|
||||
# So we move it to the end:
|
||||
@@ -509,47 +426,36 @@ class _Fence(ArchComponent.Component):
|
||||
posts = []
|
||||
foundations = []
|
||||
for placement in postPlacements:
|
||||
postCopy = obj.Post.Shape.copy()
|
||||
postCopy = Part.Solid(postCopy)
|
||||
postCopy.Placement = placement
|
||||
postCopy.Placement.Base.z += 100
|
||||
posts.append(postCopy)
|
||||
new_post = obj.Post.Shape.copy()
|
||||
new_post = Part.Solid(new_post)
|
||||
new_post.Placement = placement
|
||||
new_post.Placement.Base.z += 100
|
||||
posts.append(new_post)
|
||||
|
||||
foundation = Part.makeCylinder(150, 700)
|
||||
foundation.Placement = placement
|
||||
foundation.Placement.Base.z -= obj.Depth.Value
|
||||
foundation = foundation.cut(postCopy)
|
||||
#foundation = foundation.cut(new_post)
|
||||
foundations.append(foundation)
|
||||
|
||||
return posts, foundations
|
||||
|
||||
def calculateSections(self, obj, postPlacements):
|
||||
shapes = []
|
||||
faceNumbers = []
|
||||
def calculate_sections(self, obj, postPlacements):
|
||||
offsetz = FreeCAD.Vector(0, 0, obj.MeshOffsetZ.Value)
|
||||
meshHeight = FreeCAD.Vector(0, 0, obj.MeshHeight.Value)
|
||||
|
||||
offsetz = obj.MeshOffsetZ.Value
|
||||
meshHeight = obj.MeshHeight.Value
|
||||
points_down = []
|
||||
points_up = []
|
||||
for i in range(len(postPlacements) - 1):
|
||||
startPlacement = postPlacements[i]
|
||||
endPlacement = postPlacements[i + 1]
|
||||
p1 = postPlacements[i].Base + offsetz
|
||||
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)
|
||||
p2 = endPlacement.Base + FreeCAD.Vector(0, 0, offsetz)
|
||||
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)
|
||||
shape = Part.makeRuledSurface(Part.makePolygon(points_down), Part.makePolygon(points_up))
|
||||
return shape
|
||||
|
||||
def calculatePathWire(self, obj):
|
||||
if obj.Base:
|
||||
@@ -562,7 +468,7 @@ class _Fence(ArchComponent.Component):
|
||||
return None
|
||||
|
||||
|
||||
class _ViewProviderFence(ArchComponent.ViewProviderComponent):
|
||||
class ViewProviderFence(ArchComponent.ViewProviderComponent):
|
||||
"A View Provider for the Fence object"
|
||||
|
||||
def __init__(self, vobj):
|
||||
@@ -642,7 +548,7 @@ class _ViewProviderFence(ArchComponent.ViewProviderComponent):
|
||||
children.append(self.Object.Gate)
|
||||
return children
|
||||
|
||||
class _FenceTaskPanel:
|
||||
class FenceTaskPanel:
|
||||
'''The TaskPanel to setup the fence'''
|
||||
|
||||
def __init__(self):
|
||||
@@ -775,15 +681,8 @@ class _FenceTaskPanel:
|
||||
self.form = [self.formFence, self.formPost, self.formFoundation]
|
||||
|
||||
# valores iniciales y creación del la valla:
|
||||
import Draft
|
||||
self.post = PVPlantFencePost.makeFencePost() # Arch.makePipe()
|
||||
self.post = PVPlantFencePost.makeFencePost()
|
||||
self.post.Label = "Post"
|
||||
Draft.autogroup(self.post)
|
||||
|
||||
'''
|
||||
self.section = self.makeGrid()
|
||||
self.path = self.section.Base
|
||||
'''
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
self.fence = makePVPlantFence(self.section, self.post, self.path)
|
||||
@@ -859,7 +758,7 @@ class CommandPVPlantFence:
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
def Activated(self):
|
||||
self.TaskPanel = _FenceTaskPanel()
|
||||
self.TaskPanel = FenceTaskPanel()
|
||||
FreeCADGui.Control.showDialog(self.TaskPanel)
|
||||
|
||||
|
||||
@@ -877,12 +776,13 @@ if FreeCAD.GuiUp:
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
site = FreeCAD.ActiveDocument.getObject("Site")
|
||||
return (not (FreeCAD.ActiveDocument is None) and
|
||||
not (FreeCAD.ActiveDocument.getObject("Site") is None) and
|
||||
not (FreeCAD.ActiveDocument.getObject("Terrain") is None))
|
||||
not (site is None) and
|
||||
not (site.Terrain is None))
|
||||
|
||||
import PVPlantFenceGate
|
||||
import Civil.Fence.PVPlantFenceGate as PVPlantFenceGate
|
||||
FreeCADGui.addCommand('PVPlantFence', CommandPVPlantFence())
|
||||
FreeCADGui.addCommand('PVPlantGate', PVPlantFenceGate._CommandPVPlantGate())
|
||||
FreeCADGui.addCommand('PVPlantFencePost', PVPlantFencePost._CommandFencePost())
|
||||
FreeCADGui.addCommand('PVPlantGate', PVPlantFenceGate.CommandPVPlantGate())
|
||||
FreeCADGui.addCommand('PVPlantFencePost', PVPlantFencePost.CommandFencePost())
|
||||
#FreeCADGui.addCommand('PVPlantFenceGroup', CommandFenceGroup())
|
||||
@@ -202,7 +202,7 @@ class ViewProviderGate:
|
||||
children.append(self.Object.Base)
|
||||
return children
|
||||
|
||||
class _CommandPVPlantGate:
|
||||
class CommandPVPlantGate:
|
||||
"the PVPlant Fence command definition"
|
||||
|
||||
def __init__(self):
|
||||
@@ -256,7 +256,9 @@ class _CommandPVPlantGate:
|
||||
gate = makePVPlantFence()
|
||||
try:
|
||||
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:
|
||||
FreeCAD.Console.PrintError("No se puede encontrar punto 3D.." + "\n")
|
||||
@@ -1,5 +1,6 @@
|
||||
import ArchComponent
|
||||
import FreeCAD
|
||||
import Part
|
||||
import ArchComponent
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
@@ -9,8 +10,6 @@ else:
|
||||
# \cond
|
||||
def translate(ctxt, txt):
|
||||
return txt
|
||||
|
||||
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||
return txt
|
||||
# \endcond
|
||||
@@ -21,20 +20,14 @@ except AttributeError:
|
||||
def _fromUtf8(s):
|
||||
return s
|
||||
|
||||
|
||||
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
|
||||
def makeFencePost(diameter=48, length=3000, placement=None, name="FencePost"):
|
||||
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
|
||||
obj.Label = name
|
||||
_FencePost(obj)
|
||||
FencePost(obj)
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
_ViewProviderFencePost(obj.ViewObject)
|
||||
ViewProviderFencePost(obj.ViewObject)
|
||||
|
||||
obj.Length = length
|
||||
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"):
|
||||
"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.Label = name
|
||||
_FenceReinforcePostPost(obj)
|
||||
FenceReinforcePost(obj)
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
_ViewProviderFencePost(obj.ViewObject)
|
||||
ViewProviderFencePost(obj.ViewObject)
|
||||
|
||||
obj.Length = length
|
||||
obj.Diameter = diameter
|
||||
@@ -66,7 +54,7 @@ def makeFenceReinforcePost(diameter=48, length=3000, placement=None, name="Post"
|
||||
return obj
|
||||
|
||||
|
||||
class _FencePost(ArchComponent.Component):
|
||||
class FencePost(ArchComponent.Component):
|
||||
def __init__(self, obj):
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
self.setProperties(obj)
|
||||
@@ -80,10 +68,10 @@ class _FencePost(ArchComponent.Component):
|
||||
obj.addProperty("App::PropertyLength", "Diameter", "Pipe",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The diameter of this pipe, if not based on a profile")
|
||||
).Diameter = 48
|
||||
if not "Thickness" in pl:
|
||||
'''if not "Thickness" in pl:
|
||||
obj.addProperty("App::PropertyLength", "Thickness", "Pipe",
|
||||
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:
|
||||
obj.addProperty("App::PropertyLength", "Length", "Pipe",
|
||||
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)
|
||||
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):
|
||||
import Part
|
||||
pl = obj.Placement
|
||||
|
||||
if obj.CloneOf:
|
||||
obj.Shape = obj.CloneOf.Shape
|
||||
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
|
||||
lip_heigth = 20
|
||||
radius = obj.Diameter.Value / 2
|
||||
|
||||
# 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
|
||||
|
||||
# ------------------------- 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)
|
||||
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):
|
||||
class FenceReinforcePost(ArchComponent.Component):
|
||||
def __init__(self, obj):
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
self.setProperties(obj)
|
||||
@@ -199,10 +150,18 @@ class _FenceReinforcePost(ArchComponent.Component):
|
||||
self.setProperties(obj)
|
||||
|
||||
def execute(self, obj):
|
||||
|
||||
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:
|
||||
# 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)
|
||||
@@ -244,7 +203,7 @@ class _FenceReinforcePost(ArchComponent.Component):
|
||||
return w
|
||||
|
||||
|
||||
class _ViewProviderFencePost(ArchComponent.ViewProviderComponent):
|
||||
class ViewProviderFencePost(ArchComponent.ViewProviderComponent):
|
||||
"A View Provider for the Pipe object"
|
||||
|
||||
def __init__(self, vobj):
|
||||
@@ -254,7 +213,7 @@ class _ViewProviderFencePost(ArchComponent.ViewProviderComponent):
|
||||
return ":/icons/Arch_Pipe_Tree.svg"
|
||||
|
||||
|
||||
class _CommandFencePost:
|
||||
class CommandFencePost:
|
||||
"the Arch Pipe command definition"
|
||||
|
||||
def GetResources(self):
|
||||
@@ -269,17 +228,5 @@ class _CommandFencePost:
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
def Activated(self):
|
||||
if True:
|
||||
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()
|
||||
makeFencePost()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('FencePost', _CommandFencePost())
|
||||
200
Electrical/PowerConverter/PowerConverter.py
Normal file
200
Electrical/PowerConverter/PowerConverter.py
Normal 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
392
Electrical/group.py
Normal 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)
|
||||
@@ -1,5 +1,6 @@
|
||||
import math
|
||||
import FreeCAD
|
||||
import Part
|
||||
from Utils.PVPlantUtils import findObjects
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
@@ -75,8 +76,10 @@ def getWire(wire, nospline=False, width=.0):
|
||||
import DraftGeomUtils
|
||||
import math
|
||||
|
||||
offset = FreeCAD.ActiveDocument.Site.Origin
|
||||
|
||||
def fmt(vec, b=0.0):
|
||||
return (vec.x * 0.001, vec.y * 0.001, width, width, b)
|
||||
return ((vec.x + offset.x) * 0.001, (vec.y + offset.y) * 0.001, width, width, b)
|
||||
|
||||
points = []
|
||||
edges = Part.__sortEdges__(wire.Edges)
|
||||
@@ -259,15 +262,60 @@ class exportDXF:
|
||||
'rotation': rotation
|
||||
})
|
||||
|
||||
def createPolyline(self, wire):
|
||||
def createPolyline(self, wire, layer=""):
|
||||
try:
|
||||
data = getWire(wire.Shape)
|
||||
lwp = self.msp.add_lwpolyline(data)
|
||||
if layer:
|
||||
lwp.dxf.layer = layer
|
||||
return lwp
|
||||
except Exception as e:
|
||||
print("Error creating polyline:", e)
|
||||
return None
|
||||
|
||||
def createHatch(self, wire, pattern="SOLID", scale=1.0, angle=0, layer=None):
|
||||
"""Crea un sombreado (hatch) para un área"""
|
||||
try:
|
||||
# Obtener los puntos en metros
|
||||
points = [(x, y) for (x, y, *_) in wire]
|
||||
|
||||
# Crear el hatch
|
||||
hatch = self.msp.add_hatch(color=7, dxfattribs={'layer': layer})
|
||||
|
||||
if pattern == "SOLID":
|
||||
# Sombreado sólido
|
||||
hatch.set_solid_fill()
|
||||
else:
|
||||
# Patrón de sombreado
|
||||
hatch.set_pattern_fill(name=pattern, scale=scale, angle=angle)
|
||||
|
||||
# Añadir el contorno
|
||||
hatch.paths.add_polyline_path(points, is_closed=True)
|
||||
return hatch
|
||||
except Exception as e:
|
||||
print("Error creating hatch:", e)
|
||||
return None
|
||||
|
||||
def export_feature_image(self, feature, layer_name):
|
||||
"""Exporta una imagen del GeoFeature y la añade al DXF"""
|
||||
try:
|
||||
# Añadir la imagen al DXF
|
||||
image_def = self.doc.add_image_def(feature.ImageFile, size_in_pixel=(feature.XSize, feature.YSize))
|
||||
self.msp.add_image(image_def,
|
||||
insert=(0, 0, 0),
|
||||
size_in_units=(feature.XSize,
|
||||
feature.YSize),
|
||||
rotation=0,
|
||||
dxfattribs={'layer': layer_name})
|
||||
|
||||
print(f"Imagen exportada para {feature.Label}")
|
||||
|
||||
# Eliminar el archivo temporal
|
||||
import os
|
||||
os.unlink(temp_img.name)
|
||||
except Exception as e:
|
||||
print(f"Error en exportación de imagen: {e}")
|
||||
|
||||
# =================================================================================
|
||||
# INTERFAZ DE USUARIO
|
||||
# =================================================================================
|
||||
@@ -575,6 +623,59 @@ class LineTypeComboBox(QtWidgets.QComboBox):
|
||||
finally:
|
||||
painter.end() # Asegurar que el painter se cierre correctamente
|
||||
|
||||
layers = [
|
||||
("Available area", QtGui.QColor(0, 204, 153), "Continuous", "1", True),
|
||||
("Available area Names", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
|
||||
("Areas Exclusion", QtGui.QColor(255, 85, 0), "Continuous", "1", True),
|
||||
("Areas Exclusion Offset", QtGui.QColor(255, 85, 0), "Continuous", "1", True),
|
||||
("Areas Exclusion Name", QtGui.QColor(255, 85, 0), "Continuous", "1", True),
|
||||
("Areas Cadastral Plot", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("Areas Cadastral Plot Name", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("Areas Offset", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
|
||||
("Cable codes LV AC inverter", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("Cable codes LV string", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("Cable codes MV System", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("CABLES LV AC inverter 120 mm2", QtGui.QColor(255, 204, 0), "Continuous", "1", True),
|
||||
("CABLES LV AC inverter 185 mm2", QtGui.QColor(204, 153, 0), "Continuous", "1", True),
|
||||
("CABLES LV string 4 mm2", QtGui.QColor(255, 255, 0), "Continuous", "1", True),
|
||||
("CABLES LV string 10 mm2", QtGui.QColor(255, 255, 102), "Continuous", "1", True),
|
||||
("CABLES MV system 300 mm2", QtGui.QColor(102, 51, 0), "Continuous", "1", True),
|
||||
|
||||
("CIVIL Fence", QtGui.QColor(102, 102, 102), "FENCELINE1", "1", True),
|
||||
("CIVIL External Roads", QtGui.QColor(91, 91, 91), "Continuous", "1", True),
|
||||
("CIVIL External Roads Axis", QtGui.QColor(255, 255, 192), "Dashed", "1", True),
|
||||
("CIVIL External Roads Text", QtGui.QColor(255, 255, 192), "Continuous", "1", True),
|
||||
("CIVIL Internal Roads", QtGui.QColor(153, 95, 76), "Continuous", "1", True),
|
||||
("CIVIL Internal Roads Axis", QtGui.QColor(192, 192, 192), "Dashed", "1", True),
|
||||
("CIVIL Internal Roads Text", QtGui.QColor(192, 192, 192), "Continuous", "1", True),
|
||||
|
||||
("Contour Line Legend text", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("Major contour line", QtGui.QColor(0, 0, 0), "Continuous", "1", True),
|
||||
("Major contour value", QtGui.QColor(0, 0, 0), "Continuous", "1", True),
|
||||
("Minor contour line", QtGui.QColor(128, 128, 128), "Continuous", "1", True),
|
||||
("Minor contour value", QtGui.QColor(128, 128, 128), "Continuous", "1", True),
|
||||
("Power Stations", QtGui.QColor(255, 0, 0), "Continuous", "1", True),
|
||||
("Power Stations Names", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("ST", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("ST Names", QtGui.QColor(255, 255, 0), "Continuous", "1", True),
|
||||
("String Inv", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("STRUC Structure 1", QtGui.QColor(0, 0, 255), "Continuous", "1", True),
|
||||
("STRUC Structure 2", QtGui.QColor(0, 0, 204), "Continuous", "1", True),
|
||||
("STRUC Structure 3", QtGui.QColor(0, 0, 153), "Continuous", "1", True),
|
||||
("STRUC Structure 4", QtGui.QColor(0, 0, 128), "Continuous", "1", True),
|
||||
("STRUC Structure 5", QtGui.QColor(0, 0, 102), "Continuous", "1", True),
|
||||
("STRUC Structure 6", QtGui.QColor(0, 0, 76), "Continuous", "1", True),
|
||||
("STRUC Structure 7", QtGui.QColor(0, 0, 51), "Continuous", "1", True),
|
||||
("STRUC Structure 8", QtGui.QColor(0, 0, 25), "Continuous", "1", True),
|
||||
("Structure Codes", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("TRENCHES Low voltage 400.0 x 1000.0 m", QtGui.QColor(128, 128, 128), "Continuous", "1", True),
|
||||
("TRENCHES Medium voltage 400.0 x 1000.", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("TRENCHES Medium voltage 800.0 x 1000.", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
("TRENCHES Medium voltage 800.0 x 1500.", QtGui.QColor(255, 255, 255), "Continuous", "1", True),
|
||||
]
|
||||
|
||||
|
||||
class _PVPlantExportDXF(QtGui.QWidget):
|
||||
'''The editmode TaskPanel to select what you want to export'''
|
||||
@@ -610,10 +711,8 @@ class _PVPlantExportDXF(QtGui.QWidget):
|
||||
self.form.tableLayers.removeRow(row)
|
||||
|
||||
# Configuración de las capas por defecto
|
||||
self.add_row("Areas_Boundary", QtGui.QColor(0, 125, 125), "FENCELINE1", "1", True)
|
||||
self.add_row("Areas_Exclusion", QtGui.QColor(255, 0, 0), "CONTINUOUS", "1", True)
|
||||
self.add_row("Internal_Roads", QtGui.QColor(128, 128, 128), "CONTINUOUS", "1", True)
|
||||
self.add_row("Frames", QtGui.QColor(0, 255, 0), "CONTINUOUS", "1", True)
|
||||
for row in layers:
|
||||
self.add_row(*row)
|
||||
|
||||
def save_project_settings(self):
|
||||
"""Guarda la configuración actual en el proyecto de FreeCAD"""
|
||||
@@ -778,25 +877,28 @@ class _PVPlantExportDXF(QtGui.QWidget):
|
||||
)
|
||||
|
||||
def writeArea(self, exporter):
|
||||
exporter.createPolyline(FreeCAD.ActiveDocument.Site.Boundary, "boundary")
|
||||
|
||||
areas_types = ["Boundaries", "Exclusions", "Offsets"]
|
||||
exporter.createPolyline(FreeCAD.ActiveDocument.Site.Boundary, "Available area")
|
||||
areas_types = [("Boundaries", "Available area"),
|
||||
("CadastralPlots", "Areas Cadastral Plot"),
|
||||
("Exclusions", "Areas Exclusion"),
|
||||
("Offsets", "Areas Offset")]
|
||||
for area_type in areas_types:
|
||||
if hasattr(FreeCAD.ActiveDocument, area_type):
|
||||
for area in FreeCAD.ActiveDocument.Boundaries.Group:
|
||||
exporter.createPolyline(area, "Areas_Boundary")
|
||||
if hasattr(FreeCAD.ActiveDocument, area_type[0]):
|
||||
area_group = FreeCAD.ActiveDocument.getObjectsByLabel(area_type[0])
|
||||
if len(area_group):
|
||||
for area in area_group[0].Group:
|
||||
tmp = exporter.createPolyline(area, area_type[1])
|
||||
if area_type[0] == "Exclusions":
|
||||
exporter.createHatch(
|
||||
tmp,
|
||||
pattern="ANSI37",
|
||||
scale=0.3,
|
||||
angle=0,
|
||||
layer=area_type[1]
|
||||
)
|
||||
for obj in FreeCADGui.Selection.getSelection():
|
||||
tmp = exporter.createPolyline(obj, areas_types[0][1])
|
||||
|
||||
'''for area in FreeCAD.ActiveDocument.Boundaries.Group:
|
||||
pol = exporter.createPolyline(area)
|
||||
pol.dxf.layer = "Areas_Boundary"
|
||||
|
||||
for area in FreeCAD.ActiveDocument.Exclusions.Group:
|
||||
pol = exporter.createPolyline(area)
|
||||
pol.dxf.layer = "Areas_Exclusion"
|
||||
|
||||
for area in FreeCAD.ActiveDocument.Offsets.Group:
|
||||
pol = exporter.createPolyline(area)
|
||||
pol.dxf.layer = "Areas_Offsets"'''
|
||||
|
||||
def writeFrameSetups(self, exporter):
|
||||
if not hasattr(FreeCAD.ActiveDocument, "Site"):
|
||||
@@ -811,11 +913,11 @@ class _PVPlantExportDXF(QtGui.QWidget):
|
||||
w.Placement.Base = w.Placement.Base.sub(center)
|
||||
block.add_lwpolyline(getWire(w))
|
||||
|
||||
block.add_circle((0, 0), 0.2, dxfattribs={'color': 2})
|
||||
block.add_circle((0, 0), 0.2, dxfattribs={'layer': 'Structure Posts'}) #'color': 2,
|
||||
p = math.sin(math.radians(45)) * 0.2
|
||||
|
||||
block.add_line((-p, -p), (p, p), dxfattribs={"layer": "MyLines"})
|
||||
block.add_line((-p, p), (p, -p), dxfattribs={"layer": "MyLines"})
|
||||
block.add_line((-p, -p), (p, p), dxfattribs={'layer': 'Structure Posts'})
|
||||
block.add_line((-p, p), (p, -p), dxfattribs={'layer': 'Structure Posts'})
|
||||
|
||||
# 2. Frames
|
||||
for ts in FreeCAD.ActiveDocument.Site.Frames:
|
||||
@@ -868,10 +970,10 @@ class _PVPlantExportDXF(QtGui.QWidget):
|
||||
|
||||
if FreeCAD.ActiveDocument.Transport:
|
||||
for road in FreeCAD.ActiveDocument.Transport.Group:
|
||||
base = exporter.createPolyline(road, "External_Roads")
|
||||
base = exporter.createPolyline(road, "CIVIL External Roads")
|
||||
base.dxf.const_width = road.Width
|
||||
|
||||
axis = exporter.createPolyline(road.Base, "External_Roads_Axis")
|
||||
axis = exporter.createPolyline(road, "CIVIL External Roads Axis")
|
||||
axis.dxf.const_width = .2
|
||||
|
||||
def writeTrenches(self, exporter):
|
||||
@@ -976,8 +1078,12 @@ class _PVPlantExportDXF(QtGui.QWidget):
|
||||
self.writeTrenches(exporter)
|
||||
|
||||
# Crear espacios de papel
|
||||
self.setup_layout4(exporter.doc)
|
||||
self.createPaperSpaces(exporter)
|
||||
#self.setup_layout4(exporter.doc)
|
||||
#self.createPaperSpaces(exporter)
|
||||
|
||||
if hasattr(FreeCAD.ActiveDocument, "Background"):
|
||||
# Exportar como imagen en lugar de polilínea
|
||||
exporter.export_feature_image(FreeCAD.ActiveDocument.Background, "Site_Image")
|
||||
|
||||
# Guardar archivo
|
||||
exporter.save()
|
||||
|
||||
@@ -29,6 +29,15 @@ import Part
|
||||
import numpy
|
||||
import os
|
||||
|
||||
from xml.etree.ElementTree import Element, SubElement
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
import datetime
|
||||
from xml.dom import minidom
|
||||
|
||||
from numpy.matrixlib.defmatrix import matrix
|
||||
|
||||
from Utils import PVPlantUtils as utils
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore
|
||||
@@ -63,6 +72,11 @@ def check_collada():
|
||||
FreeCAD.Console.PrintError(translate("PVPlant", "pycollada no encontrado, soporte Collada desactivado.") + "\n")
|
||||
return COLLADA_AVAILABLE
|
||||
|
||||
# Asegurar que el texto es Unicode válido
|
||||
def safe_text(text):
|
||||
if isinstance(text, bytes):
|
||||
return text.decode('utf-8', errors='replace')
|
||||
return text
|
||||
|
||||
# from ARCH:
|
||||
def triangulate(shape):
|
||||
@@ -249,7 +263,306 @@ def export(exportList, filename, tessellation=1, colors=None):
|
||||
FreeCAD.Console.PrintMessage(translate("Arch", "file %s successfully created.") % filename)
|
||||
|
||||
|
||||
def exportToPVC(path, exportTerrain = False):
|
||||
def exportToPVC(path, exportTerrain=False):
|
||||
filename = f"{path}.pvc"
|
||||
|
||||
# 1. Validación inicial de objetos esenciales
|
||||
site = None
|
||||
for obj in FreeCAD.ActiveDocument.Objects:
|
||||
if obj.Name.startswith("Site") and hasattr(obj, 'Terrain'):
|
||||
site = obj
|
||||
break
|
||||
|
||||
if not site:
|
||||
FreeCAD.Console.PrintError("No se encontró objeto 'Site' válido\n")
|
||||
return False
|
||||
|
||||
# 2. Configuración de metadatos y autor
|
||||
generated_on = str(datetime.datetime.now())
|
||||
|
||||
try:
|
||||
author = FreeCAD.ActiveDocument.CreatedBy
|
||||
except (AttributeError, UnicodeEncodeError):
|
||||
author = "Unknown"
|
||||
|
||||
author = author.replace("<", "").replace(">", "")
|
||||
ver = FreeCAD.Version()
|
||||
appli = f"PVPlant for FreeCAD {ver[0]}.{ver[1]} build{ver[2]}"
|
||||
|
||||
# 3. Creación estructura XML base
|
||||
root = Element('COLLADA')
|
||||
root.set('xmlns', 'http://www.collada.org/2005/11/COLLADASchema')
|
||||
root.set('version', '1.4.1')
|
||||
|
||||
# 4. Sección <asset>
|
||||
asset = SubElement(root, 'asset')
|
||||
contrib = SubElement(asset, 'contributor')
|
||||
SubElement(contrib, 'author').text = safe_text(author)
|
||||
SubElement(contrib, 'authoring_tool').text = safe_text(appli)
|
||||
SubElement(asset, 'created').text = generated_on
|
||||
SubElement(asset, 'modified').text = generated_on
|
||||
SubElement(asset, 'title').text = safe_text(FreeCAD.ActiveDocument.Name)
|
||||
unit = SubElement(asset, 'unit')
|
||||
unit.set('name', 'meter')
|
||||
unit.set('meter', '1')
|
||||
|
||||
# 5. Materiales y efectos
|
||||
materials = ["Frames", "Tree_trunk", "Tree_crown", "Topography_mesh"]
|
||||
|
||||
# Library materials
|
||||
lib_materials = SubElement(root, 'library_materials')
|
||||
for i, name in enumerate(materials):
|
||||
mat = SubElement(lib_materials, 'material')
|
||||
mat.set('id', f'Material{i}')
|
||||
mat.set('name', name)
|
||||
SubElement(mat, 'instance_effect').set('url', f'#Material{i}-fx')
|
||||
|
||||
# Library effects
|
||||
lib_effects = SubElement(root, 'library_effects')
|
||||
for i, _ in enumerate(materials):
|
||||
effect = SubElement(lib_effects, 'effect')
|
||||
effect.set('id', f'Material{i}-fx')
|
||||
effect.set('name', f'Material{i}')
|
||||
profile = SubElement(effect, 'profile_COMMON')
|
||||
technique = SubElement(profile, 'technique')
|
||||
technique.set('sid', 'standard')
|
||||
lambert = SubElement(technique, 'lambert')
|
||||
|
||||
# Componentes del material
|
||||
color = SubElement(SubElement(lambert, 'emission'), 'color')
|
||||
color.set('sid', 'emission')
|
||||
color.text = '0.000000 0.000000 0.000000 1.000000'
|
||||
color = SubElement(SubElement(lambert, 'ambient'), 'color')
|
||||
color.set('sid', 'ambient')
|
||||
color.text = '0.200000 0.200000 0.200000 1.000000'
|
||||
color = SubElement(SubElement(lambert, 'diffuse'), 'color')
|
||||
color.set('sid', 'diffuse')
|
||||
color.text = '0.250000 0.500000 0.000000 1.000000'
|
||||
transparent = SubElement(lambert, 'transparent')
|
||||
transparent.set('opaque', 'RGB_ZERO')
|
||||
color = SubElement(transparent, 'color')
|
||||
color.set('sid', 'transparent')
|
||||
color.text = '0.000000 0.000000 0.000000 1.000000'
|
||||
value = SubElement(SubElement(lambert, 'transparency'), 'float')
|
||||
value.set('sid', 'transparency')
|
||||
value.text = '0.000000'
|
||||
|
||||
# 6. Geometrías
|
||||
lib_geometries = SubElement(root, 'library_geometries')
|
||||
|
||||
# 7. Escena visual
|
||||
lib_visual = SubElement(root, 'library_visual_scenes')
|
||||
visual_scene = SubElement(lib_visual, 'visual_scene')
|
||||
visual_scene.set('id', 'Scene') # cambiar a visual_scene_0
|
||||
visual_scene.set('name', 'Scene') # cambiar a Default visual scene
|
||||
|
||||
scene_node = SubElement(visual_scene, 'node')
|
||||
scene_node.set('id', 'node_0_id')
|
||||
scene_node.set('name', 'node_0_name')
|
||||
scene_node.set('sid', 'node_0_sid')
|
||||
|
||||
scene_matrix = SubElement(scene_node, 'matrix')
|
||||
scene_matrix.set('sid', 'matrix_0')
|
||||
scene_matrix.text = '1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000'
|
||||
|
||||
root_node = SubElement(scene_node, 'node')
|
||||
root_node.set('id', 'node_1_id')
|
||||
root_node.set('name', 'node_1_name')
|
||||
root_node.set('sid', 'node_1_sid')
|
||||
|
||||
# 8. Función para procesar geometrías
|
||||
def create_geometry(name, vindex, findex, material_id, objind=0, frame_data=None, isTracker = False, axis_line=None):
|
||||
"""Crea elementos COLLADA para una geometría"""
|
||||
# Source (vertices)
|
||||
source_mesh = SubElement(geom, 'mesh')
|
||||
source = SubElement(source_mesh, 'source')
|
||||
source.set('id', f'{name}-mesh_source')
|
||||
float_array = SubElement(source, 'float_array')
|
||||
float_array.set('id', f'{name}-float_array')
|
||||
float_array.set('count', str(len(vindex)))
|
||||
float_array.text = ' '.join(f'{v:.6f}' for v in vindex)
|
||||
|
||||
technique = SubElement(source, 'technique_common')
|
||||
accessor = SubElement(technique, 'accessor')
|
||||
accessor.set('count', str(len(vindex)))
|
||||
accessor.set('source', f'#{name}-float_array')
|
||||
accessor.set('stride', '3')
|
||||
for ax in ['X', 'Y', 'Z']:
|
||||
param = SubElement(accessor, 'param')
|
||||
param.set('name', ax)
|
||||
param.set('type', 'float')
|
||||
|
||||
# Vertices
|
||||
vertices = SubElement(source_mesh, 'vertices')
|
||||
vertices.set('id', f'{name}-vertices_source')
|
||||
vertices = SubElement(vertices, 'input')
|
||||
vertices.set('semantic', 'POSITION')
|
||||
vertices.set('source', f'#{name}-mesh_source')
|
||||
|
||||
# Triangles
|
||||
triangles = SubElement(source_mesh, 'triangles')
|
||||
triangles.set('count', '0')
|
||||
triangles.set('material', f'Material{material_id}')
|
||||
triangles_input = SubElement(triangles, 'input')
|
||||
triangles_input.set('offset', '0')
|
||||
triangles_input.set('semantic', 'VERTEX')
|
||||
triangles_input.set('source', f'#{name}-vertices_source')
|
||||
|
||||
p = SubElement(triangles, 'p')
|
||||
p.text = ' '.join(map(str, findex))
|
||||
|
||||
# Parámetros especiales para estructuras
|
||||
|
||||
frame_params = SubElement(source_mesh, 'tracker_parameters')
|
||||
if frame_data:
|
||||
for key, val in frame_data.items():
|
||||
elem = SubElement(frame_params, key)
|
||||
elem.text = str(val)
|
||||
|
||||
if isTracker:
|
||||
axis_parameter = SubElement(frame_params, 'axis_vertices')
|
||||
if axis_line:
|
||||
for idx, vert in enumerate(axis_line):
|
||||
array = SubElement(axis_parameter, 'float_array')
|
||||
array.set('id', f'{name}-axis_float_array{idx}')
|
||||
array.set('count', '3')
|
||||
array.text = ' '.join(f'{v:.6f}' for v in vert)
|
||||
|
||||
# 9. Procesar estructuras (frames/trackers)
|
||||
center = FreeCAD.Vector()
|
||||
if site.Terrain:
|
||||
center = site.Terrain.Mesh.BoundBox.Center
|
||||
|
||||
objind = 0
|
||||
for frame_type in site.Frames:
|
||||
is_tracker = "tracker" in frame_type.Proxy.Type.lower()
|
||||
|
||||
modules = frame_type.Shape.SubShapes[0].SubShapes[0]
|
||||
pts = []
|
||||
for i in range(4):
|
||||
pts.append(modules.BoundBox.getPoint(i))
|
||||
|
||||
new_shape = Part.Face(Part.makePolygon(pts))
|
||||
mesh = Mesh.Mesh(triangulate(new_shape))
|
||||
axis = Part.makeLine(FreeCAD.Vector(modules.BoundBox.XMin, 0, modules.BoundBox.ZMax),
|
||||
FreeCAD.Vector(modules.BoundBox.XMax, 0, modules.BoundBox.ZMax))
|
||||
|
||||
for obj in FreeCAD.ActiveDocument.Objects:
|
||||
if hasattr(obj, "Setup") and obj.Setup == frame_type:
|
||||
# Procesar geometría
|
||||
mesh.Placement = obj.getGlobalPlacement()
|
||||
axis.Placement = obj.getGlobalPlacement()
|
||||
|
||||
# Transformar vértices
|
||||
vindex = []
|
||||
for point in mesh.Points:
|
||||
adjusted = (point.Vector - center) * scale
|
||||
vindex.extend([
|
||||
-adjusted.x,
|
||||
adjusted.z,
|
||||
adjusted.y
|
||||
])
|
||||
|
||||
# Índices de caras
|
||||
findex = []
|
||||
for facet in mesh.Facets:
|
||||
findex.extend(facet.PointIndices)
|
||||
|
||||
# AXIS
|
||||
# TODO: revisar si es así:
|
||||
vaxis = []
|
||||
for vert in axis.Vertexes:
|
||||
adjusted = (vert.Point - center) * scale
|
||||
vaxis.append([
|
||||
-adjusted.x,
|
||||
adjusted.z,
|
||||
adjusted.y
|
||||
])
|
||||
|
||||
# Crear geometría COLLADA
|
||||
geom = SubElement(lib_geometries, 'geometry')
|
||||
geom.set('id', f'Frame_{objind}')
|
||||
|
||||
# Parámetros específicos de estructura
|
||||
frame_data = {
|
||||
'module_width': obj.Setup.ModuleWidth.Value,
|
||||
'module_height': obj.Setup.ModuleHeight.Value,
|
||||
'module_x_spacing': obj.Setup.ModuleColGap.Value,
|
||||
'module_y_spacing': obj.Setup.ModuleRowGap.Value,
|
||||
'module_name': 'Generic'
|
||||
}
|
||||
|
||||
if is_tracker:
|
||||
frame_data.update({
|
||||
'tracker_type': 'single_axis_trackers',
|
||||
'min_phi': obj.Setup.MinPhi.Value,
|
||||
'max_phi': obj.Setup.MaxPhi.Value,
|
||||
'min_theta': 0,
|
||||
'max_theta': 0
|
||||
})
|
||||
|
||||
create_geometry(
|
||||
name=f'Frame_{objind}',
|
||||
vindex=vindex,
|
||||
findex=findex,
|
||||
material_id=0,
|
||||
objind=objind,
|
||||
frame_data=frame_data,
|
||||
isTracker = is_tracker,
|
||||
axis_line=vaxis
|
||||
)
|
||||
|
||||
# Instancia en escena
|
||||
instance = SubElement(root_node, 'instance_geometry')
|
||||
instance.set('url', f'#Frame_{objind}')
|
||||
|
||||
bind_material = SubElement(instance, 'bind_material')
|
||||
technique_common = SubElement(bind_material, 'technique_common')
|
||||
instance_material = SubElement(technique_common, 'instance_material')
|
||||
instance_material.set('symbol', 'Material0')
|
||||
instance_material.set('target', '#Material0')
|
||||
|
||||
objind += 1
|
||||
|
||||
# 10. Procesar terreno si está habilitado
|
||||
if exportTerrain and site.Terrain:
|
||||
mesh = site.Terrain.Mesh
|
||||
vindex = []
|
||||
for point in mesh.Points:
|
||||
point = point.Vector
|
||||
vindex.extend([
|
||||
-point.x * SCALE,
|
||||
point.z * SCALE,
|
||||
point.y * SCALE
|
||||
])
|
||||
|
||||
findex = []
|
||||
for facet in mesh.Facets:
|
||||
findex.extend(facet.PointIndices)
|
||||
|
||||
geom = SubElement(lib_geometries, 'geometry')
|
||||
geom.set('id', 'Terrain')
|
||||
create_geometry('Terrain', vindex, findex, material_id=3)
|
||||
|
||||
instance = SubElement(root_node, 'instance_geometry')
|
||||
instance.set('url', '#Terrain')
|
||||
|
||||
# 11. Escena principal
|
||||
scene = SubElement(root, 'scene')
|
||||
SubElement(scene, 'instance_visual_scene').set('url', '#Scene')
|
||||
|
||||
# 12. Exportar a archivo
|
||||
xml_str = minidom.parseString(
|
||||
ElementTree.tostring(root, encoding='utf-8')
|
||||
).toprettyxml(indent=" ")
|
||||
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write(xml_str)
|
||||
|
||||
FreeCAD.Console.PrintMessage(f"Archivo PVC generado: {filename}\n")
|
||||
return True
|
||||
|
||||
def exportToPVC_old(path, exportTerrain = False):
|
||||
filename = f"{path}.pvc"
|
||||
|
||||
from xml.etree.ElementTree import Element, SubElement
|
||||
@@ -291,17 +604,18 @@ def exportToPVC(path, exportTerrain = False):
|
||||
|
||||
# xml: 1. Asset:
|
||||
asset = SubElement(root, 'asset')
|
||||
|
||||
asset_contributor = SubElement(asset, 'contributor')
|
||||
asset_contributor_autor = SubElement(asset_contributor, 'autor')
|
||||
#asset_contributor_autor.text = author
|
||||
asset_contributor_autor = SubElement(asset_contributor, 'author')
|
||||
asset_contributor_autor.text = author
|
||||
asset_contributor_authoring_tool = SubElement(asset_contributor, 'authoring_tool')
|
||||
#asset_contributor_authoring_tool.text = appli
|
||||
asset_contributor_authoring_tool.text = appli
|
||||
asset_contributor_comments = SubElement(asset_contributor, 'comments')
|
||||
asset_keywords = SubElement(asset, 'keywords')
|
||||
asset_revision = SubElement(asset, 'revision')
|
||||
asset_subject = SubElement(asset, 'subject')
|
||||
asset_tittle = SubElement(asset, 'title')
|
||||
#asset_tittle.text = FreeCAD.ActiveDocument.Name
|
||||
asset_tittle.text = FreeCAD.ActiveDocument.Name
|
||||
asset_unit = SubElement(asset, 'unit')
|
||||
asset_unit.set('meter', '0.001')
|
||||
asset_unit.set('name', 'millimeter')
|
||||
@@ -359,7 +673,6 @@ def exportToPVC(path, exportTerrain = False):
|
||||
# xml: 4. library_geometries:
|
||||
library_geometries = SubElement(root, 'library_geometries')
|
||||
def add_geometry(objtype, vindex, findex, objind = 0, centers = None):
|
||||
|
||||
isFrame = False
|
||||
if objtype == 0:
|
||||
geometryName = 'Frame'
|
||||
@@ -505,36 +818,20 @@ def exportToPVC(path, exportTerrain = False):
|
||||
end_time.text = '1.000000'
|
||||
|
||||
# xml: 6. scene:
|
||||
scene = SubElement(root, 'scene')
|
||||
'''scene = SubElement(root, 'scene')
|
||||
instance = SubElement(scene, 'instance_visual_scene')
|
||||
instance.set('url', '#')
|
||||
|
||||
full_list_of_objects = FreeCAD.ActiveDocument.Objects
|
||||
instance.set('url', '#')'''
|
||||
|
||||
# CASO 1 - FRAMES:
|
||||
frameType = site.Frames
|
||||
frame_setup = {"type": [],
|
||||
"footprint": []}
|
||||
for obj in frameType:
|
||||
frame_setup["type"] = obj
|
||||
frame_setup["footprint"] = ""
|
||||
|
||||
objind = 0
|
||||
|
||||
# TODO: revisar
|
||||
for typ in frameType:
|
||||
isTracker = "tracker" in typ.Proxy.Type.lower()
|
||||
isTracker = False
|
||||
|
||||
objectlist = FreeCAD.ActiveDocument.findObjects(Name="Tracker")
|
||||
tmp = []
|
||||
for obj in objectlist:
|
||||
if obj.Name.startswith("TrackerSetup"):
|
||||
continue
|
||||
else:
|
||||
tmp.append(obj)
|
||||
objectlist = tmp.copy()
|
||||
#isTracker = False
|
||||
|
||||
objectlist = utils.findObjects("Tracker")
|
||||
for obj in objectlist:
|
||||
if obj.Setup == typ:
|
||||
findex = numpy.array([])
|
||||
@@ -580,7 +877,6 @@ def exportToPVC(path, exportTerrain = False):
|
||||
v = Topology[0][i]
|
||||
vindex[list(range(i * 3, i * 3 + 3))] = (-(v.x - center.x) * scale, (v.z - center.z) * scale,
|
||||
(v.y - center.y) * scale)
|
||||
|
||||
# 2. face indices
|
||||
findex = numpy.empty(len(Topology[1]) * 3, numpy.int64)
|
||||
for i in range(len(Topology[1])):
|
||||
|
||||
@@ -42,11 +42,10 @@ class OSMImporter:
|
||||
}
|
||||
self.ssl_context = ssl.create_default_context(cafile=certifi.where())
|
||||
|
||||
def transform_from_latlon(self, lat, lon):
|
||||
point = ImportElevation.getElevationFromOE([[lat, lon], ])
|
||||
return FreeCAD.Vector(point[0].x, point[0].y, point[0].z) * scale - self.Origin
|
||||
'''x, y, _, _ = utm.from_latlon(lat, lon)
|
||||
return FreeCAD.Vector(x, y, .0) * scale - self.Origin'''
|
||||
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"""
|
||||
@@ -70,7 +69,7 @@ class OSMImporter:
|
||||
headers={'User-Agent': 'FreeCAD-OSM-Importer/1.0'},
|
||||
method='POST'
|
||||
)
|
||||
return urllib.request.urlopen(req, context=self.ssl_context, timeout=30).read()
|
||||
return urllib.request.urlopen(req, context=self.ssl_context, timeout=160).read()
|
||||
|
||||
def create_layer(self, name):
|
||||
if not FreeCAD.ActiveDocument.getObject(name):
|
||||
@@ -81,11 +80,16 @@ class OSMImporter:
|
||||
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'):
|
||||
@@ -162,11 +166,10 @@ class OSMImporter:
|
||||
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
|
||||
|
||||
print(data)
|
||||
|
||||
tags = data['tags']
|
||||
nodes = [self.nodes[ref] for ref in data['nodes'] if ref in self.nodes]
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ class PVPlantWorkbench(Workbench):
|
||||
"PVPlantBuilding",
|
||||
"PVPlantFenceGroup",
|
||||
]'''
|
||||
from Electrical.PowerConverter import PowerConverter
|
||||
self.electricalList = ["PVPlantStringBox",
|
||||
"PVPlantCable",
|
||||
"PVPlanElectricalLine",
|
||||
@@ -66,6 +67,8 @@ class PVPlantWorkbench(Workbench):
|
||||
"Stringing",
|
||||
"Separator",
|
||||
"StringInverter",
|
||||
"Separator",
|
||||
"PowerConverter"
|
||||
]
|
||||
|
||||
self.roads = ["PVPlantRoad",
|
||||
|
||||
@@ -540,6 +540,7 @@ def makeTrackerSetup(name="TrackerSetup"):
|
||||
pass
|
||||
return obj
|
||||
|
||||
|
||||
def getarray(array, numberofpoles):
|
||||
if len(array) == 0:
|
||||
newarray = [0] * numberofpoles
|
||||
@@ -568,6 +569,7 @@ def getarray(array, numberofpoles):
|
||||
newarray = [array[0]] * numberofpoles
|
||||
return newarray
|
||||
|
||||
|
||||
class TrackerSetup(FrameSetup):
|
||||
"A 1 Axis Tracker Obcject"
|
||||
|
||||
@@ -589,7 +591,7 @@ class TrackerSetup(FrameSetup):
|
||||
obj.addProperty("App::PropertyDistance",
|
||||
"MotorGap",
|
||||
"ModuleArray",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Thse height of this object")
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of this object")
|
||||
).MotorGap = 550
|
||||
|
||||
if not "UseGroupsOfModules" in pl:
|
||||
@@ -880,6 +882,9 @@ class TrackerSetup(FrameSetup):
|
||||
|
||||
def CalculatePosts(self, obj, totalh, totalw):
|
||||
# Temp: utilizar el uso de versiones:
|
||||
if len(obj.PoleType) == 0:
|
||||
return None, None
|
||||
|
||||
ver = 1
|
||||
if ver == 0:
|
||||
# versión 0:
|
||||
@@ -906,8 +911,8 @@ class TrackerSetup(FrameSetup):
|
||||
elif ver == 1:
|
||||
# versión 1:
|
||||
linetmp = Part.LineSegment(FreeCAD.Vector(0), FreeCAD.Vector(0, 10, 0)).toShape()
|
||||
compoundPoles = Part.makeCompound([])
|
||||
compoundAxis = Part.makeCompound([])
|
||||
compound_poles = Part.makeCompound([])
|
||||
compound_axis = Part.makeCompound([])
|
||||
|
||||
offsetX = - totalw / 2
|
||||
arrayDistance = obj.DistancePole
|
||||
@@ -915,15 +920,16 @@ class TrackerSetup(FrameSetup):
|
||||
arrayPost = obj.PoleSequence
|
||||
|
||||
for x in range(int(obj.NumberPole.Value)):
|
||||
postCopy = obj.PoleType[arrayPost[x]].Shape.copy()
|
||||
post_copy = obj.PoleType[arrayPost[x]].Shape.copy()
|
||||
offsetX += arrayDistance[x]
|
||||
postCopy.Placement.Base = FreeCAD.Vector(offsetX, 0, -(postCopy.BoundBox.ZLength - arrayAerial[x]))
|
||||
compoundPoles.add(postCopy)
|
||||
post_copy.Placement.Base = FreeCAD.Vector(offsetX, 0, -(post_copy.BoundBox.ZLength - arrayAerial[x]))
|
||||
compound_poles.add(post_copy)
|
||||
|
||||
axis = linetmp.copy()
|
||||
axis.Placement.Base = FreeCAD.Vector(offsetX, 0, arrayAerial[x])
|
||||
compoundAxis.add(axis)
|
||||
return compoundPoles, compoundAxis
|
||||
compound_axis.add(axis)
|
||||
|
||||
return compound_poles, compound_axis
|
||||
|
||||
def execute(self, obj):
|
||||
# obj.Shape: compound
|
||||
@@ -1029,14 +1035,14 @@ class Tracker(ArchComponent.Component):
|
||||
"AngleY",
|
||||
"Outputs",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of this object")
|
||||
).AngleX = 0
|
||||
).AngleY = 0
|
||||
|
||||
if not ("AngleZ" in pl):
|
||||
obj.addProperty("App::PropertyAngle",
|
||||
"AngleZ",
|
||||
"Outputs",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of this object")
|
||||
).AngleX = 0
|
||||
).AngleZ = 0
|
||||
|
||||
self.Type = "Tracker"
|
||||
#obj.Type = self.Type
|
||||
@@ -1056,12 +1062,15 @@ class Tracker(ArchComponent.Component):
|
||||
if prop.startswith("Angle"):
|
||||
base = obj.Placement.Base
|
||||
angles = obj.Placement.Rotation.toEulerAngles("XYZ")
|
||||
|
||||
# Actualizar rotación según el ángulo modificado
|
||||
if prop == "AngleX":
|
||||
rot = FreeCAD.Rotation(angles[2], angles[1], obj.AngleX.Value)
|
||||
elif prop == "AngleY":
|
||||
rot = FreeCAD.Rotation(angles[2], obj.AngleY.Value, angles[0])
|
||||
elif prop == "AngleZ":
|
||||
rot = FreeCAD.Rotation(obj.AngleZ.Value, angles[1], angles[0])
|
||||
|
||||
obj.Placement = FreeCAD.Placement(base, rot, FreeCAD.Vector(0,0,0))
|
||||
|
||||
if hasattr(FreeCAD.ActiveDocument, "FramesChecking"):
|
||||
@@ -1083,28 +1092,38 @@ class Tracker(ArchComponent.Component):
|
||||
# |-- PoleAxes: Edge
|
||||
|
||||
if obj.Setup is None:
|
||||
print("Warning: No Setup defined for tracker")
|
||||
return
|
||||
pl = obj.Placement
|
||||
shape = obj.Setup.Shape.copy()
|
||||
try:
|
||||
pl = obj.Placement
|
||||
shape = obj.Setup.Shape.copy()
|
||||
|
||||
p1 = shape.SubShapes[0].SubShapes[1].SubShapes[0].CenterOfMass
|
||||
p2 = min(shape.SubShapes[0].SubShapes[1].SubShapes[0].Faces, key=lambda face: face.Area).CenterOfMass
|
||||
axis = p1 - p2
|
||||
modules = shape.SubShapes[0].rotate(p1, axis, obj.Tilt.Value)
|
||||
# Rotar módulos
|
||||
p1 = shape.SubShapes[0].SubShapes[1].SubShapes[0].CenterOfMass
|
||||
p2 = min(shape.SubShapes[0].SubShapes[1].SubShapes[0].Faces, key=lambda face: face.Area).CenterOfMass
|
||||
axis = p1 - p2
|
||||
modules = shape.SubShapes[0].rotate(p1, axis, obj.Tilt.Value)
|
||||
|
||||
angle = obj.Placement.Rotation.toEuler()[1]
|
||||
newpoles = Part.makeCompound([])
|
||||
for i in range(len(shape.SubShapes[1].SubShapes[0].SubShapes)):
|
||||
pole = shape.SubShapes[1].SubShapes[0].SubShapes[i]
|
||||
axis = shape.SubShapes[1].SubShapes[1].SubShapes[i]
|
||||
base = axis.Vertexes[0].Point
|
||||
axis = axis.Vertexes[1].Point - axis.Vertexes[0].Point
|
||||
newpoles.add(pole.rotate(base, axis, -angle))
|
||||
poles = Part.makeCompound([newpoles, shape.SubShapes[1].SubShapes[1].copy()])
|
||||
# Rotar postes
|
||||
angle = obj.Placement.Rotation.toEuler()[1]
|
||||
newpoles = Part.makeCompound([])
|
||||
for i in range(len(shape.SubShapes[1].SubShapes[0].SubShapes)):
|
||||
pole = shape.SubShapes[1].SubShapes[0].SubShapes[i]
|
||||
axis = shape.SubShapes[1].SubShapes[1].SubShapes[i]
|
||||
base = axis.Vertexes[0].Point
|
||||
axis = axis.Vertexes[1].Point - axis.Vertexes[0].Point
|
||||
newpoles.add(pole.rotate(base, axis, -angle))
|
||||
poles = Part.makeCompound([newpoles, shape.SubShapes[1].SubShapes[1].copy()])
|
||||
|
||||
obj.Shape = Part.makeCompound([modules, poles])
|
||||
obj.Placement = pl
|
||||
obj.AngleX, obj.AngleY, obj.AngleZ = obj.Placement.Rotation.toEulerAngles("XYZ")
|
||||
# Crear forma final
|
||||
obj.Shape = Part.makeCompound([modules, poles])
|
||||
obj.Placement = pl
|
||||
|
||||
# Sincronizar propiedades de ángulo
|
||||
obj.AngleX, obj.AngleY, obj.AngleZ = obj.Placement.Rotation.toEulerAngles("XYZ")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in Tracker execution: {str(e)}")
|
||||
|
||||
|
||||
class ViewProviderTracker(ArchComponent.ViewProviderComponent):
|
||||
@@ -1271,6 +1290,7 @@ class CommandFixedRack:
|
||||
#FreeCADGui.Control.showDialog(self.TaskPanel)
|
||||
return
|
||||
|
||||
|
||||
class CommandTrackerSetup:
|
||||
"the Arch Building command definition"
|
||||
|
||||
@@ -1292,6 +1312,7 @@ class CommandTrackerSetup:
|
||||
FreeCADGui.Control.showDialog(self.TaskPanel)
|
||||
return
|
||||
|
||||
|
||||
class CommandTracker:
|
||||
"the Arch Building command definition"
|
||||
|
||||
|
||||
@@ -122,6 +122,7 @@ def getElevationFromOE(coordinates):
|
||||
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])
|
||||
|
||||
1644
PVPlantPlacement.py
1644
PVPlantPlacement.py
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,318 @@
|
||||
<string>Park Settings</string>
|
||||
</property>
|
||||
<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">
|
||||
<widget class="QGroupBox" name="groupCorridor">
|
||||
<property name="title">
|
||||
@@ -59,10 +371,10 @@
|
||||
<item row="3" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editRowGap">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string/>
|
||||
@@ -110,10 +422,10 @@
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editColGap">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string/>
|
||||
@@ -135,10 +447,10 @@
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="editRowCount">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>4</number>
|
||||
@@ -148,10 +460,10 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="editColCount">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>8</number>
|
||||
@@ -161,45 +473,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</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">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@@ -207,282 +480,9 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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">
|
||||
<widget class="QLineEdit" name="editPVArea"/>
|
||||
</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">
|
||||
<widget class="QListWidget" name="listFrameSetups">
|
||||
<property name="maximumSize">
|
||||
@@ -493,11 +493,19 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>buttonAddFrame</tabstop>
|
||||
<tabstop>buttonRemoveFrame</tabstop>
|
||||
<tabstop>editPVArea</tabstop>
|
||||
<tabstop>buttonPVArea</tabstop>
|
||||
<tabstop>comboOrientation</tabstop>
|
||||
|
||||
@@ -771,12 +771,12 @@ class _PVPlantSite(ArchSite._Site):
|
||||
import PVPlantImportGrid
|
||||
x, y, zone_number, zone_letter = utm.from_latlon(lat, lon)
|
||||
self.obj.UtmZone = zone_list[zone_number - 1]
|
||||
zz = PVPlantImportGrid.getElevationFromOE([[lat, lon]])
|
||||
self.obj.Origin = FreeCAD.Vector(x * 1000, y * 1000, zz[0].z)
|
||||
#self.obj.OriginOffset = FreeCAD.Vector(x * 1000, y * 1000, 0) #??
|
||||
|
||||
point = PVPlantImportGrid.getElevationFromOE([[lat, lon]])
|
||||
self.obj.Origin = FreeCAD.Vector(point[0].x, point[0].y, point[0].z)
|
||||
self.obj.Latitude = lat
|
||||
self.obj.Longitude = lon
|
||||
self.obj.Elevation = zz[0].z
|
||||
self.obj.Elevation = point[0].z
|
||||
|
||||
|
||||
class _ViewProviderSite(ArchSite._ViewProviderSite):
|
||||
|
||||
@@ -73,6 +73,42 @@ line_patterns = {
|
||||
"Dot (.5x) ...............................": 0x5555,
|
||||
"Dot (2x) . . . . . . . . . . .": 0x8888}
|
||||
|
||||
|
||||
def open_xyz_mmap(archivo_path):
|
||||
"""
|
||||
Usa memory-mapping para archivos muy grandes (máxima velocidad)
|
||||
"""
|
||||
# Primera pasada: contar líneas válidas
|
||||
total_puntos = 0
|
||||
with open(archivo_path, 'r') as f:
|
||||
for linea in f:
|
||||
partes = linea.strip().split()
|
||||
if len(partes) >= 3:
|
||||
try:
|
||||
float(partes[0]);
|
||||
float(partes[1]);
|
||||
float(partes[2])
|
||||
total_puntos += 1
|
||||
except:
|
||||
continue
|
||||
|
||||
# Segunda pasada: cargar datos
|
||||
puntos = np.empty((total_puntos, 3))
|
||||
idx = 0
|
||||
|
||||
with open(archivo_path, 'r') as f:
|
||||
for linea in f:
|
||||
partes = linea.strip().split()
|
||||
if len(partes) >= 3:
|
||||
try:
|
||||
x, y, z = float(partes[0]), float(partes[1]), float(partes[2])
|
||||
puntos[idx] = [x, y, z]
|
||||
idx += 1
|
||||
except:
|
||||
continue
|
||||
|
||||
return puntos
|
||||
|
||||
def makeTerrain(name="Terrain"):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Terrain")
|
||||
obj.Label = name
|
||||
@@ -81,7 +117,6 @@ def makeTerrain(name="Terrain"):
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
return obj
|
||||
|
||||
|
||||
class Terrain(ArchComponent.Component):
|
||||
"A Shadow Terrain Obcject"
|
||||
|
||||
@@ -161,101 +196,110 @@ class Terrain(ArchComponent.Component):
|
||||
if prop == "DEM" or prop == "CuttingBoundary":
|
||||
from datetime import datetime
|
||||
if obj.DEM and obj.CuttingBoundary:
|
||||
'''
|
||||
Parámetro Descripción Requisitos
|
||||
NCOLS: Cantidad de columnas de celdas Entero mayor que 0.
|
||||
NROWS: Cantidad de filas de celdas Entero mayor que 0.
|
||||
XLLCENTER o XLLCORNER: Coordenada X del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada y.
|
||||
YLLCENTER o YLLCORNER: Coordenada Y del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada x.
|
||||
CELLSIZE: Tamaño de celda Mayor que 0.
|
||||
NODATA_VALUE: Los valores de entrada que serán NoData en el ráster de salida Opcional. El valor predeterminado es -9999
|
||||
'''
|
||||
grid_space = 1
|
||||
file = open(obj.DEM, "r")
|
||||
templist = [line.split() for line in file.readlines()]
|
||||
file.close()
|
||||
del file
|
||||
from pathlib import Path
|
||||
suffix = Path(obj.DEM).suffix
|
||||
if suffix == '.asc':
|
||||
'''
|
||||
ASC format:
|
||||
|
||||
Parámetro Descripción Requisitos
|
||||
NCOLS: Cantidad de columnas de celdas Entero mayor que 0.
|
||||
NROWS: Cantidad de filas de celdas Entero mayor que 0.
|
||||
XLLCENTER o XLLCORNER: Coordenada X del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada y.
|
||||
YLLCENTER o YLLCORNER: Coordenada Y del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada x.
|
||||
CELLSIZE: Tamaño de celda Mayor que 0.
|
||||
NODATA_VALUE: Los valores de entrada que serán NoData en el ráster de salida Opcional. El valor predeterminado es -9999
|
||||
'''
|
||||
grid_space = 1
|
||||
file = open(obj.DEM, "r")
|
||||
templist = [line.split() for line in file.readlines()]
|
||||
file.close()
|
||||
del file
|
||||
|
||||
# Read meta data:
|
||||
meta = templist[0:6]
|
||||
nx = int(meta[0][1]) # NCOLS
|
||||
ny = int(meta[1][1]) # NROWS
|
||||
xllref = meta[2][0] # XLLCENTER / XLLCORNER
|
||||
xllvalue = round(float(meta[2][1]), 3)
|
||||
yllref = meta[3][0] # YLLCENTER / XLLCORNER
|
||||
yllvalue = round(float(meta[3][1]), 3)
|
||||
cellsize = round(float(meta[4][1]), 3) # CELLSIZE
|
||||
nodata_value = float(meta[5][1]) # NODATA_VALUE
|
||||
# Read meta data:
|
||||
meta = templist[0:6]
|
||||
nx = int(meta[0][1]) # NCOLS
|
||||
ny = int(meta[1][1]) # NROWS
|
||||
xllref = meta[2][0] # XLLCENTER / XLLCORNER
|
||||
xllvalue = round(float(meta[2][1]), 3)
|
||||
yllref = meta[3][0] # YLLCENTER / XLLCORNER
|
||||
yllvalue = round(float(meta[3][1]), 3)
|
||||
cellsize = round(float(meta[4][1]), 3) # CELLSIZE
|
||||
nodata_value = float(meta[5][1]) # NODATA_VALUE
|
||||
|
||||
# set coarse_factor
|
||||
coarse_factor = max(round(grid_space / cellsize), 1)
|
||||
# set coarse_factor
|
||||
coarse_factor = max(round(grid_space / cellsize), 1)
|
||||
|
||||
# Get z values
|
||||
templist = templist[6:(6 + ny)]
|
||||
templist = [templist[i][0::coarse_factor] for i in np.arange(0, len(templist), coarse_factor)]
|
||||
datavals = np.array(templist).astype(float)
|
||||
del templist
|
||||
# Get z values
|
||||
templist = templist[6:(6 + ny)]
|
||||
templist = [templist[i][0::coarse_factor] for i in np.arange(0, len(templist), coarse_factor)]
|
||||
datavals = np.array(templist).astype(float)
|
||||
del templist
|
||||
|
||||
# create xy coordinates
|
||||
offset = self.site.Origin
|
||||
x = (cellsize * np.arange(nx)[0::coarse_factor] + xllvalue) * 1000 - offset.x
|
||||
y = (cellsize * np.arange(ny)[-1::-1][0::coarse_factor] + yllvalue) * 1000 - offset.y
|
||||
datavals = datavals * 1000 # Ajuste de altura
|
||||
# create xy coordinates
|
||||
offset = self.site.Origin
|
||||
x = (cellsize * np.arange(nx)[0::coarse_factor] + xllvalue) * 1000 - offset.x
|
||||
y = (cellsize * np.arange(ny)[-1::-1][0::coarse_factor] + yllvalue) * 1000 - offset.y
|
||||
datavals = datavals * 1000 # Ajuste de altura
|
||||
|
||||
# remove points out of area
|
||||
# 1. coarse:
|
||||
if obj.CuttingBoundary:
|
||||
inc_x = obj.CuttingBoundary.Shape.BoundBox.XLength * 0.0
|
||||
inc_y = obj.CuttingBoundary.Shape.BoundBox.YLength * 0.0
|
||||
tmp = np.where(np.logical_and(x >= (obj.CuttingBoundary.Shape.BoundBox.XMin - inc_x),
|
||||
x <= (obj.CuttingBoundary.Shape.BoundBox.XMax + inc_x)))[0]
|
||||
x_max = np.ndarray.max(tmp)
|
||||
x_min = np.ndarray.min(tmp)
|
||||
# remove points out of area
|
||||
# 1. coarse:
|
||||
if obj.CuttingBoundary:
|
||||
inc_x = obj.CuttingBoundary.Shape.BoundBox.XLength * 0.0
|
||||
inc_y = obj.CuttingBoundary.Shape.BoundBox.YLength * 0.0
|
||||
tmp = np.where(np.logical_and(x >= (obj.CuttingBoundary.Shape.BoundBox.XMin - inc_x),
|
||||
x <= (obj.CuttingBoundary.Shape.BoundBox.XMax + inc_x)))[0]
|
||||
x_max = np.ndarray.max(tmp)
|
||||
x_min = np.ndarray.min(tmp)
|
||||
|
||||
tmp = np.where(np.logical_and(y >= (obj.CuttingBoundary.Shape.BoundBox.YMin - inc_y),
|
||||
y <= (obj.CuttingBoundary.Shape.BoundBox.YMax + inc_y)))[0]
|
||||
y_max = np.ndarray.max(tmp)
|
||||
y_min = np.ndarray.min(tmp)
|
||||
del tmp
|
||||
tmp = np.where(np.logical_and(y >= (obj.CuttingBoundary.Shape.BoundBox.YMin - inc_y),
|
||||
y <= (obj.CuttingBoundary.Shape.BoundBox.YMax + inc_y)))[0]
|
||||
y_max = np.ndarray.max(tmp)
|
||||
y_min = np.ndarray.min(tmp)
|
||||
del tmp
|
||||
|
||||
x = x[x_min:x_max+1]
|
||||
y = y[y_min:y_max+1]
|
||||
datavals = datavals[y_min:y_max+1, x_min:x_max+1]
|
||||
x = x[x_min:x_max+1]
|
||||
y = y[y_min:y_max+1]
|
||||
datavals = datavals[y_min:y_max+1, x_min:x_max+1]
|
||||
|
||||
# Create mesh - surface:
|
||||
import MeshTools.Triangulation as Triangulation
|
||||
import Mesh
|
||||
stepsize = 75
|
||||
stepx = math.ceil(nx / stepsize)
|
||||
stepy = math.ceil(ny / stepsize)
|
||||
# Create mesh - surface:
|
||||
import MeshTools.Triangulation as Triangulation
|
||||
import Mesh
|
||||
stepsize = 75
|
||||
stepx = math.ceil(nx / stepsize)
|
||||
stepy = math.ceil(ny / stepsize)
|
||||
|
||||
mesh = Mesh.Mesh()
|
||||
for indx in range(stepx):
|
||||
inix = indx * stepsize - 1
|
||||
finx = min([stepsize * (indx + 1), len(x)-1])
|
||||
for indy in range(stepy):
|
||||
iniy = indy * stepsize - 1
|
||||
finy = min([stepsize * (indy + 1), len(y) - 1])
|
||||
pts = []
|
||||
for i in range(inix, finx):
|
||||
for j in range(iniy, finy):
|
||||
if datavals[j][i] != nodata_value:
|
||||
if obj.CuttingBoundary:
|
||||
if obj.CuttingBoundary.Shape.isInside(FreeCAD.Vector(x[i], y[j], 0), 0, True):
|
||||
mesh = Mesh.Mesh()
|
||||
for indx in range(stepx):
|
||||
inix = indx * stepsize - 1
|
||||
finx = min([stepsize * (indx + 1), len(x)-1])
|
||||
for indy in range(stepy):
|
||||
iniy = indy * stepsize - 1
|
||||
finy = min([stepsize * (indy + 1), len(y) - 1])
|
||||
pts = []
|
||||
for i in range(inix, finx):
|
||||
for j in range(iniy, finy):
|
||||
if datavals[j][i] != nodata_value:
|
||||
if obj.CuttingBoundary:
|
||||
if obj.CuttingBoundary.Shape.isInside(FreeCAD.Vector(x[i], y[j], 0), 0, True):
|
||||
pts.append([x[i], y[j], datavals[j][i]])
|
||||
else:
|
||||
pts.append([x[i], y[j], datavals[j][i]])
|
||||
else:
|
||||
pts.append([x[i], y[j], datavals[j][i]])
|
||||
if len(pts) > 3:
|
||||
try:
|
||||
triangulated = Triangulation.Triangulate(pts)
|
||||
mesh.addMesh(triangulated)
|
||||
except TypeError:
|
||||
print(f"Error al procesar {len(pts)} puntos: {str(e)}")
|
||||
if len(pts) > 3:
|
||||
try:
|
||||
triangulated = Triangulation.Triangulate(pts)
|
||||
mesh.addMesh(triangulated)
|
||||
except TypeError:
|
||||
print(f"Error al procesar {len(pts)} puntos: {str(e)}")
|
||||
|
||||
mesh.removeDuplicatedPoints()
|
||||
mesh.removeFoldsOnSurface()
|
||||
obj.InitialMesh = mesh.copy()
|
||||
Mesh.show(mesh)
|
||||
elif suffix in ['.xyz']:
|
||||
data = open_xyz_mmap(obj.DEM)
|
||||
|
||||
|
||||
mesh.removeDuplicatedPoints()
|
||||
mesh.removeFoldsOnSurface()
|
||||
obj.InitialMesh = mesh.copy()
|
||||
Mesh.show(mesh)
|
||||
|
||||
if prop == "PointsGroup" or prop == "CuttingBoundary":
|
||||
if obj.PointsGroup and obj.CuttingBoundary:
|
||||
|
||||
@@ -54,30 +54,6 @@ class CommandPVPlantSite:
|
||||
return
|
||||
|
||||
|
||||
'''class CommandPVPlantGeoreferencing:
|
||||
@staticmethod
|
||||
def GetResources():
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "Location.svg")),
|
||||
'Accel': "G, R",
|
||||
'MenuText': QT_TRANSLATE_NOOP("Georeferencing","Georeferencing"),
|
||||
'ToolTip': QT_TRANSLATE_NOOP("Georeferencing","Referenciar el lugar")}
|
||||
|
||||
@staticmethod
|
||||
def IsActive():
|
||||
if FreeCAD.ActiveDocument:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def Activated():
|
||||
import PVPlantGeoreferencing
|
||||
taskd = PVPlantGeoreferencing.MapWindow()
|
||||
#taskd.setParent(FreeCADGui.getMainWindow())
|
||||
#taskd.setWindowFlags(QtCore.Qt.Window)
|
||||
taskd.show()#exec_()'''
|
||||
|
||||
|
||||
class CommandProjectSetup:
|
||||
@staticmethod
|
||||
def GetResources():
|
||||
@@ -673,8 +649,7 @@ if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('PVPlantTracker', PVPlantFrame.CommandTracker())
|
||||
FreeCADGui.addCommand('RackType', CommandRackGroup())
|
||||
|
||||
|
||||
import PVPlantFence
|
||||
from Civil.Fence import PVPlantFence
|
||||
FreeCADGui.addCommand('PVPlantFenceGroup', PVPlantFence.CommandFenceGroup())
|
||||
|
||||
projectlist = [ # "Reload",
|
||||
@@ -712,4 +687,4 @@ pv_mechanical = [
|
||||
]
|
||||
|
||||
objectlist = ['PVPlantTree',
|
||||
'PVPlantFence',]
|
||||
'PVPlantFenceGroup',]
|
||||
@@ -26,6 +26,9 @@ import PVPlantSite
|
||||
import Utils.PVPlantUtils as utils
|
||||
import MeshPart as mp
|
||||
|
||||
import pivy
|
||||
from pivy import coin
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from DraftTools import translate
|
||||
@@ -69,6 +72,7 @@ class _Area:
|
||||
''' Initialize the Area object '''
|
||||
self.Type = None
|
||||
self.obj = None
|
||||
self.setProperties(obj)
|
||||
|
||||
def setProperties(self, obj):
|
||||
pl = obj.PropertiesList
|
||||
@@ -101,18 +105,18 @@ class _Area:
|
||||
def __setstate__(self, state):
|
||||
pass
|
||||
|
||||
def execute(self, obj):
|
||||
''' Execute the area object '''
|
||||
pass
|
||||
|
||||
|
||||
class _ViewProviderArea:
|
||||
def __init__(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
vobj.Proxy = self
|
||||
|
||||
def attach(self, vobj):
|
||||
'''
|
||||
Create Object visuals in 3D view.
|
||||
'''
|
||||
self.Object = vobj.Object
|
||||
return
|
||||
''' Create Object visuals in 3D view. '''
|
||||
self.ViewObject = vobj
|
||||
|
||||
def getIcon(self):
|
||||
'''
|
||||
@@ -120,6 +124,7 @@ class _ViewProviderArea:
|
||||
'''
|
||||
|
||||
return str(os.path.join(DirIcons, "area.svg"))
|
||||
|
||||
'''
|
||||
def claimChildren(self):
|
||||
"""
|
||||
@@ -159,17 +164,10 @@ class _ViewProviderArea:
|
||||
pass
|
||||
|
||||
def __getstate__(self):
|
||||
"""
|
||||
Save variables to file.
|
||||
"""
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""
|
||||
Get variables from file.
|
||||
"""
|
||||
return None
|
||||
|
||||
pass
|
||||
|
||||
''' Frame Area '''
|
||||
|
||||
@@ -311,17 +309,14 @@ class ViewProviderFrameArea(_ViewProviderArea):
|
||||
|
||||
|
||||
''' offsets '''
|
||||
|
||||
|
||||
def makeOffsetArea(base = None, val=None):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "OffsetArea")
|
||||
OffsetArea(obj)
|
||||
obj.Base = base
|
||||
ViewProviderOffsetArea(obj.ViewObject)
|
||||
if val:
|
||||
obj.Distance = val
|
||||
obj.OffsetDistance = val
|
||||
|
||||
offsets = None
|
||||
try:
|
||||
offsetsgroup = FreeCAD.ActiveDocument.Offsets
|
||||
except:
|
||||
@@ -334,11 +329,13 @@ def makeOffsetArea(base = None, val=None):
|
||||
|
||||
class OffsetArea(_Area):
|
||||
def __init__(self, obj):
|
||||
_Area.__init__(self, obj)
|
||||
self.setProperties(obj)
|
||||
'''_Area.__init__(self, obj)
|
||||
self.setProperties(obj)'''
|
||||
super().__init__(obj) # Llama al constructor de _Area
|
||||
|
||||
def setProperties(self, obj):
|
||||
_Area.setProperties(self, obj)
|
||||
super().setProperties(obj) # Propiedades de la clase base
|
||||
|
||||
pl = obj.PropertiesList
|
||||
if not ("OffsetDistance" in pl):
|
||||
obj.addProperty("App::PropertyDistance",
|
||||
@@ -354,24 +351,28 @@ class OffsetArea(_Area):
|
||||
self.setProperties(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
|
||||
land = PVPlantSite.get().Terrain.Mesh
|
||||
vec = FreeCAD.Vector(0, 0, 1)
|
||||
|
||||
wire = utils.getProjected(base, vec)
|
||||
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 = []
|
||||
for section in tmp:
|
||||
for section in sections:
|
||||
pts.extend(section)
|
||||
obj.Shape = Part.makePolygon(pts)
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
pass
|
||||
# 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):
|
||||
@@ -382,14 +383,12 @@ class ViewProviderOffsetArea(_ViewProviderArea):
|
||||
def claimChildren(self):
|
||||
""" Provides object grouping """
|
||||
children = []
|
||||
if self.Object.Base:
|
||||
children.append(self.Object.Base)
|
||||
if self.ViewObject and self.ViewObject.Object.Base:
|
||||
children.append(self.ViewObject.Object.Base)
|
||||
return children
|
||||
|
||||
|
||||
''' Forbidden Area: '''
|
||||
|
||||
|
||||
def makeProhibitedArea(base = None):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "ExclusionArea")
|
||||
ProhibitedArea(obj)
|
||||
@@ -420,29 +419,443 @@ class ProhibitedArea(OffsetArea):
|
||||
"""Method run when the document is restored."""
|
||||
self.setProperties(obj)
|
||||
|
||||
def execute(self, obj):
|
||||
# Comprobar dependencias
|
||||
if not hasattr(obj, "Base") or not obj.Base or not obj.Base.Shape:
|
||||
return
|
||||
if not hasattr(PVPlantSite, "get") or not PVPlantSite.get().Terrain:
|
||||
return
|
||||
|
||||
base = obj.Base.Shape
|
||||
land = PVPlantSite.get().Terrain.Mesh
|
||||
vec = FreeCAD.Vector(0, 0, 1)
|
||||
|
||||
# 1. Crear wire original
|
||||
original_wire = utils.getProjected(base, vec)
|
||||
sections_original = mp.projectShapeOnMesh(original_wire, land, vec)
|
||||
|
||||
# 2. Crear wire offset
|
||||
offset_wire = original_wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True)
|
||||
sections_offset = mp.projectShapeOnMesh(offset_wire, land, vec)
|
||||
|
||||
# Crear formas compuestas
|
||||
def make_polygon(sections):
|
||||
if not sections:
|
||||
return Part.Shape()
|
||||
pts = []
|
||||
for section in sections:
|
||||
pts.extend(section)
|
||||
return Part.makePolygon(pts)
|
||||
|
||||
compounds = []
|
||||
if sections_original:
|
||||
compounds.append(make_polygon(sections_original))
|
||||
if sections_offset:
|
||||
compounds.append(make_polygon(sections_offset))
|
||||
|
||||
if compounds:
|
||||
obj.Shape = Part.makeCompound(compounds)
|
||||
else:
|
||||
obj.Shape = Part.Shape()
|
||||
|
||||
# Actualizar colores en la vista
|
||||
"""if FreeCAD.GuiUp and obj.ViewObject:
|
||||
obj.ViewObject.Proxy.updateVisual()"""
|
||||
|
||||
|
||||
class ViewProviderForbiddenArea_old:
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
self.setProperties(vobj)
|
||||
|
||||
def setProperties(self, vobj):
|
||||
# Propiedades de color
|
||||
if not hasattr(vobj, "OriginalColor"):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"OriginalColor",
|
||||
"ObjectStyle",
|
||||
"Color for original wire")
|
||||
vobj.OriginalColor = (1.0, 0.0, 0.0) # Rojo
|
||||
|
||||
if not hasattr(vobj, "OffsetColor"):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"OffsetColor",
|
||||
"ObjectStyle",
|
||||
"Color for offset wire")
|
||||
vobj.OffsetColor = (1.0, 0.0, 0.0) # Rojo
|
||||
|
||||
# Propiedades de grosor
|
||||
if not hasattr(vobj, "OriginalWidth"):
|
||||
vobj.addProperty("App::PropertyFloat",
|
||||
"OriginalWidth",
|
||||
"ObjectStyle",
|
||||
"Line width for original wire")
|
||||
vobj.OriginalWidth = 4.0
|
||||
|
||||
if not hasattr(vobj, "OffsetWidth"):
|
||||
vobj.addProperty("App::PropertyFloat",
|
||||
"OffsetWidth",
|
||||
"ObjectStyle",
|
||||
"Line width for offset wire")
|
||||
vobj.OffsetWidth = 4.0
|
||||
|
||||
# Deshabilitar el color por defecto
|
||||
vobj.setPropertyStatus("LineColor", "Hidden")
|
||||
vobj.setPropertyStatus("PointColor", "Hidden")
|
||||
vobj.setPropertyStatus("ShapeAppearance", "Hidden")
|
||||
|
||||
def attach(self, vobj):
|
||||
self.ViewObject = vobj
|
||||
self.Object = vobj.Object
|
||||
|
||||
# Crear la estructura de escena Coin3D
|
||||
self.root = coin.SoGroup()
|
||||
|
||||
# Switch para habilitar/deshabilitar la selección
|
||||
self.switch = coin.SoSwitch()
|
||||
self.switch.whichChild = coin.SO_SWITCH_ALL
|
||||
|
||||
# Separador para el wire original
|
||||
self.original_sep = coin.SoSeparator()
|
||||
self.original_color = coin.SoBaseColor()
|
||||
self.original_coords = coin.SoCoordinate3()
|
||||
self.original_line_set = coin.SoLineSet()
|
||||
self.original_draw_style = coin.SoDrawStyle()
|
||||
|
||||
# Separador para el wire offset
|
||||
self.offset_sep = coin.SoSeparator()
|
||||
self.offset_color = coin.SoBaseColor()
|
||||
self.offset_coords = coin.SoCoordinate3()
|
||||
self.offset_line_set = coin.SoLineSet()
|
||||
self.offset_draw_style = coin.SoDrawStyle()
|
||||
|
||||
# Construir la jerarquía de escena
|
||||
self.original_sep.addChild(self.original_color)
|
||||
self.original_sep.addChild(self.original_draw_style)
|
||||
self.original_sep.addChild(self.original_coords)
|
||||
self.original_sep.addChild(self.original_line_set)
|
||||
|
||||
self.offset_sep.addChild(self.offset_color)
|
||||
self.offset_sep.addChild(self.offset_draw_style)
|
||||
self.offset_sep.addChild(self.offset_coords)
|
||||
self.offset_sep.addChild(self.offset_line_set)
|
||||
|
||||
self.switch.addChild(self.original_sep)
|
||||
self.switch.addChild(self.offset_sep)
|
||||
self.root.addChild(self.switch)
|
||||
|
||||
vobj.addDisplayMode(self.root, "Wireframe")
|
||||
|
||||
# Inicializar estilos de dibujo
|
||||
self.original_draw_style.style = coin.SoDrawStyle.LINES
|
||||
self.offset_draw_style.style = coin.SoDrawStyle.LINES
|
||||
|
||||
# Actualizar visualización inicial
|
||||
if hasattr(self.Object, 'Shape'):
|
||||
self.updateData(self.Object, "Shape")
|
||||
self.updateVisual()
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
if prop == "Shape" and obj.Shape and not obj.Shape.isNull():
|
||||
self.updateGeometry()
|
||||
|
||||
def updateGeometry(self):
|
||||
"""Actualiza la geometría en la escena 3D"""
|
||||
if not hasattr(self, 'Object') or not self.Object.Shape or self.Object.Shape.isNull():
|
||||
return
|
||||
|
||||
# Limpiar coordenadas existentes
|
||||
self.original_coords.point.deleteValues(0)
|
||||
self.offset_coords.point.deleteValues(0)
|
||||
|
||||
# Obtener los sub-shapes
|
||||
subshapes = []
|
||||
if hasattr(self.Object.Shape, 'SubShapes') and self.Object.Shape.SubShapes:
|
||||
subshapes = self.Object.Shape.SubShapes
|
||||
elif hasattr(self.Object.Shape, 'ChildShapes') and self.Object.Shape.ChildShapes:
|
||||
subshapes = self.Object.Shape.ChildShapes
|
||||
|
||||
# Procesar wire original (primer sub-shape)
|
||||
if len(subshapes) > 0:
|
||||
self.processShape(subshapes[0], self.original_coords, self.original_line_set)
|
||||
|
||||
# Procesar wire offset (segundo sub-shape)
|
||||
if len(subshapes) > 1:
|
||||
self.processShape(subshapes[1], self.offset_coords, self.offset_line_set)
|
||||
|
||||
# Actualizar colores y grosores
|
||||
self.updateVisual()
|
||||
|
||||
def processShape(self, shape, coords_node, lineset_node):
|
||||
"""Procesa una forma y la añade al nodo de coordenadas"""
|
||||
if not shape or shape.isNull():
|
||||
return
|
||||
|
||||
points = []
|
||||
line_indices = []
|
||||
current_index = 0
|
||||
|
||||
# Obtener todos los edges de la forma
|
||||
edges = []
|
||||
if hasattr(shape, 'Edges'):
|
||||
edges = shape.Edges
|
||||
elif hasattr(shape, 'ChildShapes'):
|
||||
for child in shape.ChildShapes:
|
||||
if hasattr(child, 'Edges'):
|
||||
edges.extend(child.Edges)
|
||||
|
||||
for edge in edges:
|
||||
try:
|
||||
# Discretizar la curva para obtener puntos
|
||||
vertices = edge.discretize(Number=50)
|
||||
|
||||
for i, vertex in enumerate(vertices):
|
||||
points.append([vertex.x, vertex.y, vertex.z])
|
||||
line_indices.append(current_index)
|
||||
current_index += 1
|
||||
|
||||
# Añadir -1 para indicar fin de línea
|
||||
line_indices.append(-1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing edge: {e}")
|
||||
continue
|
||||
|
||||
# Configurar coordenadas y líneas
|
||||
if points:
|
||||
coords_node.point.setValues(0, len(points), points)
|
||||
lineset_node.numVertices.deleteValues(0)
|
||||
lineset_node.numVertices.setValues(0, len(line_indices), line_indices)
|
||||
|
||||
def updateVisual(self):
|
||||
"""Actualiza colores y grosores según las propiedades"""
|
||||
if not hasattr(self, 'ViewObject') or not self.ViewObject:
|
||||
return
|
||||
|
||||
vobj = self.ViewObject
|
||||
|
||||
try:
|
||||
# Configurar wire original
|
||||
if hasattr(vobj, "OriginalColor"):
|
||||
original_color = vobj.OriginalColor
|
||||
self.original_color.rgb.setValue(original_color[0], original_color[1], original_color[2])
|
||||
|
||||
if hasattr(vobj, "OriginalWidth"):
|
||||
self.original_draw_style.lineWidth = vobj.OriginalWidth
|
||||
|
||||
# Configurar wire offset
|
||||
if hasattr(vobj, "OffsetColor"):
|
||||
offset_color = vobj.OffsetColor
|
||||
self.offset_color.rgb.setValue(offset_color[0], offset_color[1], offset_color[2])
|
||||
|
||||
if hasattr(vobj, "OffsetWidth"):
|
||||
self.offset_draw_style.lineWidth = vobj.OffsetWidth
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error updating visual: {e}")
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
"""Maneja cambios en propiedades"""
|
||||
if prop in ["OriginalColor", "OffsetColor", "OriginalWidth", "OffsetWidth"]:
|
||||
self.updateVisual()
|
||||
|
||||
def getDisplayModes(self, obj):
|
||||
return ["Wireframe"]
|
||||
|
||||
def getDefaultDisplayMode(self):
|
||||
return "Wireframe"
|
||||
|
||||
def setDisplayMode(self, mode):
|
||||
return mode
|
||||
|
||||
def claimChildren(self):
|
||||
"""Proporciona agrupamiento de objetos"""
|
||||
children = []
|
||||
if hasattr(self, 'Object') and self.Object and hasattr(self.Object, "Base"):
|
||||
children.append(self.Object.Base)
|
||||
return children
|
||||
|
||||
def getIcon(self):
|
||||
'''Return object treeview icon'''
|
||||
return str(os.path.join(DirIcons, "area_forbidden.svg"))
|
||||
|
||||
def onDocumentRestored(self, vobj):
|
||||
"""Método ejecutado cuando el documento es restaurado"""
|
||||
self.ViewObject = vobj
|
||||
self.Object = vobj.Object
|
||||
self.setProperties(vobj)
|
||||
self.attach(vobj)
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
class ViewProviderForbiddenArea(_ViewProviderArea):
|
||||
class ViewProviderForbiddenArea:
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
self.ViewObject = vobj
|
||||
|
||||
# Inicializar propiedades PRIMERO
|
||||
self.setProperties(vobj)
|
||||
|
||||
# Configurar colores iniciales
|
||||
self.updateColors(vobj)
|
||||
|
||||
def setProperties(self, vobj):
|
||||
if not hasattr(vobj, "OriginalColor"):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"OriginalColor",
|
||||
"Display",
|
||||
"Color for original wire")
|
||||
vobj.OriginalColor = (1.0, 0.0, 0.0) # Rojo
|
||||
|
||||
if not hasattr(vobj, "OffsetColor"):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"OffsetColor",
|
||||
"Display",
|
||||
"Color for offset wire")
|
||||
vobj.OffsetColor = (1.0, 0.5, 0.0) # Naranja
|
||||
|
||||
def updateColors(self, vobj):
|
||||
"""Actualiza los colores desde las propiedades"""
|
||||
try:
|
||||
if hasattr(vobj, "OriginalColor"):
|
||||
self.original_color.rgb.setValue(*vobj.OriginalColor)
|
||||
else:
|
||||
self.original_color.rgb.setValue(1.0, 0.0, 0.0)
|
||||
|
||||
if hasattr(vobj, "OffsetColor"):
|
||||
self.offset_color.rgb.setValue(*vobj.OffsetColor)
|
||||
else:
|
||||
self.offset_color.rgb.setValue(1.0, 0.5, 0.0)
|
||||
except Exception as e:
|
||||
print(f"Error en updateColors: {e}")
|
||||
|
||||
def onDocumentRestored(self, vobj):
|
||||
self.setProperties(vobj)
|
||||
# No llamar a __init__ de nuevo, solo actualizar propiedades
|
||||
self.updateColors(vobj)
|
||||
|
||||
def getIcon(self):
|
||||
''' Return object treeview icon '''
|
||||
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):
|
||||
""" Provides object grouping """
|
||||
children = []
|
||||
if self.Object.Base:
|
||||
children.append(self.Object.Base)
|
||||
if hasattr(self, 'ViewObject') and self.ViewObject and hasattr(self.ViewObject.Object, 'Base'):
|
||||
children.append(self.ViewObject.Object.Base)
|
||||
return children
|
||||
|
||||
def dumps(self):
|
||||
return None
|
||||
|
||||
def loads(self, state):
|
||||
return None
|
||||
|
||||
''' PV Area: '''
|
||||
|
||||
|
||||
def makePVSubplant():
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PVSubplant")
|
||||
PVSubplant(obj)
|
||||
|
||||
@@ -39,6 +39,16 @@
|
||||
<property name="spacing">
|
||||
<number>9</number>
|
||||
</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">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@@ -46,17 +56,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Frame coloring:</string>
|
||||
<string>South facing</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -66,71 +79,20 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Maximum west-east slope:</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>
|
||||
<string>Frame coloring:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editNFTL">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> º</string>
|
||||
@@ -149,7 +111,45 @@
|
||||
<string>North Facing</string>
|
||||
</property>
|
||||
<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>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -224,14 +224,20 @@ def getProjected(shape, direction=FreeCAD.Vector(0, 0, 1)): # Based on Draft / s
|
||||
return ow
|
||||
|
||||
|
||||
def findObjects(classtype):
|
||||
'''def findObjects(classtype):
|
||||
objects = FreeCAD.ActiveDocument.Objects
|
||||
objlist = list()
|
||||
for object in objects:
|
||||
if hasattr(object, "Proxy"):
|
||||
if object.Proxy.Type == classtype:
|
||||
objlist.append(object)
|
||||
return objlist
|
||||
return objlist'''
|
||||
|
||||
def findObjects(classtype):
|
||||
return [obj for obj in FreeCAD.ActiveDocument.Objects
|
||||
if hasattr(obj, "Proxy")
|
||||
and hasattr(obj.Proxy, "Type")
|
||||
and obj.Proxy.Type == classtype]
|
||||
|
||||
def getClosePoints(sh1, angle):
|
||||
'''
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
|
||||
<name>PVPlant</name>
|
||||
<description>FreeCAD Fotovoltaic Power Plant Toolkit</description>
|
||||
<version>2025.07.06</version>
|
||||
<date>2025.07.06</date>
|
||||
<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="main">https://homehud.duckdns.org/javier/PVPlant</url>
|
||||
<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>RoadWorkbench</classname>
|
||||
<classname>PVPlantWorkbench</classname>
|
||||
<subdirectory>./</subdirectory>
|
||||
</workbench>
|
||||
</content>
|
||||
|
||||
10
reload.py
10
reload.py
@@ -24,11 +24,15 @@ class _CommandReload:
|
||||
def Activated(self):
|
||||
import PVPlantPlacement, \
|
||||
PVPlantGeoreferencing, PVPlantImportGrid, PVPlantTerrainAnalisys, \
|
||||
PVPlantSite, PVPlantRackChecking, PVPlantFence, PVPlantFencePost, PVPlantFenceGate, \
|
||||
PVPlantCreateTerrainMesh, \
|
||||
PVPlantSite, PVPlantRackChecking, PVPlantCreateTerrainMesh, \
|
||||
PVPlantFoundation, PVPlantBuilding, PVPlantEarthWorks, PVPlantPad, \
|
||||
PVPlantRoad, PVPlantTerrain, PVPlantStringing, PVPlantManhole, \
|
||||
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
|
||||
|
||||
@@ -59,9 +63,11 @@ class _CommandReload:
|
||||
importlib.reload(PVPlantSite)
|
||||
importlib.reload(PVPlantFrame)
|
||||
importlib.reload(PVPlantRackChecking)
|
||||
|
||||
importlib.reload(PVPlantFence)
|
||||
importlib.reload(PVPlantFenceGate)
|
||||
importlib.reload(PVPlantFencePost)
|
||||
|
||||
importlib.reload(PVPlantFoundation)
|
||||
importlib.reload(PVPlantCreateTerrainMesh)
|
||||
importlib.reload(PVPlantTreeGenerator)
|
||||
|
||||
@@ -9,16 +9,12 @@ setuptools~=68.2.2
|
||||
laspy~=2.5.3
|
||||
geopy~=2.4.1
|
||||
lxml~=4.9.3
|
||||
pip~=23.3.2
|
||||
wheel~=0.42.0
|
||||
Brotli~=1.1.0
|
||||
PySocks~=1.7.1
|
||||
typing_extensions~=4.9.0
|
||||
docutils~=0.20.1
|
||||
Pillow~=10.1.0
|
||||
pyproj~=3.7.1
|
||||
simplekml~=1.3.6
|
||||
geojson~=3.1.0
|
||||
certifi~=2023.11.17
|
||||
SciPy~=1.11.4
|
||||
ezdxf~=1.4.1
|
||||
pycollada~=0.7.2
|
||||
shapely
|
||||
rtree
|
||||
Reference in New Issue
Block a user