FreeCAD

Форк
0
/
ArchPanel.py 
1077 строк · 47.5 Кб
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
import math
23

24
import FreeCAD
25
import ArchCommands
26
import ArchComponent
27
import Draft
28
import DraftVecUtils
29
import Part
30
from FreeCAD import Vector
31
from draftutils import params
32

33
if FreeCAD.GuiUp:
34
    import FreeCADGui
35
    from PySide import QtCore, QtGui
36
    from draftutils.translate import translate
37
    from PySide.QtCore import QT_TRANSLATE_NOOP
38
else:
39
    # \cond
40
    def translate(ctxt,txt):
41
        return txt
42
    def QT_TRANSLATE_NOOP(ctxt,txt):
43
        return txt
44
    # \endcond
45

46
## @package ArchPanel
47
#  \ingroup ARCH
48
#  \brief The Panel object and tools
49
#
50
#  This module provides tools to build Panel objects.
51
#  Panels consist of a closed shape that gets extruded to
52
#  produce a flat object.
53

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

58

59
class _Panel(ArchComponent.Component):
60

61
    "The Panel object"
62

63
    def __init__(self,obj):
64

65
        ArchComponent.Component.__init__(self,obj)
66
        self.setProperties(obj)
67
        obj.IfcType = "Plate"
68

69
    def setProperties(self,obj):
70

71
        pl = obj.PropertiesList
72
        if not "Length" in pl:
73
            obj.addProperty("App::PropertyLength","Length","Panel",   QT_TRANSLATE_NOOP("App::Property","The length of this element, if not based on a profile"))
74
        if not "Width" in pl:
75
            obj.addProperty("App::PropertyLength","Width","Panel",    QT_TRANSLATE_NOOP("App::Property","The width of this element, if not based on a profile"))
76
        if not "Thickness" in pl:
77
            obj.addProperty("App::PropertyLength","Thickness","Panel",QT_TRANSLATE_NOOP("App::Property","The thickness or extrusion depth of this element"))
78
        if not "Sheets" in pl:
79
            obj.addProperty("App::PropertyInteger","Sheets","Panel",  QT_TRANSLATE_NOOP("App::Property","The number of sheets to use"))
80
            obj.Sheets = 1
81
        if not "Offset" in pl:
82
            obj.addProperty("App::PropertyDistance","Offset","Panel",   QT_TRANSLATE_NOOP("App::Property","The offset between this panel and its baseline"))
83
        if not "WaveLength" in pl:
84
            obj.addProperty("App::PropertyLength","WaveLength","Panel", QT_TRANSLATE_NOOP("App::Property","The length of waves for corrugated elements"))
85
        if not "WaveHeight" in pl:
86
            obj.addProperty("App::PropertyLength","WaveHeight","Panel", QT_TRANSLATE_NOOP("App::Property","The height of waves for corrugated elements"))
87
        if not "WaveOffset" in pl:
88
            obj.addProperty("App::PropertyDistance","WaveOffset","Panel", QT_TRANSLATE_NOOP("App::Property","The horizontal offset of waves for corrugated elements"))
89
        if not "WaveDirection" in pl:
90
            obj.addProperty("App::PropertyAngle","WaveDirection","Panel", QT_TRANSLATE_NOOP("App::Property","The direction of waves for corrugated elements"))
91
        if not "WaveType" in pl:
92
            obj.addProperty("App::PropertyEnumeration","WaveType","Panel", QT_TRANSLATE_NOOP("App::Property","The type of waves for corrugated elements"))
93
            obj.WaveType = ["Curved","Trapezoidal","Spikes"]
94
        if not "WaveBottom" in pl:
95
            obj.addProperty("App::PropertyBool","WaveBottom","Panel", QT_TRANSLATE_NOOP("App::Property","If the wave also affects the bottom side or not"))
96
        if not "Area" in pl:
97
            obj.addProperty("App::PropertyArea","Area","Panel",       QT_TRANSLATE_NOOP("App::Property","The area of this panel"))
98
        if not "FaceMaker" in pl:
99
            obj.addProperty("App::PropertyEnumeration","FaceMaker","Panel",QT_TRANSLATE_NOOP("App::Property","The facemaker type to use to build the profile of this object"))
100
            obj.FaceMaker = ["None","Simple","Cheese","Bullseye"]
101
        if not "Normal" in pl:
102
            obj.addProperty("App::PropertyVector","Normal","Panel",QT_TRANSLATE_NOOP("App::Property","The normal extrusion direction of this object (keep (0,0,0) for automatic normal)"))
103
        self.Type = "Panel"
104
        obj.setEditorMode("VerticalArea",2)
105
        obj.setEditorMode("HorizontalArea",2)
106

107
    def onDocumentRestored(self,obj):
108

109
        ArchComponent.Component.onDocumentRestored(self,obj)
110
        self.setProperties(obj)
111

112
    def execute(self,obj):
113

114
        "creates the panel shape"
115

116
        if self.clone(obj):
117
            return
118

119
        layers = []
120
        length = 0
121
        width = 0
122
        thickness = 0
123

124
        # base tests
125
        if obj.Base:
126
            if hasattr(obj.Base,'Shape'):
127
                if obj.Base.Shape.isNull():
128
                    return
129
            elif obj.Base.isDerivedFrom("Mesh::Feature"):
130
                if not obj.Base.Mesh.isSolid():
131
                    return
132
        else:
133
            if obj.Length.Value:
134
                length = obj.Length.Value
135
            else:
136
                return
137
            if obj.Width.Value:
138
                width = obj.Width.Value
139
            else:
140
                return
141
        if obj.Thickness.Value:
142
            thickness = obj.Thickness.Value
143
        else:
144
            if not obj.Base:
145
                return
146
            elif hasattr(obj.Base,'Shape'):
147
                if not obj.Base.Shape.Solids:
148
                    return
149
        if hasattr(obj,"Material"):
150
            if obj.Material:
151
                if hasattr(obj.Material,"Materials"):
152
                    varwidth = 0
153
                    thicknesses = [t for t in obj.Material.Thicknesses if t >= 0]
154
                    restwidth = thickness - sum(thicknesses)
155
                    if restwidth > 0:
