FreeCAD

Форк
0
/
ArchWall.py 
1298 строк · 64.8 Кб
1
#***************************************************************************
2
#*   Copyright (c) 2011 Yorik van Havre <yorik@uncreated.net>              *
3
#*                                                                         *
4
#*   This program is free software; you can redistribute it and/or modify  *
5
#*   it under the terms of the GNU Lesser General Public License (LGPL)    *
6
#*   as published by the Free Software Foundation; either version 2 of     *
7
#*   the License, or (at your option) any later version.                   *
8
#*   for detail see the LICENCE text file.                                 *
9
#*                                                                         *
10
#*   This program is distributed in the hope that it will be useful,       *
11
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13
#*   GNU Library General Public License for more details.                  *
14
#*                                                                         *
15
#*   You should have received a copy of the GNU Library General Public     *
16
#*   License along with this program; if not, write to the Free Software   *
17
#*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
18
#*   USA                                                                   *
19
#*                                                                         *
20
#***************************************************************************
21

22
"""This module provides tools to build Wall objects.  Walls are simple
23
objects, usually vertical, typically obtained by giving a thickness to a base
24
line, then extruding it vertically.
25

26
Examples
27
--------
28
TODO put examples here.
29

30
"""
31

32
import FreeCAD,Draft,ArchComponent,DraftVecUtils,ArchCommands,math
33
from FreeCAD import Vector
34
from draftutils import params
35
import ArchSketchObject
36

37
if FreeCAD.GuiUp:
38
    import FreeCADGui
39
    from PySide import QtCore, QtGui
40
    from draftutils.translate import translate
41
    from PySide.QtCore import QT_TRANSLATE_NOOP
42
    import draftguitools.gui_trackers as DraftTrackers
43
else:
44
    # \cond
45
    def translate(ctxt,txt):
46
        return txt
47
    def QT_TRANSLATE_NOOP(ctxt,txt):
48
        return txt
49
    # \endcond
50

51
## @package ArchWall
52
#  \ingroup ARCH
53
#  \brief The Wall object and tools
54
#
55
#  This module provides tools to build Wall objects.  Walls are simple objects,
56
#  usually vertical, typically obtained by giving a thickness to a base line,
57
#  then extruding it vertically.
58

59
__title__  = "FreeCAD Wall"
60
__author__ = "Yorik van Havre"
61
__url__    = "https://www.freecad.org"
62

63

64

65
def mergeShapes(w1,w2):
66
    """Not currently implemented.
67

68
    Return a Shape built on two walls that share same properties and have a
69
    coincident endpoint.
70
    """
71

72
    if not areSameWallTypes([w1,w2]):
73
        return None
74
    if (not hasattr(w1.Base,"Shape")) or (not hasattr(w2.Base,"Shape")):
75
        return None
76
    if w1.Base.Shape.Faces or w2.Base.Shape.Faces:
77
        return None
78

79
    # TODO fix this
80
    return None
81

82
    eds = w1.Base.Shape.Edges + w2.Base.Shape.Edges
83
    import DraftGeomUtils
84
    w = DraftGeomUtils.findWires(eds)
85
    if len(w) == 1:
86
        #print("found common wire")
87
        normal,length,width,height = w1.Proxy.getDefaultValues(w1)
88
        print(w[0].Edges)
89
        sh = w1.Proxy.getBase(w1,w[0],normal,width,height)
90
        print(sh)
91
        return sh
92
    return None
93

94
def areSameWallTypes(walls):
95
    """Check if a list of walls have the same height, width and alignment.
96

97
    Parameters
98
    ----------
99
    walls: list of <ArchComponent.Component>
100

101
    Returns
102
    -------
103
    bool
104
        True if the walls have the same height, width and alignment, False if
105
        otherwise.
106
    """
107

108
    for att in ["Width","Height","Align"]:
109
        value = None
110
        for w in walls:
111
            if not hasattr(w,att):
112
                return False
113
            if not value:
114
                value = getattr(w,att)
115
            else:
116
                if type(value) == float:
117
                    if round(value,Draft.precision()) != round(getattr(w,att),Draft.precision()):
118
                        return False
119
                else:
120
                    if value != getattr(w,att):
121
                        return False
122
    return True
123

124

125
class _Wall(ArchComponent.Component):
126
    """The Wall object.
127

128
    Turns a <App::FeaturePython> into a wall object, then uses a
129
    <Part::Feature> to create the wall's shape.
130

131
    Walls are simple objects, usually vertical, typically obtained by giving a
132
    thickness to a base line, then extruding it vertically.
133

134
    Parameters
135
    ----------
136
    obj: <App::FeaturePython>
137
        The object to turn into a wall. Note that this is not the object that
138
        forms the basis for the new wall's shape. That is given later.
139
    """
140

141
    def __init__(self, obj):
142
        ArchComponent.Component.__init__(self, obj)
143
        self.setProperties(obj)
144
        obj.IfcType = "Wall"
145

146
    def setProperties(self, obj):
147
        """Give the wall its wall specific properties, such as its alignment.
148

149
        You can learn more about properties here:
150
        https://wiki.freecad.org/property
151

152
        parameters
153
        ----------
154
        obj: <part::featurepython>
155
            The object to turn into a wall.
156
        """
157

158
        lp = obj.PropertiesList
159
        if not "Length" in lp:
160
            obj.addProperty("App::PropertyLength","Length","Wall",QT_TRANSLATE_NOOP("App::Property","The length of this wall. Not used if this wall is based on an underlying object"))
161
        if not "Width" in lp:
162
            obj.addProperty("App::PropertyLength","Width","Wall",QT_TRANSLATE_NOOP("App::Property","The width of this wall. Not used if this wall is based on a face. Disabled and ignored if Base object (ArchSketch) provides the information."))
163

164
        # To be combined into Width when PropertyLengthList is available
165
        if not "OverrideWidth" in lp:
166
            obj.addProperty("App::PropertyFloatList","OverrideWidth","Wall",QT_TRANSLATE_NOOP("App::Property","This overrides Width attribute to set width of each segment of wall.  Disabled and ignored if Base object (ArchSketch) provides Widths information, with getWidths() method  (If a value is zero, the value of 'Width' will be followed).  [ENHANCEMENT by ArchSketch] GUI 'Edit Wall Segment Width' Tool is provided in external SketchArch Add-on to let users to set the values interactively.  'Toponaming-Tolerant' if ArchSketch is used in Base (and SketchArch Add-on is installed).  Warning : Not 'Toponaming-Tolerant' if just Sketch is used."))			# see DraftGeomUtils.offsetwire()
167
        if not "OverrideAlign" in lp:
168
            obj.addProperty("App::PropertyStringList","OverrideAlign","Wall",QT_TRANSLATE_NOOP("App::Property","This overrides Align attribute to set align of each segment of wall.  Disabled and ignored if Base object (ArchSketch) provides Aligns information, with getAligns() method  (If a value is not 'Left, Right, Center', the value of 'Align' will be followed).  [ENHANCEMENT by ArchSketch] GUI 'Edit Wall Segment Align' Tool is provided in external SketchArch Add-on to let users to set the values interactively.  'Toponaming-Tolerant' if ArchSketch is used in Base (and SketchArch Add-on is installed).  Warning : Not 'Toponaming-Tolerant' if just Sketch is used."))			# see DraftGeomUtils.offsetwire()
169
        if not "OverrideOffset" in lp:
170
            obj.addProperty("App::PropertyFloatList","OverrideOffset","Wall",QT_TRANSLATE_NOOP("App::Property","This overrides Offset attribute to set offset of each segment of wall.  Disabled and ignored if Base object (ArchSketch) provides Offsets information, with getOffsets() method  (If a value is zero, the value of 'Offset' will be followed).  [ENHANCED by ArchSketch] GUI 'Edit Wall Segment Offset' Tool is provided in external Add-on ('SketchArch') to let users to select the edges interactively.  'Toponaming-Tolerant' if ArchSketch is used in Base (and SketchArch Add-on is installed).  Warning : Not 'Toponaming-Tolerant' if just Sketch is used. Property is ignored if Base ArchSketch provided the selected edges. "))			# see DraftGeomUtils.offsetwire()
171
        if not "Height" in lp:
172
            obj.addProperty("App::PropertyLength","Height","Wall",QT_TRANSLATE_NOOP("App::Property","The height of this wall. Keep 0 for automatic. Not used if this wall is based on a solid"))
173
        if not "Area" in lp:
174
            obj.addProperty("App::PropertyArea","Area","Wall",QT_TRANSLATE_NOOP("App::Property","The area of this wall as a simple Height * Length calculation"))
175
            obj.setEditorMode("Area",1)
176
        if not "Align" in lp:
177
            obj.addProperty("App::PropertyEnumeration","Align","Wall",QT_TRANSLATE_NOOP("App::Property","The alignment of this wall on its base object, if applicable. Disabled and ignored if Base object (ArchSketch) provides the information."))
178
            obj.Align = ['Left','Right','Center']
179
        if not "Normal" in lp:
180
            obj.addProperty("App::PropertyVector","Normal","Wall",QT_TRANSLATE_NOOP("App::Property","The normal extrusion direction of this object (keep (0,0,0) for automatic normal)"))
181
        if not "Face" in lp:
182
            obj.addProperty("App::PropertyInteger","Face","Wall",QT_TRANSLATE_NOOP("App::Property","The face number of the base object used to build this wall"))
183
        if not "Offset" in lp:
184
            obj.addProperty("App::PropertyDistance","Offset","Wall",QT_TRANSLATE_NOOP("App::Property","The offset between this wall and its baseline (only for left and right alignments). Disabled and ignored if Base object (ArchSketch) provides the information."))
185

186
        # See getExtrusionData(), removeSplitters are no longer used
187
        #if not "Refine" in lp:
188
        #    obj.addProperty("App::PropertyEnumeration","Refine","Wall",QT_TRANSLATE_NOOP("App::Property","Select whether or not and the method to remove splitter of the Wall. Currently Draft removeSplitter and Part removeSplitter available but may not work on complex sketch."))
189
        #    obj.Refine = ['No','DraftRemoveSplitter','PartRemoveSplitter']
190
        # TODO - To implement in Arch Component ?
191

192
        if not "MakeBlocks" in lp:
193
            obj.addProperty("App::PropertyBool","MakeBlocks","Blocks",QT_TRANSLATE_NOOP("App::Property","Enable this to make the wall generate blocks"))
194
        if not "BlockLength" in lp:
195
            obj.addProperty("App::PropertyLength","BlockLength","Blocks",QT_TRANSLATE_NOOP("App::Property","The length of each block"))
196
        if not "BlockHeight" in lp:
197
            obj.addProperty("App::PropertyLength","BlockHeight","Blocks",QT_TRANSLATE_NOOP("App::Property","The height of each block"))
198
        if not "OffsetFirst" in lp:
199
            obj.addProperty("App::PropertyLength","OffsetFirst","Blocks",QT_TRANSLATE_NOOP("App::Property","The horizontal offset of the first line of blocks"))
200
        if not "OffsetSecond" in lp:
201
            obj.addProperty("App::PropertyLength","OffsetSecond","Blocks",QT_TRANSLATE_NOOP("App::Property","The horizontal offset of the second line of blocks"))
202
        if not "Joint" in lp:
203
            obj.addProperty("App::PropertyLength","Joint","Blocks",QT_TRANSLATE_NOOP("App::Property","The size of the joints between each block"))
204
        if not "CountEntire" in lp:
205
            obj.addProperty("App::PropertyInteger","CountEntire","Blocks",QT_TRANSLATE_NOOP("App::Property","The number of entire blocks"))
206
            obj.setEditorMode("CountEntire",1)
207
        if not "CountBroken" in lp:
208
            obj.addProperty("App::PropertyInteger","CountBroken","Blocks",QT_TRANSLATE_NOOP("App::Property","The number of broken blocks"))
209
            obj.setEditorMode("CountBroken",1)
210
        if not "ArchSketchData" in lp:
211
            obj.addProperty("App::PropertyBool","ArchSketchData","Wall",QT_TRANSLATE_NOOP("App::Property","Use Base ArchSketch (if used) data (e.g. widths, aligns, offsets) instead of Wall's properties"))
212
            obj.ArchSketchData = True
213

214
        self.Type = "Wall"
215

216
    def onDocumentRestored(self,obj):
217
        """Method run when the document is restored. Re-adds the Arch component, and Arch wall properties."""
218

219
        ArchComponent.Component.onDocumentRestored(self,obj)
220
        self.setProperties(obj)
221

222
        if hasattr(obj,"ArchSketchData") and obj.ArchSketchData and Draft.getType(obj.Base) == "ArchSketch":
223
            if hasattr(obj,"Width"):
224
                obj.setEditorMode("Width", ["ReadOnly"])
225
            if hasattr(obj,"Align"):
226
                obj.setEditorMode("Align", ["ReadOnly"])
227
            if hasattr(obj,"Offset"):
228
                obj.setEditorMode("Offset", ["ReadOnly"])
229
            if hasattr(obj,"OverrideWidth"):
230
                obj.setEditorMode("OverrideWidth", ["ReadOnly"])
231
            if hasattr(obj,"OverrideAlign"):
232
                obj.setEditorMode("OverrideAlign", ["ReadOnly"])
233
            if hasattr(obj,"OverrideOffset"):
234
                obj.setEditorMode("OverrideOffset", ["ReadOnly"])
235
        else:
236
            if hasattr(obj,"Width"):
237
                obj.setEditorMode("Width", 0)
238
            if hasattr(obj,"Align"):
239
                obj.setEditorMode("Align", 0)
240
            if hasattr(obj,"Offset"):
241
                obj.setEditorMode("Offset", 0)
242
            if hasattr(obj,"OverrideWidth"):
243
                obj.setEditorMode("OverrideWidth", 0)
244
            if hasattr(obj,"OverrideAlign"):
245
                obj.setEditorMode("OverrideAlign", 0)
246
            if hasattr(obj,"OverrideOffset"):
247
                obj.setEditorMode("OverrideOffset", 0)
248

249
    def execute(self,obj):
250
        """Method run when the object is recomputed.
251

252
        Extrude the wall from the Base shape if possible. Processe additions
253
        and subtractions. Assign the resulting shape as the shape of the wall.
254

255
        Add blocks if the MakeBlocks property is assigned. If the Base shape is
256
        a mesh, just copy the mesh.
257
        """
258

259
        if self.clone(obj):
260
            return
261

262
        import Part
263
        import DraftGeomUtils
264
        base = None
265
        pl = obj.Placement
266
        extdata = self.getExtrusionData(obj)
267
        if extdata:
268
            bplates = extdata[0]
269
            extv = extdata[2].Rotation.multVec(extdata[1])
270
            if isinstance(bplates,list):
271
                shps = []
272
                # Test : if base is Sketch, then fuse all solid; otherwise, makeCompound
273
                sketchBaseToFuse = obj.Base.getLinkedObject().isDerivedFrom("Sketcher::SketchObject")
274
                # but turn this off if we have layers, otherwise layers get merged
275
                if hasattr(obj,"Material") and obj.Material \
