irie 0.0.55__py3-none-any.whl → 0.0.57__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.

Potentially problematic release.


This version of irie might be problematic. Click here for more details.

@@ -96,6 +96,7 @@ class Asset(models.Model):
96
96
  ordering = ["-id"]
97
97
 
98
98
 
99
+
99
100
  class Vulnerability: # (models.Model):
100
101
  type = None
101
102
  asset = None
@@ -125,6 +126,7 @@ class SensorGroup(models.Model):
125
126
  def __str__(self):
126
127
  return f"{self.asset.calid} - {self.name} ({self.datum})"
127
128
 
129
+
128
130
  class Sensor(models.Model):
129
131
  # class Status:
130
132
  # active: bool
@@ -138,12 +140,31 @@ class Sensor(models.Model):
138
140
  dy = models.DecimalField(decimal_places=2, max_digits=10)
139
141
  dz = models.DecimalField(decimal_places=2, max_digits=10)
140
142
 
141
- group = models.ForeignKey(SensorGroup, related_name="sensors", on_delete=models.RESTRICT)
143
+ group = models.ForeignKey(SensorGroup,
144
+ related_name="sensors",
145
+ on_delete=models.RESTRICT)
142
146
 
143
147
  def __str__(self):
144
148
  return f"{self.group.asset.calid} - {self.name} ({self.group.name})"
145
149
 
146
150
 
151
+ def acceleration(self, event):
152
+ import quakeio
153
+
154
+ motion_data = (
155
+ quakeio.read(
156
+ event.event_file.path, input_format="csmip.zip"
157
+ )
158
+ )
159
+
160
+ series = motion_data.match("l", station_channel=f"{self.name}").accel.data
161
+
162
+ return [
163
+ (series*float(self.dx)).tolist(),
164
+ (series*float(self.dy)).tolist(),
165
+ (series*float(self.dz)).tolist()
166
+ ]
167
+
147
168
 
148
169
  # class Rendering:
149
170
  # def __init__(self, url=None, units, datum):
@@ -25,8 +25,11 @@ urlpatterns = [
25
25
  ),
26
26
  re_path("^inventory/(?P<calid>[0-9 A-Z-]*)/evaluations/$", views.asset_evals, name="asset_evals"),
27
27
  re_path("^inventory/(?P<calid>[0-9 A-Z-]*)/$", views.asset_profile, name="asset_profile"),
28
+ # Sensors
28
29
  re_path("^inventory/(?P<calid>[0-9 A-Z-]*)/sensors/$", views.asset_sensors, name="asset_sensors"),
29
30
  re_path("^inventory/(?P<calid>[0-9 A-Z-]*)/sensor_upload", views.sensor_upload, name="sensor_upload"),
31
+ path("inventory/<slug:calid>/sensors/<int:group_id>/edit/", views.sensor_edit, name="sensor_edit"),
32
+
30
33
 
31
34
  path("inventory/map2/", views.map_inventory),
32
35
  path("california.json", views.california_json),
@@ -408,14 +408,16 @@ def asset_sensors(request, calid):
408
408
  asset = Asset.objects.get(calid=calid)
409
409
  context = {
410
410
  "asset": asset,
411
+ "segment": "assets",
411
412
  "groups": SensorGroup.objects.filter(asset=asset)
412
413
  }
413
414
  html_template = loader.get_template("inventory/asset-sensors.html")
414
415
  return HttpResponse(html_template.render(context, request))
415
416
 
417
+
416
418
  @login_required(login_url="/login/")
417
419
  def sensor_upload(request, calid):
418
- asset = get_object_or_404(Asset, calid=calid) # Fetch the asset using calid
420
+ asset = get_object_or_404(Asset, calid=calid)
419
421
  datums = Datum.objects.filter(asset=asset).values(
420
422
  'id', 'orient_x', 'locate_x', 'orient_y', 'locate_y', 'orient_z', 'locate_z'
421
423
  )