156
                        varwidth = [t for t in thicknesses if t == 0]
157
                        if varwidth:
158
                            varwidth = restwidth/len(varwidth)
159
                    for t in obj.Material.Thicknesses:
160
                        if t:
161
                            layers.append(t)
162
                        elif varwidth:
163
                            layers.append(varwidth)
164
        # creating base shape
165
        pl = obj.Placement
166
        base = None
167
        normal = None
168
        if hasattr(obj,"Normal"):
169
            if obj.Normal.Length > 0:
170
                normal = Vector(obj.Normal)
171
                normal.normalize()
172
                normal.multiply(thickness)
173
        baseprofile = None
174
        if obj.Base:
175
            base = obj.Base.Shape.copy()
176
            if not base.Solids:
177
               # p = FreeCAD.Placement(obj.Base.Placement)
178
                if base.Faces:
179
                    baseprofile = base
180
                    if not normal:
181
                        normal = baseprofile.Faces[0].normalAt(0,0).multiply(thickness)
182
                    if layers:
183
                        layeroffset = 0
184
                        shps = []
185
                        for l in layers:
186
                            if l >= 0:
187
                                n = Vector(normal).normalize().multiply(abs(l))
188
                                b = base.extrude(n)
189
                                if layeroffset:
190
                                    o = Vector(normal).normalize().multiply(layeroffset)
191
                                    b.translate(o)
192
                                shps.append(b)
193
                            layeroffset += abs(l)
194
                        base = Part.makeCompound(shps)
195
                    else:
196
                        base = base.extrude(normal)
197
                elif base.Wires:
198
                    fm = False
199
                    if hasattr(obj,"FaceMaker"):
200
                        if obj.FaceMaker != "None":
201
                            try:
202
                                baseprofile = Part.makeFace(base.Wires,"Part::FaceMaker"+str(obj.FaceMaker))
203
                                fm = True
204
                            except Exception:
205
                                FreeCAD.Console.PrintError(translate("Arch","Facemaker returned an error")+"\n")
206
                                return
207
                    if not fm:
208
                        closed = True
209
                        for w in base.Wires:
210
                            if not w.isClosed():
211
                                closed = False
212
                        if closed:
213
                            baseprofile = ArchCommands.makeFace(base.Wires)
214
                    if not normal:
215
                        normal = baseprofile.normalAt(0,0).multiply(thickness)
216
                    if layers:
217
                        layeroffset = 0
218
                        shps = []
219
                        for l in layers:
220
                            if l >= 0:
221
                                n = Vector(normal).normalize().multiply(abs(l))
222
                                b = baseprofile.extrude(n)
223
                                if layeroffset:
224
                                    o = Vector(normal).normalize().multiply(layeroffset)
225
                                    b.translate(o)
226
                                shps.append(b)
227
                            layeroffset += abs(l)
228
                        base = Part.makeCompound(shps)
229
                    else:
230
                        base = baseprofile.extrude(normal)
231
                elif obj.Base.isDerivedFrom("Mesh::Feature"):
232
                    if obj.Base.Mesh.isSolid():
233
                        if obj.Base.Mesh.countComponents() == 1:
234
                            sh = ArchCommands.getShapeFromMesh(obj.Base.Mesh)
235
                            if sh.isClosed() and sh.isValid() and sh.Solids:
236
                                base = sh
237
        else:
238
            if layers:
239
                shps = []
240
                layeroffset = 0
241
                for l in layers:
242
                    if l >= 0:
243
                        if normal:
244
                            n = Vector(normal).normalize().multiply(l)
245
                        else:
246
                            n = Vector(0,0,1).multiply(abs(l))
247
                        l2 = length/2 or 0.5
248
                        w2 = width/2 or 0.5
249
                        v1 = Vector(-l2,-w2,layeroffset)
250
                        v2 = Vector(l2,-w2,layeroffset)
251
                        v3 = Vector(l2,w2,layeroffset)
252
                        v4 = Vector(-l2,w2,layeroffset)
253
                        base = Part.makePolygon([v1,v2,v3,v4,v1])
254
                        baseprofile = Part.Face(base)
255
                        base = baseprofile.extrude(n)
256
                        shps.append(base)
257
                    layeroffset += abs(l)
258
                base = Part.makeCompound(shps)
259
            else:
260
                if not normal:
261
                    normal = Vector(0,0,1).multiply(thickness)
262
                l2 = length/2 or 0.5
263
                w2 = width/2 or 0.5
264
                v1 = Vector(-l2,-w2,0)
265
                v2 = Vector(l2,-w2,0)
266
                v3 = Vector(l2,w2,0)
267
                v4 = Vector(-l2,w2,0)
268
                base = Part.makePolygon([v1,v2,v3,v4,v1])
269
                baseprofile = Part.Face(base)
270
                base = baseprofile.extrude(normal)
271

272
        if hasattr(obj,"Area"):
273
            if baseprofile:
274
                obj.Area = baseprofile.Area
275

276
        if hasattr(obj,"WaveLength"):
277
            if baseprofile and obj.WaveLength.Value and obj.WaveHeight.Value:
278
                # corrugated element
279
                bb = baseprofile.BoundBox
280
                bb.enlarge(bb.DiagonalLength)
281
                downsegment = None
282
                if hasattr(obj,"WaveBottom"):
283
                    if not obj.WaveBottom:
284
                        if obj.WaveType == "Curved":
285
                            if obj.Thickness.Value > obj.WaveHeight.Value:
286
                                downsegment = obj.Thickness.Value
287
                            else:
288
                                downsegment = obj.WaveHeight.Value + obj.Thickness.Value
289
                        else:
290
                            downsegment = obj.Thickness.Value
291
                p1 = Vector(0,0,0)
292
                p5 = Vector(obj.WaveLength.Value*2,0,0)
293
                if obj.WaveType == "Curved":
294
                    p2 = Vector(obj.WaveLength.Value/2,0,obj.WaveHeight.Value)
295
                    p3 = Vector(obj.WaveLength.Value,0,0)
296
                    e1 = Part.Arc(p1,p2,p3).toShape()
297
                    p4 = Vector(obj.WaveLength.Value*1.5,0,-obj.WaveHeight.Value)
