Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mcstas-comps/contrib/SpinWave_BCO.comp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
* V_my_s: [m^-1] Attenuation factor due to incoherent scattering
* V_my_a_v: [m^-1] Attenuation factor due to absorbtion
*
* %E
******************************************************************************/

DEFINE COMPONENT SpinWave_BCO
Expand Down
1 change: 1 addition & 0 deletions mcstas-comps/misc/Cone.comp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Origin: ILL
* Modified by: E. Farhi, based on Incoherent
*
* %D
* An inactive geometrical shape (cone), for drawing purposes only.
* Derived from the <b><a href="Shape.html">Shape</a></b> component
*
Expand Down
1 change: 1 addition & 0 deletions mcstas-comps/union/Union_NCrystal_material.comp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
* interact_fraction: [1] Fraction of scattering events assigned to this process 0-1 (default -1 lets Union assign it).
* include_SLD: [0/1] Include the scattering length density calculated from cfg in the material refraction information.
* init: [string] Deprecated and unused. Accepted so that instruments written for McStas/McXtrace 3.8.7 and earlier, which name the Union_init component here, still compile.
* %E
*******************************************************************************/

/******************************************************************************/
Expand Down
1 change: 1 addition & 0 deletions mcxtrace-comps/misc/Cone.comp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Origin: ILL
* Modified by: E. Farhi, based on Incoherent
*
* %D
* An inactive geometrical shape (cone), for drawing purposes only.
* Derived from the <b><a href="Shape.html">Shape</a></b> component
*
Expand Down
3 changes: 2 additions & 1 deletion tools/Python/mccodelib/pqtgfrontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,8 @@ def click_handler(event):
posScene = event.scenePos()
plotIdx = self.get_plot_index(posScene)
posItem = event.pos()
except AttributeError:
except (AttributeError, RuntimeError):
# RuntimeError: the clicked item was deleted by a replot before this click was delivered
return

if 0 <= plotIdx < len(node_list):
Expand Down
7 changes: 6 additions & 1 deletion tools/Python/mccodelib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1037,7 +1037,12 @@ def dumpfile_pqtg(scene, filenamebase='mcplot', format='png'):
exporter.export(outputfile)
elif format=='svg':
exporter = pg.exporters.SVGExporter(scene)
exporter.export(outputfile)
try:
exporter.export(outputfile)
except ValueError as e:
# pyqtgraph 0.14.0 cannot parse the SVG path syntax written by Qt 6.11
print('SVG export failed in pyqtgraph (%s), use PNG export instead' % e)
return
# This is where PDF export would be handled via conversion from SVG
# elif format=='pdf':
# exporter = pg.exporters.SVGExporter(scene)
Expand Down
4 changes: 2 additions & 2 deletions tools/Python/mcdisplay/matplotlib/mcdisplay.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ def dumpfile(frmat, filename = None):
# Check for existance of earlier exports
if os.path.isfile(saveto):
index=1
saveto = f"{basename}_{index}{frmat}"
saveto = f"{basename}_{index}{ext}"
while os.path.isfile(saveto):
index += 1
saveto = f"{basename}_{index}{frmat}"
saveto = f"{basename}_{index}{ext}"

savefig(saveto)
print("Saved " + saveto)
Expand Down
79 changes: 51 additions & 28 deletions tools/Python/mcgui/mcgui.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ def canPlot(self):

__thread_exc_signal = QtCore.Signal(str)
def compile(self, mpi=True):
# replacing a running QThread aborts the application
if getattr(self, 'compilethread', None) is not None and self.compilethread.isRunning():
self.__emitter.status('Compile already in progress')
return
# using Qt in-built cross-thread signaling
self.__thread_exc_signal.connect(handleExceptionMsg)

Expand Down Expand Up @@ -616,7 +620,7 @@ def initDynamicView(self):
def handleRunOrInterruptSim(self):
if self.state.isSimRunning():
self.state.interrupt()
else:
elif self.state.canRun():
# ensure auto-save
self.view.ew.save()

