diff --git a/import_gds.py b/import_gds.py index 35c22f3..0e120cc 100644 --- a/import_gds.py +++ b/import_gds.py @@ -1,4 +1,5 @@ from . import gds +from concurrent.futures import ThreadPoolExecutor import importlib if "gds" in locals(): @@ -31,8 +32,6 @@ class Progressor: self.wm.progress_begin(0, t) self.report = t - print ("begin new report , total {}".format(t)) - @property def current(self): return 0 @@ -55,20 +54,86 @@ class Progressor: self.total = update.total self.current = update.current -class SceneInserter(object): +class SceneInserter(object): class Layer(object): - def __init__(self, context, number): - self.name = "layer{}".format(number) - - if self.name not in bpy.data.groups: - # create new group - self.group = bpy.data.groups.new(self.name) - self.name = self.group.name - else: - self.group = bpy.data.groups[self.name] + def __init__(self, context, number, thickness=1, datatypes=[], ): + self.number = number + self.context = context - def link(self, obj): - self.group.objects.link(obj) + # stackup results + self.thickness = thickness + self.absolute_height = 0 + + # stackup + self.Prev = None + self.Next = None + + self.groups = {} + self.materials = {} + + for datatype in datatypes: + self.new_datatype(type) + + def name_for_layer(self, datatype): + return "layer{}/{}".format(self.number, datatype) + + def new_datatype(self, datatype): + name = self.name_for_layer(datatype) + + if name not in bpy.data.groups: + # create new group + self.groups[datatype] = bpy.data.groups.new(name) + else: + self.groups[datatype] = bpy.data.groups[name] + + if name not in bpy.data.materials: + self.materials[name] = bpy.data.materials.new(name) + else: + self.materials[name] = bpy.data.materials[name] + + + def group_for_datatype(self, dtype): + if dtype not in self.groups: + self.new_datatype(dtype) + + return self.groups[dtype] + + def insert_after(self, other): + end = self + + while end.Next: + end = end.Next + + if self.Prev: + self.Prev.Next = None + + self.Prev = None + + # link own list to new list + if other.Next: + if other.Next.Prev: + other.Next.Prev = end + + end.Next = other.Next + + other.Next = self + self.Prev = other + + # recalc height + end = self + while end: + end.absolute_height = end.Prev.absolute_height + end.Prev.thickness + end = end.Next + + return self + + + def link(self, datatype, obj): + self.group_for_datatype(datatype).objects.link(obj) + + mat = self.materials[self.name_for_layer(datatype)] + obj.data.materials.append(mat) + def __init__(self, context, lib, ignore_structures={}, top_cell=None): self.lib = lib @@ -77,6 +142,8 @@ class SceneInserter(object): self.layers = {} self.ignore_structures = {} + self.add_stackup() + self.top_cell = top_cell if top_cell == "": self.top_cell = None @@ -89,6 +156,26 @@ class SceneInserter(object): if sname in lib.structures: self.ignore_structures[sname] = True + def add_layer(self, number, thickness=1): + self.layers[number] = SceneInserter.Layer(self.context, number, thickness) + return self.layers[number] + + def get_layer(self, number): + if number not in self.layers: + return self.add_layer(number) + + return self.layers[number] + + def add_stackup(self): + prev = None + for i in range(0,200): + l = self.add_layer(i) + if prev: + l.insert_after(prev) + prev = l + + # example stackup for now + #self.add_layer(8).insert_after(self.add_layer(6).insert_after(self.add_layer(5).insert_after(self.add_layer(4)))) def new_empty(self, name): obj = bpy.data.objects.new(name, None) @@ -98,6 +185,55 @@ class SceneInserter(object): def link(self, obj): self.context.scene.objects.link(obj) + def build_path_element(self, name, root, element): + # build path profile + widthcurve = bpy.data.curves.new("profile-" + name , "CURVE") + bevelobj = bpy.data.objects.new("profile-" + name , widthcurve) + bevelobj.parent = root + + # hide the bevel object + self.link(bevelobj) + + bevelobj.hide = True + bevelobj.hide_render = True + + widthspline = widthcurve.splines.new("POLY") + widthspline.use_smooth = False + widthspline.use_cyclic_u = True + widthspline.points.add(3) + + # create bevel profile + width = element.width * self.lib.units_per_dbunit + thickness = self.get_layer(element.layer).thickness + + widthspline.points[0].co = (-1*width/2.0, thickness/2.0, 0,0) + widthspline.points[1].co = ( width/2.0, thickness/2.0, 0,0) + widthspline.points[2].co = ( width/2.0, -1*thickness/2.0, 0,0) + widthspline.points[3].co = (-1*width/2.0, -1*thickness/2.0, 0,0) + + # create actual path + pathcurve = bpy.data.curves.new(name, "CURVE") + + pathcurve.use_fill_caps = True + pathcurve.bevel_object = bevelobj + + pathspline = pathcurve.splines.new("POLY") + pathspline.points.add(len(element.points)-1) + pathspline.use_smooth = False + + # import path + for i, p in enumerate(element.points): + pos = self.lib.normalize_coord(p) + pathspline.points[i].co = (pos[0], pos[1], 0,0) + + # setup pathobject + pathobj = bpy.data.objects.new(name, pathcurve) + self.link(pathobj) + self.get_layer(element.layer).link(element.datatype, pathobj) + + pathobj.location.z = self.get_layer(element.layer).absolute_height + pathobj.parent = root + def build_object(self, structure, progress=None): # is the object already existent => return new instance if structure.name in bpy.data.objects: @@ -106,9 +242,14 @@ class SceneInserter(object): if progress: progress.total += 1 + len(structure.references) - verts = {} - faces = {} - + # for boundary elements + class Mesh: + def __init__(self): + self.verts = [] + self.faces = [] + + boundaries = {} + # create control empty root = self.new_empty(structure.name) @@ -120,37 +261,52 @@ class SceneInserter(object): #print("structure name: {}".format(structure.name)) # add all geometry - for element in structure.elements: + for number, element in enumerate(structure.elements): + index = (element.layer, element.datatype) if isinstance(element, gds.Boundary): - if element.layer not in faces: - verts[element.layer] = [] - faces[element.layer] = [] - + # every element should be of derived of gds.Drawable + if index not in boundaries: + boundaries[index] = Mesh() + indices = [] for point in element.points[:-1]: pos = self.lib.normalize_coord(point) - verts[element.layer].append((pos[0], pos[1], 0)) + boundaries[index].verts.append((pos[0], pos[1], 0)) + indices.append(len(boundaries[index].verts)-1) - indices.append(len(verts[element.layer])-1) + boundaries[index].faces.append(tuple(indices)) - faces[element.layer].append(tuple(indices)) - - # build structure mesh objects - # create mesh object for each layer - for layer in verts.keys(): - if layer not in self.layers: - self.layers[layer] = SceneInserter.Layer(self.context, layer) + elif isinstance(element, gds.Path): + cname = "{}-{}[{}]".format(structure.name, number, element.layer) + self.build_path_element(cname, root, element) - meshname = "{}[{}]".format(structure.name, layer) + else: + print("import of {} not implemented".format(type(element))) - mesh = bpy.data.meshes.new(meshname) - mesh.from_pydata(verts[layer], [], faces[layer]) - mesh.update() + with ThreadPoolExecutor(max_workers=5) as executor: + # build structure mesh for boundary elements + for ident, template in boundaries.items(): + def do_boundary(layerident, meshtemplate): + layer = self.get_layer(layerident[0]) + meshname = "{}[{}]".format(structure.name, layerident) - obj = bpy.data.objects.new(meshname, mesh) - self.link(obj) - self.layers[layer].link(obj) - obj.parent = root + mesh = bpy.data.meshes.new(meshname) + mesh.from_pydata(meshtemplate.verts, [], meshtemplate.faces) + mesh.update() + + obj = bpy.data.objects.new(meshname, mesh) + self.link(obj) + layer.link(layerident[1], obj) + obj.parent = root + + # create solifiy modifier + mod = obj.modifiers.new("Solidify", 'SOLIDIFY') + mod.thickness = 1 # probaly meters ? + mod.use_even_offset = True + mod.offset = 0 + obj.location.z = layer.absolute_height + + executor.submit(do_boundary, ident, template) if progress: progress.inc() @@ -169,6 +325,7 @@ class SceneInserter(object): obj = self.build_object(reference.structure, progress=progress) if hasattr(reference, "size"): + # this is an Array reference def add(a,b): return (a[0] + b[0], a[1] + b[1]) def inv(a): @@ -182,28 +339,26 @@ class SceneInserter(object): vx = sub(reference.bounds[0], reference.position) vy = sub(reference.bounds[1], reference.position) - xspace = 1.0/reference.size[0] - yspace = 1.0/reference.size[1] + xspace = scale(vx, 1.0/reference.size[0] * self.lib.units_per_dbunit) + yspace = scale(vy, 1.0/reference.size[1] * self.lib.units_per_dbunit) - master = obj - obj = self.new_empty(reference.structure.name) - master.parent = obj - master.location = (0,0,0) + # use array modifiers to create the aref array + for child in obj.children: + mod_x = child.modifiers.new("ax", "ARRAY") + + mod_x.use_constant_offset = True + mod_x.use_relative_offset = False + mod_x.constant_offset_displace[0] = xspace[0] + mod_x.constant_offset_displace[1] = xspace[1] + mod_x.count = reference.size[0] - # this is an aref, create object array - for i in range(0, reference.size[0]): - for j in range(0, reference.size[1]): - if i == 0 and j == 0: - continue - - # calc new pos - iobj = self.new_instance(master) + mod_y = child.modifiers.new("ay", "ARRAY") - pos = add(scale(vy, j*yspace), scale(vx, i*xspace)) - loc = self.lib.normalize_coord(pos) - - iobj.location = (loc[0], loc[1], 0) - iobj.hide = True + mod_y.use_constant_offset = True + mod_y.use_relative_offset = False + mod_y.constant_offset_displace[0] = yspace[0] + mod_y.constant_offset_displace[1] = yspace[1] + mod_y.count = reference.size[1] else: # this is a single reference # just copy the object @@ -233,15 +388,33 @@ class SceneInserter(object): self.link(root) for child in obj.children: + if child.name.find("profile") != -1: + # only copy path profiles + continue + childObj = child.copy() - self.link(childObj) - childObj.parent = root - + self.link(childObj) + # relink copy to groups for group in child.users_group: group.objects.link(childObj) + # resetup curves + if childObj.type == "CURVE": + for spline in childObj.data.splines: + spline.use_smooth = False + + + # copy profile + profile = bpy.data.objects["profile-" + child.name].copy() + profile.parent = root + self.link(profile) + + # resetup path + childObj.data.use_fill_caps = True + childObj.data.bevel_object = profile + return root @@ -294,10 +467,8 @@ def load(context, top_cell_name="" ): - path = pathlib.Path(filepath) lib = None - print(lib) - + with ProgressReport(context.window_manager) as progress: progress.enter_substeps(2, "Importing GDS %r..." % filepath) prog = Progressor(context.window_manager) @@ -311,7 +482,7 @@ def load(context, return {"CANCELLED"} progress.leave_substeps("Done") - prog.end() + prog.end() context.scene.update()