399 lines
14 KiB
Python
399 lines
14 KiB
Python
# /**********************************************************************
|
|
# * *
|
|
# * Copyright (c) 2021 Javier Braña <javier.branagutierrez@gmail.com> *
|
|
# * *
|
|
# * 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 *
|
|
# * *
|
|
# ***********************************************************************
|
|
|
|
import FreeCAD
|
|
import ArchComponent
|
|
import os
|
|
import zipfile
|
|
import re
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
from DraftTools import translate
|
|
else:
|
|
# \cond
|
|
def translate(ctxt,txt):
|
|
return txt
|
|
def QT_TRANSLATE_NOOP(ctxt,txt):
|
|
return txt
|
|
# \endcond
|
|
|
|
import os
|
|
from PVPlantResources import DirIcons as DirIcons
|
|
|
|
__title__ = "PVPlant Areas"
|
|
__author__ = "Javier Braña"
|
|
__url__ = "http://www.sogos-solar.com"
|
|
|
|
import PVPlantResources
|
|
from PVPlantResources import DirIcons as DirIcons
|
|
Dir3dObjects = os.path.join(PVPlantResources.DirResources, "3dObjects")
|
|
|
|
|
|
def makeStringInverter():
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "StringInverter")
|
|
InverterBase(obj)
|
|
ViewProviderStringInverter(obj.ViewObject)
|
|
|
|
try:
|
|
folder = FreeCAD.ActiveDocument.StringInverters
|
|
except:
|
|
folder = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", 'StringInverters')
|
|
folder.Label = "StringInverters"
|
|
folder.addObject(obj)
|
|
return obj
|
|
|
|
|
|
class InverterBase(ArchComponent.Component):
|
|
def __init__(self, obj):
|
|
''' Initialize the Area object '''
|
|
ArchComponent.Component.__init__(self, obj)
|
|
|
|
self.oldMPPTs = 0
|
|
|
|
self.Type = None
|
|
self.obj = None
|
|
self.setProperties(obj)
|
|
|
|
def setProperties(self, obj):
|
|
pl = obj.PropertiesList
|
|
|
|
if not "File" in pl:
|
|
obj.addProperty("App::PropertyFile",
|
|
"File",
|
|
"Inverter",
|
|
"The base file this component is built upon")
|
|
|
|
if not ("MPPTs" in pl):
|
|
obj.addProperty("App::PropertyQuantity",
|
|
"MPPTs",
|
|
"Inverter",
|
|
"Points that define the area"
|
|
).MPPTs = 0
|
|
|
|
if not ("Generator" in pl):
|
|
obj.addProperty("App::PropertyEnumeration",
|
|
"Generator",
|
|
"Inverter",
|
|
"Points that define the area"
|
|
).Generator = ["Generic", "Library"]
|
|
obj.Generator = "Generic"
|
|
|
|
if not ("Type" in pl):
|
|
obj.addProperty("App::PropertyString",
|
|
"Type",
|
|
"Base",
|
|
"Points that define the area"
|
|
).Type = "InverterBase"
|
|
obj.setEditorMode("Type", 1)
|
|
|
|
self.Type = obj.Type
|
|
obj.Proxy = self
|
|
|
|
def onDocumentRestored(self, obj):
|
|
""" Method run when the document is restored """
|
|
self.setProperties(obj)
|
|
|
|
def onBeforeChange(self, obj, prop):
|
|
|
|
if prop == "MPPTs":
|
|
self.oldMPPTs = int(obj.MPPTs)
|
|
|
|
def onChanged(self, obj, prop):
|
|
''' '''
|
|
|
|
if prop == "Generator":
|
|
if obj.Generator == "Generic":
|
|
obj.setEditorMode("MPPTs", 0)
|
|
else:
|
|
obj.setEditorMode("MPPTs", 1)
|
|
|
|
if prop == "MPPTs":
|
|
''' '''
|
|
if self.oldMPPTs > obj.MPPTs:
|
|
''' borrar sobrantes '''
|
|
obj.removeProperty()
|
|
|
|
elif self.oldMPPTs < obj.MPPTs:
|
|
''' crear los faltantes '''
|
|
for i in range(self.oldMPPTs, int(obj.MPPTs)):
|
|
''' '''
|
|
print(i)
|
|
else:
|
|
pass
|
|
|
|
if (prop == "File") and obj.File:
|
|
''' '''
|
|
|
|
def execute(self, obj):
|
|
''' '''
|
|
# obj.Shape: compound
|
|
# |- body: compound
|
|
# |-- inverter: solid
|
|
# |-- door: solid
|
|
# |-- holder: solid
|
|
|
|
# |- connectors: compound
|
|
# |-- DC: compound
|
|
# |--- MPPT 1..x: compound
|
|
# |---- positive: compound
|
|
# |----- connector 1..y: ??
|
|
# |---- negative 1..y: compound
|
|
# |----- connector 1..y: ??
|
|
# |-- AC: compound
|
|
# |--- R,S,T,: ??
|
|
# |-- Communication
|
|
|
|
pl = obj.Placement
|
|
filename = self.getFile(obj)
|
|
if filename:
|
|
parts = self.getPartsList(obj)
|
|
if parts:
|
|
zdoc = zipfile.ZipFile(filename)
|
|
if zdoc:
|
|
f = zdoc.open(parts[list(parts.keys())[-1]][1])
|
|
shapedata = f.read()
|
|
f.close()
|
|
shapedata = shapedata.decode("utf8")
|
|
shape = self.cleanShape(shapedata, obj, parts[list(parts.keys())[-1]][2])
|
|
obj.Shape = shape
|
|
if not pl.isIdentity():
|
|
obj.Placement = pl
|
|
obj.MPPTs = len(shape.SubShapes[1].SubShapes[0].SubShapes)
|
|
|
|
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 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 os.path.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 os.path.exists(altfile):
|
|
return 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 os.path.exists(altfile):
|
|
return altfile
|
|
return None
|
|
return 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 "<Object name=" in line:
|
|
n = re.findall('name=\"(.*?)\"',line)
|
|
if n:
|
|
name = n[0]
|
|
elif "<Property name=\"Label\"" in line:
|
|
writemode = True
|
|
elif writemode and "<String value=" in line:
|
|
n = re.findall('value=\"(.*?)\"',line)
|
|
if n:
|
|
label = n[0]
|
|
writemode = False
|
|
elif "<Property name=\"Shape\" type=\"Part::PropertyPartShape\"" in line:
|
|
writemode = True
|
|
elif writemode and "<Part file=" in line:
|
|
n = re.findall('file=\"(.*?)\"',line)
|
|
if n:
|
|
part = n[0]
|
|
writemode = False
|
|
elif "<Property name=\"MaterialsTable\" type=\"App::PropertyMap\"" in line:
|
|
writemode = True
|
|
elif writemode and "<Item key=" in line:
|
|
n = re.findall('key=\"(.*?)\"',line)
|
|
v = re.findall('value=\"(.*?)\"',line)
|
|
if n and v:
|
|
materials[n[0]] = v[0]
|
|
elif writemode and "</Map>" in line:
|
|
writemode = False
|
|
elif "</Object>" 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 ("<ViewProvider name=" in line) and (part in line):
|
|
writemode1 = True
|
|
elif writemode1 and ("<Property name=\"DiffuseColor\"" in line):
|
|
writemode1 = False
|
|
writemode2 = True
|
|
elif writemode2 and ("<ColorList file=" in line):
|
|
n = re.findall('file=\"(.*?)\"',line)
|
|
if n:
|
|
colorfile = n[0]
|
|
break
|
|
if not colorfile:
|
|
return None
|
|
if not colorfile in zdoc.namelist():
|
|
return None
|
|
colors = []
|
|
cf = zdoc.open(colorfile)
|
|
buf = cf.read()
|
|
cf.close()
|
|
for i in range(1,int(len(buf)/4)):
|
|
colors.append((buf[i*4+3]/255.0,buf[i*4+2]/255.0,buf[i*4+1]/255.0,buf[i*4]/255.0))
|
|
if colors:
|
|
return colors
|
|
return None
|
|
|
|
def splitall(self,path):
|
|
|
|
"splits a path between its components"
|
|
|
|
allparts = []
|
|
while 1:
|
|
parts = os.path.split(path)
|
|
if parts[0] == path: # sentinel for absolute paths
|
|
allparts.insert(0, parts[0])
|
|
break
|
|
elif parts[1] == path: # sentinel for relative paths
|
|
allparts.insert(0, parts[1])
|
|
break
|
|
else:
|
|
path = parts[0]
|
|
allparts.insert(0, parts[1])
|
|
return allparts
|
|
|
|
class ViewProviderStringInverter(ArchComponent.ViewProviderComponent):
|
|
def __init__(self, vobj):
|
|
ArchComponent.ViewProviderComponent.__init__(self, vobj)
|
|
|
|
def getIcon(self):
|
|
return str(os.path.join(PVPlantResources.DirIcons, "Inverter.svg"))
|
|
|
|
|
|
class CommandStringInverter:
|
|
|
|
def GetResources(self):
|
|
return {'Pixmap': str(os.path.join(PVPlantResources.DirIcons, "Inverter.svg")),
|
|
'Accel': "E, I",
|
|
'MenuText': "String Inverter",
|
|
'ToolTip': "String Placement",}
|
|
|
|
def Activated(self):
|
|
sinverter = makeStringInverter()
|
|
|
|
def IsActive(self):
|
|
active = not (FreeCAD.ActiveDocument is None)
|
|
return active
|
|
|
|
if FreeCAD.GuiUp:
|
|
FreeCADGui.addCommand('StringInverter', CommandStringInverter())
|
|
|