FreeCAD

Форк
0
/
ArchRoof.py 
953 строки · 40.8 Кб
1
#***************************************************************************
2
#*   Copyright (c) 2012 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 ArchComponent
25
import DraftGeomUtils
26
import DraftVecUtils
27
import FreeCAD
28
import Part
29

30
from FreeCAD import Vector
31

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

45
## @package ArchRoof
46
#  \ingroup ARCH
47
#  \brief The Roof object and tools
48
#
49
#  This module provides tools to build Roof objects.
50
#  Roofs are built from a closed contour and a series of
51
#  slopes.
52

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

57

58
def adjust_list_len (lst, newLn, val):
59
    '''Returns a clone of lst with length newLn, val is appended if required'''
60
    ln = len(lst)
61
    if ln > newLn:
62
        return lst[0:newLn]
63
    else:
64
        return lst[:] + ([val] * (newLn - ln))
65

66

67
def find_inters (edge1, edge2, infinite1=True, infinite2=True):
68
    '''Future wrapper for DraftGeomUtils.findIntersection. The function now
69
    contains a modified copy of getLineIntersections from that function.
70
    '''
71
    def getLineIntersections(pt1, pt2, pt3, pt4, infinite1, infinite2):
72
        # if pt1:
73
            ## first check if we don't already have coincident endpoints ######## we do not want that here ########
74
            # if pt1 in [pt3, pt4]:
75
                # return [pt1]
76
            # elif (pt2 in [pt3, pt4]):
77
                # return [pt2]
78
        norm1 = pt2.sub(pt1).cross(pt3.sub(pt1))
79
        norm2 = pt2.sub(pt4).cross(pt3.sub(pt4))
80

81
        if not DraftVecUtils.isNull(norm1):
82
            try:
83
                norm1.normalize()
84
            except Part.OCCError:
85
                return []
86

87
        if not DraftVecUtils.isNull(norm2):
88
            try:
89
                norm2.normalize()
90
            except Part.OCCError:
91
                return []
92

93
        if DraftVecUtils.isNull(norm1.cross(norm2)):
94
            vec1 = pt2.sub(pt1)
95
            vec2 = pt4.sub(pt3)
96
            if DraftVecUtils.isNull(vec1) or DraftVecUtils.isNull(vec2):
97
                return []  # One of the lines has zero-length
98
            try:
99
                vec1.normalize()
100
                vec2.normalize()
101
            except Part.OCCError:
102
                return []
103
            norm3 = vec1.cross(vec2)
104
            denom = norm3.x + norm3.y + norm3.z
105
            if not DraftVecUtils.isNull(norm3) and denom != 0:
106
                k = ((pt3.z - pt1.z) * (vec2.x - vec2.y)
107
                     + (pt3.y - pt1.y) * (vec2.z - vec2.x)
108
                     + (pt3.x - pt1.x) * (vec2.y - vec2.z)) / denom
109
                vec1.scale(k, k, k)
110
                intp = pt1.add(vec1)
111

112
                if infinite1 is False and not isPtOnEdge(intp, edge1):
113
                    return []
114

115
                if infinite2 is False and not isPtOnEdge(intp, edge2):
116
                    return []
117

118
                return [intp]
119
            else:
120
                return []  # Lines have same direction
121
        else:
122
            return []  # Lines aren't on same plane
123

124
    pt1, pt2, pt3, pt4 = [edge1.Vertexes[0].Point,
125
                          edge1.Vertexes[1].Point,
126
                          edge2.Vertexes[0].Point,
127
                          edge2.Vertexes[1].Point]
128

129
    return getLineIntersections(pt1, pt2, pt3, pt4, infinite1, infinite2)
130

131

132
def face_from_points(ptLst):
133
    ptLst.append(ptLst[0])
134
    # Use DraftVecUtils.removeDouble after append as it does not compare the first and last vector:
135
    ptLst = DraftVecUtils.removeDoubles(ptLst)
136
    ln = len(ptLst)
137
    if ln < 4: # at least 4 points are required for 3 edges
138
        return None
139
    edgeLst = []
140
    for i in range(ln - 1):
141
        edge = Part.makeLine(ptLst[i], ptLst[i + 1])
142
        edgeLst.append(edge)
143
    wire = Part.Wire(edgeLst)
144
    return Part.Face(wire)
145

146

147

148
class _Roof(ArchComponent.Component):
149
    '''The Roof object'''
150
    def __init__(self, obj):
151
        ArchComponent.Component.__init__(self, obj)
152
        self.setProperties(obj)
153
        obj.IfcType = "Roof"
154
        obj.Proxy = self
155

156
    def setProperties(self, obj):
157
        pl = obj.PropertiesList
158
        if not "Angles" in pl:
159
            obj.addProperty("App::PropertyFloatList",
160
                            "Angles",
161
                            "Roof",
162
                            QT_TRANSLATE_NOOP("App::Property", "The list of angles of the roof segments"))
163
        if not "Runs" in pl:
164
            obj.addProperty("App::PropertyFloatList",
165
                            "Runs",
166
                            "Roof",
167
                            QT_TRANSLATE_NOOP("App::Property", "The list of horizontal length projections of the roof segments"))
168
        if not "IdRel" in pl:
