refactor(demos): own UserConfig/UserService copy for Hi.Sample

UserService moved out of the HiNc core package; Hi.Sample keeps its own copy under Sample.Common so the DemoSessionMessage #ShowStepPresent region (a live docfx code snippet referenced by two app-anatomy pages) still compiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
iamboss 2026-05-31 14:24:34 +08:00
parent 05ada6189b
commit 5ed0c3311e
3 changed files with 474 additions and 0 deletions

141
Common/PlayerDivConfig.cs Normal file
View File

@ -0,0 +1,141 @@
using Hi.Common;
using Hi.Common.XmlUtils;
using System.Xml;
using System.Xml.Linq;
namespace Sample.Common;
/// <summary>
/// Per-user visibility flags for the Player page's divisions (charts and
/// info panels), flattened into a single all-boolean config persisted through
/// <see cref="UserConfig"/>.
/// </summary>
/// <remarks>
/// Defaults are tuned for a first-time user: the two info panels
/// (<see cref="EnableStepDiv"/> and <see cref="MessageTable"/>) are ON
/// so the Player page looks populated without having to open the
/// Division Visibility dropdown; every chart toggle starts OFF.
/// </remarks>
public class PlayerDivConfig : IMakeXmlSource
{
#region XML IO
/// <summary>
/// Registers this type's deserializer with the given <see cref="XFactory"/>
/// (or <see cref="XFactory.Default"/> when <paramref name="factory"/> is
/// <c>null</c>). Idempotent.
/// </summary>
public static void Reg(XFactory factory = null)
{
factory ??= XFactory.Default;
factory.Generators.TryAdd(XName, (xml, baseDirectory, relFile, progress, res)
=> new PlayerDivConfig(xml));
}
/// <summary>
/// Name for XML IO.
/// </summary>
public static string XName => nameof(PlayerDivConfig);
/// <summary>
/// Default constructor.
/// </summary>
public PlayerDivConfig() { }
/// <summary>
/// Initializes a new instance of the <see cref="PlayerDivConfig"/> class from XML data.
/// </summary>
/// <param name="src">XML element containing player-div data.</param>
public PlayerDivConfig(XElement src)
{
if (src == null) return;
src.Element(nameof(EnableStripAvailabilityChart))?.Value?.SelfInvoke(
v => EnableStripAvailabilityChart = XmlConvert.ToBoolean(v));
src.Element(nameof(EnableStripRoughnessChart))?.Value?.SelfInvoke(
v => EnableStripRoughnessChart = XmlConvert.ToBoolean(v));
src.Element(nameof(ColorIndexTimeChart))?.Value?.SelfInvoke(
v => ColorIndexTimeChart = XmlConvert.ToBoolean(v));
src.Element(nameof(ForceCycleLineDiv))?.Value?.SelfInvoke(
v => ForceCycleLineDiv = XmlConvert.ToBoolean(v));
src.Element(nameof(SimSpindleMomentCycleLineDiv))?.Value?.SelfInvoke(
v => SimSpindleMomentCycleLineDiv = XmlConvert.ToBoolean(v));
src.Element(nameof(SensorSpindleMomentCycleLineDiv))?.Value?.SelfInvoke(
v => SensorSpindleMomentCycleLineDiv = XmlConvert.ToBoolean(v));
src.Element(nameof(DynamometerForceCycleLineDiv))?.Value?.SelfInvoke(
v => DynamometerForceCycleLineDiv = XmlConvert.ToBoolean(v));
src.Element(nameof(EnableStepDiv))?.Value?.SelfInvoke(
v => EnableStepDiv = XmlConvert.ToBoolean(v));
src.Element(nameof(MessageTable))?.Value?.SelfInvoke(
v => MessageTable = XmlConvert.ToBoolean(v));
src.Element(nameof(EnableDetailDiv0))?.Value?.SelfInvoke(
v => EnableDetailDiv0 = XmlConvert.ToBoolean(v));
}
/// <inheritdoc/>
public XElement MakeXmlSource(string baseDirectory, string relFile, bool exhibitionOnly)
{
return new XElement(XName,
new XElement(nameof(EnableStripAvailabilityChart), EnableStripAvailabilityChart),
new XElement(nameof(EnableStripRoughnessChart), EnableStripRoughnessChart),
new XElement(nameof(ColorIndexTimeChart), ColorIndexTimeChart),
new XElement(nameof(ForceCycleLineDiv), ForceCycleLineDiv),
new XElement(nameof(SimSpindleMomentCycleLineDiv), SimSpindleMomentCycleLineDiv),
new XElement(nameof(SensorSpindleMomentCycleLineDiv), SensorSpindleMomentCycleLineDiv),
new XElement(nameof(DynamometerForceCycleLineDiv), DynamometerForceCycleLineDiv),
new XElement(nameof(EnableStepDiv), EnableStepDiv),
new XElement(nameof(MessageTable), MessageTable),
new XElement(nameof(EnableDetailDiv0), EnableDetailDiv0)
);
}
#endregion
/// <summary>
/// Availability strip chart (per-step timeline coloured by availability aspect).
/// </summary>
public bool EnableStripAvailabilityChart { get; set; } = false;
/// <summary>
/// Surface-roughness strip chart (per-step timeline coloured by surface-roughness aspect).
/// </summary>
public bool EnableStripRoughnessChart { get; set; } = false;
/// <summary>
/// Color-index strip chart (single-strip timeline coloured by arbitrary numeric/bool aspect).
/// </summary>
public bool ColorIndexTimeChart { get; set; } = false;
/// <summary>
/// Cycle-line chart of cutting force along the step's rotation cycle.
/// </summary>
public bool ForceCycleLineDiv { get; set; } = false;
/// <summary>
/// Cycle-line chart of simulation-derived spindle moment on spindle-rotation coordinate.
/// </summary>
public bool SimSpindleMomentCycleLineDiv { get; set; } = false;
/// <summary>
/// Cycle-line chart of sensor-measured spindle moment (overlay on sim moment).
/// </summary>
public bool SensorSpindleMomentCycleLineDiv { get; set; } = false;
/// <summary>
/// Cycle-line chart of sensor-measured force from an external dynamometer.
/// </summary>
public bool DynamometerForceCycleLineDiv { get; set; } = false;
/// <summary>
/// Full / condensed step information panel (Selected Step Info).
/// </summary>
public bool EnableStepDiv { get; set; } = true;
/// <summary>
/// Session messages panel.
/// </summary>
public bool MessageTable { get; set; } = true;
/// <summary>
/// Developer-only raw step-property dump panel.
/// </summary>
public bool EnableDetailDiv0 { get; set; } = false;
}

