FEM-Design 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,454 @@
1
+ import subprocess
2
+ from datetime import datetime
3
+ from time import sleep
4
+
5
+ from enum import Enum
6
+ from femdesign.calculate.analysis import Analysis, Design
7
+ from femdesign.calculate.fdscript import Fdscript
8
+ from femdesign.utilities.filehelper import OutputFileHelper
9
+ from femdesign.calculate.command import DesignModule, CmdUser, CmdCalculation, CmdListGen, CmdOpen, CmdProjDescr, CmdSave
10
+
11
+ import win32file
12
+ import win32pipe
13
+
14
+ import os
15
+
16
+ """
17
+ FEM - Design usage with pipe
18
+
19
+ To initiate :
20
+ 1 : create a WIN32 named pipe for duplex mode, message oriented
21
+ 1a : optional : create another pipe for back channel, named appending 'b'.
22
+ 2 : launch FD with command line
23
+ / p Name
24
+ passing the name you used at creation.FD will open it right at start and exit if can't
25
+ after successful open it listens to commands while the usual interface is active
26
+ you can combine it with the windowless / minimized mode to hide the window
27
+ it also attaches to the back channel pipe at this moment, if unable, all output is permanently disabled
28
+ 3 : send commands through the pipe
29
+ 4 : FD will exit if 'exit' command received or the pipe is closed on this end
30
+
31
+ FD only reads the main pipe and only writes the back channel(if supplied), allowing this end to never
32
+ read.While the pipe is duplexand can be used in both direction, if it gets clogged in
33
+ one direction(by not reading what the other end sends), the write can get blocked too.
34
+ The document recommends using another pipe for a back channel.
35
+ By default nothing is written to the back channel, you need to set output level or commands with implicit reply.
36
+ FD buffers all outgoing messages till they can be sent over, if this end is lazy to read it will not clog,
37
+ however they will accumulate in memory.
38
+
39
+
40
+
41
+ Messages are text in 9bit ANSI(codepage), limited to 4096 bytes
42
+
43
+ The command format is
44
+ [!]cmd[space][args]
45
+ there is no delimiter at the end, the pipe message counts.
46
+ FD reads the pipe immediatelyand puts the commands in a queue.The queue is processed when it's READYSTATE
47
+ for another command, finishing execution of the previous or a running script.
48
+ The !requests out - of bound execution.That is not suppoerted by very command and mainly
49
+ serves to manipulate the queue itself, verbosity or check the communicaiton is alive.
50
+
51
+ Commands:
52
+
53
+ exit
54
+ stop the FD process
55
+
56
+ detach
57
+ close the pipe and continue IN normal interface mode
58
+
59
+ clear[in | out]
60
+ flush the FD mesage queue for the direction, both without parameters
61
+ has no Effect on what is already issued to the pipe
62
+
63
+ echo[txt]
64
+ write txt to output
65
+
66
+ stat
67
+ write queueand processing status to output
68
+
69
+ v[N]
70
+ set verbosity control (bits)
71
+ 1: enable basic output
72
+ 2: echo all INPUT commands
73
+ 4: FD log Lines
74
+ 8: script log lines
75
+ *16: calculation window messages (except fortran)
76
+ *32: progress window title
77
+
78
+ echo and stat always cretes output, otherwise nothing is written aT V = 0
79
+ * not yet supported
80
+
81
+ run[scriptfile]
82
+ execute script as from tools / run script menu
83
+ *Note: When using Unicode commands, add the suffix 'UTF8'. E.g.: "runUTF8 [scriptfile]".
84
+
85
+ cmd[command]
86
+ execute command as if typed into the command window -- No warranty!!!
87
+ *Note: When using Unicode commands, add the suffix 'UTF8'. E.g.: "cmdUTF8 [command]".
88
+
89
+ esc
90
+ press Escape during calculation to break it
91
+
92
+ """
93
+
94
+
95
+ def GetElapsedTime(start_time):
96
+ return (datetime.now() - start_time).total_seconds()
97
+
98
+
99
+ class _FdConnect:
100
+
101
+ def __exit__(self, exc_type, exc_value, traceback):
102
+ self.Detach()
103
+ self.ClosePipe()
104
+
105
+ def __init__(self, pipe_name="FdPipe1"):
106
+ """
107
+ Creating fd pipe
108
+ One fd connection for the life of the fem design until closure.
109
+ It could be more than one with given uniqe pipe_name.
110
+ """
111
+ self.pipe_name = pipe_name
112
+ self.pipe_send = self._CreatePipe(pipe_name)
113
+ self.pipe_read = self._CreatePipe(f"{pipe_name}b")
114
+ self.start_time = None
115
+ self._log_message_history = []
116
+
117
+ @staticmethod
118
+ def _CreatePipe(name):
119
+ return win32pipe.CreateNamedPipe(
120
+ r"\\.\pipe\%s" % name,
121
+ win32pipe.PIPE_ACCESS_DUPLEX,
122
+ win32pipe.PIPE_TYPE_MESSAGE
123
+ | win32pipe.PIPE_READMODE_MESSAGE
124
+ | win32pipe.PIPE_NOWAIT,
125
+ win32pipe.PIPE_UNLIMITED_INSTANCES,
126
+ 4096,
127
+ 4096,
128
+ 0,
129
+ None,
130
+ )
131
+
132
+ def _ConnectPipe(self, timeout=60):
133
+ start_time = datetime.now()
134
+ while GetElapsedTime(start_time) <= timeout:
135
+ try:
136
+ win32pipe.ConnectNamedPipe(self.pipe_send, None)
137
+ win32pipe.ConnectNamedPipe(self.pipe_read, None)
138
+ return
139
+ except:
140
+ sleep(0.5)
141
+ raise TimeoutError(f"Program not connected in {timeout} second")
142
+
143
+ def Start(self, fd_path, timeout=60):
144
+ """
145
+ Start and connect of fem-design with specified path
146
+ """
147
+ self.process = subprocess.Popen([fd_path, "/p", self.pipe_name])
148
+ self._ConnectPipe(timeout=timeout)
149
+
150
+ def _Read(self):
151
+ """
152
+ Read one message if exists
153
+ """
154
+ try:
155
+ return win32file.ReadFile(self.pipe_read, 4096)[1].decode()
156
+ except Exception as err:
157
+ if err.winerror == 232: # Error text (The pipe is being closed.) Pipe is empty
158
+ return None
159
+ elif err.winerror == 109: # Error text (The pipe has been ended.) Conecction lost
160
+ raise AssertionError("Connection lost")
161
+ else:
162
+ raise err
163
+
164
+ def ReadAll(self):
165
+ """
166
+ Read all existing message
167
+ """
168
+ results = []
169
+ while True:
170
+ result = self._Read()
171
+ if result == None:
172
+ return "\n".join(results)
173
+ results.append(result)
174
+ self._log_message_history.append(result)
175
+
176
+ def GetLogMessageHistory(self):
177
+ """
178
+ Return all collected message
179
+ """
180
+ return self._log_message_history
181
+
182
+ def Send(self, message):
183
+ """
184
+ Send one message
185
+ """
186
+ try:
187
+ win32file.WriteFile(self.pipe_send, message.encode())
188
+ except Exception as err:
189
+ if err.winerror == 232: # Error text (The pipe is being closed.) Connection lost
190
+ raise AssertionError("Connection lost")
191
+ else:
192
+ raise err
193
+
194
+ def SendAndReceive(self, message, timeout=None):
195
+ """
196
+ Complete workflow of send message and receive answer until timeout
197
+ """
198
+ self.Send(message)
199
+ start_time = self.start_time or datetime.now()
200
+ while timeout == None or GetElapsedTime(start_time) <= timeout:
201
+ result = self.ReadAll()
202
+ if result:
203
+ return result
204
+ sleep(0.5)
205
+ raise TimeoutError(f"Program not responding after {timeout} second")
206
+
207
+ def Stat(self, timeout=None):
208
+ """
209
+ Return queueand processing status
210
+ """
211
+ return self.SendAndReceive("stat", timeout=timeout)
212
+
213
+ def LogLevel(self, n):
214
+ """
215
+ Set log level
216
+ verbosity control (bits)
217
+ 1: enable basic output
218
+ 2: echo all INPUT commands
219
+ 4: FD log Lines
220
+ 8: script log lines
221
+ *16: calculation window messages (except fortran)
222
+ *32: progress window title
223
+
224
+ echo and stat always creates output, otherwise nothing is written aT V = 0
225
+ * not yet supported
226
+ """
227
+ return self.Send(f"v {n}")
228
+
229
+ def RunScript(self, path, timeout=None):
230
+ """
231
+ Execute script as from tools / run script menu
232
+ """
233
+ self.Send(f"runUTF8 {path}")
234
+ self.start_time = datetime.now()
235
+ while timeout == None or GetElapsedTime(self.start_time) <= timeout:
236
+ sleep(0.1)
237
+ if "script idle" in self.Stat(timeout=timeout):
238
+ self.start_time = None
239
+ return
240
+ raise TimeoutError(f"Too long script run time after {timeout} second")
241
+
242
+ def Cmd(self, cmd_text):
243
+ """
244
+ Execute command as if typed into the command window -- No warranty!!!
245
+ """
246
+ self.Send(f"cmdUTF8 {cmd_text}")
247
+
248
+ def Esc(self):
249
+ """
250
+ Press Escape during calculation to break it
251
+ """
252
+ self.Send("esc")
253
+
254
+ def Exit(self):
255
+ """
256
+ Close fem-design
257
+ """
258
+ self.Send("exit")
259
+
260
+ def Detach(self):
261
+ """
262
+ Disconnect from pipe and close it. You can't reattach to fem-design.
263
+ It must be outside of KillProgramIfExists scope.
264
+ """
265
+ self.Send("detach")
266
+ sleep(2)
267
+ self.ClosePipe()
268
+
269
+ def ClosePipe(self):
270
+ """
271
+ Regular closing of fd pipe
272
+ """
273
+ win32file.CloseHandle(self.pipe_send)
274
+ win32file.CloseHandle(self.pipe_read)
275
+
276
+ def KillProgramIfExists(self):
277
+ """
278
+ Kill program if exists
279
+ """
280
+ if not (self.process.poll()):
281
+ try:
282
+ self.process.kill()
283
+ except WindowsError:
284
+ print("Program gone in meantime")
285
+
286
+
287
+ class Verbosity(Enum):
288
+ BASIC = 1
289
+ ECHO_ALL_INPUT_COMMANDS = 2
290
+ FD_LOG_LINES = 4
291
+ SCRIPT_LOG_LINES = 8
292
+ CALCULATION_WINDOW_MESSAGES = 16
293
+ PROGRESS_WINDOW_TITLE = 32
294
+
295
+
296
+ ## define a private class
297
+
298
+
299
+ class FemDesignConnection(_FdConnect):
300
+ def __init__(self,
301
+ fd_path : str = r"C:\Program Files\StruSoft\FEM-Design 23\fd3dstruct.exe",
302
+ pipe_name : str ="FdPipe1",
303
+ verbose : Verbosity = Verbosity.SCRIPT_LOG_LINES,
304
+ output_dir : str = None,
305
+ minimized : bool = False,):
306
+ super().__init__(pipe_name)
307
+
308
+ self._output_dir = output_dir
309
+
310
+ os.environ["FD_NOLOGO"] = "1"
311
+
312
+ if minimized:
313
+ os.environ["FD_NOGUI"] = "1"
314
+
315
+ self.Start(fd_path)
316
+ self.LogLevel(verbose)
317
+
318
+ @property
319
+ def output_dir(self):
320
+ if self._output_dir == None:
321
+ return os.path.join(os.getcwd(), "FEM-Design API")
322
+ else:
323
+ return os.path.abspath(self._output_dir)
324
+
325
+ @output_dir.setter
326
+ def output_dir(self, value):
327
+ self._output_dir = os.path.abspath(value)
328
+ if not os.path.exists(value):
329
+ os.makedirs(os.path.abspath(value))
330
+
331
+
332
+ def RunScript(self, fdscript : Fdscript, file_name : str = "script"):
333
+ """
334
+
335
+ Args:
336
+ fdscript (Fdscript): fdscript object to run
337
+ file_name (str, optional): file name to save the script. Defaults to "script".
338
+ """
339
+ path = OutputFileHelper.GetFdscriptFilePath(self.output_dir, file_name)
340
+
341
+
342
+ fdscript.serialise_to_file(path)
343
+ super().RunScript(path)
344
+
345
+ def RunAnalysis(self, analysis : Analysis):
346
+ """Run analysis
347
+
348
+ Args:
349
+ analysis (analysis.Analysis): analysis object
350
+ """
351
+ log = OutputFileHelper.GetLogFilePath(self.output_dir)
352
+
353
+ fdscript = Fdscript(log, [CmdUser.ResMode(), CmdCalculation(analysis)])
354
+ self.RunScript(fdscript, "analysis")
355
+
356
+ def RunDesign(self, designMode : DesignModule , design : Design):
357
+ """Run design
358
+
359
+ Args:
360
+ designMode (DesignModule): design module
361
+ design (analysis.Design): design object
362
+ """
363
+ log = OutputFileHelper.GetLogFilePath(self.output_dir)
364
+
365
+ fdscript = Fdscript(log, [CmdUser(designMode.to_user()), CmdCalculation(design)])
366
+ self.RunScript(fdscript, "design")
367
+
368
+ def SetProjectDescription(self, project_name : str = "", project_description : str = "", designer : str = "", signature : str = "", comment : str = "", additional_info : dict = None, read : bool = False, reset : bool = False):
369
+ """Set project description
370
+
371
+ Args:
372
+ project_name (str): project name
373
+ project_description (str): project description
374
+ designer (str): designer
375
+ signature (str): signature
376
+ comment (str): comment
377
+ additional_info (dict): define key-value user data. Defaults to None.
378
+ read (bool, optional): read the project settings. Value will be store in the clipboard. Defaults to False.
379
+ reset (bool, optional): reset the project settings. Defaults to False.
380
+
381
+ Examples
382
+ --------
383
+ >>> pipe = FemDesignConnection(fd_path= r"C:\Program Files\StruSoft\FEM-Design 23\\fd3dstruct.exe",
384
+ minimized= False)
385
+ >>> pipe.SetProjectDescription(project_name="Amazing project",
386
+ project_description="Created through Python",
387
+ designer="Marco Pellegrino Engineer",
388
+ signature="MP",
389
+ comment="Wish for the best",
390
+ project_parameters={"italy": "amazing", "sweden": "amazing_too"})
391
+
392
+ """
393
+ log = OutputFileHelper.GetLogFilePath(self.output_dir)
394
+
395
+ cmd_project = CmdProjDescr(project_name, project_description, designer, signature, comment, additional_info, read, reset)
396
+ fdscript = Fdscript(log, [cmd_project])
397
+ self.RunScript(fdscript, "project_description")
398
+
399
+ def Save(self, file_name : str):
400
+ """Save the project
401
+
402
+ Args:
403
+ file_name (str): name of the file to save
404
+
405
+ Examples
406
+ --------
407
+ >>> pipe = FemDesignConnection(fd_path= "C:\Program Files\StruSoft\FEM-Design 23\\fd3dstruct.exe",
408
+ minimized= False)
409
+ >>> pipe.Save(r"outputFile.str")
410
+ >>> pipe.Save(r"outputFile.struxml")
411
+ """
412
+ log = OutputFileHelper.GetLogFilePath(self.output_dir)
413
+
414
+ cmd_save = CmdSave(file_name)
415
+
416
+ fdscript = Fdscript(log, [cmd_save])
417
+ self.RunScript(fdscript, "save")
418
+
419
+ def Open(self, file_name : str):
420
+ """Open a model from file
421
+
422
+ Args:
423
+ file_name (str): file path to open
424
+
425
+ Raises:
426
+ ValueError: extension must be .struxml or .str
427
+ FileNotFoundError: file not found
428
+ """
429
+ log = OutputFileHelper.GetLogFilePath(self.output_dir)
430
+
431
+ if not file_name.endswith(".struxml") and not file_name.endswith(".str"):
432
+ raise ValueError(f"File {file_name} must have extension .struxml or .str")
433
+ if not os.path.exists(file_name):
434
+ raise FileNotFoundError(f"File {file_name} not found")
435
+
436
+ cmd_open = CmdOpen(file_name)
437
+ fdscript = Fdscript(log, [cmd_open])
438
+ self.RunScript(fdscript, "open")
439
+
440
+ def SetVerbosity(self, verbosity : Verbosity):
441
+ super().LogLevel(verbosity.value)
442
+
443
+ def GenerateListTables(self, bsc_file : str, csv_file : str = None):
444
+ log = OutputFileHelper.GetLogFilePath(self.output_dir)
445
+
446
+ cmd_results = CmdListGen(bsc_file, csv_file)
447
+ fdscript = Fdscript(log, [cmd_results])
448
+ self.RunScript(fdscript, "generate_list_tables")
449
+
450
+
451
+ ## it does not work
452
+ def Disconnect(self):
453
+ super().Detach()
454
+ win32pipe.DisconnectNamedPipe(self.pipe_send)
femdesign/database.py ADDED
@@ -0,0 +1,63 @@
1
+ import xml.etree.ElementTree as ET
2
+ import uuid
3
+ import datetime
4
+
5
+ namespace = {'': 'urn:strusoft'}
6
+
7
+ class Database:
8
+ def __init__(self, country):
9
+ self.struxml_version = "01.00.000"
10
+ self.source_software = f"FEM-Design API SDK {self.get_version()}"
11
+ self.start_time = "1970-01-01T00:00:00.000"
12
+ self.end_time = datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
13
+ self.guid = str(uuid.uuid4())
14
+ self.convert_id = "00000000-0000-0000-0000-000000000000"
15
+ self.standard = "EC"
16
+ self.country = country
17
+ self.end = ""
18
+
19
+ def get_version(self):
20
+ return "0.1.0"
21
+
22
+ @property
23
+ def eurocode(self):
24
+ return self._root.attrib["standard"]
25
+
26
+ @property
27
+ def country(self):
28
+ return self._root.attrib["country"]
29
+
30
+ @property
31
+ def source_software(self):
32
+ return self._root.attrib["source_software"]
33
+
34
+ @property
35
+ def entities(self):
36
+ return self._root.findall(".//entities", namespace)
37
+
38
+ @property
39
+ def sections(self):
40
+ return self._root.findall(".//sections", namespace)
41
+
42
+ @property
43
+ def materials(self):
44
+ return self._root.findall(".//materials", namespace)
45
+
46
+ @property
47
+ def bars(self):
48
+ return self._root.findall(".//bar", namespace)
49
+
50
+ def serialise_to_xml(self):
51
+ return ET.tostring(self._root, encoding="UTF-8")
52
+
53
+ # private void Initialize(Country country)
54
+ # {
55
+ # this.StruxmlVersion = "01.00.000";
56
+ # this.SourceSoftware = $"FEM-Design API SDK {Assembly.GetExecutingAssembly().GetName().Version.ToString()}";
57
+ # this.StartTime = "1970-01-01T00:00:00.000";
58
+ # this.EndTime = System.DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fff", CultureInfo.InvariantCulture);
59
+ # this.Guid = System.Guid.NewGuid();
60
+ # this.ConvertId = "00000000-0000-0000-0000-000000000000";
61
+ # this.Standard = "EC";
62
+ # this.Country = country;
63
+ # this.End = "";
File without changes
@@ -0,0 +1,40 @@
1
+ import os
2
+ import pathlib
3
+ ## define a static class to help with file operations
4
+ class OutputFileHelper:
5
+ _scriptsDirectory = "scripts"
6
+ _resultsDirectory = "results"
7
+ _bscDirectory = "bsc"
8
+
9
+ _logFileName = "logfile.log"
10
+ _struxmlFileName = "model.struxml"
11
+ _strFileName = "model.str"
12
+ _docxFileName = "model.docx"
13
+
14
+ _fdscriptFileExtension = ".fdscript"
15
+ _bscFileExtension = ".bsc"
16
+ _csvFileExtension = ".csv"
17
+
18
+ _strFileExtension = ".str"
19
+ _struxmlFileExtension = ".struxml"
20
+
21
+ def GetLogFilePath(outputDirectory: str) -> str:
22
+ if not os.path.exists(outputDirectory):
23
+ os.makedirs(outputDirectory)
24
+ return os.path.abspath( os.path.join(outputDirectory, OutputFileHelper._logFileName) )
25
+
26
+ def GetFdscriptFilePath(outputDirectory: str, file_name: str = "script") -> str:
27
+ dir = os.path.join(outputDirectory, OutputFileHelper._scriptsDirectory)
28
+ if not os.path.exists(dir):
29
+ os.makedirs(dir)
30
+
31
+ path = os.path.abspath( os.path.join(dir, f"{file_name}" + OutputFileHelper._fdscriptFileExtension) )
32
+ return path
33
+
34
+ def GetBscFilePath(outputDirectory: str, file_name: str) -> str:
35
+ dir = os.path.join(outputDirectory, OutputFileHelper._scriptsDirectory, OutputFileHelper._bscDirectory)
36
+ if not os.path.exists(dir):
37
+ os.makedirs(dir)
38
+
39
+ path = os.path.abspath( os.path.join(dir, f"{file_name}" + OutputFileHelper._bscFileExtension) )
40
+ return path