169
            obj.addProperty("App::PropertyIntegerList",
170
                            "IdRel",
171
                            "Roof",
172
                            QT_TRANSLATE_NOOP("App::Property", "The list of IDs of the relative profiles of the roof segments"))
173
        if not "Thickness" in pl:
174
            obj.addProperty("App::PropertyFloatList",
175
                            "Thickness",
176
                            "Roof",
177
                            QT_TRANSLATE_NOOP("App::Property", "The list of thicknesses of the roof segments"))
178
        if not "Overhang" in pl:
179
            obj.addProperty("App::PropertyFloatList",
180
                            "Overhang",
181
                            "Roof",
182
                            QT_TRANSLATE_NOOP("App::Property", "The list of overhangs of the roof segments"))
183
        if not "Heights" in pl:
184
            obj.addProperty("App::PropertyFloatList",
185
                            "Heights",
186
                            "Roof",
187
                            QT_TRANSLATE_NOOP("App::Property", "The list of calculated heights of the roof segments"))
188
        if not "Face" in pl:
189
            obj.addProperty("App::PropertyInteger",
190
                            "Face",
191
                            "Roof",
192
                            QT_TRANSLATE_NOOP("App::Property", "The face number of the base object used to build the roof"))
193
        if not "RidgeLength" in pl:
194
            obj.addProperty("App::PropertyLength",
195
                            "RidgeLength",
196
                            "Roof",
197
                            QT_TRANSLATE_NOOP("App::Property", "The total length of the ridges and hips of the roof"))
198
            obj.setEditorMode("RidgeLength",1)
199
        if not "BorderLength" in pl:
200
            obj.addProperty("App::PropertyLength",
201
                            "BorderLength",
202
                            "Roof",
203
                            QT_TRANSLATE_NOOP("App::Property", "The total length of the borders of the roof"))
204
            obj.setEditorMode("BorderLength",1)
205
        if not "Flip" in pl:
206
            obj.addProperty("App::PropertyBool",
207
                            "Flip",
208
                            "Roof",
209
                            QT_TRANSLATE_NOOP("App::Property", "Specifies if the direction of the roof should be flipped"))
210
        if not "Subvolume" in pl:
211
            obj.addProperty("App::PropertyLink",
212
                            "Subvolume",
213
                            "Roof",
214
                            QT_TRANSLATE_NOOP("App::Property", "An optional object that defines a volume to be subtracted from walls. If field is set - it has a priority over auto-generated subvolume"))
215
        self.Type = "Roof"
216

217
    def onDocumentRestored(self, obj):
218
        ArchComponent.Component.onDocumentRestored(self, obj)
219
        self.setProperties(obj)
220

221
    def flipEdges(self, edges):
222
        edges.reverse()
223
        newEdges = []
224
        for edge in edges:
225
            NewEdge = DraftGeomUtils.edg(edge.Vertexes[1].Point, edge.Vertexes[0].Point)
226
            newEdges.append(NewEdge)
227
        return newEdges
228

229
    def calcHeight(self, id):
230
        '''Get the height from run and angle of the given roof profile'''
231
        htRel = self.profilsDico[id]["run"] * (math.tan(math.radians(self.profilsDico[id]["angle"])))
232
        return htRel
233

234
    def calcRun(self, id):
235
        '''Get the run from height and angle of the given roof profile'''
236
        runRel = self.profilsDico[id]["height"] / (math.tan(math.radians(self.profilsDico[id]["angle"])))
237
        return runRel
238

239
    def calcAngle(self, id):
240
        '''Get the angle from height and run of the given roof profile'''
241
        ang = math.degrees(math.atan(self.profilsDico[id]["height"] / self.profilsDico[id]["run"]))
242
        return ang
243

244
    def getPerpendicular(self, vec, rotEdge, l):
245
        '''Get the perpendicular vec of given edge on xy plane'''
246
        norm = Vector(0.0, 0.0, 1.0)
247
        if hasattr(self, "normal"):
248
            if self.normal:
249
                norm = self.normal
250
        per = vec.cross(norm)
251
        if  -180.0 <= rotEdge < -90.0:
252
            per[0] = -abs(per[0])
253
            per[1] = -abs(per[1])
254
        elif   -90.0 <= rotEdge <= 0.0:
255
            per[0] = -abs(per[0])
256
            per[1] = abs(per[1])
257
        elif 0.0 < rotEdge <= 90.0:
258
            per[0] = abs(per[0])
259
            per[1] = abs(per[1])
260
        elif 90.0 < rotEdge <= 180.0:
261
            per[0] = abs(per[0])
262
            per[1] = -abs(per[1])
263
        else:
264
            print("Unknown Angle")
265
        per[2] = abs(per[2])
266
        per.normalize()
267
        per = per.multiply(l)
268
        return per
269

270
    def makeRoofProfilsDic(self, id, angle, run, idrel, overhang, thickness):
271
        profilDico = {}
272
        profilDico["id"] = id
273
        if angle == 90.0:
274
            profilDico["name"] = "Gable" + str(id)
275
            profilDico["run"] = 0.0
276
        else:
277
            profilDico["name"] = "Sloped" + str(id)
278
            profilDico["run"] = run
279
        profilDico["angle"] = angle
280
        profilDico["idrel"] = idrel
281
        profilDico["overhang"] = overhang
282
        profilDico["thickness"] = thickness
283
        profilDico["height"] = None
284
        profilDico["points"] = []
