1557 lines
58 KiB
Python
1557 lines
58 KiB
Python
#***************************************************************************
|
|
#* Copyright (c) 2011 Yorik van Havre <yorik@uncreated.net> *
|
|
#* *
|
|
#* 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 six
|
|
|
|
import FreeCAD,Draft,math,DraftVecUtils,ArchCommands
|
|
from FreeCAD import Vector
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
from PySide import QtCore, QtGui
|
|
from DraftTools import translate
|
|
from pivy import coin
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
else:
|
|
# \cond
|
|
def translate(ctxt,txt):
|
|
return txt
|
|
def QT_TRANSLATE_NOOP(ctxt,txt):
|
|
return txt
|
|
# \endcond
|
|
|
|
__title__="FreeCAD Axis System"
|
|
__author__ = "Yorik van Havre"
|
|
__url__ = "http://www.freecadweb.org"
|
|
|
|
## @package ArchAxis
|
|
# \ingroup ARCH
|
|
# \brief Axis system for the Arch workbench
|
|
#
|
|
# This module provides tools to build axis systems
|
|
# An axis system is a collection of planar axes with a number/tag
|
|
|
|
|
|
def makeAxis(num=5,size=1000,name="Axes"):
|
|
|
|
'''makeAxis(num,size): makes an Axis set
|
|
based on the given number of axes and interval distances'''
|
|
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Axis")
|
|
obj.Label = translate("Arch",name)
|
|
_Axis(obj)
|
|
if FreeCAD.GuiUp:
|
|
_ViewProviderAxis(obj.ViewObject)
|
|
if num:
|
|
dist = []
|
|
angles = []
|
|
for i in range(num):
|
|
if i == 0:
|
|
dist.append(0)
|
|
else:
|
|
dist.append(float(size))
|
|
angles.append(float(0))
|
|
obj.Distances = dist
|
|
obj.Angles = angles
|
|
FreeCAD.ActiveDocument.recompute()
|
|
return obj
|
|
|
|
|
|
def makeAxisSystem(axes,name="Axis System"):
|
|
|
|
'''makeAxisSystem(axes): makes a system from the given list of axes'''
|
|
|
|
if not isinstance(axes,list):
|
|
axes = [axes]
|
|
obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython","AxisSystem")
|
|
obj.Label = translate("Arch",name)
|
|
_AxisSystem(obj)
|
|
obj.Axes = axes
|
|
if FreeCAD.GuiUp:
|
|
_ViewProviderAxisSystem(obj.ViewObject)
|
|
FreeCAD.ActiveDocument.recompute()
|
|
return obj
|
|
|
|
|
|
def makeGrid(name="Grid"):
|
|
|
|
'''makeGrid(): makes a grid object'''
|
|
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Grid")
|
|
obj.Label = translate("Arch",name)
|
|
ArchGrid(obj)
|
|
if FreeCAD.GuiUp:
|
|
ViewProviderArchGrid(obj.ViewObject)
|
|
obj.ViewObject.Transparency = 85
|
|
FreeCAD.ActiveDocument.recompute()
|
|
return obj
|
|
|
|
|
|
class _CommandAxis:
|
|
|
|
"the Arch Axis command definition"
|
|
|
|
def GetResources(self):
|
|
|
|
return {'Pixmap' : 'Arch_Axis',
|
|
'MenuText': QT_TRANSLATE_NOOP("Arch_Axis","Axis"),
|
|
'Accel': "A, X",
|
|
'ToolTip': QT_TRANSLATE_NOOP("Arch_Axis","Creates a set of axes")}
|
|
|
|
def Activated(self):
|
|
|
|
FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Axis"))
|
|
FreeCADGui.addModule("Arch")
|
|
|
|
FreeCADGui.doCommand("Arch.makeAxis()")
|
|
FreeCAD.ActiveDocument.commitTransaction()
|
|
|
|
def IsActive(self):
|
|
|
|
return not FreeCAD.ActiveDocument is None
|
|
|
|
|
|
class _CommandAxisSystem:
|
|
|
|
"the Arch Axis System command definition"
|
|
|
|
def GetResources(self):
|
|
|
|
return {'Pixmap' : 'Arch_Axis_System',
|
|
'MenuText': QT_TRANSLATE_NOOP("Arch_AxisSystem","Axis System"),
|
|
'Accel': "X, S",
|
|
'ToolTip': QT_TRANSLATE_NOOP("Arch_AxisSystem","Creates an axis system from a set of axes")}
|
|
|
|
def Activated(self):
|
|
|
|
if FreeCADGui.Selection.getSelection():
|
|
import Draft
|
|
s = "["
|
|
for o in FreeCADGui.Selection.getSelection():
|
|
if Draft.getType(o) != "Axis":
|
|
FreeCAD.Console.PrintError(translate("Arch","Only axes must be selected")+"\n")
|
|
return
|
|
s += "FreeCAD.ActiveDocument."+o.Name+","
|
|
s += "]"
|
|
FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Axis System"))
|
|
FreeCADGui.addModule("Arch")
|
|
FreeCADGui.doCommand("Arch.makeAxisSystem("+s+")")
|
|
FreeCAD.ActiveDocument.commitTransaction()
|
|
else:
|
|
FreeCAD.Console.PrintError(translate("Arch","Please select at least one axis")+"\n")
|
|
|
|
def IsActive(self):
|
|
|
|
return not FreeCAD.ActiveDocument is None
|
|
|
|
|
|
class CommandArchGrid:
|
|
|
|
"the Arch Grid command definition"
|
|
|
|
def GetResources(self):
|
|
|
|
return {'Pixmap' : 'Arch_Grid',
|
|
'MenuText': QT_TRANSLATE_NOOP("Arch_Axis","Grid"),
|
|
'Accel': "A, X",
|
|
'ToolTip': QT_TRANSLATE_NOOP("Arch_Axis","Creates a customizable grid object")}
|
|
|
|
def Activated(self):
|
|
|
|
FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Grid"))
|
|
FreeCADGui.addModule("Arch")
|
|
|
|
FreeCADGui.doCommand("Arch.makeGrid()")
|
|
FreeCAD.ActiveDocument.commitTransaction()
|
|
|
|
def IsActive(self):
|
|
|
|
return not FreeCAD.ActiveDocument is None
|
|
|
|
|
|
|
|
class _Axis:
|
|
|
|
"The Axis object"
|
|
|
|
def __init__(self,obj):
|
|
|
|
obj.Proxy = self
|
|
self.setProperties(obj)
|
|
|
|
def setProperties(self,obj):
|
|
|
|
pl = obj.PropertiesList
|
|
if not "Distances" in pl:
|
|
obj.addProperty("App::PropertyFloatList","Distances","Axis", QT_TRANSLATE_NOOP("App::Property","The intervals between axes"))
|
|
if not "Angles" in pl:
|
|
obj.addProperty("App::PropertyFloatList","Angles","Axis", QT_TRANSLATE_NOOP("App::Property","The angles of each axis"))
|
|
if not "Labels" in pl:
|
|
obj.addProperty("App::PropertyStringList","Labels","Axis", QT_TRANSLATE_NOOP("App::Property","The label of each axis"))
|
|
if not "CustomNumber" in pl:
|
|
obj.addProperty("App::PropertyString","CustomNumber","Axis", QT_TRANSLATE_NOOP("App::Property","An optional custom bubble number"))
|
|
if not "Length" in pl:
|
|
obj.addProperty("App::PropertyLength","Length","Axis", QT_TRANSLATE_NOOP("App::Property","The length of the axes"))
|
|
obj.Length=3000
|
|
if not "Placement" in pl:
|
|
obj.addProperty("App::PropertyPlacement","Placement","Base","")
|
|
if not "Shape" in pl:
|
|
obj.addProperty("Part::PropertyPartShape","Shape","Base","")
|
|
self.Type = "Axis"
|
|
|
|
def onDocumentRestored(self,obj):
|
|
|
|
self.setProperties(obj)
|
|
|
|
def execute(self,obj):
|
|
|
|
pl = obj.Placement
|
|
import Part
|
|
geoms = []
|
|
dist = 0
|
|
if obj.Distances:
|
|
if len(obj.Distances) == len(obj.Angles):
|
|
for i in range(len(obj.Distances)):
|
|
if hasattr(obj.Length,"Value"):
|
|
l = obj.Length.Value
|
|
else:
|
|
l = obj.Length
|
|
dist += obj.Distances[i]
|
|
ang = math.radians(obj.Angles[i])
|
|
p1 = Vector(dist,0,0)
|
|
p2 = Vector(dist+(l/math.cos(ang))*math.sin(ang),l,0)
|
|
geoms.append(Part.LineSegment(p1,p2).toShape())
|
|
if geoms:
|
|
sh = Part.Compound(geoms)
|
|
obj.Shape = sh
|
|
obj.Placement = pl
|
|
|
|
def onChanged(self,obj,prop):
|
|
|
|
if prop in ["Angles","Distances","Placement"]:
|
|
obj.touch()
|
|
|
|
def __getstate__(self):
|
|
|
|
return None
|
|
|
|
def __setstate__(self,state):
|
|
|
|
return None
|
|
|
|
def getPoints(self,obj):
|
|
|
|
"returns the gridpoints of linked axes"
|
|
|
|
pts = []
|
|
for e in obj.Shape.Edges:
|
|
pts.append(e.Vertexes[0].Point)
|
|
return pts
|
|
|
|
def getAxisData(self,obj):
|
|
data = []
|
|
num = 0
|
|
for e in obj.Shape.Edges:
|
|
axdata = []
|
|
axdata.append(e.Vertexes[0].Point)
|
|
axdata.append(e.Vertexes[-1].Point)
|
|
if obj.ViewObject:
|
|
axdata.append(obj.ViewObject.Proxy.getNumber(obj.ViewObject,num))
|
|
else:
|
|
axdata.append(str(num))
|
|
data.append(axdata)
|
|
num += 1
|
|
return data
|
|
|
|
|
|
class _ViewProviderAxis:
|
|
|
|
"A View Provider for the Axis object"
|
|
|
|
def __init__(self,vobj):
|
|
|
|
vobj.Proxy = self
|
|
self.setProperties(vobj)
|
|
|
|
def setProperties(self,vobj):
|
|
|
|
ts = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetFloat("textheight",350)
|
|
pl = vobj.PropertiesList
|
|
if not "BubbleSize" in pl:
|
|
vobj.addProperty("App::PropertyLength","BubbleSize","Axis", QT_TRANSLATE_NOOP("App::Property","The size of the axis bubbles"))
|
|
vobj.BubbleSize = ts*1.42
|
|
if not "NumberingStyle" in pl:
|
|
vobj.addProperty("App::PropertyEnumeration","NumberingStyle","Axis", QT_TRANSLATE_NOOP("App::Property","The numbering style"))
|
|
vobj.NumberingStyle = ["1,2,3","01,02,03","001,002,003","A,B,C","a,b,c","I,II,III","L0,L1,L2"]
|
|
vobj.NumberingStyle = "1,2,3"
|
|
if not "DrawStyle" in pl:
|
|
vobj.addProperty("App::PropertyEnumeration","DrawStyle","Axis",QT_TRANSLATE_NOOP("App::Property","The type of line to draw this axis"))
|
|
vobj.DrawStyle = ["Solid","Dashed","Dotted","Dashdot"]
|
|
vobj.DrawStyle = "Dashdot"
|
|
if not "BubblePosition" in pl:
|
|
vobj.addProperty("App::PropertyEnumeration","BubblePosition","Axis",QT_TRANSLATE_NOOP("App::Property","Where to add bubbles to this axis: Start, end, both or none"))
|
|
vobj.BubblePosition = ["Start","End","Both","None"]
|
|
if not "LineWidth" in pl:
|
|
vobj.addProperty("App::PropertyFloat","LineWidth","Axis",QT_TRANSLATE_NOOP("App::Property","The line width to draw this axis"))
|
|
vobj.LineWidth = 1
|
|
if not "LineColor" in pl:
|
|
vobj.addProperty("App::PropertyColor","LineColor","Axis",QT_TRANSLATE_NOOP("App::Property","The color of this axis"))
|
|
vobj.LineColor = ArchCommands.getDefaultColor("Helpers")
|
|
if not "StartNumber" in pl:
|
|
vobj.addProperty("App::PropertyInteger","StartNumber","Axis",QT_TRANSLATE_NOOP("App::Property","The number of the first axis"))
|
|
vobj.StartNumber = 1
|
|
if not "FontName" in pl:
|
|
vobj.addProperty("App::PropertyFont","FontName","Axis",QT_TRANSLATE_NOOP("App::Property","The font to use for texts"))
|
|
vobj.FontName = Draft.getParam("textfont","Arial,Sans")
|
|
if not "FontSize" in pl:
|
|
vobj.addProperty("App::PropertyLength","FontSize","Axis",QT_TRANSLATE_NOOP("App::Property","The font size"))
|
|
vobj.FontSize = ts
|
|
if not "ShowLabel" in pl:
|
|
vobj.addProperty("App::PropertyBool","ShowLabel","Axis",QT_TRANSLATE_NOOP("App::Property","If true, show the labels"))
|
|
if not "LabelOffset" in pl:
|
|
vobj.addProperty("App::PropertyPlacement","LabelOffset","Axis",QT_TRANSLATE_NOOP("App::Property","A transformation to apply to each label"))
|
|
|
|
def onDocumentRestored(self,vobj):
|
|
|
|
self.setProperties(vobj)
|
|
|
|
def getIcon(self):
|
|
|
|
import Arch_rc
|
|
return ":/icons/Arch_Axis_Tree.svg"
|
|
|
|
def claimChildren(self):
|
|
|
|
return []
|
|
|
|
def attach(self, vobj):
|
|
|
|
self.bubbles = None
|
|
self.bubbletexts = []
|
|
sep = coin.SoSeparator()
|
|
self.mat = coin.SoMaterial()
|
|
self.linestyle = coin.SoDrawStyle()
|
|
self.linecoords = coin.SoCoordinate3()
|
|
self.lineset = coin.SoType.fromName("SoBrepEdgeSet").createInstance()
|
|
self.bubbleset = coin.SoSeparator()
|
|
self.labelset = coin.SoSeparator()
|
|
sep.addChild(self.mat)
|
|
sep.addChild(self.linestyle)
|
|
sep.addChild(self.linecoords)
|
|
sep.addChild(self.lineset)
|
|
sep.addChild(self.bubbleset)
|
|
sep.addChild(self.labelset)
|
|
vobj.addDisplayMode(sep,"Default")
|
|
self.onChanged(vobj,"BubbleSize")
|
|
self.onChanged(vobj,"ShowLabel")
|
|
self.onChanged(vobj,"LineColor")
|
|
self.onChanged(vobj,"LineWidth")
|
|
self.onChanged(vobj,"DrawStyle")
|
|
|
|
def getDisplayModes(self,vobj):
|
|
|
|
return ["Default"]
|
|
|
|
def getDefaultDisplayMode(self):
|
|
|
|
return "Default"
|
|
|
|
def setDisplayMode(self,mode):
|
|
|
|
return mode
|
|
|
|
def updateData(self,obj,prop):
|
|
|
|
if prop == "Shape":
|
|
if obj.Shape:
|
|
if obj.Shape.Edges:
|
|
verts = []
|
|
vset = []
|
|
i = 0
|
|
for e in obj.Shape.Edges:
|
|
for v in e.Vertexes:
|
|
verts.append(tuple(obj.Placement.inverse().multVec(v.Point)))
|
|
vset.append(i)
|
|
i += 1
|
|
vset.append(-1)
|
|
self.linecoords.point.setValues(verts)
|
|
self.lineset.coordIndex.setValues(0,len(vset),vset)
|
|
self.lineset.coordIndex.setNum(len(vset))
|
|
self.onChanged(obj.ViewObject,"BubbleSize")
|
|
self.onChanged(obj.ViewObject,"ShowLabel")
|
|
|
|
def onChanged(self, vobj, prop):
|
|
|
|
if prop == "LineColor":
|
|
if hasattr(vobj,"LineColor"):
|
|
l = vobj.LineColor
|
|
self.mat.diffuseColor.setValue([l[0],l[1],l[2]])
|
|
elif prop == "DrawStyle":
|
|
if hasattr(vobj,"DrawStyle"):
|
|
if vobj.DrawStyle == "Solid":
|
|
self.linestyle.linePattern = 0xffff
|
|
elif vobj.DrawStyle == "Dashed":
|
|
self.linestyle.linePattern = 0xf00f
|
|
elif vobj.DrawStyle == "Dotted":
|
|
self.linestyle.linePattern = 0x0f0f
|
|
else:
|
|
self.linestyle.linePattern = 0xff88
|
|
elif prop == "LineWidth":
|
|
if hasattr(vobj,"LineWidth"):
|
|
self.linestyle.lineWidth = vobj.LineWidth
|
|
elif prop in ["BubbleSize","BubblePosition","FontName","FontSize"]:
|
|
if hasattr(self,"bubbleset"):
|
|
if self.bubbles:
|
|
self.bubbleset.removeChild(self.bubbles)
|
|
self.bubbles = None
|
|
if vobj.Object.Shape:
|
|
if vobj.Object.Shape.Edges:
|
|
self.bubbles = coin.SoSeparator()
|
|
self.bubblestyle = coin.SoDrawStyle()
|
|
self.bubblestyle.linePattern = 0xffff
|
|
self.bubbles.addChild(self.bubblestyle)
|
|
import Part,Draft
|
|
self.bubbletexts = []
|
|
pos = ["Start"]
|
|
if hasattr(vobj,"BubblePosition"):
|
|
if vobj.BubblePosition == "Both":
|
|
pos = ["Start","End"]
|
|
elif vobj.BubblePosition == "None":
|
|
pos = []
|
|
else:
|
|
pos = [vobj.BubblePosition]
|
|
for i in range(len(vobj.Object.Shape.Edges)):
|
|
for p in pos:
|
|
verts = [vobj.Object.Placement.inverse().multVec(v.Point) for v in vobj.Object.Shape.Edges[i].Vertexes]
|
|
if p == "Start":
|
|
p1 = verts[0]
|
|
p2 = verts[1]
|
|
else:
|
|
p1 = verts[1]
|
|
p2 = verts[0]
|
|
dv = p2.sub(p1)
|
|
dv.normalize()
|
|
if hasattr(vobj.BubbleSize,"Value"):
|
|
rad = vobj.BubbleSize.Value/2
|
|
else:
|
|
rad = vobj.BubbleSize/2
|
|
center = p2.add(dv.scale(rad,rad,rad))
|
|
buf = Part.makeCircle(rad,center).writeInventor()
|
|
try:
|
|
cin = coin.SoInput()
|
|
cin.setBuffer(buf)
|
|
cob = coin.SoDB.readAll(cin)
|
|
except:
|
|
import re
|
|
# workaround for pivy SoInput.setBuffer() bug
|
|
buf = buf.replace("\n","")
|
|
pts = re.findall("point \[(.*?)\]",buf)[0]
|
|
pts = pts.split(",")
|
|
pc = []
|
|
for p in pts:
|
|
v = p.strip().split()
|
|
pc.append([float(v[0]),float(v[1]),float(v[2])])
|
|
coords = coin.SoCoordinate3()
|
|
coords.point.setValues(0,len(pc),pc)
|
|
line = coin.SoLineSet()
|
|
line.numVertices.setValue(-1)
|
|
else:
|
|
coords = cob.getChild(1).getChild(0).getChild(2)
|
|
line = cob.getChild(1).getChild(0).getChild(3)
|
|
self.bubbles.addChild(coords)
|
|
self.bubbles.addChild(line)
|
|
st = coin.SoSeparator()
|
|
tr = coin.SoTransform()
|
|
fs = rad*1.5
|
|
if hasattr(vobj,"FontSize"):
|
|
fs = vobj.FontSize.Value
|
|
tr.translation.setValue((center.x,center.y-fs/2.5,center.z))
|
|
fo = coin.SoFont()
|
|
fn = Draft.getParam("textfont","Arial,Sans")
|
|
if hasattr(vobj,"FontName"):
|
|
if vobj.FontName:
|
|
try:
|
|
fn = str(vobj.FontName)
|
|
except:
|
|
pass
|
|
fo.name = fn
|
|
fo.size = fs
|
|
tx = coin.SoAsciiText()
|
|
tx.justification = coin.SoText2.CENTER
|
|
self.bubbletexts.append(tx)
|
|
st.addChild(tr)
|
|
st.addChild(fo)
|
|
st.addChild(tx)
|
|
self.bubbles.addChild(st)
|
|
self.bubbleset.addChild(self.bubbles)
|
|
self.onChanged(vobj,"NumberingStyle")
|
|
if prop in ["FontName","FontSize"]:
|
|
self.onChanged(vobj,"ShowLabel")
|
|
elif prop in ["NumberingStyle","StartNumber"]:
|
|
if hasattr(self,"bubbletexts"):
|
|
num = 0
|
|
if hasattr(vobj,"StartNumber"):
|
|
if vobj.StartNumber > 1:
|
|
num = vobj.StartNumber-1
|
|
alt = False
|
|
for t in self.bubbletexts:
|
|
t.string = self.getNumber(vobj,num)
|
|
num += 1
|
|
if hasattr(vobj,"BubblePosition"):
|
|
if vobj.BubblePosition == "Both":
|
|
if not alt:
|
|
num -= 1
|
|
alt = not alt
|
|
elif prop in ["ShowLabel", "LabelOffset"]:
|
|
if hasattr(self,"labels"):
|
|
if self.labels:
|
|
self.labelset.removeChild(self.labels)
|
|
self.labels = None
|
|
if hasattr(vobj,"ShowLabel") and hasattr(vobj.Object,"Labels"):
|
|
if vobj.ShowLabel:
|
|
self.labels = coin.SoSeparator()
|
|
for i in range(len(vobj.Object.Shape.Edges)):
|
|
if len(vobj.Object.Labels) > i:
|
|
if vobj.Object.Labels[i]:
|
|
import Draft
|
|
vert = vobj.Object.Shape.Edges[i].Vertexes[0].Point
|
|
if hasattr(vobj,"LabelOffset"):
|
|
pl = FreeCAD.Placement(vobj.LabelOffset)
|
|
pl.Base = vert.add(pl.Base)
|
|
st = coin.SoSeparator()
|
|
tr = coin.SoTransform()
|
|
fo = coin.SoFont()
|
|
tx = coin.SoAsciiText()
|
|
tx.justification = coin.SoText2.LEFT
|
|
t = vobj.Object.Labels[i]
|
|
if six.PY2 and isinstance(t,six.text_type):
|
|
t = t.encode("utf8")
|
|
tx.string.setValue(t)
|
|
if hasattr(vobj,"FontSize"):
|
|
fs = vobj.FontSize.Value
|
|
elif hasattr(vobj.BubbleSize,"Value"):
|
|
fs = vobj.BubbleSize.Value*0.75
|
|
else:
|
|
fs = vobj.BubbleSize*0.75
|
|
tr.translation.setValue(tuple(pl.Base))
|
|
tr.rotation.setValue(pl.Rotation.Q)
|
|
fn = Draft.getParam("textfont","Arial,Sans")
|
|
if hasattr(vobj,"FontName"):
|
|
if vobj.FontName:
|
|
try:
|
|
fn = str(vobj.FontName)
|
|
except:
|
|
pass
|
|
fo.name = fn
|
|
fo.size = fs
|
|
st.addChild(tr)
|
|
st.addChild(fo)
|
|
st.addChild(tx)
|
|
self.labels.addChild(st)
|
|
self.labelset.addChild(self.labels)
|
|
|
|
def getNumber(self,vobj,num):
|
|
|
|
chars = "abcdefghijklmnopqrstuvwxyz"
|
|
roman=(('M',1000),('CM',900),('D',500),('CD',400),
|
|
('C',100),('XC',90),('L',50),('XL',40),
|
|
('X',10),('IX',9),('V',5),('IV',4),('I',1))
|
|
if hasattr(vobj.Object,"CustomNumber") and vobj.Object.CustomNumber:
|
|
if six.PY2:
|
|
return vobj.Object.CustomNumber.encode("utf8")
|
|
else:
|
|
return vobj.Object.CustomNumber
|
|
elif hasattr(vobj,"NumberingStyle"):
|
|
if vobj.NumberingStyle == "1,2,3":
|
|
return str(num+1)
|
|
elif vobj.NumberingStyle == "01,02,03":
|
|
return str(num+1).zfill(2)
|
|
elif vobj.NumberingStyle == "001,002,003":
|
|
return str(num+1).zfill(3)
|
|
elif vobj.NumberingStyle == "A,B,C":
|
|
result = ""
|
|
base = num//26
|
|
if base:
|
|
result += chars[base].upper()
|
|
remainder = num % 26
|
|
result += chars[remainder].upper()
|
|
return result
|
|
elif vobj.NumberingStyle == "a,b,c":
|
|
result = ""
|
|
base = num//26
|
|
if base:
|
|
result += chars[base]
|
|
remainder = num % 26
|
|
result += chars[remainder]
|
|
return result
|
|
elif vobj.NumberingStyle == "I,II,III":
|
|
result = ""
|
|
n = num
|
|
n += 1
|
|
for numeral, integer in roman:
|
|
while n >= integer:
|
|
result += numeral
|
|
n -= integer
|
|
return result
|
|
elif vobj.NumberingStyle == "L0,L1,L2":
|
|
return "L"+str(num)
|
|
else:
|
|
return str(num+1)
|
|
|
|
def setEdit(self,vobj,mode=0):
|
|
|
|
taskd = _AxisTaskPanel()
|
|
taskd.obj = vobj.Object
|
|
taskd.update()
|
|
FreeCADGui.Control.showDialog(taskd)
|
|
return True
|
|
|
|
def unsetEdit(self,vobj,mode):
|
|
|
|
FreeCADGui.Control.closeDialog()
|
|
return
|
|
|
|
def doubleClicked(self,vobj):
|
|
|
|
self.setEdit(vobj)
|
|
|
|
def __getstate__(self):
|
|
|
|
return None
|
|
|
|
def __setstate__(self,state):
|
|
|
|
return None
|
|
|
|
|
|
class _AxisTaskPanel:
|
|
|
|
'''The editmode TaskPanel for Axis objects'''
|
|
|
|
def __init__(self):
|
|
|
|
# the panel has a tree widget that contains categories
|
|
# for the subcomponents, such as additions, subtractions.
|
|
# the categories are shown only if they are not empty.
|
|
|
|
self.updating = False
|
|
|
|
self.obj = None
|
|
self.form = QtGui.QWidget()
|
|
self.form.setObjectName("TaskPanel")
|
|
self.grid = QtGui.QGridLayout(self.form)
|
|
self.grid.setObjectName("grid")
|
|
self.title = QtGui.QLabel(self.form)
|
|
self.grid.addWidget(self.title, 0, 0, 1, 2)
|
|
|
|
# tree
|
|
self.tree = QtGui.QTreeWidget(self.form)
|
|
self.grid.addWidget(self.tree, 1, 0, 1, 2)
|
|
self.tree.setColumnCount(4)
|
|
self.tree.header().resizeSection(0,50)
|
|
self.tree.header().resizeSection(1,80)
|
|
self.tree.header().resizeSection(2,60)
|
|
|
|
# buttons
|
|
self.addButton = QtGui.QPushButton(self.form)
|
|
self.addButton.setObjectName("addButton")
|
|
self.addButton.setIcon(QtGui.QIcon(":/icons/Arch_Add.svg"))
|
|
self.grid.addWidget(self.addButton, 3, 0, 1, 1)
|
|
self.addButton.setEnabled(True)
|
|
|
|
self.delButton = QtGui.QPushButton(self.form)
|
|
self.delButton.setObjectName("delButton")
|
|
self.delButton.setIcon(QtGui.QIcon(":/icons/Arch_Remove.svg"))
|
|
self.grid.addWidget(self.delButton, 3, 1, 1, 1)
|
|
self.delButton.setEnabled(True)
|
|
|
|
QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.addElement)
|
|
QtCore.QObject.connect(self.delButton, QtCore.SIGNAL("clicked()"), self.removeElement)
|
|
QtCore.QObject.connect(self.tree, QtCore.SIGNAL("itemChanged(QTreeWidgetItem *, int)"), self.edit)
|
|
self.update()
|
|
|
|
def isAllowedAlterSelection(self):
|
|
|
|
return False
|
|
|
|
def isAllowedAlterView(self):
|
|
|
|
return True
|
|
|
|
def getStandardButtons(self):
|
|
|
|
return int(QtGui.QDialogButtonBox.Close)
|
|
|
|
def update(self):
|
|
|
|
'fills the treewidget'
|
|
self.updating = True
|
|
self.tree.clear()
|
|
if self.obj:
|
|
for i in range(len(self.obj.Distances)):
|
|
item = QtGui.QTreeWidgetItem(self.tree)
|
|
item.setText(0,str(i+1))
|
|
if len(self.obj.Distances) > i:
|
|
item.setText(1,str(self.obj.Distances[i]))
|
|
if len(self.obj.Angles) > i:
|
|
item.setText(2,str(self.obj.Angles[i]))
|
|
if hasattr(self.obj,"Labels"):
|
|
if len(self.obj.Labels) > i:
|
|
item.setText(3,str(self.obj.Labels[i]))
|
|
item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
|
|
item.setTextAlignment(0,QtCore.Qt.AlignLeft)
|
|
self.retranslateUi(self.form)
|
|
self.updating = False
|
|
|
|
def addElement(self):
|
|
|
|
item = QtGui.QTreeWidgetItem(self.tree)
|
|
item.setText(0,str(self.tree.topLevelItemCount()))
|
|
item.setText(1,"1.0")
|
|
item.setText(2,"0.0")
|
|
item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
|
|
self.resetObject()
|
|
|
|
def removeElement(self):
|
|
|
|
it = self.tree.currentItem()
|
|
if it:
|
|
nr = int(it.text(0))-1
|
|
self.resetObject(remove=nr)
|
|
self.update()
|
|
|
|
def edit(self,item,column):
|
|
|
|
if not self.updating:
|
|
self.resetObject()
|
|
|
|
def resetObject(self,remove=None):
|
|
|
|
"transfers the values from the widget to the object"
|
|
|
|
d = []
|
|
a = []
|
|
l = []
|
|
for i in range(self.tree.topLevelItemCount()):
|
|
it = self.tree.findItems(str(i+1),QtCore.Qt.MatchExactly,0)[0]
|
|
if (remove is None) or (remove != i):
|
|
if it.text(1):
|
|
d.append(float(it.text(1)))
|
|
else:
|
|
d.append(0.0)
|
|
if it.text(2):
|
|
a.append(float(it.text(2)))
|
|
else:
|
|
a.append(0.0)
|
|
l.append(it.text(3))
|
|
self.obj.Distances = d
|
|
self.obj.Angles = a
|
|
self.obj.Labels = l
|
|
self.obj.touch()
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
def reject(self):
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
FreeCADGui.ActiveDocument.resetEdit()
|
|
return True
|
|
|
|
def retranslateUi(self, TaskPanel):
|
|
|
|
TaskPanel.setWindowTitle(QtGui.QApplication.translate("Arch", "Axes", None))
|
|
self.delButton.setText(QtGui.QApplication.translate("Arch", "Remove", None))
|
|
self.addButton.setText(QtGui.QApplication.translate("Arch", "Add", None))
|
|
self.title.setText(QtGui.QApplication.translate("Arch", "Distances (mm) and angles (deg) between axes", None))
|
|
self.tree.setHeaderLabels([QtGui.QApplication.translate("Arch", "Axis", None),
|
|
QtGui.QApplication.translate("Arch", "Distance", None),
|
|
QtGui.QApplication.translate("Arch", "Angle", None),
|
|
QtGui.QApplication.translate("Arch", "Label", None)])
|
|
|
|
|
|
class _AxisSystem:
|
|
|
|
"The Axis System object"
|
|
|
|
def __init__(self,obj):
|
|
|
|
obj.Proxy = self
|
|
self.setProperties(obj)
|
|
|
|
def setProperties(self,obj):
|
|
|
|
pl = obj.PropertiesList
|
|
if not "Axes" in pl:
|
|
obj.addProperty("App::PropertyLinkList","Axes","AxisSystem", QT_TRANSLATE_NOOP("App::Property","The axes this system is made of"))
|
|
if not "Placement" in pl:
|
|
obj.addProperty("App::PropertyPlacement","Placement","AxisSystem",QT_TRANSLATE_NOOP("App::Property","The placement of this axis system"))
|
|
self.Type = "AxisSystem"
|
|
|
|
def onDocumentRestored(self,obj):
|
|
|
|
self.setProperties(obj)
|
|
|
|
def execute(self,obj):
|
|
|
|
pass
|
|
|
|
def onBeforeChange(self,obj,prop):
|
|
|
|
if prop == "Placement":
|
|
self.Placement = obj.Placement
|
|
|
|
def onChanged(self,obj,prop):
|
|
|
|
if prop == "Placement":
|
|
if hasattr(self,"Placement"):
|
|
delta = obj.Placement.multiply(self.Placement.inverse())
|
|
for o in obj.Axes:
|
|
o.Placement = delta.multiply(o.Placement)
|
|
|
|
def __getstate__(self):
|
|
|
|
return None
|
|
|
|
def __setstate__(self,state):
|
|
|
|
return None
|
|
|
|
def getPoints(self,obj):
|
|
|
|
"returns the gridpoints of linked axes"
|
|
|
|
import DraftGeomUtils
|
|
pts = []
|
|
if len(obj.Axes) == 1:
|
|
for e in obj.Axes[0].Shape.Edges:
|
|
pts.append(e.Vertexes[0].Point)
|
|
elif len(obj.Axes) == 2:
|
|
set1 = obj.Axes[0].Shape.Edges # X
|
|
set2 = obj.Axes[1].Shape.Edges # Y
|
|
for e1 in set1:
|
|
for e2 in set2:
|
|
pts.extend(DraftGeomUtils.findIntersection(e1,e2))
|
|
elif len(obj.Axes) == 3:
|
|
set1 = obj.Axes[0].Shape.Edges # X
|
|
set2 = obj.Axes[1].Shape.Edges # Y
|
|
set3 = obj.Axes[2].Shape.Edges # Z
|
|
bset = []
|
|
cv = None
|
|
for e1 in set1:
|
|
for e2 in set2:
|
|
bset.extend(DraftGeomUtils.findIntersection(e1,e2))
|
|
for e3 in set3:
|
|
if not cv:
|
|
cv = e3.Vertexes[0].Point
|
|
pts.extend(bset)
|
|
else:
|
|
v = e3.Vertexes[0].Point.sub(cv)
|
|
pts.extend([p.add(v) for p in bset])
|
|
return pts
|
|
|
|
def getAxisData(self,obj):
|
|
data = []
|
|
for axis in obj.Axes:
|
|
if hasattr(axis,"Proxy") and hasattr(axis.Proxy,"getAxisData"):
|
|
data.append(axis.Proxy.getAxisData(axis))
|
|
return data
|
|
|
|
|
|
class _ViewProviderAxisSystem:
|
|
|
|
"A View Provider for the Axis object"
|
|
|
|
def __init__(self,vobj):
|
|
|
|
vobj.Proxy = self
|
|
|
|
def getIcon(self):
|
|
|
|
import Arch_rc
|
|
return ":/icons/Arch_Axis_System_Tree.svg"
|
|
|
|
def claimChildren(self):
|
|
|
|
if hasattr(self,"axes"):
|
|
return self.axes
|
|
return []
|
|
|
|
def attach(self, vobj):
|
|
|
|
self.axes = vobj.Object.Axes
|
|
vobj.addDisplayMode(coin.SoSeparator(),"Default")
|
|
|
|
def getDisplayModes(self,vobj):
|
|
|
|
return ["Default"]
|
|
|
|
def getDefaultDisplayMode(self):
|
|
|
|
return "Default"
|
|
|
|
def setDisplayMode(self,mode):
|
|
|
|
return mode
|
|
|
|
def updateData(self,obj,prop):
|
|
|
|
self.axes = obj.Axes
|
|
|
|
def onChanged(self, vobj, prop):
|
|
|
|
if prop == "Visibility":
|
|
for o in vobj.Object.Axes:
|
|
o.ViewObject.Visibility = vobj.Visibility
|
|
|
|
def setEdit(self,vobj,mode=0):
|
|
|
|
taskd = AxisSystemTaskPanel(vobj.Object)
|
|
FreeCADGui.Control.showDialog(taskd)
|
|
return True
|
|
|
|
def unsetEdit(self,vobj,mode):
|
|
|
|
FreeCADGui.Control.closeDialog()
|
|
return
|
|
|
|
def doubleClicked(self,vobj):
|
|
|
|
self.setEdit(vobj)
|
|
|
|
def __getstate__(self):
|
|
|
|
return None
|
|
|
|
def __setstate__(self,state):
|
|
|
|
return None
|
|
|
|
|
|
class AxisSystemTaskPanel:
|
|
|
|
'''A TaskPanel for all the section plane object'''
|
|
|
|
def __init__(self,obj):
|
|
|
|
self.obj = obj
|
|
self.form = QtGui.QWidget()
|
|
self.form.setObjectName("Axis System")
|
|
self.grid = QtGui.QGridLayout(self.form)
|
|
self.grid.setObjectName("grid")
|
|
self.title = QtGui.QLabel(self.form)
|
|
self.grid.addWidget(self.title, 0, 0, 1, 2)
|
|
|
|
# tree
|
|
self.tree = QtGui.QTreeWidget(self.form)
|
|
self.grid.addWidget(self.tree, 1, 0, 1, 2)
|
|
self.tree.setColumnCount(1)
|
|
self.tree.header().hide()
|
|
|
|
# buttons
|
|
self.addButton = QtGui.QPushButton(self.form)
|
|
self.addButton.setObjectName("addButton")
|
|
self.addButton.setIcon(QtGui.QIcon(":/icons/Arch_Add.svg"))
|
|
self.grid.addWidget(self.addButton, 3, 0, 1, 1)
|
|
|
|
self.delButton = QtGui.QPushButton(self.form)
|
|
self.delButton.setObjectName("delButton")
|
|
self.delButton.setIcon(QtGui.QIcon(":/icons/Arch_Remove.svg"))
|
|
self.grid.addWidget(self.delButton, 3, 1, 1, 1)
|
|
|
|
QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.addElement)
|
|
QtCore.QObject.connect(self.delButton, QtCore.SIGNAL("clicked()"), self.removeElement)
|
|
self.update()
|
|
|
|
def isAllowedAlterSelection(self):
|
|
|
|
return True
|
|
|
|
def isAllowedAlterView(self):
|
|
|
|
return True
|
|
|
|
def getStandardButtons(self):
|
|
|
|
return int(QtGui.QDialogButtonBox.Ok)
|
|
|
|
def getIcon(self,obj):
|
|
|
|
if hasattr(obj.ViewObject,"Proxy"):
|
|
return QtGui.QIcon(obj.ViewObject.Proxy.getIcon())
|
|
elif obj.isDerivedFrom("Sketcher::SketchObject"):
|
|
return QtGui.QIcon(":/icons/Sketcher_Sketch.svg")
|
|
elif obj.isDerivedFrom("App::DocumentObjectGroup"):
|
|
return QtGui.QApplication.style().standardIcon(QtGui.QStyle.SP_DirIcon)
|
|
else:
|
|
return QtGui.QIcon(":/icons/Tree_Part.svg")
|
|
|
|
def update(self):
|
|
|
|
self.tree.clear()
|
|
if self.obj:
|
|
for o in self.obj.Axes:
|
|
item = QtGui.QTreeWidgetItem(self.tree)
|
|
item.setText(0,o.Label)
|
|
item.setToolTip(0,o.Name)
|
|
item.setIcon(0,self.getIcon(o))
|
|
self.retranslateUi(self.form)
|
|
|
|
def addElement(self):
|
|
|
|
if self.obj:
|
|
for o in FreeCADGui.Selection.getSelection():
|
|
if (not(o in self.obj.Axes)) and (o != self.obj):
|
|
g = self.obj.Axes
|
|
g.append(o)
|
|
self.obj.Axes = g
|
|
self.update()
|
|
|
|
def removeElement(self):
|
|
|
|
if self.obj:
|
|
it = self.tree.currentItem()
|
|
if it:
|
|
o = FreeCAD.ActiveDocument.getObject(str(it.toolTip(0)))
|
|
if o in self.obj.Axes:
|
|
g = self.obj.Axes
|
|
g.remove(o)
|
|
self.obj.Axes = g
|
|
self.update()
|
|
|
|
def accept(self):
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
FreeCADGui.ActiveDocument.resetEdit()
|
|
return True
|
|
|
|
def retranslateUi(self, TaskPanel):
|
|
|
|
TaskPanel.setWindowTitle(QtGui.QApplication.translate("Arch", "Axes", None))
|
|
self.delButton.setText(QtGui.QApplication.translate("Arch", "Remove", None))
|
|
self.addButton.setText(QtGui.QApplication.translate("Arch", "Add", None))
|
|
self.title.setText(QtGui.QApplication.translate("Arch", "Axis system components", None))
|
|
|
|
|
|
class ArchGrid:
|
|
|
|
"The Grid object"
|
|
|
|
def __init__(self,obj):
|
|
|
|
obj.Proxy = self
|
|
self.setProperties(obj)
|
|
|
|
def setProperties(self,obj):
|
|
|
|
pl = obj.PropertiesList
|
|
if not "Rows" in pl:
|
|
obj.addProperty("App::PropertyInteger","Rows","Grid",QT_TRANSLATE_NOOP("Arch_Grid",'The number of rows'))
|
|
if not "Columns" in pl:
|
|
obj.addProperty("App::PropertyInteger","Columns","Grid",QT_TRANSLATE_NOOP("Arch_Grid",'The number of columns'))
|
|
if not "RowSize" in pl:
|
|
obj.addProperty("App::PropertyFloatList","RowSize","Grid",QT_TRANSLATE_NOOP("Arch_Grid",'The sizes for rows'))
|
|
if not "ColumnSize" in pl:
|
|
obj.addProperty("App::PropertyFloatList","ColumnSize","Grid",QT_TRANSLATE_NOOP("Arch_Grid",'The sizes of columns'))
|
|
if not "Spans" in pl:
|
|
obj.addProperty("App::PropertyStringList","Spans","Grid",QT_TRANSLATE_NOOP("Arch_Grid",'The span ranges of cells that are merged together'))
|
|
if not "PointsOutput" in pl:
|
|
obj.addProperty("App::PropertyEnumeration","PointsOutput","Grid",QT_TRANSLATE_NOOP("Arch_Grid",'The type of 3D points produced by this grid object'))
|
|
obj.PointsOutput = ["Vertices","Edges","Vertical Edges","Horizontal Edges","Faces"]
|
|
if not "Width" in pl:
|
|
obj.addProperty("App::PropertyLength","Width","Grid",QT_TRANSLATE_NOOP("Arch_Grid",'The total width of this grid'))
|
|
if not "Height" in pl:
|
|
obj.addProperty("App::PropertyLength","Height","Grid",QT_TRANSLATE_NOOP("Arch_Grid",'The total height of this grid'))
|
|
if not "AutoWidth" in pl:
|
|
obj.addProperty("App::PropertyLength","AutoWidth","Grid",QT_TRANSLATE_NOOP("Arch_Grid",'Creates automatic column divisions (set to 0 to disable)'))
|
|
if not "AutoHeight" in pl:
|
|
obj.addProperty("App::PropertyLength","AutoHeight","Grid",QT_TRANSLATE_NOOP("Arch_Grid",'Creates automatic row divisions (set to 0 to disable)'))
|
|
if not "Reorient" in pl:
|
|
obj.addProperty("App::PropertyBool","Reorient","Grid",QT_TRANSLATE_NOOP("Arch_Grid",'When in edge midpoint mode, if this grid must reorient its children along edge normals or not'))
|
|
if not "HiddenFaces" in pl:
|
|
obj.addProperty("App::PropertyIntegerList","HiddenFaces","Grid",QT_TRANSLATE_NOOP("Arch_Grid",'The indices of faces to hide'))
|
|
self.Type = "Grid"
|
|
|
|
def onDocumentRestored(self,obj):
|
|
|
|
self.setProperties(obj)
|
|
|
|
def getSizes(self,obj):
|
|
|
|
"returns rowsizes,columnsizes,spangroups"
|
|
|
|
if not obj.Height.Value:
|
|
return [],[],[]
|
|
if not obj.Width.Value:
|
|
return [],[],[]
|
|
if (not obj.Rows) and (not obj.AutoHeight.Value):
|
|
return [],[],[]
|
|
if (not obj.Columns) and (not obj.AutoWidth.Value):
|
|
return [],[],[]
|
|
# rescale rows
|
|
rowsizes = []
|
|
if obj.AutoHeight.Value:
|
|
if obj.AutoHeight.Value > obj.Height.Value:
|
|
FreeCAD.Console.PrintError(translate("Arch","Auto height is larger than height"))
|
|
return [],[],[]
|
|
rows = int(math.floor(obj.Height.Value/obj.AutoHeight.Value))
|
|
for i in range(rows):
|
|
rowsizes.append(obj.AutoHeight.Value)
|
|
rowsizes.append(obj.Height.Value-rows*obj.AutoHeight.Value)
|
|
else:
|
|
reserved_rowsize = sum(v for v in obj.RowSize)
|
|
if reserved_rowsize > obj.Height.Value:
|
|
FreeCAD.Console.PrintError(translate("Arch","Total row size is larger than height"))
|
|
return [],[],[]
|
|
for i in range(obj.Rows):
|
|
v = 0
|
|
if i < len(obj.RowSize):
|
|
v = obj.RowSize[i]
|
|
rowsizes.append(v)
|
|
e = len([v for v in rowsizes if v == 0])
|
|
default = obj.Height.Value - reserved_rowsize
|
|
if e:
|
|
default = default / e
|
|
t = []
|
|
for v in rowsizes:
|
|
if v:
|
|
t.append(v)
|
|
else:
|
|
t.append(default)
|
|
rowsizes = t
|
|
# rescale columns
|
|
columnsizes = []
|
|
if obj.AutoWidth.Value:
|
|
if obj.AutoWidth.Value > obj.Width.Value:
|
|
FreeCAD.Console.PrintError(translate("Arch","Auto width is larger than width"))
|
|
return [],[],[]
|
|
cols = int(math.floor(obj.Width.Value/obj.AutoWidth.Value))
|
|
for i in range(cols):
|
|
columnsizes.append(obj.AutoWidth.Value)
|
|
columnsizes.append(obj.Width.Value-cols*obj.AutoWidth.Value)
|
|
else:
|
|
reserved_columnsize = sum(v for v in obj.ColumnSize)
|
|
if reserved_columnsize > obj.Width.Value:
|
|
FreeCAD.Console.PrintError(translate("Arch","Total column size is larger than width"))
|
|
return [],[],[]
|
|
for i in range(obj.Columns):
|
|
v = 0
|
|
if i < len(obj.ColumnSize):
|
|
v = obj.ColumnSize[i]
|
|
columnsizes.append(v)
|
|
e = len([v for v in columnsizes if v == 0])
|
|
default = obj.Width.Value - reserved_columnsize
|
|
if e:
|
|
default = default / e
|
|
t = []
|
|
for v in columnsizes:
|
|
if v:
|
|
t.append(v)
|
|
else:
|
|
t.append(default)
|
|
columnsizes = t
|
|
# format span groups from [row,col,rowspan,colspan] to [faceindexes]
|
|
spangroups = []
|
|
for s in obj.Spans:
|
|
nspan = []
|
|
span = [int(i.strip()) for i in s.split(",")]
|
|
for row in range(span[2]):
|
|
for column in range(span[3]):
|
|
nspan.append((span[0]+row)*obj.Columns + (span[1]+column))
|
|
spangroups.append(nspan)
|
|
return rowsizes,columnsizes,spangroups
|
|
|
|
def execute(self,obj):
|
|
|
|
pl = obj.Placement
|
|
import Part
|
|
rowsizes,columnsizes,spangroups = self.getSizes(obj)
|
|
#print rowsizes,columnsizes,spangroups
|
|
# create one face for each cell
|
|
faces = []
|
|
facenumber = 0
|
|
rowoffset = 0
|
|
for row in rowsizes:
|
|
columnoffset = 0
|
|
for column in columnsizes:
|
|
v1 = FreeCAD.Vector(columnoffset,rowoffset,0)
|
|
v2 = v1.add(FreeCAD.Vector(column,0,0))
|
|
v3 = v2.add(FreeCAD.Vector(0,-row,0))
|
|
v4 = v3.add(FreeCAD.Vector(-column,0,0))
|
|
f = Part.Face(Part.makePolygon([v1,v2,v3,v4,v1]))
|
|
if not facenumber in obj.HiddenFaces:
|
|
spanning = False
|
|
for i in range(len(spangroups)):
|
|
if facenumber in spangroups[i]:
|
|
g = spangroups[i]
|
|
g[g.index(facenumber)] = f
|
|
spangroups[i] = g
|
|
spanning = True
|
|
break
|
|
if not spanning:
|
|
faces.append(f)
|
|
facenumber += 1
|
|
columnoffset += column
|
|
rowoffset -= row
|
|
# join spangroups
|
|
for g in spangroups:
|
|
s = Part.makeShell(g)
|
|
s = s.removeSplitter()
|
|
faces.extend(s.Faces)
|
|
if faces:
|
|
obj.Shape = Part.makeCompound(faces)
|
|
obj.Placement = pl
|
|
|
|
def getPoints(self,obj):
|
|
|
|
"returns the gridpoints"
|
|
|
|
def remdupes(pts):
|
|
# eliminate possible duplicates
|
|
ret = []
|
|
for p in pts:
|
|
if not p in ret:
|
|
ret.append(p)
|
|
return ret
|
|
if obj.PointsOutput == "Vertices":
|
|
return [v.Point for v in obj.Shape.Vertexes]
|
|
elif obj.PointsOutput == "Edges":
|
|
return remdupes([e.CenterOfMass for e in obj.Shape.Edges])
|
|
elif obj.PointsOutput == "Vertical Edges":
|
|
rv = obj.Placement.Rotation.multVec(FreeCAD.Vector(0,1,0))
|
|
edges = [e for e in obj.Shape.Edges if round(rv.getAngle(e.tangentAt(e.FirstParameter)),4) in [0,3.1416]]
|
|
return remdupes([e.CenterOfMass for e in edges])
|
|
elif obj.PointsOutput == "Horizontal Edges":
|
|
rv = obj.Placement.Rotation.multVec(FreeCAD.Vector(1,0,0))
|
|
edges = [e for e in obj.Shape.Edges if round(rv.getAngle(e.tangentAt(e.FirstParameter)),4) in [0,3.1416]]
|
|
return remdupes([e.CenterOfMass for e in edges])
|
|
else:
|
|
return [f.CenterOfMass for f in obj.Shape.Faces]
|
|
|
|
def __getstate__(self):
|
|
|
|
return None
|
|
|
|
def __setstate__(self,state):
|
|
|
|
return None
|
|
|
|
|
|
class ViewProviderArchGrid:
|
|
|
|
"A View Provider for the Arch Grid"
|
|
|
|
def __init__(self,vobj):
|
|
|
|
vobj.Proxy = self
|
|
|
|
def getIcon(self):
|
|
|
|
import Arch_rc
|
|
return ":/icons/Arch_Grid.svg"
|
|
|
|
def setEdit(self,vobj,mode=0):
|
|
|
|
taskd = ArchGridTaskPanel(vobj.Object)
|
|
FreeCADGui.Control.showDialog(taskd)
|
|
return True
|
|
|
|
def unsetEdit(self,vobj,mode):
|
|
|
|
FreeCADGui.Control.closeDialog()
|
|
return
|
|
|
|
def doubleClicked(self,vobj):
|
|
|
|
self.setEdit(vobj)
|
|
|
|
def __getstate__(self):
|
|
|
|
return None
|
|
|
|
def __setstate__(self,state):
|
|
|
|
return None
|
|
|
|
|
|
class ArchGridTaskPanel:
|
|
|
|
'''A TaskPanel for the Arch Grid'''
|
|
|
|
def __init__(self,obj):
|
|
|
|
# length, width, label
|
|
self.width = 0
|
|
self.height = 0
|
|
self.spans = []
|
|
self.obj = obj
|
|
self.form = QtGui.QWidget()
|
|
uil = FreeCADGui.UiLoader()
|
|
layout = QtGui.QVBoxLayout(self.form)
|
|
hbox3 = QtGui.QHBoxLayout()
|
|
layout.addLayout(hbox3)
|
|
self.wLabel = QtGui.QLabel(self.form)
|
|
hbox3.addWidget(self.wLabel)
|
|
self.widthUi = uil.createWidget("Gui::InputField")
|
|
hbox3.addWidget(self.widthUi)
|
|
hbox4 = QtGui.QHBoxLayout()
|
|
layout.addLayout(hbox4)
|
|
self.hLabel = QtGui.QLabel(self.form)
|
|
hbox4.addWidget(self.hLabel)
|
|
self.heightUi = uil.createWidget("Gui::InputField")
|
|
hbox4.addWidget(self.heightUi)
|
|
self.title = QtGui.QLabel(self.form)
|
|
layout.addWidget(self.title)
|
|
|
|
# grid
|
|
self.table = QtGui.QTableWidget(self.form)
|
|
layout.addWidget(self.table)
|
|
style = "QTableWidget { background-color: #ffffff; gridline-color: #000000; }"
|
|
self.table.setStyleSheet(style)
|
|
self.table.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch)
|
|
|
|
# row/column buttons
|
|
hbox1 = QtGui.QHBoxLayout()
|
|
layout.addLayout(hbox1)
|
|
self.addRowButton = QtGui.QPushButton(self.form)
|
|
self.addRowButton.setIcon(QtGui.QIcon(":/icons/Arch_Add.svg"))
|
|
hbox1.addWidget(self.addRowButton)
|
|
self.delRowButton = QtGui.QPushButton(self.form)
|
|
self.delRowButton.setIcon(QtGui.QIcon(":/icons/Arch_Remove.svg"))
|
|
hbox1.addWidget(self.delRowButton)
|
|
self.addColumnButton = QtGui.QPushButton(self.form)
|
|
self.addColumnButton.setIcon(QtGui.QIcon(":/icons/Arch_Add.svg"))
|
|
hbox1.addWidget(self.addColumnButton)
|
|
self.delColumnButton = QtGui.QPushButton(self.form)
|
|
self.delColumnButton.setIcon(QtGui.QIcon(":/icons/Arch_Remove.svg"))
|
|
hbox1.addWidget(self.delColumnButton)
|
|
|
|
# span buttons
|
|
import SpreadsheetGui
|
|
hbox2 = QtGui.QHBoxLayout()
|
|
layout.addLayout(hbox2)
|
|
self.spanButton = QtGui.QPushButton(self.form)
|
|
self.spanButton.setIcon(QtGui.QIcon(":/icons/SpreadsheetMergeCells.svg"))
|
|
hbox2.addWidget(self.spanButton)
|
|
self.spanButton.setEnabled(False)
|
|
self.delSpanButton = QtGui.QPushButton(self.form)
|
|
self.delSpanButton.setIcon(QtGui.QIcon(":/icons/SpreadsheetSplitCell.svg"))
|
|
hbox2.addWidget(self.delSpanButton)
|
|
self.delSpanButton.setEnabled(False)
|
|
|
|
#signals
|
|
QtCore.QObject.connect(self.widthUi,QtCore.SIGNAL("valueChanged(double)"),self.setWidth)
|
|
QtCore.QObject.connect(self.heightUi,QtCore.SIGNAL("valueChanged(double)"),self.setHeight)
|
|
QtCore.QObject.connect(self.table, QtCore.SIGNAL("itemSelectionChanged()"), self.checkSpan)
|
|
QtCore.QObject.connect(self.addRowButton, QtCore.SIGNAL("clicked()"), self.addRow)
|
|
QtCore.QObject.connect(self.delRowButton, QtCore.SIGNAL("clicked()"), self.delRow)
|
|
QtCore.QObject.connect(self.addColumnButton, QtCore.SIGNAL("clicked()"), self.addColumn)
|
|
QtCore.QObject.connect(self.delColumnButton, QtCore.SIGNAL("clicked()"), self.delColumn)
|
|
QtCore.QObject.connect(self.spanButton, QtCore.SIGNAL("clicked()"), self.addSpan)
|
|
QtCore.QObject.connect(self.delSpanButton, QtCore.SIGNAL("clicked()"), self.removeSpan)
|
|
QtCore.QObject.connect(self.table.horizontalHeader(),QtCore.SIGNAL("sectionDoubleClicked(int)"), self.editHorizontalHeader)
|
|
QtCore.QObject.connect(self.table.verticalHeader(),QtCore.SIGNAL("sectionDoubleClicked(int)"), self.editVerticalHeader)
|
|
self.update()
|
|
self.retranslateUi()
|
|
|
|
def retranslateUi(self,widget=None):
|
|
|
|
self.form.setWindowTitle(QtGui.QApplication.translate("Arch", "Grid", None))
|
|
self.wLabel.setText(QtGui.QApplication.translate("Arch", "Total width", None))
|
|
self.hLabel.setText(QtGui.QApplication.translate("Arch", "Total height", None))
|
|
self.addRowButton.setText(QtGui.QApplication.translate("Arch", "Add row", None))
|
|
self.delRowButton.setText(QtGui.QApplication.translate("Arch", "Del row", None))
|
|
self.addColumnButton.setText(QtGui.QApplication.translate("Arch", "Add col", None))
|
|
self.delColumnButton.setText(QtGui.QApplication.translate("Arch", "Del col", None))
|
|
self.spanButton.setText(QtGui.QApplication.translate("Arch", "Create span", None))
|
|
self.delSpanButton.setText(QtGui.QApplication.translate("Arch", "Remove span", None))
|
|
self.title.setText(QtGui.QApplication.translate("Arch", "Rows", None)+": "+str(self.table.rowCount())+" / "+QtGui.QApplication.translate("Arch", "Columns", None)+": "+str(self.table.columnCount()))
|
|
|
|
def update(self):
|
|
|
|
self.table.clear()
|
|
if self.obj.Rows:
|
|
self.table.setRowCount(self.obj.Rows)
|
|
vlabels = ["0" for i in range(self.obj.Rows)]
|
|
for i,v in enumerate(self.obj.RowSize):
|
|
if i < len(vlabels):
|
|
vlabels[i] = FreeCAD.Units.Quantity(v,FreeCAD.Units.Length).getUserPreferred()[0]
|
|
self.table.setVerticalHeaderLabels(vlabels)
|
|
if self.obj.Columns:
|
|
self.table.setColumnCount(self.obj.Columns)
|
|
hlabels = ["0" for i in range(self.obj.Columns)]
|
|
for i,v in enumerate(self.obj.ColumnSize):
|
|
if i < len(hlabels):
|
|
hlabels[i] = FreeCAD.Units.Quantity(v,FreeCAD.Units.Length).getUserPreferred()[0]
|
|
self.table.setHorizontalHeaderLabels(hlabels)
|
|
self.widthUi.setText(self.obj.Width.getUserPreferred()[0])
|
|
self.heightUi.setText(self.obj.Height.getUserPreferred()[0])
|
|
self.spans = []
|
|
for s in self.obj.Spans:
|
|
span = [int(i.strip()) for i in s.split(",")]
|
|
if len(span) == 4:
|
|
self.table.setSpan(span[0],span[1],span[2],span[3])
|
|
self.spans.append(span)
|
|
|
|
def checkSpan(self):
|
|
|
|
if self.table.selectedRanges():
|
|
self.spanButton.setEnabled(False)
|
|
self.delSpanButton.setEnabled(False)
|
|
if len(self.table.selectedRanges()) > 1:
|
|
self.spanButton.setEnabled(True)
|
|
for r in self.table.selectedRanges():
|
|
if (r.rowCount() * r.columnCount()) > 1:
|
|
self.spanButton.setEnabled(True)
|
|
elif (r.rowCount() * r.columnCount()) == 1:
|
|
if self.table.rowSpan(r.topRow(),r.leftColumn()) > 1 \
|
|
or self.table.columnSpan(r.topRow(),r.leftColumn()) > 1:
|
|
self.delSpanButton.setEnabled(True)
|
|
else:
|
|
self.spanButton.setEnabled(False)
|
|
self.delSpanButton.setEnabled(False)
|
|
|
|
def addRow(self):
|
|
|
|
c = self.table.currentRow()
|
|
self.table.insertRow(c+1)
|
|
self.table.setVerticalHeaderItem(c+1,QtGui.QTableWidgetItem("0"))
|
|
self.retranslateUi()
|
|
|
|
def delRow(self):
|
|
|
|
if self.table.selectedRanges():
|
|
self.table.removeRow(self.table.currentRow())
|
|
self.retranslateUi()
|
|
|
|
def addColumn(self):
|
|
|
|
c = self.table.currentColumn()
|
|
self.table.insertColumn(c+1)
|
|
self.table.setHorizontalHeaderItem(c+1,QtGui.QTableWidgetItem("0"))
|
|
self.retranslateUi()
|
|
|
|
def delColumn(self):
|
|
|
|
if self.table.selectedRanges():
|
|
self.table.removeColumn(self.table.currentColumn())
|
|
self.retranslateUi()
|
|
|
|
def addSpan(self):
|
|
|
|
for r in self.table.selectedRanges():
|
|
if r.rowCount() * r.columnCount() > 1:
|
|
self.table.setSpan(r.topRow(),r.leftColumn(),r.rowCount(),r.columnCount())
|
|
self.spans.append([r.topRow(),r.leftColumn(),r.rowCount(),r.columnCount()])
|
|
return
|
|
if len(self.table.selectedRanges()) > 1:
|
|
tr = 99999
|
|
br = 0
|
|
lc = 99999
|
|
rc = 0
|
|
for r in self.table.selectedRanges():
|
|
if r.topRow() < tr:
|
|
tr = r.topRow()
|
|
if r.bottomRow() > br:
|
|
br = r.bottomRow()
|
|
if r.leftColumn() < lc:
|
|
lc = r.leftColumn()
|
|
if r.rightColumn() > rc:
|
|
rc = r.rightColumn()
|
|
if (rc >= lc) and (br >= tr):
|
|
self.table.setSpan(tr,lc,(br-tr)+1,(rc-lc)+1)
|
|
self.spans.append([tr,lc,(br-tr)+1,(rc-lc)+1])
|
|
|
|
def removeSpan(self):
|
|
|
|
for r in self.table.selectedRanges():
|
|
if r.rowCount() * r.columnCount() == 1:
|
|
if self.table.rowSpan(r.topRow(),r.leftColumn()) > 1 \
|
|
or self.table.columnSpan(r.topRow(),r.leftColumn()) > 1:
|
|
self.table.setSpan(r.topRow(),r.leftColumn(),1,1)
|
|
f = None
|
|
for i,s in enumerate(self.spans):
|
|
if (s[0] == r.topRow()) and (s[1] == r.leftColumn()):
|
|
f = i
|
|
break
|
|
if f != None:
|
|
self.spans.pop(f)
|
|
|
|
def editHorizontalHeader(self, index):
|
|
|
|
val,ok = QtGui.QInputDialog.getText(None,'Edit size','New size')
|
|
if ok:
|
|
self.table.setHorizontalHeaderItem(index,QtGui.QTableWidgetItem(val))
|
|
|
|
def editVerticalHeader(self, index):
|
|
|
|
val,ok = QtGui.QInputDialog.getText(None,'Edit size','New size')
|
|
if ok:
|
|
self.table.setVerticalHeaderItem(index,QtGui.QTableWidgetItem(val))
|
|
|
|
def setWidth(self,d):
|
|
|
|
self.width = d
|
|
|
|
def setHeight(self,d):
|
|
|
|
self.height = d
|
|
|
|
def accept(self):
|
|
|
|
self.obj.Width = self.width
|
|
self.obj.Height = self.height
|
|
self.obj.Rows = self.table.rowCount()
|
|
self.obj.Columns = self.table.columnCount()
|
|
self.obj.RowSize = [FreeCAD.Units.Quantity(self.table.verticalHeaderItem(i).text()).Value for i in range(self.table.rowCount())]
|
|
self.obj.ColumnSize = [FreeCAD.Units.Quantity(self.table.horizontalHeaderItem(i).text()).Value for i in range(self.table.columnCount())]
|
|
self.obj.Spans = [str(s)[1:-1] for s in self.spans]
|
|
FreeCAD.ActiveDocument.recompute()
|
|
FreeCADGui.ActiveDocument.resetEdit()
|
|
return True
|
|
|
|
def reject(self):
|
|
|
|
FreeCADGui.ActiveDocument.resetEdit()
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
if FreeCAD.GuiUp:
|
|
FreeCADGui.addCommand('Arch_Axis',_CommandAxis())
|
|
FreeCADGui.addCommand('Arch_AxisSystem',_CommandAxisSystem())
|
|
FreeCADGui.addCommand('Arch_Grid',CommandArchGrid())
|
|
|
|
class _ArchAxisGroupCommand:
|
|
|
|
def GetCommands(self):
|
|
return tuple(['Arch_Axis','Arch_AxisSystem','Arch_Grid'])
|
|
def GetResources(self):
|
|
return { 'MenuText': QT_TRANSLATE_NOOP("Arch_AxisTools",'Axis tools'),
|
|
'ToolTip': QT_TRANSLATE_NOOP("Arch_AxisTools",'Axis tools')
|
|
}
|
|
def IsActive(self):
|
|
return not FreeCAD.ActiveDocument is None
|
|
|
|
FreeCADGui.addCommand('Arch_AxisTools', _ArchAxisGroupCommand())
|