├── html ├── js │ ├── index.html │ └── faster-than-quick ├── img │ └── logo.svg ├── new │ ├── img │ │ └── logo.svg │ ├── index.php │ ├── preview.php │ ├── problems.php │ ├── process.php │ ├── import_cad.php │ └── create.php ├── css │ └── faster-than-quick ├── assets │ └── named_cube.x3d ├── .htaccess ├── cad.php ├── mesh_log.php ├── expert.php ├── index.php ├── mesh.php ├── meshing.php ├── problem.php ├── results.php ├── solving.php ├── change_step.php ├── properties.php ├── ajax2mesh.php ├── mesh_data.php ├── ajax2problem.php ├── mesh_graph.php ├── problem_fee.php ├── results_data.php ├── mesh_inp_save.php ├── mesh_inp_show.php ├── meshing_status.php ├── solving_status.php ├── meshing_relaunch.php ├── problem_fee_save.php ├── solving_relaunch.php ├── mesh_msh.php ├── results_vtk.php ├── meshing_cancel.php ├── solving_cancel.php ├── ajax2yaml.php ├── common.php └── case.php ├── .gitignore ├── data └── .gitignore ├── meshers ├── gmsh │ ├── LICENSE │ ├── default0.geo │ ├── .gitignore │ ├── default1.geo │ ├── default2.geo │ ├── default3.geo │ ├── default5.geo │ ├── default4.geo │ ├── quality.gp │ ├── meshing_relaunch.php │ ├── deps.sh │ ├── mesh_log.php │ ├── mesh_inp_show.php │ ├── mesh_graph.php │ ├── mesh_data.php │ ├── mesh_inp_save.php │ ├── quality.py │ ├── cadmesh.py │ ├── process_step.php │ ├── mesh_status.sh │ ├── meshing_status.php │ ├── mesh.sh │ ├── trymesh.py │ ├── ajax2mesh.php │ └── mesh_data.py └── README.md ├── solvers ├── ccx │ ├── LICENSE │ ├── problem_fee.php │ ├── ajax2problem.php │ ├── results_data.php │ ├── problem_fee_save.php │ ├── input_initial_mechanical.php │ ├── problems.php │ ├── deps.sh │ ├── change_step_solve.php │ ├── frd2vtk.fee │ ├── solve_status.sh │ ├── common.php │ ├── solving_status.php │ └── solve.sh ├── feenox │ ├── LICENSE │ ├── field1.fee │ ├── second2first.fee │ ├── input_initial_heat_conduction.php │ ├── field.fee │ ├── input_initial_mechanical.php │ ├── results_data.php │ ├── deps.sh │ ├── change_step_solve.php │ ├── solving_relaunch.php │ ├── problems.php │ ├── problem_fee.php │ ├── results_data │ │ ├── heat_conduction.php │ │ └── mechanical.php │ ├── displacements.fee │ ├── problem_fee_save.php │ ├── solve_status.sh │ ├── solving_status.php │ ├── common.php │ └── solve.sh ├── sparselizard │ ├── LICENSE │ └── problems.php ├── README.md ├── common.php └── problems.php ├── uxs ├── .gitignore └── faster-than-quick │ ├── css │ ├── index.html │ ├── .gitignore │ └── ftq.css │ ├── js │ ├── index.html │ ├── .gitignore │ ├── heat_conduction.js │ └── mechanical.js │ ├── bootswatch │ ├── .gitignore │ ├── make.sh │ ├── _variables.scss │ └── _bootswatch.scss │ ├── labels │ ├── .gitignore │ ├── labels.sh │ ├── README.md │ ├── labels.awk │ └── labels.txt │ ├── results │ ├── heat_conduction.php │ └── mechanical.php │ ├── mesh.php │ ├── problem.php │ ├── preview.php │ ├── about.php │ ├── cad.php │ ├── share.php │ ├── solving.php │ ├── expert.php │ ├── meshing.php │ ├── deps.sh │ ├── change_step.php │ ├── importers │ └── upload.php │ ├── properties.php │ ├── small_axes.html │ └── results.php ├── .dockerignore ├── renderers └── x3dom │ ├── LICENSE │ ├── .gitignore │ └── deps.sh ├── auths ├── htpasswd │ ├── README.md │ ├── .htpasswd │ ├── .htaccess │ └── auth.php └── single-user │ └── auth.php ├── bin └── .gitignore ├── cadprocessors └── gmsh │ ├── initial_mesh.sh │ ├── cadcheck.py │ ├── cad2stl.py │ └── process.php ├── cadimporters └── upload │ ├── gmshcheck.py │ └── import_cad.php ├── doc ├── README.md ├── virtual.md ├── workflow.md ├── FAQs.md ├── design.md ├── logo.svg └── INSTALL.md ├── Dockerfile ├── conf.php ├── deps.sh ├── LICENSES.md └── TODO.md /html/js/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | deps 2 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /meshers/gmsh/LICENSE: -------------------------------------------------------------------------------- 1 | GPLv2+ -------------------------------------------------------------------------------- /meshers/gmsh/default0.geo: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /solvers/ccx/LICENSE: -------------------------------------------------------------------------------- 1 | GPLv2+ -------------------------------------------------------------------------------- /solvers/feenox/LICENSE: -------------------------------------------------------------------------------- 1 | GPLv3+ -------------------------------------------------------------------------------- /uxs/.gitignore: -------------------------------------------------------------------------------- 1 | caeplex 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /renderers/x3dom/LICENSE: -------------------------------------------------------------------------------- 1 | MIT/GPLv3+ -------------------------------------------------------------------------------- /html/img/logo.svg: -------------------------------------------------------------------------------- 1 | ../../doc/logo.svg -------------------------------------------------------------------------------- /solvers/sparselizard/LICENSE: -------------------------------------------------------------------------------- 1 | GPLv2+ -------------------------------------------------------------------------------- /uxs/faster-than-quick/css/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/js/index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /html/new/img/logo.svg: -------------------------------------------------------------------------------- 1 | ../../img/logo.svg -------------------------------------------------------------------------------- /renderers/x3dom/.gitignore: -------------------------------------------------------------------------------- 1 | x3dom.css 2 | x3dom.js 3 | -------------------------------------------------------------------------------- /solvers/ccx/problem_fee.php: -------------------------------------------------------------------------------- 1 | ../feenox/problem_fee.php -------------------------------------------------------------------------------- /html/css/faster-than-quick: -------------------------------------------------------------------------------- 1 | ../../uxs/faster-than-quick/css/ -------------------------------------------------------------------------------- /html/js/faster-than-quick: -------------------------------------------------------------------------------- 1 | ../../uxs/faster-than-quick/js -------------------------------------------------------------------------------- /solvers/ccx/ajax2problem.php: -------------------------------------------------------------------------------- 1 | ../feenox/ajax2problem.php -------------------------------------------------------------------------------- /solvers/ccx/results_data.php: -------------------------------------------------------------------------------- 1 | ../feenox/results_data.php -------------------------------------------------------------------------------- /auths/htpasswd/README.md: -------------------------------------------------------------------------------- 1 | Explain how to setup httpasswd. 2 | -------------------------------------------------------------------------------- /meshers/gmsh/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | gmsh* 3 | libgmsh* 4 | -------------------------------------------------------------------------------- /meshers/gmsh/default1.geo: -------------------------------------------------------------------------------- 1 | Mesh.MeshSizeFromCurvature = 5; 2 | -------------------------------------------------------------------------------- /solvers/ccx/problem_fee_save.php: -------------------------------------------------------------------------------- 1 | ../feenox/problem_fee_save.php -------------------------------------------------------------------------------- /html/assets/named_cube.x3d: -------------------------------------------------------------------------------- 1 | ../../uxs/faster-than-quick/named_cube.x3d -------------------------------------------------------------------------------- /uxs/faster-than-quick/js/.gitignore: -------------------------------------------------------------------------------- 1 | bootstrap*.js 2 | x3dom*.js 3 | -------------------------------------------------------------------------------- /auths/htpasswd/.htpasswd: -------------------------------------------------------------------------------- 1 | pepe:$apr1$vATa7bcq$EBEVaET9ke0EWE6zs4P4Q1 2 | -------------------------------------------------------------------------------- /meshers/README.md: -------------------------------------------------------------------------------- 1 | # Meshers 2 | 3 | * [Gmsh](http://gmsh.info/) (GPLv2+) 4 | -------------------------------------------------------------------------------- /solvers/ccx/input_initial_mechanical.php: -------------------------------------------------------------------------------- 1 | ../feenox/input_initial_mechanical.php -------------------------------------------------------------------------------- /meshers/gmsh/default2.geo: -------------------------------------------------------------------------------- 1 | Mesh.MeshSizeFromCurvature = 10; 2 | Mesh.Algorithm = 1; 3 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/bootswatch/.gitignore: -------------------------------------------------------------------------------- 1 | bootswatch 2 | package-lock.json 3 | 4 | -------------------------------------------------------------------------------- /bin/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | feenox 3 | fee2ccx 4 | ccx 5 | gmsh* 6 | libgmsh* 7 | pandoc* 8 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/labels/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package.json 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/labels/labels.sh: -------------------------------------------------------------------------------- 1 | awk -f labels.awk labels.txt | nodejs > ../labels.php 2 | -------------------------------------------------------------------------------- /auths/htpasswd/.htaccess: -------------------------------------------------------------------------------- 1 | AuthType Basic 2 | AuthName "Restricted Content" 3 | AuthUserFile .htpasswd 4 | Require valid-user 5 | -------------------------------------------------------------------------------- /meshers/gmsh/default3.geo: -------------------------------------------------------------------------------- 1 | Mesh.MeshSizeFromCurvature = 12; 2 | Mesh.AlgorithmSwitchOnFailure = 1; 3 | Mesh.Algorithm = 2; 4 | -------------------------------------------------------------------------------- /cadprocessors/gmsh/initial_mesh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -e default.geo ]; then 4 | python ../../../../meshers/gmsh/cadmesh.py 5 | fi 6 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/css/.gitignore: -------------------------------------------------------------------------------- 1 | bootstrap-icons.min.css 2 | bootstrap-icons.css 3 | x3dom.css 4 | fonts 5 | katex.min.css 6 | katex.css 7 | -------------------------------------------------------------------------------- /cadimporters/upload/gmshcheck.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append("../../../../bin") 4 | import gmsh 5 | 6 | gmsh.initialize() 7 | gmsh.finalize() 8 | -------------------------------------------------------------------------------- /html/.htaccess: -------------------------------------------------------------------------------- 1 | # AuthType Basic 2 | # AuthName "Restricted Content" 3 | # AuthUserFile /home/gtheler/codigos/suncae/auths/htpasswd/.htpasswd 4 | # Require valid-user 5 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # SunCAE documentation 2 | 3 | * [Installation guide](INSTALL.md) 4 | * [Tutorials](tutorials.md) 5 | * [FAQs](FAQs.md) 6 | * [Explanation of the workflow](workflow.md) 7 | -------------------------------------------------------------------------------- /meshers/gmsh/default5.geo: -------------------------------------------------------------------------------- 1 | Mesh.MeshSizeFromCurvature = 20; 2 | Mesh.MeshSizeExtendFromBoundary = 1; 3 | Mesh.MinimumLineNodes = 8; 4 | Mesh.Algorithm = 2; 5 | Mesh.MeshSizeFactor = 0.5; 6 | -------------------------------------------------------------------------------- /meshers/gmsh/default4.geo: -------------------------------------------------------------------------------- 1 | Mesh.MeshSizeFromCurvature = 16; 2 | Mesh.MeshSizeExtendFromBoundary = 1; 3 | Mesh.AlgorithmSwitchOnFailure = 1; 4 | Mesh.MinimumLineNodes = 8; 5 | Mesh.Algorithm = 2; 6 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/labels/README.md: -------------------------------------------------------------------------------- 1 | Edit `labels.txt` and run 2 | 3 | ``` 4 | ./labels.sh 5 | ``` 6 | 7 | Rembember to install KaTeX: 8 | 9 | ``` 10 | npm install katex 11 | ``` 12 | 13 | -------------------------------------------------------------------------------- /doc/virtual.md: -------------------------------------------------------------------------------- 1 | # Setting up virtual machines to host SunCAE 2 | 3 | ## Vagrant 4 | 5 | ```terminal 6 | vagrant init debian/bookworm64 7 | vagrant up 8 | vagrant ssh 9 | ``` 10 | 11 | ## Docker 12 | 13 | TBD 14 | -------------------------------------------------------------------------------- /meshers/gmsh/quality.gp: -------------------------------------------------------------------------------- 1 | set style fill transparent solid 0.50 noborder 2 | set style function filledcurves y1=0 3 | plot "data" u (($1+0.5)/10):3 with boxes, "data" u (($1+0.5)/10):4 with boxes, "data" u (($1+0.5)/10):2 with boxes 4 | -------------------------------------------------------------------------------- /solvers/README.md: -------------------------------------------------------------------------------- 1 | # Solvers 2 | 3 | * [FeenoX](https://www.seamplex.com/feenox) (GPLv3+) 4 | * [CalculiX](http://calculix.de/) (GPLv3+)---through the [`fee2ccx`](https://github.com/seamplex/feenox/tree/main/utils/fee2ccx) converter 5 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/labels/labels.awk: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | print "const katex = require(\"katex\");" 3 | print "console.log(\" bash 14 | -------------------------------------------------------------------------------- /solvers/feenox/input_initial_heat_conduction.php: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /solvers/feenox/input_initial_mechanical.php: -------------------------------------------------------------------------------- 1 | run/{$problem_hash}-check.2", $output, $result); 9 | if ($result == 0) { 10 | exec("../../../../solvers/ccx/solve.sh {$problem} > run/{$problem_hash}-solve.log 2>&1 & echo $! > run/solving.pid"); 11 | $results_meta["status"] = "running"; 12 | suncae_log("{$id} problem running"); 13 | } else { 14 | $results_meta["status"] = "syntax_error"; 15 | suncae_log("{$id} problem syntax error"); 16 | } 17 | 18 | ?> 19 | -------------------------------------------------------------------------------- /solvers/feenox/change_step_solve.php: -------------------------------------------------------------------------------- 1 | run/{$problem_hash}-check.2", $output, $result); 9 | if ($result == 0) { 10 | exec("../../../../solvers/feenox/solve.sh {$problem} > run/{$problem_hash}-solve.log 2>&1 & echo $! > run/solving.pid"); 11 | $results_meta["status"] = "running"; 12 | suncae_log("{$id} problem running"); 13 | } else { 14 | $results_meta["status"] = "syntax_error"; 15 | suncae_log("{$id} problem syntax error"); 16 | } 17 | 18 | ?> 19 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/js/heat_conduction.js: -------------------------------------------------------------------------------- 1 | color["bc_1"] = [0.60, 0.30, 0.90]; 2 | color["bc_2"] = [1.00, 0.33, 0.33]; 3 | color["bc_3"] = [0.67, 0.77, 0.37]; 4 | color["bc_4"] = [0.16, 0.83, 1.00]; 5 | color["bc_5"] = [0.40, 1.00, 0.40]; 6 | color["bc_6"] = [1.00, 0.90, 0.50]; 7 | color["bc_7"] = [0.40, 0.80, 0.85]; 8 | color["bc_8"] = [1.00, 0.00, 0.40]; 9 | color["bc_9"] = [0.16, 0.83, 0.00]; 10 | color["bc_10"] = [1.00, 0.40, 0.00]; 11 | 12 | function bc_hide_all(i) { 13 | bootstrap_hide("bc_value_"+i+"_custom"); 14 | bootstrap_hide("bc_value_"+i+"_temperature"); 15 | bootstrap_hide("bc_value_"+i+"_heatflux"); 16 | bootstrap_hide("bc_value_"+i+"_convection"); 17 | } 18 | -------------------------------------------------------------------------------- /html/mesh_log.php: -------------------------------------------------------------------------------- 1 | /'"); 8 | $response["plain"] = shell_exec("tail -n+2 case.fee"); 9 | $response["html"] = shell_exec("cat << EOF | ../../../../bin/pandoc -t html --syntax-definition=../../../../solvers/feenox/feenox.xml 10 | ~~~feenox 11 | $(cat case.fee) 12 | ~~~ 13 | EOF"); 14 | 15 | return_back_json($response); 16 | -------------------------------------------------------------------------------- /solvers/feenox/results_data/heat_conduction.php: -------------------------------------------------------------------------------- 1 | 0, 0.1 * ${2} / displ_max, 1) 11 | 12 | sigma_max = vecmax(vec_sigma) 13 | 14 | OUTPUT_FILE max PATH ${1}-max.json 15 | # TODO: implement PRINTF with FILE 16 | PRINT FILE max "\{" 17 | PRINT FILE max " \"max_displacement\": " displ_max "," 18 | PRINT FILE max " \"max_warp\":" warp_max "," 19 | PRINT FILE max " \"max_sigma\":" sigma_max 20 | PRINT FILE max "\}" 21 | 22 | VECTOR dxv[nodes] 23 | VECTOR dyv[nodes] 24 | VECTOR dzv[nodes] 25 | 26 | dxv[i] = vec_u_x[i] + warp_max*vec_u[i] 27 | dyv[i] = vec_u_y[i] + warp_max*vec_v[i] 28 | dzv[i] = vec_u_z[i] + warp_max*vec_w[i] 29 | 30 | PRINT_VECTOR dxv dyv dzv 31 | 32 | 33 | -------------------------------------------------------------------------------- /meshers/gmsh/mesh_graph.php: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /meshers/gmsh/mesh_inp_save.php: -------------------------------------------------------------------------------- 1 | &1", $output, $result); 17 | if ($result != 0) { 18 | $response["status"] = "error"; 19 | for ($i = 0; $i < count($output); $i++) { 20 | if (strncmp("Error", $output[$i], 5) == 0) { 21 | $response["error"] .= $output[$i] . "
"; 22 | } 23 | } 24 | } 25 | 26 | return_back_json($response); 27 | -------------------------------------------------------------------------------- /renderers/x3dom/deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/false 2 | 3 | # x3dom_version=1.8.3 4 | x3dom_version=1.8.4-dev 5 | 6 | echo -n "renderers/x3dom: x3dom.js & x3dom.css... " 7 | if [ $force = 1 ] || [ ! -e renderers/x3dom/x3dom.js ]; then 8 | cd deps 9 | x3dom_tarball=x3dom-${x3dom_version} 10 | 11 | # if [ ! -e ${x3dom_tarball}.zip ]; then 12 | # wget -c https://www.x3dom.org/download/${x3dom_version}/${x3dom_tarball}.zip 13 | # wget -c https://www.x3dom.org/download/dev/${x3dom_tarball}.zip 14 | # fi 15 | # if [ ! -d x3dom ]; then 16 | # mkdir -p x3dom 17 | # unzip ${x3dom_tarball}.zip -d x3dom 18 | # fi 19 | # cp x3dom/x3dom.js ../renderers/x3dom 20 | # cp x3dom/x3dom.css ../renderers/x3dom 21 | 22 | wget -c https://andreasplesch.github.io/x3dom/dist/x3dom.js 23 | cp x3dom.js ../renderers/x3dom 24 | wget -c https://andreasplesch.github.io/x3dom/dist/x3dom.css 25 | cp x3dom.css ../renderers/x3dom 26 | 27 | cd ../uxs/faster-than-quick/js 28 | if [ ! -e x3dom.js ]; then 29 | ln -s ../../../renderers/x3dom/x3dom.js 30 | fi 31 | cd ../css 32 | if [ ! -e x3dom.css ]; then 33 | ln -s ../../../renderers/x3dom/x3dom.css 34 | fi 35 | echo "done" 36 | cd ../../.. 37 | else 38 | echo "already installed" 39 | fi 40 | -------------------------------------------------------------------------------- /solvers/feenox/results_data/mechanical.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | | Type | Name | Repository | License | 7 | |:----------------|:--------------|:-------------------------------------------|:-------------:| 8 | | Renderer | X3DOM | | MIT/GPLv3+ | 9 | | UX | Bootstrap | | MIT | 10 | | CAD importer | OpenCASCADE | | LGPL-2.1 | 11 | | Mesher | Gmsh | | GPLv2+ | 12 | | Solver | FeenoX | | GPLv3+ | 13 | | Solver | CalculiX | N/A | GPLv2+ | 14 | 15 | Other dependencies (i.e. new solvers, new meshers) can be added as long as their respective licenses are respected. 16 | 17 | -------------------------------------------------------------------------------- /doc/workflow.md: -------------------------------------------------------------------------------- 1 | # New case 2 | 3 | #. if someone goes to the root `index.php` (at `html/index.php`) without and `id` or directly to `new/` then the "new case" page at `new/index.php` is shown 4 | #. that one includes `uxs/$ux/new.php` 5 | 6 | ## Faster-than-quick UX 7 | 8 | #. `uxs/faster-than-quick/new.php` shows the four choices 9 | 10 | 2. Physics 11 | 3. Problem 12 | 4. Solver 13 | 5. Mesher 14 | 15 | But the first one, which is the CAD, comes from `uxs/faster-than-quick/importers/$cadimporter.php` 16 | 17 | ## Upload-importer 18 | 19 | #. `uxs/faster-than-quick/importers/upload.php` shows a file upload HTML component with an onchange call to `upload_cad_file()` that after uploading the CAD, calls `process_cad()`. 20 | #. `process_cad()` in `uxs/faster-than-quick/new.php` calls `html/new/process.php` which includes `cadprocessors/$cadprocessor/process.php` 21 | 22 | ## Gmsh-processor 23 | 24 | #. `cadprocessors/gmsh/process.php` uses `cadimport.py` to process the uploaded CAD with Gmsh (through OCC) to create a `.xao` 25 | #. It calls `initial_mesh.sh` in background to create the first mesh. 26 | #. This `initial_mesh.sh` just calls `cadmesh.py` if `default.geo` does not exit. 27 | #. `cadmesh.py` performs five attempts to create an initial mesh by calling `trymesh.py` with the number of attempt $i$ as the argument 28 | #. `trymesh.py` merges `defaulti.geo` and then tries to come up with a max $\ell_c$ 29 | #. 30 | 31 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/bootswatch/make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # https://bootswatch.com/help/#customization 4 | 5 | # * Download the repository: git clone https://github.com/thomaspark/bootswatch.git 6 | # * Install dependencies: npm install 7 | # * Make sure that you have grunt available in the command line. You can install grunt-cli as described on the Grunt Getting Started page. 8 | # * In /dist, modify _variables.scss and _bootswatch.scss in one of the theme directories, or duplicate a theme directory to create a new one. 9 | # * Type grunt swatch:[theme] to build the CSS for a theme, e.g., grunt swatch:flatly for Flatly. Or type grunt swatch to build them all at once. 10 | # * You can run grunt to start a server, watch for any changes to the SASS files, and automatically build a theme and reload it on change. Run grunt server for just the server, and grunt watch for just the watcher. 11 | 12 | if [ ! -d bootswatch ]; then 13 | git clone https://github.com/thomaspark/bootswatch.git 14 | cd bootswatch 15 | else 16 | cd bootswatch 17 | git pull 18 | fi 19 | 20 | if [ -z "$(which npm)" ]; then 21 | sudo apt-get install npm 22 | fi 23 | if [ -z "$(which grunt)" ]; then 24 | sudo apt-get install grunt 25 | fi 26 | 27 | npm install 28 | # npm audit fix 29 | mkdir -p dist/faster-than-quick 30 | cp ../_bootswatch.scss ../_variables.scss dist/faster-than-quick/ 31 | grunt swatch:faster-than-quick 32 | cp dist/faster-than-quick/bootstrap.min.css ../../css/ 33 | 34 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/about.php: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/js/mechanical.js: -------------------------------------------------------------------------------- 1 | color["bc_1"] = [0.82, 0.22, 0.68]; 2 | color["bc_2"] = [0.37, 0.85, 0.16]; 3 | color["bc_3"] = [0.93, 0.91, 0.26]; 4 | color["bc_4"] = [0.33, 0.86, 1.00]; 5 | color["bc_5"] = [1.00, 0.66, 0.66]; 6 | color["bc_6"] = [1.00, 0.50, 0.50]; 7 | color["bc_7"] = [0.00, 0.80, 1.00]; 8 | color["bc_8"] = [0.55, 0.37, 0.82]; 9 | color["bc_9"] = [0.00, 1.00, 0.80]; 10 | color["bc_10"] = [1.00, 0.40, 0.00]; 11 | 12 | color["bc_10"] = [0.10, 0.25, 0.50]; 13 | color["bc_11"] = [1.00, 0.80, 0.16]; 14 | color["bc_12"] = [0.75, 0.15, 0.90]; 15 | color["bc_13"] = [0.55, 0.01, 0.22]; 16 | color["bc_14"] = [0.17, 0.38, 0.01]; 17 | color["bc_15"] = [0.78, 0.44, 0.21]; 18 | color["bc_16"] = [1.00, 0.25, 0.50]; 19 | color["bc_17"] = [0.25, 0.83, 0.60]; 20 | color["bc_18"] = [0.35, 0.75, 0.60]; 21 | color["bc_19"] = [0.21, 0.78, 0.78]; 22 | 23 | function bc_hide_all(i) { 24 | bootstrap_hide("bc_value_"+i+"_custom"); 25 | bootstrap_hide("bc_value_"+i+"_fixture"); 26 | bootstrap_hide("bc_value_"+i+"_displacement"); 27 | bootstrap_hide("bc_value_"+i+"_pressure"); 28 | bootstrap_hide("bc_value_"+i+"_force"); 29 | } 30 | 31 | function bc_fixture_update(i) { 32 | string = ""; 33 | if (document.getElementById("bc_"+i+"_fixture_u").checked) { 34 | string += "u=0 "; 35 | } 36 | if (document.getElementById("bc_"+i+"_fixture_v").checked) { 37 | string += "v=0 "; 38 | } 39 | if (document.getElementById("bc_"+i+"_fixture_w").checked) { 40 | string += "w=0 "; 41 | } 42 | 43 | ajax2problem("bc_"+i+"_value" , string); 44 | } 45 | -------------------------------------------------------------------------------- /cadprocessors/gmsh/cadcheck.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append("../../../../bin") 4 | import gmsh 5 | import os 6 | import math 7 | import json 8 | 9 | def main(): 10 | gmsh.initialize() 11 | gmsh.option.setNumber("General.Terminal", 0) 12 | 13 | try: 14 | gmsh.open("original.step") 15 | except: 16 | print("invalid STEP"); 17 | sys.exit(1) 18 | 19 | try: 20 | [xmin, ymin, zmin, xmax, ymax, zmax] = gmsh.model.getBoundingBox(-1, -1) 21 | 22 | except: 23 | print("invalid CAD"); 24 | sys.exit(2) 25 | 26 | orig = {} 27 | orig["xmin"] = xmin 28 | orig["xmax"] = xmax 29 | orig["ymin"] = ymin 30 | orig["ymax"] = ymax 31 | orig["zmin"] = zmin 32 | orig["zmax"] = zmax 33 | orig["max_length"] = math.sqrt(math.pow(xmax-xmin,2) + math.pow(ymax-ymin,2) + math.pow(zmax-zmin,2)) 34 | orig["max_delta"] = max(xmax-xmin, ymax-ymin, zmax-zmin) 35 | # orig["tolerance"] = 7e-5*orig["max_length"] if options.ext != "brep" else 0 36 | 37 | orig["solids"] = len(gmsh.model.getEntities(3)) 38 | orig["faces"] = len(gmsh.model.getEntities(2)) 39 | orig["edges"] = len(gmsh.model.getEntities(1)) 40 | orig["vertices"] = len(gmsh.model.getEntities(0)) 41 | 42 | # orig["format"] = options.ext 43 | orig["format"] = "step" 44 | orig["size"] = os.path.getsize("original.step") 45 | 46 | 47 | 48 | with open("original.json", "w") as fp: 49 | json.dump(orig, fp, indent=2) 50 | 51 | 52 | gmsh.finalize() 53 | sys.exit(0); 54 | 55 | 56 | if __name__ == "__main__": 57 | main() 58 | 59 | -------------------------------------------------------------------------------- /meshers/gmsh/quality.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append("../../../../bin") 4 | import gmsh 5 | import math 6 | 7 | gmsh.initialize(sys.argv) 8 | gmsh.option.setNumber("General.Terminal", 0) 9 | 10 | gmsh.open("mesh.msh") 11 | 12 | ## get element qualities: 13 | #_, eleTags , _ = gmsh.model.mesh.getElements(dim=3) 14 | #q = gmsh.model.mesh.getElementQualities(eleTags[0], "minSICN") 15 | #print(zip(eleTags[0], q)) 16 | 17 | gmsh.plugin.setNumber("AnalyseMeshQuality", "JacobianDeterminant", 1.) 18 | gmsh.plugin.setNumber("AnalyseMeshQuality", "IGEMeasure", 1.) 19 | gmsh.plugin.setNumber("AnalyseMeshQuality", "ICNMeasure", 1.) 20 | 21 | gmsh.plugin.setNumber("AnalyseMeshQuality", "CreateView", 1.) 22 | gmsh.plugin.run("AnalyseMeshQuality") 23 | 24 | dataType, tags, Jac, time, numComp = gmsh.view.getModelData(0, 0) 25 | dataType, tags, IGE, time, numComp = gmsh.view.getModelData(1, 0) 26 | dataType, tags, ICN, time, numComp = gmsh.view.getModelData(2, 0) 27 | 28 | N = 20 29 | hist_Jac = [0]*(N+1) 30 | hist_IGE = [0]*(N+1) 31 | hist_ICN = [0]*(N+1) 32 | 33 | 34 | for i in range(len(tags)): 35 | n_Jac = int(math.floor(Jac[i][0]*N)) 36 | n_IGE = int(math.floor(IGE[i][0]*N)) 37 | n_ICN = int(math.floor(ICN[i][0]*N)) 38 | #print(n_Jac, n_IGE, n_ICN) 39 | hist_Jac[n_Jac] += 1 40 | hist_IGE[n_IGE] += 1 41 | hist_ICN[n_ICN] += 1 42 | 43 | # for qualities = 1 they go to N and we move it to N-1 44 | hist_Jac[N-1] += hist_Jac[N] 45 | hist_IGE[N-1] += hist_IGE[N] 46 | hist_ICN[N-1] += hist_ICN[N] 47 | 48 | 49 | for j in range(N): 50 | print("{0}\t{1}\t{2}\t{3}".format(j,hist_Jac[j],hist_IGE[j],hist_ICN[j])) 51 | 52 | gmsh.finalize() 53 | -------------------------------------------------------------------------------- /html/meshing_cancel.php: -------------------------------------------------------------------------------- 1 | &1", $output, $result); 18 | if ($result != 0) { 19 | $response["status"] = "error"; 20 | for ($i = 0; $i < count($output); $i++) { 21 | // this authorization comes from openmpi 22 | if ($output[$i] != "" && strncasecmp($output[$i], "Authorization", 13) != 0) { 23 | if (strncmp("error", $output[$i], 5) == 0) { 24 | $output_exploded = explode(":", $output[$i]); 25 | for ($j = 2; $j < count($output_exploded); $j++) { 26 | $response["error"] .= $output_exploded[$j] ; 27 | } 28 | $response["error"] .= "
"; 29 | } else { 30 | $response["error"] .= $output[$i] . "
"; 31 | } 32 | } 33 | } 34 | } 35 | 36 | return_back_json($response); 37 | -------------------------------------------------------------------------------- /cadimporters/upload/import_cad.php: -------------------------------------------------------------------------------- 1 | attempt%d.log 2>&1" % (attempt, attempt)) 9 | if os.path.exists("attempt%d.json" % attempt): 10 | fp = open("attempt%d.json" % attempt) 11 | result = json.load(fp) 12 | fp.close() 13 | return result["result"] == "success" 14 | else: 15 | return False 16 | 17 | def main(): 18 | 19 | # write a basic default.geo and close it so it's available in case it's needed 20 | geo = open("default.geo", "w") 21 | geo.write("Merge \"../../cads/%s/cad.xao\";\n" % os.path.basename(os.path.normpath(os.getcwd()))) 22 | geo.close() 23 | 24 | # try to obtain a valid mesh 25 | i = 0; 26 | i_max = 5; 27 | while mesh(i) == False: 28 | print("%d-th attempt failed" % (i)) 29 | i += 1 30 | if i == i_max+1: 31 | print("max attempts reached") 32 | sys.exit(1) 33 | 34 | print("final success with attempt = %d" % i) 35 | 36 | # append what worked 37 | geo = open("default.geo", "a") 38 | if True: 39 | print(i) 40 | fp = open("attempt%d.json" % i) 41 | attempt = json.load(fp) 42 | fp.close() 43 | 44 | print(attempt) 45 | if attempt["lc"] != 0: 46 | lc = float(attempt["lc"]) 47 | geo.write("Mesh.MeshSizeMax = %g;\n" % (lc)); 48 | geo.write("Mesh.MeshSizeMin = %g;\n" % (1e-2*lc)); 49 | 50 | default = open("../../../../meshers/gmsh/default%d.geo" % i) 51 | geo.write(default.read()) 52 | default.close() 53 | geo.close() 54 | 55 | if os.path.exists("meshes") == False: 56 | os.system("../../../../meshers/gmsh/mesh.sh cad"); 57 | sys.exit(0) 58 | 59 | if __name__ == "__main__": 60 | main() 61 | -------------------------------------------------------------------------------- /meshers/gmsh/process_step.php: -------------------------------------------------------------------------------- 1 | 1) { 32 | $response["status"] = "error"; 33 | $response["error"] = "CAD file has {$original["solids"]} solids and this PoC works with single-solid CADs only."; 34 | } 35 | } else { 36 | $response["status"] = "error"; 37 | $response["show_preview"] = false; 38 | $response["error"] = "Cannot decode original json."; 39 | } 40 | 41 | } else { 42 | $response["status"] = "error"; 43 | $response["show_preview"] = false; 44 | $response["error"] = "Cannot create original json."; 45 | } 46 | 47 | suncae_log("CAD {$response["cad_hash"]} upload {$response["status"]} {$response["error"]}"); 48 | 49 | ?> 50 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/cad.php: -------------------------------------------------------------------------------- 1 | run/${problem_hash}-status.json 52 | { 53 | "status": "running", 54 | "pid": ${feenox_pid}, 55 | "mesh": ${mesh}, 56 | "build": ${build}, 57 | "solve": ${solve}, 58 | "post": ${post}, 59 | "data": ${data}, 60 | "done_mesh": ${done_mesh}, 61 | "done_build": ${done_build}, 62 | "done_solve": ${done_solve}, 63 | "done_post": ${done_post}, 64 | "done_data": ${done_data} 65 | } 66 | EOF 67 | # sync run/${problem_hash}.json 68 | 69 | else 70 | exit 0 71 | fi 72 | -------------------------------------------------------------------------------- /solvers/feenox/solve_status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "${1}" ]; then 4 | problem_hash=($(md5sum case.fee)) 5 | else 6 | problem_hash=${1} 7 | fi 8 | 9 | status=$(jq -r .status run/${problem_hash}.json) || exit 1 10 | if [ "x${status}" == "xrunning" ]; then 11 | feenox_pid=$(jq -r .pid run/${problem_hash}.json) 12 | echo $feenox_pid 13 | if [ -z "$(ps --no-headers --pid ${feenox_pid})" ]; then 14 | echo "solver pid ${feenox_pid} is not running" 15 | echo "TODO: check if it worked or not" 16 | exit 1 17 | fi 18 | 19 | mesh=0 20 | meshfile=$(grep MESH case.fee | awk '{print $4}' | head -n1) 21 | meshname=$(basename ${meshfile} .msh) 22 | if [ -e run/meshes/${meshname}.1 ]; then 23 | mesh=$(grep % run/meshes/${meshname}.1 | tr -d [] | awk '{print $3}' | tr -d % | tail -n1) 24 | fi 25 | 26 | logfile=run/${problem_hash}.1 27 | build=$(grep \\. ${logfile} | tr -d '\n' | wc -c) 28 | solve=$(grep \\- ${logfile} | tr -d '\n' | wc -c) 29 | post=$(grep = ${logfile} | tr -d '\n' | wc -c) 30 | data=50 31 | 32 | done_mesh=0 33 | if [ ${mesh} = 100 ]; then 34 | done_mesh=1 35 | fi 36 | done_build=0 37 | if [ ${build} = 100 ]; then 38 | done_build=1 39 | fi 40 | done_solve=0 41 | if [ ${solve} = 100 ]; then 42 | done_solve=1 43 | fi 44 | done_post=0 45 | if [ ${post} = 100 ]; then 46 | done_post=1 47 | fi 48 | 49 | done_data=0 50 | 51 | cat << EOF > run/${problem_hash}-status.json 52 | { 53 | "status": "running", 54 | "pid": ${feenox_pid}, 55 | "mesh": ${mesh}, 56 | "build": ${build}, 57 | "solve": ${solve}, 58 | "post": ${post}, 59 | "data": ${data}, 60 | "done_mesh": ${done_mesh}, 61 | "done_build": ${done_build}, 62 | "done_solve": ${done_solve}, 63 | "done_post": ${done_post}, 64 | "done_data": ${done_data} 65 | } 66 | EOF 67 | # sync run/${problem_hash}.json 68 | 69 | else 70 | exit 0 71 | fi 72 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/share.php: -------------------------------------------------------------------------------- 1 | 12 | 13 |
Share case
14 |
15 | 16 |
17 |
18 |

19 | 22 |

23 |
24 |
25 | choose email address 26 |
27 |
28 |
29 |
30 |

31 | 34 |

35 |
36 |
37 | Twitter, 38 | LinkedIn, 39 | etc 40 |
41 |
42 |
43 |
44 | 45 | -------------------------------------------------------------------------------- /meshers/gmsh/mesh_status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "${1}" ]; then 4 | mesh_hash=($(md5sum mesh.geo)) 5 | else 6 | mesh_hash=${1} 7 | fi 8 | 9 | status=$(jq -r .status run/meshes/${mesh_hash}.json) || exit 1 10 | if [ "x${status}" == "xrunning" ]; then 11 | gmsh_pid=$(jq -r .pid run/meshes/${mesh_hash}.json) 12 | echo $gmsh_pid 13 | if [ -z "$(ps --no-headers --pid ${gmsh_pid})" ]; then 14 | echo "mesher pid ${gmsh_pid} is not running" 15 | echo "TODO: check if it worked or not" 16 | exit 1 17 | fi 18 | 19 | logfile=run/meshes/${mesh_hash}.1 20 | edges=$(grep "Meshing curve" ${logfile} | tail -n1 | tr -d '[]' | awk '{print $6}') 21 | if [ -z "${edges}" ]; then 22 | edges="0" 23 | fi 24 | faces=$(grep "Meshing surface" ${logfile} | tail -n1 | tr -d '[]' | awk '{print $6}') 25 | if [ -z "${faces}" ]; then 26 | faces="0" 27 | fi 28 | volumes=$(grep "Meshing 3D..." ${logfile} | wc -l) 29 | 30 | done_edges=$(grep 'Done meshing 1D' ${logfile} | wc -l) 31 | done_faces=$(grep 'Done meshing 2D' ${logfile} | wc -l) 32 | done_volumes=$(grep 'Done meshing 3D' ${logfile} | wc -l) 33 | 34 | data=0 35 | datalogfile=run/meshes/${mesh_hash}-data.log 36 | if [ -e ${datalogfile} ]; then 37 | # data=$(cat ${datalogfile} | wc -l) 38 | data=$(tail -n1 ${datalogfile}) 39 | fi 40 | if [ ${data} = 3 ]; then 41 | done_data=1 42 | else 43 | done_data=0 44 | fi 45 | 46 | cat << EOF > run/meshes/${mesh_hash}-status.json 47 | { 48 | "status": "running", 49 | "pid": ${gmsh_pid}, 50 | "edges": ${edges}, 51 | "faces": ${faces}, 52 | "volumes": ${volumes}, 53 | "data": ${data}, 54 | "done_edges": ${done_edges}, 55 | "done_faces": ${done_faces}, 56 | "done_volumes": ${done_volumes}, 57 | "done_data": ${done_data}, 58 | "log": "$(tail -n5 run/meshes/${mesh_hash}.1 | cut -d: -f 2- | awk '{printf "%s\\n", $0}')" 59 | } 60 | EOF 61 | # sync run/meshes/${mesh_hash}.json 62 | 63 | else 64 | exit 0 65 | fi 66 | -------------------------------------------------------------------------------- /cadprocessors/gmsh/cad2stl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append("../../../../bin") 4 | import gmsh 5 | import os 6 | import math 7 | import random 8 | import colorsys 9 | import json 10 | 11 | def main(): 12 | gmsh.initialize() 13 | gmsh.option.setNumber("General.Terminal", 0) 14 | 15 | gmsh.open("original.step") 16 | 17 | solids = len(gmsh.model.getEntities(3)) 18 | faces = len(gmsh.model.getEntities(2)) 19 | edges = len(gmsh.model.getEntities(1)) 20 | vertices = len(gmsh.model.getEntities(0)) 21 | 22 | for i in range(vertices): 23 | gmsh.model.addPhysicalGroup(0, [1+i], 1+i) 24 | gmsh.model.setPhysicalName(0, 1+i, "vertex%d" % (1+i)) 25 | for i in range(edges): 26 | gmsh.model.addPhysicalGroup(1, [1+i], 1+i) 27 | gmsh.model.setPhysicalName(1, 1+i, "edge%d" % (1+i)) 28 | for i in range(faces): 29 | gmsh.model.addPhysicalGroup(2, [1+i], 1+i) 30 | gmsh.model.setPhysicalName(2, 1+i, "face%d" % (1+i)) 31 | for i in range(solids): 32 | gmsh.model.addPhysicalGroup(3, [1+i], 1+i) 33 | gmsh.model.setPhysicalName(3, 1+i, "solid%d" % (1+i)) 34 | 35 | gmsh.write("cad.xao") 36 | gmsh.finalize() 37 | 38 | # xao to x3d + json 39 | gmsh.initialize() 40 | gmsh.option.setNumber("General.Terminal", 0) 41 | gmsh.open("cad.xao") 42 | 43 | # bounding box global 44 | [xmin, ymin, zmin, xmax, ymax, zmax] = gmsh.model.getBoundingBox(-1, -1) 45 | 46 | # max_length 47 | max_length = math.sqrt(math.pow(xmax-xmin,2) + math.pow(ymax-ymin,2) + math.pow(zmax-zmin,2)) 48 | max_delta = max(xmax-xmin, ymax-ymin, zmax-zmin) 49 | 50 | # grabamos el stl 51 | gmsh.option.setNumber("Geometry.OCCBoundsUseStl", 1); 52 | gmsh.option.setNumber("Mesh.StlOneSolidPerSurface", 2) 53 | 54 | # TODO: elegir por commandline 55 | gmsh.option.setNumber("Mesh.StlLinearDeflection", 3e-3*max_delta); 56 | gmsh.option.setNumber("Mesh.StlLinearDeflectionRelative", 1e-2); 57 | gmsh.option.setNumber("Mesh.StlAngularDeflection", 1); 58 | 59 | gmsh.write("cad.stl") 60 | gmsh.write("cad.ply") 61 | gmsh.finalize() 62 | 63 | if __name__ == "__main__": 64 | main() 65 | 66 | -------------------------------------------------------------------------------- /solvers/ccx/common.php: -------------------------------------------------------------------------------- 1 | 56 | -------------------------------------------------------------------------------- /solvers/ccx/solving_status.php: -------------------------------------------------------------------------------- 1 | 51 | -------------------------------------------------------------------------------- /solvers/feenox/solving_status.php: -------------------------------------------------------------------------------- 1 | 51 | -------------------------------------------------------------------------------- /html/ajax2yaml.php: -------------------------------------------------------------------------------- 1 | SunCAE found a fatal error:"; 11 | echo $error; 12 | echo "