285
        self.profilsDico.append(profilDico)
286

287
    def calcEdgeGeometry(self, i, edge):
288
        profilCurr = self.profilsDico[i]
289
        profilCurr["edge"] = edge
290
        vec = edge.Vertexes[1].Point.sub(edge.Vertexes[0].Point)
291
        profilCurr["vec"] = vec
292
        rot = math.degrees(DraftVecUtils.angle(vec))
293
        profilCurr["rot"] = rot
294

295
    def helperCalcApex(self, profilCurr, profilOpposite):
296
        ptCurr = profilCurr["edge"].Vertexes[0].Point
297
        ptOpposite = profilOpposite["edge"].Vertexes[0].Point
298
        dis = ptCurr.distanceToLine(ptOpposite, profilOpposite["vec"])
299
        if dis < profilCurr["run"] + profilOpposite["run"]: # sum of runs is larger than dis
300
            angCurr = profilCurr["angle"]
301
            angOpposite = profilOpposite["angle"]
302
            return dis / (math.tan(math.radians(angCurr)) / math.tan(math.radians(angOpposite)) + 1.0)
303
        return profilCurr["run"]
304

305
    def calcApex(self, i, numEdges):
306
        '''Recalculate the run and height if there is an opposite roof segment
307
        with a parallel edge, and if the sum of the runs of the segments is
308
        larger than the distance between the edges of the segments.
309
        '''
310
        profilCurr = self.findProfil(i)
311
        if 0 <= profilCurr["idrel"] < numEdges: # no apex calculation if idrel is used
312
            return
313
        if not 0.0 < profilCurr["angle"] < 90.0:
314
            return
315
        profilNext2 = self.findProfil(i + 2)
316
        profilBack2 = self.findProfil(i - 2)
317
        vecCurr = profilCurr["vec"]
318
        vecNext2 = profilNext2["vec"]
319
        vecBack2 = profilBack2["vec"]
320
        runs = []
321
        if ((not 0 <= profilNext2["idrel"] < numEdges)
322
            and 0.0 < profilNext2["angle"] < 90.0
323
            and vecCurr.getAngle(vecNext2) == math.pi):
324
            runs.append((self.helperCalcApex(profilCurr, profilNext2)))
325
        if ((not 0 <= profilBack2["idrel"] < numEdges)
326
            and 0.0 < profilBack2["angle"] < 90.0
327
            and vecCurr.getAngle(vecBack2) == math.pi):
328
            runs.append((self.helperCalcApex(profilCurr, profilBack2)))
329
        runs.sort()
330
        if len(runs) != 0 and runs[0] != profilCurr["run"]:
331
            profilCurr["run"] = runs[0]
332
            hgt = self.calcHeight(i)
333
            profilCurr["height"] = hgt
334

335
    def calcMissingData(self, i, numEdges):
336
        profilCurr = self.profilsDico[i]
337
        ang = profilCurr["angle"]
338
        run = profilCurr["run"]
339
        rel = profilCurr["idrel"]
340
        if i != rel and 0 <= rel < numEdges:
341
            profilRel = self.profilsDico[rel]
342
            # do not use data from the relative profile if it in turn references a relative profile:
343
            if (0 <= profilRel["idrel"] < numEdges                           # idrel of profilRel points to a profile
344
                and rel != profilRel["idrel"]                                # profilRel does not reference itself
345
                and (profilRel["angle"] == 0.0 or profilRel["run"] == 0.0)): # run or angle of profilRel is zero
346
                hgt = self.calcHeight(i)
347
                profilCurr["height"] = hgt
348
            elif ang == 0.0 and run == 0.0:
349
                profilCurr["run"] = profilRel["run"]
350
                profilCurr["angle"] = profilRel["angle"]
351
                profilCurr["height"] = self.calcHeight(i)
352
            elif run == 0.0:
353
                if ang == 90.0:
354
                    htRel = self.calcHeight(rel)
355
                    profilCurr["height"] = htRel
356
                else :
357
                    htRel = self.calcHeight(rel)
358
                    profilCurr["height"] = htRel
359
                    run = self.calcRun(i)
360
                    profilCurr["run"] = run
361
            elif ang == 0.0:
362
                htRel = self.calcHeight(rel)
363
                profilCurr["height"] = htRel
364
                ang = self.calcAngle(i)
365
                profilCurr["angle"] = ang
366
            else :
367
                hgt = self.calcHeight(i)
368
                profilCurr["height"] = hgt
369
        else:
370
            hgt = self.calcHeight(i)
371
            profilCurr["height"] = hgt
372

373
    def calcDraftEdges(self, i):
374
        profilCurr = self.profilsDico[i]
375
        edge = profilCurr["edge"]
376
        vec = profilCurr["vec"]
377
        rot = profilCurr["rot"]
378
        ang = profilCurr["angle"]
379
        run = profilCurr["run"]
380
        if ang != 90 and run == 0.0:
381
            overhang = 0.0
382
        else:
383
            overhang = profilCurr["overhang"]
384
        per = self.getPerpendicular(vec, rot, overhang).negative()
385
        eaveDraft = DraftGeomUtils.offset(edge, per)
386
        profilCurr["eaveDraft"] = eaveDraft
387
        per = self.getPerpendicular(vec, rot, run)
388
        ridge = DraftGeomUtils.offset(edge, per)
