Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25fd92e4f0 | |||
| 5abd4fae02 | |||
| e461ab2e80 | |||
| 1a22121f87 | |||
| 2858b58d86 | |||
| f3f94d4f59 | |||
| f4d43bedd0 | |||
| a67001bb88 | |||
| 26311cb344 | |||
| 6c2db07493 | |||
| 7d1127c6b5 | |||
| 7a54e424cb | |||
| 065f840941 | |||
| 74aedf6122 | |||
| 7c81beb1ba | |||
| 02b639d4ed |
@@ -0,0 +1,2 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
@@ -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:
|
try:
|
||||||
folder = FreeCAD.ActiveDocument.Trenches
|
folder = FreeCAD.ActiveDocument.Trenches
|
||||||
except:
|
except AttributeError:
|
||||||
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Trenches')
|
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Trenches')
|
||||||
folder.Label = "Trenches"
|
folder.Label = "Trenches"
|
||||||
folder.addObject(obj)
|
folder.addObject(obj)
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import zipfile
|
|||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from PySide2 import QtWidgets, QtCore, QtGui
|
from PySide import QtWidgets, QtCore, QtGui
|
||||||
import FreeCAD
|
import FreeCAD
|
||||||
import Mesh
|
import Mesh
|
||||||
import Part
|
import Part
|
||||||
|
|||||||
@@ -72,11 +72,11 @@ class _PVPlantImportDXF:
|
|||||||
|
|
||||||
def openFile(self):
|
def openFile(self):
|
||||||
''' '''
|
''' '''
|
||||||
"getOpenFileName(parent: typing.Union[PySide2.QtWidgets.QWidget, NoneType] = None," \
|
"getOpenFileName(parent: typing.Union[PySide.QtWidgets.QWidget, NoneType] = None," \
|
||||||
"caption: str = ''," \
|
"caption: str = ''," \
|
||||||
"dir: str = ''," \
|
"dir: str = ''," \
|
||||||
"filter: 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)')
|
filename, trash = QtGui.QFileDialog().getOpenFileName(None, 'Select File', os.getcwd(), 'Autocad dxf (*.dxf)')
|
||||||
if filename == "":
|
if filename == "":
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -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())
|
|
||||||
@@ -1,945 +0,0 @@
|
|||||||
import math
|
|
||||||
|
|
||||||
import FreeCAD
|
|
||||||
import Part
|
|
||||||
import ArchComponent
|
|
||||||
from pivy import coin
|
|
||||||
import numpy as np
|
|
||||||
import DraftGeomUtils
|
|
||||||
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
pts = [ver.Point for ver in pro.Vertexes]
|
|
||||||
land.trim(pts, 1)
|
|
||||||
|
|
||||||
tmp = []
|
|
||||||
shp = Part.Shape()
|
|
||||||
for face in sh.Faces:
|
|
||||||
wire = face.Wires[0].copy()
|
|
||||||
pl = wire.Placement.Base
|
|
||||||
wire.Placement.Base = wire.Placement.Base - pl
|
|
||||||
|
|
||||||
if DraftGeomUtils.isPlanar(wire):
|
|
||||||
# Caso simple
|
|
||||||
wire = wire.makeOffset2D(10000, 0, False, False, True)
|
|
||||||
wire.Placement.Base.z = wire.Placement.Base.z - 10000
|
|
||||||
top = wire.makeOffset2D(1, 0, False, False, True)
|
|
||||||
loft = Part.makeLoft([top, wire], True, True, False)
|
|
||||||
tmp.append(loft)
|
|
||||||
shp = shp.fuse(loft)
|
|
||||||
else:
|
|
||||||
# Caso complejo:
|
|
||||||
vertices = face.Vertexes
|
|
||||||
# Dividir rectángulo en 2 triángulos
|
|
||||||
triangles = [
|
|
||||||
[vertices[0], vertices[1], vertices[2]],
|
|
||||||
[vertices[2], vertices[3], vertices[0]]
|
|
||||||
]
|
|
||||||
|
|
||||||
for tri in triangles:
|
|
||||||
# Crear wire triangular
|
|
||||||
wire = Part.makePolygon([v.Point for v in tri] + [tri[0].Point])
|
|
||||||
wire = wire.makeOffset2D(10000, 0, False, False, True)
|
|
||||||
wire.Placement.Base.z = wire.Placement.Base.z - 10000
|
|
||||||
top = wire.makeOffset2D(1, 0, False, False, True)
|
|
||||||
loft = Part.makeLoft([top, wire], True, True, False)
|
|
||||||
tmp.append(loft)
|
|
||||||
shp = shp.fuse(loft)
|
|
||||||
|
|
||||||
final_tool = Part.makeCompound(tmp)
|
|
||||||
Part.show(final_tool, "tool")
|
|
||||||
Part.show(shp)
|
|
||||||
|
|
||||||
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 Part.OCCError:
|
|
||||||
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
|
|
||||||
|
|
||||||
@@ -58,8 +58,9 @@ class MapWindow(QtGui.QWidget):
|
|||||||
self.setupUi()
|
self.setupUi()
|
||||||
|
|
||||||
def setupUi(self):
|
def setupUi(self):
|
||||||
from PySide2.QtWebEngineWidgets import QWebEngineView
|
# Intentar cargar QtWebEngine (no siempre disponible, ej: FreeCAD flatpak)
|
||||||
from PySide2.QtWebChannel import QWebChannel
|
QWebEngineView, QWebChannel = self._load_webengine()
|
||||||
|
self._webengine_available = QWebEngineView is not None
|
||||||
|
|
||||||
self.ui = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantGeoreferencing.ui", self)
|
self.ui = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantGeoreferencing.ui", self)
|
||||||
|
|
||||||
@@ -86,6 +87,7 @@ class MapWindow(QtGui.QWidget):
|
|||||||
self.layout.addWidget(RightWidget)
|
self.layout.addWidget(RightWidget)
|
||||||
|
|
||||||
# Left Widgets:
|
# Left Widgets:
|
||||||
|
if self._webengine_available:
|
||||||
# -- Search Bar:
|
# -- Search Bar:
|
||||||
self.valueSearch = QtGui.QLineEdit(self)
|
self.valueSearch = QtGui.QLineEdit(self)
|
||||||
self.valueSearch.setPlaceholderText("Search")
|
self.valueSearch.setPlaceholderText("Search")
|
||||||
@@ -100,7 +102,7 @@ class MapWindow(QtGui.QWidget):
|
|||||||
SearchBarLayout.addWidget(searchbutton)
|
SearchBarLayout.addWidget(searchbutton)
|
||||||
LeftLayout.addLayout(SearchBarLayout)
|
LeftLayout.addLayout(SearchBarLayout)
|
||||||
|
|
||||||
# -- Webbroser:
|
# -- Web browser:
|
||||||
self.view = QWebEngineView()
|
self.view = QWebEngineView()
|
||||||
self.channel = QWebChannel(self.view.page())
|
self.channel = QWebChannel(self.view.page())
|
||||||
self.view.page().setWebChannel(self.channel)
|
self.view.page().setWebChannel(self.channel)
|
||||||
@@ -109,13 +111,30 @@ class MapWindow(QtGui.QWidget):
|
|||||||
self.view.page().loadFinished.connect(self.onLoadFinished)
|
self.view.page().loadFinished.connect(self.onLoadFinished)
|
||||||
self.view.page().load(QtCore.QUrl.fromLocalFile(file))
|
self.view.page().load(QtCore.QUrl.fromLocalFile(file))
|
||||||
LeftLayout.addWidget(self.view)
|
LeftLayout.addWidget(self.view)
|
||||||
# self.layout.addWidget(self.view, 1, 0, 1, 3)
|
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:
|
# -- Latitud y longitud:
|
||||||
self.labelCoordinates = QtGui.QLabel()
|
self.labelCoordinates = QtGui.QLabel()
|
||||||
self.labelCoordinates.setFixedHeight(21)
|
self.labelCoordinates.setFixedHeight(21)
|
||||||
LeftLayout.addWidget(self.labelCoordinates)
|
LeftLayout.addWidget(self.labelCoordinates)
|
||||||
# self.layout.addWidget(self.labelCoordinates, 2, 0, 1, 3)
|
|
||||||
|
|
||||||
# Right Widgets:
|
# Right Widgets:
|
||||||
labelKMZ = QtGui.QLabel()
|
labelKMZ = QtGui.QLabel()
|
||||||
@@ -139,9 +158,6 @@ class MapWindow(QtGui.QWidget):
|
|||||||
radio3 = QtGui.QRadioButton("Datos GPS")
|
radio3 = QtGui.QRadioButton("Datos GPS")
|
||||||
radio1.setChecked(True)
|
radio1.setChecked(True)
|
||||||
|
|
||||||
# buttonDialog = QtGui.QPushButton('...')
|
|
||||||
# buttonDialog.setEnabled(False)
|
|
||||||
|
|
||||||
vbox = QtGui.QVBoxLayout(self)
|
vbox = QtGui.QVBoxLayout(self)
|
||||||
vbox.addWidget(radio1)
|
vbox.addWidget(radio1)
|
||||||
vbox.addWidget(radio2)
|
vbox.addWidget(radio2)
|
||||||
@@ -149,7 +165,6 @@ class MapWindow(QtGui.QWidget):
|
|||||||
|
|
||||||
self.groupbox.setLayout(vbox)
|
self.groupbox.setLayout(vbox)
|
||||||
RightLayout.addWidget(self.groupbox)
|
RightLayout.addWidget(self.groupbox)
|
||||||
# ------------------------
|
|
||||||
|
|
||||||
self.checkboxImportGis = QtGui.QCheckBox("Importar datos GIS")
|
self.checkboxImportGis = QtGui.QCheckBox("Importar datos GIS")
|
||||||
RightLayout.addWidget(self.checkboxImportGis)
|
RightLayout.addWidget(self.checkboxImportGis)
|
||||||
@@ -174,6 +189,52 @@ class MapWindow(QtGui.QWidget):
|
|||||||
with open(file, 'r') as f:
|
with open(file, 'r') as f:
|
||||||
frame.runJavaScript(f.read())
|
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):
|
def onSearch(self):
|
||||||
if self.valueSearch.text() == "":
|
if self.valueSearch.text() == "":
|
||||||
return
|
return
|
||||||
|
|||||||
+45
-6
@@ -39,6 +39,51 @@ import os
|
|||||||
from PVPlantResources import DirIcons as DirIcons
|
from PVPlantResources import DirIcons as DirIcons
|
||||||
import PVPlantSite
|
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
|
def get_elevation_from_oe(coordinates): # v1 deepseek
|
||||||
"""Obtiene elevaciones de Open-Elevation API y devuelve vectores FreeCAD en coordenadas UTM.
|
"""Obtiene elevaciones de Open-Elevation API y devuelve vectores FreeCAD en coordenadas UTM.
|
||||||
@@ -52,7 +97,6 @@ def get_elevation_from_oe(coordinates): # v1 deepseek
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import utm
|
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
|
|
||||||
# Construcción más eficiente de parámetros
|
# Construcción más eficiente de parámetros
|
||||||
@@ -110,7 +154,6 @@ def getElevationFromOE(coordinates):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
from requests import get
|
from requests import get
|
||||||
import utm
|
|
||||||
|
|
||||||
locations_str=""
|
locations_str=""
|
||||||
total = len(coordinates) - 1
|
total = len(coordinates) - 1
|
||||||
@@ -141,7 +184,6 @@ def getElevationFromOE(coordinates):
|
|||||||
|
|
||||||
def getSinglePointElevationFromBing(lat, lng):
|
def getSinglePointElevationFromBing(lat, lng):
|
||||||
#http://dev.virtualearth.net/REST/v1/Elevation/List?points={lat1,long1,lat2,long2,latN,longnN}&heights={heights}&key={BingMapsAPIKey}
|
#http://dev.virtualearth.net/REST/v1/Elevation/List?points={lat1,long1,lat2,long2,latN,longnN}&heights={heights}&key={BingMapsAPIKey}
|
||||||
import utm
|
|
||||||
|
|
||||||
source = "http://dev.virtualearth.net/REST/v1/Elevation/List?points="
|
source = "http://dev.virtualearth.net/REST/v1/Elevation/List?points="
|
||||||
source += str(lat) + "," + str(lng)
|
source += str(lat) + "," + str(lng)
|
||||||
@@ -166,7 +208,6 @@ def getSinglePointElevationFromBing(lat, lng):
|
|||||||
def getGridElevationFromBing(polygon, lat, lng, resolution = 1000):
|
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
|
#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}
|
# &heights=ellipsoid&samples=10&key={BingMapsAPIKey}
|
||||||
import utm
|
|
||||||
import math
|
import math
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
@@ -311,7 +352,6 @@ def getSinglePointElevationUtm(lat, lon):
|
|||||||
res = s['results']
|
res = s['results']
|
||||||
print (res)
|
print (res)
|
||||||
|
|
||||||
import utm
|
|
||||||
for r in res:
|
for r in res:
|
||||||
c = utm.from_latlon(r['location']['lat'], r['location']['lng'])
|
c = utm.from_latlon(r['location']['lat'], r['location']['lng'])
|
||||||
v = FreeCAD.Vector(
|
v = FreeCAD.Vector(
|
||||||
@@ -323,7 +363,6 @@ def getSinglePointElevationUtm(lat, lon):
|
|||||||
|
|
||||||
def getElevationUTM(polygon, lat, lng, resolution = 10000):
|
def getElevationUTM(polygon, lat, lng, resolution = 10000):
|
||||||
|
|
||||||
import utm
|
|
||||||
geo = utm.from_latlon(lat, lng)
|
geo = utm.from_latlon(lat, lng)
|
||||||
# result = (679434.3578335291, 4294023.585627955, 30, 'S')
|
# result = (679434.3578335291, 4294023.585627955, 30, 'S')
|
||||||
# EASTING, NORTHING, ZONE NUMBER, ZONE LETTER
|
# EASTING, NORTHING, ZONE NUMBER, ZONE LETTER
|
||||||
|
|||||||
+185
-1852
File diff suppressed because it is too large
Load Diff
+274
-522
@@ -1,315 +1,298 @@
|
|||||||
|
# /**********************************************************************
|
||||||
|
# * *
|
||||||
|
# * 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 FreeCAD
|
||||||
import ArchComponent
|
import ArchComponent
|
||||||
|
import Part
|
||||||
|
import math
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
if FreeCAD.GuiUp:
|
||||||
import FreeCADGui
|
import FreeCADGui
|
||||||
from PySide import QtCore
|
from PySide import QtCore
|
||||||
from DraftTools import translate
|
from DraftTools import translate
|
||||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||||
|
|
||||||
import Part
|
|
||||||
import os
|
import os
|
||||||
else:
|
else:
|
||||||
# \cond
|
def translate(ctxt, txt): return txt
|
||||||
def translate(ctxt, txt):
|
def QT_TRANSLATE_NOOP(ctxt, txt): return 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"
|
|
||||||
|
|
||||||
import PVPlantResources
|
import PVPlantResources
|
||||||
from PVPlantResources import DirIcons as DirIcons
|
from PVPlantResources import DirIcons as DirIcons
|
||||||
|
from Civil.Alignment import make_alignment_from_wire
|
||||||
|
|
||||||
|
|
||||||
def makeRoad(base=None):
|
def makeRoad(base=None, alignment=None):
|
||||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Road")
|
"""Crea un objeto Road con o sin alignment."""
|
||||||
|
doc = FreeCAD.ActiveDocument
|
||||||
|
obj = doc.addObject("Part::FeaturePython", "Road")
|
||||||
_Road(obj)
|
_Road(obj)
|
||||||
_ViewProviderRoad(obj.ViewObject)
|
_ViewProviderRoad(obj.ViewObject)
|
||||||
obj.Base = base
|
obj.Base = base
|
||||||
|
obj.Alignment = alignment
|
||||||
from Project.Area import PVPlantArea
|
doc.recompute()
|
||||||
offset = PVPlantArea.makeOffsetArea(obj, 4000)
|
|
||||||
PVPlantArea.makeProhibitedArea(offset)
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class _Road(ArchComponent.Component):
|
class _Road(ArchComponent.Component):
|
||||||
|
"""Carretera con alineamiento horizontal+vertical y secciones multicapa."""
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
# Definición de Variables:
|
|
||||||
ArchComponent.Component.__init__(self, obj)
|
ArchComponent.Component.__init__(self, obj)
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
self.setProperties(obj)
|
self.setProperties(obj)
|
||||||
self.Type = "Road"
|
self.Type = "Road"
|
||||||
obj.Proxy = self
|
obj.Proxy = self
|
||||||
|
obj.IfcType = "Civil Element"
|
||||||
self.route = False
|
|
||||||
|
|
||||||
obj.IfcType = "Civil Element" ## puede ser: Cable Carrier Segment
|
|
||||||
obj.setEditorMode("IfcType", 1)
|
obj.setEditorMode("IfcType", 1)
|
||||||
|
|
||||||
|
|
||||||
self.count = 0
|
|
||||||
|
|
||||||
def setProperties(self, obj):
|
def setProperties(self, obj):
|
||||||
# Definicion de Propiedades:
|
pl = obj.PropertiesList
|
||||||
'''[
|
|
||||||
'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'
|
|
||||||
]'''
|
|
||||||
|
|
||||||
obj.addProperty("App::PropertyPercent",
|
# --- Alineamiento ---
|
||||||
"SurfaceSlope",
|
if "Alignment" not in pl:
|
||||||
"Road",
|
obj.addProperty("App::PropertyLink",
|
||||||
QT_TRANSLATE_NOOP("App::Property", "Connection")).SurfaceSlope = 2
|
"Alignment", "Road",
|
||||||
|
"Objeto Alignment que define el eje").Alignment = None
|
||||||
|
|
||||||
obj.addProperty("App::PropertyPercent",
|
if "Base" not in pl:
|
||||||
"SurfaceDrainSlope",
|
obj.addProperty("App::PropertyLink",
|
||||||
"Road",
|
"Base", "Road",
|
||||||
QT_TRANSLATE_NOOP("App::Property", "Connection")).SurfaceDrainSlope = int(3 / 2 * 100)
|
"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",
|
obj.addProperty("App::PropertyLength",
|
||||||
"Width",
|
"Width", "Road",
|
||||||
"Road",
|
"Ancho total de la carretera").Width = 4000
|
||||||
QT_TRANSLATE_NOOP("App::Property", "Connection")).Width = 4000
|
|
||||||
|
|
||||||
|
if "PavementThickness" not in pl:
|
||||||
obj.addProperty("App::PropertyLength",
|
obj.addProperty("App::PropertyLength",
|
||||||
"Height",
|
"PavementThickness", "Road",
|
||||||
"Road",
|
"Espesor del pavimento").PavementThickness = 250
|
||||||
QT_TRANSLATE_NOOP("App::Property", "Connection")).Height = 250
|
|
||||||
|
|
||||||
|
if "BaseThickness" not in pl:
|
||||||
obj.addProperty("App::PropertyLength",
|
obj.addProperty("App::PropertyLength",
|
||||||
"Subbase",
|
"BaseThickness", "Road",
|
||||||
"Road",
|
"Espesor de la base").BaseThickness = 200
|
||||||
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):
|
def onDocumentRestored(self, obj):
|
||||||
"""Method run when the document is restored.
|
|
||||||
Re-adds the Arch component, and object properties."""
|
|
||||||
|
|
||||||
ArchComponent.Component.onDocumentRestored(self, obj)
|
ArchComponent.Component.onDocumentRestored(self, obj)
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
self.Type = "Road"
|
self.Type = "Road"
|
||||||
obj.Proxy = self
|
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):
|
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)
|
||||||
w = obj.Base.Shape
|
if not wire:
|
||||||
profiles = []
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def makeLoft(self, profile):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
total_len = wire.Length
|
||||||
|
obj.TotalLength = total_len
|
||||||
|
interval = obj.StationInterval.Value
|
||||||
|
if interval <= 0:
|
||||||
|
interval = 20000
|
||||||
|
|
||||||
|
n_stations = max(2, int(total_len / interval) + 1)
|
||||||
|
obj.NumberOfStations = n_stations
|
||||||
|
|
||||||
|
# Generar el sólido mediante barrido de secciones
|
||||||
|
shapes = []
|
||||||
|
cut_volume = 0
|
||||||
|
fill_volume = 0
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
sections = self._generate_cross_section(obj, pt, tangent)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
if shapes:
|
||||||
|
try:
|
||||||
|
compound = Part.makeCompound(shapes)
|
||||||
|
obj.Shape = compound
|
||||||
|
|
||||||
|
# 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):
|
class _ViewProviderRoad(ArchComponent.ViewProviderComponent):
|
||||||
def __init__(self, vobj):
|
def __init__(self, vobj):
|
||||||
@@ -318,10 +301,12 @@ class _ViewProviderRoad(ArchComponent.ViewProviderComponent):
|
|||||||
def getIcon(self):
|
def getIcon(self):
|
||||||
return str(os.path.join(PVPlantResources.DirIcons, "road.svg"))
|
return str(os.path.join(PVPlantResources.DirIcons, "road.svg"))
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# TaskPanel para crear carretera interactivamente
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
class _RoadTaskPanel:
|
class _RoadTaskPanel:
|
||||||
|
|
||||||
def __init__(self, obj=None):
|
def __init__(self, obj=None):
|
||||||
|
|
||||||
if obj is None:
|
if obj is None:
|
||||||
self.new = True
|
self.new = True
|
||||||
self.obj = makeRoad()
|
self.obj = makeRoad()
|
||||||
@@ -329,7 +314,8 @@ class _RoadTaskPanel:
|
|||||||
self.new = False
|
self.new = False
|
||||||
self.obj = obj
|
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):
|
def accept(self):
|
||||||
FreeCADGui.Control.closeDialog()
|
FreeCADGui.Control.closeDialog()
|
||||||
@@ -342,274 +328,40 @@ class _RoadTaskPanel:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
# ---------------------------------------------------------------------------
|
||||||
|
# Comando para dibujar carretera sobre un wire seleccionado
|
||||||
import FreeCAD as App
|
# ---------------------------------------------------------------------------
|
||||||
import FreeCADGui as Gui
|
class _CommandRoad:
|
||||||
import DraftVecUtils
|
"""Comando para crear carretera seleccionando un wire + generando alignment."""
|
||||||
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
|
|
||||||
|
|
||||||
def GetResources(self):
|
def GetResources(self):
|
||||||
"""Set icon, menu and tooltip."""
|
|
||||||
return {'Pixmap': str(os.path.join(DirIcons, "road.svg")),
|
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",
|
'Accel': "C, R",
|
||||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PVPlantRoad",
|
'ToolTip': QT_TRANSLATE_NOOP("PVPlantRoad",
|
||||||
"Creates a Road object from setup dialog.")}
|
"Crea una carretera con alineamiento profesional.")}
|
||||||
|
|
||||||
def Activated(self, name=translate("draft", "Line")):
|
def IsActive(self):
|
||||||
"""Execute when the command is called."""
|
return FreeCAD.ActiveDocument is not None
|
||||||
|
|
||||||
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 Activated(self):
|
||||||
sel = FreeCADGui.Selection.getSelection()
|
sel = FreeCADGui.Selection.getSelection()
|
||||||
|
wire = None
|
||||||
done = False
|
if sel:
|
||||||
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"
|
|
||||||
import Draft
|
import Draft
|
||||||
if Draft.getType(sel[0]) == "Wire":
|
if Draft.getType(sel[0]) == "Wire":
|
||||||
self.path = sel[0]
|
wire = sel[0]
|
||||||
done = True
|
|
||||||
|
|
||||||
if not done:
|
if wire:
|
||||||
self.ui.wireUi(name)
|
# Crear alignment desde el wire seleccionado
|
||||||
self.ui.setTitle("Road")
|
alignment = make_alignment_from_wire(wire)
|
||||||
self.obj = self.doc.addObject("Part::Feature", self.featureName)
|
road = makeRoad(alignment=alignment)
|
||||||
gui_utils.format_object(self.obj)
|
FreeCAD.Console.PrintMessage(
|
||||||
|
f"Carretera creada desde '{wire.Label}'. "
|
||||||
self.call = self.view.addEventCallback("SoEvent", self.action)
|
f"Alineamiento: {alignment.Label}\n")
|
||||||
_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:
|
else:
|
||||||
self.finish(False, cont=True)
|
FreeCAD.Console.PrintWarning(
|
||||||
|
"Selecciona un Wire (polilínea) para usarlo como eje de carretera.\n")
|
||||||
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"))
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
if FreeCAD.GuiUp:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import FreeCAD
|
import FreeCAD
|
||||||
import FreeCADGui
|
import FreeCADGui
|
||||||
from PySide2 import QtWidgets
|
from PySide import QtWidgets
|
||||||
import os
|
import os
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
if FreeCAD.GuiUp:
|
||||||
|
|||||||
@@ -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 |
@@ -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):
|
def onAceptClick(self):
|
||||||
''' '''
|
''' '''
|
||||||
from PVPlantPlacement import moveFrameHead
|
from Civil.PVPlantPlacementCalc import moveFrameHead
|
||||||
moveFrameHead(self.obj, head=self.ui.comboHead.currentIndex(),
|
moveFrameHead(self.obj, head=self.ui.comboHead.currentIndex(),
|
||||||
dist=self.ui.editDist.value())
|
dist=self.ui.editDist.value())
|
||||||
self.setUI(self.obj)
|
self.setUI(self.obj)
|
||||||
|
|||||||
@@ -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()
|
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
# Script para FreeCAD - Procesador de Documentos Word con Carátula
|
# Script para FreeCAD - Procesador de Documentos Word con Carátula
|
||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
from PySide2 import QtWidgets, QtCore
|
from PySide import QtWidgets, QtCore
|
||||||
from PySide2.QtWidgets import (QFileDialog, QMessageBox, QProgressDialog,
|
from PySide.QtWidgets import (QFileDialog, QMessageBox, QProgressDialog,
|
||||||
QApplication, QVBoxLayout, QWidget, QPushButton,
|
QApplication, QVBoxLayout, QWidget, QPushButton,
|
||||||
QLabel, QTextEdit)
|
QLabel, QTextEdit)
|
||||||
import FreeCAD
|
import FreeCAD
|
||||||
|
|||||||
+1
-2
@@ -2,8 +2,6 @@ numpy~=1.26.2
|
|||||||
opencv-python~=4.8.1
|
opencv-python~=4.8.1
|
||||||
matplotlib~=3.8.2
|
matplotlib~=3.8.2
|
||||||
openpyxl~=3.1.2
|
openpyxl~=3.1.2
|
||||||
utm~=0.7.0
|
|
||||||
PySide2~=5.15.8
|
|
||||||
requests~=2.31.0
|
requests~=2.31.0
|
||||||
setuptools~=68.2.2
|
setuptools~=68.2.2
|
||||||
laspy~=2.5.3
|
laspy~=2.5.3
|
||||||
@@ -18,3 +16,4 @@ SciPy~=1.11.4
|
|||||||
pycollada~=0.7.2
|
pycollada~=0.7.2
|
||||||
shapely
|
shapely
|
||||||
rtree
|
rtree
|
||||||
|
pandas
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
import FreeCAD, FreeCADGui
|
import FreeCAD, FreeCADGui
|
||||||
#from freecad.trails import ICONPATH
|
#from freecad.trails import ICONPATH
|
||||||
from PySide2.QtWidgets import QLabel
|
from PySide.QtWidgets import QLabel
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user