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
3 changes: 3 additions & 0 deletions .github/workflows/arstack-studio-native-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ jobs:
if (-not (Test-Path $gui)) { throw 'Fresh preview GUI executable missing' }
$exe = Join-Path $stage 'ARStackStudio.exe'
Copy-Item $gui $exe
$productIcon = Join-Path $PWD 'build-studio-preview\\ARStackStudio.ico'
if (-not (Test-Path $productIcon)) { throw 'Native Studio application icon missing' }
Copy-Item $productIcon (Join-Path $stage 'ARStackStudio.ico')
windeployqt --release --qmldir "$PWD\apps\arstack_studio\qml" --compiler-runtime $exe
if ($LASTEXITCODE -ne 0) { throw 'Qt runtime deployment failed' }
# Copy directory CONTENTS to a pre-created destination, not directory
Expand Down
18 changes: 18 additions & 0 deletions apps/arstack_studio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -171,5 +171,23 @@ target_compile_definitions(arstack_session_harness PRIVATE
)

if(WIN32)
# One deterministic icon shared by executable, installer and shortcuts.
# Python is already available on supported Windows CI hosts. The icon is
# generated at configure time so RC.exe sees its file before compilation.
find_package(Python3 COMPONENTS Interpreter REQUIRED)
set(ARSTACK_STUDIO_ICON "${CMAKE_CURRENT_BINARY_DIR}/ARStackStudio.ico")
execute_process(
COMMAND "${Python3_EXECUTABLE}"
"${CMAKE_CURRENT_LIST_DIR}/assets/make_studio_icon.py"
"${ARSTACK_STUDIO_ICON}"
RESULT_VARIABLE icon_result
)
if(NOT icon_result EQUAL 0)
message(FATAL_ERROR "Could not generate native ARStack Studio icon")
endif()
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/ARStackStudio.rc"
"IDI_ARSTACK ICON \"${ARSTACK_STUDIO_ICON}\"\n")
target_sources(arstack_studio PRIVATE
"${CMAKE_CURRENT_BINARY_DIR}/ARStackStudio.rc")
set_target_properties(arstack_studio PROPERTIES WIN32_EXECUTABLE TRUE)
endif()
67 changes: 67 additions & 0 deletions apps/arstack_studio/assets/make_studio_icon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Deterministic Windows ARStack Studio icon, built with Python stdlib only.