"; 13 | exit(); 14 | } 15 | 16 | 17 | function return_back_html($response) { 18 | header("Content-Type: text/html"); 19 | echo $response; 20 | exit(); 21 | } 22 | 23 | function return_error_html($error) { 24 | header("Content-Type: text/html"); 25 | echo $response; 26 | exit(); 27 | } 28 | 29 | function return_back_json($response) { 30 | header("Content-Type: application/json"); 31 | echo json_encode($response); 32 | exit(); 33 | } 34 | 35 | function return_error_json($error) { 36 | $response["error"] = $error; 37 | suncae_log($error); 38 | return_back_json($response); 39 | exit(); 40 | } 41 | 42 | // based on original work from the PHP Laravel framework 43 | if (!function_exists('str_contains')) { 44 | function str_contains($haystack, $needle) { 45 | return $needle !== '' && mb_strpos($haystack, $needle) !== false; 46 | } 47 | } 48 | 49 | function suncae_log($message) { 50 | global $permissions; 51 | global $username; 52 | $log_dir = __DIR__ . "/../data/logs/"; 53 | if (file_exists($log_dir) == false) { 54 | if (mkdir($log_dir, $permissions, true) == false) { 55 | echo "error: cannot create log directory"; 56 | exit(); 57 | } 58 | } 59 | $log = fopen($log_dir . date("Y-m-d").".log", "a"); 60 | if ($log === false) { 61 | echo "Cannot open log file, please check permissions."; 62 | exit(1); 63 | } 64 | fprintf($log, "%s %s\t%s: %s\n", date("c"), $_SERVER['REMOTE_ADDR'], $username, $message); 65 | fclose($log); 66 | } 67 | -------------------------------------------------------------------------------- /solvers/feenox/common.php: -------------------------------------------------------------------------------- 1 | 64 | -------------------------------------------------------------------------------- /cadprocessors/gmsh/process.php: -------------------------------------------------------------------------------- 1 | &1", __DIR__), $output, $error_level); 22 | 23 | // TODO: keep output 24 | if ($error_level != 0) { 25 | $response["status"] = "error"; 26 | $response["error"] = "Error {$error_level} when importing CAD: "; 27 | for ($i = 0; $i < count($output); $i++) { 28 | $response["error"] .= $output[$i]; 29 | } 30 | suncae_log("CAD {$cad_hash} process {$response["status"]} {$response["error"]}"); 31 | return_back_json($response); 32 | } 33 | } 34 | 35 | // ------------------------------------------------------------ 36 | if (file_exists("cad.json")) { 37 | $cad = json_decode(file_get_contents("cad.json"), true); 38 | $response["position"] = $cad["position"]; 39 | $response["orientation"] = $cad["orientation"]; 40 | $response["centerOfRotation"] = $cad["centerOfRotation"]; 41 | $response["fieldOfView"] = $cad["fieldOfView"]; 42 | 43 | } else { 44 | $response["status"] = "error"; 45 | $response["error"] = "Cannot create CAD json."; 46 | suncae_log("CAd {$cad_hash} process {$response["status"]} {$response["error"]}"); 47 | return_back_json($response); 48 | } 49 | 50 | 51 | // ------------------------------------------------------------ 52 | // leave running the mesher in the background 53 | exec("../../../../cadprocessors/gmsh/initial_mesh.sh > cadmesh.log 2>&1 &"); 54 | 55 | suncae_log("CAD {$cad_hash} process {$response["status"]} {$response["error"]}"); 56 | 57 | 58 | return_back_json($response); 59 | 60 | -------------------------------------------------------------------------------- /solvers/common.php: -------------------------------------------------------------------------------- 1 | run/${problem_hash}.json 25 | { 26 | "status": "running", 27 | "pid": $$ 28 | } 29 | EOF 30 | 31 | cad=$(grep ^cad case.yaml | cut -f2 -d: | tr -d " ") 32 | max_length=$(jq .max_length ../../cads/${cad}/cad.json) 33 | 34 | cp case.fee run/${problem_hash}.fee || exit 2 35 | cd run || exit 3 36 | 37 | # check the syntax 38 | ../../../../../bin/feenox -c ${problem_hash}.fee 1> ${problem_hash}.1 2> ${problem_hash}.2 39 | if [ $? -eq 0 ]; then 40 | # all good! 41 | # see if we have the second-order mesh 42 | if [ ! -e meshes/${mesh_hash}-2.msh ]; then 43 | ../../../../../bin/gmsh -3 meshes/${mesh_hash}.msh -order 2 -o meshes/${mesh_hash}-2.msh > meshes/${mesh_hash}-2.1 44 | fi 45 | 46 | # convert from .fee to .inp 47 | ../../../../../bin/fee2ccx ${problem_hash}.fee > ${problem_hash}.inp 48 | 49 | # run 50 | ../../../../../bin/ccx -i ${problem_hash} 1> ${problem_hash}.1 2> ${problem_hash}.2 51 | ccx_error=$? 52 | 53 | if [ $ccx_error -eq 0 ]; then 54 | if [ "x${problem_type}" = "xmechanical" ]; then 55 | ../../../../../bin/feenox ../../../../../solvers/ccx/frd2vtk.fee ${problem_hash} 56 | ../../../../../bin/feenox ../../../../../solvers/feenox/second2first.fee ${problem_hash} ${mesh_hash} 57 | ../../../../../bin/feenox ../../../../../solvers/feenox/displacements.fee ${problem_hash} ${max_length} | tr -s ' \t\n' ' ' > ${problem_hash}-displacements.dat 58 | ../../../../../bin/feenox ../../../../../solvers/feenox/field1.fee ${problem_hash} sigma | tr -s ' \t\n' ' ' > ${problem_hash}-sigma.dat 59 | fi 60 | status="success" 61 | else 62 | status="error" 63 | fi 64 | else 65 | status="syntax_error" 66 | fi 67 | 68 | cat << EOF > ${problem_hash}.json 69 | { 70 | "status": "${status}" 71 | } 72 | EOF 73 | 74 | # sync run/meshes/${mesh_hash}.json 75 | rm -f ${dir}/${problem_hash}.pid 76 | -------------------------------------------------------------------------------- /solvers/feenox/solve.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | if [ ! -e ./case.fee ]; then 4 | echo "error: run from case directory" 5 | exit 1 6 | fi 7 | if [ ! -d ./run ]; then 8 | echo "error: run dir does not exist" 9 | exit 1 10 | fi 11 | if [ -z "${1}" ]; then 12 | echo "error: specify the problem type" 13 | exit 1 14 | fi 15 | 16 | 17 | # https://stackoverflow.com/questions/3679296/only-get-hash-value-using-md5sum-without-filename 18 | # A simple array assignment works... Note that the first element of a Bash array can be addressed by just the name without the [0] index, i.e., $md5 contains only the 32 characters of md5sum. 19 | 20 | problem_type=${1} 21 | problem_hash=($(md5sum case.fee)) 22 | mesh_hash=($(md5sum mesh.geo)) 23 | 24 | cat << EOF > run/${problem_hash}.json 25 | { 26 | "status": "running", 27 | "pid": $$ 28 | } 29 | EOF 30 | 31 | # cad=$(yq .cad case.yaml | tr -d \") 32 | cad=$(grep ^cad case.yaml | cut -f2 -d: | tr -d " ") 33 | max_length=$(jq .max_length ../../cads/${cad}/cad.json) 34 | 35 | cp case.fee run/${problem_hash}.fee || exit 2 36 | cd run || exit 3 37 | 38 | # check the syntax 39 | ../../../../../bin/feenox -c ${problem_hash}.fee 1> ${problem_hash}.1 2> ${problem_hash}.2 40 | if [ $? -eq 0 ]; then 41 | # all good! 42 | # see if we have the second-order mesh 43 | if [ ! -e meshes/${mesh_hash}-2.msh ]; then 44 | ../../../../../bin/gmsh -3 meshes/${mesh_hash}.msh -order 2 -o meshes/${mesh_hash}-2.msh > meshes/${mesh_hash}-2.1 45 | fi 46 | 47 | # run 48 | # TODO: time & memory (maybe we can read it from the log) 49 | ../../../../../bin/feenox --progress ${problem_hash}.fee 1> ${problem_hash}.1 2> ${problem_hash}.2 50 | feenox_error=$? 51 | 52 | if [ $feenox_error -eq 0 ]; then 53 | if [ "x${problem_type}" = "xmechanical" ]; then 54 | ../../../../../bin/feenox ../../../../../solvers/feenox/second2first.fee ${problem_hash} ${mesh_hash} 55 | ../../../../../bin/feenox ../../../../../solvers/feenox/displacements.fee ${problem_hash} ${max_length} | tr -s ' \t\n' ' ' > ${problem_hash}-displacements.dat 56 | ../../../../../bin/feenox ../../../../../solvers/feenox/field1.fee ${problem_hash} sigma | tr -s ' \t\n' ' ' > ${problem_hash}-sigma.dat 57 | elif [ "x${problem_type}" = "xheat_conduction" ]; then 58 | ../../../../../bin/feenox ../../../../../solvers/feenox/field.fee ${problem_hash} T | tr -s ' \t\n' ' ' > ${problem_hash}-T.dat 59 | fi 60 | status="success" 61 | else 62 | status="error" 63 | fi 64 | else 65 | status="syntax_error" 66 | fi 67 | 68 | cat << EOF > ${problem_hash}.json 69 | { 70 | "status": "${status}" 71 | } 72 | EOF 73 | 74 | # sync run/meshes/${mesh_hash}.json 75 | rm -f ${dir}/${problem_hash}.pid 76 | -------------------------------------------------------------------------------- /meshers/gmsh/meshing_status.php: -------------------------------------------------------------------------------- 1 | 62 | -------------------------------------------------------------------------------- /meshers/gmsh/mesh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | usage="case" 4 | if [ ! -z "${1}" ]; then 5 | usage="cad" 6 | fi 7 | 8 | if [ "x${usage}" == "xcase" ]; then 9 | dir="run" 10 | mesh="mesh" 11 | if [ ! -d ${dir}/meshes ]; then 12 | echo "error: ${dir}/meshes dir does not exist" 13 | exit 1 14 | fi 15 | else 16 | dir="." 17 | mesh="default" 18 | if [ ! -d ${dir}/meshes ]; then 19 | mkdir -p ${dir}/meshes 20 | fi 21 | fi 22 | 23 | if [ ! -e ./${mesh}.geo ]; then 24 | echo "error: run from case directory" 25 | exit 1 26 | fi 27 | 28 | mesh_hash=($(md5sum ${mesh}.geo)) 29 | 30 | cat << EOF > ${dir}/meshes/${mesh_hash}.json 31 | { 32 | "status": "running", 33 | "pid": $$ 34 | } 35 | EOF 36 | 37 | # TODO: time & memory (maybe we can read it from the log) 38 | ../../../../bin/gmsh -check ${mesh}.geo 1> ${dir}/meshes/${mesh_hash}.1 2> ${dir}/meshes/${mesh_hash}.2 39 | if [ $? -eq 0 ]; then 40 | ../../../../bin/gmsh -3 ${mesh}.geo -o ${dir}/meshes/${mesh_hash}.msh 1> ${dir}/meshes/${mesh_hash}.1 2> ${dir}/meshes/${mesh_hash}.2 41 | 42 | # the meshing could have worked or not, that's in $? 43 | gmsh_error=$? 44 | if [ "x${gmsh_error}" != "x0" ]; then 45 | echo ${dir}/meshes/${mesh_hash}.2 > tmp 46 | cat ${dir}/meshes/${mesh_hash}.2 | grep -v "No elements" | \ 47 | grep -v "\-\-\-\-\-\-\-\-\-\-\-" | \ 48 | grep -v "Mesh generation error summary" | \ 49 | grep -v " warning" | \ 50 | grep -v " errors" | \ 51 | grep -v "Check the full log for details" > tmp 52 | 53 | sed -n 's/.*intersection (\([^)]*\)).*/\1/p' tmp | tr ',' ' ' | head -n1 > ${dir}/meshes/${mesh_hash}.intersections 54 | mv tmp ${dir}/meshes/${mesh_hash}.2 55 | fi 56 | 57 | # we can have a partial mesh, though 58 | # TODO: rewrite mesh_data in C++ 59 | if [ -e ${dir}/meshes/${mesh_hash}.msh ]; then 60 | ../../../../meshers/gmsh/mesh_data.py ${mesh_hash} ${dir}/meshes > ${dir}/meshes/${mesh_hash}-data.log 61 | fi 62 | 63 | # the metadata depends on whether the mesh worked or not 64 | ../../../../meshers/gmsh/mesh_meta.py ${dir}/meshes/${mesh_hash} ${gmsh_error} > ${dir}/meshes/${mesh_hash}.json 65 | if [ -e ${dir}/meshes/${mesh_hash}.gp ]; then 66 | # TODO: bin 67 | gnuplot ${dir}/meshes/${mesh_hash}.gp 68 | fi 69 | # TODO: should we remove this guy? 70 | # rm -f ${dir}/meshes/${mesh_hash}-status.json 71 | 72 | else 73 | cat << EOF > ${dir}/meshes/${mesh_hash}.json 74 | { 75 | "status": "syntax_error" 76 | } 77 | EOF 78 | fi 79 | 80 | # sync run/meshes/${mesh_hash}.json 81 | rm -f ${dir}/${mesh_hash}.pid 82 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | # Roadmap 3 | 4 | * more problems (non-linear mechanics, transient thermal, modal, cfd, etc.) 5 | * more meshers (netgen? tetgen? salome?) 6 | * more solvers (sparselizard? fenics?) 7 | * more runners (ssh, aws, kubernetes, etc.) 8 | * more documentation 9 | 10 | # TODOs 11 | 12 | ## General 13 | 14 | * replace python with c++ to "process" msh and make it smarter 15 | * unit tests 16 | * choose units (SI, etc.) 17 | * choose points for BCs (and eventually refinements) 18 | * name in the BC should reflect the content 19 | * dashboard with case list 20 | * real-time collaboration 21 | * detect changes in CAD 22 | * git history in the UX 23 | * show face id when hovering 24 | * screenshots 25 | * once a minute refresh the axes, faces, edges, etc. (take a snapshot?) 26 | * investigate defeature operation in OCC through Gmsh (would we need a separate UX?) 27 | * re-implement how to show SunCAE version in about (when running `deps.sh`) 28 | * check that everything is fine when running `deps.sh`: 29 | - that executables work 30 | - that permissions are fine 31 | - create a `txt` with versions with `git log -1` + `git status --porcelain` 32 | * ability to take notes in markdown 33 | * help ballons, markdown + pandoc 34 | * limit DOFs: in conf? somewhere in auth? like `limits.php`? 35 | * remove visibility, everything is public 36 | 37 | 38 | ## Gmsh 39 | 40 | * STL input 41 | * combos for algorithms 42 | * checkboxes for bool 43 | * local refinements 44 | * understand failures -> train AI to come up with a proper `.geo` 45 | * other meshers! (tetget? netgen?) 46 | * multi-solid: bonded, glued, contact 47 | * curved tetrahedra 48 | * hexahedra 49 | 50 | ## Problem 51 | 52 | * choose faces with ranges e.g 1-74 53 | * other problems: modal 54 | * other solvers: ccx, sparselizard 55 | * orthotropic elasticity 56 | * thermal expansion (isotropic and orthotropic) 57 | * modal feenox 58 | * mechanical sparselizard 59 | * transient/quasistatic (a slider for time?) 60 | 61 | ## Results 62 | 63 | * fields (the list of available fields should be read from the outpt vtk/msh) 64 | - heat flux? (more general, vector fields?) 65 | * the server should tell the client 66 | - which field it is returning (so the client can choose the pallete) 67 | - if it has a warped field or not 68 | * the range scale has to be below the warp slider 69 | * nan color (yellow) 70 | * compute the .dat in the PHP, not in Bash 71 | * probes: user picks location, server returns all field 72 | * reactions: choose which BCs to compute reaction at in the problem step with a checkboxes 73 | * warning for large deformations/stresses 74 | 75 | ## Outer loops 76 | 77 | * parametric 78 | * optimization 79 | 80 | ## Dashboard 81 | 82 | * list of cases 83 | 84 | ## Backlog 85 | 86 | * zoom over mouse 87 | * disable BCs (comment them out) 88 | -------------------------------------------------------------------------------- /meshers/gmsh/trymesh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append("../../../../bin") 4 | import gmsh 5 | import math 6 | import os 7 | import json 8 | import signal 9 | 10 | def mesh(attempt): 11 | result = {} 12 | gmsh.initialize() 13 | gmsh.option.setNumber("General.Terminal", 1) 14 | # gmsh.option.setNumber("General.Verbosity", 1) # (0: silent except for fatal errors, 1: +errors, 2: +warnings, 3: +direct, 4: +information, 5: +status, 99: +debug) 15 | 16 | # now we do not care about optimization, then in the actual mesh step we will optimize 17 | gmsh.option.setNumber("Mesh.Optimize", 0) 18 | gmsh.merge("cad.xao") 19 | if attempt != 0: 20 | gmsh.merge("../../../../meshers/gmsh/default%d.geo" % attempt) 21 | 22 | cad_json_file = open("cad.json") 23 | cad = json.load(cad_json_file) 24 | cad_json_file.close() 25 | length_order = math.pow(10, math.floor(math.log10(cad["max_length"]))) 26 | length_precision = 1e-2 * length_order 27 | length_char = 4*cad["volume"]/cad["area"] 28 | lc1 = length_char * (1+0.125*(3-attempt)) 29 | lc2 = cad["max_length"] / (4 * (1 + attempt)) 30 | lc = math.ceil(min([lc1, lc2]) / length_precision) * length_precision 31 | print("length_order", length_order) 32 | print("length_precision", length_precision) 33 | print("length_char", length_char) 34 | print("lc1", lc1) 35 | 36 | gmsh.option.setNumber("Mesh.MeshSizeMax", lc) 37 | gmsh.option.setNumber("Mesh.MeshSizeMin", 1e-2*lc) 38 | 39 | try: 40 | gmsh.model.mesh.generate(3) 41 | 42 | except: 43 | result["result"] = "failed" 44 | nodes, _, _ = gmsh.model.mesh.getNodes() 45 | result["lc"] = lc 46 | result["nodes"] = len(nodes) 47 | result["error"] = gmsh.logger.getLastError() 48 | result["error_entity"] = list(gmsh.model.mesh.getLastEntityError()) 49 | # numpy uses uint64 which json does not like 50 | result["error_node"] = list() 51 | for n in gmsh.model.mesh.getLastNodeError(): 52 | result["error_node"].append(int(n)) 53 | return result 54 | 55 | result["result"] = "success" 56 | nodes, _, _ = gmsh.model.mesh.getNodes() 57 | result["lc"] = lc 58 | result["nodes"] = len(nodes) 59 | gmsh.finalize() 60 | return result 61 | 62 | #def handler(signum, frame): 63 | #signame = signal.Signals(signum).name 64 | #print(f'Signal handler called with signal {signame} ({signum})') 65 | #sys.exit(1) 66 | 67 | # gmsh installs some signal handlers of its own 68 | # I cannot seem to catch signals 69 | #signal.signal(signal.SIGTERM, handler) 70 | #signal.signal(signal.SIGINT, handler) 71 | #signal.signal(signal.SIGKILL, handler) 72 | #signal.signal(signal.SIGHUP, handler) 73 | 74 | 75 | def main(): 76 | if len(sys.argv) == 2: 77 | json_file = "attempt%d.json" % (int(sys.argv[1])) 78 | if os.path.exists(json_file): 79 | os.remove(json_file) 80 | result = mesh(int(sys.argv[1])) 81 | with open(json_file, "w") as fp: 82 | json.dump(result, fp, indent=2) 83 | 84 | if __name__ == "__main__": 85 | main() 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /solvers/problems.php: -------------------------------------------------------------------------------- 1 | $problem) { 25 | $keys[$physics] = $physics_name[$physics]; 26 | } 27 | 28 | } else if (isset($problems[$what])) { 29 | 30 | // $keys["none"] = ""; 31 | foreach ($problems[$what] as $index => $problem) { 32 | $keys[$problem] = $problem_name[$problem]; 33 | } 34 | 35 | } else if (isset($solvers[$what])) { 36 | 37 | foreach ($solvers[$what] as $index => $solver) { 38 | $keys[$solver] = $solvers_names[$solver]; 39 | } 40 | } else { 41 | // TODO: 42 | $keys["gmsh"] = "Gmsh"; 43 | 44 | /* 45 | } else if ($what == "fluid") { 46 | 47 | $keys = ["none", "fluid-irrotational", "fluid-incompressible", "fluid-compressible"]; 48 | $values = ["", "Laminar irrotational flow", "Incompressible flow", "Compressible flow"]; 49 | 50 | } else if ($what == "thermal") { 51 | 52 | $keys = ["none", "conduction", "radiation"]; 53 | $values = ["", "Heat conduction", "Radiative heat exchange"]; 54 | 55 | } else if ($what == "electromagnetism") { 56 | 57 | $keys = ["none", "electrostatics", "motor", "antenna"]; 58 | $values = ["", "Electrostatics", "Electrical motors", "Antenna radiation"]; 59 | 60 | } else if ($what == "acoustics") { 61 | 62 | $keys = ["none", "acoustics-linear", "ultrasonics"]; 63 | $values = ["", "Linear acoustics", "Ultrasonics"]; 64 | 65 | } else if ($what == "neutron") { 66 | 67 | $keys = ["none", "neutron-diffusion", "neutron-sn"]; 68 | $values = ["", "Multigroup diffusion", "Multigroup SN"]; 69 | 70 | } else if ($what == "multiphysics") { 71 | 72 | $keys = ["none", "thermo-mechanical", "conjugate-heat", "fluid-structure"]; 73 | $values = ["", "Thermomechanical", "Conjugate heat transfer", "Fluid-structure interaction"]; 74 | */ 75 | } 76 | 77 | $response["keys"] = array(); 78 | $response["values"] = array(); 79 | 80 | foreach ($keys as $key => $value) { 81 | array_push($response["keys"], $key); 82 | array_push($response["values"], $value); 83 | } 84 | 85 | return_back_json($response); 86 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/solving.php: -------------------------------------------------------------------------------- 1 | 8 | 9 |
Solving progress
10 |
11 | 12 |
13 | 14 |
15 |

