Yna_Db
October 24, 2017, 2:41pm
1
I noticed it might be useful to gather some Python related posts in one thread to make it easy to get started with some basic understanding (and also for a consultation ease purpose). This is still a work in progress and there might be some slight inconsistencies. Feel free to add your own links or Python related comments, or to PM me on anything that should be revised. This first post relates naturally to tips for LEARNING PYTHON :
http://dynamoprimer.com/en/09_Custom-Nodes/9-4_Python.html
http://dynamoprimer.com/en/09_Custom-Nodes/9-5_Python-Revit.html
http://dynamoprimer.com/en/12_Best-Practice/12-1_Scripting-Strategies.html
http://dynamoprimer.com/en/12_Best-Practice/12-3_Scripting-Reference.html
http://dynamoprimer.com/en/Appendix/A-1_resources.html
I wrote a 30 page handout for my AU class on Python for Dynamo, thought I would leave this here
This site, that popped up recently, has a lot of thoroughly explained dynamo/python examples:
Preetesh-
I was in your situation a few months ago (February) , so I know how you feel right now. All I can do is tell you how I went about learning.
Get a good book on Python basics or go to http://learnpythonthehardway.org/
Assuming you are looking to integrate with Revit - download the Revit SDK and get comfortable using the Revit API chm help file.
Download some of the better packages available (Clockwork, Grimshaw, etc), open up their nodes, look at the python scripts, and force …
Honestly, not a lot for basic stuff, and if your using packages that are written in IronPython, most of it can be ported with practically no effort. However it’s a strong-typed language and requires more configuration and know-how compared to Python (installing Visual Studio, creating the correct project, loading the API dll’s etc) which makes it un-forgiving if you’ve never coded before.
My advice would be to use Python. Its very easy to learn, you can readily examine the code written by other…
The PyRevit youtube channel is another great resource for Revit focused python learning:
However you should keep in mind that those videos are targeting the Revit python shell, which is implemented slightly differently from the Dynamo equivalent. (different ways to call the active document; no need to worry about element wrapping, because you’re not returning anything to the graph; etc.)
It seems consistent to add this other nice tutorial here:
Yna_Db
October 24, 2017, 2:43pm
2
This second post relates to IMPORTS :
Pawel,
These are the most used imports:
Thank you,
Now for the python script:
##CLR
Dynamo uses a version of python called IronPython which is capable of dealing with .NET libraries.
To interact with the Revit API we have to load an .NET assembly, a dll file named RevitAPI.dll, and for this we need the clr module from Iron Python. (http://ironpython.net/documentation/dotnet/ )
Thus the first lines of the code looks like this:
import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
##Generic collections
If we look at the ar…
Hi Hannes,
You need to import DSCore for DesignScript nodes to work and then use it like this, where the second argument is the amount of levels to flatten:
[image]
I have been doing this:
import os
appDataPath = os.getenv('APPDATA')
dynPath = appDataPath + r"\Dynamo\0.9"
if dynPath not in sys.path:
sys.path.Add(dynPath)
bbPath = appDataPath + r"\Dynamo\0.9\packages\Bumblebee\extra"
if bbPath not in sys.path:
try:
sys.path.Add(bbPath)
import bumblebee as bb
except:
import xml.etree.ElementTree as et
root = et.parse(dynPath + "\DynamoSettings.xml").getroot()
for child in root:
if child.tag == "CustomPackageFolders":
for path in child:…
Hello Nikolay!
Try this:
import sys
sys.path.append(r'C:\Program Files (x86)\IronPython 2.7\DLLs')
sys.path.append(r'C:\Program Files (x86)\IronPython 2.7\Lib')
import sqlite3
import xml
You would first have to define a path to IronPython’s librar, append it to the system path and then finally import the module.
pyt_path = r'C:\Program Files (x86)\IronPython 2.7\Lib'
sys.path.append(pyt_path)
import random
#The inputs to this node will be stored as a list in the IN variables.
dataEnteringNode = IN
#Assign your output to the OUT variable.
OUT = random.random()
Works as expected:
[image]
https://forum.dynamobim.com/t/how-to-convert-dynamo-nodes-to-python-script/13050/2
import re
dataEnteringNode = IN
html = IN[0]
exp = r’(?<=id\s)([0-9]*)'
match = re.findall(exp, html)
if match:
OUT = set(match)
else:
OUT = ‘Not Found’
Hi sajo
We know it’s overwhelming, but you are more likely to get quick responses if you break these questions down into separate posts/subjects.
In any case, I will try to touch on some of the points:
What does the first four similar lines of every script do…?
See comments below
import clr
# imports Common Language Runtime library .This will serve as a bridge between python and the .NET wold
# See this: http://ironpython.net/documentation/dotnet/dotnet.html
clr.AddReference('Proto…
https://forum.dynamobim.com/t/rounding-numbers-in-python/14671/11
This should do it:
stringlist = IN[0]
import itertools
def generate_groups(lst, n):
if not lst:
yield []
else:
for group in (((lst[0],) + xs) for xs in itertools.combinations(lst[1:], n-1)):
for groups in generate_groups([x for x in lst if x not in group], n):
yield [group] + groups
OUT = generate_groups(stringlist, 2)
Yna_Db
October 24, 2017, 3:04pm
3
This third post is about REVIT API and C# TO PYTHON :
Online Documentation for Autodesk's Revit API: 2015, 2016, 2017, 2017.1, 2018
Online Documentation for Autodesk's Revit API: 2015, 2016, 2017, 2017.1, 2018
Online Documentation for Autodesk's Revit API: 2015, 2016, 2017, 2017.1, 2018
http://thebar.cc/converting-revit-api-c-code-to-python/
Autodesk is a global leader in design and make technology, with expertise across architecture, engineering, construction, design, manufacturing, and entertainment.
http://thebuildingcoder.typepad.com/blog/about-the-author.html
Hello Rajkumar!
This article explains a lot when it comes to how dynamo works with the revit api: https://github.com/DynamoDS/Dynamo/wiki/Python-0.6.3-to-0.7.x-Migration#revitapi
The Revit API uses .NET assemblies to give access to functionalities in the program, these assemblies are .dll files. RevitAPI.dll and RevitAPIUI.dll are files that can be found in the Revit program folder.
To use .NET with python Dynamo uses a variant called IronPython. How clr and clr.AddReference work is explained…
Maybe you can try @Dimitar_Venkov s C# interpreter: (found in the package manager). I just slightly edited the code to include some references and swapped to Dynamos document manager.
[image]
//Add references
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Structure;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using Autodesk.Revit.ApplicationServices;
using Auto…
That return statement looks good and it seems to be doing what it’s supposed to inside Dynamo. Tho I’m not sure why you’re generating new lists instead of using the existing lists?
I guess your question is how to return a Dynamo element instead? You’ll need to wrap the Revit element like so:
types.Add(doc.GetElement(id).ToDSType(true) )
You could also give the new C# interpreter a try. It’s up on the package manager:
[image]
If you’re testing something specific, you could always give my Dynamic CSharp Interpreter a go:
[image]
You can find it on the package manager
Because C# is a strongly typed language you have to specify the type for every variable. That’s why it needs casting to a LocationCurve. Python on the other hand does the casting for you, so in this case you should be able to do something like this:
curve = l.Curve
Because you ask for .Curve property on a Location object Python will figure out that it needs to cast to LocationCurve and then return the Curve.
Hi sajo
We know it’s overwhelming, but you are more likely to get quick responses if you break these questions down into separate posts/subjects.
In any case, I will try to touch on some of the points:
What does the first four similar lines of every script do…?
See comments below
import clr
# imports Common Language Runtime library .This will serve as a bridge between python and the .NET wold
# See this: http://ironpython.net/documentation/dotnet/dotnet.html
clr.AddReference('Proto…
@Yna_Db
Give a man a fish and he can eat for a day.
Give a man a fishingrod and he can eat for a lifetime.
Thnx
Marcel
Yna_Db
October 24, 2017, 7:47pm
5
Totally agree
This post relates to IRONPYTHON and its LIBRARIES :
http://ironpython.net/
http://www.ironpython.info/index.php?title=Useful_Links
http://opensimulator.org/git/opensim-libs-save/IronPython/IronPython-1.1.1/Tutorial/Tutorial.htm
https://ironpython-test.readthedocs.io/en/latest/
python, ironpython
https://ironpython-test.readthedocs.io/en/latest/library/index.html
https://docs.python.org/2/library/intro.html
http://ironpython.net/documentation/dotnet/
That’s because some modules are implemented using native c# libs and reside in the IronPythonModules.dll, which is loaded automatically at startup. The rest of the ipy modules are implemented as python code (and reside in the Lib folder) and are translated to the CLR’s intermediate language every time you run the script:
[DynamoSandbox_2017-10-31_09-43-12]
Hi @Adrian_Crisan
You have to add the default ironpython library to the interpreter’s path.
Not sure why it’s not added by default (maybe @Dimitar_Venkov can shed some light?), but this should work:
[image]
Yna_Db
October 24, 2017, 8:04pm
6
This post is a selection of COMMENTED SCRIPTS :
Online Documentation for Autodesk's Revit API: 2015, 2016, 2017, 2017.1, 2018
Yna_Db is correct, it’s read only and you need to use the SetProjection Method in a python node.
Here is an example of how to use the method:
element.SetProjection(AnalyticalElementSelector.EndOrTop, StickElementProjectionY.LocationLine, StickElementProjectionZ.Bottom)
Here is a complete working code:
[image]
import clr
# Import Revit API
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Structure import *
# Import DocumentManager and TransactionManager
c…
I found another script I had written once, but forgotten about. Think it does what you want:
[image]
import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Structure import *
clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.ImportExtensions(Revit.Elements)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDo…
It was just random for debugging purposes. I believe the CreateParts method returns null. If you need to return the parts, I think you have to use PartUtils.GetAssociatedParts()
Yes, the script would be even easier if you can get the elements from an IN-port. Just replace the filtered element collector with IN[1] or something similar. Here is a modified version of the script:
import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
clr.AddReference('System')
from System.Coll…
try this: http://dp-stuff.org/room-plans-non-rectangular-viewports-revit-2014/
[image]
#dp Stuff | http://dp-stuff.org | createRoomPlanViews
#this Revit Python Script batch generates Room Plan Views
#before running the script select the rooms to generate Plan Views for
import clr
import math
clr.AddReference('RevitAPI')
clr.AddReference('RevitAPIUI')
from Autodesk.Revit.DB import *
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManage…
Here’s an easier to copy paste version of the code
import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import*
clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
views = UnwrapElement(IN[0])
ltop = UnwrapElement(IN[1])
toffset = IN[2]
lbottom = UnwrapElement(IN[3])
boffset = IN[4]
count = 0
TransactionManager.Instance.Ensur…
Probably easiest to say that just about everything directly related to how your trying to paint the walls is not correct. Here’s a script that I wrote a while back for the same kind of thing that you can look at and try to evaluate how it differs from yours. This isn’t the most robust script as it will fail if you don’t have a list (single object), or you have a multi-dimensional list, or if you have a face based wall, but it should be enough to get the point across.
[PaintWalls_python]
-Tim
Totally agree, but the main problem in the forum is that some users are still doing the work of some lazy people.
Regards,
Yna_Db
October 24, 2017, 8:13pm
8
@Organon
I can’t resist to cite your very first lesson to coding in this thread 'cause I simply love it
for users in users:
if screenshot is None:
print ("This is not a 'do my work' forum.")
elif codeSample is None:
print ("This is not a 'do my work' forum.")
elif DYNfile is None or RVTfile is None:
print ("This is not a 'do my work' forum.")
else:
print ("Ok, let's see your question.")
@Yna_Db ,
Hahaha, it’s the hard truth.
Regards,
Yna_Db
October 24, 2017, 8:18pm
10
Organon:
the hard truth
… so much easier to say with a touch of humor
Yna_Db
October 26, 2017, 7:16pm
11
To complement this IF-ELSE STATEMENT proposal, here are a few additional examples:
https://forum.dynamobim.com/t/if-list-is-empty-switch-to-another-list/14720/6
layer = IN[0]
styles=[]
for i in layer:
if i == "red" :
styles.append("0.13mm")
elif i == "yellow":
styles.append ("0.18mm")
else :
styles.append ("0.13mm")
OUT = styles
https://forum.dynamobim.com/t/wall-property-function/11685/9
https://forum.dynamobim.com/t/msgbox-and-if-statement/12993/13
you are indeed. Your code only works on a singleton (i.e. one object, or in your case, a number). As you want to provide a list you need to store your result in a list and iterate over the input list. One method is a simple loop to iterate through the list (and python has some pretty powerful built-in itertools which you should check out on python.org which are more efficient than loops):
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
#The inputs to th…
Can some one tell me what I’m doing wrong here?.
[image]
Just luckily solved it myself if anyone has the same issue here’s my solution
[Capture]
Python
# Assign curves to groups according to whether they are connected via shared start points and end points. Group numbers are not important.
# Colin McCrone, 2015-09-28
import clr
import math
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
# Determine whether two points are equal
def PointsEqual(pt1, pt2):
tolerance = 0.00001
if (math.fabs(pt1.X …
Yna_Db
November 1, 2017, 10:09pm
12
The following posts are related to PYTHON LISTS :
@staylor You have to iterate over your list of documents using a for loop. Right now you are defining doc as a list, so essentially in line 18 you are saying param_list = ListObject.FamilyManager.GetParameters() See below example, when I query the object type of the input without a for loop, it shows me the object is a list. If I query the object type of the input elements with the for loop, it returns the object type of the items in the list
[iterateoverdocs]
So, you need to put your code w…
Hi @Jordan_Billingsley - I already have a very simple Python script that accomplishes supplying a boolean output for whether or not elements are pinned are not, which I will provide below. You can use this inputting your wall elements, then using Filter.ByBoolMask with its output.
To encourage you to understand how the script works & to further learn using Python with the Revit API I will provide an explanation on how it works. if you look at the API Docs that @Nick_Boyts referenced above you w…
There’s probably a cleaner way of doing this but a simple use of append will work.
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
clr.AddReference('DSCoreNodes')
from DSCore import *
#The inputs to this node will be stored as a list in the IN variables.
dataEnteringNode = IN
items = IN[0]
inlist = IN[1]
newlist = []
for indices in inlist:
sublist = []
for i in indices:
sublist.append(items[i])
newlist.append(sublist)
#Assign your output to the O…
Strange. Works fine on my end. Have you tried closing the file and re-running it?
[image]
As an FYI, you should paste your Python code using the Preformatted Text option. Just highlight your pasted text and click </> in the toolbar above.
Yes, but I don’t think that’s what you need here. As @awilliams said, you can wrap your if statement within your for loop.
However, if you wanted to iterate through both lists simultaneously you would use:
for i, j in zip(list1, list2):
This would essentially get you paired values list1[0]/list2[0], list1[1]/list2[1], etc.
https://forum.dynamobim.com/t/lacing-on-replacing-item-on-index/16462/11
https://forum.dynamobim.com/t/help-with-group-by-key/11379/8
I have noticed that this before too, and it looks like UnwrapElement() is converting the Pyton List into a Collections.Generic.List. That’s why isintance() returns false.
[image]
@Cesar_Escalante
Using Python, option 1:
[image]
def MergeSort(lst1,lst2):
MergedList=[]
for index,i in enumerate(lst1):
MergedList.append(i+lst2[index])
MergedList[index].sort()
return MergedList
OUT=MergeSort(IN[0],IN[1])
Option 2:
[image]
MergedList=[i+IN[1][index] for index,i in enumerate(IN[0])]
for i in MergedList:
i.sort()
OUT=MergedList
Hi JN,
Have you tried setting the lacing of the custom node that you created to Longest?
Otherwise you could use a for loop in Python:
import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionM…
the function works with nulls as well :
[image]
we take each element among sublists and then find largest
i’m not sure I got this part
@ Hannes - use List Comprehensions - they are much faster than loops:
import clr
clr.AddReference(‘ProtoGeometry’)
from Autodesk.DesignScript.Geometry import *
dataEnteringNode = IN[0]
OUT = [i for sublist in IN[0] for i in sublist]
https://docs.python.org/2/tutorial/datastructures.html
Nice that you got it to work!
If you wanted to combine the lists in python you could have used zip()
for view,curves in zip(views,curvelist):
#Create curveloop
loop = CurveLoop()
for c in curves:
loop.Append(c.ToRevitType())
#Set cropregion in Transaction
TransactionManager.Instance.EnsureInTransaction(doc)
view.CropBoxActive = True
view.GetCropRegionShapeManager().SetCropShape(loop)
outputlist.append(view)
TransactionManager.Instance.TransactionTaskDone()
OUT = outputlist
I am guessing you have 2 sublists and really what you’re asking is how can you perform cross product lacing with sublist 1 against sublist 2?
Try this (this only works on a list with two sublists; if you are trying to match each item from sublist 1 with all items with sublist 2, then match each item from sublist 2 with all items from sublist 3…etc then this wont work and you should think about how to manage your data better)
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScri…
The Konrad’s function using lists comprehension:
elements = IN[0]
ProcessLists = lambda function, lists: [ProcessLists(function, item) if isinstance(item, list) else function(item) for item in lists]
sum = lambda x: x + 1
if isinstance(elements, list):
out = ProcessLists(sum, elements)
else:
out = sum(elements)
OUT = out
[image]
Try using a definition like so above your inputs…
def tolist(obj1):
if hasattr(obj1,“iter”): return obj1
else: return [obj1]
And for the input beams write the following…
beams = tolist(UnwrapElement(IN[0]))
This is what I use for inputs to ensure they are an iterable object. Very common point of failure in writing python nodes.
(I can’t seem to format the code on my mobile, but indented code after the first line naturally)
Hi @salvatoredragotta
Both versions get the ElementId using the same method, which is the .Id Property (any object that inherits from Element has it).
What’s different, is what they do with the resulting ElementId objects retrieved. The method you use, will depend what you will do with the collection of ElementIds. More details below.
The 2nd in your example, creates a list (built-in Python list ) which is a generic, container native to Python, the other creates a List, a .NET data-type.
Pyt…
Yna_Db
November 3, 2017, 10:33pm
13
These posts relate to STRINGS in Python:
you could do all that or use half a line of python code:
[Revit_2017-10-07_13-21-40]
On a side note: I assume you’re getting the trailing zeroes because you’ve converted a double value to a string. In that case you could also use this python code to get what you want straight away:
[image]
https://forum.dynamobim.com/t/combine-nested-list-items-into-one-multiline-string/13158/2
https://forum.dynamobim.com/t/list-replace-multi-elements/13010/7
@3Pinter
You can also use a line of code in python:
OUT=[i.split('- ')[1] if '- ' in i else i for i in IN[0]]
or without list comprehension:
OUT=[]
for i in IN[0]:
if '- ' in i:
OUT.append(i.split('- ')[1])
else:
OUT.append(i)
[image]
@yyi is right. The problem here is that the code doesn’t accept different types of inputs (single integer or list of integers, single list of strings or nested lists, etc.)
What I would do is adapt the code to the worst case scenario for each case, being this when you have nested list of strings for the first one and a list of integers for the second. Pay attention to the if statement using isinstance().
Remove Characters
import re
inputList = IN[0]
outputList = []
def RemoveCharacters(list…
python, regex, string
Yna_Db
November 14, 2017, 10:31pm
14
This post is about FUNCTIONS :
A couple of things:
In python def means that a function is defined. Nothing will happen before you call the function.
I think you need to convert the reference plane to a sketch plane before it can be used to set the workplane.
insert is a function and should use (), not [].
https://forum.dynamobim.com/t/passing-nodes-as-functions-in-python-script/16324/4
Tomasz,
The ProcessList() method takes care of zipping the two arguments from lists. You don’t have to perform that inside your Intersection() method. Basically change it to:
def Intersection(elA, elB):
return elA.DoesIntersect(elB)
Then call it like so:
OUT = ProcessList(Intersection, ducts, walls)
Make sure that the two lists ducts and walls are exactly of the same structure. It works like this:
ducts = [duct1, duct2, duct3]
walls = [wall1, wall2, wall3]
Calling ProcessList(Interse…
@yyi is right. The problem here is that the code doesn’t accept different types of inputs (single integer or list of integers, single list of strings or nested lists, etc.)
What I would do is adapt the code to the worst case scenario for each case, being this when you have nested list of strings for the first one and a list of integers for the second. Pay attention to the if statement using isinstance().
Remove Characters
import re
inputList = IN[0]
outputList = []
def RemoveCharacters(list…
In python it may actually work faster than to convert to some text showing the type name and then testing on that. You could do the same thing there, but there’s a better alternative.
Standard python comes with the isinstance function. As an example:
[image]
In that the Python Code:
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
#The inputs to this node will be stored as a list in the IN variables.
#Assign your output to the OUT variable.
OUT = map…
I took another look at this, but had to turn to python to make it work:
[image]
(Edit: Python is updated, center and bottom had swapped places)
import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB.Structure import *
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
#Functions for list handling
def ProcessList(_func…
@Chad_Clary ,
This is indeed a little tricky because these elements don’t have a Parameter called name, but instead its a property. Here’s how you can handle it.
[image]
Here’s the code that’s inside of Python:
# Copyright(c) 2016, Konrad K Sobon
# @arch_laboratory, http://archi-lab.net
# Import Element wrapper extension methods
import clr
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
# Import DocumentManager and TransactionManager
clr.AddReference("Revit…
I wrote a 30 page handout for my AU class on Python for Dynamo, thought I would leave this here
Yna_Db
November 17, 2017, 8:11pm
16
This is a very good document that let you get started with a clear view on the possibilities opened by this language, along with a few tips that will make it all fun from the start. I add a link into the introductory post so it can be found easily. Thanks a lot
Yna_Db
November 17, 2017, 10:13pm
18
It seems consistent to add this other nice tutorial here:
VIDEO
For a non-programmer like myself, which would be the natural course then?
I am assuming first learning the basics of Python, for which I am following some course on edX and reading some tutorials, but this doesn’t seem to relate to geometric or graphical capabilities at all (Which I assume is because of the introductory level of the course).
So what would be next after learning the basic stuff?
How can I connect Revit or Dynamo or both to Python? This is like the missing link for me. I am sure you will guide me, and many others after me, on the right path.
Thank you all.
For most Dynamo users, I would say that Python only really shines when dealing with lists (especially of multiple levels) and Revit API work (which are things usually done in C# but can be done in python nodes). For geometric capabilities, I can only think of python being useful for its import libraries, math functions/capabilities, looping, or if you can find a python function online that already has what you want.
Someone please correct me if I am wrong though.