# -*- coding: utf-8 -*-

# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃                                                                                                                            ┃
# ┃          Project Name: Aufbewahrungsdose.FCMacro                                                                           ┃
# ┃                Author: Michael Krämer                                                                                      ┃
# ┃                 Email: Michael@dk8pp.de                                                                                    ┃
# ┃                  Date: 16.11.2024                                                                                          ┃
# ┃                  Tool: Python (FreeCAD)                                                                                    ┃
# ┃               Version: 0.1                                                                                                 ┃
# ┃        Program status: still testing                                                                                       ┃
# ┃                                                                                                                            ┃
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
# ┃                                                                                                                            ┃
# ┃  Purpose: Generate FreeCAD drawing of a storage box to be produced with a 3D-printer                                       ┃
# ┃                                                                                                                            ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import FreeCAD
from FreeCAD import Placement, Rotation, Vector
import matplotlib
from io import StringIO

doc=FreeCAD.newDocument("Dose")

# Erzeugt eine Aufbewahrungsdose mit Unter- und Oberteil
# Es werden die Außenmaße und die Dicke des Oberteils spezifiziert und die Randhöhe des Unterteils.
# Die Dicke des Bodens ist separat einstellbar.
# Optional können Noppen und Nuten an der Längsseite erzeugt werden, damit der Deckel besser einrastet
# Es können auch Griffmulden erzeugt werden, die das leichtere Abheben des Deckels erlauben.

# Design auswählen
IC_klein = False
IC_gross = True
mittelgroß = False

###### Start anwendungsspezifischer Parameter #####
if (IC_klein):
# z.B. für ICs
   O_Len = 86.5            # Länge des Oberteils
   O_Breite = 66.5         # Breite des Oberteils
   O_Hoehe = 12.0          # Höhe des Oberteils
   O_Dicke = 1.5           # Wandstärke des Oberteils

   U_RandHoehe = 9.0       # Höhe des Randes des Unterteils
   U_Bodendicke = 2.0      # Dicke des Bodens des Unterteils
                           # Gesamthöhe des Unterteils: U_RandHoehe + U_Bodendicke

   Rad_V = 2.0             # Radius außen zum Abrunden der vertikalen Kanten
   Rad_H = 2.0             # Radius außen zum Abrunden der horizontalen Kanten

   Spiel = 0.25            # Spiel zwischen Oberteil und Unterteil, umlaufend

   # Rastnoppen und Nut
   N_L = True              # Erzeuge Noppen an der Längsseite
   N_B = True              # Erzeuge Noppen an der Breitseite (nicht implementiert)
   N_RNoppen = 0.8         # Radius der Noppen
   N_RNut = 1.0            # Radius der Nut, sollte etwas größer als der Radius der Noppen sein
   N_LNoppen = 5.0         # Länge der Noppen
   N_ULNut = 2.0           # Beidseitige Überlänge der Nut, sollte etwas größer als die Länge der Noppen sein
   N_WA = 25.0             # Wandabstand des Mittelpunkts der Noppen in x- und y-Richtung
   N_WAz = 3.0             # Wandabstand der Noppen in z-Richtung

   # Griffmulden
   G_L = True              # Erzeuge Griffmulden an der Längsseite
   G_LMulde = 8.0          # Länge der Griffmulden
   G_TMulde = 2.0          # Tiefe der Griffmulden
   G_Rad = 1.5             # Rundungsradius der Mulden (< G_TMulde)
   G_WA = 10.0             # Wandabstand des Mittelpunkts der Griffmulden in x- und y-Richtung
                           # sollte nicht mit der Position der Nut übereinstimmen