16 | 19 |

20 |
21 |
22 | 23 | 24 | 25 | ">Second-order mesh 26 |
" role="progressbar"> 27 |
28 |
29 | 30 | Build 31 |
32 |
33 |
34 | 35 | Solve 36 |
37 |
38 |
39 | 40 | Compute fluxes 41 |
42 |
43 |
44 | 45 | 51 |
52 |
53 |
54 | Solving... 55 |
56 |
57 |
58 | 59 |
60 | 63 | 64 |
65 | 66 |
67 |
68 |
69 | 70 | 71 | 72 |
73 | -------------------------------------------------------------------------------- /meshers/gmsh/ajax2mesh.php: -------------------------------------------------------------------------------- 1 | &1", $output, $result); 60 | if ($result != 0) { 61 | for ($i = 0; $i < count($output); $i++) { 62 | if (strncmp("Error", $output[$i], 5) == 0) { 63 | $output_exploded = explode(":", $output[$i]); 64 | for ($j = 1; $j < count($output_exploded); $j++) { 65 | $response["error"] .= $output_exploded[$j] ; 66 | } 67 | $response["error"] .= "
"; 68 | } 69 | } 70 | } 71 | 72 | } else { 73 | return_error_json("cannot open mesh.geo"); 74 | } 75 | $mesh_hash = mesh_hash(); 76 | 77 | exec("git commit -a -m 'mesh {$field} = {$value}'", $output, $result); 78 | if ($result != 0) { 79 | return_error_json("cannot git commit {$id}: {$output[0]}"); 80 | } 81 | suncae_log("mesh {$id} ajax2yaml {$field} = {$value}"); 82 | 83 | if ($response["error"] != "") { 84 | suncae_log("mesh {$id} error: {$response["error"]}"); 85 | } 86 | 87 | return_back_json($response); 88 | -------------------------------------------------------------------------------- /html/new/create.php: -------------------------------------------------------------------------------- 1 | 8 | 9 |
Expert zone
10 |
11 | 12 |
13 |
14 |

