diff --git a/src/Mod/Arch/ArchReference.py b/src/Mod/Arch/ArchReference.py index 03f6a9f66d..793b02fee4 100644 --- a/src/Mod/Arch/ArchReference.py +++ b/src/Mod/Arch/ArchReference.py @@ -53,10 +53,9 @@ else: -def makeReference(filepath=None,partname=None,name=None): +def makeReference(filepath=None, partname=None, name=None): - - "makeReference([filepath],[partname],[name]): Creates an Arch Reference object" + """makeReference([filepath],[partname],[name]): Creates an Arch Reference object""" if not FreeCAD.ActiveDocument: FreeCAD.Console.PrintError("No active document. Aborting\n") @@ -78,52 +77,61 @@ def makeReference(filepath=None,partname=None,name=None): class ArchReference: + """The Arch Reference object""" - "The Arch Reference object" - - def __init__(self,obj): + def __init__(self, obj): obj.Proxy = self - ArchReference.setProperties(self,obj) + ArchReference.setProperties(self, obj) self.Type = "Reference" self.reload = True - def setProperties(self,obj): + + def setProperties(self, obj): pl = obj.PropertiesList if not "File" in pl: - obj.addProperty("App::PropertyFile","File","Reference",QT_TRANSLATE_NOOP("App::Property","The base file this component is built upon")) + t = QT_TRANSLATE_NOOP("App::Property","The base file this component is built upon") + obj.addProperty("App::PropertyFile","File","Reference",t) if not "Part" in pl: - obj.addProperty("App::PropertyString","Part","Reference",QT_TRANSLATE_NOOP("App::Property","The part to use from the base file")) + t = QT_TRANSLATE_NOOP("App::Property","The part to use from the base file") + obj.addProperty("App::PropertyString","Part","Reference",t) if not "ReferenceMode" in pl: - obj.addProperty("App::PropertyEnumeration","ReferenceMode","Reference",QT_TRANSLATE_NOOP("App::Property","The way the referenced objects are included in the current document. 'Normal' includes the shape, 'Transient' discards the shape when the object is switched off (smaller filesize), 'Lightweight' does not import the shape but only the OpenInventor representation")) + t = QT_TRANSLATE_NOOP("App::Property","The way the referenced objects are included in the current document. 'Normal' includes the shape, 'Transient' discards the shape when the object is switched off (smaller filesize), 'Lightweight' does not import the shape but only the OpenInventor representation") + obj.addProperty("App::PropertyEnumeration","ReferenceMode","Reference",t) obj.ReferenceMode = ["Normal","Transient","Lightweight"] if "TransientReference" in pl: if obj.TransientReference: obj.ReferenceMode = "Transient" obj.removeProperty("TransientReference") - FreeCAD.Console.PrintMessage("Upgrading "+obj.Label+" TransientReference property to ReferenceMode\n") + t = translate("Arch", "TransientReference property to ReferenceMode") + FreeCAD.Console.PrintMessage(translate("Arch","Upgrading")+" "+obj.Label+" "+t+"\n") if not "FuseArch" in pl: - obj.addProperty("App::PropertyBool","FuseArch", "Reference", QT_TRANSLATE_NOOP("App::Property","Fuse objects of same material")) + t = QT_TRANSLATE_NOOP("App::Property","Fuse objects of same material") + obj.addProperty("App::PropertyBool","FuseArch", "Reference", t) self.Type = "Reference" - def onDocumentRestored(self,obj): - ArchReference.setProperties(self,obj) + def onDocumentRestored(self, obj): + + ArchReference.setProperties(self, obj) self.reload = False if obj.ReferenceMode == "Lightweight": if obj.ViewObject and obj.ViewObject.Proxy: obj.ViewObject.Proxy.loadInventor(obj) + def dumps(self): return None - def loads(self,state): + + def loads(self, state): return None - def onChanged(self,obj,prop): + + def onChanged(self, obj, prop): if prop in ["File","Part"]: self.reload = True @@ -147,32 +155,112 @@ class ArchReference: if obj.ViewObject and obj.ViewObject.Proxy: obj.ViewObject.Proxy.loadInventor(obj) - def execute(self,obj): + def execute(self, obj): + + import Part pl = obj.Placement filename = self.getFile(obj) - if filename and obj.Part and self.reload and obj.ReferenceMode in ["Normal","Transient"]: + if filename and self.reload and obj.ReferenceMode in ["Normal","Transient"]: self.parts = self.getPartsList(obj) if self.parts: - zdoc = zipfile.ZipFile(filename) - if zdoc: - if obj.Part in self.parts: - if self.parts[obj.Part][1] in zdoc.namelist(): - f = zdoc.open(self.parts[obj.Part][1]) - shapedata = f.read() - f.close() - shapedata = shapedata.decode("utf8") - shape = self.cleanShape(shapedata,obj,self.parts[obj.Part][2]) - obj.Shape = shape - if not pl.isIdentity(): - obj.Placement = pl + if filename.lower().endswith(".fcstd"): + zdoc = zipfile.ZipFile(filename) + if zdoc: + if obj.Part: + if obj.Part in self.parts: + if self.parts[obj.Part][1] in zdoc.namelist(): + f = zdoc.open(self.parts[obj.Part][1]) + shapedata = f.read() + f.close() + shapedata = shapedata.decode("utf8") + shape = self.cleanShape(shapedata,obj,self.parts[obj.Part][2]) + obj.Shape = shape + if not pl.isIdentity(): + obj.Placement = pl + else: + t = translate("Arch","Part not found in file") + FreeCAD.Console.PrintError(t+"\n") else: - print("Part not found in file") + shapes = [] + for part in self.parts.values(): + f = zdoc.open(part[1]) + shapedata = f.read() + f.close() + shapedata = shapedata.decode("utf8") + shape = self.cleanShape(shapedata,obj) + shapes.append(shape) + if shapes: + obj.Shape = Part.makeCompound(shapes) + elif filename.lower().endswith(".ifc"): + ifcfile = self.getIfcFile(filename) + if not ifcfile: + return + try: + import ifc_tools + import ifc_generator + except: + t = translate("Arch","NativeIFC not available - unable to process IFC files") + FreeCAD.Console.PrintError(t+"\n") + return + elements = self.getIFCElements(obj, ifcfile) + shape, colors = ifc_generator.generate_shape(ifcfile, elements, cached=True) + if shape: + placement = shape.Placement + obj.Shape = shape + obj.Placement = placement + if colors: + ifc_tools.set_colors(obj, colors) + elif filename.lower().endswith(".dxf"): + # create a special parameter set to control the DXF importer + loc = "User parameter:BaseApp/Preferences/Mod/Arch" + hGrp = FreeCAD.ParamGet(loc).GetGroup("RefDxfImport") + hGrp.SetBool("dxfUseDraftVisGroups", False) + hGrp.SetBool("dxfGetOriginalColors", False) + hGrp.SetBool("groupLayers", True) + hGrp.SetFloat("dxfScaling", 1.0) + hGrp.SetBool("dxftext", False) + hGrp.SetBool("dxfImportPoints", True) + hGrp.SetBool("dxflayout", False) + hGrp.SetBool("dxfstarblocks", False) + doc = obj.Document + oldobjs = list(doc.Objects) + import Import + Import.readDXF(filename, doc.Name, True, loc + "/RefDxfImport") + newobjs = [o for o in doc.Objects if o not in oldobjs] + shapes = [o.Shape for o in newobjs if o.isDerivedFrom("Part::Feature")] + if len(shapes) == 1: + obj.Shape = shapes[0] + elif len(shapes) > 1: + obj.Shape = Part.makeCompound(shapes) + names = [o.Name for o in newobjs] + for n in names: + doc.removeObject(n) self.reload = False - def cleanShape(self,shapedata,obj,materials): - "cleans the imported shape" + def getIFCElements(self, obj, ifcfile): + + """returns IFC elements for this object""" + + try: + import ifc_generator + except: + t = translate("Arch","NativeIFC not available - unable to process IFC files") + FreeCAD.Console.PrintError(t+"\n") + return + if obj.Part: + element = ifcfile[int(obj.Part)] + else: + element = ifcfile.by_type("IfcProject")[0] + elements = ifc_generator.get_decomposed_elements(element) + elements = ifc_generator.filter_types(elements) + return elements + + + def cleanShape(self, shapedata, obj, materials=None): + + """cleans the imported shape""" import Part shape = Part.Shape() @@ -193,7 +281,7 @@ class ArchReference: break else: shapes.append(edge) - print("solids:",len(shape.Solids),"mattable:",materials) + #print("solids:",len(shape.Solids),"mattable:",materials) for key,solindexes in materials.items(): if key == "Undefined": # do not join objects with no defined material @@ -212,31 +300,38 @@ class ArchReference: try: shape = shape.removeSplitter() except Exception: - print(obj.Label,": error removing splitter") + t = translate("Arch","Error removing splitter") + FreeCAD.Console.PrintError(obj.Label+": "+t+"\n") return shape + def exists(self,filepath): - "case-insensitive version of os.path.exists. Returns the actual file path or None" + """case-insensitive version of os.path.exists. Returns the actual file path or None""" if os.path.exists(filepath): return filepath - base, ext = os.path.splitext(filepath) - for e in [".fcstd",".FCStd",".FCSTD"]: - if os.path.exists(base + e): - return base + e + # check for uppercase/lowercase extensions + p, e = os.path.splitext(filepath) + if os.path.exists(p + e.lower()): + return p + e.lower() + if os.path.exists(p + e.upper()): + return p + e.upper() return None + def getFile(self,obj,filename=None): - "gets a valid file, if possible" + """gets a valid file, if possible""" if not filename: filename = obj.File if not filename: return None if not filename.lower().endswith(".fcstd"): - return None + if not filename.lower().endswith(".ifc"): + if not filename.lower().endswith(".dxf"): + return None if not self.exists(filename): # search for the file in the current directory if not found basename = os.path.basename(filename) @@ -258,15 +353,56 @@ class ArchReference: return None return self.exists(filename) - def getPartsList(self,obj,filename=None): - "returns a list of Part-based objects in a FCStd file" + def getPartsList(self, obj, filename=None): + + """returns a list of Part-based objects in a file""" + + filename = self.getFile(obj, filename) + if not filename: + return None + if filename.lower().endswith(".fcstd"): + return self.getPartsListFCSTD(obj, filename) + elif filename.lower().endswith(".ifc"): + return self.getPartsListIFC(obj, filename) + elif filename.lower().endswith(".dxf"): + return self.getPartsListDXF(obj, filename) + + + def getPartsListDXF(self, obj, filename): + + """returns a list of Part-based objects in a DXF file""" + + # support layers + #with open(filename) as f: + # txt = f.read() + return {} + + + def getPartsListIFC(self, obj, filename): + + """returns a list of Part-based objects in a IFC file""" + + ifcfile = self.getIfcFile(filename) + if not ifcfile: + return None + structs = ifcfile.by_type("IfcSpatialElement") + res = {} + for s in structs: + n = s.Name + if not n: + n = "" + name = "#" + str(s.id()) + " " + n + "(" + s.is_a() + ")" + res[str(s.id())] = [name, s, None] + return res + + + def getPartsListFCSTD(self, obj, filename): + + """returns a list of Part-based objects in a FCStd file""" parts = {} materials = {} - filename = self.getFile(obj,filename) - if not filename: - return parts zdoc = zipfile.ZipFile(filename) with zdoc.open("Document.xml") as docf: name = None @@ -313,9 +449,25 @@ class ArchReference: writemode = False return parts - def getColors(self,obj): - "returns the DiffuseColor of the referenced object" + def getIfcFile(self, filename): + + """Gets an IfcOpenShell object""" + + try: + import ifcopenshell + except: + t = translate("Arch","NativeIFC not available - unable to process IFC files") + FreeCAD.Console.PrintError(t+"\n") + return None + if not getattr(self, "ifcfile", None): + self.ifcfile = ifcopenshell.open(filename) + return self.ifcfile + + + def getColors(self, obj): + + """returns the DiffuseColor of the referenced object""" filename = self.getFile(obj) if not filename: @@ -323,42 +475,43 @@ class ArchReference: part = obj.Part if not obj.Part: return None - zdoc = zipfile.ZipFile(filename) - if not "GuiDocument.xml" in zdoc.namelist(): - return None - colorfile = None - with zdoc.open("GuiDocument.xml") as docf: - writemode1 = False - writemode2 = False - for line in docf: - line = line.decode("utf8") - if ("= 1: + if self.partCombo.itemData(i) != self.obj.Part: + self.obj.Part = self.partCombo.itemData(i) + else: + self.obj.Part = "" + QtCore.QTimer.singleShot(0,FreeCAD.ActiveDocument.recompute) + if self.filename and self.obj.Label == "External Reference": + self.obj.Label = os.path.basename(self.filename) FreeCADGui.ActiveDocument.resetEdit() return True @@ -730,24 +954,43 @@ class ArchReferenceTaskPanel: loc = QtCore.QDir.homePath() if self.obj.File: loc = os.path.dirname(self.obj.File) - f = QtGui.QFileDialog.getOpenFileName(self.form,'Choose reference file',loc,"FreeCAD standard files (*.FCStd)") + filters = "*.FCStd *.dxf" + # enable IFC support if NativeIFC is present + try: + import ifc_tools + except: + pass + else: + filters += " *.ifc" + filters = translate("Arch","Reference files")+" ("+filters+")" + f = QtGui.QFileDialog.getOpenFileName(self.form, + translate("Arch","Choose reference file"), + loc, + filters) if f: self.filename = f[0] self.fileButton.setText(os.path.basename(self.filename)) parts = self.obj.Proxy.getPartsList(self.obj,self.filename) + self.partCombo.clear() if parts: - self.partCombo.clear() + self.partCombo.setEnabled(True) sortedkeys = sorted(parts) + self.partCombo.addItem(translate("Arch","None (Use whole object)"),"") for k in sortedkeys: self.partCombo.addItem(parts[k][0],k) if self.obj.Part: if self.obj.Part in sortedkeys: self.partCombo.setCurrentIndex(sortedkeys.index(self.obj.Part)) + else: + self.partCombo.setEnabled(False) def openFile(self): if self.obj.File: - FreeCAD.openDocument(self.obj.File) + if self.obj.File.lower().endswith(".fcstd"): + FreeCAD.openDocument(self.obj.File) + else: + FreeCAD.loadFile(self.obj.File) FreeCADGui.Control.closeDialog() FreeCADGui.ActiveDocument.resetEdit() @@ -760,9 +1003,9 @@ class ArchReferenceCommand: def GetResources(self): return {'Pixmap' : 'Arch_Reference', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Arch_Reference","External reference"), + 'MenuText': QT_TRANSLATE_NOOP("Arch_Reference","External reference"), 'Accel': "E, X", - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Arch_Reference","Creates an external reference object")} + 'ToolTip': QT_TRANSLATE_NOOP("Arch_Reference","Creates an external reference object")} def IsActive(self):