if (mittelgroß):
# z.B. für Relais
   O_Len = 115             # Länge des Oberteils
   O_Breite = 80.0         # Breite des Oberteils
   O_Hoehe = 20.0          # Höhe des Oberteils
   O_Dicke = 1.5           # Wandstärke des Oberteils

   U_RandHoehe = 12.0      # Höhe des Randes des Unterteils
   U_Bodendicke = 2.0      # Dicke des Bodens des Unterteils
                           # Gesamthöhe des Unterteils: U_RandHoehe + U_Bodendicke

   Rad_V = 5.0             # Radius außen zum Abrunden der vertikalen Kanten
   Rad_H = 5.0             # Radius außen zum Abrunden der horizontalen Kanten

   Spiel = 0.25            # Spiel zwischen Oberteil und Unterteil, umlaufend

   # Rastnoppen und Nut
   N_L = True              # Erzeuge Noppen an der Längsseite
   N_B = True              # Erzeuge Noppen an der Breitseite (nicht implementiert)
   N_RNoppen = 0.8         # Radius der Noppen
   N_RNut = 1.0            # Radius der Nut, sollte etwas größer als der Radius der Noppen sein
   N_LNoppen = 5.0         # Länge der Noppen
   N_ULNut = 2.0           # Beidseitige Überlänge der Nut, sollte etwas größer als die Länge der Noppen sein
   N_WA = 25.0             # Wandabstand des Mittelpunkts der Noppen in x- und y-Richtung
   N_WAz = 5.0             # Wandabstand der Noppen in z-Richtung

   # Griffmulden
   G_L = True              # Erzeuge Griffmulden an der Längsseite
   G_LMulde = 8.0          # Länge der Griffmulden
   G_TMulde = 2.0          # Tiefe der Griffmulden
   G_Rad = 1.5             # Rundungsradius der Mulden (< G_TMulde)
   G_WA = 10.0             # Wandabstand des Mittelpunkts der Griffmulden in x- und y-Richtung
                           # sollte nicht mit der Position der Nut übereinstimmen

if (IC_gross):
# z.B. für ICs, doppelte Außenabmessungen wie IC_klein
   O_Len = 133.0           # Länge des Oberteils
   O_Breite = 86.5         # Breite des Oberteils
   O_Hoehe = 12.0          # Höhe des Oberteils
   O_Dicke = 1.5           # Wandstärke des Oberteils

   U_RandHoehe = 9.0       # Höhe des Randes des Unterteils
   U_Bodendicke = 2.0      # Dicke des Bodens des Unterteils
                           # Gesamthöhe des Unterteils: U_RandHoehe + U_Bodendicke

   Rad_V = 2.0             # Radius außen zum Abrunden der vertikalen Kanten
   Rad_H = 2.0             # Radius außen zum Abrunden der horizontalen Kanten

   Spiel = 0.25            # Spiel zwischen Oberteil und Unterteil, umlaufend

   # Rastnoppen und Nut
   N_L = True              # Erzeuge Noppen an der Längsseite
   N_B = True              # Erzeuge Noppen an der Breitseite (nicht implementiert)
   N_RNoppen = 0.8         # Radius der Noppen
   N_RNut = 1.0            # Radius der Nut, sollte etwas größer als der Radius der Noppen sein
   N_LNoppen = 5.0         # Länge der Noppen
   N_ULNut = 2.0           # Beidseitige Überlänge der Nut, sollte etwas größer als die Länge der Noppen sein
   N_WA = 25.0             # Wandabstand des Mittelpunkts der Noppen in x- und y-Richtung
   N_WAz = 5.0             # Wandabstand der Noppen in z-Richtung

   # Griffmulden
   G_L = True              # Erzeuge Griffmulden an der Längsseite
   G_LMulde = 8.0          # Länge der Griffmulden
   G_TMulde = 2.0          # Tiefe der Griffmulden
   G_Rad = 1.5             # Rundungsradius der Mulden (< G_TMulde)
   G_WA = 10.0             # Wandabstand des Mittelpunkts der Griffmulden in x- und y-Richtung
                           # sollte nicht mit der Position der Nut übereinstimmen

# Farben
Farbe_Unterteil = matplotlib.colors.to_rgb('royalblue')
Farbe_Oberteil  = matplotlib.colors.to_rgb('lightsteelblue')
###### Ende anwendungsspezifischer Parameter #####

vec0=Vector(0,0,0)      # Nullvektor
vecx=Vector(1,0,0)      # Drehachse x
vecy=Vector(0,1,0)      # Drehachse y
vecz=Vector(0,0,1)      # Drehachse z

N_LNut = N_LNoppen + 2*N_ULNut  # Länge der Nut

def MakeNoppe(rad, len):
# Noppe erzeugen, Radius rad, Länge len
# halber Zylinder in z-Richtung, offen nach -y
   noppe = Part.makeCylinder(rad, len, vec0, vecz, 180.0)
   return(noppe)

