From e586ec7bcdca7442cd1c39ff6fc690bce2238fed Mon Sep 17 00:00:00 2001 From: Tom Musgrove Date: Fri, 5 Jan 2007 00:51:12 +0000 Subject: ==dxf import script == script for DXF import by kitsu (Ed Blake) - provide superior import to our native DXF import for many files --- release/scripts/bpymodules/dxfColorMap.py | 282 +++++ release/scripts/bpymodules/dxfImportObjects.py | 1326 ++++++++++++++++++++++++ release/scripts/bpymodules/dxfReader.py | 323 ++++++ release/scripts/import_dxf.py | 1005 ++++++++++++++++++ 4 files changed, 2936 insertions(+) create mode 100644 release/scripts/bpymodules/dxfColorMap.py create mode 100644 release/scripts/bpymodules/dxfImportObjects.py create mode 100644 release/scripts/bpymodules/dxfReader.py create mode 100644 release/scripts/import_dxf.py (limited to 'release') diff --git a/release/scripts/bpymodules/dxfColorMap.py b/release/scripts/bpymodules/dxfColorMap.py new file mode 100644 index 00000000000..66c0bd4e9a2 --- /dev/null +++ b/release/scripts/bpymodules/dxfColorMap.py @@ -0,0 +1,282 @@ +# dictionary mapping AutoCAD color indexes with Blender colors + +# -------------------------------------------------------------------------- +# color_map.py Final by Ed Blake (AKA Kitsu) +# -------------------------------------------------------------------------- +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ***** END GPL LICENCE BLOCK ***** +# -------------------------------------------------------------------------- + +color_map = { + 0:[0.0, 0.0, 0.0], + 1:[0.99609375, 0.0, 0.0], + 2:[0.99609375, 0.99609375, 0.0], + 3:[0.0, 0.99609375, 0.0], + 4:[0.0, 0.99609375, 0.99609375], + 5:[0.0, 0.0, 0.99609375], + 6:[0.99609375, 0.0, 0.99609375], + 7:[0.99609375, 0.99609375, 0.99609375], + 8:[0.25390625, 0.25390625, 0.25390625], + 9:[0.5, 0.5, 0.5], + 10:[0.99609375, 0.0, 0.0], + 11:[0.99609375, 0.6640625, 0.6640625], + 12:[0.73828125, 0.0, 0.0], + 13:[0.73828125, 0.4921875, 0.4921875], + 14:[0.50390625, 0.0, 0.0], + 15:[0.50390625, 0.3359375, 0.3359375], + 16:[0.40625, 0.0, 0.0], + 17:[0.40625, 0.26953125, 0.26953125], + 18:[0.30859375, 0.0, 0.0], + 19:[0.30859375, 0.20703125, 0.20703125], + 20:[0.99609375, 0.24609375, 0.0], + 21:[0.99609375, 0.74609375, 0.6640625], + 22:[0.73828125, 0.1796875, 0.0], + 23:[0.73828125, 0.55078125, 0.4921875], + 24:[0.50390625, 0.12109375, 0.0], + 25:[0.50390625, 0.375, 0.3359375], + 26:[0.40625, 0.09765625, 0.0], + 27:[0.40625, 0.3046875, 0.26953125], + 28:[0.30859375, 0.07421875, 0.0], + 29:[0.30859375, 0.23046875, 0.20703125], + 30:[0.99609375, 0.49609375, 0.0], + 31:[0.99609375, 0.828125, 0.6640625], + 32:[0.73828125, 0.3671875, 0.0], + 33:[0.73828125, 0.61328125, 0.4921875], + 34:[0.50390625, 0.25, 0.0], + 35:[0.50390625, 0.41796875, 0.3359375], + 36:[0.40625, 0.203125, 0.0], + 37:[0.40625, 0.3359375, 0.26953125], + 38:[0.30859375, 0.15234375, 0.0], + 39:[0.30859375, 0.2578125, 0.20703125], + 40:[0.99609375, 0.74609375, 0.0], + 41:[0.99609375, 0.9140625, 0.6640625], + 42:[0.73828125, 0.55078125, 0.0], + 43:[0.73828125, 0.67578125, 0.4921875], + 44:[0.50390625, 0.375, 0.0], + 45:[0.50390625, 0.4609375, 0.3359375], + 46:[0.40625, 0.3046875, 0.0], + 47:[0.40625, 0.37109375, 0.26953125], + 48:[0.30859375, 0.23046875, 0.0], + 49:[0.30859375, 0.28515625, 0.20703125], + 50:[0.99609375, 0.99609375, 0.0], + 51:[0.99609375, 0.99609375, 0.6640625], + 52:[0.73828125, 0.73828125, 0.0], + 53:[0.73828125, 0.73828125, 0.4921875], + 54:[0.50390625, 0.50390625, 0.0], + 55:[0.50390625, 0.50390625, 0.3359375], + 56:[0.40625, 0.40625, 0.0], + 57:[0.40625, 0.40625, 0.26953125], + 58:[0.30859375, 0.30859375, 0.0], + 59:[0.30859375, 0.30859375, 0.20703125], + 60:[0.74609375, 0.99609375, 0.0], + 61:[0.9140625, 0.99609375, 0.6640625], + 62:[0.55078125, 0.73828125, 0.0], + 63:[0.67578125, 0.73828125, 0.4921875], + 64:[0.375, 0.50390625, 0.0], + 65:[0.4609375, 0.50390625, 0.3359375], + 66:[0.3046875, 0.40625, 0.0], + 67:[0.37109375, 0.40625, 0.26953125], + 68:[0.23046875, 0.30859375, 0.0], + 69:[0.28515625, 0.30859375, 0.20703125], + 70:[0.49609375, 0.99609375, 0.0], + 71:[0.828125, 0.99609375, 0.6640625], + 72:[0.3671875, 0.73828125, 0.0], + 73:[0.61328125, 0.73828125, 0.4921875], + 74:[0.25, 0.50390625, 0.0], + 75:[0.41796875, 0.50390625, 0.3359375], + 76:[0.203125, 0.40625, 0.0], + 77:[0.3359375, 0.40625, 0.26953125], + 78:[0.15234375, 0.30859375, 0.0], + 79:[0.2578125, 0.30859375, 0.20703125], + 80:[0.24609375, 0.99609375, 0.0], + 81:[0.74609375, 0.99609375, 0.6640625], + 82:[0.1796875, 0.73828125, 0.0], + 83:[0.55078125, 0.73828125, 0.4921875], + 84:[0.12109375, 0.50390625, 0.0], + 85:[0.375, 0.50390625, 0.3359375], + 86:[0.09765625, 0.40625, 0.0], + 87:[0.3046875, 0.40625, 0.26953125], + 88:[0.07421875, 0.30859375, 0.0], + 89:[0.23046875, 0.30859375, 0.20703125], + 90:[0.0, 0.99609375, 0.0], + 91:[0.6640625, 0.99609375, 0.6640625], + 92:[0.0, 0.73828125, 0.0], + 93:[0.4921875, 0.73828125, 0.4921875], + 94:[0.0, 0.50390625, 0.0], + 95:[0.3359375, 0.50390625, 0.3359375], + 96:[0.0, 0.40625, 0.0], + 97:[0.26953125, 0.40625, 0.26953125], + 98:[0.0, 0.30859375, 0.0], + 99:[0.20703125, 0.30859375, 0.20703125], + 100:[0.0, 0.99609375, 0.24609375], + 101:[0.6640625, 0.99609375, 0.74609375], + 102:[0.0, 0.73828125, 0.1796875], + 103:[0.4921875, 0.73828125, 0.55078125], + 104:[0.0, 0.50390625, 0.12109375], + 105:[0.3359375, 0.50390625, 0.375], + 106:[0.0, 0.40625, 0.09765625], + 107:[0.26953125, 0.40625, 0.3046875], + 108:[0.0, 0.30859375, 0.07421875], + 109:[0.20703125, 0.30859375, 0.23046875], + 110:[0.0, 0.99609375, 0.49609375], + 111:[0.6640625, 0.99609375, 0.828125], + 112:[0.0, 0.73828125, 0.3671875], + 113:[0.4921875, 0.73828125, 0.61328125], + 114:[0.0, 0.50390625, 0.25], + 115:[0.3359375, 0.50390625, 0.41796875], + 116:[0.0, 0.40625, 0.203125], + 117:[0.26953125, 0.40625, 0.3359375], + 118:[0.0, 0.30859375, 0.15234375], + 119:[0.20703125, 0.30859375, 0.2578125], + 120:[0.0, 0.99609375, 0.74609375], + 121:[0.6640625, 0.99609375, 0.9140625], + 122:[0.0, 0.73828125, 0.55078125], + 123:[0.4921875, 0.73828125, 0.67578125], + 124:[0.0, 0.50390625, 0.375], + 125:[0.3359375, 0.50390625, 0.4609375], + 126:[0.0, 0.40625, 0.3046875], + 127:[0.26953125, 0.40625, 0.37109375], + 128:[0.0, 0.30859375, 0.23046875], + 129:[0.20703125, 0.30859375, 0.28515625], + 130:[0.0, 0.99609375, 0.99609375], + 131:[0.6640625, 0.99609375, 0.99609375], + 132:[0.0, 0.73828125, 0.73828125], + 133:[0.4921875, 0.73828125, 0.73828125], + 134:[0.0, 0.50390625, 0.50390625], + 135:[0.3359375, 0.50390625, 0.50390625], + 136:[0.0, 0.40625, 0.40625], + 137:[0.26953125, 0.40625, 0.40625], + 138:[0.0, 0.30859375, 0.30859375], + 139:[0.20703125, 0.30859375, 0.30859375], + 140:[0.0, 0.74609375, 0.99609375], + 141:[0.6640625, 0.9140625, 0.99609375], + 142:[0.0, 0.55078125, 0.73828125], + 143:[0.4921875, 0.67578125, 0.73828125], + 144:[0.0, 0.375, 0.50390625], + 145:[0.3359375, 0.4609375, 0.50390625], + 146:[0.0, 0.3046875, 0.40625], + 147:[0.26953125, 0.37109375, 0.40625], + 148:[0.0, 0.23046875, 0.30859375], + 149:[0.20703125, 0.28515625, 0.30859375], + 150:[0.0, 0.49609375, 0.99609375], + 151:[0.6640625, 0.828125, 0.99609375], + 152:[0.0, 0.3671875, 0.73828125], + 153:[0.4921875, 0.61328125, 0.73828125], + 154:[0.0, 0.25, 0.50390625], + 155:[0.3359375, 0.41796875, 0.50390625], + 156:[0.0, 0.203125, 0.40625], + 157:[0.26953125, 0.3359375, 0.40625], + 158:[0.0, 0.15234375, 0.30859375], + 159:[0.20703125, 0.2578125, 0.30859375], + 160:[0.0, 0.24609375, 0.99609375], + 161:[0.6640625, 0.74609375, 0.99609375], + 162:[0.0, 0.1796875, 0.73828125], + 163:[0.4921875, 0.55078125, 0.73828125], + 164:[0.0, 0.12109375, 0.50390625], + 165:[0.3359375, 0.375, 0.50390625], + 166:[0.0, 0.09765625, 0.40625], + 167:[0.26953125, 0.3046875, 0.40625], + 168:[0.0, 0.07421875, 0.30859375], + 169:[0.20703125, 0.23046875, 0.30859375], + 170:[0.0, 0.0, 0.99609375], + 171:[0.6640625, 0.6640625, 0.99609375], + 172:[0.0, 0.0, 0.73828125], + 173:[0.4921875, 0.4921875, 0.73828125], + 174:[0.0, 0.0, 0.50390625], + 175:[0.3359375, 0.3359375, 0.50390625], + 176:[0.0, 0.0, 0.40625], + 177:[0.26953125, 0.26953125, 0.40625], + 178:[0.0, 0.0, 0.30859375], + 179:[0.20703125, 0.20703125, 0.30859375], + 180:[0.24609375, 0.0, 0.99609375], + 181:[0.74609375, 0.6640625, 0.99609375], + 182:[0.1796875, 0.0, 0.73828125], + 183:[0.55078125, 0.4921875, 0.73828125], + 184:[0.12109375, 0.0, 0.50390625], + 185:[0.375, 0.3359375, 0.50390625], + 186:[0.09765625, 0.0, 0.40625], + 187:[0.3046875, 0.26953125, 0.40625], + 188:[0.07421875, 0.0, 0.30859375], + 189:[0.23046875, 0.20703125, 0.30859375], + 190:[0.49609375, 0.0, 0.99609375], + 191:[0.828125, 0.6640625, 0.99609375], + 192:[0.3671875, 0.0, 0.73828125], + 193:[0.61328125, 0.4921875, 0.73828125], + 194:[0.25, 0.0, 0.50390625], + 195:[0.41796875, 0.3359375, 0.50390625], + 196:[0.203125, 0.0, 0.40625], + 197:[0.3359375, 0.26953125, 0.40625], + 198:[0.15234375, 0.0, 0.30859375], + 199:[0.2578125, 0.20703125, 0.30859375], + 200:[0.74609375, 0.0, 0.99609375], + 201:[0.9140625, 0.6640625, 0.99609375], + 202:[0.55078125, 0.0, 0.73828125], + 203:[0.67578125, 0.4921875, 0.73828125], + 204:[0.375, 0.0, 0.50390625], + 205:[0.4609375, 0.3359375, 0.50390625], + 206:[0.3046875, 0.0, 0.40625], + 207:[0.37109375, 0.26953125, 0.40625], + 208:[0.23046875, 0.0, 0.30859375], + 209:[0.28515625, 0.20703125, 0.30859375], + 210:[0.99609375, 0.0, 0.99609375], + 211:[0.99609375, 0.6640625, 0.99609375], + 212:[0.73828125, 0.0, 0.73828125], + 213:[0.73828125, 0.4921875, 0.73828125], + 214:[0.50390625, 0.0, 0.50390625], + 215:[0.50390625, 0.3359375, 0.50390625], + 216:[0.40625, 0.0, 0.40625], + 217:[0.40625, 0.26953125, 0.40625], + 218:[0.30859375, 0.0, 0.30859375], + 219:[0.30859375, 0.20703125, 0.30859375], + 220:[0.99609375, 0.0, 0.74609375], + 221:[0.99609375, 0.6640625, 0.9140625], + 222:[0.73828125, 0.0, 0.55078125], + 223:[0.73828125, 0.4921875, 0.67578125], + 224:[0.50390625, 0.0, 0.375], + 225:[0.50390625, 0.3359375, 0.4609375], + 226:[0.40625, 0.0, 0.3046875], + 227:[0.40625, 0.26953125, 0.37109375], + 228:[0.30859375, 0.0, 0.23046875], + 229:[0.30859375, 0.20703125, 0.28515625], + 230:[0.99609375, 0.0, 0.49609375], + 231:[0.99609375, 0.6640625, 0.828125], + 232:[0.73828125, 0.0, 0.3671875], + 233:[0.73828125, 0.4921875, 0.61328125], + 234:[0.50390625, 0.0, 0.25], + 235:[0.50390625, 0.3359375, 0.41796875], + 236:[0.40625, 0.0, 0.203125], + 237:[0.40625, 0.26953125, 0.3359375], + 238:[0.30859375, 0.0, 0.15234375], + 239:[0.30859375, 0.20703125, 0.2578125], + 240:[0.99609375, 0.0, 0.24609375], + 241:[0.99609375, 0.6640625, 0.74609375], + 242:[0.73828125, 0.0, 0.1796875], + 243:[0.73828125, 0.4921875, 0.55078125], + 244:[0.50390625, 0.0, 0.12109375], + 245:[0.50390625, 0.3359375, 0.375], + 246:[0.40625, 0.0, 0.09765625], + 247:[0.40625, 0.26953125, 0.3046875], + 248:[0.30859375, 0.0, 0.07421875], + 249:[0.30859375, 0.20703125, 0.23046875], + 250:[0.19921875, 0.19921875, 0.19921875], + 251:[0.3125, 0.3125, 0.3125], + 252:[0.41015625, 0.41015625, 0.41015625], + 253:[0.5078125, 0.5078125, 0.5078125], + 254:[0.7421875, 0.7421875, 0.7421875], + 255:[0.99609375, 0.99609375, 0.99609375], +} diff --git a/release/scripts/bpymodules/dxfImportObjects.py b/release/scripts/bpymodules/dxfImportObjects.py new file mode 100644 index 00000000000..b78e91e8428 --- /dev/null +++ b/release/scripts/bpymodules/dxfImportObjects.py @@ -0,0 +1,1326 @@ +"""This module provides wrapper objects for dxf entities. + + The wrappers expect a "dxf object" as input. The dxf object is + an object with a type and a data attribute. Type is a lowercase + string matching the 0 code of a dxf entity. Data is a list containing + dxf objects or lists of [code, data] pairs. + + This module is not general, and is only for dxf import. +""" + +# -------------------------------------------------------------------------- +# DXF Import Objects v0.8 by Ed Blake (AKA Kitsu) +# -------------------------------------------------------------------------- +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ***** END GPL LICENCE BLOCK ***** +# -------------------------------------------------------------------------- +from math import * + + +# from Stani's dxf writer v1.1 (c)www.stani.be (GPL) +#---color values +BYBLOCK=0 +BYLAYER=256 + +#---block-type flags (bit coded values, may be combined): +ANONYMOUS =1 # This is an anonymous block generated by hatching, associative dimensioning, other internal operations, or an application +NON_CONSTANT_ATTRIBUTES =2 # This block has non-constant attribute definitions (this bit is not set if the block has any attribute definitions that are constant, or has no attribute definitions at all) +XREF =4 # This block is an external reference (xref) +XREF_OVERLAY =8 # This block is an xref overlay +EXTERNAL =16 # This block is externally dependent +RESOLVED =32 # This is a resolved external reference, or dependent of an external reference (ignored on input) +REFERENCED =64 # This definition is a referenced external reference (ignored on input) + +#---mtext flags +#attachment point +TOP_LEFT = 1 +TOP_CENTER = 2 +TOP_RIGHT = 3 +MIDDLE_LEFT = 4 +MIDDLE_CENTER = 5 +MIDDLE_RIGHT = 6 +BOTTOM_LEFT = 7 +BOTTOM_CENTER = 8 +BOTTOM_RIGHT = 9 +#drawing direction +LEFT_RIGHT = 1 +TOP_BOTTOM = 3 +BY_STYLE = 5 #the flow direction is inherited from the associated text style +#line spacing style (optional): +AT_LEAST = 1 #taller characters will override +EXACT = 2 #taller characters will not override + +#---polyline flags +CLOSED =1 # This is a closed polyline (or a polygon mesh closed in the M direction) +CURVE_FIT =2 # Curve-fit vertices have been added +SPLINE_FIT =4 # Spline-fit vertices have been added +POLYLINE_3D =8 # This is a 3D polyline +POLYGON_MESH =16 # This is a 3D polygon mesh +CLOSED_N =32 # The polygon mesh is closed in the N direction +POLYFACE_MESH =64 # The polyline is a polyface mesh +CONTINOUS_LINETYPE_PATTERN =128 # The linetype pattern is generated continuously around the vertices of this polyline + +#---text flags +#horizontal +LEFT = 0 +CENTER = 1 +RIGHT = 2 +ALIGNED = 3 #if vertical alignment = 0 +MIDDLE = 4 #if vertical alignment = 0 +FIT = 5 #if vertical alignment = 0 +#vertical +BASELINE = 0 +BOTTOM = 1 +MIDDLE = 2 +TOP = 3 +class Object: + """Empty container class for dxf objects""" + + def __init__(self, _type=''): + """_type expects a string value.""" + self.type = _type + self.name = '' + self.data = [] + + def __str__(self): + if self.name: + return self.name + else: + return self.type + + def __repr__(self): + return str(self.data) + + def get_type(self, kind=''): + """Despite the name, this method actually returns all objects of type 'kind' from self.data.""" + if type: + objects = [] + for item in self.data: + if type(item) != list and item.type == kind: + # we want this type of object + objects.append(item) + elif type(item) == list and item[0] == kind: + # we want this type of data + objects.append(item[1]) + return objects + + +class Layer: + """Class for objects representing dxf layers.""" + + def __init__(self, obj): + """Expects an entity object of type line as input.""" + self.type = obj.type + self.data = obj.data[:] + + self.name = obj.get_type(2)[0] + self.color = obj.get_type(62)[0] + self.flags = obj.get_type(70)[0] + self.frozen = self.flags&1 + + + + def __repr__(self): + return "%s: name - %s, color - %s" %(self.__class__.__name__, self.name, self.color) + + + +class Line: + """Class for objects representing dxf lines.""" + + def __init__(self, obj): + """Expects an entity object of type line as input.""" + if not obj.type == 'line': + raise TypeError, "Wrong type %s for line object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + + self.space = obj.get_type(67) + if self.space: + self.space = self.space[0] + else: + self.space = 0 + + self.color_index = obj.get_type(62) + if self.color_index: + self.color_index = self.color_index[0] + else: + self.color_index = BYLAYER + + discard, self.layer = get_layer(obj.data) + obj.data.remove(discard) + self.points = self.get_points(obj.data) + + + + + def get_points(self, data): + """Gets start and end points for a line type object. + + Lines have a fixed number of points (two) and fixed codes for each value. + """ + + # start x, y, z and end x, y, z = 0 + sx, sy, sz, ex, ey, ez = 0, 0, 0, 0, 0, 0 + for item in data: + if item[0] == 10: # 10 = x + sx = item[1] + elif item[0] == 20: # 20 = y + sy = item[1] + elif item[0] == 30: # 30 = z + sz = item[1] + elif item[0] == 11: # 11 = x + ex = item[1] + elif item[0] == 21: # 21 = y + ey = item[1] + elif item[0] == 31: # 31 = z + ez = item[1] + return [[sx, sy, sz], [ex, ey, ez]] + + + + def __repr__(self): + return "%s: layer - %s, points - %s" %(self.__class__.__name__, self.layer, self.points) + + + +class LWpolyline: + """Class for objects representing dxf LWpolylines.""" + + def __init__(self, obj): + """Expects an entity object of type lwpolyline as input.""" + if not obj.type == 'lwpolyline': + raise TypeError, "Wrong type %s for polyline object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + + # required data + self.num_points = obj.get_type(90)[0] + + # optional data (with defaults) + self.space = obj.get_type(67) + if self.space: + self.space = self.space[0] + else: + self.space = 0 + + self.color_index = obj.get_type(62) + if self.color_index: + self.color_index = self.color_index[0] + else: + self.color_index = BYLAYER + + self.elevation = obj.get_type(38) + if self.elevation: + self.elevation = self.elevation[0] + else: + self.elevation = 0 + + self.flags = obj.get_type(70) + if self.flags: + self.flags = self.flags[0] + else: + self.flags = 0 + + self.closed = self.flags&1 # byte coded, 1 = closed, 128 = plinegen + discard, self.layer = get_layer(obj.data) + obj.data.remove(discard) + self.points = self.get_points(obj.data) + self.extrusion = self.get_extrusion(obj.data) + + + + + + + def get_points(self, data): + """Gets points for a polyline type object. + + Polylines have no fixed number of verts, and + each vert can have a number of properties. + Verts should be coded as + 10:xvalue + 20:yvalue + 40:startwidth or 0 + 41:endwidth or 0 + 42:bulge or 0 + for each vert + """ + num = self.num_points + point = None + points = [] + for item in data: + if item[0] == 10: # 10 = x + if point: + points.append(point) + point = Vertex() + point.x = item[1] + elif item[0] == 20: # 20 = y + point.y = item[1] + elif item[0] == 40: # 40 = start width + point.swidth = item[1] + elif item[0] == 41: # 41 = end width + point.ewidth = item[1] + elif item[0] == 42: # 42 = bulge + point.bulge = item[1] + points.append(point) + return points + + + def get_extrusion(self, data): + """Find the axis of extrusion. + + Used to get the objects Object Coordinate System (ocs). + """ + vec = [0,0,1] + for item in data: + if item[0] == 210: # 210 = x + vec[0] = item[1] + elif item[0] == 220: # 220 = y + vec[1] = item[1] + elif item[0] == 230: # 230 = z + vec[2] = item[1] + return vec + + + def __repr__(self): + return "%s: layer - %s, points - %s" %(self.__class__.__name__, self.layer, self.points) + + + +class Polyline: + """Class for objects representing dxf LWpolylines.""" + + def __init__(self, obj): + """Expects an entity object of type polyline as input.""" + if not obj.type == 'polyline': + raise TypeError, "Wrong type %s for polyline object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + self.points = [] + + # optional data (with defaults) + self.space = obj.get_type(67) + if self.space: + self.space = self.space[0] + else: + self.space = 0 + + self.color_index = obj.get_type(62) + if self.color_index: + self.color_index = self.color_index[0] + else: + self.color_index = BYLAYER + + self.elevation = obj.get_type(30) + if self.elevation: + self.elevation = self.elevation[0] + else: + self.elevation = 0 + + self.flags = obj.get_type(70) + if self.flags: + self.flags = self.flags[0] + else: + self.flags = 0 + + self.closed = self.flags&1 # byte coded, 1 = closed, 128 = plinegen + + discard, self.layer = get_layer(obj.data) + obj.data.remove(discard) + self.extrusion = self.get_extrusion(obj.data) + + + + + + def get_extrusion(self, data): + """Find the axis of extrusion. + + Used to get the objects Object Coordinate System (ocs). + """ + vec = [0,0,1] + for item in data: + if item[0] == 210: # 210 = x + vec[0] = item[1] + elif item[0] == 220: # 220 = y + vec[1] = item[1] + elif item[0] == 230: # 230 = z + vec[2] = item[1] + return vec + + + def __repr__(self): + return "%s: layer - %s, points - %s" %(self.__class__.__name__, self.layer, self.points) + + + +class Vertex(object): + """Generic vertex object used by polylines (and maybe others).""" + + def __init__(self, obj=None): + """Initializes vertex data. + + The optional obj arg is an entity object of type vertex. + """ + self.loc = [0,0,0] + self.bulge = 0 + self.swidth = 0 + self.ewidth = 0 + self.flags = 0 + + if obj is not None: + if not obj.type == 'vertex': + raise TypeError, "Wrong type %s for vertex object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + + self.get_props(obj.data) + + + def get_props(self, data): + """Gets coords for a vertex type object. + + Each vert can have a number of properties. + Verts should be coded as + 10:xvalue + 20:yvalue + 40:startwidth or 0 + 41:endwidth or 0 + 42:bulge or 0 + """ + for item in data: + if item[0] == 10: # 10 = x + self.x = item[1] + elif item[0] == 20: # 20 = y + self.y = item[1] + elif item[0] == 30: # 30 = z + self.z = item[1] + elif item[0] == 40: # 40 = start width + self.swidth = item[1] + elif item[0] == 41: # 41 = end width + self.ewidth = item[1] + elif item[0] == 42: # 42 = bulge + self.bulge = item[1] + elif item[0] == 70: # 70 = vert flags + self.flags = item[1] + + + def __len__(self): + return 3 + + + def __getitem__(self, key): + return self.loc[key] + + + def __setitem__(self, key, value): + if key in [0,1,2]: + self.loc[key] + + + def __iter__(self): + return self.loc.__iter__() + + + def __str__(self): + return str(self.loc) + + + def __repr__(self): + return "Vertex %s, swidth=%s, ewidth=%s, bulge=%s" %(self.loc, self.swidth, self.ewidth, self.bulge) + + + def getx(self): + return self.loc[0] + + def setx(self, value): + self.loc[0] = value + + x = property(getx, setx) + + + def gety(self): + return self.loc[1] + + def sety(self, value): + self.loc[1] = value + + y = property(gety, sety) + + + def getz(self): + return self.loc[2] + + def setz(self, value): + self.loc[2] = value + + z = property(getz, setz) + + + +class Text: + """Class for objects representing dxf Text.""" + + def __init__(self, obj): + """Expects an entity object of type text as input.""" + if not obj.type == 'text': + raise TypeError, "Wrong type %s for text object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + + # required data + self.height = obj.get_type(40)[0] + self.value = obj.get_type(1)[0] # The text string value + + # optional data (with defaults) + self.space = obj.get_type(67) + if self.space: + self.space = self.space[0] + else: + self.space = 0 + + self.color_index = obj.get_type(62) + if self.color_index: + self.color_index = self.color_index[0] + else: + self.color_index = BYLAYER + + self.rotation = obj.get_type(50) # radians? + if not self.rotation: + self.rotation = 0 + else: + self.rotation = self.rotation[0] + + self.width_factor = obj.get_type(41) # Scaling factor along local x axis + if not self.width_factor: + self.width_factor = 1 + else: + self.width_factor = self.width_factor[0] + + self.oblique = obj.get_type(51) # skew in degrees -90 <= oblique <= 90 + if not self.oblique: + self.oblique = 0 + else: + self.oblique = self.oblique[0] + + self.halignment = obj.get_type(72) # horiz. alignment + if not self.halignment: # 0=left, 1=center, 2=right, 3=aligned, 4=middle, 5=fit + self.halignment = 0 + else: + self.halignment = self.halignment[0] + + self.valignment = obj.get_type(73) # vert. alignment + if not self.valignment: # 0=baseline, 1=bottom, 2=middle, 3=top + self.valignment = 0 + else: + self.valignment = self.valignment[0] + + discard, self.layer = get_layer(obj.data) + obj.data.remove(discard) + self.loc = self.get_loc(obj.data, self.halignment, self.valignment) + self.extrusion = self.get_extrusion(obj.data) + + + + + def get_loc(self, data, halign, valign): + """Gets adjusted location for text type objects. + + If group 72 and/or 73 values are nonzero then the first alignment point values + are ignored and AutoCAD calculates new values based on the second alignment + point and the length and height of the text string itself (after applying the + text style). If the 72 and 73 values are zero or missing, then the second + alignment point is meaningless. + + I don't know how to calc text size... + """ + # bottom left x, y, z and justification x, y, z = 0 + x, y, z, jx, jy, jz = 0, 0, 0, 0, 0, 0 + for item in data: + if item[0] == 10: # 10 = x + x = item[1] + elif item[0] == 20: # 20 = y + y = item[1] + elif item[0] == 30: # 30 = z + z = item[1] + elif item[0] == 11: # 11 = x + jx = item[1] + elif item[0] == 21: # 21 = y + jy = item[1] + elif item[0] == 31: # 31 = z + jz = item[1] + + if halign or valign: + x, y, z = jx, jy, jz + return [x, y, z] + + def get_extrusion(self, data): + """Find the axis of extrusion. + + Used to get the objects Object Coordinate System (ocs). + """ + vec = [0,0,1] + for item in data: + if item[0] == 210: # 210 = x + vec[0] = item[1] + elif item[0] == 220: # 220 = y + vec[1] = item[1] + elif item[0] == 230: # 230 = z + vec[2] = item[1] + return vec + + + def __repr__(self): + return "%s: layer - %s, value - %s" %(self.__class__.__name__, self.layer, self.value) + + + +class Mtext: + """Class for objects representing dxf Mtext.""" + + def __init__(self, obj): + """Expects an entity object of type mtext as input.""" + if not obj.type == 'mtext': + raise TypeError, "Wrong type %s for mtext object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + + # required data + self.height = obj.get_type(40)[0] + self.width = obj.get_type(41)[0] + self.alignment = obj.get_type(71)[0] # alignment 1=TL, 2=TC, 3=TR, 4=ML, 5=MC, 6=MR, 7=BL, 8=BC, 9=BR + self.value = self.get_text(obj.data) # The text string value + + # optional data (with defaults) + self.space = obj.get_type(67) + if self.space: + self.space = self.space[0] + else: + self.space = 0 + + self.color_index = obj.get_type(62) + if self.color_index: + self.color_index = self.color_index[0] + else: + self.color_index = BYLAYER + + self.rotation = obj.get_type(50) # radians + if not self.rotation: + self.rotation = 0 + else: + self.rotation = self.rotation[0] + + self.width_factor = obj.get_type(42) # Scaling factor along local x axis + if not self.width_factor: + self.width_factor = 1 + else: + self.width_factor = self.width_factor[0] + + self.line_space = obj.get_type(44) # percentage of default + if not self.line_space: + self.line_space = 1 + else: + self.line_space = self.line_space[0] + + discard, self.layer = get_layer(obj.data) + obj.data.remove(discard) + self.loc = self.get_loc(obj.data) + self.extrusion = self.get_extrusion(obj.data) + + + + + + def get_text(self, data): + """Reconstructs mtext data from dxf codes.""" + primary = '' + secondary = [] + for item in data: + if item[0] == 1: # There should be only one primary... + primary = item[1] + elif item[0] == 3: # There may be any number of extra strings (in order) + secondary.append(item[1]) + if not primary: + #raise ValueError, "Empty Mtext Object!" + string = "Empty Mtext Object!" + if not secondary: + string = primary.replace(r'\P', '\n') + else: + string = ''.join(secondary)+primary + string = string.replace(r'\P', '\n') + return string + def get_loc(self, data): + """Gets location for a mtext type objects. + + Mtext objects have only one point indicating location. + """ + loc = [0,0,0] + for item in data: + if item[0] == 10: # 10 = x + loc[0] = item[1] + elif item[0] == 20: # 20 = y + loc[1] = item[1] + elif item[0] == 30: # 30 = z + loc[2] = item[1] + return loc + + + + + def get_extrusion(self, data): + """Find the axis of extrusion. + + Used to get the objects Object Coordinate System (ocs). + """ + vec = [0,0,1] + for item in data: + if item[0] == 210: # 210 = x + vec[0] = item[1] + elif item[0] == 220: # 220 = y + vec[1] = item[1] + elif item[0] == 230: # 230 = z + vec[2] = item[1] + return vec + + + def __repr__(self): + return "%s: layer - %s, value - %s" %(self.__class__.__name__, self.layer, self.value) + + + +class Circle: + """Class for objects representing dxf Circles.""" + + def __init__(self, obj): + """Expects an entity object of type circle as input.""" + if not obj.type == 'circle': + raise TypeError, "Wrong type %s for circle object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + + # required data + self.radius = obj.get_type(40)[0] + + # optional data (with defaults) + self.space = obj.get_type(67) + if self.space: + self.space = self.space[0] + else: + self.space = 0 + + self.color_index = obj.get_type(62) + if self.color_index: + self.color_index = self.color_index[0] + else: + self.color_index = BYLAYER + + discard, self.layer = get_layer(obj.data) + obj.data.remove(discard) + self.loc = self.get_loc(obj.data) + self.extrusion = self.get_extrusion(obj.data) + + + + + + def get_loc(self, data): + """Gets the center location for circle type objects. + + Circles have a single coord location. + """ + loc = [0, 0, 0] + for item in data: + if item[0] == 10: # 10 = x + loc[0] = item[1] + elif item[0] == 20: # 20 = y + loc[1] = item[1] + elif item[0] == 30: # 30 = z + loc[2] = item[1] + return loc + + + + def get_extrusion(self, data): + """Find the axis of extrusion. + + Used to get the objects Object Coordinate System (ocs). + """ + vec = [0,0,1] + for item in data: + if item[0] == 210: # 210 = x + vec[0] = item[1] + elif item[0] == 220: # 220 = y + vec[1] = item[1] + elif item[0] == 230: # 230 = z + vec[2] = item[1] + return vec + + + def __repr__(self): + return "%s: layer - %s, radius - %s" %(self.__class__.__name__, self.layer, self.radius) + + + +class Arc: + """Class for objects representing dxf arcs.""" + + def __init__(self, obj): + """Expects an entity object of type arc as input.""" + if not obj.type == 'arc': + raise TypeError, "Wrong type %s for arc object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + + # required data + self.radius = obj.get_type(40)[0] + self.start_angle = obj.get_type(50)[0] + self.end_angle = obj.get_type(51)[0] + + # optional data (with defaults) + self.space = obj.get_type(67) + if self.space: + self.space = self.space[0] + else: + self.space = 0 + + self.color_index = obj.get_type(62) + if self.color_index: + self.color_index = self.color_index[0] + else: + self.color_index = BYLAYER + + discard, self.layer = get_layer(obj.data) + obj.data.remove(discard) + self.loc = self.get_loc(obj.data) + self.extrusion = self.get_extrusion(obj.data) + + + + + + def get_loc(self, data): + """Gets the center location for arc type objects. + + Arcs have a single coord location. + """ + loc = [0, 0, 0] + for item in data: + if item[0] == 10: # 10 = x + loc[0] = item[1] + elif item[0] == 20: # 20 = y + loc[1] = item[1] + elif item[0] == 30: # 30 = z + loc[2] = item[1] + return loc + + + + def get_extrusion(self, data): + """Find the axis of extrusion. + + Used to get the objects Object Coordinate System (ocs). + """ + vec = [0,0,1] + for item in data: + if item[0] == 210: # 210 = x + vec[0] = item[1] + elif item[0] == 220: # 220 = y + vec[1] = item[1] + elif item[0] == 230: # 230 = z + vec[2] = item[1] + return vec + + + def __repr__(self): + return "%s: layer - %s, radius - %s" %(self.__class__.__name__, self.layer, self.radius) + + + +class BlockRecord: + """Class for objects representing dxf block_records.""" + + def __init__(self, obj): + """Expects an entity object of type block_record as input.""" + if not obj.type == 'block_record': + raise TypeError, "Wrong type %s for block_record object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + + # required data + self.name = obj.get_type(2)[0] + + # optional data (with defaults) + self.insertion_units = obj.get_type(70) + if not self.insertion_units: + self.insertion_units = None + else: + self.insertion_units = self.insertion_units[0] + + self.insert_units = obj.get_type(1070) + if not self.insert_units: + self.insert_units = None + else: + self.insert_units = self.insert_units[0] + + + + + + + def __repr__(self): + return "%s: name - %s, insert units - %s" %(self.__class__.__name__, self.name, self.insertion_units) + + + + +class Block: + """Class for objects representing dxf blocks.""" + + def __init__(self, obj): + """Expects an entity object of type block as input.""" + if not obj.type == 'block': + raise TypeError, "Wrong type %s for block object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + + # required data + self.flags = obj.get_type(70)[0] + self.entities = Object('block_contents') + self.entities.data = objectify([ent for ent in obj.data if type(ent) != list]) + + # optional data (with defaults) + self.name = obj.get_type(3) + if self.name: + self.name = self.name[0] + else: + self.name = '' + + self.path = obj.get_type(1) + if self.path: + self.path = self.path[0] + else: + self.path = '' + + self.discription = obj.get_type(4) + if self.discription: + self.discription = self.discription[0] + else: + self.discription = '' + + discard, self.layer = get_layer(obj.data) + obj.data.remove(discard) + self.loc = self.get_loc(obj.data) + + + + + + def get_loc(self, data): + """Gets the insert point of the block.""" + loc = [0, 0, 0] + for item in data: + if type(item) != list: + continue + if item[0] == 10: # 10 = x + loc[0] = item[1] + elif item[0] == 20: # 20 = y + loc[1] = item[1] + elif item[0] == 30: # 30 = z + loc[2] = item[1] + return loc + + + + def __repr__(self): + return "%s: name - %s, description - %s, xref-path - %s" %(self.__class__.__name__, self.name, self.discription, self.path) + + + + +class Insert: + """Class for objects representing dxf inserts.""" + + def __init__(self, obj): + """Expects an entity object of type insert as input.""" + if not obj.type == 'insert': + raise TypeError, "Wrong type %s for insert object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + + # required data + self.block = obj.get_type(2)[0] + + # optional data (with defaults) + self.rotation = obj.get_type(50) + if self.rotation: + self.rotation = self.rotation[0] + else: + self.rotation = 0 + + self.space = obj.get_type(67) + if self.space: + self.space = self.space[0] + else: + self.space = 0 + + self.color_index = obj.get_type(62) + if self.color_index: + self.color_index = self.color_index[0] + else: + self.color_index = BYLAYER + + discard, self.layer = get_layer(obj.data) + obj.data.remove(discard) + self.loc = self.get_loc(obj.data) + self.scale = self.get_scale(obj.data) + self.rows, self.columns = self.get_array(obj.data) + self.extrusion = self.get_extrusion(obj.data) + + + + + + def get_loc(self, data): + """Gets the center location for circle type objects. + + Circles have a single coord location. + """ + loc = [0, 0, 0] + for item in data: + if item[0] == 10: # 10 = x + loc[0] = item[1] + elif item[0] == 20: # 20 = y + loc[1] = item[1] + elif item[0] == 30: # 30 = z + loc[2] = item[1] + return loc + + + + def get_scale(self, data): + """Gets the x/y/z scale factor for the block. + """ + scale = [1, 1, 1] + for item in data: + if item[0] == 41: # 41 = x scale + scale[0] = item[1] + elif item[0] == 42: # 42 = y scale + scale[1] = item[1] + elif item[0] == 43: # 43 = z scale + scale[2] = item[1] + return scale + + + + def get_array(self, data): + """Returns the pair (row number, row spacing), (column number, column spacing).""" + columns = 1 + rows = 1 + cspace = 0 + rspace = 0 + for item in data: + if item[0] == 70: # 70 = columns + columns = item[1] + elif item[0] == 71: # 71 = rows + rows = item[1] + if item[0] == 44: # 44 = columns + cspace = item[1] + elif item[0] == 45: # 45 = rows + rspace = item[1] + return (rows, rspace), (columns, cspace) + + + + def get_extrusion(self, data): + """Find the axis of extrusion. + + Used to get the objects Object Coordinate System (ocs). + """ + vec = [0,0,1] + for item in data: + if item[0] == 210: # 210 = x + vec[0] = item[1] + elif item[0] == 220: # 220 = y + vec[1] = item[1] + elif item[0] == 230: # 230 = z + vec[2] = item[1] + return vec + + + def __repr__(self): + return "%s: layer - %s, block - %s" %(self.__class__.__name__, self.layer, self.block) + + + + +class Ellipse: + """Class for objects representing dxf ellipses.""" + + def __init__(self, obj): + """Expects an entity object of type ellipse as input.""" + if not obj.type == 'ellipse': + raise TypeError, "Wrong type %s for ellipse object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + + # required data + self.ratio = obj.get_type(40)[0] + self.start_angle = obj.get_type(41)[0] + self.end_angle = obj.get_type(42)[0] + + # optional data (with defaults) + self.space = obj.get_type(67) + if self.space: + self.space = self.space[0] + else: + self.space = 0 + + self.color_index = obj.get_type(62) + if self.color_index: + self.color_index = self.color_index[0] + else: + self.color_index = BYLAYER + + discard, self.layer = get_layer(obj.data) + obj.data.remove(discard) + self.loc = self.get_loc(obj.data) + self.major = self.get_major(obj.data) + self.extrusion = self.get_extrusion(obj.data) + self.radius = sqrt(self.major[0]**2 + self.major[0]**2 + self.major[0]**2) + + + + + def get_loc(self, data): + """Gets the center location for arc type objects. + + Arcs have a single coord location. + """ + loc = [0, 0, 0] + for item in data: + if item[0] == 10: # 10 = x + loc[0] = item[1] + elif item[0] == 20: # 20 = y + loc[1] = item[1] + elif item[0] == 30: # 30 = z + loc[2] = item[1] + return loc + + + + def get_major(self, data): + """Gets the major axis for ellipse type objects. + + The ellipse major axis defines the rotation of the ellipse and its radius. + """ + loc = [0, 0, 0] + for item in data: + if item[0] == 11: # 11 = x + loc[0] = item[1] + elif item[0] == 21: # 21 = y + loc[1] = item[1] + elif item[0] == 31: # 31 = z + loc[2] = item[1] + return loc + + + + def get_extrusion(self, data): + """Find the axis of extrusion. + + Used to get the objects Object Coordinate System (ocs). + """ + vec = [0,0,1] + for item in data: + if item[0] == 210: # 210 = x + vec[0] = item[1] + elif item[0] == 220: # 220 = y + vec[1] = item[1] + elif item[0] == 230: # 230 = z + vec[2] = item[1] + return vec + + + def __repr__(self): + return "%s: layer - %s, radius - %s" %(self.__class__.__name__, self.layer, self.radius) + + + +class Face: + """Class for objects representing dxf 3d faces.""" + + def __init__(self, obj): + """Expects an entity object of type 3dfaceplot as input.""" + if not obj.type == '3dface': + raise TypeError, "Wrong type %s for 3dface object!" %obj.type + self.type = obj.type + self.data = obj.data[:] + + # optional data (with defaults) + self.space = obj.get_type(67) + if self.space: + self.space = self.space[0] + else: + self.space = 0 + + self.color_index = obj.get_type(62) + if self.color_index: + self.color_index = self.color_index[0] + else: + self.color_index = BYLAYER + + discard, self.layer = get_layer(obj.data) + obj.data.remove(discard) + self.points = self.get_points(obj.data) + + + + + def get_points(self, data): + """Gets 3-4 points for a 3d face type object. + + Faces have three or optionally four verts. + """ + + a = [0, 0, 0] + b = [0, 0, 0] + c = [0, 0, 0] + d = False + for item in data: + # ----------- a ------------- + if item[0] == 10: # 10 = x + a[0] = item[1] + elif item[0] == 20: # 20 = y + a[1] = item[1] + elif item[0] == 30: # 30 = z + a[2] = item[1] + # ----------- b ------------- + elif item[0] == 11: # 11 = x + b[0] = item[1] + elif item[0] == 21: # 21 = y + b[1] = item[1] + elif item[0] == 31: # 31 = z + b[2] = item[1] + # ----------- c ------------- + elif item[0] == 12: # 12 = x + c[0] = item[1] + elif item[0] == 22: # 22 = y + c[1] = item[1] + elif item[0] == 32: # 32 = z + c[2] = item[1] + # ----------- d ------------- + elif item[0] == 13: # 13 = x + d = [0, 0, 0] + d[0] = item[1] + elif item[0] == 23: # 23 = y + d[1] = item[1] + elif item[0] == 33: # 33 = z + d[2] = item[1] + out = [a,b,c] + if d: + out.append(d) + return out + + + def __repr__(self): + return "%s: layer - %s, points - %s" %(self.__class__.__name__, self.layer, self.points) + + + +def get_name(data): + """Get the name of an object from its object data. + + Returns a pair of (data_item, name) where data_item is the list entry where the name was found + (the data_item can be used to remove the entry from the object data). Be sure to check + name not None before using the returned values! + """ + value = None + for item in data: + if item[0] == 2: + value = item[1] + break + return item, value + +def get_layer(data): + """Expects object data as input. + + Returns (entry, layer_name) where entry is the data item that provided the layer name. + """ + value = None + for item in data: + if item[0] == 8: + value = item[1] + break + return item, value + + +# type to object map +type_map = { + 'line':Line, + 'lwpolyline':LWpolyline, + 'text':Text, + 'mtext':Mtext, + 'circle':Circle, + 'arc':Arc, + 'layer':Layer, + 'block_record':BlockRecord, + 'block':Block, + 'insert':Insert, + 'ellipse':Ellipse, + '3dface':Face +} + +def objectify(data): + """Expects a section type object's data as input. + + Maps object data to the correct object type. + """ + objects = [] # colector for finished objects + known_types = type_map.keys() # so we don't have to call foo.keys() every iteration + index = 0 + while index < len(data): + item = data[index] + if type(item) != list and item.type in known_types: + # proccess the object and append the resulting object + objects.append(type_map[item.type](item)) + elif type(item) != list and item.type == 'table': + item.data = objectify(item.data) # tables have sub-objects + objects.append(item) + elif type(item) != list and item.type == 'polyline': + pline = Polyline(item) + while 1: + index += 1 + item = data[index] + if item.type == 'vertex': + v = Vertex(item) + pline.points.append(v) + elif item.type == 'seqend': + break + else: + print "Error: non-vertex found before seqend!" + index -= 1 + break + objects.append(pline) + else: + # we will just let the data pass un-harrased + objects.append(item) + index += 1 + return objects +if __name__ == "__main__": + print "No example yet!" \ No newline at end of file diff --git a/release/scripts/bpymodules/dxfReader.py b/release/scripts/bpymodules/dxfReader.py new file mode 100644 index 00000000000..8d122173b82 --- /dev/null +++ b/release/scripts/bpymodules/dxfReader.py @@ -0,0 +1,323 @@ +"""This module provides a function for reading dxf files and parsing them into a useful tree of objects and data. + + The convert function is called by the readDXF fuction to convert dxf strings into the correct data based + on their type code. readDXF expects a (full path) file name as input. +""" + +# -------------------------------------------------------------------------- +# DXF Reader v0.8 by Ed Blake (AKA Kitsu) +# -------------------------------------------------------------------------- +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ***** END GPL LICENCE BLOCK ***** +# -------------------------------------------------------------------------- + + +from dxfImportObjects import * + +class InitializationError(Exception): pass + +class StateMachine: + """(finite) State Machine from the great David Mertz's great Charming Python article.""" + + def __init__(self): + self.handlers = [] + self.startState = None + self.endStates = [] + + def add_state(self, handler, end_state=0): + """All states and handlers are functions which return + a state and a cargo.""" + self.handlers.append(handler) + if end_state: + self.endStates.append(handler) + def set_start(self, handler): + """Sets the starting handler function.""" + self.startState = handler + + + def run(self, cargo=None): + if not self.startState: + raise InitializationError,\ + "must call .set_start() before .run()" + if not self.endStates: + raise InitializationError, \ + "at least one state must be an end_state" + handler = self.startState + while 1: + (newState, cargo) = handler(cargo) + #print cargo + if newState in self.endStates: + return newState(cargo) + #break + elif newState not in self.handlers: + raise RuntimeError, "Invalid target %s" % newState + else: + handler = newState + +def convert(code, value): + """Convert a string to the correct Python type based on its dxf code. + code types: + ints = 60-79, 170-179, 270-289, 370-389, 400-409, 1060-1070 + longs = 90-99, 420-429, 440-459, 1071 + floats = 10-39, 40-59, 110-139, 140-149, 210-239, 460-469, 1010-1059 + hex = 105, 310-379, 390-399 + strings = 0-9, 100, 102, 300-309, 410-419, 430-439, 470-479, 999, 1000-1009 + """ + if 59 < code < 80 or 169 < code < 180 or 269 < code < 290 or 369 < code < 390 or 399 < code < 410 or 1059 < code < 1071: + value = int(value) + elif 89 < code < 100 or 419 < code < 430 or 439 < code < 460 or code == 1071: + value = long(value) + elif 9 < code < 60 or 109 < code < 150 or 209 < code < 240 or 459 < code < 470 or 1009 < code < 1060: + value = float(value) + elif code == 105 or 309 < code < 380 or 389 < code < 400: + value = int(value, 16) # should be left as string? + else: # it's already a string so do nothing + pass + return value + + +def findObject(infile, kind=''): + """Finds the next occurance of an object.""" + obj = False + while 1: + line = infile.readline() + if not line: # readline returns '' at eof + return False + if not obj: # We're still looking for our object code + if line.lower().strip() == '0': + obj = True # found it + else: # we are in an object definition + if kind: # if we're looking for a particular kind + if line.lower().strip() == kind: + obj = Object(line.lower().strip()) + break + else: # otherwise take anything non-numeric + if line.lower().strip() not in string.digits: + obj = Object(line.lower().strip()) + break + obj = False # whether we found one or not it's time to start over + return obj + +def handleObject(infile): + """Add data to an object until end of object is found.""" + line = infile.readline() + if line.lower().strip() == 'section': + return 'section' # this would be a problem + elif line.lower().strip() == 'endsec': + return 'endsec' # this means we are done with a section + else: # add data to the object until we find a new object + obj = Object(line.lower().strip()) + obj.name = obj.type + done = False + data = [] + while not done: + line = infile.readline() + if not data: + if line.lower().strip() == '0': + #we've found an object, time to return + return obj + else: + # first part is always an int + data.append(int(line.lower().strip())) + else: + data.append(convert(data[0], line.strip())) + obj.data.append(data) + data = [] + +def handleTable(table, infile): + """Special handler for dealing with nested table objects.""" + item, name = get_name(table.data) + if name: # We should always find a name + table.data.remove(item) + table.name = name.lower() + # This next bit is from handleObject + # handleObject should be generalized to work with any section like object + while 1: + obj = handleObject(infile) + if obj.type == 'table': + print "Warning: previous table not closed!" + return table + elif obj.type == 'endtab': + return table # this means we are done with the table + else: # add objects to the table until one of the above is found + table.data.append(obj) + + + + +def handleBlock(block, infile): + """Special handler for dealing with nested table objects.""" + item, name = get_name(block.data) + if name: # We should always find a name + block.data.remove(item) + block.name = name.lower() + # This next bit is from handleObject + # handleObject should be generalized to work with any section like object + while 1: + obj = handleObject(infile) + if obj.type == 'block': + print "Warning: previous block not closed!" + return block + elif obj.type == 'endblk': + return block # this means we are done with the table + else: # add objects to the table until one of the above is found + block.data.append(obj) + + + + +"""These are the states/functions used in the State Machine. +states: + start - find first section + start_section - add data, find first object + object - add obj-data, watch for next obj (called directly by start_section) + end_section - look for next section or eof + end - return results +""" + +def start(cargo): + """Expects the infile as cargo, initializes the cargo.""" + #print "Entering start state!" + infile = cargo + drawing = Object('drawing') + section = findObject(infile, 'section') + if section: + return start_section, (infile, drawing, section) + else: + return error, (infile, "Failed to find any sections!") + +def start_section(cargo): + """Expects [infile, drawing, section] as cargo, builds a nested section object.""" + #print "Entering start_section state!" + infile = cargo[0] + drawing = cargo[1] + section = cargo[2] + # read each line, if it is an object declaration go to object mode + # otherwise create a [index, data] pair and add it to the sections data. + done = False + data = [] + while not done: + line = infile.readline() + + if not data: # if we haven't found a dxf code yet + if line.lower().strip() == '0': + # we've found an object + while 1: # no way out unless we find an end section or a new section + obj = handleObject(infile) + if obj == 'section': # shouldn't happen + print "Warning: failed to close previous section!" + return end_section, (infile, drawing) + elif obj == 'endsec': # This section is over, look for the next + drawing.data.append(section) + return end_section, (infile, drawing) + elif obj.type == 'table': # tables are collections of data + obj = handleTable(obj, infile) # we need to find all there contents + section.data.append(obj) # before moving on + elif obj.type == 'block': # the same is true of blocks + obj = handleBlock(obj, infile) # we need to find all there contents + section.data.append(obj) # before moving on + else: # found another sub-object + section.data.append(obj) + else: + data.append(int(line.lower().strip())) + else: # we have our code, now we just need to convert the data and add it to our list. + data.append(convert(data[0], line.strip())) + section.data.append(data) + data = [] +def end_section(cargo): + """Expects (infile, drawing) as cargo, searches for next section.""" + #print "Entering end_section state!" + infile = cargo[0] + drawing = cargo[1] + section = findObject(infile, 'section') + if section: + return start_section, (infile, drawing, section) + else: + return end, (infile, drawing) + +def end(cargo): + """Expects (infile, drawing) as cargo, called when eof has been reached.""" + #print "Entering end state!" + infile = cargo[0] + drawing = cargo[1] + #infile.close() + return drawing + +def error(cargo): + """Expects a (infile, string) as cargo, called when there is an error during processing.""" + #print "Entering error state!" + infile = cargo[0] + err = cargo[1] + infile.close() + print "There has been an error:" + print err + return False + +def readDXF(filename): + """Given a file name try to read it as a dxf file. + + Output is an object with the following structure + drawing + header + header data + classes + class data + tables + table data + blocks + block data + entities + entity data + objects + object data + where foo data is a list of sub-objects. True object data + is of the form [code, data]. +""" + infile = open(filename) + + sm = StateMachine() + sm.add_state(error, True) + sm.add_state(end, True) + sm.add_state(start_section) + sm.add_state(end_section) + sm.add_state(start) + sm.set_start(start) + try: + drawing = sm.run(infile) + if drawing: + drawing.name = filename + for obj in drawing.data: + item, name = get_name(obj.data) + if name: + obj.data.remove(item) + obj.name = name.lower() + setattr(drawing, name.lower(), obj) + # Call the objectify function from dxfImportObjects to cast + # raw objects into the right types of object + obj.data = objectify(obj.data) + #print obj.name + finally: + infile.close() + return drawing +if __name__ == "__main__": + filename = r".\examples\block-test.dxf" + drawing = readDXF(filename) + for item in drawing.entities.data: + print item + + diff --git a/release/scripts/import_dxf.py b/release/scripts/import_dxf.py new file mode 100644 index 00000000000..15bee5b3905 --- /dev/null +++ b/release/scripts/import_dxf.py @@ -0,0 +1,1005 @@ +#!BPY + +# """ +# Name: 'Drawing eXchange Format (.dxf)' +# Blender: 243 +# Group: 'Import' +# Tooltip: 'Import DXF file.' +# """ +__author__ = 'Kitsu (Ed Blake)' +__version__ = '0.8 1/2007' +__url__ = ["elysiun.com", "BlenderArtists.org"] +__email__ = ["Kitsune_e@yahoo.com"] +__bpydoc__ = """\ +This is a Blender import script for dxf files. + +This script imports the dxf Geometery from dxf versions 2007 and earlier. + +Supported:
+ At this time only mesh based imports are supported.
+ Future support for all curve import is planned.
+
+Currently Supported DXF Ojects:
+ Lines
+ LightWeight polylines
+ True polylines
+ Text
+ Mtext
+ Circles
+ Arcs
+ Ellipses
+ Blocks
+ 3Dfaces
+ +Known issues:
+ Does not convert perfectly between Object Coordinate System (OCS) + and World Coordinate System (WCS). Only rudimentary support for + true polylines have been implimented - splines/fitted curves/ + 3d plines/polymeshes are not supported. + No support for most 3d entities. Doesn't support the new style object + visability. There are problems importing some curves/arcs/circles. + +Notes:
+ This is primarally a 2d drawing release. Currently only support for + 3d faces has been added. + Blocks are created on layer 19 then referenced at each insert point. The + insert point is designated with a small 3d crosshair. This handle does not render. + +""" + +# -------------------------------------------------------------------------- +# DXF Import v0.8 by Ed Blake (AKA Kitsu) +# -------------------------------------------------------------------------- +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ***** END GPL LICENCE BLOCK ***** +# -------------------------------------------------------------------------- + +import Blender +from Blender import * +Sys = sys +try: + from dxfReader import readDXF +except ImportError: + import sys + curdir = Sys.dirname(Blender.Get('filename')) + sys.path.append(curdir) +from dxfReader import readDXF +from dxfColorMap import color_map +from math import * + +try: + import os + if os.name:# != 'mac': + import psyco + psyco.log() + psyco.full(memory=100) + psyco.profile(0.05, memory=100) + psyco.profile(0.2) +except ImportError: + pass + +SCENE = Scene.GetCurrent() +WORLDX = Mathutils.Vector((1,0,0)) +AUTO = BezTriple.HandleTypes.AUTO +BYLAYER=256 + +class Layer: + """Dummy layer object.""" + def __init__(self, name, color, frozen): + self.name = name + self.color = color + self.frozen = frozen + + +class MatColors: + """A smart container for color based materials. + + This class is a wrapper around a dictionary mapping color indicies to materials. + When called with a color index it returns a material corrisponding to that index. + Behind the scenes it checks if that index is in its keys, and if not it creates + a new material. It then adds the new index:material pair to its dict and returns + the material. + """ + + def __init__(self, map): + """Expects a dictionary mapping layer names to color idices.""" + self.map = map + self.colors = {} + + + def __call__(self, color=None): + """Return the material associated with color. + + If a layer name is provided the color of that layer is used. + """ + if not color: + color = 0 + if type(color) == str: # Layer name + try: + color = self.map[color].color # color = layer_map[name].color + except KeyError: + layer = Layer(name=color, color=0, frozen=False) + self.map[color] = layer + color = 0 + color = abs(color) + if color not in self.colors.keys(): + self.add(color) + return self.colors[color] + + + + + def add(self, color): + """Create a new material using the provided color index.""" + global color_map + mat = Material.New('ColorIndex-%s' %color) + mat.setRGBCol(color_map[color]) + mat.setMode("Shadeless", "Wire") + self.colors[color] = mat + + + + +class Blocks: + """A smart container for blocks. + + This class is a wrapper around a dictionary mapping block names to Blender data blocks. + When called with a name string it returns a block corrisponding to that name. + Behind the scenes it checks if that name is in its keys, and if not it creates + a new data block. It then adds the new name:block pair to its dict and returns + the block. + """ + + def __init__(self, map, settings): + """Expects a dictionary mapping block names to block objects.""" + self.map = map + self.settings = settings + self.blocks = {} + + + def __call__(self, name=None): + """Return the data block associated with name. + + If no name is provided return self.blocks. + """ + if not name: + return self.blocks + if name not in self.blocks.keys(): + self.add(name) + return self.blocks[name] + + + + def add(self, name): + """Create a new block group for the block with name.""" + optimization = self.settings.optimization + group = Group.New(name) + block = self.map[name] + if optimization <= 1: + print "\nDrawing %s block entities..." %name + drawEntities(block.entities, self.settings, group) + if optimization <= 1: + print "Done!" + self.blocks[name] = group + + + + + +class Settings: + """A container for all the import settings and objects used by the draw functions. + + This is like a collection of globally accessable persistant properties and functions. + """ + # Optimization constants + MIN = 0 + MID = 1 + MAX = 2 + + def __init__(self, drawing, curves): + """Given the drawing initialize all the important settings used by the draw functions.""" + self.curves = curves + self.layers = True + self.blocks = True + self.optimization = self.getOpt() + + # First sort out all the sections + sections = dict([(item.name, item) for item in drawing.data]) + + # The header section may be omited + if self.optimization <= self.MID: + if 'header' in sections.keys(): + print "Found header!" + else: + print "File contains no header!" + + # The tables section may be partialy or completely missing. + if 'tables' in sections.keys(): + if self.optimization <= self.MID: + print "Found tables!" + tables = dict([(item.name, item) for item in sections["tables"].data]) + if 'layer' in tables.keys(): + if self.optimization <= self.MID: + print "Found layers!" + # Read the layers table and get the layer colors + self.colors = getLayers(drawing) + else: + if self.optimization <= self.MID: + print "File contains no layers table!" + self.layers = False + self.colors = MatColors({}) + else: + if self.optimization <= self.MID: + print "File contains no tables!" + print "File contains no layers table!" + self.layers = False + self.colors = MatColors({}) + + # The blocks section may be omited + if 'blocks' in sections.keys(): + if self.optimization <= self.MID: + print "Found blocks!" + # Read the block definitions and build our block object + self.blocks = getBlocks(drawing, self) + else: + if self.optimization <= self.MID: + print "File contains no blocks!" + self.blocks = False + + + def getOpt(self): + """Ask the user for update optimization level.""" + Window.WaitCursor(False) + + retval = Draw.PupIntInput('optimization: ', 1, 0, 2) + print "Setting optimization level %s!" %retval + + Window.WaitCursor(True) + return retval + + + def isOff(self, name): + """Given a layer name look up the layer object and return its visable status.""" + # colors are negative if layer is off + try: + layer = self.colors.map[name] + except KeyError: + return False + + if layer.frozen or layer.color < 0: + return True + else: + return False + + + + +class Drawer: + """Super 'function' for all the entitiy drawing functions. + + The code for the drawing functions was very repetitive, each differing + by only a few lines at most. So here is a callable class with methods + for each part of the import proccess. + """ + + def __init__(self, block=False): + self.block = block + + + + def __call__(self, entities, settings, group=None): + """Call with a list of entities and a settings object to generate Blender geometry.""" + if entities and settings.optimization <= settings.MID: + print "Drawing %ss..." %entities[0].type, + + if self.block: + # create one 'handle' data block to use with all blocks + handle = Mesh.New('insert') + handle.verts.extend( + [(-0.01,0,0), + (0.01,0,0), + (0,-0.01,0), + (0,0.01,0), + (0,0,-0.01), + (0,0,0.01)] + ) + handle.edges.extend([(0,1),(2,3),(4,5)]) + + # For now we only want model-space objects + entities = [entity for entity in entities if entity.space == 0] + + if group: + block_def = True + else: + block_def = False + + for entity in entities: + if settings.optimization <= settings.MID: + print '\b.', + # First get the layer group + if not block_def: + group = self.getGroup('layer %s' %entity.layer) # add overhead just to make things a little cleaner + + if not self.block: + ob = self.draw(entity, settings.curves) + else: + ob = self.draw(entity, handle, settings) + + self.setColor(entity, ob, settings) + # Link it to the scene and add it to the correct group + SCENE.link(ob) + self.setGroup(group, ob) + + # Set the visability + if settings.isOff(entity.layer): + ob.layers = [20] + elif block_def: + ob.layers = [19] + else: + ob.layers = [i+1 for i in range(20)] + + # # Set the visability + # if settings.isOff(entity.layer) or block_def: + # ob.restrictDisplay = True + # ob.restrictRender = True + + if settings.optimization == settings.MIN: + # I know it's slow to have Blender redraw after each entity type is drawn + # But is it really slower than the progress bar? + Blender.Redraw() + if entities and settings.optimization <= settings.MID: + print "\nFinished drawing %ss!" %entities[0].type + def getGroup(self, name): + """Returns a Blender group object.""" + try: + group = Group.Get(name) + except: # What is the exception? + group = Group.New(name) + return group + def draw(self, entity): + """Dummy method to be over written in subclasses.""" + pass + + + def setColor(self, entity, ob, settings): + # Set the color + if entity.color_index == BYLAYER: + mat = settings.colors(entity.layer) + else: + mat = settings.colors(entity.color_index) + try: + ob.setMaterials([mat]) + except ValueError: + print "material error - %s!" %mat + ob.colbits = 0x01 # Set OB materials. + def setGroup(self, group, it): + try: + group.objects.link(it) + except: + group.objects.append(it) + + +def main(filename): + editmode = Window.EditMode() # are we in edit mode? If so ... + if editmode: Window.EditMode(0) # leave edit mode before + Window.WaitCursor(True) # Let the user know we are thinking + + try: + if not filename: + print "DXF import: error, no file selected. Attempting to load default file." + try: + filename = Sys.expandpath(r".\examples\big-test.dxf") + except IOError: + print "DXF import: error finding default test file, exiting..." + return None + if filename: + drawing = readDXF(filename) + drawDrawing(drawing) + finally: + # restore state even if things didn't work + Window.WaitCursor(False) + if editmode: Window.EditMode(1) # and put things back how we fond them + +def getOCS(az): + """An implimentation of the Arbitrary Axis Algorithm.""" + # world x, y, and z axis + wx = WORLDX + wy = Mathutils.Vector((0,1,0)) + wz = Mathutils.Vector((0,0,1)) + + #decide if we need to transform our coords + if az[0] == 0 and az[1] == 0: + return False + # elif abs(az[0]) < 0.0001 or abs(az[1]) < 0.0001: + # return False + az = Mathutils.Vector(az) + + cap = 0.015625 # square polar cap value (1/64.0) + if abs(az.x) < cap and abs(az.y) < cap: + ax = Mathutils.CrossVecs(wy, az) + else: + ax = Mathutils.CrossVecs(wz, az) + ax = ax.normalize() + ay = Mathutils.CrossVecs(az, ax) + ay = ay.normalize() + return ax, ay, az + +def transform(normal, obj): + """Use the calculated ocs to determine the objects location/orientation in space. + + Quote from dxf docs: + The elevation value stored with an entity and output in DXF files is a sum + of the Z-coordinate difference between the UCS XY plane and the OCS XY + plane, and the elevation value that the user specified at the time the entity + was drawn. + """ + ocs = getOCS(normal) + if ocs: + #print ocs + x, y, z = ocs + x = x.resize4D() + y = y.resize4D() + z = -z.resize4D() + x.w = 0 + y.w = 0 + z.w = 0 + o = Mathutils.Vector(obj.loc) + o = o.resize4D() + mat = Mathutils.Matrix(x, y, z, o) + obj.setMatrix(mat) + +def getLayers(drawing): + """Build a dictionary of name:color pairs for the given drawing.""" + tables = drawing.tables + for table in tables.data: + if table.name == 'layer': + layers = table + break + map = {} + for item in layers.data: + if type(item) != list and item.type == 'layer': + map[item.name] = item + colors = MatColors(map) + return colors +def getBlocks(drawing, settings): + """Build a dictionary of name:block pairs for the given drawing.""" + map = {} + for item in drawing.blocks.data: + if type(item) != list and item.type == 'block': + try: + map[item.name] = item + except KeyError: + # annon block + print "Cannot map %s - %s!" %(item.name, item) + blocks = Blocks(map, settings) + return blocks +def drawDrawing(drawing): + """Given a drawing object recreate the drawing in Blender.""" + print "Getting settings..." + # The settings object controls how dxf entities are drawn + settings = Settings(drawing, curves=False) + + if settings.optimization <= settings.MID: + print "Drawings entities..." + # Draw all the know entity types in the current scene + drawEntities(drawing.entities, settings) + + # Set the visable layers + SCENE.setLayers([i+1 for i in range(18)]) + Blender.Redraw(-1) + if settings.optimization <= settings.MID: + print "Done!" +def drawEntities(entities, settings, group=None): + """Draw every kind of thing in the entity list. + + If provided 'group' is the Blender group new entities are to be added to. + """ + for _type, drawer in type_map.items(): + # for each known type get a list of that type and call the associated draw function + drawer(entities.get_type(_type), settings, group) + + +drawLines = Drawer() +def drawLine(line, curves=False): + """Do all the specific things needed to import lines into Blender.""" + # Generate the geometery + points = line.points + edges = [[0, 1]] + + me = Mesh.New('line') # create a new mesh + + me.verts.extend(points) # add vertices to mesh + me.edges.extend(edges) # add edges to the mesh + + # Now Create an object + ob = Object.New('Mesh', 'line') # link mesh to an object + ob.link(me) + + return ob +drawLines.draw = drawLine + + +drawLWpolylines = Drawer() +def drawLWpolyline(pline, curves=False): + """Do all the specific things needed to import plines into Blender.""" + # Generate the geometery + points = [] + for i in range(len(pline.points)): + point = pline.points[i] + if not point.bulge: + points.append(point.loc) + elif point.bulge and i < len(pline.points)-1:# > 0: + center, radius, start, end = solveBulge(point, pline.points[i+1]) + #print center, radius, start, end + verts, nosense = drawArc(center, radius, start, end) + verts.pop(0) # remove first + verts.pop() #remove last + if point.bulge >= 0: + verts.reverse() + points.extend(verts) + edges = [[num, num+1] for num in range(len(points)-1)] + if pline.closed: + edges.append([len(pline.points)-1, 0]) + + me = Mesh.New('lwpline') # create a new mesh + + me.verts.extend(points) # add vertices to mesh + me.edges.extend(edges) # add edges to the mesh + + # Now Create an object + ob = Object.New('Mesh', 'lwpline') # link mesh to an object + ob.link(me) + transform(pline.extrusion, ob) + ob.LocZ = pline.elevation + + return ob +drawLWpolylines.draw = drawLWpolyline + +drawPolylines = Drawer() +def drawPolyline(pline, curves=False): + """Do all the specific things needed to import plines into Blender.""" + # Generate the geometery + points = [] + for i in range(len(pline.points)): + point = pline.points[i] + if not point.bulge: + points.append(point.loc) + elif point.bulge and i < len(pline.points)-1:# > 0: + center, radius, start, end = solveBulge(point, pline.points[i+1]) + #print center, radius, start, end + verts, nosense = drawArc(center, radius, start, end) + verts.pop(0) # remove first + verts.pop() #remove last + if point.bulge >= 0: + verts.reverse() + points.extend(verts) + edges = [[num, num+1] for num in range(len(points)-1)] + if pline.closed: + edges.append([len(pline.points)-1, 0]) + + me = Mesh.New('pline') # create a new mesh + + me.verts.extend(points) # add vertices to mesh + me.edges.extend(edges) # add edges to the mesh + + # Now Create an object + ob = Object.New('Mesh', 'pline') # link mesh to an object + ob.link(me) + transform(pline.extrusion, ob) + ob.LocZ = pline.elevation + + return ob +drawPolylines.draw = drawPolyline + + +def solveBulge(p1, p2): + """return the center, radius, start angle, and end angle given two points. + + Needs to take into account bulge sign. + negative = clockwise + positive = counter-clockwise + + to find center given two points, and arc angle + calculate radius + Cord = sqrt(start^2 + end^2) + S = (bulge*Cord)/2 + radius = ((Cord/2)^2+S^2)/2*S + angle of arc = 4*atan( bulge ) + angle from p1 to center is (180-angle)/2 + get vector pointing from p1 to p2 (p2 - p1) + normalize it and multiply by radius + rotate around p1 by angle to center point to center. + + start angle = angle between (center - p1) and worldX + end angle = start angle + angle of arc + """ + bulge = p1.bulge + p2 = Mathutils.Vector(p2.loc) + p1 = Mathutils.Vector(p1.loc) + cord = p2 - p1 # vector from p1 to p2 + clength = cord.length + s = (bulge * clength)/2 # sagitta (height) + radius = abs(((clength/2)**2 + s**2)/(2*s)) # magic formula + angle = abs(degrees(4*atan(bulge))) # theta (included angle) + delta = (180 - angle)/2 # the angle from cord to center + if bulge > 0: + delta = -delta + radial = cord.normalize() * radius # a radius length vector aligned with cord + rmat = Mathutils.RotationMatrix(delta, 3, 'Z') + center = p1 + (rmat * radial) # rotate radial by delta degrees, then add to p1 to find center + if bulge < 0: + sv = (p1 - center) # start from point 2 + else: + sv = (p2 - center) # start from point 1 + start = Mathutils.AngleBetweenVecs(sv, WORLDX) # start angle is the angle between the first leg of the section and the x axis + # The next bit is my cludge to figure out if start should be negative + rmat = Mathutils.RotationMatrix(start, 3, 'Z') + rstart = rmat * sv + if Mathutils.AngleBetweenVecs(rstart, WORLDX) < start: + start = -start + # the end angle is just 'angle' more than start angle + end = start + angle + return list(center), radius, start, end +drawTexts = Drawer() +def drawText(text, curves=False): + """Do all the specific things needed to import texts into Blender.""" + # Generate the geometery + txt = Text3d.New("text") + txt.setSize(1) + txt.setShear(text.oblique/90) + txt.setExtrudeDepth(0.5) + if text.halignment == 0: + align = Text3d.LEFT + elif text.halignment == 1: + align = Text3d.MIDDLE + elif text.halignment == 2: + align = Text3d.RIGHT + elif text.halignment == 3: + align = Text3d.FLUSH + else: + align = Text3d.MIDDLE + txt.setAlignment(align) + txt.setText(text.value) + + # Now Create an object + ob = Object.New('Text', 'text') # link mesh to an object + ob.link(txt) + + transform(text.extrusion, ob) + + # move the object center to the text location + ob.loc = tuple(text.loc) + # scale it to the text size + ob.SizeX = text.height*text.width_factor + ob.SizeY = text.height + ob.SizeZ = text.height + # and rotate it around z + ob.RotZ = radians(text.rotation) + + return ob +drawTexts.draw = drawText + +drawMtexts = Drawer() +def drawMtext(text, curves=False): + """Do all the specific things needed to import mtexts into Blender.""" + # Generate the geometery + txt = Text3d.New("mtext") + txt.setSize(1) + # Blender doesn't give access to its text object width currently + # only to the text3d's curve width... + #txt.setWidth(text.width/10) + txt.setLineSeparation(text.line_space) + txt.setExtrudeDepth(0.5) + txt.setText(text.value) + + # Now Create an object + ob = Object.New('Text', 'mtext') # link mesh to an object + ob.link(txt) + + transform(text.extrusion, ob) + + # move the object center to the text location + ob.loc = tuple(text.loc) + # scale it to the text size + ob.SizeX = text.height*text.width_factor + ob.SizeY = text.height + ob.SizeZ = text.height + # and rotate it around z + ob.RotZ = radians(text.rotation) + + return ob +drawMtexts.draw = drawMtext + + + +drawCircles = Drawer() +def drawCircle(circle, curves=False): + """Do all the specific things needed to import circles into Blender.""" + # Generate the geometery + # Now Create an object + if curves: + ob = drawCurveCircle(circle) + else: + center = circle.loc + radius = circle.radius + + circ = 2 * pi * radius + if circ < 65: # if circumfrance is too small + verts = 32 # set a fixed number of 32 verts + else: + verts = circ/.5 # figure out how many verts we need + if verts > 100: # Blender only accepts values + verts = 100 # [3:100] + + c = Mesh.Primitives.Circle(int(verts), radius*2) + + ob = Object.New('Mesh', 'circle') + ob.link(c) # link curve data with this object + + ob.loc = tuple(center) + transform(circle.extrusion, ob) + + return ob +drawCircles.draw = drawCircle + +drawArcs = Drawer() +def drawArc(arc, curves=False): + """Do all the specific things needed to import arcs into Blender.""" + # Generate the geometery + # Now Create an object + if curves: + ob = drawCurveArc(arc) + else: + center = arc.loc + radius = arc.radius + start = arc.start_angle + end = arc.end_angle + verts, edges = drawArc(None, radius, start, end) + + a = Mesh.New('arc') + + a.verts.extend(verts) # add vertices to mesh + a.edges.extend(edges) # add edges to the mesh + + ob = Object.New('Mesh', 'arc') + ob.link(a) # link curve data with this object + ob.loc = tuple(center) + ob.RotX = radians(180) + + transform(arc.extrusion, ob) + ob.size = (1,1,1) + + return ob +drawArcs.draw = drawArc + + +def drawArc(center, radius, start, end, step=0.5): + """Draw a mesh arc with the given parameters.""" + # center is currently set by object + + # if start > end: + # start = start - 360 + # if end > 360: + # end = end%360 + startmatrix = Mathutils.RotationMatrix(start, 3, "Z") + startpoint = startmatrix * Mathutils.Vector((radius, 0, 0)) + endmatrix = Mathutils.RotationMatrix(end, 3, "Z") + endpoint = endmatrix * Mathutils.Vector((radius, 0, 0)) + points = [startpoint] + + if end < start: + end +=360 + + delta = end - start + length = radians(delta) * radius + if radius < step*10: # if circumfrance is too small + pieces = int(delta/10) # set a fixed step of 10 degrees + else: + pieces = int(length/step) # figure out how many pieces we need for our arc + if pieces == 0: # stupid way to avoid a div by zero error + pieces = 1 # what would be a smarter way to fix this? + step = delta/pieces # set step so pieces * step = degrees in arc + + stepmatrix = Mathutils.RotationMatrix(step, 3, "Z") + point = Mathutils.Vector(startpoint) + for i in range(int(pieces)): + point = stepmatrix * point + points.append(point) + points.append(endpoint) + + if center: + points = [[point[0]+center[0], point[1]+center[1], point[2]+center[2]] for point in points] + edges = [[num, num+1] for num in range(len(points)-1)] + + return points, edges +drawEllipses = Drawer() +def drawEllipse(ellipse, curves=False): + """Do all the specific things needed to import ellipses into Blender.""" + # Generate the geometery + # Now Create an object + if curves: + ob = drawCurveArc(ellipse) + else: + major = Mathutils.Vector(ellipse.major) + delta = Mathutils.AngleBetweenVecs(major, WORLDX) + center = ellipse.loc + radius = major.length + start = degrees(ellipse.start_angle) + end = degrees(ellipse.end_angle) + verts, edges = drawArc(None, radius, start, end) + + e = Mesh.New('ellipse') + + e.verts.extend(verts) # add vertices to mesh + e.edges.extend(edges) # add edges to the mesh + + + ob = Object.New('Mesh', 'arc') + ob.link(e) # link curve data with this object + ob.loc = tuple(center) + ob.SizeY = ellipse.ratio + #ob.RotZ = radians(delta) + ob.RotX = radians(180) + + + transform(ellipse.extrusion, ob) + ob.RotZ = radians(delta) + + return ob +drawEllipses.draw = drawEllipse +drawBlocks = Drawer(True) +def drawBlock(insert, handle, settings): + """recursivly draw block objects. + + Blocks are made of three objects: + the block_record in the tables section + the block in the blocks section + the insert object in the entities section + + block_records give the insert units, blocks provide the objects drawn in the + block, and the insert object gives the location/scale/rotation of the block + instances. To draw a block you must first get a group with all the + blocks entities drawn in it, then scale the entities to match the world + units, then dupligroup that data to an object matching each insert object.""" + if settings.blocks: + # get our block group + block = settings.blocks(insert.block) + + # Now Create an object + ob = Object.New('Mesh', insert.block) + ob.link(handle) # Give the object a handle + ob.DupGroup = block + ob.enableDupGroup = True + else: + ob = Object.New('Mesh') + + ob.loc = tuple(insert.loc) + transform(insert.extrusion, ob) + ob.RotZ += radians(insert.rotation) + ob.size = tuple(insert.scale) + + return ob +drawBlocks.draw = drawBlock + + +drawFaces = Drawer() +def drawFace(face, curves=False): + """Do all the specific things needed to import 3d faces into Blender.""" + # Generate the geometery + points = face.points + if len(face.points) > 3: + faces = [[0, 1, 2, 3]] + else: + faces = [[0, 1, 2]] + + me = Mesh.New('line') # create a new mesh + + me.verts.extend(points) # add vertices to mesh + me.faces.extend(faces) # add faces to the mesh + + # Now Create an object + ob = Object.New('Mesh', '3dface') # link mesh to an object + ob.link(me) + + return ob +drawFaces.draw = drawFace +# Here are some alternate drawing functions for creating curve geometery. + +def drawCurveCircle(circle): + """Given a dxf circle object return a blender circle object using curves.""" + c = Curve.New('circle') # create new curve data + + center = circle.loc + radius = circle.radius + + p1 = (0, -radius, 0) + p2 = (radius, 0, 0) + p3 = (0, radius, 0) + p4 = (-radius, 0, 0) + + p1 = BezTriple.New(p1) + p2 = BezTriple.New(p2) + p3 = BezTriple.New(p3) + p4 = BezTriple.New(p4) + + curve = c.appendNurb(p1) + curve.append(p2) + curve.append(p3) + curve.append(p4) + for point in curve: + point.handleTypes = [AUTO, AUTO] + curve.flagU = 1 # Set curve cyclic + c.update() + + ob = Object.New('Curve', 'circle') # make curve object + return ob + +def drawCurveArc(arc): + """Given a dxf circle object return a blender circle object using curves.""" + if start > end: + start = start - 360 + startmatrix = Mathutils.RotationMatrix(start, 3, "Z") + startpoint = startmatrix * Mathutils.Vector((radius, 0, 0)) + endmatrix = Mathutils.RotationMatrix(end, 3, "Z") + endpoint = endmatrix * Mathutils.Vector((radius, 0, 0)) + # Note: handles must be tangent to arc and of correct length... + + a = Curve.New('arc') # create new curve data + + center = circle.loc + radius = circle.radius + + p1 = (0, -radius, 0) + p2 = (radius, 0, 0) + p3 = (0, radius, 0) + p4 = (-radius, 0, 0) + + p1 = BezTriple.New(p1) + p2 = BezTriple.New(p2) + p3 = BezTriple.New(p3) + p4 = BezTriple.New(p4) + + curve = a.appendNurb(p1) + curve.append(p2) + curve.append(p3) + curve.append(p4) + for point in curve: + point.handleTypes = [AUTO, AUTO] + curve.flagU = 1 # Set curve cyclic + a.update() + + ob = Object.New('Curve', 'arc') # make curve object + return ob + + +type_map = { + 'line':drawLines, + 'lwpolyline':drawLWpolylines, + 'polyline':drawPolylines, + 'text':drawTexts, + 'mtext':drawMtexts, + 'circle':drawCircles, + 'arc':drawArcs, + 'ellipse':drawEllipses, + 'insert':drawBlocks, + '3dface':drawFaces +} + + +if __name__ == "__main__": + Window.FileSelector(main, 'Import a DXF file', '*.dxf') -- cgit v1.2.3