Files
PVPlant/Civil/Alignment.py
T

144 lines
4.9 KiB
Python
Raw Normal View History

# /**********************************************************************
# * *
# * Copyright (c) 2026 Javier Braña <javier.branagutierrez@gmail.com> *
# * *
# * Sistema de Alineamiento Horizontal y Vertical para carreteras *
# * *
# ***********************************************************************
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.
Propiedades:
- SourceWire: Polilínea base (2D o 3D)
- Stations: Lista de estaciones (progresivas)
- HorizontalCurves: Curvas circulares en el plano
- VerticalProfile: Perfil longitudinal (pendientes + curvas verticales)
- CrossSections: Secciones transversales asociadas
"""
def __init__(self, obj):
obj.Proxy = self
self.setProperties(obj)
self._cached_chainage = None
self._cached_points = None
def setProperties(self, obj):
pl = obj.PropertiesList
if "SourceWire" not in pl:
obj.addProperty("App::PropertyLink",
"SourceWire",
"Alignment",
"Polilínea base del eje").SourceWire = None
if "Stations" not in pl:
obj.addProperty("App::PropertyFloatList",
"Stations",
"Alignment",
"Progresivas (estaciones) del eje")
if "StationInterval" not in pl:
obj.addProperty("App::PropertyLength",
"StationInterval",
"Alignment",
"Intervalo entre estaciones").StationInterval = 20000 # 20m
if "NumberOfStations" not in pl:
obj.addProperty("App::PropertyInteger",
"NumberOfStations",
"Alignment",
"Número de estaciones").NumberOfStations = 0
obj.setEditorMode("NumberOfStations", 1) # read-only
if "TotalLength" not in pl:
obj.addProperty("App::PropertyLength",
"TotalLength",
"Alignment",
"Longitud total del eje").TotalLength = 0
obj.setEditorMode("TotalLength", 1)
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
total_len = wire.Length
obj.TotalLength = total_len
interval = obj.StationInterval.Value if hasattr(obj.StationInterval, 'Value') else obj.StationInterval
if interval <= 0:
interval = 20000
n_stations = max(2, int(total_len / interval) + 1)
obj.NumberOfStations = n_stations
# Generar puntos de estación a lo largo del wire
stations = []
for i in range(n_stations):
param = i / (n_stations - 1)
stations.append(param * total_len)
obj.Stations = stations
self._cached_chainage = stations
self._cached_points = None
def get_station_point(self, obj, distance):
"""Devuelve el punto en el eje a una distancia (progresiva) dada."""
if not obj.SourceWire:
return None
try:
return obj.SourceWire.Shape.valueAt(
obj.SourceWire.Shape.getParameterByLength(distance / obj.TotalLength)
)
except Exception:
return None
def get_tangent_at(self, obj, distance):
"""Devuelve el vector tangente en una progresiva."""
if not obj.SourceWire:
return None
try:
return obj.SourceWire.Shape.tangentAt(
obj.SourceWire.Shape.getParameterByLength(distance / obj.TotalLength)
)
except Exception:
return None
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