From 07eec6f648b3d8063aefe0a278b52974e6970f23 Mon Sep 17 00:00:00 2001 From: Julian Daube Date: Wed, 3 Jul 2019 13:53:25 +0200 Subject: [PATCH] blender: make definition of top cell possible --- __init__.py | 155 +++++++++++++------------- import_gds.py | 297 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 299 insertions(+), 153 deletions(-) diff --git a/__init__.py b/__init__.py index 7b28ac5..b40dedf 100644 --- a/__init__.py +++ b/__init__.py @@ -31,7 +31,6 @@ from bpy_extras.io_utils import ( IOOBJOrientationHelper = orientation_helper_factory("IOOBJOrientationHelper", axis_forward='-Z', axis_up='Y') - class ImportGDS(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper): """Load a GDSII File""" bl_idname = "import_scene.gds" @@ -44,110 +43,112 @@ class ImportGDS(bpy.types.Operator, ImportHelper, IOOBJOrientationHelper): options={'HIDDEN'}, ) - use_edges = BoolProperty( - name="Lines", - description="Import lines and faces with 2 verts as edge", - default=True, - ) - use_smooth_groups = BoolProperty( - name="Smooth Groups", - description="Surround smooth groups by sharp edges", - default=True, - ) + # use_edges = BoolProperty( + # name="Lines", + # description="Import lines and faces with 2 verts as edge", + # default=True, + # ) + # use_smooth_groups = BoolProperty( + # name="Smooth Groups", + # description="Surround smooth groups by sharp edges", + # default=True, + # ) - use_split_objects = BoolProperty( - name="Object", - description="Import OBJ Objects into Blender Objects", - default=True, - ) - use_split_groups = BoolProperty( - name="Group", - description="Import OBJ Groups into Blender Objects", - default=True, - ) + # use_split_objects = BoolProperty( + # name="Object", + # description="Import OBJ Objects into Blender Objects", + # default=True, + # ) + # use_split_groups = BoolProperty( + # name="Group", + # description="Import OBJ Groups into Blender Objects", + # default=True, + # ) - use_groups_as_vgroups = BoolProperty( - name="Poly Groups", - description="Import OBJ groups as vertex groups", - default=False, - ) + # use_groups_as_vgroups = BoolProperty( + # name="Poly Groups", + # description="Import OBJ groups as vertex groups", + # default=False, + # ) - use_image_search = BoolProperty( - name="Image Search", - description="Search subdirs for any associated images " - "(Warning, may be slow)", - default=True, - ) + # use_image_search = BoolProperty( + # name="Image Search", + # description="Search subdirs for any associated images " + # "(Warning, may be slow)", + # default=True, + # ) - split_mode = EnumProperty( - name="Split", - items=(('ON', "Split", "Split geometry, omits unused verts"), - ('OFF', "Keep Vert Order", "Keep vertex order from file"), - ), - ) + # split_mode = EnumProperty( + # name="Split", + # items=(('ON', "Split", "Split geometry, omits unused verts"), + # ('OFF', "Keep Vert Order", "Keep vertex order from file"), + # ), + # ) - global_clamp_size = FloatProperty( - name="Clamp Size", - description="Clamp bounds under this value (zero to disable)", - min=0.0, max=1000.0, - soft_min=0.0, soft_max=1000.0, - default=0.0, - ) + # global_clamp_size = FloatProperty( + # name="Clamp Size", + # description="Clamp bounds under this value (zero to disable)", + # min=0.0, max=1000.0, + # soft_min=0.0, soft_max=1000.0, + # default=0.0, + # ) + + top_cell_name = StringProperty( + name="Top Cell Name", + #descriptionn="When set, only import the structure with that name", + default="", + ) def execute(self, context): # print("Selected: " + context.active_object.name) from . import import_gds - if self.split_mode == 'OFF': - self.use_split_objects = False - self.use_split_groups = False - else: - self.use_groups_as_vgroups = False - + # ignore axis helper arguments keywords = self.as_keywords(ignore=("axis_forward", "axis_up", "filter_glob", "split_mode", )) - global_matrix = axis_conversion(from_forward=self.axis_forward, - from_up=self.axis_up, - ).to_4x4() - - keywords["global_matrix"] = global_matrix - keywords["use_cycles"] = (context.scene.render.engine == 'CYCLES') - if bpy.data.is_saved and context.user_preferences.filepaths.use_relative_paths: import os keywords["relpath"] = os.path.dirname(bpy.data.filepath) - return import_gds.load(context, **keywords) + result= import_gds.load(context, **keywords) + # properly report errors to user in ui + if len(result) != 0 and "CANCELLED" in result: + self.report({"ERROR_INVALID_INPUT"}, "Could not find structure with name {}".format(keywords["top_cell_name"])) + + return result def draw(self, context): layout = self.layout - row = layout.row(align=True) - row.prop(self, "use_smooth_groups") - row.prop(self, "use_edges") + row = layout.row() + row.prop(self, "top_cell_name") - box = layout.box() - row = box.row() - row.prop(self, "split_mode", expand=True) + # row = layout.row(align=True) + # row.prop(self, "use_smooth_groups") + # row.prop(self, "use_edges") - row = box.row() - if self.split_mode == 'ON': - row.label(text="Split by:") - row.prop(self, "use_split_objects") - row.prop(self, "use_split_groups") - else: - row.prop(self, "use_groups_as_vgroups") + # box = layout.box() + # row = box.row() + # row.prop(self, "split_mode", expand=True) - row = layout.split(percentage=0.67) - row.prop(self, "global_clamp_size") - layout.prop(self, "axis_forward") - layout.prop(self, "axis_up") + # row = box.row() + # if self.split_mode == 'ON': + # row.label(text="Split by:") + # row.prop(self, "use_split_objects") + # row.prop(self, "use_split_groups") + # else: + # row.prop(self, "use_groups_as_vgroups") - layout.prop(self, "use_image_search") + # row = layout.split(percentage=0.67) + # row.prop(self, "global_clamp_size") + # layout.prop(self, "axis_forward") + # layout.prop(self, "axis_up") + + # layout.prop(self, "use_image_search") diff --git a/import_gds.py b/import_gds.py index 7378408..a541c35 100644 --- a/import_gds.py +++ b/import_gds.py @@ -9,7 +9,7 @@ import bpy from bpy_extras.wm_utils.progress_report import ProgressReport class Progressor: - def __init__(self, wm, precision = 0.001): + def __init__(self, wm, precision = 0.0001): self.wm = wm self.report = 0 self.precision = precision @@ -26,10 +26,13 @@ class Progressor: if self.report: self.wm.progress_end() + self.next_update = 0 self.wm.progress_begin(0, t) self.report = t - + + print ("begin new report , total {}".format(t)) + @property def current(self): return 0 @@ -44,71 +47,229 @@ class Progressor: 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 -def build_object(context, layergroups, lib, struct): - name = struct.name +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] - verts = {} - faces = {} + def link(self, obj): + self.group.objects.link(obj) - print("structure name: {}".format(name)) + def __init__(self, context, lib, ignore_structures={}, top_cell=None): + self.lib = lib + self.context = context - root = bpy.data.objects.new(name, None) - context.scene.objects.link(root) + self.layers = {} + self.ignore_structures = {} + + 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 element in struct.elements: - # handle all elements - if isinstance(element, gds.Boundary): - if element.layer not in faces: - verts[element.layer] = [] - faces[element.layer] = [] + for sname in ignore_structures: + if sname in lib.structures: + self.ignore_structures[sname] = True - indices = [] - for point in element.points[:-1]: - pos = lib.normalize_coord(point) - verts[element.layer].append((pos[0], pos[1], 0)) - - indices.append(len(verts[element.layer])-1) - - faces[element.layer].append(tuple(indices)) - - # build structure object - for layer in verts.keys(): - if layer not in layergroups: - layergroups[layer] = bpy.data.groups.new("layer{}".format(layer)) - - meshname = "{}[{}]".format(name, layer) - - mesh = bpy.data.meshes.new(meshname) - mesh.from_pydata(verts[layer], [], faces[layer]) - mesh.update() - - obj = bpy.data.objects.new(meshname, mesh) - context.scene.objects.link(obj) - layergroups[layer].objects.link(obj) - obj.parent = root + def new_empty(self, name): + obj = bpy.data.objects.new(name, None) + self.link(obj) + return obj - return root + def link(self, obj): + self.context.scene.objects.link(obj) + + def build_object(self, structure): + # is the object already existent => return new instance + if structure.name in bpy.data.objects: + return self.new_instance(bpy.data.objects[structure.name]) + + verts = {} + faces = {} + + # 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 element in structure.elements: + if isinstance(element, gds.Boundary): + if element.layer not in faces: + verts[element.layer] = [] + faces[element.layer] = [] + + indices = [] + for point in element.points[:-1]: + pos = self.lib.normalize_coord(point) + verts[element.layer].append((pos[0], pos[1], 0)) + + indices.append(len(verts[element.layer])-1) + + 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) + + meshname = "{}[{}]".format(structure.name, layer) + + mesh = bpy.data.meshes.new(meshname) + mesh.from_pydata(verts[layer], [], faces[layer]) + mesh.update() + + obj = bpy.data.objects.new(meshname, mesh) + self.link(obj) + self.layers[layer].link(obj) + obj.parent = root + + # handle all references + # build tree depth first + for reference in structure.references: + obj = self.build_object(reference.structure) + + if hasattr(reference, "size"): + 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 = 1.0/reference.size[0] + yspace = 1.0/reference.size[1] + + master = obj + obj = self.new_empty(reference.structure.name) + master.parent = obj + master.location = (0,0,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) + + 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 + 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) + + return root + + def new_instance(self, obj): + root = obj.copy() + self.link(root) + + for child in obj.children: + childObj = child.copy() + self.link(childObj) + + childObj.parent = root + + # relink copy to groups + for group in child.users_group: + group.objects.link(childObj) + + return root + + + def __call__(self, progress=None): + class ProgressTracker: + def __init__(self, total, callback): + 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 + + self.build_object(self.lib.structures[self.top_cell]) + return True + + # build all structures in object + progressTracker = ProgressTracker(2*(len(self.lib.structures)-len(self.ignore_structures)), progress) + # 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) + progressTracker.total += len(value.references) + + + return True + +import math def load(context, filepath, *, - global_clamp_size=0.0, - use_smooth_groups=True, - use_edges=True, - use_split_objects=True, - use_split_groups=True, - use_image_search=True, - use_groups_as_vgroups=False, - use_cycles=True, - relpath=None, - global_matrix=None + top_cell_name="" ): path = pathlib.Path(filepath) @@ -121,38 +282,22 @@ def load(context, with open(filepath, "rb") as f: lib = gds.parse_file(f, progress_func=prog) - progress.step("Done, linking references") - lib.link_refs(prog) - progress.step("Done, building meshes") + progress.step("linking references") prog.end() - layergroups = {} + lib.link_refs(prog) - for name, value in lib.structures.items(): - build_object(context, layergroups, lib, value) + progress.step("inserting structures into scene") + prog.end() - # process all references - for name, value in lib.structures.items(): - root = bpy.data.objects[name] + if not SceneInserter(context=context, lib=lib, top_cell=top_cell_name)(prog): + return {"CANCELLED"} - for reference in value.references: - sname = reference.structure.name - - if not sname in bpy.data.objects: - print("object not build for referenced structure \"{}\"".format(sname)) - obj = build_object(context, layergroups, lib, reference.structure) - else: - obj = bpy.data.objects[sname] - - #create copy of object - obj = obj.copy() - - # reparent new instance - obj.parent = root - pos = lib.normalize_coord(reference.position) - obj.position = (pos[0], pos[1], 0) - progress.leave_substeps("Done") + prog.end() + + context.scene.update() + return {"FINISHED"}