From 3ecb88ccd65da117616c6e3057b588674360c81d Mon Sep 17 00:00:00 2001 From: Bouletto <118677264+orion4d@users.noreply.github.com> Date: Sun, 29 Jun 2025 17:08:54 +0200 Subject: [PATCH] Initial commit --- .gitattributes | 2 + README.md | 2 + __init__.py | 5 ++ nodes.py | 187 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+) create mode 100644 .gitattributes create mode 100644 README.md create mode 100644 __init__.py create mode 100644 nodes.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/README.md b/README.md new file mode 100644 index 0000000..17c31e3 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Calculator_Pro + diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..665be39 --- /dev/null +++ b/__init__.py @@ -0,0 +1,5 @@ +from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] + +print("✅ Custom Nodes: Scientific Calculator & Converter Loaded Successfully") \ No newline at end of file diff --git a/nodes.py b/nodes.py new file mode 100644 index 0000000..16aa1b8 --- /dev/null +++ b/nodes.py @@ -0,0 +1,187 @@ +# Fichier : scientific_calculator/nodes.py +# Version avec la calculatrice corrigée pour éviter l'erreur "float modulo". + +import math + +# ========================================================== +# CATEGORIE GLOBALE POUR TOUS LES NŒUDS +# ========================================================== +MAIN_CATEGORY = "CalculatorPro" + + +# =============================================== +# CLASSE 1 : CALCULATRICE SCIENTIFIQUE (CORRIGÉE) +# =============================================== +class ScientificCalculatorTriNode: + FUNCTION_CHOICES=["a+b","a-b","a*b","a/b","a^b","a mod b","a+c","a-c","a*c","a/c","a^c","a mod c","b+c","b-c","b*c","b/c","b^c","b mod c","min(a,b)","max(a,b)","min(a,c)","max(a,c)","min(b,c)","max(b,c)","a+b+c","a*b*c","sin(a)","cos(a)","tan(a)","sqrt(a)","log(a)","abs(a)","round(a)","sin(b)","cos(b)","tan(b)","sqrt(b)","log(b)","abs(b)","round(b)","sin(c)","cos(c)","tan(c)","sqrt(c)","log(c)","abs(c)","round(c)",] + CATEGORY = f"{MAIN_CATEGORY}/Math" + @classmethod + def INPUT_TYPES(cls): return {"required":{"a":("FLOAT",{"default":0.0,"step":0.01}),"b":("FLOAT",{"default":0.0,"step":0.01}),"c":("FLOAT",{"default":0.0,"step":0.01}),"function":(cls.FUNCTION_CHOICES,{"default":"a+b"}),}} + RETURN_TYPES,RETURN_NAMES,FUNCTION=("FLOAT","STRING"),("result","output_str"),"calculate" + + # --- MÉTHODE CORRIGÉE --- + def calculate(self, a, b, c, function): + res, txt, fmt = 0.0, "", "g" + fa, fb, fc = f"{a:{fmt}}", f"{b:{fmt}}", f"{c:{fmt}}" + + try: + if function == "a+b": res, txt = a + b, f"{fa} + {fb} = {a+b:{fmt}}" + elif function == "a-b": res, txt = a - b, f"{fa} - {fb} = {a-b:{fmt}}" + elif function == "a*b": res, txt = a * b, f"{fa} × {fb} = {a*b:{fmt}}" + elif function == "a/b": res, txt = (a / b, f"{fa} ÷ {fb} = {a/b:{fmt}}") if b != 0 else (float('nan'), "Err:Div/0") + elif function == "a^b": res, txt = a ** b, f"{fa} ^ {fb} = {a**b:{fmt}}" + elif function == "a mod b": res, txt = (a % b, f"{fa} mod {fb} = {a%b:{fmt}}") if b != 0 else (float('nan'), "Err:Mod/0") + elif function == "a+c": res, txt = a + c, f"{fa} + {fc} = {a+c:{fmt}}" + elif function == "a-c": res, txt = a - c, f"{fa} - {fc} = {a-c:{fmt}}" + elif function == "a*c": res, txt = a * c, f"{fa} × {fc} = {a*c:{fmt}}" + elif function == "a/c": res, txt = (a / c, f"{fa} ÷ {fc} = {a/c:{fmt}}") if c != 0 else (float('nan'), "Err:Div/0") + elif function == "a^c": res, txt = a ** c, f"{fa} ^ {fc} = {a**c:{fmt}}" + elif function == "a mod c": res, txt = (a % c, f"{fa} mod {fc} = {a%c:{fmt}}") if c != 0 else (float('nan'), "Err:Mod/0") + elif function == "b+c": res, txt = b + c, f"{fb} + {fc} = {b+c:{fmt}}" + elif function == "b-c": res, txt = b - c, f"{fb} - {fc} = {b-c:{fmt}}" + elif function == "b*c": res, txt = b * c, f"{fb} × {fc} = {b*c:{fmt}}" + elif function == "b/c": res, txt = (b / c, f"{fb} ÷ {fc} = {b/c:{fmt}}") if c != 0 else (float('nan'), "Err:Div/0") + elif function == "b^c": res, txt = b ** c, f"{fb} ^ {fc} = {b**c:{fmt}}" + elif function == "b mod c": res, txt = (b % c, f"{fb} mod {fc} = {b%c:{fmt}}") if c != 0 else (float('nan'), "Err:Mod/0") + elif function == "min(a,b)": res, txt = min(a,b), f"min({fa},{fb})={min(a,b):{fmt}}" + elif function == "max(a,b)": res, txt = max(a,b), f"max({fa},{fb})={max(a,b):{fmt}}" + elif function == "min(a,c)": res, txt = min(a,c), f"min({fa},{fc})={min(a,c):{fmt}}" + elif function == "max(a,c)": res, txt = max(a,c), f"max({fa},{fc})={max(a,c):{fmt}}" + elif function == "min(b,c)": res, txt = min(b,c), f"min({fb},{fc})={min(b,c):{fmt}}" + elif function == "max(b,c)": res, txt = max(b,c), f"max({fb},{fc})={max(b,c):{fmt}}" + elif function == "a+b+c": res, txt = a + b + c, f"{fa}+{fb}+{fc}={a+b+c:{fmt}}" + elif function == "a*b*c": res, txt = a * b * c, f"{fa}×{fb}×{fc}={a*b*c:{fmt}}" + elif function == "sin(a)": res, txt = math.sin(a), f"sin({fa})={math.sin(a):{fmt}}" + elif function == "cos(a)": res, txt = math.cos(a), f"cos({fa})={math.cos(a):{fmt}}" + elif function == "tan(a)": res, txt = math.tan(a), f"tan({fa})={math.tan(a):{fmt}}" + elif function == "sqrt(a)": res, txt = (math.sqrt(a), f"sqrt({fa})={math.sqrt(a):{fmt}}") if a >= 0 else (float('nan'), "Err:sqrt<0") + elif function == "log(a)": res, txt = (math.log(a), f"log({fa})={math.log(a):{fmt}}") if a > 0 else (float('nan'), "Err:log<=0") + elif function == "abs(a)": res, txt = abs(a), f"abs({fa})={abs(a):{fmt}}" + elif function == "round(a)": res, txt = round(a), f"round({fa})={round(a):{fmt}}" + elif function == "sin(b)": res, txt = math.sin(b), f"sin({fb})={math.sin(b):{fmt}}" + elif function == "cos(b)": res, txt = math.cos(b), f"cos({fb})={math.cos(b):{fmt}}" + elif function == "tan(b)": res, txt = math.tan(b), f"tan({fb})={math.tan(b):{fmt}}" + elif function == "sqrt(b)": res, txt = (math.sqrt(b), f"sqrt({fb})={math.sqrt(b):{fmt}}") if b >= 0 else (float('nan'), "Err:sqrt<0") + elif function == "log(b)": res, txt = (math.log(b), f"log({fb})={math.log(b):{fmt}}") if b > 0 else (float('nan'), "Err:log<=0") + elif function == "abs(b)": res, txt = abs(b), f"abs({fb})={abs(b):{fmt}}" + elif function == "round(b)": res, txt = round(b), f"round({fb})={round(b):{fmt}}" + elif function == "sin(c)": res, txt = math.sin(c), f"sin({fc})={math.sin(c):{fmt}}" + elif function == "cos(c)": res, txt = math.cos(c), f"cos({fc})={math.cos(c):{fmt}}" + elif function == "tan(c)": res, txt = math.tan(c), f"tan({fc})={math.tan(c):{fmt}}" + elif function == "sqrt(c)": res, txt = (math.sqrt(c), f"sqrt({fc})={math.sqrt(c):{fmt}}") if c >= 0 else (float('nan'), "Err:sqrt<0") + elif function == "log(c)": res, txt = (math.log(c), f"log({fc})={math.log(c):{fmt}}") if c > 0 else (float('nan'), "Err:log<=0") + elif function == "abs(c)": res, txt = abs(c), f"abs({fc})={abs(c):{fmt}}" + elif function == "round(c)": res, txt = round(c), f"round({fc})={round(c):{fmt}}" + else: res, txt = float('nan'), f"Fonction non reconnue: {function}" + except Exception as e: + res, txt = float('nan'), f"Erreur: {e}" + return (float(res), str(txt)) + +# ========================================================== +# SECTION 2 : CONVERTISSEURS +# ========================================================== +class UniversalConverterNode: + CATEGORY = f"{MAIN_CATEGORY}/Converters" + @classmethod + def INPUT_TYPES(cls): return {"optional":{"float_in":("FLOAT",{"default":0.0,"forceInput":True}),"int_in":("INT",{"default":0,"forceInput":True}),"string_in":("STRING",{"default":"","forceInput":True}),"bool_in":("BOOLEAN",{"default":False,"forceInput":True}),}} + RETURN_TYPES,RETURN_NAMES,FUNCTION=("FLOAT","INT","STRING","BOOLEAN"),("FLOAT","INT","STRING","BOOLEAN"),"convert" + def convert(self,float_in=None,int_in=None,string_in=None,bool_in=None): + out_float,out_int,out_string,out_bool=0.0,0,"",False + if float_in is not None:out_float,out_int,out_string,out_bool=float(float_in),int(float_in),str(float_in),bool(float_in) + elif int_in is not None:out_float,out_int,out_string,out_bool=float(int_in),int(int_in),str(int_in),bool(int_in) + elif string_in is not None: + out_string=str(string_in) + try:out_float,out_int=float(string_in),int(float(string_in)) + except(ValueError,TypeError):out_float,out_int=0.0,0 + out_bool=string_in.lower()in['true','1','t','y','yes','vrai','o','oui'] + elif bool_in is not None: + out_bool,out_float,out_int,out_string=bool(bool_in),1.0 if bool_in else 0.0,1 if bool_in else 0,str(bool_in) + return(out_float,out_int,out_string,out_bool) + +class ManualRateConverterNode: + CATEGORY = f"{MAIN_CATEGORY}/Converters" + RETURN_TYPES, RETURN_NAMES, FUNCTION = ("FLOAT", "STRING"), ("result", "output_str"), "calculate" + OPERATION_CHOICES = ["Multiplier par le taux", "Diviser par le taux"] + @classmethod + def INPUT_TYPES(cls): return {"required": {"amount": ("FLOAT", {"default": 100.0}), "manual_rate": ("FLOAT", {"default": 1.07, "step": 0.0001}), "operation": (cls.OPERATION_CHOICES,)}} + def calculate(self, amount, manual_rate, operation): + if operation == self.OPERATION_CHOICES[0]: result = amount * manual_rate; txt = f"{amount:.2f} × {manual_rate} = {result:.2f}" + else: + if manual_rate == 0: return (float('nan'), "Erreur: Taux ne peut pas être zéro.") + result = amount / manual_rate; txt = f"{amount:.2f} ÷ {manual_rate} = {result:.2f}" + return (result, txt) + +class LengthConverterNode: + CATEGORY = f"{MAIN_CATEGORY}/Converters" + UNITS = {"Mètre (m)": 1.0, "Centimètre (cm)": 0.01, "Millimètre (mm)": 0.001, "Kilomètre (km)": 1000.0, "Décimètre (dm)": 0.1, "Micromètre (µm)": 1e-6, "Nanomètre (nm)": 1e-9, "Angstrom (Å)": 1e-10, "Picomètre (pm)": 1e-12, "Femtomètre (fm)": 1e-15, "Pouce (in)": 0.0254, "Pied (ft)": 0.3048, "Yard (yd)": 0.9144, "Mile (mi)": 1609.344, "Mille marin (NM)": 1852.0, "Année-lumière (al)": 9.461e15, "Parsec (pc)": 3.086e16, "Unité Astronomique (ua)": 1.496e11, "Rayon terrestre (RT)": 6.371e6, "Longueur de Planck (lₚ)": 1.616e-35, "Lieue (4km)": 4000.0, "Toise (1.95m)": 1.949, "Aune (1.2m)": 1.188} + RETURN_TYPES, RETURN_NAMES, FUNCTION = ("FLOAT", "STRING"), ("result", "output_str"), "convert" + @classmethod + def INPUT_TYPES(cls): return {"required": {"value": ("FLOAT", {"default": 1.0}), "from_unit": (list(cls.UNITS.keys()),), "to_unit": (list(cls.UNITS.keys()),)}} + def convert(self, value, from_unit, to_unit): + result = (value * self.UNITS[from_unit]) / self.UNITS[to_unit] + return (result, f"{value:g} {from_unit.split(' (')[0]} = {result:g} {to_unit.split(' (')[0]}") + +class MassConverterNode: + CATEGORY = f"{MAIN_CATEGORY}/Converters" + UNITS = {"Gramme (g)": 1.0, "Kilogramme (kg)": 1000.0, "Milligramme (mg)": 0.001, "Microgramme (µg)": 1e-6, "Tonne (t)": 1_000_000.0, "Livre (lb)": 453.592, "Once (oz)": 28.3495, "Grain (gr)": 0.0648, "Stone (st)": 6350.29, "Tonne UK (long ton)": 1_016_050.0, "Tonne US (short ton)": 907_185.0} + RETURN_TYPES, RETURN_NAMES, FUNCTION = ("FLOAT", "STRING"), ("result", "output_str"), "convert" + @classmethod + def INPUT_TYPES(cls): return {"required": {"value": ("FLOAT", {"default": 1.0}), "from_unit": (list(cls.UNITS.keys()),), "to_unit": (list(cls.UNITS.keys()),)}} + def convert(self, value, from_unit, to_unit): + result = (value * self.UNITS[from_unit]) / self.UNITS[to_unit] + return (result, f"{value:g} {from_unit.split(' (')[0]} = {result:g} {to_unit.split(' (')[0]}") + +class VolumeConverterNode: + CATEGORY = f"{MAIN_CATEGORY}/Converters" + UNITS = {"Litre (L)": 1.0, "Millilitre (ml/cc)": 0.001, "Mètre cube (m³)": 1000.0, "Centilitre (cL)": 0.01, "Décilitre (dL)": 0.1, "Hectolitre (hL)": 100.0, "Gallon US (gal)": 3.78541, "Gallon Impérial (gal)": 4.54609, "Quart US (qt)": 0.946353, "Pinte US (pt)": 0.473176, "Pinte Impériale (pt)": 0.568261, "Cup US": 0.236588, "Fluid Ounce US (fl oz)": 0.0295735, "Fluid Ounce Impérial (fl oz)": 0.0284131, "Cuillère à soupe (15ml)": 0.015, "Cuillère à café (5ml)": 0.005, "Baril de pétrole (bbl)": 158.987, "Stère (bois)": 1000.0, "Pied cube (ft³)": 28.3168, "Pouce cube (in³)": 0.0163871} + RETURN_TYPES, RETURN_NAMES, FUNCTION = ("FLOAT", "STRING"), ("result", "output_str"), "convert" + @classmethod + def INPUT_TYPES(cls): return {"required": {"value": ("FLOAT", {"default": 1.0}), "from_unit": (list(cls.UNITS.keys()),), "to_unit": (list(cls.UNITS.keys()),)}} + def convert(self, value, from_unit, to_unit): + result = (value * self.UNITS[from_unit]) / self.UNITS[to_unit] + return (result, f"{value:g} {from_unit.split(' (')[0]} = {result:g} {to_unit.split(' (')[0]}") + +class DataConverterNode: + CATEGORY = f"{MAIN_CATEGORY}/Converters" + UNITS = {"Octet (Byte)": 1.0, "Bit (b)": 1/8, "Kilo-octet (KB, 10³ B)": 1000.0, "Méga-octet (MB, 10⁶ B)": 1e6, "Giga-octet (GB, 10⁹ B)": 1e9, "Téra-octet (TB, 10¹² B)": 1e12, "Péta-octet (PB, 10¹⁵ B)": 1e15, "Kibioctet (KiB, 2¹⁰ B)": 1024.0, "Mébioctet (MiB, 2²⁰ B)": 1024.0**2, "Gibioctet (GiB, 2³⁰ B)": 1024.0**3, "Tébioctet (TiB, 2⁴⁰ B)": 1024.0**4, "Pébioctet (PiB, 2⁵⁰ B)": 1024.0**5} + RETURN_TYPES, RETURN_NAMES, FUNCTION = ("FLOAT", "STRING"), ("result", "output_str"), "convert" + @classmethod + def INPUT_TYPES(cls): return {"required": {"value": ("FLOAT", {"default": 1.0}), "from_unit": (list(cls.UNITS.keys()),), "to_unit": (list(cls.UNITS.keys()),)}} + def convert(self, value, from_unit, to_unit): + result = (value * self.UNITS[from_unit]) / self.UNITS[to_unit] + return (result, f"{value:g} {from_unit.split(' (')[0]} = {result:g} {to_unit.split(' (')[0]}") + +class TimeConverterNode: + CATEGORY = f"{MAIN_CATEGORY}/Converters" + UNITS = {"Seconde (s)": 1.0, "Milliseconde (ms)": 0.001, "Microseconde (µs)": 1e-6, "Nanoseconde (ns)": 1e-9, "Picoseconde (ps)": 1e-12, "Temps de Planck (tₚ)": 5.391e-44, "Minute (min)": 60.0, "Heure (h)": 3600.0, "Jour (d)": 86400.0, "Semaine (sem)": 604800.0, "Mois (30.44j)": 2629746.0, "Année (365.24j)": 31556952.0, "Année julienne (365.25j)": 31557600.0, "Décennie": 315569520.0, "Siècle": 3155695200.0} + RETURN_TYPES, RETURN_NAMES, FUNCTION = ("FLOAT", "STRING"), ("result", "output_str"), "convert" + @classmethod + def INPUT_TYPES(cls): return {"required": {"value": ("FLOAT", {"default": 1.0}), "from_unit": (list(cls.UNITS.keys()),), "to_unit": (list(cls.UNITS.keys()),)}} + def convert(self, value, from_unit, to_unit): + result = (value * self.UNITS[from_unit]) / self.UNITS[to_unit] + return (result, f"{value:g} {from_unit.split(' (')[0]} = {result:g} {to_unit.split(' (')[0]}") + +# ================================================= +# ENREGISTREMENT DE TOUS LES NŒUDS POUR COMFYUI +# ================================================= +NODE_CLASS_MAPPINGS = { + "ScientificCalculatorTri": ScientificCalculatorTriNode, + "UniversalConverter": UniversalConverterNode, + "ManualRateConverter": ManualRateConverterNode, + "LengthConverter": LengthConverterNode, + "MassConverter": MassConverterNode, + "VolumeConverter": VolumeConverterNode, + "DataConverter": DataConverterNode, + "TimeConverter": TimeConverterNode, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "ScientificCalculatorTri": "🧮 Calculatrice Scientifique", + "UniversalConverter": "🔧 Convertisseur Universel", + "ManualRateConverter": "💱 Convertisseur Manuel (Taux)", + "LengthConverter": "📏 Convertisseur de Longueur", + "MassConverter": "⚖️ Convertisseur de Masse", + "VolumeConverter": "💧 Convertisseur de Volume", + "DataConverter": "💾 Convertisseur de Données", + "TimeConverter": "⏱️ Convertisseur de Temps", +} \ No newline at end of file