blender-gdsimporter/import_gds.py

489 lines
15 KiB
Python

from . import pygds as gds
from concurrent.futures import ThreadPoolExecutor
import importlib
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"}