@@ -443,7 +445,8 @@ def sensor_upload(request, calid):
443
445
  formset = SensorFormSet()
444
446
 
445
447
  context = {
446
- "group_form": group_form,
448
+ "group_form": group_form,
449
+ "segment": "assets",
447
450
  "formset": formset,
448
451
  "renderings": [
449
452
  {"name": predictor.name, "glb": predictor.render_file.url}
@@ -456,6 +459,68 @@ def sensor_upload(request, calid):
456
459
  return render(request, "inventory/sensor-upload.html", context)
457
460
 
458
461
 
462
+ @login_required(login_url="/login/")
463
+ def sensor_edit(request, calid, group_id):
464
+ """
465
+ Edit an existing SensorGroup + its Sensors while re-using the
466
+ sensor-upload.html template.
467
+ """
468
+ from django.forms import modelformset_factory
469
+
470
+ asset = get_object_or_404(Asset, calid=calid)
471
+ sensor_group = get_object_or_404(SensorGroup, pk=group_id, asset=asset)
472
+
473
+ #
474
+ SensorFormSet = modelformset_factory(
475
+ Sensor,
476
+ form=SensorForm,
477
+ extra=0,
478
+ can_delete=True,
479
+ )
480
+
481
+ if request.method == "POST":
482
+ group_form = SensorGroupForm(request.POST, instance=sensor_group, asset=asset)
483
+ formset = SensorFormSet(request.POST, queryset=sensor_group.sensors.all())
484
+
485
+ if group_form.is_valid() and formset.is_valid():
486
+ # update the group fields
487
+ group_form.save()
488
+
489
+ # saves edits, additions (extra rows you may allow), and deletions
490
+ sensors = formset.save(commit=False)
491
+ for sensor in sensors:
492
+ sensor.group = sensor_group
493
+ sensor.save()
494
+
495
+ # any forms flagged for deletion come back in formset.deleted_objects
496
+ for obj in formset.deleted_objects:
497
+ obj.delete()
498
+
499
+ return redirect("asset_sensors", calid=calid)
500
+ else:
501
+ group_form = SensorGroupForm(instance=sensor_group, asset=asset)
502
+ formset = SensorFormSet(queryset=sensor_group.sensors.all())
503
+
504
+ context = {
505
+ "group_form": group_form,
506
+ "segment": "assets",
507
+ "formset": formset,
508
+ "is_edit": True, # optional flag so the template can tweak titles/buttons
509
+ "renderings": [
510
+ {"name": p.name, "glb": p.render_file.url}
511
+ for p in PredictorModel.objects.filter(asset=asset, protocol="IRIE_PREDICTOR_V1")
512
+ if p.render_file and p.render_file.url
513
+ ],
514
+ "asset": asset,
515
+ "datums": list(
516
+ Datum.objects.filter(asset=asset).values(
517
+ "id", "orient_x", "locate_x", "orient_y", "locate_y", "orient_z", "locate_z"
518
+ )
519
+ ),
520
+ }
521
+ return render(request, "inventory/sensor-upload.html", context)
522
+
523
+
459
524
  def _filter_asset_table(request):
460
525
  # Copy the GET parameters and remove the 'page' parameter
461
526
  page_query = request.GET.copy()
@@ -34,8 +34,10 @@ class PredictorModel(models.Model):
34
34
  def __str__(self):
35
35
  return f"{self.asset.calid} - {self.name} : {self.description}"
36
36
 
37
- def get_runner(self):
38
- pass
37
+ @property
38
+ def runner(self):
39
+ from irie.apps.prediction.predictor import PREDICTOR_TYPES
40
+ return PREDICTOR_TYPES[self.protocol](self)
39
41
 
40
42
  def get_artist(self):
41
43
  pass
@@ -53,9 +55,9 @@ class SensorAssignment(models.Model):
53
55
  ]
54
56
  )
55
57
 