276
                and hasattr(obj.Material,"Materials") and obj.Material.Materials:
277
                    sketchBaseToFuse = False
278
                for b in bplates:
279
                    b.Placement = extdata[2].multiply(b.Placement)
280
                    b = b.extrude(extv)
281

282
                    # See getExtrusionData() - not fusing baseplates there - fuse solids here
283
                    # Remarks - If solids are fused, but exportIFC.py use underlying baseplates w/o fuse, the result in ifc look slightly different from in FC.
284

285
                    if sketchBaseToFuse:
286
                        if shps:
287
                            shps = shps.fuse(b) #shps.fuse(b)
288
                        else:
289
                            shps=b
290
                    else:
291
                        shps.append(b)
292
                    # TODO - To let user to select whether to fuse (slower) or to do a compound (faster) only ?
293

294
                if sketchBaseToFuse:
295
                    base = shps
296
                else:
297
                    base = Part.makeCompound(shps)
298
            else:
299
                bplates.Placement = extdata[2].multiply(bplates.Placement)
300
                base = bplates.extrude(extv)
301
        if obj.Base:
302
            if hasattr(obj.Base,'Shape'):
303
                if obj.Base.Shape.isNull():
304
                    return
305
                if not obj.Base.Shape.isValid():
306
                    if not obj.Base.Shape.Solids:
307
                        # let pass invalid objects if they have solids...
308
                        return
309
                elif obj.Base.Shape.Solids:
310
                    base = Part.Shape(obj.Base.Shape)
311
                # blocks calculation
312
                elif hasattr(obj,"MakeBlocks") and hasattr(self,"basewires"):
313
                    if obj.MakeBlocks and self.basewires and extdata and obj.Width and obj.Height:
314
                        #print "calculating blocks"
315
                        if len(self.basewires) == 1:
316
                            blocks = []
317
                            n = FreeCAD.Vector(extv)
318
                            n.normalize()
319
                            cuts1 = []
320
                            cuts2 = []
321
                            if obj.BlockLength.Value:
322
                                for i in range(2):
323
                                    if i == 0:
324
                                        offset = obj.OffsetFirst.Value
325
                                    else:
326
                                        offset = obj.OffsetSecond.Value
327

328
                                    # only 1 wire (first) is supported
329
                                    # TODO - Can support multiple wires?
330

331
                                    # self.basewires was list of list of edges,
332
                                    # no matter Base is DWire, Sketch or else
333
                                    # See discussion - https://forum.freecad.org/viewtopic.php?t=86365
334
                                    baseEdges = self.basewires[0]
335

336
                                    for edge in baseEdges:
337
                                        while offset < (edge.Length-obj.Joint.Value):
338
                                            #print i," Edge ",edge," : ",edge.Length," - ",offset
339
                                            if offset:
340
                                                t = edge.tangentAt(offset)
341
                                                p = t.cross(n)
342
                                                p.multiply(1.1*obj.Width.Value+obj.Offset.Value)
343
                                                p1 = edge.valueAt(offset).add(p)
344
                                                p2 = edge.valueAt(offset).add(p.negative())
345
                                                sh = Part.LineSegment(p1,p2).toShape()
346
                                                if obj.Joint.Value:
347
                                                    sh = sh.extrude(-t.multiply(obj.Joint.Value))
348
                                                sh = sh.extrude(n)
349
                                                if i == 0:
350
                                                    cuts1.append(sh)
351
                                                else:
352
                                                    cuts2.append(sh)
353
                                            offset += (obj.BlockLength.Value + obj.Joint.Value)
354
                                        offset -= edge.Length
355

356
                            if isinstance(bplates,list):
357
                                bplates = bplates[0]
358
                            if obj.BlockHeight.Value:
359
                                fsize = obj.BlockHeight.Value + obj.Joint.Value
360
                                bh = obj.BlockHeight.Value
361
                            else:
362
                                fsize = obj.Height.Value
363
                                bh = obj.Height.Value
364
                            bvec = FreeCAD.Vector(n)
365
                            bvec.multiply(bh)
366
                            svec = FreeCAD.Vector(n)
367
                            svec.multiply(fsize)
368
                            if cuts1:
369
                                plate1 = bplates.cut(cuts1).Faces
370
                            else:
371
                                plate1 = bplates.Faces
372
                            blocks1 = Part.makeCompound([f.extrude(bvec) for f in plate1])
373
                            if cuts2:
374
                                plate2 = bplates.cut(cuts2).Faces
375
                            else:
376
                                plate2 = bplates.Faces
377
                            blocks2 = Part.makeCompound([f.extrude(bvec) for f in plate2])
378
                            interval = extv.Length/(fsize)
379
                            entire = int(interval)
380
                            rest = (interval - entire)
381
                            for i in range(entire):
382
                                if i % 2: # odd
383
                                    b = Part.Shape(blocks2)
384
                                else:
385
                                    b = Part.Shape(blocks1)
386
                                if i:
387
                                    t = FreeCAD.Vector(svec)
388
                                    t.multiply(i)
389
                                    b.translate(t)
390
                                blocks.append(b)
391
                            if rest:
392
                                rest = extv.Length - (entire * fsize)
393
                                rvec = FreeCAD.Vector(n)
394
                                rvec.multiply(rest)
395
                                if entire % 2:
396
                                    b = Part.makeCompound([f.extrude(rvec) for f in plate2])
397
                                else:
398
                                    b = Part.makeCompound([f.extrude(rvec) for f in plate1])
399
                                t = FreeCAD.Vector(svec)
400
                                t.multiply(entire)
401
                                b.translate(t)
402
                                blocks.append(b)
403
                            if blocks:
404
                                base = Part.makeCompound(blocks)
405

406
                        else:
407
                            FreeCAD.Console.PrintWarning(translate("Arch","Cannot compute blocks for wall")+obj.Label+"\n")
408

409
            elif obj.Base.isDerivedFrom("Mesh::Feature"):
410
                if obj.Base.Mesh.isSolid():
411
                    if obj.Base.Mesh.countComponents() == 1:
412
                        sh = ArchCommands.getShapeFromMesh(obj.Base.Mesh)
413
                        if sh.isClosed() and sh.isValid() and sh.Solids and (not sh.isNull()):
414
                            base = sh
415
                        else:
416
                            FreeCAD.Console.PrintWarning(translate("Arch","This mesh is an invalid solid")+"\n")
417
                            obj.Base.ViewObject.show()
418
        if not base:
419
            #FreeCAD.Console.PrintError(translate("Arch","Error: Invalid base object")+"\n")
420
            #return
421
            # walls can be made of only a series of additions and have no base shape
422
            base = Part.Shape()
423

424
        base = self.processSubShapes(obj,base,pl)
425

426
        self.applyShape(obj,base,pl)
427

428
        # count blocks
429
        if hasattr(obj,"MakeBlocks"):
430
            if obj.MakeBlocks:
431
                fvol = obj.BlockLength.Value * obj.BlockHeight.Value * obj.Width.Value
432
                if fvol:
433
                    #print("base volume:",fvol)
434
                    #for s in base.Solids:
435
                        #print(abs(s.Volume - fvol))
436
                    ents = [s for s in base.Solids if abs(s.Volume - fvol) < 1]
437
                    obj.CountEntire = len(ents)
438
                    obj.CountBroken = len(base.Solids) - len(ents)
439
                else:
440
                    obj.CountEntire = 0
441
                    obj.CountBroken = 0
442

443
        # set the length property
444
        if obj.Base:
445
            if hasattr(obj.Base,'Shape'):
446
                if obj.Base.Shape.Edges:
447
                    if not obj.Base.Shape.Faces:
448
                        if hasattr(obj.Base.Shape,"Length"):
449
                            l = obj.Base.Shape.Length
450
                            if obj.Length.Value != l:
451
                                obj.Length = l
452
                                self.oldLength = None # delete the stored value to prevent triggering base change below
453

454
        # set the Area property
455
        obj.Area = obj.Length.Value * obj.Height.Value
456

457
    def onBeforeChange(self,obj,prop):
458
        """Method called before the object has a property changed.
459

460
        Specifically, this method is called before the value changes.
461

462
        If "Length" has changed, record the old length so that .onChanged() can
463
        be sure that the base needs to be changed.
464

465
        Also call ArchComponent.Component.onBeforeChange().
466

467
        Parameters
468
        ----------
469
        prop: string
470
            The name of the property that has changed.
471
        """
472

473
        if prop == "Length":
474
            self.oldLength = obj.Length.Value
475
        ArchComponent.Component.onBeforeChange(self,obj,prop)
476

477
    def onChanged(self, obj, prop):
478
        """Method called when the object has a property changed.
479

480
        If length has changed, extend the length of the Base object, if the
481
        Base object only has a single edge to extend.
482

483
        Also hide subobjects.
484

485
        Also call ArchComponent.Component.onChanged().
486

487
        Parameters
488
        ----------
489
        prop: string
490
            The name of the property that has changed.
491
        """
492

493
        if prop == "Length":
494
            if (obj.Base and obj.Length.Value
495
                    and hasattr(self,"oldLength") and (self.oldLength is not None)
496
                    and (self.oldLength != obj.Length.Value)):
497

498
                if hasattr(obj.Base,'Shape'):
499
                    if len(obj.Base.Shape.Edges) == 1:
500
                        import DraftGeomUtils
501
                        e = obj.Base.Shape.Edges[0]
502
                        if DraftGeomUtils.geomType(e) == "Line":
503
                            if e.Length != obj.Length.Value:
504
                                v = e.Vertexes[-1].Point.sub(e.Vertexes[0].Point)
505
                                v.normalize()
506
                                v.multiply(obj.Length.Value)
507
                                p2 = e.Vertexes[0].Point.add(v)
508
                                if Draft.getType(obj.Base) == "Wire":
509
                                    #print "modifying p2"
510
                                    obj.Base.End = p2
511
                                elif Draft.getType(obj.Base) in ["Sketcher::SketchObject", "ArchSketch"]:
512
                                    try:
513
                                        obj.Base.recompute() # Fix for the 'GeoId index out range' error.
514
                                        obj.Base.movePoint(0, 2, obj.Base.Placement.inverse().multVec(p2))
515
                                    except Exception: # This 'GeoId index out range' error should no longer occur.
516
                                        print("Debug: The base sketch of this wall could not be changed, because the sketch has not been edited yet in this session (this is a bug in FreeCAD). Try entering and exiting edit mode in this sketch first, and then changing the wall length should work.")
517
                                else:
518
                                    FreeCAD.Console.PrintError(translate("Arch","Error: Unable to modify the base object of this wall")+"\n")
519

520
        if hasattr(obj,"ArchSketchData") and obj.ArchSketchData and Draft.getType(obj.Base) == "ArchSketch":
521
            if hasattr(obj,"Width"):
522
                obj.setEditorMode("Width", ["ReadOnly"])
523
            if hasattr(obj,"Align"):
524
                obj.setEditorMode("Align", ["ReadOnly"])
525
            if hasattr(obj,"Offset"):
526
                obj.setEditorMode("Offset", ["ReadOnly"])
527
            if hasattr(obj,"OverrideWidth"):
528
                obj.setEditorMode("OverrideWidth", ["ReadOnly"])
529
            if hasattr(obj,"OverrideAlign"):
530
                obj.setEditorMode("OverrideAlign", ["ReadOnly"])
531
            if hasattr(obj,"OverrideOffset"):
532
                obj.setEditorMode("OverrideOffset", ["ReadOnly"])
533
        else:
534
            if hasattr(obj,"Width"):
535
                obj.setEditorMode("Width", 0)
536
            if hasattr(obj,"Align"):
537
                obj.setEditorMode("Align", 0)
538
            if hasattr(obj,"Offset"):
539
                obj.setEditorMode("Offset", 0)
540
            if hasattr(obj,"OverrideWidth"):
541
                obj.setEditorMode("OverrideWidth", 0)
542
            if hasattr(obj,"OverrideAlign"):
543
                obj.setEditorMode("OverrideAlign", 0)
544
            if hasattr(obj,"OverrideOffset"):
545
                obj.setEditorMode("OverrideOffset", 0)
546

547
        self.hideSubobjects(obj,prop)
548
        ArchComponent.Component.onChanged(self,obj,prop)
549

550
    def getFootprint(self,obj):
551
        """Get the faces that make up the base/foot of the wall.
552

553
        Returns
554
        -------
555
        list of <Part.Face>
556
            The faces that make up the foot of the wall.
557
        """
558

559
        faces = []
560
        if obj.Shape:
561
            for f in obj.Shape.Faces:
562
                if f.normalAt(0,0).getAngle(FreeCAD.Vector(0,0,-1)) < 0.01:
563
                    if abs(abs(f.CenterOfMass.z) - abs(obj.Shape.BoundBox.ZMin)) < 0.001:
564
                        faces.append(f)
565
        return faces
566

567
    def getExtrusionData(self,obj):
568
        """Get data needed to extrude the wall from a base object.
569

570
        take the Base object, and find a base face to extrude
571
        out, a vector to define the extrusion direction and distance.
572

573
        Rebase the base face to the (0,0,0) origin.
574

575
        Return the base face, rebased, with the extrusion vector, and the
576
        <Base.Placement> needed to return the face back to its original
577
        position.
578

579
        Returns
580
        -------
581
        tuple of (<Part.Face>, <Base.Vector>, <Base.Placement>)
582
            Tuple containing the base face, the vector for extrusion, and the
583
            placement needed to move the face back from the (0,0,0) origin.
584
        """
585

586
        import Part
587
        import DraftGeomUtils
588

589
        # If ArchComponent.Component.getExtrusionData() can successfully get
590
        # extrusion data, just use that.
591
        data = ArchComponent.Component.getExtrusionData(self,obj)
592
        if data:
593
            if not isinstance(data[0],list):
594
                # multifuses not considered here
595
                return data
596
        length  = obj.Length.Value
597

598
        # TODO currently layers were not supported when len(basewires) > 0	##( or 1 ? )
599
        width = 0
600

601
        # Get width of each edge segment from Base Objects if they store it
602
        # (Adding support in SketchFeaturePython, DWire...)
603
        widths = []  # [] or None are both False
604
        if hasattr(obj,"ArchSketchData") and obj.ArchSketchData and Draft.getType(obj.Base) == "ArchSketch":
605
            if hasattr(obj.Base, 'Proxy'):
606
                if hasattr(obj.Base.Proxy, 'getWidths'):
607
                    # Return a list of Width corresponding to indexes of sorted
608
                    # edges of Sketch.
609
                    widths = obj.Base.Proxy.getWidths(obj.Base)
610

611
        # Get width of each edge/wall segment from ArchWall.OverrideWidth if
612
        # Base Object does not provide it
613
        if not widths:
614
            if obj.OverrideWidth:
615
                if obj.Base and obj.Base.isDerivedFrom("Sketcher::SketchObject"):
616
                    # If Base Object is ordinary Sketch (or when ArchSketch.getWidth() not implemented yet):-
617
                    # sort the width list in OverrrideWidth to correspond to indexes of sorted edges of Sketch
618
                    try:
619
                        import ArchSketchObject