173
Common/UserConfig.cs Normal file
View File

@ -0,0 +1,173 @@
using Hi.Cbtr;
using Hi.Common;
using Hi.Common.XmlUtils;
using Hi.MachiningSteps;
using Hi.NcMech.Fixtures;
using Hi.NcMech.Workpieces;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
namespace Sample.Common;
/// <summary>
/// User Configuration.
/// </summary>
/// <remarks>
/// Sample-owned copy of the per-GUI user configuration, kept so the
/// <see cref="DemoSessionMessage"/> step-present demo still compiles. Each
/// GUI/APP keeps its own version; the core engine package (HiNc) no longer
/// carries any GUI configuration type.
/// </remarks>
public class UserConfig : IMakeXmlSource
{
/// <summary>
/// Registers this type's deserializer with the given <see cref="XFactory"/>
/// (or <see cref="XFactory.Default"/> when <paramref name="factory"/> is
/// <c>null</c>). Idempotent.
/// </summary>
public static void Reg(XFactory factory = null)
{
factory ??= XFactory.Default;
factory.Generators.TryAdd(XName, (xml, baseDirectory, relFile, progress, res)
=> new UserConfig(xml, baseDirectory));
}
#region XML IO
/// <summary>
/// Name for XML IO.
/// </summary>
public static string XName => nameof(UserConfig);
/// <summary>
/// Initializes a new instance of the <see cref="UserConfig"/> class from XML data.
/// </summary>
/// <param name="src">XML element containing configuration data</param>
/// <param name="baseDirectory">Base directory for resolving relative paths</param>
public UserConfig(XElement src, string baseDirectory)
{
if (src == null) return;
src.Element(nameof(ShowPhysicsOptions))?.Value?.SelfInvoke(
v => ShowPhysicsOptions = XmlConvert.ToBoolean(v));
src.Element(nameof(LanguageCode))?.Value?.SelfInvoke(v => LanguageCode = v);
src.Element(nameof(EnableFullControl))?.Value?.SelfInvoke(
v => EnableFullControl = XmlConvert.ToBoolean(v));
src.Element(nameof(GraphicCacheLowerLimitMb))?.Value?.SelfInvoke(
v => GraphicCacheLowerLimitMb = XmlConvert.ToDouble(v));
src.Element(nameof(GraphicCacheUpperLimitMb))?.Value?.SelfInvoke(
v => GraphicCacheUpperLimitMb = XmlConvert.ToDouble(v));
src.Element(nameof(GraphicCacheMb))?.Value?.SelfInvoke(
v => GraphicCacheMb = XmlConvert.ToInt64(v));
src.Element(nameof(DisplayedStepPresentKeyList))?.SelfInvoke(ee =>
{
DisplayedStepPresentKeyList = ee.Elements("Item").Select(e => e.Value).ToList();
});
// Load FixtureSetupDisplayeeConfig
src.Element(nameof(FixtureSetupDisplayeeConfig))?.SelfInvoke(e =>
{
FixtureSetupDisplayeeConfig = new FixtureEditorDisplayeeConfig(e);
});
// Load EquipmentWorkpieceSetupDisplayeeConfig
src.Element(nameof(EquipmentWorkpieceSetupDisplayeeConfig))?.SelfInvoke(e =>
{
EquipmentWorkpieceSetupDisplayeeConfig = new WorkpieceEditorDisplayeeConfig(e);
});
// Load PlayerDivConfig
src.Element(nameof(PlayerDivConfig))?.SelfInvoke(e =>
{
PlayerDivConfig = new PlayerDivConfig(e);
});
}
/// <inheritdoc/>
public XElement MakeXmlSource(string baseDirectory, string relFile, bool exhibitionOnly)
{
XElement dst = new XElement(XName,
new XElement(nameof(ShowPhysicsOptions), ShowPhysicsOptions),
new XElement(nameof(LanguageCode), LanguageCode),
new XElement(nameof(EnableFullControl), EnableFullControl),
new XElement(nameof(GraphicCacheLowerLimitMb), GraphicCacheLowerLimitMb),
new XElement(nameof(GraphicCacheUpperLimitMb), GraphicCacheUpperLimitMb),
new XElement(nameof(GraphicCacheMb), GraphicCacheMb),
new XElement(nameof(DisplayedStepPresentKeyList),
DisplayedStepPresentKeyList.Select(v => new XElement("Item", v))),
new XElement(nameof(FixtureSetupDisplayeeConfig),
FixtureSetupDisplayeeConfig?.MakeXmlSource(baseDirectory, relFile, exhibitionOnly)),
new XElement(nameof(EquipmentWorkpieceSetupDisplayeeConfig),
EquipmentWorkpieceSetupDisplayeeConfig?.MakeXmlSource(baseDirectory, relFile, exhibitionOnly)),
PlayerDivConfig?.MakeXmlSource(baseDirectory, relFile, exhibitionOnly)
);
return dst;
}
#endregion
/// <summary>
/// Gets or sets whether to show physics options in the UI.
/// </summary>
public bool ShowPhysicsOptions { get; set; } = false;
/// <summary>
/// Gets or sets the language code for the application UI.
/// </summary>
public string LanguageCode { get; set; } = "en";
/// <summary>
/// Enable Full control of the application.
/// Eanble System.Diagnostics.Process in GUI Script Command.
/// Not used yet.
/// </summary>
public bool EnableFullControl { get; set; }
/// <summary>
/// Gets or sets the lower limit of graphic cache in megabytes.
/// </summary>
public double GraphicCacheLowerLimitMb { get; set; } = 10;
/// <summary>
/// Gets or sets the upper limit of graphic cache in megabytes.
/// </summary>
public double GraphicCacheUpperLimitMb { get; set; } = 1200;
/// <summary>
/// Gets or sets the graphic cache size in megabytes.
/// </summary>
public long GraphicCacheMb
{
get => CubeTree.DispCacheMb;
set => CubeTree.DispCacheMb = value;
}
/// <summary>
/// Step infomation key list to show.
/// </summary>
public List<string> DisplayedStepPresentKeyList { get; set; } = new List<string>([
nameof(MachiningStep.StepIndex),
nameof(MachiningStep.FileNo), nameof(MachiningStep.LineNo),
nameof(MachiningStep.FilePath),nameof(MachiningStep.AccumulatedTime),
nameof(MachiningStep.LineText),nameof(MachiningStep.FlagsText),
nameof(MachiningStep.ToolId),
nameof(MachiningStep.SpindleSpeed_rpm),nameof(MachiningStep.Feedrate_mmdmin),
nameof(MachiningStep.Mrr_mm3ds),
nameof(MachiningStep.AvgAbsTorque_Nm),
]);
/// <summary>
/// Configuration for FixtureSetupDisplayee
/// </summary>
public FixtureEditorDisplayeeConfig FixtureSetupDisplayeeConfig { get; set; } = new FixtureEditorDisplayeeConfig();
/// <summary>
/// Configuration for EquipmentWorkpieceSetupDisplayee
/// </summary>
public WorkpieceEditorDisplayeeConfig EquipmentWorkpieceSetupDisplayeeConfig { get; set; } = new WorkpieceEditorDisplayeeConfig();
/// <summary>
/// Per-user visibility flags for the Player page's divisions (charts and info panels).
/// Persisted alongside the rest of this <see cref="UserConfig"/>.
/// </summary>
public PlayerDivConfig PlayerDivConfig { get; set; } = new PlayerDivConfig();
/// <summary>
/// Default constructor
/// </summary>
public UserConfig() { }
}