56
- orient_z = models.FloatField()
57
- orient_x = models.FloatField()
58
- orient_y = models.FloatField()
58
+ # orient_z = models.FloatField()
59
+ # orient_x = models.FloatField()
60
+ # orient_y = models.FloatField()
59
61
  # show_x = models.FloatField()
60
62
  # show_y = models.FloatField()
61
63
  # show_z = models.FloatField()
@@ -10,6 +10,15 @@ import sys, json
10
10
  import zipfile
11
11
  from pathlib import Path
12
12
  import contextlib
13
+ try:
14
+ from xcsi.csi import collect_outlines, load as load_csi
15
+ from xcsi.csi._frame.section import create_section, iter_sections
16
+ from xcsi.job import Job
17
+ from xcsi.metrics import PeakDrift
18
+ except:
19
+ from openbim.csi import load as load_csi, create_model, collect_outlines
20
+ from openbim.csi._frame.section import create_section, iter_sections
21
+
13
22
 
14
23
  from irie.apps.prediction.runners import (Runner, RunID, classproperty)
15
24
 
@@ -26,6 +35,37 @@ OPENSEES = [
26
35
  ]
27
36
 
28
37
 
38
+ def _create_excitation(model, predictor, inputs, dt):
39
+ import numpy as np
40
+ # rotation = predictor.config["orientation"]
41
+ rotation = np.eye(3)*150
42
+ i = 1
43
+ for sensor in predictor.sensorassignment_set.all():
44
+ if sensor.role == "input":
45
+ for dof in range(3):
46
+ series = sum(ai*dx
47
+ for ai, dx in zip(np.array(inputs[sensor.id]["series"]), rotation[dof]))
48
+
49
+ model.timeSeries("Path", i, dt=dt, values=series.tolist())
50
+ model.pattern("UniformExcitation", i, dof+1, accel=i)
51
+ i += 1
52
+
53
+
54
+ def _analyze_and_render(model, artist, nt, dt):
55
+ import veux, veux.motion
56
+ motion = veux.motion.Motion(artist)
57
+ for i in range(nt):
58
+ if model.analyze(1, dt) != 0:
59
+ return -1
60
+ motion.advance(i*dt)
61
+ motion.draw_sections(position=lambda x: [1000*u for u in model.nodeDisp(x)],
62
+ rotation=model.nodeRotation)
63
+
64
+ motion.add_to(artist.canvas)
65
+ return 0
66
+
67
+
68
+
29
69
  @contextlib.contextmanager
30
70
  def new_cd(x):
31
71
  d = os.getcwd()
@@ -199,10 +239,14 @@ class OpenSeesRunner(Runner):
199
239
  def newPrediction(self, event, output_directory = None):
200
240
  """
201
241
  Create a new prediction run and return the run_id. If output_directory is None,
202
- the output directory will be created automatically. Otherwise, the output directory
203
- will be copied to the new output directory.
242
+ the output directory will be created automatically.
204
243
  """
205
- event = event.event_file.path
244
+ inputs = {}
245
+ for sensor in self.predictor.sensorassignment_set.all():
246
+ if sensor.role == "input":
247
+ inputs[sensor.id] = {"series": sensor.sensor.acceleration(event)}
248
+
249
+
206
250
  if output_directory is not None:
207
251
  # this case will eventually be deleted, its just for
208
252
  # debugging metric renderers.
@@ -225,23 +269,30 @@ class OpenSeesRunner(Runner):
225
269
  run_dir.mkdir(parents=True, exist_ok=False)
226
270
 
227
271
  # Copy files to run directory
