from . import gds from concurrent.futures import ThreadPoolExecutor import importlib if "gds" in locals(): gds = importlib.reload(gds) import pathlib import bpy from bpy_extras.wm_utils.progress_report import ProgressReport class Progressor: def __init__(self, wm, precision = 0.0001): self.wm = wm self.report = 0 self.precision = precision self.next_update = 0 @property def total(self): return self.report @total.setter def total(self, t): if self.report >= t: return if self.report: self.wm.progress_end() self.next_update = 0 self.wm.progress_begin(0, t) self.report = t @property def current(self): return 0 @current.setter def current(self, c): prog = float(c)/self.total if prog >= self.next_update: self.next_update = prog + self.precision self.wm.progress_update(c) def end(self): if self.report: self.report = 0 self.next_update = 0 self.wm.progress_end() def __call__(self, update): self.total = update.total self.current = update.current class SceneInserter(object): class Layer(object): def __init__(self, context, number, thickness=1, datatypes=[], ): self.number = number self.context = context # 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 self.context = context self.layers = {} self.ignore_structures = {} self.add_stackup() self.top_cell = top_cell if top_cell == "": self.top_cell = None if self.top_cell: self.top_cell = self.top_cell.strip() return for sname in ignore_structures: 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) self.link(obj) return obj 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: return self.new_instance(bpy.data.objects[structure.name]) if progress: progress.total += 1 + len(structure.references) # for boundary elements class Mesh: def __init__(self): self.verts = [] self.faces = [] boundaries = {} # create control empty root = self.new_empty(structure.name) # reset name in case blender # changes the object name # (e.g. due to duplicates) structure.name = root.name #print("structure name: {}".format(structure.name)) # add all geometry for number, element in enumerate(structure.elements): index = (element.layer, element.datatype) if isinstance(element, gds.Boundary): # 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) boundaries[index].verts.append((pos[0], pos[1], 0)) indices.append(len(boundaries[index].verts)-1) boundaries[index].faces.append(tuple(indices)) elif isinstance(element, gds.Path): cname = "{}-{}[{}]".format(structure.name, number, element.layer) self.build_path_element(cname, root, element) else: print("import of {} not implemented".format(type(element))) 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) 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() # handle all references # build tree depth first for reference in structure.references: if isinstance(reference.structure, str): if reference.structure not in self.lib.structures: progress.inc() continue # resolve structure reference.structure = self.lib.structures[reference.structure] 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): return (-a[0], -a[1]) def sub(a, b): return add(inv(a), b) def scale(a, f): return (a[0] * f, a[1] * f) vx = sub(reference.bounds[0], reference.position) vy = sub(reference.bounds[1], reference.position) 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) # 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] mod_y = child.modifiers.new("ay", "ARRAY") 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 pass # use single arrow for references obj.empty_draw_type = "SINGLE_ARROW" # reparent new instance obj.parent = root pos = self.lib.normalize_coord(reference.position) obj.location = (pos[0], pos[1], 0) # apply transformations trans = reference.transformation obj.rotation_euler.z = math.radians(trans.rotation) obj.scale = (trans.zoom, trans.zoom, 1) if progress: progress.inc() return root def new_instance(self, obj): root = obj.copy() self.link(root) for child in obj.children: if child.name.find("profile") != -1: # only copy path profiles continue childObj = child.copy() 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 def __call__(self, progress=None): class ProgressTracker: def __init__(self, callback, total=0): self.total = total self.current = 0 self.callback = callback def inc(self): if self.callback: self.current += 1 self.callback(self) # build one cell only if self.top_cell: if self.top_cell not in self.lib.structures: return None s = self.lib.structures[self.top_cell] progressTracker = ProgressTracker(progress, 1+len(s.references)) self.build_object(s,progress=progressTracker) return True progressTracker = ProgressTracker(progress) progressTracker.total = len(self.lib.structures) # self.link_all_refs(progressTracker) # build all structures first # since references could reference unknown objects otherwise for name, value in self.lib.structures.items(): progressTracker.inc() if name in self.ignore_structures: continue self.build_object(value, progress=progressTracker) return True import math def load(context, filepath, *, top_cell_name="" ): lib = None with ProgressReport(context.window_manager) as progress: progress.enter_substeps(2, "Importing GDS %r..." % filepath) prog = Progressor(context.window_manager) with open(filepath, "rb") as f: lib = gds.parse_file(f, progress_func=prog) progress.step("linking references") prog.end() if not SceneInserter(context=context, lib=lib, top_cell=top_cell_name)(prog): return {"CANCELLED"} progress.leave_substeps("Done") prog.end() context.scene.update() return {"FINISHED"}