889 lines
33 KiB
Python
889 lines
33 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 copy
|
|
import math
|
|
|
|
import ArchComponent
|
|
import Draft
|
|
import FreeCAD
|
|
import Part
|
|
|
|
import PVPlantFencePost
|
|
import PVPlantSite
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
from PySide import QtCore, QtGui
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
import PySide.QtGui as QtGui
|
|
from pivy import coin
|
|
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 os
|
|
from PVPlantResources import DirIcons as DirIcons
|
|
|
|
EAST = FreeCAD.Vector(1, 0, 0)
|
|
|
|
def makeprojection(pathwire):
|
|
site = FreeCAD.ActiveDocument.Site
|
|
land = site.Terrain.Shape
|
|
proj = land.makeParallelProjection(pathwire, FreeCAD.Vector(0, 0, 1))
|
|
return proj
|
|
|
|
def makePVPlantFence(section, post, path):
|
|
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Fence')
|
|
|
|
_Fence(obj)
|
|
obj.Post = post
|
|
obj.Base = path
|
|
|
|
if FreeCAD.GuiUp:
|
|
_ViewProviderFence(obj.ViewObject)
|
|
|
|
hide(section)
|
|
hide(post)
|
|
hide(path)
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
return obj
|
|
|
|
def hide(obj):
|
|
if hasattr(obj, 'ViewObject') and obj.ViewObject:
|
|
obj.ViewObject.Visibility = False
|
|
|
|
def getAngle(Line1, Line2):
|
|
v1 = Line1.Vertexes[1].Point - Line1.Vertexes[0].Point
|
|
v2 = Line2.Vertexes[1].Point - Line2.Vertexes[0].Point
|
|
return v1.getAngle(v2)
|
|
|
|
|
|
def get_parameter_from_v0(edge, offset):
|
|
"""Return parameter at distance offset from edge.Vertexes[0].
|
|
sb method in Part.TopoShapeEdge???
|
|
"""
|
|
import DraftVecUtils
|
|
|
|
lpt = edge.valueAt(edge.getParameterByLength(0))
|
|
vpt = edge.Vertexes[0].Point
|
|
|
|
if not DraftVecUtils.equals(vpt, lpt):
|
|
# this edge is flipped
|
|
length = edge.Length - offset
|
|
else:
|
|
# this edge is right way around
|
|
length = offset
|
|
return edge.getParameterByLength(length)
|
|
|
|
|
|
def calculatePlacement(globalRotation, edge, offset, RefPt, xlate, align, normal=None):
|
|
"""Orient shape to tangent at parm offset along edge."""
|
|
import functools
|
|
import DraftVecUtils
|
|
# http://en.wikipedia.org/wiki/Euler_angles
|
|
# start with null Placement point so translate goes to right place.
|
|
|
|
placement = FreeCAD.Placement()
|
|
placement.Rotation = globalRotation
|
|
placement.move(RefPt + xlate)
|
|
|
|
if not align:
|
|
return placement
|
|
|
|
# unit +Z Probably defined elsewhere?
|
|
z = FreeCAD.Vector(0, 0, 1)
|
|
# y = FreeCAD.Vector(0, 1, 0) # unit +Y
|
|
x = FreeCAD.Vector(1, 0, 0) # unit +X
|
|
nullv = FreeCAD.Vector(0, 0, 0)
|
|
|
|
# get local coord system - tangent, normal, binormal, if possible
|
|
t = edge.tangentAt(get_parameter_from_v0(edge, offset))
|
|
t.normalize()
|
|
n = normal
|
|
b = t.cross(n)
|
|
b.normalize()
|
|
|
|
lnodes = z.cross(b)
|
|
try:
|
|
# Can't normalize null vector.
|
|
lnodes.normalize()
|
|
except:
|
|
# pathological cases:
|
|
pass
|
|
|
|
print(b, " - ", b.dot(z))
|
|
if abs(b.dot(z)) == 1.0: # 2) binormal is || z
|
|
# align shape to tangent only
|
|
psi = math.degrees(DraftVecUtils.angle(x, t, z))
|
|
theta = 0.0
|
|
phi = 0.0
|
|
FreeCAD.Console.PrintWarning("Draft PathArray.orientShape - Gimbal lock. Infinite lnodes. Change Path or Base.\n")
|
|
else: # regular case
|
|
psi = math.degrees(DraftVecUtils.angle(x, lnodes, z))
|
|
theta = math.degrees(DraftVecUtils.angle(z, b, lnodes))
|
|
phi = math.degrees(DraftVecUtils.angle(lnodes, t, b))
|
|
|
|
rotations = [placement.Rotation]
|
|
|
|
if psi != 0.0:
|
|
rotations.insert(0, FreeCAD.Rotation(z, psi))
|
|
if theta != 0.0:
|
|
rotations.insert(0, FreeCAD.Rotation(lnodes, theta))
|
|
if phi != 0.0:
|
|
rotations.insert(0, FreeCAD.Rotation(b, phi))
|
|
|
|
if len(rotations) == 1:
|
|
finalRotation = rotations[0]
|
|
else:
|
|
finalRotation = functools.reduce(lambda rot1, rot2: rot1.multiply(rot2), rotations)
|
|
|
|
placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), finalRotation.toEuler()[2])
|
|
|
|
return placement
|
|
|
|
def calculatePlacementsOnPath(shapeRotation, pathwire, count, xlate, align):
|
|
""" Calculates the placements of a shape along a given path so that each copy will be distributed evenly
|
|
- shapeRotation: rotation offset
|
|
- pathwire: path where place the shapes
|
|
- count: number of sections
|
|
- xlate: transformation offset
|
|
"""
|
|
import Part
|
|
import DraftGeomUtils
|
|
|
|
closedpath = DraftGeomUtils.isReallyClosed(pathwire)
|
|
normal = DraftGeomUtils.getNormal(pathwire)
|
|
if normal:
|
|
if normal.z < 0: # asegurarse de que siempre se dibuje por encima del suelo
|
|
normal.z *= -1
|
|
else:
|
|
normal = FreeCAD.Vector(0, 0, 1)
|
|
path = Part.__sortEdges__(pathwire.Edges)
|
|
ends = []
|
|
cdist = 0
|
|
|
|
# -----------------------------------------------------------------------------------------------------------------
|
|
totalEdges = len(path)
|
|
if not closedpath:
|
|
totalEdges -= 1
|
|
# -----------------------------------------------------------------------------------------------------------------
|
|
|
|
for e in path: # find cumulative edge end distance
|
|
cdist += e.Length
|
|
ends.append(cdist)
|
|
|
|
placements = []
|
|
# place the start shape
|
|
pt = path[0].Vertexes[0].Point
|
|
placements.append(calculatePlacement(shapeRotation, path[0], 0, pt, xlate, align, normal))
|
|
|
|
# closed path doesn't need shape on last vertex
|
|
if not closedpath:
|
|
# place the end shape
|
|
pt = path[-1].Vertexes[-1].Point
|
|
placements.append(calculatePlacement(shapeRotation, path[-1], path[-1].Length, pt, xlate, align, normal))
|
|
|
|
if count < 3:
|
|
return placements
|
|
|
|
# place the middle shapes
|
|
if closedpath:
|
|
stop = count
|
|
else:
|
|
stop = count - 1
|
|
step = float(cdist) / stop
|
|
travel = step
|
|
for i in range(1, stop):
|
|
# which edge in path should contain this shape?
|
|
# avoids problems with float math travel > ends[-1]
|
|
iend = len(ends) - 1
|
|
for j in range(0, len(ends)):
|
|
if travel <= ends[j]:
|
|
iend = j
|
|
break
|
|
# place shape at proper spot on proper edge
|
|
remains = ends[iend] - travel
|
|
offset = path[iend].Length - remains
|
|
pt = path[iend].valueAt(get_parameter_from_v0(path[iend], offset))
|
|
placements.append(calculatePlacement(shapeRotation, path[iend], offset, pt, xlate, align, normal))
|
|
travel += step
|
|
|
|
return placements
|
|
|
|
class _Fence(ArchComponent.Component):
|
|
def __init__(self, obj):
|
|
ArchComponent.Component.__init__(self, obj)
|
|
self.setProperties(obj)
|
|
self.Posts = []
|
|
self.Foundations = []
|
|
|
|
# Does a IfcType exist?
|
|
# obj.IfcType = "Fence"
|
|
|
|
def setProperties(self, obj):
|
|
ArchComponent.Component.setProperties(self, obj)
|
|
|
|
pl = obj.PropertiesList
|
|
|
|
if not "Gate" in pl:
|
|
obj.addProperty("App::PropertyLinkList",
|
|
"Gate",
|
|
"Fence",
|
|
"A single fence post")
|
|
|
|
if not "Post" in pl:
|
|
obj.addProperty("App::PropertyLink",
|
|
"Post",
|
|
"Fence",
|
|
QT_TRANSLATE_NOOP("App::Property", "A single fence post"))
|
|
|
|
if not "Foundation" in pl:
|
|
obj.addProperty("App::PropertyLength",
|
|
"Foundation",
|
|
"Fence",
|
|
QT_TRANSLATE_NOOP("App::Property", "A single fence post"))
|
|
|
|
if not "Depth" in pl:
|
|
obj.addProperty("App::PropertyLength",
|
|
"Depth",
|
|
"Fence",
|
|
QT_TRANSLATE_NOOP("App::Property", "A single fence post")).Depth = 650
|
|
|
|
if not "Gap" in pl:
|
|
obj.addProperty("App::PropertyLength",
|
|
"Gap",
|
|
"Fence",
|
|
QT_TRANSLATE_NOOP("App::Property", "A single fence post")).Gap = 4000
|
|
|
|
if not "Angle" in pl:
|
|
obj.addProperty("App::PropertyAngle",
|
|
"Angle",
|
|
"Fence",
|
|
QT_TRANSLATE_NOOP("App::Property", "A single fence post")).Angle = 60
|
|
|
|
if not "MeshOffsetZ" in pl:
|
|
obj.addProperty("App::PropertyLength",
|
|
"MeshOffsetZ",
|
|
"Fence",
|
|
QT_TRANSLATE_NOOP("App::Property", "A single fence post")).MeshOffsetZ = 0
|
|
|
|
if not "MeshHeight" in pl:
|
|
obj.addProperty("App::PropertyLength",
|
|
"MeshHeight",
|
|
"Fence",
|
|
QT_TRANSLATE_NOOP("App::Property", "A single fence post")).MeshHeight = 2000
|
|
|
|
if not "Reinforce" in pl:
|
|
obj.addProperty("App::PropertyLength",
|
|
"Reinforce",
|
|
"Fence",
|
|
QT_TRANSLATE_NOOP("App::Property", "A single fence post")).Reinforce = 50000
|
|
|
|
|
|
########## Datos informativos:
|
|
if not "NumberOfSections" in pl:
|
|
obj.addProperty("App::PropertyQuantity",
|
|
"NumberOfSections",
|
|
"Output",
|
|
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::PropertyQuantity",
|
|
"NumberOfPosts",
|
|
"Output",
|
|
QT_TRANSLATE_NOOP("App::Property", "The number of posts used to build the fence"))
|
|
obj.setEditorMode("NumberOfPosts", 1)
|
|
|
|
if not "Length" in pl:
|
|
obj.addProperty("App::PropertyLength",
|
|
"Length",
|
|
"Output",
|
|
QT_TRANSLATE_NOOP("App::Property", "The number of posts used to build the fence"))
|
|
obj.setEditorMode("Length", 1)
|
|
|
|
if not "Concrete" in pl:
|
|
obj.addProperty("App::PropertyVolume",
|
|
"Concrete",
|
|
"Output",
|
|
"Concrete Volume")
|
|
obj.setEditorMode("Concrete", 1)
|
|
|
|
if not "PlacementList" in pl:
|
|
obj.addProperty("App::PropertyPlacementList",
|
|
"PlacementList",
|
|
"Output",
|
|
QT_TRANSLATE_NOOP("App::Property", "The number of posts used to build the fence"))
|
|
obj.setEditorMode("Length", 1)
|
|
|
|
|
|
self.Type = "PVPlatFence"
|
|
|
|
def __getstate__(self):
|
|
if hasattr(self, 'sectionFaceNumbers'):
|
|
return (self.sectionFaceNumbers)
|
|
return None
|
|
|
|
def __setstate__(self, state):
|
|
if state is not None and isinstance(state, tuple):
|
|
self.sectionFaceNumbers = state[0]
|
|
return None
|
|
|
|
def execute(self, obj):
|
|
|
|
pathwire = self.calculatePathWire(obj)
|
|
if pathwire is None:
|
|
# FreeCAD.Console.PrintLog("ArchFence.execute: path " + obj.Base.Name + " has no edges\n")
|
|
return
|
|
|
|
if not obj.Post:
|
|
FreeCAD.Console.PrintLog("ArchFence.execute: Post not set\n")
|
|
return
|
|
|
|
self.Posts = []
|
|
self.Foundations = []
|
|
site = PVPlantSite.get()
|
|
if True: # prueba
|
|
import MeshPart as mp
|
|
land = FreeCAD.ActiveDocument.Terrain.Mesh
|
|
segments = mp.projectShapeOnMesh(pathwire, land, FreeCAD.Vector(0, 0, 1))
|
|
points=[]
|
|
for segment in segments:
|
|
points.extend(segment)
|
|
pathwire = Part.makePolygon(points)
|
|
else:
|
|
if PVPlantSite.get().Terrain.TypeId == 'Mesh::Feature':
|
|
import MeshPart as mp
|
|
land = PVPlantSite.get().Terrain.Mesh
|
|
pathwire = mp.projectShapeOnMesh(pathwire, land, FreeCAD.Vector(0, 0, 1))
|
|
|
|
else:
|
|
land = site.Terrain.Shape
|
|
pathwire = land.makeParallelProjection(pathwire, FreeCAD.Vector(0, 0, 1))
|
|
|
|
if pathwire is None:
|
|
return
|
|
|
|
''' no sirve:
|
|
if len(pathwire.Wires) > 1:
|
|
import draftgeoutils
|
|
pathwire = draftgeoutils.wires.superWire(pathwire.Edges, True)
|
|
Part.show(pathwire)
|
|
'''
|
|
|
|
''' unir todas en una '''
|
|
'''
|
|
if len(pathwire.Wires) > 1:
|
|
import Utils.PVPlantUtils as utils
|
|
wires = pathwire.Wires
|
|
new_wire = []
|
|
to_compare = utils.getPoints(wires.pop(0))
|
|
new_wire.extend(to_compare)
|
|
while len(wires)>0:
|
|
wire = wires[0]
|
|
points = utils.getPoints(wire)
|
|
to_remove = None
|
|
if points[0] in to_compare:
|
|
to_remove = points[0]
|
|
if points[-1] in to_compare:
|
|
to_remove = points[-1]
|
|
if to_remove:
|
|
to_compare = points.copy()
|
|
points.remove(to_remove)
|
|
new_wire.extend(points)
|
|
wires.pop()
|
|
continue
|
|
wires.append(wires.pop())
|
|
pathwire = Part.makePolygon(new_wire)
|
|
#Part.show(pathwire)
|
|
#return
|
|
'''
|
|
|
|
sectionLength = obj.Gap.Value
|
|
postLength = 0 #obj.Post.Diameter.Value #considerarlo 0 porque no influye
|
|
postPlacements = []
|
|
pathsegments = self.calculateSegments(obj, pathwire)
|
|
|
|
count = 0
|
|
drawFirstPost = True
|
|
pathLength = 0
|
|
for segment in pathsegments:
|
|
segwire = Part.Wire(Part.__sortEdges__(segment))
|
|
pathLength += segwire.Length
|
|
|
|
obj.NumberOfSections = self.calculateNumberOfSections(segwire.Length, sectionLength, postLength)
|
|
obj.NumberOfPosts = obj.NumberOfSections + 1
|
|
|
|
count += obj.NumberOfSections
|
|
|
|
downRotation = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), -90)
|
|
placements = self.calculatePostPlacements(obj, segwire, downRotation)
|
|
|
|
if drawFirstPost:
|
|
drawFirstPost = False
|
|
else:
|
|
placements.pop(0)
|
|
|
|
postPlacements.extend(placements)
|
|
|
|
postShapes, postFoundation = self.calculatePosts(obj, postPlacements)
|
|
sections, num = self.calculateSections(obj, postPlacements)
|
|
|
|
postShapes = Part.makeCompound(postShapes)
|
|
postFoundation = Part.makeCompound(postFoundation)
|
|
sections = Part.makeCompound(sections)
|
|
compound = Part.makeCompound([postShapes, postFoundation, sections])
|
|
obj.Shape = compound
|
|
|
|
# Give information
|
|
obj.NumberOfSections = count
|
|
obj.NumberOfPosts = obj.NumberOfSections + 1
|
|
obj.Length = pathLength
|
|
obj.Concrete = count * postFoundation.SubShapes[0].Volume
|
|
|
|
def calculateSegments(self, obj, pathwire):
|
|
''' Calcular los segmentos de la ruta '''
|
|
import math
|
|
segments = []
|
|
segment = [pathwire.Edges[0]]
|
|
segments.append(segment)
|
|
for ind in range(len(pathwire.Edges) - 1):
|
|
ed1 = pathwire.Edges[ind]
|
|
ed2 = pathwire.Edges[ind + 1]
|
|
vec1 = ed1.Vertexes[1].Point - ed1.Vertexes[0].Point
|
|
vec2 = ed2.Vertexes[1].Point - ed2.Vertexes[0].Point
|
|
angle = math.degrees(vec1.getAngle(vec2))
|
|
if angle > obj.Angle.Value:
|
|
segment = []
|
|
segments.append(segment)
|
|
segment.append(ed2)
|
|
return segments
|
|
|
|
def calculateNumberOfSections(self, pathLength, sectionLength, postLength):
|
|
withoutLastPost = pathLength - postLength
|
|
realSectionLength = sectionLength + postLength
|
|
return math.ceil(withoutLastPost / realSectionLength)
|
|
|
|
def calculatePostPlacements(self, obj, pathwire, rotation):
|
|
postWidth = obj.Post.Diameter.Value
|
|
transformationVector = FreeCAD.Vector(0, postWidth / 2, 0)
|
|
placements = calculatePlacementsOnPath(rotation, pathwire, int(obj.NumberOfSections) + 1, transformationVector, True)
|
|
# The placement of the last object is always the second entry in the list.
|
|
# So we move it to the end:
|
|
if len(placements) > 1:
|
|
placements.append(placements.pop(1))
|
|
return placements
|
|
|
|
def calculatePosts(self, obj, postPlacements):
|
|
posts = []
|
|
foundations = []
|
|
for placement in postPlacements:
|
|
postCopy = obj.Post.Shape.copy()
|
|
postCopy = Part.Solid(postCopy)
|
|
postCopy.Placement = placement
|
|
postCopy.Placement.Base.z += 100
|
|
posts.append(postCopy)
|
|
|
|
foundation = Part.makeCylinder(150, 700)
|
|
foundation.Placement = placement
|
|
foundation.Placement.Base.z -= obj.Depth.Value
|
|
foundation = foundation.cut(postCopy)
|
|
foundations.append(foundation)
|
|
|
|
return posts, foundations
|
|
|
|
def calculateSections(self, obj, postPlacements):
|
|
shapes = []
|
|
faceNumbers = []
|
|
|
|
offsetz = obj.MeshOffsetZ.Value
|
|
meshHeight = obj.MeshHeight.Value
|
|
for i in range(len(postPlacements) - 1):
|
|
startPlacement = postPlacements[i]
|
|
endPlacement = postPlacements[i + 1]
|
|
|
|
p1 = startPlacement.Base + FreeCAD.Vector(0, 0, offsetz)
|
|
p2 = endPlacement.Base + FreeCAD.Vector(0, 0, offsetz)
|
|
p3 = p2 + FreeCAD.Vector(0, 0, meshHeight)
|
|
p4 = p1 + FreeCAD.Vector(0, 0, meshHeight)
|
|
pointlist = [p1, p2, p3, p4, p1]
|
|
|
|
try:
|
|
pol = Part.makePolygon(pointlist)
|
|
face = Part.Face(pol)
|
|
shapes.append(face)
|
|
faceNumbers.append(1)
|
|
except:
|
|
print("No es posible crear la cara: ---------------------------------------------------")
|
|
print(" +++++ Start: ", startPlacement.Base, " - end: ", endPlacement.Base)
|
|
print(" +++++ algo: ", pointlist, "\n")
|
|
print("---------------------------------------------------\n")
|
|
return (shapes, faceNumbers)
|
|
|
|
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)
|
|
return wire
|
|
return None
|
|
|
|
|
|
class _ViewProviderFence(ArchComponent.ViewProviderComponent):
|
|
"A View Provider for the Fence object"
|
|
|
|
def __init__(self, vobj):
|
|
ArchComponent.ViewProviderComponent.__init__(self, vobj)
|
|
vobj.Proxy = self
|
|
#vobj.addExtension("Gui::ViewProviderGroupExtensionPython")
|
|
|
|
def getIcon(self):
|
|
return str(os.path.join(DirIcons, "fence.svg"))
|
|
|
|
def attach(self, vobj):
|
|
'''
|
|
Create Object visuals in 3D view.
|
|
'''
|
|
|
|
self.Object = vobj.Object
|
|
|
|
# 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()
|
|
|
|
# Face root.
|
|
faces = coin.SoSeparator()
|
|
faces.addChild(shape_hints)
|
|
faces.addChild(self.face_material)
|
|
faces.addChild(mat_binding)
|
|
faces.addChild(self.geo_coords)
|
|
faces.addChild(self.triangles)
|
|
|
|
# Highlight for selection.
|
|
highlight = coin.SoType.fromName('SoFCSelection').createInstance()
|
|
highlight.style = 'EMISSIVE_DIFFUSE'
|
|
faces.addChild(shape_hints)
|
|
highlight.addChild(self.edge_material)
|
|
highlight.addChild(mat_binding)
|
|
highlight.addChild(self.edge_style)
|
|
highlight.addChild(self.geo_coords)
|
|
highlight.addChild(self.triangles)
|
|
|
|
def updateData(self, obj, prop):
|
|
'''
|
|
Update Object visuals when a data property changed.
|
|
'''
|
|
origin = PVPlantSite.get()
|
|
base = copy.deepcopy(origin.Origin)
|
|
base.z = 0
|
|
#print(" - Propiedad: ", prop)
|
|
if prop == "Shape":
|
|
shape = obj.getPropertyByName(prop)
|
|
# Get GeoOrigin.
|
|
points = [ver.Point for ver in shape.Vertexes]
|
|
# Set GeoCoords.
|
|
geo_system = ["UTM", origin.UtmZone, "FLAT"]
|
|
self.geo_coords.geoSystem.setValues(geo_system)
|
|
self.geo_coords.point.values = points
|
|
|
|
def claimChildren(self):
|
|
children = []
|
|
if self.Object.Post:
|
|
children.append(self.Object.Post)
|
|
if self.Object.Base:
|
|
children.append(self.Object.Base)
|
|
if self.Object.Gate:
|
|
children.append(self.Object.Gate)
|
|
return children
|
|
|
|
class _FenceTaskPanel:
|
|
'''The TaskPanel to setup the fence'''
|
|
|
|
def __init__(self):
|
|
self.section = None
|
|
self.post = None
|
|
self.path = None
|
|
|
|
# form: -----------------------------------------------------------------------------------
|
|
self.formFence = QtGui.QWidget()
|
|
self.formFence.resize(800, 640)
|
|
self.formFence.setWindowTitle("Fence setup")
|
|
self.formFence.setWindowIcon(QtGui.QIcon(os.path.join(DirIcons, "contours.svg")))
|
|
self.grid = QtGui.QGridLayout(self.formFence)
|
|
|
|
# parameters
|
|
self.labelPath = QtGui.QLabel()
|
|
self.labelPath.setText("Recorrido:")
|
|
self.linePath = QtGui.QLineEdit(self.formFence)
|
|
self.linePath.setObjectName(_fromUtf8("lineEdit1"))
|
|
self.linePath.readOnly = True
|
|
self.grid.addWidget(self.labelPath, self.grid.rowCount(), 0, 1, 1)
|
|
self.grid.addWidget(self.linePath, self.grid.rowCount() - 1, 1, 1, 1)
|
|
|
|
self.buttonPathSelect = QtGui.QPushButton('Add')
|
|
self.grid.addWidget(self.buttonPathSelect, 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.label = QtGui.QLabel()
|
|
self.label.setText("Separación entre apoyos:")
|
|
self.grid.addWidget(self.label, self.grid.rowCount(), 0, 1, -1)
|
|
|
|
self.labelInterval = QtGui.QLabel()
|
|
self.labelInterval.setText("Intervalo:")
|
|
self.valueInterval = FreeCADGui.UiLoader().createWidget("Gui::InputField")
|
|
self.valueInterval.setText("4,0 m")
|
|
self.grid.addWidget(self.labelInterval, self.grid.rowCount(), 0, 1, 1)
|
|
self.grid.addWidget(self.valueInterval, self.grid.rowCount() - 1, 1, 1, 2)
|
|
|
|
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.label = QtGui.QLabel()
|
|
self.label.setText("Mayado:")
|
|
self.grid.addWidget(self.label, self.grid.rowCount(), 0, 1, -1)
|
|
|
|
self.labelHeight = QtGui.QLabel()
|
|
self.labelHeight.setText("Altura:")
|
|
self.valueHeight = FreeCADGui.UiLoader().createWidget("Gui::InputField")
|
|
self.valueHeight.setText("2 m")
|
|
self.grid.addWidget(self.labelHeight, self.grid.rowCount(), 0, 1, 1)
|
|
self.grid.addWidget(self.valueHeight, self.grid.rowCount() - 1, 1, 1, 2)
|
|
|
|
self.labelOffset = QtGui.QLabel()
|
|
self.labelOffset.setText("Separación del suelo:")
|
|
self.valueOffset = FreeCADGui.UiLoader().createWidget("Gui::InputField")
|
|
self.valueOffset.setText("0 m")
|
|
self.grid.addWidget(self.labelOffset, self.grid.rowCount(), 0, 1, 1)
|
|
self.grid.addWidget(self.valueOffset, self.grid.rowCount() - 1, 1, 1, 2)
|
|
|
|
self.buttonPathSelect.clicked.connect(self.addPath)
|
|
self.valueInterval.valueChanged.connect(self.SetupGrid)
|
|
self.valueHeight.valueChanged.connect(self.SetupGrid)
|
|
# self.valueDepth.valueChanged.connect(self.SetupPost)
|
|
|
|
# Form para configurar el poste: -------------------------------------------------------------------
|
|
self.formPost = QtGui.QWidget()
|
|
self.formPost.resize(800, 640)
|
|
self.formPost.setWindowTitle("Post setup")
|
|
self.formPost.setWindowIcon(QtGui.QIcon(os.path.join(DirIcons, "contours.svg")))
|
|
self.grid = QtGui.QGridLayout(self.formPost)
|
|
|
|
# parameters
|
|
self.labelDiameter = QtGui.QLabel()
|
|
self.labelDiameter.setText("Diámetro:")
|
|
self.valueDiameter = FreeCADGui.UiLoader().createWidget("Gui::InputField")
|
|
self.valueDiameter.setText("48 mm")
|
|
self.grid.addWidget(self.labelDiameter, self.grid.rowCount(), 0, 1, 1)
|
|
self.grid.addWidget(self.valueDiameter, self.grid.rowCount() - 1, 1, 1, 1)
|
|
|
|
self.labelLength = QtGui.QLabel()
|
|
self.labelLength.setText("Longitud:")
|
|
self.valueLength = FreeCADGui.UiLoader().createWidget("Gui::InputField")
|
|
self.valueLength.setText("3,0 m")
|
|
self.grid.addWidget(self.labelLength, self.grid.rowCount(), 0, 1, 1)
|
|
self.grid.addWidget(self.valueLength, self.grid.rowCount() - 1, 1, 1, 1)
|
|
|
|
self.labelDepth = QtGui.QLabel()
|
|
self.labelDepth.setText("Profundidad:")
|
|
self.valueDepth = FreeCADGui.UiLoader().createWidget("Gui::InputField")
|
|
self.valueDepth.setText("700,0 mm")
|
|
self.grid.addWidget(self.labelDepth, self.grid.rowCount(), 0, 1, 1)
|
|
self.grid.addWidget(self.valueDepth, self.grid.rowCount() - 1, 1, 1, 1)
|
|
|
|
self.valueDiameter.valueChanged.connect(self.SetupPost)
|
|
self.valueLength.valueChanged.connect(self.SetupPost)
|
|
self.valueDepth.valueChanged.connect(self.SetupPost)
|
|
|
|
# Form para configurar la zapata: ----------------------------------------------------------
|
|
self.formFoundation = QtGui.QWidget()
|
|
self.formFoundation.resize(800, 640)
|
|
self.formFoundation.setWindowTitle("Post setup")
|
|
self.formFoundation.setWindowIcon(QtGui.QIcon(os.path.join(DirIcons, "contours.svg")))
|
|
self.grid = QtGui.QGridLayout(self.formFoundation)
|
|
|
|
# parameters
|
|
self.labelFoundationDiameter = QtGui.QLabel()
|
|
self.labelFoundationDiameter.setText("Cimentación:")
|
|
self.grid.addWidget(self.labelFoundationDiameter, self.grid.rowCount(), 0, 1, 1)
|
|
|
|
self.labelFoundationDiameter = QtGui.QLabel()
|
|
self.labelFoundationDiameter.setText("Diámetro:")
|
|
self.valueFoundationDiameter = FreeCADGui.UiLoader().createWidget("Gui::InputField")
|
|
self.valueFoundationDiameter.setText("200,0 mm")
|
|
self.grid.addWidget(self.labelFoundationDiameter, self.grid.rowCount(), 0, 1, 1)
|
|
self.grid.addWidget(self.valueFoundationDiameter, self.grid.rowCount() - 1, 1, 1, 1)
|
|
|
|
self.labelFoundationDepth = QtGui.QLabel()
|
|
self.labelFoundationDepth.setText("Profundidad:")
|
|
self.valueFoundationDepth = FreeCADGui.UiLoader().createWidget("Gui::InputField")
|
|
self.valueFoundationDepth.setText("700,0 mm")
|
|
self.grid.addWidget(self.labelFoundationDepth, self.grid.rowCount(), 0, 1, 1)
|
|
self.grid.addWidget(self.valueFoundationDepth, self.grid.rowCount() - 1, 1, 1, 1)
|
|
|
|
self.form = [self.formFence, self.formPost, self.formFoundation]
|
|
|
|
# valores iniciales y creación del la valla:
|
|
import Draft
|
|
self.post = PVPlantFencePost.makeFencePost() # Arch.makePipe()
|
|
self.post.Label = "Post"
|
|
Draft.autogroup(self.post)
|
|
|
|
'''
|
|
self.section = self.makeGrid()
|
|
self.path = self.section.Base
|
|
'''
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
self.fence = makePVPlantFence(self.section, self.post, self.path)
|
|
|
|
def addPath(self):
|
|
sel = FreeCADGui.Selection.getSelection()
|
|
if len(sel) > 0:
|
|
self.path = sel[0]
|
|
self.linePath.setText(self.path.Label)
|
|
self.fence.Base = self.path
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
def SetupPost(self):
|
|
if self.post.Diameter != FreeCAD.Units.Quantity(self.valueDiameter.text()).Value:
|
|
self.post.Diameter = FreeCAD.Units.Quantity(self.valueDiameter.text()).Value
|
|
if self.post.Length != FreeCAD.Units.Quantity(self.valueLength.text()).Value:
|
|
self.post.Length = FreeCAD.Units.Quantity(self.valueLength.text()).Value
|
|
|
|
if self.post.Placement.Base.z != -FreeCAD.Units.Quantity(self.valueDepth.text()).Value:
|
|
self.post.Placement.Base.z = -FreeCAD.Units.Quantity(self.valueDepth.text()).Value
|
|
self.fence.Depth = FreeCAD.Units.Quantity(self.valueDepth.text()).Value
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
def SetupGrid(self):
|
|
return
|
|
if self.path.End.x != FreeCAD.Units.Quantity(self.valueInterval.text()).Value:
|
|
self.path.End.x = FreeCAD.Units.Quantity(self.valueInterval.text()).Value
|
|
if self.section.LengthFwd != FreeCAD.Units.Quantity(self.valueHeight.text()).Value:
|
|
self.section.LengthFwd = FreeCAD.Units.Quantity(self.valueHeight.text()).Value
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
def makeGrid(self):
|
|
return None
|
|
|
|
import Draft
|
|
|
|
p1 = FreeCAD.Vector(0, 0, 0)
|
|
p2 = FreeCAD.Vector(4000, 0, 0)
|
|
line = Draft.makeLine(p1, p2)
|
|
|
|
section = FreeCAD.ActiveDocument.addObject('Part::Extrusion', 'Extrude')
|
|
section.Base = line
|
|
section.DirMode = "Custom"
|
|
section.Dir = FreeCAD.Vector(0.0, 0.0, 1.0)
|
|
section.DirLink = None
|
|
section.LengthFwd = 2000.0
|
|
section.LengthRev = 0.0
|
|
section.Solid = False
|
|
section.Reversed = False
|
|
section.Symmetric = False
|
|
section.TaperAngle = 0.0
|
|
section.TaperAngleRev = 0.0
|
|
line.Visibility = False
|
|
|
|
return section
|
|
|
|
|
|
# Commands ---------------------------------------------------------------------------------
|
|
class CommandPVPlantFence:
|
|
"the PVPlant Fence command definition"
|
|
|
|
def GetResources(self):
|
|
return {'Pixmap': str(os.path.join(DirIcons, "fence.svg")),
|
|
'Accel': "C, F",
|
|
'MenuText': QT_TRANSLATE_NOOP("PVPlantFence", "Fence"),
|
|
'ToolTip': QT_TRANSLATE_NOOP("PVPlantFence",
|
|
"Creates a fence object from a selected section, post and path")}
|
|
|
|
def IsActive(self):
|
|
return not FreeCAD.ActiveDocument is None
|
|
|
|
def Activated(self):
|
|
self.TaskPanel = _FenceTaskPanel()
|
|
FreeCADGui.Control.showDialog(self.TaskPanel)
|
|
|
|
|
|
if FreeCAD.GuiUp:
|
|
class CommandFenceGroup:
|
|
def GetCommands(self):
|
|
return tuple(['PVPlantFence',
|
|
'PVPlantGate',
|
|
'PVPlantFencePost'
|
|
])
|
|
|
|
def GetResources(self):
|
|
return {'MenuText': QT_TRANSLATE_NOOP("", 'PVPlantFence'),
|
|
'ToolTip': QT_TRANSLATE_NOOP("", 'PVPlantFence')
|
|
}
|
|
|
|
def IsActive(self):
|
|
return (not (FreeCAD.ActiveDocument is None) and
|
|
not (FreeCAD.ActiveDocument.getObject("Site") is None) and
|
|
not (FreeCAD.ActiveDocument.getObject("Terrain") is None))
|
|
|
|
import PVPlantFenceGate
|
|
FreeCADGui.addCommand('PVPlantFence', CommandPVPlantFence())
|
|
FreeCADGui.addCommand('PVPlantGate', PVPlantFenceGate._CommandPVPlantGate())
|
|
FreeCADGui.addCommand('PVPlantFencePost', PVPlantFencePost._CommandFencePost())
|
|
#FreeCADGui.addCommand('PVPlantFenceGroup', CommandFenceGroup())
|