228
- shutil.copyfile(event, run_dir/"event.zip")
229
- shutil.copyfile(self.model_file.resolve(), run_dir/self.model_file.name)
230
-
231
- if self.model_file.suffix == ".zip":
232
- with zipfile.ZipFile(self.model_file, 'r') as zip_ref:
233
- zip_ref.extractall(run_dir)
234
- model_file = (run_dir/"nonlinear.tcl").resolve()
235
-
236
- elif self.model_file.suffix == ".b2k":
237
- pass
238
-
239
- elif self.model_file.suffix == ".tcl":
240
- model_file = (run_dir/self.model_file.name).resolve()
272
+ if False:
273
+ event = event.event_file.path
274
+ shutil.copyfile(event, run_dir/"event.zip")
275
+
276
+ model_file = None
277
+ if hasattr(self, "model_file") and self.model_file is not None:
278
+ shutil.copyfile(self.model_file.resolve(),
279
+ run_dir/self.model_file.name)
280
+
281
+ if self.model_file.suffix == ".zip":
282
+ with zipfile.ZipFile(self.model_file, 'r') as zip_ref:
283
+ zip_ref.extractall(run_dir)
284
+ model_file = (run_dir/"nonlinear.tcl").resolve()
285
+
286
+ elif self.model_file.suffix == ".b2k":
287
+ pass
288
+
289
+ elif self.model_file.suffix == ".tcl":
290
+ model_file = (run_dir/self.model_file.name).resolve()
241
291
 
242
292
  self.runs[run_id] = {
243
293
  "run_output_directory": run_dir,
244
- "event_file_name": Path(event),
294
+ # "event_file_name": Path(event),
295
+ "inputs": inputs,
245
296
  "model_file": model_file,
246
297
  **self.conf
247
298
  }
@@ -264,56 +315,33 @@ class OpenSeesRunner(Runner):
264
315
  if run_id not in self.runs:
265
316
  self._load_config(run_id)
266
317
 
267
- event_file_path = os.path.relpath(self.runs[run_id]["event_file_name"],
268
- self.model_file.parents[0])
269
- output_directory = os.path.relpath(self.runs[run_id]["run_output_directory"],
270
- self.model_file.parents[0])
318
+ if False:
319
+ event_file_path = os.path.relpath(self.runs[run_id]["event_file_name"],
320
+ self.model_file.parents[0])
271
321
 
272
- event_file_path = self.runs[run_id]["event_file_name"]
322
+ output_directory = os.path.relpath(self.runs[run_id]["run_output_directory"],
323
+ self.model_file.parents[0])
324
+
325
+ event_file_path = self.runs[run_id]["event_file_name"]
273
326
 
274
327
  # Create model
275
328
  import opensees.openseespy as ops
276
329
 
277
330
  import sys
278
- model = ops.Model(echo_file=sys.stdout)
279
- model.eval("set argv {}")
280
- with new_cd(self.runs[run_id]["run_output_directory"]):
281
- model.eval(f"source {self.runs[run_id]['model_file']}")
331
+ csi = self._csi
282
332
 
283
- model.eval(f"print -json -file modelDetails.json")
333
+ with new_cd(self.runs[run_id]["run_output_directory"]):
284
334
 
285
- model.eval(f"set python {sys.executable}")
335
+ model = ops.Model(ndm=3, ndf=6, echo_file=open("model.tcl", "w"))
336
+ if csi is not None:
337
+ asm = Job(csi).assemble(model=model)
338
+ # model = create_model(csi, model=model)
339
+ sections = collect_outlines(csi, model.frame_tags)
340
+ else:
341
+ asm = None
342
+ sections = None
343
+ model.eval(f"source {self.runs[run_id]['model_file']}")
286
344
 
287
- model.eval(r"""
288
- proc py {args} {
289
- global python
290
- eval "[exec {*}$python {*}$args]"
291
- }
292
-
293
- proc pt {args} {
294
- global python
295
- puts "[exec {*}$python {*}$args]"
296
- }
297
-
298
- proc write_modes {mode_file nmodes} {
299
- set fid_modes [open $mode_file w+]
300
- for {set m 1} {$m <= $nmodes} {incr m} {
301
- puts $fid_modes "$m:"
302
- foreach n [getNodeTags] {
303
- puts $fid_modes " $n: \[[join [nodeEigenvector $n $m] {, }]\]";
304
- }
305
- }
306
- close $fid_modes
307
- }
308
- proc write_displacements {file_name {resp Disp}} {
309
- set fid [open "$file_name" "w+"]
310
- puts $fid "[getTime]:"
311
- foreach n [getNodeTags] {
312
- puts $fid " $n: \[[join [node${resp} $n] {, }]\]";
313
- }
314
- close $fid;
315
- }
316
- """)
317
345
 
