osis-python 0.7.1__tar.gz → 0.7.3__tar.gz

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.
Files changed (119) hide show
  1. {osis_python-0.7.1/src/osis_python.egg-info → osis_python-0.7.3}/PKG-INFO +111 -3
  2. {osis_python-0.7.1 → osis_python-0.7.3}/README.md +110 -2
  3. {osis_python-0.7.1 → osis_python-0.7.3/src/osis_python.egg-info}/PKG-INFO +111 -3
  4. {osis_python-0.7.1 → osis_python-0.7.3}/src/osis_python.egg-info/SOURCES.txt +0 -1
  5. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/__init__.py +1 -2
  6. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/boundary/manager.py +1 -1
  7. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/element/manager.py +1 -1
  8. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/live/manager.py +2 -1
  9. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/load/manager.py +2 -0
  10. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/material/manager.py +4 -3
  11. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/property/manager.py +2 -2
  12. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/section/manager.py +45 -20
  13. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/transfer/generator.py +6 -2
  14. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/transfer/out_to_python.py +21 -0
  15. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/transfer/routes.py +43 -0
  16. osis_python-0.7.1/src/pyosis/core/build.py +0 -2316
  17. {osis_python-0.7.1 → osis_python-0.7.3}/LICENSE +0 -0
  18. {osis_python-0.7.1 → osis_python-0.7.3}/pyproject.toml +0 -0
  19. {osis_python-0.7.1 → osis_python-0.7.3}/setup.cfg +0 -0
  20. {osis_python-0.7.1 → osis_python-0.7.3}/src/osis_python.egg-info/dependency_links.txt +0 -0
  21. {osis_python-0.7.1 → osis_python-0.7.3}/src/osis_python.egg-info/requires.txt +0 -0
  22. {osis_python-0.7.1 → osis_python-0.7.3}/src/osis_python.egg-info/top_level.txt +0 -0
  23. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/boundary/__init__.py +0 -0
  24. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/boundary/interface.py +0 -0
  25. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/control/__init__.py +0 -0
  26. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/control/control.py +0 -0
  27. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/control/io.py +0 -0
  28. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/control/manager.py +0 -0
  29. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/control/nonlinear.py +0 -0
  30. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/core/__init__.py +0 -0
  31. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/core/all_func.py +0 -0
  32. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/core/apdl_sync.py +0 -0
  33. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/core/basic_manager.py +0 -0
  34. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/core/batch.py +0 -0
  35. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/core/client.py +0 -0
  36. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/core/command.py +0 -0
  37. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/core/engine.py +0 -0
  38. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/core/exceptions.py +0 -0
  39. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/core/references.py +0 -0
  40. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/core/solver.py +0 -0
  41. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/core/sync_apdl_cli.py +0 -0
  42. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/display/__init__.py +0 -0
  43. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/display/interface.py +0 -0
  44. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/display/manager.py +0 -0
  45. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/dynamic/__init__.py +0 -0
  46. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/dynamic/load_to_mass.py +0 -0
  47. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/dynamic/manager.py +0 -0
  48. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/dynamic/modal.py +0 -0
  49. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/dynamic/seismic.py +0 -0
  50. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/element/__init__.py +0 -0
  51. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/element/interface.py +0 -0
  52. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/general/__init__.py +0 -0
  53. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/general/interface.py +0 -0
  54. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/geometry/__init__.py +0 -0
  55. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/geometry/interface.py +0 -0
  56. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/geometry/manager.py +0 -0
  57. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/live/__init__.py +0 -0
  58. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/live/analysis.py +0 -0
  59. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/live/grade.py +0 -0
  60. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/live/lane.py +0 -0
  61. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/load/__init__.py +0 -0
  62. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/load/loadcase.py +0 -0
  63. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/load/static.py +0 -0
  64. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/load/tendon.py +0 -0
  65. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/material/__init__.py +0 -0
  66. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/material/interface.py +0 -0
  67. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/node/__init__.py +0 -0
  68. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/node/interface.py +0 -0
  69. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/node/manager.py +0 -0
  70. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/post/__init__.py +0 -0
  71. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/post/combine.py +0 -0
  72. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/post/design_check.py +0 -0
  73. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/post/display.py +0 -0
  74. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/post/manager.py +0 -0
  75. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/project/__init__.py +0 -0
  76. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/project/interface.py +0 -0
  77. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/project/manager.py +0 -0
  78. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/property/__init__.py +0 -0
  79. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/property/component_thickness.py +0 -0
  80. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/property/coordinate.py +0 -0
  81. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/property/creep_shrink.py +0 -0
  82. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/property/damping.py +0 -0
  83. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/property/pu_curve.py +0 -0
  84. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/quick_building/__init__.py +0 -0
  85. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/quick_building/interface.py +0 -0
  86. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/result/__init__.py +0 -0
  87. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/result/check.py +0 -0
  88. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/result/env.py +0 -0
  89. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/result/loadcase.py +0 -0
  90. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/result/manager.py +0 -0
  91. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/result/result.py +0 -0
  92. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/section/__init__.py +0 -0
  93. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/section/common.py +0 -0
  94. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/section/composite.py +0 -0
  95. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/section/numeric.py +0 -0
  96. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/section/param.py +0 -0
  97. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/section/rebar.py +0 -0
  98. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/section/rib.py +0 -0
  99. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/section/steel.py +0 -0
  100. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/settlement/__init__.py +0 -0
  101. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/settlement/interface.py +0 -0
  102. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/settlement/manager.py +0 -0
  103. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/stability/__init__.py +0 -0
  104. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/stability/buckling.py +0 -0
  105. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/stability/manager.py +0 -0
  106. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/stage/__init__.py +0 -0
  107. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/stage/define.py +0 -0
  108. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/stage/manager.py +0 -0
  109. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/stage/overall.py +0 -0
  110. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/thickness/__init__.py +0 -0
  111. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/thickness/interface.py +0 -0
  112. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/thickness/manager.py +0 -0
  113. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/transfer/__init__.py +0 -0
  114. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/transfer/matrix.py +0 -0
  115. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/transfer/parser.py +0 -0
  116. {osis_python-0.7.1 → osis_python-0.7.3}/src/pyosis/transfer/split.py +0 -0
  117. {osis_python-0.7.1 → osis_python-0.7.3}/tests/test_batch.py +0 -0
  118. {osis_python-0.7.1 → osis_python-0.7.3}/tests/test_delete_occupied.py +0 -0
  119. {osis_python-0.7.1 → osis_python-0.7.3}/tests/test_project_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: osis-python