Expand All @@ -639,6 +643,7 @@ def msg(msg, em=self.emitter):
em.message(msg)
handler = ThreadInfoHandler(lambda msg, em=self.emitter: em.message(msg))

instr = self.state.getInstrumentFile()
somethread = McRunQThread()
somethread.cmd = mccode_config.configuration["MCRUN"] + ' ' + os.path.basename(self.state.getInstrumentFile()) + " --info"
somethread.cwd = os.path.dirname(self.state.getInstrumentFile())
Expand All @@ -652,6 +657,11 @@ def msg(msg, em=self.emitter):
time.sleep(0.2)
QtWidgets.QApplication.processEvents()

# The GUI stays live during --info; the user may have closed or switched instrument
if self.state.getInstrumentFile() != instr:
self.emitter.status("Instrument changed while getting parameters, run cancelled")
return

params = []
for l in handler.stdout_lst:
if 'Param:' in l:
Expand Down Expand Up @@ -891,11 +901,12 @@ def displayNotSavedWhitespaceError(self, can_throw_func, raise_err=False):

def handleSaveAs(self):
oldinstr = self.state.getInstrumentFile()
if oldinstr != '':
newinstr = self.view.showSaveAsDialog(oldinstr)
if self.displayNotSavedWhitespaceError(lambda: self.state.checkInstrFileCandidate(newinstr))==False:
return

if oldinstr == '':
return
newinstr = self.view.showSaveAsDialog(oldinstr)
if self.displayNotSavedWhitespaceError(lambda: self.state.checkInstrFileCandidate(newinstr))==False:
return

if newinstr != '':
self.state.unloadInstrument()
text = get_file_contents(oldinstr)
Expand Down Expand Up @@ -930,29 +941,39 @@ def handleNewFromTemplate(self, instr_templ=''):
wd = pathlib.PurePath(self.state.getWorkDir())
if wd.stem==instrbase.stem:
self.state.setWorkDir("..")
new_instr_req = self.view.showNewInstrFromTemplateDialog(os.path.join(self.state.getWorkDir(), os.path.basename(os.path.dirname(str(instr_templ)))))
if self.displayNotSavedWhitespaceError(lambda: self.state.checkInstrFileCandidate(new_instr_req))==False:
# Templates are copied including their containing folder, into a user-selected parent folder
srcdir = pathlib.Path(str(instr_templ)).parent
parent = self.view.showNewInstrFromTemplateDialog(self.state.getWorkDir())
if not parent:
return
parent = pathlib.Path(parent)
# User may have selected the template-named folder itself
trgdir = parent if parent.name == srcdir.name else parent / srcdir.name
new_instr = str(trgdir / pathlib.Path(str(instr_templ)).name)
if self.displayNotSavedWhitespaceError(lambda: self.state.checkInstrFileCandidate(new_instr))==False:
return

if new_instr_req != '':
if self.view.closeCodeEditorWindow():
text = get_file_contents(instr_templ)
self.state.unloadInstrument()
# Templates are now copied including their containing folder
srcdir = os.path.dirname(str(instr_templ))
trgdir = pathlib.Path(new_instr_req)
# Case 1: destination folder already exists, "merge":
if pathlib.Path.exists(trgdir):
files=os.listdir(srcdir)
for fname in files:
shutil.copy2(os.path.join(srcdir,fname), trgdir)
else:
# Case 2: create a new folder:
shutil.copytree(srcdir,trgdir)

new_instr = os.path.join(trgdir, os.path.basename(str(instr_templ)))
self.state.loadInstrument(new_instr)
self.emitter.status("Instrument created: " + os.path.basename(str(new_instr)))
choice = 'copy'
if trgdir.exists():
n = 2
while (trgdir.parent / (srcdir.name + '_' + str(n))).exists():
n += 1
newdir = trgdir.parent / (srcdir.name + '_' + str(n))
choice = self.view.askTemplateDirExists(trgdir, newdir)
if choice is None:
return
if choice == 'new':
trgdir = newdir
new_instr = str(trgdir / pathlib.Path(str(instr_templ)).name)