298
                    e2 = Part.Arc(p3,p4,p5).toShape()
299
                    upsegment = Part.Wire([e1,e2])
300
                    if not downsegment:
301
                        if obj.Thickness.Value < e1.Curve.Radius:
302
                            c3 = e1.Curve.copy()
303
                            c3.Radius = e1.Curve.Radius-obj.Thickness.Value
304
                            e3 = Part.Arc(c3,e1.FirstParameter,e1.LastParameter).toShape()
305
                            c4 = e2.Curve.copy()
306
                            c4.Radius = e2.Curve.Radius+obj.Thickness.Value
307
                            e4 = Part.Arc(c4,e2.FirstParameter,e2.LastParameter).toShape()
308
                            downsegment = Part.Wire([e3,e4])
309
                        else:
310
                            r = e2.Curve.Radius+obj.Thickness.Value
311
                            z = math.sqrt(r^2 - obj.WaveLength.Value^2)
312
                            p6 = e2.Curve.Center.add(Vector(-obj.WaveLength,0,-z))
313
                            p7 = e2.Curve.Center.add(Vector(0,0,-r))
314
                            p8 = e2.Curve.Center.add(Vector(obj.WaveLength,0,-z))
315
                            downsegment = Part.Arc(p6,p7,p8).toShape()
316

317
                elif obj.WaveType == "Trapezoidal":
318
                    p2 = Vector(obj.WaveLength.Value/4,0,obj.WaveHeight.Value)
319
                    p3 = Vector(obj.WaveLength.Value,0,obj.WaveHeight.Value)
320
                    p4 = Vector(obj.WaveLength.Value*1.25,0,0)
321
                    upsegment = Part.makePolygon([p1,p2,p3,p4,p5])
322
                    if not downsegment:
323
                        a = ((p1.sub(p2)).getAngle(p3.sub(p2)))/2
324
                        tx = obj.Thickness.Value/math.tan(a)
325
                        d1 = Vector(tx,0,-obj.Thickness.Value)
326
                        d2 = Vector(-tx,0,-obj.Thickness.Value)
327
                        p6 = p1.add(d1)
328
                        if tx >= p3.sub(p2).Length/2:
329
                            d3 = p2.sub(p1)
330
                            d3.normalize()
331
                            d3.multiply((0.625*obj.WaveLength.Value)/d3.x)
332
                            d4 = Vector(d3.x,0,-d3.z)
333
                            p7 = p6.add(d3)
334
                            p8 = p7.add(d4)
335
                            p9 = p5.add(d1)
336
                            downsegment = Part.makePolygon([p6,p7,p8,p9])
337
                        elif tx <= 0.625*obj.WaveLength.Value:
338
                            p7 = p2.add(d1)
339
                            p8 = p3.add(d2)
340
                            p9 = p4.add(d2)
341
                            p10 = p5.add(d1)
342
                            downsegment = Part.makePolygon([p6,p7,p8,p9,p10])
343
                        else:
344
                            downsegment = obj.Thickness.Value
345

346
                else: # spike
347
                    p2 = Vector(obj.WaveHeight.Value,0,obj.WaveHeight.Value)
348
                    p3 = Vector(obj.WaveHeight.Value*2,0,0)
349
                    upsegment = Part.makePolygon([p1,p2,p3,p5])
350
                    if not downsegment:
351
                        downsegment = obj.Thickness.Value
352

353
                upsegment.translate(Vector(bb.getPoint(0).x,bb.getPoint(0).y,bb.Center.z))
354
                if isinstance(downsegment,Part.Shape):
355
                    downsegment.translate(Vector(bb.getPoint(0).x,bb.getPoint(0).y,bb.Center.z))
356
                if hasattr(obj,"WaveOffset"):
357
                    if obj.WaveOffset.Value:
358
                        upsegment.translate(Vector(obj.WaveOffset.Value,0,0))
359
                        if isinstance(downsegment,Part.Shape):
360
                            downsegment.translate(Vector(obj.WaveOffset.Value,0,0))
361

362
                upedges = []
363
                downedges = []
364
                for i in range(int(bb.XLength/(obj.WaveLength.Value*2))):
365
                    w1 = upsegment.copy()
366
                    w1.translate(Vector(obj.WaveLength.Value*2*i,0,0))
367
                    upedges.extend(w1.Edges)
368
                    if isinstance(downsegment,Part.Shape):
369
                        w2 = downsegment.copy()
370
                        w2.translate(Vector(obj.WaveLength.Value*2*i,0,0))
371
                        downedges.extend(w2.Edges)
372
                upwire = Part.Wire(upedges)
373
                FreeCAD.upwire = upwire # REMOVE
374
                if isinstance(downsegment,Part.Shape):
375
                    downwire = Part.Wire(downedges)
376
                    FreeCAD.downwire = downwire # REMOVE
377
                    e1 = Part.LineSegment(upwire.Vertexes[0].Point,downwire.Vertexes[0].Point).toShape()
378
                    e2 = Part.LineSegment(upwire.Vertexes[-1].Point,downwire.Vertexes[-1].Point).toShape()
379
                    basewire = Part.Wire(upwire.Edges+[e1,e2]+downwire.Edges)
380
                else:
381
                    z = obj.Thickness.Value
382
                    if obj.WaveType == "Curved":
383
                        z += obj.WaveHeight.Value
384
                    p1 = upwire.Vertexes[0].Point
385
                    p2 = p1.add(Vector(0,0,-z))
386
                    p3 = Vector(upwire.Vertexes[-1].Point.x,upwire.Vertexes[-1].Point.y,p2.z)
387
                    p4 = upwire.Vertexes[-1].Point
388
                    w = Part.makePolygon([p1,p2,p3,p4])
389
                    basewire = Part.Wire(upwire.Edges+w.Edges)
390

391
                FreeCAD.basewire = basewire
392
                if not basewire.isClosed():
393
                    print("Error closing base wire - check FreeCAD.basewire")
394
                    return
395

396
                baseface = Part.Face(basewire)
397
                base = baseface.extrude(Vector(0,bb.YLength,0))
398
                rot = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),normal)
399
                base.rotate(bb.Center,rot.Axis,math.degrees(rot.Angle))
400
                if obj.WaveDirection.Value:
401
                    base.rotate(bb.Center,normal,obj.WaveDirection.Value)
402
                n1 = normal.negative().normalize().multiply(obj.WaveHeight.Value*2)
403
                self.vol = baseprofile.copy()
404
                self.vol.translate(n1)
405
                self.vol = self.vol.extrude(n1.negative().multiply(2))
406
                base = self.vol.common(base)
407
                base = base.removeSplitter()
408
                if not base:
409
                    FreeCAD.Console.PrintError(translate("Arch","Error computing shape of")+" "+obj.Label+"\n")
410
                    return False
411

412
        if base and (obj.Sheets > 1) and normal and thickness:
413
            bases = [base]
414
            for i in range(1,obj.Sheets):
415
                n = FreeCAD.Vector(normal).normalize().multiply(i*thickness)
416
                b = base.copy()
417
                b.translate(n)
418
                bases.append(b)
419
            base = Part.makeCompound(bases)
420

421
        if base and normal and hasattr(obj,"Offset"):
422
            if obj.Offset.Value:
423
                v = DraftVecUtils.scaleTo(normal,obj.Offset.Value)
424
                base.translate(v)
425

426
        # process subshapes
427
        base = self.processSubShapes(obj,base,pl)
428

429
        # applying
430
        if base:
431
            if not base.isNull():
432
                if base.isValid() and base.Solids:
433
                    if len(base.Solids) == 1:
434
                        if base.Volume < 0:
435
                            base.reverse()
436
                        if base.Volume < 0:
437
                            FreeCAD.Console.PrintError(translate("Arch","Couldn't compute a shape"))
438
                            return
439
                        base = base.removeSplitter()
440
                    obj.Shape = base
441
                    if not pl.isNull():
442
                        obj.Placement = pl
443

444

445
class _ViewProviderPanel(ArchComponent.ViewProviderComponent):
446

447
    "A View Provider for the Panel object"
448

449
    def __init__(self,vobj):
450

451
        ArchComponent.ViewProviderComponent.__init__(self,vobj)
452
        vobj.ShapeColor = ArchCommands.getDefaultColor("Panel")
453

454
    def getIcon(self):
455

456
        #import Arch_rc
457
        if hasattr(self,"Object"):
458
            if hasattr(self.Object,"CloneOf"):
459
                if self.Object.CloneOf:
460
                    return ":/icons/Arch_Panel_Clone.svg"
461
        return ":/icons/Arch_Panel_Tree.svg"
462

463
    def updateData(self,obj,prop):
464

465
        if prop in ["Placement","Shape","Material"]:
466
            if hasattr(obj,"Material"):
467
                if obj.Material:
468
                    if hasattr(obj.Material,"Materials"):
469
                        activematerials = [obj.Material.Materials[i] for i in range(len(obj.Material.Materials)) if obj.Material.Thicknesses[i] >= 0]
470
                        if len(activematerials) == len(obj.Shape.Solids):
471
                            cols = []
472
                            for i,mat in enumerate(activematerials):
473
                                c = obj.ViewObject.ShapeColor
474
                                c = (c[0],c[1],c[2],obj.ViewObject.Transparency/100.0)
475
                                if 'DiffuseColor' in mat.Material:
476
                                    if "(" in mat.Material['DiffuseColor']:
477
                                        c = tuple([float(f) for f in mat.Material['DiffuseColor'].strip("()").split(",")])
478
                                if 'Transparency' in mat.Material:
479
                                    c = (c[0],c[1],c[2],float(mat.Material['Transparency']))
480
                                cols.extend([c for j in range(len(obj.Shape.Solids[i].Faces))])
481
                            if obj.ViewObject.DiffuseColor != cols:
482
                                obj.ViewObject.DiffuseColor = cols
483
        ArchComponent.ViewProviderComponent.updateData(self,obj,prop)
484

485

486
class PanelCut(Draft.DraftObject):
487

488
    "A flat, 2D view of an Arch Panel"
489

490
    def __init__(self, obj):
491
        Draft.DraftObject.__init__(self,obj)
492
        obj.Proxy = self
493

494
        # setProperties of ArchComponent will be overwritten
495
        # thus setProperties from ArchComponent will be explicit called to get the properties
496
        ArchComponent.ViewProviderComponent.setProperties(self, obj)
497

498
        self.setProperties(obj)
499

500
    def setProperties(self,obj):
501

502
        pl = obj.PropertiesList
503
        if not "Source" in pl:
504
            obj.addProperty("App::PropertyLink","Source","PanelCut",QT_TRANSLATE_NOOP("App::Property","The linked object"))
505
        if not "TagText" in pl:
506
            obj.addProperty("App::PropertyString","TagText","PanelCut",QT_TRANSLATE_NOOP("App::Property","The text to display. Can be %tag%, %label% or %description% to display the panel tag or label"))
507
            obj.TagText = "%tag%"
508
        if not "TagSize" in pl:
509
            obj.addProperty("App::PropertyLength","TagSize","PanelCut",QT_TRANSLATE_NOOP("App::Property","The size of the tag text"))
510
            obj.TagSize = 10
511
        if not "TagPosition" in pl:
512
            obj.addProperty("App::PropertyVector","TagPosition","PanelCut",QT_TRANSLATE_NOOP("App::Property","The position of the tag text. Keep (0,0,0) for center position"))
513
        if not "TagRotation" in pl:
514
            obj.addProperty("App::PropertyAngle","TagRotation","PanelCut",QT_TRANSLATE_NOOP("App::Property","The rotation of the tag text"))
515
        if not "FontFile" in pl:
516
            obj.addProperty("App::PropertyFile","FontFile","PanelCut",QT_TRANSLATE_NOOP("App::Property","The font of the tag text"))
517
            obj.FontFile = params.get_param("FontFile")
518
        if not "MakeFace" in pl:
519
            obj.addProperty("App::PropertyBool","MakeFace","PanelCut",QT_TRANSLATE_NOOP("App::Property","If True, the object is rendered as a face, if possible."))
520
        if not "AllowedAngles" in pl:
521
            obj.addProperty("App::PropertyFloatList","AllowedAngles","PanelCut",QT_TRANSLATE_NOOP("App::Property","The allowed angles this object can be rotated to when placed on sheets"))
