FreeCAD

Форк
0
/
ArchCommands.py 
1349 строк · 56.5 Кб
1
# -*- coding: utf8 -*-
2
#***************************************************************************
3
#*   Copyright (c) 2011 Yorik van Havre <yorik@uncreated.net>              *
4
#*                                                                         *
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.                                 *
10
#*                                                                         *
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.                  *
15
#*                                                                         *
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  *
19
#*   USA                                                                   *
20
#*                                                                         *
21
#***************************************************************************
22

23
import FreeCAD
24
import ArchComponent
25
import Draft
26
import DraftVecUtils
27
from FreeCAD import Vector
28
from draftutils import params
29

30
if FreeCAD.GuiUp:
31
    import FreeCADGui
32
    from PySide import QtGui,QtCore
33
    from draftutils.translate import translate
34
else:
35
    # \cond
36
    def translate(ctxt,txt):
37
        return txt
38
    # \endcond
39

40
__title__  = "FreeCAD Arch Commands"
41
__author__ = "Yorik van Havre"
42
__url__    = "https://www.freecad.org"
43

44
## @package ArchCommands
45
#  \ingroup ARCH
46
#  \brief Utility functions for the Arch Workbench
47
#
48
#  This module provides general functions used by Arch tools
49
#  and utility commands
50

51
# module functions ###############################################
52

53
def getStringList(objects):
54
    '''getStringList(objects): returns a string defining a list
55
    of objects'''
56
    result = "["
57
    for o in objects:
58
        if len(result) > 1:
59
            result += ","
60
        result += "FreeCAD.ActiveDocument." + o.Name
61
    result += "]"
62
    return result
63

64
def getDefaultColor(objectType):
65
    '''getDefaultColor(string): returns a color value for the given object
66
    type (Wall, Structure, Window, WindowGlass)'''
67
    transparency = 0.0
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")
85
        transparency = 0.80
86
    else:
87
        c = params.get_param_view("DefaultShapeColor")
88
    r, g, b, _ = Draft.get_rgba_tuple(c)
89
    return (r, g, b, transparency)
90

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"]:
99
        for o in objectsList:
100
            host.addObject(o)
101
    elif hostType in ["Wall","Structure","Precast","Window","Roof","Stairs","StructuralSystem","Panel","Component","Pipe"]:
102
        import DraftGeomUtils
103
        a = host.Additions
104
        if hasattr(host,"Axes"):
105
            x = 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:
111
                            g = o.Hosts
112
                            g.append(host)
113
                            o.Hosts = g
114
                elif DraftGeomUtils.isValidPath(o.Shape) and (hostType in ["Structure","Precast"]):
115
                    if o.AttachmentSupport == host:
116
                        o.AttachmentSupport = None
117
                    host.Tool = o
118
                elif Draft.getType(o) == "Axis":
119
                    if not o in x:
120
                        x.append(o)
121
                elif not o in a:
122
                    if hasattr(o,"Shape"):
123
                        a.append(o)
124
        host.Additions = a
125
        if hasattr(host,"Axes"):
126
            host.Axes = x
127
    elif hostType in ["SectionPlane"]:
128
        a = host.Objects
129
        for o in objectsList:
130
            if not o in a:
131
                a.append(o)
132
        host.Objects = a
133
    elif host.isDerivedFrom("App::DocumentObjectGroup"):
134
        for o in objectsList:
135
            host.addObject(o)
