| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| __title__ = "FreeCAD FEM solver Elmer sifio" |
| __author__ = "Markus Hovorka" |
| __url__ = "https://www.freecad.org" |
|
|
| |
| |
|
|
| import collections |
| from FreeCAD import Units |
|
|
| SIMULATION = "Simulation" |
| CONSTANTS = "Constants" |
| BODY = "Body" |
| MATERIAL = "Material" |
| BODY_FORCE = "Body Force" |
| EQUATION = "Equation" |
| SOLVER = "Solver" |
| BOUNDARY_CONDITION = "Boundary Condition" |
| INITIAL_CONDITION = "Initial Condition" |
| COMPONENT = "Component" |
|
|
|
|
| _VALID_SECTIONS = ( |
| SIMULATION, |
| CONSTANTS, |
| BODY, |
| MATERIAL, |
| BODY_FORCE, |
| EQUATION, |
| SOLVER, |
| BOUNDARY_CONDITION, |
| INITIAL_CONDITION, |
| COMPONENT, |
| ) |
|
|
| _NUMBERED_SECTIONS = ( |
| BODY, |
| MATERIAL, |
| BODY_FORCE, |
| EQUATION, |
| SOLVER, |
| BOUNDARY_CONDITION, |
| INITIAL_CONDITION, |
| COMPONENT, |
| ) |
|
|
| _SECTION_DELIM = "End" |
| _WHITESPACE = " " |
| _INDENT = " " * 2 |
| _NEWLINE = "\n" |
|
|
| _TYPE_REAL = "Real" |
| _TYPE_INTEGER = "Integer" |
| _TYPE_LOGICAL = "Logical" |
| _TYPE_STRING = "String" |
| _TYPE_FILE = "File" |
| _TYPE_VARIABLE = "Variable" |
|
|
| WARN = '"Warn"' |
| IGNORE = '"Ignore"' |
| ABORT = '"Abort"' |
| SILENT = '"Silent"' |
|
|
|
|
| def createSection(name): |
| section = Section(name) |
| if not isValid(section): |
| raise ValueError("Invalid section name: %s" % name) |
| return section |
|
|
|
|
| def writeSections(sections, stream): |
| ids = _IdManager() |
| _Writer(ids, sections, stream).write() |
|
|
|
|
| def isNumbered(section): |
| return section.name in _NUMBERED_SECTIONS |
|
|
|
|
| def isValid(section): |
| return section.name in _VALID_SECTIONS |
|
|
|
|
| class Builder: |
|
|
| _ACTIVE_SOLVERS = "Active Solvers" |
|
|
| def __init__(self): |
| self._customSections = [] |
| self._simulation = createSection(SIMULATION) |
| self._constants = createSection(CONSTANTS) |
| self._bodies = collections.OrderedDict() |
| self._boundaries = collections.OrderedDict() |
|
|
| def getBoundaryNames(self): |
| return self._boundaries.keys() |
|
|
| def getBodyNames(self): |
| return self._bodies.keys() |
|
|
| def simulation(self, key, attr): |
| self._simulation[key] = attr |
|
|
| def constant(self, key, attr): |
| self._constants[key] = attr |
|
|
| def initial(self, body, key, attr): |
| section = self._getFromBody(body, INITIAL_CONDITION) |
| section[key] = attr |
|
|
| def material(self, body, key, attr): |
| section = self._getFromBody(body, MATERIAL) |
| section[key] = attr |
|
|
| def equation(self, body, key, attr): |
| section = self._getFromBody(body, EQUATION) |
| section[key] = attr |
|
|
| def bodyForce(self, body, key, attr): |
| section = self._getFromBody(body, BODY_FORCE) |
| section[key] = attr |
|
|
| def addSolver(self, body, solverSection): |
| section = self._getFromBody(body, EQUATION) |
| if self._ACTIVE_SOLVERS not in section: |
| section[self._ACTIVE_SOLVERS] = [] |
| section[self._ACTIVE_SOLVERS].append(solverSection) |
|
|
| def boundary(self, boundary, key, attr): |
| if boundary not in self._boundaries: |
| self._boundaries[boundary] = createSection(BOUNDARY_CONDITION) |
| self._boundaries[boundary][key] = attr |
|
|
| def addSection(self, section): |
| if section not in self._customSections: |
| self._customSections.append(section) |
|
|
| def _getFromBody(self, body, key): |
| if body not in self._bodies: |
| self._bodies[body] = createSection(BODY) |
| if key not in self._bodies[body]: |
| self._bodies[body][key] = createSection(key) |
| return self._bodies[body][key] |
|
|
| def __iter__(self): |
| allSections = list(self._customSections) |
| allSections.append(self._simulation) |
| allSections.append(self._constants) |
| for name, section in self._bodies.items(): |
| section["Name"] = name |
| allSections.append(section) |
| if MATERIAL in section and section[MATERIAL] not in allSections: |
| allSections.append(section[MATERIAL]) |
| if EQUATION in section and section[EQUATION] not in allSections: |
| eqSection = section[EQUATION] |
| allSections.append(eqSection) |
| if self._ACTIVE_SOLVERS in eqSection: |
| for solverSection in eqSection[self._ACTIVE_SOLVERS]: |
| if solverSection not in allSections: |
| allSections.append(solverSection) |
| if BODY_FORCE in section and section[BODY_FORCE] not in allSections: |
| allSections.append(section[BODY_FORCE]) |
| if INITIAL_CONDITION in section and section[INITIAL_CONDITION] not in allSections: |
| allSections.append(section[INITIAL_CONDITION]) |
| for name, section in self._boundaries.items(): |
| section["Name"] = name |
| allSections.append(section) |
| return iter(allSections) |
|
|
|
|
| class Sif: |
|
|
| _CHECK_KEYWORDS = "Check Keywords" |
| _HEADER = "Header" |
| _MESHDB_ATTR = "Mesh DB" |
| _INCLUDE_ATTR = "Include Path" |
| _RESULT_ATTR = "Results Directory" |
|
|
| def __init__(self, sections=[], meshLocation="."): |
| self.sections = sections |
| self.meshPath = meshLocation |
| self.checkKeywords = WARN |
| self.incPath = "" |
| self.resPath = "" |
|
|
| def write(self, stream): |
| self._writeCheckKeywords(stream) |
| stream.write(_NEWLINE * 2) |
| self._writeHeader(stream) |
| stream.write(_NEWLINE * 2) |
| writeSections(self.sections, stream) |
|
|
| def _writeCheckKeywords(self, stream): |
| stream.write(self._CHECK_KEYWORDS) |
| stream.write(_WHITESPACE) |
| stream.write(self.checkKeywords) |
|
|
| def _writeHeader(self, stream): |
| stream.write(self._HEADER) |
| stream.write(_NEWLINE) |
| self._writeAttr(self._MESHDB_ATTR, self.meshPath, stream) |
| stream.write(_NEWLINE) |
| if self.incPath: |
| self._writeAttr(self._INCLUDE_ATTR, self.incPath, stream) |
| stream.write(_NEWLINE) |
| if self.resPath: |
| self._writeAttr(self._RESULT_ATTR, self.resPath, stream) |
| stream.write(_NEWLINE) |
| stream.write(_SECTION_DELIM) |
|
|
| def _writeAttr(self, name, value, stream): |
| stream.write(_INDENT) |
| stream.write(name) |
| stream.write(_WHITESPACE) |
| stream.write('"%s"' % value) |
|
|
|
|
| class Section: |
|
|
| def __init__(self, name): |
| self.name = name |
| self.priority = 0 |
| self._attrs = dict() |
|
|
| def __setitem__(self, key, value): |
| self._attrs[key] = value |
|
|
| def __getitem__(self, key): |
| return self._attrs[key] |
|
|
| def __delitem__(self, key): |
| del self._attrs[key] |
|
|
| def __iter__(self): |
| return self._attrs.items() |
|
|
| def keys(self): |
| return self._attrs.keys() |
|
|
| def __contains__(self, item): |
| return item in self._attrs |
|
|
| def __str__(self): |
| return str(self._attrs) |
|
|
| def __repr__(self): |
| return self.name |
|
|
|
|
| class FileAttr(str): |
| pass |
|
|
|
|
| class _Writer: |
|
|
| def __init__(self, idManager, sections, stream): |
| self._idMgr = idManager |
| self._sections = sections |
| self._stream = stream |
|
|
| def write(self): |
| firstSection, *sortedSections = sorted( |
| self._sections, key=lambda s: s.priority, reverse=True |
| ) |
| self._writeSection(firstSection) |
| for s in sortedSections: |
| self._stream.write(_NEWLINE) |
| self._writeSection(s) |
|
|
| def _writeSection(self, s): |
| self._writeSectionHeader(s) |
| self._writeSectionBody(s) |
| self._writeSectionFooter(s) |
| self._stream.write(_NEWLINE) |
|
|
| def _writeSectionHeader(self, s): |
| self._stream.write(s.name) |
| if isNumbered(s): |
| self._stream.write(_WHITESPACE) |
| self._stream.write(str(self._idMgr.getId(s))) |
|
|
| def _writeSectionFooter(self, s): |
| self._stream.write(_NEWLINE) |
| self._stream.write(_SECTION_DELIM) |
|
|
| def _writeSectionBody(self, s): |
| for key in sorted(s.keys()): |
| self._writeAttribute(key, s[key]) |
|
|
| def _writeAttribute(self, key, data): |
| if isinstance(data, Section): |
| self._stream.write(_NEWLINE) |
| self._writeScalarAttr(key, data) |
| elif isinstance(data, FileAttr): |
| self._stream.write(_NEWLINE) |
| self._writeFileAttr(key, data) |
| elif self._isCollection(data): |
| if len(data) == 1: |
| scalarData = self._getOnlyElement(data) |
| self._stream.write(_NEWLINE) |
| self._writeScalarAttr(key, scalarData) |
| elif len(data) > 1: |
| self._stream.write(_NEWLINE) |
| self._writeArrAttr(key, data) |
| else: |
| self._stream.write(_NEWLINE) |
| self._writeScalarAttr(key, data) |
|
|
| def _getOnlyElement(self, collection): |
| it = iter(collection) |
| return next(it) |
|
|
| def _isCollection(self, data): |
| return not isinstance(data, str) and isinstance(data, collections.abc.Iterable) |
|
|
| def _checkScalar(self, dataType): |
| if issubclass(dataType, int): |
| return self._genIntAttr |
| if issubclass(dataType, float): |
| return self._genFloatAttr |
| if issubclass(dataType, bool): |
| return self._genBoolAttr |
| if issubclass(dataType, str): |
| return self._genStrAttr |
| return None |
|
|
| def _writeScalarAttr(self, key, data): |
| attrType = self._getAttrTypeScalar(data) |
| if attrType is None: |
| raise ValueError("Unsupported data type: %s" % type(data)) |
| self._stream.write(_INDENT) |
| self._stream.write(key) |
| self._stream.write(_WHITESPACE) |
| self._stream.write("=") |
| self._stream.write(_WHITESPACE) |
| |
| if attrType is _TYPE_STRING: |
| if data.startswith("Variable"): |
| attrType = _TYPE_VARIABLE |
| if attrType is not _TYPE_VARIABLE: |
| self._stream.write(attrType) |
| self._stream.write(_WHITESPACE) |
| output = self._preprocess(data, type(data)) |
| |
| if attrType is _TYPE_VARIABLE: |
| output = output.lstrip('"') |
| |
| output = output[:-1] |
| self._stream.write(output) |
|
|
| def _writeArrAttr(self, key, data): |
| attrType = self._getAttrTypeArr(data) |
| self._stream.write(_INDENT) |
| self._stream.write(key) |
| self._stream.write("(%d)" % len(data)) |
| self._stream.write(_WHITESPACE) |
| self._stream.write("=") |
| self._stream.write(_WHITESPACE) |
| |
| if attrType is _TYPE_STRING: |
| if data.startswith("Variable"): |
| attrType = _TYPE_VARIABLE |
| if attrType is not _TYPE_VARIABLE: |
| self._stream.write(attrType) |
| for val in data: |
| self._stream.write(_WHITESPACE) |
| output = self._preprocess(val, type(val)) |
| |
| if attrType is _TYPE_VARIABLE: |
| output = output.lstrip('"') |
| |
| output = output[:-1] |
| self._stream.write(output) |
|
|
| def _writeFileAttr(self, key, data): |
| self._stream.write(_INDENT) |
| self._stream.write(key) |
| self._stream.write(_WHITESPACE) |
| self._stream.write("=") |
| self._stream.write(_WHITESPACE) |
| self._stream.write(_TYPE_FILE) |
| for val in data.split("/"): |
| if val: |
| self._stream.write(_WHITESPACE) |
| self._stream.write('"%s"' % val) |
|
|
| def _getSifDataType(self, dataType): |
| if issubclass(dataType, Section): |
| return _TYPE_INTEGER |
| elif issubclass(dataType, bool): |
| return _TYPE_LOGICAL |
| elif issubclass(dataType, int): |
| return _TYPE_INTEGER |
| elif issubclass(dataType, float): |
| return _TYPE_REAL |
| elif issubclass(dataType, str): |
| return _TYPE_STRING |
| elif issubclass(dataType, Units.Quantity): |
| return _TYPE_REAL |
| else: |
| raise ValueError("Unsupported data type: %s" % dataType) |
|
|
| def _preprocess(self, data, dataType): |
| if issubclass(dataType, Section): |
| return str(self._idMgr.getId(data)) |
| elif issubclass(dataType, str): |
| return '"%s"' % data |
| elif issubclass(dataType, Units.Quantity): |
| return f"{data.Value:.13g}" |
| else: |
| return str(data) |
|
|
| def _getAttrTypeScalar(self, data): |
| return self._getSifDataType(type(data)) |
|
|
| def _getAttrTypeArr(self, data): |
| if not data: |
| raise ValueError("Collections must not be empty.") |
| it = iter(data) |
| dataType = type(next(it)) |
| for entry in it: |
| if not isinstance(entry, dataType): |
| raise ValueError("Collection must be homogenueous") |
| return self._getSifDataType(dataType) |
|
|
|
|
| class _IdManager: |
|
|
| def __init__(self, firstId=1): |
| self._pool = dict() |
| self._ids = dict() |
| self.firstId = firstId |
|
|
| def setId(self, section): |
| if section.name not in self._pool: |
| self._pool[section.name] = self.firstId |
| self._ids[section] = self._pool[section.name] |
| self._pool[section.name] += 1 |
|
|
| def getId(self, section): |
| if section not in self._ids: |
| self.setId(section) |
| return self._ids[section] |
|
|
|
|
| |
|
|