318
346
  #
319
347
  # Run gravity analysis
@@ -328,61 +356,60 @@ class OpenSeesRunner(Runner):
328
356
  system SparseGeneral;
329
357
  analysis Static;
330
358
  analyze 10;
331
- # write_displacements "dispsGrav.yaml"
332
359
  """)
333
360
 
334
361
  #
335
362
  # DAMPING
336
363
  #
337
- model.eval(r"""
338
- set nmodes 8; # Number of modes to analyze for modal analysis
339
-
340
- # set wb [eigen -fullGenLapack $nmodes];
341
- # puts "\tFundamental-Period After Gravity Analysis:"
342
- # for {set iPd 1} {$iPd <= $nmodes} {incr iPd 1} {
343
- # set wwb [lindex $wb $iPd-1];
344
- # set Tb [expr 2*$pi/sqrt($wwb)];
345
- # puts "\tPeriod$iPd= $Tb"
364
+ # model.eval(r"""
365
+ # set nmodes 8; # Number of modes to analyze for modal analysis
366
+
367
+ # # set wb [eigen -fullGenLapack $nmodes];
368
+ # # puts "\tFundamental-Period After Gravity Analysis:"
369
+ # # for {set iPd 1} {$iPd <= $nmodes} {incr iPd 1} {
370
+ # # set wwb [lindex $wb $iPd-1];
371
+ # # set Tb [expr 2*$pi/sqrt($wwb)];
372
+ # # puts "\tPeriod$iPd= $Tb"
373
+ # # }
374
+ # # write_modes $output_directory/modesPostG.yaml $nmodes
375
+ # # remove recorders
376
+
377
+ # set nmodes [tcl::mathfunc::max {*}$damping_modes $nmodes]
378
+ # set lambdaN [eigen -fullGenLapack $nmodes];
379
+
380
+ # # set lambdaN [eigen $nmodes];
381
+ # if {$damping_type == "rayleigh"} {
382
+ # set nEigenI [lindex $damping_modes 0]; # first rayleigh damping mode
383
+ # set nEigenJ [lindex $damping_modes 1]; # second rayleigh damping mode
384
+ # set iDamp [lindex $damping_ratios 0]; # first rayleigh damping ratio
385
+ # set jDamp [lindex $damping_ratios 1]; # second rayleigh damping ratio
386
+ # set lambdaI [lindex $lambdaN [expr $nEigenI-1]];
387
+ # set lambdaJ [lindex $lambdaN [expr $nEigenJ-1]];
388
+ # set omegaI [expr $lambdaI**0.5];
389
+ # set omegaJ [expr $lambdaJ**0.5];
390
+ # set TI [expr 2.0*$pi/$omegaI];
391
+ # set TJ [expr 2.0*$pi/$omegaJ];
392
+ # set alpha0 [expr 2.0*($iDamp/$omegaI-$jDamp/$omegaJ)/(1/$omegaI**2-1/$omegaJ**2)];
393
+ # set alpha1 [expr 2.0*$iDamp/$omegaI-$alpha0/$omegaI**2];
394
+ # puts "\tRayleigh damping parameters:"
395
+ # puts "\tmodes: $nEigenI, $nEigenJ ; ratios: $iDamp, $jDamp"
396
+ # puts "\tTI = $TI; TJ = $TJ"
397
+ # puts "\tlambdaI = $lambdaI; lambdaJ = $lambdaJ"
398
+ # puts "\tomegaI = $omegaI; omegaJ = $omegaJ"
399
+ # puts "\talpha0 = $alpha0; alpha1 = $alpha1"
400
+ # rayleigh $alpha0 0.0 0.0 $alpha1;
401
+
402
+ # } elseif {$damping_type == "modal"} {
403
+ # # needs a bit of edit. currently assuming that the ratios are applied in order at the first modes. but should be applied at the specified damping_modes modes.
404
+ # set nratios [llength $damping_ratios]
405
+ # puts "\tModal damping parameters:"
406
+ # puts "\tratios of $damping_ratios at the first $nratios modes"
407
+ # for {set i 1} {$i <= [expr $nmodes - $nratios]} {incr i} {
408
+ # lappend damping_ratios 0
409
+ # }
410
+ # modalDamping {*}$damping_ratios
346
411
  # }