if self.view.closeCodeEditorWindow():
self.state.unloadInstrument()
if choice == 'replace':
shutil.copytree(srcdir, trgdir, dirs_exist_ok=True)
elif choice != 'open':
shutil.copytree(srcdir, trgdir)
self.state.loadInstrument(new_instr)
self.emitter.status("Instrument created: " + os.path.basename(new_instr))

def handleOpenInstrument(self):
instr = self.view.showOpenInstrumentDlg(self.state.getWorkDir())
Expand Down Expand Up @@ -1002,7 +1023,7 @@ def handleEnvironment(self):
def handleDefault(self):
reply = QtWidgets.QMessageBox.question(self.view.mw,
'Define system default?',
'Do you want to make the current ' + mccode_config.configuration["MCCODE"] + ' the system default?'),
'Do you want to make the current ' + mccode_config.configuration["MCCODE"] + ' the system default?')

if reply == QtWidgets.QMessageBox.Yes:
subprocess.Popen(mccode_config.configuration["MCCODE"] +'-postinst set_mccode_default', shell=True)
Expand Down Expand Up @@ -1090,6 +1111,8 @@ def main():
mccode_config.check_env_vars()

mcguiApp = QtWidgets.QApplication(sys.argv)
# Window/taskbar icon, and the Dock icon on macOS. ponytail: 115px logo, ship a 256px+ PNG if it looks soft
mcguiApp.setWindowIcon(QtGui.QIcon(os.path.join(os.path.dirname(os.path.abspath(__file__)), mccode_config.configuration["MCCODE"] + '-py.png')))
mcguiApp.ctr = McGuiAppController()
font = QFont()
font.setFixedPitch(True)
Expand Down
42 changes: 35 additions & 7 deletions tools/Python/mcgui/viewclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,19 @@ def showNewInstrDialog(self, lookdir):
dlg.setNameFilter(mccode_config.configuration["MCCODE"]+" instruments (*.instr)");
return dlg.getSaveFileName(parent=None, caption='Create Instrument file...')[0]

def showNewInstrFromTemplateDialog(self, instr):
dlg = QtWidgets.QFileDialog()
return dlg.getSaveFileName(parent=None, caption='Create Instrument file from Template...', directory=instr)[0]
def showNewInstrFromTemplateDialog(self, workdir):
return QtWidgets.QFileDialog.getExistingDirectory(self.mw, 'Copy template into folder...', workdir)

def askTemplateDirExists(self, trgdir, newdir):
box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Question, 'Folder exists',
str(trgdir) + ' already exists.', parent=self.mw)
open_btn = box.addButton('Open existing', QtWidgets.QMessageBox.AcceptRole)
new_btn = box.addButton('New copy (' + newdir.name + ')', QtWidgets.QMessageBox.AcceptRole)
repl_btn = box.addButton('Replace with template', QtWidgets.QMessageBox.DestructiveRole)
box.addButton(QtWidgets.QMessageBox.Cancel)
box.setDefaultButton(new_btn)
box.exec()
return {open_btn: 'open', new_btn: 'new', repl_btn: 'replace'}.get(box.clickedButton())

def showSaveAsDialog(self, instr):
dlg = QtWidgets.QFileDialog()
Expand Down Expand Up @@ -458,7 +468,11 @@ def closeEvent(self, event):

