1404 lines
61 KiB
Python
1404 lines
61 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 os
|
|
|
|
import FreeCAD
|
|
import ArchCommands
|
|
import ArchComponent
|
|
import Draft
|
|
import DraftVecUtils
|
|
import ArchWindowPresets
|
|
from FreeCAD import Units
|
|
from FreeCAD import Vector
|
|
from draftutils import params
|
|
from draftutils.messages import _wrn
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
from PySide import QtCore, QtGui
|
|
from draftutils.translate import translate
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
import draftguitools.gui_trackers as DraftTrackers
|
|
else:
|
|
# \cond
|
|
def translate(ctxt,txt):
|
|
return txt
|
|
def QT_TRANSLATE_NOOP(ctxt,txt):
|
|
return txt
|
|
# \endcond
|
|
|
|
## @package ArchWindow
|
|
# \ingroup ARCH
|
|
# \brief The Window object and tools
|
|
#
|
|
# This module provides tools to build Window objects.
|
|
# Windows are Arch objects obtained by extruding a series
|
|
# of wires, and that can be inserted into other Arch objects,
|
|
# by defining a volume that gets subtracted from them.
|
|
|
|
__title__ = "FreeCAD Window"
|
|
__author__ = "Yorik van Havre"
|
|
__url__ = "https://www.freecad.org"
|
|
|
|
# presets
|
|
WindowPartTypes = ["Frame","Solid panel","Glass panel","Louvre"]
|
|
WindowOpeningModes = ["None","Arc 90","Arc 90 inv","Arc 45","Arc 45 inv","Arc 180",
|
|
"Arc 180 inv","Triangle","Triangle inv","Sliding","Sliding inv"]
|
|
WindowPresets = ArchWindowPresets.WindowPresets
|
|
|
|
|
|
|
|
|
|
def recolorize(attr): # names is [docname,objname]
|
|
|
|
"""Recolorizes an object or a [documentname,objectname] list
|
|
This basically calls the Proxy.colorize(obj) methods of objects that
|
|
have one."""
|
|
|
|
if isinstance(attr,list):
|
|
if attr[0] in FreeCAD.listDocuments():
|
|
doc = FreeCAD.getDocument(attr[0])
|
|
obj = doc.getObject(attr[1])
|
|
if obj:
|
|
if obj.ViewObject:
|
|
if obj.ViewObject.Proxy:
|
|
obj.ViewObject.Proxy.colorize(obj)
|
|
elif hasattr(attr,"ViewObject") and attr.ViewObject:
|
|
obj = attr
|
|
if hasattr(obj.ViewObject,"Proxy") and hasattr(obj.ViewObject.Proxy,"colorize"):
|
|
obj.ViewObject.Proxy.colorize(obj)
|
|
|
|
|
|
|
|
class _Window(ArchComponent.Component):
|
|
|
|
"The Window object"
|
|
|
|
def __init__(self,obj):
|
|
|
|
ArchComponent.Component.__init__(self,obj)
|
|
self.setProperties(obj)
|
|
obj.IfcType = "Window"
|
|
obj.MoveWithHost = True
|
|
|
|
# Add features in the SketchArch External Add-on
|
|
self.addSketchArchFeatures(obj)
|
|
|
|
def addSketchArchFeatures(self,obj,linkObj=None,mode=None):
|
|
'''
|
|
To add features in the SketchArch External Add-on (https://github.com/paullee0/FreeCAD_SketchArch)
|
|
- import ArchSketchObject module, and
|
|
- set properties that are common to ArchObjects (including Links) and ArchSketch
|
|
to support the additional features
|
|
|
|
To install SketchArch External Add-on, see https://github.com/paullee0/FreeCAD_SketchArch#iv-install
|
|
'''
|
|
|
|
try:
|
|
import ArchSketchObject
|
|
ArchSketchObject.ArchSketch.setPropertiesLinkCommon(self, obj, linkObj, mode)
|
|
except:
|
|
pass
|
|
|
|
def setProperties(self,obj):
|
|
|
|
lp = obj.PropertiesList
|
|
if not "Hosts" in lp:
|
|
obj.addProperty("App::PropertyLinkList","Hosts","Window",QT_TRANSLATE_NOOP("App::Property","The objects that host this window"))
|
|
if not "WindowParts" in lp:
|
|
obj.addProperty("App::PropertyStringList","WindowParts","Window",QT_TRANSLATE_NOOP("App::Property","The components of this window"))
|
|
obj.setEditorMode("WindowParts",2)
|
|
if not "HoleDepth" in lp:
|
|
obj.addProperty("App::PropertyLength","HoleDepth","Window",QT_TRANSLATE_NOOP("App::Property","The depth of the hole that this window makes in its host object. If 0, the value will be calculated automatically."))
|
|
if not "Subvolume" in lp:
|
|
obj.addProperty("App::PropertyLink","Subvolume","Window",QT_TRANSLATE_NOOP("App::Property","An optional object that defines a volume to be subtracted from hosts of this window"))
|
|
if not "Width" in lp:
|
|
obj.addProperty("App::PropertyLength","Width","Window",QT_TRANSLATE_NOOP("App::Property","The width of this window"))
|
|
if not "Height" in lp:
|
|
obj.addProperty("App::PropertyLength","Height","Window",QT_TRANSLATE_NOOP("App::Property","The height of this window"))
|
|
if not "Normal" in lp:
|
|
obj.addProperty("App::PropertyVector","Normal","Window",QT_TRANSLATE_NOOP("App::Property","The normal direction of this window"))
|
|
if not "Preset" in lp:
|
|
obj.addProperty("App::PropertyInteger","Preset","Window",QT_TRANSLATE_NOOP("App::Property","The preset number this window is based on"))
|
|
obj.setEditorMode("Preset",2)
|
|
if not "Frame" in lp:
|
|
obj.addProperty("App::PropertyLength","Frame","Window",QT_TRANSLATE_NOOP("App::Property","The frame size of this window"))
|
|
if not "Offset" in lp:
|
|
obj.addProperty("App::PropertyLength","Offset","Window",QT_TRANSLATE_NOOP("App::Property","The offset size of this window"))
|
|
if not "Area" in lp:
|
|
obj.addProperty("App::PropertyArea","Area","Window",QT_TRANSLATE_NOOP("App::Property","The area of this window"))
|
|
if not "LouvreWidth" in lp:
|
|
obj.addProperty("App::PropertyLength","LouvreWidth","Window",QT_TRANSLATE_NOOP("App::Property","The width of louvre elements"))
|
|
if not "LouvreSpacing" in lp:
|
|
obj.addProperty("App::PropertyLength","LouvreSpacing","Window",QT_TRANSLATE_NOOP("App::Property","The space between louvre elements"))
|
|
if not "Opening" in lp:
|
|
obj.addProperty("App::PropertyPercent","Opening","Window",QT_TRANSLATE_NOOP("App::Property","Opens the subcomponents that have a hinge defined"))
|
|
if not "HoleWire" in lp:
|
|
obj.addProperty("App::PropertyInteger","HoleWire","Window",QT_TRANSLATE_NOOP("App::Property","The number of the wire that defines the hole. If 0, the value will be calculated automatically"))
|
|
if not "SymbolPlan" in lp:
|
|
obj.addProperty("App::PropertyBool","SymbolPlan","Window",QT_TRANSLATE_NOOP("App::Property","Shows plan opening symbols if available"))
|
|
if not "SymbolElevation" in lp:
|
|
obj.addProperty("App::PropertyBool","SymbolElevation","Window",QT_TRANSLATE_NOOP("App::Property","Show elevation opening symbols if available"))
|
|
obj.setEditorMode("VerticalArea",2)
|
|
obj.setEditorMode("HorizontalArea",2)
|
|
obj.setEditorMode("PerimeterLength",2)
|
|
self.Type = "Window"
|
|
|
|
def onDocumentRestored(self,obj):
|
|
|
|
ArchComponent.Component.onDocumentRestored(self,obj)
|
|
self.setProperties(obj)
|
|
|
|
# Add features in the SketchArch External Add-on
|
|
self.addSketchArchFeatures(obj, mode='ODR')
|
|
|
|
def onBeforeChange(self,obj,prop):
|
|
|
|
if prop in ["Base","WindowParts","Placement","HoleDepth","Height","Width","Hosts"]:
|
|
setattr(self,prop,getattr(obj,prop))
|
|
if prop in ["Height","Width"] and obj.CloneOf is None:
|
|
self.TouchOnShapeChange = True # touch hosts after next "Shape" change
|
|
|
|
def onChanged(self,obj,prop):
|
|
|
|
self.hideSubobjects(obj,prop)
|
|
if not "Restore" in obj.State:
|
|
if prop in ["Base","WindowParts","Placement","HoleDepth","Height","Width","Hosts","Shape"]:
|
|
# anti-recursive loops, bc the base sketch will touch the Placement all the time
|
|
touchhosts = False
|
|
if prop == "Shape":
|
|
if hasattr(self,"TouchOnShapeChange") and self.TouchOnShapeChange:
|
|
self.TouchOnShapeChange = False
|
|
touchhosts = True
|
|
elif hasattr(self,prop) and getattr(self,prop) != getattr(obj,prop):
|
|
touchhosts = True
|
|
if touchhosts:
|
|
hosts = self.Hosts if hasattr(self, "Hosts") else []
|
|
hosts += obj.Hosts if hasattr(obj, "Hosts") else []
|
|
for host in set(hosts): # use set to remove duplicates
|
|
# mark host to recompute so it can detect this object
|
|
host.touch()
|
|
if prop in ["Width","Height","Frame"]:
|
|
if obj.Base:
|
|
if hasattr(obj.Base,"Constraints") and (prop in [c.Name for c in obj.Base.Constraints]):
|
|
val = getattr(obj,prop).Value
|
|
if val > 0:
|
|
obj.Base.setDatum(prop,val)
|
|
else:
|
|
ArchComponent.Component.onChanged(self,obj,prop)
|
|
|
|
|
|
def buildShapes(self,obj):
|
|
|
|
import Part
|
|
import DraftGeomUtils
|
|
import math
|
|
self.sshapes = []
|
|
self.vshapes = []
|
|
shapes = []
|
|
rotdata = None
|
|
for i in range(int(len(obj.WindowParts)/5)):
|
|
wires = []
|
|
hinge = None
|
|
omode = None
|
|
ssymbols = []
|
|
vsymbols = []
|
|
wstr = obj.WindowParts[(i*5)+2].split(',')
|
|
for s in wstr:
|
|
if "Wire" in s:
|
|
j = int(s[4:])
|
|
if obj.Base.Shape.Wires:
|
|
if len(obj.Base.Shape.Wires) >= j:
|
|
wires.append(obj.Base.Shape.Wires[j])
|
|
elif "Edge" in s:
|
|
hinge = int(s[4:])-1
|
|
elif "Mode" in s:
|
|
omode = int(s[4:])
|
|
if omode >= len(WindowOpeningModes):
|
|
# Ignore modes not listed in WindowOpeningModes
|
|
omode = None
|
|
if wires:
|
|
max_length = 0
|
|
for w in wires:
|
|
if w.BoundBox.DiagonalLength > max_length:
|
|
max_length = w.BoundBox.DiagonalLength
|
|
ext = w
|
|
wires.remove(ext)
|
|
shape = Part.Face(ext)
|
|
norm = shape.normalAt(0,0)
|
|
if hasattr(obj,"Normal"):
|
|
if obj.Normal:
|
|
if not DraftVecUtils.isNull(obj.Normal):
|
|
norm = obj.Normal
|
|
if hinge and omode:
|
|
opening = None
|
|
if hasattr(obj,"Opening"):
|
|
if obj.Opening:
|
|
opening = obj.Opening/100.0
|
|
e = obj.Base.Shape.Edges[hinge]
|
|
ev1 = e.Vertexes[0].Point
|
|
ev2 = e.Vertexes[-1].Point
|
|
# choose the one with lowest z to draw the symbol
|
|
if ev2.z < ev1.z:
|
|
ev1,ev2 = ev2,ev1
|
|
# find the point most distant from the hinge
|
|
p = None
|
|
d = 0
|
|
for v in shape.Vertexes:
|
|
dist = v.Point.distanceToLine(ev1,ev2.sub(ev1))
|
|
if dist > d:
|
|
d = dist
|
|
p = v.Point
|
|
if p:
|
|
# bring that point to the level of ev1 if needed
|
|
chord = p.sub(ev1)
|
|
enorm = ev2.sub(ev1)
|
|
proj = DraftVecUtils.project(chord,enorm)
|
|
v1 = ev1
|
|
if proj.Length > 0:
|
|
#chord = p.sub(ev1.add(proj))
|
|
#p = v1.add(chord)
|
|
p = p.sub(proj)
|
|
chord = p.sub(ev1)
|
|
# calculate symbols
|
|
v4 = p.add(DraftVecUtils.scale(enorm,0.5))
|
|
if omode == 1: # Arc 90
|
|
v2 = v1.add(DraftVecUtils.rotate(chord,math.pi/4,enorm))
|
|
v3 = v1.add(DraftVecUtils.rotate(chord,math.pi/2,enorm))
|
|
ssymbols.append(Part.Arc(p,v2,v3).toShape())
|
|
ssymbols.append(Part.LineSegment(v3,v1).toShape())
|
|
vsymbols.append(Part.LineSegment(v1,v4).toShape())
|
|
vsymbols.append(Part.LineSegment(v4,ev2).toShape())
|
|
if opening:
|
|
rotdata = [v1,ev2.sub(ev1),90*opening]
|
|
elif omode == 2: # Arc -90
|
|
v2 = v1.add(DraftVecUtils.rotate(chord,-math.pi/4,enorm))
|
|
v3 = v1.add(DraftVecUtils.rotate(chord,-math.pi/2,enorm))
|
|
ssymbols.append(Part.Arc(p,v2,v3).toShape())
|
|
ssymbols.append(Part.LineSegment(v3,v1).toShape())
|
|
vsymbols.append(Part.LineSegment(v1,v4).toShape())
|
|
vsymbols.append(Part.LineSegment(v4,ev2).toShape())
|
|
if opening:
|
|
rotdata = [v1,ev2.sub(ev1),-90*opening]
|
|
elif omode == 3: # Arc 45
|
|
v2 = v1.add(DraftVecUtils.rotate(chord,math.pi/8,enorm))
|
|
v3 = v1.add(DraftVecUtils.rotate(chord,math.pi/4,enorm))
|
|
ssymbols.append(Part.Arc(p,v2,v3).toShape())
|
|
ssymbols.append(Part.LineSegment(v3,v1).toShape())
|
|
vsymbols.append(Part.LineSegment(v1,v4).toShape())
|
|
vsymbols.append(Part.LineSegment(v4,ev2).toShape())
|
|
if opening:
|
|
rotdata = [v1,ev2.sub(ev1),45*opening]
|
|
elif omode == 4: # Arc -45
|
|
v2 = v1.add(DraftVecUtils.rotate(chord,-math.pi/8,enorm))
|
|
v3 = v1.add(DraftVecUtils.rotate(chord,-math.pi/4,enorm))
|
|
ssymbols.append(Part.Arc(p,v2,v3).toShape())
|
|
ssymbols.append(Part.LineSegment(v3,v1).toShape())
|
|
vsymbols.append(Part.LineSegment(v1,v4).toShape())
|
|
vsymbols.append(Part.LineSegment(v4,ev2).toShape())
|
|
if opening:
|
|
rotdata = [v1,ev2.sub(ev1),-45*opening]
|
|
elif omode == 5: # Arc 180
|
|
v2 = v1.add(DraftVecUtils.rotate(chord,math.pi/2,enorm))
|
|
v3 = v1.add(DraftVecUtils.rotate(chord,math.pi,enorm))
|
|
ssymbols.append(Part.Arc(p,v2,v3).toShape())
|
|
ssymbols.append(Part.LineSegment(v3,v1).toShape())
|
|
vsymbols.append(Part.LineSegment(v1,v4).toShape())
|
|
vsymbols.append(Part.LineSegment(v4,ev2).toShape())
|
|
if opening:
|
|
rotdata = [v1,ev2.sub(ev1),180*opening]
|
|
elif omode == 6: # Arc -180
|
|
v2 = v1.add(DraftVecUtils.rotate(chord,-math.pi/2,enorm))
|
|
v3 = v1.add(DraftVecUtils.rotate(chord,-math.pi,enorm))
|
|
ssymbols.append(Part.Arc(p,v2,v3).toShape())
|
|
ssymbols.append(Part.LineSegment(v3,v1).toShape())
|
|
vsymbols.append(Part.LineSegment(v1,v4).toShape())
|
|
vsymbols.append(Part.LineSegment(v4,ev2).toShape())
|
|
if opening:
|
|
rotdata = [ev1,ev2.sub(ev1),-180*opening]
|
|
elif omode == 7: # tri
|
|
v2 = v1.add(DraftVecUtils.rotate(chord,math.pi/2,enorm))
|
|
ssymbols.append(Part.LineSegment(p,v2).toShape())
|
|
ssymbols.append(Part.LineSegment(v2,v1).toShape())
|
|
vsymbols.append(Part.LineSegment(v1,v4).toShape())
|
|
vsymbols.append(Part.LineSegment(v4,ev2).toShape())
|
|
if opening:
|
|
rotdata = [v1,ev2.sub(ev1),90*opening]
|
|
elif omode == 8: # -tri
|
|
v2 = v1.add(DraftVecUtils.rotate(chord,-math.pi/2,enorm))
|
|
ssymbols.append(Part.LineSegment(p,v2).toShape())
|
|
ssymbols.append(Part.LineSegment(v2,v1).toShape())
|
|
vsymbols.append(Part.LineSegment(v1,v4).toShape())
|
|
vsymbols.append(Part.LineSegment(v4,ev2).toShape())
|
|
if opening:
|
|
rotdata = [v1,ev2.sub(ev1),-90*opening]
|
|
elif omode == 9: # sliding
|
|
pass
|
|
elif omode == 10: # -sliding
|
|
pass
|
|
exv = FreeCAD.Vector()
|
|
zov = FreeCAD.Vector()
|
|
V = 0
|
|
thk = obj.WindowParts[(i*5)+3]
|
|
if "+V" in thk:
|
|
thk = thk[:-2]
|
|
V = obj.Frame.Value
|
|
thk = float(thk) + V
|
|
if thk:
|
|
exv = DraftVecUtils.scaleTo(norm,thk)
|
|
shape = shape.extrude(exv)
|
|
for w in wires:
|
|
f = Part.Face(w)
|
|
f = f.extrude(exv)
|
|
shape = shape.cut(f)
|
|
if obj.WindowParts[(i*5)+4]:
|
|
V = 0
|
|
zof = obj.WindowParts[(i*5)+4]
|
|
if "+V" in zof:
|
|
zof = zof[:-2]
|
|
V = obj.Offset.Value
|
|
zof = float(zof) + V
|
|
if zof:
|
|
zov = DraftVecUtils.scaleTo(norm,zof)
|
|
shape.translate(zov)
|
|
if hinge and omode and 0 < omode < 9:
|
|
if DraftVecUtils.angle(chord, norm, enorm) < 0:
|
|
if omode%2 == 0:
|
|
zov = zov.add(exv)
|
|
else:
|
|
if omode%2 == 1:
|
|
zov = zov.add(exv)
|
|
for symb in ssymbols:
|
|
symb.translate(zov)
|
|
for symb in vsymbols:
|
|
symb.translate(zov)
|
|
if rotdata:
|
|
rotdata[0] = rotdata[0].add(zov)
|
|
if obj.WindowParts[(i*5)+1] == "Louvre":
|
|
if hasattr(obj,"LouvreWidth"):
|
|
if obj.LouvreWidth and obj.LouvreSpacing:
|
|
bb = shape.BoundBox
|
|
bb.enlarge(10)
|
|
step = obj.LouvreWidth.Value+obj.LouvreSpacing.Value
|
|
if step < bb.ZLength:
|
|
box = Part.makeBox(bb.XLength,bb.YLength,obj.LouvreSpacing.Value)
|
|
boxes = []
|
|
for i in range(int(bb.ZLength/step)+1):
|
|
b = box.copy()
|
|
b.translate(FreeCAD.Vector(bb.XMin,bb.YMin,bb.ZMin+i*step))
|
|
boxes.append(b)
|
|
self.boxes = Part.makeCompound(boxes)
|
|
#rot = obj.Base.Placement.Rotation
|
|
#self.boxes.rotate(self.boxes.BoundBox.Center,rot.Axis,math.degrees(rot.Angle))
|
|
self.boxes.translate(shape.BoundBox.Center.sub(self.boxes.BoundBox.Center))
|
|
shape = shape.cut(self.boxes)
|
|
if rotdata:
|
|
shape.rotate(rotdata[0],rotdata[1],rotdata[2])
|
|
shapes.append(shape)
|
|
self.sshapes.extend(ssymbols)
|
|
self.vshapes.extend(vsymbols)
|
|
return shapes
|
|
|
|
def execute(self,obj):
|
|
|
|
if self.clone(obj):
|
|
clonedProxy = obj.CloneOf.Proxy
|
|
if not (hasattr(clonedProxy, "sshapes") and hasattr(clonedProxy, "vshapes")):
|
|
clonedProxy.buildShapes(obj.CloneOf)
|
|
self.sshapes = clonedProxy.sshapes
|
|
self.vshapes = clonedProxy.vshapes
|
|
if hasattr(clonedProxy, "boxes"):
|
|
self.boxes = clonedProxy.boxes
|
|
return
|
|
|
|
import Part
|
|
import DraftGeomUtils
|
|
import math
|
|
pl = obj.Placement
|
|
base = None
|
|
self.sshapes = []
|
|
self.vshapes = []
|
|
if obj.Base:
|
|
if hasattr(obj,'Shape'):
|
|
if hasattr(obj,"WindowParts"):
|
|
if obj.WindowParts and (len(obj.WindowParts)%5 == 0):
|
|
shapes = self.buildShapes(obj)
|
|
if shapes:
|
|
base = Part.makeCompound(shapes)
|
|
elif not obj.WindowParts:
|
|
if obj.Base.Shape.Solids:
|
|
base = obj.Base.Shape.copy()
|
|
# obj placement is already added by applyShape() below
|
|
#if not DraftGeomUtils.isNull(pl):
|
|
# base.Placement = base.Placement.multiply(pl)
|
|
else:
|
|
print("Arch: Bad formatting of window parts definitions")
|
|
|
|
base = self.processSubShapes(obj,base)
|
|
if base:
|
|
if not base.isNull():
|
|
b = []
|
|
if self.sshapes:
|
|
if hasattr(obj,"SymbolPlan"):
|
|
if obj.SymbolPlan:
|
|
b.extend(self.sshapes)
|
|
else:
|
|
b.extend(self.sshapes)
|
|
if self.vshapes:
|
|
if hasattr(obj,"SymbolElevation"):
|
|
if obj.SymbolElevation:
|
|
b.extend(self.vshapes)
|
|
else:
|
|
b.extend(self.vshapes)
|
|
if b:
|
|
base = Part.makeCompound([base]+b)
|
|
#base = Part.makeCompound([base]+self.sshapes+self.vshapes)
|
|
self.applyShape(obj,base,pl,allowinvalid=True,allownosolid=True)
|
|
obj.Placement = pl
|
|
else:
|
|
obj.Shape = Part.Shape()
|
|
if hasattr(obj,"Area"):
|
|
obj.Area = obj.Width.Value * obj.Height.Value
|
|
|
|
self.executeSketchArchFeatures(obj)
|
|
|
|
def executeSketchArchFeatures(self, obj, linkObj=None, index=None, linkElement=None):
|
|
'''
|
|
To execute features in the SketchArch External Add-on (https://github.com/paullee0/FreeCAD_SketchArch)
|
|
- import ArchSketchObject module, and
|
|
- execute features that are common to ArchObjects (including Links) and ArchSketch
|
|
|
|
To install SketchArch External Add-on, see https://github.com/paullee0/FreeCAD_SketchArch#iv-install
|
|
'''
|
|
|
|
# To execute features in SketchArch External Add-on
|
|
try:
|
|
import ArchSketchObject # Why needed ? Should have try: addSketchArchFeatures() before ! Need 'per method' ?
|
|
# Execute SketchArch Feature - Intuitive Automatic Placement for Arch Windows/Doors, Equipment etc.
|
|
# see https://forum.freecad.org/viewtopic.php?f=23&t=50802
|
|
ArchSketchObject.updateAttachmentOffset(obj, linkObj)
|
|
except:
|
|
pass
|
|
|
|
def appLinkExecute(self, obj, linkObj, index, linkElement):
|
|
'''
|
|
Default Link Execute method() -
|
|
See https://forum.freecad.org/viewtopic.php?f=22&t=42184&start=10#p361124
|
|
@realthunder added support to Links to run Linked Scripted Object's methods()
|
|
'''
|
|
|
|
# Add features in the SketchArch External Add-on
|
|
self.addSketchArchFeatures(obj, linkObj)
|
|
|
|
# Execute features in the SketchArch External Add-on
|
|
self.executeSketchArchFeatures(obj, linkObj)
|
|
|
|
def getSubVolume(self,obj,plac=None):
|
|
|
|
"returns a subvolume for cutting in a base object"
|
|
|
|
# check if we have a custom subvolume
|
|
if hasattr(obj,"Subvolume"):
|
|
if obj.Subvolume:
|
|
if hasattr(obj.Subvolume,'Shape'):
|
|
if not obj.Subvolume.Shape.isNull():
|
|
sh = obj.Subvolume.Shape.copy()
|
|
pl = FreeCAD.Placement(sh.Placement)
|
|
pl = obj.Placement.multiply(pl)
|
|
if plac:
|
|
pl = plac.multiply(pl)
|
|
sh.Placement = pl
|
|
return sh
|
|
|
|
# getting extrusion depth
|
|
base = None
|
|
if obj.Base:
|
|
base = obj.Base
|
|
width = 0
|
|
if hasattr(obj,"HoleDepth"): # the code have not checked whether this is a clone and use the original's HoleDepth; if HoleDepth is set in this object, even it is a clone, the original's HoleDepth is overridden
|
|
if obj.HoleDepth.Value:
|
|
width = obj.HoleDepth.Value
|
|
if not width:
|
|
if base:
|
|
b = base.Shape.BoundBox
|
|
width = max(b.XLength,b.YLength,b.ZLength)
|
|
if not width:
|
|
if Draft.isClone(obj,"Window"): # check whether this is a clone and use the original's HoleDepth or Shape's Boundbox
|
|
if hasattr(obj,"CloneOf"):
|
|
orig = obj.CloneOf
|
|
else:
|
|
orig = obj.Objects[0]
|
|
if orig.Base:
|
|
base = orig.Base
|
|
|
|
if hasattr(orig,"HoleDepth"):
|
|
if orig.HoleDepth.Value:
|
|
width = orig.HoleDepth.Value
|
|
if not width:
|
|
if base:
|
|
b = base.Shape.BoundBox
|
|
width = max(b.XLength,b.YLength,b.ZLength)
|
|
if not width:
|
|
width = 1.1112 # some weird value to have little chance to overlap with an existing face
|
|
|
|
if not base:
|
|
if Draft.isClone(obj,"Window"): # if this object has not base, check whether this is a clone and use the original's base
|
|
if hasattr(obj,"CloneOf"):
|
|
orig = obj.CloneOf
|
|
else:
|
|
orig = obj.Objects[0] # not sure what is this exactly
|
|
if orig.Base:
|
|
base = orig.Base
|
|
else:
|
|
return None
|
|
|
|
# finding which wire to use to drill the hole
|
|
|
|
f = None
|
|
if hasattr(obj,"HoleWire"): # the code have not checked whether this is a clone and use the original's HoleWire; if HoleWire is set in this object, even it is a clone, the original's BoundBox/HoleWire is overridden
|
|
if obj.HoleWire > 0:
|
|
if obj.HoleWire <= len(base.Shape.Wires):
|
|
f = base.Shape.Wires[obj.HoleWire-1]
|
|
|
|
if not f:
|
|
if Draft.isClone(obj,"Window"):
|
|
# check original HoleWire then
|
|
if orig.HoleWire > 0:
|
|
if orig.HoleWire <= len(base.Shape.Wires):
|
|
f = base.Shape.Wires[obj.HoleWire-1]
|
|
|
|
if not f:
|
|
# finding biggest wire in the base shape
|
|
max_length = 0
|
|
for w in base.Shape.Wires:
|
|
if w.BoundBox.DiagonalLength > max_length:
|
|
max_length = w.BoundBox.DiagonalLength
|
|
f = w
|
|
if f:
|
|
import Part
|
|
f = Part.Face(f)
|
|
norm = f.normalAt(0,0)
|
|
if hasattr(obj,"Normal"):
|
|
if obj.Normal:
|
|
if not DraftVecUtils.isNull(obj.Normal):
|
|
norm = obj.Normal
|
|
v1 = DraftVecUtils.scaleTo(norm,width)
|
|
f.translate(v1)
|
|
v2 = v1.negative()
|
|
v2 = Vector(v1).multiply(-2)
|
|
f = f.extrude(v2)
|
|
if plac:
|
|
f.Placement = plac
|
|
else:
|
|
f.Placement = obj.Placement
|
|
return f
|
|
return None
|
|
|
|
def computeAreas(self,obj):
|
|
return
|
|
|
|
|
|
|
|
class _ViewProviderWindow(ArchComponent.ViewProviderComponent):
|
|
|
|
"A View Provider for the Window object"
|
|
|
|
def __init__(self,vobj):
|
|
|
|
ArchComponent.ViewProviderComponent.__init__(self,vobj)
|
|
|
|
def getIcon(self):
|
|
|
|
import Arch_rc
|
|
if hasattr(self,"Object"):
|
|
if hasattr(self.Object,"CloneOf"):
|
|
if self.Object.CloneOf:
|
|
return ":/icons/Arch_Window_Clone.svg"
|
|
return ":/icons/Arch_Window_Tree.svg"
|
|
|
|
def updateData(self,obj,prop):
|
|
|
|
if prop == "Shape":
|
|
if obj.Base:
|
|
if obj.Base.isDerivedFrom("Part::Compound"):
|
|
if obj.ViewObject.DiffuseColor != obj.Base.ViewObject.DiffuseColor:
|
|
if len(obj.Base.ViewObject.DiffuseColor) > 1:
|
|
obj.ViewObject.DiffuseColor = obj.Base.ViewObject.DiffuseColor
|
|
obj.ViewObject.update()
|
|
self.colorize(obj)
|
|
elif prop == "CloneOf":
|
|
if hasattr(obj,"CloneOf") and obj.CloneOf:
|
|
mat = None
|
|
if hasattr(obj,"Material"):
|
|
if obj.Material:
|
|
mat = obj.Material
|
|
if not mat:
|
|
if obj.ViewObject.DiffuseColor != obj.CloneOf.ViewObject.DiffuseColor:
|
|
if len(obj.CloneOf.ViewObject.DiffuseColor) > 1:
|
|
obj.ViewObject.DiffuseColor = obj.CloneOf.ViewObject.DiffuseColor
|
|
obj.ViewObject.update()
|
|
|
|
def onDelete(self,vobj,subelements):
|
|
|
|
for o in vobj.Object.Hosts:
|
|
o.touch()
|
|
return True
|
|
|
|
def onChanged(self,vobj,prop):
|
|
|
|
if prop == "ShapeAppearance":
|
|
self.colorize(vobj.Object)
|
|
ArchComponent.ViewProviderComponent.onChanged(self,vobj,prop)
|
|
|
|
def colorize(self,obj):
|
|
|
|
def _shapeAppearanceMaterialIsSame(sapp_mat1, sapp_mat2):
|
|
for prop in (
|
|
"AmbientColor",
|
|
"DiffuseColor",
|
|
"EmissiveColor",
|
|
"Shininess",
|
|
"SpecularColor",
|
|
"Transparency",
|
|
):
|
|
if getattr(sapp_mat1, prop) != getattr(sapp_mat2, prop):
|
|
return False
|
|
return True
|
|
|
|
def _shapeAppearanceIsSame(sapp1, sapp2):
|
|
if len(sapp1) != len(sapp2):
|
|
return False
|
|
for sapp_mat1, sapp_mat2 in zip(sapp1, sapp2):
|
|
if not _shapeAppearanceMaterialIsSame(sapp_mat1, sapp_mat2):
|
|
return False
|
|
return True
|
|
|
|
if not obj.Shape:
|
|
return
|
|
if not obj.Shape.Solids:
|
|
return
|
|
|
|
# setting different part colors
|
|
if hasattr(obj, "CloneOf") and obj.CloneOf:
|
|
obj, clone = obj.CloneOf, obj
|
|
base_sapp_mat = clone.ViewObject.ShapeAppearance[0]
|
|
arch_mat = getattr(clone, "Material", None)
|
|
else:
|
|
clone = None
|
|
base_sapp_mat = obj.ViewObject.ShapeAppearance[0]
|
|
arch_mat = getattr(obj, "Material", None)
|
|
|
|
solids = obj.Shape.copy().Solids
|
|
sapp = []
|
|
for i in range(len(solids)):
|
|
color = None
|
|
if obj.WindowParts and len(obj.WindowParts) > i*5:
|
|
# WindowParts-based window
|
|
name = obj.WindowParts[(i*5)]
|
|
mtype = obj.WindowParts[(i*5)+1]
|
|
color = self.getSolidMaterial(obj,arch_mat,name,mtype)
|
|
elif obj.Base and hasattr(obj.Base,"Shape"):
|
|
# Type-based window: obj.Base furnishes the window solids
|
|
sol1 = self.getSolidSignature(solids[i])
|
|
# here we look for all the ways to retrieve a name for each
|
|
# solid. Currently we look for similar solids in the
|
|
if hasattr(obj.Base,"Group"):
|
|
for child in obj.Base.Group:
|
|
if hasattr(child,"Shape") and child.Shape and child.Shape.Solids:
|
|
sol2 = self.getSolidSignature(child.Shape)
|
|
if sol1 == sol2:
|
|
color = self.getSolidMaterial(obj,arch_mat,child.Label)
|
|
break
|
|
if color is None:
|
|
typeidx = (i*5)+1
|
|
if typeidx < len(obj.WindowParts):
|
|
typ = obj.WindowParts[typeidx]
|
|
if typ == WindowPartTypes[2]: # "Glass panel"
|
|
color = ArchCommands.getDefaultColor("WindowGlass")
|
|
|
|
if color is None:
|
|
sapp_mat = base_sapp_mat
|
|
else:
|
|
sapp_mat = FreeCAD.Material() # ShapeAppearance material with default v0.21 properties.
|
|
sapp_mat.DiffuseColor = color[:3] + (0.0, )
|
|
sapp_mat.Transparency = color[3]
|
|
sapp.extend((sapp_mat, ) * len(solids[i].Faces))
|
|
|
|
if clone is not None:
|
|
obj = clone
|
|
if not _shapeAppearanceIsSame(obj.ViewObject.ShapeAppearance, sapp):
|
|
obj.ViewObject.ShapeAppearance = sapp
|
|
|
|
def getSolidSignature(self,solid):
|
|
|
|
"""Returns a tuple defining as uniquely as possible a solid"""
|
|
|
|
return (solid.ShapeType,round(solid.Volume,3),round(solid.Area,3),round(solid.Length,3))
|
|
|
|
def getSolidMaterial(self,obj,arch_mat,name,mtype=None):
|
|
|
|
color = None
|
|
if arch_mat is not None and hasattr(arch_mat,"Materials") and arch_mat.Names:
|
|
mat = None
|
|
if name in arch_mat.Names:
|
|
mat = arch_mat.Materials[arch_mat.Names.index(name)]
|
|
elif mtype is not None and (mtype in arch_mat.Names):
|
|
mat = arch_mat.Materials[arch_mat.Names.index(mtype)]
|
|
if mat:
|
|
if 'DiffuseColor' in mat.Material:
|
|
if "(" in mat.Material['DiffuseColor']:
|
|
color = tuple([float(f) for f in mat.Material['DiffuseColor'].strip("()").split(",")])
|
|
if color and ('Transparency' in mat.Material):
|
|
t = float(mat.Material['Transparency'])/100.0
|
|
color = color[:3] + (t, )
|
|
return color
|
|
|
|
def getHingeEdgeIndices(self):
|
|
|
|
"""returns a list of hinge edge indices (0-based)"""
|
|
|
|
# WindowParts example:
|
|
# ["OuterFrame", "Frame", "Wire0,Wire1", "100.0+V", "0.00+V",
|
|
# "InnerFrame", "Frame", "Wire2,Wire3,Edge8,Mode1", "100.0", "100.0+V",
|
|
# "InnerGlass", "Glass panel", "Wire3", "10.0", "150.0+V"]
|
|
|
|
idxs = []
|
|
parts = self.Object.WindowParts
|
|
for i in range(len(parts) // 5):
|
|
for s in parts[(i * 5) + 2].split(","):
|
|
if "Edge" in s:
|
|
idxs.append(int(s[4:]) - 1) # Edge indices in string are 1-based.
|
|
return idxs
|
|
|
|
def setEdit(self, vobj, mode):
|
|
if mode != 0:
|
|
return None
|
|
|
|
taskd = _ArchWindowTaskPanel()
|
|
taskd.obj = self.Object
|
|
self.sets = [vobj.DisplayMode,vobj.Transparency]
|
|
vobj.DisplayMode = "Shaded"
|
|
vobj.Transparency = 80
|
|
if self.Object.Base:
|
|
self.Object.Base.ViewObject.show()
|
|
taskd.update()
|
|
FreeCADGui.Control.showDialog(taskd)
|
|
return True
|
|
|
|
def unsetEdit(self, vobj, mode):
|
|
if mode != 0:
|
|
return None
|
|
|
|
vobj.DisplayMode = self.sets[0]
|
|
vobj.Transparency = self.sets[1]
|
|
vobj.DiffuseColor = vobj.DiffuseColor # reset face colors
|
|
if self.Object.Base:
|
|
self.Object.Base.ViewObject.hide()
|
|
FreeCADGui.Control.closeDialog()
|
|
return True
|
|
|
|
def setupContextMenu(self, vobj, menu):
|
|
hingeIdxs = self.getHingeEdgeIndices()
|
|
|
|
super().contextMenuAddEdit(menu)
|
|
|
|
if len(hingeIdxs) > 0:
|
|
actionInvertOpening = QtGui.QAction(QtGui.QIcon(":/icons/Arch_Window_Tree.svg"),
|
|
translate("Arch", "Invert opening direction"),
|
|
menu)
|
|
QtCore.QObject.connect(actionInvertOpening,
|
|
QtCore.SIGNAL("triggered()"),
|
|
self.invertOpening)
|
|
menu.addAction(actionInvertOpening)
|
|
|
|
if len(hingeIdxs) == 1:
|
|
actionInvertHinge = QtGui.QAction(QtGui.QIcon(":/icons/Arch_Window_Tree.svg"),
|
|
translate("Arch", "Invert hinge position"),
|
|
menu)
|
|
QtCore.QObject.connect(actionInvertHinge,
|
|
QtCore.SIGNAL("triggered()"),
|
|
self.invertHinge)
|
|
menu.addAction(actionInvertHinge)
|
|
|
|
super().contextMenuAddToggleSubcomponents(menu)
|
|
|
|
def invertOpening(self):
|
|
|
|
"""swaps the opening modes found in this window"""
|
|
|
|
pairs = [["Mode"+str(i),"Mode"+str(i+1)] for i in range(1,len(WindowOpeningModes),2)]
|
|
self.invertPairs(pairs)
|
|
|
|
def invertHinge(self):
|
|
|
|
"""swaps the hinge edge of a single hinge edge window"""
|
|
|
|
idxs = self.getHingeEdgeIndices()
|
|
if len(idxs) != 1:
|
|
return
|
|
|
|
idx = idxs[0]
|
|
end = 0
|
|
for wire in self.Object.Base.Shape.Wires:
|
|
sta = end
|
|
end += len(wire.Edges)
|
|
if sta <= idx < end:
|
|
new = idx + 2 # A rectangular wire is assumed.
|
|
if not (sta <= new < end):
|
|
new = idx - 2
|
|
break
|
|
|
|
pairs = [["Edge" + str(idx + 1), "Edge" + str(new + 1)]]
|
|
self.invertPairs(pairs)
|
|
# Also invert opening direction, so the door still opens towards
|
|
# the same side of the wall
|
|
self.invertOpening()
|
|
|
|
def invertPairs(self,pairs):
|
|
|
|
"""scans the WindowParts of this window and swaps the two elements of each pair, if found"""
|
|
|
|
if hasattr(self,"Object"):
|
|
windowparts = self.Object.WindowParts
|
|
nparts = []
|
|
for part in windowparts:
|
|
for pair in pairs:
|
|
if pair[0] in part:
|
|
part = part.replace(pair[0],pair[1])
|
|
break
|
|
elif pair[1] in part:
|
|
part = part.replace(pair[1],pair[0])
|
|
break
|
|
nparts.append(part)
|
|
if nparts != self.Object.WindowParts:
|
|
self.Object.WindowParts = nparts
|
|
FreeCAD.ActiveDocument.recompute()
|
|
else:
|
|
FreeCAD.Console.PrintWarning(translate("Arch","This window has no defined opening")+"\n")
|
|
|
|
|
|
|
|
class _ArchWindowTaskPanel:
|
|
|
|
'''The TaskPanel for Arch Windows'''
|
|
|
|
def __init__(self):
|
|
|
|
self.obj = None
|
|
self.baseform = QtGui.QWidget()
|
|
self.baseform.setObjectName("TaskPanel")
|
|
self.grid = QtGui.QGridLayout(self.baseform)
|
|
self.grid.setObjectName("grid")
|
|
self.title = QtGui.QLabel(self.baseform)
|
|
self.grid.addWidget(self.title, 0, 0, 1, 7)
|
|
self.basepanel = ArchComponent.ComponentTaskPanel()
|
|
self.form = [self.baseform,self.basepanel.baseform]
|
|
|
|
# base object
|
|
self.tree = QtGui.QTreeWidget(self.baseform)
|
|
self.grid.addWidget(self.tree, 1, 0, 1, 7)
|
|
self.tree.setColumnCount(1)
|
|
self.tree.setMaximumSize(QtCore.QSize(500,24))
|
|
self.tree.header().hide()
|
|
|
|
# hole
|
|
self.holeLabel = QtGui.QLabel(self.baseform)
|
|
self.grid.addWidget(self.holeLabel, 2, 0, 1, 1)
|
|
|
|
self.holeNumber = QtGui.QLineEdit(self.baseform)
|
|
self.grid.addWidget(self.holeNumber, 2, 2, 1, 3)
|
|
|
|
self.holeButton = QtGui.QPushButton(self.baseform)
|
|
self.grid.addWidget(self.holeButton, 2, 6, 1, 1)
|
|
self.holeButton.setEnabled(True)
|
|
|
|
# trees
|
|
self.wiretree = QtGui.QTreeWidget(self.baseform)
|
|
self.grid.addWidget(self.wiretree, 3, 0, 1, 3)
|
|
self.wiretree.setColumnCount(1)
|
|
self.wiretree.setSelectionMode(QtGui.QAbstractItemView.MultiSelection)
|
|
|
|
self.comptree = QtGui.QTreeWidget(self.baseform)
|
|
self.grid.addWidget(self.comptree, 3, 4, 1, 3)
|
|
self.comptree.setColumnCount(1)
|
|
|
|
# buttons
|
|
self.addButton = QtGui.QPushButton(self.baseform)
|
|
self.addButton.setObjectName("addButton")
|
|
self.addButton.setIcon(QtGui.QIcon(":/icons/Arch_Add.svg"))
|
|
self.grid.addWidget(self.addButton, 4, 0, 1, 1)
|
|
self.addButton.setMaximumSize(QtCore.QSize(70,40))
|
|
|
|
self.editButton = QtGui.QPushButton(self.baseform)
|
|
self.editButton.setObjectName("editButton")
|
|
self.editButton.setIcon(QtGui.QIcon(":/icons/Draft_Edit.svg"))
|
|
self.grid.addWidget(self.editButton, 4, 2, 1, 3)
|
|
self.editButton.setMaximumSize(QtCore.QSize(60,40))
|
|
self.editButton.setEnabled(False)
|
|
|
|
self.delButton = QtGui.QPushButton(self.baseform)
|
|
self.delButton.setObjectName("delButton")
|
|
self.delButton.setIcon(QtGui.QIcon(":/icons/Arch_Remove.svg"))
|
|
self.grid.addWidget(self.delButton, 4, 6, 1, 1)
|
|
self.delButton.setMaximumSize(QtCore.QSize(70,40))
|
|
self.delButton.setEnabled(False)
|
|
|
|
# invert buttons
|
|
self.invertOpeningButton = QtGui.QPushButton(self.baseform)
|
|
self.invertOpeningButton.setIcon(QtGui.QIcon(":/icons/Arch_Window_Tree.svg"))
|
|
self.invertOpeningButton.clicked.connect(self.invertOpening)
|
|
self.grid.addWidget(self.invertOpeningButton, 5, 0, 1, 7)
|
|
self.invertOpeningButton.setEnabled(False)
|
|
self.invertHingeButton = QtGui.QPushButton(self.baseform)
|
|
self.invertHingeButton.setIcon(QtGui.QIcon(":/icons/Arch_Window_Tree.svg"))
|
|
self.invertHingeButton.clicked.connect(self.invertHinge)
|
|
self.grid.addWidget(self.invertHingeButton, 6, 0, 1, 7)
|
|
self.invertHingeButton.setEnabled(False)
|
|
|
|
# add new
|
|
|
|
ui = FreeCADGui.UiLoader()
|
|
self.newtitle = QtGui.QLabel(self.baseform)
|
|
self.new1 = QtGui.QLabel(self.baseform)
|
|
self.new2 = QtGui.QLabel(self.baseform)
|
|
self.new3 = QtGui.QLabel(self.baseform)
|
|
self.new4 = QtGui.QLabel(self.baseform)
|
|
self.new5 = QtGui.QLabel(self.baseform)
|
|
self.new6 = QtGui.QLabel(self.baseform)
|
|
self.new7 = QtGui.QLabel(self.baseform)
|
|
self.field1 = QtGui.QLineEdit(self.baseform)
|
|
self.field2 = QtGui.QComboBox(self.baseform)
|
|
self.field3 = QtGui.QLineEdit(self.baseform)
|
|
self.field4 = ui.createWidget("Gui::InputField")
|
|
self.field5 = ui.createWidget("Gui::InputField")
|
|
self.field6 = QtGui.QPushButton(self.baseform)
|
|
self.field7 = QtGui.QComboBox(self.baseform)
|
|
self.addp4 = QtGui.QCheckBox(self.baseform)
|
|
self.addp5 = QtGui.QCheckBox(self.baseform)
|
|
self.createButton = QtGui.QPushButton(self.baseform)
|
|
self.createButton.setObjectName("createButton")
|
|
self.createButton.setIcon(QtGui.QIcon(":/icons/Arch_Add.svg"))
|
|
self.grid.addWidget(self.newtitle, 7, 0, 1, 7)
|
|
self.grid.addWidget(self.new1, 8, 0, 1, 1)
|
|
self.grid.addWidget(self.field1, 8, 2, 1, 5)
|
|
self.grid.addWidget(self.new2, 9, 0, 1, 1)
|
|
self.grid.addWidget(self.field2, 9, 2, 1, 5)
|
|
self.grid.addWidget(self.new3, 10, 0, 1, 1)
|
|
self.grid.addWidget(self.field3, 10, 2, 1, 5)
|
|
self.grid.addWidget(self.new4, 11, 0, 1, 1)
|
|
self.grid.addWidget(self.field4, 11, 2, 1, 4)
|
|
self.grid.addWidget(self.addp4, 11, 6, 1, 1)
|
|
self.grid.addWidget(self.new5, 12, 0, 1, 1)
|
|
self.grid.addWidget(self.field5, 12, 2, 1, 4)
|
|
self.grid.addWidget(self.addp5, 12, 6, 1, 1)
|
|
self.grid.addWidget(self.new6, 13, 0, 1, 1)
|
|
self.grid.addWidget(self.field6, 13, 2, 1, 5)
|
|
self.grid.addWidget(self.new7, 14, 0, 1, 1)
|
|
self.grid.addWidget(self.field7, 14, 2, 1, 5)
|
|
self.grid.addWidget(self.createButton, 15, 0, 1, 7)
|
|
self.newtitle.setVisible(False)
|
|
self.new1.setVisible(False)
|
|
self.new2.setVisible(False)
|
|
self.new3.setVisible(False)
|
|
self.new4.setVisible(False)
|
|
self.new5.setVisible(False)
|
|
self.new6.setVisible(False)
|
|
self.new7.setVisible(False)
|
|
self.field1.setVisible(False)
|
|
self.field2.setVisible(False)
|
|
for t in WindowPartTypes:
|
|
self.field2.addItem("")
|
|
self.field3.setVisible(False)
|
|
self.field3.setReadOnly(True)
|
|
self.field4.setVisible(False)
|
|
self.field5.setVisible(False)
|
|
self.field6.setVisible(False)
|
|
self.field7.setVisible(False)
|
|
self.addp4.setVisible(False)
|
|
self.addp5.setVisible(False)
|
|
for t in WindowOpeningModes:
|
|
self.field7.addItem("")
|
|
self.createButton.setVisible(False)
|
|
|
|
QtCore.QObject.connect(self.holeButton, QtCore.SIGNAL("clicked()"), self.selectHole)
|
|
QtCore.QObject.connect(self.holeNumber, QtCore.SIGNAL("textEdited(QString)"), self.setHoleNumber)
|
|
QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.addElement)
|
|
QtCore.QObject.connect(self.delButton, QtCore.SIGNAL("clicked()"), self.removeElement)
|
|
QtCore.QObject.connect(self.editButton, QtCore.SIGNAL("clicked()"), self.editElement)
|
|
QtCore.QObject.connect(self.createButton, QtCore.SIGNAL("clicked()"), self.create)
|
|
QtCore.QObject.connect(self.comptree, QtCore.SIGNAL("itemClicked(QTreeWidgetItem*,int)"), self.check)
|
|
QtCore.QObject.connect(self.wiretree, QtCore.SIGNAL("itemClicked(QTreeWidgetItem*,int)"), self.select)
|
|
QtCore.QObject.connect(self.field6, QtCore.SIGNAL("clicked()"), self.addEdge)
|
|
self.update()
|
|
|
|
FreeCADGui.Selection.clearSelection()
|
|
|
|
def isAllowedAlterSelection(self):
|
|
|
|
return True
|
|
|
|
def isAllowedAlterView(self):
|
|
|
|
return True
|
|
|
|
def getStandardButtons(self):
|
|
|
|
return QtGui.QDialogButtonBox.Close
|
|
|
|
def check(self,wid,col):
|
|
|
|
self.editButton.setEnabled(True)
|
|
self.delButton.setEnabled(True)
|
|
|
|
def select(self,wid,col):
|
|
|
|
FreeCADGui.Selection.clearSelection()
|
|
ws = ''
|
|
for it in self.wiretree.selectedItems():
|
|
if ws: ws += ","
|
|
ws += str(it.text(0))
|
|
w = int(str(it.text(0)[4:]))
|
|
if self.obj:
|
|
if self.obj.Base:
|
|
edges = self.obj.Base.Shape.Wires[w].Edges
|
|
for e in edges:
|
|
for i in range(len(self.obj.Base.Shape.Edges)):
|
|
if e.hashCode() == self.obj.Base.Shape.Edges[i].hashCode():
|
|
FreeCADGui.Selection.addSelection(self.obj.Base,"Edge"+str(i+1))
|
|
self.field3.setText(ws)
|
|
|
|
def selectHole(self):
|
|
|
|
"takes a selected edge to determine current Hole Wire"
|
|
|
|
s = FreeCADGui.Selection.getSelectionEx()
|
|
if s and self.obj:
|
|
if s[0].SubElementNames:
|
|
if "Edge" in s[0].SubElementNames[0]:
|
|
for i,w in enumerate(self.obj.Base.Shape.Wires):
|
|
for e in w.Edges:
|
|
if e.hashCode() == s[0].SubObjects[0].hashCode():
|
|
self.holeNumber.setText(str(i+1))
|
|
self.setHoleNumber(str(i+1))
|
|
break
|
|
|
|
def setHoleNumber(self,val):
|
|
|
|
"sets the HoleWire obj property"
|
|
|
|
if val.isdigit():
|
|
val = int(val)
|
|
if self.obj:
|
|
if not hasattr(self.obj,"HoleWire"):
|
|
self.obj.addProperty("App::PropertyInteger","HoleWire","Arch",QT_TRANSLATE_NOOP("App::Property","The number of the wire that defines the hole. A value of 0 means automatic"))
|
|
self.obj.HoleWire = val
|
|
|
|
def getIcon(self,obj):
|
|
|
|
if hasattr(obj.ViewObject,"Proxy"):
|
|
if hasattr(obj.ViewObject.Proxy,"getIcon"):
|
|
return QtGui.QIcon(obj.ViewObject.Proxy.getIcon())
|
|
elif obj.isDerivedFrom("Sketcher::SketchObject"):
|
|
return QtGui.QIcon(":/icons/Sketcher_Sketch.svg")
|
|
elif hasattr(obj.ViewObject, "Icon"):
|
|
return QtGui.QIcon(obj.ViewObject.Icon)
|
|
return QtGui.QIcon(":/icons/Part_3D_object.svg")
|
|
|
|
def update(self):
|
|
|
|
'fills the tree widgets'
|
|
|
|
self.tree.clear()
|
|
self.wiretree.clear()
|
|
self.comptree.clear()
|
|
if self.obj:
|
|
if self.obj.Base:
|
|
item = QtGui.QTreeWidgetItem(self.tree)
|
|
item.setText(0,self.obj.Base.Name)
|
|
item.setIcon(0,self.getIcon(self.obj.Base))
|
|
if hasattr(self.obj.Base,'Shape'):
|
|
i = 0
|
|
for w in self.obj.Base.Shape.Wires:
|
|
if w.isClosed():
|
|
item = QtGui.QTreeWidgetItem(self.wiretree)
|
|
item.setText(0,"Wire" + str(i))
|
|
item.setIcon(0,QtGui.QIcon(":/icons/Draft_Draft.svg"))
|
|
i += 1
|
|
if self.obj.WindowParts:
|
|
for p in range(0,len(self.obj.WindowParts),5):
|
|
item = QtGui.QTreeWidgetItem(self.comptree)
|
|
item.setText(0,self.obj.WindowParts[p])
|
|
item.setIcon(0, QtGui.QIcon(":/icons/Part_3D_object.svg"))
|
|
if hasattr(self.obj,"HoleWire"):
|
|
self.holeNumber.setText(str(self.obj.HoleWire))
|
|
else:
|
|
self.holeNumber.setText("0")
|
|
|
|
self.retranslateUi(self.baseform)
|
|
self.basepanel.obj = self.obj
|
|
self.basepanel.update()
|
|
for wp in self.obj.WindowParts:
|
|
if ("Edge" in wp) and ("Mode" in wp):
|
|
self.invertOpeningButton.setEnabled(True)
|
|
self.invertHingeButton.setEnabled(True)
|
|
break
|
|
|
|
def addElement(self):
|
|
|
|
'opens the component creation dialog'
|
|
|
|
self.field1.setText('')
|
|
self.field3.setText('')
|
|
self.field4.setText('')
|
|
self.field5.setText('')
|
|
self.field6.setText(QtGui.QApplication.translate("Arch", "Get selected edge", None))
|
|
self.field7.setCurrentIndex(0)
|
|
self.addp4.setChecked(False)
|
|
self.addp5.setChecked(False)
|
|
self.newtitle.setVisible(True)
|
|
self.new1.setVisible(True)
|
|
self.new2.setVisible(True)
|
|
self.new3.setVisible(True)
|
|
self.new4.setVisible(True)
|
|
self.new5.setVisible(True)
|
|
self.new6.setVisible(True)
|
|
self.new7.setVisible(True)
|
|
self.field1.setVisible(True)
|
|
self.field2.setVisible(True)
|
|
self.field3.setVisible(True)
|
|
self.field4.setVisible(True)
|
|
self.field5.setVisible(True)
|
|
self.field6.setVisible(True)
|
|
self.field7.setVisible(True)
|
|
self.addp4.setVisible(True)
|
|
self.addp5.setVisible(True)
|
|
self.createButton.setVisible(True)
|
|
self.addButton.setEnabled(False)
|
|
self.editButton.setEnabled(False)
|
|
self.delButton.setEnabled(False)
|
|
|
|
def removeElement(self):
|
|
|
|
for it in self.comptree.selectedItems():
|
|
comp = str(it.text(0))
|
|
if self.obj:
|
|
p = self.obj.WindowParts
|
|
if comp in self.obj.WindowParts:
|
|
ind = self.obj.WindowParts.index(comp)
|
|
for i in range(5):
|
|
p.pop(ind)
|
|
self.obj.WindowParts = p
|
|
self.update()
|
|
self.editButton.setEnabled(False)
|
|
self.delButton.setEnabled(False)
|
|
|
|
def editElement(self):
|
|
|
|
for it in self.comptree.selectedItems():
|
|
self.addElement()
|
|
comp = str(it.text(0))
|
|
if self.obj:
|
|
if comp in self.obj.WindowParts:
|
|
ind = self.obj.WindowParts.index(comp)
|
|
self.field6.setText(QtGui.QApplication.translate("Arch", "Get selected edge", None))
|
|
self.field7.setCurrentIndex(0)
|
|
for i in range(5):
|
|
f = getattr(self,"field"+str(i+1))
|
|
t = self.obj.WindowParts[ind+i]
|
|
if i == 1:
|
|
# special behaviour for types
|
|
if t in WindowPartTypes:
|
|
f.setCurrentIndex(WindowPartTypes.index(t))
|
|
else:
|
|
f.setCurrentIndex(0)
|
|
elif i == 2:
|
|
wires = []
|
|
for l in t.split(","):
|
|
if "Wire" in l:
|
|
wires.append(l)
|
|
elif "Edge" in l:
|
|
self.field6.setText(l)
|
|
elif "Mode" in l:
|
|
if int(l[4:]) < len(WindowOpeningModes):
|
|
self.field7.setCurrentIndex(int(l[4:]))
|
|
else:
|
|
# Ignore modes not listed in WindowOpeningModes
|
|
self.field7.setCurrentIndex(0)
|
|
if wires:
|
|
f.setText(",".join(wires))
|
|
|
|
elif i in [3,4]:
|
|
if "+V" in t:
|
|
t = t[:-2]
|
|
if i == 3:
|
|
self.addp4.setChecked(True)
|
|
else:
|
|
self.addp5.setChecked(True)
|
|
else:
|
|
if i == 3:
|
|
self.addp4.setChecked(False)
|
|
else:
|
|
self.addp5.setChecked(False)
|
|
f.setProperty("text",FreeCAD.Units.Quantity(float(t),FreeCAD.Units.Length).UserString)
|
|
else:
|
|
f.setText(t)
|
|
|
|
def create(self):
|
|
|
|
'adds a new component'
|
|
|
|
# testing if fields are ok
|
|
ok = True
|
|
ar = []
|
|
for i in range(5):
|
|
if i == 1: # type (1)
|
|
n = getattr(self,"field"+str(i+1)).currentIndex()
|
|
if n in range(len(WindowPartTypes)):
|
|
t = WindowPartTypes[n]
|
|
else:
|
|
# if type was not specified or is invalid, we set a default
|
|
t = WindowPartTypes[0]
|
|
else: # name (0)
|
|
t = str(getattr(self,"field"+str(i+1)).property("text"))
|
|
if t in WindowPartTypes:
|
|
t = t + "_" # avoiding part names similar to types
|
|
if t == "":
|
|
if not(i in [1,5]):
|
|
ok = False
|
|
else:
|
|
if i > 2: # thickness (3), offset (4)
|
|
try:
|
|
q = FreeCAD.Units.Quantity(t)
|
|
t = str(q.Value)
|
|
if i == 3:
|
|
if self.addp4.isChecked():
|
|
t += "+V"
|
|
if i == 4:
|
|
if self.addp5.isChecked():
|
|
t += "+V"
|
|
except (ValueError,TypeError):
|
|
ok = False
|
|
elif i == 2:
|
|
# check additional opening parameters
|
|
hinge = self.field6.property("text")
|
|
n = self.field7.currentIndex()
|
|
if (hinge.startswith("Edge")) and (n > 0):
|
|
# remove accelerator added by Qt
|
|
hinge = hinge.replace("&","")
|
|
t += "," + hinge + ",Mode" + str(n)
|
|
ar.append(t)
|
|
|
|
if ok:
|
|
if self.obj:
|
|
parts = self.obj.WindowParts
|
|
if ar[0] in parts:
|
|
b = parts.index(ar[0])
|
|
for i in range(5):
|
|
parts[b+i] = ar[i]
|
|
else:
|
|
parts.extend(ar)
|
|
self.obj.WindowParts = parts
|
|
self.update()
|
|
else:
|
|
FreeCAD.Console.PrintWarning(translate("Arch", "Unable to create component")+"\n")
|
|
|
|
self.newtitle.setVisible(False)
|
|
self.new1.setVisible(False)
|
|
self.new2.setVisible(False)
|
|
self.new3.setVisible(False)
|
|
self.new4.setVisible(False)
|
|
self.new5.setVisible(False)
|
|
self.new6.setVisible(False)
|
|
self.new7.setVisible(False)
|
|
self.field1.setVisible(False)
|
|
self.field2.setVisible(False)
|
|
self.field3.setVisible(False)
|
|
self.field4.setVisible(False)
|
|
self.field5.setVisible(False)
|
|
self.field6.setVisible(False)
|
|
self.field7.setVisible(False)
|
|
self.addp4.setVisible(False)
|
|
self.addp5.setVisible(False)
|
|
self.createButton.setVisible(False)
|
|
self.addButton.setEnabled(True)
|
|
|
|
def addEdge(self):
|
|
|
|
for sel in FreeCADGui.Selection.getSelectionEx():
|
|
for sub in sel.SubElementNames:
|
|
if "Edge" in sub:
|
|
self.field6.setText(sub)
|
|
return
|
|
|
|
def reject(self):
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
FreeCADGui.ActiveDocument.resetEdit()
|
|
return True
|
|
|
|
def retranslateUi(self, TaskPanel):
|
|
|
|
TaskPanel.setWindowTitle(QtGui.QApplication.translate("Arch", "Window elements", None))
|
|
self.holeLabel.setText(QtGui.QApplication.translate("Arch", "Hole wire", None))
|
|
self.holeNumber.setToolTip(QtGui.QApplication.translate("Arch", "The number of the wire that defines a hole in the host object. A value of zero will automatically adopt the largest wire", None))
|
|
self.holeButton.setText(QtGui.QApplication.translate("Arch", "Pick selected", None))
|
|
self.delButton.setText(QtGui.QApplication.translate("Arch", "Remove", None))
|
|
self.addButton.setText(QtGui.QApplication.translate("Arch", "Add", None))
|
|
self.editButton.setText(QtGui.QApplication.translate("Arch", "Edit", None))
|
|
self.createButton.setText(QtGui.QApplication.translate("Arch", "Create/update component", None))
|
|
self.title.setText(QtGui.QApplication.translate("Arch", "Base 2D object", None))
|
|
self.wiretree.setHeaderLabels([QtGui.QApplication.translate("Arch", "Wires", None)])
|
|
self.comptree.setHeaderLabels([QtGui.QApplication.translate("Arch", "Components", None)])
|
|
self.newtitle.setText(QtGui.QApplication.translate("Arch", "Create new component", None))
|
|
self.new1.setText(QtGui.QApplication.translate("Arch", "Name", None))
|
|
self.new2.setText(QtGui.QApplication.translate("Arch", "Type", None))
|
|
self.new3.setText(QtGui.QApplication.translate("Arch", "Wires", None))
|
|
self.new4.setText(QtGui.QApplication.translate("Arch", "Thickness", None))
|
|
self.new5.setText(QtGui.QApplication.translate("Arch", "Offset", None))
|
|
self.new6.setText(QtGui.QApplication.translate("Arch", "Hinge", None))
|
|
self.new7.setText(QtGui.QApplication.translate("Arch", "Opening mode", None))
|
|
self.addp4.setText(QtGui.QApplication.translate("Arch", "+ default", None))
|
|
self.addp4.setToolTip(QtGui.QApplication.translate("Arch", "If this is checked, the default Frame value of this window will be added to the value entered here", None))
|
|
self.addp5.setText(QtGui.QApplication.translate("Arch", "+ default", None))
|
|
self.addp5.setToolTip(QtGui.QApplication.translate("Arch", "If this is checked, the default Offset value of this window will be added to the value entered here", None))
|
|
self.field6.setText(QtGui.QApplication.translate("Arch", "Get selected edge", None))
|
|
self.field6.setToolTip(QtGui.QApplication.translate("Arch", "Press to retrieve the selected edge", None))
|
|
self.invertOpeningButton.setText(QtGui.QApplication.translate("Arch", "Invert opening direction", None))
|
|
self.invertHingeButton.setText(QtGui.QApplication.translate("Arch", "Invert hinge position", None))
|
|
for i in range(len(WindowPartTypes)):
|
|
self.field2.setItemText(i, QtGui.QApplication.translate("Arch", WindowPartTypes[i], None))
|
|
for i in range(len(WindowOpeningModes)):
|
|
self.field7.setItemText(i, QtGui.QApplication.translate("Arch", WindowOpeningModes[i], None))
|
|
|
|
def invertOpening(self):
|
|
|
|
if self.obj:
|
|
self.obj.ViewObject.Proxy.invertOpening()
|
|
|
|
def invertHinge(self):
|
|
|
|
if self.obj:
|
|
self.obj.ViewObject.Proxy.invertHinge()
|
|
|
|
|
|
|