347
- # write_modes $output_directory/modesPostG.yaml $nmodes
348
- # remove recorders
349
-
350
- set nmodes [tcl::mathfunc::max {*}$damping_modes $nmodes]
351
- set lambdaN [eigen -fullGenLapack $nmodes];
352
-
353
- # set lambdaN [eigen $nmodes];
354
- if {$damping_type == "rayleigh"} {
355
- set nEigenI [lindex $damping_modes 0]; # first rayleigh damping mode
356
- set nEigenJ [lindex $damping_modes 1]; # second rayleigh damping mode
357
- set iDamp [lindex $damping_ratios 0]; # first rayleigh damping ratio
358
- set jDamp [lindex $damping_ratios 1]; # second rayleigh damping ratio
359
- set lambdaI [lindex $lambdaN [expr $nEigenI-1]];
360
- set lambdaJ [lindex $lambdaN [expr $nEigenJ-1]];
361
- set omegaI [expr $lambdaI**0.5];
362
- set omegaJ [expr $lambdaJ**0.5];
363
- set TI [expr 2.0*$pi/$omegaI];
364
- set TJ [expr 2.0*$pi/$omegaJ];
365
- set alpha0 [expr 2.0*($iDamp/$omegaI-$jDamp/$omegaJ)/(1/$omegaI**2-1/$omegaJ**2)];
366
- set alpha1 [expr 2.0*$iDamp/$omegaI-$alpha0/$omegaI**2];
367
- puts "\tRayleigh damping parameters:"
368
- puts "\tmodes: $nEigenI, $nEigenJ ; ratios: $iDamp, $jDamp"
369
- puts "\tTI = $TI; TJ = $TJ"
370
- puts "\tlambdaI = $lambdaI; lambdaJ = $lambdaJ"
371
- puts "\tomegaI = $omegaI; omegaJ = $omegaJ"
372
- puts "\talpha0 = $alpha0; alpha1 = $alpha1"
373
- rayleigh $alpha0 0.0 0.0 $alpha1;
374
-
375
- } elseif {$damping_type == "modal"} {
376
- # needs a bit of edit. currently assuming that the ratios are applied in order at the first modes. but should be applied at the specified damping_modes modes.
377
- set nratios [llength $damping_ratios]
378
- puts "\tModal damping parameters:"
379
- puts "\tratios of $damping_ratios at the first $nratios modes"
380
- for {set i 1} {$i <= [expr $nmodes - $nratios]} {incr i} {
381
- lappend damping_ratios 0
382
- }
383
- modalDamping {*}$damping_ratios
384
- }
385
- """)
412
+ # """)
386
413
 
387
414
 
388
415
  #
@@ -390,73 +417,60 @@ class OpenSeesRunner(Runner):
390
417
  #
391
418
 
392
419
  ## COLUMN SECTION DEFORMATIONS AT TOP AND BOTTOM FOR STRAIN-BASED DAMAGE STATES
