Source code for ddg.datastructures.halfedge.io

import ddg
import json
import re
import numpy as np


[docs]class HalfedgeEncoder(json.JSONEncoder): def __init__(self, vertex_attrs=[], edge_attrs=[], face_attrs=[], **kwargs): super(HalfedgeEncoder, self).__init__(**kwargs) self.vertex_slots = ['edge'] self.vertex_attrs = vertex_attrs self.edge_slots = ['pre', 'nex', 'opp', 'head', 'face'] self.edge_attrs = edge_attrs self.face_slots = ['edge'] self.face_attrs = face_attrs
[docs] def default(self, obj): if isinstance(obj, ddg.halfedge.Surface): obj.verts.add_attribute('json_idx') obj.edges.add_attribute('json_idx') obj.faces.add_attribute('json_idx') for i, v in enumerate(obj.verts): v.json_idx = i for i, e in enumerate(obj.edges): e.json_idx = i for i, f in enumerate(obj.faces): f.json_idx = i obj_dict = dict() obj_dict['verts'] = self._create_dicts(obj, obj.verts, self.vertex_slots, self.vertex_attrs) obj_dict['edges'] = self._create_dicts(obj, obj.edges, self.edge_slots, self.edge_attrs) obj_dict['faces'] = self._create_dicts(obj, obj.faces, self.face_slots, self.face_attrs) return obj_dict return json.JSONEncoder.default(self, obj)
@staticmethod def _create_dicts(surface, nodes, slots, attrs): nodes_dicts = [] for v in nodes: node_dict = {} for slot in slots: node_dict[slot] = HalfedgeEncoder._get_index(surface, getattr(v, slot)) for attr in attrs: node_attribute = getattr(nodes, attr) value = node_attribute[v] if isinstance(value, np.ndarray): node_dict[attr] = tuple(value) else: node_dict[attr] = value nodes_dicts.append(node_dict) return nodes_dicts @staticmethod def _get_index(surface, value): if isinstance(value, surface.verts): return value.json_idx if isinstance(value, surface.edges): return value.json_idx if isinstance(value, surface.faces): return value.json_idx if value is None: return -1 return value
[docs]def to_json_string(surface, vertex_attrs=[], edge_attrs=[], face_attrs=[], write_index=False): """ Create a json string from a given halfedge surface. Additional attributes to be included in the json string need to be specified explicitly in the respective dictionaries. All the references to vertices, edges, and faces are realized using integer indices. The `None` face corresponds to `-1` Parameters ---------- surface : halfedge surface The surface to be converted to a json string vertex_attrs : list of strings vertex attributes to be contained in the json string in addition to `edge` edge_attrs edge attributes to be contained in the json string in addition to `pre`, `nex`, `opp`, `head`, `face` face_attrs face attributes to be contained in the json string in addition to `edge` Returns ------- string """ if write_index: halfedge_encoder = HalfedgeEncoder( vertex_attrs + ['json_idx'], edge_attrs + ['json_idx'], face_attrs + ['json_idx']) else: halfedge_encoder = HalfedgeEncoder(vertex_attrs, edge_attrs, face_attrs, indent=2) return halfedge_encoder.encode(surface)
[docs]def write_json_file(surface, filename, vertex_attrs=[], edge_attrs=[], face_attrs=[]): """ Create a file in json format from a given halfedge surface. Additional attributes to be included in the json string need to be specified explicitly in the respective dictionaries. All the references to vertices, edges, and faces are realized using integer indices. The `None` face corresponds to `-1` Parameters ---------- surface : halfedge surface The surface to be converted to a json string filename : string file to be written vertex_attrs : list of strings vertex attributes to be contained in the json string in addition to `edge` edge_attrs edge attributes to be contained in the json string in addition to `pre`, `nex`, `opp`, `head`, `face` face_attrs face attributes to be contained in the json string in addition to `edge` """ with open(filename, 'w') as outfile: outfile.write(to_json_string(surface, vertex_attrs, edge_attrs, face_attrs))
[docs]def read_json_file(filename): """ Create a surface from a json file. All keys will be mapped to respective attribute of the vertices, edges, and faces. Parameters ---------- filename: string file containing a json representing a halfedge surface Returns ------- surface Raises ------ ValueError If required attributes are missing in the json string """ with open(filename, 'r') as f: d = json.load(f) _validate_surface_dict(d) return _dict_to_surf(d)
[docs]def parse_json_string(json_string): """ Create a surface from a json string. All keys will be mapped to respective attribute of the vertices, edges, and faces. Parameters ---------- json_string : string formatted string containing a halfedge surface Returns ------- surface Raises ------ ValueError If required attributes are missing in the json string """ json_dict = json.loads(json_string) _validate_surface_dict(json_dict) return _dict_to_surf(json_dict)
def _validate_surface_dict(json_dict): _validate_node_dict(json_dict["verts"], ["edge"]) _validate_node_dict(json_dict["edges"], ["pre", "nex", "opp", "head", "face"]) _validate_node_dict(json_dict["faces"], ["edge"]) def _validate_node_dict(nodes, required_attributes): for n in nodes: missing_attributes = required_attributes - n.keys() if len(missing_attributes) > 0: raise ValueError( 'Halfedge surface dictionary does not contain required ' 'attributes ' + str(missing_attributes) + '.') def _dict_to_surf(d): s = ddg.halfedge.Surface() vertices = _create_nodes_with_attributes(d, "verts", s.verts) edges = _create_nodes_with_attributes(d, "edges", s.edges) faces = _create_nodes_with_attributes(d, "faces", s.faces) _assign_node_attributes(zip(vertices, d["verts"]), s.verts, vertices, edges, faces) _assign_node_attributes(zip(edges, d["edges"]), s.edges, vertices, edges, faces) _assign_node_attributes(zip(faces, d["faces"]), s.faces, vertices, edges, faces) return s def _assign_node_attributes(pairs, nodes, vertices, edges, faces): for node, json_node in pairs: for k in json_node.keys(): if (k == "pre") or (k == "nex") or (k == "opp") or (k == "edge"): setattr(node, k, edges[json_node[k]]) elif k == "face": if json_node[k] < 0: node.face = None else: node.face = faces[json_node[k]] elif k == "head": node.head = vertices[json_node[k]] else: node_attribute = getattr(nodes, k) node_attribute[node] = json_node[k] if not isinstance(json_node[k], list) else np.array(json_node[k]) def _create_nodes_with_attributes(d, node_name, nodes): new_nodes = [nodes() for vd in d[node_name]] json_node = d[node_name][0] for attribute in json_node.keys(): if not hasattr(nodes, attribute): nodes.add_attribute(attribute) return new_nodes
[docs]def surface_to_ifs_json(surface, vertex_attrs=['co']): encoder = SurfaceToIFSEncoder(vertex_attrs) return encoder.encode(surface)
[docs]class SurfaceToIFSEncoder(json.JSONEncoder): def __init__(self, vertex_attrs=['co'], **kwargs): super(SurfaceToIFSEncoder, self).__init__(**kwargs) self.vertex_attrs = vertex_attrs
[docs] def default(self, surface): if isinstance(surface, ddg.halfedge.Surface): surface_dict = {} surface.verts.add_attribute('json_idx') for i, v in enumerate(surface.verts): v.json_idx = i surface_dict['faces'] = [] for f in surface.faces: vertex_index_list = tuple( [v.json_idx for v in ddg.datastructures.halfedge.get.get_vertices(f)]) surface_dict['faces'].append(vertex_index_list) if len(self.vertex_attrs) != 0: surface_dict['verts'] = [] for v in surface.verts: vertex_dict = {} for attribute in self.vertex_attrs: v_attribute = getattr(surface.verts, attribute) value = v_attribute[v] vertex_dict[attribute] = value.tolist() surface_dict['verts'].append(vertex_dict) return surface_dict return json.JSONEncoder.default(self, surface)
[docs]class ObjEncoder(object): """ Class to convert obj file to a ddg.halfedge.Surface or an IndexedFaceSet. self.indexedfs is an IndexedFaceSet constructed of the obj file. self.halfedgeds is a Surface with halfedge attributes "vt" and "vn" (if given). """ def __init__(self, filename): self.filename = filename self.filetype = None self.indexedfs = None self.halfedgeds = None self.v = [] self.vt = [] self.vn = [] self.f = [] self.vt_face_idx = [] self.vn_face_idx = [] self._read_obj() def _read_obj(self): f = open(self.filename, "r", encoding="utf8", errors='ignore') for line in f: if line.startswith(('v', 'vt', 'vn')): getattr(self, line.split()[0]).append([float(s) for s in re.findall(r"-?\d+(?:\.\d+)?", line)]) if line.startswith("f"): self.f.append(line[1:]) f.close() if self.vt == [] and self.vn == []: self.filetype = "0" if self.vt != [] and self.vn == []: self.filetype = "T" if self.vt == [] and self.vn != []: self.filetype = "N" if self.vt != [] and self.vn != []: self.filetype = "TN" for face in self.f: if self.filetype == "T": self.vt_face_idx.append([int(s[1:]) - 1 for s in re.findall(r"/\d+", face)]) if self.filetype == "N": self.vn_face_idx.append([int(s[1:]) - 1 for s in re.findall(r"/\d+", face)]) if self.filetype == "TN": self.vt_face_idx.append([int(s[1:-1]) - 1 for s in re.findall(r"/\d+/", face)]) self.vn_face_idx.append([int(s.split('/')[-1]) - 1 for s in re.findall(r"/\d+/\d+", face)]) if not self.f: raise ValueError( "Faces in the input file do not have any of the specified " "forms") self.f = [[int(s[1:]) - 1 for s in re.findall(r" \d+", face)] for face in self.f] def _set_vt_and_vn(self): if self.filetype == "T": self.halfedgeds.edges.add_attribute("vt") self.halfedgeds.edges.add_attribute("vt_index") if self.filetype == "N": self.halfedgeds.edges.add_attribute("vn") self.halfedgeds.edges.add_attribute("vn_index") if self.filetype == "TN": self.halfedgeds.edges.add_attribute("vt") self.halfedgeds.edges.add_attribute("vn") self.halfedgeds.edges.add_attribute("vt_index") self.halfedgeds.edges.add_attribute("vn_index") for face in self.halfedgeds.faces: edge = face.edge selfface = self.f[face.ifs_face_index] while edge.head.ifs_index != selfface[0]: edge = edge.nex for i, edg in enumerate(ddg.datastructures.halfedge.get.get_edge_loop(edge)): j = i if edge.nex.head.ifs_index != selfface[ 1]: # edgeloop is reverse orientation to the given # vertex order j = -i if self.filetype == "T": edg.vt_index = self.vt_face_idx[face.ifs_face_index][j] edg.vt = self.vt[edg.vt_index] if self.filetype == "N": edg.vn_index = self.vn_face_idx[face.ifs_face_index][j] edg.vn = self.vn[edg.vn_index] if self.filetype == "TN": edg.vt_index = self.vt_face_idx[face.ifs_face_index][j] edg.vt = self.vt[edg.vt_index] edg.vn_index = self.vn_face_idx[face.ifs_face_index][j] edg.vn = self.vn[edg.vn_index]
[docs] def initialize_indexedfs(self): """ Creates an IndexedFaceSet as self.indexedfs of the given obj file. """ faceSet = ddg.indexedfaceset.ifs.IndexedFaceSet(self.f) self.indexedfs = faceSet
[docs] def initialize_halfedgeds(self): """ Creates a surface as self.halfedgeds of the given obj file. Sets attributes "vn" and "vt" to edges pointing to the corresponding vertex if given. """ if not self.indexedfs: self.initialize_indexedfs() hds = ddg.indexedfaceset.utils.indexed_face_set_to_surface(self.indexedfs) hds.verts.add_attribute("co") for vert in hds.verts: vert.co = np.array(self.v[vert.ifs_index]) self.halfedgeds = hds if self.filetype != "0": self._set_vt_and_vn()
[docs]def obj_to_ifs(filename): """ Converts an obj file to an IndexedFaceSet. Parameters ---------- filename: (path and) name of the obj file Returns ------- IndexedFaceSet specified by the given obj file """ encoder = ObjEncoder(filename) encoder.initialize_indexedfs() return encoder.indexedfs
[docs]def obj_to_surface(filename): """ | Converts an obj file to a Surface with halfedge attributes "vt" and "vn" | (if given). | Obj file with given v and faces of the form f 1 2 3 | or given v,vt and faces of the form f 1/1 2/1 3/1 | or given v,vn and faces of the form f 1//1 2//1 3//1 | or given v,vt,vn and faces of the form f 1/1/2 2/1/2 3/1/2. Parameters ---------- filename: (path and) name of the obj file Returns ------- Surface specified by the given obj file """ encoder = ObjEncoder(filename) encoder.initialize_halfedgeds() return encoder.halfedgeds
[docs]class SurfaceToObjEncoder(object): """ Class to convert a ddg.halfedge.Surface or an IndexedFaceSet to an obj file. self.hds encodes the given surface. """ def __init__(self, surface, filename='obj_file.obj', co_attr='co'): self.hds = surface self.filename = filename self.filetype = None self.co = co_attr self.vt = {} self.vn = {} self._initialize_vt_and_vn() def _initialize_vt_and_vn(self): for e in self.hds.edges: if hasattr(e, 'vt'): self.vt[tuple(e.vt)] = -1 if hasattr(e, 'vn'): self.vn[tuple(e.vn)] = -1 if self.vt == {} and self.vn == {}: self.filetype = "0" if self.vt != {} and self.vn == {}: self.filetype = "T" if self.vt == {} and self.vn != {}: self.filetype = "N" if self.vt != {} and self.vn != {}: self.filetype = "TN" self.vt = {vt: i for i, vt in enumerate(sorted(self.vt.keys()))} self.vn = {vn: i for i, vn in enumerate(sorted(self.vn.keys()))}
[docs] def write_obj(self): with open(self.filename, 'w+') as f: f.write('Obj file created by halfedge data structure \n\n') for v in self.hds.verts: if not hasattr(v, self.co): raise ValueError('Vertex has not the specified coordinate attribute') f.write( 'v {0} {1} {2}\n'.format(getattr(v, self.co)[0], getattr(v, self.co)[1], getattr(v, self.co)[2])) f.write('\n') for attr in ['vt', 'vn']: for coords in sorted(getattr(self, attr), key=getattr(self, attr).get): line = ' '.join(str(x) for x in coords) f.write('{0} {1}\n'.format(attr, line)) if getattr(self, attr) != {}: f.write('\n') for face in self.hds.faces: f.write('f') for e in ddg.datastructures.halfedge.get.get_edge_loop(face.edge): if self.filetype == '0': f.write(' {0}'.format(e.pre.head.index + 1)) if self.filetype == 'T': f.write(' {0}/{1}'.format(e.pre.head.index + 1, self.vt[tuple(e.pre.vt)] + 1)) if self.filetype == 'N': f.write(' {0}//{1}'.format(e.pre.head.index + 1, self.vn[tuple(e.pre.vn)] + 1)) if self.filetype == 'TN': f.write(' {0}/{1}/{2}'.format(e.pre.head.index + 1, self.vt[tuple(e.pre.vt)] + 1, self.vn[tuple(e.pre.vn)] + 1)) f.write('\n')
[docs]def surface_to_obj(surface, filename='obj_file.obj', co_attr='co'): """ | Converts a ddg.halfedge.Surface to an obj file. | If 'vn' and/or 'vt' are given as halfegde attributes, they will be | written in the obj file format. | The vertices need to have coordinates, by default as the attribute 'co'. | Results in Obj file with given v and faces of the form f 1 2 3 | or given v,vt and faces of the form f 1/1 2/1 3/1 | or given v,vn and faces of the form f 1//1 2//1 3//1 | or given v,vt,vn and faces of the form f 1/1/2 2/1/2 3/1/2. | Vertices will be ordered by indices and vt and vn coordinates in increasing order. Parameters ---------- surface: ddg.halfedge.Surface halfedge data structure to be converted filename : str (default='obj_file.obj') Name for the resulting obj file a file path can be specified here default is 'obj_file' which will be saved in the cwd co_attr : str (default='co') string of the vertx attribute that encodes the coordinates Notes ----- * Indexing in obj files starts at 1. """ encoder = SurfaceToObjEncoder(surface, filename, co_attr) encoder.write_obj()