3
- Version: 0.7.1
3
+ Version: 0.7.3
4
4
  Summary: A Python client library for Osis APDL
5
5
  Author-email: "CCCC Highway Consultant Co. Ltd." <support@osisbim.com>
6
6
  License-Expression: MIT
@@ -53,10 +53,12 @@ pip install osis-python -i https://pypi.org/simple
53
53
 
54
54
  ## Requirements
55
55
 
56
- - OSIS >= 5.0 (includes the required Python runtime environment)
56
+ - OSIS >= 5.1 (short-form command names like `N`/`Ele`/`Sec` are accepted by both the converter and runtime)
57
57
  - Python >= 3.8
58
58
  - **Solver-only mode** additionally requires the solver distribution that ships `PySolver.dll` (e.g. `D:\OSIS_Solver\Rbin64`).
59
59
 
60
+ > OSIS 5.1 HTTP server bug workaround: the `/OSIS_Run` command-stream splitter does not strip `//` line comments, so a comment line and the command that follows it are merged into one bad command. The pyosis `.out` converter already strips comments before sending; if you ever build a raw `.out` payload yourself, pre-process the file or use `osis_run()` through the generated `main.py`.
61
+
60
62
  ## Quick Start
61
63
 
62
64
  ### 1. Using OSISEngine (Recommended)
@@ -398,7 +400,7 @@ lc.create_nforce(4, fx=200000, fy=0, fz=0)
398
400
  engine.solve()
