FreeCAD

Форк
0
/
ArchStructure.py 
1515 строк · 68.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
#Modified 2016-01-03 JAndersM
22

23
import FreeCAD,Draft,ArchComponent,DraftVecUtils,ArchCommands
24
from FreeCAD import Vector
25
import ArchProfile
26
from draftutils import params
27
from draftutils import gui_utils
28

29
if FreeCAD.GuiUp:
30
    import FreeCADGui
31
    from PySide import QtCore, QtGui
32
    from draftutils.translate import translate
33
    from PySide.QtCore import QT_TRANSLATE_NOOP
34
    import ArchPrecast
35
    import draftguitools.gui_trackers as DraftTrackers
36
else:
37
    # \cond
38
    def translate(ctxt,txt):
39
        return txt
40
    def QT_TRANSLATE_NOOP(ctxt,txt):
41
        return txt
42
    # \endcond
43

44
## @package ArchStructure
45
#  \ingroup ARCH
46
#  \brief The Structure object and tools
47
#
48
#  This module provides tools to build Structure objects.
49
#  Structure elements are beams, columns, slabs, and other
50
#  elements that have a structural function, that is, that
51
#  support other parts of the building.
52

53
__title__= "FreeCAD Structure"
54
__author__ = "Yorik van Havre"
55
__url__ = "https://www.freecad.org"
56

57

58
#Reads preset profiles and categorizes them
59
Categories=[]
60
Presets=ArchProfile.readPresets()
61
for pre in Presets:
62
    if pre[1] not in Categories:
63
        Categories.append(pre[1])
64

65

66
def makeStructure(baseobj=None,length=None,width=None,height=None,name=None):
67

68
    '''makeStructure([baseobj],[length],[width],[height],[name]): creates a
69
    structure element based on the given profile object and the given
70
    extrusion height. If no base object is given, you can also specify
71
    length and width for a cubic object.'''
72

73
    if not FreeCAD.ActiveDocument:
74
        FreeCAD.Console.PrintError("No active document. Aborting\n")
75
        return
76
    obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Structure")
77
    _Structure(obj)
78
    if FreeCAD.GuiUp:
79
        _ViewProviderStructure(obj.ViewObject)
80
    if baseobj:
81
        obj.Base = baseobj
82
        if FreeCAD.GuiUp:
83
            obj.Base.ViewObject.hide()
84
    if width:
85
        obj.Width = width
86
    else:
87
        obj.Width = params.get_param_arch("StructureWidth")
88
    if height:
89
        obj.Height = height
90
    else:
91
        if not length:
92
            obj.Height = params.get_param_arch("StructureHeight")
93
    if length:
94
        obj.Length = length
95
    else:
96
        if not baseobj:
97
            # don't set the length if we have a base object, otherwise the length X height calc
98
            # gets wrong
99
            obj.Length = params.get_param_arch("StructureLength")
100
    if baseobj:
101
        w = 0
102
        h = 0
103
        if hasattr(baseobj,"Width") and hasattr(baseobj,"Height"):
104
            w = baseobj.Width.Value
105
            h = baseobj.Height.Value
106
        elif hasattr(baseobj,"Length") and hasattr(baseobj,"Width"):
107
            w = baseobj.Length.Value
108
            h = baseobj.Width.Value
109
        elif hasattr(baseobj,"Length") and hasattr(baseobj,"Height"):
110
            w = baseobj.Length.Value
111
            h = baseobj.Height.Value
112
        if w and h:
113
            if length and not height:
114
                obj.Width = w
115
                obj.Height = h
116
            elif height and not length:
117
                obj.Width = w
118
                obj.Length = h
119

120
    if not height and not length:
121
        obj.IfcType = "Building Element Proxy"
122
        obj.Label = name if name else translate("Arch","Structure")
123
    elif obj.Length > obj.Height:
124
        obj.IfcType = "Beam"
125
        obj.Label = name if name else translate("Arch","Beam")
126
    elif obj.Height > obj.Length:
127
        obj.IfcType = "Column"
128
        obj.Label = name if name else translate("Arch","Column")
129
    return obj
130

131
def makeStructuralSystem(objects=[],axes=[],name=None):
132

133
    '''makeStructuralSystem([objects],[axes],[name]): makes a structural system
134
    based on the given objects and axes'''
135

136
    if not FreeCAD.ActiveDocument:
137
        FreeCAD.Console.PrintError("No active document. Aborting\n")
138
        return
139
    result = []
140
    if not axes:
141
        print("At least one axis must be given")
142
        return
143
    if objects:
144
        if not isinstance(objects,list):
145
            objects = [objects]
146
    else:
147
        objects = [None]
148
    for o in objects:
149
        obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","StructuralSystem")
150
        obj.Label = name if name else translate("Arch","StructuralSystem")
151
        _StructuralSystem(obj)
152
        if FreeCAD.GuiUp:
153
            _ViewProviderStructuralSystem(obj.ViewObject)
154
        if o:
155
            obj.Base = o
156
        obj.Axes = axes
157
        result.append(obj)
158
        if FreeCAD.GuiUp and o:
159
            o.ViewObject.hide()
160
            Draft.formatObject(obj,o)
161
    FreeCAD.ActiveDocument.recompute()
162
    if len(result) == 1:
163
        return result[0]
164
    else:
165
        return result
166

167
def placeAlongEdge(p1,p2,horizontal=False):
168

169
    """placeAlongEdge(p1,p2,[horizontal]): returns a Placement positioned at p1, with Z axis oriented towards p2.
170
    If horizontal is True, then the X axis is oriented towards p2, not the Z axis"""
171

172
    pl = FreeCAD.Placement()
173
    pl.Base = p1
174
    import WorkingPlane
175
    up = WorkingPlane.get_working_plane(update=False).axis
176
    zaxis = p2.sub(p1)
177
    yaxis = up.cross(zaxis)
178
    if yaxis.Length > 0:
179
        xaxis = zaxis.cross(yaxis)
180
        if horizontal:
181
            pl.Rotation = FreeCAD.Rotation(zaxis,yaxis,xaxis,"ZXY")
182
        else:
183
            pl.Rotation = FreeCAD.Rotation(xaxis,yaxis,zaxis,"ZXY")
184
            pl.Rotation = FreeCAD.Rotation(pl.Rotation.multVec(FreeCAD.Vector(0,0,1)),90).multiply(pl.Rotation)
185
    return pl
186

187

188
class CommandStructuresFromSelection:
189
    """ The Arch Structures from selection command definition. """
190

191
    def __init__(self):
192
        pass
193

194
    def GetResources(self):
195
        return {'Pixmap': 'Arch_MultipleStructures',
196
                'MenuText': QT_TRANSLATE_NOOP("Arch_StructuresFromSelection", "Multiple Structures"),
197
                'ToolTip': QT_TRANSLATE_NOOP("Arch_StructuresFromSelection", "Create multiple BIM Structures from a selected base, using each selected edge as an extrusion path")}
198

199
    def IsActive(self):
200
        v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
201
        return v
202

203
    def Activated(self):
204
        selex = FreeCADGui.Selection.getSelectionEx()
205
        if len(selex) >= 2:
206
            FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Create Structures From Selection"))
207
            FreeCADGui.addModule("Arch")
208
            FreeCADGui.addModule("Draft")
209
            base = selex[0].Object # The first selected object is the base for the Structure objects
210
            for selexi in selex[1:]: # All the edges from the other objects are used as a Tool (extrusion paths)
211
                if len(selexi.SubElementNames) == 0:
212
                    subelement_names = ["Edge" + str(i) for i in range(1, len(selexi.Object.Shape.Edges) + 1)]
213
                else:
214
                    subelement_names = [sub for sub in selexi.SubElementNames if sub.startswith("Edge")]
215
                for sub in subelement_names:
216
                    FreeCADGui.doCommand("structure = Arch.makeStructure(FreeCAD.ActiveDocument." + base.Name + ")")
217
                    FreeCADGui.doCommand("structure.Tool = (FreeCAD.ActiveDocument." + selexi.Object.Name + ", '" + sub + "')")
218
                    FreeCADGui.doCommand("structure.BasePerpendicularToTool = True")
219
                    FreeCADGui.doCommand("Draft.autogroup(structure)")
220
            FreeCAD.ActiveDocument.commitTransaction()
221
            FreeCAD.ActiveDocument.recompute()
222
        else:
223
            FreeCAD.Console.PrintError(translate("Arch", "Please select the base object first and then the edges to use as extrusion paths") + "\n")
224

225

226
class CommandStructuralSystem:
227
    """ The Arch Structural System command definition. """
228

229
    def __init__(self):
230
        pass
231

232
    def GetResources(self):
233
        return {'Pixmap': 'Arch_StructuralSystem',
234
                'MenuText': QT_TRANSLATE_NOOP("Arch_StructuralSystem", "Structural System"),
235
                'ToolTip': QT_TRANSLATE_NOOP("Arch_StructuralSystem", "Create a structural system from a selected structure and axis")}
236

237
    def IsActive(self):
238
        return not FreeCAD.ActiveDocument is None
239

240
    def Activated(self):
241
        sel = FreeCADGui.Selection.getSelection()
242
        if sel:
243
            st = Draft.getObjectsOfType(sel, "Structure")
244
            ax = Draft.getObjectsOfType(sel, "Axis")