393
- column_strains = tuple(k["key"] for k in self.runs[run_id]["columns"] if k["strain"])
394
- if len(column_strains) > 0:
395
- model.recorder("Element", "section", 1, "deformation", xml="eleDef1.txt", ele=column_strains) # section 1 deformation]
396
- model.recorder("Element", "section", 4, "deformation", xml="eleDef4.txt", ele=column_strains) # section 4 deformation]
397
-
398
-
420
+ if False:
421
+ column_strains = tuple(k["key"] for k in self.runs[run_id]["columns"] if k["strain"])
422
+ if len(column_strains) > 0:
423
+ model.recorder("Element", "section", 1, "deformation", xml="eleDef1.txt", ele=column_strains) # section 1 deformation]
424
+ model.recorder("Element", "section", 4, "deformation", xml="eleDef4.txt", ele=column_strains) # section 4 deformation]
399
425
 
400
426
  #
401
427
  # Run dynamic analysis
402
428
  #
403
- model.eval(f"""
404
- wipeAnalysis
405
- # Uniform Support Excitation
406
- # lassign [pt -m CE58658.makePattern {event_file_path} --scale $dynamic_scale_factor --node $input_location] dt steps
407
- # lassign [py -m CE58658.makePattern {event_file_path} --scale $dynamic_scale_factor --node $input_location] dt steps
408
- set dt 0.1
409
- set steps 3
410
- """)
411
429
 
412
430
  # RESPONSE HISTORY RECORDERS
431
+ if False:
432
+ model.recorder("Node", "accel", xml="model/AA_all.txt", timeSeries=(1, 2), dof=(1, 2))
433
+ model.recorder("Node", "accel", xml="model/RD_all.txt", dof=(1, 2))
413
434
 
414
- model.recorder("Node", "accel", xml="model/AA_all.txt", timeSeries=(1, 2), dof=(1, 2))
415
- model.recorder("Node", "accel", xml="model/RD_all.txt", dof=(1, 2))
435
+ column_nodes = tuple(k["node"] for k in self.runs[run_id]["bents"] if k["record"])
436
+ model.recorder("Node", "accel", file="TopColAccel_X_txt.txt", timeSeries=1 , node=column_nodes, dof=1)
437
+ model.recorder("Node", "accel", file="TopColAccel_Y_txt.txt", timeSeries=2 , node=column_nodes, dof=2)
438
+ model.recorder("Node", "disp", file="TopColDrift_X_txt.txt", node=column_nodes, dof=1)
439
+ model.recorder("Node", "disp", file="TopColDrift_Y_txt.txt", node=column_nodes, dof=2)
416
440
 
417
- column_nodes = tuple(k["node"] for k in self.runs[run_id]["bents"] if k["record"])
418
- model.recorder("Node", "accel", file="TopColAccel_X_txt.txt", timeSeries=1 , node=column_nodes, dof=1)
419
- model.recorder("Node", "accel", file="TopColAccel_Y_txt.txt", timeSeries=2 , node=column_nodes, dof=2)
420
- model.recorder("Node", "disp", file="TopColDrift_X_txt.txt", node=column_nodes, dof=1)
421
- model.recorder("Node", "disp", file="TopColDrift_Y_txt.txt", node=column_nodes, dof=2)
441
+ metrics = [
442
+ PeakDrift((31, 81))
443
+ ]
422
444
 