399
401
  ```
400
402
 
401
- ## End-to-End Demo
403
+ ## End-to-End Demo (Solver-Only Mode)
402
404
 
403
405
  See [`tests/pyosis_demo.py`](tests/pyosis_demo.py) for a runnable end-to-end example that:
404
406
 
@@ -408,3 +410,109 @@ See [`tests/pyosis_demo.py`](tests/pyosis_demo.py) for a runnable end-to-end exa
408
410
  - Exports LCND / LCEF / EnvND / EnvEF results to CSV
409
411
 
410
412
  The demo reuses the prep modules from `tests/output/output_py/25m简支小箱梁中梁-solveronly/`, so it serves both as a smoke test and a copy-paste template.
413
+
414
+ ## Batch Execution
415
+
416
+ For non-interactive workflows (large models, customer scripts that took tens of minutes per command), wrap your script in `with batch():` to merge **all** OSIS commands into a **single** `OSIS_Run` HTTP request when the block exits:
417
+
418
+ ```python
419
+ from pyosis import batch
420
+
421
+ with batch():
422
+ engine.clear()
423
+ engine.clc()
424
+ engine.control.set_gravity_acceleration(9.8066)
425
+ engine.material.create_conc(no=1, name="C50", ...)
426
+ for i in range(1000):
427
+ engine.node.create(no=i + 1, x=i * 0.1, y=0.0, z=0.0)
428
+ for i in range(999):
429
+ engine.element.create_beam3d(no=i + 1, node1=i + 1, node2=i + 2, ...)
430
+ ```
431
+
432
+ Server-side `OSIS_Run` calls `SplitOsisCommands` once and then a single `Execution(&v)`, so the network round-trip cost is paid exactly once instead of once per command. Intermediate reads (`get`, `all`) auto-flush the buffer first, so cached objects stay correct.
433
+
434
+ Notes:
435
+
436
+ - Nested `with batch():` is supported — the inner exit does not flush; only the outermost exit does.
437
+ - If the block raises, the buffered commands added since `__enter__` are discarded.
438
+ - `flush()` can be called inside the block to force an early flush.
439
+ - `next_no()` (auto-numbering) raises inside `batch()` because it would need to query the server mid-batch; pass `no=...` explicitly, or run auto-numbering outside the batch.
440
+
441
+ Benchmark on the `25m简支小箱梁中梁` project (`.out` of ~3000 commands): before `batch()` ~3000 round-trips; with `batch()` 3 round-trips (clear + clc + one final flush), **~1000x compression**.
442
+
443
+ ## .out → pyosis Converter
444
+
445
+ pyosis ships a converter that turns an OSIS command stream (`.out`) into a clean, runnable pyosis project. This is the easiest way to migrate a legacy APDL/SML script.
446
+
447
+ ```bash
448
+ python src/pyosis/transfer/out_to_python.py path/to/model.out path/to/output_project
449
+ ```
450
+
451
+ Generated layout:
452
+
453
+ ```
454
+ output_project/
455
+ └── prep/
456
+ ├── main.py # Entry point (batch mode, runs all 10 modules)
457
+ ├── _1_control.py # Global control parameters
458
+ ├── _2_property.py # Geometry / coordinate / damping / creep properties
459
+ ├── _3_material.py # Materials
460
+ ├── _4_section.py # Sections (incl. mesh)
461
+ ├── _5_node.py # Nodes
462
+ ├── _6_element.py # Elements
463
+ ├── _7_boundary.py # Boundaries
464
+ ├── _8_loadcase.py # Load cases
465
+ ├── _9_analysis.py # Analysis settings (settlement / live / dynamic / stability)
466
+ └── _10_stage.py # Construction stages
467
+ ```
468
+
469
+ The generated `main.py` wraps the entire modelling flow in `with batch():`, so the whole project is sent to OSIS in one HTTP request — matching the OSIS server's native batching path.
470
+
471
+ Run it:
472
+
473
+ ```bash
474
+ cd output_project
475
+ python prep/main.py # build the model
476
+ python prep/main.py --solve # build + solve
477
+ ```
478
+
479
+ Notes on the converter:
480
+
481
+ - Both long-form command names (`Node`, `Element`, `Section`, `Material`, `LoadCase`, `Boundary`, `Stage`) and OSIS 5.1 short-form aliases (`N`, `Ele`, `Sec`/`SecOff`/`SecMesh`, `Mat`, `LC`, `Bd`, `Stg`) are recognized and normalized.
482
+ - `SectionMesh` is emitted with the new `Index, PartID, MeshMethod, MeshSize` layout (Section 7.3.6.2) — `PartID = 1` for concrete sections, `2` for templated composite sections.
483
+ - `Spline3D` is dispatched to `engine.geometry.create_general` / `create_natural` / `create_arc2d` / `create_arc3d` based on the type field.
484
+ - Commands that are not yet modeled are passed through verbatim via `engine.run(...)` so the model still runs; rewrite them into pyosis calls manually if needed.
485
+
486
+ ## Chained Method Calls
487
+
488
+ The dataclass returned by `get()` supports method chaining without server round-trips:
489
+
490
+ ```python
491
+ with batch():
492
+ lc = engine.load.create("Self-weight", load_case_type="D")
493
+ lc.create_gravity()
494
+ lc.create_nforce(node=1, fx=0, fy=-1000, fz=0)
495
+ # ↑ all three commands are queued; the OSIS_Run request only fires at exit
496
+ ```
497
+
498
+ Accessing identity attributes (`lc.no`, `lc.name`) does not touch the server. Accessing data fields (`lc.type`, `lc.elements`) materializes the object (one-time flush + query), then caches it.
499
+
500
+ > OSIS 5.1 HTTP server bug workaround: the `/OSIS_Run` command-stream splitter does not strip `//` line comments, so a comment line and the command that follows it are merged into one bad command. The pyosis `.out` converter already strips comments before sending; if you ever build a raw `.out` payload yourself, pre-process the file or use `osis_run()` through the generated `main.py`.
501
+
502
+ ## End-to-End Demo (GUI Mode)
503
+
504
+ The single-file demo [`tests/demo_gui.py`](tests/demo_gui.py) is a merged version of the project generated from `tests/output/xiaoxiangliang.out`. Run it against an open OSIS 5.1+ GUI:
505
+
506
+ ```bash
507
+ python tests/demo_gui.py # build the model
508
+ python tests/demo_gui.py --solve # build + solve
509
+ ```
510
+
511
+ The file contains the engine setup, all 10 `build_*` functions (control / property / material / section / node / element / boundary / loadcase / analysis / stage), and a `build_model()` driver that wraps everything in `with batch():` — so ~250 commands hit OSIS in a single `OSIS_Run` request.
512
+
513
+ To regenerate both the single-file demo and the multi-module project:
514
+
515
+ ```bash
516
+ python tests/_merge_demo_gui.py # -> tests/demo_gui.py
517
+ python src/pyosis/transfer/out_to_python.py tests/output/xiaoxiangliang.out tests/output/output_py/xiaoxiangliang
518
+ ```
@@ -22,10 +22,12 @@ pip install osis-python -i https://pypi.org/simple
22
22
 
23
23
  ## Requirements
24
24
 
25
- - OSIS >= 5.0 (includes the required Python runtime environment)
25
+ - OSIS >= 5.1 (short-form command names like `N`/`Ele`/`Sec` are accepted by both the converter and runtime)
26
26
  - Python >= 3.8
27
27
  - **Solver-only mode** additionally requires the solver distribution that ships `PySolver.dll` (e.g. `D:\OSIS_Solver\Rbin64`).
28
28
 
29
+ > OSIS 5.1 HTTP server bug workaround: the `/OSIS_Run` command-stream splitter does not strip `//` line comments, so a comment line and the command that follows it are merged into one bad command. The pyosis `.out` converter already strips comments before sending; if you ever build a raw `.out` payload yourself, pre-process the file or use `osis_run()` through the generated `main.py`.
30
+
29
31
  ## Quick Start
30
32
 
31
33
  ### 1. Using OSISEngine (Recommended)
