Blog

TransparencySlider

Mein Kollege Daniel hat wieder mal was schönes für uns gezaubert 🪄

Mit diesem Script könnt Ihr schnell und einfach die Transparenz von Bauteilen in Pro Panel verändern:

Warnung: Das Script nutzt Reflection um auf die Ebene des 3D Objekts zuzugreifen. Wir empfehlen klar solche Funktionen im Script nicht zu verwenden und stattdessen die API zu nutzen!

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using System.Xml;
using Eplan.EplApi.ApplicationFramework;
using Eplan.EplApi.Gui;
using Eplan.EplApi.Scripting;

namespace DanielPa.Scripting.Prototypes
{
  public class TransparencySlider
  {
    private const string ATTRIBUTE_LAYER_NAME = "A1424";
    private const string ATTRIBUTE_LAYER_TRANSPARENCY = "A1434";

    [Start]
    [DeclareAction("TransparencySlider")]
    public void Execute()
    {
      var layer = GetLayerNameAndDescription();
      
      var percentage = GetCurrentTransparencyState(layer.Key);
      ShowSlider(percentage, layer);
    }

    [DeclareMenu]
    [DeclareRegister]
    public void AddContextMenu()
    {
      //XCabPlacerTreePage 4010
      var contextMenu = new Eplan.EplApi.Gui.ContextMenu();
      ContextMenuLocation location = new ContextMenuLocation("XCabPlacerTreePage", "4010");
      contextMenu.AddMenuItem(location, MENU_NAME, ACTION_NAME, false, false);
    }

    [DeclareUnregister]
    public void RemoveContextMenu()
    {
      var contextMenu = new Eplan.EplApi.Gui.ContextMenu();
      ContextMenuLocation location = new ContextMenuLocation("XCabPlacerTreePage", "4010");
      contextMenu.RemoveMenuItem(location, MENU_NAME, ACTION_NAME, false, false);
    }

    private const string MENU_NAME = "Transparency...";
    private const string ACTION_NAME = "TransparencySlider";

    private KeyValuePair<string, string> GetLayerNameAndDescription()
    {
      // XEsGetPropertyAction /PropertyId:? /PropertyIndex:0
      string value = null;
      var context = new ActionCallingContext();
      context.AddParameter("PropertyId", "2000");
      context.AddParameter("PropertyIndex", "0");
      var cli = new CommandLineInterpreter(true, true);
      cli.Execute("XEsGetPropertyAction", context);
      context.GetParameter("PropertyValue", ref value);
      var obj = StorableObjectWrapper.FromStringIdentifier(value);
      var placement3D = new Placement3DWrapper(obj);
      var description = placement3D.Layer.Description.Split('@').Last().TrimEnd(';');
      return new KeyValuePair<string, string>(placement3D.Layer.Name, description);
    }

    private void ShowSlider(float percentage, KeyValuePair<string,string> layer)
    {
      var form = new System.Windows.Forms.Form();
      var stackPanel = new System.Windows.Forms.FlowLayoutPanel();
      var panel = new System.Windows.Forms.Panel();
      panel.BackColor = Color.SteelBlue;
      panel.Padding = new Padding(1); // This will create a 1px border
      panel.AutoSize = true;
      
      stackPanel.Dock = DockStyle.Fill; 
      stackPanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
      stackPanel.BackColor = Color.White;
      
      stackPanel.AutoSize = true;
      stackPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
      panel.Controls.Add(stackPanel);
      form.Controls.Add(panel);
      var label = new System.Windows.Forms.Label();
      label.Text = string.Format("Transparency Slider [{0} - {1}]", layer.Key, layer.Value);
      label.AutoSize = true;
      label.Margin = new Padding(4, 4, 4, 4);
      stackPanel.Controls.Add(label);
      var slider = new System.Windows.Forms.TrackBar();
      slider.Width = 400;
      slider.Minimum = 0;
      slider.Maximum = 100;
      slider.TickFrequency = 10;
      slider.LargeChange = 10;
      slider.SmallChange = 10;
      slider.BackColor = Color.White;
      
      try
      {
        slider.Value = (int)(percentage * 100);
      }
      catch (Exception)
      {
        slider.Value = 0;
      }
      slider.TickStyle = System.Windows.Forms.TickStyle.Both;
      slider.ValueChanged += (sender, args) => SetTransparency(layer.Key, slider.Value / 100f);
      stackPanel.Controls.Add(slider);

      // Set form properties
      form.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
      form.BackColor = Color.White;
      form.TopMost = true;
      form.AutoSize = true;
      form.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
      form.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
      form.Location = System.Windows.Forms.Cursor.Position;

      // Close form when it loses focus
      form.Deactivate += (sender, args) => form.Close();

      // Close form when Escape key is pressed
      slider.KeyDown += (sender, args) =>
      {
        if (args.KeyCode == System.Windows.Forms.Keys.Escape)
        {
          form.Close();
        }
      };
      form.KeyDown += (sender, args) =>
      {
        if (args.KeyCode == System.Windows.Forms.Keys.Escape)
        {
          form.Close();
        }
      };

      WindowWrapper windowWrapper = new WindowWrapper(Process.GetCurrentProcess().MainWindowHandle);
      form.Show(windowWrapper);
    }