389
        profilCurr["ridge"] = ridge
390

391
    def calcEave(self, i):
392
        profilCurr = self.findProfil(i)
393
        ptInterEaves1Lst = find_inters(profilCurr["eaveDraft"], self.findProfil(i - 1)["eaveDraft"])
394
        if ptInterEaves1Lst:
395
            ptInterEaves1 = ptInterEaves1Lst[0]
396
        else:
397
            ptInterEaves1 = profilCurr["eaveDraft"].Vertexes[0].Point
398
        ptInterEaves2Lst = find_inters(profilCurr["eaveDraft"], self.findProfil(i + 1)["eaveDraft"])
399
        if ptInterEaves2Lst:
400
            ptInterEaves2 = ptInterEaves2Lst[0]
401
        else:
402
            ptInterEaves2 = profilCurr["eaveDraft"].Vertexes[1].Point
403
        profilCurr["eavePtLst"] = [ptInterEaves1, ptInterEaves2] # list of points instead of edge as points can be identical
404

405
    def findProfil(self, i):
406
        if 0 <= i < len(self.profilsDico):
407
            profil = self.profilsDico[i]
408
        else:
409
            i = abs(abs(i) - len(self.profilsDico))
410
            profil = self.profilsDico[i]
411
        return profil
412

413
    def helperGable(self, profilCurr, profilOther, isBack):
414
        if isBack:
415
            i = 0
416
        else:
417
            i = 1
418
        ptIntLst = find_inters(profilCurr["ridge"], profilOther["eaveDraft"])
419
        if ptIntLst: # the edges of the roof segments are not parallel
420
            ptProjLst = [ptIntLst[0]]
421
        else: # the edges of the roof segments are parallel
422
            ptProjLst = [profilCurr["ridge"].Vertexes[i].Point]
423
        ptProjLst = ptProjLst + [profilCurr["eavePtLst"][i]]
424
        if not isBack:
425
            ptProjLst.reverse()
426
        for ptProj in ptProjLst:
427
            self.ptsPaneProject.append(ptProj)
428

429
    def backGable(self, i):
430
        profilCurr = self.findProfil(i)
431
        profilBack = self.findProfil(i - 1)
432
        self.helperGable(profilCurr, profilBack, isBack = True)
433

434
    def nextGable(self, i):
435
        profilCurr = self.findProfil(i)
436
        profilNext = self.findProfil(i + 1)
437
        self.helperGable(profilCurr, profilNext, isBack = False)
438

439
    def helperSloped(self, profilCurr, profilOther, ridgeCurr, ridgeOther, isBack, otherIsLower=False):
440
        if isBack:
441
            i = 0
442
        else:
443
            i = 1
444
        ptIntLst = find_inters(ridgeCurr, ridgeOther)
445
        if ptIntLst: # the edges of the roof segments are not parallel
446
            ptInt = ptIntLst[0]
447
            if otherIsLower:
448
                ptRidgeLst = find_inters(profilCurr["ridge"], profilOther["ridge"])
449
                ptProjLst = [ptRidgeLst[0], ptInt]
450
            else:
451
                ptProjLst = [ptInt]
452
            hip = DraftGeomUtils.edg(ptInt, profilCurr["edge"].Vertexes[i].Point)
453
            ptEaveCurrLst = find_inters(hip, profilCurr["eaveDraft"])
454
            ptEaveOtherLst = find_inters(hip, profilOther["eaveDraft"])
455
            if ptEaveCurrLst and ptEaveOtherLst: # both roof segments are sloped
456
                lenToEaveCurr = ptEaveCurrLst[0].sub(ptInt).Length
457
                lenToEaveOther = ptEaveOtherLst[0].sub(ptInt).Length
458
                if lenToEaveCurr < lenToEaveOther:
459
                    ptProjLst = ptProjLst + [ptEaveCurrLst[0]]
460
                else:
461
                    ptProjLst = ptProjLst + [ptEaveOtherLst[0],
462
                                             profilCurr["eavePtLst"][i]]
463
            elif ptEaveCurrLst: # current angle is 0
464
                ptProjLst = ptProjLst + [ptEaveCurrLst[0]]
465
            elif ptEaveOtherLst: # other angle is 0
466
                ptProjLst = ptProjLst + [ptEaveOtherLst[0],
467
                                         profilCurr["eavePtLst"][i]]
468
            else:
469
                print("Error determining outline")
470
        else: # the edges of the roof segments are parallel
471
            ptProjLst = [profilCurr["ridge"].Vertexes[i].Point,
472
                         profilCurr["eavePtLst"][i]]
473
        if not isBack:
474
            ptProjLst.reverse()
475
        for ptProj in ptProjLst:
476
            self.ptsPaneProject.append(ptProj)
477

478
    def backSameHeight(self, i):
479
        profilCurr = self.findProfil(i)
480
        profilBack = self.findProfil(i - 1)
481
        self.helperSloped(profilCurr,
482
                          profilBack,
483
                          profilCurr["ridge"],
484
                          profilBack["ridge"],
485
                          isBack = True)
486

487
    def nextSameHeight(self, i):
488
        profilCurr = self.findProfil(i)
489
        profilNext = self.findProfil(i + 1)
490
        self.helperSloped(profilCurr,
491
                          profilNext,
492
                          profilCurr["ridge"],
493
                          profilNext["ridge"],
494
                          isBack = False)