620
                    except Exception:
621
                        print("ArchSketchObject add-on module is not installed yet")
622
                    try:
623
                        widths = ArchSketchObject.sortSketchWidth(obj.Base, obj.OverrideWidth)
624
                    except Exception:
625
                        widths = obj.OverrideWidth
626
                else:
627
                    # If Base Object is not Sketch, but e.g. DWire, the width
628
                    # list in OverrrideWidth just correspond to sequential
629
                    # order of edges
630
                    widths = obj.OverrideWidth
631
            elif obj.Width:
632
                widths = [obj.Width.Value]
633
            else:
634
                # having no width is valid for walls so the user doesn't need to be warned
635
                # it just disables extrusions and return none
636
                #print ("Width & OverrideWidth & base.getWidths() should not be all 0 or None or [] empty list ")
637
                return None
638

639
        # Set 'default' width - for filling in any item in the list == 0 or None
640
        if obj.Width.Value:
641
            width = obj.Width.Value
642
        else:
643
            width = 200  # 'Default' width value
644

645
        # Get align of each edge segment from Base Objects if they store it.
646
        # (Adding support in SketchFeaturePython, DWire...)
647
        aligns = []
648
        if hasattr(obj,"ArchSketchData") and obj.ArchSketchData and Draft.getType(obj.Base) == "ArchSketch":
649
            if hasattr(obj.Base, 'Proxy'):
650
                if hasattr(obj.Base.Proxy, 'getAligns'):
651
                    # Return a list of Align corresponds to indexes of sorted
652
                    # edges of Sketch.
653
                    aligns = obj.Base.Proxy.getAligns(obj.Base)
654
        # Get align of each edge/wall segment from ArchWall.OverrideAlign if
655
        # Base Object does not provide it
656
        if not aligns:
657
            if obj.OverrideAlign:
658
                if obj.Base and obj.Base.isDerivedFrom("Sketcher::SketchObject"):
659
                    # If Base Object is ordinary Sketch (or when
660
                    # ArchSketch.getAligns() not implemented yet):- sort the
661
                    # align list in OverrideAlign to correspond to indexes of
662
                    # sorted edges of Sketch
663
                    try:
664
                        import ArchSketchObject
665
                    except Exception:
666
                        print("ArchSketchObject add-on module is not installed yet")
667
                    try:
668
                        aligns = ArchSketchObject.sortSketchAlign(obj.Base, obj.OverrideAlign)
669
                    except Exception:
670
                        aligns = obj.OverrideAlign
671
                else:
672
                    # If Base Object is not Sketch, but e.g. DWire, the align
673
                    # list in OverrideAlign just correspond to sequential order
674
                    # of edges
675
                    aligns = obj.OverrideAlign
676
            else:
677
                aligns = [obj.Align]
678

679
        # set 'default' align - for filling in any item in the list == 0 or None
680
        align = obj.Align  # or aligns[0]
681

682
        # Get offset of each edge segment from Base Objects if they store it
683
        # (Adding support in SketchFeaturePython, DWire...)
684
        offsets = []  # [] or None are both False
685
        if hasattr(obj,"ArchSketchData") and obj.ArchSketchData and Draft.getType(obj.Base) == "ArchSketch":
686

687
            if hasattr(obj.Base, 'Proxy'):
688
                if hasattr(obj.Base.Proxy, 'getOffsets'):
689
                    # Return a list of Offset corresponding to indexes of sorted
690
                    # edges of Sketch.
691
                    offsets = obj.Base.Proxy.getOffsets(obj.Base)
692
        # Get offset of each edge/wall segment from ArchWall.OverrideOffset if
693
        # Base Object does not provide it
694
        if not offsets:
695
            if obj.OverrideOffset:
696
                if obj.Base and obj.Base.isDerivedFrom("Sketcher::SketchObject"):
697
                    # If Base Object is ordinary Sketch (or when ArchSketch.getOffsets() not implemented yet):-
698
                    # sort the offset list in OverrideOffset to correspond to indexes of sorted edges of Sketch
699
                    if hasattr(ArchSketchObject, 'sortSketchOffset'):
700
                        offsets = ArchSketchObject.sortSketchOffset(obj.Base, obj.OverrideOffset)
701
                    else:
702
                        offsets = obj.OverrideOffset
703
                else:
704
                    # If Base Object is not Sketch, but e.g. DWire, the width
705
                    # list in OverrrideWidth just correspond to sequential
706
                    # order of edges
707
                    offsets = obj.OverrideOffset
708
            elif obj.Offset:
709
                offsets = [obj.Offset.Value]
710

711
        # Set 'default' offset - for filling in any item in the list == 0 or None
712
        offset = obj.Offset.Value  # could be 0
713

714
        height = obj.Height.Value
715
        if not height:
716
            height = self.getParentHeight(obj)
717
        if not height:
718
            return None
719
        if obj.Normal == Vector(0,0,0):
720
            if obj.Base:
721
                normal = DraftGeomUtils.get_shape_normal(obj.Base.Shape)
722
                if normal is None:
723
                    normal = Vector(0,0,1)
724
            else:
725
                normal = Vector(0,0,1)
726
        else:
727
            normal = Vector(obj.Normal)
728
        base = None
729
        placement = None
730
        self.basewires = None
731

732
        # build wall layers
733
        layers = []
734
        if hasattr(obj,"Material"):
735
            if obj.Material:
736
                if hasattr(obj.Material,"Materials"):
737
                    thicknesses = [abs(t) for t in obj.Material.Thicknesses]
738
                    # multimaterials
739
                    varwidth = 0
740
                    restwidth = width - sum(thicknesses)
741
                    if restwidth > 0:
742
                        varwidth = [t for t in thicknesses if t == 0]
743
                        if varwidth:
744
                            varwidth = restwidth/len(varwidth)
745
                    for t in obj.Material.Thicknesses:
746
                        if t:
747
                            layers.append(t)
748
                        elif varwidth:
749
                            layers.append(varwidth)
750

751
        if obj.Base:
752
            if hasattr(obj.Base,'Shape'):
753
                if obj.Base.Shape:
754
                    if obj.Base.Shape.Solids:
755
                        return None
756

757
                    # If the user has defined a specific face of the Base
758
                    # object to build the wall from, extrude from that face,
759
                    # and return the extrusion moved to (0,0,0), normal of the
760
                    # face, and placement to move the extrusion back to its
761
                    # original position.
762
                    elif obj.Face > 0:
763
                        if len(obj.Base.Shape.Faces) >= obj.Face:
764
                            face = obj.Base.Shape.Faces[obj.Face-1]
765
                            if obj.Normal != Vector(0,0,0):
766
                                normal = face.normalAt(0,0)
767
                            if normal.getAngle(Vector(0,0,1)) > math.pi/4:
768
                                normal.multiply(width)
769
                                base = face.extrude(normal)
770
                                if obj.Align == "Center":
771
                                    base.translate(normal.negative().multiply(0.5))
772
                                elif obj.Align == "Right":
773
                                    base.translate(normal.negative())
774
                            else:
775
                                normal.multiply(height)
776
                                base = face.extrude(normal)
777
                            base, placement = self.rebase(base)
778
                            return (base,normal,placement)
779

780
                    # If the Base has faces, but no specific one has been
781
                    # selected, rebase the faces and continue.
782
                    elif obj.Base.Shape.Faces:
783
                        if not DraftGeomUtils.isCoplanar(obj.Base.Shape.Faces):
784
                            return None
785
                        else:
786
                            base,placement = self.rebase(obj.Base.Shape)
787

788
                    elif hasattr(obj.Base, 'Proxy') and obj.ArchSketchData and \