@@ -367,7 +369,7 @@ lc.create_nforce(4, fx=200000, fy=0, fz=0)
367
369
  engine.solve()
368
370
  ```
369
371
 
370
- ## End-to-End Demo
372
+ ## End-to-End Demo (Solver-Only Mode)
371
373
 
372
374
  See [`tests/pyosis_demo.py`](tests/pyosis_demo.py) for a runnable end-to-end example that:
373
375
 
@@ -377,3 +379,109 @@ See [`tests/pyosis_demo.py`](tests/pyosis_demo.py) for a runnable end-to-end exa
377
379
  - Exports LCND / LCEF / EnvND / EnvEF results to CSV
378
380
 
379
381
  The demo reuses the prep modules from `tests/output/output_py/25m简支小箱梁中梁-solveronly/`, so it serves both as a smoke test and a copy-paste template.
382
+
383
+ ## Batch Execution
384
+
385
+ For non-interactive workflows (large models, customer scripts that took tens of minutes per command), wrap your script in `with batch():` to merge **all** OSIS commands into a **single** `OSIS_Run` HTTP request when the block exits:
386
+
387
+ ```python
388
+ from pyosis import batch
389
+
390
+ with batch():
391
+ engine.clear()
392
+ engine.clc()
393
+ engine.control.set_gravity_acceleration(9.8066)
394
+ engine.material.create_conc(no=1, name="C50", ...)
395
+ for i in range(1000):
396
+ engine.node.create(no=i + 1, x=i * 0.1, y=0.0, z=0.0)
397
+ for i in range(999):
398
+ engine.element.create_beam3d(no=i + 1, node1=i + 1, node2=i + 2, ...)
399
+ ```
400
+
401
+ Server-side `OSIS_Run` calls `SplitOsisCommands` once and then a single `Execution(&v)`, so the network round-trip cost is paid exactly once instead of once per command. Intermediate reads (`get`, `all`) auto-flush the buffer first, so cached objects stay correct.
402
+
403
+ Notes:
404
+
405
+ - Nested `with batch():` is supported — the inner exit does not flush; only the outermost exit does.
406
+ - If the block raises, the buffered commands added since `__enter__` are discarded.
407
+ - `flush()` can be called inside the block to force an early flush.
408
+ - `next_no()` (auto-numbering) raises inside `batch()` because it would need to query the server mid-batch; pass `no=...` explicitly, or run auto-numbering outside the batch.
409
+
410
+ Benchmark on the `25m简支小箱梁中梁` project (`.out` of ~3000 commands): before `batch()` ~3000 round-trips; with `batch()` 3 round-trips (clear + clc + one final flush), **~1000x compression**.
411
+
412
+ ## .out → pyosis Converter
413
+
414
+ pyosis ships a converter that turns an OSIS command stream (`.out`) into a clean, runnable pyosis project. This is the easiest way to migrate a legacy APDL/SML script.
415
+
416
+ ```bash
417
+ python src/pyosis/transfer/out_to_python.py path/to/model.out path/to/output_project
418
+ ```
419
+
420
+ Generated layout:
421
+
422
+ ```
423
+ output_project/
424
+ └── prep/
425
+ ├── main.py # Entry point (batch mode, runs all 10 modules)
426
+ ├── _1_control.py # Global control parameters
427
+ ├── _2_property.py # Geometry / coordinate / damping / creep properties
428
+ ├── _3_material.py # Materials
429
+ ├── _4_section.py # Sections (incl. mesh)
430
+ ├── _5_node.py # Nodes
431
+ ├── _6_element.py # Elements
432
+ ├── _7_boundary.py # Boundaries
433
+ ├── _8_loadcase.py # Load cases
434
+ ├── _9_analysis.py # Analysis settings (settlement / live / dynamic / stability)
435
+ └── _10_stage.py # Construction stages
436
+ ```
437
+
438
+ The generated `main.py` wraps the entire modelling flow in `with batch():`, so the whole project is sent to OSIS in one HTTP request — matching the OSIS server's native batching path.
439
+
440
+ Run it:
441
+
442
+ ```bash
443
+ cd output_project
444
+ python prep/main.py # build the model
445
+ python prep/main.py --solve # build + solve
446
+ ```
447
+
448
+ Notes on the converter:
449
+
450
+ - Both long-form command names (`Node`, `Element`, `Section`, `Material`, `LoadCase`, `Boundary`, `Stage`) and OSIS 5.1 short-form aliases (`N`, `Ele`, `Sec`/`SecOff`/`SecMesh`, `Mat`, `LC`, `Bd`, `Stg`) are recognized and normalized.
451
+ - `SectionMesh` is emitted with the new `Index, PartID, MeshMethod, MeshSize` layout (Section 7.3.6.2) — `PartID = 1` for concrete sections, `2` for templated composite sections.
452
+ - `Spline3D` is dispatched to `engine.geometry.create_general` / `create_natural` / `create_arc2d` / `create_arc3d` based on the type field.
453
+ - Commands that are not yet modeled are passed through verbatim via `engine.run(...)` so the model still runs; rewrite them into pyosis calls manually if needed.
454
+
455
+ ## Chained Method Calls
456
+
457
+ The dataclass returned by `get()` supports method chaining without server round-trips:
458
+
459
+ ```python
460
+ with batch():
461
+ lc = engine.load.create("Self-weight", load_case_type="D")
462
+ lc.create_gravity()
463
+ lc.create_nforce(node=1, fx=0, fy=-1000, fz=0)
464
+ # ↑ all three commands are queued; the OSIS_Run request only fires at exit
465
+ ```
466
+
467
+ Accessing identity attributes (`lc.no`, `lc.name`) does not touch the server. Accessing data fields (`lc.type`, `lc.elements`) materializes the object (one-time flush + query), then caches it.
468
+
469
+ > OSIS 5.1 HTTP server bug workaround: the `/OSIS_Run` command-stream splitter does not strip `//` line comments, so a comment line and the command that follows it are merged into one bad command. The pyosis `.out` converter already strips comments before sending; if you ever build a raw `.out` payload yourself, pre-process the file or use `osis_run()` through the generated `main.py`.
470
+
471
+ ## End-to-End Demo (GUI Mode)
472
+
473
+ The single-file demo [`tests/demo_gui.py`](tests/demo_gui.py) is a merged version of the project generated from `tests/output/xiaoxiangliang.out`. Run it against an open OSIS 5.1+ GUI:
474
+
475
+ ```bash
476
+ python tests/demo_gui.py # build the model
477
+ python tests/demo_gui.py --solve # build + solve
478
+ ```
479
+
480
+ The file contains the engine setup, all 10 `build_*` functions (control / property / material / section / node / element / boundary / loadcase / analysis / stage), and a `build_model()` driver that wraps everything in `with batch():` — so ~250 commands hit OSIS in a single `OSIS_Run` request.
481
+
482
+ To regenerate both the single-file demo and the multi-module project:
483
+
484
+ ```bash
485
+ python tests/_merge_demo_gui.py # -> tests/demo_gui.py
486
+ python src/pyosis/transfer/out_to_python.py tests/output/xiaoxiangliang.out tests/output/output_py/xiaoxiangliang
487
+ ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: osis-python
3
- Version: 0.7.1
3
+ Version: 0.7.3
4
4
  Summary: A Python client library for Osis APDL
5
5
  Author-email: "CCCC Highway Consultant Co. Ltd." <support@osisbim.com>
6
6
  License-Expression: MIT
@@ -53,10 +53,12 @@ pip install osis-python -i https://pypi.org/simple
53
53
 
54
54
  ## Requirements
55
55
 
56
- - OSIS >= 5.0 (includes the required Python runtime environment)
56
+ - OSIS >= 5.1 (short-form command names like `N`/`Ele`/`Sec` are accepted by both the converter and runtime)
57
57
  - Python >= 3.8
58
58
  - **Solver-only mode** additionally requires the solver distribution that ships `PySolver.dll` (e.g. `D:\OSIS_Solver\Rbin64`).
59
59
 
60
+ > OSIS 5.1 HTTP server bug workaround: the `/OSIS_Run` command-stream splitter does not strip `//` line comments, so a comment line and the command that follows it are merged into one bad command. The pyosis `.out` converter already strips comments before sending; if you ever build a raw `.out` payload yourself, pre-process the file or use `osis_run()` through the generated `main.py`.
61
+
60
62
  ## Quick Start
