#*************************************************************************** #* * #* Copyright (c) 2015 - Victor Titov (DeepSOIC) * #* * #* * #* 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__="Value Series generator module" __author__ = "DeepSOIC" __url__ = "" __doc__ = "Value Series generator module: utility module to attach value generator to document object" import math import lattice2Executer from lattice2Common import ParaConfusion, screen class ValueSeriesGenerator: mode_userfriendly_names = { 'SpanN': "Span / N", 'StepN': "N * Step", 'SpanStep': "Span / Step", 'Random': "random", } gen_modes = ['SpanN','StepN','SpanStep', 'Random'] def __init__(self, docObj): self.documentObject = docObj self.source_modes = ["Values Property","Spreadsheet", "Generator"] self.gen_laws = ['Linear','Exponential'] self.alignment_modes = ['Low', 'Center', 'High', 'Justify', 'Mirrored'] self.readonlynessDict = {} # key = property name (string). Value = boolean (True == writable, non-readonly). Stores property read-only status requested by external code. def addProperties(self, groupname, groupname_gen, valuesdoc, valuestype = 'App::PropertyFloat'): # _addProperty(proptype , propname , defvalue, group, tooltip) # first, try to guess interface version. If we are re-adding properties to old feature, # it already has some other properties, but not Version. So we should default to 0 # in this case. Therwise the Version property already exists, so default desn't matter; # or we are creating a new generator, so default to 1. self._addProperty("App::PropertyInteger" ,"VSGVersion" , 0 if hasattr(self.documentObject, "Values") else 1 , groupname_gen, "Interface version") self.documentObject.setEditorMode("VSGVersion", 2) #hide this property self._addProperty("App::PropertyStringList" ,"Values" , None, groupname, valuesdoc) self._addProperty("App::PropertyEnumeration","ValuesSource" , self.source_modes, groupname, "Select where to take the value series from.") self._addProperty("App::PropertyLink" ,"SpreadsheetLink", None, groupname, "Link to spreadsheet to take values from.") self._addProperty("App::PropertyString" ,"CellStart" , 'A1', groupname, "Starting cell of first value (the rest are scanned downwards till an empty cell is encountered)") self._addProperty("App::PropertyEnumeration","GeneratorMode" , self.gen_modes, groupname_gen,"") self._addProperty("App::PropertyEnumeration","DistributionLaw", self.gen_laws, groupname_gen,"") self._addProperty(valuestype ,"SpanStart" , 1.0, groupname_gen, "Starting value for value series generator") self._addProperty(valuestype ,"SpanEnd" , 7.0, groupname_gen, "Ending value for value series generator") self._addProperty("App::PropertyBool" ,"EndInclusive" , True, groupname_gen, "If True, the last value in series will equal SpanEnd. If False, the value equal to SpanEnd will be dropped.") self._addProperty("App::PropertyEnumeration","Alignment" , self.alignment_modes,groupname_gen, "Sets how to align the values within span.") self._addProperty("App::PropertyFloat" ,"Step" , 1.0, groupname_gen, "Step for value generator. For exponential law, it is a natural logarithm of change ratio.") # using float for Step, because step's unit depends n selected distribution law self._addProperty("App::PropertyFloat" ,"Count" , 7.0, groupname_gen, "Number of values to generate") self._addProperty("App::PropertyFloat" ,"Offset" , 0.0, groupname_gen, "Extra offset for the series, expressed as fraction of step.") def _addProperty(self, proptype, propname, defvalue, group, tooltip): if hasattr(self.documentObject, propname): return self.documentObject.addProperty(proptype, propname, group, tooltip) if defvalue is not None: setattr(self.documentObject, propname, defvalue) def updateReadonlyness(self): obj = self.documentObject m = obj.GeneratorMode src = obj.ValuesSource self._setPropertyWritable("Values" , src == "Values Property" ) self._setPropertyWritable("ValuesSource" , True ) self._setPropertyWritable("SpreadsheetLink" , src == "Spreadsheet" ) self._setPropertyWritable("CellStart" , src == "Spreadsheet" ) self._setPropertyWritable("GeneratorMode" , not self.isPropertyControlledByGenerator("GeneratorMode" ) ) self._setPropertyWritable("DistributionLaw" , not self.isPropertyControlledByGenerator("DistributionLaw") ) self._setPropertyWritable("SpanStart" , not self.isPropertyControlledByGenerator("SpanStart" ) ) self._setPropertyWritable("SpanEnd" , not self.isPropertyControlledByGenerator("SpanEnd" ) ) self._setPropertyWritable("EndInclusive" , not self.isPropertyControlledByGenerator("EndInclusive" ) ) self._setPropertyWritable("Alignment" , not self.isPropertyControlledByGenerator("Alignment" ) and m != "Random" ) self._setPropertyWritable("Step" , not self.isPropertyControlledByGenerator("Step" ) ) self._setPropertyWritable("Count" , not self.isPropertyControlledByGenerator("Count" ) ) self._setPropertyWritable("Offset" , not self.isPropertyControlledByGenerator("Offset" ) ) def isPropertyControlledByGenerator(self, propname): obj = self.documentObject if not hasattr(obj, propname): raise AttributeError(obj.Name+": has no property named "+propname) m = obj.GeneratorMode genOn = obj.ValuesSource == "Generator" if not genOn: return False if propname == "SpanStart": return False elif propname == "SpanEnd": return False elif propname == "Step": return m == "SpanN" elif propname == "Count": return m == "SpanStep" else: return False def setPropertyWritable(self, propname, bool_writable): '''setPropertyWritable(self, propname, bool_writable): Use to force a property read-only (for example, when the property is driven by a link). If set to be writable, the read-onlyness will be set according to series generator logic.''' self.readonlynessDict[propname] = bool_writable def _setPropertyWritable(self, propname, bool_writable, suppress_warning = False): if propname in self.readonlynessDict: bool_writable = bool_writable and self.readonlynessDict[propname] self.documentObject.setEditorMode(propname, 0 if bool_writable else 1) def execute(self): obj = self.documentObject #shortcut values = [] #list to be filled with values, that are giong to be written to obj.Values if obj.ValuesSource == "Generator": #read out span and convert it to linear law if obj.DistributionLaw == 'Linear': vStart = float(obj.SpanStart) vEnd = float(obj.SpanEnd) vStep = float(obj.Step) elif obj.DistributionLaw == 'Exponential': vSign = 1 if obj.SpanStart > 0.0 else -1.0 vStart = math.log(obj.SpanStart * vSign) if obj.SpanEnd * vSign < ParaConfusion: raise ValueError(obj.Name+": Wrong SpanEnd value. It is either zero, or of different sign compared to SpanStart. In exponential distribution, it is not allowed.") vEnd = math.log(obj.SpanEnd * vSign) vStep = float(obj.Step) else: raise ValueError(obj.Name+": distribution law not implemented: "+obj.DistributionLaw) if obj.GeneratorMode == 'SpanN': n = obj.Count if obj.EndInclusive: n -= 1 if n == 0: n = 1 vStep = (vEnd - vStart)/n obj.Step = vStep elif obj.GeneratorMode == 'StepN': if obj.VSGVersion < 1: #old behavior: update span to match the end of array n = obj.Count if obj.EndInclusive: n -= 1 vEnd = vStart + float(vStep)*n if obj.DistributionLaw == 'Linear': obj.SpanEnd = vEnd elif obj.DistributionLaw == 'Exponential': obj.SpanEnd = math.exp(vEnd)*vSign else: raise ValueError(obj.Name+": distribution law not implemented: "+obj.DistributionLaw) else: # new behavior: keep span intact, as it can be used for alignment pass elif obj.GeneratorMode == 'SpanStep': nfloat = float((vEnd - vStart) / vStep) n = math.trunc(nfloat - ParaConfusion) + 1 if obj.EndInclusive and abs(nfloat-round(nfloat)) <= ParaConfusion: n = n + 1 obj.Count = n elif obj.GeneratorMode == 'Random': pass else: raise ValueError(obj.Name+": Generator mode "+obj.GeneratorMode+" is not implemented") # Generate the actual array. We can use Step and N directly to # completely avoid mode logic, since we had updated them # cache properties into variables # vStart,vEnd are already in sync vStep = float(obj.Step) vOffset = float(obj.Offset) n = int(obj.Count) # Generate the values if obj.GeneratorMode == 'Random': import random list_evenDistrib = [vStart + vOffset*vStep + (vEnd-vStart)*random.random() for i in range(0, n)] else: # preprocess for alignment alignment_offset = 0.0 vStep_justified = vStep if obj.Alignment != "Low" and n>0: v_first = vStart v_last = vStep*(n) if obj.Alignment == "Justify" and obj.EndInclusive == False else vStep*(n-1) if obj.Alignment == "High": alignment_offset = (vEnd-v_last) elif obj.Alignment == "Center": alignment_offset = (vEnd-v_last)*0.5 elif obj.Alignment == "Justify": #replica of SpanN logic n_tmp = n if obj.EndInclusive: n_tmp -= 1 if n_tmp == 0: n_tmp = 1 #justify failed! vStep_justified = (vEnd - vStart)/n_tmp list_evenDistrib = [vStart + vOffset*vStep + alignment_offset + vStep_justified*i for i in range(0, n)] #post-process alignment if obj.Alignment == "Mirrored": new_list = [] for v in list_evenDistrib: new_list.append(v) if abs(v) > 1e-12: new_list.append(-v) list_evenDistrib = new_list if obj.DistributionLaw == 'Linear': values = list_evenDistrib elif obj.DistributionLaw == 'Exponential': values = [math.exp(v)*vSign for v in list_evenDistrib] else: raise ValueError(obj.Name+": distribution law not implemented: "+obj.DistributionLaw) elif obj.ValuesSource == "Spreadsheet": #parse address addr = obj.CellStart #assuming only two letter column if addr[1].isalpha(): col = addr[0:2] row = addr[2:] else: col = addr[0:1] row = addr[1:] row = int(row) #loop until the value can't be read out values = [] spsh = screen(obj.SpreadsheetLink) while True: try: values.append( spsh.get(col+str(row)) ) except ValueError: break row += 1 elif obj.ValuesSource == "Values Property": pass else: raise ValueError(obj.Name+": values source mode not implemented: "+obj.ValuesSource) # finally. Fill in the values. if obj.ValuesSource != "Values Property": obj.Values = [str(v) for v in values]