    private void SetTransparency(string layerName, float sliderValue)
    {
      // changelayer /LAYER:560 /VISIBLE:1 /COLORID:9 /TRANSPARENCY:0.1
      var context = new ActionCallingContext();
      context.AddParameter("LAYER", layerName);
      context.AddParameter("TRANSPARENCY", sliderValue.ToString());
      new CommandLineInterpreter().Execute("changelayer", context);
    }

    private float GetCurrentTransparencyState(string layerName)
    {
      var fileName = ExportLayerTable();
      var transparency = GetTransparencyValue(fileName, layerName);
      return transparency;
    }

    private float GetTransparencyValue(string fileName, string layerName)
    {
      // <O76 Build="5313" A1="76/335" A3="0" A13="0" A14="0" R1421="14/1" A1422="1" A1423="1" A1424="EPLAN560" A1425="##_##@560/ESGraphics;??_??@3D-Grafik.Schrank;" A1426="560" A1427="0" A1428="274" A1429="0.5" A1430="-1" A1433="0" A1434="77" A1435="1">
      var document = new XmlDocument();
      document.Load(fileName);
      var xPathSelectElementByLayerName =
        string.Format("/EplanPxfRoot/O76[@{0}='{1}']", ATTRIBUTE_LAYER_NAME, layerName);
      var layerElement = document.SelectSingleNode(xPathSelectElementByLayerName);
      var transparencyByteValue = layerElement.Attributes[ATTRIBUTE_LAYER_TRANSPARENCY].Value;
      var transparencyPercentage = float.Parse(transparencyByteValue) / 255;
      return transparencyPercentage;
    }

    private string ExportLayerTable()
    {
      string fileName = Path.Combine(Path.GetTempPath(), "Layer.xml");
      if (File.Exists(fileName))
      {
        File.Delete(fileName);
      }
      var context = new ActionCallingContext();
      context.AddParameter("TYPE", "EXPORT");
      context.AddParameter("EXPORTFILE", fileName);
      new CommandLineInterpreter().Execute("graphicallayertable", context);
      return fileName;
    }
  }

  public class Placement3DWrapper
  {
    private readonly object _placement3D;

    public Placement3DWrapper(object o)
    {
      _placement3D = o;
    }

    public GraphicalLayerWrapper Layer
    {
      get
      {
        var layer = _placement3D.GetType().GetProperty("Layer");
        var value = layer.GetValue(_placement3D);
        return new GraphicalLayerWrapper(value);
      }
    }
  }

  public class GraphicalLayerWrapper
  {
    private readonly object _graphicalLayer;

    public GraphicalLayerWrapper(object value)
    {
      _graphicalLayer = value;
    }

    public string Name
    {
      get
      {
        var name = _graphicalLayer.GetType().GetProperty("Name");
        var value = name.GetValue(_graphicalLayer);
        return value.ToString();
      }
    }

    public string Description
    {
      get
      {
        var description = _graphicalLayer.GetType().GetProperty("Description");
        var value = description.GetValue(_graphicalLayer);
        return value.ToString();
      }
    }
  }

  public class StorableObjectWrapper
  {
    private static Assembly _dataModelAssembly;

    public static object FromStringIdentifier(string databaseId)
    {
      if (_dataModelAssembly == null)
      {
        _dataModelAssembly = AppDomain.CurrentDomain.GetAssemblies()
                                      .FirstOrDefault(a => a.FullName.StartsWith("Eplan.EplApi.DataModelu"));
      }

