From aff5da530ecbd5066f5afa63d9248aec67f3933c Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Thu, 1 Oct 2020 16:41:35 +0200 Subject: [PATCH] Arch: ArchRoof bug fix * Removed unnecessary use of FreeCAD.Vector. Renamed some functions and variables for clarity (in this description the new names are used). Reformatted the code. Moved imports to the top of the file. Updated the tooltips. Tweaked the title text "Parameters ...". * Fixed the length of list properties bug. * Flip direction did not work properly. The adopted solution is to reverse the edges the roof is based on. * Added support for consecutive parallel edges. * Added support for zero angle roof segments. * The code now handles zero length runs without throwing an error. * To handle triangular roofs with a zero length eave, "eave" (egde) has been turned into "eavePtLst" (list of points). * The ridges of opposite parallel roof segments (if they do not use a relative profile) are recalculated if the sum of their runs is larger than the distance between their edges. The old version of the command would do this only if segments connected to gables and only properly if the corners between the edges of the segments and the gable were 90 degrees. * Revised backGable and nextGable. They now call helperGable. * Revised the 6 other back/next functions. They now call helperSloped. This also fixes several cases where cookie-cutter outlines (profilCurr["points"]) were wrong. In nextHigher, backHigher, nextLower and backLower a point was wrongly projected on the X-axis. In nextHigher the point on the higher ridge was wrongly included. * Improved the cookie-cutter outlines for roof segments with lower overhangs and higher ridges in corners. * Data from a relative profile is no longer used if it in turn references a relative profile. * Changed the default idrel to -1 and changed calcMissingData to not process this value. This avoids confusion for users who are unaware of the relative profile feature. * Introduced find_inters as a workaround for DraftGeomUtils.findIntersection. The latter finds intersections for parallel lines that share a point. Find_inters uses a modified version of getLineIntersections from DraftGeomUtils.findIntersection. * Calling DraftVecUtils.removeDoubles later in the program flow as it does not compares the first and the last vector. * Avoided the QTreeWidget scrolling to the top on every change, by not clearing it but updating existing items instead. * Added self.tree.setRootIsDecorated(False). This gets rid of the 1st column's extra left margin. Removed item.setTextAlignment(0,QtCore.Qt.AlignLeft) as this did not work. --- src/Mod/Arch/ArchRoof.py | 1192 +++++++++++++++++++++----------------- 1 file changed, 671 insertions(+), 521 deletions(-) diff --git a/src/Mod/Arch/ArchRoof.py b/src/Mod/Arch/ArchRoof.py index 41ad000eea..b7c1533c7d 100644 --- a/src/Mod/Arch/ArchRoof.py +++ b/src/Mod/Arch/ArchRoof.py @@ -19,8 +19,18 @@ #* * #*************************************************************************** -import FreeCAD, Draft, math, ArchComponent, DraftVecUtils +import math + +import ArchComponent +import Arch_rc +import Draft +import DraftGeomUtils +import DraftVecUtils +import FreeCAD +import Part + from FreeCAD import Vector + if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui @@ -28,9 +38,9 @@ if FreeCAD.GuiUp: from PySide.QtCore import QT_TRANSLATE_NOOP else: # \cond - def translate(ctxt,txt): + def translate(ctxt, txt): return txt - def QT_TRANSLATE_NOOP(ctxt,txt): + def QT_TRANSLATE_NOOP(ctxt, txt): return txt # \endcond @@ -42,102 +52,168 @@ else: # Roofs are build from a closed contour and a series of # slopes. -__title__="FreeCAD Roof" +__title__ = "FreeCAD Roof" __author__ = "Yorik van Havre", "Jonathan Wiedemann" -__url__ = "http://www.freecadweb.org" +__url__ = "http://www.freecadweb.org" -def makeRoof(baseobj=None,facenr=0, angles=[45.,], run = [], idrel = [0,],thickness = [50.,], overhang=[100.,], name="Roof"): +def adjust_list_len (lst, newLn, val): + ln = len(lst) + if ln > newLn: + return lst[0:newLn] + else: + for i in range(newLn - ln): + lst.append(val) + return lst - '''makeRoof(baseobj,[facenr],[angle],[name]) : Makes a roof based on - a closed wire or an object. You can provide a list of angles, run, - idrel, thickness, overhang for each edges in the wire to define the - roof shape. The default for angle is 45 and the list is - automatically complete to match with number of edges in the wire. - If the base object is a solid the roof take the shape.''' +def find_inters (edge1, edge2, infinite1=True, infinite2=True): + '''Future wrapper for DraftGeomUtils.findIntersection. The function now + contains a modified copy of getLineIntersections from that function. + ''' + def getLineIntersections(pt1, pt2, pt3, pt4, infinite1, infinite2): + # if pt1: + ## first check if we don't already have coincident endpoints ######## we do not want that here ######## + # if pt1 in [pt3, pt4]: + # return [pt1] + # elif (pt2 in [pt3, pt4]): + # return [pt2] + norm1 = pt2.sub(pt1).cross(pt3.sub(pt1)) + norm2 = pt2.sub(pt4).cross(pt3.sub(pt4)) + + if not DraftVecUtils.isNull(norm1): + try: + norm1.normalize() + except Part.OCCError: + return [] + + if not DraftVecUtils.isNull(norm2): + try: + norm2.normalize() + except Part.OCCError: + return [] + + if DraftVecUtils.isNull(norm1.cross(norm2)): + vec1 = pt2.sub(pt1) + vec2 = pt4.sub(pt3) + if DraftVecUtils.isNull(vec1) or DraftVecUtils.isNull(vec2): + return [] # One of the lines has zero-length + try: + vec1.normalize() + vec2.normalize() + except Part.OCCError: + return [] + norm3 = vec1.cross(vec2) + denom = norm3.x + norm3.y + norm3.z + if not DraftVecUtils.isNull(norm3) and denom != 0: + k = ((pt3.z - pt1.z) * (vec2.x - vec2.y) + + (pt3.y - pt1.y) * (vec2.z - vec2.x) + + (pt3.x - pt1.x) * (vec2.y - vec2.z)) / denom + vec1.scale(k, k, k) + intp = pt1.add(vec1) + + if infinite1 is False and not isPtOnEdge(intp, edge1): + return [] + + if infinite2 is False and not isPtOnEdge(intp, edge2): + return [] + + return [intp] + else: + return [] # Lines have same direction + else: + return [] # Lines aren't on same plane + + pt1, pt2, pt3, pt4 = [edge1.Vertexes[0].Point, + edge1.Vertexes[1].Point, + edge2.Vertexes[0].Point, + edge2.Vertexes[1].Point] + + return getLineIntersections(pt1, pt2, pt3, pt4, infinite1, infinite2) + + +def face_from_points(ptLst): + ptLst.append(ptLst[0]) + # Use DraftVecUtils.removeDouble after append as it does not compare the first and last vector: + ptLst = DraftVecUtils.removeDoubles(ptLst) + ln = len(ptLst) + if ln < 4: # at least 4 points are required for 3 edges + return None + edgeLst = [] + for i in range(ln - 1): + edge = Part.makeLine(ptLst[i], ptLst[i + 1]) + edgeLst.append(edge) + wire = Part.Wire(edgeLst) + return Part.Face(wire) + + +def makeRoof(baseobj=None, + facenr=0, + angles=[45.0], run=[250.0], idrel=[-1], thickness=[50.0], overhang=[100.0], + name="Roof"): + '''makeRoof(baseobj, [facenr], [angle], [name]): Makes a roof based on + a closed wire or an object. + + You can provide a list of angles, run, idrel, thickness, overhang for + each edge in the wire to define the roof shape. The default for angle is + 45 and the list is automatically completed to match the number of edges + in the wire. + + If the base object is a solid the roof uses its shape. + ''' if not FreeCAD.ActiveDocument: FreeCAD.Console.PrintError("No active document. Aborting\n") return - import Part - obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name) - obj.Label = translate("Arch",name) - w = None + obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name) + obj.Label = translate("Arch", name) + baseWire = None _Roof(obj) if FreeCAD.GuiUp: _ViewProviderRoof(obj.ViewObject) if baseobj: obj.Base = baseobj - if hasattr(obj.Base,'Shape'): + if hasattr(obj.Base, "Shape"): if obj.Base.Shape.Solids: if FreeCAD.GuiUp: obj.Base.ViewObject.hide() else: if (obj.Base.Shape.Faces and obj.Face): - w = obj.Base.Shape.Faces[obj.Face-1].Wires[0] + baseWire = obj.Base.Shape.Faces[obj.Face-1].Wires[0] if FreeCAD.GuiUp: obj.Base.ViewObject.hide() elif obj.Base.Shape.Wires: - w = obj.Base.Shape.Wires[0] + baseWire = obj.Base.Shape.Wires[0] if FreeCAD.GuiUp: obj.Base.ViewObject.hide() - if w: - if w.isClosed(): + if baseWire: + if baseWire.isClosed(): if FreeCAD.GuiUp: obj.Base.ViewObject.hide() - edges = Part.__sortEdges__(w.Edges) - l = len(edges) + edges = Part.__sortEdges__(baseWire.Edges) + ln = len(edges) - la = len(angles) - alist = angles - for i in range(l-la): - alist.append(angles[0]) - obj.Angles=alist + obj.Angles = adjust_list_len(angles, ln, angles[0]) + obj.Runs = adjust_list_len(run, ln, run[0]) + obj.IdRel = adjust_list_len(idrel, ln, idrel[0]) + obj.Thickness = adjust_list_len(thickness, ln, thickness[0]) + obj.Overhang = adjust_list_len(overhang, ln, overhang[0]) - lr = len(run) - rlist = run - for i in range(l-lr): - #rlist.append(w.Edges[i].Length/2.) - rlist.append(250.) - obj.Runs = rlist - - lidrel = len(idrel) - rellist = idrel - for i in range(l-lidrel): - rellist.append(0) - obj.IdRel = rellist - - lthick = len(thickness) - tlist = thickness - for i in range(l-lthick): - tlist.append(thickness[0]) - obj.Thickness = tlist - - lover = len(overhang) - olist = overhang - for i in range(l-lover): - olist.append(overhang[0]) - obj.Overhang = olist obj.Face = facenr return obj + class _CommandRoof: - - "the Arch Roof command definition" - + '''the Arch Roof command definition''' def GetResources(self): - - return {'Pixmap' : 'Arch_Roof', - 'MenuText': QT_TRANSLATE_NOOP("Arch_Roof","Roof"), - 'Accel': "R, F", - 'ToolTip': QT_TRANSLATE_NOOP("Arch_Roof","Creates a roof object from the selected wire.")} + return {"Pixmap" : "Arch_Roof", + "MenuText": QT_TRANSLATE_NOOP("Arch_Roof", "Roof"), + "Accel" : "R, F", + "ToolTip" : QT_TRANSLATE_NOOP("Arch_Roof", "Creates a roof object from the selected wire.")} def IsActive(self): - return not FreeCAD.ActiveDocument is None def Activated(self): - sel = FreeCADGui.Selection.getSelectionEx() if sel: sel = sel[0] @@ -145,138 +221,159 @@ class _CommandRoof: FreeCADGui.Control.closeDialog() if sel.HasSubObjects: if "Face" in sel.SubElementNames[0]: - idx = int(sel.SubElementNames[0][4:]) - FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Roof")) + i = int(sel.SubElementNames[0][4:]) + FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Create Roof")) FreeCADGui.addModule("Arch") - FreeCADGui.doCommand("obj = Arch.makeRoof(FreeCAD.ActiveDocument."+obj.Name+","+str(idx)+")") + FreeCADGui.doCommand("obj = Arch.makeRoof(FreeCAD.ActiveDocument." + obj.Name + "," + str(i) + ")") FreeCADGui.addModule("Draft") FreeCADGui.doCommand("Draft.autogroup(obj)") FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() return - if hasattr(obj,'Shape'): + if hasattr(obj, "Shape"): if obj.Shape.Wires: - FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Roof")) + FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Create Roof")) FreeCADGui.addModule("Arch") - FreeCADGui.doCommand("obj = Arch.makeRoof(FreeCAD.ActiveDocument."+obj.Name+")") + FreeCADGui.doCommand("obj = Arch.makeRoof(FreeCAD.ActiveDocument." + obj.Name + ")") FreeCADGui.addModule("Draft") FreeCADGui.doCommand("Draft.autogroup(obj)") FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() return else: - FreeCAD.Console.PrintMessage(translate("Arch","Unable to create a roof")) + FreeCAD.Console.PrintMessage(translate("Arch", "Unable to create a roof")) else: - FreeCAD.Console.PrintMessage(translate("Arch","Please select a base object")+"\n") + FreeCAD.Console.PrintMessage(translate("Arch", "Please select a base object") + "\n") FreeCADGui.Control.showDialog(ArchComponent.SelectionTaskPanel()) - FreeCAD.ArchObserver = ArchComponent.ArchSelectionObserver(nextCommand="Arch_Roof") + FreeCAD.ArchObserver = ArchComponent.ArchSelectionObserver(nextCommand = "Arch_Roof") FreeCADGui.Selection.addObserver(FreeCAD.ArchObserver) class _Roof(ArchComponent.Component): - - "The Roof object" - - def __init__(self,obj): - - ArchComponent.Component.__init__(self,obj) + '''The Roof object''' + def __init__(self, obj): + ArchComponent.Component.__init__(self, obj) self.setProperties(obj) obj.IfcType = "Roof" obj.Proxy = self - def setProperties(self,obj): - + def setProperties(self, obj): pl = obj.PropertiesList if not "Angles" in pl: - obj.addProperty("App::PropertyFloatList","Angles","Roof", QT_TRANSLATE_NOOP("App::Property","A list of angles for each roof pane")) + obj.addProperty("App::PropertyFloatList", + "Angles", + "Roof", + QT_TRANSLATE_NOOP("App::Property", "The list of angles of the roof segments")) if not "Runs" in pl: - obj.addProperty("App::PropertyFloatList","Runs","Roof", QT_TRANSLATE_NOOP("App::Property","A list of horizontal length projections for each roof pane")) + obj.addProperty("App::PropertyFloatList", + "Runs", + "Roof", + QT_TRANSLATE_NOOP("App::Property", "The list of horizontal length projections of the roof segments")) if not "IdRel" in pl: - obj.addProperty("App::PropertyIntegerList","IdRel","Roof", QT_TRANSLATE_NOOP("App::Property","A list of IDs of relative profiles for each roof pane")) + obj.addProperty("App::PropertyIntegerList", + "IdRel", + "Roof", + QT_TRANSLATE_NOOP("App::Property", "The list of IDs of the relative profiles of the roof segments")) if not "Thickness" in pl: - obj.addProperty("App::PropertyFloatList","Thickness","Roof", QT_TRANSLATE_NOOP("App::Property","A list of thicknesses for each roof pane")) + obj.addProperty("App::PropertyFloatList", + "Thickness", + "Roof", + QT_TRANSLATE_NOOP("App::Property", "The list of thicknesses of the roof segments")) if not "Overhang" in pl: - obj.addProperty("App::PropertyFloatList","Overhang","Roof", QT_TRANSLATE_NOOP("App::Property","A list of overhangs for each roof pane")) + obj.addProperty("App::PropertyFloatList", + "Overhang", + "Roof", + QT_TRANSLATE_NOOP("App::Property", "The list of overhangs of the roof segments")) if not "Heights" in pl: - obj.addProperty("App::PropertyFloatList","Heights","Roof", QT_TRANSLATE_NOOP("App::Property","A list of calculated heights for each roof pane")) + obj.addProperty("App::PropertyFloatList", + "Heights", + "Roof", + QT_TRANSLATE_NOOP("App::Property", "The list of calculated heights of the roof segments")) if not "Face" in pl: - obj.addProperty("App::PropertyInteger","Face","Roof", QT_TRANSLATE_NOOP("App::Property","The face number of the base object used to build this roof")) + obj.addProperty("App::PropertyInteger", + "Face", + "Roof", + QT_TRANSLATE_NOOP("App::Property", "The face number of the base object used to build the roof")) if not "RidgeLength" in pl: - obj.addProperty("App::PropertyLength","RidgeLength","Roof", QT_TRANSLATE_NOOP("App::Property","The total length of ridges and hips of this roof")) + obj.addProperty("App::PropertyLength", + "RidgeLength", + "Roof", + QT_TRANSLATE_NOOP("App::Property", "The total length of the ridges and hips of the roof")) obj.setEditorMode("RidgeLength",1) if not "BorderLength" in pl: - obj.addProperty("App::PropertyLength","BorderLength","Roof", QT_TRANSLATE_NOOP("App::Property","The total length of borders of this roof")) + obj.addProperty("App::PropertyLength", + "BorderLength", + "Roof", + QT_TRANSLATE_NOOP("App::Property", "The total length of the borders of the roof")) obj.setEditorMode("BorderLength",1) if not "Flip" in pl: - obj.addProperty("App::PropertyBool","Flip","Roof",QT_TRANSLATE_NOOP("App::Property","Flip the roof direction if going the wrong way")) + obj.addProperty("App::PropertyBool", + "Flip", + "Roof", + QT_TRANSLATE_NOOP("App::Property", "Specifies if the direction of the roof should be flipped")) self.Type = "Roof" - def onDocumentRestored(self,obj): - - ArchComponent.Component.onDocumentRestored(self,obj) + def onDocumentRestored(self, obj): + ArchComponent.Component.onDocumentRestored(self, obj) self.setProperties(obj) + def flipEdges(self, edges): + edges.reverse() + newEdges = [] + for edge in edges: + NewEdge = DraftGeomUtils.edg(edge.Vertexes[1].Point, edge.Vertexes[0].Point) + newEdges.append(NewEdge) + return newEdges + def calcHeight(self, id): - - " Get the height from run and angle of the given roof profile " - - htRel = self.profilsDico[id]["run"]*(math.tan(math.radians(self.profilsDico[id]["angle"]))) + '''Get the height from run and angle of the given roof profile''' + htRel = self.profilsDico[id]["run"] * (math.tan(math.radians(self.profilsDico[id]["angle"]))) return htRel def calcRun(self, id): - - " Get the run from height and angle of the given roof profile " - - runRel = self.profilsDico[id]["height"]/(math.tan(math.radians(self.profilsDico[id]["angle"]))) + '''Get the run from height and angle of the given roof profile''' + runRel = self.profilsDico[id]["height"] / (math.tan(math.radians(self.profilsDico[id]["angle"]))) return runRel def calcAngle(self, id): - - " Get the angle from height and run of the given roof profile " - - a = math.degrees(math.atan(self.profilsDico[id]["height"]/self.profilsDico[id]["run"])) - return a + '''Get the angle from height and run of the given roof profile''' + ang = math.degrees(math.atan(self.profilsDico[id]["height"] / self.profilsDico[id]["run"])) + return ang def getPerpendicular(self, vec, rotEdge, l): - - - " Get the perpendicular vec of given edge on xy plane " - norm = FreeCAD.Vector(0,0,1) - if hasattr(self,"normal"): + '''Get the perpendicular vec of given edge on xy plane''' + norm = Vector(0.0, 0.0, 1.0) + if hasattr(self, "normal"): if self.normal: - norm = FreeCAD.Vector(self.normal) - perpendicular = vec.cross(norm) - if -180. <= rotEdge < -90.: - perpendicular[0] = abs(perpendicular[0])*-1 - perpendicular[1] = abs(perpendicular[1])*-1 - elif -90. <= rotEdge <= 0.: - perpendicular[0] = abs(perpendicular[0])*-1 - perpendicular[1] = abs(perpendicular[1]) - elif 0. < rotEdge <= 90.: - perpendicular[0] = abs(perpendicular[0]) - perpendicular[1] = abs(perpendicular[1]) - elif 90. < rotEdge <= 180.: - perpendicular[0] = abs(perpendicular[0]) - perpendicular[1] = abs(perpendicular[1])*-1 + norm = self.normal + per = vec.cross(norm) + if -180.0 <= rotEdge < -90.0: + per[0] = -abs(per[0]) + per[1] = -abs(per[1]) + elif -90.0 <= rotEdge <= 0.0: + per[0] = -abs(per[0]) + per[1] = abs(per[1]) + elif 0.0 < rotEdge <= 90.0: + per[0] = abs(per[0]) + per[1] = abs(per[1]) + elif 90.0 < rotEdge <= 180.0: + per[0] = abs(per[0]) + per[1] = -abs(per[1]) else: print("Unknown Angle") - perpendicular[2] = abs(perpendicular[2]) - perpendicular.normalize() - perpendicular = perpendicular.multiply(l) - if hasattr(self,"flip"): - if self.flip: - return perpendicular.negative() - return perpendicular - - def makeRoofProfilsDic(self, id, angle, run, idrel, overhang, thickness,): + per[2] = abs(per[2]) + per.normalize() + per = per.multiply(l) + return per + def makeRoofProfilsDic(self, id, angle, run, idrel, overhang, thickness): profilDico = {} profilDico["id"] = id - if angle == 90.: - profilDico["name"] = "Pignon"+str(id) - profilDico["run"]=0. + if angle == 90.0: + profilDico["name"] = "Gable" + str(id) + profilDico["run"] = 0.0 else: - profilDico["name"] = "Pan"+str(id) + profilDico["name"] = "Sloped" + str(id) profilDico["run"] = run profilDico["angle"] = angle profilDico["idrel"] = idrel @@ -286,393 +383,455 @@ class _Roof(ArchComponent.Component): profilDico["points"] = [] self.profilsDico.append(profilDico) - def calcMissingData(self, i): - - a = self.profilsDico[i]["angle"] - run = self.profilsDico[i]["run"] - rel = self.profilsDico[i]["idrel"] - if a == 0.0 and run == 0.0 : - self.profilsDico[i]["run"] = self.profilsDico[rel]["run"] - self.profilsDico[i]["angle"] = self.profilsDico[rel]["angle"] - self.profilsDico[i]["height"] = self.calcHeight(i) - elif run == 0: - if a == 90. : - htRel = self.calcHeight(rel) - self.profilsDico[i]["height"] = htRel - else : - htRel = self.calcHeight(rel) - self.profilsDico[i]["height"] = htRel - run = self.calcRun(i) - self.profilsDico[i]["run"] = run - elif a == 0. : - htRel = self.calcHeight(rel) - self.profilsDico[i]["height"] = htRel - a = self.calcAngle(i) - self.profilsDico[i]["angle"] = a - else : - ht = self.calcHeight(i) - self.profilsDico[i]["height"] = ht - - def calcEdgeGeometry(self, edges, i): - - self.profilsDico[i]["edge"] = edges[i] - vec = edges[i].Vertexes[-1].Point.sub(edges[i].Vertexes[0].Point) - self.profilsDico[i]["vec"] = vec + def calcEdgeGeometry(self, i, edge): + profilCurr = self.profilsDico[i] + profilCurr["edge"] = edge + vec = edge.Vertexes[1].Point.sub(edge.Vertexes[0].Point) + profilCurr["vec"] = vec rot = math.degrees(DraftVecUtils.angle(vec)) - self.profilsDico[i]["rot"] = rot + profilCurr["rot"] = rot + + def helperCalcApex(self, profilCurr, profilOpposite): + ptCurr = profilCurr["edge"].Vertexes[0].Point + ptOpposite = profilOpposite["edge"].Vertexes[0].Point + dis = ptCurr.distanceToLine(ptOpposite, profilOpposite["vec"]) + if dis < profilCurr["run"] + profilOpposite["run"]: # sum of runs is larger than dis + angCurr = profilCurr["angle"] + angOpposite = profilOpposite["angle"] + return dis / (math.tan(math.radians(angCurr)) / math.tan(math.radians(angOpposite)) + 1.0) + return profilCurr["run"] + + def calcApex(self, i, numEdges): + '''Recalculate the run and height if there is an opposite roof segment + with a parallel edge, and if the sum of the runs of the segments is + larger than the distance between the edges of the segments. + ''' + profilCurr = self.findProfil(i) + if 0 <= profilCurr["idrel"] < numEdges: # no apex calculation if idrel is used + return + if not 0.0 < profilCurr["angle"] < 90.0: + return + profilNext2 = self.findProfil(i + 2) + profilBack2 = self.findProfil(i - 2) + vecCurr = profilCurr["vec"] + vecNext2 = profilNext2["vec"] + vecBack2 = profilBack2["vec"] + runs = [] + if ((not 0 <= profilNext2["idrel"] < numEdges) + and 0.0 < profilNext2["angle"] < 90.0 + and vecCurr.getAngle(vecNext2) == math.pi): + runs.append((self.helperCalcApex(profilCurr, profilNext2))) + if ((not 0 <= profilBack2["idrel"] < numEdges) + and 0.0 < profilBack2["angle"] < 90.0 + and vecCurr.getAngle(vecBack2) == math.pi): + runs.append((self.helperCalcApex(profilCurr, profilBack2))) + runs.sort() + if len(runs) != 0 and runs[0] != profilCurr["run"]: + profilCurr["run"] = runs[0] + hgt = self.calcHeight(i) + profilCurr["height"] = hgt + + def calcMissingData(self, i, numEdges): + profilCurr = self.profilsDico[i] + ang = profilCurr["angle"] + run = profilCurr["run"] + rel = profilCurr["idrel"] + if i != rel and 0 <= rel < numEdges: + profilRel = self.profilsDico[rel] + # do not use data from the relative profile if it in turn references a relative profile: + if 0 <= profilRel["idrel"] < numEdges: + hgt = self.calcHeight(i) + profilCurr["height"] = hgt + elif ang == 0.0 and run == 0.0: + profilCurr["run"] = profilRel["run"] + profilCurr["angle"] = profilRel["angle"] + profilCurr["height"] = self.calcHeight(i) + elif run == 0.0: + if ang == 90.0: + htRel = self.calcHeight(rel) + profilCurr["height"] = htRel + else : + htRel = self.calcHeight(rel) + profilCurr["height"] = htRel + run = self.calcRun(i) + profilCurr["run"] = run + elif ang == 0.0: + htRel = self.calcHeight(rel) + profilCurr["height"] = htRel + ang = self.calcAngle(i) + profilCurr["angle"] = ang + else : + hgt = self.calcHeight(i) + profilCurr["height"] = hgt + else: + hgt = self.calcHeight(i) + profilCurr["height"] = hgt def calcDraftEdges(self, i): - - import DraftGeomUtils - edge = self.profilsDico[i]["edge"] - vec = self.profilsDico[i]["vec"] - rot = self.profilsDico[i]["rot"] - overhang = self.profilsDico[i]["overhang"] - run = self.profilsDico[i]["run"] - perpendicular = self.getPerpendicular(vec,rot,self.profilsDico[i]["overhang"]).negative() - eave = DraftGeomUtils.offset(edge,perpendicular) - self.profilsDico[i]["eaveD"] = eave - perpendicular = self.getPerpendicular(vec,rot,self.profilsDico[i]["run"]) - ridge = DraftGeomUtils.offset(edge,perpendicular) - self.profilsDico[i]["ridge"] = ridge + profilCurr = self.profilsDico[i] + edge = profilCurr["edge"] + vec = profilCurr["vec"] + rot = profilCurr["rot"] + ang = profilCurr["angle"] + run = profilCurr["run"] + if ang != 90 and run == 0.0: + overhang = 0.0 + else: + overhang = profilCurr["overhang"] + per = self.getPerpendicular(vec, rot, overhang).negative() + eaveDraft = DraftGeomUtils.offset(edge, per) + profilCurr["eaveDraft"] = eaveDraft + per = self.getPerpendicular(vec, rot, run) + ridge = DraftGeomUtils.offset(edge, per) + profilCurr["ridge"] = ridge def calcEave(self, i): - - import DraftGeomUtils - pt0Eave1 = DraftGeomUtils.findIntersection(self.findProfil(i-1)["eaveD"],self.findProfil(i)["eaveD"],infinite1=True,infinite2=True,) - pt1Eave1 = DraftGeomUtils.findIntersection(self.findProfil(i)["eaveD"],self.findProfil(i+1)["eaveD"],infinite1=True,infinite2=True,) - eave = DraftGeomUtils.edg(FreeCAD.Vector(pt0Eave1[0]),FreeCAD.Vector(pt1Eave1[0])) - self.profilsDico[i]["eave"] = eave - - def findProfil(self, idx): - - if 0<=idx profilCurrent["height"] : - self.nextHigher(i) - else: - print("Arch Roof : Case Not implemented") - if profilBack1["angle"] == 90. : - self.backPignon(i) - elif profilBack1["height"] == profilCurrent["height"] : + self.ptsPaneProject = [] + profilCurr = self.findProfil(i) + profilBack = self.findProfil(i - 1) + profilNext = self.findProfil(i + 1) + if profilCurr["angle"] == 90.0 or profilCurr["run"] == 0.0: + self.ptsPaneProject = [] + else: + if profilBack["angle"] == 90.0 or profilBack["run"] == 0.0: + self.backGable(i) + elif profilBack["height"] == profilCurr["height"]: self.backSameHeight(i) - elif profilBack1["height"] < profilCurrent["height"] : - self.backSmaller(i) - elif profilBack1["height"] > profilCurrent["height"] : + elif profilBack["height"] < profilCurr["height"]: + self.backLower(i) + elif profilBack["height"] > profilCurr["height"]: self.backHigher(i) else: - print("Arch Roof : Case Not implemented") - else: - self.ptsPaneProject=[] + print("Arch Roof: Case not implemented") - self.ptsPaneProject = DraftVecUtils.removeDoubles(self.ptsPaneProject) - profilCurrent["points"] = self.ptsPaneProject + if profilNext["angle"] == 90.0 or profilNext["run"] == 0.0: + self.nextGable(i) + elif profilNext["height"] == profilCurr["height"]: + self.nextSameHeight(i) + elif profilNext["height"] < profilCurr["height"]: + self.nextLower(i) + elif profilNext["height"] > profilCurr["height"]: + self.nextHigher(i) + else: + print("Arch Roof: Case not implemented") - def createProfilShape (self, points, midpoint, rot, vec, run, d, f): + profilCurr["points"] = self.ptsPaneProject - import Part + def createProfilShape (self, points, midpoint, rot, vec, run, diag, sol): lp = len(points) points.append(points[0]) edgesWire = [] for i in range(lp): - edge = Part.makeLine(points[i],points[i+1]) + edge = Part.makeLine(points[i],points[i + 1]) edgesWire.append(edge) profil = Part.Wire(edgesWire) profil.translate(midpoint) - profil.rotate(midpoint,FreeCAD.Vector(0,0,1), 90. + rot * -1) - perp = self.getPerpendicular(vec,rot,run) - profil.rotate(midpoint,perp,90.) + profil.rotate(midpoint, Vector(0.0, 0.0, 1.0), 90.0 - rot) + per = self.getPerpendicular(vec, rot, run) + profil.rotate(midpoint, per, 90.0) vecT = vec.normalize() - vecT.multiply(d) + vecT.multiply(diag) profil.translate(vecT) - vecE = vecT.multiply(-2.) + vecE = vecT.multiply(-2.0) profilFace = Part.Face(profil) profilShp = profilFace.extrude(vecE) - profilShp = f.common(profilShp) + profilShp = sol.common(profilShp) #shapesList.append(profilShp) return profilShp - def execute(self,obj): + def execute(self, obj): if self.clone(obj): return - import Part, math, DraftGeomUtils pl = obj.Placement #self.baseface = None self.flip = False - if hasattr(obj,"Flip"): + if hasattr(obj, "Flip"): if obj.Flip: self.flip = True base = None - w = None + baseWire = None if obj.Base: - if hasattr(obj.Base,'Shape'): + if hasattr(obj.Base, "Shape"): if obj.Base.Shape.Solids: base = obj.Base.Shape #pl = obj.Base.Placement else: if (obj.Base.Shape.Faces and obj.Face): - w = obj.Base.Shape.Faces[obj.Face-1].Wires[0] + baseWire = obj.Base.Shape.Faces[obj.Face-1].Wires[0] elif obj.Base.Shape.Wires: - w = obj.Base.Shape.Wires[0] - if w: - if w.isClosed(): + baseWire = obj.Base.Shape.Wires[0] + if baseWire: + if baseWire.isClosed(): self.profilsDico = [] self.shps = [] - self.subVolshps = [] + self.subVolShps = [] heights = [] - edges = Part.__sortEdges__(w.Edges) - l = len(edges) - for i in range(l): + edges = Part.__sortEdges__(baseWire.Edges) + if self.flip: + edges = self.flipEdges(edges) + + ln = len(edges) + + obj.Angles = adjust_list_len(obj.Angles, ln, obj.Angles[0]) + obj.Runs = adjust_list_len(obj.Runs, ln, obj.Runs[0]) + obj.IdRel = adjust_list_len(obj.IdRel, ln, obj.IdRel[0]) + obj.Thickness = adjust_list_len(obj.Thickness, ln, obj.Thickness[0]) + obj.Overhang = adjust_list_len(obj.Overhang, ln, obj.Overhang[0]) + + for i in range(ln): self.makeRoofProfilsDic(i, obj.Angles[i], obj.Runs[i], obj.IdRel[i], obj.Overhang[i], obj.Thickness[i]) - for i in range(l): - self.calcMissingData(i) - for i in range(l): - self.calcEdgeGeometry(edges, i) - for i in range(l): + for i in range(ln): + self.calcEdgeGeometry(i, edges[i]) + for i in range(ln): + self.calcApex(i, ln) # after calcEdgeGeometry as it uses vec data + for i in range(ln): + self.calcMissingData(i, ln) # after calcApex so it can use recalculated heights + for i in range(ln): self.calcDraftEdges(i) - for i in range(l): + for i in range(ln): self.calcEave(i) - for p in self.profilsDico: - heights.append(p["height"]) + for profil in self.profilsDico: + heights.append(profil["height"]) obj.Heights = heights - for i in range(l): + for i in range(ln): self.getRoofPaneProject(i) - profilCurrent = self.findProfil(i) - midpoint = DraftGeomUtils.findMidpoint(profilCurrent["edge"]) - ptsPaneProject = profilCurrent["points"] - lp = len(ptsPaneProject) - if lp != 0: - ptsPaneProject.append(ptsPaneProject[0]) - edgesWire = [] - for p in range(lp): - edge = Part.makeLine(ptsPaneProject[p],ptsPaneProject[p+1]) - edgesWire.append(edge) - wire = Part.Wire(edgesWire) - d = wire.BoundBox.DiagonalLength - thicknessV = profilCurrent["thickness"]/(math.cos(math.radians(profilCurrent["angle"]))) - overhangV = profilCurrent["overhang"]*math.tan(math.radians(profilCurrent["angle"])) - if wire.isClosed(): - f = Part.Face(wire) - f = f.extrude(FreeCAD.Vector(0,0,profilCurrent["height"]+1000000.0)) - f.translate(FreeCAD.Vector(0.0,0.0,-2*overhangV)) - ptsPaneProfil=[FreeCAD.Vector(-profilCurrent["overhang"],-overhangV,0.0),FreeCAD.Vector(profilCurrent["run"],profilCurrent["height"],0.0),FreeCAD.Vector(profilCurrent["run"],profilCurrent["height"]+thicknessV,0.0),FreeCAD.Vector(-profilCurrent["overhang"],-overhangV+thicknessV,0.0)] - self.shps.append(self.createProfilShape(ptsPaneProfil, midpoint, profilCurrent["rot"], profilCurrent["vec"], profilCurrent["run"], d, f)) + profilCurr = self.profilsDico[i] + ptsPaneProject = profilCurr["points"] + if len(ptsPaneProject) == 0: + continue + face = face_from_points(ptsPaneProject) + if face: + diag = face.BoundBox.DiagonalLength + midpoint = DraftGeomUtils.findMidpoint(profilCurr["edge"]) + thicknessV = profilCurr["thickness"] / (math.cos(math.radians(profilCurr["angle"]))) + overhangV = profilCurr["overhang"] * math.tan(math.radians(profilCurr["angle"])) + sol = face.extrude(Vector(0.0, 0.0, profilCurr["height"] + 1000000.0)) + sol.translate(Vector(0.0, 0.0, -2.0 * overhangV)) + + ## baseVolume shape + ptsPaneProfil = [Vector(-profilCurr["overhang"], -overhangV, 0.0), + Vector(profilCurr["run"], profilCurr["height"], 0.0), + Vector(profilCurr["run"], profilCurr["height"] + thicknessV, 0.0), + Vector(-profilCurr["overhang"], -overhangV + thicknessV, 0.0)] + self.shps.append(self.createProfilShape(ptsPaneProfil, + midpoint, + profilCurr["rot"], + profilCurr["vec"], + profilCurr["run"], + diag, + sol)) + ## subVolume shape - ptsSubVolumeProfil=[FreeCAD.Vector(-profilCurrent["overhang"],-overhangV,0.0),FreeCAD.Vector(profilCurrent["run"],profilCurrent["height"],0.0),FreeCAD.Vector(profilCurrent["run"],profilCurrent["height"]+900000.0,0.0),FreeCAD.Vector(-profilCurrent["overhang"],profilCurrent["height"]+900000.0,0.0)] - self.subVolshps.append(self.createProfilShape(ptsSubVolumeProfil, midpoint, profilCurrent["rot"], profilCurrent["vec"], profilCurrent["run"], d, f)) - ## SubVolume - self.sub = self.subVolshps.pop() - for s in self.subVolshps: + ptsSubVolProfil = [Vector(-profilCurr["overhang"], -overhangV, 0.0), + Vector(profilCurr["run"], profilCurr["height"], 0.0), + Vector(profilCurr["run"], profilCurr["height"] + 900000.0, 0.0), + Vector(-profilCurr["overhang"], profilCurr["height"] + 900000.0, 0.0)] + self.subVolShps.append(self.createProfilShape(ptsSubVolProfil, + midpoint, + profilCurr["rot"], + profilCurr["vec"], + profilCurr["run"], + diag, + sol)) + + if len(self.shps) == 0: # occurs if all segments have angle=90 or run=0. + # create a flat roof using the eavePtLst outline: + ptsPaneProject = [] + for i in range(ln): + ptsPaneProject.append(self.profilsDico[i]["eavePtLst"][0]) + face = face_from_points(ptsPaneProject) + if face: + thk = max(1.0, self.profilsDico[0]["thickness"]) # FreeCAD will crash when extruding with a null vector here + self.shps = [face.extrude(Vector(0.0, 0.0, thk))] + self.subVolShps = [face.extrude(Vector(0.0, 0.0, 1000000.0))] + + ## baseVolume + base = self.shps.pop() + for s in self.shps: + base = base.fuse(s) + base = self.processSubShapes(obj, base) + self.applyShape(obj, base, pl, allownosolid = True) + + ## subVolume + self.sub = self.subVolShps.pop() + for s in self.subVolShps: self.sub = self.sub.fuse(s) self.sub = self.sub.removeSplitter() if not self.sub.isNull(): if not DraftGeomUtils.isNull(pl): self.sub.Placement = pl - ## BaseVolume - base = self.shps.pop() - for s in self.shps : - base = base.fuse(s) - base = self.processSubShapes(obj,base) - self.applyShape(obj,base,pl,allownosolid=True) - elif base : - base = self.processSubShapes(obj,base) - self.applyShape(obj,base,pl,allownosolid=True) + + elif base: + base = self.processSubShapes(obj, base) + self.applyShape(obj, base, pl, allownosolid = True) else: - FreeCAD.Console.PrintMessage(translate("Arch","Unable to create a roof")) + FreeCAD.Console.PrintMessage(translate("Arch", "Unable to create a roof")) - def getSubVolume(self,obj): - - "returns a volume to be subtracted" + def getSubVolume(self, obj): + '''returns a volume to be subtracted''' if obj.Base: - if hasattr(obj.Base,'Shape'): + if hasattr(obj.Base, "Shape"): if obj.Base.Shape.Solids: return obj.Shape else : - if hasattr(self,"sub"): + if hasattr(self, "sub"): if self.sub: return self.sub else : @@ -683,43 +842,40 @@ class _Roof(ArchComponent.Component): return self.sub return None - def computeAreas(self,obj): - - "computes border and ridge roof edges length" - - if hasattr(obj,"RidgeLength") and hasattr(obj,"BorderLength"): + def computeAreas(self, obj): + '''computes border and ridge roof edges length''' + if hasattr(obj, "RidgeLength") and hasattr(obj, "BorderLength"): rl = 0 bl = 0 rn = 0 bn = 0 - import Part,math if obj.Shape: if obj.Shape.Faces: - fset = [] - for f in obj.Shape.Faces: - if f.normalAt(0,0).getAngle(FreeCAD.Vector(0,0,1)) < math.pi/2: - fset.append(f) - if fset: + faceLst = [] + for face in obj.Shape.Faces: + if face.normalAt(0, 0).getAngle(Vector(0.0, 0.0, 1.0)) < math.pi / 2.0: + faceLst.append(face) + if faceLst: try: - shell = Part.Shell(fset) + shell = Part.Shell(faceLst) except: pass else: lut={} if shell.Faces: - for f in shell.Faces: - for e in f.Edges: - hc = e.hashCode() + for face in shell.Faces: + for edge in face.Edges: + hc = edge.hashCode() if hc in lut: lut[hc] = lut[hc] + 1 else: lut[hc] = 1 - for e in shell.Edges: - if lut[e.hashCode()] == 1: - bl += e.Length + for edge in shell.Edges: + if lut[edge.hashCode()] == 1: + bl += edge.Length bn += 1 - elif lut[e.hashCode()] == 2: - rl += e.Length + elif lut[edge.hashCode()] == 2: + rl += edge.Length rn += 1 if obj.RidgeLength.Value != rl: obj.RidgeLength = rl @@ -727,35 +883,27 @@ class _Roof(ArchComponent.Component): if obj.BorderLength.Value != bl: obj.BorderLength = bl #print(str(bn)+" border edges in roof "+obj.Name) - ArchComponent.Component.computeAreas(self,obj) + ArchComponent.Component.computeAreas(self, obj) class _ViewProviderRoof(ArchComponent.ViewProviderComponent): - - "A View Provider for the Roof object" - - def __init__(self,vobj): - - ArchComponent.ViewProviderComponent.__init__(self,vobj) + '''A View Provider for the Roof object''' + def __init__(self, vobj): + ArchComponent.ViewProviderComponent.__init__(self, vobj) def getIcon(self): - - import Arch_rc return ":/icons/Arch_Roof_Tree.svg" - def attach(self,vobj): - + def attach(self, vobj): self.Object = vobj.Object return - def unsetEdit(self,vobj,mode): - + def unsetEdit(self, vobj, mode): FreeCADGui.Control.closeDialog() return - def setEdit(self,vobj,mode=0): - - if vobj.Object.Base.Shape.Solids : + def setEdit(self, vobj, mode=0): + if vobj.Object.Base.Shape.Solids: taskd = ArchComponent.ComponentTaskPanel() taskd.obj = self.Object taskd.update() @@ -769,32 +917,29 @@ class _ViewProviderRoof(ArchComponent.ViewProviderComponent): class _RoofTaskPanel: - '''The editmode TaskPanel for Roof objects''' - def __init__(self): - self.updating = False - self.obj = None self.form = QtGui.QWidget() self.form.setObjectName("TaskPanel") self.grid = QtGui.QGridLayout(self.form) self.grid.setObjectName("grid") self.title = QtGui.QLabel(self.form) - self.grid.addWidget(self.title, 0, 0, 1, 2) + self.grid.addWidget(self.title, 0, 0, 1, 1) # tree self.tree = QtGui.QTreeWidget(self.form) - self.grid.addWidget(self.tree, 1, 0, 1, 2) + self.grid.addWidget(self.tree, 1, 0, 1, 1) + self.tree.setRootIsDecorated(False) # remove 1st column's extra left margin self.tree.setColumnCount(7) - self.tree.header().resizeSection(0,30) - self.tree.header().resizeSection(1,50) - self.tree.header().resizeSection(2,60) - self.tree.header().resizeSection(3,30) - self.tree.header().resizeSection(4,50) - self.tree.header().resizeSection(5,50) - self.tree.header().resizeSection(6,50) + self.tree.header().resizeSection(0, 37) # 37px seems to be the minimum size + self.tree.header().resizeSection(1, 70) + self.tree.header().resizeSection(2, 62) + self.tree.header().resizeSection(3, 37) + self.tree.header().resizeSection(4, 60) + self.tree.header().resizeSection(5, 60) + self.tree.header().resizeSection(6, 70) QtCore.QObject.connect(self.tree, QtCore.SIGNAL("itemChanged(QTreeWidgetItem *, int)"), self.edit) self.update() @@ -809,44 +954,48 @@ class _RoofTaskPanel: return int(QtGui.QDialogButtonBox.Close) def update(self): - 'fills the treewidget' + '''fills the treewidget''' self.updating = True - self.tree.clear() if self.obj: + root = self.tree.invisibleRootItem() + if root.childCount() == 0: + for i in range(len(self.obj.Angles)): + QtGui.QTreeWidgetItem(self.tree) for i in range(len(self.obj.Angles)): - item = QtGui.QTreeWidgetItem(self.tree) - item.setText(0,str(i)) - item.setText(1,str(self.obj.Angles[i])) - item.setText(2,str(self.obj.Runs[i])) - item.setText(3,str(self.obj.IdRel[i])) - item.setText(4,str(self.obj.Thickness[i])) - item.setText(5,str(self.obj.Overhang[i])) - item.setText(6,str(self.obj.Heights[i])) + item = root.child(i) + item.setText(0, str(i)) + item.setText(1, str(self.obj.Angles[i])) + item.setText(2, str(self.obj.Runs[i])) + item.setText(3, str(self.obj.IdRel[i])) + item.setText(4, str(self.obj.Thickness[i])) + item.setText(5, str(self.obj.Overhang[i])) + item.setText(6, str(self.obj.Heights[i])) item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable) - item.setTextAlignment(0,QtCore.Qt.AlignLeft) + # treeHgt = 1 + 23 + (len(self.obj.Angles) * 17) + 1 # 1px borders, 23px header, 17px rows + # self.tree.setMinimumSize(QtCore.QSize(445, treeHgt)) self.retranslateUi(self.form) self.updating = False - def edit(self,item,column): + def edit(self, item, column): if not self.updating: self.resetObject() - def resetObject(self,remove=None): - "transfers the values from the widget to the object" - a = [] + def resetObject(self, remove=None): + '''transfers the values from the widget to the object''' + ang = [] run = [] rel = [] thick = [] over = [] - for i in range(self.tree.topLevelItemCount()): - it = self.tree.findItems(str(i),QtCore.Qt.MatchExactly,0)[0] - a.append(float(it.text(1))) + root = self.tree.invisibleRootItem() + for it in root.takeChildren(): + ang.append(float(it.text(1))) run.append(float(it.text(2))) rel.append(int(it.text(3))) thick.append(float(it.text(4))) over.append(float(it.text(5))) self.obj.Runs = run - self.obj.Angles = a + self.obj.Angles = ang self.obj.IdRel = rel self.obj.Thickness = thick self.obj.Overhang = over @@ -861,14 +1010,15 @@ class _RoofTaskPanel: def retranslateUi(self, TaskPanel): TaskPanel.setWindowTitle(QtGui.QApplication.translate("Arch", "Roof", None)) - self.title.setText(QtGui.QApplication.translate("Arch", "Parameters of the profiles of the roof:\n* Angle : slope in degrees compared to the horizontal one.\n* Run : outdistance between the wall and the ridge sheathing.\n* Thickness : thickness of the side of roof.\n* Overhang : outdistance between the sewer and the wall.\n* Height : height of the ridge sheathing (calculated automatically)\n* IdRel : Relative Id for calculations automatic.\n---\nIf Angle = 0 and Run = 0 then profile is identical to the relative profile.\nIf Angle = 0 then angle is calculated so that the height is the same one as the relative profile.\nIf Run = 0 then Run is calculated so that the height is the same one as the relative profile.", None)) + 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)) self.tree.setHeaderLabels([QtGui.QApplication.translate("Arch", "Id", None), - QtGui.QApplication.translate("Arch", "Angle (deg)", None), - QtGui.QApplication.translate("Arch", "Run (mm)", None), - QtGui.QApplication.translate("Arch", "IdRel", None), - QtGui.QApplication.translate("Arch", "Thickness (mm)", None), - QtGui.QApplication.translate("Arch", "Overhang (mm)", None), - QtGui.QApplication.translate("Arch", "Height (mm)", None)]) + QtGui.QApplication.translate("Arch", "Angle (deg)", None), + QtGui.QApplication.translate("Arch", "Run (mm)", None), + QtGui.QApplication.translate("Arch", "IdRel", None), + QtGui.QApplication.translate("Arch", "Thickness (mm)", None), + QtGui.QApplication.translate("Arch", "Overhang (mm)", None), + QtGui.QApplication.translate("Arch", "Height (mm)", None)]) + if FreeCAD.GuiUp: - FreeCADGui.addCommand('Arch_Roof',_CommandRoof()) + FreeCADGui.addCommand("Arch_Roof", _CommandRoof())