245
            if ax:
246
                FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Create Structural System"))
247
                FreeCADGui.addModule("Arch")
248
                if st:
249
                    FreeCADGui.doCommand("obj = Arch.makeStructuralSystem(" + ArchCommands.getStringList(st) + ", " + ArchCommands.getStringList(ax) + ")")
250
                else:
251
                    FreeCADGui.doCommand("obj = Arch.makeStructuralSystem(axes = " + ArchCommands.getStringList(ax) + ")")
252
                FreeCADGui.addModule("Draft")
253
                FreeCADGui.doCommand("Draft.autogroup(obj)")
254
                FreeCAD.ActiveDocument.commitTransaction()
255
                FreeCAD.ActiveDocument.recompute()
256
            else:
257
                FreeCAD.Console.PrintError(translate("Arch", "Please select at least an axis object") + "\n")
258

259

260
class _CommandStructure:
261

262
    "the Arch Structure command definition"
263

264
    def __init__(self):
265

266
        self.beammode = False
267

268
    def GetResources(self):
269

270
        return {'Pixmap'  : 'Arch_Structure',
271
                'MenuText': QT_TRANSLATE_NOOP("Arch_Structure","Structure"),
272
                'Accel': "S, T",
273
                'ToolTip': QT_TRANSLATE_NOOP("Arch_Structure","Creates a structure from scratch or from a selected object (sketch, wire, face or solid)")}
274

275
    def IsActive(self):
276

277
        return not FreeCAD.ActiveDocument is None
278

279
    def Activated(self):
280

281
        self.Width = params.get_param_arch("StructureWidth")
282
        if self.beammode:
283
            self.Height = params.get_param_arch("StructureLength")
284
            self.Length = params.get_param_arch("StructureHeight")
285
        else:
286
            self.Length = params.get_param_arch("StructureLength")
287
            self.Height = params.get_param_arch("StructureHeight")
288
        self.Profile = None
289
        self.continueCmd = False
290
        self.bpoint = None
291
        self.bmode = False
292
        self.precastvalues = None
293
        self.wp = None
294
        sel = FreeCADGui.Selection.getSelection()
295
        if sel:
296
            st = Draft.getObjectsOfType(sel,"Structure")
297
            ax = Draft.getObjectsOfType(sel,"Axis")
298
            if ax:
299
                FreeCADGui.runCommand("Arch_StructuralSystem")
300
                return
301
            elif not(ax) and not(st):
302
                FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Structure"))
303
                FreeCADGui.addModule("Arch")
304
                for obj in sel:
305
                    FreeCADGui.doCommand("obj = Arch.makeStructure(FreeCAD.ActiveDocument." + obj.Name + ")")
306
                    FreeCADGui.addModule("Draft")
307
                    FreeCADGui.doCommand("Draft.autogroup(obj)")
308
                FreeCAD.ActiveDocument.commitTransaction()
309
                FreeCAD.ActiveDocument.recompute()
310
                return
311

312
        # interactive mode
313
        import WorkingPlane
314
        self.wp = WorkingPlane.get_working_plane()
315

316
        self.points = []
317
        self.tracker = DraftTrackers.boxTracker()
318
        self.tracker.width(self.Width)
319
        self.tracker.height(self.Height)
320
        self.tracker.length(self.Length)
321
        self.tracker.setRotation(self.wp.get_placement().Rotation)
322
        self.tracker.on()
323
        self.precast = ArchPrecast._PrecastTaskPanel()
324
        self.dents = ArchPrecast._DentsTaskPanel()
325
        self.precast.Dents = self.dents
326
        if self.beammode:
327
            title=translate("Arch","First point of the beam")+":"
328
        else:
329
            title=translate("Arch","Base point of column")+":"
330
        FreeCAD.activeDraftCommand = self  # register as a Draft command for auto grid on/off
331
        FreeCADGui.Snapper.getPoint(callback=self.getPoint,movecallback=self.update,extradlg=[self.taskbox(),self.precast.form,self.dents.form],title=title)
332

333
    def getPoint(self,point=None,obj=None):
334

335
        "this function is called by the snapper when it has a 3D point"
336

337
        self.bmode = self.modeb.isChecked()
338
        if point is None:
339
            self.tracker.finalize()
340
            FreeCAD.activeDraftCommand = None
341
            FreeCADGui.Snapper.off()
342
            return
343
        if self.bmode and (self.bpoint is None):
344
            self.bpoint = point
345
            FreeCADGui.Snapper.getPoint(last=point,callback=self.getPoint,movecallback=self.update,extradlg=[self.taskbox(),self.precast.form,self.dents.form],title=translate("Arch","Next point")+":",mode="line")
346
            return
347
        self.tracker.off()
348
        FreeCAD.activeDraftCommand = None
349
        FreeCADGui.Snapper.off()
350
        horiz = True # determines the type of rotation to apply to the final object
351
        FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Structure"))
352
        FreeCADGui.addModule("Arch")
353
        FreeCADGui.addModule("WorkingPlane")
354
        if self.bmode:
355
            self.Length = point.sub(self.bpoint).Length
356
            params.set_param_arch("StructureHeight",self.Length)
357
        if self.Profile is not None:
358
            try: # try to update latest precast values - fails if dialog has been destroyed already
359
                self.precastvalues = self.precast.getValues()
360
            except Exception:
361
                pass
362
            if ("Precast" in self.Profile) and self.precastvalues:
363
                # precast concrete
364
                self.precastvalues["PrecastType"] = self.Profile.split("_")[1]
365
                self.precastvalues["Length"] = self.Length
366
                self.precastvalues["Width"] = self.Width
367
                self.precastvalues["Height"] = self.Height
368
                argstring = ""
369
                # fix for precast placement, since their (0,0) point is the lower left corner
370
                if self.bmode:
371
                    delta = FreeCAD.Vector(0,0-self.Width/2,0)
372
                else:
373
                    delta = FreeCAD.Vector(-self.Length/2,-self.Width/2,0)
374
                delta = self.wp.get_global_coords(delta,as_vector=True)
375
                point = point.add(delta)
376
                if self.bpoint:
377
                    self.bpoint = self.bpoint.add(delta)
378
                # build the string definition
379
                for pair in self.precastvalues.items():
380
                    argstring += pair[0].lower() + "="
381
                    if isinstance(pair[1],str):
382
                        argstring += '"' + pair[1] + '",'
383
                    else:
384
                        argstring += str(pair[1]) + ","
385
                FreeCADGui.addModule("ArchPrecast")
386
                FreeCADGui.doCommand("s = ArchPrecast.makePrecast("+argstring+")")
387
            else:
388
                # metal profile
389
                FreeCADGui.doCommand('p = Arch.makeProfile('+str(self.Profile)+')')
390
                if (abs(self.Length - self.Profile[4]) >= 0.1) or self.bmode: # forgive rounding errors
391
                    # horizontal
392
                    FreeCADGui.doCommand('s = Arch.makeStructure(p,length='+str(self.Length)+')')
393
                    horiz = False
394
                else:
395
                    # vertical
396
                    FreeCADGui.doCommand('s = Arch.makeStructure(p,height='+str(self.Height)+')')
397
                    #if not self.bmode:
398
                    #    FreeCADGui.doCommand('s.Placement.Rotation = FreeCAD.Rotation(-0.5,0.5,-0.5,0.5)')
399
                FreeCADGui.doCommand('s.Profile = "'+self.Profile[2]+'"')
400
        else :
401
            FreeCADGui.doCommand('s = Arch.makeStructure(length='+str(self.Length)+',width='+str(self.Width)+',height='+str(self.Height)+')')
402

403
        # calculate rotation
404
        if self.bmode and self.bpoint:
405
            FreeCADGui.doCommand('s.Placement = Arch.placeAlongEdge('+DraftVecUtils.toString(self.bpoint)+","+DraftVecUtils.toString(point)+","+str(horiz)+")")
406
        else:
407
            FreeCADGui.doCommand('s.Placement.Base = '+DraftVecUtils.toString(point))
408
            FreeCADGui.doCommand('wp = WorkingPlane.get_working_plane()')
409
            FreeCADGui.doCommand('s.Placement.Rotation = s.Placement.Rotation.multiply(wp.get_placement().Rotation)')
410

411
        FreeCADGui.addModule("Draft")
412
        FreeCADGui.doCommand("Draft.autogroup(s)")
413
        FreeCAD.ActiveDocument.commitTransaction()
414
        FreeCAD.ActiveDocument.recompute()
415
        gui_utils.end_all_events()
416
        self.tracker.finalize()
417
        if self.continueCmd:
418
            self.Activated()
419

420
    def _createItemlist(self, baselist):
421

422
        "create nice labels for presets in the task panel"
423

424
        ilist=[]
425
        for p in baselist:
426
            f = FreeCAD.Units.Quantity(p[4],FreeCAD.Units.Length).getUserPreferred()
427
            d = params.get_param("Decimals",path="Units")
428
            s1 = str(round(p[4]/f[1],d))
429
            s2 = str(round(p[5]/f[1],d))
430
            s3 = str(f[2])
431
            ilist.append(p[2]+" ("+s1+"x"+s2+s3+")")
432
        return ilist
433

434
    def taskbox(self):
435

436
        "sets up a taskbox widget"
437