def MakeMulde(length, depth, width, rad):
# Mulde erzeugen, Länge length, Tiefe depth, Breite width, Rundungsradius rad
# halber Zylinder in z-Richtung, offen nach -y
   mulde = Part.makeBox(length, width, depth)
# untere Kanten in y-Richtung zum Abrunden auswählen
   edges=[]
   edgecount = len(mulde.Edges)
   for i in range (0,edgecount):
      thisEdge = mulde.Edges[i]
      if (thisEdge.tangentAt(0.0) == Vector(0,1,0)) and (thisEdge.valueAt(0).z == 0):
         edges.append(thisEdge)
# ...und abrunden
   if ((len(edges) > 0) & (rad > 0)):
      mulde = mulde.makeFillet(rad, edges)
   return(mulde)

def Abrunden_vertikal(part, rad):
# vertikale Kanten zum Abrunden auswählen
   edges=[]
   edgecount = len(part.Edges)
   for i in range (0,edgecount):
      thisEdge = part.Edges[i]
      tan = thisEdge.tangentAt(0.0)
      if tan == Vector(0,0,1):
         edges.append(thisEdge)

# ...und abrunden
   if ((len(edges) > 0) & (rad > 0)):
      part = part.makeFillet(rad, edges)
   return(part)

# Basisformen generieren
U_Boden  = Part.makeBox(O_Len, O_Breite, U_Bodendicke)     # Boden des Unterteils
U_Aussen = Part.makeBox(O_Len-2*O_Dicke-2*Spiel, O_Breite-2*O_Dicke-2*Spiel, U_RandHoehe).translate(Vector(O_Dicke+Spiel,O_Dicke+Spiel,U_Bodendicke))          # Außenbox des Randes des Unterteils
U_Innen  = Part.makeBox(O_Len-4*O_Dicke-2*Spiel, O_Breite-4*O_Dicke-2*Spiel, U_RandHoehe).translate(Vector(2*O_Dicke+Spiel,2*O_Dicke+Spiel,U_Bodendicke))      # Innenbox des Unterteils

O_Aussen = Part.makeBox(O_Len, O_Breite, O_Hoehe)          # Außenbox Oberteil
O_Innen  = Part.makeBox(O_Len-2*O_Dicke, O_Breite-2*O_Dicke, O_Hoehe).translate(Vector(O_Dicke,O_Dicke,O_Dicke))   # Innenbox Oberteil

# Kanten abrunden
U_Boden  = Abrunden_vertikal(U_Boden,  Rad_V)                                   # vertikale Kanten des Bodens des Unterteils abrunden
U_Aussen = Abrunden_vertikal(U_Aussen, Rad_V-O_Dicke-Spiel)                     # vertikale Kanten der Außenseite des Randes des Unterteils abrunden
U_Innen  = Abrunden_vertikal(U_Innen,  Rad_V-2*O_Dicke-Spiel)                   # vertikale Kanten der Innenseite des Randes des Unterteils abrunden
O_Aussen = Abrunden_vertikal(O_Aussen, Rad_V)                                   # vertikale Kanten der Außenseite des Oberteils abrunden
O_Innen  = Abrunden_vertikal(O_Innen,  Rad_V-O_Dicke)                           # vertikale Kanten der Innenseite des Oberteils abrunden

# horizontale Kanten der Außenseite des Oberteils zum Abrunden auswählen
edges=[]
edgecount = len(O_Aussen.Edges)
for i in range (0,edgecount):
      thisEdge = O_Aussen.Edges[i]
      if thisEdge.tangentAt(0.0) == vecy and thisEdge.valueAt(0).z < O_Hoehe/2:
         edges.append(thisEdge)
#         print(thisEdge.valueAt(0))

# ...und abrunden
if ((len(edges) > 0) & (Rad_H > 0)):
   O_Aussen = O_Aussen.makeFillet(Rad_H, edges)

# horizontale Kanten der Innenseite des Oberteils zum Abrunden auswählen
edges=[]
edgecount = len(O_Innen.Edges)
for i in range (0,edgecount):
      thisEdge = O_Innen.Edges[i]
      if thisEdge.tangentAt(0.0) == vecy and thisEdge.valueAt(0).z < O_Hoehe/2:
         edges.append(thisEdge)
#         print(thisEdge.valueAt(0))

# ...und abrunden
if ((len(edges) > 0) & (Rad_H > O_Dicke)):
   O_Innen = O_Innen.makeFillet(Rad_H-O_Dicke, edges)