61
63
 
62
64
  ### 1. Using OSISEngine (Recommended)
@@ -398,7 +400,7 @@ lc.create_nforce(4, fx=200000, fy=0, fz=0)
398
400
  engine.solve()
399
401
  ```
400
402
 
401
- ## End-to-End Demo
403
+ ## End-to-End Demo (Solver-Only Mode)
402
404
 
403
405
  See [`tests/pyosis_demo.py`](tests/pyosis_demo.py) for a runnable end-to-end example that:
404
406
 
@@ -408,3 +410,109 @@ See [`tests/pyosis_demo.py`](tests/pyosis_demo.py) for a runnable end-to-end exa
408
410
  - Exports LCND / LCEF / EnvND / EnvEF results to CSV
409
411
 
410
412
  The demo reuses the prep modules from `tests/output/output_py/25m简支小箱梁中梁-solveronly/`, so it serves both as a smoke test and a copy-paste template.
413
+
414
+ ## Batch Execution
415
+
416
+ For non-interactive workflows (large models, customer scripts that took tens of minutes per command), wrap your script in `with batch():` to merge **all** OSIS commands into a **single** `OSIS_Run` HTTP request when the block exits:
417
+
418
+ ```python
419
+ from pyosis import batch
420
+
421
+ with batch():
422
+ engine.clear()
423
+ engine.clc()
424
+ engine.control.set_gravity_acceleration(9.8066)
425
+ engine.material.create_conc(no=1, name="C50", ...)
426
+ for i in range(1000):
427
+ engine.node.create(no=i + 1, x=i * 0.1, y=0.0, z=0.0)
428
+ for i in range(999):
429
+ engine.element.create_beam3d(no=i + 1, node1=i + 1, node2=i + 2, ...)
430
+ ```
431
+
432
+ Server-side `OSIS_Run` calls `SplitOsisCommands` once and then a single `Execution(&v)`, so the network round-trip cost is paid exactly once instead of once per command. Intermediate reads (`get`, `all`) auto-flush the buffer first, so cached objects stay correct.
433
+
434
+ Notes:
435
+
436
+ - Nested `with batch():` is supported — the inner exit does not flush; only the outermost exit does.
437
+ - If the block raises, the buffered commands added since `__enter__` are discarded.
438
+ - `flush()` can be called inside the block to force an early flush.
439
+ - `next_no()` (auto-numbering) raises inside `batch()` because it would need to query the server mid-batch; pass `no=...` explicitly, or run auto-numbering outside the batch.
440
+
441
+ Benchmark on the `25m简支小箱梁中梁` project (`.out` of ~3000 commands): before `batch()` ~3000 round-trips; with `batch()` 3 round-trips (clear + clc + one final flush), **~1000x compression**.
442
+
443
+ ## .out → pyosis Converter
444
+
445
+ pyosis ships a converter that turns an OSIS command stream (`.out`) into a clean, runnable pyosis project. This is the easiest way to migrate a legacy APDL/SML script.
446
+
447
+ ```bash
448
+ python src/pyosis/transfer/out_to_python.py path/to/model.out path/to/output_project
449
+ ```
450
+
451
+ Generated layout:
452
+
453
+ ```
454
+ output_project/
455
+ └── prep/
456
+ ├── main.py # Entry point (batch mode, runs all 10 modules)
457
+ ├── _1_control.py # Global control parameters
458
+ ├── _2_property.py # Geometry / coordinate / damping / creep properties
459
+ ├── _3_material.py # Materials
460
+ ├── _4_section.py # Sections (incl. mesh)
461
+ ├── _5_node.py # Nodes
462
+ ├── _6_element.py # Elements
463
+ ├── _7_boundary.py # Boundaries
464
+ ├── _8_loadcase.py # Load cases
465
+ ├── _9_analysis.py # Analysis settings (settlement / live / dynamic / stability)
466
+ └── _10_stage.py # Construction stages
467
+ ```
468
+
469
+ The generated `main.py` wraps the entire modelling flow in `with batch():`, so the whole project is sent to OSIS in one HTTP request — matching the OSIS server's native batching path.
470
+
471
+ Run it:
472
+
473
+ ```bash
474
+ cd output_project
475
+ python prep/main.py # build the model
476
+ python prep/main.py --solve # build + solve
477
+ ```
478
+
479
+ Notes on the converter:
480
+
481
+ - Both long-form command names (`Node`, `Element`, `Section`, `Material`, `LoadCase`, `Boundary`, `Stage`) and OSIS 5.1 short-form aliases (`N`, `Ele`, `Sec`/`SecOff`/`SecMesh`, `Mat`, `LC`, `Bd`, `Stg`) are recognized and normalized.
482
+ - `SectionMesh` is emitted with the new `Index, PartID, MeshMethod, MeshSize` layout (Section 7.3.6.2) — `PartID = 1` for concrete sections, `2` for templated composite sections.
483
+ - `Spline3D` is dispatched to `engine.geometry.create_general` / `create_natural` / `create_arc2d` / `create_arc3d` based on the type field.
484
+ - Commands that are not yet modeled are passed through verbatim via `engine.run(...)` so the model still runs; rewrite them into pyosis calls manually if needed.
485
+
486
+ ## Chained Method Calls
487
+
488
+ The dataclass returned by `get()` supports method chaining without server round-trips:
489
+
490
+ ```python
491
+ with batch():
492
+ lc = engine.load.create("Self-weight", load_case_type="D")
493
+ lc.create_gravity()
494
+ lc.create_nforce(node=1, fx=0, fy=-1000, fz=0)
495
+ # ↑ all three commands are queued; the OSIS_Run request only fires at exit
496
+ ```
497
+
498
+ Accessing identity attributes (`lc.no`, `lc.name`) does not touch the server. Accessing data fields (`lc.type`, `lc.elements`) materializes the object (one-time flush + query), then caches it.
499
+
500
+ > OSIS 5.1 HTTP server bug workaround: the `/OSIS_Run` command-stream splitter does not strip `//` line comments, so a comment line and the command that follows it are merged into one bad command. The pyosis `.out` converter already strips comments before sending; if you ever build a raw `.out` payload yourself, pre-process the file or use `osis_run()` through the generated `main.py`.
501
+
502
+ ## End-to-End Demo (GUI Mode)
503
+
504
+ The single-file demo [`tests/demo_gui.py`](tests/demo_gui.py) is a merged version of the project generated from `tests/output/xiaoxiangliang.out`. Run it against an open OSIS 5.1+ GUI:
505
+
506
+ ```bash
507
+ python tests/demo_gui.py # build the model
508
+ python tests/demo_gui.py --solve # build + solve
509
+ ```
510
+
511
+ The file contains the engine setup, all 10 `build_*` functions (control / property / material / section / node / element / boundary / loadcase / analysis / stage), and a `build_model()` driver that wraps everything in `with batch():` — so ~250 commands hit OSIS in a single `OSIS_Run` request.
512
+
513
+ To regenerate both the single-file demo and the multi-module project:
514
+
515
+ ```bash
516
+ python tests/_merge_demo_gui.py # -> tests/demo_gui.py
517
+ python src/pyosis/transfer/out_to_python.py tests/output/xiaoxiangliang.out tests/output/output_py/xiaoxiangliang
518
+ ```
@@ -20,7 +20,6 @@ src/pyosis/core/all_func.py
20
20
  src/pyosis/core/apdl_sync.py