438
        w = QtGui.QWidget()
439
        ui = FreeCADGui.UiLoader()
440
        w.setWindowTitle(translate("Arch","Structure options"))
441
        grid = QtGui.QGridLayout(w)
442

443
        # mode box
444
        labelmode = QtGui.QLabel(translate("Arch","Parameters of the structure")+":")
445
        self.modeb = QtGui.QRadioButton(translate("Arch","Beam"))
446
        self.modec = QtGui.QRadioButton(translate("Arch","Column"))
447
        if self.bpoint or self.beammode:
448
            self.modeb.setChecked(True)
449
        else:
450
            self.modec.setChecked(True)
451
        grid.addWidget(labelmode,0,0,1,2)
452
        grid.addWidget(self.modeb,1,0,1,1)
453
        grid.addWidget(self.modec,1,1,1,1)
454

455
        # categories box
456
        labelc = QtGui.QLabel(translate("Arch","Category"))
457
        self.valuec = QtGui.QComboBox()
458
        self.valuec.addItems([" ","Precast concrete"]+Categories)
459
        grid.addWidget(labelc,2,0,1,1)
460
        grid.addWidget(self.valuec,2,1,1,1)
461

462
        # presets box
463
        labelp = QtGui.QLabel(translate("Arch","Preset"))
464
        self.vPresets = QtGui.QComboBox()
465
        self.pSelect = [None]
466
        fpresets = [" "]
467
        self.vPresets.addItems(fpresets)
468
        grid.addWidget(labelp,3,0,1,1)
469
        grid.addWidget(self.vPresets,3,1,1,1)
470

471
        # length
472
        label1 = QtGui.QLabel(translate("Arch","Length"))
473
        self.vLength = ui.createWidget("Gui::InputField")
474
        if self.modeb.isChecked():
475
            self.vLength.setText(FreeCAD.Units.Quantity(self.Height,FreeCAD.Units.Length).UserString)
476
        else:
477
            self.vLength.setText(FreeCAD.Units.Quantity(self.Length,FreeCAD.Units.Length).UserString)
478
        grid.addWidget(label1,4,0,1,1)
479
        grid.addWidget(self.vLength,4,1,1,1)
480

481
        # width
482
        label2 = QtGui.QLabel(translate("Arch","Width"))
483
        self.vWidth = ui.createWidget("Gui::InputField")
484
        self.vWidth.setText(FreeCAD.Units.Quantity(self.Width,FreeCAD.Units.Length).UserString)
485
        grid.addWidget(label2,5,0,1,1)
486
        grid.addWidget(self.vWidth,5,1,1,1)
487

488
        # height
489
        label3 = QtGui.QLabel(translate("Arch","Height"))
490
        self.vHeight = ui.createWidget("Gui::InputField")
491
        if self.modeb.isChecked():
492
            self.vHeight.setText(FreeCAD.Units.Quantity(self.Length,FreeCAD.Units.Length).UserString)
493
        else:
494
            self.vHeight.setText(FreeCAD.Units.Quantity(self.Height,FreeCAD.Units.Length).UserString)
495
        grid.addWidget(label3,6,0,1,1)
496
        grid.addWidget(self.vHeight,6,1,1,1)
497

498
        # horizontal button
499
        value5 = QtGui.QPushButton(translate("Arch","Switch Length/Height"))
500
        grid.addWidget(value5,7,0,1,1)
501
        value6 = QtGui.QPushButton(translate("Arch","Switch Length/Width"))
502
        grid.addWidget(value6,7,1,1,1)
503

504
        # continue button
505
        label4 = QtGui.QLabel(translate("Arch","Con&tinue"))
506
        value4 = QtGui.QCheckBox()
507
        value4.setObjectName("ContinueCmd")
508
        value4.setLayoutDirection(QtCore.Qt.RightToLeft)
509
        label4.setBuddy(value4)
510
        self.continueCmd = params.get_param("ContinueMode")
511
        value4.setChecked(self.continueCmd)
512
        grid.addWidget(label4,8,0,1,1)
513
        grid.addWidget(value4,8,1,1,1)
514

515
        # connect slots
516
        QtCore.QObject.connect(self.valuec,QtCore.SIGNAL("currentIndexChanged(int)"),self.setCategory)
517
        QtCore.QObject.connect(self.vPresets,QtCore.SIGNAL("currentIndexChanged(int)"),self.setPreset)
518
        QtCore.QObject.connect(self.vLength,QtCore.SIGNAL("valueChanged(double)"),self.setLength)
519
        QtCore.QObject.connect(self.vWidth,QtCore.SIGNAL("valueChanged(double)"),self.setWidth)
520
        QtCore.QObject.connect(self.vHeight,QtCore.SIGNAL("valueChanged(double)"),self.setHeight)
521
        QtCore.QObject.connect(value4,QtCore.SIGNAL("stateChanged(int)"),self.setContinue)
522
        QtCore.QObject.connect(value5,QtCore.SIGNAL("pressed()"),self.rotateLH)
523
        QtCore.QObject.connect(value6,QtCore.SIGNAL("pressed()"),self.rotateLW)
524
        QtCore.QObject.connect(self.modeb,QtCore.SIGNAL("toggled(bool)"),self.switchLH)
525

526
        # restore preset
527
        stored = params.get_param_arch("StructurePreset")
528
        if stored:
529
            if stored.lower().startswith("precast_"):
530
                self.valuec.setCurrentIndex(1)
531
                tp = stored.split("_")[1]
532
                if tp and (tp in self.precast.PrecastTypes):
533
                    self.vPresets.setCurrentIndex(self.precast.PrecastTypes.index(tp))
534
            elif ";" in stored:
535
                stored = stored.split(";")
536
                if len(stored) >= 3:
537
                    if stored[1] in Categories:
538
                        self.valuec.setCurrentIndex(2+Categories.index(stored[1]))
539
                        ps = [p[2] for p in self.pSelect]
540
                        if stored[2] in ps:
541
                            self.vPresets.setCurrentIndex(ps.index(stored[2]))
542
        return w
543

544
    def update(self,point,info):
545

546
        "this function is called by the Snapper when the mouse is moved"
547

548
        if FreeCADGui.Control.activeDialog():
549
            try: # try to update latest precast values - fails if dialog has been destroyed already
550
                self.precastvalues = self.precast.getValues()
551
            except Exception:
552
                pass
553
            if self.Height >= self.Length:
554
                delta = Vector(0,0,self.Height/2)
555
            else:
556
                delta = Vector(self.Length/2,0,0)
557
            delta = self.wp.get_global_coords(delta,as_vector=True)
558
            if self.modec.isChecked():
559
                self.tracker.pos(point.add(delta))
560
                self.tracker.on()
561
            else:
562
                if self.bpoint:
563
                    delta = Vector(0,0,-self.Height/2)
564
                    delta = self.wp.get_global_coords(delta,as_vector=True)
565
                    self.tracker.update([self.bpoint.add(delta),point.add(delta)])
566
                    self.tracker.on()
567
                    l = (point.sub(self.bpoint)).Length
568
                    self.vLength.setText(FreeCAD.Units.Quantity(l,FreeCAD.Units.Length).UserString)
569
                else:
570
                    self.tracker.off()
571

572
    def setWidth(self,d):
573

574
        self.Width = d
575
        self.tracker.width(d)
576
        params.set_param_arch("StructureWidth",d)
577

578
    def setHeight(self,d):
579

580
        self.Height = d
581
        self.tracker.height(d)
582
        if self.modeb.isChecked():
583
            params.set_param_arch("StructureLength",d)
584
        else:
585
            params.set_param_arch("StructureHeight",d)
586

587
    def setLength(self,d):
588

589
        self.Length = d
590
        self.tracker.length(d)
591
        if self.modeb.isChecked():
592
            params.set_param_arch("StructureHeight",d)
593
        else:
594
            params.set_param_arch("StructureLength",d)
595

596
    def setContinue(self,i):
597

598
        self.continueCmd = bool(i)
599
        params.set_param("ContinueMode", bool(i))
600

601
    def setCategory(self,i):
602

603
        self.vPresets.clear()
604
        if i > 1:
605
            self.precast.form.hide()
606
            self.pSelect = [p for p in Presets if p[1] == Categories[i-2]]
607
            fpresets = self._createItemlist(self.pSelect)
608
            self.vPresets.addItems(fpresets)
609
            self.setPreset(0)
610
        elif i == 1:
611
            self.precast.form.show()
612
            self.pSelect = self.precast.PrecastTypes
613
            fpresets = self.precast.PrecastTypes
614
            self.vPresets.addItems(fpresets)
615
            self.setPreset(0)
616
        else:
617
            self.precast.form.hide()
618
            self.pSelect = [None]
619
            fpresets = [" "]
620
            self.vPresets.addItems(fpresets)
621
            params.set_param_arch("StructurePreset","")
622

623
    def setPreset(self,i):
624

625
        self.Profile = None
626
        elt = self.pSelect[i]
627
        if elt:
628
            if elt in self.precast.PrecastTypes:
629
                self.precast.setPreset(elt)
630
                self.Profile = "Precast_" + elt
631
                if elt in ["Pillar","Beam"]:
632
                    self.dents.form.show()
633
                else:
634
                    self.dents.form.hide()
635
                params.set_param_arch("StructurePreset",self.Profile)