      var storableObjectType = _dataModelAssembly.ExportedTypes.FirstOrDefault(t => t.Name == "StorableObject");
      MethodInfo fromStringIdentifier = storableObjectType.GetMethod("FromStringIdentifier",
                                                                     BindingFlags.Public | BindingFlags.Static, null,
                                                                     new[] { typeof(string) }, null);
      var args = new object[] { databaseId };
      var storableObject = fromStringIdentifier.Invoke(null, args);
      return storableObject;
    }
  }

  public class WindowWrapper : IWin32Window
  {
    private readonly IntPtr _hwnd;

    public WindowWrapper(IntPtr handle)
    {
      _hwnd = handle;
    }

    public IntPtr Handle
    {
      get { return _hwnd; }
    }
  }
}

 

Von |2024-03-12T07:04:41+01:002024-03-12|EPLAN, EPLAN-Scripts|

EPLAN DeepL: Projekt übersetzen

Es hat genau einen Tag gedauert, seit EPLAN Newtonsoft.Json unterstützt, bis mein grandioser Kollege Daniel ein Script für EPLAN gebastelt hat, dass die fehlenden Übersetzungen im Wörterbuch mit DeepL übersetzt.

Das Script exportiert erstmal alle fehlenden Übersetzungen im Projekt (Quellsprache Deutsch).
Frägt dann die pro Eintrag die DeepL API nach einer Übersetzung (English).
Die Ergebnisse werden dann in einer XML-Datei gespeichert und angezeigt.

Diese Datei kann dann ins Wörterbuch importiert werden.

Vorraussetzung ist ein API-Key, welcher in der Windows Variable DEEPL_API_KEY gespeichert ist.

Orginal-Script findet Ihr auf GitHub.
Vielen Dank Daniel 💖

using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Net.Http;
using System.Windows.Forms;
using System.Xml.Serialization;
using Eplan.EplApi.ApplicationFramework;
using Eplan.EplApi.Base;
using Eplan.EplApi.Scripting;

namespace TranslateWithDeepl
{
  public class TranslateWithDeepl
  {
    private readonly string _apiKey;
    private readonly string _url = "https://api-free.deepl.com/v2/translate";
    private readonly HttpClient _httpClient;

    [Start]
    public async void Execute(ActionCallingContext context)
    {
      var missingTerms = GetMissingTerms();
      await TranslateTerms(missingTerms);
    }

    private async Task TranslateTerms(EplanLanguageDbRoot missingTerms)
    {
      var requestDto = new RequestDto();
      requestDto.TargetLang = "EN";
      requestDto.Text = missingTerms.TextSection.MT.SelectMany(mt => mt.T).Where(t => t.Lang == "de_DE").Select(t => t.Text).ToArray();
      var translate = await Translate(requestDto);
      
      for(int i = 0; i < missingTerms.TextSection.MT.Length; i++)
      {
        missingTerms.TextSection.MT[i].T.First(t => t.Lang == "en_US").Text = translate.Translations[i].Text;
      }
      
      var projectDoc = PathMap.SubstitutePath("$(DOC)");
      var fileName = Path.Combine(projectDoc, "TranslatedTerms.xml");
      var serializer = new XmlSerializer(typeof(EplanLanguageDbRoot));
      using (var writer = new StreamWriter(fileName))
      {
        serializer.Serialize(writer, missingTerms);
      }
      
      Process.Start(fileName);
    }

    public EplanLanguageDbRoot GetMissingTerms()
    {
      var xmlFile = Path.GetTempFileName();
      var context = new ActionCallingContext();
      context.AddParameter("TYPE", "EXPORTMISSINGTRANSLATIONS");
      context.AddParameter("LANGUAGE", "en_US");
      context.AddParameter("EXPORTFILE", xmlFile);
      context.AddParameter("CONVERTER", "XTrLanguageDbXmlConverterImpl");
      new CommandLineInterpreter().Execute("translate", context);
      
      var serializer = new XmlSerializer(typeof(EplanLanguageDbRoot));
      EplanLanguageDbRoot eplanLanguageDbRoot = null;
      
      using (var reader = new StreamReader(xmlFile))
      {
        eplanLanguageDbRoot = (EplanLanguageDbRoot)serializer.Deserialize(reader);
      }
      return eplanLanguageDbRoot;
    }

    public TranslateWithDeepl()
    {
      // get api key from %DEEPL_API_KEY% environment variable
      _apiKey = System.Environment.GetEnvironmentVariable("DEEPL_API_KEY");
      _httpClient = new HttpClient();
    }

