From de2e831a7048f2d3fceac3800b85ecf7d808669a Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Wed, 19 Apr 2023 12:05:22 +0200 Subject: [PATCH] Update gestione DTO x serializzazione --- .../Controllers/QueueController.cs | 2 +- WebDoorCreator.API/Program.cs | 2 +- WebDoorCreator.Data/DDF/Converter.cs | 87 ++++++ .../Services/QueueDataService.cs | 258 +++++++----------- .../Buttons/ButtonsDoorDef.razor.cs | 47 +++- .../DoorDef/DoorDefSizingSI.razor.cs | 7 +- .../DoorDef/DoorSizingStep.razor.cs | 2 +- .../Hardware/HwSingleInstance.razor.cs | 8 +- .../Pages/DoorDefinition.razor.cs | 2 +- WebDoorCreator.UI/Program.cs | 5 +- WebDoorCreator.UI/temp/Conf.yaml | 25 +- 11 files changed, 249 insertions(+), 196 deletions(-) create mode 100644 WebDoorCreator.Data/DDF/Converter.cs diff --git a/WebDoorCreator.API/Controllers/QueueController.cs b/WebDoorCreator.API/Controllers/QueueController.cs index 65c1317..1dab00d 100644 --- a/WebDoorCreator.API/Controllers/QueueController.cs +++ b/WebDoorCreator.API/Controllers/QueueController.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; using NLog; -using WebDoorCreator.API.Data; +using WebDoorCreator.Data.Services; namespace WebDoorCreator.API.Controllers { diff --git a/WebDoorCreator.API/Program.cs b/WebDoorCreator.API/Program.cs index e81b02e..ece0173 100644 --- a/WebDoorCreator.API/Program.cs +++ b/WebDoorCreator.API/Program.cs @@ -5,8 +5,8 @@ using StackExchange.Redis.Extensions.Core.Configuration; using StackExchange.Redis.Extensions.Newtonsoft; using System.Globalization; using System.Text.Json.Serialization; -using WebDoorCreator.API.Data; using WebDoorCreator.Data; +using WebDoorCreator.Data.Services; var builder = WebApplication.CreateBuilder(args); diff --git a/WebDoorCreator.Data/DDF/Converter.cs b/WebDoorCreator.Data/DDF/Converter.cs new file mode 100644 index 0000000..5f93532 --- /dev/null +++ b/WebDoorCreator.Data/DDF/Converter.cs @@ -0,0 +1,87 @@ +using Newtonsoft.Json; +using WebDoorCreator.Data.DbModels; +using WebDoorCreator.Data.DTO; + +namespace WebDoorCreator.Data.DDF +{ + public class Converter + { + #region Public Constructors + + /// + /// Init del convertitore secondo impostazioni standard + /// + /// + /// + /// + /// + public Converter(string vers, bool remDoorOp, List headRows, List footRows) + { + FormatVers = vers; + RemDoorOp = remDoorOp; + HeadRows = headRows; + FootRows = footRows; + } + + #endregion Public Constructors + + #region Public Methods + + /// + /// Fornisce il DDF corrispondente all'elenco OP inviato, seocndo la conf del convertitore + /// + /// + /// + public string GetSerialized(List listOp) + { + string outDdf = ""; + try + { + DoorOpsDTO CurrentDoorOp = new DoorOpsDTO(); + DDFDto CurrentConf = new DDFDto(); + // per prima cosa popolo gli oggetti di appoggio... + dictDoorOP = new Dictionary>>(); + foreach (var item in listOp) + { + var deserialized = JsonConvert.DeserializeObject>(item.JsoncActVal!); + if (deserialized != null) + { + if (dictDoorOP.ContainsKey(item.ObjectId)) + { + var actList = dictDoorOP[item.ObjectId]; + actList.Add(deserialized.Where(x => x.Key != "Folder").ToDictionary(x => x.Key, x => x.Value)); + dictDoorOP[item.ObjectId] = actList; + } + else + { + var currList = new List>(); + currList.Add(deserialized.Where(x => x.Key != "Folder").ToDictionary(x => x.Key, x => x.Value)); + dictDoorOP.Add(item.ObjectId, currList); + } + } + } + CurrentDoorOp.doorOpsDict = dictDoorOP; + string doorOpSer = CurrentDoorOp.GetSerialized(RemDoorOp); + // ora popolo l'oggetto DTO principale + CurrentConf.version = FormatVers; + outDdf = CurrentConf.GetSerialized(HeadRows, FootRows, doorOpSer); + } + catch + { } + return outDdf; + } + + #endregion Public Methods + + #region Protected Properties + + protected Dictionary>> dictDoorOP { get; set; } = new Dictionary>>(); + protected List FootRows { get; set; } = new List(); + protected string FormatVers { get; set; } = "0"; + protected List HeadRows { get; set; } = new List(); + protected List> listDictDoorOP { get; set; } = new List>(); + protected bool RemDoorOp { get; set; } = false; + + #endregion Protected Properties + } +} \ No newline at end of file diff --git a/WebDoorCreator.Data/Services/QueueDataService.cs b/WebDoorCreator.Data/Services/QueueDataService.cs index 7d02282..e5b7e75 100644 --- a/WebDoorCreator.Data/Services/QueueDataService.cs +++ b/WebDoorCreator.Data/Services/QueueDataService.cs @@ -6,23 +6,11 @@ using StackExchange.Redis; using System.Diagnostics; using WebDoorCreator.Core; using WebDoorCreator.Data; -using WebDoorCreator.Data.DbModels; -namespace WebDoorCreator.API.Data +namespace WebDoorCreator.Data.Services { public class QueueDataService : IDisposable { - #region Public Fields - -#if false - /// - /// Classe Accesso metodi DB - /// - public static WebDoorCreator.Data.Controllers.WebDoorCreatorController dbController = null!; -#endif - - #endregion Public Fields - #region Public Constructors /// @@ -45,133 +33,89 @@ namespace WebDoorCreator.API.Data else { this.redisConn = ConnectionMultiplexer.Connect(redConnString); - this.redisDb = this.redisConn.GetDatabase(); + redisDb = this.redisConn.GetDatabase(); } // setup canali pub/sub calcReqPipe = new MessagePipe(redisConn, Constants.CALC_REQ_QUEUE); calcDonePipe = new MessagePipe(redisConn, Constants.CALC_DONE_QUEUE); -#if false - // Conf DB - var connStrDB = _configuration.GetConnectionString("WDC.DB"); - if (string.IsNullOrEmpty(connStrDB)) - { - Log.Error("ConnString empty!"); - } - else - { - dbController = new WebDoorCreator.Data.Controllers.WebDoorCreatorController(configuration); - Log.Info("DbController OK"); - } -#endif } #endregion Public Constructors + #region Public Properties /// - /// Message pipe esecuzione elaborazione CAM --> UI + /// Message pipe esecuzione elaborazione CAM --> UI /// public MessagePipe calcDonePipe { get; set; } = null!; /// - /// Message pipe richieste elaborazione UI --> CAM + /// Message pipe richieste elaborazione UI --> CAM /// public MessagePipe calcReqPipe { get; set; } = null!; + #endregion Public Properties + #region Public Methods public void Dispose() { redisConn.Dispose(); -#if false - // Clear database controller - dbController.Dispose(); -#endif } -#if false /// - /// Doors list by orderId + /// /// - public async Task?> DoorGetByOrderId(int orderId) + /// + /// Contenuto completo del DDF + /// indice/versione del calcolo DDF + public async Task sendCalcReq(int DoorId, string FullDDF) { - string source = "DB"; - List? dbResult = new List(); - // cerco da cache - string currKey = $"{Constants.rKeyOrderDetail}:{orderId}"; + int currVers = 1; Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); - string? rawData = await redisDb.StringGetAsync(currKey); - if (!string.IsNullOrEmpty(rawData)) - { - source = "REDIS"; - var tempResult = JsonConvert.DeserializeObject>(rawData); - if (tempResult == null) - { - dbResult = new List(); - } - else - { - dbResult = tempResult; - } - } - else - { - dbResult = dbController.DoorsGetByOrderId(orderId); - rawData = JsonConvert.SerializeObject(dbResult, JSSettings); - await redisDb.StringSetAsync(currKey, rawData, UltraLongCache); - } - if (dbResult == null) - { - dbResult = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"DoorGetByOrderId | {source} in: {ts.TotalMilliseconds} ms"); - return dbResult; - } -#endif - public async Task FlushRedisCache() - { - await Task.Delay(1); - RedisValue pattern = new RedisValue($"{Constants.BASE_HASH}:Cache*"); - bool answ = await ExecFlushRedisPattern(pattern); - return answ; - } - /// - /// Esegue flush memoria redis dato pattern - /// - /// - /// - private async Task ExecFlushRedisPattern(RedisValue pattern) - { - bool answ = false; - var listEndpoints = redisConn.GetEndPoints(); - foreach (var endPoint in listEndpoints) - { - //var server = redisConnAdmin.GetServer(listEndpoints[0]); - var server = redisConn.GetServer(endPoint); - if (server != null) - { - var keyList = server.Keys(redisDb.Database, pattern); - foreach (var item in keyList) - { - await redisDb.KeyDeleteAsync(item); - } - // brutalmente rimuovo intero contenuto DB... DANGER - //await server.FlushDatabaseAsync(); - answ = true; - } - } - return answ; - } - public async Task sendCalcReq(string message) - { // accumulo richieste di calcolo in HashTable redis - // to do + // per prima cosa controllo se ho GIA' in coda qualcosa come richeiste + var numPending = await NumRequestPending(); + // se coda vuota --> aggiungo e stop + if (numPending == 0) + { + } + // se coda non vuota: cerco la DoorId corrente, se c'è sovrascrivo versione altrimenti + // inserisco in coda + else + { + } + // salvo nell'archivio REDIS delle porte il DDF corrente (door+ vers numb), potrebbe + // venire buono anche x eventuale UNDO... + + // invio sul canale dei messaggi il numero di items in coda attuali x chiedere esecuzione... + +#if false + // cerco da cache + RedisKey currKey = new RedisKey(Constants.CALC_REQ_PEND); + currVers = redisDb.HashLength(currKey); + if (currVers > 0) + { + var rawData = await redisDb.HashGetAllAsync(currKey); + foreach (var item in rawData) + { + dictResult.Add($"{item.Name}", $"{item.Value}"); + } + } +#endif + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"EnqueueDoorDDF | DoorId: {DoorId} enqueued in: {ts.TotalMilliseconds} ms"); + + + + // to do + // calcolo quanti emssaggi ho in coda + string message = "999"; // invio sul channel redis la richiesta, salvando il valore anche in cache bool answ = calcReqPipe.saveAndSendMessage(Constants.LAST_CALC_REQ_KEY, message); @@ -189,10 +133,17 @@ namespace WebDoorCreator.API.Data } calcDonePipe.saveAndSendMessage(Constants.LAST_CALC_DONE_KEY, retMess); - return answ; + return currVers; } - protected int idxSim = 0; + + public async Task FlushRedisCache() + { + await Task.Delay(1); + RedisValue pattern = new RedisValue($"{Constants.BASE_HASH}:Cache*"); + bool answ = await ExecFlushRedisPattern(pattern); + return answ; + } /// /// Get # of calculation request pending @@ -287,7 +238,6 @@ namespace WebDoorCreator.API.Data Log.Debug($"RequestProcessing | {source} in: {ts.TotalMilliseconds} ms"); return dictResult; } - /// /// Get Queue request pending, removing from queue and putting on processing queue /// @@ -316,61 +266,14 @@ namespace WebDoorCreator.API.Data return dictResult; } - /// - /// Enqueue DDF processing req from UI - /// - /// - /// Contenuto completo del DDF - /// Version / iteration on DDF processing request - public async Task EnqueueDoorDDF(int DoorId, string FullDDF) - { - // ragiono sulla tab di PENDING REQ - string source = "REDIS"; - int currVers = 0; - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - - // per prima cosa controllo se ho GIA' in coda qualcosa come richeiste - var numPending = await NumRequestPending(); - // se coda vuota --> aggiungo e stop - if (numPending == 0) - { - } - // se coda non vuota: cerco la DoorId corrente, se c'è sovrascrivo versione altrimenti inserisco in coda - else - { - - } - // salvo nell'archivio REDIS delle porte il DDF corrente (door+ vers numb), potrebbe venire buono anche x eventuale UNDO... - - // invio sul canale dei messaggi il numero di items in coda attuali x chiedere esecuzione... - - -#if false - // cerco da cache - RedisKey currKey = new RedisKey(Constants.CALC_REQ_PEND); - currVers = redisDb.HashLength(currKey); - if (currVers > 0) - { - var rawData = await redisDb.HashGetAllAsync(currKey); - foreach (var item in rawData) - { - dictResult.Add($"{item.Name}", $"{item.Value}"); - } - } -#endif - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"EnqueueDoorDDF | DoorId: {DoorId} enqueued in: {ts.TotalMilliseconds} ms"); - return currVers; - } - #endregion Public Methods #region Protected Fields protected const string rKeyCalcOreFase = "Check:OreFasi"; + protected const string rKeyFasiAct = "Check:FasiAct"; + protected const string rKeyProjAct = "Check:ProjAct"; /// @@ -378,6 +281,8 @@ namespace WebDoorCreator.API.Data /// protected const int shortTTL = 60 * 5; + protected int idxSim = 0; + protected Random rnd = new Random(); #endregion Protected Fields @@ -447,7 +352,9 @@ namespace WebDoorCreator.API.Data #region Private Fields private static IConfiguration _configuration = null!; + private static JsonSerializerSettings? JSSettings; + private static Logger Log = LogManager.GetCurrentClassLogger(); private readonly IEmailSender _emailSender; @@ -506,5 +413,38 @@ namespace WebDoorCreator.API.Data } #endregion Private Properties + + #region Private Methods + + /// + /// Esegue flush memoria redis dato pattern + /// + /// + /// + private async Task ExecFlushRedisPattern(RedisValue pattern) + { + bool answ = false; + var listEndpoints = redisConn.GetEndPoints(); + foreach (var endPoint in listEndpoints) + { + //var server = redisConnAdmin.GetServer(listEndpoints[0]); + var server = redisConn.GetServer(endPoint); + if (server != null) + { + var keyList = server.Keys(redisDb.Database, pattern); + foreach (var item in keyList) + { + await redisDb.KeyDeleteAsync(item); + } + // brutalmente rimuovo intero contenuto DB... DANGER + //await server.FlushDatabaseAsync(); + answ = true; + } + } + + return answ; + } + + #endregion Private Methods } } \ No newline at end of file diff --git a/WebDoorCreator.UI/Components/Buttons/ButtonsDoorDef.razor.cs b/WebDoorCreator.UI/Components/Buttons/ButtonsDoorDef.razor.cs index d3142aa..c4d1fea 100644 --- a/WebDoorCreator.UI/Components/Buttons/ButtonsDoorDef.razor.cs +++ b/WebDoorCreator.UI/Components/Buttons/ButtonsDoorDef.razor.cs @@ -1,9 +1,10 @@ using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using Newtonsoft.Json; -using WebDoorCreator.API.Data; +using System.Configuration; using WebDoorCreator.Data.DbModels; using WebDoorCreator.Data.DTO; +using WebDoorCreator.Data.Services; using WebDoorCreator.UI.Data; namespace WebDoorCreator.UI.Components.Buttons @@ -58,12 +59,11 @@ namespace WebDoorCreator.UI.Components.Buttons } protected DDFDto CurrentConf { get; set; } = new DDFDto(); protected DoorOpsDTO CurrentDoorOp { get; set; } = new DoorOpsDTO(); - public async Task SaveYaml() + public async Task SaveYaml(string ddfContent) { await Task.Delay(1); - //string fileName = Path.Combine(baseDir, $"Conf.yml"); string fileName = Path.Combine("temp", $"Conf.yaml"); - +#if false string vers = config.GetValue("ConfDDF:VersNumber"); bool remDoorOp = config.GetValue("ConfDDF:RemoveDoorOps"); var headRows = config.GetSection("ConfDDF:Header").Get>(); @@ -72,29 +72,43 @@ namespace WebDoorCreator.UI.Components.Buttons string doorOpSer = CurrentDoorOp.GetSerialized(remDoorOp); CurrentConf.version = vers; - string fullData = CurrentConf.GetSerialized(headRows, footRows, doorOpSer); + string fullData = CurrentConf.GetSerialized(headRows, footRows, doorOpSer); +#endif if (File.Exists(fileName)) { File.Delete(fileName); } // scrivo! - File.WriteAllText(fileName, fullData); - - //CurrentConf.SaveYaml(fileName); + File.WriteAllText(fileName, ddfContent); } + + protected override void OnInitialized() + { + base.OnInitialized(); + string vers = config.GetValue("ConfDDF:VersNumber"); + bool remDoorOp = config.GetValue("ConfDDF:RemoveDoorOps"); + var headRows = config.GetSection("ConfDDF:Header").Get>(); + var footRows = config.GetSection("ConfDDF:Footer").Get>(); + currDdfConv = new WebDoorCreator.Data.DDF.Converter(vers, remDoorOp, headRows, footRows); + } + /// /// Effettua salvataggio record e generazione DDF /// /// protected async Task doSave() { - var listOpAll = await WDCService.DoorOpGetByDoorId(DoorId); + List? listOpAll = await WDCService.DoorOpGetByDoorId(DoorId); if (listOpAll != null) { - var listOp = listOpAll.Where(x => x.userConfirm != "" && x.DtConfirm != null).ToList(); + List listOp = listOpAll.Where(x => x.userConfirm != "" && x.DtConfirm != null).ToList(); if (listOp != null) { + // chiamo metodo x avewre DDF serializzato + string currDdf = currDdfConv.GetSerialized(listOp); + +#if false dictDoorOP = new Dictionary>>(); listDoorOp = listOp; foreach (var item in listDoorOp) @@ -123,13 +137,16 @@ namespace WebDoorCreator.UI.Components.Buttons //} } } + CurrentDoorOp.doorOpsDict = dictDoorOP; +#endif + // si potrebbe eliminare in futuro che va su REDIS + await SaveYaml(currDdf); + // versione corrente del DDF generato + int currVers = await QDataServ.sendCalcReq(DoorId, currDdf); } - CurrentDoorOp.doorOpsDict = dictDoorOP; - - await SaveYaml(); } - - await QDataServ.sendCalcReq($"ORD{idOrd:00000}"); } + + protected WebDoorCreator.Data.DDF.Converter currDdfConv { get; set; } = null!; } } \ No newline at end of file diff --git a/WebDoorCreator.UI/Components/DoorDef/DoorDefSizingSI.razor.cs b/WebDoorCreator.UI/Components/DoorDef/DoorDefSizingSI.razor.cs index 04a0a03..0b4e509 100644 --- a/WebDoorCreator.UI/Components/DoorDef/DoorDefSizingSI.razor.cs +++ b/WebDoorCreator.UI/Components/DoorDef/DoorDefSizingSI.razor.cs @@ -1,8 +1,8 @@ using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using Newtonsoft.Json; -using WebDoorCreator.API.Data; using WebDoorCreator.Data.DbModels; +using WebDoorCreator.Data.Services; using WebDoorCreator.UI.Data; namespace WebDoorCreator.UI.Components.DoorDef @@ -170,7 +170,10 @@ namespace WebDoorCreator.UI.Components.DoorDef DoorOpInst.JsoncActVal = serVal; // salvo! await WDCService.DoorOpUpdate(DoorOpInst); - await QDataServ.sendCalcReq($"ORD{DoorOpInst.DoorId:00000}"); + // invio msg dato "salvabile" +#if false + await QDataServ.sendCalcReq($"ORD{DoorOpInst.DoorId:00000}"); +#endif // rileggo await FullReloadData(true); } diff --git a/WebDoorCreator.UI/Components/DoorDef/DoorSizingStep.razor.cs b/WebDoorCreator.UI/Components/DoorDef/DoorSizingStep.razor.cs index 8c96bb1..cfeafe9 100644 --- a/WebDoorCreator.UI/Components/DoorDef/DoorSizingStep.razor.cs +++ b/WebDoorCreator.UI/Components/DoorDef/DoorSizingStep.razor.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Components; using Newtonsoft.Json; -using WebDoorCreator.API.Data; using WebDoorCreator.Data.DbModels; +using WebDoorCreator.Data.Services; using WebDoorCreator.UI.Data; namespace WebDoorCreator.UI.Components.DoorDef diff --git a/WebDoorCreator.UI/Components/Hardware/HwSingleInstance.razor.cs b/WebDoorCreator.UI/Components/Hardware/HwSingleInstance.razor.cs index 39eee69..a9bcddd 100644 --- a/WebDoorCreator.UI/Components/Hardware/HwSingleInstance.razor.cs +++ b/WebDoorCreator.UI/Components/Hardware/HwSingleInstance.razor.cs @@ -1,8 +1,8 @@ using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using Newtonsoft.Json; -using WebDoorCreator.API.Data; using WebDoorCreator.Data.DbModels; +using WebDoorCreator.Data.Services; using WebDoorCreator.UI.Data; namespace WebDoorCreator.UI.Components.Hardware @@ -200,7 +200,11 @@ namespace WebDoorCreator.UI.Components.Hardware DoorOpInst.JsoncActVal = serVal; // salvo! await WDCService.DoorOpUpdate(DoorOpInst); - await QDataServ.sendCalcReq($"ORD{DoorOpInst.DoorId:00000}"); + // invio msg dato "salvabile" +#if false + // invio richiesta calcolo + await QDataServ.sendCalcReq($"ORD{DoorOpInst.DoorId:00000}"); +#endif // rileggo await FullReloadData(true); } diff --git a/WebDoorCreator.UI/Pages/DoorDefinition.razor.cs b/WebDoorCreator.UI/Pages/DoorDefinition.razor.cs index 26b6d45..913d41d 100644 --- a/WebDoorCreator.UI/Pages/DoorDefinition.razor.cs +++ b/WebDoorCreator.UI/Pages/DoorDefinition.razor.cs @@ -2,9 +2,9 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.WebUtilities; using Microsoft.JSInterop; using NLog; -using WebDoorCreator.API.Data; using WebDoorCreator.Data; using WebDoorCreator.Data.DbModels; +using WebDoorCreator.Data.Services; using WebDoorCreator.UI.Data; namespace WebDoorCreator.UI.Pages diff --git a/WebDoorCreator.UI/Program.cs b/WebDoorCreator.UI/Program.cs index e5320eb..ec59993 100644 --- a/WebDoorCreator.UI/Program.cs +++ b/WebDoorCreator.UI/Program.cs @@ -10,8 +10,8 @@ using Microsoft.AspNetCore.Localization; using Microsoft.EntityFrameworkCore; using StackExchange.Redis; using System.Globalization; -using WebDoorCreator.API.Data; using WebDoorCreator.Data; +using WebDoorCreator.Data.Services; using WebDoorCreator.UI.Areas.Identity; using WebDoorCreator.UI.Data; @@ -53,9 +53,6 @@ builder.Services.AddServerSideBlazor(); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddSingleton(); -#if false -builder.Services.AddSingleton(); -#endif builder.Services.AddSingleton(); builder.Services.AddScoped>(); builder.Services.AddHttpContextAccessor(); diff --git a/WebDoorCreator.UI/temp/Conf.yaml b/WebDoorCreator.UI/temp/Conf.yaml index 5c6c125..aab7221 100644 --- a/WebDoorCreator.UI/temp/Conf.yaml +++ b/WebDoorCreator.UI/temp/Conf.yaml @@ -12,7 +12,7 @@ order: project: pO: line: -date: 2023-04-18T16:04:34.2625885+02:00 +date: 2023-04-19T12:03:17.7578859+02:00 piece: DO_1 size: width: 33.8125 @@ -38,15 +38,20 @@ profiles: type: BV machining: ON overmaterial: 0.1 -ept: - - template: Generic\StdEPT - ToptoCL: 15 - FacetoCL: 0.875 -flush_bolts: - - template: Corner\8501 - type: corner - side: top - position: 5 +edge_pulls: + - template: Generic\EdgePull230B + position: 45 +flush_pulls: + - template: Generic\Circle + position: 35 + back_set: 5 + depth: 1 + face: secure +locks: + - template: Generic\DocMyTest5Bore + position: 45.875 + back_set: 2.0 offset_WS: 0.0 + side: lock_top ---