522
        self.Type = "PanelCut"
523
        if not "CutOffset" in pl:
524
            obj.addProperty("App::PropertyDistance","CutOffset","PanelCut",QT_TRANSLATE_NOOP("App::Property","An offset value to move the cut plane from the center point"))
525

526
    def onDocumentRestored(self,obj):
527

528
        self.setProperties(obj)
529

530
    def execute(self, obj):
531

532
        pl = obj.Placement
533
        if obj.Source:
534
            base = None
535
            n = None
536
            if Draft.getType(obj.Source) == "Panel":
537
                import DraftGeomUtils
538
                import Part
539
                baseobj = None
540
                if obj.Source.CloneOf:
541
                    baseobj = obj.Source.CloneOf.Base
542
                if obj.Source.Base:
543
                    baseobj = obj.Source.Base
544
                if baseobj:
545
                    if hasattr(baseobj,'Shape'):
546
                        if baseobj.Shape.Solids:
547
                            center = baseobj.Shape.BoundBox.Center
548
                            diag = baseobj.Shape.BoundBox.DiagonalLength
549
                            if obj.Source.Normal.Length:
550
                                n = obj.Source.Normal
551
                            elif baseobj.isDerivedFrom("Part::Extrusion"):
552
                                n = baseobj.Dir
553
                            if not n:
554
                                n = Vector(0,0,1)
555
                            if hasattr(obj,"CutOffset") and obj.CutOffset.Value:
556
                                l = obj.CutOffset.Value
557
                                d = Vector(n)
558
                                d.multiply(l)
559
                                center = center.add(d)
560
                            plane = Part.makePlane(diag,diag,center,n)
561
                            plane.translate(center.sub(plane.BoundBox.Center))
562
                            wires = []
563
                            for sol in baseobj.Shape.Solids:
564
                                s = sol.section(plane)
565
                                wires.extend(DraftGeomUtils.findWires(s.Edges))
566
                            if wires:
567
                                base = self.buildCut(obj,wires)
568
                        else:
569
                            base = self.buildCut(obj,baseobj.Shape.Wires)
570
                            for w in base.Wires:
571
                                n = DraftGeomUtils.getNormal(w)
572
                                if n:
573
                                    break
574
                            if not n:
575
                                n = Vector(0,0,1)
576
                        if base and n:
577
                            base.translate(base.BoundBox.Center.negative())
578
                            r = FreeCAD.Rotation(n,Vector(0,0,1))
579
                            base.rotate(Vector(0,0,0),r.Axis,math.degrees(r.Angle))
580
                    elif baseobj.isDerivedFrom("Mesh::Feature"):
581
                        return
582
                else:
583
                    l2 = obj.Source.Length/2
584
                    w2 = obj.Source.Width/2
585
                    v1 = Vector(-l2,-w2,0)
586
                    v2 = Vector(l2,-w2,0)
587
                    v3 = Vector(l2,w2,0)
588
                    v4 = Vector(-l2,w2,0)
589
                    base = Part.makePolygon([v1,v2,v3,v4,v1])
590
                if base:
591
                    self.outline = base
592
                    if obj.FontFile and obj.TagText and obj.TagSize.Value:
593
                        if obj.TagPosition.Length == 0:
594
                            pos = base.BoundBox.Center
595
                        else:
596
                            pos = obj.TagPosition
597
                        if obj.TagText == "%tag%":
598
                            string = obj.Source.Tag
599
                        elif obj.TagText == "%label%":
600
                            string = obj.Source.Label
601
                        elif obj.TagText == "%description%":
602
                            string = obj.Source.Description
603
                        else:
604
                            string = obj.TagText
605
                        chars = []
606
                        for char in Part.makeWireString(string,obj.FontFile,obj.TagSize.Value,0):
607
                            chars.extend(char)
608
                        textshape = Part.Compound(chars)
609
                        textshape.translate(pos.sub(textshape.BoundBox.Center))
610
                        textshape.rotate(textshape.BoundBox.Center,Vector(0,0,1),obj.TagRotation.Value)
611
                        self.tag = textshape
612
                        base = Part.Compound([base,textshape])
613
                    else:
614
                        base = Part.Compound([base])
615
                    obj.Shape = base
616
                    obj.Placement = pl
617

618
    def buildCut(self,obj,wires):
619

620
        """buildCut(obj,wires): builds the object shape"""
621

622
        import Part
623
        if hasattr(obj,"MakeFace"):
624
            if obj.MakeFace:
625
                face = None
626
                if len(wires) > 1:
627
                    d = 0
628
                    ow = None
629
                    for w in wires:
630
                        if w.BoundBox.DiagonalLength > d:
631
                            d = w.BoundBox.DiagonalLength
632
                            ow = w
633
                    if ow:
634
                        face = Part.Face(ow)
635
                        for w in wires:
636
                            if w.hashCode() != ow.hashCode():
637
                                wface = Part.Face(w)
638
                                face = face.cut(wface)
639
                else:
640
                    face = Part.Face(wires[0])
641
                if face:
642
                    return face
643
        return Part.makeCompound(wires)
644

645
    def getWires(self, obj):
646

647
        """getWires(obj): returns a tuple containing 3 shapes
648
        that define the panel outline, the panel holes, and
649
        tags (engravings): (outline,holes,tags). Any of these can
650
        be None if nonexistent"""
651

652
        tag = None
653
        outl = None
654
        inl = None
655
        if not hasattr(self,"outline"):
656
            self.execute(obj)
657
        if not hasattr(self,"outline"):
658
            return None
659
        outl = self.outline.copy()
660
        if hasattr(self,"tag"):
661
            tag = self.tag.copy()
662
        if tag:
663
            tag.Placement = obj.Placement.multiply(tag.Placement)
664

665
        outl = self.outline.copy()
666
        outl.Placement = obj.Placement.multiply(outl.Placement)
667
        if len(outl.Wires) > 1:
668
            # separate outline
669
            d = 0
670
            ow = None
671
            for w in outl.Wires:
672
                if w.BoundBox.DiagonalLength > d:
673
                    d = w.BoundBox.DiagonalLength
674
                    ow = w