160
Common/UserService.cs Normal file
View File

@ -0,0 +1,160 @@
using Hi.Common;
using Hi.Common.XmlUtils;
using Hi.Licenses;
using Hi.MachiningSteps;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Sample.Common;
/// <summary>
/// User Service.
/// </summary>
/// <remarks>
/// Sample-owned copy, kept so the <see cref="DemoSessionMessage"/> step-present
/// demo still compiles. Each GUI/APP keeps its own version; the core engine
/// package (HiNc) no longer carries this GUI service type.
/// </remarks>
public class UserService : IDisposable
{
/// <summary>
/// Gets or sets the application configuration.
/// </summary>
public UserConfig UserConfig { get; set; } = new UserConfig();
/// <summary>
/// Gets or sets the path to the application configuration file.
/// </summary>
public string UserConfigPath { get; set; }
/// <summary>
/// Gets whether physics features are enabled based on configuration and license.
/// </summary>
public bool EnablePhysics =>
UserConfig.ShowPhysicsOptions && IsPhysicsLicensed;
/// <summary>
/// Gets whether advanced physics features are licensed.
/// </summary>
public bool IsPhysicsLicensed => License.IsLoggedIn(AuthFeature.AdvancedPhysics);
//runtime properties are not in the category of canonical IO.
#region Runtime Property
private readonly ILogger _logger;
LooseRunner SaveConfigLooseRunner;
/// <summary>
/// Gets or sets the currently selected item in the application.
/// </summary>
public object SelectedItem { get; set; }
private bool disposedValue;
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="UserService"/> class.
/// </summary>
/// <param name="logger">The logger instance.</param>
public UserService(ILogger logger) { _logger = logger; SaveConfigLooseRunner = new LooseRunner(logger); }
/// <summary>
/// Initializes a new instance of the <see cref="UserService"/> class with the specified configuration.
/// </summary>
/// <param name="appConfig">The application configuration.</param>
/// <param name="logger">The logger instance.</param>
public UserService(UserConfig appConfig, ILogger logger) : this(logger) { UserConfig = appConfig; }
/// <summary>
/// Saves the user configuration to the file specified by <see cref="UserConfigPath"/>.
/// </summary>
public void SaveUserConfig()
{
try
{
if (UserConfigPath != null)
UserConfig.MakeXmlSourceToFile(UserConfigPath);
}
catch (Exception ex)
{
_logger?.LogError(ex, "{Message}", ex.Message);
}
}
/// <summary>
/// Schedules a loose save of the user configuration using a LooseRunner.
/// </summary>
public void LooseSaveUserConfig()
{
SaveConfigLooseRunner.TryRun(token => SaveUserConfig());
}
#region step present
/// <summary>
/// Gets or sets additional step presentation access configurations.
/// </summary>
public Dictionary<string, PresentAccess> AdditionalStepPresentAccess { get; set; } = new Dictionary<string, PresentAccess>();
/// <summary>
/// StepPresentAccessDictionary.
/// Read only.
/// </summary>
public Dictionary<string, PresentAccess> StepPresentAccessDictionary
{
get
{
var originalPresentAccessDictionary = typeof(MachiningStep).GetProperties()
.Where(p => p.GetCustomAttribute<PresentAttribute>() != null)
.ToDictionary(prop => prop.Name, prop =>
new PresentAccess(prop.GetCustomAttribute<PresentAttribute>(),
step => prop.GetValue(step)));
var presentAccessDictionary = new Dictionary<string, PresentAccess>(
AdditionalStepPresentAccess.Concat(originalPresentAccessDictionary));
return presentAccessDictionary;
}
}
/// <summary>
/// Candidate Step Present Key List for display.
/// Read only.
/// </summary>
public List<string> CandidateStepPresentKeyList => StepPresentAccessDictionary.Keys.ToList();
/// <summary>
/// StepPresentAccessList for display.
/// Read only.
/// </summary>
public List<KeyValuePair<string, PresentAccess>> DisplayedStepPresentAccessList
{
get
{
var displayedStepPresentKeyList = UserConfig.DisplayedStepPresentKeyList;
List<KeyValuePair<string, PresentAccess>> dst
= new List<KeyValuePair<string, PresentAccess>>(
displayedStepPresentKeyList.Count);
var stepPresentAccessDictionary = StepPresentAccessDictionary;
foreach (var key in displayedStepPresentKeyList)
{
if (stepPresentAccessDictionary.TryGetValue(
key, out var access))
{
dst.Add(new KeyValuePair<string, PresentAccess>(key, access));
}
}
return dst;
}
}
#endregion
/// <inheritdoc/>
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
SaveConfigLooseRunner.Dispose();
}
disposedValue = true;
}
}
/// <inheritdoc/>
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}