Args:
                data (str): The obj file content.

            Returns:
                Obj: The object.

            Examples:

                .. code-block:: python

                    import ModernGL
                    from ModernGL.ext import obj

                    content = open('box.obj').read()
                    model = obj.Obj.fromstring(content)

def fromstring(data) -> 'Obj':
        '''
            Args:
                data (str): The obj file content.

            Returns:
                Obj: The object.

            Examples:

                .. code-block:: python

                    import ModernGL
                    from ModernGL.ext import obj

                    content = open('box.obj').read()
                    model = obj.Obj.fromstring(content)
        '''

        vert = []
        text = []
        norm = []
        face = []

        data = RE_COMMENT.sub('\n', data)

        for line in data.splitlines():
            line = line.strip()

            if not line:
                continue

            match = RE_VERT.match(line)

            if match:
                vert.append(tuple(map(safe_float, match.groups())))
                continue

            match = RE_TEXT.match(line)
            if match:
                text.append(tuple(map(safe_float, match.groups())))
                continue

            match = RE_NORM.match(line)

            if match:
                norm.append(tuple(map(safe_float, match.groups())))
                continue

            match = RE_TRIANGLE_FACE.match(line)

            if match:
                v, t, n = match.group(1, 3, 5)
                face.append((int(v), int_or_none(t), int_or_none(n)))
                v, t, n = match.group(6, 8, 10)
                face.append((int(v), int_or_none(t), int_or_none(n)))
                v, t, n = match.group(11, 13, 15)
                face.append((int(v), int_or_none(t), int_or_none(n)))
                continue
            
            match = RE_QUAD_FACE.match(line)
            if match:
                # we convert the face in two triangles
                v, t, n = match.group(1, 3, 5)
                face.append((int(v), int_or_none(t), int_or_none(n)))
                v, t, n = match.group(6, 8, 10)
                face.append((int(v), int_or_none(t), int_or_none(n)))
                v, t, n = match.group(11, 13, 15)
                face.append((int(v), int_or_none(t), int_or_none(n)))
                v, t, n = match.group(1, 3, 5)
                face.append((int(v), int_or_none(t), int_or_none(n)))  
                v, t, n = match.group(11, 13, 15)
                face.append((int(v), int_or_none(t), int_or_none(n)))                
                v, t, n = match.group(16, 18, 20)
                face.append((int(v), int_or_none(t), int_or_none(n)))                
                continue            

            log.debug('unknown line "%s"', line)

        if not face:
            raise Exception('empty')

        t0, n0 = face[0][1:3]

        for v, t, n in face:
            if (t0 is None) ^ (t is None):
                raise Exception('inconsinstent')

            if (n0 is None) ^ (n is None):
                raise Exception('inconsinstent')

        return Obj(vert, text, norm, face)