136

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
141
    object instead.'''
142
    if not isinstance(objectsList,list):
143
        objectsList = [objectsList]
144
    if host:
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:
148
                    host.Tool = None
149
            if hasattr(host,"Axes"):
150
                a = host.Axes
151
                for o in objectsList[:]:
152
                    if o in a:
153
                        a.remove(o)
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:
160
                            g = o.Hosts
161
                            g.append(host)
162
                            o.Hosts = g
163
                elif not o in s:
164
                    s.append(o)
165
                    if FreeCAD.GuiUp:
166
                        if not Draft.getType(o) in ["Window","Roof"]:
167
                            setAsSubcomponent(o)
168
            host.Subtractions = s
169
        elif Draft.getType(host) in ["SectionPlane"]:
170
            a = host.Objects
171
            for o in objectsList:
172
                if o in a:
173
                    a.remove(o)
174
            host.Objects = a
175
    else:
176
        for o in objectsList:
177
            if o.InList:
178
                h = o.InList[0]
179
                tp = Draft.getType(h)
180
                if tp in ["Floor","Building","Site","BuildingPart"]:
181
                    c = h.Group
182
                    if o in c:
183
                        c.remove(o)
184
                        h.Group = c
185
                        o.ViewObject.show()
186
                elif tp in ["Wall","Structure","Precast"]:
187
                    a = h.Additions
188
                    s = h.Subtractions
189
                    if o in a:
190
                        a.remove(o)
191
                        h.Additions = a
192
                        o.ViewObject.show()
193
                    elif o in s:
194
                        s.remove(o)
195
                        h.Subtractions = s
196
                        o.ViewObject.show()
197
                    elif o == s.Base:
198
                        s.Base = None
199
                        o.ViewObject.show()
200
                elif tp in ["SectionPlane"]:
201
                    a = h.Objects
202
                    if o in a:
203
                        a.remove(o)
204
                        h.Objects = a
205

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")
211
        return
212
    obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Component")
213
    obj.Label = name if name else translate("Arch","Component")
214
    ArchComponent.Component(obj)
215
    if FreeCAD.GuiUp:
216
        ArchComponent.ViewProviderComponent(obj.ViewObject)
217
    if baseobj:
218
        import Part
219
        if hasattr(baseobj,'Shape'):
220
            obj.Shape = baseobj.Shape
221
            obj.Placement = baseobj.Placement
222
            if delete:
223
                FreeCAD.ActiveDocument.removeObject(baseobj.Name)
224
            else:
225
                obj.Base = baseobj
226
                if FreeCAD.GuiUp:
227
                    baseobj.ViewObject.hide()
228
        elif isinstance(baseobj,Part.Shape):
229
            obj.Shape = baseobj
230
    Draft.select(obj)
231
    return obj
232

233
def cloneComponent(obj):
234
    '''cloneComponent(obj): Creates a clone of an object as an undefined component'''
235
    c = makeComponent()
236
    c.CloneOf = obj
237
    c.Placement = obj.Placement
238
    c.Label = obj.Label
239
    if hasattr(obj,"Material"):
240
        if obj.Material:
241
            c.Material = obj.Material
242
    if hasattr(obj,"IfcAttributes"):
243
        if obj.IfcAttributes:
244
            c.IfcAttributes = obj.IfcAttributes
245
    Draft.select(c)
246
    return c
247

248
def setAsSubcomponent(obj):
249
    '''Sets the given object properly to become a subcomponent (addition, subtraction)
