1112 lines
41 KiB
Python
1112 lines
41 KiB
Python
# /**********************************************************************
|
|
# * *
|
|
# * Copyright (c) 2021 Javier Braña <javier.branagutierrez@gmail.com> *
|
|
# * *
|
|
# * This program is free software; you can redistribute it and/or modify*
|
|
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
|
# * as published by the Free Software Foundation; either version 2 of *
|
|
# * the License, or (at your option) any later version. *
|
|
# * for detail see the LICENCE text file. *
|
|
# * *
|
|
# * This program is distributed in the hope that it will be useful, *
|
|
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
# * GNU Library General Public License for more details. *
|
|
# * *
|
|
# * You should have received a copy of the GNU Library General Public *
|
|
# * License along with this program; if not, write to the Free Software *
|
|
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307*
|
|
# * USA *
|
|
# * *
|
|
# ***********************************************************************
|
|
|
|
import os
|
|
|
|
import ArchComponent
|
|
import FreeCAD
|
|
import Part
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
from pivy import coin
|
|
import draftguitools.gui_trackers as DraftTrackers
|
|
else:
|
|
# \cond
|
|
def translate(ctxt, txt):
|
|
return txt
|
|
|
|
|
|
def QT_TRANSLATE_NOOP(ctxt, txt):
|
|
return txt
|
|
# \endcond
|
|
|
|
__title__ = "PVPlant Trench"
|
|
__author__ = "Javier Braña"
|
|
__url__ = "http://www.sogos-solar.com"
|
|
|
|
import PVPlantResources
|
|
from PVPlantResources import DirIcons as DirIcons
|
|
|
|
TrenchFillTypes = ["Arena", "Tierra de excavación", "Cemento", "Graba"]
|
|
# sand color: ece2c6 / 236 226 198
|
|
# tierra color: 4e3b31 / 78 59 49
|
|
MateriralColor = [(0.9255, 0.8863, 0.7765), (0.3059, 0.2314, 0.1922), (0.4902, 0.5176, 0.4431)]
|
|
|
|
|
|
def makeTrenchNode(point=None, trench=None):
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "TrenchNode")
|
|
TrenchNode(obj)
|
|
ViewProviderTrenchNode(obj.ViewObject)
|
|
if point:
|
|
obj.Placement.Base = point
|
|
if trench:
|
|
tl = [trench]
|
|
obj.TrenchList = tl
|
|
return obj
|
|
|
|
|
|
class TrenchNode:
|
|
def __init__(self, obj):
|
|
''' Initialize the class. '''
|
|
self.Type = None
|
|
self.obj = None
|
|
self.setProperties(obj)
|
|
|
|
def setProperties(self, obj):
|
|
pl = obj.PropertiesList
|
|
|
|
if not ("TrenchList" in pl):
|
|
obj.addProperty("App::PropertyLinkList",
|
|
"TrenchList",
|
|
"TrenchNode",
|
|
"Connection")
|
|
|
|
self.obj = obj
|
|
self.Type = "TrenchNode"
|
|
obj.Proxy = self
|
|
|
|
def onDocumentRestored(self, obj):
|
|
""" Method run when the document is restored. """
|
|
self.setProperties(obj)
|
|
|
|
def execute(self, obj):
|
|
pl = obj.Placement
|
|
obj.Shape = Part.makeSphere(1500)
|
|
obj.Placement = pl
|
|
|
|
|
|
class ViewProviderTrenchNode:
|
|
def __init__(self, vobj):
|
|
''' Set view properties. '''
|
|
vobj.Proxy = self
|
|
|
|
def getIcon(self):
|
|
''' Return object treeview icon. '''
|
|
return str(os.path.join(DirIcons, "node.jpg"))
|
|
|
|
|
|
def makeTrench(base=None):
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Trench")
|
|
Trench(obj)
|
|
ViewProviderTrench(obj.ViewObject)
|
|
obj.Base = base
|
|
|
|
try:
|
|
folder = FreeCAD.ActiveDocument.Trenches
|
|
except:
|
|
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'Trenches')
|
|
folder.Label = "Trenches"
|
|
folder.addObject(obj)
|
|
return obj
|
|
|
|
|
|
class Trench(ArchComponent.Component):
|
|
def __init__(self, obj):
|
|
# Definición de Variables:
|
|
ArchComponent.Component.__init__(self, obj)
|
|
self.numLayersSegment = dict()
|
|
self.setProperties(obj)
|
|
self.number_old_segments = int(obj.Segments)
|
|
self.obj = obj
|
|
self.route = False
|
|
|
|
def setProperties(self, obj):
|
|
# Definicion de Propiedades:
|
|
pl = obj.PropertiesList
|
|
|
|
if not ("OffsetStart" in pl):
|
|
obj.addProperty("App::PropertyLength",
|
|
"OffsetStart",
|
|
"Trench",
|
|
"Offset al comienzo")
|
|
|
|
if not ("OffsetEnd" in pl):
|
|
obj.addProperty("App::PropertyLength",
|
|
"OffsetEnd",
|
|
"Trench",
|
|
"Offset al final")
|
|
|
|
if not ("Width" in pl):
|
|
obj.addProperty("App::PropertyLength",
|
|
"Width",
|
|
"Trench",
|
|
QT_TRANSLATE_NOOP("App::Property", "Connection")).Width = 600
|
|
|
|
if not ("Trapezoid" in pl):
|
|
obj.addProperty("App::PropertyBool",
|
|
"Trapezoid",
|
|
"Trench",
|
|
"Connection").Trapezoid = False
|
|
|
|
if not ("Segments" in pl):
|
|
obj.addProperty("App::PropertyIntegerConstraint",
|
|
"Segments",
|
|
"Segments",
|
|
"Segments").Segments = (1, 1, 100, 1)
|
|
|
|
# Cables: ------------------------
|
|
if not ("Cables" in pl):
|
|
obj.addProperty("App::PropertyIntegerConstraint",
|
|
"Cables",
|
|
"Cables",
|
|
"Número de cables").Cables = (1, 1, 20, 1)
|
|
|
|
if not ("DistanceToExcavationBotton" in pl):
|
|
obj.addProperty("App::PropertyLength",
|
|
"DistanceToExcavationBotton",
|
|
"Cables",
|
|
"Separación del cable al fondo de excavación").DistanceToExcavationBotton = 100
|
|
|
|
# Outputs: ------------------------
|
|
if not ("Length" in pl):
|
|
obj.addProperty("App::PropertyLength",
|
|
"Length",
|
|
"Outputs",
|
|
"Length")
|
|
obj.setEditorMode("Length", 1)
|
|
|
|
if not ("Volume" in pl):
|
|
obj.addProperty("App::PropertyVolume",
|
|
"Volume",
|
|
"Outputs",
|
|
"Volume")
|
|
obj.setEditorMode("Volume", 1)
|
|
|
|
if not ("Type" in pl):
|
|
obj.addProperty("App::PropertyString",
|
|
"Type",
|
|
"Base",
|
|
"Type").Type = "Trench"
|
|
obj.setEditorMode("Type", 1)
|
|
|
|
self.Type = obj.Type
|
|
obj.Proxy = self
|
|
obj.IfcType = "Civil Element" ## puede ser: Cable Carrier Segment
|
|
#obj.setEditorMode("IfcType", 1)
|
|
|
|
def onDocumentRestored(self, obj):
|
|
ArchComponent.Component.onDocumentRestored(self, obj)
|
|
self.setProperties(obj)
|
|
|
|
def onBeforeChange(self, obj, prop):
|
|
if prop == "Segments":
|
|
self.number_old_segments = int(obj.Segments)
|
|
|
|
def createSegment(self, obj, ind):
|
|
pl = obj.PropertiesList
|
|
name = f"Start{ind}"
|
|
if not (name in pl):
|
|
obj.addProperty("App::PropertyLength",
|
|
name,
|
|
f"Segment{ind}",
|
|
"Number of layers")
|
|
if ind == 1:
|
|
obj.setEditorMode(name, 1)
|
|
val = 0
|
|
else:
|
|
val = obj.getPropertyByName(f"Start{ind - 1}").Value + 100
|
|
setattr(obj, name, val)
|
|
|
|
name = f"LayersSeg{ind}"
|
|
if not (name in pl):
|
|
obj.addProperty("App::PropertyIntegerConstraint",
|
|
name,
|
|
f"Segment{ind}",
|
|
"Number of layers")
|
|
setattr(obj, name, (2, 2, 6, 1))
|
|
|
|
name = f"Deep{ind}"
|
|
if not (name in pl):
|
|
obj.addProperty("App::PropertyLength",
|
|
name,
|
|
f"Segment{ind}",
|
|
"Number of layers")
|
|
setattr(obj, name, 900)
|
|
|
|
def removeSegment(self, obj, ind):
|
|
lay = getattr(obj, f"LayersSeg{ind}") # TODO: cambiar a S(xx)Layers
|
|
for i in range(1, lay + 1):
|
|
obj.removeProperty(f"Layer{i}HeigthSeg{ind}")
|
|
obj.removeProperty(f"Layer{i}MaterialSeg{ind}")
|
|
obj.removeProperty(f"LayersSeg{ind}")
|
|
obj.removeProperty(f"Start{ind}")
|
|
obj.removeProperty(f"Deep{ind}")
|
|
|
|
def createLayer(self, obj, seg_num, ind):
|
|
pl = obj.PropertiesList
|
|
name = f"Layer{ind + 1}HeigthSeg{seg_num}" # TODO: cambiar a S(xx)Layer(yy)Heigth
|
|
if not (name in pl):
|
|
obj.addProperty("App::PropertyLength",
|
|
name,
|
|
f"Segment{seg_num}",
|
|
"Number of layers")
|
|
setattr(obj, name, 450)
|
|
|
|
name = f"Layer{ind + 1}MaterialSeg{seg_num}" # TODO: cambiar a S(xx)Layer(yy)Material
|
|
if not (name in pl):
|
|
obj.addProperty("App::PropertyEnumeration",
|
|
name,
|
|
f"Segment{seg_num}",
|
|
"Number of layers")
|
|
setattr(obj, name, TrenchFillTypes)
|
|
setattr(obj, name, TrenchFillTypes[ind])
|
|
|
|
def removeLayer(self, obj, seg_num, ind):
|
|
obj.removeProperty(f"Layer{ind}HeigthSeg{seg_num}")
|
|
obj.removeProperty(f"Layer{ind}MaterialSeg{seg_num}")
|
|
|
|
def onChanged(self, obj, prop):
|
|
if prop == "Segments":
|
|
if self.number_old_segments == int(obj.Segments):
|
|
pass
|
|
elif self.number_old_segments < int(obj.Segments):
|
|
for i in range(self.number_old_segments + 1, int(obj.Segments) + 1):
|
|
self.createSegment(obj, i)
|
|
else:
|
|
for i in range(self.number_old_segments, int(obj.Segments), -1):
|
|
self.removeSegment(obj, i)
|
|
|
|
if prop.startswith("LayersSeg"):
|
|
seg_num = int(prop[len("LayersSeg"):])
|
|
if not (prop in self.numLayersSegment):
|
|
for i in range(obj.getPropertyByName(prop)):
|
|
self.createLayer(obj, seg_num, i)
|
|
elif self.numLayersSegment[prop] < obj.getPropertyByName(prop):
|
|
for i in range(self.numLayersSegment[prop], obj.getPropertyByName(prop)):
|
|
self.createLayer(obj, seg_num, i)
|
|
elif self.numLayersSegment[prop] > obj.getPropertyByName(prop):
|
|
for i in range(self.numLayersSegment[prop], obj.getPropertyByName(prop), -1):
|
|
self.removeLayer(obj, seg_num, i)
|
|
self.numLayersSegment[prop] = int(obj.getPropertyByName(prop))
|
|
|
|
def calculateOffset(self, obj, dist):
|
|
offset = obj.makeOffset2D(dist, 2, False, True, True)
|
|
pts_o = []
|
|
for offseti, offsetv in enumerate(offset.Vertexes):
|
|
lns = list()
|
|
for point in pts:
|
|
tmp = FreeCAD.Vector(point)
|
|
tmp.z = 0
|
|
lns.append(offsetv.Point.sub(tmp).Length)
|
|
|
|
tmp = FreeCAD.Vector(offset.Vertexes[offseti].Point)
|
|
tmp.z = pts[lns.index(min(lns))].z
|
|
pts_o.append(tmp)
|
|
|
|
if dist < 0:
|
|
pts_o.insert(0, pts_o.pop(1))
|
|
pts_o.reverse()
|
|
|
|
return pts_o #Part.Wire(Part.makePolygon(points))
|
|
|
|
def execute(self, obj):
|
|
# obj.Shape: compound
|
|
# |- Segments: compound
|
|
# |-- segment x: compound
|
|
# |---- Layer x
|
|
|
|
# |- Path for cables: compound
|
|
# |-- Path 1
|
|
# |-- Path x
|
|
|
|
import Part
|
|
import MeshPart as mp
|
|
|
|
def getVector(edge):
|
|
p1 = edge.Vertexes[0].Point
|
|
p1.z = 0
|
|
p2 = edge.Vertexes[1].Point
|
|
p2.z = 0
|
|
return p2.sub(p1)
|
|
|
|
def getsegments(wire):
|
|
import math
|
|
segments = []
|
|
|
|
segment = [wire.Edges[0]]
|
|
for i in range(1, len(wire.Edges)):
|
|
vec1 = getVector(wire.Edges[i - 1])
|
|
vec2 = getVector(wire.Edges[i])
|
|
angle = math.degrees(vec1.getAngle(vec2))
|
|
if angle <= 1:
|
|
segment.append(wire.Edges[i])
|
|
else:
|
|
segments.append(Part.Wire(segment))
|
|
segment = [wire.Edges[i]]
|
|
segments.append(Part.Wire(segment))
|
|
return segments
|
|
|
|
w = self.calculatePathWire(obj)
|
|
if w is None:
|
|
return
|
|
obj.Base.Visibility = False
|
|
segmetPaths = self.getSegmentPaths(obj, w)
|
|
land = FreeCAD.ActiveDocument.Site.Terrain.Mesh
|
|
d = obj.Width.Value / 2
|
|
sh = Part.makeCompound([])
|
|
|
|
for i in range(1, obj.Segments + 1):
|
|
w = segmetPaths[i - 1]
|
|
tmp = mp.projectShapeOnMesh(w, land, FreeCAD.Vector(0, 0, 1))
|
|
pts = []
|
|
for p in tmp:
|
|
if len(p) > 0:
|
|
pts.extend(p)
|
|
|
|
pts_plane = list()
|
|
for pt in pts:
|
|
tmp = FreeCAD.Vector(pt)
|
|
tmp.z = 0
|
|
pts_plane.append(tmp)
|
|
path_plane = Part.makePolygon(pts_plane)
|
|
|
|
'''o1 = path_plane.makeOffset2D(d, 2, False, True, True)
|
|
o2 = path_plane.makeOffset2D(-d, 2, False, True, True)
|
|
points = calculateOffset(o1)
|
|
points.insert(0, points.pop(1))
|
|
points.reverse()
|
|
points2 = calculateOffset(o2)'''
|
|
|
|
points = self.calculateOffset(path_plane, d)
|
|
points2 = self.calculateOffset(path_plane, -d)
|
|
|
|
w1 = Part.Wire(Part.makePolygon(points))
|
|
w2 = Part.Wire(Part.makePolygon(points2))
|
|
|
|
segments1 = getsegments(w1)
|
|
segments2 = getsegments(w2)
|
|
lines = []
|
|
for sidx in range(len(segments1)):
|
|
reverse = False
|
|
if len(segments1[sidx].Vertexes) >= len(segments2[sidx].Vertexes):
|
|
pl1 = segments1[sidx]
|
|
pl2 = segments2[sidx]
|
|
else:
|
|
pl1 = segments2[sidx]
|
|
pl2 = segments1[sidx]
|
|
reverse = True
|
|
|
|
lines.append(Part.LineSegment(pl1.Vertexes[0].Point, pl2.Vertexes[0].Point).toShape())
|
|
for i1 in range(1, len(pl1.Vertexes) - 1):
|
|
p1 = pl1.Vertexes[i1].Point
|
|
tmp = []
|
|
for i2 in range(1, len(pl2.Vertexes)):
|
|
p2 = pl2.Vertexes[i2].Point
|
|
tmp.append([p2, p1.sub(p2).Length])
|
|
p2 = min(tmp, key=lambda x: x[1])[0]
|
|
if not reverse:
|
|
lines.append(Part.LineSegment(p1, p2).toShape())
|
|
else:
|
|
lines.append(Part.LineSegment(p2, p1).toShape())
|
|
|
|
lines.append(Part.LineSegment(segments1[-1].Vertexes[-1].Point, segments2[-1].Vertexes[-1].Point).toShape())
|
|
loft = Part.makeLoft(lines, False, True, False)
|
|
|
|
lay = Part.makeCompound([])
|
|
if not obj.Trapezoid:
|
|
zz = -obj.getPropertyByName(f"Deep{i}").Value
|
|
for j in range(1, obj.getPropertyByName(f"LayersSeg{i}") + 1):
|
|
h = obj.getPropertyByName(f"Layer{j}HeigthSeg{i}").Value
|
|
tmp = loft.extrude(FreeCAD.Vector(0,0, h))
|
|
tmp.Placement.Base.z = zz
|
|
lay.add(tmp)
|
|
zz += h
|
|
else:
|
|
''' to be defined...'''
|
|
sh.add(lay)
|
|
|
|
gap = 250
|
|
if obj.Cables % 2 == 0:
|
|
xx = -gap/2 - gap * (obj.Cables / 2 - 1) # 25 se cambiará a una variable que indicará la separación entre cables
|
|
else:
|
|
xx = - gap * int(obj.Cables / 2)
|
|
|
|
paths = Part.makeCompound([])
|
|
'''for i in range(obj.Cables):
|
|
o1 = path_plane.makeOffset2D(xx, 2, False, True, True)
|
|
points = calculateOffset(o1)
|
|
for point in points:
|
|
point.z = point.z - obj.Height.Value + obj.DistanceToExcavationBotton.Value
|
|
if points[2].sub(points[0]).Length < points[2].sub(points[1]).Length:
|
|
points.insert(0, points.pop(1))
|
|
if xx < 0:
|
|
points.reverse()
|
|
p = Part.makePolygon(points)
|
|
paths.add(p)
|
|
xx += gap'''
|
|
|
|
obj.Shape = Part.makeCompound([sh, paths])
|
|
obj.Length = path_plane.Length
|
|
obj.Volume = sh.Volume
|
|
|
|
colors = []
|
|
for i in range(1, obj.Segments + 1):
|
|
for j in range(1, obj.getPropertyByName(f"LayersSeg{i}") + 1):
|
|
material = obj.getPropertyByName(f"Layer{j}MaterialSeg{i}")
|
|
#material = obj.getPropertyByName("Layer{0}Material".format(i + 1))
|
|
color = MateriralColor[TrenchFillTypes.index(material)]
|
|
colors.extend([color] * len(obj.Shape.SubShapes[0].SubShapes[i - 1].SubShapes[j - 1].Faces))
|
|
obj.ViewObject.DiffuseColor = colors
|
|
|
|
def calculatePathWire(self, obj):
|
|
if obj.Base:
|
|
wire = None
|
|
if hasattr(obj.Base.Shape, 'Wires') and obj.Base.Shape.Wires:
|
|
wire = obj.Base.Shape.Wires[0]
|
|
elif obj.Base.Shape.Edges:
|
|
wire = Part.Wire(obj.Base.Shape.Edges)
|
|
|
|
if obj.OffsetStart.Value:
|
|
d = obj.OffsetStart.Value
|
|
for i, e in enumerate(wire.Edges):
|
|
if e.Length < d:
|
|
d -= e.Length
|
|
else:
|
|
v = e.Vertexes[-1].Point.sub(e.Vertexes[0].Point).normalize()
|
|
v.multiply(d)
|
|
p = e.Vertexes[0].Point.add(v)
|
|
pts = [ver.Point for ver in wire.Vertexes]
|
|
pts = [p] + pts[i + 1:]
|
|
wire = Part.makePolygon(pts)
|
|
break
|
|
|
|
if obj.OffsetEnd.Value:
|
|
d = wire.Length - obj.OffsetEnd.Value
|
|
for i, e in enumerate(wire.Edges):
|
|
if e.Length < d:
|
|
d -= e.Length
|
|
else:
|
|
v = e.Vertexes[-1].Point.sub(e.Vertexes[0].Point).normalize()
|
|
v.multiply(d)
|
|
p = e.Vertexes[0].Point.add(v)
|
|
pts = [ver.Point for ver in wire.Vertexes]
|
|
pts = pts[:i + 1] + [p]
|
|
wire = Part.makePolygon(pts)
|
|
break
|
|
return wire
|
|
return None
|
|
|
|
def getSegmentPaths(self, obj, wire):
|
|
segmetPaths = []
|
|
for i in range(1, obj.Segments + 1):
|
|
s = obj.getPropertyByName(f"Start{i}").Value
|
|
if (i + 1) <= obj.Segments:
|
|
ln = obj.getPropertyByName(f"Start{i + 1}").Value
|
|
else:
|
|
ln = wire.Length
|
|
f = ln - s
|
|
segmetPaths.append(self.calcualteSementPath(wire, s, f))
|
|
return segmetPaths
|
|
|
|
def calcualteSementPath(self, w, start, length):
|
|
for i, e in enumerate(w.Edges):
|
|
if e.Length < start:
|
|
start -= e.Length
|
|
else:
|
|
v = e.Vertexes[-1].Point.sub(e.Vertexes[0].Point).normalize()
|
|
v.multiply(start)
|
|
p = e.Vertexes[0].Point.add(v)
|
|
pts = [ver.Point for ver in w.Vertexes]
|
|
pts = [p] + pts[i + 1:]
|
|
w = Part.makePolygon(pts)
|
|
break
|
|
|
|
for i, e in enumerate(w.Edges):
|
|
if e.Length < length:
|
|
length -= e.Length
|
|
else:
|
|
v = e.Vertexes[-1].Point.sub(e.Vertexes[0].Point).normalize()
|
|
v.multiply(length)
|
|
p = e.Vertexes[0].Point.add(v)
|
|
pts = [ver.Point for ver in w.Vertexes]
|
|
pts = pts[:i + 1] + [p]
|
|
w = Part.makePolygon(pts)
|
|
break
|
|
return w
|
|
|
|
|
|
class ViewProviderTrench(ArchComponent.ViewProviderComponent):
|
|
def __init__(self, vobj):
|
|
ArchComponent.ViewProviderComponent.__init__(self, vobj)
|
|
|
|
def getIcon(self):
|
|
return str(os.path.join(PVPlantResources.DirIcons, "trench.svg"))
|
|
|
|
|
|
import DraftVecUtils
|
|
import draftutils.utils as utils
|
|
|
|
|
|
class TrenchTaskPanel:
|
|
def __init__(self, obj=None):
|
|
self.new = False
|
|
self.obj = obj
|
|
|
|
if obj is None:
|
|
import Draft
|
|
self.new = True
|
|
self.obj = Part.Shape()
|
|
|
|
self.form = FreeCADGui.PySideUic.loadUi(os.path.join(PVPlantResources.__dir__, "PVPlantTrench.ui"))
|
|
self.form.buttonAddLayer.clicked.connect(self.addLayer)
|
|
self.form.buttonDeleteLayer.clicked.connect(self.removeLayer)
|
|
self.form.buttonUp.clicked.connect(self.moveUp)
|
|
self.form.buttonDown.clicked.connect(self.moveDown)
|
|
|
|
self.trenchnode = None
|
|
self.point = None
|
|
self.points = []
|
|
self.linesegments = []
|
|
self.path = None
|
|
self.pos = None
|
|
self.support = None
|
|
self.info = None
|
|
self.tracker = DraftTrackers.wireTracker(
|
|
Part.Wire(Part.makePolygon([FreeCAD.Vector(), FreeCAD.Vector(1, 1, 1)])))
|
|
|
|
self.view = FreeCADGui.ActiveDocument.ActiveView
|
|
self.call = self.view.addEventCallback("SoEvent", self.action)
|
|
|
|
def action(self, arg):
|
|
|
|
if arg["Type"] == "SoKeyboardEvent":
|
|
if arg["Key"] == "ESCAPE":
|
|
self.finish()
|
|
|
|
elif arg["Type"] == "SoLocation2Event":
|
|
pos = arg['Position']
|
|
point = FreeCADGui.ActiveDocument.ActiveView.getPoint(pos)
|
|
if len(self.points) > 0:
|
|
pts = self.points.copy()
|
|
pts.append(point)
|
|
self.tracker.updateFromPointlist(pts)
|
|
|
|
elif (arg["Type"] == "SoMouseButtonEvent" and
|
|
arg["State"] == "DOWN" and
|
|
arg["Button"] == "BUTTON1"):
|
|
|
|
pos = arg['Position']
|
|
listObjects = FreeCADGui.ActiveDocument.ActiveView.getObjectsInfo((int(pos[0]), int(pos[1])))
|
|
|
|
if len(listObjects) == 1:
|
|
if listObjects[0]["Object"].startswith("Mesh002"):
|
|
print("Press on Mesh002. Point: ", pos)
|
|
self.setPoint(pos)
|
|
|
|
elif len(listObjects) > 1:
|
|
find = False
|
|
for object in listObjects:
|
|
if object["Object"].startswith("TrenchNode"):
|
|
self.trenchnode = FreeCAD.ActiveDocument.getObject(object["Object"])
|
|
self.setPoint(self.trenchnode.Placement.Base)
|
|
find = True
|
|
break
|
|
|
|
elif object["Object"].startswith("Trench"):
|
|
tmp = SplitTrench(FreeCAD.ActiveDocument.getObject(object["Object"]),
|
|
FreeCADGui.ActiveDocument.ActiveView.getPoint(pos))
|
|
print(tmp)
|
|
'''if tmp:
|
|
self.setPoint(tmp[0])
|
|
self.trenchnode = tmp[2]'''
|
|
find = True
|
|
break
|
|
|
|
if not find:
|
|
if listObjects[0]["Object"].startswith("Mesh002"):
|
|
self.setPoint(pos)
|
|
else:
|
|
self.setPoint(FreeCAD.ActiveDocument.getObject(listObjects[0]["Object"].Name).Placement.Base)
|
|
|
|
def setPoint(self, position):
|
|
if isinstance(position, FreeCAD.Vector):
|
|
point = position
|
|
else:
|
|
point = FreeCADGui.ActiveDocument.ActiveView.getPoint(position)
|
|
|
|
self.points.append(point)
|
|
if len(self.points) == 1:
|
|
self.tracker.on()
|
|
self.drawSegment()
|
|
|
|
def finish(self, close=False, some=False):
|
|
""" Terminate the operation. """
|
|
|
|
if len(self.points) > 1:
|
|
import Draft
|
|
self.path = Draft.makeWire(self.points)
|
|
FreeCAD.ActiveDocument.recompute()
|
|
makeTrench(self.path)
|
|
|
|
def removeTemporaryObject(self):
|
|
""" Remove temporary object created. """
|
|
self.tracker.finalize()
|
|
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
|
|
for no in self.linesegments:
|
|
sg.removeChild(no)
|
|
|
|
def undolast(self):
|
|
"""Undoes last line segment."""
|
|
|
|
if len(self.points) > 1:
|
|
self.points.pop()
|
|
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
|
|
no = self.linesegments.pop()
|
|
sg.removeChild(no)
|
|
|
|
def drawSegment(self):
|
|
"""Draws new line segment."""
|
|
|
|
if len(self.points) > 1:
|
|
self.tracker.updateFromPointlist(self.points)
|
|
"""p1 = self.points[-2]
|
|
p2 = self.points[-1]
|
|
print(" linesegment from p1(", p1, ") to p2 (", p2, ")")
|
|
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
|
|
co = coin.SoCoordinate3()
|
|
pts = [[p1.x, p1.y, p1.z], [p2.x, p2.y, p2.z]]
|
|
'''for i in range(len(pts)):
|
|
p = pts[i]
|
|
co.point.set1Value(i, [p.x, p.y, p.z])'''
|
|
co.point.setValues(0, len(pts), pts)
|
|
ma = coin.SoBaseColor()
|
|
ma.rgb = (0, 0, 1)
|
|
st = coin.SoDrawStyle()
|
|
st.style = coin.SoDrawStyle.LINES
|
|
st.lineWidth = 3
|
|
li = coin.SoLineSet()
|
|
li.numVertices.setValue(2)
|
|
no = coin.SoSeparator()
|
|
no.addChild(co)
|
|
no.addChild(ma)
|
|
no.addChild(st)
|
|
no.addChild(li)
|
|
sg.addChild(no)
|
|
self.linesegments.append(no)"""
|
|
|
|
def wipe(self):
|
|
"""Remove all previous segments and starts from last point."""
|
|
|
|
if len(self.points) > 1:
|
|
self.obj.ViewObject.Visibility = False
|
|
self.points = [self.points[-1]]
|
|
|
|
def numericInput(self, numx, numy, numz):
|
|
""" Validate the entry fields in the user interface.
|
|
|
|
This function is called by the toolbar or taskpanel interface
|
|
when valid x, y, and z have been entered in the input fields.
|
|
"""
|
|
self.point = FreeCAD.Vector(numx, numy, numz)
|
|
self.points.append(self.point)
|
|
self.drawSegment()
|
|
self.ui.setNextFocus()
|
|
|
|
def addLayer(self):
|
|
num = self.form.listLayers.count() + 1
|
|
self.form.listLayers.addItem("Layer" + str(num))
|
|
# TODO: add property to obj
|
|
layer = "Layer" + str(num)
|
|
self.obj.addProperty("App::PropertyIntegerList",
|
|
"Name",
|
|
layer,
|
|
layer + " Name"
|
|
)
|
|
setattr(self.obj, "Name", layer)
|
|
|
|
self.obj.addProperty("App::PropertyIntegerList",
|
|
"Description",
|
|
layer,
|
|
layer + " description"
|
|
)
|
|
|
|
self.obj.addProperty("App::PropertyIntegerList",
|
|
"Height",
|
|
layer,
|
|
layer + " Height"
|
|
)
|
|
setattr(self.obj, "Heigth", 100)
|
|
|
|
def removeLayer(self):
|
|
# TODO: remove property to obj
|
|
currentRow = self.form.listLayers.currentRow()
|
|
currentItem = self.form.listLayers.takeItem(currentRow)
|
|
del (currentItem)
|
|
|
|
def moveUp(self):
|
|
currentRow = self.form.listLayers.currentRow()
|
|
currentItem = self.form.listLayers.takeItem(currentRow)
|
|
self.form.listLayers.insertItem(currentRow - 1, currentItem)
|
|
|
|
def moveDown(self):
|
|
currentRow = self.form.listLayers.currentRow()
|
|
currentItem = self.form.listLayers.takeItem(currentRow)
|
|
self.form.listLayers.insertItem(currentRow + 1, currentItem)
|
|
|
|
def accept(self):
|
|
FreeCAD.ActiveDocument.openTransaction("Create Trench")
|
|
self.finish()
|
|
FreeCAD.ActiveDocument.commitTransaction()
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
self.closeForm()
|
|
return True
|
|
|
|
def reject(self):
|
|
self.closeForm()
|
|
return False
|
|
|
|
def closeForm(self):
|
|
print(" .. Closing .. ")
|
|
self.removeTemporaryObject()
|
|
self.view.removeEventCallback("SoEvent", self.call)
|
|
FreeCADGui.Control.closeDialog()
|
|
|
|
|
|
class semiAutomaticTrench:
|
|
def __init__(self):
|
|
import draftguitools.gui_trackers as DraftTrackers
|
|
|
|
self.objects = []
|
|
self.state = 0
|
|
self.direction = None
|
|
self.distance = 1500
|
|
self.point = None
|
|
self.tracker = DraftTrackers.wireTracker(
|
|
Part.Wire(Part.makePolygon([FreeCAD.Vector(), FreeCAD.Vector(1, 1, 1)])))
|
|
|
|
# event callbacks
|
|
self._keyPressedCB = None
|
|
self._mouseMovedCB = None
|
|
self._mousePressedCB = None
|
|
|
|
self.view = FreeCADGui.activeDocument().activeView()
|
|
self.render_manager = FreeCADGui.ActiveDocument.ActiveView.getViewer().getSoRenderManager()
|
|
|
|
# Callbacks
|
|
self.unregister_editing_callbacks()
|
|
self.register_editing_callbacks()
|
|
|
|
# -------------------------------------------------------------------------
|
|
# SCENE EVENTS CALLBACKS
|
|
# -------------------------------------------------------------------------
|
|
def register_editing_callbacks(self):
|
|
""" Register editing callbacks (former action function) """
|
|
|
|
print("Registering callbacks")
|
|
if self._keyPressedCB is None:
|
|
self._keyPressedCB = self.view.addEventCallbackPivy(coin.SoKeyboardEvent.getClassTypeId(), self.keyPressed)
|
|
|
|
if self._mousePressedCB is None:
|
|
self._mousePressedCB = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),
|
|
self.mousePressed)
|
|
|
|
if self._mouseMovedCB is None:
|
|
self._mouseMovedCB = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.mouseMoved)
|
|
|
|
def unregister_editing_callbacks(self):
|
|
""" Remove callbacks used during editing if they exist """
|
|
|
|
print("Unregistering callbacks")
|
|
if self._keyPressedCB:
|
|
self.view.removeEventCallbackPivy(coin.SoKeyboardEvent.getClassTypeId(), self._keyPressedCB)
|
|
self._keyPressedCB = None
|
|
|
|
if self._mousePressedCB:
|
|
self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self._mousePressedCB)
|
|
self._mousePressedCB = None
|
|
|
|
if self._mouseMovedCB:
|
|
self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self._mouseMovedCB)
|
|
self._mouseMovedCB = None
|
|
|
|
# -------------------------------------------------------------------------
|
|
# SCENE EVENT HANDLERS
|
|
# -------------------------------------------------------------------------
|
|
def keyPressed(self, event_callback):
|
|
event = event_callback.getEvent()
|
|
print(event.getKey(), " - ", event.getState())
|
|
if event.getState() == event.UP:
|
|
if event.getKey() == 65307: # ESC
|
|
self.quit()
|
|
elif event.getKey() == 65293: # ENTER
|
|
print("ENTER")
|
|
self.state += 1
|
|
if self.state == 2:
|
|
'''print("----------------------------------------------------------------")
|
|
print(" -- objects: ")
|
|
print(self.objects)
|
|
print(" -- distance:")
|
|
print(self.distance)
|
|
print("----------------------------------------------------------------")'''
|
|
self.calculateTrenches()
|
|
self.quit()
|
|
|
|
'''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.selected_objects)):
|
|
if isinstance(self.selected_objects[i], MarkerOnShape):
|
|
self.selected_objects[i].sublink = tup
|
|
# FreeCAD.Console.PrintMessage("Snapped to {}\n".format(str(self.selected_objects[i].sublink)))
|
|
self.selected_objects[i].drag_start()
|
|
self.selected_objects[i].drag((0, 0, 0.))
|
|
self.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.dynamic_objects:
|
|
if isinstance(o, MarkerOnShape):
|
|
pts.append(o)
|
|
self.points = pts
|
|
self.setupInteractionSeparator()'''
|
|
|
|
def mousePressed(self, event_callback):
|
|
""" Mouse button event handler, calls: startEditing, endEditing, addPoint, delPoint """
|
|
event = event_callback.getEvent()
|
|
pos = event.getPosition().getValue()
|
|
listObjects = FreeCADGui.ActiveDocument.ActiveView.getObjectsInfo((int(pos[0]), int(pos[1])))
|
|
|
|
if event.getButton() == event.BUTTON1: # left click
|
|
if event.getState() == coin.SoMouseButtonEvent.DOWN:
|
|
if self.state == 0:
|
|
''' Select objects '''
|
|
obj = FreeCAD.ActiveDocument.getObject(listObjects[0]['Object'])
|
|
if obj in self.objects:
|
|
self.objects.remove(obj)
|
|
else:
|
|
self.objects.append(obj)
|
|
numobjs = len(self.objects)
|
|
if numobjs == 0:
|
|
self.tracker.finalize()
|
|
elif numobjs == 1:
|
|
self.tracker.updateFromPointlist([obj.Placement.Base for obj in self.objects])
|
|
self.tracker.on()
|
|
else:
|
|
self.tracker.updateFromPointlist([obj.Placement.Base for obj in self.objects])
|
|
|
|
elif self.state == 1:
|
|
''' Select distance and direction '''
|
|
self.direction = FreeCAD.Vector(listObjects[0]['x'], listObjects[0]['y'], listObjects[0]['z'])
|
|
# self.point = None
|
|
|
|
elif event.getState() == coin.SoMouseButtonEvent.UP:
|
|
if listObjects and self.point:
|
|
if self.state == 0:
|
|
FreeCADGui.Selection.clearSelection()
|
|
for obj in listObjects:
|
|
FreeCADGui.Selection.addSelection(obj)
|
|
|
|
"""if (event.getState() == coin.SoMouseButtonEvent.DOWN) and (
|
|
event.getButton() == event.BUTTON1): # left click
|
|
print("Mouse button down and mouse button 1")
|
|
if not event.wasAltDown():
|
|
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, event_callback):
|
|
""" Execute as callback for mouse movement. Update tracker position and update preview ghost. """
|
|
if self.state == 1:
|
|
event = event_callback.getEvent()
|
|
pos = event.getPosition().getValue()
|
|
listObjects = FreeCADGui.ActiveDocument.ActiveView.getObjectsInfo((int(pos[0]), int(pos[1])))
|
|
print(listObjects)
|
|
point = FreeCAD.Vector(listObjects[0]['x'], listObjects[0]['y'], listObjects[0]['z'])
|
|
pts = [obj.Placement.Base for obj in self.objects]
|
|
offset = point.sub(pts[0])
|
|
self.tracker.updateFromPointlist([point.add(offset) for point in pts])
|
|
|
|
def calculateTrenches(self):
|
|
if len(self.objects) > 1:
|
|
import Draft
|
|
pts = [obj.Placement.Base for obj in self.objects]
|
|
vec = self.direction.sub(pts[0])
|
|
pts1 = [point.add(vec) for point in pts]
|
|
for i in range(len(pts1) - 1):
|
|
makeTrench(Draft.makeLine(pts1[i], pts1[i + 1]))
|
|
|
|
return
|
|
pts = [obj.Placement.Base for obj in FreeCADGui.Selection.getSelection()]
|
|
from sklearn.cluster import OPTICS, cluster_optics_dbscan
|
|
import numpy as np
|
|
clust = OPTICS(min_samples=5, xi=0.05, min_cluster_size=0.05)
|
|
X = np.array(pts)
|
|
|
|
# Run the fit
|
|
clust.fit(X)
|
|
|
|
labels_050 = cluster_optics_dbscan(
|
|
reachability=clust.reachability_,
|
|
core_distances=clust.core_distances_,
|
|
ordering=clust.ordering_,
|
|
eps=0.5,
|
|
)
|
|
labels_200 = cluster_optics_dbscan(
|
|
reachability=clust.reachability_,
|
|
core_distances=clust.core_distances_,
|
|
ordering=clust.ordering_,
|
|
eps=2,
|
|
)
|
|
|
|
space = np.arange(len(X))
|
|
reachability = clust.reachability_[clust.ordering_]
|
|
labels = clust.labels_[clust.ordering_]
|
|
print("\n")
|
|
print(" Space: ", space)
|
|
print(" Reachability", reachability)
|
|
print(" Labels", labels)
|
|
|
|
return
|
|
from scipy import stats
|
|
xx = list()
|
|
yy = list()
|
|
zz = list()
|
|
for point in pts:
|
|
xx.append(point.x)
|
|
yy.append(point.y)
|
|
zz.append(point.z)
|
|
slope, intercept, r, p, std_err = stats.linregress(xx, yy)
|
|
|
|
def myfunc(val):
|
|
return slope * val + intercept
|
|
|
|
newzz = list(map(myfunc, [xx[0], xx[-1]]))
|
|
points3D = list()
|
|
points3D.append(FreeCAD.Vector(xx[0], yy[0], newzz[0]))
|
|
points3D.append(FreeCAD.Vector(xx[-1], yy[-1], newzz[1]))
|
|
|
|
def quit(self):
|
|
print("Quit")
|
|
self.tracker.finalize()
|
|
self.unregister_editing_callbacks()
|
|
|
|
|
|
class CommandTrench: # V1:
|
|
"""Gui command for the Line tool."""
|
|
|
|
def GetResources(self):
|
|
"""Set icon, menu and tooltip."""
|
|
return {'Pixmap': str(os.path.join(DirIcons, "trench.svg")),
|
|
'MenuText': "Trench",
|
|
'Accel': "C, T",
|
|
'ToolTip': "Creates a Trench object from setup dialog."}
|
|
|
|
def IsActive(self):
|
|
active = not (FreeCAD.ActiveDocument is None)
|
|
terrain = not (FreeCAD.ActiveDocument.getObject("Terrain") is None)
|
|
active = active and terrain
|
|
if terrain:
|
|
active = active and not (FreeCAD.ActiveDocument.getObject("Terrain").Mesh is None)
|
|
return active
|
|
|
|
def Activated(self):
|
|
"""Execute when the command is called."""
|
|
|
|
sel = FreeCADGui.Selection.getSelection()
|
|
done = False
|
|
|
|
if len(sel) > 0:
|
|
import Draft
|
|
for obj in sel:
|
|
if Draft.getType(obj) == "Wire":
|
|
FreeCAD.ActiveDocument.openTransaction("Create Trench")
|
|
makeTrench(obj)
|
|
FreeCAD.ActiveDocument.commitTransaction()
|
|
FreeCAD.ActiveDocument.recompute()
|
|
done = True
|
|
break
|
|
|
|
if not done:
|
|
taskd = TrenchTaskPanel()
|
|
if taskd:
|
|
FreeCADGui.Control.showDialog(taskd)
|
|
else:
|
|
print(" No ha sido posible crear el formulario")
|
|
|
|
|
|
class CommandSemiAutomaticTrench: # V1:
|
|
"""Gui command for the Line tool."""
|
|
|
|
def GetResources(self):
|
|
"""Set icon, menu and tooltip."""
|
|
return {'Pixmap': str(os.path.join(DirIcons, "trench.svg")),
|
|
'MenuText': "Semi-Automatic Trench Generator",
|
|
'Accel': "T, S",
|
|
'ToolTip': "Creates a Trench object from setup dialog."}
|
|
|
|
def IsActive(self):
|
|
active = not (FreeCAD.ActiveDocument is None)
|
|
terrain = not (FreeCAD.ActiveDocument.getObject("Terrain") is None)
|
|
active = active and terrain
|
|
if terrain:
|
|
active = active and not (FreeCAD.ActiveDocument.getObject("Terrain").Mesh is None)
|
|
return active
|
|
|
|
def Activated(self):
|
|
"""Execute when the command is called."""
|
|
semi = semiAutomaticTrench()
|
|
|
|
|
|
if FreeCAD.GuiUp:
|
|
class CommandTrenchGroup:
|
|
|
|
def GetCommands(self):
|
|
return tuple(['PVPlantTrench',
|
|
'PVPlantSemiAutomaticTrench',
|
|
])
|
|
|
|
def GetResources(self):
|
|
return {'MenuText': 'Rack Types',
|
|
'ToolTip': 'Rack Types'
|
|
}
|
|
|
|
def IsActive(self):
|
|
active = not (FreeCAD.ActiveDocument is None)
|
|
terrain = not (FreeCAD.ActiveDocument.getObject("Terrain") is None)
|
|
active = active and terrain
|
|
if terrain:
|
|
active = active and not (FreeCAD.ActiveDocument.getObject("Terrain").Mesh is None)
|
|
return active
|
|
|
|
FreeCADGui.addCommand('PVPlantTrench', CommandTrench())
|
|
FreeCADGui.addCommand('PVPlantSemiAutomaticTrench', CommandSemiAutomaticTrench())
|
|
FreeCADGui.addCommand('Trenches', CommandTrenchGroup())
|
|
|