15 | 18 |

19 |
20 |
21 | TODO: mesh 22 | 23 | TOD: vtk 24 |
25 |
26 |
27 |
28 |

29 | 32 |

33 |
34 |
35 | TODO: show usage 36 | 37 | TODO: clear 38 |
39 |
40 |
41 |
42 |

43 | 46 |

47 |
48 |
49 | 59 | 60 |
61 |
62 | 63 |
64 |
65 | 66 |
67 |
68 | 69 |
70 |
71 | 72 |
73 |
74 | 75 |
76 |
77 | 78 |
79 |
80 |
81 |
82 | 83 | -------------------------------------------------------------------------------- /doc/FAQs.md: -------------------------------------------------------------------------------- 1 | # Frequently asked questions 2 | 3 | > [!TIP] 4 | > If your question is not listed here, do not hesitate [contacting us](https://www.seamplex.com/suncae/#contact). 5 | 6 | ### What is the difference between SunCAE, FeenoX, Seamplex and CAEplex? 7 | 8 | * [SunCAE](https://www.seamplex.com/suncae) is an open-source web-based interface for performing CAE in the cloud. 9 | * [FeenoX](https://www.seamplex.com/feenox) is an open-source finite-element solver which used in SunCAE by default. 10 | * [Seamplex](https://www.seamplex.com) is the company that developed both SunCAE and FeenoX 11 | * [CAEplex](https://www.caeplex.com) is the first web-based interface developed by Seamplex. It is [100% integrated into Onshape](https://www.youtube.com/watch?v=ylXAUAsfb5E). 12 | 13 | 14 | ### Can I try SunCAE without having to install it? 15 | 16 | Yes, check out our [live demo](https://www.caeplex.com/suncae). 17 | Note that 18 | 19 | * There is no need to register, but your IP might get logged. 20 | * The data (including CAD files) might (or not) get lost. Do not put sensitive stuff in the demo. 21 | 22 | ### Which language is SunCAE written in? 23 | 24 | The front end is HTML with plain vanilla Javascript. This means no Angular, no React, no NodeJS, nothing. Not even JQuery. Plain vanilla Javascript (plus the Javascript libraries needed for 3D rendering). 25 | 26 | The back end is written in PHP. Again, plain PHP with at most `php-yaml` to read and write YAML files. 27 | The front end would make asynchronous AJAX calls to the back end, which would run PHP file and respond with a JSON content and so the front end can update the DOM. 28 | 29 | ### What are the licensing terms? 30 | 31 | TL;DR: if you use a modified version of SunCAE in your server, you have to provide a link to the modified sources. 32 | 33 | The content of this SunCAE repository is licensed under the terms of the [GNU Affero General Public License version 3](https://www.gnu.org/licenses/agpl-3.0.en.html), or at your option, any later version (AGPLv3+). 34 | 35 | See the [licenses table](../LICENSES.md) and [licensing details](../README.,d#licensing) for more information. 36 | 37 | 38 | ### How does SunCAE import the CAD geometry? 39 | 40 | So far, only using [OpenCASCADE](https://dev.opencascade.org) through the [Gmsh API](https://gitlab.onelab.info/gmsh/gmsh/-/tree/master/api). 41 | 42 | > [!NOTE] 43 | > If you want other CAD imported to be supported, say so in the [forum](https://github.com/seamplex/suncae/discussions). 44 | 45 | ### What are the supported meshers? 46 | 47 | So far, only [Gmsh](http://gmsh.info/) is supported. 48 | 49 | See the directory [`meshers`](https://github.com/seamplex/suncae/tree/main/meshers) for the current list. 50 | 51 | > [!NOTE] 52 | > If you want other meshers to be supported, say so in the [forum](https://github.com/seamplex/suncae/discussions). 53 | 54 | 55 | ### What are the supported solvers? 56 | 57 | * [FeenoX](http://www.seamplex.com/feenox) 58 | * [CalculiX](https://www.dhondt.de/) 59 | 60 | The "single source of truth" is still the FeenoX input file. 61 | CalculiX is supportted through the [`fee2ccx` converter](https://github.com/seamplex/feenox/tree/main/utils/fee2ccx) from `.fee` to `.inp`. 62 | 63 | See the directory [`solvers`](https://github.com/seamplex/suncae/tree/main/solvers) for the current list. 64 | 65 | > [!NOTE] 66 | > If you want other solvers to be supported, say so in the [forum](https://github.com/seamplex/suncae/discussions). 67 | 68 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/meshing.php: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 |
Meshing progress
20 |
21 | 22 |
23 | 24 |
25 |

