4 Commits

Author SHA1 Message Date
d1e035e01e Update repo with template version v1.2.8
All checks were successful
CI for addams_bec / test (pull_request) Successful in 35s
CI for addams_bec / test (push) Successful in 32s
2026-02-27 15:49:26 +01:00
d49d9cef33 Update repo with template version v1.2.7
Some checks failed
CI for addams_bec / test (push) Failing after 1s
CI for addams_bec / test (pull_request) Failing after 0s
2026-02-27 12:11:40 +01:00
a62fe77b56 fix: ipython startup
All checks were successful
CI for addams_bec / test (pull_request) Successful in 30s
CI for addams_bec / test (push) Successful in 28s
2026-01-17 18:24:39 +01:00
e642cbaae8 refactor: upgrade copier to v1-2-2
All checks were successful
CI for addams_bec / test (pull_request) Successful in 35s
CI for addams_bec / test (push) Successful in 29s
2025-09-11 18:24:35 +02:00
7 changed files with 231 additions and 164 deletions

View File

@@ -2,7 +2,7 @@
# It is needed to track the repo template version, and editing may break things.
# This file will be overwritten by copier on template updates.
_commit: v1.2.1
_commit: v1.2.8
_src_path: https://github.com/bec-project/plugin_copier_template.git
make_commit: false
project_name: addams_bec

View File

@@ -28,7 +28,7 @@ on:
description: "Python version to use"
required: false
type: string
default: "3.11"
default: "3.12"
permissions:
pull-requests: write
@@ -44,7 +44,19 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "${{ inputs.PYTHON_VERSION || '3.11' }}"
python-version: "${{ inputs.PYTHON_VERSION || '3.12' }}"
- name: Checkout BEC Plugin Repository
uses: actions/checkout@v4
with:
repository: bec/addams_bec
ref: "${{ inputs.BEC_PLUGIN_REPO_BRANCH || github.head_ref || github.sha }}"
path: ./addams_bec
- name: Lint for merge conflicts from template updates
shell: bash
# Find all Copier conflicts except this line
run: '! grep -r "<<<<<<< before updating" | grep -v "grep -r \"<<<<<<< before updating"'
- name: Checkout BEC Core
uses: actions/checkout@v4
@@ -67,13 +79,6 @@ jobs:
ref: "${{ inputs.BEC_WIDGETS_BRANCH || 'main' }}"
path: ./bec_widgets
- name: Checkout BEC Plugin Repository
uses: actions/checkout@v4
with:
repository: bec/addams_bec
ref: "${{ inputs.BEC_PLUGIN_REPO_BRANCH || github.head_ref || github.sha }}"
path: ./addams_bec
- name: Install dependencies
shell: bash
run: |
@@ -94,4 +99,4 @@ jobs:
- name: Run Pytest with Coverage
id: coverage
run: pytest --random-order --cov=./addams_bec --cov-config=./addams_bec/pyproject.toml --cov-branch --cov-report=xml --no-cov-on-fail ./addams_bec/tests/
run: pytest --random-order --cov=./addams_bec --cov-config=./addams_bec/pyproject.toml --cov-branch --cov-report=xml --no-cov-on-fail ./addams_bec/tests/ || test $? -eq 5

View File

@@ -0,0 +1,62 @@
name: Create template upgrade PR for addams_bec
on:
workflow_dispatch:
permissions:
pull-requests: write
jobs:
create_update_branch_and_pr:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install tools
run: |
pip install copier PySide6
- name: Checkout
uses: actions/checkout@v4
- name: Perform update
run: |
git config --global user.email "bec_ci_staging@psi.ch"
git config --global user.name "BEC automated CI"
branch="chore/update-template-$(python -m uuid)"
echo "switching to branch $branch"
git checkout -b $branch
echo "Running copier update..."
output="$(copier update --trust --defaults --conflict inline 2>&1)"
echo "$output"
msg="$(printf '%s\n' "$output" | head -n 1)"
if ! grep -q "make_commit: true" .copier-answers.yml ; then
echo "Autocommit not made, committing..."
git add -A
git commit -a -m "$msg"
fi
if diff-index --quiet HEAD ; then
echo "No changes detected"
exit 0
fi
git push -u origin $branch
curl -X POST "https://gitea.psi.ch/api/v1/repos/${{ gitea.repository }}/pulls" \
-H "Authorization: token ${{ secrets.CI_REPO_WRITE }}" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"Template: $(echo $msg)\",
\"body\": \"This PR was created by Gitea Actions\",
\"head\": \"$(echo $branch)\",
\"base\": \"main\"
}"