    private async Task<string> Translate(string text, string targetLang)
    {
      var translateDto = new RequestDto
      {
        Text = new string[] { text },
        TargetLang = targetLang
      };

      ResponseDto responseDto = await Translate(translateDto);
      return responseDto.Translations[0].Text;
    }

    private async Task<ResponseDto> Translate(RequestDto translateDto)
    {
      var json = JsonConvert.SerializeObject(translateDto);
      var data = new StringContent(json, Encoding.UTF8, "application/json");

      _httpClient.DefaultRequestHeaders.Add("Authorization", string.Format("DeepL-Auth-Key {0}", _apiKey));

      var response = await _httpClient.PostAsync(_url, data);
      var result = await response.Content.ReadAsStringAsync();
      ResponseDto responseDto = JsonConvert.DeserializeObject<ResponseDto>(result);

      return responseDto;
    }
  }

  public class ResponseDto
  {
    [JsonProperty("translations")]
    public TranslationDto[] Translations { get; set; }
  }

  public class TranslationDto
  {
    [JsonProperty("detected_source_language")]
    public string DetectedSourceLanguage { get; set; }

    [JsonProperty("text")]
    public string Text { get; set; }
  }

  public class RequestDto
  {
    [JsonProperty("text")]
    public string[] Text { get; set; }

    [JsonProperty("target_lang")]
    public string TargetLang { get; set; }
  }

  [XmlRoot(ElementName = "EplanLanguageDbRoot")]
  public class EplanLanguageDbRoot
  {
    [XmlAttribute(AttributeName = "Languages")]
    public string Languages { get; set; }

    [XmlAttribute(AttributeName = "numTexts")]
    public int NumTexts { get; set; }

    [XmlAttribute(AttributeName = "numTerms")]
    public int NumTerms { get; set; }

    [XmlAttribute(AttributeName = "numNonTranslate")]
    public int NumNonTranslate { get; set; }

    [XmlElement(ElementName = "TextSection")]
    public TextSection TextSection { get; set; }
  }

  public class TextSection
  {
    [XmlElement(ElementName = "MT")]
    public MT[] MT { get; set; }
  }

  public class MT
  {
    [XmlElement(ElementName = "T")]
    public T[] T { get; set; }
  }

  public class T
  {
    [XmlAttribute(AttributeName = "xml:lang")]
    public string Lang { get; set; }

    [XmlText]
    public string Text { get; set; }
  }
}

 

Von |2024-03-13T09:27:15+01:002024-03-08|EPLAN, EPLAN-Scripts|

Newtonsoft.Json in EPLAN Scripting

Still und leise kam ein neuer Namespace zum Scripting mit dem EPLAN 2024 Update 2 hinzu: Newtonsoft.Json

Nun kann man im Scripting auch mit REST-APIs oder JSON-Dateien direkt arbeiten und so z.B. Daten aus der EPLAN Cloud abrufen.
Leider setzt EPLAN hier auf eine sehr alte (2015) Version 6.0.8, welche mit der Installation mitkommt.

Anbei mal Beispiele um mit JSON zu arbeiten.

Serialize
Product product = new Product();
product.Name = "Apple";
product.Expiry = new DateTime(2008, 12, 28);
product.Sizes = new string[] { "Small" };

string json = JsonConvert.SerializeObject(product);
// {
//   "Name": "Apple",
//   "Expiry": "2008-12-28T00:00:00",
//   "Sizes": [
//     "Small"
//   ]
// }
Deserialize
string json = @"{
  'Name': 'Bad Boys',
  'ReleaseDate': '1995-4-7T00:00:00',
  'Genres': [
    'Action',
    'Comedy'
  ]
}";

Movie m = JsonConvert.DeserializeObject<Movie>(json);

string name = m.Name;
// Bad Boys
Von |2024-03-06T14:41:24+01:002024-03-06|EPLAN, EPLAN-Scripts|

EPLAN Ribbon mit Built-In Befehlen & RibbonCreator

Seit EPLAN 2022 war der Wunsch bei vielen Usern da, auch bestehende Commands von EPLAN in einer Registerkarte zu nutzen. Mit EPLAN 2024 ist dies nun möglich… Leider aber nur per Scripting / API.

Mit der Methode AddCommandWithId() kann eine “Command ID” übergeben werden, welcher der von EPLAN entspricht.

Beispiel für Strukturkennzeichenverwaltung:

const int commandMenuId = 35089;
var tab = ribbonBar.AddTab("CustomTab");
var commandGroup = tab.AddCommandGroup("Group1");
var commandAction = commandGroup.AddCommandWithId(commandMenuId);