636
            else:
637
                p=elt[0]-1 # Presets indexes are 1-based
638
                self.vLength.setText(FreeCAD.Units.Quantity(float(Presets[p][4]),FreeCAD.Units.Length).UserString)
639
                self.vWidth.setText(FreeCAD.Units.Quantity(float(Presets[p][5]),FreeCAD.Units.Length).UserString)
640
                self.Profile = Presets[p]
641
                params.set_param_arch("StructurePreset",";".join([str(i) for i in self.Profile]))
642

643
    def switchLH(self,bmode):
644

645
        if bmode:
646
            self.bmode = True
647
            if self.Height > self.Length:
648
                self.rotateLH()
649
        else:
650
            self.bmode = False
651
            if self.Length > self.Height:
652
                self.rotateLH()
653
                self.tracker.setRotation(FreeCAD.Rotation())
654

655
    def rotateLH(self):
656

657
        l = self.vLength.text()
658
        h = self.vHeight.text()
659
        self.vLength.setText(h)
660
        self.vHeight.setText(l)
661

662
    def rotateLW(self):
663

664
        l = self.vLength.text()
665
        w = self.vWidth.text()
666
        self.vLength.setText(w)
667
        self.vWidth.setText(l)
668

669

670
class _Structure(ArchComponent.Component):
671

672
    "The Structure object"
673

674
    def __init__(self,obj):
675

676
        ArchComponent.Component.__init__(self,obj)
677
        self.setProperties(obj)
678
        obj.IfcType = "Beam"
679

680
    def setProperties(self,obj):
681

682
        pl = obj.PropertiesList
683
        if not "Tool" in pl:
684
            obj.addProperty("App::PropertyLinkSubList", "Tool", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "An optional extrusion path for this element"))
685
        if not "ComputedLength" in pl:
686
            obj.addProperty("App::PropertyDistance", "ComputedLength", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "The computed length of the extrusion path"), 1)
687
        if not "ToolOffsetFirst" in pl:
688
            obj.addProperty("App::PropertyDistance", "ToolOffsetFirst", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Start offset distance along the extrusion path (positive: extend, negative: trim)"))
689
        if not "ToolOffsetLast" in pl:
690
            obj.addProperty("App::PropertyDistance", "ToolOffsetLast", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "End offset distance along the extrusion path (positive: extend, negative: trim)"))
691
        if not "BasePerpendicularToTool" in pl:
692
            obj.addProperty("App::PropertyBool", "BasePerpendicularToTool", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Automatically align the Base of the Structure perpendicular to the Tool axis"))
693
        if not "BaseOffsetX" in pl:
694
            obj.addProperty("App::PropertyDistance", "BaseOffsetX", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "X offset between the Base origin and the Tool axis (only used if BasePerpendicularToTool is True)"))
695
        if not "BaseOffsetY" in pl:
696
            obj.addProperty("App::PropertyDistance", "BaseOffsetY", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Y offset between the Base origin and the Tool axis (only used if BasePerpendicularToTool is True)"))
697
        if not "BaseMirror" in pl:
698
            obj.addProperty("App::PropertyBool", "BaseMirror", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Mirror the Base along its Y axis (only used if BasePerpendicularToTool is True)"))
699
        if not "BaseRotation" in pl:
700
            obj.addProperty("App::PropertyAngle", "BaseRotation", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Base rotation around the Tool axis (only used if BasePerpendicularToTool is True)"))
701
        if not "Length" in pl:
702
            obj.addProperty("App::PropertyLength","Length","Structure",QT_TRANSLATE_NOOP("App::Property","The length of this element, if not based on a profile"))
703
        if not "Width" in pl:
704
            obj.addProperty("App::PropertyLength","Width","Structure",QT_TRANSLATE_NOOP("App::Property","The width of this element, if not based on a profile"))
705
        if not "Height" in pl:
706
            obj.addProperty("App::PropertyLength","Height","Structure",QT_TRANSLATE_NOOP("App::Property","The height or extrusion depth of this element. Keep 0 for automatic"))
707
        if not "Normal" in pl:
708
            obj.addProperty("App::PropertyVector","Normal","Structure",QT_TRANSLATE_NOOP("App::Property","The normal extrusion direction of this object (keep (0,0,0) for automatic normal)"))
709
        if not "Nodes" in pl:
710
            obj.addProperty("App::PropertyVectorList","Nodes","Structure",QT_TRANSLATE_NOOP("App::Property","The structural nodes of this element"))
711
        if not "Profile" in pl:
712
            obj.addProperty("App::PropertyString","Profile","Structure",QT_TRANSLATE_NOOP("App::Property","A description of the standard profile this element is based upon"))
713
        if not "NodesOffset" in pl:
714
            obj.addProperty("App::PropertyDistance","NodesOffset","Structure",QT_TRANSLATE_NOOP("App::Property","Offset distance between the centerline and the nodes line"))
715
        if not "FaceMaker" in pl:
716
            obj.addProperty("App::PropertyEnumeration","FaceMaker","Structure",QT_TRANSLATE_NOOP("App::Property","The facemaker type to use to build the profile of this object"))
717
            obj.FaceMaker = ["None","Simple","Cheese","Bullseye"]
718
        if not "ArchSketchEdges" in pl:  # PropertyStringList
719
            obj.addProperty("App::PropertyStringList","ArchSketchEdges","Structure",QT_TRANSLATE_NOOP("App::Property","Selected edges (or group of edges) of the base ArchSketch, to use in creating the shape of this BIM Structure (instead of using all the Base shape's edges by default).  Input are index numbers of edges or groups."))
720
        else:
721
            # test if the property was added but as IntegerList, then update;
722
            type = obj.getTypeIdOfProperty('ArchSketchEdges')
723
            if type == "App::PropertyIntegerList":
724
                oldIntValue = obj.ArchSketchEdges
725
                newStrValue = [str(x) for x in oldIntValue]
726
                obj.removeProperty("ArchSketchEdges")
727
                obj.addProperty("App::PropertyStringList","ArchSketchEdges","Structure",QT_TRANSLATE_NOOP("App::Property","Selected edges (or group of edges) of the base ArchSketch, to use in creating the shape of this BIM Structure (instead of using all the Base shape's edges by default).  Input are index numbers of edges or groups."))
728
                obj.ArchSketchEdges = newStrValue
729
        self.Type = "Structure"
730

731
    def onDocumentRestored(self,obj):
732

733
        ArchComponent.Component.onDocumentRestored(self,obj)
734
        self.setProperties(obj)
735

736
    def execute(self,obj):
737

738
        "creates the structure shape"
739

740
        import Part, DraftGeomUtils
741

742
        if self.clone(obj):
743
            return
744

745
        base = None
746
        pl = obj.Placement
747
        extdata = self.getExtrusionData(obj)
748

749
        if extdata:
750
            sh = extdata[0]
751
            if not isinstance(sh,list):
752
                sh = [sh]
753
            ev = extdata[1]
754
            if not isinstance(ev,list):
755
                ev = [ev]
756
            pla = extdata[2]
757
            if not isinstance(pla,list):
758
                pla = [pla]
759
            base = []
760
            extrusion_length = 0.0
761
            for i in range(len(sh)):
762
                shi = sh[i]
763
                if i < len(ev):
764
                    evi = ev[i]
765
                else:
766
                    evi = ev[-1]
767
                    if isinstance(evi, FreeCAD.Vector):
768
                        evi = FreeCAD.Vector(evi)
769
                    else:
770
                        evi = evi.copy()
771
                if i < len(pla):
772
                    pli = pla[i]
773
                else:
774
                    pli = pla[-1].copy()
775
                shi.Placement = pli.multiply(shi.Placement)
776
                if isinstance(evi, FreeCAD.Vector):
777
                    extv = pla[0].Rotation.multVec(evi)
778
                    shi = shi.extrude(extv)
779
                else:
780
                    try:
781
                        shi = evi.makePipe(shi)
782
                    except Part.OCCError:
783
                        FreeCAD.Console.PrintError(translate("Arch","Error: The base shape couldn't be extruded along this tool object")+"\n")
784
                        return
785
                base.append(shi)
786
                extrusion_length += evi.Length
787
            if len(base) == 1:
788
                base = base[0]
789
            else:
790
                base = Part.makeCompound(base)
791
            obj.ComputedLength = FreeCAD.Units.Quantity(extrusion_length, FreeCAD.Units.Length)
792
        if obj.Base:
793
            if hasattr(obj.Base,'Shape'):
794
                if obj.Base.Shape.isNull():
795
                    return
796
                if not obj.Base.Shape.isValid():
797
                    if not obj.Base.Shape.Solids:
798
                        # let pass invalid objects if they have solids...
799
                        return
800
                elif obj.Base.Shape.Solids:
801
                    base = obj.Base.Shape.copy()
802
            elif obj.Base.isDerivedFrom("Mesh::Feature"):
803
                if obj.Base.Mesh.isSolid():
804
                    if obj.Base.Mesh.countComponents() == 1:
805
                        sh = ArchCommands.getShapeFromMesh(obj.Base.Mesh)
806
                        if sh.isClosed() and sh.isValid() and sh.Solids and (not sh.isNull()):
807
                            base = sh
808
                        else:
809
                            FreeCAD.Console.PrintWarning(translate("Arch","This mesh is an invalid solid")+"\n")
810
                            obj.Base.ViewObject.show()
811
        if (not base) and (not obj.Additions):
812
            #FreeCAD.Console.PrintError(translate("Arch","Error: Invalid base object")+"\n")
813
            return
814

815
        base = self.processSubShapes(obj,base,pl)
816
        self.applyShape(obj,base,pl)
817

818
    def getExtrusionData(self,obj):
819
        """returns (shape,extrusion vector or path,placement) or None"""
820
        if hasattr(obj,"IfcType"):
821
            IfcType = obj.IfcType
822
        else:
823
            IfcType = None
824
        import Part,DraftGeomUtils
825
        data = ArchComponent.Component.getExtrusionData(self,obj)
826
        if data:
827
            if not isinstance(data[0],list):
828
                # multifuses not considered here
829
                return data
830
        length  = obj.Length.Value
831
        width = obj.Width.Value
832
        height = obj.Height.Value
833
        if not height:
834
            height = self.getParentHeight(obj)
835
        baseface = None
836
        extrusion = None
837
        normal = None
838
        if obj.Base:
839
            if hasattr(obj.Base,'Shape'):
840
                if obj.Base.Shape:
841
                    if obj.Base.Shape.Solids:
842
                        return None
843
                    elif obj.Base.Shape.Faces:
844
                        if not DraftGeomUtils.isCoplanar(obj.Base.Shape.Faces,tol=0.01):
845
                            return None
846
                        else:
847
                            baseface = obj.Base.Shape.copy()
848
                    elif obj.Base.Shape.Wires:
849
                        # ArchSketch feature :
850
                        # Get base shape wires, and faceMaker, for Structure (slab. etc.) from Base Objects if they store and provide by getStructureBaseShapeWires()
851
                        # (thickness, normal/extrusion, length, width, baseface maybe for later) of structure (slab etc.)
852
                        structureBaseShapeWires = None
853
                        baseShapeWires = None                   #baseSlabWires / baseSlabOpeningWires = None
854
                        faceMaker = None
855
                        if hasattr(obj.Base, 'Proxy'):
856
                            if hasattr(obj.Base.Proxy, 'getStructureBaseShapeWires'):
857
                                structureBaseShapeWires = obj.Base.Proxy.getStructureBaseShapeWires(obj.Base, archsketchEdges=obj.ArchSketchEdges)
858
                                # provide selected edges, or groups, in obj.ArchSketchEdges for processing in getStructureBaseShapeWires() (getSortedClusters) as override
859
                                # returned a {dict} ( or a [list] )
860
                        # get slab wires; use original wires if structureBaseShapeWires() provided none
861
                        if structureBaseShapeWires:  # would be false (none) if both base ArchSketch and obj do not have the edges stored / inputted by user
862
                            # if structureBaseShapeWires is {dict}
863
                            baseShapeWires = structureBaseShapeWires.get('slabWires')
864
                            faceMaker = structureBaseShapeWires.get('faceMaker')
865
                        if not baseShapeWires:
866
                            baseShapeWires = obj.Base.Shape.Wires
867
                        if faceMaker or (obj.FaceMaker != "None"):
868
                            if not faceMaker:
869
                                faceMaker = obj.FaceMaker
870
                            try:
871
                                baseface = Part.makeFace(baseShapeWires,"Part::FaceMaker"+str(faceMaker))
872
                            except Exception:
873
                                FreeCAD.Console.PrintError(translate("Arch","Facemaker returned an error")+"\n")
874
                                # Not returning even Part.makeFace fails, fall back to 'non-Part.makeFace' method
875
                        if not baseface:
876
                            for w in baseShapeWires:
877
                                if not w.isClosed():
878
                                    p0 = w.OrderedVertexes[0].Point
879
                                    p1 = w.OrderedVertexes[-1].Point
880
                                    if p0 != p1:
881
                                        e = Part.LineSegment(p0,p1).toShape()
882
                                        w.add(e)
883
                                w.fix(0.1,0,1) # fixes self-intersecting wires
884
                                f = Part.Face(w)
885
                                # check if it is 1st face (f) created from w in baseShapeWires; if not, fuse()
886
                                if baseface:
887
                                    baseface = baseface.fuse(f)
888
                                else:
889
                                    # TODO use Part.Shape() rather than shape.copy() ... ?
890
                                    baseface = f.copy()
891
        elif length and width and height:
892
            if (length > height) and (IfcType != "Slab"):
893
                h2 = height/2 or 0.5
894
                w2 = width/2 or 0.5
895
                v1 = Vector(0,-w2,-h2)
896
                v4 = Vector(0,-w2,h2)
897
                v3 = Vector(0,w2,h2)
898
                v2 = Vector(0,w2,-h2)
899
            else:
900
                l2 = length/2 or 0.5
901
                w2 = width/2 or 0.5
902
                v1 = Vector(-l2,-w2,0)
903
                v2 = Vector(l2,-w2,0)
904
                v3 = Vector(l2,w2,0)
905
                v4 = Vector(-l2,w2,0)
906
            import Part
907
            baseface = Part.Face(Part.makePolygon([v1,v2,v3,v4,v1]))
908
        if baseface:
909
            if hasattr(obj, "Tool") and obj.Tool:
910
                tool = obj.Tool
911
                edges = DraftGeomUtils.get_referenced_edges(tool)
912
                if len(edges) > 0:
913
                    extrusion = Part.Wire(Part.__sortEdges__(edges))
914
                    if hasattr(obj, "ToolOffsetFirst"):
915
                        offset_start = float(obj.ToolOffsetFirst.getValueAs("mm"))
916
                    else:
917
                        offset_start = 0.0
918
                    if hasattr(obj, "ToolOffsetLast"):
919
                        offset_end = float(obj.ToolOffsetLast.getValueAs("mm"))
920
                    else:
921
                        offset_end = 0.0
922
                    if offset_start  != 0.0 or offset_end != 0.0:
923
                        extrusion = DraftGeomUtils.get_extended_wire(extrusion, offset_start, offset_end)
924
                    if hasattr(obj, "BasePerpendicularToTool") and obj.BasePerpendicularToTool:
925
                        pl = FreeCAD.Placement()
926
                        if hasattr(obj, "BaseRotation"):
927
                            pl.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1), -obj.BaseRotation)
928
                        if hasattr(obj, "BaseOffsetX") and hasattr(obj, "BaseOffsetY"):
929
                            pl.translate(FreeCAD.Vector(obj.BaseOffsetX, obj.BaseOffsetY, 0))
930
                        if hasattr(obj, "BaseMirror") and obj.BaseMirror:
931
                            pl.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 1, 0), 180)
932
                        baseface.Placement = DraftGeomUtils.get_placement_perpendicular_to_wire(extrusion).multiply(pl)
933
            else:
934
                if obj.Normal.Length:
935
                    normal = Vector(obj.Normal).normalize()
936
                else:
937
                    normal = baseface.Faces[0].normalAt(0, 0)  ## TODO to use ArchSketch's 'normal' for consistency
938
            base = None
939
            placement = None
940
            inverse_placement = None
941
            if len(baseface.Faces) > 1:
942
                base = []
943
                placement = []
944
                hint = baseface.Faces[0].normalAt(0, 0)  ## TODO anything to do ?
945
                for f in baseface.Faces:
946
                    bf, pf = self.rebase(f, hint)
947
                    base.append(bf)
948
                    placement.append(pf)
949
                inverse_placement = placement[0].inverse()
950
            else:
951
                base, placement = self.rebase(baseface)
952
                inverse_placement = placement.inverse()
953
            if extrusion:
954
                if len(extrusion.Edges) == 1 and DraftGeomUtils.geomType(extrusion.Edges[0]) == "Line":
955
                    extrusion = DraftGeomUtils.vec(extrusion.Edges[0], True)
956
                if isinstance(extrusion, FreeCAD.Vector):
957
                    extrusion = inverse_placement.Rotation.multVec(extrusion)
958
            elif normal:
959
                normal = inverse_placement.Rotation.multVec(normal)
960
                if not normal:
961
                    normal = Vector(0,0,1)
962
                if not normal.Length:
963
                    normal = Vector(0,0,1)
964
                extrusion = normal
965
                if (length > height) and (IfcType != "Slab"):
966
                    if length:
967
                        extrusion = normal.multiply(length)
968
                else:
969
                    if height:
970
                        extrusion = normal.multiply(height)
971
            if extrusion:
972
                return (base, extrusion, placement)
973
        return None
974

975
    def onChanged(self,obj,prop):
976

977
        if hasattr(obj,"IfcType"):
978
            IfcType = obj.IfcType
979
        else:
980
            IfcType = None
981
        self.hideSubobjects(obj,prop)
982
        if prop in ["Shape","ResetNodes","NodesOffset"]:
983
            # ResetNodes is not a property but it allows us to use this function to force reset the nodes
984
            nodes = None
985
            extdata = self.getExtrusionData(obj)
986
            if extdata and not isinstance(extdata[0],list):
987
                nodes = extdata[0]
988
                if IfcType not in ["Slab"]:
989
                    if not isinstance(extdata[1], FreeCAD.Vector):
990
                        nodes = extdata[1]
991
                    elif extdata[1].Length > 0:
992
                        if hasattr(nodes,"CenterOfMass"):
993
                            import Part
