425 lines
17 KiB
Python
425 lines
17 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 ArchComponent
|
||
|
|
import FreeCAD
|
||
|
|
import Part
|
||
|
|
|
||
|
|
if FreeCAD.GuiUp:
|
||
|
|
import FreeCADGui
|
||
|
|
from PySide import QtCore, QtGui, QtSvg
|
||
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||
|
|
import PySide.QtGui as QtGui
|
||
|
|
import draftguitools.gui_trackers as DraftTrackers
|
||
|
|
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
|
||
|
|
import PVPlantResources
|
||
|
|
from PVPlantResources import DirIcons as DirIcons
|
||
|
|
|
||
|
|
def makePVPlantFence():
|
||
|
|
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'FenceGate')
|
||
|
|
FenceGate(obj)
|
||
|
|
if FreeCAD.GuiUp:
|
||
|
|
ViewProviderGate(obj.ViewObject)
|
||
|
|
return obj
|
||
|
|
|
||
|
|
|
||
|
|
class FenceGate(ArchComponent.Component):
|
||
|
|
def __init__(self, obj):
|
||
|
|
ArchComponent.Component.__init__(self, obj)
|
||
|
|
self.setProperties(obj)
|
||
|
|
|
||
|
|
def setProperties(self, obj):
|
||
|
|
pl = obj.PropertiesList
|
||
|
|
|
||
|
|
if not ("GateType" in pl):
|
||
|
|
obj.addProperty("App::PropertyEnumeration",
|
||
|
|
"GateType",
|
||
|
|
"Gate",
|
||
|
|
QT_TRANSLATE_NOOP("App::Property",
|
||
|
|
"The facemaker type to use to build the profile of this object")
|
||
|
|
).GateType = ["Single", "Double"]
|
||
|
|
|
||
|
|
if not ("GateWidth" in pl):
|
||
|
|
obj.addProperty("App::PropertyDistance",
|
||
|
|
"GateWidth",
|
||
|
|
"Gate",
|
||
|
|
"Base wire"
|
||
|
|
).GateWidth = 3000
|
||
|
|
|
||
|
|
if not ("GateHeight" in pl):
|
||
|
|
obj.addProperty("App::PropertyDistance",
|
||
|
|
"GateHeight",
|
||
|
|
"Gate",
|
||
|
|
"Base wire"
|
||
|
|
).GateHeight = 2000
|
||
|
|
|
||
|
|
if not ("PostWidth" in pl):
|
||
|
|
obj.addProperty("App::PropertyDistance",
|
||
|
|
"PostWidth",
|
||
|
|
"Gate",
|
||
|
|
"Base wire"
|
||
|
|
).PostWidth = 60
|
||
|
|
|
||
|
|
if not ("PostHeight" in pl):
|
||
|
|
obj.addProperty("App::PropertyDistance",
|
||
|
|
"PostHeight",
|
||
|
|
"Gate",
|
||
|
|
"Base wire"
|
||
|
|
).PostHeight = 2000
|
||
|
|
|
||
|
|
if not "PostDepth" in pl:
|
||
|
|
obj.addProperty("App::PropertyLength",
|
||
|
|
"PostDepth",
|
||
|
|
"Fence",
|
||
|
|
"A single fence post").PostDepth = 700
|
||
|
|
|
||
|
|
self.Type = "PVPlatFenceGate"
|
||
|
|
#obj.Type = self.Type
|
||
|
|
obj.Proxy = self
|
||
|
|
self.obj = obj
|
||
|
|
|
||
|
|
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 onDocumentRestored(self, obj):
|
||
|
|
"""Method run when the document is restored."""
|
||
|
|
self.setProperties(obj)
|
||
|
|
|
||
|
|
def onChanged(self, obj, prop):
|
||
|
|
'''Do something when a property has changed'''
|
||
|
|
|
||
|
|
def execute(self, obj):
|
||
|
|
tubewidth = 50
|
||
|
|
gap = 20
|
||
|
|
posts = Part.makeCompound([])
|
||
|
|
gates = Part.makeCompound([])
|
||
|
|
pl = obj.Placement
|
||
|
|
|
||
|
|
post = Part.makeBox(obj.PostWidth.Value,
|
||
|
|
obj.PostWidth.Value,
|
||
|
|
obj.PostHeight.Value + obj.PostDepth.Value)
|
||
|
|
|
||
|
|
gate = Part.makeBox(obj.GateWidth.Value,
|
||
|
|
tubewidth,
|
||
|
|
obj.GateHeight.Value)
|
||
|
|
|
||
|
|
cut = Part.makeBox(obj.GateWidth.Value - tubewidth * 2,
|
||
|
|
tubewidth,
|
||
|
|
obj.GateHeight.Value - tubewidth * 2)
|
||
|
|
cut.Placement.Base += FreeCAD.Vector(tubewidth, 0, tubewidth)
|
||
|
|
gate = gate.cut(cut)
|
||
|
|
|
||
|
|
if obj.GateType == "Single":
|
||
|
|
post.Placement.Base = FreeCAD.Vector(-(obj.PostWidth.Value + gap + obj.GateWidth.Value / 2),
|
||
|
|
-obj.PostWidth.Value / 2,
|
||
|
|
-obj.PostDepth.Value)
|
||
|
|
posts.add(post)
|
||
|
|
post = post.copy()
|
||
|
|
post.Placement.Base.x = -(post.Placement.Base.x + obj.PostWidth.Value)
|
||
|
|
posts.add(post)
|
||
|
|
gate.Placement.Base += FreeCAD.Vector(-obj.GateWidth.Value / 2,
|
||
|
|
obj.PostWidth.Value / 2 - tubewidth,
|
||
|
|
0)
|
||
|
|
gates.add(gate)
|
||
|
|
else:
|
||
|
|
post.Placement.Base = FreeCAD.Vector(-(obj.PostWidth.Value + gap * 1.5 + obj.GateWidth.Value),
|
||
|
|
-obj.PostWidth.Value / 2,
|
||
|
|
-obj.PostDepth.Value)
|
||
|
|
posts.add(post)
|
||
|
|
post = post.copy()
|
||
|
|
post.Placement.Base.x = -(post.Placement.Base.x + obj.PostWidth.Value)
|
||
|
|
posts.add(post)
|
||
|
|
|
||
|
|
gate.Placement.Base += FreeCAD.Vector(-(obj.GateWidth.Value + gap * 0.5),
|
||
|
|
obj.PostWidth.Value / 2 - tubewidth,
|
||
|
|
0)
|
||
|
|
gates.add(gate)
|
||
|
|
gate = gate.copy()
|
||
|
|
gate.Placement.Base.x = gap * 0.5
|
||
|
|
gates.add(gate)
|
||
|
|
|
||
|
|
obj.Shape = Part.makeCompound([gates, posts])
|
||
|
|
obj.Placement = pl
|
||
|
|
|
||
|
|
|
||
|
|
class ViewProviderGate:
|
||
|
|
def __init__(self, vobj):
|
||
|
|
''' Set view properties. '''
|
||
|
|
vobj.Proxy = self
|
||
|
|
|
||
|
|
def attach(self, vobj):
|
||
|
|
self.Object = vobj.Object
|
||
|
|
|
||
|
|
def getIcon(self):
|
||
|
|
''' Return object treeview icon. '''
|
||
|
|
return str(os.path.join(DirIcons, "gate.svg"))
|
||
|
|
|
||
|
|
def claimChildren(self):
|
||
|
|
""" Provides object grouping """
|
||
|
|
children = []
|
||
|
|
if self.Object.Base:
|
||
|
|
children.append(self.Object.Base)
|
||
|
|
return children
|
||
|
|
|
||
|
|
class _CommandPVPlantGate:
|
||
|
|
"the PVPlant Fence command definition"
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
''' p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
|
||
|
|
self.Thickness = p.GetFloat("WindowThickness", 50)
|
||
|
|
self.Width = p.GetFloat("WindowWidth", 1000)
|
||
|
|
self.Height = p.GetFloat("DoorHeight", 2100)'''
|
||
|
|
|
||
|
|
self.Width = 0
|
||
|
|
self.Height = 0
|
||
|
|
self.Length = 0
|
||
|
|
self.tracker = None
|
||
|
|
|
||
|
|
def GetResources(self):
|
||
|
|
return {'Pixmap': str(os.path.join(DirIcons, "gate.svg")),
|
||
|
|
'Accel': "C, F",
|
||
|
|
'MenuText': QT_TRANSLATE_NOOP("PVPlantFence", "Gate"),
|
||
|
|
'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.tracker = DraftTrackers.boxTracker()
|
||
|
|
self.tracker.length(self.Length)
|
||
|
|
self.tracker.width(self.Width)
|
||
|
|
self.tracker.height(self.Height)
|
||
|
|
self.tracker.on()
|
||
|
|
FreeCAD.Console.PrintMessage("Choose a face on an existing object or select a preset" + "\n")
|
||
|
|
FreeCADGui.Snapper.getPoint(callback=self.getPoint,
|
||
|
|
movecallback=self.update,
|
||
|
|
extradlg=self.taskbox())
|
||
|
|
|
||
|
|
def getPoint(self, point=None, obj=None):
|
||
|
|
"this function is called by the snapper when it has a 3D point"
|
||
|
|
|
||
|
|
if obj.Name[:5] == "Fence":
|
||
|
|
self.tracker.finalize()
|
||
|
|
if point is None:
|
||
|
|
return
|
||
|
|
ver = Part.Vertex(point)
|
||
|
|
dist = ver.distToShape(obj.Base.Shape)
|
||
|
|
point1 = dist[1][0][1]
|
||
|
|
ed = None
|
||
|
|
if dist[2][0][3] == "Edge":
|
||
|
|
ed = obj.Base.Shape.Edges[int(dist[-1][0][4])]
|
||
|
|
vec = ed.Vertexes[1].Point.sub(ed.Vertexes[0].Point)
|
||
|
|
|
||
|
|
FreeCAD.ActiveDocument.openTransaction("Create Gate")
|
||
|
|
gate = makePVPlantFence()
|
||
|
|
try:
|
||
|
|
import MeshPart as mp
|
||
|
|
point1 = mp.projectPointsOnMesh([point1,], FreeCAD.ActiveDocument.Terrain.Mesh, FreeCAD.Vector(0, 0, 1))[0]
|
||
|
|
|
||
|
|
except:
|
||
|
|
FreeCAD.Console.PrintError("No se puede encontrar punto 3D.." + "\n")
|
||
|
|
pass
|
||
|
|
|
||
|
|
gate.Placement.Base = point1
|
||
|
|
gate.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(-1, 0, 0), vec)
|
||
|
|
tmp = obj.Gate
|
||
|
|
tmp.append(gate)
|
||
|
|
obj.Gate = tmp
|
||
|
|
|
||
|
|
FreeCAD.ActiveDocument.commitTransaction()
|
||
|
|
FreeCAD.ActiveDocument.recompute()
|
||
|
|
return
|
||
|
|
else:
|
||
|
|
FreeCADGui.Snapper.getPoint(callback=self.getPoint,
|
||
|
|
movecallback=self.update,
|
||
|
|
extradlg=self.taskbox())
|
||
|
|
def update(self, point, info):
|
||
|
|
''' this function is called by the Snapper when the mouse is moved '''
|
||
|
|
|
||
|
|
delta = FreeCAD.Vector(self.Length / 2, self.Width / 2, self.Height / 2)
|
||
|
|
rot = FreeCAD.Rotation()
|
||
|
|
|
||
|
|
#self.tracker.setRotation(rot)
|
||
|
|
self.tracker.pos(point)
|
||
|
|
return
|
||
|
|
|
||
|
|
delta = FreeCAD.Vector(self.Length / 2, self.Width / 2, self.Height / 2)
|
||
|
|
delta = delta.add(FreeCAD.Vector(0, 0, self.Sill))
|
||
|
|
rot = FreeCAD.Rotation()
|
||
|
|
if info:
|
||
|
|
if "Face" in info['Component']:
|
||
|
|
import WorkingPlane
|
||
|
|
o = FreeCAD.ActiveDocument.getObject(info['Object'])
|
||
|
|
self.baseFace = [o, int(info['Component'][4:]) - 1]
|
||
|
|
# print("switching to ",o.Label," face ",self.baseFace[1])
|
||
|
|
f = o.Shape.Faces[self.baseFace[1]]
|
||
|
|
p = WorkingPlane.getPlacementFromFace(f, rotated=True)
|
||
|
|
if p:
|
||
|
|
rot = p.Rotation
|
||
|
|
self.tracker.setRotation(rot)
|
||
|
|
r = self.tracker.trans.rotation.getValue().getValue()
|
||
|
|
if r != (0, 0, 0, 1):
|
||
|
|
delta = FreeCAD.Rotation(r[0], r[1], r[2], r[3]).multVec(FreeCAD.Vector(delta.x, -delta.y, -delta.z))
|
||
|
|
self.tracker.pos(point.add(delta))
|
||
|
|
|
||
|
|
def taskbox(self):
|
||
|
|
"sets up a taskbox widget"
|
||
|
|
|
||
|
|
w = FreeCADGui.PySideUic.loadUi(PVPlantResources.__dir__ + "/PVPlantFenceGate.ui")
|
||
|
|
self.Width = 80
|
||
|
|
self.Height = 2000
|
||
|
|
self.Length = 6146
|
||
|
|
return w
|
||
|
|
|
||
|
|
w = QtGui.QWidget()
|
||
|
|
ui = FreeCADGui.UiLoader()
|
||
|
|
|
||
|
|
w.setWindowTitle("Window options")
|
||
|
|
grid = QtGui.QGridLayout(w)
|
||
|
|
|
||
|
|
# include box
|
||
|
|
include = QtGui.QCheckBox(translate("Arch","Auto include in host object"))
|
||
|
|
include.setChecked(True)
|
||
|
|
grid.addWidget(include,0,0,1,2)
|
||
|
|
QtCore.QObject.connect(include,QtCore.SIGNAL("stateChanged(int)"),self.setInclude)
|
||
|
|
|
||
|
|
# sill height
|
||
|
|
labels = QtGui.QLabel(translate("Arch","Sill height"))
|
||
|
|
values = ui.createWidget("Gui::InputField")
|
||
|
|
grid.addWidget(labels,1,0,1,1)
|
||
|
|
grid.addWidget(values,1,1,1,1)
|
||
|
|
QtCore.QObject.connect(values,QtCore.SIGNAL("valueChanged(double)"),self.setSill)
|
||
|
|
|
||
|
|
# check for Parts library and Arch presets
|
||
|
|
|
||
|
|
# because of the use of FreeCADGui.doCommand() backslashes in the
|
||
|
|
# paths in librarypresets need to be double escaped "\\\\", so let's
|
||
|
|
# use forward slashes instead...
|
||
|
|
self.librarypresets = []
|
||
|
|
librarypath = FreeCAD.ParamGet("User parameter:Plugins/parts_library").GetString("destination", "")
|
||
|
|
# librarypath should have only forward slashes already, but let's use replace() anyway just to be sure:
|
||
|
|
librarypath = librarypath.replace("\\", "/") + "/Architectural Parts"
|
||
|
|
presetdir = FreeCAD.getUserAppDataDir().replace("\\", "/") + "/Arch"
|
||
|
|
for path in [librarypath, presetdir]:
|
||
|
|
if os.path.isdir(path):
|
||
|
|
for wtype in ["Windows", "Doors"]:
|
||
|
|
wdir = path + "/" + wtype
|
||
|
|
if os.path.isdir(wdir):
|
||
|
|
for subtype in os.listdir(wdir):
|
||
|
|
subdir = wdir + "/" + subtype
|
||
|
|
if os.path.isdir(subdir):
|
||
|
|
for subfile in os.listdir(subdir):
|
||
|
|
if (os.path.isfile(subdir + "/" + subfile)
|
||
|
|
and subfile.lower().endswith(".fcstd")):
|
||
|
|
self.librarypresets.append([wtype + " - " + subtype + " - " + subfile[:-6],
|
||
|
|
subdir + "/" + subfile])
|
||
|
|
|
||
|
|
# presets box
|
||
|
|
labelp = QtGui.QLabel(translate("Arch","Preset"))
|
||
|
|
valuep = QtGui.QComboBox()
|
||
|
|
valuep.setMinimumContentsLength(6)
|
||
|
|
valuep.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
|
||
|
|
valuep.addItems(WindowPresets)
|
||
|
|
valuep.setCurrentIndex(self.Preset)
|
||
|
|
grid.addWidget(labelp,2,0,1,1)
|
||
|
|
grid.addWidget(valuep,2,1,1,1)
|
||
|
|
QtCore.QObject.connect(valuep,QtCore.SIGNAL("currentIndexChanged(int)"),self.setPreset)
|
||
|
|
for it in self.librarypresets:
|
||
|
|
valuep.addItem(it[0])
|
||
|
|
|
||
|
|
# image display
|
||
|
|
self.pic = QtGui.QLabel()
|
||
|
|
grid.addWidget(self.pic,3,0,1,2)
|
||
|
|
self.pic.setFixedHeight(128)
|
||
|
|
self.pic.hide()
|
||
|
|
|
||
|
|
# SVG display
|
||
|
|
self.im = QtSvg.QSvgWidget(":/ui/ParametersWindowFixed.svg")
|
||
|
|
self.im.setMaximumWidth(200)
|
||
|
|
self.im.setMinimumHeight(120)
|
||
|
|
grid.addWidget(self.im,4,0,1,2)
|
||
|
|
#self.im.hide()
|
||
|
|
|
||
|
|
# parameters
|
||
|
|
i = 5
|
||
|
|
for param in self.wparams:
|
||
|
|
lab = QtGui.QLabel(translate("Arch",param))
|
||
|
|
setattr(self,"val"+param,ui.createWidget("Gui::InputField"))
|
||
|
|
wid = getattr(self,"val"+param)
|
||
|
|
if param == "Width":
|
||
|
|
wid.setText(FreeCAD.Units.Quantity(self.Width,FreeCAD.Units.Length).UserString)
|
||
|
|
elif param == "Height":
|
||
|
|
wid.setText(FreeCAD.Units.Quantity(self.Height,FreeCAD.Units.Length).UserString)
|
||
|
|
elif param == "O1":
|
||
|
|
n = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetFloat("WindowO1",0.0)
|
||
|
|
wid.setText(FreeCAD.Units.Quantity(n,FreeCAD.Units.Length).UserString)
|
||
|
|
setattr(self,param,n)
|
||
|
|
elif param == "W1":
|
||
|
|
n = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetFloat("WindowW1",self.Thickness*2)
|
||
|
|
wid.setText(FreeCAD.Units.Quantity(n,FreeCAD.Units.Length).UserString)
|
||
|
|
setattr(self,param,n)
|
||
|
|
else:
|
||
|
|
n = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetFloat("Window"+param,self.Thickness)
|
||
|
|
wid.setText(FreeCAD.Units.Quantity(n,FreeCAD.Units.Length).UserString)
|
||
|
|
setattr(self,param,n)
|
||
|
|
grid.addWidget(lab,i,0,1,1)
|
||
|
|
grid.addWidget(wid,i,1,1,1)
|
||
|
|
i += 1
|
||
|
|
valueChanged = self.getValueChanged(param)
|
||
|
|
FreeCAD.wid = wid
|
||
|
|
QtCore.QObject.connect(getattr(self,"val"+param),QtCore.SIGNAL("valueChanged(double)"), valueChanged)
|
||
|
|
|
||
|
|
# restore saved states
|
||
|
|
if self.doormode:
|
||
|
|
i = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("DoorPreset",0)
|
||
|
|
d = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetFloat("DoorSill",0)
|
||
|
|
else:
|
||
|
|
i = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("WindowPreset",0)
|
||
|
|
d = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetFloat("WindowSill",0)
|
||
|
|
if i < valuep.count():
|
||
|
|
valuep.setCurrentIndex(i)
|
||
|
|
values.setText(FreeCAD.Units.Quantity(d,FreeCAD.Units.Length).UserString)
|
||
|
|
|
||
|
|
return w
|