789
                    hasattr(obj.Base.Proxy, 'getWallBaseShapeEdgesInfo'):
790

791
                        wallBaseShapeEdgesInfo = obj.Base.Proxy.getWallBaseShapeEdgesInfo(obj.Base)
792
                        #get wall edges (not wires); use original edges if getWallBaseShapeEdgesInfo() provided none
793
                        if wallBaseShapeEdgesInfo:
794
                            self.basewires = wallBaseShapeEdgesInfo.get('wallAxis')  # 'wallEdges'  # widths, aligns, offsets?
795

796
                    # Sort Sketch edges consistently with below procedures
797
                    # without using Sketch.Shape.Edges - found the latter order
798
                    # in some corner case != getSortedClusters()
799
                    elif obj.Base.isDerivedFrom("Sketcher::SketchObject"):
800
                        self.basewires = []
801
                        skGeom = obj.Base.GeometryFacadeList
802
                        skGeomEdges = []
803
                        skPlacement = obj.Base.Placement  # Get Sketch's placement to restore later
804
                        for i in skGeom:
805
                            if not i.Construction:
806
                                # support Line, Arc, Circle, Ellipse for Sketch as Base at the moment
807
                                if isinstance(i.Geometry, (Part.LineSegment, Part.Circle, Part.ArcOfCircle, Part.Ellipse)):
808
                                    skGeomEdgesI = i.Geometry.toShape()
809
                                    skGeomEdges.append(skGeomEdgesI)
810
                        for cluster in Part.getSortedClusters(skGeomEdges):
811
                            clusterTransformed = []
812
                            for edge in cluster:
813
                                # TODO 2023.11.26: Multiplication order should be switched?
814
                                # So far 'no problem' as 'edge.placement' is always '0,0,0' ?
815
                                edge.Placement = edge.Placement.multiply(skPlacement)  ## TODO add attribute to skip Transform...
816

817
                                clusterTransformed.append(edge)
818
                            # Only use cluster of edges rather than turning into wire
819
                            self.basewires.append(clusterTransformed)
820

821
                        # Use Sketch's Normal for all edges/wires generated
822
                        # from sketch for consistency. Discussion on checking
823
                        # normal of sketch.Placement vs
824
                        # sketch.getGlobalPlacement() -
825
                        # https://forum.freecad.org/viewtopic.php?f=22&t=39341&p=334275#p334275
826
                        # normal = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))
827
                        normal = obj.Base.getGlobalPlacement().Rotation.multVec(FreeCAD.Vector(0,0,1))
828

829
                    else:  #For all objects except Sketch, single edge or more
830
                        # See discussion - https://forum.freecad.org/viewtopic.php?t=86365
831
                        # See discussion - https://forum.freecad.org/viewtopic.php?t=82207&start=10
832
                        #self.basewires = obj.Base.Shape.Wires
833
                        #
834
                        # Now, adopt approach same as for Sketch
835
                        self.basewires = []
836
                        clusters = Part.getSortedClusters(obj.Base.Shape.Edges)
837
                        self.basewires = clusters
838
                        # Previously :
839
                        # Found case that after sorting below, direction of
840
                        # edges sorted are not as 'expected' thus resulted in
841
                        # bug - e.g. a Dwire with edges/vertexes in clockwise
842
                        # order, 1st vertex is Forward as expected.  After
843
                        # sorting below, edges sorted still in clockwise order
844
                        # - no problem, but 1st vertex of each edge become
845
                        # Reverse rather than Forward.
846

847
                        # See FC discussion -
848
                        # https://forum.freecad.org/viewtopic.php?f=23&t=48275&p=413745#p413745
849

850
                        #self.basewires = []
851
                        #for cluster in Part.getSortedClusters(obj.Base.Shape.Edges):
852
                        #    for c in Part.sortEdges(cluster):
853
                        #        self.basewires.append(Part.Wire(c))
854
                        # if not sketch, e.g. Dwire, can have wire which is 3d
855
                        # so not on the placement's working plane - below
856
                        # applied to Sketch not applicable here
857
                        #normal = obj.Base.getGlobalPlacement().Rotation.multVec(FreeCAD.Vector(0,0,1))
858
                        #normal = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))
859

860
                    if self.basewires:
861
                        if (len(self.basewires) == 1) and layers:
862
                            self.basewires = [self.basewires[0] for l in layers]
863
                        layeroffset = 0
864
                        baseface = None
865

866
                        for i,wire in enumerate(self.basewires):
867

868
                            # Check number of edges per 'wire' and get the 1st edge
869
                            if isinstance(wire,Part.Wire):
870
                                edgeNum = len(wire.Edges)
871
                                e = wire.Edges[0]
872
                            elif isinstance(wire[0],Part.Edge):
873
                                edgeNum = len(wire)
874
                                e = wire[0]
875

876
                            for n in range(0,edgeNum,1):  # why these not work - range(edgeNum), range(0,edgeNum) ...
877

878
                                # Fill the aligns list with ArchWall's default
879
                                # align entry and with same number of items as
880
                                # number of edges
881
                                try:
882
                                    if aligns[n] not in ['Left', 'Right', 'Center']:
883
                                        aligns[n] = align
884
                                except Exception:
885
                                    aligns.append(align)
886

887
                                # Fill the widths List with ArchWall's default
888
                                # width entry and with same number of items as
889
                                # number of edges
890
                                try:
891
                                    if not widths[n]:
892
                                        widths[n] = width
893
                                except Exception:
894
                                    widths.append(width)
895
                                # Fill the offsets List with ArchWall's default
896
                                # offset entry and with same number of items as
897
                                # number of edges
898
                                try:
899
                                    if not offsets[n]:
900
                                        offsets[n] = offset
901
                                except Exception:
902
                                    offsets.append(offset)
903

904
                            # Get a direction vector orthogonal to both the
905
                            # normal of the face/sketch and the direction the
906
                            # wire was drawn in. IE: along the width direction
907
                            # of the wall.
908
                            if isinstance(e.Curve,(Part.Circle,Part.Ellipse)):
909
                                dvec = e.Vertexes[0].Point.sub(e.Curve.Center)
910
                            else:
911
                                dvec = DraftGeomUtils.vec(e).cross(normal)
912

913
                            if not DraftVecUtils.isNull(dvec):
914
                                dvec.normalize()
915
                            face = None
916

917
                            curAligns = aligns[0]
918
                            #off = obj.Offset.Value  # off is no longer used
919

920
                            if curAligns == "Left":
921

922
                                if layers:
923
                                    curWidth = []
924
                                    for n in range(edgeNum):
925
                                        curWidth.append(abs(layers[i]))
926
                                    #off = off+layeroffset  # off is no longer used
927
                                    offsets = [x+layeroffset for x in offsets]
928
                                    dvec.multiply(curWidth[0])
929
                                    layeroffset += abs(curWidth[0])
930
                                else:
931
                                    curWidth = widths
932
                                    dvec.multiply(width)
933

934
                                # Now DraftGeomUtils.offsetWire() support
935
                                # similar effect as ArchWall Offset
936
                                #
937
                                #if off:
938
                                #    dvec2 = DraftVecUtils.scaleTo(dvec,off)
939
                                #    wire = DraftGeomUtils.offsetWire(wire,dvec2)
940

941
                                # Get the 'offseted' wire taking into account
942
                                # of Width and Align of each edge, and overall
943
                                # Offset
944
                                w2 = DraftGeomUtils.offsetWire(wire, dvec,
945
                                                               bind=False,
946
                                                               occ=False,
947
                                                               widthList=curWidth,
948
                                                               offsetMode=None,
949
                                                               alignList=aligns,
950
                                                               normal=normal,
951
                                                               basewireOffset=offsets)