994
                            nodes = Part.LineSegment(nodes.CenterOfMass,nodes.CenterOfMass.add(extdata[1])).toShape()
995
                if isinstance(extdata[1], FreeCAD.Vector):
996
                    nodes.Placement = nodes.Placement.multiply(extdata[2])
997
            offset = FreeCAD.Vector()
998
            if hasattr(obj,"NodesOffset"):
999
                offset = FreeCAD.Vector(0,0,obj.NodesOffset.Value)
1000
            if obj.Nodes and (prop != "ResetNodes"):
1001
                if hasattr(self,"nodes"):
1002
                    if self.nodes:
1003
                        if obj.Nodes != self.nodes:
1004
                            # nodes are set manually: don't touch them
1005
                            return
1006
                else:
1007
                    # nodes haven't been calculated yet, but are set (file load)
1008
                    # we set the nodes now but don't change the property
1009
                    if nodes:
1010
                        self.nodes = [v.Point.add(offset) for v in nodes.Vertexes]
1011
                        return
1012
            # we set the nodes
1013
            if nodes:
1014
                self.nodes = [v.Point.add(offset) for v in nodes.Vertexes]
1015
                obj.Nodes = self.nodes
1016
        ArchComponent.Component.onChanged(self,obj,prop)
1017

1018
    def getNodeEdges(self,obj):
1019

1020
        "returns a list of edges from structural nodes"
1021

1022
        edges = []
1023
        if obj.Nodes:
1024
            import Part
1025
            for i in range(len(obj.Nodes)-1):
1026
                edges.append(Part.LineSegment(obj.Placement.multVec(obj.Nodes[i]),obj.Placement.multVec(obj.Nodes[i+1])).toShape())
1027
            if hasattr(obj.ViewObject,"NodeType"):
1028
                if (obj.ViewObject.NodeType == "Area") and (len(obj.Nodes) > 2):
1029
                    edges.append(Part.LineSegment(obj.Placement.multVec(obj.Nodes[-1]),obj.Placement.multVec(obj.Nodes[0])).toShape())
1030
        return edges
1031

1032

1033
class _ViewProviderStructure(ArchComponent.ViewProviderComponent):
1034

1035
    "A View Provider for the Structure object"
1036

1037
    def __init__(self,vobj):
1038

1039
        ArchComponent.ViewProviderComponent.__init__(self,vobj)
1040

1041
        # setProperties of ArchComponent will be overwritten
1042
        # thus setProperties from ArchComponent will be explicit called to get the properties
1043
        ArchComponent.ViewProviderComponent.setProperties(self, vobj)
1044

1045
        self.setProperties(vobj)
1046
        vobj.ShapeColor = ArchCommands.getDefaultColor("Structure")
1047

1048
    def setProperties(self,vobj):
1049

1050
        pl = vobj.PropertiesList
1051
        if not "ShowNodes" in pl:
1052
            vobj.addProperty("App::PropertyBool","ShowNodes","Nodes",QT_TRANSLATE_NOOP("App::Property","If the nodes are visible or not")).ShowNodes = False
1053
        if not "NodeLine" in pl:
1054
            vobj.addProperty("App::PropertyFloat","NodeLine","Nodes",QT_TRANSLATE_NOOP("App::Property","The width of the nodes line"))
1055
        if not "NodeSize" in pl:
1056
            vobj.addProperty("App::PropertyFloat","NodeSize","Nodes",QT_TRANSLATE_NOOP("App::Property","The size of the node points"))
1057
            vobj.NodeSize = 6
1058
        if not "NodeColor" in pl:
1059
            vobj.addProperty("App::PropertyColor","NodeColor","Nodes",QT_TRANSLATE_NOOP("App::Property","The color of the nodes line"))
1060
            vobj.NodeColor = (1.0,1.0,1.0,1.0)
1061
        if not "NodeType" in pl:
1062
            vobj.addProperty("App::PropertyEnumeration","NodeType","Nodes",QT_TRANSLATE_NOOP("App::Property","The type of structural node"))
1063
            vobj.NodeType = ["Linear","Area"]
1064

1065
    def onDocumentRestored(self,vobj):
1066

1067
        self.setProperties(vobj)
1068

1069
    def getIcon(self):
1070

1071
        import Arch_rc
1072
        if hasattr(self,"Object"):
1073
            if hasattr(self.Object,"CloneOf"):
1074
                if self.Object.CloneOf:
1075
                    return ":/icons/Arch_Structure_Clone.svg"
1076
        return ":/icons/Arch_Structure_Tree.svg"
1077

1078
    def updateData(self,obj,prop):
1079

1080
        if prop == "Nodes":
1081
            if obj.Nodes:
1082
                if hasattr(self,"nodes"):
1083
                    p = []
1084
                    self.pointset.numPoints.setValue(0)
1085
                    self.lineset.coordIndex.deleteValues(0)
1086
                    self.faceset.coordIndex.deleteValues(0)
1087
                    for n in obj.Nodes:
1088
                        p.append([n.x,n.y,n.z])
1089
                    self.coords.point.setValues(0,len(p),p)
1090
                    self.pointset.numPoints.setValue(len(p))
1091
                    self.lineset.coordIndex.setValues(0,len(p)+1,list(range(len(p)))+[-1])
1092
                    if hasattr(obj.ViewObject,"NodeType"):
1093
                        if (obj.ViewObject.NodeType == "Area") and (len(p) > 2):
1094
                            self.coords.point.set1Value(len(p),p[0][0],p[0][1],p[0][2])
1095
                            self.lineset.coordIndex.setValues(0,len(p)+2,list(range(len(p)+1))+[-1])
1096
                            self.faceset.coordIndex.setValues(0,len(p)+1,list(range(len(p)))+[-1])
1097

1098
        elif prop in ["IfcType"]:
1099
            if hasattr(obj.ViewObject,"NodeType"):
1100
                if hasattr(obj,"IfcType"):
1101
                    IfcType = obj.IfcType
1102
                else:
1103
                    IfcType = None
1104
                if IfcType == "Slab":
1105
                    obj.ViewObject.NodeType = "Area"
1106
                else:
1107
                    obj.ViewObject.NodeType = "Linear"
1108
        else:
1109
            ArchComponent.ViewProviderComponent.updateData(self,obj,prop)
1110

1111
    def onChanged(self,vobj,prop):
1112

1113
        if prop == "ShowNodes":
1114
            if hasattr(self,"nodes"):
1115
                vobj.Annotation.removeChild(self.nodes)
1116
                del self.nodes
1117
            if vobj.ShowNodes:
1118
                from pivy import coin
1119
                self.nodes = coin.SoAnnotation()
1120
                self.coords = coin.SoCoordinate3()
1121
                self.mat = coin.SoMaterial()
1122
                self.pointstyle = coin.SoDrawStyle()
1123
                self.pointstyle.style = coin.SoDrawStyle.POINTS
1124
                self.pointset = coin.SoType.fromName("SoBrepPointSet").createInstance()
1125
                self.linestyle = coin.SoDrawStyle()
1126
                self.linestyle.style = coin.SoDrawStyle.LINES
1127
                self.lineset = coin.SoType.fromName("SoBrepEdgeSet").createInstance()
1128
                self.facestyle = coin.SoDrawStyle()
1129
                self.facestyle.style = coin.SoDrawStyle.FILLED
1130
                self.shapehints = coin.SoShapeHints()
1131
                self.shapehints.faceType = coin.SoShapeHints.UNKNOWN_FACE_TYPE
1132
                self.fmat = coin.SoMaterial()
1133
                self.fmat.transparency.setValue(0.75)
1134
                self.faceset = coin.SoIndexedFaceSet()
1135
                self.nodes.addChild(self.coords)
1136
                self.nodes.addChild(self.mat)
1137
                self.nodes.addChild(self.pointstyle)
1138
                self.nodes.addChild(self.pointset)
1139
                self.nodes.addChild(self.linestyle)
1140
                self.nodes.addChild(self.lineset)
1141
                self.nodes.addChild(self.facestyle)
1142
                self.nodes.addChild(self.shapehints)
1143
                self.nodes.addChild(self.fmat)
1144
                self.nodes.addChild(self.faceset)
1145
                vobj.Annotation.addChild(self.nodes)
1146
                self.updateData(vobj.Object,"Nodes")
1147
                self.onChanged(vobj,"NodeColor")
1148
                self.onChanged(vobj,"NodeLine")
1149
                self.onChanged(vobj,"NodeSize")
1150

1151
        elif prop == "NodeColor":
1152
            if hasattr(self,"mat"):
1153
                l = vobj.NodeColor
1154
                self.mat.diffuseColor.setValue([l[0],l[1],l[2]])
1155
                self.fmat.diffuseColor.setValue([l[0],l[1],l[2]])
1156

1157
        elif prop == "NodeLine":
1158
            if hasattr(self,"linestyle"):
1159
                self.linestyle.lineWidth = vobj.NodeLine
1160

1161
        elif prop == "NodeSize":
1162
            if hasattr(self,"pointstyle"):
1163
                self.pointstyle.pointSize = vobj.NodeSize
1164

1165
        elif prop == "NodeType":
1166
            self.updateData(vobj.Object,"Nodes")
1167

1168
        else:
1169
            ArchComponent.ViewProviderComponent.onChanged(self,vobj,prop)