def __handleComponentClicked(self, comp_parser):
dlg = McInsertComponentDialog()
dlg.initComponentData(comp_parser)
try:
dlg.initComponentData(comp_parser)
except Exception as e:
QtWidgets.QMessageBox.critical(self, 'Could not read component', str(comp_parser.file) + ':\n' + str(e))
return
if dlg.exec():
comp_type, inst_name, params, atrel = dlg.getValues()
else:
Expand Down Expand Up @@ -662,6 +676,17 @@ def __init__(self, parent=None):
self._set_inspect_visible(False)
self.ui.cbxSimTrace.currentIndexChanged.connect(lambda i: self._set_inspect_visible(i))

def accept(self):
try:
float(self.ui.edtNeutronCnt.text())
if self.ui.edtRandomSeed.text():
int(self.ui.edtRandomSeed.text())
except ValueError:
QtWidgets.QMessageBox.warning(self, 'Invalid value',
'The ray count must be a number (e.g. 1e6) and the random seed a whole number.')
return
super(McStartSimDialog, self).accept()

def _setupAdvancedCollapse(self):
''' replace the static "Advanced" group box with a roll-up section:
a QToolButton with arrow toggles gbxAdvanced visibility '''
Expand Down Expand Up @@ -847,7 +872,7 @@ def getValues(self):
inspect = None
idx = self.ui.cbxInspect.currentIndex()
if idx > 0:
inspect = self._last_inspect_compnames[idx]
inspect = self._last_inspect_compnames[idx-1]

mcdisplay = None
idx = self.ui.cbxMcdisplays.currentIndex()
Expand Down Expand Up @@ -1298,9 +1323,12 @@ def __pullValuesTo_mccode_config(self):
mccode_config.compilation[str(self.ui.edtMpicc.conf_var)] = str(self.ui.edtMpicc.text())
mccode_config.compilation[str(self.ui.edtMPIrun.conf_var)] = str(self.ui.edtMPIrun.text())
mccode_config.compilation[str(self.ui.edtNumNodes.conf_var)] = str(self.ui.edtNumNodes.text())
mccode_config.configuration[str(self.ui.edtNumCols.conf_var)] = str(self.ui.edtNumCols.text())
# GUICOLS and GUIFONTSIZE are parsed with int(): keep the old value unless a positive integer is given
if self.ui.edtNumCols.text().isdigit() and int(self.ui.edtNumCols.text()) > 0:
mccode_config.configuration[str(self.ui.edtNumCols.conf_var)] = str(self.ui.edtNumCols.text())
mccode_config.configuration[str(self.ui.edtNDbufsiz.conf_var)] = str(self.ui.edtNDbufsiz.text())
mccode_config.configuration[str(self.ui.edtFontSize.conf_var)] = str(self.ui.edtFontSize.text())
if self.ui.edtFontSize.text().isdigit() and int(self.ui.edtFontSize.text()) > 0:
mccode_config.configuration[str(self.ui.edtFontSize.conf_var)] = str(self.ui.edtFontSize.text())
mccode_config.configuration[str(self.ui.editor.conf_var)] = str(self.ui.editor.text())
# Export selected variables to the system / mcrun
target_mccode=mccode_config.configuration["MCCODE"].upper()
Expand Down
7 changes: 4 additions & 3 deletions tools/Python/mcplot/matplotlib/plotfuncs.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,18 +492,19 @@ def dumpfile(frmat, filename = None):
# Check for existance of earlier exports
if os.path.isfile(saveto):
index=1
saveto = f"{basename}_{index}{frmat}"
saveto = f"{basename}_{index}{ext}"
while os.path.isfile(saveto):
index += 1
saveto = f"{basename}_{index}{frmat}"
saveto = f"{basename}_{index}{ext}"

savefig(saveto)
print("Saved " + saveto)


def click(event, subplts, click_cbs, ctrl_cbs, back_cb, dc_cb):
subplt = event.inaxes
if not subplt:
# also ignore axes that are not panels, e.g. a 2D plot's colorbar
if not subplt or subplt not in subplts:
return False
else:
# Don't treat a click that landed on the panel's own legend as a
Expand Down
Loading