675
            if ow:
676
                inl = Part.Compound([w for w in outl.Wires if w.hashCode() != ow.hashCode()])
677
                outl = Part.Compound([ow])
678
        else:
679
            inl = None
680
            outl = Part.Compound([outl.Wires[0]])
681
        return (outl, inl, tag)
682

683

684
class ViewProviderPanelCut(Draft.ViewProviderDraft):
685

686
    "a view provider for the panel cut object"
687

688
    def __init__(self,vobj):
689

690
        Draft.ViewProviderDraft.__init__(self,vobj)
691
        self.setProperties(vobj)
692

693
    def setProperties(self,vobj):
694

695
        pl = vobj.PropertiesList
696
        if not "Margin" in pl:
697
            vobj.addProperty("App::PropertyLength","Margin","Arch",QT_TRANSLATE_NOOP("App::Property","A margin inside the boundary"))
698
        if not "ShowMargin" in pl:
699
            vobj.addProperty("App::PropertyBool","ShowMargin","Arch",QT_TRANSLATE_NOOP("App::Property","Turns the display of the margin on/off"))
700

701
    def onDocumentRestored(self,vobj):
702

703
        self.setProperties(vobj)
704

705
    def attach(self,vobj):
706

707
        Draft.ViewProviderDraft.attach(self,vobj)
708
        from pivy import coin
709
        self.coords = coin.SoCoordinate3()
710
        self.lineset = coin.SoLineSet()
711
        self.lineset.numVertices.setValue(-1)
712
        lineStyle = coin.SoDrawStyle()
713
        lineStyle.linePattern = 0x0f0f
714
        self.color = coin.SoBaseColor()
715
        self.switch = coin.SoSwitch()
716
        sep = coin.SoSeparator()
717
        self.switch.whichChild = -1
718
        sep.addChild(self.color)
719
        sep.addChild(lineStyle)
720
        sep.addChild(self.coords)
721
        sep.addChild(self.lineset)
722
        self.switch.addChild(sep)
723
        vobj.Annotation.addChild(self.switch)
724
        self.onChanged(vobj,"ShowMargin")
725
        self.onChanged(vobj,"LineColor")
726

727
    def onChanged(self,vobj,prop):
728

729
        if prop in ["Margin","ShowMargin"]:
730
            if hasattr(vobj,"Margin") and hasattr(vobj,"ShowMargin"):
731
                if (vobj.Margin.Value > 0) and vobj.Object.Shape and vobj.ShowMargin:
732
                    self.lineset.numVertices.setValue(-1)
733
                    if vobj.Object.Shape.Wires:
734
                        d = 0
735
                        dw = None
736
                        for w in vobj.Object.Shape.Wires:
737
                            if w.BoundBox.DiagonalLength > d:
738
                                d = w.BoundBox.DiagonalLength
739
                                dw = w
740
                        if dw:
741
                            ow = dw.makeOffset2D(vobj.Margin.Value)
742
                            verts = []
743
                            for v in ow.OrderedVertexes:
744
                                v = vobj.Object.Placement.inverse().multVec(v.Point)
745
                                verts.append((v.x,v.y,v.z))
746
                            if dw.isClosed():
747
                                verts.append(verts[0])
748
                        self.coords.point.setValues(verts)
749
                        self.lineset.numVertices.setValue(len(verts))
750
                        self.switch.whichChild = 0
751
                else:
752
                    self.switch.whichChild = -1
753
        elif prop == "LineColor":
754
            if hasattr(vobj,"LineColor"):
755
                c = vobj.LineColor
756
                self.color.rgb.setValue(c[0],c[1],c[2])
757
        Draft.ViewProviderDraft.onChanged(self,vobj,prop)
758

759
    def updateData(self,obj,prop):
760

761
        if prop in ["Shape"]:
762
            self.onChanged(obj.ViewObject,"Margin")
763
        Draft.ViewProviderDraft.updateData(self,obj,prop)
764

765
    def doubleClicked(self,vobj):
766

767
        # See setEdit in ViewProviderDraft.
768
        FreeCADGui.runCommand("Std_TransformManip")
769
        return True
770

771
class PanelSheet(Draft.DraftObject):
772

773
    "A collection of Panel cuts under a sheet"
774

775
    def __init__(self, obj):
776

777
        Draft.DraftObject.__init__(self,obj)
778
        obj.Proxy = self
779
        self.setProperties(obj)
780

781
    def setProperties(self,obj):
782

783
        pl = obj.PropertiesList
784
        if not "Group" in pl:
785
            obj.addProperty("App::PropertyLinkList","Group","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The linked Panel cuts"))
786
        if not "TagText" in pl:
787
            obj.addProperty("App::PropertyString","TagText","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The tag text to display"))
788
        if not "TagSize" in pl:
789
            obj.addProperty("App::PropertyLength","TagSize","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The size of the tag text"))
790
            obj.TagSize = 10
791
        if not "TagPosition" in pl:
792
            obj.addProperty("App::PropertyVector","TagPosition","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The position of the tag text. Keep (0,0,0) for center position"))
793
        if not "TagRotation" in pl:
794
            obj.addProperty("App::PropertyAngle","TagRotation","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The rotation of the tag text"))
795
        if not "FontFile" in pl:
796
            obj.addProperty("App::PropertyFile","FontFile","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The font of the tag text"))
797
            obj.FontFile = params.get_param("FontFile")
798
        if not "Width" in pl:
799
            obj.addProperty("App::PropertyLength","Width","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The width of the sheet"))
800
            obj.Width = params.get_param_arch("PanelLength")
801
        if not "Height" in pl:
802
            obj.addProperty("App::PropertyLength","Height","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The height of the sheet"))
803
            obj.Height = params.get_param_arch("PanelWidth")
804
        if not "FillRatio" in pl:
805
            obj.addProperty("App::PropertyPercent","FillRatio","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The fill ratio of this sheet"))
806
            obj.setEditorMode("FillRatio",2)
807
        if not "MakeFace" in pl:
808
            obj.addProperty("App::PropertyBool","MakeFace","PanelSheet",QT_TRANSLATE_NOOP("App::Property","If True, the object is rendered as a face, if possible."))
809
        if not "GrainDirection" in pl:
810
            obj.addProperty("App::PropertyAngle","GrainDirection","PanelSheet",QT_TRANSLATE_NOOP("App::Property","Specifies an angle for the wood grain (Clockwise, 0 is North)"))
811
        if not "Scale" in pl:
812
            obj.addProperty("App::PropertyFloat","Scale","PanelSheet", QT_TRANSLATE_NOOP("App::Property","Specifies the scale applied to each panel view."))
813
            obj.Scale = 1.0
814
        if not "Rotations" in pl:
815
            obj.addProperty("App::PropertyFloatList","Rotations","PanelSheet", QT_TRANSLATE_NOOP("App::Property","A list of possible rotations for the nester"))
816
        self.Type = "PanelSheet"
817

818
    def onDocumentRestored(self, obj):
819

820
        self.setProperties(obj)
821

822
    def execute(self, obj):
823

824
        import Part
825
        self.sheettag = None
826
        self.sheetborder = None
827
        pl = obj.Placement
828
        if obj.Width.Value and obj.Height.Value:
829
            l2 = obj.Width.Value/2
830
            w2 = obj.Height.Value/2
831
            v1 = Vector(-l2,-w2,0)
832
            v2 = Vector(l2,-w2,0)
833
            v3 = Vector(l2,w2,0)
834
            v4 = Vector(-l2,w2,0)
835
            base = Part.makePolygon([v1,v2,v3,v4,v1])
836
            if hasattr(obj,"MakeFace"):
837
                if obj.MakeFace:
838
                    base = Part.Face(base)
839
            self.sheetborder = base
840
            wires = []
841
            area = obj.Width.Value * obj.Height.Value
842
            subarea = 0
843
            for v in obj.Group:
844
                if hasattr(v,'Shape'):
845
                    wires.extend(v.Shape.Wires)
846
                    if Draft.getType(v) == "PanelCut":
847
                        if v.Source:
848
                            subarea += v.Source.Area.Value
849
                    else:
850
                        for w in v.Shape.Wires:
851
                            if w.isClosed():
852
                                f = Part.Face(w)
853
                                subarea += f.Area
854
            if wires:
855
                base = Part.Compound([base]+wires)
856
            if obj.FontFile and obj.TagText and obj.TagSize.Value:
857
                chars = []
858
                for char in Part.makeWireString(obj.TagText,obj.FontFile,obj.TagSize.Value,0):
859
                    chars.extend(char)
860
                textshape = Part.Compound(chars)
861
                textshape.translate(obj.TagPosition)
862
                textshape.rotate(textshape.BoundBox.Center,Vector(0,0,1),obj.TagRotation.Value)
863
                self.sheettag = textshape
864
                base = Part.Compound([base,textshape])
865
            base.scale(obj.Scale, FreeCAD.Vector())
866
            obj.Shape = base
867
            obj.Placement = pl
868
            obj.FillRatio = int((subarea/area)*100)
869

870
    def getOutlines(self,obj,transform=False):
871

872
        """getOutlines(obj,transform=False): returns a list of compounds whose wires define the
873
        outlines of the panels in this sheet. If transform is True, the placement of
874
        the sheet will be added to each wire"""
875

876
        outp = []
877
        for p in obj.Group:
878
            ispanel = False
879
            if hasattr(p,"Proxy"):
880
                if hasattr(p.Proxy,"getWires"):
881
                    ispanel = True
882
                    w = p.Proxy.getWires(p)
883
                    if w[0]:
884
                        w = w[0]
885
                        w.scale(obj.Scale, FreeCAD.Vector())
886
                        if transform:
887
                            w.Placement = obj.Placement.multiply(w.Placement)
888
                        outp.append(w)
889
            if not ispanel:
890
                if hasattr(p,'Shape'):
891
                    for w in p.Shape.Wires:
892
                        w.scale(obj.Scale, FreeCAD.Vector())
893
                        if transform:
894
                            w.Placement = obj.Placement.multiply(w.Placement)
895
                        outp.append(w)
896
        return outp
897

898
    def getHoles(self,obj,transform=False):
899

900
        """getHoles(obj,transform=False): returns a list of compound whose wires define the
901
        holes contained in the panels in this sheet. If transform is True, the placement of
902
        the sheet will be added to each wire"""
903

904
        outp = []
905
        for p in obj.Group:
906
            if hasattr(p,"Proxy"):
907
                if hasattr(p.Proxy,"getWires"):
908
                    w = p.Proxy.getWires(p)
909
                    if w[1]:
910
                        w = w[1]
911
                        w.scale(obj.Scale, FreeCAD.Vector())
912
                        if transform:
913
                            w.Placement = obj.Placement.multiply(w.Placement)
914
                        outp.append(w)
915
        return outp
916

917
    def getTags(self,obj,transform=False):
918

919
        """getTags(obj,transform=False): returns a list of compounds whose wires define the
920
        tags (engravings) contained in the panels in this sheet and the sheet intself.
921
        If transform is True, the placement of the sheet will be added to each wire.
922
        Warning, the wires returned by this function may not be closed,
923
        depending on the font"""
924

925
        outp = []
926
        for p in obj.Group:
927
            if hasattr(p,"Proxy"):
928
                if hasattr(p.Proxy,"getWires"):
929
                    w = p.Proxy.getWires(p)
930
                    if w[2]:
931
                        w = w[2]
932
                        w.scale(obj.Scale, FreeCAD.Vector())
933
                        if transform:
934
                            w.Placement = obj.Placement.multiply(w.Placement)
935
                        outp.append(w)
936
        if self.sheettag is not None:
937
            w = self.sheettag.copy()
938
            w.scale(obj.Scale, FreeCAD.Vector())
939
            if transform:
940
                w.Placement = obj.Placement.multiply(w.Placement)
941
            outp.append(w)
942

943
        return outp
944

945

946
class ViewProviderPanelSheet(Draft.ViewProviderDraft):
947

948
    "a view provider for the panel sheet object"
949

950
    def __init__(self,vobj):
951

952
        Draft.ViewProviderDraft.__init__(self,vobj)
953
        self.setProperties(vobj)
954
        vobj.PatternSize = 0.0035
955

956
    def setProperties(self,vobj):