View File

@@ -1,7 +0,0 @@
include:
- file: /templates/plugin-repo-template.yml
inputs:
name: addams_bec
target: addams_bec
branch: $CHILD_PIPELINE_BRANCH
project: bec/awi_utils

View File

@@ -1,75 +1,57 @@
import builtins
import collections
import functools
import json
import math
import pathlib
import numpy
from bec_ipython_client.main import BECClientPrompt
from bec_ipython_client.prettytable import PrettyTable
__all__ = [
'setlat',
'setlambda',
'setmode',
'freeze',
'unfreeze',
'br',
'ubr',
'mvhkl',
'umvhkl',
'ca',
'wh',
'pa',
'orientAdd',
'orientRemove',
'orientShow',
'orientFit',
'ct'
"setlat",
"setlambda",
"setmode",
"freeze",
"unfreeze",
"br",
"ubr",
"mvhkl",
"umvhkl",
"ca",
"wh",
"pa",
"orientAdd",
"orientRemove",
"orientShow",
"orientFit",
"ct",
]
bec = builtins.__dict__.get("bec")
dev = builtins.__dict__.get("dev")
scans = builtins.__dict__.get("scans")
class BECClientPromptDiffractometer(BECClientPrompt):
@property
def username(self):
"""current username"""
if "x04v" in dev:
return "x04v"
if "x04h" in dev:
return "x04h"
return "demo"
bec._ip.prompts = BECClientPromptDiffractometer(ip=bec._ip, username="demo", client=bec._client, status=1)
# check for diffractometer device
diffract = None
if dev is not None:
if 'x04h' in dev:
if "x04h" in dev:
diffract = dev.x04h
elif 'x04v' in dev:
elif "x04v" in dev:
diffract = dev.x04v
if diffract is not None:
RealPosition = collections.namedtuple('RealPosition', ' '.join(diffract.get_real_positioners()))
RealPosition = collections.namedtuple("RealPosition", " ".join(diffract.get_real_positioners()))
def freeze(
angle: float | None
):
def freeze(angle: float | None):
"""
Freeze the value of the mode dependent angle, so when calculating motor positions
corresponding to an arbitrary (H, K, L ), the angle will be reset to the frozen value
Freeze the value of the mode dependent angle, so when calculating motor positions
corresponding to an arbitrary (H, K, L ), the angle will be reset to the frozen value
before the calculation no matter what the current position of the diffractometer.
"""
diffract.freeze(angle)
def unfreeze():
"""
Subsequent angle calculations will use whatever the current value of the associated
@@ -77,74 +59,69 @@ def unfreeze():
"""
diffract.unfreeze()
def setlat(
a: float, b: float, c: float, alpha: float, beta: float, gamma: float
):
def setlat(a: float, b: float, c: float, alpha: float, beta: float, gamma: float):
"""
Set sample lattice parameters
"""
diffract.set_lattice((a, b, c, alpha, beta, gamma))
def setlambda(
wavelength: float
):
def setlambda(wavelength: float):
"""
Set the x-ray wavelength (in Angstroms)
"""
if wavelength <= 0:
print('Invalid input: wavelength <=0!')
print("Invalid input: wavelength <=0!")
return
current_wavelength = diffract.get_wavelength()
if math.isclose(wavelength, current_wavelength):
print(f'Still using {current_wavelength} A')
print(f"Still using {current_wavelength} A")
else:
diffract.set_wavelength(wavelength)
print(f'Lambda reset from {current_wavelength} to {wavelength} A')
print(f"Lambda reset from {current_wavelength} to {wavelength} A")
def setmode(
mode: int
):
def setmode(mode: int):
"""
Set the geometry mode
"""
if mode < 0 or mode > 2:
print('Valid mode is from 0 to 2')
print("Valid mode is from 0 to 2")
return
current_mode = diffract.get_mode()
if mode == current_mode:
print(f'Still using mode {current_mode}')
print(f"Still using mode {current_mode}")
else:
diffract.set_mode(mode)
print(f'Mode reset from {current_mode} to {mode}')
print(f"Mode reset from {current_mode} to {mode}")
def mvhkl(
h: float, k: float, l: float, auto=False
):
def mvhkl(h: float, k: float, l: float, auto=False):
"""
Move to the reciprocol space coordinates
"""
try:
angles = diffract.forward(h, k, l)[:-2]
except Exception as exc:
print(f'{h} {k} {l} is not obtainable: {exc}')
print(f"{h} {k} {l} is not obtainable: {exc}")
return
if not auto:
for axis, current, target in zip(RealPosition._fields, _currentPosition(), angles):
print('%7s = %9.4f --> %9.4f' % (axis, current, target))
print("%7s = %9.4f --> %9.4f" % (axis, current, target))
answer = input('Move to these values? [Y/n]: ')
if answer.startswith(('N', 'n')):
print('Move abandoned.')
answer = input("Move to these values? [Y/n]: ")
if answer.startswith(("N", "n")):
print("Move abandoned.")
return
br(h, k, l)
def br(
h: float, k: float, l: float
):
def br(h: float, k: float, l: float):
"""
Move to the reciprocol space coordinates
"""
@@ -156,9 +133,8 @@ def br(
scans.mv(*args, relative=False)
def ubr(
h: float, k: float, l: float
):
def ubr(h: float, k: float, l: float):
"""
Move to the reciprocol space coordinates with updates
"""
@@ -170,44 +146,43 @@ def ubr(
scans.umv(*args, relative=False)
def umvhkl(
h: float, k: float, l: float, auto=False
):
def umvhkl(h: float, k: float, l: float, auto=False):
"""
Move to the reciprocol space coordinates with updates
"""
try:
angles = diffract.forward(h, k, l)[:-2]
except Exception as exc:
print(f'{h} {k} {l} is not obtainable: {exc}')
print(f"{h} {k} {l} is not obtainable: {exc}")
return
if not auto:
for axis, current, target in zip(RealPosition._fields, _currentPosition(), angles):
print('%7s = %9.4f --> %9.4f' % (axis, current, target))
print("%7s = %9.4f --> %9.4f" % (axis, current, target))
answer = input('Move to these values? [Y/n]: ')
if answer.startswith(('N', 'n')):
print('Move abandoned.')
answer = input("Move to these values? [Y/n]: ")
if answer.startswith(("N", "n")):
print("Move abandoned.")
return
ubr(h, k, l)
def ca(
h: float, k: float, l: float
):
def ca(h: float, k: float, l: float):
"""
Calculate angle positions for a given point in reciprocol space
"""
angles = diffract.forward(h, k, l)
print("\nCalculated positions:\n")
print(f'H K L = {h} {k} {l}')
print('BetaIn = %.5f BetaOut = %.5f' %(angles[-2], angles[-1]))
print('Lambda = %.3f' % diffract.get_wavelength())
print(f"H K L = {h} {k} {l}")
print("BetaIn = %.5f BetaOut = %.5f" % (angles[-2], angles[-1]))
print("Lambda = %.3f" % diffract.get_wavelength())
print()
_showAngles(angles[:-2])
def wh():
"""
Show where principal axes and reciprocal space
@@ -218,41 +193,45 @@ def wh():
betaIn = diffract.betaIn.position
betaOut = diffract.betaOut.position
print(f'H K L = {h:.4f} {k:.4f} {l:.4f}')
print('BetaIn = %.5f BetaOut = %.5f' %(betaIn, betaOut))
print('Lambda = %.3f' % diffract.get_wavelength())
print(f"H K L = {h:.4f} {k:.4f} {l:.4f}")
print("BetaIn = %.5f BetaOut = %.5f" % (betaIn, betaOut))
print("Lambda = %.3f" % diffract.get_wavelength())
print()
_showAngles()
def pa():
"""
Show geometry parameters
"""
if diffract.name == 'x04v':
print('x04v (Newport Microcontrols 2+3 at SLS) vertical geometry')
elif diffract.name == 'x04h':
print('x04h (Newport Microcontrols 2+3 at SLS) horizontal geometry')
if diffract.name == "x04v":
print("x04v (Newport Microcontrols 2+3 at SLS) vertical geometry")
elif diffract.name == "x04h":
print("x04h (Newport Microcontrols 2+3 at SLS) horizontal geometry")
match mode := diffract.get_mode():
case 0:
print(f' BetaIn Fixed (mode {mode})')
print(f" BetaIn Fixed (mode {mode})")
case 1:
print(f' BetaOut Fixed (mode {mode})')
print(f" BetaOut Fixed (mode {mode})")
case 2:
print(f' BetaIn equals BetaOut (mode {mode})')
print(f" BetaIn equals BetaOut (mode {mode})")
if beta_frozen := diffract.get_frozen():
print(f' Frozen coordinate: {beta_frozen}')
print(f" Frozen coordinate: {beta_frozen}")
def orientShow():
"""
Display list of measured reflections
"""
print('\n(Using lattice constants:)')
print("\n(Using lattice constants:)")
lattice = diffract.get_lattice()
print('a = %.4g, b = %.4g, b = %.4g, alpha = %.6g, beta = %.6g, gamma = %.6g' %
(lattice[0], lattice[1], lattice[2], lattice[3], lattice[4], lattice[5]))
print(
"a = %.4g, b = %.4g, b = %.4g, alpha = %.6g, beta = %.6g, gamma = %.6g"
% (lattice[0], lattice[1], lattice[2], lattice[3], lattice[4], lattice[5])
)
print("\n------------------------------------------------\n")
@@ -263,17 +242,15 @@ def orientShow():
_showUB()
def orientRemove(
h: float, k: float, l: float
):
def orientRemove(h: float, k: float, l: float):
"""
Remove a measured reflection from the list
"""
diffract.remove_reflection(h, k, l)
def orientAdd(
h: float, k: float, l: float, *args
):
def orientAdd(h: float, k: float, l: float, *args):
"""
Add a reflection to the list of measured reflections
"""
@@ -282,63 +259,67 @@ def orientAdd(
response = diffract.real_position
# The original return value is of namedtuple type,
# which gets serialized to a dictionary by the device server.
angles = tuple(response['values'][axis] for axis in response['fields'] if axis != 'nu')
angles = tuple(response["values"][axis] for axis in response["fields"] if axis != "nu")
if len(angles) < 4:
print('Please specify all angles')
print("Please specify all angles")
return
diffract.add_reflection(h, k, l, angles)
def orientSave(
filename: str
):
def orientSave(filename: str):
"""
Save the current reflections
"""
configuration = {}
configuration['geometry'] = diffract.name
configuration['wavelength'] = diffract.get_wavelength()
configuration['lattice'] = diffract.get_lattice()
configuration['reflections'] = diffract.get_reflections()
configuration["geometry"] = diffract.name
configuration["wavelength"] = diffract.get_wavelength()
configuration["lattice"] = diffract.get_lattice()
configuration["reflections"] = diffract.get_reflections()
filepath = pathlib.Path(filename)
if filepath.exists():
answer = input('File "%s" already exists. Do you want to overwrite it? [y/N]: ' %(filepath.absolute()))
if not answer.startswith(('Y', 'y')):
answer = input(
'File "%s" already exists. Do you want to overwrite it? [y/N]: ' % (filepath.absolute())
)
if not answer.startswith(("Y", "y")):
return
with open(filepath, 'w') as f:
with open(filepath, "w") as f:
json.dump(configuration, f)
def orientLoad(
filename: str
):
def orientLoad(filename: str):
"""
Load relfections from file
"""
with open(filename, 'r') as f:
with open(filename, "r") as f:
configuration = json.load(f)
if configuration['geometry'] != diffract.name:
print('Saved orientation is for a different geometry "%s", current is "%s".' % configuration['geometry'], diffract.name)
if configuration["geometry"] != diffract.name:
print(
'Saved orientation is for a different geometry "%s", current is "%s".'
% configuration["geometry"],
diffract.name,
)
return
# save current wavelength, lattice and reflections
saved_wavelength = diffract.get_wavelength()
saved_lattice = diffract.get_lattice()
saved_reflections = diffract.get_reflections()
try:
diffract.set_lattice(configuration['lattice'])
diffract.set_lattice(configuration["lattice"])
diffract.clear_reflections()
for reflection in configuration['reflections']:
for reflection in configuration["reflections"]:
diffract.add_reflection(*reflection)
_showReflections(configuration['reflections'])
_showReflections(configuration["reflections"])
print("\n------------------------------------------------\n")
# set wavelength temporarily for orientFit and restore later
diffract.set_wavelength(configuration['wavelength'])
diffract.set_wavelength(configuration["wavelength"])
orientFit()
except Exception as exc:
# restore saved lattice and reflections
@@ -352,6 +333,7 @@ def orientLoad(
# restore wavelength
diffract.set_wavelength(saved_wavelength)
def orientFit():
"""
Fit UB matrix from given reflections
@@ -364,19 +346,22 @@ def orientFit():
diffract.compute_UB()
_showUB()
def ct(exp_time: float):
"""
Acquire all detectors
"""
scans.acquire(exp_time=exp_time)
def _showUB():
UB = diffract.get_UB()
print('Orientation matrix by row:')
print(' Row 1: %8.5f %8.5f %8.5f' % (UB[0,0], UB[0,1], UB[0,2]))
print(' Row 2: %8.5f %8.5f %8.5f' % (UB[1,0], UB[1,1], UB[1,2]))
print(' Row 3: %8.5f %8.5f %8.5f' % (UB[2,0], UB[2,1], UB[2,2]))
print("Orientation matrix by row:")
print(" Row 1: %8.5f %8.5f %8.5f" % (UB[0, 0], UB[0, 1], UB[0, 2]))
print(" Row 2: %8.5f %8.5f %8.5f" % (UB[1, 0], UB[1, 1], UB[1, 2]))
print(" Row 3: %8.5f %8.5f %8.5f" % (UB[2, 0], UB[2, 1], UB[2, 2]))
def _showAngles(angles=None):
if angles is None:
@@ -384,23 +369,25 @@ def _showAngles(angles=None):
table = PrettyTable(RealPosition._fields, padding=12)
print(table.get_header())
text = tuple(f'{x:9.4f}' for x in angles)
text = tuple(f"{x:9.4f}" for x in angles)
print(table.get_row(*text))
def _currentPosition():
response = diffract.real_position
# The original return value is of namedtuple type,
# which gets serialized to a dictionary by the device server.
angles = RealPosition(*(response['values'][axis] for axis in response['fields']))
angles = RealPosition(*(response["values"][axis] for axis in response["fields"]))
return angles
def _showReflections(reflections):
print('The defined reflections are:')
header = ['h', 'k', 'l'] + diffract.real_position['fields'][:-1]
print("The defined reflections are:")
header = ["h", "k", "l"] + diffract.real_position["fields"][:-1]
table = PrettyTable(header, padding=12)
print(' ', table.get_header())
print(" ", table.get_header())
for reflection in reflections:
h, k, l, angles = reflection
text = [f'{h:9.4f}', f'{k:9.4f}', f'{l:9.4f}'] + [f'{x:9.4f}' for x in angles]
print(' ', table.get_row(*text))
text = [f"{h:9.4f}", f"{k:9.4f}", f"{l:9.4f}"] + [f"{x:9.4f}" for x in angles]
print(" ", table.get_row(*text))

View File

@@ -34,7 +34,27 @@ to setup the prompts.
"""
# pylint: disable=invalid-name, unused-import, import-error, undefined-variable, unused-variable, unused-argument, no-name-in-module
import builtins
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bec_ipython_client.main import BECIPythonClient
bec: BECIPythonClient = BECIPythonClient()
dev = bec.device_manager.devices
scans = bec.scans
else:
bec = builtins.__dict__.get("bec")
dev = builtins.__dict__.get("dev")
scans = builtins.__dict__.get("scans")
bec.load_high_level_interface("bec_hli")
bec.load_high_level_interface("spec_hli")
bec.load_high_level_interface("hkl_hli")
if "x04v" in dev:
bec._ip.prompts.session_name = "x04v"
elif "x04h" in dev:
bec._ip.prompts.session_name = "x04h"

View File

@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
name = "addams_bec"
version = "0.0.0"
description = "A plugin repository for BEC"
requires-python = ">=3.10"
requires-python = ">=3.11"
classifiers = [
"Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3",