| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | __title__ = "FreeCAD Arch Commands" |
| | __author__ = "Yorik van Havre" |
| | __url__ = "https://www.freecad.org" |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import FreeCAD |
| | import ArchComponent |
| | import Draft |
| | import DraftVecUtils |
| |
|
| | from FreeCAD import Vector |
| | from draftutils import params |
| | from draftutils.groups import is_group |
| |
|
| | if FreeCAD.GuiUp: |
| | from PySide import QtGui, QtCore |
| | import FreeCADGui |
| | from draftutils.translate import translate |
| | else: |
| | |
| | def translate(ctxt, txt): |
| | return txt |
| |
|
| | |
| |
|
| |
|
| | |
| |
|
| |
|
| | def getStringList(objects): |
| | """getStringList(objects): returns a string defining a list |
| | of objects""" |
| | result = "[" |
| | for o in objects: |
| | if len(result) > 1: |
| | result += "," |
| | result += "FreeCAD.ActiveDocument." + o.Name |
| | result += "]" |
| | return result |
| |
|
| |
|
| | def getDefaultColor(objectType): |
| | """getDefaultColor(string): returns a color value for the given object |
| | type (Wall, Structure, Window, WindowGlass)""" |
| | alpha = 1.0 |
| | if objectType == "Wall": |
| | c = params.get_param_arch("WallColor") |
| | elif objectType == "Structure": |
| | c = params.get_param_arch("StructureColor") |
| | elif objectType == "WindowGlass": |
| | c = params.get_param_arch("WindowGlassColor") |
| | alpha = 1.0 - params.get_param_arch("WindowTransparency") / 100.0 |
| | elif objectType == "Rebar": |
| | c = params.get_param_arch("RebarColor") |
| | elif objectType == "Panel": |
| | c = params.get_param_arch("PanelColor") |
| | elif objectType == "Space": |
| | c = params.get_param_arch("defaultSpaceColor") |
| | elif objectType == "Helpers": |
| | c = params.get_param_arch("ColorHelpers") |
| | elif objectType == "Construction": |
| | c = params.get_param("constructioncolor") |
| | alpha = 0.2 |
| | else: |
| | c = params.get_param_view("DefaultShapeColor") |
| | r, g, b, _ = Draft.get_rgba_tuple(c) |
| | return (r, g, b, alpha) |
| |
|
| |
|
| | def _usedForAttachment(host, obj): |
| | if not getattr(obj, "AttachmentSupport", []): |
| | return False |
| | for sub in obj.AttachmentSupport: |
| | if sub[0] == host: |
| | return True |
| | return False |
| |
|
| |
|
| | def addComponents(objectsList, host): |
| | """addComponents(objectsList,hostObject): adds the given object or the objects |
| | from the given list as components to the given host Object. Use this for |
| | example to add windows to a wall, or to add walls to a cell or floor.""" |
| | if not isinstance(objectsList, list): |
| | objectsList = [objectsList] |
| | hostType = Draft.getType(host) |
| | if hostType in ["Floor", "Building", "Site", "Project", "BuildingPart"]: |
| | for o in objectsList: |
| | host.addObject(o) |
| | elif hostType in [ |
| | "Wall", |
| | "CurtainWall", |
| | "Structure", |
| | "Precast", |
| | "Window", |
| | "Roof", |
| | "Stairs", |
| | "StructuralSystem", |
| | "Panel", |
| | "Component", |
| | "Pipe", |
| | ]: |
| | import DraftGeomUtils |
| |
|
| | outList = host.OutListRecursive |
| | a = host.Additions |
| | x = getattr(host, "Axes", []) |
| | for o in objectsList: |
| | if hasattr(o, "Shape"): |
| | if Draft.getType(o) == "Window": |
| | if hasattr(o, "Hosts"): |
| | if not host in o.Hosts: |
| | g = o.Hosts |
| | g.append(host) |
| | o.Hosts = g |
| | elif o in outList: |
| | FreeCAD.Console.PrintWarning( |
| | translate( |
| | "Arch", "Cannot add {0} as it is already referenced by {1}." |
| | ).format(o.Label, host.Label) |
| | + "\n" |
| | ) |
| | elif Draft.getType(o) == "Axis": |
| | if not o in x: |
| | x.append(o) |
| | elif DraftGeomUtils.isValidPath(o.Shape) and (hostType in ["Structure", "Precast"]): |
| | if _usedForAttachment(host, o): |
| | o.AttachmentSupport = None |
| | o.MapMode = "Deactivated" |
| | host.Tool = o |
| | elif not o in a: |
| | if hasattr(o, "Shape"): |
| | a.append(o) |
| | host.Additions = a |
| | if hasattr(host, "Axes"): |
| | host.Axes = x |
| | elif hostType in ["SectionPlane"]: |
| | a = host.Objects |
| | for o in objectsList: |
| | if not o in a: |
| | a.append(o) |
| | host.Objects = a |
| | elif host.isDerivedFrom("App::DocumentObjectGroup"): |
| | for o in objectsList: |
| | host.addObject(o) |
| |
|
| |
|
| | def removeComponents(objectsList, host=None): |
| | """removeComponents(objectsList,[hostObject]): removes the given component or |
| | the components from the given list from their parents. If a host object is |
| | specified, this function will try adding the components as holes to the host |
| | object instead.""" |
| | if not isinstance(objectsList, list): |
| | objectsList = [objectsList] |
| | if host: |
| | if Draft.getType(host) in [ |
| | "Wall", |
| | "CurtainWall", |
| | "Structure", |
| | "Precast", |
| | "Window", |
| | "Roof", |
| | "Stairs", |
| | "StructuralSystem", |
| | "Panel", |
| | "Component", |
| | "Pipe", |
| | ]: |
| | if getattr(host, "Tool", None) in objectsList: |
| | host.Tool = None |
| | if hasattr(host, "Axes"): |
| | a = host.Axes |
| | for o in objectsList[:]: |
| | if o in a: |
| | a.remove(o) |
| | objectsList.remove(o) |
| | host.Axes = a |
| | s = host.Subtractions |
| | for o in objectsList: |
| | if Draft.getType(o) == "Window": |
| | if hasattr(o, "Hosts"): |
| | if not host in o.Hosts: |
| | g = o.Hosts |
| | g.append(host) |
| | o.Hosts = g |
| | elif not o in s: |
| | s.append(o) |
| | if FreeCAD.GuiUp: |
| | if Draft.getType(o) != "Roof": |
| | setAsSubcomponent(o) |
| | |
| | if hasattr(o, "Base") and o.Base: |
| | objList = [o, o.Base] |
| | else: |
| | objList = [o] |
| | for i in objList: |
| | if _usedForAttachment(host, i): |
| | FreeCAD.Console.PrintMessage( |
| | translate( |
| | "Arch", |
| | "{0} is mapped to {1}, removing the former's " |
| | + "Attachment Support to avoid cyclic dependency.", |
| | ).format(o.Label, host.Label) |
| | + "\n" |
| | ) |
| | i.AttachmentSupport = None |
| | i.MapMode = "Deactivated" |
| | host.Subtractions = s |
| | elif Draft.getType(host) in ["SectionPlane"]: |
| | a = host.Objects |
| | for o in objectsList: |
| | if o in a: |
| | a.remove(o) |
| | host.Objects = a |
| | else: |
| | for o in objectsList: |
| | if o.InList: |
| | h = o.InList[0] |
| |
|
| | is_base_removal = hasattr(h, "Base") and h.Base == o |
| | has_handler = hasattr(h, "Proxy") and hasattr(h.Proxy, "handleComponentRemoval") |
| |
|
| | if is_base_removal and has_handler: |
| | |
| | h.Proxy.handleComponentRemoval(h, o) |
| | continue |
| |
|
| | tp = Draft.getType(h) |
| | if tp in ["Floor", "Building", "Site", "BuildingPart"]: |
| | c = h.Group |
| | if o in c: |
| | c.remove(o) |
| | h.Group = c |
| | o.ViewObject.show() |
| | elif tp in ["Wall", "Structure", "Precast"]: |
| | a = h.Additions |
| | s = h.Subtractions |
| | if o in a: |
| | a.remove(o) |
| | h.Additions = a |
| | o.ViewObject.show() |
| | elif o in s: |
| | s.remove(o) |
| | h.Subtractions = s |
| | o.ViewObject.show() |
| | elif o == h.Base: |
| | h.Base = None |
| | if FreeCAD.GuiUp: |
| | o.ViewObject.show() |
| | elif tp in ["SectionPlane"]: |
| | a = h.Objects |
| | if o in a: |
| | a.remove(o) |
| | h.Objects = a |
| | if hasattr(o, "Hosts") and Draft.getType(o) == "Window": |
| | o.Hosts = [] |
| |
|
| |
|
| | def makeComponent(baseobj=None, name=None, delete=False): |
| | """makeComponent([baseobj],[name],[delete]): creates an undefined, non-parametric BIM |
| | component from the given base object""" |
| | if not FreeCAD.ActiveDocument: |
| | FreeCAD.Console.PrintError("No active document. Aborting\n") |
| | return |
| | obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Component") |
| | obj.Label = name if name else translate("Arch", "Component") |
| | ArchComponent.Component(obj) |
| | if FreeCAD.GuiUp: |
| | ArchComponent.ViewProviderComponent(obj.ViewObject) |
| | if baseobj: |
| | import Part |
| |
|
| | if hasattr(baseobj, "Shape"): |
| | obj.Shape = baseobj.Shape |
| | obj.Placement = baseobj.Placement |
| | if delete: |
| | FreeCAD.ActiveDocument.removeObject(baseobj.Name) |
| | else: |
| | obj.Base = baseobj |
| | if FreeCAD.GuiUp: |
| | baseobj.ViewObject.hide() |
| | elif isinstance(baseobj, Part.Shape): |
| | obj.Shape = baseobj |
| | Draft.select(obj) |
| | return obj |
| |
|
| |
|
| | def cloneComponent(obj): |
| | """cloneComponent(obj): Creates a clone of an object as an undefined component""" |
| | c = makeComponent() |
| | c.CloneOf = obj |
| | c.Placement = obj.Placement |
| | c.Label = obj.Label |
| | if hasattr(obj, "Material"): |
| | if obj.Material: |
| | c.Material = obj.Material |
| | if hasattr(obj, "IfcAttributes"): |
| | if obj.IfcAttributes: |
| | c.IfcAttributes = obj.IfcAttributes |
| | Draft.select(c) |
| | return c |
| |
|
| |
|
| | def setAsSubcomponent(obj): |
| | """Sets the given object properly to become a subcomponent (addition, subtraction) |
| | of an Arch component""" |
| | Draft.ungroup(obj) |
| | if params.get_param_arch("applyConstructionStyle"): |
| | if FreeCAD.GuiUp: |
| | color = getDefaultColor("Construction") |
| | if hasattr(obj.ViewObject, "LineColor"): |
| | obj.ViewObject.LineColor = color |
| | if hasattr(obj.ViewObject, "PointColor"): |
| | obj.ViewObject.PointColor = color |
| | if hasattr(obj.ViewObject, "ShapeColor"): |
| | obj.ViewObject.ShapeColor = color |
| | if hasattr(obj.ViewObject, "Transparency"): |
| | obj.ViewObject.Transparency = int(color[3] * 100) |
| | obj.ViewObject.hide() |
| |
|
| |
|
| | def copyProperties(obj1, obj2): |
| | """copyProperties(obj1,obj2): Copies properties values from obj1 to obj2, |
| | when that property exists in both objects""" |
| | for prop in obj1.PropertiesList: |
| | if prop in obj2.PropertiesList: |
| | if not prop in ["Proxy", "Shape"]: |
| | setattr(obj2, prop, getattr(obj1, prop)) |
| | if obj1.ViewObject and obj2.ViewObject: |
| | for prop in obj1.ViewObject.PropertiesList: |
| | if prop in obj2.ViewObject.PropertiesList: |
| | if not prop in ["Proxy", "Shape"]: |
| | setattr(obj2.ViewObject, prop, getattr(obj1.ViewObject, prop)) |
| |
|
| |
|
| | def splitMesh(obj, mark=True): |
| | """splitMesh(object,[mark]): splits the given mesh object into separated components. |
| | If mark is False, nothing else is done. If True (default), non-manifold components |
| | will be painted in red.""" |
| | if not obj.isDerivedFrom("Mesh::Feature"): |
| | return [] |
| | basemesh = obj.Mesh |
| | comps = basemesh.getSeparateComponents() |
| | nlist = [] |
| | if comps: |
| | basename = obj.Name |
| | FreeCAD.ActiveDocument.removeObject(basename) |
| | for c in comps: |
| | newobj = FreeCAD.ActiveDocument.addObject("Mesh::Feature", basename) |
| | newobj.Mesh = c |
| | if mark and (not (c.isSolid()) or c.hasNonManifolds()): |
| | newobj.ViewObject.ShapeColor = (1.0, 0.0, 0.0, 1.0) |
| | nlist.append(newobj) |
| | return nlist |
| | return [obj] |
| |
|
| |
|
| | def makeFace(wires, method=2, cleanup=False): |
| | """makeFace(wires): makes a face from a list of wires, finding which ones are holes""" |
| | |
| | import Part |
| | import DraftGeomUtils |
| |
|
| | if not isinstance(wires, list): |
| | if len(wires.Vertexes) < 3: |
| | raise |
| | return Part.Face(wires) |
| | elif len(wires) == 1: |
| | |
| | if len(wires[0].Vertexes) < 3: |
| | raise |
| | return Part.Face(wires[0]) |
| |
|
| | wires = wires[:] |
| |
|
| | |
| | ext = None |
| | max_length = 0 |
| | |
| | if cleanup: |
| | for i in range(len(wires)): |
| | wires[i] = DraftGeomUtils.removeInterVertices(wires[i]) |
| | |
| | for w in wires: |
| | |
| | |
| | if w.BoundBox.DiagonalLength > max_length: |
| | max_length = w.BoundBox.DiagonalLength |
| | ext = w |
| | |
| | wires.remove(ext) |
| |
|
| | if method == 1: |
| | |
| | |
| | |
| | for w in wires: |
| | |
| | w.reverse() |
| | |
| | wires.insert(0, ext) |
| | |
| | if wires: |
| | return Part.Face(wires) |
| | else: |
| | |
| | mf = Part.Face(ext) |
| | |
| | for w in wires: |
| | f = Part.Face(w) |
| | |
| | mf = mf.cut(f) |
| | |
| | return mf.Faces[0] |
| |
|
| |
|
| | def closeHole(shape): |
| | """closeHole(shape): closes a hole in an open shape""" |
| | import DraftGeomUtils |
| | import Part |
| |
|
| | |
| | lut = {} |
| | for face in shape.Faces: |
| | for edge in face.Edges: |
| | hc = edge.hashCode() |
| | if hc in lut: |
| | lut[hc] = lut[hc] + 1 |
| | else: |
| | lut[hc] = 1 |
| | |
| | bound = [] |
| | for e in shape.Edges: |
| | if lut[e.hashCode()] == 1: |
| | bound.append(e) |
| | bound = Part.__sortEdges__(bound) |
| | try: |
| | nface = Part.Face(Part.Wire(bound)) |
| | shell = Part.makeShell(shape.Faces + [nface]) |
| | solid = Part.Solid(shell) |
| | except Part.OCCError: |
| | raise |
| | else: |
| | return solid |
| |
|
| |
|
| | def getCutVolume(cutplane, shapes, clip=False, depth=None): |
| | """getCutVolume(cutplane,shapes,[clip,depth]): returns a cut face and a cut volume |
| | from the given shapes and the given cutting plane. If clip is True, the cutvolume will |
| | also cut off everything outside the cutplane projection. If depth is non-zero, geometry |
| | further than this distance will be clipped off""" |
| | if not shapes: |
| | return None, None, None |
| | if not cutplane.Faces: |
| | return None, None, None |
| | import Part |
| |
|
| | if not isinstance(shapes, list): |
| | shapes = [shapes] |
| | |
| | bb = shapes[0].optimalBoundingBox() |
| | for sh in shapes[1:]: |
| | bb.add(sh.optimalBoundingBox()) |
| | bb.enlarge(1) |
| | |
| | um = vm = wm = 0 |
| | try: |
| | if hasattr(cutplane, "Shape"): |
| | p = cutplane.Shape.copy().Faces[0] |
| | else: |
| | p = cutplane.copy().Faces[0] |
| | except Part.OCCError: |
| | FreeCAD.Console.PrintMessage(translate("Arch", "Invalid cut plane") + "\n") |
| | return None, None, None |
| | ce = p.CenterOfMass |
| | ax = p.normalAt(0, 0) |
| | prm_range = p.ParameterRange |
| | u = p.valueAt(prm_range[0], 0).sub(p.valueAt(prm_range[1], 0)).normalize() |
| | v = u.cross(ax) |
| | if not bb.isCutPlane(ce, ax): |
| | |
| | return None, None, None |
| | else: |
| | corners = [ |
| | FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), |
| | FreeCAD.Vector(bb.XMin, bb.YMax, bb.ZMin), |
| | FreeCAD.Vector(bb.XMax, bb.YMin, bb.ZMin), |
| | FreeCAD.Vector(bb.XMax, bb.YMax, bb.ZMin), |
| | FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMax), |
| | FreeCAD.Vector(bb.XMin, bb.YMax, bb.ZMax), |
| | FreeCAD.Vector(bb.XMax, bb.YMin, bb.ZMax), |
| | FreeCAD.Vector(bb.XMax, bb.YMax, bb.ZMax), |
| | ] |
| | for c in corners: |
| | dv = c.sub(ce) |
| | um1 = DraftVecUtils.project(dv, u).Length |
| | um = max(um, um1) |
| | vm1 = DraftVecUtils.project(dv, v).Length |
| | vm = max(vm, vm1) |
| | wm1 = DraftVecUtils.project(dv, ax).Length |
| | wm = max(wm, wm1) |
| | vu = DraftVecUtils.scaleTo(u, um) |
| | vui = vu.negative() |
| | vv = DraftVecUtils.scaleTo(v, vm) |
| | vvi = vv.negative() |
| | p1 = ce.add(vu.add(vvi)) |
| | p2 = ce.add(vu.add(vv)) |
| | p3 = ce.add(vui.add(vv)) |
| | p4 = ce.add(vui.add(vvi)) |
| | cutface = Part.makePolygon([p1, p2, p3, p4, p1]) |
| | cutface = Part.Face(cutface) |
| | cutnormal = DraftVecUtils.scaleTo(ax, wm) |
| | cutvolume = cutface.extrude(cutnormal) |
| | cutnormal = cutnormal.negative() |
| | invcutvolume = cutface.extrude(cutnormal) |
| | if clip: |
| | extrudedplane = p.extrude(cutnormal) |
| | bordervolume = invcutvolume.cut(extrudedplane) |
| | cutvolume = cutvolume.fuse(bordervolume) |
| | cutvolume = cutvolume.removeSplitter() |
| | invcutvolume = extrudedplane |
| | cutface = p |
| | if depth: |
| | depthnormal = DraftVecUtils.scaleTo(cutnormal, depth) |
| | depthvolume = cutface.extrude(depthnormal) |
| | depthclipvolume = invcutvolume.cut(depthvolume) |
| | cutvolume = cutvolume.fuse(depthclipvolume) |
| | cutvolume = cutvolume.removeSplitter() |
| | return cutface, cutvolume, invcutvolume |
| |
|
| |
|
| | def getShapeFromMesh(mesh, fast=True, tolerance=0.001, flat=False, cut=True): |
| | import Part |
| | import MeshPart |
| | import DraftGeomUtils |
| |
|
| | if mesh.isSolid() and (mesh.countComponents() == 1) and fast: |
| | |
| | faces = [] |
| | for f in mesh.Facets: |
| | p = f.Points + [f.Points[0]] |
| | pts = [] |
| | for pp in p: |
| | pts.append(FreeCAD.Vector(pp[0], pp[1], pp[2])) |
| | try: |
| | f = Part.Face(Part.makePolygon(pts)) |
| | except Exception: |
| | print("getShapeFromMesh: error building face from polygon") |
| | |
| | else: |
| | faces.append(f) |
| | shell = Part.makeShell(faces) |
| | try: |
| | solid = Part.Solid(shell) |
| | except Part.OCCError: |
| | print("getShapeFromMesh: error creating solid") |
| | else: |
| | try: |
| | solid = solid.removeSplitter() |
| | except Part.OCCError: |
| | print("getShapeFromMesh: error removing splitter") |
| | |
| | return solid |
| |
|
| | |
| | |
| | faces = [] |
| | segments = mesh.getPlanarSegments(tolerance) |
| | |
| | for i in segments: |
| | if len(i) > 0: |
| | wires = MeshPart.wireFromSegment(mesh, i) |
| | if wires: |
| | if flat: |
| | nwires = [] |
| | for w in wires: |
| | nwires.append(DraftGeomUtils.flattenWire(w)) |
| | wires = nwires |
| | try: |
| | faces.append(makeFace(wires, method=int(cut) + 1)) |
| | except Exception: |
| | return None |
| | try: |
| | se = Part.makeShell(faces) |
| | se = se.removeSplitter() |
| | if flat: |
| | return se |
| | except Part.OCCError: |
| | print("getShapeFromMesh: error removing splitter") |
| | try: |
| | cp = Part.makeCompound(faces) |
| | except Part.OCCError: |
| | print("getShapeFromMesh: error creating compound") |
| | return None |
| | else: |
| | return cp |
| | else: |
| | try: |
| | solid = Part.Solid(se) |
| | except Part.OCCError: |
| | print("getShapeFromMesh: error creating solid") |
| | return se |
| | else: |
| | if solid.isClosed(): |
| | return solid |
| | else: |
| | return se |
| |
|
| |
|
| | def projectToVector(shape, vector): |
| | """projectToVector(shape,vector): projects the given shape on the given |
| | vector""" |
| | projpoints = [] |
| | minl = 10000000000 |
| | maxl = -10000000000 |
| | for v in shape.Vertexes: |
| | p = DraftVecUtils.project(v.Point, vector) |
| | projpoints.append(p) |
| | l = p.Length |
| | if p.getAngle(vector) > 1: |
| | l = -l |
| | if l > maxl: |
| | maxl = l |
| | if l < minl: |
| | minl = l |
| | return DraftVecUtils.scaleTo(vector, maxl - minl) |
| |
|
| |
|
| | def meshToShape(obj, mark=True, fast=True, tol=0.001, flat=False, cut=True): |
| | """meshToShape(object,[mark,fast,tol,flat,cut]): turns a mesh into a shape, joining coplanar facets. If |
| | mark is True (default), non-solid objects will be marked in red. Fast uses a faster algorithm by |
| | building a shell from the facets then removing splitter, tol is the tolerance used when converting |
| | mesh segments to wires, flat will force the wires to be perfectly planar, to be sure they can be |
| | turned into faces, but this might leave gaps in the final shell. If cut is true, holes in faces are |
| | made by subtraction (default)""" |
| |
|
| | name = obj.Name |
| | if "Mesh" in obj.PropertiesList: |
| | mesh = obj.Mesh |
| | |
| | solid = getShapeFromMesh(mesh, fast, tol, flat, cut) |
| | if solid: |
| | if solid.isClosed() and solid.isValid(): |
| | FreeCAD.ActiveDocument.removeObject(name) |
| | newobj = FreeCAD.ActiveDocument.addObject("Part::Feature", name) |
| | newobj.Shape = solid |
| | |
| | if (not solid.isClosed()) or (not solid.isValid()): |
| | if mark: |
| | newobj.ViewObject.ShapeColor = (1.0, 0.0, 0.0, 1.0) |
| | return newobj |
| | return None |
| |
|
| |
|
| | def removeCurves(shape, dae=False, tolerance=5): |
| | """removeCurves(shape,dae,tolerance=5): replaces curved faces in a shape |
| | with faceted segments. If dae is True, DAE triangulation options are used""" |
| | import Mesh |
| |
|
| | if dae: |
| | from importers import importDAE |
| |
|
| | t = importDAE.triangulate(shape.cleaned()) |
| | else: |
| | t = shape.cleaned().tessellate(tolerance) |
| | m = Mesh.Mesh(t) |
| | return getShapeFromMesh(m) |
| |
|
| |
|
| | def removeShape(objs, mark=True): |
| | """removeShape(objs,mark=True): takes an arch object (wall or structure) built on a cubic shape, and removes |
| | the inner shape, keeping its length, width and height as parameters. If mark is True, objects that cannot |
| | be processed by this function will become red.""" |
| | import DraftGeomUtils |
| |
|
| | if not isinstance(objs, list): |
| | objs = [objs] |
| | for obj in objs: |
| | if DraftGeomUtils.isCubic(obj.Shape): |
| | dims = DraftGeomUtils.getCubicDimensions(obj.Shape) |
| | if dims: |
| | name = obj.Name |
| | tp = Draft.getType(obj) |
| | print(tp) |
| | if tp == "Structure": |
| | FreeCAD.ActiveDocument.removeObject(name) |
| | import ArchStructure |
| |
|
| | str = ArchStructure.makeStructure( |
| | length=dims[1], width=dims[2], height=dims[3], name=name |
| | ) |
| | str.Placement = dims[0] |
| | elif tp == "Wall": |
| | FreeCAD.ActiveDocument.removeObject(name) |
| | import Arch |
| |
|
| | length = dims[1] |
| | width = dims[2] |
| | v1 = Vector(length / 2, 0, 0) |
| | v2 = v1.negative() |
| | v1 = dims[0].multVec(v1) |
| | v2 = dims[0].multVec(v2) |
| | line = Draft.makeLine(v1, v2) |
| | Arch.makeWall(line, width=width, height=dims[3], name=name) |
| | else: |
| | if mark: |
| | obj.ViewObject.ShapeColor = (1.0, 0.0, 0.0, 1.0) |
| |
|
| |
|
| | def mergeCells(objectslist): |
| | """mergeCells(objectslist): merges the objects in the given list |
| | into one. All objects must be of the same type and based on the Cell |
| | object (cells, floors, buildings, or sites).""" |
| | if not objectslist: |
| | return None |
| | if not isinstance(objectslist, list): |
| | return None |
| | if len(objectslist) < 2: |
| | return None |
| | typ = Draft.getType(objectslist[0]) |
| | if not (typ in ["Cell", "Floor", "Building", "Site"]): |
| | return None |
| | for o in objectslist: |
| | if Draft.getType(o) != typ: |
| | return None |
| | base = objectslist.pop(0) |
| | for o in objectslist: |
| | l = base.Components |
| | for c in o.Components: |
| | if not c in l: |
| | l.append(c) |
| | base.Components = l |
| | FreeCAD.ActiveDocument.removeObject(o.Name) |
| | FreeCAD.ActiveDocument.recompute() |
| | return base |
| |
|
| |
|
| | def download(url, force=False): |
| | """download(url,force=False): downloads a file from the given URL and saves it in the |
| | macro path. Returns the path to the saved file. If force is True, the file will be |
| | downloaded again evn if it already exists.""" |
| | from urllib.request import urlopen |
| | import os |
| |
|
| | name = url.split("/")[-1] |
| | macropath = FreeCAD.getUserMacroDir(True) |
| | filepath = os.path.join(macropath, name) |
| | if os.path.exists(filepath) and not (force): |
| | return filepath |
| | try: |
| | FreeCAD.Console.PrintMessage("Downloading " + url + " …\n") |
| | response = urlopen(url) |
| | s = response.read() |
| | f = open(filepath, "wb") |
| | f.write(s) |
| | f.close() |
| | except Exception: |
| | return None |
| | else: |
| | return filepath |
| |
|
| |
|
| | def check(objectslist, includehidden=False): |
| | """check(objectslist,includehidden=False): checks if the given objects contain only solids""" |
| | objs = Draft.get_group_contents(objectslist) |
| | if not includehidden: |
| | objs = Draft.removeHidden(objs) |
| | bad = [] |
| | for o in objs: |
| | if not hasattr(o, "Shape"): |
| | bad.append([o, "is not a Part-based object"]) |
| | else: |
| | s = o.Shape |
| | if (not s.isClosed()) and (not (Draft.getType(o) == "Axis")): |
| | bad.append([o, translate("Arch", "is not closed")]) |
| | elif not s.isValid(): |
| | bad.append([o, translate("Arch", "is not valid")]) |
| | elif (not s.Solids) and (not (Draft.getType(o) == "Axis")): |
| | bad.append([o, translate("Arch", "does not contain any solid")]) |
| | else: |
| | f = 0 |
| | for sol in s.Solids: |
| | f += len(sol.Faces) |
| | if not sol.isClosed(): |
| | bad.append([o, translate("Arch", "contains a non-closed solid")]) |
| | if len(s.Faces) != f: |
| | bad.append( |
| | [o, translate("Arch", "contains faces that are not part of any solid")] |
| | ) |
| | return bad |
| |
|
| |
|
| | def getHost(obj, strict=True): |
| | """getHost(obj,[strict]): returns the host of the current object. If strict is true (default), |
| | the host can only be an object of a higher level than the given one, or in other words, if a wall |
| | is contained in another wall which is part of a floor, the floor is returned instead of the parent wall |
| | """ |
| | import Draft |
| |
|
| | t = Draft.getType(obj) |
| | for par in obj.InList: |
| | if par.isDerivedFrom("Part::Feature") or par.isDerivedFrom("App::DocumentObjectGroup"): |
| | if strict: |
| | if Draft.getType(par) != t: |
| | return par |
| | else: |
| | return getHost(par, strict) |
| | else: |
| | return par |
| | return None |
| |
|
| |
|
| | def pruneIncluded(objectslist, strict=False, silent=False): |
| | """pruneIncluded(objectslist,[strict]): removes from a list of Arch objects, those that are subcomponents of |
| | another shape-based object, leaving only the top-level shapes. If strict is True, the object |
| | is removed only if the parent is also part of the selection.""" |
| | import Draft |
| |
|
| | newlist = [] |
| | for obj in objectslist: |
| | toplevel = True |
| | if obj.isDerivedFrom("Part::Feature"): |
| | if Draft.getType(obj) not in ["Window", "Clone", "Pipe", "Rebar", "Roof"]: |
| | for parent in obj.InList: |
| | if not parent.isDerivedFrom("Part::Feature"): |
| | pass |
| | elif parent.isDerivedFrom("Part::Part2DObject"): |
| | |
| | pass |
| | elif Draft.getType(parent) in [ |
| | "BezCurve", |
| | "BSpline", |
| | "Clone", |
| | "Facebinder", |
| | "Wire", |
| | "Project", |
| | "Roof", |
| | "Site", |
| | "Space", |
| | "Window", |
| | ]: |
| | pass |
| | elif parent.isDerivedFrom("PartDesign::FeatureBase"): |
| | |
| | pass |
| | elif parent.isDerivedFrom("PartDesign::Body") and obj == parent.BaseFeature: |
| | |
| | pass |
| | elif parent.isDerivedFrom("PartDesign::SubShapeBinder") or ( |
| | hasattr(parent, "TypeId") and parent.TypeId == "PartDesign::ShapeBinder" |
| | ): |
| | |
| | pass |
| | elif hasattr(parent, "Host") and parent.Host == obj: |
| | pass |
| | elif hasattr(parent, "Hosts") and obj in parent.Hosts: |
| | pass |
| | elif hasattr(parent, "TypeId") and parent.TypeId == "Part::Mirroring": |
| | pass |
| | elif hasattr(parent, "CloneOf"): |
| | if parent.CloneOf: |
| | if parent.CloneOf.Name != obj.Name: |
| | toplevel = False |
| | else: |
| | toplevel = False |
| | else: |
| | toplevel = False |
| |
|
| | if toplevel == False and strict: |
| | if parent not in objectslist and parent not in newlist: |
| | toplevel = True |
| | if toplevel: |
| | newlist.append(obj) |
| | elif not silent: |
| | FreeCAD.Console.PrintWarning("pruning " + obj.Label + "\n") |
| | return newlist |
| |
|
| |
|
| | def getAllChildren(objectlist): |
| | "getAllChildren(objectlist): returns all the children of all the object sin the list" |
| | obs = [] |
| | for o in objectlist: |
| | if not o in obs: |
| | obs.append(o) |
| | if o.OutList: |
| | l = getAllChildren(o.OutList) |
| | for c in l: |
| | if not c in obs: |
| | obs.append(c) |
| | return obs |
| |
|
| |
|
| | def get_architectural_contents( |
| | initial_objects, |
| | recursive=True, |
| | discover_hosted_elements=True, |
| | include_components_from_additions=False, |
| | include_initial_objects_in_result=True, |
| | ): |
| | """ |
| | Retrieves a flat list of unique architectural objects that are considered "contents" of or are |
| | related to the given initial_objects. |
| | |
| | This includes: |
| | - Children from .Group properties (hierarchical traversal if recursive=True). |
| | - Architecturally hosted elements (e.g., windows in walls, rebars in structures) |
| | if discover_hosted_elements=True. |
| | - Optionally, components from .Additions properties. |
| | |
| | The traversal uses a queue and ensures objects are processed only once. |
| | |
| | Parameters: |
| | ----------- |
| | initial_objects : App::DocumentObject or list of App::DocumentObject |
| | The starting object(s) from which to discover contents. |
| | recursive : bool, optional |
| | If True (default), recursively find contents of discovered group-like |
| | objects (those with a .Group property and identified by draftutils.groups.is_group()). |
| | discover_hosted_elements : bool, optional |
| | If True (default), try to find elements like windows, doors, rebars |
| | that are architecturally hosted by other elements encountered during traversal. |
| | This relies on Draft.getType() and common Arch properties like .Hosts or .Host. |
| | include_components_from_additions : bool, optional |
| | If False (default), objects found in .Additions lists are not added. |
| | Set to True if these components should be part of the discovered contents. |
| | include_initial_objects_in_result : bool, optional |
| | If True (default), the objects in initial_objects themselves will be |
| | included in the output list (if not already discovered as a child of another). |
| | If False, only their "descendant" contents are returned. |
| | |
| | Returns: |
| | -------- |
| | list of App::DocumentObject |
| | A flat list of unique architectural document objects. |
| | """ |
| |
|
| | final_contents_list = [] |
| | queue = [] |
| |
|
| | if not isinstance(initial_objects, list): |
| | initial_objects_list = [initial_objects] |
| | else: |
| | initial_objects_list = list(initial_objects) |
| |
|
| | queue.extend(initial_objects_list) |
| |
|
| | |
| | |
| | processed_or_queued_names = set() |
| | for ( |
| | item |
| | ) in initial_objects_list: |
| | processed_or_queued_names.add(item.Name) |
| |
|
| | idx = 0 |
| | while idx < len(queue): |
| | obj = queue[idx] |
| | idx += 1 |
| |
|
| | |
| | |
| | |
| | if obj not in final_contents_list: |
| | is_initial = obj in initial_objects_list |
| | if (is_initial and include_initial_objects_in_result) or not is_initial: |
| | final_contents_list.append(obj) |
| |
|
| | children_to_add_to_queue_next = [] |
| |
|
| | |
| | if recursive and is_group(obj) and hasattr(obj, "Group") and obj.Group: |
| | for child in obj.Group: |
| | if child.Name not in processed_or_queued_names: |
| | children_to_add_to_queue_next.append(child) |
| | processed_or_queued_names.add(child.Name) |
| |
|
| | |
| | if discover_hosted_elements: |
| | host_types = ["Wall", "Structure", "CurtainWall", "Precast", "Panel", "Roof"] |
| | if Draft.getType(obj) in host_types: |
| | |
| | for item_in_inlist in obj.InList: |
| | element_to_check = item_in_inlist |
| | if hasattr(item_in_inlist, "getLinkedObject"): |
| | linked = item_in_inlist.getLinkedObject() |
| | if linked: |
| | element_to_check = linked |
| |
|
| | if element_to_check.Name in processed_or_queued_names: |
| | continue |
| |
|
| | is_confirmed_hosted = False |
| | element_type = Draft.getType(element_to_check) |
| |
|
| | if element_type == "Window": |
| | if hasattr(element_to_check, "Hosts") and obj in element_to_check.Hosts: |
| | is_confirmed_hosted = True |
| | elif element_type == "Rebar": |
| | if hasattr(element_to_check, "Host") and obj == element_to_check.Host: |
| | is_confirmed_hosted = True |
| |
|
| | if is_confirmed_hosted: |
| | children_to_add_to_queue_next.append(element_to_check) |
| | processed_or_queued_names.add(element_to_check.Name) |
| |
|
| | |
| | if include_components_from_additions and hasattr(obj, "Additions") and obj.Additions: |
| | for addition_comp in obj.Additions: |
| | actual_addition = addition_comp |
| | if hasattr(addition_comp, "getLinkedObject"): |
| | linked_add = addition_comp.getLinkedObject() |
| | if linked_add: |
| | actual_addition = linked_add |
| |
|
| | if actual_addition.Name not in processed_or_queued_names: |
| | children_to_add_to_queue_next.append(actual_addition) |
| | processed_or_queued_names.add(actual_addition.Name) |
| |
|
| | if children_to_add_to_queue_next: |
| | |
| | |
| | |
| | |
| | |
| | queue.extend(children_to_add_to_queue_next) |
| |
|
| | return final_contents_list |
| |
|
| |
|
| | def survey(callback=False): |
| | """survey(): starts survey mode, where you can click edges and faces to get their lengths or area. |
| | Clicking on no object (on an empty area) resets the count.""" |
| | if not callback: |
| | if hasattr(FreeCAD, "SurveyObserver"): |
| | for label in FreeCAD.SurveyObserver.labels: |
| | FreeCAD.ActiveDocument.removeObject(label) |
| | FreeCADGui.Selection.removeObserver(FreeCAD.SurveyObserver) |
| | del FreeCAD.SurveyObserver |
| | FreeCADGui.Control.closeDialog() |
| | if hasattr(FreeCAD, "SurveyDialog"): |
| | del FreeCAD.SurveyDialog |
| | else: |
| | FreeCAD.SurveyObserver = _SurveyObserver(callback=survey) |
| | FreeCADGui.Selection.addObserver(FreeCAD.SurveyObserver) |
| | FreeCAD.SurveyDialog = SurveyTaskPanel() |
| | FreeCADGui.Control.showDialog(FreeCAD.SurveyDialog) |
| | else: |
| | sel = FreeCADGui.Selection.getSelectionEx() |
| | if hasattr(FreeCAD, "SurveyObserver"): |
| | if not sel: |
| | if FreeCAD.SurveyObserver.labels: |
| | for label in FreeCAD.SurveyObserver.labels: |
| | FreeCAD.ActiveDocument.removeObject(label) |
| | tl = FreeCAD.SurveyObserver.totalLength |
| | ta = FreeCAD.SurveyObserver.totalArea |
| | FreeCAD.SurveyObserver.labels = [] |
| | FreeCAD.SurveyObserver.selection = [] |
| | FreeCAD.SurveyObserver.totalLength = 0 |
| | FreeCAD.SurveyObserver.totalArea = 0 |
| | FreeCAD.SurveyObserver.totalVolume = 0 |
| | if not FreeCAD.SurveyObserver.cancellable: |
| | FreeCAD.Console.PrintMessage("\n---- Reset ----\n\n") |
| | FreeCAD.SurveyObserver.cancellable = True |
| | if hasattr(FreeCAD, "SurveyDialog"): |
| | FreeCAD.SurveyDialog.newline(tl, ta) |
| | else: |
| | FreeCADGui.Selection.removeObserver(FreeCAD.SurveyObserver) |
| | del FreeCAD.SurveyObserver |
| | FreeCADGui.Control.closeDialog() |
| | if hasattr(FreeCAD, "SurveyDialog"): |
| | del FreeCAD.SurveyDialog |
| | else: |
| | FreeCAD.SurveyObserver.cancellable = False |
| | basesel = FreeCAD.SurveyObserver.selection |
| | newsels = [] |
| | for o in sel: |
| | found = False |
| | for eo in basesel: |
| | if o.ObjectName == eo.ObjectName: |
| | if o.SubElementNames == eo.SubElementNames: |
| | found = True |
| | if not found: |
| | newsels.append(o) |
| | if newsels: |
| | for o in newsels: |
| | if hasattr(o.Object, "Shape"): |
| | n = o.Object.Label |
| | showUnit = params.get_param_arch("surveyUnits") |
| | t = "" |
| | u = FreeCAD.Units.Quantity() |
| | if not o.HasSubObjects: |
| | |
| | anno = FreeCAD.ActiveDocument.addObject( |
| | "App::AnnotationLabel", "surveyLabel" |
| | ) |
| | if hasattr(o.Object.Shape, "CenterOfMass"): |
| | anno.BasePosition = o.Object.Shape.CenterOfMass |
| | else: |
| | anno.BasePosition = o.Object.Shape.BoundBox.Center |
| | FreeCAD.SurveyObserver.labels.append(anno.Name) |
| | if o.Object.Shape.Solids: |
| | u = FreeCAD.Units.Quantity( |
| | o.Object.Shape.Volume, FreeCAD.Units.Volume |
| | ) |
| | t = u.getUserPreferred()[0] |
| | t = t.replace("^3", "³") |
| | anno.LabelText = "v " + t |
| | FreeCAD.Console.PrintMessage( |
| | "Object: " + n + ", Element: Whole, Volume: " + t + "\n" |
| | ) |
| | FreeCAD.SurveyObserver.totalVolume += u.Value |
| | elif o.Object.Shape.Faces: |
| | u = FreeCAD.Units.Quantity( |
| | o.Object.Shape.Area, FreeCAD.Units.Area |
| | ) |
| | t = u.getUserPreferred()[0] |
| | t = t.replace("^2", "²") |
| | anno.LabelText = "a " + t |
| | FreeCAD.Console.PrintMessage( |
| | "Object: " + n + ", Element: Whole, Area: " + t + "\n" |
| | ) |
| | FreeCAD.SurveyObserver.totalArea += u.Value |
| | if hasattr(FreeCAD, "SurveyDialog"): |
| | FreeCAD.SurveyDialog.update(2, t) |
| | else: |
| | u = FreeCAD.Units.Quantity( |
| | o.Object.Shape.Length, FreeCAD.Units.Length |
| | ) |
| | t = u.getUserPreferred()[0] |
| | anno.LabelText = "l " + t |
| | FreeCAD.Console.PrintMessage( |
| | "Object: " + n + ", Element: Whole, Length: " + t + "\n" |
| | ) |
| | FreeCAD.SurveyObserver.totalLength += u.Value |
| | if hasattr(FreeCAD, "SurveyDialog"): |
| | FreeCAD.SurveyDialog.update(1, t) |
| | if FreeCAD.GuiUp and t: |
| | if showUnit: |
| | QtGui.QApplication.clipboard().setText(t) |
| | else: |
| | QtGui.QApplication.clipboard().setText(str(u.Value)) |
| | else: |
| | |
| | for el in o.SubElementNames: |
| | e = getattr(o.Object.Shape, el) |
| | anno = FreeCAD.ActiveDocument.addObject( |
| | "App::AnnotationLabel", "surveyLabel" |
| | ) |
| | if "Vertex" in el: |
| | anno.BasePosition = e.Point |
| | else: |
| | if hasattr(e, "CenterOfMass"): |
| | anno.BasePosition = e.CenterOfMass |
| | else: |
| | anno.BasePosition = e.BoundBox.Center |
| | FreeCAD.SurveyObserver.labels.append(anno.Name) |
| | if "Face" in el: |
| | u = FreeCAD.Units.Quantity(e.Area, FreeCAD.Units.Area) |
| | t = u.getUserPreferred()[0] |
| | t = t.replace("^2", "²") |
| | anno.LabelText = "a " + t |
| | FreeCAD.Console.PrintMessage( |
| | "Object: " |
| | + n |
| | + ", Element: " |
| | + el |
| | + ", Area: " |
| | + t |
| | + "\n" |
| | ) |
| | FreeCAD.SurveyObserver.totalArea += u.Value |
| | if hasattr(FreeCAD, "SurveyDialog"): |
| | FreeCAD.SurveyDialog.update(2, t) |
| | elif "Edge" in el: |
| | u = FreeCAD.Units.Quantity(e.Length, FreeCAD.Units.Length) |
| | t = u.getUserPreferred()[0] |
| | anno.LabelText = "l " + t |
| | FreeCAD.Console.PrintMessage( |
| | "Object: " |
| | + n |
| | + ", Element: " |
| | + el |
| | + ", Length: " |
| | + t |
| | + "\n" |
| | ) |
| | FreeCAD.SurveyObserver.totalLength += u.Value |
| | if hasattr(FreeCAD, "SurveyDialog"): |
| | FreeCAD.SurveyDialog.update(1, t) |
| | elif "Vertex" in el: |
| | u = FreeCAD.Units.Quantity(e.Z, FreeCAD.Units.Length) |
| | t = u.getUserPreferred()[0] |
| | anno.LabelText = "z " + t |
| | FreeCAD.Console.PrintMessage( |
| | "Object: " |
| | + n |
| | + ", Element: " |
| | + el |
| | + ", Zcoord: " |
| | + t |
| | + "\n" |
| | ) |
| | if FreeCAD.GuiUp and t: |
| | if showUnit: |
| | QtGui.QApplication.clipboard().setText(t) |
| | else: |
| | QtGui.QApplication.clipboard().setText(str(u.Value)) |
| |
|
| | FreeCAD.SurveyObserver.selection.extend(newsels) |
| | if hasattr(FreeCAD, "SurveyObserver"): |
| | if ( |
| | FreeCAD.SurveyObserver.totalLength |
| | or FreeCAD.SurveyObserver.totalArea |
| | or FreeCAD.SurveyObserver.totalVolume |
| | ): |
| | msg = " Total:" |
| | if FreeCAD.SurveyObserver.totalLength: |
| | u = FreeCAD.Units.Quantity( |
| | FreeCAD.SurveyObserver.totalLength, FreeCAD.Units.Length |
| | ) |
| | t = u.getUserPreferred()[0] |
| | msg += " Length: " + t |
| | if FreeCAD.SurveyObserver.totalArea: |
| | u = FreeCAD.Units.Quantity( |
| | FreeCAD.SurveyObserver.totalArea, FreeCAD.Units.Area |
| | ) |
| | t = u.getUserPreferred()[0] |
| | t = t.replace("^2", "²") |
| | msg += " Area: " + t |
| | if FreeCAD.SurveyObserver.totalVolume: |
| | u = FreeCAD.Units.Quantity( |
| | FreeCAD.SurveyObserver.totalVolume, FreeCAD.Units.Volume |
| | ) |
| | t = u.getUserPreferred()[0] |
| | t = t.replace("^3", "³") |
| | msg += " Volume: " + t |
| | FreeCAD.Console.PrintMessage(msg + "\n") |
| |
|
| |
|
| | class _SurveyObserver: |
| | "an observer for the survey() function" |
| |
|
| | def __init__(self, callback): |
| | self.callback = callback |
| | self.selection = [] |
| | self.labels = [] |
| | self.totalLength = 0 |
| | self.totalArea = 0 |
| | self.totalVolume = 0 |
| | self.cancellable = False |
| | self.doubleclear = False |
| |
|
| | def addSelection(self, document, object, element, position): |
| | self.doubleclear = False |
| | self.callback(True) |
| |
|
| | def clearSelection(self, document): |
| | if not self.doubleclear: |
| | self.doubleclear = True |
| | else: |
| | self.callback(True) |
| |
|
| |
|
| | class SurveyTaskPanel: |
| | "A task panel for the survey tool" |
| |
|
| | def __init__(self): |
| | self.form = QtGui.QWidget() |
| | self.form.setWindowIcon(QtGui.QIcon(":/icons/Arch_Survey.svg")) |
| | layout = QtGui.QVBoxLayout(self.form) |
| | llayout = QtGui.QHBoxLayout() |
| | self.descr = QtGui.QLineEdit() |
| | llayout.addWidget(self.descr) |
| | self.addButton = QtGui.QPushButton() |
| | llayout.addWidget(self.addButton) |
| | layout.addLayout(llayout) |
| | self.tree = QtGui.QTreeWidget() |
| | self.tree.setColumnCount(3) |
| | layout.addWidget(self.tree) |
| | blayout = QtGui.QHBoxLayout() |
| | self.clearButton = QtGui.QPushButton() |
| | blayout.addWidget(self.clearButton) |
| | self.copyLength = QtGui.QPushButton() |
| | blayout.addWidget(self.copyLength) |
| | self.copyArea = QtGui.QPushButton() |
| | blayout.addWidget(self.copyArea) |
| | layout.addLayout(blayout) |
| | self.export = QtGui.QPushButton() |
| | layout.addWidget(self.export) |
| | QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.setText) |
| | QtCore.QObject.connect(self.clearButton, QtCore.SIGNAL("clicked()"), self.clear) |
| | QtCore.QObject.connect(self.copyLength, QtCore.SIGNAL("clicked()"), self.clipLength) |
| | QtCore.QObject.connect(self.copyArea, QtCore.SIGNAL("clicked()"), self.clipArea) |
| | QtCore.QObject.connect(self.export, QtCore.SIGNAL("clicked()"), self.exportCSV) |
| | QtCore.QObject.connect( |
| | self.tree, QtCore.SIGNAL("itemClicked(QTreeWidgetItem*,int)"), self.setDescr |
| | ) |
| | self.retranslateUi(self) |
| | item = QtGui.QTreeWidgetItem(self.tree) |
| | self.tree.setCurrentItem(item) |
| |
|
| | def retranslateUi(self, dlg): |
| | self.form.setWindowTitle(QtGui.QApplication.translate("Arch", "Survey", None)) |
| | self.addButton.setText(QtGui.QApplication.translate("Arch", "Set Description", None)) |
| | self.clearButton.setText(QtGui.QApplication.translate("Arch", "Clear", None)) |
| | self.copyLength.setText(QtGui.QApplication.translate("Arch", "Copy Total Length", None)) |
| | self.copyArea.setText(QtGui.QApplication.translate("Arch", "Copy Total Area", None)) |
| | self.export.setText(QtGui.QApplication.translate("Arch", "Export CSV", None)) |
| | self.tree.setHeaderLabels( |
| | [ |
| | QtGui.QApplication.translate("Arch", "Description", None), |
| | QtGui.QApplication.translate("Arch", "Length", None), |
| | QtGui.QApplication.translate("Arch", "Area", None), |
| | ] |
| | ) |
| |
|
| | def isAllowedAlterSelection(self): |
| | return True |
| |
|
| | def isAllowedAlterView(self): |
| | return True |
| |
|
| | def getStandardButtons(self): |
| | return QtGui.QDialogButtonBox.Close |
| |
|
| | def reject(self): |
| | if hasattr(FreeCAD, "SurveyObserver"): |
| | for label in FreeCAD.SurveyObserver.labels: |
| | FreeCAD.ActiveDocument.removeObject(label) |
| | FreeCADGui.Selection.removeObserver(FreeCAD.SurveyObserver) |
| | del FreeCAD.SurveyObserver |
| | return True |
| |
|
| | def clear(self): |
| | FreeCADGui.Selection.clearSelection() |
| |
|
| | def clipLength(self): |
| | if hasattr(FreeCAD, "SurveyObserver"): |
| | u = FreeCAD.Units.Quantity(FreeCAD.SurveyObserver.totalLength, FreeCAD.Units.Length) |
| | t = u.getUserPreferred()[0] |
| | if params.get_param_arch("surveyUnits"): |
| | QtGui.QApplication.clipboard().setText(t) |
| | else: |
| | QtGui.QApplication.clipboard().setText(str(u.Value / u.getUserPreferred()[1])) |
| |
|
| | def clipArea(self): |
| | if hasattr(FreeCAD, "SurveyObserver"): |
| | u = FreeCAD.Units.Quantity(FreeCAD.SurveyObserver.totalArea, FreeCAD.Units.Area) |
| | t = u.getUserPreferred()[0] |
| | t = t.replace("^2", "²") |
| | if params.get_param_arch("surveyUnits"): |
| | QtGui.QApplication.clipboard().setText(t) |
| | else: |
| | QtGui.QApplication.clipboard().setText(str(u.Value / u.getUserPreferred()[1])) |
| |
|
| | def newline(self, length=0, area=0): |
| | FreeCADGui.Selection.clearSelection() |
| | item = QtGui.QTreeWidgetItem(self.tree) |
| | if length or area: |
| | item.setText(0, QtGui.QApplication.translate("Arch", "Total", None)) |
| | item.setToolTip(0, "total") |
| | f = QtGui.QFont() |
| | f.setBold(True) |
| | item.setFont(0, f) |
| | item.setFont(1, f) |
| | item.setFont(2, f) |
| | else: |
| | item.setText(0, self.descr.text()) |
| | self.descr.setText("") |
| | self.tree.setCurrentItem(item) |
| | if length: |
| | u = FreeCAD.Units.Quantity(length, FreeCAD.Units.Length) |
| | t = u.getUserPreferred()[0] |
| | item.setText(1, t) |
| | if area: |
| | u = FreeCAD.Units.Quantity(area, FreeCAD.Units.Area) |
| | t = u.getUserPreferred()[0] |
| | t = t.replace("^2", "²") |
| | item.setText(2, t) |
| | if length or area: |
| | item = QtGui.QTreeWidgetItem(self.tree) |
| | self.tree.setCurrentItem(item) |
| |
|
| | def update(self, column, txt): |
| | item = QtGui.QTreeWidgetItem(self.tree) |
| | self.tree.setCurrentItem(item) |
| | item.setText(column, txt) |
| |
|
| | def setDescr(self, item, col): |
| | self.descr.setText(item.text(0)) |
| |
|
| | def setText(self): |
| | item = self.tree.currentItem() |
| | if item: |
| | item.setText(0, self.descr.text()) |
| | self.descr.setText("") |
| |
|
| | def exportCSV(self): |
| | import csv |
| |
|
| | rows = self.tree.topLevelItemCount() |
| | if rows: |
| | filename = QtGui.QFileDialog.getSaveFileName( |
| | QtGui.QApplication.activeWindow(), |
| | translate("Arch", "Export CSV File"), |
| | None, |
| | "CSV file (*.csv)", |
| | ) |
| | if filename: |
| | with open(filename[0].encode("utf8"), "w") as csvfile: |
| | csvfile = csv.writer(csvfile, delimiter="\t") |
| | suml = 0 |
| | for i in range(rows): |
| | item = self.tree.topLevelItem(i) |
| | row = [] |
| | row.append(item.text(0)) |
| | if item.text(1): |
| | u = FreeCAD.Units.Quantity(item.text(1)) |
| | if item.toolTip(0) == "total": |
| | row.append("=SUM(B" + str(suml + 1) + ":B" + str(i) + ")") |
| | else: |
| | row.append(u.Value / u.getUserPreferred()[1]) |
| | row.append(u.getUserPreferred()[2]) |
| | else: |
| | row.extend(["", ""]) |
| | if item.text(2): |
| | t = item.text(2).replace("²", "^2") |
| | u = FreeCAD.Units.Quantity(t) |
| | if item.toolTip(0) == "total": |
| | row.append("=SUM(D" + str(suml + 1) + ":D" + str(i) + ")") |
| | else: |
| | row.append(u.Value / u.getUserPreferred()[1]) |
| | row.append(u.getUserPreferred()[2]) |
| | else: |
| | row.extend(["", ""]) |
| | csvfile.writerow(row) |
| | if item.toolTip(0) == "total": |
| | suml = i + 1 |
| | print("successfully exported ", filename[0]) |
| |
|
| |
|
| | def toggleIfcBrepFlag(obj): |
| | """toggleIfcBrepFlag(obj): toggles the IFC brep flag of the given object, forcing it |
| | to be exported as brep geometry or not.""" |
| | if not hasattr(obj, "IfcData"): |
| | FreeCAD.Console.PrintMessage( |
| | translate("Arch", "Object does not have settable IFC attributes") |
| | ) |
| | else: |
| | d = obj.IfcData |
| | if "FlagForceBrep" in d: |
| | if d["FlagForceBrep"] == "True": |
| | d["FlagForceBrep"] = "False" |
| | FreeCAD.Console.PrintMessage( |
| | translate("Arch", "Disabling B-rep force flag of object") |
| | + " " |
| | + obj.Label |
| | + "\n" |
| | ) |
| | else: |
| | d["FlagForceBrep"] = "True" |
| | FreeCAD.Console.PrintMessage( |
| | translate("Arch", "Enabling B-rep force flag of object") |
| | + " " |
| | + obj.Label |
| | + "\n" |
| | ) |
| | else: |
| | d["FlagForceBrep"] = "True" |
| | FreeCAD.Console.PrintMessage( |
| | translate("Arch", "Enabling B-rep force flag of object") + " " + obj.Label + "\n" |
| | ) |
| | obj.IfcData = d |
| |
|
| |
|
| | def makeCompoundFromSelected(objects=None): |
| | """makeCompoundFromSelected([objects]): Creates a new compound object from the given |
| | subobjects (faces, edges) or from the selection if objects is None""" |
| | import FreeCADGui |
| | import Part |
| |
|
| | so = [] |
| | if not objects: |
| | objects = FreeCADGui.Selection.getSelectionEx() |
| | if not isinstance(objects, list): |
| | objects = [objects] |
| | for o in objects: |
| | so.extend(o.SubObjects) |
| | if so: |
| | c = Part.makeCompound(so) |
| | Part.show(c) |
| |
|
| |
|
| | def cleanArchSplitter(objects=None): |
| | """cleanArchSplitter([objects]): removes the splitters from the base shapes |
| | of the given Arch objects or selected Arch objects if objects is None""" |
| | import FreeCAD |
| | import FreeCADGui |
| |
|
| | if not objects: |
| | objects = FreeCADGui.Selection.getSelection() |
| | if not isinstance(objects, list): |
| | objects = [objects] |
| | for obj in objects: |
| | if hasattr(obj, "Shape"): |
| | if hasattr(obj, "Base"): |
| | if obj.Base: |
| | print("Attempting to clean splitters from ", obj.Label) |
| | base = obj.Base.getLinkedObject() |
| | if base.isDerivedFrom("Part::Feature"): |
| | if not base.Shape.isNull(): |
| | base.Shape = base.Shape.removeSplitter() |
| | FreeCAD.ActiveDocument.recompute() |
| |
|
| |
|
| | def rebuildArchShape(objects=None): |
| | """rebuildArchShape([objects]): takes the faces from the base shape of the given (or selected |
| | if objects is None) Arch objects, and tries to rebuild a valid solid from them.""" |
| | import FreeCAD |
| | import Part |
| |
|
| | if not objects and FreeCAD.GuiUp: |
| | objects = FreeCADGui.Selection.getSelection() |
| | if not isinstance(objects, list): |
| | objects = [objects] |
| | for obj in objects: |
| | success = False |
| | if hasattr(obj, "Shape"): |
| | if hasattr(obj, "Base"): |
| | if obj.Base: |
| | try: |
| | print("Attempting to rebuild ", obj.Label) |
| | base = obj.Base.getLinkedObject() |
| | if base.isDerivedFrom("Part::Feature"): |
| | if not base.Shape.isNull(): |
| | faces = [] |
| | for f in base.Shape.Faces: |
| | f2 = Part.Face(f.Wires) |
| | |
| | faces.append(f2) |
| | if faces: |
| | shell = Part.Shell(faces) |
| | if shell: |
| | |
| | solid = Part.Solid(shell) |
| | if solid: |
| | if not solid.isValid(): |
| | solid.sewShape() |
| | solid = Part.Solid(solid) |
| | |
| | if solid.isValid(): |
| | base.Shape = solid |
| | success = True |
| | except Exception: |
| | pass |
| | if not success: |
| | print("Failed to rebuild a valid solid for object ", obj.Name) |
| | FreeCAD.ActiveDocument.recompute() |
| |
|
| |
|
| | def getExtrusionData(shape, sortmethod="area"): |
| | """If a shape has been extruded, returns the base face, and extrusion vector. |
| | |
| | Determines if a shape appears to have been extruded from some base face, and |
| | extruded at the normal from that base face. IE: it looks like a cuboid. |
| | https://en.wikipedia.org/wiki/Cuboid#Rectangular_cuboid |
| | |
| | If this is the case, returns what appears to be the base face, and the vector |
| | used to make that extrusion. |
| | |
| | The base face is determined based on the sortmethod parameter, which can either |
| | be: |
| | |
| | "area" = Of the faces with the smallest area, the one with the lowest z coordinate. |
| | "z" = The face with the lowest z coordinate. |
| | a 3D vector = the face which center is closest to the given 3D point |
| | |
| | Parameters |
| | ---------- |
| | shape: <Part.Shape> |
| | Shape to examine. |
| | sortmethod: {"area", "z"} |
| | Which sorting algorithm to use to determine the base face. |
| | |
| | Returns |
| | ------- |
| | Extrusion data: list |
| | Two item list containing the base face, and the vector used to create the |
| | extrusion. In that order. |
| | Failure: None |
| | Returns None if the object does not appear to be an extrusion. |
| | """ |
| |
|
| | if shape.isNull(): |
| | return None |
| | if not shape.Solids: |
| | return None |
| | if len(shape.Faces) < 3: |
| | return None |
| | |
| | faces = [] |
| | import Part |
| |
|
| | for f in shape.Faces: |
| | try: |
| | faces.append([f, f.normalAt(0, 0)]) |
| | except Part.OCCError: |
| | return None |
| | |
| | pairs = [] |
| | for i1, f1 in enumerate(faces): |
| | for i2, f2 in enumerate(faces): |
| | if f1[0].hashCode() != f2[0].hashCode(): |
| | if round(f1[1].getAngle(f2[1]), 4) == 3.1416: |
| | pairs.append([i1, i2]) |
| | if not pairs: |
| | return None |
| | valids = [] |
| | for pair in pairs: |
| | hc = [faces[pair[0]][0].hashCode(), faces[pair[1]][0].hashCode()] |
| | |
| | ok = True |
| | for f in faces: |
| | if f[0].hashCode() not in hc: |
| | if round(f[1].getAngle(faces[pair[0]][1]), 4) != 1.5708: |
| | ok = False |
| | if ok: |
| | |
| | if faces[pair[0]][0].CenterOfMass.z < faces[pair[1]][0].CenterOfMass.z: |
| | valids.append( |
| | [ |
| | faces[pair[0]][0], |
| | faces[pair[1]][0].CenterOfMass.sub(faces[pair[0]][0].CenterOfMass), |
| | ] |
| | ) |
| | else: |
| | valids.append( |
| | [ |
| | faces[pair[1]][0], |
| | faces[pair[0]][0].CenterOfMass.sub(faces[pair[1]][0].CenterOfMass), |
| | ] |
| | ) |
| | if valids: |
| | if sortmethod == "z": |
| | valids.sort(key=lambda v: v[0].CenterOfMass.z) |
| | elif sortmethod == "area": |
| | |
| | valids.sort(key=lambda v: v[0].Area) |
| | else: |
| | valids.sort(key=lambda v: (v[0].CenterOfMass.sub(sortmethod)).Length) |
| | return valids[0] |
| | return None |
| |
|
| |
|
| | def printMessage(message): |
| | FreeCAD.Console.PrintMessage(message) |
| | if FreeCAD.GuiUp: |
| | QtGui.QMessageBox.information(None, "", message) |
| |
|
| |
|
| | def printWarning(message): |
| | FreeCAD.Console.PrintMessage(message) |
| | if FreeCAD.GuiUp: |
| | QtGui.QMessageBox.warning(None, "", message) |
| |
|
| |
|
| | def makeIfcSpreadsheet(archobj=None): |
| | ifc_container = None |
| | for obj in FreeCAD.ActiveDocument.Objects: |
| | if obj.Name == "IfcPropertiesContainer": |
| | ifc_container = obj |
| | if not ifc_container: |
| | ifc_container = FreeCAD.ActiveDocument.addObject( |
| | "App::DocumentObjectGroup", "IfcPropertiesContainer" |
| | ) |
| | import Spreadsheet |
| |
|
| | ifc_spreadsheet = FreeCAD.ActiveDocument.addObject("Spreadsheet::Sheet", "IfcProperties") |
| | ifc_spreadsheet.set("A1", translate("Arch", "Category")) |
| | ifc_spreadsheet.set("B1", translate("Arch", "Key")) |
| | ifc_spreadsheet.set("C1", translate("Arch", "Type")) |
| | ifc_spreadsheet.set("D1", translate("Arch", "Value")) |
| | ifc_spreadsheet.set("E1", translate("Arch", "Unit")) |
| | ifc_container.addObject(ifc_spreadsheet) |
| | if archobj: |
| | if hasattr(obj, "IfcProperties"): |
| | archobj.IfcProperties = ifc_spreadsheet |
| | return ifc_spreadsheet |
| | else: |
| | FreeCAD.Console.PrintWarning( |
| | translate( |
| | "Arch", |
| | "The object does not have an IfcProperties attribute. Cancel spreadsheet creation for object:", |
| | ) |
| | + " " |
| | + archobj.Label |
| | ) |
| | FreeCAD.ActiveDocument.removeObject(ifc_spreadsheet) |
| | else: |
| | return ifc_spreadsheet |
| |
|