250
    of an Arch component'''
251
    Draft.ungroup(obj)
252
    if params.get_param_arch("applyConstructionStyle"):
253
        if FreeCAD.GuiUp:
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()
264

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))
277

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 []
283
    basemesh = obj.Mesh
284
    comps = basemesh.getSeparateComponents()
285
    nlist = []
286
    if comps:
287
        basename = obj.Name
288
        FreeCAD.ActiveDocument.removeObject(basename)
289
        for c in comps:
290
            newobj = FreeCAD.ActiveDocument.addObject("Mesh::Feature",basename)
291
            newobj.Mesh = c
292
            if mark and (not(c.isSolid()) or c.hasNonManifolds()):
293
                newobj.ViewObject.ShapeColor = (1.0,0.0,0.0,1.0)
294
            nlist.append(newobj)
295
        return nlist
296
    return [obj]
297

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)
301
    import Part
302

303
    if not isinstance(wires,list):
304
        if len(wires.Vertexes) < 3:
305
            raise
306
        return Part.Face(wires)
307
    elif len(wires) == 1:
308
        #import Draft;Draft.printShape(wires[0])
309
        if len(wires[0].Vertexes) < 3:
310
            raise
311
        return Part.Face(wires[0])
312

313
    wires = wires[:]
314

315
    #print("makeFace: inner wires found")
316
    ext = None
317
    max_length = 0
318
    # cleaning up rubbish in wires
319
    if cleanup:
320
        for i in range(len(wires)):
321
            wires[i] = DraftGeomUtils.removeInterVertices(wires[i])
322
        #print("makeFace: garbage removed")
323
    for w in wires:
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
328
            ext = w
329
    #print("makeFace: exterior wire", ext)
330
    wires.remove(ext)
331

332
    if method == 1:
333
        # method 1: reverse inner wires
334
        # all interior wires mark a hole and must reverse
335
        # their orientation, otherwise Part.Face fails
336
        for w in wires:
337
            #print("makeFace: reversing", w)
338
            w.reverse()
339
            # make sure that the exterior wires comes as first in the list
340
        wires.insert(0, ext)
341
        #print("makeFace: done sorting", wires)
342
        if wires:
343
            return Part.Face(wires)
344
    else:
345
        # method 2: use the cut method
346
        mf = Part.Face(ext)
347
        #print("makeFace: external face:", mf)
348
        for w in wires:
349
            f = Part.Face(w)
350
            #print("makeFace: internal face:", f)
351
            mf = mf.cut(f)
352
        #print("makeFace: final face:", mf.Faces)
353
        return mf.Faces[0]
354

355
def closeHole(shape):
356
    '''closeHole(shape): closes a hole in an open shape'''
357
    import DraftGeomUtils
358
    import Part
359
    # creating an edges lookup table
360
    lut = {}
361
    for face in shape.Faces:
362
        for edge in face.Edges:
363
            hc = edge.hashCode()
364
            if hc in lut:
365
                lut[hc] = lut[hc] + 1
366
            else:
367
                lut[hc] = 1
368
    # filter out the edges shared by more than one face
369
    bound = []
370
    for e in shape.Edges:
371
        if lut[e.hashCode()] == 1:
372
            bound.append(e)
373
    bound = Part.__sortEdges__(bound)
374
    try:
375
        nface = Part.Face(Part.Wire(bound))
376
        shell = Part.makeShell(shape.Faces+[nface])
377
        solid = Part.Solid(shell)
378
    except Part.OCCError:
379
        raise
380
    else:
381
        return solid
382

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"""
388
    if not shapes:
389
        return None,None,None
390
    if not cutplane.Faces:
391
        return None,None,None
392
    import Part
393
    if not isinstance(shapes,list):
394
        shapes = [shapes]
395
    # building boundbox
396
    bb = shapes[0].BoundBox
397
    for sh in shapes[1:]:
398
        bb.add(sh.BoundBox)
399
    bb.enlarge(1)
400
    # building cutplane space
401
    um = vm = wm = 0
402
    try:
403
        if hasattr(cutplane,"Shape"):
404
            p = cutplane.Shape.copy().Faces[0]
405
        else:
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
410
    ce = p.CenterOfMass
411
    ax = p.normalAt(0,0)
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()
414
    v = u.cross(ax)
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
418
    else:
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)]
427
        for c in corners:
428
            dv = c.sub(ce)
429
            um1 = DraftVecUtils.project(dv,u).Length
430
            um = max(um,um1)
431
            vm1 = DraftVecUtils.project(dv,v).Length
432
            vm = max(vm,vm1)
433
            wm1 = DraftVecUtils.project(dv,ax).Length
434
            wm = max(wm,wm1)
435
        vu = DraftVecUtils.scaleTo(u,um)
436
        vui = vu.negative()
437
        vv = DraftVecUtils.scaleTo(v,vm)
438
        vvi = vv.negative()
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)
449
        if clip:
450
            extrudedplane = p.extrude(cutnormal)
451
            bordervolume = invcutvolume.cut(extrudedplane)
452
            cutvolume = cutvolume.fuse(bordervolume)
453
            cutvolume = cutvolume.removeSplitter()
454
            invcutvolume = extrudedplane
455
            cutface = p
456
        if depth:
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
463

464
def getShapeFromMesh(mesh,fast=True,tolerance=0.001,flat=False,cut=True):
465
    import Part
466
    import MeshPart
467
    import DraftGeomUtils
468
    if mesh.isSolid() and (mesh.countComponents() == 1) and fast:
469
        # use the best method
470
        faces = []
471
        for f in mesh.Facets:
472
            p=f.Points+[f.Points[0]]
473
            pts = []
474
            for pp in p:
475
                pts.append(FreeCAD.Vector(pp[0],pp[1],pp[2]))
476
            try:
477
                f = Part.Face(Part.makePolygon(pts))
478
            except Exception:
479
                print("getShapeFromMesh: error building face from polygon")
480
                #pass
481
            else:
482
                faces.append(f)
483
        shell = Part.makeShell(faces)
484
        try:
485
            solid = Part.Solid(shell)
486
        except Part.OCCError:
487
            print("getShapeFromMesh: error creating solid")
488
        else:
489
            try:
490
                solid = solid.removeSplitter()
491
            except Part.OCCError:
492
                print("getShapeFromMesh: error removing splitter")
493
                #pass
494
            return solid
495

496
    #if not mesh.isSolid():
497
    #    print "getShapeFromMesh: non-solid mesh, using slow method"
498
    faces = []
499
    segments = mesh.getPlanarSegments(tolerance)
500
    #print(len(segments))
501
    for i in segments:
502
        if len(i) > 0:
503
            wires = MeshPart.wireFromSegment(mesh, i)
504
            if wires:
505
                if flat:
506
                    nwires = []
507
                    for w in wires:
508
                        nwires.append(DraftGeomUtils.flattenWire(w))
509
                    wires = nwires
510
                try:
511
                    faces.append(makeFace(wires,method=int(cut)+1))
512
                except Exception:
513
                    return None
514
    try:
515
        se = Part.makeShell(faces)
516
        se = se.removeSplitter()
517
        if flat:
518
            return se
519
    except Part.OCCError:
520
        print("getShapeFromMesh: error removing splitter")
521
        try:
522
            cp = Part.makeCompound(faces)
523
        except Part.OCCError:
524
            print("getShapeFromMesh: error creating compound")
525
            return None
526
        else:
527
            return cp
528
    else:
529
        try:
530
            solid = Part.Solid(se)
531
        except Part.OCCError:
532
            print("getShapeFromMesh: error creating solid")
533
            return se
534
        else:
535
            return solid
536

537
def projectToVector(shape,vector):
538
    '''projectToVector(shape,vector): projects the given shape on the given
