Class: Tsf::Vector

Inherits:
Object
  • Object
show all
Defined in:
lib/tsf/vector.rb

Overview

Loads a TSF file and parses its content

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeVector

Initialize the Vector class



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/tsf/vector.rb', line 8

def initialize
  @loaded = false
  @grouped_data = nil
  @polygons = []
  @headers = []

  ## Header information. Deconstructed.
  # Material settings for the job.
  @material_group = nil
  @material_name = nil

  # Job name as set in the Trotec software.
  @job_name = nil
  @job_number = nil

  # Usualy 500 DPI.
  @resoultion = nil
  # Will be set artboard size. when creating the tsf.
  @size = nil

  ## Image Data
  # Images in TSF files are stored as a monochrome bitmap.
  # The bitmap is used to determine the engraving area.
  # Image data, if present.
  @bitmap = nil

  #####

  # Minimum required variables to be present in the TSF file.
  # CamelCase
  @min_vars = ['MaterialGroup', 'MaterialName', 'JobName', 'JobNumber', 'Resolution', 'Size']
end

Instance Attribute Details

#bitmapObject (readonly)

Returns the value of attribute bitmap.



5
6
7
# File 'lib/tsf/vector.rb', line 5

def bitmap
  @bitmap
end

#grouped_dataObject (readonly)

Returns the value of attribute grouped_data.



4
5
6
# File 'lib/tsf/vector.rb', line 4

def grouped_data
  @grouped_data
end

#headersObject (readonly)

Returns the value of attribute headers.



4
5
6
# File 'lib/tsf/vector.rb', line 4

def headers
  @headers
end

#job_nameObject (readonly)

Returns the value of attribute job_name.



5
6
7
# File 'lib/tsf/vector.rb', line 5

def job_name
  @job_name
end

#job_numberObject (readonly)

Returns the value of attribute job_number.



5
6
7
# File 'lib/tsf/vector.rb', line 5

def job_number
  @job_number
end

#loadedObject (readonly)

Returns the value of attribute loaded.



4
5
6
# File 'lib/tsf/vector.rb', line 4

def loaded
  @loaded
end

#material_groupObject (readonly)

Returns the value of attribute material_group.



5
6
7
# File 'lib/tsf/vector.rb', line 5

def material_group
  @material_group
end

#material_nameObject (readonly)

Returns the value of attribute material_name.



5
6
7
# File 'lib/tsf/vector.rb', line 5

def material_name
  @material_name
end

#polygonsObject (readonly)

Returns the value of attribute polygons.



4
5
6
# File 'lib/tsf/vector.rb', line 4

def polygons
  @polygons
end

#resolutionObject (readonly)

Returns the value of attribute resolution.



5
6
7
# File 'lib/tsf/vector.rb', line 5

def resolution
  @resolution
end

#sizeObject (readonly)

Returns the value of attribute size.



5
6
7
# File 'lib/tsf/vector.rb', line 5

def size
  @size
end

Class Method Details

.load_tsf(data) ⇒ Object

Data is the raw data from the TSF file. data = File.read(‘path/to/file.tsf’)



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/tsf/vector.rb', line 43

def self.load_tsf(data)
  vector = new
  if data.is_a?(String) 

    # Check if the data is empty
    if data.empty?
      raise ArgumentError, "Data is empty. Please provide a valid TSF file."
    end

    # Parse vector data
    vector.parse_tsf(data)
  else
    raise ArgumentError, "Expected a String, got #{data.class}"
  end
  vector
end

Instance Method Details

#get_grouped_data(data) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/tsf/vector.rb', line 103

def get_grouped_data(data)
  # Groups are delimited by the "BegGroup" and "EndGroup" tags.
  # Each group contains a list of commands or meta data.
  # The normal groups are:
  # - Header
  # - JobMeta
  # - Bitmap (optional)
  # - DrawCommands

  # Auto create nested hash when accessed.
  nested_group_and_values = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
  # Typically, we only get 2 levels of groups. But we can have more.
  current_groups = []

  data.split("\n").each do |line|
    # a line is a command and a value. ie. <JobNumber: 1234> or <DrawPolygon: 12;0;1;2;3>
    # a line can define a group where value is the group name. ie. <BegGroup: Header> <EndGroup: Header>
    # groups can have groups (max 2.)
    # groups can have multple commands.
    # a command has only one value.

    command, value = line.split(":").map(&:strip).map { |s| s.gsub(/<|>/, '') }
    
    case command
    when "BegGroup"
      # Start a new group
      current_groups << value
    when "EndGroup"
      # End the current group
      current_groups.pop
    else
      # Assign the command and value to the appropriate group
      if current_groups.any?
        current = nested_group_and_values.dig(*current_groups)
        current[command] = value
      end
    end

  end
  nested_group_and_values
end

#get_polygons(grouped_data) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/tsf/vector.rb', line 145

def get_polygons(grouped_data)
  polygons = []
  grouped_data['DrawCommands'].each do |key, value|
    polygon_string = value['DrawPolygon']
    polygon_array = polygon_string.split(";")

    polygon = {
      'id': polygon_array[0].to_i,
      'color': {
        'r': polygon_array[1].to_i,
        'g': polygon_array[2].to_i,
        'b': polygon_array[3].to_i,
      },
      'points': []
    }

    polygon[:data] = polygon_array.drop(4).map(&:to_i).each_slice(2).to_a

    polygons << polygon
  end
  polygons
end

#parse_tsf(data) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/tsf/vector.rb', line 61

def parse_tsf(data)
  # Tsf files are text formatted files that repsresnte vector paths for a Troctec laser to follow. Similar to that of Gcode.
  # TSF file use a xml-like format the prefix "BegGroup" and "EndGroup" to denote the start and end of a group of commands or meta data.
  # TSF files are structed it to 4 top level groups.
  # - Header - Contains the header information of the file.
  # - JobMeta - Contains meta data about the job, such as the name, date, and other information.
  # - Bitmap - Contains the engraving data, such as the image to be engraved.
  # - DrawCommands - Contains the vector data, such as the paths to be followed by the laser.
  #     - contains many "DrawPolygon" commands, which are the actual vector paths to be followed by the laser.
  #     - Each "DrawPolygon" command contains a list of points, which are the coordinates of the points to be followed by the laser. Delimtited by a ;.
  #     - The first 4 digits of the command contain, ID, then R,G,B color values of the Polygon.

  @grouped_data = get_grouped_data(data)

  # Check if the grouped data has the required groups
  # A TSF file is not complete if it does not have a header and draw commands.
  unless @grouped_data.key?('Header') || @grouped_data.key?('DrawCommands')
    raise ArgumentError, "Invalid TSF data. Missing required groups core groups: Header, DrawCommands"
  end

  # Check if the grouped data has the required keys
  missing_headers = @min_vars - @grouped_data['Header'].keys
  if missing_headers.any?
    raise ArgumentError, "Invalid TSF data. Missing required headers: #{missing_headers.join(', ')}"
  end


  @material_group = @grouped_data['Header']['MaterialGroup']
  @material_name = @grouped_data['Header']['MaterialName']

  @job_name = @grouped_data['Header']['JobName']
  @job_number = @grouped_data['Header']['JobNumber']

  @resolution = @grouped_data['Header']['Resolution'].to_i
  @size = @grouped_data['Header']['Size'].split(";").map(&:to_f)

  @polygons = get_polygons(@grouped_data)

  @loaded = true
  @grouped_data
end