# /********************************************************************** # * * # * Copyright (c) 2026 Javier Braña * # * * # * 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