539
    vector'''
540
    projpoints = []
541
    minl = 10000000000
542
    maxl = -10000000000
543
    for v in shape.Vertexes:
544
        p = DraftVecUtils.project(v.Point,vector)
545
        projpoints.append(p)
546
        l = p.Length
547
        if p.getAngle(vector) > 1:
548
            l = -l
549
        if l > maxl:
550
            maxl = l
551
        if l < minl:
552
            minl = l
553
    return DraftVecUtils.scaleTo(vector,maxl-minl)
554

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)'''
562

563
    name = obj.Name
564
    if "Mesh" in obj.PropertiesList:
565
        mesh = obj.Mesh
566
        #plac = obj.Placement
567
        solid = getShapeFromMesh(mesh,fast,tol,flat,cut)
568
        if solid:
569
            if solid.isClosed() and solid.isValid():
570
                FreeCAD.ActiveDocument.removeObject(name)
571
            newobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name)
572
            newobj.Shape = solid
573
            #newobj.Placement = plac #the placement is already computed in the mesh
574
            if (not solid.isClosed()) or (not solid.isValid()):
575
                if mark:
576
                    newobj.ViewObject.ShapeColor = (1.0,0.0,0.0,1.0)
577
            return newobj
578
    return None
579

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'''
583
    import Mesh
584
    if dae:
585
        import importDAE
586
        t = importDAE.triangulate(shape.cleaned())
587
    else:
588
        t = shape.cleaned().tessellate(tolerance)
589
    m = Mesh.Mesh(t)
590
    return getShapeFromMesh(m)
591

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):
598
        objs = [objs]
599
    for obj in objs:
600
        if DraftGeomUtils.isCubic(obj.Shape):
601
            dims = DraftGeomUtils.getCubicDimensions(obj.Shape)
602
            if dims:
603
                name = obj.Name
604
                tp = Draft.getType(obj)
605
                print(tp)
606
                if tp == "Structure":
607
                    FreeCAD.ActiveDocument.removeObject(name)
608
                    import ArchStructure
609
                    str = ArchStructure.makeStructure(length=dims[1],width=dims[2],height=dims[3],name=name)
610
                    str.Placement = dims[0]
611
                elif tp == "Wall":
612
                    FreeCAD.ActiveDocument.removeObject(name)
613
                    import ArchWall
614
                    length = dims[1]
615
                    width = dims[2]
616
                    v1 = Vector(length/2,0,0)
617
                    v2 = v1.negative()
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)
622
        else:
623
            if mark:
624
                obj.ViewObject.ShapeColor = (1.0,0.0,0.0,1.0)
625

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).'''
630
    if not objectslist:
631
        return None
632
    if not isinstance(objectslist,list):
633
        return None
634
    if len(objectslist) < 2:
635
        return None
636
    typ = Draft.getType(objectslist[0])
637
    if not(typ in ["Cell","Floor","Building","Site"]):
638
        return None
639
    for o in objectslist:
640
        if Draft.getType(o) != typ:
641
            return None
642
    base = objectslist.pop(0)
643
    for o in objectslist:
644
        l = base.Components
645
        for c in o.Components:
646
            if not c in l:
647
                l.append(c)
648
        base.Components = l
649
        FreeCAD.ActiveDocument.removeObject(o.Name)
650
    FreeCAD.ActiveDocument.recompute()
651
    return base
