diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index 8e0f621702..b305654135 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -535,7 +535,10 @@ class _Wall(ArchComponent.Component): # To be combined into Width when PropertyLengthList is available if not "OverrideWidth" in lp: - obj.addProperty("App::PropertyFloatList","OverrideWidth","Wall",QT_TRANSLATE_NOOP("App::Property","This override Width attribute to set width of each segment of wall (The 1st value override 'Width' attribute for 1st segment of wall; if a value is zero, 1st value of 'OverrideWidth' will be followed)")) # see DraftGeomUtils.offsetwire() + obj.addProperty("App::PropertyFloatList","OverrideWidth","Wall",QT_TRANSLATE_NOOP("App::Property","This overrides Width attribute to set width of each segment of wall. Ignored if Base object provides Widths information, with getWidths() method. (The 1st value override 'Width' attribute for 1st segment of wall; if a value is zero, 1st value of 'OverrideWidth' will be followed)")) # see DraftGeomUtils.offsetwire() + + if not "OverrideAlign" in lp: + obj.addProperty("App::PropertyStringList","OverrideAlign","Wall",QT_TRANSLATE_NOOP("App::Property","This overrides Align attribute to set Align of each segment of wall. Ignored if Base object provides Aligns information, with getAligns() method. (The 1st value override 'Align' attribute for 1st segment of wall; if a value is not 'Left, Right, Center', 1st value of 'OverrideAlign' will be followed)")) # see DraftGeomUtils.offsetwire() if not "Height" in lp: obj.addProperty("App::PropertyLength","Height","Wall",QT_TRANSLATE_NOOP("App::Property","The height of this wall. Keep 0 for automatic. Not used if this wall is based on a solid")) @@ -821,19 +824,72 @@ class _Wall(ArchComponent.Component): return data length = obj.Length.Value - # TODO currently layers were not supported when len(basewires) > 0 + # TODO currently layers were not supported when len(basewires) > 0 ##( or 1 ? ) width = 0 - widths = obj.OverrideWidth - if obj.OverrideWidth: - if obj.OverrideWidth[0]: - width = obj.OverrideWidth[0] - if not width: - if obj.Width: - width = obj.Width.Value + # Get width of each edge segment from Base Objects if they store it (Adding support in SketchFeaturePython, DWire...) + widths = [] # [] or None are both False + if obj.Base: + if hasattr(obj.Base, 'Proxy'): + if hasattr(obj.Base.Proxy, 'getWidths'): + widths = obj.Base.Proxy.getWidths(obj.Base) # return a list of Width corresponds to indexes of sorted edges of Sketch + + # Get width of each edge/wall segment from ArchWall.OverrideWidth if Base Object does not provide it + if not widths: + if obj.OverrideWidth: + if Draft.getType(obj.Base) == 'Sketch': + # If Base Object is ordinary Sketch, sort the width list in OverrrideWidth to correspond to indexes of sorted edges of Sketch + try: + import ArchSketchObject + except: + print("ArchSketchObject add-on module is not installed yet") + try: + widths = ArchSketchObject.sortSketchWidth(obj.Base, obj.OverrideWidth) + except: + widths = obj.OverrideWidth + else: + # If Base Object is not Sketch, but e.g. DWire, the width list in OverrrideWidth just correspond to sequential order of edges + widths = obj.OverrideWidth + elif obj.Width: + widths = [obj.Width.Value] else: - print("Width or OverrideWidth[0] should not be 0") - return + print ("Width & OverrideWidth & base.getWidths() should not be all 0 or None or [] empty list ") + return None + + # set 'default' width - for filling in any item in the list == 0 or None + if obj.Width.Value: + width = obj.Width.Value + else: + width = 200 # 'Default' width value + + # Get align of each edge segment from Base Objects if they store it (Adding support in SketchFeaturePython, DWire...) + aligns = [] + if obj.Base: + if hasattr(obj.Base, 'Proxy'): + if hasattr(obj.Base.Proxy, 'getAligns'): + aligns = obj.Base.Proxy.getAligns(obj.Base) # return a list of Align corresponds to indexes of sorted edges of Sketch + + # Get align of each edge/wall segment from ArchWall.OverrideAlign if Base Object does not provide it + if not aligns: + if obj.OverrideAlign: + if Draft.getType(obj.Base) == 'Sketch': + # If Base Object is ordinary Sketch, sort the align list in OverrideAlign to correspond to indexes of sorted edges of Sketch + try: + import ArchSketchObject + except: + print("ArchSketchObject add-on module is not installed yet") + try: + aligns = ArchSketchObject.sortSketchAlign(obj.Base, obj.OverrideAlign) + except: + aligns = obj.OverrideAlign + else: + # If Base Object is not Sketch, but e.g. DWire, the align list in OverrideAlign just correspond to sequential order of edges + aligns = obj.OverrideAlign + else: + aligns = [obj.Align] + + # set 'default' align - for filling in any item in the list == 0 or None + align = obj.Align # or aligns[0] height = obj.Height.Value if not height: @@ -893,34 +949,83 @@ class _Wall(ArchComponent.Component): return None else: base,placement = self.rebase(obj.Base.Shape) + elif len(obj.Base.Shape.Edges) == 1: self.basewires = [Part.Wire(obj.Base.Shape.Edges)] + + # Sort Sketch edges consistently with below procedures without using Sketch.Shape.Edges - found the latter order in some corner case != getSortedClusters() + elif obj.Base.isDerivedFrom("Sketcher::SketchObject"): + self.basewires = [] + skGeom = obj.Base.Geometry + skGeomEdges = [] + skPlacement = obj.Base.Placement # Get Sketch's placement to restore later + for i in skGeom: + if not i.Construction: + skGeomEdgesI = i.toShape() + skGeomEdges.append(skGeomEdgesI) + for cluster in Part.getSortedClusters(skGeomEdges): + clusterTransformed = [] + for edge in cluster: + edge.Placement = edge.Placement.multiply(skPlacement) ## TODO add attribute to skip Transform... + clusterTransformed.append(edge) + self.basewires.append(clusterTransformed) # Only use cluster of edges rather than turning into wire + # Use Sketch's Normal for all edges/wires generated from sketch for consistency + # Discussion on checking normal of sketch.Placement vs sketch.getGlobalPlacement() - https://forum.freecadweb.org/viewtopic.php?f=22&t=39341&p=334275#p334275 + # normal = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1)) + normal = obj.Base.getGlobalPlacement().Rotation.multVec(FreeCAD.Vector(0,0,1)) + else: # self.basewires = obj.Base.Shape.Wires self.basewires = [] for cluster in Part.getSortedClusters(obj.Base.Shape.Edges): for c in Part.sortEdges(cluster): self.basewires.append(Part.Wire(c)) + # if not sketch, e.g. Dwire, can have wire which is 3d so not on the placement's working plane - below applied to Sketch not applicable here + #normal = obj.Base.getGlobalPlacement().Rotation.multVec(FreeCAD.Vector(0,0,1)) #normal = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1)) if self.basewires: # and width: # width already tested earlier... - if (len(self.basewires) == 1) and layers: self.basewires = [self.basewires[0] for l in layers] - layeroffset = 0 baseface = None for i,wire in enumerate(self.basewires): - edgeNum = len(wire.Edges) - e = wire.Edges[0] + # Check number of edges per 'wire' and get the 1st edge + if isinstance(wire,Part.Wire): + edgeNum = len(wire.Edges) + e = wire.Edges[0] + elif isinstance(wire[0],Part.Edge): + edgeNum = len(wire) + e = wire[0] + + for n in range(0,edgeNum,1): # why these not work - range(edgeNum), range(0,edgeNum) ... + + # Fill the aligns list with ArchWall's default align entry and with same number of items as number of edges + try: + if aligns[n] not in ['Left', 'Right', 'Center']: + aligns[n] = align + except: + aligns.append(align) + + # Fill the widths List with ArchWall's default width entry and with same number of items as number of edges + try: + if not widths[n]: + widths[n] = width + except: + widths.append(width) + if isinstance(e.Curve,Part.Circle): dvec = e.Vertexes[0].Point.sub(e.Curve.Center) else: - dvec = DraftGeomUtils.vec(wire.Edges[0]).cross(normal) + #dvec = DraftGeomUtils.vec(wire.Edges[0]).cross(normal) + dvec = DraftGeomUtils.vec(e).cross(normal) + if not DraftVecUtils.isNull(dvec): dvec.normalize() sh = None - if obj.Align == "Left": + + curAligns = aligns[0] + if curAligns == "Left": off = obj.Offset.Value if layers: off = off+layeroffset @@ -932,11 +1037,15 @@ class _Wall(ArchComponent.Component): dvec2 = DraftVecUtils.scaleTo(dvec,off) wire = DraftGeomUtils.offsetWire(wire,dvec2) - w2 = DraftGeomUtils.offsetWire(wire,dvec,False, False, widths) - w1 = Part.Wire(Part.__sortEdges__(wire.Edges)) + # Get the 'offseted' wire taking into account of width and align of each edge + w2 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,None,aligns,normal) + + # Get the 'base' wire taking into account of width and align of each edge + w1 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,"BasewireMode",aligns,normal) sh = DraftGeomUtils.bind(w1,w2) - elif obj.Align == "Right": + + elif curAligns == "Right": dvec = dvec.negative() off = obj.Offset.Value if layers: @@ -949,11 +1058,12 @@ class _Wall(ArchComponent.Component): dvec2 = DraftVecUtils.scaleTo(dvec,off) wire = DraftGeomUtils.offsetWire(wire,dvec2) - w2 = DraftGeomUtils.offsetWire(wire,dvec,False, False, widths) - - w1 = Part.Wire(Part.__sortEdges__(wire.Edges)) + w2 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,None,aligns,normal) + w1 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,"BasewireMode",aligns,normal) sh = DraftGeomUtils.bind(w1,w2) - elif obj.Align == "Center": + + #elif obj.Align == "Center": + elif curAligns == "Center": if layers: off = width/2-layeroffset d1 = Vector(dvec).multiply(off) @@ -963,18 +1073,13 @@ class _Wall(ArchComponent.Component): d1 = Vector(dvec).multiply(off) w2 = DraftGeomUtils.offsetWire(wire,d1) else: - dvec.multiply(width/2) ## TODO width Value should be of no use (width/2), width Direction remains 'in use' - - overrideWidthHalfen = [i/2 for i in widths] - - w1 = DraftGeomUtils.offsetWire(wire,dvec,False, False, overrideWidthHalfen) - dvec = dvec.negative() - w2 = DraftGeomUtils.offsetWire(wire,dvec,False, False, overrideWidthHalfen) - + dvec.multiply(width) + w2 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,None,aligns,normal) + w1 = DraftGeomUtils.offsetWire(wire,dvec,False,False,widths,"BasewireMode",aligns,normal) sh = DraftGeomUtils.bind(w1,w2) del widths[0:edgeNum] - + del aligns[0:edgeNum] if sh: sh.fix(0.1,0,1) # fixes self-intersecting wires f = Part.Face(sh) @@ -983,34 +1088,25 @@ class _Wall(ArchComponent.Component): # To allow exportIFC.py to work properly on sketch, which use only 1st face / wire, do not fuse baseface here # So for a sketch with multiple wires, each returns individual face (rather than fusing together) for exportIFC.py to work properly # "ArchWall - Based on Sketch Issues" - https://forum.freecadweb.org/viewtopic.php?f=39&t=31235 - # + + # "Bug #2408: [PartDesign] .fuse is splitting edges it should not" + # - https://forum.freecadweb.org/viewtopic.php?f=10&t=20349&p=346237#p346237 + # - bugtracker - https://freecadweb.org/tracker/view.php?id=2408 + + # Try Part.Shell before removeSplitter + # - https://forum.freecadweb.org/viewtopic.php?f=10&t=20349&start=10 + # - 1st finding : if a rectangle + 1 line, can't removesSplitter properly... + # - 2nd finding : if 2 faces do not touch, can't form a shell; then, subsequently for remaining faces even though touch each faces, can't form a shell + baseface.append(f) # The above make Refine methods below (in else) useless, regardless removeSpitters yet to be improved for cases do not work well - ''' Whether layers or not, all baseface.append(f) ''' - #if layers: - # if layers[i] >= 0: - # baseface.append(f) - #else: - #baseface = baseface.fuse(f) - #if obj.Refine == 'DraftRemoveSplitter': - # s = DraftGeomUtils.removeSplitter(baseface) - # if s: - # baseface = s - #elif obj.Refine == 'PartRemoveSplitter': - # baseface = baseface.removeSplitter() else: baseface = [f] ''' Whether layers or not, all baseface = [f] ''' - #if layers: - # if layers[i] >= 0: - # baseface = [f] - #else: - #baseface = f - if baseface: base,placement = self.rebase(baseface) else: @@ -1069,9 +1165,7 @@ class _ViewProviderWall(ArchComponent.ViewProviderComponent): self.Object = vobj.Object from pivy import coin tex = coin.SoTexture2() - image = Draft.loadTexture(Draft.svgpatterns()['simple'][1], 128) - if not image is None: - tex.image = image + tex.image = Draft.loadTexture(Draft.svgpatterns()['simple'][1], 128) texcoords = coin.SoTextureCoordinatePlane() s = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetFloat("patternScale",0.01) texcoords.directionS.setValue(s,0,0)