2
#***************************************************************************
3
#* Copyright (c) 2011 Yorik van Havre <yorik@uncreated.net> *
5
#* This program is free software; you can redistribute it and/or modify *
6
#* it under the terms of the GNU Lesser General Public License (LGPL) *
7
#* as published by the Free Software Foundation; either version 2 of *
8
#* the License, or (at your option) any later version. *
9
#* for detail see the LICENCE text file. *
11
#* This program is distributed in the hope that it will be useful, *
12
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
#* GNU Library General Public License for more details. *
16
#* You should have received a copy of the GNU Library General Public *
17
#* License along with this program; if not, write to the Free Software *
18
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
21
#***************************************************************************
27
from FreeCAD import Vector
28
from draftutils import params
32
from PySide import QtGui,QtCore
33
from draftutils.translate import translate
36
def translate(ctxt,txt):
40
__title__ = "FreeCAD Arch Commands"
41
__author__ = "Yorik van Havre"
42
__url__ = "https://www.freecad.org"
44
## @package ArchCommands
46
# \brief Utility functions for the Arch Workbench
48
# This module provides general functions used by Arch tools
51
# module functions ###############################################
53
def getStringList(objects):
54
'''getStringList(objects): returns a string defining a list
60
result += "FreeCAD.ActiveDocument." + o.Name
64
def getDefaultColor(objectType):
65
'''getDefaultColor(string): returns a color value for the given object
66
type (Wall, Structure, Window, WindowGlass)'''
68
if objectType == "Wall":
69
c = params.get_param_arch("WallColor")
70
elif objectType == "Structure":
71
c = params.get_param_arch("StructureColor")
72
elif objectType == "WindowGlass":
73
c = params.get_param_arch("WindowGlassColor")
74
transparency = params.get_param_arch("WindowTransparency") / 100.0
75
elif objectType == "Rebar":
76
c = params.get_param_arch("RebarColor")
77
elif objectType == "Panel":
78
c = params.get_param_arch("PanelColor")
79
elif objectType == "Space":
80
c = params.get_param_arch("defaultSpaceColor")
81
elif objectType == "Helpers":
82
c = params.get_param_arch("ColorHelpers")
83
elif objectType == "Construction":
84
c = params.get_param("constructioncolor")
87
c = params.get_param_view("DefaultShapeColor")
88
r, g, b, _ = Draft.get_rgba_tuple(c)
89
return (r, g, b, transparency)
91
def addComponents(objectsList,host):
92
'''addComponents(objectsList,hostObject): adds the given object or the objects
93
from the given list as components to the given host Object. Use this for
94
example to add windows to a wall, or to add walls to a cell or floor.'''
95
if not isinstance(objectsList,list):
96
objectsList = [objectsList]
97
hostType = Draft.getType(host)
98
if hostType in ["Floor","Building","Site","Project","BuildingPart"]:
101
elif hostType in ["Wall","Structure","Precast","Window","Roof","Stairs","StructuralSystem","Panel","Component","Pipe"]:
102
import DraftGeomUtils
104
if hasattr(host,"Axes"):
106
for o in objectsList:
107
if hasattr(o,'Shape'):
108
if Draft.getType(o) == "Window":
109
if hasattr(o,"Hosts"):
110
if not host in o.Hosts:
114
elif DraftGeomUtils.isValidPath(o.Shape) and (hostType in ["Structure","Precast"]):
115
if o.AttachmentSupport == host:
116
o.AttachmentSupport = None
118
elif Draft.getType(o) == "Axis":
122
if hasattr(o,"Shape"):
125
if hasattr(host,"Axes"):
127
elif hostType in ["SectionPlane"]:
129
for o in objectsList:
133
elif host.isDerivedFrom("App::DocumentObjectGroup"):
134
for o in objectsList:
137
def removeComponents(objectsList,host=None):
138
'''removeComponents(objectsList,[hostObject]): removes the given component or
139
the components from the given list from their parents. If a host object is
140
specified, this function will try adding the components as holes to the host
142
if not isinstance(objectsList,list):
143
objectsList = [objectsList]
145
if Draft.getType(host) in ["Wall","Structure","Precast","Window","Roof","Stairs","StructuralSystem","Panel","Component","Pipe"]:
146
if hasattr(host,"Tool"):
147
if objectsList[0] == host.Tool:
149
if hasattr(host,"Axes"):
151
for o in objectsList[:]:
154
objectsList.remove(o)
155
s = host.Subtractions
156
for o in objectsList:
157
if Draft.getType(o) == "Window":
158
if hasattr(o,"Hosts"):
159
if not host in o.Hosts:
166
if not Draft.getType(o) in ["Window","Roof"]:
168
host.Subtractions = s
169
elif Draft.getType(host) in ["SectionPlane"]:
171
for o in objectsList:
176
for o in objectsList:
179
tp = Draft.getType(h)
180
if tp in ["Floor","Building","Site","BuildingPart"]:
186
elif tp in ["Wall","Structure","Precast"]:
200
elif tp in ["SectionPlane"]:
206
def makeComponent(baseobj=None,name=None,delete=False):
207
'''makeComponent([baseobj],[name],[delete]): creates an undefined, non-parametric BIM
208
component from the given base object'''
209
if not FreeCAD.ActiveDocument:
210
FreeCAD.Console.PrintError("No active document. Aborting\n")
212
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Component")
213
obj.Label = name if name else translate("Arch","Component")
214
ArchComponent.Component(obj)
216
ArchComponent.ViewProviderComponent(obj.ViewObject)
219
if hasattr(baseobj,'Shape'):
220
obj.Shape = baseobj.Shape
221
obj.Placement = baseobj.Placement
223
FreeCAD.ActiveDocument.removeObject(baseobj.Name)
227
baseobj.ViewObject.hide()
228
elif isinstance(baseobj,Part.Shape):
233
def cloneComponent(obj):
234
'''cloneComponent(obj): Creates a clone of an object as an undefined component'''
237
c.Placement = obj.Placement
239
if hasattr(obj,"Material"):
241
c.Material = obj.Material
242
if hasattr(obj,"IfcAttributes"):
243
if obj.IfcAttributes:
244
c.IfcAttributes = obj.IfcAttributes
248
def setAsSubcomponent(obj):
249
'''Sets the given object properly to become a subcomponent (addition, subtraction)
250
of an Arch component'''
252
if params.get_param_arch("applyConstructionStyle"):
254
color = getDefaultColor("Construction")
255
if hasattr(obj.ViewObject,"LineColor"):
256
obj.ViewObject.LineColor = color
257
if hasattr(obj.ViewObject, "PointColor"):
258
obj.ViewObject.PointColor = color
259
if hasattr(obj.ViewObject,"ShapeColor"):
260
obj.ViewObject.ShapeColor = color
261
if hasattr(obj.ViewObject,"Transparency"):
262
obj.ViewObject.Transparency = int(color[3]*100)
263
obj.ViewObject.hide()
265
def copyProperties(obj1,obj2):
266
'''copyProperties(obj1,obj2): Copies properties values from obj1 to obj2,
267
when that property exists in both objects'''
268
for prop in obj1.PropertiesList:
269
if prop in obj2.PropertiesList:
270
if not prop in ["Proxy","Shape"]:
271
setattr(obj2,prop,getattr(obj1,prop))
272
if obj1.ViewObject and obj2.ViewObject:
273
for prop in obj1.ViewObject.PropertiesList:
274
if prop in obj2.ViewObject.PropertiesList:
275
if not prop in ["Proxy","Shape"]:
276
setattr(obj2.ViewObject,prop,getattr(obj1.ViewObject,prop))
278
def splitMesh(obj,mark=True):
279
'''splitMesh(object,[mark]): splits the given mesh object into separated components.
280
If mark is False, nothing else is done. If True (default), non-manifold components
281
will be painted in red.'''
282
if not obj.isDerivedFrom("Mesh::Feature"): return []
284
comps = basemesh.getSeparateComponents()
288
FreeCAD.ActiveDocument.removeObject(basename)
290
newobj = FreeCAD.ActiveDocument.addObject("Mesh::Feature",basename)
292
if mark and (not(c.isSolid()) or c.hasNonManifolds()):
293
newobj.ViewObject.ShapeColor = (1.0,0.0,0.0,1.0)
298
def makeFace(wires,method=2,cleanup=False):
299
'''makeFace(wires): makes a face from a list of wires, finding which ones are holes'''
300
#print("makeFace: start:", wires)
303
if not isinstance(wires,list):
304
if len(wires.Vertexes) < 3:
306
return Part.Face(wires)
307
elif len(wires) == 1:
308
#import Draft;Draft.printShape(wires[0])
309
if len(wires[0].Vertexes) < 3:
311
return Part.Face(wires[0])
315
#print("makeFace: inner wires found")
318
# cleaning up rubbish in wires
320
for i in range(len(wires)):
321
wires[i] = DraftGeomUtils.removeInterVertices(wires[i])
322
#print("makeFace: garbage removed")
324
# we assume that the exterior boundary is that one with
325
# the biggest bounding box
326
if w.BoundBox.DiagonalLength > max_length:
327
max_length = w.BoundBox.DiagonalLength
329
#print("makeFace: exterior wire", ext)
333
# method 1: reverse inner wires
334
# all interior wires mark a hole and must reverse
335
# their orientation, otherwise Part.Face fails
337
#print("makeFace: reversing", w)
339
# make sure that the exterior wires comes as first in the list
341
#print("makeFace: done sorting", wires)
343
return Part.Face(wires)
345
# method 2: use the cut method
347
#print("makeFace: external face:", mf)
350
#print("makeFace: internal face:", f)
352
#print("makeFace: final face:", mf.Faces)
356
'''closeHole(shape): closes a hole in an open shape'''
357
import DraftGeomUtils
359
# creating an edges lookup table
361
for face in shape.Faces:
362
for edge in face.Edges:
365
lut[hc] = lut[hc] + 1
368
# filter out the edges shared by more than one face
370
for e in shape.Edges:
371
if lut[e.hashCode()] == 1:
373
bound = Part.__sortEdges__(bound)
375
nface = Part.Face(Part.Wire(bound))
376
shell = Part.makeShell(shape.Faces+[nface])
377
solid = Part.Solid(shell)
378
except Part.OCCError:
383
def getCutVolume(cutplane,shapes,clip=False,depth=None):
384
"""getCutVolume(cutplane,shapes,[clip,depth]): returns a cut face and a cut volume
385
from the given shapes and the given cutting plane. If clip is True, the cutvolume will
386
also cut off everything outside the cutplane projection. If depth is non-zero, geometry
387
further than this distance will be clipped off"""
389
return None,None,None
390
if not cutplane.Faces:
391
return None,None,None
393
if not isinstance(shapes,list):
396
bb = shapes[0].BoundBox
397
for sh in shapes[1:]:
400
# building cutplane space
403
if hasattr(cutplane,"Shape"):
404
p = cutplane.Shape.copy().Faces[0]
406
p = cutplane.copy().Faces[0]
407
except Part.OCCError:
408
FreeCAD.Console.PrintMessage(translate("Arch","Invalid cut plane")+"\n")
409
return None,None,None
412
prm_range = p.ParameterRange # (uMin, uMax, vMin, vMax)
413
u = p.valueAt(prm_range[0], 0).sub(p.valueAt(prm_range[1], 0)).normalize()
415
if not bb.isCutPlane(ce,ax):
416
#FreeCAD.Console.PrintMessage(translate("Arch","No objects are cut by the plane)+"\n")
417
return None,None,None
419
corners = [FreeCAD.Vector(bb.XMin,bb.YMin,bb.ZMin),
420
FreeCAD.Vector(bb.XMin,bb.YMax,bb.ZMin),
421
FreeCAD.Vector(bb.XMax,bb.YMin,bb.ZMin),
422
FreeCAD.Vector(bb.XMax,bb.YMax,bb.ZMin),
423
FreeCAD.Vector(bb.XMin,bb.YMin,bb.ZMax),
424
FreeCAD.Vector(bb.XMin,bb.YMax,bb.ZMax),
425
FreeCAD.Vector(bb.XMax,bb.YMin,bb.ZMax),
426
FreeCAD.Vector(bb.XMax,bb.YMax,bb.ZMax)]
429
um1 = DraftVecUtils.project(dv,u).Length
431
vm1 = DraftVecUtils.project(dv,v).Length
433
wm1 = DraftVecUtils.project(dv,ax).Length
435
vu = DraftVecUtils.scaleTo(u,um)
437
vv = DraftVecUtils.scaleTo(v,vm)
439
p1 = ce.add(vu.add(vvi))
440
p2 = ce.add(vu.add(vv))
441
p3 = ce.add(vui.add(vv))
442
p4 = ce.add(vui.add(vvi))
443
cutface = Part.makePolygon([p1,p2,p3,p4,p1])
444
cutface = Part.Face(cutface)
445
cutnormal = DraftVecUtils.scaleTo(ax,wm)
446
cutvolume = cutface.extrude(cutnormal)
447
cutnormal = cutnormal.negative()
448
invcutvolume = cutface.extrude(cutnormal)
450
extrudedplane = p.extrude(cutnormal)
451
bordervolume = invcutvolume.cut(extrudedplane)
452
cutvolume = cutvolume.fuse(bordervolume)
453
cutvolume = cutvolume.removeSplitter()
454
invcutvolume = extrudedplane
457
depthnormal = DraftVecUtils.scaleTo(cutnormal,depth)
458
depthvolume = cutface.extrude(depthnormal)
459
depthclipvolume = invcutvolume.cut(depthvolume)
460
cutvolume = cutvolume.fuse(depthclipvolume)
461
cutvolume = cutvolume.removeSplitter()
462
return cutface,cutvolume,invcutvolume
464
def getShapeFromMesh(mesh,fast=True,tolerance=0.001,flat=False,cut=True):
467
import DraftGeomUtils
468
if mesh.isSolid() and (mesh.countComponents() == 1) and fast:
469
# use the best method
471
for f in mesh.Facets:
472
p=f.Points+[f.Points[0]]
475
pts.append(FreeCAD.Vector(pp[0],pp[1],pp[2]))
477
f = Part.Face(Part.makePolygon(pts))
479
print("getShapeFromMesh: error building face from polygon")
483
shell = Part.makeShell(faces)
485
solid = Part.Solid(shell)
486
except Part.OCCError:
487
print("getShapeFromMesh: error creating solid")
490
solid = solid.removeSplitter()
491
except Part.OCCError:
492
print("getShapeFromMesh: error removing splitter")
496
#if not mesh.isSolid():
497
# print "getShapeFromMesh: non-solid mesh, using slow method"
499
segments = mesh.getPlanarSegments(tolerance)
500
#print(len(segments))
503
wires = MeshPart.wireFromSegment(mesh, i)
508
nwires.append(DraftGeomUtils.flattenWire(w))
511
faces.append(makeFace(wires,method=int(cut)+1))
515
se = Part.makeShell(faces)
516
se = se.removeSplitter()
519
except Part.OCCError:
520
print("getShapeFromMesh: error removing splitter")
522
cp = Part.makeCompound(faces)
523
except Part.OCCError:
524
print("getShapeFromMesh: error creating compound")
530
solid = Part.Solid(se)
531
except Part.OCCError:
532
print("getShapeFromMesh: error creating solid")
537
def projectToVector(shape,vector):
538
'''projectToVector(shape,vector): projects the given shape on the given
543
for v in shape.Vertexes:
544
p = DraftVecUtils.project(v.Point,vector)
547
if p.getAngle(vector) > 1:
553
return DraftVecUtils.scaleTo(vector,maxl-minl)
555
def meshToShape(obj,mark=True,fast=True,tol=0.001,flat=False,cut=True):
556
'''meshToShape(object,[mark,fast,tol,flat,cut]): turns a mesh into a shape, joining coplanar facets. If
557
mark is True (default), non-solid objects will be marked in red. Fast uses a faster algorithm by
558
building a shell from the facets then removing splitter, tol is the tolerance used when converting
559
mesh segments to wires, flat will force the wires to be perfectly planar, to be sure they can be
560
turned into faces, but this might leave gaps in the final shell. If cut is true, holes in faces are
561
made by subtraction (default)'''
564
if "Mesh" in obj.PropertiesList:
566
#plac = obj.Placement
567
solid = getShapeFromMesh(mesh,fast,tol,flat,cut)
569
if solid.isClosed() and solid.isValid():
570
FreeCAD.ActiveDocument.removeObject(name)
571
newobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name)
573
#newobj.Placement = plac #the placement is already computed in the mesh
574
if (not solid.isClosed()) or (not solid.isValid()):
576
newobj.ViewObject.ShapeColor = (1.0,0.0,0.0,1.0)
580
def removeCurves(shape,dae=False,tolerance=5):
581
'''removeCurves(shape,dae,tolerance=5): replaces curved faces in a shape
582
with faceted segments. If dae is True, DAE triangulation options are used'''
586
t = importDAE.triangulate(shape.cleaned())
588
t = shape.cleaned().tessellate(tolerance)
590
return getShapeFromMesh(m)
592
def removeShape(objs,mark=True):
593
'''removeShape(objs,mark=True): takes an arch object (wall or structure) built on a cubic shape, and removes
594
the inner shape, keeping its length, width and height as parameters. If mark is True, objects that cannot
595
be processed by this function will become red.'''
596
import DraftGeomUtils
597
if not isinstance(objs,list):
600
if DraftGeomUtils.isCubic(obj.Shape):
601
dims = DraftGeomUtils.getCubicDimensions(obj.Shape)
604
tp = Draft.getType(obj)
606
if tp == "Structure":
607
FreeCAD.ActiveDocument.removeObject(name)
609
str = ArchStructure.makeStructure(length=dims[1],width=dims[2],height=dims[3],name=name)
610
str.Placement = dims[0]
612
FreeCAD.ActiveDocument.removeObject(name)
616
v1 = Vector(length/2,0,0)
618
v1 = dims[0].multVec(v1)
619
v2 = dims[0].multVec(v2)
620
line = Draft.makeLine(v1,v2)
621
ArchWall.makeWall(line,width=width,height=dims[3],name=name)
624
obj.ViewObject.ShapeColor = (1.0,0.0,0.0,1.0)
626
def mergeCells(objectslist):
627
'''mergeCells(objectslist): merges the objects in the given list
628
into one. All objects must be of the same type and based on the Cell
629
object (cells, floors, buildings, or sites).'''
632
if not isinstance(objectslist,list):
634
if len(objectslist) < 2:
636
typ = Draft.getType(objectslist[0])
637
if not(typ in ["Cell","Floor","Building","Site"]):
639
for o in objectslist:
640
if Draft.getType(o) != typ:
642
base = objectslist.pop(0)
643
for o in objectslist:
645
for c in o.Components:
649
FreeCAD.ActiveDocument.removeObject(o.Name)
650
FreeCAD.ActiveDocument.recompute()
653
def download(url,force=False):
654
'''download(url,force=False): downloads a file from the given URL and saves it in the
655
macro path. Returns the path to the saved file. If force is True, the file will be
656
downloaded again evn if it already exists.'''
658
from urllib.request import urlopen
660
from urllib2 import urlopen
662
name = url.split('/')[-1]
663
macropath = FreeCAD.getUserMacroDir(True)
664
filepath = os.path.join(macropath,name)
665
if os.path.exists(filepath) and not(force):
668
FreeCAD.Console.PrintMessage("downloading "+url+" ...\n")
669
response = urlopen(url)
671
f = open(filepath,'wb')
679
def check(objectslist,includehidden=False):
680
"""check(objectslist,includehidden=False): checks if the given objects contain only solids"""
681
objs = Draft.get_group_contents(objectslist)
682
if not includehidden:
683
objs = Draft.removeHidden(objs)
686
if not hasattr(o,'Shape'):
687
bad.append([o,"is not a Part-based object"])
690
if (not s.isClosed()) and (not (Draft.getType(o) == "Axis")):
691
bad.append([o,translate("Arch","is not closed")])
692
elif not s.isValid():
693
bad.append([o,translate("Arch","is not valid")])
694
elif (not s.Solids) and (not (Draft.getType(o) == "Axis")):
695
bad.append([o,translate("Arch","doesn't contain any solid")])
700
if not sol.isClosed():
701
bad.append([o,translate("Arch","contains a non-closed solid")])
702
if len(s.Faces) != f:
703
bad.append([o,translate("Arch","contains faces that are not part of any solid")])
706
def getHost(obj,strict=True):
707
"""getHost(obj,[strict]): returns the host of the current object. If strict is true (default),
708
the host can only be an object of a higher level than the given one, or in other words, if a wall
709
is contained in another wall which is part of a floor, the floor is returned instead of the parent wall"""
711
t = Draft.getType(obj)
712
for par in obj.InList:
713
if par.isDerivedFrom("Part::Feature") or par.isDerivedFrom("App::DocumentObjectGroup"):
715
if Draft.getType(par) != t:
718
return getHost(par,strict)
723
def pruneIncluded(objectslist,strict=False):
724
"""pruneIncluded(objectslist,[strict]): removes from a list of Arch objects, those that are subcomponents of
725
another shape-based object, leaving only the top-level shapes. If strict is True, the object
726
is removed only if the parent is also part of the selection."""
729
for obj in objectslist:
731
if obj.isDerivedFrom("Part::Feature"):
732
if Draft.getType(obj) not in ["Window","Clone","Pipe","Rebar"]:
733
for parent in obj.InList:
734
if not parent.isDerivedFrom("Part::Feature"):
736
elif Draft.getType(parent) in ["Space","Facebinder","Window","Roof","Clone","Site","Project"]:
738
elif parent.isDerivedFrom("Part::Part2DObject"):
739
# don't consider 2D objects based on arch elements
741
elif parent.isDerivedFrom("PartDesign::FeatureBase"):
742
# don't consider a PartDesign_Clone that references obj
744
elif parent.isDerivedFrom("PartDesign::Body") and obj == parent.BaseFeature:
745
# don't consider a PartDesign_Body with a PartDesign_Clone that references obj
747
elif hasattr(parent,"Host") and parent.Host == obj:
749
elif hasattr(parent,"Hosts") and obj in parent.Hosts:
751
elif hasattr(parent,"TypeId") and parent.TypeId == "Part::Mirroring":
753
elif hasattr(parent,"CloneOf"):
755
if parent.CloneOf.Name != obj.Name:
762
if toplevel == False and strict:
763
if parent not in objectslist and parent not in newlist:
768
FreeCAD.Console.PrintLog("pruning "+obj.Label+"\n")
771
def getAllChildren(objectlist):
772
"getAllChildren(objectlist): returns all the children of all the object sin the list"
778
l = getAllChildren(o.OutList)
785
def survey(callback=False):
786
"""survey(): starts survey mode, where you can click edges and faces to get their lengths or area.
787
Clicking on no object (on an empty area) resets the count."""
789
if hasattr(FreeCAD,"SurveyObserver"):
790
for label in FreeCAD.SurveyObserver.labels:
791
FreeCAD.ActiveDocument.removeObject(label)
792
FreeCADGui.Selection.removeObserver(FreeCAD.SurveyObserver)
793
del FreeCAD.SurveyObserver
794
FreeCADGui.Control.closeDialog()
795
if hasattr(FreeCAD,"SurveyDialog"):
796
del FreeCAD.SurveyDialog
798
FreeCAD.SurveyObserver = _SurveyObserver(callback=survey)
799
FreeCADGui.Selection.addObserver(FreeCAD.SurveyObserver)
800
FreeCAD.SurveyDialog = SurveyTaskPanel()
801
FreeCADGui.Control.showDialog(FreeCAD.SurveyDialog)
803
sel = FreeCADGui.Selection.getSelectionEx()
804
if hasattr(FreeCAD,"SurveyObserver"):
806
if FreeCAD.SurveyObserver.labels:
807
for label in FreeCAD.SurveyObserver.labels:
808
FreeCAD.ActiveDocument.removeObject(label)
809
tl = FreeCAD.SurveyObserver.totalLength
810
ta = FreeCAD.SurveyObserver.totalArea
811
FreeCAD.SurveyObserver.labels = []
812
FreeCAD.SurveyObserver.selection = []
813
FreeCAD.SurveyObserver.totalLength = 0
814
FreeCAD.SurveyObserver.totalArea = 0
815
FreeCAD.SurveyObserver.totalVolume = 0
816
if not FreeCAD.SurveyObserver.cancellable:
817
FreeCAD.Console.PrintMessage("\n---- Reset ----\n\n")
818
FreeCAD.SurveyObserver.cancellable = True
819
if hasattr(FreeCAD,"SurveyDialog"):
820
FreeCAD.SurveyDialog.newline(tl,ta)
822
FreeCADGui.Selection.removeObserver(FreeCAD.SurveyObserver)
823
del FreeCAD.SurveyObserver
824
FreeCADGui.Control.closeDialog()
825
if hasattr(FreeCAD,"SurveyDialog"):
826
del FreeCAD.SurveyDialog
828
FreeCAD.SurveyObserver.cancellable = False
829
basesel = FreeCAD.SurveyObserver.selection
834
if o.ObjectName == eo.ObjectName:
835
if o.SubElementNames == eo.SubElementNames:
841
if hasattr(o.Object, 'Shape'):
843
showUnit = params.get_param_arch("surveyUnits")
845
u = FreeCAD.Units.Quantity()
846
if not o.HasSubObjects:
848
anno = FreeCAD.ActiveDocument.addObject("App::AnnotationLabel","surveyLabel")
849
if hasattr(o.Object.Shape,"CenterOfMass"):
850
anno.BasePosition = o.Object.Shape.CenterOfMass
852
anno.BasePosition = o.Object.Shape.BoundBox.Center
853
FreeCAD.SurveyObserver.labels.append(anno.Name)
854
if o.Object.Shape.Solids:
855
u = FreeCAD.Units.Quantity(o.Object.Shape.Volume,FreeCAD.Units.Volume)
856
t = u.getUserPreferred()[0]
857
t = t.replace("^3","³")
858
anno.LabelText = "v " + t
859
FreeCAD.Console.PrintMessage("Object: " + n + ", Element: Whole, Volume: " + t + "\n")
860
FreeCAD.SurveyObserver.totalVolume += u.Value
861
elif o.Object.Shape.Faces:
862
u = FreeCAD.Units.Quantity(o.Object.Shape.Area,FreeCAD.Units.Area)
863
t = u.getUserPreferred()[0]
864
t = t.replace("^2","²")
865
anno.LabelText = "a " + t
866
FreeCAD.Console.PrintMessage("Object: " + n + ", Element: Whole, Area: " + t + "\n")
867
FreeCAD.SurveyObserver.totalArea += u.Value
868
if hasattr(FreeCAD,"SurveyDialog"):
869
FreeCAD.SurveyDialog.update(2,t)
871
u = FreeCAD.Units.Quantity(o.Object.Shape.Length,FreeCAD.Units.Length)
872
t = u.getUserPreferred()[0]
873
anno.LabelText = "l " + t
874
FreeCAD.Console.PrintMessage("Object: " + n + ", Element: Whole, Length: " + t + "\n")
875
FreeCAD.SurveyObserver.totalLength += u.Value
876
if hasattr(FreeCAD,"SurveyDialog"):
877
FreeCAD.SurveyDialog.update(1,t)
878
if FreeCAD.GuiUp and t:
880
QtGui.QApplication.clipboard().setText(t)
882
QtGui.QApplication.clipboard().setText(str(u.Value))
885
for el in o.SubElementNames:
886
e = getattr(o.Object.Shape,el)
887
anno = FreeCAD.ActiveDocument.addObject("App::AnnotationLabel","surveyLabel")
889
anno.BasePosition = e.Point
891
if hasattr(e,"CenterOfMass"):
892
anno.BasePosition = e.CenterOfMass
894
anno.BasePosition = e.BoundBox.Center
895
FreeCAD.SurveyObserver.labels.append(anno.Name)
897
u = FreeCAD.Units.Quantity(e.Area,FreeCAD.Units.Area)
898
t = u.getUserPreferred()[0]
899
t = t.replace("^2","²")
900
anno.LabelText = "a " + t
901
FreeCAD.Console.PrintMessage("Object: " + n + ", Element: " + el + ", Area: "+ t + "\n")
902
FreeCAD.SurveyObserver.totalArea += u.Value
903
if hasattr(FreeCAD,"SurveyDialog"):
904
FreeCAD.SurveyDialog.update(2,t)
906
u= FreeCAD.Units.Quantity(e.Length,FreeCAD.Units.Length)
907
t = u.getUserPreferred()[0]
908
anno.LabelText = "l " + t
909
FreeCAD.Console.PrintMessage("Object: " + n + ", Element: " + el + ", Length: " + t + "\n")
910
FreeCAD.SurveyObserver.totalLength += u.Value
911
if hasattr(FreeCAD,"SurveyDialog"):
912
FreeCAD.SurveyDialog.update(1,t)
914
u = FreeCAD.Units.Quantity(e.Z,FreeCAD.Units.Length)
915
t = u.getUserPreferred()[0]
916
anno.LabelText = "z " + t
917
FreeCAD.Console.PrintMessage("Object: " + n + ", Element: " + el + ", Zcoord: " + t + "\n")
918
if FreeCAD.GuiUp and t:
920
QtGui.QApplication.clipboard().setText(t)
922
QtGui.QApplication.clipboard().setText(str(u.Value))
924
FreeCAD.SurveyObserver.selection.extend(newsels)
925
if hasattr(FreeCAD,"SurveyObserver"):
926
if FreeCAD.SurveyObserver.totalLength or FreeCAD.SurveyObserver.totalArea or FreeCAD.SurveyObserver.totalVolume:
928
if FreeCAD.SurveyObserver.totalLength:
929
u = FreeCAD.Units.Quantity(FreeCAD.SurveyObserver.totalLength,FreeCAD.Units.Length)
930
t = u.getUserPreferred()[0]
931
msg += " Length: " + t
932
if FreeCAD.SurveyObserver.totalArea:
933
u = FreeCAD.Units.Quantity(FreeCAD.SurveyObserver.totalArea,FreeCAD.Units.Area)
934
t = u.getUserPreferred()[0]
935
t = t.replace("^2","²")
937
if FreeCAD.SurveyObserver.totalVolume:
938
u = FreeCAD.Units.Quantity(FreeCAD.SurveyObserver.totalVolume,FreeCAD.Units.Volume)
939
t = u.getUserPreferred()[0]
940
t = t.replace("^3","³")
941
msg += " Volume: " + t
942
FreeCAD.Console.PrintMessage(msg+"\n")
944
class _SurveyObserver:
945
"an observer for the survey() function"
946
def __init__(self,callback):
947
self.callback = callback
953
self.cancellable = False
954
self.doubleclear = False
956
def addSelection(self,document, object, element, position):
957
self.doubleclear = False
960
def clearSelection(self,document):
961
if not self.doubleclear:
962
self.doubleclear = True
966
class SurveyTaskPanel:
967
"A task panel for the survey tool"
970
self.form = QtGui.QWidget()
971
self.form.setWindowIcon(QtGui.QIcon(":/icons/Arch_Survey.svg"))
972
layout = QtGui.QVBoxLayout(self.form)
973
llayout = QtGui.QHBoxLayout()
974
self.descr = QtGui.QLineEdit()
975
llayout.addWidget(self.descr)
976
self.addButton = QtGui.QPushButton()
977
llayout.addWidget(self.addButton)
978
layout.addLayout(llayout)
979
self.tree = QtGui.QTreeWidget()
980
self.tree.setColumnCount(3)
981
layout.addWidget(self.tree)
982
blayout = QtGui.QHBoxLayout()
983
self.clearButton = QtGui.QPushButton()
984
blayout.addWidget(self.clearButton)
985
self.copyLength = QtGui.QPushButton()
986
blayout.addWidget(self.copyLength)
987
self.copyArea = QtGui.QPushButton()
988
blayout.addWidget(self.copyArea)
989
layout.addLayout(blayout)
990
self.export = QtGui.QPushButton()
991
layout.addWidget(self.export)
992
QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.setText)
993
QtCore.QObject.connect(self.clearButton, QtCore.SIGNAL("clicked()"), self.clear)
994
QtCore.QObject.connect(self.copyLength, QtCore.SIGNAL("clicked()"), self.clipLength)
995
QtCore.QObject.connect(self.copyArea, QtCore.SIGNAL("clicked()"), self.clipArea)
996
QtCore.QObject.connect(self.export, QtCore.SIGNAL("clicked()"), self.exportCSV)
997
QtCore.QObject.connect(self.tree, QtCore.SIGNAL("itemClicked(QTreeWidgetItem*,int)"), self.setDescr)
998
self.retranslateUi(self)
999
item = QtGui.QTreeWidgetItem(self.tree)
1000
self.tree.setCurrentItem(item)
1002
def retranslateUi(self,dlg):
1003
self.form.setWindowTitle(QtGui.QApplication.translate("Arch", "Survey", None))
1004
self.addButton.setText(QtGui.QApplication.translate("Arch", "Set description", None))
1005
self.clearButton.setText(QtGui.QApplication.translate("Arch", "Clear", None))
1006
self.copyLength.setText(QtGui.QApplication.translate("Arch", "Copy Length", None))
1007
self.copyArea.setText(QtGui.QApplication.translate("Arch", "Copy Area", None))
1008
self.export.setText(QtGui.QApplication.translate("Arch", "Export CSV", None))
1009
self.tree.setHeaderLabels([QtGui.QApplication.translate("Arch", "Description", None),
1010
QtGui.QApplication.translate("Arch", "Length", None),
1011
QtGui.QApplication.translate("Arch", "Area", None)])
1013
def isAllowedAlterSelection(self):
1016
def isAllowedAlterView(self):
1019
def getStandardButtons(self):
1020
return QtGui.QDialogButtonBox.Close
1023
if hasattr(FreeCAD,"SurveyObserver"):
1024
for label in FreeCAD.SurveyObserver.labels:
1025
FreeCAD.ActiveDocument.removeObject(label)
1026
FreeCADGui.Selection.removeObserver(FreeCAD.SurveyObserver)
1027
del FreeCAD.SurveyObserver
1031
FreeCADGui.Selection.clearSelection()
1033
def clipLength(self):
1034
if hasattr(FreeCAD,"SurveyObserver"):
1035
u = FreeCAD.Units.Quantity(FreeCAD.SurveyObserver.totalLength,FreeCAD.Units.Length)
1036
t = u.getUserPreferred()[0]
1037
if params.get_param_arch("surveyUnits"):
1038
QtGui.QApplication.clipboard().setText(t)
1040
QtGui.QApplication.clipboard().setText(str(u.Value/u.getUserPreferred()[1]))
1043
if hasattr(FreeCAD,"SurveyObserver"):
1044
u = FreeCAD.Units.Quantity(FreeCAD.SurveyObserver.totalArea,FreeCAD.Units.Area)
1045
t = u.getUserPreferred()[0]
1046
t = t.replace("^2","²")
1047
if params.get_param_arch("surveyUnits"):
1048
QtGui.QApplication.clipboard().setText(t)
1050
QtGui.QApplication.clipboard().setText(str(u.Value/u.getUserPreferred()[1]))
1052
def newline(self,length=0,area=0):
1053
FreeCADGui.Selection.clearSelection()
1054
item = QtGui.QTreeWidgetItem(self.tree)
1056
item.setText(0,QtGui.QApplication.translate("Arch", "Total", None))
1057
item.setToolTip(0,"total")
1064
item.setText(0,self.descr.text())
1065
self.descr.setText("")
1066
self.tree.setCurrentItem(item)
1068
u = FreeCAD.Units.Quantity(length,FreeCAD.Units.Length)
1069
t = u.getUserPreferred()[0]
1072
u = FreeCAD.Units.Quantity(area,FreeCAD.Units.Area)
1073
t = u.getUserPreferred()[0]
1074
t = t.replace(u"^2",u"²")
1077
item = QtGui.QTreeWidgetItem(self.tree)
1078
self.tree.setCurrentItem(item)
1080
def update(self,column,txt):
1081
item = QtGui.QTreeWidgetItem(self.tree)
1082
self.tree.setCurrentItem(item)
1083
item.setText(column,txt)
1085
def setDescr(self,item,col):
1086
self.descr.setText(item.text(0))
1089
item = self.tree.currentItem()
1091
item.setText(0,self.descr.text())
1092
self.descr.setText("")
1094
def exportCSV(self):
1096
rows = self.tree.topLevelItemCount()
1098
filename = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), translate("Arch","Export CSV File"), None, "CSV file (*.csv)")
1100
with open(filename[0].encode("utf8"), "w") as csvfile:
1101
csvfile = csv.writer(csvfile,delimiter="\t")
1103
for i in range(rows):
1104
item = self.tree.topLevelItem(i)
1106
row.append(item.text(0))
1108
u = FreeCAD.Units.Quantity(item.text(1))
1109
if item.toolTip(0) == "total":
1110
row.append("=SUM(B"+str(suml+1)+":B"+str(i)+")")
1112
row.append(u.Value/u.getUserPreferred()[1])
1113
row.append(u.getUserPreferred()[2])
1117
t = item.text(2).replace(u"²",u"^2")
1118
u = FreeCAD.Units.Quantity(t)
1119
if item.toolTip(0) == "total":
1120
row.append("=SUM(D"+str(suml+1)+":D"+str(i)+")")
1122
row.append(u.Value/u.getUserPreferred()[1])
1123
row.append(u.getUserPreferred()[2])
1126
csvfile.writerow(row)
1127
if item.toolTip(0) == "total":
1129
print("successfully exported ",filename[0])
1132
def toggleIfcBrepFlag(obj):
1133
"""toggleIfcBrepFlag(obj): toggles the IFC brep flag of the given object, forcing it
1134
to be exported as brep geometry or not."""
1135
if not hasattr(obj,"IfcData"):
1136
FreeCAD.Console.PrintMessage(translate("Arch","Object doesn't have settable IFC attributes"))
1139
if "FlagForceBrep" in d:
1140
if d["FlagForceBrep"] == "True":
1141
d["FlagForceBrep"] = "False"
1142
FreeCAD.Console.PrintMessage(translate("Arch","Disabling B-rep force flag of object")+" "+obj.Label+"\n")
1144
d["FlagForceBrep"] = "True"
1145
FreeCAD.Console.PrintMessage(translate("Arch","Enabling B-rep force flag of object")+" "+obj.Label+"\n")
1147
d["FlagForceBrep"] = "True"
1148
FreeCAD.Console.PrintMessage(translate("Arch","Enabling B-rep force flag of object")+" "+obj.Label+"\n")
1152
def makeCompoundFromSelected(objects=None):
1153
"""makeCompoundFromSelected([objects]): Creates a new compound object from the given
1154
subobjects (faces, edges) or from the selection if objects is None"""
1159
objects = FreeCADGui.Selection.getSelectionEx()
1160
if not isinstance(objects,list):
1163
so.extend(o.SubObjects)
1165
c = Part.makeCompound(so)
1169
def cleanArchSplitter(objects=None):
1170
"""cleanArchSplitter([objects]): removes the splitters from the base shapes
1171
of the given Arch objects or selected Arch objects if objects is None"""
1175
objects = FreeCADGui.Selection.getSelection()
1176
if not isinstance(objects,list):
1179
if hasattr(obj,'Shape'):
1180
if hasattr(obj,"Base"):
1182
print("Attempting to clean splitters from ", obj.Label)
1183
base = obj.Base.getLinkedObject()
1184
if base.isDerivedFrom("Part::Feature"):
1185
if not base.Shape.isNull():
1186
base.Shape = base.Shape.removeSplitter()
1187
FreeCAD.ActiveDocument.recompute()
1190
def rebuildArchShape(objects=None):
1191
"""rebuildArchShape([objects]): takes the faces from the base shape of the given (or selected
1192
if objects is None) Arch objects, and tries to rebuild a valid solid from them."""
1195
if not objects and FreeCAD.GuiUp:
1196
objects = FreeCADGui.Selection.getSelection()
1197
if not isinstance(objects,list):
1201
if hasattr(obj,'Shape'):
1202
if hasattr(obj,"Base"):
1205
print("Attempting to rebuild ", obj.Label)
1206
base = obj.Base.getLinkedObject()
1207
if base.isDerivedFrom("Part::Feature"):
1208
if not base.Shape.isNull():
1210
for f in base.Shape.Faces:
1211
f2 = Part.Face(f.Wires)
1212
#print("rebuilt face: isValid is ", f2.isValid())
1215
shell = Part.Shell(faces)
1217
#print("rebuilt shell: isValid is ", shell.isValid())
1218
solid = Part.Solid(shell)
1220
if not solid.isValid():
1222
solid = Part.Solid(solid)
1223
#print("rebuilt solid: isValid is ",solid.isValid())
1230
print ("Failed to rebuild a valid solid for object ",obj.Name)
1231
FreeCAD.ActiveDocument.recompute()
1234
def getExtrusionData(shape,sortmethod="area"):
1235
"""If a shape has been extruded, returns the base face, and extrusion vector.
1237
Determines if a shape appears to have been extruded from some base face, and
1238
extruded at the normal from that base face. IE: it looks like a cuboid.
1239
https://en.wikipedia.org/wiki/Cuboid#Rectangular_cuboid
1241
If this is the case, returns what appears to be the base face, and the vector
1242
used to make that extrusion.
1244
The base face is determined based on the sortmethod parameter, which can either
1247
"area" = Of the faces with the smallest area, the one with the lowest z coordinate.
1248
"z" = The face with the lowest z coordinate.
1249
a 3D vector = the face which center is closest to the given 3D point
1255
sortmethod: {"area", "z"}
1256
Which sorting algorithm to use to determine the base face.
1260
Extrusion data: list
1261
Two item list containing the base face, and the vector used to create the
1262
extrusion. In that order.
1264
Returns None if the object does not appear to be an extrusion.
1269
if not shape.Solids:
1271
if len(shape.Faces) < 3:
1273
# build faces list with normals
1276
for f in shape.Faces:
1278
faces.append([f,f.normalAt(0,0)])
1279
except Part.OCCError:
1281
# find opposite normals pairs
1283
for i1, f1 in enumerate(faces):
1284
for i2, f2 in enumerate(faces):
1285
if f1[0].hashCode() != f2[0].hashCode():
1286
if round(f1[1].getAngle(f2[1]),4) == 3.1416:
1287
pairs.append([i1,i2])
1292
hc = [faces[pair[0]][0].hashCode(),faces[pair[1]][0].hashCode()]
1293
# check if other normals are all at 90 degrees
1296
if f[0].hashCode() not in hc:
1297
if round(f[1].getAngle(faces[pair[0]][1]),4) != 1.5708:
1300
# prefer the face with the lowest z
1301
if faces[pair[0]][0].CenterOfMass.z < faces[pair[1]][0].CenterOfMass.z:
1302
valids.append([faces[pair[0]][0],faces[pair[1]][0].CenterOfMass.sub(faces[pair[0]][0].CenterOfMass)])
1304
valids.append([faces[pair[1]][0],faces[pair[0]][0].CenterOfMass.sub(faces[pair[1]][0].CenterOfMass)])
1306
if sortmethod == "z":
1307
valids.sort(key=lambda v: v[0].CenterOfMass.z)
1308
elif sortmethod == "area":
1309
# sort by smallest area
1310
valids.sort(key=lambda v: v[0].Area)
1312
valids.sort(key=lambda v: (v[0].CenterOfMass.sub(sortmethod)).Length)
1316
def printMessage( message ):
1317
FreeCAD.Console.PrintMessage( message )
1319
QtGui.QMessageBox.information( None , "" , message )
1321
def printWarning( message ):
1322
FreeCAD.Console.PrintMessage( message )
1324
QtGui.QMessageBox.warning( None , "" , message )
1326
def makeIfcSpreadsheet(archobj=None):
1327
ifc_container = None
1328
for obj in FreeCAD.ActiveDocument.Objects :
1329
if obj.Name == "IfcPropertiesContainer" :
1331
if not ifc_container :
1332
ifc_container = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup','IfcPropertiesContainer')
1334
ifc_spreadsheet = FreeCAD.ActiveDocument.addObject('Spreadsheet::Sheet','IfcProperties')
1335
ifc_spreadsheet.set('A1', translate("Arch","Category"))
1336
ifc_spreadsheet.set('B1', translate("Arch","Key"))
1337
ifc_spreadsheet.set('C1', translate("Arch","Type"))
1338
ifc_spreadsheet.set('D1', translate("Arch","Value"))
1339
ifc_spreadsheet.set('E1', translate("Arch","Unit"))
1340
ifc_container.addObject(ifc_spreadsheet)
1342
if hasattr(obj,"IfcProperties") :
1343
archobj.IfcProperties = ifc_spreadsheet
1344
return ifc_spreadsheet
1346
FreeCAD.Console.PrintWarning(translate("Arch", "The object doesn't have an IfcProperties attribute. Cancel spreadsheet creation for object:")+ ' ' + archobj.Label)
1347
FreeCAD.ActiveDocument.removeObject(ifc_spreadsheet)
1349
return ifc_spreadsheet