docs: 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)