diff --git a/rocolib/utils/roco2sim/Node.py b/rocolib/utils/roco2sim/Node.py
new file mode 100644
index 0000000000000000000000000000000000000000..b3ab00588956a234f06350e6c710723614cb6b2c
--- /dev/null
+++ b/rocolib/utils/roco2sim/Node.py
@@ -0,0 +1,147 @@
+import numpy as np
+
+from rocolib.utils.roco2sim.material_data import Material as mat, MaterialConst as mc
+from rocolib.utils.roco2sim.material_data import materialData as md
+from rocolib.utils.roco2sim.format_3d import format_wrl as wrl
+from rocolib.utils.roco2sim.format_3d import wrl2stl
+
+"""BaseNodes (No children allowed)"""
+
+class AppearanceNode:
+    """appearance"""
+
+    def __init__(self, material: mat = md[mc.MAT_DEFAULT]):
+        # if no material specified, use default material
+        self._material = material
+
+    def getMaterial(self):
+        return self._material
+
+    def getRGB(self):
+        return self._material.rgb
+
+    def getRoughness(self):
+        return self._material.roughness
+
+    def getReflection(self):
+        return self._material.reflection
+
+    def getName(self):
+        return self._material.name
+
+    def getDescription(self):
+        return self._material.description
+
+
+class PhysicsNode:
+    def __init__(self, material: mat = md[mc.MAT_DEFAULT], mass=0.3, contact=None):
+        self.material = material
+        self.mass = mass
+        self.contact = contact
+
+    def isMassSpecified(self):
+        return self.mass is not None
+
+
+
+class DeviceNode:
+    def __int__(self, name=None, type=None, options=None):
+        if options is None:
+            options = {}
+        self.options = options
+        self.name = name
+        self.type = type
+
+
+"""Composable Nodes (can have children)"""
+class BaseComposableNode:
+    def __init__(self, name, children=None):
+        self.name = name
+        if children is None:
+            children = []
+        self.children = children
+
+
+class ShapeNode(BaseComposableNode):
+    """Shape node holds faces and appearance information.
+    If solidified, then includes physics"""
+
+    def __init__(self, name, wrl: wrl, trans, appearance: AppearanceNode = None, solidify: PhysicsNode = None,
+                 children=None):
+        super().__init__(name, children)
+        self.trans = trans
+        self.wrl = wrl
+        self.solid = False
+
+
+        # if no appearance specified, default
+        if appearance is None:
+            self.appearance = AppearanceNode()
+        else:
+            self.appearance = appearance
+
+        # physics is None, then inherit, if Physics node is given, then use it.
+        # otherwise not physics
+        self.physics = PhysicsNode
+        if solidify is None:
+            self.solidify()
+        elif isinstance(solidify, PhysicsNode):
+            self.solidifyWithNewPhysics(solidify)
+
+    def solidify(self):
+        """Add physics property to this shape based on appearance material"""
+        self.physics = PhysicsNode(self.appearance.getMaterial())
+        self.solid = True
+
+    def solidifyWithNewPhysics(self, physics: PhysicsNode):
+        """Add physics property to this shape by specified physics nodes"""
+        self.physics = physics
+        self.solid = True
+
+    def gasify(self):
+        self.solid = False
+
+    def isSolid(self):
+        return self.solid
+
+    def getXyz(self):
+        return self.trans
+    def getRotationVector(self):
+        R = self.trans[0:3,0:3]
+        if np.array_equal(R, np.identity(3)):
+            rVector = np.array([0,0,0,0])
+        else:
+            v = np.array([R[2,1]-R[1,2],R[0,2]-R[2,0],R[1,0]-R[0,1]])
+            rVector = np.append(v/np.linalg.norm(v), np.arccos((np.trace(R)-1)/2))
+
+        return rVector
+
+
+    def getPhysicsNode(self):
+        return self.physics
+
+    def getWRL(self):
+        return self.wrl
+
+    def getSTL(self):
+        return wrl2stl(self.wrl)
+
+    # def getFaces(self):
+    #     return self.faces
+    # def getAppearance(self):
+    #     return self.appearance
+
+
+class ModuleNode(BaseComposableNode):
+    def __init__(self, name, shape: ShapeNode, device: DeviceNode = None, children=None):
+        super().__init__(name, children)
+        self.shape = shape
+        self.device = device
+
+    def getDeviceOptions(self, optionName):
+        if optionName in self.device.options.items():
+            return self.device.options[optionName]
+        return ""
+
+    def getPhysicsNode(self):
+        return self.shape.physics
diff --git a/rocolib/utils/roco2sim/__init__.py b/rocolib/utils/roco2sim/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/rocolib/utils/roco2sim/format_3d.py b/rocolib/utils/roco2sim/format_3d.py
new file mode 100644
index 0000000000000000000000000000000000000000..78fa78ca5f5de3bfca6fb959bd8d6846bf1a25fc
--- /dev/null
+++ b/rocolib/utils/roco2sim/format_3d.py
@@ -0,0 +1,19 @@
+from dataclasses import dataclass
+import numpy as np
+
+@dataclass
+class format_wrl:
+    coord: list
+    normal: list
+    index: list
+
+@dataclass
+class format_stl:
+    face: list
+
+def wrl2stl(wrl: format_wrl):
+    faces = []
+    for (index) in wrl.index:
+        faces.append(np.transpose(np.vstack((wrl.coord[int(index[0])],wrl.coord[int(index[1])],wrl.coord[int(index[2])]))))
+
+    return format_stl(faces)
diff --git a/rocolib/utils/roco2sim/material_data.py b/rocolib/utils/roco2sim/material_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..c80db6db9f599a8f9d587d4dc9a91aa893eb2a7a
--- /dev/null
+++ b/rocolib/utils/roco2sim/material_data.py
@@ -0,0 +1,27 @@
+from dataclasses import dataclass, field
+import numpy as np
+
+@dataclass
+class Material:
+    name: str
+    density: float = 1 # kg/m3
+    rgb: np.ndarray = np.array([0, 0, 0])
+    roughness: float = 1.0
+    reflection: float = 0.
+    optional: dict = field(default_factory=dict)
+    description: str = """"""
+
+@dataclass(frozen=True)
+class MaterialConst:
+    MAT_DEFAULT: str = 'defaultMaterial'
+
+
+
+mc = MaterialConst()
+materialData = {}
+
+materialData[mc.MAT_DEFAULT] \
+    = Material('generic_material', description="""This is a default material.""")
+
+materialData['paperThick'] \
+    = Material('Thick Paper', 833.3, description="""Business card paper""")
diff --git a/rocolib/utils/roco2sim/wbo_nodes.py b/rocolib/utils/roco2sim/wbo_nodes.py
new file mode 100644
index 0000000000000000000000000000000000000000..ebc6a02517add235cc39f85601a4ab19847c232f
--- /dev/null
+++ b/rocolib/utils/roco2sim/wbo_nodes.py
@@ -0,0 +1,132 @@
+from typing import List
+from rocolib.utils.roco2sim.Node import *
+
+
+scale = 0.001
+
+class WrlFormatter:
+    def __init__(self):
+        self.COORD_FACET = """{face[0]:g} {face[1]:g} {face[2]:g}, """
+        self.COORD_FACET_INDEX = """{face_pt_num[0]}, {face_pt_num[1]}, {face_pt_num[2]}, -1, """
+        self.FACET_NORMAL = """0 0 0, """
+
+    def coordPointsFormatter(self, p):
+        self.coordPoints = []
+        for x in p:
+            self.coordPoints.append(self.COORD_FACET.format(face=x.astype(float)+0))
+        return ''.join(self.coordPoints).strip(', ')
+
+    def normalFormatter(self,n):
+        self.normal = []
+        for x in n:
+            self.normal.append(self.FACET_NORMAL.format(face=x))
+        return ''.join(self.normal).strip(', ')
+
+    def coordIndexFormatter(self,c):
+        self.coordIndex = []
+        for x in c:
+            self.coordIndex.append(self.COORD_FACET_INDEX.format(face_pt_num = [int(y) for y in x]))
+        return ''.join(self.coordIndex).strip(', ')
+
+
+def wboShape(sNode: ShapeNode):
+    appearance = wboAppearance(sNode.appearance)
+    name = wboName(sNode.name)
+    geometry = wboGeometryIndexedFaceSet(sNode)
+    node = """DEF %(name)s Shape {%(appearance)s %(geometry)s}""" % locals()
+    return node
+
+
+def wboGeometryIndexedFaceSet(sNode: ShapeNode):
+    wrl = sNode.wrl
+    if not wrl.coord:
+        return ""
+
+    wf = WrlFormatter()
+    normal = wf.coordPointsFormatter(wrl.normal)
+    points = wf.coordPointsFormatter(wrl.coord)
+    coordIndex = wf.coordIndexFormatter(wrl.index)
+
+    node = """geometry IndexedFaceSet {coord Coordinate {point [%(points)s]} normal Normal {vector [%(normal)s]} coordIndex [%(coordIndex)s]}""" % locals()
+    return node
+
+
+def wboAppearance(aNode: AppearanceNode):
+    baseColor = str(aNode.getRGB()).strip('[ ]')
+    roughness = str(aNode.getRoughness())
+    metalness = str(aNode.getReflection())
+    name = wboName(aNode.getName())
+    node = """appearance PBRAppearance { baseColor %(baseColor)s roughness %(roughness)s metalness %(metalness)s name "%(name)s"}""" % locals()
+    return node
+
+
+def wboPhysics(pNode: PhysicsNode):
+    if pNode is None:
+        return """ """
+    density = pNode.material.density
+    mass = pNode.mass
+    node = """physics Physics {density %(density)s mass %(mass)s}""" % locals()
+    return node
+
+
+def wboXyz(sNode: ShapeNode):
+    return str(scale * np.array(sNode.getXyz()).astype(int)[0:3, 3]).strip('[ ]')
+
+
+def wboSolid(sNode: ShapeNode):
+    if not sNode.isSolid():
+        return wboShape(sNode)
+    shape = wboShape(sNode)
+    contactMaterial = wboContact(sNode)
+    physics = wboPhysics(sNode.getPhysicsNode())
+    name = wboName(sNode.name)
+    node = """DEF %(name)s Solid {children [%(shape)s] %(contactMaterial)s boundingObject USE %(name)s %(physics)s}""" % locals()
+    return node
+
+def wboContact(sNode: ShapeNode):
+    contactMaterial = sNode.getPhysicsNode().contact
+    if contactMaterial is None:
+        return """contactMaterial "default" """
+    return """contactMaterial "%(contactMaterial)s" """
+
+
+def wboRobot(mNode: ShapeNode):
+    # create local var to fill the nodes
+    name = wboName(mNode.name)
+    mNode.gasify()
+    solid = wboSolid(mNode)
+    mNode.children.append(solid)
+    childrenNode = " ".join(mNode.children)
+    physics = wboPhysics(mNode.getPhysicsNode())
+    contactMaterial = wboContact(mNode)
+    rotation = str(mNode.getRotationVector()).strip('[ ]')
+    # battery = mNode.getDeviceOptions('battery')
+    # cpuConsumption = mNode.getDeviceOptions('cpuConsumption')
+    node = """DEF %(name)s Robot {
+  rotation %(rotation)s
+  children [%(childrenNode)s]
+  name "%(name)s"
+  %(contactMaterial)s
+  boundingObject USE %(name)s
+  %(physics)s
+  controller "%(name)s"
+  supervisor TRUE
+}""" % locals()
+    return node
+
+
+def wboHeader(version="R2021b"):
+    return """#VRML_OBJ R2021b utf8
+"""
+
+
+def wboConverter(sNodes: List[ShapeNode]):
+    robot = sNodes[0]
+    for sNode in sNodes[1:len(sNodes)]:
+        robot.children.append(wboSolid(sNode))
+    node = wboHeader() + wboRobot(robot)
+    return node
+
+
+def wboName(name):
+    return name.replace('.', '_')