957

958
        pl = vobj.PropertiesList
959
        if not "Margin" in pl:
960
            vobj.addProperty("App::PropertyLength","Margin","PanelSheet",QT_TRANSLATE_NOOP("App::Property","A margin inside the boundary"))
961
        if not "ShowMargin" in pl:
962
            vobj.addProperty("App::PropertyBool","ShowMargin","PanelSheet",QT_TRANSLATE_NOOP("App::Property","Turns the display of the margin on/off"))
963
        if not "ShowGrain" in pl:
964
            vobj.addProperty("App::PropertyBool","ShowGrain","PanelSheet",QT_TRANSLATE_NOOP("App::Property","Turns the display of the wood grain texture on/off"))
965

966
    def onDocumentRestored(self,vobj):
967

968
        self.setProperties(vobj)
969

970
    def getIcon(self):
971

972
        return ":/icons/Arch_Panel_Sheet_Tree.svg"
973

974
    def setEdit(self, vobj, mode):
975
        if mode == 1 or mode == 2:
976
            return None
977

978
        taskd = SheetTaskPanel(vobj.Object)
979
        taskd.update()
980
        FreeCADGui.Control.showDialog(taskd)
981
        return True
982

983
    def unsetEdit(self, vobj, mode):
984
        if mode == 1 or mode == 2:
985
            return None
986

987
        FreeCADGui.Control.closeDialog()
988
        return True
989

990
    def attach(self,vobj):
991

992
        Draft.ViewProviderDraft.attach(self,vobj)
993
        from pivy import coin
994
        self.coords = coin.SoCoordinate3()
995
        self.lineset = coin.SoLineSet()
996
        self.lineset.numVertices.setValue(-1)
997
        lineStyle = coin.SoDrawStyle()
998
        lineStyle.linePattern = 0x0f0f
999
        self.color = coin.SoBaseColor()
1000
        self.switch = coin.SoSwitch()
1001
        sep = coin.SoSeparator()
1002
        self.switch.whichChild = -1
1003
        sep.addChild(self.color)
1004
        sep.addChild(lineStyle)
1005
        sep.addChild(self.coords)
1006
        sep.addChild(self.lineset)
1007
        self.switch.addChild(sep)
1008
        vobj.Annotation.addChild(self.switch)
1009
        self.onChanged(vobj,"ShowMargin")
1010
        self.onChanged(vobj,"LineColor")
1011

1012
    def onChanged(self,vobj,prop):
1013

1014
        if prop in ["Margin","ShowMargin"]:
1015
            if hasattr(vobj,"Margin") and hasattr(vobj,"ShowMargin"):
1016
                if (vobj.Margin.Value > 0) and (vobj.Margin.Value < vobj.Object.Width.Value/2) and (vobj.Margin.Value < vobj.Object.Height.Value/2):
1017
                    l2 = vobj.Object.Width.Value/2
1018
                    w2 = vobj.Object.Height.Value/2
1019
                    v = vobj.Margin.Value
1020
                    v1 = (-l2+v,-w2+v,0)
1021
                    v2 = (l2-v,-w2+v,0)
1022
                    v3 = (l2-v,w2-v,0)
1023
                    v4 = (-l2+v,w2-v,0)
1024
                    self.coords.point.setValues([v1,v2,v3,v4,v1])
1025
                    self.lineset.numVertices.setValue(5)
1026
                if vobj.ShowMargin:
1027
                    self.switch.whichChild = 0
1028
                else:
1029
                    self.switch.whichChild = -1
1030
        elif prop == "LineColor":
1031
            if hasattr(vobj,"LineColor"):
1032
                c = vobj.LineColor
1033
                self.color.rgb.setValue(c[0],c[1],c[2])
1034
        elif prop == "ShowGrain":
1035
            if hasattr(vobj,"ShowGrain"):
1036
                if vobj.ShowGrain:
1037
                    vobj.Pattern = "woodgrain"
1038
                else:
1039
                    vobj.Pattern = "None"
1040
        Draft.ViewProviderDraft.onChanged(self,vobj,prop)
1041

1042

1043
    def updateData(self,obj,prop):
1044

1045
        if prop in ["Width","Height"]:
1046
            self.onChanged(obj.ViewObject,"Margin")
1047
        elif prop == "GrainDirection":
1048
            if hasattr(self,"texcoords"):
1049
                if self.texcoords:
1050
                    s = FreeCAD.Vector(self.texcoords.directionS.getValue().getValue()).Length
1051
                    vS  = DraftVecUtils.rotate(FreeCAD.Vector(s,0,0),-math.radians(obj.GrainDirection.Value))
1052
                    vT  = DraftVecUtils.rotate(FreeCAD.Vector(0,s,0),-math.radians(obj.GrainDirection.Value))
1053
                    self.texcoords.directionS.setValue(vS.x,vS.y,vS.z)
1054
                    self.texcoords.directionT.setValue(vT.x,vT.y,vT.z)
1055
        Draft.ViewProviderDraft.updateData(self,obj,prop)
1056

1057

1058
class SheetTaskPanel(ArchComponent.ComponentTaskPanel):
1059

1060
    def __init__(self,obj):
1061

1062
        ArchComponent.ComponentTaskPanel.__init__(self)
1063
        self.obj = obj
1064
        self.optwid = QtGui.QWidget()
1065
        self.optwid.setWindowTitle(QtGui.QApplication.translate("Arch", "Tools", None))
1066
        lay = QtGui.QVBoxLayout(self.optwid)
1067
        self.editButton = QtGui.QPushButton(self.optwid)
1068
        self.editButton.setIcon(QtGui.QIcon(":/icons/Draft_Edit.svg"))
1069
        self.editButton.setText(QtGui.QApplication.translate("Arch", "Edit views positions", None))
1070
        lay.addWidget(self.editButton)
1071
        QtCore.QObject.connect(self.editButton, QtCore.SIGNAL("clicked()"), self.editNodes)
1072
        self.form = [self.form,self.optwid]
1073

1074
    def editNodes(self):
1075

1076
        FreeCADGui.Control.closeDialog()
1077
        FreeCADGui.runCommand("Draft_Edit")
1078

1079

1080

1081

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

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

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

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