952
                                # Get the 'base' wire taking into account of
953
                                # width and align of each edge
954
                                w1 = DraftGeomUtils.offsetWire(wire, dvec,
955
                                                               bind=False,
956
                                                               occ=False,
957
                                                               widthList=curWidth,
958
                                                               offsetMode="BasewireMode",
959
                                                               alignList=aligns,
960
                                                               normal=normal,
961
                                                               basewireOffset=offsets)
962
                                face = DraftGeomUtils.bind(w1, w2, per_segment=True)
963

964
                            elif curAligns == "Right":
965
                                dvec = dvec.negative()
966

967
                                if layers:
968
                                    curWidth = []
969
                                    for n in range(edgeNum):
970
                                        curWidth.append(abs(layers[i]))
971
                                    #off = off+layeroffset  # off is no longer used
972
                                    offsets = [x+layeroffset for x in offsets]
973
                                    dvec.multiply(curWidth[0])
974
                                    layeroffset += abs(curWidth[0])
975
                                else:
976
                                    curWidth = widths
977
                                    dvec.multiply(width)
978

979
                                # Now DraftGeomUtils.offsetWire() support similar effect as ArchWall Offset
980
                                #
981
                                #if off:
982
                                #    dvec2 = DraftVecUtils.scaleTo(dvec,off)
983
                                #    wire = DraftGeomUtils.offsetWire(wire,dvec2)
984

985

986
                                w2 = DraftGeomUtils.offsetWire(wire, dvec,
987
                                                               bind=False,
988
                                                               occ=False,
989
                                                               widthList=curWidth,
990
                                                               offsetMode=None,
991
                                                               alignList=aligns,
992
                                                               normal=normal,
993
                                                               basewireOffset=offsets)
994
                                w1 = DraftGeomUtils.offsetWire(wire, dvec,
995
                                                               bind=False,
996
                                                               occ=False,
997
                                                               widthList=curWidth,
998
                                                               offsetMode="BasewireMode",
999
                                                               alignList=aligns,
1000
                                                               normal=normal,
1001
                                                               basewireOffset=offsets)
1002
                                face = DraftGeomUtils.bind(w1, w2, per_segment=True)
1003

1004
                            #elif obj.Align == "Center":
1005
                            elif curAligns == "Center":
1006
                                if layers:
1007
                                    totalwidth=sum([abs(l) for l in layers])
1008
                                    curWidth = abs(layers[i])
1009
                                    off = totalwidth/2-layeroffset
1010
                                    d1 = Vector(dvec).multiply(off)
1011
                                    w1 = DraftGeomUtils.offsetWire(wire, d1)
1012
                                    layeroffset += curWidth
1013
                                    off = totalwidth/2-layeroffset
1014
                                    d1 = Vector(dvec).multiply(off)
1015
                                    w2 = DraftGeomUtils.offsetWire(wire, d1)
1016
                                else:
1017
                                    dvec.multiply(width)
1018

1019
                                    w2 = DraftGeomUtils.offsetWire(wire, dvec,
1020
                                                                   bind=False,
1021
                                                                   occ=False,
1022
                                                                   widthList=widths,
1023
                                                                   offsetMode=None,
1024
                                                                   alignList=aligns,
1025
                                                                   normal=normal,
1026
                                                                   basewireOffset=offsets)
1027
                                    w1 = DraftGeomUtils.offsetWire(wire, dvec,
1028
                                                                   bind=False,
1029
                                                                   occ=False,
1030
                                                                   widthList=widths,
1031
                                                                   offsetMode="BasewireMode",
1032
                                                                   alignList=aligns,
1033
                                                                   normal=normal,
1034
                                                                   basewireOffset=offsets)
1035
                                face = DraftGeomUtils.bind(w1, w2, per_segment=True)
1036

1037
                            del widths[0:edgeNum]
1038
                            del aligns[0:edgeNum]
1039
                            del offsets[0:edgeNum]
1040

1041
                            if face:
1042

1043
                                if layers and (layers[i] < 0):
1044
                                    # layers with negative values are not drawn
1045
                                    continue
1046

1047
                                if baseface:
1048

1049
                                    # To allow exportIFC.py to work properly on
1050
                                    # sketch, which use only 1st face / wire,
1051
                                    # do not fuse baseface here So for a sketch
1052
                                    # with multiple wires, each returns
1053
                                    # individual face (rather than fusing
1054
                                    # together) for exportIFC.py to work
1055
                                    # properly
1056
                                    # "ArchWall - Based on Sketch Issues" - https://forum.freecad.org/viewtopic.php?f=39&t=31235
1057

1058
                                    # "Bug #2408: [PartDesign] .fuse is splitting edges it should not"
1059
                                    # - https://forum.freecad.org/viewtopic.php?f=10&t=20349&p=346237#p346237
1060
                                    # - bugtracker - https://freecad.org/tracker/view.php?id=2408
1061

1062
                                    # Try Part.Shell before removeSplitter
1063
                                    # - https://forum.freecad.org/viewtopic.php?f=10&t=20349&start=10
1064
                                    # - 1st finding : if a rectangle + 1 line, can't removesSplitter properly...
1065
                                    # - 2nd finding : if 2 faces do not touch, can't form a shell; then, subsequently for remaining faces even though touch each faces, can't form a shell
1066

1067
                                    baseface.append(face)
1068
                                    # The above make Refine methods below (in else) useless, regardless removeSpitters yet to be improved for cases do not work well
1069
                                    '''  Whether layers or not, all baseface.append(face) '''
1070

1071
                                else:
1072
                                    baseface = [face]
1073

1074
                                    '''  Whether layers or not, all baseface = [face] '''
1075

1076
                        if baseface:
1077
                            base,placement = self.rebase(baseface)
1078
        else:
1079
            if layers:
1080
                totalwidth = sum([abs(l) for l in layers])
1081
                offset = 0
1082
                base = []
1083
                for l in layers:
1084
                    if l > 0:
1085
                        l2 = length/2 or 0.5
1086
                        w1 = -totalwidth/2 + offset
1087
                        w2 = w1 + l
1088
                        v1 = Vector(-l2,w1,0)
1089
                        v2 = Vector(l2,w1,0)
1090
                        v3 = Vector(l2,w2,0)
1091
                        v4 = Vector(-l2,w2,0)
1092
                        base.append(Part.Face(Part.makePolygon([v1,v2,v3,v4,v1])))
1093
                    offset += abs(l)
1094
            else:
1095
                l2 = length/2 or 0.5
1096
                w2 = width/2 or 0.5
1097
                v1 = Vector(-l2,-w2,0)
1098
                v2 = Vector(l2,-w2,0)
1099
                v3 = Vector(l2,w2,0)
1100
                v4 = Vector(-l2,w2,0)
1101
                base = Part.Face(Part.makePolygon([v1,v2,v3,v4,v1]))
1102
            placement = FreeCAD.Placement()
1103
        if base and placement:
1104
            normal.normalize()
1105
            extrusion = normal.multiply(height)
1106
            if placement.Rotation.Angle > 0:
1107
                extrusion = placement.inverse().Rotation.multVec(extrusion)
1108
            return (base,extrusion,placement)
1109
        return None
1110

1111
class _ViewProviderWall(ArchComponent.ViewProviderComponent):
1112
    """The view provider for the wall object.
1113

1114
    Parameters
1115
    ----------
1116
    vobj: <Gui.ViewProviderDocumentObject>
1117
        The view provider to turn into a wall view provider.
1118
    """
1119

1120
    def __init__(self,vobj):
1121
        ArchComponent.ViewProviderComponent.__init__(self,vobj)