21
21
  src/pyosis/core/basic_manager.py
22
22
  src/pyosis/core/batch.py
23
- src/pyosis/core/build.py
24
23
  src/pyosis/core/client.py
25
24
  src/pyosis/core/command.py
26
25
  src/pyosis/core/engine.py
@@ -7,8 +7,7 @@ A library for extending OSIS functionality.
7
7
  A library that allows users to use CAE with great flexibility.
8
8
  """
9
9
 
10
- __version__ = "0.7.1"
11
- __author__ = "CCCC Highway Consultant Co. Ltd." # 中交公路规划设计院 - 工程技术研究中心 - lzh - wjh - myf
10
+ __version__ = "0.7.3"
12
11
 
13
12
  from .core.engine import OSISEngine
14
13
  from .core.batch import batch, flush, batch_state, BatchError
@@ -536,7 +536,7 @@ class BoundaryGroupManager(BasicManager):
536
536
  Returns:
537
537
  BoundaryGroup: 创建(或刷新后的)边界组对象
538
538
  '''
539
- ok, err = osis_boundary_group(name, op, *param)
539
+ ok, err = osis_boundary_group(name, str(op).lower(), *param)
540
540
  if not ok:
541
541
  raise RuntimeError(f"创建边界组 {name} 失败: {err}")
542
542
  return self.get(name)
@@ -316,7 +316,7 @@ class ElementGroupManager(BasicManager):
316
316
  Returns:
317
317
  ElementGroup: 创建的单元组对象
318
318
  """
