diff --git a/README.md b/README.md
index 63cb149..0a77902 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,30 @@
-# Galaxy Show and Tell
+# MCFE Galaxy
-This repository serves as an example for RSEs to self host Galaxy. The repository contains a `docker-compose.yml` file that can be used to start a Galaxy instance with a few tools pre-installed.
+## Triggering events
-Some guides are given below to help you get started!
+Randall includes services which listen for events on a MQTT broker.
+These events cause further events to be triggered which may include:
+- updating the metadata in Fuseki
+- running workflows on Galaxy
-If you are interested in some context for this repo some slides [are available](https://uomresearchit.github.io/Galaxy-Show-And-Tell/).
+To see an example of this in action you can run the following command:
-## Tutorials
+```bash
+mosquitto_pub -u {{YOUR_USER}} -P {{YOUR_PASS}} -t '/parameter/update/01234' -m '{"MajorRadius": "8.5"}
+```
-[Tutorial: From bootstrap to workflows! Getting started with Galaxy](docs/getting-started.md)
+If you want to see the logs of the Crater service, you can do so by running:
-## How to guides
+```bash
+mosquitto_sub -u {{YOUR_USER}} -P {{YOUR_PASS}} -t '/#' -v
+```
-[How to: Add tools to your Galaxy](docs/add-tools.md)
+## Useful web interfaces
-[How to: Add public workflows your Galaxy](docs/add-workflows.md)
-
-## Useful resources
-
-The best introductions to Galaxy are given by the Galaxy project itself. You may choose to start your Galaxy journey by reading the following:
-[Introduction to Galaxy Analyses](https://training.galaxyproject.org/training-material/topics/introduction/)
-
-If you are an RSE looking to develop tooling for Galaxy then generic documentation can be found at: [Developing Galaxy Tools](https://training.galaxyproject.org/training-material/topics/dev/#st-tooldev)
-
-You could also explore the Galaxy training material for coaching Galaxy administrators, however, this is not necessary to get started and the techniques may not necessarily apply to a self-hosted instance (the Galaxy Training Network assume deployment through Ansible, we use Docker Compose).
-https://training.galaxyproject.org/training-material/topics/admin/
-
-## Technical reference
-
-The `.env` file is self documented via the `env.template` file.
+Randall is made of serveral tools which provide web interfaces. These include:
+- Fuseki: http://fuseki.localhost (default username: `admin`, default password: `admin`)
+ * This is a triplestore which stores the metadata
+- Galaxy: http://galaxy.localhost (username and password from .env file)
+ * This is a workflow manager which runs workflows
+- Traefik: http://localhost:8888 (no authentication)
+ * This is a reverse proxy which routes traffic to the appropriate service
diff --git a/docker-compose.yml b/compose.yaml
similarity index 91%
rename from docker-compose.yml
rename to compose.yaml
index 1f6803e..5593228 100644
--- a/docker-compose.yml
+++ b/compose.yaml
@@ -26,10 +26,11 @@ services:
USER root
RUN apt update && apt -y install docker.io
USER galaxy
- COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
+ COPY --from=ghcr.io/astral-sh/uv:0.8.15 /uv /uvx /bin/
ENV HOME=/tmp
ENV UV_CACHE=/tmp/uv_cache
- RUN uv add boto3
+ RUN sed -i '/wheels.galaxyproject.org/d' pyproject.toml
+ RUN uv add boto3 --index https://pypi.org/simple
restart: unless-stopped
group_add:
- ${DOCKER_GID}
@@ -49,8 +50,9 @@ services:
- ./galaxy/tusd:/usr/local/sbin/tusd
# Populate the instance
- ./galaxy/tools:/galaxy/server/tools/
+ - ./galaxy/lib/mcfe_datatypes.py:/galaxy/server/lib/galaxy/datatypes/mcfe_datatypes.py
# Add custom configuration files
- - ./galaxy/config/tool_conf.xml:/galaxy/server/config/tool_conf.xml.sample
+ - ./galaxy/config/tool_conf.xml:/galaxy/server/config/tool_conf.xml
- ./galaxy/config/datatypes_conf.xml:/galaxy/server/config/datatypes_conf.xml
# Provide docker-in-docker
- /var/run/docker.sock:/var/run/docker.sock:ro
@@ -86,11 +88,13 @@ services:
build:
context: .
dockerfile_inline: |
- FROM alpine:3.22
- RUN apk update && apk add gettext bash
+ FROM ubuntu:22.04
+ RUN apt update && apt install -y gettext
COPY ./galaxy/init/template.sh /template.sh
container_name: ${PROJECT_NAME}-galaxy-template-eval
env_file: .env
+ environment:
+ REPO_PATH: ${PWD}
volumes:
- ./galaxy/config/:/work
- galaxy-store:/galaxy/server/
@@ -146,7 +150,6 @@ services:
minio:
condition: service_healthy
volumes:
- - ./minio/init/bootstrap.sh:/bootstrap.sh
- ./minio/data:/data:ro
networks:
- galaxy
diff --git a/env.template b/env.template
index f3d4597..0f4c78d 100644
--- a/env.template
+++ b/env.template
@@ -6,9 +6,6 @@
# ------------------------------------------------------------------------------
# Parameters to configure the Galaxy instance
-# The path to this repo
-REPO_PATH=/home/owool/Projects/2025/RSE-Galaxy
-
# The group ID of the docker group on the host machine, to allow the Galaxy
# container to access the docker socket.
@@ -23,7 +20,8 @@ GALAXY_ADMIN_PASS={{adminadmin}}
GALAXY_API_KEY={{iamanadminyouknow}}
# Galaxy config
-GALAXY_VERSION=25.0.2
+GALAXY_VERSION={{25.0.2}}
+GALAXY_URL={{https://mygalaxy.org}}
# ------------------------------------------------------------------------------
# General configuration parameters
diff --git a/galaxy/config/datatypes_conf.xml b/galaxy/config/datatypes_conf.xml
index 20fc503..65cadfe 100644
--- a/galaxy/config/datatypes_conf.xml
+++ b/galaxy/config/datatypes_conf.xml
@@ -694,6 +694,10 @@
+
+
+
+
diff --git a/galaxy/config/galaxy.yml.template b/galaxy/config/galaxy.yml.template
index b049069..da96a3c 100644
--- a/galaxy/config/galaxy.yml.template
+++ b/galaxy/config/galaxy.yml.template
@@ -2,17 +2,16 @@ gravity:
gunicorn:
bind: 0.0.0.0:8080
workers: 2
- extra_ars: '--forwarded-allow-ips="*"'
+ extra_args: '--forwarded-allow-ips="*"'
preload: true
celery:
concurrency: 2
loglevel: WARNING
tusd:
enable: true
- host: 0.0.0.0
- port: 1080
tusd_path: /usr/local/sbin/tusd
upload_dir: /galaxy/server/data/tus/
+ extra_args: '-verbose'
# handlers:
# handler:
# processes: 2
@@ -28,12 +27,15 @@ gravity:
galaxy:
# Main config
- admin_users:
- - admin@example.org
+ admin_users: admin@example.org
bootstrap_admin_api_key: '$GALAXY_API_KEY'
id_secret: 'morestufftoreplace'
log_level: WARNING
+ # Use remote users
+ use_remote_user: true
+ remote_user_maildomain: example.org
+
# Watch file changes
watch_tools: 'true'
watch_job_rules: 'true'
@@ -61,7 +63,7 @@ galaxy:
new_user_dataset_access_role_default_private: true
# TUS
- galaxy_infrastructure_url: "http://localhost:8080"
+ galaxy_infrastructure_url: "$GALAXY_URL"
tus_upload_store: /galaxy/server/data/tus/
# SQL Performance
@@ -75,7 +77,6 @@ galaxy:
retry_job_output_collection: 3
# Debugging
- cleanup_job: onsuccess
allow_user_impersonation: true
# Object Store
@@ -117,7 +118,7 @@ galaxy:
shed_data_manager_config_file: /galaxy/server/config/shed_data_manager_conf.xml
shed_tool_config_file: /galaxy/server/config/shed_tool_conf.xml
shed_tool_data_table_config: /galaxy/server/config/shed_tool_data_table_conf.xml
- tool_config_file: /galaxy/server/config/tool_conf.xml.sample
+ tool_config_file: /galaxy/server/config/tool_conf.xml
job_config_file: /galaxy/server/config/job_conf.xml
tool_data_path: /galaxy/server/database/tool_data
tool_data_table_config_path: /galaxy/server/config/tool_data_table_conf.xml.sample
diff --git a/galaxy/config/job_conf.xml.template b/galaxy/config/job_conf.xml.template
index 0a76d4e..1ce78b7 100644
--- a/galaxy/config/job_conf.xml.template
+++ b/galaxy/config/job_conf.xml.template
@@ -20,11 +20,16 @@
true
- galaxy-store:/galaxy/server
- true
+ galaxy-store:/galaxy/server,$REPO_PATH/galaxy/tools:/galaxy/server/tools
+ false
+ 0
true
+ --entrypoint '' --env PATH=/opt/openmpi/bin:/opt/rh/gcc-toolset-12/root/usr/bin:$PATH
+
+
+
diff --git a/galaxy/config/tool_conf.xml b/galaxy/config/tool_conf.xml
index 7b891d5..e2429dc 100644
--- a/galaxy/config/tool_conf.xml
+++ b/galaxy/config/tool_conf.xml
@@ -60,4 +60,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/galaxy/lib/mcfe_datatypes.py b/galaxy/lib/mcfe_datatypes.py
new file mode 100644
index 0000000..ea69e3f
--- /dev/null
+++ b/galaxy/lib/mcfe_datatypes.py
@@ -0,0 +1,74 @@
+"""
+Classes for MCFE added datatypes
+"""
+
+from galaxy.datatypes import data
+from galaxy.datatypes.metadata import MetadataElement
+
+import os
+import logging
+
+log = logging.getLogger(__name__)
+
+class vtp(data.Data):
+ file_ext = "vtp"
+
+class vtk(data.Data):
+ file_ext = "vtk"
+
+class vtu(data.Data):
+ file_ext = "vtu"
+
+class pvtu(data.Data):
+ file_ext = "pvtu"
+
+class usd(data.Data):
+ file_ext = "usd"
+
+class usda(data.Data):
+ file_ext = "udsa"
+
+class usdc(data.Data):
+ file_ext = "usdc"
+
+class usdz(data.Data):
+ file_ext = "usdz"
+
+class obj(data.Data):
+ file_ext = "obj"
+
+class stp(data.Data):
+ file_ext = "stp"
+
+class step(data.Data):
+ file_ext = "step"
+
+class h5(data.Data):
+ file_ext = "h5"
+
+class h5m(data.Data):
+ file_ext = "h5m"
+
+class out(data.Data):
+ file_ext = "out"
+
+class xml(data.Data):
+ file_ext = "xml"
+
+class stl(data.Data):
+ file_ext = "stl"
+
+class npz(data.Data):
+ file_ext = "npz"
+
+class yaml(data.Data):
+ file_ext = "yaml"
+
+class odb(data.Data):
+ file_ext = "odb"
+
+class inp(data.Data):
+ file_ext = "inp"
+
+class pth(data.Data):
+ file_ext = "pth"
diff --git a/galaxy/tools/CylinderBCMesh/CylinderBCMesh.xml b/galaxy/tools/CylinderBCMesh/CylinderBCMesh.xml
new file mode 100644
index 0000000..30f159c
--- /dev/null
+++ b/galaxy/tools/CylinderBCMesh/CylinderBCMesh.xml
@@ -0,0 +1,32 @@
+
+
+ Sets Boundary Conditions and Meshes for Cylinders
+
+
+ nttaudom/gmsh:08.12.23
+
+
+
+ &1 &&
+
+ mv outputfile.msh '$Output_File'
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+ This tool takes a cylindrical reactor STEP file and creates a mesh - including setting boundary conditions
+ Expected output: .msh
+
+
+
diff --git a/galaxy/tools/CylinderBCMesh/cylinder_bc_mesh.py b/galaxy/tools/CylinderBCMesh/cylinder_bc_mesh.py
new file mode 100644
index 0000000..7c4f7e8
--- /dev/null
+++ b/galaxy/tools/CylinderBCMesh/cylinder_bc_mesh.py
@@ -0,0 +1,108 @@
+"""
+This module contains the `create_geometry_and_mesh` function which is used for
+creating and meshing a cylindrical geometry using the gmsh Python API.
+
+The function takes a dictionary of cylinder data as input, which includes the
+number of layers in the cylinder and a mesh factor for controlling the mesh
+density. It then creates the geometry of the cylinder, applies the mesh, and
+creates physical groups for the surfaces and volumes of the cylinder.
+
+The module also includes functionality for parsing command-line arguments and
+reading parameters from a JSON file. This allows the cylinder data to be easily
+specified and modified.
+
+Dependencies:
+ argparse: A standard library module for parsing command-line arguments.
+ json: A standard library module for working with JSON data.
+ os: A standard library module for interacting with the operating system.
+ gmsh: A Python library for scripting Gmsh, a 3D finite element mesh generator.
+
+Author: Luis Garcia
+"""
+
+import argparse
+import json
+import os
+import gmsh
+
+
+def create_geometry_and_mesh(cylinder_data):
+ """
+ Creates and meshes a cylindrical geometry using the gmsh Python API.
+
+ Parameters:
+ cylinder_data (dict): A dictionary containing the number of layers in the
+ cylinder and a mesh factor for controlling the mesh density.
+
+ Returns:
+ None: The function creates the geometry and mesh, but does not return any value.
+ """
+ gmsh.initialize()
+ gmsh.model.add("cylinder_model")
+
+ step_file = "reactor.step"
+ num_layers = cylinder_data['num_layers']
+ mesh_factor = cylinder_data['mesh_factor']
+
+ gmsh.option.setNumber("Mesh.CharacteristicLengthFactor", mesh_factor)
+ gmsh.option.setNumber("Mesh.Algorithm", 1) # Delaunay
+ gmsh.option.setNumber("Mesh.Algorithm3D", 1) # 3D algorithm
+
+ # Import STEP file
+ gmsh.model.occ.importShapes(step_file)
+
+ # Synchronize necessary due to use of OpenCASCADE
+ gmsh.model.occ.synchronize()
+
+ j = 1 # Initial tag offset
+
+ # Creating physical groups for surfaces
+ for i in range(1, num_layers + 1):
+ wall_tag = i * 3 - 2 # Wall is the long length of cylinder
+ top_tag = i * 3 - 1 # Top circular face
+ bottom_tag = i * 3 # Bottom circular face
+
+ if i <= 2:
+ gmsh.model.addPhysicalGroup(2, [wall_tag], wall_tag)
+ gmsh.model.setPhysicalName(2, wall_tag, f"cyl_{i}_wall")
+ gmsh.model.addPhysicalGroup(2, [top_tag], top_tag)
+ gmsh.model.setPhysicalName(2, top_tag, f"cyl_{i}_top")
+ gmsh.model.addPhysicalGroup(2, [bottom_tag], bottom_tag)
+ gmsh.model.setPhysicalName(2, bottom_tag, f"cyl_{i}_bottom")
+ else:
+ gmsh.model.addPhysicalGroup(2, [wall_tag + j], wall_tag + j)
+ gmsh.model.setPhysicalName(2, wall_tag, f"cyl_{i}_wall")
+ gmsh.model.addPhysicalGroup(2, [top_tag + j], top_tag + j)
+ gmsh.model.setPhysicalName(2, top_tag, f"cyl_{i}_top")
+ gmsh.model.addPhysicalGroup(2, [bottom_tag + j], bottom_tag + j)
+ gmsh.model.setPhysicalName(2, bottom_tag, f"cyl_{i}_bottom")
+ j += 1
+
+ for i in range(1, num_layers + 1):
+ volume_tag = i
+ gmsh.model.addPhysicalGroup(3, [volume_tag], tag=i)
+ gmsh.model.setPhysicalName(3, i, f"cyl_{i}_volume")
+
+ # Generate mesh
+ gmsh.model.mesh.generate(3)
+
+ # Save mesh (optional, uncomment to save)
+ gmsh.write("meshed_model.msh")
+
+ gmsh.finalize()
+
+
+# Main script
+parser = argparse.ArgumentParser()
+parser.add_argument('file_path')
+args = parser.parse_args()
+file_path = args.file_path
+
+with open(file_path, 'r', encoding='utf-8') as file:
+ data = json.load(file)
+
+cylinder_data_input = data['cylinder_data']
+create_geometry_and_mesh(cylinder_data_input)
+
+
+os.rename("meshed_model.msh", "outputfile.msh")
diff --git a/galaxy/tools/LICENSE b/galaxy/tools/LICENSE
new file mode 100644
index 0000000..692606e
--- /dev/null
+++ b/galaxy/tools/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Metaverse Collaboration for Fusion Energy
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/galaxy/tools/MQTT_pub/MQTT_pub.xml b/galaxy/tools/MQTT_pub/MQTT_pub.xml
new file mode 100644
index 0000000..72146b3
--- /dev/null
+++ b/galaxy/tools/MQTT_pub/MQTT_pub.xml
@@ -0,0 +1,36 @@
+
+
+ MQTT Message Publisher
+
+
+ williamjsmith15/galaxy-listener:23052024
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MQTT Message Publisher, will send a message with a given topic, on server address
+ provided, with username and password.
+
+
+
+
+
+
diff --git a/galaxy/tools/MQTT_pub/send.py b/galaxy/tools/MQTT_pub/send.py
new file mode 100644
index 0000000..9edefe7
--- /dev/null
+++ b/galaxy/tools/MQTT_pub/send.py
@@ -0,0 +1,89 @@
+import random
+from paho.mqtt import client as mqtt_client
+import argparse
+import json
+
+parser = argparse.ArgumentParser(
+ prog='send.py',
+ description="Useage: python send.py '$Server' '$Topic' '$Message' '$Username' '$Password'"
+)
+
+parser.add_argument(
+ 'Server',
+ help='Broker server address. Format: String'
+)
+parser.add_argument(
+ 'Topic',
+ help='Topic to send the message on. Format: String'
+)
+parser.add_argument(
+ 'Message',
+ help='Message to send. Format: JSON'
+)
+parser.add_argument(
+ 'Username',
+ help='Username to connect to the broker. Format: String'
+)
+parser.add_argument(
+ 'Password',
+ help='Password to connect to the broker. Format: String'
+)
+
+args = parser.parse_args()
+
+broker = args.Server
+topic = args.Topic
+message_file = args.Message
+username = args.Username
+password = args.Password
+
+port = 80
+
+# Read json from the message file
+with open(message_file, 'r') as file:
+ message = json.load(file)
+
+message = json.dumps(message)
+
+# Generate random client ID so no conflicts with other clients
+client_id = f"galaxy-tool-sender-{random.randint(0, 999999)}"
+
+
+def on_connect(client, userdata, flags, reason_code, properties):
+ print("Connected with reason code: " + str(reason_code))
+
+
+def connect_mqtt():
+ client = mqtt_client.Client(
+ client_id=client_id,
+ transport="websockets",
+ callback_api_version=mqtt_client.CallbackAPIVersion.VERSION2
+ )
+ client.username_pw_set(username, password)
+ client.ws_set_options(path="/ws")
+ client.on_connect = on_connect
+ client.connect(broker, port)
+ return client
+
+
+def publish(client, topic, message):
+ client.loop_start()
+ infot = client.publish(topic, message, qos=1)
+ infot.wait_for_publish()
+ status = infot[0]
+ client.loop_stop()
+ return status
+
+
+def send(topic, message):
+ client = connect_mqtt()
+ print(message)
+ status = publish(client, topic, message)
+ if status == 0:
+ print(f"Sent `{message}` to topic `{topic}`")
+ else:
+ print(f"Failed to send message {message} to topic {topic}")
+
+
+if __name__ == '__main__':
+ send(topic, message)
diff --git a/galaxy/tools/PINN_training_point_gen/PINN_training_point_gen.py b/galaxy/tools/PINN_training_point_gen/PINN_training_point_gen.py
new file mode 100644
index 0000000..744f49d
--- /dev/null
+++ b/galaxy/tools/PINN_training_point_gen/PINN_training_point_gen.py
@@ -0,0 +1,38 @@
+import argparse
+from modulus.sym.geometry.tessellation import Tessellation
+from modulus.sym.geometry import Geometry
+
+parser = argparse.ArgumentParser(
+ prog="heatflux-to-PINN.py",
+ description="Usage: python heatflux-to-PINN.py -n ",
+)
+parser.add_argument(
+ "-n", "--nr_points", help="number of points to sample", type=int
+)
+
+args = parser.parse_args()
+
+number_of_points = args.nr_points
+
+reactorwall = Tessellation.from_stl("Output_Vessel.stl", airtight=True)
+
+# interior
+sample_point = reactorwall.sample_interior(
+ nr_points=number_of_points,
+ compute_sdf_derivatives=False
+)
+
+data_tuples = list(zip(
+ sample_point['x'],
+ sample_point['y'],
+ sample_point['z'],
+ sample_point['area']
+))
+print(data_tuples)
+
+file_path = 'test.txt'
+with open(file_path, 'w') as file:
+ for data_tuple in data_tuples:
+ data_values = [str(value.item()) for value in data_tuple]
+ tuple_string = ' '.join(data_values)
+ file.write(tuple_string + '\n')
diff --git a/galaxy/tools/PINN_training_point_gen/PINN_training_point_gen.xml b/galaxy/tools/PINN_training_point_gen/PINN_training_point_gen.xml
new file mode 100644
index 0000000..002ed3a
--- /dev/null
+++ b/galaxy/tools/PINN_training_point_gen/PINN_training_point_gen.xml
@@ -0,0 +1,37 @@
+
+
+ Sample point generator for interior points for training PINN
+
+
+ nvcr.io/nvidia/modulus/modulus:23.05
+
+
+
+ &1 &&
+ mv test.txt '$Points'
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+ Sample point generator for interior points for training PINN
+
+
+
+
+
+
\ No newline at end of file
diff --git a/galaxy/tools/README.md b/galaxy/tools/README.md
new file mode 100644
index 0000000..2c6fcd4
--- /dev/null
+++ b/galaxy/tools/README.md
@@ -0,0 +1 @@
+# Galaxy-Tools
\ No newline at end of file
diff --git a/galaxy/tools/USD_to_h5m/USD_to_h5m.xml b/galaxy/tools/USD_to_h5m/USD_to_h5m.xml
new file mode 100644
index 0000000..be0c41c
--- /dev/null
+++ b/galaxy/tools/USD_to_h5m/USD_to_h5m.xml
@@ -0,0 +1,32 @@
+
+ CAD conversion from USD to h5m
+
+
+ williamjsmith15/omniverse-openmc:05062023
+
+
+
+
+ &1 &&
+ mv dagmc.h5m '$h5m_CAD'
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+ This tool takes in a USD geometry and up axis to produce a h5m version of the CAD to be run in OpenMC simulations.
+
+
+
+
+
\ No newline at end of file
diff --git a/galaxy/tools/USD_to_h5m/usd_h5m.py b/galaxy/tools/USD_to_h5m/usd_h5m.py
new file mode 100644
index 0000000..8dabf81
--- /dev/null
+++ b/galaxy/tools/USD_to_h5m/usd_h5m.py
@@ -0,0 +1,414 @@
+from typing import Iterable
+from pxr import Usd, UsdShade
+from vertices_to_h5m import vertices_to_h5m
+import numpy as np
+import os, tempfile
+import argparse
+
+"""
+Converter from USD to h5m for use in a Omniverse - OpenMC workflow (and other related workflows :))
+
+Usage:
+ On the OpenMC docker container (currently williamjsmith15/omniverse-openmc)
+ Run:
+ python usd_h5m.py
+ usd_to_convert - Input USD file (omniverse output format)
+ h5m_as_output - Output h5m file (DAGMC input format)
+ up-axis - Set the up-axis of the conversion (Omniverse is annoying with its choice of up axis (they choose Y whereas it should be Z for OpenMC...))
+"""
+
+# USD Helper Functions
+def getValidProperty (primative, parameterName):
+ # Get param
+ prop = primative.GetProperty(parameterName)
+
+ # Test validity
+ if ( type(prop) == type(Usd.Attribute())): # is valid
+ return prop.Get()
+ else: # is not
+ print("Requested parameter is not valid!")
+ return None
+ #raise Exception("Requested parameter is not valid!")
+
+def getProperty (primative, parameterName): # Unsafe
+ # Get param
+ prop = primative.GetProperty(parameterName).Get()
+
+ return prop
+
+def propertyIsValid (primative, parameterName):
+ # Get param
+ prop = primative.GetProperty(parameterName)
+
+ # Test validity
+ if ( type(prop) == type(Usd.Attribute())): # is valid
+ return True
+ else:
+ return False
+
+def get_rot(rotation):
+ # Calculates rotation matrix given a x,y,z rotation in degrees
+ factor = 2.0 * np.pi / 360.0 # Convert to radians
+ x_angle, y_angle, z_angle = rotation[0]*factor, rotation[1]*factor, rotation[2]*factor
+ x_rot = np.array([[1,0,0],[0,np.cos(x_angle),-np.sin(x_angle)],[0,np.sin(x_angle),np.cos(x_angle)]], dtype='float64')
+ y_rot = np.array([[np.cos(y_angle),0,np.sin(y_angle)],[0,1,0],[-np.sin(y_angle),0,np.cos(y_angle)]], dtype='float64')
+ z_rot = np.array([[np.cos(z_angle),-np.sin(z_angle),0],[np.sin(z_angle),np.cos(z_angle),0],[0,0,1]], dtype='float64')
+ rot_mat = np.dot(np.dot(x_rot,y_rot),z_rot)
+ return rot_mat
+
+_ALLOWED_MATERIAL_PURPOSES = (
+ UsdShade.Tokens.full,
+ UsdShade.Tokens.preview,
+ UsdShade.Tokens.allPurpose,
+)
+
+def get_bound_material(
+ prim, material_purpose=UsdShade.Tokens.allPurpose, collection=""
+):
+ # From https://github.com/ColinKennedy/USD-Cookbook/blob/master/tricks/bound_material_finder/python/material_binding_api.py 30/01/23
+ """
+ Find the strongest material for some prim / purpose / collection.
+ If no material is found for `prim`, this function will check every
+ ancestor of Prim for a bound material and return that, instead.
+ Reference:
+ https://graphics.pixar.com/usd/docs/UsdShade-Material-Assignment.html#UsdShadeMaterialAssignment-MaterialResolve:DeterminingtheBoundMaterialforanyGeometryPrim
+ Args:
+ prim (`pxr.Usd.Prim`):
+ The path to begin looking for material bindings.
+ material_purpose (str, optional):
+ A specific name to filter materials by. Available options
+ are: `UsdShade.Tokens.full`, `UsdShade.Tokens.preview`,
+ or `UsdShade.Tokens.allPurpose`.
+ Default: `UsdShade.Tokens.allPurpose`
+ collection (str, optional):
+ The name of a collection to filter by, for any found
+ collection bindings. If not collection name is given then
+ the strongest collection is used, instead. Though generally,
+ it's recommended to always provide a collection name if you
+ can. Default: "".
+ Raises:
+ ValueError:
+ If `prim` is invalid or if `material_purpose` is not an allowed purpose.
+ Returns:
+ `pxr.UsdShade.Material` or NoneType:
+ The strongest bound material, if one is assigned.
+ """
+ def is_collection_binding_stronger_than_descendents(binding):
+ return (
+ UsdShade.MaterialBindingAPI.GetMaterialBindingStrength(
+ binding.GetBindingRel()
+ )
+ == "strongerThanDescendents"
+ )
+
+ def is_binding_stronger_than_descendents(binding, purpose):
+ """bool: Check if the given binding/purpose is allowed to override any descendent bindings."""
+ return (
+ UsdShade.MaterialBindingAPI.GetMaterialBindingStrength(
+ binding.GetDirectBindingRel(materialPurpose=purpose)
+ )
+ == "strongerThanDescendents"
+ )
+
+ def get_collection_material_bindings_for_purpose(binding, purpose):
+ """Find the closest ancestral collection bindings for some `purpose`.
+ Args:
+ binding (`pxr.UsdShade.MaterialBindingAPI`):
+ The material binding that will be used to search
+ for a direct binding.
+ purpose (str):
+ The name of some direct-binding purpose to filter by. If
+ no name is given, any direct-binding that is found gets
+ returned.
+ Returns:
+ list[`pxr.UsdShade.MaterialBindingAPI.CollectionBinding`]:
+ The found bindings, if any could be found.
+ """
+ # XXX : Note, Normally I'd just do
+ # `UsdShadeMaterialBindingAPI.GetCollectionBindings` but, for
+ # some reason, `binding.GetCollectionBindings(purpose)` does not
+ # yield the same result as parsing the relationships, manually.
+ # Maybe it's a bug?
+ #
+ # return binding.GetCollectionBindings(purpose)
+ #
+ parent = binding.GetPrim()
+
+ # TODO : We're doing quadratic work here... not sure how to improve this section
+ while not parent.IsPseudoRoot():
+ binding = binding.__class__(parent)
+
+ material_bindings = [
+ UsdShade.MaterialBindingAPI.CollectionBinding(relationship)
+ for relationship in binding.GetCollectionBindingRels(purpose)
+ if relationship.IsValid()
+ ]
+
+ if material_bindings:
+ return material_bindings
+
+ parent = parent.GetParent()
+
+ return []
+
+ def get_direct_bound_material_for_purpose(binding, purpose):
+ """Find the bound material, using direct binding, if it exists.
+ Args:
+ binding (`pxr.UsdShade.MaterialBindingAPI`):
+ The material binding that will be used to search
+ for a direct binding.
+ purpose (str):
+ The name of some direct-binding purpose to filter by. If
+ no name is given, any direct-binding that is found gets
+ returned.
+ Returns:
+ `pxr.UsdShade.Material` or NoneType: The found material, if one could be found.
+ """
+ relationship = binding.GetDirectBindingRel(materialPurpose=purpose)
+ direct = UsdShade.MaterialBindingAPI.DirectBinding(relationship)
+
+ if not direct.GetMaterial():
+ return None
+
+ material = direct.GetMaterialPath()
+ prim = binding.GetPrim().GetStage().GetPrimAtPath(material)
+
+ if not prim.IsValid():
+ return None
+
+ return UsdShade.Material(prim)
+
+ if not prim.IsValid():
+ raise ValueError('Prim "{prim}" is not valid.'.format(prim=prim))
+
+ if material_purpose not in _ALLOWED_MATERIAL_PURPOSES:
+ raise ValueError(
+ 'Purpose "{material_purpose}" is not valid. Options were, "{options}".'.format(
+ material_purpose=material_purpose,
+ options=sorted(_ALLOWED_MATERIAL_PURPOSES),
+ )
+ )
+
+ purposes = {material_purpose, UsdShade.Tokens.allPurpose}
+
+ for purpose in purposes:
+ material = None
+ parent = prim
+
+ while not parent.IsPseudoRoot():
+ binding = UsdShade.MaterialBindingAPI(parent)
+
+ if not material or is_binding_stronger_than_descendents(binding, purpose):
+ material = get_direct_bound_material_for_purpose(binding, purpose)
+
+ for collection_binding in get_collection_material_bindings_for_purpose(
+ binding, purpose
+ ):
+ binding_collection = collection_binding.GetCollection()
+
+ if collection and binding_collection.GetName() != collection:
+ continue
+
+ membership = binding_collection.ComputeMembershipQuery()
+
+ if membership.IsPathIncluded(parent.GetPath()) and (
+ not material
+ or is_collection_binding_stronger_than_descendents(
+ collection_binding
+ )
+ ):
+ material = collection_binding.GetMaterial()
+
+ # Keep searching ancestors until we hit the scene root
+ parent = parent.GetParent()
+
+ if material:
+ return material
+
+
+
+
+
+
+
+
+
+
+
+
+
+class USD_to_h5m:
+ '''
+ Class to convert USD to h5m file format usable for DAGMC, for use with OpenMC
+ '''
+ def __init__(self):
+ # Initialise with blank numpy arrays
+ self.vertices = np.array([])
+ self.triangles = []
+ self.material_tags = []
+
+ def add_USD_file(self, filepath, up_axis):
+ '''
+ Load parts form USD into class with their associated material tags and then converts to triangles for use
+ in the conversion script
+
+ Args:
+ filepath: filepath used to import the USD file with
+ '''
+
+ stage_file = filepath
+ stage = Usd.Stage.Open(stage_file)
+ volumeOffset = 0 # Required as all vertices have to be in a global 1D array (all volumes) => this offsets the indexing
+ # for each individual volume as all vertices for new volumes are added into the same array as previous
+ # volumes (vertices is 1D, triangles is 2D with 2nd dimension have the number of volumes in)
+
+ material_count = 0 # For materials that 'fall through the net'
+
+ for primID, x in enumerate(stage.Traverse()):
+ primType = x.GetTypeName()
+ print(f"PRIM: {str(primType)}")
+ print(f'PrimID is {primID}')
+
+ if str(primType) == 'Mesh' or str(primType) == 'Xform':
+ material_count += 1
+ # Get the material type of the meshes
+ material_name = str(get_bound_material(x))
+ try:
+ material_name = material_name.split('<')[1] # Just get material name from between <>
+ material_name = material_name.split('>')[0] # In form of UsdShade.Material(Usd.Prim())
+ material_name = material_name.split('/')[-1] # Get the last name from file path
+ print(f"Material name is: {material_name}")
+ except:
+ material_name = str(get_bound_material(x))
+ print('No omniverse-set USD material found')
+ print(f'Setting material name to default: {material_name}')
+
+ # Get num of vertecies in elements
+ allVertexCounts = np.array(getValidProperty(x,"faceVertexCounts"))
+ allVertexIndices = np.array(getValidProperty(x,"faceVertexIndices"))
+ print(allVertexCounts.size)
+
+ # Get if there is rotation or translation of the meshes
+ rotation = [0.0,0.0,0.0] if not propertyIsValid(x,"xformOp:rotateXYZ") else list(getProperty(x,"xformOp:rotateXYZ"))
+ translation = np.array([0,0,0]) if not propertyIsValid(x,"xformOp:translate") else np.array(list(getProperty(x,"xformOp:translate")))
+ print(f'Rotation is {rotation}')
+ print(f'Translation is {translation}')
+
+ # Handling for changing the up axis
+ if up_axis == 'X':
+ rotation[1] -= 90.0 # TODO: Check this is correct...
+ elif up_axis == 'Y':
+ rotation[0] += 90.0
+ elif up_axis == 'Z':
+ rotation = rotation
+ else:
+ print('Something went wrong with up_axis')
+
+
+ rot_matrix = get_rot(rotation)
+
+ # TODO: Make the rotation matrix multiplication better! Lazy coding for now...
+ newVertices = np.array(getValidProperty(x,"points"), dtype='float64') # Assign vertices here and add rotation and translation
+ newVertices = np.array([np.dot(rot_matrix,xyz) for xyz in newVertices]) # Have to rotate first before translating as it rotates around the origin
+ newVertices = newVertices + translation
+
+ if self.vertices.size == 0: # For first run though just set vertices to newVertices array
+ self.vertices = newVertices
+ else:
+ self.vertices = np.append(self.vertices, newVertices, axis=0)
+
+ globalCount = 0
+ extraPointCount = 0
+ endOfVolumeIdx = np.size(self.vertices,0)
+ trianglesForVolume = np.array([], dtype="int")
+
+ if np.all(allVertexCounts == 3): # Case where mesh is already in triangles (makes program run much faster - hopeuflly!)
+ trianglesForVolume = allVertexIndices.reshape((allVertexCounts.size,3)) + volumeOffset
+ else:
+ for Count in allVertexCounts:
+ if Count == 3: # Triangle
+ a, b, c = globalCount, globalCount+1, globalCount+2
+ # For explanation of +volumeOffset see initialisation of volumeOffset variable
+ if trianglesForVolume.size == 0: # This whole shenanegans is because i dont know how to use numpy arrays properly.... LEARN
+ trianglesForVolume = np.array([[allVertexIndices[a]+volumeOffset, allVertexIndices[b]+volumeOffset, allVertexIndices[c]+volumeOffset]])
+ else:
+ trianglesForVolume = np.append(trianglesForVolume, np.array([[allVertexIndices[a]+volumeOffset, allVertexIndices[b]+volumeOffset, allVertexIndices[c]+volumeOffset]]), axis=0)
+ elif Count == 4: # Quadrilateral => Split into 2 triangles
+ a, b, c, d = globalCount, globalCount+1, globalCount+2, globalCount+3
+ if trianglesForVolume.size == 0:
+ trianglesForVolume = np.array([[allVertexIndices[a]+volumeOffset, allVertexIndices[b]+volumeOffset, allVertexIndices[c]+volumeOffset]])
+ else:
+ trianglesForVolume = np.append(trianglesForVolume, np.array([[allVertexIndices[a]+volumeOffset, allVertexIndices[b]+volumeOffset, allVertexIndices[c]+volumeOffset]]), axis=0)
+ #Think this may cause issues with some quadrilaterials being split into 2 triangles that overlap and leave a gap - see latex doc
+ trianglesForVolume = np.append(trianglesForVolume, np.array([[allVertexIndices[a]+volumeOffset, allVertexIndices[c]+volumeOffset, allVertexIndices[d]+volumeOffset]]), axis=0)
+ elif Count > 4: # n points to triangles
+ indices = np.array([allVertexIndices[globalCount+i]+volumeOffset for i in range(Count)]) # Get array of indices of points
+ points = np.array([self.vertices[idx] for idx in indices]) # Get points that match those indices
+ # Find mifddle of n-sided polygon => can make triangles from every edge to centre point and add to end of vertices matrix
+ self.vertices = np.append(self.vertices, np.array([[np.average(points[:,dir]) for dir in range(3)]]), axis=0)
+
+ centrePointIdx = endOfVolumeIdx + extraPointCount
+ extraPointCount += 1 # Just added an extra point into the vertices array
+
+ for triangleNum in range(Count):
+ if triangleNum == Count - 1: # Last triangle
+ trianglesForVolume = np.append(trianglesForVolume, np.array([[indices[0], indices[triangleNum], centrePointIdx]]), axis=0)
+ else:
+ if trianglesForVolume.size == 0:
+ trianglesForVolume = np.array([[indices[triangleNum], indices[triangleNum+1], centrePointIdx]])
+ else:
+ trianglesForVolume = np.append(trianglesForVolume, np.array([[indices[triangleNum], indices[triangleNum+1], centrePointIdx]]), axis=0)
+ else:
+ print(f"I don't know what to do with a {Count} count yet...")
+
+ globalCount += Count
+
+
+ self.triangles.append(trianglesForVolume)
+ self.material_tags.append(material_name)
+ shapeVertices = np.shape(newVertices)
+ volumeOffset += shapeVertices[0] + extraPointCount # Account for all points plus any extras added in from Counts>4
+
+ else:
+ print(f"I don't know what to do with a {str(primType)} yet...")
+
+ print("\n\n")
+
+
+ def save_to_h5m(self, filepath):
+ '''
+ Use the verticies saved in the class to convert to h5m using the vertices_to_h5m mini package
+ https://github.com/fusion-energy/vertices_to_h5m
+
+ Args:
+ filename: Filename to save the h5m file, default will be dagmc.h5m (as this is the format required by DAGMC)
+ '''
+
+ vertices_to_h5m(
+ vertices=self.vertices,
+ triangles=self.triangles,
+ material_tags=self.material_tags,
+ h5m_filename=filepath,
+ )
+
+
+
+if __name__ == '__main__':
+
+ parser = argparse.ArgumentParser(
+ description="USD to h5m geometry converter",
+ prog="usd_h5m.py"
+ )
+
+ parser.add_argument("usd_to_convert", help="USD file to convert")
+ parser.add_argument("h5m_as_output", help="h5m filename/location to write")
+ parser.add_argument("up_axis", help="The up axis for the simulation (Omniverse sets y as vertical, OpenMC sets Z)")
+
+ args = parser.parse_args()
+
+ usd_to_convert = args.usd_to_convert
+ h5m_as_output = args.h5m_as_output
+ up_axis = args.up_axis
+ convert = USD_to_h5m()
+ convert.add_USD_file(filepath=usd_to_convert, up_axis=up_axis)
+ convert.save_to_h5m(filepath=h5m_as_output)
\ No newline at end of file
diff --git a/galaxy/tools/abaqus_core/abaqus_core.xml b/galaxy/tools/abaqus_core/abaqus_core.xml
new file mode 100644
index 0000000..d14e1f8
--- /dev/null
+++ b/galaxy/tools/abaqus_core/abaqus_core.xml
@@ -0,0 +1,31 @@
+
+ Abaqus Computaional Core
+
+
+ jacthyli/abaqus2023:latest
+
+
+
+ &1 &&
+ ls -la &&
+ mv *.odb '$Abaqus_out'
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+ This tool takes in a vtp file and converts it to an obj file.
+
+
+
+
+
\ No newline at end of file
diff --git a/galaxy/tools/abaqus_subroutine/abaqus_subroutine.xml b/galaxy/tools/abaqus_subroutine/abaqus_subroutine.xml
new file mode 100644
index 0000000..35e43ad
--- /dev/null
+++ b/galaxy/tools/abaqus_subroutine/abaqus_subroutine.xml
@@ -0,0 +1,39 @@
+
+ Abaqus Running with a SubRoutine
+
+
+ jacthyli/abaqus2023:0.3
+
+
+
+ &1 &&
+ mv *.odb '$Abaqus_out'
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This tool runs an Abaqus simulation with a user-defined subroutine. The subroutine should be provided as a .for file. The optional text file can be used to provide additional input to the simulation.
+
+
+
+
+
\ No newline at end of file
diff --git a/galaxy/tools/capella_TBR_extract/capella_TBR_extract.py b/galaxy/tools/capella_TBR_extract/capella_TBR_extract.py
new file mode 100644
index 0000000..9e40957
--- /dev/null
+++ b/galaxy/tools/capella_TBR_extract/capella_TBR_extract.py
@@ -0,0 +1,36 @@
+import json
+import argparse
+
+# Import all command line arguments
+# Example python3 '$__tool_directory__/json_dict_return.py' --tallies '$tallies_json' --TBR '$TBR_mean' --TBR_std '$TBR_std'
+parser = argparse.ArgumentParser(description='Extract TBR from Capella output JSON')
+
+parser.add_argument('--tallies', type=str, help='JSON file containing tallies')
+parser.add_argument('--TBR', type=str, help='Mean TBR value')
+parser.add_argument('--TBR_std', type=str, help='Standard deviation of TBR value')
+
+args = parser.parse_args()
+
+# Read in the JSON file
+with open(args.tallies, 'r') as read_f:
+ tallies = json.load(read_f)
+
+# Read all the TBR values from the JSON file
+TBRs = []
+for key, value in tallies.items():
+ if 'TBR' in key:
+ TBRs.append(value)
+
+TBR = 0
+TBR_STD = 0
+
+for item in TBRs:
+ TBR += item['mean']['0']
+ TBR_STD += item['std. dev.']['0']
+
+# Write the values to the passed variables
+with open(args.TBR, 'w') as write_f:
+ write_f.write(str(TBR))
+
+with open(args.TBR_std, 'w') as write_f:
+ write_f.write(str(TBR_STD))
diff --git a/galaxy/tools/capella_TBR_extract/capella_TBR_extract.xml b/galaxy/tools/capella_TBR_extract/capella_TBR_extract.xml
new file mode 100644
index 0000000..f5a59d0
--- /dev/null
+++ b/galaxy/tools/capella_TBR_extract/capella_TBR_extract.xml
@@ -0,0 +1,28 @@
+
diff --git a/galaxy/tools/capella_opemc_parameterised_gen/capella_opemc_parameterised_gen.py b/galaxy/tools/capella_opemc_parameterised_gen/capella_opemc_parameterised_gen.py
new file mode 100644
index 0000000..3bd6269
--- /dev/null
+++ b/galaxy/tools/capella_opemc_parameterised_gen/capella_opemc_parameterised_gen.py
@@ -0,0 +1,31 @@
+import json
+import argparse
+
+# Import all command line arguments
+parser = argparse.ArgumentParser(description='Generate JSON file for OpenMC')
+
+parser.add_argument('--maj_rad', type=float, help='Major radius of the tokamak')
+parser.add_argument('--triang', type=float, help='Triangularity of the tokamak')
+parser.add_argument('--elong', type=float, help='Elongation of the tokamak')
+parser.add_argument('--ion_temp', type=float, help='Central ion temperature')
+parser.add_argument('--li6_en', type=float, help='Li6 enrichment')
+parser.add_argument('--fw_thick', type=float, help='First wall thickness')
+
+args = parser.parse_args()
+
+# Read in the template JSON file
+with open('template_input.json', 'r') as read_f:
+ parametric_config = json.load(read_f)
+
+# Update the values in the template JSON file
+parametric_config['geometry']['major_radius'] = float(args.maj_rad)
+parametric_config['geometry']['triangularity'] = float(args.triang)
+parametric_config['geometry']['elongation'] = float(args.elong)
+parametric_config['source']['central_ion_temp'] = float(args.ion_temp)
+parametric_config['materials']['Blanket_inboard']['Lithium']['enrichment_amount'] = float(args.li6_en)
+parametric_config['materials']['Blanket_outboard']['Lithium']['enrichment_amount'] = float(args.li6_en)
+parametric_config['geometry']['firstwall_thickness'] = float(args.fw_thick)
+
+# Write the updated JSON file
+with open('output.json', 'w') as write_f:
+ json.dump(parametric_config, write_f)
diff --git a/galaxy/tools/capella_opemc_parameterised_gen/capella_opemc_parameterised_gen.xml b/galaxy/tools/capella_opemc_parameterised_gen/capella_opemc_parameterised_gen.xml
new file mode 100644
index 0000000..a2ed909
--- /dev/null
+++ b/galaxy/tools/capella_opemc_parameterised_gen/capella_opemc_parameterised_gen.xml
@@ -0,0 +1,34 @@
+
+
+ Generates input files for the Capella OpenMC Workflow
+
+
+ python:3.11
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Generates JSON input file from a series of input parameters for the Capella OpenMC Workflow using the deafult JSON configuration file.
+
+
+
diff --git a/galaxy/tools/capella_opemc_parameterised_gen/template_input.json b/galaxy/tools/capella_opemc_parameterised_gen/template_input.json
new file mode 100644
index 0000000..82fc78d
--- /dev/null
+++ b/galaxy/tools/capella_opemc_parameterised_gen/template_input.json
@@ -0,0 +1 @@
+{"materials": {"Divertor": {"Tungsten": {"mixture_amount": 100, "enriched": false}}, "Inboard_Structure_Blanket": {"eurofer": {"mixture_amount": 80, "enriched": false}, "Helium, Natural": {"mixture_amount": 20, "enriched": false}}, "Outboard_Structure_Blanket": {"eurofer": {"mixture_amount": 80, "enriched": false}, "Xenon": {"mixture_amount": 20, "enriched": false}}, "Blanket_inboard": {"Lithium": {"mixture_amount": 80, "enriched": true, "enrichment_amount": 20, "enrichment_target": "Li6"}, "Water, Vapor": {"mixture_amount": 10, "enriched": false}, "eurofer": {"mixture_amount": 10, "enriched": false}}, "Blanket_outboard": {"Lithium": {"mixture_amount": 80, "enriched": true, "enrichment_amount": 20, "enrichment_target": "Li6"}, "Water, Vapor": {"mixture_amount": 10, "enriched": false}, "eurofer": {"mixture_amount": 10, "enriched": false}}, "Manifold_inboard": {"eurofer": {"mixture_amount": 80, "enriched": false}, "Nitrogen": {"mixture_amount": 20, "enriched": false}}, "Manifold_outboard": {"eurofer": {"mixture_amount": 80, "enriched": false}, "Nitrogen": {"mixture_amount": 20, "enriched": false}}, "Firstwall_inboard": {"Tungsten": {"mixture_amount": 100, "enriched": false}}, "Firstwall_outboard": {"Tungsten": {"mixture_amount": 100, "enriched": false}}, "Multiplier_inboard": {"Be12Ti": {"mixture_amount": 100, "enriched": false}}, "Multiplier_outboard": {"Be12Ti": {"mixture_amount": 100, "enriched": false}}, "Reflector_inboard": {"Beryllium": {"mixture_amount": 100, "enriched": false}}, "Reflector_outboard": {"Beryllium": {"mixture_amount": 100, "enriched": false}}}, "geometry": {"firstwall_thickness": 1.8, "blanket_outboard_thickness": 90.0, "blanket_inboard_thickness": 60.0, "manifold_thickness": 15.0, "plasma_offset": 15.0, "blanket_structure_thickness": 5.0, "distance_between_segments": 10.0, "multiplier_thickness": 5.0, "reflector_thickness": 5.0, "amount_of_segments_poloidal": 6, "amount_of_segments_toroidal_outboard": 36, "amount_of_segments_toroidal_inboard": 36, "minor_radius": 290.0, "major_radius": 900.0, "triangularity": 0.5, "elongation": 1.84, "angle_start": 0.0, "angle_end": 90.0, "mode": "H", "outer_sphere": 10000.0}, "source": {"type": "tokamak source", "central_ion_temp": 45.9}, "settings": {"temperature": 600.0, "run_mode": "fixed source", "batches": 5, "particles": 10000, "num_tracks": 0, "blanket_type": "User"}, "tally": {"scatter_tally_materials": ["Firstwall_inboard", "Firstwall_outboard", "Inboard_Structure_Blanket", "Outboard_Structure_Blanket", "Reflector_inboard", "Reflector_outboard"], "absorbtion_tally_materials": ["Firstwall_inboard", "Firstwall_outboard", "Inboard_Structure_Blanket", "Outboard_Structure_Blanket", "Reflector_inboard", "Reflector_outboard"], "multiplication_tally_materials": ["Multiplier_inboard", "Multiplier_outboard"], "tbr_tally_materials": ["Blanket_inboard", "Blanket_outboard"]}}
\ No newline at end of file
diff --git a/galaxy/tools/capella_openmc/capella_openmc.py b/galaxy/tools/capella_openmc/capella_openmc.py
new file mode 100644
index 0000000..64271f1
--- /dev/null
+++ b/galaxy/tools/capella_openmc/capella_openmc.py
@@ -0,0 +1,504 @@
+import math
+import openmc
+from openmc_plasma_source import TokamakSource
+import neutronics_material_maker as nmm
+import os
+
+import json
+import argparse
+
+
+# HCPB HCLL WCLL DCLL are presets for premade blanket designs
+def HCPB(Temperature):
+ mat_Blanket = nmm.Material.from_library(
+ name="Li4SiO4",
+ enrichment=60.0,
+ enrichment_target="Li6",
+ temperature=Temperature,
+ ).openmc_material
+
+ mat_Structure = nmm.Material.from_library(
+ name="eurofer", temperature=700
+ ).openmc_material
+
+ mat_Coolant = nmm.Material.from_library(
+ name="Helium, Natural", temperature=Temperature
+ ).openmc_material
+ mat_Multipler = nmm.Material.from_library(
+ name="Be", temperature=Temperature
+ ).openmc_material
+
+ mat_Mixture_in = nmm.Material.from_mixture(
+ materials=[mat_Blanket, mat_Structure, mat_Coolant, mat_Multipler],
+ fracs=[0.097, 0.098, 0.369, 0.436],
+ percent_type="vo",
+ ).openmc_material
+ mat_Mixture_in.name = "Blanket_inboard"
+
+ mat_Mixture_out = nmm.Material.from_mixture(
+ materials=[mat_Blanket, mat_Structure, mat_Coolant, mat_Multipler],
+ fracs=[0.097, 0.098, 0.369, 0.436],
+ percent_type="vo",
+ ).openmc_material
+ mat_Mixture_out.name = "Blanket_outboard"
+
+ return mat_Mixture_in, mat_Mixture_out
+
+
+def HCLL(Temperature):
+ mat_Blanket = nmm.Material.from_library(
+ name="lithium-lead",
+ enrichment=90.0,
+ enrichment_target="Li6",
+ temperature=Temperature,
+ ).openmc_material
+
+ mat_Structure = nmm.Material.from_library(
+ name="eurofer", temperature=Temperature
+ ).openmc_material
+
+ mat_Coolant = nmm.Material.from_library(
+ name="Helium, Natural", temperature=Temperature
+ ).openmc_material
+
+ mat_Mixture_in = nmm.Material.from_mixture(
+ materials=[mat_Blanket, mat_Structure, mat_Coolant],
+ fracs=[0.85, 0.07, 0.08],
+ percent_type="vo",
+ ).openmc_material
+ mat_Mixture_in.name = "Blanket_inboard"
+
+ mat_Mixture_out = nmm.Material.from_mixture(
+ materials=[mat_Blanket, mat_Structure, mat_Coolant],
+ fracs=[0.85, 0.07, 0.08],
+ percent_type="vo",
+ ).openmc_material
+ mat_Mixture_out.name = "Blanket_outboard"
+
+ return mat_Mixture_in, mat_Mixture_out
+
+
+def WCLL(Temperature):
+ mat_Blanket = nmm.Material.from_library(
+ name="lithium-lead",
+ enrichment=90.0,
+ enrichment_target="Li6",
+ temperature=Temperature,
+ ).openmc_material
+
+ mat_Structure = nmm.Material.from_library(
+ name="eurofer", temperature=Temperature
+ ).openmc_material
+
+ mat_Coolant = nmm.Material.from_library(
+ name="Water, Vapor", temperature=Temperature
+ ).openmc_material
+
+ mat_Mixture_in = nmm.Material.from_mixture(
+ materials=[mat_Blanket, mat_Structure, mat_Coolant],
+ fracs=[0.85, 0.1, 0.05],
+ percent_type="vo",
+ ).openmc_material
+ mat_Mixture_in.name = "Blanket_inboard"
+
+ mat_Mixture_out = nmm.Material.from_mixture(
+ materials=[mat_Blanket, mat_Structure, mat_Coolant],
+ fracs=[0.85, 0.1, 0.05],
+ percent_type="vo",
+ ).openmc_material
+ mat_Mixture_out.name = "Blanket_outboard"
+
+ return mat_Mixture_in, mat_Mixture_out
+
+
+def DCLL(Temperature):
+ mat_Blanket = nmm.Material.from_library(
+ name="lithium-lead",
+ enrichment=90.0,
+ enrichment_target="Li6",
+ temperature=Temperature,
+ ).openmc_material
+
+ mat_Structure = nmm.Material.from_library(
+ name="eurofer", temperature=Temperature
+ ).openmc_material
+
+ mat_Coolant = nmm.Material.from_library(
+ name="Helium, Natural", temperature=Temperature
+ ).openmc_material
+
+ mat_insulator = nmm.Material.from_library(
+ name="SiC", temperature=Temperature
+ ).openmc_material
+
+ mat_Mixture_in = nmm.Material.from_mixture(
+ materials=[mat_Blanket, mat_Structure, mat_Coolant, mat_insulator],
+ fracs=[0.85, 0.08, 0.03, 0.04],
+ percent_type="vo",
+ ).openmc_material
+ mat_Mixture_in.name = "Blanket_inboard"
+
+ mat_Mixture_out = nmm.Material.from_mixture(
+ materials=[mat_Blanket, mat_Structure, mat_Coolant, mat_insulator],
+ fracs=[0.85, 0.08, 0.03, 0.04],
+ percent_type="vo",
+ ).openmc_material
+ mat_Mixture_out.name = "Blanket_outboard"
+
+ return mat_Mixture_in, mat_Mixture_out
+
+
+# Create a scatter tally from a material filter
+def ScatterTally(material_filter, name):
+ neutron_particle_filter = openmc.ParticleFilter(["neutron"])
+ material_scatter_tally = openmc.Tally(
+ name=("Scatter_" + str(name))
+ )
+ material_scatter_tally.scores = ["scatter"]
+ material_scatter_tally.filters = [
+ material_filter, neutron_particle_filter
+ ]
+
+ return material_scatter_tally
+
+
+# Create an absorption tally from a material filter
+def AbsorptionTally(material_filter, name):
+ neutron_particle_filter = openmc.ParticleFilter(["neutron"])
+ material_absorption_tally = openmc.Tally(
+ name=("Absorption_" + str(name))
+ )
+ material_absorption_tally.scores = ["absorption"]
+ material_absorption_tally.filters = [
+ material_filter, neutron_particle_filter
+ ]
+
+ return material_absorption_tally
+
+
+# Create a Muliplication tally from a material filter
+def MultiplicationTally(material_filter, name):
+ neutron_particle_filter = openmc.ParticleFilter(["neutron"])
+ material_multiplication_tally = openmc.Tally(
+ name=("Multiplication_" + str(name))
+ )
+ material_multiplication_tally.scores = ["(n,2n)"]
+ material_multiplication_tally.filters = [
+ material_filter, neutron_particle_filter
+ ]
+
+ return material_multiplication_tally
+
+
+# TBR Tally from material filter
+def TBRTally(material_filter, name):
+ tbr_tally = openmc.Tally(
+ name=(f"TBR_{name}")
+ )
+ tbr_tally.filters = [material_filter]
+ tbr_tally.scores = ["(n,Xt)"] # Trigger doesnt work for H3-production score changed to equivalent
+
+ return tbr_tally
+
+
+def openmc_model(config_dict, geom_path, statepoint_path, threads):
+ """Creates a Model of the h5m file from Paramak_H5M_creater.
+ This Model will be used to drive neutronic simulations
+
+ Arguments:
+ h5m_inputfile: The file containing the cad model of the blanket region.
+ str
+ Firstwall: Material of the Firstwall must be from Neutronics Material
+ maker format. str
+ Blanket_type: Type of Blanket resets ('HCPB','WCLL', 'HCLL', 'DCLL')
+ Also 'User' can be used but Blanket_Breeder must be inputted]. str
+ Inboard_Structure_Blanket: List containing Material name, coolant name
+ (from Neutronics Material maker) and fractions of volumes(0.8,0.2). arr
+ Outboard_Structure_Blanket: List containing Material name, coolant name
+ (from Neutronics Material maker) and fractions of volumes(0.8,0.2). arr
+ Blanket_Manifold: List containing Material name, coolant name (from
+ Neutronics Material maker) and fractions of volumes(0.8,0.2). list
+ Divertor: Material of the Divertor must be from Neutronics Material
+ maker format. str
+ Plasma_elongation: Use the same value as used to create the H5M file.
+ float
+ Plasma_triangularity: Use the same value as used to create the H5M file.
+ float
+ Temperature: Temperature of all components. float
+ minor_radius: Use the same value as used to create the H5M file. float
+ major_radius: Use the same value as used to create the H5M file. float
+ Reflector: Material of the Reflector must be from Neutronics Material
+ maker format. Use None if no reflector was used when creating the H5M
+ files. str
+ Multiplier: Material of the Multiplier must be from Neutronics Material
+ maker format. Use None if no reflector was used when creating the H5M
+ files. str
+ Blanket_Breeder: Must be used if Blanket_type = 'User'. List containing
+ Material name, coolant name (from Neutronics Material maker), fractions
+ of volumes(0.8,0.2) and lithium 6 enrichment. list
+ """
+ model = openmc.model.Model()
+
+ materials = config_dict["materials"]
+ geometry = config_dict["geometry"]
+ settings = config_dict["settings"]
+ source = config_dict["source"]
+ tally = config_dict['tally']
+
+ #############
+ # Create MATERIALS
+ #############
+ # A list to collect all created materials
+ Collect_Materials = []
+ # A list to collect all materials, used for tallies
+ material_lookup = []
+
+ for key, value in materials.items():
+ # TODO: This currently doesn't handle just a single enriched material
+ # (need option for this at somepoint)
+ print(key)
+ if settings['blanket_type'] != 'User' and (key == 'Blanket_inboard' or key == 'Blanket_outboard'):
+ continue
+
+ # Assume everything is a mix of materials
+ mix_material_array = []
+ frac_array = []
+
+ for mat_name, mat_properties in value.items():
+ # Case for no enrichment
+ if mat_properties['enriched'] is False:
+ temp_mix_material = nmm.Material.from_library(
+ name=mat_name,
+ temperature=settings['temperature'],
+ ).openmc_material
+ frac_array.append(mat_properties['mixture_amount']/100)
+ # Case for enrichment
+ else:
+ # sub_value format:
+ # [, , ]
+ temp_mix_material = nmm.Material.from_library(
+ name=mat_name,
+ enrichment=mat_properties['enrichment_amount'],
+ enrichment_target=mat_properties['enrichment_target'],
+ # TODO: Check this is correct with Adam
+ enrichment_type='wo',
+ temperature=settings['temperature'],
+ ).openmc_material
+ frac_array.append(mat_properties['mixture_amount']/100)
+ mix_material_array.append(temp_mix_material)
+
+ if sum(frac_array) == 1:
+ print(f"Good {key}")
+ else:
+ print(f"Bad {key}")
+ print(sum(frac_array))
+ print(frac_array)
+
+ temp_material = nmm.Material.from_mixture(
+ materials=mix_material_array,
+ fracs=frac_array,
+ percent_type='vo'
+ ).openmc_material
+
+ temp_material.name = key
+ Collect_Materials.append(temp_material)
+ material_lookup.append([key, temp_material])
+
+ # Create dictionary to call the appropriate function and check JSON input
+ blanket_type_dict = {
+ "HCPB": HCPB,
+ "HCLL": HCLL,
+ "WCLL": WCLL,
+ "DCLL": DCLL,
+ }
+
+ # Get the type from the JSON and select the function
+ selected_blanket = blanket_type_dict.get(settings['blanket_type'])
+
+ if selected_blanket:
+ Blanket_mat_in, Blanket_mat_out = selected_blanket(settings['temperature'])
+ Collect_Materials.extend([Blanket_mat_in, Blanket_mat_out])
+
+ for material in [Blanket_mat_in, Blanket_mat_out]:
+ material_lookup.append([str(material.name), material])
+ else:
+ if settings['blanket_type'] == "User":
+ print("User Blanket type selected")
+ else:
+ print("""Incorrect Blanket type, options:
+ HCPB, HCLL, WCLL, DCLL or User""")
+
+ # Combine all materials
+ materials = openmc.Materials(Collect_Materials)
+ model.materials = materials
+
+ ########################################
+ # CREATE GEOMETRY & BOUNDARY CONDITIONS
+ ########################################
+
+ # Create universe with the input file
+ dag_univ = openmc.DAGMCUniverse(filename=geom_path)
+ vac_surf = openmc.Sphere(
+ r=geometry['outer_sphere'],
+ surface_id=9999,
+ boundary_type="vacuum"
+ )
+
+ # Adds reflective surface for the sector model at 0 degrees
+ reflective_1 = openmc.Plane(
+ a=math.sin(math.radians(geometry['angle_start'])),
+ b=-math.cos(math.radians(geometry['angle_start'])),
+ c=0.0,
+ d=0.0,
+ surface_id=9991,
+ boundary_type="reflective",
+ )
+
+ # Adds reflective surface for the sector model at 90 degrees
+ reflective_2 = openmc.Plane(
+ a=math.sin(math.radians(geometry['angle_end'])),
+ b=-math.cos(math.radians(geometry['angle_end'])),
+ c=0.0,
+ d=0.0,
+ surface_id=9990,
+ boundary_type="reflective",
+ )
+
+ # Sim region in the universe boundary and the reflective surfaces
+ region = -vac_surf & -reflective_1 & +reflective_2
+
+ # Creates a cell from the region and fills the cell with the dagmc geometry
+ containing_cell = openmc.Cell(cell_id=9998, region=region, fill=dag_univ)
+
+ geom = openmc.Geometry(root=[containing_cell])
+ model.geometry = geom
+
+ ########################################
+ # SOURCE
+ ########################################
+
+ # Source type
+ if source['type'] == "tokamak source":
+ source = TokamakSource(
+ elongation=geometry['elongation'],
+ ion_density_centre=1.09e20,
+ ion_density_peaking_factor=1,
+ ion_density_pedestal=1.09e20,
+ ion_density_separatrix=3e19,
+ ion_temperature_centre=source['central_ion_temp'],
+ ion_temperature_peaking_factor=8.06,
+ ion_temperature_pedestal=6.09,
+ ion_temperature_separatrix=0.1,
+ major_radius=geometry['major_radius'],
+ minor_radius=geometry['minor_radius'],
+ pedestal_radius=0.8 * geometry['minor_radius'],
+ mode=geometry['mode'],
+ shafranov_factor=0.44789,
+ triangularity=geometry['triangularity'],
+ ion_temperature_beta=6,
+ angles=(
+ math.radians(geometry['angle_start']),
+ math.radians(geometry['angle_end'])
+ ),
+ sample_size=100 # TODO: Add this to the config file
+ )
+ else:
+ print("Incorrect Source type, input must be a string of source type")
+ print(f'Input was {source["type"]}')
+
+ model.settings.source = source.sources
+
+ ########################################
+ # TALLIES
+ ########################################
+ scatter_tally_materials = tally['scatter_tally_materials']
+ absorbtion_tally_materials = tally['absorbtion_tally_materials']
+ multiplication_tally_materials = tally['multiplication_tally_materials']
+ tbr_tally_materials = tally['tbr_tally_materials']
+
+ Tally_collect = [] # A list to collect all the tallies used
+
+ for material in material_lookup:
+ material_filter = openmc.MaterialFilter(material[1])
+
+ if material[0] in scatter_tally_materials:
+ scatter_tally = ScatterTally(
+ material_filter=material_filter,
+ name=material[1].name
+ )
+ Tally_collect.append(scatter_tally)
+
+ if material[0] in absorbtion_tally_materials:
+ absorbtion_tally = AbsorptionTally(
+ material_filter=material_filter,
+ name=material[1].name
+ )
+ Tally_collect.append(absorbtion_tally)
+
+ if material[0] in multiplication_tally_materials:
+ multiplication_tally = MultiplicationTally(
+ material_filter=material_filter,
+ name=material[1].name
+ )
+ Tally_collect.append(multiplication_tally)
+
+ if material[0] in tbr_tally_materials:
+ tbr_tally = TBRTally(
+ material_filter=material_filter,
+ name=material[1].name
+ )
+ Tally_collect.append(tbr_tally)
+
+ # Add to model
+ model.tallies = openmc.Tallies(Tally_collect)
+
+ ########################################
+ # SETTINGS
+ ########################################
+ model.settings.batches = settings['batches']
+ model.settings.particles = settings['particles']
+ model.settings.run_mode = settings['run_mode']
+ model.settings.output = {'tallies': False}
+ # Do we want this static or variable from JSON?
+ model.settings.temperature = {"method": "interpolation"}
+ print(f"Threads: {threads}")
+ sp_file = model.run(threads=threads)
+ # return model & save to results.h5 so the tool can pick it up
+ os.system(f"mv {sp_file} {statepoint_path}")
+
+
+if __name__ == "__main__":
+
+ parser = argparse.ArgumentParser(
+ prog='openmc_parametric.py',
+ description='''OpenMC parametric CAD run script.
+ Useage: python openmc_parametric.py '''
+ )
+
+ parser.add_argument(
+ 'geometry_file',
+ help='dagmc.h5m geometry file for simulation'
+ )
+ parser.add_argument(
+ 'config_file',
+ help='config.json file with the run settings for the simulation'
+ )
+ parser.add_argument(
+ 'statepoint_file',
+ help='Output file to save the statepoint to. Format: h5'
+ )
+ parser.add_argument(
+ 'threads',
+ help='Number of threads to use for the simulation. Format: int'
+ )
+ args = parser.parse_args()
+
+ geom_path = args.geometry_file
+ config_path = args.config_file
+ statepoint_path = args.statepoint_file
+ threads = args.threads
+
+ with open(config_path, "r") as config_file:
+ config_dict = json.load(config_file)
+
+ openmc_model(config_dict, geom_path, statepoint_path, threads)
diff --git a/galaxy/tools/capella_openmc/capella_openmc.xml b/galaxy/tools/capella_openmc/capella_openmc.xml
new file mode 100644
index 0000000..2188095
--- /dev/null
+++ b/galaxy/tools/capella_openmc/capella_openmc.xml
@@ -0,0 +1,35 @@
+
+
+ Parametric neutronics simulation via OpenMC for Capella
+
+
+ williamjsmith15/omniverse-openmc:05062023
+
+
+
+ &1
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+ This tool takes in a 3D geometry and parameterised config file to run a neutronics simulation
+ via the OSS OpenMC.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/galaxy/tools/capella_paramak_generator/capella_paramak_generator.py b/galaxy/tools/capella_paramak_generator/capella_paramak_generator.py
new file mode 100644
index 0000000..96d0471
--- /dev/null
+++ b/galaxy/tools/capella_paramak_generator/capella_paramak_generator.py
@@ -0,0 +1,538 @@
+import paramak
+import numpy as np
+import argparse
+from stl_to_h5m import stl_to_h5m
+import json
+import os
+
+parser = argparse.ArgumentParser(
+ prog="paramak_generator.py",
+ description="""Paramak geometry generator.
+ Usage: python paramak_generator.py """,
+)
+
+parser.add_argument(
+ "config_file",
+ help="""Config .txt file with the parameters for the geometry creation.
+ See:
+ templates/JSON Generators/parametric_json_creator.py
+ for more details.""",
+)
+args = parser.parse_args()
+config_path = args.config_file
+
+
+def parametric_blanket(geometry):
+ """
+ Function which creates a Blanket, firstwall, divertor and plasma region,
+ and outputs a H5M file for OpenMC. ALso returns the plasma
+ triangularity and elongation valves to be used when creating the
+ plasma in OpenMC.
+
+ Arguments:
+ First_wall_thickness : The thickness of the Firstwall shield of
+ the Tokamak(cm)
+ Blanket_outboard_thickness: Thickness of the outboard blanket, The breeding zone
+ equals Blanket_thickness - Blanket_Structure_Thicnkness (cm)
+ Blanket_inboard_thickness: Thickness of the inboard blanket, The breeding zone
+ equals Blanket_thickness - Blanket_Structure_Thicnkness (cm)
+ Manifold_thickness: The Thickness of the Manifold to the rear of
+ the blanket (cm)
+ Plasma_offset: Distance between the firstwall and the plasma (cm)
+ Blanket_structure_thickness: The thickness of the firstwall
+ structure, between the firstwall shield plates and the
+ breeding material
+ Distance_between_segment: The toroidal gaps distance between each
+ blanket unit (cm)
+ Multiplier_thickness: Thickness of the Multiplier if not required
+ it must be 0 (cm)
+ Reflector_thickness: Thickness of the Reflector if not required
+ it be 0 (cm)
+ amount_of_segments_pol: The number of poloidal segments of the blanket
+ amount_of_segments_tor: The number of toroidal segments of the blanket
+ Minor_radius: The lenght of the plasma from the centre to the edge
+ in a straight line on the x plane (cm)
+ Major_radius: The lenght from the (0,0,0) coordinate to the centre
+ of the plasma (cm)
+ html_output: Creates an 3D html output to view the reactor created
+ """
+ # For testing
+ Final_Blanket = [] # A list containing items for the final blanket
+
+ try:
+ First_wall_thickness = float(geometry["firstwall_thickness"])
+ Blanket_outboard_thickness = float(geometry["blanket_outboard_thickness"])
+ Blanket_inboard_thickness = float(geometry["blanket_inboard_thickness"])
+ Manifold_thickness = float(geometry["manifold_thickness"])
+ Plasma_offset = float(geometry["plasma_offset"])
+ Blanket_structure_thickness = float(
+ geometry["blanket_structure_thickness"]
+ )
+ Distance_between_segment = float(geometry["distance_between_segments"])
+ Multiplier_thickness = float(geometry["multiplier_thickness"])
+ Reflector_thickness = float(geometry["reflector_thickness"])
+ amount_of_segments_pol = int(geometry["amount_of_segments_poloidal"])
+ amount_of_segments_tor_in = int(geometry["amount_of_segments_toroidal_inboard"])
+ amount_of_segments_tor_out = int(geometry["amount_of_segments_toroidal_outboard"])
+ Minor_radius = float(geometry["minor_radius"])
+ Major_Radius = float(geometry["major_radius"])
+ # html_output = geometry["html_output"]
+ except ValueError:
+ print("Error: The input values much match the expected types")
+
+ # Error Capture 50 represents the central solenoid 3603
+ if float(Major_Radius) - 50 <= float(
+ First_wall_thickness
+ + Blanket_outboard_thickness
+ + Manifold_thickness
+ + Plasma_offset
+ + Minor_radius
+ + Blanket_structure_thickness
+ + Multiplier_thickness
+ + Reflector_thickness
+ ):
+ print(
+ "Error: The Major Radius must be 50cm > all other inputs combined"
+ )
+ exit()
+
+ # Error Capture for Blanket size
+ if float(Blanket_outboard_thickness) <= float(
+ Blanket_structure_thickness
+ + Reflector_thickness
+ + Multiplier_thickness
+ ):
+ print("Error: The Blanket unit must be larger than its parts")
+ exit()
+
+ # For error caused by zero input for reflector and multiplier
+ cut_Structure_values = [] # A list of item that will cut the Structure
+ cut_Blanket_value = [] # A list of items to cut the blanket
+ cut_poloidal_values = [] ## A list of items to segment poloidally
+
+ # Create plasma and firstwall
+ # Rotation angle fo the tokamak
+ Angle = float(geometry['angle_end']) - float(geometry['angle_start'])
+ gap = 2 # Gap between the in and out board blanket
+
+ Plasma = paramak.Plasma(
+ minor_radius=Minor_radius,
+ major_radius=Major_Radius,
+ # Must be outputted for the plasma parameters in neutronics
+ triangularity=geometry['triangularity'],
+ # Must be outputted for the plasma parameters in neutronics
+ elongation=geometry['elongation'],
+ rotation_angle=Angle,
+ )
+
+ ## Create cutting tools
+ Structure_star_cutter_in = paramak.BlanketCutterStar(
+ height=Major_Radius * 3,
+ distance=Distance_between_segment, # Distance between segments
+ azimuth_placement_angle=np.linspace(
+ 0, 360, (amount_of_segments_tor_in), endpoint=False
+ ),
+ )
+ # Used to cut the Blanket from the structure
+ Blanket_star_cutter_in = paramak.BlanketCutterStar(
+ height=Major_Radius * 3,
+ distance=Distance_between_segment
+ + (Blanket_structure_thickness * 2), # Distance between segments
+ azimuth_placement_angle=np.linspace(
+ 0, 360, (amount_of_segments_tor_in), endpoint=False
+ ),
+ )
+ ## Create cutting tools
+ Structure_star_cutter_out = paramak.BlanketCutterStar(
+ height=Major_Radius * 3,
+ distance=Distance_between_segment, # Distance between segments
+ azimuth_placement_angle=np.linspace(
+ 0, 360, (amount_of_segments_tor_out), endpoint=False
+ ),
+ )
+ # Used to cut the Blanket from the structure
+ Blanket_star_cutter_out = paramak.BlanketCutterStar(
+ height=Major_Radius * 3,
+ distance=Distance_between_segment
+ + (Blanket_structure_thickness * 2), # Distance between segments
+ azimuth_placement_angle=np.linspace(
+ 0, 360, (amount_of_segments_tor_out), endpoint=False
+ ),
+ )
+
+ # Blanket_cut Shape created to cut the remaining shapes for the divertor
+ # Not used in the final reactor
+
+ Blanket_cut = paramak.BlanketFP(
+ plasma=Plasma,
+ # To make it thicker than the Divertor for cutting
+ thickness=Blanket_outboard_thickness + Plasma_offset + Manifold_thickness,
+ stop_angle=230,
+ start_angle=-60,
+ # To make it thicker on the internal dimensions for cutting
+ offset_from_plasma=Plasma_offset - First_wall_thickness,
+ rotation_angle=Angle,
+ )
+
+ # Creates the gap between the inboard and outboard at the plasma high point
+ inboard_to_outboard_gaps = paramak.RotateStraightShape(
+ points=[
+ (Plasma.high_point[0] - (0.5 * gap), Plasma.high_point[1]),
+ (Plasma.high_point[0] - (0.5 * gap), Plasma.high_point[1] + 1000),
+ (Plasma.high_point[0] + (0.5 * gap), Plasma.high_point[1] + 1000),
+ (Plasma.high_point[0] + (0.5 * gap), Plasma.high_point[1]),
+ ],
+ rotation_angle=Angle,
+ )
+
+ # Create components
+
+ # Test to check if the values for Multiplier and Reflector is zero
+ if float(Reflector_thickness) > 0:
+ Reflector_out = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Reflector_thickness,
+ stop_angle=90,
+ start_angle=-60,
+ offset_from_plasma=Plasma_offset
+ + First_wall_thickness
+ + Blanket_outboard_thickness
+ - Reflector_thickness,
+ rotation_angle=Angle,
+ cut=[Blanket_star_cutter_out, inboard_to_outboard_gaps],
+ color=list(
+ np.random.rand(
+ 3,
+ )
+ ),
+ name="Reflector_outboard",
+ )
+ # Used just to cut a larger section
+ Reflector_cut_out = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Reflector_thickness + 5,
+ stop_angle=90,
+ start_angle=-60,
+ offset_from_plasma=Plasma_offset
+ + First_wall_thickness
+ + Blanket_outboard_thickness
+ - Reflector_thickness,
+ rotation_angle=Angle,
+ cut=[Blanket_star_cutter_out, inboard_to_outboard_gaps],
+ color=list(
+ np.random.rand(
+ 3,
+ )
+ ),
+ name="Reflector",
+ )
+ cut_Structure_values.append(Reflector_cut_out)
+ cut_Blanket_value.append(Reflector_cut_out)
+ cut_poloidal_values.append(Reflector_out)
+
+ Reflector_in = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Reflector_thickness,
+ stop_angle= 230,
+ start_angle=90,
+ offset_from_plasma= Plasma_offset
+ + First_wall_thickness
+ + Blanket_inboard_thickness
+ - Reflector_thickness,
+ rotation_angle=Angle,
+ cut = [Blanket_star_cutter_in, inboard_to_outboard_gaps],
+ color= list(np.random.rand(3,)),
+ name = 'Reflector_inboard'
+ )
+ ##Used just to cut a larger section
+ Reflector_cut_in = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Reflector_thickness +5,
+ stop_angle= 230,
+ start_angle=90,
+ offset_from_plasma= Plasma_offset
+ + First_wall_thickness
+ + Blanket_inboard_thickness
+ - Reflector_thickness,
+ rotation_angle=Angle,
+ cut = [Blanket_star_cutter_in, inboard_to_outboard_gaps],
+ color= list(np.random.rand(3,)),
+ name = 'Reflector'
+ )
+ cut_Structure_values.append(Reflector_cut_in)
+ cut_Blanket_value.append(Reflector_cut_in)
+ cut_poloidal_values.append(Reflector_in)
+
+ if float(Multiplier_thickness) > 0:
+ Multiplier_in = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Multiplier_thickness,
+ stop_angle=230,
+ start_angle=90,
+ offset_from_plasma=Plasma_offset
+ + First_wall_thickness
+ + Blanket_structure_thickness,
+ rotation_angle=Angle,
+ cut=[Blanket_star_cutter_in, inboard_to_outboard_gaps],
+ color=list(
+ np.random.rand(
+ 3,
+ )
+ ),
+ name="Multiplier_inboard",
+ )
+ cut_Structure_values.append(Multiplier_in)
+ cut_poloidal_values.append(Multiplier_in)
+ cut_Blanket_value.append(Multiplier_in)
+
+ Multiplier_out = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Multiplier_thickness,
+ stop_angle=90,
+ start_angle=-60,
+ offset_from_plasma=Plasma_offset
+ + First_wall_thickness
+ + Blanket_structure_thickness,
+ rotation_angle=Angle,
+ cut=[Blanket_star_cutter_out, inboard_to_outboard_gaps],
+ color=list(
+ np.random.rand(
+ 3,
+ )
+ ),
+ name="Multiplier_outboard",
+ )
+ cut_Structure_values.append(Multiplier_out)
+ cut_poloidal_values.append(Multiplier_out)
+ cut_Blanket_value.append(Multiplier_out)
+
+ Firstwall_in = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=First_wall_thickness,
+ stop_angle=230,
+ start_angle=90,
+ offset_from_plasma=Plasma_offset,
+ rotation_angle=Angle,
+ num_points=100,
+ cut=[Blanket_star_cutter_in],
+ color=list(
+ np.random.rand(
+ 3,
+ )
+ ),
+ name="Firstwall_inboard",
+ )
+ Final_Blanket.append(Firstwall_in)
+
+ Firstwall_out = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=First_wall_thickness,
+ stop_angle=90,
+ start_angle=-60,
+ offset_from_plasma=Plasma_offset,
+ rotation_angle=Angle,
+ num_points=100,
+ cut=[Blanket_star_cutter_out],
+ color=list(
+ np.random.rand(
+ 3,
+ )
+ ),
+ name="Firstwall_outboard",
+ )
+ Final_Blanket.append(Firstwall_out)
+
+ Manifold_out = paramak.BlanketFP(
+ plasma =Plasma,
+ thickness = Manifold_thickness,
+ stop_angle= 90,
+ start_angle=-60,
+ offset_from_plasma= Plasma_offset
+ + First_wall_thickness
+ + Blanket_outboard_thickness,
+ rotation_angle=Angle,
+ cut = [inboard_to_outboard_gaps],
+ name = 'Manifold_outboard'
+ )
+ Final_Blanket.append(Manifold_out)
+
+ Manifold_in = paramak.BlanketFP(
+ plasma =Plasma,
+ thickness = Manifold_thickness +
+ (Blanket_outboard_thickness - Blanket_inboard_thickness),
+ stop_angle= 230,
+ start_angle= 90,
+ offset_from_plasma= Plasma_offset
+ + First_wall_thickness
+ + Blanket_inboard_thickness,
+ rotation_angle=Angle,
+ cut = [inboard_to_outboard_gaps],
+ name = 'Manifold_inboard'
+ )
+ Final_Blanket.append(Manifold_in)
+
+ Blanket_out = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Blanket_outboard_thickness
+ - Blanket_structure_thickness,
+ stop_angle= 90,
+ start_angle= -60,
+ offset_from_plasma=Plasma_offset
+ + First_wall_thickness
+ + Blanket_structure_thickness,
+ rotation_angle=Angle,
+ cut=[Blanket_star_cutter_out, inboard_to_outboard_gaps]
+ + cut_Blanket_value,
+ color=list(
+ np.random.rand(
+ 3,
+ )
+ ),
+ name="Blanket_outboard",
+ )
+ cut_poloidal_values.append(Blanket_out)
+
+ Blanket_in = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Blanket_inboard_thickness - Blanket_structure_thickness,
+ stop_angle= 230,
+ start_angle=90,
+ offset_from_plasma= Plasma_offset
+ + First_wall_thickness
+ + Blanket_structure_thickness,
+ rotation_angle=Angle,
+ cut = [Blanket_star_cutter_in, inboard_to_outboard_gaps]
+ + cut_Blanket_value,
+ color= list(np.random.rand(3,)),
+ name = 'Blanket_inboard'
+ )
+ cut_poloidal_values.append(Blanket_in)
+
+ Blanket_cut_Structure_out = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Blanket_outboard_thickness
+ + Blanket_structure_thickness
+ + Reflector_thickness,
+ stop_angle=90,
+ start_angle=-60,
+ offset_from_plasma=Plasma_offset
+ + First_wall_thickness
+ + Blanket_structure_thickness,
+ rotation_angle=Angle,
+ cut=[Blanket_star_cutter_out, inboard_to_outboard_gaps]
+ + cut_Blanket_value,
+ color=list(
+ np.random.rand(
+ 3,
+ )
+ ),
+ )
+ Blanket_cut_Structure_in = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Blanket_outboard_thickness
+ + Blanket_structure_thickness
+ + Reflector_thickness,
+ stop_angle=230,
+ start_angle=90,
+ offset_from_plasma=Plasma_offset
+ + First_wall_thickness
+ + Blanket_structure_thickness,
+ rotation_angle=Angle,
+ cut=[Blanket_star_cutter_in, inboard_to_outboard_gaps]
+ + cut_Blanket_value,
+ color=list(
+ np.random.rand(
+ 3,
+ )
+ ),
+ )
+ outboard_Structure_Blanket = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Blanket_outboard_thickness,
+ stop_angle=90,
+ start_angle=-60,
+ offset_from_plasma= Plasma_offset
+ + First_wall_thickness,
+ rotation_angle=Angle,
+ cut=[Blanket_cut_Structure_out, Structure_star_cutter_out, inboard_to_outboard_gaps]
+ + cut_Structure_values,
+ color=list(
+ np.random.rand(
+ 3,
+ )
+ ),
+ name="Outboard_Structure_Blanket",
+ )
+ cut_poloidal_values.append(outboard_Structure_Blanket)
+
+ inboard_Structure_Blanket = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Blanket_inboard_thickness,
+ stop_angle=230,
+ start_angle=90,
+ offset_from_plasma=Plasma_offset
+ + First_wall_thickness,
+ rotation_angle=Angle,
+ cut=[Blanket_cut_Structure_in, Structure_star_cutter_in, inboard_to_outboard_gaps]
+ + cut_Structure_values,
+ color=list(
+ np.random.rand(
+ 3,
+ )
+ ),
+ name="Inboard_Structure_Blanket",
+ )
+ Final_Blanket.append(inboard_Structure_Blanket)
+ print(inboard_Structure_Blanket.name)
+ Divertor = paramak.BlanketFP(
+ plasma=Plasma,
+ thickness=Blanket_outboard_thickness + Manifold_thickness,
+ stop_angle=270,
+ start_angle=-90,
+ offset_from_plasma=Plasma_offset + First_wall_thickness,
+ rotation_angle=Angle,
+ cut=Blanket_cut,
+ color=list(
+ np.random.rand(
+ 3,
+ )
+ ),
+ name="Divertor",
+ )
+ Final_Blanket.append(Divertor)
+
+ ## Used to gather all shapes that need cutting poloially and cut to the amount of
+ for component in cut_poloidal_values:
+
+ segmented_structure = paramak.PoloidalSegments(
+ shape_to_segment=component,
+ center_point=(Major_Radius, 0), # this is the middle of the plasma
+ number_of_segments=amount_of_segments_pol,
+ color= list(np.random.rand(3,)),
+ name = component.name
+ )
+ Final_Blanket.append(segmented_structure)
+
+ shape_name = []
+ #reactor = paramak.Reactor(Final_Blanket)
+ #reactor.export_html_3d('example.html')
+
+
+ # Create stl files
+ for shape in Final_Blanket:
+ shape.export_stl(str(shape.name) + ".stl")
+ shape_name.append((str(shape.name) + ".stl", str(shape.name)))
+
+ stl_to_h5m(
+ files_with_tags=shape_name,
+ h5m_filename="dagmc.h5m",
+ )
+
+ os.system("rm *.stl")
+
+
+if __name__ == "__main__":
+ with open(config_path, "r") as config_file:
+ config_dict = json.load(config_file)
+
+ parametric_blanket(config_dict["geometry"])
diff --git a/galaxy/tools/capella_paramak_generator/capella_paramak_generator.xml b/galaxy/tools/capella_paramak_generator/capella_paramak_generator.xml
new file mode 100644
index 0000000..703d42c
--- /dev/null
+++ b/galaxy/tools/capella_paramak_generator/capella_paramak_generator.xml
@@ -0,0 +1,35 @@
+
+
+ Parametric Reactor Geometry Creator For Capella
+
+
+ williamjsmith15/omniverse-openmc:05062023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This tool takes a set of parameters and outputs a geometry ready for simulation in OpenMC.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/galaxy/tools/combine_coils/combine_coils.py b/galaxy/tools/combine_coils/combine_coils.py
new file mode 100644
index 0000000..ed501cc
--- /dev/null
+++ b/galaxy/tools/combine_coils/combine_coils.py
@@ -0,0 +1,54 @@
+import cadquery as cq
+import os
+import zipfile
+def import_step_files(directory):
+ step_parts = []
+
+ # Load all .step files in the directory
+ for filename in os.listdir(directory):
+ if filename.endswith('.step'):
+ file_path = os.path.join(directory, filename)
+ part = cq.importers.importStep(file_path)
+
+ # Check if the part imported successfully
+ if part is not None:
+ step_parts.append(part)
+ else:
+ print(f"Warning: {filename} could not be loaded and was skipped.")
+
+ return step_parts
+
+def create_compound(components: list[cq.Solid]) -> cq.Compound:
+ """
+ Create a CadQuery Compound object from a list of CadQuery Solid objects.
+
+ This function takes a list of CadQuery Solid objects, extracts the underlying geometric values
+ from each Solid, and combines them into a single Compound object.
+
+ Args:
+ components (list[cq.Solid]): List of CadQuery Solid objects.
+
+ Returns:
+ cq.Compound: CadQuery Compound object
+ containing the combined geometric values of all input Solids.
+ """
+ vals = []
+ for component in components:
+ for sub_solid in component.all():
+ vals.extend(sub_solid.vals())
+ compound_object = cq.Compound.makeCompound(vals)
+ return compound_object
+# Directory containing your .step files
+# Unzip 'zipped_cad.zip' to 'stell_cad'
+zip_file = 'zipped_cad.zip' # Replace with the correct path to your zip file if necessary
+directory = 'stell_cad' # Directory to extract to
+
+# Ensure the output directory exists
+os.makedirs(directory, exist_ok=True)
+
+with zipfile.ZipFile(zip_file, 'r') as zip_ref:
+ zip_ref.extractall(directory)
+
+step_parts = import_step_files(directory)
+compound = create_compound(step_parts)
+cq.exporters.export(compound, 'combined_coils.step')
\ No newline at end of file
diff --git a/galaxy/tools/combine_coils/combine_coils.xml b/galaxy/tools/combine_coils/combine_coils.xml
new file mode 100644
index 0000000..23ca39c
--- /dev/null
+++ b/galaxy/tools/combine_coils/combine_coils.xml
@@ -0,0 +1,25 @@
+
+
+ Utility tool to combine all the individual coils into one step file
+
+
+ nttaudom/paramak:29.11.23
+
+
+ &1 &&
+ cp 'combined_coils.step' '$combined_cad'
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/galaxy/tools/containment_vessel/containment_vessel.py b/galaxy/tools/containment_vessel/containment_vessel.py
new file mode 100644
index 0000000..d8758ab
--- /dev/null
+++ b/galaxy/tools/containment_vessel/containment_vessel.py
@@ -0,0 +1,215 @@
+"""
+Author: @DomLonghorn
+
+The `reactor_building` function calculates the reactor core radius
+and height based on the input parameters,
+The type of roof (flat, domed, or cone) is determined by the 'roof_type' parameter.
+
+This module can be run as a script, taking a file path as a command-line argument.
+The file at the given path should contain a JSON object
+with the parameters for the `reactor_building` function.
+
+Example:
+ $ python containment_vessel.py parameters.json
+
+This will read the parameters from `parameters.json` and
+use them to construct a 3D model of a reactor building.
+
+Dependencies:
+ cadquery: A Python library for building parametric 3D CAD models.
+"""
+
+import argparse
+import json
+
+import cadquery as cq
+
+parser = argparse.ArgumentParser()
+parser.add_argument('file_path')
+args = parser.parse_args()
+file_path = args.file_path
+
+
+def reactor_building(radial_build, major_rad, scale_factor, shield_thickness, contain_thickness):
+ """
+ Constructs a 3D model of a reactor building using the provided parameters.
+
+ The function calculates the reactor core radius and height based on the input parameters.
+ The type of roof (flat, domed, or cone) is determined by the 'roof_type' parameter.
+
+ Parameters:
+ radial_build (list): A list of radial dimensions for the reactor core.
+ major_rad (float): The major radius of the reactor core.
+ scale_factor (float): A scale factor for the reactor building dimensions.
+ shield_thickness (float): The thickness of the shield for the reactor building.
+ contain_thickness (float): The thickness of the containment for the reactor building.
+
+ Returns:
+ cq.Workplane: A CadQuery Workplane object representing the 3D model of the reactor building.
+ """
+ reactor_core_radius = sum(radial_build) + 0.8 + major_rad
+ reactor_core_height = 2 * reactor_core_radius
+
+ def create_roof(roof_type, reactor_building_radius, building_height):
+ roof_thickness = 0.3 # Adjust as needed
+ offset_height = building_height / 2 + roof_thickness / 2
+
+ if roof_type == "flat":
+ return (
+ cq.Workplane("ZX")
+ .transformed(offset=(0, 0, offset_height))
+ .cylinder(roof_thickness, reactor_building_radius)
+ )
+ if roof_type == "domed":
+ outer_radius = reactor_building_radius
+ inner_radius = outer_radius - contain_thickness
+
+ outer_sphere = (
+ cq.Workplane("ZX").transformed(
+ offset=(0, 0, 0)).sphere(outer_radius)
+ )
+
+ inner_sphere = (
+ cq.Workplane("ZX").transformed(
+ offset=(0, 0, 0)).sphere(inner_radius)
+ )
+
+ hemisphere = outer_sphere.cut(inner_sphere)
+ hemisphere = hemisphere.translate((0, offset_height, 0))
+ cut_box = (
+ cq.Workplane("ZX")
+ .box(3 * outer_radius, 3 * outer_radius, 2 * outer_radius)
+ .translate((0, 0, 0))
+ )
+ hemisphere = hemisphere.cut(cut_box)
+ return hemisphere
+ if roof_type == "cone":
+ cone_height = roof_thickness * 4 # Define the height of the cone roof
+ bottom_radius = reactor_building_radius
+ wall_thickness = 0.2
+ # Create a cylinder
+ cone_base = cq.Workplane("ZX").cylinder(cone_height, bottom_radius)
+ # Apply a chamfer to the top edge to create the cone shape
+ truncated_cone = cone_base.edges(">Y").chamfer(cone_height * 0.75)
+
+ inner_radius = bottom_radius - wall_thickness
+ inner_cone = cq.Workplane("ZX").cylinder(cone_height, inner_radius)
+
+ # Apply a chamfer to the top edge of the inner cone to match the outer cone
+ truncated_inner_cone = (
+ inner_cone.edges(">Y")
+ .chamfer(cone_height * 0.75)
+ .translate((0, -0.3, 0))
+ )
+
+ # Cut the inner cone from the outer cone to create a hollow cone
+ truncated_cone = truncated_cone.cut(truncated_inner_cone)
+
+ # Translate the truncated cone to the correct position
+ cone_translated = truncated_cone.translate((0, offset_height, 0))
+
+ return cone_translated
+ raise ValueError("Invalid roof type")
+
+ def generate_hollow_cylinder(height, outer_radius, inner_radius, offset=(0, 0, 0)):
+ if outer_radius <= inner_radius:
+ raise ValueError("outer_radius must be larger than inner_radius")
+
+ cylinder1 = (
+ cq.Workplane("ZX").transformed(
+ offset=offset).cylinder(height, outer_radius)
+ )
+ cylinder2 = (
+ cq.Workplane("ZX").transformed(
+ offset=offset).cylinder(height, inner_radius)
+ )
+
+ cylinder = cylinder1.cut(cylinder2)
+
+ return cylinder
+
+ reactor_shielding = generate_hollow_cylinder(
+ reactor_core_height,
+ reactor_core_radius,
+ reactor_core_radius - shield_thickness,
+ )
+
+ building_scale_factor = scale_factor
+ reactor_building = generate_hollow_cylinder(
+ reactor_core_height * building_scale_factor,
+ reactor_core_radius * building_scale_factor,
+ reactor_core_radius * building_scale_factor - contain_thickness,
+ )
+
+ reactor_core_floor = (
+ cq.Workplane("ZX")
+ .transformed(offset=(0, 0, -reactor_core_height / 2))
+ .cylinder(0.3, reactor_core_radius)
+ )
+
+ reactor_building_floor = (
+ cq.Workplane("ZX")
+ .transformed(offset=(0, 0, -reactor_core_height * building_scale_factor / 2))
+ .cylinder(0.3, reactor_core_radius * building_scale_factor)
+ )
+
+ support_structure_upper = generate_hollow_cylinder(
+ 0.3,
+ reactor_core_radius * building_scale_factor,
+ reactor_core_radius,
+ (0, 0, reactor_core_height / 2),
+ )
+
+ support_structure_middle = generate_hollow_cylinder(
+ 0.3,
+ reactor_core_radius * building_scale_factor,
+ reactor_core_radius,
+ (0, 0, 0),
+ )
+
+ support_structure_lower = generate_hollow_cylinder(
+ 0.3,
+ reactor_core_radius * building_scale_factor,
+ reactor_core_radius,
+ (0, 0, -reactor_core_height / 2),
+ )
+
+ building_height = reactor_core_height * building_scale_factor
+ roof = create_roof(
+ "cone", reactor_core_radius * building_scale_factor, building_height
+ )
+
+ # total_structure = reactor_shielding.union(hemisphere)
+ total_structure = reactor_shielding.union(reactor_building)
+ total_structure = total_structure.union(reactor_core_floor)
+ total_structure = total_structure.union(reactor_building_floor)
+ total_structure = total_structure.union(support_structure_upper)
+ total_structure = total_structure.union(support_structure_middle)
+ total_structure = total_structure.union(support_structure_lower)
+ total_structure = total_structure.union(roof)
+ box1 = cq.Workplane("front").box(100, 100, 100).translate((0, 0, 50))
+ total_structure = total_structure.cut(box1)
+
+ return total_structure
+
+
+# Open the file and load the data
+with open(file_path, 'r', encoding='utf-8') as file:
+ data = json.load(file)
+
+# Assuming your data is under the 'geometry' key
+geometry_data = data['geometry']
+aspect_ratio = geometry_data['aspect_ratio']
+radial_build_input = geometry_data['radial_build']
+
+containment_data = data['containment_vessel']
+building_sf = containment_data['building_scale_factor']
+shield_thickness_input = containment_data['shield_thickness']
+contain_thick = containment_data['containment_vessel_thickness']
+
+major_radius = aspect_ratio * radial_build_input[0]
+
+
+containment_vessel = reactor_building(
+ radial_build_input, major_radius, building_sf, shield_thickness_input, contain_thick)
+cq.exporters.export(containment_vessel, "containment.step")
diff --git a/galaxy/tools/containment_vessel/containment_vessel.xml b/galaxy/tools/containment_vessel/containment_vessel.xml
new file mode 100644
index 0000000..4fdc8ea
--- /dev/null
+++ b/galaxy/tools/containment_vessel/containment_vessel.xml
@@ -0,0 +1,33 @@
+
+
+ Tool to generate the CAD for a tokamak from a radial build
+
+
+ cadquery/cadquery:latest
+
+
+
+
+
+
+
+
+
+
+ s
+
+
+
+
+ This tool run the command writes the file input 'Text_To_Write' to the txt file 'Output_File' in an Ubuntu 20.04 container.
+ This is done via a python script to demonstrate running scripts in Galaxy tools.
+ Expected output:
+ std_out: ''
+ std_err: ''
+ outputs: Output_File: Content of the 'File_To_Write' input
+
+
+
diff --git a/galaxy/tools/create_sample_msh/create_sample_msh.py b/galaxy/tools/create_sample_msh/create_sample_msh.py
new file mode 100644
index 0000000..237b20f
--- /dev/null
+++ b/galaxy/tools/create_sample_msh/create_sample_msh.py
@@ -0,0 +1,53 @@
+import gmsh
+import argparse
+
+parser = argparse.ArgumentParser(
+ prog="create_sample_msh.py",
+ description="Usage: python heatflux-to-PINN.py ",
+)
+
+parser.add_argument(
+ "sample_diameter", help="diameter of the sample cylinder in mm", type=float
+)
+parser.add_argument(
+ "sample_thickness", help="thickness of the sample cylinder in mm", type=float
+)
+args = parser.parse_args()
+
+sample_diameter = float(args.sample_diameter)
+sample_thickness = float(args.sample_thickness)
+
+gmsh.initialize()
+gmsh.model.add("test")
+
+circle = gmsh.model.occ.addDisk(0, 0, 0, sample_diameter/2, sample_diameter/2)
+gmsh.model.occ.synchronize()
+
+ext = gmsh.model.occ.extrude([(2, circle)], 0, 0, sample_thickness)
+gmsh.model.occ.synchronize()
+
+surfaces = gmsh.model.getEntities(dim=2)
+
+bottom_tag = gmsh.model.addPhysicalGroup(2, [3])
+gmsh.model.setPhysicalName(2, bottom_tag, "left")
+
+top_tag = gmsh.model.addPhysicalGroup(2, [1])
+gmsh.model.setPhysicalName(2, top_tag, "right")
+
+side_tag = gmsh.model.addPhysicalGroup(2, [2])
+gmsh.model.setPhysicalName(2, side_tag, "side")
+
+vol_tag = gmsh.model.addPhysicalGroup(3, [1])
+gmsh.model.setPhysicalName(3, vol_tag, "volume")
+
+# gmsh.option.setNumber("Mesh.CharacteristicLengthMin", 0.5)
+# gmsh.option.setNumber("Mesh.CharacteristicLengthMax", 0.5)
+gmsh.model.mesh.generate(3)
+node_tags, node_coords, _ = gmsh.model.mesh.getNodes()
+num_nodes = len(node_tags)
+with open("num_nodes.txt", "w") as f:
+ f.write(f"{num_nodes}\n")
+
+gmsh.write("mesh.msh")
+# gmsh.fltk.run()
+gmsh.finalize()
diff --git a/galaxy/tools/create_sample_msh/create_sample_msh.xml b/galaxy/tools/create_sample_msh/create_sample_msh.xml
new file mode 100644
index 0000000..2c0afcf
--- /dev/null
+++ b/galaxy/tools/create_sample_msh/create_sample_msh.xml
@@ -0,0 +1,38 @@
+
+
+ generate mesh for disk sample
+
+
+ williamjsmith15/omniverse-gmsh:05092023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This tool generates a mesh for a cylindrical sample based on the specified diameter and thickness.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/galaxy/tools/cylinder_gen/cylinder_gen.py b/galaxy/tools/cylinder_gen/cylinder_gen.py
new file mode 100644
index 0000000..46e6588
--- /dev/null
+++ b/galaxy/tools/cylinder_gen/cylinder_gen.py
@@ -0,0 +1,549 @@
+# pylint: disable=redefined-outer-name
+# pylint: disable=import-error
+"""
+This module contains the `REBCOCoilDesign` class which is used for designing
+REBCO (Rare-Earth Barium Copper Oxide) coils.
+
+The class provides methods for defining the coil geometry, optimizing the coil
+design, and generating a 3D model of the coil.
+
+The module also includes functionality for parsing command-line arguments and
+reading parameters from a JSON file. This allows the coil design parameters to
+be easily specified and modified.
+
+Dependencies:
+ argparse: A standard library module for parsing command-line arguments.
+ json: A standard library module for working with JSON data.
+ cadquery: A Python library for building parametric 3D CAD models.
+ numpy: A library for numerical computing with Python.
+ scipy: A library for scientific computing with Python.
+"""
+
+import argparse
+import json
+import cadquery as cq
+import numpy as np
+from scipy.optimize import minimize
+
+
+class REBCOCoilDesign:
+ """
+ A class used to design REBCO (Rare-Earth Barium Copper Oxide) coils.
+
+ The class provides methods for defining the coil geometry, optimizing the coil
+ design, and generating a 3D model of the coil.
+
+ Attributes:
+ mu_0 (float): Vacuum permeability.
+ B_target (float): Target magnetic field.
+ workplane (str): The workplane for the 3D model.
+ ybco_rho (float): Density of YBCO.
+ critical_current_density (float): Critical current density of YBCO.
+ max_supply_current (float): Maximum supply current for the coil.
+ ybco_cost (float): Cost per unit volume of YBCO.
+ cu_cost (float): Cost per unit volume of copper.
+ ss_cost (float): Cost per unit volume of stainless steel.
+ ybco_cs (float): Cross-sectional area of YBCO.
+ coolant_cs (float): Cross-sectional area of coolant.
+ cu_cs (float): Cross-sectional area of copper.
+ ss_cs (float): Cross-sectional area of stainless steel.
+ min_i_rad (float): Minimum inner radius of the coil.
+ r_center (float): Radius of the center of the coil.
+ dr (float): Radial build of the coil.
+ dz (float): Height of the coil.
+ """
+ mu_0 = 4 * np.pi * 10**-7 # vacuum permeability
+ B_target = 9 # T, target magnetic field
+
+ def __init__(
+ self,
+ workplane="XY",
+ ybco_rho=6200,
+ critical_current_density=1e10,
+ max_supply_current=16.786e4,
+ ybco_cost=100,
+ cu_cost=10.3,
+ ss_cost=5,
+ ybco_cs=0.00009,
+ coolant_cs=0.000009,
+ cu_cs=0.000031,
+ ss_cs=0.000108,
+ min_i_rad=4.93,
+ r_center=0.18,
+ dr=0.1,
+ dz=0.1,
+ z_center=0,
+ max_supply_current_2=16.786e6,
+ ):
+ self.workplane = workplane
+ self.ybco_rho = ybco_rho
+ self.critical_current_density = critical_current_density
+ self.max_supply_current = max_supply_current
+ self.ybco_cost = ybco_cost
+ self.cu_cost = cu_cost
+ self.ss_cost = ss_cost
+ self.ybco_cs = ybco_cs
+ self.coolant_cs = coolant_cs
+ self.cu_cs = cu_cs
+ self.ss_cs = ss_cs
+ self.total_cs = ybco_cs + coolant_cs + cu_cs + ss_cs
+ self.min_i_rad = min_i_rad
+ self.turn_thickness = np.sqrt(self.total_cs)
+ self.r_center = r_center
+ self.z_center = z_center
+ self.dr = dr
+ self.dz = dz
+ self.max_supply_current = max_supply_current_2
+ self.max_allowable_current = min(
+ self.max_supply_current, self.critical_current_density / 3 * self.ybco_cs
+ )
+
+ self.coil = self.make_coil()
+
+ def num_turns(self, params):
+ """
+ Calculates the number of turns in the coil based on the average radius and current.
+
+ Parameters:
+ params (tuple): A tuple containing the current (float)
+ and the average radius (float) of the coil.
+
+ Returns:
+ float: The number of turns in the coil.
+ """
+ current, r_avg = params
+ return 2 * r_avg * self.B_target / (self.mu_0 * current)
+
+ def wire_length(self, params):
+ """
+ Calculates the length of the wire in the coil.
+
+ Parameters:
+ params (tuple): A tuple containing the current (float)
+ and the average radius (float) of the coil.
+
+ Returns:
+ float: The length of the wire in the coil.
+ """
+ _, r_avg = params
+ n = self.num_turns(params)
+ return 2 * np.pi * r_avg * n
+
+ def current_constraint(self, params):
+ """
+ Calculates the difference between the current and the maximum allowable current.
+
+ Parameters:
+ params (tuple): A tuple containing the current (float)
+ and the average radius (float) of the coil.
+
+ Returns:
+ float: The difference between the current and the maximum allowable current.
+ """
+ current, _ = params
+ return current - self.max_allowable_current
+
+ def turns_constraint(self, params):
+ """
+ Calculates the difference between the number of turns and the nearest integer value of turns
+
+ Parameters:
+ params (tuple): A tuple containing the current (float)
+ and the average radius (float) of the coil.
+
+ Returns:
+ float: The difference between the number of turns and the nearest integer value of turns.
+ """
+ n = self.num_turns(params)
+ return n - np.round(n)
+
+ def inner_radius_constraint(self, params):
+ """
+ Calculates the difference between the inner radius of the coil
+ and the minimum allowable inner radius.
+
+ Parameters:
+ params (tuple): A tuple containing the current (float)
+ and the average radius (float) of the coil.
+
+ Returns:
+ float: The difference between the inner radius of the coil
+ and the minimum allowable inner radius.
+ """
+ _, r_avg = params
+ n = self.num_turns(params)
+ i_rad = r_avg - 0.5 * n * self.turn_thickness
+ return i_rad - self.min_i_rad
+
+ def compute(self):
+ """
+ Computes the optimal parameters for the coil design by minimizing the wire length.
+
+ The function uses the scipy.optimize.minimize function with the 'SLSQP' method
+ (Sequential Least Squares Programming) which allows for bounds and constraint optimization.
+
+ The constraints are defined as follows:
+ - The current must be equal to the maximum allowable current.
+ - The number of turns must be an integer.
+ - The inner radius of the coil must be greater than or
+ equal to the minimum allowable inner radius.
+
+ The bounds are defined as follows:
+ - The current must be greater than or equal to 1.
+ - The average radius of the coil must be greater than or equal to the minimum inner radius.
+
+ The initial guess for the optimization is the maximum allowable current
+ and the minimum inner radius.
+
+ The result of the optimization is stored in the 'result' attribute of the class instance.
+
+ Returns:
+ None
+ """
+ bnds = ((1, None), (self.min_i_rad, None))
+ initial_guess = [self.max_allowable_current, self.min_i_rad]
+
+ cons = (
+ {"type": "eq", "fun": self.current_constraint},
+ {"type": "eq", "fun": self.turns_constraint},
+ {"type": "ineq", "fun": self.inner_radius_constraint},
+ )
+
+ self.result = minimize(
+ self.wire_length, initial_guess, bounds=bnds, constraints=cons
+ )
+
+ def make_coil(self):
+ """
+ Creates a coil based on the optimal parameters computed by the 'compute' method.
+
+ The function calculates the number of turns and the inner radius of the coil based on the
+ optimal parameters. It then creates a coil with these parameters and calculates the radii
+ of the coil.
+
+ The function also calculates the total cost of the coil.
+
+ Returns:
+ coil (object): The created coil object.
+ coil_radii (list): The radii of the coil.
+ """
+ self.compute()
+ # N = self.num_turns(self.result.x)
+ r_center = self.r_center
+ z_center = self.z_center
+ dr = self.dr
+ dz = self.dz
+ i_rad = r_center - (0.5 * dr)
+ o_rad = r_center + (0.5 * dr)
+ z_max = z_center + (0.5 * dz)
+ height = dz
+
+ print("COIL VALS")
+ print(r_center, z_center, height, z_max)
+ print(self.workplane)
+
+ outer_coil = cq.Workplane(self.workplane).circle(o_rad).extrude(height)
+ inner_coil = cq.Workplane(self.workplane).circle(i_rad).extrude(height)
+ outer_coil = outer_coil.translate((0, z_max, 0))
+ inner_coil = inner_coil.translate((0, z_max, 0))
+ radii = [i_rad, o_rad]
+ coil = outer_coil.cut(inner_coil)
+ # display(coil)
+ return coil, radii
+ # display(coil)
+
+ def calculate_cost(self):
+ """
+ Calculates the total cost of the coil.
+
+ The function calculates the cost of the YBCO, copper,
+ and stainless steel components of the coil
+ based on the wire length and the cost per unit volume of each material.
+
+ The total cost is the sum of the costs of the YBCO, copper, and stainless steel components,
+ multiplied by a factor of 9 to account for manufacturing costs.
+
+ The total cost is stored in the 'total_cost' attribute of the class instance.
+
+ Returns:
+ None
+ """
+ final_ybco_cost = (
+ self.wire_length(self.result.x)
+ * self.ybco_cs
+ / self.total_cs
+ * self.ybco_cost
+ * 1e-6
+ )
+ final_cu_cost = self.result.fun * self.cu_cs * self.cu_cost * 7900 * 1e-6
+ final_ss_cost = self.result.fun * self.ss_cs * self.ss_cost * 7900 * 1e-6
+ tot_mat_cost = final_ybco_cost + final_cu_cost + final_ss_cost
+ self.total_cost = round(tot_mat_cost * 9, 3)
+
+ def print_results(self):
+ """
+ Prints the results of the coil design.
+
+ The function first calls the 'compute' method to
+ calculate the optimal parameters for the coil design.
+ It then calculates various parameters of the coil,
+ such as the inner and outer radius, height,
+ current density, and number of turns, based on the optimal parameters.
+
+ The function also calculates the cost of the YBCO, copper,
+ and stainless steel components of the coil.
+
+ All these results are then printed to the console.
+
+ Returns:
+ None
+ """
+ self.compute()
+ n = self.num_turns(self.result.x)
+ i_rad = self.result.x[1] - 0.5 * np.sqrt(n) * self.turn_thickness
+ o_rad = self.result.x[1] + 0.5 * np.sqrt(n) * self.turn_thickness
+ height = np.sqrt(n) * self.turn_thickness
+ current_density = self.result.x[0] / self.ybco_cs
+
+ print(
+ "\n--------------------------------RESULTS--------------------------------\n"
+ )
+
+ # PARAMETERS
+ print("Field at center:", self.B_target, "T \n")
+ print("Current:", round(self.result.x[0], 3), "A")
+ print("Current Density:", round(current_density, 3), "A/m^2")
+ print("Inner radius:", round(i_rad, 3), "m")
+ print("Outer radius:", round(o_rad, 3), "m")
+ print("Height:", round(height, 3), "m")
+ print("Number of turns:", round(n))
+ print("Length of wire:", round(self.wire_length(self.result.x), 2), "m\n")
+
+ # COST
+ final_ybco_cost = (
+ self.wire_length(self.result.x)
+ * self.ybco_cs
+ / self.total_cs
+ * self.ybco_cost
+ * 1e-6
+ )
+ final_cu_cost = self.result.fun * self.cu_cs * self.cu_cost * 7900 * 1e-6
+ final_ss_cost = self.result.fun * self.ss_cs * self.ss_cost * 7900 * 1e-6
+ print("Cost of YBCO: $", round(final_ybco_cost, 3), "M")
+ print("Cost of copper: $", round(final_cu_cost, 3), "M")
+ print("Cost of steel: $", round(final_ss_cost, 3), "M")
+
+ tot_mat_cost = final_ybco_cost + final_cu_cost + final_ss_cost
+ print("Total material cost: $", round(tot_mat_cost, 3), "M")
+ print("Cost to manufacture w/ mfr factor 9: $",
+ round(tot_mat_cost * 9, 3), "M")
+ self.total_cost = round(tot_mat_cost * 9, 3)
+
+ def append_text_file(self, file_path):
+ """
+ Appends the coil parameters to a text file.
+
+ The function first calls the 'compute' method to
+ calculate the optimal parameters for the coil design.
+ It then calculates the average radius, number of turns,
+ and current based on the optimal parameters.
+
+ These parameters, along with the coordinates of the center of the coil,
+ are then written to the text file
+ specified by 'file_path'. Each parameter is separated by a comma,
+ and each set of parameters is written
+ on a new line.
+
+ Parameters:
+ file_path (str): The path to the text file to which the coil parameters will be appended.
+
+ Returns:
+ None
+ """
+ self.compute()
+ r_avg = (i_rad + o_rad) / 2
+ turns = round(n)
+ current = round(self.result.x[0], 3)
+
+ with open(file_path, "a", encoding='utf-8') as file:
+ file.write(
+ f"{r_avg},{turns},{current},{self.r_center},{self.z_center}\n")
+
+
+class Cylinder:
+ """
+ A class used to represent a Cylinder.
+
+ The class provides methods for defining the cylinder geometry, calculating its volume,
+ and generating a 3D model of the cylinder.
+
+ Attributes:
+ radius (float): The radius of the cylinder.
+ height (float): The height of the cylinder.
+ """
+
+ def __init__(self, radial_build, height):
+ reactor = cq.Assembly()
+ outer_radius = 0
+ layers = []
+ i = 0
+ while i <= 3:
+ print(radial_build[i], type(radial_build[i]))
+ outer_radius += round(float(radial_build[i]), 2)
+ print("outer radius")
+ print(outer_radius, round(
+ float(radial_build[i]), 2), (radial_build[i]))
+ if i == 0:
+ cylinder = (
+ cq.Workplane("XY")
+ .cylinder(height, radial_build[i])
+ )
+ layers.append(cylinder)
+ else:
+ inner_radius = outer_prev
+ cylinder = self.generate_hollow_cylinder(
+ height, outer_radius, inner_radius)
+ layers.append(cylinder)
+ reactor.add(cylinder)
+ self.reactor = reactor
+ self.layers = layers
+ outer_prev = outer_radius
+ i = i+1
+
+ def generate_hollow_cylinder(self, height, outer_radius, inner_radius):
+ """
+ Generates a 3D model of a hollow cylinder using CadQuery.
+
+ Parameters:
+ height (float): The height of the cylinder.
+ outer_radius (float): The outer radius of the cylinder.
+ inner_radius (float): The inner radius of the cylinder.
+
+ Returns:
+ cylinder (CadQuery object): The 3D model of the hollow cylinder.
+ """
+
+ if outer_radius <= inner_radius:
+ raise ValueError("outer_radius must be larger than inner_radius")
+
+ cylinder1 = (
+ cq.Workplane("XY")
+ .cylinder(height, outer_radius)
+ )
+ cylinder2 = (
+ cq.Workplane("XY")
+ .cylinder(height, inner_radius)
+ )
+
+ cylinder = cylinder1.cut(cylinder2)
+ print("radii: ", inner_radius, outer_radius)
+
+ return cylinder
+
+
+def make_coil(min_i_rad, current):
+ """
+ Creates a coil design and calculates its cost.
+
+ Parameters:
+ min_i_rad (float): The minimum inner radius of the coil.
+ current (float): The maximum supply current for the coil.
+
+ Returns:
+ coil (object): The created coil object.
+ coil_radii (list): The radii of the coil.
+ result (object): The result of the coil design.
+ coil_cost (float): The total cost of the coil.
+ """
+ coil_design = REBCOCoilDesign(
+ min_i_rad=min_i_rad, workplane="XY", max_supply_current=current)
+ result = coil_design.result
+ coil_design.calculate_cost()
+ coil_cost = coil_design.total_cost
+ coil, coil_radii = coil_design.make_coil()
+
+ return coil, coil_radii, result, coil_cost
+
+
+def generate_cylinder(radial_build, height, n_coils, inner_radius, current, output_filepath):
+ """
+ Generates a cylinder with a specified number of coils and calculates the total cost.
+
+ Parameters:
+ radial_build (float): The radial build of the cylinder.
+ height (float): The height of the cylinder.
+ n_coils (int): The number of coils in the cylinder.
+ inner_radius (float): The inner radius of the coil.
+ current (float): The maximum supply current for the coil.
+ output_filepath (str): The path to the output file where the coil info will be written.
+
+ Returns:
+ None. The function writes coil info to a CSV file and prints the total cost of each coil.
+ """
+ total_cost = 0
+ count = 0
+ cylinder = Cylinder(radial_build, height)
+
+ # for i,layer in enumerate(cylinder.layers):
+ # layer.val().exportStep(f"data/geometry_data/Cylindrical/layer{i}.step")
+
+ coil_z = np.linspace(-height/2, height/2, n_coils)
+
+ # Write coil info to csv file
+
+ coils = cq.Assembly()
+
+ with open(output_filepath, "w", encoding='utf-8') as file:
+ file.write(
+ "N,I (A),Inner Radius,Outer radius,Coil_X,Coil_Y,Coil_Z,Normal X,Normal Y,Normal Z \n")
+
+ for coil_z in coil_z:
+ coil, _, _, coil_cost = make_coil(
+ inner_radius, current)
+ outer_radius = inner_radius + (0.1 * inner_radius)
+ coil = coil.translate(cq.Vector(0, 0, coil_z))
+ total_cost += coil_cost
+ print("Total Cost of Coil", count, "is:", coil_cost)
+ cylinder.reactor.add(coil)
+ coils.add(coil)
+ count += 1
+ if count == 1:
+ i_val = 10*current
+ elif count == 1:
+ i_val = 10*current
+ else:
+ i_val = current
+
+ file.write(
+ f"1,{i_val},{inner_radius},{outer_radius},0,0,{coil_z},0,0,1\n")
+
+ # print("Count : ",count,N_coils)
+ cylinder.reactor.save("reactor.step")
+ coils.save("coils.step")
+ print("Total Cost of all Coils is:", total_cost)
+ return cylinder
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('file_path')
+parser.add_argument('output_filepath')
+args = parser.parse_args()
+file_path = args.file_path
+output_csv = args.output_filepath
+# Open the file and load the data
+with open(file_path, 'r', encoding='utf-8') as file:
+ data = json.load(file)
+
+# Now, assign the values to variables
+# Assuming your data is under the 'geometry' key
+geometry_data = data['geometry']
+radial_build = geometry_data['radial_build']
+reactor_height = geometry_data['height']
+coil_num = geometry_data['number_of_coils']
+current = geometry_data['current']
+
+
+inner_radius = sum(radial_build)+3
+
+generate_cylinder(radial_build, reactor_height, coil_num,
+ inner_radius, current, output_csv)
diff --git a/galaxy/tools/cylinder_gen/cylinder_gen.xml b/galaxy/tools/cylinder_gen/cylinder_gen.xml
new file mode 100644
index 0000000..29e5294
--- /dev/null
+++ b/galaxy/tools/cylinder_gen/cylinder_gen.xml
@@ -0,0 +1,30 @@
+
+
+ Tool to generate the CAD for a cylindrical reactor from a radial build
+
+
+ nttaudom/scipycq:15.12.23
+
+
+
+ &1 &&
+ mv 'reactor.step' '$reactor_output'
+ ]]>
+
+
+
+
+
+
+ s
+
+
+
+
+
+ This tool creates a cylinder from an input JSON file containing component thicknesses and height. Runs in a cadquery container.
+ Expected output: .STEP file describing the reactor
+
+
+
diff --git a/galaxy/tools/download_nucleus/download.py b/galaxy/tools/download_nucleus/download.py
new file mode 100644
index 0000000..3a82cc3
--- /dev/null
+++ b/galaxy/tools/download_nucleus/download.py
@@ -0,0 +1,77 @@
+import requests
+import json
+import argparse
+
+parser = argparse.ArgumentParser(
+ prog="download_nucleus.py",
+ description="""nucleus download.
+ Usage: python download.py """
+)
+
+parser.add_argument('url', help='url')
+parser.add_argument('username', help='username')
+parser.add_argument('password', help='password')
+parser.add_argument('file', help='file')
+
+args = parser.parse_args()
+url = args.url
+username = args.username
+password = args.password
+files = args.file
+
+
+# ------------------------------------------------------------------------------
+# Authenticate with Authelia
+
+AUTHELIA = "auth.mcfe.itservices.manchester.ac.uk"
+# TODO: Change to bind user or have as tool input
+AUTHELIA_USERNAME = 'authelia_galaxy_bind'
+AUTHELIA_PASSWORD = 'r4l/@p8f42mu57M@'
+NUCLEUS_BASE = "nucleus-api.mcfe.itservices.manchester.ac.uk"
+
+response = requests.post(
+ f"https://{AUTHELIA}/api/firstfactor",
+ headers={"accept": "application/json", "Content-Type": "application/json"},
+ data=json.dumps({"username": AUTHELIA_USERNAME,
+ "password": AUTHELIA_PASSWORD}),
+)
+
+cookies = response.cookies
+
+print("Authelia response:", response.json())
+
+# ------------------------------------------------------------------------------
+# Get user info (validate Authelia session)
+
+response = requests.get(
+ f"https://{AUTHELIA}/api/user/info",
+ headers={"accept": "application/json"},
+ cookies=cookies,
+)
+
+print("User info:", response.json())
+
+while True:
+ file_name = url.split("/")[-1]
+ if "." not in file_name:
+ print("Not a valid URL for downloading single files.")
+ print("Please input another valid URL")
+ else:
+ break
+
+response = requests.get(
+ f"https://{NUCLEUS_BASE}/omnicli/download?url={url}",
+ headers={
+ "accept": "application/json",
+ "username": username,
+ "password": password
+ },
+ cookies=cookies,
+)
+
+# user_profile = os.path.expanduser("~")
+# destination_path = os.path.join(user_profile, file_name)
+
+# Write the response content to the output file
+with open(files, "wb") as file:
+ file.write(response.content)
diff --git a/galaxy/tools/download_nucleus/download_nucleus.xml b/galaxy/tools/download_nucleus/download_nucleus.xml
new file mode 100644
index 0000000..91c3941
--- /dev/null
+++ b/galaxy/tools/download_nucleus/download_nucleus.xml
@@ -0,0 +1,31 @@
+
+ Download a file from the Nucleus
+
+
+ williamjsmith15/nucleus-api:17052024
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This tools downloads a given file from a loaction on the nucleus using the nucleus api.
+
+
+
+
+
\ No newline at end of file
diff --git a/galaxy/tools/extract_openmc_tallies/extract_openmc_tallies.xml b/galaxy/tools/extract_openmc_tallies/extract_openmc_tallies.xml
new file mode 100644
index 0000000..29e7e10
--- /dev/null
+++ b/galaxy/tools/extract_openmc_tallies/extract_openmc_tallies.xml
@@ -0,0 +1,33 @@
+
\ No newline at end of file
diff --git a/galaxy/tools/extract_openmc_tallies/extract_tallies.py b/galaxy/tools/extract_openmc_tallies/extract_tallies.py
new file mode 100644
index 0000000..57d2747
--- /dev/null
+++ b/galaxy/tools/extract_openmc_tallies/extract_tallies.py
@@ -0,0 +1,40 @@
+import openmc
+import json
+import argparse
+
+parser = argparse.ArgumentParser(
+ prog='openmc_parametric.py',
+ description='''OpenMC parametric CAD run script.
+ Useage: python openmc_parametric.py '''
+)
+
+parser.add_argument(
+ 'statepoint_file',
+ help='statepoint.5.h5 file with the results of the simulation. Format: h5'
+)
+parser.add_argument(
+ 'output_file',
+ help='File to write the results to. Format: JSON'
+)
+args = parser.parse_args()
+
+statepoint_filename = args.statepoint_file
+output_filename = args.output_file
+
+sp = openmc.StatePoint(statepoint_filename)
+tally_dict = sp.tallies
+
+tally_names = []
+
+for tally in tally_dict:
+ tally_names.append(tally_dict[tally].name)
+
+tallies = {}
+
+for name in tally_names:
+ tally = sp.get_tally(name=name)
+ tally_dict = json.loads(tally.get_pandas_dataframe().to_json())
+ tallies[name] = tally_dict
+
+with open(output_filename, 'w') as write_f:
+ json.dump(tallies, write_f)
diff --git a/galaxy/tools/flatten_USD/flatten_USD.xml b/galaxy/tools/flatten_USD/flatten_USD.xml
new file mode 100644
index 0000000..e2c4d98
--- /dev/null
+++ b/galaxy/tools/flatten_USD/flatten_USD.xml
@@ -0,0 +1,30 @@
+
+ Flatten USD Scene for Conversion Steps
+
+
+ ghcr.io/uomresearchit/usdutils:06072023
+
+
+
+ &1 &&
+ mv flat.usd '$USD_flat'
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+ This tool takes in a USD file and flattens it for CAD conversion steps.
+
+
+
+
+
\ No newline at end of file
diff --git a/galaxy/tools/get_bounding_box/Dockerfile b/galaxy/tools/get_bounding_box/Dockerfile
new file mode 100644
index 0000000..8bae21e
--- /dev/null
+++ b/galaxy/tools/get_bounding_box/Dockerfile
@@ -0,0 +1,8 @@
+# Start from a Miniconda base image
+FROM continuumio/miniconda3
+
+# Create a Conda environment and install pyOCC
+RUN conda create -n pyocc python=3.8 && \
+ echo "source activate pyocc" > ~/.bashrc && \
+ /bin/bash -c "source activate pyocc && \
+ conda install -c conda-forge pythonocc-core"
diff --git a/galaxy/tools/get_bounding_box/get_bounding_box.py b/galaxy/tools/get_bounding_box/get_bounding_box.py
new file mode 100644
index 0000000..e1c6a8a
--- /dev/null
+++ b/galaxy/tools/get_bounding_box/get_bounding_box.py
@@ -0,0 +1,49 @@
+# pylint: disable=import-error
+"""
+This module contains functionality for calculating the bounding box of a 3D object
+imported from a STEP file using the FreeCAD library.
+
+The module uses the FreeCAD and Part libraries to load the STEP file and access the
+3D object. It then calculates the bounding box of the object and writes the dimensions
+to a specified output file. The dimensions are rounded to 5 decimal places for precision.
+
+The module also includes functionality for parsing command-line arguments to specify
+the output file path.
+
+Dependencies:
+ argparse: A standard library module for parsing command-line arguments.
+ FreeCAD: A Python library for scripting FreeCAD, a 3D CAD modeler.
+ Part: A FreeCAD module for working with 3D parts.
+"""
+
+import argparse
+import FreeCAD
+import Part
+
+
+# Load the STEP file
+doc = FreeCAD.newDocument()
+Part.insert("file_to_import.step", doc.Name)
+
+# Get the active object (assuming the STEP file has one main object)
+obj = doc.ActiveObject
+
+# Calculate the bounding box
+bbox = obj.Shape.BoundBox
+
+parser = argparse.ArgumentParser()
+parser.add_argument('output_file_path')
+args = parser.parse_args()
+output_file = "output.csv"
+
+# Write the bounding box dimensions to a file, rounding to 5 decimal places
+with open(output_file, "w", encoding='utf-8') as file:
+ file.write("Min/Max,X,Y,Z\n")
+
+ # Write the data with rounding
+ file.write(
+ f"Min,{round(bbox.XMin, 5)},{round(bbox.YMin, 5)},{round(bbox.ZMin, 5)}\n")
+ file.write(
+ f"Max,{round(bbox.XMax, 5)},{round(bbox.YMax, 5)},{round(bbox.ZMax, 5)}\n")
+
+FreeCAD.closeDocument(doc.Name)
diff --git a/galaxy/tools/get_bounding_box/get_bounding_box.xml b/galaxy/tools/get_bounding_box/get_bounding_box.xml
new file mode 100755
index 0000000..c88b2ca
--- /dev/null
+++ b/galaxy/tools/get_bounding_box/get_bounding_box.xml
@@ -0,0 +1,30 @@
+
+
+ Utility function to get the bounding box of a given STEP file using freeCAD
+
+
+ williamjsmith15/omniverse-freecad:31072023
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This tool takes in a STEP file as input and finds the maxiumum and minimum x,y,z of the object
+ (The bounding box). This is returned to the user via a .csv file
+
+
+
diff --git a/galaxy/tools/h5m_materials/h5m_materials.py b/galaxy/tools/h5m_materials/h5m_materials.py
new file mode 100644
index 0000000..1d44ad3
--- /dev/null
+++ b/galaxy/tools/h5m_materials/h5m_materials.py
@@ -0,0 +1,25 @@
+import argparse
+import dagmc_h5m_file_inspector as di
+
+#############
+# IMPORT CAD
+#############
+
+parser = argparse.ArgumentParser(
+ prog='h5m_materials.py',
+ description = 'Returns a list of material volumes from the inputted h5m file. Usage: python h5m_materials.py