Limpieza: eliminados archivos viejos (PVPLantPlacement-old_2022.py, -copia, -old, .bak)
This commit is contained in:
@@ -1,144 +0,0 @@
|
||||
# /**********************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2026 Javier Braña <javier.branagutierrez@gmail.com> *
|
||||
# * *
|
||||
# * Sistema de Alineamiento Horizontal y Vertical para carreteras *
|
||||
# * *
|
||||
# ***********************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import Part
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
def make_alignment_from_wire(wire, name="Alignment"):
|
||||
"""Crea un objeto Alignment a partir de un wire de FreeCAD."""
|
||||
if FreeCAD.ActiveDocument is None:
|
||||
return None
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
|
||||
Alignment(obj)
|
||||
_ViewProviderAlignment(obj.ViewObject)
|
||||
obj.SourceWire = wire
|
||||
obj.Label = name
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
return obj
|
||||
|
||||
|
||||
class Alignment:
|
||||
"""
|
||||
Alineamiento horizontal + vertical.
|
||||
|
||||
Propiedades:
|
||||
- SourceWire: Polilínea base (2D o 3D)
|
||||
- Stations: Lista de estaciones (progresivas)
|
||||
- HorizontalCurves: Curvas circulares en el plano
|
||||
- VerticalProfile: Perfil longitudinal (pendientes + curvas verticales)
|
||||
- CrossSections: Secciones transversales asociadas
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.Proxy = self
|
||||
self.setProperties(obj)
|
||||
self._cached_chainage = None
|
||||
self._cached_points = None
|
||||
|
||||
def setProperties(self, obj):
|
||||
pl = obj.PropertiesList
|
||||
if "SourceWire" not in pl:
|
||||
obj.addProperty("App::PropertyLink",
|
||||
"SourceWire",
|
||||
"Alignment",
|
||||
"Polilínea base del eje").SourceWire = None
|
||||
if "Stations" not in pl:
|
||||
obj.addProperty("App::PropertyFloatList",
|
||||
"Stations",
|
||||
"Alignment",
|
||||
"Progresivas (estaciones) del eje")
|
||||
if "StationInterval" not in pl:
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"StationInterval",
|
||||
"Alignment",
|
||||
"Intervalo entre estaciones").StationInterval = 20000 # 20m
|
||||
if "NumberOfStations" not in pl:
|
||||
obj.addProperty("App::PropertyInteger",
|
||||
"NumberOfStations",
|
||||
"Alignment",
|
||||
"Número de estaciones").NumberOfStations = 0
|
||||
obj.setEditorMode("NumberOfStations", 1) # read-only
|
||||
if "TotalLength" not in pl:
|
||||
obj.addProperty("App::PropertyLength",
|
||||
"TotalLength",
|
||||
"Alignment",
|
||||
"Longitud total del eje").TotalLength = 0
|
||||
obj.setEditorMode("TotalLength", 1)
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
self.setProperties(obj)
|
||||
|
||||
def execute(self, obj):
|
||||
"""Calcula estaciones y geometría del alignment."""
|
||||
if not obj.SourceWire or not obj.SourceWire.Shape:
|
||||
return
|
||||
|
||||
wire = obj.SourceWire.Shape
|
||||
total_len = wire.Length
|
||||
obj.TotalLength = total_len
|
||||
|
||||
interval = obj.StationInterval.Value if hasattr(obj.StationInterval, 'Value') else obj.StationInterval
|
||||
if interval <= 0:
|
||||
interval = 20000
|
||||
|
||||
n_stations = max(2, int(total_len / interval) + 1)
|
||||
obj.NumberOfStations = n_stations
|
||||
|
||||
# Generar puntos de estación a lo largo del wire
|
||||
stations = []
|
||||
for i in range(n_stations):
|
||||
param = i / (n_stations - 1)
|
||||
stations.append(param * total_len)
|
||||
|
||||
obj.Stations = stations
|
||||
self._cached_chainage = stations
|
||||
self._cached_points = None
|
||||
|
||||
def get_station_point(self, obj, distance):
|
||||
"""Devuelve el punto en el eje a una distancia (progresiva) dada."""
|
||||
if not obj.SourceWire:
|
||||
return None
|
||||
try:
|
||||
return obj.SourceWire.Shape.valueAt(
|
||||
obj.SourceWire.Shape.getParameterByLength(distance / obj.TotalLength)
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_tangent_at(self, obj, distance):
|
||||
"""Devuelve el vector tangente en una progresiva."""
|
||||
if not obj.SourceWire:
|
||||
return None
|
||||
try:
|
||||
return obj.SourceWire.Shape.tangentAt(
|
||||
obj.SourceWire.Shape.getParameterByLength(distance / obj.TotalLength)
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
|
||||
class _ViewProviderAlignment:
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
|
||||
def getIcon(self):
|
||||
return ""
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
+1
-1
@@ -5,7 +5,7 @@ import zipfile
|
||||
import tempfile
|
||||
import shutil
|
||||
import xml.etree.ElementTree as ET
|
||||
from PySide2 import QtWidgets, QtCore, QtGui
|
||||
from PySide import QtWidgets, QtCore, QtGui
|
||||
import FreeCAD
|
||||
import Mesh
|
||||
import Part
|
||||
|
||||
@@ -72,11 +72,11 @@ class _PVPlantImportDXF:
|
||||
|
||||
def openFile(self):
|
||||
''' '''
|
||||
"getOpenFileName(parent: typing.Union[PySide2.QtWidgets.QWidget, NoneType] = None," \
|
||||
"getOpenFileName(parent: typing.Union[PySide.QtWidgets.QWidget, NoneType] = None," \
|
||||
"caption: str = ''," \
|
||||
"dir: str = ''," \
|
||||
"filter: str = ''," \
|
||||
"options: PySide2.QtWidgets.QFileDialog.Options = Default(QFileDialog.Options)) -> typing.Tuple[str, str]"
|
||||
"options: PySide.QtWidgets.QFileDialog.Options = Default(QFileDialog.Options)) -> typing.Tuple[str, str]"
|
||||
filename, trash = QtGui.QFileDialog().getOpenFileName(None, 'Select File', os.getcwd(), 'Autocad dxf (*.dxf)')
|
||||
if filename == "":
|
||||
return
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
from PySide2 import QtWidgets
|
||||
from PySide import QtWidgets
|
||||
import os
|
||||
|
||||
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
|
||||
@@ -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()
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
import FreeCAD, FreeCADGui
|
||||
#from freecad.trails import ICONPATH
|
||||
from PySide2.QtWidgets import QLabel
|
||||
from PySide.QtWidgets import QLabel
|
||||
import copy
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user