1170

1171
    def setEdit(self,vobj,mode):
1172
        if mode != 0:
1173
            return None
1174

1175
        taskd = StructureTaskPanel(vobj.Object)
1176
        taskd.obj = self.Object
1177
        taskd.update()
1178
        FreeCADGui.Control.showDialog(taskd)
1179
        return True
1180

1181

1182
class StructureTaskPanel(ArchComponent.ComponentTaskPanel):
1183

1184
    def __init__(self,obj):
1185

1186
        ArchComponent.ComponentTaskPanel.__init__(self)
1187
        self.nodes_widget = QtGui.QWidget()
1188
        self.nodes_widget.setWindowTitle(QtGui.QApplication.translate("Arch", "Node Tools", None))
1189
        lay = QtGui.QVBoxLayout(self.nodes_widget)
1190

1191
        self.resetButton = QtGui.QPushButton(self.nodes_widget)
1192
        self.resetButton.setIcon(QtGui.QIcon(":/icons/edit-undo.svg"))
1193
        self.resetButton.setText(QtGui.QApplication.translate("Arch", "Reset nodes", None))
1194

1195
        lay.addWidget(self.resetButton)
1196
        QtCore.QObject.connect(self.resetButton, QtCore.SIGNAL("clicked()"), self.resetNodes)
1197

1198
        self.editButton = QtGui.QPushButton(self.nodes_widget)
1199
        self.editButton.setIcon(QtGui.QIcon(":/icons/Draft_Edit.svg"))
1200
        self.editButton.setText(QtGui.QApplication.translate("Arch", "Edit nodes", None))
1201
        lay.addWidget(self.editButton)
1202
        QtCore.QObject.connect(self.editButton, QtCore.SIGNAL("clicked()"), self.editNodes)
1203

1204
        self.extendButton = QtGui.QPushButton(self.nodes_widget)
1205
        self.extendButton.setIcon(QtGui.QIcon(":/icons/Snap_Perpendicular.svg"))
1206
        self.extendButton.setText(QtGui.QApplication.translate("Arch", "Extend nodes", None))
1207
        self.extendButton.setToolTip(QtGui.QApplication.translate("Arch", "Extends the nodes of this element to reach the nodes of another element", None))
1208
        lay.addWidget(self.extendButton)
1209
        QtCore.QObject.connect(self.extendButton, QtCore.SIGNAL("clicked()"), self.extendNodes)
1210

1211
        self.connectButton = QtGui.QPushButton(self.nodes_widget)
1212
        self.connectButton.setIcon(QtGui.QIcon(":/icons/Snap_Intersection.svg"))
1213
        self.connectButton.setText(QtGui.QApplication.translate("Arch", "Connect nodes", None))
1214
        self.connectButton.setToolTip(QtGui.QApplication.translate("Arch", "Connects nodes of this element with the nodes of another element", None))
1215
        lay.addWidget(self.connectButton)
1216
        QtCore.QObject.connect(self.connectButton, QtCore.SIGNAL("clicked()"), self.connectNodes)
1217

1218
        self.toggleButton = QtGui.QPushButton(self.nodes_widget)
1219
        self.toggleButton.setIcon(QtGui.QIcon(":/icons/dagViewVisible.svg"))
1220
        self.toggleButton.setText(QtGui.QApplication.translate("Arch", "Toggle all nodes", None))
1221
        self.toggleButton.setToolTip(QtGui.QApplication.translate("Arch", "Toggles all structural nodes of the document on/off", None))
1222
        lay.addWidget(self.toggleButton)
1223
        QtCore.QObject.connect(self.toggleButton, QtCore.SIGNAL("clicked()"), self.toggleNodes)
1224

1225
        self.extrusion_widget = QtGui.QWidget()
1226
        self.extrusion_widget.setWindowTitle(QtGui.QApplication.translate("Arch", "Extrusion Tools", None))
1227
        lay = QtGui.QVBoxLayout(self.extrusion_widget)
1228

1229
        self.selectToolButton = QtGui.QPushButton(self.extrusion_widget)
1230
        self.selectToolButton.setIcon(QtGui.QIcon())
1231
        self.selectToolButton.setText(QtGui.QApplication.translate("Arch", "Select tool...", None))
1232
        self.selectToolButton.setToolTip(QtGui.QApplication.translate("Arch", "Select object or edges to be used as a Tool (extrusion path)", None))
1233
        lay.addWidget(self.selectToolButton)
1234
        QtCore.QObject.connect(self.selectToolButton, QtCore.SIGNAL("clicked()"), self.setSelectionFromTool)
1235

1236
        self.form = [self.form, self.nodes_widget, self.extrusion_widget]
1237
        self.Object = obj
1238
        self.observer = None
1239
        self.nodevis = None
1240

1241
    def editNodes(self):
1242

1243
        FreeCADGui.Control.closeDialog()
1244
        FreeCADGui.runCommand("Draft_Edit")
1245

1246
    def resetNodes(self):
1247

1248
        self.Object.Proxy.onChanged(self.Object,"ResetNodes")
1249

1250
    def extendNodes(self,other=None):
1251

1252
        if not other:
1253
            self.observer = StructSelectionObserver(self.extendNodes)
1254
            FreeCADGui.Selection.addObserver(self.observer)
1255
            FreeCAD.Console.PrintMessage(translate("Arch","Choose another Structure object:"))
1256
        else:
1257
            FreeCADGui.Selection.removeObserver(self.observer)
1258
            self.observer = None
1259
            if Draft.getType(other) != "Structure":
1260
                FreeCAD.Console.PrintError(translate("Arch","The chosen object is not a Structure")+"\n")
1261
            else:
1262
                if not other.Nodes:
1263
                    FreeCAD.Console.PrintError(translate("Arch","The chosen object has no structural nodes")+"\n")
1264
                else:
1265
                    if (len(self.Object.Nodes) != 2) or (len(other.Nodes) != 2):
1266
                        FreeCAD.Console.PrintError(translate("Arch","One of these objects has more than 2 nodes")+"\n")
1267
                    else:
1268
                        import DraftGeomUtils
1269
                        nodes1 = [self.Object.Placement.multVec(v) for v in self.Object.Nodes]
1270
                        nodes2 = [other.Placement.multVec(v) for v in other.Nodes]
1271
                        intersect = DraftGeomUtils.findIntersection(nodes1[0],nodes1[1],nodes2[0],nodes2[1],True,True)
1272
                        if not intersect:
1273
                            FreeCAD.Console.PrintError(translate("Arch","Unable to find a suitable intersection point")+"\n")
1274
                        else:
1275
                            intersect = intersect[0]
1276
                            FreeCAD.Console.PrintMessage(translate("Arch","Intersection found.\n"))
1277
                            if DraftGeomUtils.findClosest(intersect,nodes1) == 0:
1278
                                self.Object.Nodes = [self.Object.Placement.inverse().multVec(intersect),self.Object.Nodes[1]]
1279
                            else:
1280
                                self.Object.Nodes = [self.Object.Nodes[0],self.Object.Placement.inverse().multVec(intersect)]
1281

1282
    def connectNodes(self,other=None):
1283

1284
        if not other:
1285
            self.observer = StructSelectionObserver(self.connectNodes)
1286
            FreeCADGui.Selection.addObserver(self.observer)
1287
            FreeCAD.Console.PrintMessage(translate("Arch","Choose another Structure object:"))
1288
        else:
1289
            FreeCADGui.Selection.removeObserver(self.observer)
1290
            self.observer = None
1291
            if Draft.getType(other) != "Structure":
1292
                FreeCAD.Console.PrintError(translate("Arch","The chosen object is not a Structure")+"\n")
1293
            else:
1294
                if not other.Nodes:
1295
                    FreeCAD.Console.PrintError(translate("Arch","The chosen object has no structural nodes")+"\n")
1296
                else:
1297
                    if (len(self.Object.Nodes) != 2) or (len(other.Nodes) != 2):
1298
                        FreeCAD.Console.PrintError(translate("Arch","One of these objects has more than 2 nodes")+"\n")
1299
                    else:
1300
                        import DraftGeomUtils
1301
                        nodes1 = [self.Object.Placement.multVec(v) for v in self.Object.Nodes]
1302
                        nodes2 = [other.Placement.multVec(v) for v in other.Nodes]
1303
                        intersect = DraftGeomUtils.findIntersection(nodes1[0],nodes1[1],nodes2[0],nodes2[1],True,True)
1304
                        if not intersect:
1305
                            FreeCAD.Console.PrintError(translate("Arch","Unable to find a suitable intersection point")+"\n")
1306
                        else:
1307
                            intersect = intersect[0]
1308
                            FreeCAD.Console.PrintMessage(translate("Arch","Intersection found.")+"\n")
1309
                            if DraftGeomUtils.findClosest(intersect,nodes1) == 0:
1310
                                self.Object.Nodes = [self.Object.Placement.inverse().multVec(intersect),self.Object.Nodes[1]]
1311
                            else:
1312
                                self.Object.Nodes = [self.Object.Nodes[0],self.Object.Placement.inverse().multVec(intersect)]
1313
                            if DraftGeomUtils.findClosest(intersect,nodes2) == 0:
1314
                                other.Nodes = [other.Placement.inverse().multVec(intersect),other.Nodes[1]]
1315
                            else:
1316
                                other.Nodes = [other.Nodes[0],other.Placement.inverse().multVec(intersect)]
1317

1318
    def toggleNodes(self):
1319

1320
        if self.nodevis:
1321
            for obj in self.nodevis:
1322
                obj[0].ViewObject.ShowNodes = obj[1]
1323
            self.nodevis = None
1324
        else:
1325
            self.nodevis = []
1326
            for obj in FreeCAD.ActiveDocument.Objects:
1327
                if hasattr(obj.ViewObject,"ShowNodes"):
1328
                    self.nodevis.append([obj,obj.ViewObject.ShowNodes])
1329
                    obj.ViewObject.ShowNodes = True
1330

1331
    def setSelectionFromTool(self):
1332
        FreeCADGui.Selection.clearSelection()
1333
        if hasattr(self.Object, "Tool"):
1334
            tool = self.Object.Tool
1335
            if hasattr(tool, "Shape") and tool.Shape:
1336
                FreeCADGui.Selection.addSelection(tool)
1337
            else:
1338
                if not isinstance(tool, list):
1339
                    tool = [tool]
1340
                for o, subs in tool:
1341
                    FreeCADGui.Selection.addSelection(o, subs)
1342
        QtCore.QObject.disconnect(self.selectToolButton, QtCore.SIGNAL("clicked()"), self.setSelectionFromTool)
1343
        QtCore.QObject.connect(self.selectToolButton, QtCore.SIGNAL("clicked()"), self.setToolFromSelection)
1344
        self.selectToolButton.setText(QtGui.QApplication.translate("Arch", "Done", None))
1345

1346
    def setToolFromSelection(self):
1347
        objectList = []
1348
        selEx = FreeCADGui.Selection.getSelectionEx()
1349
        for selExi in selEx:
1350
            if len(selExi.SubElementNames) == 0:
1351
                # Add entirely selected objects
1352
                objectList.append(selExi.Object)
1353
            else:
1354
                subElementsNames = [subElementName for subElementName in selExi.SubElementNames if subElementName.startswith("Edge")]
1355
                # Check that at least an edge is selected from the object's shape
1356
                if len(subElementsNames) > 0:
1357
                    objectList.append((selExi.Object, subElementsNames))
1358
        if self.Object.getTypeIdOfProperty("Tool") != "App::PropertyLinkSubList":
1359
            # Upgrade property Tool from App::PropertyLink to App::PropertyLinkSubList (note: Undo/Redo fails)
1360
            self.Object.removeProperty("Tool")
1361
            self.Object.addProperty("App::PropertyLinkSubList", "Tool", "Structure", QT_TRANSLATE_NOOP("App::Property", "An optional extrusion path for this element"))
1362
        self.Object.Tool = objectList
1363
        QtCore.QObject.disconnect(self.selectToolButton, QtCore.SIGNAL("clicked()"), self.setToolFromSelection)
1364
        QtCore.QObject.connect(self.selectToolButton, QtCore.SIGNAL("clicked()"), self.setSelectionFromTool)
1365
        self.selectToolButton.setText(QtGui.QApplication.translate("Arch", "Select tool...", None))
1366

1367
    def accept(self):
1368

1369
        if self.observer:
1370
            FreeCADGui.Selection.removeObserver(self.observer)
1371
        if self.nodevis:
1372
            self.toggleNodes()
1373
        FreeCAD.ActiveDocument.recompute()
1374
        FreeCADGui.ActiveDocument.resetEdit()
1375
        return True
1376

1377

1378
class StructSelectionObserver:
1379

1380
    def __init__(self,callback):
1381
        self.callback = callback
1382

1383
    def addSelection(self, docName, objName, sub, pos):
1384
        print("got ",objName)
1385
        obj = FreeCAD.getDocument(docName).getObject(objName)
1386
        self.callback(obj)
1387

1388

1389
class _StructuralSystem(ArchComponent.Component): # OBSOLETE - All Arch objects can now be based on axes
1390

1391
    "The Structural System object"
1392

1393
    def __init__(self,obj):
1394

1395
        ArchComponent.Component.__init__(self,obj)
1396
        obj.addProperty("App::PropertyLinkList","Axes","Arch",QT_TRANSLATE_NOOP("App::Property","Axes systems this structure is built on"))
1397
        obj.addProperty("App::PropertyIntegerList","Exclude","Arch",QT_TRANSLATE_NOOP("App::Property","The element numbers to exclude when this structure is based on axes"))
1398
        obj.addProperty("App::PropertyBool","Align","Arch",QT_TRANSLATE_NOOP("App::Property","If true the element are aligned with axes")).Align = False
1399
        self.Type = "StructuralSystem"
1400

1401
    def execute(self,obj):
1402
        "creates the structure shape"
1403

1404
        import Part, DraftGeomUtils
1405

1406
        # creating base shape
1407
        pl = obj.Placement
1408
        if obj.Base:
1409
            if hasattr(obj.Base,'Shape'):
1410
                if obj.Base.Shape.isNull():
1411
                    return
1412
                if not obj.Base.Shape.Solids:
1413
                    return
1414

1415
                base = None
1416

1417
                # applying axes
1418
                pts = self.getAxisPoints(obj)
1419
                if hasattr(obj,"Align"):
1420
                    if obj.Align == False :
1421
                        apl = self.getAxisPlacement(obj)
1422
                    if obj.Align:
1423
                        apl = None
1424
                else :
1425
                    apl = self.getAxisPlacement(obj)
1426

1427
                if pts:
1428
                    fsh = []
1429
                    for i in range(len(pts)):
1430
                        sh = obj.Base.Shape.copy()
1431
                        if hasattr(obj,"Exclude"):
1432
                            if i in obj.Exclude:
1433
                                continue
1434
                        if apl:
1435
                            sh.Placement.Rotation = sh.Placement.Rotation.multiply(apl.Rotation)
1436
                        sh.translate(pts[i])
1437
                        fsh.append(sh)
1438

1439
                    if fsh:
1440
                        base = Part.makeCompound(fsh)
1441
                        base = self.processSubShapes(obj,base,pl)
1442

1443
                if base:
1444
                    if not base.isNull():
1445
                        if base.isValid() and base.Solids:
1446
                            if base.Volume < 0:
1447
                                base.reverse()
1448
                            if base.Volume < 0:
1449
                                FreeCAD.Console.PrintError(translate("Arch","Couldn't compute a shape"))
1450
                                return
1451
                            base = base.removeSplitter()
1452
                            obj.Shape = base
1453
                            if not pl.isNull():
1454
                                obj.Placement = pl
1455

1456
    def getAxisPoints(self,obj):
1457
        "returns the gridpoints of linked axes"
1458
        import DraftGeomUtils
1459
        pts = []
1460
        if len(obj.Axes) == 1:
1461
            if hasattr(obj,"Align"):
1462
                if obj.Align:
1463
                    p0 = obj.Axes[0].Shape.Edges[0].Vertexes[1].Point
1464
                    for e in obj.Axes[0].Shape.Edges:
1465
                        p = e.Vertexes[1].Point
1466
                        p = p.sub(p0)
1467
                        pts.append(p)
1468
                else:
1469
                    for e in obj.Axes[0].Shape.Edges:
1470
                        pts.append(e.Vertexes[0].Point)
1471
            else:
1472
                for e in obj.Axes[0].Shape.Edges:
1473
                        pts.append(e.Vertexes[0].Point)
1474
        elif len(obj.Axes) >= 2:
1475
            set1 = obj.Axes[0].Shape.Edges
1476
            set2 = obj.Axes[1].Shape.Edges
1477
            for e1 in set1:
1478
                for e2 in set2:
1479
                    pts.extend(DraftGeomUtils.findIntersection(e1,e2))
1480
        return pts
1481

1482
    def getAxisPlacement(self,obj):
1483
        "returns an axis placement"
1484
        if obj.Axes:
1485
            return obj.Axes[0].Placement
1486
        return None
1487

1488

1489
class _ViewProviderStructuralSystem(ArchComponent.ViewProviderComponent):
1490

1491
    "A View Provider for the Structural System object"
1492

1493
    def getIcon(self):
1494

1495
        import Arch_rc
1496
        return ":/icons/Arch_StructuralSystem_Tree.svg"
1497

1498

1499
if FreeCAD.GuiUp:
1500
    FreeCADGui.addCommand("Arch_Structure", _CommandStructure())
1501
    FreeCADGui.addCommand("Arch_StructuralSystem", CommandStructuralSystem())
1502
    FreeCADGui.addCommand("Arch_StructuresFromSelection", CommandStructuresFromSelection())
1503

1504
    class _ArchStructureGroupCommand:
1505

1506
        def GetCommands(self):
1507
            return ("Arch_Structure", "Arch_StructuralSystem", "Arch_StructuresFromSelection")
1508
        def GetResources(self):
1509
            return { "MenuText": QT_TRANSLATE_NOOP("Arch_StructureTools", "Structure tools"),
1510
                     "ToolTip": QT_TRANSLATE_NOOP("Arch_StructureTools", "Structure tools")
1511
                   }
1512
        def IsActive(self):
1513
            return not FreeCAD.ActiveDocument is None
1514

1515
    FreeCADGui.addCommand("Arch_StructureTools", _ArchStructureGroupCommand())
1516

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

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

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

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