495

496
    def backHigher(self, i):
497
        profilCurr = self.findProfil(i)
498
        profilBack = self.findProfil(i - 1)
499
        dec = profilCurr["height"] / math.tan(math.radians(profilBack["angle"]))
500
        per = self.getPerpendicular(profilBack["vec"], profilBack["rot"], dec)
501
        edgeRidgeOnPane = DraftGeomUtils.offset(profilBack["edge"], per)
502
        self.helperSloped(profilCurr,
503
                          profilBack,
504
                          profilCurr["ridge"],
505
                          edgeRidgeOnPane,
506
                          isBack = True)
507

508
    def nextHigher(self, i):
509
        profilCurr = self.findProfil(i)
510
        profilNext = self.findProfil(i + 1)
511
        dec = profilCurr["height"] / math.tan(math.radians(profilNext["angle"]))
512
        per = self.getPerpendicular(profilNext["vec"], profilNext["rot"], dec)
513
        edgeRidgeOnPane = DraftGeomUtils.offset(profilNext["edge"], per)
514
        self.helperSloped(profilCurr,
515
                          profilNext,
516
                          profilCurr["ridge"],
517
                          edgeRidgeOnPane,
518
                          isBack = False)
519

520
    def backLower(self, i):
521
        profilCurr = self.findProfil(i)
522
        profilBack = self.findProfil(i - 1)
523
        dec = profilBack["height"] / math.tan(math.radians(profilCurr["angle"]))
524
        per = self.getPerpendicular(profilCurr["vec"], profilCurr["rot"], dec)
525
        edgeRidgeOnPane = DraftGeomUtils.offset(profilCurr["edge"], per)
526
        self.helperSloped(profilCurr,
527
                          profilBack,
528
                          edgeRidgeOnPane,
529
                          profilBack["ridge"],
530
                          isBack = True,
531
                          otherIsLower = True)
532

533
    def nextLower(self, i):
534
        profilCurr = self.findProfil(i)
535
        profilNext = self.findProfil(i + 1)
536
        dec = profilNext["height"] / math.tan(math.radians(profilCurr["angle"]))
537
        per = self.getPerpendicular(profilCurr["vec"], profilCurr["rot"], dec)
538
        edgeRidgeOnPane = DraftGeomUtils.offset(profilCurr["edge"], per)
539
        self.helperSloped(profilCurr,
540
                          profilNext,
541
                          edgeRidgeOnPane,
542
                          profilNext["ridge"],
543
                          isBack = False,
544
                          otherIsLower = True)
545

546
    def getRoofPaneProject(self, i):
547
        self.ptsPaneProject = []
548
        profilCurr = self.findProfil(i)
549
        profilBack = self.findProfil(i - 1)
550
        profilNext = self.findProfil(i + 1)
551
        if profilCurr["angle"] == 90.0 or profilCurr["run"] == 0.0:
552
            self.ptsPaneProject = []
553
        else:
554
            if profilBack["angle"] == 90.0 or profilBack["run"] == 0.0:
555
                self.backGable(i)
556
            elif profilBack["height"] == profilCurr["height"]:
557
                self.backSameHeight(i)
558
            elif profilBack["height"] < profilCurr["height"]:
559
                self.backLower(i)
560
            elif profilBack["height"] > profilCurr["height"]:
561
                self.backHigher(i)
562
            else:
563
                print("Arch Roof: Case not implemented")
564

565
            if profilNext["angle"] == 90.0 or profilNext["run"] == 0.0:
566
                self.nextGable(i)
567
            elif profilNext["height"] == profilCurr["height"]:
568
                self.nextSameHeight(i)
569
            elif profilNext["height"] < profilCurr["height"]:
570
                self.nextLower(i)
571
            elif profilNext["height"] > profilCurr["height"]:
572
                self.nextHigher(i)
573
            else:
574
                print("Arch Roof: Case not implemented")
575

576
        profilCurr["points"] = self.ptsPaneProject
577

578
    def createProfilShape (self, points, midpoint, rot, vec, run, diag, sol):
579
        lp = len(points)
580
        points.append(points[0])
581
        edgesWire = []
582
        for i in range(lp):
583
            edge = Part.makeLine(points[i],points[i + 1])
584
            edgesWire.append(edge)
585
        profil = Part.Wire(edgesWire)
586
        profil.translate(midpoint)
587
        profil.rotate(midpoint, Vector(0.0, 0.0, 1.0), 90.0 - rot)
588
        per = self.getPerpendicular(vec, rot, run)
589
        profil.rotate(midpoint, per, 90.0)
590
        vecT = vec.normalize()
591
        vecT.multiply(diag)
592
        profil.translate(vecT)
593
        vecE = vecT.multiply(-2.0)
594
        profilFace = Part.Face(profil)
595
        profilShp = profilFace.extrude(vecE)
596
        profilShp = sol.common(profilShp)
597
        #shapesList.append(profilShp)
598
        return profilShp
599

600
    def execute(self, obj):
601

602
        if self.clone(obj):
603
            return
604

605
        pl = obj.Placement
606
        #self.baseface = None
607
        self.flip = False
608
        if hasattr(obj, "Flip"):
609
            if obj.Flip:
610
                self.flip = True
611
        base = None
612
        baseWire = None
613
        if obj.Base:
614
            if hasattr(obj.Base, "Shape"):
