{
  "Uuid": "e67a43f6-724b-48d4-aee5-9a27dffeeea6",
  "IsCustomNode": false,
  "Description": "",
  "Name": "Dynamo - Parametric Cube from Python Script",
  "ElementResolver": {
    "ResolutionMap": {}
  },
  "Inputs": [],
  "Outputs": [],
  "Nodes": [
    {
      "ConcreteType": "PythonNodeModels.PythonNode, PythonNodeModels",
      "Code": "# ParametricBox_Dynamo3.py\r\n# Dynamo 3.x Python Script node (CPython3 / PythonNet3 engine), Revit 2022+\r\n#\r\n# Builds a parametric Generic Model family: a rectangular box whose Length,\r\n# Width and Height are family type parameters. Box faces are locked to\r\n# reference planes; the planes are dimensioned and labeled, so the family\r\n# flexes in Family Types / Type Properties.\r\n#\r\n# INPUTS (wire to the Python node):\r\n#   IN[0]  Length       number, in the project's length units (e.g. 4 for 4 ft, 1200 for 1200 mm)\r\n#   IN[1]  Width        number, project length units\r\n#   IN[2]  Height       number, project length units\r\n#   IN[3]  Folder       string, folder to save the .rfa in (blank / null -> Documents)\r\n#   IN[4]  FamilyName   string (blank / null -> \"ParametricBox\")\r\n#   IN[5]  Run          boolean toggle; set True to build\r\n#\r\n# OUTPUT:\r\n#   OUT    status message and saved .rfa path\r\n\r\nimport clr\r\nimport os\r\nimport uuid\r\n\r\nclr.AddReference(\"RevitAPI\")\r\nfrom Autodesk.Revit.DB import (\r\n    BuiltInParameter, CurveArray, CurveArrArray, Extrusion, FamilySource,\r\n    FilteredElementCollector, GeometryInstance, GroupTypeId, IFamilyLoadOptions,\r\n    Level, Line, Options, Plane, PlanarFace, ReferenceArray, ReferencePlane,\r\n    SaveAsOptions, SketchPlane, Solid, SpecTypeId, Transaction, UnitUtils,\r\n    UnitTypeId, View, ViewPlan, ViewType, XYZ,\r\n)\r\n\r\nclr.AddReference(\"RevitServices\")\r\nfrom RevitServices.Persistence import DocumentManager\r\nfrom RevitServices.Transactions import TransactionManager\r\n\r\n\r\n# ── Inputs ──────────────────────────────────────────────────────────────────\r\ndef _in(i, default=None):\r\n    try:\r\n        v = IN[i]\r\n        return default if v is None or v == \"\" else v\r\n    except IndexError:\r\n        return default\r\n\r\nlength_in = float(_in(0, 4.0))\r\nwidth_in  = float(_in(1, 2.0))\r\nheight_in = float(_in(2, 3.0))\r\nfolder    = _in(3, os.path.join(os.path.expanduser(\"~\"), \"Documents\"))\r\nfam_name  = _in(4, \"ParametricBox\")\r\nrun       = bool(_in(5, False))\r\n\r\nuiapp = DocumentManager.Instance.CurrentUIApplication\r\napp   = uiapp.Application\r\ndoc   = DocumentManager.Instance.CurrentDBDocument   # the open project (or family)\r\n\r\n\r\n# ── Units: convert project display units -> Revit internal feet ─────────────\r\ndef to_internal(value):\r\n    try:\r\n        unit = doc.GetUnits().GetFormatOptions(SpecTypeId.Length).GetUnitTypeId()\r\n        return UnitUtils.ConvertToInternalUnits(value, unit)\r\n    except Exception:\r\n        return value  # assume feet if no document units are available\r\n\r\n\r\n# ── Helpers ────────────────────────────────────────────────────────────────\r\ndef find_template():\r\n    root = app.FamilyTemplatePath\r\n    for name in (\"Generic Model.rft\", \"Metric Generic Model.rft\"):\r\n        direct = os.path.join(root, name)\r\n        if os.path.isfile(direct):\r\n            return direct\r\n        if os.path.isdir(root):\r\n            for dirpath, _, files in os.walk(root):\r\n                if name in files:\r\n                    return os.path.join(dirpath, name)\r\n    return None\r\n\r\n\r\ndef collect(fdoc, cls):\r\n    return list(FilteredElementCollector(fdoc).OfClass(cls).ToElements())\r\n\r\n\r\ndef ref_array(*refs):\r\n    ra = ReferenceArray()\r\n    for r in refs:\r\n        ra.Append(r)\r\n    return ra\r\n\r\n\r\ndef face_by_normal(extrusion, normal):\r\n    opts = Options()\r\n    opts.ComputeReferences = True\r\n    for go in extrusion.get_Geometry(opts):\r\n        if isinstance(go, Solid):\r\n            for f in go.Faces:\r\n                if isinstance(f, PlanarFace) and f.FaceNormal.IsAlmostEqualTo(normal):\r\n                    return f.Reference\r\n    raise Exception(\"Box face with normal {} not found\".format(normal))\r\n\r\n\r\ndef lock(fc, view, ref_a, ref_b):\r\n    a = fc.NewAlignment(view, ref_a, ref_b)\r\n    a.IsLocked = True\r\n    return a\r\n\r\n\r\n# Family load options (unique namespace so the node can be re-run in one session)\r\nclass _OverwriteLoadOptions(IFamilyLoadOptions):\r\n    __namespace__ = \"ParametricBox_\" + uuid.uuid4().hex\r\n\r\n    def OnFamilyFound(self, familyInUse, overwriteParameterValues):\r\n        return (True, True)\r\n\r\n    def OnSharedFamilyFound(self, sharedFamily, familyInUse, source, overwriteParameterValues):\r\n        return (True, FamilySource.Family, True)\r\n\r\n\r\n# ── Family builder ─────────────────────────────────────────────────────────\r\ndef build_family(fam, L, W, H):\r\n    fm = fam.FamilyManager\r\n    fc = fam.FamilyCreate\r\n\r\n    plans = [v for v in collect(fam, ViewPlan) if not v.IsTemplate]\r\n    plan_view = next((v for v in plans if v.Name == \"Ref. Level\"), plans[0])\r\n\r\n    elevs = [v for v in collect(fam, View) if not v.IsTemplate and v.ViewType == ViewType.Elevation]\r\n    front_view = next((v for v in elevs if v.Name == \"Front\"), elevs[0])\r\n\r\n    ref_level = collect(fam, Level)[0]\r\n    rps = {rp.Name: rp for rp in collect(fam, ReferencePlane)}\r\n    center_lr = rps[\"Center (Left/Right)\"]\r\n    center_fb = rps[\"Center (Front/Back)\"]\r\n\r\n    t = Transaction(fam, \"Build parametric box\")\r\n    t.Start()\r\n    try:\r\n        # Parameters\r\n        p_len = fm.AddParameter(\"Length\", GroupTypeId.Geometry, SpecTypeId.Length, False)\r\n        p_wid = fm.AddParameter(\"Width\",  GroupTypeId.Geometry, SpecTypeId.Length, False)\r\n        p_hgt = fm.AddParameter(\"Height\", GroupTypeId.Geometry, SpecTypeId.Length, False)\r\n        if fm.CurrentType is None:\r\n            fm.NewType(\"Default\")\r\n        fm.Set(p_len, L)\r\n        fm.Set(p_wid, W)\r\n        fm.Set(p_hgt, H)\r\n\r\n        # Reference planes\r\n        ext = 10.0\r\n        Z, Y = XYZ.BasisZ, XYZ.BasisY\r\n        rp_left  = fc.NewReferencePlane(XYZ(-L / 2, -ext, 0), XYZ(-L / 2, ext, 0), Z, plan_view)\r\n        rp_right = fc.NewReferencePlane(XYZ( L / 2, -ext, 0), XYZ( L / 2, ext, 0), Z, plan_view)\r\n        rp_front = fc.NewReferencePlane(XYZ(-ext, -W / 2, 0), XYZ(ext, -W / 2, 0), Z, plan_view)\r\n        rp_back  = fc.NewReferencePlane(XYZ(-ext,  W / 2, 0), XYZ(ext,  W / 2, 0), Z, plan_view)\r\n        rp_top   = fc.NewReferencePlane(XYZ(-ext, 0, H), XYZ(ext, 0, H), Y, front_view)\r\n        rp_left.Name, rp_right.Name = \"Left\", \"Right\"\r\n        rp_front.Name, rp_back.Name, rp_top.Name = \"Front\", \"Back\", \"Top\"\r\n\r\n        # Box extrusion\r\n        pts = [XYZ(-L / 2, -W / 2, 0), XYZ(L / 2, -W / 2, 0),\r\n               XYZ( L / 2,  W / 2, 0), XYZ(-L / 2, W / 2, 0)]\r\n        loop = CurveArray()\r\n        for i in range(4):\r\n            loop.Append(Line.CreateBound(pts[i], pts[(i + 1) % 4]))\r\n        profile = CurveArrArray()\r\n        profile.Append(loop)\r\n        sketch = SketchPlane.Create(fam, Plane.CreateByNormalAndOrigin(XYZ.BasisZ, XYZ.Zero))\r\n        box = fc.NewExtrusion(True, profile, sketch, H)\r\n        fam.Regenerate()\r\n\r\n        # Lock faces to planes\r\n        lock(fc, plan_view,  rp_left.GetReference(),  face_by_normal(box, XYZ(-1, 0, 0)))\r\n        lock(fc, plan_view,  rp_right.GetReference(), face_by_normal(box, XYZ( 1, 0, 0)))\r\n        lock(fc, plan_view,  rp_front.GetReference(), face_by_normal(box, XYZ(0, -1, 0)))\r\n        lock(fc, plan_view,  rp_back.GetReference(),  face_by_normal(box, XYZ(0,  1, 0)))\r\n        lock(fc, front_view, rp_top.GetReference(),   face_by_normal(box, XYZ(0, 0,  1)))\r\n        lock(fc, front_view, ref_level.GetPlaneReference(), face_by_normal(box, XYZ(0, 0, -1)))\r\n\r\n        # Dimensions + labels\r\n        off = 1.5\r\n        d = fc.NewDimension(plan_view,\r\n            Line.CreateBound(XYZ(-L / 2, -W / 2 - off, 0), XYZ(L / 2, -W / 2 - off, 0)),\r\n            ref_array(rp_left.GetReference(), rp_right.GetReference()))\r\n        d.FamilyLabel = p_len\r\n        d = fc.NewDimension(plan_view,\r\n            Line.CreateBound(XYZ(-L / 2, -W / 2 - off / 2, 0), XYZ(L / 2, -W / 2 - off / 2, 0)),\r\n            ref_array(rp_left.GetReference(), center_lr.GetReference(), rp_right.GetReference()))\r\n        d.AreSegmentsEqual = True\r\n\r\n        d = fc.NewDimension(plan_view,\r\n            Line.CreateBound(XYZ(L / 2 + off, -W / 2, 0), XYZ(L / 2 + off, W / 2, 0)),\r\n            ref_array(rp_front.GetReference(), rp_back.GetReference()))\r\n        d.FamilyLabel = p_wid\r\n        d = fc.NewDimension(plan_view,\r\n            Line.CreateBound(XYZ(L / 2 + off / 2, -W / 2, 0), XYZ(L / 2 + off / 2, W / 2, 0)),\r\n            ref_array(rp_front.GetReference(), center_fb.GetReference(), rp_back.GetReference()))\r\n        d.AreSegmentsEqual = True\r\n\r\n        d = fc.NewDimension(front_view,\r\n            Line.CreateBound(XYZ(L / 2 + off, 0, 0), XYZ(L / 2 + off, 0, H)),\r\n            ref_array(ref_level.GetPlaneReference(), rp_top.GetReference()))\r\n        d.FamilyLabel = p_hgt\r\n\r\n        fam.Regenerate()\r\n        t.Commit()\r\n    except Exception:\r\n        if t.HasStarted() and not t.HasEnded():\r\n            t.RollBack()\r\n        raise\r\n\r\n\r\n# ── Main ────────────────────────────────────────────────────────────────────\r\nif not run:\r\n    OUT = \"Set Run (IN[5]) to True to build the family.\"\r\nelse:\r\n    fam_doc = None\r\n    try:\r\n        template = find_template()\r\n        if template is None:\r\n            raise Exception(\"Generic Model template not found under \" + app.FamilyTemplatePath)\r\n\r\n        L, W, H = to_internal(length_in), to_internal(width_in), to_internal(height_in)\r\n        if min(L, W, H) <= 0:\r\n            raise Exception(\"Length, Width and Height must all be greater than zero.\")\r\n\r\n        # Dynamo keeps a transaction open on the active document; close it so\r\n        # the new family can be loaded back into the project.\r\n        TransactionManager.Instance.ForceCloseTransaction()\r\n\r\n        fam_doc = app.NewFamilyDocument(template)\r\n        build_family(fam_doc, L, W, H)\r\n\r\n        if not os.path.isdir(folder):\r\n            os.makedirs(folder)\r\n        path = os.path.join(folder, fam_name + \".rfa\")\r\n        sao = SaveAsOptions()\r\n        sao.OverwriteExistingFile = True\r\n        fam_doc.SaveAs(path, sao)\r\n\r\n        loaded = False\r\n        if doc is not None and not doc.IsFamilyDocument:\r\n            fam_doc.LoadFamily(doc, _OverwriteLoadOptions())\r\n            loaded = True\r\n\r\n        OUT = \"Saved: {}{}\".format(path, \"  |  Loaded into project\" if loaded else \"\")\r\n    except Exception as e:\r\n        OUT = \"Error: {}\".format(e)\r\n    finally:\r\n        if fam_doc is not None and fam_doc.IsValidObject:\r\n            fam_doc.Close(False)",
      "Engine": "PythonNet3",
      "VariableInputPorts": true,
      "Id": "d4f8e630cfa34ce485e0ba7242393859",
      "NodeType": "PythonScriptNode",
      "Inputs": [
        {
          "Id": "9bd787c17df44b0c90d2f8de89a98cf8",
          "Name": "IN[0]",
          "Description": "Input #0",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        },
        {
          "Id": "604b0e6616524896b1fca6d6eadcad67",
          "Name": "IN[1]",
          "Description": "Input #1",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        },
        {
          "Id": "3c3e57883c114442af5bbdaf86c9277e",
          "Name": "IN[2]",
          "Description": "Input #2",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        },
        {
          "Id": "c9af7a60e7744fc283343cf560ea1c83",
          "Name": "IN[3]",
          "Description": "Input #3",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        },
        {
          "Id": "d9f33125f1f241128b05a409562d128f",
          "Name": "IN[4]",
          "Description": "Input #4",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        },
        {
          "Id": "82ada1ccaca34b37aeffd2919c5417ce",
          "Name": "IN[5]",
          "Description": "Input #5",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Outputs": [
        {
          "Id": "471dafd008fe42d2a600bcef056fda82",
          "Name": "OUT",
          "Description": "Result of the python script",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Replication": "Disabled",
      "Description": "Runs an embedded Python script."
    },
    {
      "ConcreteType": "CoreNodeModels.Input.BoolSelector, CoreNodeModels",
      "Id": "5b25493ad66e4693b3f89e69a9c9a17c",
      "NodeType": "BooleanInputNode",
      "Inputs": [],
      "Outputs": [
        {
          "Id": "950e9fe0499a4985bd2d5957cf96b86e",
          "Name": "",
          "Description": "Boolean",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Replication": "Disabled",
      "Description": "Enables selection between True and False",
      "InputValue": true
    },
    {
      "ConcreteType": "CoreNodeModels.Input.IntegerSlider, CoreNodeModels",
      "NumberType": "Integer",
      "MaximumValue": 100,
      "MinimumValue": 0,
      "StepValue": 1,
      "Id": "5f1b876c4cb044f6876654b84512664d",
      "NodeType": "NumberInputNode",
      "Inputs": [],
      "Outputs": [
        {
          "Id": "a9d7150a5890460bacf63eb87cc58de5",
          "Name": "",
          "Description": "Int64",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Replication": "Disabled",
      "Description": "Produces integer values",
      "InputValue": 20
    },
    {
      "ConcreteType": "CoreNodeModels.Input.IntegerSlider, CoreNodeModels",
      "NumberType": "Integer",
      "MaximumValue": 100,
      "MinimumValue": 0,
      "StepValue": 1,
      "Id": "f936665b50ac409796b4569226c57e88",
      "NodeType": "NumberInputNode",
      "Inputs": [],
      "Outputs": [
        {
          "Id": "073a210964454de28c0b1258be18db22",
          "Name": "",
          "Description": "Int64",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Replication": "Disabled",
      "Description": "Produces integer values",
      "InputValue": 20
    },
    {
      "ConcreteType": "CoreNodeModels.Input.IntegerSlider, CoreNodeModels",
      "NumberType": "Integer",
      "MaximumValue": 100,
      "MinimumValue": 0,
      "StepValue": 1,
      "Id": "b2303647c8a64d26bd3366eb087e11f3",
      "NodeType": "NumberInputNode",
      "Inputs": [],
      "Outputs": [
        {
          "Id": "1bd28f3853cc4bfb939afcfd5eb2475d",
          "Name": "",
          "Description": "Int64",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Replication": "Disabled",
      "Description": "Produces integer values",
      "InputValue": 28
    }
  ],
  "Connectors": [
    {
      "Start": "950e9fe0499a4985bd2d5957cf96b86e",
      "End": "82ada1ccaca34b37aeffd2919c5417ce",
      "Id": "33da687d94a14fbe99a2f47a23578f98",
      "IsHidden": "False"
    },
    {
      "Start": "a9d7150a5890460bacf63eb87cc58de5",
      "End": "9bd787c17df44b0c90d2f8de89a98cf8",
      "Id": "364be40fabff47e68e6fae247f1d22c4",
      "IsHidden": "False"
    },
    {
      "Start": "073a210964454de28c0b1258be18db22",
      "End": "604b0e6616524896b1fca6d6eadcad67",
      "Id": "9e7a9b2ccf9b4d6c89575e1004098083",
      "IsHidden": "False"
    },
    {
      "Start": "1bd28f3853cc4bfb939afcfd5eb2475d",
      "End": "3c3e57883c114442af5bbdaf86c9277e",
      "Id": "6f3912759da24f34838dab3d5ac5eee9",
      "IsHidden": "False"
    }
  ],
  "Dependencies": [],
  "NodeLibraryDependencies": [],
  "EnableLegacyPolyCurveBehavior": null,
  "Thumbnail": "",
  "GraphDocumentationURL": null,
  "ExtensionWorkspaceData": [
    {
      "ExtensionGuid": "28992e1d-abb9-417f-8b1b-05e053bee670",
      "Name": "Properties",
      "Version": "4.2",
      "Data": {}
    },
    {
      "ExtensionGuid": "DFBD9CC0-DB40-457A-939E-8C8555555A9D",
      "Name": "Generative Design",
      "Version": "10.3",
      "Data": {}
    }
  ],
  "Author": "",
  "Linting": {
    "activeLinter": "None",
    "activeLinterId": "7b75fb44-43fd-4631-a878-29f4d5d8399a",
    "warningCount": 0,
    "errorCount": 0
  },
  "Bindings": [],
  "View": {
    "Dynamo": {
      "ScaleFactor": 1.0,
      "HasRunWithoutCrash": true,
      "IsVisibleInDynamoLibrary": true,
      "Version": "4.2.1.5887",
      "RunType": "Automatic",
      "RunPeriod": "1000"
    },
    "Camera": {
      "Name": "_Background Preview",
      "EyeX": -17.0,
      "EyeY": 24.0,
      "EyeZ": 50.0,
      "LookX": 12.0,
      "LookY": -13.0,
      "LookZ": -58.0,
      "UpX": 0.0,
      "UpY": 1.0,
      "UpZ": 0.0
    },
    "ConnectorPins": [],
    "NodeViews": [
      {
        "Id": "d4f8e630cfa34ce485e0ba7242393859",
        "Name": "Python Script",
        "IsSetAsInput": false,
        "IsSetAsOutput": false,
        "Excluded": false,
        "ShowGeometry": true,
        "X": 562.25,
        "Y": 78.25
      },
      {
        "Id": "5b25493ad66e4693b3f89e69a9c9a17c",
        "Name": "Boolean",
        "IsSetAsInput": false,
        "IsSetAsOutput": false,
        "Excluded": false,
        "ShowGeometry": true,
        "X": 154.5,
        "Y": 381.5
      },
      {
        "Id": "5f1b876c4cb044f6876654b84512664d",
        "Name": "Integer Slider",
        "IsSetAsInput": false,
        "IsSetAsOutput": false,
        "Excluded": false,
        "ShowGeometry": true,
        "X": 83.0,
        "Y": 6.0
      },
      {
        "Id": "f936665b50ac409796b4569226c57e88",
        "Name": "Integer Slider",
        "IsSetAsInput": false,
        "IsSetAsOutput": false,
        "Excluded": false,
        "ShowGeometry": true,
        "X": 92.0,
        "Y": 124.0
      },
      {
        "Id": "b2303647c8a64d26bd3366eb087e11f3",
        "Name": "Integer Slider",
        "IsSetAsInput": false,
        "IsSetAsOutput": false,
        "Excluded": false,
        "ShowGeometry": true,
        "X": 98.0,
        "Y": 245.5
      }
    ],
    "Annotations": [],
    "X": 0.0,
    "Y": 0.0,
    "Zoom": 1.0
  }
}