Limpieza: eliminados archivos viejos (PVPLantPlacement-old_2022.py, -copia, -old, .bak)
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user