615
                if obj.Base.Shape.Solids:
616
                    base = obj.Base.Shape
617
                    #pl = obj.Base.Placement
618
                else:
619
                    if (obj.Base.Shape.Faces and obj.Face):
620
                        baseWire = obj.Base.Shape.Faces[obj.Face-1].Wires[0]
621
                    elif obj.Base.Shape.Wires:
622
                        baseWire = obj.Base.Shape.Wires[0]
623
        if baseWire:
624
            if baseWire.isClosed():
625
                self.profilsDico = []
626
                self.shps = []
627
                self.subVolShps = []
628
                heights = []
629
                edges = Part.__sortEdges__(baseWire.Edges)
630
                if self.flip:
631
                    edges = self.flipEdges(edges)
632

633
                ln = len(edges)
634

635
                obj.Angles    = adjust_list_len(obj.Angles, ln, obj.Angles[0])
636
                obj.Runs      = adjust_list_len(obj.Runs, ln, obj.Runs[0])
637
                obj.IdRel     = adjust_list_len(obj.IdRel, ln, obj.IdRel[0])
638
                obj.Thickness = adjust_list_len(obj.Thickness, ln, obj.Thickness[0])
639
                obj.Overhang  = adjust_list_len(obj.Overhang, ln, obj.Overhang[0])
640

641
                for i in range(ln):
642
                    self.makeRoofProfilsDic(i, obj.Angles[i], obj.Runs[i], obj.IdRel[i], obj.Overhang[i], obj.Thickness[i])
643
                for i in range(ln):
644
                    self.calcEdgeGeometry(i, edges[i])
645
                for i in range(ln):
646
                    self.calcApex(i, ln) # after calcEdgeGeometry as it uses vec data
647
                for i in range(ln):
648
                    self.calcMissingData(i, ln) # after calcApex so it can use recalculated heights
649
                for i in range(ln):
650
                    self.calcDraftEdges(i)
651
                for i in range(ln):
652
                    self.calcEave(i)
653
                for profil in self.profilsDico:
654
                    heights.append(profil["height"])
655
                obj.Heights = heights
656
                for i in range(ln):
657
                    self.getRoofPaneProject(i)
658
                    profilCurr = self.profilsDico[i]
659
                    ptsPaneProject = profilCurr["points"]
660
                    if len(ptsPaneProject) == 0:
661
                        continue
662
                    face = face_from_points(ptsPaneProject)
663
                    if face:
664
                        diag = face.BoundBox.DiagonalLength
665
                        midpoint = DraftGeomUtils.findMidpoint(profilCurr["edge"])
666
                        thicknessV = profilCurr["thickness"] / (math.cos(math.radians(profilCurr["angle"])))
667
                        overhangV = profilCurr["overhang"] * math.tan(math.radians(profilCurr["angle"]))
668
                        sol = face.extrude(Vector(0.0, 0.0, profilCurr["height"] + 1000000.0))
669
                        sol.translate(Vector(0.0, 0.0, -2.0 * overhangV))
670

671
                        ## baseVolume shape
672
                        ptsPaneProfil = [Vector(-profilCurr["overhang"], -overhangV, 0.0),
673
                                         Vector(profilCurr["run"], profilCurr["height"], 0.0),
674
                                         Vector(profilCurr["run"], profilCurr["height"] + thicknessV, 0.0),
675
                                         Vector(-profilCurr["overhang"], -overhangV + thicknessV, 0.0)]
676
                        self.shps.append(self.createProfilShape(ptsPaneProfil,
677
                                                                midpoint,
678
                                                                profilCurr["rot"],
679
                                                                profilCurr["vec"],
680
                                                                profilCurr["run"],
681
                                                                diag,
682
                                                                sol))
683

684
                        ## subVolume shape
685
                        ptsSubVolProfil = [Vector(-profilCurr["overhang"], -overhangV, 0.0),
686
                                           Vector(profilCurr["run"], profilCurr["height"], 0.0),
687
                                           Vector(profilCurr["run"], profilCurr["height"] + 900000.0, 0.0),
688
                                           Vector(-profilCurr["overhang"], profilCurr["height"] + 900000.0, 0.0)]
689
                        self.subVolShps.append(self.createProfilShape(ptsSubVolProfil,
690
                                                                      midpoint,
691
                                                                      profilCurr["rot"],
692
                                                                      profilCurr["vec"],
693
                                                                      profilCurr["run"],
694
                                                                      diag,
695
                                                                      sol))
696

697
                if len(self.shps) == 0: # occurs if all segments have angle=90 or run=0.
698
                    # create a flat roof using the eavePtLst outline:
699
                    ptsPaneProject = []
700
                    for i in range(ln):
701
                        ptsPaneProject.append(self.profilsDico[i]["eavePtLst"][0])
702
                    face = face_from_points(ptsPaneProject)
703
                    if face:
704
                        thk = max(1.0, self.profilsDico[0]["thickness"]) # FreeCAD will crash when extruding with a null vector here
705
                        self.shps = [face.extrude(Vector(0.0, 0.0, thk))]
706
                        self.subVolShps = [face.extrude(Vector(0.0, 0.0, 1000000.0))]
707

708
                ## baseVolume
709
                base = self.shps.pop()
710
                for s in self.shps:
711
                    base = base.fuse(s)