319
- ok, err = osis_element_group(name, op, *param)
319
+ ok, err = osis_element_group(name, str(op).lower(), *param)
320
320
  if not ok:
321
321
  raise RuntimeError(f"创建单元组 {name} 失败: {err}")
322
322
  return self.get(name)
@@ -265,6 +265,7 @@ class LiveCase:
265
265
  Returns:
266
266
  更新后的 LiveCase 对象
267
267
  """
268
+ op = str(op).lower()
268
269
  if op in ("a", "m"):
269
270
  _MU_PARAM_COUNT = {
270
271
  "SIMPLE": 4,
@@ -278,7 +279,7 @@ class LiveCase:
278
279
  grade_name, scalar, mu_flag = args[0], float(args[1]), int(args[2])
279
280
  rest = args[4:]
280
281
  if mu_flag == 1:
281
- bridge_type = args[3] or "SIMPLE"
282
+ bridge_type = (args[3] or "SIMPLE").strip().upper()
282
283
  mu_count = _MU_PARAM_COUNT.get(bridge_type, 1)
283
284
  mu_params: list[float] = [float(x) for x in rest[:mu_count]]
284
285
  lane_names: list[str] = list(rest[mu_count:])
@@ -295,6 +295,8 @@ class LoadCase:
295
295
  elem, e_coord, n_range, *rest = args
296
296
  forces = [list(rest[i: i + 6]) for i in range(0, len(rest), 6)]
297
297
  return self.create_concentrated_force(elem,e_coord,is_moment=(type_key == "PTM"),forces=forces)
298
+ if type_key not in _creator:
299
+ raise ValueError(f"未知荷载类型: {type!r},支持: {', '.join(_creator)}")
298
300
  return _creator[type_key](*args, **kwargs)
299
301
 
300
302
  def create_gravity(
@@ -158,9 +158,10 @@ class MaterialManager(BasicManager):
158
158
  "REBAR": self.create_rebar,
159
159
  "CUSTOM": self.create_custom,
160
160
  }
161
- if type not in _creator:
162
- raise ValueError(f"未知材料类型: {type}")
163
- return _creator[type](no, name, *args, **kwargs)
161
+ type_key = str(type).strip().upper()
162
+ if type_key not in _creator:
163
+ raise ValueError(f"未知材料类型: {type!r},支持: {', '.join(_creator)}")
164
+ return _creator[type_key](no, name, *args, **kwargs)
164
165
 
165
166
  def create_conc(
166
167
  self,
@@ -518,9 +518,9 @@ class DampingManager(BasicManager):
518
518
  raise ValueError(
519
519
  f"未知阻尼类型: {type!r},支持: {', '.join(sorted(_supported_types))}"
520
520
  )
521
- if type_key == "modal":
521
+ if type_key == "MODAL":
522
522
  return self.create_modal(name, *args, **kwargs)
523
- # type_key == "ryl"
523
+ # type_key == "RYL"
524
524
  method = args[0] if args else kwargs.get("method")
525
525
  _supported_methods = {0: "根据公式计算因子", 1: "自定义因子"}
526
526
  if method not in _supported_methods:
@@ -384,15 +384,18 @@ class Section:
384
384
  """添加或修改组合截面 Part(按类型分发)
385
385
 
386
386
  Args:
387
- type: Part 类型
387
+ type: Part 类型(大小写不敏感)
388
388
  * POLYGON = 多边形
389
389
  * LINE = 折线
390
390
  args: 剩余参数透传给对应子方法
391
391
  """
392
- if type == "POLYGON":
392
+ key = str(type).upper()
393
+ if key == "POLYGON":
393
394
  self.add_composite_part_polygon(*args)
394
- elif type == "LINE":
395
+ elif key == "LINE":
395
396
  self.add_composite_part_line(*args)
397
+ else:
398
+ raise ValueError(f"未知 Part 类型: {type!r},支持 POLYGON/LINE")
396
399
 
397
400
  # ── 组合截面材料 ──────────────────────────
398
401
 
@@ -682,21 +685,26 @@ class Section:
682
685
 
683
686
  Args:
684
687
  rebar_no: 钢筋编号
685
- type: 钢筋输入方式
688
+ type: 钢筋输入方式(大小写不敏感,兼容命令流全大写写法 LINEA 等)
686
689
  * Point = 点输入
687
690
  * LineA = 直线-垂直方式 A
688
691
  * LineB = 直线-垂直方式 B
689
692
  * Circle = 圆周输入
690
693
  args: 剩余参数透传给对应子方法
691
694
  """
