├── data ├── sessions │ └── PLACEHOLDER ├── debugging │ ├── .gitignore │ ├── zero_modified.obj │ ├── truss.obj │ ├── zero.obj │ └── howe.obj ├── paper │ ├── three_bar_problem.obj │ ├── gs_form_force-03.obj │ ├── gs_form_force.obj │ ├── gs_form_force-04.obj │ ├── funicular.obj │ ├── spider_symm.obj │ ├── gs_arch.json │ ├── fink.obj │ ├── fink_interaction.obj │ ├── two-rings.obj │ ├── gs_truss.obj │ ├── exD_truss.obj │ ├── exA_arch-circular.obj │ ├── discretised_spline.obj │ ├── discretised_arc.obj │ ├── exB_arch-output.json │ └── 3hinged.obj ├── examples │ └── truss2.obj └── forms │ └── zero.ags ├── docs ├── _images │ ├── PLACEHOLDER │ ├── AGS_intro.png │ ├── example_fink.png │ ├── example_rtl.png │ ├── example_basic.png │ └── example_lpopt.png ├── api.rst ├── api │ ├── compas_ags.rst │ ├── compas_ags.exceptions.rst │ ├── compas_ags.diagrams.rst │ └── compas_ags.ags.rst ├── examples.rst ├── examples │ ├── 00_basic.rst │ ├── 03_fink.rst │ ├── 01_rtl.rst │ ├── 02_lpopt.rst │ ├── 00_basic.py │ ├── 01_rtl.py │ ├── 03_fink.py │ └── 02_lpopt.py ├── publications.rst ├── installation.rst ├── index.rst ├── publications │ ├── bi-ags.rst │ ├── loadpath.rst │ ├── interactive-ags.rst │ └── ags.rst ├── license.rst └── conf.py ├── requirements.txt ├── compas_ags.png ├── src └── compas_ags │ ├── viewer │ ├── __init__.py │ └── viewer.py │ ├── exceptions │ ├── __init__.py │ └── errorhandler.py │ ├── diagrams │ ├── __init__.py │ ├── diagram.py │ └── formgraph.py │ ├── __init__.py │ └── ags │ └── __init__.py ├── tests └── test_dummy.py ├── scripts ├── test_rpc.py ├── test_dof.py ├── test_loadpath.py ├── test_data.py ├── test_shelve.py ├── test_where_dual.py ├── test_artists.py ├── test_rtl_debug.py ├── test_scene.py ├── test_objects.py ├── test_scale.py ├── test_zero_rhino_interactive.py ├── test_deviations.py ├── test_zero_rhino.py ├── test_zero.py ├── test_sync_ltr.py ├── test_sync_rtl.py ├── example_fix_x_AGS.py ├── example_fix_x.py ├── example_detect_lineloads.py ├── example_truss.py ├── paper-CSD │ ├── exampleB_dragging.py │ ├── exampleA_arch-input.py │ ├── exampleC_dragging.py │ └── exampleD_truss_constant.py ├── example_interactive.py ├── example_nullspace.py └── example_truss_edge_orientation.py ├── requirements-dev.txt ├── AUTHORS.md ├── .github ├── workflows │ ├── docs.yml │ ├── pr-checks.yml │ ├── build.yml │ └── release.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── pull_request_template.md └── CODE_OF_CONDUCT.md ├── .editorconfig ├── tasks.py ├── LICENSE ├── README.md ├── CHANGELOG.md ├── .gitignore └── pyproject.toml /data/sessions/PLACEHOLDER: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_images/PLACEHOLDER: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | compas 2 | sympy 3 | -------------------------------------------------------------------------------- /data/debugging/.gitignore: -------------------------------------------------------------------------------- 1 | /inspectortest.ags 2 | -------------------------------------------------------------------------------- /compas_ags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockResearchGroup/compas_ags/HEAD/compas_ags.png -------------------------------------------------------------------------------- /src/compas_ags/viewer/__init__.py: -------------------------------------------------------------------------------- 1 | from .viewer import AGSViewer 2 | 3 | __all__ = ["AGSViewer"] 4 | -------------------------------------------------------------------------------- /docs/_images/AGS_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockResearchGroup/compas_ags/HEAD/docs/_images/AGS_intro.png -------------------------------------------------------------------------------- /docs/_images/example_fink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockResearchGroup/compas_ags/HEAD/docs/_images/example_fink.png -------------------------------------------------------------------------------- /docs/_images/example_rtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockResearchGroup/compas_ags/HEAD/docs/_images/example_rtl.png -------------------------------------------------------------------------------- /docs/_images/example_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockResearchGroup/compas_ags/HEAD/docs/_images/example_basic.png -------------------------------------------------------------------------------- /docs/_images/example_lpopt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockResearchGroup/compas_ags/HEAD/docs/_images/example_lpopt.png -------------------------------------------------------------------------------- /src/compas_ags/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .errorhandler import SolutionError 2 | 3 | __all__ = [ 4 | "SolutionError", 5 | ] 6 | -------------------------------------------------------------------------------- /tests/test_dummy.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | 3 | 4 | def test_trivial(): 5 | print(compas_ags.__version__) 6 | assert True 7 | -------------------------------------------------------------------------------- /scripts/test_rpc.py: -------------------------------------------------------------------------------- 1 | from compas.rpc import Proxy 2 | 3 | graphstatics = Proxy('compas_ags.ags.graphstatics') 4 | graphstatics.restart_server() 5 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | attrs >=17.4 2 | black >=22.12.0 3 | bump-my-version 4 | compas_invocations2 5 | compas_notebook 6 | compas_viewer 7 | invoke >=0.14 8 | ruff 9 | sphinx_compas2_theme 10 | twine 11 | wheel 12 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | API Reference 3 | ******************************************************************************** 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | api/compas_ags 9 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | 3 | - Tom Van Mele <> [@brgcode](https://github.com/brgcode) 4 | - Vedad Alic <> [@vedadalic](https://github.com/vedadalic) 5 | - Andrew Liew <> [@andrewliew](https://github.com/andrewliew) 6 | -------------------------------------------------------------------------------- /src/compas_ags/exceptions/errorhandler.py: -------------------------------------------------------------------------------- 1 | class SolutionError(Exception): 2 | """Used to throw solution errors during form or force computations.""" 3 | 4 | def __init__(self, value): 5 | self.value = value 6 | 7 | def __str__(self): 8 | return repr(self.value) 9 | -------------------------------------------------------------------------------- /src/compas_ags/diagrams/__init__.py: -------------------------------------------------------------------------------- 1 | from .formgraph import FormGraph 2 | from .diagram import Diagram 3 | from .formdiagram import FormDiagram 4 | from .forcediagram import ForceDiagram 5 | 6 | __all__ = [ 7 | "FormGraph", 8 | "Diagram", 9 | "FormDiagram", 10 | "ForceDiagram", 11 | ] 12 | -------------------------------------------------------------------------------- /docs/api/compas_ags.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | compas_ags 3 | ******************************************************************************** 4 | 5 | .. currentmodule:: compas_ags 6 | 7 | .. toctree:: 8 | :maxdepth: 1 9 | 10 | compas_ags.ags 11 | compas_ags.diagrams 12 | compas_ags.exceptions 13 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Examples 3 | ******************************************************************************** 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | :titlesonly: 8 | :glob: 9 | 10 | examples/00_basic 11 | examples/01_rtl 12 | examples/02_lpopt 13 | examples/03_fink 14 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "v*" 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | docs: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: compas-dev/compas-actions.docs@v4 18 | with: 19 | github_token: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /docs/api/compas_ags.exceptions.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | compas_ags.exceptions 3 | ******************************************************************************** 4 | 5 | .. currentmodule:: compas_ags.exceptions 6 | 7 | 8 | Classes 9 | ======= 10 | 11 | .. autosummary:: 12 | :toctree: generated/ 13 | 14 | SolutionError 15 | -------------------------------------------------------------------------------- /docs/examples/00_basic.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Basic example 3 | ******************************************************************************** 4 | 5 | .. figure:: /_images/example_basic.png 6 | :figclass: figure 7 | :class: figure-img img-fluid 8 | 9 | 10 | .. literalinclude:: 00_basic.py 11 | :language: python 12 | -------------------------------------------------------------------------------- /docs/examples/03_fink.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Fink Truss 3 | ******************************************************************************** 4 | 5 | .. figure:: /_images/example_fink.png 6 | :figclass: figure 7 | :class: figure-img img-fluid 8 | 9 | 10 | .. literalinclude:: 03_fink.py 11 | :language: python 12 | 13 | -------------------------------------------------------------------------------- /docs/examples/01_rtl.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Updating the form diagram 3 | ******************************************************************************** 4 | 5 | .. figure:: /_images/example_rtl.png 6 | :figclass: figure 7 | :class: figure-img img-fluid 8 | 9 | 10 | .. literalinclude:: 01_rtl.py 11 | :language: python 12 | 13 | -------------------------------------------------------------------------------- /docs/publications.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Publications 3 | ******************************************************************************** 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | :titlesonly: 8 | :glob: 9 | 10 | publications/ags 11 | publications/bi-ags 12 | publications/interactive-ags 13 | publications/loadpath 14 | -------------------------------------------------------------------------------- /docs/examples/02_lpopt.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Optimise the load path 3 | ******************************************************************************** 4 | 5 | .. figure:: /_images/example_lpopt.png 6 | :figclass: figure 7 | :class: figure-img img-fluid 8 | 9 | 10 | .. literalinclude:: 02_lpopt.py 11 | :language: python 12 | 13 | -------------------------------------------------------------------------------- /scripts/test_dof.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from compas_ags.diagrams import FormGraph 4 | from compas_ags.diagrams import FormDiagram 5 | from compas.rpc import Proxy 6 | 7 | HERE = os.path.dirname(__file__) 8 | FILE = os.path.join(HERE, '../data/paper/gs_form_force.obj') 9 | 10 | graphstatics = Proxy('compas_ags.ags.graphstatics') 11 | 12 | graph = FormGraph.from_obj(FILE) 13 | form = FormDiagram.from_graph(graph) 14 | 15 | dof = graphstatics.form_count_dof(form) 16 | 17 | print(dof) 18 | -------------------------------------------------------------------------------- /docs/api/compas_ags.diagrams.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | compas_ags.diagrams 3 | ******************************************************************************** 4 | 5 | .. currentmodule:: compas_ags.diagrams 6 | 7 | Graphs 8 | ====== 9 | 10 | .. autosummary:: 11 | :toctree: generated/ 12 | 13 | FormGraph 14 | 15 | Diagrams 16 | ======== 17 | 18 | .. autosummary:: 19 | :toctree: generated/ 20 | 21 | Diagram 22 | FormDiagram 23 | ForceDiagram 24 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 4 11 | charset = utf-8 12 | max_line_length = 180 13 | 14 | [*.{bat,cmd,ps1}] 15 | end_of_line = crlf 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | indent_size = 4 23 | 24 | [*.yml] 25 | indent_style = space 26 | indent_size = 2 27 | 28 | [LICENSE] 29 | insert_final_newline = false 30 | -------------------------------------------------------------------------------- /scripts/test_loadpath.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from compas_ags.diagrams import FormGraph 4 | from compas_ags.diagrams import ForceDiagram 5 | from compas_ags.diagrams import FormDiagram 6 | from compas.rpc import Proxy 7 | 8 | HERE = os.path.dirname(__file__) 9 | FILE = os.path.join(HERE, '../data/paper/gs_form_force.obj') 10 | 11 | graph = FormGraph.from_obj(FILE) 12 | form = FormDiagram.from_graph(graph) 13 | force = ForceDiagram.from_formdiagram(form) 14 | 15 | loadpath = Proxy('compas_ags.ags.loadpath') 16 | 17 | lp = loadpath.compute_loadpath(form, force) 18 | 19 | print(lp) 20 | -------------------------------------------------------------------------------- /src/compas_ags/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | HERE = os.path.abspath(os.path.dirname(__file__)) 4 | HOME = os.path.abspath(os.path.join(HERE, "../../")) 5 | DATA = os.path.join(HOME, "data") 6 | TEMP = os.path.join(HOME, "temp") 7 | 8 | 9 | __author__ = "Tom Van Mele and others" 10 | __copyright__ = "Copyright 2014-2018 - Block Research Group, ETH Zurich" 11 | __license__ = "MIT License" 12 | __email__ = "vanmelet@ethz.ch" 13 | __version__ = "1.3.3" 14 | 15 | 16 | def get(relpath): 17 | return os.path.join(DATA, relpath) 18 | 19 | 20 | __all__ = ["HOME", "DATA", "DOCS", "TEMP"] 21 | -------------------------------------------------------------------------------- /scripts/test_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | from compas_ags.diagrams import FormDiagram 5 | from compas_ags.diagrams import ForceDiagram 6 | 7 | HERE = os.path.dirname(__file__) 8 | FILE = os.path.join(HERE, '../data/forms/howe_modified.ags') 9 | 10 | with open(FILE, 'r') as f: 11 | data = json.load(f) 12 | 13 | form = FormDiagram.from_data(data['data']['form']) 14 | force = ForceDiagram.from_data(data['data']['force']) 15 | form.dual = force 16 | force.dual = form 17 | 18 | print(form.edgedata) 19 | 20 | print(len(list(form.edges()))) 21 | print(len(list(force.edges()))) 22 | -------------------------------------------------------------------------------- /.github/workflows/pr-checks.yml: -------------------------------------------------------------------------------- 1 | name: verify-pr-checklist 2 | on: 3 | pull_request: 4 | types: [assigned, opened, synchronize, reopened, labeled, unlabeled] 5 | branches: 6 | - main 7 | - master 8 | 9 | jobs: 10 | build: 11 | name: Check Actions 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Changelog check 16 | uses: Zomzog/changelog-checker@v1.2.0 17 | with: 18 | fileName: CHANGELOG.md 19 | checkNotification: Simple 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /scripts/test_shelve.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pickle 3 | import shelve 4 | 5 | from compas.geometry import Point 6 | 7 | from compas_ags.diagrams import FormGraph 8 | from compas_ags.diagrams import FormDiagram 9 | 10 | HERE = os.path.dirname(__file__) 11 | FILE = os.path.join(HERE, '../data/debugging/truss.obj') 12 | 13 | graph = FormGraph.from_obj(FILE) 14 | form = FormDiagram.from_graph(graph) 15 | 16 | s = pickle.dumps(form) 17 | 18 | db = shelve.open('test', 'n', 2, False) 19 | 20 | db['form'] = form.data 21 | 22 | print(db['form']) 23 | 24 | p = Point(0, 0, 0) 25 | 26 | db['p'] = p.data 27 | 28 | print(db['p']) 29 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | Build: 13 | if: "!contains(github.event.pull_request.labels.*.name, 'docs-only')" 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [macos-latest, windows-latest, ubuntu-latest] 18 | python: ["3.9", "3.10", "3.11", "3.12"] 19 | 20 | steps: 21 | - uses: compas-dev/compas-actions.build@v4 22 | with: 23 | invoke_lint: true 24 | invoke_test: true 25 | python: ${{ matrix.python }} 26 | -------------------------------------------------------------------------------- /data/paper/three_bar_problem.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 3 3 0 4 | v 3 5 0 5 | cstype bspline 6 | deg 1 7 | curv 0 2.23606797749979 1 2 8 | parm u 0 0 2.23606797749979 2.23606797749979 9 | end 10 | v 3 3 0 11 | v 10 0 0 12 | cstype bspline 13 | deg 1 14 | curv 5.8309518948453 14.43327716188793 3 4 15 | parm u 5.8309518948453 5.8309518948453 14.43327716188793 14.43327716188793 16 | end 17 | v 0 0 0 18 | v 3 3 0 19 | cstype bspline 20 | deg 1 21 | curv 0 5.8309518948453 5 6 22 | parm u 0 0 5.8309518948453 5.8309518948453 23 | end 24 | v 4 0 0 25 | v 3 3 0 26 | cstype bspline 27 | deg 1 28 | curv 0 5.099019513592785 7 8 29 | parm u 0 0 5.099019513592785 5.099019513592785 30 | end 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | # Feature Request 7 | 8 | As a [role], I want [something] so that [benefit]. 9 | 10 | ## Details 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | 14 | A clear and concise description of what the problem is. 15 | 16 | **Describe the solution you'd like.** 17 | 18 | A clear and concise description of what you want to happen. 19 | 20 | **Describe alternatives you've considered.** 21 | 22 | A clear and concise description of any alternative solutions or features you've considered. 23 | 24 | **Additional context.** 25 | 26 | Add any other context or screenshots about the feature request here. 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - "v*" 5 | 6 | name: Create Release 7 | 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [macos-latest, windows-latest, ubuntu-latest] 14 | python: ["3.9", "3.10", "3.11", "3.12"] 15 | 16 | steps: 17 | - uses: compas-dev/compas-actions.build@v4 18 | with: 19 | invoke_lint: true 20 | invoke_test: true 21 | python: ${{ matrix.python }} 22 | 23 | Publish: 24 | needs: build 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: compas-dev/compas-actions.publish@v3 28 | with: 29 | pypi_token: ${{ secrets.PYPI }} 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | 9 | A clear and concise description of what the bug is. 10 | 11 | **To Reproduce** 12 | 13 | Steps to reproduce the behavior: 14 | 15 | 1. Context [e.g. ST3, Rhino, Blender, ...] 16 | 2. Sample script 17 | 3. Sample data 18 | 4. See error 19 | 20 | **Expected behavior** 21 | 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | **Desktop (please complete the following information):** 29 | 30 | - OS: [e.g. iOS] 31 | - Python version [e.g. 2.7] 32 | - Python package manager [e.g. macports, pip, conda] 33 | 34 | **Additional context** 35 | 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import os 4 | 5 | from compas_invocations2 import build 6 | from compas_invocations2 import docs 7 | from compas_invocations2 import style 8 | from compas_invocations2 import tests 9 | from invoke import Collection 10 | 11 | ns = Collection( 12 | docs.help, 13 | style.check, 14 | style.lint, 15 | style.format, 16 | docs.docs, 17 | docs.linkcheck, 18 | tests.test, 19 | tests.testdocs, 20 | tests.testcodeblocks, 21 | build.prepare_changelog, 22 | build.clean, 23 | build.release, 24 | build.build_ghuser_components, 25 | ) 26 | ns.configure( 27 | { 28 | "base_folder": os.path.dirname(__file__), 29 | "ghuser": { 30 | "source_dir": "src/compas_ghpython/components", 31 | "target_dir": "src/compas_ghpython/components/ghuser", 32 | "prefix": "COMPAS: ", 33 | }, 34 | } 35 | ) 36 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Installation 3 | ******************************************************************************** 4 | 5 | Stable 6 | ====== 7 | 8 | Stable releases are available on PyPI and can be installed with pip. 9 | 10 | .. code-block:: bash 11 | 12 | pip install compas_ags 13 | 14 | 15 | Latest 16 | ====== 17 | 18 | The latest version can be installed from local source. 19 | 20 | .. code-block:: bash 21 | 22 | git clone https://github.com/blockresearchgroup/compas_ags.git 23 | cd compas_ags 24 | pip install -e . 25 | 26 | 27 | Development 28 | =========== 29 | 30 | To install `compas_ags` for development, install from local source with the "dev" requirements. 31 | 32 | .. code-block:: bash 33 | 34 | git clone https://github.com/blockresearchgroup/compas_ags.git 35 | cd compas_ags 36 | pip install -e ".[dev]" 37 | -------------------------------------------------------------------------------- /docs/api/compas_ags.ags.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | compas_ags.ags 3 | ******************************************************************************** 4 | 5 | .. currentmodule:: compas_ags.ags 6 | 7 | 8 | Core 9 | ==== 10 | 11 | .. autosummary:: 12 | :toctree: generated/ 13 | 14 | update_q_from_qind 15 | update_primal_from_dual 16 | 17 | 18 | Graph Statics 19 | ============= 20 | 21 | .. autosummary:: 22 | :toctree: generated/ 23 | 24 | form_count_dof 25 | form_identify_dof 26 | form_update_q_from_qind 27 | form_update_from_force 28 | force_update_from_form 29 | 30 | 31 | Load Path 32 | ========= 33 | 34 | .. autosummary:: 35 | :toctree: generated/ 36 | 37 | compute_loadpath 38 | compute_external_work 39 | compute_internal_work 40 | compute_internal_work_tension 41 | compute_internal_work_compression 42 | optimise_loadpath 43 | 44 | -------------------------------------------------------------------------------- /scripts/test_where_dual.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | 3 | from compas_ags.diagrams import FormGraph 4 | from compas_ags.diagrams import FormDiagram 5 | from compas_ags.diagrams import ForceDiagram 6 | 7 | FILE = compas_ags.get('debugging/zero.obj') 8 | 9 | graph = FormGraph.from_obj(FILE) 10 | form = FormDiagram.from_graph(graph) 11 | force = ForceDiagram.from_formdiagram(form) 12 | 13 | form.edge_force((0, 1), +10.0) 14 | form.edge_force((2, 3), +10.0) 15 | form.edge_force((4, 5), +10.0) 16 | 17 | # for edge in force.edges_where_dual({'is_external': True}): 18 | # dual_edge = force.dual_edge(edge) 19 | # print(edge, (form.halfedge[dual_edge[0]][dual_edge[1]], form.halfedge[dual_edge[1]][dual_edge[0]])) 20 | 21 | # print(list(form.edges())) 22 | # print(form.edge_index()) 23 | print(list(force.edges())) 24 | # print([force.dual_edge(edge) for edge in force.edges()]) 25 | print(all(form.has_edge(force.dual_edge(edge)) for edge in force.edges())) 26 | print(list(force.edges_where_dual({'is_ind': True}))) 27 | -------------------------------------------------------------------------------- /data/paper/gs_form_force-03.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 6 0 0 4 | v 0 0 0 5 | cstype bspline 6 | deg 1 7 | curv 8.485281374238571 14.48528137423857 1 2 8 | parm u 8.485281374238571 8.485281374238571 14.48528137423857 14.48528137423857 9 | end 10 | v 3 3 0 11 | v 6 0 0 12 | cstype bspline 13 | deg 1 14 | curv 4.242640687119286 8.485281374238571 3 4 15 | parm u 4.242640687119286 4.242640687119286 8.485281374238571 8.485281374238571 16 | end 17 | v 0 0 0 18 | v 3 3 0 19 | cstype bspline 20 | deg 1 21 | curv 0 4.242640687119286 5 6 22 | parm u 0 0 4.242640687119286 4.242640687119286 23 | end 24 | v 0 0 0 25 | v -2 0 0 26 | cstype bspline 27 | deg 1 28 | curv 2 4 7 8 29 | parm u 2 2 4 4 30 | end 31 | v 0 -2 0 32 | v 0 0 0 33 | cstype bspline 34 | deg 1 35 | curv 0 2 9 10 36 | parm u 0 0 2 2 37 | end 38 | v 3 3 0 39 | v 4 6 0 40 | cstype bspline 41 | deg 1 42 | curv 0 3.16227766016838 11 12 43 | parm u 0 0 3.16227766016838 3.16227766016838 44 | end 45 | v 6 -2 0 46 | v 6 0 0 47 | cstype bspline 48 | deg 1 49 | curv 0 2 13 14 50 | parm u 0 0 2 2 51 | end 52 | -------------------------------------------------------------------------------- /data/paper/gs_form_force.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 3 3 0 4 | v 3.669563106796117 5.008689320388349 0 5 | cstype bspline 6 | deg 1 7 | curv 0 3.16227766016838 1 2 8 | parm u 0 0 3.16227766016838 3.16227766016838 9 | end 10 | v 6 -2 0 11 | v 6 0 0 12 | cstype bspline 13 | deg 1 14 | curv 0 2 3 4 15 | parm u 0 0 2 2 16 | end 17 | v 0 -2 0 18 | v 0 0 0 19 | cstype bspline 20 | deg 1 21 | curv 0 2 5 6 22 | parm u 0 0 2 2 23 | end 24 | v 0 0 0 25 | v -2 0 0 26 | cstype bspline 27 | deg 1 28 | curv 2 4 7 8 29 | parm u 2 2 4 4 30 | end 31 | v 0 0 0 32 | v 3 3 0 33 | cstype bspline 34 | deg 1 35 | curv 0 4.242640687119286 9 10 36 | parm u 0 0 4.242640687119286 4.242640687119286 37 | end 38 | v 3 3 0 39 | v 6 0 0 40 | cstype bspline 41 | deg 1 42 | curv 4.242640687119286 8.485281374238571 11 12 43 | parm u 4.242640687119286 4.242640687119286 8.485281374238571 8.485281374238571 44 | end 45 | v 6 0 0 46 | v 0 0 0 47 | cstype bspline 48 | deg 1 49 | curv 8.485281374238571 14.48528137423857 13 14 50 | parm u 8.485281374238571 8.485281374238571 14.48528137423857 14.48528137423857 51 | end 52 | -------------------------------------------------------------------------------- /src/compas_ags/diagrams/diagram.py: -------------------------------------------------------------------------------- 1 | from compas.datastructures import Mesh 2 | 3 | 4 | class Diagram(Mesh): 5 | """Basic mesh-based data structure for diagrams in AGS. 6 | 7 | Attributes 8 | ---------- 9 | dual : :class:`compas_ags.diagrams.Diagram` 10 | The dual diagram of this diagram. 11 | 12 | """ 13 | 14 | def __init__(self, **kwargs): 15 | super().__init__(**kwargs) 16 | self._dual = None 17 | 18 | @property 19 | def dual(self): 20 | return self._dual 21 | 22 | @dual.setter 23 | def dual(self, dual): 24 | self._dual = dual 25 | 26 | def vertex_index(self): 27 | return {vertex: index for index, vertex in enumerate(self.vertices())} 28 | 29 | def index_vertex(self): 30 | return {index: vertex for index, vertex in enumerate(self.vertices())} 31 | 32 | def edge_index(self): 33 | return {edge: index for index, edge in enumerate(self.edges())} 34 | 35 | def index_edge(self): 36 | return {index: edge for index, edge in enumerate(self.edges())} 37 | -------------------------------------------------------------------------------- /data/paper/gs_form_force-04.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 6 0 0 4 | v 8 0 0 5 | cstype bspline 6 | deg 1 7 | curv 0 2 1 2 8 | parm u 0 0 2 2 9 | end 10 | v 6 0 0 11 | v 0 0 0 12 | cstype bspline 13 | deg 1 14 | curv 8.485281374238571 14.48528137423857 3 4 15 | parm u 8.485281374238571 8.485281374238571 14.48528137423857 14.48528137423857 16 | end 17 | v 3 3 0 18 | v 6 0 0 19 | cstype bspline 20 | deg 1 21 | curv 4.242640687119286 8.485281374238571 5 6 22 | parm u 4.242640687119286 4.242640687119286 8.485281374238571 8.485281374238571 23 | end 24 | v 0 0 0 25 | v 3 3 0 26 | cstype bspline 27 | deg 1 28 | curv 0 4.242640687119286 7 8 29 | parm u 0 0 4.242640687119286 4.242640687119286 30 | end 31 | v 0 0 0 32 | v -2 0 0 33 | cstype bspline 34 | deg 1 35 | curv 2 4 9 10 36 | parm u 2 2 4 4 37 | end 38 | v 0 -2 0 39 | v 0 0 0 40 | cstype bspline 41 | deg 1 42 | curv 0 2 11 12 43 | parm u 0 0 2 2 44 | end 45 | v 3 3 0 46 | v 4 6 0 47 | cstype bspline 48 | deg 1 49 | curv 0 3.16227766016838 13 14 50 | parm u 0 0 3.16227766016838 3.16227766016838 51 | end 52 | v 6 -2 0 53 | v 6 0 0 54 | cstype bspline 55 | deg 1 56 | curv 0 2 15 16 57 | parm u 0 0 2 2 58 | end 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Block Research Group, ETH Zurich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/test_artists.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from compas_ags.diagrams import FormGraph 4 | from compas_ags.diagrams import FormDiagram 5 | from compas_ags.diagrams import ForceDiagram 6 | from compas_ags.rhino import FormArtist 7 | from compas_ags.rhino import ForceArtist 8 | 9 | from compas.rpc import Proxy 10 | 11 | graphstatics = Proxy('compas_ags.ags.graphstatics') 12 | 13 | HERE = os.path.dirname(__file__) 14 | DATA = os.path.join(HERE, '../data') 15 | FILE = os.path.join(DATA, 'debugging', 'truss.obj') 16 | 17 | graph = FormGraph.from_obj(FILE) 18 | 19 | form = FormDiagram.from_graph(graph) 20 | force = ForceDiagram.from_formdiagram(form) 21 | 22 | form.edge_force((0, 1), -1.0) 23 | form.edge_force((2, 3), -1.0) 24 | form.edge_force((4, 5), -1.0) 25 | 26 | form.data = graphstatics.form_update_q_from_qind_proxy(form.data) 27 | force.data = graphstatics.force_update_from_form_proxy(force.data, form.data) 28 | 29 | formartist = FormArtist(form, layer="AGS::FormDiagram") 30 | forceartist = ForceArtist(force, layer="AGS::ForceDiagram") 31 | 32 | formartist.draw_vertices() 33 | formartist.draw_edges() 34 | 35 | forceartist.draw_vertices() 36 | forceartist.draw_edges() 37 | -------------------------------------------------------------------------------- /data/paper/funicular.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 20 3 0 4 | v 23 0 0 5 | cstype bspline 6 | deg 1 7 | curv 23.37953681139935 27.62217749851863 1 2 8 | parm u 23.37953681139935 23.37953681139935 27.62217749851863 27.62217749851863 9 | end 10 | v 13 7 0 11 | v 20 3 0 12 | cstype bspline 13 | deg 1 14 | curv 15.3172790631008 23.37953681139935 3 4 15 | parm u 15.3172790631008 15.3172790631008 23.37953681139935 23.37953681139935 16 | end 17 | v 5 5 0 18 | v 13 7 0 19 | cstype bspline 20 | deg 1 21 | curv 7.071067811865476 15.3172790631008 5 6 22 | parm u 7.071067811865476 7.071067811865476 15.3172790631008 15.3172790631008 23 | end 24 | v 0 0 0 25 | v 5 5 0 26 | cstype bspline 27 | deg 1 28 | curv 0 7.071067811865476 7 8 29 | parm u 0 0 7.071067811865476 7.071067811865476 30 | end 31 | v 20 3 0 32 | v 22 8 0 33 | cstype bspline 34 | deg 1 35 | curv 0 5.385164807134505 9 10 36 | parm u 0 0 5.385164807134505 5.385164807134505 37 | end 38 | v 13 7 0 39 | v 14 12 0 40 | cstype bspline 41 | deg 1 42 | curv 0 5.099019513592785 11 12 43 | parm u 0 0 5.099019513592785 5.099019513592785 44 | end 45 | v 5 5 0 46 | v 4 9 0 47 | cstype bspline 48 | deg 1 49 | curv 0 4.123105625617661 13 14 50 | parm u 0 0 4.123105625617661 4.123105625617661 51 | end 52 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | COMPAS AGS 3 | ******************************************************************************** 4 | 5 | .. figure:: /_images/AGS_intro.png 6 | :figclass: figure 7 | :class: figure-img img-fluid 8 | 9 | .. rst-class:: lead 10 | 11 | :mod:`compas_ags` is a computational implementation of Algebraic Graph(ic) Statics (AGS) for and based on the COMPAS framework. 12 | AGS is an algebraic approach to graphic statics. 13 | With AGS, form and force diagrams can be generated automatically from both numerical and geometric inputs. 14 | The reciprocal relationship between the diagrams is encoded in dual meshes, with geometric constraints imposed on the coordinates of their vertices. 15 | This allows for a parametric exploration of the relationship between form and forces in two-dimensional structural systems, 16 | and for the combination of graphic statics with traditional optimisation methods. 17 | 18 | 19 | Table of Contents 20 | ================= 21 | 22 | .. toctree:: 23 | :maxdepth: 3 24 | :titlesonly: 25 | 26 | Introduction 27 | installation 28 | examples 29 | api 30 | publications 31 | license 32 | -------------------------------------------------------------------------------- /scripts/test_rtl_debug.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | from compas_ags.diagrams import FormDiagram 5 | from compas_ags.diagrams import ForceDiagram 6 | from compas_ags.ags import graphstatics 7 | from compas_ags.viewers import Viewer 8 | 9 | HERE = os.path.dirname(__file__) 10 | FILE = os.path.join(HERE, '../data/forms/howe_modified.ags') 11 | 12 | with open(FILE, 'r') as f: 13 | data = json.load(f) 14 | 15 | form = FormDiagram.from_data(data['data']['form']) 16 | force = ForceDiagram.from_data(data['data']['force']) 17 | form.dual = force 18 | force.dual = form 19 | 20 | graphstatics.form_update_from_force(form, force, kmax=100) 21 | 22 | # ============================================================================== 23 | # Visualize 24 | # ============================================================================== 25 | 26 | viewer = Viewer(form, force, delay_setup=False, figsize=(12, 7.5)) 27 | 28 | viewer.draw_form( 29 | vertexsize=0.15, 30 | vertexcolor={key: '#000000' for key in form.vertices_where({'is_fixed': True})}, 31 | vertexlabel={key: key for key in form.vertices()}) 32 | 33 | viewer.draw_force( 34 | vertexsize=0.15, 35 | vertexlabel={key: key for key in force.vertices()}) 36 | 37 | viewer.show() 38 | -------------------------------------------------------------------------------- /src/compas_ags/diagrams/formgraph.py: -------------------------------------------------------------------------------- 1 | from compas.datastructures import Graph 2 | 3 | 4 | class FormGraph(Graph): 5 | """A graph representing the geometry and connectivity of the lines of a form diagram.""" 6 | 7 | def __init__(self, **kwargs): 8 | super().__init__(**kwargs) 9 | 10 | def node_index(self) -> dict[int, int]: 11 | return {node: index for index, node in enumerate(self.nodes())} 12 | 13 | def is_2d(self) -> bool: 14 | """Verify that all nodes of the graph lie in a horizontal plane. 15 | 16 | Returns 17 | ------- 18 | bool 19 | 20 | """ 21 | z = self.nodes_attribute("z") 22 | zmin = min(z) 23 | zmax = max(z) 24 | if (zmax - zmin) ** 2 > 0.001: 25 | return False 26 | return True 27 | 28 | def is_planar_embedding(self) -> bool: 29 | """Verify that the current embedding of the graph is planar.""" 30 | return self.is_2d() and self.is_planar() and not self.is_crossed() 31 | 32 | def embed(self, fixed: list[int] = None, straightline: bool = True) -> bool: 33 | """Compute a geometry for the graph that embeds it in the plane. 34 | 35 | Returns 36 | ------- 37 | bool 38 | 39 | """ 40 | return self.embed_in_plane(fixed) 41 | -------------------------------------------------------------------------------- /docs/publications/bi-ags.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Bi-directional Algebraic Graph Statics 3 | ******************************************************************************** 4 | 5 | .. code-block:: latex 6 | 7 | @article{ALIC201726, 8 | author = "Vedad Alic and Daniel Åkesson", 9 | title = "Bi-directional algebraic graphic statics", 10 | journal = "Computer-Aided Design", 11 | year = "2017", 12 | volume = "93", 13 | pages = "26-37", 14 | issn = "0010-4485", 15 | doi = "10.1016/j.cad.2017.08.003", 16 | note = "", 17 | } 18 | 19 | **Abstract** 20 | 21 | A pre-existing algebraic graphic statics method is extended to allow for interactive manipulations of the force 22 | diagram, from which an updated form diagram is determined. 23 | Newton’s method is used to solve a set of non-linear equations, and the required Jacobian matrix is derived. 24 | Additional geometric constraints on the form diagram are introduced, and methods for improving the robustness 25 | of the method are presented. We discuss the implementation of the method as a back-end to an interactive application, 26 | and demonstrate the usability of the method in several examples where the qualities of directly manipulating the 27 | force diagram are emphasized. 28 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ### What type of change is this? 6 | 7 | - [ ] Bug fix in a **backwards-compatible** manner. 8 | - [ ] New feature in a **backwards-compatible** manner. 9 | - [ ] Breaking change: bug fix or new feature that involve incompatible API changes. 10 | - [ ] Other (e.g. doc update, configuration, etc) 11 | 12 | ### Checklist 13 | 14 | _Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ 15 | 16 | - [ ] I added a line to the `CHANGELOG.md` file in the `Unreleased` section under the most fitting heading (e.g. `Added`, `Changed`, `Removed`). 17 | - [ ] I ran all tests on my computer and it's all green (i.e. `invoke test`). 18 | - [ ] I ran lint on my computer and there are no errors (i.e. `invoke lint`). 19 | - [ ] I added new functions/classes and made them available on a second-level import, e.g. `compas.datastructures.Mesh`. 20 | - [ ] I have added tests that prove my fix is effective or that my feature works. 21 | - [ ] I have added necessary documentation (if appropriate) 22 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | License 3 | ******************************************************************************** 4 | 5 | .. code-block:: none 6 | 7 | MIT License 8 | 9 | Copyright (c) 2016-2018 Block Research Group 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | -------------------------------------------------------------------------------- /data/paper/spider_symm.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 5 0 0 4 | v 8 0 0 5 | cstype bspline 6 | deg 1 7 | curv 5 8 1 2 8 | parm u 5 5 8 8 9 | end 10 | v 0 0 0 11 | v 5 0 0 12 | cstype bspline 13 | deg 1 14 | curv 0 5 3 4 15 | parm u 0 0 5 5 16 | end 17 | v 0 -5 0 18 | v 0 -8 0 19 | cstype bspline 20 | deg 1 21 | curv 5 8 5 6 22 | parm u 5 5 8 8 23 | end 24 | v 0 0 0 25 | v 0 -5 0 26 | cstype bspline 27 | deg 1 28 | curv 0 5 7 8 29 | parm u 0 0 5 5 30 | end 31 | v -5 0 0 32 | v -8 0 0 33 | cstype bspline 34 | deg 1 35 | curv 5 8 9 10 36 | parm u 5 5 8 8 37 | end 38 | v 0 0 0 39 | v -5 0 0 40 | cstype bspline 41 | deg 1 42 | curv 0 5 11 12 43 | parm u 0 0 5 5 44 | end 45 | v 0 5 0 46 | v 0 8 0 47 | cstype bspline 48 | deg 1 49 | curv 5 8 13 14 50 | parm u 5 5 8 8 51 | end 52 | v 0 0 0 53 | v 0 5 0 54 | cstype bspline 55 | deg 1 56 | curv 0 5 15 16 57 | parm u 0 0 5 5 58 | end 59 | v 0 -5 0 60 | v -5 0 0 61 | cstype bspline 62 | deg 1 63 | curv 21.21320343559643 28.2842712474619 17 18 64 | parm u 21.21320343559643 21.21320343559643 28.2842712474619 28.2842712474619 65 | end 66 | v 5 0 0 67 | v 0 -5 0 68 | cstype bspline 69 | deg 1 70 | curv 14.14213562373095 21.21320343559643 19 20 71 | parm u 14.14213562373095 14.14213562373095 21.21320343559643 21.21320343559643 72 | end 73 | v 0 5 0 74 | v 5 0 0 75 | cstype bspline 76 | deg 1 77 | curv 7.071067811865476 14.14213562373095 21 22 78 | parm u 7.071067811865476 7.071067811865476 14.14213562373095 14.14213562373095 79 | end 80 | v -5 0 0 81 | v 0 5 0 82 | cstype bspline 83 | deg 1 84 | curv 0 7.071067811865476 23 24 85 | parm u 0 0 7.071067811865476 7.071067811865476 86 | end 87 | -------------------------------------------------------------------------------- /docs/examples/00_basic.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.ags import graphstatics 3 | from compas_ags.diagrams import ForceDiagram 4 | from compas_ags.diagrams import FormDiagram 5 | from compas_ags.diagrams import FormGraph 6 | from compas_ags.viewer import AGSViewer 7 | 8 | # ------------------------------------------------------------------------------ 9 | # 1. get lines of a plane triangle frame in equilibrium, its applied loads and reaction forces 10 | # make form and force diagrams 11 | # ------------------------------------------------------------------------------ 12 | 13 | graph = FormGraph.from_obj(compas_ags.get("paper/gs_form_force.obj")) 14 | 15 | form = FormDiagram.from_graph(graph) 16 | force = ForceDiagram.from_formdiagram(form) 17 | 18 | # ------------------------------------------------------------------------------ 19 | # 2. set applied load 20 | # ------------------------------------------------------------------------------ 21 | 22 | # choose an independent edge and set the magnitude of the applied load 23 | # the system is statically determinate, thus choosing one edge is enough 24 | form.edge_force(0, -3.0) 25 | 26 | # update force densities of form and force diagrams 27 | graphstatics.form_update_q_from_qind(form) 28 | graphstatics.force_update_from_form(force, form) 29 | 30 | # ------------------------------------------------------------------------------ 31 | # 3. display force and form diagrams 32 | # ------------------------------------------------------------------------------ 33 | 34 | viewer = AGSViewer() 35 | viewer.add_form(form) 36 | viewer.add_force(force) 37 | viewer.show() 38 | -------------------------------------------------------------------------------- /scripts/test_scene.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from __future__ import absolute_import 3 | from __future__ import division 4 | 5 | import os 6 | 7 | from compas.rpc import Proxy 8 | 9 | from compas_ags.diagrams import FormGraph 10 | from compas_ags.diagrams import FormDiagram 11 | from compas_ags.diagrams import ForceDiagram 12 | 13 | from compas_ags.rhino import Scene 14 | 15 | graphstatics = Proxy('compas_ags.ags.graphstatics') 16 | 17 | HERE = os.path.dirname(__file__) 18 | DATA = os.path.join(HERE, '../data') 19 | FILE = os.path.join(DATA, 'debugging', 'truss.obj') 20 | 21 | graph = FormGraph.from_obj(FILE) 22 | 23 | form = FormDiagram.from_graph(graph) 24 | force = ForceDiagram.from_formdiagram(form) 25 | 26 | form.edge_force((0, 1), -1.0) 27 | form.edge_force((2, 3), -1.0) 28 | form.edge_force((4, 5), -1.0) 29 | 30 | form.data = graphstatics.form_update_q_from_qind_proxy(form.data) 31 | force.data = graphstatics.force_update_from_form_proxy(force.data, form.data) 32 | 33 | # ============================================================================== 34 | # Visualize and Interact 35 | # ============================================================================== 36 | 37 | scene = Scene() 38 | 39 | form_id = scene.add(form, name="Form", layer="AGS::FormDiagram", visible=True) 40 | force_id = scene.add(force, name="Force", layer="AGS::ForceDiagram", visible=True) 41 | 42 | scene.clear_layers() 43 | 44 | form_obj = scene.find(form_id) 45 | force_obj = scene.find(force_id) 46 | 47 | force_obj.anchor = 5 48 | force_obj.location = [35, 0, 0] 49 | force_obj.scale = 5.0 50 | 51 | scene.update() 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # compas_ags 2 | 3 | ![build](https://github.com/blockresearchgroup/compas_ags/workflows/build/badge.svg) 4 | 5 | [![GitHub - License](https://img.shields.io/github/license/blockresearchgroup/compas_ags.svg)](https://github.com/blockresearchgroup/compas_ags) 6 | 7 | **compas_ags** is a COMPAS package for Computational Graphic Statics. 8 | It combines implementations of the following papers: 9 | 10 | * Algebraic Graph Statics 11 | * Bi-directional Algebraic Graph Statics 12 | * Load-path optimisation of funcicular networks 13 | 14 | See "[Publications](https://blockresearchgroup.github.io/compas_ags/latest/publications.html)" for more info. 15 | 16 | ## Getting Started 17 | 18 | Have a look at the "Getting Started" instructions in the docs: . 19 | 20 | ## First Steps 21 | 22 | A good place to start exploring AGS through `compas_ags` are the [examples](https://blockresearchgroup.github.io/compas_ags/latest/examples.html). 23 | 24 | ## Questions and feedback 25 | 26 | For questions and feedback, read through or post something in the [compas_ags category](https://forum.compas-framework.org/c/compas-ags) of the COMPAS forum. 27 | 28 | ## Issue tracker 29 | 30 | If you find a bug, please help us solve it by [filing a report](https://github.com/blockresearchgroup/compas_ags/issues). 31 | 32 | ## Contributing 33 | 34 | If you want to contribute, check out our [developer guidelines](https://blockresearchgroup.github.io/compas_ags/latest/devguide.html). 35 | 36 | ## License 37 | 38 | `compas_ags` is licensed under the MIT License. See [LICENSE](https://github.com/blockresearchgroup/compas_ags/blob/master/LICENSE), for more information. 39 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | 10 | ### Added 11 | 12 | ### Changed 13 | 14 | ### Removed 15 | 16 | 17 | ## [1.3.3] 2024-11-27 18 | 19 | ### Added 20 | 21 | ### Changed 22 | 23 | * Changed calculation of target vectors to use edge geometry directly (`edge_line` and `edge_direction`). 24 | 25 | ### Removed 26 | 27 | 28 | ## [1.3.2] 2024-11-21 29 | 30 | ### Added 31 | 32 | ### Changed 33 | 34 | * Fixed bug in `Formdiagram.from_graph` related to zero-force edges. 35 | 36 | ### Removed 37 | 38 | 39 | ## [1.3.1] 2024-11-14 40 | 41 | ## Added 42 | 43 | ## Changed 44 | 45 | * Fixed bug in `compas_ags.diagrams.forcediagram.ForceDiagram.constraints_from_dual()`. 46 | 47 | ## Removed 48 | 49 | 50 | ## [1.3.0] 2024-11-12 51 | 52 | ### Added 53 | 54 | * Added viewer based on `compas_viewer`. 55 | * Added `compas_ags.ags.core.rref_sympy`. 56 | * Added type annotations where possible. 57 | * Added `requirements-dof.txt` to include `sympy` as optional dependency. 58 | * Added annotations. 59 | 60 | ### Changed 61 | 62 | * Changed examples to use new viewer. 63 | * Fixed bug in viewer rotation. 64 | * Changed code to be compatible with COMPAS 2. 65 | 66 | ### Removed 67 | 68 | * Removed viewer based on `compas_plotter`. 69 | 70 | 71 | ## [1.2.1] 2023-11-10 72 | 73 | ### Added 74 | 75 | ### Changed 76 | 77 | ### Removed 78 | 79 | 80 | ## [1.2.0] 2022-11-17 81 | 82 | ### Added 83 | 84 | ### Changed 85 | 86 | * Updated workflow. 87 | 88 | ### Removed 89 | -------------------------------------------------------------------------------- /src/compas_ags/ags/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import ( 2 | update_q_from_qind, 3 | update_primal_from_dual, 4 | get_jacobian_and_residual, 5 | compute_jacobian, 6 | parallelise_edges, 7 | ) 8 | from .graphstatics import ( 9 | form_identify_dof, 10 | form_count_dof, 11 | form_update_q_from_qind, 12 | form_update_from_force, 13 | form_update_from_force_newton, 14 | force_update_from_form, 15 | force_update_from_constraints, 16 | update_diagrams_from_constraints, 17 | ) 18 | from .loadpath import ( 19 | compute_loadpath, 20 | compute_external_work, 21 | compute_internal_work, 22 | compute_internal_work_tension, 23 | compute_internal_work_compression, 24 | optimise_loadpath, 25 | ) 26 | from .constraints import ( 27 | ConstraintsCollection, 28 | HorizontalFix, 29 | VerticalFix, 30 | AngleFix, 31 | LengthFix, 32 | SetLength, 33 | ) 34 | 35 | __all__ = [ 36 | "update_q_from_qind", 37 | "update_primal_from_dual", 38 | "get_jacobian_and_residual", 39 | "compute_jacobian", 40 | "parallelise_edges", 41 | "form_identify_dof", 42 | "form_count_dof", 43 | "form_update_q_from_qind", 44 | "form_update_from_force", 45 | "form_update_from_force_newton", 46 | "force_update_from_form", 47 | "force_update_from_constraints", 48 | "update_diagrams_from_constraints", 49 | "compute_loadpath", 50 | "compute_external_work", 51 | "compute_internal_work", 52 | "compute_internal_work_tension", 53 | "compute_internal_work_compression", 54 | "optimise_loadpath", 55 | "ConstraintsCollection", 56 | "HorizontalFix", 57 | "VerticalFix", 58 | "AngleFix", 59 | "LengthFix", 60 | "SetLength", 61 | ] 62 | -------------------------------------------------------------------------------- /scripts/test_objects.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from __future__ import absolute_import 3 | from __future__ import division 4 | 5 | import os 6 | 7 | from compas.rpc import Proxy 8 | 9 | from compas_ags.diagrams import FormGraph 10 | from compas_ags.diagrams import FormDiagram 11 | from compas_ags.diagrams import ForceDiagram 12 | 13 | from compas_ags.rhino import FormObject 14 | from compas_ags.rhino import ForceObject 15 | 16 | graphstatics = Proxy('compas_ags.ags.graphstatics') 17 | 18 | HERE = os.path.dirname(__file__) 19 | DATA = os.path.join(HERE, '../data') 20 | FILE = os.path.join(DATA, 'debugging', 'truss.obj') 21 | 22 | graph = FormGraph.from_obj(FILE) 23 | 24 | form = FormDiagram.from_graph(graph) 25 | force = ForceDiagram.from_formdiagram(form) 26 | 27 | form.edge_force((0, 1), -1.0) 28 | form.edge_force((2, 3), -1.0) 29 | form.edge_force((4, 5), -1.0) 30 | 31 | form.data = graphstatics.form_update_q_from_qind_proxy(form.data) 32 | force.data = graphstatics.force_update_from_form_proxy(force.data, form.data) 33 | 34 | # ============================================================================== 35 | # Visualize and Interact 36 | # ============================================================================== 37 | 38 | form_object = FormObject(form, name="Form", layer="AGS::FormDiagram") 39 | force_object = ForceObject(force, name="Force", layer="AGS::ForceDiagram") 40 | 41 | form_object.clear_layer() 42 | force_object.clear_layer() 43 | 44 | form_object.anchor = 9 45 | 46 | force_object.anchor = 5 47 | force_object.location = [35, 0, 0] 48 | force_object.scale = 5.0 49 | 50 | form_object.draw() 51 | force_object.draw() 52 | 53 | vertices = form_object.select_vertices() 54 | if vertices and form_object.move_vertices(vertices): 55 | form_object.draw() 56 | 57 | vertices = force_object.select_vertices() 58 | if vertices and force_object.move_vertices(vertices): 59 | force_object.draw() 60 | -------------------------------------------------------------------------------- /data/paper/gs_arch.json: -------------------------------------------------------------------------------- 1 | {"compas": "0.17.2", "datatype": "compas_ags.diagrams/FormGraph", "data": {"attributes": {"name": "Network"}, "node_attributes": {"x": 0.0, "y": 0.0, "z": 0.0}, "edge_attributes": {}, "node": {"0": {"x": 3.62477467512, "y": 2.99447312681, "z": 0.0}, "1": {"x": 43.4972961014, "y": -7.27758178128, "z": 0.0}, "2": {"x": 0.0, "y": 0.0, "z": 0.0}, "3": {"x": 0.0, "y": -7.27758178128, "z": 0.0}, "4": {"x": 39.8725214263, "y": 2.99447312681, "z": 0.0}, "5": {"x": 32.622972076, "y": 6.81140730234, "z": 0.0}, "6": {"x": 25.3734227258, "y": 8.54514654776, "z": 0.0}, "7": {"x": 18.1238733756, "y": 8.54514654776, "z": 0.0}, "8": {"x": 10.8743240253, "y": 6.81140730234, "z": 0.0}, "9": {"x": 43.4972961014, "y": 0.0, "z": 0.0}, "10": {"x": 3.62477467512, "y": 10.2720549081, "z": 0.0}, "11": {"x": -3.52953624469, "y": 0.0, "z": 0.0}, "12": {"x": 39.8725214263, "y": 10.2720549081, "z": 0.0}, "13": {"x": 32.622972076, "y": 14.0889890836, "z": 0.0}, "14": {"x": 25.3734227258, "y": 15.822728329, "z": 0.0}, "15": {"x": 18.1238733756, "y": 15.822728329, "z": 0.0}, "16": {"x": 10.8743240253, "y": 14.0889890836, "z": 0.0}, "17": {"x": 47.4502140636, "y": 0.0, "z": 0.0}}, "edge": {"0": {"10": {}, "8": {}}, "1": {"9": {}}, "2": {"11": {}, "0": {}}, "3": {"2": {}}, "4": {"12": {}, "9": {}}, "5": {"13": {}, "4": {}}, "6": {"14": {}, "5": {}}, "7": {"15": {}, "6": {}}, "8": {"16": {}, "7": {}}, "9": {"17": {}}, "10": {}, "11": {}, "12": {}, "13": {}, "14": {}, "15": {}, "16": {}, "17": {}}, "adjacency": {"0": {"10": null, "2": null, "8": null}, "1": {"9": null}, "2": {"11": null, "3": null, "0": null}, "3": {"2": null}, "4": {"12": null, "5": null, "9": null}, "5": {"13": null, "6": null, "4": null}, "6": {"14": null, "7": null, "5": null}, "7": {"15": null, "8": null, "6": null}, "8": {"16": null, "0": null, "7": null}, "9": {"1": null, "4": null, "17": null}, "10": {"0": null}, "11": {"2": null}, "12": {"4": null}, "13": {"5": null}, "14": {"6": null}, "15": {"7": null}, "16": {"8": null}, "17": {"9": null}}, "max_int_key": 17}} -------------------------------------------------------------------------------- /docs/publications/loadpath.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Load-path optimisation of funicular networks 3 | ******************************************************************************** 4 | 5 | .. code-block:: latex 6 | 7 | @article{Liew2018, 8 | author = "Liew, A. and Pagonakis, D. and Van Mele, T. and Block, P.", 9 | title = "Load-path optimisation of funicular networks", 10 | journal = "Meccanica", 11 | year = "2018", 12 | volume = "53", 13 | number = "1", 14 | pages = "279-294", 15 | month = "", 16 | doi = "10.1007/s11012-017-0714-1", 17 | note = "", 18 | } 19 | 20 | **Abstract** 21 | 22 | This paper describes the use of load-path optimisation for discrete compression vaults. The presented 23 | approach allows for the finding of the funicular solution for a network layout defined in plan, that has 24 | the lowest volume for the given boundary conditions. The compression-only thrust networks are constructed 25 | with Thrust Network Analysis by assigning force densities to the network's independent edges. By defining 26 | a load-path function and deriving its associated gradient and Hessian functions, optimisation routines 27 | were used to find the optimum independent force densities that minimised the load-path function subject to 28 | compression only restraints. A selection of example cases showed a dependence of the optimum load-path and 29 | force distribution on the network topology. Appropriate selection of the network pattern encouraged the flow 30 | of compression forces such that efficient vaulting action could form internally, avoiding long network edges 31 | with high force densities. A general, non-orthogonal network example showed that vaulted structures of high 32 | network indeterminacy can be investigated both directly for weight minimisation, and for the understanding 33 | of efficient thrust network patterns within the structure. 34 | -------------------------------------------------------------------------------- /scripts/test_scale.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from __future__ import absolute_import 3 | from __future__ import division 4 | 5 | import os 6 | 7 | from compas.rpc import Proxy 8 | 9 | from compas_ags.diagrams import FormGraph 10 | from compas_ags.diagrams import FormDiagram 11 | from compas_ags.diagrams import ForceDiagram 12 | 13 | from compas_ags.rhino import FormObject 14 | from compas_ags.rhino import ForceObject 15 | 16 | graphstatics = Proxy('compas_ags.ags.graphstatics') 17 | 18 | HERE = os.path.dirname(__file__) 19 | DATA = os.path.join(HERE, '../data') 20 | FILE = os.path.join(DATA, 'debugging', 'truss.obj') 21 | 22 | graph = FormGraph.from_obj(FILE) 23 | 24 | form = FormDiagram.from_graph(graph) 25 | force = ForceDiagram.from_formdiagram(form) 26 | 27 | form.edge_force((0, 1), -1.0) 28 | form.edge_force((2, 3), -1.0) 29 | form.edge_force((4, 5), -1.0) 30 | 31 | form.data = graphstatics.form_update_q_from_qind_proxy(form.data) 32 | force.data = graphstatics.force_update_from_form_proxy(force.data, form.data) 33 | 34 | # ============================================================================== 35 | # Visualize and Interact 36 | # ============================================================================== 37 | 38 | form_object = FormObject(form, name="Form", layer="AGS::FormDiagram") 39 | force_object = ForceObject(force, name="Force", layer="AGS::ForceDiagram") 40 | 41 | form_object.clear_layer() 42 | force_object.clear_layer() 43 | 44 | force_object.location = [35, 0, 0] 45 | force_object.scale = 5.0 46 | 47 | form_object.draw() 48 | force_object.draw() 49 | 50 | # vertices = form_object.select_vertices() 51 | # if vertices and form_object.move_vertices(vertices): 52 | # form_object.draw() 53 | 54 | # vertices = force_object.select_vertices() 55 | # if vertices and force_object.move_vertices(vertices): 56 | # force_object.draw() 57 | 58 | if force_object.scale_from_3_points(message="Select the base node of the Force Diagram for the scaling operation"): 59 | force_object.draw() 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # COMPAS 104 | 105 | *.3dmbak 106 | *.rhl 107 | *.rui_bak 108 | *.rui 109 | 110 | temp/** 111 | !temp/PLACEHOLDER 112 | 113 | docsource/api/generated 114 | .DS_Store 115 | 116 | .vscode/ 117 | 118 | docs/api/generated 119 | dist/docs 120 | 121 | 122 | # electron 123 | node_modules 124 | src/compas_ags/web/electron -------------------------------------------------------------------------------- /data/debugging/zero_modified.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 15 0 0 4 | v 15 -2 0 5 | cstype bspline 6 | deg 1 7 | curv 0 2 1 2 8 | parm u 0 0 2 2 9 | end 10 | v 10 0 0 11 | v 10 -2 0 12 | cstype bspline 13 | deg 1 14 | curv 0 2 3 4 15 | parm u 0 0 2 2 16 | end 17 | v 5 -2 0 18 | v 5 0 0 19 | cstype bspline 20 | deg 1 21 | curv 0 2 5 6 22 | parm u 0 0 2 2 23 | end 24 | v 20 -2 0 25 | v 20 0 0 26 | cstype bspline 27 | deg 1 28 | curv 0 2 7 8 29 | parm u 0 0 2 2 30 | end 31 | v 0 0 0 32 | v -2 0 0 33 | cstype bspline 34 | deg 1 35 | curv 0 2 9 10 36 | parm u 0 0 2 2 37 | end 38 | v 0 -2 0 39 | v 0 0 0 40 | cstype bspline 41 | deg 1 42 | curv 0 2 11 12 43 | parm u 0 0 2 2 44 | end 45 | v 10 5 0 46 | v 10 0 0 47 | cstype bspline 48 | deg 1 49 | curv 0 5 13 14 50 | parm u 0 0 5 5 51 | end 52 | v 5 0 0 53 | v 0 0 0 54 | cstype bspline 55 | deg 1 56 | curv 0 5 15 16 57 | parm u 0 0 5 5 58 | end 59 | v 10 0 0 60 | v 5 0 0 61 | cstype bspline 62 | deg 1 63 | curv 0 5 17 18 64 | parm u 0 0 5 5 65 | end 66 | v 15 0 0 67 | v 10 0 0 68 | cstype bspline 69 | deg 1 70 | curv 0 5 19 20 71 | parm u 0 0 5 5 72 | end 73 | v 20 0 0 74 | v 15 0 0 75 | cstype bspline 76 | deg 1 77 | curv 0 5 21 22 78 | parm u 0 0 5 5 79 | end 80 | v 10 5 0 81 | v 15 5 0 82 | cstype bspline 83 | deg 1 84 | curv 0 5 23 24 85 | parm u 0 0 5 5 86 | end 87 | v 5 5 0 88 | v 10 5 0 89 | cstype bspline 90 | deg 1 91 | curv 0 5 25 26 92 | parm u 0 0 5 5 93 | end 94 | v 15 5 0 95 | v 20 0 0 96 | cstype bspline 97 | deg 1 98 | curv 0 7.07107 27 28 99 | parm u 0 0 7.07107 7.07107 100 | end 101 | v 15 0 0 102 | v 15 5 0 103 | cstype bspline 104 | deg 1 105 | curv 0 5 29 30 106 | parm u 0 0 5 5 107 | end 108 | v 10 5 0 109 | v 15 0 0 110 | cstype bspline 111 | deg 1 112 | curv 0 7.07107 31 32 113 | parm u 0 0 7.07107 7.07107 114 | end 115 | v 5 0 0 116 | v 10 5 0 117 | cstype bspline 118 | deg 1 119 | curv 0 7.07107 33 34 120 | parm u 0 0 7.07107 7.07107 121 | end 122 | v 5 5 0 123 | v 5 0 0 124 | cstype bspline 125 | deg 1 126 | curv 0 5 35 36 127 | parm u 0 0 5 5 128 | end 129 | v 0 0 0 130 | v 5 5 0 131 | cstype bspline 132 | deg 1 133 | curv 0 7.07107 37 38 134 | parm u 0 0 7.07107 7.07107 135 | end 136 | -------------------------------------------------------------------------------- /scripts/test_zero_rhino_interactive.py: -------------------------------------------------------------------------------- 1 | import compas_rhino 2 | 3 | from compas_ags.diagrams import FormGraph 4 | from compas_ags.diagrams import FormDiagram 5 | from compas_ags.diagrams import ForceDiagram 6 | from compas_ags.rhino import Scene 7 | 8 | from compas.rpc import Proxy 9 | 10 | graphstatics = Proxy('compas_ags.ags.graphstatics') 11 | graphstatics.restart_server() 12 | 13 | # this file has unloaded, 2-valent nodes 14 | # they will be removed automatically 15 | # and the result renumbered 16 | # HERE = os.path.dirname(__file__) 17 | # FILE = os.path.join(HERE, '../data/debugging/zero.obj') 18 | 19 | # graph = FormGraph.from_obj(FILE) 20 | guids = compas_rhino.select_lines() 21 | lines = compas_rhino.get_line_coordinates(guids) 22 | graph = FormGraph.from_lines(lines) 23 | form = FormDiagram.from_graph(graph) 24 | force = ForceDiagram.from_formdiagram(form) 25 | scene = Scene() 26 | 27 | # fix the supports 28 | form.vertices_attribute('is_fixed', True, [1, 12]) 29 | 30 | # set the loads 31 | form.edge_force((8, 19), +10.0) 32 | form.edge_force((14, 3), +10.0) 33 | form.edge_force((17, 7), +10.0) 34 | form.edge_force((11, 9), +10.0) 35 | form.edge_force((13, 16), +10.0) 36 | 37 | # compute initial form and force diagrams 38 | form.data = graphstatics.form_update_q_from_qind_proxy(form.data) 39 | force.data = graphstatics.force_update_from_form_proxy(force.data, form.data) 40 | 41 | form_id = scene.add(form, name="Form", layer="AGS::FormDiagram") 42 | force_id = scene.add(force, name="Force", layer="AGS::ForceDiagram") 43 | 44 | form_obj = scene.find(form_id) 45 | force_obj = scene.find(force_id) 46 | 47 | force_obj.artist.anchor_vertex = 0 48 | force_obj.artist.anchor_point = [130, 0, 0] 49 | force_obj.artist.scale = 1.0 50 | 51 | form_obj.artist.settings['scale.forces'] = 0.05 52 | 53 | scene.update() 54 | 55 | while True: 56 | vertices = force_obj.select_vertices() 57 | if not vertices: 58 | break 59 | if vertices and force_obj.move_vertices(vertices): 60 | scene.update() 61 | 62 | # fix some of the nodes in the from diagram 63 | # to constraint the problem to a single solution 64 | form.vertices_attribute('is_fixed', True, [8, 14, 17, 11, 13]) 65 | 66 | # update the form diagram 67 | form.data = graphstatics.form_update_from_force_proxy(form.data, force.data) 68 | 69 | scene.update() 70 | -------------------------------------------------------------------------------- /scripts/test_deviations.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | 3 | from compas.rpc import Proxy 4 | 5 | from compas_ags.diagrams import FormGraph 6 | from compas_ags.diagrams import FormDiagram 7 | from compas_ags.diagrams import ForceDiagram 8 | 9 | from compas.geometry import angle_vectors_xy 10 | from compas.geometry import subtract_vectors 11 | 12 | from compas_ags.rhino import Scene 13 | 14 | import compas_rhino 15 | 16 | p = Proxy() 17 | p.start_server() 18 | p.stop_server() 19 | 20 | graphstatics = Proxy('compas_ags.ags.graphstatics') 21 | 22 | FILE = compas_ags.get('debugging/zero.obj') 23 | FILE = '/Users/mricardo/compas_dev/compas_ags/data/debugging/zero.obj' 24 | 25 | graph = FormGraph.from_obj(FILE) 26 | form = FormDiagram.from_graph(graph) 27 | force = ForceDiagram.from_formdiagram(form) 28 | 29 | form.edge_force((0, 1), +1.0) 30 | form.edge_force((2, 3), +1.0) 31 | form.edge_force((4, 5), +1.0) 32 | 33 | form.data = graphstatics.form_update_q_from_qind_proxy(form.data) 34 | force.data = graphstatics.force_update_from_form_proxy(force.data, form.data) 35 | 36 | # Pick one key and move 37 | move_key = 2 38 | delta = + 0.1 39 | moving_point = force.vertex_coordinates(move_key) 40 | force.vertex_attribute(move_key, 'x', moving_point[0] + delta) 41 | force.vertex_attribute(move_key, 'y', moving_point[1] + delta) 42 | 43 | # Create Scene 44 | 45 | from compas_ags.utilities import check_deviations 46 | if not check_deviations(form, force): 47 | compas_rhino.display_message('Error: Diagrams are not in equilibrium!') 48 | 49 | scene = Scene() 50 | 51 | form_id = scene.add(form, name="Form", layer="AGS::FormDiagram") 52 | force_id = scene.add(force, name="Force", layer="AGS::ForceDiagram") 53 | 54 | form_obj = scene.find(form_id) 55 | force_obj = scene.find(force_id) 56 | 57 | force_obj.artist.anchor_vertex = 5 58 | force_obj.artist.anchor_point = [35, 0, 0] 59 | force_obj.artist.scale = 5.0 60 | 61 | scene.update() 62 | 63 | # if not checked: 64 | # from compas_plotters import MeshPlotter 65 | # plotter = MeshPlotter(form) 66 | # plotter.draw_edges(text={edge: form.edge_attribute(edge, 'a') for edge in form.edges()}) 67 | # plotter.show() 68 | 69 | # plotter = MeshPlotter(force) 70 | # plotter.draw_edges(text={edge: form.edge_attribute(force.dual_edge(edge), 'a') for edge in force.edges()}) 71 | # plotter.draw_vertices(text={key: key for key in force.vertices()}) 72 | # plotter.show() 73 | 74 | 75 | -------------------------------------------------------------------------------- /scripts/test_zero_rhino.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from compas_ags.diagrams import FormGraph 4 | from compas_ags.diagrams import FormDiagram 5 | from compas_ags.diagrams import ForceDiagram 6 | from compas_ags.rhino import Scene 7 | 8 | from compas.rpc import Proxy 9 | 10 | graphstatics = Proxy('compas_ags.ags.graphstatics') 11 | graphstatics.stop_server() 12 | graphstatics.start_server() 13 | 14 | # this file has unloaded, 2-valent nodes 15 | # they will be removed automatically 16 | # and the result renumbered 17 | HERE = os.path.dirname(__file__) 18 | FILE = os.path.join(HERE, '../data/debugging/zero.obj') 19 | 20 | graph = FormGraph.from_obj(FILE) 21 | form = FormDiagram.from_graph(graph) 22 | force = ForceDiagram.from_formdiagram(form) 23 | 24 | # fix the supports 25 | form.vertices_attribute('is_fixed', True, [8, 7]) 26 | 27 | # set the loads 28 | form.edge_force((0, 1), +10.0) 29 | form.edge_force((2, 3), +10.0) 30 | form.edge_force((4, 5), +10.0) 31 | 32 | # compute initial form and force diagrams 33 | form.data = graphstatics.form_update_q_from_qind_proxy(form.data) 34 | force.data = graphstatics.force_update_from_form_proxy(force.data, form.data) 35 | 36 | # change the geometry of the force diagram 37 | force.vertex_attribute(8, 'x', force.vertex_attribute(7, 'x')) 38 | force.vertex_attribute(9, 'x', force.vertex_attribute(10, 'x')) 39 | force.vertex_attributes(6, 'xyz', force.vertex_attributes(8, 'xyz')) 40 | force.vertex_attributes(11, 'xyz', force.vertex_attributes(9, 'xyz')) 41 | 42 | # # change the depth of the structure 43 | # force.vertices_attribute('x', 20, [6, 7, 8, 9, 10, 11]) 44 | 45 | # fix some of the nodes in the from diagram 46 | # to constraint the problem to a single solution 47 | form.vertices_attribute('is_fixed', True, [0, 2, 5]) 48 | 49 | # update the form diagram 50 | form.data = graphstatics.form_update_from_force_proxy(form.data, force.data) 51 | 52 | # ============================================================================== 53 | # Visualize 54 | # ============================================================================== 55 | 56 | scene = Scene() 57 | 58 | form_id = scene.add(form, name="Form", layer="AGS::FormDiagram") 59 | force_id = scene.add(force, name="Force", layer="AGS::ForceDiagram") 60 | 61 | scene.clear_layers() 62 | 63 | form_obj = scene.find(form_id) 64 | force_obj = scene.find(force_id) 65 | 66 | force_obj.anchor = 0 67 | force_obj.location = [35, 0, 0] 68 | force_obj.scale = 0.5 69 | 70 | form_obj.settings['scale.forces'] = 0.02 71 | 72 | scene.update() 73 | -------------------------------------------------------------------------------- /docs/publications/interactive-ags.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Interactive Graphic Statics 3 | ******************************************************************************** 4 | 5 | .. code-block:: latex 6 | 7 | @inproceedings{MaiaAvelino2021, 8 | author = "Maia Avelino, R. and Lee, J. and Van Mele, T. and Block, P. ", 9 | title = "An interactive implementation of algebraic graphic statics for geometry-based teaching and design of structures", 10 | booktitle = "Proceedings of the International fib Symposium on the Conceptual Design of Structures 2021", 11 | year = "2021", 12 | editor = "C. Fivet, P. D’Acunto, M. Fernández Ruiz, P. O. Ohlbrock.", 13 | volume = "", 14 | number = "", 15 | pages = "447-454", 16 | publisher = "", 17 | address = "Attisholz Areal, Switzerland", 18 | month = "September", 19 | doi = "10.35789/fib.PROC.0055.2021.CDSymp.P054", 20 | note = "", 21 | } 22 | 23 | **Abstract** 24 | 25 | This paper presents an interactive implementation of graphic statics, which can be 26 | integrated into a CAD environment. 27 | Graphic statics is a well-known design and analysis method for two-dimensional discrete 28 | structures that rely on geometrical rather than an analytical representation of the relation 29 | between the structure's geometry and the equilibrium of its internal forces. The method was 30 | formalised in the 19th century but slowly disappeared from structural engineering practice 31 | over the 20th century. 32 | Recent developments have introduced Algebraic Graph Statics (AGS), which formulates the 33 | geometrical relationship between the graph representations of the reciprocal form and force 34 | diagrams in graphic statics using linear algebra. 35 | AGS and its extensions enable automatic construction of force diagrams from given form diagrams 36 | and allow a few basic modifications of the force diagram from which the form diagram is updated. 37 | This paper builds on the previous work of AGS by implementing a real-time, bi-directional 38 | workflow allowing users to impose various constraints, and perform geometrical modifications 39 | in either the form or force diagram from which the other is automatically updated by using an 40 | iterative geometric solver. 41 | The presented implementation of interactive AGS provides a robust computational back-end to 42 | harness the advantages of traditional graphic statics for geometry-based teaching and design of structures. 43 | -------------------------------------------------------------------------------- /data/paper/fink.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 15 5 0 4 | v 15 7 0 5 | cstype bspline 6 | deg 1 7 | curv 0 2 1 2 8 | parm u 0 0 2 2 9 | end 10 | v 10 5 0 11 | v 10 7 0 12 | cstype bspline 13 | deg 1 14 | curv 0 2 3 4 15 | parm u 0 0 2 2 16 | end 17 | v 5 5 0 18 | v 5 7 0 19 | cstype bspline 20 | deg 1 21 | curv 0 2 5 6 22 | parm u 0 0 2 2 23 | end 24 | v 20 5 0 25 | v 20 3 0 26 | cstype bspline 27 | deg 1 28 | curv 0 2 7 8 29 | parm u 0 0 2 2 30 | end 31 | v -2 5 0 32 | v 0 5 0 33 | cstype bspline 34 | deg 1 35 | curv 0 2 9 10 36 | parm u 0 0 2 2 37 | end 38 | v 0 5 0 39 | v 0 3 0 40 | cstype bspline 41 | deg 1 42 | curv 0 2 11 12 43 | parm u 0 0 2 2 44 | end 45 | v 15 5 0 46 | v 20 5 0 47 | cstype bspline 48 | deg 1 49 | curv 15 20 13 14 50 | parm u 15 15 20 20 51 | end 52 | v 10 5 0 53 | v 15 5 0 54 | cstype bspline 55 | deg 1 56 | curv 10 15 15 16 57 | parm u 10 10 15 15 58 | end 59 | v 5 5 0 60 | v 10 5 0 61 | cstype bspline 62 | deg 1 63 | curv 5 10 17 18 64 | parm u 5 5 10 10 65 | end 66 | v 0 5 0 67 | v 5 5 0 68 | cstype bspline 69 | deg 1 70 | curv 0 5 19 20 71 | parm u 0 0 5 5 72 | end 73 | v 15 0 0 74 | v 20 5 0 75 | cstype bspline 76 | deg 1 77 | curv 21.21320343559643 28.2842712474619 21 22 78 | parm u 21.21320343559643 21.21320343559643 28.2842712474619 28.2842712474619 79 | end 80 | v 10 5 0 81 | v 15 0 0 82 | cstype bspline 83 | deg 1 84 | curv 14.14213562373095 21.21320343559643 23 24 85 | parm u 14.14213562373095 14.14213562373095 21.21320343559643 21.21320343559643 86 | end 87 | v 5 0 0 88 | v 10 5 0 89 | cstype bspline 90 | deg 1 91 | curv 7.071067811865476 14.14213562373095 25 26 92 | parm u 7.071067811865476 7.071067811865476 14.14213562373095 14.14213562373095 93 | end 94 | v 0 5 0 95 | v 5 0 0 96 | cstype bspline 97 | deg 1 98 | curv 0 7.071067811865476 27 28 99 | parm u 0 0 7.071067811865476 7.071067811865476 100 | end 101 | v 10 0 0 102 | v 15 5 0 103 | cstype bspline 104 | deg 1 105 | curv 7.071067811865476 14.14213562373095 29 30 106 | parm u 7.071067811865476 7.071067811865476 14.14213562373095 14.14213562373095 107 | end 108 | v 5 5 0 109 | v 10 0 0 110 | cstype bspline 111 | deg 1 112 | curv 0 7.071067811865476 31 32 113 | parm u 0 0 7.071067811865476 7.071067811865476 114 | end 115 | v 15 5 0 116 | v 15 0 0 117 | cstype bspline 118 | deg 1 119 | curv 0 5 33 34 120 | parm u 0 0 5 5 121 | end 122 | v 10 5 0 123 | v 10 0 0 124 | cstype bspline 125 | deg 1 126 | curv 0 5 35 36 127 | parm u 0 0 5 5 128 | end 129 | v 5 5 0 130 | v 5 0 0 131 | cstype bspline 132 | deg 1 133 | curv 0 5 37 38 134 | parm u 0 0 5 5 135 | end 136 | -------------------------------------------------------------------------------- /data/paper/fink_interaction.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 15 5 0 4 | v 13 7 0 5 | cstype bspline 6 | deg 1 7 | curv 0 2 1 2 8 | parm u 0 0 2 2 9 | end 10 | v 10 5 0 11 | v 11 7 0 12 | cstype bspline 13 | deg 1 14 | curv 0 2 3 4 15 | parm u 0 0 2 2 16 | end 17 | v 5 5 0 18 | v 4 8 0 19 | cstype bspline 20 | deg 1 21 | curv 0 2 5 6 22 | parm u 0 0 2 2 23 | end 24 | v 5 5 0 25 | v 5 3 0 26 | cstype bspline 27 | deg 1 28 | curv 0 5 7 8 29 | parm u 0 0 5 5 30 | end 31 | v 0 5 0 32 | v 5 3 0 33 | cstype bspline 34 | deg 1 35 | curv 0 7.071067811865476 9 10 36 | parm u 0 0 7.071067811865476 7.071067811865476 37 | end 38 | v 5 3 0 39 | v 10 5 0 40 | cstype bspline 41 | deg 1 42 | curv 7.071067811865476 14.14213562373095 11 12 43 | parm u 7.071067811865476 7.071067811865476 14.14213562373095 14.14213562373095 44 | end 45 | v 15 5 0 46 | v 15 1 0 47 | cstype bspline 48 | deg 1 49 | curv 0 5 13 14 50 | parm u 0 0 5 5 51 | end 52 | v 10 5 0 53 | v 15 1 0 54 | cstype bspline 55 | deg 1 56 | curv 14.14213562373095 21.21320343559643 15 16 57 | parm u 14.14213562373095 14.14213562373095 21.21320343559643 21.21320343559643 58 | end 59 | v 15 1 0 60 | v 20 5 0 61 | cstype bspline 62 | deg 1 63 | curv 21.21320343559643 28.2842712474619 17 18 64 | parm u 21.21320343559643 21.21320343559643 28.2842712474619 28.2842712474619 65 | end 66 | v 10 5 0 67 | v 10 0 0 68 | cstype bspline 69 | deg 1 70 | curv 0 5 19 20 71 | parm u 0 0 5 5 72 | end 73 | v 5 5 0 74 | v 10 0 0 75 | cstype bspline 76 | deg 1 77 | curv 0 7.071067811865476 21 22 78 | parm u 0 0 7.071067811865476 7.071067811865476 79 | end 80 | v 10 0 0 81 | v 15 5 0 82 | cstype bspline 83 | deg 1 84 | curv 7.071067811865476 14.14213562373095 23 24 85 | parm u 7.071067811865476 7.071067811865476 14.14213562373095 14.14213562373095 86 | end 87 | v 0 5 0 88 | v 5 5 0 89 | cstype bspline 90 | deg 1 91 | curv 0 5 25 26 92 | parm u 0 0 5 5 93 | end 94 | v 5 5 0 95 | v 10 5 0 96 | cstype bspline 97 | deg 1 98 | curv 5 10 27 28 99 | parm u 5 5 10 10 100 | end 101 | v 10 5 0 102 | v 15 5 0 103 | cstype bspline 104 | deg 1 105 | curv 10 15 29 30 106 | parm u 10 10 15 15 107 | end 108 | v 15 5 0 109 | v 20 5 0 110 | cstype bspline 111 | deg 1 112 | curv 15 20 31 32 113 | parm u 15 15 20 20 114 | end 115 | v 0 5 0 116 | v 0 3 0 117 | cstype bspline 118 | deg 1 119 | curv 0 2 33 34 120 | parm u 0 0 2 2 121 | end 122 | v -2 5 0 123 | v 0 5 0 124 | cstype bspline 125 | deg 1 126 | curv 0 2 35 36 127 | parm u 0 0 2 2 128 | end 129 | v 20 5 0 130 | v 20 3 0 131 | cstype bspline 132 | deg 1 133 | curv 0 2 37 38 134 | parm u 0 0 2 2 135 | end 136 | -------------------------------------------------------------------------------- /scripts/test_zero.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | 3 | from compas_ags.diagrams import FormGraph 4 | from compas_ags.diagrams import FormDiagram 5 | from compas_ags.diagrams import ForceDiagram 6 | # from compas_ags.ags import graphstatics 7 | from compas_ags.viewers import Viewer 8 | 9 | from compas.rpc import Proxy 10 | 11 | graphstatics = Proxy('compas_ags.ags.graphstatics') 12 | 13 | # this file has unloaded, 2-valent nodes 14 | # they will be removed automatically 15 | # and the result renumbered 16 | FILE = compas_ags.get('debugging/zero.obj') 17 | 18 | graph = FormGraph.from_obj(FILE) 19 | form = FormDiagram.from_graph(graph) 20 | force = ForceDiagram.from_formdiagram(form) 21 | 22 | # fix the supports 23 | form.vertices_attribute('is_fixed', True, [8, 7]) 24 | 25 | # set the loads 26 | form.edge_force((0, 1), +10.0) 27 | form.edge_force((2, 3), +10.0) 28 | form.edge_force((4, 5), +10.0) 29 | 30 | # # compute initial form and force diagrams 31 | # graphstatics.form_update_q_from_qind(form) 32 | # graphstatics.force_update_from_form(force, form) 33 | 34 | # compute initial form and force diagrams 35 | form.data = graphstatics.form_update_q_from_qind_proxy(form.data) 36 | force.data = graphstatics.force_update_from_form_proxy(force.data, form.data) 37 | 38 | # change the geometry of the force diagram 39 | force.vertex_attribute(6, 'x', force.vertex_attribute(8, 'x')) 40 | force.vertex_attribute(9, 'x', force.vertex_attribute(10, 'x')) 41 | force.vertex_attributes(7, 'xyz', force.vertex_attributes(6, 'xyz')) 42 | force.vertex_attributes(11, 'xyz', force.vertex_attributes(9, 'xyz')) 43 | 44 | # # change the depth of the structure 45 | # force.vertices_attribute('x', 20, [6, 7, 8, 9, 10, 11]) 46 | 47 | # fix some of the nodes in the from diagram 48 | # to constraint the problem to a single solution 49 | form.vertices_attribute('is_fixed', True, [0, 2, 5]) 50 | 51 | # # update the form diagram 52 | # graphstatics.form_update_from_force(form, force) 53 | 54 | # update the form diagram 55 | form.data = graphstatics.form_update_from_force_proxy(form.data, force.data) 56 | 57 | # ============================================================================== 58 | # Visualize 59 | # ============================================================================== 60 | 61 | viewer = Viewer(form, force, delay_setup=False, figsize=(12, 7.5)) 62 | 63 | viewer.draw_form( 64 | vertexsize=0.15, 65 | vertexcolor={key: '#000000' for key in (8, 7)}, 66 | vertexlabel={key: key for key in form.vertices()}) 67 | 68 | viewer.draw_force( 69 | vertexsize=0.15, 70 | vertexlabel={key: key for key in force.vertices()}) 71 | 72 | viewer.show() 73 | -------------------------------------------------------------------------------- /data/debugging/truss.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 15 7 0 4 | v 15 5 0 5 | cstype bspline 6 | deg 1 7 | curv 0 2 1 2 8 | parm u 0 0 2 2 9 | end 10 | v 10 7 0 11 | v 10 5 0 12 | cstype bspline 13 | deg 1 14 | curv 0 2 3 4 15 | parm u 0 0 2 2 16 | end 17 | v 5 7 0 18 | v 5 5 0 19 | cstype bspline 20 | deg 1 21 | curv 0 2 5 6 22 | parm u 0 0 2 2 23 | end 24 | v 20 -2 0 25 | v 20 0 0 26 | cstype bspline 27 | deg 1 28 | curv 0 2 7 8 29 | parm u 0 0 2 2 30 | end 31 | v -2 0 0 32 | v 0 0 0 33 | cstype bspline 34 | deg 1 35 | curv 0 2 9 10 36 | parm u 0 0 2 2 37 | end 38 | v 0 -2 0 39 | v 0 0 0 40 | cstype bspline 41 | deg 1 42 | curv 0 2 11 12 43 | parm u 0 0 2 2 44 | end 45 | v 5 5 0 46 | v 10 0 0 47 | cstype bspline 48 | deg 1 49 | curv 0 7.07107 13 14 50 | parm u 0 0 7.07107 7.07107 51 | end 52 | v 0 5 0 53 | v 5 0 0 54 | cstype bspline 55 | deg 1 56 | curv 0 7.07107 15 16 57 | parm u 0 0 7.07107 7.07107 58 | end 59 | v 15 5 0 60 | v 10 0 0 61 | cstype bspline 62 | deg 1 63 | curv 0 7.07107 17 18 64 | parm u 0 0 7.07107 7.07107 65 | end 66 | v 20 5 0 67 | v 15 0 0 68 | cstype bspline 69 | deg 1 70 | curv 0 7.07107 19 20 71 | parm u 0 0 7.07107 7.07107 72 | end 73 | v 15 5 0 74 | v 15 0 0 75 | cstype bspline 76 | deg 1 77 | curv 0 5 21 22 78 | parm u 0 0 5 5 79 | end 80 | v 10 5 0 81 | v 10 0 0 82 | cstype bspline 83 | deg 1 84 | curv 0 5 23 24 85 | parm u 0 0 5 5 86 | end 87 | v 5 5 0 88 | v 5 0 0 89 | cstype bspline 90 | deg 1 91 | curv 0 5 25 26 92 | parm u 0 0 5 5 93 | end 94 | v 0 5 0 95 | v 0 0 0 96 | cstype bspline 97 | deg 1 98 | curv 0 5 27 28 99 | parm u 0 0 5 5 100 | end 101 | v 5 5 0 102 | v 0 5 0 103 | cstype bspline 104 | deg 1 105 | curv 0 5 29 30 106 | parm u 0 0 5 5 107 | end 108 | v 10 5 0 109 | v 5 5 0 110 | cstype bspline 111 | deg 1 112 | curv 0 5 31 32 113 | parm u 0 0 5 5 114 | end 115 | v 15 5 0 116 | v 10 5 0 117 | cstype bspline 118 | deg 1 119 | curv 0 5 33 34 120 | parm u 0 0 5 5 121 | end 122 | v 20 5 0 123 | v 15 5 0 124 | cstype bspline 125 | deg 1 126 | curv 0 5 35 36 127 | parm u 0 0 5 5 128 | end 129 | v 20 0 0 130 | v 20 5 0 131 | cstype bspline 132 | deg 1 133 | curv 0 5 37 38 134 | parm u 0 0 5 5 135 | end 136 | v 15 0 0 137 | v 20 0 0 138 | cstype bspline 139 | deg 1 140 | curv 0 5 39 40 141 | parm u 0 0 5 5 142 | end 143 | v 10 0 0 144 | v 15 0 0 145 | cstype bspline 146 | deg 1 147 | curv 0 5 41 42 148 | parm u 0 0 5 5 149 | end 150 | v 5 0 0 151 | v 10 0 0 152 | cstype bspline 153 | deg 1 154 | curv 0 5 43 44 155 | parm u 0 0 5 5 156 | end 157 | v 0 0 0 158 | v 5 0 0 159 | cstype bspline 160 | deg 1 161 | curv 0 5 45 46 162 | parm u 0 0 5 5 163 | end 164 | -------------------------------------------------------------------------------- /data/debugging/zero.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 15 0 0 4 | v 15 -2 0 5 | cstype bspline 6 | deg 1 7 | curv 0 2 1 2 8 | parm u 0 0 2 2 9 | end 10 | v 10 0 0 11 | v 10 -2 0 12 | cstype bspline 13 | deg 1 14 | curv 0 2 3 4 15 | parm u 0 0 2 2 16 | end 17 | v 5 -2 0 18 | v 5 0 0 19 | cstype bspline 20 | deg 1 21 | curv 0 2 5 6 22 | parm u 0 0 2 2 23 | end 24 | v 20 -2 0 25 | v 20 0 0 26 | cstype bspline 27 | deg 1 28 | curv 0 2 7 8 29 | parm u 0 0 2 2 30 | end 31 | v 0 0 0 32 | v -2 0 0 33 | cstype bspline 34 | deg 1 35 | curv 0 2 9 10 36 | parm u 0 0 2 2 37 | end 38 | v 0 -2 0 39 | v 0 0 0 40 | cstype bspline 41 | deg 1 42 | curv 0 2 11 12 43 | parm u 0 0 2 2 44 | end 45 | v 10 5 0 46 | v 10 0 0 47 | cstype bspline 48 | deg 1 49 | curv 0 5 13 14 50 | parm u 0 0 5 5 51 | end 52 | v 0 0 0 53 | v 0 5 0 54 | cstype bspline 55 | deg 1 56 | curv 0 5 15 16 57 | parm u 0 0 5 5 58 | end 59 | v 5 0 0 60 | v 0 0 0 61 | cstype bspline 62 | deg 1 63 | curv 0 5 17 18 64 | parm u 0 0 5 5 65 | end 66 | v 10 0 0 67 | v 5 0 0 68 | cstype bspline 69 | deg 1 70 | curv 0 5 19 20 71 | parm u 0 0 5 5 72 | end 73 | v 15 0 0 74 | v 10 0 0 75 | cstype bspline 76 | deg 1 77 | curv 0 5 21 22 78 | parm u 0 0 5 5 79 | end 80 | v 20 0 0 81 | v 15 0 0 82 | cstype bspline 83 | deg 1 84 | curv 0 5 23 24 85 | parm u 0 0 5 5 86 | end 87 | v 20 5 0 88 | v 20 0 0 89 | cstype bspline 90 | deg 1 91 | curv 0 5 25 26 92 | parm u 0 0 5 5 93 | end 94 | v 15 5 0 95 | v 20 5 0 96 | cstype bspline 97 | deg 1 98 | curv 0 5 27 28 99 | parm u 0 0 5 5 100 | end 101 | v 10 5 0 102 | v 15 5 0 103 | cstype bspline 104 | deg 1 105 | curv 0 5 29 30 106 | parm u 0 0 5 5 107 | end 108 | v 5 5 0 109 | v 10 5 0 110 | cstype bspline 111 | deg 1 112 | curv 0 5 31 32 113 | parm u 0 0 5 5 114 | end 115 | v 0 5 0 116 | v 5 5 0 117 | cstype bspline 118 | deg 1 119 | curv 0 5 33 34 120 | parm u 0 0 5 5 121 | end 122 | v 15 5 0 123 | v 20 0 0 124 | cstype bspline 125 | deg 1 126 | curv 0 7.07107 35 36 127 | parm u 0 0 7.07107 7.07107 128 | end 129 | v 15 0 0 130 | v 15 5 0 131 | cstype bspline 132 | deg 1 133 | curv 0 5 37 38 134 | parm u 0 0 5 5 135 | end 136 | v 10 5 0 137 | v 15 0 0 138 | cstype bspline 139 | deg 1 140 | curv 0 7.07107 39 40 141 | parm u 0 0 7.07107 7.07107 142 | end 143 | v 5 0 0 144 | v 10 5 0 145 | cstype bspline 146 | deg 1 147 | curv 0 7.07107 41 42 148 | parm u 0 0 7.07107 7.07107 149 | end 150 | v 5 5 0 151 | v 5 0 0 152 | cstype bspline 153 | deg 1 154 | curv 0 5 43 44 155 | parm u 0 0 5 5 156 | end 157 | v 0 0 0 158 | v 5 5 0 159 | cstype bspline 160 | deg 1 161 | curv 0 7.07107 45 46 162 | parm u 0 0 7.07107 7.07107 163 | end 164 | -------------------------------------------------------------------------------- /docs/publications/ags.rst: -------------------------------------------------------------------------------- 1 | ******************************************************************************** 2 | Algebraic Graph Statics 3 | ******************************************************************************** 4 | 5 | .. warning:: 6 | 7 | Under construction... 8 | 9 | 10 | .. code-block:: latex 11 | 12 | @article{VanMele2014, 13 | author = "Van Mele, T. and Block, P.", 14 | title = "Algebraic Graph Statics", 15 | journal = "Computer-Aided Design", 16 | year = "2014", 17 | volume = "53", 18 | number = "", 19 | pages = "104-116", 20 | month = "", 21 | doi = "10.1016/j.cad.2014.04.004", 22 | note = "", 23 | } 24 | 25 | 26 | **Abstract** 27 | 28 | This paper presents a general, non-procedural, algebraic approach to graphical 29 | analysis of structures. 30 | Using graph theoretical properties of reciprocal graphs, the geometrical relation 31 | between the form and force diagrams used in graphic statics is written algebraically. 32 | These formulations have been found to be equivalent to the equilibrium equations 33 | used in matrix analysis of planar, self-stressed structural systems. 34 | The significance and uses of this general approach are demonstrated through several 35 | examples and it is shown that it provides a robust back-end for a real-time, interactive 36 | and flexible computational implementation of traditional graphic statics. 37 | 38 | 39 | **Contributions** 40 | 41 | The paper brings together concepts and techniques from graph theory and matrix 42 | analysis of structures and present them in a unified framework for algebraic graphical 43 | analysis built around the reciprocal relation between the form and force diagrams 44 | of graphic statics. 45 | 46 | A general scheme is discussed for a computational implementation of the presented 47 | approach that can be used as back-end of a real-time, interactive graphic statics 48 | application. 49 | Different steps of the implementation are illustrated using a Fink truss, which 50 | is a statically determinate structure that cannot be calculated directly with 51 | traditional graphic statics, because it contains crossing edges. 52 | Relevant algorithms are provided as code snippets. 53 | 54 | The use of this framework for non-procedural graphic statics is demonstrated through 55 | four examples: a three-hinged trussed frame, an externally statically indeterminate 56 | three-bar truss, a geometrically constrained thrust line, defining its funicular 57 | loading, and a pre-stressed net. 58 | Finally, we briefly discuss the relevance of the presented approach for three-dimensional 59 | equilibrium methods, such as Thrust Network Analysis. 60 | -------------------------------------------------------------------------------- /scripts/test_sync_ltr.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from __future__ import absolute_import 3 | from __future__ import division 4 | 5 | import os 6 | 7 | from compas.rpc import Proxy 8 | 9 | from compas_ags.diagrams import FormGraph 10 | from compas_ags.diagrams import FormDiagram 11 | from compas_ags.diagrams import ForceDiagram 12 | 13 | from compas_ags.rhino import Scene 14 | 15 | graphstatics = Proxy('compas_ags.ags.graphstatics') 16 | 17 | HERE = os.path.dirname(__file__) 18 | DATA = os.path.join(HERE, '../data') 19 | FILE = os.path.join(DATA, 'debugging', 'truss.obj') 20 | 21 | # ============================================================================== 22 | # Init 23 | # ============================================================================== 24 | 25 | graph = FormGraph.from_obj(FILE) 26 | 27 | form = FormDiagram.from_graph(graph) 28 | force = ForceDiagram.from_formdiagram(form) 29 | 30 | # ============================================================================== 31 | # Boundary conditions 32 | # ============================================================================== 33 | 34 | form.edge_force((0, 1), -1.0) 35 | form.edge_force((2, 3), -1.0) 36 | form.edge_force((4, 5), -1.0) 37 | 38 | # ============================================================================== 39 | # Equilibrium 40 | # ============================================================================== 41 | 42 | form.data = graphstatics.form_update_q_from_qind_proxy(form.data) 43 | force.data = graphstatics.force_update_from_form_proxy(force.data, form.data) 44 | 45 | # ============================================================================== 46 | # Visualize 47 | # ============================================================================== 48 | 49 | scene = Scene() 50 | 51 | form_id = scene.add(form, name="Form", layer="AGS::FormDiagram", visible=True) 52 | force_id = scene.add(force, name="Force", layer="AGS::ForceDiagram", visible=True) 53 | 54 | scene.clear_layers() 55 | 56 | form_obj = scene.find(form_id) 57 | force_obj = scene.find(force_id) 58 | 59 | # this should become part of "add" 60 | force_obj.anchor = 5 61 | force_obj.location = [35, 0, 0] 62 | force_obj.scale = 5.0 63 | 64 | scene.update() 65 | 66 | # ============================================================================== 67 | # Update 68 | # ============================================================================== 69 | 70 | while True: 71 | vertices = form_obj.select_vertices() 72 | if not vertices: 73 | break 74 | 75 | if form_obj.move_vertices(vertices): 76 | # update force diagram 77 | form.data = graphstatics.form_update_q_from_qind_proxy(form.data) 78 | force.data = graphstatics.force_update_from_form_proxy(force.data, form.data) 79 | # update the scene 80 | scene.update() 81 | -------------------------------------------------------------------------------- /scripts/test_sync_rtl.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from __future__ import absolute_import 3 | from __future__ import division 4 | 5 | import os 6 | 7 | from compas.rpc import Proxy 8 | 9 | from compas_ags.diagrams import FormGraph 10 | from compas_ags.diagrams import FormDiagram 11 | from compas_ags.diagrams import ForceDiagram 12 | 13 | from compas_ags.rhino import Scene 14 | 15 | graphstatics = Proxy('compas_ags.ags.graphstatics') 16 | 17 | HERE = os.path.dirname(__file__) 18 | DATA = os.path.join(HERE, '../data') 19 | FILE = os.path.join(DATA, 'paper', 'gs_form_force.obj') 20 | 21 | # ============================================================================== 22 | # Init 23 | # ============================================================================== 24 | 25 | graph = FormGraph.from_obj(FILE) 26 | 27 | form = FormDiagram.from_graph(graph) 28 | force = ForceDiagram.from_formdiagram(form) 29 | 30 | # ============================================================================== 31 | # Boundary conditions 32 | # ============================================================================== 33 | 34 | left = next(form.vertices_where({'x': 0.0, 'y': 0.0})) 35 | right = next(form.vertices_where({'x': 6.0, 'y': 0.0})) 36 | 37 | form.vertices_attribute('is_fixed', True, keys=[left, right]) 38 | 39 | form.edge_force(1, -10.0) 40 | 41 | # ============================================================================== 42 | # Equilibrium 43 | # ============================================================================== 44 | 45 | form.data = graphstatics.form_update_q_from_qind_proxy(form.data) 46 | force.data = graphstatics.force_update_from_form_proxy(force.data, form.data) 47 | 48 | # ============================================================================== 49 | # Visualize 50 | # ============================================================================== 51 | 52 | scene = Scene() 53 | 54 | form_id = scene.add(form, name="Form", layer="AGS::FormDiagram") 55 | force_id = scene.add(force, name="Force", layer="AGS::ForceDiagram") 56 | 57 | scene.clear_layers() 58 | 59 | form_obj = scene.find(form_id) 60 | force_obj = scene.find(force_id) 61 | 62 | # this should become part of "add" 63 | force_obj.anchor = 3 64 | force_obj.location = [15, 0, 0] 65 | force_obj.scale = 0.3 66 | 67 | scene.update() 68 | 69 | # ============================================================================== 70 | # Update 71 | # ============================================================================== 72 | 73 | while True: 74 | vertices = force_obj.select_vertices() 75 | if not vertices: 76 | break 77 | if force_obj.move_vertices(vertices): 78 | # update form diagram 79 | form.data = graphstatics.form_update_from_force_proxy(form.data, force.data, kmax=100) 80 | # update the scene 81 | scene.update() 82 | -------------------------------------------------------------------------------- /data/paper/two-rings.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 0 10 0 4 | v 0 13 0 5 | cstype bspline 6 | deg 1 7 | curv 10 13 1 2 8 | parm u 10 10 13 13 9 | end 10 | v 0 5 0 11 | v 0 10 0 12 | cstype bspline 13 | deg 1 14 | curv 5 10 3 4 15 | parm u 5 5 10 10 16 | end 17 | v 0 0 0 18 | v 0 5 0 19 | cstype bspline 20 | deg 1 21 | curv 0 5 5 6 22 | parm u 0 0 5 5 23 | end 24 | v 10 0 0 25 | v 13 0 0 26 | cstype bspline 27 | deg 1 28 | curv 10 13 7 8 29 | parm u 10 10 13 13 30 | end 31 | v 5 0 0 32 | v 10 0 0 33 | cstype bspline 34 | deg 1 35 | curv 5 10 9 10 36 | parm u 5 5 10 10 37 | end 38 | v 0 0 0 39 | v 5 0 0 40 | cstype bspline 41 | deg 1 42 | curv 0 5 11 12 43 | parm u 0 0 5 5 44 | end 45 | v 0 -10 0 46 | v 0 -13 0 47 | cstype bspline 48 | deg 1 49 | curv 10 13 13 14 50 | parm u 10 10 13 13 51 | end 52 | v 0 -5 0 53 | v 0 -10 0 54 | cstype bspline 55 | deg 1 56 | curv 5 10 15 16 57 | parm u 5 5 10 10 58 | end 59 | v 0 0 0 60 | v 0 -5 0 61 | cstype bspline 62 | deg 1 63 | curv 0 5 17 18 64 | parm u 0 0 5 5 65 | end 66 | v -10 0 0 67 | v -13 0 0 68 | cstype bspline 69 | deg 1 70 | curv 10 13 19 20 71 | parm u 10 10 13 13 72 | end 73 | v -5 0 0 74 | v -10 0 0 75 | cstype bspline 76 | deg 1 77 | curv 5 10 21 22 78 | parm u 5 5 10 10 79 | end 80 | v 0 0 0 81 | v -5 0 0 82 | cstype bspline 83 | deg 1 84 | curv 0 5 23 24 85 | parm u 0 0 5 5 86 | end 87 | v 0 -5 0 88 | v -5 0 0 89 | cstype bspline 90 | deg 1 91 | curv 21.21320343559643 28.2842712474619 25 26 92 | parm u 21.21320343559643 21.21320343559643 28.2842712474619 28.2842712474619 93 | end 94 | v 5 0 0 95 | v 0 -5 0 96 | cstype bspline 97 | deg 1 98 | curv 14.14213562373095 21.21320343559643 27 28 99 | parm u 14.14213562373095 14.14213562373095 21.21320343559643 21.21320343559643 100 | end 101 | v 0 5 0 102 | v 5 0 0 103 | cstype bspline 104 | deg 1 105 | curv 7.071067811865476 14.14213562373095 29 30 106 | parm u 7.071067811865476 7.071067811865476 14.14213562373095 14.14213562373095 107 | end 108 | v -5 0 0 109 | v 0 5 0 110 | cstype bspline 111 | deg 1 112 | curv 0 7.071067811865476 31 32 113 | parm u 0 0 7.071067811865476 7.071067811865476 114 | end 115 | v 10 0 0 116 | v 0 -10 0 117 | cstype bspline 118 | deg 1 119 | curv 28.2842712474619 42.42640687119285 33 34 120 | parm u 28.2842712474619 28.2842712474619 42.42640687119285 42.42640687119285 121 | end 122 | v 0 10 0 123 | v 10 0 0 124 | cstype bspline 125 | deg 1 126 | curv 14.14213562373095 28.2842712474619 35 36 127 | parm u 14.14213562373095 14.14213562373095 28.2842712474619 28.2842712474619 128 | end 129 | v -10 0 0 130 | v 0 10 0 131 | cstype bspline 132 | deg 1 133 | curv 0 14.14213562373095 37 38 134 | parm u 0 0 14.14213562373095 14.14213562373095 135 | end 136 | v -10 0 0 137 | v 0 -10 0 138 | cstype bspline 139 | deg 1 140 | curv 0 14.14213562373095 39 40 141 | parm u 0 0 14.14213562373095 14.14213562373095 142 | end 143 | -------------------------------------------------------------------------------- /docs/examples/01_rtl.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.ags import graphstatics 3 | from compas_ags.diagrams import ForceDiagram 4 | from compas_ags.diagrams import FormDiagram 5 | from compas_ags.diagrams import FormGraph 6 | from compas_ags.viewer import AGSViewer 7 | 8 | # ============================================================================== 9 | # Construct the graph of a single panel truss, 10 | # including loads and reaction forces. 11 | # ============================================================================== 12 | 13 | graph = FormGraph.from_obj(compas_ags.get("paper/gs_form_force.obj")) 14 | 15 | form = FormDiagram.from_graph(graph) 16 | force = ForceDiagram.from_formdiagram(form) 17 | 18 | # ============================================================================== 19 | # Fix the left and right supports. 20 | # ============================================================================== 21 | 22 | left = next(form.vertices_where({"x": 0.0, "y": 0.0})) 23 | right = next(form.vertices_where({"x": 6.0, "y": 0.0})) 24 | fixed = [left, right] 25 | 26 | form.vertices_attribute("is_fixed", True, keys=fixed) 27 | 28 | # ============================================================================== 29 | # Set the magnitude of the load. 30 | # ============================================================================== 31 | 32 | form.edge_force(1, -10.0) 33 | 34 | # ============================================================================== 35 | # Update the force densities in the form diagram. 36 | # ============================================================================== 37 | 38 | graphstatics.form_update_q_from_qind(form) 39 | 40 | # ============================================================================== 41 | # Update the geometry of the force diagram. 42 | # ============================================================================== 43 | 44 | graphstatics.force_update_from_form(force, form) 45 | 46 | # ============================================================================== 47 | # Store the original geometries. 48 | # ============================================================================== 49 | 50 | # ============================================================================== 51 | # Change the position of the "free" node of the force diagram 52 | # ============================================================================== 53 | 54 | force.vertex[4]["x"] -= 8.0 55 | 56 | # ============================================================================== 57 | # Update the form diagram accordingly. 58 | # ============================================================================== 59 | 60 | graphstatics.form_update_from_force(form, force, kmax=100) 61 | 62 | # ============================================================================== 63 | # Indicate the movement of the free node in the force diagram with an arrow. 64 | # ============================================================================== 65 | 66 | # ============================================================================== 67 | # Visualize the result. 68 | # ============================================================================== 69 | 70 | viewer = AGSViewer() 71 | viewer.add_form(form, scale_forces=0.05) 72 | viewer.add_force(force) 73 | viewer.show() 74 | -------------------------------------------------------------------------------- /docs/examples/03_fink.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas.geometry import normalize_vector 3 | from compas.geometry import scale_vector 4 | from compas.geometry import subtract_vectors 5 | from compas.geometry import sum_vectors 6 | from compas_ags.diagrams import FormDiagram 7 | from compas_ags.diagrams import FormGraph 8 | from compas_ags.viewer import AGSViewer 9 | 10 | # ============================================================================== 11 | # Construct the graph of a Fink truss. 12 | # ============================================================================== 13 | 14 | graph = FormGraph.from_obj(compas_ags.get("paper/fink.obj")) 15 | 16 | # ============================================================================== 17 | # Identify the fixed points of the graph. 18 | # ============================================================================== 19 | 20 | fixed = [6, 9] 21 | 22 | # ============================================================================== 23 | # Assert that the graph is 2D and that it is planar. 24 | # ============================================================================== 25 | 26 | assert graph.is_2d(), "The graph is not 2D." 27 | assert graph.is_planar(), "The graph is not planar." 28 | 29 | # ============================================================================== 30 | # Construct a planar embedding of the graph 31 | # ============================================================================== 32 | 33 | embedding: FormGraph = graph.copy() 34 | 35 | if embedding.is_crossed(): 36 | 37 | # if the graph has crossed edges 38 | # we create a copy without leaves 39 | # and compute a spring layout to find a planar embedding 40 | # finally we re-add the leaves towards "the outside" 41 | 42 | noleaves: FormGraph = embedding.copy() 43 | for node in embedding.leaves(): 44 | noleaves.delete_node(node) 45 | 46 | assert noleaves.embed(fixed=fixed), "The graph could not be embedded in the plane using a spring layout." 47 | 48 | E = noleaves.number_of_edges() 49 | L = sum(noleaves.edge_length(edge) for edge in noleaves.edges()) 50 | length = 0.5 * L / E 51 | 52 | for node in noleaves.nodes(): 53 | embedding.node_attributes(node, "xy", noleaves.node_attributes(node, "xy")) 54 | 55 | for node in embedding.leaves(): 56 | u = embedding.neighbors(node)[0] 57 | if u in fixed: 58 | continue 59 | 60 | a = embedding.node_attributes(u, "xyz") 61 | 62 | vectors = [] 63 | for v in noleaves.neighbors(u): 64 | b = embedding.node_attributes(v, "xyz") 65 | vectors.append(normalize_vector(subtract_vectors(b, a))) 66 | 67 | ab = scale_vector(normalize_vector(scale_vector(sum_vectors(vectors), 1 / len(vectors))), length) 68 | embedding.node_attributes(node, "xyz", subtract_vectors(a, ab)) 69 | 70 | # ============================================================================== 71 | # Construct a form diagram of the embedding 72 | # ============================================================================== 73 | 74 | form = FormDiagram.from_graph(embedding) 75 | 76 | # ============================================================================== 77 | # Visualize the result 78 | # ============================================================================== 79 | 80 | viewer = AGSViewer() 81 | viewer.add_form(form, show_forces=False) 82 | viewer.show() 83 | -------------------------------------------------------------------------------- /data/debugging/howe.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 50 -5 0 4 | v 50 0 0 5 | cstype bspline 6 | deg 1 7 | curv 0 5 1 2 8 | parm u 0 0 5 5 9 | end 10 | v 40 -5 0 11 | v 40 0 0 12 | cstype bspline 13 | deg 1 14 | curv 0 5 3 4 15 | parm u 0 0 5 5 16 | end 17 | v 30 -5 0 18 | v 30 0 0 19 | cstype bspline 20 | deg 1 21 | curv 0 5 5 6 22 | parm u 0 0 5 5 23 | end 24 | v 20 -5 0 25 | v 20 0 0 26 | cstype bspline 27 | deg 1 28 | curv 0 5 7 8 29 | parm u 0 0 5 5 30 | end 31 | v 10 -5 0 32 | v 10 0 0 33 | cstype bspline 34 | deg 1 35 | curv 0 5 9 10 36 | parm u 0 0 5 5 37 | end 38 | v 60 -5 0 39 | v 60 0 0 40 | cstype bspline 41 | deg 1 42 | curv 0 5 11 12 43 | parm u 0 0 5 5 44 | end 45 | v 0 0 0 46 | v -5 0 0 47 | cstype bspline 48 | deg 1 49 | curv 0 5 13 14 50 | parm u 0 0 5 5 51 | end 52 | v 0 -5 0 53 | v 0 0 0 54 | cstype bspline 55 | deg 1 56 | curv 0 5 15 16 57 | parm u 0 0 5 5 58 | end 59 | v 50 0 0 60 | v 60 0 0 61 | cstype bspline 62 | deg 1 63 | curv 0 10 17 18 64 | parm u 0 0 10 10 65 | end 66 | v 30 0 0 67 | v 40 0 0 68 | cstype bspline 69 | deg 1 70 | curv 0 10 19 20 71 | parm u 0 0 10 10 72 | end 73 | v 10 0 0 74 | v 20 0 0 75 | cstype bspline 76 | deg 1 77 | curv 0 10 21 22 78 | parm u 0 0 10 10 79 | end 80 | v 0 0 0 81 | v 10 0 0 82 | cstype bspline 83 | deg 1 84 | curv 0 10 23 24 85 | parm u 0 0 10 10 86 | end 87 | v 20 10 0 88 | v 30 10 0 89 | cstype bspline 90 | deg 1 91 | curv 0 10 25 26 92 | parm u 0 0 10 10 93 | end 94 | v 50 10 0 95 | v 40 10 0 96 | cstype bspline 97 | deg 1 98 | curv 0 10 27 28 99 | parm u 0 0 10 10 100 | end 101 | v 50 0 0 102 | v 50 10 0 103 | cstype bspline 104 | deg 1 105 | curv 0 10 29 30 106 | parm u 0 0 10 10 107 | end 108 | v 40 0 0 109 | v 50 0 0 110 | cstype bspline 111 | deg 1 112 | curv 0 10 31 32 113 | parm u 0 0 10 10 114 | end 115 | v 40 10 0 116 | v 40 0 0 117 | cstype bspline 118 | deg 1 119 | curv 0 10 33 34 120 | parm u 0 0 10 10 121 | end 122 | v 30 10 0 123 | v 40 10 0 124 | cstype bspline 125 | deg 1 126 | curv 0 10 35 36 127 | parm u 0 0 10 10 128 | end 129 | v 30 0 0 130 | v 30 10 0 131 | cstype bspline 132 | deg 1 133 | curv 0 10 37 38 134 | parm u 0 0 10 10 135 | end 136 | v 20 0 0 137 | v 30 0 0 138 | cstype bspline 139 | deg 1 140 | curv 0 10 39 40 141 | parm u 0 0 10 10 142 | end 143 | v 20 10 0 144 | v 20 0 0 145 | cstype bspline 146 | deg 1 147 | curv 0 10 41 42 148 | parm u 0 0 10 10 149 | end 150 | v 10 10 0 151 | v 20 10 0 152 | cstype bspline 153 | deg 1 154 | curv 0 10 43 44 155 | parm u 0 0 10 10 156 | end 157 | v 10 0 0 158 | v 10 10 0 159 | cstype bspline 160 | deg 1 161 | curv 0 10 45 46 162 | parm u 0 0 10 10 163 | end 164 | v 50 10 0 165 | v 60 0 0 166 | cstype bspline 167 | deg 1 168 | curv 0 14.1421 47 48 169 | parm u 0 0 14.1421 14.1421 170 | end 171 | v 40 0 0 172 | v 50 10 0 173 | cstype bspline 174 | deg 1 175 | curv 0 14.1421 49 50 176 | parm u 0 0 14.1421 14.1421 177 | end 178 | v 30 10 0 179 | v 40 0 0 180 | cstype bspline 181 | deg 1 182 | curv 0 14.1421 51 52 183 | parm u 0 0 14.1421 14.1421 184 | end 185 | v 20 0 0 186 | v 30 10 0 187 | cstype bspline 188 | deg 1 189 | curv 0 14.1421 53 54 190 | parm u 0 0 14.1421 14.1421 191 | end 192 | v 10 10 0 193 | v 20 0 0 194 | cstype bspline 195 | deg 1 196 | curv 0 14.1421 55 56 197 | parm u 0 0 14.1421 14.1421 198 | end 199 | v 0 0 0 200 | v 10 10 0 201 | cstype bspline 202 | deg 1 203 | curv 0 14.1421 57 58 204 | parm u 0 0 14.1421 14.1421 205 | end 206 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at brg@arch.ethz.ch. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /data/paper/gs_truss.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | mtllib gs_truss.mtl 4 | usemtl diffuse_0_0_0_255 5 | v 8 1.2799999999999998 0 6 | v 10 0 0 7 | cstype bspline 8 | deg 1 9 | curv 0 2.3745315327449328 1 2 10 | parm u 0 0 2.3745315327449328 2.3745315327449328 11 | end 12 | usemtl diffuse_0_0_0_255 13 | v 6 1.9199999999999997 0 14 | v 8 1.2799999999999998 0 15 | cstype bspline 16 | deg 1 17 | curv 0 2.09990475974507 3 4 18 | parm u 0 0 2.09990475974507 2.09990475974507 19 | end 20 | usemtl diffuse_0_0_0_255 21 | v 3.9999999999999991 1.9199999999999997 0 22 | v 6 1.9199999999999997 0 23 | cstype bspline 24 | deg 1 25 | curv 0 2.0000000000000009 5 6 26 | parm u 0 0 2.0000000000000009 2.0000000000000009 27 | end 28 | usemtl diffuse_0_0_0_255 29 | v 1.9999999999999996 1.2799999999999998 0 30 | v 3.9999999999999991 1.9199999999999997 0 31 | cstype bspline 32 | deg 1 33 | curv 0 2.0999047597450695 7 8 34 | parm u 0 0 2.0999047597450695 2.0999047597450695 35 | end 36 | usemtl diffuse_0_0_0_255 37 | v 0 0 0 38 | v 1.9999999999999996 1.2799999999999998 0 39 | cstype bspline 40 | deg 1 41 | curv 0 2.3745315327449323 9 10 42 | parm u 0 0 2.3745315327449323 2.3745315327449323 43 | end 44 | usemtl diffuse_0_0_0_255 45 | v 2 0 0 46 | v 1.9999999999999996 1.2799999999999998 0 47 | cstype bspline 48 | deg 1 49 | curv 0 1.2799999999999998 11 12 50 | parm u 0 0 1.2799999999999998 1.2799999999999998 51 | end 52 | usemtl diffuse_0_0_0_255 53 | v 4 0 0 54 | v 3.9999999999999991 1.9199999999999997 0 55 | cstype bspline 56 | deg 1 57 | curv 0 1.9199999999999997 13 14 58 | parm u 0 0 1.9199999999999997 1.9199999999999997 59 | end 60 | usemtl diffuse_0_0_0_255 61 | v 8 0 0 62 | v 8 1.2799999999999998 0 63 | cstype bspline 64 | deg 1 65 | curv 0 1.2799999999999998 15 16 66 | parm u 0 0 1.2799999999999998 1.2799999999999998 67 | end 68 | usemtl diffuse_0_0_0_255 69 | v 6.0000000000000009 0 0 70 | v 6 1.9199999999999997 0 71 | cstype bspline 72 | deg 1 73 | curv 0 1.9199999999999997 17 18 74 | parm u 0 0 1.9199999999999997 1.9199999999999997 75 | end 76 | usemtl diffuse_0_0_0_255 77 | v 10 0 0 78 | v 10 -2 0 79 | cstype bspline 80 | deg 1 81 | curv 0 2 19 20 82 | parm u 0 0 2 2 83 | end 84 | usemtl diffuse_0_0_0_255 85 | v 8 0 0 86 | v 8 -2 0 87 | cstype bspline 88 | deg 1 89 | curv 0 2 21 22 90 | parm u 0 0 2 2 91 | end 92 | usemtl diffuse_0_0_0_255 93 | v 6.0000000000000009 0 0 94 | v 6.0000000000000009 -2 0 95 | cstype bspline 96 | deg 1 97 | curv 0 2 23 24 98 | parm u 0 0 2 2 99 | end 100 | usemtl diffuse_0_0_0_255 101 | v 4 0 0 102 | v 4 -2 0 103 | cstype bspline 104 | deg 1 105 | curv 0 2 25 26 106 | parm u 0 0 2 2 107 | end 108 | usemtl diffuse_0_0_0_255 109 | v 2 0 0 110 | v 2 -2 0 111 | cstype bspline 112 | deg 1 113 | curv 0 2 27 28 114 | parm u 0 0 2 2 115 | end 116 | usemtl diffuse_0_0_0_255 117 | v 0 0 0 118 | v 0 -2 0 119 | cstype bspline 120 | deg 1 121 | curv 0 2 29 30 122 | parm u 0 0 2 2 123 | end 124 | usemtl diffuse_0_0_0_255 125 | v 8 0 0 126 | v 10 0 0 127 | cstype bspline 128 | deg 1 129 | curv 8 10 31 32 130 | parm u 8 8 10 10 131 | end 132 | usemtl diffuse_0_0_0_255 133 | v 6.0000000000000009 0 0 134 | v 8 0 0 135 | cstype bspline 136 | deg 1 137 | curv 6.0000000000000009 8 33 34 138 | parm u 6.0000000000000009 6.0000000000000009 8 8 139 | end 140 | usemtl diffuse_0_0_0_255 141 | v 4 0 0 142 | v 6.0000000000000009 0 0 143 | cstype bspline 144 | deg 1 145 | curv 4 6.0000000000000009 35 36 146 | parm u 4 4 6.0000000000000009 6.0000000000000009 147 | end 148 | usemtl diffuse_0_0_0_255 149 | v 2 0 0 150 | v 4 0 0 151 | cstype bspline 152 | deg 1 153 | curv 2 4 37 38 154 | parm u 2 2 4 4 155 | end 156 | usemtl diffuse_0_0_0_255 157 | v 0 0 0 158 | v 2 0 0 159 | cstype bspline 160 | deg 1 161 | curv 0 2 39 40 162 | parm u 0 0 2 2 163 | end 164 | -------------------------------------------------------------------------------- /scripts/example_fix_x_AGS.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.diagrams import FormGraph 3 | from compas_ags.diagrams import FormDiagram 4 | from compas_ags.diagrams import ForceDiagram 5 | from compas_ags.viewers import Viewer 6 | from compas_ags.ags import form_update_from_force 7 | from compas_ags.ags import form_update_q_from_qind 8 | from compas_ags.ags import force_update_from_form 9 | from compas_ags.ags import ConstraintsCollection 10 | from compas_ags.ags import form_update_from_force 11 | 12 | # ------------------------------------------------------------------------------ 13 | # 1. create a simple arch from nodes and edges, make form and force diagrams 14 | # ------------------------------------------------------------------------------ 15 | 16 | graph = FormGraph.from_json(compas_ags.get('paper/gs_arch.json')) 17 | form = FormDiagram.from_graph(graph) 18 | force = ForceDiagram.from_formdiagram(form) 19 | 20 | # ------------------------------------------------------------------------------ 21 | # 2. prescribe edge force density and set fixed vertices 22 | # ------------------------------------------------------------------------------ 23 | # prescribe force density to edge 24 | edges_ind = [ 25 | (2, 11), 26 | ] 27 | for index in edges_ind: 28 | u, v = index 29 | form.edge_attribute((u, v), 'is_ind', True) 30 | form.edge_attribute((u, v), 'q', -1.) 31 | 32 | # set the fixed corners 33 | left = 2 34 | right = 9 35 | fixed = [left, right] 36 | 37 | for key in fixed: 38 | form.vertex_attribute(key, 'is_fixed', True) 39 | 40 | # set the horizontal fix in internal nodes: 41 | internal = [0, 4, 5, 6, 7, 8] 42 | 43 | for key in internal: 44 | form.vertex_attribute(key, 'is_fixed_x', True) 45 | 46 | # update the diagrams 47 | form_update_q_from_qind(form) 48 | force_update_from_form(force, form) 49 | 50 | # store lines representing the current state of equilibrium 51 | form_lines = [] 52 | for u, v in form.edges(): 53 | form_lines.append({ 54 | 'start': form.vertex_coordinates(u, 'xy'), 55 | 'end': form.vertex_coordinates(v, 'xy'), 56 | 'width': 1.0, 57 | 'color': '#cccccc', 58 | 'style': '--' 59 | }) 60 | 61 | force_lines = [] 62 | for u, v in force.edges(): 63 | force_lines.append({ 64 | 'start': force.vertex_coordinates(u, 'xy'), 65 | 'end': force.vertex_coordinates(v, 'xy'), 66 | 'width': 1.0, 67 | 'color': '#cccccc', 68 | 'style': '--' 69 | }) 70 | 71 | # -------------------------------------------------------------------------- 72 | # 3. force diagram manipulation and modify the form diagram 73 | # -------------------------------------------------------------------------- 74 | 75 | # modify the geometry of the force diagram moving nodes further at right to the left 76 | move_vertices = [0, 9, 8] 77 | translation = +1.0 78 | for key in move_vertices: 79 | x0 = force.vertex_attribute(key, 'x') 80 | force.vertex_attribute(key, 'x', x0 + translation) 81 | 82 | form_update_from_force(form, force) 83 | 84 | # ------------------------------------------------------------------------------ 85 | # 4. display the orginal configuration 86 | # and the configuration after modifying the force diagram 87 | # ------------------------------------------------------------------------------ 88 | viewer = Viewer(form, force, delay_setup=False) 89 | 90 | viewer.draw_form(lines=form_lines, 91 | forces_on=True, 92 | vertexlabel={key: key for key in form.vertices()}, 93 | external_on=False, 94 | vertexsize=0.2, 95 | vertexcolor={key: '#000000' for key in fixed}, 96 | edgelabel={uv: index for index, uv in enumerate(form.edges())} 97 | ) 98 | 99 | viewer.draw_force(lines=force_lines, 100 | vertexlabel={key: key for key in force.vertices()}, 101 | vertexsize=0.2, 102 | edgelabel={uv: index for index, uv in enumerate(force.edges())} 103 | ) 104 | 105 | viewer.show() 106 | -------------------------------------------------------------------------------- /data/paper/exD_truss.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | mtllib ex3_truss.mtl 4 | usemtl diffuse_0_0_0_255 5 | v 4 0.6399999999999999 0 6 | v 5 0 0 7 | cstype bspline 8 | deg 1 9 | curv 4.2871705261175368 5.4744362924900027 1 2 10 | parm u 4.2871705261175368 4.2871705261175368 5.4744362924900027 5.4744362924900027 11 | end 12 | usemtl diffuse_0_0_0_255 13 | v 3.0000000000000004 0.95999999999999996 0 14 | v 4 0.6399999999999999 0 15 | cstype bspline 16 | deg 1 17 | curv 3.2372181462450018 4.2871705261175368 3 4 18 | parm u 3.2372181462450018 3.2372181462450018 4.2871705261175368 4.2871705261175368 19 | end 20 | usemtl diffuse_0_0_0_255 21 | v 2 0.95999999999999996 0 22 | v 3.0000000000000004 0.95999999999999996 0 23 | cstype bspline 24 | deg 1 25 | curv 2.2372181462450014 3.2372181462450018 5 6 26 | parm u 2.2372181462450014 2.2372181462450014 3.2372181462450018 3.2372181462450018 27 | end 28 | usemtl diffuse_0_0_0_255 29 | v 1 0.64000000000000012 0 30 | v 2 0.95999999999999996 0 31 | cstype bspline 32 | deg 1 33 | curv 1.1872657663724664 2.2372181462450014 7 8 34 | parm u 1.1872657663724664 1.1872657663724664 2.2372181462450014 2.2372181462450014 35 | end 36 | usemtl diffuse_0_0_0_255 37 | v 0 0 0 38 | v 1 0.64000000000000012 0 39 | cstype bspline 40 | deg 1 41 | curv 0 1.1872657663724664 9 10 42 | parm u 0 0 1.1872657663724664 1.1872657663724664 43 | end 44 | usemtl diffuse_0_0_0_255 45 | v 5 0 0 46 | v 5 -1 0 47 | cstype bspline 48 | deg 1 49 | curv 0 1 11 12 50 | parm u 0 0 1 1 51 | end 52 | usemtl diffuse_0_0_0_255 53 | v 4 0 0 54 | v 4 -1 0 55 | cstype bspline 56 | deg 1 57 | curv 0 1 13 14 58 | parm u 0 0 1 1 59 | end 60 | usemtl diffuse_0_0_0_255 61 | v 3.0000000000000004 0 0 62 | v 3 -1 0 63 | cstype bspline 64 | deg 1 65 | curv 0 1 15 16 66 | parm u 0 0 1 1 67 | end 68 | usemtl diffuse_0_0_0_255 69 | v 2 0 0 70 | v 2 -1 0 71 | cstype bspline 72 | deg 1 73 | curv 0 1 17 18 74 | parm u 0 0 1 1 75 | end 76 | usemtl diffuse_0_0_0_255 77 | v 1 0 0 78 | v 1 -1 0 79 | cstype bspline 80 | deg 1 81 | curv 0 1 19 20 82 | parm u 0 0 1 1 83 | end 84 | usemtl diffuse_0_0_0_255 85 | v 0 0 0 86 | v 0 -1 0 87 | cstype bspline 88 | deg 1 89 | curv 0 1 21 22 90 | parm u 0 0 1 1 91 | end 92 | usemtl diffuse_0_0_0_255 93 | v 0 0 0 94 | v -1 0 0 95 | cstype bspline 96 | deg 1 97 | curv 0 1 23 24 98 | parm u 0 0 1 1 99 | end 100 | usemtl diffuse_0_0_0_255 101 | v 1 0 0 102 | v 1 0.64000000000000012 0 103 | cstype bspline 104 | deg 1 105 | curv 0 0.64000000000000012 25 26 106 | parm u 0 0 0.64000000000000012 0.64000000000000012 107 | end 108 | usemtl diffuse_0_0_0_255 109 | v 2 0 0 110 | v 2 0.95999999999999996 0 111 | cstype bspline 112 | deg 1 113 | curv 0 0.95999999999999996 27 28 114 | parm u 0 0 0.95999999999999996 0.95999999999999996 115 | end 116 | usemtl diffuse_0_0_0_255 117 | v 4 0 0 118 | v 4 0.6399999999999999 0 119 | cstype bspline 120 | deg 1 121 | curv 0 0.6399999999999999 29 30 122 | parm u 0 0 0.6399999999999999 0.6399999999999999 123 | end 124 | usemtl diffuse_0_0_0_255 125 | v 3.0000000000000004 0 0 126 | v 3.0000000000000004 0.95999999999999996 0 127 | cstype bspline 128 | deg 1 129 | curv 0 0.95999999999999996 31 32 130 | parm u 0 0 0.95999999999999996 0.95999999999999996 131 | end 132 | usemtl diffuse_0_0_0_255 133 | v 4 0 0 134 | v 5 0 0 135 | cstype bspline 136 | deg 1 137 | curv 4 5 33 34 138 | parm u 4 4 5 5 139 | end 140 | usemtl diffuse_0_0_0_255 141 | v 3.0000000000000004 0 0 142 | v 4 0 0 143 | cstype bspline 144 | deg 1 145 | curv 3.0000000000000004 4 35 36 146 | parm u 3.0000000000000004 3.0000000000000004 4 4 147 | end 148 | usemtl diffuse_0_0_0_255 149 | v 2 0 0 150 | v 3.0000000000000004 0 0 151 | cstype bspline 152 | deg 1 153 | curv 2 3.0000000000000004 37 38 154 | parm u 2 2 3.0000000000000004 3.0000000000000004 155 | end 156 | usemtl diffuse_0_0_0_255 157 | v 1 0 0 158 | v 2 0 0 159 | cstype bspline 160 | deg 1 161 | curv 1 2 39 40 162 | parm u 1 1 2 2 163 | end 164 | usemtl diffuse_0_0_0_255 165 | v 0 0 0 166 | v 1 0 0 167 | cstype bspline 168 | deg 1 169 | curv 0 1 41 42 170 | parm u 0 0 1 1 171 | end 172 | -------------------------------------------------------------------------------- /docs/examples/02_lpopt.py: -------------------------------------------------------------------------------- 1 | from compas_ags.ags import graphstatics 2 | from compas_ags.ags import loadpath 3 | from compas_ags.diagrams import ForceDiagram 4 | from compas_ags.diagrams import FormDiagram 5 | from compas_ags.diagrams import FormGraph 6 | from compas_ags.viewer import AGSViewer 7 | 8 | # ------------------------------------------------------------------------------ 9 | # 1. create a planar truss structure, its applied loads and boundary conditions 10 | # from nodes and edges 11 | # make form and force diagrams 12 | # ------------------------------------------------------------------------------ 13 | 14 | nodes = [ 15 | [0.0, 0.0, 0], 16 | [1.0, 0.0, 0], 17 | [2.0, 0.0, 0], 18 | [3.0, 0.0, 0], 19 | [4.0, 0.0, 0], 20 | [5.0, 0.0, 0], 21 | [6.0, 0.0, 0], 22 | [0.0, -1.0, 0], 23 | [1.0, -1.0, 0], 24 | [2.0, -1.0, 0], 25 | [3.0, -1.0, 0], 26 | [4.0, -1.0, 0], 27 | [5.0, -1.0, 0], 28 | [6.0, -1.0, 0], 29 | [1.0, +1.0, 0], 30 | [2.0, +1.0, 0], 31 | [3.0, +1.0, 0], 32 | [4.0, +1.0, 0], 33 | [5.0, +1.0, 0], 34 | ] 35 | 36 | edges = [ 37 | (0, 1), 38 | (1, 2), 39 | (2, 3), 40 | (3, 4), 41 | (4, 5), 42 | (5, 6), 43 | (0, 7), 44 | (1, 8), 45 | (2, 9), 46 | (3, 10), 47 | (4, 11), 48 | (5, 12), 49 | (6, 13), 50 | (0, 14), 51 | (14, 15), 52 | (15, 16), 53 | (16, 17), 54 | (17, 18), 55 | (18, 6), 56 | (1, 14), 57 | (2, 15), 58 | (3, 16), 59 | (4, 17), 60 | (5, 18), 61 | ] 62 | 63 | graph = FormGraph.from_nodes_and_edges(nodes, edges) 64 | 65 | form = FormDiagram.from_graph(graph) 66 | force = ForceDiagram.from_formdiagram(form) 67 | 68 | # ------------------------------------------------------------------------------ 69 | # 2. assign applied loads to bottom chord 70 | # ------------------------------------------------------------------------------ 71 | 72 | edges = [(8, 1), (9, 2), (10, 3), (11, 4), (12, 5)] 73 | 74 | for edge in edges: 75 | form.edge_attribute(edge, "is_ind", True) 76 | form.edge_attribute(edge, "q", 1.0) 77 | 78 | # update force densities of form and force diagram 79 | graphstatics.form_update_q_from_qind(form) 80 | graphstatics.force_update_from_form(force, form) 81 | 82 | # ------------------------------------------------------------------------------ 83 | # 3. optimize the loadpath 84 | # ------------------------------------------------------------------------------ 85 | 86 | # modify force in the truss by updating vertex coordinates of the force diagram 87 | # force in members of the top chord and bottom chord are set to be the same 88 | # now the form is no longer in equilibrium 89 | force.vertex_attributes(1, "xy", [0, 2.5]) 90 | force.vertex_attributes(2, "xy", [0, 1.5]) 91 | force.vertex_attributes(3, "xy", [0, 0.5]) 92 | force.vertex_attributes(0, "xy", [0, 0]) 93 | force.vertex_attributes(4, "xy", [0, -0.5]) 94 | force.vertex_attributes(5, "xy", [0, -1.5]) 95 | force.vertex_attributes(6, "xy", [0, -2.5]) 96 | 97 | force.vertex_attributes(12, "xy", [-2, 2.5]) 98 | force.vertex_attributes(11, "xy", [-2, 1.5]) 99 | force.vertex_attributes(10, "xy", [-2, 0.5]) 100 | force.vertex_attributes(9, "xy", [-2, -0.5]) 101 | force.vertex_attributes(8, "xy", [-2, -1.5]) 102 | force.vertex_attributes(7, "xy", [-2, -2.5]) 103 | 104 | # forces in members of top chord and connecting struts are force domain parameters 105 | force.vertices_attribute("is_param", True, keys=[7, 8, 9, 10, 11, 12]) 106 | 107 | # fix boundary vertices, the nodes of the bottom chord 108 | form.vertices_attribute("is_fixed", True, keys=[0, 1, 2, 3, 4, 5, 6]) 109 | 110 | # optimize the loadpath and output the optimal distribution of forces that 111 | # results in overall minimum-volumn solution for given form diagram 112 | loadpath.optimise_loadpath(form, force) 113 | 114 | # ------------------------------------------------------------------------------ 115 | # 4. display force and form diagrams 116 | # ------------------------------------------------------------------------------ 117 | 118 | viewer = AGSViewer() 119 | viewer.add_form(form, scale_forces=0.05) 120 | viewer.add_force(force) 121 | viewer.show() 122 | -------------------------------------------------------------------------------- /src/compas_ags/viewer/viewer.py: -------------------------------------------------------------------------------- 1 | from compas_viewer import Viewer 2 | from compas_viewer.config import Config 3 | 4 | from compas.colors import Color 5 | from compas.geometry import Box 6 | from compas.geometry import Circle 7 | from compas.geometry import Polygon 8 | from compas.geometry import bounding_box 9 | from compas_ags.diagrams import ForceDiagram 10 | from compas_ags.diagrams import FormDiagram 11 | 12 | loadcolor = Color.green().darkened(50) 13 | reactioncolor = Color.green().darkened(50) 14 | tensioncolor = Color.red().lightened(25) 15 | compressioncolor = Color.blue().lightened(25) 16 | 17 | 18 | class AGSViewer(Viewer): 19 | def __init__(self, config: Config | None = None, **kwargs): 20 | config = config or Config() 21 | config.renderer.view = "top" 22 | config.camera.target = [0, 0, 0] 23 | config.camera.position = [0, 0, 10] 24 | config.renderer.gridsize = [100, 100, 100, 100] 25 | 26 | super().__init__(config, **kwargs) 27 | 28 | self.renderer.camera.rotation.set(0, 0, 0, False) 29 | 30 | self.form: FormDiagram = None 31 | self.force: ForceDiagram = None 32 | 33 | def add_form(self, form: FormDiagram, show_forces=True, scale_forces=0.1, nodesize=0.1): 34 | self.form = form 35 | 36 | self.scene.add( 37 | self.form, 38 | show_faces=False, 39 | show_lines=not show_forces, 40 | linewidth=2, 41 | name="FormDiagram", 42 | ) 43 | 44 | nodes = [] 45 | for vertex in self.form.vertices(): 46 | circle = Circle.from_point_and_radius( 47 | point=self.form.vertex_point(vertex) + [0, 0, 0.001], 48 | radius=nodesize, 49 | ) 50 | nodes.append(circle.to_polygon(n=128)) 51 | 52 | self.scene.add( 53 | nodes, 54 | name="Nodes", 55 | facecolor=Color.white(), 56 | linecolor=Color.black(), 57 | ) 58 | 59 | if not show_forces: 60 | return 61 | 62 | external = [] 63 | compression = [] 64 | tension = [] 65 | for edge in self.form.edges(): 66 | line = self.form.edge_line(edge) 67 | vector = line.direction.cross([0, 0, 1]) 68 | force = self.form.edge_attribute(edge, name="f") 69 | is_external = self.form.edge_attribute(edge, name="is_external") 70 | 71 | w = scale_forces * 0.5 * abs(force) 72 | a = line.start + vector * -w 73 | b = line.end + vector * -w 74 | c = line.end + vector * +w 75 | d = line.start + vector * +w 76 | 77 | if is_external: 78 | external.append(Polygon([a, b, c, d])) 79 | elif force > 0: 80 | tension.append(Polygon([a, b, c, d])) 81 | elif force < 0: 82 | compression.append(Polygon([a, b, c, d])) 83 | 84 | self.scene.add( 85 | external, 86 | name="External Forces", 87 | facecolor=reactioncolor, 88 | linecolor=reactioncolor.contrast, 89 | ) 90 | self.scene.add( 91 | compression, 92 | name="Compression", 93 | facecolor=compressioncolor, 94 | linecolor=compressioncolor.contrast, 95 | ) 96 | self.scene.add( 97 | tension, 98 | name="Tension", 99 | facecolor=tensioncolor, 100 | linecolor=tensioncolor.contrast, 101 | ) 102 | 103 | def add_force(self, force: ForceDiagram): 104 | self.force = force 105 | 106 | b1 = Box.from_bounding_box(bounding_box(self.form.vertices_attributes("xyz"))) 107 | b2 = Box.from_bounding_box(bounding_box(self.force.vertices_attributes("xyz"))) 108 | 109 | dx = b2.xmin - b1.xmax 110 | if dx < 1: 111 | dx = 1.5 * (b1.xmax - b2.xmin) 112 | else: 113 | dx = 0 114 | 115 | self.scene.add( 116 | force.translated([dx, 0, 0]), 117 | show_faces=False, 118 | linewidth=2, 119 | name="ForceDiagram", 120 | ) 121 | -------------------------------------------------------------------------------- /data/paper/exA_arch-circular.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | mtllib ex0_arch-circular.mtl 4 | usemtl diffuse_0_0_0_255 5 | v 6 1.2603986447377271 0 6 | v 7 0 0 7 | cstype bspline 8 | deg 1 9 | curv 7.0152009539678311 8.6241151044608593 1 2 10 | parm u 7.0152009539678311 7.0152009539678311 8.6241151044608593 8.6241151044608593 11 | end 12 | usemtl diffuse_0_0_0_255 13 | v 5 1.8642080737036446 0 14 | v 6 1.2603986447377271 0 15 | cstype bspline 16 | deg 1 17 | curv 5.8470460727520965 7.0152009539678311 3 4 18 | parm u 5.8470460727520965 5.8470460727520965 7.0152009539678311 7.0152009539678311 19 | end 20 | usemtl diffuse_0_0_0_255 21 | v 4 2.1310436740650065 0 22 | v 5 1.8642080737036446 0 23 | cstype bspline 24 | deg 1 25 | curv 4.8120575522262854 5.8470460727520965 5 6 26 | parm u 4.8120575522262854 4.8120575522262854 5.8470460727520965 5.8470460727520965 27 | end 28 | usemtl diffuse_0_0_0_255 29 | v 3 2.1310436740772691 0 30 | v 4 2.1310436740650065 0 31 | cstype bspline 32 | deg 1 33 | curv 3.8120575522262854 4.8120575522262854 7 8 34 | parm u 3.8120575522262854 3.8120575522262854 4.8120575522262854 4.8120575522262854 35 | end 36 | usemtl diffuse_0_0_0_255 37 | v 1.9999999999999998 1.8642080737002402 0 38 | v 3 2.1310436740772691 0 39 | cstype bspline 40 | deg 1 41 | curv 2.7770690316964344 3.8120575522262854 9 10 42 | parm u 2.7770690316964344 2.7770690316964344 3.8120575522262854 3.8120575522262854 43 | end 44 | usemtl diffuse_0_0_0_255 45 | v 0.99999999999999967 1.2603986446980737 0 46 | v 1.9999999999999998 1.8642080737002402 0 47 | cstype bspline 48 | deg 1 49 | curv 1.6089141504619628 2.7770690316964344 11 12 50 | parm u 1.6089141504619628 1.6089141504619628 2.7770690316964344 2.7770690316964344 51 | end 52 | usemtl diffuse_0_0_0_255 53 | v 0 0 0 54 | v 0.99999999999999967 1.2603986446980737 0 55 | cstype bspline 56 | deg 1 57 | curv 0 1.6089141504619628 13 14 58 | parm u 0 0 1.6089141504619628 1.6089141504619628 59 | end 60 | usemtl diffuse_0_0_0_255 61 | v 8 0 0 62 | v 7 0 0 63 | cstype bspline 64 | deg 1 65 | curv 0 1.0000000000000002 15 16 66 | parm u 0 0 1.0000000000000002 1.0000000000000002 67 | end 68 | usemtl diffuse_0_0_0_255 69 | v -1.0000000000000004 0 0 70 | v 0 0 0 71 | cstype bspline 72 | deg 1 73 | curv 0 1.0000000000000002 17 18 74 | parm u 0 0 1.0000000000000002 1.0000000000000002 75 | end 76 | usemtl diffuse_0_0_0_255 77 | v 0 -1.0000000000000004 0 78 | v 0 0 0 79 | cstype bspline 80 | deg 1 81 | curv 0 1.0000000000000002 19 20 82 | parm u 0 0 1.0000000000000002 1.0000000000000002 83 | end 84 | usemtl diffuse_0_0_0_255 85 | v 7 -1.0000000000000004 0 86 | v 7 0 0 87 | cstype bspline 88 | deg 1 89 | curv 0 1.0000000000000002 21 22 90 | parm u 0 0 1.0000000000000002 1.0000000000000002 91 | end 92 | usemtl diffuse_0_0_0_255 93 | v 6 1.2603986447377271 0 94 | v 6 2.2603986447377276 0 95 | cstype bspline 96 | deg 1 97 | curv 0 1.0000000000000002 23 24 98 | parm u 0 0 1.0000000000000002 1.0000000000000002 99 | end 100 | usemtl diffuse_0_0_0_255 101 | v 5 1.8642080737036446 0 102 | v 5 2.8642080737036446 0 103 | cstype bspline 104 | deg 1 105 | curv 0 1.0000000000000002 25 26 106 | parm u 0 0 1.0000000000000002 1.0000000000000002 107 | end 108 | usemtl diffuse_0_0_0_255 109 | v 4 2.1310436740650065 0 110 | v 4 3.1310436740650065 0 111 | cstype bspline 112 | deg 1 113 | curv 0 1.0000000000000002 27 28 114 | parm u 0 0 1.0000000000000002 1.0000000000000002 115 | end 116 | usemtl diffuse_0_0_0_255 117 | v 3 2.1310436740772691 0 118 | v 3 3.1310436740772696 0 119 | cstype bspline 120 | deg 1 121 | curv 0 1.0000000000000002 29 30 122 | parm u 0 0 1.0000000000000002 1.0000000000000002 123 | end 124 | usemtl diffuse_0_0_0_255 125 | v 1.9999999999999996 1.8642080737002402 0 126 | v 1.9999999999999996 2.8642080737002402 0 127 | cstype bspline 128 | deg 1 129 | curv 0 1.0000000000000002 31 32 130 | parm u 0 0 1.0000000000000002 1.0000000000000002 131 | end 132 | usemtl diffuse_0_0_0_255 133 | v 0.99999999999999967 1.2603986446980737 0 134 | v 0.99999999999999967 2.2603986446980739 0 135 | cstype bspline 136 | deg 1 137 | curv 0 1.0000000000000002 33 34 138 | parm u 0 0 1.0000000000000002 1.0000000000000002 139 | end 140 | -------------------------------------------------------------------------------- /scripts/example_fix_x.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.diagrams import FormGraph 3 | from compas_ags.diagrams import FormDiagram 4 | from compas_ags.diagrams import ForceDiagram 5 | from compas_ags.viewers import Viewer 6 | from compas_ags.ags import form_update_from_force 7 | from compas_ags.ags import form_update_q_from_qind 8 | from compas_ags.ags import force_update_from_form 9 | from compas_ags.ags import ConstraintsCollection 10 | from compas_ags.ags import form_update_from_force_newton 11 | 12 | # ------------------------------------------------------------------------------ 13 | # 1. create a simple arch from nodes and edges, make form and force diagrams 14 | # ------------------------------------------------------------------------------ 15 | 16 | graph = FormGraph.from_json(compas_ags.get('paper/gs_arch.json')) 17 | form = FormDiagram.from_graph(graph) 18 | force = ForceDiagram.from_formdiagram(form) 19 | 20 | # ------------------------------------------------------------------------------ 21 | # 2. prescribe edge force density and set fixed vertices 22 | # ------------------------------------------------------------------------------ 23 | # prescribe force density to edge 24 | edges_ind = [ 25 | (2, 11), 26 | ] 27 | for index in edges_ind: 28 | u, v = index 29 | form.edge_attribute((u, v), 'is_ind', True) 30 | form.edge_attribute((u, v), 'q', -1.) 31 | 32 | # set the fixed corners 33 | left = 2 34 | right = 9 35 | fixed = [left, right] 36 | 37 | for key in fixed: 38 | form.vertex_attribute(key, 'is_fixed', True) 39 | 40 | # set the horizontal fix in internal nodes: 41 | internal = [0, 4, 5, 6, 7, 8] 42 | 43 | for key in internal: 44 | form.vertex_attribute(key, 'is_fixed_x', True) 45 | 46 | # update the diagrams 47 | form_update_q_from_qind(form) 48 | force_update_from_form(force, form) 49 | 50 | # store lines representing the current state of equilibrium 51 | form_lines = [] 52 | for u, v in form.edges(): 53 | form_lines.append({ 54 | 'start': form.vertex_coordinates(u, 'xy'), 55 | 'end': form.vertex_coordinates(v, 'xy'), 56 | 'width': 1.0, 57 | 'color': '#cccccc', 58 | 'style': '--' 59 | }) 60 | 61 | force_lines = [] 62 | for u, v in force.edges(): 63 | force_lines.append({ 64 | 'start': force.vertex_coordinates(u, 'xy'), 65 | 'end': force.vertex_coordinates(v, 'xy'), 66 | 'width': 1.0, 67 | 'color': '#cccccc', 68 | 'style': '--' 69 | }) 70 | 71 | # -------------------------------------------------------------------------- 72 | # 3. force diagram manipulation and modify the form diagram 73 | # -------------------------------------------------------------------------- 74 | 75 | # modify the geometry of the force diagram moving nodes further at right to the left 76 | move_vertices = [0, 9, 8] 77 | translation = +1.0 78 | for key in move_vertices: 79 | x0 = force.vertex_attribute(key, 'x') 80 | force.vertex_attribute(key, 'x', x0 + translation) 81 | 82 | # set constraints automatically with the form diagram's attributes 83 | C = ConstraintsCollection(form) 84 | C.constraints_from_form() 85 | 86 | form_update_from_force_newton(form, force, constraints=C) 87 | 88 | # ------------------------------------------------------------------------------ 89 | # 4. display the orginal configuration 90 | # and the configuration after modifying the force diagram 91 | # ------------------------------------------------------------------------------ 92 | viewer = Viewer(form, force, delay_setup=False) 93 | 94 | viewer.draw_form(lines=form_lines, 95 | forces_on=True, 96 | vertexlabel={key: key for key in form.vertices()}, 97 | external_on=False, 98 | vertexsize=0.2, 99 | vertexcolor={key: '#000000' for key in fixed}, 100 | edgelabel={uv: index for index, uv in enumerate(form.edges())} 101 | ) 102 | 103 | viewer.draw_force(lines=force_lines, 104 | vertexlabel={key: key for key in force.vertices()}, 105 | vertexsize=0.2, 106 | edgelabel={uv: index for index, uv in enumerate(force.edges())} 107 | ) 108 | 109 | viewer.show() 110 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=66.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | # ============================================================================ 6 | # project info 7 | # ============================================================================ 8 | 9 | [project] 10 | name = "compas_ags" 11 | description = "COMPAS package for Computational Graphic Statics" 12 | keywords = ['graphic statics', '2D structures'] 13 | authors = [ 14 | { name = "tom van mele", email = "tom.v.mele@gmail.com" }, 15 | { name = "Vedad Alic", email = "vedad.alic@outlook.com" }, 16 | { name = "Andrew Liew", email = "a.liew@sheffield.ac.uk" }, 17 | ] 18 | license = { file = "LICENSE" } 19 | readme = "README.md" 20 | requires-python = ">=3.9" 21 | dynamic = ['dependencies', 'optional-dependencies', 'version'] 22 | classifiers = [ 23 | "Development Status :: 4 - Beta", 24 | "Topic :: Scientific/Engineering", 25 | "Programming Language :: Python", 26 | "Programming Language :: Python :: 3", 27 | "Programming Language :: Python :: 3.9", 28 | "Programming Language :: Python :: 3.10", 29 | "Programming Language :: Python :: 3.11", 30 | ] 31 | 32 | [project.urls] 33 | Homepage = "https://compas-dev.github.io/compas_ags" 34 | Documentation = "https://compas-dev.github.io/compas_ags" 35 | Repository = "https://github.com/compas-dev/compas_ags.git" 36 | Changelog = "https://github.com/compas-dev/compas_ags/blob/main/CHANGELOG.md" 37 | 38 | # ============================================================================ 39 | # setuptools config 40 | # ============================================================================ 41 | 42 | [tool.setuptools] 43 | package-dir = { "" = "src" } 44 | include-package-data = true 45 | zip-safe = false 46 | 47 | [tool.setuptools.dynamic] 48 | version = { attr = "compas_ags.__version__" } 49 | dependencies = { file = "requirements.txt" } 50 | optional-dependencies = { dev = { file = "requirements-dev.txt" } } 51 | 52 | [tool.setuptools.packages.find] 53 | where = ["src"] 54 | 55 | [tool.setuptools.package-data] 56 | compas_ags = ['config.json'] 57 | "compas_ags.icons" = ["*.svg", "*.png"] 58 | "compas_ags.components.renderer.shaders" = ["*.vert", "*.frag"] 59 | "compas_ags.configurations.default_config" = ["*.json", "*.ttf"] 60 | 61 | # ============================================================================ 62 | # replace pytest.ini 63 | # ============================================================================ 64 | 65 | [tool.pytest.ini_options] 66 | minversion = "6.0" 67 | testpaths = ["tests", "src/compas_ags"] 68 | python_files = ["test_*.py", "*_test.py", "test.py"] 69 | addopts = ["-ra", "--strict-markers", "--doctest-glob=*.rst", "--tb=short"] 70 | doctest_optionflags = [ 71 | "NORMALIZE_WHITESPACE", 72 | "IGNORE_EXCEPTION_DETAIL", 73 | "ALLOW_UNICODE", 74 | "ALLOW_BYTES", 75 | "NUMBER", 76 | ] 77 | 78 | # ============================================================================ 79 | # replace bumpversion.cfg 80 | # ============================================================================ 81 | 82 | [tool.bumpversion] 83 | current_version = "1.3.3" 84 | message = "Bump version to {new_version}" 85 | commit = true 86 | tag = true 87 | 88 | [[tool.bumpversion.files]] 89 | filename = "src/compas_ags/__init__.py" 90 | search = "{current_version}" 91 | replace = "{new_version}" 92 | 93 | [[tool.bumpversion.files]] 94 | filename = "CHANGELOG.md" 95 | search = "Unreleased" 96 | replace = "[{new_version}] {now:%Y-%m-%d}" 97 | 98 | # ============================================================================ 99 | # replace setup.cfg 100 | # ============================================================================ 101 | 102 | [tool.black] 103 | line-length = 179 104 | 105 | [tool.ruff] 106 | line-length = 179 107 | indent-width = 4 108 | target-version = "py39" 109 | 110 | [tool.ruff.lint] 111 | select = ["E", "F", "I"] 112 | 113 | [tool.ruff.lint.per-file-ignores] 114 | "__init__.py" = ["I001"] 115 | "tests/*" = ["I001"] 116 | "tasks.py" = ["I001"] 117 | 118 | [tool.ruff.lint.isort] 119 | force-single-line = true 120 | known-first-party = ["compas", "compas_ags"] 121 | 122 | [tool.ruff.lint.pydocstyle] 123 | convention = "numpy" 124 | 125 | [tool.ruff.lint.pycodestyle] 126 | max-doc-length = 179 127 | 128 | [tool.ruff.format] 129 | docstring-code-format = true 130 | docstring-code-line-length = "dynamic" 131 | -------------------------------------------------------------------------------- /data/paper/discretised_spline.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 18.48065810630669 1.880794105389116 0 4 | v 19.47984874610206 4.716420267859766 0 5 | cstype bspline 6 | deg 1 7 | curv 0 3.006519194673919 1 2 8 | parm u 0 0 3.006519194673919 3.006519194673919 9 | end 10 | v 16.74125276357046 3.555868502891093 0 11 | v 18.20244511749519 6.485132984392367 0 12 | cstype bspline 13 | deg 1 14 | curv 0 3.273480334102073 3 4 15 | parm u 0 0 3.273480334102073 3.273480334102073 16 | end 17 | v 14.53650275847931 4.42330159522281 0 18 | v 14.8124124108077 6.878180254732945 0 19 | cstype bspline 20 | deg 1 21 | curv 0 2.470335072245515 5 6 22 | parm u 0 0 2.470335072245515 2.470335072245515 23 | end 24 | v 12.21819770047614 3.770453806293508 0 25 | v 11.42237970412022 6.927311163525516 0 26 | cstype bspline 27 | deg 1 28 | curv 0 3.255622007732749 7 8 29 | parm u 0 0 3.255622007732749 3.255622007732749 30 | end 31 | v 9.821261639420873 3.849958565427682 0 32 | v 10.02214880353191 6.018389350862931 0 33 | cstype bspline 34 | deg 1 35 | curv 0 2.177716171572389 9 10 36 | parm u 0 0 2.177716171572389 2.177716171572389 37 | end 38 | v 7.466509386530103 4.400589440997196 0 39 | v 7.29538336554415 6.583394801977511 0 40 | cstype bspline 41 | deg 1 42 | curv 0 2.189502993599892 11 12 43 | parm u 0 0 2.189502993599892 2.189502993599892 44 | end 45 | v 5.085646278019927 4.787850302060676 0 46 | v 5.674063375389265 7.737971158602958 0 47 | cstype bspline 48 | deg 1 49 | curv 0 3.008230002623192 13 14 50 | parm u 0 0 3.008230002623192 3.008230002623192 51 | end 52 | v 2.917679004320495 3.83521276240793 0 53 | v 2.578816121457216 6.092085714051787 0 54 | cstype bspline 55 | deg 1 56 | curv 0 2.282170802819997 15 16 57 | parm u 0 0 2.282170802819997 2.282170802819997 58 | end 59 | v 1.323382559231487 2.023323937336372 0 60 | v 0.1714015906211763 4.421634815104333 0 61 | cstype bspline 62 | deg 1 63 | curv 0 2.660630605413024 17 18 64 | parm u 0 0 2.660630605413024 2.660630605413024 65 | end 66 | v 18.48065810630669 1.880794105389116 0 67 | v 20 0 0 68 | cstype bspline 69 | deg 1 70 | curv 21.62040409101909 24.03821017439261 19 20 71 | parm u 21.62040409101909 21.62040409101909 24.03821017439261 24.03821017439261 72 | end 73 | v 16.74125276357046 3.555868502891093 0 74 | v 18.48065810630669 1.880794105389116 0 75 | cstype bspline 76 | deg 1 77 | curv 19.20557383124803 21.62040409101909 21 22 78 | parm u 19.20557383124803 19.20557383124803 21.62040409101909 21.62040409101909 79 | end 80 | v 14.53650275847931 4.42330159522281 0 81 | v 16.74125276357046 3.555868502891093 0 82 | cstype bspline 83 | deg 1 84 | curv 16.83632020307278 19.20557383124803 23 24 85 | parm u 16.83632020307278 16.83632020307278 19.20557383124803 19.20557383124803 86 | end 87 | v 12.21819770047614 3.770453806293508 0 88 | v 14.53650275847931 4.42330159522281 0 89 | cstype bspline 90 | deg 1 91 | curv 14.42784587739004 16.83632020307278 25 26 92 | parm u 14.42784587739004 14.42784587739004 16.83632020307278 16.83632020307278 93 | end 94 | v 9.821261639420873 3.849958565427682 0 95 | v 12.21819770047614 3.770453806293508 0 96 | cstype bspline 97 | deg 1 98 | curv 12.02959161907794 14.42784587739004 27 28 99 | parm u 12.02959161907794 12.02959161907794 14.42784587739004 14.42784587739004 100 | end 101 | v 7.466509386530103 4.400589440997196 0 102 | v 9.821261639420873 3.849958565427682 0 103 | cstype bspline 104 | deg 1 105 | curv 9.611316917224441 12.02959161907794 29 30 106 | parm u 9.611316917224441 9.611316917224441 12.02959161907794 12.02959161907794 107 | end 108 | v 5.085646278019927 4.787850302060676 0 109 | v 7.466509386530103 4.400589440997196 0 110 | cstype bspline 111 | deg 1 112 | curv 7.199164327528596 9.611316917224441 31 32 113 | parm u 7.199164327528596 7.199164327528596 9.611316917224441 9.611316917224441 114 | end 115 | v 2.917679004320495 3.83521276240793 0 116 | v 5.085646278019927 4.787850302060676 0 117 | cstype bspline 118 | deg 1 119 | curv 4.831127085045408 7.199164327528596 33 34 120 | parm u 4.831127085045408 4.831127085045408 7.199164327528596 7.199164327528596 121 | end 122 | v 1.323382559231487 2.023323937336372 0 123 | v 2.917679004320495 3.83521276240793 0 124 | cstype bspline 125 | deg 1 126 | curv 2.417680945343375 4.831127085045408 35 36 127 | parm u 2.417680945343375 2.417680945343375 4.831127085045408 4.831127085045408 128 | end 129 | v 0 0 0 130 | v 1.323382559231487 2.023323937336372 0 131 | cstype bspline 132 | deg 1 133 | curv 0 2.417680945343375 37 38 134 | parm u 0 0 2.417680945343375 2.417680945343375 135 | end 136 | -------------------------------------------------------------------------------- /scripts/example_detect_lineloads.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.diagrams import FormGraph 3 | from compas_ags.diagrams import FormDiagram 4 | from compas_ags.diagrams import ForceDiagram 5 | from compas_ags.viewers import Viewer 6 | from compas_ags.ags import form_update_from_force 7 | from compas_ags.ags import form_update_q_from_qind 8 | from compas_ags.ags import force_update_from_form 9 | from compas_ags.ags import ConstraintsCollection 10 | from compas_ags.ags import form_update_from_force 11 | 12 | # ------------------------------------------------------------------------------ 13 | # 1. create a simple arch from nodes and edges, make form and force diagrams 14 | # ------------------------------------------------------------------------------ 15 | 16 | graph = FormGraph.from_json(compas_ags.get("paper/gs_arch.json")) 17 | form = FormDiagram.from_graph(graph) 18 | force = ForceDiagram.from_formdiagram(form) 19 | 20 | # ------------------------------------------------------------------------------ 21 | # 2. prescribe edge force density and set fixed vertices 22 | # ------------------------------------------------------------------------------ 23 | # prescribe force density to edge 24 | edges_ind = [ 25 | (2, 11), 26 | ] 27 | for index in edges_ind: 28 | u, v = index 29 | form.edge_attribute((u, v), "is_ind", True) 30 | form.edge_attribute((u, v), "q", -1.0) 31 | 32 | # set the fixed corners 33 | left = 2 34 | right = 9 35 | fixed = [left, right] 36 | 37 | for key in fixed: 38 | form.vertex_attribute(key, "is_fixed", True) 39 | 40 | # update the diagrams 41 | form_update_q_from_qind(form) 42 | force_update_from_form(force, form) 43 | 44 | # store lines representing the current state of equilibrium 45 | form_lines = [] 46 | for u, v in form.edges(): 47 | form_lines.append({"start": form.vertex_coordinates(u, "xy"), "end": form.vertex_coordinates(v, "xy"), "width": 1.0, "color": "#cccccc", "style": "--"}) 48 | 49 | force_lines = [] 50 | for u, v in force.edges(): 51 | force_lines.append({"start": force.vertex_coordinates(u, "xy"), "end": force.vertex_coordinates(v, "xy"), "width": 1.0, "color": "#cccccc", "style": "--"}) 52 | 53 | # Detect the leaves of form diagram: 54 | 55 | form.identify_constraints() 56 | 57 | # Next 58 | # Reflect the leaves cosntraints in the force diagram: 59 | 60 | # edge_index = form.edge_index() 61 | # # index = edge_index[edge_form] 62 | # edges_force = list(force.ordered_edges(form)) 63 | # vertex_leaves= form.leaves() 64 | 65 | # for edge in form.leaf_edges(): 66 | # index = edge_index[edge] 67 | # dual = edges_force[index] 68 | # sp, ep = form.edge_coordinates(edge) 69 | # print('INDEX -->', index) 70 | # print('original edge -->', edge) 71 | # print('dual edge -->', dual) 72 | 73 | force_edge_labels1 = {(u, v): index for index, (u, v) in enumerate(force.ordered_edges(form))} 74 | force_edge_labels2 = {(v, u): index for index, (u, v) in enumerate(force.ordered_edges(form))} 75 | force_edge_labels = {**force_edge_labels1, **force_edge_labels2} 76 | 77 | # -------------------------------------------------------------------------- 78 | # 3. force diagram manipulation and modify the form diagram 79 | # -------------------------------------------------------------------------- 80 | 81 | # modify the geometry of the force diagram moving nodes further at right to the left 82 | move_vertices = [0, 9, 8] 83 | translation = +1.0 84 | for key in move_vertices: 85 | x0 = force.vertex_attribute(key, "x") 86 | force.vertex_attribute(key, "x", x0 + translation) 87 | 88 | form_update_from_force(form, force) 89 | 90 | # ------------------------------------------------------------------------------ 91 | # 4. display the orginal configuration 92 | # and the configuration after modifying the force diagram 93 | # ------------------------------------------------------------------------------ 94 | viewer = Viewer(form, force, delay_setup=False) 95 | 96 | viewer.draw_form( 97 | lines=form_lines, 98 | forces_on=True, 99 | vertexlabel={key: key for key in form.vertices()}, 100 | external_on=False, 101 | vertexsize=0.2, 102 | vertexcolor={**{key: "#000000" for key in form.fixed()}, **{key: "#FF0000" for key in form.fixed_x()}}, 103 | edgelabel={uv: index for index, uv in enumerate(form.edges())}, 104 | ) 105 | 106 | viewer.draw_force(lines=force_lines, vertexlabel={key: key for key in force.vertices()}, vertexsize=0.2, edgelabel=force_edge_labels) 107 | 108 | viewer.show() 109 | -------------------------------------------------------------------------------- /scripts/example_truss.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.diagrams import FormGraph 3 | from compas_ags.diagrams import FormDiagram 4 | from compas_ags.diagrams import ForceDiagram 5 | from compas_ags.viewers import Viewer 6 | from compas_ags.ags import form_update_from_force 7 | from compas_ags.ags import form_update_q_from_qind 8 | from compas_ags.ags import force_update_from_form 9 | from compas_ags.ags import ConstraintsCollection 10 | from compas_ags.ags import form_update_from_force_newton 11 | from compas_ags.ags import form_update_from_force 12 | 13 | # ------------------------------------------------------------------------------ 14 | # 1. create a simple arch from nodes and edges, make form and force diagrams 15 | # ------------------------------------------------------------------------------ 16 | 17 | graph = FormGraph.from_obj(compas_ags.get('paper/gs_truss.obj')) 18 | form = FormDiagram.from_graph(graph) 19 | force = ForceDiagram.from_formdiagram(form) 20 | 21 | # ------------------------------------------------------------------------------ 22 | # 2. prescribe edge force density and set fixed vertices 23 | # ------------------------------------------------------------------------------ 24 | # prescribe force density to edge 25 | edges_ind = [ 26 | (6, 14), 27 | ] 28 | for index in edges_ind: 29 | u, v = index 30 | form.edge_attribute((u, v), 'is_ind', True) 31 | form.edge_attribute((u, v), 'q', +1.) 32 | 33 | # set the fixed corners 34 | left = 5 35 | right = 1 36 | fixed = [left, right] 37 | 38 | for key in fixed: 39 | form.vertex_attribute(key, 'is_fixed', True) 40 | 41 | # set the horizontal fix in internal nodes: 42 | internal = [0, 2, 3, 4] 43 | 44 | for key in internal: 45 | form.vertex_attribute(key, 'is_fixed_x', True) 46 | 47 | # update the diagrams 48 | form_update_q_from_qind(form) 49 | force_update_from_form(force, form) 50 | 51 | # store lines representing the current state of equilibrium 52 | form_lines = [] 53 | for u, v in form.edges(): 54 | form_lines.append({ 55 | 'start': form.vertex_coordinates(u, 'xy'), 56 | 'end': form.vertex_coordinates(v, 'xy'), 57 | 'width': 1.0, 58 | 'color': '#cccccc', 59 | 'style': '--' 60 | }) 61 | 62 | force_lines = [] 63 | for u, v in force.edges(): 64 | force_lines.append({ 65 | 'start': force.vertex_coordinates(u, 'xy'), 66 | 'end': force.vertex_coordinates(v, 'xy'), 67 | 'width': 1.0, 68 | 'color': '#cccccc', 69 | 'style': '--' 70 | }) 71 | 72 | viewer = Viewer(form, force, delay_setup=False) 73 | viewer.draw_form(vertexlabel={key: key for key in form.vertices()}) 74 | viewer.draw_force(vertexlabel={key: key for key in force.vertices()}) 75 | viewer.show() 76 | 77 | # -------------------------------------------------------------------------- 78 | # 3. force diagram manipulation and modify the form diagram 79 | # -------------------------------------------------------------------------- 80 | 81 | # modify the geometry of the force diagram moving nodes further at right to the left 82 | move_vertices = [6, 7, 8, 9, 10] 83 | translation = +10.0 84 | for key in move_vertices: 85 | x0 = force.vertex_attribute(key, 'x') 86 | force.vertex_attribute(key, 'x', x0 + translation) 87 | 88 | # set constraints automatically with the form diagram's attributes 89 | C = ConstraintsCollection(form) 90 | C.constraints_from_form() 91 | 92 | # form_update_from_force_newton(form, force, constraints=C) 93 | form_update_from_force(form, force) 94 | 95 | # ------------------------------------------------------------------------------ 96 | # 4. display the orginal configuration 97 | # and the configuration after modifying the force diagram 98 | # ------------------------------------------------------------------------------ 99 | viewer = Viewer(form, force, delay_setup=False) 100 | 101 | viewer.draw_form(lines=form_lines, 102 | forces_on=True, 103 | vertexlabel={key: key for key in form.vertices()}, 104 | external_on=False, 105 | vertexsize=0.2, 106 | vertexcolor={key: '#000000' for key in fixed}, 107 | edgelabel={uv: index for index, uv in enumerate(form.edges())} 108 | ) 109 | 110 | viewer.draw_force(lines=force_lines, 111 | vertexlabel={key: key for key in force.vertices()}, 112 | vertexsize=0.2, 113 | edgelabel={uv: index for index, uv in enumerate(force.edges())} 114 | ) 115 | 116 | viewer.show() 117 | -------------------------------------------------------------------------------- /scripts/paper-CSD/exampleB_dragging.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.diagrams import FormDiagram 3 | from compas_ags.diagrams import ForceDiagram 4 | from compas_ags.ags import form_update_q_from_qind 5 | from compas_ags.ags import force_update_from_form 6 | from compas_ags.ags import form_update_from_force 7 | from compas_ags.viewers import Viewer 8 | 9 | 10 | def view_form_force(form, force, forcescale=0.5, edge_label=True): 11 | if edge_label: 12 | form_edge_label = {uv: index for index, uv in enumerate(form.edges())} 13 | force_edge_label = force_edge_labels 14 | else: 15 | form_edge_label = None 16 | force_edge_label = None 17 | 18 | viewer = Viewer(form, force, delay_setup=False) 19 | viewer.draw_form(edgelabel=form_edge_label, 20 | forces_on=True, 21 | forcescale=forcescale, 22 | vertexcolor={key: '#000000' for key in form.vertices_where({'is_fixed': True})}) 23 | viewer.draw_force(edgelabel=force_edge_label) 24 | viewer.show() 25 | 26 | 27 | def view_with_initial_stage(form, force, forcescale=0.5, edge_label=True): 28 | if edge_label: 29 | form_edge_label = {uv: index for index, uv in enumerate(form.edges())} 30 | force_edge_label = force_edge_labels 31 | else: 32 | form_edge_label = None 33 | force_edge_label = None 34 | 35 | viewer = Viewer(form, force, delay_setup=False) 36 | viewer.draw_form(lines=form_lines, 37 | forces_on=True, 38 | external_on=True, 39 | forcescale=forcescale, 40 | edgelabel=form_edge_label, 41 | vertexcolor={key: '#000000' for key in form.vertices_where({'is_fixed': True})}) 42 | viewer.draw_force(lines=force_lines, 43 | edgelabel=force_edge_label 44 | ) 45 | viewer.show() 46 | 47 | 48 | def store_initial_lines(form, force): 49 | 50 | form_lines = [] 51 | for u, v in form.edges(): 52 | form_lines.append({ 53 | 'start': form.vertex_coordinates(u, 'xy'), 54 | 'end': form.vertex_coordinates(v, 'xy'), 55 | 'width': 1.0, 56 | 'color': '#cccccc', 57 | 'style': '--' 58 | }) 59 | 60 | force_lines = [] 61 | for u, v in force.edges(): 62 | force_lines.append({ 63 | 'start': force.vertex_coordinates(u, 'xy'), 64 | 'end': force.vertex_coordinates(v, 'xy'), 65 | 'width': 1.0, 66 | 'color': '#cccccc', 67 | 'style': '--' 68 | }) 69 | 70 | return form_lines, force_lines 71 | 72 | 73 | # ------------------------------------------------------------------------------ 74 | # 2. Dragging the force diagram and updating form diagram 75 | # - Find a deeper form diagram 76 | # - Invert compression/tension 77 | # ------------------------------------------------------------------------------ 78 | 79 | input_file = compas_ags.get('paper/exB_arch-output.json') 80 | 81 | form = FormDiagram.from_json(input_file) 82 | force = ForceDiagram.from_formdiagram(form) 83 | 84 | # create label for plots 85 | force_edges = force.ordered_edges(form) 86 | force_edge_labels = {(u, v): index for index, (u, v) in enumerate(force_edges)} 87 | force_edge_labels.update({(v, u): index for index, (u, v) in enumerate(force_edges)}) 88 | 89 | # update the diagrams 90 | form_update_q_from_qind(form) 91 | force_update_from_form(force, form) 92 | 93 | # visualise initial solution 94 | view_form_force(form, force, forcescale=2.0) 95 | 96 | # Identify auto constraints 97 | form.identify_constraints() 98 | 99 | form_lines, force_lines = store_initial_lines(form, force) 100 | 101 | # --------------------------------------------- 102 | # Move to change sag (deeper form diagram) 103 | 104 | move_vertices = [0, 9, 8] 105 | translation = +1.81 106 | for key in move_vertices: 107 | x0 = force.vertex_attribute(key, 'x') 108 | force.vertex_attribute(key, 'x', x0 + translation) 109 | 110 | form_update_from_force(form, force) 111 | 112 | view_with_initial_stage(form, force, forcescale=2.0, edge_label=False) 113 | 114 | # --------------------------------------------- 115 | # Move to invert compression to tension 116 | 117 | move_vertices = [0, 9, 8] 118 | translation = +4.00 119 | for key in move_vertices: 120 | x0 = force.vertex_attribute(key, 'x') 121 | force.vertex_attribute(key, 'x', x0 + translation) 122 | 123 | form_update_from_force(form, force) 124 | 125 | view_with_initial_stage(form, force, forcescale=2.0, edge_label=False) 126 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # -*- coding: utf-8 -*- 3 | 4 | from sphinx.writers import html, html5 5 | import sphinx_compas2_theme 6 | 7 | # -- General configuration ------------------------------------------------ 8 | 9 | project = "COMPAS AGS" 10 | copyright = "Block Research Group - ETH Zurich" 11 | package = "compas_ags" 12 | organization = "blockresearchgroup" 13 | 14 | master_doc = "index" 15 | source_suffix = {".rst": "restructuredtext", ".md": "markdown"} 16 | templates_path = sphinx_compas2_theme.get_autosummary_templates_path() 17 | exclude_patterns = sphinx_compas2_theme.default_exclude_patterns 18 | add_module_names = True 19 | language = "en" 20 | 21 | latest_version = sphinx_compas2_theme.get_latest_version() 22 | 23 | if latest_version == "Unreleased": 24 | release = "Unreleased" 25 | version = "latest" 26 | else: 27 | release = latest_version 28 | version = ".".join(release.split(".")[0:2]) # type: ignore 29 | 30 | # -- Extension configuration ------------------------------------------------ 31 | 32 | extensions = sphinx_compas2_theme.default_extensions 33 | extensions.remove("sphinx.ext.linkcode") 34 | 35 | # numpydoc options 36 | 37 | numpydoc_show_class_members = False 38 | numpydoc_class_members_toctree = False 39 | numpydoc_attributes_as_param_list = True 40 | 41 | # bibtex options 42 | 43 | # autodoc options 44 | 45 | autodoc_type_aliases = {} 46 | 47 | autodoc_typehints_description_target = "documented" 48 | autodoc_mock_imports = sphinx_compas2_theme.default_mock_imports 49 | autodoc_default_options = { 50 | "undoc-members": True, 51 | "show-inheritance": True, 52 | } 53 | autodoc_member_order = "groupwise" 54 | autodoc_typehints = "description" 55 | autodoc_class_signature = "separated" 56 | 57 | autoclass_content = "class" 58 | 59 | 60 | def setup(app): 61 | app.connect("autodoc-skip-member", sphinx_compas2_theme.skip) 62 | 63 | 64 | # autosummary options 65 | 66 | autosummary_generate = True 67 | autosummary_mock_imports = sphinx_compas2_theme.default_mock_imports 68 | 69 | # graph options 70 | 71 | # plot options 72 | 73 | # intersphinx options 74 | 75 | intersphinx_mapping = { 76 | "python": ("https://docs.python.org/", None), 77 | "compas": ("https://compas.dev/compas/latest/", None), 78 | } 79 | 80 | # linkcode 81 | 82 | # linkcode_resolve = sphinx_compas2_theme.get_linkcode_resolve(organization, package) 83 | 84 | # extlinks 85 | 86 | extlinks = { 87 | "rhino": ("https://developer.rhino3d.com/api/RhinoCommon/html/T_%s.htm", "%s"), 88 | } 89 | 90 | # from pytorch 91 | 92 | sphinx_compas2_theme.replace(html.HTMLTranslator) 93 | sphinx_compas2_theme.replace(html5.HTML5Translator) 94 | 95 | # -- Options for HTML output ---------------------------------------------- 96 | 97 | html_theme = "sidebaronly" 98 | html_title = project 99 | 100 | favicons = [ 101 | { 102 | "rel": "icon", 103 | "href": "compas.ico", 104 | } 105 | ] 106 | 107 | html_theme_options = { 108 | "icon_links": [ 109 | { 110 | "name": "GitHub", 111 | "url": f"https://github.com/{organization}/{package}", 112 | "icon": "fa-brands fa-github", 113 | "type": "fontawesome", 114 | }, 115 | { 116 | "name": "Discourse", 117 | "url": "http://forum.compas-framework.org/", 118 | "icon": "fa-brands fa-discourse", 119 | "type": "fontawesome", 120 | }, 121 | { 122 | "name": "PyPI", 123 | "url": f"https://pypi.org/project/{package}/", 124 | "icon": "fa-brands fa-python", 125 | "type": "fontawesome", 126 | }, 127 | ], 128 | "switcher": { 129 | "json_url": f"https://raw.githubusercontent.com/{organization}/{package}/gh-pages/versions.json", 130 | "version_match": version, 131 | }, 132 | "check_switcher": False, 133 | "logo": { 134 | "image_light": "_static/compas_icon_white.png", 135 | "image_dark": "_static/compas_icon_white.png", 136 | "text": project, 137 | }, 138 | "navigation_depth": 3, 139 | } 140 | 141 | 142 | html_context = { 143 | "github_url": "https://github.com", 144 | "github_user": organization, 145 | "github_repo": package, 146 | "github_version": "main", 147 | "doc_path": "docs", 148 | } 149 | 150 | html_static_path = sphinx_compas2_theme.get_html_static_path() 151 | html_css_files = [] 152 | html_extra_path = [] 153 | html_last_updated_fmt = "" 154 | html_copy_source = False 155 | html_show_sourcelink = True 156 | html_permalinks = False 157 | html_permalinks_icon = "" 158 | html_compact_lists = True 159 | -------------------------------------------------------------------------------- /scripts/example_interactive.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.diagrams import FormGraph 3 | from compas_ags.diagrams import FormDiagram 4 | from compas_ags.diagrams import ForceDiagram 5 | from compas_ags.viewers import Viewer 6 | from compas_ags.ags import form_update_q_from_qind 7 | from compas_ags.ags import force_update_from_form 8 | from compas_ags.ags import form_update_from_force_newton 9 | from compas_ags.ags import ConstraintsCollection 10 | 11 | 12 | # ------------------------------------------------------------------------------ 13 | # 1. lines from isostatic structure from paper AGS 14 | # ------------------------------------------------------------------------------ 15 | graph = FormGraph.from_obj(compas_ags.get('paper/gs_form_force.obj')) 16 | form = FormDiagram.from_graph(graph) 17 | force = ForceDiagram.from_formdiagram(form) 18 | 19 | # ------------------------------------------------------------------------------ 20 | # 2. set independent edge and assign load 21 | # ------------------------------------------------------------------------------ 22 | u_edge = 0 23 | v_edge = 1 24 | f = -5.0 25 | l = form.edge_length(u_edge, v_edge) 26 | form.edge_attribute((u_edge, v_edge), 'q', f/l) 27 | form.edge_attribute((u_edge, v_edge), 'is_ind', True) 28 | 29 | # ------------------------------------------------------------------------------ 30 | # 3. set the fixed vertices 31 | # ------------------------------------------------------------------------------ 32 | left = 5 33 | right = 3 34 | form.vertex_attribute(left, 'is_fixed', True) 35 | form.vertex_attribute(right, 'is_fixed', True) 36 | fixed = [left, right] 37 | 38 | # ------------------------------------------------------------------------------ 39 | # 4. compute equilibrium 40 | # ------------------------------------------------------------------------------ 41 | 42 | # update the diagrams 43 | form_update_q_from_qind(form) 44 | force_update_from_form(force, form) 45 | 46 | # store the original vertex locations 47 | force_key_xyz = {key: force.vertex_coordinates(key) for key in force.vertices()} 48 | 49 | # store lines representing the current state of equilibrium 50 | form_lines = [] 51 | for u, v in form.edges(): 52 | form_lines.append({ 53 | 'start': form.vertex_coordinates(u, 'xy'), 54 | 'end': form.vertex_coordinates(v, 'xy'), 55 | 'width': 1.0, 56 | 'color': '#cccccc', 57 | 'style': '--' 58 | }) 59 | 60 | force_lines = [] 61 | for u, v in force.edges(): 62 | force_lines.append({ 63 | 'start': force.vertex_coordinates(u, 'xy'), 64 | 'end': force.vertex_coordinates(v, 'xy'), 65 | 'width': 1.0, 66 | 'color': '#cccccc', 67 | 'style': '--' 68 | }) 69 | 70 | 71 | # -------------------------------------------------------------------------- 72 | # 5. force diagram manipulation and modify the form diagram 73 | # -------------------------------------------------------------------------- 74 | # set constraints according to fixity predefined 75 | C = ConstraintsCollection(form) 76 | 77 | C.constraints_from_form() # alternativelly this could be break to assign each Horiz/Vert fixity 78 | 79 | constraint_lines = C.get_lines() 80 | 81 | # modify the geometry of the force diagram and update the form diagram using Newton's method 82 | translation = 0.5 83 | force.vertex[4]['x'] -= translation 84 | 85 | form_update_from_force_newton(form, force, constraints=C) 86 | 87 | # add arrow to lines to indicate movement 88 | force_lines.append({ 89 | 'start': force_key_xyz[4], 90 | 'end': force.vertex_coordinates(4), 91 | 'color': '#ff0000', 92 | 'width': 10.0, 93 | 'style': '-', 94 | }) 95 | 96 | form_lines = form_lines + constraint_lines 97 | 98 | # ------------------------------------------------------------------------------ 99 | # 6. display the orginal configuration 100 | # and the configuration after modifying the force diagram 101 | # ------------------------------------------------------------------------------ 102 | viewer = Viewer(form, force, delay_setup=False) 103 | 104 | viewer.draw_form(lines=form_lines, 105 | forces_on=False, 106 | vertexlabel={key: key for key in form.vertices()}, 107 | external_on=False, 108 | vertexsize=0.2, 109 | vertexcolor={key: '#000000' for key in fixed}, 110 | edgelabel={uv: index for index, uv in enumerate(form.edges())} 111 | ) 112 | 113 | viewer.draw_force(lines=force_lines, 114 | vertexlabel={key: key for key in force.vertices()}, 115 | vertexsize=0.2, 116 | edgelabel={uv: index for index, uv in enumerate(force.edges())} 117 | ) 118 | 119 | viewer.show() 120 | -------------------------------------------------------------------------------- /data/paper/discretised_arc.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 0 0 0 4 | v 0 -1 0 5 | cstype bspline 6 | deg 1 7 | curv 1 2 1 2 8 | parm u 1 1 2 2 9 | end 10 | v -1 0 0 11 | v 0 0 0 12 | cstype bspline 13 | deg 1 14 | curv 0 1 3 4 15 | parm u 0 0 1 1 16 | end 17 | v 15 0 0 18 | v 16 0 0 19 | cstype bspline 20 | deg 1 21 | curv 1 2 5 6 22 | parm u 1 1 2 2 23 | end 24 | v 15 -1 0 25 | v 15 0 0 26 | cstype bspline 27 | deg 1 28 | curv 0 1 7 8 29 | parm u 0 0 1 1 30 | end 31 | v 14.06837749481201 1.666174292564392 0 32 | v 14.06837749481201 2.994326922178447 0 33 | cstype bspline 34 | deg 1 35 | curv 0 1.944800491252584 9 10 36 | parm u 0 0 1.944800491252584 1.944800491252584 37 | end 38 | v 12.77332401275635 3.068634510040283 0 39 | v 12.77332401275635 4.396787139654338 0 40 | cstype bspline 41 | deg 1 42 | curv 0 1.944800491252584 11 12 43 | parm u 0 0 1.944800491252584 1.944800491252584 44 | end 45 | v 11.18649578094482 4.129781723022461 0 46 | v 11.18649578094482 5.457934352636515 0 47 | cstype bspline 48 | deg 1 49 | curv 0 1.944800491252584 13 14 50 | parm u 0 0 1.944800491252584 1.944800491252584 51 | end 52 | v 9.395692825317383 4.790903091430664 0 53 | v 9.395692825317383 6.119055721044719 0 54 | cstype bspline 55 | deg 1 56 | curv 0 1.944800491252584 15 16 57 | parm u 0 0 1.944800491252584 1.944800491252584 58 | end 59 | v 7.5 5.015417575836182 0 60 | v 7.5 6.343570205450236 0 61 | cstype bspline 62 | deg 1 63 | curv 0 1.944800491252584 17 18 64 | parm u 0 0 1.944800491252584 1.944800491252584 65 | end 66 | v 5.604307174682617 4.790903091430664 0 67 | v 5.604307174682617 6.119055721044719 0 68 | cstype bspline 69 | deg 1 70 | curv 0 1.944800491252584 19 20 71 | parm u 0 0 1.944800491252584 1.944800491252584 72 | end 73 | v 3.813503980636597 4.129781723022461 0 74 | v 3.813503980636597 5.457934352636515 0 75 | cstype bspline 76 | deg 1 77 | curv 0 1.944800491252584 21 22 78 | parm u 0 0 1.944800491252584 1.944800491252584 79 | end 80 | v 2.226675748825073 3.068634510040283 0 81 | v 2.226675748825073 4.396787139654338 0 82 | cstype bspline 83 | deg 1 84 | curv 0 1.944800491252584 23 24 85 | parm u 0 0 1.944800491252584 1.944800491252584 86 | end 87 | v 0.9316225647926331 1.666174292564392 0 88 | v 0.9316225647926331 2.994326922178447 0 89 | cstype bspline 90 | deg 1 91 | curv 0 1.944800491252584 25 26 92 | parm u 0 0 1.944800491252584 1.944800491252584 93 | end 94 | v 0 0 0 95 | v 0.9316225647926331 1.666174292564392 0 96 | cstype bspline 97 | deg 1 98 | curv 0 1.908941454773378 27 28 99 | parm u 0 0 1.908941454773378 1.908941454773378 100 | end 101 | v 0.9316225647926331 1.666174292564392 0 102 | v 2.226675748825073 3.068634510040283 0 103 | cstype bspline 104 | deg 1 105 | curv 1.908941454773378 3.817882918528454 29 30 106 | parm u 1.908941454773378 1.908941454773378 3.817882918528454 3.817882918528454 107 | end 108 | v 2.226675748825073 3.068634510040283 0 109 | v 3.813503980636597 4.129781723022461 0 110 | cstype bspline 111 | deg 1 112 | curv 3.817882918528454 5.726824388020281 31 32 113 | parm u 3.817882918528454 3.817882918528454 5.726824388020281 5.726824388020281 114 | end 115 | v 3.813503980636597 4.129781723022461 0 116 | v 5.604307174682617 4.790903091430664 0 117 | cstype bspline 118 | deg 1 119 | curv 5.726824388020281 7.635765857921273 33 34 120 | parm u 5.726824388020281 5.726824388020281 7.635765857921273 7.635765857921273 121 | end 122 | v 5.604307174682617 4.790903091430664 0 123 | v 7.5 5.015417575836182 0 124 | cstype bspline 125 | deg 1 126 | curv 7.635765857921273 9.544707324256033 35 36 127 | parm u 7.635765857921273 7.635765857921273 9.544707324256033 9.544707324256033 128 | end 129 | v 7.5 5.015417575836182 0 130 | v 9.395692825317383 4.790903091430664 0 131 | cstype bspline 132 | deg 1 133 | curv 9.544707324256033 11.45364877902941 37 38 134 | parm u 9.544707324256033 9.544707324256033 11.45364877902941 11.45364877902941 135 | end 136 | v 9.395692825317383 4.790903091430664 0 137 | v 11.18649578094482 4.129781723022461 0 138 | cstype bspline 139 | deg 1 140 | curv 11.45364877902941 13.36259024278447 39 40 141 | parm u 11.45364877902941 11.45364877902941 13.36259024278447 13.36259024278447 142 | end 143 | v 11.18649578094482 4.129781723022461 0 144 | v 12.77332401275635 3.068634510040283 0 145 | cstype bspline 146 | deg 1 147 | curv 13.36259024278447 15.27153171227631 41 42 148 | parm u 13.36259024278447 13.36259024278447 15.27153171227631 15.27153171227631 149 | end 150 | v 12.77332401275635 3.068634510040283 0 151 | v 14.06837749481201 1.666174292564392 0 152 | cstype bspline 153 | deg 1 154 | curv 15.27153171227631 17.1804731821773 43 44 155 | parm u 15.27153171227631 15.27153171227631 17.1804731821773 17.1804731821773 156 | end 157 | v 14.06837749481201 1.666174292564392 0 158 | v 15 0 0 159 | cstype bspline 160 | deg 1 161 | curv 17.1804731821773 19.08941464851205 45 46 162 | parm u 17.1804731821773 17.1804731821773 19.08941464851205 19.08941464851205 163 | end 164 | -------------------------------------------------------------------------------- /data/examples/truss2.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 15 0 0 4 | v 20 5 0 5 | cstype bspline 6 | deg 1 7 | curv 0 7.07107 1 2 8 | parm u 0 0 7.07107 7.07107 9 | end 10 | v 40 5 0 11 | v 40 0 0 12 | cstype bspline 13 | deg 1 14 | curv 0 5 3 4 15 | parm u 0 0 5 5 16 | end 17 | v 35 5 0 18 | v 40 5 0 19 | cstype bspline 20 | deg 1 21 | curv 0 5 5 6 22 | parm u 0 0 5 5 23 | end 24 | v 35 5 0 25 | v 40 0 0 26 | cstype bspline 27 | deg 1 28 | curv 0 7.07107 7 8 29 | parm u 0 0 7.07107 7.07107 30 | end 31 | v 40 0 0 32 | v 35 0 0 33 | cstype bspline 34 | deg 1 35 | curv 0 5 9 10 36 | parm u 0 0 5 5 37 | end 38 | v 40 -2 0 39 | v 40 0 0 40 | cstype bspline 41 | deg 1 42 | curv 0 2 11 12 43 | parm u 0 0 2 2 44 | end 45 | v 10 0 0 46 | v 15 5 0 47 | cstype bspline 48 | deg 1 49 | curv 0 7.07107 13 14 50 | parm u 0 0 7.07107 7.07107 51 | end 52 | v 35 5 0 53 | v 35 0 0 54 | cstype bspline 55 | deg 1 56 | curv 0 5 15 16 57 | parm u 0 0 5 5 58 | end 59 | v 30 5 0 60 | v 35 5 0 61 | cstype bspline 62 | deg 1 63 | curv 0 5 17 18 64 | parm u 0 0 5 5 65 | end 66 | v 30 5 0 67 | v 35 0 0 68 | cstype bspline 69 | deg 1 70 | curv 0 7.07107 19 20 71 | parm u 0 0 7.07107 7.07107 72 | end 73 | v 35 0 0 74 | v 30 0 0 75 | cstype bspline 76 | deg 1 77 | curv 0 5 21 22 78 | parm u 0 0 5 5 79 | end 80 | v 35 -2 0 81 | v 35 0 0 82 | cstype bspline 83 | deg 1 84 | curv 0 2 23 24 85 | parm u 0 0 2 2 86 | end 87 | v 30 5 0 88 | v 30 0 0 89 | cstype bspline 90 | deg 1 91 | curv 0 5 25 26 92 | parm u 0 0 5 5 93 | end 94 | v 25 5 0 95 | v 30 5 0 96 | cstype bspline 97 | deg 1 98 | curv 0 5 27 28 99 | parm u 0 0 5 5 100 | end 101 | v 25 5 0 102 | v 30 0 0 103 | cstype bspline 104 | deg 1 105 | curv 0 7.07107 29 30 106 | parm u 0 0 7.07107 7.07107 107 | end 108 | v 30 0 0 109 | v 25 0 0 110 | cstype bspline 111 | deg 1 112 | curv 0 5 31 32 113 | parm u 0 0 5 5 114 | end 115 | v 30 -2 0 116 | v 30 0 0 117 | cstype bspline 118 | deg 1 119 | curv 0 2 33 34 120 | parm u 0 0 2 2 121 | end 122 | v 25 5 0 123 | v 25 0 0 124 | cstype bspline 125 | deg 1 126 | curv 0 5 35 36 127 | parm u 0 0 5 5 128 | end 129 | v 20 5 0 130 | v 25 5 0 131 | cstype bspline 132 | deg 1 133 | curv 0 5 37 38 134 | parm u 0 0 5 5 135 | end 136 | v 20 5 0 137 | v 25 0 0 138 | cstype bspline 139 | deg 1 140 | curv 0 7.07107 39 40 141 | parm u 0 0 7.07107 7.07107 142 | end 143 | v 25 0 0 144 | v 20 0 0 145 | cstype bspline 146 | deg 1 147 | curv 0 5 41 42 148 | parm u 0 0 5 5 149 | end 150 | v 25 -2 0 151 | v 25 0 0 152 | cstype bspline 153 | deg 1 154 | curv 0 2 43 44 155 | parm u 0 0 2 2 156 | end 157 | v 20 5 0 158 | v 20 0 0 159 | cstype bspline 160 | deg 1 161 | curv 0 5 45 46 162 | parm u 0 0 5 5 163 | end 164 | v 15 5 0 165 | v 20 5 0 166 | cstype bspline 167 | deg 1 168 | curv 0 5 47 48 169 | parm u 0 0 5 5 170 | end 171 | v 0 5 0 172 | v 5 5 0 173 | cstype bspline 174 | deg 1 175 | curv 0 5 49 50 176 | parm u 0 0 5 5 177 | end 178 | v 0 0 0 179 | v 0 5 0 180 | cstype bspline 181 | deg 1 182 | curv 0 5 51 52 183 | parm u 0 0 5 5 184 | end 185 | v 0 0 0 186 | v 5 5 0 187 | cstype bspline 188 | deg 1 189 | curv 0 7.07107 53 54 190 | parm u 0 0 7.07107 7.07107 191 | end 192 | v 5 5 0 193 | v 5 0 0 194 | cstype bspline 195 | deg 1 196 | curv 0 5 55 56 197 | parm u 0 0 5 5 198 | end 199 | v 5 0 0 200 | v 10 5 0 201 | cstype bspline 202 | deg 1 203 | curv 0 7.07107 57 58 204 | parm u 0 0 7.07107 7.07107 205 | end 206 | v 15 0 0 207 | v 15 5 0 208 | cstype bspline 209 | deg 1 210 | curv 0 5 59 60 211 | parm u 0 0 5 5 212 | end 213 | v 5 5 0 214 | v 10 5 0 215 | cstype bspline 216 | deg 1 217 | curv 0 5 61 62 218 | parm u 0 0 5 5 219 | end 220 | v 10 5 0 221 | v 15 5 0 222 | cstype bspline 223 | deg 1 224 | curv 0 5 63 64 225 | parm u 0 0 5 5 226 | end 227 | v 20 0 0 228 | v 15 0 0 229 | cstype bspline 230 | deg 1 231 | curv 0 5 65 66 232 | parm u 0 0 5 5 233 | end 234 | v 15 0 0 235 | v 10 0 0 236 | cstype bspline 237 | deg 1 238 | curv 0 5 67 68 239 | parm u 0 0 5 5 240 | end 241 | v 10 0 0 242 | v 5 0 0 243 | cstype bspline 244 | deg 1 245 | curv 0 5 69 70 246 | parm u 0 0 5 5 247 | end 248 | v 5 0 0 249 | v 0 0 0 250 | cstype bspline 251 | deg 1 252 | curv 0 5 71 72 253 | parm u 0 0 5 5 254 | end 255 | v 10 5 0 256 | v 10 0 0 257 | cstype bspline 258 | deg 1 259 | curv 0 5 73 74 260 | parm u 0 0 5 5 261 | end 262 | v 0 -2 0 263 | v 0 0 0 264 | cstype bspline 265 | deg 1 266 | curv 0 2 75 76 267 | parm u 0 0 2 2 268 | end 269 | v 0 0 0 270 | v -2 0 0 271 | cstype bspline 272 | deg 1 273 | curv 0 2 77 78 274 | parm u 0 0 2 2 275 | end 276 | v 20 -2 0 277 | v 20 0 0 278 | cstype bspline 279 | deg 1 280 | curv 0 2 79 80 281 | parm u 0 0 2 2 282 | end 283 | v 5 -2 0 284 | v 5 0 0 285 | cstype bspline 286 | deg 1 287 | curv 0 2 81 82 288 | parm u 0 0 2 2 289 | end 290 | v 10 0 0 291 | v 10 -2 0 292 | cstype bspline 293 | deg 1 294 | curv 0 2 83 84 295 | parm u 0 0 2 2 296 | end 297 | v 15 0 0 298 | v 15 -2 0 299 | cstype bspline 300 | deg 1 301 | curv 0 2 85 86 302 | parm u 0 0 2 2 303 | end 304 | -------------------------------------------------------------------------------- /scripts/paper-CSD/exampleA_arch-input.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.diagrams import FormGraph 3 | from compas_ags.diagrams import FormDiagram 4 | from compas_ags.diagrams import ForceDiagram 5 | from compas_ags.ags import form_update_q_from_qind 6 | from compas_ags.ags import force_update_from_form 7 | from compas_ags.ags import update_diagrams_from_constraints 8 | from compas_ags.viewers import Viewer 9 | 10 | 11 | def view_form_force(form, force, forcescale=0.5, edge_label=True): 12 | if edge_label: 13 | form_edge_label = {uv: index for index, uv in enumerate(form.edges())} 14 | force_edge_label = force_edge_labels 15 | else: 16 | form_edge_label = None 17 | force_edge_label = None 18 | 19 | viewer = Viewer(form, force, delay_setup=False) 20 | viewer.draw_form(edgelabel=form_edge_label, 21 | forces_on=True, 22 | forcescale=forcescale, 23 | vertexcolor={key: '#000000' for key in form.vertices_where({'is_fixed': True})}) 24 | viewer.draw_force(edgelabel=force_edge_label) 25 | viewer.show() 26 | 27 | 28 | def view_with_initial_stage(form, force, forcescale=0.5, edge_label=True): 29 | if edge_label: 30 | form_edge_label = {uv: index for index, uv in enumerate(form.edges())} 31 | force_edge_label = force_edge_labels 32 | else: 33 | form_edge_label = None 34 | force_edge_label = None 35 | 36 | viewer = Viewer(form, force, delay_setup=False) 37 | viewer.draw_form(lines=form_lines, 38 | forces_on=True, 39 | external_on=True, 40 | forcescale=forcescale, 41 | edgelabel=form_edge_label, 42 | vertexcolor={key: '#000000' for key in form.vertices_where({'is_fixed': True})}) 43 | viewer.draw_force(lines=force_lines, 44 | edgelabel=force_edge_label 45 | ) 46 | viewer.show() 47 | 48 | 49 | def store_initial_lines(form, force): 50 | 51 | form_lines = [] 52 | for u, v in form.edges(): 53 | form_lines.append({ 54 | 'start': form.vertex_coordinates(u, 'xy'), 55 | 'end': form.vertex_coordinates(v, 'xy'), 56 | 'width': 1.0, 57 | 'color': '#cccccc', 58 | 'style': '--' 59 | }) 60 | 61 | force_lines = [] 62 | for u, v in force.edges(): 63 | force_lines.append({ 64 | 'start': force.vertex_coordinates(u, 'xy'), 65 | 'end': force.vertex_coordinates(v, 'xy'), 66 | 'width': 1.0, 67 | 'color': '#cccccc', 68 | 'style': '--' 69 | }) 70 | 71 | return form_lines, force_lines 72 | 73 | 74 | # ------------------------------------------------------------------------------ 75 | # 1. Problem of getting to a funicular shape 76 | # - Input a circular arch 77 | # - Input "target forces" for the loads applied 78 | # - Update form and force diagram 79 | # ------------------------------------------------------------------------------ 80 | 81 | graph = FormGraph.from_obj(compas_ags.get('paper/exA_arch-circular.obj')) 82 | form = FormDiagram.from_graph(graph) 83 | edge_index = form.edge_index() 84 | index_edge = form.index_edge() 85 | 86 | # create a dual force diagram 87 | force = ForceDiagram.from_formdiagram(form) 88 | 89 | # create label for plots 90 | force_edges = force.ordered_edges(form) 91 | force_edge_labels = {(u, v): index for index, (u, v) in enumerate(force_edges)} 92 | force_edge_labels.update({(v, u): index for index, (u, v) in enumerate(force_edges)}) 93 | 94 | # set supports 95 | supports = [1, 7] 96 | for key in supports: 97 | form.vertex_attribute(key, 'is_fixed', True) 98 | 99 | # apply force density (fd) on independents 100 | fd_applied = -1.0 101 | ind_edge = (4, 15) 102 | form.edge_attribute(ind_edge, 'is_ind', True) 103 | form.edge_attribute(ind_edge, 'q', fd_applied) 104 | 105 | # compute starting point 106 | form_update_q_from_qind(form) 107 | force_update_from_form(force, form) 108 | 109 | # Identify auto constraints 110 | form.identify_constraints() 111 | 112 | # visualise initial solution 113 | view_form_force(form, force, forcescale=2.0) 114 | 115 | # store lines for plot with initial stage 116 | form_lines, force_lines = store_initial_lines(form, force) 117 | 118 | # set target lengths 119 | for u, v in form.edges_where({'is_load': True}): 120 | form.edge_attribute((u, v), 'target_force', abs(fd_applied)) 121 | 122 | # Reflect all constraints to force diagram 123 | force.constraints_from_dual() 124 | 125 | # view_with_force_lengths(form, force) 126 | 127 | update_diagrams_from_constraints(form, force) 128 | # update_diagrams_from_constraints(form, force, callback=view_with_force_lengths, printout=True, max_iter=20) 129 | 130 | view_with_initial_stage(form, force, forcescale=2.0, edge_label=False) 131 | -------------------------------------------------------------------------------- /scripts/example_nullspace.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.diagrams import FormGraph 3 | from compas_ags.diagrams import FormDiagram 4 | from compas_ags.diagrams import ForceDiagram 5 | from compas_ags.viewers import Viewer 6 | from compas_ags.ags import form_update_from_force 7 | from compas_ags.ags import form_update_q_from_qind 8 | from compas_ags.ags import force_update_from_form 9 | from compas_ags.ags import form_compute_nullspace 10 | from compas_ags.ags import ConstraintsCollection 11 | from compas_ags.ags import HorizontalFix 12 | 13 | 14 | # ------------------------------------------------------------------------------ 15 | # 1. lines from isostatic structure from paper AGS 16 | # ------------------------------------------------------------------------------ 17 | graph = FormGraph.from_obj(compas_ags.get('paper/gs_form_force.obj')) 18 | 19 | form = FormDiagram.from_graph(graph) 20 | force = ForceDiagram.from_formdiagram(form) 21 | 22 | # ------------------------------------------------------------------------------ 23 | # 2. set independent edge and assign load 24 | # ------------------------------------------------------------------------------ 25 | u_edge = 0 26 | v_edge = 1 27 | f = -5.0 28 | l = form.edge_length(u_edge, v_edge) 29 | form.edge_attribute((u_edge, v_edge), 'q', f/l) 30 | form.edge_attribute((u_edge, v_edge), 'is_ind', True) 31 | 32 | # ------------------------------------------------------------------------------ 33 | # 3. set the fixed vertices 34 | # ------------------------------------------------------------------------------ 35 | left = 5 36 | right = 3 37 | form.vertex_attribute(left, 'is_fixed', True) 38 | form.vertex_attribute(right, 'is_fixed', True) 39 | fixed = [left, right] 40 | 41 | # ------------------------------------------------------------------------------ 42 | # 3. set applied load 43 | # ------------------------------------------------------------------------------ 44 | e1 = {'v': list(form.vertices_where({'x': 3.0, 'y': 3.0}))[0], 45 | 'u': list(form.vertices_where({'x': 3.669563106796117, 'y': 5.008689320388349}))[0]} 46 | form.edge_attribute((e1['v'], e1['u']), 'q', -1.0) 47 | form.edge_attribute((e1['v'], e1['u']), 'is_ind', True) 48 | 49 | # update the diagrams 50 | form_update_q_from_qind(form) 51 | force_update_from_form(force, form) 52 | 53 | # store the original vertex locations 54 | force_key_xyz = {key: force.vertex_coordinates(key) for key in force.vertices()} 55 | 56 | # -------------------------------------------------------------------------- 57 | # 4. force diagram manipulation and compute the nullspace 58 | # -------------------------------------------------------------------------- 59 | # set constraints 60 | C = ConstraintsCollection(form) 61 | 62 | # fix y coordinates of the left and right vertices 63 | C.add_constraint(HorizontalFix(form, left)) 64 | C.add_constraint(HorizontalFix(form, right)) 65 | 66 | # fix the length of edges connecting leaf vertices 67 | C.constrain_dependent_leaf_edges_lengths() 68 | 69 | constraint_lines = C.get_lines() 70 | 71 | # compute the amount of nullspace modes 72 | ns = form_compute_nullspace(form, force, C) 73 | print("Dimension of nullspace: " + str(len(ns))) 74 | 75 | # -------------------------------------------------------------------------- 76 | # 5. display force diagram and a specific solution of form diagram 77 | # -------------------------------------------------------------------------- 78 | 79 | def show(i): 80 | # i is the index of nullspace mode 81 | c = 10 82 | nsi = ns[i] * c 83 | # store lines representing the current null space mode 84 | form_lines = [] 85 | for u, v in form.edges(): 86 | form_lines.append({ 87 | 'start': [x + y for x, y in zip(form.vertex_coordinates(u, 'xy'), nsi[u])], 88 | 'end': [x + y for x, y in zip(form.vertex_coordinates(v, 'xy'), nsi[v])], 89 | 'width': 1.0, 90 | 'color': '#cccccc', 91 | 'style': '--' 92 | }) 93 | 94 | form_lines = form_lines + constraint_lines 95 | 96 | # display the original configuration 97 | # and the configuration after modifying the force diagram 98 | viewer = Viewer(form, force, delay_setup=False) 99 | viewer.draw_form(lines=form_lines, 100 | forces_on=False, 101 | vertexlabel={key: key for key in form.vertices()}, 102 | external_on=False, 103 | vertexsize=0.2, 104 | vertexcolor={key: '#000000' for key in fixed}, 105 | edgelabel={uv: index for index, uv in enumerate(form.edges())} 106 | ) 107 | 108 | viewer.draw_force(vertexlabel={key: key for key in force.vertices()}, 109 | vertexsize=0.2, 110 | edgelabel={uv: index for index, uv in enumerate(force.edges())} 111 | ) 112 | 113 | viewer.show() 114 | 115 | for i in range(len(ns)): 116 | show(i) 117 | -------------------------------------------------------------------------------- /data/forms/zero.ags: -------------------------------------------------------------------------------- 1 | {"data": {"force": {"dfa": {}, "vertex": {"0": {"y": -0.66666666666666663, "z": 0.0, "x": -0.66666666666666663}, "1": {"y": -0.66666666666666552, "z": 0.0, "x": -0.66666666666666075}, "2": {"y": 14.333333333333332, "z": 0.0, "x": -0.66666666666666075}, "3": {"y": 4.3333333333333348, "z": 0.0, "x": -0.66666666666665941}, "4": {"y": -5.6666666666666643, "z": 0.0, "x": -0.66666666666666163}, "5": {"y": -15.666666666666666, "z": 0.0, "x": -0.66666666666666308}, "6": {"y": -0.6666666666666663, "z": 0.0, "x": -15.666666666666661}, "7": {"y": 14.333333333333334, "z": 0.0, "x": -15.666666666666663}, "8": {"y": 4.3333333333333339, "z": 0.0, "x": -20.666666666666661}, "9": {"y": -5.6666666666666634, "z": 0.0, "x": -20.666666666666661}, "10": {"y": -15.666666666666664, "z": 0.0, "x": -15.666666666666661}, "11": {"y": -0.66666666666666496, "z": 0.0, "x": -15.666666666666661}}, "max_int_key": 11, "dea": {"lmin": 9.9999999999999995e-08, "l": 0.0, "lmax": 10000000.0}, "face": {"0": [2, 3, 8, 6, 7], "2": [8, 3, 4, 9], "5": [9, 4, 5, 10, 11], "7": [2, 7, 1], "8": [0, 1, 10, 5], "11": [6, 8, 9, 11, 1], "12": [1, 7, 6], "13": [10, 1, 11]}, "facedata": {"0": {}, "2": {}, "5": {}, "7": {}, "8": {}, "11": {}, "12": {}, "13": {}}, "attributes": {"name": "Force"}, "dva": {"y": 0.0, "z": 0.0, "x": 0.0, "is_param": false, "is_fixed": false}, "max_int_fkey": 13, "edgedata": {}}, "form": {"dfa": {}, "vertex": {"0": {"y": 0.0, "z": 0.0, "x": 15.0, "is_fixed": true}, "1": {"y": -2.0, "z": 0.0, "x": 15.0}, "2": {"y": 0.0, "z": 0.0, "x": 10.0, "is_fixed": true}, "3": {"y": -2.0, "z": 0.0, "x": 10.0}, "4": {"y": -2.0, "z": 0.0, "x": 5.0}, "5": {"y": 0.0, "z": 0.0, "x": 5.0, "is_fixed": true}, "6": {"y": -2.0, "z": 0.0, "x": 20.0}, "7": {"y": 0.0, "z": 0.0, "x": 20.0, "is_fixed": true}, "8": {"y": 0.0, "z": 0.0, "x": 0.0, "is_fixed": true}, "9": {"y": 0.0, "z": 0.0, "x": -2.0}, "10": {"y": -2.0, "z": 0.0, "x": 0.0}, "11": {"y": 5.0, "z": 0.0, "x": 10.0}, "12": {"y": 5.0, "z": 0.0, "x": 15.0}, "13": {"y": 5.0, "z": 0.0, "x": 5.0}}, "max_int_key": 13, "dea": {"l": 0.0, "_is_edge": true, "f": 0.0, "q": 1.0, "is_external": false, "is_load": false, "is_reaction": false, "is_ind": false}, "face": {"0": [10, 8, 9], "1": [9, 8, 13, 11, 12, 7, 6], "2": [6, 7, 0, 1], "3": [1, 0, 2, 3], "4": [3, 2, 5, 4], "5": [4, 5, 8, 10], "6": [0, 12, 11], "7": [12, 0, 7], "8": [2, 0, 11], "9": [5, 2, 11], "10": [8, 5, 13], "11": [5, 11, 13]}, "facedata": {"0": {}, "1": {}, "2": {}, "3": {}, "4": {}, "5": {}, "6": {}, "7": {}, "8": {}, "9": {}, "10": {}, "11": {}}, "attributes": {"name": "Form"}, "dva": {"y": 0.0, "cy": 0.0, "z": 0.0, "x": 0.0, "cx": 0.0, "is_fixed": false}, "max_int_fkey": 11, "edgedata": {"(11, 13)": {"l": 5.0, "f": -15.0, "q": -3.0}, "(8, 13)": {"l": 7.0710678118654755, "f": -21.213203435596427, "q": -3.0}, "(8, 9)": {"l": 2.0, "is_external": true, "f": -0.0, "q": -0.0}, "(0, 1)": {"l": 2.0, "f": 10.0, "q": 5.0, "is_external": true, "is_ind": true}, "(1, 3)": {"_is_edge": false}, "(5, 13)": {"l": 5.0, "f": 15.0, "q": 3.0}, "(3, 2)": {"l": 2.0, "f": 10.0, "q": 5.0, "is_external": true, "is_ind": true}, "(5, 4)": {"l": 2.0, "f": 10.0, "q": 5.0, "is_external": true, "is_ind": true}, "(0, 2)": {"l": 5.0, "f": 20.0, "q": 4.0}, "(9, 10)": {"_is_edge": false}, "(8, 10)": {"l": 2.0, "is_external": true, "f": -15.0, "q": -7.5}, "(6, 1)": {"_is_edge": false}, "(12, 11)": {"l": 5.0, "f": -15.0, "q": -3.0}, "(7, 6)": {"l": 2.0, "is_external": true, "f": -15.0, "q": -7.5}, "(10, 4)": {"_is_edge": false}, "(11, 2)": {"l": 5.0, "f": 10.0, "q": 2.0}, "(4, 3)": {"_is_edge": false}, "(3, 4)": {"_is_edge": false}, "(13, 8)": {"l": 7.0710678118654755, "f": -21.213203435596427, "q": -3.0}, "(5, 11)": {"l": 7.0710678118654755, "f": -7.0710678118654755, "q": -1.0}, "(6, 9)": {"_is_edge": false}, "(5, 2)": {"l": 5.0, "f": 20.0, "q": 4.0}, "(12, 7)": {"l": 7.0710678118654755, "f": -21.213203435596427, "q": -3.0}, "(2, 3)": {"l": 2.0, "f": 10.0, "q": 5.0, "is_external": true, "is_ind": true}, "(1, 6)": {"_is_edge": false}, "(9, 8)": {"l": 2.0, "is_external": true, "f": -0.0, "q": -0.0}, "(2, 0)": {"l": 5.0, "f": 20.0, "q": 4.0}, "(1, 0)": {"l": 2.0, "f": 10.0, "q": 5.0, "is_external": true, "is_ind": true}, "(0, 7)": {"l": 5.0, "f": 15.0, "q": 3.0}, "(13, 5)": {"l": 5.0, "f": 15.0, "q": 3.0}, "(7, 0)": {"l": 5.0, "f": 15.0, "q": 3.0}, "(2, 5)": {"l": 5.0, "f": 20.0, "q": 4.0}, "(12, 0)": {"l": 5.0, "f": 15.0, "q": 3.0}, "(11, 5)": {"l": 7.0710678118654755, "f": -7.0710678118654755, "q": -1.0}, "(6, 7)": {"l": 2.0, "is_external": true, "f": -15.0, "q": -7.5}, "(8, 5)": {"l": 5.0, "f": 15.0, "q": 3.0}, "(7, 12)": {"l": 7.0710678118654755, "f": -21.213203435596427, "q": -3.0}, "(0, 11)": {"l": 7.0710678118654755, "f": -7.0710678118654755, "q": -1.0}, "(11, 0)": {"l": 7.0710678118654755, "f": -7.0710678118654755, "q": -1.0}, "(9, 6)": {"_is_edge": false}, "(3, 1)": {"_is_edge": false}, "(10, 9)": {"_is_edge": false}, "(0, 12)": {"l": 5.0, "f": 15.0, "q": 3.0}, "(10, 8)": {"l": 2.0, "is_external": true, "f": -15.0, "q": -7.5}, "(11, 12)": {"l": 5.0, "f": -15.0, "q": -3.0}, "(13, 11)": {"l": 5.0, "f": -15.0, "q": -3.0}, "(4, 10)": {"_is_edge": false}, "(4, 5)": {"l": 2.0, "f": 10.0, "q": 5.0, "is_external": true, "is_ind": true}, "(5, 8)": {"l": 5.0, "f": 15.0, "q": 3.0}, "(2, 11)": {"l": 5.0, "f": 10.0, "q": 2.0}}}}} -------------------------------------------------------------------------------- /scripts/paper-CSD/exampleC_dragging.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.diagrams import FormDiagram 3 | from compas_ags.diagrams import ForceDiagram 4 | from compas_ags.ags import form_update_q_from_qind 5 | from compas_ags.ags import force_update_from_form 6 | from compas_ags.ags import update_diagrams_from_constraints 7 | from compas_ags.viewers import Viewer 8 | 9 | 10 | def view_form_force(form, force, forcescale=0.5, edge_label=True): 11 | if edge_label: 12 | form_edge_label = {uv: index for index, uv in enumerate(form.edges())} 13 | force_edge_label = force_edge_labels 14 | else: 15 | form_edge_label = None 16 | force_edge_label = None 17 | 18 | viewer = Viewer(form, force, delay_setup=False) 19 | viewer.draw_form(edgelabel=form_edge_label, 20 | forces_on=True, 21 | forcescale=forcescale, 22 | vertexcolor={key: '#000000' for key in form.vertices_where({'is_fixed': True})}) 23 | viewer.draw_force(edgelabel=force_edge_label) 24 | viewer.show() 25 | 26 | 27 | def view_with_initial_stage(form, force, forcescale=0.5, edge_label=True): 28 | if edge_label: 29 | form_edge_label = {uv: index for index, uv in enumerate(form.edges())} 30 | force_edge_label = force_edge_labels 31 | else: 32 | form_edge_label = None 33 | force_edge_label = None 34 | 35 | viewer = Viewer(form, force, delay_setup=False) 36 | viewer.draw_form(lines=form_lines, 37 | forces_on=True, 38 | external_on=True, 39 | forcescale=forcescale, 40 | edgelabel=form_edge_label, 41 | vertexcolor={key: '#000000' for key in form.vertices_where({'is_fixed': True})}) 42 | viewer.draw_force(lines=force_lines, 43 | edgelabel=force_edge_label 44 | ) 45 | viewer.show() 46 | 47 | 48 | def store_initial_lines(form, force): 49 | 50 | form_lines = [] 51 | for u, v in form.edges(): 52 | form_lines.append({ 53 | 'start': form.vertex_coordinates(u, 'xy'), 54 | 'end': form.vertex_coordinates(v, 'xy'), 55 | 'width': 1.0, 56 | 'color': '#cccccc', 57 | 'style': '--' 58 | }) 59 | 60 | force_lines = [] 61 | for u, v in force.edges(): 62 | force_lines.append({ 63 | 'start': force.vertex_coordinates(u, 'xy'), 64 | 'end': force.vertex_coordinates(v, 'xy'), 65 | 'width': 1.0, 66 | 'color': '#cccccc', 67 | 'style': '--' 68 | }) 69 | 70 | return form_lines, force_lines 71 | 72 | 73 | # --------------------------------------------------------------------------------------- 74 | # 3. Dragging nodes of form diagram and updating both diagrams according to constraints 75 | # - Input a parabolic arch (uniform load case). 76 | # - Move the form diagram support 77 | # - Move one of the internal nodes to a specific position 78 | # --------------------------------------------------------------------------------------- 79 | 80 | input_file = compas_ags.get('paper/exB_arch-output.json') 81 | 82 | form = FormDiagram.from_json(input_file) 83 | force = ForceDiagram.from_formdiagram(form) 84 | 85 | # create label for plots 86 | force_edges = force.ordered_edges(form) 87 | force_edge_labels = {(u, v): index for index, (u, v) in enumerate(force_edges)} 88 | force_edge_labels.update({(v, u): index for index, (u, v) in enumerate(force_edges)}) 89 | 90 | # update the diagrams 91 | form_update_q_from_qind(form) 92 | force_update_from_form(force, form) 93 | 94 | # visualise initial solution 95 | view_form_force(form, force, forcescale=2.0) 96 | 97 | # Identify auto constraints 98 | form.identify_constraints() 99 | 100 | # set target lengths 101 | force_constraint = 1.0 102 | for u, v in form.edges_where({'is_load': True}): 103 | form.edge_attribute((u, v), 'target_force', abs(force_constraint)) 104 | 105 | form_lines, force_lines = store_initial_lines(form, force) 106 | 107 | # --------------------------------------------- 108 | # Move the form diagram support 109 | 110 | # Reflect all constraints to force diagram 111 | force.constraints_from_dual() 112 | 113 | form_nodes_move = [1, 8, 11] # move the support and all leaf nodes connected to it 114 | move_support_dist = +2.0 115 | for key in form_nodes_move: 116 | _, y, _ = form.vertex_coordinates(key) 117 | form.vertex_attribute(key, 'y', y + move_support_dist) 118 | 119 | update_diagrams_from_constraints(form, force) 120 | 121 | view_with_initial_stage(form, force, forcescale=2.0, edge_label=False) 122 | 123 | # --------------------------------------------- 124 | # Move the form diagram midspan 125 | 126 | # Reflect all constraints to force diagram 127 | force.constraints_from_dual() 128 | 129 | form_nodes_move = [4, 15] # move the node and all leaf nodes connected to it 130 | move_support_dist = +1.5 131 | for key in form_nodes_move: 132 | _, y, _ = form.vertex_coordinates(key) 133 | form.vertex_attribute(key, 'y', y + move_support_dist) 134 | 135 | update_diagrams_from_constraints(form, force) 136 | 137 | view_with_initial_stage(form, force, forcescale=2.0, edge_label=False) 138 | -------------------------------------------------------------------------------- /data/paper/exB_arch-output.json: -------------------------------------------------------------------------------- 1 | {"attributes": {"name": "Form"}, "dva": {"x": 0.0, "y": 0.0, "z": 0.0, "is_fixed": false, "line_constraint": null, "cx": 0.0, "cy": 0.0}, "dea": {"_is_edge": true, "a": 0.0, "q": 1.0, "f": 0.0, "l": 0.0, "is_ind": false, "is_external": false, "is_reaction": false, "is_load": false, "target_vector": null, "target_length": null}, "dfa": {}, "vertex": {"0": {"x": 5.999842473611735, "y": 0.8303533474689845, "z": 0.0, "line_constraint": {"dtype": "compas.geometry/Line", "value": {"start": [6.0, 1.2603986447377271, 0.0], "end": [6.0, 2.2603986447377276, 0.0]}}}, "1": {"x": 7.0, "y": 0.0, "z": 0.0, "is_fixed": true}, "2": {"x": 4.9998470834974515, "y": 1.3836844429883535, "z": 0.0, "line_constraint": {"dtype": "compas.geometry/Line", "value": {"start": [5.0, 1.8642080737036446, 0.0], "end": [5.0, 2.8642080737036446, 0.0]}}}, "3": {"x": 3.9998540304003223, "y": 1.6602185993071166, "z": 0.0, "line_constraint": {"dtype": "compas.geometry/Line", "value": {"start": [4.0, 2.1310436740650065, 0.0], "end": [4.0, 3.1310436740650065, 0.0]}}}, "4": {"x": 2.999862355482195, "y": 1.6600226907633568, "z": 0.0, "line_constraint": {"dtype": "compas.geometry/Line", "value": {"start": [3.0, 2.131043674077269, 0.0], "end": [3.0, 3.1310436740772696, 0.0]}}}, "5": {"x": 1.9998696256176691, "y": 1.3831937223198871, "z": 0.0, "line_constraint": {"dtype": "compas.geometry/Line", "value": {"start": [1.9999999999999996, 1.8642080737002402, 0.0], "end": [1.9999999999999996, 2.86420807370024, 0.0]}}}, "6": {"x": 0.9998735663427135, "y": 0.8297893118580302, "z": 0.0, "line_constraint": {"dtype": "compas.geometry/Line", "value": {"start": [0.9999999999999997, 1.2603986446980737, 0.0], "end": [0.9999999999999997, 2.260398644698074, 0.0]}}}, "7": {"x": 0.0, "y": 0.0, "z": 0.0, "is_fixed": true}, "8": {"x": 8.0, "y": 0.0, "z": 0.0}, "9": {"x": -1.0000000000000004, "y": 0.0, "z": 0.0}, "10": {"x": 0.0, "y": -1.0000000000000004, "z": 0.0}, "11": {"x": 7.0, "y": -1.0000000000000004, "z": 0.0}, "12": {"x": 5.999842473611734, "y": 1.8303533474689846, "z": 0.0}, "13": {"x": 4.999847083497451, "y": 2.3836844429883532, "z": 0.0}, "14": {"x": 3.9998540304003236, "y": 2.660218599307117, "z": 0.0}, "15": {"x": 2.999862355482195, "y": 2.660022690763357, "z": 0.0}, "16": {"x": 1.9998696256176691, "y": 2.3831937223198874, "z": 0.0}, "17": {"x": 0.9998735663427136, "y": 1.8297893118580306, "z": 0.0}}, "face": {"0": [10, 7, 9], "1": [9, 7, 6, 17], "2": [17, 6, 5, 16], "3": [16, 5, 4, 15], "4": [15, 4, 3, 14], "5": [14, 3, 2, 13], "6": [13, 2, 0, 12], "7": [12, 0, 1, 8], "8": [8, 1, 11], "9": [11, 1, 0, 2, 3, 4, 5, 6, 7, 10]}, "facedata": {"0": {}, "1": {}, "2": {}, "3": {}, "4": {}, "5": {}, "6": {}, "7": {}, "8": {}, "9": {}}, "edgedata": {"(8, 11)": {"_is_edge": false}, "(10, 11)": {"_is_edge": false}, "(9, 10)": {"_is_edge": false}, "(9, 17)": {"_is_edge": false}, "(16, 17)": {"_is_edge": false}, "(15, 16)": {"_is_edge": false}, "(14, 15)": {"_is_edge": false}, "(13, 14)": {"_is_edge": false}, "(12, 13)": {"_is_edge": false}, "(8, 12)": {"_is_edge": false}, "(0, 12)": {"is_external": true, "q": -1.0000024843177844, "f": -1.0000024843177844, "l": 1.0, "target_vector": [0.0, 1.0], "is_load": true, "target_length": 1.0, "a": 180.0}, "(1, 8)": {"is_external": true, "q": -3.6146194084295975, "f": -3.6146194084295975, "l": 1.0, "target_vector": [1.0, 0.0], "is_load": false, "is_reaction": true, "a": 179.99997905207596}, "(1, 11)": {"is_external": true, "q": -2.997466976189598, "f": -2.997466976189599, "l": 1.0000000000000004, "target_vector": [0.0, -1.0], "is_load": false, "is_reaction": true, "a": 180.0}, "(2, 13)": {"is_external": true, "q": -1.0000033471122978, "f": -1.0000033471122975, "l": 0.9999999999999998, "target_vector": [0.0, 1.0], "is_load": true, "target_length": 1.0, "a": 180.0}, "(3, 14)": {"is_external": true, "q": -1.0000038061956675, "f": -1.0000038061956678, "l": 1.0000000000000002, "target_vector": [0.0, 1.0], "is_load": true, "target_length": 1.0, "a": 180.0}, "(4, 15)": {"is_external": true, "is_ind": true, "q": -1.0000000000000004, "f": -1.0000000000000004, "l": 1.0, "target_vector": [0.0, 1.0], "is_load": true, "target_length": 1.0, "a": 180.0}, "(5, 16)": {"is_external": true, "q": -0.9999999667428461, "f": -0.9999999667428463, "l": 1.0000000000000002, "target_vector": [0.0, 1.0], "is_load": true, "target_length": 1.0, "a": 180.0}, "(6, 17)": {"is_external": true, "q": -0.9999999733298326, "f": -0.999999973329833, "l": 1.0000000000000004, "target_vector": [0.0, 1.0], "is_load": true, "target_length": 1.0, "a": 180.0}, "(7, 10)": {"is_external": true, "q": -3.0025439085858867, "f": -3.002543908585888, "l": 1.0000000000000004, "target_vector": [0.0, -1.0], "is_load": false, "is_reaction": true, "a": 180.0}, "(7, 9)": {"is_external": true, "q": -3.6146194084290393, "f": -3.614619408429041, "l": 1.0000000000000004, "target_vector": [-1.0, 0.0], "is_load": false, "is_reaction": true, "a": 180.0}, "(0, 2)": {"q": -3.613523599803634, "f": -4.1298102357463025, "l": 1.1428762319334858, "a": 179.9681057676462}, "(0, 1)": {"q": -3.6123441115103945, "f": -4.695771908490776, "l": 1.2999237513193, "a": 179.9673965162892}, "(2, 3)": {"q": -3.6141022760661605, "f": -3.749719931682337, "l": 1.0375245760238396, "a": 179.96888348207202}, "(3, 4)": {"q": -3.614650326555669, "f": -3.61462030366176, "l": 0.9999916941083655, "a": 179.97089983839112}, "(4, 5)": {"q": -3.6151360382748057, "f": -3.751075566590455, "l": 1.0376028804660202, "a": 179.97195170600756}, "(5, 6)": {"q": -3.615561747026529, "f": -4.132270046802622, "l": 1.1429123151336134, "a": 179.97343482004084}, "(6, 7)": {"q": -3.6164491573721107, "f": -4.699015160611765, "l": 1.2993450083579496, "a": 179.97380202496836}}, "max_vertex": 17, "max_face": 9} -------------------------------------------------------------------------------- /scripts/example_truss_edge_orientation.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.diagrams import FormGraph 3 | from compas_ags.diagrams import FormDiagram 4 | from compas_ags.diagrams import ForceDiagram 5 | from compas_ags.viewers import Viewer 6 | from compas_ags.ags import form_update_q_from_qind 7 | from compas_ags.ags import force_update_from_form 8 | from compas_ags.ags import form_update_from_force 9 | from compas_ags.ags import force_update_from_constraints 10 | 11 | # ------------------------------------------------------------------------------ 12 | # 1. Get OBJ file for the geometry 13 | # ------------------------------------------------------------------------------ 14 | 15 | graph = FormGraph.from_obj(compas_ags.get('paper/gs_truss.obj')) 16 | 17 | # Add horizontal line to graph to make Structure isostatic. 18 | lines = graph.to_lines() 19 | lines.append(([-2.0, 0.0, 0.0], [0.0, 0.0, 0.0])) 20 | graph = FormGraph.from_lines(lines) 21 | 22 | form = FormDiagram.from_graph(graph) 23 | force = ForceDiagram.from_formdiagram(form) 24 | 25 | # ------------------------------------------------------------------------------ 26 | # 2. prescribe edge force density and set fixed vertices 27 | # ------------------------------------------------------------------------------ 28 | # prescribe force density to edge 29 | edges_ind = [ 30 | (8, 9), 31 | ] 32 | for index in edges_ind: 33 | u, v = index 34 | form.edge_attribute((u, v), 'is_ind', True) 35 | form.edge_attribute((u, v), 'q', +1.) 36 | 37 | index_edge = form.index_edge() 38 | 39 | # set the fixed corners 40 | left = 6 41 | right = 1 42 | fixed = [left, right] 43 | 44 | for key in fixed: 45 | form.vertex_attribute(key, 'is_fixed', True) 46 | 47 | # update the diagrams 48 | form_update_q_from_qind(form) 49 | force_update_from_form(force, form) 50 | 51 | # store lines representing the current state of equilibrium 52 | form_lines = [] 53 | for u, v in form.edges(): 54 | form_lines.append({ 55 | 'start': form.vertex_coordinates(u, 'xy'), 56 | 'end': form.vertex_coordinates(v, 'xy'), 57 | 'width': 1.0, 58 | 'color': '#cccccc', 59 | 'style': '--' 60 | }) 61 | 62 | force_lines = [] 63 | for u, v in force.edges(): 64 | force_lines.append({ 65 | 'start': force.vertex_coordinates(u, 'xy'), 66 | 'end': force.vertex_coordinates(v, 'xy'), 67 | 'width': 1.0, 68 | 'color': '#cccccc', 69 | 'style': '--' 70 | }) 71 | 72 | force_edges = force.ordered_edges(form) 73 | 74 | force_edge_labels1 = {(u, v): index for index, (u, v) in enumerate(force_edges)} 75 | force_edge_labels2 = {(v, u): index for index, (u, v) in enumerate(force_edges)} 76 | force_edge_labels = {**force_edge_labels1, **force_edge_labels2} 77 | 78 | # ------------------------------------------------------------------------------ 79 | # 3. prescribe constraints on the form 80 | # ------------------------------------------------------------------------------ 81 | 82 | # # A. Fix orientation of edges at bottom chord 83 | edges_fix_orient = [13, 14, 16, 18, 4] 84 | 85 | for index in edges_fix_orient: 86 | form.edge_attribute(index_edge[index], 'has_fixed_orientation', True) 87 | 88 | # B. Assign forces on the top chord to have the same length 89 | index_edges_constant_force = [0, 1, 5, 7, 9] 90 | L = force.edge_length(*force.ordered_edges(form)[1]) 91 | 92 | for index in index_edges_constant_force: 93 | form.edge_attribute(index_edge[index], 'target_force', L) 94 | 95 | # C. Make edge 4 short (less force) - Try options B and C... 96 | # edges_change = [4, 13] 97 | # new_length = 5.5 98 | # for edge_change in edges_change: 99 | # form.edge_attribute(index_edge[edge_change], 'target_force', new_length) 100 | 101 | viewer = Viewer(form, force, delay_setup=False) 102 | viewer.draw_form(edgelabel={uv: index for index, uv in enumerate(form.edges())}) 103 | viewer.draw_force(edgelabel=force_edge_labels) 104 | viewer.show() 105 | 106 | # -------------------------------------------------------------------------- 107 | # 4. find force diagram to respect force/geometry constraints and update equilibtium 108 | # -------------------------------------------------------------------------- 109 | 110 | # Identify auto constraints 111 | form.identify_constraints() 112 | 113 | # Reflect all constraints to force diagram 114 | force.constraints_from_dual() 115 | 116 | # Update force diagram given constraints 117 | force_update_from_constraints(force) 118 | 119 | # Update form diagram based on force diagram 120 | form_update_from_force(form, force) 121 | 122 | # ------------------------------------------------------------------------------ 123 | # 5. display the orginal configuration 124 | # and the configuration after modifying the force diagram 125 | # ------------------------------------------------------------------------------ 126 | viewer = Viewer(form, force, delay_setup=False) 127 | 128 | lengths_uv = {} 129 | for u, v in force.edges(): 130 | lengths_uv[(u, v)] = force.edge_length(u, v) 131 | 132 | viewer.draw_form(lines=form_lines, 133 | forces_on=True, 134 | vertexlabel={key: key for key in form.vertices()}, 135 | external_on=False, 136 | vertexsize=0.2, 137 | vertexcolor={key: '#000000' for key in fixed}, 138 | ) 139 | 140 | viewer.draw_force(lines=force_lines, 141 | vertexlabel={key: key for key in force.vertices()}, 142 | vertexsize=0.2, 143 | edgelabel=lengths_uv 144 | ) 145 | 146 | viewer.show() 147 | -------------------------------------------------------------------------------- /scripts/paper-CSD/exampleD_truss_constant.py: -------------------------------------------------------------------------------- 1 | import compas_ags 2 | from compas_ags.viewers import Viewer 3 | from compas_ags.diagrams import FormGraph 4 | from compas_ags.diagrams import FormDiagram 5 | from compas_ags.diagrams import ForceDiagram 6 | from compas_ags.ags import form_update_q_from_qind 7 | from compas_ags.ags import force_update_from_form 8 | from compas_ags.ags import update_diagrams_from_constraints 9 | 10 | 11 | def view_form_force(form, force, forcescale=0.5, edge_label=True): 12 | if edge_label: 13 | form_edge_label = {uv: index for index, uv in enumerate(form.edges())} 14 | force_edge_label = force_edge_labels 15 | else: 16 | form_edge_label = None 17 | force_edge_label = None 18 | 19 | viewer = Viewer(form, force, delay_setup=False) 20 | viewer.draw_form(edgelabel=form_edge_label, forces_on=True, forcescale=forcescale, vertexcolor={key: "#000000" for key in form.vertices_where({"is_fixed": True})}) 21 | viewer.draw_force(edgelabel=force_edge_label) 22 | viewer.show() 23 | 24 | 25 | def view_with_initial_stage(form, force, forcescale=0.5, edge_label=True): 26 | if edge_label: 27 | form_edge_label = {uv: index for index, uv in enumerate(form.edges())} 28 | force_edge_label = force_edge_labels 29 | else: 30 | form_edge_label = None 31 | force_edge_label = None 32 | 33 | viewer = Viewer(form, force, delay_setup=False) 34 | viewer.draw_form( 35 | lines=form_lines, 36 | forces_on=True, 37 | external_on=True, 38 | forcescale=forcescale, 39 | edgelabel=form_edge_label, 40 | vertexcolor={key: "#000000" for key in form.vertices_where({"is_fixed": True})}, 41 | ) 42 | viewer.draw_force(lines=force_lines, edgelabel=force_edge_label) 43 | viewer.show() 44 | 45 | 46 | def store_initial_lines(form, force): 47 | 48 | form_lines = [] 49 | for u, v in form.edges(): 50 | form_lines.append({"start": form.vertex_coordinates(u, "xy"), "end": form.vertex_coordinates(v, "xy"), "width": 1.0, "color": "#cccccc", "style": "--"}) 51 | 52 | force_lines = [] 53 | for u, v in force.edges(): 54 | force_lines.append({"start": force.vertex_coordinates(u, "xy"), "end": force.vertex_coordinates(v, "xy"), "width": 1.0, "color": "#cccccc", "style": "--"}) 55 | 56 | return form_lines, force_lines 57 | 58 | 59 | # ------------------------------------------------------------------------------ 60 | # 4. Constant force truss 61 | # - Input a non-triangulated truss, compute initial equilibrium 62 | # - Move the form diagram support 63 | # - Move one of the internal nodes to a specific position 64 | # ------------------------------------------------------------------------------ 65 | 66 | # ------------------------------------------------------------------------------ 67 | # 1. Get geometry, apply loads and and compute equilibrium 68 | # ------------------------------------------------------------------------------ 69 | 70 | input_file = compas_ags.get("paper/exD_truss.obj") 71 | 72 | graph = FormGraph.from_obj(input_file) 73 | form = FormDiagram.from_graph(graph) 74 | force = ForceDiagram.from_formdiagram(form) 75 | force_edges = force.ordered_edges(form) 76 | 77 | force_edge_labels1 = {(u, v): index for index, (u, v) in enumerate(force_edges)} 78 | force_edge_labels2 = {(v, u): index for index, (u, v) in enumerate(force_edges)} 79 | force_edge_labels = {**force_edge_labels1, **force_edge_labels2} 80 | 81 | # prescribe force density to edge 82 | edges_ind = [ 83 | (13, 14), 84 | ] 85 | for index in edges_ind: 86 | u, v = index 87 | form.edge_attribute((u, v), "is_ind", True) 88 | form.edge_attribute((u, v), "q", +2.0) 89 | 90 | index_edge = form.index_edge() 91 | 92 | # set the fixed corners 93 | fixed = [5, 1] 94 | 95 | for key in fixed: 96 | form.vertex_attribute(key, "is_fixed", True) 97 | 98 | # update the diagrams 99 | form_update_q_from_qind(form) 100 | force_update_from_form(force, form) 101 | 102 | # store lines representing the current state of equilibrium 103 | form_lines, force_lines = store_initial_lines(form, force) 104 | 105 | view_form_force(form, force, forcescale=1.0, edge_label=False) 106 | 107 | # ------------------------------------------------------------------------------ 108 | # 2. prescribe constraints on the form and update form and force 109 | # ------------------------------------------------------------------------------ 110 | 111 | # A. Fix orientation of edges at bottom chord 112 | edges_fix_orient = [13, 19, 17, 15, 4] 113 | 114 | for index in edges_fix_orient: 115 | edge = index_edge[index] 116 | sp, ep = form.edge_coordinates(edge) 117 | dx = ep[0] - sp[0] 118 | dy = ep[1] - sp[1] 119 | length = (dx**2 + dy**2) ** 0.5 120 | form.edge_attribute(edge, "target_vector", [dx / length, dy / length]) 121 | 122 | # B. Assign forces on the top chord to have the same length 123 | index_edges_constant_force = [9, 7, 5, 0, 1] 124 | L = 5.0 125 | 126 | for index in index_edges_constant_force: 127 | form.edge_attribute(index_edge[index], "target_force", L) 128 | 129 | # C. Guarantee constant force application 130 | index_edges_constant_load = [20, 18, 16, 14] 131 | load = 2.0 132 | 133 | for index in index_edges_constant_load: 134 | form.edge_attribute(index_edge[index], "target_force", load) 135 | 136 | # Identify auto constraints 137 | form.identify_constraints() 138 | 139 | # Reflect all constraints to force diagram 140 | force.constraints_from_dual() 141 | 142 | update_diagrams_from_constraints(form, force) 143 | 144 | view_with_initial_stage(form, force, forcescale=1.0, edge_label=False) 145 | -------------------------------------------------------------------------------- /data/paper/3hinged.obj: -------------------------------------------------------------------------------- 1 | # Rhino 2 | 3 | v 0 0 0 4 | v 1.210834893407057 4.090658423672486 0 5 | cstype bspline 6 | deg 1 7 | curv 0 4.266099797034141 1 2 8 | parm u 0 0 4.266099797034141 4.266099797034141 9 | end 10 | v 1.210834893407057 4.090658423672486 0 11 | v 4.319735295398147 7.003207221327296 0 12 | cstype bspline 13 | deg 1 14 | curv 4.266099797034141 8.526170478163838 3 4 15 | parm u 4.266099797034141 4.266099797034141 8.526170478163838 8.526170478163838 16 | end 17 | v 4.319735295398147 7.003207221327296 0 18 | v 8.443118986460011 8.115866312566212 0 19 | cstype bspline 20 | deg 1 21 | curv 8.526170478163838 12.79703728913226 5 6 22 | parm u 8.526170478163838 8.526170478163838 12.79703728913226 12.79703728913226 23 | end 24 | v 8.443118986460011 8.115866312566212 0 25 | v 12.76285428185816 7.919514708229933 0 26 | cstype bspline 27 | deg 1 28 | curv 12.79703728913226 17.12123281834159 7 8 29 | parm u 12.79703728913226 12.79703728913226 17.12123281834159 17.12123281834159 30 | end 31 | v 12.76285428185816 7.919514708229933 0 32 | v 17.86799599460142 8.83582219513257 0 33 | cstype bspline 34 | deg 1 35 | curv 17.12123281834159 22.3079554172228 9 10 36 | parm u 17.12123281834159 17.12123281834159 22.3079554172228 22.3079554172228 37 | end 38 | v 17.86799599460142 8.83582219513257 0 39 | v 22.7767861030084 7.265009360442335 0 40 | cstype bspline 41 | deg 1 42 | curv 22.3079554172228 27.46195127383947 11 12 43 | parm u 22.3079554172228 22.3079554172228 27.46195127383947 27.46195127383947 44 | end 45 | v 22.7767861030084 7.265009360442335 0 46 | v 25.42753276154818 2.945274065044189 0 47 | cstype bspline 48 | deg 1 49 | curv 27.46195127383947 32.53014334498952 13 14 50 | parm u 27.46195127383947 27.46195127383947 32.53014334498952 32.53014334498952 51 | end 52 | v 25.42753276154818 2.945274065044189 0 53 | v 19.40608356190227 6.152350269203419 0 54 | cstype bspline 55 | deg 1 56 | curv 32.53014334498952 39.35240017416274 15 16 57 | parm u 32.53014334498952 32.53014334498952 39.35240017416274 39.35240017416274 58 | end 59 | v 8.116529121669164 5.98790919393003 0 60 | v 3.800848486900561 3.346970298026856 0 61 | cstype bspline 62 | deg 1 63 | curv 51.24476332363906 56.30437374275346 17 18 64 | parm u 51.24476332363906 51.24476332363906 56.30437374275346 56.30437374275346 65 | end 66 | v 3.800848486900561 3.346970298026856 0 67 | v 0 0 0 68 | cstype bspline 69 | deg 1 70 | curv 56.30437374275346 61.36882429500967 19 20 71 | parm u 56.30437374275346 56.30437374275346 61.36882429500967 61.36882429500967 72 | end 73 | v 19.40608356190227 6.152350269203419 0 74 | v 12.76285428185816 7.919514708229933 0 75 | cstype bspline 76 | deg 1 77 | curv 39.35240017416274 46.20889849964632 21 22 78 | parm u 39.35240017416274 39.35240017416274 46.20889849964632 46.20889849964632 79 | end 80 | v 12.76285428185816 7.919514708229933 0 81 | v 8.116529121669164 5.98790919393003 0 82 | cstype bspline 83 | deg 1 84 | curv 46.20889849964632 51.24476332363906 23 24 85 | parm u 46.20889849964632 46.20889849964632 51.24476332363906 51.24476332363906 86 | end 87 | v 3.800848486900561 3.346970298026856 0 88 | v 1.210834893407057 4.090658423672486 0 89 | cstype bspline 90 | deg 1 91 | curv 0 2.694669264067011 25 26 92 | parm u 0 0 2.694669264067011 2.694669264067011 93 | end 94 | v 3.800848486900561 3.346970298026856 0 95 | v 4.319735295398147 7.003207221327296 0 96 | cstype bspline 97 | deg 1 98 | curv 0 3.692873130685411 27 28 99 | parm u 0 0 3.692873130685411 3.692873130685411 100 | end 101 | v 8.116529121669164 5.98790919393003 0 102 | v 4.319735295398147 7.003207221327296 0 103 | cstype bspline 104 | deg 1 105 | curv 0 3.930200178571886 29 30 106 | parm u 0 0 3.930200178571886 3.930200178571886 107 | end 108 | v 8.116529121669164 5.98790919393003 0 109 | v 8.443118986460011 8.115866312566212 0 110 | cstype bspline 111 | deg 1 112 | curv 0 2.152873066053479 31 32 113 | parm u 0 0 2.152873066053479 2.152873066053479 114 | end 115 | v 19.40608356190227 6.152350269203419 0 116 | v 17.86799599460142 8.83582219513257 0 117 | cstype bspline 118 | deg 1 119 | curv 0 3.093013892942506 33 34 120 | parm u 0 0 3.093013892942506 3.093013892942506 121 | end 122 | v 19.40608356190227 6.152350269203419 0 123 | v 22.7767861030084 7.265009360442335 0 124 | cstype bspline 125 | deg 1 126 | curv 0 3.549597987650986 35 36 127 | parm u 0 0 3.549597987650986 3.549597987650986 128 | end 129 | v 0 0 0 130 | v 0 -1.820677625437187 0 131 | cstype bspline 132 | deg 1 133 | curv 0 1.820677625437187 37 38 134 | parm u 0 0 1.820677625437187 1.820677625437187 135 | end 136 | v 0 0 0 137 | v -2.128903134782259 0 0 138 | cstype bspline 139 | deg 1 140 | curv 0 2.128903134782259 39 40 141 | parm u 0 0 2.128903134782259 2.128903134782259 142 | end 143 | v 25.42753276154818 2.945274065044189 0 144 | v 27.27975341847735 2.945274065044189 0 145 | cstype bspline 146 | deg 1 147 | curv 0 1.852220656929176 41 42 148 | parm u 0 0 1.852220656929176 1.852220656929176 149 | end 150 | v 25.42753276154818 2.945274065044189 0 151 | v 25.42753276154818 1.038271145757675 0 152 | cstype bspline 153 | deg 1 154 | curv 0 1.907002919286514 43 44 155 | parm u 0 0 1.907002919286514 1.907002919286514 156 | end 157 | v 1.210834893407057 4.090658423672486 0 158 | v 1.210834893407057 6.120303089630128 0 159 | cstype bspline 160 | deg 1 161 | curv 0 2.029644665957642 45 46 162 | parm u 0 0 2.029644665957642 2.029644665957642 163 | end 164 | v 4.319735295398147 7.003207221327296 0 165 | v 4.319735295398147 9.13270021851881 0 166 | cstype bspline 167 | deg 1 168 | curv 0 2.129492997191514 47 48 169 | parm u 0 0 2.129492997191514 2.129492997191514 170 | end 171 | v 8.443118986460011 8.115866312566212 0 172 | v 8.443118986460011 10.11401140444467 0 173 | cstype bspline 174 | deg 1 175 | curv 0 1.998145091878455 49 50 176 | parm u 0 0 1.998145091878455 1.998145091878455 177 | end 178 | v 17.86799599460142 8.83582219513257 0 179 | v 17.86799599460142 10.90126078629827 0 180 | cstype bspline 181 | deg 1 182 | curv 0 2.065438591165699 51 52 183 | parm u 0 0 2.065438591165699 2.065438591165699 184 | end 185 | v 22.7767861030084 7.265009360442335 0 186 | v 22.7767861030084 8.904863052698406 0 187 | cstype bspline 188 | deg 1 189 | curv 0 1.639853692256072 53 54 190 | parm u 0 0 1.639853692256072 1.639853692256072 191 | end 192 | --------------------------------------------------------------------------------