423
- model.eval("""
424
- set dtfact 1;
425
- set Tol 1.0e-8;
426
- set maxNumIter 100;
427
- set printFlag 0;
428
- set TestType EnergyIncr;
445
+ for metric in metrics:
446
+ metric.record(asm)
447
+
448
+ nt = 500
449
+ dt = 0.02
450
+ _create_excitation(model, self.predictor, self.runs[run_id]["inputs"], dt)
451
+
452
+ model.eval(f"print -json -file model.json")
453
+
454
+ model.eval(f"""
455
+ wipeAnalysis
429
456
  set NewmarkGamma 0.50;
430
- set NewmarkBeta 0.25;
457
+ set NewmarkBeta 0.25;
431
458
  constraints Transformation;
432
- numberer RCM;
433
- test $TestType $Tol $maxNumIter $printFlag;
434
- set algorithmType "Newton";
435
- system BandSPD;
436
- integrator Newmark $NewmarkGamma $NewmarkBeta;
437
-
438
- algorithm {*}$algorithmType;
439
- analysis Transient;
440
-
441
- set DtAnalysis $dt;
442
- set TmaxAnalysis [expr $dt*$steps];
443
- set Nsteps $steps;
444
- if {$dynamic_truncated != 0} {
445
- set Nsteps $dynamic_timesteps;
446
- }
447
- puts "\tGround Motion: dt= $DtAnalysis, NumPts= $Nsteps, TmaxAnalysis= $TmaxAnalysis";
448
-
449
- puts "\tRunning dynamic ground motion analysis..."
450
- set t3 [clock clicks -millisec];
451
- catch {progress create $Nsteps} _
459
+ numberer RCM;
460
+ test EnergyIncr 1.0e-6 50 0;
461
+ system Umfpack;
462
+ integrator Newmark $NewmarkGamma $NewmarkBeta;
463
+ algorithm Newton;
464
+ analysis Transient;
465
+ """)
452
466
 
453
- analyze 2 $DtAnalysis;
467
+ import veux
468
+ artist = veux.create_artist(model, vertical=3, model_config={
469
+ "frame_outlines": sections
470
+ })
471
+ _analyze_and_render(model, artist, nt, dt)
454
472
 
455
- # for {set ik 1} {$ik <= $Nsteps} {incr ik 1} {
456
- # catch {progress update} _
457
- # set ok [analyze 1 $DtAnalysis];
458
- # }
459
- """)
473
+ artist.save("motion.glb")
460
474
 
461
475
  model.wipe()
462
476
 
@@ -477,10 +491,7 @@ class OpenSeesRunner(Runner):
477
491
  else:
478
492
  output_dir = self.out_dir/str(run_id)
479
493
 
480
- # with open(output_dir/"modelDetails.json", "r") as f:
481
- # model = json.load(f)
482
-
483
- model = read_model(output_dir/"modelDetails.json")
494
+ model = read_model(output_dir/"model.json")
484
495
 
485
496
  # if type == "COLUMN_STRAIN_STATES":
486
497
  # return _clean_json(column_strain_state_metric(model, output_dir, config))
@@ -504,11 +515,10 @@ class OpenSeesRunner(Runner):
504
515
  @property
505
516
  def _csi(self):
506
517
  if not hasattr(self, "_csi_data") or self._csi_data is None:
507
- from openbim.csi import load, create_model, collect_outlines
508
518
  # 1) Parse the CSI file
509
519
  try:
510
520
  csi_file = self.predictor.config_file
511
- self._csi_data = load((str(line.decode()).replace("\r\n","\n") for line in csi_file.readlines()))
521
+ self._csi_data = load_csi((str(line.decode()).replace("\r\n","\n") for line in csi_file.readlines()))
512
522
  except Exception as e:
513
523
  import sys
514
524
  print(f"Error loading CSiBridge file: {e}", file=sys.stderr)
@@ -518,21 +528,13 @@ class OpenSeesRunner(Runner):
518
528
 
519
529
 
520
530
  def structural_section(self, name):
521
- from openbim.csi._frame.section import create_section
522
- # from openbim.csi._frame.outlines import section_mesh
523
531
  if (s:= create_section(self._csi, name)) is not None:
524
532
  return {}, s._create_model(mesh_size=0.1)
525
533
 
526
534
 
527
- def structural_sections(self):
528
- from openbim.csi._frame.section import iter_sections
535
+ def structural_sections(self):
529
536
  yield from iter_sections(self._csi)
530
- # for s, name in iter_sections(self._csi):
531
- # yield {
532
- # "name": name,
533
- # "type": "Section",
534
- # "section": name,
535
- # }
537
+
536
538
 
537
539
  def structural_members(self):
538
540