712
                base = self.processSubShapes(obj, base, pl)
713
                self.applyShape(obj, base, pl, allownosolid = True)
714

715
                ## subVolume
716
                self.sub = self.subVolShps.pop()
717
                for s in self.subVolShps:
718
                    self.sub = self.sub.fuse(s)
719
                self.sub = self.sub.removeSplitter()
720
                if not self.sub.isNull():
721
                    if not DraftGeomUtils.isNull(pl):
722
                        self.sub.Placement = pl
723

724
        elif base:
725
            base = self.processSubShapes(obj, base, pl)
726
            self.applyShape(obj, base, pl, allownosolid = True)
727
        else:
728
            FreeCAD.Console.PrintMessage(translate("Arch", "Unable to create a roof"))
729

730
    def getSubVolume(self, obj):
731
        '''returns a volume to be subtracted'''
732
        custom_subvolume = getattr(obj, 'Subvolume', None)
733
        if custom_subvolume:
734
            return custom_subvolume.Shape
735

736
        if not obj.Base:
737
            return None
738

739
        if not hasattr(obj.Base, "Shape"):
740
            return None
741

742
        if obj.Base.Shape.Solids:
743
            # For roof created from Base object as solids:
744
            # Not only the solid of the base object itself be subtracted from
745
            # a Wall, but all portion of the wall above the roof solid would be
746
            # subtracted as well.
747
            #
748
            # FC forum discussion : Sketch based Arch_Roof and wall substraction
749
            # - https://forum.freecad.org/viewtopic.php?t=84389
750
            #
751
            faces = []
752
            solids = []
753
            for f in obj.Base.Shape.Faces:  # obj.Base.Shape.Solids.Faces
754
                p = f.findPlane()  # Curve face (surface) seems return no Plane
755
                if p:
756
                    if p.Axis[2] < 0:  # z<0, i.e. normal pointing below horizon
757
                        faces.append(f)
758
                else:
759
                    # Not sure if it is pointing towards and/or above horizon
760
                    # (upward or downward), or it is curve surface, just add.
761
                    faces.append(f)
762

763
                    # Attempt to find normal at non-planar face to verify if
764
                    # it is pointing downward, but cannot conclude even all test
765
                    # points happens to be pointing upward. So add in any rate.
766

767
            for f in faces:
768
                solid = f.extrude(Vector(0.0, 0.0, 1000000.0))
769
                solids.append(solid)
770
            compound = Part.Compound(solids)
771
            return compound
772

773
        sub_field = getattr(self, 'sub', None)
774
        if not sub_field:
775
            self.execute(obj)
776
        return self.sub
777

778

779
    def computeAreas(self, obj):
780
        '''computes border and ridge roof edges length'''
781
        if hasattr(obj, "RidgeLength") and hasattr(obj, "BorderLength"):
782
            rl = 0
783
            bl = 0
784
            rn = 0
785
            bn = 0
786
            if obj.Shape:
787
                if obj.Shape.Faces:
788
                    faceLst = []
789
                    for face in obj.Shape.Faces:
790
                        if face.normalAt(0, 0).getAngle(Vector(0.0, 0.0, 1.0)) < math.pi / 2.0:
791
                            faceLst.append(face)
792
                    if faceLst:
793
                        try:
794
                            shell = Part.Shell(faceLst)
795
                        except Exception:
796
                            pass
797
                        else:
798
                            lut={}
799
                            if shell.Faces:
800
                                for face in shell.Faces:
801
                                    for edge in face.Edges:
802
                                        hc = edge.hashCode()
803
                                        if hc in lut:
804
                                            lut[hc] = lut[hc] + 1
805
                                        else:
806
                                            lut[hc] = 1
807
                                for edge in shell.Edges:
808
                                    if lut[edge.hashCode()] == 1:
809
                                        bl += edge.Length
810
                                        bn += 1
811
                                    elif lut[edge.hashCode()] == 2:
812
                                        rl += edge.Length
813
                                        rn += 1
814
            if obj.RidgeLength.Value != rl:
815
                obj.RidgeLength = rl
816
                #print(str(rn)+" ridge edges in roof "+obj.Name)
817
            if obj.BorderLength.Value != bl:
818
                obj.BorderLength = bl
819
                #print(str(bn)+" border edges in roof "+obj.Name)
820
        ArchComponent.Component.computeAreas(self, obj)
821

822

823
class _ViewProviderRoof(ArchComponent.ViewProviderComponent):
824
    '''A View Provider for the Roof object'''
825
    def __init__(self, vobj):
826
        ArchComponent.ViewProviderComponent.__init__(self, vobj)
827

828
    def getIcon(self):
829
        return ":/icons/Arch_Roof_Tree.svg"
830

831
    def attach(self, vobj):
832
        self.Object = vobj.Object
833
        return
834

835
    def setEdit(self, vobj, mode=0):
836
        if mode != 0:
837
            return None
838

839
        if vobj.Object.Base.Shape.Solids:
840
            taskd = ArchComponent.ComponentTaskPanel()
841
            taskd.obj = self.Object
842
            taskd.update()
843
            FreeCADGui.Control.showDialog(taskd)
844
        else:
845
            taskd = _RoofTaskPanel()
846
            taskd.obj = self.Object
847
            taskd.update()
848
            FreeCADGui.Control.showDialog(taskd)
849
        return True