652

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.'''
657
    try:
658
        from urllib.request import urlopen
659
    except ImportError:
660
        from urllib2 import urlopen
661
    import os
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):
666
        return filepath
667
    try:
668
        FreeCAD.Console.PrintMessage("downloading "+url+" ...\n")
669
        response = urlopen(url)
670
        s = response.read()
671
        f = open(filepath,'wb')
672
        f.write(s)
673
        f.close()
674
    except Exception:
675
        return None
676
    else:
677
        return filepath
678

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)
684
    bad = []
685
    for o in objs:
686
        if not hasattr(o,'Shape'):
687
            bad.append([o,"is not a Part-based object"])
688
        else:
689
            s = o.Shape
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")])
696
            else:
697
                f = 0
698
                for sol in s.Solids:
699
                    f += len(sol.Faces)
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")])
704
    return bad
705

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"""
710
    import Draft
711
    t = Draft.getType(obj)
712
    for par in obj.InList:
713
        if par.isDerivedFrom("Part::Feature") or par.isDerivedFrom("App::DocumentObjectGroup"):
714
            if strict:
715
                if Draft.getType(par) != t:
716
                    return par
717
                else:
718
                    return getHost(par,strict)
719
            else:
720
                return par
721
    return None
722

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."""
727
    import Draft
728
    newlist = []
729
    for obj in objectslist:
730
        toplevel = True
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"):
735
                        pass
736
                    elif Draft.getType(parent) in ["Space","Facebinder","Window","Roof","Clone","Site","Project"]:
737
                        pass
738
                    elif parent.isDerivedFrom("Part::Part2DObject"):
739
                        # don't consider 2D objects based on arch elements
740
                        pass
741
                    elif parent.isDerivedFrom("PartDesign::FeatureBase"):
742
                        # don't consider a PartDesign_Clone that references obj
743
                        pass
744
                    elif parent.isDerivedFrom("PartDesign::Body") and obj == parent.BaseFeature:
745
                        # don't consider a PartDesign_Body with a PartDesign_Clone that references obj
746
                        pass
747
                    elif hasattr(parent,"Host") and parent.Host == obj:
748
                        pass
749
                    elif hasattr(parent,"Hosts") and obj in parent.Hosts:
750
                        pass
751
                    elif hasattr(parent,"TypeId") and parent.TypeId == "Part::Mirroring":
752
                        pass
753
                    elif hasattr(parent,"CloneOf"):
754
                        if parent.CloneOf:
755
                            if parent.CloneOf.Name != obj.Name:
756
                                toplevel = False
757
                        else:
758
                            toplevel = False
759
                    else:
760
                        toplevel = False
761

762
                    if toplevel == False and strict:
763
                        if parent not in objectslist and parent not in newlist:
764
                            toplevel = True
765
        if toplevel:
766
            newlist.append(obj)
767
        else:
768
            FreeCAD.Console.PrintLog("pruning "+obj.Label+"\n")
769
    return newlist
770

771
def getAllChildren(objectlist):
772
    "getAllChildren(objectlist): returns all the children of all the object sin the list"
773
    obs = []
774
    for o in objectlist:
775
        if not o in obs:
776
            obs.append(o)
777
        if o.OutList:
778
            l = getAllChildren(o.OutList)
779
            for c in l:
780
                if not c in obs:
781
                    obs.append(c)
782
    return obs
783

784

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."""
788
    if not callback:
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
797
        else:
798
            FreeCAD.SurveyObserver = _SurveyObserver(callback=survey)
799
            FreeCADGui.Selection.addObserver(FreeCAD.SurveyObserver)
800
            FreeCAD.SurveyDialog = SurveyTaskPanel()
801
            FreeCADGui.Control.showDialog(FreeCAD.SurveyDialog)
802
    else:
803
        sel = FreeCADGui.Selection.getSelectionEx()
804
        if hasattr(FreeCAD,"SurveyObserver"):
805
            if not sel:
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)
821
                    else:
822
                        FreeCADGui.Selection.removeObserver(FreeCAD.SurveyObserver)
823
                        del FreeCAD.SurveyObserver
824
                        FreeCADGui.Control.closeDialog()
825
                        if hasattr(FreeCAD,"SurveyDialog"):
826
                            del FreeCAD.SurveyDialog
827
            else:
828
                FreeCAD.SurveyObserver.cancellable = False
829
                basesel = FreeCAD.SurveyObserver.selection
830
                newsels = []
831
                for o in sel:
832
                    found = False
833
                    for eo in basesel:
834
                        if o.ObjectName == eo.ObjectName:
835
                            if o.SubElementNames == eo.SubElementNames:
836
                                found = True
837
                    if not found:
838
                        newsels.append(o)
839
                if newsels:
840
                    for o in newsels:
841
                        if hasattr(o.Object, 'Shape'):
842
                            n = o.Object.Label
843
                            showUnit = params.get_param_arch("surveyUnits")
844
                            t = ""
845
                            u = FreeCAD.Units.Quantity()
846
                            if not o.HasSubObjects:
847
                                # entire object
848
                                anno = FreeCAD.ActiveDocument.addObject("App::AnnotationLabel","surveyLabel")
849
                                if hasattr(o.Object.Shape,"CenterOfMass"):
850
                                    anno.BasePosition = o.Object.Shape.CenterOfMass
851
                                else:
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)
870
                                else:
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:
879
                                    if showUnit:
880
                                        QtGui.QApplication.clipboard().setText(t)
881
                                    else:
882
                                        QtGui.QApplication.clipboard().setText(str(u.Value))
883
                            else:
884
                                # single element(s)
885
                                for el in o.SubElementNames:
886
                                    e = getattr(o.Object.Shape,el)
887
                                    anno = FreeCAD.ActiveDocument.addObject("App::AnnotationLabel","surveyLabel")
888
                                    if "Vertex" in el:
889
                                        anno.BasePosition = e.Point
890
                                    else:
891
                                        if hasattr(e,"CenterOfMass"):
892
                                            anno.BasePosition = e.CenterOfMass
893
                                        else:
894
                                            anno.BasePosition = e.BoundBox.Center
895
                                    FreeCAD.SurveyObserver.labels.append(anno.Name)
896
                                    if "Face" in el:
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)
905
                                    elif "Edge" in el:
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)
913
                                    elif "Vertex" in el:
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:
919
                                        if showUnit:
920
                                            QtGui.QApplication.clipboard().setText(t)
921
                                        else:
922
                                            QtGui.QApplication.clipboard().setText(str(u.Value))
923

924
                    FreeCAD.SurveyObserver.selection.extend(newsels)
925
            if hasattr(FreeCAD,"SurveyObserver"):
926
                if FreeCAD.SurveyObserver.totalLength or FreeCAD.SurveyObserver.totalArea or FreeCAD.SurveyObserver.totalVolume:
927
                    msg = " Total:"
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","²")
936
                        msg += " Area: " + t
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")
943

944
class _SurveyObserver:
945
    "an observer for the survey() function"
946
    def __init__(self,callback):
947
        self.callback = callback
948
        self.selection = []
949
        self.labels = []
950
        self.totalLength = 0
951
        self.totalArea = 0
952
        self.totalVolume = 0
953
        self.cancellable = False
954
        self.doubleclear = False
955

956
    def addSelection(self,document, object, element, position):
957
        self.doubleclear = False
958
        self.callback(True)
959

960
    def clearSelection(self,document):
961
        if not self.doubleclear:
962
            self.doubleclear = True
963
        else:
964
            self.callback(True)
965

966
class SurveyTaskPanel:
967
    "A task panel for the survey tool"
968

969
    def __init__(self):
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)
1001

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)])
1012

1013
    def isAllowedAlterSelection(self):
1014
        return True
1015

1016
    def isAllowedAlterView(self):
1017
        return True
1018

1019
    def getStandardButtons(self):
1020
        return QtGui.QDialogButtonBox.Close
1021

1022
    def reject(self):
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
1028
        return True
1029

1030
    def clear(self):
1031
        FreeCADGui.Selection.clearSelection()
1032

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)
1039
            else:
1040
                QtGui.QApplication.clipboard().setText(str(u.Value/u.getUserPreferred()[1]))
1041

1042
    def clipArea(self):
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)
1049
            else:
1050
                QtGui.QApplication.clipboard().setText(str(u.Value/u.getUserPreferred()[1]))
1051

1052
    def newline(self,length=0,area=0):
1053
        FreeCADGui.Selection.clearSelection()
1054
        item = QtGui.QTreeWidgetItem(self.tree)
1055
        if length or area:
1056
            item.setText(0,QtGui.QApplication.translate("Arch", "Total", None))
1057
            item.setToolTip(0,"total")
1058
            f = QtGui.QFont()
1059
            f.setBold(True)
1060
            item.setFont(0,f)
1061
            item.setFont(1,f)
1062
            item.setFont(2,f)
1063
        else:
1064
            item.setText(0,self.descr.text())
1065
            self.descr.setText("")
1066
        self.tree.setCurrentItem(item)
1067
        if length:
1068
            u = FreeCAD.Units.Quantity(length,FreeCAD.Units.Length)
1069
            t = u.getUserPreferred()[0]
1070
            item.setText(1,t)
1071
        if area:
1072
            u = FreeCAD.Units.Quantity(area,FreeCAD.Units.Area)
1073
            t = u.getUserPreferred()[0]
1074
            t = t.replace(u"^2",u"²")
1075
            item.setText(2,t)
1076
        if length or area:
1077
            item = QtGui.QTreeWidgetItem(self.tree)
1078
            self.tree.setCurrentItem(item)
1079

1080
    def update(self,column,txt):
1081
        item = QtGui.QTreeWidgetItem(self.tree)
1082
        self.tree.setCurrentItem(item)
1083
        item.setText(column,txt)
1084

1085
    def setDescr(self,item,col):
1086
        self.descr.setText(item.text(0))
1087

1088
    def setText(self):
1089
        item = self.tree.currentItem()
1090
        if item:
1091
            item.setText(0,self.descr.text())
1092
            self.descr.setText("")
1093

1094
    def exportCSV(self):
1095
        import csv
1096
        rows = self.tree.topLevelItemCount()
1097
        if rows:
1098
            filename = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), translate("Arch","Export CSV File"), None, "CSV file (*.csv)")
1099
            if filename:
1100
                with open(filename[0].encode("utf8"), "w") as csvfile:
1101
                    csvfile = csv.writer(csvfile,delimiter="\t")
1102
                    suml = 0
1103
                    for i in range(rows):
1104
                        item = self.tree.topLevelItem(i)
1105
                        row = []
1106
                        row.append(item.text(0))
1107
                        if item.text(1):
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)+")")
1111
                            else:
1112
                                row.append(u.Value/u.getUserPreferred()[1])
1113
                            row.append(u.getUserPreferred()[2])
1114
                        else:
1115
                            row.extend(["",""])
1116
                        if item.text(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)+")")
1121
                            else:
1122
                                row.append(u.Value/u.getUserPreferred()[1])
1123
                            row.append(u.getUserPreferred()[2])
1124
                        else:
1125
                            row.extend(["",""])
1126
                        csvfile.writerow(row)
1127
                        if item.toolTip(0) == "total":
1128
                            suml = i+1
1129
                print("successfully exported ",filename[0])
1130

1131

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"))
1137
    else:
1138
        d = obj.IfcData
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")
1143
            else:
1144
                d["FlagForceBrep"] = "True"
1145
                FreeCAD.Console.PrintMessage(translate("Arch","Enabling B-rep force flag of object")+" "+obj.Label+"\n")
1146
        else:
1147
            d["FlagForceBrep"] = "True"
1148
            FreeCAD.Console.PrintMessage(translate("Arch","Enabling B-rep force flag of object")+" "+obj.Label+"\n")
1149
        obj.IfcData = d
1150

1151

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"""
1155
    import FreeCADGui
1156
    import Part
1157
    so = []
1158
    if not objects:
1159
        objects = FreeCADGui.Selection.getSelectionEx()
1160
    if not isinstance(objects,list):
1161
        objects = [objects]
1162
    for o in objects:
1163
        so.extend(o.SubObjects)
1164
    if so:
1165
        c = Part.makeCompound(so)
1166
        Part.show(c)
1167

1168

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"""
1172
    import FreeCAD
1173
    import FreeCADGui
1174
    if not objects:
1175
        objects = FreeCADGui.Selection.getSelection()
1176
    if not isinstance(objects,list):
1177
        objects = [objects]
1178
    for obj in objects:
1179
        if hasattr(obj,'Shape'):
1180
            if hasattr(obj,"Base"):
1181
                if 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()
1188

1189

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."""
1193
    import FreeCAD
1194
    import Part
1195
    if not objects and FreeCAD.GuiUp:
1196
        objects = FreeCADGui.Selection.getSelection()
1197
    if not isinstance(objects,list):
1198
        objects = [objects]
1199
    for obj in objects:
1200
        success = False
1201
        if hasattr(obj,'Shape'):
1202
            if hasattr(obj,"Base"):
1203
                if obj.Base:
1204
                    try:
1205
                        print("Attempting to rebuild ", obj.Label)
1206
                        base = obj.Base.getLinkedObject()
1207
                        if base.isDerivedFrom("Part::Feature"):
1208
                            if not base.Shape.isNull():
1209
                                faces = []
1210
                                for f in base.Shape.Faces:
1211
                                    f2 = Part.Face(f.Wires)
1212
                                    #print("rebuilt face: isValid is ", f2.isValid())
1213
                                    faces.append(f2)
1214
                                if faces:
1215
                                    shell = Part.Shell(faces)
1216
                                    if shell:
1217
                                        #print("rebuilt shell: isValid is ", shell.isValid())
1218
                                        solid = Part.Solid(shell)
1219
                                        if solid:
1220
                                            if not solid.isValid():
1221
                                                solid.sewShape()
1222
                                                solid = Part.Solid(solid)
1223
                                            #print("rebuilt solid: isValid is ",solid.isValid())
1224
                                            if solid.isValid():
1225
                                                base.Shape = solid
1226
                                                success = True
1227
                    except Exception:
1228
                        pass
1229
        if not success:
1230
            print ("Failed to rebuild a valid solid for object ",obj.Name)
1231
    FreeCAD.ActiveDocument.recompute()
1232

1233

1234
def getExtrusionData(shape,sortmethod="area"):
1235
    """If a shape has been extruded, returns the base face, and extrusion vector.
1236

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
1240

1241
    If this is the case, returns what appears to be the base face, and the vector
1242
    used to make that extrusion.
1243

1244
    The base face is determined based on the sortmethod parameter, which can either
1245
    be:
1246

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
1250

1251
    Parameters
1252
    ----------
1253
    shape: <Part.Shape>
1254
        Shape to examine.
1255
    sortmethod: {"area", "z"}
1256
        Which sorting algorithm to use to determine the base face.
1257

1258
    Returns
1259
    -------
1260
    Extrusion data: list
1261
        Two item list containing the base face, and the vector used to create the
1262
        extrusion. In that order.
1263
    Failure: None
1264
        Returns None if the object does not appear to be an extrusion.
1265
    """
1266

1267
    if shape.isNull():
1268
        return None
1269
    if not shape.Solids:
1270
        return None
1271
    if len(shape.Faces) < 3:
1272
        return None
1273
    # build faces list with normals
1274
    faces = []
1275
    import Part
1276
    for f in shape.Faces:
1277
        try:
1278
            faces.append([f,f.normalAt(0,0)])
1279
        except Part.OCCError:
1280
            return None
1281
    # find opposite normals pairs
1282
    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])
1288
    if not pairs:
1289
        return None
1290
    valids = []
1291
    for pair in pairs:
1292
        hc = [faces[pair[0]][0].hashCode(),faces[pair[1]][0].hashCode()]
1293
        # check if other normals are all at 90 degrees
1294
        ok = True
1295
        for f in faces:
1296
            if f[0].hashCode() not in hc:
1297
                if round(f[1].getAngle(faces[pair[0]][1]),4) != 1.5708:
1298
                    ok = False
1299
        if ok:
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)])
1303
            else:
1304
                valids.append([faces[pair[1]][0],faces[pair[0]][0].CenterOfMass.sub(faces[pair[1]][0].CenterOfMass)])
1305
    if valids:
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)
1311
        else:
1312
            valids.sort(key=lambda v: (v[0].CenterOfMass.sub(sortmethod)).Length)
1313
        return valids[0]
1314
    return None
1315

1316
def printMessage( message ):
1317
    FreeCAD.Console.PrintMessage( message )
1318
    if FreeCAD.GuiUp :
1319
        QtGui.QMessageBox.information( None , "" , message )
1320

1321
def printWarning( message ):
1322
    FreeCAD.Console.PrintMessage( message )
1323
    if FreeCAD.GuiUp :
1324
        QtGui.QMessageBox.warning( None , "" , message )
1325

1326
def makeIfcSpreadsheet(archobj=None):
1327
    ifc_container = None
1328
    for obj in FreeCAD.ActiveDocument.Objects :
1329
        if obj.Name == "IfcPropertiesContainer" :
1330
            ifc_container = obj
1331
    if not ifc_container :
1332
        ifc_container = FreeCAD.ActiveDocument.addObject('App::DocumentObjectGroup','IfcPropertiesContainer')
1333
    import Spreadsheet
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)
1341
    if archobj :
1342
        if hasattr(obj,"IfcProperties") :
1343
            archobj.IfcProperties = ifc_spreadsheet
1344
            return ifc_spreadsheet
1345
        else :
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)
1348
    else :
1349
        return ifc_spreadsheet
1350

1351

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.