A single generated ICO is embedded in the PE executable and reused by Inno
Setup, Desktop and Start menu shortcuts. No image/font build dependencies.
"""
import math
import pathlib
import struct
import sys
import zlib


def chunk(tag, body):
return struct.pack(">I", len(body)) + tag + body + struct.pack(
">I", zlib.crc32(tag + body) & 0xffffffff
)


def make_png(size):
rows = bytearray()
for y in range(size):
rows.append(0)
for x in range(size):
px = (x + 0.5) / size
py = (y + 0.5) / size
inside = (0.05 <= px <= 0.95 and 0.05 <= py <= 0.95)
corner = (min(abs(px - 0.19), abs(px - 0.81)) ** 2
+ min(abs(py - 0.19), abs(py - 0.81)) ** 2) < 0.0225
alpha = 255 if inside or corner else 0
col = (12, 29, 53)
if alpha and (px < 0.075 or px > 0.925 or py < 0.075 or py > 0.925):
col = (29, 115, 193)
# Bright dual-wave symbol: PTP timing and 4I+4V SV.
for offset, color in ((-0.095, (80, 184, 255)), (0.10, (46, 221, 197))):
curve = 0.57 + offset + 0.13 * math.sin((px - 0.18) * math.pi * 3.1)
if 0.14 <= px <= 0.86 and abs(py - curve) < 0.028:
col = color
# Three timing/sampling reference dots.
if any((px - cx) ** 2 + (py - 0.23) ** 2 < 0.0022
for cx in (0.28, 0.50, 0.72)):
col = (242, 249, 255)
rows.extend((*col, alpha))
ihdr = struct.pack(">IIBBBBB", size, size, 8, 6, 0, 0, 0)
return (bytes((137, 80, 78, 71, 13, 10, 26, 10)) + chunk(b"IHDR", ihdr)
+ chunk(b"IDAT", zlib.compress(bytes(rows), 9))
+ chunk(b"IEND", b""))


def main():
output = pathlib.Path(sys.argv[1])
sizes = (16, 24, 32, 48, 64, 128)
images = [make_png(n) for n in sizes]
directory = bytearray(struct.pack("<HHH", 0, 1, len(images)))
offset = 6 + 16 * len(images)
for size, data in zip(sizes, images):
directory.extend(struct.pack("<BBBBHHII", size if size < 256 else 0,
size if size < 256 else 0, 0, 0, 1, 32,
len(data), offset))
offset += len(data)
output.parent.mkdir(parents=True, exist_ok=True)
output.write_bytes(bytes(directory) + b"".join(images))
print(f"ARStack Studio Windows icon: {output} ({output.stat().st_size} bytes)")


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions apps/arstack_studio/packaging/ARStackStudio.iss
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ UsePreviousGroup=yes
DisableDirPage=auto
DisableProgramGroupPage=auto
WizardStyle=modern
SetupIconFile={#STAGE_DIR}\\ARStackStudio.ico
UninstallDisplayIcon={app}\\ARStackStudio.exe
Compression=lzma2
SolidCompression=yes
SetupLogging=yes
Expand Down
8 changes: 4 additions & 4 deletions apps/arstack_studio/qml/ConfigurationHub.qml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Item {

ColumnLayout {
anchors.fill: parent
spacing: 12
spacing: 16

RowLayout {
Layout.fillWidth: true
Expand All @@ -75,7 +75,7 @@ Item {
font.weight: Font.DemiBold
}
Label {
text: "Firmware recovery, engineering profile, waveform and timing tools"
text: "Lab simulation controls · configure one task at a time"
color: hub.theme.muted
font.family: hub.uiFont
font.pixelSize: 11
Expand Down Expand Up @@ -121,10 +121,10 @@ Item {
border.width: 1
border.color: hub.theme.lineSoft
}
ExpertTab { text: "Injection" }
ExpertTab { text: "Signals" }
ExpertTab { text: "Firmware" }
ExpertTab { text: "Waveform" }
ExpertTab { text: "Timing" }
ExpertTab { text: "Time Sync" }
ExpertTab { text: "Device" }
}

Expand Down
71 changes: 67 additions & 4 deletions apps/arstack_studio/qml/Main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ ApplicationWindow {
visible: true
title: "ARStack Studio · SMV + PTP · v" + Qt.application.version
color: studioTheme.bg
// Dark control palette inherited by native Controls (ComboBox/CheckBox).
// Without this the platform Windows style can render black text on
// Studio's intentionally dark backgrounds.
palette.window: studioTheme.bg
palette.windowText: studioTheme.text
palette.base: studioTheme.surface2
palette.text: studioTheme.text
palette.button: studioTheme.raised
palette.buttonText: studioTheme.text
palette.highlight: studioTheme.accentSoft
palette.highlightedText: studioTheme.text

StudioTheme { id: studioTheme }

Expand Down Expand Up @@ -346,6 +357,52 @@ ApplicationWindow {
return true
}

// One-click simulation shortcuts affect ABC only, never neutral or quality.
function equalGroupMagnitudes(group, row) {
if (row < 0 || row > 2) return
var model = groupModel(group)
var value = model.get(row).magnitude
if (!validMagnitude(group, value)) return
for (var i = 0; i < 3; ++i) model.setProperty(i, "magnitude", value)
selectSignal(group, row)
if (workflowBar.session && workflowBar.session.liveControlReady && !sendLinkedGroup(group)) {
showMessage("ABC update was not accepted; inspect device diagnostics.", true)
return
}
showMessage("ABC magnitude queued from " + model.get(row).signalId, false)
}

function equalGroupAngles(group, row) {
if (row < 0 || row > 2) return
var model = groupModel(group)
var value = model.get(row).phase
if (!validPhase(value)) return
for (var i = 0; i < 3; ++i) model.setProperty(i, "phase", value)
selectSignal(group, row)
if (workflowBar.session && workflowBar.session.liveControlReady && !sendLinkedGroup(group)) {
showMessage("ABC update was not accepted; inspect device diagnostics.", true)
return
}
showMessage("ABC angles queued from " + model.get(row).signalId, false)
}

function balanceGroupAngles(group, row) {
if (row < 0 || row > 2) return
var model = groupModel(group)
var base = model.get(row).phase + (row === 1 ? 120 : row === 2 ? -120 : 0)
if (!validPhase(model.get(row).phase)) return
base = normalizedAngle(base)
model.setProperty(0, "phase", base)
model.setProperty(1, "phase", normalizedAngle(base - 120))
model.setProperty(2, "phase", normalizedAngle(base + 120))
selectSignal(group, row)
if (workflowBar.session && workflowBar.session.liveControlReady && !sendLinkedGroup(group)) {
showMessage("ABC update was not accepted; inspect device diagnostics.", true)
return
}
showMessage("Balanced ABC angles queued from " + model.get(row).signalId, false)
}

function balanced() {
var angles = [0, -120, 120, 0]
for (var i = 0; i < 4; ++i) {
Expand Down Expand Up @@ -472,13 +529,19 @@ ApplicationWindow {

Window {
id: configurationWindow
width: 760
height: 780
minimumWidth: 640
minimumHeight: 660
width: 910
height: 740
minimumWidth: 760
minimumHeight: 610
visible: false
title: "ARStack Studio · Advanced"
color: studioTheme.bg
palette.window: studioTheme.bg
palette.windowText: studioTheme.text
palette.base: studioTheme.surface2
palette.text: studioTheme.text
palette.button: studioTheme.raised
palette.buttonText: studioTheme.text

onClosing: function(close) {
close.accepted = false
Expand Down
Loading
Loading