850

851

852
class _RoofTaskPanel:
853
    '''The editmode TaskPanel for Roof objects'''
854
    def __init__(self):
855
        self.updating = False
856
        self.obj = None
857
        self.form = QtGui.QWidget()
858
        self.form.setObjectName("TaskPanel")
859
        self.grid = QtGui.QGridLayout(self.form)
860
        self.grid.setObjectName("grid")
861
        self.title = QtGui.QLabel(self.form)
862
        self.grid.addWidget(self.title, 0, 0, 1, 1)
863

864
        # tree
865
        self.tree = QtGui.QTreeWidget(self.form)
866
        self.grid.addWidget(self.tree, 1, 0, 1, 1)
867
        self.tree.setRootIsDecorated(False) # remove 1st column's extra left margin
868
        self.tree.setColumnCount(7)
869
        self.tree.header().resizeSection(0, 37) # 37px seems to be the minimum size
870
        self.tree.header().resizeSection(1, 70)
871
        self.tree.header().resizeSection(2, 62)
872
        self.tree.header().resizeSection(3, 37)
873
        self.tree.header().resizeSection(4, 60)
874
        self.tree.header().resizeSection(5, 60)
875
        self.tree.header().resizeSection(6, 70)
876

877
        QtCore.QObject.connect(self.tree, QtCore.SIGNAL("itemChanged(QTreeWidgetItem *, int)"), self.edit)
878
        self.update()
879

880
    def isAllowedAlterSelection(self):
881
        return False
882

883
    def isAllowedAlterView(self):
884
        return True
885

886
    def getStandardButtons(self):
887
        return QtGui.QDialogButtonBox.Close
888

889
    def update(self):
890
        '''fills the treewidget'''
891
        self.updating = True
892
        if self.obj:
893
            root = self.tree.invisibleRootItem()
894
            if root.childCount() == 0:
895
                for i in range(len(self.obj.Angles)):
896
                    QtGui.QTreeWidgetItem(self.tree)
897
            for i in range(len(self.obj.Angles)):
898
                item = root.child(i)
899
                item.setText(0, str(i))
900
                item.setText(1, str(self.obj.Angles[i]))
901
                item.setText(2, str(self.obj.Runs[i]))
902
                item.setText(3, str(self.obj.IdRel[i]))
903
                item.setText(4, str(self.obj.Thickness[i]))
904
                item.setText(5, str(self.obj.Overhang[i]))
905
                item.setText(6, str(self.obj.Heights[i]))
906
                item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
907
            # treeHgt = 1 + 23 + (len(self.obj.Angles) * 17) + 1 # 1px borders, 23px header, 17px rows
908
            # self.tree.setMinimumSize(QtCore.QSize(445, treeHgt))
909
        self.retranslateUi(self.form)
910
        self.updating = False
911

912
    def edit(self, item, column):
913
        if not self.updating:
914
            self.resetObject()
915

916
    def resetObject(self, remove=None):
917
        '''transfers the values from the widget to the object'''
918
        ang = []
919
        run = []
920
        rel = []
921
        thick = []
922
        over = []
923
        root = self.tree.invisibleRootItem()
924
        for it in root.takeChildren():
925
            ang.append(float(it.text(1)))
926
            run.append(float(it.text(2)))
927
            rel.append(int(it.text(3)))
928
            thick.append(float(it.text(4)))
929
            over.append(float(it.text(5)))
930
        self.obj.Runs = run
931
        self.obj.Angles = ang
932
        self.obj.IdRel = rel
933
        self.obj.Thickness = thick
934
        self.obj.Overhang = over
935
        self.obj.touch()
936
        FreeCAD.ActiveDocument.recompute()
937
        self.update()
938

939
    def reject(self):
940
        FreeCAD.ActiveDocument.recompute()
941
        FreeCADGui.ActiveDocument.resetEdit()
942
        return True
943

944
    def retranslateUi(self, TaskPanel):
945
        TaskPanel.setWindowTitle(QtGui.QApplication.translate("Arch", "Roof", None))
946
        self.title.setText(QtGui.QApplication.translate("Arch", "Parameters of the roof profiles :\n* Angle : slope in degrees relative to the horizontal.\n* Run : horizontal distance between the wall and the ridge.\n* Thickness : thickness of the roof.\n* Overhang : horizontal distance between the eave and the wall.\n* Height : height of the ridge above the base (calculated automatically).\n* IdRel : Id of the relative profile used for automatic calculations.\n---\nIf Angle = 0 and Run = 0 then the profile is identical to the relative profile.\nIf Angle = 0 then the angle is calculated so that the height is the same as the relative profile.\nIf Run = 0 then the run is calculated so that the height is the same as the relative profile.", None))
947
        self.tree.setHeaderLabels([QtGui.QApplication.translate("Arch", "Id", None),
948
                                   QtGui.QApplication.translate("Arch", "Angle (deg)", None),
949
                                   QtGui.QApplication.translate("Arch", "Run (mm)", None),
950
                                   QtGui.QApplication.translate("Arch", "IdRel", None),
951
                                   QtGui.QApplication.translate("Arch", "Thickness (mm)", None),
952
                                   QtGui.QApplication.translate("Arch", "Overhang (mm)", None),
953
                                   QtGui.QApplication.translate("Arch", "Height (mm)", None)])
954

955

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

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

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

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