Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25fd92e4f0 | |||
| 5abd4fae02 | |||
| e461ab2e80 | |||
| 1a22121f87 | |||
| 2858b58d86 | |||
| f3f94d4f59 | |||
| f4d43bedd0 | |||
| a67001bb88 | |||
| 26311cb344 | |||
| 6c2db07493 | |||
| 7d1127c6b5 | |||
| 7a54e424cb | |||
| 065f840941 | |||
| 74aedf6122 | |||
| 7c81beb1ba | |||
| 02b639d4ed | |||
| fc4142cfec | |||
| a515f31726 | |||
| e0a0dc2f0d | |||
| 02d6c4f412 | |||
| e129aba2fe | |||
| 9d65323052 | |||
| 0b13a8c5f1 | |||
| 3bcdc95978 | |||
| 4b7035e6be | |||
| 02758a6ee8 | |||
| 111df89033 | |||
| 4476afc1a2 | |||
| d61260fdd3 | |||
| 049898c939 | |||
| 3a188cc47d | |||
| 5db8f5439d | |||
| e1e1441892 | |||
| d009cb7695 | |||
| 5a642a4119 | |||
| 74bf60101c | |||
| 5dd8869caf | |||
| 03464ffafd | |||
| e111a985c3 | |||
| 65d57e98b7 | |||
| 86bae4f643 | |||
| e49d0694b4 | |||
| 1241ee97ba | |||
| 0e4b6e7fa4 | |||
| 092ccb75e0 | |||
| 9524e73955 | |||
| a7ac8826a0 | |||
| 1d062a087f | |||
| af559092bf | |||
| c0291198b1 | |||
| 4981b00918 | |||
| 3b38651609 | |||
| 322830c79e |
@@ -0,0 +1,2 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
Generated
+4
@@ -5,4 +5,8 @@
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PackageRequirementsSettings">
|
||||
<option name="removeUnused" value="true" />
|
||||
<option name="modifyBaseFiles" value="true" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -20,16 +20,18 @@
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import Part
|
||||
import Draft
|
||||
import MeshPart as mp
|
||||
import ArchComponent
|
||||
|
||||
import Civil.Fence.PVPlantFencePost as PVPlantFencePost
|
||||
import PVPlantSite
|
||||
import Utils.PVPlantUtils as utils
|
||||
import copy
|
||||
import math
|
||||
|
||||
import ArchComponent
|
||||
import Draft
|
||||
import FreeCAD
|
||||
import Part
|
||||
|
||||
import PVPlantFencePost
|
||||
import PVPlantSite
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
@@ -56,26 +58,28 @@ from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
EAST = FreeCAD.Vector(1, 0, 0)
|
||||
|
||||
def makeprojection(pathwire):
|
||||
site = FreeCAD.ActiveDocument.Site
|
||||
land = site.Terrain.Shape
|
||||
proj = land.makeParallelProjection(pathwire, FreeCAD.Vector(0, 0, 1))
|
||||
return proj
|
||||
|
||||
def makePVPlantFence(section, post, path):
|
||||
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Fence')
|
||||
|
||||
_Fence(obj)
|
||||
Fence(obj)
|
||||
obj.Post = post
|
||||
obj.Base = path
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
_ViewProviderFence(obj.ViewObject)
|
||||
ViewProviderFence(obj.ViewObject)
|
||||
|
||||
hide(section)
|
||||
hide(post)
|
||||
hide(path)
|
||||
|
||||
try:
|
||||
fende_group = FreeCAD.ActiveDocument.Fences
|
||||
except:
|
||||
fende_group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Fences')
|
||||
fende_group.Label = "Fences"
|
||||
FreeCAD.ActiveDocument.CivilGroup.addObject(fende_group)
|
||||
fende_group.addObject(obj)
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
return obj
|
||||
|
||||
@@ -83,16 +87,8 @@ def hide(obj):
|
||||
if hasattr(obj, 'ViewObject') and obj.ViewObject:
|
||||
obj.ViewObject.Visibility = False
|
||||
|
||||
def getAngle(Line1, Line2):
|
||||
v1 = Line1.Vertexes[1].Point - Line1.Vertexes[0].Point
|
||||
v2 = Line2.Vertexes[1].Point - Line2.Vertexes[0].Point
|
||||
return v1.getAngle(v2)
|
||||
|
||||
|
||||
def get_parameter_from_v0(edge, offset):
|
||||
"""Return parameter at distance offset from edge.Vertexes[0].
|
||||
sb method in Part.TopoShapeEdge???
|
||||
"""
|
||||
def get_parameter_from_v0_old(edge, offset):
|
||||
""" Return parameter at distance offset from edge.Vertexes[0].sb method in Part.TopoShapeEdge??? """
|
||||
import DraftVecUtils
|
||||
|
||||
lpt = edge.valueAt(edge.getParameterByLength(0))
|
||||
@@ -106,14 +102,16 @@ def get_parameter_from_v0(edge, offset):
|
||||
length = offset
|
||||
return edge.getParameterByLength(length)
|
||||
|
||||
def get_parameter_from_v0(edge, offset):
|
||||
"""Parámetro a distancia offset desde el primer vértice"""
|
||||
lpt = edge.valueAt(edge.getParameterByLength(0))
|
||||
vpt = edge.Vertexes[0].Point
|
||||
|
||||
if not vpt.isEqual(lpt, 1e-6):
|
||||
return edge.getParameterByLength(edge.Length - offset)
|
||||
return edge.getParameterByLength(offset)
|
||||
|
||||
def calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal=None):
|
||||
"""Orient shape to tangent at parm offset along edge."""
|
||||
import functools
|
||||
import DraftVecUtils
|
||||
# http://en.wikipedia.org/wiki/Euler_angles
|
||||
# start with null Placement point so translate goes to right place.
|
||||
|
||||
placement = FreeCAD.Placement()
|
||||
placement.Rotation = globalRotation
|
||||
placement.move(RefPt + xlate)
|
||||
@@ -121,55 +119,23 @@ def calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal
|
||||
if not align:
|
||||
return placement
|
||||
|
||||
# unit +Z Probably defined elsewhere?
|
||||
z = FreeCAD.Vector(0, 0, 1)
|
||||
# y = FreeCAD.Vector(0, 1, 0) # unit +Y
|
||||
x = FreeCAD.Vector(1, 0, 0) # unit +X
|
||||
nullv = FreeCAD.Vector(0, 0, 0)
|
||||
t = edge.tangentAt(get_parameter_from_v0(edge, offset)).normalize()
|
||||
n = normal or FreeCAD.Vector(0, 0, 1)
|
||||
b = t.cross(n).normalize()
|
||||
|
||||
# get local coord system - tangent, normal, binormal, if possible
|
||||
t = edge.tangentAt(get_parameter_from_v0(edge, offset))
|
||||
t.normalize()
|
||||
n = normal
|
||||
b = t.cross(n)
|
||||
b.normalize()
|
||||
# Asegurar sistema de coordenadas derecho
|
||||
if n.dot(t.cross(b)) < 0:
|
||||
b = -b
|
||||
|
||||
lnodes = z.cross(b)
|
||||
try:
|
||||
# Can't normalize null vector.
|
||||
lnodes.normalize()
|
||||
except:
|
||||
# pathological cases:
|
||||
pass
|
||||
|
||||
print(b, " - ", b.dot(z))
|
||||
if abs(b.dot(z)) == 1.0: # 2) binormal is || z
|
||||
# align shape to tangent only
|
||||
psi = math.degrees(DraftVecUtils.angle(x, t, z))
|
||||
theta = 0.0
|
||||
phi = 0.0
|
||||
FreeCAD.Console.PrintWarning("Draft PathArray.orientShape - Gimbal lock. Infinite lnodes. Change Path or Base.\n")
|
||||
else: # regular case
|
||||
psi = math.degrees(DraftVecUtils.angle(x, lnodes, z))
|
||||
theta = math.degrees(DraftVecUtils.angle(z, b, lnodes))
|
||||
phi = math.degrees(DraftVecUtils.angle(lnodes, t, b))
|
||||
|
||||
rotations = [placement.Rotation]
|
||||
|
||||
if psi != 0.0:
|
||||
rotations.insert(0, FreeCAD.Rotation(z, psi))
|
||||
if theta != 0.0:
|
||||
rotations.insert(0, FreeCAD.Rotation(lnodes, theta))
|
||||
if phi != 0.0:
|
||||
rotations.insert(0, FreeCAD.Rotation(b, phi))
|
||||
|
||||
if len(rotations) == 1:
|
||||
finalRotation = rotations[0]
|
||||
else:
|
||||
finalRotation = functools.reduce(lambda rot1, rot2: rot1.multiply(rot2), rotations)
|
||||
|
||||
placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), finalRotation.toEuler()[2])
|
||||
# Construir matriz
|
||||
rotation_matrix = FreeCAD.Matrix(
|
||||
t.x, b.x, n.x, 0,
|
||||
t.y, b.y, n.y, 0,
|
||||
t.z, b.z, n.z, 0,
|
||||
0, 0, 0, 1
|
||||
)
|
||||
|
||||
placement.Rotation = FreeCAD.Rotation(rotation_matrix)
|
||||
return placement
|
||||
|
||||
def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
|
||||
@@ -183,12 +149,8 @@ def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
|
||||
import DraftGeomUtils
|
||||
|
||||
closedpath = DraftGeomUtils.isReallyClosed(pathwire)
|
||||
normal = DraftGeomUtils.getNormal(pathwire)
|
||||
if normal:
|
||||
if normal.z < 0: # asegurarse de que siempre se dibuje por encima del suelo
|
||||
normal.z *= -1
|
||||
else:
|
||||
normal = FreeCAD.Vector(0, 0, 1)
|
||||
|
||||
normal = FreeCAD.Vector(0, 0, 1)
|
||||
path = Part.__sortEdges__(pathwire.Edges)
|
||||
ends = []
|
||||
cdist = 0
|
||||
@@ -241,7 +203,7 @@ def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
|
||||
|
||||
return placements
|
||||
|
||||
class _Fence(ArchComponent.Component):
|
||||
class Fence(ArchComponent.Component):
|
||||
def __init__(self, obj):
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
self.setProperties(obj)
|
||||
@@ -347,7 +309,6 @@ class _Fence(ArchComponent.Component):
|
||||
QT_TRANSLATE_NOOP("App::Property", "The number of posts used to build the fence"))
|
||||
obj.setEditorMode("Length", 1)
|
||||
|
||||
|
||||
self.Type = "PVPlatFence"
|
||||
|
||||
def __getstate__(self):
|
||||
@@ -361,75 +322,30 @@ class _Fence(ArchComponent.Component):
|
||||
return None
|
||||
|
||||
def execute(self, obj):
|
||||
if not obj.Base or not obj.Post:
|
||||
return
|
||||
|
||||
# 1. Preparar trazado base
|
||||
pathwire = self.calculatePathWire(obj)
|
||||
if pathwire is None:
|
||||
# FreeCAD.Console.PrintLog("ArchFence.execute: path " + obj.Base.Name + " has no edges\n")
|
||||
return
|
||||
|
||||
if not obj.Post:
|
||||
FreeCAD.Console.PrintLog("ArchFence.execute: Post not set\n")
|
||||
pathwire = utils.getProjected(pathwire, FreeCAD.Vector(0, 0, 1))
|
||||
pathwire = utils.simplifyWire(pathwire)
|
||||
if not pathwire or not pathwire.Edges:
|
||||
return
|
||||
|
||||
# 2. Proyectar sobre terreno (con caché)
|
||||
self.Posts = []
|
||||
self.Foundations = []
|
||||
site = PVPlantSite.get()
|
||||
if True: # prueba
|
||||
import MeshPart as mp
|
||||
land = FreeCAD.ActiveDocument.Terrain.Mesh
|
||||
segments = mp.projectShapeOnMesh(pathwire, land, FreeCAD.Vector(0, 0, 1))
|
||||
points=[]
|
||||
for segment in segments:
|
||||
points.extend(segment)
|
||||
pathwire = Part.makePolygon(points)
|
||||
else:
|
||||
if PVPlantSite.get().Terrain.TypeId == 'Mesh::Feature':
|
||||
import MeshPart as mp
|
||||
land = PVPlantSite.get().Terrain.Mesh
|
||||
pathwire = mp.projectShapeOnMesh(pathwire, land, FreeCAD.Vector(0, 0, 1))
|
||||
|
||||
else:
|
||||
land = site.Terrain.Shape
|
||||
pathwire = land.makeParallelProjection(pathwire, FreeCAD.Vector(0, 0, 1))
|
||||
site = PVPlantSite.get()
|
||||
segments = mp.projectShapeOnMesh(pathwire, site.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))
|
||||
points=[]
|
||||
for segment in segments:
|
||||
points.extend(segment)
|
||||
pathwire = Part.makePolygon(points)
|
||||
|
||||
if pathwire is None:
|
||||
return
|
||||
|
||||
''' no sirve:
|
||||
if len(pathwire.Wires) > 1:
|
||||
import draftgeoutils
|
||||
pathwire = draftgeoutils.wires.superWire(pathwire.Edges, True)
|
||||
Part.show(pathwire)
|
||||
'''
|
||||
|
||||
''' unir todas en una '''
|
||||
'''
|
||||
if len(pathwire.Wires) > 1:
|
||||
import Utils.PVPlantUtils as utils
|
||||
wires = pathwire.Wires
|
||||
new_wire = []
|
||||
to_compare = utils.getPoints(wires.pop(0))
|
||||
new_wire.extend(to_compare)
|
||||
while len(wires)>0:
|
||||
wire = wires[0]
|
||||
points = utils.getPoints(wire)
|
||||
to_remove = None
|
||||
if points[0] in to_compare:
|
||||
to_remove = points[0]
|
||||
if points[-1] in to_compare:
|
||||
to_remove = points[-1]
|
||||
if to_remove:
|
||||
to_compare = points.copy()
|
||||
points.remove(to_remove)
|
||||
new_wire.extend(points)
|
||||
wires.pop()
|
||||
continue
|
||||
wires.append(wires.pop())
|
||||
pathwire = Part.makePolygon(new_wire)
|
||||
#Part.show(pathwire)
|
||||
#return
|
||||
'''
|
||||
|
||||
sectionLength = obj.Gap.Value
|
||||
postLength = 0 #obj.Post.Diameter.Value #considerarlo 0 porque no influye
|
||||
postPlacements = []
|
||||
@@ -457,18 +373,19 @@ class _Fence(ArchComponent.Component):
|
||||
|
||||
postPlacements.extend(placements)
|
||||
|
||||
# 5. Generar geometría
|
||||
postShapes, postFoundation = self.calculatePosts(obj, postPlacements)
|
||||
sections, num = self.calculateSections(obj, postPlacements)
|
||||
mesh = self.calculate_sections(obj, postPlacements)
|
||||
|
||||
postShapes = Part.makeCompound(postShapes)
|
||||
postFoundation = Part.makeCompound(postFoundation)
|
||||
sections = Part.makeCompound(sections)
|
||||
compound = Part.makeCompound([postShapes, postFoundation, sections])
|
||||
obj.Shape = compound
|
||||
|
||||
# Give information
|
||||
# 6. Crear forma final
|
||||
obj.Shape = Part.makeCompound([postShapes, postFoundation, mesh])
|
||||
|
||||
# 7. Actualizar propiedades
|
||||
obj.NumberOfSections = count
|
||||
obj.NumberOfPosts = obj.NumberOfSections + 1
|
||||
obj.NumberOfPosts = count + 1
|
||||
obj.Length = pathLength
|
||||
obj.Concrete = count * postFoundation.SubShapes[0].Volume
|
||||
|
||||
@@ -497,7 +414,7 @@ class _Fence(ArchComponent.Component):
|
||||
|
||||
def calculatePostPlacements(self, obj, pathwire, rotation):
|
||||
postWidth = obj.Post.Diameter.Value
|
||||
transformationVector = FreeCAD.Vector(0, postWidth / 2, 0)
|
||||
transformationVector = FreeCAD.Vector(0, - postWidth / 2, 0)
|
||||
placements = calculatePlacementsOnPath(rotation, pathwire, int(obj.NumberOfSections) + 1, transformationVector, True)
|
||||
# The placement of the last object is always the second entry in the list.
|
||||
# So we move it to the end:
|
||||
@@ -509,47 +426,36 @@ class _Fence(ArchComponent.Component):
|
||||
posts = []
|
||||
foundations = []
|
||||
for placement in postPlacements:
|
||||
postCopy = obj.Post.Shape.copy()
|
||||
postCopy = Part.Solid(postCopy)
|
||||
postCopy.Placement = placement
|
||||
postCopy.Placement.Base.z += 100
|
||||
posts.append(postCopy)
|
||||
new_post = obj.Post.Shape.copy()
|
||||
new_post = Part.Solid(new_post)
|
||||
new_post.Placement = placement
|
||||
new_post.Placement.Base.z += 100
|
||||
posts.append(new_post)
|
||||
|
||||
foundation = Part.makeCylinder(150, 700)
|
||||
foundation.Placement = placement
|
||||
foundation.Placement.Base.z -= obj.Depth.Value
|
||||
foundation = foundation.cut(postCopy)
|
||||
#foundation = foundation.cut(new_post)
|
||||
foundations.append(foundation)
|
||||
|
||||
return posts, foundations
|
||||
|
||||
def calculateSections(self, obj, postPlacements):
|
||||
shapes = []
|
||||
faceNumbers = []
|
||||
def calculate_sections(self, obj, postPlacements):
|
||||
offsetz = FreeCAD.Vector(0, 0, obj.MeshOffsetZ.Value)
|
||||
meshHeight = FreeCAD.Vector(0, 0, obj.MeshHeight.Value)
|
||||
|
||||
offsetz = obj.MeshOffsetZ.Value
|
||||
meshHeight = obj.MeshHeight.Value
|
||||
points_down = []
|
||||
points_up = []
|
||||
for i in range(len(postPlacements) - 1):
|
||||
startPlacement = postPlacements[i]
|
||||
endPlacement = postPlacements[i + 1]
|
||||
p1 = postPlacements[i].Base + offsetz
|
||||
p2 = postPlacements[i + 1].Base + offsetz
|
||||
p3 = p1 + meshHeight
|
||||
p4 = p2 + meshHeight
|
||||
points_down.extend([p1, p2])
|
||||
points_up.extend([p3, p4])
|
||||
|
||||
p1 = startPlacement.Base + FreeCAD.Vector(0, 0, offsetz)
|
||||
p2 = endPlacement.Base + FreeCAD.Vector(0, 0, offsetz)
|
||||
p3 = p2 + FreeCAD.Vector(0, 0, meshHeight)
|
||||
p4 = p1 + FreeCAD.Vector(0, 0, meshHeight)
|
||||
pointlist = [p1, p2, p3, p4, p1]
|
||||
|
||||
try:
|
||||
pol = Part.makePolygon(pointlist)
|
||||
face = Part.Face(pol)
|
||||
shapes.append(face)
|
||||
faceNumbers.append(1)
|
||||
except:
|
||||
print("No es posible crear la cara: ---------------------------------------------------")
|
||||
print(" +++++ Start: ", startPlacement.Base, " - end: ", endPlacement.Base)
|
||||
print(" +++++ algo: ", pointlist, "\n")
|
||||
print("---------------------------------------------------\n")
|
||||
return (shapes, faceNumbers)
|
||||
shape = Part.makeRuledSurface(Part.makePolygon(points_down), Part.makePolygon(points_up))
|
||||
return shape
|
||||
|
||||
def calculatePathWire(self, obj):
|
||||
if obj.Base:
|
||||
@@ -562,7 +468,7 @@ class _Fence(ArchComponent.Component):
|
||||
return None
|
||||
|
||||
|
||||
class _ViewProviderFence(ArchComponent.ViewProviderComponent):
|
||||
class ViewProviderFence(ArchComponent.ViewProviderComponent):
|
||||
"A View Provider for the Fence object"
|
||||
|
||||
def __init__(self, vobj):
|
||||
@@ -642,7 +548,7 @@ class _ViewProviderFence(ArchComponent.ViewProviderComponent):
|
||||
children.append(self.Object.Gate)
|
||||
return children
|
||||
|
||||
class _FenceTaskPanel:
|
||||
class FenceTaskPanel:
|
||||
'''The TaskPanel to setup the fence'''
|
||||
|
||||
def __init__(self):
|
||||
@@ -775,15 +681,8 @@ class _FenceTaskPanel:
|
||||
self.form = [self.formFence, self.formPost, self.formFoundation]
|
||||
|
||||
# valores iniciales y creación del la valla:
|
||||
import Draft
|
||||
self.post = PVPlantFencePost.makeFencePost() # Arch.makePipe()
|
||||
self.post = PVPlantFencePost.makeFencePost()
|
||||
self.post.Label = "Post"
|
||||
Draft.autogroup(self.post)
|
||||
|
||||
'''
|
||||
self.section = self.makeGrid()
|
||||
self.path = self.section.Base
|
||||
'''
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
self.fence = makePVPlantFence(self.section, self.post, self.path)
|
||||
@@ -845,7 +744,7 @@ class _FenceTaskPanel:
|
||||
|
||||
|
||||
# Commands ---------------------------------------------------------------------------------
|
||||
class _CommandPVPlantFence:
|
||||
class CommandPVPlantFence:
|
||||
"the PVPlant Fence command definition"
|
||||
|
||||
def GetResources(self):
|
||||
@@ -859,7 +758,7 @@ class _CommandPVPlantFence:
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
def Activated(self):
|
||||
self.TaskPanel = _FenceTaskPanel()
|
||||
self.TaskPanel = FenceTaskPanel()
|
||||
FreeCADGui.Control.showDialog(self.TaskPanel)
|
||||
|
||||
|
||||
@@ -877,19 +776,13 @@ if FreeCAD.GuiUp:
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
site = FreeCAD.ActiveDocument.getObject("Site")
|
||||
return (not (FreeCAD.ActiveDocument is None) and
|
||||
not (FreeCAD.ActiveDocument.getObject("Site") is None) and
|
||||
not (FreeCAD.ActiveDocument.getObject("Terrain") is None))
|
||||
not (site is None) and
|
||||
not (site.Terrain is None))
|
||||
|
||||
import PVPlantFenceGate
|
||||
FreeCADGui.addCommand('PVPlantFence', _CommandPVPlantFence())
|
||||
FreeCADGui.addCommand('PVPlantGate', PVPlantFenceGate._CommandPVPlantGate())
|
||||
FreeCADGui.addCommand('PVPlantFencePost', PVPlantFencePost._CommandFencePost())
|
||||
FreeCADGui.addCommand('PVPlantFenceGroup', CommandFenceGroup())
|
||||
|
||||
def movep(obj):
|
||||
pl = obj.Shape.BoundBox.Center
|
||||
points = []
|
||||
for ind in range(len(obj.Shape.Vertexes)):
|
||||
points.append(obj.Shape.Vertexes[ind].Point - pl)
|
||||
Draft.makeWire(points)
|
||||
import Civil.Fence.PVPlantFenceGate as PVPlantFenceGate
|
||||
FreeCADGui.addCommand('PVPlantFence', CommandPVPlantFence())
|
||||
FreeCADGui.addCommand('PVPlantGate', PVPlantFenceGate.CommandPVPlantGate())
|
||||
FreeCADGui.addCommand('PVPlantFencePost', PVPlantFencePost.CommandFencePost())
|
||||
#FreeCADGui.addCommand('PVPlantFenceGroup', CommandFenceGroup())
|
||||
@@ -202,7 +202,7 @@ class ViewProviderGate:
|
||||
children.append(self.Object.Base)
|
||||
return children
|
||||
|
||||
class _CommandPVPlantGate:
|
||||
class CommandPVPlantGate:
|
||||
"the PVPlant Fence command definition"
|
||||
|
||||
def __init__(self):
|
||||
@@ -256,7 +256,9 @@ class _CommandPVPlantGate:
|
||||
gate = makePVPlantFence()
|
||||
try:
|
||||
import MeshPart as mp
|
||||
point1 = mp.projectPointsOnMesh([point1,], FreeCAD.ActiveDocument.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))[0]
|
||||
import PVPlantSite
|
||||
site = PVPlantSite.get()
|
||||
point1 = mp.projectPointsOnMesh([point1,], site.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))[0]
|
||||
|
||||
except:
|
||||
FreeCAD.Console.PrintError("No se puede encontrar punto 3D.." + "\n")
|
||||
@@ -1,5 +1,6 @@
|
||||
import ArchComponent
|
||||
import FreeCAD
|
||||
import Part
|
||||
import ArchComponent
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
@@ -9,8 +10,6 @@ else:
|
||||
# \cond
|
||||
def translate(ctxt, txt):
|
||||
return txt
|
||||
|
||||
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||
return txt
|
||||
# \endcond
|
||||
@@ -21,20 +20,14 @@ except AttributeError:
|
||||
def _fromUtf8(s):
|
||||
return s
|
||||
|
||||
|
||||
def makeFencePost(diameter=48, length=3000, placement=None, name="Post"):
|
||||
"makePipe([baseobj,diamerter,length,placement,name]): creates an pipe object from the given base object"
|
||||
|
||||
if not FreeCAD.ActiveDocument:
|
||||
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
||||
return
|
||||
def makeFencePost(diameter=48, length=3000, placement=None, name="FencePost"):
|
||||
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
|
||||
obj.Label = name
|
||||
_FencePost(obj)
|
||||
FencePost(obj)
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
_ViewProviderFencePost(obj.ViewObject)
|
||||
ViewProviderFencePost(obj.ViewObject)
|
||||
|
||||
obj.Length = length
|
||||
obj.Diameter = diameter
|
||||
@@ -45,18 +38,13 @@ def makeFencePost(diameter=48, length=3000, placement=None, name="Post"):
|
||||
|
||||
|
||||
def makeFenceReinforcePost(diameter=48, length=3000, placement=None, name="Post"):
|
||||
"makePipe([baseobj,diamerter,length,placement,name]): creates an pipe object from the given base object"
|
||||
|
||||
if not FreeCAD.ActiveDocument:
|
||||
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
||||
return
|
||||
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
|
||||
obj.Label = name
|
||||
_FenceReinforcePostPost(obj)
|
||||
FenceReinforcePost(obj)
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
_ViewProviderFencePost(obj.ViewObject)
|
||||
ViewProviderFencePost(obj.ViewObject)
|
||||
|
||||
obj.Length = length
|
||||
obj.Diameter = diameter
|
||||
@@ -66,7 +54,7 @@ def makeFenceReinforcePost(diameter=48, length=3000, placement=None, name="Post"
|
||||
return obj
|
||||
|
||||
|
||||
class _FencePost(ArchComponent.Component):
|
||||
class FencePost(ArchComponent.Component):
|
||||
def __init__(self, obj):
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
self.setProperties(obj)
|
||||
@@ -80,10 +68,10 @@ class _FencePost(ArchComponent.Component):
|
||||
obj.addProperty("App::PropertyLength", "Diameter", "Pipe",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The diameter of this pipe, if not based on a profile")
|
||||
).Diameter = 48
|
||||
if not "Thickness" in pl:
|
||||
'''if not "Thickness" in pl:
|
||||
obj.addProperty("App::PropertyLength", "Thickness", "Pipe",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The Thickness of this pipe, if not based on a profile")
|
||||
).Thickness = 4
|
||||
).Thickness = 4'''
|
||||
if not "Length" in pl:
|
||||
obj.addProperty("App::PropertyLength", "Length", "Pipe",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The length of this pipe, if not based on an edge")
|
||||
@@ -94,86 +82,49 @@ class _FencePost(ArchComponent.Component):
|
||||
ArchComponent.Component.onDocumentRestored(self, obj)
|
||||
self.setProperties(obj)
|
||||
|
||||
def get_axis(self, obj, lip_heigth):
|
||||
wire = Part.makeLine(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, obj.Length.Value - lip_heigth))
|
||||
#wire = Part.makePolygon(FreeCAD.Vector(0, 0, obj.Length.Value - lip_heigth),)
|
||||
return Part.Wire(wire)
|
||||
|
||||
def execute(self, obj):
|
||||
import Part
|
||||
pl = obj.Placement
|
||||
|
||||
if obj.CloneOf:
|
||||
obj.Shape = obj.CloneOf.Shape
|
||||
else:
|
||||
w = self.getProfile(obj)
|
||||
try:
|
||||
# sh = w.makePipeShell([p], True, False, 2)
|
||||
sh = w.revolve(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(0.0, 0.0, 1.0), 360)
|
||||
sh = Part.Solid(sh)
|
||||
except:
|
||||
FreeCAD.Console.PrintError("Unable to build the pipe \n")
|
||||
else:
|
||||
obj.Shape = sh
|
||||
obj.Placement = pl
|
||||
lip_heigth = 20
|
||||
radius = obj.Diameter.Value / 2
|
||||
|
||||
# para que sea una función que sirva para los postes rectos y con curva:
|
||||
axis = self.get_axis(obj, lip_heigth)
|
||||
profile = Part.Wire([Part.Circle(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), radius).toShape()])
|
||||
post = axis.makePipeShell([profile, ],True,True,2)
|
||||
|
||||
lip = Part.makeCylinder(radius + 2, lip_heigth)
|
||||
lip = lip.makeFillet(5, [lip.Edges[0]])
|
||||
|
||||
# Obtener caras
|
||||
face_post = post.Faces[2] # Cara superior del cilindro largo
|
||||
face_lip = lip.Faces[2] # Cara inferior del cilindro corto
|
||||
|
||||
# Calcular centro y normal de las caras
|
||||
face_post_center = face_post.CenterOfMass
|
||||
face_post_normal = face_post.normalAt(0, 0)
|
||||
face_lip_center = face_lip.CenterOfMass
|
||||
face_lip_normal = face_lip.normalAt(0, 0)
|
||||
|
||||
# Calcular rotación para alinear normales (ajustar dirección)
|
||||
rotacion = FreeCAD.Rotation(face_lip_normal, -face_post_normal) # Invertir normal del cilindro corto
|
||||
lip.Placement.Rotation = rotacion.multiply(lip.Placement.Rotation)
|
||||
|
||||
# Calcular traslación: mover centro del cilindro corto al centro del cilindro largo
|
||||
traslacion = face_post_center - rotacion.multVec(face_lip_center)
|
||||
lip.Placement.Base = traslacion #face_post_center
|
||||
|
||||
obj.Shape = post.fuse(lip)
|
||||
obj.Placement = pl
|
||||
return
|
||||
|
||||
# ------------------------- Prueba para apoyos de refuerzo:
|
||||
import math
|
||||
L = math.pi / 2 * (obj.Diameter.Value - 2 * obj.Thickness.Value)
|
||||
|
||||
v1 = FreeCAD.Vector(L / 2, 0, obj.Thickness.Value)
|
||||
vc1 = FreeCAD.Vector(L / 2 + obj.Thickness.Value, 0, 0)
|
||||
v2 = FreeCAD.Vector(L / 2, 0, -obj.Thickness.Value)
|
||||
|
||||
v11 = FreeCAD.Vector(-L / 2, 0, obj.Thickness.Value)
|
||||
vc11 = FreeCAD.Vector(-(L / 2 + obj.Thickness.Value), 0, 0)
|
||||
v21 = FreeCAD.Vector(-L / 2, 0, -obj.Thickness.Value)
|
||||
|
||||
arc1 = Part.Arc(v1, vc1, v2).toShape()
|
||||
arc11 = Part.Arc(v11, vc11, v21).toShape()
|
||||
line1 = Part.LineSegment(v11, v1).toShape()
|
||||
line2 = Part.LineSegment(v21, v2).toShape()
|
||||
w = Part.Wire([arc1, line2, arc11, line1])
|
||||
face = Part.Face(w)
|
||||
pro = face.extrude(FreeCAD.Vector(0, 40, 0))
|
||||
|
||||
#Part.Circle(Center, Normal, Radius)
|
||||
cir1 = Part.Face(Part.Wire(Part.Circle(FreeCAD.Vector(0, -200, 0), FreeCAD.Vector(0, 1, 0), obj.Diameter.Value / 2).toShape()))
|
||||
ext = cir1.extrude(FreeCAD.Vector(0, 170, 0))
|
||||
cir2 = Part.Circle(FreeCAD.Vector(0, -30, 0), FreeCAD.Vector(0, 1, 0), obj.Diameter.Value/2).toShape()
|
||||
loft = Part.makeLoft([cir2, w], True)
|
||||
ext = ext.fuse([loft, pro])
|
||||
Part.show(ext)
|
||||
|
||||
|
||||
def getProfile(self, obj):
|
||||
import Part
|
||||
sin45 = 0.707106781
|
||||
radio = obj.Diameter.Value / 2
|
||||
taph = 20
|
||||
tapw = radio + 2
|
||||
chamfer = 5
|
||||
chamfer2 = chamfer * sin45
|
||||
|
||||
edge1 = Part.makeLine(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(radio, 0, 0))
|
||||
edge2 = Part.makeLine(FreeCAD.Vector(radio, 0, 0), FreeCAD.Vector(radio, 0, obj.Length.Value - taph))
|
||||
edge3 = Part.makeLine(FreeCAD.Vector(radio, 0, obj.Length.Value - taph),
|
||||
FreeCAD.Vector(tapw, 0, obj.Length.Value - taph))
|
||||
edge4 = Part.makeLine(FreeCAD.Vector(tapw, 0, obj.Length.Value - taph),
|
||||
FreeCAD.Vector(tapw, 0, obj.Length.Value - chamfer))
|
||||
if True:
|
||||
edge5 = Part.makeLine(FreeCAD.Vector(tapw, 0, obj.Length.Value - chamfer),
|
||||
FreeCAD.Vector(tapw - chamfer, 0, obj.Length.Value))
|
||||
else:
|
||||
edge5 = Part.Arc(FreeCAD.Vector(tapw, 0, obj.Length.Value - chamfer),
|
||||
FreeCAD.Vector(tapw - chamfer2, 0, obj.Length.Value - chamfer2),
|
||||
FreeCAD.Vector(tapw - chamfer, 0, obj.Length.Value)
|
||||
).toShape()
|
||||
edge6 = Part.makeLine(FreeCAD.Vector(tapw - chamfer, 0, obj.Length.Value),
|
||||
FreeCAD.Vector(0, 0, obj.Length.Value))
|
||||
w = Part.Wire([edge1, edge2, edge3, edge4, edge5, edge6])
|
||||
|
||||
return w
|
||||
|
||||
|
||||
class _FenceReinforcePost(ArchComponent.Component):
|
||||
class FenceReinforcePost(ArchComponent.Component):
|
||||
def __init__(self, obj):
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
self.setProperties(obj)
|
||||
@@ -199,10 +150,18 @@ class _FenceReinforcePost(ArchComponent.Component):
|
||||
self.setProperties(obj)
|
||||
|
||||
def execute(self, obj):
|
||||
|
||||
pl = obj.Placement
|
||||
w = self.getWire(obj)
|
||||
|
||||
lip_heigth = 20
|
||||
post = Part.makeCylinder(obj.Diameter.Value / 2, obj.Length.Value - lip_heigth)
|
||||
lip = Part.makeCylinder(obj.Diameter.Value / 2 + 2, lip_heigth)
|
||||
lip = lip.makeFillet(5, [lip.Edges[0]])
|
||||
lip.translate(FreeCAD.Vector(0, 0, obj.Length.Value - lip_heigth))
|
||||
obj.Shape = post.fuse(lip)
|
||||
obj.Placement = pl
|
||||
return
|
||||
|
||||
w = self.getWire(obj)
|
||||
try:
|
||||
# sh = w.makePipeShell([p], True, False, 2)
|
||||
sh = w.revolve(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(0.0, 0.0, 1.0), 360)
|
||||
@@ -244,7 +203,7 @@ class _FenceReinforcePost(ArchComponent.Component):
|
||||
return w
|
||||
|
||||
|
||||
class _ViewProviderFencePost(ArchComponent.ViewProviderComponent):
|
||||
class ViewProviderFencePost(ArchComponent.ViewProviderComponent):
|
||||
"A View Provider for the Pipe object"
|
||||
|
||||
def __init__(self, vobj):
|
||||
@@ -254,7 +213,7 @@ class _ViewProviderFencePost(ArchComponent.ViewProviderComponent):
|
||||
return ":/icons/Arch_Pipe_Tree.svg"
|
||||
|
||||
|
||||
class _CommandFencePost:
|
||||
class CommandFencePost:
|
||||
"the Arch Pipe command definition"
|
||||
|
||||
def GetResources(self):
|
||||
@@ -269,17 +228,5 @@ class _CommandFencePost:
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
def Activated(self):
|
||||
if True:
|
||||
makeFencePost()
|
||||
else:
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Create Pipe"))
|
||||
FreeCADGui.addModule("Arch")
|
||||
FreeCADGui.doCommand("obj = Arch.makePipe()")
|
||||
FreeCADGui.addModule("Draft")
|
||||
FreeCADGui.doCommand("Draft.autogroup(obj)")
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
makeFencePost()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('FencePost', _CommandFencePost())
|
||||
@@ -0,0 +1,387 @@
|
||||
# /**********************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2026 Javier Braña <javier.branagutierrez@gmail.com> *
|
||||
# * *
|
||||
# * EarthWorks - Cálculo de movimiento de tierras *
|
||||
# * *
|
||||
# * Calcula volúmenes de desmonte (cut) y terraplén (fill) entre una *
|
||||
# * plataforma diseñada (generada por PVPlantPlatform) y el terreno *
|
||||
# * natural representado por un mesh. *
|
||||
# * *
|
||||
# * Flujo: *
|
||||
# * 1. build_platform(frames) → Part.Solid (superficie diseñada) *
|
||||
# * 2. cut_mesh = mesh_above(platform_mesh, terrain_mesh) *
|
||||
# * 3. fill_mesh = mesh_below(platform_mesh, terrain_mesh) *
|
||||
# * 4. Volumen = mesh.Volume *
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import Part
|
||||
import Mesh
|
||||
import math
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui, os
|
||||
from PySide import QtCore, QtGui
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
else:
|
||||
def translate(ctxt, txt): return txt
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt): return txt
|
||||
|
||||
import PVPlantResources
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
from .PVPlantPlatform import build_platform, get_platform_shape, make_platform
|
||||
|
||||
VOLUME_TYPES = ["Fill", "Cut"]
|
||||
|
||||
|
||||
def compute_earthworks(platform_or_frames, terrain_mesh, slope_tolerance=10.0):
|
||||
"""
|
||||
Calcula el movimiento de tierras.
|
||||
|
||||
Args:
|
||||
platform_or_frames: Objeto Platform o lista de frames/trackers
|
||||
Si es Platform, usa su Shape directamente.
|
||||
Si es lista de frames, genera la plataforma primero.
|
||||
terrain_mesh: Mesh del terreno natural
|
||||
slope_tolerance: pendiente máxima E-W (grados)
|
||||
|
||||
Returns:
|
||||
tuple: (mesh_cut, mesh_fill, volume_cut_mm3, volume_fill_mm3)
|
||||
"""
|
||||
import MeshPart
|
||||
|
||||
# 1. Obtener la plataforma
|
||||
if hasattr(platform_or_frames, 'Proxy') and hasattr(platform_or_frames.Proxy, '__class__'):
|
||||
cls_name = platform_or_frames.Proxy.__class__.__name__
|
||||
if cls_name == 'Platform':
|
||||
# Ya es un objeto Platform → usar su Shape
|
||||
platform = get_platform_shape(platform_or_frames)
|
||||
else:
|
||||
platform = build_platform([platform_or_frames], slope_tolerance)
|
||||
elif hasattr(platform_or_frames, '__iter__'):
|
||||
# Es una lista de frames
|
||||
platform = build_platform(platform_or_frames, slope_tolerance)
|
||||
else:
|
||||
platform = None
|
||||
|
||||
# 2. Convertir plataforma a mesh para booleanos
|
||||
try:
|
||||
platform_mesh = MeshPart.meshFromShape(
|
||||
Shape=platform,
|
||||
LinearDeflection=500,
|
||||
AngularDeflection=0.5
|
||||
)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(f"Error al meshificar la plataforma: {e}\n")
|
||||
return None, None, 0, 0
|
||||
|
||||
if platform_mesh is None or platform_mesh.countPoints() == 0:
|
||||
return None, None, 0, 0
|
||||
|
||||
# 3. Calcular corte y relleno
|
||||
cut_mesh = _mesh_above(platform_mesh, terrain_mesh)
|
||||
fill_mesh = _mesh_below(platform_mesh, terrain_mesh)
|
||||
|
||||
volume_cut = _mesh_volume(cut_mesh)
|
||||
volume_fill = _mesh_volume(fill_mesh)
|
||||
|
||||
return cut_mesh, fill_mesh, volume_cut, volume_fill
|
||||
|
||||
|
||||
def _mesh_above(reference, terrain):
|
||||
"""
|
||||
Porción del mesh de referencia que está por encima del terreno.
|
||||
Representa material a excavar (cut).
|
||||
"""
|
||||
try:
|
||||
common = reference.common(terrain)
|
||||
if common and common.countPoints() > 3:
|
||||
return common
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _mesh_below(reference, terrain):
|
||||
"""
|
||||
Porción del mesh de referencia que está por debajo del terreno.
|
||||
Representa material a rellenar (fill).
|
||||
"""
|
||||
try:
|
||||
diff = reference.cut(terrain)
|
||||
if diff and diff.countPoints() > 3:
|
||||
return diff
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _mesh_volume(mesh):
|
||||
"""Volumen de un mesh en mm³. Retorna 0 si no hay mesh válido."""
|
||||
if mesh is None or mesh.countPoints() < 4:
|
||||
return 0
|
||||
try:
|
||||
return mesh.Volume
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
def makeEarthWorksVolume(vtype=0):
|
||||
"""Crea un objeto FeaturePython con el mesh de volumen."""
|
||||
obj = FreeCAD.ActiveDocument.addObject(
|
||||
"Part::FeaturePython", VOLUME_TYPES[vtype])
|
||||
EarthWorksVolume(obj)
|
||||
ViewProviderEarthWorksVolume(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# FeaturePython: EarthWorksVolume
|
||||
# =========================================================================
|
||||
|
||||
class EarthWorksVolume:
|
||||
"""Objeto que almacena un mesh de volumen (cut o fill)."""
|
||||
|
||||
def __init__(self, obj):
|
||||
self.setProperties(obj)
|
||||
obj.Proxy = self
|
||||
|
||||
def setProperties(self, obj):
|
||||
pl = obj.PropertiesList
|
||||
|
||||
if "VolumeType" not in pl:
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration", "VolumeType", "Volume",
|
||||
"Fill o Cut").VolumeType = VOLUME_TYPES
|
||||
|
||||
if "VolumeMesh" not in pl:
|
||||
obj.addProperty(
|
||||
"Mesh::PropertyMeshKernel", "VolumeMesh", "Volume",
|
||||
"Mesh del volumen")
|
||||
obj.setEditorMode("VolumeMesh", 2)
|
||||
|
||||
if "Volume" not in pl:
|
||||
obj.addProperty(
|
||||
"App::PropertyVolume", "Volume", "Volume",
|
||||
"Volumen calculado (mm³)")
|
||||
obj.setEditorMode("Volume", 1)
|
||||
|
||||
obj.IfcType = "Civil Element"
|
||||
obj.setEditorMode("IfcType", 1)
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
self.setProperties(obj)
|
||||
|
||||
def onChange(self, obj, prop):
|
||||
if prop == "VolumeMesh" and obj.VolumeMesh:
|
||||
obj.Volume = obj.VolumeMesh.Volume
|
||||
|
||||
def execute(self, obj):
|
||||
pass
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# ViewProvider (Coin3D)
|
||||
# =========================================================================
|
||||
|
||||
class ViewProviderEarthWorksVolume:
|
||||
def __init__(self, vobj):
|
||||
pl = vobj.PropertiesList
|
||||
is_cut = vobj.Object.VolumeType == "Cut"
|
||||
r, g, b = (1.0, 0.0, 0.0) if is_cut else (0.0, 0.0, 1.0)
|
||||
|
||||
if "Transparency" not in pl:
|
||||
vobj.addProperty(
|
||||
"App::PropertyIntegerConstraint", "Transparency",
|
||||
"Surface Style", "Transparencia (0=opaco, 100=invisible)")
|
||||
vobj.Transparency = (50, 0, 100, 1)
|
||||
|
||||
if "ShapeColor" not in pl:
|
||||
vobj.addProperty(
|
||||
"App::PropertyColor", "ShapeColor", "Surface Style",
|
||||
"Color de superficie")
|
||||
vobj.ShapeColor = (r, g, b, vobj.Transparency / 100)
|
||||
|
||||
if "ShapeMaterial" not in pl:
|
||||
vobj.addProperty(
|
||||
"App::PropertyMaterial", "ShapeMaterial", "Surface Style",
|
||||
"Material de superficie")
|
||||
vobj.ShapeMaterial = FreeCAD.Material()
|
||||
|
||||
vobj.Proxy = self
|
||||
vobj.ShapeMaterial.DiffuseColor = vobj.ShapeColor
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
if prop in ("ShapeColor", "Transparency"):
|
||||
if hasattr(vobj, "ShapeColor") and hasattr(vobj, "Transparency"):
|
||||
c = vobj.ShapeColor
|
||||
t = vobj.Transparency
|
||||
vobj.ShapeMaterial.DiffuseColor = (c[0], c[1], c[2], t / 100)
|
||||
|
||||
if prop == "ShapeMaterial":
|
||||
if hasattr(self, "face_material"):
|
||||
mat = vobj.ShapeMaterial
|
||||
self.face_material.diffuseColor.setValue(mat.DiffuseColor[:3])
|
||||
self.face_material.transparency = mat.DiffuseColor[3]
|
||||
|
||||
def attach(self, vobj):
|
||||
from pivy import coin
|
||||
|
||||
self.geo_coords = coin.SoGeoCoordinate()
|
||||
self.triangles = coin.SoIndexedFaceSet()
|
||||
self.face_material = coin.SoMaterial()
|
||||
self.edge_material = coin.SoMaterial()
|
||||
self.edge_style = coin.SoDrawStyle()
|
||||
self.edge_style.style = coin.SoDrawStyle.LINES
|
||||
|
||||
shape_hints = coin.SoShapeHints()
|
||||
shape_hints.vertex_ordering = coin.SoShapeHints.COUNTERCLOCKWISE
|
||||
mat_binding = coin.SoMaterialBinding()
|
||||
mat_binding.value = coin.SoMaterialBinding.PER_FACE
|
||||
offset = coin.SoPolygonOffset()
|
||||
offset.styles = coin.SoPolygonOffset.LINES
|
||||
offset.factor = -2.0
|
||||
|
||||
highlight = coin.SoType.fromName("SoFCSelection").createInstance()
|
||||
highlight.style = "EMISSIVE_DIFFUSE"
|
||||
highlight.addChild(shape_hints)
|
||||
highlight.addChild(mat_binding)
|
||||
highlight.addChild(self.geo_coords)
|
||||
highlight.addChild(self.triangles)
|
||||
|
||||
face = coin.SoSeparator()
|
||||
face.addChild(self.face_material)
|
||||
face.addChild(highlight)
|
||||
|
||||
edge = coin.SoSeparator()
|
||||
edge.addChild(self.edge_material)
|
||||
edge.addChild(self.edge_style)
|
||||
edge.addChild(highlight)
|
||||
|
||||
surface = coin.SoSeparator()
|
||||
surface.addChild(face)
|
||||
surface.addChild(offset)
|
||||
surface.addChild(edge)
|
||||
|
||||
wireframe = coin.SoSeparator()
|
||||
wireframe.addChild(edge)
|
||||
|
||||
vobj.addDisplayMode(surface, "Surface")
|
||||
vobj.addDisplayMode(wireframe, "Wireframe")
|
||||
|
||||
self.onChanged(vobj, "ShapeColor")
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
if prop == "VolumeMesh":
|
||||
mesh = obj.VolumeMesh
|
||||
if mesh is None or mesh.countPoints() == 0:
|
||||
return
|
||||
try:
|
||||
geo = ["UTM", FreeCAD.ActiveDocument.Site.UtmZone, "FLAT"]
|
||||
except Exception:
|
||||
geo = ["UTM", "30N", "FLAT"]
|
||||
self.geo_coords.geoSystem.setValues(geo)
|
||||
|
||||
cm = mesh.copy()
|
||||
triangles = []
|
||||
for i in cm.Topology[1]:
|
||||
triangles.extend(list(i))
|
||||
triangles.append(-1)
|
||||
self.geo_coords.point.setValues(cm.Topology[0])
|
||||
self.triangles.coordIndex.setValues(triangles)
|
||||
|
||||
def getIcon(self):
|
||||
return str(os.path.join(DirIcons, "googleearth.svg"))
|
||||
|
||||
def getDisplayModes(self, vobj):
|
||||
return ["Surface", "Wireframe"]
|
||||
|
||||
def getDefaultDisplayMode(self):
|
||||
return "Surface"
|
||||
|
||||
def setDisplayMode(self, mode):
|
||||
return mode
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# TaskPanel
|
||||
# =========================================================================
|
||||
|
||||
class EarthWorksTaskPanel:
|
||||
def __init__(self):
|
||||
self.form = FreeCADGui.PySideUic.loadUi(
|
||||
os.path.join(PVPlantResources.__dir__, "PVPlantEarthworks.ui"))
|
||||
self.form.setWindowIcon(
|
||||
QtGui.QIcon(os.path.join(PVPlantResources.DirIcons, "convert.svg")))
|
||||
|
||||
def accept(self):
|
||||
land = FreeCAD.ActiveDocument.Terrain.Mesh.copy()
|
||||
|
||||
# Detectar si hay un Platform seleccionado o trackers sueltos
|
||||
platform_obj = None
|
||||
frames = []
|
||||
|
||||
for obj in FreeCADGui.Selection.getSelection():
|
||||
if hasattr(obj, "Proxy"):
|
||||
if obj.Proxy.__class__.__name__ == 'Platform':
|
||||
platform_obj = obj
|
||||
t = getattr(obj.Proxy, "Type", None)
|
||||
if t == "Tracker" and obj not in frames:
|
||||
frames.append(obj)
|
||||
elif t == "FrameArea":
|
||||
for fr in obj.Frames:
|
||||
if fr not in frames:
|
||||
frames.append(fr)
|
||||
|
||||
if not frames and not platform_obj:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Selecciona trackers, un FrameArea o un Platform\n")
|
||||
return False
|
||||
|
||||
slope = getattr(FreeCAD.ActiveDocument,
|
||||
"MaximumWestEastSlope", 10.0)
|
||||
|
||||
FreeCAD.ActiveDocument.openTransaction("Movimiento de tierras")
|
||||
try:
|
||||
input_data = platform_obj if platform_obj else frames
|
||||
cut_mesh, fill_mesh, vol_cut, vol_fill = compute_earthworks(
|
||||
input_data, land, slope)
|
||||
|
||||
if cut_mesh and cut_mesh.countPoints() > 3:
|
||||
v = makeEarthWorksVolume(1) # Cut
|
||||
v.VolumeMesh = cut_mesh
|
||||
FreeCAD.Console.PrintMessage(
|
||||
f"Volumen de corte: {vol_cut:,.0f} mm³\n")
|
||||
|
||||
if fill_mesh and fill_mesh.countPoints() > 3:
|
||||
v = makeEarthWorksVolume(0) # Fill
|
||||
v.VolumeMesh = fill_mesh
|
||||
FreeCAD.Console.PrintMessage(
|
||||
f"Volumen de relleno: {vol_fill:,.0f} mm³\n")
|
||||
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(
|
||||
f"Error en movimiento de tierras: {e}\n")
|
||||
finally:
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return True
|
||||
|
||||
def reject(self):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return True
|
||||
@@ -0,0 +1,403 @@
|
||||
# /**********************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2026 Javier Braña <javier.branagutierrez@gmail.com> *
|
||||
# * *
|
||||
# * PlacementCalc - Lógica de cálculo de placement de trackers *
|
||||
# * *
|
||||
# * Separado de PVPlantPlacement.py para mantener limpio el archivo *
|
||||
# * de interfaz (TaskPanels, comandos, ViewProviders). *
|
||||
# * *
|
||||
# * Funciones exportadas: *
|
||||
# * - getRows(objs) → listas de filas *
|
||||
# * - getCols(objs) → listas de columnas *
|
||||
# * - optimized_cut(L_total, piezas, margen, metodo) *
|
||||
# * - adjustToTerrain(frames, individual) *
|
||||
# * - get_trend(points) / getTrend(points) *
|
||||
# * - getHeadsAndSoil(frame=None) *
|
||||
# * - moveFrameHead(obj, head, dist) *
|
||||
# * - selectionFilter(sel, objtype) *
|
||||
# * - ConvertObjectsTo(sel, objTo) *
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import Part
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# selectionFilter
|
||||
# =========================================================================
|
||||
|
||||
def selectionFilter(sel, objtype):
|
||||
"""Filtra una selección por tipo de Proxy."""
|
||||
fil = []
|
||||
for obj in sel:
|
||||
if hasattr(obj, "Proxy"):
|
||||
if obj.Proxy.__class__ is objtype:
|
||||
fil.append(obj)
|
||||
return fil
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# optimized_cut
|
||||
# =========================================================================
|
||||
|
||||
def optimized_cut(L_total, piezas, margen=0, metodo='auto'):
|
||||
"""
|
||||
Optimiza el corte de piezas en una longitud total.
|
||||
Similar al algoritmo de corte óptimo de barras.
|
||||
|
||||
Args:
|
||||
L_total: Longitud total disponible
|
||||
piezas: Lista de longitudes de piezas a cortar
|
||||
margen: Margen de seguridad por corte
|
||||
metodo: 'auto', 'greedy' o 'exact'
|
||||
|
||||
Returns:
|
||||
dict con piezas cortadas, desperdicio, etc.
|
||||
"""
|
||||
if not piezas:
|
||||
return {'piezas': [], 'desperdicio': L_total}
|
||||
|
||||
piezas_ord = sorted(piezas, reverse=True)
|
||||
resultado = []
|
||||
restante = L_total
|
||||
|
||||
for pieza in piezas_ord:
|
||||
if pieza + margen <= restante:
|
||||
resultado.append(pieza)
|
||||
restante -= (pieza + margen)
|
||||
|
||||
return {
|
||||
'piezas': resultado,
|
||||
'desperdicio': restante,
|
||||
'n_piezas': len(resultado),
|
||||
'eficiencia': (L_total - restante) / L_total * 100 if L_total > 0 else 0
|
||||
}
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# get_trend / getTrend
|
||||
# =========================================================================
|
||||
|
||||
def get_trend(points):
|
||||
"""
|
||||
Calcula la tendencia lineal de un conjunto de puntos 3D.
|
||||
Devuelve (pendiente_x, pendiente_z, intersección) en el plano XZ.
|
||||
"""
|
||||
if len(points) < 2:
|
||||
return 0, 0, 0
|
||||
|
||||
xs = np.array([p.x for p in points])
|
||||
zs = np.array([p.z for p in points])
|
||||
|
||||
if np.std(xs) < 1:
|
||||
return 0, 0, np.mean(zs)
|
||||
|
||||
A = np.vstack([xs, np.ones(len(xs))]).T
|
||||
m, c = np.linalg.lstsq(A, zs, rcond=None)[0]
|
||||
return m, 0, c
|
||||
|
||||
|
||||
def getTrend(points):
|
||||
"""Wrapper para compatibilidad (versión antigua)."""
|
||||
return get_trend(points)
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# adjustToTerrain
|
||||
# =========================================================================
|
||||
|
||||
def adjustToTerrain(frames, individual=True):
|
||||
"""
|
||||
Ajusta la altura de los frames al terreno.
|
||||
|
||||
Args:
|
||||
frames: lista de objetos tracker
|
||||
individual: si True, ajusta cada frame individualmente.
|
||||
si False, ajusta por filas.
|
||||
"""
|
||||
if not frames:
|
||||
return
|
||||
|
||||
terrain = None
|
||||
try:
|
||||
terrain = FreeCAD.ActiveDocument.Site.Terrain
|
||||
except Exception:
|
||||
FreeCAD.Console.PrintWarning("No hay terreno en el Site\n")
|
||||
return
|
||||
|
||||
if individual:
|
||||
for frame in frames:
|
||||
_adjust_single_frame(frame, terrain)
|
||||
else:
|
||||
cols = getCols(list(frames))
|
||||
if cols:
|
||||
for col in cols:
|
||||
for group in col:
|
||||
if group:
|
||||
_adjust_frame_group(group, terrain)
|
||||
|
||||
|
||||
def _adjust_single_frame(frame, terrain):
|
||||
"""Ajusta un frame individual al terreno."""
|
||||
try:
|
||||
bb = frame.Shape.BoundBox
|
||||
center = bb.Center
|
||||
z_terrain = _get_terrain_z(terrain, center.x, center.y)
|
||||
if z_terrain is not None:
|
||||
frame.Placement.Base.z = z_terrain
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _adjust_frame_group(group, terrain):
|
||||
"""Ajusta un grupo de frames al terreno siguiendo la pendiente."""
|
||||
if not group:
|
||||
return
|
||||
z_values = []
|
||||
for frame in group:
|
||||
try:
|
||||
bb = frame.Shape.BoundBox
|
||||
center = bb.Center
|
||||
z = _get_terrain_z(terrain, center.x, center.y)
|
||||
if z is not None:
|
||||
z_values.append(z)
|
||||
except Exception:
|
||||
z_values.append(None)
|
||||
|
||||
valid_zs = [z for z in z_values if z is not None]
|
||||
if not valid_zs:
|
||||
return
|
||||
|
||||
# Ajustar cada frame a la altura del terreno interpolada
|
||||
for i, frame in enumerate(group):
|
||||
if i < len(z_values) and z_values[i] is not None:
|
||||
try:
|
||||
frame.Placement.Base.z = z_values[i]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _get_terrain_z(terrain, x, y):
|
||||
"""Obtiene la cota Z del terreno en un punto (x, y)."""
|
||||
try:
|
||||
if hasattr(terrain, 'Shape') and terrain.Shape:
|
||||
shape = terrain.Shape
|
||||
# Proyectar un rayo vertical
|
||||
p1 = FreeCAD.Vector(x, y, 10000)
|
||||
p2 = FreeCAD.Vector(x, y, -10000)
|
||||
dist, pts, info = shape.distToShape(Part.LineSegment(p1, p2).toShape())
|
||||
if pts:
|
||||
return pts[0][0].z
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# getRows / getCols
|
||||
# =========================================================================
|
||||
|
||||
def getRows(objs):
|
||||
"""
|
||||
Agrupa objetos tracker en filas según su posición Y y estructura de Placement.
|
||||
|
||||
Args:
|
||||
objs: lista de objetos tracker
|
||||
|
||||
Returns:
|
||||
(rows, columns): tupla de listas de listas
|
||||
"""
|
||||
if not objs:
|
||||
return None, None
|
||||
|
||||
# Ordenar por Placement.Base.y
|
||||
sorted_objs = sorted(objs, key=lambda x: x.Placement.Base.y, reverse=True)
|
||||
|
||||
rows = []
|
||||
processed = set()
|
||||
|
||||
for obj in sorted_objs:
|
||||
if obj.Name in processed:
|
||||
continue
|
||||
row = [obj]
|
||||
processed.add(obj.Name)
|
||||
base = obj.Placement.Base
|
||||
for other in sorted_objs:
|
||||
if other.Name in processed:
|
||||
continue
|
||||
# Misma fila si están alineados en Y (misma posición de fila)
|
||||
if abs(other.Placement.Base.y - base.y) < 5000:
|
||||
row.append(other)
|
||||
processed.add(other.Name)
|
||||
rows.append(row)
|
||||
|
||||
# Ordenar cada fila por X
|
||||
for row in rows:
|
||||
row.sort(key=lambda x: x.Placement.Base.x)
|
||||
|
||||
# Calcular columnas
|
||||
columns = _compute_columns(rows)
|
||||
|
||||
return rows, columns
|
||||
|
||||
|
||||
def getCols(objs):
|
||||
"""
|
||||
Agrupa objetos tracker en columnas.
|
||||
|
||||
Args:
|
||||
objs: lista de objetos tracker
|
||||
|
||||
Returns:
|
||||
list: columnas, donde cada columna es una lista de grupos (filas)
|
||||
"""
|
||||
rows, columns = getRows(objs)
|
||||
return columns
|
||||
|
||||
|
||||
def getCols_old(sel, tolerance=4000, sort=True):
|
||||
"""Versión antigua de getCols, mantenida por compatibilidad."""
|
||||
if not sel:
|
||||
return []
|
||||
|
||||
# Ordenar por Y descendente
|
||||
sorted_sel = sorted(sel, key=lambda x: x.Placement.Base.y, reverse=True)
|
||||
|
||||
cols = []
|
||||
used = set()
|
||||
|
||||
for obj in sorted_sel:
|
||||
if obj.Name in used:
|
||||
continue
|
||||
fila = [obj]
|
||||
used.add(obj.Name)
|
||||
base_x = obj.Placement.Base.x
|
||||
for other in sorted_sel:
|
||||
if other.Name in used:
|
||||
continue
|
||||
if abs(other.Placement.Base.x - base_x) <= tolerance:
|
||||
fila.append(other)
|
||||
used.add(other.Name)
|
||||
if sort:
|
||||
fila.sort(key=lambda x: x.Placement.Base.y, reverse=True)
|
||||
cols.append(fila)
|
||||
|
||||
return cols
|
||||
|
||||
|
||||
def _compute_columns(rows):
|
||||
"""
|
||||
Calcula la estructura de columnas a partir de las filas.
|
||||
Cada columna agrupa los frames en la misma posición X vertical.
|
||||
"""
|
||||
if not rows:
|
||||
return []
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
# Mapa: posición X → lista de frames
|
||||
col_map = defaultdict(list)
|
||||
for row in rows:
|
||||
for i, frame in enumerate(row):
|
||||
col_map[i].append(frame)
|
||||
|
||||
columns = []
|
||||
for idx in sorted(col_map.keys()):
|
||||
col = col_map[idx]
|
||||
columns.append(col)
|
||||
|
||||
return columns
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# getHeadsAndSoil / moveFrameHead
|
||||
# =========================================================================
|
||||
|
||||
def getHeadsAndSoil(frame=None):
|
||||
"""
|
||||
Obtiene las cabezas y suelos de un tracker (o del documento activo).
|
||||
"""
|
||||
if frame:
|
||||
frames = [frame]
|
||||
else:
|
||||
try:
|
||||
frames = [o for o in FreeCAD.ActiveDocument.Objects
|
||||
if hasattr(o, 'Proxy') and getattr(o.Proxy, 'Type', None) == 'Tracker']
|
||||
except Exception:
|
||||
return [], []
|
||||
|
||||
heads = []
|
||||
soils = []
|
||||
for f in frames:
|
||||
try:
|
||||
if hasattr(f, 'HeadPoints'):
|
||||
heads.extend(f.HeadPoints)
|
||||
if hasattr(f, 'SoilPoints'):
|
||||
soils.extend(f.SoilPoints)
|
||||
except Exception:
|
||||
pass
|
||||
return heads, soils
|
||||
|
||||
|
||||
def moveFrameHead(obj, head=0, dist=0):
|
||||
"""
|
||||
Mueve la cabeza de un tracker una distancia determinada.
|
||||
|
||||
Args:
|
||||
obj: objeto tracker
|
||||
head: 0=izquierda, 1=derecha
|
||||
dist: distancia a mover (mm)
|
||||
"""
|
||||
try:
|
||||
if not hasattr(obj, 'Proxy') or getattr(obj.Proxy, 'Type', None) != 'Tracker':
|
||||
return
|
||||
# Lógica de movimiento basada en Placement
|
||||
placement = obj.Placement
|
||||
direction = placement.Rotation.multVec(FreeCAD.Vector(1, 0, 0))
|
||||
if head == 0:
|
||||
placement.Base = placement.Base - direction * dist
|
||||
else:
|
||||
placement.Base = placement.Base + direction * dist
|
||||
obj.Placement = placement
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# ConvertObjectsTo
|
||||
# =========================================================================
|
||||
|
||||
def ConvertObjectsTo(sel, objTo):
|
||||
"""
|
||||
Convierte objetos seleccionados a otro tipo.
|
||||
|
||||
Args:
|
||||
sel: lista de objetos seleccionados
|
||||
objTo: clase destino (FeaturePython)
|
||||
"""
|
||||
if not sel or not objTo:
|
||||
return
|
||||
|
||||
for obj in sel:
|
||||
try:
|
||||
if hasattr(obj, "Proxy"):
|
||||
isFrame = obj.Proxy.__class__ is objTo
|
||||
# Si ya es del tipo destino, se salta
|
||||
if isFrame:
|
||||
continue
|
||||
|
||||
# Crear nuevo objeto del tipo destino
|
||||
if hasattr(obj, "Shape") and obj.Shape:
|
||||
new_obj = FreeCAD.ActiveDocument.addObject(
|
||||
"Part::FeaturePython", obj.Name + "_converted")
|
||||
# Aquí iría la lógica específica de conversión
|
||||
# dependiendo del tipo de objeto origen y destino
|
||||
FreeCAD.Console.PrintMessage(
|
||||
f"Convertido {obj.Label}\n")
|
||||
except Exception:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
f"No se pudo convertir {obj.Label}\n")
|
||||
@@ -0,0 +1,468 @@
|
||||
# /**********************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2026 Javier Braña <javier.branagutierrez@gmail.com> *
|
||||
# * *
|
||||
# * PVPlantPlatform - Plataforma de diseño solar *
|
||||
# * *
|
||||
# * Es el elemento central del movimiento de tierras. Representa la *
|
||||
# * superficie diseñada generada a partir de la disposición de trackers.*
|
||||
# * *
|
||||
# * De ella dependen: *
|
||||
# * - EarthWorks: cut/fill entre plataforma y terreno natural *
|
||||
# * - Road: trazado de viales sobre la plataforma *
|
||||
# * - Drainage: drenaje superficial *
|
||||
# * - Trench: zanjas sobre la plataforma *
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import Part
|
||||
import math
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui, os
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
else:
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt): return txt
|
||||
|
||||
import PVPlantResources
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# Constructor
|
||||
# =========================================================================
|
||||
|
||||
def make_platform(frames=None, name="Platform"):
|
||||
"""
|
||||
Crea un objeto Platform en el documento activo.
|
||||
|
||||
Args:
|
||||
frames: lista opcional de objetos tracker para inicializar
|
||||
name: nombre del objeto
|
||||
|
||||
Returns:
|
||||
Objeto FeaturePython Platform, o None si no hay documento
|
||||
"""
|
||||
doc = FreeCAD.ActiveDocument
|
||||
if doc is None:
|
||||
return None
|
||||
|
||||
obj = doc.addObject("Part::FeaturePython", name)
|
||||
Platform(obj)
|
||||
_ViewProviderPlatform(obj.ViewObject)
|
||||
obj.Label = name
|
||||
|
||||
if frames:
|
||||
# Asignar SourceFrames como lista de enlaces
|
||||
obj.SourceFrames = frames
|
||||
|
||||
doc.recompute()
|
||||
return obj
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# FeaturePython: Platform
|
||||
# =========================================================================
|
||||
|
||||
class Platform:
|
||||
"""
|
||||
Plataforma de diseño generada desde trackers solares.
|
||||
|
||||
Propiedades principales:
|
||||
SourceFrames : Lista de trackers que definen la plataforma
|
||||
SlopeTolerance : Pendiente máxima E-W (grados)
|
||||
PlatformArea : Área de la plataforma (solo lectura)
|
||||
PlatformVolume : Volumen bajo la plataforma (solo lectura)
|
||||
Status : Estado del último cálculo
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
self.setProperties(obj)
|
||||
obj.Proxy = self
|
||||
|
||||
def setProperties(self, obj):
|
||||
pl = obj.PropertiesList
|
||||
|
||||
if "SourceFrames" not in pl:
|
||||
obj.addProperty(
|
||||
"App::PropertyLinkList", "SourceFrames",
|
||||
"Platform",
|
||||
"Trackers que definen la plataforma")
|
||||
|
||||
if "SlopeTolerance" not in pl:
|
||||
obj.addProperty(
|
||||
"App::PropertyFloat", "SlopeTolerance",
|
||||
"Platform",
|
||||
"Pendiente transversal máxima (grados)").SlopeTolerance = 10.0
|
||||
|
||||
if "PlatformArea" not in pl:
|
||||
obj.addProperty(
|
||||
"App::PropertyArea", "PlatformArea",
|
||||
"Platform",
|
||||
"Área total de la plataforma (solo lectura)")
|
||||
obj.setEditorMode("PlatformArea", 1)
|
||||
|
||||
if "PlatformVolume" not in pl:
|
||||
obj.addProperty(
|
||||
"App::PropertyVolume", "PlatformVolume",
|
||||
"Platform",
|
||||
"Volumen bajo la plataforma (solo lectura)")
|
||||
obj.setEditorMode("PlatformVolume", 1)
|
||||
|
||||
if "NumberOfFrames" not in pl:
|
||||
obj.addProperty(
|
||||
"App::PropertyInteger", "NumberOfFrames",
|
||||
"Platform",
|
||||
"Número de trackers en la plataforma")
|
||||
obj.setEditorMode("NumberOfFrames", 1)
|
||||
|
||||
if "Status" not in pl:
|
||||
obj.addProperty(
|
||||
"App::PropertyString", "Status",
|
||||
"Platform",
|
||||
"Estado del último cálculo")
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
self.setProperties(obj)
|
||||
|
||||
def execute(self, obj):
|
||||
"""Calcula la plataforma a partir de los SourceFrames."""
|
||||
frames = obj.SourceFrames
|
||||
if not frames:
|
||||
obj.Shape = Part.Shape()
|
||||
obj.Status = "Sin frames"
|
||||
return
|
||||
|
||||
obj.NumberOfFrames = len(frames)
|
||||
slope = obj.SlopeTolerance
|
||||
|
||||
try:
|
||||
shape = _build_platform_shape(frames, slope)
|
||||
except Exception as e:
|
||||
obj.Status = f"Error: {e}"
|
||||
FreeCAD.Console.PrintError(
|
||||
f"Error al generar plataforma: {e}\n")
|
||||
return
|
||||
|
||||
if shape is None:
|
||||
obj.Status = "No se pudo generar"
|
||||
return
|
||||
|
||||
obj.Shape = shape
|
||||
|
||||
# Calcular área y volumen
|
||||
try:
|
||||
area = shape.Area
|
||||
if area > 0:
|
||||
obj.PlatformArea = area
|
||||
# Volumen aproximado: proyectar al plano XY
|
||||
try:
|
||||
obj.PlatformVolume = shape.Volume
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
obj.Status = f"OK - {len(frames)} frames"
|
||||
FreeCAD.Console.PrintMessage(
|
||||
f"Plataforma generada: {len(frames)} frames, "
|
||||
f"área={area:,.0f} mm²\n")
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# Cálculo de la plataforma (lógica principal)
|
||||
# =========================================================================
|
||||
|
||||
def _build_platform_shape(frames, slope_tolerance):
|
||||
"""
|
||||
Construye la geometría de la plataforma desde los frames.
|
||||
|
||||
Returns:
|
||||
Part.Solid o None si falla
|
||||
"""
|
||||
rows, columns = _get_tracker_rows(frames)
|
||||
if rows is None or not rows:
|
||||
return None
|
||||
|
||||
all_faces = []
|
||||
tools = []
|
||||
|
||||
# Fase 1: Lofts longitudinales (a lo largo de cada fila)
|
||||
for group in rows:
|
||||
lines = _generate_row_lines(group, slope_tolerance)
|
||||
tools.extend(lines["tools"])
|
||||
if len(lines["edges"]) >= 2:
|
||||
try:
|
||||
loft = Part.makeLoft(lines["edges"], False, True, False)
|
||||
if loft and not loft.isNull():
|
||||
all_faces.extend(loft.Faces)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fase 2: Lofts transversales (entre columnas)
|
||||
if columns:
|
||||
for group in rows:
|
||||
for frame in group:
|
||||
col, idx = _find_in_columns(frame, columns)
|
||||
tool = _find_tool(frame, tools)
|
||||
if tool is None or idx >= len(col) - 1:
|
||||
continue
|
||||
|
||||
next_frame = col[idx + 1]
|
||||
next_tool = _find_tool(next_frame, tools)
|
||||
if next_tool is None:
|
||||
continue
|
||||
|
||||
try:
|
||||
l1 = Part.LineSegment(
|
||||
tool[1].Vertexes[-1].Point,
|
||||
next_tool[1].Vertexes[0].Point
|
||||
).toShape()
|
||||
l2 = Part.LineSegment(
|
||||
tool[2].Vertexes[-1].Point,
|
||||
next_tool[2].Vertexes[0].Point
|
||||
).toShape()
|
||||
if l1 and l2:
|
||||
loft = Part.makeLoft([l1, l2], False, True, False)
|
||||
if loft and not loft.isNull():
|
||||
all_faces.extend(loft.Faces)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not all_faces:
|
||||
return None
|
||||
|
||||
# Fase 3: Unir caras en un sólido
|
||||
try:
|
||||
platform = None
|
||||
for face in all_faces:
|
||||
if platform is None:
|
||||
platform = face
|
||||
else:
|
||||
try:
|
||||
platform = platform.fuse(face)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if platform is None:
|
||||
return None
|
||||
|
||||
if platform.ShapeType == "Shell":
|
||||
try:
|
||||
platform = Part.makeSolid(platform)
|
||||
except Exception:
|
||||
pass
|
||||
elif platform.ShapeType == "Compound":
|
||||
faces_in = [s for s in platform.SubShapes if s.ShapeType == "Face"]
|
||||
if faces_in:
|
||||
try:
|
||||
shell = Part.makeShell(faces_in)
|
||||
platform = Part.makeSolid(shell)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return platform if not platform.isNull() else None
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _get_tracker_rows(frames):
|
||||
"""Agrupa trackers usando la lógica de PVPlantPlacement."""
|
||||
try:
|
||||
import PVPlantPlacement
|
||||
return PVPlantPlacement.getRows(frames)
|
||||
except Exception:
|
||||
return None, None
|
||||
|
||||
|
||||
def _generate_row_lines(group, slope_tolerance):
|
||||
"""
|
||||
Genera líneas de borde (izquierda/derecha) para una fila de trackers.
|
||||
|
||||
Returns:
|
||||
dict con edges (lista de Part.Shape) y tools (lista de [frame, izq, der])
|
||||
"""
|
||||
result = {"edges": [], "tools": []}
|
||||
|
||||
for i, frame in enumerate(group):
|
||||
if not hasattr(frame, "Setup"):
|
||||
continue
|
||||
|
||||
aw = _angle_to_prev(group, i)
|
||||
ae = _angle_to_next(group, i)
|
||||
anf = (aw + ae) / 2
|
||||
if anf > slope_tolerance:
|
||||
anf = slope_tolerance
|
||||
|
||||
wdt = _get_half_width(frame)
|
||||
zz = wdt * math.sin(math.radians(anf))
|
||||
|
||||
base = _get_base_line(frame)
|
||||
|
||||
li = base.copy()
|
||||
li.Placement = frame.Placement
|
||||
li.Placement.Rotation = frame.Placement.Rotation
|
||||
li.Placement.Base.x -= wdt
|
||||
li.Placement.Base.z -= zz
|
||||
result["edges"].append(li)
|
||||
|
||||
ld = base.copy()
|
||||
ld.Placement = frame.Placement
|
||||
ld.Placement.Rotation = frame.Placement.Rotation
|
||||
ld.Placement.Base.x += wdt
|
||||
ld.Placement.Base.z += zz
|
||||
result["edges"].append(ld)
|
||||
|
||||
result["tools"].append([frame, li, ld])
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _get_half_width(frame):
|
||||
try:
|
||||
return int(frame.Setup.Width / 2)
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
def _get_base_line(frame):
|
||||
try:
|
||||
lng = int(frame.Setup.Length / 2)
|
||||
return Part.LineSegment(
|
||||
FreeCAD.Vector(-lng, 0, 0),
|
||||
FreeCAD.Vector(lng, 0, 0)
|
||||
).toShape()
|
||||
except Exception:
|
||||
try:
|
||||
bb = frame.Setup.Shape.BoundBox
|
||||
return Part.LineSegment(
|
||||
FreeCAD.Vector(bb.XMin, 0, 0),
|
||||
FreeCAD.Vector(bb.XMax, 0, 0)
|
||||
).toShape()
|
||||
except Exception:
|
||||
return Part.LineSegment(
|
||||
FreeCAD.Vector(-2000, 0, 0),
|
||||
FreeCAD.Vector(2000, 0, 0)
|
||||
).toShape()
|
||||
|
||||
|
||||
def _angle_to_prev(group, i):
|
||||
if i <= 0:
|
||||
return 0
|
||||
return _angle_xz(
|
||||
group[i - 1].Placement.Base,
|
||||
group[i].Placement.Base
|
||||
)
|
||||
|
||||
|
||||
def _angle_to_next(group, i):
|
||||
if i >= len(group) - 1:
|
||||
return 0
|
||||
return _angle_xz(
|
||||
group[i].Placement.Base,
|
||||
group[i + 1].Placement.Base
|
||||
)
|
||||
|
||||
|
||||
def _angle_xz(v1, v2):
|
||||
dx = v2.x - v1.x
|
||||
dz = v2.z - v1.z
|
||||
return math.degrees(math.atan2(dz, dx))
|
||||
|
||||
|
||||
def _find_in_columns(frame, columns):
|
||||
for col in columns:
|
||||
for g in col:
|
||||
if frame in g:
|
||||
return g, g.index(frame)
|
||||
return [], -1
|
||||
|
||||
|
||||
def _find_tool(frame, tools):
|
||||
for t in tools:
|
||||
if t[0] == frame:
|
||||
return t
|
||||
return None
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# ViewProvider
|
||||
# =========================================================================
|
||||
|
||||
class _ViewProviderPlatform:
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
pl = vobj.PropertiesList
|
||||
|
||||
if "Transparency" not in pl:
|
||||
vobj.addProperty(
|
||||
"App::PropertyIntegerConstraint", "Transparency",
|
||||
"Platform Style", "Transparencia de la plataforma")
|
||||
vobj.Transparency = (40, 0, 100, 1)
|
||||
|
||||
if "ShapeColor" not in pl:
|
||||
vobj.addProperty(
|
||||
"App::PropertyColor", "ShapeColor",
|
||||
"Platform Style", "Color de la plataforma")
|
||||
vobj.ShapeColor = (0.3, 0.8, 0.3, 0.6) # verde semitransparente
|
||||
|
||||
if "ShapeMaterial" not in pl:
|
||||
vobj.addProperty(
|
||||
"App::PropertyMaterial", "ShapeMaterial",
|
||||
"Platform Style", "Material")
|
||||
vobj.ShapeMaterial = FreeCAD.Material()
|
||||
vobj.ShapeMaterial.DiffuseColor = vobj.ShapeColor
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
if prop in ("ShapeColor", "Transparency"):
|
||||
if hasattr(vobj, "ShapeColor") and hasattr(vobj, "Transparency"):
|
||||
c = vobj.ShapeColor
|
||||
t = vobj.Transparency
|
||||
vobj.ShapeMaterial.DiffuseColor = (c[0], c[1], c[2], t / 100)
|
||||
|
||||
def getIcon(self):
|
||||
return str(os.path.join(DirIcons, "solar-fixed.svg"))
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# Functions de conveniencia (API pública)
|
||||
# =========================================================================
|
||||
|
||||
def build_platform(frames, slope_tolerance=10.0):
|
||||
"""
|
||||
API pública: construye la geometría de plataforma desde frames.
|
||||
Útil para EarthWorks, Road, etc. que quieran la Shape sin crear objeto.
|
||||
|
||||
Returns:
|
||||
Part.Solid o None
|
||||
"""
|
||||
return _build_platform_shape(frames, slope_tolerance)
|
||||
|
||||
|
||||
def get_platform_shape(platform_obj):
|
||||
"""
|
||||
Obtiene la Shape de un objeto Platform de forma segura.
|
||||
"""
|
||||
if platform_obj is None:
|
||||
return None
|
||||
try:
|
||||
shape = platform_obj.Shape
|
||||
if shape and not shape.isNull():
|
||||
return shape
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
@@ -114,7 +114,7 @@ def makeTrench(base=None):
|
||||
|
||||
try:
|
||||
folder = FreeCAD.ActiveDocument.Trenches
|
||||
except:
|
||||
except AttributeError:
|
||||
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Trenches')
|
||||
folder.Label = "Trenches"
|
||||
folder.addObject(obj)
|
||||
@@ -340,7 +340,44 @@ class Trench(ArchComponent.Component):
|
||||
p2.z = 0
|
||||
return p2.sub(p1)
|
||||
|
||||
def getsegments(wire):
|
||||
def getsegments(wire): #deepseek
|
||||
"""Divide un wire en segmentos rectos basados en cambios de dirección (sin splitWiresByCurvature)"""
|
||||
import Part
|
||||
from math import degrees
|
||||
|
||||
segments = []
|
||||
current_segment = []
|
||||
angle_threshold = 1.0 # Grados para considerar cambio de dirección
|
||||
|
||||
def get_angle(v1, v2):
|
||||
return degrees(v1.getAngle(v2))
|
||||
|
||||
edges = wire.Edges
|
||||
for i in range(len(edges)):
|
||||
if i == 0:
|
||||
current_segment.append(edges[i])
|
||||
continue
|
||||
|
||||
prev_edge = edges[i - 1]
|
||||
curr_edge = edges[i]
|
||||
|
||||
# Vectores de dirección
|
||||
v1 = prev_edge.tangentAt(prev_edge.FirstParameter)
|
||||
v2 = curr_edge.tangentAt(curr_edge.FirstParameter)
|
||||
|
||||
angle = get_angle(v1, v2)
|
||||
if angle > angle_threshold:
|
||||
segments.append(Part.Wire(current_segment))
|
||||
current_segment = [curr_edge]
|
||||
else:
|
||||
current_segment.append(curr_edge)
|
||||
|
||||
if current_segment:
|
||||
segments.append(Part.Wire(current_segment))
|
||||
|
||||
return segments
|
||||
|
||||
def getsegments_old(wire):
|
||||
import math
|
||||
segments = []
|
||||
|
||||
@@ -381,13 +418,6 @@ class Trench(ArchComponent.Component):
|
||||
pts_plane.append(tmp)
|
||||
path_plane = Part.makePolygon(pts_plane)
|
||||
|
||||
'''o1 = path_plane.makeOffset2D(d, 2, False, True, True)
|
||||
o2 = path_plane.makeOffset2D(-d, 2, False, True, True)
|
||||
points = calculateOffset(o1)
|
||||
points.insert(0, points.pop(1))
|
||||
points.reverse()
|
||||
points2 = calculateOffset(o2)'''
|
||||
|
||||
points = self.calculateOffset(path_plane, d)
|
||||
points2 = self.calculateOffset(path_plane, -d)
|
||||
|
||||
@@ -1018,7 +1048,7 @@ class semiAutomaticTrench:
|
||||
self.unregister_editing_callbacks()
|
||||
|
||||
|
||||
class CommandTrench: # V1:
|
||||
'''class CommandTrench: # V1:
|
||||
"""Gui command for the Line tool."""
|
||||
|
||||
def GetResources(self):
|
||||
@@ -1107,5 +1137,5 @@ if FreeCAD.GuiUp:
|
||||
|
||||
FreeCADGui.addCommand('PVPlantTrench', CommandTrench())
|
||||
FreeCADGui.addCommand('PVPlantSemiAutomaticTrench', CommandSemiAutomaticTrench())
|
||||
FreeCADGui.addCommand('Trenches', CommandTrenchGroup())
|
||||
FreeCADGui.addCommand('Trenches', CommandTrenchGroup())'''
|
||||
|
||||
@@ -47,16 +47,16 @@ except AttributeError:
|
||||
import PVPlantResources
|
||||
|
||||
|
||||
def makeCable(base = None):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Cable")
|
||||
def makeCable(name="Cable"):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
|
||||
obj.Label = name
|
||||
Cable(obj)
|
||||
ViewProviderCable(obj.ViewObject)
|
||||
if base:
|
||||
obj.Base = base
|
||||
return obj
|
||||
|
||||
|
||||
class Cable(ArchComponent.Component):
|
||||
"A Base Frame Obcject - Class"
|
||||
"A Cable Obcject - Class"
|
||||
|
||||
def __init__(self, obj):
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
@@ -254,48 +254,10 @@ class Cable(ArchComponent.Component):
|
||||
return val.Placement.Base
|
||||
|
||||
def execute(self, obj):
|
||||
import Part, DraftGeomUtils
|
||||
import Draft
|
||||
|
||||
if obj.Base:
|
||||
w = obj.Base.Shape.SubShapes[1].SubShapes[0]
|
||||
w = DraftGeomUtils.filletWire(w, 150)
|
||||
else:
|
||||
return
|
||||
|
||||
"""if obj.Base:
|
||||
# Si tiene ruta, dibujar ruteado
|
||||
import PVPlantTrench as trench
|
||||
if isinstance(obj.Base, trench.Trench):
|
||||
w = obj.Base.Shape.SubShapes[0]
|
||||
else:
|
||||
w = obj.Base.Shape
|
||||
elif obj.From and obj.Name:
|
||||
'''line = Part.LineSegment()
|
||||
line.StartPoint = getPoint(obj.From)
|
||||
line.EndPoint = getPoint(obj.To)
|
||||
w = Part.Wire(line.toShape())'''
|
||||
w = Part.makePolygon([self.getPoint(obj.From), self.getPoint(obj.To)])
|
||||
else:
|
||||
return"""
|
||||
|
||||
r = obj.ExternalDiameter.Value / 2
|
||||
p = Part.Wire([Part.Circle(FreeCAD.Vector(0, 0, 0),
|
||||
FreeCAD.Vector(0, 1, 0),
|
||||
r).toShape()])
|
||||
c = obj.Offset
|
||||
c.x -= r
|
||||
c.z += r
|
||||
v1 = w.Vertexes[1].Point - w.Vertexes[0].Point
|
||||
v2 = DraftGeomUtils.getNormal(p)
|
||||
p.Placement.Base = w.Vertexes[0].Point + c
|
||||
p.Placement.Rotation = FreeCAD.Rotation(v2, v1)
|
||||
|
||||
obj.Shape = w.makePipeShell([p], True, False, 0)
|
||||
obj.Distance = w.Length
|
||||
''' No hacer nada. Es un componente sin shape'''
|
||||
|
||||
|
||||
class ViewProviderCable(ArchComponent.ViewProviderComponent):
|
||||
class ViewProviderCable(ArchComponent.ViewProviderComponent): #hace falta??
|
||||
def __init__(self, vobj):
|
||||
ArchComponent.ViewProviderComponent.__init__(self, vobj)
|
||||
|
||||
@@ -311,15 +273,7 @@ class CommandCable:
|
||||
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Calcular el BOQ de la")}
|
||||
|
||||
def Activated(self):
|
||||
import Draft
|
||||
sel = FreeCADGui.Selection.getSelection()
|
||||
wire = None
|
||||
for obj in sel:
|
||||
if Draft.getType(obj) == "Wire":
|
||||
wire = obj
|
||||
break
|
||||
|
||||
makeCable(wire)
|
||||
makeCable()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def IsActive(self):
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
# /**********************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2021 Javier Braña <javier.branagutierrez@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify*
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307*
|
||||
# * USA *
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import ArchComponent
|
||||
import Part
|
||||
import os
|
||||
import zipfile
|
||||
import re
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from DraftTools import translate
|
||||
else:
|
||||
# \cond
|
||||
def translate(ctxt,txt):
|
||||
return txt
|
||||
def QT_TRANSLATE_NOOP(ctxt,txt):
|
||||
return txt
|
||||
# \endcond
|
||||
|
||||
import os
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
__title__ = "PVPlant Areas"
|
||||
__author__ = "Javier Braña"
|
||||
__url__ = "http://www.sogos-solar.com"
|
||||
|
||||
import PVPlantResources
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
Dir3dObjects = os.path.join(PVPlantResources.DirResources, "3dObjects")
|
||||
|
||||
vector = ["Y", "YN", "Z", "ZN", "D"]
|
||||
|
||||
def makePCS():
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PowerConversionSystem")
|
||||
PowerConverter(obj)
|
||||
ViewProviderPowerConverter(obj.ViewObject)
|
||||
|
||||
try:
|
||||
folder = FreeCAD.ActiveDocument.PowerConversionSystemGroup
|
||||
except:
|
||||
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'PowerConversionSystemGroup')
|
||||
folder.Label = "PowerConversionSystemGroup"
|
||||
folder.addObject(obj)
|
||||
return obj
|
||||
|
||||
|
||||
class PowerConverter(ArchComponent.Component):
|
||||
def __init__(self, obj):
|
||||
''' Initialize the Area object '''
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
self.Type = None
|
||||
self.obj = None
|
||||
self.setProperties(obj)
|
||||
|
||||
def setProperties(self, obj):
|
||||
pl = obj.PropertiesList
|
||||
|
||||
# Transformer properties
|
||||
if not "Technology" in pl:
|
||||
obj.addProperty("App::PropertyEnumeration",
|
||||
"Technology",
|
||||
"Transformer",
|
||||
"Number of phases and type of transformer"
|
||||
).Technology = ["Single Phase Transformer", "Three Phase Transformer"]
|
||||
|
||||
if not "PowerPrimary" in pl:
|
||||
obj.addProperty("App::PropertyPower",
|
||||
"PowerPrimary",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").PowerPrimary = 6000000000
|
||||
|
||||
if not "PowerSecundary1" in pl:
|
||||
obj.addProperty("App::PropertyPower",
|
||||
"PowerSecundary1",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").PowerSecundary1 = 3000000000
|
||||
|
||||
if not "PowerSecundary2" in pl:
|
||||
obj.addProperty("App::PropertyPower",
|
||||
"PowerSecundary2",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").PowerSecundary2 = 3000000000
|
||||
|
||||
if not "VoltagePrimary" in pl:
|
||||
obj.addProperty("App::PropertyElectricPotential",
|
||||
"VoltagePrimary",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").VoltagePrimary = 33000000000
|
||||
|
||||
if not "VoltageSecundary1" in pl:
|
||||
obj.addProperty("App::PropertyElectricPotential",
|
||||
"VoltageSecundary1",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").VoltageSecundary1 = 11000000000
|
||||
|
||||
if not "VoltageSecundary2" in pl:
|
||||
obj.addProperty("App::PropertyElectricPotential",
|
||||
"VoltageSecundary2",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").VoltageSecundary2 = 11000000000
|
||||
|
||||
if not "VectorPrimary" in pl:
|
||||
obj.addProperty("App::PropertyEnumeration",
|
||||
"VectorPrimary",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").VectorPrimary = vector
|
||||
|
||||
if not "VectorSecundary1" in pl:
|
||||
obj.addProperty("App::PropertyEnumeration",
|
||||
"VectorSecundary1",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").VectorSecundary1 = vector
|
||||
|
||||
if not "VectorSecundary2" in pl:
|
||||
obj.addProperty("App::PropertyEnumeration",
|
||||
"VectorSecundary2",
|
||||
"Transformer",
|
||||
"The base file this component is built upon").VectorSecundary2 = vector
|
||||
|
||||
self.Type = "PowerConverter"
|
||||
obj.Proxy = self
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
""" Method run when the document is restored """
|
||||
self.setProperties(obj)
|
||||
|
||||
def onBeforeChange(self, obj, prop):
|
||||
''' '''
|
||||
# This method is called before a property is changed.
|
||||
# It can be used to validate the property value or to update other properties.
|
||||
# If the property is not valid, you can raise an exception.
|
||||
# If you want to prevent the change, you can return False.
|
||||
# Otherwise, return True to allow the change.
|
||||
return True
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
''' '''
|
||||
|
||||
|
||||
def execute(self, obj):
|
||||
''' '''
|
||||
# obj.Shape: compound
|
||||
# |- body: compound
|
||||
# |- transformer: solid
|
||||
# |- primary switchgear: compound
|
||||
# |- secundary 1 switchgear: compound
|
||||
# |- secundary 2 switchgear: compound
|
||||
|
||||
pl = obj.Placement
|
||||
obj.Shape = Part.makeBox(6058, 2438, 2591) # Placeholder for the shape
|
||||
obj.Placement = pl
|
||||
|
||||
|
||||
|
||||
class ViewProviderPowerConverter(ArchComponent.ViewProviderComponent):
|
||||
def __init__(self, vobj):
|
||||
ArchComponent.ViewProviderComponent.__init__(self, vobj)
|
||||
|
||||
def getIcon(self):
|
||||
return str(os.path.join(PVPlantResources.DirIcons, "Inverter.svg"))
|
||||
|
||||
class CommandPowerConverter:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "Inverter.svg")),
|
||||
'Accel': "E, P",
|
||||
'MenuText': "Power Converter",
|
||||
'ToolTip': "Power Converter",}
|
||||
|
||||
def Activated(self):
|
||||
sinverter = makePCS()
|
||||
|
||||
def IsActive(self):
|
||||
active = not (FreeCAD.ActiveDocument is None)
|
||||
return active
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('PowerConverter', CommandPowerConverter())
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
def groupTrackersToTransformers(transformer_power, max_distance):
|
||||
import numpy as np
|
||||
from scipy.spatial import KDTree
|
||||
import FreeCAD
|
||||
from collections import deque
|
||||
|
||||
# 1. Obtener todos los trackers válidos
|
||||
valid_trackers = []
|
||||
valid_points = []
|
||||
valid_power = []
|
||||
|
||||
for tracker in FreeCAD.ActiveDocument.Objects:
|
||||
if hasattr(tracker, 'Proxy') and (tracker.Proxy.Type == "Tracker"):
|
||||
base = tracker.Placement.Base
|
||||
if all(np.isfinite([base.x, base.y, base.z])):
|
||||
valid_trackers.append(tracker)
|
||||
valid_points.append([base.x, base.y])
|
||||
valid_power.append(tracker.Setup.TotalPower)
|
||||
|
||||
if not valid_trackers:
|
||||
FreeCAD.Console.PrintWarning("No se encontraron trackers válidos para agrupar\n")
|
||||
return
|
||||
|
||||
# 2. Obtener parámetros de los CTs
|
||||
target_power = transformer_power * 1.2
|
||||
points = np.array(valid_points)
|
||||
power_values = np.array(valid_power)
|
||||
|
||||
# 3. Determinar dirección de barrido (oeste a este por defecto)
|
||||
min_x, min_y = np.min(points, axis=0)
|
||||
max_x, max_y = np.max(points, axis=0)
|
||||
|
||||
# 4. Ordenar trackers de oeste a este (menor X a mayor X)
|
||||
'''
|
||||
Norte a Sur: sorted_indices = np.argsort(-points[:, 1]) (Y descendente)
|
||||
Sur a Norte: sorted_indices = np.argsort(points[:, 1]) (Y ascendente)
|
||||
Este a Oeste: sorted_indices = np.argsort(-points[:, 0]) (X descendente)
|
||||
'''
|
||||
sorted_indices = np.argsort(points[:, 0])
|
||||
sorted_points = points[sorted_indices]
|
||||
sorted_power = power_values[sorted_indices]
|
||||
sorted_trackers = [valid_trackers[i] for i in sorted_indices]
|
||||
|
||||
# 5. Crear KDTree para búsquedas rápidas
|
||||
kdtree = KDTree(sorted_points)
|
||||
|
||||
# 6. Algoritmo de barrido espacial
|
||||
transformer_groups = []
|
||||
used_indices = set()
|
||||
|
||||
# Función para expandir un grupo desde un punto inicial
|
||||
def expand_group(start_idx):
|
||||
group = []
|
||||
total_power = 0
|
||||
queue = deque([start_idx])
|
||||
|
||||
while queue and total_power < target_power:
|
||||
idx = queue.popleft()
|
||||
if idx in used_indices:
|
||||
continue
|
||||
|
||||
# Añadir tracker al grupo si no excede la potencia
|
||||
tracker_power = sorted_power[idx]
|
||||
if total_power + tracker_power > target_power * 1.05:
|
||||
continue
|
||||
|
||||
group.append(sorted_trackers[idx])
|
||||
total_power += tracker_power
|
||||
used_indices.add(idx)
|
||||
|
||||
# Buscar vecinos cercanos
|
||||
neighbors = kdtree.query_ball_point(
|
||||
sorted_points[idx],
|
||||
max_distance
|
||||
)
|
||||
|
||||
# Filtrar vecinos no usados y ordenar por proximidad al punto inicial
|
||||
neighbors = [n for n in neighbors if n not in used_indices]
|
||||
neighbors.sort(key=lambda n: abs(n - start_idx))
|
||||
queue.extend(neighbors)
|
||||
|
||||
return group, total_power
|
||||
|
||||
# 7. Barrido principal de oeste a este
|
||||
for i in range(len(sorted_points)):
|
||||
if i in used_indices:
|
||||
continue
|
||||
|
||||
group, total_power = expand_group(i)
|
||||
|
||||
if group:
|
||||
# Calcular centro del grupo
|
||||
group_points = np.array([t.Placement.Base[:2] for t in group])
|
||||
center = np.mean(group_points, axis=0)
|
||||
|
||||
transformer_groups.append({
|
||||
'trackers': group,
|
||||
'total_power': total_power,
|
||||
'center': center
|
||||
})
|
||||
|
||||
# 8. Manejar grupos residuales (si los hay)
|
||||
unused_indices = set(range(len(sorted_points))) - used_indices
|
||||
if unused_indices:
|
||||
# Intentar añadir trackers residuales a grupos existentes
|
||||
for idx in unused_indices:
|
||||
point = sorted_points[idx]
|
||||
tracker_power = sorted_power[idx]
|
||||
|
||||
# Buscar el grupo más cercano que pueda aceptar este tracker
|
||||
best_group = None
|
||||
min_distance = float('inf')
|
||||
|
||||
for group in transformer_groups:
|
||||
if group['total_power'] + tracker_power <= target_power * 1.05:
|
||||
dist = np.linalg.norm(point - group['center'])
|
||||
if dist < min_distance and dist < max_distance * 1.5:
|
||||
min_distance = dist
|
||||
best_group = group
|
||||
|
||||
# Añadir al grupo si se encontró uno adecuado
|
||||
if best_group:
|
||||
best_group['trackers'].append(sorted_trackers[idx])
|
||||
best_group['total_power'] += tracker_power
|
||||
# Actualizar centro del grupo
|
||||
group_points = np.array([t.Placement.Base[:2] for t in best_group['trackers']])
|
||||
best_group['center'] = np.mean(group_points, axis=0)
|
||||
else:
|
||||
# Crear un nuevo grupo con este tracker residual
|
||||
group = [sorted_trackers[idx]]
|
||||
center = point
|
||||
transformer_groups.append({
|
||||
'trackers': group,
|
||||
'total_power': tracker_power,
|
||||
'center': center
|
||||
})
|
||||
|
||||
# 9. Crear los grupos en FreeCAD
|
||||
transformer_group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "Transformers")
|
||||
transformer_group.Label = "Centros de Transformación"
|
||||
|
||||
for i, group in enumerate(transformer_groups):
|
||||
# Crear la esfera que representará el CT
|
||||
ct_shape = FreeCAD.ActiveDocument.addObject("Part::Box", f"CT_{i + 1}")
|
||||
ct_shape.Length = 6058
|
||||
ct_shape.Width = 2438
|
||||
ct_shape.Height = 2591
|
||||
ct_shape.Placement.Base = FreeCAD.Vector(group['center'][0], group['center'][1], 0)
|
||||
|
||||
# Añadir propiedades personalizadas
|
||||
ct_shape.addProperty("App::PropertyLinkList", "Trackers", "CT",
|
||||
"Lista de trackers asociados a este CT")
|
||||
ct_shape.addProperty("App::PropertyFloat", "TotalPower", "CT",
|
||||
"Potencia total del grupo (W)")
|
||||
ct_shape.addProperty("App::PropertyFloat", "NominalPower", "CT",
|
||||
"Potencia nominal del transformador (W)")
|
||||
ct_shape.addProperty("App::PropertyFloat", "Utilization", "CT",
|
||||
"Porcentaje de utilización (Total/Nominal)")
|
||||
|
||||
# Establecer valores de las propiedades
|
||||
ct_shape.Trackers = group['trackers']
|
||||
ct_shape.TotalPower = group['total_power'].Value
|
||||
ct_shape.NominalPower = transformer_power
|
||||
ct_shape.Utilization = (group['total_power'].Value / transformer_power) * 100
|
||||
|
||||
# Configurar visualización
|
||||
# Calcular color basado en utilización (verde < 100%, amarillo < 110%, rojo > 110%)
|
||||
utilization = ct_shape.Utilization
|
||||
if utilization <= 100:
|
||||
color = (0.0, 1.0, 0.0) # Verde
|
||||
elif utilization <= 110:
|
||||
color = (1.0, 1.0, 0.0) # Amarillo
|
||||
else:
|
||||
color = (1.0, 0.0, 0.0) # Rojo
|
||||
|
||||
ct_shape.ViewObject.ShapeColor = color
|
||||
ct_shape.ViewObject.Transparency = 40 # 40% de transparencia
|
||||
|
||||
# Añadir etiqueta con información
|
||||
ct_shape.ViewObject.DisplayMode = "Shaded"
|
||||
ct_shape.Label = f"CT {i + 1} ({ct_shape.TotalPower / 1000:.1f}kW/{ct_shape.NominalPower / 1000:.1f}kW)"
|
||||
|
||||
# Añadir al grupo principal
|
||||
transformer_group.addObject(ct_shape)
|
||||
|
||||
FreeCAD.Console.PrintMessage(f"Se crearon {len(transformer_groups)} centros de transformación\n")
|
||||
|
||||
#onSelectGatePoint()
|
||||
|
||||
|
||||
import FreeCAD, FreeCADGui, Part
|
||||
import numpy as np
|
||||
from scipy.stats import linregress
|
||||
from PySide import QtGui
|
||||
|
||||
class InternalPathCreator:
|
||||
def __init__(self, gate_point, strategy=1, path_width=4000):
|
||||
self.gate_point = gate_point
|
||||
self.strategy = strategy
|
||||
self.path_width = path_width
|
||||
self.ct_shapes = []
|
||||
self.ct_positions = []
|
||||
|
||||
def get_transformers(self):
|
||||
transformers_group = FreeCAD.ActiveDocument.getObject("Transformers")
|
||||
if not transformers_group:
|
||||
FreeCAD.Console.PrintError("No se encontró el grupo 'Transformers'\n")
|
||||
return False
|
||||
|
||||
self.ct_shapes = transformers_group.Group
|
||||
if not self.ct_shapes:
|
||||
FreeCAD.Console.PrintWarning("No hay Centros de Transformación en el grupo\n")
|
||||
return False
|
||||
|
||||
# Obtener las posiciones de los CTs
|
||||
for sphere in self.ct_shapes:
|
||||
base = sphere.Placement.Base
|
||||
self.ct_positions.append(FreeCAD.Vector(base.x, base.y, 0))
|
||||
return True
|
||||
|
||||
def create_paths(self):
|
||||
if not self.get_transformers():
|
||||
return []
|
||||
|
||||
if self.strategy == 1:
|
||||
return self.create_direct_paths()
|
||||
elif self.strategy == 2:
|
||||
return self.create_unified_path()
|
||||
else:
|
||||
FreeCAD.Console.PrintError("Estrategia no válida. Use 1 o 2.\n")
|
||||
return []
|
||||
|
||||
def create_direct_paths(self):
|
||||
"""Estrategia 1: Caminos independientes desde cada CT hasta la puerta"""
|
||||
paths = []
|
||||
for ct in self.ct_positions:
|
||||
paths.append([ct, self.gate_point])
|
||||
return paths
|
||||
|
||||
def create_unified_path(self):
|
||||
"""Estrategia 2: Único camino que une todos los CTs y la puerta usando regresión lineal"""
|
||||
if not self.ct_positions:
|
||||
return []
|
||||
|
||||
all_points = self.ct_positions + [self.gate_point]
|
||||
x = [p.x for p in all_points]
|
||||
y = [p.y for p in all_points]
|
||||
|
||||
# Manejar caso de puntos alineados verticalmente
|
||||
if np.std(x) < 1e-6:
|
||||
sorted_points = sorted(all_points, key=lambda p: p.y)
|
||||
paths = []
|
||||
for i in range(len(sorted_points) - 1):
|
||||
paths.append([sorted_points[i], sorted_points[i + 1]])
|
||||
return paths
|
||||
|
||||
# Calcular regresión lineal
|
||||
slope, intercept, _, _, _ = linregress(x, y)
|
||||
|
||||
# Función para proyectar puntos
|
||||
def project_point(point):
|
||||
x0, y0 = point.x, point.y
|
||||
if abs(slope) > 1e6:
|
||||
return FreeCAD.Vector(x0, intercept, 0)
|
||||
x_proj = (x0 + slope * (y0 - intercept)) / (1 + slope ** 2)
|
||||
y_proj = slope * x_proj + intercept
|
||||
return FreeCAD.Vector(x_proj, y_proj, 0)
|
||||
|
||||
# return slope * x + intercept --> desde placement
|
||||
|
||||
projected_points = [project_point(p) for p in all_points]
|
||||
|
||||
# Calcular distancias a lo largo de la línea
|
||||
ref_point = projected_points[0]
|
||||
direction_vector = FreeCAD.Vector(1, slope).normalize()
|
||||
distances = []
|
||||
for p in projected_points:
|
||||
vec_to_point = p - ref_point
|
||||
distance = vec_to_point.dot(direction_vector)
|
||||
distances.append(distance)
|
||||
|
||||
# Ordenar por distancia
|
||||
sorted_indices = np.argsort(distances)
|
||||
sorted_points = [all_points[i] for i in sorted_indices]
|
||||
|
||||
# Crear caminos
|
||||
paths = []
|
||||
for i in range(len(sorted_points) - 1):
|
||||
paths.append([sorted_points[i], sorted_points[i + 1]])
|
||||
return paths
|
||||
|
||||
def create_3d_path(self, path_poly):
|
||||
"""Crea geometría 3D para el camino adaptada a orientación norte-sur"""
|
||||
segments = []
|
||||
for i in range(len(path_poly.Vertexes) - 1):
|
||||
start = path_poly.Vertexes[i].Point
|
||||
end = path_poly.Vertexes[i + 1].Point
|
||||
direction = end - start
|
||||
|
||||
# Determinar orientación predominante
|
||||
if abs(direction.x) > abs(direction.y):
|
||||
normal = FreeCAD.Vector(0, 1, 0) # Norte-sur
|
||||
else:
|
||||
normal = FreeCAD.Vector(1, 0, 0) # Este-oeste
|
||||
|
||||
offset = normal * self.path_width / 2
|
||||
|
||||
# Crear puntos para la sección transversal
|
||||
p1 = start + offset
|
||||
p2 = start - offset
|
||||
p3 = end - offset
|
||||
p4 = end + offset
|
||||
|
||||
# Crear cara
|
||||
wire = Part.makePolygon([p1, p2, p3, p4, p1])
|
||||
face = Part.Face(wire)
|
||||
segments.append(face)
|
||||
|
||||
if segments:
|
||||
'''road_shape = segments[0].fuse(segments[1:])
|
||||
return road_shape.removeSplitter()'''
|
||||
return Part.makeCompound(segments)
|
||||
return Part.Shape()
|
||||
|
||||
def build(self):
|
||||
paths = self.create_paths()
|
||||
if not paths:
|
||||
return
|
||||
|
||||
path_group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "InternalPaths")
|
||||
path_group.Label = f"Caminos Internos (Estrategia {self.strategy})"
|
||||
|
||||
for i, path in enumerate(paths):
|
||||
poly = Part.makePolygon(path)
|
||||
|
||||
# Objeto para la línea central
|
||||
path_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", f"Path_{i + 1}")
|
||||
path_obj.Shape = poly
|
||||
path_obj.ViewObject.LineWidth = 3.0
|
||||
path_obj.ViewObject.LineColor = (0.0, 0.0, 1.0)
|
||||
|
||||
# Objeto para la superficie 3D
|
||||
road_shape = self.create_3d_path(poly)
|
||||
road_obj = FreeCAD.ActiveDocument.addObject("Part::Feature", f"Road_{i + 1}")
|
||||
road_obj.Shape = road_shape
|
||||
road_obj.ViewObject.ShapeColor = (0.7, 0.7, 0.7)
|
||||
|
||||
path_group.addObject(path_obj)
|
||||
path_group.addObject(road_obj)
|
||||
|
||||
FreeCAD.Console.PrintMessage(f"Se crearon {len(paths)} segmentos de caminos internos\n")
|
||||
|
||||
|
||||
# Función para mostrar el diálogo de estrategia
|
||||
def show_path_strategy_dialog(gate_point):
|
||||
dialog = QtGui.QDialog()
|
||||
dialog.setWindowTitle("Seleccionar Estrategia de Caminos")
|
||||
layout = QtGui.QVBoxLayout(dialog)
|
||||
|
||||
label = QtGui.QLabel("Seleccione la estrategia para crear los caminos internos:")
|
||||
layout.addWidget(label)
|
||||
|
||||
rb1 = QtGui.QRadioButton("Estrategia 1: Caminos independientes desde cada CT hasta la puerta")
|
||||
rb1.setChecked(True)
|
||||
layout.addWidget(rb1)
|
||||
|
||||
rb2 = QtGui.QRadioButton("Estrategia 2: Único camino que une todos los CTs y la puerta")
|
||||
layout.addWidget(rb2)
|
||||
|
||||
btn_box = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
|
||||
layout.addWidget(btn_box)
|
||||
|
||||
def on_accept():
|
||||
strategy = 1 if rb1.isChecked() else 2
|
||||
dialog.accept()
|
||||
creator = InternalPathCreator(gate_point, strategy)
|
||||
creator.build()
|
||||
|
||||
btn_box.accepted.connect(on_accept)
|
||||
btn_box.rejected.connect(dialog.reject)
|
||||
|
||||
dialog.exec_()
|
||||
|
||||
|
||||
# Uso: seleccionar un punto para la puerta de entrada
|
||||
def onSelectGatePoint():
|
||||
'''gate = FreeCAD.ActiveDocument.findObjects(Name="FenceGate")[0]
|
||||
gate_point = gate.Placement.Base
|
||||
show_path_strategy_dialog(gate_point)'''
|
||||
|
||||
sel = FreeCADGui.Selection.getSelectionEx()[0]
|
||||
show_path_strategy_dialog(sel.SubObjects[0].CenterOfMass)
|
||||
@@ -0,0 +1,78 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2017 - Amritpal Singh <amrit3701@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
__title__ = "RebarCommands"
|
||||
__author__ = "Amritpal Singh"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import FreeCADGui, FreeCAD
|
||||
from PySide import QtGui, QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
import os
|
||||
|
||||
|
||||
class CommandExportToPVSyst:
|
||||
"Export to PVSyst"
|
||||
@staticmethod
|
||||
def GetResources():
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "PVsyst.png")),
|
||||
'Accel': "E, P",
|
||||
'MenuText': QT_TRANSLATE_NOOP("Outputs", "Export to PVSyst"),
|
||||
'ToolTip': QT_TRANSLATE_NOOP("Outputs", "Exportar a PVSyst")}
|
||||
|
||||
@staticmethod
|
||||
def Activated():
|
||||
from Export import exportPVSyst
|
||||
taskd = exportPVSyst.PVSystTaskPanel()
|
||||
# taskd.show()
|
||||
FreeCADGui.Control.showDialog(taskd)
|
||||
|
||||
@staticmethod
|
||||
def IsActive():
|
||||
if FreeCAD.ActiveDocument:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
from Export import PVPlantBOQMechanical, PVPlantBOQElectrical, PVPlantBOQCivil
|
||||
from Export import exportDXF, exportKMZ
|
||||
FreeCADGui.addCommand('BOQMechanical', PVPlantBOQMechanical.CommandBOQMechanical())
|
||||
FreeCADGui.addCommand('BOQElectrical', PVPlantBOQElectrical.CommandBOQElectrical())
|
||||
FreeCADGui.addCommand('BOQCivil', PVPlantBOQCivil.CommandBOQCivil())
|
||||
FreeCADGui.addCommand('exportDXF', exportDXF.CommandExportDXF())
|
||||
FreeCADGui.addCommand('exportToPVSyst', CommandExportToPVSyst())
|
||||
FreeCADGui.addCommand('exportKMZ', exportKMZ.CommandExportKMZ())
|
||||
|
||||
Exportlist = ["BOQCivil",
|
||||
"BOQMechanical",
|
||||
"BOQElectrical",
|
||||
"Separator",
|
||||
"exportDXF",
|
||||
# "importDXF",
|
||||
"exportToPVSyst",
|
||||
"exportKMZ",
|
||||
]
|
||||
@@ -59,7 +59,7 @@ def makeBOQCivil():
|
||||
|
||||
|
||||
|
||||
class _CommandBOQCivil:
|
||||
class CommandBOQCivil:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "boqc.svg")),
|
||||
@@ -76,5 +76,5 @@ class _CommandBOQCivil:
|
||||
else:
|
||||
return False
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('BOQCivil', _CommandBOQCivil())
|
||||
'''if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('BOQCivil', _CommandBOQCivil())'''
|
||||
|
||||
@@ -83,7 +83,8 @@ def makeBOQElectrical():
|
||||
b = mgb.get_boundary(m)
|
||||
Part.show(b)
|
||||
|
||||
class _CommandBOQElectrical:
|
||||
|
||||
class CommandBOQElectrical:
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "boqe.svg")),
|
||||
'Accel': "R, E",
|
||||
@@ -100,5 +101,5 @@ class _CommandBOQElectrical:
|
||||
return False
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('BOQElectrical', _CommandBOQElectrical())
|
||||
'''if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('BOQElectrical', _CommandBOQElectrical())'''
|
||||
|
||||
+182
-166
@@ -20,32 +20,43 @@
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
from typing import Dict, List
|
||||
|
||||
import FreeCAD
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui, os
|
||||
from PySide import QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
else:
|
||||
# \cond
|
||||
def translate(ctxt, txt):
|
||||
return txt
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||
return txt
|
||||
# \endcond
|
||||
|
||||
try:
|
||||
_fromUtf8 = QtCore.QString.fromUtf8
|
||||
except AttributeError:
|
||||
def _fromUtf8(s):
|
||||
return s
|
||||
|
||||
import openpyxl
|
||||
from openpyxl.styles import Alignment, Border, Side, PatternFill, Font
|
||||
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
|
||||
from openpyxl.workbook import Workbook
|
||||
from openpyxl.worksheet.worksheet import Worksheet
|
||||
|
||||
import PVPlantResources
|
||||
import PVPlantSite
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
|
||||
__title__ = "PVPlant Mechanical BOQ"
|
||||
__author__ = "Javier Braña"
|
||||
__url__ = "http://www.sogos-solar.com"
|
||||
|
||||
# Constants
|
||||
SCALE = 0.001 # Millimeters to meters
|
||||
THIN_BORDER = Border(
|
||||
top=Side(border_style="thin", color="7DA4B8"),
|
||||
left=Side(border_style="thin", color="7DA4B8"),
|
||||
right=Side(border_style="thin", color="7DA4B8"),
|
||||
bottom=Side(border_style="thin", color="7DA4B8")
|
||||
)
|
||||
HEADER_FILL = PatternFill("solid", fgColor="7DA4B8")
|
||||
HEADER_FONT = Font(name='Arial', size=10, bold=True, color="FFFFFF")
|
||||
DATA_FONT = Font(name='Arial', size=10)
|
||||
CENTER_ALIGN = Alignment(horizontal="center", vertical="center")
|
||||
|
||||
|
||||
|
||||
# Estilos:
|
||||
thin = Side(border_style="thin", color="7DA4B8")
|
||||
double = Side(border_style="double", color="ff0000")
|
||||
@@ -56,6 +67,16 @@ border_fat = Border(top=thin, left=thin, right=thin, bottom=thin)
|
||||
|
||||
scale = 0.001 # milimeters to meter
|
||||
|
||||
|
||||
def open_file(path: str) -> None:
|
||||
"""Open a file or directory using the system's default handler"""
|
||||
if platform.system() == "Windows":
|
||||
os.startfile(path)
|
||||
elif platform.system() == "Darwin":
|
||||
subprocess.Popen(["open", path])
|
||||
else:
|
||||
subprocess.Popen(["xdg-open", path])
|
||||
|
||||
def style_range(ws, cell_range, border=Border(), fill=None, font=None, alignment=None):
|
||||
"""
|
||||
Apply styles to a range of cells as if they were a single cell.
|
||||
@@ -94,124 +115,118 @@ def style_range(ws, cell_range, border=Border(), fill=None, font=None, alignment
|
||||
for c in row:
|
||||
c.fill = fill
|
||||
|
||||
def spreadsheetBOQFrames(sheet, sel):
|
||||
sheet['A1'] = 'Index'
|
||||
sheet['B1'] = 'Frame'
|
||||
sheet['C1'] = 'Frame Type'
|
||||
sheet['D1'] = 'X'
|
||||
sheet['E1'] = 'Y'
|
||||
sheet['F1'] = 'Z'
|
||||
sheet['G1'] = 'Angle N-S'
|
||||
sheet['H1'] = 'Angle L-W'
|
||||
sheet['I1'] = 'Nº Poles'
|
||||
|
||||
sheet.column_dimensions['A'].width = 8
|
||||
sheet.column_dimensions['B'].width = 30
|
||||
sheet.column_dimensions['C'].width = 20
|
||||
sheet.column_dimensions['D'].width = 20
|
||||
sheet.column_dimensions['E'].width = 20
|
||||
sheet.column_dimensions['F'].width = 20
|
||||
sheet.column_dimensions['G'].width = 15
|
||||
sheet.column_dimensions['H'].width = 15
|
||||
sheet.column_dimensions['I'].width = 15
|
||||
sheet.row_dimensions[1].height = 40
|
||||
def create_sheet_headers(sheet: Worksheet, headers: List[str], widths: Dict[str, float]) -> None:
|
||||
"""Create standardized sheet headers."""
|
||||
for col, header in enumerate(headers, start=1):
|
||||
cell = sheet.cell(row=1, column=col, value=header)
|
||||
cell.fill = HEADER_FILL
|
||||
cell.font = HEADER_FONT
|
||||
cell.alignment = CENTER_ALIGN
|
||||
|
||||
style_range(sheet, 'A1:I1',
|
||||
border=Border(top=thin, left=thin, right=thin, bottom=thin),
|
||||
fill=PatternFill("solid", fgColor="7DA4B8"),
|
||||
font=Font(name='Quicksand', size=10, b=True, color="FFFFFF"),
|
||||
alignment=Alignment(horizontal="center", vertical="center"))
|
||||
for col, width in widths.items():
|
||||
sheet.column_dimensions[col].width = width
|
||||
|
||||
for ind in range(0, len(sel)):
|
||||
row = ind + 2
|
||||
sheet['A{0}'.format(row)] = ind + 1
|
||||
sheet['B{0}'.format(row)] = sel[ind].Label
|
||||
sheet['C{0}'.format(row)] = sel[ind].Setup.Label
|
||||
sheet['D{0}'.format(row)] = sel[ind].Placement.Base.x * scale
|
||||
sheet['E{0}'.format(row)] = sel[ind].Placement.Base.y * scale
|
||||
sheet['R{0}'.format(row)] = sel[ind].Placement.Base.z * scale
|
||||
sheet['G{0}'.format(row)] = sel[ind].Placement.Rotation.toEuler()[0]
|
||||
sheet['H{0}'.format(row)] = sel[ind].Placement.Rotation.toEuler()[1]
|
||||
sheet['I{0}'.format(row)] = sel[ind].Setup.NumberPole.Value
|
||||
style_range(sheet, 'A' + str(row) + ':I' + str(row),
|
||||
border=Border(top=thin, left=thin, right=thin, bottom=thin),
|
||||
font=Font(name='Quicksand', size=10),
|
||||
alignment=Alignment(horizontal="center", vertical="center"))
|
||||
sheet.row_dimensions[1].height = 30
|
||||
style_range(sheet, f'A1:{chr(64 + len(headers))}1',
|
||||
border=THIN_BORDER,
|
||||
fill=HEADER_FILL,
|
||||
font=HEADER_FONT,
|
||||
alignment=CENTER_ALIGN)
|
||||
|
||||
def spreadsheetBOQPoles(sheet, sel):
|
||||
import MeshPart as mp
|
||||
from Mechanical.Frame import PVPlantFrame
|
||||
|
||||
# Data:
|
||||
terrain = PVPlantSite.get().Terrain.Mesh # Shape
|
||||
def spreadsheetBOQFrames(sheet: Worksheet, selection: List[FreeCAD.DocumentObject]) -> None:
|
||||
"""Generate frames information sheet."""
|
||||
headers = [
|
||||
'Index', 'Frame', 'Frame Type', 'X (m)', 'Y (m)', 'Z (m)',
|
||||
'Angle N-S', 'Angle L-W', 'Nº Poles'
|
||||
]
|
||||
widths = {'A': 8, 'B': 30, 'C': 20, 'D': 15, 'E': 15,
|
||||
'F': 15, 'G': 15, 'H': 15, 'I': 10}
|
||||
|
||||
# Headers:
|
||||
sheet['A1'] = 'Frame'
|
||||
sheet['B1'] = 'Pole'
|
||||
sheet['C1'] = 'Pole Type'
|
||||
sheet['D1'] = 'X'
|
||||
sheet['E1'] = 'Y'
|
||||
sheet['F1'] = 'Z frame attach'
|
||||
sheet['G1'] = 'Z aerial head'
|
||||
sheet['H1'] = 'Pole length'
|
||||
sheet['I1'] = 'Pole aerial length'
|
||||
sheet['J1'] = 'Pole terrain enter length'
|
||||
create_sheet_headers(sheet, headers, widths)
|
||||
|
||||
sheet.column_dimensions['A'].width = 30
|
||||
sheet.column_dimensions['B'].width = 8
|
||||
sheet.column_dimensions['C'].width = 20
|
||||
sheet.column_dimensions['D'].width = 20
|
||||
sheet.column_dimensions['E'].width = 20
|
||||
sheet.column_dimensions['F'].width = 20
|
||||
sheet.column_dimensions['G'].width = 20
|
||||
sheet.column_dimensions['H'].width = 20
|
||||
sheet.column_dimensions['I'].width = 20
|
||||
sheet.column_dimensions['J'].width = 20
|
||||
sheet.row_dimensions[1].height = 40
|
||||
style_range(sheet, 'A1:J1',
|
||||
border=Border(top=thin, left=thin, right=thin, bottom=thin),
|
||||
fill=PatternFill("solid", fgColor="7DA4B8"),
|
||||
font=Font(name='Quicksand', size=11, b=True, color="FFFFFF"),
|
||||
alignment=Alignment(horizontal="center", vertical="center"))
|
||||
sheet['A2'] = ""
|
||||
for idx, obj in enumerate(selection, start=2):
|
||||
placement = obj.Placement
|
||||
sheet[f'A{idx}'] = idx - 1
|
||||
sheet[f'B{idx}'] = obj.Label
|
||||
sheet[f'C{idx}'] = obj.Setup.Label
|
||||
sheet[f'D{idx}'] = placement.Base.x * SCALE
|
||||
sheet[f'E{idx}'] = placement.Base.y * SCALE
|
||||
sheet[f'F{idx}'] = placement.Base.z * SCALE
|
||||
sheet[f'G{idx}'] = placement.Rotation.toEuler()[0]
|
||||
sheet[f'H{idx}'] = placement.Rotation.toEuler()[1]
|
||||
sheet[f'I{idx}'] = obj.Setup.NumberPole.Value
|
||||
|
||||
style_range(sheet, f'A{idx}:I{idx}',
|
||||
border=THIN_BORDER,
|
||||
font=DATA_FONT,
|
||||
alignment=CENTER_ALIGN)
|
||||
|
||||
def spreadsheetBOQPoles(sheet: Worksheet, selection: List[FreeCAD.DocumentObject]) -> None:
|
||||
"""Generate poles information sheet."""
|
||||
headers = [
|
||||
'Frame', 'Pole', 'Pole Type', 'X (m)', 'Y (m)', 'Z Frame Attach (m)',
|
||||
'Z Aerial Head (m)', 'Pole Length (m)', 'Aerial Length (m)',
|
||||
'Terrain Enter Length (m)'
|
||||
]
|
||||
widths = {chr(65 + i): 20 for i in range(10)}
|
||||
widths['A'] = 30
|
||||
widths['B'] = 10
|
||||
|
||||
create_sheet_headers(sheet, headers, widths)
|
||||
sheet.row_dimensions[2].height = 5
|
||||
|
||||
data = {"Frame": [],
|
||||
#"FrameType": [],
|
||||
"Pole": [],
|
||||
"PoleType": [],
|
||||
"PoleLength": [],
|
||||
"Center": [],
|
||||
"Head": []}
|
||||
|
||||
cnt = 0
|
||||
for frame_ind, frame in enumerate(sel):
|
||||
poles = frame.Shape.SubShapes[1].SubShapes[0].SubShapes
|
||||
numpoles = int(frame.Setup.NumberPole.Value)
|
||||
seq = frame.Setup.PoleSequence
|
||||
if len(seq) < numpoles:
|
||||
seq = PVPlantFrame.getarray(frame.Setup.PoleSequence, numpoles)
|
||||
for pole_ind in range(numpoles):
|
||||
pole = poles[pole_ind]
|
||||
poletype = frame.Setup.PoleType[seq[pole_ind]]
|
||||
data["Frame"].append(frame.Label)
|
||||
#data["FrameType"].append(frame.Setup.Label)
|
||||
data["Pole"].append(pole_ind + 1)
|
||||
data["PoleType"].append(poletype.Label)
|
||||
data["PoleLength"].append(int(poletype.Height))
|
||||
data["Center"].append(pole.BoundBox.Center)
|
||||
data["Head"].append(pole.BoundBox.ZMax)
|
||||
cnt += 1
|
||||
import MeshPart as mp
|
||||
from Mechanical.Frame import PVPlantFrame
|
||||
# Data:
|
||||
terrain = PVPlantSite.get().Terrain.Mesh # Shape
|
||||
poles_data = []
|
||||
|
||||
pts = mp.projectPointsOnMesh(data["Center"], terrain, FreeCAD.Vector(0, 0, 1))
|
||||
#if cnt == len(pts):
|
||||
data["Soil"] = pts
|
||||
for frame in selection:
|
||||
try:
|
||||
poles = frame.Shape.SubShapes[1].SubShapes[0].SubShapes
|
||||
num_poles = int(frame.Setup.NumberPole.Value)
|
||||
sequence = frame.Setup.PoleSequence
|
||||
|
||||
if len(sequence) < num_poles:
|
||||
sequence = PVPlantFrame.getarray(frame.Setup.PoleSequence, num_poles)
|
||||
|
||||
for pole_idx in range(num_poles):
|
||||
pole = poles[pole_idx]
|
||||
pole_type = frame.Setup.PoleType[sequence[pole_idx]]
|
||||
center = pole.BoundBox.Center
|
||||
|
||||
poles_data.append({
|
||||
'frame': frame.Label,
|
||||
'number': pole_idx + 1,
|
||||
'type': pole_type.Label,
|
||||
'center': center,
|
||||
'head_z': pole.BoundBox.ZMax,
|
||||
'length': pole_type.Height.Value
|
||||
})
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(f"Error processing frame {frame.Label}: {str(e)}\n")
|
||||
|
||||
# Project points on terrain
|
||||
try:
|
||||
import MeshPart
|
||||
points = [data['center'] for data in poles_data]
|
||||
terrain_z = MeshPart.projectPointsOnMesh(points, terrain, FreeCAD.Vector(0, 0, 1))
|
||||
for data, z in zip(poles_data, terrain_z):
|
||||
data['terrain_z'] = z.z
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(f"Terrain projection failed: {str(e)}\n")
|
||||
for data in poles_data:
|
||||
data['terrain_z'] = 0
|
||||
|
||||
# Write data to sheet
|
||||
row = 3
|
||||
group_from = row
|
||||
f = data["Frame"][0]
|
||||
for i in range(0, len(data["Frame"])):
|
||||
if f != data["Frame"][i]:
|
||||
f = poles_data[0]['frame']
|
||||
for i, data in enumerate(poles_data):
|
||||
if f != data["frame"]:
|
||||
style_range(sheet, 'A' + str(group_from) + ':F' + str(row - 1),
|
||||
border=Border(top=thin, left=thin, right=thin, bottom=thin),
|
||||
font=Font(name='Quicksand', size=11, ),
|
||||
@@ -221,27 +236,28 @@ def spreadsheetBOQPoles(sheet, sel):
|
||||
border=Border(top=thin, left=thin, right=thin, bottom=thin),
|
||||
font=Font(name='Quicksand', size=11, ),
|
||||
alignment=Alignment(horizontal="center", vertical="center"))
|
||||
#sheet['A{0}'.format(row)] = ""
|
||||
# sheet['A{0}'.format(row)] = ""
|
||||
sheet.row_dimensions[row].height = 5
|
||||
row += 1
|
||||
f = data["Frame"][i]
|
||||
f = data["frame"]
|
||||
group_from = row
|
||||
|
||||
sheet['A{0}'.format(row)] = data['Frame'][i]
|
||||
sheet['B{0}'.format(row)] = data['Pole'][i]
|
||||
sheet['C{0}'.format(row)] = data['PoleType'][i]
|
||||
sheet['D{0}'.format(row)] = round(data['Center'][i].x, 0) * scale
|
||||
sheet['A{0}'.format(row)] = data['frame']
|
||||
sheet['B{0}'.format(row)] = data['number']
|
||||
sheet['C{0}'.format(row)] = data['type']
|
||||
sheet['D{0}'.format(row)] = round(data['center'].x, 0) * scale
|
||||
sheet['D{0}'.format(row)].number_format = "0.000"
|
||||
sheet['E{0}'.format(row)] = round(data['Center'][i].y, 0) * scale
|
||||
sheet['E{0}'.format(row)] = round(data['center'].y, 0) * scale
|
||||
sheet['E{0}'.format(row)].number_format = "0.000"
|
||||
try:
|
||||
sheet['F{0}'.format(row)] = round(data['Soil'][i].z, 0) * scale
|
||||
sheet['F{0}'.format(row)] = round(data['terrain_z'].z, 0) * scale
|
||||
sheet['F{0}'.format(row)].number_format = "0.000"
|
||||
except:
|
||||
pass
|
||||
sheet['G{0}'.format(row)] = round(data['Head'][i]) * scale
|
||||
|
||||
sheet['G{0}'.format(row)] = round(data['head_z']) * scale
|
||||
sheet['G{0}'.format(row)].number_format = "0.000"
|
||||
sheet['H{0}'.format(row)] = data["PoleLength"][i] * scale
|
||||
sheet['H{0}'.format(row)] = data["length"] * scale
|
||||
sheet['H{0}'.format(row)].number_format = "0.000"
|
||||
sheet['I{0}'.format(row)] = '=G{0}-F{0}'.format(row)
|
||||
sheet['I{0}'.format(row)].number_format = "0.000"
|
||||
@@ -250,7 +266,7 @@ def spreadsheetBOQPoles(sheet, sel):
|
||||
|
||||
style_range(sheet, 'A' + str(row) + ':J' + str(row),
|
||||
border=Border(top=thin, left=thin, right=thin, bottom=thin),
|
||||
font=Font(name='Quicksand', size=11,),
|
||||
font=Font(name='Quicksand', size=11, ),
|
||||
alignment=Alignment(horizontal="center", vertical="center"))
|
||||
row += 1
|
||||
|
||||
@@ -294,7 +310,7 @@ def spreadsheetBOQPanelCollision(sheet, sel):
|
||||
sheet['G{0}'.format(ind + 2)] = sel[ind].Placement.Rotation.toEuler()[1]
|
||||
sheet['H{0}'.format(ind + 2)] = sel[ind].NumberPole.Value
|
||||
|
||||
class _CommandBOQMechanical:
|
||||
class CommandBOQMechanical:
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "boqm.svg")),
|
||||
'Accel': "R, M",
|
||||
@@ -302,37 +318,39 @@ class _CommandBOQMechanical:
|
||||
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Calcular el BOQ de la")}
|
||||
|
||||
def Activated(self):
|
||||
# make file global:
|
||||
#sel = FreeCAD.ActiveDocument.findObjects(Name="Tracker")
|
||||
sel = []
|
||||
for obj in FreeCAD.ActiveDocument.Objects:
|
||||
'''if not hasattr(obj, "Proxy"):
|
||||
continue
|
||||
if issubclass(obj.Proxy.__class__, PVPlantRack.Frame):
|
||||
objects.append(obj)'''
|
||||
if obj.Name.startswith("Tracker") and not obj.Name.startswith("TrackerSetup"):
|
||||
sel.append(obj)
|
||||
sel = sorted(sel, key=lambda x: x.Label)
|
||||
if len(sel) > 0:
|
||||
path = os.path.dirname(FreeCAD.ActiveDocument.FileName)
|
||||
filename = os.path.join(path, "BOQMechanical.xlsx")
|
||||
mywb = openpyxl.Workbook()
|
||||
sheet = mywb.active
|
||||
sheet.title = 'Frames information'
|
||||
spreadsheetBOQFrames(sheet, sel)
|
||||
doc = FreeCAD.ActiveDocument
|
||||
|
||||
sheet = mywb.create_sheet("Poles information")
|
||||
spreadsheetBOQPoles(sheet, sel)
|
||||
mywb.save(filename)
|
||||
if not doc or not doc.FileName:
|
||||
FreeCAD.Console.PrintError("Document must be saved first\n")
|
||||
return
|
||||
|
||||
print("Se ha generado el BOQMechanical: ")
|
||||
print(filename)
|
||||
'''import sys
|
||||
path = r'C:\Program Files (x86)\IronPython 2.7\Lib'
|
||||
sys.path.append(path)'''
|
||||
try:
|
||||
sel = [obj for obj in FreeCAD.ActiveDocument.Objects if (hasattr(obj, "Proxy") and (obj.Proxy.Type == "Tracker"))]
|
||||
|
||||
import subprocess
|
||||
subprocess.Popen('explorer ' + path)
|
||||
if not sel:
|
||||
FreeCAD.Console.PrintWarning("No valid trackers found\n")
|
||||
return
|
||||
|
||||
path = os.path.dirname(doc.FileName)
|
||||
filename = os.path.join(path, "BOQ_Mechanical.xlsx")
|
||||
|
||||
sel = sorted(sel, key=lambda x: x.Label)
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
wb.remove(wb.active) # Remove default sheet
|
||||
|
||||
frames_sheet = wb.create_sheet("Frames Information")
|
||||
spreadsheetBOQFrames(frames_sheet, sel)
|
||||
|
||||
poles_sheet = wb.create_sheet("Poles information")
|
||||
spreadsheetBOQPoles(poles_sheet , sel)
|
||||
|
||||
wb.save(filename)
|
||||
FreeCAD.Console.PrintMessage(f"Report generated: {filename}\n")
|
||||
open_file(path)
|
||||
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(f"Error generating BOQ: {str(e)}\n")
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument:
|
||||
@@ -340,5 +358,3 @@ class _CommandBOQMechanical:
|
||||
else:
|
||||
return False
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('BOQMechanical', _CommandBOQMechanical())
|
||||
|
||||
+827
-166
File diff suppressed because it is too large
Load Diff
+182
-4
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>715</width>
|
||||
<height>520</height>
|
||||
<width>462</width>
|
||||
<height>282</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -33,19 +33,197 @@
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Tab 1</string>
|
||||
<string>Layers</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="tableLayers">
|
||||
<property name="showGrid">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="cornerButtonEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<row/>
|
||||
<row/>
|
||||
<row/>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Sel</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Color</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>LineType</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Lineweight</string>
|
||||
</property>
|
||||
</column>
|
||||
<item row="2" column="1">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Tab 2</string>
|
||||
<string>Objetos a exportar</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="tableObjects">
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<row/>
|
||||
<row/>
|
||||
<row/>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string>New Row</string>
|
||||
</property>
|
||||
</row>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string>New Row</string>
|
||||
</property>
|
||||
</row>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Sel</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Color</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>LineType</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Lineweight</string>
|
||||
</property>
|
||||
</column>
|
||||
<item row="0" column="0">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
<string>Planos en papel a crear</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="tablePapers">
|
||||
<property name="showGrid">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<row/>
|
||||
<row/>
|
||||
<row/>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string>New Row</string>
|
||||
</property>
|
||||
</row>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string>New Row</string>
|
||||
</property>
|
||||
</row>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Sel</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Color</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>LineType</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Lineweight</string>
|
||||
</property>
|
||||
</column>
|
||||
<item row="0" column="0">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonAcept">
|
||||
<property name="text">
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import zipfile
|
||||
import tempfile
|
||||
import shutil
|
||||
import xml.etree.ElementTree as ET
|
||||
from PySide2 import QtWidgets, QtCore, QtGui
|
||||
from PySide import QtWidgets, QtCore, QtGui
|
||||
import FreeCAD
|
||||
import Mesh
|
||||
import Part
|
||||
|
||||
+2
-2
@@ -186,8 +186,8 @@ def createPVPanel(name):
|
||||
typpvpanel.noct = 45.0 # SINTC (double) (Cº)
|
||||
|
||||
typpvpanel.manuf = "" # Fabricante
|
||||
typpypanel.chr_name = "" # Nombre característico
|
||||
typpypanel.appr_status = 1 # Estado: 0: No aprobado, 1: aprobado
|
||||
typpvpanel.chr_name = "" # Nombre característico
|
||||
typpvpanel.appr_status = 1 # Estado: 0: No aprobado, 1: aprobado
|
||||
|
||||
return typpvpanel
|
||||
|
||||
|
||||
+369
-84
@@ -29,6 +29,15 @@ import Part
|
||||
import numpy
|
||||
import os
|
||||
|
||||
from xml.etree.ElementTree import Element, SubElement
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
import datetime
|
||||
from xml.dom import minidom
|
||||
|
||||
from numpy.matrixlib.defmatrix import matrix
|
||||
|
||||
from Utils import PVPlantUtils as utils
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore
|
||||
@@ -44,38 +53,30 @@ except AttributeError:
|
||||
def _fromUtf8(s):
|
||||
return s
|
||||
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
## @package importDAE
|
||||
# \ingroup ARCH
|
||||
# \brief DAE (Collada) file format importer and exporter
|
||||
#
|
||||
# This module provides tools to import and export Collada (.dae) files.
|
||||
|
||||
__title__ = "FreeCAD Collada importer"
|
||||
__author__ = "Yorik van Havre"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
|
||||
try:
|
||||
# Python 2 forward compatibility
|
||||
range = xrange
|
||||
except NameError:
|
||||
pass
|
||||
import collada
|
||||
COLLADA_AVAILABLE = True
|
||||
except ImportError:
|
||||
COLLADA_AVAILABLE = False
|
||||
|
||||
__title__ = "FreeCAD PVSyst importer"
|
||||
__author__ = "Javier Braña"
|
||||
#__url__ = "http://www.freecadweb.org"
|
||||
|
||||
scale = 0.001 # from millimeters (FreeCAD) to meters (Collada)
|
||||
|
||||
|
||||
def checkCollada():
|
||||
"checks if collada if available"
|
||||
|
||||
global collada
|
||||
COLLADA = None
|
||||
try:
|
||||
import collada
|
||||
except ImportError:
|
||||
FreeCAD.Console.PrintError(translate("Arch", "pycollada not found, collada support is disabled.") + "\n")
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
def check_collada():
|
||||
"""Verifica la disponibilidad de pycollada"""
|
||||
if not COLLADA_AVAILABLE:
|
||||
FreeCAD.Console.PrintError(translate("PVPlant", "pycollada no encontrado, soporte Collada desactivado.") + "\n")
|
||||
return COLLADA_AVAILABLE
|
||||
|
||||
# Asegurar que el texto es Unicode válido
|
||||
def safe_text(text):
|
||||
if isinstance(text, bytes):
|
||||
return text.decode('utf-8', errors='replace')
|
||||
return text
|
||||
|
||||
# from ARCH:
|
||||
def triangulate(shape):
|
||||
@@ -84,6 +85,7 @@ def triangulate(shape):
|
||||
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
|
||||
mesher = p.GetInt("ColladaMesher", 0)
|
||||
tessellation = p.GetFloat("ColladaTessellation", 1.0)
|
||||
|
||||
grading = p.GetFloat("ColladaGrading", 0.3)
|
||||
segsperedge = p.GetInt("ColladaSegsPerEdge", 1)
|
||||
segsperradius = p.GetInt("ColladaSegsPerRadius", 2)
|
||||
@@ -96,9 +98,15 @@ def triangulate(shape):
|
||||
elif mesher == 1:
|
||||
return MeshPart.meshFromShape(Shape=shape, MaxLength=tessellation).Topology
|
||||
else:
|
||||
return MeshPart.meshFromShape(Shape=shape, GrowthRate=grading, SegPerEdge=segsperedge,
|
||||
SegPerRadius=segsperradius, SecondOrder=secondorder, Optimize=optimize,
|
||||
AllowQuad=allowquads).Topology
|
||||
return MeshPart.meshFromShape(Shape=shape,
|
||||
GrowthRate=grading,
|
||||
SegPerEdge=segsperedge,
|
||||
SegPerRadius=segsperradius,
|
||||
SecondOrder=secondorder,
|
||||
Optimize=optimize,
|
||||
AllowQuad=allowquads
|
||||
).Topology
|
||||
|
||||
|
||||
def export(exportList, filename, tessellation=1, colors=None):
|
||||
"""export(exportList,filename,tessellation=1,colors=None) -- exports FreeCAD contents to a DAE file.
|
||||
@@ -254,12 +262,308 @@ def export(exportList, filename, tessellation=1, colors=None):
|
||||
|
||||
FreeCAD.Console.PrintMessage(translate("Arch", "file %s successfully created.") % filename)
|
||||
|
||||
def exportToDAE(path):
|
||||
filename = path + ".dae"
|
||||
|
||||
def exportToPVC(path, exportTerrain = False):
|
||||
filename = path + ".pvc"
|
||||
scale = 0.001 # from millimeters (FreeCAD) to meters (Collada)
|
||||
def exportToPVC(path, exportTerrain=False):
|
||||
filename = f"{path}.pvc"
|
||||
|
||||
# 1. Validación inicial de objetos esenciales
|
||||
site = None
|
||||
for obj in FreeCAD.ActiveDocument.Objects:
|
||||
if obj.Name.startswith("Site") and hasattr(obj, 'Terrain'):
|
||||
site = obj
|
||||
break
|
||||
|
||||
if not site:
|
||||
FreeCAD.Console.PrintError("No se encontró objeto 'Site' válido\n")
|
||||
return False
|
||||
|
||||
# 2. Configuración de metadatos y autor
|
||||
generated_on = str(datetime.datetime.now())
|
||||
|
||||
try:
|
||||
author = FreeCAD.ActiveDocument.CreatedBy
|
||||
except (AttributeError, UnicodeEncodeError):
|
||||
author = "Unknown"
|
||||
|
||||
author = author.replace("<", "").replace(">", "")
|
||||
ver = FreeCAD.Version()
|
||||
appli = f"PVPlant for FreeCAD {ver[0]}.{ver[1]} build{ver[2]}"
|
||||
|
||||
# 3. Creación estructura XML base
|
||||
root = Element('COLLADA')
|
||||
root.set('xmlns', 'http://www.collada.org/2005/11/COLLADASchema')
|
||||
root.set('version', '1.4.1')
|
||||
|
||||
# 4. Sección <asset>
|
||||
asset = SubElement(root, 'asset')
|
||||
contrib = SubElement(asset, 'contributor')
|
||||
SubElement(contrib, 'author').text = safe_text(author)
|
||||
SubElement(contrib, 'authoring_tool').text = safe_text(appli)
|
||||
SubElement(asset, 'created').text = generated_on
|
||||
SubElement(asset, 'modified').text = generated_on
|
||||
SubElement(asset, 'title').text = safe_text(FreeCAD.ActiveDocument.Name)
|
||||
unit = SubElement(asset, 'unit')
|
||||
unit.set('name', 'meter')
|
||||
unit.set('meter', '1')
|
||||
|
||||
# 5. Materiales y efectos
|
||||
materials = ["Frames", "Tree_trunk", "Tree_crown", "Topography_mesh"]
|
||||
|
||||
# Library materials
|
||||
lib_materials = SubElement(root, 'library_materials')
|
||||
for i, name in enumerate(materials):
|
||||
mat = SubElement(lib_materials, 'material')
|
||||
mat.set('id', f'Material{i}')
|
||||
mat.set('name', name)
|
||||
SubElement(mat, 'instance_effect').set('url', f'#Material{i}-fx')
|
||||
|
||||
# Library effects
|
||||
lib_effects = SubElement(root, 'library_effects')
|
||||
for i, _ in enumerate(materials):
|
||||
effect = SubElement(lib_effects, 'effect')
|
||||
effect.set('id', f'Material{i}-fx')
|
||||
effect.set('name', f'Material{i}')
|
||||
profile = SubElement(effect, 'profile_COMMON')
|
||||
technique = SubElement(profile, 'technique')
|
||||
technique.set('sid', 'standard')
|
||||
lambert = SubElement(technique, 'lambert')
|
||||
|
||||
# Componentes del material
|
||||
color = SubElement(SubElement(lambert, 'emission'), 'color')
|
||||
color.set('sid', 'emission')
|
||||
color.text = '0.000000 0.000000 0.000000 1.000000'
|
||||
color = SubElement(SubElement(lambert, 'ambient'), 'color')
|
||||
color.set('sid', 'ambient')
|
||||
color.text = '0.200000 0.200000 0.200000 1.000000'
|
||||
color = SubElement(SubElement(lambert, 'diffuse'), 'color')
|
||||
color.set('sid', 'diffuse')
|
||||
color.text = '0.250000 0.500000 0.000000 1.000000'
|
||||
transparent = SubElement(lambert, 'transparent')
|
||||
transparent.set('opaque', 'RGB_ZERO')
|
||||
color = SubElement(transparent, 'color')
|
||||
color.set('sid', 'transparent')
|
||||
color.text = '0.000000 0.000000 0.000000 1.000000'
|
||||
value = SubElement(SubElement(lambert, 'transparency'), 'float')
|
||||
value.set('sid', 'transparency')
|
||||
value.text = '0.000000'
|
||||
|
||||
# 6. Geometrías
|
||||
lib_geometries = SubElement(root, 'library_geometries')
|
||||
|
||||
# 7. Escena visual
|
||||
lib_visual = SubElement(root, 'library_visual_scenes')
|
||||
visual_scene = SubElement(lib_visual, 'visual_scene')
|
||||
visual_scene.set('id', 'Scene') # cambiar a visual_scene_0
|
||||
visual_scene.set('name', 'Scene') # cambiar a Default visual scene
|
||||
|
||||
scene_node = SubElement(visual_scene, 'node')
|
||||
scene_node.set('id', 'node_0_id')
|
||||
scene_node.set('name', 'node_0_name')
|
||||
scene_node.set('sid', 'node_0_sid')
|
||||
|
||||
scene_matrix = SubElement(scene_node, 'matrix')
|
||||
scene_matrix.set('sid', 'matrix_0')
|
||||
scene_matrix.text = '1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000'
|
||||
|
||||
root_node = SubElement(scene_node, 'node')
|
||||
root_node.set('id', 'node_1_id')
|
||||
root_node.set('name', 'node_1_name')
|
||||
root_node.set('sid', 'node_1_sid')
|
||||
|
||||
# 8. Función para procesar geometrías
|
||||
def create_geometry(name, vindex, findex, material_id, objind=0, frame_data=None, isTracker = False, axis_line=None):
|
||||
"""Crea elementos COLLADA para una geometría"""
|
||||
# Source (vertices)
|
||||
source_mesh = SubElement(geom, 'mesh')
|
||||
source = SubElement(source_mesh, 'source')
|
||||
source.set('id', f'{name}-mesh_source')
|
||||
float_array = SubElement(source, 'float_array')
|
||||
float_array.set('id', f'{name}-float_array')
|
||||
float_array.set('count', str(len(vindex)))
|
||||
float_array.text = ' '.join(f'{v:.6f}' for v in vindex)
|
||||
|
||||
technique = SubElement(source, 'technique_common')
|
||||
accessor = SubElement(technique, 'accessor')
|
||||
accessor.set('count', str(len(vindex)))
|
||||
accessor.set('source', f'#{name}-float_array')
|
||||
accessor.set('stride', '3')
|
||||
for ax in ['X', 'Y', 'Z']:
|
||||
param = SubElement(accessor, 'param')
|
||||
param.set('name', ax)
|
||||
param.set('type', 'float')
|
||||
|
||||
# Vertices
|
||||
vertices = SubElement(source_mesh, 'vertices')
|
||||
vertices.set('id', f'{name}-vertices_source')
|
||||
vertices = SubElement(vertices, 'input')
|
||||
vertices.set('semantic', 'POSITION')
|
||||
vertices.set('source', f'#{name}-mesh_source')
|
||||
|
||||
# Triangles
|
||||
triangles = SubElement(source_mesh, 'triangles')
|
||||
triangles.set('count', '0')
|
||||
triangles.set('material', f'Material{material_id}')
|
||||
triangles_input = SubElement(triangles, 'input')
|
||||
triangles_input.set('offset', '0')
|
||||
triangles_input.set('semantic', 'VERTEX')
|
||||
triangles_input.set('source', f'#{name}-vertices_source')
|
||||
|
||||
p = SubElement(triangles, 'p')
|
||||
p.text = ' '.join(map(str, findex))
|
||||
|
||||
# Parámetros especiales para estructuras
|
||||
|
||||
frame_params = SubElement(source_mesh, 'tracker_parameters')
|
||||
if frame_data:
|
||||
for key, val in frame_data.items():
|
||||
elem = SubElement(frame_params, key)
|
||||
elem.text = str(val)
|
||||
|
||||
if isTracker:
|
||||
axis_parameter = SubElement(frame_params, 'axis_vertices')
|
||||
if axis_line:
|
||||
for idx, vert in enumerate(axis_line):
|
||||
array = SubElement(axis_parameter, 'float_array')
|
||||
array.set('id', f'{name}-axis_float_array{idx}')
|
||||
array.set('count', '3')
|
||||
array.text = ' '.join(f'{v:.6f}' for v in vert)
|
||||
|
||||
# 9. Procesar estructuras (frames/trackers)
|
||||
center = FreeCAD.Vector()
|
||||
if site.Terrain:
|
||||
center = site.Terrain.Mesh.BoundBox.Center
|
||||
|
||||
objind = 0
|
||||
for frame_type in site.Frames:
|
||||
is_tracker = "tracker" in frame_type.Proxy.Type.lower()
|
||||
|
||||
modules = frame_type.Shape.SubShapes[0].SubShapes[0]
|
||||
pts = []
|
||||
for i in range(4):
|
||||
pts.append(modules.BoundBox.getPoint(i))
|
||||
|
||||
new_shape = Part.Face(Part.makePolygon(pts))
|
||||
mesh = Mesh.Mesh(triangulate(new_shape))
|
||||
axis = Part.makeLine(FreeCAD.Vector(modules.BoundBox.XMin, 0, modules.BoundBox.ZMax),
|
||||
FreeCAD.Vector(modules.BoundBox.XMax, 0, modules.BoundBox.ZMax))
|
||||
|
||||
for obj in FreeCAD.ActiveDocument.Objects:
|
||||
if hasattr(obj, "Setup") and obj.Setup == frame_type:
|
||||
# Procesar geometría
|
||||
mesh.Placement = obj.getGlobalPlacement()
|
||||
axis.Placement = obj.getGlobalPlacement()
|
||||
|
||||
# Transformar vértices
|
||||
vindex = []
|
||||
for point in mesh.Points:
|
||||
adjusted = (point.Vector - center) * scale
|
||||
vindex.extend([
|
||||
-adjusted.x,
|
||||
adjusted.z,
|
||||
adjusted.y
|
||||
])
|
||||
|
||||
# Índices de caras
|
||||
findex = []
|
||||
for facet in mesh.Facets:
|
||||
findex.extend(facet.PointIndices)
|
||||
|
||||
# AXIS
|
||||
# TODO: revisar si es así:
|
||||
vaxis = []
|
||||
for vert in axis.Vertexes:
|
||||
adjusted = (vert.Point - center) * scale
|
||||
vaxis.append([
|
||||
-adjusted.x,
|
||||
adjusted.z,
|
||||
adjusted.y
|
||||
])
|
||||
|
||||
# Crear geometría COLLADA
|
||||
geom = SubElement(lib_geometries, 'geometry')
|
||||
geom.set('id', f'Frame_{objind}')
|
||||
|
||||
# Parámetros específicos de estructura
|
||||
frame_data = {
|
||||
'module_width': obj.Setup.ModuleWidth.Value,
|
||||
'module_height': obj.Setup.ModuleHeight.Value,
|
||||
'module_x_spacing': obj.Setup.ModuleColGap.Value,
|
||||
'module_y_spacing': obj.Setup.ModuleRowGap.Value,
|
||||
'module_name': 'Generic'
|
||||
}
|
||||
|
||||
if is_tracker:
|
||||
frame_data.update({
|
||||
'tracker_type': 'single_axis_trackers',
|
||||
'min_phi': obj.Setup.MinPhi.Value,
|
||||
'max_phi': obj.Setup.MaxPhi.Value,
|
||||
'min_theta': 0,
|
||||
'max_theta': 0
|
||||
})
|
||||
|
||||
create_geometry(
|
||||
name=f'Frame_{objind}',
|
||||
vindex=vindex,
|
||||
findex=findex,
|
||||
material_id=0,
|
||||
objind=objind,
|
||||
frame_data=frame_data,
|
||||
isTracker = is_tracker,
|
||||
axis_line=vaxis
|
||||
)
|
||||
|
||||
# Instancia en escena
|
||||
instance = SubElement(root_node, 'instance_geometry')
|
||||
instance.set('url', f'#Frame_{objind}')
|
||||
|
||||
bind_material = SubElement(instance, 'bind_material')
|
||||
technique_common = SubElement(bind_material, 'technique_common')
|
||||
instance_material = SubElement(technique_common, 'instance_material')
|
||||
instance_material.set('symbol', 'Material0')
|
||||
instance_material.set('target', '#Material0')
|
||||
|
||||
objind += 1
|
||||
|
||||
# 10. Procesar terreno si está habilitado
|
||||
if exportTerrain and site.Terrain:
|
||||
mesh = site.Terrain.Mesh
|
||||
vindex = []
|
||||
for point in mesh.Points:
|
||||
point = point.Vector
|
||||
vindex.extend([
|
||||
-point.x * SCALE,
|
||||
point.z * SCALE,
|
||||
point.y * SCALE
|
||||
])
|
||||
|
||||
findex = []
|
||||
for facet in mesh.Facets:
|
||||
findex.extend(facet.PointIndices)
|
||||
|
||||
geom = SubElement(lib_geometries, 'geometry')
|
||||
geom.set('id', 'Terrain')
|
||||
create_geometry('Terrain', vindex, findex, material_id=3)
|
||||
|
||||
instance = SubElement(root_node, 'instance_geometry')
|
||||
instance.set('url', '#Terrain')
|
||||
|
||||
# 11. Escena principal
|
||||
scene = SubElement(root, 'scene')
|
||||
SubElement(scene, 'instance_visual_scene').set('url', '#Scene')
|
||||
|
||||
# 12. Exportar a archivo
|
||||
xml_str = minidom.parseString(
|
||||
ElementTree.tostring(root, encoding='utf-8')
|
||||
).toprettyxml(indent=" ")
|
||||
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write(xml_str)
|
||||
|
||||
FreeCAD.Console.PrintMessage(f"Archivo PVC generado: {filename}\n")
|
||||
return True
|
||||
|
||||
def exportToPVC_old(path, exportTerrain = False):
|
||||
filename = f"{path}.pvc"
|
||||
|
||||
from xml.etree.ElementTree import Element, SubElement
|
||||
import datetime
|
||||
@@ -300,17 +604,18 @@ def exportToPVC(path, exportTerrain = False):
|
||||
|
||||
# xml: 1. Asset:
|
||||
asset = SubElement(root, 'asset')
|
||||
|
||||
asset_contributor = SubElement(asset, 'contributor')
|
||||
asset_contributor_autor = SubElement(asset_contributor, 'autor')
|
||||
#asset_contributor_autor.text = author
|
||||
asset_contributor_autor = SubElement(asset_contributor, 'author')
|
||||
asset_contributor_autor.text = author
|
||||
asset_contributor_authoring_tool = SubElement(asset_contributor, 'authoring_tool')
|
||||
#asset_contributor_authoring_tool.text = appli
|
||||
asset_contributor_authoring_tool.text = appli
|
||||
asset_contributor_comments = SubElement(asset_contributor, 'comments')
|
||||
asset_keywords = SubElement(asset, 'keywords')
|
||||
asset_revision = SubElement(asset, 'revision')
|
||||
asset_subject = SubElement(asset, 'subject')
|
||||
asset_tittle = SubElement(asset, 'title')
|
||||
#asset_tittle.text = FreeCAD.ActiveDocument.Name
|
||||
asset_tittle.text = FreeCAD.ActiveDocument.Name
|
||||
asset_unit = SubElement(asset, 'unit')
|
||||
asset_unit.set('meter', '0.001')
|
||||
asset_unit.set('name', 'millimeter')
|
||||
@@ -368,7 +673,6 @@ def exportToPVC(path, exportTerrain = False):
|
||||
# xml: 4. library_geometries:
|
||||
library_geometries = SubElement(root, 'library_geometries')
|
||||
def add_geometry(objtype, vindex, findex, objind = 0, centers = None):
|
||||
|
||||
isFrame = False
|
||||
if objtype == 0:
|
||||
geometryName = 'Frame'
|
||||
@@ -453,7 +757,7 @@ def exportToPVC(path, exportTerrain = False):
|
||||
array = SubElement(axis, 'float_array')
|
||||
array.set('id', 'tracker{0}AxisFloatArray1'.format(ind))
|
||||
array.set('count', '3')
|
||||
array.text = '{0:.6f} {1:.6f} {2:.6f}'.format(centers[i].x, centers[i].y, centers[i].z)
|
||||
array.text = '{0:.6f} {1:.6f} {2:.6f}'.format(centers[ind].x, centers[ind].y, centers[ind].z)
|
||||
|
||||
min_phi = SubElement(frame, 'min_phi')
|
||||
min_phi.text = '{0}'.format(int(obj.Setup.MinPhi.Value))
|
||||
@@ -514,38 +818,22 @@ def exportToPVC(path, exportTerrain = False):
|
||||
end_time.text = '1.000000'
|
||||
|
||||
# xml: 6. scene:
|
||||
scene = SubElement(root, 'scene')
|
||||
'''scene = SubElement(root, 'scene')
|
||||
instance = SubElement(scene, 'instance_visual_scene')
|
||||
instance.set('url', '#')
|
||||
|
||||
full_list_of_objects = FreeCAD.ActiveDocument.Objects
|
||||
instance.set('url', '#')'''
|
||||
|
||||
# CASO 1 - FRAMES:
|
||||
frameType = site.Frames
|
||||
frame_setup = {"type": [],
|
||||
"footprint": []}
|
||||
for obj in frameType:
|
||||
frame_setup["type"] = obj
|
||||
frame_setup["footprint"] = ""
|
||||
|
||||
objind = 0
|
||||
|
||||
# TODO: revisar
|
||||
for type in frameType:
|
||||
isTracker = "tracker" in type.Proxy.Type.lower()
|
||||
#TODO: Sólo para los proyectos de NAcho. Borrar
|
||||
isTracker = False
|
||||
for typ in frameType:
|
||||
isTracker = "tracker" in typ.Proxy.Type.lower()
|
||||
#isTracker = False
|
||||
|
||||
objectlist = FreeCAD.ActiveDocument.findObjects(Name="Tracker")
|
||||
tmp = []
|
||||
objectlist = utils.findObjects("Tracker")
|
||||
for obj in objectlist:
|
||||
if obj.Name.startswith("TrackerSetup"):
|
||||
continue
|
||||
else:
|
||||
tmp.append(obj)
|
||||
objectlist = tmp.copy()
|
||||
|
||||
for obj in objectlist:
|
||||
if obj.Setup == type:
|
||||
if obj.Setup == typ:
|
||||
findex = numpy.array([])
|
||||
|
||||
modules = obj.Setup.Shape.SubShapes[0].SubShapes[0]
|
||||
@@ -589,7 +877,6 @@ def exportToPVC(path, exportTerrain = False):
|
||||
v = Topology[0][i]
|
||||
vindex[list(range(i * 3, i * 3 + 3))] = (-(v.x - center.x) * scale, (v.z - center.z) * scale,
|
||||
(v.y - center.y) * scale)
|
||||
|
||||
# 2. face indices
|
||||
findex = numpy.empty(len(Topology[1]) * 3, numpy.int64)
|
||||
for i in range(len(Topology[1])):
|
||||
@@ -726,26 +1013,26 @@ def exportToH2P(path): # sólo válido para mesas
|
||||
#for obj in objects:
|
||||
grouptype.append(objects[0])
|
||||
|
||||
for type in grouptype:
|
||||
for typ in grouptype:
|
||||
st += 'TABLE\n' \
|
||||
'10\n'
|
||||
st += f3.format(type.Width.Value) + ',' + f3.format(type.Length.Value) + ',' + \
|
||||
st += f3.format(typ.Width.Value) + ',' + f3.format(typ.Length.Value) + ',' + \
|
||||
f3.format(0) + ',' + f3.format(0) + ',' + f3.format(0) + ',' + f3.format(0) + "\n"
|
||||
#'#{ f3 %pvsyst.ilb.to_mm },#{f3 %pvsyst.irb.to_mm},#{f3 %pvsyst.itb.to_mm},' \
|
||||
#'#{f3 %pvsyst.ibb.to_mm}\n'
|
||||
st += '20\n'
|
||||
st += str(int(type.ModulesCols.Value)) + ',' + str(int(type.ModulesRows.Value)) + ',' + \
|
||||
str(type.ModuleColGap.Value) + ',' + str(type.ModuleRowGap.Value) + ',' + '30\n'
|
||||
st += str(int(typ.ModulesCols.Value)) + ',' + str(int(typ.ModulesRows.Value)) + ',' + \
|
||||
str(typ.ModuleColGap.Value) + ',' + str(typ.ModuleRowGap.Value) + ',' + '30\n'
|
||||
st += '30\n'
|
||||
st += '1,' + f3.format(type.ModuleWidth.Value) + ',' + f3.format(type.ModuleHeight.Value) + ',' + \
|
||||
f3.format(type.ModuleThick.Value) + ',' + f2.format(450) + '\n' #f2.format(type.ModulePower.Value) + '\n'
|
||||
st += '1,' + f3.format(typ.ModuleWidth.Value) + ',' + f3.format(typ.ModuleHeight.Value) + ',' + \
|
||||
f3.format(typ.ModuleThick.Value) + ',' + f2.format(450) + '\n' #f2.format(typ.ModulePower.Value) + '\n'
|
||||
|
||||
# cornerdown = find_component_sizes(group.cdef)[1]
|
||||
# pvorigin = Geom::Point3d.new(cornerdown.x, cornerdown.y, 0)
|
||||
# group.instances.each{ | ins | str += pvsyst_insert(ins, pvorigin)}
|
||||
|
||||
for obj in objects:
|
||||
if obj.CloneOf == type:
|
||||
if obj.CloneOf == typ:
|
||||
st += H2PInsert(obj)
|
||||
|
||||
## TODO: Bucle para buscar objetos que den sombra y el terreno. Todos llaman a H2PMesh
|
||||
@@ -778,12 +1065,12 @@ def H2PInsert(obj):
|
||||
|
||||
return st
|
||||
|
||||
def H2PMesh(mesh, type):
|
||||
def H2PMesh(mesh, typ):
|
||||
scale = 0.001 ## ver como se puede hacer para que sea general. Pasar de mm a m
|
||||
|
||||
f3 = '{:.3f}'
|
||||
st = ''
|
||||
if type:
|
||||
if typ:
|
||||
st = 'ShadowObject\nFence\n'
|
||||
else:
|
||||
st = 'DGM\n'
|
||||
@@ -799,7 +1086,7 @@ def H2PMesh(mesh, type):
|
||||
|
||||
return st
|
||||
|
||||
class _PVSystTaskPanel:
|
||||
class PVSystTaskPanel:
|
||||
|
||||
def __init__(self):
|
||||
self.form = FreeCADGui.PySideUic.loadUi(os.path.dirname(__file__) + "/exportPVSyst.ui")
|
||||
@@ -812,7 +1099,7 @@ class _PVSystTaskPanel:
|
||||
def accept(self):
|
||||
import datetime
|
||||
x = datetime.datetime.now()
|
||||
date = x.strftime("%y%m%d%H%M%S")
|
||||
date = x.strftime("%Y%m%d%H%M%S")
|
||||
overwrite = True
|
||||
|
||||
path = os.path.join(os.path.dirname(FreeCAD.ActiveDocument.FileName), "outputs", "PVSyst")
|
||||
@@ -820,12 +1107,10 @@ class _PVSystTaskPanel:
|
||||
os.makedirs(path)
|
||||
|
||||
name = FreeCAD.ActiveDocument.Label
|
||||
if not overwrite:
|
||||
name = date + "-" + name
|
||||
#if not overwrite:
|
||||
name = date + "-" + name
|
||||
filename = os.path.join(path, name)
|
||||
|
||||
#if self.form.cbDAE.isChecked():
|
||||
# exportToDAE(filename)
|
||||
|
||||
if self.form.cbPVC.isChecked():
|
||||
exportToPVC(filename, self.form.cbTerrain.isChecked())
|
||||
@@ -840,7 +1125,7 @@ class _PVSystTaskPanel:
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return True
|
||||
|
||||
class _CommandExportToPVSyst:
|
||||
'''class _CommandExportToPVSyst:
|
||||
"Export to PVSyst"
|
||||
|
||||
def GetResources(self):
|
||||
@@ -861,4 +1146,4 @@ class _CommandExportToPVSyst:
|
||||
return False
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('ExportToPVSyst', _CommandExportToPVSyst())
|
||||
FreeCADGui.addCommand('ExportToPVSyst', _CommandExportToPVSyst())'''
|
||||
|
||||
+11
-26
@@ -20,47 +20,32 @@
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
import FreeCAD, Draft
|
||||
import PVPlantSite
|
||||
import copy
|
||||
import FreeCAD
|
||||
import Draft
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import openpyxl
|
||||
from openpyxl.styles import Alignment, Border, Side, Font
|
||||
|
||||
from PVPlantPlacement import getCols
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
from DraftTools import translate
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
import Part
|
||||
import pivy
|
||||
from pivy import coin
|
||||
import os
|
||||
else:
|
||||
# \cond
|
||||
def translate(ctxt, txt):
|
||||
return txt
|
||||
|
||||
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||
return txt
|
||||
# \endcond
|
||||
|
||||
__title__ = "PVPlant Trench"
|
||||
__author__ = "Javier Braña"
|
||||
__url__ = "http://www.sogos-solar.com"
|
||||
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
from PVPlantResources import DirDocuments as DirDocuments
|
||||
|
||||
|
||||
'''import os
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
def open_file(path):
|
||||
"""Open a file or directory using the default system handler"""
|
||||
if platform.system() == "Windows":
|
||||
os.startfile(path)
|
||||
elif platform.system() == "Darwin":
|
||||
subprocess.Popen(["open", path])
|
||||
else:
|
||||
subprocess.Popen(["xdg-open", path])'''
|
||||
subprocess.Popen(["xdg-open", path])
|
||||
|
||||
|
||||
from PVPlantPlacement import getCols
|
||||
|
||||
@@ -72,11 +72,11 @@ class _PVPlantImportDXF:
|
||||
|
||||
def openFile(self):
|
||||
''' '''
|
||||
"getOpenFileName(parent: typing.Union[PySide2.QtWidgets.QWidget, NoneType] = None," \
|
||||
"getOpenFileName(parent: typing.Union[PySide.QtWidgets.QWidget, NoneType] = None," \
|
||||
"caption: str = ''," \
|
||||
"dir: str = ''," \
|
||||
"filter: str = ''," \
|
||||
"options: PySide2.QtWidgets.QFileDialog.Options = Default(QFileDialog.Options)) -> typing.Tuple[str, str]"
|
||||
"options: PySide.QtWidgets.QFileDialog.Options = Default(QFileDialog.Options)) -> typing.Tuple[str, str]"
|
||||
filename, trash = QtGui.QFileDialog().getOpenFileName(None, 'Select File', os.getcwd(), 'Autocad dxf (*.dxf)')
|
||||
if filename == "":
|
||||
return
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
import os
|
||||
import csv
|
||||
from PySide import QtGui, QtCore
|
||||
|
||||
|
||||
class SelectorDialog(QtGui.QDialog):
|
||||
def __init__(self, csv_path, title, parent=None):
|
||||
super(SelectorDialog, self).__init__(parent)
|
||||
self.setWindowTitle(title)
|
||||
self.csv_path = csv_path
|
||||
self.data = []
|
||||
self.brand_filter = ""
|
||||
self.model_filter = ""
|
||||
|
||||
# Cargar datos del CSV
|
||||
self.load_csv_data()
|
||||
|
||||
# Crear widgets
|
||||
self.create_widgets()
|
||||
self.create_layout()
|
||||
self.create_connections()
|
||||
|
||||
def load_csv_data(self):
|
||||
"""Carga los datos desde el archivo CSV"""
|
||||
if os.path.exists(self.csv_path):
|
||||
with open(self.csv_path, 'r') as f:
|
||||
reader = csv.DictReader(f, delimiter=';')
|
||||
self.data = [row for row in reader]
|
||||
|
||||
def get_unique_brands(self):
|
||||
"""Obtiene marcas únicas"""
|
||||
return list(set(row['Marca'] for row in self.data))
|
||||
|
||||
def get_models_by_brand(self, brand):
|
||||
"""Filtra modelos por marca"""
|
||||
return [row['Modelo'] for row in self.data if row['Marca'] == brand]
|
||||
|
||||
def create_widgets(self):
|
||||
self.lbl_brand = QtGui.QLabel("Marca:")
|
||||
self.cb_brand = QtGui.QComboBox()
|
||||
self.cb_brand.addItems(self.get_unique_brands())
|
||||
|
||||
self.lbl_model = QtGui.QLabel("Modelo:")
|
||||
self.cb_model = QtGui.QComboBox()
|
||||
self.update_model_combo()
|
||||
|
||||
self.btn_accept = QtGui.QPushButton("Aceptar")
|
||||
self.btn_cancel = QtGui.QPushButton("Cancelar")
|
||||
|
||||
def create_layout(self):
|
||||
layout = QtGui.QVBoxLayout()
|
||||
form_layout = QtGui.QFormLayout()
|
||||
form_layout.addRow(self.lbl_brand, self.cb_brand)
|
||||
form_layout.addRow(self.lbl_model, self.cb_model)
|
||||
|
||||
button_layout = QtGui.QHBoxLayout()
|
||||
button_layout.addWidget(self.btn_accept)
|
||||
button_layout.addWidget(self.btn_cancel)
|
||||
|
||||
layout.addLayout(form_layout)
|
||||
layout.addLayout(button_layout)
|
||||
self.setLayout(layout)
|
||||
|
||||
def create_connections(self):
|
||||
self.cb_brand.currentIndexChanged.connect(self.update_model_combo)
|
||||
self.btn_accept.clicked.connect(self.accept)
|
||||
self.btn_cancel.clicked.connect(self.reject)
|
||||
|
||||
def update_model_combo(self):
|
||||
brand = self.cb_brand.currentText()
|
||||
models = self.get_models_by_brand(brand)
|
||||
self.cb_model.clear()
|
||||
self.cb_model.addItems(models)
|
||||
|
||||
def get_selected_item(self):
|
||||
brand = self.cb_brand.currentText()
|
||||
model = self.cb_model.currentText()
|
||||
for row in self.data:
|
||||
if row['Marca'] == brand and row['Modelo'] == model:
|
||||
return row
|
||||
return None
|
||||
|
||||
|
||||
def select_modulo():
|
||||
csv_path = "/ruta/a/tu/databases/modulos.csv" # Ajusta esta ruta
|
||||
dialog = SelectorDialog(csv_path, "Seleccionar Módulo")
|
||||
if dialog.exec_():
|
||||
selected = dialog.get_selected_item()
|
||||
print("Módulo seleccionado:", selected) # Aquí puedes agregar la lógica de importación
|
||||
|
||||
|
||||
def select_inversor():
|
||||
csv_path = "/ruta/a/tu/databases/inversores.csv" # Ajusta esta ruta
|
||||
dialog = SelectorDialog(csv_path, "Seleccionar Inversor")
|
||||
if dialog.exec_():
|
||||
selected = dialog.get_selected_item()
|
||||
print("Inversor seleccionado:", selected) # Aquí puedes agregar la lógica de importación
|
||||
|
||||
|
||||
# Crear una barra de herramientas para acceder fácilmente
|
||||
toolbar = QtGui.QToolBar()
|
||||
select_modulo_action = QtGui.QAction("Seleccionar Módulo", toolbar)
|
||||
select_modulo_action.triggered.connect(select_modulo)
|
||||
toolbar.addAction(select_modulo_action)
|
||||
|
||||
select_inversor_action = QtGui.QAction("Seleccionar Inversor", toolbar)
|
||||
select_inversor_action.triggered.connect(select_inversor)
|
||||
toolbar.addAction(select_inversor_action)
|
||||
|
||||
# Agregar la barra de herramientas a FreeCAD
|
||||
Gui.addToolBar(toolbar)
|
||||
+75
-111
@@ -20,93 +20,48 @@
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
__title__="FreeCAD Fotovoltaic Power Plant Toolkit"
|
||||
__title__ = "FreeCAD Fotovoltaic Power Plant Toolkit"
|
||||
__author__ = "Javier Braña"
|
||||
__url__ = "sn"
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import os
|
||||
import FreeCADGui
|
||||
import FreeCAD
|
||||
|
||||
FreeCADGui.updateLocale()
|
||||
|
||||
|
||||
class PVPlantWorkbench (Workbench):
|
||||
import os
|
||||
class PVPlantWorkbench(Workbench):
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
MenuText = "PVPlant"
|
||||
ToolTip = "Workbench for PV design"
|
||||
Icon = str(os.path.join(DirIcons, "icon.svg"))
|
||||
|
||||
def __init__(self):
|
||||
''' init '''
|
||||
|
||||
def Initialize(self):
|
||||
import sys
|
||||
sys.path.append(r"C:\Users\javie\AppData\Roaming\FreeCAD\Mod")
|
||||
|
||||
# Mias
|
||||
import PVPlantGeoreferencing, PVPlantPlacement, \
|
||||
PVPlantTerrainAnalisys, PVPlantSite, PVPlantImportGrid, PVPlantFence,\
|
||||
PVPlantFoundation, PVPlantCreateTerrainMesh, \
|
||||
PVPlantTreeGenerator, PVPlantBuilding, PVPlantTrench, PVPlantEarthWorks, \
|
||||
PVPlantStringing, \
|
||||
PVPlantPad, PVPlantRoad, PVPlantTerrain, PVPlantManhole, \
|
||||
GraphProfile, Utils.PVPlantTrace,\
|
||||
reload
|
||||
import PVPlantRackChecking
|
||||
sys.path.append(os.path.join(FreeCAD.getUserAppDataDir(), 'Mod'))
|
||||
import PVPlantTools, reload
|
||||
|
||||
from Project.Area import PVPlantArea, PVPlantAreaUtils
|
||||
from Project import ProjectSetup
|
||||
from Export import exportPVSyst, PVPlantBOQMechanical, PVPlantBOQElectrical, PVPlantBOQCivil,\
|
||||
exportDXF
|
||||
from Importer import importDXF
|
||||
self.projectlist = PVPlantTools.projectlist
|
||||
self.projectlist.insert(0, 'Reload')
|
||||
self.projectlist.insert(1, 'Separator')
|
||||
self.framelist = PVPlantTools.pv_mechanical
|
||||
|
||||
from Mechanical.Frame import PVPlantFrame
|
||||
|
||||
from Electrical.Cable import PVPlantCable, PVPlantElectricalLine
|
||||
from Electrical.CombinerBox import PVPlantStringBox
|
||||
from Electrical.Inverter import PVPlantInverter
|
||||
|
||||
# A list of command names created in the line above
|
||||
self.projectlist = ["Reload",
|
||||
"PVPlantSite",
|
||||
"PVPlantGeoreferencing",
|
||||
"ProjectSetup",
|
||||
#"ImportGrid",
|
||||
"Terrain",
|
||||
"PointsGroup",
|
||||
"PVPlantCreateTerrainMesh",
|
||||
"PVPlantAreas",
|
||||
"SplitArea",
|
||||
"TerrainAnalisys",
|
||||
"Trenches",
|
||||
"PVPlantEarthworks",
|
||||
"PVPlantPad",
|
||||
"PVPlantRoad",
|
||||
"PVPlantManhole",
|
||||
#"PVPlantFoundation"
|
||||
"GraphTerrainProfile",
|
||||
"Trace",
|
||||
]
|
||||
self.framelist = [
|
||||
"RackType",
|
||||
"PVPlantRackCheck",
|
||||
"Separator",
|
||||
"PVPlantPlacement",
|
||||
"PVPlantAdjustToTerrain",
|
||||
"PVPlantConvertTo",
|
||||
"PVArea"
|
||||
]
|
||||
|
||||
self.objectlist = [
|
||||
"PVPlantTree",
|
||||
"PVPlantBuilding",
|
||||
"PVPlantFenceGroup",
|
||||
]
|
||||
|
||||
self.inportExportlist = ["BOQCivil",
|
||||
"BOQMechanical",
|
||||
"BOQElectrical",
|
||||
"Separator",
|
||||
"exportDXF",
|
||||
#"importDXF",
|
||||
"ExportToPVSyst",
|
||||
]
|
||||
from Export import ExporterCommands
|
||||
self.inportExportlist = ExporterCommands.Exportlist
|
||||
|
||||
self.objectlist = PVPlantTools.objectlist
|
||||
''' [
|
||||
"PVPlantTree",
|
||||
"PVPlantBuilding",
|
||||
"PVPlantFenceGroup",
|
||||
]'''
|
||||
from Electrical.PowerConverter import PowerConverter
|
||||
self.electricalList = ["PVPlantStringBox",
|
||||
"PVPlantCable",
|
||||
"PVPlanElectricalLine",
|
||||
@@ -114,60 +69,63 @@ class PVPlantWorkbench (Workbench):
|
||||
"Stringing",
|
||||
"Separator",
|
||||
"StringInverter",
|
||||
]
|
||||
"Separator",
|
||||
"PowerConverter"
|
||||
]
|
||||
|
||||
self.roads = ["PVPlantRoad",
|
||||
]
|
||||
|
||||
]
|
||||
|
||||
self.pads = ["PVPlantPad",
|
||||
"Separator"
|
||||
self.pads = ["PVPlantPad",
|
||||
"Separator"
|
||||
]
|
||||
|
||||
# Toolbar
|
||||
self.appendToolbar("Civil", self.projectlist) # creates a new toolbar with your commands
|
||||
self.appendToolbar("PVPlant", self.framelist) # creates a new toolbar with your commands
|
||||
self.appendToolbar("Mechanical", self.framelist) # creates a new toolbar with your commands
|
||||
self.appendToolbar("Shadow", self.objectlist) # creates a new toolbar with your commands
|
||||
self.appendToolbar("Outputs", self.inportExportlist) # creates a new toolbar with your commands
|
||||
self.appendToolbar("Electrical", self.electricalList) # creates a new toolbar with your commands
|
||||
|
||||
# Menu
|
||||
self.appendMenu("&Civil", self.projectlist) # creates a new menu
|
||||
self.appendMenu("&PVPlant", self.framelist) # creates a new menu
|
||||
self.appendMenu("&Mechanical", self.framelist) # creates a new menu
|
||||
self.appendMenu("&Shadow", self.objectlist) # creates a new menu
|
||||
self.appendMenu("&Outputs", self.inportExportlist) # creates a new menu
|
||||
self.appendMenu("&Electrical", self.electricalList) # creates a new menu
|
||||
|
||||
# Draft tools
|
||||
from DraftTools import translate
|
||||
self.drafttools = ["Draft_Line","Draft_Wire","Draft_Circle","Draft_Arc","Draft_Ellipse",
|
||||
"Draft_Polygon","Draft_Rectangle", "Draft_Text",
|
||||
"Draft_Dimension", "Draft_BSpline","Draft_Point",
|
||||
"Draft_Facebinder","Draft_BezCurve","Draft_Label"]
|
||||
self.draftmodtools = ["Draft_Move","Draft_Rotate","Draft_Offset",
|
||||
"Draft_Trimex", "Draft_Upgrade", "Draft_Downgrade", "Draft_Scale",
|
||||
"Draft_Shape2DView","Draft_Draft2Sketch","Draft_Array",
|
||||
"Draft_Clone"]
|
||||
self.draftextratools = ["Draft_WireToBSpline","Draft_ShapeString",
|
||||
"Draft_PathArray","Draft_Mirror","Draft_Stretch"]
|
||||
self.draftcontexttools = ["Draft_ApplyStyle","Draft_ToggleDisplayMode","Draft_AddToGroup","Draft_AutoGroup",
|
||||
"Draft_SelectGroup","Draft_SelectPlane",
|
||||
"Draft_ShowSnapBar","Draft_ToggleGrid",]
|
||||
self.draftutils = ["Draft_Heal","Draft_FlipDimension",
|
||||
"Draft_ToggleConstructionMode","Draft_ToggleContinueMode","Draft_Edit",
|
||||
"Draft_Slope","Draft_AddConstruction"]
|
||||
self.snapList = ['Draft_Snap_Lock','Draft_Snap_Midpoint','Draft_Snap_Perpendicular',
|
||||
'Draft_Snap_Grid','Draft_Snap_Intersection','Draft_Snap_Parallel',
|
||||
'Draft_Snap_Endpoint','Draft_Snap_Angle','Draft_Snap_Center',
|
||||
'Draft_Snap_Extension','Draft_Snap_Near','Draft_Snap_Ortho','Draft_Snap_Special',
|
||||
'Draft_Snap_Dimensions','Draft_Snap_WorkingPlane']
|
||||
|
||||
self.drafttools = ["Draft_Line", "Draft_Wire", "Draft_Circle", "Draft_Arc", "Draft_Ellipse",
|
||||
"Draft_Polygon", "Draft_Rectangle", "Draft_Text",
|
||||
"Draft_Dimension", "Draft_BSpline", "Draft_Point",
|
||||
"Draft_Facebinder", "Draft_BezCurve", "Draft_Label"]
|
||||
self.draftmodtools = ["Draft_Move", "Draft_Rotate", "Draft_Offset",
|
||||
"Draft_Trimex", "Draft_Upgrade", "Draft_Downgrade", "Draft_Scale",
|
||||
"Draft_Shape2DView", "Draft_Draft2Sketch", "Draft_Array",
|
||||
"Draft_Clone"]
|
||||
self.draftextratools = ["Draft_WireToBSpline", "Draft_ShapeString",
|
||||
"Draft_PathArray", "Draft_Mirror", "Draft_Stretch"]
|
||||
self.draftcontexttools = ["Draft_ApplyStyle", "Draft_ToggleDisplayMode", "Draft_AddToGroup", "Draft_AutoGroup",
|
||||
"Draft_SelectGroup", "Draft_SelectPlane",
|
||||
"Draft_ShowSnapBar", "Draft_ToggleGrid", ]
|
||||
self.draftutils = ["Draft_Heal", "Draft_FlipDimension",
|
||||
"Draft_ToggleConstructionMode", "Draft_ToggleContinueMode", "Draft_Edit",
|
||||
"Draft_Slope", "Draft_AddConstruction"]
|
||||
self.snapList = ['Draft_Snap_Lock', 'Draft_Snap_Midpoint', 'Draft_Snap_Perpendicular',
|
||||
'Draft_Snap_Grid', 'Draft_Snap_Intersection', 'Draft_Snap_Parallel',
|
||||
'Draft_Snap_Endpoint', 'Draft_Snap_Angle', 'Draft_Snap_Center',
|
||||
'Draft_Snap_Extension', 'Draft_Snap_Near', 'Draft_Snap_Ortho', 'Draft_Snap_Special',
|
||||
'Draft_Snap_Dimensions', 'Draft_Snap_WorkingPlane']
|
||||
|
||||
def QT_TRANSLATE_NOOP(scope, text): return text
|
||||
|
||||
self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Draft tools"), self.drafttools)
|
||||
self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Draft mod tools"), self.draftmodtools)
|
||||
self.appendMenu(QT_TRANSLATE_NOOP("arch", "&Draft"), self.drafttools + self.draftmodtools + self.draftextratools)
|
||||
self.appendMenu([QT_TRANSLATE_NOOP("arch", "&Draft"), QT_TRANSLATE_NOOP("arch", "Utilities")], self.draftutils + self.draftcontexttools)
|
||||
self.appendMenu(QT_TRANSLATE_NOOP("arch", "&Draft"),
|
||||
self.drafttools + self.draftmodtools + self.draftextratools)
|
||||
self.appendMenu([QT_TRANSLATE_NOOP("arch", "&Draft"), QT_TRANSLATE_NOOP("arch", "Utilities")],
|
||||
self.draftutils + self.draftcontexttools)
|
||||
self.appendMenu([QT_TRANSLATE_NOOP("arch", "&Draft"), QT_TRANSLATE_NOOP("arch", "Snapping")], self.snapList)
|
||||
|
||||
import Part
|
||||
@@ -188,27 +146,33 @@ class PVPlantWorkbench (Workbench):
|
||||
from widgets import CountSelection
|
||||
|
||||
def Activated(self):
|
||||
"This function is executed when the workbench is activated"
|
||||
"""This function is executed when the workbench is activated"""
|
||||
|
||||
FreeCAD.Console.PrintLog("Road workbench activated.\n")
|
||||
|
||||
import SelectionObserver
|
||||
import FreeCADGui
|
||||
|
||||
self.observer = SelectionObserver.SelObserver()
|
||||
FreeCADGui.Selection.addObserver(self.observer) # installe la fonction en mode resident
|
||||
#self.observer = SelectionObserver.SelObserver()
|
||||
#FreeCADGui.Selection.addObserver(self.observer) # installe la fonction en mode resident
|
||||
return
|
||||
|
||||
def Deactivated(self):
|
||||
"This function is executed when the workbench is deactivated"
|
||||
FreeCADGui.Selection.removeObserver(self.observer)
|
||||
"""This function is executed when the workbench is deactivated"""
|
||||
|
||||
FreeCAD.Console.PrintLog("Road workbench deactivated.\n")
|
||||
|
||||
#FreeCADGui.Selection.removeObserver(self.observer)
|
||||
return
|
||||
|
||||
def ContextMenu(self, recipient):
|
||||
"This is executed whenever the user right-clicks on screen"
|
||||
# "recipient" will be either "view" or "tree"
|
||||
|
||||
#if FreeCAD.activeDraftCommand is None:
|
||||
# if FreeCAD.activeDraftCommand is None:
|
||||
if recipient.lower() == "view":
|
||||
print("Menus en la 'View'")
|
||||
#if FreeCAD.activeDraftCommand is None:
|
||||
# if FreeCAD.activeDraftCommand is None:
|
||||
presel = FreeCADGui.Selection.getPreselection()
|
||||
print(presel.SubElementNames, " - ", presel.PickedPoints)
|
||||
if not presel is None:
|
||||
@@ -244,4 +208,4 @@ class PVPlantWorkbench (Workbench):
|
||||
return "Gui::PythonWorkbench"
|
||||
|
||||
|
||||
Gui.addWorkbench(PVPlantWorkbench())
|
||||
FreeCADGui.addWorkbench(PVPlantWorkbench())
|
||||
|
||||
@@ -0,0 +1,877 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>formRack</class>
|
||||
<widget class="QDialog" name="formRack">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>476</width>
|
||||
<height>1032</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Fixed Frame:</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Módulos:</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="horizontalSpacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Altura (m)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editModuleHeight">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>5.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.990000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Largura (m)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editModuleLenght">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>5.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.960000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Anchura (m)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editModuleWidth">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.030000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Potencia (wp)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="editModulePower">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>150</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>350</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Estructura</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Columnas (un)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editFrontHeight">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>5.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.800000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Orientación del módulo</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editVerticalGap">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>0.900000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.020000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="editRows">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboFrameType">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Fija</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Tracker 1 Eje</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editLeftOffset">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>0.900000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.050000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>Offset borde derecha (m)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Ángulo de inclinación (º)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editRightOffset">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>0.900000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.050000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="1">
|
||||
<widget class="QSpinBox" name="editTilt">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>60</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="1">
|
||||
<widget class="QSpinBox" name="editInclination">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>60</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Filas (un)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Distancia al suelo en el frente (m)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="editCols">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>20</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="labelVerticalGap">
|
||||
<property name="text">
|
||||
<string>Separación vertical entre módulos (m)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="comboModuleOrientation">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Landscape</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Portrait</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Separación horizontal entre módulos (m)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Ängulo máximo de inclinación longitudinal (ª)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editHorizontalGap">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>0.900000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.020000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Tipo de estructura</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>Offset borde izquierda (m)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0" colspan="2">
|
||||
<widget class="QWidget" name="widgetTracker" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelVerticalGap_2">
|
||||
<property name="text">
|
||||
<string>Separación entre uniones (m)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="checkBox">
|
||||
<property name="text">
|
||||
<string>Separación Motor (m)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="editInternalGapNumber">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="text">
|
||||
<string>Número de uniones</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editInternalGap">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>0.900000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.020000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editMotorGap">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>0.900000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>0.020000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Resultado</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Total de módulos</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="editTotalModules">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Potencia total (wp)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="editTotalPower">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Longitud (m)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="editTotalLength">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Anchura (m)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="editTotalWidth">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>140</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
+202
-633
File diff suppressed because it is too large
Load Diff
@@ -1,554 +0,0 @@
|
||||
import ArchComponent
|
||||
import FreeCAD
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore, QtGui
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
else:
|
||||
# \cond
|
||||
def translate(ctxt, txt):
|
||||
return txt
|
||||
|
||||
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||
return txt
|
||||
# \endcond
|
||||
|
||||
try:
|
||||
_fromUtf8 = QtCore.QString.fromUtf8
|
||||
except AttributeError:
|
||||
def _fromUtf8(s):
|
||||
return s
|
||||
|
||||
import threading
|
||||
|
||||
|
||||
def makePlacement(baseobj=None, diameter=0, length=0, placement=None, name="Placement"):
|
||||
"makePipe([baseobj,diamerter,length,placement,name]): creates an pipe object from the given base object"
|
||||
|
||||
if not FreeCAD.ActiveDocument:
|
||||
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
||||
return
|
||||
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
|
||||
obj.Label = name
|
||||
_PVPlantPlacement(obj)
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
_ViewProviderPVPlantPlacement(obj.ViewObject)
|
||||
if baseobj:
|
||||
baseobj.ViewObject.hide()
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
class _CommandPVPlantPlacement:
|
||||
"the Arch Schedule command definition"
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Placement',
|
||||
'Accel': "P, S",
|
||||
'MenuText': QT_TRANSLATE_NOOP("Placement", "Placement"),
|
||||
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Crear un campo fotovoltaico")}
|
||||
|
||||
def Activated(self):
|
||||
taskd = _PVPlantPlacementTaskPanel()
|
||||
FreeCADGui.Control.showDialog(taskd)
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class _PVPlantPlacement(ArchComponent.Component):
|
||||
"the PVPlantPlacement object"
|
||||
|
||||
def __init__(self, obj):
|
||||
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
self.setProperties(obj)
|
||||
# Does a IfcType exist?
|
||||
# obj.IfcType = "Fence"
|
||||
obj.MoveWithHost = False
|
||||
|
||||
def setProperties(self, obj):
|
||||
ArchComponent.Component.setProperties(self, obj)
|
||||
|
||||
pl = obj.PropertiesList
|
||||
|
||||
if not "Section" in pl:
|
||||
obj.addProperty("App::PropertyLink", "Land", "Placement", QT_TRANSLATE_NOOP(
|
||||
"App::Property", "A single section of the fence"))
|
||||
|
||||
if not "Post" in pl:
|
||||
obj.addProperty("App::PropertyLink", "Structure", "Placement", QT_TRANSLATE_NOOP(
|
||||
"App::Property", "A single fence post"))
|
||||
|
||||
if not "Path" in pl:
|
||||
obj.addProperty("App::PropertyLink", "Path", "Placement", QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The Path the fence should follow"))
|
||||
|
||||
if not "NumberOfSections" in pl:
|
||||
obj.addProperty("App::PropertyInteger", "NumberOfSections", "Count", QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The number of sections the fence is built of"))
|
||||
obj.setEditorMode("NumberOfSections", 1)
|
||||
|
||||
if not "NumberOfPosts" in pl:
|
||||
obj.addProperty("App::PropertyInteger", "NumberOfPosts", "Count", QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The number of posts used to build the fence"))
|
||||
obj.setEditorMode("NumberOfPosts", 1)
|
||||
|
||||
self.Type = "Fence"
|
||||
|
||||
def execute(self, obj):
|
||||
# fills columns A, B and C of the spreadsheet
|
||||
if not obj.Description:
|
||||
return
|
||||
|
||||
def __getstate__(self):
|
||||
return self.Type
|
||||
|
||||
def __setstate__(self, state):
|
||||
if state:
|
||||
self.Type = state
|
||||
|
||||
|
||||
class _ViewProviderPVPlantPlacement:
|
||||
"A View Provider for PVPlantPlacement"
|
||||
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/Arch_Schedule.svg"
|
||||
|
||||
def attach(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
|
||||
def setEdit(self, vobj, mode):
|
||||
# taskd = _ArchScheduleTaskPanel(vobj.Object)
|
||||
# FreeCADGui.Control.showDialog(taskd)
|
||||
return True
|
||||
|
||||
def doubleClicked(self, vobj):
|
||||
# taskd = _ArchScheduleTaskPanel(vobj.Object)
|
||||
# FreeCADGui.Control.showDialog(taskd)
|
||||
return True
|
||||
|
||||
def unsetEdit(self, vobj, mode):
|
||||
# FreeCADGui.Control.closeDialog()
|
||||
return
|
||||
|
||||
def claimChildren(self):
|
||||
# if hasattr(self,"Object"):
|
||||
# return [self.Object.Result]
|
||||
return None
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
def getDisplayModes(self, vobj):
|
||||
return ["Default"]
|
||||
|
||||
def getDefaultDisplayMode(self):
|
||||
return "Default"
|
||||
|
||||
def setDisplayMode(self, mode):
|
||||
return mode
|
||||
|
||||
|
||||
class _PVPlantPlacementTaskPanel:
|
||||
'''The editmode TaskPanel for Schedules'''
|
||||
|
||||
def __init__(self, obj=None):
|
||||
self.Terrain = None
|
||||
self.Rack = None
|
||||
self.Gap = 200
|
||||
self.Pitch = 4500
|
||||
|
||||
# form:
|
||||
self.form = QtGui.QWidget()
|
||||
self.form.resize(800, 640)
|
||||
self.form.setWindowTitle("Curvas de nivel")
|
||||
self.form.setWindowIcon(QtGui.QIcon(":/icons/Arch_Schedule.svg"))
|
||||
self.grid = QtGui.QGridLayout(self.form)
|
||||
|
||||
# parameters
|
||||
self.labelTerrain = QtGui.QLabel()
|
||||
self.labelTerrain.setText("Terreno:")
|
||||
self.lineTerrain = QtGui.QLineEdit(self.form)
|
||||
self.lineTerrain.setObjectName(_fromUtf8("lineTerrain"))
|
||||
self.lineTerrain.readOnly = True
|
||||
self.grid.addWidget(self.labelTerrain, self.grid.rowCount(), 0, 1, 1)
|
||||
self.grid.addWidget(self.lineTerrain, self.grid.rowCount() - 1, 1, 1, 1)
|
||||
self.buttonAddTerrain = QtGui.QPushButton('Sel')
|
||||
self.grid.addWidget(self.buttonAddTerrain, self.grid.rowCount() - 1, 2, 1, 1)
|
||||
|
||||
self.labelRack = QtGui.QLabel()
|
||||
self.labelRack.setText("Rack:")
|
||||
self.lineRack = QtGui.QLineEdit(self.form)
|
||||
self.lineRack.setObjectName(_fromUtf8("lineRack"))
|
||||
self.lineRack.readOnly = True
|
||||
self.grid.addWidget(self.labelRack, self.grid.rowCount(), 0, 1, 1)
|
||||
self.grid.addWidget(self.lineRack, self.grid.rowCount() - 1, 1, 1, 1)
|
||||
self.buttonAddRack = QtGui.QPushButton('Sel')
|
||||
self.grid.addWidget(self.buttonAddRack, self.grid.rowCount() - 1, 2, 1, 1)
|
||||
|
||||
self.line1 = QtGui.QFrame()
|
||||
self.line1.setFrameShape(QtGui.QFrame.HLine)
|
||||
self.line1.setFrameShadow(QtGui.QFrame.Sunken)
|
||||
self.grid.addWidget(self.line1, self.grid.rowCount(), 0, 1, -1)
|
||||
|
||||
self.labelTypeStructure = QtGui.QLabel()
|
||||
self.labelTypeStructure.setText("Tipo de estructura:")
|
||||
self.valueTypeStructure = QtGui.QComboBox()
|
||||
self.valueTypeStructure.addItems(["Fixed", "Tracker 1 Axis"])
|
||||
self.valueTypeStructure.setCurrentIndex(0)
|
||||
self.grid.addWidget(self.labelTypeStructure, self.grid.rowCount(), 0, 1, 1)
|
||||
self.grid.addWidget(self.valueTypeStructure, self.grid.rowCount() - 1, 1, 1, -1)
|
||||
|
||||
self.labelOrientation = QtGui.QLabel()
|
||||
self.labelOrientation.setText("Orientacion:")
|
||||
self.valueOrientation = QtGui.QComboBox()
|
||||
self.valueOrientation.addItems(["Norte-Sur", "Este-Oeste"])
|
||||
self.valueOrientation.setCurrentIndex(0)
|
||||
self.grid.addWidget(self.labelOrientation, self.grid.rowCount(), 0, 1, 1)
|
||||
self.grid.addWidget(self.valueOrientation, self.grid.rowCount() - 1, 1, 1, -1)
|
||||
|
||||
self.labelGap = QtGui.QLabel()
|
||||
self.labelGap.setText("Espacio entre Columnas:")
|
||||
self.valueGap = FreeCADGui.UiLoader().createWidget("Gui::InputField")
|
||||
self.valueGap.setText(str(self.Gap) + " mm")
|
||||
self.grid.addWidget(self.labelGap, self.grid.rowCount(), 0, 1, 1)
|
||||
self.grid.addWidget(self.valueGap, self.grid.rowCount() - 1, 1, 1, -1)
|
||||
|
||||
self.labelPitch = QtGui.QLabel()
|
||||
self.labelPitch.setText("Separacion entre Filas:")
|
||||
self.valuePitch = FreeCADGui.UiLoader().createWidget("Gui::InputField")
|
||||
self.valuePitch.setText(str(self.Pitch) + " mm")
|
||||
self.grid.addWidget(self.labelPitch, self.grid.rowCount(), 0, 1, 1)
|
||||
self.grid.addWidget(self.valuePitch, self.grid.rowCount() - 1, 1, 1, -1)
|
||||
|
||||
self.labelAlign = QtGui.QLabel()
|
||||
self.labelAlign.setText("Método de alineación:")
|
||||
self.valueAlign = QtGui.QComboBox()
|
||||
self.valueAlign.addItems(["Si", "No"])
|
||||
self.valueAlign.setCurrentIndex(0)
|
||||
self.grid.addWidget(self.labelAlign, self.grid.rowCount(), 0, 1, 1)
|
||||
self.grid.addWidget(self.valueAlign, self.grid.rowCount() - 1, 1, 1, -1)
|
||||
|
||||
self.line2 = QtGui.QFrame()
|
||||
self.line2.setFrameShape(QtGui.QFrame.HLine)
|
||||
self.line2.setFrameShadow(QtGui.QFrame.Sunken)
|
||||
self.grid.addWidget(self.line2, self.grid.rowCount(), 0, 1, -1)
|
||||
|
||||
self.labelSideSlope = QtGui.QLabel()
|
||||
self.labelSideSlope.setText("Maxima inclinacion longitudinal:")
|
||||
self.valueSideSlope = FreeCADGui.UiLoader().createWidget("Gui::InputField")
|
||||
self.valueSideSlope.setText("15")
|
||||
self.grid.addWidget(self.labelSideSlope, self.grid.rowCount(), 0, 1, 1)
|
||||
self.grid.addWidget(self.valueSideSlope, self.grid.rowCount() - 1, 1, 1, -1)
|
||||
|
||||
QtCore.QObject.connect(self.buttonAddTerrain, QtCore.SIGNAL("clicked()"), self.addTerrain)
|
||||
QtCore.QObject.connect(self.buttonAddRack, QtCore.SIGNAL("clicked()"), self.addRack)
|
||||
# QtCore.QObject.connect(self.form.buttonDel, QtCore.SIGNAL("clicked()"), self.remove)
|
||||
# QtCore.QObject.connect(self.form.buttonClear, QtCore.SIGNAL("clicked()"), self.clear)
|
||||
# QtCore.QObject.connect(self.form.buttonSelect, QtCore.SIGNAL("clicked()"), self.select)
|
||||
|
||||
def addTerrain(self):
|
||||
sel = FreeCADGui.Selection.getSelection()
|
||||
if len(sel) > 0:
|
||||
self.Terrain = sel[0]
|
||||
self.lineTerrain.setText(self.Terrain.Label)
|
||||
|
||||
def addRack(self):
|
||||
sel = FreeCADGui.Selection.getSelection()
|
||||
if len(sel) > 0:
|
||||
self.Rack = sel[0]
|
||||
self.lineRack.setText(self.Rack.Label)
|
||||
|
||||
def accept(self):
|
||||
if self.Terrain is not None and self.Rack is not None:
|
||||
self.Gap = FreeCAD.Units.Quantity(self.valueGap.text()).Value
|
||||
self.Pitch = FreeCAD.Units.Quantity(self.valuePitch.text()).Value
|
||||
self.placement()
|
||||
return True
|
||||
|
||||
def placement(self):
|
||||
if self.valueTypeStructure.currentIndex() == 0: # Fixed
|
||||
print("Rack")
|
||||
else:
|
||||
print("Tracker")
|
||||
if self.Rack.Height < self.Rack.Length:
|
||||
print("rotar")
|
||||
aux = self.Rack.Length
|
||||
self.Rack.Length = self.Rack.Height
|
||||
self.Rack.Height = aux
|
||||
|
||||
self.Rack.Placement.Base.x = self.Terrain.Shape.BoundBox.XMin
|
||||
self.Rack.Placement.Base.y = self.Terrain.Shape.BoundBox.YMin
|
||||
|
||||
DistColls = self.Rack.Length.Value + self.Gap
|
||||
DistRows = self.Rack.Height.Value + self.Pitch
|
||||
area = self.Rack.Shape.Faces[0].Area # * 0.999999999
|
||||
|
||||
import Draft
|
||||
rec = Draft.makeRectangle(length=self.Terrain.Shape.BoundBox.XLength, height=self.Rack.Height, face=True,
|
||||
support=None)
|
||||
rec.Placement.Base.x = self.Terrain.Shape.BoundBox.XMin
|
||||
rec.Placement.Base.y = self.Terrain.Shape.BoundBox.YMin
|
||||
|
||||
try:
|
||||
while rec.Shape.BoundBox.YMax <= self.Terrain.Shape.BoundBox.YMax:
|
||||
common = self.Terrain.Shape.common(rec.Shape)
|
||||
for shape in common.Faces:
|
||||
if shape.Area >= area:
|
||||
if False:
|
||||
minorPoint = FreeCAD.Vector(0, 0, 0)
|
||||
for spoint in shape.OuterWire.Vertexes:
|
||||
if minorPoint.y >= spoint.Point.y:
|
||||
if minorPoint.x >= spoint.x:
|
||||
minorPoint = spoint
|
||||
self.Rack.Placement.Base = spoint
|
||||
else:
|
||||
# más rápido
|
||||
self.Rack.Placement.Base.x = shape.BoundBox.XMin
|
||||
self.Rack.Placement.Base.y = shape.BoundBox.YMin
|
||||
|
||||
while self.Rack.Shape.BoundBox.XMax <= shape.BoundBox.XMax:
|
||||
verts = [v.Point for v in rackClone.Shape.OuterWire.OrderedVertexes]
|
||||
inside = True
|
||||
for vert in verts:
|
||||
if not shape.isInside(vert, 0, True):
|
||||
inside = False
|
||||
break
|
||||
|
||||
if inside:
|
||||
raise
|
||||
else:
|
||||
# ajuste fino hasta encontrar el primer sitio:
|
||||
rackClone.Placement.Base.x += 100 # un metro
|
||||
|
||||
'''old version
|
||||
common1 = shape.common(self.Rack.Shape)
|
||||
if common1.Area >= area:
|
||||
raise
|
||||
else:
|
||||
# ajuste fino hasta encontrar el primer sitio:
|
||||
self.Rack.Placement.Base.x += 500 # un metro
|
||||
del common1
|
||||
'''
|
||||
# ajuste fino hasta encontrar el primer sitio:
|
||||
rec.Placement.Base.y += 100
|
||||
del common
|
||||
except:
|
||||
pass
|
||||
#print("Found")
|
||||
|
||||
FreeCAD.ActiveDocument.removeObject(rec.Name)
|
||||
|
||||
from datetime import datetime
|
||||
starttime = datetime.now()
|
||||
|
||||
if self.valueOrientation.currentIndex() == 0:
|
||||
# Código para crear filas:
|
||||
self.Rack.Placement.Base.x = self.Terrain.Shape.BoundBox.XMin
|
||||
i = 1
|
||||
yy = self.Rack.Placement.Base.y
|
||||
while yy < self.Terrain.Shape.BoundBox.YMax:
|
||||
CreateRow1(self.Rack.Placement.Base.x, yy, self.Rack, self.Terrain, DistColls, area, i)
|
||||
i += 1
|
||||
yy += DistRows
|
||||
elif self.valueOrientation.currentIndex() == 2:
|
||||
# Código para crear columnas:
|
||||
while self.Rack.Placement.Base.x > self.Terrain.Shape.BoundBox.XMin:
|
||||
self.Rack.Placement.Base.x -= DistColls
|
||||
else:
|
||||
xx = self.Rack.Placement.Base.x
|
||||
while xx < self.Terrain.Shape.BoundBox.XMax:
|
||||
CreateGrid(xx, self.Rack.Placement.Base.y, self.Rack, self.Terrain, DistRows, area)
|
||||
xx += DistColls
|
||||
|
||||
FreeCAD.activeDocument().recompute()
|
||||
print("Everything OK (", datetime.now() - starttime, ")")
|
||||
|
||||
|
||||
# Alinear solo filas. las columnas donde se pueda
|
||||
def CreateRow(XX, YY, rack, land, gap, area, rowNumber):
|
||||
import Draft
|
||||
rackClone = Draft.makeRectangle(length=rack.Length, height=rack.Height, face=True, support=None)
|
||||
rackClone.Label = 'rackClone{a}'.format(a=rowNumber)
|
||||
rackClone.Placement.Base.x = XX
|
||||
rackClone.Placement.Base.y = YY
|
||||
|
||||
rec = Draft.makeRectangle(length=land.Shape.BoundBox.XLength, height=rack.Height, face=True, support=None)
|
||||
rec.Placement.Base.x = land.Shape.BoundBox.XMin
|
||||
rec.Placement.Base.y = YY
|
||||
FreeCAD.activeDocument().recompute()
|
||||
|
||||
common = land.Shape.common(rec.Shape)
|
||||
for shape in common.Faces:
|
||||
if shape.Area >= area:
|
||||
rackClone.Placement.Base.x = shape.BoundBox.XMin
|
||||
rackClone.Placement.Base.y = shape.BoundBox.YMin
|
||||
while rackClone.Shape.BoundBox.XMax <= shape.BoundBox.XMax:
|
||||
common1 = shape.common(rackClone.Shape)
|
||||
if common1.Area >= area:
|
||||
tmp = Draft.makeRectangle(length=rack.Length, height=rack.Height, placement=rackClone.Placement,
|
||||
face=True, support=None)
|
||||
tmp.Label = 'R{:03}-000'.format(rowNumber)
|
||||
rackClone.Placement.Base.x += gap
|
||||
else:
|
||||
# ajuste fino hasta encontrar el primer sitio:
|
||||
rackClone.Placement.Base.x += 500 # un metro
|
||||
del common1
|
||||
del common
|
||||
FreeCAD.ActiveDocument.removeObject(rackClone.Name)
|
||||
FreeCAD.ActiveDocument.removeObject(rec.Name)
|
||||
|
||||
|
||||
# Alinear solo filas. las columnas donde se pueda
|
||||
def CreateRow1(XX, YY, rack, land, gap, area, rowNumber):
|
||||
import Draft
|
||||
rackClone = Draft.makeRectangle(length=rack.Length, height=rack.Height, face=True, support=None)
|
||||
rackClone.Label = 'rackClone{a}'.format(a=rowNumber)
|
||||
rackClone.Placement.Base.x = XX
|
||||
rackClone.Placement.Base.y = YY
|
||||
|
||||
rec = Draft.makeRectangle(length=land.Shape.BoundBox.XLength, height=rack.Height, face=True, support=None)
|
||||
rec.Placement.Base.x = land.Shape.BoundBox.XMin
|
||||
rec.Placement.Base.y = YY
|
||||
FreeCAD.activeDocument().recompute()
|
||||
|
||||
common = land.Shape.common(rec.Shape)
|
||||
for shape in common.Faces:
|
||||
if shape.Area >= area:
|
||||
if False:
|
||||
minorPoint = FreeCAD.Vector(0, 0, 0)
|
||||
for spoint in shape.OuterWire.Vertexes:
|
||||
if minorPoint.y >= spoint.Point.y:
|
||||
if minorPoint.x >= spoint.x:
|
||||
minorPoint = spoint
|
||||
rackClone.Placement.Base = spoint
|
||||
else:
|
||||
# más rápido
|
||||
rackClone.Placement.Base.x = shape.BoundBox.XMin
|
||||
rackClone.Placement.Base.y = shape.BoundBox.YMin
|
||||
|
||||
while rackClone.Shape.BoundBox.XMax <= shape.BoundBox.XMax:
|
||||
verts = [v.Point for v in rackClone.Shape.OuterWire.OrderedVertexes]
|
||||
inside = True
|
||||
for vert in verts:
|
||||
if not shape.isInside(vert, 0, True):
|
||||
inside = False
|
||||
break
|
||||
if inside:
|
||||
#tmp = rack.Shape.copy()
|
||||
#tmp.Placement = rack.Placement
|
||||
tmp = Draft.makeRectangle(length=rack.Length, height=rack.Height, placement=rackClone.Placement,
|
||||
face=True, support=None)
|
||||
tmp.Label = 'R{:03}-000'.format(rowNumber)
|
||||
|
||||
rackClone.Placement.Base.x += gap
|
||||
else:
|
||||
# ajuste fino hasta encontrar el primer sitio:
|
||||
rackClone.Placement.Base.x += 500 # un metro
|
||||
del common
|
||||
FreeCAD.ActiveDocument.removeObject(rackClone.Name)
|
||||
FreeCAD.ActiveDocument.removeObject(rec.Name)
|
||||
|
||||
|
||||
# Alinear columna y fila (grid perfecta)
|
||||
def CreateGrid(XX, YY, rack, land, gap, area):
|
||||
print("CreateGrid")
|
||||
import Draft
|
||||
rackClone = Draft.makeRectangle(length=rack.Length, height=rack.Height, face=True, support=None)
|
||||
rackClone.Label = 'rackClone{a}'.format(a=XX)
|
||||
rackClone.Placement.Base.x = XX
|
||||
rackClone.Placement.Base.y = YY
|
||||
|
||||
# if False:
|
||||
while rackClone.Shape.BoundBox.YMax < land.Shape.BoundBox.YMax:
|
||||
common = land.Shape.common(rackClone.Shape)
|
||||
|
||||
if common.Area >= area:
|
||||
tmp = Draft.makeRectangle(length=rack.Length, height=rack.Height,
|
||||
placement=rackClone.Placement, face=True, support=None)
|
||||
tmp.Label = 'rackClone{a}'.format(a=XX)
|
||||
rackClone.Placement.Base.y += gap
|
||||
# else:
|
||||
# # ajuste fino hasta encontrar el primer sitio:
|
||||
# rackClone.Placement.Base.y += 1000
|
||||
FreeCAD.ActiveDocument.removeObject(rackClone.Name)
|
||||
|
||||
|
||||
# Alinear solo filas. las columnas donde se pueda
|
||||
def CreateCol(XX, YY, rack, land, gap, area):
|
||||
import Draft
|
||||
rackClone = Draft.makeRectangle(length=rack.Length, height=rack.Height, face=True, support=None)
|
||||
rackClone.Label = 'rackClone{a}'.format(a=XX)
|
||||
rackClone.Placement.Base.x = XX
|
||||
rackClone.Placement.Base.y = YY
|
||||
|
||||
while rackClone.Shape.BoundBox.YMax < land.Shape.BoundBox.YMax:
|
||||
common = land.Shape.common(rackClone.Shape)
|
||||
|
||||
if common.Area >= area:
|
||||
tmp = Draft.makeRectangle(length=rack.Length, height=rack.Height,
|
||||
placement=rackClone.Placement, face=True, support=None)
|
||||
tmp.Label = 'rackClone{a}'.format(a=XX)
|
||||
rackClone.Placement.Base.y += gap
|
||||
else:
|
||||
# ajuste fino hasta encontrar el primer sitio:
|
||||
rackClone.Placement.Base.y += 100
|
||||
|
||||
FreeCAD.ActiveDocument.removeObject(rackClone.Name)
|
||||
|
||||
|
||||
# TODO: Probar a usar hilos:
|
||||
class _CreateCol(threading.Thread):
|
||||
def __init__(self, args=()):
|
||||
super().__init__()
|
||||
self.XX = args[0]
|
||||
self.YY = args[1]
|
||||
self.rack = args[2]
|
||||
self.land = args[3]
|
||||
self.gap = args[4]
|
||||
self.area = args[5]
|
||||
|
||||
def run(self):
|
||||
import Draft
|
||||
# rackClone = Draft.makeRectangle(length=land.Shape.BoundBox.XLength, height=rack.Height,
|
||||
# face=True, support=None)
|
||||
# rackClone = FreeCAD.activeDocument().addObject('Part::Feature')
|
||||
# rackClone.Shape = self.rack.Shape
|
||||
|
||||
rackClone = Draft.makeRectangle(length=self.rack.Length, height=self.rack.Height, face=True, support=None)
|
||||
rackClone.Label = 'rackClone{a}'.format(a=self.XX)
|
||||
rackClone.Placement.Base.x = self.XX
|
||||
rackClone.Placement.Base.y = self.YY
|
||||
|
||||
# if False:
|
||||
while rackClone.Shape.BoundBox.YMax < self.land.Shape.BoundBox.YMax:
|
||||
common = self.land.Shape.common(rackClone.Shape)
|
||||
|
||||
if common.Area >= self.area:
|
||||
rack = Draft.makeRectangle(length=self.rack.Length, height=self.rack.Height,
|
||||
placement=rackClone.Placement, face=True, support=None)
|
||||
rack.Label = 'rackClone{a}'.format(a=self.XX)
|
||||
rackClone.Placement.Base.y += self.gap
|
||||
# else:
|
||||
# # ajuste fino hasta encontrar el primer sitio:
|
||||
# rackClone.Placement.Base.y += 1000
|
||||
|
||||
# FreeCAD.ActiveDocument.removeObject(rackClone.Name)
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('PVPlantPlacement', _CommandPVPlantPlacement())
|
||||
@@ -33,7 +33,7 @@ except AttributeError:
|
||||
import os, math
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
class _TaskPanel:
|
||||
class TaskPanel:
|
||||
def __init__(self, obj = None):
|
||||
self.obj = None
|
||||
self.select = 0
|
||||
@@ -251,7 +251,7 @@ def Open3DTriangle(point_cloud):
|
||||
#p_mesh_crop = mesh.crop(bbox)
|
||||
return mesh
|
||||
|
||||
class _PVPlantCreateTerrainMesh:
|
||||
'''class _PVPlantCreateTerrainMesh:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "surface.svg")),
|
||||
@@ -268,4 +268,4 @@ class _PVPlantCreateTerrainMesh:
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('PVPlantCreateTerrainMesh', _PVPlantCreateTerrainMesh())
|
||||
FreeCADGui.addCommand('PVPlantCreateTerrainMesh', _PVPlantCreateTerrainMesh())'''
|
||||
|
||||
@@ -1,967 +0,0 @@
|
||||
import math
|
||||
|
||||
import FreeCAD
|
||||
import Part
|
||||
import ArchComponent
|
||||
from pivy import coin
|
||||
import numpy as np
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui, os
|
||||
from PySide import QtCore, QtGui
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
else:
|
||||
# \cond
|
||||
def translate(ctxt, txt):
|
||||
return txt
|
||||
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||
return txt
|
||||
# \endcond
|
||||
|
||||
try:
|
||||
_fromUtf8 = QtCore.QString.fromUtf8
|
||||
except AttributeError:
|
||||
def _fromUtf8(s):
|
||||
return s
|
||||
|
||||
import PVPlantResources
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
voltype = ["Fill", "Cut"]
|
||||
|
||||
|
||||
def makeEarthWorksVolume(vtype = 0):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", voltype[vtype])
|
||||
EarthWorksVolume(obj)
|
||||
ViewProviderEarthWorksVolume(obj.ViewObject)
|
||||
return obj
|
||||
|
||||
|
||||
class EarthWorksVolume(ArchComponent.Component):
|
||||
def __init__(self, obj):
|
||||
# Definición de Variables:
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
self.obj = obj
|
||||
self.setProperties(obj)
|
||||
|
||||
def setProperties(self, obj):
|
||||
# Definicion de Propiedades:
|
||||
pl = obj.PropertiesList
|
||||
|
||||
if not ("VolumeType" in pl):
|
||||
obj.addProperty("App::PropertyEnumeration",
|
||||
"VolumeType",
|
||||
"Volume",
|
||||
"Connection").VolumeType = voltype
|
||||
|
||||
if not ("SurfaceSlope" in pl):
|
||||
obj.addProperty("App::PropertyPercent",
|
||||
"SurfaceSlope",
|
||||
"Volume",
|
||||
"Connection").SurfaceSlope = 2
|
||||
|
||||
if not ("VolumeMesh" in pl):
|
||||
obj.addProperty("Mesh::PropertyMeshKernel",
|
||||
"VolumeMesh",
|
||||
"Volume",
|
||||
"Volume")
|
||||
obj.setEditorMode("VolumeMesh", 2)
|
||||
|
||||
if not ("Volume" in pl):
|
||||
obj.addProperty("App::PropertyVolume",
|
||||
"Volume",
|
||||
"Volume",
|
||||
"Volume")
|
||||
obj.setEditorMode("Volume", 1)
|
||||
|
||||
obj.Proxy = self
|
||||
obj.IfcType = "Civil Element"
|
||||
obj.setEditorMode("IfcType", 1)
|
||||
obj.Proxy = self
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
ArchComponent.Component.onDocumentRestored(self, obj)
|
||||
self.setProperties(obj)
|
||||
|
||||
def onChange(self, obj, prop):
|
||||
if prop == "VolumeMesh":
|
||||
if obj.VolumeMesh:
|
||||
obj.VolumeMesh = obj.VolumeMesh.Volume
|
||||
|
||||
def execute(self, obj):
|
||||
''' '''
|
||||
pass
|
||||
|
||||
|
||||
class ViewProviderEarthWorksVolume:
|
||||
"A View Provider for the Pipe object"
|
||||
|
||||
def __init__(self, vobj):
|
||||
''' Set view properties. '''
|
||||
pl = vobj.PropertiesList
|
||||
|
||||
(r, g, b) = (1.0, 0.0, 0.0) if vobj.Object.VolumeType == "Cut" else (0.0, 0.0, 1.0)
|
||||
|
||||
# Triangulation properties.
|
||||
if not "Transparency" in pl:
|
||||
vobj.addProperty("App::PropertyIntegerConstraint",
|
||||
"Transparency", "Surface Style",
|
||||
"Set triangle face transparency")
|
||||
vobj.Transparency = (50, 0, 100, 1)
|
||||
|
||||
if not "ShapeColor" in pl:
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"ShapeColor",
|
||||
"Surface Style",
|
||||
"Set triangle face color")
|
||||
vobj.ShapeColor = (r, g, b, vobj.Transparency / 100)
|
||||
|
||||
if not "ShapeMaterial" in pl:
|
||||
vobj.addProperty("App::PropertyMaterial",
|
||||
"ShapeMaterial", "Surface Style",
|
||||
"Triangle face material")
|
||||
vobj.ShapeMaterial = FreeCAD.Material()
|
||||
|
||||
if not "LineTransparency" in pl:
|
||||
vobj.addProperty("App::PropertyIntegerConstraint",
|
||||
"LineTransparency", "Surface Style",
|
||||
"Set triangle edge transparency")
|
||||
vobj.LineTransparency = (50, 0, 100, 1)
|
||||
|
||||
if not "LineColor" in pl:
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"LineColor", "Surface Style",
|
||||
"Set triangle face color")
|
||||
vobj.LineColor = (0.5, 0.5, 0.5, vobj.LineTransparency / 100)
|
||||
|
||||
'''vobj.addProperty(
|
||||
"App::PropertyMaterial", "LineMaterial", "Surface Style",
|
||||
"Triangle face material").LineMaterial = FreeCAD.Material()
|
||||
|
||||
vobj.addProperty(
|
||||
"App::PropertyFloatConstraint", "LineWidth", "Surface Style",
|
||||
"Set triangle edge line width").LineWidth = (0.0, 1.0, 20.0, 1.0)
|
||||
|
||||
# Boundary properties.
|
||||
vobj.addProperty(
|
||||
"App::PropertyColor", "BoundaryColor", "Boundary Style",
|
||||
"Set boundary contour color").BoundaryColor = (0.0, 0.75, 1.0, 0.0)
|
||||
|
||||
vobj.addProperty(
|
||||
"App::PropertyFloatConstraint", "BoundaryWidth", "Boundary Style",
|
||||
"Set boundary contour line width").BoundaryWidth = (3.0, 1.0, 20.0, 1.0)
|
||||
|
||||
vobj.addProperty(
|
||||
"App::PropertyEnumeration", "BoundaryPattern", "Boundary Style",
|
||||
"Set a line pattern for boundary").BoundaryPattern = [*line_patterns]
|
||||
|
||||
vobj.addProperty(
|
||||
"App::PropertyIntegerConstraint", "PatternScale", "Boundary Style",
|
||||
"Scale the line pattern").PatternScale = (3, 1, 20, 1)
|
||||
|
||||
# Contour properties.
|
||||
vobj.addProperty(
|
||||
"App::PropertyColor", "MajorColor", "Contour Style",
|
||||
"Set major contour color").MajorColor = (1.0, 0.0, 0.0, 0.0)
|
||||
|
||||
vobj.addProperty(
|
||||
"App::PropertyFloatConstraint", "MajorWidth", "Contour Style",
|
||||
"Set major contour line width").MajorWidth = (4.0, 1.0, 20.0, 1.0)
|
||||
|
||||
vobj.addProperty(
|
||||
"App::PropertyColor", "MinorColor", "Contour Style",
|
||||
"Set minor contour color").MinorColor = (1.0, 1.0, 0.0, 0.0)
|
||||
|
||||
vobj.addProperty(
|
||||
"App::PropertyFloatConstraint", "MinorWidth", "Contour Style",
|
||||
"Set major contour line width").MinorWidth = (2.0, 1.0, 20.0, 1.0)
|
||||
'''
|
||||
vobj.Proxy = self
|
||||
vobj.ShapeMaterial.DiffuseColor = vobj.ShapeColor
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
'''
|
||||
Update Object visuals when a view property changed.
|
||||
'''
|
||||
if prop == "ShapeColor" or prop == "Transparency":
|
||||
if hasattr(vobj, "ShapeColor") and hasattr(vobj, "Transparency"):
|
||||
color = vobj.getPropertyByName("ShapeColor")
|
||||
transparency = vobj.getPropertyByName("Transparency")
|
||||
color = (color[0], color[1], color[2], transparency / 100)
|
||||
vobj.ShapeMaterial.DiffuseColor = color
|
||||
|
||||
if prop == "ShapeMaterial":
|
||||
if hasattr(vobj, "ShapeMaterial"):
|
||||
material = vobj.getPropertyByName("ShapeMaterial")
|
||||
self.face_material.diffuseColor.setValue(material.DiffuseColor[:3])
|
||||
self.face_material.transparency = material.DiffuseColor[3]
|
||||
|
||||
if prop == "LineColor" or prop == "LineTransparency":
|
||||
if hasattr(vobj, "LineColor") and hasattr(vobj, "LineTransparency"):
|
||||
color = vobj.getPropertyByName("LineColor")
|
||||
transparency = vobj.getPropertyByName("LineTransparency")
|
||||
color = (color[0], color[1], color[2], transparency / 100)
|
||||
vobj.LineMaterial.DiffuseColor = color
|
||||
|
||||
if prop == "LineMaterial":
|
||||
material = vobj.getPropertyByName(prop)
|
||||
self.edge_material.diffuseColor.setValue(material.DiffuseColor[:3])
|
||||
self.edge_material.transparency = material.DiffuseColor[3]
|
||||
|
||||
if prop == "LineWidth":
|
||||
width = vobj.getPropertyByName(prop)
|
||||
self.edge_style.lineWidth = width
|
||||
|
||||
if prop == "BoundaryColor":
|
||||
color = vobj.getPropertyByName(prop)
|
||||
self.boundary_color.rgb = color[:3]
|
||||
|
||||
if prop == "BoundaryWidth":
|
||||
width = vobj.getPropertyByName(prop)
|
||||
self.boundary_style.lineWidth = width
|
||||
|
||||
if prop == "BoundaryPattern":
|
||||
if hasattr(vobj, "BoundaryPattern"):
|
||||
pattern = vobj.getPropertyByName(prop)
|
||||
self.boundary_style.linePattern = line_patterns[pattern]
|
||||
|
||||
if prop == "PatternScale":
|
||||
if hasattr(vobj, "PatternScale"):
|
||||
scale = vobj.getPropertyByName(prop)
|
||||
self.boundary_style.linePatternScaleFactor = scale
|
||||
|
||||
if prop == "MajorColor":
|
||||
color = vobj.getPropertyByName(prop)
|
||||
self.major_color.rgb = color[:3]
|
||||
|
||||
if prop == "MajorWidth":
|
||||
width = vobj.getPropertyByName(prop)
|
||||
self.major_style.lineWidth = width
|
||||
|
||||
if prop == "MinorColor":
|
||||
color = vobj.getPropertyByName(prop)
|
||||
self.minor_color.rgb = color[:3]
|
||||
|
||||
if prop == "MinorWidth":
|
||||
width = vobj.getPropertyByName(prop)
|
||||
self.minor_style.lineWidth = width
|
||||
|
||||
|
||||
def attach(self, vobj):
|
||||
'''
|
||||
Create Object visuals in 3D view.
|
||||
'''
|
||||
# GeoCoords Node.
|
||||
self.geo_coords = coin.SoGeoCoordinate()
|
||||
|
||||
# Surface features.
|
||||
self.triangles = coin.SoIndexedFaceSet()
|
||||
self.face_material = coin.SoMaterial()
|
||||
self.edge_material = coin.SoMaterial()
|
||||
self.edge_color = coin.SoBaseColor()
|
||||
self.edge_style = coin.SoDrawStyle()
|
||||
self.edge_style.style = coin.SoDrawStyle.LINES
|
||||
|
||||
shape_hints = coin.SoShapeHints()
|
||||
shape_hints.vertex_ordering = coin.SoShapeHints.COUNTERCLOCKWISE
|
||||
mat_binding = coin.SoMaterialBinding()
|
||||
mat_binding.value = coin.SoMaterialBinding.PER_FACE
|
||||
offset = coin.SoPolygonOffset()
|
||||
offset.styles = coin.SoPolygonOffset.LINES
|
||||
offset.factor = -2.0
|
||||
|
||||
# Boundary features.
|
||||
self.boundary_color = coin.SoBaseColor()
|
||||
self.boundary_coords = coin.SoGeoCoordinate()
|
||||
self.boundary_lines = coin.SoLineSet()
|
||||
self.boundary_style = coin.SoDrawStyle()
|
||||
self.boundary_style.style = coin.SoDrawStyle.LINES
|
||||
|
||||
# Boundary root.
|
||||
boundaries = coin.SoType.fromName('SoFCSelection').createInstance()
|
||||
boundaries.style = 'EMISSIVE_DIFFUSE'
|
||||
boundaries.addChild(self.boundary_color)
|
||||
boundaries.addChild(self.boundary_style)
|
||||
boundaries.addChild(self.boundary_coords)
|
||||
boundaries.addChild(self.boundary_lines)
|
||||
|
||||
# Major Contour features.
|
||||
self.major_color = coin.SoBaseColor()
|
||||
self.major_coords = coin.SoGeoCoordinate()
|
||||
self.major_lines = coin.SoLineSet()
|
||||
self.major_style = coin.SoDrawStyle()
|
||||
self.major_style.style = coin.SoDrawStyle.LINES
|
||||
|
||||
# Major Contour root.
|
||||
major_contours = coin.SoSeparator()
|
||||
major_contours.addChild(self.major_color)
|
||||
major_contours.addChild(self.major_style)
|
||||
major_contours.addChild(self.major_coords)
|
||||
major_contours.addChild(self.major_lines)
|
||||
|
||||
# Minor Contour features.
|
||||
self.minor_color = coin.SoBaseColor()
|
||||
self.minor_coords = coin.SoGeoCoordinate()
|
||||
self.minor_lines = coin.SoLineSet()
|
||||
self.minor_style = coin.SoDrawStyle()
|
||||
self.minor_style.style = coin.SoDrawStyle.LINES
|
||||
|
||||
# Minor Contour root.
|
||||
minor_contours = coin.SoSeparator()
|
||||
minor_contours.addChild(self.minor_color)
|
||||
minor_contours.addChild(self.minor_style)
|
||||
minor_contours.addChild(self.minor_coords)
|
||||
minor_contours.addChild(self.minor_lines)
|
||||
|
||||
# Highlight for selection.
|
||||
highlight = coin.SoType.fromName('SoFCSelection').createInstance()
|
||||
highlight.style = 'EMISSIVE_DIFFUSE'
|
||||
highlight.addChild(shape_hints)
|
||||
highlight.addChild(mat_binding)
|
||||
highlight.addChild(self.geo_coords)
|
||||
highlight.addChild(self.triangles)
|
||||
highlight.addChild(boundaries)
|
||||
|
||||
# Face root.
|
||||
face = coin.SoSeparator()
|
||||
face.addChild(self.face_material)
|
||||
face.addChild(highlight)
|
||||
|
||||
# Edge root.
|
||||
edge = coin.SoSeparator()
|
||||
edge.addChild(self.edge_material)
|
||||
edge.addChild(self.edge_style)
|
||||
edge.addChild(highlight)
|
||||
|
||||
# Surface root.
|
||||
surface_root = coin.SoSeparator()
|
||||
surface_root.addChild(face)
|
||||
surface_root.addChild(offset)
|
||||
surface_root.addChild(edge)
|
||||
surface_root.addChild(major_contours)
|
||||
surface_root.addChild(minor_contours)
|
||||
vobj.addDisplayMode(surface_root, "Surface")
|
||||
|
||||
# Boundary root.
|
||||
boundary_root = coin.SoSeparator()
|
||||
boundary_root.addChild(boundaries)
|
||||
vobj.addDisplayMode(boundary_root, "Boundary")
|
||||
|
||||
# Elevation/Shaded root.
|
||||
shaded_root = coin.SoSeparator()
|
||||
shaded_root.addChild(face)
|
||||
vobj.addDisplayMode(shaded_root, "Elevation")
|
||||
vobj.addDisplayMode(shaded_root, "Slope")
|
||||
vobj.addDisplayMode(shaded_root, "Shaded")
|
||||
|
||||
# Flat Lines root.
|
||||
flatlines_root = coin.SoSeparator()
|
||||
flatlines_root.addChild(face)
|
||||
flatlines_root.addChild(offset)
|
||||
flatlines_root.addChild(edge)
|
||||
vobj.addDisplayMode(flatlines_root, "Flat Lines")
|
||||
|
||||
# Wireframe root.
|
||||
wireframe_root = coin.SoSeparator()
|
||||
wireframe_root.addChild(edge)
|
||||
wireframe_root.addChild(major_contours)
|
||||
wireframe_root.addChild(minor_contours)
|
||||
vobj.addDisplayMode(wireframe_root, "Wireframe")
|
||||
|
||||
# Take features from properties.
|
||||
self.onChanged(vobj, "ShapeColor")
|
||||
self.onChanged(vobj, "LineColor")
|
||||
self.onChanged(vobj, "LineWidth")
|
||||
'''self.onChanged(vobj, "BoundaryColor")
|
||||
self.onChanged(vobj, "BoundaryWidth")
|
||||
self.onChanged(vobj, "BoundaryPattern")
|
||||
self.onChanged(vobj, "PatternScale")
|
||||
self.onChanged(vobj, "MajorColor")
|
||||
self.onChanged(vobj, "MajorWidth")
|
||||
self.onChanged(vobj, "MinorColor")
|
||||
self.onChanged(vobj, "MinorWidth")'''
|
||||
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
'''
|
||||
Update Object visuals when a data property changed.
|
||||
'''
|
||||
|
||||
# Set System.
|
||||
geo_system = ["UTM", FreeCAD.ActiveDocument.Site.UtmZone, "FLAT"]
|
||||
self.geo_coords.geoSystem.setValues(geo_system)
|
||||
self.boundary_coords.geoSystem.setValues(geo_system)
|
||||
self.major_coords.geoSystem.setValues(geo_system)
|
||||
self.minor_coords.geoSystem.setValues(geo_system)
|
||||
|
||||
if prop == "VolumeMesh":
|
||||
mesh = obj.VolumeMesh
|
||||
copy_mesh = mesh.copy()
|
||||
#copy_mesh.Placement.move(origin.Origin)
|
||||
|
||||
triangles = []
|
||||
for i in copy_mesh.Topology[1]:
|
||||
triangles.extend(list(i))
|
||||
triangles.append(-1)
|
||||
|
||||
self.geo_coords.point.values = copy_mesh.Topology[0]
|
||||
self.triangles.coordIndex.values = triangles
|
||||
del copy_mesh
|
||||
|
||||
'''if prop == "ContourShapes":
|
||||
contour_shape = obj.getPropertyByName(prop)
|
||||
|
||||
if contour_shape.SubShapes:
|
||||
major_shape = contour_shape.SubShapes[0]
|
||||
points, vertices = self.wire_view(major_shape, origin.Origin)
|
||||
|
||||
self.major_coords.point.values = points
|
||||
self.major_lines.numVertices.values = vertices
|
||||
|
||||
minor_shape = contour_shape.SubShapes[1]
|
||||
points, vertices = self.wire_view(minor_shape, origin.Origin)
|
||||
|
||||
self.minor_coords.point.values = points
|
||||
self.minor_lines.numVertices.values = vertices
|
||||
|
||||
if prop == "BoundaryShapes":
|
||||
boundary_shape = obj.getPropertyByName(prop)
|
||||
points, vertices = self.wire_view(boundary_shape, origin.Origin, True)
|
||||
|
||||
self.boundary_coords.point.values = points
|
||||
self.boundary_lines.numVertices.values = vertices
|
||||
|
||||
if prop == "AnalysisType" or prop == "Ranges":
|
||||
analysis_type = obj.getPropertyByName("AnalysisType")
|
||||
ranges = obj.getPropertyByName("Ranges")
|
||||
|
||||
if analysis_type == "Default":
|
||||
if hasattr(obj.ViewObject, "ShapeMaterial"):
|
||||
material = obj.ViewObject.ShapeMaterial
|
||||
self.face_material.diffuseColor = material.DiffuseColor[:3]
|
||||
|
||||
if analysis_type == "Elevation":
|
||||
colorlist = self.elevation_analysis(obj.Mesh, ranges)
|
||||
self.face_material.diffuseColor.setValues(0, len(colorlist), colorlist)
|
||||
|
||||
elif analysis_type == "Slope":
|
||||
colorlist = self.slope_analysis(obj.Mesh, ranges)
|
||||
self.face_material.diffuseColor.setValues(0, len(colorlist), colorlist)
|
||||
'''
|
||||
def getIcon(self):
|
||||
""" Return the path to the appropriate icon. """
|
||||
return str(os.path.join(DirIcons, "solar-fixed.svg"))
|
||||
|
||||
def getDisplayModes(self, vobj):
|
||||
'''
|
||||
Return a list of display modes.
|
||||
'''
|
||||
modes = ["Surface", "Boundary", "Flat Lines", "Shaded", "Wireframe"]
|
||||
|
||||
return modes
|
||||
|
||||
def getDefaultDisplayMode(self):
|
||||
'''
|
||||
Return the name of the default display mode.
|
||||
'''
|
||||
|
||||
return "Surface"
|
||||
|
||||
def setDisplayMode(self, mode):
|
||||
'''
|
||||
Map the display mode defined in attach with
|
||||
those defined in getDisplayModes.
|
||||
'''
|
||||
return mode
|
||||
|
||||
def __getstate__(self):
|
||||
"""
|
||||
Save variables to file.
|
||||
"""
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""
|
||||
Get variables from file.
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
class _EarthWorksTaskPanel:
|
||||
def __init__(self):
|
||||
self.To = None
|
||||
|
||||
# self.form:
|
||||
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "PVPlantEarthworks.ui"))
|
||||
self.form.setWindowIcon(QtGui.QIcon(os.path.join(PVPlantResources.DirIcons, "convert.svg")))
|
||||
|
||||
def accept(self):
|
||||
from datetime import datetime
|
||||
starttime = datetime.now()
|
||||
|
||||
import MeshPart as mp
|
||||
land = FreeCAD.ActiveDocument.Terrain.Mesh.copy()
|
||||
frames = []
|
||||
for obj in FreeCADGui.Selection.getSelection():
|
||||
if hasattr(obj, "Proxy"):
|
||||
if obj.Proxy.Type == "Tracker":
|
||||
if not (obj in frames):
|
||||
frames.append(obj)
|
||||
elif obj.Proxy.Type == "FrameArea":
|
||||
for fr in obj.Frames:
|
||||
if not (fr in frames):
|
||||
frames.append(fr)
|
||||
if len(frames) == 0:
|
||||
return False
|
||||
|
||||
FreeCAD.ActiveDocument.openTransaction("Calcular movimiento de tierras")
|
||||
def calculateEarthWorks(line, extreme=False):
|
||||
pts = []
|
||||
pts1 = []
|
||||
line1 = line.copy()
|
||||
angles = line.Placement.Rotation.toEulerAngles("XYZ")
|
||||
line1.Placement.Rotation.setEulerAngles("XYZ", 0, 0, angles[2])
|
||||
line1.Placement.Base.z = 0
|
||||
pro = mp.projectShapeOnMesh(line1, land, FreeCAD.Vector(0, 0, 1))
|
||||
flat = []
|
||||
for points in pro:
|
||||
flat.extend(points)
|
||||
pro = Part.makePolygon(flat)
|
||||
points = pro.discretize(Distance=500)
|
||||
|
||||
for point in points:
|
||||
ver = Part.Vertex(point)
|
||||
dist = ver.distToShape(line)
|
||||
linepoint = dist[1][0][1]
|
||||
|
||||
if not extreme:
|
||||
if self.form.groupTolerances.isChecked():
|
||||
if linepoint.z > point.z:
|
||||
if linepoint.sub(point).Length > self.form.editToleranceCut.value():
|
||||
pts.append(linepoint)
|
||||
elif linepoint.z < point.z:
|
||||
if linepoint.sub(point).Length > self.form.editToleranceFill.value():
|
||||
pts1.append(linepoint)
|
||||
else:
|
||||
if linepoint.z > point.z:
|
||||
pts.append(linepoint)
|
||||
elif linepoint.z < point.z:
|
||||
pts1.append(linepoint)
|
||||
#pts.append(linepoint)
|
||||
else:
|
||||
if linepoint.z > point.z:
|
||||
if linepoint.sub(point).Length > 200:
|
||||
pts.append(linepoint)
|
||||
|
||||
return pts, pts1
|
||||
|
||||
tools = [[],[]]
|
||||
ver = 2
|
||||
if ver == 0:
|
||||
frames = sorted(frames, key=lambda x: (x.Placement.Base.x, x.Placement.Base.y))
|
||||
for frame in frames:
|
||||
length = frame.Setup.Length.Value / 2
|
||||
p1 = FreeCAD.Vector(-length, 0, 0)
|
||||
p2 = FreeCAD.Vector(length, 0, 0)
|
||||
line = Part.LineSegment(p1, p2).toShape()
|
||||
line.Placement = frame.Placement.copy()
|
||||
line.Placement.Base.x = frame.Shape.BoundBox.XMin
|
||||
step = (frame.Shape.BoundBox.XMax - frame.Shape.BoundBox.XMin) / 2
|
||||
for n in range(3):
|
||||
ret = calculateEarthWorks(line, n % 2)
|
||||
tools[0].extend(ret[0])
|
||||
tools[1].extend(ret[1])
|
||||
line.Placement.Base.x += step
|
||||
elif ver == 1:
|
||||
from PVPlantPlacement import getCols
|
||||
columns = getCols(frames)
|
||||
|
||||
'''colelements = set()
|
||||
rowelements = set()
|
||||
for groups in columns:
|
||||
for group in groups:
|
||||
for frame in group:
|
||||
colelements.add(frame.Placement.Base.x)
|
||||
rowelements.add(frame.Placement.Base.y)
|
||||
colelements = sorted(colelements)
|
||||
rowelements = sorted(rowelements, reverse=True)
|
||||
print("Cols: ", len(colelements), " - ", colelements)
|
||||
print("Rows: ", len(rowelements), " - ", rowelements)
|
||||
|
||||
a = []
|
||||
colnum = len(colelements)
|
||||
for r in range(len(rowelements)):
|
||||
a.append([None] * colnum)
|
||||
mat = np.array(a, dtype=object)
|
||||
for groups in columns:
|
||||
for group in groups:
|
||||
for frame in group:
|
||||
colidx = colelements.index(frame.Placement.Base.x)
|
||||
rowidx = rowelements.index(frame.Placement.Base.y)
|
||||
mat[rowidx][colidx] = frame
|
||||
print(mat)
|
||||
return'''
|
||||
|
||||
for groups in columns:
|
||||
for group in groups:
|
||||
first = group[0]
|
||||
last = group[-1]
|
||||
for frame in group:
|
||||
length = frame.Setup.Length.Value / 2
|
||||
p1 = FreeCAD.Vector(-(length + (self.form.editOffset.value() if frame == first else -1000)),
|
||||
0, 0)
|
||||
p2 = FreeCAD.Vector(length + (self.form.editOffset.value() if frame == last else -1000),
|
||||
0, 0)
|
||||
line = Part.LineSegment(p1, p2).toShape()
|
||||
line.Placement = frame.Placement.copy()
|
||||
line.Placement.Base.x = frame.Shape.BoundBox.XMin
|
||||
step = (frame.Shape.BoundBox.XMax - frame.Shape.BoundBox.XMin) / 2
|
||||
for n in range(3):
|
||||
ret = calculateEarthWorks(line, n % 2 == 1)
|
||||
tools[0].extend(ret[0])
|
||||
tools[1].extend(ret[1])
|
||||
line.Placement.Base.x += step
|
||||
elif ver == 2:
|
||||
print("versión 2")
|
||||
import PVPlantPlacement
|
||||
rows, columns = PVPlantPlacement.getRows(frames)
|
||||
if (rows is None) or (columns is None):
|
||||
print("Nada que procesar")
|
||||
return False
|
||||
tools = []
|
||||
lofts = []
|
||||
for group in rows:
|
||||
lines = []
|
||||
cont = 0
|
||||
while cont < len(group):
|
||||
aw = 0
|
||||
if cont > 0:
|
||||
p0 = FreeCAD.Vector(group[cont - 1].Placement.Base)
|
||||
p1 = FreeCAD.Vector(group[cont].Placement.Base)
|
||||
aw = getAngle(p0, p1)
|
||||
|
||||
ae = 0
|
||||
if cont < (len(group) - 1):
|
||||
p1 = FreeCAD.Vector(group[cont].Placement.Base)
|
||||
p2 = FreeCAD.Vector(group[cont + 1].Placement.Base)
|
||||
ae = getAngle(p1, p2)
|
||||
|
||||
lng = int(group[cont].Setup.Length / 2)
|
||||
wdt = int(group[cont].Setup.Width / 2)
|
||||
line = Part.LineSegment(FreeCAD.Vector(-lng, 0, 0),
|
||||
FreeCAD.Vector(lng, 0, 0)).toShape()
|
||||
|
||||
line = Part.LineSegment(FreeCAD.Vector(group[cont].Setup.Shape.SubShapes[1].SubShapes[0].SubShapes[0].Placement.Base.x, 0, 0),
|
||||
FreeCAD.Vector(group[cont].Setup.Shape.SubShapes[1].SubShapes[0].SubShapes[-1].Placement.Base.x, 0, 0)).toShape()
|
||||
|
||||
anf = (aw + ae) / 2
|
||||
if anf > FreeCAD.ActiveDocument.MaximumWestEastSlope.Value:
|
||||
anf = FreeCAD.ActiveDocument.MaximumWestEastSlope.Value
|
||||
zz = wdt * math.sin(math.radians(anf))
|
||||
|
||||
li = line.copy()
|
||||
li.Placement = group[cont].Placement
|
||||
li.Placement.Rotation = group[cont].Placement.Rotation
|
||||
li.Placement.Base.x -= wdt #+ (3000 if cont == 0 else 0))
|
||||
li.Placement.Base.z -= zz
|
||||
lines.append(li)
|
||||
|
||||
ld = line.copy()
|
||||
ld.Placement = group[cont].Placement
|
||||
ld.Placement.Rotation = group[cont].Placement.Rotation
|
||||
ld.Placement.Base.x += wdt #+ (3000 if cont == len(group) - 1 else 0))
|
||||
ld.Placement.Base.z += zz
|
||||
lines.append(ld)
|
||||
tools.append([group[cont], li, ld])
|
||||
cont += 1
|
||||
loft = Part.makeLoft(lines, False, True, False)
|
||||
lofts.append(loft)
|
||||
|
||||
for group in rows:
|
||||
lines = []
|
||||
for frame in group:
|
||||
col, idx = searchFrameInColumns(frame, columns)
|
||||
tool = searchTool(frame, tools)
|
||||
if idx == 0:
|
||||
''' '''
|
||||
|
||||
if idx == (len(col) - 1):
|
||||
''' '''
|
||||
|
||||
if (idx + 1) < len(col):
|
||||
frame1 = col[idx + 1]
|
||||
tool1 = searchTool(frame1, tools)
|
||||
line = Part.LineSegment(tool[1].Vertexes[1].Point, tool1[1].Vertexes[0].Point).toShape()
|
||||
lines.append(line)
|
||||
line = Part.LineSegment(tool[2].Vertexes[1].Point, tool1[2].Vertexes[0].Point).toShape()
|
||||
lines.append(line)
|
||||
|
||||
if len(lines) > 0:
|
||||
loft = Part.makeLoft(lines, False, True, False)
|
||||
lofts.append(loft)
|
||||
|
||||
faces = []
|
||||
for loft in lofts:
|
||||
faces.extend(loft.Faces)
|
||||
sh = Part.makeShell(faces)
|
||||
import Utils.PVPlantUtils as utils
|
||||
import Mesh
|
||||
pro = utils.getProjected(sh)
|
||||
pro = utils.simplifyWire(pro)
|
||||
#pro = pro.makeOffset2D(20000, 2, False, False, True)
|
||||
Part.show(sh, "loft")
|
||||
Part.show(pro, "pro")
|
||||
pts = [ver.Point for ver in pro.Vertexes]
|
||||
'''if pts[0] != pts[-1]:
|
||||
pts.append(pts[0])'''
|
||||
land.trim(pts, 1)
|
||||
tmp = []
|
||||
for face in sh.Faces:
|
||||
wire = face.Wires[0].copy()
|
||||
pl = wire.Placement.Base
|
||||
wire.Placement.Base = wire.Placement.Base - pl
|
||||
wire = wire.scale(2)
|
||||
wire.Placement.Base = wire.Placement.Base + pl
|
||||
#wire = wire.makeOffset2D(10000, 0, False, False, True)
|
||||
wire.Placement.Base.z = wire.Placement.Base.z - 10000
|
||||
face1 = Part.makeLoft([face.Wires[0], wire], True, True, False)
|
||||
|
||||
|
||||
Part.show(face1, "tool")
|
||||
#tmp.append(face.extrude(FreeCAD.Vector(0, 0, -10000)))
|
||||
#Part.show(tmp[-1], "face-extrude")
|
||||
sh = sh.extrude(FreeCAD.Vector(0, 0, -10000))
|
||||
sh = Part.Solid(sh)
|
||||
Part.show(sh)
|
||||
import MeshPart as mp
|
||||
msh = mp.meshFromShape(Shape=sh) # , MaxLength=1)
|
||||
# msh = msh.smooth("Laplace", 3)
|
||||
Mesh.show(msh, "tool")
|
||||
Mesh.show(land, "trim")
|
||||
'''inner = msh.inner(land)
|
||||
Mesh.show(inner)
|
||||
outer = msh.inner(land)
|
||||
Mesh.show(outer)'''
|
||||
'''intersec = land.section(msh, MinDist=0.01)
|
||||
import Draft
|
||||
for sec in intersec:
|
||||
Draft.makeWire(sec)'''
|
||||
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
self.closeForm()
|
||||
return True
|
||||
|
||||
import MeshTools.Triangulation as TriangulateMesh
|
||||
import MeshTools.MeshGetBoundary as mgb
|
||||
import Mesh
|
||||
|
||||
for ind, points in enumerate(tools):
|
||||
mesh = TriangulateMesh.Triangulate(points, MaxlengthLE=3000, MaxAngleLE=math.radians(100))
|
||||
if mesh:
|
||||
for mesh in mesh.getSeparateComponents():
|
||||
boundary = mgb.get_boundary(mesh)
|
||||
Part.show(boundary)
|
||||
'''if self.form.editOffset.value() != 0:
|
||||
import Utils.PVPlantUtils as utils
|
||||
pro = utils.getProjected(boundary)
|
||||
pro = pro.makeOffset2D(self.form.editOffset.value(), 0, False, False, True)
|
||||
# TODO: paso intermedio de restar las areas prohibidas
|
||||
pro = mp.projectShapeOnMesh(pro, land, FreeCAD.Vector(0, 0, 1))
|
||||
cnt = 0
|
||||
for lp in pro:
|
||||
cnt += len(lp)
|
||||
# points.extend(boundary.Wires[0].discretize(Number=cnt))
|
||||
points = boundary.Wires[0].discretize(Distance=cnt)
|
||||
for lp in pro:
|
||||
points.extend(lp)
|
||||
mesh1 = TriangulateMesh.Triangulate(points, MaxlengthLE=5000) # , MaxAngleLE=math.pi / 1.334)
|
||||
import Mesh
|
||||
Mesh.show(mesh1)
|
||||
boundary = Part.makeCompound([])
|
||||
for section in pro:
|
||||
if len(section) > 0:
|
||||
try:
|
||||
boundary.add(Part.makePolygon(section))
|
||||
except:
|
||||
pass
|
||||
Part.show(boundary)'''
|
||||
#mesh.smooth("Laplace", 3)
|
||||
#Mesh.show(mesh)
|
||||
#Part.show(boundary)
|
||||
vol = makeEarthWorksVolume(ind)
|
||||
vol.VolumeMesh = mesh.copy()
|
||||
if ind == 0:
|
||||
''' put inside fills group '''
|
||||
else:
|
||||
''' put inside fills group '''
|
||||
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
self.closeForm()
|
||||
return True
|
||||
|
||||
def reject(self):
|
||||
self.closeForm()
|
||||
return True
|
||||
|
||||
def closeForm(self):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
|
||||
|
||||
def getAngle(vec1, vec2):
|
||||
dX = vec2.x - vec1.x
|
||||
dZ = vec2.z - vec1.z
|
||||
return math.degrees(math.atan2(float(dZ), float(dX)))
|
||||
|
||||
|
||||
def searchFrameInColumns(obj, columns):
|
||||
for colidx, col in enumerate(columns):
|
||||
for group in col:
|
||||
if obj in group:
|
||||
return group, group.index(obj) #groupidx
|
||||
|
||||
|
||||
def searchTool(obj, tools):
|
||||
for tool in tools:
|
||||
if obj in tool:
|
||||
return tool
|
||||
|
||||
|
||||
class _CommandCalculateEarthworks:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "pico.svg")),
|
||||
'Accel': "C, E",
|
||||
'MenuText': QT_TRANSLATE_NOOP("Placement", "Movimiento de tierras"),
|
||||
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Calcular el movimiento de tierras")}
|
||||
|
||||
def Activated(self):
|
||||
TaskPanel = _EarthWorksTaskPanel()
|
||||
FreeCADGui.Control.showDialog(TaskPanel)
|
||||
|
||||
def IsActive(self):
|
||||
active = not (FreeCAD.ActiveDocument is None)
|
||||
if not (FreeCAD.ActiveDocument.getObject("Terrain") is None):
|
||||
active = active and not (FreeCAD.ActiveDocument.getObject("Terrain").Mesh is None)
|
||||
return active
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('PVPlantEarthworks', _CommandCalculateEarthworks())
|
||||
|
||||
|
||||
def accept():
|
||||
import MeshPart as mp
|
||||
land = FreeCAD.ActiveDocument.Terrain.Mesh
|
||||
frames = []
|
||||
for obj in FreeCADGui.Selection.getSelection():
|
||||
if hasattr(obj, "Proxy"):
|
||||
if obj.Proxy.Type == "Tracker":
|
||||
if not (obj in frames):
|
||||
frames.append(obj)
|
||||
elif obj.Proxy.Type == "FrameArea":
|
||||
for fr in obj.Frames:
|
||||
if not (fr in frames):
|
||||
frames.append(fr)
|
||||
if len(frames) == 0:
|
||||
return False
|
||||
|
||||
FreeCAD.ActiveDocument.openTransaction("Calcular movimiento de tierras")
|
||||
import PVPlantPlacement
|
||||
rows, columns = PVPlantPlacement.getRows(frames)
|
||||
if (rows is None) or (columns is None):
|
||||
print("Nada que procesar")
|
||||
return False
|
||||
tools = []
|
||||
|
||||
for group in rows:
|
||||
lines = []
|
||||
cont = 0
|
||||
while cont < len(group):
|
||||
aw = 0
|
||||
if cont > 0:
|
||||
p0 = FreeCAD.Vector(group[cont - 1].Placement.Base)
|
||||
p1 = FreeCAD.Vector(group[cont].Placement.Base)
|
||||
aw = getAngle(p0, p1)
|
||||
|
||||
ae = 0
|
||||
if cont < (len(group) - 1):
|
||||
p1 = FreeCAD.Vector(group[cont].Placement.Base)
|
||||
p2 = FreeCAD.Vector(group[cont + 1].Placement.Base)
|
||||
ae = getAngle(p1, p2)
|
||||
|
||||
lng = int(group[cont].Setup.Length / 2)
|
||||
wdt = int(group[cont].Setup.Width / 2)
|
||||
line = Part.LineSegment(FreeCAD.Vector(-lng, 0, 0),
|
||||
FreeCAD.Vector(lng, 0, 0)).toShape()
|
||||
|
||||
line = Part.LineSegment(FreeCAD.Vector(group[cont].Setup.Shape.SubShapes[1].SubShapes[0].SubShapes[0].Placement.Base.x, 0, 0),
|
||||
FreeCAD.Vector(group[cont].Setup.Shape.SubShapes[1].SubShapes[0].SubShapes[-1].Placement.Base.x, 0, 0)).toShape()
|
||||
|
||||
anf = (aw + ae) / 2
|
||||
if anf > FreeCAD.ActiveDocument.MaximumWestEastSlope.Value:
|
||||
anf = FreeCAD.ActiveDocument.MaximumWestEastSlope.Value
|
||||
zz = wdt * math.sin(math.radians(anf))
|
||||
|
||||
li = line.copy()
|
||||
li.Placement = group[cont].Placement
|
||||
li.Placement.Rotation = group[cont].Placement.Rotation
|
||||
li.Placement.Base.x -= wdt #+ (3000 if cont == 0 else 0))
|
||||
li.Placement.Base.z -= zz
|
||||
lines.append(li)
|
||||
|
||||
ld = line.copy()
|
||||
ld.Placement = group[cont].Placement
|
||||
ld.Placement.Rotation = group[cont].Placement.Rotation
|
||||
ld.Placement.Base.x += wdt #+ (3000 if cont == len(group) - 1 else 0))
|
||||
ld.Placement.Base.z += zz
|
||||
lines.append(ld)
|
||||
tools.append([group[cont], li, ld])
|
||||
cont += 1
|
||||
|
||||
loft = Part.makeLoft(lines, False, True, False)
|
||||
import MeshPart as mp
|
||||
msh = mp.meshFromShape(Shape=loft) #, MaxLength=1)
|
||||
#msh = msh.smooth("Laplace", 3)
|
||||
import Mesh
|
||||
Mesh.show(msh)
|
||||
'''intersec = land.section(msh, MinDist=0.01)
|
||||
import Draft
|
||||
for sec in intersec:
|
||||
Draft.makeWire(sec)'''
|
||||
|
||||
for group in rows:
|
||||
lines = []
|
||||
for frame in group:
|
||||
col, idx = searchFrameInColumns(frame, columns)
|
||||
tool = searchTool(frame, tools)
|
||||
if idx == 0:
|
||||
''' '''
|
||||
if idx == (len(col) - 1):
|
||||
''' '''
|
||||
|
||||
if (idx + 1) < len(col):
|
||||
frame1 = col[idx + 1]
|
||||
tool1 = searchTool(frame1, tools)
|
||||
line = Part.LineSegment(tool[1].Vertexes[1].Point, tool1[1].Vertexes[0].Point).toShape()
|
||||
Part.show(line)
|
||||
lines.append(line)
|
||||
line = Part.LineSegment(tool[2].Vertexes[1].Point, tool1[2].Vertexes[0].Point).toShape()
|
||||
Part.show(line)
|
||||
lines.append(line)
|
||||
|
||||
|
||||
if len(lines) > 0:
|
||||
loft = Part.makeLoft(lines, False, True, False)
|
||||
import MeshPart as mp
|
||||
msh = mp.meshFromShape(Shape=loft) # , MaxLength=1)
|
||||
#msh = msh.smooth("Laplace", 3)
|
||||
import Mesh
|
||||
Mesh.show(msh)
|
||||
intersec = land.section(msh, MinDist=0.01)
|
||||
import Draft
|
||||
for sec in intersec:
|
||||
Draft.makeWire(sec)
|
||||
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
self.closeForm()
|
||||
return True
|
||||
+340
-50
@@ -27,8 +27,7 @@ if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore, QtGui
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
from PySide2.QtWebEngineWidgets import QWebEngineView
|
||||
from PySide2.QtWebChannel import QWebChannel
|
||||
|
||||
import os
|
||||
else:
|
||||
# \cond
|
||||
@@ -47,13 +46,22 @@ class MapWindow(QtGui.QWidget):
|
||||
def __init__(self, WinTitle="MapWindow"):
|
||||
super(MapWindow, self).__init__()
|
||||
self.raise_()
|
||||
self.lat = 0
|
||||
self.lon = 0
|
||||
self.lat = None
|
||||
self.lon = None
|
||||
self.minLat = None
|
||||
self.maxLat = None
|
||||
self.minLon = None
|
||||
self.maxLon = None
|
||||
self.zoom = None
|
||||
self.WinTitle = WinTitle
|
||||
|
||||
self.georeference_coordinates = {'lat': None, 'lon': None}
|
||||
self.setupUi()
|
||||
|
||||
def setupUi(self):
|
||||
# Intentar cargar QtWebEngine (no siempre disponible, ej: FreeCAD flatpak)
|
||||
QWebEngineView, QWebChannel = self._load_webengine()
|
||||
self._webengine_available = QWebEngineView is not None
|
||||
|
||||
self.ui = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantGeoreferencing.ui", self)
|
||||
|
||||
self.resize(1200, 800)
|
||||
@@ -79,36 +87,54 @@ class MapWindow(QtGui.QWidget):
|
||||
self.layout.addWidget(RightWidget)
|
||||
|
||||
# Left Widgets:
|
||||
# -- Search Bar:
|
||||
self.valueSearch = QtGui.QLineEdit(self)
|
||||
self.valueSearch.setPlaceholderText("Search")
|
||||
self.valueSearch.returnPressed.connect(self.onSearch)
|
||||
if self._webengine_available:
|
||||
# -- Search Bar:
|
||||
self.valueSearch = QtGui.QLineEdit(self)
|
||||
self.valueSearch.setPlaceholderText("Search")
|
||||
self.valueSearch.returnPressed.connect(self.onSearch)
|
||||
|
||||
searchbutton = QtGui.QPushButton('Search')
|
||||
searchbutton.setFixedWidth(80)
|
||||
searchbutton.clicked.connect(self.onSearch)
|
||||
searchbutton = QtGui.QPushButton('Search')
|
||||
searchbutton.setFixedWidth(80)
|
||||
searchbutton.clicked.connect(self.onSearch)
|
||||
|
||||
SearchBarLayout = QtGui.QHBoxLayout(self)
|
||||
SearchBarLayout.addWidget(self.valueSearch)
|
||||
SearchBarLayout.addWidget(searchbutton)
|
||||
LeftLayout.addLayout(SearchBarLayout)
|
||||
SearchBarLayout = QtGui.QHBoxLayout(self)
|
||||
SearchBarLayout.addWidget(self.valueSearch)
|
||||
SearchBarLayout.addWidget(searchbutton)
|
||||
LeftLayout.addLayout(SearchBarLayout)
|
||||
|
||||
# -- Webbroser:
|
||||
self.view = QWebEngineView()
|
||||
self.channel = QWebChannel(self.view.page())
|
||||
self.view.page().setWebChannel(self.channel)
|
||||
self.channel.registerObject("MyApp", self)
|
||||
file = os.path.join(DirResources, "webs", "main.html")
|
||||
self.view.page().loadFinished.connect(self.onLoadFinished)
|
||||
self.view.page().load(QtCore.QUrl.fromLocalFile(file))
|
||||
LeftLayout.addWidget(self.view)
|
||||
# self.layout.addWidget(self.view, 1, 0, 1, 3)
|
||||
# -- Web browser:
|
||||
self.view = QWebEngineView()
|
||||
self.channel = QWebChannel(self.view.page())
|
||||
self.view.page().setWebChannel(self.channel)
|
||||
self.channel.registerObject("MyApp", self)
|
||||
file = os.path.join(DirResources, "webs", "main.html")
|
||||
self.view.page().loadFinished.connect(self.onLoadFinished)
|
||||
self.view.page().load(QtCore.QUrl.fromLocalFile(file))
|
||||
LeftLayout.addWidget(self.view)
|
||||
else:
|
||||
# -- Modo manual: entrada de coordenadas sin mapa web
|
||||
self.valueSearch = QtGui.QLineEdit(self)
|
||||
self.valueSearch.setPlaceholderText("Latitud, Longitud (ej: 40.4168, -3.7038)")
|
||||
self.valueSearch.returnPressed.connect(self.onManualCoords)
|
||||
|
||||
searchbutton = QtGui.QPushButton('Ir')
|
||||
searchbutton.setFixedWidth(80)
|
||||
searchbutton.clicked.connect(self.onManualCoords)
|
||||
|
||||
SearchBarLayout = QtGui.QHBoxLayout(self)
|
||||
SearchBarLayout.addWidget(self.valueSearch)
|
||||
SearchBarLayout.addWidget(searchbutton)
|
||||
LeftLayout.addLayout(SearchBarLayout)
|
||||
|
||||
info = QtGui.QLabel("Mapa web no disponible. Introduce coordenadas manualmente.")
|
||||
info.setStyleSheet("color: #888; font-style: italic; padding: 20px;")
|
||||
info.setAlignment(QtCore.Qt.AlignCenter)
|
||||
LeftLayout.addWidget(info)
|
||||
|
||||
# -- Latitud y longitud:
|
||||
self.labelCoordinates = QtGui.QLabel()
|
||||
self.labelCoordinates.setFixedHeight(21)
|
||||
LeftLayout.addWidget(self.labelCoordinates)
|
||||
# self.layout.addWidget(self.labelCoordinates, 2, 0, 1, 3)
|
||||
|
||||
# Right Widgets:
|
||||
labelKMZ = QtGui.QLabel()
|
||||
@@ -132,9 +158,6 @@ class MapWindow(QtGui.QWidget):
|
||||
radio3 = QtGui.QRadioButton("Datos GPS")
|
||||
radio1.setChecked(True)
|
||||
|
||||
# buttonDialog = QtGui.QPushButton('...')
|
||||
# buttonDialog.setEnabled(False)
|
||||
|
||||
vbox = QtGui.QVBoxLayout(self)
|
||||
vbox.addWidget(radio1)
|
||||
vbox.addWidget(radio2)
|
||||
@@ -142,7 +165,12 @@ class MapWindow(QtGui.QWidget):
|
||||
|
||||
self.groupbox.setLayout(vbox)
|
||||
RightLayout.addWidget(self.groupbox)
|
||||
# ------------------------
|
||||
|
||||
self.checkboxImportGis = QtGui.QCheckBox("Importar datos GIS")
|
||||
RightLayout.addWidget(self.checkboxImportGis)
|
||||
|
||||
self.checkboxImportSatelitalImagen = QtGui.QCheckBox("Importar Imagen Satelital")
|
||||
RightLayout.addWidget(self.checkboxImportSatelitalImagen)
|
||||
|
||||
verticalSpacer = QtGui.QSpacerItem(20, 48, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
|
||||
RightLayout.addItem(verticalSpacer)
|
||||
@@ -161,6 +189,52 @@ class MapWindow(QtGui.QWidget):
|
||||
with open(file, 'r') as f:
|
||||
frame.runJavaScript(f.read())
|
||||
|
||||
def _load_webengine(self):
|
||||
"""Intenta cargar QWebEngineView desde cualquier versión de PySide.
|
||||
Retorna (QWebEngineView_class, QWebChannel_class) o (None, None)."""
|
||||
for modpath in [
|
||||
'PySide6.QtWebEngineWidgets',
|
||||
'PySide6.QtWebEngineCore',
|
||||
'PySide6.QtWebEngineQuick',
|
||||
'PySide2.QtWebEngineWidgets',
|
||||
'PySide.QtWebEngineWidgets',
|
||||
]:
|
||||
try:
|
||||
parts = modpath.split('.')
|
||||
mod = __import__(parts[0], fromlist=parts[1:])
|
||||
for p in parts[1:]:
|
||||
mod = getattr(mod, p)
|
||||
View = getattr(mod, 'QWebEngineView', None)
|
||||
Channel = getattr(mod, 'QWebChannel', None)
|
||||
if View is not None:
|
||||
return View, Channel
|
||||
except (ImportError, AttributeError):
|
||||
continue
|
||||
# Fallback: intentar por separado QtWebChannel (sí existe en flatpak)
|
||||
try:
|
||||
from PySide6.QtWebChannel import QWebChannel as Channel
|
||||
except ImportError:
|
||||
Channel = None
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"PVPlantGeoreferencing: QtWebEngine no disponible. "
|
||||
"Usando modo manual de coordenadas.\n")
|
||||
return None, Channel
|
||||
|
||||
def onManualCoords(self):
|
||||
"""Procesa entrada manual de latitud,longitud"""
|
||||
text = self.valueSearch.text().strip()
|
||||
if not text:
|
||||
return
|
||||
try:
|
||||
parts = text.replace(',', ' ').split()
|
||||
lat = float(parts[0])
|
||||
lon = float(parts[1])
|
||||
self.georeference_coordinates = {'lat': lat, 'lon': lon}
|
||||
self.labelCoordinates.setText(f"{lat:.6f}, {lon:.6f}")
|
||||
FreeCAD.Console.PrintMessage(f"Coordenadas: {lat:.6f}, {lon:.6f}\n")
|
||||
except (ValueError, IndexError):
|
||||
FreeCAD.Console.PrintError("Formato inválido. Usa: latitud, longitud\n")
|
||||
|
||||
def onSearch(self):
|
||||
if self.valueSearch.text() == "":
|
||||
return
|
||||
@@ -169,7 +243,6 @@ class MapWindow(QtGui.QWidget):
|
||||
|
||||
geolocator = Nominatim(user_agent="http")
|
||||
location = geolocator.geocode(self.valueSearch.text())
|
||||
print(location.raw)
|
||||
self.valueSearch.setText(location.address)
|
||||
self.panMap(location.longitude, location.latitude, location.raw['boundingbox'])
|
||||
|
||||
@@ -185,6 +258,7 @@ class MapWindow(QtGui.QWidget):
|
||||
"var data = drawnItems.toGeoJSON();"
|
||||
"MyApp.shapes(JSON.stringify(data));"
|
||||
)
|
||||
|
||||
self.close()
|
||||
|
||||
@QtCore.Slot(float, float)
|
||||
@@ -196,10 +270,22 @@ class MapWindow(QtGui.QWidget):
|
||||
' | UTM: ' + str(zone_number) + zone_letter +
|
||||
', {:.5f}m E, {:.5f}m N'.format(x, y))
|
||||
|
||||
@QtCore.Slot(float, float, float, float, int)
|
||||
def onMapZoom(self, minLat, minLon, maxLat, maxLon, zoom):
|
||||
self.minLat = min([minLat, maxLat])
|
||||
self.maxLat = max([minLat, maxLat])
|
||||
self.minLon = min([minLon, maxLon])
|
||||
self.maxLon = max([minLon, maxLon])
|
||||
self.zoom = zoom
|
||||
|
||||
@QtCore.Slot(float, float)
|
||||
def georeference(self, lat, lng):
|
||||
import PVPlantSite
|
||||
from geopy.geocoders import Nominatim
|
||||
|
||||
self.georeference_coordinates['lat'] = lat
|
||||
self.georeference_coordinates['lon'] = lng
|
||||
|
||||
Site = PVPlantSite.get(create=True)
|
||||
Site.Proxy.setLatLon(lat, lng)
|
||||
|
||||
@@ -228,13 +314,20 @@ class MapWindow(QtGui.QWidget):
|
||||
import geojson
|
||||
import PVPlantImportGrid as ImportElevation
|
||||
import Draft
|
||||
import PVPlantSite
|
||||
Site = PVPlantSite.get()
|
||||
|
||||
offset = FreeCAD.Vector(0, 0, 0)
|
||||
if not (self.lat is None or self.lon is None):
|
||||
offset = FreeCAD.Vector(Site.Origin)
|
||||
offset.z = 0
|
||||
|
||||
items = geojson.loads(drawnItems)
|
||||
for item in items['features']:
|
||||
if item['geometry']['type'] == "Point": # 1. if the feature is a Point or Circle:
|
||||
coord = item['geometry']['coordinates']
|
||||
point = ImportElevation.getElevationFromOE([[coord[0], coord[1]],])
|
||||
c = FreeCAD.Vector(point[0][0], point[0][1], point[0][2])
|
||||
point = ImportElevation.getElevationFromOE([[coord[1], coord[0]],])
|
||||
c = FreeCAD.Vector(point[0][0], point[0][1], point[0][2]).sub(offset)
|
||||
if item['properties'].get('radius'):
|
||||
r = round(item['properties']['radius'] * 1000, 0)
|
||||
p = FreeCAD.Placement()
|
||||
@@ -252,34 +345,231 @@ class MapWindow(QtGui.QWidget):
|
||||
name = "Area"
|
||||
lp = item['geometry']['coordinates'][0]
|
||||
|
||||
pts = []
|
||||
for cords in lp:
|
||||
pts.append([cords[1], cords[0]])
|
||||
pts = [[cords[1], cords[0]] for cords in lp]
|
||||
tmp = ImportElevation.getElevationFromOE(pts)
|
||||
pts = []
|
||||
for p in tmp:
|
||||
pts.append(p.sub(FreeCAD.ActiveDocument.Site.Origin))
|
||||
pts = [p.sub(offset) for p in tmp]
|
||||
|
||||
obj = Draft.makeWire(pts, closed=cw, face=False)
|
||||
obj.Placement.Base = FreeCAD.ActiveDocument.Site.Origin
|
||||
#obj.Placement.Base = Site.Origin
|
||||
obj.Label = name
|
||||
Draft.autogroup(obj)
|
||||
|
||||
if item['properties'].get('name'):
|
||||
obj.Label = item['properties']['name']
|
||||
|
||||
FreeCAD.activeDocument().recompute()
|
||||
FreeCADGui.updateGui()
|
||||
FreeCADGui.SendMsgToActiveView("ViewFit")
|
||||
if self.checkboxImportGis.isChecked():
|
||||
self.getDataFromOSM(self.minLat, self.minLon, self.maxLat, self.maxLon)
|
||||
|
||||
def panMap(self, lng, lat, geometry=""):
|
||||
if self.checkboxImportSatelitalImagen.isChecked():
|
||||
# Usar los límites reales del terreno (rectangular)
|
||||
'''s_lat = self.minLat
|
||||
s_lon = self.minLon
|
||||
n_lat = self.maxLat
|
||||
n_lon = self.maxLon
|
||||
|
||||
# Obtener puntos UTM para las esquinas
|
||||
corners = ImportElevation.getElevationFromOE([
|
||||
[s_lat, s_lon], # Esquina suroeste
|
||||
[n_lat, s_lon], # Esquina sureste
|
||||
[n_lat, n_lon], # Esquina noreste
|
||||
[s_lat, n_lon] # Esquina noroeste
|
||||
])
|
||||
|
||||
if not corners or len(corners) < 4:
|
||||
FreeCAD.Console.PrintError("Error obteniendo elevaciones para las esquinas\n")
|
||||
return
|
||||
|
||||
# Descargar imagen satelital
|
||||
from lib.GoogleSatelitalImageDownload import GoogleMapDownloader
|
||||
downloader = GoogleMapDownloader(
|
||||
zoom= 18, #self.zoom,
|
||||
layer='raw_satellite'
|
||||
)
|
||||
img = downloader.generateImage(
|
||||
sw_lat=s_lat,
|
||||
sw_lng=s_lon,
|
||||
ne_lat=n_lat,
|
||||
ne_lng=n_lon
|
||||
)
|
||||
|
||||
# Guardar imagen en el directorio del documento
|
||||
doc_path = os.path.dirname(FreeCAD.ActiveDocument.FileName) if FreeCAD.ActiveDocument.FileName else ""
|
||||
if not doc_path:
|
||||
doc_path = FreeCAD.ConfigGet("UserAppData")
|
||||
|
||||
filename = os.path.join(doc_path, "background.jpeg")
|
||||
img.save(filename)
|
||||
|
||||
ancho, alto = img.size
|
||||
|
||||
# Crear objeto de imagen en FreeCAD
|
||||
doc = FreeCAD.ActiveDocument
|
||||
img_obj = doc.addObject('Image::ImagePlane', 'Background')
|
||||
img_obj.ImageFile = filename
|
||||
img_obj.Label = 'Background'
|
||||
|
||||
# Calcular dimensiones en metros usando las coordenadas UTM
|
||||
# Extraer las coordenadas de las esquinas
|
||||
sw = corners[0] # Suroeste
|
||||
se = corners[1] # Sureste
|
||||
ne = corners[2] # Noreste
|
||||
nw = corners[3] # Noroeste
|
||||
|
||||
# Calcular ancho (promedio de los lados superior e inferior)
|
||||
width_bottom = se.x - sw.x
|
||||
width_top = ne.x - nw.x
|
||||
width_m = (width_bottom + width_top) / 2
|
||||
|
||||
# Calcular alto (promedio de los lados izquierdo y derecho)
|
||||
height_left = nw.y - sw.y
|
||||
height_right = ne.y - se.y
|
||||
height_m = (height_left + height_right) / 2
|
||||
|
||||
img_obj.XSize = width_m
|
||||
img_obj.YSize = height_m
|
||||
|
||||
# Posicionar el centro de la imagen en (0,0,0)
|
||||
img_obj.Placement.Base = FreeCAD.Vector(-width_m / 2, -height_m / 2, 0)'''
|
||||
|
||||
# Definir área rectangular
|
||||
s_lat = self.minLat
|
||||
s_lon = self.minLon
|
||||
n_lat = self.maxLat
|
||||
n_lon = self.maxLon
|
||||
|
||||
# Obtener puntos UTM para las esquinas y el punto de referencia
|
||||
points = [
|
||||
[s_lat, s_lon], # Suroeste
|
||||
[n_lat, n_lon], # Noreste
|
||||
[self.georeference_coordinates['lat'], self.georeference_coordinates['lon']] # Punto de referencia
|
||||
]
|
||||
utm_points = ImportElevation.getElevationFromOE(points)
|
||||
|
||||
if not utm_points or len(utm_points) < 3:
|
||||
FreeCAD.Console.PrintError("Error obteniendo elevaciones para las esquinas y referencia\n")
|
||||
return
|
||||
|
||||
sw_utm, ne_utm, ref_utm = utm_points
|
||||
|
||||
# Descargar imagen satelital
|
||||
from lib.GoogleSatelitalImageDownload import GoogleMapDownloader
|
||||
downloader = GoogleMapDownloader(
|
||||
zoom=self.zoom,
|
||||
layer='raw_satellite'
|
||||
)
|
||||
img = downloader.generateImage(
|
||||
sw_lat=s_lat,
|
||||
sw_lng=s_lon,
|
||||
ne_lat=n_lat,
|
||||
ne_lng=n_lon
|
||||
)
|
||||
|
||||
# Guardar imagen
|
||||
doc_path = os.path.dirname(FreeCAD.ActiveDocument.FileName) if FreeCAD.ActiveDocument.FileName else ""
|
||||
if not doc_path:
|
||||
doc_path = FreeCAD.ConfigGet("UserAppData")
|
||||
|
||||
filename = os.path.join(doc_path, "background.jpeg")
|
||||
img.save(filename)
|
||||
|
||||
# Calcular dimensiones reales en metros
|
||||
width_m = ne_utm.x - sw_utm.x # Ancho en metros (este-oeste)
|
||||
height_m = ne_utm.y - sw_utm.y # Alto en metros (norte-sur)
|
||||
|
||||
# Calcular posición relativa del punto de referencia dentro de la imagen
|
||||
rel_x = (ref_utm.x - sw_utm.x) / width_m if width_m != 0 else 0.5
|
||||
rel_y = (ref_utm.y - sw_utm.y) / height_m if height_m != 0 else 0.5
|
||||
|
||||
# Crear objeto de imagen en FreeCAD
|
||||
doc = FreeCAD.ActiveDocument
|
||||
img_obj = doc.addObject('Image::ImagePlane', 'Background')
|
||||
img_obj.ImageFile = filename
|
||||
img_obj.Label = 'Background'
|
||||
|
||||
# Convertir dimensiones a milímetros (FreeCAD trabaja en mm)
|
||||
img_obj.XSize = width_m * 1000
|
||||
img_obj.YSize = height_m * 1000
|
||||
|
||||
# Posicionar para que el punto de referencia esté en (0,0,0)
|
||||
# La esquina inferior izquierda debe estar en:
|
||||
# x = -rel_x * ancho_total
|
||||
# y = -rel_y * alto_total
|
||||
img_obj.Placement.Base = FreeCAD.Vector(
|
||||
-rel_x * width_m * 1000,
|
||||
-rel_y * height_m * 1000,
|
||||
0
|
||||
)
|
||||
|
||||
# Refrescar el documento
|
||||
doc.recompute()
|
||||
|
||||
def calculate_texture_transform(self, mesh_obj, width_m, height_m):
|
||||
"""Calcula la transformación precisa para la textura"""
|
||||
try:
|
||||
# Obtener coordenadas reales de las esquinas
|
||||
import utm
|
||||
sw = utm.from_latlon(self.minLat, self.minLon)
|
||||
ne = utm.from_latlon(self.maxLat, self.maxLon)
|
||||
|
||||
# Crear matriz de transformación
|
||||
scale_x = (ne[0] - sw[0]) / width_m
|
||||
scale_y = (ne[1] - sw[1]) / height_m
|
||||
|
||||
# Aplicar transformación (solo si se usa textura avanzada)
|
||||
if hasattr(mesh_obj.ViewObject, "TextureMapping"):
|
||||
mesh_obj.ViewObject.TextureMapping = "PLANE"
|
||||
mesh_obj.ViewObject.TextureScale = (scale_x, scale_y)
|
||||
mesh_obj.ViewObject.TextureOffset = (sw[0], sw[1])
|
||||
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintWarning(f"No se pudo calcular transformación: {str(e)}\n")
|
||||
|
||||
def getDataFromOSM(self, min_lat, min_lon, max_lat, max_lon):
|
||||
import Importer.importOSM as importOSM
|
||||
import PVPlantSite
|
||||
site = PVPlantSite.get()
|
||||
|
||||
offset = FreeCAD.Vector(0, 0, 0)
|
||||
if not (self.lat is None or self.lon is None):
|
||||
offset = FreeCAD.Vector(site.Origin)
|
||||
offset.z = 0
|
||||
importer = importOSM.OSMImporter(offset)
|
||||
osm_data = importer.get_osm_data(f"{min_lat},{min_lon},{max_lat},{max_lon}")
|
||||
importer.process_osm_data(osm_data)
|
||||
|
||||
'''FreeCAD.activeDocument().recompute()
|
||||
FreeCADGui.updateGui()
|
||||
FreeCADGui.SendMsgToActiveView("ViewFit")'''
|
||||
|
||||
def panMap_old(self, lng, lat, geometry=""):
|
||||
frame = self.view.page()
|
||||
bbox = "[{0}, {1}], [{2}, {3}]".format(float(geometry[0]), float(geometry[2]),
|
||||
float(geometry[1]), float(geometry[3]))
|
||||
command = 'map.panTo(L.latLng({lt}, {lg}));'.format(lt=lat, lg=lng)
|
||||
command = 'map.panTo(L.latLng({lt}, {lg}));'.format(lt=lat, lg=lng)
|
||||
command += 'map.fitBounds([{box}]);'.format(box=bbox)
|
||||
frame.runJavaScript(command)
|
||||
|
||||
# deepseek
|
||||
def panMap(self, lng, lat, geometry=None):
|
||||
frame = self.view.page()
|
||||
|
||||
# 1. Validación del parámetro geometry
|
||||
if not geometry or len(geometry) < 4:
|
||||
# Pan básico sin ajuste de bounds
|
||||
command = f'map.panTo(L.latLng({lat}, {lng}));'
|
||||
else:
|
||||
try:
|
||||
# 2. Mejor manejo de coordenadas (Leaflet usa [lat, lng])
|
||||
# Asumiendo que geometry es [min_lng, min_lat, max_lng, max_lat]
|
||||
southwest = f"{float(geometry[1])}, {float(geometry[0])}" # min_lat, min_lng
|
||||
northeast = f"{float(geometry[3])}, {float(geometry[2])}" # max_lat, max_lng
|
||||
command = f'map.panTo(L.latLng({lat}, {lng}));'
|
||||
command += f'map.fitBounds(L.latLngBounds([{southwest}], [{northeast}]));'
|
||||
except (IndexError, ValueError, TypeError) as e:
|
||||
print(f"Error en geometry: {str(e)}")
|
||||
command = f'map.panTo(L.latLng({lat}, {lng}));'
|
||||
frame.runJavaScript(command)
|
||||
|
||||
def importKML(self):
|
||||
file = QtGui.QFileDialog.getOpenFileName(None, "FileDialog", "", "Google Earth (*.kml *.kmz)")[0]
|
||||
|
||||
@@ -287,11 +577,11 @@ class MapWindow(QtGui.QWidget):
|
||||
layers = kmz_convert(file, "", )
|
||||
frame = self.view.page()
|
||||
for layer in layers:
|
||||
command = "drawnItems.addLayer(L.geoJSON({0}));".format(layer)
|
||||
command = "var geoJsonLayer = L.geoJSON({0}); drawnItems.addLayer(geoJsonLayer); map.fitBounds(geoJsonLayer.getBounds());".format( layer)
|
||||
frame.runJavaScript(command)
|
||||
|
||||
|
||||
class _CommandPVPlantGeoreferencing:
|
||||
class CommandPVPlantGeoreferencing:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "Location.svg")),
|
||||
@@ -309,6 +599,6 @@ class _CommandPVPlantGeoreferencing:
|
||||
else:
|
||||
return False
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
'''if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('PVPlantGeoreferencing',_CommandPVPlantGeoreferencing())
|
||||
|
||||
'''
|
||||
|
||||
+21
-25
@@ -57,14 +57,14 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit">
|
||||
<widget class="QLineEdit" name="search_bar">
|
||||
<property name="placeholderText">
|
||||
<string>Search...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<widget class="QPushButton" name="search_button">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -80,10 +80,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWebEngineView" name="widget_4" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<widget class="QLabel" name="coordinates_label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -91,7 +88,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
<string>coordenadas:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -111,7 +108,7 @@
|
||||
<widget class="QWidget" name="widget_5" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="pushButton_4">
|
||||
<widget class="QPushButton" name="kmz_button">
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
@@ -124,20 +121,27 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="checkBox">
|
||||
<property name="text">
|
||||
<string>Georeferenciar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBoxGeoreference">
|
||||
<property name="text">
|
||||
<string>Georeferenciar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBoxImportGis">
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -164,7 +168,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_3">
|
||||
<widget class="QPushButton" name="accept_button">
|
||||
<property name="text">
|
||||
<string>Aceptar</string>
|
||||
</property>
|
||||
@@ -178,14 +182,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QWebEngineView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>qwebengineview.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Georeferencing</class>
|
||||
<widget class="QDialog" name="Georeferencing">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>574</width>
|
||||
<height>350</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Create Surface</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QWidget" name="widgetLeft" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="widgetSearch" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWebEngineView" name="widget_4" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widgetRight" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Configuraciones:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox">
|
||||
<property name="text">
|
||||
<string>Georeferenciar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Cargar un archivo KMZ/KML:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_4">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="listWidget">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>New Item</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Unchecked</enum>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_6" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_2">
|
||||
<property name="text">
|
||||
<string>Cancelar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_3">
|
||||
<property name="text">
|
||||
<string>Aceptar</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QWebEngineView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>qwebengineview.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
+130
-62
@@ -39,37 +39,152 @@ import os
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
import PVPlantSite
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Adaptador UTM: emula la API de la librería 'utm' usando pyproj
|
||||
# La librería 'utm' dejó de usarse en favor de pyproj (más completa y mantenida).
|
||||
# from_latlon(lat, lon) -> (easting, northing, zone_number, zone_letter)
|
||||
# to_latlon(easting, northing, zone_number, zone_letter) -> (lat, lon)
|
||||
# ---------------------------------------------------------------------------
|
||||
_utm_cache = {}
|
||||
|
||||
def _get_transformer(lat, lon):
|
||||
"""Obtiene o crea un transformador UTM para las coordenadas dadas."""
|
||||
from pyproj import Transformer
|
||||
zone = int((lon + 180) / 6) + 1
|
||||
hem = 'S' if lat < 0 else 'N'
|
||||
key = (zone, hem)
|
||||
if key not in _utm_cache:
|
||||
crs_utm = f'+proj=utm +zone={zone} +{hem.lower()} +ellps=WGS84 +datum=WGS84 +units=m +no_defs'
|
||||
_utm_cache[key] = Transformer.from_crs('EPSG:4326', crs_utm, always_xy=True)
|
||||
return _utm_cache[key], zone, hem
|
||||
|
||||
def from_latlon(lat, lon):
|
||||
"""Convierte (lat, lon) a UTM. Retorna (easting, northing, zone_number, zone_letter)."""
|
||||
transformer, zone, hem = _get_transformer(lat, lon)
|
||||
easting, northing = transformer.transform(lon, lat)
|
||||
return (easting, northing, zone, hem)
|
||||
|
||||
def to_latlon(easting, northing, zone_number, zone_letter):
|
||||
"""Convierte UTM a (lat, lon)."""
|
||||
from pyproj import Transformer
|
||||
hem = zone_letter.upper()
|
||||
key = (zone_number, hem)
|
||||
if key not in _utm_cache:
|
||||
crs_utm = f'+proj=utm +zone={zone_number} +{hem.lower()} +ellps=WGS84 +datum=WGS84 +units=m +no_defs'
|
||||
_utm_cache[key] = Transformer.from_crs(crs_utm, 'EPSG:4326', always_xy=True)
|
||||
lon, lat = _utm_cache[key].transform(easting, northing)
|
||||
return (lat, lon)
|
||||
|
||||
# Parche: reemplazar el módulo 'utm' por nuestro adaptador
|
||||
import sys
|
||||
class _UTMWrapper:
|
||||
"""Wrapper para que 'import utm' devuelva nuestras funciones."""
|
||||
from_latlon = staticmethod(from_latlon)
|
||||
to_latlon = staticmethod(to_latlon)
|
||||
sys.modules['utm'] = _UTMWrapper
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def get_elevation_from_oe(coordinates): # v1 deepseek
|
||||
"""Obtiene elevaciones de Open-Elevation API y devuelve vectores FreeCAD en coordenadas UTM.
|
||||
Args:
|
||||
coordinates (list): Lista de tuplas con coordenadas (latitud, longitud)
|
||||
Returns:
|
||||
list: Lista de vectores FreeCAD con coordenadas UTM y elevación (en milímetros)
|
||||
o lista vacía en caso de error.
|
||||
"""
|
||||
if not coordinates:
|
||||
return []
|
||||
|
||||
import requests
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
# Construcción más eficiente de parámetros
|
||||
locations = "|".join([f"{lat:.6f},{lon:.6f}" for lat, lon in coordinates])
|
||||
|
||||
try:
|
||||
response = requests.get(
|
||||
url="https://api.open-elevation.com/api/v1/lookup",
|
||||
params={'locations': locations},
|
||||
timeout=20,
|
||||
verify=True
|
||||
)
|
||||
response.raise_for_status() # Lanza excepción para códigos 4xx/5xx
|
||||
|
||||
except RequestException as e:
|
||||
print(f"Error en la solicitud: {e}")
|
||||
return []
|
||||
|
||||
try:
|
||||
data = response.json()
|
||||
except ValueError:
|
||||
print("Respuesta JSON inválida")
|
||||
return []
|
||||
|
||||
if "results" not in data or len(data["results"]) != len(coordinates):
|
||||
print("Formato de respuesta inesperado")
|
||||
return []
|
||||
|
||||
points = []
|
||||
for result in data["results"]:
|
||||
try:
|
||||
# Conversión UTM con manejo de errores
|
||||
easting, northing, _, _ = utm.from_latlon(
|
||||
result["latitude"],
|
||||
result["longitude"]
|
||||
)
|
||||
|
||||
points.append(FreeCAD.Vector(round(easting), # Convertir metros a milímetros
|
||||
round(northing),
|
||||
round(result["elevation"])) * 1000)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error procesando coordenadas: {e}")
|
||||
continue
|
||||
|
||||
return points
|
||||
|
||||
|
||||
def getElevationFromOE(coordinates):
|
||||
"""Obtiene elevaciones de Open-Elevation API y devuelve vectores FreeCAD en coordenadas UTM."""
|
||||
|
||||
import certifi
|
||||
from requests.exceptions import RequestException
|
||||
if len(coordinates) == 0:
|
||||
return None
|
||||
|
||||
from requests import get
|
||||
import utm
|
||||
|
||||
str=""
|
||||
locations_str=""
|
||||
total = len(coordinates) - 1
|
||||
for i, point in enumerate(coordinates):
|
||||
str += '{:.6f},{:.6f}'.format(point[0], point[1])
|
||||
locations_str += '{:.6f},{:.6f}'.format(point[0], point[1])
|
||||
if i != total:
|
||||
str += '|'
|
||||
query = 'https://api.open-elevation.com/api/v1/lookup?locations=' + str
|
||||
r = get(query, timeout=20)
|
||||
|
||||
# Only get the json response in case of 200 or 201
|
||||
locations_str += '|'
|
||||
query = 'https://api.open-elevation.com/api/v1/lookup?locations=' + locations_str
|
||||
points = []
|
||||
if r.status_code == 200 or r.status_code == 201:
|
||||
try:
|
||||
r = get(query, timeout=20, verify=certifi.where()) # <-- Corrección aquí
|
||||
results = r.json()
|
||||
for point in results["results"]:
|
||||
c = utm.from_latlon(point["latitude"], point["longitude"])
|
||||
v = FreeCAD.Vector(round(c[0] * 1000, 0),
|
||||
round(c[1] * 1000, 0),
|
||||
round(point["elevation"] * 1000, 0))
|
||||
v = FreeCAD.Vector(round(c[0], 0),
|
||||
round(c[1], 0),
|
||||
round(point["elevation"], 0)) * 1000
|
||||
points.append(v)
|
||||
except RequestException as e:
|
||||
# print(f"Error en la solicitud: {str(e)}")
|
||||
for i, point in enumerate(coordinates):
|
||||
c = utm.from_latlon(point[0], point[1])
|
||||
points.append(FreeCAD.Vector(round(c[0], 0),
|
||||
round(c[1], 0),
|
||||
0) * 1000)
|
||||
|
||||
return points
|
||||
|
||||
def getSinglePointElevationFromBing(lat, lng):
|
||||
#http://dev.virtualearth.net/REST/v1/Elevation/List?points={lat1,long1,lat2,long2,latN,longnN}&heights={heights}&key={BingMapsAPIKey}
|
||||
|
||||
source = "http://dev.virtualearth.net/REST/v1/Elevation/List?points="
|
||||
source += str(lat) + "," + str(lng)
|
||||
source += "&heights=sealevel"
|
||||
@@ -79,11 +194,9 @@ def getSinglePointElevationFromBing(lat, lng):
|
||||
response = requests.get(source)
|
||||
ans = response.text
|
||||
|
||||
# +# to do: error handling - wait and try again
|
||||
s = json.loads(ans)
|
||||
print(s)
|
||||
res = s['resourceSets'][0]['resources'][0]['elevations']
|
||||
|
||||
import utm
|
||||
for elevation in res:
|
||||
c = utm.from_latlon(lat, lng)
|
||||
v = FreeCAD.Vector(
|
||||
@@ -95,7 +208,6 @@ def getSinglePointElevationFromBing(lat, lng):
|
||||
def getGridElevationFromBing(polygon, lat, lng, resolution = 1000):
|
||||
#http://dev.virtualearth.net/REST/v1/Elevation/Polyline?points=35.89431,-110.72522,35.89393,-110.72578,35.89374,-110.72606,35.89337,-110.72662
|
||||
# &heights=ellipsoid&samples=10&key={BingMapsAPIKey}
|
||||
import utm
|
||||
import math
|
||||
import requests
|
||||
|
||||
@@ -240,7 +352,6 @@ def getSinglePointElevationUtm(lat, lon):
|
||||
res = s['results']
|
||||
print (res)
|
||||
|
||||
import utm
|
||||
for r in res:
|
||||
c = utm.from_latlon(r['location']['lat'], r['location']['lng'])
|
||||
v = FreeCAD.Vector(
|
||||
@@ -250,10 +361,8 @@ def getSinglePointElevationUtm(lat, lon):
|
||||
print (v)
|
||||
return v
|
||||
|
||||
|
||||
def getElevationUTM(polygon, lat, lng, resolution = 10000):
|
||||
|
||||
import utm
|
||||
geo = utm.from_latlon(lat, lng)
|
||||
# result = (679434.3578335291, 4294023.585627955, 30, 'S')
|
||||
# EASTING, NORTHING, ZONE NUMBER, ZONE LETTER
|
||||
@@ -322,7 +431,7 @@ def getElevation1(polygon,resolution=10):
|
||||
|
||||
s = json.loads(ans)
|
||||
res = s['results']
|
||||
except:
|
||||
except (json.JSONDecodeError, KeyError):
|
||||
continue
|
||||
|
||||
#points = []
|
||||
@@ -374,47 +483,6 @@ def getElevation(lat, lon, b=50.35, le=11.17, size=40):
|
||||
FreeCADGui.updateGui()
|
||||
return FreeCAD.activeDocument().ActiveObject
|
||||
|
||||
|
||||
'''
|
||||
# original::
|
||||
def getElevation(lat, lon, b=50.35, le=11.17, size=40):
|
||||
tm.lat = lat
|
||||
tm.lon = lon
|
||||
baseheight = 0 #getheight(tm.lat, tm.lon)
|
||||
center = tm.fromGeographic(tm.lat, tm.lon)
|
||||
|
||||
#https://maps.googleapis.com/maps/api/elevation/json?path=36.578581,-118.291994|36.23998,-116.83171&samples=3&key=YOUR_API_KEY
|
||||
#https://maps.googleapis.com/maps/api/elevation/json?locations=39.7391536,-104.9847034&key=YOUR_API_KEY
|
||||
|
||||
source = "https://maps.googleapis.com/maps/api/elevation/json?path="
|
||||
source += str(b-size*0.001) + "," + str(le) + "|" + str(b+size*0.001) + "," + str(le)
|
||||
source += "&samples=" + str(100)
|
||||
source += "&key=AIzaSyB07X6lowYJ-iqyPmaFJvr-6zp1J63db8U"
|
||||
|
||||
response = urllib.request.urlopen(source)
|
||||
ans = response.read()
|
||||
|
||||
# +# to do: error handling - wait and try again
|
||||
s = json.loads(ans)
|
||||
res = s['results']
|
||||
|
||||
points = []
|
||||
for r in res:
|
||||
c = tm.fromGeographic(r['location']['lat'], r['location']['lng'])
|
||||
v = FreeCAD.Vector(
|
||||
round(c[0], 2),
|
||||
round(c[1], 2),
|
||||
round(r['elevation'] * 1000, 2) - baseheight
|
||||
)
|
||||
points.append(v)
|
||||
|
||||
line = Draft.makeWire(points, closed=False, face=False, support=None)
|
||||
line.ViewObject.Visibility = False
|
||||
#FreeCAD.activeDocument().recompute()
|
||||
FreeCADGui.updateGui()
|
||||
return FreeCAD.activeDocument().ActiveObject
|
||||
'''
|
||||
|
||||
class _ImportPointsTaskPanel:
|
||||
|
||||
def __init__(self, obj = None):
|
||||
@@ -497,7 +565,7 @@ class _ImportPointsTaskPanel:
|
||||
|
||||
try:
|
||||
PointGroups = FreeCAD.ActiveDocument.Point_Groups
|
||||
except:
|
||||
except AttributeError:
|
||||
PointGroups = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Point_Groups')
|
||||
PointGroups.Label = "Point Groups"
|
||||
|
||||
|
||||
+3
-5
@@ -138,8 +138,6 @@ class _Manhole(ArchComponent.Component):
|
||||
obj.Shape = ext_sol.cut([ins_sol, ], 0.0)
|
||||
|
||||
|
||||
|
||||
|
||||
class _ViewProviderManhole(ArchComponent.ViewProviderComponent):
|
||||
"A View Provider for the Pipe object"
|
||||
|
||||
@@ -180,7 +178,7 @@ class _ViewProviderManhole(ArchComponent.ViewProviderComponent):
|
||||
|
||||
import draftguitools.gui_tool_utils as gui_tool_utils
|
||||
|
||||
class _ManholeTaskPanel:
|
||||
class ManholeTaskPanel:
|
||||
def __init__(self, obj=None):
|
||||
self.new = False
|
||||
if obj is None:
|
||||
@@ -249,7 +247,7 @@ class _ManholeTaskPanel:
|
||||
self.view.removeEventCallback("SoEvent", self.call)
|
||||
|
||||
|
||||
class _CommandManhole:
|
||||
'''class _CommandManhole:
|
||||
"the Arch Building command definition"
|
||||
|
||||
def GetResources(self):
|
||||
@@ -274,5 +272,5 @@ class _CommandManhole:
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('PVPlantManhole', _CommandManhole())
|
||||
FreeCADGui.addCommand('PVPlantManhole', _CommandManhole())'''
|
||||
|
||||
|
||||
+1
-1
@@ -323,7 +323,7 @@ class _PadTaskPanel:
|
||||
self.new = False
|
||||
self.obj = obj
|
||||
|
||||
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "PVPlantTrench.ui"))
|
||||
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "Civil/PVPlantTrench.ui"))
|
||||
|
||||
def accept(self):
|
||||
FreeCAD.ActiveDocument.openTransaction("Create Pad")
|
||||
|
||||
+455
-924
File diff suppressed because it is too large
Load Diff
+330
-322
@@ -15,6 +15,318 @@
|
||||
<string>Park Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" alignment="Qt::AlignmentFlag::AlignTop">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Estructura:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="buttonPVArea">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="3">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Configuración</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="horizontalSpacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="8" column="1">
|
||||
<widget class="QComboBox" name="comboDirV">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>De arriba a abajo</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>De abajo a arriba</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Del centro a los lados</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Dirección Horizontal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QComboBox" name="comboDirH">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>De izquierda a derecha</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>De derecha a izquiera</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>De centro a los lados</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<widget class="QCheckBox" name="cbAlignFrames">
|
||||
<property name="text">
|
||||
<string>Alinear estructuras</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="editGapRows">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> mm</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>500</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Pitch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editOffsetHorizontal">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> m</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-10000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>10000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Orientación</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Offset Horizontal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Offset Vertical</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editOffsetVertical">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> m</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-10000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>10000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editGapCols">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> m</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>5.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboOrientation">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Norte - Sur</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Este - Oeste</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Dirección Vertical</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Espacio entre filas</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="editInnerSpacing">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string> - Inner Spacing</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="3">
|
||||
<widget class="QGroupBox" name="groupCorridor">
|
||||
<property name="title">
|
||||
@@ -59,10 +371,10 @@
|
||||
<item row="3" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editRowGap">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string/>
|
||||
@@ -110,10 +422,10 @@
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editColGap">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string/>
|
||||
@@ -135,10 +447,10 @@
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="editRowCount">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>4</number>
|
||||
@@ -148,10 +460,10 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="editColCount">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>8</number>
|
||||
@@ -161,45 +473,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="buttonPVArea">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonAddFrame">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonRemoveFrame">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@@ -207,282 +480,9 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="editPVArea"/>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="3">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Configuración</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="horizontalSpacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Offset Vertical</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Orientación</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QCheckBox" name="cbAlignFrames">
|
||||
<property name="text">
|
||||
<string>Alinear estructuras</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Espacio entre filas</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Dirección Horizontal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Offset Horizontal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Pitch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Dirección Vertical</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboOrientation">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Norte - Sur</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Este - Oeste</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editGapCols">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> m</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>5.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="comboDirH">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>De izquierda a derecha</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>De derecha a izquiera</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>De centro a los lados</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="comboDirV">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>De arriba a abajo</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>De abajo a arriba</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Del centro a los lados</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editOffsetHorizontal">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> m</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-10000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>10000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editOffsetVertical">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> m</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-10000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>10000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="editGapRows">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> mm</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>500</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" alignment="Qt::AlignTop">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Estructura:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QListWidget" name="listFrameSetups">
|
||||
<property name="maximumSize">
|
||||
@@ -493,11 +493,19 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="cbSubfolders">
|
||||
<property name="text">
|
||||
<string>Organizar en subcarpetas</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>buttonAddFrame</tabstop>
|
||||
<tabstop>buttonRemoveFrame</tabstop>
|
||||
<tabstop>editPVArea</tabstop>
|
||||
<tabstop>buttonPVArea</tabstop>
|
||||
<tabstop>comboOrientation</tabstop>
|
||||
|
||||
+268
-516
@@ -1,314 +1,297 @@
|
||||
# /**********************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2021-2026 Javier Braña <javier.branagutierrez@gmail.com>*
|
||||
# * *
|
||||
# * PVPlant Road - Sistema de carreteras con alineamiento profesional *
|
||||
# * Basado en ejes (Alignment) con estaciones, perfiles y cubicación. *
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import ArchComponent
|
||||
import Part
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore
|
||||
from DraftTools import translate
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
import Part
|
||||
import os
|
||||
else:
|
||||
# \cond
|
||||
def translate(ctxt, txt):
|
||||
return txt
|
||||
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||
return txt
|
||||
# \endcond
|
||||
|
||||
__title__ = "PVPlant Road"
|
||||
__author__ = "Javier Braña"
|
||||
__url__ = "http://www.sogos-solar.com"
|
||||
def translate(ctxt, txt): return txt
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt): return txt
|
||||
|
||||
import PVPlantResources
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
from Civil.Alignment import make_alignment_from_wire
|
||||
|
||||
|
||||
def makeRoad(base=None):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Road")
|
||||
def makeRoad(base=None, alignment=None):
|
||||
"""Crea un objeto Road con o sin alignment."""
|
||||
doc = FreeCAD.ActiveDocument
|
||||
obj = doc.addObject("Part::FeaturePython", "Road")
|
||||
_Road(obj)
|
||||
_ViewProviderRoad(obj.ViewObject)
|
||||
obj.Base = base
|
||||
|
||||
from Project.Area import PVPlantArea
|
||||
offset = PVPlantArea.makeOffsetArea(obj, 4000)
|
||||
PVPlantArea.makeProhibitedArea(offset)
|
||||
obj.Alignment = alignment
|
||||
doc.recompute()
|
||||
return obj
|
||||
|
||||
|
||||
class _Road(ArchComponent.Component):
|
||||
"""Carretera con alineamiento horizontal+vertical y secciones multicapa."""
|
||||
|
||||
def __init__(self, obj):
|
||||
# Definición de Variables:
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
self.obj = obj
|
||||
self.setProperties(obj)
|
||||
self.Type = "Road"
|
||||
obj.Proxy = self
|
||||
|
||||
self.route = False
|
||||
|
||||
obj.IfcType = "Civil Element" ## puede ser: Cable Carrier Segment
|
||||
obj.IfcType = "Civil Element"
|
||||
obj.setEditorMode("IfcType", 1)
|
||||
|
||||
|
||||
self.count = 0
|
||||
|
||||
def setProperties(self, obj):
|
||||
# Definicion de Propiedades:
|
||||
'''[
|
||||
'App::PropertyBool',
|
||||
'App::PropertyBoolList',
|
||||
'App::PropertyFloat',
|
||||
'App::PropertyFloatList',
|
||||
'App::PropertyFloatConstraint',
|
||||
'App::PropertyPrecision',
|
||||
'App::PropertyQuantity',
|
||||
'App::PropertyQuantityConstraint',
|
||||
'App::PropertyAngle',
|
||||
'App::PropertyDistance',
|
||||
'App::PropertyLength',
|
||||
'App::PropertyArea',
|
||||
'App::PropertyVolume',
|
||||
'App::PropertyFrequency',
|
||||
'App::PropertySpeed',
|
||||
'App::PropertyAcceleration',
|
||||
'App::PropertyForce',
|
||||
'App::PropertyPressure',
|
||||
'App::PropertyVacuumPermittivity',
|
||||
'App::PropertyInteger',
|
||||
'App::PropertyIntegerConstraint',
|
||||
'App::PropertyPercent',
|
||||
'App::PropertyEnumeration',
|
||||
'App::PropertyIntegerList',
|
||||
'App::PropertyIntegerSet',
|
||||
'App::PropertyMap',
|
||||
'App::PropertyString',
|
||||
'App::PropertyPersistentObject',
|
||||
'App::PropertyUUID',
|
||||
'App::PropertyFont',
|
||||
'App::PropertyStringList',
|
||||
'p::PropertyLink',
|
||||
'App::PropertyLinkChild',
|
||||
'App::PropertyLinkGlobal',
|
||||
'App::PropertyLinkHidden',
|
||||
'App::PropertyLinkSub',
|
||||
'App::PropertyLinkSubChild',
|
||||
'App::PropertyLinkSubGlobal',
|
||||
'App::PropertyLinkSubHidden',
|
||||
'App::PropertyLinkList',
|
||||
'App::PropertyLinkListChild',
|
||||
'App::PropertyLinkListGlobal',
|
||||
'App::PropertyLinkListHidden',
|
||||
'App::PropertyLinkSubList',
|
||||
'App::PropertyLinkSubListChild',
|
||||
'App::PropertyLinkSubListGlobal',
|
||||
'App::PropertyLinkSubListHidden',
|
||||
'App::PropertyXLink',
|
||||
'App::PropertyXLinkSub',
|
||||
'App::PropertyXLinkSubList',
|
||||
'App::PropertyXLinkList',
|
||||
'App::PropertyMatrix',
|
||||
'App::PropertyVector',
|
||||
'App::PropertyVectorDistance',
|
||||
'App::PropertyPosition',
|
||||
'App::PropertyDirection',
|
||||
'App::PropertyVectorList',
|
||||
'App::PropertyPlacement',
|
||||
'App::PropertyPlacementList',
|
||||
'App::PropertyPlacementLink',
|
||||
'App::PropertyColor',
|
||||
'App::PropertyColorList',
|
||||
'App::PropertyMaterial',
|
||||
'App::PropertyMaterialList',
|
||||
'App::PropertyPath',
|
||||
'App::PropertyFile',
|
||||
'App::PropertyFileIncluded',
|
||||
'App::PropertyPythonObject',
|
||||
'App::PropertyExpressionEngine',
|
||||
'Part::PropertyPartShape',
|
||||
'Part::PropertyGeometryList',
|
||||
'Part::PropertyShapeHistory',
|
||||
'Part::PropertyFilletEdges',
|
||||
'Mesh::PropertyNormalList',
|
||||
'Mesh::PropertyCurvatureList',
|
||||
'Mesh::PropertyMeshKernel',
|
||||
'Sketcher::PropertyConstraintList'
|
||||
]'''
|
||||
pl = obj.PropertiesList
|
||||
|
||||
obj.addProperty("App::PropertyPercent",
|
||||
"SurfaceSlope",
|
||||
"Road",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Connection")).SurfaceSlope = 2
|
||||
# --- Alineamiento ---
|
||||
if "Alignment" not in pl:
|
||||
obj.addProperty("App::PropertyLink",
|
||||
"Alignment", "Road",
|
||||
"Objeto Alignment que define el eje").Alignment = None
|
||||
|
||||
obj.addProperty("App::PropertyPercent",
|
||||
"SurfaceDrainSlope",
|
||||
"Road",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Connection")).SurfaceDrainSlope = int(3 / 2 * 100)
|
||||
if "Base" not in pl:
|
||||
obj.addProperty("App::PropertyLink",
|
||||
"Base", "Road",
|
||||
"Wire base (alternativo si no hay Alignment)").Base = None
|
||||
|
||||
obj.addProperty("App::PropertyPercent",
|
||||
"SubbaseDrainSlope",
|
||||
"Road",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Connection")).SubbaseDrainSlope = int(2 / 3 * 100)
|
||||
# --- Geometría transversal ---
|
||||
if "Width" not in pl:
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"Width", "Road",
|
||||
"Ancho total de la carretera").Width = 4000
|
||||
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"Width",
|
||||
"Road",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Connection")).Width = 4000
|
||||
if "PavementThickness" not in pl:
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"PavementThickness", "Road",
|
||||
"Espesor del pavimento").PavementThickness = 250
|
||||
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"Height",
|
||||
"Road",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Connection")).Height = 250
|
||||
if "BaseThickness" not in pl:
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"BaseThickness", "Road",
|
||||
"Espesor de la base").BaseThickness = 200
|
||||
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"Subbase",
|
||||
"Road",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Connection")).Subbase = 400
|
||||
if "SubbaseThickness" not in pl:
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"SubbaseThickness", "Road",
|
||||
"Espesor de la subbase").SubbaseThickness = 300
|
||||
|
||||
if "ShoulderWidth" not in pl:
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"ShoulderWidth", "Road",
|
||||
"Ancho del arcén cada lado").ShoulderWidth = 500
|
||||
|
||||
if "CrossSlope" not in pl:
|
||||
obj.addProperty("App::PropertyPercent",
|
||||
"CrossSlope", "Road",
|
||||
"Pendiente transversal del pavimento (%)").CrossSlope = 2
|
||||
|
||||
if "DitchSlope" not in pl:
|
||||
obj.addProperty("App::PropertyPercent",
|
||||
"DitchSlope", "Road",
|
||||
"Pendiente del drenaje (%)").DitchSlope = 3
|
||||
|
||||
# --- Estaciones y cubicación ---
|
||||
if "StationInterval" not in pl:
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"StationInterval", "Road",
|
||||
"Intervalo entre estaciones de cálculo").StationInterval = 20000
|
||||
|
||||
if "NumberOfStations" not in pl:
|
||||
obj.addProperty("App::PropertyInteger",
|
||||
"NumberOfStations", "Road",
|
||||
"Número de estaciones calculadas").NumberOfStations = 0
|
||||
obj.setEditorMode("NumberOfStations", 1)
|
||||
|
||||
if "CutVolume" not in pl:
|
||||
obj.addProperty("App::PropertyVolume",
|
||||
"CutVolume", "Road",
|
||||
"Volumen de desmonte (corte)").CutVolume = 0
|
||||
obj.setEditorMode("CutVolume", 1)
|
||||
|
||||
if "FillVolume" not in pl:
|
||||
obj.addProperty("App::PropertyVolume",
|
||||
"FillVolume", "Road",
|
||||
"Volumen de terraplén (relleno)").FillVolume = 0
|
||||
obj.setEditorMode("FillVolume", 1)
|
||||
|
||||
if "TotalLength" not in pl:
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"TotalLength", "Road",
|
||||
"Longitud total del eje").TotalLength = 0
|
||||
obj.setEditorMode("TotalLength", 1)
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
"""Method run when the document is restored.
|
||||
Re-adds the Arch component, and object properties."""
|
||||
|
||||
ArchComponent.Component.onDocumentRestored(self, obj)
|
||||
self.obj = obj
|
||||
self.Type = "Road"
|
||||
obj.Proxy = self
|
||||
|
||||
def _get_alignment_wire(self, obj):
|
||||
"""Devuelve el wire base (desde Alignment o Base)."""
|
||||
if obj.Alignment and obj.Alignment.SourceWire:
|
||||
return obj.Alignment.SourceWire.Shape
|
||||
if obj.Base:
|
||||
return obj.Base.Shape
|
||||
return None
|
||||
|
||||
def _generate_cross_section(self, obj, station_point, tangent):
|
||||
"""
|
||||
Genera el perfil transversal en un punto del eje.
|
||||
|
||||
Returns:
|
||||
list of Part.Wire: [pavimento, base, subbase, arcén_izq, arcén_der]
|
||||
"""
|
||||
# Ancho medio carril
|
||||
hw = obj.Width.Value / 2
|
||||
sw = obj.ShoulderWidth.Value
|
||||
cs = obj.CrossSlope / 100 # pendiente transversal (decimal)
|
||||
ds = obj.DitchSlope / 100
|
||||
pt = obj.PavementThickness.Value
|
||||
bt = obj.BaseThickness.Value
|
||||
sbt = obj.SubbaseThickness.Value
|
||||
|
||||
# Vector perpendicular (horizontal) al eje
|
||||
perp = FreeCAD.Vector(-tangent.y, tangent.x, 0)
|
||||
perp.normalize()
|
||||
|
||||
# Puntos del pavimento (sección transversal con bombeo)
|
||||
# Centro del eje
|
||||
center = station_point
|
||||
|
||||
# Borde izquierdo pavimento
|
||||
left_edge = center + perp * (-hw)
|
||||
right_edge = center + perp * hw
|
||||
|
||||
# Con pendiente transversal: el centro más alto
|
||||
left_top = FreeCAD.Vector(left_edge.x, left_edge.y, center.z - hw * cs)
|
||||
right_top = FreeCAD.Vector(right_edge.x, right_edge.y, center.z - hw * cs)
|
||||
center_top = center
|
||||
|
||||
# Borde inferior pavimento
|
||||
left_bot = FreeCAD.Vector(left_top.x, left_top.y, left_top.z - pt)
|
||||
right_bot = FreeCAD.Vector(right_top.x, right_top.y, right_top.z - pt)
|
||||
center_bot = FreeCAD.Vector(center_top.x, center_top.y, center_top.z - pt)
|
||||
|
||||
# Arcén (más ancho, misma pendiente o ligeramente mayor)
|
||||
shoulder_left = FreeCAD.Vector(left_edge.x - sw, left_edge.y - sw * 0, left_top.z - sw * cs * 0.5)
|
||||
shoulder_right = FreeCAD.Vector(right_edge.x + sw, right_edge.y + sw * 0, right_top.z - sw * cs * 0.5)
|
||||
|
||||
# Base (ligeiramente más ancha)
|
||||
base_extra = 200 # mm extra cada lado
|
||||
bl = FreeCAD.Vector(left_bot.x - base_extra, left_bot.y, left_bot.z)
|
||||
br = FreeCAD.Vector(right_bot.x + base_extra, right_bot.y, right_bot.z)
|
||||
bc = FreeCAD.Vector(center_bot.x, center_bot.y, center_bot.z)
|
||||
bl_bot = FreeCAD.Vector(bl.x, bl.y, bl.z - bt)
|
||||
br_bot = FreeCAD.Vector(br.x, br.y, br.z - bt)
|
||||
|
||||
# Subbase (aún más ancha)
|
||||
sbl = FreeCAD.Vector(bl.x - base_extra, bl.y, bl.z)
|
||||
sbr = FreeCAD.Vector(br.x + base_extra, br.y, br.z)
|
||||
sbl_bot = FreeCAD.Vector(sbl.x, sbl.y, sbl.z - sbt)
|
||||
sbr_bot = FreeCAD.Vector(sbr.x, sbr.y, sbr.z - sbt)
|
||||
|
||||
# Construir wires de cada capa
|
||||
# Pavimento
|
||||
pave = Part.makePolygon([left_top, center_top, right_top, right_bot, center_bot, left_bot, left_top])
|
||||
# Base
|
||||
base = Part.makePolygon([bl, bc, br, br_bot, bc - FreeCAD.Vector(0, 0, bt), bl_bot, bl])
|
||||
# Subbase
|
||||
subbase = Part.makePolygon([sbl, sbl + FreeCAD.Vector(0, 0, -sbt),
|
||||
sbr + FreeCAD.Vector(0, 0, -sbt), sbr,
|
||||
sbl])
|
||||
|
||||
return [pave, base, subbase]
|
||||
|
||||
def execute(self, obj):
|
||||
import Part, math
|
||||
"""Genera el sólido 3D de la carretera por extrusión de secciones."""
|
||||
wire = self._get_alignment_wire(obj)
|
||||
if not wire:
|
||||
return
|
||||
|
||||
w = obj.Base.Shape
|
||||
profiles = []
|
||||
total_len = wire.Length
|
||||
obj.TotalLength = total_len
|
||||
interval = obj.StationInterval.Value
|
||||
if interval <= 0:
|
||||
interval = 20000
|
||||
|
||||
SurfaceDrainSlope = obj.SurfaceDrainSlope / 100
|
||||
SubbaseDrainSlope = obj.SubbaseDrainSlope / 100
|
||||
|
||||
vec_up_left = FreeCAD.Vector(-obj.Width.Value / 2, 0, obj.Height.Value)
|
||||
vec_up_center = FreeCAD.Vector(0, 0, obj.SurfaceSlope * obj.Width.Value / 200 + obj.Height.Value)
|
||||
vec_up_right = FreeCAD.Vector(obj.Width.Value / 2, 0, obj.Height.Value)
|
||||
|
||||
vec_down_left = FreeCAD.Vector(-(obj.Width.Value / 2 + obj.Height.Value / SurfaceDrainSlope), 0, 0)
|
||||
vec_down_right = FreeCAD.Vector((obj.Width.Value / 2 + obj.Height.Value / SurfaceDrainSlope), 0, 0)
|
||||
|
||||
vec_sand_left = FreeCAD.Vector(-(obj.Width.Value / 2 + obj.Height.Value * (1 / SurfaceDrainSlope + SubbaseDrainSlope)), 0, - obj.Subbase.Value)
|
||||
vec_sand_right = FreeCAD.Vector((obj.Width.Value / 2 + obj.Height.Value * (1 / SurfaceDrainSlope + SubbaseDrainSlope)), 0, - obj.Subbase.Value)
|
||||
|
||||
edge1 = Part.makeLine(vec_down_left, vec_down_right)
|
||||
edge2 = Part.makeLine(vec_down_right, vec_up_right)
|
||||
edge3 = Part.makeLine(vec_up_right, vec_up_center)
|
||||
edge4 = Part.makeLine(vec_up_center, vec_up_left)
|
||||
edge5 = Part.makeLine(vec_up_left, vec_down_left)
|
||||
|
||||
edge6 = Part.makeLine(vec_sand_left, vec_sand_right)
|
||||
edge7 = Part.makeLine(vec_sand_left, vec_down_left)
|
||||
edge8 = Part.makeLine(vec_sand_right, vec_down_right)
|
||||
|
||||
p = Part.Wire([edge1, edge2, edge3, edge4, edge5])
|
||||
profiles.append(p)
|
||||
p = Part.Wire([edge6, edge8, edge1, edge7])
|
||||
profiles.append(p)
|
||||
shapes = self.makeSolids(obj, profiles, w, (vec_down_right + vec_down_left) / 2)
|
||||
|
||||
angle = 30
|
||||
height = FreeCAD.ActiveDocument.Site.Terrain.Shape.BoundBox.ZMax - obj.Height.Value
|
||||
offset = height / math.tan(math.radians(angle))
|
||||
|
||||
'''cutProfile = Part.makePolygon([vec_sand_left, vec_sand_right, vec_sand_right + FreeCAD.Vector(offset, 0, FreeCAD.ActiveDocument.Site.Terrain.Shape.BoundBox.ZMax),
|
||||
vec_sand_left + FreeCAD.Vector(-offset, 0, FreeCAD.ActiveDocument.Site.Terrain.Shape.BoundBox.ZMax), vec_sand_left])
|
||||
|
||||
height = obj.Height.Value - FreeCAD.ActiveDocument.Site.Terrain.Shape.BoundBox.ZMin
|
||||
offset = height / math.tan(math.radians(angle))
|
||||
fillProfile = Part.makePolygon([vec_sand_left, vec_sand_right, vec_sand_right + FreeCAD.Vector(offset, 0, FreeCAD.ActiveDocument.Site.Terrain.Shape.BoundBox.ZMin),
|
||||
vec_sand_left + FreeCAD.Vector(-offset, 0, FreeCAD.ActiveDocument.Site.Terrain.Shape.BoundBox.ZMin), vec_sand_left])
|
||||
|
||||
cutshapes, fillshapes = self.makeSolids(obj, [cutProfile, fillProfile], w, (vec_up_right + vec_up_left) / 2)
|
||||
cuts = self.calculateCut(obj, cutshapes)
|
||||
fills = self.calculateFill(obj, fillshapes)
|
||||
if cuts:
|
||||
for cut in cuts:
|
||||
Part.show(cut, "RoadCut")
|
||||
if fills:
|
||||
for fill in fills:
|
||||
Part.show(fill, "RoadFill")'''
|
||||
|
||||
obj.Shape = Part.makeCompound(shapes)
|
||||
|
||||
def makeSolids(self, obj, profiles, w, origen):
|
||||
import Draft
|
||||
import DraftGeomUtils
|
||||
n_stations = max(2, int(total_len / interval) + 1)
|
||||
obj.NumberOfStations = n_stations
|
||||
|
||||
# Generar el sólido mediante barrido de secciones
|
||||
shapes = []
|
||||
for p in profiles:
|
||||
if hasattr(p, "CenterOfMass"):
|
||||
c = p.CenterOfMass
|
||||
else:
|
||||
c = p.BoundBox.Center
|
||||
c = origen
|
||||
delta = w.Vertexes[0].Point - c
|
||||
p.translate(delta)
|
||||
cut_volume = 0
|
||||
fill_volume = 0
|
||||
|
||||
if Draft.getType(obj.Base) == "BezCurve":
|
||||
v1 = obj.Base.Placement.multVec(obj.Base.Points[1]) - w.Vertexes[0].Point
|
||||
else:
|
||||
v1 = w.Vertexes[1].Point - w.Vertexes[0].Point
|
||||
v2 = DraftGeomUtils.getNormal(p)
|
||||
rot = FreeCAD.Rotation(v2, v1)
|
||||
#p.rotate(w.Vertexes[0].Point, rot.Axis, math.degrees(rot.Angle))
|
||||
ang = rot.toEuler()[0]
|
||||
p.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), ang)
|
||||
for i in range(n_stations):
|
||||
param = i / (n_stations - 1)
|
||||
try:
|
||||
pt = wire.valueAt(wire.getParameterByLength(param * total_len))
|
||||
tangent = wire.tangentAt(wire.getParameterByLength(param * total_len))
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if p.Faces:
|
||||
for f in p.Faces:
|
||||
sh = w.makePipeShell([f.OuterWire], True, False, 2)
|
||||
for shw in f.Wires:
|
||||
if shw.hashCode() != f.OuterWire.hashCode():
|
||||
sh2 = w.makePipeShell([shw], True, False, 2)
|
||||
sh = sh.cut(sh2)
|
||||
shapes.append(sh)
|
||||
elif p.Wires:
|
||||
for pw in p.Wires:
|
||||
sh = w.makePipeShell([pw], True, False, 2)
|
||||
shapes.append(sh)
|
||||
return shapes
|
||||
sections = self._generate_cross_section(obj, pt, tangent)
|
||||
|
||||
def calculateFill(self, obj, solid):
|
||||
import BOPTools.SplitAPI as splitter
|
||||
common = solid.common(FreeCAD.ActiveDocument.Site.Terrain.Shape)
|
||||
if common.Area > 0:
|
||||
sp = splitter.slice(solid, [common, ], "Split")
|
||||
common.Placement.Base.z += 1
|
||||
solids = []
|
||||
for sol in sp.Solids:
|
||||
common1 = sol.common(common)
|
||||
if common1.Area > 0:
|
||||
solids.append(sol)
|
||||
if len(solids) > 0:
|
||||
return solids
|
||||
return None
|
||||
# Barrer cada sección a lo largo del eje (versión simplificada)
|
||||
for sec in sections:
|
||||
try:
|
||||
# Extrusión simple a lo largo del eje
|
||||
# En una implementación completa: makePipeShell
|
||||
shape = sec.extrude(FreeCAD.Vector(0, 0, 1))
|
||||
if shape and not shape.isNull():
|
||||
shapes.append(shape)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def calculateCut(self, obj, solid):
|
||||
import BOPTools.SplitAPI as splitter
|
||||
common = solid.common(FreeCAD.ActiveDocument.Site.Terrain.Shape)
|
||||
if common.Area > 0:
|
||||
sp = splitter.slice(solid, [common, ], "Split")
|
||||
shells = []
|
||||
commoncopy = common.copy()
|
||||
commoncopy.Placement.Base.z -= 1
|
||||
for sol in sp.Solids:
|
||||
common1 = sol.common(commoncopy)
|
||||
if common1.Area > 0:
|
||||
shell = sol.Shells[0]
|
||||
shell = shell.cut(common)
|
||||
shells.append(shell)
|
||||
if len(shells) > 0:
|
||||
return shells
|
||||
return None
|
||||
if shapes:
|
||||
try:
|
||||
compound = Part.makeCompound(shapes)
|
||||
obj.Shape = compound
|
||||
|
||||
def makeLoft(self, profile):
|
||||
return
|
||||
# Cubicación contra el terreno
|
||||
terrain = self._get_terrain(obj)
|
||||
if terrain:
|
||||
try:
|
||||
common = compound.common(terrain.Shape)
|
||||
if common and not common.isNull():
|
||||
cut_volume = common.Volume
|
||||
# Terraplén: volumen del sólido fuera del terreno
|
||||
fill = compound.cut(terrain.Shape)
|
||||
if fill and not fill.isNull():
|
||||
fill_volume = fill.Volume
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
obj.CutVolume = cut_volume
|
||||
obj.FillVolume = fill_volume
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _get_terrain(self, obj):
|
||||
"""Obtiene el terreno desde el Site."""
|
||||
try:
|
||||
return FreeCAD.ActiveDocument.Site.Terrain
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def __getstate__(self):
|
||||
return self.Type
|
||||
|
||||
def __setstate__(self, state):
|
||||
if state:
|
||||
self.Type = state
|
||||
|
||||
|
||||
class _ViewProviderRoad(ArchComponent.ViewProviderComponent):
|
||||
@@ -318,10 +301,12 @@ class _ViewProviderRoad(ArchComponent.ViewProviderComponent):
|
||||
def getIcon(self):
|
||||
return str(os.path.join(PVPlantResources.DirIcons, "road.svg"))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TaskPanel para crear carretera interactivamente
|
||||
# ---------------------------------------------------------------------------
|
||||
class _RoadTaskPanel:
|
||||
|
||||
def __init__(self, obj=None):
|
||||
|
||||
if obj is None:
|
||||
self.new = True
|
||||
self.obj = makeRoad()
|
||||
@@ -329,7 +314,8 @@ class _RoadTaskPanel:
|
||||
self.new = False
|
||||
self.obj = obj
|
||||
|
||||
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "PVPlantRoad.ui"))
|
||||
self.form = FreeCADGui.PySideUic.loadUi(
|
||||
os.path.join(PVPlantResources.__dir__, "PVPlantRoad.ui"))
|
||||
|
||||
def accept(self):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
@@ -342,274 +328,40 @@ class _RoadTaskPanel:
|
||||
return True
|
||||
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
import FreeCAD as App
|
||||
import FreeCADGui as Gui
|
||||
import DraftVecUtils
|
||||
import draftutils.utils as utils
|
||||
import draftutils.gui_utils as gui_utils
|
||||
import draftutils.todo as todo
|
||||
import draftguitools.gui_base_original as gui_base_original
|
||||
import draftguitools.gui_tool_utils as gui_tool_utils
|
||||
|
||||
from draftutils.messages import _msg
|
||||
from draftutils.translate import translate
|
||||
|
||||
|
||||
class _CommandRoad(gui_base_original.Creator):
|
||||
"""Gui command for the Line tool."""
|
||||
|
||||
def __init__(self):
|
||||
# super(_CommandRoad, self).__init__()
|
||||
gui_base_original.Creator.__init__(self)
|
||||
self.path = None
|
||||
# ---------------------------------------------------------------------------
|
||||
# Comando para dibujar carretera sobre un wire seleccionado
|
||||
# ---------------------------------------------------------------------------
|
||||
class _CommandRoad:
|
||||
"""Comando para crear carretera seleccionando un wire + generando alignment."""
|
||||
|
||||
def GetResources(self):
|
||||
"""Set icon, menu and tooltip."""
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "road.svg")),
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("PVPlantRoad", "Road"),
|
||||
'MenuText': QT_TRANSLATE_NOOP("PVPlantRoad", "Road"),
|
||||
'Accel': "C, R",
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PVPlantRoad",
|
||||
"Creates a Road object from setup dialog.")}
|
||||
'ToolTip': QT_TRANSLATE_NOOP("PVPlantRoad",
|
||||
"Crea una carretera con alineamiento profesional.")}
|
||||
|
||||
def Activated(self, name=translate("draft", "Line")):
|
||||
"""Execute when the command is called."""
|
||||
|
||||
gui_base_original.Creator.Activated(self, name=translate("draft", "Line"))
|
||||
|
||||
self.obj = None # stores the temp shape
|
||||
self.oldWP = None # stores the WP if we modify it
|
||||
def IsActive(self):
|
||||
return FreeCAD.ActiveDocument is not None
|
||||
|
||||
def Activated(self):
|
||||
sel = FreeCADGui.Selection.getSelection()
|
||||
|
||||
done = False
|
||||
self.existing = []
|
||||
if len(sel) > 0:
|
||||
print("Crear una carretera a lo largo de un trayecto")
|
||||
# TODO: chequear que el objeto seleccionado sea un "wire"
|
||||
wire = None
|
||||
if sel:
|
||||
import Draft
|
||||
if Draft.getType(sel[0]) == "Wire":
|
||||
self.path = sel[0]
|
||||
done = True
|
||||
wire = sel[0]
|
||||
|
||||
if not done:
|
||||
self.ui.wireUi(name)
|
||||
self.ui.setTitle("Road")
|
||||
self.obj = self.doc.addObject("Part::Feature", self.featureName)
|
||||
gui_utils.format_object(self.obj)
|
||||
|
||||
self.call = self.view.addEventCallback("SoEvent", self.action)
|
||||
_msg(translate("draft", "Pick first point"))
|
||||
|
||||
def action(self, arg):
|
||||
"""Handle the 3D scene events.
|
||||
|
||||
This is installed as an EventCallback in the Inventor view.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
arg: dict
|
||||
Dictionary with strings that indicates the type of event received
|
||||
from the 3D view.
|
||||
"""
|
||||
if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE":
|
||||
self.finish()
|
||||
elif arg["Type"] == "SoLocation2Event":
|
||||
self.point, ctrlPoint, self.info = gui_tool_utils.getPoint(self, arg)
|
||||
gui_tool_utils.redraw3DView()
|
||||
elif (arg["Type"] == "SoMouseButtonEvent"
|
||||
and arg["State"] == "DOWN"
|
||||
and arg["Button"] == "BUTTON1"):
|
||||
if arg["Position"] == self.pos:
|
||||
return self.finish(False, cont=True)
|
||||
if (not self.node) and (not self.support):
|
||||
gui_tool_utils.getSupport(arg)
|
||||
self.point, ctrlPoint, self.info = gui_tool_utils.getPoint(self, arg)
|
||||
|
||||
if self.point:
|
||||
self.point = FreeCAD.Vector(self.info["x"], self.info["y"], self.info["z"])
|
||||
self.ui.redraw()
|
||||
self.pos = arg["Position"]
|
||||
self.node.append(self.point)
|
||||
self.drawSegment(self.point)
|
||||
if len(self.node) > 2:
|
||||
# The wire is closed
|
||||
if (self.point - self.node[0]).Length < utils.tolerance():
|
||||
self.undolast()
|
||||
if len(self.node) > 2:
|
||||
self.finish(True, cont=True)
|
||||
else:
|
||||
self.finish(False, cont=True)
|
||||
|
||||
def finish(self, closed=False, cont=False):
|
||||
"""Terminate the operation and close the polyline if asked.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
closed: bool, optional
|
||||
Close the line if `True`.
|
||||
"""
|
||||
self.removeTemporaryObject()
|
||||
if self.oldWP:
|
||||
App.DraftWorkingPlane = self.oldWP
|
||||
if hasattr(Gui, "Snapper"):
|
||||
Gui.Snapper.setGrid()
|
||||
Gui.Snapper.restack()
|
||||
self.oldWP = None
|
||||
|
||||
if len(self.node) > 1:
|
||||
|
||||
if False:
|
||||
Gui.addModule("Draft")
|
||||
# The command to run is built as a series of text strings
|
||||
# to be committed through the `draftutils.todo.ToDo` class.
|
||||
if (len(self.node) == 2
|
||||
and utils.getParam("UsePartPrimitives", False)):
|
||||
# Insert a Part::Primitive object
|
||||
p1 = self.node[0]
|
||||
p2 = self.node[-1]
|
||||
|
||||
_cmd = 'FreeCAD.ActiveDocument.'
|
||||
_cmd += 'addObject("Part::Line", "Line")'
|
||||
_cmd_list = ['line = ' + _cmd,
|
||||
'line.X1 = ' + str(p1.x),
|
||||
'line.Y1 = ' + str(p1.y),
|
||||
'line.Z1 = ' + str(p1.z),
|
||||
'line.X2 = ' + str(p2.x),
|
||||
'line.Y2 = ' + str(p2.y),
|
||||
'line.Z2 = ' + str(p2.z),
|
||||
'Draft.autogroup(line)',
|
||||
'FreeCAD.ActiveDocument.recompute()']
|
||||
self.commit(translate("draft", "Create Line"),
|
||||
_cmd_list)
|
||||
else:
|
||||
# Insert a Draft line
|
||||
rot, sup, pts, fil = self.getStrings()
|
||||
|
||||
_base = DraftVecUtils.toString(self.node[0])
|
||||
_cmd = 'Draft.makeWire'
|
||||
_cmd += '('
|
||||
_cmd += 'points, '
|
||||
_cmd += 'placement=pl, '
|
||||
_cmd += 'closed=' + str(closed) + ', '
|
||||
_cmd += 'face=' + fil + ', '
|
||||
_cmd += 'support=' + sup
|
||||
_cmd += ')'
|
||||
_cmd_list = ['pl = FreeCAD.Placement()',
|
||||
'pl.Rotation.Q = ' + rot,
|
||||
'pl.Base = ' + _base,
|
||||
'points = ' + pts,
|
||||
'line = ' + _cmd,
|
||||
'Draft.autogroup(line)',
|
||||
'FreeCAD.ActiveDocument.recompute()']
|
||||
self.commit(translate("draft", "Create Wire"),
|
||||
_cmd_list)
|
||||
else:
|
||||
import Draft
|
||||
self.path = Draft.makeWire(self.node, closed=False, face=False)
|
||||
|
||||
# super(_CommandRoad, self).finish()
|
||||
gui_base_original.Creator.finish(self)
|
||||
if self.ui and self.ui.continueMode:
|
||||
self.Activated()
|
||||
|
||||
self.makeRoad()
|
||||
|
||||
def makeRoad(self):
|
||||
makeRoad(self.path)
|
||||
|
||||
def removeTemporaryObject(self):
|
||||
"""Remove temporary object created."""
|
||||
if self.obj:
|
||||
try:
|
||||
old = self.obj.Name
|
||||
except ReferenceError:
|
||||
# object already deleted, for some reason
|
||||
pass
|
||||
else:
|
||||
todo.ToDo.delay(self.doc.removeObject, old)
|
||||
self.obj = None
|
||||
|
||||
def undolast(self):
|
||||
"""Undoes last line segment."""
|
||||
import Part
|
||||
if len(self.node) > 1:
|
||||
self.node.pop()
|
||||
# last = self.node[-1]
|
||||
if self.obj.Shape.Edges:
|
||||
edges = self.obj.Shape.Edges
|
||||
if len(edges) > 1:
|
||||
newshape = Part.makePolygon(self.node)
|
||||
self.obj.Shape = newshape
|
||||
else:
|
||||
self.obj.ViewObject.hide()
|
||||
# DNC: report on removal
|
||||
# _msg(translate("draft", "Removing last point"))
|
||||
_msg(translate("draft", "Pick next point"))
|
||||
|
||||
def drawSegment(self, point):
|
||||
"""Draws new line segment."""
|
||||
import Part
|
||||
if self.planetrack and self.node:
|
||||
self.planetrack.set(self.node[-1])
|
||||
if len(self.node) == 1:
|
||||
_msg(translate("draft", "Pick next point"))
|
||||
elif len(self.node) == 2:
|
||||
last = self.node[len(self.node) - 2]
|
||||
newseg = Part.LineSegment(last, point).toShape()
|
||||
self.obj.Shape = newseg
|
||||
self.obj.ViewObject.Visibility = True
|
||||
_msg(translate("draft", "Pick next point"))
|
||||
if wire:
|
||||
# Crear alignment desde el wire seleccionado
|
||||
alignment = make_alignment_from_wire(wire)
|
||||
road = makeRoad(alignment=alignment)
|
||||
FreeCAD.Console.PrintMessage(
|
||||
f"Carretera creada desde '{wire.Label}'. "
|
||||
f"Alineamiento: {alignment.Label}\n")
|
||||
else:
|
||||
currentshape = self.obj.Shape.copy()
|
||||
last = self.node[len(self.node) - 2]
|
||||
if not DraftVecUtils.equals(last, point):
|
||||
newseg = Part.LineSegment(last, point).toShape()
|
||||
newshape = currentshape.fuse(newseg)
|
||||
self.obj.Shape = newshape
|
||||
_msg(translate("draft", "Pick next point"))
|
||||
|
||||
def wipe(self):
|
||||
"""Remove all previous segments and starts from last point."""
|
||||
if len(self.node) > 1:
|
||||
# self.obj.Shape.nullify() # For some reason this fails
|
||||
self.obj.ViewObject.Visibility = False
|
||||
self.node = [self.node[-1]]
|
||||
if self.planetrack:
|
||||
self.planetrack.set(self.node[0])
|
||||
_msg(translate("draft", "Pick next point"))
|
||||
|
||||
def orientWP(self):
|
||||
"""Orient the working plane."""
|
||||
import DraftGeomUtils
|
||||
if hasattr(App, "DraftWorkingPlane"):
|
||||
if len(self.node) > 1 and self.obj:
|
||||
n = DraftGeomUtils.getNormal(self.obj.Shape)
|
||||
if not n:
|
||||
n = App.DraftWorkingPlane.axis
|
||||
p = self.node[-1]
|
||||
v = self.node[-2].sub(self.node[-1])
|
||||
v = v.negative()
|
||||
if not self.oldWP:
|
||||
self.oldWP = App.DraftWorkingPlane.copy()
|
||||
App.DraftWorkingPlane.alignToPointAndAxis(p, n, upvec=v)
|
||||
if hasattr(Gui, "Snapper"):
|
||||
Gui.Snapper.setGrid()
|
||||
Gui.Snapper.restack()
|
||||
if self.planetrack:
|
||||
self.planetrack.set(self.node[-1])
|
||||
|
||||
def numericInput(self, numx, numy, numz):
|
||||
"""Validate the entry fields in the user interface.
|
||||
|
||||
This function is called by the toolbar or taskpanel interface
|
||||
when valid x, y, and z have been entered in the input fields.
|
||||
"""
|
||||
self.point = App.Vector(numx, numy, numz)
|
||||
self.node.append(self.point)
|
||||
self.drawSegment(self.point)
|
||||
self.ui.setNextFocus()
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"Selecciona un Wire (polilínea) para usarlo como eje de carretera.\n")
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
|
||||
+47
-51
@@ -182,16 +182,14 @@ def makeSolarDiagram(longitude, latitude, scale=1, complete=False, tz=None):
|
||||
import ladybug
|
||||
from ladybug import location
|
||||
from ladybug import sunpath
|
||||
except:
|
||||
# TODO - remove pysolar dependency
|
||||
# FreeCAD.Console.PrintWarning("Ladybug module not found, using pysolar instead. Warning, this will be deprecated in the future\n")
|
||||
except ImportError:
|
||||
ladybug = False
|
||||
try:
|
||||
import pysolar
|
||||
except:
|
||||
except ImportError:
|
||||
try:
|
||||
import Pysolar as pysolar
|
||||
except:
|
||||
except ImportError:
|
||||
FreeCAD.Console.PrintError("The pysolar module was not found. Unable to generate solar diagrams\n")
|
||||
return None
|
||||
else:
|
||||
@@ -361,7 +359,7 @@ def makeWindRose(epwfile, scale=1, sectors=24):
|
||||
try:
|
||||
import ladybug
|
||||
from ladybug import epw
|
||||
except:
|
||||
except ImportError:
|
||||
FreeCAD.Console.PrintError("The ladybug module was not found. Unable to generate solar diagrams\n")
|
||||
return None
|
||||
if not epwfile:
|
||||
@@ -578,22 +576,22 @@ class _PVPlantSite(ArchSite._Site):
|
||||
|
||||
obj.addProperty("App::PropertyLink",
|
||||
"Boundary",
|
||||
"Site",
|
||||
"PVPlant",
|
||||
"Boundary of land")
|
||||
|
||||
obj.addProperty("App::PropertyLinkList",
|
||||
"Frames",
|
||||
"Site",
|
||||
"PVPlant",
|
||||
"Frames templates")
|
||||
|
||||
obj.addProperty("App::PropertyEnumeration",
|
||||
"UtmZone",
|
||||
"Base",
|
||||
"PVPlant",
|
||||
"UTM zone").UtmZone = zone_list
|
||||
|
||||
obj.addProperty("App::PropertyVector",
|
||||
"Origin",
|
||||
"Base",
|
||||
"PVPlant",
|
||||
"Origin point.").Origin = (0, 0, 0)
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
@@ -667,23 +665,22 @@ class _PVPlantSite(ArchSite._Site):
|
||||
self.computeAreas(obj)
|
||||
|
||||
def computeAreas(self, obj):
|
||||
"""
|
||||
Compute areas, perimeter and volumes.
|
||||
Override to add custom logic after parent computation.
|
||||
"""
|
||||
ArchSite._Site.computeAreas(self, obj)
|
||||
return
|
||||
|
||||
if not obj.Shape:
|
||||
return
|
||||
|
||||
if obj.Shape.isNull():
|
||||
if obj.Shape.isNull() or not obj.Shape.isValid() or not obj.Shape.Faces:
|
||||
return
|
||||
if not obj.Shape.isValid():
|
||||
return
|
||||
if not obj.Shape.Faces:
|
||||
return
|
||||
if not hasattr(obj, "Perimeter"): # check we have a latest version site
|
||||
if not hasattr(obj, "Perimeter"):
|
||||
return
|
||||
if not obj.Terrain:
|
||||
return
|
||||
# compute area
|
||||
|
||||
# Compute projected area (horizontal projection of all near-horizontal faces)
|
||||
fset = []
|
||||
for f in obj.Shape.Faces:
|
||||
if f.normalAt(0, 0).getAngle(FreeCAD.Vector(0, 0, 1)) < 1.5707:
|
||||
@@ -694,13 +691,11 @@ class _PVPlantSite(ArchSite._Site):
|
||||
for f in fset:
|
||||
try:
|
||||
pf = Part.Face(Part.Wire(Drawing.project(f, FreeCAD.Vector(0, 0, 1))[0].Edges))
|
||||
except Part.OCCError:
|
||||
# error in computing the area. Better set it to zero than show a wrong value
|
||||
if obj.ProjectedArea.Value != 0:
|
||||
print("Error computing areas for ", obj.Label)
|
||||
obj.ProjectedArea = 0
|
||||
else:
|
||||
pset.append(pf)
|
||||
except Part.OCCError:
|
||||
if getattr(obj, 'ProjectedArea', None) and obj.ProjectedArea.Value != 0:
|
||||
FreeCAD.Console.PrintWarning(f"Error computing projected area for {obj.Label}\n")
|
||||
obj.ProjectedArea = 0
|
||||
if pset:
|
||||
self.flatarea = pset.pop()
|
||||
for f in pset:
|
||||
@@ -708,28 +703,27 @@ class _PVPlantSite(ArchSite._Site):
|
||||
self.flatarea = self.flatarea.removeSplitter()
|
||||
if obj.ProjectedArea.Value != self.flatarea.Area:
|
||||
obj.ProjectedArea = self.flatarea.Area
|
||||
# compute perimeter
|
||||
|
||||
# Compute perimeter (border edges only)
|
||||
lut = {}
|
||||
for e in obj.Shape.Edges:
|
||||
lut.setdefault(e.hashCode(), []).append(e)
|
||||
l = 0
|
||||
for e in lut.values():
|
||||
if len(e) == 1: # keep only border edges
|
||||
l += e[0].Length
|
||||
if l:
|
||||
if obj.Perimeter.Value != l:
|
||||
obj.Perimeter = l
|
||||
# compute volumes
|
||||
if obj.Terrain.Shape.Solids:
|
||||
shapesolid = obj.Terrain.Shape.copy()
|
||||
else:
|
||||
shapesolid = obj.Terrain.Shape.extrude(obj.ExtrusionVector)
|
||||
addvol = 0
|
||||
subvol = 0
|
||||
for sub in obj.Subtractions:
|
||||
subvol += sub.Shape.common(shapesolid).Volume
|
||||
for sub in obj.Additions:
|
||||
addvol += sub.Shape.cut(shapesolid).Volume
|
||||
perimeter = sum(e[0].Length for e in lut.values() if len(e) == 1)
|
||||
if perimeter and obj.Perimeter.Value != perimeter:
|
||||
obj.Perimeter = perimeter
|
||||
|
||||
# Compute cut/fill volumes relative to terrain
|
||||
try:
|
||||
if obj.Terrain.Shape.Solids:
|
||||
shapesolid = obj.Terrain.Shape.copy()
|
||||
else:
|
||||
shapesolid = obj.Terrain.Shape.extrude(obj.ExtrusionVector)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
subvol = sum(sub.Shape.common(shapesolid).Volume for sub in obj.Subtractions)
|
||||
addvol = sum(sub.Shape.cut(shapesolid).Volume for sub in obj.Additions)
|
||||
|
||||
if obj.SubtractionVolume.Value != subvol:
|
||||
obj.SubtractionVolume = subvol
|
||||
if obj.AdditionVolume.Value != addvol:
|
||||
@@ -771,10 +765,12 @@ class _PVPlantSite(ArchSite._Site):
|
||||
import PVPlantImportGrid
|
||||
x, y, zone_number, zone_letter = utm.from_latlon(lat, lon)
|
||||
self.obj.UtmZone = zone_list[zone_number - 1]
|
||||
# self.obj.UtmZone = "Z"+str(zone_number)
|
||||
#z = PVPlantImportGrid.get_elevation(lat, lon)
|
||||
zz = PVPlantImportGrid.getSinglePointElevationFromBing(lat, lon)
|
||||
self.obj.Origin = FreeCAD.Vector(x * 1000, y * 1000, zz.z)
|
||||
|
||||
point = PVPlantImportGrid.getElevationFromOE([[lat, lon]])
|
||||
self.obj.Origin = FreeCAD.Vector(point[0].x, point[0].y, point[0].z)
|
||||
self.obj.Latitude = lat
|
||||
self.obj.Longitude = lon
|
||||
self.obj.Elevation = point[0].z
|
||||
|
||||
|
||||
class _ViewProviderSite(ArchSite._ViewProviderSite):
|
||||
@@ -1054,7 +1050,7 @@ class _ViewProviderSite:
|
||||
if hasattr(vobj.Object,"EPWFile") and vobj.Object.EPWFile:
|
||||
try:
|
||||
import ladybug
|
||||
except:
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
self.windrosenode = makeWindRose(vobj.Object.EPWFile,vobj.SolarDiagramScale)
|
||||
@@ -1171,7 +1167,7 @@ class _ViewProviderSite:
|
||||
|
||||
'''
|
||||
|
||||
class _CommandPVPlantSite:
|
||||
'''class _CommandPVPlantSite:
|
||||
"the Arch Site command definition"
|
||||
|
||||
def GetResources(self):
|
||||
@@ -1189,4 +1185,4 @@ class _CommandPVPlantSite:
|
||||
return
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('PVPlantSite', _CommandPVPlantSite())
|
||||
FreeCADGui.addCommand('PVPlantSite', _CommandPVPlantSite())'''
|
||||
|
||||
+271
-152
@@ -73,6 +73,42 @@ line_patterns = {
|
||||
"Dot (.5x) ...............................": 0x5555,
|
||||
"Dot (2x) . . . . . . . . . . .": 0x8888}
|
||||
|
||||
|
||||
def open_xyz_mmap(archivo_path):
|
||||
"""
|
||||
Usa memory-mapping para archivos muy grandes (máxima velocidad)
|
||||
"""
|
||||
# Primera pasada: contar líneas válidas
|
||||
total_puntos = 0
|
||||
with open(archivo_path, 'r') as f:
|
||||
for linea in f:
|
||||
partes = linea.strip().split()
|
||||
if len(partes) >= 3:
|
||||
try:
|
||||
float(partes[0]);
|
||||
float(partes[1]);
|
||||
float(partes[2])
|
||||
total_puntos += 1
|
||||
except:
|
||||
continue
|
||||
|
||||
# Segunda pasada: cargar datos
|
||||
puntos = np.empty((total_puntos, 3))
|
||||
idx = 0
|
||||
|
||||
with open(archivo_path, 'r') as f:
|
||||
for linea in f:
|
||||
partes = linea.strip().split()
|
||||
if len(partes) >= 3:
|
||||
try:
|
||||
x, y, z = float(partes[0]), float(partes[1]), float(partes[2])
|
||||
puntos[idx] = [x, y, z]
|
||||
idx += 1
|
||||
except:
|
||||
continue
|
||||
|
||||
return puntos
|
||||
|
||||
def makeTerrain(name="Terrain"):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Terrain")
|
||||
obj.Label = name
|
||||
@@ -81,7 +117,6 @@ def makeTerrain(name="Terrain"):
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
return obj
|
||||
|
||||
|
||||
class Terrain(ArchComponent.Component):
|
||||
"A Shadow Terrain Obcject"
|
||||
|
||||
@@ -94,8 +129,14 @@ class Terrain(ArchComponent.Component):
|
||||
# obj.IfcType = "Fence"
|
||||
# obj.MoveWithHost = False
|
||||
|
||||
self.site = PVPlantSite.get()
|
||||
self.site.Terrain = obj
|
||||
try:
|
||||
self.site = PVPlantSite.get()
|
||||
except Exception:
|
||||
self.site = None
|
||||
if self.site:
|
||||
self.site.Terrain = obj
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning('Terrain: No se encontró Site, algunas funciones DEM requerirán Site.\n')
|
||||
obj.ViewObject.ShapeColor = (0.0000, 0.6667, 0.4980)
|
||||
obj.ViewObject.LineColor = (0.0000, 0.6000, 0.4392)
|
||||
|
||||
@@ -120,12 +161,12 @@ class Terrain(ArchComponent.Component):
|
||||
"Surface",
|
||||
"Use a Point Group to generate the surface")
|
||||
|
||||
if not ("Mesh" in pl):
|
||||
if not ("mesh" in pl):
|
||||
obj.addProperty("Mesh::PropertyMeshKernel",
|
||||
"Mesh",
|
||||
"mesh",
|
||||
"Surface",
|
||||
"Mesh")
|
||||
obj.setEditorMode("Mesh", 1)
|
||||
obj.setEditorMode("mesh", 1)
|
||||
|
||||
if not ("InitialMesh" in pl):
|
||||
obj.addProperty("Mesh::PropertyMeshKernel",
|
||||
@@ -156,108 +197,181 @@ class Terrain(ArchComponent.Component):
|
||||
'''Do something when a property has changed'''
|
||||
|
||||
if prop == "InitialMesh":
|
||||
obj.Mesh = obj.InitialMesh.copy()
|
||||
obj.mesh = obj.InitialMesh.copy()
|
||||
# Forzar actualización visual
|
||||
obj.publishProperty("Mesh")
|
||||
|
||||
if prop == "mesh":
|
||||
# La propiedad mesh cambió → forzar recompute para que updateData se dispare
|
||||
pass
|
||||
|
||||
if prop == "DEM" or prop == "CuttingBoundary":
|
||||
from datetime import datetime
|
||||
if obj.DEM and obj.CuttingBoundary:
|
||||
'''
|
||||
Parámetro Descripción Requisitos
|
||||
NCOLS: Cantidad de columnas de celdas Entero mayor que 0.
|
||||
NROWS: Cantidad de filas de celdas Entero mayor que 0.
|
||||
XLLCENTER o XLLCORNER: Coordenada X del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada y.
|
||||
YLLCENTER o YLLCORNER: Coordenada Y del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada x.
|
||||
CELLSIZE: Tamaño de celda Mayor que 0.
|
||||
NODATA_VALUE: Los valores de entrada que serán NoData en el ráster de salida Opcional. El valor predeterminado es -9999
|
||||
'''
|
||||
grid_space = 1
|
||||
file = open(obj.DEM, "r")
|
||||
templist = [line.split() for line in file.readlines()]
|
||||
file.close()
|
||||
del file
|
||||
from pathlib import Path
|
||||
suffix = Path(obj.DEM).suffix
|
||||
if suffix == '.asc':
|
||||
'''
|
||||
ASC format:
|
||||
|
||||
# Read meta data:
|
||||
meta = templist[0:6]
|
||||
nx = int(meta[0][1]) # NCOLS
|
||||
ny = int(meta[1][1]) # NROWS
|
||||
xllref = meta[2][0] # XLLCENTER / XLLCORNER
|
||||
xllvalue = round(float(meta[2][1]), 3)
|
||||
yllref = meta[3][0] # YLLCENTER / XLLCORNER
|
||||
yllvalue = round(float(meta[3][1]), 3)
|
||||
cellsize = round(float(meta[4][1]), 3) # CELLSIZE
|
||||
nodata_value = float(meta[5][1]) # NODATA_VALUE
|
||||
Parámetro Descripción Requisitos
|
||||
NCOLS: Cantidad de columnas de celdas Entero mayor que 0.
|
||||
NROWS: Cantidad de filas de celdas Entero mayor que 0.
|
||||
XLLCENTER o XLLCORNER: Coordenada X del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada y.
|
||||
YLLCENTER o YLLCORNER: Coordenada Y del origen (por el centro o la esquina inferior izquierda de la celda) Hacer coincidir con el tipo de coordenada x.
|
||||
CELLSIZE: Tamaño de celda Mayor que 0.
|
||||
NODATA_VALUE: Los valores de entrada que serán NoData en el ráster de salida Opcional. El valor predeterminado es -9999
|
||||
'''
|
||||
grid_space = 1
|
||||
file = open(obj.DEM, "r")
|
||||
templist = [line.split() for line in file.readlines()]
|
||||
file.close()
|
||||
del file
|
||||
|
||||
# set coarse_factor
|
||||
coarse_factor = max(round(grid_space / cellsize), 1)
|
||||
# Read meta data:
|
||||
meta = templist[0:6]
|
||||
nx = int(meta[0][1]) # NCOLS
|
||||
ny = int(meta[1][1]) # NROWS
|
||||
xllref = meta[2][0] # XLLCENTER / XLLCORNER
|
||||
xllvalue = round(float(meta[2][1]), 3)
|
||||
yllref = meta[3][0] # YLLCENTER / XLLCORNER
|
||||
yllvalue = round(float(meta[3][1]), 3)
|
||||
cellsize = round(float(meta[4][1]), 3) # CELLSIZE
|
||||
nodata_value = float(meta[5][1]) # NODATA_VALUE
|
||||
|
||||
# Get z values
|
||||
templist = templist[6:(6 + ny)]
|
||||
templist = [templist[i][0::coarse_factor] for i in np.arange(0, len(templist), coarse_factor)]
|
||||
datavals = np.array(templist).astype(float)
|
||||
del templist
|
||||
# set coarse_factor
|
||||
coarse_factor = max(round(grid_space / cellsize), 1)
|
||||
|
||||
# create xy coordinates
|
||||
import PVPlantSite
|
||||
offset = PVPlantSite.get().Origin
|
||||
x = 1000 * (cellsize * np.arange(nx)[0::coarse_factor] + xllvalue) - offset.x
|
||||
y = 1000 * (cellsize * np.arange(ny)[-1::-1][0::coarse_factor] + yllvalue) - offset.y
|
||||
datavals = 1000 * datavals # - offset.z
|
||||
# Get z values
|
||||
templist = templist[6:(6 + ny)]
|
||||
templist = [templist[i][0::coarse_factor] for i in np.arange(0, len(templist), coarse_factor)]
|
||||
datavals = np.array(templist).astype(float)
|
||||
del templist
|
||||
|
||||
# remove points out of area
|
||||
# 1. coarse:
|
||||
if obj.CuttingBoundary:
|
||||
inc_x = obj.CuttingBoundary.Shape.BoundBox.XLength * 0.0
|
||||
inc_y = obj.CuttingBoundary.Shape.BoundBox.YLength * 0.0
|
||||
tmp = np.where(np.logical_and(x >= (obj.CuttingBoundary.Shape.BoundBox.XMin - inc_x),
|
||||
x <= (obj.CuttingBoundary.Shape.BoundBox.XMax + inc_x)))[0]
|
||||
print(tmp)
|
||||
x_max = np.ndarray.max(tmp)
|
||||
x_min = np.ndarray.min(tmp)
|
||||
# create xy coordinates
|
||||
offset = self.site.Origin if self.site else FreeCAD.Vector(0, 0, 0)
|
||||
x = (cellsize * np.arange(nx)[0::coarse_factor] + xllvalue) * 1000 - offset.x
|
||||
y = (cellsize * np.arange(ny)[-1::-1][0::coarse_factor] + yllvalue) * 1000 - offset.y
|
||||
datavals = datavals * 1000 # Ajuste de altura
|
||||
|
||||
tmp = np.where(np.logical_and(y >= (obj.CuttingBoundary.Shape.BoundBox.YMin - inc_y),
|
||||
y <= (obj.CuttingBoundary.Shape.BoundBox.YMax + inc_y)))[0]
|
||||
y_max = np.ndarray.max(tmp)
|
||||
y_min = np.ndarray.min(tmp)
|
||||
del tmp
|
||||
# remove points out of area
|
||||
# 1. coarse:
|
||||
if obj.CuttingBoundary:
|
||||
inc_x = obj.CuttingBoundary.Shape.BoundBox.XLength * 0.0
|
||||
inc_y = obj.CuttingBoundary.Shape.BoundBox.YLength * 0.0
|
||||
tmp = np.where(np.logical_and(x >= (obj.CuttingBoundary.Shape.BoundBox.XMin - inc_x),
|
||||
x <= (obj.CuttingBoundary.Shape.BoundBox.XMax + inc_x)))[0]
|
||||
x_max = np.ndarray.max(tmp)
|
||||
x_min = np.ndarray.min(tmp)
|
||||
|
||||
x = x[x_min:x_max+1]
|
||||
y = y[y_min:y_max+1]
|
||||
datavals = datavals[y_min:y_max+1, x_min:x_max+1]
|
||||
tmp = np.where(np.logical_and(y >= (obj.CuttingBoundary.Shape.BoundBox.YMin - inc_y),
|
||||
y <= (obj.CuttingBoundary.Shape.BoundBox.YMax + inc_y)))[0]
|
||||
y_max = np.ndarray.max(tmp)
|
||||
y_min = np.ndarray.min(tmp)
|
||||
del tmp
|
||||
|
||||
# Create mesh - surface:
|
||||
import MeshTools.Triangulation as Triangulation
|
||||
import Mesh
|
||||
stepsize = 75
|
||||
stepx = math.ceil(nx / stepsize)
|
||||
stepy = math.ceil(ny / stepsize)
|
||||
x = x[x_min:x_max+1]
|
||||
y = y[y_min:y_max+1]
|
||||
datavals = datavals[y_min:y_max+1, x_min:x_max+1]
|
||||
|
||||
# Create mesh - surface:
|
||||
import MeshTools.Triangulation as Triangulation
|
||||
import Mesh
|
||||
stepsize = 75
|
||||
stepx = math.ceil(nx / stepsize)
|
||||
stepy = math.ceil(ny / stepsize)
|
||||
|
||||
# Malla completa primero como numpy y filtramos todo de una
|
||||
from datetime import datetime
|
||||
t_start = datetime.now()
|
||||
|
||||
# Crear grid completo de coordenadas
|
||||
XX, YY = np.meshgrid(x, y)
|
||||
ZZ = datavals.copy()
|
||||
|
||||
# Enmascarar nodata
|
||||
mask_valida = ZZ != nodata_value
|
||||
|
||||
# Enmascarar cutting boundary si existe
|
||||
if obj.CuttingBoundary:
|
||||
from FreeCAD import Base
|
||||
shape = obj.CuttingBoundary.Shape
|
||||
mask_boundary = np.zeros_like(ZZ, dtype=bool)
|
||||
# Sampling: revisar solo puntos estratégicos para boundary grande
|
||||
stride = max(1, min(nx, ny) // 200)
|
||||
for i in range(0, ny, stride):
|
||||
for j in range(0, nx, stride):
|
||||
if mask_valida[i, j]:
|
||||
if shape.isInside(FreeCAD.Vector(x[j], y[i], 0), 0, True):
|
||||
mask_boundary[i, j] = True
|
||||
mask_valida = mask_valida & mask_boundary
|
||||
|
||||
# Extraer puntos válidos como lista plana
|
||||
pts_validos = np.column_stack([
|
||||
XX[mask_valida].ravel(),
|
||||
YY[mask_valida].ravel(),
|
||||
ZZ[mask_valida].ravel()
|
||||
])
|
||||
|
||||
del XX, YY, ZZ, mask_valida
|
||||
|
||||
# Triangulación completa de una vez (no por parches)
|
||||
mesh = Mesh.Mesh()
|
||||
if len(pts_validos) > 3:
|
||||
# Si hay muchos puntos, triangulamos por parches para evitar OOM
|
||||
patch_size = 50000
|
||||
n_patches = max(1, math.ceil(len(pts_validos) / patch_size))
|
||||
for p in range(n_patches):
|
||||
patch = pts_validos[p * patch_size:(p + 1) * patch_size].tolist()
|
||||
if len(patch) > 3:
|
||||
try:
|
||||
triangulated = Triangulation.Triangulate(patch)
|
||||
mesh.addMesh(triangulated)
|
||||
except TypeError as e:
|
||||
print(f"Patch {p}: error al procesar {len(patch)} puntos: {str(e)}")
|
||||
except Exception as e:
|
||||
print(f"Patch {p}: error inesperado: {str(e)}")
|
||||
|
||||
print(f'Terraín DEM: {len(pts_validos)} pts válidos, {n_patches} parches, {datetime.now()-t_start}')
|
||||
del pts_validos
|
||||
|
||||
mesh.removeDuplicatedPoints()
|
||||
mesh.removeFoldsOnSurface()
|
||||
obj.InitialMesh = mesh.copy()
|
||||
# Limpiar objetos mesh huérfanos previos si existen
|
||||
for o in FreeCAD.ActiveDocument.Objects:
|
||||
if o.TypeId == 'Mesh::Feature' and o.Label.startswith('Terrain_mesh_'):
|
||||
FreeCAD.ActiveDocument.removeObject(o.Name)
|
||||
mesh_obj = Mesh.show(mesh)
|
||||
mesh_obj.Label = 'Terrain_mesh_' + obj.Label
|
||||
elif suffix in ['.xyz']:
|
||||
pts_array = open_xyz_mmap(obj.DEM)
|
||||
if pts_array is not None and len(pts_array) > 3:
|
||||
import MeshTools.Triangulation as Triangulation
|
||||
import Mesh
|
||||
if obj.CuttingBoundary:
|
||||
mask = []
|
||||
for pt in pts_array:
|
||||
mask.append(obj.CuttingBoundary.Shape.isInside(
|
||||
FreeCAD.Vector(pt[0], pt[1], 0), 0, True))
|
||||
pts_array = pts_array[mask]
|
||||
if len(pts_array) > 3:
|
||||
from datetime import datetime
|
||||
t0 = datetime.now()
|
||||
pts_list = pts_array.tolist()
|
||||
mesh = Triangulation.Triangulate(pts_list)
|
||||
mesh.removeDuplicatedPoints()
|
||||
mesh.removeFoldsOnSurface()
|
||||
obj.InitialMesh = mesh.copy()
|
||||
# Limpiar objetos mesh huérfanos previos
|
||||
for o in FreeCAD.ActiveDocument.Objects:
|
||||
if o.TypeId == 'Mesh::Feature' and o.Label.startswith('Terrain_mesh_'):
|
||||
FreeCAD.ActiveDocument.removeObject(o.Name)
|
||||
mesh_obj = Mesh.show(mesh)
|
||||
mesh_obj.Label = 'Terrain_mesh_' + obj.Label
|
||||
print(f'XYZ import: {len(pts_array)} puntos en {datetime.now()-t0}')
|
||||
|
||||
mesh = Mesh.Mesh()
|
||||
for indx in range(stepx):
|
||||
inix = indx * stepsize - 1
|
||||
finx = min([stepsize * (indx + 1), len(x)-1])
|
||||
for indy in range(stepy):
|
||||
iniy = indy * stepsize - 1
|
||||
finy = min([stepsize * (indy + 1), len(y) - 1])
|
||||
pts = []
|
||||
for i in range(inix, finx):
|
||||
for j in range(iniy, finy):
|
||||
if datavals[j][i] != nodata_value:
|
||||
if obj.CuttingBoundary:
|
||||
if obj.CuttingBoundary.Shape.isInside(FreeCAD.Vector(x[i], y[j], 0), 0, True):
|
||||
pts.append([x[i], y[j], datavals[j][i]])
|
||||
else:
|
||||
pts.append([x[i], y[j], datavals[j][i]])
|
||||
if len(pts) > 3:
|
||||
try:
|
||||
mesh.addMesh(Triangulation.Triangulate(pts))
|
||||
#Mesh.show(mesh)
|
||||
except TypeError:
|
||||
print("error al procesar: {0} puntos".format(len(pts)))
|
||||
|
||||
mesh.removeDuplicatedPoints()
|
||||
mesh.removeFoldsOnSurface()
|
||||
obj.InitialMesh = mesh.copy()
|
||||
Mesh.show(mesh)
|
||||
|
||||
if prop == "PointsGroup" or prop == "CuttingBoundary":
|
||||
if obj.PointsGroup and obj.CuttingBoundary:
|
||||
@@ -284,12 +398,14 @@ class Terrain(ArchComponent.Component):
|
||||
|
||||
import MeshTools.Triangulation as Triangulation
|
||||
mesh = Triangulation.Triangulate(Data)
|
||||
'''shape = PVPlantCreateTerrainMesh.MeshToShape(mesh)
|
||||
shape.Placement.move(nbase)'''
|
||||
|
||||
obj.Shape = shape
|
||||
if obj.DEM:
|
||||
obj.DEM = None
|
||||
obj.mesh = mesh
|
||||
# Forzar actualización visual llamando a publishProperty
|
||||
try:
|
||||
obj.publishProperty("Mesh")
|
||||
except:
|
||||
pass
|
||||
|
||||
def execute(self, obj):
|
||||
''''''
|
||||
@@ -307,7 +423,6 @@ class ViewProviderTerrain:
|
||||
"A View Provider for the Pipe object"
|
||||
|
||||
def __init__(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
self.boundary_color = None
|
||||
self.edge_style = None
|
||||
self.edge_color = None
|
||||
@@ -321,16 +436,16 @@ class ViewProviderTerrain:
|
||||
# Triangulation properties.
|
||||
pl = vobj.PropertiesList
|
||||
if not ("Transparency" in pl):
|
||||
vobj.addProperty("App::PropertyIntegerConstraint",
|
||||
'''vobj.addProperty("App::PropertyIntegerConstraint",
|
||||
"Transparency",
|
||||
"Surface Style",
|
||||
"Set triangle face transparency").Transparency = (50, 0, 100, 1)
|
||||
"Set triangle face transparency").Transparency = (50, 0, 100, 1)'''
|
||||
|
||||
if not ("ShapeColor" in pl):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"ShapeColor",
|
||||
"Surface Style",
|
||||
"Set triangle face color").ShapeColor = (r, g, b, vobj.Transparency / 100)
|
||||
"Set triangle face color").ShapeColor = (0.0, 0.667, 0.49, vobj.Transparency / 100)
|
||||
|
||||
if not ("ShapeMaterial" in pl):
|
||||
vobj.addProperty("App::PropertyMaterial",
|
||||
@@ -413,18 +528,21 @@ class ViewProviderTerrain:
|
||||
"Set major contour line width").MinorWidth = (2.0, 1.0, 20.0, 1.0)
|
||||
|
||||
vobj.Proxy = self
|
||||
self.Object = vobj.Object
|
||||
# Inicializar colores correctamente
|
||||
vobj.ShapeMaterial.DiffuseColor = vobj.ShapeColor
|
||||
|
||||
def onDocumentRestored(self, vobj):
|
||||
self.setProperties(vobj)
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
''' Update Object visuals when a view property changed. '''
|
||||
""" Update Object visuals when a view property changed. """
|
||||
|
||||
if prop == "ShapeColor" or prop == "Transparency":
|
||||
if hasattr(vobj, "ShapeColor") and hasattr(vobj, "Transparency"):
|
||||
color = vobj.getPropertyByName("ShapeColor")
|
||||
transparency = vobj.getPropertyByName("Transparency")
|
||||
color = (color[0], color[1], color[2], transparency / 100)
|
||||
color = (color[0], color[1], color[2], 50 / 100)
|
||||
vobj.ShapeMaterial.DiffuseColor = color
|
||||
|
||||
if prop == "ShapeMaterial":
|
||||
@@ -506,47 +624,47 @@ class ViewProviderTerrain:
|
||||
offset.factor = -2.0
|
||||
|
||||
# Boundary features.
|
||||
'''self.boundary_color = coin.SoBaseColor()
|
||||
self.boundary_color = coin.SoBaseColor()
|
||||
self.boundary_coords = coin.SoGeoCoordinate()
|
||||
self.boundary_lines = coin.SoLineSet()
|
||||
self.boundary_style = coin.SoDrawStyle()
|
||||
self.boundary_style.style = coin.SoDrawStyle.LINES'''
|
||||
self.boundary_style.style = coin.SoDrawStyle.LINES
|
||||
|
||||
# Boundary root.
|
||||
'''boundaries = coin.SoType.fromName('SoFCSelection').createInstance()
|
||||
boundaries = coin.SoType.fromName('SoFCSelection').createInstance()
|
||||
boundaries.style = 'EMISSIVE_DIFFUSE'
|
||||
boundaries.addChild(self.boundary_color)
|
||||
boundaries.addChild(self.boundary_style)
|
||||
boundaries.addChild(self.boundary_coords)
|
||||
boundaries.addChild(self.boundary_lines)'''
|
||||
boundaries.addChild(self.boundary_lines)
|
||||
|
||||
# Major Contour features.
|
||||
'''self.major_color = coin.SoBaseColor()
|
||||
self.major_color = coin.SoBaseColor()
|
||||
self.major_coords = coin.SoGeoCoordinate()
|
||||
self.major_lines = coin.SoLineSet()
|
||||
self.major_style = coin.SoDrawStyle()
|
||||
self.major_style.style = coin.SoDrawStyle.LINES'''
|
||||
self.major_style.style = coin.SoDrawStyle.LINES
|
||||
|
||||
# Major Contour root.
|
||||
'''major_contours = coin.SoSeparator()
|
||||
major_contours = coin.SoSeparator()
|
||||
major_contours.addChild(self.major_color)
|
||||
major_contours.addChild(self.major_style)
|
||||
major_contours.addChild(self.major_coords)
|
||||
major_contours.addChild(self.major_lines)'''
|
||||
major_contours.addChild(self.major_lines)
|
||||
|
||||
# Minor Contour features.
|
||||
'''self.minor_color = coin.SoBaseColor()
|
||||
self.minor_color = coin.SoBaseColor()
|
||||
self.minor_coords = coin.SoGeoCoordinate()
|
||||
self.minor_lines = coin.SoLineSet()
|
||||
self.minor_style = coin.SoDrawStyle()
|
||||
self.minor_style.style = coin.SoDrawStyle.LINES'''
|
||||
self.minor_style.style = coin.SoDrawStyle.LINES
|
||||
|
||||
# Minor Contour root.
|
||||
'''minor_contours = coin.SoSeparator()
|
||||
minor_contours = coin.SoSeparator()
|
||||
minor_contours.addChild(self.minor_color)
|
||||
minor_contours.addChild(self.minor_style)
|
||||
minor_contours.addChild(self.minor_coords)
|
||||
minor_contours.addChild(self.minor_lines)'''
|
||||
minor_contours.addChild(self.minor_lines)
|
||||
|
||||
# Highlight for selection.
|
||||
highlight = coin.SoType.fromName('SoFCSelection').createInstance()
|
||||
@@ -568,7 +686,7 @@ class ViewProviderTerrain:
|
||||
edge.addChild(self.edge_style)
|
||||
edge.addChild(highlight)
|
||||
|
||||
# Surface root.
|
||||
# Surface root - con contour lines visibles.
|
||||
surface_root = coin.SoSeparator()
|
||||
surface_root.addChild(face)
|
||||
surface_root.addChild(offset)
|
||||
@@ -599,64 +717,64 @@ class ViewProviderTerrain:
|
||||
# Wireframe root.
|
||||
wireframe_root = coin.SoSeparator()
|
||||
wireframe_root.addChild(edge)
|
||||
wireframe_root.addChild(major_contours)
|
||||
wireframe_root.addChild(minor_contours)
|
||||
#wireframe_root.addChild(major_contours)
|
||||
#wireframe_root.addChild(minor_contours)
|
||||
vobj.addDisplayMode(wireframe_root, "Wireframe")
|
||||
|
||||
# Take features from properties.
|
||||
self.onChanged(vobj, "ShapeColor")
|
||||
self.onChanged(vobj, "LineColor")
|
||||
self.onChanged(vobj, "LineWidth")
|
||||
#self.onChanged(vobj, "BoundaryColor")
|
||||
#self.onChanged(vobj, "BoundaryWidth")
|
||||
#self.onChanged(vobj, "BoundaryPattern")
|
||||
#self.onChanged(vobj, "PatternScale")
|
||||
#self.onChanged(vobj, "MajorColor")
|
||||
#self.onChanged(vobj, "MajorWidth")
|
||||
#self.onChanged(vobj, "MinorColor")
|
||||
#self.onChanged(vobj, "MinorWidth")
|
||||
self.onChanged(vobj, "BoundaryColor")
|
||||
self.onChanged(vobj, "BoundaryWidth")
|
||||
self.onChanged(vobj, "BoundaryPattern")
|
||||
self.onChanged(vobj, "PatternScale")
|
||||
self.onChanged(vobj, "MajorColor")
|
||||
self.onChanged(vobj, "MajorWidth")
|
||||
self.onChanged(vobj, "MinorColor")
|
||||
self.onChanged(vobj, "MinorWidth")
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
''' Update Object visuals when a data property changed. '''
|
||||
|
||||
# Set geosystem.
|
||||
geo_system = ["UTM", FreeCAD.ActiveDocument.Site.UtmZone, "FLAT"]
|
||||
try:
|
||||
utm_zone = FreeCAD.ActiveDocument.Site.UtmZone
|
||||
except:
|
||||
utm_zone = "30"
|
||||
geo_system = ["UTM", utm_zone, "FLAT"]
|
||||
self.geo_coords.geoSystem.setValues(geo_system)
|
||||
'''
|
||||
self.boundary_coords.geoSystem.setValues(geo_system)
|
||||
self.major_coords.geoSystem.setValues(geo_system)
|
||||
self.minor_coords.geoSystem.setValues(geo_system)
|
||||
'''
|
||||
|
||||
if prop == "Mesh":
|
||||
print("update terrain mesh")
|
||||
mesh = obj.Mesh
|
||||
copy_mesh = mesh.copy()
|
||||
# copy_mesh.Placement.move(origin.Origin)
|
||||
if prop == "mesh" or prop == "Mesh":
|
||||
if obj.mesh:
|
||||
mesh = obj.mesh
|
||||
try:
|
||||
vertices = [tuple(v) for v in mesh.Topology[0]]
|
||||
faces = []
|
||||
for face in mesh.Topology[1]:
|
||||
faces.extend(face)
|
||||
faces.append(-1)
|
||||
|
||||
triangles = []
|
||||
for i in copy_mesh.Topology[1]:
|
||||
triangles.extend(list(i))
|
||||
triangles.append(-1)
|
||||
|
||||
self.geo_coords.point.values = copy_mesh.Topology[0]
|
||||
self.triangles.coordIndex.values = triangles
|
||||
del copy_mesh
|
||||
# Asignar a los nodos de visualización
|
||||
self.geo_coords.point.values = vertices
|
||||
self.triangles.coordIndex.values = faces
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(f"Error actualizando mesh visual: {e}\n")
|
||||
|
||||
def getDisplayModes(self, vobj):
|
||||
''' Return a list of display modes. '''
|
||||
modes = ["Surface", "Boundary"]
|
||||
|
||||
return modes
|
||||
return ["Surface", "Boundary", "Flat Lines", "Wireframe"]
|
||||
|
||||
def getDefaultDisplayMode(self):
|
||||
'''
|
||||
Return the name of the default display mode.
|
||||
'''
|
||||
return "Surface"
|
||||
|
||||
def claimChildren(self):
|
||||
return [self.Object.CuttingBoundary, ]
|
||||
if hasattr(self, "Object") and self.Object:
|
||||
return [self.Object.CuttingBoundary, ]
|
||||
return []
|
||||
|
||||
def getIcon(self):
|
||||
return str(os.path.join(DirIcons, "terrain.svg"))
|
||||
@@ -670,7 +788,7 @@ class ViewProviderTerrain:
|
||||
return None
|
||||
|
||||
|
||||
class _CommandTerrain:
|
||||
'''class _CommandTerrain:
|
||||
"the PVPlant Terrain command definition"
|
||||
|
||||
def GetResources(self):
|
||||
@@ -692,4 +810,5 @@ class _CommandTerrain:
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('Terrain', _CommandTerrain())
|
||||
FreeCADGui.addCommand('Terrain', _CommandTerrain())'''
|
||||
|
||||
|
||||
+21
-35
@@ -23,6 +23,7 @@ except AttributeError:
|
||||
import os
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
|
||||
def Mest2FemMesh(obj):
|
||||
import Fem
|
||||
|
||||
@@ -44,6 +45,7 @@ def Mest2FemMesh(obj):
|
||||
FreeCAD.activeDocument().recompute()
|
||||
return obj2
|
||||
|
||||
|
||||
def makeContours(land, minor = 1000, mayor = 5000,
|
||||
minorColor=(0.0, 0.00, 0.80), mayorColor=(0.00, 0.00, 1.00),
|
||||
minorThickness = 2, mayorThickness = 5,
|
||||
@@ -58,6 +60,7 @@ def makeContours(land, minor = 1000, mayor = 5000,
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
def Contours_Mesh(Mesh, minor, mayor,
|
||||
minorColor, mayorColor,
|
||||
minorLineWidth, mayorLineWidth,
|
||||
@@ -144,6 +147,7 @@ def Contours_Mesh(Mesh, minor, mayor,
|
||||
calculateSection(minor_array)
|
||||
calculateSection(mayor_array)
|
||||
|
||||
|
||||
def Contours_Part(Terrain, minor, mayor,
|
||||
minorColor, mayorColor,
|
||||
minorLineWidth, mayorLineWidth,
|
||||
@@ -235,6 +239,8 @@ def Contours_Part(Terrain, minor, mayor,
|
||||
calculateSection(mayor_array)
|
||||
|
||||
# Base widget for task panel terrain analisys
|
||||
|
||||
|
||||
class _generalTaskPanel:
|
||||
'''The TaskPanel for Slope setup'''
|
||||
|
||||
@@ -324,7 +330,7 @@ class _generalTaskPanel:
|
||||
self.ranges[curentIndex.row()][2] = (color.red()/255, color.green()/255, color.blue()/255)
|
||||
|
||||
# Contours Analisys: ---------------------------------------------------------------------------------
|
||||
class _ContourTaskPanel():
|
||||
class ContourTaskPanel():
|
||||
'''The editmode TaskPanel for contours generator'''
|
||||
|
||||
def __init__(self):
|
||||
@@ -444,35 +450,18 @@ class _ContourTaskPanel():
|
||||
starttime = datetime.now()
|
||||
|
||||
if self.land is None:
|
||||
print("No hay objetos para procesar")
|
||||
FreeCAD.Console.PrintWarning("No hay objetos para procesar\n")
|
||||
return False
|
||||
else:
|
||||
minor = FreeCAD.Units.Quantity(self.inputMinorContourMargin.currentText()).Value
|
||||
mayor = FreeCAD.Units.Quantity(self.inputMayorContourMargin.currentText()).Value
|
||||
|
||||
i = 2
|
||||
if i == 0:
|
||||
makeContours(self.land, minor, mayor, self.MinorColor, self.MayorColor,
|
||||
self.inputMinorContourThickness.value(), self.inputMayorContourThickness.value())
|
||||
elif i == 1:
|
||||
import multiprocessing
|
||||
p = multiprocessing.Process(target=makeContours,
|
||||
args=(self.land, minor, mayor,
|
||||
self.MinorColor, self.MayorColor,
|
||||
self.inputMinorContourThickness.value(),
|
||||
self.inputMayorContourThickness.value(), ))
|
||||
p.start()
|
||||
p.join()
|
||||
|
||||
else:
|
||||
import threading
|
||||
hilo = threading.Thread(target = makeContours,
|
||||
args = (self.land, minor, mayor,
|
||||
self.MinorColor, self.MayorColor,
|
||||
self.inputMinorContourThickness.value(),
|
||||
self.inputMayorContourThickness.value()))
|
||||
hilo.daemon = True
|
||||
hilo.start()
|
||||
makeContours(
|
||||
self.land, minor, mayor,
|
||||
self.MinorColor, self.MayorColor,
|
||||
self.inputMinorContourThickness.value(),
|
||||
self.inputMayorContourThickness.value()
|
||||
)
|
||||
|
||||
total_time = datetime.now() - starttime
|
||||
print(" -- Tiempo tardado:", total_time)
|
||||
@@ -480,7 +469,7 @@ class _ContourTaskPanel():
|
||||
return True
|
||||
|
||||
# Height Analisys: ---------------------------------------------------------------------------------
|
||||
class _HeightTaskPanel(_generalTaskPanel):
|
||||
class HeightTaskPanel(_generalTaskPanel):
|
||||
'''The TaskPanel for Slope setup'''
|
||||
|
||||
def __init__(self):
|
||||
@@ -513,7 +502,7 @@ class _HeightTaskPanel(_generalTaskPanel):
|
||||
return True
|
||||
|
||||
# Slope Analisys: ---------------------------------------------------------------------------------
|
||||
class _SlopeTaskPanel(_generalTaskPanel):
|
||||
class SlopeTaskPanel(_generalTaskPanel):
|
||||
'''The TaskPanel for Slope setup'''
|
||||
|
||||
def __init__(self):
|
||||
@@ -563,7 +552,7 @@ class _SlopeTaskPanel(_generalTaskPanel):
|
||||
land.ViewObject.DiffuseColor = colorlist
|
||||
|
||||
# TODO: check this code:
|
||||
elif obj.isDerivedFrom("Mesh::Feature"):
|
||||
elif hasattr(land, 'Mesh') and land.isDerivedFrom("Mesh::Feature"):
|
||||
fMesh = Mest2FemMesh(land)
|
||||
import math
|
||||
setColors = []
|
||||
@@ -596,14 +585,11 @@ class _SlopeTaskPanel(_generalTaskPanel):
|
||||
print("Everything OK (", datetime.now() - starttime, ")")
|
||||
|
||||
def accept(self):
|
||||
# self.getPointSlope()
|
||||
import threading
|
||||
hilo = threading.Thread(target=self.getPointSlope(self.ranges))
|
||||
hilo.start()
|
||||
self.getPointSlope(self.ranges)
|
||||
return True
|
||||
|
||||
# Orientation Analisys: ---------------------------------------------------------------------------------
|
||||
class _OrientationTaskPanel(_generalTaskPanel):
|
||||
class OrientationTaskPanel(_generalTaskPanel):
|
||||
'''The TaskPanel for Orientation setup'''
|
||||
|
||||
def __init__(self):
|
||||
@@ -707,7 +693,7 @@ class _OrientationTaskPanel(_generalTaskPanel):
|
||||
|
||||
## Commands ----------------------------------------------------------------------------------------------------------
|
||||
## 1. Contours:
|
||||
class _CommandContours:
|
||||
'''class _CommandContours:
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "TerrainContours.svg")),
|
||||
'Accel': "T, C",
|
||||
@@ -803,4 +789,4 @@ if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('SlopeAnalisys', _CommandSlopeAnalisys())
|
||||
FreeCADGui.addCommand('HeightAnalisys', _CommandHeightAnalisys())
|
||||
FreeCADGui.addCommand('OrientationAnalisys', _CommandOrientationAnalisys())
|
||||
FreeCADGui.addCommand('TerrainAnalisys', CommandTerrainAnalisysGroup())
|
||||
FreeCADGui.addCommand('TerrainAnalisys', CommandTerrainAnalisysGroup())'''
|
||||
|
||||
+111
-73
@@ -25,10 +25,8 @@ __title__ = "RebarCommands"
|
||||
__author__ = "Amritpal Singh"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import FreeCADGui, FreeCAD
|
||||
from PySide import QtGui, QtCore
|
||||
from PySide import QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
import os
|
||||
@@ -56,28 +54,6 @@ class CommandPVPlantSite:
|
||||
return
|
||||
|
||||
|
||||
class CommandPVPlantGeoreferencing:
|
||||
@staticmethod
|
||||
def GetResources():
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "Location.svg")),
|
||||
'Accel': "G, R",
|
||||
'MenuText': QT_TRANSLATE_NOOP("Georeferencing","Georeferencing"),
|
||||
'ToolTip': QT_TRANSLATE_NOOP("Georeferencing","Referenciar el lugar")}
|
||||
|
||||
@staticmethod
|
||||
def IsActive():
|
||||
if FreeCAD.ActiveDocument:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def Activated():
|
||||
import PVPlantGeoreferencing
|
||||
form = PVPlantGeoreferencing.MapWindow()
|
||||
form.show()
|
||||
|
||||
|
||||
class CommandProjectSetup:
|
||||
@staticmethod
|
||||
def GetResources():
|
||||
@@ -163,7 +139,6 @@ class CommandDivideArea:
|
||||
|
||||
@staticmethod
|
||||
def Activated():
|
||||
from Project.Area import PVPlantArea
|
||||
sel = FreeCADGui.Selection.getSelection()[0]
|
||||
|
||||
|
||||
@@ -187,8 +162,8 @@ class CommandBoundary:
|
||||
from Project.Area import PVPlantArea
|
||||
sel = FreeCADGui.Selection.getSelection()[0]
|
||||
obj = PVPlantArea.makeArea([ver.Point for ver in sel.Shape.Vertexes])
|
||||
#taskd = _PVPlantPlacementTaskPanel()
|
||||
#FreeCADGui.Control.showDialog(taskd)
|
||||
# taskd = _PVPlantPlacementTaskPanel()
|
||||
# FreeCADGui.Control.showDialog(taskd)
|
||||
|
||||
|
||||
class CommandFrameArea:
|
||||
@@ -208,7 +183,6 @@ class CommandFrameArea:
|
||||
|
||||
@staticmethod
|
||||
def Activated():
|
||||
from Project.Area import PVPlantArea
|
||||
sel = FreeCADGui.Selection.getSelection()
|
||||
makeFramedArea(None, sel)
|
||||
|
||||
@@ -249,6 +223,7 @@ class CommandPVSubplant:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def Activated():
|
||||
from Project.Area import PVPlantArea
|
||||
@@ -440,7 +415,7 @@ class CommandTrench: # V1:
|
||||
@staticmethod
|
||||
def Activated():
|
||||
"""Execute when the command is called."""
|
||||
import PVPlantTrench
|
||||
from Civil import PVPlantTrench
|
||||
sel = FreeCADGui.Selection.getSelection()
|
||||
done = False
|
||||
|
||||
@@ -486,7 +461,7 @@ class CommandSemiAutomaticTrench: # V1:
|
||||
@staticmethod
|
||||
def Activated():
|
||||
"""Execute when the command is called."""
|
||||
import PVPlantTrench
|
||||
from Civil import PVPlantTrench
|
||||
semi = PVPlantTrench.semiAutomaticTrench()
|
||||
|
||||
|
||||
@@ -507,8 +482,8 @@ class CommandCalculateEarthworks:
|
||||
|
||||
@staticmethod
|
||||
def Activated():
|
||||
import PVPlantEarthworks
|
||||
TaskPanel = PVPlantEarthworks.EarthWorksTaskPanel()
|
||||
import PVPlantEarthWorks
|
||||
TaskPanel = PVPlantEarthWorks.EarthWorksTaskPanel()
|
||||
FreeCADGui.Control.showDialog(TaskPanel)
|
||||
|
||||
|
||||
@@ -524,7 +499,7 @@ class CommandManhole:
|
||||
|
||||
@staticmethod
|
||||
def IsActive():
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
return not (FreeCAD.ActiveDocument is None)
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
if FreeCADGui.Selection.getCompleteSelection():
|
||||
for ob in FreeCAD.ActiveDocument.Objects:
|
||||
@@ -534,29 +509,30 @@ class CommandManhole:
|
||||
@staticmethod
|
||||
def Activated():
|
||||
import PVPlantManhole
|
||||
TaskPanel = PVPlantManhole._ManholeTaskPanel()
|
||||
FreeCADGui.Control.showDialog(TaskPanel)
|
||||
task_panel = PVPlantManhole._ManholeTaskPanel()
|
||||
FreeCADGui.Control.showDialog(task_panel)
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('PVPlantSite', CommandPVPlantSite())
|
||||
FreeCADGui.addCommand('PVPlantGeoreferencing', CommandPVPlantGeoreferencing())
|
||||
import PVPlantGeoreferencing
|
||||
|
||||
FreeCADGui.addCommand('PVPlantGeoreferencing', PVPlantGeoreferencing.CommandPVPlantGeoreferencing())
|
||||
FreeCADGui.addCommand('ProjectSetup', CommandProjectSetup())
|
||||
FreeCADGui.addCommand('Terrain', CommandTerrain())
|
||||
FreeCADGui.addCommand('PVPlantCreateTerrainMesh', CommandCreateTerrainMesh())
|
||||
|
||||
|
||||
class CommandAreaGroup:
|
||||
@staticmethod
|
||||
def GetCommands():
|
||||
return tuple([#'Area',
|
||||
'FrameArea',
|
||||
'ForbiddenArea',
|
||||
'PVSubplant',
|
||||
'OffsetArea'
|
||||
])
|
||||
return tuple([ # 'Area',
|
||||
'FrameArea',
|
||||
'ForbiddenArea',
|
||||
'PVSubplant',
|
||||
'OffsetArea'
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
def GetResources():
|
||||
@@ -568,7 +544,8 @@ if FreeCAD.GuiUp:
|
||||
def IsActive():
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
#FreeCADGui.addCommand('Area', CommandBoundary())
|
||||
|
||||
# FreeCADGui.addCommand('Area', CommandBoundary())
|
||||
FreeCADGui.addCommand('FrameArea', CommandFrameArea())
|
||||
FreeCADGui.addCommand('ForbiddenArea', CommandProhibitedArea())
|
||||
FreeCADGui.addCommand('PVSubplant', CommandPVSubplant())
|
||||
@@ -578,6 +555,7 @@ if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('SplitArea', CommandSplitArea())
|
||||
FreeCADGui.addCommand('JoinAreas', CommandJoinAreas())
|
||||
|
||||
|
||||
class CommandTerrainAnalisysGroup:
|
||||
@staticmethod
|
||||
def GetCommands():
|
||||
@@ -589,20 +567,22 @@ if FreeCAD.GuiUp:
|
||||
|
||||
@staticmethod
|
||||
def GetResources():
|
||||
return { 'MenuText': QT_TRANSLATE_NOOP("",'Terrain Analisys'),
|
||||
'ToolTip': QT_TRANSLATE_NOOP("",'Terrain Analisys')
|
||||
}
|
||||
return {'MenuText': QT_TRANSLATE_NOOP("", 'Terrain Analisys'),
|
||||
'ToolTip': QT_TRANSLATE_NOOP("", 'Terrain Analisys')
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def IsActive():
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
|
||||
FreeCADGui.addCommand('Contours', CommandContours())
|
||||
FreeCADGui.addCommand('SlopeAnalisys', CommandSlopeAnalisys())
|
||||
FreeCADGui.addCommand('HeightAnalisys', CommandHeightAnalisys())
|
||||
FreeCADGui.addCommand('OrientationAnalisys', CommandOrientationAnalisys())
|
||||
FreeCADGui.addCommand('TerrainAnalisys', CommandTerrainAnalisysGroup())
|
||||
|
||||
|
||||
class CommandTrenchGroup:
|
||||
@staticmethod
|
||||
def GetCommands():
|
||||
@@ -625,33 +605,91 @@ if FreeCAD.GuiUp:
|
||||
active = active and not (FreeCAD.ActiveDocument.getObject("Terrain").Mesh is None)
|
||||
return active
|
||||
|
||||
|
||||
FreeCADGui.addCommand('PVPlantTrench', CommandTrench())
|
||||
FreeCADGui.addCommand('PVPlantSemiAutomaticTrench', CommandSemiAutomaticTrench())
|
||||
FreeCADGui.addCommand('Trenches', CommandTrenchGroup())
|
||||
FreeCADGui.addCommand('PVPlantEarthworks', CommandCalculateEarthworks())
|
||||
|
||||
FreeCADGui.addCommand('PVPlantManhole', _ommandManhole())
|
||||
FreeCADGui.addCommand('PVPlantManhole', CommandManhole())
|
||||
|
||||
projectlist = [ #"Reload",
|
||||
"PVPlantSite",
|
||||
"ProjectSetup",
|
||||
"PVPlantGeoreferencing",
|
||||
"Separator",
|
||||
# "ImportGrid",
|
||||
"Terrain",
|
||||
"TerrainAnalisys",
|
||||
"PVPlantCreateTerrainMesh",
|
||||
"Separator",
|
||||
#"PointsGroup",
|
||||
"PVPlantAreas",
|
||||
"SplitArea",
|
||||
"Separator",
|
||||
"Trenches",
|
||||
"PVPlantEarthworks",
|
||||
#"PVPlantPad",
|
||||
#"PVPlantRoad",
|
||||
#"PVPlantManhole",
|
||||
#"PVPlantFoundation"
|
||||
#"GraphTerrainProfile",
|
||||
#"Trace",
|
||||
]
|
||||
import PVPlantPlacement
|
||||
FreeCADGui.addCommand('PVPlantPlacement', PVPlantPlacement.CommandPVPlantPlacement())
|
||||
FreeCADGui.addCommand('PVPlantAdjustToTerrain', PVPlantPlacement.CommandAdjustToTerrain())
|
||||
FreeCADGui.addCommand('PVPlantConvertTo', PVPlantPlacement.CommandConvert())
|
||||
|
||||
import hydro.hydrological as hydro
|
||||
FreeCADGui.addCommand('HydrologicalAnalysis', hydro.CommandHydrologicalAnalysis())
|
||||
|
||||
import Vegetation.PVPlantTreeGenerator as TreeGenerator
|
||||
FreeCADGui.addCommand('PVPlantTree', TreeGenerator.CommandTree())
|
||||
|
||||
import Project.GenerateExternalDocument as GED
|
||||
FreeCADGui.addCommand('newExternalDocument', GED.CommandGenerateExternalDocument())
|
||||
|
||||
from Mechanical.Frame import PVPlantFrame
|
||||
class CommandRackGroup:
|
||||
|
||||
def GetCommands(self):
|
||||
return tuple(['PVPlantFixedRack',
|
||||
'PVPlantTrackerSetup',
|
||||
'PVPlantTracker'
|
||||
])
|
||||
|
||||
def GetResources(self):
|
||||
return {'MenuText': QT_TRANSLATE_NOOP("", 'Rack Types'),
|
||||
'ToolTip': QT_TRANSLATE_NOOP("", 'Rack Types')
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
FreeCADGui.addCommand('PVPlantFixedRack', PVPlantFrame.CommandFixedRack())
|
||||
FreeCADGui.addCommand('PVPlantTrackerSetup', PVPlantFrame.CommandTrackerSetup())
|
||||
FreeCADGui.addCommand('PVPlantTracker', PVPlantFrame.CommandTracker())
|
||||
FreeCADGui.addCommand('RackType', CommandRackGroup())
|
||||
|
||||
from Civil.Fence import PVPlantFence
|
||||
FreeCADGui.addCommand('PVPlantFenceGroup', PVPlantFence.CommandFenceGroup())
|
||||
|
||||
import docgenerator
|
||||
FreeCADGui.addCommand('GenerateDocuments', docgenerator.generateDocuments())
|
||||
|
||||
projectlist = [ # "Reload",
|
||||
"PVPlantSite",
|
||||
"ProjectSetup",
|
||||
"PVPlantGeoreferencing",
|
||||
"Separator",
|
||||
# "ImportGrid",
|
||||
"Terrain",
|
||||
"TerrainAnalisys",
|
||||
"PVPlantCreateTerrainMesh",
|
||||
"Separator",
|
||||
# "PointsGroup",
|
||||
"PVPlantAreas",
|
||||
"SplitArea",
|
||||
"Separator",
|
||||
"Trenches",
|
||||
"PVPlantEarthworks",
|
||||
# "PVPlantPad",
|
||||
# "PVPlantRoad",
|
||||
"PVPlantManhole",
|
||||
# "PVPlantFoundation"
|
||||
# "GraphTerrainProfile",
|
||||
# "Trace",
|
||||
"Separator",
|
||||
'HydrologicalAnalysis',
|
||||
'newExternalDocument',
|
||||
]
|
||||
|
||||
pv_mechanical = [
|
||||
"RackType",
|
||||
"PVPlantPlacement",
|
||||
"PVPlantAdjustToTerrain",
|
||||
"PVPlantConvertTo",
|
||||
]
|
||||
|
||||
objectlist = ['PVPlantTree',
|
||||
'PVPlantFenceGroup',
|
||||
'GenerateDocuments',
|
||||
]
|
||||
@@ -1,368 +0,0 @@
|
||||
|
||||
import math
|
||||
|
||||
import ArchComponent
|
||||
import FreeCAD
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore, QtGui
|
||||
from DraftTools import translate
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
import Part
|
||||
import os
|
||||
else:
|
||||
# \cond
|
||||
def translate(ctxt, txt):
|
||||
return txt
|
||||
|
||||
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||
return txt
|
||||
# \endcond
|
||||
|
||||
__title__ = "FreeCAD Fixed Rack"
|
||||
__author__ = "Javier Braña"
|
||||
__url__ = "http://www.sogos-solar.com"
|
||||
|
||||
__dir__ = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", "PVPlant")
|
||||
DirResources = os.path.join(__dir__, "Resources")
|
||||
DirIcons = os.path.join(DirResources, "Icons")
|
||||
DirImages = os.path.join(DirResources, "Images")
|
||||
|
||||
|
||||
def makeTree():
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Tree")
|
||||
Tree(obj)
|
||||
ViewProviderTree(obj.ViewObject)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
try:
|
||||
folder = FreeCAD.ActiveDocument.Vegetation
|
||||
except:
|
||||
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Vegetation')
|
||||
folder.Label = "Vegetation"
|
||||
folder.addObject(obj)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
class Tree(ArchComponent.Component):
|
||||
""" A Shadow Tree Obcject """
|
||||
|
||||
def __init__(self, obj):
|
||||
# Definición de variables:
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
self.obj = obj
|
||||
self.setProperties(obj)
|
||||
|
||||
def setProperties(self, obj):
|
||||
# Definicion de Propiedades:
|
||||
pl = obj.PropertiesList
|
||||
|
||||
# CANOPY: ---------------------------------------------------------
|
||||
if not ("CanopyHeight" in pl):
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"CanopyHeight",
|
||||
"Canopy",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
|
||||
).CanopyHeight = 4000
|
||||
|
||||
if not ("CanopyRadius" in pl):
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"CanopyRadius",
|
||||
"Canopy",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
|
||||
).CanopyRadius = 1500
|
||||
|
||||
if not ("Spikiness" in pl):
|
||||
obj.addProperty("App::PropertyFloatConstraint",
|
||||
"Spikiness",
|
||||
"Canopy",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
|
||||
).Spikiness = (0.5, 0.0, 1.0, 0.05) # (Default, Start, Finish, Step)
|
||||
|
||||
'''
|
||||
if not ("Lumpiness" in pl):
|
||||
obj.addProperty("App::PropertyFloatConstraint",
|
||||
"Lumpiness",
|
||||
"Canopy",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
|
||||
).Lumpiness = (0.0, 0.0, 1.0, 0.05) #(Default, Start, Finish, Step)'''
|
||||
|
||||
if not ("CrownExpansion" in pl):
|
||||
obj.addProperty("App::PropertyFloatConstraint",
|
||||
"CrownExpansion",
|
||||
"Canopy",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
|
||||
).CrownExpansion = (1.0, 0.0, 2.0, 0.05) # (Default, Start, Finish, Step)
|
||||
|
||||
if not ("UmbrellaEffect" in pl):
|
||||
obj.addProperty("App::PropertyFloatConstraint",
|
||||
"UmbrellaEffect",
|
||||
"Canopy",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
|
||||
).UmbrellaEffect = (0.0, 0.0, 1.0, 0.05) # (Default, Start, Finish, Step)
|
||||
|
||||
if not ("LeafCount" in pl):
|
||||
obj.addProperty("App::PropertyQuantity",
|
||||
"LeafCount",
|
||||
"Canopy",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
|
||||
).LeafCount = 20
|
||||
|
||||
# TRUNK: ------------------------------------------------------------------------------------------------------
|
||||
if not ("TrunkHeight" in pl):
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"TrunkHeight",
|
||||
"Trunk",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
|
||||
).TrunkHeight = 2000
|
||||
|
||||
if not ("TrunkRadius" in pl):
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"TrunkRadius",
|
||||
"Trunk",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
|
||||
).TrunkRadius = 150
|
||||
|
||||
if not ("TrunkFaces" in pl):
|
||||
obj.addProperty("App::PropertyQuantity",
|
||||
"TrunkFaces",
|
||||
"Trunk",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The height of self object")
|
||||
).TrunkFaces = 6
|
||||
|
||||
if not ("Type" in pl):
|
||||
obj.addProperty("App::PropertyString",
|
||||
"Type",
|
||||
"Base",
|
||||
"Type").Type = "Vegetable-Tree"
|
||||
obj.setEditorMode("Type", 1)
|
||||
|
||||
self.Type = obj.Type
|
||||
obj.Proxy = self
|
||||
obj.IfcType = "Shading Device"
|
||||
obj.setEditorMode("IfcType", 1)
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
ArchComponent.Component.onDocumentRestored(self, obj)
|
||||
self.setProperties(obj)
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
'''if prop in ["CanopyHeight", "CanopyHeight", "Spikiness", "CrownExpansion", "UmbrellaEffect",
|
||||
"LeafCount"]:
|
||||
self.canopy = self.createCanopy(obj)
|
||||
|
||||
if prop in ["TrunkHeight", "TrunkRadius", "TrunkFaces"]:
|
||||
self.trunk = self.createTrunk(obj)'''
|
||||
|
||||
def createTrunk(self, obj):
|
||||
import Part
|
||||
angle = (math.pi * 2) / obj.TrunkFaces.Value
|
||||
delta = obj.TrunkRadius.Value
|
||||
pts = [FreeCAD.Vector(delta, 0, 0)]
|
||||
for i in range(int(obj.TrunkFaces.Value) - 1):
|
||||
ang = (i + 1) * angle
|
||||
pts.append(FreeCAD.Vector(delta * math.cos(ang),
|
||||
delta * math.sin(ang),
|
||||
0))
|
||||
pts.append(pts[0])
|
||||
p1 = Part.makePolygon(pts)
|
||||
p0 = p1.makeOffset2D(90, 2, False, False, True)
|
||||
p2 = p1.makeOffset2D(-50, 2, False, False, True)
|
||||
p0.Placement.Base.z = -150
|
||||
p1.Placement.Base.z = 150
|
||||
p2.Placement.Base.z = obj.TrunkHeight.Value - 250
|
||||
return Part.makeLoft([p0, p1, p2], True, True, False)
|
||||
|
||||
def createCanopy(self, obj):
|
||||
import Part
|
||||
import random
|
||||
import Mesh
|
||||
import numpy as np
|
||||
|
||||
ncircles = int(obj.LeafCount.Value)
|
||||
if ncircles % 2 == 0:
|
||||
ncircles += 1
|
||||
half_ncircles = int(ncircles / 2)
|
||||
ncirclesumbrella = int(half_ncircles/2)
|
||||
ncirclestop = ncircles - ncirclesumbrella
|
||||
|
||||
# 1. Create circles to define the sphere
|
||||
circles = []
|
||||
dist = 2 * obj.CanopyRadius.Value / (ncircles - 1)
|
||||
margin = dist * 0.01
|
||||
'''for i in range(half_ncircles + 1):
|
||||
if i > 0:
|
||||
d = (obj.CanopyRadius.Value - dist * i)
|
||||
else:
|
||||
d = obj.CanopyRadius.Value - margin
|
||||
r = (obj.CanopyRadius.Value ** 2 - d ** 2) ** 0.5
|
||||
c = Part.makeCircle(r)
|
||||
circles.append(c)
|
||||
|
||||
ctmp = [c.copy() for c in circles]
|
||||
ctmp.pop()
|
||||
ctmp.reverse()
|
||||
circles.extend(ctmp)'''
|
||||
|
||||
d = - obj.CanopyRadius.Value - dist
|
||||
b = (obj.CanopyRadius.Value ** 2 - (dist * (ncirclesumbrella - 1)) ** 2) ** 0.5
|
||||
|
||||
for i in range(ncircles):
|
||||
d += dist
|
||||
r = (obj.CanopyRadius.Value ** 2 - d ** 2) ** 0.5
|
||||
if i > ncirclesumbrella:
|
||||
if obj.CrownExpansion < 1:
|
||||
r = r * obj.CrownExpansion
|
||||
if r == 0:
|
||||
r = obj.CanopyRadius.Value * 0.01
|
||||
c = Part.makeCircle(r)
|
||||
circles.append(c)
|
||||
|
||||
# 2. Place circles
|
||||
dist = obj.CanopyHeight.Value / ncircles
|
||||
z = 0
|
||||
#zmax = dist * half_ncircles
|
||||
for idx in range(1, half_ncircles):
|
||||
z += dist
|
||||
circles[idx].Placement.Base.z = (1 - obj.UmbrellaEffect) * z
|
||||
#c.Placement.Base.z = obj.UmbrellaEffect * (zmax - z) + z
|
||||
|
||||
dist1 = (obj.CanopyHeight.Value - z) / (half_ncircles + 1)
|
||||
for idx in range(half_ncircles, ncircles):
|
||||
c = circles[idx]
|
||||
z += dist1
|
||||
c.Placement.Base.z = z
|
||||
|
||||
# 3. noise generator
|
||||
pts = []
|
||||
val = (dist / 2 - margin) * obj.Spikiness
|
||||
for c in circles:
|
||||
tmppts = c.discretize(ncircles)
|
||||
for j in range(len(tmppts)):
|
||||
point = tmppts[j]
|
||||
point.x += random.uniform(-val, val)
|
||||
point.y += random.uniform(-val, val)
|
||||
point.z += random.uniform(-val, val)
|
||||
pts.append(point)
|
||||
|
||||
# 4. generate the mesh / solid
|
||||
from scipy import spatial as sp_spatial
|
||||
pts = np.array(pts)
|
||||
hull = sp_spatial.ConvexHull(pts)
|
||||
indices = hull.simplices
|
||||
faces = pts[indices]
|
||||
mesh = Mesh.Mesh(faces.tolist())
|
||||
if len(mesh.Facets) == 0:
|
||||
return None
|
||||
mesh.harmonizeNormals()
|
||||
'''if mesh.Facets[0].Normal.z < 0:
|
||||
mesh.flipNormals()'''
|
||||
shape = Part.Shape()
|
||||
shape.makeShapeFromMesh(mesh.Topology, 0.1)
|
||||
return Part.makeSolid(shape)
|
||||
|
||||
def execute(self, obj):
|
||||
pl = obj.Placement
|
||||
|
||||
trunk = self.createTrunk(obj)
|
||||
canopy = self.createCanopy(obj)
|
||||
canopy.Placement.Base.z = obj.TrunkHeight.Value - 250 # - obj.CanopyRadius.Value / 2
|
||||
obj.Shape = Part.makeCompound([trunk, canopy])
|
||||
obj.Placement = pl
|
||||
|
||||
color = [(0.2510, 0.1255, 0.0)] * len(trunk.Faces)
|
||||
color.extend([(0.0, 0.3922, 0.0)] * len(canopy.Faces))
|
||||
obj.ViewObject.DiffuseColor = color
|
||||
|
||||
|
||||
class ViewProviderTree(ArchComponent.ViewProviderComponent):
|
||||
"A View Provider for the Pipe object"
|
||||
|
||||
def __init__(self, vobj):
|
||||
ArchComponent.ViewProviderComponent.__init__(self, vobj)
|
||||
|
||||
def getIcon(self):
|
||||
return str(os.path.join(DirIcons, "tree(1).svg"))
|
||||
|
||||
|
||||
class TreeTaskPanel(QtGui.QWidget):
|
||||
def __init__(self, obj=None):
|
||||
QtGui.QWidget.__init__(self)
|
||||
self.obj = obj
|
||||
if self.obj is None:
|
||||
self.obj = makeTree()
|
||||
|
||||
self.form = FreeCADGui.PySideUic.loadUi(__dir__ + "/PVPlantTree.ui")
|
||||
self.layout = QtGui.QHBoxLayout(self)
|
||||
self.layout.setContentsMargins(4, 4, 4, 4)
|
||||
self.layout.addWidget(self.form)
|
||||
|
||||
self.form.editCanopyHeight.valueChanged.connect(self.Canopy)
|
||||
self.form.editCanopyRadius.valueChanged.connect(self.Canopy)
|
||||
self.form.editSpikiness.valueChanged.connect(self.Canopy)
|
||||
self.form.editCrownExpansion.valueChanged.connect(self.Canopy)
|
||||
self.form.editLeftUmbrellaEffect.valueChanged.connect(self.Canopy)
|
||||
self.form.editLeafCount.valueChanged.connect(self.Canopy)
|
||||
|
||||
def Canopy(self):
|
||||
self.obj.CanopyHeight = FreeCAD.Units.Quantity(self.form.editCanopyHeight.text()).Value
|
||||
self.obj.CanopyRadius = FreeCAD.Units.Quantity(self.form.editCanopyRadius.text()).Value
|
||||
self.obj.Spikiness = self.form.editSpikiness.value()
|
||||
self.obj.CrownExpansion = self.form.editCrownExpansion.value()
|
||||
self.obj.UmbrellaEffect = self.form.editLeftUmbrellaEffect.value()
|
||||
self.obj.LeafCount = self.form.editLeafCount.value()
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def accept(self):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return True
|
||||
|
||||
def reject(self):
|
||||
FreeCAD.ActiveDocument.removeObject(self.obj.Name)
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return True
|
||||
|
||||
|
||||
class _CommandTree:
|
||||
"the PVPlant Tree command definition"
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "tree(1).svg")),
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("PVPlantTree", "Tree"),
|
||||
'Accel': "S, T",
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PVPlanTree",
|
||||
"Creates a Tree object from setup dialog.")}
|
||||
|
||||
def IsActive(self):
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
def Activated(self):
|
||||
import draftguitools.gui_trackers as DraftTrackers
|
||||
self.tree = makeTree()
|
||||
FreeCADGui.Snapper.getPoint(callback=self.getPoint,
|
||||
movecallback=self.mousemove,
|
||||
extradlg=self.taskbox(),
|
||||
title="Position of the tree:")
|
||||
|
||||
def getPoint(self, point=None, obj=None):
|
||||
self.tree.Placement.Base = point
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
self.tracker.finalize()
|
||||
|
||||
def mousemove(self, pt, snapInfo):
|
||||
self.tree.Placement.Base = pt
|
||||
|
||||
def taskbox(self):
|
||||
self.form = TreeTaskPanel(self.tree)
|
||||
return self.form
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('PVPlantTree', _CommandTree())
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
# /**********************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2021 Javier Braña <javier.branagutierrez@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify*
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307*
|
||||
# * USA *
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
import ArchComponent
|
||||
import FreeCAD
|
||||
import Part
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore
|
||||
import draftguitools.gui_trackers as DraftTrackers
|
||||
else:
|
||||
# \cond
|
||||
def translate(ctxt, txt):
|
||||
return txt
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||
return txt
|
||||
# \endcond
|
||||
|
||||
try:
|
||||
_fromUtf8 = QtCore.QString.fromUtf8
|
||||
except AttributeError:
|
||||
def _fromUtf8(s):
|
||||
return s
|
||||
|
||||
import os
|
||||
import PVPlantResources
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
|
||||
def makeModule(name="Module"):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
|
||||
obj.Label = name
|
||||
Module(obj)
|
||||
ViewProviderModule(obj.ViewObject)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
return obj
|
||||
|
||||
|
||||
class Module(ArchComponent.Component):
|
||||
"A Manhole Obcject"
|
||||
|
||||
def __init__(self, obj):
|
||||
# Definición de Variables:
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
self.setProperties(obj)
|
||||
|
||||
def setProperties(self, obj):
|
||||
pl = obj.PropertiesList
|
||||
# Base de datos: -----------------------------------------------------------------------------------------------
|
||||
if not ("Manufacturer" in pl):
|
||||
obj.addProperty("App::PropertyStringList",
|
||||
"Manufacturer",
|
||||
"Module",
|
||||
"The manufacturer of this object"
|
||||
)
|
||||
|
||||
if not ("Model" in pl):
|
||||
obj.addProperty("App::PropertyStringList",
|
||||
"Model",
|
||||
"Module",
|
||||
"The model of this object"
|
||||
)
|
||||
|
||||
# Dimensions: --------------------------------------------------------------------------------------------------
|
||||
if not ("Height" in pl):
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"Height",
|
||||
"Manhole",
|
||||
"The height of this object"
|
||||
).Height = 2804
|
||||
|
||||
if not ("Width" in pl):
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"Width",
|
||||
"Manhole",
|
||||
"The width of this object"
|
||||
).Width = 1303
|
||||
|
||||
if not ("Thickness" in pl):
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"Thickness",
|
||||
"Manhole",
|
||||
"The height of this object"
|
||||
).Thickness = 35
|
||||
|
||||
# Electrical: --------------------------------------------------------------------------------------------------
|
||||
if not ("Power" in pl):
|
||||
obj.addProperty("App::PropertyPower",
|
||||
"Power",
|
||||
"Outputs",
|
||||
"The height of this object"
|
||||
).Power = 650
|
||||
|
||||
if not ("Umpp" in pl):
|
||||
obj.addProperty("App::PropertyElectricCurrent",
|
||||
"Umpp",
|
||||
"Outputs",
|
||||
"The height of this object"
|
||||
).Umpp = 650
|
||||
|
||||
if not ("Impp" in pl):
|
||||
obj.addProperty("App::PropertyElectricCurrent",
|
||||
"Impp",
|
||||
"Outputs",
|
||||
"The height of this object"
|
||||
).Impp = 650
|
||||
|
||||
if not ("Uoc" in pl):
|
||||
obj.addProperty("App::PropertyElectricCurrent",
|
||||
"Uoc",
|
||||
"Outputs",
|
||||
"The height of this object"
|
||||
).Uoc = 650
|
||||
|
||||
if not ("Isc" in pl):
|
||||
obj.addProperty("App::PropertyElectricCurrent",
|
||||
"Isc",
|
||||
"Outputs",
|
||||
"The height of this object"
|
||||
).Isc = 650
|
||||
|
||||
if not ("Isc" in pl):
|
||||
obj.addProperty("App::PropertyElectricCurrent",
|
||||
"Impp",
|
||||
"Outputs",
|
||||
"The height of this object"
|
||||
).Impp = 650
|
||||
|
||||
self.Type = "Module"
|
||||
obj.Proxy = self
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
"""Method run when the document is restored.
|
||||
Re-adds the Arch component, and Arch wall properties."""
|
||||
ArchComponent.Component.onDocumentRestored(self, obj)
|
||||
self.setProperties(obj)
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
'''Do something when a property has changed'''
|
||||
|
||||
def execute(self, obj):
|
||||
box = Part.makeBox(obj.Width, obj.Height, obj.Thickness)
|
||||
box.translate(FreeCAD.Vector(-obj.Width, -obj.Height, 0) / 2)
|
||||
obj.Shape = box
|
||||
|
||||
|
||||
class ViewProviderModule(ArchComponent.ViewProviderComponent):
|
||||
"A View Provider for the Module object"
|
||||
|
||||
def __init__(self, vobj):
|
||||
ArchComponent.ViewProviderComponent.__init__(self, vobj)
|
||||
|
||||
def getIcon(self):
|
||||
return str(os.path.join(DirIcons, "manhole.svg"))
|
||||
|
||||
def setEdit(self, vobj, mode):
|
||||
"""Method called when the document requests the object to enter edit mode.
|
||||
|
||||
Edit mode is entered when a user double clicks on an object in the tree
|
||||
view, or when they use the menu option [Edit -> Toggle Edit Mode].
|
||||
|
||||
Just display the standard Arch component task panel.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mode: int or str
|
||||
The edit mode the document has requested. Set to 0 when requested via
|
||||
a double click or [Edit -> Toggle Edit Mode].
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
If edit mode was entered.
|
||||
"""
|
||||
|
||||
if (mode == 0) and hasattr(self, "Object"):
|
||||
taskd = _ManholeTaskPanel(self.Object)
|
||||
taskd.obj = self.Object
|
||||
# taskd.update()
|
||||
FreeCADGui.Control.showDialog(taskd)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class ManholeTaskPanel:
|
||||
def __init__(self, obj=None):
|
||||
self.new = False
|
||||
if obj is None:
|
||||
self.new = True
|
||||
obj = makeManhole()
|
||||
|
||||
self.obj = obj
|
||||
|
||||
self.form = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantManhole.ui")
|
||||
|
||||
self.node = None
|
||||
self.view = FreeCADGui.ActiveDocument.ActiveView
|
||||
self.tracker = DraftTrackers.ghostTracker(obj)
|
||||
self.tracker.on()
|
||||
self.call = self.view.addEventCallback("SoEvent", self.action)
|
||||
|
||||
def action(self, arg):
|
||||
"""Handle the 3D scene events.
|
||||
|
||||
This is installed as an EventCallback in the Inventor view.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
arg: dict
|
||||
Dictionary with strings that indicates the type of event received
|
||||
from the 3D view.
|
||||
"""
|
||||
|
||||
if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE":
|
||||
self.finish()
|
||||
|
||||
elif arg["Type"] == "SoLocation2Event":
|
||||
point, ctrlPoint, info = gui_tool_utils.getPoint(self, arg)
|
||||
if info:
|
||||
self.tracker.move(FreeCAD.Vector(info["x"], info["y"], info["z"]))
|
||||
else:
|
||||
self.tracker.move(point)
|
||||
|
||||
elif (arg["Type"] == "SoMouseButtonEvent" and
|
||||
arg["State"] == "DOWN" and
|
||||
arg["Button"] == "BUTTON1"):
|
||||
|
||||
point, ctrlPoint, info = gui_tool_utils.getPoint(self, arg)
|
||||
if info:
|
||||
self.obj.Placement.Base = FreeCAD.Vector(info["x"], info["y"], info["z"])
|
||||
else:
|
||||
self.obj.Placement.Base = point
|
||||
self.finish()
|
||||
|
||||
def finish(self):
|
||||
self.accept()
|
||||
|
||||
def accept(self):
|
||||
self.closeForm()
|
||||
return True
|
||||
|
||||
def reject(self):
|
||||
if self.new:
|
||||
FreeCAD.ActiveDocument.removeObject(self.obj.Name)
|
||||
self.closeForm()
|
||||
return True
|
||||
|
||||
def closeForm(self):
|
||||
self.tracker.finalize()
|
||||
FreeCADGui.Control.closeDialog()
|
||||
self.view.removeEventCallback("SoEvent", self.call)
|
||||
|
||||
|
||||
'''class _CommandManhole:
|
||||
"the Arch Building command definition"
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "manhole.svg")),
|
||||
'MenuText': "Manhole",
|
||||
'Accel': "C, M",
|
||||
'ToolTip': "Creates a Manhole object from setup dialog."}
|
||||
|
||||
def IsActive(self):
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
if FreeCADGui.Selection.getCompleteSelection():
|
||||
for ob in FreeCAD.ActiveDocument.Objects:
|
||||
if ob.Name[:4] == "Site":
|
||||
return True
|
||||
|
||||
def Activated(self):
|
||||
TaskPanel = _ManholeTaskPanel()
|
||||
FreeCADGui.Control.showDialog(TaskPanel)
|
||||
return
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('PVPlantManhole', _CommandManhole())'''
|
||||
|
||||
+495
-55
@@ -26,6 +26,9 @@ import PVPlantSite
|
||||
import Utils.PVPlantUtils as utils
|
||||
import MeshPart as mp
|
||||
|
||||
import pivy
|
||||
from pivy import coin
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from DraftTools import translate
|
||||
@@ -69,6 +72,7 @@ class _Area:
|
||||
''' Initialize the Area object '''
|
||||
self.Type = None
|
||||
self.obj = None
|
||||
self.setProperties(obj)
|
||||
|
||||
def setProperties(self, obj):
|
||||
pl = obj.PropertiesList
|
||||
@@ -95,18 +99,24 @@ class _Area:
|
||||
""" Method run when the document is restored """
|
||||
self.setProperties(obj)
|
||||
|
||||
def __getstate__(self):
|
||||
return None # No necesitamos guardar estado adicional
|
||||
|
||||
def __setstate__(self, state):
|
||||
pass
|
||||
|
||||
def execute(self, obj):
|
||||
''' Execute the area object '''
|
||||
pass
|
||||
|
||||
|
||||
class _ViewProviderArea:
|
||||
def __init__(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
vobj.Proxy = self
|
||||
|
||||
def attach(self, vobj):
|
||||
'''
|
||||
Create Object visuals in 3D view.
|
||||
'''
|
||||
self.Object = vobj.Object
|
||||
return
|
||||
''' Create Object visuals in 3D view. '''
|
||||
self.ViewObject = vobj
|
||||
|
||||
def getIcon(self):
|
||||
'''
|
||||
@@ -114,6 +124,7 @@ class _ViewProviderArea:
|
||||
'''
|
||||
|
||||
return str(os.path.join(DirIcons, "area.svg"))
|
||||
|
||||
'''
|
||||
def claimChildren(self):
|
||||
"""
|
||||
@@ -153,17 +164,10 @@ class _ViewProviderArea:
|
||||
pass
|
||||
|
||||
def __getstate__(self):
|
||||
"""
|
||||
Save variables to file.
|
||||
"""
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""
|
||||
Get variables from file.
|
||||
"""
|
||||
return None
|
||||
|
||||
pass
|
||||
|
||||
''' Frame Area '''
|
||||
|
||||
@@ -263,7 +267,7 @@ class FrameArea(_Area):
|
||||
if not hasattr(o, "Proxy"):
|
||||
continue
|
||||
if o.Proxy.Type == "Tracker":
|
||||
lf.append(obj)
|
||||
lf.append(o)
|
||||
obj.Frames = lf
|
||||
obj.FramesNumber = len(obj.Frames)
|
||||
|
||||
@@ -274,10 +278,17 @@ class FrameArea(_Area):
|
||||
|
||||
def execute(self, obj):
|
||||
''' execute '''
|
||||
#_Area.execute(self, obj)
|
||||
|
||||
if not hasattr(obj, "Frames"):
|
||||
return
|
||||
obj.FrameNumber = len(obj.Frames)
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
pass
|
||||
|
||||
|
||||
class ViewProviderFrameArea(_ViewProviderArea):
|
||||
def __init__(self, vobj):
|
||||
@@ -298,17 +309,14 @@ class ViewProviderFrameArea(_ViewProviderArea):
|
||||
|
||||
|
||||
''' offsets '''
|
||||
|
||||
|
||||
def makeOffsetArea(base = None, val=None):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "OffsetArea")
|
||||
OffsetArea(obj)
|
||||
obj.Base = base
|
||||
ViewProviderOffsetArea(obj.ViewObject)
|
||||
if val:
|
||||
obj.Distance = val
|
||||
obj.OffsetDistance = val
|
||||
|
||||
offsets = None
|
||||
try:
|
||||
offsetsgroup = FreeCAD.ActiveDocument.Offsets
|
||||
except:
|
||||
@@ -321,11 +329,13 @@ def makeOffsetArea(base = None, val=None):
|
||||
|
||||
class OffsetArea(_Area):
|
||||
def __init__(self, obj):
|
||||
_Area.__init__(self, obj)
|
||||
self.setProperties(obj)
|
||||
'''_Area.__init__(self, obj)
|
||||
self.setProperties(obj)'''
|
||||
super().__init__(obj) # Llama al constructor de _Area
|
||||
|
||||
def setProperties(self, obj):
|
||||
_Area.setProperties(self, obj)
|
||||
super().setProperties(obj) # Propiedades de la clase base
|
||||
|
||||
pl = obj.PropertiesList
|
||||
if not ("OffsetDistance" in pl):
|
||||
obj.addProperty("App::PropertyDistance",
|
||||
@@ -341,18 +351,28 @@ class OffsetArea(_Area):
|
||||
self.setProperties(obj)
|
||||
|
||||
def execute(self, obj):
|
||||
import Utils.PVPlantUtils as utils
|
||||
# Comprobar dependencias críticas
|
||||
if not hasattr(obj, "Base") or not obj.Base or not obj.Base.Shape:
|
||||
return
|
||||
if not hasattr(PVPlantSite, "get") or not PVPlantSite.get().Terrain:
|
||||
return
|
||||
|
||||
base = obj.Base.Shape
|
||||
land = PVPlantSite.get().Terrain.Mesh
|
||||
vec = FreeCAD.Vector(0, 0, 1)
|
||||
|
||||
wire = utils.getProjected(base, vec)
|
||||
wire = wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True)
|
||||
tmp = mp.projectShapeOnMesh(wire, land, vec)
|
||||
sections = mp.projectShapeOnMesh(wire, land, vec)
|
||||
print(" javi ", sections)
|
||||
pts = []
|
||||
for section in tmp:
|
||||
for section in sections:
|
||||
pts.extend(section)
|
||||
obj.Shape = Part.makePolygon(pts)
|
||||
# Crear forma solo si hay resultados
|
||||
if len(pts)>0:
|
||||
obj.Shape = Part.makePolygon(pts)
|
||||
else:
|
||||
obj.Shape = Part.Shape() # Forma vacía si falla
|
||||
|
||||
|
||||
class ViewProviderOffsetArea(_ViewProviderArea):
|
||||
@@ -363,24 +383,22 @@ class ViewProviderOffsetArea(_ViewProviderArea):
|
||||
def claimChildren(self):
|
||||
""" Provides object grouping """
|
||||
children = []
|
||||
if self.Object.Base:
|
||||
children.append(self.Object.Base)
|
||||
if self.ViewObject and self.ViewObject.Object.Base:
|
||||
children.append(self.ViewObject.Object.Base)
|
||||
return children
|
||||
|
||||
|
||||
''' Forbidden Area: '''
|
||||
|
||||
|
||||
def makeProhibitedArea(base = None):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "ProhibitedArea")
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "ExclusionArea")
|
||||
ProhibitedArea(obj)
|
||||
ViewProviderForbiddenArea(obj.ViewObject)
|
||||
if base:
|
||||
obj.Base = base
|
||||
try:
|
||||
group = FreeCAD.ActiveDocument.Exclusion
|
||||
group = FreeCAD.ActiveDocument.getObject("Exclusions")
|
||||
except:
|
||||
group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Exclusion')
|
||||
group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Exclusions')
|
||||
group.Label = "Exclusions"
|
||||
group.addObject(obj)
|
||||
|
||||
@@ -401,23 +419,443 @@ class ProhibitedArea(OffsetArea):
|
||||
"""Method run when the document is restored."""
|
||||
self.setProperties(obj)
|
||||
|
||||
def execute(self, obj):
|
||||
# Comprobar dependencias
|
||||
if not hasattr(obj, "Base") or not obj.Base or not obj.Base.Shape:
|
||||
return
|
||||
if not hasattr(PVPlantSite, "get") or not PVPlantSite.get().Terrain:
|
||||
return
|
||||
|
||||
class ViewProviderForbiddenArea(_ViewProviderArea):
|
||||
def getIcon(self):
|
||||
''' Return object treeview icon '''
|
||||
return str(os.path.join(DirIcons, "area_forbidden.svg"))
|
||||
base = obj.Base.Shape
|
||||
land = PVPlantSite.get().Terrain.Mesh
|
||||
vec = FreeCAD.Vector(0, 0, 1)
|
||||
|
||||
# 1. Crear wire original
|
||||
original_wire = utils.getProjected(base, vec)
|
||||
sections_original = mp.projectShapeOnMesh(original_wire, land, vec)
|
||||
|
||||
# 2. Crear wire offset
|
||||
offset_wire = original_wire.makeOffset2D(obj.OffsetDistance.Value, 2, False, False, True)
|
||||
sections_offset = mp.projectShapeOnMesh(offset_wire, land, vec)
|
||||
|
||||
# Crear formas compuestas
|
||||
def make_polygon(sections):
|
||||
if not sections:
|
||||
return Part.Shape()
|
||||
pts = []
|
||||
for section in sections:
|
||||
pts.extend(section)
|
||||
return Part.makePolygon(pts)
|
||||
|
||||
compounds = []
|
||||
if sections_original:
|
||||
compounds.append(make_polygon(sections_original))
|
||||
if sections_offset:
|
||||
compounds.append(make_polygon(sections_offset))
|
||||
|
||||
if compounds:
|
||||
obj.Shape = Part.makeCompound(compounds)
|
||||
else:
|
||||
obj.Shape = Part.Shape()
|
||||
|
||||
# Actualizar colores en la vista
|
||||
"""if FreeCAD.GuiUp and obj.ViewObject:
|
||||
obj.ViewObject.Proxy.updateVisual()"""
|
||||
|
||||
|
||||
class ViewProviderForbiddenArea_old:
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
self.setProperties(vobj)
|
||||
|
||||
def setProperties(self, vobj):
|
||||
# Propiedades de color
|
||||
if not hasattr(vobj, "OriginalColor"):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"OriginalColor",
|
||||
"ObjectStyle",
|
||||
"Color for original wire")
|
||||
vobj.OriginalColor = (1.0, 0.0, 0.0) # Rojo
|
||||
|
||||
if not hasattr(vobj, "OffsetColor"):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"OffsetColor",
|
||||
"ObjectStyle",
|
||||
"Color for offset wire")
|
||||
vobj.OffsetColor = (1.0, 0.0, 0.0) # Rojo
|
||||
|
||||
# Propiedades de grosor
|
||||
if not hasattr(vobj, "OriginalWidth"):
|
||||
vobj.addProperty("App::PropertyFloat",
|
||||
"OriginalWidth",
|
||||
"ObjectStyle",
|
||||
"Line width for original wire")
|
||||
vobj.OriginalWidth = 4.0
|
||||
|
||||
if not hasattr(vobj, "OffsetWidth"):
|
||||
vobj.addProperty("App::PropertyFloat",
|
||||
"OffsetWidth",
|
||||
"ObjectStyle",
|
||||
"Line width for offset wire")
|
||||
vobj.OffsetWidth = 4.0
|
||||
|
||||
# Deshabilitar el color por defecto
|
||||
vobj.setPropertyStatus("LineColor", "Hidden")
|
||||
vobj.setPropertyStatus("PointColor", "Hidden")
|
||||
vobj.setPropertyStatus("ShapeAppearance", "Hidden")
|
||||
|
||||
def attach(self, vobj):
|
||||
self.ViewObject = vobj
|
||||
self.Object = vobj.Object
|
||||
|
||||
# Crear la estructura de escena Coin3D
|
||||
self.root = coin.SoGroup()
|
||||
|
||||
# Switch para habilitar/deshabilitar la selección
|
||||
self.switch = coin.SoSwitch()
|
||||
self.switch.whichChild = coin.SO_SWITCH_ALL
|
||||
|
||||
# Separador para el wire original
|
||||
self.original_sep = coin.SoSeparator()
|
||||
self.original_color = coin.SoBaseColor()
|
||||
self.original_coords = coin.SoCoordinate3()
|
||||
self.original_line_set = coin.SoLineSet()
|
||||
self.original_draw_style = coin.SoDrawStyle()
|
||||
|
||||
# Separador para el wire offset
|
||||
self.offset_sep = coin.SoSeparator()
|
||||
self.offset_color = coin.SoBaseColor()
|
||||
self.offset_coords = coin.SoCoordinate3()
|
||||
self.offset_line_set = coin.SoLineSet()
|
||||
self.offset_draw_style = coin.SoDrawStyle()
|
||||
|
||||
# Construir la jerarquía de escena
|
||||
self.original_sep.addChild(self.original_color)
|
||||
self.original_sep.addChild(self.original_draw_style)
|
||||
self.original_sep.addChild(self.original_coords)
|
||||
self.original_sep.addChild(self.original_line_set)
|
||||
|
||||
self.offset_sep.addChild(self.offset_color)
|
||||
self.offset_sep.addChild(self.offset_draw_style)
|
||||
self.offset_sep.addChild(self.offset_coords)
|
||||
self.offset_sep.addChild(self.offset_line_set)
|
||||
|
||||
self.switch.addChild(self.original_sep)
|
||||
self.switch.addChild(self.offset_sep)
|
||||
self.root.addChild(self.switch)
|
||||
|
||||
vobj.addDisplayMode(self.root, "Wireframe")
|
||||
|
||||
# Inicializar estilos de dibujo
|
||||
self.original_draw_style.style = coin.SoDrawStyle.LINES
|
||||
self.offset_draw_style.style = coin.SoDrawStyle.LINES
|
||||
|
||||
# Actualizar visualización inicial
|
||||
if hasattr(self.Object, 'Shape'):
|
||||
self.updateData(self.Object, "Shape")
|
||||
self.updateVisual()
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
if prop == "Shape" and obj.Shape and not obj.Shape.isNull():
|
||||
self.updateGeometry()
|
||||
|
||||
def updateGeometry(self):
|
||||
"""Actualiza la geometría en la escena 3D"""
|
||||
if not hasattr(self, 'Object') or not self.Object.Shape or self.Object.Shape.isNull():
|
||||
return
|
||||
|
||||
# Limpiar coordenadas existentes
|
||||
self.original_coords.point.deleteValues(0)
|
||||
self.offset_coords.point.deleteValues(0)
|
||||
|
||||
# Obtener los sub-shapes
|
||||
subshapes = []
|
||||
if hasattr(self.Object.Shape, 'SubShapes') and self.Object.Shape.SubShapes:
|
||||
subshapes = self.Object.Shape.SubShapes
|
||||
elif hasattr(self.Object.Shape, 'ChildShapes') and self.Object.Shape.ChildShapes:
|
||||
subshapes = self.Object.Shape.ChildShapes
|
||||
|
||||
# Procesar wire original (primer sub-shape)
|
||||
if len(subshapes) > 0:
|
||||
self.processShape(subshapes[0], self.original_coords, self.original_line_set)
|
||||
|
||||
# Procesar wire offset (segundo sub-shape)
|
||||
if len(subshapes) > 1:
|
||||
self.processShape(subshapes[1], self.offset_coords, self.offset_line_set)
|
||||
|
||||
# Actualizar colores y grosores
|
||||
self.updateVisual()
|
||||
|
||||
def processShape(self, shape, coords_node, lineset_node):
|
||||
"""Procesa una forma y la añade al nodo de coordenadas"""
|
||||
if not shape or shape.isNull():
|
||||
return
|
||||
|
||||
points = []
|
||||
line_indices = []
|
||||
current_index = 0
|
||||
|
||||
# Obtener todos los edges de la forma
|
||||
edges = []
|
||||
if hasattr(shape, 'Edges'):
|
||||
edges = shape.Edges
|
||||
elif hasattr(shape, 'ChildShapes'):
|
||||
for child in shape.ChildShapes:
|
||||
if hasattr(child, 'Edges'):
|
||||
edges.extend(child.Edges)
|
||||
|
||||
for edge in edges:
|
||||
try:
|
||||
# Discretizar la curva para obtener puntos
|
||||
vertices = edge.discretize(Number=50)
|
||||
|
||||
for i, vertex in enumerate(vertices):
|
||||
points.append([vertex.x, vertex.y, vertex.z])
|
||||
line_indices.append(current_index)
|
||||
current_index += 1
|
||||
|
||||
# Añadir -1 para indicar fin de línea
|
||||
line_indices.append(-1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing edge: {e}")
|
||||
continue
|
||||
|
||||
# Configurar coordenadas y líneas
|
||||
if points:
|
||||
coords_node.point.setValues(0, len(points), points)
|
||||
lineset_node.numVertices.deleteValues(0)
|
||||
lineset_node.numVertices.setValues(0, len(line_indices), line_indices)
|
||||
|
||||
def updateVisual(self):
|
||||
"""Actualiza colores y grosores según las propiedades"""
|
||||
if not hasattr(self, 'ViewObject') or not self.ViewObject:
|
||||
return
|
||||
|
||||
vobj = self.ViewObject
|
||||
|
||||
try:
|
||||
# Configurar wire original
|
||||
if hasattr(vobj, "OriginalColor"):
|
||||
original_color = vobj.OriginalColor
|
||||
self.original_color.rgb.setValue(original_color[0], original_color[1], original_color[2])
|
||||
|
||||
if hasattr(vobj, "OriginalWidth"):
|
||||
self.original_draw_style.lineWidth = vobj.OriginalWidth
|
||||
|
||||
# Configurar wire offset
|
||||
if hasattr(vobj, "OffsetColor"):
|
||||
offset_color = vobj.OffsetColor
|
||||
self.offset_color.rgb.setValue(offset_color[0], offset_color[1], offset_color[2])
|
||||
|
||||
if hasattr(vobj, "OffsetWidth"):
|
||||
self.offset_draw_style.lineWidth = vobj.OffsetWidth
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error updating visual: {e}")
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
"""Maneja cambios en propiedades"""
|
||||
if prop in ["OriginalColor", "OffsetColor", "OriginalWidth", "OffsetWidth"]:
|
||||
self.updateVisual()
|
||||
|
||||
def getDisplayModes(self, obj):
|
||||
return ["Wireframe"]
|
||||
|
||||
def getDefaultDisplayMode(self):
|
||||
return "Wireframe"
|
||||
|
||||
def setDisplayMode(self, mode):
|
||||
return mode
|
||||
|
||||
def claimChildren(self):
|
||||
""" Provides object grouping """
|
||||
"""Proporciona agrupamiento de objetos"""
|
||||
children = []
|
||||
if self.Object.Base:
|
||||
if hasattr(self, 'Object') and self.Object and hasattr(self.Object, "Base"):
|
||||
children.append(self.Object.Base)
|
||||
return children
|
||||
|
||||
def getIcon(self):
|
||||
'''Return object treeview icon'''
|
||||
return str(os.path.join(DirIcons, "area_forbidden.svg"))
|
||||
|
||||
def onDocumentRestored(self, vobj):
|
||||
"""Método ejecutado cuando el documento es restaurado"""
|
||||
self.ViewObject = vobj
|
||||
self.Object = vobj.Object
|
||||
self.setProperties(vobj)
|
||||
self.attach(vobj)
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
|
||||
class ViewProviderForbiddenArea:
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
self.ViewObject = vobj
|
||||
|
||||
# Inicializar propiedades PRIMERO
|
||||
self.setProperties(vobj)
|
||||
|
||||
# Configurar colores iniciales
|
||||
self.updateColors(vobj)
|
||||
|
||||
def setProperties(self, vobj):
|
||||
if not hasattr(vobj, "OriginalColor"):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"OriginalColor",
|
||||
"Display",
|
||||
"Color for original wire")
|
||||
vobj.OriginalColor = (1.0, 0.0, 0.0) # Rojo
|
||||
|
||||
if not hasattr(vobj, "OffsetColor"):
|
||||
vobj.addProperty("App::PropertyColor",
|
||||
"OffsetColor",
|
||||
"Display",
|
||||
"Color for offset wire")
|
||||
vobj.OffsetColor = (1.0, 0.5, 0.0) # Naranja
|
||||
|
||||
def updateColors(self, vobj):
|
||||
"""Actualiza los colores desde las propiedades"""
|
||||
try:
|
||||
if hasattr(vobj, "OriginalColor"):
|
||||
self.original_color.rgb.setValue(*vobj.OriginalColor)
|
||||
else:
|
||||
self.original_color.rgb.setValue(1.0, 0.0, 0.0)
|
||||
|
||||
if hasattr(vobj, "OffsetColor"):
|
||||
self.offset_color.rgb.setValue(*vobj.OffsetColor)
|
||||
else:
|
||||
self.offset_color.rgb.setValue(1.0, 0.5, 0.0)
|
||||
except Exception as e:
|
||||
print(f"Error en updateColors: {e}")
|
||||
|
||||
def onDocumentRestored(self, vobj):
|
||||
self.setProperties(vobj)
|
||||
# No llamar a __init__ de nuevo, solo actualizar propiedades
|
||||
self.updateColors(vobj)
|
||||
|
||||
def getIcon(self):
|
||||
return str(os.path.join(DirIcons, "area_forbidden.svg"))
|
||||
|
||||
def attach(self, vobj):
|
||||
self.ViewObject = vobj
|
||||
|
||||
# Inicializar nodos Coin3D
|
||||
self.root = coin.SoGroup()
|
||||
self.original_coords = coin.SoCoordinate3()
|
||||
self.offset_coords = coin.SoCoordinate3()
|
||||
self.original_color = coin.SoBaseColor()
|
||||
self.offset_color = coin.SoBaseColor()
|
||||
self.original_lineset = coin.SoLineSet()
|
||||
self.offset_lineset = coin.SoLineSet()
|
||||
|
||||
# Añadir un nodo de dibujo para establecer el estilo de línea
|
||||
self.draw_style = coin.SoDrawStyle()
|
||||
self.draw_style.style = coin.SoDrawStyle.LINES
|
||||
self.draw_style.lineWidth = 3.0
|
||||
|
||||
# Construir la escena
|
||||
self.root.addChild(self.draw_style)
|
||||
|
||||
# Grupo para el polígono original
|
||||
original_group = coin.SoGroup()
|
||||
original_group.addChild(self.original_color)
|
||||
original_group.addChild(self.original_coords)
|
||||
original_group.addChild(self.original_lineset)
|
||||
|
||||
# Grupo para el polígono offset
|
||||
offset_group = coin.SoGroup()
|
||||
offset_group.addChild(self.offset_color)
|
||||
offset_group.addChild(self.offset_coords)
|
||||
offset_group.addChild(self.offset_lineset)
|
||||
|
||||
self.root.addChild(original_group)
|
||||
self.root.addChild(offset_group)
|
||||
|
||||
vobj.addDisplayMode(self.root, "Standard")
|
||||
# Asegurar que la visibilidad esté activada
|
||||
vobj.Visibility = True
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
if prop == "Shape":
|
||||
self.updateVisual(obj)
|
||||
|
||||
def updateVisual(self, obj):
|
||||
"""Actualiza la representación visual basada en la forma del objeto"""
|
||||
if not hasattr(obj, 'Shape') or not obj.Shape or obj.Shape.isNull():
|
||||
return
|
||||
|
||||
try:
|
||||
# Obtener todos los bordes de la forma compuesta
|
||||
all_edges = obj.Shape.Edges
|
||||
|
||||
# Separar bordes por polígono (asumimos que el primer polígono es el original)
|
||||
# Esto es una simplificación - podrías necesitar una lógica más sofisticada
|
||||
if len(all_edges) >= 2:
|
||||
# Polígono original - primer conjunto de bordes
|
||||
original_edges = [all_edges[0]]
|
||||
original_points = []
|
||||
for edge in original_edges:
|
||||
for vertex in edge.Vertexes:
|
||||
original_points.append((vertex.Point.x, vertex.Point.y, vertex.Point.z))
|
||||
|
||||
# Polígono offset - segundo conjunto de bordes
|
||||
offset_edges = [all_edges[1]]
|
||||
offset_points = []
|
||||
for edge in offset_edges:
|
||||
for vertex in edge.Vertexes:
|
||||
offset_points.append((vertex.Point.x, vertex.Point.y, vertex.Point.z))
|
||||
|
||||
# Asignar puntos a los nodos Coordinate3
|
||||
if original_points:
|
||||
self.original_coords.point.setValues(0, len(original_points), original_points)
|
||||
self.original_lineset.numVertices.setValue(len(original_points))
|
||||
|
||||
if offset_points:
|
||||
self.offset_coords.point.setValues(0, len(offset_points), offset_points)
|
||||
self.offset_lineset.numVertices.setValue(len(offset_points))
|
||||
|
||||
# Actualizar colores
|
||||
if hasattr(obj, 'ViewObject') and obj.ViewObject:
|
||||
self.updateColors(obj.ViewObject)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error en updateVisual: {e}")
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
if prop in ["OriginalColor", "OffsetColor"]:
|
||||
self.updateColors(vobj)
|
||||
elif prop == "Visibility" and vobj.Visibility:
|
||||
# Cuando la visibilidad cambia a True, actualizar visual
|
||||
self.updateVisual(vobj.Object)
|
||||
|
||||
def getDisplayModes(self, obj):
|
||||
return ["Standard"]
|
||||
|
||||
def getDefaultDisplayMode(self):
|
||||
return "Standard"
|
||||
|
||||
def setDisplayMode(self, mode):
|
||||
return mode
|
||||
|
||||
def claimChildren(self):
|
||||
children = []
|
||||
if hasattr(self, 'ViewObject') and self.ViewObject and hasattr(self.ViewObject.Object, 'Base'):
|
||||
children.append(self.ViewObject.Object.Base)
|
||||
return children
|
||||
|
||||
def dumps(self):
|
||||
return None
|
||||
|
||||
def loads(self, state):
|
||||
return None
|
||||
|
||||
''' PV Area: '''
|
||||
|
||||
|
||||
def makePVSubplant():
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PVSubplant")
|
||||
PVSubplant(obj)
|
||||
@@ -556,6 +994,12 @@ class PVSubplant:
|
||||
''' '''
|
||||
pass
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
pass
|
||||
|
||||
|
||||
class ViewProviderPVSubplant:
|
||||
def __init__(self, vobj):
|
||||
@@ -702,8 +1146,8 @@ class CommandFrameArea:
|
||||
'ToolTip': "Frame Area"}
|
||||
|
||||
def Activated(self):
|
||||
sel = FreeCADGui.Selection.getSelection()
|
||||
makeFramedArea(None, sel)
|
||||
for base in FreeCADGui.Selection.getSelection():
|
||||
makeFramedArea(None, base)
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument:
|
||||
@@ -720,8 +1164,8 @@ class CommandProhibitedArea:
|
||||
'ToolTip': "Prohibited Area"}
|
||||
|
||||
def Activated(self):
|
||||
sel = FreeCADGui.Selection.getSelection()
|
||||
makeProhibitedArea(sel[0])
|
||||
for base in FreeCADGui.Selection.getSelection():
|
||||
makeProhibitedArea(base)
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument:
|
||||
@@ -742,12 +1186,11 @@ class CommandPVSubplant:
|
||||
area = makePVSubplant()
|
||||
sel = FreeCADGui.Selection.getSelection()
|
||||
for obj in sel:
|
||||
if obj.Name[:7] == "Tracker":
|
||||
if hasattr(obj, 'Proxy') and obj.Proxy.Type == "Tracker":
|
||||
frame_list = area.Frames
|
||||
frame_list.append(obj)
|
||||
area.Frames = frame_list
|
||||
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument:
|
||||
return True
|
||||
@@ -763,11 +1206,8 @@ class CommandOffsetArea:
|
||||
'ToolTip': "OffsetArea"}
|
||||
|
||||
def Activated(self):
|
||||
sel = FreeCADGui.Selection.getSelection()
|
||||
base = None
|
||||
if sel:
|
||||
base = sel[0]
|
||||
obj = makeOffsetArea(base)
|
||||
for base in FreeCADGui.Selection.getSelection():
|
||||
makeOffsetArea(base)
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument:
|
||||
@@ -776,7 +1216,7 @@ class CommandOffsetArea:
|
||||
return False
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
'''if FreeCAD.GuiUp:
|
||||
class CommandAreaGroup:
|
||||
|
||||
def GetCommands(self):
|
||||
@@ -800,4 +1240,4 @@ if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('ForbiddenArea', CommandProhibitedArea())
|
||||
FreeCADGui.addCommand('PVSubplant', CommandPVSubplant())
|
||||
FreeCADGui.addCommand('OffsetArea', CommandOffsetArea())
|
||||
FreeCADGui.addCommand('PVPlantAreas', CommandAreaGroup())
|
||||
FreeCADGui.addCommand('PVPlantAreas', CommandAreaGroup())'''
|
||||
|
||||
@@ -128,7 +128,7 @@ def joinAreas(areas):
|
||||
shape.fuse(shapes)
|
||||
return shape
|
||||
|
||||
class CommandSplitArea:
|
||||
'''class CommandSplitArea:
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "split_area.svg")),
|
||||
'Accel': "A, S",
|
||||
@@ -162,6 +162,6 @@ class CommandJoinAreas:
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('SplitArea', CommandSplitArea())
|
||||
FreeCADGui.addCommand('JoinAreas', CommandJoinAreas())
|
||||
FreeCADGui.addCommand('JoinAreas', CommandJoinAreas())'''
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
from PySide import QtWidgets
|
||||
import os
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore, QtGui, QtWidgets
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
import os
|
||||
else:
|
||||
# \cond
|
||||
def translate(ctxt, txt):
|
||||
return txt
|
||||
|
||||
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||
return txt
|
||||
# \endcond
|
||||
|
||||
__title__ = "PVPlant Export to DXF"
|
||||
__author__ = "Javier Braña"
|
||||
__url__ = "http://www.sogos-solar.com"
|
||||
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
|
||||
def copy_object_with_reference():
|
||||
try:
|
||||
# Verificar selección
|
||||
selected = FreeCADGui.Selection.getSelection()
|
||||
if len(selected) != 1:
|
||||
QtWidgets.QMessageBox.critical(None, "Error", "Selecciona exactamente un objeto")
|
||||
return
|
||||
|
||||
original_doc = FreeCAD.ActiveDocument
|
||||
original_obj = selected[0]
|
||||
original_center = original_obj.Shape.BoundBox.Center
|
||||
|
||||
# Crear nuevo documento
|
||||
new_doc = FreeCAD.newDocument(f"{original_doc.Name} - {original_obj.Label}")
|
||||
|
||||
# Copiar objeto al nuevo documento
|
||||
new_obj = new_doc.copyObject(original_obj, True)
|
||||
new_obj.Label = f"Linked_{original_obj.Label}"
|
||||
new_obj.Placement.Base = original_obj.Placement.Base - original_center
|
||||
|
||||
# Guardar el documenton nuevp
|
||||
path = os.path.dirname(FreeCAD.ActiveDocument.FileName)
|
||||
new_doc.saveAs(os.path.join(path, new_doc.Name))
|
||||
|
||||
# Mantener posición original en el nuevo documento
|
||||
# new_obj.Placement = original_obj.Placement
|
||||
|
||||
# Crear referencia (App::Link) en el documento original
|
||||
link = original_doc.addObject("App::Link", f"Link_{new_obj.Label}")
|
||||
link.LinkedObject = new_obj
|
||||
|
||||
# Mantener posición original del objeto
|
||||
link.Placement = original_obj.Placement
|
||||
|
||||
# Actualizar vistas
|
||||
original_doc.recompute()
|
||||
new_doc.recompute()
|
||||
|
||||
# Regresar al documento original
|
||||
FreeCAD.setActiveDocument(original_doc.Name)
|
||||
|
||||
#QtWidgets.QMessageBox.information(None, "Éxito", "Operación completada correctamente")
|
||||
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.critical(None, "Error", f"Error: {str(e)}")
|
||||
|
||||
|
||||
# Ejecutar la función
|
||||
class CommandGenerateExternalDocument:
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "dxf.svg")),
|
||||
'Accel': "P, E",
|
||||
'MenuText': "Export to DXF",
|
||||
'ToolTip': QT_TRANSLATE_NOOP("Placement", "Export choosed layers to dxf")}
|
||||
|
||||
def Activated(self):
|
||||
''' '''
|
||||
copy_object_with_reference()
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -138,7 +138,7 @@ class ProjectSetupDialog(QtGui.QWidget):
|
||||
def closeForm(self):
|
||||
self.close()
|
||||
|
||||
class CommandProjectSetup:
|
||||
'''class CommandProjectSetup:
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "flash.svg")),
|
||||
'Accel': "P, S",
|
||||
@@ -159,5 +159,5 @@ class CommandProjectSetup:
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('ProjectSetup', CommandProjectSetup())
|
||||
FreeCADGui.addCommand('ProjectSetup', CommandProjectSetup())'''
|
||||
|
||||
|
||||
+61
-61
@@ -39,6 +39,16 @@
|
||||
<property name="spacing">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Maximum west-east slope:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QWidget" name="widget_2" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@@ -46,17 +56,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Frame coloring:</string>
|
||||
<string>South facing</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -66,71 +79,20 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Maximum west-east slope:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editWETL">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> º</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>90.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>8.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QDoubleSpinBox" name="editSFTL">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> º</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>90.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>2.800000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QWidget" name="widget_2" native="true"/>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>South facing</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
<string>Frame coloring:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editNFTL">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> º</string>
|
||||
@@ -149,7 +111,45 @@
|
||||
<string>North Facing</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QDoubleSpinBox" name="editSFTL">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> º</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>90.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>2.800000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QDoubleSpinBox" name="editWETL">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> º</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>90.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>8.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
+94
-42
@@ -21,6 +21,10 @@
|
||||
# ***********************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
from PySide import QtGui, QtCore
|
||||
import datetime
|
||||
import getpass
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui, os
|
||||
@@ -43,51 +47,99 @@ except AttributeError:
|
||||
import PVPlantResources
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
class SafeDict(dict):
|
||||
"""Diccionario seguro para manejar placeholders no definidos"""
|
||||
|
||||
def rename(objects, mask, mode=0):
|
||||
'''
|
||||
mode = 0/1/2/3
|
||||
0: izquierda a derecha - arriba a abajo
|
||||
1: arriba a abajo - izquierda a derecha
|
||||
'''
|
||||
|
||||
# sort:
|
||||
tmp = sorted(objects, key=lambda x: (x.Placement.Base.x,
|
||||
x.Placement.Base.y))
|
||||
|
||||
for idx, obj in tmp:
|
||||
obj.Name = name
|
||||
|
||||
class renamerTaskPanel:
|
||||
def __init__(self, obj=None):
|
||||
self.obj = obj
|
||||
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
# Module widget form
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
self.formRack = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantFrame.ui")
|
||||
self.formRack.widgetTracker.setVisible(False)
|
||||
self.formRack.comboFrameType.currentIndexChanged.connect(self.selectionchange)
|
||||
|
||||
self.formPiling = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantRackFixedPiling.ui")
|
||||
self.formPiling.editBreadthwaysNumOfPost.valueChanged.connect(self.editBreadthwaysNumOfPostChange)
|
||||
self.formPiling.editAlongNumOfPost.valueChanged.connect(self.editAlongNumOfPostChange)
|
||||
|
||||
self.form = [self.formRack, self.formPiling]
|
||||
|
||||
def accept(self):
|
||||
self.closeForm()
|
||||
return True
|
||||
|
||||
def reject(self):
|
||||
self.closeForm()
|
||||
return False
|
||||
|
||||
def closeForm(self):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
def __missing__(self, key):
|
||||
return f'{{{key}}}'
|
||||
|
||||
|
||||
class _CommandRenamer:
|
||||
class RenameDialog(QtGui.QDialog):
|
||||
def __init__(self):
|
||||
super(RenameDialog, self).__init__()
|
||||
self.setupUI()
|
||||
|
||||
def setupUI(self):
|
||||
self.setWindowTitle("Renombrar objetos con plantilla")
|
||||
self.setMinimumWidth(400)
|
||||
layout = QtGui.QVBoxLayout(self)
|
||||
|
||||
# Campo para la plantilla
|
||||
layout.addWidget(QtGui.QLabel("Plantilla de nombre:"))
|
||||
self.template_input = QtGui.QLineEdit()
|
||||
self.template_input.setPlaceholderText("Ej: {label}_mod_{index:03d}_{date:%Y%m%d}")
|
||||
layout.addWidget(self.template_input)
|
||||
|
||||
# Info de placeholders
|
||||
info = QtGui.QLabel(
|
||||
"Placeholders disponibles:\n"
|
||||
"• {index} - Número en orden\n"
|
||||
"• {label} - Nombre actual del objeto\n"
|
||||
"• {name} - Nombre interno\n"
|
||||
"• {date} - Fecha actual\n"
|
||||
"• {time} - Hora actual\n"
|
||||
"• {user} - Usuario del sistema\n"
|
||||
"• {datetime} - Fecha y hora completa\n"
|
||||
"Formatos: {date:%Y/%m/%d}, {index:03d}, etc."
|
||||
)
|
||||
layout.addWidget(info)
|
||||
|
||||
# Botones
|
||||
btn_box = QtGui.QDialogButtonBox()
|
||||
btn_box.addButton(QtGui.QDialogButtonBox.Apply)
|
||||
btn_box.addButton(QtGui.QDialogButtonBox.Close)
|
||||
btn_box.clicked.connect(self.on_button_click)
|
||||
layout.addWidget(btn_box)
|
||||
|
||||
def on_button_click(self, button):
|
||||
if button == btn_box.button(QtGui.QDialogButtonBox.Apply):
|
||||
self.rename_objects()
|
||||
else:
|
||||
self.close()
|
||||
|
||||
def rename_objects(self):
|
||||
template = self.template_input.text()
|
||||
if not template:
|
||||
QtGui.QMessageBox.warning(self, "Error", "¡La plantilla no puede estar vacía!")
|
||||
return
|
||||
|
||||
selected_objects = FreeCADGui.Selection.getSelection()
|
||||
if not selected_objects:
|
||||
QtGui.QMessageBox.warning(self, "Error", "¡No hay objetos seleccionados!")
|
||||
return
|
||||
|
||||
now = datetime.datetime.now()
|
||||
user_name = getpass.getuser()
|
||||
errors = []
|
||||
|
||||
for idx, obj in enumerate(selected_objects, 1):
|
||||
try:
|
||||
placeholders = SafeDict({
|
||||
'index': idx,
|
||||
'label': obj.Label,
|
||||
'name': obj.Name,
|
||||
'date': now.date(),
|
||||
'time': now.time(),
|
||||
'datetime': now,
|
||||
'user': user_name
|
||||
})
|
||||
|
||||
new_name = template.format_map(placeholders)
|
||||
obj.Label = new_name
|
||||
except Exception as e:
|
||||
errors.append(f"{obj.Name}: {str(e)}")
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
if errors:
|
||||
error_msg = "\n".join(errors)
|
||||
QtGui.QMessageBox.critical(self, "Errores", f"Error(es) encontrado(s):\n{error_msg}")
|
||||
else:
|
||||
QtGui.QMessageBox.information(self, "Éxito", "¡Objetos renombrados correctamente!")
|
||||
|
||||
|
||||
|
||||
class CommandRenamer:
|
||||
"the Arch Building command definition"
|
||||
|
||||
def GetResources(self):
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
# 🚧 FreeCAD Road Workbench
|
||||
# 🚧 FreeCAD PVPlant Workbench
|
||||
|
||||
Road is the Transportation and Geomatics Engineering workbench for FreeCAD.
|
||||
PVPlant es el workbench de Ingeniería Fotovoltaica para FreeCAD.
|
||||
|
||||
## ✨ Features
|
||||
## ✨ Características
|
||||
|
||||
* Geopoints
|
||||
* Terrain
|
||||
* Alignment
|
||||
* Profile
|
||||
* Regions
|
||||
* Sections
|
||||
* Volume
|
||||
* GeoLine
|
||||
* LandXML
|
||||
* Puntos Geográficos
|
||||
* Terreno
|
||||
* Diseño de Estructuras
|
||||
* Generación de Perfiles
|
||||
* Regiones
|
||||
* Secciones
|
||||
* Cálculo de Volumen
|
||||
* Líneas Geográficas
|
||||
* Importación/Exportación LandXML
|
||||
|
||||
## 📥 Installation
|
||||
### 🔹 Option 1: Install via Addon Manager (Recommended)
|
||||
## 📥 Instalación
|
||||
### 🔹 Opción 1: Instalar mediante el Administrador de Complementos (Recomendado)
|
||||
|
||||
1. Open FreeCAD.
|
||||
2. Go to **Tools > Addon Manager**.
|
||||
3. In the Addon Manager window, search for **Road**.
|
||||
4. Select the workbench and click the **Install** button.
|
||||
5. Restart FreeCAD to complete the installation.
|
||||
1. Abre FreeCAD.
|
||||
2. Ve a **Herramientas > Administrador de Complementos**.
|
||||
3. En la ventana del Administrador de Complementos, busca **PVPlant**.
|
||||
4. Selecciona el workbench y haz clic en el botón **Instalar**.
|
||||
5. Reinicia FreeCAD para completar la instalación.
|
||||
|
||||
### 🔹 Option 2: Manual Installation
|
||||
### 🔹 Opción 2: Instalación Manual
|
||||
|
||||
1. Download the latest release the repository.
|
||||
2. Copy the downloaded folder to your FreeCAD Mod directory:
|
||||
1. Descarga la última versión del repositorio.
|
||||
2. Copia la carpeta descargada en el directorio Mod de FreeCAD:
|
||||
|
||||
```
|
||||
Windows: C:\Users\<YourUsername>\AppData\Roaming\FreeCAD\Mod
|
||||
Linux(Flatpak): /home/<YourUsername>/.var/app/org.freecad.FreeCAD/data/FreeCAD/Mod
|
||||
Windows: C:\Users\<TuUsuario>\AppData\Roaming\FreeCAD\Mod
|
||||
Linux(Flatpak): /home/<TuUsuario>/.var/app/org.freecad.FreeCAD/data/FreeCAD/Mod
|
||||
MacOS: ~/Library/Preferences/FreeCAD/Mod
|
||||
```
|
||||
|
||||
3. Restart FreeCAD to complete the installation.
|
||||
3. Reinicia FreeCAD para completar la instalación.
|
||||
|
||||
## 💬 Feedback and Support
|
||||
## 💬 Feedback y Soporte
|
||||
|
||||
💡 Need help? Join the discussion on the FreeCAD Forum: [FreeCAD Road Workbench](https://forum.freecadweb.org/viewtopic.php?f=8&t=34371).
|
||||
💡 ¿Necesitas ayuda? Únete a la discusión en el foro de FreeCAD: [FreeCAD PVPlant Workbench](https://forum.freecadweb.org/).
|
||||
|
||||
🐞 Found a bug? Report issues on [GitHub](https://github.com/HakanSeven12/Road/issues).
|
||||
🐞 ¿Encontraste un error? Reporta problemas en [GitHub](https://github.com/HakanSeven12/PVPlant/issues).
|
||||
|
||||
## 👨💻 Developer
|
||||
## 👨💻 Desarrollador
|
||||
|
||||
Developed with passion by Hakan Seven ([@HakanSeven12](https://github.com/HakanSeven12)) with inspiration and contributions from the FreeCAD community.
|
||||
Desarrollado con pasión por Javier Braña con inspiración y contribuciones de la comunidad de FreeCAD.
|
||||
|
||||
## 📸 Screenshots
|
||||

|
||||

|
||||

|
||||

|
||||
## 📸 Capturas de Pantalla
|
||||

|
||||

|
||||

|
||||

|
||||
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
@@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<polygon style="fill:#FFB74F;" points="432.106,250.534 432.106,470.021 296.578,470.021 296.578,336.975 221.399,336.975
|
||||
221.399,470.021 79.894,470.021 79.894,250.534 256,115.075 "/>
|
||||
<path style="fill:#FF7D3C;" d="M439.485,183.135V90.306h-74.167v35.772L256,41.979L0,238.92l53.633,69.712L256,152.959
|
||||
l202.367,155.672L512,238.92L439.485,183.135z"/>
|
||||
<polygon style="fill:#FF9A00;" points="432.106,250.534 432.106,470.021 296.578,470.021 296.578,336.975 256,336.975 256,115.075
|
||||
"/>
|
||||
<polygon style="fill:#FF4E19;" points="512,238.92 458.367,308.632 256,152.959 256,41.979 365.318,126.078 365.318,90.306
|
||||
439.485,90.306 439.485,183.135 "/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="Layer_1"
|
||||
enable-background="new 0 0 511.771 511.771"
|
||||
height="512"
|
||||
viewBox="0 0 511.771 511.771"
|
||||
width="512"
|
||||
version="1.1"
|
||||
sodipodi:docname="stringsetup.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs
|
||||
id="defs11" />
|
||||
<sodipodi:namedview
|
||||
id="namedview9"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.4707031"
|
||||
inkscape:cx="256"
|
||||
inkscape:cy="256"
|
||||
inkscape:window-width="2160"
|
||||
inkscape:window-height="1361"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<g
|
||||
id="g6"
|
||||
transform="matrix(0.99955273,0,0,0.99955273,-8.003632e-5,1.12e-6)">
|
||||
<g
|
||||
id="g4">
|
||||
<path
|
||||
d="m 496.659,312.107 -47.061,-36.8 c 0.597,-5.675 1.109,-12.309 1.109,-19.328 0,-7.019 -0.491,-13.653 -1.109,-19.328 l 47.104,-36.821 c 8.747,-6.912 11.136,-19.179 5.568,-29.397 L 453.331,85.76 C 448.104,76.203 436.648,71.296 425.022,75.584 L 369.491,97.877 C 358.846,90.197 347.688,83.712 336.147,78.528 L 327.699,19.627 C 326.312,8.448 316.584,0 305.086,0 h -98.133 c -11.499,0 -21.205,8.448 -22.571,19.456 l -8.469,59.115 c -11.179,5.035 -22.165,11.435 -33.28,19.349 L 86.953,75.563 C 76.52,71.531 64.04,76.053 58.856,85.568 L 9.854,170.347 c -5.781,9.771 -3.392,22.464 5.547,29.547 l 47.061,36.8 c -0.747,7.189 -1.109,13.44 -1.109,19.307 0,5.867 0.363,12.117 1.109,19.328 L 15.358,312.15 c -8.747,6.933 -11.115,19.2 -5.547,29.397 l 48.939,84.672 c 5.227,9.536 16.576,14.485 28.309,10.176 l 55.531,-22.293 c 10.624,7.659 21.781,14.144 33.323,19.349 l 8.448,58.88 C 185.747,503.552 195.454,512 206.974,512 h 98.133 c 11.499,0 21.227,-8.448 22.592,-19.456 l 8.469,-59.093 c 11.179,-5.056 22.144,-11.435 33.28,-19.371 l 55.68,22.357 c 2.688,1.045 5.483,1.579 8.363,1.579 8.277,0 15.893,-4.523 19.733,-11.563 l 49.152,-85.12 c 5.462,-9.984 3.072,-22.25 -5.717,-29.226 z m -240.64,29.226 c -47.061,0 -85.333,-38.272 -85.333,-85.333 0,-47.061 38.272,-85.333 85.333,-85.333 47.061,0 85.333,38.272 85.333,85.333 0,47.061 -38.272,85.333 -85.333,85.333 z"
|
||||
id="path2" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1,132 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="trench.svg"
|
||||
id="svg66"
|
||||
version="1.1"
|
||||
width="512pt"
|
||||
viewBox="0 0 512 512"
|
||||
height="512pt">
|
||||
<metadata
|
||||
id="metadata72">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs70" />
|
||||
<sodipodi:namedview
|
||||
inkscape:current-layer="svg66"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:cy="341.33333"
|
||||
inkscape:cx="341.33333"
|
||||
inkscape:zoom="1.0766602"
|
||||
showgrid="false"
|
||||
id="namedview68"
|
||||
inkscape:window-height="1361"
|
||||
inkscape:window-width="2160"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
<path
|
||||
id="path2"
|
||||
fill="#ffb655"
|
||||
d="m359.78125 71.285156v288.496094h-207.5625v-288.496094h-144.71875v433.214844h497v-433.214844zm0 0" />
|
||||
<path
|
||||
id="path4"
|
||||
fill="#a4e276"
|
||||
d="m7.5 15.5c18.089844 0 18.089844-8 36.183594-8 18.089844 0 18.089844 8 36.179687 8 18.089844 0 18.089844-8 36.175781-8 18.089844 0 18.089844 8 36.179688 8v55.785156h-144.71875zm0 0" />
|
||||
<path
|
||||
id="path6"
|
||||
fill="#a4e276"
|
||||
d="m359.78125 15.5c18.089844 0 18.089844-8 36.183594-8 18.089844 0 18.089844 8 36.179687 8 18.089844 0 18.089844-8 36.175781-8 18.089844 0 18.089844 8 36.179688 8v55.785156h-144.71875zm0 0" />
|
||||
<path
|
||||
id="path16"
|
||||
fill="#ff7956"
|
||||
d="m359.78125 71.285156h30v288.496094h-30zm0 0" />
|
||||
<path
|
||||
id="path18"
|
||||
fill="#ff7956"
|
||||
d="m7.5 71.285156h30v433.214844h-30zm0 0" />
|
||||
<path
|
||||
id="path20"
|
||||
fill="#64c37d"
|
||||
d="m58.683594 10.179688c-3.6875-1.476563-7.984375-2.679688-15-2.679688-18.09375 0-18.09375 8-36.183594 8v55.785156h30v-55.785156c11.070312 0 15.367188-2.996094 21.183594-5.320312zm0 0" />
|
||||
<path
|
||||
id="path22"
|
||||
fill="#64c37d"
|
||||
d="m410.964844 10.179688c-3.6875-1.476563-7.984375-2.679688-15-2.679688-18.09375 0-18.09375 8-36.183594 8v55.785156h30v-55.785156c11.070312 0 15.367188-2.996094 21.183594-5.320312zm0 0" />
|
||||
<path
|
||||
id="path28"
|
||||
d="m144.71875 91.289062v275.992188h67.402344v-15h-52.402344v-260.992188zm0 0" />
|
||||
<path
|
||||
id="path30"
|
||||
d="m299.871094 367.28125h67.410156v-275.992188h-15v260.992188h-52.410156zm0 0" />
|
||||
<path
|
||||
id="path32"
|
||||
d="m497 497h-482v-405.714844h-15v420.714844h512v-420.714844h-15zm0 0" />
|
||||
<path
|
||||
id="path34"
|
||||
d="m159.71875 8h-7.5c-7.460938 0-10.8125-1.480469-15.054688-3.359375-4.917968-2.175781-10.492187-4.640625-21.125-4.640625-10.628906 0-16.203124 2.464844-21.121093 4.640625-4.242188 1.878906-7.59375 3.359375-15.054688 3.359375-7.460937 0-10.8125-1.480469-15.058593-3.359375-4.917969-2.175781-10.492188-4.640625-21.121094-4.640625-10.632813 0-16.207032 2.464844-21.125 4.640625-4.246094 1.878906-7.597656 3.359375-15.058594 3.359375h-7.5v70.785156h159.71875zm-15 55.785156h-129.71875v-41.257812c6.054688-.820313 10.015625-2.574219 13.625-4.167969 4.242188-1.878906 7.597656-3.359375 15.058594-3.359375 7.460937 0 10.8125 1.480469 15.054687 3.359375 4.917969 2.175781 10.496094 4.640625 21.125 4.640625 10.628907 0 16.203125-2.464844 21.121094-4.640625 4.246094-1.878906 7.597656-3.359375 15.054687-3.359375 7.460938 0 10.8125 1.480469 15.058594 3.359375 3.605469 1.59375 7.570313 3.347656 13.621094 4.167969zm0 0" />
|
||||
<path
|
||||
id="path36"
|
||||
d="m504.5 8c-7.460938 0-10.8125-1.480469-15.054688-3.359375-4.917968-2.175781-10.496093-4.640625-21.125-4.640625-10.628906 0-16.203124 2.464844-21.121093 4.640625-4.242188 1.878906-7.59375 3.359375-15.054688 3.359375-7.460937 0-10.8125-1.480469-15.058593-3.359375-4.917969-2.175781-10.492188-4.640625-21.121094-4.640625-10.632813 0-16.207032 2.464844-21.125 4.640625-4.246094 1.878906-7.597656 3.359375-15.058594 3.359375h-7.5v70.785156h159.71875v-70.785156zm-7.5 55.785156h-129.71875v-41.257812c6.054688-.820313 10.015625-2.574219 13.625-4.167969 4.242188-1.878906 7.597656-3.359375 15.058594-3.359375 7.460937 0 10.8125 1.480469 15.054687 3.359375 4.917969 2.175781 10.496094 4.640625 21.125 4.640625 10.628907 0 16.203125-2.464844 21.121094-4.640625 4.246094-1.878906 7.597656-3.359375 15.058594-3.359375 7.457031 0 10.8125 1.480469 15.054687 3.359375 3.609375 1.59375 7.570313 3.347656 13.621094 4.167969zm0 0" />
|
||||
<path
|
||||
id="path40"
|
||||
d="m131.007812 423.90625h15v15h-15zm0 0" />
|
||||
<path
|
||||
id="path42"
|
||||
d="m348.007812 456.929688h15v15h-15zm0 0" />
|
||||
<path
|
||||
id="path44"
|
||||
d="m386.429688 430.050781h15v15h-15zm0 0" />
|
||||
<path
|
||||
id="path46"
|
||||
d="m423.140625 260.738281h15v15h-15zm0 0" />
|
||||
<path
|
||||
id="path48"
|
||||
d="m84.722656 403.050781h15v15h-15zm0 0" />
|
||||
<path
|
||||
id="path50"
|
||||
d="m384.929688 294.355469h15v15h-15zm0 0" />
|
||||
<path
|
||||
id="path52"
|
||||
d="m50.222656 130.597656h15v15h-15zm0 0" />
|
||||
<path
|
||||
id="path54"
|
||||
d="m92.222656 98.785156h15v15h-15zm0 0" />
|
||||
<path
|
||||
id="path56"
|
||||
d="m430.640625 313.023438h15v15h-15zm0 0" />
|
||||
<path
|
||||
id="path58"
|
||||
d="m57.722656 439.050781h15v15h-15zm0 0" />
|
||||
<g
|
||||
id="g64"
|
||||
fill="#fff">
|
||||
<path
|
||||
id="path60"
|
||||
d="m487 318.523438h-15v-172.238282h15zm0-182.238282h-15v-15h15zm0-25h-15v-15h15zm0 0" />
|
||||
<path
|
||||
id="path62"
|
||||
d="m486.5 53.785156h-25.71875v-15h25.71875zm-35.71875 0h-15v-15h15zm-25 0h-15v-15h15zm0 0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.9 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,462 +0,0 @@
|
||||
/*!
|
||||
Copyright (c) 2011-2015, Pavel Shramov, Bruno Bergot - MIT licence
|
||||
*/
|
||||
|
||||
L.KML = L.FeatureGroup.extend({
|
||||
|
||||
initialize: function (kml) {
|
||||
this._kml = kml;
|
||||
this._layers = {};
|
||||
|
||||
if (kml) {
|
||||
this.addKML(kml);
|
||||
}
|
||||
},
|
||||
|
||||
addKML: function (xml) {
|
||||
var layers = L.KML.parseKML(xml);
|
||||
if (!layers || !layers.length) return;
|
||||
for (var i = 0; i < layers.length; i++) {
|
||||
this.fire('addlayer', {
|
||||
layer: layers[i]
|
||||
});
|
||||
this.addLayer(layers[i]);
|
||||
}
|
||||
this.latLngs = L.KML.getLatLngs(xml);
|
||||
this.fire('loaded');
|
||||
},
|
||||
|
||||
latLngs: []
|
||||
});
|
||||
|
||||
L.Util.extend(L.KML, {
|
||||
|
||||
parseKML: function (xml) {
|
||||
var style = this.parseStyles(xml);
|
||||
this.parseStyleMap(xml, style);
|
||||
var el = xml.getElementsByTagName('Folder');
|
||||
var layers = [], l;
|
||||
for (var i = 0; i < el.length; i++) {
|
||||
if (!this._check_folder(el[i])) { continue; }
|
||||
l = this.parseFolder(el[i], style);
|
||||
if (l) { layers.push(l); }
|
||||
}
|
||||
el = xml.getElementsByTagName('Placemark');
|
||||
for (var j = 0; j < el.length; j++) {
|
||||
if (!this._check_folder(el[j])) { continue; }
|
||||
l = this.parsePlacemark(el[j], xml, style);
|
||||
if (l) { layers.push(l); }
|
||||
}
|
||||
el = xml.getElementsByTagName('GroundOverlay');
|
||||
for (var k = 0; k < el.length; k++) {
|
||||
l = this.parseGroundOverlay(el[k]);
|
||||
if (l) { layers.push(l); }
|
||||
}
|
||||
return layers;
|
||||
},
|
||||
|
||||
// Return false if e's first parent Folder is not [folder]
|
||||
// - returns true if no parent Folders
|
||||
_check_folder: function (e, folder) {
|
||||
e = e.parentNode;
|
||||
while (e && e.tagName !== 'Folder')
|
||||
{
|
||||
e = e.parentNode;
|
||||
}
|
||||
return !e || e === folder;
|
||||
},
|
||||
|
||||
parseStyles: function (xml) {
|
||||
var styles = {};
|
||||
var sl = xml.getElementsByTagName('Style');
|
||||
for (var i=0, len=sl.length; i<len; i++) {
|
||||
var style = this.parseStyle(sl[i]);
|
||||
if (style) {
|
||||
var styleName = '#' + style.id;
|
||||
styles[styleName] = style;
|
||||
}
|
||||
}
|
||||
return styles;
|
||||
},
|
||||
|
||||
parseStyle: function (xml) {
|
||||
var style = {}, poptions = {}, ioptions = {}, el, id;
|
||||
|
||||
var attributes = {color: true, width: true, Icon: true, href: true, hotSpot: true};
|
||||
|
||||
function _parse (xml) {
|
||||
var options = {};
|
||||
for (var i = 0; i < xml.childNodes.length; i++) {
|
||||
var e = xml.childNodes[i];
|
||||
var key = e.tagName;
|
||||
if (!attributes[key]) { continue; }
|
||||
if (key === 'hotSpot')
|
||||
{
|
||||
for (var j = 0; j < e.attributes.length; j++) {
|
||||
options[e.attributes[j].name] = e.attributes[j].nodeValue;
|
||||
}
|
||||
} else {
|
||||
var value = e.childNodes[0].nodeValue;
|
||||
if (key === 'color') {
|
||||
options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
|
||||
options.color = '#' + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
|
||||
} else if (key === 'width') {
|
||||
options.weight = parseInt(value);
|
||||
} else if (key === 'Icon') {
|
||||
ioptions = _parse(e);
|
||||
if (ioptions.href) { options.href = ioptions.href; }
|
||||
} else if (key === 'href') {
|
||||
options.href = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
el = xml.getElementsByTagName('LineStyle');
|
||||
if (el && el[0]) { style = _parse(el[0]); }
|
||||
el = xml.getElementsByTagName('PolyStyle');
|
||||
if (el && el[0]) { poptions = _parse(el[0]); }
|
||||
if (poptions.color) { style.fillColor = poptions.color; }
|
||||
if (poptions.opacity) { style.fillOpacity = poptions.opacity; }
|
||||
el = xml.getElementsByTagName('IconStyle');
|
||||
if (el && el[0]) { ioptions = _parse(el[0]); }
|
||||
if (ioptions.href) {
|
||||
style.icon = new L.KMLIcon({
|
||||
iconUrl: ioptions.href,
|
||||
shadowUrl: null,
|
||||
anchorRef: {x: ioptions.x, y: ioptions.y},
|
||||
anchorType: {x: ioptions.xunits, y: ioptions.yunits}
|
||||
});
|
||||
}
|
||||
|
||||
id = xml.getAttribute('id');
|
||||
if (id && style) {
|
||||
style.id = id;
|
||||
}
|
||||
|
||||
return style;
|
||||
},
|
||||
|
||||
parseStyleMap: function (xml, existingStyles) {
|
||||
var sl = xml.getElementsByTagName('StyleMap');
|
||||
|
||||
for (var i = 0; i < sl.length; i++) {
|
||||
var e = sl[i], el;
|
||||
var smKey, smStyleUrl;
|
||||
|
||||
el = e.getElementsByTagName('key');
|
||||
if (el && el[0]) { smKey = el[0].textContent; }
|
||||
el = e.getElementsByTagName('styleUrl');
|
||||
if (el && el[0]) { smStyleUrl = el[0].textContent; }
|
||||
|
||||
if (smKey === 'normal')
|
||||
{
|
||||
existingStyles['#' + e.getAttribute('id')] = existingStyles[smStyleUrl];
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
},
|
||||
|
||||
parseFolder: function (xml, style) {
|
||||
var el, layers = [], l;
|
||||
el = xml.getElementsByTagName('Folder');
|
||||
for (var i = 0; i < el.length; i++) {
|
||||
if (!this._check_folder(el[i], xml)) { continue; }
|
||||
l = this.parseFolder(el[i], style);
|
||||
if (l) { layers.push(l); }
|
||||
}
|
||||
el = xml.getElementsByTagName('Placemark');
|
||||
for (var j = 0; j < el.length; j++) {
|
||||
if (!this._check_folder(el[j], xml)) { continue; }
|
||||
l = this.parsePlacemark(el[j], xml, style);
|
||||
if (l) { layers.push(l); }
|
||||
}
|
||||
el = xml.getElementsByTagName('GroundOverlay');
|
||||
for (var k = 0; k < el.length; k++) {
|
||||
if (!this._check_folder(el[k], xml)) { continue; }
|
||||
l = this.parseGroundOverlay(el[k]);
|
||||
if (l) { layers.push(l); }
|
||||
}
|
||||
if (!layers.length) { return; }
|
||||
if (layers.length === 1) { return layers[0]; }
|
||||
return new L.FeatureGroup(layers);
|
||||
},
|
||||
|
||||
parsePlacemark: function (place, xml, style, options) {
|
||||
var h, i, j, k, el, il, opts = options || {};
|
||||
|
||||
el = place.getElementsByTagName('styleUrl');
|
||||
for (i = 0; i < el.length; i++) {
|
||||
var url = el[i].childNodes[0].nodeValue;
|
||||
for (var a in style[url]) {
|
||||
opts[a] = style[url][a];
|
||||
}
|
||||
}
|
||||
|
||||
il = place.getElementsByTagName('Style')[0];
|
||||
if (il) {
|
||||
var inlineStyle = this.parseStyle(place);
|
||||
if (inlineStyle) {
|
||||
for (k in inlineStyle) {
|
||||
opts[k] = inlineStyle[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var multi = ['MultiGeometry', 'MultiTrack', 'gx:MultiTrack'];
|
||||
for (h in multi) {
|
||||
el = place.getElementsByTagName(multi[h]);
|
||||
for (i = 0; i < el.length; i++) {
|
||||
var layer = this.parsePlacemark(el[i], xml, style, opts);
|
||||
this.addPlacePopup(place, layer);
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
|
||||
var layers = [];
|
||||
|
||||
var parse = ['LineString', 'Polygon', 'Point', 'Track', 'gx:Track'];
|
||||
for (j in parse) {
|
||||
var tag = parse[j];
|
||||
el = place.getElementsByTagName(tag);
|
||||
for (i = 0; i < el.length; i++) {
|
||||
var l = this['parse' + tag.replace(/gx:/, '')](el[i], xml, opts);
|
||||
if (l) { layers.push(l); }
|
||||
}
|
||||
}
|
||||
|
||||
if (!layers.length) {
|
||||
return;
|
||||
}
|
||||
var layer = layers[0];
|
||||
if (layers.length > 1) {
|
||||
layer = new L.FeatureGroup(layers);
|
||||
}
|
||||
|
||||
this.addPlacePopup(place, layer);
|
||||
return layer;
|
||||
},
|
||||
|
||||
addPlacePopup: function(place, layer) {
|
||||
var i, j, name, descr = '';
|
||||
el = place.getElementsByTagName('name');
|
||||
if (el.length && el[0].childNodes.length) {
|
||||
name = el[0].childNodes[0].nodeValue;
|
||||
}
|
||||
el = place.getElementsByTagName('description');
|
||||
for (i = 0; i < el.length; i++) {
|
||||
for (j = 0; j < el[i].childNodes.length; j++) {
|
||||
descr = descr + el[i].childNodes[j].nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (name) {
|
||||
layer.bindPopup('<h2>' + name + '</h2>' + descr, { className: 'kml-popup'});
|
||||
}
|
||||
},
|
||||
|
||||
parseCoords: function (xml) {
|
||||
var el = xml.getElementsByTagName('coordinates');
|
||||
return this._read_coords(el[0]);
|
||||
},
|
||||
|
||||
parseLineString: function (line, xml, options) {
|
||||
var coords = this.parseCoords(line);
|
||||
if (!coords.length) { return; }
|
||||
return new L.Polyline(coords, options);
|
||||
},
|
||||
|
||||
parseTrack: function (line, xml, options) {
|
||||
var el = xml.getElementsByTagName('gx:coord');
|
||||
if (el.length === 0) { el = xml.getElementsByTagName('coord'); }
|
||||
var coords = [];
|
||||
for (var j = 0; j < el.length; j++) {
|
||||
coords = coords.concat(this._read_gxcoords(el[j]));
|
||||
}
|
||||
if (!coords.length) { return; }
|
||||
return new L.Polyline(coords, options);
|
||||
},
|
||||
|
||||
parsePoint: function (line, xml, options) {
|
||||
var el = line.getElementsByTagName('coordinates');
|
||||
if (!el.length) {
|
||||
return;
|
||||
}
|
||||
var ll = el[0].childNodes[0].nodeValue.split(',');
|
||||
return new L.KMLMarker(new L.LatLng(ll[1], ll[0]), options);
|
||||
},
|
||||
|
||||
parsePolygon: function (line, xml, options) {
|
||||
var el, polys = [], inner = [], i, coords;
|
||||
el = line.getElementsByTagName('outerBoundaryIs');
|
||||
for (i = 0; i < el.length; i++) {
|
||||
coords = this.parseCoords(el[i]);
|
||||
if (coords) {
|
||||
polys.push(coords);
|
||||
}
|
||||
}
|
||||
el = line.getElementsByTagName('innerBoundaryIs');
|
||||
for (i = 0; i < el.length; i++) {
|
||||
coords = this.parseCoords(el[i]);
|
||||
if (coords) {
|
||||
inner.push(coords);
|
||||
}
|
||||
}
|
||||
if (!polys.length) {
|
||||
return;
|
||||
}
|
||||
if (options.fillColor) {
|
||||
options.fill = true;
|
||||
}
|
||||
if (polys.length === 1) {
|
||||
return new L.Polygon(polys.concat(inner), options);
|
||||
}
|
||||
return new L.MultiPolygon(polys, options);
|
||||
},
|
||||
|
||||
getLatLngs: function (xml) {
|
||||
var el = xml.getElementsByTagName('coordinates');
|
||||
var coords = [];
|
||||
for (var j = 0; j < el.length; j++) {
|
||||
// text might span many childNodes
|
||||
coords = coords.concat(this._read_coords(el[j]));
|
||||
}
|
||||
return coords;
|
||||
},
|
||||
|
||||
_read_coords: function (el) {
|
||||
var text = '', coords = [], i;
|
||||
for (i = 0; i < el.childNodes.length; i++) {
|
||||
text = text + el.childNodes[i].nodeValue;
|
||||
}
|
||||
text = text.split(/[\s\n]+/);
|
||||
for (i = 0; i < text.length; i++) {
|
||||
var ll = text[i].split(',');
|
||||
if (ll.length < 2) {
|
||||
continue;
|
||||
}
|
||||
coords.push(new L.LatLng(ll[1], ll[0]));
|
||||
}
|
||||
return coords;
|
||||
},
|
||||
|
||||
_read_gxcoords: function (el) {
|
||||
var text = '', coords = [];
|
||||
text = el.firstChild.nodeValue.split(' ');
|
||||
coords.push(new L.LatLng(text[1], text[0]));
|
||||
return coords;
|
||||
},
|
||||
|
||||
parseGroundOverlay: function (xml) {
|
||||
var latlonbox = xml.getElementsByTagName('LatLonBox')[0];
|
||||
var bounds = new L.LatLngBounds(
|
||||
[
|
||||
latlonbox.getElementsByTagName('south')[0].childNodes[0].nodeValue,
|
||||
latlonbox.getElementsByTagName('west')[0].childNodes[0].nodeValue
|
||||
],
|
||||
[
|
||||
latlonbox.getElementsByTagName('north')[0].childNodes[0].nodeValue,
|
||||
latlonbox.getElementsByTagName('east')[0].childNodes[0].nodeValue
|
||||
]
|
||||
);
|
||||
var attributes = {Icon: true, href: true, color: true};
|
||||
function _parse (xml) {
|
||||
var options = {}, ioptions = {};
|
||||
for (var i = 0; i < xml.childNodes.length; i++) {
|
||||
var e = xml.childNodes[i];
|
||||
var key = e.tagName;
|
||||
if (!attributes[key]) { continue; }
|
||||
var value = e.childNodes[0].nodeValue;
|
||||
if (key === 'Icon') {
|
||||
ioptions = _parse(e);
|
||||
if (ioptions.href) { options.href = ioptions.href; }
|
||||
} else if (key === 'href') {
|
||||
options.href = value;
|
||||
} else if (key === 'color') {
|
||||
options.opacity = parseInt(value.substring(0, 2), 16) / 255.0;
|
||||
options.color = '#' + value.substring(6, 8) + value.substring(4, 6) + value.substring(2, 4);
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
var options = {};
|
||||
options = _parse(xml);
|
||||
if (latlonbox.getElementsByTagName('rotation')[0] !== undefined) {
|
||||
var rotation = latlonbox.getElementsByTagName('rotation')[0].childNodes[0].nodeValue;
|
||||
options.rotation = parseFloat(rotation);
|
||||
}
|
||||
return new L.RotatedImageOverlay(options.href, bounds, {opacity: options.opacity, angle: options.rotation});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
L.KMLIcon = L.Icon.extend({
|
||||
options: {
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 16],
|
||||
},
|
||||
_setIconStyles: function (img, name) {
|
||||
L.Icon.prototype._setIconStyles.apply(this, [img, name]);
|
||||
if( img.complete ) {
|
||||
this.applyCustomStyles( img )
|
||||
} else {
|
||||
img.onload = this.applyCustomStyles.bind(this,img)
|
||||
}
|
||||
|
||||
},
|
||||
applyCustomStyles: function(img) {
|
||||
var options = this.options;
|
||||
var width = options.iconSize[0];
|
||||
var height = options.iconSize[1];
|
||||
|
||||
this.options.popupAnchor = [0,(-0.83*height)];
|
||||
if (options.anchorType.x === 'fraction')
|
||||
img.style.marginLeft = (-options.anchorRef.x * width) + 'px';
|
||||
if (options.anchorType.y === 'fraction')
|
||||
img.style.marginTop = ((-(1 - options.anchorRef.y) * height) + 1) + 'px';
|
||||
if (options.anchorType.x === 'pixels')
|
||||
img.style.marginLeft = (-options.anchorRef.x) + 'px';
|
||||
if (options.anchorType.y === 'pixels')
|
||||
img.style.marginTop = (options.anchorRef.y - height + 1) + 'px';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
L.KMLMarker = L.Marker.extend({
|
||||
options: {
|
||||
icon: new L.KMLIcon.Default()
|
||||
}
|
||||
});
|
||||
|
||||
// Inspired by https://github.com/bbecquet/Leaflet.PolylineDecorator/tree/master/src
|
||||
L.RotatedImageOverlay = L.ImageOverlay.extend({
|
||||
options: {
|
||||
angle: 0
|
||||
},
|
||||
_reset: function () {
|
||||
L.ImageOverlay.prototype._reset.call(this);
|
||||
this._rotate();
|
||||
},
|
||||
_animateZoom: function (e) {
|
||||
L.ImageOverlay.prototype._animateZoom.call(this, e);
|
||||
this._rotate();
|
||||
},
|
||||
_rotate: function () {
|
||||
if (L.DomUtil.TRANSFORM) {
|
||||
// use the CSS transform rule if available
|
||||
this._image.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)';
|
||||
} else if (L.Browser.ie) {
|
||||
// fallback for IE6, IE7, IE8
|
||||
var rad = this.options.angle * (Math.PI / 180),
|
||||
costheta = Math.cos(rad),
|
||||
sintheta = Math.sin(rad);
|
||||
this._image.style.filter += ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' +
|
||||
costheta + ', M12=' + (-sintheta) + ', M21=' + sintheta + ', M22=' + costheta + ')';
|
||||
}
|
||||
},
|
||||
getBounds: function () {
|
||||
return this._bounds;
|
||||
}
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
# Leaflet KML layer plugin
|
||||
|
||||

|
||||
|
||||
Demo: https://www.windy.com/uploader
|
||||
|
||||
This plugin was extracted from Pavel Shramov's Leaflet Plugins [repository](https://github.com/shramov/leaflet-plugins) in order to maintain this code more frequently and separate KML layer from other plugins.
|
||||
|
||||
So far we have fixed few issues.
|
||||
|
||||
Probably will work on Leaflet 1+, tested on Leaflet 1.4.
|
||||
|
||||
## How to use
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="http://unpkg.com/leaflet@1.4.0/dist/leaflet.css" />
|
||||
<script src="http://unpkg.com/leaflet@1.4.0/dist/leaflet.js"></script>
|
||||
<script src="./L.KML.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="width: 100vw; height: 100vh" id="map"></div>
|
||||
<script type="text/javascript">
|
||||
// Make basemap
|
||||
const map = new L.Map('map', { center: new L.LatLng(58.4, 43.0), zoom: 11 });
|
||||
const osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
|
||||
|
||||
map.addLayer(osm);
|
||||
|
||||
// Load kml file
|
||||
fetch('assets/example1.kml')
|
||||
.then(res => res.text())
|
||||
.then(kmltext => {
|
||||
// Create new kml overlay
|
||||
const parser = new DOMParser();
|
||||
const kml = parser.parseFromString(kmltext, 'text/xml');
|
||||
const track = new L.KML(kml);
|
||||
map.addLayer(track);
|
||||
|
||||
// Adjust map to show the kml
|
||||
const bounds = track.getBounds();
|
||||
map.fitBounds(bounds);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
## Changelog
|
||||
|
||||
- 1.0.1 - Updated README
|
||||
- 1.0.0 - Initial commit, original version with few fixes
|
||||
|
||||
## Licence
|
||||
|
||||
MIT
|
||||
@@ -1,915 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<kml xmlns="http://www.opengis.net/kml/2.2">
|
||||
<Document>
|
||||
<name>KML Samples</name>
|
||||
<open>1</open>
|
||||
<description>Unleash your creativity with the help of these examples!</description>
|
||||
<Style id="downArrowIcon">
|
||||
<IconStyle>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pal4/icon28.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
<Style id="globeIcon">
|
||||
<IconStyle>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pal3/icon19.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
<LineStyle>
|
||||
<width>2</width>
|
||||
</LineStyle>
|
||||
</Style>
|
||||
<Style id="transPurpleLineGreenPoly">
|
||||
<LineStyle>
|
||||
<color>7fff00ff</color>
|
||||
<width>4</width>
|
||||
</LineStyle>
|
||||
<PolyStyle>
|
||||
<color>7f00ff00</color>
|
||||
</PolyStyle>
|
||||
</Style>
|
||||
<Style id="yellowLineGreenPoly">
|
||||
<LineStyle>
|
||||
<color>7f00ffff</color>
|
||||
<width>4</width>
|
||||
</LineStyle>
|
||||
<PolyStyle>
|
||||
<color>7f00ff00</color>
|
||||
</PolyStyle>
|
||||
</Style>
|
||||
<Style id="thickBlackLine">
|
||||
<LineStyle>
|
||||
<color>87000000</color>
|
||||
<width>10</width>
|
||||
</LineStyle>
|
||||
</Style>
|
||||
<Style id="redLineBluePoly">
|
||||
<LineStyle>
|
||||
<color>ff0000ff</color>
|
||||
</LineStyle>
|
||||
<PolyStyle>
|
||||
<color>ffff0000</color>
|
||||
</PolyStyle>
|
||||
</Style>
|
||||
<Style id="blueLineRedPoly">
|
||||
<LineStyle>
|
||||
<color>ffff0000</color>
|
||||
</LineStyle>
|
||||
<PolyStyle>
|
||||
<color>ff0000ff</color>
|
||||
</PolyStyle>
|
||||
</Style>
|
||||
<Style id="transRedPoly">
|
||||
<LineStyle>
|
||||
<width>1.5</width>
|
||||
</LineStyle>
|
||||
<PolyStyle>
|
||||
<color>7d0000ff</color>
|
||||
</PolyStyle>
|
||||
</Style>
|
||||
<Style id="transBluePoly">
|
||||
<LineStyle>
|
||||
<width>1.5</width>
|
||||
</LineStyle>
|
||||
<PolyStyle>
|
||||
<color>7dff0000</color>
|
||||
</PolyStyle>
|
||||
</Style>
|
||||
<Style id="transGreenPoly">
|
||||
<LineStyle>
|
||||
<width>1.5</width>
|
||||
</LineStyle>
|
||||
<PolyStyle>
|
||||
<color>7d00ff00</color>
|
||||
</PolyStyle>
|
||||
</Style>
|
||||
<Style id="transYellowPoly">
|
||||
<LineStyle>
|
||||
<width>1.5</width>
|
||||
</LineStyle>
|
||||
<PolyStyle>
|
||||
<color>7d00ffff</color>
|
||||
</PolyStyle>
|
||||
</Style>
|
||||
<Style id="noDrivingDirections">
|
||||
<BalloonStyle>
|
||||
<text><![CDATA[
|
||||
<b>$[name]</b>
|
||||
<br /><br />
|
||||
$[description]
|
||||
]]></text>
|
||||
</BalloonStyle>
|
||||
</Style>
|
||||
<Folder>
|
||||
<name>Placemarks</name>
|
||||
<description>These are just some of the different kinds of placemarks with
|
||||
which you can mark your favorite places</description>
|
||||
<LookAt>
|
||||
<longitude>-122.0839597145766</longitude>
|
||||
<latitude>37.42222904525232</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>-148.4122922628044</heading>
|
||||
<tilt>40.5575073395506</tilt>
|
||||
<range>500.6566641072245</range>
|
||||
</LookAt>
|
||||
<Placemark>
|
||||
<name>Simple placemark</name>
|
||||
<description>Attached to the ground. Intelligently places itself at the
|
||||
height of the underlying terrain.</description>
|
||||
<Point>
|
||||
<coordinates>-122.0822035425683,37.42228990140251,0</coordinates>
|
||||
</Point>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Floating placemark</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Floats a defined distance above the ground.</description>
|
||||
<LookAt>
|
||||
<longitude>-122.0839597145766</longitude>
|
||||
<latitude>37.42222904525232</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>-148.4122922628044</heading>
|
||||
<tilt>40.5575073395506</tilt>
|
||||
<range>500.6566641072245</range>
|
||||
</LookAt>
|
||||
<styleUrl>#downArrowIcon</styleUrl>
|
||||
<Point>
|
||||
<altitudeMode>relativeToGround</altitudeMode>
|
||||
<coordinates>-122.084075,37.4220033612141,50</coordinates>
|
||||
</Point>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Extruded placemark</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Tethered to the ground by a customizable
|
||||
"tail"</description>
|
||||
<LookAt>
|
||||
<longitude>-122.0845787421525</longitude>
|
||||
<latitude>37.42215078737763</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>-148.4126684946234</heading>
|
||||
<tilt>40.55750733918048</tilt>
|
||||
<range>365.2646606980322</range>
|
||||
</LookAt>
|
||||
<styleUrl>#globeIcon</styleUrl>
|
||||
<Point>
|
||||
<extrude>1</extrude>
|
||||
<altitudeMode>relativeToGround</altitudeMode>
|
||||
<coordinates>-122.0857667006183,37.42156927867553,50</coordinates>
|
||||
</Point>
|
||||
</Placemark>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Styles and Markup</name>
|
||||
<visibility>0</visibility>
|
||||
<description>With KML it is easy to create rich, descriptive markup to
|
||||
annotate and enrich your placemarks</description>
|
||||
<LookAt>
|
||||
<longitude>-122.0845787422371</longitude>
|
||||
<latitude>37.42215078726837</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>-148.4126777488172</heading>
|
||||
<tilt>40.55750733930874</tilt>
|
||||
<range>365.2646826292919</range>
|
||||
</LookAt>
|
||||
<styleUrl>#noDrivingDirections</styleUrl>
|
||||
<Document>
|
||||
<name>Highlighted Icon</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Place your mouse over the icon to see it display the new
|
||||
icon</description>
|
||||
<LookAt>
|
||||
<longitude>-122.0856552124024</longitude>
|
||||
<latitude>37.4224281311035</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>0</heading>
|
||||
<tilt>0</tilt>
|
||||
<range>265.8520424250024</range>
|
||||
</LookAt>
|
||||
<Style id="highlightPlacemark">
|
||||
<IconStyle>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/paddle/red-stars.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
<Style id="normalPlacemark">
|
||||
<IconStyle>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/paddle/wht-blank.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
<StyleMap id="exampleStyleMap">
|
||||
<Pair>
|
||||
<key>normal</key>
|
||||
<styleUrl>#normalPlacemark</styleUrl>
|
||||
</Pair>
|
||||
<Pair>
|
||||
<key>highlight</key>
|
||||
<styleUrl>#highlightPlacemark</styleUrl>
|
||||
</Pair>
|
||||
</StyleMap>
|
||||
<Placemark>
|
||||
<name>Roll over this icon</name>
|
||||
<visibility>0</visibility>
|
||||
<styleUrl>#exampleStyleMap</styleUrl>
|
||||
<Point>
|
||||
<coordinates>-122.0856545755255,37.42243077405461,0</coordinates>
|
||||
</Point>
|
||||
</Placemark>
|
||||
</Document>
|
||||
<Placemark>
|
||||
<name>Descriptive HTML</name>
|
||||
<visibility>0</visibility>
|
||||
<description><![CDATA[Click on the blue link!<br><br>
|
||||
Placemark descriptions can be enriched by using many standard HTML tags.<br>
|
||||
For example:
|
||||
<hr>
|
||||
Styles:<br>
|
||||
<i>Italics</i>,
|
||||
<b>Bold</b>,
|
||||
<u>Underlined</u>,
|
||||
<s>Strike Out</s>,
|
||||
subscript<sub>subscript</sub>,
|
||||
superscript<sup>superscript</sup>,
|
||||
<big>Big</big>,
|
||||
<small>Small</small>,
|
||||
<tt>Typewriter</tt>,
|
||||
<em>Emphasized</em>,
|
||||
<strong>Strong</strong>,
|
||||
<code>Code</code>
|
||||
<hr>
|
||||
Fonts:<br>
|
||||
<font color="red">red by name</font>,
|
||||
<font color="#408010">leaf green by hexadecimal RGB</font>
|
||||
<br>
|
||||
<font size=1>size 1</font>,
|
||||
<font size=2>size 2</font>,
|
||||
<font size=3>size 3</font>,
|
||||
<font size=4>size 4</font>,
|
||||
<font size=5>size 5</font>,
|
||||
<font size=6>size 6</font>,
|
||||
<font size=7>size 7</font>
|
||||
<br>
|
||||
<font face=times>Times</font>,
|
||||
<font face=verdana>Verdana</font>,
|
||||
<font face=arial>Arial</font><br>
|
||||
<hr>
|
||||
Links:
|
||||
<br>
|
||||
<a href="http://earth.google.com/">Google Earth!</a>
|
||||
<br>
|
||||
or: Check out our website at www.google.com
|
||||
<hr>
|
||||
Alignment:<br>
|
||||
<p align=left>left</p>
|
||||
<p align=center>center</p>
|
||||
<p align=right>right</p>
|
||||
<hr>
|
||||
Ordered Lists:<br>
|
||||
<ol><li>First</li><li>Second</li><li>Third</li></ol>
|
||||
<ol type="a"><li>First</li><li>Second</li><li>Third</li></ol>
|
||||
<ol type="A"><li>First</li><li>Second</li><li>Third</li></ol>
|
||||
<hr>
|
||||
Unordered Lists:<br>
|
||||
<ul><li>A</li><li>B</li><li>C</li></ul>
|
||||
<ul type="circle"><li>A</li><li>B</li><li>C</li></ul>
|
||||
<ul type="square"><li>A</li><li>B</li><li>C</li></ul>
|
||||
<hr>
|
||||
Definitions:<br>
|
||||
<dl>
|
||||
<dt>Google:</dt><dd>The best thing since sliced bread</dd>
|
||||
</dl>
|
||||
<hr>
|
||||
Centered:<br><center>
|
||||
Time present and time past<br>
|
||||
Are both perhaps present in time future,<br>
|
||||
And time future contained in time past.<br>
|
||||
If all time is eternally present<br>
|
||||
All time is unredeemable.<br>
|
||||
</center>
|
||||
<hr>
|
||||
Block Quote:
|
||||
<br>
|
||||
<blockquote>
|
||||
We shall not cease from exploration<br>
|
||||
And the end of all our exploring<br>
|
||||
Will be to arrive where we started<br>
|
||||
And know the place for the first time.<br>
|
||||
<i>-- T.S. Eliot</i>
|
||||
</blockquote>
|
||||
<br>
|
||||
<hr>
|
||||
Headings:<br>
|
||||
<h1>Header 1</h1>
|
||||
<h2>Header 2</h2>
|
||||
<h3>Header 3</h3>
|
||||
<h3>Header 4</h4>
|
||||
<h3>Header 5</h5>
|
||||
<hr>
|
||||
Images:<br>
|
||||
<i>Remote image</i><br>
|
||||
<img src="//developers.google.com/kml/documentation/images/googleSample.png"><br>
|
||||
<i>Scaled image</i><br>
|
||||
<img src="//developers.google.com/kml/documentation/images/googleSample.png" width=100><br>
|
||||
<hr>
|
||||
Simple Tables:<br>
|
||||
<table border="1" padding="1">
|
||||
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td></tr>
|
||||
<tr><td>a</td><td>b</td><td>c</td><td>d</td><td>e</td></tr>
|
||||
</table>
|
||||
<br>
|
||||
[Did you notice that double-clicking on the placemark doesn't cause the viewer to take you anywhere? This is because it is possible to directly author a "placeless placemark". If you look at the code for this example, you will see that it has neither a point coordinate nor a LookAt element.]]]></description>
|
||||
</Placemark>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Ground Overlays</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Examples of ground overlays</description>
|
||||
<GroundOverlay>
|
||||
<name>Large-scale overlay on terrain</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Overlay shows Mount Etna erupting on July 13th, 2001.</description>
|
||||
<LookAt>
|
||||
<longitude>15.02468937557116</longitude>
|
||||
<latitude>37.67395167941667</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>-16.5581842842829</heading>
|
||||
<tilt>58.31228652890705</tilt>
|
||||
<range>30350.36838438907</range>
|
||||
</LookAt>
|
||||
<Icon>
|
||||
<href>http://developers.google.com/kml/documentation/images/etna.jpg</href>
|
||||
</Icon>
|
||||
<LatLonBox>
|
||||
<north>37.91904192681665</north>
|
||||
<south>37.46543388598137</south>
|
||||
<east>15.35832653742206</east>
|
||||
<west>14.60128369746704</west>
|
||||
<rotation>-0.1556640799496235</rotation>
|
||||
</LatLonBox>
|
||||
</GroundOverlay>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Screen Overlays</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Screen overlays have to be authored directly in KML. These
|
||||
examples illustrate absolute and dynamic positioning in screen space.</description>
|
||||
<ScreenOverlay>
|
||||
<name>Simple crosshairs</name>
|
||||
<visibility>0</visibility>
|
||||
<description>This screen overlay uses fractional positioning to put the
|
||||
image in the exact center of the screen</description>
|
||||
<Icon>
|
||||
<href>http://developers.google.com/kml/documentation/images/crosshairs.png</href>
|
||||
</Icon>
|
||||
<overlayXY x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
|
||||
<screenXY x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
|
||||
<rotationXY x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
|
||||
<size x="0" y="0" xunits="pixels" yunits="pixels"/>
|
||||
</ScreenOverlay>
|
||||
<ScreenOverlay>
|
||||
<name>Absolute Positioning: Top left</name>
|
||||
<visibility>0</visibility>
|
||||
<Icon>
|
||||
<href>http://developers.google.com/kml/documentation/images/top_left.jpg</href>
|
||||
</Icon>
|
||||
<overlayXY x="0" y="1" xunits="fraction" yunits="fraction"/>
|
||||
<screenXY x="0" y="1" xunits="fraction" yunits="fraction"/>
|
||||
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
|
||||
<size x="0" y="0" xunits="fraction" yunits="fraction"/>
|
||||
</ScreenOverlay>
|
||||
<ScreenOverlay>
|
||||
<name>Absolute Positioning: Top right</name>
|
||||
<visibility>0</visibility>
|
||||
<Icon>
|
||||
<href>http://developers.google.com/kml/documentation/images/top_right.jpg</href>
|
||||
</Icon>
|
||||
<overlayXY x="1" y="1" xunits="fraction" yunits="fraction"/>
|
||||
<screenXY x="1" y="1" xunits="fraction" yunits="fraction"/>
|
||||
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
|
||||
<size x="0" y="0" xunits="fraction" yunits="fraction"/>
|
||||
</ScreenOverlay>
|
||||
<ScreenOverlay>
|
||||
<name>Absolute Positioning: Bottom left</name>
|
||||
<visibility>0</visibility>
|
||||
<Icon>
|
||||
<href>http://developers.google.com/kml/documentation/images/bottom_left.jpg</href>
|
||||
</Icon>
|
||||
<overlayXY x="0" y="-1" xunits="fraction" yunits="fraction"/>
|
||||
<screenXY x="0" y="0" xunits="fraction" yunits="fraction"/>
|
||||
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
|
||||
<size x="0" y="0" xunits="fraction" yunits="fraction"/>
|
||||
</ScreenOverlay>
|
||||
<ScreenOverlay>
|
||||
<name>Absolute Positioning: Bottom right</name>
|
||||
<visibility>0</visibility>
|
||||
<Icon>
|
||||
<href>http://developers.google.com/kml/documentation/images/bottom_right.jpg</href>
|
||||
</Icon>
|
||||
<overlayXY x="1" y="-1" xunits="fraction" yunits="fraction"/>
|
||||
<screenXY x="1" y="0" xunits="fraction" yunits="fraction"/>
|
||||
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
|
||||
<size x="0" y="0" xunits="fraction" yunits="fraction"/>
|
||||
</ScreenOverlay>
|
||||
<ScreenOverlay>
|
||||
<name>Dynamic Positioning: Top of screen</name>
|
||||
<visibility>0</visibility>
|
||||
<Icon>
|
||||
<href>http://developers.google.com/kml/documentation/images/dynamic_screenoverlay.jpg</href>
|
||||
</Icon>
|
||||
<overlayXY x="0" y="1" xunits="fraction" yunits="fraction"/>
|
||||
<screenXY x="0" y="1" xunits="fraction" yunits="fraction"/>
|
||||
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
|
||||
<size x="1" y="0.2" xunits="fraction" yunits="fraction"/>
|
||||
</ScreenOverlay>
|
||||
<ScreenOverlay>
|
||||
<name>Dynamic Positioning: Right of screen</name>
|
||||
<visibility>0</visibility>
|
||||
<Icon>
|
||||
<href>http://developers.google.com/kml/documentation/images/dynamic_right.jpg</href>
|
||||
</Icon>
|
||||
<overlayXY x="1" y="1" xunits="fraction" yunits="fraction"/>
|
||||
<screenXY x="1" y="1" xunits="fraction" yunits="fraction"/>
|
||||
<rotationXY x="0" y="0" xunits="fraction" yunits="fraction"/>
|
||||
<size x="0" y="1" xunits="fraction" yunits="fraction"/>
|
||||
</ScreenOverlay>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Paths</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Examples of paths. Note that the tessellate tag is by default
|
||||
set to 0. If you want to create tessellated lines, they must be authored
|
||||
(or edited) directly in KML.</description>
|
||||
<Placemark>
|
||||
<name>Tessellated</name>
|
||||
<visibility>0</visibility>
|
||||
<description><![CDATA[If the <tessellate> tag has a value of 1, the line will contour to the underlying terrain]]></description>
|
||||
<LookAt>
|
||||
<longitude>-112.0822680013139</longitude>
|
||||
<latitude>36.09825589333556</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>103.8120432044965</heading>
|
||||
<tilt>62.04855796276328</tilt>
|
||||
<range>2889.145007690472</range>
|
||||
</LookAt>
|
||||
<LineString>
|
||||
<tessellate>1</tessellate>
|
||||
<coordinates> -112.0814237830345,36.10677870477137,0
|
||||
-112.0870267752693,36.0905099328766,0 </coordinates>
|
||||
</LineString>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Untessellated</name>
|
||||
<visibility>0</visibility>
|
||||
<description><![CDATA[If the <tessellate> tag has a value of 0, the line follow a simple straight-line path from point to point]]></description>
|
||||
<LookAt>
|
||||
<longitude>-112.0822680013139</longitude>
|
||||
<latitude>36.09825589333556</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>103.8120432044965</heading>
|
||||
<tilt>62.04855796276328</tilt>
|
||||
<range>2889.145007690472</range>
|
||||
</LookAt>
|
||||
<LineString>
|
||||
<tessellate>0</tessellate>
|
||||
<coordinates> -112.080622229595,36.10673460007995,0
|
||||
-112.085242575315,36.09049598612422,0 </coordinates>
|
||||
</LineString>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Absolute</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Transparent purple line</description>
|
||||
<LookAt>
|
||||
<longitude>-112.2719329043177</longitude>
|
||||
<latitude>36.08890633450894</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>-106.8161545998597</heading>
|
||||
<tilt>44.60763714063257</tilt>
|
||||
<range>2569.386744398339</range>
|
||||
</LookAt>
|
||||
<styleUrl>#transPurpleLineGreenPoly</styleUrl>
|
||||
<LineString>
|
||||
<tessellate>1</tessellate>
|
||||
<altitudeMode>absolute</altitudeMode>
|
||||
<coordinates> -112.265654928602,36.09447672602546,2357
|
||||
-112.2660384528238,36.09342608838671,2357
|
||||
-112.2668139013453,36.09251058776881,2357
|
||||
-112.2677826834445,36.09189827357996,2357
|
||||
-112.2688557510952,36.0913137941187,2357
|
||||
-112.2694810717219,36.0903677207521,2357
|
||||
-112.2695268555611,36.08932171487285,2357
|
||||
-112.2690144567276,36.08850916060472,2357
|
||||
-112.2681528815339,36.08753813597956,2357
|
||||
-112.2670588176031,36.08682685262568,2357
|
||||
-112.2657374587321,36.08646312301303,2357 </coordinates>
|
||||
</LineString>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Absolute Extruded</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Transparent green wall with yellow outlines</description>
|
||||
<LookAt>
|
||||
<longitude>-112.2643334742529</longitude>
|
||||
<latitude>36.08563154742419</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>-125.7518698668815</heading>
|
||||
<tilt>44.61038665812578</tilt>
|
||||
<range>4451.842204068102</range>
|
||||
</LookAt>
|
||||
<styleUrl>#yellowLineGreenPoly</styleUrl>
|
||||
<LineString>
|
||||
<extrude>1</extrude>
|
||||
<tessellate>1</tessellate>
|
||||
<altitudeMode>absolute</altitudeMode>
|
||||
<coordinates> -112.2550785337791,36.07954952145647,2357
|
||||
-112.2549277039738,36.08117083492122,2357
|
||||
-112.2552505069063,36.08260761307279,2357
|
||||
-112.2564540158376,36.08395660588506,2357
|
||||
-112.2580238976449,36.08511401044813,2357
|
||||
-112.2595218489022,36.08584355239394,2357
|
||||
-112.2608216347552,36.08612634548589,2357
|
||||
-112.262073428656,36.08626019085147,2357
|
||||
-112.2633204928495,36.08621519860091,2357
|
||||
-112.2644963846444,36.08627897945274,2357
|
||||
-112.2656969554589,36.08649599090644,2357 </coordinates>
|
||||
</LineString>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Relative</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Black line (10 pixels wide), height tracks terrain</description>
|
||||
<LookAt>
|
||||
<longitude>-112.2580438551384</longitude>
|
||||
<latitude>36.1072674824385</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>4.947421249553717</heading>
|
||||
<tilt>44.61324882043339</tilt>
|
||||
<range>2927.61105910266</range>
|
||||
</LookAt>
|
||||
<styleUrl>#thickBlackLine</styleUrl>
|
||||
<LineString>
|
||||
<tessellate>1</tessellate>
|
||||
<altitudeMode>relativeToGround</altitudeMode>
|
||||
<coordinates> -112.2532845153347,36.09886943729116,645
|
||||
-112.2540466121145,36.09919570465255,645
|
||||
-112.254734666947,36.09984998366178,645
|
||||
-112.255493345654,36.10051310621746,645
|
||||
-112.2563157098468,36.10108441943419,645
|
||||
-112.2568033076439,36.10159722088088,645
|
||||
-112.257494011321,36.10204323542867,645
|
||||
-112.2584106072308,36.10229131995655,645
|
||||
-112.2596588987972,36.10240001286358,645
|
||||
-112.2610581199487,36.10213176873407,645
|
||||
-112.2626285262793,36.10157011437219,645 </coordinates>
|
||||
</LineString>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Relative Extruded</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Opaque blue walls with red outline, height tracks terrain</description>
|
||||
<LookAt>
|
||||
<longitude>-112.2683594333433</longitude>
|
||||
<latitude>36.09884362144909</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>-72.24271551768405</heading>
|
||||
<tilt>44.60855445139561</tilt>
|
||||
<range>2184.193522571467</range>
|
||||
</LookAt>
|
||||
<styleUrl>#redLineBluePoly</styleUrl>
|
||||
<LineString>
|
||||
<extrude>1</extrude>
|
||||
<tessellate>1</tessellate>
|
||||
<altitudeMode>relativeToGround</altitudeMode>
|
||||
<coordinates> -112.2656634181359,36.09445214722695,630
|
||||
-112.2652238941097,36.09520916122063,630
|
||||
-112.2645079986395,36.09580763864907,630
|
||||
-112.2638827428817,36.09628572284063,630
|
||||
-112.2635746835406,36.09679275951239,630
|
||||
-112.2635711822407,36.09740038871899,630
|
||||
-112.2640296531825,36.09804913435539,630
|
||||
-112.264327720538,36.09880337400301,630
|
||||
-112.2642436562271,36.09963644790288,630
|
||||
-112.2639148687042,36.10055381117246,630
|
||||
-112.2626894973474,36.10149062823369,630 </coordinates>
|
||||
</LineString>
|
||||
</Placemark>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Polygons</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Examples of polygon shapes</description>
|
||||
<Folder>
|
||||
<name>Google Campus</name>
|
||||
<visibility>0</visibility>
|
||||
<description>A collection showing how easy it is to create 3-dimensional
|
||||
buildings</description>
|
||||
<LookAt>
|
||||
<longitude>-122.084120030116</longitude>
|
||||
<latitude>37.42174011925477</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>-34.82469740081282</heading>
|
||||
<tilt>53.454348562403</tilt>
|
||||
<range>276.7870053764046</range>
|
||||
</LookAt>
|
||||
<Placemark>
|
||||
<name>Building 40</name>
|
||||
<visibility>0</visibility>
|
||||
<styleUrl>#transRedPoly</styleUrl>
|
||||
<Polygon>
|
||||
<extrude>1</extrude>
|
||||
<altitudeMode>relativeToGround</altitudeMode>
|
||||
<outerBoundaryIs>
|
||||
<LinearRing>
|
||||
<coordinates> -122.0848938459612,37.42257124044786,17
|
||||
-122.0849580979198,37.42211922626856,17
|
||||
-122.0847469573047,37.42207183952619,17
|
||||
-122.0845725380962,37.42209006729676,17
|
||||
-122.0845954886723,37.42215932700895,17
|
||||
-122.0838521118269,37.42227278564371,17
|
||||
-122.083792243335,37.42203539112084,17
|
||||
-122.0835076656616,37.42209006957106,17
|
||||
-122.0834709464152,37.42200987395161,17
|
||||
-122.0831221085748,37.4221046494946,17
|
||||
-122.0829247374572,37.42226503990386,17
|
||||
-122.0829339169385,37.42231242843094,17
|
||||
-122.0833837359737,37.42225046087618,17
|
||||
-122.0833607854248,37.42234159228745,17
|
||||
-122.0834204551642,37.42237075460644,17
|
||||
-122.083659133885,37.42251292011001,17
|
||||
-122.0839758438952,37.42265873093781,17
|
||||
-122.0842374743331,37.42265143972521,17
|
||||
-122.0845036949503,37.4226514386435,17
|
||||
-122.0848020460801,37.42261133916315,17
|
||||
-122.0847882750515,37.42256395055121,17
|
||||
-122.0848938459612,37.42257124044786,17 </coordinates>
|
||||
</LinearRing>
|
||||
</outerBoundaryIs>
|
||||
</Polygon>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Building 41</name>
|
||||
<visibility>0</visibility>
|
||||
<styleUrl>#transBluePoly</styleUrl>
|
||||
<Polygon>
|
||||
<extrude>1</extrude>
|
||||
<altitudeMode>relativeToGround</altitudeMode>
|
||||
<outerBoundaryIs>
|
||||
<LinearRing>
|
||||
<coordinates> -122.0857412771483,37.42227033155257,17
|
||||
-122.0858169768481,37.42231408832346,17
|
||||
-122.085852582875,37.42230337469744,17
|
||||
-122.0858799945639,37.42225686138789,17
|
||||
-122.0858860101409,37.4222311076138,17
|
||||
-122.0858069157288,37.42220250173855,17
|
||||
-122.0858379542653,37.42214027058678,17
|
||||
-122.0856732640519,37.42208690214408,17
|
||||
-122.0856022926407,37.42214885429042,17
|
||||
-122.0855902778436,37.422128290487,17
|
||||
-122.0855841672237,37.42208171967246,17
|
||||
-122.0854852065741,37.42210455874995,17
|
||||
-122.0855067264352,37.42214267949824,17
|
||||
-122.0854430712915,37.42212783846172,17
|
||||
-122.0850990714904,37.42251282407603,17
|
||||
-122.0856769818632,37.42281815323651,17
|
||||
-122.0860162273783,37.42244918858722,17
|
||||
-122.0857260327004,37.42229239604253,17
|
||||
-122.0857412771483,37.42227033155257,17 </coordinates>
|
||||
</LinearRing>
|
||||
</outerBoundaryIs>
|
||||
</Polygon>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Building 42</name>
|
||||
<visibility>0</visibility>
|
||||
<styleUrl>#transGreenPoly</styleUrl>
|
||||
<Polygon>
|
||||
<extrude>1</extrude>
|
||||
<altitudeMode>relativeToGround</altitudeMode>
|
||||
<outerBoundaryIs>
|
||||
<LinearRing>
|
||||
<coordinates> -122.0857862287242,37.42136208886969,25
|
||||
-122.0857312990603,37.42136935989481,25
|
||||
-122.0857312992918,37.42140934910903,25
|
||||
-122.0856077073679,37.42138390166565,25
|
||||
-122.0855802426516,37.42137299550869,25
|
||||
-122.0852186221971,37.42137299504316,25
|
||||
-122.0852277765639,37.42161656508265,25
|
||||
-122.0852598189347,37.42160565894403,25
|
||||
-122.0852598185499,37.42168200156,25
|
||||
-122.0852369311478,37.42170017860346,25
|
||||
-122.0852643957828,37.42176197982575,25
|
||||
-122.0853239032746,37.42176198013907,25
|
||||
-122.0853559454324,37.421852864452,25
|
||||
-122.0854108752463,37.42188921823734,25
|
||||
-122.0854795379357,37.42189285337048,25
|
||||
-122.0855436229819,37.42188921797546,25
|
||||
-122.0856260178042,37.42186013499926,25
|
||||
-122.085937287963,37.42186013453605,25
|
||||
-122.0859428718666,37.42160898590042,25
|
||||
-122.0859655469861,37.42157992759144,25
|
||||
-122.0858640462341,37.42147115002957,25
|
||||
-122.0858548911215,37.42140571326184,25
|
||||
-122.0858091162768,37.4214057134039,25
|
||||
-122.0857862287242,37.42136208886969,25 </coordinates>
|
||||
</LinearRing>
|
||||
</outerBoundaryIs>
|
||||
</Polygon>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Building 43</name>
|
||||
<visibility>0</visibility>
|
||||
<styleUrl>#transYellowPoly</styleUrl>
|
||||
<Polygon>
|
||||
<extrude>1</extrude>
|
||||
<altitudeMode>relativeToGround</altitudeMode>
|
||||
<outerBoundaryIs>
|
||||
<LinearRing>
|
||||
<coordinates> -122.0844371128284,37.42177253003091,19
|
||||
-122.0845118855746,37.42191111542896,19
|
||||
-122.0850470999805,37.42178755121535,19
|
||||
-122.0850719913391,37.42143663023161,19
|
||||
-122.084916406232,37.42137237822116,19
|
||||
-122.0842193868167,37.42137237801626,19
|
||||
-122.08421938659,37.42147617161496,19
|
||||
-122.0838086419991,37.4214613409357,19
|
||||
-122.0837899728564,37.42131306410796,19
|
||||
-122.0832796534698,37.42129328840593,19
|
||||
-122.0832609819207,37.42139213944298,19
|
||||
-122.0829373621737,37.42137236399876,19
|
||||
-122.0829062425667,37.42151569778871,19
|
||||
-122.0828502269665,37.42176282576465,19
|
||||
-122.0829435788635,37.42176776969635,19
|
||||
-122.083217411188,37.42179248552686,19
|
||||
-122.0835970430103,37.4217480074456,19
|
||||
-122.0839455556771,37.42169364237603,19
|
||||
-122.0840077894637,37.42176283815853,19
|
||||
-122.084113587521,37.42174801104392,19
|
||||
-122.0840762473784,37.42171341292375,19
|
||||
-122.0841447047739,37.42167881534569,19
|
||||
-122.084144704223,37.42181720660197,19
|
||||
-122.0842503333074,37.4218170700446,19
|
||||
-122.0844371128284,37.42177253003091,19 </coordinates>
|
||||
</LinearRing>
|
||||
</outerBoundaryIs>
|
||||
</Polygon>
|
||||
</Placemark>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Extruded Polygon</name>
|
||||
<description>A simple way to model a building</description>
|
||||
<Placemark>
|
||||
<name>The Pentagon</name>
|
||||
<LookAt>
|
||||
<longitude>-77.05580139178142</longitude>
|
||||
<latitude>38.870832443487</latitude>
|
||||
<heading>59.88865561738225</heading>
|
||||
<tilt>48.09646074797388</tilt>
|
||||
<range>742.0552506670548</range>
|
||||
</LookAt>
|
||||
<Polygon>
|
||||
<extrude>1</extrude>
|
||||
<altitudeMode>relativeToGround</altitudeMode>
|
||||
<outerBoundaryIs>
|
||||
<LinearRing>
|
||||
<coordinates> -77.05788457660967,38.87253259892824,100
|
||||
-77.05465973756702,38.87291016281703,100
|
||||
-77.05315536854791,38.87053267794386,100
|
||||
-77.05552622493516,38.868757801256,100
|
||||
-77.05844056290393,38.86996206506943,100
|
||||
-77.05788457660967,38.87253259892824,100 </coordinates>
|
||||
</LinearRing>
|
||||
</outerBoundaryIs>
|
||||
<innerBoundaryIs>
|
||||
<LinearRing>
|
||||
<coordinates> -77.05668055019126,38.87154239798456,100
|
||||
-77.05542625960818,38.87167890344077,100
|
||||
-77.05485125901024,38.87076535397792,100
|
||||
-77.05577677433152,38.87008686581446,100
|
||||
-77.05691162017543,38.87054446963351,100
|
||||
-77.05668055019126,38.87154239798456,100 </coordinates>
|
||||
</LinearRing>
|
||||
</innerBoundaryIs>
|
||||
</Polygon>
|
||||
</Placemark>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Absolute and Relative</name>
|
||||
<visibility>0</visibility>
|
||||
<description>Four structures whose roofs meet exactly. Turn on/off
|
||||
terrain to see the difference between relative and absolute
|
||||
positioning.</description>
|
||||
<LookAt>
|
||||
<longitude>-112.3348969157552</longitude>
|
||||
<latitude>36.14845533214919</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>-86.91235037566909</heading>
|
||||
<tilt>49.30695423894192</tilt>
|
||||
<range>990.6761201087104</range>
|
||||
</LookAt>
|
||||
<Placemark>
|
||||
<name>Absolute</name>
|
||||
<visibility>0</visibility>
|
||||
<styleUrl>#transBluePoly</styleUrl>
|
||||
<Polygon>
|
||||
<tessellate>1</tessellate>
|
||||
<altitudeMode>absolute</altitudeMode>
|
||||
<outerBoundaryIs>
|
||||
<LinearRing>
|
||||
<coordinates> -112.3372510731295,36.14888505105317,1784
|
||||
-112.3356128688403,36.14781540589019,1784
|
||||
-112.3368169371048,36.14658677734382,1784
|
||||
-112.3384408457543,36.14762778914076,1784
|
||||
-112.3372510731295,36.14888505105317,1784 </coordinates>
|
||||
</LinearRing>
|
||||
</outerBoundaryIs>
|
||||
</Polygon>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Absolute Extruded</name>
|
||||
<visibility>0</visibility>
|
||||
<styleUrl>#transRedPoly</styleUrl>
|
||||
<Polygon>
|
||||
<extrude>1</extrude>
|
||||
<tessellate>1</tessellate>
|
||||
<altitudeMode>absolute</altitudeMode>
|
||||
<outerBoundaryIs>
|
||||
<LinearRing>
|
||||
<coordinates> -112.3396586818843,36.14637618647505,1784
|
||||
-112.3380597654315,36.14531751871353,1784
|
||||
-112.3368254237788,36.14659596244607,1784
|
||||
-112.3384555043203,36.14762621763982,1784
|
||||
-112.3396586818843,36.14637618647505,1784 </coordinates>
|
||||
</LinearRing>
|
||||
</outerBoundaryIs>
|
||||
</Polygon>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Relative</name>
|
||||
<visibility>0</visibility>
|
||||
<LookAt>
|
||||
<longitude>-112.3350152490417</longitude>
|
||||
<latitude>36.14943123077423</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>-118.9214100848499</heading>
|
||||
<tilt>37.92486261093203</tilt>
|
||||
<range>345.5169113679813</range>
|
||||
</LookAt>
|
||||
<styleUrl>#transGreenPoly</styleUrl>
|
||||
<Polygon>
|
||||
<tessellate>1</tessellate>
|
||||
<altitudeMode>relativeToGround</altitudeMode>
|
||||
<outerBoundaryIs>
|
||||
<LinearRing>
|
||||
<coordinates> -112.3349463145932,36.14988705767721,100
|
||||
-112.3354019540677,36.14941108398372,100
|
||||
-112.3344428289146,36.14878490381308,100
|
||||
-112.3331289492913,36.14780840132443,100
|
||||
-112.3317019516947,36.14680755678357,100
|
||||
-112.331131440106,36.1474173426228,100
|
||||
-112.332616324338,36.14845453364654,100
|
||||
-112.3339876620524,36.14926570522069,100
|
||||
-112.3349463145932,36.14988705767721,100 </coordinates>
|
||||
</LinearRing>
|
||||
</outerBoundaryIs>
|
||||
</Polygon>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<name>Relative Extruded</name>
|
||||
<visibility>0</visibility>
|
||||
<LookAt>
|
||||
<longitude>-112.3351587892382</longitude>
|
||||
<latitude>36.14979247129029</latitude>
|
||||
<altitude>0</altitude>
|
||||
<heading>-55.42811560891606</heading>
|
||||
<tilt>56.10280503739589</tilt>
|
||||
<range>401.0997279712519</range>
|
||||
</LookAt>
|
||||
<styleUrl>#transYellowPoly</styleUrl>
|
||||
<Polygon>
|
||||
<extrude>1</extrude>
|
||||
<tessellate>1</tessellate>
|
||||
<altitudeMode>relativeToGround</altitudeMode>
|
||||
<outerBoundaryIs>
|
||||
<LinearRing>
|
||||
<coordinates> -112.3348783983763,36.1514008468736,100
|
||||
-112.3372535345629,36.14888517553886,100
|
||||
-112.3356068927954,36.14781612679284,100
|
||||
-112.3350034807972,36.14846469024177,100
|
||||
-112.3358353861232,36.1489624162954,100
|
||||
-112.3345888301373,36.15026229372507,100
|
||||
-112.3337937856278,36.14978096026463,100
|
||||
-112.3331798208424,36.1504472788618,100
|
||||
-112.3348783983763,36.1514008468736,100 </coordinates>
|
||||
</LinearRing>
|
||||
</outerBoundaryIs>
|
||||
</Polygon>
|
||||
</Placemark>
|
||||
</Folder>
|
||||
</Folder>
|
||||
</Document>
|
||||
</kml>
|
||||
@@ -1,446 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<kml xmlns="http://www.opengis.net/kml/2.2">
|
||||
<Document>
|
||||
<name>Red Bull X-Alps 2019 Route</name>
|
||||
<snippet>https://www.redbullxalps.com/ Created by twpayne@gmail.com</snippet>
|
||||
<open>1</open>
|
||||
<Folder>
|
||||
<name>Route</name>
|
||||
<Placemark>
|
||||
<LineString>
|
||||
<coordinates>13.0484,47.79885 13.110917,47.804133 13.305787,47.332295 12.33277,47.784362 11.9549,46.737598 10.98526,47.4211 10.879767,47.401283 9.851879,46.815225 8.424457,46.770918 8.005393,46.577621 5.887857,45.306816 7.090381,44.667312 6.422229,44.120985 7.410751,43.755956 7.454787,43.75875</coordinates>
|
||||
<tessellate>1</tessellate>
|
||||
</LineString>
|
||||
<Style>
|
||||
<LineStyle>
|
||||
<color>c0009090</color>
|
||||
<width>4</width>
|
||||
</LineStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Turnpoints</name>
|
||||
<Folder>
|
||||
<name>Salzburg</name>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>13.0484,47.79885</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/go.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Gaisberg</name>
|
||||
<snippet>signboard</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>13.110917,47.804133</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/1.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Wagrain-Kleinarl</name>
|
||||
<snippet>signboard</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>13.305787,47.332295</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/2.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Aschau-Chiemsee</name>
|
||||
<snippet>signboard</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>12.33277,47.784362</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/3.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Kronplatz</name>
|
||||
<snippet>signboard</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>11.9549,46.737598</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/4.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Zugspitz</name>
|
||||
<snippet>pass N</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>10.98526,47.4211</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/pal2/icon15.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Folder>
|
||||
<Placemark>
|
||||
<LineString>
|
||||
<coordinates>10.98526,47.4211 10.98526,47.196269598520324</coordinates>
|
||||
</LineString>
|
||||
<Style>
|
||||
<LineStyle>
|
||||
<color>c00000c0</color>
|
||||
<tessellate>1</tessellate>
|
||||
<width>3</width>
|
||||
</LineStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
</Folder>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Lermoos-Tiroler Zugspitz Arena</name>
|
||||
<snippet>signboard</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>10.879767,47.401283</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/5.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Davos</name>
|
||||
<snippet>signboard</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>9.851879,46.815225</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/6.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Titlis</name>
|
||||
<snippet>signboard</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>8.424457,46.770918</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/7.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Eiger</name>
|
||||
<snippet>1500m radius</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>8.005393,46.577621</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/8.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<LineString>
|
||||
<coordinates>8.005393,46.59111082408879 8.007411386004984,46.59103930859754 8.009408360780075,46.590825520768504 8.011362740730094,46.59047172847008 8.013253795087904,46.58998168468399 8.01506146625202,46.58936058760861 8.016766582908044,46.58861502540384 8.018351063653368,46.587752906169705 8.019798108949551,46.586783373908844 8.021092379355597,46.58571671137243 8.022220158146354,46.584564230829145 8.02316949659154,46.58333815392459 8.023930340360643,46.582051481914704 8.024494635724556,46.58071785765729 8.024856414444256,46.57935142083319 8.025011856467065,46.57796665793827 8.024959329790072,46.576578248641624 8.024699407094783,46.57520091014225 8.02423485900483,46.5738492411753 8.023570624066586,46.572537567321 8.022713755798401,46.57127978925356 8.02167334739507,46.57008923553433 8.020460434907957,46.56897852150325 8.019087879945149,46.56795941575718 8.01757023314834,46.5670427156218 8.015923579901438,46.56623813292746 8.014165369908543,46.565554191290495 8.012314232444112,46.56499813597875 8.01038977922428,46.564575857307794 8.00841239697435,46.56429182837157 8.006403031871999,46.5641490577601 8.004382968128002,46.5641490577601 8.002373603025651,46.56429182837157 8.00039622077572,46.564575857307794 7.998471767555888,46.56499813597875 7.996620630091457,46.565554191290495 7.9948624200985625,46.56623813292746 7.99321576685166,46.5670427156218 7.991698120054851,46.56795941575718 7.990325565092044,46.56897852150325 7.989112652604931,46.57008923553433 7.9880722442016,46.57127978925356 7.987215375933413,46.572537567321 7.986551140995171,46.5738492411753 7.986086592905218,46.57520091014225 7.98582667020993,46.576578248641624 7.985774143532935,46.57796665793827 7.985929585555744,46.57935142083319 7.986291364275444,46.58071785765729 7.9868556596393585,46.582051481914704 7.987616503408459,46.58333815392459 7.988565841853647,46.584564230829145 7.989693620644403,46.58571671137243 7.990987891050449,46.586783373908844 7.992434936346632,46.587752906169705 7.994019417091955,46.58861502540384 7.995724533747981,46.58936058760861 7.997532204912097,46.58998168468399 7.999423259269906,46.59047172847008 8.001377639219925,46.590825520768504 8.003374613995016,46.59103930859754 8.005393,46.59111082408879</coordinates>
|
||||
</LineString>
|
||||
<Style>
|
||||
<LineStyle>
|
||||
<color>c000c000</color>
|
||||
<tessellate>1</tessellate>
|
||||
<width>3</width>
|
||||
</LineStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Mont Blanc</name>
|
||||
<snippet>pass N</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>6.867674,45.830359</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/9.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Folder>
|
||||
<Placemark>
|
||||
<LineString>
|
||||
<coordinates>6.867674,45.830359 6.867674,45.605528598520316</coordinates>
|
||||
</LineString>
|
||||
<Style>
|
||||
<LineStyle>
|
||||
<color>c00000c0</color>
|
||||
<tessellate>1</tessellate>
|
||||
<width>3</width>
|
||||
</LineStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
</Folder>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>St. Hilare</name>
|
||||
<snippet>signboard</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>5.887857,45.306816</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/10.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Monte Viso</name>
|
||||
<snippet>2250m radius</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>7.090381,44.667312</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/A.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Placemark>
|
||||
<LineString>
|
||||
<coordinates>7.090381,44.68754673613318 7.092762589980537,44.6874757453242 7.095127457411984,44.687263271366724 7.09745899753014,44.686910806157954 7.099740840300694,44.68642082451503 7.101956965690705,44.68579676674505 7.104091816445464,44.685043014417296 7.106130407568226,44.68416485951042 7.108058431724881,44.68316846715423 7.109862359825947,44.6820608322314 7.111529536074018,44.680849730147806 7.113048266805791,44.67954366212132 7.114407902503551,44.678151795378 7.115598912401194,44.67668389867965 7.1166129511641305,44.675150273640085 7.117442917180201,44.673561682316134 7.118083002059652,44.67192927158581 7.118528731005785,44.67026449484756 7.118776993783513,44.668579031593154 7.11882606608039,44.666884705420784 7.118675621123042,44.66519340106522 7.118326731480924,44.663516981028 7.1177818610584405,44.661867202392706 7.117044847345161,44.66025563440796 7.116120874061689,44.65869357741505 7.115016434405281,44.65719198368645 7.113739285164013,44.65576138072787 7.1122983920308585,44.65441179757801 7.110703866508997,44.653152694619315 7.108966894856653,44.65199289738741 7.1070996595734846,44.650940534838895 7.105115253980594,44.65000298250571 7.103027590492422,44.64918681092998 7.1008513032207565,44.64849773973612 7.098601645588721,44.64794059765821 7.096294383665553,44.64751928879869 7.093945685961249,44.64723676535147 7.091572010443376,44.64709500697793 7.0891899895566235,44.64709500697793 7.08681631403875,44.64723676535147 7.084467616334447,44.64751928879869 7.08216035441128,44.64794059765821 7.079910696779244,44.64849773973612 7.0777344095075785,44.64918681092998 7.075646746019405,44.65000298250571 7.073662340426515,44.650940534838895 7.071795105143346,44.65199289738741 7.070058133491003,44.653152694619315 7.068463607969141,44.65441179757801 7.0670227148359865,44.65576138072787 7.065745565594718,44.65719198368645 7.06464112593831,44.65869357741505 7.063717152654839,44.66025563440796 7.062980138941559,44.661867202392706 7.062435268519076,44.663516981028 7.062086378876957,44.66519340106522 7.061935933919609,44.666884705420784 7.061985006216487,44.668579031593154 7.062233268994214,44.67026449484756 7.062678997940347,44.67192927158581 7.063319082819799,44.673561682316134 7.064149048835869,44.675150273640085 7.065163087598806,44.67668389867965 7.06635409749645,44.678151795378 7.067713733194209,44.67954366212132 7.0692324639259825,44.680849730147806 7.070899640174052,44.6820608322314 7.072703568275118,44.68316846715423 7.074631592431774,44.68416485951042 7.076670183554536,44.685043014417296 7.078805034309295,44.68579676674505 7.081021159699306,44.68642082451503 7.08330300246986,44.686910806157954 7.085634542588016,44.687263271366724 7.087999410019463,44.6874757453242 7.090381,44.68754673613318</coordinates>
|
||||
</LineString>
|
||||
<Style>
|
||||
<LineStyle>
|
||||
<color>c000c000</color>
|
||||
<tessellate>1</tessellate>
|
||||
<width>3</width>
|
||||
</LineStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Cheval Blanc</name>
|
||||
<snippet>pass W</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>6.422229,44.120985</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/B.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Folder>
|
||||
<Placemark>
|
||||
<LineString>
|
||||
<coordinates>6.422229,44.120985 6.7354178618529215,44.12055721299625</coordinates>
|
||||
</LineString>
|
||||
<Style>
|
||||
<LineStyle>
|
||||
<color>c00000c0</color>
|
||||
<tessellate>1</tessellate>
|
||||
<width>3</width>
|
||||
</LineStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
</Folder>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Peille</name>
|
||||
<snippet>signboard</snippet>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>7.410751,43.755956</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/stop.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
<Folder>
|
||||
<name>Monaco</name>
|
||||
<Placemark>
|
||||
<Point>
|
||||
<coordinates>7.454787,43.75875</coordinates>
|
||||
</Point>
|
||||
<Style>
|
||||
<IconStyle>
|
||||
<hotSpot x="0.5" y="0" xunits="fraction" yunits="fraction"></hotSpot>
|
||||
<Icon>
|
||||
<href>https://maps.google.com/mapfiles/kml/paddle/ylw-stars.png</href>
|
||||
</Icon>
|
||||
</IconStyle>
|
||||
</Style>
|
||||
</Placemark>
|
||||
<Style>
|
||||
<ListStyle>
|
||||
<listItemType>checkHideChildren</listItemType>
|
||||
</ListStyle>
|
||||
</Style>
|
||||
</Folder>
|
||||
</Folder>
|
||||
</Document>
|
||||
</kml>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 352 KiB |
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "leaflet-kml",
|
||||
"version": "1.0.1",
|
||||
"description": "Leaflet KML layer plugin",
|
||||
"main": "L.KML.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/windycom/leaflet-kml.git"
|
||||
},
|
||||
"author": "Pavel Shramov, Bruno Bergot",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/windycom/leaflet-kml/issues"
|
||||
},
|
||||
"homepage": "https://github.com/windycom/leaflet-kml#readme"
|
||||
}
|
||||
@@ -4,14 +4,17 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css">
|
||||
<script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"></script>
|
||||
<!-- 1. Core Leaflet library -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://rawgit.com/Leaflet/Leaflet.draw/v1.0.4/dist/leaflet.draw.css">
|
||||
<script src="https://rawgit.com/Leaflet/Leaflet.draw/v1.0.4/dist/leaflet.draw-src.js"></script>
|
||||
<!-- 2. Leaflet.draw Plugin (MUST be loaded AFTER Leaflet) -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css" />
|
||||
<script src="https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js"></script>
|
||||
|
||||
<script src="https://unpkg.com/togeojson@0.16.0"></script>
|
||||
<script src="https://unpkg.com/leaflet-filelayer@1.2.0"></script>
|
||||
<!-- 3. Other plugins -->
|
||||
<script src="https://unpkg.com/togeojson@0.16.0"></script>
|
||||
<script src="https://unpkg.com/leaflet-filelayer@1.2.0"></script>
|
||||
|
||||
<!--script type="text/javascript" src="https://getfirebug.com/firebug-lite.js"></script-->
|
||||
<script type="text/javascript" src="./qwebchannel.js"></script>
|
||||
|
||||
@@ -38,7 +38,10 @@ new QWebChannel(qt.webChannelTransport, function (channel)
|
||||
|
||||
map.on('mousemove', function(e)
|
||||
{
|
||||
MyApp.onMapMove(e.latlng.lat, e.latlng.lng)
|
||||
MyApp.onMapMove(e.latlng.lat, e.latlng.lng);
|
||||
|
||||
const bounds = map.getBounds();
|
||||
MyApp.onMapZoom(bounds.getSouth(), bounds.getWest(), bounds.getNorth(), bounds.getEast(), map.getZoom());
|
||||
});
|
||||
|
||||
var DrawShapes;
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
# /**********************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2026 Javier Braña <javier.branagutierrez@gmail.com> *
|
||||
# * *
|
||||
# * Alignment - Alineamiento horizontal y vertical *
|
||||
# * *
|
||||
# * Define el eje de la carretera mediante: *
|
||||
# * - Alineamiento horizontal: polilínea + curvas circulares *
|
||||
# * - Alineamiento vertical: rasante con pendientes y curvas verticales*
|
||||
# * - Estaciones (progresivas) *
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import Part
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
|
||||
def make_alignment_from_wire(wire, name="Alignment"):
|
||||
"""Crea un objeto Alignment a partir de un wire de FreeCAD."""
|
||||
if FreeCAD.ActiveDocument is None:
|
||||
return None
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
|
||||
Alignment(obj)
|
||||
_ViewProviderAlignment(obj.ViewObject)
|
||||
obj.SourceWire = wire
|
||||
obj.Label = name
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
return obj
|
||||
|
||||
|
||||
class Alignment:
|
||||
"""
|
||||
Alineamiento horizontal + vertical de una carretera.
|
||||
|
||||
Propiedades principales:
|
||||
SourceWire : Polilínea base del eje
|
||||
Stations : Progresivas (lista de distancias)
|
||||
StationInterval : Intervalo entre estaciones
|
||||
TotalLength : Longitud total del eje
|
||||
HorizontalCurves : Curvas circulares (radio, longitud, parámetros)
|
||||
VerticalPVI : Puntos de intersección vertical (PVI) para la rasante
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.Proxy = self
|
||||
self.setProperties(obj)
|
||||
self._cached_chainage = None
|
||||
self._cached_station_points = None
|
||||
self._cached_tangents = None
|
||||
|
||||
def setProperties(self, obj):
|
||||
pl = obj.PropertiesList
|
||||
|
||||
if "SourceWire" not in pl:
|
||||
obj.addProperty("App::PropertyLink",
|
||||
"SourceWire", "Alignment",
|
||||
"Polilínea base del eje")
|
||||
|
||||
if "Stations" not in pl:
|
||||
obj.addProperty("App::PropertyFloatList",
|
||||
"Stations", "Alignment",
|
||||
"Estaciones (progresivas) en mm")
|
||||
|
||||
if "StationInterval" not in pl:
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"StationInterval", "Alignment",
|
||||
"Intervalo entre estaciones").StationInterval = 20000
|
||||
|
||||
if "NumberOfStations" not in pl:
|
||||
obj.addProperty("App::PropertyInteger",
|
||||
"NumberOfStations", "Alignment",
|
||||
"Número de estaciones")
|
||||
obj.setEditorMode("NumberOfStations", 1)
|
||||
|
||||
if "TotalLength" not in pl:
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"TotalLength", "Alignment",
|
||||
"Longitud total del eje")
|
||||
obj.setEditorMode("TotalLength", 1)
|
||||
|
||||
if "HorizontalCurveRadii" not in pl:
|
||||
obj.addProperty("App::PropertyFloatList",
|
||||
"HorizontalCurveRadii", "Alignment",
|
||||
"Radios de curva en cada vértice (0 = recta)")
|
||||
|
||||
if "ShowStations" not in pl:
|
||||
obj.addProperty("App::PropertyBool",
|
||||
"ShowStations", "Alignment",
|
||||
"Mostrar marcas de estación en 3D").ShowStations = False
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
self.setProperties(obj)
|
||||
|
||||
def execute(self, obj):
|
||||
"""Calcula estaciones y geometría del alignment."""
|
||||
if not obj.SourceWire or not obj.SourceWire.Shape:
|
||||
return
|
||||
|
||||
wire = obj.SourceWire.Shape
|
||||
if wire.isNull():
|
||||
return
|
||||
|
||||
total_len = wire.Length
|
||||
obj.TotalLength = total_len
|
||||
if total_len <= 0:
|
||||
return
|
||||
|
||||
interval = obj.StationInterval.Value
|
||||
if interval <= 0:
|
||||
interval = 20000
|
||||
|
||||
n_stations = max(2, int(total_len / interval) + 1)
|
||||
obj.NumberOfStations = n_stations
|
||||
|
||||
stations = np.linspace(0, total_len, n_stations).tolist()
|
||||
obj.Stations = stations
|
||||
|
||||
# Calcular radios de curva en cada vértice del wire
|
||||
self._compute_curve_radii(obj, wire)
|
||||
|
||||
# Cache
|
||||
self._cached_chainage = stations
|
||||
self._cached_station_points = None
|
||||
self._cached_tangents = None
|
||||
|
||||
def _compute_curve_radii(self, obj, wire):
|
||||
"""Calcula el radio de curvatura en cada vértice de la polilínea."""
|
||||
vertices = wire.Vertexes
|
||||
n = len(vertices)
|
||||
if n < 3:
|
||||
obj.HorizontalCurveRadii = []
|
||||
return
|
||||
|
||||
radii = []
|
||||
for i in range(1, n - 1):
|
||||
p0 = vertices[i - 1].Point
|
||||
p1 = vertices[i].Point
|
||||
p2 = vertices[i + 1].Point
|
||||
|
||||
v1 = p1 - p0
|
||||
v2 = p2 - p1
|
||||
cross = FreeCAD.Vector(0, 0, 1).dot(v1.cross(v2))
|
||||
|
||||
if abs(cross) < 1.0: # casi colineal
|
||||
radii.append(0.0)
|
||||
else:
|
||||
# Radio aproximado = |v1| * |v2| / |v1 x v2|
|
||||
r = v1.Length * v2.Length / abs(cross)
|
||||
radii.append(round(r, 0))
|
||||
|
||||
obj.HorizontalCurveRadii = radii
|
||||
|
||||
def get_station_point(self, obj, distance):
|
||||
"""Devuelve el punto 3D en el eje a una progresiva dada (mm)."""
|
||||
if not obj.SourceWire:
|
||||
return None
|
||||
try:
|
||||
wire = obj.SourceWire.Shape
|
||||
param = wire.getParameterByLength(distance / obj.TotalLength)
|
||||
return wire.valueAt(param)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_tangent_at(self, obj, distance):
|
||||
"""Devuelve el vector tangente en una progresiva (normalizado)."""
|
||||
if not obj.SourceWire:
|
||||
return None
|
||||
try:
|
||||
wire = obj.SourceWire.Shape
|
||||
param = wire.getParameterByLength(distance / obj.TotalLength)
|
||||
t = wire.tangentAt(param)
|
||||
if t.Length > 0:
|
||||
t.normalize()
|
||||
return t
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_perpendicular_at(self, obj, distance):
|
||||
"""Devuelve el vector perpendicular (horizontal) en una progresiva."""
|
||||
t = self.get_tangent_at(obj, distance)
|
||||
if t is None:
|
||||
return None
|
||||
perp = FreeCAD.Vector(-t.y, t.x, 0)
|
||||
perp.normalize()
|
||||
return perp
|
||||
|
||||
def get_station_data(self, obj):
|
||||
"""
|
||||
Devuelve arrays de (puntos, tangentes, perpendiculares) para todas las estaciones.
|
||||
|
||||
Returns:
|
||||
tuple: (points, tangents, perps) listas de FreeCAD.Vector
|
||||
"""
|
||||
stations = obj.Stations
|
||||
if not stations:
|
||||
return [], [], []
|
||||
|
||||
points = []
|
||||
tangents = []
|
||||
perps = []
|
||||
|
||||
for s in stations:
|
||||
pt = self.get_station_point(obj, s)
|
||||
tg = self.get_tangent_at(obj, s)
|
||||
pp = self.get_perpendicular_at(obj, s)
|
||||
if pt and tg and pp:
|
||||
points.append(pt)
|
||||
tangents.append(tg)
|
||||
perps.append(pp)
|
||||
|
||||
return points, tangents, perps
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
|
||||
class _ViewProviderAlignment:
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
|
||||
def getIcon(self):
|
||||
return ""
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
@@ -0,0 +1,7 @@
|
||||
# Road Module - PVPlant Workbench
|
||||
# Sistema de carreteras con alineamiento profesional
|
||||
|
||||
from .Alignment import make_alignment_from_wire, Alignment
|
||||
from .CrossSection import CrossSectionBuilder
|
||||
from .Road import make_road, Road
|
||||
from .CutFill import calculate_cut_fill
|
||||
@@ -61,7 +61,7 @@ class SelObserver:
|
||||
|
||||
def onAceptClick(self):
|
||||
''' '''
|
||||
from PVPlantPlacement import moveFrameHead
|
||||
from Civil.PVPlantPlacementCalc import moveFrameHead
|
||||
moveFrameHead(self.obj, head=self.ui.comboHead.currentIndex(),
|
||||
dist=self.ui.editDist.value())
|
||||
self.setUI(self.obj)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import FreeCAD
|
||||
import Part
|
||||
import math
|
||||
import requests
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
# ==================================================================
|
||||
# CONFIGURACIÓN INICIAL (AJUSTAR ESTOS VALORES)
|
||||
# ==================================================================
|
||||
lat_min = 40.4165 # Latitud mínima (Ej: Madrid)
|
||||
lat_max = 40.4300
|
||||
lon_min = -3.7150 # Longitud mínima
|
||||
lon_max = -3.6900
|
||||
zoom = 16 # Nivel de zoom (13-19 para mejor resolución)
|
||||
terrain_resolution = 50 # Puntos por eje en la malla del terreno
|
||||
|
||||
|
||||
# ==================================================================
|
||||
def download_tile(lon_deg, lat_deg, zoom):
|
||||
"""Descarga un tile de OpenStreetMap"""
|
||||
xtile = int((lon_deg + 180) / 360 * (2 ** zoom))
|
||||
ytile = int((1 - math.log(math.tan(math.radians(lat_deg)) + 1 / math.cos(math.radians(lat_deg))) / math.pi) / 2 * (
|
||||
2 ** zoom))
|
||||
|
||||
url = f"https://tile.openstreetmap.org/{zoom}/{xtile}/{ytile}.png"
|
||||
response = requests.get(url)
|
||||
return Image.open(BytesIO(response.content))
|
||||
|
||||
|
||||
def get_tile_grid(lon_min, lon_max, lat_min, lat_max, zoom):
|
||||
"""Calcula la cuadrícula de tiles necesarios"""
|
||||
xtile_min = int((lon_min + 180) / 360 * (2 ** zoom))
|
||||
xtile_max = int((lon_max + 180) / 360 * (2 ** zoom))
|
||||
|
||||
ytile_min = int((1 - math.log(math.tan(math.radians(lat_min)) + 1 / math.cos(math.radians(lat_min))) / math.pi) / 2 * (2 ** zoom))
|
||||
ytile_max = int((1 - math.log(math.tan(math.radians(lat_max)) + 1 / math.cos(math.radians(lat_max))) / math.pi) / 2 * (2 ** zoom))
|
||||
|
||||
return range(xtile_min, xtile_max + 1), range(ytile_min, ytile_max + 1)
|
||||
|
||||
|
||||
def create_stitched_image(xtiles, ytiles, zoom):
|
||||
"""Une los tiles en una sola imagen"""
|
||||
tile_size = 256
|
||||
full_image = Image.new('RGB', (len(xtiles) * tile_size, len(ytiles) * tile_size))
|
||||
|
||||
for i, x in enumerate(xtiles):
|
||||
for j, y in enumerate(ytiles):
|
||||
tile = download_tile(x, y, zoom)
|
||||
full_image.paste(tile, (i * tile_size, j * tile_size))
|
||||
|
||||
return full_image
|
||||
|
||||
|
||||
def create_parametric_terrain(lon_min, lon_max, lat_min, lat_max, resolution):
|
||||
"""Crea una superficie paramétrica basada en coordenadas geográficas"""
|
||||
points = []
|
||||
for i in np.linspace(lat_min, lat_max, resolution):
|
||||
row = []
|
||||
for j in np.linspace(lon_min, lon_max, resolution):
|
||||
row.append(FreeCAD.Vector(j, i, 0)) # Añadir altura aquí si hay datos de elevación
|
||||
points.append(row)
|
||||
|
||||
terrain = Part.makeFilledFace([points])
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "Terrain")
|
||||
obj.Shape = terrain
|
||||
return obj
|
||||
|
||||
|
||||
def apply_texture(obj, image_path):
|
||||
"""Aplica la textura al objeto en FreeCAD"""
|
||||
obj.ViewObject.TextureMode = "EMBEDDED"
|
||||
obj.ViewObject.TextureImage = image_path
|
||||
obj.ViewObject.DisplayMode = "Shaded"
|
||||
|
||||
|
||||
# Ejecución principal
|
||||
'''if __name__ == "__main__":
|
||||
# Paso 1: Descargar y unir tiles
|
||||
xtiles, ytiles = get_tile_grid(lon_min, lon_max, lat_min, lat_max, zoom)
|
||||
stitched_image = create_stitched_image(xtiles, ytiles, zoom)
|
||||
texture_path = "/tmp/combined_texture.png"
|
||||
stitched_image.save(texture_path)
|
||||
|
||||
# Paso 2: Crear terreno
|
||||
doc = FreeCAD.newDocument()
|
||||
terrain = create_parametric_terrain(lon_min, lon_max, lat_min, lat_max, terrain_resolution)
|
||||
|
||||
# Paso 3: Aplicar textura
|
||||
apply_texture(terrain, texture_path)
|
||||
|
||||
# Ajustar vista
|
||||
FreeCAD.Gui.SendMsgToActiveView("ViewFit")
|
||||
FreeCAD.Gui.updateGui()
|
||||
|
||||
print("¡Proceso completado!")'''
|
||||
+10
-4
@@ -201,8 +201,8 @@ def simplifyWire(wire):
|
||||
else:
|
||||
i += 1
|
||||
|
||||
if closed:
|
||||
points.append(FreeCAD.Vector(points[0]))
|
||||
#if closed:
|
||||
points.append(FreeCAD.Vector(points[0]))
|
||||
|
||||
pol = Part.makePolygon(points)
|
||||
return pol
|
||||
@@ -224,14 +224,20 @@ def getProjected(shape, direction=FreeCAD.Vector(0, 0, 1)): # Based on Draft / s
|
||||
return ow
|
||||
|
||||
|
||||
def findObjects(classtype):
|
||||
'''def findObjects(classtype):
|
||||
objects = FreeCAD.ActiveDocument.Objects
|
||||
objlist = list()
|
||||
for object in objects:
|
||||
if hasattr(object, "Proxy"):
|
||||
if object.Proxy.Type == classtype:
|
||||
objlist.append(object)
|
||||
return objlist
|
||||
return objlist'''
|
||||
|
||||
def findObjects(classtype):
|
||||
return [obj for obj in FreeCAD.ActiveDocument.Objects
|
||||
if hasattr(obj, "Proxy")
|
||||
and hasattr(obj.Proxy, "Type")
|
||||
and obj.Proxy.Type == classtype]
|
||||
|
||||
def getClosePoints(sh1, angle):
|
||||
'''
|
||||
|
||||
@@ -1,348 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__title__ = "Freehand BSpline"
|
||||
__author__ = "Christophe Grellier (Chris_G)"
|
||||
__license__ = "LGPL 2.1"
|
||||
__doc__ = "Creates an freehand BSpline curve"
|
||||
__usage__ = """*** Interpolation curve control keys :
|
||||
|
||||
a - Select all / Deselect
|
||||
i - Insert point in selected segments
|
||||
t - Set / unset tangent (view direction)
|
||||
p - Align selected objects
|
||||
s - Snap points on shape / Unsnap
|
||||
l - Set/unset a linear interpolation
|
||||
x,y,z - Axis constraints during grab
|
||||
q - Apply changes and quit editing"""
|
||||
|
||||
import os
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import Part
|
||||
|
||||
from . import ICONPATH
|
||||
from . import _utils
|
||||
from . import profile_editor
|
||||
|
||||
TOOL_ICON = os.path.join(ICONPATH, 'editableSpline.svg')
|
||||
# debug = _utils.debug
|
||||
debug = _utils.doNothing
|
||||
|
||||
|
||||
def check_pivy():
|
||||
try:
|
||||
profile_editor.MarkerOnShape([FreeCAD.Vector()])
|
||||
return True
|
||||
except Exception as exc:
|
||||
FreeCAD.Console.PrintWarning(str(exc) + "\nPivy interaction library failure\n")
|
||||
return False
|
||||
|
||||
|
||||
def midpoint(e):
|
||||
p = e.FirstParameter + 0.5 * (e.LastParameter - e.FirstParameter)
|
||||
return e.valueAt(p)
|
||||
|
||||
|
||||
class GordonProfileFP:
|
||||
"""Creates an editable interpolation curve"""
|
||||
def __init__(self, obj, s, d, t):
|
||||
"""Add the properties"""
|
||||
obj.addProperty("App::PropertyLinkSubList", "Support", "Profile", "Constraint shapes").Support = s
|
||||
obj.addProperty("App::PropertyFloatConstraint", "Parametrization", "Profile", "Parametrization factor")
|
||||
obj.addProperty("App::PropertyFloat", "Tolerance", "Profile", "Tolerance").Tolerance = 1e-5
|
||||
obj.addProperty("App::PropertyBool", "Periodic", "Profile", "Periodic curve").Periodic = False
|
||||
obj.addProperty("App::PropertyVectorList", "Data", "Profile", "Data list").Data = d
|
||||
obj.addProperty("App::PropertyVectorList", "Tangents", "Profile", "Tangents list")
|
||||
obj.addProperty("App::PropertyBoolList", "Flags", "Profile", "Tangent flags")
|
||||
obj.addProperty("App::PropertyIntegerList", "DataType", "Profile", "Types of interpolated points").DataType = t
|
||||
obj.addProperty("App::PropertyBoolList", "LinearSegments", "Profile", "Linear segment flags")
|
||||
obj.Parametrization = (1.0, 0.0, 1.0, 0.05)
|
||||
obj.Proxy = self
|
||||
|
||||
def get_shapes(self, fp):
|
||||
if hasattr(fp, 'Support'):
|
||||
sl = list()
|
||||
for ob, names in fp.Support:
|
||||
for name in names:
|
||||
if "Vertex" in name:
|
||||
n = eval(name.lstrip("Vertex"))
|
||||
if len(ob.Shape.Vertexes) >= n:
|
||||
sl.append(ob.Shape.Vertexes[n - 1])
|
||||
elif ("Point" in name):
|
||||
sl.append(Part.Vertex(ob.Shape.Point))
|
||||
elif ("Edge" in name):
|
||||
n = eval(name.lstrip("Edge"))
|
||||
if len(ob.Shape.Edges) >= n:
|
||||
sl.append(ob.Shape.Edges[n - 1])
|
||||
elif ("Face" in name):
|
||||
n = eval(name.lstrip("Face"))
|
||||
if len(ob.Shape.Faces) >= n:
|
||||
sl.append(ob.Shape.Faces[n - 1])
|
||||
return sl
|
||||
|
||||
def get_points(self, fp, stretch=True):
|
||||
touched = False
|
||||
shapes = self.get_shapes(fp)
|
||||
if not len(fp.Data) == len(fp.DataType):
|
||||
FreeCAD.Console.PrintError("Gordon Profile : Data and DataType mismatch\n")
|
||||
return(None)
|
||||
pts = list()
|
||||
shape_idx = 0
|
||||
for i in range(len(fp.Data)):
|
||||
if fp.DataType[i] == 0: # Free point
|
||||
pts.append(fp.Data[i])
|
||||
elif (fp.DataType[i] == 1):
|
||||
if (shape_idx < len(shapes)): # project on shape
|
||||
d, p, i = Part.Vertex(fp.Data[i]).distToShape(shapes[shape_idx])
|
||||
if d > fp.Tolerance:
|
||||
touched = True
|
||||
pts.append(p[0][1]) # shapes[shape_idx].valueAt(fp.Data[i].x))
|
||||
shape_idx += 1
|
||||
else:
|
||||
pts.append(fp.Data[i])
|
||||
if stretch and touched:
|
||||
params = [0]
|
||||
knots = [0]
|
||||
moves = [pts[0] - fp.Data[0]]
|
||||
lsum = 0
|
||||
mults = [2]
|
||||
for i in range(1, len(pts)):
|
||||
lsum += fp.Data[i - 1].distanceToPoint(fp.Data[i])
|
||||
params.append(lsum)
|
||||
if fp.DataType[i] == 1:
|
||||
knots.append(lsum)
|
||||
moves.append(pts[i] - fp.Data[i])
|
||||
mults.insert(1, 1)
|
||||
mults[-1] = 2
|
||||
if len(moves) < 2:
|
||||
return(pts)
|
||||
# FreeCAD.Console.PrintMessage("%s\n%s\n%s\n"%(moves,mults,knots))
|
||||
curve = Part.BSplineCurve()
|
||||
curve.buildFromPolesMultsKnots(moves, mults, knots, False, 1)
|
||||
for i in range(1, len(pts)):
|
||||
if fp.DataType[i] == 0:
|
||||
# FreeCAD.Console.PrintMessage("Stretch %s #%d: %s to %s\n"%(fp.Label,i,pts[i],curve.value(params[i])))
|
||||
pts[i] += curve.value(params[i])
|
||||
if touched:
|
||||
return pts
|
||||
else:
|
||||
return False
|
||||
|
||||
def execute(self, obj):
|
||||
try:
|
||||
o = FreeCADGui.ActiveDocument.getInEdit().Object
|
||||
if o == obj:
|
||||
return
|
||||
except:
|
||||
FreeCAD.Console.PrintWarning("execute is disabled during editing\n")
|
||||
pts = self.get_points(obj)
|
||||
if pts:
|
||||
if len(pts) < 2:
|
||||
FreeCAD.Console.PrintError("{} : Not enough points\n".format(obj.Label))
|
||||
return False
|
||||
else:
|
||||
obj.Data = pts
|
||||
else:
|
||||
pts = obj.Data
|
||||
|
||||
tans = [FreeCAD.Vector()] * len(pts)
|
||||
flags = [False] * len(pts)
|
||||
for i in range(len(obj.Tangents)):
|
||||
tans[i] = obj.Tangents[i]
|
||||
for i in range(len(obj.Flags)):
|
||||
flags[i] = obj.Flags[i]
|
||||
# if not (len(obj.LinearSegments) == len(pts)-1):
|
||||
# FreeCAD.Console.PrintError("%s : Points and LinearSegments mismatch\n"%obj.Label)
|
||||
if len(obj.LinearSegments) > 0:
|
||||
for i, b in enumerate(obj.LinearSegments):
|
||||
if b:
|
||||
tans[i] = pts[i + 1] - pts[i]
|
||||
tans[i + 1] = tans[i]
|
||||
flags[i] = True
|
||||
flags[i + 1] = True
|
||||
params = profile_editor.parameterization(pts, obj.Parametrization, obj.Periodic)
|
||||
|
||||
curve = Part.BSplineCurve()
|
||||
if len(pts) == 2:
|
||||
curve.buildFromPoles(pts)
|
||||
elif obj.Periodic and pts[0].distanceToPoint(pts[-1]) < 1e-7:
|
||||
curve.interpolate(Points=pts[:-1], Parameters=params, PeriodicFlag=obj.Periodic, Tolerance=obj.Tolerance, Tangents=tans[:-1], TangentFlags=flags[:-1])
|
||||
else:
|
||||
curve.interpolate(Points=pts, Parameters=params, PeriodicFlag=obj.Periodic, Tolerance=obj.Tolerance, Tangents=tans, TangentFlags=flags)
|
||||
obj.Shape = curve.toShape()
|
||||
|
||||
def onChanged(self, fp, prop):
|
||||
if prop in ("Support", "Data", "DataType", "Periodic"):
|
||||
# FreeCAD.Console.PrintMessage("%s : %s changed\n"%(fp.Label,prop))
|
||||
if (len(fp.Data) == len(fp.DataType)) and (sum(fp.DataType) == len(fp.Support)):
|
||||
new_pts = self.get_points(fp, True)
|
||||
if new_pts:
|
||||
fp.Data = new_pts
|
||||
if prop == "Parametrization":
|
||||
self.execute(fp)
|
||||
|
||||
def onDocumentRestored(self, fp):
|
||||
fp.setEditorMode("Data", 2)
|
||||
fp.setEditorMode("DataType", 2)
|
||||
|
||||
|
||||
class GordonProfileVP:
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
self.select_state = True
|
||||
self.active = False
|
||||
|
||||
def getIcon(self):
|
||||
return TOOL_ICON
|
||||
|
||||
def attach(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
self.active = False
|
||||
self.select_state = vobj.Selectable
|
||||
self.ip = None
|
||||
|
||||
def setEdit(self, vobj, mode=0):
|
||||
if mode == 0 and check_pivy():
|
||||
if vobj.Selectable:
|
||||
self.select_state = True
|
||||
vobj.Selectable = False
|
||||
pts = list()
|
||||
sl = list()
|
||||
for ob, names in self.Object.Support:
|
||||
for name in names:
|
||||
sl.append((ob, (name,)))
|
||||
shape_idx = 0
|
||||
for i in range(len(self.Object.Data)):
|
||||
p = self.Object.Data[i]
|
||||
t = self.Object.DataType[i]
|
||||
if t == 0:
|
||||
pts.append(profile_editor.MarkerOnShape([p]))
|
||||
elif t == 1:
|
||||
pts.append(profile_editor.MarkerOnShape([p], sl[shape_idx]))
|
||||
shape_idx += 1
|
||||
for i in range(len(pts)): # p,t,f in zip(pts, self.Object.Tangents, self.Object.Flags):
|
||||
if i < min(len(self.Object.Flags), len(self.Object.Tangents)):
|
||||
if self.Object.Flags[i]:
|
||||
pts[i].tangent = self.Object.Tangents[i]
|
||||
self.ip = profile_editor.InterpoCurveEditor(pts, self.Object)
|
||||
self.ip.periodic = self.Object.Periodic
|
||||
self.ip.param_factor = self.Object.Parametrization
|
||||
for i in range(min(len(self.Object.LinearSegments), len(self.ip.lines))):
|
||||
self.ip.lines[i].tangent = self.Object.LinearSegments[i]
|
||||
self.ip.lines[i].updateLine()
|
||||
self.active = True
|
||||
return True
|
||||
return False
|
||||
|
||||
def unsetEdit(self, vobj, mode=0):
|
||||
if isinstance(self.ip, profile_editor.InterpoCurveEditor) and check_pivy():
|
||||
pts = list()
|
||||
typ = list()
|
||||
tans = list()
|
||||
flags = list()
|
||||
# original_links = self.Object.Support
|
||||
new_links = list()
|
||||
for p in self.ip.points:
|
||||
if isinstance(p, profile_editor.MarkerOnShape):
|
||||
pt = p.points[0]
|
||||
pts.append(FreeCAD.Vector(pt[0], pt[1], pt[2]))
|
||||
if p.sublink:
|
||||
new_links.append(p.sublink)
|
||||
typ.append(1)
|
||||
else:
|
||||
typ.append(0)
|
||||
if p.tangent:
|
||||
tans.append(p.tangent)
|
||||
flags.append(True)
|
||||
else:
|
||||
tans.append(FreeCAD.Vector())
|
||||
flags.append(False)
|
||||
self.Object.Tangents = tans
|
||||
self.Object.Flags = flags
|
||||
self.Object.LinearSegments = [li.linear for li in self.ip.lines]
|
||||
self.Object.DataType = typ
|
||||
self.Object.Data = pts
|
||||
self.Object.Support = new_links
|
||||
vobj.Selectable = self.select_state
|
||||
self.ip.quit()
|
||||
self.ip = None
|
||||
self.active = False
|
||||
self.Object.Document.recompute()
|
||||
return True
|
||||
|
||||
def doubleClicked(self, vobj):
|
||||
if not hasattr(self, 'active'):
|
||||
self.active = False
|
||||
if not self.active:
|
||||
self.active = True
|
||||
# self.setEdit(vobj)
|
||||
vobj.Document.setEdit(vobj)
|
||||
else:
|
||||
vobj.Document.resetEdit()
|
||||
self.active = False
|
||||
return True
|
||||
|
||||
def __getstate__(self):
|
||||
return {"name": self.Object.Name}
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.Object = FreeCAD.ActiveDocument.getObject(state["name"])
|
||||
return None
|
||||
|
||||
|
||||
class GordonProfileCommand:
|
||||
"""Creates a editable interpolation curve"""
|
||||
|
||||
def makeFeature(self, sub, pts, typ):
|
||||
fp = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Freehand BSpline")
|
||||
GordonProfileFP(fp, sub, pts, typ)
|
||||
GordonProfileVP(fp.ViewObject)
|
||||
FreeCAD.Console.PrintMessage(__usage__)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
FreeCADGui.SendMsgToActiveView("ViewFit")
|
||||
fp.ViewObject.Document.setEdit(fp.ViewObject)
|
||||
|
||||
def Activated(self):
|
||||
s = FreeCADGui.Selection.getSelectionEx()
|
||||
try:
|
||||
ordered = FreeCADGui.activeWorkbench().Selection
|
||||
if ordered:
|
||||
s = ordered
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
sub = list()
|
||||
pts = list()
|
||||
for obj in s:
|
||||
if obj.HasSubObjects:
|
||||
# FreeCAD.Console.PrintMessage("object has subobjects %s\n"%str(obj.SubElementNames))
|
||||
for n in obj.SubElementNames:
|
||||
sub.append((obj.Object, [n]))
|
||||
for p in obj.PickedPoints:
|
||||
pts.append(p)
|
||||
|
||||
if len(pts) == 0:
|
||||
pts = [FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(5, 0, 0), FreeCAD.Vector(10, 0, 0)]
|
||||
typ = [0, 0, 0]
|
||||
elif len(pts) == 1:
|
||||
pts.append(pts[0] + FreeCAD.Vector(5, 0, 0))
|
||||
pts.append(pts[0] + FreeCAD.Vector(10, 0, 0))
|
||||
typ = [1, 0, 0]
|
||||
else:
|
||||
typ = [1] * len(pts)
|
||||
self.makeFeature(sub, pts, typ)
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': TOOL_ICON,
|
||||
'MenuText': __title__,
|
||||
'ToolTip': "{}<br><br><b>Usage :</b><br>{}".format(__doc__, "<br>".join(__usage__.splitlines()))}
|
||||
|
||||
|
||||
FreeCADGui.addCommand('gordon_profile', GordonProfileCommand())
|
||||
@@ -1,533 +0,0 @@
|
||||
from pivy import coin
|
||||
#from pivy.utils import getPointOnScreen
|
||||
|
||||
def getPointOnScreen(render_manager, screen_pos, normal="camera", point=None):
|
||||
"""get coordinates from pixel position"""
|
||||
|
||||
pCam = render_manager.getCamera()
|
||||
vol = pCam.getViewVolume()
|
||||
|
||||
point = point or coin.SbVec3f(0, 0, 0)
|
||||
|
||||
if normal == "camera":
|
||||
plane = vol.getPlane(10)
|
||||
normal = plane.getNormal()
|
||||
elif normal == "x":
|
||||
normal = SbVec3f(1, 0, 0)
|
||||
elif normal == "y":
|
||||
normal = SbVec3f(0, 1, 0)
|
||||
elif normal == "z":
|
||||
normal = SbVec3f(0, 0, 1)
|
||||
normal.normalize()
|
||||
x, y = screen_pos
|
||||
vp = render_manager.getViewportRegion()
|
||||
size = vp.getViewportSize()
|
||||
dX, dY = size
|
||||
|
||||
fRatio = vp.getViewportAspectRatio()
|
||||
pX = float(x) / float(vp.getViewportSizePixels()[0])
|
||||
pY = float(y) / float(vp.getViewportSizePixels()[1])
|
||||
|
||||
if (fRatio > 1.0):
|
||||
pX = (pX - 0.5 * dX) * fRatio + 0.5 * dX
|
||||
elif (fRatio < 1.0):
|
||||
pY = (pY - 0.5 * dY) / fRatio + 0.5 * dY
|
||||
|
||||
plane = coin.SbPlane(normal, point)
|
||||
line = coin.SbLine(*vol.projectPointToLine(coin.SbVec2f(pX,pY)))
|
||||
pt = plane.intersect(line)
|
||||
return(pt)
|
||||
|
||||
COLORS = {
|
||||
"black": (0., 0., 0.),
|
||||
"white": (1., 1., 1.),
|
||||
"grey": (.5, .5, .5),
|
||||
"red": (1., 0., 0.),
|
||||
"green": (0., 1., 0.),
|
||||
"blue": (0., 0., 1.),
|
||||
"yellow": (1., 1., 0.),
|
||||
"cyan": (0., 1., 1.),
|
||||
"magenta":(1., 0., 1.)
|
||||
}
|
||||
|
||||
class Object3D(coin.SoSeparator):
|
||||
std_col = "black"
|
||||
ovr_col = "red"
|
||||
sel_col = "yellow"
|
||||
non_col = "grey"
|
||||
|
||||
def __init__(self, points, dynamic=False):
|
||||
super(Object3D, self).__init__()
|
||||
self.data = coin.SoCoordinate3()
|
||||
self.color = coin.SoMaterial()
|
||||
self.set_color()
|
||||
self += [self.color, self.data]
|
||||
self.start_pos = None
|
||||
self.dynamic = dynamic
|
||||
|
||||
# callback function lists
|
||||
self.on_drag = []
|
||||
self.on_drag_release = []
|
||||
self.on_drag_start = []
|
||||
|
||||
self._delete = False
|
||||
self._tmp_points = None
|
||||
self.enabled = True
|
||||
self.points = points
|
||||
|
||||
def set_disabled(self):
|
||||
self.color.diffuseColor = COLORS[self.non_col]
|
||||
self.enabled = False
|
||||
|
||||
def set_enabled(self):
|
||||
self.color.diffuseColor = COLORS[self.std_col]
|
||||
self.enabled = True
|
||||
|
||||
def set_color(self, col=None):
|
||||
self.std_col = col or self.std_col
|
||||
self.color.diffuseColor = COLORS[self.std_col]
|
||||
|
||||
@property
|
||||
def points(self):
|
||||
return self.data.point.getValues()
|
||||
|
||||
@points.setter
|
||||
def points(self, points):
|
||||
self.data.point.setValue(0, 0, 0)
|
||||
self.data.point.setValues(0, len(points), points)
|
||||
|
||||
def set_mouse_over(self):
|
||||
if self.enabled:
|
||||
self.color.diffuseColor = COLORS[self.ovr_col]
|
||||
|
||||
def unset_mouse_over(self):
|
||||
if self.enabled:
|
||||
self.color.diffuseColor = COLORS[self.std_col]
|
||||
|
||||
def select(self):
|
||||
if self.enabled:
|
||||
self.color.diffuseColor = COLORS[self.sel_col]
|
||||
|
||||
def unselect(self):
|
||||
if self.enabled:
|
||||
self.color.diffuseColor = COLORS[self.std_col]
|
||||
|
||||
def drag(self, mouse_coords, fact=1.):
|
||||
if self.enabled:
|
||||
pts = self.points
|
||||
for i, pt in enumerate(pts):
|
||||
pt[0] = mouse_coords[0] * fact + self._tmp_points[i][0]
|
||||
pt[1] = mouse_coords[1] * fact + self._tmp_points[i][1]
|
||||
pt[2] = mouse_coords[2] * fact + self._tmp_points[i][2]
|
||||
self.points = pts
|
||||
for foo in self.on_drag:
|
||||
foo()
|
||||
|
||||
def drag_release(self):
|
||||
if self.enabled:
|
||||
for foo in self.on_drag_release:
|
||||
foo()
|
||||
|
||||
def drag_start(self):
|
||||
self._tmp_points = self.points
|
||||
if self.enabled:
|
||||
for foo in self.on_drag_start:
|
||||
foo()
|
||||
|
||||
@property
|
||||
def drag_objects(self):
|
||||
if self.enabled:
|
||||
return [self]
|
||||
|
||||
def delete(self):
|
||||
if self.enabled and not self._delete:
|
||||
self._delete = True
|
||||
|
||||
def check_dependency(self):
|
||||
pass
|
||||
|
||||
|
||||
class Marker(Object3D):
|
||||
def __init__(self, points, dynamic=False):
|
||||
super(Marker, self).__init__(points, dynamic)
|
||||
self.marker = coin.SoMarkerSet()
|
||||
self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9
|
||||
self.addChild(self.marker)
|
||||
|
||||
|
||||
class Line(Object3D):
|
||||
def __init__(self, points, dynamic=False):
|
||||
super(Line, self).__init__(points, dynamic)
|
||||
self.drawstyle = coin.SoDrawStyle()
|
||||
self.line = coin.SoLineSet()
|
||||
self.addChild(self.drawstyle)
|
||||
self.addChild(self.line)
|
||||
|
||||
class Point(Object3D):
|
||||
def __init__(self, points, dynamic=False):
|
||||
super(Point, self).__init__(points, dynamic)
|
||||
self.drawstyle = coin.SoDrawStyle()
|
||||
self.point = coin.SoPointSet()
|
||||
self.addChild(self.drawstyle)
|
||||
self.addChild(self.point)
|
||||
|
||||
class Polygon(Object3D):
|
||||
def __init__(self, points, dynamic=False):
|
||||
super(Polygon, self).__init__(points, dynamic)
|
||||
self.polygon = coin.SoFaceSet()
|
||||
self.addChild(self.polygon)
|
||||
|
||||
class Arrow(Line):
|
||||
def __init__(self, points, dynamic=False, arrow_size=0.04, length=2):
|
||||
super(Arrow, self).__init__(points, dynamic)
|
||||
self.arrow_sep = coin.SoSeparator()
|
||||
self.arrow_rot = coin.SoRotation()
|
||||
self.arrow_scale = coin.SoScale()
|
||||
self.arrow_translate = coin.SoTranslation()
|
||||
self.arrow_scale.scaleFactor.setValue(arrow_size, arrow_size, arrow_size)
|
||||
self.cone = coin.SoCone()
|
||||
arrow_length = coin.SoScale()
|
||||
arrow_length.scaleFactor = (1, length, 1)
|
||||
arrow_origin = coin.SoTranslation()
|
||||
arrow_origin.translation = (0, -1, 0)
|
||||
self.arrow_sep += [self.arrow_translate, self.arrow_rot, self.arrow_scale]
|
||||
self.arrow_sep += [arrow_length, arrow_origin, self.cone]
|
||||
self += [self.arrow_sep]
|
||||
self.set_arrow_direction()
|
||||
|
||||
def set_arrow_direction(self):
|
||||
pts = np.array(self.points)
|
||||
self.arrow_translate.translation = tuple(pts[-1])
|
||||
direction = pts[-1] - pts[-2]
|
||||
direction /= np.linalg.norm(direction)
|
||||
_rot = coin.SbRotation()
|
||||
_rot.setValue(coin.SbVec3f(0, 1, 0), coin.SbVec3f(*direction))
|
||||
self.arrow_rot.rotation.setValue(_rot)
|
||||
|
||||
class InteractionSeparator(coin.SoSeparator):
|
||||
pick_radius = 20
|
||||
ctrl_keys = {"grab": "g",
|
||||
"abort_grab": u"\uff1b",
|
||||
"select_all": "a",
|
||||
"delete": u"\uffff",
|
||||
"axis_x": "x",
|
||||
"axis_y": "y",
|
||||
"axis_z": "z"}
|
||||
|
||||
def __init__(self, render_manager):
|
||||
super(InteractionSeparator, self).__init__()
|
||||
self.render_manager = render_manager
|
||||
self.objects = coin.SoSeparator()
|
||||
self.dynamic_objects = []
|
||||
self.static_objects = []
|
||||
self.over_object = None
|
||||
self.selected_objects = []
|
||||
self.drag_objects = []
|
||||
|
||||
self.on_drag = []
|
||||
self.on_drag_release = []
|
||||
self.on_drag_start = []
|
||||
|
||||
self._direction = None
|
||||
|
||||
self.events = coin.SoEventCallback()
|
||||
self += self.events, self.objects
|
||||
|
||||
def register(self):
|
||||
self._highlightCB = self.events.addEventCallback(
|
||||
coin.SoLocation2Event.getClassTypeId(), self.highlightCB)
|
||||
self._selectCB = self.events.addEventCallback(
|
||||
coin.SoMouseButtonEvent.getClassTypeId(), self.selectCB)
|
||||
self._grabCB = self.events.addEventCallback(
|
||||
coin.SoMouseButtonEvent.getClassTypeId(), self.grabCB)
|
||||
self._deleteCB = self.events.addEventCallback(
|
||||
coin.SoKeyboardEvent.getClassTypeId(), self.deleteCB)
|
||||
self._selectAllCB = self.events.addEventCallback(
|
||||
coin.SoKeyboardEvent.getClassTypeId(), self.selectAllCB)
|
||||
|
||||
def unregister(self):
|
||||
self.events.removeEventCallback(
|
||||
coin.SoLocation2Event.getClassTypeId(), self._highlightCB)
|
||||
self.events.removeEventCallback(
|
||||
coin.SoMouseButtonEvent.getClassTypeId(), self._selectCB)
|
||||
self.events.removeEventCallback(
|
||||
coin.SoMouseButtonEvent.getClassTypeId(), self._grabCB)
|
||||
self.events.removeEventCallback(
|
||||
coin.SoKeyboardEvent.getClassTypeId(), self._deleteCB)
|
||||
self.events.removeEventCallback(
|
||||
coin.SoKeyboardEvent.getClassTypeId(), self._selectAllCB)
|
||||
|
||||
|
||||
def addChild(self, child):
|
||||
if hasattr(child, "dynamic"):
|
||||
self.objects.addChild(child)
|
||||
if child.dynamic:
|
||||
self.dynamic_objects.append(child)
|
||||
else:
|
||||
self.static_objects.append(child)
|
||||
else:
|
||||
super(InteractionSeparator, self).addChild(child)
|
||||
|
||||
#-----------------------HIGHLIGHTING-----------------------#
|
||||
# a SoLocation2Event calling a function which sends rays #
|
||||
# int the scene. This will return the object the mouse is #
|
||||
# currently hoovering. #
|
||||
|
||||
def highlightObject(self, obj):
|
||||
if self.over_object:
|
||||
self.over_object.unset_mouse_over()
|
||||
self.over_object = obj
|
||||
if self.over_object:
|
||||
self.over_object.set_mouse_over()
|
||||
self.colorSelected()
|
||||
|
||||
def highlightCB(self, attr, event_callback):
|
||||
event = event_callback.getEvent()
|
||||
pos = event.getPosition()
|
||||
obj = self.sendRay(pos)
|
||||
self.highlightObject(obj)
|
||||
|
||||
def sendRay(self, mouse_pos):
|
||||
"""sends a ray through the scene and return the nearest entity"""
|
||||
ray_pick = coin.SoRayPickAction(self.render_manager.getViewportRegion())
|
||||
ray_pick.setPoint(coin.SbVec2s(*mouse_pos))
|
||||
ray_pick.setRadius(InteractionSeparator.pick_radius)
|
||||
ray_pick.setPickAll(True)
|
||||
ray_pick.apply(self.render_manager.getSceneGraph())
|
||||
picked_point = ray_pick.getPickedPointList()
|
||||
return self.objByID(picked_point)
|
||||
|
||||
def objByID(self, picked_point):
|
||||
for point in picked_point:
|
||||
path = point.getPath()
|
||||
length = path.getLength()
|
||||
point = path.getNode(length - 2)
|
||||
for o in self.dynamic_objects:
|
||||
if point == o:
|
||||
return(o)
|
||||
# Code below was not working with python 2.7 (pb with getNodeId ?)
|
||||
#point = list(filter(
|
||||
#lambda ctrl: ctrl.getNodeId() == point.getNodeId(),
|
||||
#self.dynamic_objects))
|
||||
#if point != []:
|
||||
#return point[0]
|
||||
return None
|
||||
|
||||
|
||||
|
||||
#------------------------SELECTION------------------------#
|
||||
def selectObject(self, obj, multi=False):
|
||||
if not multi:
|
||||
for o in self.selected_objects:
|
||||
o.unselect()
|
||||
self.selected_objects = []
|
||||
if obj:
|
||||
if obj in self.selected_objects:
|
||||
self.selected_objects.remove(obj)
|
||||
else:
|
||||
self.selected_objects.append(obj)
|
||||
self.colorSelected()
|
||||
self.selectionChanged()
|
||||
|
||||
def selectCB(self, attr, event_callback):
|
||||
event = event_callback.getEvent()
|
||||
if (event.getState() == coin.SoMouseButtonEvent.DOWN and
|
||||
event.getButton() == event.BUTTON1):
|
||||
pos = event.getPosition()
|
||||
obj = self.sendRay(pos)
|
||||
self.selectObject(obj, event.wasCtrlDown())
|
||||
|
||||
def select_all_cb(self, event_callback):
|
||||
event = event_callback.getEvent()
|
||||
if (event.getKey() == ord(InteractionSeparator.ctrl_keys["select_all"])):
|
||||
if event.getState() == event.DOWN:
|
||||
if self.selected_objects:
|
||||
for o in self.selected_objects:
|
||||
o.unselect()
|
||||
self.selected_objects = []
|
||||
else:
|
||||
for obj in self.objects:
|
||||
if obj.dynamic:
|
||||
self.selected_objects.append(obj)
|
||||
self.ColorSelected()
|
||||
self.selection_changed()
|
||||
|
||||
def deselect_all(self):
|
||||
if self.selected_objects:
|
||||
for o in self.selected_objects:
|
||||
o.unselect()
|
||||
self.selected_objects = []
|
||||
|
||||
def colorSelected(self):
|
||||
for obj in self.selected_objects:
|
||||
obj.select()
|
||||
|
||||
def selectionChanged(self):
|
||||
pass
|
||||
|
||||
def selectAllCB(self, attr, event_callback):
|
||||
event = event_callback.getEvent()
|
||||
if (event.getKey() == ord(InteractionSeparator.ctrl_keys["select_all"])):
|
||||
if event.getState() == event.DOWN:
|
||||
if self.selected_objects:
|
||||
for o in self.selected_objects:
|
||||
o.unselect()
|
||||
self.selected_objects = []
|
||||
else:
|
||||
for obj in self.dynamic_objects:
|
||||
if obj.dynamic:
|
||||
self.selected_objects.append(obj)
|
||||
self.colorSelected()
|
||||
self.selectionChanged()
|
||||
|
||||
|
||||
#------------------------INTERACTION------------------------#
|
||||
|
||||
def cursor_pos(self, event):
|
||||
pos = event.getPosition()
|
||||
# print(list(getPointOnScreen1(self.render_manager, pos)))
|
||||
return getPointOnScreen(self.render_manager, pos)
|
||||
|
||||
|
||||
def constrained_vector(self, vector):
|
||||
if self._direction is None:
|
||||
return vector
|
||||
if self._direction == InteractionSeparator.ctrl_keys["axis_x"]:
|
||||
return [vector[0], 0, 0]
|
||||
elif self._direction == InteractionSeparator.ctrl_keys["axis_y"]:
|
||||
return [0, vector[1], 0]
|
||||
elif self._direction == InteractionSeparator.ctrl_keys["axis_z"]:
|
||||
return [0, 0, vector[2]]
|
||||
|
||||
def grabCB(self, attr, event_callback):
|
||||
# press grab key to move an entity
|
||||
event = event_callback.getEvent()
|
||||
# get all drag objects, every selected object can add some drag objects
|
||||
# but the eventhandler is not allowed to call the drag twice on an object
|
||||
#if event.getKey() == ord(InteractionSeparator.ctrl_keys["grab"]):
|
||||
if (event.getState() == coin.SoMouseButtonEvent.DOWN and
|
||||
event.getButton() == event.BUTTON1):
|
||||
pos = event.getPosition()
|
||||
obj = self.sendRay(pos)
|
||||
if obj:
|
||||
#if not obj in self.selected_objects:
|
||||
#self.selectObject(obj, event.wasCtrlDown())
|
||||
self.drag_objects = set()
|
||||
for i in self.selected_objects:
|
||||
for j in i.drag_objects:
|
||||
self.drag_objects.add(j)
|
||||
# check if something is selected
|
||||
if self.drag_objects:
|
||||
# first delete the selection_cb, and higlight_cb
|
||||
self.unregister()
|
||||
# now add a callback that calls the dragfunction of the selected entities
|
||||
self.start_pos = self.cursor_pos(event)
|
||||
self._dragCB = self.events.addEventCallback(
|
||||
coin.SoEvent.getClassTypeId(), self.dragCB)
|
||||
for obj in self.drag_objects:
|
||||
obj.drag_start()
|
||||
for foo in self.on_drag_start:
|
||||
foo()
|
||||
|
||||
def dragCB(self, attr, event_callback, force=False):
|
||||
event = event_callback.getEvent()
|
||||
b = ""
|
||||
s = ""
|
||||
if type(event) == coin.SoMouseButtonEvent:
|
||||
if event.getButton() == coin.SoMouseButtonEvent.BUTTON1:
|
||||
b = "mb1"
|
||||
elif event.getButton() == coin.SoMouseButtonEvent.BUTTON2:
|
||||
b = "mb2"
|
||||
if event.getState() == coin.SoMouseButtonEvent.UP:
|
||||
s = "up"
|
||||
elif event.getState() == coin.SoMouseButtonEvent.DOWN:
|
||||
s = "down"
|
||||
import FreeCAD
|
||||
FreeCAD.Console.PrintMessage("{} {}\n".format(b,s))
|
||||
if ((type(event) == coin.SoMouseButtonEvent and
|
||||
event.getState() == coin.SoMouseButtonEvent.UP
|
||||
and event.getButton() == coin.SoMouseButtonEvent.BUTTON1) or
|
||||
force):
|
||||
self.register()
|
||||
if self._dragCB:
|
||||
self.events.removeEventCallback(
|
||||
coin.SoEvent.getClassTypeId(), self._dragCB)
|
||||
self._direction = None
|
||||
self._dragCB = None
|
||||
self.start_pos = None
|
||||
for obj in self.drag_objects:
|
||||
obj.drag_release()
|
||||
for foo in self.on_drag_release:
|
||||
foo()
|
||||
self.drag_objects = []
|
||||
elif (type(event) == coin.SoKeyboardEvent and
|
||||
event.getState() == coin.SoMouseButtonEvent.DOWN):
|
||||
if event.getKey() == InteractionSeparator.ctrl_keys["abort_grab"]: # esc
|
||||
for obj in self.drag_objects:
|
||||
obj.drag([0, 0, 0], 1) # set back to zero
|
||||
self.dragCB(attr, event_callback, force=True)
|
||||
return
|
||||
try:
|
||||
key = chr(event.getKey())
|
||||
except ValueError:
|
||||
# there is no character for this value
|
||||
key = "_"
|
||||
if key in [InteractionSeparator.ctrl_keys["axis_x"],
|
||||
InteractionSeparator.ctrl_keys["axis_y"],
|
||||
InteractionSeparator.ctrl_keys["axis_z"]] and key != self._direction:
|
||||
self._direction = key
|
||||
else:
|
||||
self._direction = None
|
||||
diff = self.cursor_pos(event) - self.start_pos
|
||||
diff = self.constrained_vector(diff)
|
||||
for obj in self.drag_objects:
|
||||
obj.drag(diff, 1)
|
||||
for foo in self.on_drag:
|
||||
foo()
|
||||
|
||||
elif type(event) == coin.SoLocation2Event:
|
||||
fact = 0.1 if event.wasShiftDown() else 1.
|
||||
diff = self.cursor_pos(event) - self.start_pos
|
||||
diff = self.constrained_vector(diff)
|
||||
for obj in self.drag_objects:
|
||||
obj.drag(diff, fact)
|
||||
for foo in self.on_drag:
|
||||
foo()
|
||||
|
||||
def deleteCB(self, attr, event_callback):
|
||||
event = event_callback.getEvent()
|
||||
# get all drag objects, every selected object can add some drag objects
|
||||
# but the eventhandler is not allowed to call the drag twice on an object
|
||||
if event.getKey() == ord(InteractionSeparator.ctrl_keys["delete"]) and (event.getState() == 1):
|
||||
self.removeSelected()
|
||||
|
||||
def removeSelected(self):
|
||||
temp = []
|
||||
for i in self.selected_objects:
|
||||
i.delete()
|
||||
for i in self.dynamic_objects + self.static_objects:
|
||||
i.check_dependency() #dependency length max = 1
|
||||
for i in self.dynamic_objects + self.static_objects:
|
||||
if i._delete:
|
||||
temp.append(i)
|
||||
self.selected_objects = []
|
||||
self.over_object = None
|
||||
self.selectionChanged()
|
||||
for i in temp:
|
||||
if i in self.dynamic_objects:
|
||||
self.dynamic_objects.remove(i)
|
||||
else:
|
||||
self.static_objects.remove(i)
|
||||
self.objects.removeChild(i)
|
||||
del(i)
|
||||
self.selectionChanged()
|
||||
|
||||
def removeAllChildren(self):
|
||||
for i in self.dynamic_objects:
|
||||
i.delete()
|
||||
self.dynamic_objects = []
|
||||
self.static_objects = []
|
||||
self.selected_objects = []
|
||||
self.over_object = None
|
||||
super(InteractionSeparator, self).removeAllChildren()
|
||||
|
||||
@@ -1,501 +0,0 @@
|
||||
# from curve workbench
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import Part
|
||||
import PySide.QtCore as QtCore
|
||||
import PySide.QtGui as QtGui
|
||||
from pivy import coin
|
||||
|
||||
from Utils import graphics
|
||||
|
||||
|
||||
def parameterization(points, a, closed):
|
||||
"""Computes a knot Sequence for a set of points
|
||||
fac (0-1) : parameterization factor
|
||||
fac=0 -> Uniform / fac=0.5 -> Centripetal / fac=1.0 -> Chord-Length"""
|
||||
pts = points.copy()
|
||||
if closed and pts[0].distanceToPoint(pts[-1]) > 1e-7: # we need to add the first point as the end point
|
||||
pts.append(pts[0])
|
||||
params = [0]
|
||||
for i in range(1, len(pts)):
|
||||
p = pts[i] - pts[i - 1]
|
||||
if isinstance(p, FreeCAD.Vector):
|
||||
le = p.Length
|
||||
else:
|
||||
le = p.length()
|
||||
pl = pow(le, a)
|
||||
params.append(params[-1] + pl)
|
||||
return params
|
||||
|
||||
class ConnectionMarker(graphics.Marker):
|
||||
def __init__(self, points):
|
||||
super(ConnectionMarker, self).__init__(points, True)
|
||||
|
||||
class MarkerOnShape(graphics.Marker):
|
||||
def __init__(self, points, sh=None):
|
||||
super(MarkerOnShape, self).__init__(points, True)
|
||||
self._shape = None
|
||||
self._sublink = None
|
||||
self._tangent = None
|
||||
self._translate = coin.SoTranslation()
|
||||
self._text_font = coin.SoFont()
|
||||
self._text_font.name = "Arial:Bold"
|
||||
self._text_font.size = 13.0
|
||||
self._text = coin.SoText2()
|
||||
self._text_switch = coin.SoSwitch()
|
||||
self._text_switch.addChild(self._translate)
|
||||
self._text_switch.addChild(self._text_font)
|
||||
self._text_switch.addChild(self._text)
|
||||
self.on_drag_start.append(self.add_text)
|
||||
self.on_drag_release.append(self.remove_text)
|
||||
self.addChild(self._text_switch)
|
||||
if isinstance(sh, Part.Shape):
|
||||
self.snap_shape = sh
|
||||
elif isinstance(sh, (tuple, list)):
|
||||
self.sublink = sh
|
||||
|
||||
def subshape_from_sublink(self, o):
|
||||
name = o[1][0]
|
||||
print(name, " selected")
|
||||
if 'Vertex' in name:
|
||||
n = eval(name.lstrip('Vertex'))
|
||||
return o[0].Shape.Vertexes[n - 1]
|
||||
elif 'Edge' in name:
|
||||
n = eval(name.lstrip('Edge'))
|
||||
return o[0].Shape.Edges[n - 1]
|
||||
elif 'Face' in name:
|
||||
n = eval(name.lstrip('Face'))
|
||||
return o[0].Shape.Faces[n - 1]
|
||||
|
||||
def add_text(self):
|
||||
self._text_switch.whichChild = coin.SO_SWITCH_ALL
|
||||
self.on_drag.append(self.update_text)
|
||||
|
||||
def remove_text(self):
|
||||
self._text_switch.whichChild = coin.SO_SWITCH_NONE
|
||||
self.on_drag.remove(self.update_text)
|
||||
|
||||
def update_text(self):
|
||||
p = self.points[0]
|
||||
coords = ['{: 9.3f}'.format(p[0]), '{: 9.3f}'.format(p[1]), '{: 9.3f}'.format(p[2])]
|
||||
self._translate.translation = p
|
||||
self._text.string.setValues(0, 3, coords)
|
||||
|
||||
@property
|
||||
def tangent(self):
|
||||
return self._tangent
|
||||
|
||||
@tangent.setter
|
||||
def tangent(self, t):
|
||||
if isinstance(t, FreeCAD.Vector):
|
||||
if t.Length > 1e-7:
|
||||
self._tangent = t
|
||||
self._tangent.normalize()
|
||||
self.marker.markerIndex = coin.SoMarkerSet.DIAMOND_FILLED_9_9
|
||||
else:
|
||||
self._tangent = None
|
||||
self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9
|
||||
else:
|
||||
self._tangent = None
|
||||
self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9
|
||||
|
||||
@property
|
||||
def snap_shape(self):
|
||||
return self._shape
|
||||
|
||||
@snap_shape.setter
|
||||
def snap_shape(self, sh):
|
||||
if isinstance(sh, Part.Shape):
|
||||
self._shape = sh
|
||||
else:
|
||||
self._shape = None
|
||||
self.alter_color()
|
||||
|
||||
@property
|
||||
def sublink(self):
|
||||
return self._sublink
|
||||
|
||||
@sublink.setter
|
||||
def sublink(self, sl):
|
||||
if isinstance(sl, (tuple, list)) and not (sl == self._sublink):
|
||||
self._shape = self.subshape_from_sublink(sl)
|
||||
self._sublink = sl
|
||||
else:
|
||||
self._shape = None
|
||||
self._sublink = None
|
||||
self.alter_color()
|
||||
|
||||
def alter_color(self):
|
||||
if isinstance(self._shape, Part.Vertex):
|
||||
self.set_color("white")
|
||||
elif isinstance(self._shape, Part.Edge):
|
||||
self.set_color("cyan")
|
||||
elif isinstance(self._shape, Part.Face):
|
||||
self.set_color("magenta")
|
||||
else:
|
||||
self.set_color("black")
|
||||
|
||||
def __repr__(self):
|
||||
return "MarkerOnShape({})".format(self._shape)
|
||||
|
||||
def drag(self, mouse_coords, fact=1.):
|
||||
if self.enabled:
|
||||
pts = self.points
|
||||
for i, p in enumerate(pts):
|
||||
p[0] = mouse_coords[0] * fact + self._tmp_points[i][0]
|
||||
p[1] = mouse_coords[1] * fact + self._tmp_points[i][1]
|
||||
p[2] = mouse_coords[2] * fact + self._tmp_points[i][2]
|
||||
if self._shape:
|
||||
v = Part.Vertex(p[0], p[1], p[2])
|
||||
proj = v.distToShape(self._shape)[1][0][1]
|
||||
# FreeCAD.Console.PrintMessage("%s -> %s\n"%(p.getValue(), proj))
|
||||
p[0] = proj.x
|
||||
p[1] = proj.y
|
||||
p[2] = proj.z
|
||||
self.points = pts
|
||||
for foo in self.on_drag:
|
||||
foo()
|
||||
|
||||
|
||||
class ConnectionPolygon(graphics.Polygon):
|
||||
std_col = "green"
|
||||
|
||||
def __init__(self, markers):
|
||||
super(ConnectionPolygon, self).__init__(
|
||||
sum([m.points for m in markers], []), True)
|
||||
self.markers = markers
|
||||
|
||||
for m in self.markers:
|
||||
m.on_drag.append(self.updatePolygon)
|
||||
|
||||
def updatePolygon(self):
|
||||
self.points = sum([m.points for m in self.markers], [])
|
||||
|
||||
@property
|
||||
def drag_objects(self):
|
||||
return self.markers
|
||||
|
||||
def check_dependency(self):
|
||||
if any([m._delete for m in self.markers]):
|
||||
self.delete()
|
||||
|
||||
class ConnectionLine(graphics.Line):
|
||||
def __init__(self, markers):
|
||||
super(ConnectionLine, self).__init__(
|
||||
sum([m.points for m in markers], []), True)
|
||||
self.markers = markers
|
||||
self._linear = False
|
||||
for m in self.markers:
|
||||
m.on_drag.append(self.updateLine)
|
||||
|
||||
def updateLine(self):
|
||||
self.points = sum([m.points for m in self.markers], [])
|
||||
if self._linear:
|
||||
p1 = self.markers[0].points[0]
|
||||
p2 = self.markers[-1].points[0]
|
||||
t = p2 - p1
|
||||
tan = FreeCAD.Vector(t[0], t[1], t[2])
|
||||
for m in self.markers:
|
||||
m.tangent = tan
|
||||
|
||||
@property
|
||||
def linear(self):
|
||||
return self._linear
|
||||
|
||||
@linear.setter
|
||||
def linear(self, b):
|
||||
self._linear = bool(b)
|
||||
|
||||
@property
|
||||
def drag_objects(self):
|
||||
return self.markers
|
||||
|
||||
def check_dependency(self):
|
||||
if any([m._delete for m in self.markers]):
|
||||
self.delete()
|
||||
|
||||
class Edit(object):
|
||||
|
||||
def __init__(self, points=[], obj=None):
|
||||
self.points = list()
|
||||
self.lines = list()
|
||||
self.obj = obj
|
||||
self.root_inserted = False
|
||||
self.root = None
|
||||
|
||||
self.editing = None
|
||||
|
||||
# event callbacks
|
||||
self.selection_callback = None
|
||||
self._keyPressedCB = None
|
||||
self._mouseMovedCB = None
|
||||
self._mousePressedCB = None
|
||||
|
||||
for p in points:
|
||||
if isinstance(p, FreeCAD.Vector):
|
||||
self.points.append(MarkerOnShape([p]))
|
||||
elif isinstance(p, (tuple, list)):
|
||||
self.points.append(MarkerOnShape([p[0]], p[1]))
|
||||
elif isinstance(p, (MarkerOnShape, ConnectionMarker)):
|
||||
self.points.append(p)
|
||||
else:
|
||||
FreeCAD.Console.PrintError("InterpoCurveEditor : bad input")
|
||||
|
||||
# Setup coin objects
|
||||
if self.obj:
|
||||
self.guidoc = self.obj.ViewObject.Document
|
||||
else:
|
||||
if not FreeCADGui.ActiveDocument:
|
||||
FreeCAD.newDocument("New")
|
||||
self.guidoc = FreeCADGui.ActiveDocument
|
||||
self.view = self.guidoc.ActiveView
|
||||
self.rm = self.view.getViewer().getSoRenderManager()
|
||||
self.sg = self.view.getSceneGraph()
|
||||
self.setupInteractionSeparator()
|
||||
|
||||
# Callbacks
|
||||
#self.unregister_editing_callbacks()
|
||||
#self.register_editing_callbacks()
|
||||
|
||||
def setupInteractionSeparator(self):
|
||||
if self.root_inserted:
|
||||
self.sg.removeChild(self.root)
|
||||
self.root = graphics.InteractionSeparator(self.rm)
|
||||
self.root.setName("InteractionSeparator")
|
||||
self.root.pick_radius = 40
|
||||
|
||||
# Populate root node
|
||||
self.root += self.points
|
||||
self.build_lines()
|
||||
self.root += self.lines
|
||||
|
||||
# set FreeCAD color scheme
|
||||
for o in self.points + self.lines:
|
||||
o.ovr_col = "yellow"
|
||||
o.sel_col = "green"
|
||||
|
||||
self.root.register()
|
||||
self.sg.addChild(self.root)
|
||||
self.root_inserted = True
|
||||
self.root.selected_objects = list()
|
||||
|
||||
def build_lines(self):
|
||||
for i in range(len(self.points) - 1):
|
||||
line = ConnectionLine([self.points[i], self.points[i + 1]])
|
||||
line.set_color("blue")
|
||||
self.lines.append(line)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# SCENE EVENTS CALLBACKS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def register_editing_callbacks(self):
|
||||
""" Register editing callbacks (former action function) """
|
||||
|
||||
if self._keyPressedCB is None:
|
||||
self._keyPressedCB = self.root.events.addEventCallback(coin.SoKeyboardEvent.getClassTypeId(), self.keyPressed)
|
||||
|
||||
if self._mousePressedCB is None:
|
||||
self._mousePressedCB = self.root.events.addEventCallback(coin.SoMouseButtonEvent.getClassTypeId(), self.mousePressed)
|
||||
|
||||
if self._mouseMovedCB is None:
|
||||
self._mouseMovedCB = self.root.events.addEventCallback(coin.SoLocation2Event.getClassTypeId(), self.mouseMoved)
|
||||
|
||||
def unregister_editing_callbacks(self):
|
||||
""" Remove callbacks used during editing if they exist """
|
||||
|
||||
if self._keyPressedCB:
|
||||
self.root.events.removeEventCallback(coin.SoKeyboardEvent.getClassTypeId(), self._keyPressedCB)
|
||||
self._keyPressedCB = None
|
||||
|
||||
if self._mousePressedCB:
|
||||
self.root.events.removeEventCallback(coin.SoMouseButtonEvent.getClassTypeId(), self._mousePressedCB)
|
||||
self._mousePressedCB = None
|
||||
|
||||
if self._mouseMovedCB:
|
||||
self.root.events.removeEventCallback(coin.SoLocation2Event.getClassTypeId(), self._mouseMovedCB)
|
||||
self._mouseMovedCB = None
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# SCENE EVENT HANDLERS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def keyPressed(self, attr, event_callback):
|
||||
event = event_callback.getEvent()
|
||||
if event.getState() == event.UP:
|
||||
#FreeCAD.Console.PrintMessage("Key pressed : %s\n"%event.getKey())
|
||||
if event.getKey() == ord("i"):
|
||||
self.subdivide()
|
||||
elif event.getKey() == ord("q"):# or event.getKey() == ord(65307):
|
||||
if self.obj:
|
||||
self.obj.ViewObject.Proxy.doubleClicked(self.obj.ViewObject)
|
||||
else:
|
||||
self.quit()
|
||||
elif event.getKey() == ord("s"):
|
||||
sel = FreeCADGui.Selection.getSelectionEx()
|
||||
tup = None
|
||||
if len(sel) == 1:
|
||||
tup = (sel[0].Object, sel[0].SubElementNames)
|
||||
for i in range(len(self.root.selected_objects)):
|
||||
if isinstance(self.root.selected_objects[i], MarkerOnShape):
|
||||
self.root.selected_objects[i].sublink = tup
|
||||
#FreeCAD.Console.PrintMessage("Snapped to {}\n".format(str(self.root.selected_objects[i].sublink)))
|
||||
self.root.selected_objects[i].drag_start()
|
||||
self.root.selected_objects[i].drag((0, 0, 0.))
|
||||
self.root.selected_objects[i].drag_release()
|
||||
elif (event.getKey() == 65535) or (event.getKey() == 65288): # Suppr or Backspace
|
||||
# FreeCAD.Console.PrintMessage("Some objects have been deleted\n")
|
||||
pts = list()
|
||||
for o in self.root.dynamic_objects:
|
||||
if isinstance(o, MarkerOnShape):
|
||||
pts.append(o)
|
||||
self.points = pts
|
||||
self.setupInteractionSeparator()
|
||||
|
||||
def mousePressed(self, attr, event_callback):
|
||||
""" Mouse button event handler, calls: startEditing, endEditing, addPoint, delPoint """
|
||||
event = event_callback.getEvent()
|
||||
if (event.getState() == coin.SoMouseButtonEvent.DOWN) and (event.getButton() == event.BUTTON1): # left click
|
||||
if not event.wasAltDown():
|
||||
''' do something '''
|
||||
if self.editing is None:
|
||||
''' do something'''
|
||||
else:
|
||||
self.endEditing(self.obj, self.editing)
|
||||
|
||||
elif event.wasAltDown(): # left click with ctrl down
|
||||
self.display_tracker_menu(event)
|
||||
|
||||
elif (event.getState() == coin.SoMouseButtonEvent.DOWN) and (event.getButton() == event.BUTTON2): # right click
|
||||
self.display_tracker_menu(event)
|
||||
|
||||
def mouseMoved(self, attr, event_callback):
|
||||
""" Execute as callback for mouse movement. Update tracker position and update preview ghost. """
|
||||
|
||||
event = event_callback.getEvent()
|
||||
pos = event.getPosition()
|
||||
|
||||
'''
|
||||
if self.editing is not None:
|
||||
self.updateTrackerAndGhost(event)
|
||||
else:
|
||||
# look for a node in mouse position and highlight it
|
||||
pos = event.getPosition()
|
||||
node = self.getEditNode(pos)
|
||||
ep = self.getEditNodeIndex(node)
|
||||
if ep is not None:
|
||||
if self.overNode is not None:
|
||||
self.overNode.setColor(COLORS["default"])
|
||||
self.trackers[str(node.objectName.getValue())][ep].setColor(COLORS["red"])
|
||||
self.overNode = self.trackers[str(node.objectName.getValue())][ep]
|
||||
print("show menu")
|
||||
# self.display_tracker_menu(event)
|
||||
else:
|
||||
if self.overNode is not None:
|
||||
self.overNode.setColor(COLORS["default"])
|
||||
self.overNode = None
|
||||
'''
|
||||
|
||||
def endEditing(self, obj, nodeIndex=None, v=None):
|
||||
self.editing = None
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
# DRAFT EDIT Context menu
|
||||
# ------------------------------------------------------------------------
|
||||
|
||||
def display_tracker_menu(self, event):
|
||||
self.tracker_menu = QtGui.QMenu()
|
||||
self.event = event
|
||||
actions = None
|
||||
actions = ["add point"]
|
||||
|
||||
'''
|
||||
if self.overNode:
|
||||
# if user is over a node
|
||||
doc = self.overNode.get_doc_name()
|
||||
obj = App.getDocument(doc).getObject(self.overNode.get_obj_name())
|
||||
ep = self.overNode.get_subelement_index()
|
||||
|
||||
obj_gui_tools = self.get_obj_gui_tools(obj)
|
||||
if obj_gui_tools:
|
||||
actions = obj_gui_tools.get_edit_point_context_menu(obj, ep)
|
||||
|
||||
else:
|
||||
# try if user is over an edited object
|
||||
pos = self.event.getPosition()
|
||||
obj = self.get_selected_obj_at_position(pos)
|
||||
if utils.get_type(obj) in ["Line", "Wire", "BSpline", "BezCurve"]:
|
||||
actions = ["add point"]
|
||||
elif utils.get_type(obj) in ["Circle"] and obj.FirstAngle != obj.LastAngle:
|
||||
actions = ["invert arc"]
|
||||
|
||||
if actions is None:
|
||||
return
|
||||
'''
|
||||
for a in actions:
|
||||
self.tracker_menu.addAction(a)
|
||||
|
||||
self.tracker_menu.popup(FreeCADGui.getMainWindow().cursor().pos())
|
||||
QtCore.QObject.connect(self.tracker_menu,
|
||||
QtCore.SIGNAL("triggered(QAction *)"),
|
||||
self.evaluate_menu_action)
|
||||
|
||||
def evaluate_menu_action(self, labelname):
|
||||
action_label = str(labelname.text())
|
||||
|
||||
doc = None
|
||||
obj = None
|
||||
idx = None
|
||||
|
||||
if action_label == "add point":
|
||||
self.addPoint(self.event)
|
||||
|
||||
del self.event
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# EDIT functions
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def addPoint(self, event):
|
||||
''' add point to the end '''
|
||||
pos = event.getPosition()
|
||||
pts = self.points.copy()
|
||||
new_select = list()
|
||||
point = FreeCAD.Vector(pos)
|
||||
mark = MarkerOnShape([point])
|
||||
pts.append(mark)
|
||||
new_select.append(mark)
|
||||
self.points = pts
|
||||
self.setupInteractionSeparator()
|
||||
self.root.selected_objects = new_select
|
||||
|
||||
def subdivide(self):
|
||||
# get selected lines and subdivide them
|
||||
pts = list()
|
||||
new_select = list()
|
||||
for o in self.lines:
|
||||
#FreeCAD.Console.PrintMessage("object %s\n"%str(o))
|
||||
if isinstance(o, ConnectionLine):
|
||||
pts.append(o.markers[0])
|
||||
if o in self.root.selected_objects:
|
||||
#idx = self.lines.index(o)
|
||||
#FreeCAD.Console.PrintMessage("Subdividing line #{}\n".format(idx))
|
||||
p1 = o.markers[0].points[0]
|
||||
p2 = o.markers[1].points[0]
|
||||
midpar = (FreeCAD.Vector(p1) + FreeCAD.Vector(p2)) / 2.0
|
||||
mark = MarkerOnShape([midpar])
|
||||
pts.append(mark)
|
||||
new_select.append(mark)
|
||||
pts.append(self.points[-1])
|
||||
self.points = pts
|
||||
self.setupInteractionSeparator()
|
||||
self.root.selected_objects = new_select
|
||||
return True
|
||||
|
||||
def quit(self):
|
||||
self.unregister_editing_callbacks()
|
||||
self.root.unregister()
|
||||
self.sg.removeChild(self.root)
|
||||
self.root_inserted = False
|
||||
@@ -1,533 +0,0 @@
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import Part
|
||||
from freecad.Curves import graphics
|
||||
from pivy import coin
|
||||
|
||||
|
||||
# from graphics import COLORS
|
||||
# FreeCAD.Console.PrintMessage("Using local Pivy.graphics library\n")
|
||||
|
||||
|
||||
def parameterization(points, a, closed):
|
||||
"""Computes a knot Sequence for a set of points
|
||||
fac (0-1) : parameterization factor
|
||||
fac=0 -> Uniform / fac=0.5 -> Centripetal / fac=1.0 -> Chord-Length"""
|
||||
pts = points.copy()
|
||||
if closed and pts[0].distanceToPoint(pts[-1]) > 1e-7: # we need to add the first point as the end point
|
||||
pts.append(pts[0])
|
||||
params = [0]
|
||||
for i in range(1, len(pts)):
|
||||
p = pts[i] - pts[i - 1]
|
||||
if isinstance(p, FreeCAD.Vector):
|
||||
le = p.Length
|
||||
else:
|
||||
le = p.length()
|
||||
pl = pow(le, a)
|
||||
params.append(params[-1] + pl)
|
||||
return params
|
||||
|
||||
|
||||
class ConnectionMarker(graphics.Marker):
|
||||
def __init__(self, points):
|
||||
super(ConnectionMarker, self).__init__(points, True)
|
||||
|
||||
|
||||
class MarkerOnShape(graphics.Marker):
|
||||
def __init__(self, points, sh=None):
|
||||
super(MarkerOnShape, self).__init__(points, True)
|
||||
self._shape = None
|
||||
self._sublink = None
|
||||
self._tangent = None
|
||||
self._translate = coin.SoTranslation()
|
||||
self._text_font = coin.SoFont()
|
||||
self._text_font.name = "Arial:Bold"
|
||||
self._text_font.size = 13.0
|
||||
self._text = coin.SoText2()
|
||||
self._text_switch = coin.SoSwitch()
|
||||
self._text_switch.addChild(self._translate)
|
||||
self._text_switch.addChild(self._text_font)
|
||||
self._text_switch.addChild(self._text)
|
||||
self.on_drag_start.append(self.add_text)
|
||||
self.on_drag_release.append(self.remove_text)
|
||||
self.addChild(self._text_switch)
|
||||
if isinstance(sh, Part.Shape):
|
||||
self.snap_shape = sh
|
||||
elif isinstance(sh, (tuple, list)):
|
||||
self.sublink = sh
|
||||
|
||||
def subshape_from_sublink(self, o):
|
||||
name = o[1][0]
|
||||
if 'Vertex' in name:
|
||||
n = eval(name.lstrip('Vertex'))
|
||||
return(o[0].Shape.Vertexes[n - 1])
|
||||
elif 'Edge' in name:
|
||||
n = eval(name.lstrip('Edge'))
|
||||
return(o[0].Shape.Edges[n - 1])
|
||||
elif 'Face' in name:
|
||||
n = eval(name.lstrip('Face'))
|
||||
return(o[0].Shape.Faces[n - 1])
|
||||
|
||||
def add_text(self):
|
||||
self._text_switch.whichChild = coin.SO_SWITCH_ALL
|
||||
self.on_drag.append(self.update_text)
|
||||
|
||||
def remove_text(self):
|
||||
self._text_switch.whichChild = coin.SO_SWITCH_NONE
|
||||
self.on_drag.remove(self.update_text)
|
||||
|
||||
def update_text(self):
|
||||
p = self.points[0]
|
||||
coords = ['{: 9.3f}'.format(p[0]), '{: 9.3f}'.format(p[1]), '{: 9.3f}'.format(p[2])]
|
||||
self._translate.translation = p
|
||||
self._text.string.setValues(0, 3, coords)
|
||||
|
||||
@property
|
||||
def tangent(self):
|
||||
return self._tangent
|
||||
|
||||
@tangent.setter
|
||||
def tangent(self, t):
|
||||
if isinstance(t, FreeCAD.Vector):
|
||||
if t.Length > 1e-7:
|
||||
self._tangent = t
|
||||
self._tangent.normalize()
|
||||
self.marker.markerIndex = coin.SoMarkerSet.DIAMOND_FILLED_9_9
|
||||
else:
|
||||
self._tangent = None
|
||||
self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9
|
||||
else:
|
||||
self._tangent = None
|
||||
self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9
|
||||
|
||||
@property
|
||||
def snap_shape(self):
|
||||
return self._shape
|
||||
|
||||
@snap_shape.setter
|
||||
def snap_shape(self, sh):
|
||||
if isinstance(sh, Part.Shape):
|
||||
self._shape = sh
|
||||
else:
|
||||
self._shape = None
|
||||
self.alter_color()
|
||||
|
||||
@property
|
||||
def sublink(self):
|
||||
return self._sublink
|
||||
|
||||
@sublink.setter
|
||||
def sublink(self, sl):
|
||||
if isinstance(sl, (tuple, list)) and not (sl == self._sublink):
|
||||
self._shape = self.subshape_from_sublink(sl)
|
||||
self._sublink = sl
|
||||
else:
|
||||
self._shape = None
|
||||
self._sublink = None
|
||||
self.alter_color()
|
||||
|
||||
def alter_color(self):
|
||||
if isinstance(self._shape, Part.Vertex):
|
||||
self.set_color("white")
|
||||
elif isinstance(self._shape, Part.Edge):
|
||||
self.set_color("cyan")
|
||||
elif isinstance(self._shape, Part.Face):
|
||||
self.set_color("magenta")
|
||||
else:
|
||||
self.set_color("black")
|
||||
|
||||
def __repr__(self):
|
||||
return("MarkerOnShape({})".format(self._shape))
|
||||
|
||||
def drag(self, mouse_coords, fact=1.):
|
||||
if self.enabled:
|
||||
pts = self.points
|
||||
for i, p in enumerate(pts):
|
||||
p[0] = mouse_coords[0] * fact + self._tmp_points[i][0]
|
||||
p[1] = mouse_coords[1] * fact + self._tmp_points[i][1]
|
||||
p[2] = mouse_coords[2] * fact + self._tmp_points[i][2]
|
||||
if self._shape:
|
||||
v = Part.Vertex(p[0], p[1], p[2])
|
||||
proj = v.distToShape(self._shape)[1][0][1]
|
||||
# FreeCAD.Console.PrintMessage("%s -> %s\n"%(p.getValue(), proj))
|
||||
p[0] = proj.x
|
||||
p[1] = proj.y
|
||||
p[2] = proj.z
|
||||
self.points = pts
|
||||
for foo in self.on_drag:
|
||||
foo()
|
||||
|
||||
|
||||
class ConnectionPolygon(graphics.Polygon):
|
||||
std_col = "green"
|
||||
|
||||
def __init__(self, markers):
|
||||
super(ConnectionPolygon, self).__init__(
|
||||
sum([m.points for m in markers], []), True)
|
||||
self.markers = markers
|
||||
|
||||
for m in self.markers:
|
||||
m.on_drag.append(self.updatePolygon)
|
||||
|
||||
def updatePolygon(self):
|
||||
self.points = sum([m.points for m in self.markers], [])
|
||||
|
||||
@property
|
||||
def drag_objects(self):
|
||||
return self.markers
|
||||
|
||||
def check_dependency(self):
|
||||
if any([m._delete for m in self.markers]):
|
||||
self.delete()
|
||||
|
||||
|
||||
class ConnectionLine(graphics.Line):
|
||||
def __init__(self, markers):
|
||||
super(ConnectionLine, self).__init__(
|
||||
sum([m.points for m in markers], []), True)
|
||||
self.markers = markers
|
||||
self._linear = False
|
||||
for m in self.markers:
|
||||
m.on_drag.append(self.updateLine)
|
||||
|
||||
def updateLine(self):
|
||||
self.points = sum([m.points for m in self.markers], [])
|
||||
if self._linear:
|
||||
p1 = self.markers[0].points[0]
|
||||
p2 = self.markers[-1].points[0]
|
||||
t = p2 - p1
|
||||
tan = FreeCAD.Vector(t[0], t[1], t[2])
|
||||
for m in self.markers:
|
||||
m.tangent = tan
|
||||
|
||||
@property
|
||||
def linear(self):
|
||||
return self._linear
|
||||
|
||||
@linear.setter
|
||||
def linear(self, b):
|
||||
self._linear = bool(b)
|
||||
|
||||
@property
|
||||
def drag_objects(self):
|
||||
return self.markers
|
||||
|
||||
def check_dependency(self):
|
||||
if any([m._delete for m in self.markers]):
|
||||
self.delete()
|
||||
|
||||
|
||||
class InterpoCurveEditor(object):
|
||||
"""Interpolation curve free-hand editor
|
||||
my_editor = InterpoCurveEditor([points], obj)
|
||||
obj is the FreeCAD object that will receive
|
||||
the curve shape at the end of editing.
|
||||
points can be :
|
||||
- Vector (free point)
|
||||
- (Vector, shape) (point on shape)"""
|
||||
def __init__(self, points=[], fp=None):
|
||||
self.points = list()
|
||||
self.curve = Part.BSplineCurve()
|
||||
self.fp = fp
|
||||
self.root_inserted = False
|
||||
self.periodic = False
|
||||
self.param_factor = 1.0
|
||||
# self.support = None # Not yet implemented
|
||||
for p in points:
|
||||
if isinstance(p, FreeCAD.Vector):
|
||||
self.points.append(MarkerOnShape([p]))
|
||||
elif isinstance(p, (tuple, list)):
|
||||
self.points.append(MarkerOnShape([p[0]], p[1]))
|
||||
elif isinstance(p, (MarkerOnShape, ConnectionMarker)):
|
||||
self.points.append(p)
|
||||
else:
|
||||
FreeCAD.Console.PrintError("InterpoCurveEditor : bad input")
|
||||
# Setup coin objects
|
||||
if self.fp:
|
||||
self.guidoc = self.fp.ViewObject.Document
|
||||
else:
|
||||
if not FreeCADGui.ActiveDocument:
|
||||
FreeCAD.newDocument("New")
|
||||
self.guidoc = FreeCADGui.ActiveDocument
|
||||
self.view = self.guidoc.ActiveView
|
||||
self.rm = self.view.getViewer().getSoRenderManager()
|
||||
self.sg = self.view.getSceneGraph()
|
||||
self.setup_InteractionSeparator()
|
||||
self.update_curve()
|
||||
|
||||
def setup_InteractionSeparator(self):
|
||||
if self.root_inserted:
|
||||
self.sg.removeChild(self.root)
|
||||
self.root = graphics.InteractionSeparator(self.rm)
|
||||
self.root.setName("InteractionSeparator")
|
||||
# self.root.ovr_col = "yellow"
|
||||
# self.root.sel_col = "green"
|
||||
self.root.pick_radius = 40
|
||||
self.root.on_drag.append(self.update_curve)
|
||||
# Keyboard callback
|
||||
# self.events = coin.SoEventCallback()
|
||||
self._controlCB = self.root.events.addEventCallback(coin.SoKeyboardEvent.getClassTypeId(), self.controlCB)
|
||||
# populate root node
|
||||
# self.root.addChild(self.events)
|
||||
self.root += self.points
|
||||
self.build_lines()
|
||||
self.root += self.lines
|
||||
# set FreeCAD color scheme
|
||||
for o in self.points + self.lines:
|
||||
o.ovr_col = "yellow"
|
||||
o.sel_col = "green"
|
||||
self.root.register()
|
||||
self.sg.addChild(self.root)
|
||||
self.root_inserted = True
|
||||
self.root.selected_objects = list()
|
||||
|
||||
def compute_tangents(self):
|
||||
tans = list()
|
||||
flags = list()
|
||||
for i in range(len(self.points)):
|
||||
if isinstance(self.points[i].snap_shape, Part.Face):
|
||||
for vec in self.points[i].points:
|
||||
u, v = self.points[i].snap_shape.Surface.parameter(FreeCAD.Vector(vec))
|
||||
norm = self.points[i].snap_shape.normalAt(u, v)
|
||||
cp = self.curve.parameter(FreeCAD.Vector(vec))
|
||||
t = self.curve.tangent(cp)[0]
|
||||
pl = Part.Plane(FreeCAD.Vector(), norm)
|
||||
ci = Part.Geom2d.Circle2d()
|
||||
ci.Radius = t.Length * 2
|
||||
w = Part.Wire([ci.toShape(pl)])
|
||||
f = Part.Face(w)
|
||||
# proj = f.project([Part.Vertex(t)])
|
||||
proj = Part.Vertex(t).distToShape(f)[1][0][1]
|
||||
# pt = proj.Vertexes[0].Point
|
||||
# FreeCAD.Console.PrintMessage("Projection %s -> %s\n"%(t, proj))
|
||||
if proj.Length > 1e-7:
|
||||
tans.append(proj)
|
||||
flags.append(True)
|
||||
else:
|
||||
tans.append(FreeCAD.Vector(1, 0, 0))
|
||||
flags.append(False)
|
||||
elif self.points[i].tangent:
|
||||
for j in range(len(self.points[i].points)):
|
||||
tans.append(self.points[i].tangent)
|
||||
flags.append(True)
|
||||
else:
|
||||
for j in range(len(self.points[i].points)):
|
||||
tans.append(FreeCAD.Vector(0, 0, 0))
|
||||
flags.append(False)
|
||||
return(tans, flags)
|
||||
|
||||
def update_curve(self):
|
||||
pts = list()
|
||||
for p in self.points:
|
||||
pts += p.points
|
||||
# FreeCAD.Console.PrintMessage("pts :\n%s\n"%str(pts))
|
||||
if len(pts) > 1:
|
||||
fac = self.param_factor
|
||||
if self.fp:
|
||||
fac = self.fp.Parametrization
|
||||
params = parameterization(pts, fac, self.periodic)
|
||||
self.curve.interpolate(Points=pts, Parameters=params, PeriodicFlag=self.periodic)
|
||||
tans, flags = self.compute_tangents()
|
||||
if any(flags):
|
||||
if (len(tans) == len(pts)) and (len(flags) == len(pts)):
|
||||
self.curve.interpolate(Points=pts, Parameters=params, PeriodicFlag=self.periodic, Tangents=tans, TangentFlags=flags)
|
||||
if self.fp:
|
||||
self.fp.Shape = self.curve.toShape()
|
||||
|
||||
def build_lines(self):
|
||||
self.lines = list()
|
||||
for i in range(len(self.points) - 1):
|
||||
line = ConnectionLine([self.points[i], self.points[i + 1]])
|
||||
line.set_color("blue")
|
||||
self.lines.append(line)
|
||||
|
||||
def controlCB(self, attr, event_callback):
|
||||
event = event_callback.getEvent()
|
||||
if event.getState() == event.UP:
|
||||
# FreeCAD.Console.PrintMessage("Key pressed : %s\n"%event.getKey())
|
||||
if event.getKey() == ord("i"):
|
||||
self.subdivide()
|
||||
elif event.getKey() == ord("p"):
|
||||
self.set_planar()
|
||||
elif event.getKey() == ord("t"):
|
||||
self.set_tangents()
|
||||
elif event.getKey() == ord("q"):
|
||||
if self.fp:
|
||||
self.fp.ViewObject.Proxy.doubleClicked(self.fp.ViewObject)
|
||||
else:
|
||||
self.quit()
|
||||
elif event.getKey() == ord("s"):
|
||||
sel = FreeCADGui.Selection.getSelectionEx()
|
||||
tup = None
|
||||
if len(sel) == 1:
|
||||
tup = (sel[0].Object, sel[0].SubElementNames)
|
||||
for i in range(len(self.root.selected_objects)):
|
||||
if isinstance(self.root.selected_objects[i], MarkerOnShape):
|
||||
self.root.selected_objects[i].sublink = tup
|
||||
FreeCAD.Console.PrintMessage("Snapped to {}\n".format(str(self.root.selected_objects[i].sublink)))
|
||||
self.root.selected_objects[i].drag_start()
|
||||
self.root.selected_objects[i].drag((0, 0, 0.))
|
||||
self.root.selected_objects[i].drag_release()
|
||||
self.update_curve()
|
||||
elif event.getKey() == ord("l"):
|
||||
self.toggle_linear()
|
||||
elif (event.getKey() == 65535) or (event.getKey() == 65288): # Suppr or Backspace
|
||||
# FreeCAD.Console.PrintMessage("Some objects have been deleted\n")
|
||||
pts = list()
|
||||
for o in self.root.dynamic_objects:
|
||||
if isinstance(o, MarkerOnShape):
|
||||
pts.append(o)
|
||||
self.points = pts
|
||||
self.setup_InteractionSeparator()
|
||||
self.update_curve()
|
||||
|
||||
def toggle_linear(self):
|
||||
for o in self.root.selected_objects:
|
||||
if isinstance(o, ConnectionLine):
|
||||
o.linear = not o.linear
|
||||
i = self.lines.index(o)
|
||||
if i > 0:
|
||||
self.lines[i - 1].linear = False
|
||||
if i < len(self.lines) - 1:
|
||||
self.lines[i + 1].linear = False
|
||||
o.updateLine()
|
||||
o.drag_start()
|
||||
o.drag((0, 0, 0.00001))
|
||||
o.drag_release()
|
||||
self.update_curve()
|
||||
|
||||
def set_tangents(self):
|
||||
# view_dir = FreeCAD.Vector(0, 0, 1)
|
||||
view_dir = FreeCADGui.ActiveDocument.ActiveView.getViewDirection()
|
||||
markers = list()
|
||||
for o in self.root.selected_objects:
|
||||
if isinstance(o, MarkerOnShape):
|
||||
markers.append(o)
|
||||
elif isinstance(o, ConnectionLine):
|
||||
markers.extend(o.markers)
|
||||
if len(markers) > 0:
|
||||
for m in markers:
|
||||
if m.tangent:
|
||||
m.tangent = None
|
||||
else:
|
||||
i = self.points.index(m)
|
||||
if i == 0:
|
||||
m.tangent = -view_dir
|
||||
else:
|
||||
m.tangent = view_dir
|
||||
self.update_curve()
|
||||
|
||||
def set_planar(self):
|
||||
# view_dir = FreeCAD.Vector(0, 0, 1)
|
||||
view_dir = FreeCADGui.ActiveDocument.ActiveView.getViewDirection()
|
||||
markers = list()
|
||||
for o in self.root.selected_objects:
|
||||
if isinstance(o, MarkerOnShape):
|
||||
markers.append(o)
|
||||
elif isinstance(o, ConnectionLine):
|
||||
markers.extend(o.markers)
|
||||
if len(markers) > 2:
|
||||
vec0 = markers[0].points[0]
|
||||
vec1 = markers[-1].points[0]
|
||||
p0 = FreeCAD.Vector(vec0[0], vec0[1], vec0[2])
|
||||
p1 = FreeCAD.Vector(vec1[0], vec1[1], vec1[2])
|
||||
pl = Part.Plane(p0, p1, p1 + view_dir)
|
||||
for o in markers:
|
||||
if isinstance(o.snap_shape, Part.Vertex):
|
||||
FreeCAD.Console.PrintMessage("Snapped to Vertex\n")
|
||||
elif isinstance(o.snap_shape, Part.Edge):
|
||||
FreeCAD.Console.PrintMessage("Snapped to Edge\n")
|
||||
c = o.snap_shape.Curve
|
||||
pts = pl.intersect(c)[0]
|
||||
new_pts = list()
|
||||
for ip in o.points:
|
||||
iv = FreeCAD.Vector(ip[0], ip[1], ip[2])
|
||||
dmin = 1e50
|
||||
new = None
|
||||
for op in pts:
|
||||
ov = FreeCAD.Vector(op.X, op.Y, op.Z)
|
||||
if iv.distanceToPoint(ov) < dmin:
|
||||
dmin = iv.distanceToPoint(ov)
|
||||
new = ov
|
||||
new_pts.append(new)
|
||||
o.points = new_pts
|
||||
elif isinstance(o.snap_shape, Part.Face):
|
||||
FreeCAD.Console.PrintMessage("Snapped to Face\n")
|
||||
s = o.snap_shape.Surface
|
||||
cvs = pl.intersect(s)
|
||||
new_pts = list()
|
||||
for ip in o.points:
|
||||
iv = Part.Vertex(FreeCAD.Vector(ip[0], ip[1], ip[2]))
|
||||
dmin = 1e50
|
||||
new = None
|
||||
for c in cvs:
|
||||
e = c.toShape()
|
||||
d, pts, info = iv.distToShape(e)
|
||||
if d < dmin:
|
||||
dmin = d
|
||||
new = pts[0][1]
|
||||
new_pts.append(new)
|
||||
o.points = new_pts
|
||||
else:
|
||||
FreeCAD.Console.PrintMessage("Not snapped\n")
|
||||
new_pts = list()
|
||||
for ip in o.points:
|
||||
iv = FreeCAD.Vector(ip[0], ip[1], ip[2])
|
||||
u, v = pl.parameter(iv)
|
||||
new_pts.append(pl.value(u, v))
|
||||
o.points = new_pts
|
||||
for li in self.lines:
|
||||
li.updateLine()
|
||||
self.update_curve()
|
||||
|
||||
def subdivide(self):
|
||||
# get selected lines and subdivide them
|
||||
pts = list()
|
||||
new_select = list()
|
||||
for o in self.lines:
|
||||
# FreeCAD.Console.PrintMessage("object %s\n"%str(o))
|
||||
if isinstance(o, ConnectionLine):
|
||||
pts.append(o.markers[0])
|
||||
if o in self.root.selected_objects:
|
||||
idx = self.lines.index(o)
|
||||
FreeCAD.Console.PrintMessage("Subdividing line #{}\n".format(idx))
|
||||
p1 = o.markers[0].points[0]
|
||||
p2 = o.markers[1].points[0]
|
||||
par1 = self.curve.parameter(FreeCAD.Vector(p1))
|
||||
par2 = self.curve.parameter(FreeCAD.Vector(p2))
|
||||
midpar = (par1 + par2) / 2.0
|
||||
mark = MarkerOnShape([self.curve.value(midpar)])
|
||||
pts.append(mark)
|
||||
new_select.append(mark)
|
||||
pts.append(self.points[-1])
|
||||
self.points = pts
|
||||
self.setup_InteractionSeparator()
|
||||
self.root.selected_objects = new_select
|
||||
self.update_curve()
|
||||
return(True)
|
||||
|
||||
def quit(self):
|
||||
self.root.events.removeEventCallback(coin.SoKeyboardEvent.getClassTypeId(), self._controlCB)
|
||||
self.root.unregister()
|
||||
# self.root.removeAllChildren()
|
||||
self.sg.removeChild(self.root)
|
||||
self.root_inserted = False
|
||||
|
||||
|
||||
def get_guide_params():
|
||||
sel = FreeCADGui.Selection.getSelectionEx()
|
||||
pts = list()
|
||||
for s in sel:
|
||||
pts.extend(list(zip(s.PickedPoints, s.SubObjects)))
|
||||
return(pts)
|
||||
|
||||
|
||||
def main():
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::Spline", "profile")
|
||||
tups = get_guide_params()
|
||||
InterpoCurveEditor(tups, obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,305 @@
|
||||
|
||||
import math
|
||||
|
||||
import ArchComponent
|
||||
import FreeCAD
|
||||
import Part
|
||||
import random
|
||||
from FreeCAD import Qt
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
try:
|
||||
from scipy import spatial
|
||||
has_scipy = True
|
||||
except ImportError:
|
||||
has_scipy = False
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore, QtGui
|
||||
from DraftTools import translate
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
import Part
|
||||
import os
|
||||
else:
|
||||
# \cond
|
||||
def translate(ctxt, txt):
|
||||
return txt
|
||||
|
||||
def QT_TRANSLATE_NOOP(ctxt, txt):
|
||||
return txt
|
||||
# \endcond
|
||||
|
||||
__title__ = "FreeCAD Fixed Rack"
|
||||
__author__ = "Javier Braña"
|
||||
__url__ = "http://www.sogos-solar.com"
|
||||
|
||||
__dir__ = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", "PVPlant")
|
||||
DirResources = os.path.join(__dir__, "../Resources")
|
||||
DirIcons = os.path.join(DirResources, "Icons")
|
||||
DirImages = os.path.join(DirResources, "Images")
|
||||
|
||||
|
||||
def makeTree():
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Tree")
|
||||
Tree(obj)
|
||||
ViewProviderTree(obj.ViewObject)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
try:
|
||||
folder = FreeCAD.ActiveDocument.Vegetation
|
||||
except:
|
||||
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Vegetation')
|
||||
folder.Label = "Vegetation"
|
||||
folder.addObject(obj)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
class Tree(ArchComponent.Component):
|
||||
"""A parametric tree object for architectural design"""
|
||||
|
||||
def __init__(self, obj):
|
||||
ArchComponent.Component.__init__(self, obj)
|
||||
self.obj = obj
|
||||
self.setProperties(obj)
|
||||
random.seed(42) # Semilla para resultados consistentes
|
||||
|
||||
def setProperties(self, obj):
|
||||
"""Define y configura las propiedades del objeto"""
|
||||
pl = obj.PropertiesList
|
||||
|
||||
# Propiedades de la copa
|
||||
canopy_props = [
|
||||
("CanopyHeight", "App::PropertyLength", "Altura total de la copa"),
|
||||
("CanopyRadius", "App::PropertyLength", "Radio máximo de la copa"),
|
||||
("Spikiness", "App::PropertyFloatConstraint", "Irregularidad de la superficie", (0.5, 0.0, 1.0, 0.05)),
|
||||
(
|
||||
"CrownExpansion", "App::PropertyFloatConstraint", "Expansión de la corona superior", (1.0, 0.0, 2.0, 0.05)),
|
||||
("UmbrellaEffect", "App::PropertyFloatConstraint", "Efecto de dosel/paraguas", (0.0, 0.0, 1.0, 0.05)),
|
||||
("LeafCount", "App::PropertyInteger", "Densidad de follaje (número de segmentos)")
|
||||
]
|
||||
|
||||
for prop in canopy_props:
|
||||
name, ptype, doc, *args = prop
|
||||
if name not in pl:
|
||||
if ptype == "App::PropertyFloatConstraint":
|
||||
obj.addProperty(ptype, name, "Canopy", doc).__setattr__(name, args[0])
|
||||
else:
|
||||
obj.addProperty(ptype, name, "Canopy", doc)
|
||||
# Valores por defecto
|
||||
if name == "LeafCount":
|
||||
setattr(obj, name, 20)
|
||||
elif name in ["CanopyHeight", "CanopyRadius"]:
|
||||
setattr(obj, name, 4000 if "Height" in name else 1500)
|
||||
|
||||
# Propiedades del tronco
|
||||
trunk_props = [
|
||||
("TrunkHeight", "App::PropertyLength", "Altura del tronco", 2000),
|
||||
("TrunkRadius", "App::PropertyLength", "Radio base del tronco", 150),
|
||||
("TrunkFaces", "App::PropertyInteger", "Caras del tronco", 6)
|
||||
]
|
||||
|
||||
for prop in trunk_props:
|
||||
name, ptype, doc, default = prop
|
||||
if name not in pl:
|
||||
obj.addProperty(ptype, name, "Trunk", doc)
|
||||
setattr(obj, name, default)
|
||||
|
||||
# Propiedades base
|
||||
if "Type" not in pl:
|
||||
obj.addProperty("App::PropertyString", "Type", "Base", "Tipo de objeto").Type = "Vegetable-Tree"
|
||||
obj.setEditorMode("Type", 1) # Hacerla de solo lectura
|
||||
|
||||
obj.Proxy = self
|
||||
obj.IfcType = "Shading Device"
|
||||
obj.setEditorMode("IfcType", 1)
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
ArchComponent.Component.onDocumentRestored(self, obj)
|
||||
self.setProperties(obj)
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
"""Actualiza la forma cuando cambian propiedades"""
|
||||
if prop in ["CanopyHeight", "CanopyRadius", "Spikiness", "CrownExpansion",
|
||||
"UmbrellaEffect", "LeafCount", "TrunkHeight", "TrunkRadius", "TrunkFaces"]:
|
||||
self.execute(obj)
|
||||
|
||||
def createTrunk(self, obj):
|
||||
"""Crea la geometría del tronco usando un loft"""
|
||||
try:
|
||||
# Calcula dimensiones proporcionales
|
||||
base_radius = obj.TrunkRadius.Value
|
||||
top_radius = base_radius * 0.8
|
||||
height = obj.TrunkHeight.Value
|
||||
|
||||
# Crea tres perfiles circulares
|
||||
profiles = []
|
||||
for z, radius in [(0, base_radius),
|
||||
(height / 3, base_radius),
|
||||
(height, top_radius)]:
|
||||
circle = Part.makeCircle(radius, FreeCAD.Vector(0, 0, z))
|
||||
profiles.append(Part.Wire([circle]))
|
||||
|
||||
return Part.makeLoft(profiles, True, True)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(f"Error creando tronco: {str(e)}\n")
|
||||
return None
|
||||
|
||||
def createCanopy(self, obj):
|
||||
"""Genera la forma de la copa usando una envoltura convexa"""
|
||||
if not has_scipy:
|
||||
FreeCAD.Console.PrintError("Scipy no está instalado. No se puede generar la copa.\n")
|
||||
return None
|
||||
|
||||
try:
|
||||
# Configuración inicial
|
||||
n_segments = max(3, obj.LeafCount) # Mínimo 3 segmentos
|
||||
radius = obj.CanopyRadius.Value
|
||||
height = obj.CanopyHeight.Value
|
||||
|
||||
# Genera puntos distribuidos esféricamente con ruido
|
||||
points = []
|
||||
for _ in range(n_segments * 10): # 10 puntos por segmento
|
||||
theta = random.uniform(0, 2 * math.pi)
|
||||
phi = math.acos(random.uniform(-1, 1))
|
||||
|
||||
# Aplica parámetros de forma
|
||||
r = radius * (1 - obj.Spikiness * random.random())
|
||||
x = r * math.sin(phi) * math.cos(theta)
|
||||
y = r * math.sin(phi) * math.sin(theta)
|
||||
z = height * (0.5 + 0.5 * math.cos(phi)) # Distribución vertical
|
||||
|
||||
# Aplica efectos de forma
|
||||
z *= (1 - obj.UmbrellaEffect)
|
||||
if z > height / 2:
|
||||
x *= obj.CrownExpansion
|
||||
y *= obj.CrownExpansion
|
||||
|
||||
points.append(FreeCAD.Vector(x, y, z))
|
||||
|
||||
# Crea la envoltura convexa
|
||||
hull = spatial.ConvexHull([(p.x, p.y, p.z) for p in points])
|
||||
faces = []
|
||||
for simplex in hull.simplices:
|
||||
triangle = [points[i] for i in simplex]
|
||||
faces.append(Part.Face(Part.makePolygon(triangle + [triangle[0]])))
|
||||
|
||||
return Part.Compound(faces)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(f"Error creando copa: {str(e)}\n")
|
||||
return None
|
||||
|
||||
def execute(self, obj):
|
||||
"""Ensambla el objeto final"""
|
||||
try:
|
||||
# Crea componentes
|
||||
trunk = self.createTrunk(obj)
|
||||
canopy = self.createCanopy(obj)
|
||||
|
||||
# Verifica componentes válidos
|
||||
if not trunk or not canopy:
|
||||
raise ValueError("Error en la generación de componentes")
|
||||
|
||||
# Posiciona la copa sobre el tronco
|
||||
canopy_placement = FreeCAD.Placement()
|
||||
canopy_placement.Base.z = obj.TrunkHeight.Value
|
||||
canopy.Placement = canopy_placement
|
||||
|
||||
# Combina las formas
|
||||
compound = Part.Compound([trunk, canopy])
|
||||
obj.Shape = compound
|
||||
|
||||
# Configura apariencia
|
||||
if obj.ViewObject:
|
||||
obj.ViewObject.DiffuseColor = ([(0.35, 0.2, 0.05)] * len(trunk.Faces) +
|
||||
[(0.1, 0.6, 0.2)] * len(canopy.Faces)) # Color copa
|
||||
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(f"Error al ejecutar: {str(e)}\n")
|
||||
|
||||
|
||||
class ViewProviderTree(ArchComponent.ViewProviderComponent):
|
||||
"A View Provider for the Pipe object"
|
||||
|
||||
def __init__(self, vobj):
|
||||
ArchComponent.ViewProviderComponent.__init__(self, vobj)
|
||||
|
||||
def getIcon(self):
|
||||
return str(os.path.join(DirIcons, "tree(1).svg"))
|
||||
|
||||
|
||||
class TreeTaskPanel(QtGui.QWidget):
|
||||
def __init__(self, obj=None):
|
||||
QtGui.QWidget.__init__(self)
|
||||
self.obj = obj
|
||||
if self.obj is None:
|
||||
self.obj = makeTree()
|
||||
|
||||
self.form = FreeCADGui.PySideUic.loadUi(__dir__ + "/PVPlantTree.ui")
|
||||
self.layout = QtGui.QHBoxLayout(self)
|
||||
self.layout.setContentsMargins(4, 4, 4, 4)
|
||||
self.layout.addWidget(self.form)
|
||||
|
||||
self.form.editCanopyHeight.valueChanged.connect(self.Canopy)
|
||||
self.form.editCanopyRadius.valueChanged.connect(self.Canopy)
|
||||
self.form.editSpikiness.valueChanged.connect(self.Canopy)
|
||||
self.form.editCrownExpansion.valueChanged.connect(self.Canopy)
|
||||
self.form.editLeftUmbrellaEffect.valueChanged.connect(self.Canopy)
|
||||
self.form.editLeafCount.valueChanged.connect(self.Canopy)
|
||||
|
||||
def Canopy(self):
|
||||
self.obj.CanopyHeight = FreeCAD.Units.Quantity(self.form.editCanopyHeight.text()).Value
|
||||
self.obj.CanopyRadius = FreeCAD.Units.Quantity(self.form.editCanopyRadius.text()).Value
|
||||
self.obj.Spikiness = self.form.editSpikiness.value()
|
||||
self.obj.CrownExpansion = self.form.editCrownExpansion.value()
|
||||
self.obj.UmbrellaEffect = self.form.editLeftUmbrellaEffect.value()
|
||||
self.obj.LeafCount = self.form.editLeafCount.value()
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def accept(self):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return True
|
||||
|
||||
def reject(self):
|
||||
FreeCAD.ActiveDocument.removeObject(self.obj.Name)
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return True
|
||||
|
||||
|
||||
class CommandTree:
|
||||
"the PVPlant Tree command definition"
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "tree(1).svg")),
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("PVPlantTree", "Tree"),
|
||||
'Accel': "S, T",
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PVPlanTree",
|
||||
"Creates a Tree object from setup dialog.")}
|
||||
|
||||
def IsActive(self):
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
def Activated(self):
|
||||
import draftguitools.gui_trackers as DraftTrackers
|
||||
self.tree = makeTree()
|
||||
FreeCADGui.Snapper.getPoint(callback=self.getPoint,
|
||||
movecallback=self.mousemove,
|
||||
extradlg=self.taskbox(),
|
||||
title="Position of the tree:")
|
||||
|
||||
def getPoint(self, point=None, obj=None):
|
||||
self.tree.Placement.Base = point
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
self.tracker.finalize()
|
||||
|
||||
def mousemove(self, pt, snapInfo):
|
||||
self.tree.Placement.Base = pt
|
||||
|
||||
def taskbox(self):
|
||||
self.form = TreeTaskPanel(self.tree)
|
||||
return self.form
|
||||
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
# Script para FreeCAD - Procesador de Documentos Word con Carátula
|
||||
import os
|
||||
import glob
|
||||
from PySide import QtWidgets, QtCore
|
||||
from PySide.QtWidgets import (QFileDialog, QMessageBox, QProgressDialog,
|
||||
QApplication, QVBoxLayout, QWidget, QPushButton,
|
||||
QLabel, QTextEdit)
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
import PVPlantResources
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
try:
|
||||
from docx import Document
|
||||
from docx.shared import Pt, RGBColor, Inches
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.oxml.ns import qn
|
||||
|
||||
DOCX_AVAILABLE = True
|
||||
except ImportError:
|
||||
DOCX_AVAILABLE = False
|
||||
FreeCAD.Console.PrintError("Error: python-docx no está instalado. Instala con: pip install python-docx\n")
|
||||
|
||||
|
||||
class DocumentProcessor(QtWidgets.QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super(DocumentProcessor, self).__init__(parent)
|
||||
self.caratula_path = ""
|
||||
self.carpeta_path = ""
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
self.setWindowTitle("Procesador de Documentos Word")
|
||||
self.setMinimumWidth(600)
|
||||
self.setMinimumHeight(500)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Título
|
||||
title = QLabel("<h2>Procesador de Documentos Word</h2>")
|
||||
title.setAlignment(QtCore.Qt.AlignCenter)
|
||||
layout.addWidget(title)
|
||||
|
||||
# Información
|
||||
info_text = QLabel(
|
||||
"Este script buscará recursivamente todos los archivos .docx en una carpeta,\n"
|
||||
"insertará una carátula y aplicará formato estándar a todos los documentos."
|
||||
)
|
||||
info_text.setAlignment(QtCore.Qt.AlignCenter)
|
||||
layout.addWidget(info_text)
|
||||
|
||||
# Botón seleccionar carátula
|
||||
self.btn_caratula = QPushButton("1. Seleccionar Carátula")
|
||||
self.btn_caratula.clicked.connect(self.seleccionar_caratula)
|
||||
layout.addWidget(self.btn_caratula)
|
||||
|
||||
self.label_caratula = QLabel("No se ha seleccionado carátula")
|
||||
self.label_caratula.setWordWrap(True)
|
||||
layout.addWidget(self.label_caratula)
|
||||
|
||||
# Botón seleccionar carpeta
|
||||
self.btn_carpeta = QPushButton("2. Seleccionar Carpeta de Documentos")
|
||||
self.btn_carpeta.clicked.connect(self.seleccionar_carpeta)
|
||||
layout.addWidget(self.btn_carpeta)
|
||||
|
||||
self.label_carpeta = QLabel("No se ha seleccionado carpeta")
|
||||
self.label_carpeta.setWordWrap(True)
|
||||
layout.addWidget(self.label_carpeta)
|
||||
|
||||
# Botón procesar
|
||||
self.btn_procesar = QPushButton("3. Procesar Documentos")
|
||||
self.btn_procesar.clicked.connect(self.procesar_documentos)
|
||||
self.btn_procesar.setEnabled(False)
|
||||
layout.addWidget(self.btn_procesar)
|
||||
|
||||
# Área de log
|
||||
self.log_area = QTextEdit()
|
||||
self.log_area.setReadOnly(True)
|
||||
layout.addWidget(self.log_area)
|
||||
|
||||
# Botón cerrar
|
||||
self.btn_cerrar = QPushButton("Cerrar")
|
||||
self.btn_cerrar.clicked.connect(self.close)
|
||||
layout.addWidget(self.btn_cerrar)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def log(self, mensaje):
|
||||
"""Agrega un mensaje al área de log"""
|
||||
self.log_area.append(mensaje)
|
||||
QApplication.processEvents() # Para actualizar la UI
|
||||
|
||||
def seleccionar_caratula(self):
|
||||
"""Abre un diálogo para seleccionar el archivo de carátula"""
|
||||
archivo, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Seleccionar archivo de carátula",
|
||||
"",
|
||||
"Word documents (*.docx);;All files (*.*)"
|
||||
)
|
||||
|
||||
if archivo and os.path.exists(archivo):
|
||||
self.caratula_path = archivo
|
||||
self.label_caratula.setText(f"Carátula: {os.path.basename(archivo)}")
|
||||
self.verificar_estado()
|
||||
self.log(f"✓ Carátula seleccionada: {archivo}")
|
||||
|
||||
def seleccionar_carpeta(self):
|
||||
"""Abre un diálogo para seleccionar la carpeta de documentos"""
|
||||
carpeta = QFileDialog.getExistingDirectory(
|
||||
self,
|
||||
"Seleccionar carpeta con documentos"
|
||||
)
|
||||
|
||||
if carpeta:
|
||||
self.carpeta_path = carpeta
|
||||
self.label_carpeta.setText(f"Carpeta: {carpeta}")
|
||||
self.verificar_estado()
|
||||
self.log(f"✓ Carpeta seleccionada: {carpeta}")
|
||||
|
||||
def verificar_estado(self):
|
||||
"""Habilita el botón procesar si ambos paths están seleccionados"""
|
||||
if self.caratula_path and self.carpeta_path:
|
||||
self.btn_procesar.setEnabled(True)
|
||||
|
||||
def buscar_docx_recursivamente(self, carpeta):
|
||||
"""Busca recursivamente todos los archivos .docx en una carpeta"""
|
||||
archivos_docx = []
|
||||
patron = os.path.join(carpeta, "**", "*.docx")
|
||||
|
||||
for archivo in glob.glob(patron, recursive=True):
|
||||
archivos_docx.append(archivo)
|
||||
|
||||
return archivos_docx
|
||||
|
||||
def aplicar_formato_estandar(self, doc):
|
||||
"""Aplica formato estándar al documento"""
|
||||
try:
|
||||
# Configurar estilos por defecto
|
||||
style = doc.styles['Normal']
|
||||
font = style.font
|
||||
font.name = 'Arial'
|
||||
font.size = Pt(11)
|
||||
font.color.rgb = RGBColor(0, 0, 0) # Negro
|
||||
|
||||
# Configurar encabezados
|
||||
try:
|
||||
heading_style = doc.styles['Heading 1']
|
||||
heading_font = heading_style.font
|
||||
heading_font.name = 'Arial'
|
||||
heading_font.size = Pt(14)
|
||||
heading_font.bold = True
|
||||
heading_font.color.rgb = RGBColor(0, 51, 102) # Azul oscuro
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
self.log(f" ⚠ Advertencia en formato: {str(e)}")
|
||||
|
||||
def aplicar_formato_avanzado(self, doc):
|
||||
"""Aplica formato más avanzado y personalizado"""
|
||||
try:
|
||||
# Configurar márgenes
|
||||
sections = doc.sections
|
||||
for section in sections:
|
||||
section.top_margin = Inches(1)
|
||||
section.bottom_margin = Inches(1)
|
||||
section.left_margin = Inches(1)
|
||||
section.right_margin = Inches(1)
|
||||
|
||||
# Configurar estilos de párrafo
|
||||
for paragraph in doc.paragraphs:
|
||||
paragraph.paragraph_format.space_after = Pt(6)
|
||||
paragraph.paragraph_format.space_before = Pt(0)
|
||||
paragraph.paragraph_format.line_spacing = 1.15
|
||||
|
||||
# Alinear párrafos justificados
|
||||
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
|
||||
|
||||
# Aplicar fuente específica a cada run
|
||||
for run in paragraph.runs:
|
||||
run.font.name = 'Arial'
|
||||
run._element.rPr.rFonts.set(qn('w:eastAsia'), 'Arial')
|
||||
run.font.size = Pt(11)
|
||||
|
||||
except Exception as e:
|
||||
self.log(f" ⚠ Advertencia en formato avanzado: {str(e)}")
|
||||
|
||||
def insertar_caratula_y_formatear(self, archivo_docx, archivo_caratula):
|
||||
"""Inserta la carátula y aplica formato al documento"""
|
||||
try:
|
||||
# Abrir el documento de carátula
|
||||
doc_caratula = Document(archivo_caratula)
|
||||
|
||||
# Abrir el documento destino
|
||||
doc_destino = Document(archivo_docx)
|
||||
|
||||
# Crear un nuevo documento que contendrá la carátula + contenido original
|
||||
nuevo_doc = Document()
|
||||
|
||||
# Copiar todo el contenido de la carátula
|
||||
for elemento in doc_caratula.element.body:
|
||||
print(elemento)
|
||||
nuevo_doc.element.body.append(elemento)
|
||||
|
||||
# Agregar un salto de página después de la carátula
|
||||
nuevo_doc.add_page_break()
|
||||
|
||||
# Copiar todo el contenido del documento original
|
||||
for elemento in doc_destino.element.body:
|
||||
nuevo_doc.element.body.append(elemento)
|
||||
|
||||
# Aplicar formatos
|
||||
self.aplicar_formato_estandar(nuevo_doc)
|
||||
self.aplicar_formato_avanzado(nuevo_doc)
|
||||
|
||||
# Guardar el documento (sobrescribir el original)
|
||||
nombre_base = os.path.splitext(os.path.basename(archivo_docx))[0]
|
||||
extension = os.path.splitext(archivo_docx)[1]
|
||||
name = f"{nombre_base}{extension}"
|
||||
nuevo_docx = os.path.join(self.output_carpeta, name)
|
||||
nuevo_doc.save(nuevo_docx)
|
||||
|
||||
return True, ""
|
||||
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
def procesar_documentos(self):
|
||||
"""Función principal que orquesta todo el proceso"""
|
||||
if not DOCX_AVAILABLE:
|
||||
QMessageBox.critical(self, "Error",
|
||||
"La biblioteca python-docx no está disponible.\n\n"
|
||||
"Instala con: pip install python-docx")
|
||||
return
|
||||
|
||||
# Verificar paths
|
||||
if not os.path.exists(self.caratula_path):
|
||||
QMessageBox.warning(self, "Error", "El archivo de carátula no existe.")
|
||||
return
|
||||
|
||||
if not os.path.exists(self.carpeta_path):
|
||||
QMessageBox.warning(self, "Error", "La carpeta de documentos no existe.")
|
||||
return
|
||||
|
||||
self.log("\n=== INICIANDO PROCESAMIENTO ===")
|
||||
self.log(f"Carátula: {self.caratula_path}")
|
||||
self.log(f"Carpeta: {self.carpeta_path}")
|
||||
|
||||
directorio_padre = os.path.dirname(self.carpeta_path)
|
||||
self.output_carpeta = os.path.join(directorio_padre, "03.Outputs")
|
||||
os.makedirs(self.output_carpeta, exist_ok=True)
|
||||
|
||||
# Buscar archivos .docx
|
||||
self.log("Buscando archivos .docx...")
|
||||
archivos_docx = self.buscar_docx_recursivamente(self.carpeta_path)
|
||||
|
||||
if not archivos_docx:
|
||||
self.log("No se encontraron archivos .docx en la carpeta seleccionada.")
|
||||
QMessageBox.information(self, "Información",
|
||||
"No se encontraron archivos .docx en la carpeta seleccionada.")
|
||||
return
|
||||
|
||||
self.log(f"Se encontraron {len(archivos_docx)} archivos .docx")
|
||||
|
||||
# Crear diálogo de progreso
|
||||
progress = QProgressDialog("Procesando documentos...", "Cancelar", 0, len(archivos_docx), self)
|
||||
progress.setWindowTitle("Procesando")
|
||||
progress.setWindowModality(QtCore.Qt.WindowModal)
|
||||
progress.show()
|
||||
|
||||
# Procesar cada archivo
|
||||
exitosos = 0
|
||||
fallidos = 0
|
||||
errores_detallados = []
|
||||
|
||||
for i, archivo_docx in enumerate(archivos_docx):
|
||||
if progress.wasCanceled():
|
||||
self.log("Proceso cancelado por el usuario.")
|
||||
break
|
||||
|
||||
progress.setValue(i)
|
||||
progress.setLabelText(f"Procesando {i + 1}/{len(archivos_docx)}: {os.path.basename(archivo_docx)}")
|
||||
QApplication.processEvents()
|
||||
|
||||
self.log(f"Procesando: {os.path.basename(archivo_docx)}")
|
||||
|
||||
success, error_msg = self.insertar_caratula_y_formatear(archivo_docx, self.caratula_path)
|
||||
|
||||
if success:
|
||||
self.log(f" ✓ Completado")
|
||||
exitosos += 1
|
||||
else:
|
||||
self.log(f" ✗ Error: {error_msg}")
|
||||
fallidos += 1
|
||||
errores_detallados.append(f"{os.path.basename(archivo_docx)}: {error_msg}")
|
||||
|
||||
progress.setValue(len(archivos_docx))
|
||||
|
||||
# Mostrar resumen
|
||||
self.log("\n=== RESUMEN ===")
|
||||
self.log(f"Documentos procesados exitosamente: {exitosos}")
|
||||
self.log(f"Documentos con errores: {fallidos}")
|
||||
self.log(f"Total procesados: {exitosos + fallidos}")
|
||||
|
||||
# Mostrar mensaje final
|
||||
mensaje = (f"Procesamiento completado:\n"
|
||||
f"✓ Exitosos: {exitosos}\n"
|
||||
f"✗ Fallidos: {fallidos}\n"
|
||||
f"Total: {len(archivos_docx)}")
|
||||
|
||||
if fallidos > 0:
|
||||
mensaje += f"\n\nErrores encontrados:\n" + "\n".join(
|
||||
errores_detallados[:5]) # Mostrar solo primeros 5 errores
|
||||
if len(errores_detallados) > 5:
|
||||
mensaje += f"\n... y {len(errores_detallados) - 5} más"
|
||||
|
||||
QMessageBox.information(self, "Proceso Completado", mensaje)
|
||||
|
||||
|
||||
# Función para ejecutar desde FreeCAD
|
||||
def run_document_processor():
|
||||
"""Función principal para ejecutar el procesador desde FreeCAD"""
|
||||
# Verificar si python-docx está disponible
|
||||
if not DOCX_AVAILABLE:
|
||||
msg = QMessageBox()
|
||||
msg.setIcon(QMessageBox.Critical)
|
||||
msg.setText("Biblioteca python-docx no encontrada")
|
||||
msg.setInformativeText(
|
||||
"Para usar este script necesitas instalar python-docx:\n\n"
|
||||
"1. Abre la consola de FreeCAD\n"
|
||||
"2. Ejecuta: import subprocess, sys\n"
|
||||
"3. Ejecuta: subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'python-docx'])\n\n"
|
||||
"O instala desde una terminal externa con: pip install python-docx"
|
||||
)
|
||||
msg.setWindowTitle("Dependencia faltante")
|
||||
msg.exec_()
|
||||
return
|
||||
|
||||
# Crear y mostrar la interfaz
|
||||
dialog = DocumentProcessor(FreeCADGui.getMainWindow())
|
||||
dialog.exec_()
|
||||
|
||||
class generateDocuments:
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "house.svg")),
|
||||
'MenuText': "DocumentGenerator",
|
||||
'Accel': "D, G",
|
||||
'ToolTip': "Creates a Building object from setup dialog."}
|
||||
|
||||
def IsActive(self):
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
|
||||
def Activated(self):
|
||||
run_document_processor()
|
||||
@@ -0,0 +1,389 @@
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import Mesh
|
||||
import Part
|
||||
import numpy as np
|
||||
import random
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from multiprocessing import Pool, cpu_count
|
||||
from collections import deque
|
||||
|
||||
|
||||
import os
|
||||
from PVPlantResources import DirIcons as DirIcons
|
||||
|
||||
|
||||
def mesh_to_numpy(mesh_obj):
|
||||
"""Convierte la malla a arrays de NumPy con validación robusta"""
|
||||
mesh = mesh_obj.Mesh
|
||||
|
||||
# Convertir vértices a array NumPy (shape: Nx3)
|
||||
vertices = np.array([(v.x, v.y, v.z) for v in mesh.Points], dtype=np.float32)
|
||||
|
||||
# Convertir facetas a array NumPy (shape: Mx3)
|
||||
facets = np.array( [f.PointIndices for f in mesh.Facets], dtype=np.uint32)
|
||||
|
||||
# Verificar integridad de índices
|
||||
max_index = len(mesh.Points) - 1
|
||||
if facets.size > 0 and (facets > max_index).any():
|
||||
raise ValueError("Índices de vértices fuera de rango")
|
||||
|
||||
return vertices, facets
|
||||
|
||||
|
||||
def build_adjacency_matrix(facets):
|
||||
"""Construye matriz de adyacencia con conversión segura de tipos"""
|
||||
edges = {}
|
||||
adjacency = [[] for _ in range(len(facets))]
|
||||
|
||||
for idx, facet in enumerate(facets):
|
||||
if len(facet) != 3:
|
||||
continue
|
||||
|
||||
v0, v1, v2 = facet
|
||||
|
||||
for edge in [(v0, v1), (v1, v2), (v2, v0)]:
|
||||
sorted_edge = tuple(sorted(edge))
|
||||
|
||||
if sorted_edge not in edges:
|
||||
edges[sorted_edge] = []
|
||||
edges[sorted_edge].append(idx)
|
||||
|
||||
# Procesar solo aristas con 2 facetas
|
||||
for edge, facet_indices in edges.items():
|
||||
if len(facet_indices) == 2:
|
||||
f1, f2 = facet_indices
|
||||
adjacency[f1].append(f2)
|
||||
adjacency[f2].append(f1)
|
||||
|
||||
return adjacency
|
||||
|
||||
|
||||
def calculate_incenters_parallel(vertices, facets):
|
||||
"""Cálculo paralelizado de incentros usando NumPy"""
|
||||
v0 = vertices[facets[:, 0]]
|
||||
v1 = vertices[facets[:, 1]]
|
||||
v2 = vertices[facets[:, 2]]
|
||||
|
||||
a = np.linalg.norm(v1 - v2, axis=1)
|
||||
b = np.linalg.norm(v0 - v2, axis=1)
|
||||
c = np.linalg.norm(v0 - v1, axis=1)
|
||||
|
||||
perimeters = a + b + c
|
||||
return (a[:, None] * v0 + b[:, None] * v1 + c[:, None] * v2) / perimeters[:, None]
|
||||
|
||||
|
||||
def find_basins_parallel(args):
|
||||
"""Función paralelizable para procesamiento de cuencas"""
|
||||
chunk, adjacency, elevations = args
|
||||
basins = []
|
||||
visited = np.zeros(len(elevations), dtype=bool)
|
||||
|
||||
for seed in chunk:
|
||||
if visited[seed]:
|
||||
continue
|
||||
|
||||
queue = deque([seed])
|
||||
basin = []
|
||||
min_elev = elevations[seed]
|
||||
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
if visited[current]:
|
||||
continue
|
||||
|
||||
visited[current] = True
|
||||
basin.append(current)
|
||||
|
||||
neighbors = [n for n in adjacency[current] if elevations[n] >= min_elev]
|
||||
queue.extend(neighbors)
|
||||
|
||||
if len(basin) > 0:
|
||||
basins.append(basin)
|
||||
|
||||
return basins
|
||||
|
||||
|
||||
def find_hydrological_basins(mesh_obj, min_area=100):
|
||||
"""Identificación de cuencas optimizada"""
|
||||
FreeCAD.Console.PrintMessage(f" -- vertices y facets: ")
|
||||
FreeCADGui.updateGui()
|
||||
vertices, facets = mesh_to_numpy(mesh_obj)
|
||||
FreeCAD.Console.PrintMessage(f" -- Adjacency: ")
|
||||
FreeCADGui.updateGui()
|
||||
adjacency = build_adjacency_matrix(facets)
|
||||
FreeCAD.Console.PrintMessage(f" -- Elevations: ")
|
||||
FreeCADGui.updateGui()
|
||||
elevations = calculate_incenters_parallel(vertices, facets)[:, 2]
|
||||
|
||||
# Dividir trabajo en chunks
|
||||
chunk_size = len(facets) // (cpu_count() * 2)
|
||||
chunks = [
|
||||
(chunk_range, adjacency, elevations) # Empaqueta los 3 argumentos
|
||||
for chunk_range in [
|
||||
range(i, min(i + chunk_size, len(facets)))
|
||||
for i in range(0, len(facets), chunk_size)
|
||||
]
|
||||
]
|
||||
|
||||
# Procesamiento paralelo
|
||||
with ThreadPoolExecutor(max_workers=cpu_count()) as executor:
|
||||
results = list(executor.map(find_basins_parallel, chunks))
|
||||
|
||||
# Combinar resultados
|
||||
all_basins = [b for sublist in results for b in sublist]
|
||||
|
||||
# Filtrar por área mínima
|
||||
valid_basins = []
|
||||
for basin in all_basins:
|
||||
area = sum(triangle_area(vertices[facets[i]]) for i in basin)
|
||||
if area >= min_area:
|
||||
valid_basins.append({'facets': basin, 'area': area})
|
||||
|
||||
return valid_basins
|
||||
|
||||
|
||||
def triangle_area(vertices):
|
||||
"""Cálculo rápido de área con producto cruz"""
|
||||
return 0.5 * np.linalg.norm(
|
||||
np.cross(vertices[1] - vertices[0], vertices[2] - vertices[0])
|
||||
)
|
||||
|
||||
|
||||
def validate_facet(facet):
|
||||
"""Valida que la faceta sea un triángulo válido"""
|
||||
return hasattr(facet, 'Points') and len(facet.Points) == 3
|
||||
|
||||
|
||||
def calculate_incenter(facet):
|
||||
"""Calcula el incentro usando la función nativa de FreeCAD"""
|
||||
try:
|
||||
return facet.InCircle[0] # (x, y, z)
|
||||
except (IndexError, AttributeError):
|
||||
return None
|
||||
|
||||
|
||||
def build_adjacency(mesh):
|
||||
"""Construye matriz de adyacencia eficiente en memoria"""
|
||||
edges = {}
|
||||
adjacency = [[] for _ in mesh.Facets]
|
||||
|
||||
for idx, facet in enumerate(mesh.Facets):
|
||||
if not validate_facet(facet):
|
||||
continue
|
||||
|
||||
pts = facet.Points
|
||||
for edge in [(min(pts[0], pts[1]), max(pts[0], pts[1])),
|
||||
(min(pts[1], pts[2]), max(pts[1], pts[2])),
|
||||
(min(pts[2], pts[0]), max(pts[2], pts[0]))]:
|
||||
if edge in edges:
|
||||
neighbor = edges[edge]
|
||||
adjacency[idx].append(neighbor)
|
||||
adjacency[neighbor].append(idx)
|
||||
del edges[edge] # Liberar memoria
|
||||
else:
|
||||
edges[edge] = idx
|
||||
return adjacency
|
||||
|
||||
|
||||
def find_hydrological_basins_old(mesh_obj, min_area=100):
|
||||
"""Identificación de cuencas con validación de datos"""
|
||||
mesh = mesh_obj.Mesh
|
||||
adjacency = build_adjacency(mesh)
|
||||
basin_map = {}
|
||||
current_basin = 0
|
||||
|
||||
for seed in range(len(mesh.Facets)):
|
||||
if seed in basin_map or not validate_facet(mesh.Facets[seed]):
|
||||
continue
|
||||
|
||||
queue = deque([seed])
|
||||
basin_area = 0.0
|
||||
basin_facets = []
|
||||
|
||||
while queue:
|
||||
facet_idx = queue.popleft()
|
||||
if facet_idx in basin_map:
|
||||
continue
|
||||
|
||||
facet = mesh.Facets[facet_idx]
|
||||
in_center = calculate_incenter(facet)
|
||||
if not in_center:
|
||||
continue
|
||||
|
||||
# Verificar mínimo local
|
||||
is_sink = True
|
||||
for neighbor in adjacency[facet_idx]:
|
||||
if neighbor >= len(mesh.Facets) or not validate_facet(mesh.Facets[neighbor]):
|
||||
continue
|
||||
|
||||
n_center = calculate_incenter(mesh.Facets[neighbor])
|
||||
if n_center and n_center[2] < in_center[2]:
|
||||
is_sink = False
|
||||
break
|
||||
|
||||
if is_sink:
|
||||
basin_map[facet_idx] = current_basin
|
||||
basin_facets.append(facet_idx)
|
||||
basin_area += facet.Area
|
||||
|
||||
# Expansión controlada
|
||||
for neighbor in adjacency[facet_idx]:
|
||||
if neighbor not in basin_map:
|
||||
queue.append(neighbor)
|
||||
|
||||
if basin_area >= min_area:
|
||||
yield {
|
||||
'facets': basin_facets,
|
||||
'area': basin_area,
|
||||
'depth': calculate_basin_depth(mesh, basin_facets)
|
||||
}
|
||||
current_basin += 1
|
||||
|
||||
|
||||
def calculate_basin_depth(mesh, basin_facets):
|
||||
"""Calcula la profundidad máxima de la cuenca"""
|
||||
min_z = float('inf')
|
||||
max_z = -float('inf')
|
||||
for idx in basin_facets:
|
||||
center = calculate_incenter(mesh.Facets[idx])
|
||||
if center:
|
||||
min_z = min(min_z, center[2])
|
||||
max_z = max(max_z, center[2])
|
||||
return max_z - min_z if max_z != min_z else 0.0
|
||||
|
||||
|
||||
def simulate_water_flow(mesh_obj, basins, rainfall=1.0):
|
||||
""" Simulación de flujo con prevención de bucles infinitos """
|
||||
mesh = mesh_obj.Mesh
|
||||
adjacency = build_adjacency(mesh)
|
||||
flow_paths = []
|
||||
|
||||
for basin in basins:
|
||||
start_facets = basin['facets'][:2] # Muestra primeros 10 caminos
|
||||
for start in start_facets:
|
||||
path = []
|
||||
visited = set()
|
||||
current = start
|
||||
|
||||
while current is not None and current not in visited:
|
||||
visited.add(current)
|
||||
facet = mesh.Facets[current]
|
||||
center = calculate_incenter(facet)
|
||||
if not center:
|
||||
break
|
||||
|
||||
path.append(FreeCAD.Vector(*center))
|
||||
|
||||
# Buscar vecino más bajo
|
||||
next_facet = None
|
||||
min_elev = float('inf')
|
||||
for neighbor in adjacency[current]:
|
||||
if neighbor >= len(mesh.Facets):
|
||||
continue
|
||||
|
||||
n_center = calculate_incenter(mesh.Facets[neighbor])
|
||||
if n_center and n_center[2] < min_elev:
|
||||
min_elev = n_center[2]
|
||||
next_facet = neighbor
|
||||
|
||||
current = next_facet if min_elev < center[2] else None
|
||||
|
||||
if len(path) > 1:
|
||||
flow_paths.append(path)
|
||||
|
||||
return flow_paths
|
||||
|
||||
|
||||
def colorize_mesh(mesh_obj, facet_indices, color):
|
||||
"""Coloriza facetas específicas de forma compatible"""
|
||||
mesh = mesh_obj.Mesh
|
||||
|
||||
# Crear nuevo objeto Mesh
|
||||
colored_mesh = Mesh.Mesh()
|
||||
colored_mesh.addMesh(mesh)
|
||||
|
||||
# Crear nuevo objeto en el documento
|
||||
new_obj = FreeCAD.ActiveDocument.addObject("Mesh::Feature", "ColoredBasin")
|
||||
new_obj.Mesh = colored_mesh
|
||||
|
||||
# Asignar colores a los vértices
|
||||
vcolors = []
|
||||
for idx in range(len(mesh.Points)):
|
||||
vcolors.append((0.8, 0.8, 0.8)) # Color base
|
||||
|
||||
for facet_id in facet_indices:
|
||||
facet = mesh.Facets[facet_id]
|
||||
for vtx in facet.PointIndices:
|
||||
vcolors[vtx] = color # Color de la cuenca
|
||||
|
||||
new_obj.ViewObject.PointColor = vcolors
|
||||
new_obj.ViewObject.Lighting = "One side"
|
||||
new_obj.ViewObject.Shading = "Flat Lines"
|
||||
|
||||
|
||||
def create_polyline(points):
|
||||
"""Crea un objeto Polyline en FreeCAD"""
|
||||
if len(points) < 2:
|
||||
return
|
||||
|
||||
poly = Part.makePolygon(points)
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "FlowPath")
|
||||
obj.Shape = poly
|
||||
obj.ViewObject.LineWidth = 2.0
|
||||
obj.ViewObject.LineColor = (0.0, 0.0, 1.0)
|
||||
|
||||
|
||||
class CommandHydrologicalAnalysis:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': str(os.path.join(DirIcons, "drop.jpg")),
|
||||
'MenuText': "Hidrological analysis",
|
||||
'Accel': "H, A",
|
||||
'ToolTip': "Hidrological analysis"}
|
||||
|
||||
def IsActive(self):
|
||||
return True
|
||||
|
||||
def Activated(self):
|
||||
# User input parameters (example values)
|
||||
os.environ['OMP_NUM_THREADS'] = str(cpu_count())
|
||||
os.environ['MKL_NUM_THREADS'] = str(cpu_count())
|
||||
os.environ["FREECAD_NO_FORK"] = "1" # Desactiva el fork en sistemas Unix
|
||||
#try:
|
||||
# Parámetros de usuario
|
||||
min_basin_area = 100 # m²
|
||||
rainfall_intensity = 1.0
|
||||
|
||||
# Validar selección
|
||||
mesh_obj = FreeCADGui.Selection.getSelection()[0]
|
||||
if not mesh_obj.isDerivedFrom("Mesh::Feature"):
|
||||
raise ValueError("Selecciona un objeto de malla")
|
||||
|
||||
# Procesamiento principal
|
||||
FreeCAD.Console.PrintMessage(f"buscar basins: ")
|
||||
FreeCADGui.updateGui()
|
||||
basins = list(find_hydrological_basins(mesh_obj, min_basin_area))
|
||||
FreeCAD.Console.PrintMessage(f" - Cuencas identificadas: {len(basins)}\n")
|
||||
'''FreeCAD.Console.PrintMessage(f"simulate_water_flow: ")
|
||||
FreeCADGui.updateGui()
|
||||
flow_paths = simulate_water_flow(mesh_obj, basins, rainfall_intensity)
|
||||
FreeCAD.Console.PrintMessage(f" - Trayectorias de flujo generadas: {len(flow_paths)}\n")
|
||||
FreeCADGui.updateGui()'''
|
||||
|
||||
# Visualización
|
||||
for basin in basins:
|
||||
color = (random.random(), random.random(), random.random())
|
||||
colorize_mesh(mesh_obj, basin['facets'], color)
|
||||
|
||||
'''for path in flow_paths:
|
||||
create_polyline(path)'''
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
'''except Exception as e:
|
||||
FreeCAD.Console.PrintError(f"Error: {str(e)}\n")
|
||||
finally:
|
||||
# Limpieza de memoria
|
||||
import gc
|
||||
gc.collect()'''
|
||||
@@ -7,7 +7,7 @@
|
||||
# Find the associated blog post at: http://blog.eskriett.com/2013/07/19/downloading-google-maps/
|
||||
|
||||
import math
|
||||
# from PIL import Image
|
||||
from PIL import Image
|
||||
import os
|
||||
import urllib
|
||||
|
||||
@@ -15,7 +15,7 @@ import urllib
|
||||
# alternativa a PIL: Image
|
||||
# CV2
|
||||
|
||||
class GoogleMapDownloader:
|
||||
class GoogleMapDownloader1:
|
||||
"""
|
||||
A class which generates high resolution google maps images given
|
||||
a longitude, latitude and zoom level
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
import math
|
||||
from PIL import Image
|
||||
import urllib.request
|
||||
from io import BytesIO
|
||||
import time
|
||||
|
||||
|
||||
class GoogleMapDownloader:
|
||||
def __init__(self, zoom=12, layer='raw_satellite'):
|
||||
self._zoom = zoom
|
||||
self.layer_map = {
|
||||
'roadmap': 'm',
|
||||
'terrain': 'p',
|
||||
'satellite': 's',
|
||||
'hybrid': 'y',
|
||||
'raw_satellite': 's'
|
||||
}
|
||||
self._layer = self.layer_map.get(layer, 's')
|
||||
self._style = 'style=feature:all|element:labels|visibility:off' if layer == 'raw_satellite' else ''
|
||||
|
||||
def latlng_to_tile(self, lat, lng):
|
||||
"""Convierte coordenadas a tiles X/Y con precisión decimal"""
|
||||
tile_size = 256
|
||||
numTiles = 1 << self._zoom
|
||||
|
||||
point_x = (tile_size / 2 + lng * tile_size / 360.0) * numTiles / tile_size
|
||||
sin_y = math.sin(lat * (math.pi / 180.0))
|
||||
point_y = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) *
|
||||
-(tile_size / (2 * math.pi))) * numTiles / tile_size
|
||||
|
||||
return point_x, point_y
|
||||
|
||||
def generateImage(self, sw_lat, sw_lng, ne_lat, ne_lng):
|
||||
"""Genera la imagen para un área rectangular definida por coordenadas"""
|
||||
# Convertir coordenadas a tiles con precisión decimal
|
||||
sw_x, sw_y = self.latlng_to_tile(sw_lat, sw_lng)
|
||||
ne_x, ne_y = self.latlng_to_tile(ne_lat, ne_lng)
|
||||
|
||||
# Asegurar que las coordenadas estén en el orden correcto
|
||||
min_x = min(sw_x, ne_x)
|
||||
max_x = max(sw_x, ne_x)
|
||||
min_y = min(sw_y, ne_y)
|
||||
max_y = max(sw_y, ne_y)
|
||||
|
||||
# Calcular los tiles mínimos y máximos necesarios
|
||||
min_tile_x = math.floor(min_x)
|
||||
max_tile_x = math.ceil(max_x)
|
||||
min_tile_y = math.floor(min_y)
|
||||
max_tile_y = math.ceil(max_y)
|
||||
|
||||
# Calcular dimensiones en tiles
|
||||
tile_width = int(max_tile_x - min_tile_x) + 1
|
||||
tile_height = int(max_tile_y - min_tile_y) + 1
|
||||
|
||||
# Crear imagen temporal para todos los tiles necesarios
|
||||
full_img = Image.new('RGB', (tile_width * 256, tile_height * 256))
|
||||
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
|
||||
servers = ['mt0', 'mt1', 'mt2', 'mt3']
|
||||
|
||||
for x in range(min_tile_x, max_tile_x + 1):
|
||||
for y in range(min_tile_y, max_tile_y + 1):
|
||||
server = servers[(x + y) % len(servers)]
|
||||
base_url = f"https://{server}.google.com/vt?lyrs={self._layer}&x={x}&y={y}&z={self._zoom}"
|
||||
url = f"{base_url}&{self._style}" if self._style else base_url
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
with urllib.request.urlopen(req) as response:
|
||||
tile_data = response.read()
|
||||
|
||||
img = Image.open(BytesIO(tile_data))
|
||||
pos_x = (x - min_tile_x) * 256
|
||||
pos_y = (y - min_tile_y) * 256
|
||||
full_img.paste(img, (pos_x, pos_y))
|
||||
#print(f"✅ Tile ({x}, {y}) descargado")
|
||||
|
||||
except Exception as e:
|
||||
#print(f"❌ Error en tile ({x},{y}): {str(e)}")
|
||||
error_tile = Image.new('RGB', (256, 256), (255, 0, 0))
|
||||
full_img.paste(error_tile, (pos_x, pos_y))
|
||||
|
||||
time.sleep(0.05)
|
||||
|
||||
# Calcular desplazamientos para recorte final
|
||||
left_offset = int((min_x - min_tile_x) * 256)
|
||||
right_offset = int((max_tile_x - max_x) * 256)
|
||||
top_offset = int((min_y - min_tile_y) * 256)
|
||||
bottom_offset = int((max_tile_y - max_y) * 256)
|
||||
|
||||
# Calcular coordenadas de recorte
|
||||
left = left_offset
|
||||
top = top_offset
|
||||
right = full_img.width - right_offset
|
||||
bottom = full_img.height - bottom_offset
|
||||
|
||||
# Asegurar que las coordenadas sean válidas
|
||||
if right < left:
|
||||
right = left + 1
|
||||
if bottom < top:
|
||||
bottom = top + 1
|
||||
# Recortar la imagen al área exacta solicitada
|
||||
result = full_img.crop((
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom
|
||||
))
|
||||
|
||||
return full_img
|
||||
|
||||
|
||||
class GoogleMapDownloader_1:
|
||||
def __init__(self, zoom=12, layer='hybrid'):
|
||||
"""
|
||||
Args:
|
||||
zoom: Zoom level (0-23)
|
||||
layer: Map type (roadmap, terrain, satellite, hybrid)
|
||||
"""
|
||||
self._zoom = zoom
|
||||
self.layer_map = {
|
||||
'roadmap': 'm',
|
||||
'terrain': 'p',
|
||||
'satellite': 's',
|
||||
'hybrid': 'y',
|
||||
'raw_satellite': 's' # Capa especial sin etiquetas
|
||||
}
|
||||
self._layer = self.layer_map.get(layer, 's')
|
||||
self._style = 'style=feature:all|element:labels|visibility:off' if layer == 'raw_satellite' else ''
|
||||
|
||||
def latlng_to_tile(self, lat, lng):
|
||||
"""Convierte coordenadas a tiles X/Y"""
|
||||
tile_size = 256
|
||||
numTiles = 1 << self._zoom
|
||||
|
||||
# Cálculo para coordenada X
|
||||
point_x = (tile_size / 2 + lng * tile_size / 360.0) * numTiles / tile_size
|
||||
|
||||
# Cálculo para coordenada Y
|
||||
sin_y = math.sin(lat * (math.pi / 180.0))
|
||||
point_y = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) *
|
||||
-(tile_size / (2 * math.pi))) * numTiles / tile_size
|
||||
|
||||
return int(point_x), int(point_y)
|
||||
|
||||
def generateImage(self, sw_lat, sw_lng, ne_lat, ne_lng):
|
||||
"""
|
||||
Genera la imagen para un área rectangular definida por:
|
||||
- sw_lat, sw_lng: Esquina suroeste (latitud, longitud)
|
||||
- ne_lat, ne_lng: Esquina noreste (latitud, longitud)
|
||||
"""
|
||||
# Convertir coordenadas a tiles
|
||||
sw_x, sw_y = self.latlng_to_tile(sw_lat, sw_lng)
|
||||
ne_x, ne_y = self.latlng_to_tile(ne_lat, ne_lng)
|
||||
|
||||
# Determinar rango de tiles
|
||||
min_x = min(sw_x, ne_x)
|
||||
max_x = max(sw_x, ne_x)
|
||||
min_y = min(sw_y, ne_y)
|
||||
max_y = max(sw_y, ne_y)
|
||||
|
||||
# Calcular dimensiones en tiles
|
||||
tile_width = max_x - min_x + 1
|
||||
tile_height = max_y - min_y + 1
|
||||
|
||||
# Crear imagen final
|
||||
result = Image.new('RGB', (256 * tile_width, 256 * tile_height))
|
||||
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
|
||||
servers = ['mt0', 'mt1', 'mt2', 'mt3']
|
||||
|
||||
print(f"Descargando {tile_width}x{tile_height} tiles ({tile_width * tile_height} total)")
|
||||
|
||||
for x in range(min_x, max_x + 1):
|
||||
for y in range(min_y, max_y + 1):
|
||||
# Seleccionar servidor rotatorio
|
||||
server = servers[(x + y) % len(servers)]
|
||||
# Construir URL con parámetro para quitar etiquetas si es necesario
|
||||
url = f"https://{server}.google.com/vt?lyrs={self._layer}&x={x}&y={y}&z={self._zoom}"
|
||||
if self._style:
|
||||
url = f"{url}&{self._style}"
|
||||
|
||||
print("Descargando tile:", url)
|
||||
try:
|
||||
# Descargar tile
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
with urllib.request.urlopen(req) as response:
|
||||
tile_data = response.read()
|
||||
|
||||
# Procesar en memoria
|
||||
img = Image.open(BytesIO(tile_data))
|
||||
pos_x = (x - min_x) * 256
|
||||
pos_y = (y - min_y) * 256
|
||||
result.paste(img, (pos_x, pos_y))
|
||||
|
||||
print(f"✅ Tile ({x}, {y}) descargado")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error en tile ({x},{y}): {str(e)}")
|
||||
# Crear tile de error (rojo)
|
||||
error_tile = Image.new('RGB', (256, 256), (255, 0, 0))
|
||||
pos_x = (x - min_x) * 256
|
||||
pos_y = (y - min_y) * 256
|
||||
result.paste(error_tile, (pos_x, pos_y))
|
||||
|
||||
# Pausa para evitar bloqueos
|
||||
time.sleep(0.05)
|
||||
|
||||
return result
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
|
||||
<name>PVPlant</name>
|
||||
<description>FreeCAD Fotovoltaic Power Plant Toolkit</description>
|
||||
<version>2026.02.12</version>
|
||||
<date>2026.02.15</date>
|
||||
|
||||
<maintainer email="javier.branagutierrez@gmail.com">
|
||||
Javier Braña
|
||||
</maintainer>
|
||||
|
||||
<license file="LICENSE">LGPL-2.1-or-later</license>
|
||||
|
||||
<url type="repository" branch="main">
|
||||
https://homehud.duckdns.org/javier/PVPlant
|
||||
</url>
|
||||
|
||||
<url type="bugtracker">
|
||||
https://homehud.duckdns.org/javier/PVPlant/issues
|
||||
</url>
|
||||
|
||||
<url type="readme">
|
||||
https://homehud.duckdns.org/javier/PVPlant/raw/branch/main/README.md
|
||||
</url>
|
||||
|
||||
<icon>PVPlant/Resources/Icons/PVPlantWorkbench.svg</icon>
|
||||
|
||||
<content>
|
||||
<workbench>
|
||||
<classname>PVPlantWorkbench</classname>
|
||||
<subdirectory>./</subdirectory>
|
||||
</workbench>
|
||||
</content>
|
||||
|
||||
</package>
|
||||
@@ -24,18 +24,23 @@ class _CommandReload:
|
||||
def Activated(self):
|
||||
import PVPlantPlacement, \
|
||||
PVPlantGeoreferencing, PVPlantImportGrid, PVPlantTerrainAnalisys, \
|
||||
PVPlantSite, PVPlantRackChecking, PVPlantFence, PVPlantFencePost, PVPlantFenceGate, \
|
||||
PVPlantCreateTerrainMesh, \
|
||||
PVPlantFoundation, PVPlantTreeGenerator, PVPlantBuilding, PVPlantTrench, PVPlantEarthWorks, PVPlantPad, \
|
||||
PVPlantSite, PVPlantRackChecking, PVPlantCreateTerrainMesh, \
|
||||
PVPlantFoundation, PVPlantBuilding, PVPlantEarthWorks, PVPlantPad, \
|
||||
PVPlantRoad, PVPlantTerrain, PVPlantStringing, PVPlantManhole, \
|
||||
GraphProfile
|
||||
|
||||
from Civil.Fence import PVPlantFenceGate as PVPlantFenceGate
|
||||
from Civil.Fence import PVPlantFence as PVPlantFence
|
||||
from Civil.Fence import PVPlantFencePost as PVPlantFencePost
|
||||
|
||||
from Civil import PVPlantTrench
|
||||
from Vegetation import PVPlantTreeGenerator
|
||||
|
||||
from Mechanical.Frame import PVPlantFrame
|
||||
from Project.Area import PVPlantArea, PVPlantAreaUtils
|
||||
#from Importer import importDXF
|
||||
from Export import PVPlantBOQCivil, PVPlantBOQElectrical, PVPlantBOQMechanical, exportPVSyst, exportDXF
|
||||
from Utils import PVPlantUtils, PVPlantTrace, m_gui_edit, profile_editor, graphics
|
||||
#from Lib import GoogleMapDownloader
|
||||
|
||||
from Electrical.Cable import PVPlantCable, PVPlantElectricalLine
|
||||
from Electrical import Conduit
|
||||
@@ -47,6 +52,12 @@ class _CommandReload:
|
||||
import MeshTools.Triangulation as Triangulation
|
||||
from Project import ProjectSetup
|
||||
import importlib
|
||||
import hydro.hydrological as hydro
|
||||
import Importer.importOSM as iOSM
|
||||
|
||||
import docgenerator
|
||||
|
||||
importlib.reload(docgenerator)
|
||||
|
||||
importlib.reload(ProjectSetup)
|
||||
importlib.reload(PVPlantPlacement)
|
||||
@@ -56,9 +67,11 @@ class _CommandReload:
|
||||
importlib.reload(PVPlantSite)
|
||||
importlib.reload(PVPlantFrame)
|
||||
importlib.reload(PVPlantRackChecking)
|
||||
|
||||
importlib.reload(PVPlantFence)
|
||||
importlib.reload(PVPlantFenceGate)
|
||||
importlib.reload(PVPlantFencePost)
|
||||
|
||||
importlib.reload(PVPlantFoundation)
|
||||
importlib.reload(PVPlantCreateTerrainMesh)
|
||||
importlib.reload(PVPlantTreeGenerator)
|
||||
@@ -98,6 +111,11 @@ class _CommandReload:
|
||||
importlib.reload(layoutToExcel)
|
||||
importlib.reload(Conduit)
|
||||
|
||||
importlib.reload(hydro)
|
||||
importlib.reload(iOSM)
|
||||
|
||||
import Project.GenerateExternalDocument as GED
|
||||
importlib.reload(GED)
|
||||
|
||||
#importlib.reload(GoogleMapDownloader)
|
||||
print("Reload modules...")
|
||||
|
||||
+13
-2
@@ -2,7 +2,18 @@ numpy~=1.26.2
|
||||
opencv-python~=4.8.1
|
||||
matplotlib~=3.8.2
|
||||
openpyxl~=3.1.2
|
||||
utm~=0.7.0
|
||||
PySide2~=5.15.8
|
||||
requests~=2.31.0
|
||||
setuptools~=68.2.2
|
||||
laspy~=2.5.3
|
||||
geopy~=2.4.1
|
||||
lxml~=4.9.3
|
||||
Pillow~=10.1.0
|
||||
pyproj~=3.7.1
|
||||
simplekml~=1.3.6
|
||||
geojson~=3.1.0
|
||||
certifi~=2023.11.17
|
||||
SciPy~=1.11.4
|
||||
pycollada~=0.7.2
|
||||
shapely
|
||||
rtree
|
||||
pandas
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
import FreeCAD, FreeCADGui
|
||||
#from freecad.trails import ICONPATH
|
||||
from PySide2.QtWidgets import QLabel
|
||||
from PySide.QtWidgets import QLabel
|
||||
import copy
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user