#*************************************************************************** #* Copyright (c) 2018 Yorik van Havre * #* * #* This program is free software; you can redistribute it and/or modify * #* it under the terms of the GNU Lesser General Public License (LGPL) * #* as published by the Free Software Foundation; either version 2 of * #* the License, or (at your option) any later version. * #* for detail see the LICENCE text file. * #* * #* This program is distributed in the hope that it will be useful, * #* but WITHOUT ANY WARRANTY; without even the implied warranty of * #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * #* GNU Library General Public License for more details. * #* * #* You should have received a copy of the GNU Library General Public * #* License along with this program; if not, write to the Free Software * #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * #* USA * #* * #*************************************************************************** __title__ = "FreeCAD Arch External Reference" __author__ = "Yorik van Havre" __url__ = "https://www.freecad.org" import FreeCAD import os import zipfile import re from draftutils import params if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui from draftutils.translate import translate from PySide.QtCore import QT_TRANSLATE_NOOP else: # \cond def translate(ctxt,txt): return txt def QT_TRANSLATE_NOOP(ctxt,txt): return txt # \endcond ## @package ArchReference # \ingroup ARCH # \brief The Reference object and tools # # This module provides tools to build Reference objects. # References can take a shape from a Part-based object in # another file. def makeReference(filepath=None,partname=None,name=None): "makeReference([filepath],[partname],[name]): Creates an Arch Reference object" if not FreeCAD.ActiveDocument: FreeCAD.Console.PrintError("No active document. Aborting\n") return obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","ArchReference") obj.Label = name if name else translate("Arch","External Reference") ArchReference(obj) if FreeCAD.GuiUp: ViewProviderArchReference(obj.ViewObject) if filepath: obj.File = filepath if partname: obj.Part = partname import Draft Draft.select(obj) return obj class ArchReference: "The Arch Reference object" def __init__(self,obj): obj.Proxy = self ArchReference.setProperties(self,obj) self.Type = "Reference" self.reload = True 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")) if not "Part" in pl: obj.addProperty("App::PropertyString","Part","Reference",QT_TRANSLATE_NOOP("App::Property","The part to use from the base file")) 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")) 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") if not "FuseArch" in pl: obj.addProperty("App::PropertyBool","FuseArch", "Reference", QT_TRANSLATE_NOOP("App::Property","Fuse objects of same material")) self.Type = "Reference" 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): return None def onChanged(self,obj,prop): if prop in ["File","Part"]: self.reload = True elif prop == "ReferenceMode": if obj.ReferenceMode == "Normal": if obj.ViewObject and obj.ViewObject.Proxy: obj.ViewObject.Proxy.unloadInventor(obj) if (not obj.Shape) or obj.Shape.isNull(): self.reload = True obj.touch() elif obj.ReferenceMode == "Transient": if obj.ViewObject and obj.ViewObject.Proxy: obj.ViewObject.Proxy.unloadInventor(obj) self.reload = False elif obj.ReferenceMode == "Lightweight": self.reload = False import Part pl = obj.Placement obj.Shape = Part.Shape() obj.Placement = pl if obj.ViewObject and obj.ViewObject.Proxy: obj.ViewObject.Proxy.loadInventor(obj) def execute(self,obj): pl = obj.Placement filename = self.getFile(obj) if filename and obj.Part 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 else: print("Part not found in file") self.reload = False def cleanShape(self,shapedata,obj,materials): "cleans the imported shape" import Part shape = Part.Shape() shape.importBrepFromString(shapedata) if obj.FuseArch and materials: # separate lone edges shapes = [] for edge in shape.Edges: found = False for solid in shape.Solids: for soledge in solid.Edges: if edge.hashCode() == soledge.hashCode(): found = True break if found: break if found: break else: shapes.append(edge) print("solids:",len(shape.Solids),"mattable:",materials) for key,solindexes in materials.items(): if key == "Undefined": # do not join objects with no defined material for solindex in [int(i) for i in solindexes.split(",")]: shapes.append(shape.Solids[solindex]) else: fusion = None for solindex in [int(i) for i in solindexes.split(",")]: if not fusion: fusion = shape.Solids[solindex] else: fusion = fusion.fuse(shape.Solids[solindex]) if fusion: shapes.append(fusion) shape = Part.makeCompound(shapes) try: shape = shape.removeSplitter() except Exception: print(obj.Label,": error removing splitter") return shape def exists(self,filepath): "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 return None def getFile(self,obj,filename=None): "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 self.exists(filename): # search for the file in the current directory if not found basename = os.path.basename(filename) currentdir = os.path.dirname(obj.Document.FileName) altfile = os.path.join(currentdir,basename) if altfile == obj.Document.FileName: return None elif self.exists(altfile): return self.exists(altfile) else: # search for subpaths in current folder altfile = None subdirs = self.splitall(os.path.dirname(filename)) for i in range(len(subdirs)): subpath = [currentdir]+subdirs[-i:]+[basename] altfile = os.path.join(*subpath) if self.exists(altfile): return self.exists(altfile) return None return self.exists(filename) def getPartsList(self,obj,filename=None): "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 label = None part = None materials = {} writemode = False for line in docf: line = line.decode("utf8") if "" in line: writemode = False elif "" in line: if name and label and part: parts[name] = [label,part,materials] name = None label = None part = None materials = {} writemode = False return parts def getColors(self,obj): "returns the DiffuseColor of the referenced object" filename = self.getFile(obj) if not filename: return None 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 ("