Sind Buttons z.B. Drop-Down-Buttons vorhanden, wie bei der Artikelverwaltung, werden diese auch so übernommen:

Die Command ID bekommt Ihr über den Diagnose-Dialog (STRG+^):

Leider sind derzeit nicht alle Commands auch mit IDs im Diagnose-Dialog versehen (z.B. Artikelverwaltung):

Darum haben wir eine Auflistung aller Commands mit deren ID aus dem EPLAN Standard Ribbon exportiert und im ShopForProcess.com für euch frei zugänglich aufgelistet (unter “Anleitung” zu finden).

 

Denn wir haben diese Funktionalität gleich in ein fertiges Produkt gekippt: RibbonCreator

  • Einfaches Verteilen von Ribbons
  • Rechteverwaltung pro Ribbon
  • Simple UI um Ribbons zu konfigurieren
  • EPLAN Commands
  • Custom Actions

Von |2024-03-13T09:27:25+01:002024-02-06|EPLAN, EPLAN-Scripts|

Automatische Heubox

Wir wollten für unser Pferd eine Möglichkeit schaffen, dass es auch in der Nacht eine Portion Heu bekommt 🦄

Aus diesem Grund haben wir uns mal umgeschaut, was es hier für Lösungen gibt. Eine automatische Heubox, welche das Heu “fallen” lässt, sollte es werden…

Doch die Preise am Markt sind utopisch. Die Boxen an sich sind noch ganz OK, aber die Steuerungen dazu sind aus meiner Sicht viel zu teuer…

Ihr wisst was kommt: Also bauen wir uns mal einen Controller. Hab mich für ein ESP-Lösung entschieden:
LC Technologies 4 Kanal Relay Board mit ESP-01

Den ESP habe ich mit Tasmota geflasht. Bin total begeistert was damit alles möglich ist. Natürlich wurde das auch noch in Home Assistant eingebunden :)

Das Ganze noch in ein Gehäuse gepackt und paar Klemmen zum Anschluss dazu. Den nächsten Controller würde ich aber steckbar machen.

Die Box selbst habe ich aus OSB Platten gebaut, da sie nicht draußen steht und das Material vergleichbar günstig ist.
Klappe vorne zum Befüllen mit Klavierband und tollen Edelstahl-Verschluss. Ich habe die Box auf Gehrung verleimt und zur Unterstützung noch Winkel in die Ecken.

Das Herzstück ist ein elektrischer abschaltbarer Permanentmagnet mit 120N. Dieser wird für 1s abgeschalten und die Klappe fällt nach unten.

Da wir leider keinen Strom an der Stelle haben, wo die Futterbox aufgestellt wird, haben wir auch den Stromverbrauch so gut es geht optimiert. Unser Test mit AA-Batterien (12V Pack), hat nicht gut geklappt. Mit einer 12V Powerbank hält das länger aber im Winter leider keine 3 Tage.
Darum ist es eine PV-Lösung geworden und eine 12Ah Gel-Batterie als Speicher reicht locker, auch im kalten Winter (hatten -12°C und keine Sonne).

Die Fallklappe haben wir so leicht wie möglich gebaut mit einer Polyethylen-Platte. Sie biegt sich aber sehr durch. Aus diesem Grund wurde es später durch eine Multiplex-Platte 6mm getauscht. Dadurch ist sie stabil genug, aber nicht allzu schwer.

Das Magnet-Gegenstück und auch alle anderen Schrauben haben wir verletzungssicher mit Hutmuttern befestigt.

Die Steuerung haben wir an der Seite angebracht und Einschalter bzw. Kabel sind an der Hinterseite pferdesicher angebracht.

Wir können nun beliebige Timer über die Weboberfläche einstellen. Hierfür nehme ich meinen Hotspot mit in den Stall und wir konfigurieren das.

Das war der erste Test mit Heu… hat auf Anhieb funktioniert.
Bei der nächsten Box würden wir einen 180NM Magnet verbauen damit dieser noch besser hält.

 

Auf alle Fälle sind wir zufrieden… Denn unser Pferd ist es auch!

Da ich im Bekanntenkreis nun mehrere Controller bauen darf, habe ich das Ganze gleich als Produkt in den Shop gepackt. Also falls jemand Interesse hat, oder Fragen, einfach melden!

Shop

Von |2024-01-24T13:24:45+01:002024-01-16|OpenFeedBox, Projekte|
Nach oben