Punto de restauración.
This commit is contained in:
4
.idea/PVPlant.iml
generated
4
.idea/PVPlant.iml
generated
@@ -5,4 +5,8 @@
|
|||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
|
<component name="PackageRequirementsSettings">
|
||||||
|
<option name="removeUnused" value="true" />
|
||||||
|
<option name="modifyBaseFiles" value="true" />
|
||||||
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -20,16 +20,18 @@
|
|||||||
# * *
|
# * *
|
||||||
# ***********************************************************************
|
# ***********************************************************************
|
||||||
|
|
||||||
|
import FreeCAD
|
||||||
|
import Part
|
||||||
|
import Draft
|
||||||
|
import MeshPart as mp
|
||||||
|
import ArchComponent
|
||||||
|
|
||||||
|
import Civil.Fence.PVPlantFencePost as PVPlantFencePost
|
||||||
|
import PVPlantSite
|
||||||
|
import Utils.PVPlantUtils as utils
|
||||||
import copy
|
import copy
|
||||||
import math
|
import math
|
||||||
|
|
||||||
import ArchComponent
|
|
||||||
import Draft
|
|
||||||
import FreeCAD
|
|
||||||
import Part
|
|
||||||
|
|
||||||
import PVPlantFencePost
|
|
||||||
import PVPlantSite
|
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
if FreeCAD.GuiUp:
|
||||||
import FreeCADGui
|
import FreeCADGui
|
||||||
@@ -56,26 +58,28 @@ from PVPlantResources import DirIcons as DirIcons
|
|||||||
|
|
||||||
EAST = FreeCAD.Vector(1, 0, 0)
|
EAST = FreeCAD.Vector(1, 0, 0)
|
||||||
|
|
||||||
def makeprojection(pathwire):
|
|
||||||
site = FreeCAD.ActiveDocument.Site
|
|
||||||
land = site.Terrain.Shape
|
|
||||||
proj = land.makeParallelProjection(pathwire, FreeCAD.Vector(0, 0, 1))
|
|
||||||
return proj
|
|
||||||
|
|
||||||
def makePVPlantFence(section, post, path):
|
def makePVPlantFence(section, post, path):
|
||||||
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Fence')
|
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Fence')
|
||||||
|
|
||||||
_Fence(obj)
|
Fence(obj)
|
||||||
obj.Post = post
|
obj.Post = post
|
||||||
obj.Base = path
|
obj.Base = path
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
if FreeCAD.GuiUp:
|
||||||
_ViewProviderFence(obj.ViewObject)
|
ViewProviderFence(obj.ViewObject)
|
||||||
|
|
||||||
hide(section)
|
hide(section)
|
||||||
hide(post)
|
hide(post)
|
||||||
hide(path)
|
hide(path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
fende_group = FreeCAD.ActiveDocument.Fences
|
||||||
|
except:
|
||||||
|
fende_group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Fences')
|
||||||
|
fende_group.Label = "Fences"
|
||||||
|
FreeCAD.ActiveDocument.CivilGroup.addObject(fende_group)
|
||||||
|
fende_group.addObject(obj)
|
||||||
|
|
||||||
FreeCAD.ActiveDocument.recompute()
|
FreeCAD.ActiveDocument.recompute()
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
@@ -83,16 +87,8 @@ def hide(obj):
|
|||||||
if hasattr(obj, 'ViewObject') and obj.ViewObject:
|
if hasattr(obj, 'ViewObject') and obj.ViewObject:
|
||||||
obj.ViewObject.Visibility = False
|
obj.ViewObject.Visibility = False
|
||||||
|
|
||||||
def getAngle(Line1, Line2):
|
def get_parameter_from_v0_old(edge, offset):
|
||||||
v1 = Line1.Vertexes[1].Point - Line1.Vertexes[0].Point
|
""" Return parameter at distance offset from edge.Vertexes[0].sb method in Part.TopoShapeEdge??? """
|
||||||
v2 = Line2.Vertexes[1].Point - Line2.Vertexes[0].Point
|
|
||||||
return v1.getAngle(v2)
|
|
||||||
|
|
||||||
|
|
||||||
def get_parameter_from_v0(edge, offset):
|
|
||||||
"""Return parameter at distance offset from edge.Vertexes[0].
|
|
||||||
sb method in Part.TopoShapeEdge???
|
|
||||||
"""
|
|
||||||
import DraftVecUtils
|
import DraftVecUtils
|
||||||
|
|
||||||
lpt = edge.valueAt(edge.getParameterByLength(0))
|
lpt = edge.valueAt(edge.getParameterByLength(0))
|
||||||
@@ -106,14 +102,16 @@ def get_parameter_from_v0(edge, offset):
|
|||||||
length = offset
|
length = offset
|
||||||
return edge.getParameterByLength(length)
|
return edge.getParameterByLength(length)
|
||||||
|
|
||||||
|
def get_parameter_from_v0(edge, offset):
|
||||||
|
"""Parámetro a distancia offset desde el primer vértice"""
|
||||||
|
lpt = edge.valueAt(edge.getParameterByLength(0))
|
||||||
|
vpt = edge.Vertexes[0].Point
|
||||||
|
|
||||||
|
if not vpt.isEqual(lpt, 1e-6):
|
||||||
|
return edge.getParameterByLength(edge.Length - offset)
|
||||||
|
return edge.getParameterByLength(offset)
|
||||||
|
|
||||||
def calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal=None):
|
def calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal=None):
|
||||||
"""Orient shape to tangent at parm offset along edge."""
|
|
||||||
import functools
|
|
||||||
import DraftVecUtils
|
|
||||||
# http://en.wikipedia.org/wiki/Euler_angles
|
|
||||||
# start with null Placement point so translate goes to right place.
|
|
||||||
|
|
||||||
placement = FreeCAD.Placement()
|
placement = FreeCAD.Placement()
|
||||||
placement.Rotation = globalRotation
|
placement.Rotation = globalRotation
|
||||||
placement.move(RefPt + xlate)
|
placement.move(RefPt + xlate)
|
||||||
@@ -121,55 +119,23 @@ def calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal
|
|||||||
if not align:
|
if not align:
|
||||||
return placement
|
return placement
|
||||||
|
|
||||||
# unit +Z Probably defined elsewhere?
|
t = edge.tangentAt(get_parameter_from_v0(edge, offset)).normalize()
|
||||||
z = FreeCAD.Vector(0, 0, 1)
|
n = normal or FreeCAD.Vector(0, 0, 1)
|
||||||
# y = FreeCAD.Vector(0, 1, 0) # unit +Y
|
b = t.cross(n).normalize()
|
||||||
x = FreeCAD.Vector(1, 0, 0) # unit +X
|
|
||||||
nullv = FreeCAD.Vector(0, 0, 0)
|
|
||||||
|
|
||||||
# get local coord system - tangent, normal, binormal, if possible
|
# Asegurar sistema de coordenadas derecho
|
||||||
t = edge.tangentAt(get_parameter_from_v0(edge, offset))
|
if n.dot(t.cross(b)) < 0:
|
||||||
t.normalize()
|
b = -b
|
||||||
n = normal
|
|
||||||
b = t.cross(n)
|
|
||||||
b.normalize()
|
|
||||||
|
|
||||||
lnodes = z.cross(b)
|
# Construir matriz
|
||||||
try:
|
rotation_matrix = FreeCAD.Matrix(
|
||||||
# Can't normalize null vector.
|
t.x, b.x, n.x, 0,
|
||||||
lnodes.normalize()
|
t.y, b.y, n.y, 0,
|
||||||
except:
|
t.z, b.z, n.z, 0,
|
||||||
# pathological cases:
|
0, 0, 0, 1
|
||||||
pass
|
)
|
||||||
|
|
||||||
print(b, " - ", b.dot(z))
|
|
||||||
if abs(b.dot(z)) == 1.0: # 2) binormal is || z
|
|
||||||
# align shape to tangent only
|
|
||||||
psi = math.degrees(DraftVecUtils.angle(x, t, z))
|
|
||||||
theta = 0.0
|
|
||||||
phi = 0.0
|
|
||||||
FreeCAD.Console.PrintWarning("Draft PathArray.orientShape - Gimbal lock. Infinite lnodes. Change Path or Base.\n")
|
|
||||||
else: # regular case
|
|
||||||
psi = math.degrees(DraftVecUtils.angle(x, lnodes, z))
|
|
||||||
theta = math.degrees(DraftVecUtils.angle(z, b, lnodes))
|
|
||||||
phi = math.degrees(DraftVecUtils.angle(lnodes, t, b))
|
|
||||||
|
|
||||||
rotations = [placement.Rotation]
|
|
||||||
|
|
||||||
if psi != 0.0:
|
|
||||||
rotations.insert(0, FreeCAD.Rotation(z, psi))
|
|
||||||
if theta != 0.0:
|
|
||||||
rotations.insert(0, FreeCAD.Rotation(lnodes, theta))
|
|
||||||
if phi != 0.0:
|
|
||||||
rotations.insert(0, FreeCAD.Rotation(b, phi))
|
|
||||||
|
|
||||||
if len(rotations) == 1:
|
|
||||||
finalRotation = rotations[0]
|
|
||||||
else:
|
|
||||||
finalRotation = functools.reduce(lambda rot1, rot2: rot1.multiply(rot2), rotations)
|
|
||||||
|
|
||||||
placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), finalRotation.toEuler()[2])
|
|
||||||
|
|
||||||
|
placement.Rotation = FreeCAD.Rotation(rotation_matrix)
|
||||||
return placement
|
return placement
|
||||||
|
|
||||||
def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
|
def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
|
||||||
@@ -183,12 +149,8 @@ def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
|
|||||||
import DraftGeomUtils
|
import DraftGeomUtils
|
||||||
|
|
||||||
closedpath = DraftGeomUtils.isReallyClosed(pathwire)
|
closedpath = DraftGeomUtils.isReallyClosed(pathwire)
|
||||||
normal = DraftGeomUtils.getNormal(pathwire)
|
|
||||||
if normal:
|
normal = FreeCAD.Vector(0, 0, 1)
|
||||||
if normal.z < 0: # asegurarse de que siempre se dibuje por encima del suelo
|
|
||||||
normal.z *= -1
|
|
||||||
else:
|
|
||||||
normal = FreeCAD.Vector(0, 0, 1)
|
|
||||||
path = Part.__sortEdges__(pathwire.Edges)
|
path = Part.__sortEdges__(pathwire.Edges)
|
||||||
ends = []
|
ends = []
|
||||||
cdist = 0
|
cdist = 0
|
||||||
@@ -241,7 +203,7 @@ def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
|
|||||||
|
|
||||||
return placements
|
return placements
|
||||||
|
|
||||||
class _Fence(ArchComponent.Component):
|
class Fence(ArchComponent.Component):
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
ArchComponent.Component.__init__(self, obj)
|
ArchComponent.Component.__init__(self, obj)
|
||||||
self.setProperties(obj)
|
self.setProperties(obj)
|
||||||
@@ -347,7 +309,6 @@ class _Fence(ArchComponent.Component):
|
|||||||
QT_TRANSLATE_NOOP("App::Property", "The number of posts used to build the fence"))
|
QT_TRANSLATE_NOOP("App::Property", "The number of posts used to build the fence"))
|
||||||
obj.setEditorMode("Length", 1)
|
obj.setEditorMode("Length", 1)
|
||||||
|
|
||||||
|
|
||||||
self.Type = "PVPlatFence"
|
self.Type = "PVPlatFence"
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
@@ -361,74 +322,30 @@ class _Fence(ArchComponent.Component):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def execute(self, obj):
|
def execute(self, obj):
|
||||||
|
if not obj.Base or not obj.Post:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 1. Preparar trazado base
|
||||||
pathwire = self.calculatePathWire(obj)
|
pathwire = self.calculatePathWire(obj)
|
||||||
if pathwire is None:
|
pathwire = utils.getProjected(pathwire, FreeCAD.Vector(0, 0, 1))
|
||||||
# FreeCAD.Console.PrintLog("ArchFence.execute: path " + obj.Base.Name + " has no edges\n")
|
pathwire = utils.simplifyWire(pathwire)
|
||||||
return
|
if not pathwire or not pathwire.Edges:
|
||||||
|
|
||||||
if not obj.Post:
|
|
||||||
FreeCAD.Console.PrintLog("ArchFence.execute: Post not set\n")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 2. Proyectar sobre terreno (con caché)
|
||||||
self.Posts = []
|
self.Posts = []
|
||||||
self.Foundations = []
|
self.Foundations = []
|
||||||
site = PVPlantSite.get()
|
|
||||||
if True: # prueba
|
|
||||||
import MeshPart as mp
|
|
||||||
segments = mp.projectShapeOnMesh(pathwire, site.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))
|
|
||||||
points=[]
|
|
||||||
for segment in segments:
|
|
||||||
points.extend(segment)
|
|
||||||
pathwire = Part.makePolygon(points)
|
|
||||||
else:
|
|
||||||
if PVPlantSite.get().Terrain.TypeId == 'Mesh::Feature':
|
|
||||||
import MeshPart as mp
|
|
||||||
land = PVPlantSite.get().Terrain.Mesh
|
|
||||||
pathwire = mp.projectShapeOnMesh(pathwire, land, FreeCAD.Vector(0, 0, 1))
|
|
||||||
|
|
||||||
else:
|
site = PVPlantSite.get()
|
||||||
land = site.Terrain.Shape
|
segments = mp.projectShapeOnMesh(pathwire, site.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))
|
||||||
pathwire = land.makeParallelProjection(pathwire, FreeCAD.Vector(0, 0, 1))
|
points=[]
|
||||||
|
for segment in segments:
|
||||||
|
points.extend(segment)
|
||||||
|
pathwire = Part.makePolygon(points)
|
||||||
|
|
||||||
if pathwire is None:
|
if pathwire is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
''' no sirve:
|
|
||||||
if len(pathwire.Wires) > 1:
|
|
||||||
import draftgeoutils
|
|
||||||
pathwire = draftgeoutils.wires.superWire(pathwire.Edges, True)
|
|
||||||
Part.show(pathwire)
|
|
||||||
'''
|
|
||||||
|
|
||||||
''' unir todas en una '''
|
|
||||||
'''
|
|
||||||
if len(pathwire.Wires) > 1:
|
|
||||||
import Utils.PVPlantUtils as utils
|
|
||||||
wires = pathwire.Wires
|
|
||||||
new_wire = []
|
|
||||||
to_compare = utils.getPoints(wires.pop(0))
|
|
||||||
new_wire.extend(to_compare)
|
|
||||||
while len(wires)>0:
|
|
||||||
wire = wires[0]
|
|
||||||
points = utils.getPoints(wire)
|
|
||||||
to_remove = None
|
|
||||||
if points[0] in to_compare:
|
|
||||||
to_remove = points[0]
|
|
||||||
if points[-1] in to_compare:
|
|
||||||
to_remove = points[-1]
|
|
||||||
if to_remove:
|
|
||||||
to_compare = points.copy()
|
|
||||||
points.remove(to_remove)
|
|
||||||
new_wire.extend(points)
|
|
||||||
wires.pop()
|
|
||||||
continue
|
|
||||||
wires.append(wires.pop())
|
|
||||||
pathwire = Part.makePolygon(new_wire)
|
|
||||||
#Part.show(pathwire)
|
|
||||||
#return
|
|
||||||
'''
|
|
||||||
|
|
||||||
sectionLength = obj.Gap.Value
|
sectionLength = obj.Gap.Value
|
||||||
postLength = 0 #obj.Post.Diameter.Value #considerarlo 0 porque no influye
|
postLength = 0 #obj.Post.Diameter.Value #considerarlo 0 porque no influye
|
||||||
postPlacements = []
|
postPlacements = []
|
||||||
@@ -456,18 +373,19 @@ class _Fence(ArchComponent.Component):
|
|||||||
|
|
||||||
postPlacements.extend(placements)
|
postPlacements.extend(placements)
|
||||||
|
|
||||||
|
# 5. Generar geometría
|
||||||
postShapes, postFoundation = self.calculatePosts(obj, postPlacements)
|
postShapes, postFoundation = self.calculatePosts(obj, postPlacements)
|
||||||
sections, num = self.calculateSections(obj, postPlacements)
|
mesh = self.calculate_sections(obj, postPlacements)
|
||||||
|
|
||||||
postShapes = Part.makeCompound(postShapes)
|
postShapes = Part.makeCompound(postShapes)
|
||||||
postFoundation = Part.makeCompound(postFoundation)
|
postFoundation = Part.makeCompound(postFoundation)
|
||||||
sections = Part.makeCompound(sections)
|
|
||||||
compound = Part.makeCompound([postShapes, postFoundation, sections])
|
|
||||||
obj.Shape = compound
|
|
||||||
|
|
||||||
# Give information
|
# 6. Crear forma final
|
||||||
|
obj.Shape = Part.makeCompound([postShapes, postFoundation, mesh])
|
||||||
|
|
||||||
|
# 7. Actualizar propiedades
|
||||||
obj.NumberOfSections = count
|
obj.NumberOfSections = count
|
||||||
obj.NumberOfPosts = obj.NumberOfSections + 1
|
obj.NumberOfPosts = count + 1
|
||||||
obj.Length = pathLength
|
obj.Length = pathLength
|
||||||
obj.Concrete = count * postFoundation.SubShapes[0].Volume
|
obj.Concrete = count * postFoundation.SubShapes[0].Volume
|
||||||
|
|
||||||
@@ -496,7 +414,7 @@ class _Fence(ArchComponent.Component):
|
|||||||
|
|
||||||
def calculatePostPlacements(self, obj, pathwire, rotation):
|
def calculatePostPlacements(self, obj, pathwire, rotation):
|
||||||
postWidth = obj.Post.Diameter.Value
|
postWidth = obj.Post.Diameter.Value
|
||||||
transformationVector = FreeCAD.Vector(0, postWidth / 2, 0)
|
transformationVector = FreeCAD.Vector(0, - postWidth / 2, 0)
|
||||||
placements = calculatePlacementsOnPath(rotation, pathwire, int(obj.NumberOfSections) + 1, transformationVector, True)
|
placements = calculatePlacementsOnPath(rotation, pathwire, int(obj.NumberOfSections) + 1, transformationVector, True)
|
||||||
# The placement of the last object is always the second entry in the list.
|
# The placement of the last object is always the second entry in the list.
|
||||||
# So we move it to the end:
|
# So we move it to the end:
|
||||||
@@ -508,47 +426,36 @@ class _Fence(ArchComponent.Component):
|
|||||||
posts = []
|
posts = []
|
||||||
foundations = []
|
foundations = []
|
||||||
for placement in postPlacements:
|
for placement in postPlacements:
|
||||||
postCopy = obj.Post.Shape.copy()
|
new_post = obj.Post.Shape.copy()
|
||||||
postCopy = Part.Solid(postCopy)
|
new_post = Part.Solid(new_post)
|
||||||
postCopy.Placement = placement
|
new_post.Placement = placement
|
||||||
postCopy.Placement.Base.z += 100
|
new_post.Placement.Base.z += 100
|
||||||
posts.append(postCopy)
|
posts.append(new_post)
|
||||||
|
|
||||||
foundation = Part.makeCylinder(150, 700)
|
foundation = Part.makeCylinder(150, 700)
|
||||||
foundation.Placement = placement
|
foundation.Placement = placement
|
||||||
foundation.Placement.Base.z -= obj.Depth.Value
|
foundation.Placement.Base.z -= obj.Depth.Value
|
||||||
foundation = foundation.cut(postCopy)
|
#foundation = foundation.cut(new_post)
|
||||||
foundations.append(foundation)
|
foundations.append(foundation)
|
||||||
|
|
||||||
return posts, foundations
|
return posts, foundations
|
||||||
|
|
||||||
def calculateSections(self, obj, postPlacements):
|
def calculate_sections(self, obj, postPlacements):
|
||||||
shapes = []
|
offsetz = FreeCAD.Vector(0, 0, obj.MeshOffsetZ.Value)
|
||||||
faceNumbers = []
|
meshHeight = FreeCAD.Vector(0, 0, obj.MeshHeight.Value)
|
||||||
|
|
||||||
offsetz = obj.MeshOffsetZ.Value
|
points_down = []
|
||||||
meshHeight = obj.MeshHeight.Value
|
points_up = []
|
||||||
for i in range(len(postPlacements) - 1):
|
for i in range(len(postPlacements) - 1):
|
||||||
startPlacement = postPlacements[i]
|
p1 = postPlacements[i].Base + offsetz
|
||||||
endPlacement = postPlacements[i + 1]
|
p2 = postPlacements[i + 1].Base + offsetz
|
||||||
|
p3 = p1 + meshHeight
|
||||||
|
p4 = p2 + meshHeight
|
||||||
|
points_down.extend([p1, p2])
|
||||||
|
points_up.extend([p3, p4])
|
||||||
|
|
||||||
p1 = startPlacement.Base + FreeCAD.Vector(0, 0, offsetz)
|
shape = Part.makeRuledSurface(Part.makePolygon(points_down), Part.makePolygon(points_up))
|
||||||
p2 = endPlacement.Base + FreeCAD.Vector(0, 0, offsetz)
|
return shape
|
||||||
p3 = p2 + FreeCAD.Vector(0, 0, meshHeight)
|
|
||||||
p4 = p1 + FreeCAD.Vector(0, 0, meshHeight)
|
|
||||||
pointlist = [p1, p2, p3, p4, p1]
|
|
||||||
|
|
||||||
try:
|
|
||||||
pol = Part.makePolygon(pointlist)
|
|
||||||
face = Part.Face(pol)
|
|
||||||
shapes.append(face)
|
|
||||||
faceNumbers.append(1)
|
|
||||||
except:
|
|
||||||
print("No es posible crear la cara: ---------------------------------------------------")
|
|
||||||
print(" +++++ Start: ", startPlacement.Base, " - end: ", endPlacement.Base)
|
|
||||||
print(" +++++ algo: ", pointlist, "\n")
|
|
||||||
print("---------------------------------------------------\n")
|
|
||||||
return (shapes, faceNumbers)
|
|
||||||
|
|
||||||
def calculatePathWire(self, obj):
|
def calculatePathWire(self, obj):
|
||||||
if obj.Base:
|
if obj.Base:
|
||||||
@@ -561,7 +468,7 @@ class _Fence(ArchComponent.Component):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class _ViewProviderFence(ArchComponent.ViewProviderComponent):
|
class ViewProviderFence(ArchComponent.ViewProviderComponent):
|
||||||
"A View Provider for the Fence object"
|
"A View Provider for the Fence object"
|
||||||
|
|
||||||
def __init__(self, vobj):
|
def __init__(self, vobj):
|
||||||
@@ -641,7 +548,7 @@ class _ViewProviderFence(ArchComponent.ViewProviderComponent):
|
|||||||
children.append(self.Object.Gate)
|
children.append(self.Object.Gate)
|
||||||
return children
|
return children
|
||||||
|
|
||||||
class _FenceTaskPanel:
|
class FenceTaskPanel:
|
||||||
'''The TaskPanel to setup the fence'''
|
'''The TaskPanel to setup the fence'''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -774,15 +681,8 @@ class _FenceTaskPanel:
|
|||||||
self.form = [self.formFence, self.formPost, self.formFoundation]
|
self.form = [self.formFence, self.formPost, self.formFoundation]
|
||||||
|
|
||||||
# valores iniciales y creación del la valla:
|
# valores iniciales y creación del la valla:
|
||||||
import Draft
|
self.post = PVPlantFencePost.makeFencePost()
|
||||||
self.post = PVPlantFencePost.makeFencePost() # Arch.makePipe()
|
|
||||||
self.post.Label = "Post"
|
self.post.Label = "Post"
|
||||||
Draft.autogroup(self.post)
|
|
||||||
|
|
||||||
'''
|
|
||||||
self.section = self.makeGrid()
|
|
||||||
self.path = self.section.Base
|
|
||||||
'''
|
|
||||||
|
|
||||||
FreeCAD.ActiveDocument.recompute()
|
FreeCAD.ActiveDocument.recompute()
|
||||||
self.fence = makePVPlantFence(self.section, self.post, self.path)
|
self.fence = makePVPlantFence(self.section, self.post, self.path)
|
||||||
@@ -858,7 +758,7 @@ class CommandPVPlantFence:
|
|||||||
return not FreeCAD.ActiveDocument is None
|
return not FreeCAD.ActiveDocument is None
|
||||||
|
|
||||||
def Activated(self):
|
def Activated(self):
|
||||||
self.TaskPanel = _FenceTaskPanel()
|
self.TaskPanel = FenceTaskPanel()
|
||||||
FreeCADGui.Control.showDialog(self.TaskPanel)
|
FreeCADGui.Control.showDialog(self.TaskPanel)
|
||||||
|
|
||||||
|
|
||||||
@@ -876,12 +776,13 @@ if FreeCAD.GuiUp:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def IsActive(self):
|
def IsActive(self):
|
||||||
|
site = FreeCAD.ActiveDocument.getObject("Site")
|
||||||
return (not (FreeCAD.ActiveDocument is None) and
|
return (not (FreeCAD.ActiveDocument is None) and
|
||||||
not (FreeCAD.ActiveDocument.getObject("Site") is None) and
|
not (site is None) and
|
||||||
not (FreeCAD.ActiveDocument.getObject("Terrain") is None))
|
not (site.Terrain is None))
|
||||||
|
|
||||||
import PVPlantFenceGate
|
import Civil.Fence.PVPlantFenceGate as PVPlantFenceGate
|
||||||
FreeCADGui.addCommand('PVPlantFence', CommandPVPlantFence())
|
FreeCADGui.addCommand('PVPlantFence', CommandPVPlantFence())
|
||||||
FreeCADGui.addCommand('PVPlantGate', PVPlantFenceGate._CommandPVPlantGate())
|
FreeCADGui.addCommand('PVPlantGate', PVPlantFenceGate.CommandPVPlantGate())
|
||||||
FreeCADGui.addCommand('PVPlantFencePost', PVPlantFencePost._CommandFencePost())
|
FreeCADGui.addCommand('PVPlantFencePost', PVPlantFencePost.CommandFencePost())
|
||||||
#FreeCADGui.addCommand('PVPlantFenceGroup', CommandFenceGroup())
|
#FreeCADGui.addCommand('PVPlantFenceGroup', CommandFenceGroup())
|
||||||
@@ -202,7 +202,7 @@ class ViewProviderGate:
|
|||||||
children.append(self.Object.Base)
|
children.append(self.Object.Base)
|
||||||
return children
|
return children
|
||||||
|
|
||||||
class _CommandPVPlantGate:
|
class CommandPVPlantGate:
|
||||||
"the PVPlant Fence command definition"
|
"the PVPlant Fence command definition"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import ArchComponent
|
|
||||||
import FreeCAD
|
import FreeCAD
|
||||||
|
import Part
|
||||||
|
import ArchComponent
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
if FreeCAD.GuiUp:
|
||||||
import FreeCADGui
|
import FreeCADGui
|
||||||
@@ -9,8 +10,6 @@ else:
|
|||||||
# \cond
|
# \cond
|
||||||
def translate(ctxt, txt):
|
def translate(ctxt, txt):
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
|
|
||||||
def QT_TRANSLATE_NOOP(ctxt, txt):
|
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||||
return txt
|
return txt
|
||||||
# \endcond
|
# \endcond
|
||||||
@@ -21,20 +20,14 @@ except AttributeError:
|
|||||||
def _fromUtf8(s):
|
def _fromUtf8(s):
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
def makeFencePost(diameter=48, length=3000, placement=None, name="FencePost"):
|
||||||
def makeFencePost(diameter=48, length=3000, placement=None, name="Post"):
|
|
||||||
"makePipe([baseobj,diamerter,length,placement,name]): creates an pipe object from the given base object"
|
|
||||||
|
|
||||||
if not FreeCAD.ActiveDocument:
|
|
||||||
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
|
||||||
obj.Label = name
|
obj.Label = name
|
||||||
_FencePost(obj)
|
FencePost(obj)
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
if FreeCAD.GuiUp:
|
||||||
_ViewProviderFencePost(obj.ViewObject)
|
ViewProviderFencePost(obj.ViewObject)
|
||||||
|
|
||||||
obj.Length = length
|
obj.Length = length
|
||||||
obj.Diameter = diameter
|
obj.Diameter = diameter
|
||||||
@@ -45,18 +38,13 @@ def makeFencePost(diameter=48, length=3000, placement=None, name="Post"):
|
|||||||
|
|
||||||
|
|
||||||
def makeFenceReinforcePost(diameter=48, length=3000, placement=None, name="Post"):
|
def makeFenceReinforcePost(diameter=48, length=3000, placement=None, name="Post"):
|
||||||
"makePipe([baseobj,diamerter,length,placement,name]): creates an pipe object from the given base object"
|
|
||||||
|
|
||||||
if not FreeCAD.ActiveDocument:
|
|
||||||
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
|
||||||
obj.Label = name
|
obj.Label = name
|
||||||
_FenceReinforcePostPost(obj)
|
FenceReinforcePost(obj)
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
if FreeCAD.GuiUp:
|
||||||
_ViewProviderFencePost(obj.ViewObject)
|
ViewProviderFencePost(obj.ViewObject)
|
||||||
|
|
||||||
obj.Length = length
|
obj.Length = length
|
||||||
obj.Diameter = diameter
|
obj.Diameter = diameter
|
||||||
@@ -66,7 +54,7 @@ def makeFenceReinforcePost(diameter=48, length=3000, placement=None, name="Post"
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class _FencePost(ArchComponent.Component):
|
class FencePost(ArchComponent.Component):
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
ArchComponent.Component.__init__(self, obj)
|
ArchComponent.Component.__init__(self, obj)
|
||||||
self.setProperties(obj)
|
self.setProperties(obj)
|
||||||
@@ -80,10 +68,10 @@ class _FencePost(ArchComponent.Component):
|
|||||||
obj.addProperty("App::PropertyLength", "Diameter", "Pipe",
|
obj.addProperty("App::PropertyLength", "Diameter", "Pipe",
|
||||||
QT_TRANSLATE_NOOP("App::Property", "The diameter of this pipe, if not based on a profile")
|
QT_TRANSLATE_NOOP("App::Property", "The diameter of this pipe, if not based on a profile")
|
||||||
).Diameter = 48
|
).Diameter = 48
|
||||||
if not "Thickness" in pl:
|
'''if not "Thickness" in pl:
|
||||||
obj.addProperty("App::PropertyLength", "Thickness", "Pipe",
|
obj.addProperty("App::PropertyLength", "Thickness", "Pipe",
|
||||||
QT_TRANSLATE_NOOP("App::Property", "The Thickness of this pipe, if not based on a profile")
|
QT_TRANSLATE_NOOP("App::Property", "The Thickness of this pipe, if not based on a profile")
|
||||||
).Thickness = 4
|
).Thickness = 4'''
|
||||||
if not "Length" in pl:
|
if not "Length" in pl:
|
||||||
obj.addProperty("App::PropertyLength", "Length", "Pipe",
|
obj.addProperty("App::PropertyLength", "Length", "Pipe",
|
||||||
QT_TRANSLATE_NOOP("App::Property", "The length of this pipe, if not based on an edge")
|
QT_TRANSLATE_NOOP("App::Property", "The length of this pipe, if not based on an edge")
|
||||||
@@ -94,86 +82,49 @@ class _FencePost(ArchComponent.Component):
|
|||||||
ArchComponent.Component.onDocumentRestored(self, obj)
|
ArchComponent.Component.onDocumentRestored(self, obj)
|
||||||
self.setProperties(obj)
|
self.setProperties(obj)
|
||||||
|
|
||||||
|
def get_axis(self, obj, lip_heigth):
|
||||||
|
wire = Part.makeLine(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, obj.Length.Value - lip_heigth))
|
||||||
|
#wire = Part.makePolygon(FreeCAD.Vector(0, 0, obj.Length.Value - lip_heigth),)
|
||||||
|
return Part.Wire(wire)
|
||||||
|
|
||||||
def execute(self, obj):
|
def execute(self, obj):
|
||||||
import Part
|
|
||||||
pl = obj.Placement
|
pl = obj.Placement
|
||||||
|
|
||||||
if obj.CloneOf:
|
lip_heigth = 20
|
||||||
obj.Shape = obj.CloneOf.Shape
|
radius = obj.Diameter.Value / 2
|
||||||
else:
|
|
||||||
w = self.getProfile(obj)
|
|
||||||
try:
|
|
||||||
# sh = w.makePipeShell([p], True, False, 2)
|
|
||||||
sh = w.revolve(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(0.0, 0.0, 1.0), 360)
|
|
||||||
sh = Part.Solid(sh)
|
|
||||||
except:
|
|
||||||
FreeCAD.Console.PrintError("Unable to build the pipe \n")
|
|
||||||
else:
|
|
||||||
obj.Shape = sh
|
|
||||||
obj.Placement = pl
|
|
||||||
|
|
||||||
|
# para que sea una función que sirva para los postes rectos y con curva:
|
||||||
|
axis = self.get_axis(obj, lip_heigth)
|
||||||
|
profile = Part.Wire([Part.Circle(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), radius).toShape()])
|
||||||
|
post = axis.makePipeShell([profile, ],True,True,2)
|
||||||
|
|
||||||
|
lip = Part.makeCylinder(radius + 2, lip_heigth)
|
||||||
|
lip = lip.makeFillet(5, [lip.Edges[0]])
|
||||||
|
|
||||||
|
# Obtener caras
|
||||||
|
face_post = post.Faces[2] # Cara superior del cilindro largo
|
||||||
|
face_lip = lip.Faces[2] # Cara inferior del cilindro corto
|
||||||
|
|
||||||
|
# Calcular centro y normal de las caras
|
||||||
|
face_post_center = face_post.CenterOfMass
|
||||||
|
face_post_normal = face_post.normalAt(0, 0)
|
||||||
|
face_lip_center = face_lip.CenterOfMass
|
||||||
|
face_lip_normal = face_lip.normalAt(0, 0)
|
||||||
|
|
||||||
|
# Calcular rotación para alinear normales (ajustar dirección)
|
||||||
|
rotacion = FreeCAD.Rotation(face_lip_normal, -face_post_normal) # Invertir normal del cilindro corto
|
||||||
|
lip.Placement.Rotation = rotacion.multiply(lip.Placement.Rotation)
|
||||||
|
|
||||||
|
# Calcular traslación: mover centro del cilindro corto al centro del cilindro largo
|
||||||
|
traslacion = face_post_center - rotacion.multVec(face_lip_center)
|
||||||
|
lip.Placement.Base = traslacion #face_post_center
|
||||||
|
|
||||||
|
obj.Shape = post.fuse(lip)
|
||||||
|
obj.Placement = pl
|
||||||
return
|
return
|
||||||
|
|
||||||
# ------------------------- Prueba para apoyos de refuerzo:
|
|
||||||
import math
|
|
||||||
L = math.pi / 2 * (obj.Diameter.Value - 2 * obj.Thickness.Value)
|
|
||||||
|
|
||||||
v1 = FreeCAD.Vector(L / 2, 0, obj.Thickness.Value)
|
class FenceReinforcePost(ArchComponent.Component):
|
||||||
vc1 = FreeCAD.Vector(L / 2 + obj.Thickness.Value, 0, 0)
|
|
||||||
v2 = FreeCAD.Vector(L / 2, 0, -obj.Thickness.Value)
|
|
||||||
|
|
||||||
v11 = FreeCAD.Vector(-L / 2, 0, obj.Thickness.Value)
|
|
||||||
vc11 = FreeCAD.Vector(-(L / 2 + obj.Thickness.Value), 0, 0)
|
|
||||||
v21 = FreeCAD.Vector(-L / 2, 0, -obj.Thickness.Value)
|
|
||||||
|
|
||||||
arc1 = Part.Arc(v1, vc1, v2).toShape()
|
|
||||||
arc11 = Part.Arc(v11, vc11, v21).toShape()
|
|
||||||
line1 = Part.LineSegment(v11, v1).toShape()
|
|
||||||
line2 = Part.LineSegment(v21, v2).toShape()
|
|
||||||
w = Part.Wire([arc1, line2, arc11, line1])
|
|
||||||
face = Part.Face(w)
|
|
||||||
pro = face.extrude(FreeCAD.Vector(0, 40, 0))
|
|
||||||
|
|
||||||
#Part.Circle(Center, Normal, Radius)
|
|
||||||
cir1 = Part.Face(Part.Wire(Part.Circle(FreeCAD.Vector(0, -200, 0), FreeCAD.Vector(0, 1, 0), obj.Diameter.Value / 2).toShape()))
|
|
||||||
ext = cir1.extrude(FreeCAD.Vector(0, 170, 0))
|
|
||||||
cir2 = Part.Circle(FreeCAD.Vector(0, -30, 0), FreeCAD.Vector(0, 1, 0), obj.Diameter.Value/2).toShape()
|
|
||||||
loft = Part.makeLoft([cir2, w], True)
|
|
||||||
ext = ext.fuse([loft, pro])
|
|
||||||
Part.show(ext)
|
|
||||||
|
|
||||||
|
|
||||||
def getProfile(self, obj):
|
|
||||||
import Part
|
|
||||||
sin45 = 0.707106781
|
|
||||||
radio = obj.Diameter.Value / 2
|
|
||||||
taph = 20
|
|
||||||
tapw = radio + 2
|
|
||||||
chamfer = 5
|
|
||||||
chamfer2 = chamfer * sin45
|
|
||||||
|
|
||||||
edge1 = Part.makeLine(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(radio, 0, 0))
|
|
||||||
edge2 = Part.makeLine(FreeCAD.Vector(radio, 0, 0), FreeCAD.Vector(radio, 0, obj.Length.Value - taph))
|
|
||||||
edge3 = Part.makeLine(FreeCAD.Vector(radio, 0, obj.Length.Value - taph),
|
|
||||||
FreeCAD.Vector(tapw, 0, obj.Length.Value - taph))
|
|
||||||
edge4 = Part.makeLine(FreeCAD.Vector(tapw, 0, obj.Length.Value - taph),
|
|
||||||
FreeCAD.Vector(tapw, 0, obj.Length.Value - chamfer))
|
|
||||||
if True:
|
|
||||||
edge5 = Part.makeLine(FreeCAD.Vector(tapw, 0, obj.Length.Value - chamfer),
|
|
||||||
FreeCAD.Vector(tapw - chamfer, 0, obj.Length.Value))
|
|
||||||
else:
|
|
||||||
edge5 = Part.Arc(FreeCAD.Vector(tapw, 0, obj.Length.Value - chamfer),
|
|
||||||
FreeCAD.Vector(tapw - chamfer2, 0, obj.Length.Value - chamfer2),
|
|
||||||
FreeCAD.Vector(tapw - chamfer, 0, obj.Length.Value)
|
|
||||||
).toShape()
|
|
||||||
edge6 = Part.makeLine(FreeCAD.Vector(tapw - chamfer, 0, obj.Length.Value),
|
|
||||||
FreeCAD.Vector(0, 0, obj.Length.Value))
|
|
||||||
w = Part.Wire([edge1, edge2, edge3, edge4, edge5, edge6])
|
|
||||||
|
|
||||||
return w
|
|
||||||
|
|
||||||
|
|
||||||
class _FenceReinforcePost(ArchComponent.Component):
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
ArchComponent.Component.__init__(self, obj)
|
ArchComponent.Component.__init__(self, obj)
|
||||||
self.setProperties(obj)
|
self.setProperties(obj)
|
||||||
@@ -199,10 +150,18 @@ class _FenceReinforcePost(ArchComponent.Component):
|
|||||||
self.setProperties(obj)
|
self.setProperties(obj)
|
||||||
|
|
||||||
def execute(self, obj):
|
def execute(self, obj):
|
||||||
|
|
||||||
pl = obj.Placement
|
pl = obj.Placement
|
||||||
w = self.getWire(obj)
|
|
||||||
|
|
||||||
|
lip_heigth = 20
|
||||||
|
post = Part.makeCylinder(obj.Diameter.Value / 2, obj.Length.Value - lip_heigth)
|
||||||
|
lip = Part.makeCylinder(obj.Diameter.Value / 2 + 2, lip_heigth)
|
||||||
|
lip = lip.makeFillet(5, [lip.Edges[0]])
|
||||||
|
lip.translate(FreeCAD.Vector(0, 0, obj.Length.Value - lip_heigth))
|
||||||
|
obj.Shape = post.fuse(lip)
|
||||||
|
obj.Placement = pl
|
||||||
|
return
|
||||||
|
|
||||||
|
w = self.getWire(obj)
|
||||||
try:
|
try:
|
||||||
# sh = w.makePipeShell([p], True, False, 2)
|
# sh = w.makePipeShell([p], True, False, 2)
|
||||||
sh = w.revolve(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(0.0, 0.0, 1.0), 360)
|
sh = w.revolve(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(0.0, 0.0, 1.0), 360)
|
||||||
@@ -244,7 +203,7 @@ class _FenceReinforcePost(ArchComponent.Component):
|
|||||||
return w
|
return w
|
||||||
|
|
||||||
|
|
||||||
class _ViewProviderFencePost(ArchComponent.ViewProviderComponent):
|
class ViewProviderFencePost(ArchComponent.ViewProviderComponent):
|
||||||
"A View Provider for the Pipe object"
|
"A View Provider for the Pipe object"
|
||||||
|
|
||||||
def __init__(self, vobj):
|
def __init__(self, vobj):
|
||||||
@@ -254,7 +213,7 @@ class _ViewProviderFencePost(ArchComponent.ViewProviderComponent):
|
|||||||
return ":/icons/Arch_Pipe_Tree.svg"
|
return ":/icons/Arch_Pipe_Tree.svg"
|
||||||
|
|
||||||
|
|
||||||
class _CommandFencePost:
|
class CommandFencePost:
|
||||||
"the Arch Pipe command definition"
|
"the Arch Pipe command definition"
|
||||||
|
|
||||||
def GetResources(self):
|
def GetResources(self):
|
||||||
@@ -269,17 +228,5 @@ class _CommandFencePost:
|
|||||||
return not FreeCAD.ActiveDocument is None
|
return not FreeCAD.ActiveDocument is None
|
||||||
|
|
||||||
def Activated(self):
|
def Activated(self):
|
||||||
if True:
|
makeFencePost()
|
||||||
makeFencePost()
|
|
||||||
else:
|
|
||||||
FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Create Pipe"))
|
|
||||||
FreeCADGui.addModule("Arch")
|
|
||||||
FreeCADGui.doCommand("obj = Arch.makePipe()")
|
|
||||||
FreeCADGui.addModule("Draft")
|
|
||||||
FreeCADGui.doCommand("Draft.autogroup(obj)")
|
|
||||||
FreeCAD.ActiveDocument.commitTransaction()
|
|
||||||
FreeCAD.ActiveDocument.recompute()
|
FreeCAD.ActiveDocument.recompute()
|
||||||
|
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
|
||||||
FreeCADGui.addCommand('FencePost', _CommandFencePost())
|
|
||||||
397
Electrical/PowerConverter/PowerConverter.py
Normal file
397
Electrical/PowerConverter/PowerConverter.py
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
# /**********************************************************************
|
||||||
|
# * *
|
||||||
|
# * 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 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")
|
||||||
|
|
||||||
|
|
||||||
|
def makePCS():
|
||||||
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "StringInverter")
|
||||||
|
PowerConverter(obj)
|
||||||
|
ViewProviderStringInverter(obj.ViewObject)
|
||||||
|
|
||||||
|
try:
|
||||||
|
folder = FreeCAD.ActiveDocument.StringInverters
|
||||||
|
except:
|
||||||
|
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'StringInverters')
|
||||||
|
folder.Label = "StringInverters"
|
||||||
|
folder.addObject(obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class PowerConverter(ArchComponent.Component):
|
||||||
|
def __init__(self, obj):
|
||||||
|
''' Initialize the Area object '''
|
||||||
|
ArchComponent.Component.__init__(self, obj)
|
||||||
|
|
||||||
|
self.oldMPPTs = 0
|
||||||
|
|
||||||
|
self.Type = None
|
||||||
|
self.obj = None
|
||||||
|
self.setProperties(obj)
|
||||||
|
|
||||||
|
def setProperties(self, obj):
|
||||||
|
pl = obj.PropertiesList
|
||||||
|
|
||||||
|
if not "File" in pl:
|
||||||
|
obj.addProperty("App::PropertyFile",
|
||||||
|
"File",
|
||||||
|
"Inverter",
|
||||||
|
"The base file this component is built upon")
|
||||||
|
|
||||||
|
if not ("MPPTs" in pl):
|
||||||
|
obj.addProperty("App::PropertyQuantity",
|
||||||
|
"MPPTs",
|
||||||
|
"Inverter",
|
||||||
|
"Points that define the area"
|
||||||
|
).MPPTs = 0
|
||||||
|
|
||||||
|
if not ("Generator" in pl):
|
||||||
|
obj.addProperty("App::PropertyEnumeration",
|
||||||
|
"Generator",
|
||||||
|
"Inverter",
|
||||||
|
"Points that define the area"
|
||||||
|
).Generator = ["Generic", "Library"]
|
||||||
|
obj.Generator = "Generic"
|
||||||
|
|
||||||
|
if not ("Type" in pl):
|
||||||
|
obj.addProperty("App::PropertyString",
|
||||||
|
"Type",
|
||||||
|
"Base",
|
||||||
|
"Points that define the area"
|
||||||
|
).Type = "PowerConverter"
|
||||||
|
obj.setEditorMode("Type", 1)
|
||||||
|
|
||||||
|
self.Type = obj.Type
|
||||||
|
obj.Proxy = self
|
||||||
|
|
||||||
|
def onDocumentRestored(self, obj):
|
||||||
|
""" Method run when the document is restored """
|
||||||
|
self.setProperties(obj)
|
||||||
|
|
||||||
|
def onBeforeChange(self, obj, prop):
|
||||||
|
|
||||||
|
if prop == "MPPTs":
|
||||||
|
self.oldMPPTs = int(obj.MPPTs)
|
||||||
|
|
||||||
|
def onChanged(self, obj, prop):
|
||||||
|
''' '''
|
||||||
|
|
||||||
|
if prop == "Generator":
|
||||||
|
if obj.Generator == "Generic":
|
||||||
|
obj.setEditorMode("MPPTs", 0)
|
||||||
|
else:
|
||||||
|
obj.setEditorMode("MPPTs", 1)
|
||||||
|
|
||||||
|
if prop == "MPPTs":
|
||||||
|
''' '''
|
||||||
|
if self.oldMPPTs > obj.MPPTs:
|
||||||
|
''' borrar sobrantes '''
|
||||||
|
obj.removeProperty()
|
||||||
|
|
||||||
|
elif self.oldMPPTs < obj.MPPTs:
|
||||||
|
''' crear los faltantes '''
|
||||||
|
for i in range(self.oldMPPTs, int(obj.MPPTs)):
|
||||||
|
''' '''
|
||||||
|
print(i)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if (prop == "File") and obj.File:
|
||||||
|
''' '''
|
||||||
|
|
||||||
|
def execute(self, obj):
|
||||||
|
''' '''
|
||||||
|
# obj.Shape: compound
|
||||||
|
# |- body: compound
|
||||||
|
# |-- inverter: solid
|
||||||
|
# |-- door: solid
|
||||||
|
# |-- holder: solid
|
||||||
|
|
||||||
|
# |- connectors: compound
|
||||||
|
# |-- DC: compound
|
||||||
|
# |--- MPPT 1..x: compound
|
||||||
|
# |---- positive: compound
|
||||||
|
# |----- connector 1..y: ??
|
||||||
|
# |---- negative 1..y: compound
|
||||||
|
# |----- connector 1..y: ??
|
||||||
|
# |-- AC: compound
|
||||||
|
# |--- R,S,T,: ??
|
||||||
|
# |-- Communication
|
||||||
|
|
||||||
|
pl = obj.Placement
|
||||||
|
filename = self.getFile(obj)
|
||||||
|
if filename:
|
||||||
|
parts = self.getPartsList(obj)
|
||||||
|
if parts:
|
||||||
|
zdoc = zipfile.ZipFile(filename)
|
||||||
|
if zdoc:
|
||||||
|
f = zdoc.open(parts[list(parts.keys())[-1]][1])
|
||||||
|
shapedata = f.read()
|
||||||
|
f.close()
|
||||||
|
shapedata = shapedata.decode("utf8")
|
||||||
|
shape = self.cleanShape(shapedata, obj, parts[list(parts.keys())[-1]][2])
|
||||||
|
obj.Shape = shape
|
||||||
|
if not pl.isIdentity():
|
||||||
|
obj.Placement = pl
|
||||||
|
obj.MPPTs = len(shape.SubShapes[1].SubShapes[0].SubShapes)
|
||||||
|
|
||||||
|
def cleanShape(self, shapedata, obj, materials):
|
||||||
|
"cleans the imported shape"
|
||||||
|
|
||||||
|
import Part
|
||||||
|
shape = Part.Shape()
|
||||||
|
shape.importBrepFromString(shapedata)
|
||||||
|
'''if obj.FuseArch and materials:
|
||||||
|
# separate lone edges
|
||||||
|
shapes = []
|
||||||
|
for edge in shape.Edges:
|
||||||
|
found = False
|
||||||
|
for solid in shape.Solids:
|
||||||
|
for soledge in solid.Edges:
|
||||||
|
if edge.hashCode() == soledge.hashCode():
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
shapes.append(edge)
|
||||||
|
print("solids:",len(shape.Solids),"mattable:",materials)
|
||||||
|
for key,solindexes in materials.items():
|
||||||
|
if key == "Undefined":
|
||||||
|
# do not join objects with no defined material
|
||||||
|
for solindex in [int(i) for i in solindexes.split(",")]:
|
||||||
|
shapes.append(shape.Solids[solindex])
|
||||||
|
else:
|
||||||
|
fusion = None
|
||||||
|
for solindex in [int(i) for i in solindexes.split(",")]:
|
||||||
|
if not fusion:
|
||||||
|
fusion = shape.Solids[solindex]
|
||||||
|
else:
|
||||||
|
fusion = fusion.fuse(shape.Solids[solindex])
|
||||||
|
if fusion:
|
||||||
|
shapes.append(fusion)
|
||||||
|
shape = Part.makeCompound(shapes)
|
||||||
|
try:
|
||||||
|
shape = shape.removeSplitter()
|
||||||
|
except Exception:
|
||||||
|
print(obj.Label,": error removing splitter")'''
|
||||||
|
return shape
|
||||||
|
|
||||||
|
def getFile(self, obj, filename=None):
|
||||||
|
"gets a valid file, if possible"
|
||||||
|
|
||||||
|
if not filename:
|
||||||
|
filename = obj.File
|
||||||
|
if not filename:
|
||||||
|
return None
|
||||||
|
if not filename.lower().endswith(".fcstd"):
|
||||||
|
return None
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
# search for the file in the current directory if not found
|
||||||
|
basename = os.path.basename(filename)
|
||||||
|
currentdir = os.path.dirname(obj.Document.FileName)
|
||||||
|
altfile = os.path.join(currentdir,basename)
|
||||||
|
if altfile == obj.Document.FileName:
|
||||||
|
return None
|
||||||
|
elif os.path.exists(altfile):
|
||||||
|
return altfile
|
||||||
|
else:
|
||||||
|
# search for subpaths in current folder
|
||||||
|
altfile = None
|
||||||
|
subdirs = self.splitall(os.path.dirname(filename))
|
||||||
|
for i in range(len(subdirs)):
|
||||||
|
subpath = [currentdir]+subdirs[-i:]+[basename]
|
||||||
|
altfile = os.path.join(*subpath)
|
||||||
|
if os.path.exists(altfile):
|
||||||
|
return altfile
|
||||||
|
return None
|
||||||
|
return filename
|
||||||
|
|
||||||
|
def getPartsList(self, obj, filename=None):
|
||||||
|
|
||||||
|
"returns a list of Part-based objects in a FCStd file"
|
||||||
|
|
||||||
|
parts = {}
|
||||||
|
materials = {}
|
||||||
|
filename = self.getFile(obj,filename)
|
||||||
|
if not filename:
|
||||||
|
return parts
|
||||||
|
zdoc = zipfile.ZipFile(filename)
|
||||||
|
with zdoc.open("Document.xml") as docf:
|
||||||
|
name = None
|
||||||
|
label = None
|
||||||
|
part = None
|
||||||
|
materials = {}
|
||||||
|
writemode = False
|
||||||
|
for line in docf:
|
||||||
|
line = line.decode("utf8")
|
||||||
|
if "<Object name=" in line:
|
||||||
|
n = re.findall('name=\"(.*?)\"',line)
|
||||||
|
if n:
|
||||||
|
name = n[0]
|
||||||
|
elif "<Property name=\"Label\"" in line:
|
||||||
|
writemode = True
|
||||||
|
elif writemode and "<String value=" in line:
|
||||||
|
n = re.findall('value=\"(.*?)\"',line)
|
||||||
|
if n:
|
||||||
|
label = n[0]
|
||||||
|
writemode = False
|
||||||
|
elif "<Property name=\"Shape\" type=\"Part::PropertyPartShape\"" in line:
|
||||||
|
writemode = True
|
||||||
|
elif writemode and "<Part file=" in line:
|
||||||
|
n = re.findall('file=\"(.*?)\"',line)
|
||||||
|
if n:
|
||||||
|
part = n[0]
|
||||||
|
writemode = False
|
||||||
|
elif "<Property name=\"MaterialsTable\" type=\"App::PropertyMap\"" in line:
|
||||||
|
writemode = True
|
||||||
|
elif writemode and "<Item key=" in line:
|
||||||
|
n = re.findall('key=\"(.*?)\"',line)
|
||||||
|
v = re.findall('value=\"(.*?)\"',line)
|
||||||
|
if n and v:
|
||||||
|
materials[n[0]] = v[0]
|
||||||
|
elif writemode and "</Map>" in line:
|
||||||
|
writemode = False
|
||||||
|
elif "</Object>" in line:
|
||||||
|
if name and label and part:
|
||||||
|
parts[name] = [label,part,materials]
|
||||||
|
name = None
|
||||||
|
label = None
|
||||||
|
part = None
|
||||||
|
materials = {}
|
||||||
|
writemode = False
|
||||||
|
return parts
|
||||||
|
|
||||||
|
def getColors(self,obj):
|
||||||
|
|
||||||
|
"returns the DiffuseColor of the referenced object"
|
||||||
|
|
||||||
|
filename = self.getFile(obj)
|
||||||
|
if not filename:
|
||||||
|
return None
|
||||||
|
part = obj.Part
|
||||||
|
if not obj.Part:
|
||||||
|
return None
|
||||||
|
zdoc = zipfile.ZipFile(filename)
|
||||||
|
if not "GuiDocument.xml" in zdoc.namelist():
|
||||||
|
return None
|
||||||
|
colorfile = None
|
||||||
|
with zdoc.open("GuiDocument.xml") as docf:
|
||||||
|
writemode1 = False
|
||||||
|
writemode2 = False
|
||||||
|
for line in docf:
|
||||||
|
line = line.decode("utf8")
|
||||||
|
if ("<ViewProvider name=" in line) and (part in line):
|
||||||
|
writemode1 = True
|
||||||
|
elif writemode1 and ("<Property name=\"DiffuseColor\"" in line):
|
||||||
|
writemode1 = False
|
||||||
|
writemode2 = True
|
||||||
|
elif writemode2 and ("<ColorList file=" in line):
|
||||||
|
n = re.findall('file=\"(.*?)\"',line)
|
||||||
|
if n:
|
||||||
|
colorfile = n[0]
|
||||||
|
break
|
||||||
|
if not colorfile:
|
||||||
|
return None
|
||||||
|
if not colorfile in zdoc.namelist():
|
||||||
|
return None
|
||||||
|
colors = []
|
||||||
|
cf = zdoc.open(colorfile)
|
||||||
|
buf = cf.read()
|
||||||
|
cf.close()
|
||||||
|
for i in range(1,int(len(buf)/4)):
|
||||||
|
colors.append((buf[i*4+3]/255.0,buf[i*4+2]/255.0,buf[i*4+1]/255.0,buf[i*4]/255.0))
|
||||||
|
if colors:
|
||||||
|
return colors
|
||||||
|
return None
|
||||||
|
|
||||||
|
def splitall(self,path):
|
||||||
|
|
||||||
|
"splits a path between its components"
|
||||||
|
|
||||||
|
allparts = []
|
||||||
|
while 1:
|
||||||
|
parts = os.path.split(path)
|
||||||
|
if parts[0] == path: # sentinel for absolute paths
|
||||||
|
allparts.insert(0, parts[0])
|
||||||
|
break
|
||||||
|
elif parts[1] == path: # sentinel for relative paths
|
||||||
|
allparts.insert(0, parts[1])
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
path = parts[0]
|
||||||
|
allparts.insert(0, parts[1])
|
||||||
|
return allparts
|
||||||
|
|
||||||
|
class ViewProviderStringInverter(ArchComponent.ViewProviderComponent):
|
||||||
|
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, I",
|
||||||
|
'MenuText': "String Inverter",
|
||||||
|
'ToolTip': "String Placement",}
|
||||||
|
|
||||||
|
def Activated(self):
|
||||||
|
sinverter = makeStringInverter()
|
||||||
|
|
||||||
|
def IsActive(self):
|
||||||
|
active = not (FreeCAD.ActiveDocument is None)
|
||||||
|
return active
|
||||||
|
|
||||||
|
if FreeCAD.GuiUp:
|
||||||
|
FreeCADGui.addCommand('PowerConverter', CommandPowerConverter())
|
||||||
|
|
||||||
387
Electrical/group.py
Normal file
387
Electrical/group.py
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
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_sphere = FreeCAD.ActiveDocument.addObject("Part::Sphere", f"CT_{i + 1}")
|
||||||
|
ct_sphere.Radius = 5000 # 2m de radio
|
||||||
|
ct_sphere.Placement.Base = FreeCAD.Vector(group['center'][0], group['center'][1], 0)
|
||||||
|
|
||||||
|
# Añadir propiedades personalizadas
|
||||||
|
ct_sphere.addProperty("App::PropertyLinkList", "Trackers", "CT",
|
||||||
|
"Lista de trackers asociados a este CT")
|
||||||
|
ct_sphere.addProperty("App::PropertyFloat", "TotalPower", "CT",
|
||||||
|
"Potencia total del grupo (W)")
|
||||||
|
ct_sphere.addProperty("App::PropertyFloat", "NominalPower", "CT",
|
||||||
|
"Potencia nominal del transformador (W)")
|
||||||
|
ct_sphere.addProperty("App::PropertyFloat", "Utilization", "CT",
|
||||||
|
"Porcentaje de utilización (Total/Nominal)")
|
||||||
|
|
||||||
|
# Establecer valores de las propiedades
|
||||||
|
ct_sphere.Trackers = group['trackers']
|
||||||
|
ct_sphere.TotalPower = group['total_power'].Value
|
||||||
|
ct_sphere.NominalPower = transformer_power
|
||||||
|
ct_sphere.Utilization = (group['total_power'].Value / transformer_power) * 100
|
||||||
|
|
||||||
|
# Configurar visualización
|
||||||
|
# Calcular color basado en utilización (verde < 100%, amarillo < 110%, rojo > 110%)
|
||||||
|
utilization = ct_sphere.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_sphere.ViewObject.ShapeColor = color
|
||||||
|
ct_sphere.ViewObject.Transparency = 40 # 40% de transparencia
|
||||||
|
|
||||||
|
# Añadir etiqueta con información
|
||||||
|
ct_sphere.ViewObject.DisplayMode = "Shaded"
|
||||||
|
ct_sphere.Label = f"CT {i + 1} ({ct_sphere.TotalPower / 1000:.1f}kW/{ct_sphere.NominalPower / 1000:.1f}kW)"
|
||||||
|
|
||||||
|
# Añadir al grupo principal
|
||||||
|
transformer_group.addObject(ct_sphere)
|
||||||
|
|
||||||
|
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_spheres = []
|
||||||
|
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_spheres = transformers_group.Group
|
||||||
|
if not self.ct_spheres:
|
||||||
|
FreeCAD.Console.PrintWarning("No hay Centros de Transformación en el grupo\n")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Obtener las posiciones de los CTs
|
||||||
|
for sphere in self.ct_spheres:
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -78,11 +78,12 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "PVPlantPlacement.ui"))
|
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "PVPlantPlacement.ui"))
|
||||||
self.form.setWindowIcon(QtGui.QIcon(os.path.join(PVPlantResources.DirIcons, "way.svg")))
|
self.form.setWindowIcon(QtGui.QIcon(os.path.join(PVPlantResources.DirIcons, "way.svg")))
|
||||||
|
|
||||||
self.form.buttonPVArea.clicked.connect(self.addPVArea)
|
|
||||||
#self.form.buttonAddFrame.clicked.connect(self.addFrames)
|
|
||||||
#self.form.buttonRemoveFrame.clicked.connect(self.removeFrame)
|
|
||||||
|
|
||||||
self.addFrames()
|
self.addFrames()
|
||||||
|
self.maxWidth = max([frame.Width.Value for frame in self.site.Frames])
|
||||||
|
|
||||||
|
self.form.buttonPVArea.clicked.connect(self.addPVArea)
|
||||||
|
self.form.editGapCols.valueChanged.connect(self.update_inner_spacing)
|
||||||
|
self.update_inner_spacing()
|
||||||
|
|
||||||
def addPVArea(self):
|
def addPVArea(self):
|
||||||
sel = FreeCADGui.Selection.getSelection()
|
sel = FreeCADGui.Selection.getSelection()
|
||||||
@@ -95,6 +96,10 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
list_item = QListWidgetItem(frame_setup.Name, self.form.listFrameSetups)
|
list_item = QListWidgetItem(frame_setup.Name, self.form.listFrameSetups)
|
||||||
list_item.setCheckState(QtCore.Qt.Checked)
|
list_item.setCheckState(QtCore.Qt.Checked)
|
||||||
|
|
||||||
|
def update_inner_spacing(self):
|
||||||
|
self.form.editInnerSpacing.setText(
|
||||||
|
("{} m".format((self.form.editGapCols.value() - self.maxWidth / 1000))))
|
||||||
|
|
||||||
def createFrameFromPoints(self, dataframe):
|
def createFrameFromPoints(self, dataframe):
|
||||||
from Mechanical.Frame import PVPlantFrame
|
from Mechanical.Frame import PVPlantFrame
|
||||||
try:
|
try:
|
||||||
@@ -104,22 +109,37 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
MechanicalGroup.Label = "Frames"
|
MechanicalGroup.Label = "Frames"
|
||||||
FreeCAD.ActiveDocument.MechanicalGroup.addObject(MechanicalGroup)
|
FreeCAD.ActiveDocument.MechanicalGroup.addObject(MechanicalGroup)
|
||||||
|
|
||||||
if self.form.cbSubfolders.checked:
|
if self.form.cbSubfolders.isChecked:
|
||||||
group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", self.PVArea.Label)
|
label = "Frames-" + self.PVArea.Label
|
||||||
group.Label = self.PVArea.Label
|
if label in [obj.Label for obj in FreeCAD.ActiveDocument.Frames.Group]:
|
||||||
MechanicalGroup.addObject(group)
|
MechanicalGroup = FreeCAD.ActiveDocument.getObject(label)[0]
|
||||||
MechanicalGroup = group
|
else:
|
||||||
|
group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", label)
|
||||||
placements = dataframe["placement"].tolist()
|
group.Label = label
|
||||||
types = dataframe["type"].tolist()
|
MechanicalGroup.addObject(group)
|
||||||
frames = []
|
MechanicalGroup = group
|
||||||
for idx in range(len(placements)):
|
try:
|
||||||
newrack = PVPlantFrame.makeTracker(setup=types[idx])
|
placements = dataframe["placement"].tolist()
|
||||||
newrack.Label = "Tracker"
|
types = dataframe["type"].tolist()
|
||||||
newrack.Visibility = False
|
frames = []
|
||||||
newrack.Placement = placements[idx]
|
for idx in range(len(placements)):
|
||||||
MechanicalGroup.addObject(newrack)
|
newrack = PVPlantFrame.makeTracker(setup=types[idx])
|
||||||
frames.append(newrack)
|
newrack.Label = "Tracker"
|
||||||
|
newrack.Visibility = False
|
||||||
|
newrack.Placement = placements[idx]
|
||||||
|
MechanicalGroup.addObject(newrack)
|
||||||
|
frames.append(newrack)
|
||||||
|
except:
|
||||||
|
placements = dataframe[0]
|
||||||
|
frames = []
|
||||||
|
for idx in placements:
|
||||||
|
print(idx)
|
||||||
|
newrack = PVPlantFrame.makeTracker(setup=idx[0])
|
||||||
|
newrack.Label = "Tracker"
|
||||||
|
newrack.Visibility = False
|
||||||
|
newrack.Placement = idx[1]
|
||||||
|
MechanicalGroup.addObject(newrack)
|
||||||
|
frames.append(newrack)
|
||||||
|
|
||||||
if self.PVArea.Name.startswith("FrameArea"):
|
if self.PVArea.Name.startswith("FrameArea"):
|
||||||
self.PVArea.Frames = frames
|
self.PVArea.Frames = frames
|
||||||
@@ -179,7 +199,7 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
return np.arange(startx, self.Area.BoundBox.XMax, self.gap_col, dtype=np.int64), \
|
return np.arange(startx, self.Area.BoundBox.XMax, self.gap_col, dtype=np.int64), \
|
||||||
np.arange(starty, self.Area.BoundBox.YMin, -self.gap_row, dtype=np.int64)
|
np.arange(starty, self.Area.BoundBox.YMin, -self.gap_row, dtype=np.int64)
|
||||||
|
|
||||||
def adjustToTerrain(self, coordinates):
|
def adjustToTerrain_old(self, coordinates):
|
||||||
mode = 1
|
mode = 1
|
||||||
terrain = self.Terrain.Mesh
|
terrain = self.Terrain.Mesh
|
||||||
|
|
||||||
@@ -276,6 +296,106 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
placeRegion(df)
|
placeRegion(df)
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
def _setup_terrain_interpolator(self):
|
||||||
|
"""Prepara interpolador del terreno para ajuste rápido"""
|
||||||
|
import numpy as np
|
||||||
|
from scipy.interpolate import LinearNDInterpolator
|
||||||
|
|
||||||
|
mesh = self.Terrain.Mesh
|
||||||
|
points = np.array([p.Vector for p in mesh.Points])
|
||||||
|
bbox = self.Area.BoundBox
|
||||||
|
|
||||||
|
# Filtrar puntos dentro del área de trabajo
|
||||||
|
in_bbox = [
|
||||||
|
p for p in points
|
||||||
|
if bbox.XMin <= p[0] <= bbox.XMax and
|
||||||
|
bbox.YMin <= p[1] <= bbox.YMax
|
||||||
|
]
|
||||||
|
|
||||||
|
if not in_bbox:
|
||||||
|
return None
|
||||||
|
|
||||||
|
coords = np.array(in_bbox)
|
||||||
|
return LinearNDInterpolator(coords[:, :2], coords[:, 2])
|
||||||
|
|
||||||
|
def adjustToTerrain(self, coordinates):
|
||||||
|
from scipy.ndimage import label as sclabel
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from scipy import stats
|
||||||
|
import MeshPart
|
||||||
|
|
||||||
|
# Crear matriz binaria
|
||||||
|
arr = np.array([[1 if obj != 0 else 0 for obj in col] for col in coordinates])
|
||||||
|
labeled_array, num_features = sclabel(arr)
|
||||||
|
|
||||||
|
# Construir DataFrame optimizado
|
||||||
|
data = []
|
||||||
|
terrain_interp = self._setup_terrain_interpolator()
|
||||||
|
|
||||||
|
for label in range(1, num_features + 1):
|
||||||
|
cols, rows = np.where(labeled_array == label)
|
||||||
|
for idx, (col, row) in enumerate(zip(cols, rows)):
|
||||||
|
frame_type, placement = coordinates[col][row]
|
||||||
|
data.append({
|
||||||
|
'ID': len(data) + 1,
|
||||||
|
'region': label,
|
||||||
|
'type': frame_type,
|
||||||
|
'column': col,
|
||||||
|
'row': row,
|
||||||
|
'placement': placement
|
||||||
|
})
|
||||||
|
|
||||||
|
df = pd.DataFrame(data)
|
||||||
|
|
||||||
|
# Ajustar al terreno
|
||||||
|
for idx, row in df.iterrows():
|
||||||
|
pl = row['placement']
|
||||||
|
yl = row['type'].Length.Value / 2
|
||||||
|
|
||||||
|
# Calcular puntos extremos
|
||||||
|
top_point = FreeCAD.Vector(pl.x, pl.y + yl, 0)
|
||||||
|
bot_point = FreeCAD.Vector(pl.x, pl.y - yl, 0)
|
||||||
|
|
||||||
|
# Usar interpolador si está disponible
|
||||||
|
if terrain_interp:
|
||||||
|
yy = np.linspace(bot_point.y, top_point.y, 10)
|
||||||
|
xx = np.full(10, pl.x)
|
||||||
|
zz = terrain_interp(xx, yy)
|
||||||
|
|
||||||
|
if not np.isnan(zz).all():
|
||||||
|
slope, intercept, *_ = stats.linregress(yy, zz)
|
||||||
|
z_top = slope * top_point.y + intercept
|
||||||
|
z_bot = slope * bot_point.y + intercept
|
||||||
|
else:
|
||||||
|
z_top = z_bot = 0
|
||||||
|
else:
|
||||||
|
# Fallback a proyección directa
|
||||||
|
line = Part.LineSegment(bot_point, top_point).toShape()
|
||||||
|
projected = MeshPart.projectShapeOnMesh(line, self.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))[0]
|
||||||
|
if len(projected) >= 2:
|
||||||
|
yy = [p.y for p in projected]
|
||||||
|
zz = [p.z for p in projected]
|
||||||
|
slope, intercept, *_ = stats.linregress(yy, zz)
|
||||||
|
z_top = slope * top_point.y + intercept
|
||||||
|
z_bot = slope * bot_point.y + intercept
|
||||||
|
else:
|
||||||
|
z_top = z_bot = 0
|
||||||
|
|
||||||
|
# Actualizar placement
|
||||||
|
new_top = FreeCAD.Vector(top_point.x, top_point.y, z_top)
|
||||||
|
new_bot = FreeCAD.Vector(bot_point.x, bot_point.y, z_bot)
|
||||||
|
|
||||||
|
new_pl = FreeCAD.Placement()
|
||||||
|
new_pl.Base = (new_top + new_bot) / 2
|
||||||
|
new_pl.Rotation = FreeCAD.Rotation(
|
||||||
|
FreeCAD.Vector(-1, 0, 0),
|
||||||
|
new_top - new_bot
|
||||||
|
)
|
||||||
|
df.at[idx, 'placement'] = new_pl
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
def isInside(self, frame, point):
|
def isInside(self, frame, point):
|
||||||
if self.Area.isInside(point, 10, True):
|
if self.Area.isInside(point, 10, True):
|
||||||
frame.Placement.Base = point
|
frame.Placement.Base = point
|
||||||
@@ -349,86 +469,68 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
if countcols == self.form.editColCount.value():
|
if countcols == self.form.editColCount.value():
|
||||||
offsetcols += valcols
|
offsetcols += valcols
|
||||||
countcols = 0
|
countcols = 0
|
||||||
print("/n/n")
|
|
||||||
print(cols)
|
|
||||||
return self.adjustToTerrain(cols)
|
return self.adjustToTerrain(cols)
|
||||||
|
|
||||||
def calculateNonAlignedArray(self):
|
def calculateNonAlignedArray(self):
|
||||||
gap_col = FreeCAD.Units.Quantity(self.form.editGapCols.text()).Value
|
pointsx, pointsy = self.getAligments()
|
||||||
gap_row = FreeCAD.Units.Quantity(self.form.editGapRows.text()).Value + max(self.Rack.Shape.BoundBox.XLength,
|
|
||||||
self.Rack.Shape.BoundBox.YLength)
|
|
||||||
offset_x = FreeCAD.Units.Quantity(self.form.editOffsetHorizontal.text()).Value
|
|
||||||
offset_y = FreeCAD.Units.Quantity(self.form.editOffsetVertical.text()).Value
|
|
||||||
|
|
||||||
Area = self.calculateWorkingArea()
|
footprints = []
|
||||||
|
for frame in self.FrameSetups:
|
||||||
|
xx = frame.Length.Value
|
||||||
|
yy = frame.Width.Value
|
||||||
|
xx_med = xx / 2
|
||||||
|
yy_med = yy / 2
|
||||||
|
rec = Part.makePolygon([FreeCAD.Vector(-xx_med, -yy_med, 0),
|
||||||
|
FreeCAD.Vector(xx_med, -yy_med, 0),
|
||||||
|
FreeCAD.Vector(xx_med, yy_med, 0),
|
||||||
|
FreeCAD.Vector(-xx_med, yy_med, 0),
|
||||||
|
FreeCAD.Vector(-xx_med, -yy_med, 0)])
|
||||||
|
rec.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), FreeCAD.Vector(0, 1, 0))
|
||||||
|
footprints.append([frame, rec])
|
||||||
|
ref = footprints.pop(0)
|
||||||
|
xx = ref[0].Length.Value
|
||||||
|
yy = ref[0].Width.Value
|
||||||
|
xx_med = xx / 2
|
||||||
|
yy_med = yy / 2
|
||||||
|
|
||||||
rec = Part.makePlane(self.Rack.Shape.BoundBox.YLength, self.Rack.Shape.BoundBox.XLength)
|
# variables for corridors:
|
||||||
|
countcols = 0
|
||||||
# TODO: revisar todo esto: -----------------------------------------------------------------
|
countrows = 0
|
||||||
sel = FreeCADGui.Selection.getSelectionEx()[0]
|
offsetcols = 0 # ??
|
||||||
refh = None
|
offsetrows = 0 # ??
|
||||||
refv = None
|
valcols = FreeCAD.Units.Quantity(self.form.editColGap.text()).Value - (self.gap_col - yy)
|
||||||
|
|
||||||
if len(sel.SubObjects) == 0:
|
|
||||||
refh = refv = Area.Edges[0]
|
|
||||||
|
|
||||||
if len(sel.SubObjects) == 1:
|
|
||||||
refh = refv = sel.SubObjects[0]
|
|
||||||
|
|
||||||
if len(sel.SubObjects) == 2:
|
|
||||||
if sel.SubObjects[0].BoundBox.XLength > sel.SubObjects[1].BoundBox.XLength:
|
|
||||||
refh = sel.SubObjects[0]
|
|
||||||
else:
|
|
||||||
refh = sel.SubObjects[1]
|
|
||||||
|
|
||||||
if sel.SubObjects[0].BoundBox.YLength > sel.SubObjects[1].BoundBox.YLength:
|
|
||||||
refv = sel.SubObjects[0]
|
|
||||||
else:
|
|
||||||
refv = sel.SubObjects[1]
|
|
||||||
|
|
||||||
steps = int((refv.BoundBox.XMax - Area.BoundBox.XMin + offset_x) / gap_col)
|
|
||||||
startx = refv.BoundBox.XMax + offset_x - gap_col * steps
|
|
||||||
# todo end ----------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
start = FreeCAD.Vector(startx, 0.0, 0.0)
|
|
||||||
pointsx = np.arange(start.x, Area.BoundBox.XMax, gap_col)
|
|
||||||
|
|
||||||
if self.form.groupCorridor.isChecked():
|
|
||||||
if (self.form.editColCount.value() > 0):
|
|
||||||
xlen = len(pointsx)
|
|
||||||
count = self.form.editColCount.value()
|
|
||||||
val = FreeCAD.Units.Quantity(self.form.editColGap.text()).Value - (
|
|
||||||
gap_col - min(self.Rack.Shape.BoundBox.XLength, self.Rack.Shape.BoundBox.YLength))
|
|
||||||
while count <= xlen:
|
|
||||||
for i, point in enumerate(pointsx):
|
|
||||||
if i >= count:
|
|
||||||
pointsx[i] += val
|
|
||||||
count += self.form.editColCount.value()
|
|
||||||
|
|
||||||
pl = []
|
pl = []
|
||||||
for point in pointsx:
|
for point in pointsx:
|
||||||
p1 = FreeCAD.Vector(point, Area.BoundBox.YMax, 0.0)
|
p1 = FreeCAD.Vector(point, self.Area.BoundBox.YMax, 0.0)
|
||||||
p2 = FreeCAD.Vector(point, Area.BoundBox.YMin, 0.0)
|
p2 = FreeCAD.Vector(point, self.Area.BoundBox.YMin, 0.0)
|
||||||
line = Part.makePolygon([p1, p2])
|
line = Part.makePolygon([p1, p2])
|
||||||
|
|
||||||
inter = Area.section([line])
|
inter = self.Area.section([line])
|
||||||
pts = [ver.Point for ver in inter.Vertexes] # todo: sort points
|
pts = [ver.Point for ver in inter.Vertexes] # todo: sort points
|
||||||
for i in range(0, len(pts), 2):
|
for i in range(0, len(pts), 2):
|
||||||
line = Part.LineSegment(pts[i], pts[i + 1])
|
line = Part.LineSegment(pts[i], pts[i + 1])
|
||||||
if line.length() >= rec.BoundBox.YLength:
|
if line.length() >= ref[1].BoundBox.YLength:
|
||||||
y1 = pts[i].y - rec.BoundBox.YLength
|
y1 = pts[i].y - ref[1].BoundBox.YLength / 2
|
||||||
cp = rec.copy()
|
cp = ref[1].copy()
|
||||||
cp.Placement.Base = FreeCAD.Vector(pts[i].x - rec.BoundBox.XLength / 2, y1, 0.0)
|
cp.Placement.Base = FreeCAD.Vector(pts[i].x, y1, 0.0)
|
||||||
inter = cp.cut([Area])
|
Part.show(cp)
|
||||||
y1 = min([ver.Point.y for ver in inter.Vertexes])
|
inter = cp.cut([self.Area])
|
||||||
pointsy = np.arange(y1, pts[i + 1].y, -gap_row)
|
pts1 = [ver.Point for ver in inter.Vertexes]
|
||||||
for point in pointsy:
|
if len(pts1) == 0:
|
||||||
cp = rec.copy()
|
continue
|
||||||
cp.Placement.Base = FreeCAD.Vector(pts[i].x - rec.BoundBox.XLength / 2, point, 0.0)
|
y1 = min(pts1, key=lambda p: p.y).y
|
||||||
cut = cp.cut([Area], 0)
|
pointsy = np.arange(y1, pts[i + 1].y, -self.gap_row)
|
||||||
if len(cut.Vertexes) == 0:
|
continue
|
||||||
Part.show(cp)
|
for pointy in pointsy:
|
||||||
pl.append(point)
|
cp = ref[1].copy()
|
||||||
|
cp.Placement.Base = FreeCAD.Vector(pts[i].x + ref[1].BoundBox.XLength / 2, pointy, 0.0)
|
||||||
|
cut = cp.cut([self.Area], 0)
|
||||||
|
#print(y1, " - ", pointy, " - ", len(cut.Vertexes))
|
||||||
|
#if len(cut.Vertexes) == 0:
|
||||||
|
Part.show(cp)
|
||||||
|
pl.append([ref[0], pointy])
|
||||||
return pl
|
return pl
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
@@ -446,21 +548,6 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
if (item := self.form.listFrameSetups.item(i)).checkState() == QtCore.Qt.Checked
|
if (item := self.form.listFrameSetups.item(i)).checkState() == QtCore.Qt.Checked
|
||||||
]
|
]
|
||||||
|
|
||||||
"""seen_lengths = set()
|
|
||||||
tmpframes = []
|
|
||||||
for frame in sorted(items, key=lambda rack: rack.Length, reverse=True):
|
|
||||||
if frame.Length not in seen_lengths:
|
|
||||||
seen_lengths.add(frame.Length)
|
|
||||||
tmpframes.append(frame)
|
|
||||||
'''found = False
|
|
||||||
for tmp in tmpframes:
|
|
||||||
if tmp.Length == frame.Length:
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
if not found:
|
|
||||||
tmpframes.append(frame)'''
|
|
||||||
self.FrameSetups = tmpframes.copy()"""
|
|
||||||
|
|
||||||
unique_frames = {frame.Length.Value: frame for frame in items}
|
unique_frames = {frame.Length.Value: frame for frame in items}
|
||||||
self.FrameSetups = sorted(list(unique_frames.values()), key=lambda rack: rack.Length, reverse=True)
|
self.FrameSetups = sorted(list(unique_frames.values()), key=lambda rack: rack.Length, reverse=True)
|
||||||
|
|
||||||
@@ -479,8 +566,14 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
dataframe = self.calculateNonAlignedArray()
|
dataframe = self.calculateNonAlignedArray()
|
||||||
# 3. Adjust to terrain:
|
# 3. Adjust to terrain:
|
||||||
self.createFrameFromPoints(dataframe)
|
self.createFrameFromPoints(dataframe)
|
||||||
FreeCAD.ActiveDocument.commitTransaction()
|
|
||||||
|
|
||||||
|
import Electrical.group as egroup
|
||||||
|
import importlib
|
||||||
|
importlib.reload(egroup)
|
||||||
|
egroup.groupTrackersToTransformers(5000000, self.gap_row + self.FrameSetups[0].Length.Value)
|
||||||
|
|
||||||
|
|
||||||
|
FreeCAD.ActiveDocument.commitTransaction()
|
||||||
FreeCAD.ActiveDocument.RecomputesFrozen = False
|
FreeCAD.ActiveDocument.RecomputesFrozen = False
|
||||||
params.SetBool("AutoSaveEnabled", auto_save_enabled)
|
params.SetBool("AutoSaveEnabled", auto_save_enabled)
|
||||||
|
|
||||||
@@ -489,6 +582,8 @@ class _PVPlantPlacementTaskPanel:
|
|||||||
FreeCADGui.Control.closeDialog()
|
FreeCADGui.Control.closeDialog()
|
||||||
FreeCAD.ActiveDocument.recompute()
|
FreeCAD.ActiveDocument.recompute()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------------------------------------------------
|
||||||
# function AdjustToTerrain
|
# function AdjustToTerrain
|
||||||
# Take a group of objects and adjust it to the slope and altitude of the terrain mesh. It detects the terrain mesh
|
# Take a group of objects and adjust it to the slope and altitude of the terrain mesh. It detects the terrain mesh
|
||||||
|
|||||||
@@ -22,16 +22,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QListWidget" name="listFrameSetups">
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>16777215</width>
|
|
||||||
<height>54</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="7" column="1">
|
<item row="7" column="1">
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@@ -60,20 +50,6 @@
|
|||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -335,7 +311,11 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="QLineEdit" name="editInnerSpacing"/>
|
<widget class="QLineEdit" name="editInnerSpacing">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="4" column="0">
|
||||||
<widget class="QLabel" name="label_8">
|
<widget class="QLabel" name="label_8">
|
||||||
@@ -503,11 +483,29 @@
|
|||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QLineEdit" name="editPVArea"/>
|
<widget class="QLineEdit" name="editPVArea"/>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QListWidget" name="listFrameSetups">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>54</height>
|
||||||
|
</size>
|
||||||
|
</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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>buttonAddFrame</tabstop>
|
|
||||||
<tabstop>buttonRemoveFrame</tabstop>
|
|
||||||
<tabstop>editPVArea</tabstop>
|
<tabstop>editPVArea</tabstop>
|
||||||
<tabstop>buttonPVArea</tabstop>
|
<tabstop>buttonPVArea</tabstop>
|
||||||
<tabstop>comboOrientation</tabstop>
|
<tabstop>comboOrientation</tabstop>
|
||||||
|
|||||||
@@ -673,8 +673,7 @@ if FreeCAD.GuiUp:
|
|||||||
FreeCADGui.addCommand('PVPlantTracker', PVPlantFrame.CommandTracker())
|
FreeCADGui.addCommand('PVPlantTracker', PVPlantFrame.CommandTracker())
|
||||||
FreeCADGui.addCommand('RackType', CommandRackGroup())
|
FreeCADGui.addCommand('RackType', CommandRackGroup())
|
||||||
|
|
||||||
|
from Civil.Fence import PVPlantFence
|
||||||
import PVPlantFence
|
|
||||||
FreeCADGui.addCommand('PVPlantFenceGroup', PVPlantFence.CommandFenceGroup())
|
FreeCADGui.addCommand('PVPlantFenceGroup', PVPlantFence.CommandFenceGroup())
|
||||||
|
|
||||||
projectlist = [ # "Reload",
|
projectlist = [ # "Reload",
|
||||||
@@ -712,4 +711,4 @@ pv_mechanical = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
objectlist = ['PVPlantTree',
|
objectlist = ['PVPlantTree',
|
||||||
'PVPlantFence',]
|
'PVPlantFenceGroup',]
|
||||||
@@ -69,6 +69,7 @@ class _Area:
|
|||||||
''' Initialize the Area object '''
|
''' Initialize the Area object '''
|
||||||
self.Type = None
|
self.Type = None
|
||||||
self.obj = None
|
self.obj = None
|
||||||
|
self.setProperties(obj)
|
||||||
|
|
||||||
def setProperties(self, obj):
|
def setProperties(self, obj):
|
||||||
pl = obj.PropertiesList
|
pl = obj.PropertiesList
|
||||||
@@ -101,18 +102,18 @@ class _Area:
|
|||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def execute(self, obj):
|
||||||
|
''' Execute the area object '''
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class _ViewProviderArea:
|
class _ViewProviderArea:
|
||||||
def __init__(self, vobj):
|
def __init__(self, vobj):
|
||||||
self.Object = vobj.Object
|
|
||||||
vobj.Proxy = self
|
vobj.Proxy = self
|
||||||
|
|
||||||
def attach(self, vobj):
|
def attach(self, vobj):
|
||||||
'''
|
''' Create Object visuals in 3D view. '''
|
||||||
Create Object visuals in 3D view.
|
self.ViewObject = vobj
|
||||||
'''
|
|
||||||
self.Object = vobj.Object
|
|
||||||
return
|
|
||||||
|
|
||||||
def getIcon(self):
|
def getIcon(self):
|
||||||
'''
|
'''
|
||||||
@@ -120,6 +121,7 @@ class _ViewProviderArea:
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
return str(os.path.join(DirIcons, "area.svg"))
|
return str(os.path.join(DirIcons, "area.svg"))
|
||||||
|
|
||||||
'''
|
'''
|
||||||
def claimChildren(self):
|
def claimChildren(self):
|
||||||
"""
|
"""
|
||||||
@@ -159,17 +161,10 @@ class _ViewProviderArea:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
"""
|
|
||||||
Save variables to file.
|
|
||||||
"""
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
"""
|
pass
|
||||||
Get variables from file.
|
|
||||||
"""
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
''' Frame Area '''
|
''' Frame Area '''
|
||||||
|
|
||||||
@@ -311,17 +306,14 @@ class ViewProviderFrameArea(_ViewProviderArea):
|
|||||||
|
|
||||||
|
|
||||||
''' offsets '''
|
''' offsets '''
|
||||||
|
|
||||||
|
|
||||||
def makeOffsetArea(base = None, val=None):
|
def makeOffsetArea(base = None, val=None):
|
||||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "OffsetArea")
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "OffsetArea")
|
||||||
OffsetArea(obj)
|
OffsetArea(obj)
|
||||||
obj.Base = base
|
obj.Base = base
|
||||||
ViewProviderOffsetArea(obj.ViewObject)
|
ViewProviderOffsetArea(obj.ViewObject)
|
||||||
if val:
|
if val:
|
||||||
obj.Distance = val
|
obj.OffsetDistance = val
|
||||||
|
|
||||||
offsets = None
|
|
||||||
try:
|
try:
|
||||||
offsetsgroup = FreeCAD.ActiveDocument.Offsets
|
offsetsgroup = FreeCAD.ActiveDocument.Offsets
|
||||||
except:
|
except:
|
||||||
@@ -334,11 +326,13 @@ def makeOffsetArea(base = None, val=None):
|
|||||||
|
|
||||||
class OffsetArea(_Area):
|
class OffsetArea(_Area):
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
_Area.__init__(self, obj)
|
'''_Area.__init__(self, obj)
|
||||||
self.setProperties(obj)
|
self.setProperties(obj)'''
|
||||||
|
super().__init__(obj) # Llama al constructor de _Area
|
||||||
|
|
||||||
def setProperties(self, obj):
|
def setProperties(self, obj):
|
||||||
_Area.setProperties(self, obj)
|
super().setProperties(obj) # Propiedades de la clase base
|
||||||
|
|
||||||
pl = obj.PropertiesList
|
pl = obj.PropertiesList
|
||||||
if not ("OffsetDistance" in pl):
|
if not ("OffsetDistance" in pl):
|
||||||
obj.addProperty("App::PropertyDistance",
|
obj.addProperty("App::PropertyDistance",
|
||||||
@@ -354,24 +348,28 @@ class OffsetArea(_Area):
|
|||||||
self.setProperties(obj)
|
self.setProperties(obj)
|
||||||
|
|
||||||
def execute(self, obj):
|
def execute(self, obj):
|
||||||
import Utils.PVPlantUtils as utils
|
# Comprobar dependencias críticas
|
||||||
|
if not hasattr(obj, "Base") or not obj.Base or not obj.Base.Shape:
|
||||||
|
return
|
||||||
|
if not hasattr(PVPlantSite, "get") or not PVPlantSite.get().Terrain:
|
||||||
|
return
|
||||||
|
|
||||||
base = obj.Base.Shape
|
base = obj.Base.Shape
|
||||||
land = PVPlantSite.get().Terrain.Mesh
|
land = PVPlantSite.get().Terrain.Mesh
|
||||||
vec = FreeCAD.Vector(0, 0, 1)
|
vec = FreeCAD.Vector(0, 0, 1)
|
||||||
|
|
||||||
wire = utils.getProjected(base, vec)
|
wire = utils.getProjected(base, vec)
|
||||||
wire = wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True)
|
wire = wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True)
|
||||||
tmp = mp.projectShapeOnMesh(wire, land, vec)
|
sections = mp.projectShapeOnMesh(wire, land, vec)
|
||||||
pts = []
|
pts = []
|
||||||
for section in tmp:
|
for section in sections:
|
||||||
pts.extend(section)
|
pts.extend(section)
|
||||||
obj.Shape = Part.makePolygon(pts)
|
|
||||||
|
|
||||||
def __getstate__(self):
|
# Crear forma solo si hay resultados
|
||||||
return None
|
if sections:
|
||||||
|
obj.Shape = Part.makePolygon(pts)
|
||||||
def __setstate__(self, state):
|
else:
|
||||||
pass
|
obj.Shape = Part.Shape() # Forma vacía si falla
|
||||||
|
|
||||||
|
|
||||||
class ViewProviderOffsetArea(_ViewProviderArea):
|
class ViewProviderOffsetArea(_ViewProviderArea):
|
||||||
@@ -382,14 +380,12 @@ class ViewProviderOffsetArea(_ViewProviderArea):
|
|||||||
def claimChildren(self):
|
def claimChildren(self):
|
||||||
""" Provides object grouping """
|
""" Provides object grouping """
|
||||||
children = []
|
children = []
|
||||||
if self.Object.Base:
|
if self.ViewObject and self.ViewObject.Object.Base:
|
||||||
children.append(self.Object.Base)
|
children.append(self.ViewObject.Object.Base)
|
||||||
return children
|
return children
|
||||||
|
|
||||||
|
|
||||||
''' Forbidden Area: '''
|
''' Forbidden Area: '''
|
||||||
|
|
||||||
|
|
||||||
def makeProhibitedArea(base = None):
|
def makeProhibitedArea(base = None):
|
||||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "ExclusionArea")
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "ExclusionArea")
|
||||||
ProhibitedArea(obj)
|
ProhibitedArea(obj)
|
||||||
@@ -416,33 +412,192 @@ class ProhibitedArea(OffsetArea):
|
|||||||
self.Type = obj.Type = "ProhibitedArea"
|
self.Type = obj.Type = "ProhibitedArea"
|
||||||
obj.Proxy = self
|
obj.Proxy = self
|
||||||
|
|
||||||
def onDocumentRestored(self, obj):
|
'''# Propiedades de color
|
||||||
"""Method run when the document is restored."""
|
if not hasattr(obj, "OriginalColor"):
|
||||||
self.setProperties(obj)
|
obj.addProperty("App::PropertyColor",
|
||||||
|
"OriginalColor",
|
||||||
|
"Display",
|
||||||
|
"Color for original wire")
|
||||||
|
obj.OriginalColor = (1.0, 0.0, 0.0) # Rojo
|
||||||
|
|
||||||
def __getstate__(self):
|
if not hasattr(obj, "OffsetColor"):
|
||||||
return None
|
obj.addProperty("App::PropertyColor",
|
||||||
|
"OffsetColor",
|
||||||
|
"Display",
|
||||||
|
"Color for offset wire")
|
||||||
|
obj.OffsetColor = (1.0, 0.5, 0.0) # Naranja
|
||||||
|
|
||||||
def __setstate__(self, state):
|
# Propiedades de grosor
|
||||||
pass
|
if not hasattr(obj, "OriginalWidth"):
|
||||||
|
obj.addProperty("App::PropertyFloat",
|
||||||
|
"OriginalWidth",
|
||||||
|
"Display",
|
||||||
|
"Line width for original wire")
|
||||||
|
obj.OriginalWidth = 4.0
|
||||||
|
|
||||||
|
if not hasattr(obj, "OffsetWidth"):
|
||||||
|
obj.addProperty("App::PropertyFloat",
|
||||||
|
"OffsetWidth",
|
||||||
|
"Display",
|
||||||
|
"Line width for offset wire")
|
||||||
|
obj.OffsetWidth = 4.0'''
|
||||||
|
|
||||||
|
def 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(_ViewProviderArea):
|
class ViewProviderForbiddenArea(_ViewProviderArea):
|
||||||
|
def __init__(self, vobj):
|
||||||
|
super().__init__(vobj)
|
||||||
|
# Valores por defecto
|
||||||
|
self.original_color = (1.0, 0.0, 0.0) # Rojo
|
||||||
|
self.offset_color = (1.0, 0.5, 0.0) # Naranja
|
||||||
|
self.original_width = 4.0
|
||||||
|
self.offset_width = 4.0
|
||||||
|
self.line_widths = [] # Almacenará los grosores por arista
|
||||||
|
|
||||||
|
vobj.LineColor = (1.0, 0.0, 0.0)
|
||||||
|
vobj.LineWidth = 4
|
||||||
|
vobj.PointColor = (1.0, 0.0, 0.0)
|
||||||
|
vobj.PointSize = 4
|
||||||
|
|
||||||
def getIcon(self):
|
def getIcon(self):
|
||||||
''' Return object treeview icon '''
|
''' Return object treeview icon. '''
|
||||||
return str(os.path.join(DirIcons, "area_forbidden.svg"))
|
return str(os.path.join(DirIcons, "area_forbidden.svg"))
|
||||||
|
|
||||||
def claimChildren(self):
|
def claimChildren(self):
|
||||||
""" Provides object grouping """
|
""" Provides object grouping """
|
||||||
children = []
|
children = []
|
||||||
if self.Object.Base:
|
if self.ViewObject and self.ViewObject.Object.Base:
|
||||||
children.append(self.Object.Base)
|
children.append(self.ViewObject.Object.Base)
|
||||||
return children
|
return children
|
||||||
|
|
||||||
|
def attach(self, vobj):
|
||||||
|
super().attach(vobj)
|
||||||
|
# Inicializar visualización
|
||||||
|
self.updateVisual()
|
||||||
|
|
||||||
|
def updateVisual(self):
|
||||||
|
"""Actualiza colores y grosores de línea"""
|
||||||
|
if not hasattr(self, 'ViewObject') or not self.ViewObject or not self.ViewObject.Object:
|
||||||
|
return
|
||||||
|
|
||||||
|
obj = self.ViewObject.Object
|
||||||
|
|
||||||
|
# Obtener propiedades de color y grosor
|
||||||
|
try:
|
||||||
|
self.original_color = obj.OriginalColor
|
||||||
|
self.offset_color = obj.OffsetColor
|
||||||
|
self.original_width = obj.OriginalWidth
|
||||||
|
self.offset_width = obj.OffsetWidth
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Actualizar colores si hay forma
|
||||||
|
if hasattr(obj, 'Shape') and obj.Shape and not obj.Shape.isNull():
|
||||||
|
if len(obj.Shape.SubShapes) >= 2:
|
||||||
|
# Asignar colores
|
||||||
|
colors = []
|
||||||
|
colors.append(self.original_color) # Primer wire (original)
|
||||||
|
colors.append(self.offset_color) # Segundo wire (offset)
|
||||||
|
self.ViewObject.DiffuseColor = colors
|
||||||
|
|
||||||
|
# Preparar grosores por arista
|
||||||
|
#self.prepareLineWidths()
|
||||||
|
|
||||||
|
# Asignar grosores usando LineWidthArray
|
||||||
|
'''if self.line_widths:
|
||||||
|
self.ViewObject.LineWidthArray = self.line_widths'''
|
||||||
|
|
||||||
|
# Establecer grosor global como respaldo
|
||||||
|
#self.ViewObject.LineWidth = max(self.original_width, self.offset_width)
|
||||||
|
|
||||||
|
def prepareLineWidths(self):
|
||||||
|
"""Prepara la lista de grosores para cada arista"""
|
||||||
|
self.line_widths = []
|
||||||
|
obj = self.ViewObject.Object
|
||||||
|
|
||||||
|
if hasattr(obj, 'Shape') and obj.Shape and not obj.Shape.isNull():
|
||||||
|
# Contar aristas en cada subforma
|
||||||
|
for i, subshape in enumerate(obj.Shape.SubShapes):
|
||||||
|
edge_count = len(subshape.Edges) if hasattr(subshape, 'Edges') else 1
|
||||||
|
|
||||||
|
# Determinar grosor según tipo de wire
|
||||||
|
width = self.original_width if i == 0 else self.offset_width
|
||||||
|
|
||||||
|
# Asignar el mismo grosor a todas las aristas de este wire
|
||||||
|
self.line_widths.extend([width] * edge_count)
|
||||||
|
|
||||||
|
def onChanged(self, vobj, prop):
|
||||||
|
"""Maneja cambios en propiedades de visualización"""
|
||||||
|
if prop in ["LineColor", "PointColor", "ShapeColor", "LineWidth"]:
|
||||||
|
self.updateVisual()
|
||||||
|
|
||||||
|
def updateData(self, obj, prop):
|
||||||
|
"""Actualiza cuando cambian los datos del objeto"""
|
||||||
|
if prop == "Shape":
|
||||||
|
self.updateVisual()
|
||||||
|
|
||||||
|
'''def __getstate__(self):
|
||||||
|
return {
|
||||||
|
"original_color": self.original_color,
|
||||||
|
"offset_color": self.offset_color,
|
||||||
|
"original_width": self.original_width,
|
||||||
|
"offset_width": self.offset_width
|
||||||
|
}
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
if "original_color" in state:
|
||||||
|
self.original_color = state["original_color"]
|
||||||
|
if "offset_color" in state:
|
||||||
|
self.offset_color = state["offset_color"]
|
||||||
|
if "original_width" in state:
|
||||||
|
self.original_width = state.get("original_width", 4.0)
|
||||||
|
if "offset_width" in state:
|
||||||
|
self.offset_width = state.get("offset_width", 4.0)'''
|
||||||
|
|
||||||
|
|
||||||
''' PV Area: '''
|
''' PV Area: '''
|
||||||
|
|
||||||
|
|
||||||
def makePVSubplant():
|
def makePVSubplant():
|
||||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PVSubplant")
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PVSubplant")
|
||||||
PVSubplant(obj)
|
PVSubplant(obj)
|
||||||
|
|||||||
10
reload.py
10
reload.py
@@ -24,11 +24,15 @@ class _CommandReload:
|
|||||||
def Activated(self):
|
def Activated(self):
|
||||||
import PVPlantPlacement, \
|
import PVPlantPlacement, \
|
||||||
PVPlantGeoreferencing, PVPlantImportGrid, PVPlantTerrainAnalisys, \
|
PVPlantGeoreferencing, PVPlantImportGrid, PVPlantTerrainAnalisys, \
|
||||||
PVPlantSite, PVPlantRackChecking, PVPlantFence, PVPlantFencePost, PVPlantFenceGate, \
|
PVPlantSite, PVPlantRackChecking, PVPlantCreateTerrainMesh, \
|
||||||
PVPlantCreateTerrainMesh, \
|
|
||||||
PVPlantFoundation, PVPlantBuilding, PVPlantEarthWorks, PVPlantPad, \
|
PVPlantFoundation, PVPlantBuilding, PVPlantEarthWorks, PVPlantPad, \
|
||||||
PVPlantRoad, PVPlantTerrain, PVPlantStringing, PVPlantManhole, \
|
PVPlantRoad, PVPlantTerrain, PVPlantStringing, PVPlantManhole, \
|
||||||
GraphProfile
|
GraphProfile
|
||||||
|
|
||||||
|
from Civil.Fence import PVPlantFenceGate as PVPlantFenceGate
|
||||||
|
from Civil.Fence import PVPlantFence as PVPlantFence
|
||||||
|
from Civil.Fence import PVPlantFencePost as PVPlantFencePost
|
||||||
|
|
||||||
from Civil import PVPlantTrench
|
from Civil import PVPlantTrench
|
||||||
from Vegetation import PVPlantTreeGenerator
|
from Vegetation import PVPlantTreeGenerator
|
||||||
|
|
||||||
@@ -59,9 +63,11 @@ class _CommandReload:
|
|||||||
importlib.reload(PVPlantSite)
|
importlib.reload(PVPlantSite)
|
||||||
importlib.reload(PVPlantFrame)
|
importlib.reload(PVPlantFrame)
|
||||||
importlib.reload(PVPlantRackChecking)
|
importlib.reload(PVPlantRackChecking)
|
||||||
|
|
||||||
importlib.reload(PVPlantFence)
|
importlib.reload(PVPlantFence)
|
||||||
importlib.reload(PVPlantFenceGate)
|
importlib.reload(PVPlantFenceGate)
|
||||||
importlib.reload(PVPlantFencePost)
|
importlib.reload(PVPlantFencePost)
|
||||||
|
|
||||||
importlib.reload(PVPlantFoundation)
|
importlib.reload(PVPlantFoundation)
|
||||||
importlib.reload(PVPlantCreateTerrainMesh)
|
importlib.reload(PVPlantCreateTerrainMesh)
|
||||||
importlib.reload(PVPlantTreeGenerator)
|
importlib.reload(PVPlantTreeGenerator)
|
||||||
|
|||||||
@@ -9,16 +9,12 @@ setuptools~=68.2.2
|
|||||||
laspy~=2.5.3
|
laspy~=2.5.3
|
||||||
geopy~=2.4.1
|
geopy~=2.4.1
|
||||||
lxml~=4.9.3
|
lxml~=4.9.3
|
||||||
pip~=23.3.2
|
|
||||||
wheel~=0.42.0
|
|
||||||
Brotli~=1.1.0
|
|
||||||
PySocks~=1.7.1
|
|
||||||
typing_extensions~=4.9.0
|
|
||||||
docutils~=0.20.1
|
|
||||||
Pillow~=10.1.0
|
Pillow~=10.1.0
|
||||||
pyproj~=3.7.1
|
pyproj~=3.7.1
|
||||||
simplekml~=1.3.6
|
simplekml~=1.3.6
|
||||||
geojson~=3.1.0
|
geojson~=3.1.0
|
||||||
certifi~=2023.11.17
|
certifi~=2023.11.17
|
||||||
SciPy~=1.11.4
|
SciPy~=1.11.4
|
||||||
ezdxf~=1.4.1
|
pycollada~=0.7.2
|
||||||
|
shapely
|
||||||
|
rtree
|
||||||
Reference in New Issue
Block a user