26 | 29 |

30 |
31 |
32 | 33 | 34 | 35 | 1D : : 0/ edges 36 |
37 |
38 |
39 | 40 | 2D : : 0/ faces 41 |
42 |
43 |
44 | 45 | 3D : : 0/ volumes 46 |
47 |
48 |
49 | 50 | Processing 51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 | Meshing... 59 |
60 |
61 |
62 | 63 |
64 |
65 |
66 | 
67 | 
68 | 
69 | 
70 | 
71 | 
72 |
73 | 74 | 77 | 78 |
79 | 80 |
81 |
82 |
83 | 84 | 85 | 86 |
87 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/false 2 | 3 | bootstrap_version=5.3.3 4 | bootstrap_icons_version=1.11.3 5 | katex_version=0.16.11 6 | pandoc_version=3.5 7 | 8 | # boostrap (we only need the js, the css comes from bootswatch) 9 | echo -n "uxs/faster-than-quick/bootstrap.js... " 10 | bootstrap_tarball=bootstrap-${bootstrap_version}-dist 11 | if [ $force = 1 ] || [ ! -e uxs/faster-than-quick/js/bootstrap.min.js ] || [ ! -f deps/${bootstrap_tarball}.zip ]; then 12 | cd deps 13 | if [ ! -e ${bootstrap_tarball}.zip ]; then 14 | wget https://github.com/twbs/bootstrap/releases/download/v${bootstrap_version}/${bootstrap_tarball}.zip 15 | fi 16 | if [ ! -d ${bootstrap_tarball} ]; then 17 | unzip ${bootstrap_tarball}.zip 18 | fi 19 | cp ${bootstrap_tarball}/js/bootstrap.min.js ../uxs/faster-than-quick/js 20 | cp ${bootstrap_tarball}/js/bootstrap.bundle.min.js ../uxs/faster-than-quick/js 21 | echo "done" 22 | cd .. 23 | else 24 | echo "already installed" 25 | fi 26 | 27 | 28 | # boostrap icons 29 | echo -n "uxs/faster-than-quick/bootstrap icons... " 30 | bootstrap_icons_tarball=bootstrap-icons-${bootstrap_icons_version} 31 | 32 | if [ $force = 1 ] || [ ! -e uxs/faster-than-quick/css/bootstrap-icons.min.css ] || [ ! -f deps/${bootstrap_icons_tarball}.zip ]; then 33 | cd deps 34 | if [ ! -e ${bootstrap_icons_tarball}.zip ]; then 35 | wget https://github.com/twbs/icons/releases/download/v${bootstrap_icons_version}/${bootstrap_icons_tarball}.zip 36 | fi 37 | if [ ! -d ${bootstrap_icons_tarball} ]; then 38 | unzip ${bootstrap_icons_tarball}.zip 39 | fi 40 | cp ${bootstrap_icons_tarball}/font/bootstrap-icons.min.css ../uxs/faster-than-quick/css 41 | mkdir -p ../uxs/faster-than-quick/css/fonts 42 | cp ${bootstrap_icons_tarball}/font/fonts/bootstrap-icons.woff ../uxs/faster-than-quick/css/fonts 43 | cp ${bootstrap_icons_tarball}/font/fonts/bootstrap-icons.woff2 ../uxs/faster-than-quick/css/fonts 44 | for i in Carlito-Bold Carlito-Italic Carlito-BoldItalic Carlito-Regular; do 45 | wget https://raw.githubusercontent.com/googlefonts/carlito/refs/heads/main/fonts/ttf/${i}.ttf -O ../uxs/faster-than-quick/css/fonts/${i}.ttf 46 | done 47 | echo "done" 48 | cd .. 49 | else 50 | echo "already installed" 51 | fi 52 | 53 | 54 | # katex 55 | echo -n "uxs/faster-than-quick/katex... " 56 | if [ $force = 1 ] || [ ! -e uxs/faster-than-quick/css/katex.min.css ]; then 57 | cd deps 58 | if [ ! -e katex.tar.gz ]; then 59 | wget https://github.com/KaTeX/KaTeX/releases/download/v${katex_version}/katex.tar.gz 60 | fi 61 | if [ ! -d katex ]; then 62 | tar xvzf katex.tar.gz 63 | fi 64 | cp katex/katex.min.css ../uxs/faster-than-quick/css 65 | mkdir -p ../uxs/faster-than-quick/css/fonts 66 | cp katex/fonts/* ../uxs/faster-than-quick/css/fonts 67 | echo "done" 68 | cd .. 69 | else 70 | echo "already installed" 71 | fi 72 | 73 | # pandoc 74 | echo -n "uxs/faster-than-quick/pandoc... " 75 | pandoc_tarball=pandoc-${pandoc_version}-linux-amd64 76 | if [ $force = 1 ] || [ ! -e bin/pandoc ] || [ ! -f deps/${pandoc_tarball}.tar.gz ]; then 77 | cd deps 78 | if [ ! -e ${pandoc_tarball}.tar.gz ]; then 79 | wget https://github.com/jgm/pandoc/releases/download/${pandoc_version}/${pandoc_tarball}.tar.gz 80 | fi 81 | if [ ! -d pandoc-${pandoc_version} ]; then 82 | tar xvzf ${pandoc_tarball}.tar.gz 83 | fi 84 | cp pandoc-${pandoc_version}/bin/pandoc ../bin 85 | echo "done" 86 | cd .. 87 | else 88 | echo "already installed" 89 | fi 90 | 91 | # TODO: gnuplot? 92 | 93 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/change_step.php: -------------------------------------------------------------------------------- 1 | 5) { 23 | return_error_json("Invalid step"); 24 | } 25 | $next_step = $_POST["next_step"]; 26 | $current_step = (isset($_POST["current_step"])) ? $_POST["current_step"] : 2; 27 | 28 | // if the user is coming from mesh but the mesh options has changed, we have to re-mesh and go back 29 | if ($current_step == 1 && $has_mesh_attempt == false) { 30 | $next_step = 1; 31 | } 32 | 33 | switch ($next_step) { 34 | case 1: 35 | // TODO: per-mesher 36 | chdir($case_dir); 37 | if (file_exists("mesh.geo") == false) { 38 | $geo = fopen("mesh.geo", "w"); 39 | fprintf($geo, "Merge \"../../cads/{$case["cad"]}/cad.xao\";\n"); 40 | fclose($geo); 41 | } 42 | 43 | $response["mesh"] = ($has_mesh) ? $mesh_hash : ""; 44 | if ($has_mesh_attempt == false) { 45 | exec("../../../../bin/gmsh -check mesh.geo 1> run/meshes/{$mesh_hash}-check.1 2> run/meshes/{$mesh_hash}-check.2", $output, $result); 46 | if ($result == 0) { 47 | // https://www.php.net/manual/en/function.exec.php 48 | // https://stackoverflow.com/questions/45953/php-execute-a-background-process 49 | exec("../../../../meshers/gmsh/mesh.sh > run/meshing.log 2>&1 & echo $! > run/meshing.pid"); 50 | $mesh_meta["status"] = "running"; 51 | suncae_log("{$id} mesh running"); 52 | } else { 53 | $mesh_meta["status"] = "syntax_error"; 54 | suncae_log("{$id} mesh syntax error"); 55 | } 56 | file_put_contents("run/meshes/{$mesh_hash}.json", json_encode($mesh_meta)); 57 | } 58 | // if running, go to meshing.php otherwise go to mesh.php, it will know what to show} 59 | // TODO: AND or OR? 60 | $next_step *= (isset($mesh_meta["status"]) && $mesh_meta["status"] != "running") ? (+1) : (-1); 61 | 62 | suncae_log("{$id} change_step {$current_step} -> {$next_step}"); 63 | 64 | break; 65 | 66 | case 3: 67 | $response["results"] = ($has_results) ? $problem_hash : ""; 68 | $response["has_results_attempt"] = $has_results_attempt; 69 | if ($has_results_attempt == false) { 70 | 71 | include("../solvers/{$solver}/change_step_solve.php"); 72 | file_put_contents("run/{$problem_hash}.json", json_encode($results_meta)); 73 | suncae_log("{$id} change_step {$current_step} -> {$next_step}"); 74 | 75 | } 76 | if (isset($results_meta)) { 77 | $next_step *= ($results_meta["status"] != "running") ? (+1) : (-1); 78 | } 79 | break; 80 | } 81 | 82 | $response["step" ] = $next_step; 83 | $response["url"] = "{$step_url[$next_step]}?id={$id}"; 84 | 85 | 86 | return_back_json($response); 87 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/importers/upload.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 48 | 49 |
50 | 53 |
54 | 55 | 56 |
57 |
58 |
59 |
60 | Pick or drag-and-drop a single-solid CAD file in STEP format.
61 | If you do not have one, download a sample STEP file here. 62 |
63 |
64 | 65 |
66 | 72 | 73 | 74 | 75 | 76 | 77 |
78 | 79 |
80 | 85 |
86 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/properties.php: -------------------------------------------------------------------------------- 1 | 30 | 31 |
32 | 33 |
34 | 38 |
39 |
40 | 41 | 80 | 81 | -------------------------------------------------------------------------------- /html/case.php: -------------------------------------------------------------------------------- 1 | 107 | -------------------------------------------------------------------------------- /doc/design.md: -------------------------------------------------------------------------------- 1 | # SunCAE design description 2 | 3 | SunCAE is split into a front end and a back end. 4 | 5 | * The front end is HTML + Javascript running on a web browser on any operating system (even mobile devices). 6 | * The back end is PHP + Bash + Python + particular binaries running on one or more Unix servers. 7 | 8 | The spirit is that the definition of the CAE problem being solved is stored in a single source of truth as a text file, hopefully the actual solver's input file. The web-based interface should allow both 9 | 10 | 1. To update the solver's input file from the status of web-based widgets (e.g. picking faces in the 3D model to hold boundary conditions, entering material properties in text boxes, choosing sdef/ldef from a combo box, etc.), and 11 | 2. To allow the user to edit the input file and then to update the status of web-based widgets from the contents of the input file. 12 | 13 | For instance, consider the following [FeenoX](https://www.seamplex.com/feenox/) input file: 14 | 15 | ``` 16 | PROBLEM mechanical READ_MESH meshes/9061bd607902d31a8aac5f97a1066504-2.msh 17 | 18 | E(x,y,z) = 200e3 19 | nu = 0.3 20 | 21 | BC face1 fixed 22 | BC face3 Fx=10e3 23 | ``` 24 | 25 | If the user changes the value of the Young's modulus to `210e3`, the front end issues an AJAX call and the back end updates the file. 26 | Conversely, if the user edits the input file (through a web-based editor the UX has to provide) and changes the value in the file to something else, then the back-end sends instructions to the front end to update the web-based interface. 27 | Moreover, each time the file changes (either because of the user changing a value in the interface or editing the file), a Git commit is issued. Therefore, every single change in the single source of truth is tracked (what? who? when?). 28 | 29 | # The SunCAE interface 30 | 31 | The SunCAE interface consists of four steps divided into two stages: 32 | 33 | 1. New case 34 | 1. CAD import, physics, problem, mesher and solver selection 35 | 2. Case view 36 | 1. Mesh 37 | 2. Problem 38 | 3. Results 39 | 40 | The source code of the index page at [html/index.php](../html/index.php) looks like this 41 | 42 | ```php 43 | include("../conf.php"); 44 | include("../auths/{$auth}/auth.php"); 45 | include("common.php"); 46 | include("case.php"); 47 | include("../uxs/{$ux}/index.php"); 48 | ``` 49 | 50 | * The [`conf.php`](../conf.php) file is included first. 51 | * The authorization scheme `$auth` defined in `conf.php` is included. 52 | This step should ask for authentication/authorization, set/read cookies, etc. 53 | PHP's [session_start()](https://www.php.net/manual/en/function.session-start.php) can be used. 54 | This file should define a non-empty global PHP string `$username`. 55 | The default [`single-user`](../auths/single-user/auth.php) auth scheme just does 56 | 57 | ```php 58 | $username = "root"; 59 | ``` 60 | 61 | * The file `common.php` defines common functions and methods needed by the back end. 62 | It also defines a PHP variable `$id` coming from either a `GET` or `POST` argument with name `id`: 63 | 64 | ```php 65 | $id = (isset($_POST["id"])) ? $_POST["id"] : ((isset($_GET["id"])) ? $_GET["id"] : ""); 66 | ``` 67 | 68 | This should be the `id` of an existing case. 69 | 70 | * The file `case.php` reads the metadata for the case `id`. But, if the variable `$id` is empty, it will re-direct the user to the ["New case"](#new-case) page at `new/`: 71 | 72 | ```php 73 | if ($id == "") { 74 | header("Location: new/"); 75 | exit(); 76 | } 77 | ``` 78 | 79 | * If `$id` is not empty, the workflow continues to include the `$ux` scheme defined in `conf.php` which should load case `$id` and show the ["Case view"](#case-view), loading either the mesh, problem or results view depending on the state of the case. 80 | 81 | 82 | ## New case 83 | 84 | The [`html/new/index.php`](../html/new/index.php) source is 85 | 86 | ```php 87 | include("../../conf.php"); 88 | include("../../auths/{$auth}/auth.php"); 89 | include("../common.php"); 90 | include("../../uxs/{$ux}/new.php"); 91 | ``` 92 | 93 | The first three lines have been already discussed. 94 | The default `$ux` is `faster-than-quick`. As its name suggest, it is a quick hack that works. 95 | 96 | To be completed. 97 | 98 | ## Case view 99 | 100 | To be completed. 101 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/bootswatch/_variables.scss: -------------------------------------------------------------------------------- 1 | // CAEplex - based on Flatly 5.0.0-beta2 2 | // Bootswatch 3 | 4 | $theme: "faster-than-quick" !default; 5 | 6 | // 7 | // Color system 8 | // 9 | 10 | $white: #fff !default; 11 | $gray-100: #f8f9fa !default; 12 | $gray-200: #ecf0f1 !default; 13 | $gray-300: #dee2e6 !default; 14 | $gray-400: #ced4da !default; 15 | $gray-500: #b4bcc2 !default; 16 | $gray-600: #95a5a6 !default; 17 | $gray-700: #7b8a8b !default; 18 | $gray-800: #343a40 !default; 19 | $gray-900: #212529 !default; 20 | $black: #000 !default; 21 | 22 | $blue: #2c3e50 !default; 23 | $indigo: #6610f2 !default; 24 | $purple: #6f42c1 !default; 25 | $pink: #e83e8c !default; 26 | $red: #e74c3c !default; 27 | $orange: #fd7e14 !default; 28 | $yellow: #f39c12 !default; 29 | $green: #18bc9c !default; 30 | $teal: #20c997 !default; 31 | $cyan: #3498db !default; 32 | 33 | $primary: #0f85af !default; 34 | $secondary: #a0a050 !default; 35 | $info: #3b4550 !default; 36 | 37 | $success: $green !default; 38 | $warning: #e58c30 !default; 39 | $danger: $red !default; 40 | $light: $gray-200 !default; 41 | $dark: $gray-700 !default; 42 | 43 | $min-contrast-ratio: 2.15 !default; 44 | 45 | // Links 46 | 47 | $link-color: $primary !default; 48 | 49 | // Fonts 50 | 51 | $font-family-sans-serif: Carlito, Arial, sans-serif !default; 52 | 53 | // $font-size-base: 0.9375rem !default; 54 | $font-size-base: 0.9675rem !default; 55 | 56 | 57 | /* 58 | $h1-font-size: 2.10rem !default; 59 | $h2-font-size: 1.85rem !default; 60 | $h3-font-size: 1.50rem !default; 61 | $h4-font-size: 1.25rem !default; 62 | $h5-font-size: 1.15rem !default; 63 | */ 64 | 65 | // stylelint-disable-next-line value-keyword-case 66 | // $font-family-sans-serif: Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default; 67 | $h1-font-size: 3rem !default; 68 | $h2-font-size: 2.5rem !default; 69 | $h3-font-size: 2rem !default; 70 | 71 | // Tables 72 | 73 | $table-accent-bg: $gray-200 !default; 74 | 75 | // Dropdowns 76 | 77 | $dropdown-link-color: $gray-700 !default; 78 | $dropdown-link-hover-color: $white !default; 79 | $dropdown-link-hover-bg: $primary !default; 80 | 81 | // Navs 82 | 83 | $nav-link-padding-y: .5rem !default; 84 | $nav-link-padding-x: 2rem !default; 85 | $nav-link-disabled-color: $gray-600 !default; 86 | $nav-tabs-border-color: $gray-200 !default; 87 | 88 | // Navbar 89 | 90 | $navbar-padding-y: 1rem !default; 91 | $navbar-dark-color: $white !default; 92 | $navbar-dark-hover-color: $success !default; 93 | 94 | // Pagination 95 | 96 | $pagination-color: $white !default; 97 | $pagination-bg: $success !default; 98 | $pagination-border-width: 0 !default; 99 | $pagination-border-color: transparent !default; 100 | $pagination-hover-color: $white !default; 101 | $pagination-hover-bg: darken($success, 15%) !default; 102 | $pagination-hover-border-color: transparent !default; 103 | $pagination-active-bg: $pagination-hover-bg !default; 104 | $pagination-active-border-color: transparent !default; 105 | $pagination-disabled-color: $gray-200 !default; 106 | $pagination-disabled-bg: lighten($success, 15%) !default; 107 | $pagination-disabled-border-color: transparent !default; 108 | 109 | // List group 110 | 111 | $list-group-hover-bg: $gray-200 !default; 112 | $list-group-disabled-bg: $gray-200 !default; 113 | 114 | // Breadcrumbs 115 | 116 | $breadcrumb-padding-y: .375rem !default; 117 | $breadcrumb-padding-x: .75rem !default; 118 | $breadcrumb-border-radius: .25rem !default; 119 | 120 | // Close 121 | 122 | $close-color: $white !default; 123 | $close-text-shadow: none !default; 124 | 125 | // from bootstrap docs 126 | //$breadcrumb-divider: quote(">"); 127 | $breadcrumb-divider: quote("»"); 128 | /* $breadcrumb-divider: quote("🞂"); */ 129 | /* $brseadcrumb-divider: quote("⇒"); */ 130 | 131 | $btn-close-color: $white !default; 132 | $btn-close-opacity: .4 !default; 133 | $btn-close-hover-opacity: 1 !default; 134 | -------------------------------------------------------------------------------- /meshers/gmsh/mesh_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append("../../../../bin") 4 | import gmsh 5 | import os 6 | import json 7 | 8 | # Add a segment, always in increasing order, to make duplicate detection easy 9 | def addLine2(lines, lines_set, tags, first, second): 10 | a = int(tags[first]) 11 | b = int(tags[second]) 12 | entry = (min(a-1, b-1), max(a-1, b-1)) 13 | if entry not in lines_set: 14 | lines.append([entry[0], entry[1]]) 15 | lines_set.add(entry) 16 | return 17 | 18 | def addLine3(lines, lines_set, tags, first, second, third): 19 | a = int(tags[first]) 20 | b = int(tags[second]) 21 | c = int(tags[third]) 22 | # Always store in the lowest, middle, largest order (for consistency) 23 | sorted_entry = tuple(sorted([a-1, b-1, c-1])) 24 | if sorted_entry not in lines_set: 25 | lines.append(list(sorted_entry)) 26 | lines_set.add(sorted_entry) 27 | return 28 | 29 | # ------------------------------------------ 30 | 31 | if (len(sys.argv) < 3): 32 | print("need mesh hash and dir in the command line") 33 | sys.exit(1) 34 | 35 | mesh_file = "%s/%s.msh" % (sys.argv[2], sys.argv[1]) 36 | if not os.path.exists(mesh_file): 37 | print("mesh file does not exist") 38 | sys.exit(1) 39 | 40 | print("1", flush=True) 41 | gmsh.initialize() 42 | gmsh.option.setNumber("General.Terminal", 0) 43 | gmsh.open(mesh_file) 44 | 45 | # TODO: read whether the mesh is curved or not 46 | curved = 0 47 | 48 | mesh = {} 49 | 50 | # nodes ------------ 51 | mesh["nodes"] = "" 52 | tags, coord, _ = gmsh.model.mesh.getNodes() 53 | maxtag = max(tags) 54 | for i in range(maxtag): 55 | mesh["nodes"] += "{:6g} {:6g} {:6g} ".format(coord[3*i+0], coord[3*i+1], coord[3*i+2]) 56 | 57 | # surface edges ------------ 58 | mesh["surfaces_edges_set"] = "" 59 | elements = gmsh.model.mesh.getElements(2) 60 | n_elements = len(elements) 61 | lines = [] 62 | lines_set = set() 63 | i = 0 64 | k = 0 65 | for type in elements[0]: 66 | j = 0 67 | if type == 2 or (curved == 0 and type == 9): # 3-node triangle 68 | for element in elements[1][i]: 69 | addLine2(lines, lines_set, elements[2][i], j+0, j+1) 70 | addLine2(lines, lines_set, elements[2][i], j+1, j+2) 71 | addLine2(lines, lines_set, elements[2][i], j+2, j+0) 72 | j += 3 if type == 2 else 6 73 | k += 1 74 | elif type == 9: # 6-node triangle 75 | for element in elements[1][i]: 76 | addLine3(lines, lines_set, elements[2][i], j+0, j+3, j+1) 77 | addLine3(lines, lines_set, elements[2][i], j+1, j+4, j+2) 78 | addLine3(lines, lines_set, elements[2][i], j+2, j+5, j+0) 79 | j += 6 80 | k += 1 81 | i += 1 82 | 83 | print("2", flush=True) 84 | 85 | n_lines = len(lines) 86 | k = 0 87 | for line in lines: 88 | if (len(line) == 2): 89 | mesh["surfaces_edges_set"] += "{:d} {:d} -1 ".format(line[0], line[1]) 90 | elif (len(line) == 3): 91 | mesh["surfaces_edges_set"] += "{:d} {:d} {:d} -1 ".format(line[0], line[1], line[2]) 92 | k += 1 93 | print("3", flush=True) 94 | 95 | # surface faces, one per each physical group ------------ 96 | mesh["surfaces_faces_set"] = {} 97 | physicals = gmsh.model.getPhysicalGroups() 98 | k = 0 99 | for physical in physicals: 100 | dim = physical[0] 101 | physical_tag = physical[1] 102 | if (dim == 2): 103 | mesh["surfaces_faces_set"][physical_tag] = "" 104 | for entity in gmsh.model.getEntitiesForPhysicalGroup(dim, physical_tag): 105 | types, tags, nodetags = gmsh.model.mesh.getElements(dim, entity) 106 | for i in range(len(types)): 107 | for j in range(len(tags[i])): 108 | if types[i] == 2 or (curved == 0 and types[i] == 9): 109 | N = 6 if (types[i] == 9) else 3 110 | # for triangles remove the last int and the -1 111 | mesh["surfaces_faces_set"][physical_tag] += "{:d} {:d} {:d} ".format(int(nodetags[i][j*N+0])-1, int(nodetags[i][j*N+1])-1, int(nodetags[i][j*N+2])-1) 112 | elif types[i] == 9: 113 | N = 6 114 | mesh["surfaces_faces_set"][physical_tag] += "{:d} {:d} {:d} ".format(int(nodetags[i][j*N+0])-1, int(nodetags[i][j*N+3])-1, int(nodetags[i][j*N+5])-1) 115 | mesh["surfaces_faces_set"][physical_tag] += "{:d} {:d} {:d} ".format(int(nodetags[i][j*N+1])-1, int(nodetags[i][j*N+4])-1, int(nodetags[i][j*N+3])-1) 116 | mesh["surfaces_faces_set"][physical_tag] += "{:d} {:d} {:d} ".format(int(nodetags[i][j*N+2])-1, int(nodetags[i][j*N+5])-1, int(nodetags[i][j*N+4])-1) 117 | mesh["surfaces_faces_set"][physical_tag] += "{:d} {:d} {:d} ".format(int(nodetags[i][j*N+3])-1, int(nodetags[i][j*N+4])-1, int(nodetags[i][j*N+5])-1) 118 | k += 1 119 | 120 | print("4", flush=True) 121 | gmsh.finalize() 122 | 123 | with open("%s/%s-data.json" % (sys.argv[2], sys.argv[1]), "w", encoding ='utf8') as json_file: 124 | json.dump(mesh, json_file) 125 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/small_axes.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/results.php: -------------------------------------------------------------------------------- 1 | 19 |
20 | The solving process was canceled. 21 | 22 |
23 | 24 | 27 | 30 |
31 | 35 |
36 | 39 | 42 | Got status error but no stderr. 43 | 47 | Not sure what happened. 48 | 53 |
54 | There are no results nor any attempt at getting them. 55 |
56 | 61 |
62 | 72 |
73 | 79 |
Not yet implemented
80 | 86 |
Not yet implemented
87 | 98 | 99 |
100 |
101 |
102 | 
103 | 
104 |
105 | 110 | 111 |
112 |
113 | 117 | 118 | 122 |
123 |
124 | 125 |
"> 126 |
127 |
128 | 129 |
130 |
131 | 132 |
133 |
134 | 135 |
136 |
137 |
138 | 139 | 0) { 141 | ?> 142 | 143 | 146 | 147 | 150 | -------------------------------------------------------------------------------- /doc/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 37 | 42 | 43 | 45 | 50 | 57 | 61 | 65 | 70 | 75 | 80 | 81 | 82 | 88 | Sun 99 | 100 | 101 | -------------------------------------------------------------------------------- /uxs/faster-than-quick/bootswatch/_bootswatch.scss: -------------------------------------------------------------------------------- 1 | // CAEplex - based on Flatly 5.0.0-beta2 2 | // Bootswatch 3 | 4 | code { 5 | color: $secondary; 6 | } 7 | 8 | @font-face { 9 | font-family: Carlito; 10 | src: url(fonts/Carlito-Regular.ttf); 11 | } 12 | 13 | @font-face { 14 | font-family: Carlito; 15 | src: url(fonts/Carlito-Bold.ttf); 16 | font-weight: bold; 17 | } 18 | 19 | @font-face { 20 | font-family: Carlito; 21 | src: url(fonts/Carlito-Italic.ttf); 22 | font-style: italic; 23 | } 24 | 25 | @font-face { 26 | font-family: Carlito; 27 | src: url(fonts/Carlito-BoldItalic.ttf); 28 | font-weight: bold; 29 | font-style: italic; 30 | } 31 | 32 | 33 | // Navbar 34 | 35 | .bg-primary { 36 | .navbar-nav .active > .nav-link { 37 | color: $success !important; 38 | } 39 | } 40 | 41 | .bg-dark { 42 | &.navbar-dark .navbar-nav { 43 | .nav-link:focus, 44 | .nav-link:hover, 45 | .active > .nav-link { 46 | color: $primary !important; 47 | } 48 | } 49 | } 50 | 51 | // Buttons 52 | 53 | .btn { 54 | &-secondary, 55 | &-secondary:hover, 56 | &-warning, 57 | &-warning:hover { 58 | color: $white; 59 | } 60 | } 61 | 62 | // Tables 63 | 64 | .table { 65 | &-primary, 66 | &-secondary, 67 | &-success, 68 | &-info, 69 | &-warning, 70 | &-danger { 71 | color: $white; 72 | } 73 | 74 | &-primary { 75 | &, 76 | > th, 77 | > td { 78 | background-color: $primary; 79 | } 80 | } 81 | 82 | &-secondary { 83 | &, 84 | > th, 85 | > td { 86 | background-color: $secondary; 87 | } 88 | } 89 | 90 | &-light { 91 | &, 92 | > th, 93 | > td { 94 | background-color: $light; 95 | } 96 | } 97 | 98 | &-dark { 99 | &, 100 | > th, 101 | > td { 102 | background-color: $dark; 103 | } 104 | } 105 | 106 | &-success { 107 | &, 108 | > th, 109 | > td { 110 | background-color: $success; 111 | } 112 | } 113 | 114 | &-info { 115 | &, 116 | > th, 117 | > td { 118 | background-color: $info; 119 | } 120 | } 121 | 122 | &-danger { 123 | &, 124 | > th, 125 | > td { 126 | background-color: $danger; 127 | } 128 | } 129 | 130 | &-warning { 131 | &, 132 | > th, 133 | > td { 134 | background-color: $warning; 135 | } 136 | } 137 | 138 | &-active { 139 | &, 140 | > th, 141 | > td { 142 | background-color: $table-active-bg; 143 | } 144 | } 145 | 146 | &-hover { 147 | .table-primary:hover { 148 | &, 149 | > th, 150 | > td { 151 | background-color: darken($primary, 5%); 152 | } 153 | } 154 | 155 | .table-secondary:hover { 156 | &, 157 | > th, 158 | > td { 159 | background-color: darken($secondary, 5%); 160 | } 161 | } 162 | 163 | .table-light:hover { 164 | &, 165 | > th, 166 | > td { 167 | background-color: darken($light, 5%); 168 | } 169 | } 170 | 171 | .table-dark:hover { 172 | &, 173 | > th, 174 | > td { 175 | background-color: darken($dark, 5%); 176 | } 177 | } 178 | 179 | .table-success:hover { 180 | &, 181 | > th, 182 | > td { 183 | background-color: darken($success, 5%); 184 | } 185 | } 186 | 187 | .table-info:hover { 188 | &, 189 | > th, 190 | > td { 191 | background-color: darken($info, 5%); 192 | } 193 | } 194 | 195 | .table-danger:hover { 196 | &, 197 | > th, 198 | > td { 199 | background-color: darken($danger, 5%); 200 | } 201 | } 202 | 203 | .table-warning:hover { 204 | &, 205 | > th, 206 | > td { 207 | background-color: darken($warning, 5%); 208 | } 209 | } 210 | 211 | .table-active:hover { 212 | &, > th, > td { 213 | background-color: $table-active-bg; 214 | } 215 | } 216 | 217 | } 218 | } 219 | 220 | // Navs 221 | 222 | .nav-tabs { 223 | .nav-link.active, 224 | .nav-link.active:focus, 225 | .nav-link.active:hover, 226 | .nav-item.open .nav-link, 227 | .nav-item.open .nav-link:focus, 228 | .nav-item.open .nav-link:hover { 229 | color: $primary; 230 | } 231 | } 232 | 233 | .pagination { 234 | a:hover { 235 | text-decoration: none; 236 | } 237 | } 238 | 239 | // Indicators 240 | 241 | .badge { 242 | &-secondary, 243 | &-warning { 244 | color: $white; 245 | } 246 | } 247 | 248 | .alert { 249 | border: none; 250 | color: $white; 251 | 252 | a, 253 | .alert-link { 254 | color: $white; 255 | font-weight: bold; 256 | } 257 | 258 | @each $color, $value in $theme-colors { 259 | &-#{$color} { 260 | @if $enable-gradients { 261 | background: $value linear-gradient(180deg, mix($body-bg, $value, 15%), $value) repeat-x; 262 | } @else { 263 | background-color: $value; 264 | } 265 | } 266 | } 267 | 268 | &-light { 269 | &, 270 | a, 271 | .alert-link { 272 | color: $body-color; 273 | } 274 | } 275 | } 276 | 277 | // Containers 278 | 279 | .modal, 280 | .toast { 281 | .btn-close { 282 | background-image: url("data:image/svg+xml,"); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /doc/INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installing and setting up SunCAE 2 | 3 | > [!TIP] 4 | > If you just want to use SunCAE without installing it, you can do so with the [live demo](https://www.caeplex.com/suncae). 5 | 6 | > [!NOTE] 7 | > Mind the license of SunCAE itself and the license of all the related packages that SunCAE uses to make sure you are not infringing any license. 8 | 9 | This document explains how to set up [SunCAE](https://www.seamplex.com/suncae) so as to serve one or more clients. 10 | A basic installation can be done relatively simple, even without understanding the meaning of the commands. 11 | Keep in mind that a full-fledged installation being able to serve different users might need deep understanding of networking administration and operating systems details. 12 | 13 | ## Architectures 14 | 15 | The code is aimed at being run on Unix systems. Specifically, Debian GNU/Linux. 16 | There might be ways of making SunCAE run on other architectures. 17 | If you happen to know how, please help us by explaining how. 18 | 19 | ## Cloning the repository 20 | 21 | The first step would be to clone the SunCAE repository: 22 | 23 | ``` 24 | git clone https://github.com/seamplex/suncae 25 | cd suncae 26 | ``` 27 | 28 | 29 | ## Dependencies 30 | 31 | The repository only hosts particular code and files which are not already available somewhere else. 32 | The latter include 33 | 34 | * meshers and solvers executables (e.g. `gmsh`, `feenox`, `ccx`, etc.) 35 | * Javascript libraries (e.g. `x3dom.js`) 36 | * CSS and fonts (e.g. `bootstrap.css`) 37 | 38 | 39 | ### Common 40 | 41 | SunCAE needs some functionality which is provided by packages which are commonly available in the most common GNU/Linux distribution repositories. Ranging from the web server itself, script interpreters (e.g. PHP and Bash) and other standard Unix utilities, this line (or a similar one for a non-Debian distribution) should be enough: 42 | 43 | ``` 44 | sudo apt-get install git unzip patchelf wget php-cli php-yaml gnuplot 45 | ``` 46 | 47 | ### Particular 48 | 49 | The (free and open source) meshers, solvers and required libraries and fonts can be downloaded by executing the `deps.sh` script in SunCAE's root directory: 50 | 51 | ``` 52 | ./deps.sh 53 | ``` 54 | 55 | > [!IMPORTANT] 56 | > Run the script from SunCAE's root directory, i.e. 57 | > 58 | > ``` 59 | > ./deps.sh 60 | > ``` 61 | > 62 | > and **not** from the parent (or any other directory) like 63 | > 64 | > ``` 65 | > suncae/dep.sh 66 | > ``` 67 | 68 | > [!TIP] 69 | > The script will try to download and copy the dependencies inside SunCAE's directories (ignored by Git) only if they are not already copied. To force the download and copy (say if the version changed), you can either delete the dependencies or pass `--force` to `deps.sh` 70 | > 71 | > ``` 72 | > ./deps.sh --force 73 | > ``` 74 | 75 | 76 | ## The web server 77 | 78 | SunCAE can be hosted with any web server capable of executing PHP scripts. 79 | The main entry point is under directory `html`. 80 | 81 | All the user information is stored as files under the directory `data`. 82 | That is to say, there is not **database** (either SQL or Mongo-like). 83 | Just plain (Git-tracked) files. 84 | 85 | > [!TIP] 86 | > Backups are as simple as `cp`ing (or `rsync`ing, `tar`ring, etc.) the directory `data` somewhere else. 87 | 88 | 89 | 90 | ### PHP's internal web server 91 | 92 | The `php-cli` package includes a simple web server which is enough to host SunCAE for single-user mode (and it is even handy for debugging purposes). 93 | Just run `php` with the `-S` option. Choose an available port and pass the `html` directory in the `-t` option (or go into the `html` directory and run `php -S` from there without `-t`): 94 | 95 | ```terminal 96 | php -S localhost:8000 -t html 97 | ``` 98 | 99 | > [!IMPORTANT] 100 | > The first time that SunCAE needs to use the `data` directory, it will be created and owned by the user running the server, which in this case will be the user that ran `php`. 101 | > Mind ownerships and permissions if you then change from the internal web server to a professional one such as Apache. 102 | 103 | ### Apache 104 | 105 | Configure Apache to serve the `html` directory in SunCAE's repository. 106 | By default, Apache's root directory is `/var/www/html`. 107 | 108 | A quick hack is to make sure that SunCAE’s [`html`](html) directory is available to be served. For instance, in the default installation you could do 109 | 110 | ```terminal 111 | ln -s html /var/www/html/suncae 112 | ``` 113 | 114 | and then SunCAE would be available at . 115 | 116 | > [!WARNING] 117 | > Mind Apache's policies about symbolic links. They are not straightforward, so symlinking SunCAE's `html` directory into Apache's `html` directory might not work out of the box. 118 | 119 | 120 | * If you do not have experience with Apache, you might want to delete the default `/var/www` tree and clone SunCAE there. 121 | * If you have experience with Apache, there is little more to add. 122 | 123 | 124 | > [!IMPORTANT] 125 | > The first time that SunCAE needs to use the `data` directory, it will be created and owned by the user running the server, which in this case by default is `www-data`. 126 | > Mind ownerships and permissions when accessing `data`. 127 | 128 | ### Other servers 129 | 130 | We do not know. 131 | 132 | 133 | ## Stack configuration 134 | 135 | The file [`conf.php`](../conf.php) in SunCAE's root directory controls the choices of the implementations of the different components for the current instance of SunCAE being served: 136 | 137 | ```php 138 | $auth = "single-user"; 139 | $ux = "faster-than-quick"; 140 | $cadimporter = "upload"; 141 | $cadprocessor = "gmsh"; 142 | $runner = "local"; 143 | $max_nodes = 100e3; 144 | ``` 145 | 146 | This means that 147 | 148 | 1. the same server can change the implementations by changing the content of `conf.php` dynamically 149 | 2. different servers (or the same server with different entry points) can serve different implementations by serving different `html` directories whose parent's `conf.php` is different. 150 | 3. any other combination is also possible, e.g. an interactive HTML-based panel that modifies `conf.php` on demand or that clones a new instance of SunCAE in an arbitrary location (and configures Apache to serve it). 151 | 152 | Read the [Design Manual](design.md) for details about what each of the definitions mean. 153 | 154 | --------------------------------------------------------------------------------