# Unterteil zusammenbauen
Unterteil = U_Aussen.cut(U_Innen).fuse(U_Boden)

# Oberteil zusammenbauen
Oberteil = O_Aussen.cut(O_Innen)
if (N_L):
# Erzeuge Mulden an der Längsseite des Oberteils
   Mulde = MakeMulde(G_LMulde, G_TMulde, O_Dicke, G_Rad)
   Oberteil = Oberteil.cut(Mulde.copy().translate(Vector(      G_WA-G_LMulde/2,   0,                      O_Hoehe-G_TMulde)))
   Oberteil = Oberteil.cut(Mulde.copy().translate(Vector(O_Len-G_WA-G_LMulde/2,   0,                      O_Hoehe-G_TMulde)))
   Oberteil = Oberteil.cut(Mulde.copy().translate(Vector(      G_WA-G_LMulde/2,   O_Breite-O_Dicke,       O_Hoehe-G_TMulde)))
   Oberteil = Oberteil.cut(Mulde.copy().translate(Vector(O_Len-G_WA-G_LMulde/2,   O_Breite-O_Dicke,       O_Hoehe-G_TMulde)))

if (N_L):
# Erzeuge Noppen an der Längsseite des Oberteils
   Noppe = MakeNoppe(N_RNoppen, N_LNoppen).rotate(vec0, vecy, 90.0)
   Oberteil = Oberteil.fuse(Noppe.copy().translate(Vector(N_WA-N_LNoppen/2,       O_Dicke,                O_Hoehe-N_WAz)))
   Oberteil = Oberteil.fuse(Noppe.copy().translate(Vector(O_Len-N_WA-N_LNoppen/2, O_Dicke,                O_Hoehe-N_WAz)))
   Noppe = MakeNoppe(N_RNoppen, N_LNoppen).rotate(vec0, vecy, 90.0).rotate(vec0,  vecx, 180.0)
   Oberteil = Oberteil.fuse(Noppe.copy().translate(Vector(N_WA-N_LNoppen/2,       O_Breite-O_Dicke,       O_Hoehe-N_WAz)))
   Oberteil = Oberteil.fuse(Noppe.copy().translate(Vector(O_Len-N_WA-N_LNoppen/2, O_Breite-O_Dicke,       O_Hoehe-N_WAz)))

# Erzeuge Noppen an der Längsseite des Unterteils
   Noppe = MakeNoppe(N_RNut, N_LNut).rotate(vec0, vecy, 90.0)
   Unterteil = Unterteil.cut(Noppe.copy().translate(Vector(N_WA-N_LNut/2,         O_Dicke+Spiel,          U_Bodendicke+N_WAz)))
   Unterteil = Unterteil.cut(Noppe.copy().translate(Vector(O_Len-N_WA-N_LNut/2,   O_Dicke+Spiel,          U_Bodendicke+N_WAz)))
   Noppe = MakeNoppe(N_RNut, N_LNut).rotate(vec0, vecy, 90.0).rotate(vec0,  vecx, 180.0)
   Unterteil = Unterteil.cut(Noppe.copy().translate(Vector(N_WA-N_LNut/2,         O_Breite-O_Dicke-Spiel, U_Bodendicke+N_WAz)))
   Unterteil = Unterteil.cut(Noppe.copy().translate(Vector(O_Len-N_WA-N_LNut/2,   O_Breite-O_Dicke-Spiel, U_Bodendicke+N_WAz)))

# Oberteil drehen und passend für das Unterteil verschieben
Oberteil = Oberteil.rotate(vec0, vecx, 180).translate(Vector(0,O_Breite,O_Hoehe+U_Bodendicke))

output = StringIO()
print(O_Len,"x",O_Breite,"x",O_Hoehe, file=output, sep='', end='')

# Namen und Farben zuweisen
p1=doc.addObject("Part::Feature")
p1.Shape = Unterteil
p1.ViewObject.ShapeColor=Farbe_Unterteil
p1.Label="Unterteil_"+output.getvalue()

p1=doc.addObject("Part::Feature")
p1.Shape = Oberteil
p1.ViewObject.ShapeColor=Farbe_Oberteil
p1.Label="Oberteil_"+output.getvalue()

view = FreeCADGui.ActiveDocument.ActiveView
view.fitAll()

doc.recompute()

# Macro End