1122
        vobj.ShapeColor = ArchCommands.getDefaultColor("Wall")
1123

1124
    def getIcon(self):
1125
        """Return the path to the appropriate icon.
1126

1127
        If a clone, return the cloned wall icon path. Otherwise return the
1128
        Arch wall icon.
1129

1130
        Returns
1131
        -------
1132
        str
1133
            Path to the appropriate icon .svg file.
1134
        """
1135

1136
        import Arch_rc
1137
        if hasattr(self,"Object"):
1138
            if self.Object.CloneOf:
1139
                return ":/icons/Arch_Wall_Clone.svg"
1140
            elif (not self.Object.Base) and self.Object.Additions:
1141
                return ":/icons/Arch_Wall_Tree_Assembly.svg"
1142
        return ":/icons/Arch_Wall_Tree.svg"
1143

1144
    def attach(self,vobj):
1145
        """Add display modes' data to the coin scenegraph.
1146

1147
        Add each display mode as a coin node, whose parent is this view
1148
        provider.
1149

1150
        Each display mode's node includes the data needed to display the object
1151
        in that mode. This might include colors of faces, or the draw style of
1152
        lines. This data is stored as additional coin nodes which are children
1153
        of the display mode node.
1154

1155
        Add the textures used in the Footprint display mode.
1156
        """
1157

1158
        self.Object = vobj.Object
1159
        from pivy import coin
1160
        tex = coin.SoTexture2()
1161
        image = Draft.loadTexture(Draft.svgpatterns()['simple'][1], 128)
1162
        if not image is None:
1163
            tex.image = image
1164
        texcoords = coin.SoTextureCoordinatePlane()
1165
        s = params.get_param_arch("patternScale")
1166
        texcoords.directionS.setValue(s,0,0)
1167
        texcoords.directionT.setValue(0,s,0)
1168
        self.fcoords = coin.SoCoordinate3()
1169
        self.fset = coin.SoIndexedFaceSet()
1170
        sep = coin.SoSeparator()
1171
        sep.addChild(tex)
1172
        sep.addChild(texcoords)
1173
        sep.addChild(self.fcoords)
1174
        sep.addChild(self.fset)
1175
        vobj.RootNode.addChild(sep)
1176
        ArchComponent.ViewProviderComponent.attach(self,vobj)
1177

1178
    def updateData(self,obj,prop):
1179
        """Method called when the host object has a property changed.
1180

1181
        If the host object's Placement, Shape, or Material has changed, and the
1182
        host object has a Material assigned, give the shape the color and
1183
        transparency of the Material.
1184

1185
        Parameters
1186
        ----------
1187
        obj: <App::FeaturePython>
1188
            The host object that has changed.
1189
        prop: string
1190
            The name of the property that has changed.
1191
        """
1192

1193
        if prop in ["Placement","Shape","Material"]:
1194
            if obj.ViewObject.DisplayMode == "Footprint":
1195
                obj.ViewObject.Proxy.setDisplayMode("Footprint")
1196
            if hasattr(obj,"Material"):
1197
                if obj.Material and obj.Shape:
1198
                    if hasattr(obj.Material,"Materials"):
1199
                        activematerials = [obj.Material.Materials[i] for i in range(len(obj.Material.Materials)) if obj.Material.Thicknesses[i] >= 0]
1200
                        if len(activematerials) == len(obj.Shape.Solids):
1201
                            cols = []
1202
                            for i,mat in enumerate(activematerials):
1203
                                c = obj.ViewObject.ShapeColor
1204
                                c = (c[0],c[1],c[2],obj.ViewObject.Transparency/100.0)
1205
                                if 'DiffuseColor' in mat.Material:
1206
                                    if "(" in mat.Material['DiffuseColor']:
1207
                                        c = tuple([float(f) for f in mat.Material['DiffuseColor'].strip("()").split(",")])
1208
                                if 'Transparency' in mat.Material:
1209
                                    c = (c[0],c[1],c[2],float(mat.Material['Transparency']))
1210
                                cols.extend([c for j in range(len(obj.Shape.Solids[i].Faces))])
1211
                            obj.ViewObject.DiffuseColor = cols
1212
        ArchComponent.ViewProviderComponent.updateData(self,obj,prop)
1213
        if len(obj.ViewObject.DiffuseColor) > 1:
1214
            # force-reset colors if changed
1215
            obj.ViewObject.DiffuseColor = obj.ViewObject.DiffuseColor
1216

1217
    def getDisplayModes(self,vobj):
1218
        """Define the display modes unique to the Arch Wall.
1219

1220
        Define mode Footprint, which only displays the footprint of the wall.
1221
        Also add the display modes of the Arch Component.
1222

1223
        Returns
1224
        -------
1225
        list of str
1226
            List containing the names of the new display modes.
1227
        """
1228

1229
        modes = ArchComponent.ViewProviderComponent.getDisplayModes(self,vobj)+["Footprint"]
1230
        return modes
1231

1232
    def setDisplayMode(self,mode):
1233
        """Method called when the display mode changes.
1234

1235
        Called when the display mode changes, this method can be used to set
1236
        data that wasn't available when .attach() was called.
1237

1238
        When Footprint is set as display mode, find the faces that make up the
1239
        footprint of the wall, and give them a lined texture. Then display
1240
        the wall as a wireframe.
1241

1242
        Then pass the displaymode onto Arch Component's .setDisplayMode().
1243

1244
        Parameters
1245
        ----------
1246
        mode: str
1247
            The name of the display mode the view provider has switched to.
1248

1249
        Returns
1250
        -------
1251
        str:
1252
            The name of the display mode the view provider has switched to.
1253
        """
1254

1255
        self.fset.coordIndex.deleteValues(0)
1256
        self.fcoords.point.deleteValues(0)
1257
        if mode == "Footprint":
1258
            if hasattr(self,"Object"):
1259
                faces = self.Object.Proxy.getFootprint(self.Object)
1260
                if faces:
1261
                    verts = []
1262
                    fdata = []
1263
                    idx = 0
1264
                    for face in faces:
1265
                        tri = face.tessellate(1)
1266
                        for v in tri[0]:
1267
                            verts.append([v.x,v.y,v.z])
1268
                        for f in tri[1]:
1269
                            fdata.extend([f[0]+idx,f[1]+idx,f[2]+idx,-1])
1270
                        idx += len(tri[0])
1271
                    self.fcoords.point.setValues(verts)
1272
                    self.fset.coordIndex.setValues(0,len(fdata),fdata)
1273
            return "Wireframe"
1274
        return ArchComponent.ViewProviderComponent.setDisplayMode(self,mode)
1275

1276
    def setupContextMenu(self, vobj, menu):
1277
        super().contextMenuAddEdit(menu)
1278

1279
        actionFlipDirection = QtGui.QAction(QtGui.QIcon(":/icons/Arch_Wall_Tree.svg"),
1280
                                            translate("Arch", "Flip direction"),
1281
                                            menu)
1282
        QtCore.QObject.connect(actionFlipDirection,
1283
                               QtCore.SIGNAL("triggered()"),
1284
                               self.flipDirection)
1285
        menu.addAction(actionFlipDirection)
1286

1287
        super().contextMenuAddToggleSubcomponents(menu)
1288

1289
    def flipDirection(self):
1290

1291
        if hasattr(self,"Object") and self.Object:
1292
            obj = self.Object
1293
            if obj.Align == "Left":
1294
                obj.Align = "Right"
1295
                FreeCAD.ActiveDocument.recompute()
1296
            elif obj.Align == "Right":
1297
                obj.Align = "Left"
1298
                FreeCAD.ActiveDocument.recompute()
1299

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

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

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

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