dxf 0.2 → 0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.travis.yml +11 -0
- data/Gemfile +7 -1
- data/README.markdown +3 -1
- data/Rakefile +2 -0
- data/dxf.gemspec +6 -3
- data/lib/dxf.rb +10 -94
- data/lib/dxf/cluster_factory.rb +15 -0
- data/lib/dxf/entity.rb +169 -0
- data/lib/dxf/parser.rb +248 -0
- data/lib/dxf/unparser.rb +109 -0
- data/test/dxf/parser.rb +48 -0
- data/test/dxf/{builder.rb → unparser.rb} +84 -15
- data/test/fixtures/circle.dxf +24 -0
- data/test/fixtures/circle_translate.dxf +24 -0
- data/test/fixtures/spline.dxf +5098 -0
- data/test/fixtures/square_lwpolyline_inches.dxf +38 -0
- data/test/fixtures/square_lwpolyline_millimeters.dxf +38 -0
- metadata +50 -26
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2645909e4268c3367196986f86b48822419a8299
|
4
|
+
data.tar.gz: ef973d3d48e9f3cdbb0079d21e97eed21ec19706
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3a3f3a3582ba79bfc5b8d19227dae5c3ee874cb0fbe3f30c1da21cb46de86e386b51d519578b0094183202b2967cc83d78f6753462f5c8251185f300c791f26e
|
7
|
+
data.tar.gz: 7d3307bc5e85f8e66ab9d582c1511d47ff646fddebeef222daa189f92b87d802d3014ecceb072b8972a146325698717e5f6d97db72b8f7bf9d9691118b7b4fd3
|
data/.travis.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.0.0
|
4
|
+
deploy:
|
5
|
+
provider: rubygems
|
6
|
+
api_key:
|
7
|
+
secure: S0O4Y3l92qkVXey7ZqIQnRt+81UflIXtsRLOlqONmk2eJ+OLWF8PnQf2PHLuCN39xtaNRPRmjzXxq5SqIvZdJNveAWhTMR+JerW6itGEa1pDEeqtbLsZ42uzfoUdw4kuw/tr2cB8FaNujtRndbzsqRsEinfJ/zlPO606C3CdodI=
|
8
|
+
gem: dxf
|
9
|
+
on:
|
10
|
+
tags: true
|
11
|
+
repo: bfoz/dxf-ruby
|
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# DXF
|
2
2
|
|
3
|
+
[](https://travis-ci.org/bfoz/dxf-ruby)
|
4
|
+
|
3
5
|
Tools for working with the popular DXF file format
|
4
6
|
|
5
7
|
## Installation
|
@@ -28,4 +30,4 @@ DXF.write('filename.dxf', my_sketch, :inches)
|
|
28
30
|
License
|
29
31
|
-------
|
30
32
|
|
31
|
-
Copyright 2012-
|
33
|
+
Copyright 2012-2014 Brandon Fosdick <bfoz@bfoz.net> and released under the BSD license.
|
data/Rakefile
CHANGED
data/dxf.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
6
|
gem.name = "dxf"
|
7
|
-
gem.version = '0.
|
7
|
+
gem.version = '0.3'
|
8
8
|
gem.authors = ["Brandon Fosdick"]
|
9
9
|
gem.email = ["bfoz@bfoz.net"]
|
10
10
|
gem.description = %q{Read and write DXF files using Ruby}
|
@@ -16,6 +16,9 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
17
|
gem.require_paths = ["lib"]
|
18
18
|
|
19
|
-
|
20
|
-
gem.add_dependency
|
19
|
+
gem.add_dependency 'geometry', '~> 6.4'
|
20
|
+
gem.add_dependency 'sketch', '~> 0.4'
|
21
|
+
gem.add_dependency 'units', '~> 2.4'
|
22
|
+
|
23
|
+
gem.required_ruby_version = '>= 2.0'
|
21
24
|
end
|
data/lib/dxf.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'units'
|
1
|
+
require_relative 'dxf/parser'
|
2
|
+
require_relative 'dxf/unparser'
|
4
3
|
|
5
4
|
module DXF
|
6
5
|
=begin
|
@@ -9,101 +8,18 @@ Reading and writing of files using AutoCAD's {http://en.wikipedia.org/wiki/AutoC
|
|
9
8
|
{http://usa.autodesk.com/adsk/servlet/item?siteID=123112&id=12272454&linkID=10809853 DXF Specifications}
|
10
9
|
=end
|
11
10
|
|
12
|
-
class Builder
|
13
|
-
attr_accessor :container
|
14
|
-
|
15
|
-
# Initialize with a Sketch
|
16
|
-
# @param [String,Symbol] units The units to convert length values to (:inches or :millimeters)
|
17
|
-
def initialize(units=:mm)
|
18
|
-
@units = units
|
19
|
-
end
|
20
|
-
|
21
|
-
# Convert the given value to the correct units and return it as a formatted string
|
22
|
-
# @return [String]
|
23
|
-
def format_value(value)
|
24
|
-
if value.is_a? Units::Literal
|
25
|
-
"%g" % value.send("to_#{@units}".to_sym)
|
26
|
-
else
|
27
|
-
"%g" % value
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def to_s
|
32
|
-
from_sketch(container)
|
33
|
-
end
|
34
|
-
|
35
|
-
# Convert a {Geometry::Line} into an entity array
|
36
|
-
# @overload line(Line, layer=0)
|
37
|
-
# @overload line(Point, Point, layer=0)
|
38
|
-
def line(*args)
|
39
|
-
if args[0].is_a?(Geometry::Line)
|
40
|
-
first, last = args[0].first, args[0].last
|
41
|
-
layer = args[1] ||= 0
|
42
|
-
else
|
43
|
-
first = args[0]
|
44
|
-
last = args[1]
|
45
|
-
layer = args[2] ||= 0
|
46
|
-
end
|
47
|
-
first = Point[first] unless first.is_a?(Geometry::Point)
|
48
|
-
last = Point[last] unless last.is_a?(Geometry::Point)
|
49
|
-
[ 0, 'LINE',
|
50
|
-
8, layer,
|
51
|
-
10, format_value(first.x),
|
52
|
-
20, format_value(first.y),
|
53
|
-
11, format_value(last.x),
|
54
|
-
21, format_value(last.y)]
|
55
|
-
end
|
56
|
-
|
57
|
-
def section(name)
|
58
|
-
[0, 'SECTION', 2, name]
|
59
|
-
end
|
60
|
-
|
61
|
-
# Build a DXF from a Sketch
|
62
|
-
# @return [Array] Array of bytes to be written to a file
|
63
|
-
def from_sketch(sketch)
|
64
|
-
bytes = []
|
65
|
-
bytes.push section('HEADER')
|
66
|
-
bytes.push 0, 'ENDSEC'
|
67
|
-
bytes.push section('ENTITIES')
|
68
|
-
|
69
|
-
sketch.geometry.map do |element|
|
70
|
-
case element
|
71
|
-
when Geometry::Arc
|
72
|
-
bytes.push 0, 'ARC'
|
73
|
-
bytes.push 10, format_value(element.center.x)
|
74
|
-
bytes.push 20, format_value(element.center.y)
|
75
|
-
bytes.push 40, format_value(element.radius)
|
76
|
-
bytes.push 50, format_value(element.start_angle)
|
77
|
-
bytes.push 51, format_value(element.end_angle)
|
78
|
-
when Geometry::Circle
|
79
|
-
bytes.push 0, 'CIRCLE'
|
80
|
-
bytes.push 10, format_value(element.center.x)
|
81
|
-
bytes.push 20, format_value(element.center.y)
|
82
|
-
bytes.push 40, format_value(element.radius)
|
83
|
-
when Geometry::Line
|
84
|
-
bytes.push line(element.first, element.last)
|
85
|
-
when Geometry::Polyline
|
86
|
-
element.edges.map {|edge| bytes.push line(edge.first, edge.last) }
|
87
|
-
when Geometry::Rectangle
|
88
|
-
element.edges.map {|edge| bytes.push line(edge.first, edge.last) }
|
89
|
-
when Geometry::Square
|
90
|
-
points = element.points
|
91
|
-
points.each_cons(2) {|p1,p2| bytes.push line(p1,p2) }
|
92
|
-
bytes.push line(points.last, point.first)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
bytes.push 0, 'ENDSEC'
|
97
|
-
bytes.push 0, 'EOF'
|
98
|
-
bytes.join "\n"
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
11
|
# Export a {Sketch} to a DXF file
|
103
12
|
# @param [String] filename The path to write to
|
104
13
|
# @param [Sketch] sketch The {Sketch} to export
|
105
14
|
# @param [Symbol] units Convert all values to the specified units (:inches or :mm)
|
106
15
|
def self.write(filename, sketch, units=:mm)
|
107
|
-
File.
|
16
|
+
File.open(filename, 'w') {|f| Unparser.new(units).unparse(f, sketch)}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Read a DXF file
|
20
|
+
# @param [String] filename The path to the file to read
|
21
|
+
# @return [DXF] the resulting {DXF} object
|
22
|
+
def self.read(filename)
|
23
|
+
File.open(filename, 'r') {|f| DXF::Parser.new.parse(f) }
|
108
24
|
end
|
109
25
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Include this module in the base class of a class cluster to handle swizzling
|
2
|
+
# of ::new
|
3
|
+
module ClusterFactory
|
4
|
+
def self.included(parent)
|
5
|
+
class << parent
|
6
|
+
alias :original_new :new
|
7
|
+
|
8
|
+
def inherited(subclass)
|
9
|
+
class << subclass
|
10
|
+
alias :new :original_new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/dxf/entity.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'geometry'
|
2
|
+
|
3
|
+
require_relative 'cluster_factory'
|
4
|
+
|
5
|
+
module DXF
|
6
|
+
Point = Geometry::Point
|
7
|
+
|
8
|
+
# {Entity} is the base class for everything that can live in the ENTITIES block
|
9
|
+
class Entity
|
10
|
+
TypeError = Class.new(StandardError)
|
11
|
+
|
12
|
+
include ClusterFactory
|
13
|
+
|
14
|
+
attr_accessor :handle
|
15
|
+
attr_accessor :layer
|
16
|
+
|
17
|
+
def self.new(type)
|
18
|
+
case type
|
19
|
+
when 'CIRCLE' then Circle.new
|
20
|
+
when 'LINE' then Line.new
|
21
|
+
when 'SPLINE' then Spline.new
|
22
|
+
else
|
23
|
+
raise TypeError, "Unrecognized entity type '#{type}'"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_pair(code, value)
|
28
|
+
# Handle group codes that are common to all entities
|
29
|
+
# These are from the table that starts on page 70 of specification
|
30
|
+
case code
|
31
|
+
when '5'
|
32
|
+
handle = value
|
33
|
+
when '8'
|
34
|
+
layer = value
|
35
|
+
else
|
36
|
+
p "Unrecognized entity group code: #{code} #{value}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def point_from_values(*args)
|
43
|
+
Geometry::Point[args.flatten.reverse.drop_while {|a| not a }.reverse]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Circle < Entity
|
48
|
+
attr_accessor :x, :y, :z
|
49
|
+
attr_accessor :radius
|
50
|
+
|
51
|
+
def parse_pair(code, value)
|
52
|
+
case code
|
53
|
+
when '10' then self.x = value.to_f
|
54
|
+
when '20' then self.y = value.to_f
|
55
|
+
when '30' then self.z = value.to_f
|
56
|
+
when '40' then self.radius = value.to_f
|
57
|
+
else
|
58
|
+
super # Handle common and unrecognized codes
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @!attribute [r] center
|
63
|
+
# @return [Point] the composed center of the {Circle}
|
64
|
+
def center
|
65
|
+
a = [x, y, z]
|
66
|
+
a.pop until a.last
|
67
|
+
Geometry::Point[*a]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Line < Entity
|
72
|
+
attr_reader :first, :last
|
73
|
+
attr_accessor :x1, :y1, :z1
|
74
|
+
attr_accessor :x2, :y2, :z2
|
75
|
+
|
76
|
+
def parse_pair(code, value)
|
77
|
+
case code
|
78
|
+
when '10' then self.x1 = value.to_f
|
79
|
+
when '20' then self.y1 = value.to_f
|
80
|
+
when '30' then self.z1 = value.to_f
|
81
|
+
when '11' then self.x2 = value.to_f
|
82
|
+
when '21' then self.y2 = value.to_f
|
83
|
+
when '31' then self.z2 = value.to_f
|
84
|
+
else
|
85
|
+
super # Handle common and unrecognized codes
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def initialize(*args)
|
90
|
+
@first, @last = *args
|
91
|
+
end
|
92
|
+
|
93
|
+
# @!attribute [r] first
|
94
|
+
# @return [Point] the starting point of the {Line}
|
95
|
+
def first
|
96
|
+
@first ||= point_from_values(x1, y1, z1)
|
97
|
+
end
|
98
|
+
|
99
|
+
# @!attribute [r] last
|
100
|
+
# @return [Point] the end point of the {Line}
|
101
|
+
def last
|
102
|
+
@last ||= point_from_values(x2, y2, z2)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class LWPolyline < Entity
|
107
|
+
# @!attribute points
|
108
|
+
# @return [Array<Point>] The points that make up the polyline
|
109
|
+
attr_reader :points
|
110
|
+
|
111
|
+
def initialize(*points)
|
112
|
+
@points = points.map {|a| Point[a]}
|
113
|
+
end
|
114
|
+
|
115
|
+
# Return the individual line segments
|
116
|
+
def lines
|
117
|
+
points.each_cons(2).map {|a,b| Line.new a, b}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Spline < Entity
|
122
|
+
attr_reader :degree
|
123
|
+
attr_reader :knots
|
124
|
+
attr_reader :points
|
125
|
+
|
126
|
+
def initialize(degree:nil, knots:[], points:nil)
|
127
|
+
@degree = degree
|
128
|
+
@knots = knots || []
|
129
|
+
@points = points || []
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class Bezier < Spline
|
134
|
+
# @!attribute degree
|
135
|
+
# @return [Number] The degree of the curve
|
136
|
+
def degree
|
137
|
+
points.length - 1
|
138
|
+
end
|
139
|
+
|
140
|
+
# @!attribute points
|
141
|
+
# @return [Array<Point>] The control points for the Bézier curve
|
142
|
+
attr_reader :points
|
143
|
+
|
144
|
+
def initialize(*points)
|
145
|
+
@points = points.map {|v| Geometry::Point[v]}
|
146
|
+
end
|
147
|
+
|
148
|
+
# http://en.wikipedia.org/wiki/Binomial_coefficient
|
149
|
+
# http://rosettacode.org/wiki/Evaluate_binomial_coefficients#Ruby
|
150
|
+
def binomial_coefficient(k)
|
151
|
+
(0...k).inject(1) {|m,i| (m * (degree - i)) / (i + 1) }
|
152
|
+
end
|
153
|
+
|
154
|
+
# @param t [Float] the input parameter
|
155
|
+
def [](t)
|
156
|
+
return nil unless (0..1).include?(t)
|
157
|
+
result = Geometry::Point.zero(points.first.size)
|
158
|
+
points.each_with_index do |v, i|
|
159
|
+
result += v * binomial_coefficient(i) * ((1 - t) ** (degree - i)) * (t ** i)
|
160
|
+
end
|
161
|
+
result
|
162
|
+
end
|
163
|
+
|
164
|
+
# Convert the {Bezier} into the given number of line segments
|
165
|
+
def lines(count=20)
|
166
|
+
(0..1).step(1.0/count).map {|t| self[t]}.each_cons(2).map {|a,b| Line.new a, b}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
data/lib/dxf/parser.rb
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
require_relative 'entity'
|
2
|
+
|
3
|
+
module DXF
|
4
|
+
class Parser
|
5
|
+
ParseError = Class.new(StandardError)
|
6
|
+
|
7
|
+
# @!attribute entities
|
8
|
+
# @return [Array] the entities that comprise the drawing
|
9
|
+
attr_accessor :entities
|
10
|
+
|
11
|
+
# @!attribute header
|
12
|
+
# @return [Hash] the header variables
|
13
|
+
attr_accessor :header
|
14
|
+
|
15
|
+
def initialize(units=:mm)
|
16
|
+
@entities = []
|
17
|
+
@header = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse(io)
|
21
|
+
parse_pairs io do |code, value|
|
22
|
+
next if '999' == code
|
23
|
+
raise ParseError, "DXF files must begin with group code 0, not #{code}" unless '0' == code
|
24
|
+
raise ParseError, "Expecting a SECTION, not #{value}" unless 'SECTION' == value
|
25
|
+
parse_section(io)
|
26
|
+
end
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def read_pair(io)
|
33
|
+
code = io.gets.strip
|
34
|
+
value = io.gets.strip
|
35
|
+
value = case code.to_i
|
36
|
+
when 1..9
|
37
|
+
value.to_s
|
38
|
+
when 10..18, 20..28, 30..37, 40..49
|
39
|
+
value.to_f
|
40
|
+
when 50..58
|
41
|
+
value.to_f # degrees
|
42
|
+
when 70..78, 90..99, 270..289
|
43
|
+
value.to_i
|
44
|
+
else
|
45
|
+
value
|
46
|
+
end
|
47
|
+
|
48
|
+
[code, value]
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse_pairs(io, &block)
|
52
|
+
while not io.eof?
|
53
|
+
code, value = read_pair(io)
|
54
|
+
case [code, value]
|
55
|
+
when ['0', 'ENDSEC']
|
56
|
+
yield code, value # Allow the handler a chance to clean up
|
57
|
+
return
|
58
|
+
when ['0', 'EOF'] then return
|
59
|
+
else
|
60
|
+
yield code, value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_section(io)
|
66
|
+
code, value = read_pair(io)
|
67
|
+
raise ParseError, 'SECTION must be followed by a section type' unless '2' == code
|
68
|
+
|
69
|
+
case value
|
70
|
+
when 'BLOCKS' then parse_pairs(io) {|code, value|} # Ignore until implemented
|
71
|
+
when 'CLASSES' then parse_pairs(io) {|code, value|} # Ignore until implemented
|
72
|
+
when 'ENTITIES'
|
73
|
+
parse_entities(io)
|
74
|
+
when 'HEADER'
|
75
|
+
parse_header(io)
|
76
|
+
when 'OBJECTS' then parse_pairs(io) {|code, value|} # Ignore until implemented
|
77
|
+
when 'TABLES' then parse_pairs(io) {|code, value|} # Ignore until implemented
|
78
|
+
# when 'THUMBNAILIMAGE'
|
79
|
+
else
|
80
|
+
raise ParseError, "Unrecognized section type '#{value}'"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Parse the ENTITIES section
|
85
|
+
def parse_entities(io)
|
86
|
+
parser = nil
|
87
|
+
parse_pairs io do |code, value|
|
88
|
+
if 0 == code.to_i
|
89
|
+
if parser
|
90
|
+
entities.push parser.to_entity
|
91
|
+
parser = nil
|
92
|
+
end
|
93
|
+
|
94
|
+
# Nothing to do
|
95
|
+
next if 'ENDSEC' == value
|
96
|
+
|
97
|
+
if 'LWPOLYLINE' == value
|
98
|
+
parser = EntityParser.new(value)
|
99
|
+
elsif 'SPLINE' == value
|
100
|
+
parser = SplineParser.new
|
101
|
+
else
|
102
|
+
entities.push Entity.new(value)
|
103
|
+
end
|
104
|
+
elsif parser
|
105
|
+
parser.parse_pair(code.to_i, value)
|
106
|
+
else
|
107
|
+
entities.last.parse_pair(code, value)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Parse the HEADER section
|
113
|
+
def parse_header(io)
|
114
|
+
variable_name = nil
|
115
|
+
parse_pairs io do |code, value|
|
116
|
+
case code
|
117
|
+
when '0' then next
|
118
|
+
when '9'
|
119
|
+
variable_name = value
|
120
|
+
else
|
121
|
+
header[variable_name] = value
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# @group Helpers
|
127
|
+
def self.code_to_symbol(code)
|
128
|
+
case code
|
129
|
+
when 10..13 then :x
|
130
|
+
when 20..23 then :y
|
131
|
+
when 30..33 then :z
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.update_point(point, x:nil, y:nil, z:nil)
|
136
|
+
a = point ? point.to_a : []
|
137
|
+
a[0] = x if x
|
138
|
+
a[1] = y if y
|
139
|
+
a[2] = z if z
|
140
|
+
Geometry::Point[a]
|
141
|
+
end
|
142
|
+
# @endgroup
|
143
|
+
end
|
144
|
+
|
145
|
+
class EntityParser
|
146
|
+
# @!attribute points
|
147
|
+
# @return [Array] points
|
148
|
+
attr_accessor :points
|
149
|
+
|
150
|
+
attr_reader :handle
|
151
|
+
attr_reader :layer
|
152
|
+
|
153
|
+
def initialize(type_name)
|
154
|
+
@flags = nil
|
155
|
+
@points = Array.new { Point.new }
|
156
|
+
@type_name = type_name
|
157
|
+
|
158
|
+
@point_index = Hash.new {|h,k| h[k] = 0}
|
159
|
+
end
|
160
|
+
|
161
|
+
def parse_pair(code, value)
|
162
|
+
case code
|
163
|
+
when 5 then @handle = value # Fixed
|
164
|
+
when 8 then @layer = value # Fixed
|
165
|
+
when 62 then @color_number = value # Fixed
|
166
|
+
when 10, 20, 30
|
167
|
+
k = Parser.code_to_symbol(code)
|
168
|
+
i = @point_index[k]
|
169
|
+
@points[i] = Parser.update_point(@points[i], k => value)
|
170
|
+
@point_index[k] += 1
|
171
|
+
when 70 then @flags = value
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def to_entity
|
176
|
+
case @type_name
|
177
|
+
when 'LWPOLYLINE' then LWPolyline.new(*points)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class SplineParser < EntityParser
|
183
|
+
# @!attribute points
|
184
|
+
# @return [Array] points
|
185
|
+
attr_accessor :points
|
186
|
+
|
187
|
+
attr_reader :closed, :periodic, :rational, :planar, :linear
|
188
|
+
attr_reader :degree
|
189
|
+
attr_reader :knots
|
190
|
+
|
191
|
+
def initialize
|
192
|
+
super 'SPLINE'
|
193
|
+
@fit_points = []
|
194
|
+
@knots = []
|
195
|
+
@weights = []
|
196
|
+
|
197
|
+
@fit_point_index = Hash.new {|h,k| h[k] = 0}
|
198
|
+
end
|
199
|
+
|
200
|
+
def parse_pair(code, value)
|
201
|
+
case code
|
202
|
+
when 11, 21, 31
|
203
|
+
k = Parser.code_to_symbol(code)
|
204
|
+
i = @fit_point_index[k]
|
205
|
+
@fit_points[i] = Parser.update_point(@fit_points[i], k => value)
|
206
|
+
@fit_point_index[k] += 1
|
207
|
+
when 12, 22, 32 then @start_tangent = update_point(@start_tangent, Parser.code_to_symbol(code) => value)
|
208
|
+
when 13, 23, 33 then @end_tangent = update_point(@end_tangent, Parser.code_to_symbol(code) => value)
|
209
|
+
when 40 then knots.push value.to_f
|
210
|
+
when 41 then @weights.push value
|
211
|
+
when 42 then @knot_tolerance = value
|
212
|
+
when 43 then @control_tolerance = value
|
213
|
+
when 44 then @fit_tolerance = value
|
214
|
+
when 70
|
215
|
+
value = value.to_i
|
216
|
+
@closed = value[0].zero? ? nil : true
|
217
|
+
@periodic = value[1].zero? ? nil : true
|
218
|
+
@rational = value[2].zero? ? nil : true
|
219
|
+
@planar = value[3].zero? ? nil : true
|
220
|
+
@linear = value[4].zero? ? nil : true
|
221
|
+
when 71 then @degree = value
|
222
|
+
when 72 then @num_knots = value
|
223
|
+
when 73 then @num_control_points = value
|
224
|
+
when 74 then @num_fit_points = value
|
225
|
+
else
|
226
|
+
super
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def to_entity
|
231
|
+
raise ParseError, "Wrong number of control points" unless points.size == @num_control_points
|
232
|
+
|
233
|
+
# If all of the points lie in the XY plane, remove the Z component from each point
|
234
|
+
if planar && points.all? {|a| a.z.zero?}
|
235
|
+
@points.map! {|a| Geometry::Point[a[0, 2]]}
|
236
|
+
end
|
237
|
+
|
238
|
+
if knots.size == 2*points.size
|
239
|
+
# Bezier?
|
240
|
+
if knots[0,points.size].all?(&:zero?) && (knots[-points.size,points.size].uniq.size==1)
|
241
|
+
Bezier.new *points
|
242
|
+
end
|
243
|
+
else
|
244
|
+
Spline.new degree:degree, knots:knots, points:points
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|