| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | __title__ = "FreeCAD Wall" |
| | __author__ = "Yorik van Havre" |
| | __url__ = "https://www.freecad.org" |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | """This module provides tools to build Wall objects. Walls are simple |
| | objects, usually vertical, typically obtained by giving a thickness to a base |
| | line, then extruding it vertically. |
| | |
| | Examples |
| | -------- |
| | TODO put examples here. |
| | |
| | """ |
| |
|
| | import math |
| |
|
| | import FreeCAD |
| | import ArchCommands |
| | import ArchComponent |
| | import ArchSketchObject |
| | import Draft |
| | import DraftVecUtils |
| |
|
| | from FreeCAD import Vector |
| | from draftutils import params |
| |
|
| | if FreeCAD.GuiUp: |
| | from PySide import QtCore, QtGui |
| | from PySide.QtCore import QT_TRANSLATE_NOOP |
| | import FreeCADGui |
| | import draftguitools.gui_trackers as DraftTrackers |
| | from draftutils.translate import translate |
| | else: |
| | |
| | def translate(ctxt, txt): |
| | return txt |
| |
|
| | def QT_TRANSLATE_NOOP(ctxt, txt): |
| | return txt |
| |
|
| | |
| |
|
| |
|
| | def mergeShapes(w1, w2): |
| | """Not currently implemented. |
| | |
| | Return a Shape built on two walls that share same properties and have a |
| | coincident endpoint. |
| | """ |
| |
|
| | if not areSameWallTypes([w1, w2]): |
| | return None |
| | if (not hasattr(w1.Base, "Shape")) or (not hasattr(w2.Base, "Shape")): |
| | return None |
| | if w1.Base.Shape.Faces or w2.Base.Shape.Faces: |
| | return None |
| |
|
| | |
| | return None |
| |
|
| | eds = w1.Base.Shape.Edges + w2.Base.Shape.Edges |
| | import DraftGeomUtils |
| |
|
| | w = DraftGeomUtils.findWires(eds) |
| | if len(w) == 1: |
| | |
| | normal, length, width, height = w1.Proxy.getDefaultValues(w1) |
| | print(w[0].Edges) |
| | sh = w1.Proxy.getBase(w1, w[0], normal, width, height) |
| | print(sh) |
| | return sh |
| | return None |
| |
|
| |
|
| | def areSameWallTypes(walls): |
| | """Check if a list of walls have the same height, width and alignment. |
| | |
| | Parameters |
| | ---------- |
| | walls: list of <ArchComponent.Component> |
| | |
| | Returns |
| | ------- |
| | bool |
| | True if the walls have the same height, width and alignment, False if |
| | otherwise. |
| | """ |
| |
|
| | for att in ["Width", "Height", "Align"]: |
| | value = None |
| | for w in walls: |
| | if not hasattr(w, att): |
| | return False |
| | if not value: |
| | value = getattr(w, att) |
| | else: |
| | if type(value) == float: |
| | if round(value, Draft.precision()) != round(getattr(w, att), Draft.precision()): |
| | return False |
| | else: |
| | if value != getattr(w, att): |
| | return False |
| | return True |
| |
|
| |
|
| | class _Wall(ArchComponent.Component): |
| | """The Wall object. |
| | |
| | Turns a <App::FeaturePython> into a wall object, then uses a |
| | <Part::Feature> to create the wall's shape. |
| | |
| | Walls are simple objects, usually vertical, typically obtained by giving a |
| | thickness to a base line, then extruding it vertically. |
| | |
| | Parameters |
| | ---------- |
| | obj: <App::FeaturePython> |
| | The object to turn into a wall. Note that this is not the object that |
| | forms the basis for the new wall's shape. That is given later. |
| | """ |
| |
|
| | def __init__(self, obj): |
| | ArchComponent.Component.__init__(self, obj) |
| | self.Type = "Wall" |
| | self.setProperties(obj) |
| | obj.IfcType = "Wall" |
| |
|
| | def setProperties(self, obj): |
| | """Give the wall its wall specific properties, such as its alignment. |
| | |
| | You can learn more about properties here: |
| | https://wiki.freecad.org/property |
| | |
| | parameters |
| | ---------- |
| | obj: <part::featurepython> |
| | The object to turn into a wall. |
| | """ |
| |
|
| | lp = obj.PropertiesList |
| | if not "Length" in lp: |
| | obj.addProperty( |
| | "App::PropertyLength", |
| | "Length", |
| | "Wall", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "The length of this wall. Read-only if this wall is not based on an unconstrained sketch with a single edge, or on a Draft Wire with a single edge. Refer to wiki for details how length is deduced.", |
| | ), |
| | locked=True, |
| | ) |
| | if not "Width" in lp: |
| | obj.addProperty( |
| | "App::PropertyLength", |
| | "Width", |
| | "Wall", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "The width of this wall. Not used if this wall is based on a face. Disabled and ignored if Base object (ArchSketch) provides the information.", |
| | ), |
| | locked=True, |
| | ) |
| |
|
| | |
| | if not "OverrideWidth" in lp: |
| | obj.addProperty( |
| | "App::PropertyFloatList", |
| | "OverrideWidth", |
| | "Wall", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "This overrides Width attribute to set width of each segment of wall. Disabled and ignored if Base object (ArchSketch) provides Widths information, with getWidths() method (If a value is zero, the value of 'Width' will be followed). [ENHANCEMENT by ArchSketch] GUI 'Edit Wall Segment Width' Tool is provided in external SketchArch Add-on to let users to set the values interactively. 'Toponaming-Tolerant' if ArchSketch is used in Base (and SketchArch Add-on is installed). Warning : Not 'Toponaming-Tolerant' if just Sketch is used.", |
| | ), |
| | locked=True, |
| | ) |
| | 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. Disabled and ignored if Base object (ArchSketch) provides Aligns information, with getAligns() method (If a value is not 'Left, Right, Center', the value of 'Align' will be followed). [ENHANCEMENT by ArchSketch] GUI 'Edit Wall Segment Align' Tool is provided in external SketchArch Add-on to let users to set the values interactively. 'Toponaming-Tolerant' if ArchSketch is used in Base (and SketchArch Add-on is installed). Warning : Not 'Toponaming-Tolerant' if just Sketch is used.", |
| | ), |
| | locked=True, |
| | ) |
| | if not "OverrideOffset" in lp: |
| | obj.addProperty( |
| | "App::PropertyFloatList", |
| | "OverrideOffset", |
| | "Wall", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "This overrides Offset attribute to set offset of each segment of wall. Disabled and ignored if Base object (ArchSketch) provides Offsets information, with getOffsets() method (If a value is zero, the value of 'Offset' will be followed). [ENHANCED by ArchSketch] GUI 'Edit Wall Segment Offset' Tool is provided in external Add-on ('SketchArch') to let users to select the edges interactively. 'Toponaming-Tolerant' if ArchSketch is used in Base (and SketchArch Add-on is installed). Warning : Not 'Toponaming-Tolerant' if just Sketch is used. Property is ignored if Base ArchSketch provided the selected edges. ", |
| | ), |
| | locked=True, |
| | ) |
| | 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", |
| | ), |
| | locked=True, |
| | ) |
| | if not "Area" in lp: |
| | obj.addProperty( |
| | "App::PropertyArea", |
| | "Area", |
| | "Wall", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", "The area of this wall as a simple Height * Length calculation" |
| | ), |
| | locked=True, |
| | ) |
| | obj.setEditorMode("Area", 1) |
| | if not "Align" in lp: |
| | obj.addProperty( |
| | "App::PropertyEnumeration", |
| | "Align", |
| | "Wall", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "The alignment of this wall on its base object, if applicable. Disabled and ignored if Base object (ArchSketch) provides the information.", |
| | ), |
| | locked=True, |
| | ) |
| | obj.Align = ["Left", "Right", "Center"] |
| | if not "Normal" in lp: |
| | obj.addProperty( |
| | "App::PropertyVector", |
| | "Normal", |
| | "Wall", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "The normal extrusion direction of this object (keep (0,0,0) for automatic normal)", |
| | ), |
| | locked=True, |
| | ) |
| | if not "Face" in lp: |
| | obj.addProperty( |
| | "App::PropertyInteger", |
| | "Face", |
| | "Wall", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", "The face number of the base object used to build this wall" |
| | ), |
| | locked=True, |
| | ) |
| | if not "Offset" in lp: |
| | obj.addProperty( |
| | "App::PropertyDistance", |
| | "Offset", |
| | "Wall", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "The offset between this wall and its baseline (only for left and right alignments). Disabled and ignored if Base object (ArchSketch) provides the information.", |
| | ), |
| | locked=True, |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | if not "MakeBlocks" in lp: |
| | obj.addProperty( |
| | "App::PropertyBool", |
| | "MakeBlocks", |
| | "Blocks", |
| | QT_TRANSLATE_NOOP("App::Property", "Enable this to make the wall generate blocks"), |
| | locked=True, |
| | ) |
| | if not "BlockLength" in lp: |
| | obj.addProperty( |
| | "App::PropertyLength", |
| | "BlockLength", |
| | "Blocks", |
| | QT_TRANSLATE_NOOP("App::Property", "The length of each block"), |
| | locked=True, |
| | ) |
| | if not "BlockHeight" in lp: |
| | obj.addProperty( |
| | "App::PropertyLength", |
| | "BlockHeight", |
| | "Blocks", |
| | QT_TRANSLATE_NOOP("App::Property", "The height of each block"), |
| | locked=True, |
| | ) |
| | if not "OffsetFirst" in lp: |
| | obj.addProperty( |
| | "App::PropertyLength", |
| | "OffsetFirst", |
| | "Blocks", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", "The horizontal offset of the first line of blocks" |
| | ), |
| | locked=True, |
| | ) |
| | if not "OffsetSecond" in lp: |
| | obj.addProperty( |
| | "App::PropertyLength", |
| | "OffsetSecond", |
| | "Blocks", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", "The horizontal offset of the second line of blocks" |
| | ), |
| | locked=True, |
| | ) |
| | if not "Joint" in lp: |
| | obj.addProperty( |
| | "App::PropertyLength", |
| | "Joint", |
| | "Blocks", |
| | QT_TRANSLATE_NOOP("App::Property", "The size of the joints between each block"), |
| | locked=True, |
| | ) |
| | if not "CountEntire" in lp: |
| | obj.addProperty( |
| | "App::PropertyInteger", |
| | "CountEntire", |
| | "Blocks", |
| | QT_TRANSLATE_NOOP("App::Property", "The number of entire blocks"), |
| | locked=True, |
| | ) |
| | obj.setEditorMode("CountEntire", 1) |
| | if not "CountBroken" in lp: |
| | obj.addProperty( |
| | "App::PropertyInteger", |
| | "CountBroken", |
| | "Blocks", |
| | QT_TRANSLATE_NOOP("App::Property", "The number of broken blocks"), |
| | locked=True, |
| | ) |
| | obj.setEditorMode("CountBroken", 1) |
| | if not "ArchSketchData" in lp: |
| | obj.addProperty( |
| | "App::PropertyBool", |
| | "ArchSketchData", |
| | "Wall", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Use Base ArchSketch (if used) data (e.g. widths, aligns, offsets) instead of Wall's properties", |
| | ), |
| | locked=True, |
| | ) |
| | obj.ArchSketchData = True |
| | if not "ArchSketchEdges" in lp: |
| | obj.addProperty( |
| | "App::PropertyStringList", |
| | "ArchSketchEdges", |
| | "Wall", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Selected edges (or group of edges) of the base Sketch/ArchSketch, to use in creating the shape of this Arch Wall (instead of using all the Base Sketch/ArchSketch's edges by default). Input are index numbers of edges or groups. Disabled and ignored if Base object (ArchSketch) provides selected edges (as Wall Axis) information, with getWallBaseShapeEdgesInfo() method. [ENHANCEMENT by ArchSketch] GUI 'Edit Wall Segment' Tool is provided in external SketchArch Add-on to let users to (de)select the edges interactively. 'Toponaming-Tolerant' if ArchSketch is used in Base (and SketchArch Add-on is installed). Warning : Not 'Toponaming-Tolerant' if just Sketch is used.", |
| | ), |
| | locked=True, |
| | ) |
| | if not hasattr(obj, "ArchSketchPropertySet"): |
| | obj.addProperty( |
| | "App::PropertyEnumeration", |
| | "ArchSketchPropertySet", |
| | "Wall", |
| | QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "Select User Defined PropertySet to use in creating variant shape, layers of the Arch Wall with same ArchSketch ", |
| | ), |
| | locked=True, |
| | ) |
| | obj.ArchSketchPropertySet = ["Default"] |
| | if not hasattr(self, "ArchSkPropSetPickedUuid"): |
| | self.ArchSkPropSetPickedUuid = "" |
| | if not hasattr(self, "ArchSkPropSetListPrev"): |
| | self.ArchSkPropSetListPrev = [] |
| | self.connectEdges = [] |
| |
|
| | def dumps(self): |
| | dump = super().dumps() |
| | if not isinstance(dump, tuple): |
| | dump = (dump,) |
| | dump = dump + (self.ArchSkPropSetPickedUuid, self.ArchSkPropSetListPrev) |
| | return dump |
| |
|
| | def loads(self, state): |
| | self.Type = "Wall" |
| | if state == None: |
| | return |
| | elif state[0] == "W": |
| | return |
| | elif state[0] == "Wall": |
| | self.ArchSkPropSetPickedUuid = state[1] |
| | self.ArchSkPropSetListPrev = state[2] |
| | elif state[0] != "Wall": |
| | self.ArchSkPropSetPickedUuid = state[0] |
| | self.ArchSkPropSetListPrev = state[1] |
| |
|
| | def onDocumentRestored(self, obj): |
| | """Method run when the document is restored. Re-adds the Arch component, and Arch wall properties.""" |
| |
|
| | import DraftGeomUtils |
| | from draftutils.messages import _log |
| |
|
| | ArchComponent.Component.onDocumentRestored(self, obj) |
| | self.setProperties(obj) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if ( |
| | FreeCAD.ActiveDocument.getProgramVersion() < "0.22" |
| | and obj.Normal == Vector(0, 0, 0) |
| | and hasattr(obj.Base, "Shape") |
| | and not obj.Base.Shape.Solids |
| | and obj.Face == 0 |
| | and not obj.Base.isDerivedFrom("Sketcher::SketchObject") |
| | and DraftGeomUtils.get_shape_normal(obj.Base.Shape) != Vector(0, 0, 1) |
| | ): |
| | obj.Normal = Vector(0, 0, 1) |
| | _log( |
| | "v1.0, " |
| | + obj.Name |
| | + ", " |
| | + "changed 'Normal' to [0, 0, 1] to preserve extrusion direction" |
| | ) |
| |
|
| | if ( |
| | hasattr(obj, "ArchSketchData") |
| | and obj.ArchSketchData |
| | and Draft.getType(obj.Base) == "ArchSketch" |
| | ): |
| | if hasattr(obj, "Width"): |
| | obj.setEditorMode("Width", ["ReadOnly"]) |
| | if hasattr(obj, "Align"): |
| | obj.setEditorMode("Align", ["ReadOnly"]) |
| | if hasattr(obj, "Offset"): |
| | obj.setEditorMode("Offset", ["ReadOnly"]) |
| | if hasattr(obj, "OverrideWidth"): |
| | obj.setEditorMode("OverrideWidth", ["ReadOnly"]) |
| | if hasattr(obj, "OverrideAlign"): |
| | obj.setEditorMode("OverrideAlign", ["ReadOnly"]) |
| | if hasattr(obj, "OverrideOffset"): |
| | obj.setEditorMode("OverrideOffset", ["ReadOnly"]) |
| | if hasattr(obj, "ArchSketchEdges"): |
| | obj.setEditorMode("ArchSketchEdges", ["ReadOnly"]) |
| | if hasattr(obj, "ArchSketchPropertySet"): |
| | obj.setEditorMode("ArchSketchPropertySet", 0) |
| | else: |
| | if hasattr(obj, "Width"): |
| | obj.setEditorMode("Width", 0) |
| | if hasattr(obj, "Align"): |
| | obj.setEditorMode("Align", 0) |
| | if hasattr(obj, "Offset"): |
| | obj.setEditorMode("Offset", 0) |
| | if hasattr(obj, "OverrideWidth"): |
| | obj.setEditorMode("OverrideWidth", 0) |
| | if hasattr(obj, "OverrideAlign"): |
| | obj.setEditorMode("OverrideAlign", 0) |
| | if hasattr(obj, "OverrideOffset"): |
| | obj.setEditorMode("OverrideOffset", 0) |
| | if hasattr(obj, "ArchSketchEdges"): |
| | obj.setEditorMode("ArchSketchEdges", 0) |
| | if hasattr(obj, "ArchSketchPropertySet"): |
| | obj.setEditorMode("ArchSketchPropertySet", ["ReadOnly"]) |
| |
|
| | def execute(self, obj): |
| | """Method run when the object is recomputed. |
| | |
| | Extrude the wall from the Base shape if possible. Processe additions |
| | and subtractions. Assign the resulting shape as the shape of the wall. |
| | |
| | Add blocks if the MakeBlocks property is assigned. If the Base shape is |
| | a mesh, just copy the mesh. |
| | """ |
| |
|
| | if self.clone(obj): |
| | return |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | import Part |
| | import DraftGeomUtils |
| |
|
| | base = None |
| | pl = obj.Placement |
| |
|
| | |
| | propSetPickedUuidPrev = self.ArchSkPropSetPickedUuid |
| | propSetListPrev = self.ArchSkPropSetListPrev |
| | propSetSelectedNamePrev = obj.ArchSketchPropertySet |
| | propSetSelectedNameCur = None |
| | propSetListCur = None |
| | if Draft.getType(obj.Base) == "ArchSketch": |
| | baseProxy = obj.Base.Proxy |
| | if hasattr(baseProxy, "getPropertySet"): |
| | |
| | propSetListCur = baseProxy.getPropertySet(obj.Base) |
| | |
| | propSetSelectedNameCur = baseProxy.getPropertySet( |
| | obj.Base, propSetUuid=propSetPickedUuidPrev |
| | ) |
| | if propSetSelectedNameCur: |
| | if propSetListPrev != propSetListCur: |
| | obj.ArchSketchPropertySet = propSetListCur |
| | obj.ArchSketchPropertySet = propSetSelectedNameCur |
| | self.ArchSkPropSetListPrev = propSetListCur |
| | |
| | |
| | |
| | elif propSetSelectedNamePrev != propSetSelectedNameCur: |
| | obj.ArchSketchPropertySet = propSetSelectedNameCur |
| | else: |
| | if propSetListCur: |
| | if propSetListPrev != propSetListCur: |
| | obj.ArchSketchPropertySet = propSetListCur |
| | obj.ArchSketchPropertySet = "Default" |
| | |
| | |
| |
|
| | extdata = self.getExtrusionData(obj) |
| | if extdata: |
| | base_faces = extdata[0] |
| | extv = extdata[2].Rotation.multVec(extdata[1]) |
| |
|
| | |
| | |
| | if not isinstance(base_faces, list): |
| | base_faces = [base_faces] |
| |
|
| | |
| | |
| | should_fuse_solids = False |
| | if obj.Base and obj.Base.isDerivedFrom("Sketcher::SketchObject"): |
| | is_multi_layer = ( |
| | hasattr(obj, "Material") |
| | and obj.Material |
| | and hasattr(obj.Material, "Materials") |
| | and obj.Material.Materials |
| | ) |
| | if not is_multi_layer: |
| | should_fuse_solids = True |
| |
|
| | |
| | solids = [] |
| | for face in base_faces: |
| | face.Placement = extdata[2].multiply(face.Placement) |
| | solids.append(face.extrude(extv)) |
| |
|
| | |
| | if should_fuse_solids: |
| | fused_shape = None |
| | for solid in solids: |
| | fused_shape = fused_shape.fuse(solid) if fused_shape else solid |
| | base = fused_shape |
| | else: |
| | base = Part.makeCompound(solids) |
| | if obj.Base: |
| | if hasattr(obj.Base, "Shape"): |
| | if obj.Base.Shape.isNull(): |
| | return |
| | if not obj.Base.Shape.isValid(): |
| | if not obj.Base.Shape.Solids: |
| | |
| | return |
| | elif obj.Base.Shape.Solids: |
| | base = Part.Shape(obj.Base.Shape) |
| | |
| | elif hasattr(obj, "MakeBlocks") and hasattr(self, "basewires"): |
| | if obj.MakeBlocks and self.basewires and extdata and obj.Width and obj.Height: |
| | |
| | if len(self.basewires) == 1: |
| | blocks = [] |
| | n = FreeCAD.Vector(extv) |
| | n.normalize() |
| | cuts1 = [] |
| | cuts2 = [] |
| | if obj.BlockLength.Value: |
| | for i in range(2): |
| | if i == 0: |
| | offset = obj.OffsetFirst.Value |
| | else: |
| | offset = obj.OffsetSecond.Value |
| |
|
| | |
| | |
| |
|
| | |
| | |
| | |
| | baseEdges = self.basewires[0] |
| |
|
| | for edge in baseEdges: |
| | while offset < (edge.Length - obj.Joint.Value): |
| | if offset: |
| | t = edge.tangentAt(offset) |
| | p = t.cross(n) |
| | p.multiply(1.1 * obj.Width.Value + obj.Offset.Value) |
| | p1 = edge.valueAt(offset).add(p) |
| | p2 = edge.valueAt(offset).add(p.negative()) |
| | sh = Part.LineSegment(p1, p2).toShape() |
| | if obj.Joint.Value: |
| | sh = sh.extrude(-t.multiply(obj.Joint.Value)) |
| | sh = sh.extrude(n) |
| | if i == 0: |
| | cuts1.append(sh) |
| | else: |
| | cuts2.append(sh) |
| | offset += obj.BlockLength.Value + obj.Joint.Value |
| | offset -= edge.Length |
| |
|
| | if isinstance(bplates, list): |
| | bplates = bplates[0] |
| | if obj.BlockHeight.Value: |
| | fsize = obj.BlockHeight.Value + obj.Joint.Value |
| | bh = obj.BlockHeight.Value |
| | else: |
| | fsize = obj.Height.Value |
| | bh = obj.Height.Value |
| | bvec = FreeCAD.Vector(n) |
| | bvec.multiply(bh) |
| | svec = FreeCAD.Vector(n) |
| | svec.multiply(fsize) |
| | if cuts1: |
| | plate1 = bplates.cut(cuts1).Faces |
| | else: |
| | plate1 = bplates.Faces |
| | blocks1 = Part.makeCompound([f.extrude(bvec) for f in plate1]) |
| | if cuts2: |
| | plate2 = bplates.cut(cuts2).Faces |
| | else: |
| | plate2 = bplates.Faces |
| | blocks2 = Part.makeCompound([f.extrude(bvec) for f in plate2]) |
| | interval = extv.Length / (fsize) |
| | entire = int(interval) |
| | rest = interval - entire |
| | for i in range(entire): |
| | if i % 2: |
| | b = Part.Shape(blocks2) |
| | else: |
| | b = Part.Shape(blocks1) |
| | if i: |
| | t = FreeCAD.Vector(svec) |
| | t.multiply(i) |
| | b.translate(t) |
| | blocks.append(b) |
| | if rest: |
| | rest = extv.Length - (entire * fsize) |
| | rvec = FreeCAD.Vector(n) |
| | rvec.multiply(rest) |
| | if entire % 2: |
| | b = Part.makeCompound([f.extrude(rvec) for f in plate2]) |
| | else: |
| | b = Part.makeCompound([f.extrude(rvec) for f in plate1]) |
| | t = FreeCAD.Vector(svec) |
| | t.multiply(entire) |
| | b.translate(t) |
| | blocks.append(b) |
| | if blocks: |
| | base = Part.makeCompound(blocks) |
| |
|
| | else: |
| | FreeCAD.Console.PrintWarning( |
| | translate("Arch", "Cannot compute blocks for wall") |
| | + obj.Label |
| | + "\n" |
| | ) |
| |
|
| | elif obj.Base.isDerivedFrom("Mesh::Feature"): |
| | if obj.Base.Mesh.isSolid(): |
| | if obj.Base.Mesh.countComponents() == 1: |
| | sh = ArchCommands.getShapeFromMesh(obj.Base.Mesh) |
| | if sh.isClosed() and sh.isValid() and sh.Solids and (not sh.isNull()): |
| | base = sh |
| | else: |
| | FreeCAD.Console.PrintWarning( |
| | translate("Arch", "This mesh is an invalid solid") + "\n" |
| | ) |
| | obj.Base.ViewObject.show() |
| | if not base: |
| | |
| | |
| | |
| | base = Part.Shape() |
| | base = self.processSubShapes(obj, base, pl) |
| | self.applyShape(obj, base, pl) |
| |
|
| | |
| | |
| | if base.isNull() and (self.noWidths or self.noHeight): |
| | FreeCAD.Console.PrintWarning( |
| | translate( |
| | "Arch", |
| | f"Cannot create or update {obj.Label} as its length, height or width is zero, and there are no solids in its additions", |
| | ) |
| | + "\n" |
| | ) |
| |
|
| | |
| | if hasattr(obj, "MakeBlocks"): |
| | if obj.MakeBlocks: |
| | fvol = obj.BlockLength.Value * obj.BlockHeight.Value * obj.Width.Value |
| | if fvol: |
| | |
| | |
| | |
| | ents = [s for s in base.Solids if abs(s.Volume - fvol) < 1] |
| | obj.CountEntire = len(ents) |
| | obj.CountBroken = len(base.Solids) - len(ents) |
| | else: |
| | obj.CountEntire = 0 |
| | obj.CountBroken = 0 |
| |
|
| | |
| | if self.connectEdges: |
| | l = float(0) |
| | for e in self.connectEdges: |
| | l += e.Length |
| | l = l / 2 |
| | if self.layersNum: |
| | l = l / self.layersNum |
| | if obj.Length.Value != l: |
| | obj.Length = l |
| | self.oldLength = ( |
| | None |
| | ) |
| |
|
| | |
| | obj.Area = obj.Length.Value * obj.Height.Value |
| |
|
| | def onBeforeChange(self, obj, prop): |
| | """Method called before the object has a property changed. |
| | |
| | Specifically, this method is called before the value changes. |
| | |
| | If "Length" has changed, record the old length so that .onChanged() can |
| | be sure that the base needs to be changed. |
| | |
| | Also call ArchComponent.Component.onBeforeChange(). |
| | |
| | Parameters |
| | ---------- |
| | prop: string |
| | The name of the property that has changed. |
| | """ |
| |
|
| | if prop == "Length": |
| | self.oldLength = obj.Length.Value |
| | ArchComponent.Component.onBeforeChange(self, obj, prop) |
| |
|
| | def onChanged(self, obj, prop): |
| | """Method called when the object has a property changed. |
| | |
| | If length has changed, extend the length of the Base object, if the |
| | Base object only has a single edge to extend. |
| | |
| | Also hide subobjects. |
| | |
| | Also call ArchComponent.Component.onChanged(). |
| | |
| | Parameters |
| | ---------- |
| | prop: string |
| | The name of the property that has changed. |
| | """ |
| |
|
| | if prop == "Length": |
| | if ( |
| | obj.Base |
| | and obj.Length.Value |
| | and hasattr(self, "oldLength") |
| | and (self.oldLength is not None) |
| | and (self.oldLength != obj.Length.Value) |
| | ): |
| | if hasattr(obj.Base, "Shape"): |
| | if len(obj.Base.Shape.Edges) == 1: |
| | import DraftGeomUtils |
| |
|
| | e = obj.Base.Shape.Edges[0] |
| | if DraftGeomUtils.geomType(e) == "Line": |
| | if e.Length != obj.Length.Value: |
| | v = e.Vertexes[-1].Point.sub(e.Vertexes[0].Point) |
| | v.normalize() |
| | v.multiply(obj.Length.Value) |
| | p2 = e.Vertexes[0].Point.add(v) |
| | if Draft.getType(obj.Base) == "Wire": |
| | |
| | obj.Base.End = p2 |
| | elif Draft.getType(obj.Base) in [ |
| | "Sketcher::SketchObject", |
| | "ArchSketch", |
| | ]: |
| | |
| | obj.Base.moveGeometry( |
| | 0, 2, obj.Base.Placement.inverse().multVec(p2) |
| | ) |
| | else: |
| | FreeCAD.Console.PrintError( |
| | translate( |
| | "Arch", |
| | "Error: Unable to modify the base object of this wall", |
| | ) |
| | + "\n" |
| | ) |
| |
|
| | if prop == "ArchSketchPropertySet" and Draft.getType(obj.Base) == "ArchSketch": |
| | baseProxy = obj.Base.Proxy |
| | if hasattr(baseProxy, "getPropertySet"): |
| | uuid = baseProxy.getPropertySet(obj, propSetName=obj.ArchSketchPropertySet) |
| | self.ArchSkPropSetPickedUuid = uuid |
| | if ( |
| | hasattr(obj, "ArchSketchData") |
| | and obj.ArchSketchData |
| | and Draft.getType(obj.Base) == "ArchSketch" |
| | ): |
| | if hasattr(obj, "Width"): |
| | obj.setEditorMode("Width", ["ReadOnly"]) |
| | if hasattr(obj, "Align"): |
| | obj.setEditorMode("Align", ["ReadOnly"]) |
| | if hasattr(obj, "Offset"): |
| | obj.setEditorMode("Offset", ["ReadOnly"]) |
| | if hasattr(obj, "OverrideWidth"): |
| | obj.setEditorMode("OverrideWidth", ["ReadOnly"]) |
| | if hasattr(obj, "OverrideAlign"): |
| | obj.setEditorMode("OverrideAlign", ["ReadOnly"]) |
| | if hasattr(obj, "OverrideOffset"): |
| | obj.setEditorMode("OverrideOffset", ["ReadOnly"]) |
| | if hasattr(obj, "ArchSketchEdges"): |
| | obj.setEditorMode("ArchSketchEdges", ["ReadOnly"]) |
| | if hasattr(obj, "ArchSketchPropertySet"): |
| | obj.setEditorMode("ArchSketchPropertySet", 0) |
| | else: |
| | if hasattr(obj, "Width"): |
| | obj.setEditorMode("Width", 0) |
| | if hasattr(obj, "Align"): |
| | obj.setEditorMode("Align", 0) |
| | if hasattr(obj, "Offset"): |
| | obj.setEditorMode("Offset", 0) |
| | if hasattr(obj, "OverrideWidth"): |
| | obj.setEditorMode("OverrideWidth", 0) |
| | if hasattr(obj, "OverrideAlign"): |
| | obj.setEditorMode("OverrideAlign", 0) |
| | if hasattr(obj, "OverrideOffset"): |
| | obj.setEditorMode("OverrideOffset", 0) |
| | if hasattr(obj, "ArchSketchEdges"): |
| | obj.setEditorMode("ArchSketchEdges", 0) |
| | if hasattr(obj, "ArchSketchPropertySet"): |
| | obj.setEditorMode("ArchSketchPropertySet", ["ReadOnly"]) |
| |
|
| | self.hideSubobjects(obj, prop) |
| | ArchComponent.Component.onChanged(self, obj, prop) |
| |
|
| | def getFootprint(self, obj): |
| | """Get the faces that make up the base/foot of the wall. |
| | |
| | Returns |
| | ------- |
| | list of <Part.Face> |
| | The faces that make up the foot of the wall. |
| | """ |
| |
|
| | faces = [] |
| | if obj.Shape: |
| | for f in obj.Shape.Faces: |
| | if f.normalAt(0, 0).getAngle(FreeCAD.Vector(0, 0, -1)) < 0.01: |
| | if abs(abs(f.CenterOfMass.z) - abs(obj.Shape.BoundBox.ZMin)) < 0.001: |
| | faces.append(f) |
| | return faces |
| |
|
| | def getExtrusionData(self, obj): |
| | """Get data needed to extrude the wall from a base object. |
| | |
| | take the Base object, and find a base face to extrude |
| | out, a vector to define the extrusion direction and distance. |
| | |
| | Rebase the base face to the (0,0,0) origin. |
| | |
| | Return the base face, rebased, with the extrusion vector, and the |
| | <Base.Placement> needed to return the face back to its original |
| | position. |
| | |
| | Returns |
| | ------- |
| | tuple of (<Part.Face>, <Base.Vector>, <Base.Placement>) |
| | Tuple containing the base face, the vector for extrusion, and the |
| | placement needed to move the face back from the (0,0,0) origin. |
| | """ |
| |
|
| | import Part |
| | import DraftGeomUtils |
| | import ArchSketchObject |
| |
|
| | propSetUuid = self.ArchSkPropSetPickedUuid |
| |
|
| | |
| | |
| | data = ArchComponent.Component.getExtrusionData(self, obj) |
| | if data: |
| | if not isinstance(data[0], list): |
| | |
| | return data |
| | length = obj.Length.Value |
| | |
| |
|
| | self.noWidths = False |
| | self.noHeight = False |
| | width = 0 |
| | |
| | |
| | widths = [] |
| | if ( |
| | hasattr(obj, "ArchSketchData") |
| | and obj.ArchSketchData |
| | and Draft.getType(obj.Base) == "ArchSketch" |
| | ): |
| | if hasattr(obj.Base, "Proxy"): |
| | if hasattr(obj.Base.Proxy, "getWidths"): |
| | |
| | |
| | widths = obj.Base.Proxy.getWidths(obj.Base, propSetUuid=propSetUuid) |
| | |
| | |
| | if not widths: |
| | if obj.OverrideWidth: |
| | if obj.Base and obj.Base.isDerivedFrom("Sketcher::SketchObject"): |
| | |
| | |
| | try: |
| | import ArchSketchObject |
| | except Exception: |
| | print("ArchSketchObject add-on module is not installed yet") |
| | try: |
| | widths = ArchSketchObject.sortSketchWidth( |
| | obj.Base, obj.OverrideWidth, obj.ArchSketchEdges |
| | ) |
| | except Exception: |
| | widths = obj.OverrideWidth |
| | else: |
| | |
| | |
| | |
| | widths = obj.OverrideWidth |
| | elif obj.Width: |
| | widths = [obj.Width.Value] |
| | else: |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | self.noWidths = True |
| | |
| |
|
| | |
| | if obj.Width.Value: |
| | width = obj.Width.Value |
| | else: |
| | width = 200 |
| |
|
| | |
| | height = obj.Height.Value |
| | if not height: |
| | height = self.getParentHeight(obj) |
| | if not height: |
| | self.noHeight = True |
| |
|
| | |
| | if self.noWidths or self.noHeight: |
| | return None |
| |
|
| | |
| | |
| | aligns = [] |
| | if ( |
| | hasattr(obj, "ArchSketchData") |
| | and obj.ArchSketchData |
| | and Draft.getType(obj.Base) == "ArchSketch" |
| | ): |
| | if hasattr(obj.Base, "Proxy"): |
| | if hasattr(obj.Base.Proxy, "getAligns"): |
| | |
| | |
| | aligns = obj.Base.Proxy.getAligns(obj.Base, propSetUuid=propSetUuid) |
| | |
| | |
| | if not aligns: |
| | if obj.OverrideAlign: |
| | if obj.Base and obj.Base.isDerivedFrom("Sketcher::SketchObject"): |
| | |
| | |
| | |
| | |
| | try: |
| | import ArchSketchObject |
| | except Exception: |
| | print("ArchSketchObject add-on module is not installed yet") |
| | try: |
| | aligns = ArchSketchObject.sortSketchAlign( |
| | obj.Base, obj.OverrideAlign, obj.ArchSketchEdges |
| | ) |
| | except Exception: |
| | aligns = obj.OverrideAlign |
| | else: |
| | |
| | |
| | |
| | aligns = obj.OverrideAlign |
| | else: |
| | aligns = [obj.Align] |
| |
|
| | |
| | align = obj.Align |
| |
|
| | |
| | |
| | offsets = [] |
| | if ( |
| | hasattr(obj, "ArchSketchData") |
| | and obj.ArchSketchData |
| | and Draft.getType(obj.Base) == "ArchSketch" |
| | ): |
| | if hasattr(obj.Base, "Proxy"): |
| | if hasattr(obj.Base.Proxy, "getOffsets"): |
| | |
| | |
| | offsets = obj.Base.Proxy.getOffsets(obj.Base, propSetUuid=propSetUuid) |
| | |
| | |
| | if not offsets: |
| | if obj.OverrideOffset: |
| | if obj.Base and obj.Base.isDerivedFrom("Sketcher::SketchObject"): |
| | |
| | |
| | if hasattr(ArchSketchObject, "sortSketchOffset"): |
| | offsets = ArchSketchObject.sortSketchOffset( |
| | obj.Base, obj.OverrideOffset, obj.ArchSketchEdges |
| | ) |
| | else: |
| | offsets = obj.OverrideOffset |
| | else: |
| | |
| | |
| | |
| | offsets = obj.OverrideOffset |
| | elif obj.Offset: |
| | offsets = [obj.Offset.Value] |
| |
|
| | |
| | offset = obj.Offset.Value |
| |
|
| | if obj.Normal == Vector(0, 0, 0): |
| | if obj.Base and hasattr(obj.Base, "Shape"): |
| | normal = DraftGeomUtils.get_shape_normal(obj.Base.Shape) |
| | if normal is None: |
| | normal = Vector(0, 0, 1) |
| | else: |
| | normal = Vector(0, 0, 1) |
| | else: |
| | normal = Vector(obj.Normal) |
| | base = None |
| | placement = None |
| | self.basewires = None |
| |
|
| | |
| | layers = [] |
| | if hasattr(obj, "Material"): |
| | if obj.Material: |
| | if hasattr(obj.Material, "Materials"): |
| | thicknesses = [abs(t) for t in obj.Material.Thicknesses] |
| | |
| | varwidth = 0 |
| | restwidth = width - sum(thicknesses) |
| | if restwidth > 0: |
| | varwidth = [t for t in thicknesses if t == 0] |
| | if varwidth: |
| | varwidth = restwidth / len(varwidth) |
| | for t in obj.Material.Thicknesses: |
| | if t: |
| | layers.append(t) |
| | elif varwidth: |
| | layers.append(varwidth) |
| |
|
| | |
| | if self.ensureBase(obj): |
| | if hasattr(obj.Base, "Shape"): |
| | if obj.Base.Shape: |
| | if obj.Base.Shape.Solids: |
| | return None |
| |
|
| | |
| | |
| | |
| | |
| | |
| | elif obj.Face > 0: |
| | if len(obj.Base.Shape.Faces) >= obj.Face: |
| | face = obj.Base.Shape.Faces[obj.Face - 1] |
| | if obj.Normal != Vector(0, 0, 0): |
| | normal = face.normalAt(0, 0) |
| | if normal.getAngle(Vector(0, 0, 1)) > math.pi / 4: |
| | normal.multiply(width) |
| | base = face.extrude(normal) |
| | if obj.Align == "Center": |
| | base.translate(normal.negative().multiply(0.5)) |
| | elif obj.Align == "Right": |
| | base.translate(normal.negative()) |
| | else: |
| | normal.multiply(height) |
| | base = face.extrude(normal) |
| | base, placement = self.rebase(base) |
| | return (base, normal, placement) |
| |
|
| | |
| | |
| | elif obj.Base.Shape.Faces: |
| | if not DraftGeomUtils.isCoplanar(obj.Base.Shape.Faces): |
| | return None |
| | else: |
| | base, placement = self.rebase(obj.Base.Shape) |
| |
|
| | elif ( |
| | hasattr(obj.Base, "Proxy") |
| | and obj.ArchSketchData |
| | and hasattr(obj.Base.Proxy, "getWallBaseShapeEdgesInfo") |
| | ): |
| | wallBaseShapeEdgesInfo = obj.Base.Proxy.getWallBaseShapeEdgesInfo( |
| | obj.Base, propSetUuid=propSetUuid |
| | ) |
| | |
| | if wallBaseShapeEdgesInfo: |
| | self.basewires = wallBaseShapeEdgesInfo.get( |
| | "wallAxis" |
| | ) |
| |
|
| | |
| | |
| | |
| | elif obj.Base.isDerivedFrom("Sketcher::SketchObject"): |
| | self.basewires = [] |
| | skGeom = obj.Base.GeometryFacadeList |
| | skGeomEdges = [] |
| | skPlacement = obj.Base.Placement |
| | |
| | |
| | for ig, geom in enumerate(skGeom): |
| | |
| | |
| | |
| | |
| | if (not obj.ArchSketchEdges and not geom.Construction) or str( |
| | ig |
| | ) in obj.ArchSketchEdges: |
| | |
| | |
| | if isinstance( |
| | geom.Geometry, |
| | (Part.LineSegment, Part.Circle, Part.ArcOfCircle, Part.Ellipse), |
| | ): |
| | skGeomEdgesI = geom.Geometry.toShape() |
| | skGeomEdges.append(skGeomEdgesI) |
| | for cluster in Part.getSortedClusters(skGeomEdges): |
| | clusterTransformed = [] |
| | for edge in cluster: |
| | |
| | |
| | edge.Placement = edge.Placement.multiply( |
| | skPlacement |
| | ) |
| | clusterTransformed.append(edge) |
| | |
| | self.basewires.append(clusterTransformed) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | normal = obj.Base.getGlobalPlacement().Rotation.multVec( |
| | FreeCAD.Vector(0, 0, 1) |
| | ) |
| |
|
| | else: |
| | |
| | |
| | |
| | |
| | |
| | self.basewires = [] |
| | clusters = Part.getSortedClusters(obj.Base.Shape.Edges) |
| | self.basewires = clusters |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | if self.basewires: |
| | if (len(self.basewires) == 1) and layers: |
| | self.basewires = [self.basewires[0] for l in layers] |
| | self.layersNum = len(layers) |
| | else: |
| | self.layersNum = 0 |
| | layeroffset = 0 |
| | baseface = None |
| | self.connectEdges = [] |
| | for i, wire in enumerate(self.basewires): |
| |
|
| | |
| | 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 |
| | ): |
| |
|
| | |
| | |
| | |
| | try: |
| | if aligns[n] not in ["Left", "Right", "Center"]: |
| | aligns[n] = align |
| | except Exception: |
| | aligns.append(align) |
| |
|
| | |
| | |
| | |
| | try: |
| | if not widths[n]: |
| | widths[n] = width |
| | except Exception: |
| | widths.append(width) |
| | |
| | |
| | |
| | try: |
| | if not offsets[n]: |
| | offsets[n] = offset |
| | except Exception: |
| | offsets.append(offset) |
| |
|
| | |
| | |
| | |
| | |
| | if isinstance(e.Curve, (Part.Circle, Part.Ellipse)): |
| | dvec = e.Vertexes[0].Point.sub(e.Curve.Center) |
| | else: |
| | dvec = DraftGeomUtils.vec(e).cross(normal) |
| |
|
| | if not DraftVecUtils.isNull(dvec): |
| | dvec.normalize() |
| | face = None |
| |
|
| | curAligns = aligns[0] |
| | |
| |
|
| | if curAligns == "Left": |
| |
|
| | if layers: |
| | curWidth = [] |
| | for n in range(edgeNum): |
| | curWidth.append(abs(layers[i])) |
| | |
| | offsets = [x + layeroffset for x in offsets] |
| | dvec.multiply(curWidth[0]) |
| | layeroffset += abs(curWidth[0]) |
| | else: |
| | curWidth = widths |
| | dvec.multiply(width) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | wNe2 = DraftGeomUtils.offsetWire( |
| | wire, |
| | dvec, |
| | bind=False, |
| | occ=False, |
| | widthList=curWidth, |
| | offsetMode=None, |
| | alignList=aligns, |
| | normal=normal, |
| | basewireOffset=offsets, |
| | wireNedge=True, |
| | ) |
| | |
| | |
| | wNe1 = DraftGeomUtils.offsetWire( |
| | wire, |
| | dvec, |
| | bind=False, |
| | occ=False, |
| | widthList=curWidth, |
| | offsetMode="BasewireMode", |
| | alignList=aligns, |
| | normal=normal, |
| | basewireOffset=offsets, |
| | wireNedge=True, |
| | ) |
| | elif curAligns == "Right": |
| | dvec = dvec.negative() |
| |
|
| | if layers: |
| | curWidth = [] |
| | for n in range(edgeNum): |
| | curWidth.append(abs(layers[i])) |
| | |
| | offsets = [x + layeroffset for x in offsets] |
| | dvec.multiply(curWidth[0]) |
| | layeroffset += abs(curWidth[0]) |
| | else: |
| | curWidth = widths |
| | dvec.multiply(width) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | wNe2 = DraftGeomUtils.offsetWire( |
| | wire, |
| | dvec, |
| | bind=False, |
| | occ=False, |
| | widthList=curWidth, |
| | offsetMode=None, |
| | alignList=aligns, |
| | normal=normal, |
| | basewireOffset=offsets, |
| | wireNedge=True, |
| | ) |
| | wNe1 = DraftGeomUtils.offsetWire( |
| | wire, |
| | dvec, |
| | bind=False, |
| | occ=False, |
| | widthList=curWidth, |
| | offsetMode="BasewireMode", |
| | alignList=aligns, |
| | normal=normal, |
| | basewireOffset=offsets, |
| | wireNedge=True, |
| | ) |
| | elif curAligns == "Center": |
| | if layers: |
| | |
| | |
| | |
| | |
| | dvec = dvec.negative() |
| | totalwidth = sum([abs(l) for l in layers]) |
| | off = totalwidth / 2 - layeroffset |
| | |
| | |
| | |
| | |
| | off = -off |
| | |
| | curWidth = [] |
| | alignListC = [] |
| | offsetListC = [] |
| | for n in range(edgeNum): |
| | curWidth.append(abs(layers[i])) |
| | alignListC.append("Right") |
| | offsetListC.append(off) |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | wNe1 = DraftGeomUtils.offsetWire( |
| | wire, |
| | dvec, |
| | widthList=curWidth, |
| | offsetMode="BasewireMode", |
| | alignList=alignListC, |
| | normal=normal, |
| | basewireOffset=offsetListC, |
| | wireNedge=True, |
| | ) |
| | wNe2 = DraftGeomUtils.offsetWire( |
| | wire, |
| | dvec, |
| | widthList=curWidth, |
| | offsetMode=None, |
| | alignList=alignListC, |
| | normal=normal, |
| | basewireOffset=offsetListC, |
| | wireNedge=True, |
| | ) |
| | layeroffset += abs(curWidth[0]) |
| | else: |
| | dvec.multiply(width) |
| | wNe2 = DraftGeomUtils.offsetWire( |
| | wire, |
| | dvec, |
| | bind=False, |
| | occ=False, |
| | widthList=widths, |
| | offsetMode=None, |
| | alignList=aligns, |
| | normal=normal, |
| | basewireOffset=offsets, |
| | wireNedge=True, |
| | ) |
| | wNe1 = DraftGeomUtils.offsetWire( |
| | wire, |
| | dvec, |
| | bind=False, |
| | occ=False, |
| | widthList=widths, |
| | offsetMode="BasewireMode", |
| | alignList=aligns, |
| | normal=normal, |
| | basewireOffset=offsets, |
| | wireNedge=True, |
| | ) |
| | w2 = wNe2[0] |
| | w1 = wNe1[0] |
| | face = DraftGeomUtils.bind(w1, w2, per_segment=True) |
| | cEdgesF2 = wNe2[1] |
| | cEdges2 = wNe2[2] |
| | oEdges2 = wNe2[3] |
| | cEdgesF1 = wNe1[1] |
| | cEdges1 = wNe1[2] |
| | oEdges1 = wNe1[3] |
| | self.connectEdges.extend(cEdges2) |
| | self.connectEdges.extend(cEdges1) |
| |
|
| | del widths[0:edgeNum] |
| | del aligns[0:edgeNum] |
| | del offsets[0:edgeNum] |
| |
|
| | if face: |
| |
|
| | if layers and (layers[i] < 0): |
| | |
| | continue |
| |
|
| | if baseface: |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | baseface.append(face) |
| | |
| | """ Whether layers or not, all baseface.append(face) """ |
| |
|
| | else: |
| | baseface = [face] |
| |
|
| | """ Whether layers or not, all baseface = [face] """ |
| |
|
| | if baseface: |
| | base, placement = self.rebase(baseface) |
| |
|
| | |
| | else: |
| | base, placement = self.build_base_from_scratch(obj) |
| |
|
| | if base and placement: |
| | normal.normalize() |
| | extrusion = normal.multiply(height) |
| | if placement.Rotation.Angle > 0: |
| | extrusion = placement.inverse().Rotation.multVec(extrusion) |
| | return (base, extrusion, placement) |
| | return None |
| |
|
| | def calc_endpoints(self, obj): |
| | """Returns the global start and end points of a baseless wall's centerline.""" |
| | |
| | |
| | p1_local = FreeCAD.Vector(-obj.Length.Value / 2, 0, 0) |
| | p2_local = FreeCAD.Vector(obj.Length.Value / 2, 0, 0) |
| |
|
| | |
| | p1_global = obj.Placement.multVec(p1_local) |
| | p2_global = obj.Placement.multVec(p2_local) |
| |
|
| | return [p1_global, p2_global] |
| |
|
| | def set_from_endpoints(self, obj, pts): |
| | """Sets the Length and Placement of a baseless wall from two global points.""" |
| | if len(pts) < 2: |
| | return |
| |
|
| | p1 = pts[0] |
| | p2 = pts[1] |
| |
|
| | |
| | new_length = p1.distanceToPoint(p2) |
| | new_midpoint = (p1 + p2) * 0.5 |
| | new_direction = (p2 - p1).normalize() |
| |
|
| | |
| | new_rotation = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), new_direction) |
| |
|
| | |
| | obj.Length = new_length |
| | obj.Placement.Base = new_midpoint |
| | obj.Placement.Rotation = new_rotation |
| |
|
| | def handleComponentRemoval(self, obj, subobject): |
| | """ |
| | Overrides the default component removal to implement smart debasing |
| | when the Base object is being removed. |
| | """ |
| | import Arch |
| | from PySide import QtGui |
| |
|
| | |
| | if hasattr(obj, "Base") and obj.Base == subobject: |
| | if Arch.is_debasable(obj): |
| | |
| | Arch.debaseWall(obj) |
| | else: |
| | |
| | if FreeCAD.GuiUp: |
| | |
| | from PySide import QtGui |
| |
|
| | msg_box = QtGui.QMessageBox() |
| | msg_box.setWindowTitle(translate("ArchComponent", "Unsupported Base")) |
| | msg_box.setText( |
| | translate( |
| | "ArchComponent", "The base of this wall is not a single straight line." |
| | ) |
| | ) |
| | msg_box.setInformativeText( |
| | translate( |
| | "ArchComponent", |
| | "Removing the base of this complex wall will alter its shape and reset its position.\n\n" |
| | "Do you want to proceed?", |
| | ) |
| | ) |
| | msg_box.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel) |
| | msg_box.setDefaultButton(QtGui.QMessageBox.Cancel) |
| | if msg_box.exec_() == QtGui.QMessageBox.Yes: |
| | |
| | super(_Wall, self).handleComponentRemoval(obj, subobject) |
| | else: |
| | |
| | FreeCAD.Console.PrintWarning( |
| | f"Skipping removal of complex base for wall '{obj.Label}'. " |
| | "This interactive action is not supported in headless mode.\n" |
| | ) |
| | else: |
| | |
| | |
| | super(_Wall, self).handleComponentRemoval(obj, subobject) |
| |
|
| | def get_width(self, obj, widths=True): |
| | """Returns a width and a list of widths for this wall. |
| | If widths is False, only the main width is returned""" |
| | import ArchSketchObject |
| |
|
| | |
| | if obj.Width.Value: |
| | width = obj.Width.Value |
| | else: |
| | width = 200 |
| | if not widths: |
| | return width |
| |
|
| | lwidths = [] |
| | if ( |
| | hasattr(obj, "ArchSketchData") |
| | and obj.ArchSketchData |
| | and Draft.getType(obj.Base) == "ArchSketch" |
| | ): |
| | if hasattr(obj.Base, "Proxy"): |
| | if hasattr(obj.Base.Proxy, "getWidths"): |
| | lwidths = obj.Base.Proxy.getWidths( |
| | obj.Base, propSetUuid=self.ArchSkPropSetPickedUuid |
| | ) |
| | if not lwidths: |
| | if obj.OverrideWidth: |
| | if obj.Base and obj.Base.isDerivedFrom("Sketcher::SketchObject"): |
| | lwidths = ArchSketchObject.sortSketchWidth( |
| | obj.Base, obj.OverrideWidth, obj.ArchSketchEdges |
| | ) |
| | else: |
| | lwidths = obj.OverrideWidth |
| | elif obj.Width: |
| | lwidths = [obj.Width.Value] |
| | else: |
| | return None |
| | return width, lwidths |
| |
|
| | def get_layers(self, obj): |
| | """Returns a list of layers""" |
| | layers = [] |
| | width = self.get_width(obj, widths=False) |
| | if hasattr(obj, "Material"): |
| | if obj.Material: |
| | if hasattr(obj.Material, "Materials"): |
| | thicknesses = [abs(t) for t in obj.Material.Thicknesses] |
| | restwidth = width - sum(thicknesses) |
| | varwidth = 0 |
| | if restwidth > 0: |
| | varwidth = [t for t in thicknesses if t == 0] |
| | if varwidth: |
| | varwidth = restwidth / len(varwidth) |
| | for t in obj.Material.Thicknesses: |
| | if t: |
| | layers.append(t) |
| | elif varwidth: |
| | layers.append(varwidth) |
| | return layers |
| |
|
| | def build_base_from_scratch(self, obj): |
| | """Generate the 2D profile for extruding a baseless Arch Wall. |
| | |
| | This function creates the rectangular face or faces that form the wall's cross-section, |
| | which is then used by the caller for extrusion. It handles both single- and multi-layer wall |
| | configurations. |
| | |
| | Parameters |
| | ---------- |
| | obj : FreeCAD.DocumentObject |
| | The wall object being built. Its Length, Align, and layer |
| | properties are used. |
| | |
| | Returns |
| | ------- |
| | tuple of (list of Part.Face, FreeCAD.Placement) |
| | A tuple containing two elements: |
| | 1. A list of one or more Part.Face objects representing the cross-section. |
| | 2. An identity Placement, as the geometry is in local coordinates. |
| | |
| | Notes |
| | ----- |
| | The geometry follows the convention where `Align='Left'` offsets the wall in the negative-Y |
| | direction (the geometric right). |
| | """ |
| | import Part |
| |
|
| | def _create_face_from_coords(half_length, y_min, y_max): |
| | """Creates a rectangular Part.Face centered on the X-axis, defined by Y coordinates.""" |
| | bottom_left = Vector(-half_length, y_min, 0) |
| | bottom_right = Vector(half_length, y_min, 0) |
| | top_right = Vector(half_length, y_max, 0) |
| | top_left = Vector(-half_length, y_max, 0) |
| | return Part.Face( |
| | Part.makePolygon([bottom_left, bottom_right, top_right, top_left, bottom_left]) |
| | ) |
| |
|
| | layers = self.get_layers(obj) |
| | width = self.get_width(obj, widths=False) |
| | align = obj.Align |
| |
|
| | |
| | safe_length = obj.Length.Value or 0.5 |
| |
|
| | if not layers: |
| | safe_width = width or 0.5 |
| | layers = [safe_width] |
| |
|
| | |
| | base_faces = [] |
| |
|
| | |
| | totalwidth = sum([abs(layer) for layer in layers]) |
| |
|
| | |
| | offset = 0 |
| | if align == "Center": |
| | offset = -totalwidth / 2 |
| | elif align == "Left": |
| | |
| | offset = -totalwidth |
| |
|
| | |
| | for layer in layers: |
| | |
| | if layer > 0: |
| | half_length = safe_length / 2 |
| | layer_y_min = offset |
| | layer_y_max = offset + layer |
| | face = _create_face_from_coords(half_length, layer_y_min, layer_y_max) |
| | base_faces.append(face) |
| |
|
| | |
| | offset += abs(layer) |
| |
|
| | placement = FreeCAD.Placement() |
| |
|
| | return base_faces, placement |
| |
|
| |
|
| | class _ViewProviderWall(ArchComponent.ViewProviderComponent): |
| | """The view provider for the wall object. |
| | |
| | Parameters |
| | ---------- |
| | vobj: <Gui.ViewProviderDocumentObject> |
| | The view provider to turn into a wall view provider. |
| | """ |
| |
|
| | def __init__(self, vobj): |
| | ArchComponent.ViewProviderComponent.__init__(self, vobj) |
| | vobj.ShapeColor = ArchCommands.getDefaultColor("Wall") |
| |
|
| | def getIcon(self): |
| | """Return the path to the appropriate icon. |
| | |
| | If a clone, return the cloned wall icon path. Otherwise return the |
| | Arch wall icon. |
| | |
| | Returns |
| | ------- |
| | str |
| | Path to the appropriate icon .svg file. |
| | """ |
| |
|
| | import Arch_rc |
| |
|
| | if hasattr(self, "Object"): |
| | if self.Object.CloneOf: |
| | return ":/icons/Arch_Wall_Clone.svg" |
| | elif (not self.Object.Base) and self.Object.Additions: |
| | return ":/icons/Arch_Wall_Tree_Assembly.svg" |
| | return ":/icons/Arch_Wall_Tree.svg" |
| |
|
| | def attach(self, vobj): |
| | """Add display modes' data to the coin scenegraph. |
| | |
| | Add each display mode as a coin node, whose parent is this view |
| | provider. |
| | |
| | Each display mode's node includes the data needed to display the object |
| | in that mode. This might include colors of faces, or the draw style of |
| | lines. This data is stored as additional coin nodes which are children |
| | of the display mode node. |
| | |
| | Add the textures used in the Footprint display mode. |
| | """ |
| |
|
| | 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 |
| | texcoords = coin.SoTextureCoordinatePlane() |
| | s = params.get_param_arch("patternScale") |
| | texcoords.directionS.setValue(s, 0, 0) |
| | texcoords.directionT.setValue(0, s, 0) |
| | self.fcoords = coin.SoCoordinate3() |
| | self.fset = coin.SoIndexedFaceSet() |
| | sep = coin.SoSeparator() |
| | sep.addChild(tex) |
| | sep.addChild(texcoords) |
| | sep.addChild(self.fcoords) |
| | sep.addChild(self.fset) |
| | vobj.RootNode.addChild(sep) |
| | ArchComponent.ViewProviderComponent.attach(self, vobj) |
| |
|
| | def updateData(self, obj, prop): |
| | """Method called when the host object has a property changed. |
| | |
| | If the host object's Placement, Shape, or Material has changed, and the |
| | host object has a Material assigned, give the shape the color and |
| | transparency of the Material. |
| | |
| | Parameters |
| | ---------- |
| | obj: <App::FeaturePython> |
| | The host object that has changed. |
| | prop: string |
| | The name of the property that has changed. |
| | """ |
| |
|
| | if prop in ["Placement", "Shape", "Material"]: |
| | if obj.ViewObject.DisplayMode == "Footprint": |
| | obj.ViewObject.Proxy.setDisplayMode("Footprint") |
| | if hasattr(obj, "Material"): |
| | if obj.Material and obj.Shape: |
| | if hasattr(obj.Material, "Materials"): |
| | activematerials = [ |
| | obj.Material.Materials[i] |
| | for i in range(len(obj.Material.Materials)) |
| | if obj.Material.Thicknesses[i] >= 0 |
| | ] |
| | if len(activematerials) == len(obj.Shape.Solids): |
| | cols = [] |
| | for i, mat in enumerate(activematerials): |
| | c = obj.ViewObject.ShapeColor |
| | c = (c[0], c[1], c[2], 1.0 - obj.ViewObject.Transparency / 100.0) |
| | if "DiffuseColor" in mat.Material: |
| | if "(" in mat.Material["DiffuseColor"]: |
| | c = tuple( |
| | [ |
| | float(f) |
| | for f in mat.Material["DiffuseColor"] |
| | .strip("()") |
| | .split(",") |
| | ] |
| | ) |
| | if "Transparency" in mat.Material: |
| | c = ( |
| | c[0], |
| | c[1], |
| | c[2], |
| | 1.0 - float(mat.Material["Transparency"]), |
| | ) |
| | cols.extend([c for j in range(len(obj.Shape.Solids[i].Faces))]) |
| | obj.ViewObject.DiffuseColor = cols |
| | ArchComponent.ViewProviderComponent.updateData(self, obj, prop) |
| | if len(obj.ViewObject.DiffuseColor) > 1: |
| | |
| | obj.ViewObject.DiffuseColor = obj.ViewObject.DiffuseColor |
| |
|
| | def getDisplayModes(self, vobj): |
| | """Define the display modes unique to the Arch Wall. |
| | |
| | Define mode Footprint, which only displays the footprint of the wall. |
| | Also add the display modes of the Arch Component. |
| | |
| | Returns |
| | ------- |
| | list of str |
| | List containing the names of the new display modes. |
| | """ |
| |
|
| | modes = ArchComponent.ViewProviderComponent.getDisplayModes(self, vobj) + ["Footprint"] |
| | return modes |
| |
|
| | def setDisplayMode(self, mode): |
| | """Method called when the display mode changes. |
| | |
| | Called when the display mode changes, this method can be used to set |
| | data that wasn't available when .attach() was called. |
| | |
| | When Footprint is set as display mode, find the faces that make up the |
| | footprint of the wall, and give them a lined texture. Then display |
| | the wall as a wireframe. |
| | |
| | Then pass the displaymode onto Arch Component's .setDisplayMode(). |
| | |
| | Parameters |
| | ---------- |
| | mode: str |
| | The name of the display mode the view provider has switched to. |
| | |
| | Returns |
| | ------- |
| | str: |
| | The name of the display mode the view provider has switched to. |
| | """ |
| |
|
| | self.fset.coordIndex.deleteValues(0) |
| | self.fcoords.point.deleteValues(0) |
| | if mode == "Footprint": |
| | if hasattr(self, "Object"): |
| | faces = self.Object.Proxy.getFootprint(self.Object) |
| | if faces: |
| | verts = [] |
| | fdata = [] |
| | idx = 0 |
| | for face in faces: |
| | tri = face.tessellate(1) |
| | for v in tri[0]: |
| | verts.append([v.x, v.y, v.z]) |
| | for f in tri[1]: |
| | fdata.extend([f[0] + idx, f[1] + idx, f[2] + idx, -1]) |
| | idx += len(tri[0]) |
| | self.fcoords.point.setValues(verts) |
| | self.fset.coordIndex.setValues(0, len(fdata), fdata) |
| | return "Wireframe" |
| | return ArchComponent.ViewProviderComponent.setDisplayMode(self, mode) |
| |
|
| | def setupContextMenu(self, vobj, menu): |
| |
|
| | if FreeCADGui.activeWorkbench().name() != "BIMWorkbench": |
| | return |
| |
|
| | super().contextMenuAddEdit(menu) |
| |
|
| | actionFlipDirection = QtGui.QAction( |
| | QtGui.QIcon(":/icons/Arch_Wall_Tree.svg"), translate("Arch", "Flip Direction"), menu |
| | ) |
| | QtCore.QObject.connect( |
| | actionFlipDirection, QtCore.SIGNAL("triggered()"), self.flipDirection |
| | ) |
| | menu.addAction(actionFlipDirection) |
| |
|
| | super().contextMenuAddToggleSubcomponents(menu) |
| |
|
| | def flipDirection(self): |
| |
|
| | if hasattr(self, "Object") and self.Object: |
| | obj = self.Object |
| | if obj.Align == "Left": |
| | obj.Align = "Right" |
| | FreeCAD.ActiveDocument.recompute() |
| | elif obj.Align == "Right": |
| | obj.Align = "Left" |
| | FreeCAD.ActiveDocument.recompute() |
| |
|