692
- if type == "Point":
695
+ key = str(type).upper()
696
+ if key == "POINT":
693
697
  self.add_rebar_point(rebar_no, *args)
694
- elif type == "LineA":
698
+ elif key == "LINEA":
695
699
  self.add_rebar_line_a(rebar_no, *args)
696
- elif type == "LineB":
700
+ elif key == "LINEB":
697
701
  self.add_rebar_line_b(rebar_no, *args)
698
- elif type == "Circle":
702
+ elif key == "CIRCLE":
699
703
  self.add_rebar_circle(rebar_no, *args)
704
+ else:
705
+ raise ValueError(
706
+ f"未知纵向钢筋类型: {type!r},支持 Point/LineA/LineB/Circle"
707
+ )
700
708
 
701
709
  def add_rebar_s(
702
710
  self,
@@ -706,21 +714,26 @@ class Section:
706
714
  """添加或修改抗剪钢筋(按类型分发)
707
715
 
708
716
  Args:
709
- type: 钢筋类型
717
+ type: 钢筋类型(大小写不敏感,兼容 SHEARSTIRRUP 等命令流写法)
710
718
  * BentUpRebar = 弯起钢筋
711
719
  * ShearStirrup = 抗剪箍筋
712
720
  * WebVerticalRebar = 腹板竖筋
713
721
  * TorsionalStirrup = 扭转箍筋
714
722
  args: 剩余参数透传给对应子方法
715
723
  """
716
- if type == "BentUpRebar":
724
+ key = str(type).upper()
725
+ if key == "BENTUPREBAR":
717
726
  self.add_rebar_s_bent_up(*args)
718
- elif type == "ShearStirrup":
727
+ elif key == "SHEARSTIRRUP":
719
728
  self.add_rebar_s_shear_stirrup(*args)
720
- elif type == "WebVerticalRebar":
729
+ elif key == "WEBVERTICALREBAR":
721
730
  self.add_rebar_s_web_vertical(*args)
722
- elif type == "TorsionalStirrup":
731
+ elif key == "TORSIONALSTIRRUP":
723
732
  self.add_rebar_s_torsional_stirrup(*args)
733
+ else:
734
+ raise ValueError(
735
+ f"未知抗剪钢筋类型: {type!r},支持 BentUpRebar/ShearStirrup/WebVerticalRebar/TorsionalStirrup"
736
+ )
724
737
 
725
738
  def add_rib(
726
739
  self,
@@ -730,7 +743,7 @@ class Section:
730
743
  """添加或修改加劲肋(按类型分发)
731
744
 
732
745
  Args:
733
- type: 加劲肋类型
746
+ type: 加劲肋类型(大小写不敏感)
734
747
  * Flat = 扁平加劲肋
735
748
  * T = T 形加劲肋
736
749
  * U = U 形加劲肋
@@ -738,14 +751,19 @@ class Section:
738
751
  * LR = 右 L 形加劲肋
739
752
  args: 剩余参数透传给对应子方法
740
753
  """
741
- if type == "Flat":
754
+ key = str(type).upper()
755
+ if key == "FLAT":
742
756
  self.add_rib_flat(*args)
743
- elif type == "T":
757
+ elif key == "T":
744
758
  self.add_rib_t(*args)
745
- elif type == "U":
759
+ elif key == "U":
746
760
  self.add_rib_u(*args)
747
- elif type in ("LL", "LR"):
748
- self.add_rib_l(args[0], type, *args[1:])
761
+ elif key in ("LL", "LR"):
762
+ self.add_rib_l(args[0], key, *args[1:])
763
+ else:
764
+ raise ValueError(
765
+ f"未知加劲肋类型: {type!r},支持 Flat/T/U/LL/LR"
766
+ )
749
767
 
750
768
  def add_rib_flat(
751
769
  self,
@@ -909,15 +927,22 @@ class Section:
909
927
  '''删除箍筋
910
928
 
911
929
  Args:
912
- rebar_type (str): 钢筋类型
930
+ rebar_type (str): 钢筋类型(大小写不敏感)
913
931
  * BentUpRebar = 弯起钢筋
914
932
  * ShearStirrup = 抗剪箍筋
915
933
  * WebVerticalRebar = 腹板竖筋
916
934
  * TorsionalStirrup = 扭转箍筋
917
935
 
918
936
  Raises:
937
+ ValueError: 未知的钢筋类型
919
938
  RuntimeError: 删除失败时抛出
920
939
  '''
940
+ if str(rebar_type).strip().upper() not in (
941
+ "BENTUPREBAR", "SHEARSTIRRUP", "WEBVERTICALREBAR", "TORSIONALSTIRRUP",
942
+ ):
943
+ raise ValueError(
944
+ f"未知抗剪钢筋类型: {rebar_type!r},支持 BentUpRebar/ShearStirrup/WebVerticalRebar/TorsionalStirrup"
945
+ )
921
946
  ok, err = osis_rebar_s_del(self.no, rebar_type)
922
947
  if not ok:
923
948
  raise RuntimeError(f"删除截面 {self.no} 箍筋 {rebar_type} 失败: {err}")